From 27f69f5439c046abd1e738885d9811f5bc7f9400 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 15 Jun 2023 14:25:36 +1200 Subject: [PATCH 001/106] Bump version to 2023.7.0-dev --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 470f8a46e5..f07eb49b5a 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.6.0-dev" +__version__ = "2023.7.0-dev" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From a023f24a08462e1ad50070a8799b668bb0ff04a0 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:51:44 +1000 Subject: [PATCH 002/106] Add support in vbus component for Deltasol BS 2009 (#4943) --- esphome/components/vbus/__init__.py | 1 + .../components/vbus/binary_sensor/__init__.py | 52 ++++++++ .../vbus/binary_sensor/vbus_binary_sensor.cpp | 22 ++++ .../vbus/binary_sensor/vbus_binary_sensor.h | 21 +++ esphome/components/vbus/sensor/__init__.py | 121 ++++++++++++++++++ .../components/vbus/sensor/vbus_sensor.cpp | 41 ++++++ esphome/components/vbus/sensor/vbus_sensor.h | 31 +++++ 7 files changed, 289 insertions(+) diff --git a/esphome/components/vbus/__init__.py b/esphome/components/vbus/__init__.py index 70f130e23b..99a473a3dc 100644 --- a/esphome/components/vbus/__init__.py +++ b/esphome/components/vbus/__init__.py @@ -15,6 +15,7 @@ VBus = vbus_ns.class_("VBus", uart.UARTDevice, cg.Component) CONF_VBUS_ID = "vbus_id" CONF_DELTASOL_BS_PLUS = "deltasol_bs_plus" +CONF_DELTASOL_BS_2009 = "deltasol_bs_2009" CONF_DELTASOL_C = "deltasol_c" CONF_DELTASOL_CS2 = "deltasol_cs2" CONF_DELTASOL_CS_PLUS = "deltasol_cs_plus" diff --git a/esphome/components/vbus/binary_sensor/__init__.py b/esphome/components/vbus/binary_sensor/__init__.py index 9901fb2724..70fbda2d1f 100644 --- a/esphome/components/vbus/binary_sensor/__init__.py +++ b/esphome/components/vbus/binary_sensor/__init__.py @@ -18,12 +18,14 @@ from .. import ( VBus, CONF_VBUS_ID, CONF_DELTASOL_BS_PLUS, + CONF_DELTASOL_BS_2009, CONF_DELTASOL_C, CONF_DELTASOL_CS2, CONF_DELTASOL_CS_PLUS, ) DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusBSensor", cg.Component) +DeltaSol_BS_2009 = vbus_ns.class_("DeltaSolBS2009BSensor", cg.Component) DeltaSol_C = vbus_ns.class_("DeltaSolCBSensor", cg.Component) DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2BSensor", cg.Component) DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusBSensor", cg.Component) @@ -42,6 +44,7 @@ CONF_COLLECTOR_FROST = "collector_frost" CONF_TUBE_COLLECTOR = "tube_collector" CONF_RECOOLING = "recooling" CONF_HQM = "hqm" +CONF_FROST_PROTECTION_ACTIVE = "frost_protection_active" CONFIG_SCHEMA = cv.typed_schema( { @@ -87,6 +90,33 @@ CONFIG_SCHEMA = cv.typed_schema( ), } ), + CONF_DELTASOL_BS_2009: cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(DeltaSol_BS_2009), + cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus), + cv.Optional(CONF_SENSOR1_ERROR): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_SENSOR2_ERROR): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_SENSOR3_ERROR): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_SENSOR4_ERROR): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional( + CONF_FROST_PROTECTION_ACTIVE + ): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ), CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(DeltaSol_C), @@ -222,6 +252,28 @@ async def to_code(config): sens = await binary_sensor.new_binary_sensor(config[CONF_HQM]) cg.add(var.set_hqm_bsensor(sens)) + elif config[CONF_MODEL] == CONF_DELTASOL_BS_2009: + cg.add(var.set_command(0x0100)) + cg.add(var.set_source(0x427B)) + cg.add(var.set_dest(0x0010)) + if CONF_SENSOR1_ERROR in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR1_ERROR]) + cg.add(var.set_s1_error_bsensor(sens)) + if CONF_SENSOR2_ERROR in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR2_ERROR]) + cg.add(var.set_s2_error_bsensor(sens)) + if CONF_SENSOR3_ERROR in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR3_ERROR]) + cg.add(var.set_s3_error_bsensor(sens)) + if CONF_SENSOR4_ERROR in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR4_ERROR]) + cg.add(var.set_s4_error_bsensor(sens)) + if CONF_FROST_PROTECTION_ACTIVE in config: + sens = await binary_sensor.new_binary_sensor( + config[CONF_FROST_PROTECTION_ACTIVE] + ) + cg.add(var.set_frost_protection_active_bsensor(sens)) + elif config[CONF_MODEL] == CONF_DELTASOL_C: cg.add(var.set_command(0x0100)) cg.add(var.set_source(0x4212)) diff --git a/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp b/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp index 6edbae22ba..087d049a57 100644 --- a/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp +++ b/esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp @@ -50,6 +50,28 @@ void DeltaSolBSPlusBSensor::handle_message(std::vector &message) { this->hqm_bsensor_->publish_state(message[15] & 0x20); } +void DeltaSolBS2009BSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Deltasol BS 2009:"); + LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_); + LOG_BINARY_SENSOR(" ", "Sensor 2 Error", this->s2_error_bsensor_); + LOG_BINARY_SENSOR(" ", "Sensor 3 Error", this->s3_error_bsensor_); + LOG_BINARY_SENSOR(" ", "Sensor 4 Error", this->s4_error_bsensor_); + LOG_BINARY_SENSOR(" ", "Frost Protection Active", this->frost_protection_active_bsensor_); +} + +void DeltaSolBS2009BSensor::handle_message(std::vector &message) { + if (this->s1_error_bsensor_ != nullptr) + this->s1_error_bsensor_->publish_state(message[20] & 1); + if (this->s2_error_bsensor_ != nullptr) + this->s2_error_bsensor_->publish_state(message[20] & 2); + if (this->s3_error_bsensor_ != nullptr) + this->s3_error_bsensor_->publish_state(message[20] & 4); + if (this->s4_error_bsensor_ != nullptr) + this->s4_error_bsensor_->publish_state(message[20] & 8); + if (this->frost_protection_active_bsensor_ != nullptr) + this->frost_protection_active_bsensor_->publish_state(message[25] & 1); +} + void DeltaSolCBSensor::dump_config() { ESP_LOGCONFIG(TAG, "Deltasol C:"); LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_); diff --git a/esphome/components/vbus/binary_sensor/vbus_binary_sensor.h b/esphome/components/vbus/binary_sensor/vbus_binary_sensor.h index c0a823a0ab..146aa1b673 100644 --- a/esphome/components/vbus/binary_sensor/vbus_binary_sensor.h +++ b/esphome/components/vbus/binary_sensor/vbus_binary_sensor.h @@ -39,6 +39,27 @@ class DeltaSolBSPlusBSensor : public VBusListener, public Component { void handle_message(std::vector &message) override; }; +class DeltaSolBS2009BSensor : public VBusListener, public Component { + public: + void dump_config() override; + void set_s1_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s1_error_bsensor_ = bsensor; } + void set_s2_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s2_error_bsensor_ = bsensor; } + void set_s3_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s3_error_bsensor_ = bsensor; } + void set_s4_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s4_error_bsensor_ = bsensor; } + void set_frost_protection_active_bsensor(binary_sensor::BinarySensor *bsensor) { + this->frost_protection_active_bsensor_ = bsensor; + } + + protected: + binary_sensor::BinarySensor *s1_error_bsensor_{nullptr}; + binary_sensor::BinarySensor *s2_error_bsensor_{nullptr}; + binary_sensor::BinarySensor *s3_error_bsensor_{nullptr}; + binary_sensor::BinarySensor *s4_error_bsensor_{nullptr}; + binary_sensor::BinarySensor *frost_protection_active_bsensor_{nullptr}; + + void handle_message(std::vector &message) override; +}; + class DeltaSolCBSensor : public VBusListener, public Component { public: void dump_config() override; diff --git a/esphome/components/vbus/sensor/__init__.py b/esphome/components/vbus/sensor/__init__.py index bce28758ce..2ad9da424e 100644 --- a/esphome/components/vbus/sensor/__init__.py +++ b/esphome/components/vbus/sensor/__init__.py @@ -33,12 +33,14 @@ from .. import ( VBus, CONF_VBUS_ID, CONF_DELTASOL_BS_PLUS, + CONF_DELTASOL_BS_2009, CONF_DELTASOL_C, CONF_DELTASOL_CS2, CONF_DELTASOL_CS_PLUS, ) DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusSensor", cg.Component) +DeltaSol_BS_2009 = vbus_ns.class_("DeltaSolBS2009Sensor", cg.Component) DeltaSol_C = vbus_ns.class_("DeltaSolCSensor", cg.Component) DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2Sensor", cg.Component) DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusSensor", cg.Component) @@ -142,6 +144,87 @@ CONFIG_SCHEMA = cv.typed_schema( ), } ), + CONF_DELTASOL_BS_2009: cv.COMPONENT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(DeltaSol_BS_2009), + cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus), + cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_3): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_4): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PUMP_SPEED_1): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PUMP_SPEED_2): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_OPERATING_HOURS_1): sensor.sensor_schema( + unit_of_measurement=UNIT_HOUR, + icon=ICON_TIMER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_OPERATING_HOURS_2): sensor.sensor_schema( + unit_of_measurement=UNIT_HOUR, + icon=ICON_TIMER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HEAT_QUANTITY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + icon=ICON_RADIATOR, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TIME): sensor.sensor_schema( + unit_of_measurement=UNIT_MINUTE, + icon=ICON_TIMER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_DURATION, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_VERSION): sensor.sensor_schema( + accuracy_decimals=2, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ), CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(DeltaSol_C), @@ -437,6 +520,44 @@ async def to_code(config): sens = await sensor.new_sensor(config[CONF_VERSION]) cg.add(var.set_version_sensor(sens)) + elif config[CONF_MODEL] == CONF_DELTASOL_BS_2009: + cg.add(var.set_command(0x0100)) + cg.add(var.set_source(0x427B)) + cg.add(var.set_dest(0x0010)) + if CONF_TEMPERATURE_1 in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE_1]) + cg.add(var.set_temperature1_sensor(sens)) + if CONF_TEMPERATURE_2 in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE_2]) + cg.add(var.set_temperature2_sensor(sens)) + if CONF_TEMPERATURE_3 in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE_3]) + cg.add(var.set_temperature3_sensor(sens)) + if CONF_TEMPERATURE_4 in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE_4]) + cg.add(var.set_temperature4_sensor(sens)) + if CONF_PUMP_SPEED_1 in config: + sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_1]) + cg.add(var.set_pump_speed1_sensor(sens)) + if CONF_PUMP_SPEED_2 in config: + sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_2]) + cg.add(var.set_pump_speed2_sensor(sens)) + if CONF_OPERATING_HOURS_1 in config: + sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_1]) + cg.add(var.set_operating_hours1_sensor(sens)) + if CONF_OPERATING_HOURS_2 in config: + sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_2]) + cg.add(var.set_operating_hours2_sensor(sens)) + if CONF_HEAT_QUANTITY in config: + sens = await sensor.new_sensor(config[CONF_HEAT_QUANTITY]) + cg.add(var.set_heat_quantity_sensor(sens)) + if CONF_TIME in config: + sens = await sensor.new_sensor(config[CONF_TIME]) + cg.add(var.set_time_sensor(sens)) + if CONF_VERSION in config: + sens = await sensor.new_sensor(config[CONF_VERSION]) + cg.add(var.set_version_sensor(sens)) + elif config[CONF_MODEL] == CONF_DELTASOL_C: cg.add(var.set_command(0x0100)) cg.add(var.set_source(0x4212)) diff --git a/esphome/components/vbus/sensor/vbus_sensor.cpp b/esphome/components/vbus/sensor/vbus_sensor.cpp index 8261773431..5644f707d0 100644 --- a/esphome/components/vbus/sensor/vbus_sensor.cpp +++ b/esphome/components/vbus/sensor/vbus_sensor.cpp @@ -57,6 +57,47 @@ void DeltaSolBSPlusSensor::handle_message(std::vector &message) { this->version_sensor_->publish_state(get_u16(message, 26) * 0.01f); } +void DeltaSolBS2009Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "Deltasol BS 2009:"); + LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_); + LOG_SENSOR(" ", "Temperature 2", this->temperature2_sensor_); + LOG_SENSOR(" ", "Temperature 3", this->temperature3_sensor_); + LOG_SENSOR(" ", "Temperature 4", this->temperature4_sensor_); + LOG_SENSOR(" ", "Pump Speed 1", this->pump_speed1_sensor_); + LOG_SENSOR(" ", "Pump Speed 2", this->pump_speed2_sensor_); + LOG_SENSOR(" ", "Operating Hours 1", this->operating_hours1_sensor_); + LOG_SENSOR(" ", "Operating Hours 2", this->operating_hours2_sensor_); + LOG_SENSOR(" ", "Heat Quantity", this->heat_quantity_sensor_); + LOG_SENSOR(" ", "System Time", this->time_sensor_); + LOG_SENSOR(" ", "FW Version", this->version_sensor_); +} + +void DeltaSolBS2009Sensor::handle_message(std::vector &message) { + if (this->temperature1_sensor_ != nullptr) + this->temperature1_sensor_->publish_state(get_i16(message, 0) * 0.1f); + if (this->temperature2_sensor_ != nullptr) + this->temperature2_sensor_->publish_state(get_i16(message, 2) * 0.1f); + if (this->temperature3_sensor_ != nullptr) + this->temperature3_sensor_->publish_state(get_i16(message, 4) * 0.1f); + if (this->temperature4_sensor_ != nullptr) + this->temperature4_sensor_->publish_state(get_i16(message, 6) * 0.1f); + if (this->pump_speed1_sensor_ != nullptr) + this->pump_speed1_sensor_->publish_state(message[8]); + if (this->pump_speed2_sensor_ != nullptr) + this->pump_speed2_sensor_->publish_state(message[12]); + if (this->operating_hours1_sensor_ != nullptr) + this->operating_hours1_sensor_->publish_state(get_u16(message, 10)); + if (this->operating_hours2_sensor_ != nullptr) + this->operating_hours2_sensor_->publish_state(get_u16(message, 18)); + if (this->heat_quantity_sensor_ != nullptr) { + this->heat_quantity_sensor_->publish_state(get_u16(message, 28) + get_u16(message, 30) * 1000); + } + if (this->time_sensor_ != nullptr) + this->time_sensor_->publish_state(get_u16(message, 22)); + if (this->version_sensor_ != nullptr) + this->version_sensor_->publish_state(get_u16(message, 32) * 0.01f); +} + void DeltaSolCSensor::dump_config() { ESP_LOGCONFIG(TAG, "Deltasol C:"); LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_); diff --git a/esphome/components/vbus/sensor/vbus_sensor.h b/esphome/components/vbus/sensor/vbus_sensor.h index 6ba752b68c..d5535b2019 100644 --- a/esphome/components/vbus/sensor/vbus_sensor.h +++ b/esphome/components/vbus/sensor/vbus_sensor.h @@ -37,6 +37,37 @@ class DeltaSolBSPlusSensor : public VBusListener, public Component { void handle_message(std::vector &message) override; }; +class DeltaSolBS2009Sensor : public VBusListener, public Component { + public: + void dump_config() override; + void set_temperature1_sensor(sensor::Sensor *sensor) { this->temperature1_sensor_ = sensor; } + void set_temperature2_sensor(sensor::Sensor *sensor) { this->temperature2_sensor_ = sensor; } + void set_temperature3_sensor(sensor::Sensor *sensor) { this->temperature3_sensor_ = sensor; } + void set_temperature4_sensor(sensor::Sensor *sensor) { this->temperature4_sensor_ = sensor; } + void set_pump_speed1_sensor(sensor::Sensor *sensor) { this->pump_speed1_sensor_ = sensor; } + void set_pump_speed2_sensor(sensor::Sensor *sensor) { this->pump_speed2_sensor_ = sensor; } + void set_operating_hours1_sensor(sensor::Sensor *sensor) { this->operating_hours1_sensor_ = sensor; } + void set_operating_hours2_sensor(sensor::Sensor *sensor) { this->operating_hours2_sensor_ = sensor; } + void set_heat_quantity_sensor(sensor::Sensor *sensor) { this->heat_quantity_sensor_ = sensor; } + void set_time_sensor(sensor::Sensor *sensor) { this->time_sensor_ = sensor; } + void set_version_sensor(sensor::Sensor *sensor) { this->version_sensor_ = sensor; } + + protected: + sensor::Sensor *temperature1_sensor_{nullptr}; + sensor::Sensor *temperature2_sensor_{nullptr}; + sensor::Sensor *temperature3_sensor_{nullptr}; + sensor::Sensor *temperature4_sensor_{nullptr}; + sensor::Sensor *pump_speed1_sensor_{nullptr}; + sensor::Sensor *pump_speed2_sensor_{nullptr}; + sensor::Sensor *operating_hours1_sensor_{nullptr}; + sensor::Sensor *operating_hours2_sensor_{nullptr}; + sensor::Sensor *heat_quantity_sensor_{nullptr}; + sensor::Sensor *time_sensor_{nullptr}; + sensor::Sensor *version_sensor_{nullptr}; + + void handle_message(std::vector &message) override; +}; + class DeltaSolCSensor : public VBusListener, public Component { public: void dump_config() override; From 467e42d8aa5c1dd8a6ec13b09075117d2f209e6a Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 15 Jun 2023 01:05:28 -0700 Subject: [PATCH 003/106] fix vbus sensor offsets (#4952) --- esphome/components/vbus/sensor/vbus_sensor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/vbus/sensor/vbus_sensor.cpp b/esphome/components/vbus/sensor/vbus_sensor.cpp index 5644f707d0..5b4f57f73d 100644 --- a/esphome/components/vbus/sensor/vbus_sensor.cpp +++ b/esphome/components/vbus/sensor/vbus_sensor.cpp @@ -207,9 +207,9 @@ void DeltaSolCSPlusSensor::handle_message(std::vector &message) { if (this->heat_quantity_sensor_ != nullptr) this->heat_quantity_sensor_->publish_state((get_u16(message, 30) << 16) + get_u16(message, 28)); if (this->time_sensor_ != nullptr) - this->time_sensor_->publish_state(get_u16(message, 12)); + this->time_sensor_->publish_state(get_u16(message, 22)); if (this->version_sensor_ != nullptr) - this->version_sensor_->publish_state(get_u16(message, 26) * 0.01f); + this->version_sensor_->publish_state(get_u16(message, 32) * 0.01f); if (this->flow_rate_sensor_ != nullptr) this->flow_rate_sensor_->publish_state(get_u16(message, 38)); } From 17fed954bf74a018f527b541dab2d30101169bb8 Mon Sep 17 00:00:00 2001 From: guillempages Date: Fri, 16 Jun 2023 01:39:50 +0200 Subject: [PATCH 004/106] Add support for ESP32-S3-BOX-Lite displays (#4941) --- esphome/components/ili9xxx/display.py | 1 + .../components/ili9xxx/ili9xxx_display.cpp | 12 ++++++++ esphome/components/ili9xxx/ili9xxx_display.h | 5 ++++ esphome/components/ili9xxx/ili9xxx_init.h | 30 +++++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 780c64ec70..98edadb6a5 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -44,6 +44,7 @@ MODELS = { "ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), "ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), "ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), + "S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI), } COLOR_PALETTE = cv.one_of("NONE", "GRAYSCALE", "IMAGE_ADAPTIVE") diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 67d643fe31..ad70bd6e48 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -421,5 +421,17 @@ void ILI9XXXST7796::initialize() { } } +// 24_TFT rotated display +void ILI9XXXS3BoxLite::initialize() { + this->init_lcd_(INITCMD_S3BOXLITE); + if (this->width_ == 0) { + this->width_ = 320; + } + if (this->height_ == 0) { + this->height_ = 240; + } + this->invert_display_(true); +} + } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 8a8cd4bb44..dbdf023bc0 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -134,5 +134,10 @@ class ILI9XXXST7796 : public ILI9XXXDisplay { void initialize() override; }; +class ILI9XXXS3BoxLite : public ILI9XXXDisplay { + protected: + void initialize() override; +}; + } // namespace ili9xxx } // namespace esphome diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index 593b9a79ce..36cc30ec2e 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -169,6 +169,36 @@ static const uint8_t PROGMEM INITCMD_ST7796[] = { 0x00 // End of list }; +static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = { + 0xEF, 3, 0x03, 0x80, 0x02, + 0xCF, 3, 0x00, 0xC1, 0x30, + 0xED, 4, 0x64, 0x03, 0x12, 0x81, + 0xE8, 3, 0x85, 0x00, 0x78, + 0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, + 0xF7, 1, 0x20, + 0xEA, 2, 0x00, 0x00, + ILI9XXX_PWCTR1 , 1, 0x23, // Power control VRH[5:0] + ILI9XXX_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0] + ILI9XXX_VMCTR1 , 2, 0x3e, 0x28, // VCM control + ILI9XXX_VMCTR2 , 1, 0x86, // VCM control2 + ILI9XXX_MADCTL , 1, 0x40, // Memory Access Control + ILI9XXX_VSCRSADD, 1, 0x00, // Vertical scroll zero + ILI9XXX_PIXFMT , 1, 0x55, + ILI9XXX_FRMCTR1 , 2, 0x00, 0x18, + ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control + 0xF2, 1, 0x00, // 3Gamma Function Disable + ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected + ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, + 0x0E, 0x09, 0x00, + ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, + 0x31, 0x36, 0x0F, + ILI9XXX_SLPOUT , 0x80, // Exit Sleep + ILI9XXX_DISPON , 0x80, // Display on + 0x00 // End of list +}; + // clang-format on } // namespace ili9xxx } // namespace esphome From ffa669899aa9d4b5892bc3cec18c4f39031a77e6 Mon Sep 17 00:00:00 2001 From: guillempages Date: Sat, 17 Jun 2023 10:32:07 +0200 Subject: [PATCH 005/106] Split display_buffer sub-components into own files (#4950) * Split display_buffer sub-components into own files Move the Image, Animation and Font classes to their own h/cpp pairs, instead of having everything into the display_buffer h/cpp files. * Fixed COLOR_ON duplicate definition --- esphome/components/display/animation.cpp | 69 ++++ esphome/components/display/animation.h | 37 +++ esphome/components/display/display_buffer.cpp | 303 +----------------- esphome/components/display/display_buffer.h | 136 +------- esphome/components/display/font.cpp | 105 ++++++ esphome/components/display/font.h | 66 ++++ esphome/components/display/image.cpp | 135 ++++++++ esphome/components/display/image.h | 75 +++++ tests/test2.yaml | 7 + 9 files changed, 502 insertions(+), 431 deletions(-) create mode 100644 esphome/components/display/animation.cpp create mode 100644 esphome/components/display/animation.h create mode 100644 esphome/components/display/font.cpp create mode 100644 esphome/components/display/font.h create mode 100644 esphome/components/display/image.cpp create mode 100644 esphome/components/display/image.h diff --git a/esphome/components/display/animation.cpp b/esphome/components/display/animation.cpp new file mode 100644 index 0000000000..d68084b68d --- /dev/null +++ b/esphome/components/display/animation.cpp @@ -0,0 +1,69 @@ +#include "animation.h" + +#include "esphome/core/hal.h" + +namespace esphome { +namespace display { + +Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) + : Image(data_start, width, height, type), + animation_data_start_(data_start), + current_frame_(0), + animation_frame_count_(animation_frame_count), + loop_start_frame_(0), + loop_end_frame_(animation_frame_count_), + loop_count_(0), + loop_current_iteration_(1) {} +void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) { + loop_start_frame_ = std::min(start_frame, animation_frame_count_); + loop_end_frame_ = std::min(end_frame, animation_frame_count_); + loop_count_ = count; + loop_current_iteration_ = 1; +} + +uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; } +int Animation::get_current_frame() const { return this->current_frame_; } +void Animation::next_frame() { + this->current_frame_++; + if (loop_count_ && this->current_frame_ == loop_end_frame_ && + (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { + this->current_frame_ = loop_start_frame_; + this->loop_current_iteration_++; + } + if (this->current_frame_ >= animation_frame_count_) { + this->loop_current_iteration_ = 1; + this->current_frame_ = 0; + } + + this->update_data_start_(); +} +void Animation::prev_frame() { + this->current_frame_--; + if (this->current_frame_ < 0) { + this->current_frame_ = this->animation_frame_count_ - 1; + } + + this->update_data_start_(); +} + +void Animation::set_frame(int frame) { + unsigned abs_frame = abs(frame); + + if (abs_frame < this->animation_frame_count_) { + if (frame >= 0) { + this->current_frame_ = frame; + } else { + this->current_frame_ = this->animation_frame_count_ - abs_frame; + } + } + + this->update_data_start_(); +} + +void Animation::update_data_start_() { + const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_; + this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; +} + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/animation.h b/esphome/components/display/animation.h new file mode 100644 index 0000000000..38e632ccf0 --- /dev/null +++ b/esphome/components/display/animation.h @@ -0,0 +1,37 @@ +#pragma once +#include "image.h" + +namespace esphome { +namespace display { + +class Animation : public Image { + public: + Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); + + uint32_t get_animation_frame_count() const; + int get_current_frame() const; + void next_frame(); + void prev_frame(); + + /** Selects a specific frame within the animation. + * + * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame. + */ + void set_frame(int frame); + + void set_loop(uint32_t start_frame, uint32_t end_frame, int count); + + protected: + void update_data_start_(); + + const uint8_t *animation_data_start_; + int current_frame_; + uint32_t animation_frame_count_; + uint32_t loop_start_frame_; + uint32_t loop_end_frame_; + int loop_count_; + int loop_current_iteration_; +}; + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index c8dc7b62e2..8092b9f12f 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -7,6 +7,10 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" +#include "animation.h" +#include "image.h" +#include "font.h" + namespace esphome { namespace display { @@ -15,25 +19,6 @@ static const char *const TAG = "display"; const Color COLOR_OFF(0, 0, 0, 255); const Color COLOR_ON(255, 255, 255, 255); -static int image_type_to_bpp(ImageType type) { - switch (type) { - case IMAGE_TYPE_BINARY: - return 1; - case IMAGE_TYPE_GRAYSCALE: - return 8; - case IMAGE_TYPE_RGB565: - return 16; - case IMAGE_TYPE_RGB24: - return 24; - case IMAGE_TYPE_RGBA: - return 32; - default: - return 0; - } -} - -static int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; } - void Rect::expand(int16_t horizontal, int16_t vertical) { if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) { this->x = this->x - horizontal; @@ -505,286 +490,6 @@ Rect DisplayBuffer::get_clipping() { return this->clipping_rectangle_.back(); } } -bool Glyph::get_pixel(int x, int y) const { - const int x_data = x - this->glyph_data_->offset_x; - const int y_data = y - this->glyph_data_->offset_y; - if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height) - return false; - const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; - const uint32_t pos = x_data + y_data * width_8; - return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); -} -const char *Glyph::get_char() const { return this->glyph_data_->a_char; } -bool Glyph::compare_to(const char *str) const { - // 1 -> this->char_ - // 2 -> str - for (uint32_t i = 0;; i++) { - if (this->glyph_data_->a_char[i] == '\0') - return true; - if (str[i] == '\0') - return false; - if (this->glyph_data_->a_char[i] > str[i]) - return false; - if (this->glyph_data_->a_char[i] < str[i]) - return true; - } - // this should not happen - return false; -} -int Glyph::match_length(const char *str) const { - for (uint32_t i = 0;; i++) { - if (this->glyph_data_->a_char[i] == '\0') - return i; - if (str[i] != this->glyph_data_->a_char[i]) - return 0; - } - // this should not happen - return 0; -} -void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { - *x1 = this->glyph_data_->offset_x; - *y1 = this->glyph_data_->offset_y; - *width = this->glyph_data_->width; - *height = this->glyph_data_->height; -} -int Font::match_next_glyph(const char *str, int *match_length) { - int lo = 0; - int hi = this->glyphs_.size() - 1; - while (lo != hi) { - int mid = (lo + hi + 1) / 2; - if (this->glyphs_[mid].compare_to(str)) { - lo = mid; - } else { - hi = mid - 1; - } - } - *match_length = this->glyphs_[lo].match_length(str); - if (*match_length <= 0) - return -1; - return lo; -} -void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { - *baseline = this->baseline_; - *height = this->height_; - int i = 0; - int min_x = 0; - bool has_char = false; - int x = 0; - while (str[i] != '\0') { - int match_length; - int glyph_n = this->match_next_glyph(str + i, &match_length); - if (glyph_n < 0) { - // Unknown char, skip - if (!this->get_glyphs().empty()) - x += this->get_glyphs()[0].glyph_data_->width; - i++; - continue; - } - - const Glyph &glyph = this->glyphs_[glyph_n]; - if (!has_char) { - min_x = glyph.glyph_data_->offset_x; - } else { - min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); - } - x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; - - i += match_length; - has_char = true; - } - *x_offset = min_x; - *width = x - min_x; -} -Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { - glyphs_.reserve(data_nr); - for (int i = 0; i < data_nr; ++i) - glyphs_.emplace_back(&data[i]); -} - -void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) { - switch (type_) { - case IMAGE_TYPE_BINARY: { - for (int img_x = 0; img_x < width_; img_x++) { - for (int img_y = 0; img_y < height_; img_y++) { - if (this->get_binary_pixel_(img_x, img_y)) { - display->draw_pixel_at(x + img_x, y + img_y, color_on); - } else if (!this->transparent_) { - display->draw_pixel_at(x + img_x, y + img_y, color_off); - } - } - } - break; - } - case IMAGE_TYPE_GRAYSCALE: - for (int img_x = 0; img_x < width_; img_x++) { - for (int img_y = 0; img_y < height_; img_y++) { - auto color = this->get_grayscale_pixel_(img_x, img_y); - if (color.w >= 0x80) { - display->draw_pixel_at(x + img_x, y + img_y, color); - } - } - } - break; - case IMAGE_TYPE_RGB565: - for (int img_x = 0; img_x < width_; img_x++) { - for (int img_y = 0; img_y < height_; img_y++) { - auto color = this->get_rgb565_pixel_(img_x, img_y); - if (color.w >= 0x80) { - display->draw_pixel_at(x + img_x, y + img_y, color); - } - } - } - break; - case IMAGE_TYPE_RGB24: - for (int img_x = 0; img_x < width_; img_x++) { - for (int img_y = 0; img_y < height_; img_y++) { - auto color = this->get_rgb24_pixel_(img_x, img_y); - if (color.w >= 0x80) { - display->draw_pixel_at(x + img_x, y + img_y, color); - } - } - } - break; - case IMAGE_TYPE_RGBA: - for (int img_x = 0; img_x < width_; img_x++) { - for (int img_y = 0; img_y < height_; img_y++) { - auto color = this->get_rgba_pixel_(img_x, img_y); - if (color.w >= 0x80) { - display->draw_pixel_at(x + img_x, y + img_y, color); - } - } - } - break; - } -} -Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const { - if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) - return color_off; - switch (this->type_) { - case IMAGE_TYPE_BINARY: - return this->get_binary_pixel_(x, y) ? color_on : color_off; - case IMAGE_TYPE_GRAYSCALE: - return this->get_grayscale_pixel_(x, y); - case IMAGE_TYPE_RGB565: - return this->get_rgb565_pixel_(x, y); - case IMAGE_TYPE_RGB24: - return this->get_rgb24_pixel_(x, y); - case IMAGE_TYPE_RGBA: - return this->get_rgba_pixel_(x, y); - default: - return color_off; - } -} -bool Image::get_binary_pixel_(int x, int y) const { - const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; - const uint32_t pos = x + y * width_8; - return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); -} -Color Image::get_rgba_pixel_(int x, int y) const { - const uint32_t pos = (x + y * this->width_) * 4; - return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), - progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3)); -} -Color Image::get_rgb24_pixel_(int x, int y) const { - const uint32_t pos = (x + y * this->width_) * 3; - Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), - progmem_read_byte(this->data_start_ + pos + 2)); - if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) { - // (0, 0, 1) has been defined as transparent color for non-alpha images. - // putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if) - color.w = 0; - } else { - color.w = 0xFF; - } - return color; -} -Color Image::get_rgb565_pixel_(int x, int y) const { - const uint32_t pos = (x + y * this->width_) * 2; - uint16_t rgb565 = - progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); - auto r = (rgb565 & 0xF800) >> 11; - auto g = (rgb565 & 0x07E0) >> 5; - auto b = rgb565 & 0x001F; - Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); - if (rgb565 == 0x0020 && transparent_) { - // darkest green has been defined as transparent color for transparent RGB565 images. - color.w = 0; - } else { - color.w = 0xFF; - } - return color; -} -Color Image::get_grayscale_pixel_(int x, int y) const { - const uint32_t pos = (x + y * this->width_); - const uint8_t gray = progmem_read_byte(this->data_start_ + pos); - uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF; - return Color(gray, gray, gray, alpha); -} -int Image::get_width() const { return this->width_; } -int Image::get_height() const { return this->height_; } -ImageType Image::get_type() const { return this->type_; } -Image::Image(const uint8_t *data_start, int width, int height, ImageType type) - : width_(width), height_(height), type_(type), data_start_(data_start) {} - -Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) - : Image(data_start, width, height, type), - animation_data_start_(data_start), - current_frame_(0), - animation_frame_count_(animation_frame_count), - loop_start_frame_(0), - loop_end_frame_(animation_frame_count_), - loop_count_(0), - loop_current_iteration_(1) {} -void Animation::set_loop(uint32_t start_frame, uint32_t end_frame, int count) { - loop_start_frame_ = std::min(start_frame, animation_frame_count_); - loop_end_frame_ = std::min(end_frame, animation_frame_count_); - loop_count_ = count; - loop_current_iteration_ = 1; -} - -uint32_t Animation::get_animation_frame_count() const { return this->animation_frame_count_; } -int Animation::get_current_frame() const { return this->current_frame_; } -void Animation::next_frame() { - this->current_frame_++; - if (loop_count_ && this->current_frame_ == loop_end_frame_ && - (this->loop_current_iteration_ < loop_count_ || loop_count_ < 0)) { - this->current_frame_ = loop_start_frame_; - this->loop_current_iteration_++; - } - if (this->current_frame_ >= animation_frame_count_) { - this->loop_current_iteration_ = 1; - this->current_frame_ = 0; - } - - this->update_data_start_(); -} -void Animation::prev_frame() { - this->current_frame_--; - if (this->current_frame_ < 0) { - this->current_frame_ = this->animation_frame_count_ - 1; - } - - this->update_data_start_(); -} - -void Animation::set_frame(int frame) { - unsigned abs_frame = abs(frame); - - if (abs_frame < this->animation_frame_count_) { - if (frame >= 0) { - this->current_frame_ = frame; - } else { - this->current_frame_ = this->animation_frame_count_ - abs_frame; - } - } - - this->update_data_start_(); -} - -void Animation::update_data_start_() { - const uint32_t image_size = image_type_to_width_stride(this->width_, this->type_) * this->height_; - this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; -} DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} void DisplayPage::show() { this->parent_->show_page(this); } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 0c31ac24d9..b66ec529f7 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -16,6 +16,10 @@ #include "esphome/components/qr_code/qr_code.h" #endif +#include "animation.h" +#include "font.h" +#include "image.h" + namespace esphome { namespace display { @@ -70,19 +74,6 @@ enum class TextAlign { BOTTOM_RIGHT = BOTTOM | RIGHT, }; -/// Turn the pixel OFF. -extern const Color COLOR_OFF; -/// Turn the pixel ON. -extern const Color COLOR_ON; - -enum ImageType { - IMAGE_TYPE_BINARY = 0, - IMAGE_TYPE_GRAYSCALE = 1, - IMAGE_TYPE_RGB24 = 2, - IMAGE_TYPE_RGB565 = 3, - IMAGE_TYPE_RGBA = 4, -}; - enum DisplayType { DISPLAY_TYPE_BINARY = 1, DISPLAY_TYPE_GRAYSCALE = 2, @@ -123,8 +114,6 @@ class Rect { void info(const std::string &prefix = "rect info:"); }; -class BaseImage; -class Font; class DisplayBuffer; class DisplayPage; class DisplayOnPageChangeTrigger; @@ -475,123 +464,6 @@ class DisplayPage { DisplayPage *next_{nullptr}; }; -struct GlyphData { - const char *a_char; - const uint8_t *data; - int offset_x; - int offset_y; - int width; - int height; -}; - -class Glyph { - public: - Glyph(const GlyphData *data) : glyph_data_(data) {} - - bool get_pixel(int x, int y) const; - - const char *get_char() const; - - bool compare_to(const char *str) const; - - int match_length(const char *str) const; - - void scan_area(int *x1, int *y1, int *width, int *height) const; - - protected: - friend Font; - friend DisplayBuffer; - - const GlyphData *glyph_data_; -}; - -class Font { - public: - /** Construct the font with the given glyphs. - * - * @param glyphs A vector of glyphs, must be sorted lexicographically. - * @param baseline The y-offset from the top of the text to the baseline. - * @param bottom The y-offset from the top of the text to the bottom (i.e. height). - */ - Font(const GlyphData *data, int data_nr, int baseline, int height); - - int match_next_glyph(const char *str, int *match_length); - - void measure(const char *str, int *width, int *x_offset, int *baseline, int *height); - inline int get_baseline() { return this->baseline_; } - inline int get_height() { return this->height_; } - - const std::vector> &get_glyphs() const { return glyphs_; } - - protected: - std::vector> glyphs_; - int baseline_; - int height_; -}; - -class BaseImage { - public: - virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0; - virtual int get_width() const = 0; - virtual int get_height() const = 0; -}; - -class Image : public BaseImage { - public: - Image(const uint8_t *data_start, int width, int height, ImageType type); - Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const; - int get_width() const override; - int get_height() const override; - ImageType get_type() const; - - void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override; - - void set_transparency(bool transparent) { transparent_ = transparent; } - bool has_transparency() const { return transparent_; } - - protected: - bool get_binary_pixel_(int x, int y) const; - Color get_rgb24_pixel_(int x, int y) const; - Color get_rgba_pixel_(int x, int y) const; - Color get_rgb565_pixel_(int x, int y) const; - Color get_grayscale_pixel_(int x, int y) const; - - int width_; - int height_; - ImageType type_; - const uint8_t *data_start_; - bool transparent_; -}; - -class Animation : public Image { - public: - Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); - - uint32_t get_animation_frame_count() const; - int get_current_frame() const; - void next_frame(); - void prev_frame(); - - /** Selects a specific frame within the animation. - * - * @param frame If possitive, advance to the frame. If negative, recede to that frame from the end frame. - */ - void set_frame(int frame); - - void set_loop(uint32_t start_frame, uint32_t end_frame, int count); - - protected: - void update_data_start_(); - - const uint8_t *animation_data_start_; - int current_frame_; - uint32_t animation_frame_count_; - uint32_t loop_start_frame_; - uint32_t loop_end_frame_; - int loop_count_; - int loop_current_iteration_; -}; - template class DisplayPageShowAction : public Action { public: TEMPLATABLE_VALUE(DisplayPage *, page) diff --git a/esphome/components/display/font.cpp b/esphome/components/display/font.cpp new file mode 100644 index 0000000000..1833ef5023 --- /dev/null +++ b/esphome/components/display/font.cpp @@ -0,0 +1,105 @@ +#include "font.h" + +#include "esphome/core/hal.h" + +namespace esphome { +namespace display { + +bool Glyph::get_pixel(int x, int y) const { + const int x_data = x - this->glyph_data_->offset_x; + const int y_data = y - this->glyph_data_->offset_y; + if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height) + return false; + const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; + const uint32_t pos = x_data + y_data * width_8; + return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); +} +const char *Glyph::get_char() const { return this->glyph_data_->a_char; } +bool Glyph::compare_to(const char *str) const { + // 1 -> this->char_ + // 2 -> str + for (uint32_t i = 0;; i++) { + if (this->glyph_data_->a_char[i] == '\0') + return true; + if (str[i] == '\0') + return false; + if (this->glyph_data_->a_char[i] > str[i]) + return false; + if (this->glyph_data_->a_char[i] < str[i]) + return true; + } + // this should not happen + return false; +} +int Glyph::match_length(const char *str) const { + for (uint32_t i = 0;; i++) { + if (this->glyph_data_->a_char[i] == '\0') + return i; + if (str[i] != this->glyph_data_->a_char[i]) + return 0; + } + // this should not happen + return 0; +} +void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { + *x1 = this->glyph_data_->offset_x; + *y1 = this->glyph_data_->offset_y; + *width = this->glyph_data_->width; + *height = this->glyph_data_->height; +} +int Font::match_next_glyph(const char *str, int *match_length) { + int lo = 0; + int hi = this->glyphs_.size() - 1; + while (lo != hi) { + int mid = (lo + hi + 1) / 2; + if (this->glyphs_[mid].compare_to(str)) { + lo = mid; + } else { + hi = mid - 1; + } + } + *match_length = this->glyphs_[lo].match_length(str); + if (*match_length <= 0) + return -1; + return lo; +} +void Font::measure(const char *str, int *width, int *x_offset, int *baseline, int *height) { + *baseline = this->baseline_; + *height = this->height_; + int i = 0; + int min_x = 0; + bool has_char = false; + int x = 0; + while (str[i] != '\0') { + int match_length; + int glyph_n = this->match_next_glyph(str + i, &match_length); + if (glyph_n < 0) { + // Unknown char, skip + if (!this->get_glyphs().empty()) + x += this->get_glyphs()[0].glyph_data_->width; + i++; + continue; + } + + const Glyph &glyph = this->glyphs_[glyph_n]; + if (!has_char) { + min_x = glyph.glyph_data_->offset_x; + } else { + min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); + } + x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; + + i += match_length; + has_char = true; + } + *x_offset = min_x; + *width = x - min_x; +} +Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { + glyphs_.reserve(data_nr); + for (int i = 0; i < data_nr; ++i) + glyphs_.emplace_back(&data[i]); +} + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/font.h b/esphome/components/display/font.h new file mode 100644 index 0000000000..08b457116e --- /dev/null +++ b/esphome/components/display/font.h @@ -0,0 +1,66 @@ +#pragma once + +#include "esphome/core/datatypes.h" + +namespace esphome { +namespace display { + +class DisplayBuffer; +class Font; + +struct GlyphData { + const char *a_char; + const uint8_t *data; + int offset_x; + int offset_y; + int width; + int height; +}; + +class Glyph { + public: + Glyph(const GlyphData *data) : glyph_data_(data) {} + + bool get_pixel(int x, int y) const; + + const char *get_char() const; + + bool compare_to(const char *str) const; + + int match_length(const char *str) const; + + void scan_area(int *x1, int *y1, int *width, int *height) const; + + protected: + friend Font; + friend DisplayBuffer; + + const GlyphData *glyph_data_; +}; + +class Font { + public: + /** Construct the font with the given glyphs. + * + * @param glyphs A vector of glyphs, must be sorted lexicographically. + * @param baseline The y-offset from the top of the text to the baseline. + * @param bottom The y-offset from the top of the text to the bottom (i.e. height). + */ + Font(const GlyphData *data, int data_nr, int baseline, int height); + + int match_next_glyph(const char *str, int *match_length); + + void measure(const char *str, int *width, int *x_offset, int *baseline, int *height); + inline int get_baseline() { return this->baseline_; } + inline int get_height() { return this->height_; } + + const std::vector> &get_glyphs() const { return glyphs_; } + + protected: + std::vector> glyphs_; + int baseline_; + int height_; +}; + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/image.cpp b/esphome/components/display/image.cpp new file mode 100644 index 0000000000..b3cab3ff7f --- /dev/null +++ b/esphome/components/display/image.cpp @@ -0,0 +1,135 @@ +#include "image.h" + +#include "esphome/core/hal.h" +#include "display_buffer.h" + +namespace esphome { +namespace display { + +void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) { + switch (type_) { + case IMAGE_TYPE_BINARY: { + for (int img_x = 0; img_x < width_; img_x++) { + for (int img_y = 0; img_y < height_; img_y++) { + if (this->get_binary_pixel_(img_x, img_y)) { + display->draw_pixel_at(x + img_x, y + img_y, color_on); + } else if (!this->transparent_) { + display->draw_pixel_at(x + img_x, y + img_y, color_off); + } + } + } + break; + } + case IMAGE_TYPE_GRAYSCALE: + for (int img_x = 0; img_x < width_; img_x++) { + for (int img_y = 0; img_y < height_; img_y++) { + auto color = this->get_grayscale_pixel_(img_x, img_y); + if (color.w >= 0x80) { + display->draw_pixel_at(x + img_x, y + img_y, color); + } + } + } + break; + case IMAGE_TYPE_RGB565: + for (int img_x = 0; img_x < width_; img_x++) { + for (int img_y = 0; img_y < height_; img_y++) { + auto color = this->get_rgb565_pixel_(img_x, img_y); + if (color.w >= 0x80) { + display->draw_pixel_at(x + img_x, y + img_y, color); + } + } + } + break; + case IMAGE_TYPE_RGB24: + for (int img_x = 0; img_x < width_; img_x++) { + for (int img_y = 0; img_y < height_; img_y++) { + auto color = this->get_rgb24_pixel_(img_x, img_y); + if (color.w >= 0x80) { + display->draw_pixel_at(x + img_x, y + img_y, color); + } + } + } + break; + case IMAGE_TYPE_RGBA: + for (int img_x = 0; img_x < width_; img_x++) { + for (int img_y = 0; img_y < height_; img_y++) { + auto color = this->get_rgba_pixel_(img_x, img_y); + if (color.w >= 0x80) { + display->draw_pixel_at(x + img_x, y + img_y, color); + } + } + } + break; + } +} +Color Image::get_pixel(int x, int y, Color color_on, Color color_off) const { + if (x < 0 || x >= this->width_ || y < 0 || y >= this->height_) + return color_off; + switch (this->type_) { + case IMAGE_TYPE_BINARY: + return this->get_binary_pixel_(x, y) ? color_on : color_off; + case IMAGE_TYPE_GRAYSCALE: + return this->get_grayscale_pixel_(x, y); + case IMAGE_TYPE_RGB565: + return this->get_rgb565_pixel_(x, y); + case IMAGE_TYPE_RGB24: + return this->get_rgb24_pixel_(x, y); + case IMAGE_TYPE_RGBA: + return this->get_rgba_pixel_(x, y); + default: + return color_off; + } +} +bool Image::get_binary_pixel_(int x, int y) const { + const uint32_t width_8 = ((this->width_ + 7u) / 8u) * 8u; + const uint32_t pos = x + y * width_8; + return progmem_read_byte(this->data_start_ + (pos / 8u)) & (0x80 >> (pos % 8u)); +} +Color Image::get_rgba_pixel_(int x, int y) const { + const uint32_t pos = (x + y * this->width_) * 4; + return Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), + progmem_read_byte(this->data_start_ + pos + 2), progmem_read_byte(this->data_start_ + pos + 3)); +} +Color Image::get_rgb24_pixel_(int x, int y) const { + const uint32_t pos = (x + y * this->width_) * 3; + Color color = Color(progmem_read_byte(this->data_start_ + pos + 0), progmem_read_byte(this->data_start_ + pos + 1), + progmem_read_byte(this->data_start_ + pos + 2)); + if (color.b == 1 && color.r == 0 && color.g == 0 && transparent_) { + // (0, 0, 1) has been defined as transparent color for non-alpha images. + // putting blue == 1 as a first condition for performance reasons (least likely value to short-cut the if) + color.w = 0; + } else { + color.w = 0xFF; + } + return color; +} +Color Image::get_rgb565_pixel_(int x, int y) const { + const uint32_t pos = (x + y * this->width_) * 2; + uint16_t rgb565 = + progmem_read_byte(this->data_start_ + pos + 0) << 8 | progmem_read_byte(this->data_start_ + pos + 1); + auto r = (rgb565 & 0xF800) >> 11; + auto g = (rgb565 & 0x07E0) >> 5; + auto b = rgb565 & 0x001F; + Color color = Color((r << 3) | (r >> 2), (g << 2) | (g >> 4), (b << 3) | (b >> 2)); + if (rgb565 == 0x0020 && transparent_) { + // darkest green has been defined as transparent color for transparent RGB565 images. + color.w = 0; + } else { + color.w = 0xFF; + } + return color; +} +Color Image::get_grayscale_pixel_(int x, int y) const { + const uint32_t pos = (x + y * this->width_); + const uint8_t gray = progmem_read_byte(this->data_start_ + pos); + uint8_t alpha = (gray == 1 && transparent_) ? 0 : 0xFF; + return Color(gray, gray, gray, alpha); +} +int Image::get_width() const { return this->width_; } +int Image::get_height() const { return this->height_; } +ImageType Image::get_type() const { return this->type_; } +Image::Image(const uint8_t *data_start, int width, int height, ImageType type) + : width_(width), height_(height), type_(type), data_start_(data_start) {} + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/image.h b/esphome/components/display/image.h new file mode 100644 index 0000000000..ac2d5a3421 --- /dev/null +++ b/esphome/components/display/image.h @@ -0,0 +1,75 @@ +#pragma once +#include "esphome/core/color.h" + +namespace esphome { +namespace display { + +enum ImageType { + IMAGE_TYPE_BINARY = 0, + IMAGE_TYPE_GRAYSCALE = 1, + IMAGE_TYPE_RGB24 = 2, + IMAGE_TYPE_RGB565 = 3, + IMAGE_TYPE_RGBA = 4, +}; + +inline int image_type_to_bpp(ImageType type) { + switch (type) { + case IMAGE_TYPE_BINARY: + return 1; + case IMAGE_TYPE_GRAYSCALE: + return 8; + case IMAGE_TYPE_RGB565: + return 16; + case IMAGE_TYPE_RGB24: + return 24; + case IMAGE_TYPE_RGBA: + return 32; + } + return 0; +} + +inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; } + +/// Turn the pixel OFF. +extern const Color COLOR_OFF; +/// Turn the pixel ON. +extern const Color COLOR_ON; + +class DisplayBuffer; + +class BaseImage { + public: + virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0; + virtual int get_width() const = 0; + virtual int get_height() const = 0; +}; + +class Image : public BaseImage { + public: + Image(const uint8_t *data_start, int width, int height, ImageType type); + Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const; + int get_width() const override; + int get_height() const override; + ImageType get_type() const; + + void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override; + + void set_transparency(bool transparent) { transparent_ = transparent; } + bool has_transparency() const { return transparent_; } + + protected: + bool get_binary_pixel_(int x, int y) const; + Color get_rgb24_pixel_(int x, int y) const; + Color get_rgba_pixel_(int x, int y) const; + Color get_rgb565_pixel_(int x, int y) const; + Color get_grayscale_pixel_(int x, int y) const; + + int width_; + int height_; + ImageType type_; + const uint8_t *data_start_; + bool transparent_; +}; + +} // namespace display +} // namespace esphome diff --git a/tests/test2.yaml b/tests/test2.yaml index 2873d0e8e0..fa4b97c7c1 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -695,6 +695,13 @@ image: file: mdi:alert-outline type: BINARY +graph: + - id: my_graph + sensor: ha_hello_world_temperature + duration: 1h + width: 100 + height: 100 + cap1188: id: cap1188_component address: 0x29 From 1a7f121ac67f17957ce34962b37fec56d74d63d6 Mon Sep 17 00:00:00 2001 From: guillempages Date: Sat, 17 Jun 2023 10:38:44 +0200 Subject: [PATCH 006/106] Add support for ESP32-S3-BOX displays (#4942) The ESP32-S3-BOX display has an ILI9xxx driver Add the needed configuration so that it works. --- esphome/components/ili9xxx/display.py | 1 + .../components/ili9xxx/ili9xxx_display.cpp | 11 +++++++ esphome/components/ili9xxx/ili9xxx_display.h | 5 ++++ esphome/components/ili9xxx/ili9xxx_init.h | 30 +++++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 98edadb6a5..29603eb30f 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -44,6 +44,7 @@ MODELS = { "ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), "ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), "ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), + "S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI), "S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI), } diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index ad70bd6e48..6fc6da3cdb 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -421,6 +421,17 @@ void ILI9XXXST7796::initialize() { } } +// 24_TFT rotated display +void ILI9XXXS3Box::initialize() { + this->init_lcd_(INITCMD_S3BOX); + if (this->width_ == 0) { + this->width_ = 320; + } + if (this->height_ == 0) { + this->height_ = 240; + } +} + // 24_TFT rotated display void ILI9XXXS3BoxLite::initialize() { this->init_lcd_(INITCMD_S3BOXLITE); diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index dbdf023bc0..dc7bfdc6eb 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -134,6 +134,11 @@ class ILI9XXXST7796 : public ILI9XXXDisplay { void initialize() override; }; +class ILI9XXXS3Box : public ILI9XXXDisplay { + protected: + void initialize() override; +}; + class ILI9XXXS3BoxLite : public ILI9XXXDisplay { protected: void initialize() override; diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index 36cc30ec2e..a17e6b127c 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -169,6 +169,36 @@ static const uint8_t PROGMEM INITCMD_ST7796[] = { 0x00 // End of list }; +static const uint8_t PROGMEM INITCMD_S3BOX[] = { + 0xEF, 3, 0x03, 0x80, 0x02, + 0xCF, 3, 0x00, 0xC1, 0x30, + 0xED, 4, 0x64, 0x03, 0x12, 0x81, + 0xE8, 3, 0x85, 0x00, 0x78, + 0xCB, 5, 0x39, 0x2C, 0x00, 0x34, 0x02, + 0xF7, 1, 0x20, + 0xEA, 2, 0x00, 0x00, + ILI9XXX_PWCTR1 , 1, 0x23, // Power control VRH[5:0] + ILI9XXX_PWCTR2 , 1, 0x10, // Power control SAP[2:0];BT[3:0] + ILI9XXX_VMCTR1 , 2, 0x3e, 0x28, // VCM control + ILI9XXX_VMCTR2 , 1, 0x86, // VCM control2 + ILI9XXX_MADCTL , 1, 0xC8, // Memory Access Control + ILI9XXX_VSCRSADD, 1, 0x00, // Vertical scroll zero + ILI9XXX_PIXFMT , 1, 0x55, + ILI9XXX_FRMCTR1 , 2, 0x00, 0x18, + ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control + 0xF2, 1, 0x00, // 3Gamma Function Disable + ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected + ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, + 0x0E, 0x09, 0x00, + ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, + 0x31, 0x36, 0x0F, + ILI9XXX_SLPOUT , 0x80, // Exit Sleep + ILI9XXX_DISPON , 0x80, // Display on + 0x00 // End of list +}; + static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = { 0xEF, 3, 0x03, 0x80, 0x02, 0xCF, 3, 0x00, 0xC1, 0x30, From e1b0d860988be0aeb113da20c11d4ad20298dd00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Sun, 18 Jun 2023 21:24:23 +0200 Subject: [PATCH 007/106] display: allow to align image with `ImageAlign` (#4933) --- esphome/components/display/display_buffer.cpp | 31 ++++++++++ esphome/components/display/display_buffer.h | 61 ++++++++++++++++++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 8092b9f12f..672c6a22b0 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -311,6 +311,37 @@ void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign al } void DisplayBuffer::image(int x, int y, BaseImage *image, Color color_on, Color color_off) { + this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off); +} + +void DisplayBuffer::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) { + auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT))); + auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT))); + + switch (x_align) { + case ImageAlign::RIGHT: + x -= image->get_width(); + break; + case ImageAlign::CENTER_HORIZONTAL: + x -= image->get_width() / 2; + break; + case ImageAlign::LEFT: + default: + break; + } + + switch (y_align) { + case ImageAlign::BOTTOM: + y -= image->get_height(); + break; + case ImageAlign::CENTER_VERTICAL: + y -= image->get_height() / 2; + break; + case ImageAlign::TOP: + default: + break; + } + image->draw(x, y, this, color_on, color_off); } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index b66ec529f7..652039517f 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -74,6 +74,54 @@ enum class TextAlign { BOTTOM_RIGHT = BOTTOM | RIGHT, }; +/** ImageAlign is used to tell the display class how to position a image. By default + * the coordinates you enter for the image() functions take the upper left corner of the image + * as the "anchor" point. You can customize this behavior to, for example, make the coordinates + * refer to the *center* of the image. + * + * All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis + * these options are allowed: + * + * - LEFT (x-coordinate of anchor point is on left) + * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image) + * - RIGHT (x-coordinate of anchor point is on right) + * + * For the Y-Axis alignment these options are allowed: + * + * - TOP (y-coordinate of anchor is on the top of the image) + * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image) + * - BOTTOM (y-coordinate of anchor is on the bottom of the image) + * + * These options are then combined to create combined TextAlignment options like: + * - TOP_LEFT (default) + * - CENTER (anchor point is in the middle of the image bounds) + * - ... + */ +enum class ImageAlign { + TOP = 0x00, + CENTER_VERTICAL = 0x01, + BOTTOM = 0x02, + + LEFT = 0x00, + CENTER_HORIZONTAL = 0x04, + RIGHT = 0x08, + + TOP_LEFT = TOP | LEFT, + TOP_CENTER = TOP | CENTER_HORIZONTAL, + TOP_RIGHT = TOP | RIGHT, + + CENTER_LEFT = CENTER_VERTICAL | LEFT, + CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL, + CENTER_RIGHT = CENTER_VERTICAL | RIGHT, + + BOTTOM_LEFT = BOTTOM | LEFT, + BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL, + BOTTOM_RIGHT = BOTTOM | RIGHT, + + HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT, + VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM +}; + enum DisplayType { DISPLAY_TYPE_BINARY = 1, DISPLAY_TYPE_GRAYSCALE = 2, @@ -300,12 +348,23 @@ class DisplayBuffer { * * @param x The x coordinate of the upper left corner. * @param y The y coordinate of the upper left corner. - * @param image The image to draw + * @param image The image to draw. * @param color_on The color to replace in binary images for the on bits. * @param color_off The color to replace in binary images for the off bits. */ void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); + /** Draw the `image` at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param image The image to draw. + * @param align The alignment of the image. + * @param color_on The color to replace in binary images for the on bits. + * @param color_off The color to replace in binary images for the off bits. + */ + void image(int x, int y, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); + #ifdef USE_GRAPH /** Draw the `graph` with the top-left corner at [x,y] to the screen. * From d4099d68a78fb97bb921eb851fa72a3e3407624e Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 19 Jun 2023 07:24:44 +1200 Subject: [PATCH 008/106] Use HW SPI for rp2040 (#4955) --- esphome/components/spi/spi.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index 141bfb9448..c9bb075fb5 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -87,6 +87,30 @@ void SPIComponent::setup() { return; } #endif // USE_ESP32 +#ifdef USE_RP2040 + static uint8_t spi_bus_num = 0; + if (spi_bus_num >= 2) { + use_hw_spi = false; + } + if (use_hw_spi) { + SPIClassRP2040 *spi; + if (spi_bus_num == 0) { + spi = &SPI; + } else { + spi = &SPI1; + } + spi_bus_num++; + + if (miso_pin != -1) + spi->setRX(miso_pin); + if (mosi_pin != -1) + spi->setTX(mosi_pin); + spi->setSCK(clk_pin); + this->hw_spi_ = spi; + this->hw_spi_->begin(); + return; + } +#endif // USE_RP2040 #endif // USE_SPI_ARDUINO_BACKEND if (this->miso_ != nullptr) { From 5a8e93ed0a7cfe1af626ba5701f0373ad34dbf34 Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi Date: Mon, 19 Jun 2023 00:24:52 +0200 Subject: [PATCH 009/106] Upgraded Haier climate component implementation (#4521) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Pavlo Dudnytskyi Co-authored-by: esphomebot --- CODEOWNERS | 2 +- esphome/components/haier/__init__.py | 1 - esphome/components/haier/automation.h | 130 +++ esphome/components/haier/climate.py | 359 +++++++- esphome/components/haier/haier.cpp | 302 ------ esphome/components/haier/haier.h | 37 - esphome/components/haier/haier_base.cpp | 311 +++++++ esphome/components/haier/haier_base.h | 142 +++ esphome/components/haier/hon_climate.cpp | 857 ++++++++++++++++++ esphome/components/haier/hon_climate.h | 95 ++ esphome/components/haier/hon_packet.h | 228 +++++ esphome/components/haier/logger_handler.cpp | 33 + esphome/components/haier/logger_handler.h | 14 + .../components/haier/smartair2_climate.cpp | 457 ++++++++++ esphome/components/haier/smartair2_climate.h | 31 + esphome/components/haier/smartair2_packet.h | 97 ++ platformio.ini | 1 + tests/test3.yaml | 26 +- 18 files changed, 2758 insertions(+), 365 deletions(-) create mode 100644 esphome/components/haier/automation.h delete mode 100644 esphome/components/haier/haier.cpp delete mode 100644 esphome/components/haier/haier.h create mode 100644 esphome/components/haier/haier_base.cpp create mode 100644 esphome/components/haier/haier_base.h create mode 100644 esphome/components/haier/hon_climate.cpp create mode 100644 esphome/components/haier/hon_climate.h create mode 100644 esphome/components/haier/hon_packet.h create mode 100644 esphome/components/haier/logger_handler.cpp create mode 100644 esphome/components/haier/logger_handler.h create mode 100644 esphome/components/haier/smartair2_climate.cpp create mode 100644 esphome/components/haier/smartair2_climate.h create mode 100644 esphome/components/haier/smartair2_packet.h diff --git a/CODEOWNERS b/CODEOWNERS index c6cbf3c2ab..595e4a5684 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -102,7 +102,7 @@ esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/graph/* @synco esphome/components/growatt_solar/* @leeuwte -esphome/components/haier/* @Yarikx +esphome/components/haier/* @paveldn esphome/components/havells_solar/* @sourabhjaiswal esphome/components/hbridge/fan/* @WeekendWarrior esphome/components/hbridge/light/* @DotNetDann diff --git a/esphome/components/haier/__init__.py b/esphome/components/haier/__init__.py index b9ea055a41..e69de29bb2 100644 --- a/esphome/components/haier/__init__.py +++ b/esphome/components/haier/__init__.py @@ -1 +0,0 @@ -CODEOWNERS = ["@Yarikx"] diff --git a/esphome/components/haier/automation.h b/esphome/components/haier/automation.h new file mode 100644 index 0000000000..84e4554db8 --- /dev/null +++ b/esphome/components/haier/automation.h @@ -0,0 +1,130 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "haier_base.h" +#include "hon_climate.h" + +namespace esphome { +namespace haier { + +template class DisplayOnAction : public Action { + public: + DisplayOnAction(HaierClimateBase *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->set_display_state(true); } + + protected: + HaierClimateBase *parent_; +}; + +template class DisplayOffAction : public Action { + public: + DisplayOffAction(HaierClimateBase *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->set_display_state(false); } + + protected: + HaierClimateBase *parent_; +}; + +template class BeeperOnAction : public Action { + public: + BeeperOnAction(HonClimate *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->set_beeper_state(true); } + + protected: + HonClimate *parent_; +}; + +template class BeeperOffAction : public Action { + public: + BeeperOffAction(HonClimate *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->set_beeper_state(false); } + + protected: + HonClimate *parent_; +}; + +template class VerticalAirflowAction : public Action { + public: + VerticalAirflowAction(HonClimate *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(AirflowVerticalDirection, direction) + void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); } + + protected: + HonClimate *parent_; +}; + +template class HorizontalAirflowAction : public Action { + public: + HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(AirflowHorizontalDirection, direction) + void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); } + + protected: + HonClimate *parent_; +}; + +template class HealthOnAction : public Action { + public: + HealthOnAction(HaierClimateBase *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->set_health_mode(true); } + + protected: + HaierClimateBase *parent_; +}; + +template class HealthOffAction : public Action { + public: + HealthOffAction(HaierClimateBase *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->set_health_mode(false); } + + protected: + HaierClimateBase *parent_; +}; + +template class StartSelfCleaningAction : public Action { + public: + StartSelfCleaningAction(HonClimate *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->start_self_cleaning(); } + + protected: + HonClimate *parent_; +}; + +template class StartSteriCleaningAction : public Action { + public: + StartSteriCleaningAction(HonClimate *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->start_steri_cleaning(); } + + protected: + HonClimate *parent_; +}; + +template class PowerOnAction : public Action { + public: + PowerOnAction(HaierClimateBase *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->send_power_on_command(); } + + protected: + HaierClimateBase *parent_; +}; + +template class PowerOffAction : public Action { + public: + PowerOffAction(HaierClimateBase *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->send_power_off_command(); } + + protected: + HaierClimateBase *parent_; +}; + +template class PowerToggleAction : public Action { + public: + PowerToggleAction(HaierClimateBase *parent) : parent_(parent) {} + void play(Ts... x) { this->parent_->toggle_power(); } + + protected: + HaierClimateBase *parent_; +}; + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/climate.py b/esphome/components/haier/climate.py index cee83232a1..12b76084ba 100644 --- a/esphome/components/haier/climate.py +++ b/esphome/components/haier/climate.py @@ -1,43 +1,364 @@ -from esphome.components import climate +import logging import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import uart -from esphome.components.climate import ClimateSwingMode -from esphome.const import CONF_ID, CONF_SUPPORTED_SWING_MODES +import esphome.final_validate as fv +from esphome.components import uart, sensor, climate, logger +from esphome import automation +from esphome.const import ( + CONF_BEEPER, + CONF_ID, + CONF_LEVEL, + CONF_LOGGER, + CONF_LOGS, + CONF_MAX_TEMPERATURE, + CONF_MIN_TEMPERATURE, + CONF_PROTOCOL, + CONF_SUPPORTED_MODES, + CONF_SUPPORTED_SWING_MODES, + CONF_VISUAL, + CONF_WIFI, + DEVICE_CLASS_TEMPERATURE, + ICON_THERMOMETER, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) +from esphome.components.climate import ( + ClimateSwingMode, + ClimateMode, +) -DEPENDENCIES = ["uart"] +_LOGGER = logging.getLogger(__name__) + +PROTOCOL_MIN_TEMPERATURE = 16.0 +PROTOCOL_MAX_TEMPERATURE = 30.0 +PROTOCOL_TEMPERATURE_STEP = 1.0 + +CODEOWNERS = ["@paveldn"] +AUTO_LOAD = ["sensor"] +DEPENDENCIES = ["climate", "uart"] +CONF_WIFI_SIGNAL = "wifi_signal" +CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature" +CONF_VERTICAL_AIRFLOW = "vertical_airflow" +CONF_HORIZONTAL_AIRFLOW = "horizontal_airflow" + + +PROTOCOL_HON = "HON" +PROTOCOL_SMARTAIR2 = "SMARTAIR2" +PROTOCOLS_SUPPORTED = [PROTOCOL_HON, PROTOCOL_SMARTAIR2] haier_ns = cg.esphome_ns.namespace("haier") -HaierClimate = haier_ns.class_( - "HaierClimate", climate.Climate, cg.PollingComponent, uart.UARTDevice +HaierClimateBase = haier_ns.class_( + "HaierClimateBase", uart.UARTDevice, climate.Climate, cg.Component ) +HonClimate = haier_ns.class_("HonClimate", HaierClimateBase) +Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase) -ALLOWED_CLIMATE_SWING_MODES = { - "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, - "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, - "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, + +AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection") +AIRFLOW_VERTICAL_DIRECTION_OPTIONS = { + "UP": AirflowVerticalDirection.UP, + "CENTER": AirflowVerticalDirection.CENTER, + "DOWN": AirflowVerticalDirection.DOWN, } -validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True) +AirflowHorizontalDirection = haier_ns.enum("AirflowHorizontalDirection") +AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = { + "LEFT": AirflowHorizontalDirection.LEFT, + "CENTER": AirflowHorizontalDirection.CENTER, + "RIGHT": AirflowHorizontalDirection.RIGHT, +} -CONFIG_SCHEMA = cv.All( +SUPPORTED_SWING_MODES_OPTIONS = { + "OFF": ClimateSwingMode.CLIMATE_SWING_OFF, # always available + "VERTICAL": ClimateSwingMode.CLIMATE_SWING_VERTICAL, # always available + "HORIZONTAL": ClimateSwingMode.CLIMATE_SWING_HORIZONTAL, + "BOTH": ClimateSwingMode.CLIMATE_SWING_BOTH, +} + +SUPPORTED_CLIMATE_MODES_OPTIONS = { + "OFF": ClimateMode.CLIMATE_MODE_OFF, # always available + "AUTO": ClimateMode.CLIMATE_MODE_AUTO, # always available + "COOL": ClimateMode.CLIMATE_MODE_COOL, + "HEAT": ClimateMode.CLIMATE_MODE_HEAT, + "DRY": ClimateMode.CLIMATE_MODE_DRY, + "FAN_ONLY": ClimateMode.CLIMATE_MODE_FAN_ONLY, +} + + +def validate_visual(config): + if CONF_VISUAL in config: + visual_config = config[CONF_VISUAL] + if CONF_MIN_TEMPERATURE in visual_config: + min_temp = visual_config[CONF_MIN_TEMPERATURE] + if min_temp < PROTOCOL_MIN_TEMPERATURE: + raise cv.Invalid( + f"Configured visual minimum temperature {min_temp} is lower than supported by Haier protocol is {PROTOCOL_MIN_TEMPERATURE}" + ) + else: + config[CONF_VISUAL][CONF_MIN_TEMPERATURE] = PROTOCOL_MIN_TEMPERATURE + if CONF_MAX_TEMPERATURE in visual_config: + max_temp = visual_config[CONF_MAX_TEMPERATURE] + if max_temp > PROTOCOL_MAX_TEMPERATURE: + raise cv.Invalid( + f"Configured visual maximum temperature {max_temp} is higher than supported by Haier protocol is {PROTOCOL_MAX_TEMPERATURE}" + ) + else: + config[CONF_VISUAL][CONF_MAX_TEMPERATURE] = PROTOCOL_MAX_TEMPERATURE + else: + config[CONF_VISUAL] = { + CONF_MIN_TEMPERATURE: PROTOCOL_MIN_TEMPERATURE, + CONF_MAX_TEMPERATURE: PROTOCOL_MAX_TEMPERATURE, + } + return config + + +BASE_CONFIG_SCHEMA = ( climate.CLIMATE_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(HaierClimate), - cv.Optional(CONF_SUPPORTED_SWING_MODES): cv.ensure_list( - validate_swing_modes + cv.Optional(CONF_SUPPORTED_MODES): cv.ensure_list( + cv.enum(SUPPORTED_CLIMATE_MODES_OPTIONS, upper=True) ), + cv.Optional( + CONF_SUPPORTED_SWING_MODES, + default=[ + "OFF", + "VERTICAL", + "HORIZONTAL", + "BOTH", + ], + ): cv.ensure_list(cv.enum(SUPPORTED_SWING_MODES_OPTIONS, upper=True)), } ) - .extend(cv.polling_component_schema("5s")) - .extend(uart.UART_DEVICE_SCHEMA), + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) ) +CONFIG_SCHEMA = cv.All( + cv.typed_schema( + { + PROTOCOL_SMARTAIR2: BASE_CONFIG_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(Smartair2Climate), + } + ), + PROTOCOL_HON: BASE_CONFIG_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(HonClimate), + cv.Optional(CONF_WIFI_SIGNAL, default=True): cv.boolean, + cv.Optional(CONF_BEEPER, default=True): cv.boolean, + cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ), + }, + key=CONF_PROTOCOL, + default_type=PROTOCOL_SMARTAIR2, + upper=True, + ), + validate_visual, +) + + +# Actions +DisplayOnAction = haier_ns.class_("DisplayOnAction", automation.Action) +DisplayOffAction = haier_ns.class_("DisplayOffAction", automation.Action) +BeeperOnAction = haier_ns.class_("BeeperOnAction", automation.Action) +BeeperOffAction = haier_ns.class_("BeeperOffAction", automation.Action) +StartSelfCleaningAction = haier_ns.class_("StartSelfCleaningAction", automation.Action) +StartSteriCleaningAction = haier_ns.class_( + "StartSteriCleaningAction", automation.Action +) +VerticalAirflowAction = haier_ns.class_("VerticalAirflowAction", automation.Action) +HorizontalAirflowAction = haier_ns.class_("HorizontalAirflowAction", automation.Action) +HealthOnAction = haier_ns.class_("HealthOnAction", automation.Action) +HealthOffAction = haier_ns.class_("HealthOffAction", automation.Action) +PowerOnAction = haier_ns.class_("PowerOnAction", automation.Action) +PowerOffAction = haier_ns.class_("PowerOffAction", automation.Action) +PowerToggleAction = haier_ns.class_("PowerToggleAction", automation.Action) + +HAIER_BASE_ACTION_SCHEMA = automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(HaierClimateBase), + } +) + +HAIER_HON_BASE_ACTION_SCHEMA = automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(HonClimate), + } +) + + +@automation.register_action( + "climate.haier.display_on", DisplayOnAction, HAIER_BASE_ACTION_SCHEMA +) +@automation.register_action( + "climate.haier.display_off", DisplayOffAction, HAIER_BASE_ACTION_SCHEMA +) +async def display_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) + return var + + +@automation.register_action( + "climate.haier.beeper_on", BeeperOnAction, HAIER_HON_BASE_ACTION_SCHEMA +) +@automation.register_action( + "climate.haier.beeper_off", BeeperOffAction, HAIER_HON_BASE_ACTION_SCHEMA +) +async def beeper_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) + return var + + +# Start self cleaning or steri-cleaning action action +@automation.register_action( + "climate.haier.start_self_cleaning", + StartSelfCleaningAction, + HAIER_HON_BASE_ACTION_SCHEMA, +) +@automation.register_action( + "climate.haier.start_steri_cleaning", + StartSteriCleaningAction, + HAIER_HON_BASE_ACTION_SCHEMA, +) +async def start_cleaning_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 + + +# Set vertical airflow direction action +@automation.register_action( + "climate.haier.set_vertical_airflow", + VerticalAirflowAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(HonClimate), + cv.Required(CONF_VERTICAL_AIRFLOW): cv.templatable( + cv.enum(AIRFLOW_VERTICAL_DIRECTION_OPTIONS, upper=True) + ), + } + ), +) +async def haier_set_vertical_airflow_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_VERTICAL_AIRFLOW], args, AirflowVerticalDirection + ) + cg.add(var.set_direction(template_)) + return var + + +# Set horizontal airflow direction action +@automation.register_action( + "climate.haier.set_horizontal_airflow", + HorizontalAirflowAction, + cv.Schema( + { + cv.GenerateID(): cv.use_id(HonClimate), + cv.Required(CONF_HORIZONTAL_AIRFLOW): cv.templatable( + cv.enum(AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS, upper=True) + ), + } + ), +) +async def haier_set_horizontal_airflow_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_HORIZONTAL_AIRFLOW], args, AirflowHorizontalDirection + ) + cg.add(var.set_direction(template_)) + return var + + +@automation.register_action( + "climate.haier.health_on", HealthOnAction, HAIER_BASE_ACTION_SCHEMA +) +@automation.register_action( + "climate.haier.health_off", HealthOffAction, HAIER_BASE_ACTION_SCHEMA +) +async def health_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) + return var + + +@automation.register_action( + "climate.haier.power_on", PowerOnAction, HAIER_BASE_ACTION_SCHEMA +) +@automation.register_action( + "climate.haier.power_off", PowerOffAction, HAIER_BASE_ACTION_SCHEMA +) +@automation.register_action( + "climate.haier.power_toggle", PowerToggleAction, HAIER_BASE_ACTION_SCHEMA +) +async def power_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) + return var + + +def _final_validate(config): + full_config = fv.full_config.get() + if CONF_LOGGER in full_config: + _level = "NONE" + logger_config = full_config[CONF_LOGGER] + if CONF_LOGS in logger_config: + if "haier.protocol" in logger_config[CONF_LOGS]: + _level = logger_config[CONF_LOGS]["haier.protocol"] + else: + _level = logger_config[CONF_LEVEL] + _LOGGER.info("Detected log level for Haier protocol: %s", _level) + if _level not in logger.LOG_LEVEL_SEVERITY: + raise cv.Invalid("Unknown log level for Haier protocol") + _severity = logger.LOG_LEVEL_SEVERITY.index(_level) + cg.add_build_flag(f"-DHAIER_LOG_LEVEL={_severity}") + else: + _LOGGER.info( + "No logger component found, logging for Haier protocol is disabled" + ) + cg.add_build_flag("-DHAIER_LOG_LEVEL=0") + if ( + (CONF_WIFI_SIGNAL in config) + and (config[CONF_WIFI_SIGNAL]) + and CONF_WIFI not in full_config + ): + raise cv.Invalid( + f"No WiFi configured, if you want to use haier climate without WiFi add {CONF_WIFI_SIGNAL}: false to climate configuration" + ) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate + async def to_code(config): + cg.add(haier_ns.init_haier_protocol_logging()) var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - await climate.register_climate(var, config) await uart.register_uart_device(var, config) + await climate.register_climate(var, config) + + if (CONF_WIFI_SIGNAL in config) and (config[CONF_WIFI_SIGNAL]): + cg.add(var.set_send_wifi(config[CONF_WIFI_SIGNAL])) + if CONF_BEEPER in config: + cg.add(var.set_beeper_state(config[CONF_BEEPER])) + if CONF_OUTDOOR_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_OUTDOOR_TEMPERATURE]) + cg.add(var.set_outdoor_temperature_sensor(sens)) + if CONF_SUPPORTED_MODES in config: + cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES])) if CONF_SUPPORTED_SWING_MODES in config: cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES])) + # https://github.com/paveldn/HaierProtocol + cg.add_library("pavlodn/HaierProtocol", "0.9.18") diff --git a/esphome/components/haier/haier.cpp b/esphome/components/haier/haier.cpp deleted file mode 100644 index cf69d483b5..0000000000 --- a/esphome/components/haier/haier.cpp +++ /dev/null @@ -1,302 +0,0 @@ -#include -#include "haier.h" -#include "esphome/core/macros.h" - -namespace esphome { -namespace haier { - -static const char *const TAG = "haier"; - -static const uint8_t TEMPERATURE = 13; -static const uint8_t HUMIDITY = 15; - -static const uint8_t MODE = 23; - -static const uint8_t FAN_SPEED = 25; - -static const uint8_t SWING = 27; - -static const uint8_t POWER = 29; -static const uint8_t POWER_MASK = 1; - -static const uint8_t SET_TEMPERATURE = 35; -static const uint8_t DECIMAL_MASK = (1 << 5); - -static const uint8_t CRC = 36; - -static const uint8_t COMFORT_PRESET_MASK = (1 << 3); - -static const uint8_t MIN_VALID_TEMPERATURE = 16; -static const uint8_t MAX_VALID_TEMPERATURE = 50; -static const float TEMPERATURE_STEP = 0.5f; - -static const uint8_t POLL_REQ[13] = {255, 255, 10, 0, 0, 0, 0, 0, 1, 1, 77, 1, 90}; -static const uint8_t OFF_REQ[13] = {255, 255, 10, 0, 0, 0, 0, 0, 1, 1, 77, 3, 92}; - -void HaierClimate::dump_config() { - ESP_LOGCONFIG(TAG, "Haier:"); - ESP_LOGCONFIG(TAG, " Update interval: %u", this->get_update_interval()); - this->dump_traits_(TAG); - this->check_uart_settings(9600); -} - -void HaierClimate::loop() { - if (this->available() >= sizeof(this->data_)) { - this->read_array(this->data_, sizeof(this->data_)); - if (this->data_[0] != 255 || this->data_[1] != 255) - return; - - read_state_(this->data_, sizeof(this->data_)); - } -} - -void HaierClimate::update() { - this->write_array(POLL_REQ, sizeof(POLL_REQ)); - dump_message_("Poll sent", POLL_REQ, sizeof(POLL_REQ)); -} - -climate::ClimateTraits HaierClimate::traits() { - auto traits = climate::ClimateTraits(); - - traits.set_visual_min_temperature(MIN_VALID_TEMPERATURE); - traits.set_visual_max_temperature(MAX_VALID_TEMPERATURE); - traits.set_visual_temperature_step(TEMPERATURE_STEP); - - traits.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_HEAT_COOL, climate::CLIMATE_MODE_COOL, - climate::CLIMATE_MODE_HEAT, climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY}); - - traits.set_supported_fan_modes({ - climate::CLIMATE_FAN_AUTO, - climate::CLIMATE_FAN_LOW, - climate::CLIMATE_FAN_MEDIUM, - climate::CLIMATE_FAN_HIGH, - }); - - traits.set_supported_swing_modes(this->supported_swing_modes_); - traits.set_supports_current_temperature(true); - traits.set_supports_two_point_target_temperature(false); - - traits.add_supported_preset(climate::CLIMATE_PRESET_NONE); - traits.add_supported_preset(climate::CLIMATE_PRESET_COMFORT); - - return traits; -} - -void HaierClimate::read_state_(const uint8_t *data, uint8_t size) { - dump_message_("Received state", data, size); - - uint8_t check = data[CRC]; - - uint8_t crc = get_checksum_(data, size); - - if (check != crc) { - ESP_LOGW(TAG, "Invalid checksum"); - return; - } - - this->current_temperature = data[TEMPERATURE]; - - this->target_temperature = data[SET_TEMPERATURE] + MIN_VALID_TEMPERATURE; - - if (data[POWER] & DECIMAL_MASK) { - this->target_temperature += 0.5f; - } - - switch (data[MODE]) { - case MODE_SMART: - this->mode = climate::CLIMATE_MODE_HEAT_COOL; - break; - case MODE_COOL: - this->mode = climate::CLIMATE_MODE_COOL; - break; - case MODE_HEAT: - this->mode = climate::CLIMATE_MODE_HEAT; - break; - case MODE_ONLY_FAN: - this->mode = climate::CLIMATE_MODE_FAN_ONLY; - break; - case MODE_DRY: - this->mode = climate::CLIMATE_MODE_DRY; - break; - default: // other modes are unsupported - this->mode = climate::CLIMATE_MODE_HEAT_COOL; - } - - switch (data[FAN_SPEED]) { - case FAN_AUTO: - this->fan_mode = climate::CLIMATE_FAN_AUTO; - break; - - case FAN_MIN: - this->fan_mode = climate::CLIMATE_FAN_LOW; - break; - - case FAN_MIDDLE: - this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - break; - - case FAN_MAX: - this->fan_mode = climate::CLIMATE_FAN_HIGH; - break; - } - - switch (data[SWING]) { - case SWING_OFF: - this->swing_mode = climate::CLIMATE_SWING_OFF; - break; - - case SWING_VERTICAL: - this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - break; - - case SWING_HORIZONTAL: - this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - break; - - case SWING_BOTH: - this->swing_mode = climate::CLIMATE_SWING_BOTH; - break; - } - - if (data[POWER] & COMFORT_PRESET_MASK) { - this->preset = climate::CLIMATE_PRESET_COMFORT; - } else { - this->preset = climate::CLIMATE_PRESET_NONE; - } - - if ((data[POWER] & POWER_MASK) == 0) { - this->mode = climate::CLIMATE_MODE_OFF; - } - - this->publish_state(); -} - -void HaierClimate::control(const climate::ClimateCall &call) { - if (call.get_mode().has_value()) { - switch (call.get_mode().value()) { - case climate::CLIMATE_MODE_OFF: - send_data_(OFF_REQ, sizeof(OFF_REQ)); - break; - - case climate::CLIMATE_MODE_HEAT_COOL: - case climate::CLIMATE_MODE_AUTO: - data_[POWER] |= POWER_MASK; - data_[MODE] = MODE_SMART; - break; - case climate::CLIMATE_MODE_HEAT: - data_[POWER] |= POWER_MASK; - data_[MODE] = MODE_HEAT; - break; - case climate::CLIMATE_MODE_COOL: - data_[POWER] |= POWER_MASK; - data_[MODE] = MODE_COOL; - break; - - case climate::CLIMATE_MODE_FAN_ONLY: - data_[POWER] |= POWER_MASK; - data_[MODE] = MODE_ONLY_FAN; - break; - - case climate::CLIMATE_MODE_DRY: - data_[POWER] |= POWER_MASK; - data_[MODE] = MODE_DRY; - break; - } - } - - if (call.get_preset().has_value()) { - if (call.get_preset().value() == climate::CLIMATE_PRESET_COMFORT) { - data_[POWER] |= COMFORT_PRESET_MASK; - } else { - data_[POWER] &= ~COMFORT_PRESET_MASK; - } - } - - if (call.get_target_temperature().has_value()) { - float target = call.get_target_temperature().value() - MIN_VALID_TEMPERATURE; - - data_[SET_TEMPERATURE] = (uint8_t) target; - - if ((int) target == std::lroundf(target)) { - data_[POWER] &= ~DECIMAL_MASK; - } else { - data_[POWER] |= DECIMAL_MASK; - } - } - - if (call.get_fan_mode().has_value()) { - switch (call.get_fan_mode().value()) { - case climate::CLIMATE_FAN_AUTO: - data_[FAN_SPEED] = FAN_AUTO; - break; - case climate::CLIMATE_FAN_LOW: - data_[FAN_SPEED] = FAN_MIN; - break; - case climate::CLIMATE_FAN_MEDIUM: - data_[FAN_SPEED] = FAN_MIDDLE; - break; - case climate::CLIMATE_FAN_HIGH: - data_[FAN_SPEED] = FAN_MAX; - break; - - default: // other modes are unsupported - break; - } - } - - if (call.get_swing_mode().has_value()) { - switch (call.get_swing_mode().value()) { - case climate::CLIMATE_SWING_OFF: - data_[SWING] = SWING_OFF; - break; - case climate::CLIMATE_SWING_VERTICAL: - data_[SWING] = SWING_VERTICAL; - break; - case climate::CLIMATE_SWING_HORIZONTAL: - data_[SWING] = SWING_HORIZONTAL; - break; - case climate::CLIMATE_SWING_BOTH: - data_[SWING] = SWING_BOTH; - break; - } - } - - // Parts of the message that must have specific values for "send" command. - // The meaning of those values is unknown at the moment. - data_[9] = 1; - data_[10] = 77; - data_[11] = 95; - data_[17] = 0; - - // Compute checksum - uint8_t crc = get_checksum_(data_, sizeof(data_)); - data_[CRC] = crc; - - send_data_(data_, sizeof(data_)); -} - -void HaierClimate::send_data_(const uint8_t *message, uint8_t size) { - this->write_array(message, size); - - dump_message_("Sent message", message, size); -} - -void HaierClimate::dump_message_(const char *title, const uint8_t *message, uint8_t size) { - ESP_LOGV(TAG, "%s:", title); - for (int i = 0; i < size; i++) { - ESP_LOGV(TAG, " byte %02d - %d", i, message[i]); - } -} - -uint8_t HaierClimate::get_checksum_(const uint8_t *message, size_t size) { - uint8_t position = size - 1; - uint8_t crc = 0; - - for (int i = 2; i < position; i++) - crc += message[i]; - - return crc; -} - -} // namespace haier -} // namespace esphome diff --git a/esphome/components/haier/haier.h b/esphome/components/haier/haier.h deleted file mode 100644 index 5399fd187b..0000000000 --- a/esphome/components/haier/haier.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "esphome/core/component.h" -#include "esphome/components/climate/climate.h" -#include "esphome/components/uart/uart.h" - -namespace esphome { -namespace haier { - -enum Mode : uint8_t { MODE_SMART = 0, MODE_COOL = 1, MODE_HEAT = 2, MODE_ONLY_FAN = 3, MODE_DRY = 4 }; -enum FanSpeed : uint8_t { FAN_MAX = 0, FAN_MIDDLE = 1, FAN_MIN = 2, FAN_AUTO = 3 }; -enum SwingMode : uint8_t { SWING_OFF = 0, SWING_VERTICAL = 1, SWING_HORIZONTAL = 2, SWING_BOTH = 3 }; - -class HaierClimate : public climate::Climate, public uart::UARTDevice, public PollingComponent { - public: - void loop() override; - void update() override; - void dump_config() override; - void control(const climate::ClimateCall &call) override; - void set_supported_swing_modes(const std::set &modes) { - this->supported_swing_modes_ = modes; - } - - protected: - climate::ClimateTraits traits() override; - void read_state_(const uint8_t *data, uint8_t size); - void send_data_(const uint8_t *message, uint8_t size); - void dump_message_(const char *title, const uint8_t *message, uint8_t size); - uint8_t get_checksum_(const uint8_t *message, size_t size); - - private: - uint8_t data_[37]; - std::set supported_swing_modes_{}; -}; - -} // namespace haier -} // namespace esphome diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp new file mode 100644 index 0000000000..d9349cb8fe --- /dev/null +++ b/esphome/components/haier/haier_base.cpp @@ -0,0 +1,311 @@ +#include +#include +#include "esphome/components/climate/climate.h" +#include "esphome/components/uart/uart.h" +#include "haier_base.h" + +using namespace esphome::climate; +using namespace esphome::uart; + +namespace esphome { +namespace haier { + +static const char *const TAG = "haier.climate"; +constexpr size_t COMMUNICATION_TIMEOUT_MS = 60000; +constexpr size_t STATUS_REQUEST_INTERVAL_MS = 5000; +constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000; +constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000; +constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400; +constexpr size_t CONTROL_TIMEOUT_MS = 7000; +constexpr size_t NO_COMMAND = 0xFF; // Indicate that there is no command supplied + +#if (HAIER_LOG_LEVEL > 4) +// To reduce size of binary this function only available when log level is Verbose +const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) { + static const char *phase_names[] = { + "SENDING_INIT_1", + "WAITING_ANSWER_INIT_1", + "SENDING_INIT_2", + "WAITING_ANSWER_INIT_2", + "SENDING_FIRST_STATUS_REQUEST", + "WAITING_FIRST_STATUS_ANSWER", + "SENDING_ALARM_STATUS_REQUEST", + "WAITING_ALARM_STATUS_ANSWER", + "IDLE", + "SENDING_STATUS_REQUEST", + "WAITING_STATUS_ANSWER", + "SENDING_UPDATE_SIGNAL_REQUEST", + "WAITING_UPDATE_SIGNAL_ANSWER", + "SENDING_SIGNAL_LEVEL", + "WAITING_SIGNAL_LEVEL_ANSWER", + "SENDING_CONTROL", + "WAITING_CONTROL_ANSWER", + "SENDING_POWER_ON_COMMAND", + "WAITING_POWER_ON_ANSWER", + "SENDING_POWER_OFF_COMMAND", + "WAITING_POWER_OFF_ANSWER", + "UNKNOWN" // Should be the last! + }; + int phase_index = (int) phase; + if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0)) + phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES; + return phase_names[phase_index]; +} +#endif + +HaierClimateBase::HaierClimateBase() + : haier_protocol_(*this), + protocol_phase_(ProtocolPhases::SENDING_INIT_1), + action_request_(ActionRequest::NO_ACTION), + display_status_(true), + health_mode_(false), + force_send_control_(false), + forced_publish_(false), + forced_request_status_(false), + first_control_attempt_(false), + reset_protocol_request_(false) { + this->traits_ = climate::ClimateTraits(); + this->traits_.set_supported_modes({climate::CLIMATE_MODE_OFF, climate::CLIMATE_MODE_COOL, climate::CLIMATE_MODE_HEAT, + climate::CLIMATE_MODE_FAN_ONLY, climate::CLIMATE_MODE_DRY, + climate::CLIMATE_MODE_AUTO}); + this->traits_.set_supported_fan_modes( + {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}); + this->traits_.set_supported_swing_modes({climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_BOTH, + climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL}); + this->traits_.set_supports_current_temperature(true); +} + +HaierClimateBase::~HaierClimateBase() {} + +void HaierClimateBase::set_phase_(ProtocolPhases phase) { + if (this->protocol_phase_ != phase) { +#if (HAIER_LOG_LEVEL > 4) + ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase)); +#else + ESP_LOGV(TAG, "Phase transition: %d => %d", (int) this->protocol_phase_, (int) phase); +#endif + this->protocol_phase_ = phase; + } +} + +bool HaierClimateBase::check_timeout_(std::chrono::steady_clock::time_point now, + std::chrono::steady_clock::time_point tpoint, size_t timeout) { + return std::chrono::duration_cast(now - tpoint).count() > timeout; +} + +bool HaierClimateBase::is_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { + return this->check_timeout_(now, this->last_request_timestamp_, DEFAULT_MESSAGES_INTERVAL_MS); +} + +bool HaierClimateBase::is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now) { + return this->check_timeout_(now, this->last_status_request_, STATUS_REQUEST_INTERVAL_MS); +} + +bool HaierClimateBase::is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now) { + return this->check_timeout_(now, this->control_request_timestamp_, CONTROL_TIMEOUT_MS); +} + +bool HaierClimateBase::is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now) { + return this->check_timeout_(now, this->last_request_timestamp_, CONTROL_MESSAGES_INTERVAL_MS); +} + +bool HaierClimateBase::is_protocol_initialisation_interval_exceded_(std::chrono::steady_clock::time_point now) { + return this->check_timeout_(now, this->last_request_timestamp_, PROTOCOL_INITIALIZATION_INTERVAL); +} + +bool HaierClimateBase::get_display_state() const { return this->display_status_; } + +void HaierClimateBase::set_display_state(bool state) { + if (this->display_status_ != state) { + this->display_status_ = state; + this->set_force_send_control_(true); + } +} + +bool HaierClimateBase::get_health_mode() const { return this->health_mode_; } + +void HaierClimateBase::set_health_mode(bool state) { + if (this->health_mode_ != state) { + this->health_mode_ = state; + this->set_force_send_control_(true); + } +} + +void HaierClimateBase::send_power_on_command() { this->action_request_ = ActionRequest::TURN_POWER_ON; } + +void HaierClimateBase::send_power_off_command() { this->action_request_ = ActionRequest::TURN_POWER_OFF; } + +void HaierClimateBase::toggle_power() { this->action_request_ = ActionRequest::TOGGLE_POWER; } +void HaierClimateBase::set_supported_swing_modes(const std::set &modes) { + this->traits_.set_supported_swing_modes(modes); + this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_OFF); // Always available + this->traits_.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL); // Always available +} + +void HaierClimateBase::set_supported_modes(const std::set &modes) { + this->traits_.set_supported_modes(modes); + this->traits_.add_supported_mode(climate::CLIMATE_MODE_OFF); // Always available + this->traits_.add_supported_mode(climate::CLIMATE_MODE_AUTO); // Always available +} + +haier_protocol::HandlerError HaierClimateBase::answer_preprocess_(uint8_t request_message_type, + uint8_t expected_request_message_type, + uint8_t answer_message_type, + uint8_t expected_answer_message_type, + ProtocolPhases expected_phase) { + haier_protocol::HandlerError result = haier_protocol::HandlerError::HANDLER_OK; + if ((expected_request_message_type != NO_COMMAND) && (request_message_type != expected_request_message_type)) + result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + if ((expected_answer_message_type != NO_COMMAND) && (answer_message_type != expected_answer_message_type)) + result = haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + if ((expected_phase != ProtocolPhases::UNKNOWN) && (expected_phase != this->protocol_phase_)) + result = haier_protocol::HandlerError::UNEXPECTED_MESSAGE; + if (is_message_invalid(answer_message_type)) + result = haier_protocol::HandlerError::INVALID_ANSWER; + return result; +} + +haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(uint8_t request_type) { +#if (HAIER_LOG_LEVEL > 4) + ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", request_type, phase_to_string_(this->protocol_phase_)); +#else + ESP_LOGW(TAG, "Answer timeout for command %02X, phase %d", request_type, (int) this->protocol_phase_); +#endif + if (this->protocol_phase_ > ProtocolPhases::IDLE) { + this->set_phase_(ProtocolPhases::IDLE); + } else { + this->set_phase_(ProtocolPhases::SENDING_INIT_1); + } + return haier_protocol::HandlerError::HANDLER_OK; +} + +void HaierClimateBase::setup() { + ESP_LOGI(TAG, "Haier initialization..."); + // Set timestamp here to give AC time to boot + this->last_request_timestamp_ = std::chrono::steady_clock::now(); + this->set_phase_(ProtocolPhases::SENDING_INIT_1); + this->set_answers_handlers(); + this->haier_protocol_.set_default_timeout_handler( + std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1)); +} + +void HaierClimateBase::dump_config() { + LOG_CLIMATE("", "Haier Climate", this); + ESP_LOGCONFIG(TAG, " Device communication status: %s", + (this->protocol_phase_ >= ProtocolPhases::IDLE) ? "established" : "none"); +} + +void HaierClimateBase::loop() { + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + if ((std::chrono::duration_cast(now - this->last_valid_status_timestamp_).count() > + COMMUNICATION_TIMEOUT_MS) || + (this->reset_protocol_request_)) { + if (this->protocol_phase_ >= ProtocolPhases::IDLE) { + // No status too long, reseting protocol + if (this->reset_protocol_request_) { + this->reset_protocol_request_ = false; + ESP_LOGW(TAG, "Protocol reset requested"); + } else { + ESP_LOGW(TAG, "Communication timeout, reseting protocol"); + } + this->last_valid_status_timestamp_ = now; + this->set_force_send_control_(false); + if (this->hvac_settings_.valid) + this->hvac_settings_.reset(); + this->set_phase_(ProtocolPhases::SENDING_INIT_1); + return; + } else { + // No need to reset protocol if we didn't pass initialization phase + this->last_valid_status_timestamp_ = now; + } + }; + if ((this->protocol_phase_ == ProtocolPhases::IDLE) || + (this->protocol_phase_ == ProtocolPhases::SENDING_STATUS_REQUEST) || + (this->protocol_phase_ == ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST) || + (this->protocol_phase_ == ProtocolPhases::SENDING_SIGNAL_LEVEL)) { + // If control message or action is pending we should send it ASAP unless we are in initialisation + // procedure or waiting for an answer + if (this->action_request_ != ActionRequest::NO_ACTION) { + this->process_pending_action(); + } else if (this->hvac_settings_.valid || this->force_send_control_) { + ESP_LOGV(TAG, "Control packet is pending..."); + this->set_phase_(ProtocolPhases::SENDING_CONTROL); + } + } + this->process_phase(now); + this->haier_protocol_.loop(); +} + +void HaierClimateBase::process_pending_action() { + ActionRequest request = this->action_request_; + if (this->action_request_ == ActionRequest::TOGGLE_POWER) { + request = this->mode == CLIMATE_MODE_OFF ? ActionRequest::TURN_POWER_ON : ActionRequest::TURN_POWER_OFF; + } + switch (request) { + case ActionRequest::TURN_POWER_ON: + this->set_phase_(ProtocolPhases::SENDING_POWER_ON_COMMAND); + break; + case ActionRequest::TURN_POWER_OFF: + this->set_phase_(ProtocolPhases::SENDING_POWER_OFF_COMMAND); + break; + case ActionRequest::TOGGLE_POWER: + case ActionRequest::NO_ACTION: + // shouldn't get here, do nothing + break; + default: + ESP_LOGW(TAG, "Unsupported action: %d", (uint8_t) this->action_request_); + break; + } + this->action_request_ = ActionRequest::NO_ACTION; +} + +ClimateTraits HaierClimateBase::traits() { return traits_; } + +void HaierClimateBase::control(const ClimateCall &call) { + ESP_LOGD("Control", "Control call"); + if (this->protocol_phase_ < ProtocolPhases::IDLE) { + ESP_LOGW(TAG, "Can't send control packet, first poll answer not received"); + return; // cancel the control, we cant do it without a poll answer. + } + if (this->hvac_settings_.valid) { + ESP_LOGW(TAG, "Overriding old valid settings before they were applied!"); + } + { + if (call.get_mode().has_value()) + this->hvac_settings_.mode = call.get_mode(); + if (call.get_fan_mode().has_value()) + this->hvac_settings_.fan_mode = call.get_fan_mode(); + if (call.get_swing_mode().has_value()) + this->hvac_settings_.swing_mode = call.get_swing_mode(); + if (call.get_target_temperature().has_value()) + this->hvac_settings_.target_temperature = call.get_target_temperature(); + if (call.get_preset().has_value()) + this->hvac_settings_.preset = call.get_preset(); + this->hvac_settings_.valid = true; + } + this->first_control_attempt_ = true; +} + +void HaierClimateBase::HvacSettings::reset() { + this->valid = false; + this->mode.reset(); + this->fan_mode.reset(); + this->swing_mode.reset(); + this->target_temperature.reset(); + this->preset.reset(); +} + +void HaierClimateBase::set_force_send_control_(bool status) { + this->force_send_control_ = status; + if (status) { + this->first_control_attempt_ = true; + } +} + +void HaierClimateBase::send_message_(const haier_protocol::HaierMessage &command, bool use_crc) { + this->haier_protocol_.send_message(command, use_crc); + this->last_request_timestamp_ = std::chrono::steady_clock::now(); +} + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h new file mode 100644 index 0000000000..046b59af96 --- /dev/null +++ b/esphome/components/haier/haier_base.h @@ -0,0 +1,142 @@ +#pragma once + +#include +#include +#include "esphome/components/climate/climate.h" +#include "esphome/components/uart/uart.h" +// HaierProtocol +#include + +namespace esphome { +namespace haier { + +enum class ActionRequest : uint8_t { + NO_ACTION = 0, + TURN_POWER_ON = 1, + TURN_POWER_OFF = 2, + TOGGLE_POWER = 3, + START_SELF_CLEAN = 4, // only hOn + START_STERI_CLEAN = 5, // only hOn +}; + +class HaierClimateBase : public esphome::Component, + public esphome::climate::Climate, + public esphome::uart::UARTDevice, + public haier_protocol::ProtocolStream { + public: + HaierClimateBase(); + HaierClimateBase(const HaierClimateBase &) = delete; + HaierClimateBase &operator=(const HaierClimateBase &) = delete; + ~HaierClimateBase(); + void setup() override; + void loop() override; + void control(const esphome::climate::ClimateCall &call) override; + void dump_config() override; + float get_setup_priority() const override { return esphome::setup_priority::HARDWARE; } + void set_fahrenheit(bool fahrenheit); + void set_display_state(bool state); + bool get_display_state() const; + void set_health_mode(bool state); + bool get_health_mode() const; + void send_power_on_command(); + void send_power_off_command(); + void toggle_power(); + void reset_protocol() { this->reset_protocol_request_ = true; }; + void set_supported_modes(const std::set &modes); + void set_supported_swing_modes(const std::set &modes); + size_t available() noexcept override { return esphome::uart::UARTDevice::available(); }; + size_t read_array(uint8_t *data, size_t len) noexcept override { + return esphome::uart::UARTDevice::read_array(data, len) ? len : 0; + }; + void write_array(const uint8_t *data, size_t len) noexcept override { + esphome::uart::UARTDevice::write_array(data, len); + }; + bool can_send_message() const { return haier_protocol_.get_outgoing_queue_size() == 0; }; + + protected: + enum class ProtocolPhases { + UNKNOWN = -1, + // INITIALIZATION + SENDING_INIT_1 = 0, + WAITING_ANSWER_INIT_1 = 1, + SENDING_INIT_2 = 2, + WAITING_ANSWER_INIT_2 = 3, + SENDING_FIRST_STATUS_REQUEST = 4, + WAITING_FIRST_STATUS_ANSWER = 5, + SENDING_ALARM_STATUS_REQUEST = 6, + WAITING_ALARM_STATUS_ANSWER = 7, + // FUNCTIONAL STATE + IDLE = 8, + SENDING_STATUS_REQUEST = 9, + WAITING_STATUS_ANSWER = 10, + SENDING_UPDATE_SIGNAL_REQUEST = 11, + WAITING_UPDATE_SIGNAL_ANSWER = 12, + SENDING_SIGNAL_LEVEL = 13, + WAITING_SIGNAL_LEVEL_ANSWER = 14, + SENDING_CONTROL = 15, + WAITING_CONTROL_ANSWER = 16, + SENDING_POWER_ON_COMMAND = 17, + WAITING_POWER_ON_ANSWER = 18, + SENDING_POWER_OFF_COMMAND = 19, + WAITING_POWER_OFF_ANSWER = 20, + NUM_PROTOCOL_PHASES + }; +#if (HAIER_LOG_LEVEL > 4) + const char *phase_to_string_(ProtocolPhases phase); +#endif + virtual void set_answers_handlers() = 0; + virtual void process_phase(std::chrono::steady_clock::time_point now) = 0; + virtual haier_protocol::HaierMessage get_control_message() = 0; + virtual bool is_message_invalid(uint8_t message_type) = 0; + virtual void process_pending_action(); + esphome::climate::ClimateTraits traits() override; + // Answers handlers + haier_protocol::HandlerError answer_preprocess_(uint8_t request_message_type, uint8_t expected_request_message_type, + uint8_t answer_message_type, uint8_t expected_answer_message_type, + ProtocolPhases expected_phase); + // Timeout handler + haier_protocol::HandlerError timeout_default_handler_(uint8_t request_type); + // Helper functions + void set_force_send_control_(bool status); + void send_message_(const haier_protocol::HaierMessage &command, bool use_crc); + void set_phase_(ProtocolPhases phase); + bool check_timeout_(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint, + size_t timeout); + bool is_message_interval_exceeded_(std::chrono::steady_clock::time_point now); + bool is_status_request_interval_exceeded_(std::chrono::steady_clock::time_point now); + bool is_control_message_timeout_exceeded_(std::chrono::steady_clock::time_point now); + bool is_control_message_interval_exceeded_(std::chrono::steady_clock::time_point now); + bool is_protocol_initialisation_interval_exceded_(std::chrono::steady_clock::time_point now); + + struct HvacSettings { + esphome::optional mode; + esphome::optional fan_mode; + esphome::optional swing_mode; + esphome::optional target_temperature; + esphome::optional preset; + bool valid; + HvacSettings() : valid(false){}; + void reset(); + }; + haier_protocol::ProtocolHandler haier_protocol_; + ProtocolPhases protocol_phase_; + ActionRequest action_request_; + uint8_t fan_mode_speed_; + uint8_t other_modes_fan_speed_; + bool display_status_; + bool health_mode_; + bool force_send_control_; + bool forced_publish_; + bool forced_request_status_; + bool first_control_attempt_; + bool reset_protocol_request_; + esphome::climate::ClimateTraits traits_; + HvacSettings hvac_settings_; + std::chrono::steady_clock::time_point last_request_timestamp_; // For interval between messages + std::chrono::steady_clock::time_point last_valid_status_timestamp_; // For protocol timeout + std::chrono::steady_clock::time_point last_status_request_; // To request AC status + std::chrono::steady_clock::time_point control_request_timestamp_; // To send control message +}; + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp new file mode 100644 index 0000000000..3016cda397 --- /dev/null +++ b/esphome/components/haier/hon_climate.cpp @@ -0,0 +1,857 @@ +#include +#include +#include "esphome/components/climate/climate.h" +#include "esphome/components/uart/uart.h" +#ifdef USE_WIFI +#include "esphome/components/wifi/wifi_component.h" +#endif +#include "hon_climate.h" +#include "hon_packet.h" + +using namespace esphome::climate; +using namespace esphome::uart; + +namespace esphome { +namespace haier { + +static const char *const TAG = "haier.climate"; +constexpr size_t SIGNAL_LEVEL_UPDATE_INTERVAL_MS = 10000; +constexpr int PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET = -64; + +hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) { + switch (direction) { + case AirflowVerticalDirection::HEALTH_UP: + return hon_protocol::VerticalSwingMode::HEALTH_UP; + case AirflowVerticalDirection::MAX_UP: + return hon_protocol::VerticalSwingMode::MAX_UP; + case AirflowVerticalDirection::UP: + return hon_protocol::VerticalSwingMode::UP; + case AirflowVerticalDirection::DOWN: + return hon_protocol::VerticalSwingMode::DOWN; + case AirflowVerticalDirection::HEALTH_DOWN: + return hon_protocol::VerticalSwingMode::HEALTH_DOWN; + default: + return hon_protocol::VerticalSwingMode::CENTER; + } +} + +hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDirection direction) { + switch (direction) { + case AirflowHorizontalDirection::MAX_LEFT: + return hon_protocol::HorizontalSwingMode::MAX_LEFT; + case AirflowHorizontalDirection::LEFT: + return hon_protocol::HorizontalSwingMode::LEFT; + case AirflowHorizontalDirection::RIGHT: + return hon_protocol::HorizontalSwingMode::RIGHT; + case AirflowHorizontalDirection::MAX_RIGHT: + return hon_protocol::HorizontalSwingMode::MAX_RIGHT; + default: + return hon_protocol::HorizontalSwingMode::CENTER; + } +} + +HonClimate::HonClimate() + : last_status_message_(new uint8_t[sizeof(hon_protocol::HaierPacketControl)]), + cleaning_status_(CleaningState::NO_CLEANING), + got_valid_outdoor_temp_(false), + hvac_hardware_info_available_(false), + hvac_functions_{false, false, false, false, false}, + use_crc_(hvac_functions_[2]), + active_alarms_{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + outdoor_sensor_(nullptr), + send_wifi_signal_(true) { + this->traits_.set_supported_presets({ + climate::CLIMATE_PRESET_NONE, + climate::CLIMATE_PRESET_ECO, + climate::CLIMATE_PRESET_BOOST, + climate::CLIMATE_PRESET_SLEEP, + }); + this->fan_mode_speed_ = (uint8_t) hon_protocol::FanMode::FAN_MID; + this->other_modes_fan_speed_ = (uint8_t) hon_protocol::FanMode::FAN_AUTO; +} + +HonClimate::~HonClimate() {} + +void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; } + +bool HonClimate::get_beeper_state() const { return this->beeper_status_; } + +void HonClimate::set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor) { this->outdoor_sensor_ = sensor; } + +AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; }; + +void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) { + if (direction > AirflowVerticalDirection::DOWN) { + this->vertical_direction_ = AirflowVerticalDirection::CENTER; + } else { + this->vertical_direction_ = direction; + } + this->set_force_send_control_(true); +} + +AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; } + +void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) { + if (direction > AirflowHorizontalDirection::RIGHT) { + this->horizontal_direction_ = AirflowHorizontalDirection::CENTER; + } else { + this->horizontal_direction_ = direction; + } + this->set_force_send_control_(true); +} + +std::string HonClimate::get_cleaning_status_text() const { + switch (this->cleaning_status_) { + case CleaningState::SELF_CLEAN: + return "Self clean"; + case CleaningState::STERI_CLEAN: + return "56°C Steri-Clean"; + default: + return "No cleaning"; + } +} + +CleaningState HonClimate::get_cleaning_status() const { return this->cleaning_status_; } + +void HonClimate::start_self_cleaning() { + if (this->cleaning_status_ == CleaningState::NO_CLEANING) { + ESP_LOGI(TAG, "Sending self cleaning start request"); + this->action_request_ = ActionRequest::START_SELF_CLEAN; + this->set_force_send_control_(true); + } +} + +void HonClimate::start_steri_cleaning() { + if (this->cleaning_status_ == CleaningState::NO_CLEANING) { + ESP_LOGI(TAG, "Sending steri cleaning start request"); + this->action_request_ = ActionRequest::START_STERI_CLEAN; + this->set_force_send_control_(true); + } +} + +void HonClimate::set_send_wifi(bool send_wifi) { this->send_wifi_signal_ = send_wifi; } + +haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size) { + haier_protocol::HandlerError result = this->answer_preprocess_( + request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, message_type, + (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_1); + if (result == haier_protocol::HandlerError::HANDLER_OK) { + if (data_size < sizeof(hon_protocol::DeviceVersionAnswer)) { + // Wrong structure + this->set_phase_(ProtocolPhases::SENDING_INIT_1); + return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + } + // All OK + hon_protocol::DeviceVersionAnswer *answr = (hon_protocol::DeviceVersionAnswer *) data; + char tmp[9]; + tmp[8] = 0; + strncpy(tmp, answr->protocol_version, 8); + this->hvac_protocol_version_ = std::string(tmp); + strncpy(tmp, answr->software_version, 8); + this->hvac_software_version_ = std::string(tmp); + strncpy(tmp, answr->hardware_version, 8); + this->hvac_hardware_version_ = std::string(tmp); + strncpy(tmp, answr->device_name, 8); + this->hvac_device_name_ = std::string(tmp); + this->hvac_functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support + this->hvac_functions_[1] = (answr->functions[1] & 0x02) != 0; // controller-device mode support + this->hvac_functions_[2] = (answr->functions[1] & 0x04) != 0; // crc support + this->hvac_functions_[3] = (answr->functions[1] & 0x08) != 0; // multiple AC support + this->hvac_functions_[4] = (answr->functions[1] & 0x20) != 0; // roles support + this->hvac_hardware_info_available_ = true; + this->set_phase_(ProtocolPhases::SENDING_INIT_2); + return result; + } else { + this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); + return result; + } +} + +haier_protocol::HandlerError HonClimate::get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size) { + haier_protocol::HandlerError result = this->answer_preprocess_( + request_type, (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID, message_type, + (uint8_t) hon_protocol::FrameType::GET_DEVICE_ID_RESPONSE, ProtocolPhases::WAITING_ANSWER_INIT_2); + if (result == haier_protocol::HandlerError::HANDLER_OK) { + this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + return result; + } else { + this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); + return result; + } +} + +haier_protocol::HandlerError HonClimate::status_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size) { + haier_protocol::HandlerError result = + this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::CONTROL, message_type, + (uint8_t) hon_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); + if (result == haier_protocol::HandlerError::HANDLER_OK) { + result = this->process_status_message_(data, data_size); + if (result != haier_protocol::HandlerError::HANDLER_OK) { + ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); + this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); + } else { + if (data_size >= sizeof(hon_protocol::HaierPacketControl) + 2) { + memcpy(this->last_status_message_.get(), data + 2, sizeof(hon_protocol::HaierPacketControl)); + } else { + ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, + sizeof(hon_protocol::HaierPacketControl)); + } + if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { + ESP_LOGI(TAG, "First HVAC status received"); + this->set_phase_(ProtocolPhases::SENDING_ALARM_STATUS_REQUEST); + } else if ((this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) || + (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_ON_ANSWER) || + (this->protocol_phase_ == ProtocolPhases::WAITING_POWER_OFF_ANSWER)) { + this->set_phase_(ProtocolPhases::IDLE); + } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { + this->set_phase_(ProtocolPhases::IDLE); + this->set_force_send_control_(false); + if (this->hvac_settings_.valid) + this->hvac_settings_.reset(); + } + } + return result; + } else { + this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_INIT_1); + return result; + } +} + +haier_protocol::HandlerError HonClimate::get_management_information_answer_handler_(uint8_t request_type, + uint8_t message_type, + const uint8_t *data, + size_t data_size) { + haier_protocol::HandlerError result = + this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION, + message_type, (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION_RESPONSE, + ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); + if (result == haier_protocol::HandlerError::HANDLER_OK) { + this->set_phase_(ProtocolPhases::SENDING_SIGNAL_LEVEL); + return result; + } else { + this->set_phase_(ProtocolPhases::IDLE); + return result; + } +} + +haier_protocol::HandlerError HonClimate::report_network_status_answer_handler_(uint8_t request_type, + uint8_t message_type, + const uint8_t *data, size_t data_size) { + haier_protocol::HandlerError result = + this->answer_preprocess_(request_type, (uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, message_type, + (uint8_t) hon_protocol::FrameType::CONFIRM, ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); + this->set_phase_(ProtocolPhases::IDLE); + return result; +} + +haier_protocol::HandlerError HonClimate::get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size) { + if (request_type == (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS) { + if (message_type != (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS_RESPONSE) { + // Unexpected answer to request + this->set_phase_(ProtocolPhases::IDLE); + return haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + } + if (this->protocol_phase_ != ProtocolPhases::WAITING_ALARM_STATUS_ANSWER) { + // Don't expect this answer now + this->set_phase_(ProtocolPhases::IDLE); + return haier_protocol::HandlerError::UNEXPECTED_MESSAGE; + } + memcpy(this->active_alarms_, data + 2, 8); + this->set_phase_(ProtocolPhases::IDLE); + return haier_protocol::HandlerError::HANDLER_OK; + } else { + this->set_phase_(ProtocolPhases::IDLE); + return haier_protocol::HandlerError::UNSUPORTED_MESSAGE; + } +} + +void HonClimate::set_answers_handlers() { + // Set handlers + this->haier_protocol_.set_answer_handler( + (uint8_t) (hon_protocol::FrameType::GET_DEVICE_VERSION), + std::bind(&HonClimate::get_device_version_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_answer_handler( + (uint8_t) (hon_protocol::FrameType::GET_DEVICE_ID), + std::bind(&HonClimate::get_device_id_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_answer_handler( + (uint8_t) (hon_protocol::FrameType::CONTROL), + std::bind(&HonClimate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, + std::placeholders::_4)); + this->haier_protocol_.set_answer_handler( + (uint8_t) (hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION), + std::bind(&HonClimate::get_management_information_answer_handler_, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_answer_handler( + (uint8_t) (hon_protocol::FrameType::GET_ALARM_STATUS), + std::bind(&HonClimate::get_alarm_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); + this->haier_protocol_.set_answer_handler( + (uint8_t) (hon_protocol::FrameType::REPORT_NETWORK_STATUS), + std::bind(&HonClimate::report_network_status_answer_handler_, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); +} + +void HonClimate::dump_config() { + HaierClimateBase::dump_config(); + ESP_LOGCONFIG(TAG, " Protocol version: hOn"); + if (this->hvac_hardware_info_available_) { + ESP_LOGCONFIG(TAG, " Device protocol version: %s", this->hvac_protocol_version_.c_str()); + ESP_LOGCONFIG(TAG, " Device software version: %s", this->hvac_software_version_.c_str()); + ESP_LOGCONFIG(TAG, " Device hardware version: %s", this->hvac_hardware_version_.c_str()); + ESP_LOGCONFIG(TAG, " Device name: %s", this->hvac_device_name_.c_str()); + ESP_LOGCONFIG(TAG, " Device features:%s%s%s%s%s", (this->hvac_functions_[0] ? " interactive" : ""), + (this->hvac_functions_[1] ? " controller-device" : ""), (this->hvac_functions_[2] ? " crc" : ""), + (this->hvac_functions_[3] ? " multinode" : ""), (this->hvac_functions_[4] ? " role" : "")); + ESP_LOGCONFIG(TAG, " Active alarms: %s", buf_to_hex(this->active_alarms_, sizeof(this->active_alarms_)).c_str()); + } +} + +void HonClimate::process_phase(std::chrono::steady_clock::time_point now) { + switch (this->protocol_phase_) { + case ProtocolPhases::SENDING_INIT_1: + if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) { + this->hvac_hardware_info_available_ = false; + // Indicate device capabilities: + // bit 0 - if 1 module support interactive mode + // bit 1 - if 1 module support controller-device mode + // bit 2 - if 1 module support crc + // bit 3 - if 1 module support multiple devices + // bit 4..bit 15 - not used + uint8_t module_capabilities[2] = {0b00000000, 0b00000111}; + static const haier_protocol::HaierMessage DEVICE_VERSION_REQUEST( + (uint8_t) hon_protocol::FrameType::GET_DEVICE_VERSION, module_capabilities, sizeof(module_capabilities)); + this->send_message_(DEVICE_VERSION_REQUEST, this->use_crc_); + this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_1); + } + break; + case ProtocolPhases::SENDING_INIT_2: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + static const haier_protocol::HaierMessage DEVICEID_REQUEST((uint8_t) hon_protocol::FrameType::GET_DEVICE_ID); + this->send_message_(DEVICEID_REQUEST, this->use_crc_); + this->set_phase_(ProtocolPhases::WAITING_ANSWER_INIT_2); + } + break; + case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: + case ProtocolPhases::SENDING_STATUS_REQUEST: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + static const haier_protocol::HaierMessage STATUS_REQUEST( + (uint8_t) hon_protocol::FrameType::CONTROL, (uint16_t) hon_protocol::SubcomandsControl::GET_USER_DATA); + this->send_message_(STATUS_REQUEST, this->use_crc_); + this->last_status_request_ = now; + this->set_phase_((ProtocolPhases) ((uint8_t) this->protocol_phase_ + 1)); + } + break; +#ifdef USE_WIFI + case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + static const haier_protocol::HaierMessage UPDATE_SIGNAL_REQUEST( + (uint8_t) hon_protocol::FrameType::GET_MANAGEMENT_INFORMATION); + this->send_message_(UPDATE_SIGNAL_REQUEST, this->use_crc_); + this->last_signal_request_ = now; + this->set_phase_(ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER); + } + break; + case ProtocolPhases::SENDING_SIGNAL_LEVEL: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + static uint8_t wifi_status_data[4] = {0x00, 0x00, 0x00, 0x00}; + if (wifi::global_wifi_component->is_connected()) { + wifi_status_data[1] = 0; + int8_t rssi = wifi::global_wifi_component->wifi_rssi(); + wifi_status_data[3] = uint8_t((128 + rssi) / 1.28f); + ESP_LOGD(TAG, "WiFi signal is: %ddBm => %d%%", rssi, wifi_status_data[3]); + } else { + ESP_LOGD(TAG, "WiFi is not connected"); + wifi_status_data[1] = 1; + wifi_status_data[3] = 0; + } + haier_protocol::HaierMessage wifi_status_request((uint8_t) hon_protocol::FrameType::REPORT_NETWORK_STATUS, + wifi_status_data, sizeof(wifi_status_data)); + this->send_message_(wifi_status_request, this->use_crc_); + this->set_phase_(ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER); + } + break; + case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: + case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: + break; +#else + case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: + case ProtocolPhases::SENDING_SIGNAL_LEVEL: + case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: + case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: + this->set_phase_(ProtocolPhases::IDLE); + break; +#endif + case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + static const haier_protocol::HaierMessage ALARM_STATUS_REQUEST( + (uint8_t) hon_protocol::FrameType::GET_ALARM_STATUS); + this->send_message_(ALARM_STATUS_REQUEST, this->use_crc_); + this->set_phase_(ProtocolPhases::WAITING_ALARM_STATUS_ANSWER); + } + break; + case ProtocolPhases::SENDING_CONTROL: + if (this->first_control_attempt_) { + this->control_request_timestamp_ = now; + this->first_control_attempt_ = false; + } + if (this->is_control_message_timeout_exceeded_(now)) { + ESP_LOGW(TAG, "Sending control packet timeout!"); + this->set_force_send_control_(false); + if (this->hvac_settings_.valid) + this->hvac_settings_.reset(); + this->forced_request_status_ = true; + this->forced_publish_ = true; + this->set_phase_(ProtocolPhases::IDLE); + } else if (this->can_send_message() && this->is_control_message_interval_exceeded_(now)) { + haier_protocol::HaierMessage control_message = get_control_message(); + this->send_message_(control_message, this->use_crc_); + ESP_LOGI(TAG, "Control packet sent"); + this->set_phase_(ProtocolPhases::WAITING_CONTROL_ANSWER); + } + break; + case ProtocolPhases::SENDING_POWER_ON_COMMAND: + case ProtocolPhases::SENDING_POWER_OFF_COMMAND: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + uint8_t pwr_cmd_buf[2] = {0x00, 0x00}; + if (this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND) + pwr_cmd_buf[1] = 0x01; + haier_protocol::HaierMessage power_cmd((uint8_t) hon_protocol::FrameType::CONTROL, + ((uint16_t) hon_protocol::SubcomandsControl::SET_SINGLE_PARAMETER) + 1, + pwr_cmd_buf, sizeof(pwr_cmd_buf)); + this->send_message_(power_cmd, this->use_crc_); + this->set_phase_(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND + ? ProtocolPhases::WAITING_POWER_ON_ANSWER + : ProtocolPhases::WAITING_POWER_OFF_ANSWER); + } + break; + + case ProtocolPhases::WAITING_ANSWER_INIT_1: + case ProtocolPhases::WAITING_ANSWER_INIT_2: + case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: + case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: + case ProtocolPhases::WAITING_STATUS_ANSWER: + case ProtocolPhases::WAITING_CONTROL_ANSWER: + case ProtocolPhases::WAITING_POWER_ON_ANSWER: + case ProtocolPhases::WAITING_POWER_OFF_ANSWER: + break; + case ProtocolPhases::IDLE: { + if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { + this->set_phase_(ProtocolPhases::SENDING_STATUS_REQUEST); + this->forced_request_status_ = false; + } +#ifdef USE_WIFI + else if (this->send_wifi_signal_ && + (std::chrono::duration_cast(now - this->last_signal_request_).count() > + SIGNAL_LEVEL_UPDATE_INTERVAL_MS)) + this->set_phase_(ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST); +#endif + } break; + default: + // Shouldn't get here +#if (HAIER_LOG_LEVEL > 4) + ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication", + phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_); +#else + ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); +#endif + this->set_phase_(ProtocolPhases::SENDING_INIT_1); + break; + } +} + +haier_protocol::HaierMessage HonClimate::get_control_message() { + uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)]; + memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl)); + hon_protocol::HaierPacketControl *out_data = (hon_protocol::HaierPacketControl *) control_out_buffer; + bool has_hvac_settings = false; + if (this->hvac_settings_.valid) { + has_hvac_settings = true; + HvacSettings climate_control; + climate_control = this->hvac_settings_; + if (climate_control.mode.has_value()) { + switch (climate_control.mode.value()) { + case CLIMATE_MODE_OFF: + out_data->ac_power = 0; + break; + case CLIMATE_MODE_AUTO: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::AUTO; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + case CLIMATE_MODE_HEAT: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::HEAT; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + case CLIMATE_MODE_DRY: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + case CLIMATE_MODE_FAN_ONLY: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::FAN; + out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode + // Disabling boost and eco mode for Fan only + out_data->quiet_mode = 0; + out_data->fast_mode = 0; + break; + case CLIMATE_MODE_COOL: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::COOL; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + default: + ESP_LOGE("Control", "Unsupported climate mode"); + break; + } + } + // Set fan speed, if we are in fan mode, reject auto in fan mode + if (climate_control.fan_mode.has_value()) { + switch (climate_control.fan_mode.value()) { + case CLIMATE_FAN_LOW: + out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_LOW; + break; + case CLIMATE_FAN_MEDIUM: + out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_MID; + break; + case CLIMATE_FAN_HIGH: + out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_HIGH; + break; + case CLIMATE_FAN_AUTO: + if (mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode + out_data->fan_mode = (uint8_t) hon_protocol::FanMode::FAN_AUTO; + break; + default: + ESP_LOGE("Control", "Unsupported fan mode"); + break; + } + } + // Set swing mode + if (climate_control.swing_mode.has_value()) { + switch (climate_control.swing_mode.value()) { + case CLIMATE_SWING_OFF: + out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_); + out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_); + break; + case CLIMATE_SWING_VERTICAL: + out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_); + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO; + break; + case CLIMATE_SWING_HORIZONTAL: + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO; + out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_); + break; + case CLIMATE_SWING_BOTH: + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO; + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO; + break; + } + } + if (climate_control.target_temperature.has_value()) { + out_data->set_point = + climate_control.target_temperature.value() - 16; // set the temperature at our offset, subtract 16. + } + if (out_data->ac_power == 0) { + // If AC is off - no presets alowed + out_data->quiet_mode = 0; + out_data->fast_mode = 0; + out_data->sleep_mode = 0; + } else if (climate_control.preset.has_value()) { + switch (climate_control.preset.value()) { + case CLIMATE_PRESET_NONE: + out_data->quiet_mode = 0; + out_data->fast_mode = 0; + out_data->sleep_mode = 0; + break; + case CLIMATE_PRESET_ECO: + // Eco is not supported in Fan only mode + out_data->quiet_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; + out_data->fast_mode = 0; + out_data->sleep_mode = 0; + break; + case CLIMATE_PRESET_BOOST: + out_data->quiet_mode = 0; + // Boost is not supported in Fan only mode + out_data->fast_mode = (this->mode != CLIMATE_MODE_FAN_ONLY) ? 1 : 0; + out_data->sleep_mode = 0; + break; + case CLIMATE_PRESET_AWAY: + out_data->quiet_mode = 0; + out_data->fast_mode = 0; + out_data->sleep_mode = 0; + break; + case CLIMATE_PRESET_SLEEP: + out_data->quiet_mode = 0; + out_data->fast_mode = 0; + out_data->sleep_mode = 1; + break; + default: + ESP_LOGE("Control", "Unsupported preset"); + break; + } + } + } else { + if (out_data->vertical_swing_mode != (uint8_t) hon_protocol::VerticalSwingMode::AUTO) + out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_); + if (out_data->horizontal_swing_mode != (uint8_t) hon_protocol::HorizontalSwingMode::AUTO) + out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_); + } + out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0; + control_out_buffer[4] = 0; // This byte should be cleared before setting values + out_data->display_status = this->display_status_ ? 1 : 0; + out_data->health_mode = this->health_mode_ ? 1 : 0; + switch (this->action_request_) { + case ActionRequest::START_SELF_CLEAN: + this->action_request_ = ActionRequest::NO_ACTION; + out_data->self_cleaning_status = 1; + out_data->steri_clean = 0; + out_data->set_point = 0x06; + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; + out_data->light_status = 0; + break; + case ActionRequest::START_STERI_CLEAN: + this->action_request_ = ActionRequest::NO_ACTION; + out_data->self_cleaning_status = 0; + out_data->steri_clean = 1; + out_data->set_point = 0x06; + out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::CENTER; + out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::CENTER; + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) hon_protocol::ConditioningMode::DRY; + out_data->light_status = 0; + break; + default: + // No change + break; + } + return haier_protocol::HaierMessage((uint8_t) hon_protocol::FrameType::CONTROL, + (uint16_t) hon_protocol::SubcomandsControl::SET_GROUP_PARAMETERS, + control_out_buffer, sizeof(hon_protocol::HaierPacketControl)); +} + +haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { + if (size < sizeof(hon_protocol::HaierStatus)) + return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + hon_protocol::HaierStatus packet; + if (size < sizeof(hon_protocol::HaierStatus)) + size = sizeof(hon_protocol::HaierStatus); + memcpy(&packet, packet_buffer, size); + if (packet.sensors.error_status != 0) { + ESP_LOGW(TAG, "HVAC error, code=0x%02X", packet.sensors.error_status); + } + if ((this->outdoor_sensor_ != nullptr) && (got_valid_outdoor_temp_ || (packet.sensors.outdoor_temperature > 0))) { + got_valid_outdoor_temp_ = true; + float otemp = (float) (packet.sensors.outdoor_temperature + PROTOCOL_OUTDOOR_TEMPERATURE_OFFSET); + if ((!this->outdoor_sensor_->has_state()) || (this->outdoor_sensor_->get_raw_state() != otemp)) + this->outdoor_sensor_->publish_state(otemp); + } + bool should_publish = false; + { + // Extra modes/presets + optional old_preset = this->preset; + if (packet.control.quiet_mode != 0) { + this->preset = CLIMATE_PRESET_ECO; + } else if (packet.control.fast_mode != 0) { + this->preset = CLIMATE_PRESET_BOOST; + } else if (packet.control.sleep_mode != 0) { + this->preset = CLIMATE_PRESET_SLEEP; + } else { + this->preset = CLIMATE_PRESET_NONE; + } + should_publish = should_publish || (!old_preset.has_value()) || (old_preset.value() != this->preset.value()); + } + { + // Target temperature + float old_target_temperature = this->target_temperature; + this->target_temperature = packet.control.set_point + 16.0f; + should_publish = should_publish || (old_target_temperature != this->target_temperature); + } + { + // Current temperature + float old_current_temperature = this->current_temperature; + this->current_temperature = packet.sensors.room_temperature / 2.0f; + should_publish = should_publish || (old_current_temperature != this->current_temperature); + } + { + // Fan mode + optional old_fan_mode = this->fan_mode; + // remember the fan speed we last had for climate vs fan + if (packet.control.ac_mode == (uint8_t) hon_protocol::ConditioningMode::FAN) { + if (packet.control.fan_mode != (uint8_t) hon_protocol::FanMode::FAN_AUTO) + this->fan_mode_speed_ = packet.control.fan_mode; + } else { + this->other_modes_fan_speed_ = packet.control.fan_mode; + } + switch (packet.control.fan_mode) { + case (uint8_t) hon_protocol::FanMode::FAN_AUTO: + if (packet.control.ac_mode != (uint8_t) hon_protocol::ConditioningMode::FAN) { + this->fan_mode = CLIMATE_FAN_AUTO; + } else { + // Shouldn't accept fan speed auto in fan-only mode even if AC reports it + ESP_LOGI(TAG, "Fan speed Auto is not supported in Fan only AC mode, ignoring"); + } + break; + case (uint8_t) hon_protocol::FanMode::FAN_MID: + this->fan_mode = CLIMATE_FAN_MEDIUM; + break; + case (uint8_t) hon_protocol::FanMode::FAN_LOW: + this->fan_mode = CLIMATE_FAN_LOW; + break; + case (uint8_t) hon_protocol::FanMode::FAN_HIGH: + this->fan_mode = CLIMATE_FAN_HIGH; + break; + } + should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); + } + { + // Display status + // should be before "Climate mode" because it is changing this->mode + if (packet.control.ac_power != 0) { + // if AC is off display status always ON so process it only when AC is on + bool disp_status = packet.control.display_status != 0; + if (disp_status != this->display_status_) { + // Do something only if display status changed + if (this->mode == CLIMATE_MODE_OFF) { + // AC just turned on from remote need to turn off display + this->set_force_send_control_(true); + } else { + this->display_status_ = disp_status; + } + } + } + } + { + // Health mode + bool old_health_mode = this->health_mode_; + this->health_mode_ = packet.control.health_mode == 1; + should_publish = should_publish || (old_health_mode != this->health_mode_); + } + { + CleaningState new_cleaning; + if (packet.control.steri_clean == 1) { + // Steri-cleaning + new_cleaning = CleaningState::STERI_CLEAN; + } else if (packet.control.self_cleaning_status == 1) { + // Self-cleaning + new_cleaning = CleaningState::SELF_CLEAN; + } else { + // No cleaning + new_cleaning = CleaningState::NO_CLEANING; + } + if (new_cleaning != this->cleaning_status_) { + ESP_LOGD(TAG, "Cleaning status change: %d => %d", (uint8_t) this->cleaning_status_, (uint8_t) new_cleaning); + if (new_cleaning == CleaningState::NO_CLEANING) { + // Turnuin AC off after cleaning + this->action_request_ = ActionRequest::TURN_POWER_OFF; + } + this->cleaning_status_ = new_cleaning; + } + } + { + // Climate mode + ClimateMode old_mode = this->mode; + if (packet.control.ac_power == 0) { + this->mode = CLIMATE_MODE_OFF; + } else { + // Check current hvac mode + switch (packet.control.ac_mode) { + case (uint8_t) hon_protocol::ConditioningMode::COOL: + this->mode = CLIMATE_MODE_COOL; + break; + case (uint8_t) hon_protocol::ConditioningMode::HEAT: + this->mode = CLIMATE_MODE_HEAT; + break; + case (uint8_t) hon_protocol::ConditioningMode::DRY: + this->mode = CLIMATE_MODE_DRY; + break; + case (uint8_t) hon_protocol::ConditioningMode::FAN: + this->mode = CLIMATE_MODE_FAN_ONLY; + break; + case (uint8_t) hon_protocol::ConditioningMode::AUTO: + this->mode = CLIMATE_MODE_AUTO; + break; + } + } + should_publish = should_publish || (old_mode != this->mode); + } + { + // Swing mode + ClimateSwingMode old_swing_mode = this->swing_mode; + if (packet.control.horizontal_swing_mode == (uint8_t) hon_protocol::HorizontalSwingMode::AUTO) { + if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) { + this->swing_mode = CLIMATE_SWING_BOTH; + } else { + this->swing_mode = CLIMATE_SWING_HORIZONTAL; + } + } else { + if (packet.control.vertical_swing_mode == (uint8_t) hon_protocol::VerticalSwingMode::AUTO) { + this->swing_mode = CLIMATE_SWING_VERTICAL; + } else { + this->swing_mode = CLIMATE_SWING_OFF; + } + } + should_publish = should_publish || (old_swing_mode != this->swing_mode); + } + this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); + if (this->forced_publish_ || should_publish) { +#if (HAIER_LOG_LEVEL > 4) + std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now(); +#endif + this->publish_state(); +#if (HAIER_LOG_LEVEL > 4) + ESP_LOGV(TAG, "Publish delay: %lld ms", + std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - + _publish_start) + .count()); +#endif + this->forced_publish_ = false; + } + if (should_publish) { + ESP_LOGI(TAG, "HVAC values changed"); + } + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "HVAC Mode = 0x%X", packet.control.ac_mode); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Fan speed Status = 0x%X", packet.control.fan_mode); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing_mode); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Vertical Swing Status = 0x%X", packet.control.vertical_swing_mode); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Set Point Status = 0x%X", packet.control.set_point); + return haier_protocol::HandlerError::HANDLER_OK; +} + +bool HonClimate::is_message_invalid(uint8_t message_type) { + return message_type == (uint8_t) hon_protocol::FrameType::INVALID; +} + +void HonClimate::process_pending_action() { + switch (this->action_request_) { + case ActionRequest::START_SELF_CLEAN: + case ActionRequest::START_STERI_CLEAN: + // Will reset action with control message sending + this->set_phase_(ProtocolPhases::SENDING_CONTROL); + break; + default: + HaierClimateBase::process_pending_action(); + break; + } +} + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h new file mode 100644 index 0000000000..ab913f44e2 --- /dev/null +++ b/esphome/components/haier/hon_climate.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include "esphome/components/sensor/sensor.h" +#include "haier_base.h" + +namespace esphome { +namespace haier { + +enum class AirflowVerticalDirection : uint8_t { + HEALTH_UP = 0, + MAX_UP = 1, + UP = 2, + CENTER = 3, + DOWN = 4, + HEALTH_DOWN = 5, +}; + +enum class AirflowHorizontalDirection : uint8_t { + MAX_LEFT = 0, + LEFT = 1, + CENTER = 2, + RIGHT = 3, + MAX_RIGHT = 4, +}; + +enum class CleaningState : uint8_t { + NO_CLEANING = 0, + SELF_CLEAN = 1, + STERI_CLEAN = 2, +}; + +class HonClimate : public HaierClimateBase { + public: + HonClimate(); + HonClimate(const HonClimate &) = delete; + HonClimate &operator=(const HonClimate &) = delete; + ~HonClimate(); + void dump_config() override; + void set_beeper_state(bool state); + bool get_beeper_state() const; + void set_outdoor_temperature_sensor(esphome::sensor::Sensor *sensor); + AirflowVerticalDirection get_vertical_airflow() const; + void set_vertical_airflow(AirflowVerticalDirection direction); + AirflowHorizontalDirection get_horizontal_airflow() const; + void set_horizontal_airflow(AirflowHorizontalDirection direction); + std::string get_cleaning_status_text() const; + CleaningState get_cleaning_status() const; + void start_self_cleaning(); + void start_steri_cleaning(); + void set_send_wifi(bool send_wifi); + + protected: + void set_answers_handlers() override; + void process_phase(std::chrono::steady_clock::time_point now) override; + haier_protocol::HaierMessage get_control_message() override; + bool is_message_invalid(uint8_t message_type) override; + void process_pending_action() override; + + // Answers handlers + haier_protocol::HandlerError get_device_version_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + haier_protocol::HandlerError get_device_id_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, + size_t data_size); + haier_protocol::HandlerError get_management_information_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + haier_protocol::HandlerError report_network_status_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + haier_protocol::HandlerError get_alarm_status_answer_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size); + // Helper functions + haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); + std::unique_ptr last_status_message_; + bool beeper_status_; + CleaningState cleaning_status_; + bool got_valid_outdoor_temp_; + AirflowVerticalDirection vertical_direction_; + AirflowHorizontalDirection horizontal_direction_; + bool hvac_hardware_info_available_; + std::string hvac_protocol_version_; + std::string hvac_software_version_; + std::string hvac_hardware_version_; + std::string hvac_device_name_; + bool hvac_functions_[5]; + bool &use_crc_; + uint8_t active_alarms_[8]; + esphome::sensor::Sensor *outdoor_sensor_; + bool send_wifi_signal_; + std::chrono::steady_clock::time_point last_signal_request_; // To send WiFI signal level +}; + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/hon_packet.h b/esphome/components/haier/hon_packet.h new file mode 100644 index 0000000000..d572ce80d9 --- /dev/null +++ b/esphome/components/haier/hon_packet.h @@ -0,0 +1,228 @@ +#pragma once + +#include + +namespace esphome { +namespace haier { +namespace hon_protocol { + +enum class VerticalSwingMode : uint8_t { + HEALTH_UP = 0x01, + MAX_UP = 0x02, + HEALTH_DOWN = 0x03, + UP = 0x04, + CENTER = 0x06, + DOWN = 0x08, + AUTO = 0x0C +}; + +enum class HorizontalSwingMode : uint8_t { + CENTER = 0x00, + MAX_LEFT = 0x03, + LEFT = 0x04, + RIGHT = 0x05, + MAX_RIGHT = 0x06, + AUTO = 0x07 +}; + +enum class ConditioningMode : uint8_t { + AUTO = 0x00, + COOL = 0x01, + DRY = 0x02, + HEALTHY_DRY = 0x03, + HEAT = 0x04, + ENERGY_SAVING = 0x05, + FAN = 0x06 +}; + +enum class SpecialMode : uint8_t { NONE = 0x00, ELDERLY = 0x01, CHILDREN = 0x02, PREGNANT = 0x03 }; + +enum class FanMode : uint8_t { FAN_HIGH = 0x01, FAN_MID = 0x02, FAN_LOW = 0x03, FAN_AUTO = 0x05 }; + +struct HaierPacketControl { + // Control bytes starts here + // 10 + uint8_t set_point; // Target temperature with 16°C offset (0x00 = 16°C) + // 11 + uint8_t vertical_swing_mode : 4; // See enum VerticalSwingMode + uint8_t : 0; + // 12 + uint8_t fan_mode : 3; // See enum FanMode + uint8_t special_mode : 2; // See enum SpecialMode + uint8_t ac_mode : 3; // See enum ConditioningMode + // 13 + uint8_t : 8; + // 14 + uint8_t ten_degree : 1; // 10 degree status + uint8_t display_status : 1; // If 0 disables AC's display + uint8_t half_degree : 1; // Use half degree + uint8_t intelegence_status : 1; // Intelligence status + uint8_t pmv_status : 1; // Comfort/PMV status + uint8_t use_fahrenheit : 1; // Use Fahrenheit instead of Celsius + uint8_t : 1; + uint8_t steri_clean : 1; + // 15 + uint8_t ac_power : 1; // Is ac on or off + uint8_t health_mode : 1; // Health mode (negative ions) on or off + uint8_t electric_heating_status : 1; // Electric heating status + uint8_t fast_mode : 1; // Fast mode + uint8_t quiet_mode : 1; // Quiet mode + uint8_t sleep_mode : 1; // Sleep mode + uint8_t lock_remote : 1; // Disable remote + uint8_t beeper_status : 1; // If 1 disables AC's command feedback beeper (need to be set on every control command) + // 16 + uint8_t target_humidity; // Target humidity (0=30% .. 3C=90%, step = 1%) + // 17 + uint8_t horizontal_swing_mode : 3; // See enum HorizontalSwingMode + uint8_t : 3; + uint8_t human_sensing_status : 2; // Human sensing status + // 18 + uint8_t change_filter : 1; // Filter need replacement + uint8_t : 0; + // 19 + uint8_t fresh_air_status : 1; // Fresh air status + uint8_t humidification_status : 1; // Humidification status + uint8_t pm2p5_cleaning_status : 1; // PM2.5 cleaning status + uint8_t ch2o_cleaning_status : 1; // CH2O cleaning status + uint8_t self_cleaning_status : 1; // Self cleaning status + uint8_t light_status : 1; // Light status + uint8_t energy_saving_status : 1; // Energy saving status + uint8_t cleaning_time_status : 1; // Cleaning time (0 - accumulation, 1 - clear) +}; + +struct HaierPacketSensors { + // 20 + uint8_t room_temperature; // 0.5°C step + // 21 + uint8_t room_humidity; // 0%-100% with 1% step + // 22 + uint8_t outdoor_temperature; // 1°C step, -64°C offset (0=-64°C) + // 23 + uint8_t pm2p5_level : 2; // Indoor PM2.5 grade (00: Excellent, 01: good, 02: Medium, 03: Bad) + uint8_t air_quality : 2; // Air quality grade (00: Excellent, 01: good, 02: Medium, 03: Bad) + uint8_t human_sensing : 2; // Human presence result (00: N/A, 01: not detected, 02: One, 03: Multiple) + uint8_t : 1; + uint8_t ac_type : 1; // 00 - Heat and cool, 01 - Cool only) + // 24 + uint8_t error_status; // See enum ErrorStatus + // 25 + uint8_t operation_source : 2; // who is controlling AC (00: Other, 01: Remote control, 02: Button, 03: ESP) + uint8_t operation_mode_hk : 2; // Homekit only, operation mode (00: Cool, 01: Dry, 02: Heat, 03: Fan) + uint8_t : 3; + uint8_t err_confirmation : 1; // If 1 clear error status + // 26 + uint16_t total_cleaning_time; // Cleaning cumulative time (1h step) + // 28 + uint16_t indoor_pm2p5_value; // Indoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) + // 30 + uint16_t outdoor_pm2p5_value; // Outdoor PM2.5 value (0 ug/m3 - 4095 ug/m3, 1 ug/m3 step) + // 32 + uint16_t ch2o_value; // Formaldehyde value (0 ug/m3 - 10000 ug/m3, 1 ug/m3 step) + // 34 + uint16_t voc_value; // VOC value (Volatile Organic Compounds) (0 ug/m3 - 1023 ug/m3, 1 ug/m3 step) + // 36 + uint16_t co2_value; // CO2 value (0 PPM - 10000 PPM, 1 PPM step) +}; + +struct HaierStatus { + uint16_t subcommand; + HaierPacketControl control; + HaierPacketSensors sensors; +}; + +struct DeviceVersionAnswer { + char protocol_version[8]; + char software_version[8]; + uint8_t encryption[3]; + char hardware_version[8]; + uint8_t : 8; + char device_name[8]; + uint8_t functions[2]; +}; + +// In this section comments: +// - module is the ESP32 control module (communication module in Haier protocol document) +// - device is the conditioner control board (network appliances in Haier protocol document) +enum class FrameType : uint8_t { + CONTROL = 0x01, // Requests or sets one or multiple parameters (module <-> device, required) + STATUS = 0x02, // Contains one or multiple parameters values, usually answer to control frame (module <-> device, + // required) + INVALID = 0x03, // Communication error indication (module <-> device, required) + ALARM_STATUS = 0x04, // Alarm status report (module <-> device, interactive, required) + CONFIRM = 0x05, // Acknowledgment, usually used to confirm reception of frame if there is no special answer (module + // <-> device, required) + REPORT = 0x06, // Report frame (module <-> device, interactive, required) + STOP_FAULT_ALARM = 0x09, // Stop fault alarm frame (module -> device, interactive, required) + SYSTEM_DOWNLIK = 0x11, // System downlink frame (module -> device, optional) + DEVICE_UPLINK = 0x12, // Device uplink frame (module <- device , interactive, optional) + SYSTEM_QUERY = 0x13, // System query frame (module -> device, optional) + SYSTEM_QUERY_RESPONSE = 0x14, // System query response frame (module <- device , optional) + DEVICE_QUERY = 0x15, // Device query frame (module <- device, optional) + DEVICE_QUERY_RESPONSE = 0x16, // Device query response frame (module -> device, optional) + GROUP_COMMAND = 0x60, // Group command frame (module -> device, interactive, optional) + GET_DEVICE_VERSION = 0x61, // Requests device version (module -> device, required) + GET_DEVICE_VERSION_RESPONSE = 0x62, // Device version answer (module <- device, required_ + GET_ALL_ADDRESSES = 0x67, // Requests all devices addresses (module -> device, interactive, optional) + GET_ALL_ADDRESSES_RESPONSE = + 0x68, // Answer to request of all devices addresses (module <- device , interactive, optional) + HANDSET_CHANGE_NOTIFICATION = 0x69, // Handset change notification frame (module <- device , interactive, optional) + GET_DEVICE_ID = 0x70, // Requests Device ID (module -> device, required) + GET_DEVICE_ID_RESPONSE = 0x71, // Response to device ID request (module <- device , required) + GET_ALARM_STATUS = 0x73, // Alarm status request (module -> device, required) + GET_ALARM_STATUS_RESPONSE = 0x74, // Response to alarm status request (module <- device, required) + GET_DEVICE_CONFIGURATION = 0x7C, // Requests device configuration (module -> device, interactive, required) + GET_DEVICE_CONFIGURATION_RESPONSE = + 0x7D, // Response to device configuration request (module <- device, interactive, required) + DOWNLINK_TRANSPARENT_TRANSMISSION = 0x8C, // Downlink transparent transmission (proxy data Haier cloud -> device) + // (module -> device, interactive, optional) + UPLINK_TRANSPARENT_TRANSMISSION = 0x8D, // Uplink transparent transmission (proxy data device -> Haier cloud) (module + // <- device, interactive, optional) + START_DEVICE_UPGRADE = 0xE1, // Initiate device OTA upgrade (module -> device, OTA required) + START_DEVICE_UPGRADE_RESPONSE = 0xE2, // Response to initiate device upgrade command (module <- device, OTA required) + GET_FIRMWARE_CONTENT = 0xE5, // Requests to send firmware (module <- device, OTA required) + GET_FIRMWARE_CONTENT_RESPONSE = + 0xE6, // Response to send firmware request (module -> device, OTA required) (multipacket?) + CHANGE_BAUD_RATE = 0xE7, // Requests to change port baud rate (module <- device, OTA required) + CHANGE_BAUD_RATE_RESPONSE = 0xE8, // Response to change port baud rate request (module -> device, OTA required) + GET_SUBBOARD_INFO = 0xE9, // Requests subboard information (module -> device, required) + GET_SUBBOARD_INFO_RESPONSE = 0xEA, // Response to subboard information request (module <- device, required) + GET_HARDWARE_INFO = 0xEB, // Requests information about device and subboard (module -> device, required) + GET_HARDWARE_INFO_RESPONSE = 0xEC, // Response to hardware information request (module <- device, required) + GET_UPGRADE_RESULT = 0xED, // Requests result of the firmware update (module <- device, OTA required) + GET_UPGRADE_RESULT_RESPONSE = 0xEF, // Response to firmware update results request (module -> device, OTA required) + GET_NETWORK_STATUS = 0xF0, // Requests network status (module <- device, interactive, optional) + GET_NETWORK_STATUS_RESPONSE = 0xF1, // Response to network status request (module -> device, interactive, optional) + START_WIFI_CONFIGURATION = 0xF2, // Starts WiFi configuration procedure (module <- device, interactive, required) + START_WIFI_CONFIGURATION_RESPONSE = + 0xF3, // Response to start WiFi configuration request (module -> device, interactive, required) + STOP_WIFI_CONFIGURATION = 0xF4, // Stop WiFi configuration procedure (module <- device, interactive, required) + STOP_WIFI_CONFIGURATION_RESPONSE = + 0xF5, // Response to stop WiFi configuration request (module -> device, interactive, required) + REPORT_NETWORK_STATUS = 0xF7, // Reports network status (module -> device, required) + CLEAR_CONFIGURATION = 0xF8, // Request to clear module configuration (module <- device, interactive, optional) + BIG_DATA_REPORT_CONFIGURATION = + 0xFA, // Configuration for autoreport device full status (module -> device, interactive, optional) + BIG_DATA_REPORT_CONFIGURATION_RESPONSE = + 0xFB, // Response to set big data configuration (module <- device, interactive, optional) + GET_MANAGEMENT_INFORMATION = 0xFC, // Request management information from device (module -> device, required) + GET_MANAGEMENT_INFORMATION_RESPONSE = + 0xFD, // Response to management information request (module <- device, required) + WAKE_UP = 0xFE, // Request to wake up (module <-> device, optional) +}; + +enum class SubcomandsControl : uint16_t { + GET_PARAMETERS = 0x4C01, // Request specific parameters (packet content: parameter ID1 + parameter ID2 + ...) + GET_USER_DATA = 0x4D01, // Request all user data from device (packet content: None) + GET_BIG_DATA = 0x4DFE, // Request big data information from device (packet content: None) + SET_PARAMETERS = 0x5C01, // Set parameters of the device and device return parameters (packet content: parameter ID1 + // + parameter data1 + parameter ID2 + parameter data 2 + ...) + SET_SINGLE_PARAMETER = 0x5D00, // Set single parameter (0x5DXX second byte define parameter ID) and return all user + // data (packet content: ???) + SET_GROUP_PARAMETERS = 0x6001, // Set group parameters to device (0x60XX second byte define parameter is group ID, + // the only group mentioned in document is 1) and return all user data (packet + // content: all values like in status packet) +}; + +} // namespace hon_protocol +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/logger_handler.cpp b/esphome/components/haier/logger_handler.cpp new file mode 100644 index 0000000000..f886318097 --- /dev/null +++ b/esphome/components/haier/logger_handler.cpp @@ -0,0 +1,33 @@ +#include "logger_handler.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace haier { + +void esphome_logger(haier_protocol::HaierLogLevel level, const char *tag, const char *message) { + switch (level) { + case haier_protocol::HaierLogLevel::LEVEL_ERROR: + esp_log_printf_(ESPHOME_LOG_LEVEL_ERROR, tag, __LINE__, "%s", message); + break; + case haier_protocol::HaierLogLevel::LEVEL_WARNING: + esp_log_printf_(ESPHOME_LOG_LEVEL_WARN, tag, __LINE__, "%s", message); + break; + case haier_protocol::HaierLogLevel::LEVEL_INFO: + esp_log_printf_(ESPHOME_LOG_LEVEL_INFO, tag, __LINE__, "%s", message); + break; + case haier_protocol::HaierLogLevel::LEVEL_DEBUG: + esp_log_printf_(ESPHOME_LOG_LEVEL_DEBUG, tag, __LINE__, "%s", message); + break; + case haier_protocol::HaierLogLevel::LEVEL_VERBOSE: + esp_log_printf_(ESPHOME_LOG_LEVEL_VERBOSE, tag, __LINE__, "%s", message); + break; + default: + // Just ignore everything else + break; + } +} + +void init_haier_protocol_logging() { haier_protocol::set_log_handler(esphome::haier::esphome_logger); }; + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/logger_handler.h b/esphome/components/haier/logger_handler.h new file mode 100644 index 0000000000..2955468f37 --- /dev/null +++ b/esphome/components/haier/logger_handler.h @@ -0,0 +1,14 @@ +#pragma once + +// HaierProtocol +#include + +namespace esphome { +namespace haier { + +// This file is called in the code generated by python script +// Do not use it directly! +void init_haier_protocol_logging(); + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/smartair2_climate.cpp b/esphome/components/haier/smartair2_climate.cpp new file mode 100644 index 0000000000..9c0fbac350 --- /dev/null +++ b/esphome/components/haier/smartair2_climate.cpp @@ -0,0 +1,457 @@ +#include +#include "esphome/components/climate/climate.h" +#include "esphome/components/uart/uart.h" +#include "smartair2_climate.h" +#include "smartair2_packet.h" + +using namespace esphome::climate; +using namespace esphome::uart; + +namespace esphome { +namespace haier { + +static const char *const TAG = "haier.climate"; + +Smartair2Climate::Smartair2Climate() + : last_status_message_(new uint8_t[sizeof(smartair2_protocol::HaierPacketControl)]) { + this->traits_.set_supported_presets({ + climate::CLIMATE_PRESET_NONE, + climate::CLIMATE_PRESET_BOOST, + climate::CLIMATE_PRESET_COMFORT, + }); +} + +haier_protocol::HandlerError Smartair2Climate::status_handler_(uint8_t request_type, uint8_t message_type, + const uint8_t *data, size_t data_size) { + haier_protocol::HandlerError result = + this->answer_preprocess_(request_type, (uint8_t) smartair2_protocol::FrameType::CONTROL, message_type, + (uint8_t) smartair2_protocol::FrameType::STATUS, ProtocolPhases::UNKNOWN); + if (result == haier_protocol::HandlerError::HANDLER_OK) { + result = this->process_status_message_(data, data_size); + if (result != haier_protocol::HandlerError::HANDLER_OK) { + ESP_LOGW(TAG, "Error %d while parsing Status packet", (int) result); + this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + } else { + if (data_size >= sizeof(smartair2_protocol::HaierPacketControl) + 2) { + memcpy(this->last_status_message_.get(), data + 2, sizeof(smartair2_protocol::HaierPacketControl)); + } else { + ESP_LOGW(TAG, "Status packet too small: %d (should be >= %d)", data_size, + sizeof(smartair2_protocol::HaierPacketControl)); + } + if (this->protocol_phase_ == ProtocolPhases::WAITING_FIRST_STATUS_ANSWER) { + ESP_LOGI(TAG, "First HVAC status received"); + this->set_phase_(ProtocolPhases::IDLE); + } else if (this->protocol_phase_ == ProtocolPhases::WAITING_STATUS_ANSWER) { + this->set_phase_(ProtocolPhases::IDLE); + } else if (this->protocol_phase_ == ProtocolPhases::WAITING_CONTROL_ANSWER) { + this->set_phase_(ProtocolPhases::IDLE); + this->set_force_send_control_(false); + if (this->hvac_settings_.valid) + this->hvac_settings_.reset(); + } + } + return result; + } else { + this->set_phase_((this->protocol_phase_ >= ProtocolPhases::IDLE) ? ProtocolPhases::IDLE + : ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + return result; + } +} + +void Smartair2Climate::set_answers_handlers() { + this->haier_protocol_.set_answer_handler( + (uint8_t) (smartair2_protocol::FrameType::CONTROL), + std::bind(&Smartair2Climate::status_handler_, this, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3, std::placeholders::_4)); +} + +void Smartair2Climate::dump_config() { + HaierClimateBase::dump_config(); + ESP_LOGCONFIG(TAG, " Protocol version: smartAir2"); +} + +void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now) { + switch (this->protocol_phase_) { + case ProtocolPhases::SENDING_INIT_1: + this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + break; + case ProtocolPhases::WAITING_ANSWER_INIT_1: + case ProtocolPhases::SENDING_INIT_2: + case ProtocolPhases::WAITING_ANSWER_INIT_2: + case ProtocolPhases::SENDING_ALARM_STATUS_REQUEST: + case ProtocolPhases::WAITING_ALARM_STATUS_ANSWER: + this->set_phase_(ProtocolPhases::SENDING_INIT_1); + break; + case ProtocolPhases::SENDING_UPDATE_SIGNAL_REQUEST: + case ProtocolPhases::WAITING_UPDATE_SIGNAL_ANSWER: + case ProtocolPhases::SENDING_SIGNAL_LEVEL: + case ProtocolPhases::WAITING_SIGNAL_LEVEL_ANSWER: + this->set_phase_(ProtocolPhases::IDLE); + break; + case ProtocolPhases::SENDING_FIRST_STATUS_REQUEST: + if (this->can_send_message() && this->is_protocol_initialisation_interval_exceded_(now)) { + static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, + 0x4D01); + this->send_message_(STATUS_REQUEST, false); + this->last_status_request_ = now; + this->set_phase_(ProtocolPhases::WAITING_FIRST_STATUS_ANSWER); + } + break; + case ProtocolPhases::SENDING_STATUS_REQUEST: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + static const haier_protocol::HaierMessage STATUS_REQUEST((uint8_t) smartair2_protocol::FrameType::CONTROL, + 0x4D01); + this->send_message_(STATUS_REQUEST, false); + this->last_status_request_ = now; + this->set_phase_(ProtocolPhases::WAITING_STATUS_ANSWER); + } + break; + case ProtocolPhases::SENDING_CONTROL: + if (this->first_control_attempt_) { + this->control_request_timestamp_ = now; + this->first_control_attempt_ = false; + } + if (this->is_control_message_timeout_exceeded_(now)) { + ESP_LOGW(TAG, "Sending control packet timeout!"); + this->set_force_send_control_(false); + if (this->hvac_settings_.valid) + this->hvac_settings_.reset(); + this->forced_request_status_ = true; + this->forced_publish_ = true; + this->set_phase_(ProtocolPhases::IDLE); + } else if (this->can_send_message() && this->is_control_message_interval_exceeded_( + now)) // Using CONTROL_MESSAGES_INTERVAL_MS to speedup requests + { + haier_protocol::HaierMessage control_message = get_control_message(); + this->send_message_(control_message, false); + ESP_LOGI(TAG, "Control packet sent"); + this->set_phase_(ProtocolPhases::WAITING_CONTROL_ANSWER); + } + break; + case ProtocolPhases::SENDING_POWER_ON_COMMAND: + case ProtocolPhases::SENDING_POWER_OFF_COMMAND: + if (this->can_send_message() && this->is_message_interval_exceeded_(now)) { + haier_protocol::HaierMessage power_cmd( + (uint8_t) smartair2_protocol::FrameType::CONTROL, + this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND ? 0x4D02 : 0x4D03); + this->send_message_(power_cmd, false); + this->set_phase_(this->protocol_phase_ == ProtocolPhases::SENDING_POWER_ON_COMMAND + ? ProtocolPhases::WAITING_POWER_ON_ANSWER + : ProtocolPhases::WAITING_POWER_OFF_ANSWER); + } + break; + case ProtocolPhases::WAITING_FIRST_STATUS_ANSWER: + case ProtocolPhases::WAITING_STATUS_ANSWER: + case ProtocolPhases::WAITING_CONTROL_ANSWER: + case ProtocolPhases::WAITING_POWER_ON_ANSWER: + case ProtocolPhases::WAITING_POWER_OFF_ANSWER: + break; + case ProtocolPhases::IDLE: { + if (this->forced_request_status_ || this->is_status_request_interval_exceeded_(now)) { + this->set_phase_(ProtocolPhases::SENDING_STATUS_REQUEST); + this->forced_request_status_ = false; + } + } break; + default: + // Shouldn't get here + ESP_LOGE(TAG, "Wrong protocol handler state: %d, resetting communication", (int) this->protocol_phase_); + this->set_phase_(ProtocolPhases::SENDING_FIRST_STATUS_REQUEST); + break; + } +} + +haier_protocol::HaierMessage Smartair2Climate::get_control_message() { + uint8_t control_out_buffer[sizeof(smartair2_protocol::HaierPacketControl)]; + memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(smartair2_protocol::HaierPacketControl)); + smartair2_protocol::HaierPacketControl *out_data = (smartair2_protocol::HaierPacketControl *) control_out_buffer; + out_data->cntrl = 0; + if (this->hvac_settings_.valid) { + HvacSettings climate_control; + climate_control = this->hvac_settings_; + if (climate_control.mode.has_value()) { + switch (climate_control.mode.value()) { + case CLIMATE_MODE_OFF: + out_data->ac_power = 0; + break; + + case CLIMATE_MODE_AUTO: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::AUTO; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + + case CLIMATE_MODE_HEAT: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::HEAT; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + + case CLIMATE_MODE_DRY: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::DRY; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + + case CLIMATE_MODE_FAN_ONLY: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::FAN; + out_data->fan_mode = this->fan_mode_speed_; // Auto doesn't work in fan only mode + break; + + case CLIMATE_MODE_COOL: + out_data->ac_power = 1; + out_data->ac_mode = (uint8_t) smartair2_protocol::ConditioningMode::COOL; + out_data->fan_mode = this->other_modes_fan_speed_; + break; + default: + ESP_LOGE("Control", "Unsupported climate mode"); + break; + } + } + // Set fan speed, if we are in fan mode, reject auto in fan mode + if (climate_control.fan_mode.has_value()) { + switch (climate_control.fan_mode.value()) { + case CLIMATE_FAN_LOW: + out_data->fan_mode = (uint8_t) smartair2_protocol::FanMode::FAN_LOW; + break; + case CLIMATE_FAN_MEDIUM: + out_data->fan_mode = (uint8_t) smartair2_protocol::FanMode::FAN_MID; + break; + case CLIMATE_FAN_HIGH: + out_data->fan_mode = (uint8_t) smartair2_protocol::FanMode::FAN_HIGH; + break; + case CLIMATE_FAN_AUTO: + if (this->mode != CLIMATE_MODE_FAN_ONLY) // if we are not in fan only mode + out_data->fan_mode = (uint8_t) smartair2_protocol::FanMode::FAN_AUTO; + break; + default: + ESP_LOGE("Control", "Unsupported fan mode"); + break; + } + } + // Set swing mode + if (climate_control.swing_mode.has_value()) { + switch (climate_control.swing_mode.value()) { + case CLIMATE_SWING_OFF: + out_data->use_swing_bits = 0; + out_data->swing_both = 0; + break; + case CLIMATE_SWING_VERTICAL: + out_data->swing_both = 0; + out_data->vertical_swing = 1; + out_data->horizontal_swing = 0; + break; + case CLIMATE_SWING_HORIZONTAL: + out_data->swing_both = 0; + out_data->vertical_swing = 0; + out_data->horizontal_swing = 1; + break; + case CLIMATE_SWING_BOTH: + out_data->swing_both = 1; + out_data->use_swing_bits = 0; + out_data->vertical_swing = 0; + out_data->horizontal_swing = 0; + break; + } + } + if (climate_control.target_temperature.has_value()) { + out_data->set_point = + climate_control.target_temperature.value() - 16; // set the temperature at our offset, subtract 16. + } + if (out_data->ac_power == 0) { + // If AC is off - no presets alowed + out_data->turbo_mode = 0; + out_data->quiet_mode = 0; + } else if (climate_control.preset.has_value()) { + switch (climate_control.preset.value()) { + case CLIMATE_PRESET_NONE: + out_data->turbo_mode = 0; + out_data->quiet_mode = 0; + break; + case CLIMATE_PRESET_BOOST: + out_data->turbo_mode = 1; + out_data->quiet_mode = 0; + break; + case CLIMATE_PRESET_COMFORT: + out_data->turbo_mode = 0; + out_data->quiet_mode = 1; + break; + default: + ESP_LOGE("Control", "Unsupported preset"); + out_data->turbo_mode = 0; + out_data->quiet_mode = 0; + break; + } + } + } + out_data->display_status = this->display_status_ ? 0 : 1; + out_data->health_mode = this->health_mode_ ? 1 : 0; + return haier_protocol::HaierMessage((uint8_t) smartair2_protocol::FrameType::CONTROL, 0x4D5F, control_out_buffer, + sizeof(smartair2_protocol::HaierPacketControl)); +} + +haier_protocol::HandlerError Smartair2Climate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) { + if (size < sizeof(smartair2_protocol::HaierStatus)) + return haier_protocol::HandlerError::WRONG_MESSAGE_STRUCTURE; + smartair2_protocol::HaierStatus packet; + memcpy(&packet, packet_buffer, size); + bool should_publish = false; + { + // Extra modes/presets + optional old_preset = this->preset; + if (packet.control.turbo_mode != 0) { + this->preset = CLIMATE_PRESET_BOOST; + } else if (packet.control.quiet_mode != 0) { + this->preset = CLIMATE_PRESET_COMFORT; + } else { + this->preset = CLIMATE_PRESET_NONE; + } + should_publish = should_publish || (!old_preset.has_value()) || (old_preset.value() != this->preset.value()); + } + { + // Target temperature + float old_target_temperature = this->target_temperature; + this->target_temperature = packet.control.set_point + 16.0f; + should_publish = should_publish || (old_target_temperature != this->target_temperature); + } + { + // Current temperature + float old_current_temperature = this->current_temperature; + this->current_temperature = packet.control.room_temperature; + should_publish = should_publish || (old_current_temperature != this->current_temperature); + } + { + // Fan mode + optional old_fan_mode = this->fan_mode; + // remember the fan speed we last had for climate vs fan + if (packet.control.ac_mode == (uint8_t) smartair2_protocol::ConditioningMode::FAN) { + if (packet.control.fan_mode != (uint8_t) smartair2_protocol::FanMode::FAN_AUTO) + this->fan_mode_speed_ = packet.control.fan_mode; + } else { + this->other_modes_fan_speed_ = packet.control.fan_mode; + } + switch (packet.control.fan_mode) { + case (uint8_t) smartair2_protocol::FanMode::FAN_AUTO: + // Somtimes AC reports in fan only mode that fan speed is auto + // but never accept this value back + if (packet.control.ac_mode != (uint8_t) smartair2_protocol::ConditioningMode::FAN) { + this->fan_mode = CLIMATE_FAN_AUTO; + } else { + should_publish = true; + } + break; + case (uint8_t) smartair2_protocol::FanMode::FAN_MID: + this->fan_mode = CLIMATE_FAN_MEDIUM; + break; + case (uint8_t) smartair2_protocol::FanMode::FAN_LOW: + this->fan_mode = CLIMATE_FAN_LOW; + break; + case (uint8_t) smartair2_protocol::FanMode::FAN_HIGH: + this->fan_mode = CLIMATE_FAN_HIGH; + break; + } + should_publish = should_publish || (!old_fan_mode.has_value()) || (old_fan_mode.value() != fan_mode.value()); + } + { + // Display status + // should be before "Climate mode" because it is changing this->mode + if (packet.control.ac_power != 0) { + // if AC is off display status always ON so process it only when AC is on + bool disp_status = packet.control.display_status == 0; + if (disp_status != this->display_status_) { + // Do something only if display status changed + if (this->mode == CLIMATE_MODE_OFF) { + // AC just turned on from remote need to turn off display + this->set_force_send_control_(true); + } else { + this->display_status_ = disp_status; + } + } + } + } + { + // Climate mode + ClimateMode old_mode = this->mode; + if (packet.control.ac_power == 0) { + this->mode = CLIMATE_MODE_OFF; + } else { + // Check current hvac mode + switch (packet.control.ac_mode) { + case (uint8_t) smartair2_protocol::ConditioningMode::COOL: + this->mode = CLIMATE_MODE_COOL; + break; + case (uint8_t) smartair2_protocol::ConditioningMode::HEAT: + this->mode = CLIMATE_MODE_HEAT; + break; + case (uint8_t) smartair2_protocol::ConditioningMode::DRY: + this->mode = CLIMATE_MODE_DRY; + break; + case (uint8_t) smartair2_protocol::ConditioningMode::FAN: + this->mode = CLIMATE_MODE_FAN_ONLY; + break; + case (uint8_t) smartair2_protocol::ConditioningMode::AUTO: + this->mode = CLIMATE_MODE_AUTO; + break; + } + } + should_publish = should_publish || (old_mode != this->mode); + } + { + // Health mode + bool old_health_mode = this->health_mode_; + this->health_mode_ = packet.control.health_mode == 1; + should_publish = should_publish || (old_health_mode != this->health_mode_); + } + { + // Swing mode + ClimateSwingMode old_swing_mode = this->swing_mode; + if (packet.control.swing_both == 0) { + if (packet.control.vertical_swing != 0) { + this->swing_mode = CLIMATE_SWING_VERTICAL; + } else if (packet.control.horizontal_swing != 0) { + this->swing_mode = CLIMATE_SWING_HORIZONTAL; + } else { + this->swing_mode = CLIMATE_SWING_OFF; + } + } else { + swing_mode = CLIMATE_SWING_BOTH; + } + should_publish = should_publish || (old_swing_mode != this->swing_mode); + } + this->last_valid_status_timestamp_ = std::chrono::steady_clock::now(); + if (this->forced_publish_ || should_publish) { +#if (HAIER_LOG_LEVEL > 4) + std::chrono::high_resolution_clock::time_point _publish_start = std::chrono::high_resolution_clock::now(); +#endif + this->publish_state(); +#if (HAIER_LOG_LEVEL > 4) + ESP_LOGV(TAG, "Publish delay: %lld ms", + std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - + _publish_start) + .count()); +#endif + this->forced_publish_ = false; + } + if (should_publish) { + ESP_LOGI(TAG, "HVAC values changed"); + } + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "HVAC Mode = 0x%X", packet.control.ac_mode); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Fan speed Status = 0x%X", packet.control.fan_mode); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Horizontal Swing Status = 0x%X", packet.control.horizontal_swing); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Vertical Swing Status = 0x%X", packet.control.vertical_swing); + esp_log_printf_((should_publish ? ESPHOME_LOG_LEVEL_INFO : ESPHOME_LOG_LEVEL_DEBUG), TAG, __LINE__, + "Set Point Status = 0x%X", packet.control.set_point); + return haier_protocol::HandlerError::HANDLER_OK; +} + +bool Smartair2Climate::is_message_invalid(uint8_t message_type) { + return message_type == (uint8_t) smartair2_protocol::FrameType::INVALID; +} + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/smartair2_climate.h b/esphome/components/haier/smartair2_climate.h new file mode 100644 index 0000000000..c89d1f0be9 --- /dev/null +++ b/esphome/components/haier/smartair2_climate.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include "haier_base.h" + +namespace esphome { +namespace haier { + +class Smartair2Climate : public HaierClimateBase { + public: + Smartair2Climate(); + Smartair2Climate(const Smartair2Climate &) = delete; + Smartair2Climate &operator=(const Smartair2Climate &) = delete; + ~Smartair2Climate(); + void dump_config() override; + + protected: + void set_answers_handlers() override; + void process_phase(std::chrono::steady_clock::time_point now) override; + haier_protocol::HaierMessage get_control_message() override; + bool is_message_invalid(uint8_t message_type) override; + // Answers handlers + haier_protocol::HandlerError status_handler_(uint8_t request_type, uint8_t message_type, const uint8_t *data, + size_t data_size); + // Helper functions + haier_protocol::HandlerError process_status_message_(const uint8_t *packet, uint8_t size); + std::unique_ptr last_status_message_; +}; + +} // namespace haier +} // namespace esphome diff --git a/esphome/components/haier/smartair2_packet.h b/esphome/components/haier/smartair2_packet.h new file mode 100644 index 0000000000..8046516c5f --- /dev/null +++ b/esphome/components/haier/smartair2_packet.h @@ -0,0 +1,97 @@ +#pragma once + +namespace esphome { +namespace haier { +namespace smartair2_protocol { + +enum class ConditioningMode : uint8_t { AUTO = 0x00, COOL = 0x01, HEAT = 0x02, FAN = 0x03, DRY = 0x04 }; + +enum class FanMode : uint8_t { FAN_HIGH = 0x00, FAN_MID = 0x01, FAN_LOW = 0x02, FAN_AUTO = 0x03 }; + +struct HaierPacketControl { + // Control bytes starts here + // 10 + uint8_t : 8; // Temperature high byte + // 11 + uint8_t room_temperature; // current room temperature 1°C step + // 12 + uint8_t : 8; // Humidity high byte + // 13 + uint8_t room_humidity; // Humidity 0%-100% with 1% step + // 14 + uint8_t : 8; + // 15 + uint8_t cntrl; // In AC => ESP packets - 0x7F, in ESP => AC packets - 0x00 + // 16 + uint8_t : 8; + // 17 + uint8_t : 8; + // 18 + uint8_t : 8; + // 19 + uint8_t : 8; + // 20 + uint8_t : 8; + // 21 + uint8_t ac_mode; // See enum ConditioningMode + // 22 + uint8_t : 8; + // 23 + uint8_t fan_mode; // See enum FanMode + // 24 + uint8_t : 8; + // 25 + uint8_t swing_both; // If 1 - swing both direction, if 0 - horizontal_swing and vertical_swing define + // vertical/horizontal/off + // 26 + uint8_t : 3; + uint8_t use_fahrenheit : 1; + uint8_t : 3; + uint8_t lock_remote : 1; // Disable remote + // 27 + uint8_t ac_power : 1; // Is ac on or off + uint8_t : 2; + uint8_t health_mode : 1; // Health mode on or off + uint8_t compressor : 1; // Compressor on or off ??? + uint8_t : 1; + uint8_t ten_degree : 1; // 10 degree status (only work in heat mode) + uint8_t : 0; + // 28 + uint8_t : 8; + // 29 + uint8_t use_swing_bits : 1; // Indicate if horizontal_swing and vertical_swing should be used + uint8_t turbo_mode : 1; // Turbo mode + uint8_t quiet_mode : 1; // Sleep mode + uint8_t horizontal_swing : 1; // Horizontal swing (if swing_both == 0) + uint8_t vertical_swing : 1; // Vertical swing (if swing_both == 0) if vertical_swing and horizontal_swing both 0 => + // swing off + uint8_t display_status : 1; // Led on or off + uint8_t : 0; + // 30 + uint8_t : 8; + // 31 + uint8_t : 8; + // 32 + uint8_t : 8; // Target temperature high byte + // 33 + uint8_t set_point; // Target temperature with 16°C offset, 1°C step +}; + +struct HaierStatus { + uint16_t subcommand; + HaierPacketControl control; +}; + +enum class FrameType : uint8_t { + CONTROL = 0x01, + STATUS = 0x02, + INVALID = 0x03, + CONFIRM = 0x05, + GET_DEVICE_VERSION = 0x61, + REPORT_NETWORK_STATUS = 0xF7, + NO_COMMAND = 0xFF, +}; + +} // namespace smartair2_protocol +} // namespace haier +} // namespace esphome diff --git a/platformio.ini b/platformio.ini index 3565b15809..ab16d47c6f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -39,6 +39,7 @@ lib_deps = bblanchon/ArduinoJson@6.18.5 ; json wjtje/qr-code-generator-library@1.7.0 ; qr_code functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 + pavlodn/HaierProtocol@0.9.18 ; haier ; This is using the repository until a new release is published to PlatformIO https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library build_flags = diff --git a/tests/test3.yaml b/tests/test3.yaml index 8307ac2984..f7b66a748e 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -944,13 +944,29 @@ climate: kd_multiplier: 0.0 deadband_output_averaging_samples: 1 - platform: haier + protocol: hOn name: Haier AC - supported_swing_modes: - - vertical - - horizontal - - both - update_interval: 10s uart_id: uart_12 + wifi_signal: true + beeper: true + outdoor_temperature: + name: Haier AC outdoor temperature + visual: + min_temperature: 16 °C + max_temperature: 30 °C + temperature_step: 1 °C + supported_modes: + - 'OFF' + - AUTO + - COOL + - HEAT + - DRY + - FAN_ONLY + supported_swing_modes: + - 'OFF' + - VERTICAL + - HORIZONTAL + - BOTH sprinkler: - id: yard_sprinkler_ctrlr From 8c9d63f48f49b4ff98f1564e7d667ec60e713d25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 19 Jun 2023 01:04:19 +0200 Subject: [PATCH 010/106] display: add `BaseFont` and introduce `Font::draw` methods (#4963) --- esphome/components/display/display_buffer.cpp | 77 ++++--------------- esphome/components/display/display_buffer.h | 51 +++++++----- esphome/components/display/font.cpp | 52 ++++++++++++- esphome/components/display/font.h | 9 ++- esphome/components/display/image.cpp | 1 - esphome/components/display/image.h | 15 +--- esphome/components/graph/graph.cpp | 1 + 7 files changed, 106 insertions(+), 100 deletions(-) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 672c6a22b0..fc59f9abb5 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -7,10 +7,6 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "animation.h" -#include "image.h" -#include "font.h" - namespace esphome { namespace display { @@ -256,54 +252,14 @@ void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color } while (dx <= 0); } -void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align, const char *text) { +void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { int x_start, y_start; int width, height; this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height); - - int i = 0; - int x_at = x_start; - while (text[i] != '\0') { - int match_length; - int glyph_n = font->match_next_glyph(text + i, &match_length); - if (glyph_n < 0) { - // Unknown char, skip - ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); - if (!font->get_glyphs().empty()) { - uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width; - for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) { - for (int glyph_y = 0; glyph_y < height; glyph_y++) - this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); - } - x_at += glyph_width; - } - - i++; - continue; - } - - const Glyph &glyph = font->get_glyphs()[glyph_n]; - int scan_x1, scan_y1, scan_width, scan_height; - glyph.scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); - - { - const int glyph_x_max = scan_x1 + scan_width; - const int glyph_y_max = scan_y1 + scan_height; - for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) { - for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) { - if (glyph.get_pixel(glyph_x, glyph_y)) { - this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); - } - } - } - } - - x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; - - i += match_length; - } + font->print(x_start, y_start, this, color, text); } -void DisplayBuffer::vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg) { +void DisplayBuffer::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, + va_list arg) { char buffer[256]; int ret = vsnprintf(buffer, sizeof(buffer), format, arg); if (ret > 0) @@ -358,7 +314,7 @@ void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_ } #endif // USE_QR_CODE -void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, +void DisplayBuffer::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width, int *height) { int x_offset, baseline; font->measure(text, width, &x_offset, &baseline, height); @@ -396,34 +352,34 @@ void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, break; } } -void DisplayBuffer::print(int x, int y, Font *font, Color color, const char *text) { +void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, const char *text) { this->print(x, y, font, color, TextAlign::TOP_LEFT, text); } -void DisplayBuffer::print(int x, int y, Font *font, TextAlign align, const char *text) { +void DisplayBuffer::print(int x, int y, BaseFont *font, TextAlign align, const char *text) { this->print(x, y, font, COLOR_ON, align, text); } -void DisplayBuffer::print(int x, int y, Font *font, const char *text) { +void DisplayBuffer::print(int x, int y, BaseFont *font, const char *text) { this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text); } -void DisplayBuffer::printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) { +void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, color, align, format, arg); va_end(arg); } -void DisplayBuffer::printf(int x, int y, Font *font, Color color, const char *format, ...) { +void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg); va_end(arg); } -void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char *format, ...) { +void DisplayBuffer::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, COLOR_ON, align, format, arg); va_end(arg); } -void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) { +void DisplayBuffer::printf(int x, int y, BaseFont *font, const char *format, ...) { va_list arg; va_start(arg, format); this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg); @@ -470,19 +426,20 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) this->trigger(from, to); } -void DisplayBuffer::strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time) { +void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, + ESPTime time) { char buffer[64]; size_t ret = time.strftime(buffer, sizeof(buffer), format); if (ret > 0) this->print(x, y, font, color, align, buffer); } -void DisplayBuffer::strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time) { +void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); } -void DisplayBuffer::strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time) { +void DisplayBuffer::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) { this->strftime(x, y, font, COLOR_ON, align, format, time); } -void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, ESPTime time) { +void DisplayBuffer::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) { this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); } diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 652039517f..b5cd2737be 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -16,10 +16,6 @@ #include "esphome/components/qr_code/qr_code.h" #endif -#include "animation.h" -#include "font.h" -#include "image.h" - namespace esphome { namespace display { @@ -175,6 +171,24 @@ using display_writer_t = std::function; ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \ } +/// Turn the pixel OFF. +extern const Color COLOR_OFF; +/// Turn the pixel ON. +extern const Color COLOR_ON; + +class BaseImage { + public: + virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0; + virtual int get_width() const = 0; + virtual int get_height() const = 0; +}; + +class BaseFont { + public: + virtual void print(int x, int y, DisplayBuffer *display, Color color, const char *text) = 0; + virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0; +}; + class DisplayBuffer { public: /// Fill the entire screen with the given color. @@ -221,7 +235,7 @@ class DisplayBuffer { * @param align The alignment of the text. * @param text The text to draw. */ - void print(int x, int y, Font *font, Color color, TextAlign align, const char *text); + void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text); /** Print `text` with the top left at [x,y] with `font`. * @@ -231,7 +245,7 @@ class DisplayBuffer { * @param color The color to draw the text with. * @param text The text to draw. */ - void print(int x, int y, Font *font, Color color, const char *text); + void print(int x, int y, BaseFont *font, Color color, const char *text); /** Print `text` with the anchor point at [x,y] with `font`. * @@ -241,7 +255,7 @@ class DisplayBuffer { * @param align The alignment of the text. * @param text The text to draw. */ - void print(int x, int y, Font *font, TextAlign align, const char *text); + void print(int x, int y, BaseFont *font, TextAlign align, const char *text); /** Print `text` with the top left at [x,y] with `font`. * @@ -250,7 +264,7 @@ class DisplayBuffer { * @param font The font to draw the text with. * @param text The text to draw. */ - void print(int x, int y, Font *font, const char *text); + void print(int x, int y, BaseFont *font, const char *text); /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. * @@ -262,7 +276,7 @@ class DisplayBuffer { * @param format The format to use. * @param ... The arguments to use for the text formatting. */ - void printf(int x, int y, Font *font, Color color, TextAlign align, const char *format, ...) + void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) __attribute__((format(printf, 7, 8))); /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. @@ -274,7 +288,7 @@ class DisplayBuffer { * @param format The format to use. * @param ... The arguments to use for the text formatting. */ - void printf(int x, int y, Font *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7))); + void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7))); /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. * @@ -285,7 +299,8 @@ class DisplayBuffer { * @param format The format to use. * @param ... The arguments to use for the text formatting. */ - void printf(int x, int y, Font *font, TextAlign align, const char *format, ...) __attribute__((format(printf, 6, 7))); + void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) + __attribute__((format(printf, 6, 7))); /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. * @@ -295,7 +310,7 @@ class DisplayBuffer { * @param format The format to use. * @param ... The arguments to use for the text formatting. */ - void printf(int x, int y, Font *font, const char *format, ...) __attribute__((format(printf, 5, 6))); + void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6))); /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. * @@ -307,7 +322,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, Color color, TextAlign align, const char *format, ESPTime time) + void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) __attribute__((format(strftime, 7, 0))); /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. @@ -319,7 +334,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, Color color, const char *format, ESPTime time) + void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) __attribute__((format(strftime, 6, 0))); /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. @@ -331,7 +346,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, TextAlign align, const char *format, ESPTime time) + void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) __attribute__((format(strftime, 6, 0))); /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. @@ -342,7 +357,7 @@ class DisplayBuffer { * @param format The strftime format to use. * @param time The time to format. */ - void strftime(int x, int y, Font *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0))); + void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0))); /** Draw the `image` with the top-left corner at [x,y] to the screen. * @@ -412,7 +427,7 @@ class DisplayBuffer { * @param width A pointer to store the returned text width in. * @param height A pointer to store the returned text height in. */ - void get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width, + void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width, int *height); /// Internal method to set the display writer lambda. @@ -487,7 +502,7 @@ class DisplayBuffer { bool is_clipping() const { return !this->clipping_rectangle_.empty(); } protected: - void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); + void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg); virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; diff --git a/esphome/components/display/font.cpp b/esphome/components/display/font.cpp index 1833ef5023..a7d0b7780c 100644 --- a/esphome/components/display/font.cpp +++ b/esphome/components/display/font.cpp @@ -1,10 +1,13 @@ #include "font.h" #include "esphome/core/hal.h" +#include "esphome/core/log.h" namespace esphome { namespace display { +static const char *const TAG = "display"; + bool Glyph::get_pixel(int x, int y) const { const int x_data = x - this->glyph_data_->offset_x; const int y_data = y - this->glyph_data_->offset_y; @@ -14,6 +17,20 @@ bool Glyph::get_pixel(int x, int y) const { const uint32_t pos = x_data + y_data * width_8; return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); } +void Glyph::draw(int x_at, int y_start, DisplayBuffer *display, Color color) const { + int scan_x1, scan_y1, scan_width, scan_height; + this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); + + const int glyph_x_max = scan_x1 + scan_width; + const int glyph_y_max = scan_y1 + scan_height; + for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) { + for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) { + if (this->get_pixel(glyph_x, glyph_y)) { + display->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); + } + } + } +} const char *Glyph::get_char() const { return this->glyph_data_->a_char; } bool Glyph::compare_to(const char *str) const { // 1 -> this->char_ @@ -47,6 +64,12 @@ void Glyph::scan_area(int *x1, int *y1, int *width, int *height) const { *width = this->glyph_data_->width; *height = this->glyph_data_->height; } + +Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { + glyphs_.reserve(data_nr); + for (int i = 0; i < data_nr; ++i) + glyphs_.emplace_back(&data[i]); +} int Font::match_next_glyph(const char *str, int *match_length) { int lo = 0; int hi = this->glyphs_.size() - 1; @@ -95,10 +118,31 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in *x_offset = min_x; *width = x - min_x; } -Font::Font(const GlyphData *data, int data_nr, int baseline, int height) : baseline_(baseline), height_(height) { - glyphs_.reserve(data_nr); - for (int i = 0; i < data_nr; ++i) - glyphs_.emplace_back(&data[i]); +void Font::print(int x_start, int y_start, DisplayBuffer *display, Color color, const char *text) { + int i = 0; + int x_at = x_start; + while (text[i] != '\0') { + int match_length; + int glyph_n = this->match_next_glyph(text + i, &match_length); + if (glyph_n < 0) { + // Unknown char, skip + ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); + if (!this->get_glyphs().empty()) { + uint8_t glyph_width = this->get_glyphs()[0].glyph_data_->width; + display->filled_rectangle(x_at, y_start, glyph_width, this->height_, color); + x_at += glyph_width; + } + + i++; + continue; + } + + const Glyph &glyph = this->get_glyphs()[glyph_n]; + glyph.draw(x_at, y_start, display, color); + x_at += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; + + i += match_length; + } } } // namespace display diff --git a/esphome/components/display/font.h b/esphome/components/display/font.h index 08b457116e..678aee65a1 100644 --- a/esphome/components/display/font.h +++ b/esphome/components/display/font.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/core/datatypes.h" +#include "display_buffer.h" namespace esphome { namespace display { @@ -23,6 +24,8 @@ class Glyph { bool get_pixel(int x, int y) const; + void draw(int x, int y, DisplayBuffer *display, Color color) const; + const char *get_char() const; bool compare_to(const char *str) const; @@ -33,12 +36,11 @@ class Glyph { protected: friend Font; - friend DisplayBuffer; const GlyphData *glyph_data_; }; -class Font { +class Font : public BaseFont { public: /** Construct the font with the given glyphs. * @@ -50,7 +52,8 @@ class Font { int match_next_glyph(const char *str, int *match_length); - void measure(const char *str, int *width, int *x_offset, int *baseline, int *height); + void print(int x_start, int y_start, DisplayBuffer *display, Color color, const char *text) override; + void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; inline int get_baseline() { return this->baseline_; } inline int get_height() { return this->height_; } diff --git a/esphome/components/display/image.cpp b/esphome/components/display/image.cpp index b3cab3ff7f..33c26ef127 100644 --- a/esphome/components/display/image.cpp +++ b/esphome/components/display/image.cpp @@ -1,7 +1,6 @@ #include "image.h" #include "esphome/core/hal.h" -#include "display_buffer.h" namespace esphome { namespace display { diff --git a/esphome/components/display/image.h b/esphome/components/display/image.h index ac2d5a3421..b16828a5be 100644 --- a/esphome/components/display/image.h +++ b/esphome/components/display/image.h @@ -1,5 +1,6 @@ #pragma once #include "esphome/core/color.h" +#include "display_buffer.h" namespace esphome { namespace display { @@ -30,20 +31,6 @@ inline int image_type_to_bpp(ImageType type) { inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; } -/// Turn the pixel OFF. -extern const Color COLOR_OFF; -/// Turn the pixel ON. -extern const Color COLOR_ON; - -class DisplayBuffer; - -class BaseImage { - public: - virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0; - virtual int get_width() const = 0; - virtual int get_height() const = 0; -}; - class Image : public BaseImage { public: Image(const uint8_t *data_start, int width, int height, ImageType type); diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index c229f17dd8..88850f4b92 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -1,5 +1,6 @@ #include "graph.h" #include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/font.h" #include "esphome/core/color.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" From 77a7d3f24b8b9b4a73c3f65252f7688e1fabd0d2 Mon Sep 17 00:00:00 2001 From: Hawawa McTaru <86658622+TaruDesigns@users.noreply.github.com> Date: Mon, 19 Jun 2023 01:20:32 +0200 Subject: [PATCH 011/106] Fix for Fujitsu AC not having Quiet Fan Mode (#4962) --- esphome/components/fujitsu_general/fujitsu_general.cpp | 7 +++++-- esphome/components/fujitsu_general/fujitsu_general.h | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 291af8c8cd..6c7adebfea 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -151,11 +151,13 @@ void FujitsuGeneralClimate::transmit_state() { case climate::CLIMATE_FAN_LOW: SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_LOW); break; + case climate::CLIMATE_FAN_QUIET: + SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_SILENT); + break; case climate::CLIMATE_FAN_AUTO: default: SET_NIBBLE(remote_state, FUJITSU_GENERAL_FAN_NIBBLE, FUJITSU_GENERAL_FAN_AUTO); break; - // TODO Quiet / Silent } // Set swing @@ -345,8 +347,9 @@ bool FujitsuGeneralClimate::on_receive(remote_base::RemoteReceiveData data) { const uint8_t recv_fan_mode = GET_NIBBLE(recv_message, FUJITSU_GENERAL_FAN_NIBBLE); ESP_LOGV(TAG, "Received fan mode %X", recv_fan_mode); switch (recv_fan_mode) { - // TODO No Quiet / Silent in ESPH case FUJITSU_GENERAL_FAN_SILENT: + this->fan_mode = climate::CLIMATE_FAN_QUIET; + break; case FUJITSU_GENERAL_FAN_LOW: this->fan_mode = climate::CLIMATE_FAN_LOW; break; diff --git a/esphome/components/fujitsu_general/fujitsu_general.h b/esphome/components/fujitsu_general/fujitsu_general.h index ee83ae9d19..d7d01bf6f3 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.h +++ b/esphome/components/fujitsu_general/fujitsu_general.h @@ -52,7 +52,7 @@ class FujitsuGeneralClimate : public climate_ir::ClimateIR { FujitsuGeneralClimate() : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true, {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, - climate::CLIMATE_FAN_HIGH}, + climate::CLIMATE_FAN_HIGH, climate::CLIMATE_FAN_QUIET}, {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} From 54eb52c19aa185c26150353762fd1db794952c3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 19 Jun 2023 01:29:43 +0200 Subject: [PATCH 012/106] display/font: optimise font rendering by about 25% (#4956) --- esphome/components/display/font.cpp | 28 +++++++++++++--------------- esphome/components/display/font.h | 2 -- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/esphome/components/display/font.cpp b/esphome/components/display/font.cpp index a7d0b7780c..0a5881b48b 100644 --- a/esphome/components/display/font.cpp +++ b/esphome/components/display/font.cpp @@ -8,25 +8,23 @@ namespace display { static const char *const TAG = "display"; -bool Glyph::get_pixel(int x, int y) const { - const int x_data = x - this->glyph_data_->offset_x; - const int y_data = y - this->glyph_data_->offset_y; - if (x_data < 0 || x_data >= this->glyph_data_->width || y_data < 0 || y_data >= this->glyph_data_->height) - return false; - const uint32_t width_8 = ((this->glyph_data_->width + 7u) / 8u) * 8u; - const uint32_t pos = x_data + y_data * width_8; - return progmem_read_byte(this->glyph_data_->data + (pos / 8u)) & (0x80 >> (pos % 8u)); -} void Glyph::draw(int x_at, int y_start, DisplayBuffer *display, Color color) const { int scan_x1, scan_y1, scan_width, scan_height; this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); - const int glyph_x_max = scan_x1 + scan_width; - const int glyph_y_max = scan_y1 + scan_height; - for (int glyph_x = scan_x1; glyph_x < glyph_x_max; glyph_x++) { - for (int glyph_y = scan_y1; glyph_y < glyph_y_max; glyph_y++) { - if (this->get_pixel(glyph_x, glyph_y)) { - display->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); + const unsigned char *data = this->glyph_data_->data; + const int max_x = x_at + scan_x1 + scan_width; + const int max_y = y_start + scan_y1 + scan_height; + + for (int glyph_y = y_start + scan_y1; glyph_y < max_y; glyph_y++) { + for (int glyph_x = x_at + scan_x1; glyph_x < max_x; data++, glyph_x += 8) { + uint8_t pixel_data = progmem_read_byte(data); + const int pixel_max_x = std::min(max_x, glyph_x + 8); + + for (int pixel_x = glyph_x; pixel_x < pixel_max_x && pixel_data; pixel_x++, pixel_data <<= 1) { + if (pixel_data & 0x80) { + display->draw_pixel_at(pixel_x, glyph_y, color); + } } } } diff --git a/esphome/components/display/font.h b/esphome/components/display/font.h index 678aee65a1..5ba6685a1c 100644 --- a/esphome/components/display/font.h +++ b/esphome/components/display/font.h @@ -22,8 +22,6 @@ class Glyph { public: Glyph(const GlyphData *data) : glyph_data_(data) {} - bool get_pixel(int x, int y) const; - void draw(int x, int y, DisplayBuffer *display, Color color) const; const char *get_char() const; From 62d2640c3728a73350a19162b24c6ba0099480b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Mon, 19 Jun 2023 01:32:39 +0200 Subject: [PATCH 013/106] display: move `Rect` into `rect.cpp/.h` (#4957) --- esphome/components/display/display_buffer.cpp | 87 ---------------- esphome/components/display/display_buffer.h | 28 +----- esphome/components/display/rect.cpp | 98 +++++++++++++++++++ esphome/components/display/rect.h | 36 +++++++ 4 files changed, 135 insertions(+), 114 deletions(-) create mode 100644 esphome/components/display/rect.cpp create mode 100644 esphome/components/display/rect.h diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index fc59f9abb5..8d37d6536a 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -15,93 +15,6 @@ static const char *const TAG = "display"; const Color COLOR_OFF(0, 0, 0, 255); const Color COLOR_ON(255, 255, 255, 255); -void Rect::expand(int16_t horizontal, int16_t vertical) { - if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) { - this->x = this->x - horizontal; - this->y = this->y - vertical; - this->w = this->w + (2 * horizontal); - this->h = this->h + (2 * vertical); - } -} - -void Rect::extend(Rect rect) { - if (!this->is_set()) { - this->x = rect.x; - this->y = rect.y; - this->w = rect.w; - this->h = rect.h; - } else { - if (this->x > rect.x) { - this->w = this->w + (this->x - rect.x); - this->x = rect.x; - } - if (this->y > rect.y) { - this->h = this->h + (this->y - rect.y); - this->y = rect.y; - } - if (this->x2() < rect.x2()) { - this->w = rect.x2() - this->x; - } - if (this->y2() < rect.y2()) { - this->h = rect.y2() - this->y; - } - } -} -void Rect::shrink(Rect rect) { - if (!this->inside(rect)) { - (*this) = Rect(); - } else { - if (this->x2() > rect.x2()) { - this->w = rect.x2() - this->x; - } - if (this->x < rect.x) { - this->w = this->w + (this->x - rect.x); - this->x = rect.x; - } - if (this->y2() > rect.y2()) { - this->h = rect.y2() - this->y; - } - if (this->y < rect.y) { - this->h = this->h + (this->y - rect.y); - this->y = rect.y; - } - } -} - -bool Rect::equal(Rect rect) { - return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h); -} - -bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) { // NOLINT - if (!this->is_set()) { - return true; - } - if (absolute) { - return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2())); - } else { - return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h)); - } -} - -bool Rect::inside(Rect rect, bool absolute) { - if (!this->is_set() || !rect.is_set()) { - return true; - } - if (absolute) { - return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y)); - } else { - return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0)); - } -} - -void Rect::info(const std::string &prefix) { - if (this->is_set()) { - ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(), - this->y2()); - } else - ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str()); -} - void DisplayBuffer::init_internal_(uint32_t buffer_length) { ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); this->buffer_ = allocator.allocate(buffer_length); diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index b5cd2737be..1a62da2323 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -2,6 +2,7 @@ #include #include +#include "rect.h" #include "display_color_utils.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" @@ -131,33 +132,6 @@ enum DisplayRotation { DISPLAY_ROTATION_270_DEGREES = 270, }; -static const int16_t VALUE_NO_SET = 32766; - -class Rect { - public: - int16_t x; ///< X coordinate of corner - int16_t y; ///< Y coordinate of corner - int16_t w; ///< Width of region - int16_t h; ///< Height of region - - Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT - inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {} - inline int16_t x2() { return this->x + this->w; }; ///< X coordinate of corner - inline int16_t y2() { return this->y + this->h; }; ///< Y coordinate of corner - - inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); } - - void expand(int16_t horizontal, int16_t vertical); - - void extend(Rect rect); - void shrink(Rect rect); - - bool inside(Rect rect, bool absolute = true); - bool inside(int16_t test_x, int16_t test_y, bool absolute = true); - bool equal(Rect rect); - void info(const std::string &prefix = "rect info:"); -}; - class DisplayBuffer; class DisplayPage; class DisplayOnPageChangeTrigger; diff --git a/esphome/components/display/rect.cpp b/esphome/components/display/rect.cpp new file mode 100644 index 0000000000..6e91c86c4f --- /dev/null +++ b/esphome/components/display/rect.cpp @@ -0,0 +1,98 @@ +#include "rect.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace display { + +static const char *const TAG = "display"; + +void Rect::expand(int16_t horizontal, int16_t vertical) { + if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) { + this->x = this->x - horizontal; + this->y = this->y - vertical; + this->w = this->w + (2 * horizontal); + this->h = this->h + (2 * vertical); + } +} + +void Rect::extend(Rect rect) { + if (!this->is_set()) { + this->x = rect.x; + this->y = rect.y; + this->w = rect.w; + this->h = rect.h; + } else { + if (this->x > rect.x) { + this->w = this->w + (this->x - rect.x); + this->x = rect.x; + } + if (this->y > rect.y) { + this->h = this->h + (this->y - rect.y); + this->y = rect.y; + } + if (this->x2() < rect.x2()) { + this->w = rect.x2() - this->x; + } + if (this->y2() < rect.y2()) { + this->h = rect.y2() - this->y; + } + } +} +void Rect::shrink(Rect rect) { + if (!this->inside(rect)) { + (*this) = Rect(); + } else { + if (this->x2() > rect.x2()) { + this->w = rect.x2() - this->x; + } + if (this->x < rect.x) { + this->w = this->w + (this->x - rect.x); + this->x = rect.x; + } + if (this->y2() > rect.y2()) { + this->h = rect.y2() - this->y; + } + if (this->y < rect.y) { + this->h = this->h + (this->y - rect.y); + this->y = rect.y; + } + } +} + +bool Rect::equal(Rect rect) { + return (rect.x == this->x) && (rect.w == this->w) && (rect.y == this->y) && (rect.h == this->h); +} + +bool Rect::inside(int16_t test_x, int16_t test_y, bool absolute) { // NOLINT + if (!this->is_set()) { + return true; + } + if (absolute) { + return ((test_x >= this->x) && (test_x <= this->x2()) && (test_y >= this->y) && (test_y <= this->y2())); + } else { + return ((test_x >= 0) && (test_x <= this->w) && (test_y >= 0) && (test_y <= this->h)); + } +} + +bool Rect::inside(Rect rect, bool absolute) { + if (!this->is_set() || !rect.is_set()) { + return true; + } + if (absolute) { + return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y)); + } else { + return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0)); + } +} + +void Rect::info(const std::string &prefix) { + if (this->is_set()) { + ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d] (%3d,%3d)", prefix.c_str(), this->x, this->y, this->w, this->h, this->x2(), + this->y2()); + } else + ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str()); +} + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/rect.h b/esphome/components/display/rect.h new file mode 100644 index 0000000000..867a9c67c7 --- /dev/null +++ b/esphome/components/display/rect.h @@ -0,0 +1,36 @@ +#pragma once + +#include "esphome/core/helpers.h" + +namespace esphome { +namespace display { + +static const int16_t VALUE_NO_SET = 32766; + +class Rect { + public: + int16_t x; ///< X coordinate of corner + int16_t y; ///< Y coordinate of corner + int16_t w; ///< Width of region + int16_t h; ///< Height of region + + Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT + inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {} + inline int16_t x2() { return this->x + this->w; }; ///< X coordinate of corner + inline int16_t y2() { return this->y + this->h; }; ///< Y coordinate of corner + + inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); } + + void expand(int16_t horizontal, int16_t vertical); + + void extend(Rect rect); + void shrink(Rect rect); + + bool inside(Rect rect, bool absolute = true); + bool inside(int16_t test_x, int16_t test_y, bool absolute = true); + bool equal(Rect rect); + void info(const std::string &prefix = "rect info:"); +}; + +} // namespace display +} // namespace esphome From b9f20b36cbae0515f36a877615b880e1491dde61 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 18 Jun 2023 18:35:47 -0500 Subject: [PATCH 014/106] Store app comment and compilation_time in flash (#4945) --- esphome/core/application.cpp | 2 +- esphome/core/application.h | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index c012195f34..d82a7a5d37 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -99,7 +99,7 @@ void Application::loop() { if (this->dump_config_at_ < this->components_.size()) { if (this->dump_config_at_ == 0) { - ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_.c_str()); + ESP_LOGI(TAG, "ESPHome version " ESPHOME_VERSION " compiled on %s", this->compilation_time_); #ifdef ESPHOME_PROJECT_NAME ESP_LOGI(TAG, "Project " ESPHOME_PROJECT_NAME " version " ESPHOME_PROJECT_VERSION); #endif diff --git a/esphome/core/application.h b/esphome/core/application.h index 0501d1a56a..054f2ea648 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -56,7 +56,7 @@ namespace esphome { class Application { public: - void pre_setup(const std::string &name, const std::string &friendly_name, const std::string &comment, + void pre_setup(const std::string &name, const std::string &friendly_name, const char *comment, const char *compilation_time, bool name_add_mac_suffix) { arch_init(); this->name_add_mac_suffix_ = name_add_mac_suffix; @@ -154,11 +154,11 @@ class Application { /// Get the friendly name of this Application set by pre_setup(). const std::string &get_friendly_name() const { return this->friendly_name_; } /// Get the comment of this Application set by pre_setup(). - const std::string &get_comment() const { return this->comment_; } + std::string get_comment() const { return this->comment_; } bool is_name_add_mac_suffix_enabled() const { return this->name_add_mac_suffix_; } - const std::string &get_compilation_time() const { return this->compilation_time_; } + std::string get_compilation_time() const { return this->compilation_time_; } /** Set the target interval with which to run the loop() calls. * If the loop() method takes longer than the target interval, ESPHome won't @@ -376,8 +376,8 @@ class Application { std::string name_; std::string friendly_name_; - std::string comment_; - std::string compilation_time_; + const char *comment_{nullptr}; + const char *compilation_time_{nullptr}; bool name_add_mac_suffix_; uint32_t last_loop_{0}; uint32_t loop_interval_{16}; From cd57271386bfb844d430c386d386ddf19fa97621 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 18 Jun 2023 20:51:19 -0500 Subject: [PATCH 015/106] Construct web_server assets at build time instead of run time (#4944) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/web_server/__init__.py | 49 ++++++++++++++--- esphome/components/web_server/web_server.cpp | 53 ++++++++----------- esphome/components/web_server/web_server.h | 42 ++++++++++++--- .../web_server_base/web_server_base.h | 1 + 4 files changed, 100 insertions(+), 45 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index d8343c6c39..130c082277 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -81,6 +81,37 @@ CONFIG_SCHEMA = cv.All( ) +def build_index_html(config) -> str: + html = "" + css_include = config.get(CONF_CSS_INCLUDE) + js_include = config.get(CONF_JS_INCLUDE) + if css_include: + html += "" + if config[CONF_CSS_URL]: + html += f'' + html += "" + if js_include: + html += "" + html += "" + if config[CONF_JS_URL]: + html += f'' + html += "" + return html + + +def add_resource_as_progmem(resource_name: str, content: str) -> None: + """Add a resource to progmem.""" + content_encoded = content.encode("utf-8") + content_encoded_size = len(content_encoded) + bytes_as_int = ", ".join(str(x) for x in content_encoded) + uint8_t = f"const uint8_t ESPHOME_WEBSERVER_{resource_name}[{content_encoded_size}] PROGMEM = {{{bytes_as_int}}}" + size_t = ( + f"const size_t ESPHOME_WEBSERVER_{resource_name}_SIZE = {content_encoded_size}" + ) + cg.add_global(cg.RawExpression(uint8_t)) + cg.add_global(cg.RawExpression(size_t)) + + @coroutine_with_priority(40.0) async def to_code(config): paren = await cg.get_variable(config[CONF_WEB_SERVER_BASE_ID]) @@ -89,13 +120,17 @@ async def to_code(config): await cg.register_component(var, config) cg.add_define("USE_WEBSERVER") + version = config[CONF_VERSION] cg.add(paren.set_port(config[CONF_PORT])) cg.add_define("USE_WEBSERVER") cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) - cg.add_define("USE_WEBSERVER_VERSION", config[CONF_VERSION]) - cg.add(var.set_css_url(config[CONF_CSS_URL])) - cg.add(var.set_js_url(config[CONF_JS_URL])) + cg.add_define("USE_WEBSERVER_VERSION", version) + if version == 2: + add_resource_as_progmem("INDEX_HTML", build_index_html(config)) + else: + cg.add(var.set_css_url(config[CONF_CSS_URL])) + cg.add(var.set_js_url(config[CONF_JS_URL])) cg.add(var.set_allow_ota(config[CONF_OTA])) if CONF_AUTH in config: cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) @@ -103,13 +138,13 @@ async def to_code(config): if CONF_CSS_INCLUDE in config: cg.add_define("USE_WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) - with open(file=path, encoding="utf-8") as myfile: - cg.add(var.set_css_include(myfile.read())) + with open(file=path, encoding="utf-8") as css_file: + add_resource_as_progmem("CSS_INCLUDE", css_file.read()) if CONF_JS_INCLUDE in config: cg.add_define("USE_WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) - with open(file=path, encoding="utf-8") as myfile: - cg.add(var.set_js_include(myfile.read())) + with open(file=path, encoding="utf-8") as js_file: + add_resource_as_progmem("JS_INCLUDE", js_file.read()) cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL])) if CONF_LOCAL in config and config[CONF_LOCAL]: cg.add_define("USE_WEBSERVER_LOCAL") diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 1ac94375c2..b3a2dfdb66 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -90,10 +90,17 @@ WebServer::WebServer(web_server_base::WebServerBase *base) #endif } +#if USE_WEBSERVER_VERSION == 1 void WebServer::set_css_url(const char *css_url) { this->css_url_ = css_url; } -void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; } void WebServer::set_js_url(const char *js_url) { this->js_url_ = js_url; } +#endif + +#ifdef USE_WEBSERVER_CSS_INCLUDE +void WebServer::set_css_include(const char *css_include) { this->css_include_ = css_include; } +#endif +#ifdef USE_WEBSERVER_JS_INCLUDE void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; } +#endif void WebServer::setup() { ESP_LOGCONFIG(TAG, "Setting up web server..."); @@ -159,20 +166,14 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { response->addHeader("Content-Encoding", "gzip"); request->send(response); } -#else +#elif USE_WEBSERVER_VERSION == 1 void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/html"); - // All content is controlled and created by user - so allowing all origins is fine here. - stream->addHeader("Access-Control-Allow-Origin", "*"); -#if USE_WEBSERVER_VERSION == 1 const std::string &title = App.get_name(); stream->print(F("")); stream->print(title.c_str()); stream->print(F("")); -#else - stream->print(F("")); -#endif #ifdef USE_WEBSERVER_CSS_INCLUDE stream->print(F("")); #endif @@ -182,7 +183,6 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("\">")); } stream->print(F("")); -#if USE_WEBSERVER_VERSION == 1 stream->print(F("

")); stream->print(title.c_str()); stream->print(F("

")); @@ -308,49 +308,40 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { "type=\"file\" name=\"update\">")); } stream->print(F("

Debug Log

"));
-#endif
 #ifdef USE_WEBSERVER_JS_INCLUDE
   if (this->js_include_ != nullptr) {
     stream->print(F(""));
   }
-#endif
-#if USE_WEBSERVER_VERSION == 2
-  stream->print(F(""));
 #endif
   if (strlen(this->js_url_) > 0) {
     stream->print(F(""));
   }
-#if USE_WEBSERVER_VERSION == 1
   stream->print(F("
")); -#else - stream->print(F("")); -#endif - request->send(stream); } +#elif USE_WEBSERVER_VERSION == 2 +void WebServer::handle_index_request(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = + request->beginResponse_P(200, "text/html", ESPHOME_WEBSERVER_INDEX_HTML, ESPHOME_WEBSERVER_INDEX_HTML_SIZE); + request->send(response); +} #endif + #ifdef USE_WEBSERVER_CSS_INCLUDE void WebServer::handle_css_request(AsyncWebServerRequest *request) { - AsyncResponseStream *stream = request->beginResponseStream("text/css"); - if (this->css_include_ != nullptr) { - stream->print(this->css_include_); - } - - request->send(stream); + AsyncWebServerResponse *response = + request->beginResponse_P(200, "text/css", ESPHOME_WEBSERVER_CSS_INCLUDE, ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE); + request->send(response); } #endif #ifdef USE_WEBSERVER_JS_INCLUDE void WebServer::handle_js_request(AsyncWebServerRequest *request) { - AsyncResponseStream *stream = request->beginResponseStream("text/javascript"); - if (this->js_include_ != nullptr) { - stream->addHeader("Access-Control-Allow-Origin", "*"); - stream->print(this->js_include_); - } - - request->send(stream); + AsyncWebServerResponse *response = + request->beginResponse_P(200, "text/javascript", ESPHOME_WEBSERVER_JS_INCLUDE, ESPHOME_WEBSERVER_JS_INCLUDE_SIZE); + request->send(response); } #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 45d0bc03a4..a664bb5a12 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -14,6 +14,22 @@ #include #include #endif + +#if USE_WEBSERVER_VERSION == 2 +extern const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM; +extern const size_t ESPHOME_WEBSERVER_INDEX_HTML_SIZE; +#endif + +#ifdef USE_WEBSERVER_CSS_INCLUDE +extern const uint8_t ESPHOME_WEBSERVER_CSS_INCLUDE[] PROGMEM; +extern const size_t ESPHOME_WEBSERVER_CSS_INCLUDE_SIZE; +#endif + +#ifdef USE_WEBSERVER_JS_INCLUDE +extern const uint8_t ESPHOME_WEBSERVER_JS_INCLUDE[] PROGMEM; +extern const size_t ESPHOME_WEBSERVER_JS_INCLUDE_SIZE; +#endif + namespace esphome { namespace web_server { @@ -40,6 +56,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { public: WebServer(web_server_base::WebServerBase *base); +#if USE_WEBSERVER_VERSION == 1 /** Set the URL to the CSS that's sent to each client. Defaults to * https://esphome.io/_static/webserver-v1.min.css * @@ -47,24 +64,29 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { */ void set_css_url(const char *css_url); - /** Set local path to the script that's embedded in the index page. Defaults to - * - * @param css_include Local path to web server script. - */ - void set_css_include(const char *css_include); - /** Set the URL to the script that's embedded in the index page. Defaults to * https://esphome.io/_static/webserver-v1.min.js * * @param js_url The url to the web server script. */ void set_js_url(const char *js_url); +#endif +#ifdef USE_WEBSERVER_CSS_INCLUDE + /** Set local path to the script that's embedded in the index page. Defaults to + * + * @param css_include Local path to web server script. + */ + void set_css_include(const char *css_include); +#endif + +#ifdef USE_WEBSERVER_JS_INCLUDE /** Set local path to the script that's embedded in the index page. Defaults to * * @param js_include Local path to web server script. */ void set_js_include(const char *js_include); +#endif /** Determine whether internal components should be displayed on the web server. * Defaults to false. @@ -240,10 +262,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { web_server_base::WebServerBase *base_; AsyncEventSource events_{"/events"}; ListEntitiesIterator entities_iterator_; +#if USE_WEBSERVER_VERSION == 1 const char *css_url_{nullptr}; - const char *css_include_{nullptr}; const char *js_url_{nullptr}; +#endif +#ifdef USE_WEBSERVER_CSS_INCLUDE + const char *css_include_{nullptr}; +#endif +#ifdef USE_WEBSERVER_JS_INCLUDE const char *js_include_{nullptr}; +#endif bool include_internal_{false}; bool allow_ota_{true}; #ifdef USE_ESP32 diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index f6d3a84f89..ae286b1e22 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -83,6 +83,7 @@ class WebServerBase : public Component { return; } this->server_ = std::make_shared(this->port_); + // All content is controlled and created by user - so allowing all origins is fine here. DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); this->server_->begin(); From b346ad8080b3a9b29a3920c4ecb56c2dc69321bb Mon Sep 17 00:00:00 2001 From: Stanislav Habich Date: Mon, 19 Jun 2023 03:56:12 +0200 Subject: [PATCH 016/106] Update pca9685_output.cpp (#4929) --- esphome/components/pca9685/pca9685_output.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/esphome/components/pca9685/pca9685_output.cpp b/esphome/components/pca9685/pca9685_output.cpp index c61251b66f..d92312355a 100644 --- a/esphome/components/pca9685/pca9685_output.cpp +++ b/esphome/components/pca9685/pca9685_output.cpp @@ -29,13 +29,10 @@ void PCA9685Output::setup() { ESP_LOGCONFIG(TAG, "Setting up PCA9685OutputComponent..."); ESP_LOGV(TAG, " Resetting devices..."); - uint8_t address_tmp = this->address_; - this->set_i2c_address(0x00); if (!this->write_bytes(PCA9685_REGISTER_SOFTWARE_RESET, nullptr, 0)) { this->mark_failed(); return; } - this->set_i2c_address(address_tmp); if (!this->write_byte(PCA9685_REGISTER_MODE1, PCA9685_MODE1_RESTART | PCA9685_MODE1_AUTOINC)) { this->mark_failed(); From c151df32bcc1de5a73b0e14113f0e7a2232f4299 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jun 2023 13:58:01 +1200 Subject: [PATCH 017/106] Bump pytest from 7.3.1 to 7.3.2 (#4936) 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 d5235d733b..66ce075f91 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.4.0 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.3.1 +pytest==7.3.2 pytest-cov==4.1.0 pytest-mock==3.10.0 pytest-asyncio==0.21.0 From 67771abc9d57d87ae0ceb434998e9b171e6527e5 Mon Sep 17 00:00:00 2001 From: Carson Full Date: Sun, 18 Jun 2023 21:10:05 -0500 Subject: [PATCH 018/106] Add read/write for 16bit registers (#4844) --- esphome/components/i2c/i2c.cpp | 39 ++++++++++++++++++++++++++++++++++ esphome/components/i2c/i2c.h | 22 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index fdc9fd1ddf..2b2190d28b 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -14,6 +14,14 @@ ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len return bus_->read(address_, data, len); } +ErrorCode I2CDevice::read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop) { + a_register = convert_big_endian(a_register); + ErrorCode const err = this->write(reinterpret_cast(&a_register), 2, stop); + if (err != ERROR_OK) + return err; + return bus_->read(address_, data, len); +} + ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop) { WriteBuffer buffers[2]; buffers[0].data = &a_register; @@ -23,6 +31,16 @@ ErrorCode I2CDevice::write_register(uint8_t a_register, const uint8_t *data, siz return bus_->writev(address_, buffers, 2, stop); } +ErrorCode I2CDevice::write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop) { + a_register = convert_big_endian(a_register); + WriteBuffer buffers[2]; + buffers[0].data = reinterpret_cast(&a_register); + buffers[0].len = 2; + buffers[1].data = data; + buffers[1].len = len; + return bus_->writev(address_, buffers, 2, stop); +} + bool I2CDevice::read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len) { if (read_register(a_register, reinterpret_cast(data), len * 2) != ERROR_OK) return false; @@ -60,5 +78,26 @@ uint8_t I2CRegister::get() const { return value; } +I2CRegister16 &I2CRegister16::operator=(uint8_t value) { + this->parent_->write_register16(this->register_, &value, 1); + return *this; +} +I2CRegister16 &I2CRegister16::operator&=(uint8_t value) { + value &= get(); + this->parent_->write_register16(this->register_, &value, 1); + return *this; +} +I2CRegister16 &I2CRegister16::operator|=(uint8_t value) { + value |= get(); + this->parent_->write_register16(this->register_, &value, 1); + return *this; +} + +uint8_t I2CRegister16::get() const { + uint8_t value = 0x00; + this->parent_->read_register16(this->register_, &value, 1); + return value; +} + } // namespace i2c } // namespace esphome diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index 780528a5c7..eb5d463b65 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -31,6 +31,25 @@ class I2CRegister { uint8_t register_; }; +class I2CRegister16 { + public: + I2CRegister16 &operator=(uint8_t value); + I2CRegister16 &operator&=(uint8_t value); + I2CRegister16 &operator|=(uint8_t value); + + explicit operator uint8_t() const { return get(); } + + uint8_t get() const; + + protected: + friend class I2CDevice; + + I2CRegister16(I2CDevice *parent, uint16_t a_register) : parent_(parent), register_(a_register) {} + + I2CDevice *parent_; + uint16_t register_; +}; + // like ntohs/htons but without including networking headers. // ("i2c" byte order is big-endian) inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); } @@ -44,12 +63,15 @@ class I2CDevice { void set_i2c_bus(I2CBus *bus) { bus_ = bus; } I2CRegister reg(uint8_t a_register) { return {this, a_register}; } + I2CRegister16 reg16(uint16_t a_register) { return {this, a_register}; } ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); } ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true); + ErrorCode read_register16(uint16_t a_register, uint8_t *data, size_t len, bool stop = true); ErrorCode write(const uint8_t *data, uint8_t len, bool stop = true) { return bus_->write(address_, data, len, stop); } ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true); + ErrorCode write_register16(uint16_t a_register, const uint8_t *data, size_t len, bool stop = true); // Compat APIs From 41a618737be7173ea2bc60a9b0e5873bf2fb05d3 Mon Sep 17 00:00:00 2001 From: MrEditor97 Date: Mon, 19 Jun 2023 04:26:06 +0100 Subject: [PATCH 019/106] XL9535 I/O Expander (#4899) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/xl9535/__init__.py | 73 +++++++++++++++ esphome/components/xl9535/xl9535.cpp | 122 ++++++++++++++++++++++++++ esphome/components/xl9535/xl9535.h | 54 ++++++++++++ tests/test4.yaml | 13 +++ 5 files changed, 263 insertions(+) create mode 100644 esphome/components/xl9535/__init__.py create mode 100644 esphome/components/xl9535/xl9535.cpp create mode 100644 esphome/components/xl9535/xl9535.h diff --git a/CODEOWNERS b/CODEOWNERS index 595e4a5684..93a599d7fd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -318,4 +318,5 @@ esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_rtcgq02lm/* @jesserockz +esphome/components/xl9535/* @mreditor97 esphome/components/xpt2046/* @nielsnl68 @numo68 diff --git a/esphome/components/xl9535/__init__.py b/esphome/components/xl9535/__init__.py new file mode 100644 index 0000000000..7fcac50ba7 --- /dev/null +++ b/esphome/components/xl9535/__init__.py @@ -0,0 +1,73 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import ( + CONF_ID, + CONF_INPUT, + CONF_INVERTED, + CONF_MODE, + CONF_NUMBER, + CONF_OUTPUT, +) +from esphome import pins + +CONF_XL9535 = "xl9535" + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@mreditor97"] + +xl9535_ns = cg.esphome_ns.namespace(CONF_XL9535) + +XL9535Component = xl9535_ns.class_("XL9535Component", cg.Component, i2c.I2CDevice) +XL9535GPIOPin = xl9535_ns.class_("XL9535GPIOPin", cg.GPIOPin) + +MULTI_CONF = True +CONFIG_SCHEMA = ( + cv.Schema({cv.Required(CONF_ID): cv.declare_id(XL9535Component)}) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x20)) +) + + +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) + + +def validate_mode(mode): + if not (mode[CONF_INPUT] or mode[CONF_OUTPUT]) or ( + mode[CONF_INPUT] and mode[CONF_OUTPUT] + ): + raise cv.Invalid("Mode must be either a input or a output") + return mode + + +XL9535_PIN_SCHEMA = cv.All( + { + cv.GenerateID(): cv.declare_id(XL9535GPIOPin), + cv.Required(CONF_XL9535): cv.use_id(XL9535Component), + cv.Required(CONF_NUMBER): cv.int_range(min=0, max=15), + cv.Optional(CONF_MODE, default={}): cv.All( + { + cv.Optional(CONF_INPUT, default=False): cv.boolean, + cv.Optional(CONF_OUTPUT, default=False): cv.boolean, + }, + validate_mode, + ), + cv.Optional(CONF_INVERTED, default=False): cv.boolean, + } +) + + +@pins.PIN_SCHEMA_REGISTRY.register(CONF_XL9535, XL9535_PIN_SCHEMA) +async def xl9535_pin_to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + parent = await cg.get_variable(config[CONF_XL9535]) + + cg.add(var.set_parent(parent)) + cg.add(var.set_pin(config[CONF_NUMBER])) + 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/xl9535/xl9535.cpp b/esphome/components/xl9535/xl9535.cpp new file mode 100644 index 0000000000..8f4f556b4a --- /dev/null +++ b/esphome/components/xl9535/xl9535.cpp @@ -0,0 +1,122 @@ +#include "xl9535.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace xl9535 { + +static const char *const TAG = "xl9535"; + +void XL9535Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up XL9535..."); + + // Check to see if the device can read from the register + uint8_t port = 0; + if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) { + this->mark_failed(); + return; + } +} + +void XL9535Component::dump_config() { + ESP_LOGCONFIG(TAG, "XL9535:"); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with XL9535 failed!"); + } +} + +bool XL9535Component::digital_read(uint8_t pin) { + bool state = false; + uint8_t port = 0; + + if (pin > 7) { + if (this->read_register(XL9535_INPUT_PORT_1_REGISTER, &port, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return state; + } + + state = (port & (pin - 10)) != 0; + } else { + if (this->read_register(XL9535_INPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return state; + } + + state = (port & pin) != 0; + } + + this->status_clear_warning(); + return state; +} + +void XL9535Component::digital_write(uint8_t pin, bool value) { + uint8_t port = 0; + uint8_t register_data = 0; + + if (pin > 7) { + if (this->read_register(XL9535_OUTPUT_PORT_1_REGISTER, ®ister_data, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + + register_data = register_data & (~(1 << (pin - 10))); + port = register_data | value << (pin - 10); + + if (this->write_register(XL9535_OUTPUT_PORT_1_REGISTER, &port, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + } else { + if (this->read_register(XL9535_OUTPUT_PORT_0_REGISTER, ®ister_data, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + register_data = register_data & (~(1 << pin)); + port = register_data | value << pin; + + if (this->write_register(XL9535_OUTPUT_PORT_0_REGISTER, &port, 1) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + } + + this->status_clear_warning(); +} + +void XL9535Component::pin_mode(uint8_t pin, gpio::Flags mode) { + uint8_t port = 0; + + if (pin > 7) { + this->read_register(XL9535_CONFIG_PORT_1_REGISTER, &port, 1); + + if (mode == gpio::FLAG_INPUT) { + port = port | (1 << (pin - 10)); + } else if (mode == gpio::FLAG_OUTPUT) { + port = port & (~(1 << (pin - 10))); + } + + this->write_register(XL9535_CONFIG_PORT_1_REGISTER, &port, 1); + } else { + this->read_register(XL9535_CONFIG_PORT_0_REGISTER, &port, 1); + + if (mode == gpio::FLAG_INPUT) { + port = port | (1 << pin); + } else if (mode == gpio::FLAG_OUTPUT) { + port = port & (~(1 << pin)); + } + + this->write_register(XL9535_CONFIG_PORT_0_REGISTER, &port, 1); + } +} + +void XL9535GPIOPin::setup() { this->pin_mode(this->flags_); } + +std::string XL9535GPIOPin::dump_summary() const { return str_snprintf("%u via XL9535", 15, this->pin_); } + +void XL9535GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); } +bool XL9535GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; } +void XL9535GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); } + +} // namespace xl9535 +} // namespace esphome diff --git a/esphome/components/xl9535/xl9535.h b/esphome/components/xl9535/xl9535.h new file mode 100644 index 0000000000..8f0a868c42 --- /dev/null +++ b/esphome/components/xl9535/xl9535.h @@ -0,0 +1,54 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace xl9535 { + +enum { + XL9535_INPUT_PORT_0_REGISTER = 0x00, + XL9535_INPUT_PORT_1_REGISTER = 0x01, + XL9535_OUTPUT_PORT_0_REGISTER = 0x02, + XL9535_OUTPUT_PORT_1_REGISTER = 0x03, + XL9535_INVERSION_PORT_0_REGISTER = 0x04, + XL9535_INVERSION_PORT_1_REGISTER = 0x05, + XL9535_CONFIG_PORT_0_REGISTER = 0x06, + XL9535_CONFIG_PORT_1_REGISTER = 0x07, +}; + +class XL9535Component : public Component, public i2c::I2CDevice { + public: + bool digital_read(uint8_t pin); + void digital_write(uint8_t pin, bool value); + void pin_mode(uint8_t pin, gpio::Flags mode); + + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } +}; + +class XL9535GPIOPin : public GPIOPin { + public: + void set_parent(XL9535Component *parent) { this->parent_ = parent; } + void set_pin(uint8_t pin) { this->pin_ = pin; } + void set_inverted(bool inverted) { this->inverted_ = inverted; } + void set_flags(gpio::Flags flags) { this->flags_ = flags; } + + void setup() override; + std::string dump_summary() const override; + void pin_mode(gpio::Flags flags) override; + bool digital_read() override; + void digital_write(bool value) override; + + protected: + XL9535Component *parent_; + + uint8_t pin_; + bool inverted_; + gpio::Flags flags_; +}; + +} // namespace xl9535 +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index 8e76a5fd66..3c9f9f610c 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -384,6 +384,15 @@ binary_sensor: pullup: true inverted: false + - platform: gpio + name: XL9535 Pin 0 + pin: + xl9535: xl9535_hub + number: 0 + mode: + input: true + inverted: false + climate: - platform: tuya id: tuya_climate @@ -745,3 +754,7 @@ voice_assistant: max6956: - id: max6956_1 address: 0x40 + +xl9535: + - id: xl9535_hub + address: 0x20 From 5a8b7c17daf7152e8f56a1966ee8f921d016fbce Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 20 Jun 2023 09:08:23 +1200 Subject: [PATCH 020/106] Fix python venv restoring (#4965) * Fix python venv restoring * Add shell * Fix indentation --- .github/actions/restore-python/action.yml | 38 ++++++++++ .github/workflows/ci.yml | 90 +++++++++++------------ 2 files changed, 81 insertions(+), 47 deletions(-) create mode 100644 .github/actions/restore-python/action.yml diff --git a/.github/actions/restore-python/action.yml b/.github/actions/restore-python/action.yml new file mode 100644 index 0000000000..c6e9ca4153 --- /dev/null +++ b/.github/actions/restore-python/action.yml @@ -0,0 +1,38 @@ +name: Restore Python +inputs: + python-version: + description: Python version to restore + required: true + type: string + cache-key: + description: Cache key to use + required: true + type: string +outputs: + python-version: + description: Python version restored + value: ${{ steps.python.outputs.python-version }} +runs: + using: "composite" + steps: + - name: Set up Python ${{ inputs.python-version }} + id: python + uses: actions/setup-python@v4.6.0 + with: + python-version: ${{ inputs.python-version }} + - name: Restore Python virtual environment + id: cache-venv + uses: actions/cache/restore@v3.3.1 + with: + path: venv + # yamllint disable-line rule:line-length + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ inputs.cache-key }} + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + shell: bash + run: | + python -m venv venv + . venv/bin/activate + python --version + pip install -r requirements.txt -r requirements_optional.txt -r requirements_test.txt + pip install -e . diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95e7619a19..105f0f12b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,10 +26,16 @@ jobs: common: name: Create common environment runs-on: ubuntu-latest + outputs: + cache-key: ${{ steps.cache-key.outputs.key }} steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 + - name: Generate cache-key + id: cache-key + run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT - name: Set up Python ${{ env.DEFAULT_PYTHON }} + id: python uses: actions/setup-python@v4.6.0 with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -39,7 +45,7 @@ jobs: with: path: venv # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + key: ${{ runner.os }}-${{ steps.python.outputs.python-version }}-venv-${{ steps.cache-key.outputs.key }} - name: Create Python virtual environment if: steps.cache-venv.outputs.cache-hit != 'true' run: | @@ -66,12 +72,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Run black run: | . venv/bin/activate @@ -88,12 +93,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Run flake8 run: | . venv/bin/activate @@ -110,12 +114,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Run pylint run: | . venv/bin/activate @@ -132,12 +135,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Run pyupgrade run: | . venv/bin/activate @@ -154,12 +156,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Register matcher run: echo "::add-matcher::.github/workflows/matchers/ci-custom.json" - name: Run script/ci-custom @@ -176,12 +177,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Register matcher run: echo "::add-matcher::.github/workflows/matchers/pytest.json" - name: Run pytest @@ -197,12 +197,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Install clang-format run: | . venv/bin/activate @@ -237,12 +236,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio uses: actions/cache@v3.3.1 with: @@ -300,13 +298,11 @@ jobs: steps: - name: Check out code from GitHub uses: actions/checkout@v3.5.2 - - name: Restore Python virtual environment - uses: actions/cache/restore@v3.3.1 + - name: Restore Python + uses: ./.github/actions/restore-python with: - path: venv - # yamllint disable-line rule:line-length - key: ${{ runner.os }}-${{ env.DEFAULT_PYTHON }}-venv-${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }} - # Use per check platformio cache because checks use different parts + python-version: ${{ env.DEFAULT_PYTHON }} + cache-key: ${{ needs.common.outputs.cache-key }} - name: Cache platformio uses: actions/cache@v3.3.1 with: From 7ceb16cc5a4b078eebe0e67e82462f16faee9f1f Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 20 Jun 2023 00:34:46 +0200 Subject: [PATCH 021/106] Preprocess away unused code when IPv6 is disabled (#4973) --- esphome/components/wifi/wifi_component_esp_idf.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index e18d3cc043..744fc755fe 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -58,7 +58,9 @@ struct IDFWiFiEvent { wifi_event_ap_probe_req_rx_t ap_probe_req_rx; wifi_event_bss_rssi_low_t bss_rssi_low; ip_event_got_ip_t ip_got_ip; +#if LWIP_IPV6 ip_event_got_ip6_t ip_got_ip6; +#endif ip_event_ap_staipassigned_t ip_ap_staipassigned; } data; }; @@ -82,8 +84,10 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); +#if LWIP_IPV6 } else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) { memcpy(&event.data.ip_got_ip6, event_data, sizeof(ip_event_got_ip6_t)); +#endif } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { @@ -504,7 +508,9 @@ const char *get_auth_mode_str(uint8_t mode) { } std::string format_ip4_addr(const esp_ip4_addr_t &ip) { return str_snprintf(IPSTR, 15, IP2STR(&ip)); } +#if LWIP_IPV6 std::string format_ip6_addr(const esp_ip6_addr_t &ip) { return str_snprintf(IPV6STR, 39, IPV62STR(ip)); } +#endif const char *get_disconnect_reason_str(uint8_t reason) { switch (reason) { case WIFI_REASON_AUTH_EXPIRE: @@ -644,9 +650,11 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { format_ip4_addr(it.ip_info.gw).c_str()); s_sta_got_ip = true; +#if LWIP_IPV6 } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { const auto &it = data->data.ip_got_ip6; ESP_LOGV(TAG, "Event: Got IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); +#endif } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGV(TAG, "Event: Lost IP"); From b2ccd32cd7d81034febdb551142394977c8b806c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:35:26 +1200 Subject: [PATCH 022/106] Bump aioesphomeapi from 14.0.0 to 14.1.0 (#4972) 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 b994de1932..e0d6108526 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==6.1.7 # When updating platformio, also update Dockerfile esptool==4.6 click==8.1.3 esphome-dashboard==20230516.0 -aioesphomeapi==14.0.0 +aioesphomeapi==14.1.0 zeroconf==0.63.0 # esp-idf requires this, but doesn't bundle it by default From ee12c68b8faa24241941033d7259e08958b41fef Mon Sep 17 00:00:00 2001 From: guillempages Date: Tue, 20 Jun 2023 00:50:02 +0200 Subject: [PATCH 023/106] Add actions to animation (#4959) --- esphome/components/animation/__init__.py | 45 ++++++++++++++++++++++-- esphome/components/display/animation.h | 30 ++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index f51d115d9e..8a39ec5a87 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -1,6 +1,6 @@ import logging -from esphome import core +from esphome import automation, core from esphome.components import display, font import esphome.components.image as espImage from esphome.components.image import CONF_USE_TRANSPARENCY @@ -18,15 +18,28 @@ from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) +CODEOWNERS = ["@syndlex"] DEPENDENCIES = ["display"] MULTI_CONF = True CONF_LOOP = "loop" CONF_START_FRAME = "start_frame" CONF_END_FRAME = "end_frame" +CONF_FRAME = "frame" Animation_ = display.display_ns.class_("Animation", espImage.Image_) +# Actions +NextFrameAction = display.display_ns.class_( + "AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_) +) +PrevFrameAction = display.display_ns.class_( + "AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_) +) +SetFrameAction = display.display_ns.class_( + "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_) +) + def validate_cross_dependencies(config): """ @@ -74,7 +87,35 @@ ANIMATION_SCHEMA = cv.Schema( CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA) -CODEOWNERS = ["@syndlex"] +NEXT_FRAME_SCHEMA = automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(Animation_), + } +) +PREV_FRAME_SCHEMA = automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(Animation_), + } +) +SET_FRAME_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(Animation_), + cv.Required(CONF_FRAME): cv.uint16_t, + } +) + + +@automation.register_action("animation.next_frame", NextFrameAction, NEXT_FRAME_SCHEMA) +@automation.register_action("animation.prev_frame", PrevFrameAction, PREV_FRAME_SCHEMA) +@automation.register_action("animation.set_frame", SetFrameAction, SET_FRAME_SCHEMA) +async def animation_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) + + if CONF_FRAME in config: + template_ = await cg.templatable(config[CONF_FRAME], args, cg.uint16) + cg.add(var.set_frame(template_)) + return var async def to_code(config): diff --git a/esphome/components/display/animation.h b/esphome/components/display/animation.h index 38e632ccf0..3371cd9e71 100644 --- a/esphome/components/display/animation.h +++ b/esphome/components/display/animation.h @@ -1,6 +1,8 @@ #pragma once #include "image.h" +#include "esphome/core/automation.h" + namespace esphome { namespace display { @@ -33,5 +35,33 @@ class Animation : public Image { int loop_current_iteration_; }; +template class AnimationNextFrameAction : public Action { + public: + AnimationNextFrameAction(Animation *parent) : parent_(parent) {} + void play(Ts... x) override { this->parent_->next_frame(); } + + protected: + Animation *parent_; +}; + +template class AnimationPrevFrameAction : public Action { + public: + AnimationPrevFrameAction(Animation *parent) : parent_(parent) {} + void play(Ts... x) override { this->parent_->prev_frame(); } + + protected: + Animation *parent_; +}; + +template class AnimationSetFrameAction : public Action { + public: + AnimationSetFrameAction(Animation *parent) : parent_(parent) {} + TEMPLATABLE_VALUE(uint16_t, frame) + void play(Ts... x) override { this->parent_->set_frame(this->frame_.value(x...)); } + + protected: + Animation *parent_; +}; + } // namespace display } // namespace esphome From 24067312f68aedfa3c360f823210853464fa2723 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:18:06 +1200 Subject: [PATCH 024/106] Bump zeroconf from 0.63.0 to 0.69.0 (#4970) 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 e0d6108526..269405d55e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ esptool==4.6 click==8.1.3 esphome-dashboard==20230516.0 aioesphomeapi==14.1.0 -zeroconf==0.63.0 +zeroconf==0.69.0 # esp-idf requires this, but doesn't bundle it by default # https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24 From bfe85dd710fc1d34dd9413d268f72999b546ad2c Mon Sep 17 00:00:00 2001 From: Martin Murray Date: Tue, 20 Jun 2023 19:53:21 -0400 Subject: [PATCH 025/106] Apply configured IIR filter setting in generated BMP280 code (#4975) Co-authored-by: Martin Murray --- esphome/components/bmp280/sensor.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/bmp280/sensor.py b/esphome/components/bmp280/sensor.py index 95a9577f7e..e80511a509 100644 --- a/esphome/components/bmp280/sensor.py +++ b/esphome/components/bmp280/sensor.py @@ -94,3 +94,5 @@ async def to_code(config): sens = await sensor.new_sensor(conf) cg.add(var.set_pressure_sensor(sens)) cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING])) + + cg.add(var.set_iir_filter(config[CONF_IIR_FILTER])) From cb5a01da29001759eb2deb0e8866e9be311e4baf Mon Sep 17 00:00:00 2001 From: Stijn Tintel Date: Wed, 21 Jun 2023 02:53:32 +0300 Subject: [PATCH 026/106] mqtt: add ESP-IDF >= 5.0 support (#4854) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/mqtt/mqtt_backend_idf.cpp | 39 +++++++++++++++++++- esphome/components/mqtt/mqtt_backend_idf.h | 2 + esphome/components/mqtt/mqtt_component.cpp | 2 +- esphome/components/mqtt/mqtt_sensor.cpp | 3 +- 4 files changed, 42 insertions(+), 4 deletions(-) diff --git a/esphome/components/mqtt/mqtt_backend_idf.cpp b/esphome/components/mqtt/mqtt_backend_idf.cpp index 812e36d522..7a7aca3fa6 100644 --- a/esphome/components/mqtt/mqtt_backend_idf.cpp +++ b/esphome/components/mqtt/mqtt_backend_idf.cpp @@ -11,6 +11,7 @@ namespace mqtt { static const char *const TAG = "mqtt.idf"; bool MQTTBackendIDF::initialize_() { +#if ESP_IDF_VERSION_MAJOR < 5 mqtt_cfg_.user_context = (void *) this; mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE; @@ -47,6 +48,41 @@ bool MQTTBackendIDF::initialize_() { } else { mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP; } +#else + mqtt_cfg_.broker.address.hostname = this->host_.c_str(); + mqtt_cfg_.broker.address.port = this->port_; + mqtt_cfg_.session.keepalive = this->keep_alive_; + mqtt_cfg_.session.disable_clean_session = !this->clean_session_; + + if (!this->username_.empty()) { + mqtt_cfg_.credentials.username = this->username_.c_str(); + if (!this->password_.empty()) { + mqtt_cfg_.credentials.authentication.password = this->password_.c_str(); + } + } + + if (!this->lwt_topic_.empty()) { + mqtt_cfg_.session.last_will.topic = this->lwt_topic_.c_str(); + this->mqtt_cfg_.session.last_will.qos = this->lwt_qos_; + this->mqtt_cfg_.session.last_will.retain = this->lwt_retain_; + + if (!this->lwt_message_.empty()) { + mqtt_cfg_.session.last_will.msg = this->lwt_message_.c_str(); + mqtt_cfg_.session.last_will.msg_len = this->lwt_message_.size(); + } + } + + if (!this->client_id_.empty()) { + mqtt_cfg_.credentials.client_id = this->client_id_.c_str(); + } + if (ca_certificate_.has_value()) { + mqtt_cfg_.broker.verification.certificate = ca_certificate_.value().c_str(); + mqtt_cfg_.broker.verification.skip_cert_common_name_check = skip_cert_cn_check_; + mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_SSL; + } else { + mqtt_cfg_.broker.address.transport = MQTT_TRANSPORT_OVER_TCP; + } +#endif auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_); if (mqtt_client) { handler_.reset(mqtt_client); @@ -78,9 +114,8 @@ void MQTTBackendIDF::mqtt_event_handler_(const Event &event) { case MQTT_EVENT_CONNECTED: ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED"); - // TODO session present check this->is_connected_ = true; - this->on_connect_.call(!mqtt_cfg_.disable_clean_session); + this->on_connect_.call(event.session_present); break; case MQTT_EVENT_DISCONNECTED: ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED"); diff --git a/esphome/components/mqtt/mqtt_backend_idf.h b/esphome/components/mqtt/mqtt_backend_idf.h index 900ee9709b..9c7a5f80e9 100644 --- a/esphome/components/mqtt/mqtt_backend_idf.h +++ b/esphome/components/mqtt/mqtt_backend_idf.h @@ -22,6 +22,7 @@ struct Event { bool retain; int qos; bool dup; + bool session_present; esp_mqtt_error_codes_t error_handle; // Construct from esp_mqtt_event_t @@ -36,6 +37,7 @@ struct Event { retain(event.retain), qos(event.qos), dup(event.dup), + session_present(event.session_present), error_handle(*event.error_handle) {} }; diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index cf228efd1b..b663d7751d 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -118,7 +118,7 @@ bool MQTTComponent::send_discovery_() { } else { if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { char friendly_name_hash[9]; - sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name())); + sprintf(friendly_name_hash, "%08" PRIx32, fnv1_hash(this->friendly_name())); friendly_name_hash[8] = 0; // ensure the hash-string ends with null root[MQTT_UNIQUE_ID] = get_mac_address() + "-" + this->component_type() + "-" + friendly_name_hash; } else { diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 4946cfb924..fff75a3c00 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -1,3 +1,4 @@ +#include #include "mqtt_sensor.h" #include "esphome/core/log.h" @@ -26,7 +27,7 @@ void MQTTSensorComponent::setup() { void MQTTSensorComponent::dump_config() { ESP_LOGCONFIG(TAG, "MQTT Sensor '%s':", this->sensor_->get_name().c_str()); if (this->get_expire_after() > 0) { - ESP_LOGCONFIG(TAG, " Expire After: %us", this->get_expire_after() / 1000); + ESP_LOGCONFIG(TAG, " Expire After: %" PRIu32 "s", this->get_expire_after() / 1000); } LOG_MQTT_COMPONENT(true, false) } From 8bd9f5065909fa0487b76f5db342f808d8e6550c Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 20 Jun 2023 19:53:44 -0400 Subject: [PATCH 027/106] airthings_wave: refactor to eliminate code duplication (#4910) --- CODEOWNERS | 1 + .../airthings_wave_base/__init__.py | 81 ++++++++++++ .../airthings_wave_base.cpp | 83 ++++++++++++ .../airthings_wave_base/airthings_wave_base.h | 50 ++++++++ .../airthings_wave_mini.cpp | 102 ++++----------- .../airthings_wave_mini/airthings_wave_mini.h | 34 +---- .../components/airthings_wave_mini/sensor.py | 74 ++--------- .../airthings_wave_plus.cpp | 118 +++++------------- .../airthings_wave_plus/airthings_wave_plus.h | 31 +---- .../components/airthings_wave_plus/sensor.py | 108 +++++----------- tests/test2.yaml | 6 +- 11 files changed, 317 insertions(+), 371 deletions(-) create mode 100644 esphome/components/airthings_wave_base/__init__.py create mode 100644 esphome/components/airthings_wave_base/airthings_wave_base.cpp create mode 100644 esphome/components/airthings_wave_base/airthings_wave_base.h diff --git a/CODEOWNERS b/CODEOWNERS index 93a599d7fd..94813f73dd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -17,6 +17,7 @@ esphome/components/adc/* @esphome/core esphome/components/adc128s102/* @DeerMaximum esphome/components/addressable_light/* @justfalter esphome/components/airthings_ble/* @jeromelaban +esphome/components/airthings_wave_base/* @jeromelaban @ncareau esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/alarm_control_panel/* @grahambrown11 diff --git a/esphome/components/airthings_wave_base/__init__.py b/esphome/components/airthings_wave_base/__init__.py new file mode 100644 index 0000000000..3ff55fc6b0 --- /dev/null +++ b/esphome/components/airthings_wave_base/__init__.py @@ -0,0 +1,81 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client + +from esphome.const import ( + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + UNIT_PERCENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, + CONF_HUMIDITY, + CONF_TVOC, + CONF_PRESSURE, + CONF_TEMPERATURE, + UNIT_PARTS_PER_BILLION, + ICON_RADIATOR, +) + +CODEOWNERS = ["@ncareau", "@jeromelaban"] + +DEPENDENCIES = ["ble_client"] + +airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base") +AirthingsWaveBase = airthings_wave_base_ns.class_( + "AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode +) + + +BASE_SCHEMA = ( + sensor.SENSOR_SCHEMA.extend( + { + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + accuracy_decimals=0, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TVOC): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_BILLION, + icon=ICON_RADIATOR, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("5min")) + .extend(ble_client.BLE_CLIENT_SCHEMA) +) + + +async def wave_base_to_code(var, config): + await cg.register_component(var, config) + + await ble_client.register_ble_node(var, config) + + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(sens)) + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure(sens)) + if CONF_TVOC in config: + sens = await sensor.new_sensor(config[CONF_TVOC]) + cg.add(var.set_tvoc(sens)) diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.cpp b/esphome/components/airthings_wave_base/airthings_wave_base.cpp new file mode 100644 index 0000000000..349d8d58eb --- /dev/null +++ b/esphome/components/airthings_wave_base/airthings_wave_base.cpp @@ -0,0 +1,83 @@ +#include "airthings_wave_base.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace airthings_wave_base { + +static const char *const TAG = "airthings_wave_base"; + +void AirthingsWaveBase::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, "Connected successfully!"); + } + break; + } + + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "Disconnected!"); + break; + } + + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->handle_ = 0; + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), + this->sensors_data_characteristic_uuid_.to_string().c_str()); + break; + } + this->handle_ = chr->handle; + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + + this->request_read_values_(); + break; + } + + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->parent()->get_conn_id()) + break; + if (param->read.status != ESP_GATT_OK) { + ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); + break; + } + if (param->read.handle == this->handle_) { + this->read_sensors(param->read.value, param->read.value_len); + } + break; + } + + default: + break; + } +} + +bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } + +void AirthingsWaveBase::update() { + if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { + if (!this->parent()->enabled) { + ESP_LOGW(TAG, "Reconnecting to device"); + this->parent()->set_enabled(true); + this->parent()->connect(); + } else { + ESP_LOGW(TAG, "Connection in progress"); + } + } +} + +void AirthingsWaveBase::request_read_values_() { + 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); + } +} + +} // namespace airthings_wave_base +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.h b/esphome/components/airthings_wave_base/airthings_wave_base.h new file mode 100644 index 0000000000..68c0b3497d --- /dev/null +++ b/esphome/components/airthings_wave_base/airthings_wave_base.h @@ -0,0 +1,50 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include +#include +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace airthings_wave_base { + +class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode { + public: + AirthingsWaveBase() = default; + + void update() 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 set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } + void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } + void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } + + protected: + bool is_valid_voc_value_(uint16_t voc); + + virtual void read_sensors(uint8_t *value, uint16_t value_len) = 0; + void request_read_values_(); + + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *humidity_sensor_{nullptr}; + sensor::Sensor *pressure_sensor_{nullptr}; + sensor::Sensor *tvoc_sensor_{nullptr}; + + uint16_t handle_; + esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; +}; + +} // namespace airthings_wave_base +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 40873ec005..331a13434f 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -7,105 +7,47 @@ namespace airthings_wave_mini { static const char *const TAG = "airthings_wave_mini"; -void AirthingsWaveMini::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, "Connected successfully!"); - } - break; - } - - case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGW(TAG, "Disconnected!"); - break; - } - - case ESP_GATTC_SEARCH_CMPL_EVT: { - this->handle_ = 0; - auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); - if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_data_characteristic_uuid_.to_string().c_str()); - break; - } - this->handle_ = chr->handle; - this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; - - request_read_values_(); - break; - } - - case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->get_conn_id()) - break; - if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); - break; - } - if (param->read.handle == this->handle_) { - read_sensors_(param->read.value, param->read.value_len); - } - break; - } - - default: - break; - } -} - -void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { +void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) { auto *value = (WaveMiniReadings *) raw_value; if (sizeof(WaveMiniReadings) <= value_len) { - this->humidity_sensor_->publish_state(value->humidity / 100.0f); - this->pressure_sensor_->publish_state(value->pressure / 50.0f); - this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f); - if (is_valid_voc_value_(value->voc)) { + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->publish_state(value->humidity / 100.0f); + } + + if (this->pressure_sensor_ != nullptr) { + this->pressure_sensor_->publish_state(value->pressure / 50.0f); + } + + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(value->temperature / 100.0f - 273.15f); + } + + if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { this->tvoc_sensor_->publish_state(value->voc); } // This instance must not stay connected // so other clients can connect to it (e.g. the // mobile app). - parent()->set_enabled(false); - } -} - -bool AirthingsWaveMini::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } - -void AirthingsWaveMini::update() { - if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { - if (!parent()->enabled) { - ESP_LOGW(TAG, "Reconnecting to device"); - parent()->set_enabled(true); - parent()->connect(); - } else { - ESP_LOGW(TAG, "Connection in progress"); - } - } -} - -void AirthingsWaveMini::request_read_values_() { - 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); + this->parent()->set_enabled(false); } } void AirthingsWaveMini::dump_config() { + // these really don't belong here, but there doesn't seem to be a + // practical way to have the base class use LOG_SENSOR and include + // the TAG from this component LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); } -AirthingsWaveMini::AirthingsWaveMini() - : PollingComponent(10000), - service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), - sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} +AirthingsWaveMini::AirthingsWaveMini() { + this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); + this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); +} } // namespace airthings_wave_mini } // namespace esphome diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.h b/esphome/components/airthings_wave_mini/airthings_wave_mini.h index 128774f9cb..ec4fd23e60 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.h +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.h @@ -2,14 +2,7 @@ #ifdef USE_ESP32 -#include -#include -#include -#include "esphome/components/ble_client/ble_client.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/core/component.h" -#include "esphome/core/log.h" +#include "esphome/components/airthings_wave_base/airthings_wave_base.h" namespace esphome { namespace airthings_wave_mini { @@ -17,35 +10,14 @@ namespace airthings_wave_mini { static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; -class AirthingsWaveMini : public PollingComponent, public ble_client::BLEClientNode { +class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase { public: AirthingsWaveMini(); void dump_config() override; - void update() 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 set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } - void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } - void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } - void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } protected: - bool is_valid_voc_value_(uint16_t voc); - - void read_sensors_(uint8_t *value, uint16_t value_len); - void request_read_values_(); - - sensor::Sensor *temperature_sensor_{nullptr}; - sensor::Sensor *humidity_sensor_{nullptr}; - sensor::Sensor *pressure_sensor_{nullptr}; - sensor::Sensor *tvoc_sensor_{nullptr}; - - uint16_t handle_; - esp32_ble_tracker::ESPBTUUID service_uuid_; - esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; + void read_sensors(uint8_t *value, uint16_t value_len) override; struct WaveMiniReadings { uint16_t unused01; diff --git a/esphome/components/airthings_wave_mini/sensor.py b/esphome/components/airthings_wave_mini/sensor.py index d38354fa84..0f4fd1a13a 100644 --- a/esphome/components/airthings_wave_mini/sensor.py +++ b/esphome/components/airthings_wave_mini/sensor.py @@ -1,82 +1,28 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor, ble_client +from esphome.components import airthings_wave_base from esphome.const import ( - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, - UNIT_PERCENT, - UNIT_CELSIUS, - UNIT_HECTOPASCAL, CONF_ID, - CONF_HUMIDITY, - CONF_TVOC, - CONF_PRESSURE, - CONF_TEMPERATURE, - UNIT_PARTS_PER_BILLION, - ICON_RADIATOR, ) -DEPENDENCIES = ["ble_client"] +DEPENDENCIES = airthings_wave_base.DEPENDENCIES + +AUTO_LOAD = ["airthings_wave_base"] airthings_wave_mini_ns = cg.esphome_ns.namespace("airthings_wave_mini") AirthingsWaveMini = airthings_wave_mini_ns.class_( - "AirthingsWaveMini", cg.PollingComponent, ble_client.BLEClientNode + "AirthingsWaveMini", airthings_wave_base.AirthingsWaveBase ) -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(AirthingsWaveMini), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - device_class=DEVICE_CLASS_HUMIDITY, - state_class=STATE_CLASS_MEASUREMENT, - accuracy_decimals=2, - ), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=2, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - unit_of_measurement=UNIT_HECTOPASCAL, - accuracy_decimals=2, - device_class=DEVICE_CLASS_PRESSURE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_TVOC): sensor.sensor_schema( - unit_of_measurement=UNIT_PARTS_PER_BILLION, - icon=ICON_RADIATOR, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - } - ) - .extend(cv.polling_component_schema("5min")) - .extend(ble_client.BLE_CLIENT_SCHEMA), +CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AirthingsWaveMini), + } ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) - - await ble_client.register_ble_node(var, config) - - if CONF_HUMIDITY in config: - sens = await sensor.new_sensor(config[CONF_HUMIDITY]) - cg.add(var.set_humidity(sens)) - if CONF_TEMPERATURE in config: - sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) - cg.add(var.set_temperature(sens)) - if CONF_PRESSURE in config: - sens = await sensor.new_sensor(config[CONF_PRESSURE]) - cg.add(var.set_pressure(sens)) - if CONF_TVOC in config: - sens = await sensor.new_sensor(config[CONF_TVOC]) - cg.add(var.set_tvoc(sens)) + await airthings_wave_base.wave_base_to_code(var, config) diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 11f86307fe..acd3a4316d 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -7,55 +7,7 @@ namespace airthings_wave_plus { static const char *const TAG = "airthings_wave_plus"; -void AirthingsWavePlus::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, "Connected successfully!"); - } - break; - } - - case ESP_GATTC_DISCONNECT_EVT: { - ESP_LOGW(TAG, "Disconnected!"); - break; - } - - case ESP_GATTC_SEARCH_CMPL_EVT: { - this->handle_ = 0; - auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); - if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), - sensors_data_characteristic_uuid_.to_string().c_str()); - break; - } - this->handle_ = chr->handle; - this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; - - request_read_values_(); - break; - } - - case ESP_GATTC_READ_CHAR_EVT: { - if (param->read.conn_id != this->parent()->get_conn_id()) - break; - if (param->read.status != ESP_GATT_OK) { - ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status); - break; - } - if (param->read.handle == this->handle_) { - read_sensors_(param->read.value, param->read.value_len); - } - break; - } - - default: - break; - } -} - -void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { +void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { auto *value = (WavePlusReadings *) raw_value; if (sizeof(WavePlusReadings) <= value_len) { @@ -64,26 +16,38 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { if (value->version == 1) { ESP_LOGD(TAG, "ambient light = %d", value->ambientLight); - this->humidity_sensor_->publish_state(value->humidity / 2.0f); - if (is_valid_radon_value_(value->radon)) { + if (this->humidity_sensor_ != nullptr) { + this->humidity_sensor_->publish_state(value->humidity / 2.0f); + } + + if ((this->radon_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon)) { this->radon_sensor_->publish_state(value->radon); } - if (is_valid_radon_value_(value->radon_lt)) { + + if ((this->radon_long_term_sensor_ != nullptr) && this->is_valid_radon_value_(value->radon_lt)) { this->radon_long_term_sensor_->publish_state(value->radon_lt); } - this->temperature_sensor_->publish_state(value->temperature / 100.0f); - this->pressure_sensor_->publish_state(value->pressure / 50.0f); - if (is_valid_co2_value_(value->co2)) { + + if (this->temperature_sensor_ != nullptr) { + this->temperature_sensor_->publish_state(value->temperature / 100.0f); + } + + if (this->pressure_sensor_ != nullptr) { + this->pressure_sensor_->publish_state(value->pressure / 50.0f); + } + + if ((this->co2_sensor_ != nullptr) && this->is_valid_co2_value_(value->co2)) { this->co2_sensor_->publish_state(value->co2); } - if (is_valid_voc_value_(value->voc)) { + + if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { this->tvoc_sensor_->publish_state(value->voc); } // This instance must not stay connected // so other clients can connect to it (e.g. the // mobile app). - parent()->set_enabled(false); + this->parent()->set_enabled(false); } else { ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); } @@ -92,44 +56,26 @@ void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } -bool AirthingsWavePlus::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } - bool AirthingsWavePlus::is_valid_co2_value_(uint16_t co2) { return 0 <= co2 && co2 <= 16383; } -void AirthingsWavePlus::update() { - if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { - if (!parent()->enabled) { - ESP_LOGW(TAG, "Reconnecting to device"); - parent()->set_enabled(true); - parent()->connect(); - } else { - ESP_LOGW(TAG, "Connection in progress"); - } - } -} - -void AirthingsWavePlus::request_read_values_() { - 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); - } -} - void AirthingsWavePlus::dump_config() { + // these really don't belong here, but there doesn't seem to be a + // practical way to have the base class use LOG_SENSOR and include + // the TAG from this component LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); - LOG_SENSOR(" ", "Radon", this->radon_sensor_); - LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); - LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); + + LOG_SENSOR(" ", "Radon", this->radon_sensor_); + LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); } -AirthingsWavePlus::AirthingsWavePlus() - : PollingComponent(10000), - service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), - sensors_data_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID)) {} +AirthingsWavePlus::AirthingsWavePlus() { + this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); + this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); +} } // namespace airthings_wave_plus } // namespace esphome diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 9dd6ed92d5..4acfb9279a 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -2,14 +2,7 @@ #ifdef USE_ESP32 -#include -#include -#include -#include "esphome/components/ble_client/ble_client.h" -#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" -#include "esphome/components/sensor/sensor.h" -#include "esphome/core/component.h" -#include "esphome/core/log.h" +#include "esphome/components/airthings_wave_base/airthings_wave_base.h" namespace esphome { namespace airthings_wave_plus { @@ -17,43 +10,25 @@ namespace airthings_wave_plus { static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; -class AirthingsWavePlus : public PollingComponent, public ble_client::BLEClientNode { +class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { public: AirthingsWavePlus(); void dump_config() override; - void update() 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 set_temperature(sensor::Sensor *temperature) { temperature_sensor_ = temperature; } void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } - void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } - void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } void set_co2(sensor::Sensor *co2) { co2_sensor_ = co2; } - void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } protected: bool is_valid_radon_value_(uint16_t radon); - bool is_valid_voc_value_(uint16_t voc); bool is_valid_co2_value_(uint16_t co2); - void read_sensors_(uint8_t *value, uint16_t value_len); - void request_read_values_(); + void read_sensors(uint8_t *value, uint16_t value_len) override; - sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; - sensor::Sensor *humidity_sensor_{nullptr}; - sensor::Sensor *pressure_sensor_{nullptr}; sensor::Sensor *co2_sensor_{nullptr}; - sensor::Sensor *tvoc_sensor_{nullptr}; - - uint16_t handle_; - esp32_ble_tracker::ESPBTUUID service_uuid_; - esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; struct WavePlusReadings { uint8_t version; diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index 727fbe15fb..a5903b1d42 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -1,116 +1,64 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import sensor, ble_client +from esphome.components import sensor, airthings_wave_base from esphome.const import ( DEVICE_CLASS_CARBON_DIOXIDE, - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_PRESSURE, STATE_CLASS_MEASUREMENT, - UNIT_PERCENT, - UNIT_CELSIUS, - UNIT_HECTOPASCAL, ICON_RADIOACTIVE, CONF_ID, CONF_RADON, CONF_RADON_LONG_TERM, - CONF_HUMIDITY, - CONF_TVOC, CONF_CO2, - CONF_PRESSURE, - CONF_TEMPERATURE, UNIT_BECQUEREL_PER_CUBIC_METER, UNIT_PARTS_PER_MILLION, - UNIT_PARTS_PER_BILLION, - ICON_RADIATOR, ) -DEPENDENCIES = ["ble_client"] +DEPENDENCIES = airthings_wave_base.DEPENDENCIES + +AUTO_LOAD = ["airthings_wave_base"] airthings_wave_plus_ns = cg.esphome_ns.namespace("airthings_wave_plus") AirthingsWavePlus = airthings_wave_plus_ns.class_( - "AirthingsWavePlus", cg.PollingComponent, ble_client.BLEClientNode + "AirthingsWavePlus", airthings_wave_base.AirthingsWaveBase ) -CONFIG_SCHEMA = cv.All( - cv.Schema( - { - cv.GenerateID(): cv.declare_id(AirthingsWavePlus), - cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( - unit_of_measurement=UNIT_PERCENT, - device_class=DEVICE_CLASS_HUMIDITY, - state_class=STATE_CLASS_MEASUREMENT, - accuracy_decimals=0, - ), - cv.Optional(CONF_RADON): sensor.sensor_schema( - unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, - icon=ICON_RADIOACTIVE, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( - unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, - icon=ICON_RADIOACTIVE, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( - unit_of_measurement=UNIT_CELSIUS, - accuracy_decimals=2, - device_class=DEVICE_CLASS_TEMPERATURE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_PRESSURE): sensor.sensor_schema( - unit_of_measurement=UNIT_HECTOPASCAL, - accuracy_decimals=1, - device_class=DEVICE_CLASS_PRESSURE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_CO2): sensor.sensor_schema( - unit_of_measurement=UNIT_PARTS_PER_MILLION, - accuracy_decimals=0, - device_class=DEVICE_CLASS_CARBON_DIOXIDE, - state_class=STATE_CLASS_MEASUREMENT, - ), - cv.Optional(CONF_TVOC): sensor.sensor_schema( - unit_of_measurement=UNIT_PARTS_PER_BILLION, - icon=ICON_RADIATOR, - accuracy_decimals=0, - state_class=STATE_CLASS_MEASUREMENT, - ), - } - ) - .extend(cv.polling_component_schema("5min")) - .extend(ble_client.BLE_CLIENT_SCHEMA), +CONFIG_SCHEMA = airthings_wave_base.BASE_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AirthingsWavePlus), + cv.Optional(CONF_RADON): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } ) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - await cg.register_component(var, config) + await airthings_wave_base.wave_base_to_code(var, config) - await ble_client.register_ble_node(var, config) - - if CONF_HUMIDITY in config: - sens = await sensor.new_sensor(config[CONF_HUMIDITY]) - cg.add(var.set_humidity(sens)) if CONF_RADON in config: sens = await sensor.new_sensor(config[CONF_RADON]) cg.add(var.set_radon(sens)) if CONF_RADON_LONG_TERM in config: sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) cg.add(var.set_radon_long_term(sens)) - if CONF_TEMPERATURE in config: - sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) - cg.add(var.set_temperature(sens)) - if CONF_PRESSURE in config: - sens = await sensor.new_sensor(config[CONF_PRESSURE]) - cg.add(var.set_pressure(sens)) if CONF_CO2 in config: sens = await sensor.new_sensor(config[CONF_CO2]) cg.add(var.set_co2(sens)) - if CONF_TVOC in config: - sens = await sensor.new_sensor(config[CONF_TVOC]) - cg.add(var.set_tvoc(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index fa4b97c7c1..aa3e467816 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -312,7 +312,8 @@ sensor: id: freezer_temp_source reference_voltage: 3.19 number: 0 - - platform: airthings_wave_plus + - id: airthingswp + platform: airthings_wave_plus ble_client_id: airthings01 update_interval: 5min temperature: @@ -329,7 +330,8 @@ sensor: name: Wave Plus CO2 tvoc: name: Wave Plus VOC - - platform: airthings_wave_mini + - id: airthingswm + platform: airthings_wave_mini ble_client_id: airthingsmini01 update_interval: 5min temperature: From 9e7e3708e37987955e624e220f05fa424e2bdc55 Mon Sep 17 00:00:00 2001 From: Onne Date: Wed, 21 Jun 2023 02:22:32 +0200 Subject: [PATCH 028/106] Make growatt play nicer with other modbus components. (#4947) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- .../growatt_solar/growatt_solar.cpp | 31 +++++++++++++++++++ .../components/growatt_solar/growatt_solar.h | 4 +++ 2 files changed, 35 insertions(+) diff --git a/esphome/components/growatt_solar/growatt_solar.cpp b/esphome/components/growatt_solar/growatt_solar.cpp index ed753c4d3f..c4ed5ab841 100644 --- a/esphome/components/growatt_solar/growatt_solar.cpp +++ b/esphome/components/growatt_solar/growatt_solar.cpp @@ -9,11 +9,42 @@ static const char *const TAG = "growatt_solar"; static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04; static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95}; // indexed with enum GrowattProtocolVersion +void GrowattSolar::loop() { + // If update() was unable to send we retry until we can send. + if (!this->waiting_to_update_) + return; + update(); +} + void GrowattSolar::update() { + // If our last send has had no reply yet, and it wasn't that long ago, do nothing. + uint32_t now = millis(); + if (now - this->last_send_ < this->get_update_interval() / 2) { + return; + } + + // The bus might be slow, or there might be other devices, or other components might be talking to our device. + if (this->waiting_for_response()) { + this->waiting_to_update_ = true; + return; + } + + this->waiting_to_update_ = false; this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]); + this->last_send_ = millis(); } void GrowattSolar::on_modbus_data(const std::vector &data) { + // Other components might be sending commands to our device. But we don't get called with enough + // context to know what is what. So if we didn't do a send, we ignore the data. + if (!this->last_send_) + return; + this->last_send_ = 0; + + // Also ignore the data if the message is too short. Otherwise we will publish invalid values. + if (data.size() < MODBUS_REGISTER_COUNT[this->protocol_version_] * 2) + return; + auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void { if (sensor == nullptr) return; diff --git a/esphome/components/growatt_solar/growatt_solar.h b/esphome/components/growatt_solar/growatt_solar.h index d1b08b534a..b0ddd4b99d 100644 --- a/esphome/components/growatt_solar/growatt_solar.h +++ b/esphome/components/growatt_solar/growatt_solar.h @@ -19,6 +19,7 @@ enum GrowattProtocolVersion { class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { public: + void loop() override; void update() override; void on_modbus_data(const std::vector &data) override; void dump_config() override; @@ -55,6 +56,9 @@ class GrowattSolar : public PollingComponent, public modbus::ModbusDevice { } protected: + bool waiting_to_update_; + uint32_t last_send_; + struct GrowattPhase { sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_{nullptr}; From de6c527ca43bdcf4eeb66041da991d6f9c7ee10c Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:30:19 +1200 Subject: [PATCH 029/106] Bump esphome-dashboard to 20230621.0 (#4980) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 269405d55e..a2ef604446 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.1.7 # When updating platformio, also update Dockerfile esptool==4.6 click==8.1.3 -esphome-dashboard==20230516.0 +esphome-dashboard==20230621.0 aioesphomeapi==14.1.0 zeroconf==0.69.0 From e8ce7048d8b5d2bccbf23581c47822e2954645aa Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 21 Jun 2023 16:36:54 +1200 Subject: [PATCH 030/106] Fix pypi release (#4983) --- .github/workflows/release.yml | 4 +++- script/setup | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ebd04e793..74ff4d87f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,9 +49,11 @@ jobs: with: python-version: "3.x" - name: Set up python environment + env: + ESPHOME_NO_VENV: 1 run: | script/setup - pip install setuptools wheel twine + pip install twine - name: Build run: python setup.py sdist bdist_wheel - name: Upload diff --git a/script/setup b/script/setup index 656e95eba6..e1b5941d0e 100755 --- a/script/setup +++ b/script/setup @@ -5,12 +5,13 @@ set -e cd "$(dirname "$0")/.." -if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ]; then +if [ ! -n "$DEVCONTAINER" ] && [ ! -n "$VIRTUAL_ENV" ] && [ ! "$ESPHOME_NO_VENV" ]; 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 setuptools wheel pip3 install --no-use-pep517 -e . pre-commit install From 1cc7428445431f5c2b5e493ca9f240448d94a6fe Mon Sep 17 00:00:00 2001 From: Dion Hulse Date: Thu, 22 Jun 2023 07:58:49 +1000 Subject: [PATCH 031/106] Add configuration option to disable the log UI. (#4419) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/components/web_server/__init__.py | 3 +++ esphome/components/web_server/web_server.cpp | 21 +++++++++++--------- esphome/components/web_server/web_server.h | 9 +++++++++ esphome/const.py | 1 + 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 130c082277..25c0254f90 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_PASSWORD, CONF_INCLUDE_INTERNAL, CONF_OTA, + CONF_LOG, CONF_VERSION, CONF_LOCAL, ) @@ -71,6 +72,7 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.Optional(CONF_OTA, default=True): cv.boolean, + cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), @@ -132,6 +134,7 @@ async def to_code(config): cg.add(var.set_css_url(config[CONF_CSS_URL])) cg.add(var.set_js_url(config[CONF_JS_URL])) cg.add(var.set_allow_ota(config[CONF_OTA])) + cg.add(var.set_expose_log(config[CONF_LOG])) if CONF_AUTH in config: cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME])) cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index b3a2dfdb66..eb1858a09c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -102,6 +102,16 @@ void WebServer::set_css_include(const char *css_include) { this->css_include_ = void WebServer::set_js_include(const char *js_include) { this->js_include_ = js_include; } #endif +std::string WebServer::get_config_json() { + return json::build_json([this](JsonObject root) { + root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); + root["comment"] = App.get_comment(); + root["ota"] = this->allow_ota_; + root["log"] = this->expose_log_; + root["lang"] = "en"; + }); +} + void WebServer::setup() { ESP_LOGCONFIG(TAG, "Setting up web server..."); this->setup_controller(this->include_internal_); @@ -109,20 +119,13 @@ void WebServer::setup() { this->events_.onConnect([this](AsyncEventSourceClient *client) { // Configure reconnect timeout and send config - - client->send(json::build_json([this](JsonObject root) { - root["title"] = App.get_friendly_name().empty() ? App.get_name() : App.get_friendly_name(); - root["comment"] = App.get_comment(); - root["ota"] = this->allow_ota_; - root["lang"] = "en"; - }).c_str(), - "ping", millis(), 30000); + client->send(this->get_config_json().c_str(), "ping", millis(), 30000); this->entities_iterator_.begin(this->include_internal_); }); #ifdef USE_LOGGER - if (logger::global_logger != nullptr) { + if (logger::global_logger != nullptr && this->expose_log_) { logger::global_logger->add_on_log_callback( [this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); }); } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index a664bb5a12..83ebba205f 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -99,6 +99,11 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { * @param allow_ota. */ void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; } + /** Set whether or not the webserver should expose the Log. + * + * @param expose_log. + */ + void set_expose_log(bool expose_log) { this->expose_log_ = expose_log; } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) @@ -114,6 +119,9 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle an index request under '/'. void handle_index_request(AsyncWebServerRequest *request); + /// Return the webserver configuration as JSON. + std::string get_config_json(); + #ifdef USE_WEBSERVER_CSS_INCLUDE /// Handle included css request under '/0.css'. void handle_css_request(AsyncWebServerRequest *request); @@ -274,6 +282,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif bool include_internal_{false}; bool allow_ota_{true}; + bool expose_log_{true}; #ifdef USE_ESP32 std::deque> to_schedule_; SemaphoreHandle_t to_schedule_lock_; diff --git a/esphome/const.py b/esphome/const.py index f07eb49b5a..c8b85fcdeb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -368,6 +368,7 @@ CONF_LINE_TYPE = "line_type" CONF_LOADED_INTEGRATIONS = "loaded_integrations" CONF_LOCAL = "local" CONF_LOCK_ACTION = "lock_action" +CONF_LOG = "log" CONF_LOG_TOPIC = "log_topic" CONF_LOGGER = "logger" CONF_LOGS = "logs" From 211453df43576b109d14665fe5cd55dee9e805b6 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:12:25 +1200 Subject: [PATCH 032/106] Update webserver and captive portal pages to 67c48ee9 (#4986) --- .../components/captive_portal/captive_index.h | 190 ++- esphome/components/web_server/server_index.h | 1169 +++++++++-------- 2 files changed, 684 insertions(+), 675 deletions(-) diff --git a/esphome/components/captive_portal/captive_index.h b/esphome/components/captive_portal/captive_index.h index bf2e6e6e8b..56071f3d2a 100644 --- a/esphome/components/captive_portal/captive_index.h +++ b/esphome/components/captive_portal/captive_index.h @@ -6,102 +6,100 @@ namespace esphome { namespace captive_portal { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xdd, 0x58, 0x09, 0x6f, 0xdc, 0x36, 0x16, 0xfe, 0x2b, - 0xac, 0x92, 0x74, 0x34, 0x8d, 0xc5, 0xd1, 0x31, 0x97, 0x35, 0xd2, 0x14, 0x89, 0x37, 0x45, 0x0b, 0x24, 0x69, 0x00, - 0xbb, 0x5d, 0x14, 0x69, 0x00, 0x73, 0x24, 0x6a, 0xc4, 0x58, 0xa2, 0x54, 0x91, 0x9a, 0x23, 0x83, 0xd9, 0xdf, 0xde, - 0x47, 0x52, 0x73, 0x38, 0x6b, 0x2f, 0x90, 0x62, 0x8b, 0xa2, 0x4d, 0x6c, 0x9a, 0xc7, 0x3b, 0x3f, 0xf2, 0xf1, 0x3d, - 0x2a, 0xfa, 0x2a, 0xad, 0x12, 0xb9, 0xad, 0x29, 0xca, 0x65, 0x59, 0xcc, 0x23, 0xd5, 0xa2, 0x82, 0xf0, 0x65, 0x4c, - 0x39, 0x8c, 0x28, 0x49, 0xe7, 0x51, 0x49, 0x25, 0x41, 0x49, 0x4e, 0x1a, 0x41, 0x65, 0xfc, 0xd3, 0xcd, 0x77, 0xce, - 0x14, 0x0d, 0xe6, 0x51, 0xc1, 0xf8, 0x1d, 0x6a, 0x68, 0x11, 0xb3, 0xa4, 0xe2, 0x28, 0x6f, 0x68, 0x16, 0xa7, 0x44, - 0x92, 0x90, 0x95, 0x64, 0x49, 0x15, 0x81, 0x66, 0xe3, 0xa4, 0xa4, 0xf1, 0x8a, 0xd1, 0x75, 0x5d, 0x35, 0x12, 0x01, - 0xa5, 0xa4, 0x5c, 0xc6, 0xd6, 0x9a, 0xa5, 0x32, 0x8f, 0x53, 0xba, 0x62, 0x09, 0x75, 0xf4, 0xe0, 0x82, 0x71, 0x26, - 0x19, 0x29, 0x1c, 0x91, 0x90, 0x82, 0xc6, 0xde, 0x45, 0x2b, 0x68, 0xa3, 0x07, 0x64, 0x01, 0x63, 0x5e, 0x59, 0x20, - 0x52, 0x24, 0x0d, 0xab, 0x25, 0x52, 0xf6, 0xc6, 0x65, 0x95, 0xb6, 0x05, 0x9d, 0x67, 0x2d, 0x4f, 0x24, 0x03, 0x0b, - 0x84, 0xcd, 0xfb, 0xbb, 0x82, 0x4a, 0x44, 0xe3, 0x37, 0x44, 0xe6, 0xb8, 0x24, 0x1b, 0xdb, 0x74, 0x18, 0xb7, 0xfd, - 0x6f, 0x6c, 0xfe, 0xdc, 0x73, 0xdd, 0xfe, 0x85, 0x6e, 0xdc, 0xfe, 0x00, 0xfe, 0xce, 0x1a, 0x2a, 0xdb, 0x86, 0x23, - 0x62, 0xdf, 0x46, 0x35, 0x50, 0xa2, 0x34, 0xb6, 0x4a, 0xcf, 0xc7, 0xae, 0x3b, 0x45, 0xde, 0x25, 0xf6, 0x47, 0x8e, - 0xe7, 0xe1, 0xc0, 0xf1, 0x46, 0xc9, 0xc4, 0x19, 0x21, 0x6f, 0x08, 0x8d, 0xef, 0xe3, 0x11, 0x72, 0x3f, 0x59, 0x28, - 0x63, 0x45, 0x11, 0x5b, 0xbc, 0xe2, 0xd4, 0x42, 0x42, 0x36, 0xd5, 0x1d, 0x8d, 0xad, 0xa4, 0x6d, 0x1a, 0xf0, 0xee, - 0xaa, 0x2a, 0xaa, 0x06, 0xac, 0xfd, 0x95, 0xa3, 0x7b, 0xff, 0xbe, 0x58, 0x87, 0x6c, 0x08, 0x17, 0x59, 0xd5, 0x94, - 0xb1, 0xa5, 0x41, 0xb1, 0x9f, 0xee, 0xe8, 0x1e, 0xa9, 0xa6, 0x7f, 0xb6, 0xe8, 0x54, 0x0d, 0x5b, 0x32, 0x1e, 0x5b, - 0x9e, 0x8f, 0xbc, 0x29, 0xe8, 0xbd, 0xed, 0xef, 0x8f, 0xa0, 0x10, 0x05, 0x4a, 0xe7, 0x66, 0x65, 0xbf, 0xbf, 0x8d, - 0xc4, 0x6a, 0x89, 0x36, 0x65, 0xc1, 0x45, 0x6c, 0xe5, 0x52, 0xd6, 0xe1, 0x60, 0xb0, 0x5e, 0xaf, 0xf1, 0x3a, 0xc0, - 0x55, 0xb3, 0x1c, 0xf8, 0xae, 0xeb, 0x0e, 0x80, 0xc2, 0x42, 0x66, 0x7f, 0x2c, 0x7f, 0x68, 0xa1, 0x9c, 0xb2, 0x65, - 0x2e, 0x75, 0x7f, 0xfe, 0x74, 0xc7, 0xf7, 0x91, 0xa2, 0x98, 0xdf, 0x7e, 0x38, 0xd3, 0xd2, 0x9c, 0x69, 0xe1, 0xdf, - 0x12, 0xdb, 0x3a, 0xb8, 0xda, 0x7b, 0xa3, 0x8c, 0x9a, 0x10, 0x1f, 0xf9, 0xc8, 0xd5, 0xff, 0x7d, 0x47, 0xf5, 0xbb, - 0x91, 0xf3, 0xd9, 0x08, 0x9d, 0x8d, 0xe0, 0xaf, 0x02, 0xd0, 0x2f, 0xc7, 0xce, 0xe5, 0x91, 0xdf, 0x53, 0xeb, 0x2b, - 0xcf, 0x3d, 0x4d, 0x28, 0xa6, 0xef, 0xc7, 0xe7, 0x63, 0xc7, 0xff, 0x59, 0x11, 0x68, 0xf4, 0x8f, 0x5c, 0x8e, 0x9f, - 0x7b, 0x3f, 0x8f, 0xc9, 0x08, 0x8d, 0xba, 0x99, 0x91, 0xa3, 0xfa, 0xc7, 0x91, 0xd6, 0x85, 0x46, 0x2b, 0x20, 0x2b, - 0x9d, 0xb1, 0x33, 0x22, 0x01, 0x0a, 0x3a, 0xab, 0xa0, 0x07, 0xd3, 0x63, 0xe0, 0x3e, 0x9b, 0x73, 0x82, 0x4f, 0xbd, - 0xc1, 0xdc, 0xea, 0x87, 0x96, 0x75, 0x82, 0xa1, 0x3a, 0x87, 0x01, 0x7f, 0xac, 0xe0, 0xdc, 0x59, 0x56, 0x7f, 0x6f, - 0x7d, 0x2b, 0xc8, 0x8a, 0x5a, 0x71, 0x1c, 0x43, 0xa8, 0xb5, 0x25, 0x9c, 0x10, 0x5c, 0x54, 0x09, 0x51, 0x2c, 0x58, - 0x50, 0xd2, 0x24, 0xf9, 0xd7, 0x5f, 0xdb, 0xc7, 0xa5, 0x25, 0x95, 0xaf, 0x0a, 0xaa, 0xba, 0xe2, 0xe5, 0xf6, 0x86, - 0x2c, 0xdf, 0x42, 0x00, 0xd9, 0x16, 0x11, 0x2c, 0xa5, 0x56, 0xff, 0xbd, 0xfb, 0x01, 0x0b, 0xb9, 0x2d, 0x28, 0x4e, - 0x99, 0xa8, 0x0b, 0xb2, 0x8d, 0xad, 0x05, 0xc8, 0xba, 0xb3, 0xfa, 0x17, 0x19, 0x95, 0x49, 0x6e, 0x5b, 0x03, 0x08, - 0xb1, 0x8c, 0x2d, 0xf1, 0x47, 0x51, 0x71, 0xab, 0x8f, 0x65, 0x4e, 0xb9, 0x6d, 0x1f, 0x2c, 0x54, 0xf6, 0x71, 0xbd, - 0x64, 0x3f, 0xb4, 0x74, 0xb4, 0x41, 0x32, 0xa9, 0x42, 0x0e, 0xab, 0xe0, 0xbd, 0x38, 0xce, 0x2e, 0xaa, 0x74, 0xfb, - 0x88, 0x79, 0xb9, 0x67, 0x6c, 0x63, 0x9c, 0xd3, 0xe6, 0x86, 0x6e, 0xe0, 0xb8, 0xfc, 0x9b, 0x7d, 0xc7, 0xd0, 0x5b, - 0x2a, 0xd7, 0x55, 0x73, 0x27, 0x42, 0x64, 0x3d, 0x37, 0xe2, 0x66, 0x26, 0x42, 0x39, 0x26, 0xb5, 0xc0, 0xa2, 0x80, - 0xf0, 0xb7, 0xbd, 0x3e, 0xc4, 0x6a, 0x7d, 0xdf, 0x14, 0x83, 0xe2, 0x6d, 0x94, 0xb2, 0x15, 0x4a, 0x0a, 0x22, 0xe0, - 0xb8, 0x72, 0x23, 0xcb, 0x42, 0x87, 0xb8, 0xaa, 0x78, 0x02, 0xfc, 0x77, 0xb1, 0xf5, 0x00, 0x76, 0x2f, 0xb7, 0x3f, - 0xa4, 0x76, 0x4f, 0x00, 0x6a, 0xbd, 0x3e, 0x5e, 0x91, 0xa2, 0xa5, 0x28, 0x46, 0x32, 0x67, 0xe2, 0x64, 0xe2, 0xec, - 0x51, 0xb6, 0x5a, 0xdc, 0x01, 0x57, 0x06, 0xcb, 0xc2, 0xee, 0x5b, 0xc7, 0x38, 0x8e, 0x88, 0xb9, 0xe5, 0xac, 0x27, - 0xd6, 0x67, 0x36, 0x39, 0x05, 0xcd, 0xa4, 0x75, 0x16, 0xf0, 0x4f, 0x77, 0x70, 0x1b, 0xe1, 0x06, 0xf4, 0xf7, 0xf7, - 0xa7, 0xd9, 0x48, 0xd4, 0x84, 0x7f, 0xce, 0xaa, 0x6c, 0xd4, 0x81, 0x85, 0x55, 0x4f, 0x45, 0x17, 0x10, 0x9d, 0x74, - 0x0e, 0xc8, 0xb1, 0xff, 0x74, 0x07, 0x71, 0xa6, 0x8e, 0xce, 0xdd, 0x49, 0x68, 0x34, 0x00, 0x84, 0xe6, 0xb7, 0xfb, - 0x7e, 0xff, 0xe4, 0xce, 0x6f, 0x2d, 0x6d, 0xb6, 0xd7, 0xb4, 0xa0, 0x89, 0xac, 0x1a, 0xdb, 0x7a, 0x02, 0x9a, 0xe0, - 0x24, 0x68, 0xbf, 0xbf, 0xbf, 0x79, 0xf3, 0x3a, 0xae, 0x6c, 0xda, 0xbf, 0x78, 0x8c, 0x5a, 0xdd, 0xea, 0xef, 0xe1, - 0x56, 0xff, 0x4f, 0xdc, 0x53, 0xf7, 0x7a, 0xef, 0x03, 0xb0, 0x1a, 0xaf, 0x4f, 0x97, 0xbb, 0xba, 0x00, 0x9e, 0xc3, - 0x25, 0x72, 0x61, 0x3d, 0x17, 0xb6, 0x33, 0x1e, 0xf5, 0x41, 0x3d, 0xfc, 0x80, 0xe9, 0xfa, 0x7a, 0x86, 0x6b, 0x5a, - 0x1d, 0xd1, 0xf9, 0x37, 0xbb, 0x45, 0xb5, 0x71, 0x04, 0xfb, 0xc4, 0xf8, 0x32, 0x64, 0x3c, 0xa7, 0x0d, 0x93, 0x7b, - 0x30, 0x17, 0x6e, 0xfa, 0xba, 0x95, 0xbb, 0x9a, 0xa4, 0xa9, 0x5a, 0x19, 0xd5, 0x9b, 0x59, 0x06, 0x79, 0x41, 0x51, - 0xd2, 0xd0, 0xa3, 0xe5, 0xde, 0xac, 0xeb, 0x2b, 0x28, 0xbc, 0x1c, 0x3d, 0xdb, 0xab, 0x83, 0xb7, 0x93, 0xb0, 0x65, - 0x0e, 0x29, 0xd8, 0x92, 0x87, 0x09, 0xd8, 0x4d, 0x1b, 0xc3, 0x94, 0x91, 0x92, 0x15, 0xdb, 0x50, 0xc0, 0x65, 0xe8, - 0x40, 0xc2, 0x60, 0xd9, 0x7e, 0xd1, 0x4a, 0x59, 0x71, 0xd0, 0xdd, 0xa4, 0xb4, 0x09, 0xdd, 0x99, 0xe9, 0x38, 0x0d, - 0x49, 0x59, 0x2b, 0x42, 0x1c, 0x34, 0xb4, 0x9c, 0x2d, 0x48, 0x72, 0xb7, 0x6c, 0xaa, 0x96, 0xa7, 0x4e, 0xa2, 0x6e, - 0xeb, 0xf0, 0x89, 0x97, 0x91, 0x80, 0x26, 0xb3, 0x6e, 0x94, 0x65, 0xd9, 0x0c, 0x90, 0xa0, 0x8e, 0xb9, 0xfc, 0x42, - 0x1f, 0x0f, 0x15, 0xdb, 0x99, 0x99, 0xd8, 0x57, 0x13, 0xc6, 0x46, 0x48, 0x25, 0xcf, 0x66, 0x07, 0x77, 0xdc, 0x19, - 0xa4, 0x01, 0x01, 0x42, 0x6a, 0x88, 0x7f, 0x30, 0x73, 0x5f, 0x12, 0xc6, 0xcf, 0xad, 0x57, 0x67, 0x65, 0xd6, 0x85, - 0x2f, 0xc0, 0xa2, 0xd5, 0xe8, 0x20, 0x9e, 0x41, 0xa2, 0x32, 0xb9, 0x30, 0xf4, 0xc7, 0x6e, 0xbd, 0xd9, 0xe3, 0xee, - 0x8c, 0xec, 0x0e, 0xd4, 0x59, 0x41, 0x37, 0xb3, 0x8f, 0xad, 0x90, 0x2c, 0xdb, 0x3a, 0x5d, 0x2e, 0x0d, 0xe1, 0xbc, - 0x40, 0x0e, 0x5d, 0x00, 0x29, 0xa5, 0x7c, 0xa6, 0x75, 0x38, 0x4c, 0xd2, 0x52, 0x74, 0x38, 0x1d, 0xc5, 0xe8, 0x53, - 0x7a, 0x5f, 0xd6, 0xff, 0xa2, 0x56, 0xc7, 0x71, 0x57, 0x92, 0x06, 0x72, 0x8b, 0xb3, 0xa8, 0x00, 0xd3, 0x32, 0x74, - 0x26, 0xb0, 0x57, 0xdd, 0x94, 0x12, 0x06, 0x9e, 0x83, 0x99, 0xfa, 0x6e, 0x3a, 0xe0, 0xed, 0xd5, 0x1b, 0x24, 0xaa, - 0x82, 0xa5, 0x1d, 0x9d, 0x26, 0x41, 0xee, 0x11, 0x1e, 0x0f, 0xb6, 0x1b, 0xa9, 0xb9, 0x03, 0xd4, 0xc3, 0x6c, 0x4a, - 0x3c, 0xf7, 0x81, 0x1d, 0x49, 0xb3, 0xcc, 0x5f, 0x64, 0x47, 0xa4, 0x54, 0xaa, 0xdd, 0xb3, 0xee, 0x54, 0xf8, 0x43, - 0x10, 0x70, 0xd8, 0x1b, 0xe8, 0xef, 0x99, 0x8e, 0x8b, 0xdd, 0x99, 0x14, 0x7d, 0x52, 0xc3, 0xb6, 0x29, 0xec, 0x87, - 0x4e, 0xee, 0xb3, 0xe0, 0xea, 0x94, 0x09, 0x7b, 0x8f, 0x67, 0xc2, 0x1e, 0x52, 0xb5, 0xcb, 0xcb, 0x6a, 0x13, 0xf7, - 0x74, 0x4e, 0x1a, 0xc2, 0x4f, 0xef, 0x59, 0xf0, 0x0a, 0xf8, 0xff, 0x2f, 0x29, 0xee, 0x0f, 0xa7, 0xb7, 0x2f, 0x48, - 0x6d, 0x5f, 0x98, 0xd5, 0x8c, 0x77, 0xca, 0x79, 0xe8, 0x41, 0xfa, 0x62, 0x58, 0xb0, 0xa5, 0xf7, 0x67, 0x40, 0xfb, - 0xdf, 0x38, 0x06, 0x2f, 0xbc, 0x29, 0xbe, 0x44, 0xba, 0x31, 0x10, 0xe1, 0x60, 0x8a, 0x26, 0x57, 0x43, 0x3c, 0xf4, - 0x90, 0xaa, 0x9a, 0xc6, 0x68, 0x82, 0xa7, 0x40, 0x30, 0xc6, 0xc1, 0x04, 0x26, 0x90, 0xef, 0xe1, 0xd1, 0x6b, 0x3f, - 0xc0, 0xe3, 0x11, 0x50, 0xf9, 0x2e, 0x0e, 0x7c, 0x64, 0x68, 0xc7, 0xd8, 0x07, 0x71, 0x8a, 0x24, 0x28, 0x01, 0xe8, - 0x24, 0xc0, 0xee, 0x04, 0xc4, 0x8d, 0xb1, 0x7b, 0x89, 0xa7, 0x63, 0x34, 0xc5, 0x13, 0x80, 0x0e, 0x0f, 0x47, 0x85, - 0x33, 0xc2, 0x1e, 0x4c, 0x07, 0x63, 0x32, 0xc5, 0xc3, 0x00, 0xe9, 0xc6, 0xc0, 0x31, 0x01, 0x11, 0x0e, 0x76, 0xbd, - 0xd7, 0x01, 0xf6, 0x27, 0xa0, 0x77, 0x38, 0x7c, 0x01, 0x62, 0x2f, 0x87, 0xc8, 0xb4, 0x06, 0x5e, 0x50, 0x30, 0x7a, - 0x0c, 0x34, 0xff, 0x9f, 0x0b, 0x1a, 0x40, 0xe2, 0xa1, 0x00, 0x5f, 0x42, 0xec, 0x7a, 0x8a, 0xdf, 0xb4, 0x06, 0x37, - 0xcf, 0x43, 0xee, 0x1f, 0xc6, 0x2c, 0xf8, 0xe7, 0x62, 0xe6, 0x29, 0x04, 0xa0, 0x0b, 0xba, 0x41, 0x0e, 0xd2, 0x8d, - 0xd1, 0x0d, 0xcc, 0xd3, 0xab, 0x4b, 0x34, 0x05, 0xae, 0xf1, 0x14, 0x5d, 0xa2, 0x91, 0x42, 0x17, 0xd8, 0x87, 0x86, - 0xc9, 0x01, 0xa6, 0x2f, 0x84, 0x71, 0xf8, 0x37, 0x86, 0xf1, 0x31, 0x9f, 0xfe, 0xc6, 0x2e, 0xfd, 0x15, 0x57, 0x10, - 0x94, 0x63, 0xba, 0x0c, 0x8b, 0x06, 0xe6, 0x15, 0xaf, 0xaa, 0x28, 0x78, 0x94, 0x43, 0x35, 0x02, 0xef, 0x7a, 0x0f, - 0xb1, 0x34, 0xce, 0xbd, 0xf9, 0xbd, 0x2a, 0x1d, 0x28, 0xbd, 0x79, 0xa4, 0xd3, 0xf9, 0xfc, 0x26, 0xa7, 0xe8, 0xd5, - 0xf5, 0x3b, 0x78, 0x08, 0x16, 0x05, 0xe2, 0xd5, 0x1a, 0xde, 0x9b, 0x5b, 0x24, 0x2b, 0xf5, 0x82, 0xe7, 0x50, 0x2a, - 0xaa, 0x2e, 0x3c, 0x20, 0x50, 0x57, 0x2c, 0x60, 0x8c, 0xa3, 0x45, 0x33, 0x7f, 0x57, 0x50, 0x22, 0x28, 0x5a, 0xb2, - 0x15, 0x45, 0x4c, 0x42, 0x1d, 0x50, 0x52, 0x24, 0x99, 0x6a, 0x8e, 0x8c, 0x9a, 0xee, 0x6d, 0x25, 0x69, 0x88, 0xae, - 0xaa, 0x7a, 0xab, 0x85, 0x24, 0x39, 0xe1, 0x4b, 0x9a, 0x1e, 0x84, 0x29, 0xea, 0x6d, 0xd5, 0x36, 0xe8, 0x97, 0x17, - 0x6f, 0x5e, 0xab, 0x87, 0x36, 0x45, 0x4e, 0xa7, 0x6c, 0x23, 0xd1, 0x8f, 0x37, 0x2f, 0x50, 0x5b, 0xc3, 0xa6, 0x53, - 0x63, 0x5b, 0xb5, 0xa2, 0xcd, 0x1a, 0x2a, 0x4b, 0xaa, 0x48, 0x40, 0xb9, 0xa0, 0x52, 0x42, 0xa1, 0x21, 0x30, 0x94, - 0xce, 0xda, 0x13, 0x53, 0x75, 0x83, 0xbb, 0x20, 0x7e, 0xde, 0x95, 0xd7, 0x51, 0x1e, 0x18, 0xd7, 0xaf, 0x3b, 0x6a, - 0x70, 0x3d, 0x98, 0x47, 0xea, 0x39, 0x8d, 0x88, 0x7e, 0x84, 0xc4, 0x83, 0x35, 0xcb, 0x98, 0x7a, 0xb8, 0xcd, 0x23, - 0x5d, 0x8f, 0x2a, 0x09, 0xaa, 0x24, 0x32, 0x5f, 0x34, 0x74, 0xaf, 0xa0, 0x7c, 0x09, 0xaf, 0x64, 0xd8, 0x70, 0xa8, - 0x50, 0x12, 0x9a, 0x57, 0x05, 0x54, 0x40, 0xf1, 0xf5, 0xf5, 0x0f, 0xff, 0x52, 0x9f, 0x3f, 0xc0, 0xcf, 0x13, 0x27, - 0x3c, 0x29, 0x0c, 0xa3, 0xea, 0x74, 0x7c, 0xe3, 0xa1, 0xf9, 0x90, 0x51, 0xc3, 0x7b, 0x00, 0xfc, 0x4e, 0xef, 0x49, - 0x79, 0x77, 0x98, 0xec, 0x24, 0xe9, 0x5f, 0x5d, 0xd9, 0x1a, 0x26, 0xd1, 0x2e, 0x4a, 0x26, 0xe7, 0xd7, 0x60, 0x60, - 0x34, 0x30, 0x0b, 0xe0, 0x9c, 0x72, 0xc0, 0xd0, 0xe6, 0x1d, 0x0f, 0xec, 0xa8, 0x42, 0xec, 0x27, 0x8d, 0x98, 0xd9, - 0x60, 0xed, 0x65, 0x49, 0x65, 0x5e, 0xa5, 0xf1, 0xbb, 0x1f, 0xaf, 0x6f, 0x8e, 0x1e, 0x77, 0xb0, 0x52, 0x9e, 0x98, - 0x0f, 0x2c, 0x6d, 0x21, 0x59, 0x4d, 0x1a, 0xa9, 0xc5, 0x3a, 0x2a, 0xce, 0x0e, 0x1e, 0xe9, 0x75, 0xbd, 0x33, 0xda, - 0xa9, 0x8e, 0x71, 0x30, 0x47, 0x0f, 0xd9, 0x78, 0xd0, 0xfd, 0x99, 0x95, 0x03, 0x73, 0x14, 0x07, 0xe6, 0x5c, 0x0e, - 0xf4, 0xe7, 0xa7, 0xdf, 0x01, 0xf1, 0x69, 0xfc, 0xac, 0x8e, 0x12, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x58, 0x5b, 0x8f, 0xdb, 0x36, 0x16, 0x7e, 0xef, + 0xaf, 0xe0, 0x2a, 0x49, 0x2d, 0x37, 0x23, 0xea, 0x66, 0xf9, 0x2a, 0xa9, 0x48, 0xb2, 0x29, 0x5a, 0x20, 0x69, 0x03, + 0xcc, 0xb4, 0xfb, 0x10, 0x04, 0x18, 0x5a, 0xa2, 0x2c, 0x66, 0x24, 0x4a, 0x15, 0xe9, 0x5b, 0x0c, 0xef, 0x6f, 0xdf, + 0x43, 0x52, 0xf6, 0x38, 0xb3, 0x99, 0x05, 0x52, 0xec, 0x62, 0xd1, 0x4e, 0x26, 0x1c, 0x92, 0x3a, 0xd7, 0x4f, 0x3c, + 0x17, 0x2a, 0xfe, 0x5b, 0xde, 0x64, 0x72, 0xdf, 0x52, 0x54, 0xca, 0xba, 0x4a, 0x63, 0x35, 0xa2, 0x8a, 0xf0, 0x55, + 0x42, 0x39, 0xac, 0x28, 0xc9, 0xd3, 0xb8, 0xa6, 0x92, 0xa0, 0xac, 0x24, 0x9d, 0xa0, 0x32, 0xf9, 0xf5, 0xe6, 0x07, + 0x67, 0x8a, 0xdc, 0x34, 0xae, 0x18, 0xbf, 0x43, 0x1d, 0xad, 0x12, 0x96, 0x35, 0x1c, 0x95, 0x1d, 0x2d, 0x92, 0x9c, + 0x48, 0x32, 0x67, 0x35, 0x59, 0x51, 0x45, 0xa0, 0xd9, 0x38, 0xa9, 0x69, 0xb2, 0x61, 0x74, 0xdb, 0x36, 0x9d, 0x44, + 0x40, 0x29, 0x29, 0x97, 0x89, 0xb5, 0x65, 0xb9, 0x2c, 0x93, 0x9c, 0x6e, 0x58, 0x46, 0x1d, 0xbd, 0xb8, 0x62, 0x9c, + 0x49, 0x46, 0x2a, 0x47, 0x64, 0xa4, 0xa2, 0x89, 0x7f, 0xb5, 0x16, 0xb4, 0xd3, 0x0b, 0xb2, 0x84, 0x35, 0x6f, 0x2c, + 0x10, 0x29, 0xb2, 0x8e, 0xb5, 0x12, 0x29, 0x7b, 0x93, 0xba, 0xc9, 0xd7, 0x15, 0x4d, 0x5d, 0x97, 0x08, 0xb0, 0x4b, + 0xb8, 0x8c, 0xe7, 0x74, 0x87, 0xa7, 0xb3, 0x68, 0x32, 0x9e, 0xe6, 0x13, 0xfc, 0x51, 0x7c, 0x03, 0x9e, 0xad, 0x6b, + 0x50, 0x87, 0xab, 0x26, 0x23, 0x92, 0x35, 0x1c, 0x0b, 0x4a, 0xba, 0xac, 0x4c, 0x92, 0xc4, 0xfa, 0x5e, 0x90, 0x0d, + 0xb5, 0xbe, 0xfd, 0xd6, 0x3e, 0x13, 0xad, 0xa8, 0x7c, 0x5d, 0x51, 0x35, 0x15, 0x2f, 0xf7, 0x37, 0x64, 0xf5, 0x33, + 0x58, 0x6e, 0x5b, 0x44, 0xb0, 0x9c, 0x5a, 0xc3, 0xf7, 0xde, 0x07, 0x2c, 0xe4, 0xbe, 0xa2, 0x38, 0x67, 0xa2, 0xad, + 0xc8, 0x3e, 0xb1, 0x96, 0x20, 0xf5, 0xce, 0x1a, 0x2e, 0x8a, 0x35, 0xcf, 0x94, 0x70, 0x24, 0x6c, 0x3a, 0x3c, 0x54, + 0x14, 0xcc, 0x4b, 0xde, 0x12, 0x59, 0xe2, 0x9a, 0xec, 0x6c, 0x33, 0x61, 0xdc, 0x0e, 0xbe, 0xb3, 0xe9, 0x73, 0xdf, + 0xf3, 0x86, 0x57, 0x7a, 0xf0, 0x86, 0x2e, 0xfc, 0x5d, 0x74, 0x54, 0xae, 0x3b, 0x8e, 0x88, 0x7d, 0x1b, 0xb7, 0x40, + 0x89, 0xf2, 0xc4, 0xaa, 0xfd, 0x00, 0x7b, 0xde, 0x14, 0xf9, 0x33, 0x1c, 0x44, 0x8e, 0xef, 0xe3, 0xd0, 0xf1, 0xa3, + 0x6c, 0xe2, 0x44, 0xc8, 0x1f, 0xc1, 0x10, 0x04, 0x38, 0x42, 0xde, 0x27, 0x0b, 0x15, 0xac, 0xaa, 0x12, 0x8b, 0x37, + 0x9c, 0x5a, 0x48, 0xc8, 0xae, 0xb9, 0xa3, 0x89, 0x95, 0xad, 0xbb, 0x0e, 0xec, 0x7f, 0xd5, 0x54, 0x4d, 0x07, 0x70, + 0x7d, 0x83, 0x3e, 0xfb, 0xf9, 0x6a, 0x15, 0xb2, 0x23, 0x5c, 0x14, 0x4d, 0x57, 0x27, 0x96, 0x7e, 0x29, 0xf6, 0xd3, + 0x83, 0x3c, 0x22, 0x35, 0x0c, 0x2f, 0x1e, 0x3a, 0x4d, 0xc7, 0x56, 0x8c, 0x27, 0x96, 0x1f, 0x20, 0x7f, 0x0a, 0x6a, + 0x6f, 0x87, 0xc7, 0x33, 0x26, 0x44, 0x61, 0xd2, 0x7b, 0xd9, 0xd8, 0xef, 0x6f, 0x63, 0xb1, 0x59, 0xa1, 0x5d, 0x5d, + 0x71, 0x91, 0x58, 0xa5, 0x94, 0xed, 0xdc, 0x75, 0xb7, 0xdb, 0x2d, 0xde, 0x86, 0xb8, 0xe9, 0x56, 0x6e, 0xe0, 0x79, + 0x9e, 0x0b, 0x14, 0x16, 0x32, 0xe7, 0xc3, 0x0a, 0x46, 0x16, 0x2a, 0x29, 0x5b, 0x95, 0x52, 0xcf, 0xd3, 0xa7, 0x07, + 0x7a, 0x8c, 0x15, 0x45, 0x7a, 0xfb, 0xe1, 0x42, 0x4b, 0x77, 0xa1, 0x85, 0x7e, 0x7f, 0x81, 0xe6, 0xe0, 0xad, 0x32, + 0x6a, 0x42, 0x02, 0x14, 0x20, 0x4f, 0xff, 0x0b, 0x1c, 0x35, 0xef, 0x57, 0xce, 0x83, 0x15, 0xba, 0x58, 0xc1, 0x5f, + 0xc0, 0x2f, 0xa8, 0xc7, 0xce, 0xec, 0xcc, 0xee, 0xab, 0xc7, 0x1b, 0xdf, 0xbb, 0xdf, 0x50, 0x3c, 0x3f, 0x8e, 0x2f, + 0xd7, 0x4e, 0xf0, 0x9b, 0x22, 0x50, 0xd8, 0x9f, 0x99, 0x9c, 0xa0, 0xf4, 0x7f, 0x1b, 0x93, 0x08, 0x45, 0xfd, 0x4e, + 0xe4, 0xa8, 0xf9, 0x79, 0xa5, 0x34, 0xa1, 0x68, 0x03, 0x54, 0xb5, 0x33, 0x76, 0x22, 0x12, 0xa2, 0xb0, 0x37, 0x09, + 0x66, 0xb0, 0x3d, 0x06, 0xe6, 0x8b, 0x3d, 0x27, 0xfc, 0x34, 0x50, 0x30, 0xcf, 0x2d, 0xeb, 0x1e, 0x83, 0xe6, 0x12, + 0x03, 0xfc, 0xb1, 0x81, 0x33, 0x67, 0x59, 0x80, 0x11, 0x95, 0x59, 0x69, 0x5b, 0x2e, 0x44, 0x5e, 0xc1, 0x56, 0x10, + 0x15, 0x0d, 0xb7, 0x86, 0x58, 0x96, 0x94, 0xdb, 0x27, 0x56, 0xc5, 0x48, 0xf5, 0x13, 0xfb, 0xe1, 0x13, 0x39, 0x3c, + 0x9c, 0xe3, 0x43, 0x32, 0x09, 0x71, 0x28, 0xb1, 0x8a, 0xe8, 0xab, 0xf3, 0xee, 0xb2, 0xc9, 0xf7, 0x8f, 0x84, 0x4e, + 0xe9, 0x9b, 0xb8, 0x61, 0x9c, 0xd3, 0xee, 0x86, 0xee, 0xe0, 0x1d, 0xfe, 0x83, 0xfd, 0xc0, 0xd0, 0xcf, 0x54, 0x6e, + 0x9b, 0xee, 0x4e, 0xcc, 0x91, 0xf5, 0xdc, 0x88, 0x5b, 0xa8, 0xa8, 0x61, 0x20, 0x9b, 0xb4, 0x02, 0x8b, 0x0a, 0x72, + 0x82, 0xed, 0x0f, 0x21, 0x7e, 0xda, 0x7b, 0x4b, 0xf8, 0xc9, 0xb9, 0xdb, 0x38, 0x67, 0x1b, 0x94, 0x55, 0x10, 0xf5, + 0x70, 0xfc, 0x8d, 0x28, 0x0b, 0xf5, 0x47, 0xbd, 0xe1, 0x19, 0x70, 0xdf, 0x25, 0xd6, 0x17, 0xa2, 0xfa, 0xe5, 0xfe, + 0xa7, 0xdc, 0x1e, 0x08, 0x88, 0xe7, 0xc1, 0x10, 0x6f, 0x48, 0xb5, 0xa6, 0x28, 0x41, 0xb2, 0x64, 0xe2, 0xde, 0xc0, + 0xc5, 0xa3, 0x6c, 0xad, 0xb8, 0x03, 0xae, 0x02, 0x1e, 0x0b, 0x7b, 0x68, 0x9d, 0x22, 0x2b, 0x26, 0x26, 0xef, 0x59, + 0x4f, 0xac, 0x07, 0x16, 0x39, 0x15, 0x2d, 0xa4, 0x75, 0x1f, 0x81, 0x4f, 0x0f, 0xc2, 0xe6, 0xb8, 0x03, 0xed, 0xc3, + 0xe3, 0x79, 0x33, 0x16, 0x2d, 0xe1, 0x0f, 0x19, 0x95, 0x81, 0xea, 0xa0, 0x43, 0xb2, 0x82, 0x99, 0x3a, 0xed, 0x40, + 0x74, 0x56, 0xe8, 0x92, 0xd3, 0xf4, 0xe9, 0xa1, 0x03, 0x89, 0x2a, 0x07, 0x9d, 0x25, 0xc6, 0x2e, 0x40, 0x93, 0xde, + 0x1e, 0x87, 0xf7, 0x7e, 0xfc, 0xbe, 0xa6, 0xdd, 0xfe, 0x9a, 0x56, 0x34, 0x93, 0x4d, 0x67, 0x5b, 0x4f, 0x40, 0x0b, + 0xbc, 0x7e, 0xed, 0xf0, 0x8f, 0x37, 0x6f, 0xdf, 0x24, 0x8d, 0xcd, 0x86, 0x57, 0x8f, 0x51, 0xab, 0x0c, 0xff, 0x1e, + 0x32, 0xfc, 0x3f, 0x93, 0x81, 0xca, 0xf1, 0x83, 0x0f, 0xc0, 0xaa, 0xfd, 0xbd, 0xbd, 0x4f, 0xf4, 0x2a, 0x18, 0x9f, + 0x43, 0x40, 0x5f, 0x29, 0x0f, 0x9d, 0x71, 0x34, 0x3c, 0x82, 0x7e, 0xb0, 0x00, 0xec, 0xd6, 0xb9, 0x1a, 0x72, 0xb6, + 0x4a, 0x9b, 0xe9, 0x77, 0x87, 0x65, 0xb3, 0x73, 0x04, 0xfb, 0xc4, 0xf8, 0x6a, 0xce, 0x78, 0x49, 0x3b, 0x26, 0x8f, + 0x60, 0x2e, 0xa4, 0xfd, 0x76, 0x2d, 0x0f, 0x2d, 0xc9, 0x73, 0xf5, 0x24, 0x6a, 0x77, 0x8b, 0x02, 0x8a, 0x84, 0xa2, + 0xa4, 0x73, 0x9f, 0xd6, 0x47, 0xf3, 0x5c, 0xe7, 0x83, 0xf9, 0x2c, 0x7a, 0x76, 0x54, 0x07, 0xee, 0x20, 0xe1, 0x65, + 0x39, 0xa4, 0x62, 0x2b, 0x3e, 0xcf, 0xc0, 0x70, 0xda, 0x19, 0xa6, 0x82, 0xd4, 0xac, 0xda, 0xcf, 0x05, 0x64, 0x26, + 0x07, 0xaa, 0x07, 0x2b, 0x8e, 0xcb, 0xb5, 0x94, 0x0d, 0x07, 0xdd, 0x5d, 0x4e, 0xbb, 0xb9, 0xb7, 0x30, 0x13, 0xa7, + 0x23, 0x39, 0x5b, 0x8b, 0x39, 0x0e, 0x3b, 0x5a, 0x2f, 0x96, 0x24, 0xbb, 0x5b, 0x75, 0xcd, 0x9a, 0xe7, 0x4e, 0xa6, + 0x32, 0xe7, 0xfc, 0x89, 0x5f, 0x90, 0x90, 0x66, 0x8b, 0x7e, 0x55, 0x14, 0xc5, 0x02, 0xa0, 0xa0, 0x8e, 0xc9, 0x44, + 0xf3, 0x00, 0x8f, 0x14, 0xdb, 0x85, 0x99, 0x38, 0x50, 0x1b, 0xc6, 0x46, 0x48, 0xeb, 0xcf, 0x16, 0x27, 0x77, 0xbc, + 0x05, 0xa4, 0x64, 0x01, 0x42, 0x5a, 0x88, 0x47, 0x30, 0xf3, 0x58, 0x13, 0xc6, 0x2f, 0xad, 0x57, 0xc7, 0x64, 0xd1, + 0x97, 0x14, 0x80, 0x45, 0xab, 0xd1, 0x85, 0x65, 0x01, 0x45, 0xc3, 0x14, 0xc6, 0x79, 0x30, 0xf6, 0xda, 0xdd, 0x11, + 0xf7, 0x07, 0xe4, 0x70, 0xa2, 0x2e, 0x2a, 0xba, 0x5b, 0x7c, 0x5c, 0x0b, 0xc9, 0x8a, 0xbd, 0xd3, 0x17, 0xd6, 0x39, + 0x1c, 0x16, 0x28, 0xa8, 0x4b, 0x20, 0xa5, 0x94, 0x2f, 0xb4, 0x0e, 0x87, 0x49, 0x5a, 0x8b, 0x1e, 0xa7, 0xb3, 0x18, + 0x7d, 0x40, 0x3f, 0x97, 0xf5, 0x9f, 0xa8, 0xd5, 0x59, 0x3c, 0xd4, 0xa4, 0x83, 0x44, 0xef, 0x2c, 0x1b, 0xc0, 0xb4, + 0x9e, 0x3b, 0x13, 0x78, 0x57, 0xfd, 0x96, 0x12, 0x06, 0x9e, 0x83, 0x99, 0xba, 0x5e, 0x9e, 0xf0, 0xf6, 0xdb, 0x1d, + 0x12, 0x4d, 0xc5, 0xf2, 0x9e, 0x4e, 0x93, 0x20, 0xef, 0x0c, 0x8f, 0x0f, 0xaf, 0x1b, 0xa9, 0xbd, 0x13, 0xd4, 0xa3, + 0x62, 0x4a, 0x7c, 0xef, 0x0b, 0x6f, 0x24, 0x2f, 0x8a, 0x60, 0x59, 0x9c, 0x91, 0x52, 0x65, 0xef, 0xc8, 0xfa, 0x53, + 0x11, 0x8c, 0x40, 0xc0, 0xe9, 0xdd, 0xc0, 0xfc, 0xc8, 0x74, 0x58, 0x1c, 0x2e, 0xa4, 0xe8, 0xa3, 0x3a, 0x5f, 0x77, + 0x95, 0x6d, 0x7d, 0xe1, 0xe8, 0x3e, 0x0b, 0x5f, 0xdd, 0x97, 0xa5, 0xc1, 0xe3, 0x65, 0x69, 0x80, 0x54, 0x23, 0xf3, + 0xb2, 0xd9, 0x25, 0x03, 0x5d, 0x20, 0x46, 0xf0, 0x3b, 0x78, 0x16, 0xbe, 0x06, 0xfe, 0xff, 0x4a, 0xbd, 0xf9, 0xc3, + 0xc5, 0xe6, 0x2b, 0x2a, 0xcd, 0x57, 0x56, 0x19, 0xe3, 0x9d, 0x72, 0x1e, 0x66, 0x50, 0x4e, 0x18, 0x16, 0x6c, 0xe5, + 0xff, 0x2f, 0xa0, 0xfd, 0x77, 0x1c, 0xc3, 0x17, 0xfe, 0x14, 0xcf, 0x90, 0x1e, 0x0c, 0x44, 0x38, 0x9c, 0xa2, 0xc9, + 0xab, 0x11, 0x1e, 0xf9, 0x48, 0xb5, 0x30, 0x63, 0x34, 0x81, 0x7e, 0x0f, 0xf9, 0x63, 0x1c, 0x4e, 0x60, 0x03, 0x05, + 0x3e, 0x8e, 0xde, 0x04, 0x21, 0x1e, 0x47, 0x40, 0x15, 0x78, 0x38, 0x0c, 0x90, 0xa1, 0x1d, 0xe3, 0x00, 0xc4, 0x29, + 0x92, 0xb0, 0x06, 0xa0, 0xb3, 0x10, 0x7b, 0x13, 0x10, 0x37, 0xc6, 0xde, 0x0c, 0x4f, 0xc7, 0x68, 0x8a, 0x27, 0x00, + 0x1d, 0x1e, 0x45, 0x95, 0x13, 0x61, 0x1f, 0xb6, 0xc3, 0x31, 0x99, 0xe2, 0x51, 0x88, 0xf4, 0x60, 0xe0, 0x98, 0x80, + 0x08, 0x07, 0x7b, 0xfe, 0x9b, 0x10, 0x07, 0x13, 0xd0, 0x3b, 0x1a, 0xbd, 0x00, 0xb1, 0xb3, 0x11, 0x32, 0xa3, 0x81, + 0x17, 0x14, 0x44, 0x8f, 0x81, 0x16, 0xfc, 0x75, 0x41, 0x03, 0x48, 0x7c, 0x14, 0xe2, 0x19, 0xc4, 0xae, 0xaf, 0xf8, + 0xcd, 0x68, 0x70, 0xf3, 0x7d, 0xe4, 0xfd, 0x61, 0xcc, 0xc2, 0xbf, 0x2e, 0x66, 0xbe, 0x42, 0x00, 0xa6, 0xa0, 0x1b, + 0xe4, 0x20, 0x3d, 0x18, 0xdd, 0xc0, 0x3c, 0x7d, 0x35, 0x43, 0x53, 0xe0, 0x1a, 0x4f, 0xd1, 0x0c, 0x45, 0x0a, 0x5d, + 0x60, 0x1f, 0x19, 0x26, 0x07, 0x98, 0xbe, 0x12, 0xc6, 0xd1, 0x9f, 0x18, 0xc6, 0xc7, 0x7c, 0xfa, 0x13, 0xbb, 0xf4, + 0xff, 0x48, 0x41, 0xd0, 0x8e, 0xe9, 0x36, 0x2c, 0x76, 0xcd, 0x95, 0x5e, 0x75, 0x51, 0x70, 0x43, 0x87, 0x6e, 0x04, + 0x2e, 0xf9, 0x3e, 0x62, 0x79, 0x52, 0xfa, 0xe9, 0x67, 0xdd, 0x39, 0x50, 0xfa, 0x69, 0xac, 0xcb, 0x79, 0x7a, 0x53, + 0x52, 0xf4, 0xfa, 0xfa, 0x1d, 0xdc, 0xca, 0xaa, 0x0a, 0xf1, 0x66, 0x0b, 0x97, 0xbf, 0x3d, 0x92, 0x8d, 0xba, 0xce, + 0x73, 0xe8, 0x15, 0xd5, 0x14, 0xee, 0x0d, 0xa8, 0x6f, 0x16, 0x30, 0xc6, 0xf1, 0xb2, 0x4b, 0xdf, 0x55, 0x94, 0x08, + 0x8a, 0x56, 0x6c, 0x43, 0x11, 0x93, 0xd0, 0x07, 0xd4, 0x14, 0x49, 0xa6, 0x86, 0x33, 0xa3, 0xa6, 0x83, 0x9e, 0x56, + 0x2b, 0x31, 0xdd, 0x30, 0x58, 0x02, 0x62, 0xd2, 0xbe, 0xed, 0x8d, 0xcb, 0xd0, 0x58, 0x75, 0x4d, 0xa5, 0x84, 0x8e, + 0x41, 0x59, 0x15, 0xa6, 0xb1, 0xba, 0x76, 0x22, 0xa2, 0x2f, 0x06, 0x89, 0xbb, 0x65, 0x05, 0x53, 0x97, 0xf9, 0x34, + 0xd6, 0xad, 0xa2, 0x92, 0xa0, 0xba, 0x15, 0xf3, 0xe5, 0x41, 0xcf, 0x2a, 0xca, 0x57, 0x70, 0x9b, 0x84, 0x77, 0x01, + 0xcd, 0x43, 0x46, 0xcb, 0xa6, 0x82, 0xe6, 0x24, 0xb9, 0xbe, 0xfe, 0xe9, 0xef, 0xea, 0x33, 0x85, 0x32, 0xe1, 0xcc, + 0x09, 0x7d, 0xbe, 0x61, 0x54, 0x93, 0x9e, 0x6f, 0x3c, 0x32, 0x1f, 0x1c, 0x5a, 0xe8, 0xd3, 0xc1, 0xbf, 0xfc, 0x33, + 0x29, 0xef, 0x4e, 0x9b, 0xbd, 0x24, 0xfd, 0x5f, 0x37, 0x9d, 0x86, 0x49, 0xac, 0x97, 0x35, 0x93, 0xe9, 0x35, 0x18, + 0x18, 0xbb, 0xe6, 0x01, 0x38, 0xa7, 0x1c, 0x30, 0xb4, 0x65, 0xcf, 0x03, 0x60, 0xff, 0x72, 0xf3, 0x02, 0xfd, 0xda, + 0xc2, 0x09, 0xa6, 0x06, 0x7b, 0xed, 0x65, 0x4d, 0x65, 0xd9, 0xe4, 0xc9, 0xbb, 0x5f, 0xae, 0x6f, 0xce, 0x1e, 0xaf, + 0x35, 0x11, 0xa2, 0x3c, 0x33, 0x1f, 0x42, 0xd6, 0x95, 0x64, 0x2d, 0xe9, 0xa4, 0x16, 0xeb, 0xa8, 0x10, 0x38, 0x79, + 0xa4, 0x9f, 0x17, 0xac, 0xa2, 0xc6, 0xa9, 0x9e, 0xd1, 0x4d, 0xd1, 0x97, 0x6c, 0x3c, 0xe9, 0x7e, 0x60, 0xa5, 0x6b, + 0x4e, 0x89, 0x6b, 0x8e, 0x8c, 0xab, 0x3f, 0x13, 0xfd, 0x0b, 0x65, 0x37, 0xa3, 0x8e, 0x36, 0x12, 0x00, 0x00}; } // namespace captive_portal } // namespace esphome diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h index 8eaaaf4581..4e6e136f8c 100644 --- a/esphome/components/web_server/server_index.h +++ b/esphome/components/web_server/server_index.h @@ -6,585 +6,596 @@ namespace esphome { namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xd9, 0x76, 0xdb, 0xc8, 0x92, 0xe0, 0xf3, - 0x9c, 0xd3, 0x7f, 0x30, 0x2f, 0x30, 0x4a, 0x6d, 0x03, 0x57, 0x20, 0x44, 0x52, 0x96, 0xed, 0x02, 0x05, 0xf2, 0xca, - 0x4b, 0x5d, 0xbb, 0xca, 0x5b, 0x59, 0xb2, 0x6b, 0x51, 0xb1, 0x2c, 0x88, 0x4c, 0x8a, 0x28, 0x83, 0x00, 0x0b, 0x48, - 0x6a, 0x29, 0x0a, 0x7d, 0xfa, 0xa9, 0x9f, 0xe6, 0x9c, 0x59, 0x1f, 0xfa, 0x65, 0x4e, 0xf7, 0xc3, 0x7c, 0xc4, 0x3c, - 0xf7, 0xa7, 0xdc, 0x1f, 0x98, 0xfe, 0x84, 0x89, 0x88, 0x5c, 0x90, 0x00, 0xa9, 0xc5, 0xd5, 0xd5, 0x7d, 0xbc, 0x08, - 0xc8, 0x35, 0x22, 0x32, 0x32, 0xb6, 0x8c, 0x84, 0x76, 0xef, 0x8c, 0xb3, 0x11, 0xbf, 0x98, 0x33, 0x6b, 0xca, 0x67, - 0x49, 0x7f, 0x57, 0xfe, 0xcf, 0xa2, 0x71, 0x7f, 0x37, 0x89, 0xd3, 0x4f, 0x56, 0xce, 0x92, 0x30, 0x1e, 0x65, 0xa9, - 0x35, 0xcd, 0xd9, 0x24, 0x1c, 0x47, 0x3c, 0x0a, 0xe2, 0x59, 0x74, 0xc2, 0xac, 0xad, 0xfe, 0xee, 0x8c, 0xf1, 0xc8, - 0x1a, 0x4d, 0xa3, 0xbc, 0x60, 0x3c, 0x7c, 0x7f, 0xf0, 0x55, 0xeb, 0x51, 0x7f, 0xb7, 0x18, 0xe5, 0xf1, 0x9c, 0x5b, - 0x38, 0x64, 0x38, 0xcb, 0xc6, 0x8b, 0x84, 0xf5, 0x4f, 0xa3, 0xdc, 0x7a, 0xc6, 0xc2, 0x37, 0xc7, 0xbf, 0xb0, 0x11, - 0xf7, 0xc7, 0x6c, 0x12, 0xa7, 0xec, 0x6d, 0x9e, 0xcd, 0x59, 0xce, 0x2f, 0xbc, 0xfd, 0xf5, 0x15, 0x31, 0x2b, 0xbc, - 0x4f, 0xba, 0xea, 0x84, 0xf1, 0x37, 0x67, 0xa9, 0xea, 0xf3, 0x94, 0x89, 0x49, 0xb2, 0xbc, 0xf0, 0x8a, 0x2b, 0xda, - 0xec, 0x5f, 0xcc, 0x8e, 0xb3, 0xa4, 0xf0, 0x9e, 0xe8, 0xfa, 0x79, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0xa7, 0x51, 0x61, - 0xb4, 0xf4, 0xde, 0xac, 0x69, 0x32, 0x97, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x62, 0xc6, 0xf2, 0xe8, 0x38, 0x61, 0x5e, - 0xcc, 0x42, 0x87, 0x79, 0xdc, 0x8b, 0xdd, 0xb0, 0xcf, 0xad, 0x38, 0xb5, 0xd8, 0xe0, 0x19, 0xa3, 0x92, 0x25, 0xd3, - 0xad, 0x82, 0x3b, 0x6d, 0x0f, 0xc8, 0x35, 0x89, 0x4f, 0x16, 0xfa, 0xfd, 0x2c, 0x8f, 0xb9, 0x7a, 0x3e, 0x8d, 0x92, - 0x05, 0x0b, 0xe2, 0xd2, 0x0d, 0xd8, 0x21, 0x1f, 0x86, 0xb1, 0xf7, 0x84, 0x06, 0x85, 0x21, 0x97, 0x93, 0x2c, 0x77, - 0x90, 0x56, 0x31, 0x8e, 0xcd, 0x2f, 0x2f, 0x1d, 0x1e, 0x2e, 0x4b, 0xd7, 0x7d, 0xc2, 0xfc, 0x51, 0x94, 0x24, 0x0e, - 0x4e, 0x7c, 0xf7, 0x6e, 0x8c, 0x33, 0xc6, 0x1e, 0x3f, 0x8c, 0x87, 0x6e, 0x2f, 0x9e, 0x38, 0x05, 0x73, 0xab, 0x7e, - 0xd9, 0xc4, 0x2a, 0x98, 0xc3, 0x5d, 0xf7, 0xcd, 0xd5, 0x7d, 0x72, 0xc6, 0x17, 0x39, 0xc0, 0x5e, 0x7a, 0x6f, 0xd4, - 0xcc, 0xfb, 0x58, 0xff, 0x89, 0x3a, 0xf6, 0x00, 0xf6, 0x82, 0x5b, 0x1f, 0xc2, 0xb3, 0x38, 0x1d, 0x67, 0x67, 0xfe, - 0xfe, 0x34, 0x82, 0x1f, 0xef, 0xb2, 0x8c, 0xdf, 0xbd, 0xeb, 0x9c, 0x66, 0xf1, 0xd8, 0x6a, 0x87, 0xa1, 0x59, 0x79, - 0xf1, 0x64, 0x7f, 0xff, 0xf2, 0xb2, 0x51, 0xe0, 0xa7, 0x11, 0x8f, 0x4f, 0x99, 0xe8, 0x0c, 0x00, 0xd8, 0xf0, 0x73, - 0xce, 0xd9, 0x78, 0x9f, 0x5f, 0x24, 0x50, 0xca, 0x18, 0x2f, 0x6c, 0xc0, 0xf1, 0x69, 0x36, 0x02, 0xb2, 0xa5, 0x06, - 0xe1, 0xa1, 0x69, 0xce, 0xe6, 0x49, 0x34, 0x62, 0x58, 0x0f, 0x23, 0x55, 0x3d, 0xaa, 0x46, 0xde, 0x57, 0xa1, 0x58, - 0x5e, 0xc7, 0xf5, 0x72, 0x16, 0xa6, 0xec, 0xcc, 0x7a, 0x15, 0xcd, 0x7b, 0xa3, 0x24, 0x2a, 0x0a, 0x2b, 0x63, 0x4b, - 0x42, 0x21, 0x5f, 0x8c, 0x80, 0x41, 0x08, 0xc1, 0x25, 0x90, 0x89, 0x4f, 0xe3, 0xc2, 0xff, 0xb8, 0x31, 0x2a, 0x8a, - 0x77, 0xac, 0x58, 0x24, 0x7c, 0x23, 0x84, 0xb5, 0xe0, 0x77, 0xc2, 0xf0, 0x2b, 0x97, 0x4f, 0xf3, 0xec, 0xcc, 0x7a, - 0x96, 0xe7, 0xd0, 0xdc, 0x86, 0x29, 0x45, 0x03, 0x2b, 0x2e, 0xac, 0x34, 0xe3, 0x96, 0x1e, 0x0c, 0x17, 0xd0, 0xb7, - 0xde, 0x17, 0xcc, 0x3a, 0x5a, 0xa4, 0x45, 0x34, 0x61, 0xd0, 0xf4, 0xc8, 0xca, 0x72, 0xeb, 0x08, 0x06, 0x3d, 0x82, - 0x25, 0x2b, 0x38, 0xec, 0x1a, 0xdf, 0x76, 0x7b, 0x34, 0x17, 0x14, 0x1e, 0xb0, 0x73, 0x1e, 0xb2, 0x12, 0x18, 0xd3, - 0x2a, 0x34, 0x1a, 0x8e, 0xbb, 0x4c, 0xa0, 0x80, 0x85, 0x39, 0x43, 0x96, 0x75, 0xcc, 0xc6, 0x7a, 0x71, 0x3e, 0xdc, - 0xbd, 0xab, 0x69, 0x0d, 0x34, 0x71, 0xa0, 0x6d, 0xd1, 0x68, 0xeb, 0x09, 0xc4, 0x6b, 0x24, 0x72, 0x3d, 0xe6, 0x4b, - 0xf2, 0xed, 0x5f, 0xa4, 0xa3, 0xfa, 0xd8, 0x50, 0x59, 0xf2, 0x6c, 0x9f, 0xe7, 0x71, 0x7a, 0x02, 0x40, 0xc8, 0x99, - 0xcc, 0x26, 0x65, 0x29, 0x16, 0xff, 0x2d, 0x0b, 0x59, 0xd8, 0xc7, 0xd1, 0x33, 0xe6, 0xd8, 0x05, 0xf5, 0xb0, 0xc3, - 0x10, 0x49, 0x0f, 0x0c, 0xc6, 0x06, 0x2c, 0x60, 0x9b, 0xb6, 0xed, 0x7d, 0xe5, 0x7a, 0x17, 0xc8, 0x41, 0xbe, 0xef, - 0x13, 0xfb, 0x8a, 0xce, 0x71, 0xd8, 0x41, 0xa0, 0xfd, 0x84, 0xa5, 0x27, 0x7c, 0x3a, 0x60, 0x87, 0xed, 0x61, 0xc0, - 0x01, 0xaa, 0xf1, 0x62, 0xc4, 0x1c, 0xe4, 0x47, 0x2f, 0xc7, 0xed, 0xb3, 0xe9, 0xc0, 0x14, 0xb8, 0x30, 0x77, 0x08, - 0xc7, 0xda, 0xd2, 0xb8, 0x8a, 0x45, 0x15, 0x60, 0xc8, 0xe7, 0x36, 0xec, 0xb0, 0x63, 0x96, 0x1b, 0x70, 0xe8, 0x66, - 0xbd, 0xda, 0x0a, 0x2e, 0x60, 0x85, 0xa0, 0x9f, 0x35, 0x59, 0xa4, 0x23, 0x1e, 0x83, 0xe0, 0xb2, 0x37, 0x01, 0x5c, - 0xb1, 0x72, 0x7a, 0xe1, 0x6c, 0xb7, 0x74, 0x9d, 0xd8, 0xdd, 0x64, 0x87, 0xf9, 0x66, 0x67, 0xe8, 0x21, 0x94, 0x9a, - 0xf8, 0x12, 0xf1, 0x18, 0x10, 0x2c, 0xbd, 0xf7, 0x4c, 0x6f, 0xcf, 0x0f, 0x03, 0xe6, 0xaf, 0xf2, 0x71, 0xc8, 0xfd, - 0x59, 0x34, 0x47, 0x6c, 0x18, 0xf1, 0x40, 0x94, 0x8e, 0x10, 0xba, 0xda, 0xba, 0x20, 0xc5, 0xfc, 0x8a, 0x05, 0x5c, - 0x20, 0x08, 0xec, 0xd9, 0x67, 0xd1, 0x68, 0x0a, 0x5b, 0xbc, 0x22, 0xdc, 0x58, 0x6d, 0x87, 0x51, 0xce, 0x22, 0xce, - 0x9e, 0x25, 0x0c, 0xdf, 0x70, 0x05, 0xa0, 0xa7, 0x0d, 0xbc, 0xae, 0xf6, 0x5d, 0x12, 0xf3, 0xd7, 0x19, 0xcc, 0xd3, - 0x13, 0x4c, 0x02, 0x5c, 0x9c, 0xc3, 0x26, 0x47, 0x16, 0xd9, 0xe3, 0xb0, 0x5a, 0xc7, 0x0b, 0x0e, 0xeb, 0x96, 0x62, - 0x0b, 0x1b, 0xa8, 0xed, 0xc5, 0x3e, 0x07, 0x22, 0x3e, 0xc9, 0x52, 0x0e, 0xc3, 0x01, 0xbc, 0x9a, 0x83, 0xfc, 0x68, - 0x3e, 0x67, 0xe9, 0xf8, 0xc9, 0x34, 0x4e, 0xc6, 0x40, 0x8d, 0x12, 0xf0, 0x4d, 0x59, 0x08, 0x78, 0x02, 0x32, 0xc1, - 0xf5, 0x18, 0xd1, 0xf2, 0x21, 0x23, 0xf3, 0xd0, 0xb6, 0x7b, 0x28, 0x81, 0x24, 0x16, 0x28, 0x83, 0x68, 0xe1, 0xde, - 0x81, 0xe8, 0x2f, 0x5c, 0xbe, 0x19, 0xc6, 0x7a, 0x19, 0x25, 0x81, 0xdf, 0xa2, 0xa4, 0x01, 0xfa, 0x33, 0x90, 0x81, - 0x3d, 0x14, 0x5c, 0xdf, 0x49, 0xa9, 0x93, 0x30, 0x85, 0x21, 0x10, 0x60, 0x84, 0x12, 0x44, 0xd2, 0xe0, 0x6d, 0x96, - 0x5c, 0x4c, 0xe2, 0x24, 0xd9, 0x5f, 0xcc, 0xe7, 0x59, 0xce, 0xbd, 0xaf, 0xc3, 0x25, 0xcf, 0x2a, 0x5c, 0x69, 0x93, - 0x17, 0x67, 0x31, 0x47, 0x82, 0xba, 0xcb, 0x51, 0x04, 0x4b, 0xfd, 0x38, 0xcb, 0x12, 0x16, 0xa5, 0x80, 0x06, 0x1b, - 0xd8, 0x76, 0x90, 0x2e, 0x92, 0xa4, 0x77, 0x0c, 0xc3, 0x7e, 0xea, 0x51, 0xb5, 0x90, 0xf8, 0x01, 0x3d, 0xef, 0xe5, - 0x79, 0x74, 0x01, 0x0d, 0xb1, 0x0d, 0xf0, 0x22, 0xac, 0xd6, 0xd7, 0xfb, 0x6f, 0x5e, 0xfb, 0x82, 0xf1, 0xe3, 0xc9, - 0x05, 0x00, 0x5a, 0x56, 0x52, 0x73, 0x92, 0x67, 0xb3, 0xc6, 0xd4, 0x48, 0x87, 0x38, 0x64, 0xbd, 0x2b, 0x40, 0x88, - 0x69, 0x64, 0x58, 0x25, 0x66, 0x42, 0xf0, 0x9a, 0xf8, 0x59, 0x56, 0xe2, 0x1e, 0x18, 0xe0, 0x43, 0x20, 0x8a, 0x61, - 0xca, 0xeb, 0xa1, 0xe5, 0xf9, 0xc5, 0x32, 0x0e, 0x09, 0xce, 0x39, 0xea, 0x5f, 0x84, 0x71, 0x14, 0xc1, 0xec, 0x4b, - 0x31, 0x60, 0xa9, 0x20, 0x8e, 0xcb, 0xd2, 0x8b, 0x34, 0x13, 0xa3, 0xc4, 0x43, 0x81, 0xc2, 0x61, 0x1b, 0x5d, 0x5e, - 0x32, 0x78, 0x71, 0xbd, 0x6f, 0xc2, 0x65, 0xa4, 0xf0, 0x41, 0x0d, 0x85, 0xfb, 0x2b, 0x10, 0x72, 0x02, 0x35, 0xd9, - 0x29, 0xe8, 0x41, 0x80, 0xf3, 0x6b, 0x10, 0xb5, 0x93, 0x04, 0xa1, 0xb8, 0xd3, 0xf1, 0x40, 0x83, 0x3e, 0x99, 0x46, - 0xe9, 0x09, 0x1b, 0x07, 0x11, 0x2b, 0xa5, 0xe4, 0xdd, 0xb3, 0x60, 0x8d, 0x81, 0x9d, 0x0a, 0xeb, 0xf9, 0xc1, 0xab, - 0x97, 0x72, 0xe5, 0x6a, 0xc2, 0x18, 0x16, 0x69, 0x01, 0x6a, 0x15, 0xc4, 0xb6, 0x14, 0xc7, 0xcf, 0xb8, 0x92, 0xde, - 0xa2, 0x24, 0x2e, 0xde, 0xcf, 0xc1, 0xc4, 0x60, 0x6f, 0x61, 0x18, 0x98, 0x3e, 0x84, 0xa9, 0xa8, 0x1c, 0xe6, 0x13, - 0x15, 0x63, 0x5d, 0x04, 0x9d, 0x05, 0xa6, 0xe2, 0x35, 0x73, 0xdc, 0x12, 0x58, 0x95, 0xc7, 0x23, 0x2b, 0x1a, 0x8f, - 0x5f, 0xa4, 0x31, 0x8f, 0xa3, 0x24, 0xfe, 0x8d, 0x28, 0xb9, 0x44, 0x1e, 0xe3, 0x3d, 0xb9, 0x08, 0x80, 0x3b, 0xf5, - 0x48, 0x5c, 0x25, 0x64, 0xef, 0x10, 0x31, 0x84, 0xb4, 0x4c, 0xc2, 0xc3, 0xa1, 0x04, 0x2f, 0xf1, 0xe7, 0x8b, 0x62, - 0x8a, 0x84, 0x95, 0x03, 0xa3, 0x20, 0xcf, 0x8e, 0x0b, 0x96, 0x9f, 0xb2, 0xb1, 0xe6, 0x80, 0x02, 0xb0, 0xa2, 0xe6, - 0x60, 0xbc, 0xd0, 0x8c, 0x8e, 0xd2, 0xa1, 0x0c, 0x86, 0xea, 0x99, 0x62, 0x96, 0x49, 0x66, 0xd6, 0x16, 0x8e, 0x96, - 0x02, 0x8e, 0x30, 0x2a, 0xa4, 0x24, 0xc8, 0x43, 0x85, 0xe1, 0x14, 0xa4, 0x10, 0x68, 0x05, 0x73, 0x9b, 0x2b, 0x4d, - 0xf6, 0x6c, 0x41, 0x2a, 0x21, 0x87, 0x8e, 0xb0, 0x91, 0x09, 0xd2, 0xdc, 0x85, 0x5d, 0x05, 0x52, 0x5e, 0x82, 0x2b, - 0xa4, 0x88, 0x32, 0x73, 0x90, 0x01, 0xc2, 0x6f, 0x84, 0x2e, 0xf4, 0xb1, 0x05, 0xb1, 0x81, 0xaf, 0x57, 0x1e, 0x08, - 0x2b, 0xf1, 0xae, 0x10, 0xf1, 0xae, 0x00, 0x1b, 0x27, 0x46, 0x7e, 0xf2, 0xee, 0x70, 0x3f, 0xcd, 0xf6, 0x46, 0x23, - 0x56, 0x14, 0x19, 0xc0, 0x76, 0x87, 0xda, 0x5f, 0x65, 0x68, 0x01, 0x25, 0x5d, 0x2d, 0xeb, 0xec, 0x82, 0x34, 0xb8, - 0xa9, 0x56, 0x94, 0x4e, 0x0f, 0xec, 0x8f, 0x1f, 0x41, 0x66, 0x7b, 0x92, 0x0c, 0x40, 0xf5, 0x55, 0xc3, 0x4f, 0xd8, - 0x33, 0x75, 0xca, 0xac, 0xb5, 0x2f, 0x9d, 0x3a, 0x48, 0x1e, 0x0c, 0xeb, 0x96, 0xc6, 0x82, 0xae, 0x1d, 0x1a, 0x57, - 0x43, 0x2a, 0xc8, 0xe5, 0x09, 0xa9, 0x6c, 0x63, 0x19, 0xc1, 0x6a, 0x2b, 0x3d, 0x22, 0xbd, 0xc2, 0xa6, 0x20, 0x40, - 0x0f, 0xd9, 0xb0, 0x27, 0xeb, 0xc3, 0x5c, 0x50, 0x2e, 0x67, 0xbf, 0x2e, 0x58, 0xc1, 0x05, 0xeb, 0xc2, 0xb8, 0x05, - 0x8c, 0x5b, 0xae, 0x58, 0x87, 0x35, 0xdb, 0x71, 0x1d, 0x6c, 0x6f, 0xe6, 0xa8, 0xc7, 0x0a, 0xe4, 0xe4, 0xeb, 0xd9, - 0x09, 0x61, 0x65, 0xee, 0xe5, 0xe5, 0x37, 0x6a, 0x90, 0x6a, 0x29, 0xb5, 0x0d, 0xd4, 0x58, 0x13, 0x5b, 0x35, 0x19, - 0xdb, 0xae, 0x54, 0xa8, 0x77, 0x3a, 0xbd, 0x1a, 0x1f, 0xc0, 0x9e, 0x6b, 0x6b, 0x96, 0xae, 0x8c, 0xed, 0xb7, 0x8a, - 0xa6, 0x6f, 0xc4, 0xc8, 0x64, 0x8d, 0xb2, 0x9b, 0xb9, 0x47, 0xed, 0x78, 0x68, 0xbb, 0x52, 0x57, 0x09, 0x86, 0x45, - 0x5d, 0x30, 0x34, 0xa1, 0x9e, 0xeb, 0x2e, 0xb6, 0x66, 0x2a, 0x16, 0xaa, 0xb5, 0x56, 0x0e, 0x04, 0x0f, 0x0f, 0xc1, - 0x38, 0x59, 0xeb, 0x1f, 0xbc, 0x8e, 0x66, 0x0c, 0x29, 0xea, 0x5d, 0xd5, 0x40, 0x3a, 0x10, 0xd0, 0x64, 0xd8, 0x54, - 0x6f, 0xdc, 0x15, 0x56, 0x53, 0x7d, 0x7f, 0xc5, 0x60, 0x45, 0x80, 0x7d, 0x5d, 0xae, 0x59, 0x22, 0xd2, 0x9b, 0x82, - 0x4b, 0x34, 0x7d, 0x44, 0x99, 0x58, 0x13, 0x52, 0xf0, 0x80, 0x3c, 0x2c, 0x7f, 0x63, 0xe1, 0x64, 0x2b, 0xa6, 0x70, - 0xe4, 0x28, 0x53, 0x80, 0xce, 0xa4, 0x04, 0x40, 0x5c, 0xd2, 0xcf, 0xda, 0xc6, 0x42, 0xb2, 0xed, 0x23, 0x1f, 0xf8, - 0x93, 0x24, 0xe2, 0x4e, 0x67, 0xab, 0xed, 0x02, 0x1f, 0x82, 0x10, 0x07, 0x1d, 0x01, 0xe6, 0x7d, 0x85, 0x0a, 0x43, - 0x54, 0x62, 0x97, 0xfb, 0x60, 0x14, 0x4d, 0xe3, 0x09, 0x77, 0x52, 0x54, 0x22, 0x6e, 0xc9, 0x12, 0x50, 0x32, 0x7a, - 0x5f, 0x81, 0x94, 0xe0, 0x42, 0xba, 0x88, 0x6a, 0x2d, 0xd0, 0x14, 0xa4, 0x24, 0xa5, 0x48, 0x0b, 0x2a, 0x08, 0x0c, - 0xa1, 0xd2, 0x53, 0x1c, 0x05, 0xfa, 0x2d, 0x1e, 0x88, 0x41, 0x83, 0x15, 0x8b, 0x32, 0x1e, 0xc4, 0xab, 0x85, 0xa0, - 0x86, 0x7d, 0x9e, 0xbd, 0xcc, 0xce, 0x58, 0xfe, 0x24, 0x42, 0xd8, 0x03, 0xd1, 0xbd, 0x04, 0x49, 0x4f, 0x02, 0x9d, - 0xf5, 0x14, 0xaf, 0x9c, 0x12, 0xd2, 0xb0, 0x10, 0xb3, 0x18, 0x15, 0x21, 0x68, 0x39, 0xa2, 0x7d, 0x8a, 0x5b, 0x8a, - 0xf6, 0x1e, 0xaa, 0x12, 0xa6, 0x79, 0x6b, 0xef, 0x65, 0x9d, 0xb7, 0x60, 0x84, 0xb9, 0xe2, 0xd6, 0xfa, 0x8e, 0x75, - 0x3d, 0xa9, 0x9b, 0x1d, 0xc9, 0x5b, 0x86, 0x32, 0x03, 0xfd, 0x71, 0x79, 0x59, 0x19, 0xe9, 0xa0, 0x4c, 0xb5, 0x34, - 0x47, 0xcb, 0x49, 0x6c, 0x09, 0xb7, 0x04, 0x65, 0x84, 0x86, 0x57, 0x9e, 0x25, 0x89, 0xa1, 0x8b, 0xbc, 0xb8, 0xe7, - 0x34, 0xd4, 0x11, 0x40, 0x31, 0xab, 0x69, 0xa4, 0x01, 0x0f, 0x74, 0x05, 0x2a, 0x25, 0xa5, 0x8d, 0xbc, 0xaa, 0x89, - 0x80, 0x38, 0x1d, 0xb3, 0x5c, 0x38, 0x68, 0x52, 0x87, 0xc2, 0x84, 0x29, 0x30, 0x34, 0x1b, 0x83, 0x84, 0x57, 0x08, - 0x80, 0x79, 0xe2, 0x4f, 0xb3, 0x82, 0xeb, 0x3a, 0x13, 0xfa, 0xf8, 0xf2, 0x32, 0x16, 0xfe, 0x22, 0x32, 0x40, 0xce, - 0x66, 0xd9, 0x29, 0x5b, 0x03, 0x75, 0x4f, 0x0d, 0x66, 0x82, 0x6c, 0x0c, 0x03, 0x4a, 0x14, 0x54, 0xcb, 0x3c, 0x89, - 0x47, 0x4c, 0x6b, 0xa9, 0x99, 0x0f, 0x06, 0x1d, 0x3b, 0x07, 0x19, 0xc1, 0xdc, 0x7e, 0xbf, 0xdf, 0xf6, 0x3a, 0x6e, - 0x29, 0x08, 0xbe, 0x5c, 0xa1, 0xe8, 0x35, 0xfa, 0x51, 0x9a, 0xe0, 0xeb, 0x64, 0x01, 0x77, 0x0d, 0xa5, 0xc8, 0x85, - 0x9f, 0xe4, 0x49, 0x41, 0xec, 0x7a, 0x63, 0x18, 0x94, 0x33, 0x25, 0xb8, 0xd1, 0xc4, 0x15, 0xdb, 0xf6, 0x9d, 0x26, - 0x9b, 0x66, 0x27, 0xb5, 0xc3, 0xd4, 0xc2, 0xc8, 0x35, 0x2f, 0xb4, 0x07, 0x6c, 0x2e, 0x0f, 0x5a, 0x89, 0x54, 0x0d, - 0xbc, 0x0e, 0x10, 0x0a, 0x4f, 0xd7, 0x59, 0x41, 0xa9, 0xea, 0x2c, 0x85, 0xb8, 0xde, 0x40, 0xef, 0x99, 0x04, 0x73, - 0x1d, 0x09, 0xf6, 0xa5, 0x40, 0xe0, 0xe8, 0x91, 0x89, 0xf5, 0x7a, 0x02, 0xcb, 0x73, 0x1c, 0x8d, 0x3e, 0x69, 0x70, - 0x2b, 0xb2, 0x37, 0xd9, 0xc0, 0x69, 0x94, 0x84, 0x86, 0xb8, 0x32, 0xf1, 0x56, 0x12, 0xba, 0xb6, 0x51, 0xc0, 0x21, - 0x5b, 0x61, 0xfb, 0xe6, 0x42, 0x37, 0xb9, 0x5d, 0xb2, 0x87, 0xf2, 0x9f, 0x34, 0x97, 0x5c, 0xc3, 0x72, 0x5c, 0x49, - 0x03, 0xae, 0x18, 0x0f, 0x96, 0xa6, 0x01, 0x09, 0xf0, 0x5d, 0x39, 0x8e, 0x8b, 0xab, 0x49, 0xf0, 0x87, 0x82, 0xf9, - 0xd4, 0x98, 0xe9, 0x46, 0x48, 0xb5, 0x84, 0x93, 0x66, 0xb0, 0x06, 0x4d, 0x1a, 0x0f, 0x4a, 0xd4, 0x7c, 0x83, 0x86, - 0x0a, 0x71, 0xfc, 0x89, 0xa8, 0x42, 0x13, 0x0c, 0xc1, 0xc8, 0xbd, 0x42, 0x32, 0x5c, 0xb6, 0x2a, 0x5a, 0xa4, 0x4c, - 0x8d, 0x49, 0xa5, 0x6a, 0x96, 0xcb, 0xc0, 0xc0, 0xa2, 0xdd, 0xea, 0x4b, 0x4b, 0x5c, 0x89, 0xdc, 0x34, 0xd4, 0xc2, - 0xa4, 0x50, 0xde, 0x84, 0x93, 0xa3, 0xdf, 0xa5, 0xac, 0x77, 0x13, 0x9f, 0x5c, 0xe1, 0x93, 0xfb, 0x86, 0x0f, 0x65, - 0xf2, 0x76, 0x31, 0x28, 0x82, 0xaf, 0x6b, 0x95, 0x68, 0x9f, 0xfa, 0x28, 0x98, 0x5d, 0x2d, 0x74, 0x41, 0xa0, 0x48, - 0x36, 0x49, 0x07, 0x92, 0xdf, 0x50, 0x6c, 0x54, 0x9e, 0x51, 0xe6, 0x8a, 0x0d, 0x52, 0xf3, 0x4a, 0x33, 0x2f, 0x75, - 0x1b, 0xf6, 0x7b, 0x59, 0x4a, 0x3a, 0x71, 0x41, 0x99, 0xd8, 0xbb, 0x8e, 0x36, 0x5e, 0x1a, 0x66, 0xc2, 0xfa, 0x15, - 0xc6, 0x4e, 0x8d, 0x42, 0xa9, 0x14, 0x81, 0x38, 0x36, 0xbe, 0x56, 0x96, 0x41, 0xe6, 0xaf, 0xb1, 0xa7, 0x00, 0x94, - 0x04, 0x16, 0x5f, 0x53, 0xc9, 0x8b, 0xc2, 0x3a, 0x1d, 0xef, 0x10, 0x1d, 0x2b, 0x11, 0x5a, 0x13, 0xf9, 0x5a, 0x9f, - 0xc5, 0x7e, 0xcd, 0x25, 0x34, 0x29, 0x99, 0x0f, 0xf2, 0xc0, 0x56, 0x81, 0x88, 0x4a, 0xb7, 0x25, 0x83, 0x84, 0x1c, - 0xd2, 0x55, 0xa2, 0xd7, 0x46, 0x32, 0x68, 0x9d, 0x0a, 0x89, 0x96, 0x0e, 0xc3, 0xc8, 0x41, 0xc7, 0x9d, 0xd6, 0x62, - 0x85, 0x90, 0x4d, 0x7b, 0x93, 0x58, 0x11, 0x9d, 0xd3, 0x1c, 0x4d, 0x38, 0x53, 0xa7, 0x3b, 0x0e, 0xa0, 0x03, 0x62, - 0x7f, 0x85, 0xf5, 0xd6, 0x9a, 0x9d, 0xae, 0x5f, 0x39, 0x7c, 0x97, 0x97, 0x11, 0xf2, 0x83, 0x30, 0x78, 0x61, 0xcd, - 0x06, 0x4a, 0xf6, 0xee, 0xbd, 0xc4, 0x56, 0x64, 0x7f, 0x56, 0x25, 0x95, 0xa7, 0x50, 0xe3, 0xdc, 0xfa, 0x3a, 0x31, - 0x33, 0xb4, 0xa8, 0x2a, 0xf6, 0x0d, 0xa9, 0xbe, 0xaf, 0x14, 0x76, 0x85, 0xf2, 0xbe, 0x1c, 0x3a, 0x76, 0x5d, 0x37, - 0xc8, 0xc9, 0x79, 0xb9, 0xb3, 0xce, 0x85, 0xbc, 0x7b, 0xd7, 0xf4, 0x99, 0x4e, 0xf5, 0xf0, 0x4f, 0x1c, 0x54, 0xce, - 0xc5, 0x45, 0x4a, 0x16, 0xcc, 0x13, 0xa5, 0x8e, 0x56, 0x1c, 0xd0, 0x76, 0x0f, 0x3d, 0xed, 0xe8, 0x2c, 0x8a, 0xb9, - 0xa5, 0x47, 0x11, 0x9e, 0x36, 0xca, 0x27, 0x69, 0x74, 0x00, 0x5e, 0x68, 0x42, 0x92, 0x13, 0x6e, 0xda, 0xa2, 0xc5, - 0x68, 0xca, 0x30, 0x04, 0xae, 0xec, 0x09, 0x53, 0xf6, 0xdc, 0x41, 0xbc, 0xc5, 0xc0, 0x6c, 0x3d, 0xec, 0x65, 0xb3, - 0x7b, 0xcd, 0xfc, 0x87, 0x35, 0x02, 0xd9, 0x36, 0x53, 0x75, 0x65, 0xe3, 0x5d, 0x8a, 0x48, 0x8c, 0xb0, 0xad, 0x1b, - 0x5b, 0xda, 0xfa, 0xbd, 0x86, 0x7b, 0x5d, 0x39, 0xe6, 0x35, 0xa5, 0xda, 0xd0, 0xc3, 0xca, 0xcd, 0x61, 0xa6, 0x23, - 0x2f, 0x56, 0xd0, 0xed, 0x89, 0xa0, 0x10, 0x38, 0x11, 0xda, 0x1e, 0x54, 0xdc, 0x40, 0xa4, 0xe4, 0x4a, 0xab, 0x66, - 0x8b, 0x64, 0x2c, 0x81, 0x05, 0x17, 0x96, 0x4b, 0x3e, 0x3a, 0x8b, 0x93, 0xa4, 0x2a, 0xfd, 0x43, 0x05, 0xbc, 0x18, - 0xf6, 0x26, 0xd1, 0x2e, 0x30, 0x5a, 0x28, 0x10, 0x5c, 0x6d, 0x84, 0xbd, 0x77, 0xdc, 0x6a, 0xdd, 0x45, 0xc4, 0x91, - 0x9b, 0xd1, 0x08, 0xa8, 0xc7, 0x08, 0xab, 0x66, 0xed, 0xbd, 0x67, 0x18, 0x52, 0x33, 0xf0, 0x41, 0x75, 0x46, 0xc5, - 0x9f, 0x65, 0x4f, 0x7d, 0x26, 0x7a, 0x37, 0xaa, 0xae, 0x66, 0x40, 0x45, 0x05, 0x3e, 0xcc, 0x10, 0x4b, 0x5b, 0x05, - 0x02, 0x72, 0x3d, 0x2c, 0x4a, 0x01, 0x93, 0x34, 0x58, 0x50, 0x0a, 0xac, 0xb5, 0xb2, 0x7b, 0x79, 0x53, 0x30, 0x87, - 0x42, 0xe1, 0xa2, 0xff, 0x93, 0x6c, 0x36, 0x47, 0xcb, 0xac, 0xc1, 0xd4, 0xd0, 0xe0, 0x7d, 0xa3, 0xbe, 0x5c, 0x53, - 0x56, 0xeb, 0x43, 0x3b, 0xb2, 0xc6, 0x4f, 0xda, 0x51, 0x06, 0x87, 0x6a, 0xa1, 0x8b, 0xea, 0x76, 0x73, 0x53, 0xc4, - 0xac, 0xe3, 0x71, 0x9f, 0xf4, 0xb6, 0xb6, 0x26, 0x3d, 0x4d, 0x03, 0x92, 0x49, 0x92, 0xe1, 0x4d, 0x06, 0x28, 0x2b, - 0xe2, 0x2c, 0xcb, 0x06, 0xf9, 0x96, 0x65, 0x89, 0xeb, 0xf7, 0x6d, 0x6f, 0xaf, 0xe6, 0x59, 0x7b, 0x7b, 0x57, 0xbb, - 0xc8, 0x55, 0x9d, 0xf4, 0x20, 0x0f, 0x87, 0x50, 0xb4, 0x62, 0x53, 0x86, 0xcb, 0x59, 0x36, 0x66, 0x81, 0x0d, 0xdd, - 0x53, 0xbb, 0x94, 0x9b, 0x26, 0x81, 0xcd, 0x91, 0x30, 0x67, 0xf9, 0xae, 0x1e, 0x49, 0x0d, 0xf6, 0x80, 0x05, 0xb4, - 0xb9, 0xf0, 0x5d, 0x78, 0x92, 0x64, 0xc7, 0x51, 0x72, 0x20, 0x14, 0x78, 0xad, 0xe5, 0x07, 0x70, 0x19, 0xc9, 0x62, - 0x35, 0x94, 0xd4, 0x77, 0x83, 0xef, 0x82, 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, 0x9b, 0xb2, 0xf0, 0xe5, 0xe0, 0xa5, - 0x72, 0xa9, 0x33, 0x70, 0x21, 0xc0, 0xc9, 0x4f, 0x62, 0xde, 0xc2, 0xf3, 0x48, 0xdb, 0x5b, 0x8a, 0x0a, 0x8c, 0x2b, - 0x52, 0x5c, 0xba, 0x54, 0xde, 0xa0, 0xf7, 0x31, 0x3c, 0x82, 0x66, 0x1b, 0x1b, 0x4b, 0xe7, 0x55, 0xc4, 0xa7, 0x7e, - 0x1e, 0xa5, 0xe3, 0x6c, 0xe6, 0xb8, 0x9b, 0xb6, 0xed, 0xfa, 0x05, 0x79, 0x22, 0x5f, 0xba, 0xe5, 0xc6, 0x91, 0x37, - 0x62, 0xa1, 0x3d, 0xb0, 0x37, 0x3f, 0x7a, 0x07, 0x2c, 0x3c, 0xda, 0xdd, 0x58, 0x8e, 0x58, 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, 0xf0, 0x3e, 0xc2, 0x9a, 0xa7, 0xee, 0x3d, 0xa7, 0xa3, 0x95, 0xfa, 0x46, 0x1e, 0x33, 0x52, - 0x9a, 0xab, 0x76, 0x82, 0x63, 0x6c, 0xf1, 0xf5, 0xdb, 0xfa, 0x50, 0x44, 0x29, 0xfc, 0x18, 0xac, 0x97, 0x08, 0xd4, - 0x37, 0x38, 0x38, 0xde, 0x41, 0xb8, 0xb5, 0xeb, 0x0c, 0x02, 0xe7, 0x4e, 0xab, 0x75, 0xf9, 0xd3, 0xd6, 0xe1, 0xcf, - 0x51, 0xeb, 0xb7, 0xbd, 0xd6, 0x8f, 0x43, 0xf7, 0xd2, 0xf9, 0x69, 0x6b, 0x70, 0x28, 0xdf, 0x0e, 0x7f, 0xee, 0xff, - 0x54, 0x0c, 0xff, 0x24, 0x0a, 0x37, 0x5c, 0x77, 0xeb, 0xc4, 0x5b, 0xb0, 0x70, 0xab, 0xd5, 0xea, 0xc3, 0xd3, 0x1c, - 0x9e, 0xf0, 0xe7, 0x19, 0xfc, 0xb8, 0x3c, 0xb4, 0xfe, 0xd3, 0x4f, 0xe9, 0xdf, 0xfc, 0x94, 0x0f, 0x71, 0xcc, 0xc3, - 0x9f, 0x7f, 0x2a, 0xec, 0x7b, 0xfd, 0x70, 0x6b, 0xb8, 0xe9, 0x3a, 0xba, 0xe6, 0x4f, 0x61, 0xf5, 0x08, 0xad, 0x0e, - 0x7f, 0x96, 0x6f, 0xf6, 0xbd, 0xa3, 0xdd, 0x7e, 0x38, 0xbc, 0x74, 0xec, 0xcb, 0x7b, 0xee, 0xa5, 0xeb, 0x5e, 0x6e, - 0xe0, 0x3c, 0x27, 0x30, 0xfa, 0x3d, 0xf8, 0x79, 0x0a, 0x3f, 0x6d, 0xf8, 0x39, 0x81, 0x9f, 0x3f, 0x43, 0x37, 0x11, - 0x7f, 0xbb, 0xa4, 0x58, 0xc8, 0x25, 0x1e, 0x58, 0x44, 0xb0, 0x0a, 0xee, 0xc6, 0x56, 0xec, 0x6d, 0x10, 0xd1, 0x60, - 0x1f, 0xfa, 0xbe, 0x8f, 0x61, 0x52, 0x67, 0xf9, 0x71, 0x03, 0x16, 0x1d, 0x39, 0x67, 0x23, 0x60, 0x9e, 0x88, 0x1c, - 0x14, 0x01, 0x17, 0x67, 0xab, 0x05, 0x1e, 0xae, 0x7a, 0xe3, 0x70, 0x83, 0x39, 0x60, 0x14, 0xbc, 0x66, 0xf8, 0xd0, - 0x75, 0xbd, 0x67, 0xf2, 0xcc, 0x10, 0xf7, 0xb9, 0x60, 0xad, 0x34, 0x13, 0x26, 0x8d, 0xed, 0x7a, 0xf3, 0x35, 0x95, - 0xb0, 0xad, 0xd3, 0x13, 0xa8, 0x9b, 0x89, 0x83, 0xb6, 0xef, 0x58, 0xf4, 0x09, 0xb7, 0xe4, 0x2b, 0xe3, 0x10, 0x78, - 0xc5, 0x92, 0x6f, 0x1a, 0x8d, 0x86, 0x8d, 0x28, 0xdc, 0xb1, 0xc7, 0x0c, 0x66, 0x58, 0x31, 0x11, 0x39, 0x29, 0x4d, - 0x61, 0xd9, 0xc2, 0xe4, 0x6f, 0xa3, 0x9c, 0x6f, 0x54, 0x86, 0x6d, 0x58, 0xb3, 0x64, 0x9b, 0x96, 0xfe, 0x2d, 0xa6, - 0x40, 0xd3, 0x92, 0xce, 0x3f, 0xcc, 0xf1, 0xc3, 0x94, 0xd0, 0x7a, 0xed, 0x70, 0xf0, 0xd0, 0x0b, 0x90, 0x3b, 0xa2, - 0x9f, 0xf3, 0x16, 0xd5, 0x18, 0xfc, 0x95, 0x61, 0x06, 0x4f, 0xcc, 0x87, 0x21, 0x9a, 0x65, 0xa9, 0x83, 0x5b, 0x29, - 0x8a, 0xfb, 0x17, 0xb8, 0x33, 0xd2, 0xd2, 0xdb, 0x0f, 0xd5, 0x8e, 0x39, 0xc8, 0x19, 0xfb, 0x2e, 0x4a, 0x3e, 0xb1, - 0xdc, 0x39, 0xf7, 0x3a, 0xdd, 0x2f, 0xa9, 0xb3, 0x87, 0xb6, 0xd9, 0xbb, 0xea, 0x18, 0x4d, 0x99, 0x05, 0xea, 0x88, - 0xb0, 0xd5, 0xf1, 0x72, 0x8c, 0x6a, 0x21, 0x09, 0x0a, 0x2f, 0x0b, 0xbb, 0xc4, 0xe1, 0xf6, 0x6e, 0x71, 0x7a, 0xd2, - 0xb7, 0x03, 0xdb, 0x06, 0x8b, 0xff, 0x80, 0xc2, 0x56, 0xc2, 0xb0, 0x00, 0x83, 0x6c, 0x37, 0xee, 0xf1, 0xcd, 0xcd, - 0x2a, 0xe0, 0x84, 0x07, 0xe9, 0xd4, 0x3d, 0xf1, 0x22, 0x6f, 0x1a, 0xc2, 0x80, 0x23, 0x68, 0x86, 0x5d, 0x7a, 0xa3, - 0xdd, 0x58, 0x4e, 0x83, 0xb1, 0x10, 0x3f, 0x89, 0x0a, 0xfe, 0x02, 0xe3, 0x11, 0xe1, 0x08, 0x8d, 0x7d, 0x9f, 0x9d, - 0xb3, 0x91, 0xb2, 0x33, 0x80, 0x50, 0x91, 0xdb, 0x73, 0x47, 0xa1, 0xd1, 0x0c, 0xe6, 0x0e, 0xc3, 0x83, 0x81, 0x0d, - 0x7b, 0x09, 0x76, 0x65, 0x18, 0x1d, 0x76, 0x86, 0x83, 0x34, 0x5c, 0xb0, 0x40, 0xd3, 0x56, 0x16, 0xcd, 0x6b, 0x45, - 0xdd, 0xe1, 0xc0, 0x99, 0x80, 0x91, 0x0e, 0xb6, 0xb8, 0x83, 0x6f, 0x18, 0xa1, 0x28, 0xc2, 0x77, 0xec, 0xe4, 0xd9, - 0xf9, 0xdc, 0xb1, 0x77, 0xb7, 0xec, 0x4d, 0x2c, 0xf5, 0x6c, 0x60, 0x2f, 0x98, 0x3b, 0x3c, 0x73, 0xcd, 0xce, 0xdb, - 0x43, 0x04, 0x15, 0x0b, 0x71, 0xf2, 0xb3, 0x81, 0xdd, 0x17, 0x53, 0xb7, 0x61, 0xd0, 0x54, 0x2e, 0x3f, 0xae, 0xe8, - 0x01, 0xa1, 0xaa, 0xba, 0x2a, 0xe8, 0xa0, 0xac, 0x1b, 0x38, 0x53, 0x13, 0x89, 0x16, 0x4e, 0x26, 0xa9, 0x00, 0x0e, - 0x0f, 0x36, 0x83, 0x49, 0x8d, 0x6e, 0xdb, 0xc3, 0xc1, 0x59, 0x70, 0xcf, 0xbe, 0xa7, 0x5e, 0x4e, 0x59, 0x70, 0xc2, - 0xc4, 0xf4, 0xa7, 0x20, 0xed, 0xf0, 0xe7, 0x09, 0x03, 0x24, 0xcf, 0xa8, 0x68, 0x21, 0x8b, 0xe6, 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, 0xc5, 0x1c, 0x54, 0xc2, 0x56, 0xdf, 0x76, 0x07, 0xb6, 0x85, 0x4b, 0xdb, 0xcb, 0x36, - 0x43, 0x41, 0xe1, 0x78, 0xf3, 0x80, 0x05, 0xd3, 0x7e, 0xd8, 0x1e, 0x38, 0xb9, 0x50, 0x1d, 0x09, 0x9e, 0x5b, 0x0a, - 0x09, 0xde, 0xf6, 0xa6, 0x20, 0xd0, 0x91, 0x73, 0x37, 0xec, 0x4d, 0x55, 0x08, 0x45, 0x1f, 0x37, 0xc7, 0x6e, 0x10, - 0xc3, 0x0f, 0xa7, 0x85, 0x4c, 0x33, 0xd5, 0x7d, 0xb5, 0x66, 0x76, 0x83, 0xb1, 0xb2, 0xc8, 0x93, 0x30, 0xdb, 0x74, - 0x30, 0x42, 0x0b, 0x92, 0x76, 0x77, 0x00, 0x30, 0x6c, 0x3a, 0x8a, 0xd3, 0xb6, 0x14, 0xab, 0x29, 0xfb, 0xfc, 0x50, - 0x2f, 0xc7, 0x94, 0x0d, 0xa6, 0xcc, 0xaf, 0xb4, 0x0f, 0x80, 0x15, 0x24, 0x5e, 0x3e, 0x54, 0x67, 0x5e, 0xcf, 0x6b, - 0xe7, 0x5b, 0x4b, 0x25, 0x8a, 0x98, 0x67, 0x48, 0x28, 0x5e, 0x6a, 0x37, 0x4c, 0x98, 0xdb, 0x73, 0x24, 0x86, 0x66, - 0xf9, 0xb0, 0x0d, 0x4c, 0xaf, 0x02, 0xec, 0xa9, 0xb9, 0x2d, 0x92, 0xb0, 0x6a, 0xee, 0x1d, 0x02, 0x6b, 0x0f, 0x81, - 0x87, 0x68, 0x1b, 0xf5, 0x54, 0x34, 0x9f, 0x25, 0xe1, 0xf3, 0xc6, 0x71, 0x71, 0x84, 0x27, 0x42, 0xfb, 0xfe, 0x68, - 0x91, 0x83, 0x3c, 0xe0, 0xaf, 0xc1, 0x32, 0x08, 0x65, 0x53, 0x74, 0xf4, 0xf0, 0x08, 0xd8, 0x23, 0xc4, 0x1b, 0x61, - 0x73, 0xa3, 0x1a, 0x2d, 0x4a, 0x32, 0x5e, 0xe8, 0x60, 0xb8, 0xc7, 0xa5, 0x6b, 0x8f, 0x82, 0x41, 0x9e, 0x18, 0x3b, - 0x78, 0xe6, 0xef, 0x8f, 0xb0, 0x1a, 0x27, 0x28, 0xdc, 0x92, 0x76, 0x5b, 0x25, 0xfe, 0xf6, 0xfd, 0x14, 0x24, 0x38, - 0xd6, 0x81, 0x9f, 0x75, 0xf7, 0x6e, 0x22, 0x91, 0xda, 0x4d, 0x7b, 0x74, 0x12, 0x81, 0xf1, 0xe0, 0xdc, 0x4f, 0xa1, - 0x1a, 0x49, 0x44, 0x45, 0x39, 0x5a, 0xa0, 0xe6, 0xa9, 0x5a, 0x05, 0xdf, 0xa1, 0x19, 0x81, 0xe7, 0x18, 0xb6, 0x26, - 0x3f, 0x55, 0x37, 0x16, 0xb1, 0x7c, 0xd7, 0xa5, 0xa3, 0x2d, 0x3c, 0x80, 0x14, 0x8c, 0x26, 0x18, 0xc6, 0xa5, 0xa0, - 0x64, 0xc5, 0x7f, 0x1f, 0x8d, 0x58, 0xf9, 0xf4, 0x30, 0xdb, 0xdc, 0x1c, 0x8a, 0x73, 0x0b, 0x62, 0x1c, 0x6e, 0x44, - 0x57, 0xe3, 0x0a, 0x80, 0xfa, 0x74, 0x4e, 0x5c, 0x0f, 0x4c, 0x2b, 0xd6, 0x74, 0x29, 0xf6, 0xc9, 0x61, 0x06, 0xa0, - 0xe0, 0x96, 0x73, 0xe8, 0x0f, 0xfe, 0x3c, 0x04, 0xf7, 0xd8, 0xff, 0x93, 0xbb, 0xa5, 0x04, 0x4d, 0x4f, 0x9e, 0x29, - 0x2e, 0xe9, 0x8c, 0xb5, 0xe3, 0x51, 0x6c, 0x34, 0x28, 0xbc, 0x14, 0x30, 0x00, 0x6d, 0x0e, 0x32, 0xa1, 0xe2, 0x20, - 0xe4, 0xa8, 0xc0, 0xf6, 0x71, 0xf3, 0x73, 0xdc, 0xd9, 0x4f, 0xc1, 0xc2, 0x1b, 0xe8, 0xb7, 0x97, 0xf0, 0xf6, 0x67, - 0xfd, 0xf6, 0x0b, 0x0b, 0x7e, 0x29, 0x65, 0xe8, 0xbe, 0x36, 0xc5, 0x03, 0x35, 0x45, 0x29, 0x96, 0xc8, 0xa0, 0x21, - 0x73, 0xf3, 0x95, 0x98, 0x0d, 0x77, 0x4b, 0x20, 0x86, 0x12, 0x5d, 0xb9, 0xcf, 0xa3, 0x13, 0x24, 0xae, 0x6b, 0x92, - 0xc2, 0xc8, 0x25, 0x30, 0x11, 0xae, 0xf8, 0x96, 0x98, 0xb3, 0xdf, 0x06, 0x1b, 0xbc, 0x96, 0x77, 0x80, 0xf6, 0x1d, - 0x9b, 0xcd, 0xf9, 0xc5, 0x3e, 0x29, 0xfa, 0x40, 0xa6, 0x0d, 0x88, 0xb3, 0xf3, 0x76, 0x2f, 0xde, 0xe5, 0xbd, 0x18, - 0xa4, 0x7a, 0xae, 0x58, 0x0c, 0xf7, 0xaa, 0xf7, 0x16, 0xa3, 0x94, 0x26, 0x33, 0x79, 0x35, 0xf4, 0xba, 0x12, 0xbd, - 0xcd, 0x4d, 0x40, 0xb0, 0x67, 0x74, 0xe5, 0xa2, 0x6b, 0x59, 0x0a, 0x9a, 0x00, 0x44, 0x8f, 0xea, 0x2c, 0x47, 0x1c, - 0x87, 0xd9, 0x6c, 0x50, 0x3c, 0x62, 0xee, 0xda, 0x51, 0x71, 0x4c, 0xec, 0x2e, 0x13, 0x76, 0x00, 0x33, 0xe2, 0xf2, - 0x56, 0x47, 0x44, 0x87, 0x45, 0x7f, 0x1d, 0xdf, 0xfe, 0xe8, 0xb1, 0xcd, 0x8e, 0x0b, 0x1a, 0xa4, 0x36, 0xd6, 0xc3, - 0x6a, 0x2c, 0xa8, 0x0f, 0x3f, 0x6a, 0x2a, 0x95, 0xc5, 0xe6, 0x66, 0x59, 0x3f, 0xaa, 0x55, 0x3b, 0xb8, 0x76, 0x9a, - 0x72, 0xde, 0xcc, 0x06, 0xe1, 0x40, 0xc4, 0x04, 0x0a, 0xb4, 0xb4, 0xb2, 0x62, 0x80, 0x21, 0x65, 0x39, 0xca, 0xa7, - 0x90, 0x79, 0x71, 0x59, 0xea, 0xd4, 0x17, 0x19, 0x8f, 0x0c, 0xf1, 0xd4, 0x93, 0x8c, 0x15, 0x50, 0xb0, 0x5e, 0xea, - 0x25, 0xb4, 0x44, 0x80, 0xf9, 0x33, 0x95, 0x43, 0x23, 0x2c, 0x90, 0x28, 0x34, 0xcc, 0x12, 0x65, 0x7c, 0x16, 0x61, - 0x0c, 0xda, 0xfe, 0x49, 0x2d, 0xf6, 0x55, 0x28, 0xa3, 0xa3, 0x38, 0xcc, 0x87, 0x01, 0xd5, 0x2f, 0xa4, 0x04, 0x9b, - 0x86, 0xef, 0x81, 0x8d, 0x2a, 0xc7, 0x93, 0x04, 0xe1, 0xd3, 0x38, 0x67, 0xe4, 0x29, 0x6c, 0x48, 0x98, 0xa5, 0x69, - 0x1b, 0xa9, 0x76, 0x91, 0x19, 0x84, 0x72, 0x51, 0xf0, 0x1a, 0x67, 0x17, 0x59, 0xb8, 0xd2, 0x1a, 0xcc, 0x8f, 0x37, - 0x26, 0x40, 0xd9, 0xe5, 0x65, 0x26, 0x7c, 0xdc, 0x88, 0xec, 0x0d, 0x5d, 0x31, 0x1d, 0x28, 0xa4, 0x02, 0x27, 0x22, - 0x8b, 0x87, 0xce, 0x50, 0x68, 0x84, 0x03, 0x3a, 0x45, 0xce, 0x5d, 0x63, 0xd3, 0xe7, 0x03, 0xed, 0x1b, 0xa5, 0xa1, - 0x93, 0x80, 0x10, 0x10, 0xb8, 0x1b, 0xd6, 0x54, 0x3a, 0x48, 0x83, 0x84, 0x4a, 0xd1, 0xcf, 0x01, 0xfc, 0xc3, 0x48, - 0x52, 0x00, 0xec, 0x87, 0x6a, 0xa4, 0x88, 0xb2, 0x2c, 0x70, 0x01, 0x68, 0xae, 0x7d, 0x5c, 0x09, 0x5f, 0x18, 0xa8, - 0x30, 0x3d, 0xcd, 0xca, 0x4b, 0xa1, 0x44, 0x1e, 0xaf, 0x49, 0x59, 0x23, 0x99, 0x7c, 0x8a, 0x0e, 0x9f, 0xf2, 0xae, - 0x5f, 0x4b, 0x3c, 0x74, 0xc1, 0x53, 0x58, 0x56, 0xf5, 0xfc, 0x2a, 0xe4, 0xe4, 0x5c, 0x83, 0xae, 0x90, 0x42, 0x7f, - 0xc5, 0x49, 0xde, 0x7b, 0xe5, 0x57, 0xb5, 0xd4, 0x18, 0xca, 0xde, 0xaf, 0x6b, 0x86, 0xe5, 0xe5, 0xbc, 0x0a, 0x53, - 0x10, 0x70, 0x4b, 0x96, 0x04, 0x4b, 0xa9, 0x21, 0xc0, 0xc2, 0xf6, 0x48, 0x2b, 0x05, 0x79, 0xa9, 0xc3, 0x3b, 0x4f, - 0xc1, 0x0a, 0x30, 0x0e, 0xb5, 0x54, 0x32, 0x8d, 0x24, 0xbe, 0x54, 0xa2, 0xc0, 0x94, 0xfb, 0x23, 0xf0, 0x53, 0x9b, - 0x27, 0x5d, 0xe7, 0xae, 0x1f, 0xcf, 0x30, 0xb5, 0x87, 0x40, 0x8f, 0xbd, 0x3b, 0x60, 0x4a, 0xd4, 0x75, 0x58, 0x41, - 0x1c, 0x9a, 0xd5, 0x34, 0x0b, 0x98, 0x31, 0x6d, 0xd0, 0x92, 0x6d, 0xb0, 0xe5, 0x72, 0xb0, 0x8f, 0xc4, 0xf6, 0xac, - 0x56, 0x40, 0xe8, 0x1a, 0x34, 0x30, 0xe4, 0x2e, 0x15, 0x5a, 0x98, 0xf7, 0xba, 0x54, 0x84, 0xfb, 0x73, 0xc0, 0xa5, - 0x15, 0x9c, 0x79, 0x19, 0x0d, 0xbc, 0x1f, 0x1f, 0x27, 0x98, 0xf8, 0x82, 0x58, 0x81, 0x1d, 0x1c, 0x74, 0x9a, 0x4d, - 0x81, 0x53, 0x71, 0x91, 0x32, 0x58, 0x56, 0x94, 0xda, 0xf0, 0x43, 0x8a, 0x6c, 0xdd, 0xe5, 0x81, 0xee, 0x42, 0x2c, - 0x80, 0x9d, 0x7e, 0xc3, 0xc8, 0xb7, 0xac, 0x97, 0x01, 0x83, 0x53, 0xad, 0x71, 0x10, 0xf8, 0xcd, 0xcd, 0x64, 0x58, - 0xa6, 0xc4, 0x76, 0x4d, 0x56, 0x17, 0x90, 0xc3, 0x50, 0x4d, 0xdc, 0x41, 0x58, 0x2a, 0x7b, 0xbc, 0x28, 0x67, 0xb8, - 0x5c, 0xca, 0x42, 0x6e, 0x9e, 0x57, 0xd3, 0x7c, 0x6e, 0xa5, 0xd9, 0x74, 0xbc, 0x15, 0x5f, 0x14, 0xfc, 0x03, 0x27, - 0x96, 0x56, 0x3d, 0xa5, 0x56, 0x78, 0x94, 0xb9, 0x25, 0xeb, 0x94, 0xd4, 0xea, 0xba, 0x81, 0x6a, 0x84, 0xa7, 0x69, - 0xd8, 0x08, 0x84, 0x98, 0xe0, 0xe2, 0xd7, 0x4d, 0x26, 0xa6, 0xbd, 0x25, 0xa4, 0x8e, 0xb0, 0x7b, 0x28, 0x27, 0xb8, - 0xab, 0x79, 0xf6, 0x79, 0x38, 0xbf, 0x9a, 0xb9, 0xf7, 0x0c, 0xe6, 0x7e, 0x1c, 0x72, 0x83, 0xd1, 0x63, 0x99, 0xf0, - 0x23, 0x63, 0x1f, 0xb9, 0xaa, 0x7a, 0x72, 0x12, 0x56, 0x22, 0x4b, 0x3c, 0x19, 0x47, 0x1d, 0xc6, 0xa9, 0x68, 0x4d, - 0x90, 0x5d, 0x5e, 0x16, 0xe6, 0x5e, 0xa0, 0xa0, 0xa9, 0xc7, 0xeb, 0x71, 0xda, 0x8a, 0x9d, 0x8d, 0x48, 0xe4, 0xde, - 0xab, 0x5a, 0x24, 0xb2, 0xe2, 0x73, 0x1c, 0xe9, 0x8a, 0x83, 0xdc, 0x27, 0x27, 0xab, 0x9b, 0x54, 0xe8, 0x16, 0x8d, - 0xb6, 0xb1, 0x47, 0xf5, 0x81, 0xa4, 0x9e, 0x51, 0x81, 0x55, 0x8d, 0x7d, 0xf7, 0x6e, 0x47, 0xa4, 0x5b, 0x2a, 0xc5, - 0x06, 0x4b, 0x0b, 0xa3, 0x19, 0xa3, 0x60, 0x50, 0x52, 0x64, 0xa0, 0x46, 0xf9, 0x15, 0x82, 0x61, 0x8f, 0x1a, 0x80, - 0xe2, 0x5c, 0x5f, 0xfd, 0xb8, 0x94, 0x6c, 0x21, 0x20, 0x71, 0x97, 0x0c, 0xc4, 0x9a, 0x60, 0x66, 0xe4, 0x93, 0xf7, - 0xc0, 0x79, 0x03, 0x86, 0x0e, 0x01, 0xf8, 0x05, 0x62, 0xd3, 0x83, 0x89, 0x6d, 0x13, 0x51, 0xf4, 0xd9, 0xc0, 0x73, - 0x00, 0x76, 0x5e, 0x85, 0x46, 0xdf, 0x55, 0x29, 0x60, 0xc8, 0x06, 0x6e, 0xc0, 0xaa, 0xb0, 0xdc, 0xde, 0x73, 0x70, - 0x1b, 0xe0, 0xf5, 0x99, 0x6c, 0xbe, 0x81, 0x79, 0x82, 0xd5, 0xd9, 0x85, 0x5f, 0x59, 0xd6, 0xe2, 0xdc, 0xe9, 0xa0, - 0x51, 0xaf, 0x28, 0x21, 0x6a, 0xf7, 0xb1, 0xf6, 0x39, 0x46, 0x58, 0xc4, 0xfb, 0x2b, 0x7c, 0xd7, 0xe3, 0x96, 0x7b, - 0x1a, 0x2d, 0xc2, 0x74, 0x95, 0x34, 0x06, 0x25, 0xeb, 0x7e, 0x32, 0xe2, 0x5e, 0xee, 0x8b, 0x58, 0x70, 0x85, 0x23, - 0xab, 0x42, 0x8a, 0x0d, 0x24, 0xe9, 0x69, 0x8f, 0x0e, 0xd8, 0x37, 0x9a, 0xbd, 0x80, 0x32, 0xef, 0x2b, 0x52, 0x49, - 0x48, 0x69, 0x76, 0x43, 0x24, 0x09, 0x6b, 0x45, 0x9e, 0x3a, 0xef, 0x3b, 0xda, 0xe7, 0x56, 0x12, 0xc1, 0x08, 0x4e, - 0xc2, 0x74, 0xac, 0x3c, 0x68, 0x0a, 0x70, 0x15, 0x1d, 0x31, 0x7d, 0x13, 0x90, 0xdf, 0x0c, 0xe4, 0xf6, 0x4a, 0x72, - 0x6d, 0xae, 0x61, 0x78, 0x82, 0x04, 0xab, 0x22, 0x11, 0x78, 0x44, 0x8d, 0x09, 0xc9, 0xeb, 0x3c, 0x0f, 0x30, 0xe1, - 0x6b, 0x7b, 0x13, 0x00, 0xca, 0xc9, 0x55, 0x71, 0x56, 0x02, 0xdd, 0x80, 0xe5, 0xfa, 0x38, 0x35, 0x2a, 0x12, 0x17, - 0x37, 0xa6, 0xab, 0x5b, 0xfa, 0x33, 0xb4, 0x9c, 0xc9, 0x10, 0xd3, 0x41, 0x10, 0x90, 0xa9, 0x8f, 0x99, 0x23, 0x64, - 0xae, 0xb0, 0x3e, 0xe7, 0x4e, 0x6d, 0xea, 0x1e, 0xa3, 0x6e, 0x9e, 0xa4, 0x16, 0xaf, 0xd3, 0xa6, 0x94, 0x88, 0x49, - 0x89, 0x39, 0x13, 0xa9, 0xd8, 0x4c, 0x89, 0x3b, 0xb7, 0xbe, 0xd1, 0x42, 0xda, 0x68, 0x33, 0x91, 0x83, 0xcd, 0x2a, - 0x79, 0x4f, 0x60, 0x3c, 0x17, 0x84, 0x2f, 0x91, 0xb1, 0x96, 0x63, 0xe6, 0x98, 0x08, 0x56, 0x2f, 0xa6, 0x22, 0x7f, - 0xe7, 0xe8, 0x34, 0x7b, 0x83, 0x1e, 0xa4, 0xde, 0x40, 0x62, 0xd6, 0xc4, 0x77, 0x21, 0x0d, 0x75, 0x84, 0x40, 0x65, - 0x54, 0xcb, 0x74, 0x9c, 0x58, 0x85, 0x6f, 0x04, 0x5f, 0xbd, 0xd5, 0xc7, 0xf9, 0xc6, 0x73, 0x63, 0x35, 0x82, 0x18, - 0xbc, 0x85, 0x7c, 0xe8, 0x49, 0x11, 0x0e, 0x84, 0xcb, 0x37, 0x37, 0x7b, 0xf9, 0x2e, 0xaf, 0x42, 0x24, 0x15, 0x8c, - 0x31, 0x66, 0x14, 0xe3, 0x9e, 0xa8, 0xa9, 0xc5, 0x1c, 0x06, 0x96, 0xad, 0xc3, 0x1c, 0x0f, 0x00, 0xa0, 0xa5, 0x29, - 0xbd, 0x6a, 0x2a, 0x54, 0x9e, 0xe7, 0x12, 0x3e, 0xd5, 0x21, 0xaa, 0x6a, 0xfc, 0x76, 0x7d, 0x06, 0x0a, 0xc1, 0x7d, - 0xa7, 0xe3, 0xe1, 0x21, 0x04, 0xac, 0xa2, 0x90, 0x05, 0x7a, 0x83, 0xf6, 0xaa, 0x44, 0x28, 0x66, 0x4e, 0xd6, 0x63, - 0x86, 0x93, 0x0a, 0xb6, 0x50, 0x09, 0x4b, 0xa5, 0x05, 0x7e, 0xb5, 0x11, 0x9a, 0xa7, 0x8c, 0x7b, 0xaf, 0x2a, 0x9c, - 0x41, 0x7f, 0x30, 0x6f, 0x95, 0x51, 0xdf, 0xae, 0x9c, 0xc8, 0x54, 0x60, 0xe2, 0x66, 0x96, 0xda, 0xef, 0x97, 0x75, - 0xda, 0xcf, 0x2b, 0xe4, 0x3e, 0x27, 0xcd, 0xd7, 0xb9, 0x85, 0xe6, 0x93, 0xe1, 0x7e, 0xa5, 0xfc, 0xd0, 0xc2, 0xa8, - 0x29, 0xbf, 0xbc, 0xae, 0xfc, 0x0a, 0x4f, 0x85, 0xb7, 0xfa, 0x5d, 0x14, 0xba, 0xa8, 0xcf, 0xc1, 0x10, 0xd2, 0x8f, - 0xe0, 0x1a, 0x1a, 0x3c, 0x28, 0x92, 0xc5, 0x62, 0xed, 0x82, 0xb8, 0x3e, 0xe6, 0x54, 0x3b, 0x94, 0x31, 0x46, 0x3c, - 0x2d, 0x39, 0x48, 0x32, 0x38, 0x18, 0xbf, 0x81, 0x01, 0x31, 0x29, 0x09, 0xe9, 0x10, 0x3a, 0x6b, 0x33, 0x11, 0x95, - 0xbb, 0x78, 0xb3, 0x71, 0x59, 0x53, 0x28, 0xc2, 0x4e, 0x30, 0x53, 0x29, 0x15, 0x04, 0xd2, 0xe4, 0xbb, 0xd3, 0xa9, - 0x05, 0x43, 0x0b, 0xd7, 0x54, 0x40, 0x5e, 0xdb, 0xf5, 0xa0, 0xc9, 0x7b, 0x8a, 0xa1, 0xaf, 0x53, 0x23, 0x5e, 0x66, - 0xf0, 0x35, 0x6c, 0xfe, 0x9a, 0x28, 0xc9, 0x43, 0x26, 0x62, 0xaf, 0xe0, 0x13, 0x21, 0x9b, 0x82, 0x9d, 0x09, 0xf4, - 0x43, 0xbb, 0xb2, 0x97, 0xee, 0x16, 0x95, 0x4b, 0x8b, 0xc6, 0x56, 0xa2, 0x66, 0xcd, 0x0f, 0xe3, 0xcd, 0x14, 0xf6, - 0xb3, 0x47, 0x09, 0x04, 0xa4, 0xa9, 0x9c, 0xa4, 0x9a, 0xf7, 0x30, 0x1d, 0x02, 0x48, 0xb0, 0xfb, 0x09, 0x2c, 0xf4, - 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, 0x73, 0xd0, 0x9a, 0x73, 0xd2, 0x7c, 0x73, 0xd4, 0xda, 0x9b, 0xca, 0x7a, - 0xc6, 0xec, 0x00, 0xdb, 0x76, 0x37, 0x8b, 0xc3, 0x74, 0xb3, 0x33, 0x34, 0x04, 0x17, 0x1e, 0xff, 0x27, 0x25, 0xa6, - 0x81, 0xe4, 0x52, 0x37, 0x7e, 0x42, 0x1d, 0x86, 0xff, 0x2d, 0x49, 0x01, 0x0f, 0x6a, 0xab, 0xb1, 0xe2, 0xdc, 0x2b, - 0x8e, 0x92, 0xcb, 0xaa, 0xda, 0xd5, 0x12, 0x34, 0x74, 0x23, 0x19, 0x13, 0xc5, 0x3c, 0x27, 0x00, 0x46, 0xb1, 0xf9, - 0x53, 0xa6, 0x93, 0xbc, 0x7f, 0x59, 0x9b, 0xda, 0xed, 0xfb, 0x7e, 0x94, 0x9f, 0xd0, 0x91, 0x8a, 0xca, 0xe6, 0x24, - 0xe6, 0xdf, 0x16, 0x60, 0x9a, 0x13, 0x1f, 0xea, 0xb9, 0x86, 0xa1, 0x00, 0x5f, 0xd9, 0x50, 0x6a, 0xb6, 0x97, 0xbf, - 0x77, 0xb6, 0xfb, 0x92, 0x28, 0x82, 0x05, 0x1a, 0x74, 0xb9, 0x02, 0x5f, 0xc0, 0x32, 0xb8, 0x25, 0xfd, 0xf4, 0xa6, - 0xbf, 0x0a, 0x3e, 0x63, 0xff, 0x0b, 0x40, 0xab, 0x02, 0x03, 0xca, 0x9d, 0xa6, 0x61, 0x25, 0xc4, 0x25, 0x2a, 0xcc, - 0x2a, 0xce, 0x1f, 0xd7, 0x79, 0xdd, 0xb4, 0x2c, 0x31, 0x28, 0x3f, 0x77, 0x0d, 0x37, 0xbe, 0xd7, 0xc8, 0x1f, 0xdf, - 0x7b, 0x0e, 0xba, 0x9d, 0x48, 0x7b, 0xf7, 0x6e, 0x7e, 0x87, 0x2c, 0x34, 0xbc, 0x17, 0x36, 0x87, 0xb6, 0x48, 0x97, - 0x5c, 0x3d, 0x63, 0x31, 0xde, 0x16, 0xa1, 0x32, 0x7c, 0xc0, 0x82, 0x39, 0x60, 0x08, 0x1e, 0x3b, 0x95, 0xc9, 0x67, - 0xd8, 0x68, 0x8a, 0x5d, 0x73, 0x61, 0xf0, 0x81, 0xaa, 0x2c, 0x24, 0x2f, 0xd6, 0xc9, 0xf6, 0xec, 0x14, 0x9e, 0x5f, - 0xc6, 0x05, 0x50, 0x07, 0xd0, 0xaf, 0xa8, 0x2c, 0x36, 0x90, 0x8b, 0x9b, 0xb2, 0xd6, 0x2b, 0x1a, 0x8f, 0xaf, 0xed, - 0xc2, 0xea, 0x0a, 0x7c, 0x1a, 0xa5, 0xe3, 0x44, 0x4c, 0x62, 0x26, 0x55, 0xae, 0xc9, 0xb5, 0xd1, 0xbd, 0xb4, 0x45, - 0xf3, 0x5c, 0x48, 0xf0, 0x8a, 0xc0, 0x0d, 0xa1, 0xaf, 0xf4, 0xe5, 0x7a, 0x03, 0x05, 0x8f, 0xda, 0x9b, 0x8b, 0x60, - 0x62, 0xe2, 0x31, 0x43, 0x6a, 0xfa, 0x75, 0x38, 0x15, 0xdf, 0xfc, 0xb6, 0xe2, 0xf0, 0xeb, 0x9c, 0xb1, 0x86, 0x02, - 0x20, 0x3e, 0x79, 0x70, 0xb5, 0x9b, 0xf4, 0x4a, 0x69, 0x07, 0xa5, 0x11, 0xe2, 0xdb, 0x0a, 0x5f, 0x77, 0xa9, 0xf8, - 0xca, 0x55, 0xf7, 0xbe, 0x8e, 0x99, 0x71, 0xc1, 0xe8, 0x39, 0x9f, 0x25, 0x8d, 0x6b, 0x37, 0x74, 0x57, 0xe7, 0x47, - 0xef, 0x07, 0x99, 0xb7, 0x70, 0x0c, 0x6c, 0x72, 0xcc, 0x9c, 0xe7, 0xde, 0x6b, 0xe3, 0x44, 0xf9, 0x5b, 0xf3, 0x88, - 0x57, 0x0e, 0xb3, 0xee, 0x24, 0xf9, 0xdb, 0xc1, 0xb7, 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, 0x74, 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, 0x23, 0x5b, 0x3f, - 0x78, 0x73, 0xf7, 0x0a, 0x58, 0x3e, 0x02, 0x76, 0x1f, 0x99, 0xd3, 0x18, 0xaa, 0xda, 0xc0, 0x3f, 0xac, 0x1f, 0x6c, - 0xdd, 0x1e, 0xfe, 0x61, 0xf0, 0x43, 0x70, 0x6d, 0x63, 0x63, 0x1b, 0x6f, 0xd7, 0x12, 0x41, 0x5e, 0xe1, 0x81, 0x3e, - 0x5e, 0x7d, 0x14, 0xb4, 0x5c, 0x27, 0xb6, 0x07, 0x0e, 0x85, 0xad, 0x41, 0xbe, 0x49, 0x99, 0x34, 0x5a, 0x14, 0x3c, - 0x9b, 0xc9, 0x19, 0x0a, 0x79, 0xcd, 0xc7, 0x41, 0xdb, 0x11, 0xfe, 0x06, 0x4e, 0xed, 0x78, 0x79, 0xf9, 0x09, 0xfa, - 0x80, 0xa7, 0x2b, 0xa5, 0xa9, 0x88, 0x53, 0xca, 0x2d, 0xba, 0x5a, 0xe7, 0xc1, 0x48, 0x71, 0x31, 0x45, 0xa5, 0xe3, - 0x2e, 0xaf, 0x9d, 0x8d, 0x9c, 0xfe, 0x12, 0xaf, 0x2e, 0xd2, 0xe5, 0x23, 0x91, 0xad, 0x5a, 0x7a, 0x2f, 0xf4, 0xe9, - 0xb6, 0x3d, 0x63, 0x7c, 0x9a, 0x8d, 0xe9, 0x60, 0xc6, 0xc7, 0x89, 0xf0, 0xfa, 0xc4, 0x58, 0xdf, 0x2d, 0x02, 0xd3, - 0xcd, 0xb1, 0xc9, 0x0f, 0xc7, 0xeb, 0xcd, 0x66, 0x8d, 0x3b, 0x78, 0xe3, 0x3c, 0x71, 0x96, 0x25, 0x46, 0x54, 0x96, - 0x1a, 0x1e, 0xd0, 0x0a, 0x71, 0xf3, 0x9e, 0x09, 0x8c, 0xcb, 0x2e, 0x48, 0x6a, 0xbb, 0x81, 0xc0, 0xc5, 0x9e, 0xc4, - 0x2c, 0x19, 0xdb, 0x1e, 0x94, 0x07, 0xfa, 0x62, 0x34, 0xdd, 0x02, 0xa6, 0xe5, 0xb5, 0xb3, 0xb3, 0xd4, 0xf6, 0xaa, - 0xa9, 0x02, 0x98, 0x25, 0xcb, 0xe3, 0x13, 0x64, 0xdd, 0x6f, 0xa0, 0x8b, 0x18, 0x30, 0x36, 0xae, 0xcc, 0xb9, 0xcb, - 0x75, 0x2b, 0xe2, 0x1b, 0x4d, 0xa4, 0x49, 0x7d, 0x48, 0x7d, 0x87, 0x61, 0xad, 0xae, 0x72, 0x90, 0xc0, 0x3d, 0xf2, - 0x6e, 0x89, 0x4b, 0x4f, 0x9f, 0x59, 0x4c, 0xaa, 0xf4, 0x2d, 0x75, 0x2d, 0xae, 0x19, 0xf6, 0x8a, 0x07, 0x60, 0x7f, - 0x60, 0xdc, 0x22, 0x16, 0xf1, 0x76, 0x5e, 0x4b, 0x61, 0x6d, 0xcc, 0x81, 0xe6, 0x86, 0x1b, 0xbc, 0x60, 0xd5, 0x9a, - 0x81, 0x19, 0x66, 0x9c, 0x91, 0xfc, 0x66, 0xdc, 0xab, 0x9a, 0x38, 0x72, 0x15, 0x40, 0xf4, 0x2d, 0xe9, 0x92, 0x1c, - 0x5e, 0xc9, 0x72, 0xd5, 0x19, 0xf2, 0xaf, 0xb0, 0xce, 0x7a, 0x71, 0x02, 0x66, 0xd2, 0x94, 0x97, 0x98, 0x98, 0x22, - 0x2e, 0x37, 0xcb, 0x98, 0xa7, 0xe9, 0xb3, 0x68, 0x07, 0x27, 0x37, 0x12, 0x38, 0x62, 0xdf, 0x58, 0x86, 0x66, 0xc2, - 0x46, 0x4c, 0xa4, 0x51, 0x29, 0x25, 0x7c, 0x20, 0x97, 0x5a, 0xf2, 0x97, 0xb9, 0xbc, 0xfa, 0x72, 0x9b, 0xe0, 0x80, - 0xbc, 0x06, 0x96, 0x43, 0xe3, 0xb8, 0x65, 0x20, 0x11, 0x8b, 0x01, 0x31, 0x6a, 0x55, 0xae, 0x26, 0xa3, 0x3a, 0x99, - 0xaf, 0x90, 0x0b, 0x15, 0x79, 0x70, 0x4b, 0xa0, 0xe4, 0xcf, 0x31, 0x75, 0x30, 0x2b, 0xb5, 0x9b, 0x16, 0x9b, 0x24, - 0xef, 0x99, 0x01, 0xc9, 0xf5, 0xd7, 0xf0, 0xd0, 0xf8, 0xc5, 0x2b, 0x73, 0x4a, 0xf8, 0xa2, 0x8c, 0xa5, 0xa5, 0x31, - 0x97, 0xfe, 0x83, 0xbc, 0x4f, 0x2b, 0x01, 0xfb, 0x15, 0xc4, 0x94, 0x81, 0x4b, 0x6c, 0x5c, 0x90, 0x94, 0xd7, 0xf2, - 0x94, 0xdd, 0xd7, 0x50, 0xbe, 0x2b, 0x26, 0x5d, 0xa5, 0xb2, 0xae, 0xb0, 0xea, 0x7e, 0x5d, 0xb0, 0xfc, 0x62, 0x9f, - 0x61, 0x6e, 0x32, 0x1a, 0x64, 0x2b, 0x66, 0x36, 0xe5, 0x57, 0x7b, 0xd7, 0x7e, 0xe5, 0xa1, 0xa4, 0x43, 0xb5, 0x4a, - 0x37, 0xaf, 0xdc, 0x70, 0x8c, 0x1b, 0x37, 0x1c, 0x01, 0x6c, 0x0c, 0x3b, 0x55, 0xa4, 0xd6, 0xf9, 0xef, 0xab, 0xe1, - 0x27, 0xda, 0x6b, 0x43, 0xbd, 0xeb, 0x86, 0x6b, 0xd3, 0xd3, 0xaf, 0x41, 0xd5, 0xc8, 0x12, 0xba, 0x0e, 0x55, 0x4c, - 0x46, 0xa2, 0xc4, 0x74, 0x95, 0xf2, 0xa8, 0xaf, 0x11, 0xe7, 0x20, 0x6e, 0x28, 0x7f, 0xf1, 0x2f, 0xe1, 0xc5, 0x51, - 0x80, 0x46, 0xd4, 0x72, 0x92, 0xa5, 0xbc, 0x35, 0x89, 0x66, 0x71, 0x72, 0x11, 0x2c, 0xe2, 0xd6, 0x2c, 0x4b, 0xb3, - 0x62, 0x0e, 0x5c, 0xe9, 0x15, 0x17, 0x60, 0xc3, 0xcf, 0x5a, 0x8b, 0xd8, 0x7b, 0xce, 0x92, 0x53, 0xc6, 0xe3, 0x51, - 0xe4, 0xd9, 0x7b, 0x39, 0x88, 0x07, 0xeb, 0x75, 0x94, 0xe7, 0xd9, 0x99, 0xed, 0xbd, 0xcb, 0x8e, 0x81, 0x69, 0xbd, - 0x37, 0xe7, 0x17, 0x27, 0x2c, 0xf5, 0xde, 0x1f, 0x2f, 0x52, 0xbe, 0xf0, 0x8a, 0x28, 0x2d, 0x5a, 0x05, 0xcb, 0xe3, - 0x09, 0xa8, 0x89, 0x24, 0xcb, 0x5b, 0x98, 0xff, 0x3c, 0x63, 0x41, 0x12, 0x9f, 0x4c, 0xb9, 0x35, 0x8e, 0xf2, 0x4f, - 0xbd, 0x56, 0x6b, 0x9e, 0xc7, 0xb3, 0x28, 0xbf, 0x68, 0x51, 0x8b, 0xe0, 0x8b, 0xf6, 0x76, 0xf4, 0xe5, 0xe4, 0x7e, - 0x8f, 0xe7, 0xd0, 0x37, 0x46, 0x2a, 0x06, 0x20, 0x7c, 0xac, 0xed, 0x9d, 0xf6, 0xac, 0xb8, 0x23, 0x4e, 0x94, 0xa2, - 0x94, 0x97, 0x47, 0xde, 0x19, 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, - 0xad, 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, 0xab, 0xab, 0x3e, 0x62, 0xf3, 0xdd, 0x8d, 0x5f, 0xaa, 0xf1, 0x2e, 0x54, 0xde, 0xa0, 0x50, 0x11, 0xea, 0x9b, - 0x2d, 0x98, 0xf1, 0x96, 0xf7, 0x3b, 0xfa, 0xa0, 0x6a, 0xf0, 0x1d, 0x23, 0xad, 0x17, 0x70, 0xcf, 0xcc, 0x05, 0xea, - 0xa5, 0x7d, 0x0c, 0x49, 0xb5, 0x5a, 0x2e, 0xe8, 0x0d, 0x86, 0x21, 0x24, 0x3a, 0x10, 0x74, 0xf2, 0x41, 0x41, 0xdf, - 0xd4, 0xc8, 0xdc, 0xa0, 0x70, 0x32, 0x17, 0xb6, 0x7c, 0xa6, 0xe5, 0x3a, 0x28, 0x69, 0xf0, 0xb2, 0xbf, 0x62, 0xb2, - 0x01, 0x48, 0xef, 0x4a, 0xd2, 0x7e, 0xaf, 0x4f, 0x9e, 0x94, 0xc7, 0x97, 0x8d, 0x88, 0x70, 0xe0, 0xea, 0xf3, 0x29, - 0xba, 0xdd, 0xfa, 0x3b, 0x31, 0x46, 0x46, 0xcd, 0x96, 0xed, 0x0e, 0x98, 0x4e, 0xca, 0xc2, 0xe4, 0x33, 0x56, 0xe2, - 0x28, 0x5f, 0xb3, 0xf0, 0x7b, 0x0c, 0xbc, 0xb2, 0x50, 0x78, 0x69, 0xca, 0x47, 0x9b, 0x5d, 0x77, 0xfb, 0x1f, 0x16, - 0x3c, 0xa6, 0x64, 0xe7, 0xc3, 0xe1, 0xbf, 0xc1, 0x67, 0x70, 0x34, 0x06, 0x45, 0xb6, 0xc8, 0x47, 0x14, 0x04, 0x5c, - 0x0d, 0x26, 0xd8, 0xa4, 0xcb, 0x6d, 0x8f, 0x69, 0x15, 0x82, 0x1e, 0x76, 0xed, 0xfb, 0x09, 0x9c, 0xce, 0x57, 0xc4, - 0xf5, 0x05, 0x19, 0x40, 0x51, 0x10, 0xa2, 0x56, 0x1c, 0x53, 0x2a, 0x1d, 0x5d, 0xed, 0xf4, 0x17, 0x69, 0x0c, 0x42, - 0xf4, 0x63, 0x3c, 0xa6, 0x2b, 0x2d, 0xf1, 0x98, 0xce, 0x38, 0x5a, 0x94, 0xd3, 0x84, 0x41, 0x73, 0x28, 0x90, 0xb4, - 0xc5, 0x67, 0x99, 0x23, 0x63, 0xb7, 0x6c, 0x3c, 0xa7, 0x30, 0xb4, 0xf0, 0x38, 0x9b, 0x45, 0x71, 0x1a, 0xe0, 0xa7, - 0x47, 0x3c, 0x3d, 0x62, 0x80, 0x5d, 0x3c, 0xf8, 0xa9, 0xc8, 0xdc, 0x71, 0xfd, 0x5f, 0x40, 0x44, 0x51, 0xff, 0x52, - 0xba, 0x78, 0x1a, 0x2e, 0x75, 0x82, 0x5c, 0x2f, 0x05, 0xb1, 0xc6, 0x95, 0x39, 0xcc, 0x28, 0x84, 0xb2, 0xcb, 0xe9, - 0xc7, 0xa0, 0xd5, 0x09, 0x3a, 0xda, 0x2b, 0xae, 0x5d, 0x74, 0x15, 0x69, 0x32, 0xf2, 0xb2, 0x24, 0xc1, 0xa0, 0x9f, - 0x05, 0x9c, 0xd5, 0xbb, 0x86, 0xd5, 0x93, 0x2c, 0x8f, 0xb1, 0xa1, 0x93, 0xd4, 0xa9, 0x01, 0x41, 0xc7, 0x0c, 0x57, - 0x4c, 0xe5, 0x96, 0x11, 0x31, 0xe1, 0x63, 0x92, 0x0d, 0xf5, 0x6b, 0x4a, 0x78, 0x25, 0xa9, 0x9e, 0x5d, 0xa5, 0xb3, - 0xa4, 0x8f, 0x76, 0x85, 0x30, 0xb1, 0x88, 0xc7, 0x42, 0x1b, 0x76, 0xb7, 0x6d, 0xfd, 0x79, 0x04, 0x54, 0xfa, 0x14, - 0xda, 0x1b, 0x4b, 0x47, 0xe5, 0xec, 0xe7, 0x30, 0xd7, 0x9e, 0x50, 0xa8, 0x74, 0xd9, 0xdf, 0xee, 0x6f, 0x2c, 0x79, - 0xb9, 0xbb, 0x25, 0x7a, 0xf7, 0x8f, 0xca, 0x82, 0x74, 0x9f, 0x19, 0xa3, 0xab, 0xa6, 0x10, 0x75, 0x30, 0x2c, 0x65, - 0x06, 0xe3, 0xb8, 0xb9, 0xb6, 0xe7, 0x44, 0x30, 0x5a, 0xb2, 0x5d, 0x81, 0x99, 0x50, 0x51, 0x0e, 0xdb, 0x5d, 0xe7, - 0xba, 0x14, 0x22, 0xbd, 0xa3, 0xb7, 0x0a, 0xc5, 0x11, 0x42, 0x30, 0xd8, 0x58, 0xc6, 0x65, 0xb8, 0xb1, 0x64, 0xe9, - 0x28, 0x1b, 0xb3, 0xf7, 0xef, 0x5e, 0xe0, 0x75, 0x86, 0x2c, 0x45, 0xb9, 0x97, 0xb9, 0xe5, 0x11, 0x18, 0x42, 0x08, - 0x69, 0xae, 0xbe, 0x26, 0x03, 0xc0, 0x88, 0x98, 0x8e, 0x45, 0xa3, 0x22, 0x28, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x10, - 0x1c, 0x4e, 0x2c, 0x00, 0x53, 0x91, 0x7a, 0x31, 0xc0, 0x4f, 0xb4, 0xee, 0xc3, 0x40, 0xbb, 0x5b, 0xa2, 0x11, 0xe0, - 0x9a, 0x23, 0x1a, 0x15, 0xaa, 0x98, 0xfd, 0x63, 0xa2, 0x3b, 0x8e, 0x4f, 0x35, 0x39, 0x29, 0x15, 0xba, 0xbf, 0x9b, - 0x44, 0xc7, 0x2c, 0x81, 0x21, 0x8b, 0xcb, 0xcb, 0x36, 0x8c, 0x24, 0x5e, 0xad, 0xdd, 0x38, 0x9d, 0x2f, 0xe4, 0x57, - 0xbd, 0x60, 0xe2, 0x0e, 0x1e, 0xb4, 0xe2, 0xa5, 0x83, 0x81, 0x3a, 0x39, 0x0c, 0xe4, 0x00, 0x00, 0x22, 0x1d, 0x5a, - 0x20, 0x74, 0x15, 0xab, 0x40, 0x69, 0x3c, 0x5e, 0x2d, 0x83, 0xdd, 0x39, 0xc7, 0xd2, 0x14, 0x9e, 0x67, 0x71, 0x8a, - 0x8f, 0x05, 0x3e, 0x46, 0xe7, 0xf8, 0x98, 0xc1, 0xa3, 0xc6, 0x3d, 0x2f, 0xed, 0x7f, 0xd7, 0x55, 0xc9, 0xe4, 0x0a, - 0x58, 0x9a, 0x00, 0xd9, 0xe5, 0x25, 0xa8, 0x17, 0x4d, 0x82, 0xdd, 0x2d, 0x20, 0x16, 0x72, 0x8f, 0xf8, 0xc6, 0x0b, - 0x33, 0xc9, 0xc8, 0x8a, 0x79, 0x4b, 0x94, 0x5b, 0xa4, 0xc4, 0x43, 0xf0, 0xf1, 0x72, 0xa7, 0x61, 0xab, 0x78, 0x32, - 0x9b, 0xe5, 0x09, 0xbe, 0xb8, 0xb6, 0x25, 0x3e, 0xc2, 0x21, 0x88, 0x42, 0x8f, 0x88, 0xa1, 0x2e, 0xe3, 0xf2, 0xf3, - 0x3a, 0x71, 0x68, 0xe3, 0x2c, 0x60, 0x2e, 0xa2, 0xd2, 0xe1, 0x51, 0x9c, 0x88, 0xc6, 0x6b, 0xf0, 0x69, 0xa4, 0x25, - 0x12, 0x3a, 0xbb, 0x5b, 0x15, 0x6c, 0x00, 0xfc, 0x48, 0x5c, 0xea, 0x76, 0x44, 0xca, 0xa5, 0x2d, 0xca, 0xe9, 0xa8, - 0x5e, 0x6e, 0x73, 0x19, 0x48, 0x16, 0xa1, 0x79, 0x8d, 0x2a, 0xa5, 0x48, 0xda, 0x93, 0x28, 0x5d, 0xd7, 0x14, 0xa0, - 0x9f, 0x33, 0x36, 0xf6, 0x6c, 0x0b, 0xe4, 0xab, 0x78, 0xfe, 0x98, 0xb0, 0x53, 0x26, 0x3f, 0xcc, 0xa2, 0x07, 0xd1, - 0x95, 0x23, 0xb0, 0x00, 0xb8, 0xbc, 0x33, 0x2a, 0xd9, 0x53, 0xe1, 0x28, 0x29, 0x51, 0x47, 0xc4, 0xb3, 0x8d, 0x41, - 0x9b, 0x73, 0xb4, 0xeb, 0xc3, 0x7a, 0xa0, 0x93, 0x6c, 0x5b, 0xc0, 0x4b, 0x66, 0xe3, 0xcd, 0xc8, 0xc1, 0x00, 0xc7, - 0x39, 0x36, 0x4d, 0x59, 0x51, 0xac, 0x03, 0x0b, 0x9c, 0x60, 0xcf, 0xae, 0x9a, 0xd8, 0xb5, 0x0e, 0x00, 0x40, 0x77, - 0x67, 0x47, 0x4c, 0x0b, 0x15, 0x6c, 0x32, 0x81, 0x8d, 0x87, 0x1a, 0x23, 0xe1, 0x18, 0xf6, 0x0f, 0xfb, 0xf6, 0x6b, - 0xd8, 0xe3, 0x36, 0xf8, 0x57, 0xae, 0x3e, 0xc0, 0xa5, 0xe9, 0x95, 0x10, 0x32, 0xe6, 0x10, 0x9d, 0x8d, 0x61, 0xf4, - 0x93, 0x81, 0x54, 0x36, 0xfa, 0xb4, 0x06, 0x27, 0xe0, 0xc2, 0x0d, 0x11, 0x40, 0x6e, 0xc8, 0x56, 0xfb, 0x5f, 0xff, - 0xe9, 0x7f, 0xfd, 0x37, 0x18, 0x9b, 0xfa, 0xb9, 0xa5, 0x75, 0x75, 0xab, 0xff, 0x09, 0xad, 0x16, 0xe9, 0x0d, 0xed, - 0xfe, 0xfa, 0x0f, 0xff, 0x1d, 0x9a, 0xd1, 0x45, 0x23, 0x90, 0x59, 0x04, 0xd1, 0x08, 0x6d, 0xbb, 0xcf, 0x02, 0xa9, - 0x36, 0xc8, 0x95, 0x33, 0xfd, 0x23, 0x82, 0x5d, 0xf0, 0x6c, 0x7e, 0x2d, 0x38, 0x08, 0xf5, 0x28, 0xc9, 0x0a, 0xa6, - 0xe1, 0x11, 0x72, 0xfe, 0xf3, 0x00, 0xa2, 0xb9, 0xe6, 0xb0, 0x9b, 0x0a, 0x4b, 0x8f, 0x23, 0x56, 0x68, 0xdd, 0x38, - 0x8d, 0x05, 0x2c, 0x18, 0x27, 0x74, 0x28, 0x5c, 0x02, 0x4b, 0x26, 0x9e, 0xe0, 0x81, 0x04, 0x8f, 0x5b, 0xff, 0x78, - 0xd9, 0xfa, 0xc1, 0x34, 0xc3, 0x89, 0xb1, 0x44, 0x84, 0x48, 0x8d, 0x00, 0x3f, 0x41, 0x38, 0x7e, 0xd4, 0xcf, 0xd1, - 0xb9, 0x7e, 0x46, 0x01, 0x2a, 0x26, 0x00, 0x3d, 0x38, 0x43, 0x13, 0xc7, 0x9c, 0x41, 0x64, 0x37, 0x54, 0xee, 0xb1, - 0x91, 0x24, 0x23, 0x84, 0xe4, 0x47, 0xcc, 0x25, 0xc5, 0x9b, 0x43, 0x8b, 0x9c, 0x7d, 0x4c, 0xb2, 0x33, 0x8c, 0xb9, - 0x21, 0x91, 0xae, 0xaa, 0x2f, 0xff, 0xe5, 0x9f, 0x7d, 0xff, 0x5f, 0xfe, 0xf9, 0x8a, 0x06, 0x53, 0xd8, 0x13, 0x60, - 0x24, 0xf3, 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, 0xa0, 0x65, 0x84, 0x1e, 0x7a, 0x22, 0xb4, 0x48, 0x20, - 0x96, 0x41, 0xea, 0x94, 0x1a, 0x40, 0x9e, 0x75, 0x40, 0x13, 0x50, 0x93, 0xb8, 0xd2, 0xe1, 0x64, 0x06, 0x1d, 0xe7, - 0xfd, 0x57, 0x78, 0x59, 0xd0, 0xc2, 0xde, 0xa8, 0xc1, 0x0b, 0x32, 0x38, 0x38, 0x19, 0x1c, 0x52, 0xd3, 0x99, 0xba, - 0x1e, 0x1d, 0xa7, 0x6c, 0xbd, 0x4e, 0xff, 0x88, 0xdd, 0x6b, 0x5a, 0x99, 0x4b, 0xad, 0x1c, 0x4b, 0x2b, 0x5a, 0x6a, - 0x65, 0xfc, 0x24, 0x4c, 0x43, 0x2b, 0xc7, 0x57, 0x6a, 0x65, 0xa4, 0xdc, 0x00, 0x47, 0x0e, 0xed, 0x4d, 0x8c, 0x0e, - 0x19, 0x36, 0x00, 0x47, 0xfb, 0x67, 0x34, 0x65, 0xa3, 0x4f, 0xd2, 0xfc, 0x21, 0x04, 0x30, 0x3c, 0xa2, 0x8d, 0x3c, - 0x81, 0x01, 0xa8, 0xf2, 0xa3, 0x52, 0x6f, 0x7a, 0x7c, 0x34, 0x26, 0xe0, 0xee, 0x72, 0xc2, 0x50, 0xf4, 0xc3, 0x9a, - 0x7d, 0xcd, 0xca, 0x2d, 0x1c, 0x47, 0x6c, 0x18, 0xf1, 0x0c, 0x98, 0x6d, 0xe1, 0x60, 0x47, 0xde, 0x52, 0x04, 0xdb, - 0x02, 0xfb, 0xed, 0x9b, 0xfd, 0x03, 0xdb, 0x3b, 0xce, 0xc6, 0x17, 0x81, 0x0d, 0xde, 0x0c, 0x58, 0x39, 0xae, 0xcf, - 0xa7, 0x2c, 0x75, 0x94, 0x3f, 0x91, 0x25, 0xe0, 0xaa, 0x65, 0x27, 0xe2, 0xdb, 0x10, 0xcd, 0x83, 0x02, 0x20, 0x2c, - 0x7d, 0x3c, 0xb2, 0xbf, 0xcb, 0xc5, 0x77, 0x55, 0x79, 0x8e, 0x8f, 0x7d, 0x4c, 0x95, 0xd8, 0xdd, 0x82, 0x07, 0x7c, - 0xd9, 0x47, 0xbd, 0xa7, 0xdf, 0x04, 0xb0, 0x85, 0x78, 0xdf, 0xc2, 0xf6, 0x5b, 0xaa, 0x2f, 0x42, 0xd1, 0x97, 0xdc, - 0xa6, 0x4d, 0xfe, 0xca, 0x66, 0xa4, 0xb1, 0xc7, 0x68, 0x12, 0x92, 0xc9, 0x0f, 0x24, 0xe1, 0x63, 0x5d, 0x22, 0xcc, - 0x0c, 0xa3, 0x88, 0x46, 0xa9, 0x8c, 0x02, 0x59, 0x85, 0x13, 0x92, 0x19, 0x29, 0x26, 0x83, 0x9f, 0x04, 0xfe, 0x91, - 0xf9, 0x1d, 0x34, 0xf1, 0xc9, 0x22, 0x8d, 0xe4, 0xe1, 0x5f, 0xbc, 0x33, 0xe6, 0x5d, 0x1c, 0x51, 0x4b, 0xe5, 0x74, - 0x63, 0x34, 0x08, 0x83, 0x13, 0x6d, 0x15, 0x5d, 0x01, 0x3b, 0x28, 0x89, 0xe6, 0x05, 0x0b, 0xd4, 0x83, 0xf4, 0xbf, - 0xd1, 0x8d, 0x5f, 0x0d, 0x78, 0x98, 0xf6, 0x52, 0xc9, 0xa7, 0x4b, 0xd3, 0x41, 0x7f, 0x00, 0x0e, 0x3a, 0x5e, 0x2e, - 0x68, 0x45, 0xa0, 0xe5, 0xd3, 0x20, 0x61, 0x13, 0x5e, 0x72, 0xbc, 0xbd, 0xbe, 0x54, 0x11, 0x11, 0xbf, 0xbb, 0x03, - 0x4e, 0xbb, 0xe5, 0xe3, 0xff, 0x37, 0x8d, 0x3d, 0x0e, 0x52, 0x70, 0xb2, 0xe9, 0x3a, 0x0b, 0x5e, 0x15, 0x04, 0x88, - 0xcc, 0xf7, 0xa5, 0x31, 0xd1, 0x88, 0x61, 0xb4, 0xa8, 0xe4, 0x39, 0xc8, 0x6d, 0x8f, 0xe7, 0x66, 0x3b, 0x90, 0xb7, - 0x2b, 0x21, 0xa3, 0xd5, 0xa0, 0xc5, 0xb6, 0x2b, 0xfd, 0x8f, 0xd5, 0xc6, 0x2a, 0xf2, 0x53, 0x7f, 0x5b, 0xa1, 0x90, - 0x11, 0xa3, 0x2a, 0x85, 0xaa, 0x59, 0x8a, 0x1e, 0x26, 0x4e, 0xab, 0xd1, 0xab, 0x1b, 0x2d, 0xd2, 0x92, 0xb6, 0xfd, - 0x21, 0x6d, 0x7b, 0x12, 0x63, 0xc3, 0xa5, 0x98, 0x7b, 0x14, 0x25, 0x23, 0x07, 0x01, 0xb0, 0x5a, 0xd6, 0x23, 0xa0, - 0xa6, 0xab, 0x22, 0x28, 0xfe, 0x43, 0x24, 0x6e, 0x29, 0x84, 0xde, 0x1a, 0x2a, 0x1d, 0x0d, 0xcb, 0xb2, 0x77, 0xc1, - 0x9c, 0xc3, 0xdf, 0xe4, 0x65, 0x08, 0x71, 0x07, 0x56, 0x7f, 0x47, 0xb0, 0x5d, 0xba, 0x43, 0x8f, 0x31, 0xe3, 0xeb, - 0x6c, 0xb6, 0xe2, 0x68, 0xdb, 0xeb, 0x52, 0x3c, 0x01, 0x7b, 0xbf, 0x72, 0x6c, 0x34, 0x62, 0xa9, 0xea, 0xa2, 0x45, - 0x1c, 0x66, 0x53, 0x47, 0x11, 0xcd, 0xff, 0xe6, 0xaa, 0xa0, 0xcc, 0xb7, 0x37, 0x07, 0x65, 0xf8, 0x2d, 0x83, 0x32, - 0xdf, 0xfe, 0xc1, 0x41, 0x99, 0x6f, 0xcc, 0xa0, 0x0c, 0xca, 0xca, 0x17, 0x9f, 0x13, 0x39, 0xc9, 0xb3, 0xb3, 0x22, - 0xec, 0xc8, 0x24, 0x00, 0x10, 0x3b, 0xff, 0x31, 0x21, 0x14, 0x98, 0xa8, 0x11, 0x40, 0xa1, 0x88, 0x89, 0xc8, 0x5b, - 0x04, 0x09, 0x2f, 0xe3, 0x15, 0x6d, 0x9d, 0x20, 0xd8, 0xba, 0xaf, 0x6e, 0x44, 0x81, 0x77, 0xe8, 0xea, 0xb0, 0x51, - 0x57, 0x45, 0x34, 0x02, 0xfa, 0xa4, 0xa9, 0xee, 0xd8, 0xdd, 0x54, 0x99, 0x69, 0xe6, 0x08, 0x3d, 0x75, 0xe0, 0x20, - 0x38, 0x68, 0x69, 0xff, 0xe7, 0xc3, 0x4e, 0x6f, 0xbb, 0x33, 0x83, 0xde, 0xa0, 0x4b, 0xe1, 0xad, 0xdd, 0xdb, 0xde, - 0xc6, 0xb7, 0x33, 0xf5, 0xd6, 0xc5, 0xb7, 0x58, 0xbd, 0xed, 0xe0, 0xdb, 0x48, 0xbd, 0x3d, 0xc0, 0xb7, 0xb1, 0x7a, - 0x7b, 0x88, 0x6f, 0xa7, 0x76, 0x79, 0xc8, 0x35, 0x70, 0x0f, 0x81, 0xb1, 0xc8, 0xb1, 0x08, 0x54, 0x19, 0xec, 0x5b, - 0xbc, 0x49, 0x18, 0x9d, 0x04, 0xb1, 0x27, 0x1c, 0xb0, 0x20, 0xf7, 0xce, 0x40, 0xf8, 0x07, 0x94, 0x38, 0xf7, 0x14, - 0x3f, 0x29, 0x01, 0xfe, 0xca, 0x41, 0x3c, 0x63, 0xea, 0xdb, 0xba, 0x0a, 0x6b, 0xb0, 0x25, 0x0f, 0xdb, 0xc3, 0xb2, - 0xa7, 0xd7, 0x49, 0x04, 0x6c, 0x54, 0x62, 0x02, 0xad, 0x5c, 0x55, 0x27, 0xa6, 0x6b, 0xe9, 0x15, 0xbe, 0x42, 0x95, - 0x18, 0x2e, 0xfb, 0x04, 0x6c, 0xa4, 0xd6, 0x39, 0x38, 0x79, 0x6b, 0xd5, 0x0b, 0x42, 0xa4, 0x15, 0x0a, 0xe1, 0xa4, - 0xdf, 0x0e, 0xa2, 0x13, 0xfd, 0xfc, 0x0a, 0x8c, 0xde, 0xe8, 0x84, 0xdd, 0xa4, 0x6a, 0x08, 0x44, 0x53, 0xcd, 0x28, - 0x20, 0xc8, 0x2a, 0x82, 0xa5, 0x41, 0x67, 0x53, 0xaa, 0x19, 0xa4, 0x4e, 0x5d, 0xf1, 0xd0, 0xf4, 0xf5, 0x22, 0xa0, - 0x68, 0x55, 0xb0, 0x0b, 0xb6, 0x37, 0x95, 0x0a, 0x0a, 0x43, 0x05, 0x16, 0x5c, 0xab, 0x8d, 0xb4, 0x3f, 0x7e, 0xa5, - 0x4e, 0xb2, 0x94, 0x3a, 0x32, 0x0f, 0x2a, 0xf4, 0x29, 0xc5, 0xaa, 0x84, 0xfc, 0xa2, 0x33, 0xc2, 0x3f, 0x52, 0xfe, - 0x7e, 0x31, 0x99, 0x4c, 0xae, 0x55, 0x4f, 0x5f, 0x8c, 0x27, 0xac, 0xcb, 0x76, 0x7a, 0x18, 0xc4, 0x6e, 0x49, 0x89, - 0xd8, 0x29, 0x89, 0x76, 0xcb, 0xdb, 0x35, 0x46, 0xe1, 0x09, 0x1a, 0xeb, 0xf6, 0x7a, 0xac, 0x04, 0xaa, 0x2c, 0x41, - 0x7e, 0x9f, 0xc4, 0x69, 0xd0, 0x2e, 0xfd, 0x53, 0x29, 0xf8, 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, 0x12, 0xfe, 0xb0, 0x0d, 0x95, 0x82, - 0xd1, 0x96, 0xe8, 0xe8, 0x89, 0xc7, 0x20, 0x5a, 0xf0, 0x0c, 0x6c, 0x2c, 0xe0, 0x6d, 0x10, 0xd0, 0x13, 0x29, 0xde, - 0xc5, 0xa7, 0x6b, 0x51, 0xa8, 0xbf, 0x30, 0x65, 0x3a, 0x32, 0x33, 0xc9, 0x73, 0x4e, 0xaa, 0xa0, 0x59, 0x8d, 0x9c, - 0x45, 0xd5, 0x2f, 0x42, 0x5e, 0x49, 0x7b, 0x94, 0x36, 0xd8, 0x52, 0xc8, 0xf8, 0x1f, 0xaf, 0x92, 0xf1, 0x3f, 0xdc, - 0x2c, 0xe3, 0x8f, 0x6f, 0x27, 0xe2, 0x7f, 0xf8, 0x83, 0x45, 0xfc, 0x8f, 0xa6, 0x88, 0x17, 0x42, 0x6c, 0x0f, 0xac, - 0x58, 0x32, 0x5f, 0x8f, 0xb3, 0xf3, 0x16, 0x6e, 0x89, 0xdc, 0x26, 0xe9, 0xb9, 0x71, 0x2b, 0xe1, 0xbf, 0x26, 0xb5, - 0x49, 0x0d, 0x66, 0x7c, 0x07, 0x97, 0x67, 0x27, 0x27, 0x09, 0x53, 0x32, 0xde, 0xa8, 0x20, 0xcb, 0xf8, 0x4d, 0x1a, - 0xda, 0x6f, 0xc0, 0x49, 0x35, 0x4a, 0x26, 0x13, 0x28, 0x9a, 0x4c, 0x6c, 0x95, 0xfa, 0x0b, 0xf2, 0x8c, 0x5a, 0xbd, - 0xae, 0x95, 0x50, 0xab, 0xaf, 0xbe, 0x32, 0xcb, 0xcc, 0x02, 0x19, 0xf5, 0x32, 0xed, 0x09, 0x59, 0x33, 0x8e, 0x0b, - 0xdc, 0x83, 0xd5, 0x77, 0x7b, 0xd1, 0x64, 0x99, 0x81, 0x52, 0x89, 0x47, 0xf8, 0x41, 0x98, 0xe6, 0x37, 0x52, 0x44, - 0x9a, 0xf6, 0x2a, 0x72, 0xd5, 0x51, 0xae, 0xf1, 0x39, 0xbe, 0xea, 0xf8, 0x16, 0x16, 0x5f, 0xa6, 0x6a, 0x3c, 0xbe, - 0x78, 0x31, 0x76, 0xf6, 0xc0, 0x94, 0x8d, 0x8b, 0x37, 0x69, 0x23, 0x05, 0x4e, 0x80, 0x1d, 0x86, 0x26, 0xa6, 0xa5, - 0x20, 0x58, 0x75, 0x17, 0xa0, 0xaa, 0xec, 0x19, 0x9d, 0x64, 0xa6, 0x14, 0x0e, 0x39, 0xa8, 0x91, 0x25, 0x30, 0x07, - 0x93, 0xba, 0x90, 0xbe, 0xcb, 0x2e, 0xf2, 0x47, 0x4e, 0xe5, 0x07, 0xbc, 0xe9, 0xf4, 0x61, 0x29, 0xf5, 0x87, 0xcc, - 0x2c, 0xa8, 0x7a, 0x62, 0xc0, 0x5f, 0xcc, 0x30, 0x2e, 0x55, 0x90, 0x1f, 0x08, 0x37, 0xc7, 0xaf, 0x0d, 0x89, 0x21, - 0x54, 0x2c, 0xbd, 0xa2, 0xde, 0xe5, 0xa5, 0xf9, 0xd1, 0xcb, 0xda, 0x07, 0x12, 0x1b, 0x3c, 0xc0, 0xf0, 0x6b, 0xb5, - 0xa8, 0x0d, 0xb2, 0x05, 0x77, 0x1c, 0x6a, 0xe5, 0xb8, 0xa5, 0xb7, 0xd3, 0x6e, 0x83, 0x8a, 0xf1, 0xc5, 0x27, 0x8d, - 0x1c, 0xdd, 0x59, 0xe2, 0x7b, 0x55, 0xe8, 0x7e, 0xe5, 0x13, 0x63, 0x9a, 0xc4, 0xf8, 0x0d, 0x14, 0x81, 0xa8, 0x71, - 0x0d, 0x46, 0x2d, 0x62, 0xf3, 0xdd, 0x57, 0x6e, 0x9c, 0x41, 0x58, 0x77, 0x1d, 0x07, 0xcb, 0x34, 0xd1, 0x7a, 0x21, - 0xb6, 0x15, 0x56, 0xcd, 0x2a, 0x38, 0x37, 0xe8, 0xcc, 0xe2, 0xcc, 0x88, 0x71, 0xd7, 0xb6, 0x41, 0xa9, 0x82, 0xdc, - 0x22, 0x52, 0xbd, 0x87, 0xf1, 0x58, 0xe1, 0x03, 0x2b, 0xa0, 0xeb, 0xde, 0xa7, 0x01, 0x39, 0xfa, 0xa5, 0x9a, 0xd1, - 0x55, 0x95, 0x2a, 0x28, 0xcd, 0x53, 0x0a, 0x03, 0x19, 0x0a, 0x36, 0xc3, 0x1a, 0xa7, 0x42, 0x6f, 0xc1, 0x34, 0x24, - 0x80, 0xb5, 0x53, 0x86, 0x9e, 0x89, 0xad, 0xc0, 0x16, 0xd2, 0x02, 0x94, 0x1e, 0x76, 0xe8, 0x5b, 0x35, 0xd0, 0xd3, - 0xd5, 0x18, 0xf5, 0x75, 0x7e, 0xda, 0xc5, 0x91, 0x5f, 0x9c, 0x79, 0xf0, 0xcf, 0xfa, 0xd3, 0x12, 0xa4, 0xfc, 0xf1, - 0xa7, 0x98, 0x83, 0x51, 0x3d, 0x6f, 0x61, 0x24, 0x84, 0x42, 0xa6, 0x52, 0x1d, 0xd2, 0xa9, 0xaa, 0xb8, 0xfd, 0xd4, - 0x5b, 0x14, 0xe8, 0xce, 0x91, 0xdf, 0x12, 0xa4, 0x59, 0xca, 0x7a, 0xf5, 0xd3, 0x73, 0xd3, 0x75, 0x50, 0xc4, 0x1a, - 0x2e, 0x33, 0x74, 0xff, 0xf8, 0x05, 0xb8, 0x7f, 0x42, 0x8d, 0xb6, 0x95, 0xdf, 0xd0, 0x5e, 0xdb, 0x3e, 0x90, 0xb4, - 0xdd, 0x24, 0x6b, 0x21, 0x5f, 0xf5, 0x8f, 0xae, 0xf2, 0x6f, 0x6e, 0x3a, 0x4b, 0xc6, 0xf8, 0xac, 0xfa, 0x67, 0x1c, - 0xc2, 0x37, 0x8b, 0xe9, 0x2c, 0xf9, 0x36, 0x90, 0x05, 0xd1, 0x04, 0x3f, 0x16, 0x78, 0x9b, 0x96, 0xc7, 0x94, 0xc8, - 0xb9, 0x44, 0xb5, 0x1e, 0x74, 0x1e, 0x81, 0xc3, 0x76, 0xeb, 0xe1, 0xaf, 0x47, 0xbf, 0x94, 0x34, 0x52, 0xf7, 0x71, - 0x6d, 0xbb, 0x87, 0xf2, 0x22, 0x89, 0x2e, 0xc0, 0x6f, 0x24, 0x1b, 0xe3, 0x18, 0x03, 0xb9, 0xbd, 0x79, 0x26, 0x93, - 0x22, 0x72, 0x96, 0xd0, 0x6f, 0xe4, 0x90, 0x4b, 0xb1, 0xfd, 0x60, 0x7e, 0xae, 0x56, 0xa3, 0xd3, 0x48, 0x76, 0xf8, - 0x43, 0x73, 0x1a, 0xae, 0x4e, 0xa2, 0xa8, 0x9f, 0xcb, 0xef, 0x00, 0x0c, 0xc2, 0xb0, 0x69, 0xe5, 0x02, 0xaa, 0x36, - 0x94, 0x18, 0x59, 0x1d, 0xd5, 0x40, 0x96, 0xbf, 0x0d, 0xaa, 0x32, 0x2a, 0x58, 0x0f, 0xbf, 0xda, 0x18, 0x83, 0x77, - 0x2a, 0x8d, 0xa7, 0x59, 0x3c, 0x1e, 0x27, 0xac, 0xa7, 0xec, 0x23, 0xab, 0xf3, 0x00, 0x93, 0x22, 0xcc, 0x25, 0xab, - 0xaf, 0x8a, 0x41, 0x3c, 0x4d, 0xa7, 0xe8, 0x18, 0xec, 0x35, 0xfc, 0xf4, 0xe2, 0x5a, 0x72, 0xca, 0x6c, 0x81, 0x76, - 0x45, 0x3c, 0x7a, 0xae, 0xe3, 0xb2, 0x03, 0xc6, 0x22, 0x2d, 0x78, 0xbb, 0xc7, 0xb3, 0x79, 0xd0, 0xda, 0xae, 0x23, - 0x82, 0x55, 0x1a, 0x05, 0x6f, 0x0d, 0x5a, 0x1e, 0x5a, 0x07, 0x42, 0xcb, 0x59, 0x7e, 0x47, 0x96, 0xd1, 0x00, 0xf8, - 0x79, 0x3f, 0x5d, 0x54, 0xd6, 0x91, 0xf9, 0xf7, 0xd9, 0x2d, 0x5f, 0xae, 0xdf, 0x2d, 0x5f, 0xaa, 0xdd, 0x72, 0x3d, - 0xc7, 0x7e, 0x31, 0xe9, 0xe0, 0x9f, 0x5e, 0x85, 0x10, 0xac, 0x0a, 0x90, 0xc3, 0x42, 0xbb, 0xb8, 0xd5, 0x85, 0xff, - 0x68, 0xe8, 0xb6, 0x87, 0x7f, 0x7c, 0xb0, 0x00, 0xdb, 0x16, 0x16, 0xe2, 0xbf, 0x76, 0xad, 0xaa, 0x73, 0x1f, 0xeb, - 0xb0, 0xd7, 0xce, 0x6a, 0x5d, 0xf7, 0xfa, 0x4d, 0x0b, 0xf2, 0x8a, 0x3b, 0x81, 0x12, 0xc6, 0xe0, 0xaa, 0x45, 0xc7, - 0xc7, 0x50, 0x3a, 0xc9, 0x46, 0x8b, 0xe2, 0xef, 0x24, 0xfc, 0x92, 0x88, 0xd7, 0x6e, 0xe9, 0xc6, 0x38, 0xaa, 0xab, - 0xc8, 0xb0, 0x51, 0x23, 0x2c, 0xf5, 0x3a, 0x05, 0x05, 0x30, 0x26, 0x73, 0xba, 0xfe, 0xfd, 0x35, 0x9b, 0xe0, 0x3f, - 0x64, 0x6d, 0xd6, 0x22, 0xf3, 0x6f, 0x25, 0xc6, 0xb5, 0x44, 0xf8, 0x2c, 0x1a, 0x98, 0x6b, 0xd8, 0x7e, 0xb4, 0x1e, - 0xdc, 0x43, 0x35, 0xd3, 0x50, 0x29, 0x05, 0xa9, 0x77, 0xc0, 0x0b, 0x88, 0x16, 0x09, 0xbf, 0x7e, 0xd4, 0xab, 0x38, - 0x63, 0x65, 0xd4, 0x6b, 0x04, 0x7a, 0xd5, 0xf6, 0x96, 0x52, 0xfa, 0x8b, 0x2f, 0xef, 0xe3, 0x1f, 0x11, 0xfb, 0x3a, - 0xae, 0x7c, 0x23, 0x11, 0x1b, 0x40, 0xdf, 0x68, 0xa3, 0xe6, 0xfc, 0x08, 0x0d, 0x4e, 0xfe, 0xcf, 0x6d, 0x5b, 0xa3, - 0xb1, 0x7e, 0xab, 0xe6, 0xd2, 0x2a, 0xfd, 0xac, 0xd6, 0x9f, 0x37, 0xf8, 0x2d, 0xdb, 0x8e, 0x84, 0x43, 0x50, 0x6f, - 0x2b, 0x7f, 0x3b, 0xca, 0x4a, 0x63, 0x45, 0xf1, 0xdb, 0xb6, 0xaf, 0x4c, 0x62, 0xea, 0xb1, 0x11, 0x1e, 0x6b, 0x27, - 0x52, 0x9e, 0x6f, 0x63, 0x0f, 0xe1, 0x47, 0xfe, 0x85, 0x85, 0xf7, 0xf0, 0xc3, 0x62, 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, 0xbf, 0x07, 0x65, 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, 0x7c, 0x7b, 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, 0x84, 0x19, 0x5a, 0x16, 0x3c, 0xbf, 0xea, 0x3e, 0x04, 0x1d, 0x77, 0xda, 0xba, 0x3f, 0x6a, 0xb7, 0x3a, - 0x56, 0xa7, 0xd5, 0xf5, 0x1f, 0x5a, 0x5d, 0xf1, 0x3f, 0xc8, 0xc8, 0x6d, 0xab, 0x03, 0x4f, 0xdb, 0x16, 0xbc, 0x9f, - 0xde, 0x17, 0xe9, 0x17, 0x91, 0xbd, 0xd5, 0xdf, 0xc5, 0x5f, 0x8f, 0x04, 0x48, 0x7d, 0x69, 0x8b, 0x5f, 0xe8, 0x66, - 0x7f, 0x61, 0x96, 0x76, 0x1e, 0xad, 0x2d, 0xee, 0x3e, 0x5c, 0x5b, 0xbc, 0xfd, 0x60, 0x6d, 0xf1, 0xfd, 0x9d, 0x7a, - 0xf1, 0xd6, 0x89, 0xa8, 0xd2, 0x72, 0x21, 0xb4, 0x67, 0x11, 0x30, 0xca, 0xb9, 0xd3, 0x01, 0x38, 0xdb, 0x56, 0x0b, - 0x7f, 0x3c, 0xec, 0xba, 0xba, 0xd7, 0x31, 0xf6, 0xd2, 0x58, 0x3e, 0x7c, 0x04, 0x58, 0x3e, 0xef, 0x3e, 0x18, 0x61, - 0x3b, 0x42, 0x14, 0xfe, 0x9d, 0x6e, 0x3f, 0x1a, 0x81, 0x46, 0xb0, 0xf0, 0x1f, 0xfc, 0x99, 0xee, 0x74, 0x47, 0xe2, - 0xa5, 0x8d, 0xf5, 0x1f, 0x3a, 0x0f, 0x0b, 0x68, 0x8a, 0x7f, 0x7e, 0xd3, 0x26, 0x34, 0x1a, 0xf0, 0xe6, 0xb8, 0xf7, - 0x81, 0x46, 0x8f, 0xa6, 0x5d, 0xff, 0xcb, 0xd3, 0x87, 0xfe, 0xa3, 0x69, 0xe7, 0xe1, 0x07, 0xf1, 0x96, 0x00, 0x05, - 0xbf, 0xc4, 0x7f, 0x1f, 0xb6, 0xdb, 0xd3, 0x56, 0xc7, 0x7f, 0x74, 0xba, 0xed, 0x6f, 0x27, 0xad, 0x07, 0xfe, 0x23, - 0xfc, 0x57, 0x0d, 0x37, 0xcd, 0x66, 0xcc, 0xb6, 0x70, 0xbd, 0x1b, 0x7e, 0xaf, 0x39, 0x47, 0xf7, 0xbe, 0xb5, 0x73, - 0xff, 0xf9, 0x23, 0x58, 0xa3, 0x69, 0xa7, 0x0b, 0xff, 0x5f, 0xf5, 0xf8, 0x01, 0x09, 0x2f, 0x07, 0x8e, 0x18, 0x66, - 0xca, 0x2a, 0xc2, 0xd1, 0xb7, 0xc9, 0xee, 0x79, 0xdf, 0x5f, 0x15, 0x00, 0x61, 0xfc, 0xe6, 0x20, 0x37, 0xbf, 0x5d, - 0x04, 0x84, 0x3e, 0x9c, 0xff, 0x07, 0x46, 0x40, 0xbe, 0x6f, 0x06, 0xb9, 0xcf, 0x57, 0xf3, 0x03, 0x9b, 0xce, 0xda, - 0x6b, 0xe6, 0x1c, 0xfe, 0x85, 0x0d, 0x31, 0x2b, 0x1c, 0x5a, 0x73, 0x6e, 0xc6, 0x83, 0x32, 0xdc, 0xc8, 0xe7, 0x32, - 0xea, 0x5f, 0xf0, 0x2b, 0x08, 0x12, 0xdf, 0x4c, 0x90, 0x5f, 0x6f, 0x47, 0x8f, 0xf8, 0x0f, 0xa6, 0x47, 0xc1, 0x0d, - 0x7a, 0xd4, 0x22, 0xee, 0x14, 0x31, 0x20, 0x47, 0x7f, 0x9f, 0xde, 0x9d, 0xef, 0xf1, 0xab, 0x62, 0x5b, 0x0c, 0x4b, - 0x0a, 0x5b, 0xe4, 0x24, 0xbe, 0xfb, 0x9c, 0x13, 0x02, 0x91, 0x38, 0x1d, 0xda, 0x32, 0x08, 0x33, 0xc7, 0xcf, 0xef, - 0xaa, 0x97, 0x53, 0x71, 0x39, 0x27, 0xa4, 0x9b, 0x75, 0x3b, 0x3a, 0x80, 0x83, 0xb9, 0xec, 0xe1, 0x32, 0xe3, 0x11, - 0xfe, 0x7e, 0x27, 0x1e, 0xf3, 0x04, 0xef, 0xfd, 0xca, 0x3b, 0x72, 0x98, 0x7a, 0xfd, 0x2d, 0xa6, 0x8d, 0xab, 0x83, - 0x82, 0x19, 0x06, 0x0d, 0x5e, 0xb1, 0x71, 0x1c, 0x39, 0xb6, 0x33, 0x87, 0x5d, 0x0b, 0x63, 0xb6, 0x6a, 0x39, 0xdb, - 0x94, 0xae, 0xed, 0xda, 0xea, 0x57, 0x0a, 0xe5, 0xf8, 0x89, 0xb6, 0xf0, 0x50, 0x06, 0x19, 0x6d, 0xe9, 0x05, 0xc0, - 0xf8, 0xaa, 0x24, 0x47, 0x81, 0x5f, 0x59, 0x0e, 0xb6, 0x30, 0x1d, 0x3a, 0x7e, 0x17, 0x5c, 0x09, 0x2a, 0xc6, 0x4f, - 0x5e, 0xfd, 0xe0, 0xb4, 0xb6, 0xc1, 0xb4, 0x31, 0xba, 0xe9, 0x81, 0x86, 0x2b, 0xa1, 0x24, 0x11, 0x20, 0x68, 0x94, - 0x7a, 0xfa, 0x77, 0xac, 0x55, 0x21, 0xa3, 0xe2, 0xf1, 0xc5, 0x81, 0xbc, 0xd6, 0x6e, 0x63, 0xf4, 0x96, 0xa2, 0xf6, - 0xd5, 0x27, 0xb5, 0x36, 0x41, 0x65, 0xd0, 0x2f, 0xba, 0xa4, 0x33, 0x70, 0xd4, 0x0a, 0x98, 0x55, 0x6e, 0x49, 0xef, - 0x21, 0xb4, 0x85, 0x4e, 0x18, 0xb3, 0xd3, 0x78, 0x24, 0x45, 0xbb, 0x67, 0xc9, 0xdb, 0x30, 0x2d, 0xc2, 0x22, 0xec, - 0x78, 0xc2, 0x7f, 0x86, 0x17, 0xd4, 0x6c, 0x61, 0x9a, 0xd9, 0xfd, 0x7b, 0x3d, 0x0d, 0x49, 0x3d, 0x21, 0xdf, 0xc6, - 0xdf, 0xba, 0x79, 0x08, 0xfe, 0xda, 0xdf, 0x85, 0xf7, 0xf0, 0xf7, 0x6e, 0xde, 0x1b, 0xda, 0xae, 0x4f, 0x82, 0xf1, - 0x5e, 0xf5, 0xcb, 0x37, 0x51, 0x2a, 0x6c, 0x82, 0x0e, 0xf3, 0x6e, 0xab, 0xcc, 0xa4, 0xe2, 0xea, 0xee, 0x54, 0x8a, - 0x0b, 0x9e, 0x0d, 0x49, 0x05, 0x42, 0xb4, 0xeb, 0xef, 0x18, 0xe2, 0xf0, 0xb4, 0x85, 0x3f, 0x6b, 0x02, 0xf1, 0x3e, - 0x34, 0x50, 0x12, 0xf1, 0x25, 0x34, 0xdf, 0x16, 0xc2, 0x17, 0xfa, 0xfd, 0x48, 0xe2, 0x4a, 0x88, 0xaa, 0x3a, 0xc7, - 0xac, 0x39, 0x48, 0x12, 0xf9, 0x02, 0xb6, 0x67, 0xc4, 0x9c, 0x04, 0xbb, 0xca, 0x88, 0xca, 0x53, 0xe8, 0xeb, 0xe8, - 0x2f, 0x55, 0xaf, 0xab, 0xf3, 0x6a, 0xbb, 0x67, 0xcd, 0x14, 0xc8, 0xf0, 0x8d, 0xc3, 0x2a, 0xba, 0x9d, 0x21, 0xbe, - 0xd8, 0x26, 0xb6, 0x72, 0xf5, 0x4d, 0xbb, 0x35, 0x59, 0xc0, 0xe6, 0xa6, 0x60, 0x15, 0xd3, 0xd0, 0xbe, 0xc0, 0xf4, - 0x19, 0xfc, 0x59, 0x15, 0xab, 0x07, 0xc9, 0x50, 0x7e, 0x12, 0xe1, 0x2f, 0x9c, 0xa1, 0x1f, 0x65, 0xb5, 0x01, 0x39, - 0x7d, 0x92, 0x93, 0x20, 0x7d, 0x31, 0x2e, 0x9b, 0x48, 0x80, 0xcd, 0x80, 0xbf, 0xbf, 0xb0, 0xba, 0x0d, 0x22, 0xaf, - 0x79, 0x62, 0x6a, 0xc1, 0x38, 0xce, 0xe9, 0xf6, 0xb0, 0xc2, 0xbf, 0x16, 0xd5, 0xac, 0x48, 0x4d, 0xbb, 0x92, 0x15, - 0x03, 0x1b, 0x8b, 0xec, 0x40, 0x26, 0xc0, 0x99, 0xdf, 0xa4, 0x36, 0xaf, 0x77, 0x8e, 0x45, 0xee, 0x1b, 0x7e, 0xb3, - 0xdf, 0x16, 0x44, 0xb6, 0x41, 0x94, 0x5d, 0x89, 0x13, 0x19, 0x38, 0x78, 0x2b, 0xb2, 0xfa, 0x25, 0x4c, 0xe6, 0x86, - 0xb7, 0xcd, 0xd5, 0xd2, 0xe3, 0xd2, 0x3a, 0xb8, 0x32, 0x86, 0x77, 0xcc, 0x22, 0xee, 0x47, 0x29, 0xe5, 0x29, 0x39, - 0x86, 0x58, 0xf0, 0x3a, 0x6c, 0xdb, 0x2d, 0x41, 0xf2, 0x18, 0xbf, 0xa5, 0x4a, 0x90, 0xde, 0x87, 0x42, 0x95, 0x4b, - 0xfd, 0x7d, 0x2d, 0x15, 0x78, 0xda, 0xed, 0xbf, 0x39, 0xd8, 0xb3, 0xc4, 0xc6, 0xde, 0xdd, 0x82, 0xd7, 0x5d, 0xf2, - 0x8e, 0x45, 0xc6, 0x46, 0x28, 0x32, 0x36, 0x2c, 0x91, 0xe7, 0x25, 0x52, 0x67, 0xb7, 0x04, 0xd6, 0xb6, 0xc5, 0xd2, - 0x91, 0x08, 0xeb, 0xcd, 0xc0, 0x83, 0x88, 0xf1, 0x33, 0x66, 0x5b, 0xd8, 0xb5, 0x85, 0x0b, 0x6f, 0xab, 0xe4, 0x17, - 0x65, 0x33, 0xf0, 0x54, 0x05, 0x01, 0x41, 0xd3, 0x33, 0x95, 0x07, 0x23, 0x87, 0xd2, 0x69, 0xb1, 0xab, 0xad, 0x8b, - 0xc5, 0xf1, 0x0c, 0xc4, 0x92, 0xca, 0x5d, 0x79, 0x2f, 0x3b, 0xec, 0xd2, 0x54, 0xfd, 0xa3, 0x72, 0x5d, 0x94, 0x72, - 0xda, 0xe9, 0xef, 0x46, 0xd2, 0x06, 0xc2, 0xbd, 0x5c, 0xc0, 0x66, 0x06, 0xd5, 0x87, 0x86, 0x86, 0x1f, 0x67, 0x5b, - 0x67, 0xec, 0xb8, 0x15, 0xcd, 0xe3, 0x2a, 0x24, 0x88, 0x1a, 0xb1, 0xbf, 0xab, 0x94, 0xa3, 0x4c, 0xf5, 0x94, 0x8f, - 0x91, 0x91, 0xdc, 0x81, 0x84, 0x24, 0x86, 0x2d, 0x65, 0xbc, 0x91, 0x0c, 0x49, 0x58, 0x0c, 0x00, 0x96, 0xf8, 0x59, - 0xc5, 0x25, 0xa5, 0x66, 0x28, 0xed, 0xfe, 0x5f, 0xff, 0xf7, 0xff, 0x91, 0xa1, 0x46, 0xa0, 0x2d, 0x80, 0x85, 0xd9, - 0x31, 0xd5, 0xa9, 0x23, 0x3b, 0x07, 0xe7, 0x34, 0x1e, 0xb7, 0xa6, 0x51, 0x32, 0x01, 0x08, 0x0a, 0x26, 0xee, 0x14, - 0xc8, 0x7a, 0xe0, 0x0a, 0x09, 0x96, 0x79, 0x62, 0x2f, 0xc1, 0xab, 0x17, 0xe1, 0xb2, 0xfd, 0xae, 0xdc, 0x59, 0x95, - 0xb3, 0x4c, 0x0c, 0x6e, 0x64, 0xd2, 0x1a, 0x3c, 0x58, 0xcb, 0xa6, 0x55, 0xbf, 0x13, 0x4a, 0x0a, 0x13, 0x56, 0x4b, - 0xa5, 0x85, 0x96, 0xfa, 0x70, 0xe4, 0x5f, 0xff, 0xe9, 0xbf, 0xfc, 0x0f, 0xf5, 0x8a, 0x67, 0x1e, 0x7f, 0xfd, 0xc7, - 0xbf, 0xff, 0x7f, 0xff, 0xf7, 0xbf, 0x62, 0xa6, 0xb2, 0x3c, 0x17, 0xa1, 0xad, 0x65, 0x55, 0x87, 0x22, 0x62, 0x8f, - 0x59, 0x95, 0x13, 0x52, 0x4f, 0xb9, 0xdd, 0xa7, 0x09, 0x89, 0x41, 0x25, 0x74, 0xc4, 0xe7, 0x94, 0xa2, 0x4d, 0x54, - 0xbb, 0x86, 0x7c, 0xb0, 0x94, 0x16, 0x1d, 0xf5, 0xdb, 0x3b, 0x6d, 0xbb, 0x5a, 0xde, 0xbe, 0xd1, 0x77, 0x0b, 0x17, - 0xe6, 0x56, 0x89, 0x39, 0xbe, 0x5e, 0xb6, 0xa5, 0x0a, 0x6d, 0x61, 0x49, 0x59, 0x95, 0x5b, 0x18, 0x73, 0x5e, 0xe2, - 0x6b, 0xd0, 0x35, 0x8a, 0x69, 0x95, 0x6b, 0x7d, 0x7a, 0xbf, 0x2c, 0x00, 0xd1, 0x09, 0x2e, 0x8d, 0x08, 0xa0, 0xd1, - 0x79, 0x6a, 0x0b, 0xad, 0x95, 0xe4, 0xa2, 0xa4, 0x51, 0x84, 0x87, 0x73, 0xff, 0xd1, 0xdf, 0x96, 0x7f, 0x9e, 0xa1, - 0x95, 0x60, 0x39, 0xb3, 0xe8, 0x5c, 0xfa, 0x3d, 0x0f, 0xda, 0xed, 0xf9, 0xb9, 0xbb, 0xac, 0x66, 0xf0, 0xae, 0x9a, - 0x8c, 0x82, 0x6e, 0xe6, 0x80, 0x74, 0x10, 0xab, 0xe3, 0x7b, 0x60, 0xea, 0xb7, 0x31, 0x1c, 0x54, 0x96, 0x7f, 0x5a, - 0x52, 0x88, 0x29, 0xfe, 0x0d, 0x0f, 0x4c, 0x65, 0x34, 0x0e, 0x4a, 0x0c, 0x2c, 0x96, 0x46, 0xaf, 0xae, 0xe8, 0x35, - 0xed, 0xac, 0xa6, 0xac, 0x98, 0x07, 0xbe, 0xe6, 0x51, 0xed, 0x7d, 0x3c, 0x7c, 0x9d, 0x76, 0xbc, 0x69, 0x77, 0xa9, - 0x87, 0xe7, 0x3c, 0x9b, 0x99, 0x27, 0xbc, 0x2c, 0x62, 0x23, 0x36, 0x51, 0x51, 0x4c, 0x59, 0x2f, 0x4e, 0x6f, 0xcb, - 0x2f, 0x70, 0xbb, 0x01, 0x6d, 0xb3, 0x88, 0x07, 0xc4, 0xb4, 0x3d, 0xf3, 0x0c, 0x38, 0xc2, 0xd3, 0xf5, 0x6c, 0x69, - 0xcc, 0xd5, 0x13, 0x4d, 0x31, 0x56, 0x58, 0x4f, 0x07, 0x2a, 0x7d, 0xea, 0x6e, 0x0e, 0x25, 0x42, 0x0d, 0xbf, 0xca, - 0xa3, 0xd5, 0x77, 0x35, 0x1f, 0x5d, 0x8a, 0x66, 0x70, 0x8b, 0xd7, 0xd6, 0x0b, 0x35, 0x29, 0x6a, 0x3f, 0x80, 0xf5, - 0x43, 0x60, 0xda, 0xcd, 0x56, 0x54, 0x88, 0xad, 0xde, 0x85, 0xbf, 0x6a, 0x7b, 0x3c, 0x9a, 0xcf, 0xa9, 0xa1, 0x0b, - 0xdc, 0x48, 0x76, 0x35, 0x4a, 0x0a, 0x4a, 0x1b, 0x10, 0xa7, 0xf4, 0xb2, 0x8d, 0x64, 0x5b, 0xf1, 0x24, 0xcf, 0xef, - 0xe9, 0x57, 0x8c, 0xff, 0x7f, 0x2a, 0xa5, 0xd0, 0x17, 0x78, 0x7c, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3, + 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x28, 0x58, 0x5d, 0x05, 0x5c, 0x81, 0x10, 0x49, 0x95, 0xaa, 0xca, 0xa0, 0x40, 0x5e, + 0xd5, 0x62, 0x57, 0xd9, 0xb5, 0xb9, 0xa4, 0xb2, 0xaf, 0x2d, 0xeb, 0x4a, 0x10, 0x99, 0x14, 0xe1, 0x02, 0x01, 0x1a, + 0x48, 0x6a, 0x31, 0x85, 0x3e, 0xfd, 0xd4, 0x4f, 0x7d, 0xce, 0x6c, 0xfd, 0xd0, 0x0f, 0xd3, 0xa7, 0xfb, 0x61, 0x3e, + 0x62, 0x9e, 0xfb, 0x53, 0xee, 0x0f, 0x4c, 0x7f, 0xc2, 0x44, 0x44, 0x2e, 0x48, 0x80, 0xa4, 0x24, 0xbb, 0x7d, 0xe7, + 0x78, 0x11, 0x90, 0x6b, 0x44, 0x64, 0x64, 0x6c, 0x19, 0x09, 0xee, 0xde, 0x1b, 0x65, 0x43, 0x7e, 0x35, 0x63, 0xd6, + 0x84, 0x4f, 0x93, 0xfe, 0xae, 0xfc, 0x3f, 0x8b, 0x46, 0xfd, 0xdd, 0x24, 0x4e, 0x3f, 0x59, 0x39, 0x4b, 0xc2, 0x78, + 0x98, 0xa5, 0xd6, 0x24, 0x67, 0xe3, 0x70, 0x14, 0xf1, 0x28, 0x88, 0xa7, 0xd1, 0x19, 0xb3, 0xb6, 0xfa, 0xbb, 0x53, + 0xc6, 0x23, 0x6b, 0x38, 0x89, 0xf2, 0x82, 0xf1, 0xf0, 0xe3, 0xc1, 0x17, 0xad, 0x27, 0xfd, 0xdd, 0x62, 0x98, 0xc7, + 0x33, 0x6e, 0xe1, 0x90, 0xe1, 0x34, 0x1b, 0xcd, 0x13, 0xd6, 0x3f, 0x8f, 0x72, 0xeb, 0x05, 0x0b, 0xdf, 0x9d, 0xfe, + 0xc4, 0x86, 0xdc, 0x1f, 0xb1, 0x71, 0x9c, 0xb2, 0xf7, 0x79, 0x36, 0x63, 0x39, 0xbf, 0xf2, 0xf6, 0x57, 0x57, 0xc4, + 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x33, 0xc6, 0xdf, 0x5d, 0xa4, 0xaa, 0xcf, 0x73, 0x26, 0x26, 0xc9, 0xf2, 0xc2, 0x8b, + 0xd7, 0xb4, 0xd9, 0xbf, 0x9a, 0x9e, 0x66, 0x49, 0xe1, 0x7d, 0xd2, 0xf5, 0xb3, 0x3c, 0xe3, 0x19, 0x82, 0xe5, 0x4f, + 0xa2, 0xc2, 0x68, 0xe9, 0xbd, 0x5b, 0xd1, 0x64, 0x26, 0x2b, 0x5f, 0x15, 0x2f, 0xd2, 0xf9, 0x94, 0xe5, 0xd1, 0x69, + 0xc2, 0xbc, 0x9c, 0x85, 0x0e, 0xf3, 0xb8, 0x17, 0xbb, 0x61, 0x9f, 0x5b, 0x71, 0x6a, 0xb1, 0xc1, 0x0b, 0x46, 0x25, + 0x0b, 0xa6, 0x5b, 0x05, 0xf7, 0xda, 0x1e, 0x90, 0x6b, 0x1c, 0x9f, 0xcd, 0xf5, 0xfb, 0x45, 0x1e, 0x73, 0xf5, 0x7c, + 0x1e, 0x25, 0x73, 0x16, 0xc4, 0xa5, 0x1b, 0xb0, 0x43, 0x7e, 0x14, 0xc6, 0xde, 0x27, 0x1a, 0x14, 0x86, 0x5c, 0x8c, + 0xb3, 0xdc, 0x41, 0x5a, 0xc5, 0x38, 0x36, 0xbf, 0xbe, 0x76, 0x78, 0xb8, 0x28, 0x5d, 0xf7, 0x13, 0xf3, 0x87, 0x51, + 0x92, 0x38, 0x38, 0xf1, 0xfd, 0xfb, 0x39, 0xce, 0x18, 0x7b, 0xfc, 0x30, 0x3e, 0x72, 0x7b, 0xf1, 0xd8, 0x89, 0x99, + 0x5b, 0xf5, 0xcb, 0xc6, 0x56, 0xcc, 0x1c, 0xee, 0xba, 0xef, 0xd6, 0xf7, 0xc9, 0x19, 0x9f, 0xe7, 0x00, 0x7b, 0xe9, + 0xbd, 0x53, 0x33, 0xef, 0x63, 0xfd, 0x33, 0xea, 0xd8, 0x03, 0xd8, 0x0b, 0x6e, 0x7d, 0x11, 0x5e, 0xc4, 0xe9, 0x28, + 0xbb, 0xf0, 0xf7, 0x27, 0x11, 0xfc, 0xf9, 0x90, 0x65, 0xfc, 0xfe, 0x7d, 0xe7, 0x3c, 0x8b, 0x47, 0x56, 0x3b, 0x0c, + 0xcd, 0xca, 0xab, 0x67, 0xfb, 0xfb, 0xd7, 0xd7, 0x8d, 0x02, 0x3f, 0x8d, 0x78, 0x7c, 0xce, 0x44, 0x67, 0x00, 0xc0, + 0x86, 0xbf, 0x33, 0xce, 0x46, 0xfb, 0xfc, 0x2a, 0x81, 0x52, 0xc6, 0x78, 0x61, 0x03, 0x8e, 0xcf, 0xb3, 0x21, 0x90, + 0x2d, 0x35, 0x08, 0x0f, 0x4d, 0x73, 0x36, 0x4b, 0xa2, 0x21, 0xc3, 0x7a, 0x18, 0xa9, 0xea, 0x51, 0x35, 0xf2, 0xbe, + 0x0b, 0xc5, 0xf2, 0x3a, 0xae, 0x97, 0xb1, 0x30, 0x65, 0x17, 0xd6, 0x9b, 0x68, 0xd6, 0x1b, 0x26, 0x51, 0x51, 0x58, + 0x29, 0x5b, 0x10, 0x0a, 0xf9, 0x7c, 0x08, 0x0c, 0x42, 0x08, 0x2e, 0x80, 0x4c, 0x7c, 0x12, 0x17, 0xfe, 0xf1, 0xc6, + 0xb0, 0x28, 0x3e, 0xb0, 0x62, 0x9e, 0xf0, 0x8d, 0x10, 0xd6, 0x82, 0xdf, 0x0b, 0xc3, 0xef, 0x5c, 0x3e, 0xc9, 0xb3, + 0x0b, 0xeb, 0x45, 0x9e, 0x43, 0x73, 0x1b, 0xa6, 0x14, 0x0d, 0xac, 0x18, 0xc6, 0xca, 0xb8, 0xa5, 0x07, 0xc3, 0x05, + 0xf4, 0xad, 0x8f, 0x05, 0xb3, 0x4e, 0xe6, 0x69, 0x11, 0x8d, 0x19, 0x34, 0x3d, 0xb1, 0xb2, 0xdc, 0x3a, 0x81, 0x41, + 0x4f, 0x60, 0xc9, 0x0a, 0x0e, 0xbb, 0xc6, 0xb7, 0xdd, 0x1e, 0xcd, 0x05, 0x85, 0x07, 0xec, 0x92, 0x87, 0xac, 0x04, + 0xc6, 0xb4, 0x0a, 0x8d, 0x86, 0xe3, 0x2e, 0x12, 0x28, 0x60, 0x61, 0xc6, 0x90, 0x65, 0x1d, 0xb3, 0xb1, 0x5e, 0x9c, + 0x2f, 0xee, 0xdf, 0xd7, 0xb4, 0x06, 0x9a, 0x38, 0xd0, 0xb6, 0x68, 0xb4, 0xf5, 0x04, 0xe2, 0x35, 0x12, 0xb9, 0x1e, + 0xf3, 0x25, 0xf9, 0xf6, 0xaf, 0xd2, 0x61, 0x7d, 0x6c, 0xa8, 0x2c, 0x79, 0xb6, 0xcf, 0xf3, 0x38, 0x3d, 0x03, 0x20, + 0xe4, 0x4c, 0x66, 0x93, 0xb2, 0x14, 0x8b, 0xff, 0x9e, 0x85, 0x2c, 0xec, 0xe3, 0xe8, 0x29, 0x73, 0xec, 0x82, 0x7a, + 0xd8, 0x61, 0x88, 0xa4, 0x07, 0x06, 0x63, 0x03, 0x16, 0xb0, 0x4d, 0xdb, 0xf6, 0xbe, 0x73, 0xbd, 0x2b, 0xe4, 0x20, + 0xdf, 0xf7, 0x89, 0x7d, 0x45, 0xe7, 0x38, 0xec, 0x20, 0xd0, 0x7e, 0xc2, 0xd2, 0x33, 0x3e, 0x19, 0xb0, 0xc3, 0xf6, + 0x51, 0xc0, 0x01, 0xaa, 0xd1, 0x7c, 0xc8, 0x1c, 0xe4, 0x47, 0x2f, 0xc7, 0xed, 0xb3, 0xe9, 0xc0, 0x14, 0xb8, 0x30, + 0xf7, 0x08, 0xc7, 0xda, 0xd2, 0xb8, 0x8a, 0x45, 0x15, 0x60, 0xc8, 0xe7, 0x36, 0xec, 0xb0, 0x53, 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, 0x87, 0xf9, 0x66, 0xe7, 0xc8, 0x43, + 0x28, 0x35, 0xf1, 0x25, 0xe2, 0x31, 0x20, 0x58, 0x7a, 0x1f, 0x99, 0xde, 0x9e, 0x5f, 0x0c, 0x98, 0xbf, 0xcc, 0xc7, + 0x21, 0xf7, 0xa7, 0xd1, 0x0c, 0xb1, 0x61, 0xc4, 0x03, 0x51, 0x3a, 0x44, 0xe8, 0x6a, 0xeb, 0x82, 0x14, 0xf3, 0x2b, + 0x16, 0x70, 0x81, 0x20, 0xb0, 0x67, 0x5f, 0x44, 0xc3, 0x09, 0x6c, 0xf1, 0x8a, 0x70, 0x23, 0xb5, 0x1d, 0x86, 0x39, + 0x8b, 0x38, 0x7b, 0x91, 0x30, 0x7c, 0xc3, 0x15, 0x80, 0x9e, 0xb6, 0xeb, 0xe5, 0x6a, 0xdf, 0x25, 0x31, 0x7f, 0x9b, + 0xc1, 0x3c, 0x3d, 0xc1, 0x24, 0xc0, 0xc5, 0xf9, 0xfd, 0xfb, 0x31, 0xb2, 0xc8, 0x1e, 0x87, 0xd5, 0x3a, 0x9d, 0x73, + 0x58, 0xb7, 0x14, 0x5b, 0xd8, 0x40, 0x6d, 0x2f, 0xf6, 0x39, 0x10, 0xf1, 0x59, 0x96, 0x72, 0x18, 0x0e, 0xe0, 0xd5, + 0x1c, 0xe4, 0x47, 0xb3, 0x19, 0x4b, 0x47, 0xcf, 0x26, 0x71, 0x32, 0x02, 0x6a, 0x94, 0x80, 0x6f, 0xc2, 0x42, 0xc0, + 0x13, 0x90, 0x09, 0x6e, 0xc6, 0x88, 0x96, 0x0f, 0x19, 0x99, 0x87, 0xb6, 0xdd, 0x43, 0x09, 0x24, 0xb1, 0x40, 0x19, + 0x44, 0x0b, 0xf7, 0x01, 0x44, 0x7f, 0xe1, 0xf2, 0xcd, 0x30, 0xd6, 0xcb, 0x28, 0x09, 0xfc, 0x1e, 0x25, 0x0d, 0xd0, + 0x9f, 0x81, 0x0c, 0xec, 0xa1, 0xe0, 0xfa, 0x4a, 0x4a, 0x9d, 0x88, 0x29, 0x0c, 0x81, 0x00, 0x43, 0x94, 0x20, 0x92, + 0x06, 0xef, 0xb3, 0xe4, 0x6a, 0x1c, 0x27, 0xc9, 0xfe, 0x7c, 0x36, 0xcb, 0x72, 0xee, 0x7d, 0x1d, 0x2e, 0x78, 0x56, + 0xe1, 0x4a, 0x9b, 0xbc, 0xb8, 0x88, 0x39, 0x12, 0xd4, 0x5d, 0x0c, 0x23, 0x58, 0xea, 0xa7, 0x59, 0x96, 0xb0, 0x28, + 0x05, 0x34, 0xd8, 0xc0, 0xb6, 0x83, 0x74, 0x9e, 0x24, 0xbd, 0x53, 0x18, 0xf6, 0x53, 0x8f, 0xaa, 0x85, 0xc4, 0x0f, + 0xe8, 0x79, 0x2f, 0xcf, 0xa3, 0x2b, 0x68, 0x88, 0x6d, 0x80, 0x17, 0x61, 0xb5, 0xbe, 0xda, 0x7f, 0xf7, 0xd6, 0x17, + 0x8c, 0x1f, 0x8f, 0xaf, 0x00, 0xd0, 0xb2, 0x92, 0x9a, 0xe3, 0x3c, 0x9b, 0x36, 0xa6, 0x46, 0x3a, 0xc4, 0x21, 0xeb, + 0xad, 0x01, 0x21, 0xa6, 0x91, 0x61, 0x95, 0x98, 0x09, 0xc1, 0x5b, 0xe2, 0x67, 0x59, 0x89, 0x7b, 0x60, 0x80, 0x0f, + 0x81, 0x28, 0x86, 0x29, 0x6f, 0x86, 0x96, 0xe7, 0x57, 0x8b, 0x38, 0x24, 0x38, 0x67, 0xa8, 0x7f, 0x11, 0xc6, 0x61, + 0x04, 0xb3, 0x2f, 0xc4, 0x80, 0xa5, 0x82, 0x38, 0x2e, 0x4b, 0x6f, 0xa2, 0x99, 0x18, 0x25, 0x1e, 0x0a, 0x14, 0x0e, + 0xdb, 0xe8, 0xfa, 0x9a, 0xc1, 0x8b, 0xeb, 0x7d, 0x13, 0x2e, 0x22, 0x85, 0x0f, 0x6a, 0x28, 0xdc, 0x5f, 0x81, 0x90, + 0x13, 0xa8, 0xc9, 0xce, 0x41, 0x0f, 0x02, 0x9c, 0x5f, 0x83, 0xfa, 0x1b, 0x27, 0x08, 0xc5, 0xbd, 0x8e, 0x07, 0x1a, + 0xf4, 0xd9, 0x24, 0x4a, 0xcf, 0xd8, 0x28, 0x98, 0xb0, 0x52, 0x4a, 0xde, 0x3d, 0x0b, 0xd6, 0x18, 0xd8, 0xa9, 0xb0, + 0x5e, 0x1e, 0xbc, 0x79, 0x2d, 0x57, 0xae, 0x26, 0x8c, 0x61, 0x91, 0xe6, 0xa0, 0x56, 0x41, 0x6c, 0x4b, 0x71, 0xfc, + 0x82, 0x2b, 0xe9, 0x2d, 0x4a, 0xe2, 0xe2, 0xe3, 0x0c, 0x4c, 0x0c, 0xf6, 0x1e, 0x86, 0x81, 0xe9, 0x43, 0x98, 0x8a, + 0xca, 0x61, 0x3e, 0x51, 0x31, 0xd2, 0x45, 0xd0, 0x59, 0x60, 0x2a, 0x5e, 0x33, 0xc7, 0x2d, 0x81, 0x55, 0x79, 0x3c, + 0xb4, 0xa2, 0xd1, 0xe8, 0x55, 0x1a, 0xf3, 0x38, 0x4a, 0xe2, 0x5f, 0x88, 0x92, 0x0b, 0xe4, 0x31, 0xde, 0x93, 0x8b, + 0x00, 0xb8, 0x53, 0x8f, 0xc4, 0x55, 0x42, 0xf6, 0x1e, 0x11, 0x43, 0x48, 0xcb, 0x24, 0x3c, 0x3c, 0x92, 0xe0, 0x25, + 0xfe, 0x6c, 0x5e, 0x4c, 0x90, 0xb0, 0x72, 0x60, 0x14, 0xe4, 0xd9, 0x69, 0xc1, 0xf2, 0x73, 0x36, 0xd2, 0x1c, 0x50, + 0x00, 0x56, 0xd4, 0x1c, 0x8c, 0x17, 0x9a, 0xd1, 0x51, 0x3a, 0x94, 0xc1, 0x50, 0x3d, 0x53, 0xcc, 0x32, 0xc9, 0xcc, + 0xda, 0xc2, 0xd1, 0x52, 0xc0, 0x11, 0x46, 0x85, 0x94, 0x04, 0x79, 0xa8, 0x30, 0x9c, 0x80, 0x14, 0x02, 0xad, 0x60, + 0x6e, 0x73, 0xa5, 0xc9, 0x5e, 0xcc, 0x49, 0x25, 0xe4, 0xd0, 0x11, 0x36, 0x32, 0x41, 0x9a, 0xbb, 0xb0, 0xab, 0x40, + 0xca, 0x4b, 0x70, 0x85, 0x14, 0x51, 0x66, 0x0e, 0x32, 0x40, 0xf8, 0x8d, 0xd0, 0x85, 0x3e, 0xb6, 0x20, 0x36, 0xf0, + 0xf5, 0xca, 0x03, 0x61, 0x25, 0xde, 0x15, 0x22, 0xde, 0x1a, 0xb0, 0x71, 0x62, 0xe4, 0x27, 0xef, 0x1e, 0xf7, 0xd3, + 0x6c, 0x6f, 0x38, 0x64, 0x45, 0x91, 0x01, 0x6c, 0xf7, 0xa8, 0xfd, 0x3a, 0x43, 0x0b, 0x28, 0xe9, 0x6a, 0x59, 0x67, + 0x17, 0xa4, 0xc1, 0x4d, 0xb5, 0xa2, 0x74, 0x7a, 0x60, 0x1f, 0x1f, 0x83, 0xcc, 0xf6, 0x24, 0x19, 0x80, 0xea, 0xcb, + 0x86, 0x9f, 0xb0, 0x67, 0xea, 0x94, 0x59, 0x69, 0x5f, 0x3a, 0x75, 0x90, 0x3c, 0x18, 0xd6, 0x2d, 0x8d, 0x05, 0x5d, + 0x39, 0x34, 0xae, 0x86, 0x54, 0x90, 0x8b, 0x33, 0x52, 0xd9, 0xc6, 0x32, 0x82, 0xd5, 0x56, 0x7a, 0x44, 0x7a, 0x85, + 0x4d, 0x41, 0x80, 0x1e, 0xb2, 0xa3, 0x9e, 0xac, 0x0f, 0x73, 0x41, 0xb9, 0x9c, 0xfd, 0x3c, 0x67, 0x05, 0x17, 0xac, + 0x0b, 0xe3, 0x82, 0xb9, 0x0a, 0x22, 0xb6, 0x69, 0x1d, 0xd6, 0x6c, 0xc7, 0x55, 0xb0, 0xbd, 0x9b, 0xa1, 0x1e, 0x2b, + 0x90, 0x93, 0x6f, 0x66, 0x27, 0x84, 0x95, 0xb9, 0xd7, 0xd7, 0xdf, 0xa8, 0x41, 0xaa, 0xa5, 0xd4, 0x36, 0x50, 0x63, + 0x4d, 0x6c, 0xd5, 0x64, 0x64, 0xbb, 0x52, 0xa1, 0xde, 0xeb, 0xf4, 0x6a, 0x7c, 0x00, 0x7b, 0xae, 0xad, 0x59, 0xba, + 0x32, 0xb6, 0xdf, 0x2b, 0x9a, 0xbe, 0x13, 0x23, 0x93, 0x35, 0xca, 0x6e, 0xe7, 0x1e, 0xb5, 0xe3, 0xa1, 0xed, 0x52, + 0x5d, 0x25, 0x18, 0xe6, 0x75, 0xc1, 0xd0, 0x84, 0x7a, 0xa6, 0xbb, 0xd8, 0x9a, 0xa9, 0x58, 0xa8, 0xd6, 0x5a, 0x39, + 0x10, 0x3c, 0x3c, 0x04, 0xe3, 0x64, 0xa5, 0x7f, 0xf0, 0x36, 0x9a, 0x32, 0xa4, 0xa8, 0xb7, 0xae, 0x81, 0x74, 0x20, + 0xa0, 0xc9, 0x51, 0x53, 0xbd, 0x71, 0x57, 0x58, 0x4d, 0xf5, 0xfd, 0x15, 0x83, 0x15, 0x01, 0xf6, 0x75, 0xb9, 0x62, + 0x89, 0x48, 0x6f, 0x0a, 0x2e, 0xd1, 0xf4, 0x11, 0x65, 0x62, 0x4d, 0x48, 0xc1, 0x03, 0xf2, 0xb0, 0xfc, 0x8d, 0x85, + 0x93, 0xad, 0x98, 0xc2, 0x91, 0xa3, 0x4c, 0x01, 0x3a, 0x93, 0x12, 0x00, 0x71, 0x49, 0x7f, 0x6b, 0x1b, 0x0b, 0xc9, + 0xb6, 0x8f, 0x7c, 0xe0, 0x8f, 0x93, 0x88, 0x3b, 0x9d, 0xad, 0xb6, 0x0b, 0x7c, 0x08, 0x42, 0x1c, 0x74, 0x04, 0x98, + 0xf7, 0x15, 0x2a, 0x8c, 0xbc, 0x05, 0x97, 0xfb, 0x60, 0x14, 0x4d, 0xe2, 0x31, 0x77, 0x12, 0x54, 0x22, 0x6e, 0xc9, + 0x12, 0x50, 0x32, 0x7a, 0x5f, 0x81, 0x94, 0xe0, 0x42, 0xba, 0x88, 0x6a, 0x2d, 0xd0, 0x14, 0xa4, 0x24, 0xa5, 0x48, + 0x0b, 0x2a, 0x08, 0x0c, 0xa1, 0xd2, 0x53, 0x1c, 0x05, 0xfa, 0x2d, 0x1e, 0x88, 0x41, 0x83, 0x25, 0x8b, 0x32, 0x1e, + 0xc4, 0xcb, 0x85, 0xa0, 0x86, 0x7d, 0x9e, 0xbd, 0xce, 0x2e, 0x58, 0xfe, 0x2c, 0x42, 0xd8, 0x03, 0xd1, 0xbd, 0x04, + 0x49, 0x4f, 0x02, 0x9d, 0xf5, 0x14, 0xaf, 0x9c, 0x13, 0xd2, 0xb0, 0x10, 0xd3, 0x18, 0x15, 0x21, 0x68, 0x39, 0xa2, + 0x7d, 0x8a, 0x5b, 0x8a, 0xf6, 0x1e, 0xaa, 0x12, 0xa6, 0x79, 0x6b, 0xef, 0x75, 0x9d, 0xb7, 0x60, 0x84, 0x99, 0xe2, + 0xd6, 0xfa, 0x8e, 0x75, 0x3d, 0xa9, 0x9b, 0x1d, 0xc9, 0x5b, 0x86, 0x32, 0x03, 0xfd, 0x71, 0x7d, 0x5d, 0x19, 0xe9, + 0xa0, 0x4c, 0xb5, 0x34, 0x47, 0xcb, 0x49, 0x6c, 0x09, 0xb7, 0x04, 0x65, 0x84, 0x86, 0x57, 0x9e, 0x25, 0x89, 0xa1, + 0x8b, 0xbc, 0xb8, 0xe7, 0x34, 0xd4, 0x11, 0x40, 0x31, 0xad, 0x69, 0xa4, 0x01, 0x0f, 0x74, 0x05, 0x2a, 0x25, 0xa5, + 0x8d, 0xbc, 0xaa, 0x89, 0x80, 0x38, 0x1d, 0xb1, 0x5c, 0x38, 0x68, 0x52, 0x87, 0xc2, 0x84, 0x29, 0x30, 0x34, 0x1b, + 0x81, 0x84, 0x57, 0x08, 0x80, 0x79, 0xe2, 0x4f, 0xb2, 0x82, 0xeb, 0x3a, 0x13, 0xfa, 0xf8, 0xfa, 0x3a, 0x16, 0xfe, + 0x22, 0x32, 0x40, 0xce, 0xa6, 0xd9, 0x39, 0x5b, 0x01, 0x75, 0x4f, 0x0d, 0x66, 0x82, 0x6c, 0x0c, 0x03, 0x4a, 0x14, + 0x54, 0xcb, 0x2c, 0x89, 0x87, 0x4c, 0x6b, 0xa9, 0xa9, 0x0f, 0x06, 0x1d, 0xbb, 0x04, 0x19, 0xc1, 0xdc, 0x7e, 0xbf, + 0xdf, 0xf6, 0x3a, 0x6e, 0x29, 0x08, 0xbe, 0x58, 0xa2, 0xe8, 0x0d, 0xfa, 0x51, 0x9a, 0xe0, 0xab, 0x64, 0x01, 0x77, + 0x0d, 0xa5, 0xc8, 0x85, 0x9f, 0xe4, 0x49, 0x41, 0xec, 0x7a, 0x23, 0x18, 0x94, 0x33, 0x25, 0xb8, 0xd1, 0xc4, 0x15, + 0xdb, 0xf6, 0x83, 0x26, 0x9b, 0x66, 0x27, 0xb5, 0xc3, 0xd4, 0xc2, 0xc8, 0x35, 0x2f, 0xb4, 0x07, 0x6c, 0x2e, 0x0f, + 0x5a, 0x89, 0x54, 0x0d, 0xbc, 0x0e, 0x10, 0x0a, 0x4f, 0xd7, 0x59, 0x42, 0xa9, 0xea, 0x2c, 0x85, 0xb8, 0xde, 0x40, + 0x1f, 0x99, 0x04, 0x73, 0x15, 0x09, 0xf6, 0xa5, 0x40, 0xe0, 0xe8, 0x91, 0x89, 0xf5, 0x7a, 0x06, 0xcb, 0x73, 0x1a, + 0x0d, 0x3f, 0x69, 0x70, 0x2b, 0xb2, 0x37, 0xd9, 0xc0, 0x69, 0x94, 0x84, 0x86, 0xb8, 0x32, 0xf1, 0x56, 0x12, 0xba, + 0xb6, 0x51, 0xc0, 0x21, 0x5b, 0x62, 0xfb, 0xe6, 0x42, 0x37, 0xb9, 0x5d, 0xb2, 0x87, 0xf2, 0x9f, 0x34, 0x97, 0xdc, + 0xc0, 0x72, 0x5c, 0x49, 0x03, 0xae, 0x18, 0x0f, 0x96, 0xa6, 0x01, 0x09, 0xf0, 0x5d, 0x39, 0x8a, 0x8b, 0xf5, 0x24, + 0xf8, 0x5d, 0xc1, 0x7c, 0x6e, 0xcc, 0x74, 0x2b, 0xa4, 0x5a, 0xc2, 0x49, 0x33, 0x58, 0x83, 0x26, 0x8d, 0x07, 0x25, + 0x6a, 0xbe, 0x46, 0x43, 0x85, 0x38, 0xfe, 0x4c, 0x54, 0xa1, 0x09, 0x86, 0x60, 0xe4, 0x5e, 0x21, 0x19, 0x2e, 0x5b, + 0x16, 0x2d, 0x52, 0xa6, 0xc6, 0xa4, 0x52, 0x35, 0xcb, 0x65, 0x60, 0x60, 0xd1, 0x6e, 0xf5, 0xa5, 0x25, 0xae, 0x44, + 0x6e, 0x1a, 0x6a, 0x61, 0x52, 0x28, 0x6f, 0xc2, 0xc9, 0xd1, 0xef, 0x52, 0xd6, 0xbb, 0x89, 0x4f, 0xae, 0xf0, 0xc9, + 0x7d, 0xc3, 0x87, 0x32, 0x79, 0xbb, 0x18, 0x14, 0xc1, 0xd7, 0xb5, 0x4a, 0xb4, 0x4f, 0x7d, 0x14, 0xcc, 0xae, 0x16, + 0xba, 0x20, 0x50, 0x24, 0x9b, 0xa4, 0x03, 0xc9, 0x6f, 0x28, 0x36, 0x2a, 0xcf, 0x28, 0x73, 0xc5, 0x06, 0xa9, 0x79, + 0xa5, 0x99, 0x97, 0xba, 0x0d, 0xfb, 0xbd, 0x2c, 0x25, 0x9d, 0xb8, 0xa0, 0x4c, 0xec, 0xdd, 0x44, 0x1b, 0x2f, 0x0d, + 0x33, 0x61, 0xfd, 0x0a, 0x63, 0xa7, 0x46, 0xa1, 0x54, 0x8a, 0x40, 0x1c, 0x1b, 0x5f, 0x2b, 0xcb, 0x20, 0xf3, 0x57, + 0xd8, 0x53, 0x00, 0x4a, 0x02, 0x8b, 0xaf, 0xa9, 0xe4, 0x45, 0x61, 0x9d, 0x8e, 0xf7, 0x88, 0x8e, 0x95, 0x08, 0xad, + 0x89, 0x7c, 0xad, 0xcf, 0x62, 0xbf, 0xe6, 0x12, 0x9a, 0x94, 0xcc, 0x07, 0x79, 0x60, 0xab, 0x40, 0x44, 0xa5, 0xdb, + 0x92, 0x41, 0x42, 0x0e, 0xe9, 0x32, 0xd1, 0x6b, 0x23, 0x19, 0xb4, 0x4e, 0x85, 0x44, 0x4b, 0x8f, 0xc2, 0xc8, 0x41, + 0xc7, 0x9d, 0xd6, 0x62, 0x89, 0x90, 0x4d, 0x7b, 0x93, 0x58, 0x11, 0x9d, 0xd3, 0x1c, 0x4d, 0x38, 0x53, 0xa7, 0x3b, + 0x0e, 0xa0, 0x03, 0x62, 0x7f, 0x89, 0xf5, 0x56, 0x9a, 0x9d, 0xae, 0x5f, 0x39, 0x7c, 0xd7, 0xd7, 0x13, 0xe4, 0x07, + 0x61, 0xf0, 0xc2, 0x9a, 0x0d, 0x94, 0xec, 0xdd, 0x7b, 0x8d, 0xad, 0xc8, 0xfe, 0xac, 0x4a, 0x2a, 0x4f, 0xa1, 0xc6, + 0xb9, 0xf5, 0x75, 0x62, 0x66, 0x68, 0x51, 0x55, 0xec, 0x1b, 0x52, 0x7d, 0x5f, 0x29, 0xec, 0x0a, 0xe5, 0x7d, 0x39, + 0x74, 0xec, 0xba, 0x6e, 0x90, 0x93, 0xf3, 0x72, 0x6f, 0x95, 0x0b, 0x79, 0xff, 0xbe, 0xe9, 0x33, 0x9d, 0xeb, 0xe1, + 0x9f, 0x39, 0xa8, 0x9c, 0x8b, 0xab, 0x94, 0x2c, 0x98, 0x67, 0x4a, 0x1d, 0x2d, 0x39, 0xa0, 0xed, 0x1e, 0x7a, 0xda, + 0xd1, 0x45, 0x14, 0x73, 0x4b, 0x8f, 0x22, 0x3c, 0x6d, 0x94, 0x4f, 0xd2, 0xe8, 0x00, 0xbc, 0xd0, 0x84, 0x24, 0x27, + 0xdc, 0xb4, 0x45, 0x8b, 0xe1, 0x84, 0x61, 0x08, 0x5c, 0xd9, 0x13, 0xa6, 0xec, 0xb9, 0x87, 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, 0x11, 0x27, 0x49, 0x55, 0xfa, + 0xbb, 0x0a, 0x78, 0x31, 0xec, 0x6d, 0xa2, 0x5d, 0x60, 0x34, 0x57, 0x20, 0xb8, 0xda, 0x08, 0xfb, 0xe8, 0xb8, 0xd5, + 0xba, 0x8b, 0x88, 0x23, 0x37, 0xa3, 0x11, 0x50, 0x8f, 0x11, 0x56, 0xcd, 0xda, 0x7b, 0x2f, 0x30, 0xa4, 0x66, 0xe0, + 0x83, 0xea, 0x8c, 0x8a, 0x7f, 0x95, 0x3d, 0xf5, 0x2b, 0xd1, 0xbb, 0x55, 0x75, 0x35, 0x03, 0x2a, 0x2a, 0xf0, 0x61, + 0x86, 0x58, 0xda, 0x2a, 0x10, 0x90, 0xeb, 0x61, 0x51, 0x0a, 0x98, 0xa4, 0xc1, 0x82, 0x52, 0x60, 0xad, 0x95, 0xdd, + 0xeb, 0xdb, 0x82, 0x39, 0x14, 0x0a, 0x17, 0xfd, 0x9f, 0x65, 0xd3, 0x19, 0x5a, 0x66, 0x0d, 0xa6, 0x86, 0x06, 0x1f, + 0x1b, 0xf5, 0xe5, 0x8a, 0xb2, 0x5a, 0x1f, 0xda, 0x91, 0x35, 0x7e, 0xd2, 0x8e, 0x32, 0x38, 0x54, 0x73, 0x5d, 0x54, + 0xb7, 0x9b, 0x9b, 0x22, 0x66, 0x15, 0x8f, 0xfb, 0xa4, 0xb7, 0xb5, 0x35, 0xe9, 0x69, 0x1a, 0x90, 0x4c, 0x92, 0x0c, + 0x6f, 0x32, 0x40, 0x59, 0x11, 0x67, 0x51, 0x36, 0xc8, 0xb7, 0x28, 0x4b, 0x5c, 0xbf, 0x1f, 0x7a, 0x7b, 0x35, 0xcf, + 0xda, 0xdb, 0x5b, 0xef, 0x22, 0x57, 0x75, 0xd2, 0x83, 0x3c, 0x3c, 0x82, 0xa2, 0x25, 0x9b, 0x32, 0x5c, 0x4c, 0xb3, + 0x11, 0x0b, 0x6c, 0xe8, 0x9e, 0xda, 0xa5, 0xdc, 0x34, 0x11, 0x6c, 0x8e, 0x88, 0x39, 0x8b, 0x0f, 0xf5, 0x48, 0x6a, + 0xb0, 0x07, 0x2c, 0xa0, 0xcd, 0x85, 0xaf, 0xc2, 0xb3, 0x24, 0x3b, 0x8d, 0x92, 0x03, 0xa1, 0xc0, 0x6b, 0x2d, 0xbf, + 0x05, 0x97, 0x91, 0x2c, 0x56, 0x43, 0x49, 0x7d, 0x35, 0xf8, 0x2a, 0xb8, 0xbd, 0x47, 0xe5, 0xad, 0xd8, 0x1d, 0xbf, + 0xed, 0x77, 0x6c, 0x15, 0x11, 0xfb, 0xc9, 0x9c, 0x0e, 0x34, 0x4e, 0x01, 0x94, 0x39, 0x00, 0x4d, 0x56, 0x78, 0x43, + 0x16, 0xfe, 0x34, 0xf8, 0x49, 0xb9, 0xd4, 0x19, 0xb8, 0x10, 0xe0, 0xe4, 0x27, 0x31, 0x6f, 0xe1, 0x79, 0xa4, 0xed, + 0x2d, 0x44, 0x05, 0xc6, 0x15, 0x29, 0x2e, 0x5d, 0x2a, 0x6f, 0xd0, 0x3b, 0x0e, 0x4f, 0xa0, 0xd9, 0xc6, 0xc6, 0xc2, + 0x79, 0x13, 0xf1, 0x89, 0x9f, 0x47, 0xe9, 0x28, 0x9b, 0x3a, 0xee, 0xa6, 0x6d, 0xbb, 0x7e, 0x41, 0x9e, 0xc8, 0xe7, + 0x6e, 0xb9, 0x71, 0x02, 0x7e, 0x40, 0x68, 0x0f, 0xec, 0xcd, 0x63, 0xef, 0x80, 0x85, 0x27, 0xbb, 0x1b, 0x8b, 0x11, + 0x2b, 0xfb, 0x27, 0xde, 0xa5, 0x8e, 0xb9, 0x7b, 0xef, 0x51, 0xca, 0x40, 0xaf, 0xb0, 0x7f, 0x29, 0xc1, 0x00, 0x76, + 0xa3, 0xf8, 0x3b, 0x48, 0xb9, 0x8f, 0x74, 0x20, 0x22, 0xe3, 0xb4, 0xd7, 0xd7, 0x76, 0x46, 0x11, 0x03, 0xfb, 0x9e, + 0x76, 0x56, 0xef, 0xdf, 0xaf, 0xd4, 0x7c, 0x55, 0xea, 0xcd, 0x59, 0x58, 0xf3, 0xd4, 0xbd, 0x97, 0x74, 0xb4, 0x52, + 0xdf, 0xc8, 0x73, 0x46, 0x4a, 0x73, 0xd9, 0x4e, 0x70, 0x8c, 0x2d, 0xbe, 0x7a, 0x5b, 0x1f, 0x8a, 0x28, 0x85, 0x1f, + 0x83, 0xf5, 0x12, 0x81, 0xfa, 0x06, 0x07, 0xc7, 0x3b, 0x08, 0xb7, 0x76, 0x9d, 0x41, 0xe0, 0xdc, 0x6b, 0xb5, 0xae, + 0x7f, 0xdc, 0x3a, 0xfc, 0x73, 0xd4, 0xfa, 0x65, 0xaf, 0xf5, 0xc3, 0x91, 0x7b, 0xed, 0xfc, 0xb8, 0x35, 0x38, 0x94, + 0x6f, 0x87, 0x7f, 0xee, 0xff, 0x58, 0x1c, 0xfd, 0x41, 0x14, 0x6e, 0xb8, 0xee, 0xd6, 0x99, 0x37, 0x63, 0xe1, 0x56, + 0xab, 0xd5, 0x87, 0xa7, 0x33, 0x78, 0xc2, 0xbf, 0x17, 0xf0, 0xe7, 0xfa, 0xd0, 0xfa, 0x4f, 0x3f, 0xa6, 0xff, 0xf9, + 0xc7, 0xfc, 0x08, 0xc7, 0x3c, 0xfc, 0xf3, 0x8f, 0x85, 0xfd, 0xa0, 0x1f, 0x6e, 0x1d, 0x6d, 0xba, 0x8e, 0xae, 0xf9, + 0x43, 0x58, 0x3d, 0x42, 0xab, 0xc3, 0x3f, 0xcb, 0x37, 0xfb, 0xc1, 0xc9, 0x6e, 0x3f, 0x3c, 0xba, 0x76, 0xec, 0xeb, + 0x07, 0xee, 0xb5, 0xeb, 0x5e, 0x6f, 0xe0, 0x3c, 0xe7, 0x30, 0xfa, 0x03, 0xf8, 0x3b, 0x86, 0xbf, 0x36, 0xfc, 0x9d, + 0xc2, 0xdf, 0x3f, 0x43, 0x37, 0x11, 0x7f, 0xbb, 0xa6, 0x58, 0xc8, 0x35, 0x1e, 0x58, 0x44, 0xb0, 0x0a, 0xee, 0xc6, + 0x56, 0xec, 0x6d, 0x10, 0xd1, 0x60, 0x1f, 0xfa, 0xbe, 0x8f, 0x61, 0x52, 0x67, 0x71, 0xbc, 0x01, 0x8b, 0x8e, 0x9c, + 0xb3, 0x11, 0x30, 0x4f, 0x44, 0x0e, 0x8a, 0x80, 0x8b, 0xb3, 0xd5, 0x02, 0x0f, 0x57, 0xbd, 0x61, 0xb8, 0xc1, 0x1c, + 0x30, 0x0a, 0xde, 0x32, 0x7c, 0xe8, 0xba, 0xde, 0x0b, 0x79, 0x66, 0x88, 0xfb, 0x5c, 0xb0, 0x56, 0x9a, 0x09, 0x93, + 0xc6, 0x76, 0xbd, 0xd9, 0x8a, 0x4a, 0xd8, 0xd6, 0xe9, 0x19, 0xd4, 0x9d, 0x8a, 0x83, 0xb6, 0xef, 0x58, 0xf4, 0x09, + 0xb7, 0xe4, 0x1b, 0xe3, 0x10, 0x78, 0xc9, 0x92, 0x6f, 0x1a, 0x8d, 0x86, 0x8d, 0x28, 0xdc, 0xb1, 0xa7, 0x0c, 0x66, + 0x58, 0x32, 0x11, 0x39, 0x29, 0x4d, 0x61, 0xd9, 0xc2, 0xe4, 0xef, 0xa3, 0x9c, 0x6f, 0x54, 0x86, 0x6d, 0x58, 0xb3, + 0x64, 0x9b, 0x96, 0xfe, 0x1d, 0xa6, 0x40, 0xd3, 0x92, 0xce, 0x3f, 0xcc, 0xf1, 0xc3, 0x94, 0xd0, 0x7a, 0xeb, 0x70, + 0xf0, 0xd0, 0x0b, 0x90, 0x3b, 0xa2, 0x9f, 0xf3, 0x1e, 0xd5, 0x18, 0xfc, 0x2b, 0xc3, 0x0c, 0x9e, 0x98, 0x0f, 0x43, + 0x34, 0x8b, 0x52, 0x07, 0xb7, 0x52, 0x14, 0xf7, 0xaf, 0x70, 0x67, 0xa4, 0xa5, 0xb7, 0x1f, 0xaa, 0x1d, 0x73, 0x90, + 0x33, 0xf6, 0x5d, 0x94, 0x7c, 0x62, 0xb9, 0x73, 0xe9, 0x75, 0xba, 0x9f, 0x53, 0x67, 0x0f, 0x6d, 0xb3, 0x0f, 0xd5, + 0x31, 0x9a, 0x32, 0x0b, 0xd4, 0x11, 0x61, 0xab, 0xe3, 0xe5, 0x18, 0xd5, 0x42, 0x12, 0x14, 0x5e, 0x16, 0x76, 0x89, + 0xc3, 0xed, 0xdd, 0xe2, 0xfc, 0xac, 0x6f, 0x07, 0xb6, 0x0d, 0x16, 0xff, 0x01, 0x85, 0xad, 0x84, 0x61, 0x01, 0x06, + 0xd9, 0x6e, 0xdc, 0xe3, 0x9b, 0x9b, 0x55, 0xc0, 0x09, 0x0f, 0xd2, 0xa9, 0x7b, 0xe2, 0x45, 0xde, 0x24, 0x84, 0x01, + 0x87, 0xd0, 0x0c, 0xbb, 0xf4, 0x86, 0xbb, 0xb1, 0x9c, 0x06, 0x63, 0x21, 0x7e, 0x12, 0x15, 0xfc, 0x15, 0xc6, 0x23, + 0xc2, 0x21, 0x1a, 0xfb, 0x3e, 0xbb, 0x64, 0x43, 0x65, 0x67, 0x00, 0xa1, 0x22, 0xb7, 0xe7, 0x0e, 0x43, 0xa3, 0x19, + 0xcc, 0x1d, 0x86, 0x07, 0x03, 0x1b, 0xf6, 0x12, 0xec, 0xca, 0x30, 0x3a, 0xec, 0x1c, 0x0d, 0xd2, 0x70, 0xc6, 0x02, + 0x4d, 0x5b, 0x59, 0x74, 0x56, 0x2b, 0xea, 0x1e, 0x0d, 0x9c, 0x29, 0x18, 0xe9, 0x60, 0x8b, 0x3b, 0xf8, 0x86, 0x11, + 0x8a, 0x22, 0xfc, 0xc0, 0xce, 0x5e, 0x5c, 0xce, 0x1c, 0x7b, 0x77, 0xcb, 0xde, 0xc4, 0x52, 0xcf, 0x06, 0xf6, 0x82, + 0xb9, 0xc3, 0x0b, 0xd7, 0xec, 0xbc, 0x7d, 0x84, 0xa0, 0x62, 0x21, 0x4e, 0x7e, 0x31, 0xb0, 0xfb, 0x62, 0xea, 0x36, + 0x0c, 0x9a, 0xca, 0xe5, 0xc7, 0x15, 0x3d, 0x20, 0x54, 0x55, 0x57, 0x05, 0x1d, 0x94, 0x75, 0x03, 0x67, 0x62, 0x22, + 0xd1, 0xc2, 0xc9, 0x24, 0x15, 0xc0, 0xe1, 0xc1, 0x66, 0x30, 0xa9, 0xd1, 0x6d, 0xfb, 0x68, 0x70, 0x11, 0x3c, 0xb0, + 0x1f, 0xa8, 0x97, 0x31, 0x20, 0xc3, 0xc4, 0xf4, 0x63, 0x90, 0x76, 0xf8, 0xf7, 0x9c, 0x01, 0x92, 0x17, 0x54, 0x34, + 0x93, 0x45, 0x67, 0x58, 0x74, 0x10, 0x20, 0xa8, 0x5e, 0xa1, 0xad, 0x3f, 0xb1, 0x26, 0xa3, 0x90, 0x60, 0xbf, 0x7f, + 0x1f, 0x96, 0x66, 0xb3, 0x73, 0x84, 0xe7, 0x0d, 0x39, 0x2f, 0xbe, 0x8b, 0x39, 0xa8, 0x84, 0xad, 0xbe, 0xed, 0x0e, + 0x6c, 0x0b, 0x97, 0xb6, 0x97, 0x6d, 0x86, 0x82, 0xc2, 0xf1, 0xe6, 0x01, 0x0b, 0x26, 0xfd, 0xb0, 0x3d, 0x70, 0x72, + 0x19, 0x6e, 0xc4, 0x73, 0x4b, 0x21, 0xc1, 0xdb, 0xde, 0x04, 0x04, 0x3a, 0x72, 0xee, 0x86, 0xbd, 0xa9, 0x0a, 0xa1, + 0xe8, 0x78, 0x73, 0xe4, 0x06, 0x31, 0xfc, 0x71, 0x5a, 0xc8, 0x34, 0x13, 0xdd, 0x57, 0x6b, 0x66, 0x37, 0x18, 0x29, + 0x8b, 0x3c, 0x09, 0xb3, 0x4d, 0x07, 0x23, 0xb4, 0x20, 0x69, 0x77, 0x07, 0x00, 0xc3, 0xa6, 0xa3, 0x38, 0x6d, 0x4b, + 0xb1, 0x9a, 0xb2, 0xcf, 0x0f, 0xf5, 0x72, 0x0c, 0xd9, 0x60, 0xc8, 0xfc, 0x4a, 0xfb, 0x00, 0x58, 0x41, 0xe2, 0xe5, + 0x47, 0xea, 0xcc, 0xeb, 0x65, 0xed, 0x7c, 0x6b, 0xa1, 0x44, 0x11, 0xf3, 0x0c, 0x09, 0xc5, 0x4b, 0xed, 0x86, 0x09, + 0x73, 0x7b, 0x86, 0xc4, 0xd0, 0x2c, 0x1f, 0xb6, 0x81, 0xe9, 0x55, 0x80, 0x3d, 0x35, 0xb7, 0x45, 0x12, 0x56, 0xcd, + 0xbd, 0x43, 0x60, 0xed, 0x23, 0xe0, 0x21, 0xda, 0x46, 0x3d, 0x15, 0xcd, 0x67, 0x49, 0xf8, 0xb2, 0x71, 0x5c, 0x1c, + 0xe1, 0x89, 0xd0, 0xbe, 0x3f, 0x9c, 0xe7, 0x20, 0x0f, 0xf8, 0x5b, 0xb0, 0x0c, 0x42, 0xd9, 0x14, 0x1d, 0x3d, 0x3c, + 0x02, 0xf6, 0x08, 0xf1, 0x46, 0xd8, 0xdc, 0xa8, 0x46, 0x8b, 0x92, 0x8c, 0x17, 0x3a, 0x18, 0xee, 0x71, 0xe9, 0xda, + 0xa3, 0x60, 0x90, 0x27, 0xc6, 0x0e, 0x9e, 0xf9, 0xfb, 0x43, 0xac, 0xc6, 0x09, 0x0a, 0xb7, 0xa4, 0xdd, 0x56, 0x89, + 0xbf, 0x7d, 0x3f, 0x05, 0x09, 0x8e, 0x75, 0xe0, 0x67, 0xdd, 0xbf, 0x9f, 0x48, 0xa4, 0x76, 0xd3, 0x1e, 0x9d, 0x44, + 0x60, 0x3c, 0x38, 0xf7, 0x53, 0xa8, 0x46, 0x12, 0x51, 0x51, 0x8e, 0x16, 0xa8, 0x79, 0xaa, 0x56, 0xc1, 0x77, 0x68, + 0x46, 0xe0, 0x39, 0x86, 0xad, 0xc9, 0x4f, 0xd5, 0x8d, 0x45, 0x2c, 0xdf, 0x75, 0xe9, 0x68, 0x0b, 0x0f, 0x20, 0x05, + 0xa3, 0x09, 0x86, 0x71, 0x29, 0x28, 0x59, 0xf1, 0xdf, 0xb1, 0x11, 0x2b, 0x9f, 0x1c, 0x66, 0x9b, 0x9b, 0x47, 0xe2, + 0xdc, 0x82, 0x18, 0x87, 0x1b, 0xd1, 0xd5, 0xb8, 0x02, 0xa0, 0x3e, 0x9d, 0x13, 0xd7, 0x03, 0xd3, 0x8a, 0x35, 0x5d, + 0x8a, 0x7d, 0x72, 0x98, 0x01, 0x28, 0xb8, 0xe5, 0x1c, 0xfa, 0x83, 0x3f, 0x1e, 0x81, 0x7b, 0xec, 0xff, 0xc1, 0xdd, + 0x52, 0x82, 0xa6, 0x27, 0xcf, 0x14, 0x17, 0x74, 0xc6, 0xda, 0xf1, 0x28, 0x36, 0x1a, 0x14, 0x5e, 0x0a, 0x18, 0x80, + 0x36, 0x07, 0x99, 0x50, 0x71, 0x10, 0x72, 0x54, 0x60, 0xfb, 0xb8, 0xf9, 0x39, 0xee, 0xec, 0xe7, 0x60, 0xe1, 0x0d, + 0xf4, 0xdb, 0x6b, 0x78, 0xfb, 0xa3, 0x7e, 0xfb, 0x89, 0x05, 0xbf, 0x94, 0x32, 0x74, 0x5f, 0x9b, 0xe2, 0x91, 0x9a, + 0xa2, 0x14, 0x4b, 0x64, 0xd0, 0x90, 0xb9, 0xf9, 0x52, 0xcc, 0x86, 0xbb, 0x25, 0x10, 0x43, 0x89, 0xae, 0xdc, 0xe7, + 0xd1, 0x19, 0x12, 0xd7, 0x35, 0x49, 0x61, 0xe4, 0x12, 0x98, 0x08, 0x57, 0x7c, 0x4b, 0xcc, 0xd9, 0x6f, 0x83, 0x0d, + 0x5e, 0xcb, 0x3b, 0x40, 0xfb, 0x8e, 0x4d, 0x67, 0xfc, 0x6a, 0x9f, 0x14, 0x7d, 0x20, 0xd3, 0x06, 0xc4, 0xd9, 0x79, + 0xbb, 0x17, 0xef, 0xf2, 0x5e, 0x0c, 0x52, 0x3d, 0x57, 0x2c, 0x86, 0x7b, 0xd5, 0x7b, 0x8f, 0x51, 0x4a, 0x93, 0x99, + 0xbc, 0x1a, 0x7a, 0x5d, 0x89, 0xde, 0xe6, 0x26, 0x20, 0xd8, 0x33, 0xba, 0x72, 0xd1, 0xb5, 0x2c, 0x05, 0x4d, 0x00, + 0xa2, 0x27, 0x75, 0x96, 0x23, 0x8e, 0xc3, 0x6c, 0x36, 0x28, 0x1e, 0x31, 0x77, 0xe5, 0xa8, 0x38, 0x26, 0x76, 0x97, + 0x09, 0x3b, 0x80, 0x19, 0x71, 0x79, 0xab, 0x23, 0xa2, 0xc3, 0xa2, 0xbf, 0x8e, 0x6f, 0x1f, 0x7b, 0x6c, 0xb3, 0xe3, + 0x82, 0x06, 0xa9, 0x8d, 0xf5, 0xb8, 0x1a, 0x0b, 0xea, 0xc3, 0x63, 0x4d, 0xa5, 0xb2, 0xd8, 0xdc, 0x2c, 0xeb, 0x47, + 0xb5, 0x6a, 0x07, 0xd7, 0x4e, 0x53, 0x2e, 0x9b, 0xd9, 0x20, 0x1c, 0x88, 0x98, 0x40, 0x81, 0x96, 0x56, 0x56, 0x0c, + 0x30, 0xa4, 0x2c, 0x47, 0xf9, 0x14, 0x32, 0x2f, 0x2e, 0x4b, 0x9d, 0xfa, 0xf2, 0x4c, 0x06, 0x1d, 0xf1, 0xd4, 0x93, + 0x8c, 0x15, 0x50, 0xb0, 0x5e, 0xea, 0x25, 0xb4, 0x44, 0x80, 0xf9, 0x0b, 0x95, 0x43, 0x23, 0x2c, 0x90, 0x28, 0x34, + 0xcc, 0x12, 0x65, 0x7c, 0x16, 0x61, 0x0c, 0xda, 0xfe, 0x59, 0x2d, 0xf6, 0x55, 0x28, 0xa3, 0xa3, 0x38, 0xcc, 0x8f, + 0x02, 0xaa, 0x9f, 0x4b, 0x09, 0x36, 0x09, 0x3f, 0x02, 0x1b, 0x55, 0x8e, 0x27, 0x09, 0xc2, 0xe7, 0x71, 0xce, 0xc8, + 0x53, 0xd8, 0x90, 0x30, 0x4b, 0xd3, 0x36, 0x52, 0xed, 0x22, 0x33, 0x08, 0xe5, 0xc2, 0xfc, 0x13, 0xe3, 0xec, 0x22, + 0x0b, 0x97, 0x5a, 0x83, 0xf9, 0xf1, 0xce, 0x04, 0x28, 0xbb, 0xbe, 0xce, 0x84, 0x8f, 0x1b, 0x91, 0xbd, 0xa1, 0x2b, + 0x26, 0x03, 0x85, 0x54, 0xe0, 0x44, 0x64, 0xf1, 0xd0, 0x19, 0x0a, 0x8d, 0x70, 0x40, 0xa7, 0xc8, 0xb9, 0x6b, 0x6c, + 0xfa, 0x7c, 0xa0, 0x7d, 0xa3, 0x34, 0x74, 0x12, 0x10, 0x02, 0x02, 0x77, 0xc3, 0x9a, 0x4a, 0x07, 0x69, 0x90, 0x50, + 0x29, 0xfa, 0x39, 0x80, 0x7f, 0x18, 0x49, 0x0a, 0x80, 0xfd, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05, 0x2e, 0x00, 0xcd, + 0xb5, 0x8f, 0x2b, 0xe1, 0x0b, 0x03, 0x15, 0xa6, 0xa7, 0x59, 0x79, 0x29, 0x94, 0xc8, 0xd3, 0x15, 0x29, 0x6b, 0x24, + 0x93, 0xcf, 0xd1, 0xe1, 0x53, 0xde, 0xf5, 0x5b, 0x89, 0x87, 0x2e, 0x78, 0x0e, 0xcb, 0xaa, 0x9e, 0xdf, 0x84, 0x9c, + 0x9c, 0x6b, 0xd0, 0x15, 0x52, 0xe8, 0x2f, 0x39, 0xc9, 0x7b, 0x6f, 0xfc, 0xaa, 0x96, 0x1a, 0x43, 0xd9, 0xc7, 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, 0x39, 0x58, 0x01, 0xc6, 0xa1, 0x96, 0x4a, 0xa6, 0x91, 0xc4, 0x97, 0x4a, 0x14, + 0x98, 0x72, 0x7f, 0x08, 0x7e, 0x6a, 0xf3, 0xa4, 0xeb, 0xd2, 0xf5, 0xe3, 0x29, 0xa6, 0xf6, 0x10, 0xe8, 0xb1, 0x77, + 0x0f, 0x4c, 0x89, 0xba, 0x0e, 0x2b, 0x88, 0x43, 0xb3, 0x9a, 0x66, 0x01, 0x33, 0xa6, 0x0d, 0x5a, 0xb2, 0x0d, 0xb6, + 0x5c, 0x0e, 0xf6, 0x91, 0xd8, 0x9e, 0xd5, 0x0a, 0x08, 0x5d, 0x83, 0x06, 0x86, 0xdc, 0xa5, 0x42, 0x0b, 0xf3, 0x5e, + 0x97, 0x8a, 0x70, 0x7f, 0x0e, 0xb8, 0xb4, 0x82, 0x33, 0x2f, 0xa3, 0x81, 0xf7, 0xe3, 0xd3, 0x04, 0x13, 0x5f, 0x10, + 0x2b, 0xb0, 0x83, 0x83, 0x4e, 0xb3, 0x29, 0x70, 0x2a, 0x2e, 0x52, 0x06, 0xcb, 0x8a, 0x52, 0x1b, 0xfe, 0x48, 0x91, + 0xad, 0xbb, 0x3c, 0xd2, 0x5d, 0x88, 0x05, 0xb0, 0xd3, 0x2f, 0x18, 0xf9, 0x96, 0xf5, 0x32, 0x60, 0x70, 0xae, 0x35, + 0x0e, 0x02, 0xbf, 0xb9, 0x99, 0x1c, 0x95, 0x29, 0xb1, 0x5d, 0x93, 0xd5, 0x05, 0xe4, 0x98, 0x04, 0xd8, 0xc0, 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, 0x3f, 0x70, 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, 0xdb, 0x64, 0x62, 0xda, 0x5b, + 0x42, 0xea, 0x08, 0xbb, 0x87, 0x72, 0x82, 0xbb, 0x9a, 0x67, 0x5f, 0x86, 0xb3, 0xf5, 0xcc, 0xbd, 0x67, 0x30, 0xf7, + 0xd3, 0x90, 0x1b, 0x8c, 0x1e, 0xcb, 0x84, 0x1f, 0x19, 0xfb, 0xc8, 0x55, 0xd5, 0xb3, 0xb3, 0xb0, 0x12, 0x59, 0xe2, + 0xc9, 0x38, 0xea, 0x30, 0x4e, 0x45, 0x6b, 0x82, 0xec, 0xfa, 0xba, 0x30, 0xf7, 0x02, 0x05, 0x4d, 0x3d, 0x5e, 0x8f, + 0xd3, 0x56, 0xec, 0x6c, 0x44, 0x22, 0xf7, 0xde, 0xd4, 0x22, 0x91, 0x15, 0x9f, 0xe3, 0x48, 0x6b, 0x0e, 0x72, 0x9f, + 0x9d, 0x2d, 0x6f, 0x52, 0xa1, 0x5b, 0x34, 0xda, 0xc6, 0x1e, 0xd5, 0x07, 0x92, 0x7a, 0x46, 0x05, 0x56, 0x35, 0xf6, + 0xfd, 0xfb, 0x1d, 0x91, 0x6e, 0xa9, 0x14, 0x1b, 0x2c, 0x2d, 0x8c, 0x66, 0x8c, 0x82, 0x41, 0x49, 0x91, 0x81, 0x1a, + 0xe5, 0x6b, 0x04, 0xc3, 0x1e, 0x35, 0x00, 0xc5, 0xb9, 0xba, 0xfa, 0x69, 0x29, 0xd9, 0x42, 0x40, 0xe2, 0x2e, 0x18, + 0x88, 0x35, 0xc1, 0xcc, 0xc8, 0x27, 0x1f, 0x81, 0xf3, 0x06, 0x0c, 0x1d, 0x03, 0xf0, 0x0b, 0xc4, 0xa6, 0x07, 0x13, + 0xdb, 0x26, 0xa2, 0xe8, 0xb3, 0x81, 0x97, 0x00, 0xec, 0xac, 0x0a, 0x8d, 0x7e, 0xa8, 0x52, 0xc0, 0x90, 0x0d, 0xdc, + 0x80, 0x55, 0x61, 0xb9, 0xbd, 0x97, 0xe0, 0x36, 0xc0, 0xeb, 0x0b, 0xd9, 0x7c, 0x03, 0xf3, 0x04, 0xab, 0xb3, 0x0b, + 0xbf, 0xb2, 0xac, 0xc5, 0xb9, 0xd3, 0x41, 0xa3, 0x5e, 0x51, 0x42, 0xd4, 0xee, 0x63, 0xed, 0x4b, 0x8c, 0xb0, 0x88, + 0xf7, 0x37, 0xf8, 0xae, 0xc7, 0x2d, 0xf7, 0x34, 0x5a, 0x84, 0xe9, 0x32, 0x69, 0x0c, 0x4a, 0xd6, 0xfd, 0x64, 0xc4, + 0xbd, 0xdc, 0x17, 0xb1, 0xe0, 0x0a, 0x47, 0x56, 0x85, 0x14, 0x1b, 0x48, 0xd2, 0xd3, 0x1e, 0x1d, 0xb0, 0x6f, 0x34, + 0x7b, 0x01, 0x65, 0x3e, 0x56, 0xa4, 0x92, 0x90, 0xd2, 0xec, 0x86, 0x48, 0x12, 0xd6, 0x8a, 0x3c, 0x75, 0xde, 0x77, + 0xb4, 0xcf, 0xad, 0x24, 0x82, 0x11, 0x9c, 0x84, 0xe9, 0x58, 0x79, 0xd0, 0x14, 0xe0, 0x2a, 0x3a, 0x62, 0xfa, 0x26, + 0x20, 0xbf, 0x19, 0xc8, 0xed, 0xa5, 0xe4, 0xda, 0x5c, 0xc3, 0xf0, 0x0c, 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, 0x9f, 0x32, 0x47, 0xc8, 0x5c, 0x61, 0x7d, 0xce, 0x9c, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x49, + 0x2d, 0x5e, 0xa7, 0x4d, 0x29, 0x11, 0x93, 0x12, 0xf3, 0x54, 0xa4, 0x62, 0x33, 0x25, 0xee, 0xdc, 0xfa, 0x46, 0x0b, + 0x69, 0xa3, 0x9d, 0x8a, 0x1c, 0x6c, 0x56, 0xc9, 0x7b, 0x02, 0xe3, 0xa5, 0x20, 0x7c, 0x89, 0x8c, 0xb5, 0x98, 0x33, + 0xc7, 0x44, 0xb0, 0x7a, 0x31, 0x15, 0xf9, 0x07, 0x47, 0xa7, 0xd9, 0x1b, 0xf4, 0x20, 0xf5, 0x06, 0x12, 0xb3, 0x26, + 0xbe, 0x0b, 0x69, 0xa8, 0x23, 0x04, 0x2a, 0xa3, 0x5a, 0xa6, 0xe3, 0xc4, 0x2a, 0x7c, 0x23, 0xf8, 0xea, 0xbd, 0x3e, + 0xce, 0x37, 0x9e, 0x1b, 0xab, 0x11, 0xc4, 0xe0, 0x2d, 0xe4, 0x47, 0x9e, 0x14, 0xe1, 0x40, 0xb8, 0x7c, 0x73, 0xb3, + 0x97, 0xef, 0xf2, 0x2a, 0x44, 0x52, 0xc1, 0x18, 0x63, 0x46, 0x31, 0xee, 0x89, 0x9a, 0x5a, 0xcc, 0x61, 0x60, 0xd9, + 0x3a, 0xcc, 0xf1, 0x00, 0x00, 0x5a, 0x9a, 0xd2, 0xab, 0xa6, 0x42, 0xe5, 0x79, 0x2e, 0xe1, 0x53, 0x1d, 0xa2, 0xaa, + 0xc6, 0xef, 0x57, 0x67, 0xa0, 0x10, 0xdc, 0xf7, 0x3a, 0x1e, 0x1e, 0x42, 0xc0, 0x2a, 0x0a, 0x59, 0xa0, 0x37, 0x68, + 0xaf, 0x4a, 0x84, 0x62, 0xe6, 0x64, 0x3d, 0x66, 0x38, 0xa9, 0x60, 0x0b, 0x95, 0xb0, 0x54, 0x5a, 0xe0, 0x57, 0x1b, + 0xa1, 0x79, 0xca, 0xb8, 0xf7, 0xa6, 0xc2, 0x19, 0xf4, 0x07, 0xf3, 0x96, 0x19, 0xf5, 0xfd, 0xd2, 0x89, 0x4c, 0x05, + 0x26, 0x6e, 0x66, 0xa9, 0xfd, 0x7e, 0x59, 0xa5, 0xfd, 0xbc, 0x42, 0xee, 0x73, 0xd2, 0x7c, 0x9d, 0x3b, 0x68, 0x3e, + 0x19, 0xee, 0x57, 0xca, 0x0f, 0x2d, 0x8c, 0x9a, 0xf2, 0xcb, 0xeb, 0xca, 0xaf, 0xf0, 0x54, 0x78, 0xab, 0xdf, 0x45, + 0xa1, 0x8b, 0xfa, 0x1c, 0x0c, 0x21, 0xfd, 0x08, 0xae, 0xa1, 0xc1, 0x83, 0x22, 0x59, 0x2c, 0xd6, 0x2e, 0x88, 0xeb, + 0x63, 0x4e, 0xb5, 0x43, 0x19, 0x63, 0xc4, 0xd3, 0x92, 0x83, 0x24, 0x83, 0x83, 0xf1, 0x1b, 0x18, 0x10, 0x93, 0x92, + 0x90, 0x0e, 0xa1, 0xb3, 0x32, 0x13, 0x51, 0xb9, 0x8b, 0xb7, 0x1b, 0x97, 0x35, 0x85, 0x22, 0xec, 0x04, 0x33, 0x95, + 0x52, 0x41, 0x20, 0x4d, 0xbe, 0x7b, 0x9d, 0x5a, 0x30, 0xb4, 0x70, 0x4d, 0x05, 0xe4, 0xb5, 0x5d, 0x0f, 0x9a, 0x7c, + 0xa4, 0x18, 0xfa, 0x2a, 0x35, 0xe2, 0x65, 0x06, 0x5f, 0xc3, 0xe6, 0xaf, 0x89, 0x92, 0x3c, 0x64, 0x22, 0xf6, 0x0a, + 0x3e, 0x11, 0xb2, 0x29, 0xd8, 0x99, 0x40, 0x3f, 0xb4, 0x2b, 0x7b, 0xe9, 0x6e, 0x51, 0xb9, 0xb4, 0x68, 0x6c, 0x25, + 0x6a, 0xd6, 0xfc, 0x30, 0xde, 0x4c, 0x61, 0x3f, 0x7b, 0x94, 0x40, 0x40, 0x9a, 0xca, 0x49, 0xaa, 0x79, 0x0f, 0xd3, + 0x23, 0x00, 0x09, 0x76, 0x3f, 0x81, 0x85, 0x7e, 0x53, 0x62, 0x82, 0x45, 0xd5, 0xd8, 0x6d, 0x06, 0x5a, 0x73, 0x46, + 0x9a, 0x6f, 0x86, 0x5a, 0x7b, 0x53, 0x59, 0xcf, 0x98, 0x1d, 0x60, 0xdb, 0xee, 0x66, 0x71, 0x98, 0x6e, 0x76, 0x8e, + 0x0c, 0xc1, 0x85, 0xc7, 0xff, 0x49, 0x89, 0x69, 0x20, 0xb9, 0xd4, 0x8d, 0x9f, 0x50, 0x87, 0xe1, 0xff, 0x16, 0xa4, + 0x80, 0x07, 0xb5, 0xd5, 0x58, 0x72, 0xee, 0x15, 0x47, 0xc9, 0x65, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0x8c, + 0x89, 0x62, 0x9e, 0x13, 0x00, 0xa3, 0xd8, 0xfc, 0x39, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4c, 0xed, 0xf6, 0x7d, 0x3f, + 0xca, 0xcf, 0xe8, 0x48, 0x45, 0x65, 0x73, 0x12, 0xf3, 0x6f, 0x0b, 0x30, 0xcd, 0x89, 0x0f, 0xf5, 0x5c, 0x47, 0xa1, + 0x00, 0x5f, 0xd9, 0x50, 0x6a, 0xb6, 0xd7, 0xbf, 0x75, 0xb6, 0x87, 0x92, 0x28, 0x82, 0x05, 0x1a, 0x74, 0x59, 0x83, + 0x2f, 0x60, 0x19, 0xdc, 0x91, 0x7e, 0x0a, 0xbe, 0x9f, 0xd6, 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, + 0x67, 0xae, 0xe1, 0xc6, 0xf7, 0x1a, 0xf9, 0xe3, 0x7b, 0x2f, 0x41, 0xb7, 0x13, 0x69, 0xef, 0xdf, 0xcf, 0xef, 0x91, + 0x85, 0x86, 0xf7, 0xc2, 0x66, 0xd0, 0x16, 0xe9, 0x92, 0xab, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, + 0x30, 0x03, 0x0c, 0xc1, 0x63, 0xa7, 0x32, 0xf9, 0x0c, 0x1b, 0x4d, 0xb1, 0x6b, 0x2e, 0x0c, 0x3e, 0x50, 0x95, 0x85, + 0xe4, 0xc5, 0x3a, 0xd9, 0x5e, 0x9c, 0xc3, 0xf3, 0xeb, 0xb8, 0x00, 0xea, 0x00, 0xfa, 0x15, 0x95, 0xc5, 0x06, 0x72, + 0x71, 0x53, 0xd6, 0x7a, 0x45, 0xa3, 0xd1, 0x8d, 0x5d, 0x58, 0x5d, 0x81, 0x4f, 0xa2, 0x74, 0x94, 0x88, 0x49, 0xcc, + 0xa4, 0xca, 0x15, 0xb9, 0x36, 0xba, 0x97, 0xb6, 0x68, 0x5e, 0x0a, 0x09, 0x5e, 0x11, 0xb8, 0x21, 0xf4, 0x95, 0xbe, + 0x5c, 0x6d, 0xa0, 0xe0, 0x51, 0x7b, 0x73, 0x11, 0x4c, 0x4c, 0x3c, 0x66, 0x48, 0x4d, 0xbf, 0x0e, 0xa7, 0x56, 0x16, + 0x4b, 0x0e, 0xbf, 0xce, 0x19, 0x6b, 0x28, 0x00, 0xe2, 0x93, 0x47, 0xeb, 0xdd, 0xa4, 0x37, 0x4a, 0x3b, 0x28, 0x8d, + 0x10, 0xdf, 0x55, 0xf8, 0xba, 0x0b, 0xc5, 0x57, 0xae, 0xba, 0xf7, 0x75, 0xcc, 0x8c, 0x0b, 0x46, 0x2f, 0xf9, 0x34, + 0x69, 0x5c, 0xbb, 0xa1, 0xbb, 0x3a, 0xdf, 0x7b, 0x5f, 0xca, 0xbc, 0x85, 0x63, 0x60, 0x93, 0x63, 0xe6, 0xbc, 0xf4, + 0xde, 0x1a, 0x27, 0xca, 0x3f, 0x98, 0x47, 0xbc, 0x72, 0x98, 0x55, 0x27, 0xc9, 0x3f, 0x0c, 0x7e, 0x08, 0xd6, 0xb7, + 0x34, 0x4e, 0x90, 0xbb, 0xea, 0x04, 0x99, 0x28, 0xb7, 0xa1, 0x37, 0xdc, 0xde, 0x5d, 0x05, 0x82, 0x38, 0x15, 0xd3, + 0x47, 0xe5, 0xb8, 0x7e, 0xb4, 0x40, 0xa5, 0x22, 0xe2, 0x73, 0x95, 0xbb, 0xb2, 0x36, 0x35, 0xd4, 0xe3, 0x3a, 0x99, + 0x85, 0xa6, 0x59, 0x91, 0x4b, 0xd9, 0xf4, 0x18, 0x99, 0x66, 0xa7, 0xda, 0xfc, 0xee, 0xda, 0x43, 0x3a, 0x86, 0xe6, + 0x62, 0xad, 0x16, 0xdc, 0xef, 0x2a, 0x0a, 0xef, 0x7a, 0xb1, 0x91, 0xca, 0x50, 0xb3, 0x1e, 0x45, 0x1f, 0xc7, 0x6d, + 0xe6, 0xf2, 0x28, 0xfb, 0xb3, 0x06, 0x80, 0xe9, 0x08, 0x8b, 0xee, 0xa6, 0x67, 0xec, 0x09, 0xf4, 0xf4, 0x44, 0x06, + 0x89, 0xde, 0xe8, 0x7c, 0xd5, 0x2a, 0xb1, 0x74, 0x05, 0x81, 0xdd, 0x1b, 0x32, 0x56, 0x25, 0xed, 0x96, 0xeb, 0x97, + 0xf3, 0x7c, 0x9e, 0xf3, 0xa5, 0x3c, 0x9f, 0x9a, 0x45, 0x77, 0xaf, 0xed, 0xde, 0x9c, 0x1a, 0x2a, 0xe6, 0x5a, 0xdd, + 0xe4, 0x37, 0x4c, 0xd7, 0xc1, 0x50, 0x8b, 0x20, 0xb3, 0xda, 0x55, 0x2f, 0xca, 0x72, 0xa3, 0x9e, 0xc9, 0xb1, 0x21, + 0x7c, 0x53, 0xe9, 0x0e, 0xd1, 0x0d, 0x53, 0x35, 0xd3, 0xf7, 0x8d, 0x6d, 0x21, 0xdb, 0xbc, 0xbc, 0x1a, 0xe5, 0x40, + 0x69, 0xb9, 0xbf, 0x4c, 0x18, 0xbe, 0xbf, 0xbe, 0xfe, 0x5e, 0xc8, 0xa9, 0xaa, 0xa3, 0xb7, 0x78, 0xad, 0x7b, 0x06, + 0x1b, 0xa5, 0x72, 0x22, 0x2e, 0xd8, 0xea, 0xc1, 0x9b, 0xbb, 0x57, 0xc0, 0x72, 0x01, 0xd8, 0x5d, 0x30, 0xa7, 0x31, + 0x54, 0xb5, 0x81, 0xbf, 0x5c, 0x3d, 0xd8, 0xaa, 0x3d, 0xfc, 0xe5, 0xe0, 0xcb, 0xe0, 0xc6, 0xc6, 0xc6, 0x36, 0xde, + 0xae, 0x25, 0x82, 0xbc, 0xc1, 0x03, 0x7d, 0xbc, 0xfa, 0x28, 0x68, 0xb9, 0x4a, 0x6c, 0x0f, 0x1c, 0x0a, 0x5b, 0x83, + 0x7c, 0x93, 0x32, 0x69, 0x38, 0x2f, 0x78, 0x36, 0x95, 0x33, 0x14, 0xf2, 0x9a, 0x8f, 0x83, 0xb6, 0x23, 0xfc, 0x1b, + 0x38, 0xb5, 0xe3, 0xe5, 0xc5, 0x27, 0xe8, 0x03, 0x9e, 0xae, 0x94, 0xa6, 0x22, 0x4e, 0x29, 0xb7, 0xe8, 0x72, 0x9d, + 0x07, 0x23, 0xc5, 0xc5, 0x04, 0x95, 0x8e, 0xbb, 0xb8, 0x71, 0x36, 0x72, 0xfa, 0x4b, 0xbc, 0xba, 0x48, 0x97, 0x8f, + 0x44, 0xb6, 0x6a, 0xe9, 0xfd, 0xac, 0x4f, 0xb7, 0xed, 0x29, 0xe3, 0x93, 0x6c, 0x44, 0x07, 0x33, 0x3e, 0x4e, 0x84, + 0xd7, 0x27, 0x46, 0xfa, 0x6e, 0x11, 0x98, 0x6e, 0x8e, 0x4d, 0x7e, 0x38, 0x5e, 0x6f, 0x36, 0x6b, 0xdc, 0xc1, 0x3b, + 0xe7, 0x93, 0xb3, 0x28, 0x31, 0xa2, 0xb2, 0xd0, 0xf0, 0x80, 0x56, 0x88, 0x9b, 0xf7, 0x4c, 0x60, 0x5c, 0x76, 0x45, + 0x52, 0xdb, 0x0d, 0x04, 0x2e, 0xf6, 0x38, 0x66, 0xc9, 0xc8, 0xf6, 0xa0, 0x3c, 0xd0, 0x17, 0xa3, 0xe9, 0x16, 0x30, + 0x2d, 0xaf, 0x9d, 0x5d, 0xa4, 0xb6, 0x57, 0x4d, 0x15, 0xc0, 0x2c, 0x59, 0x1e, 0x9f, 0x21, 0xeb, 0x7e, 0x0d, 0x5d, + 0xc4, 0x80, 0xb1, 0x71, 0x65, 0xce, 0x5d, 0xac, 0x5a, 0x11, 0xdf, 0x68, 0x22, 0x4d, 0xea, 0x43, 0xea, 0x7b, 0x14, + 0xd6, 0xea, 0x2a, 0x07, 0x09, 0xdc, 0x23, 0xef, 0x8e, 0xb8, 0xf4, 0xf4, 0x99, 0xc5, 0xb8, 0x4a, 0xdf, 0x52, 0xd7, + 0xe2, 0x9a, 0x61, 0xaf, 0x78, 0x00, 0xf6, 0x07, 0xc6, 0x2d, 0x62, 0x11, 0x6f, 0xe7, 0xb5, 0x14, 0xd6, 0xc6, 0x1c, + 0x68, 0x6e, 0xb8, 0xc1, 0xcf, 0xac, 0x5a, 0x33, 0x30, 0xc3, 0x8c, 0x33, 0x92, 0x0f, 0xc6, 0xbd, 0xaa, 0xb1, 0x23, + 0x57, 0x01, 0x44, 0xdf, 0x82, 0x2e, 0xc9, 0xe1, 0x95, 0x2c, 0x57, 0x9d, 0x21, 0xbf, 0x82, 0x75, 0xd6, 0x8b, 0x13, + 0x30, 0x93, 0xa6, 0xbc, 0xc4, 0xc4, 0x14, 0x71, 0xb9, 0x59, 0xc6, 0x3c, 0x4d, 0x9f, 0x45, 0x3b, 0x38, 0xb9, 0x91, + 0xc0, 0x11, 0xfb, 0xc6, 0x32, 0x34, 0x13, 0x36, 0x62, 0x22, 0x8d, 0x4a, 0x29, 0xe1, 0x03, 0xb9, 0xd4, 0x92, 0xbf, + 0xcc, 0xe5, 0xd5, 0x97, 0xdb, 0x04, 0x07, 0xe4, 0x35, 0xb0, 0x1c, 0x1a, 0xc7, 0x2d, 0x03, 0x89, 0x58, 0x0c, 0x88, + 0x51, 0xab, 0x72, 0x39, 0x19, 0xd5, 0xc9, 0x7c, 0x85, 0x5c, 0xa8, 0xc8, 0x83, 0x5b, 0x02, 0x25, 0x7f, 0x8e, 0xa9, + 0x83, 0x59, 0xa9, 0xdd, 0xb4, 0xd8, 0x24, 0x79, 0xcf, 0x0c, 0x48, 0xae, 0xbe, 0x86, 0x87, 0xc6, 0x2f, 0x5e, 0x99, + 0x53, 0xc2, 0x17, 0x65, 0x2c, 0x2d, 0x8d, 0xb9, 0xf4, 0xdf, 0xca, 0xfb, 0xb4, 0x12, 0xb0, 0x57, 0x20, 0xa6, 0x0c, + 0x5c, 0x62, 0xe3, 0x82, 0xa4, 0xbc, 0x96, 0xa7, 0xec, 0xbe, 0x86, 0xf2, 0x5d, 0x32, 0xe9, 0x2a, 0x95, 0xb5, 0xc6, + 0xaa, 0xfb, 0x79, 0xce, 0xf2, 0xab, 0x7d, 0x86, 0xb9, 0xc9, 0x68, 0x90, 0x2d, 0x99, 0xd9, 0x94, 0x5f, 0xed, 0xdd, + 0xf8, 0x95, 0x87, 0x92, 0x0e, 0xd5, 0x2a, 0xdd, 0xbc, 0x74, 0xc3, 0x31, 0x6e, 0xdc, 0x70, 0x04, 0xb0, 0x31, 0xec, + 0x54, 0x91, 0x5a, 0xe7, 0xbf, 0x2f, 0x87, 0x9f, 0x68, 0xaf, 0x1d, 0xe9, 0x5d, 0x77, 0xb4, 0x32, 0x3d, 0xfd, 0x06, + 0x54, 0x8d, 0x2c, 0xa1, 0x9b, 0x50, 0xc5, 0x64, 0x24, 0x4a, 0x4c, 0x57, 0x29, 0x8f, 0xfa, 0x1a, 0x71, 0x0e, 0xe2, + 0x86, 0xf2, 0x17, 0xff, 0x14, 0x5e, 0x9d, 0x04, 0x68, 0x44, 0x2d, 0xc6, 0x59, 0xca, 0x5b, 0xe3, 0x68, 0x1a, 0x27, + 0x57, 0xc1, 0x3c, 0x6e, 0x4d, 0xb3, 0x34, 0x2b, 0x66, 0xc0, 0x95, 0x5e, 0x71, 0x05, 0x36, 0xfc, 0xb4, 0x35, 0x8f, + 0xbd, 0x97, 0x2c, 0x39, 0x67, 0x3c, 0x1e, 0x46, 0x9e, 0xbd, 0x97, 0x83, 0x78, 0xb0, 0xde, 0x46, 0x79, 0x9e, 0x5d, + 0xd8, 0xde, 0x87, 0xec, 0x14, 0x98, 0xd6, 0x7b, 0x77, 0x79, 0x75, 0xc6, 0x52, 0xef, 0xe3, 0xe9, 0x3c, 0xe5, 0x73, + 0xaf, 0x88, 0xd2, 0xa2, 0x55, 0xb0, 0x3c, 0x1e, 0x83, 0x9a, 0x48, 0xb2, 0xbc, 0x85, 0xf9, 0xcf, 0x53, 0x16, 0x24, + 0xf1, 0xd9, 0x84, 0x5b, 0xa3, 0x28, 0xff, 0xd4, 0x6b, 0xb5, 0x66, 0x79, 0x3c, 0x8d, 0xf2, 0xab, 0x16, 0xb5, 0x08, + 0x3e, 0x6b, 0x6f, 0x47, 0x9f, 0x8f, 0x1f, 0xf6, 0x78, 0x0e, 0x7d, 0x63, 0xa4, 0x62, 0x00, 0xc2, 0xc7, 0xda, 0xde, + 0x69, 0x4f, 0x8b, 0x7b, 0xe2, 0x44, 0x29, 0x4a, 0x79, 0x79, 0xe2, 0x5d, 0x31, 0x80, 0xdb, 0x3f, 0xe5, 0xa9, 0x07, + 0xbe, 0x1c, 0xcf, 0xd2, 0xc5, 0x70, 0x9e, 0x17, 0x30, 0xc0, 0x2c, 0x8b, 0x53, 0xce, 0xf2, 0xde, 0x69, 0x96, 0x03, + 0xd9, 0x5a, 0x79, 0x34, 0x8a, 0xe7, 0x45, 0xf0, 0x70, 0x76, 0xd9, 0x43, 0x5b, 0xe1, 0x2c, 0xcf, 0xe6, 0xe9, 0x48, + 0xce, 0x15, 0xa7, 0xb0, 0x31, 0x62, 0x6e, 0x56, 0xd0, 0x97, 0x50, 0x00, 0xbe, 0x94, 0x45, 0x79, 0xeb, 0x0c, 0x3b, + 0xa3, 0xa1, 0xdf, 0x1e, 0xb1, 0x33, 0x2f, 0x3f, 0x3b, 0x8d, 0x9c, 0x4e, 0xf7, 0xb1, 0xa7, 0xfe, 0xf3, 0x77, 0x5c, + 0x30, 0xdc, 0x57, 0x16, 0x77, 0xda, 0xed, 0xbf, 0x71, 0x7b, 0x8d, 0x59, 0x08, 0xa0, 0xa0, 0x33, 0xbb, 0xb4, 0x8a, + 0x2c, 0x81, 0xf5, 0x59, 0xd5, 0xb3, 0x37, 0x03, 0xbf, 0x29, 0x4e, 0xcf, 0x82, 0xee, 0xec, 0xb2, 0x44, 0xec, 0x02, + 0x91, 0x90, 0x29, 0x91, 0x94, 0x6f, 0x8b, 0xdf, 0x0a, 0xf1, 0x93, 0xd5, 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd5, 0x5b, + 0x23, 0xd8, 0x07, 0x44, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x80, 0x13, 0x98, 0x2b, 0x38, 0xe8, 0xe5, 0x37, 0x83, 0xd1, + 0x5d, 0x0d, 0xc6, 0x93, 0xdb, 0xc0, 0xc8, 0xd3, 0xd1, 0xa2, 0xbe, 0xae, 0x1d, 0x70, 0x4e, 0x7b, 0x13, 0x86, 0xfc, + 0x14, 0x74, 0xf1, 0xf9, 0x22, 0x1e, 0xf1, 0x89, 0x78, 0x24, 0x76, 0xbe, 0x10, 0x75, 0x3b, 0xed, 0xb6, 0x78, 0x2f, + 0x40, 0xa1, 0x05, 0x1d, 0x1f, 0x1b, 0x00, 0x13, 0x7d, 0xb1, 0xee, 0x23, 0x36, 0xdf, 0xdd, 0xfa, 0xa5, 0x1a, 0x8f, + 0xa9, 0xbc, 0x41, 0xa1, 0x22, 0xd4, 0x37, 0x5b, 0x30, 0xe3, 0x2d, 0xef, 0x77, 0xf4, 0x41, 0xd5, 0xe0, 0x3b, 0x46, + 0x5a, 0x2f, 0xe0, 0x9e, 0x99, 0x0b, 0xd4, 0x4b, 0xfb, 0x18, 0x92, 0x6a, 0xb5, 0x5c, 0xd0, 0x1b, 0x0c, 0x43, 0x48, + 0x74, 0x20, 0xe8, 0xe4, 0x83, 0x82, 0xbe, 0xa9, 0x91, 0xb9, 0x41, 0xe1, 0x64, 0x2e, 0x6c, 0xf9, 0x4c, 0xcb, 0x75, + 0x50, 0xd2, 0xe0, 0x65, 0x7f, 0xc1, 0x64, 0x03, 0x90, 0xde, 0x95, 0xa4, 0xe5, 0xd5, 0xd1, 0x93, 0x72, 0xf9, 0xb2, + 0x21, 0x51, 0x0e, 0x7c, 0x7d, 0x3e, 0x41, 0xbf, 0x5b, 0x7f, 0x28, 0xc6, 0x48, 0xa9, 0xd9, 0xb2, 0xdd, 0x01, 0xd3, + 0x59, 0x59, 0x98, 0x7d, 0xc6, 0x4a, 0x1c, 0xe5, 0x2b, 0xb0, 0xa4, 0x31, 0xf4, 0xfa, 0x73, 0x28, 0xdc, 0x34, 0xe5, + 0xa4, 0x6d, 0xdc, 0x74, 0xfd, 0x1f, 0x56, 0x3c, 0xa6, 0x6c, 0x67, 0x15, 0x1b, 0x07, 0xd7, 0xe5, 0x78, 0x28, 0xae, + 0x1d, 0x16, 0x98, 0x2d, 0xfe, 0xdb, 0x3d, 0x09, 0x47, 0xa3, 0x55, 0x64, 0xf3, 0x7c, 0x48, 0xa1, 0xc1, 0xe5, 0x10, + 0x83, 0x4d, 0x1a, 0xde, 0xf6, 0x98, 0x56, 0x2c, 0xe8, 0x77, 0xd7, 0xbe, 0xaa, 0xc0, 0xe9, 0xd4, 0x45, 0x5c, 0x6a, + 0x90, 0x61, 0x15, 0x05, 0x36, 0xea, 0xca, 0x11, 0x25, 0xd8, 0xd1, 0x85, 0x4f, 0x7f, 0x9e, 0xc6, 0x20, 0x5a, 0x8f, + 0xe3, 0x11, 0x5d, 0x74, 0x89, 0x47, 0x74, 0xf2, 0xd1, 0xa2, 0x4c, 0x27, 0x0c, 0xa5, 0x43, 0x81, 0x24, 0x38, 0x3e, + 0xcb, 0xcc, 0x19, 0xbb, 0x65, 0xe3, 0xe9, 0x85, 0xa1, 0x9b, 0x47, 0xd9, 0x34, 0x8a, 0xd3, 0x00, 0x3f, 0x48, 0xe2, + 0xe9, 0x11, 0x03, 0xec, 0xe2, 0xc1, 0x5f, 0x45, 0xfb, 0x8e, 0xeb, 0xff, 0x04, 0x82, 0x8b, 0xfa, 0x97, 0xd2, 0xf1, + 0xd3, 0x70, 0xa9, 0x73, 0xe5, 0x7a, 0x29, 0x08, 0x3b, 0xae, 0x8c, 0x64, 0x46, 0x81, 0x95, 0x5d, 0x4e, 0x7f, 0x06, + 0xad, 0x4e, 0xa0, 0xae, 0xfe, 0x9b, 0x2b, 0x60, 0x5c, 0x0c, 0xa8, 0x56, 0x85, 0x4a, 0xe4, 0x1b, 0xcc, 0x21, 0xf9, + 0xf3, 0xfa, 0x5a, 0x7f, 0x3c, 0xa0, 0x71, 0x81, 0x56, 0xa4, 0xdf, 0xc8, 0x4b, 0x98, 0x84, 0x85, 0x7e, 0x16, 0x98, + 0x56, 0xef, 0x1a, 0x5b, 0x4f, 0x6e, 0x25, 0x8c, 0x39, 0x9d, 0xa5, 0x4e, 0x0d, 0x0d, 0x3a, 0xbe, 0x58, 0x33, 0x95, + 0x5b, 0x46, 0xc4, 0xdc, 0x4f, 0x49, 0xe6, 0xd4, 0xaf, 0x3f, 0xe1, 0x55, 0xa7, 0x7a, 0xd6, 0x96, 0x62, 0xef, 0xe1, + 0xc9, 0xae, 0x10, 0x52, 0x16, 0xb1, 0x6e, 0x68, 0x83, 0xd4, 0xb0, 0xad, 0x3f, 0x0e, 0x81, 0xce, 0x9f, 0x42, 0x7b, + 0x63, 0xe1, 0xa8, 0xbb, 0x00, 0x39, 0xcc, 0xb5, 0x27, 0x14, 0x35, 0x7d, 0x44, 0xc0, 0xee, 0x6f, 0x2c, 0x78, 0xb9, + 0xbb, 0x25, 0x7a, 0xf7, 0x4f, 0xca, 0x82, 0x74, 0xaa, 0x19, 0xfb, 0xab, 0xa6, 0x10, 0x75, 0x30, 0x2c, 0x65, 0x1c, + 0xe3, 0xb8, 0xb9, 0xb6, 0x13, 0x45, 0x90, 0x5b, 0x32, 0x6e, 0x81, 0x19, 0x56, 0x51, 0x0e, 0x62, 0x44, 0xe7, 0xd0, + 0x14, 0x22, 0x6d, 0xa4, 0xb7, 0x0c, 0xc5, 0x09, 0x42, 0x30, 0xd8, 0x58, 0xc4, 0x65, 0xb8, 0xb1, 0x60, 0xe9, 0x30, + 0x1b, 0xb1, 0x8f, 0x1f, 0x5e, 0xe1, 0x35, 0x89, 0x2c, 0x45, 0x79, 0x9a, 0xb9, 0xe5, 0x09, 0x18, 0x58, 0x08, 0x69, + 0xae, 0xbe, 0x52, 0x03, 0xc0, 0x88, 0x58, 0x91, 0x45, 0xa3, 0x22, 0x28, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x10, 0x1c, + 0x59, 0x2c, 0x00, 0x13, 0x94, 0x7a, 0x31, 0xc0, 0x4f, 0xb4, 0xee, 0xc3, 0x40, 0xbb, 0x5b, 0xa2, 0x11, 0xe0, 0x9a, + 0x23, 0x1a, 0x15, 0xaa, 0x98, 0x55, 0x64, 0xa2, 0x3b, 0x8a, 0xcf, 0x35, 0x39, 0x29, 0xc5, 0xba, 0xbf, 0x9b, 0x44, + 0xa7, 0x2c, 0x81, 0x21, 0x81, 0xaf, 0xda, 0x30, 0x92, 0x78, 0xb5, 0x76, 0xe3, 0x74, 0x36, 0x97, 0x5f, 0x0b, 0x83, + 0x89, 0x3b, 0x78, 0x80, 0x8b, 0x97, 0x19, 0x06, 0xea, 0x44, 0x32, 0x90, 0x03, 0x00, 0x88, 0x74, 0x18, 0x82, 0xd0, + 0x55, 0xac, 0x02, 0xa5, 0xf1, 0x68, 0xb9, 0x0c, 0xf6, 0xf7, 0x0c, 0x4b, 0x53, 0x78, 0x9e, 0xc6, 0x29, 0x3e, 0x16, + 0xf8, 0x18, 0x5d, 0xe2, 0x63, 0x06, 0x8f, 0x1a, 0xf7, 0xbc, 0xb4, 0xff, 0xaa, 0xab, 0x92, 0xc9, 0x15, 0xb0, 0x34, + 0x01, 0xb2, 0xeb, 0x6b, 0x50, 0x5b, 0x9a, 0x04, 0xbb, 0x5b, 0x40, 0x2c, 0xe4, 0x1e, 0xf1, 0xed, 0x18, 0x66, 0x92, + 0x91, 0x15, 0xb3, 0x96, 0x28, 0xb7, 0xc8, 0x38, 0x08, 0xc1, 0x77, 0xcc, 0x9d, 0x86, 0x0d, 0xe4, 0xc9, 0x2c, 0x99, + 0x67, 0xf8, 0xe2, 0xda, 0x96, 0xf8, 0xb8, 0x87, 0x20, 0x0a, 0x3d, 0x22, 0x86, 0xba, 0x8c, 0xcb, 0xcf, 0xf6, 0xc4, + 0xa1, 0x8d, 0xb3, 0x80, 0x19, 0x8a, 0xca, 0x8c, 0x47, 0x71, 0x22, 0x1a, 0xaf, 0xc0, 0xa7, 0x91, 0xee, 0x48, 0xe8, + 0xec, 0x6e, 0x55, 0xb0, 0x01, 0xf0, 0x4a, 0x22, 0x88, 0x54, 0x4e, 0x5b, 0x94, 0x53, 0x0a, 0x80, 0xdc, 0xe6, 0xd5, + 0x27, 0x9d, 0x80, 0x29, 0xc0, 0x88, 0x1e, 0x1d, 0xd3, 0x6c, 0x83, 0x21, 0x12, 0x0b, 0x67, 0x6c, 0x6c, 0x5d, 0xfb, + 0x2f, 0xff, 0xfc, 0x0f, 0xb6, 0x27, 0x40, 0xcc, 0xc6, 0x63, 0x90, 0x72, 0xd6, 0xba, 0x86, 0xff, 0xeb, 0x1f, 0xff, + 0xef, 0xff, 0xf9, 0xaf, 0xba, 0x6d, 0x0a, 0x4d, 0x4f, 0x02, 0x71, 0xb4, 0xa0, 0x49, 0x4a, 0x29, 0x9e, 0xf6, 0x38, + 0x4a, 0x57, 0x80, 0x74, 0x08, 0x54, 0x9a, 0x31, 0x36, 0xf2, 0x6c, 0x0b, 0x34, 0x81, 0x78, 0x3e, 0x4e, 0xd8, 0x39, + 0x93, 0x1f, 0x96, 0xd1, 0x83, 0xe8, 0xca, 0x21, 0x58, 0x30, 0x5c, 0xde, 0x79, 0x95, 0xdb, 0x40, 0xd1, 0x52, 0x52, + 0xbc, 0x4e, 0x30, 0xcf, 0x36, 0x06, 0x6d, 0xce, 0xd1, 0xae, 0x0f, 0xeb, 0x81, 0x4a, 0xb5, 0x6d, 0x01, 0x2f, 0x99, + 0xbd, 0xab, 0x20, 0x5e, 0x82, 0xeb, 0x34, 0xc7, 0xa6, 0x29, 0x2b, 0x8a, 0x55, 0x60, 0x01, 0x4d, 0x3c, 0xbb, 0x6a, + 0x62, 0xd7, 0x3a, 0x00, 0x00, 0xdd, 0x9d, 0x1d, 0x31, 0x2d, 0x54, 0xb0, 0xf1, 0x18, 0x36, 0x38, 0xea, 0xb6, 0x84, + 0xe3, 0xb1, 0x45, 0xd8, 0xb7, 0xdf, 0x82, 0x2c, 0xb1, 0xc1, 0x3f, 0x74, 0xf5, 0x01, 0x34, 0x4d, 0xaf, 0x84, 0x9d, + 0x31, 0x87, 0xe8, 0x6c, 0x0c, 0xa3, 0x9f, 0x0c, 0xa4, 0xb2, 0xe1, 0xa7, 0x55, 0x8c, 0xb1, 0x96, 0x11, 0xfe, 0xfd, + 0x5f, 0xfe, 0xf1, 0xbf, 0xc1, 0xd8, 0xd4, 0x6f, 0x3d, 0x17, 0x40, 0xab, 0xff, 0x09, 0xad, 0xe6, 0xe9, 0x2d, 0xed, + 0xfe, 0xf2, 0xf7, 0xff, 0x1d, 0x9a, 0xd1, 0x45, 0x29, 0xe0, 0x13, 0x82, 0x68, 0x88, 0xb6, 0xe9, 0xaf, 0x02, 0xa9, + 0x36, 0xc8, 0xda, 0x99, 0xfe, 0x09, 0xc1, 0x2e, 0x78, 0x36, 0xbb, 0x11, 0x1c, 0x84, 0x7a, 0x98, 0x64, 0x05, 0xd3, + 0xf0, 0x08, 0x7d, 0xf2, 0xeb, 0x00, 0xa2, 0xb9, 0x66, 0xb0, 0x6b, 0x0b, 0x4b, 0x8f, 0x23, 0x56, 0x68, 0xd5, 0x38, + 0x8d, 0x05, 0x2c, 0x18, 0x27, 0x74, 0x28, 0xdc, 0x03, 0x4b, 0x26, 0x9e, 0xe0, 0x81, 0x04, 0x9c, 0x5b, 0xff, 0xf8, + 0xda, 0xea, 0xc1, 0x34, 0xc3, 0x89, 0xb1, 0x44, 0x84, 0x4b, 0x8d, 0x00, 0x7f, 0x41, 0x08, 0x1f, 0xeb, 0xe7, 0xe8, + 0x52, 0x3f, 0xa3, 0xa0, 0x16, 0x13, 0x80, 0xbe, 0x9d, 0xa2, 0x31, 0x66, 0xce, 0x20, 0xb2, 0x33, 0x2a, 0xf7, 0xde, + 0x48, 0xf2, 0x11, 0xc2, 0xf8, 0x18, 0x73, 0x61, 0xf1, 0xe6, 0xd3, 0x3c, 0x67, 0xc7, 0x49, 0x76, 0x81, 0x31, 0x43, + 0x24, 0xd2, 0xba, 0xfa, 0xf2, 0xdf, 0xfe, 0xd5, 0xf7, 0xff, 0xed, 0x5f, 0xd7, 0x34, 0x98, 0xc0, 0x9e, 0x00, 0x23, + 0x9f, 0x87, 0x9a, 0xce, 0x0d, 0xb4, 0x56, 0x0f, 0x8a, 0x78, 0xae, 0xae, 0x91, 0x88, 0x63, 0xa9, 0xc4, 0x5b, 0x3e, + 0x12, 0xda, 0x9a, 0x29, 0x6e, 0x9f, 0x05, 0x21, 0x5b, 0x33, 0x0d, 0x56, 0xdd, 0x32, 0xcf, 0x89, 0x1b, 0xdc, 0x40, + 0x97, 0x5f, 0x89, 0xf1, 0x6a, 0x30, 0x6e, 0x85, 0xc0, 0x03, 0x6d, 0x26, 0xf4, 0xdd, 0x33, 0xa1, 0xad, 0x02, 0xb1, + 0x0c, 0x52, 0x77, 0xd5, 0x00, 0xf2, 0xac, 0x03, 0x9a, 0x80, 0x9a, 0xc4, 0x95, 0xad, 0x40, 0xe6, 0xd6, 0x69, 0xde, + 0x7f, 0x83, 0x97, 0x1d, 0x2d, 0xec, 0x8d, 0x96, 0x42, 0x41, 0x86, 0x0d, 0x27, 0xc3, 0x46, 0x6a, 0x54, 0xd3, 0xa6, + 0x40, 0xc7, 0x2f, 0x5b, 0x6d, 0x3b, 0x1c, 0x63, 0xf7, 0x9a, 0xf6, 0xe7, 0x52, 0xfb, 0xc7, 0xd2, 0xde, 0x97, 0xda, + 0x1f, 0x3f, 0x69, 0xd3, 0xd0, 0xfe, 0xf1, 0x5a, 0xed, 0x8f, 0x94, 0x1b, 0xe0, 0xc8, 0xa1, 0xbd, 0x89, 0xd1, 0x2d, + 0xc3, 0xd6, 0xe0, 0x68, 0x67, 0x0d, 0x27, 0x6c, 0xf8, 0x49, 0x9a, 0x59, 0x84, 0x00, 0x86, 0x77, 0xb4, 0x31, 0x29, + 0x30, 0x00, 0x93, 0xe1, 0xa4, 0xd4, 0x9b, 0x1e, 0x1f, 0x8d, 0x09, 0xb8, 0xbb, 0x18, 0x33, 0x14, 0xfd, 0xb0, 0x66, + 0x5f, 0xb1, 0x72, 0x0b, 0xc7, 0x11, 0x1b, 0x46, 0x3c, 0x03, 0x66, 0x5b, 0x38, 0xd8, 0x89, 0xb7, 0x10, 0xc1, 0xc2, + 0xc0, 0x7e, 0xff, 0x6e, 0xff, 0xc0, 0xf6, 0x4e, 0xb3, 0xd1, 0x55, 0x60, 0x83, 0x33, 0x06, 0xd6, 0x94, 0xeb, 0xf3, + 0x09, 0x4b, 0x1d, 0xe5, 0xf9, 0x64, 0x09, 0xb8, 0x9a, 0xd9, 0x99, 0xf8, 0xb6, 0x45, 0xf3, 0xa0, 0x03, 0x08, 0x4b, + 0x1f, 0xbf, 0xec, 0xef, 0x72, 0xf1, 0x5d, 0x58, 0x9e, 0xe3, 0x63, 0x1f, 0x53, 0x3d, 0x76, 0xb7, 0xe0, 0x01, 0x5f, + 0xf6, 0x51, 0xef, 0xd1, 0xdb, 0xc6, 0x62, 0xc9, 0x6d, 0x18, 0xe0, 0x10, 0x93, 0xbe, 0x40, 0xa1, 0xa0, 0x56, 0x27, + 0x01, 0x22, 0x06, 0x8f, 0x30, 0xd6, 0x96, 0x1a, 0x17, 0x21, 0x54, 0xfd, 0xb5, 0xe3, 0x52, 0xd9, 0xad, 0x34, 0xef, + 0x08, 0xcd, 0x52, 0x72, 0x5c, 0xb0, 0xf7, 0x48, 0x97, 0x08, 0x53, 0x87, 0x8a, 0xd6, 0x41, 0xa0, 0x6b, 0x2a, 0x73, + 0x45, 0x74, 0x30, 0x80, 0x21, 0x33, 0x57, 0x00, 0x02, 0x7f, 0x09, 0xed, 0x13, 0xf3, 0xfb, 0x6f, 0xe2, 0x53, 0x4d, + 0x9a, 0x38, 0x87, 0x7f, 0xf2, 0xae, 0x98, 0x77, 0x75, 0x42, 0x2d, 0x55, 0xb0, 0x01, 0xa3, 0x60, 0x18, 0x94, 0x69, + 0xab, 0xa8, 0x12, 0xd8, 0x69, 0x49, 0x34, 0x2b, 0x58, 0xa0, 0x1e, 0x64, 0xdc, 0x01, 0xc3, 0x17, 0xcb, 0x81, 0x1e, + 0xd3, 0x9e, 0x2b, 0xf9, 0x64, 0x61, 0x06, 0x26, 0x1e, 0xb5, 0xdb, 0x3d, 0xbc, 0x54, 0xd1, 0x8a, 0xc0, 0x3a, 0x48, + 0x83, 0x84, 0x8d, 0x79, 0xc9, 0xf1, 0xd6, 0xfe, 0x42, 0x45, 0x82, 0xfc, 0xee, 0x4e, 0xce, 0xa6, 0x96, 0x8f, 0xff, + 0xbf, 0x6d, 0xec, 0x51, 0x90, 0xf2, 0x49, 0x8b, 0xae, 0xf1, 0xe0, 0x15, 0x49, 0x80, 0xc8, 0x7c, 0x5f, 0x18, 0x13, + 0x0d, 0x19, 0x46, 0xc9, 0x4a, 0x9e, 0x83, 0xbc, 0xf7, 0x78, 0x6e, 0xb6, 0x03, 0x39, 0xbd, 0x14, 0x2a, 0x5b, 0x0e, + 0xd6, 0x6c, 0xbb, 0xd2, 0x3f, 0x5a, 0x6e, 0xac, 0x22, 0x5e, 0xf5, 0xb7, 0x25, 0x0a, 0x19, 0xb1, 0xb9, 0x52, 0xa8, + 0xa8, 0x85, 0xe8, 0x61, 0xe2, 0xb4, 0x1c, 0xb5, 0xbb, 0xd5, 0x62, 0x2e, 0x49, 0x5c, 0x1c, 0x92, 0xb8, 0x20, 0xf1, + 0x77, 0xb4, 0x10, 0x73, 0x0f, 0xa3, 0x64, 0xe8, 0x20, 0x00, 0x56, 0xcb, 0x7a, 0x02, 0xd4, 0x74, 0x55, 0xe4, 0xc8, + 0x7f, 0x8c, 0xc4, 0x2d, 0x85, 0xb0, 0x5c, 0x41, 0xa5, 0x93, 0xa3, 0xb2, 0xec, 0x31, 0xe6, 0x1c, 0x7e, 0x90, 0x97, + 0x40, 0xc4, 0xdd, 0x5f, 0xfd, 0xfd, 0xc4, 0x76, 0xe9, 0x1e, 0x79, 0x3f, 0x1b, 0x1f, 0xa5, 0xb3, 0x15, 0xb3, 0xdb, + 0x1e, 0x2c, 0x83, 0xd9, 0x53, 0x7e, 0x42, 0xf2, 0xa6, 0xbe, 0x26, 0x9b, 0x53, 0xff, 0x9f, 0x43, 0x1c, 0xe1, 0x8d, + 0x63, 0xa3, 0x89, 0x4e, 0x23, 0x5f, 0xb5, 0x88, 0x3f, 0x6d, 0xec, 0x2a, 0x8e, 0x40, 0xbe, 0x5e, 0x17, 0xc9, 0xfa, + 0xe6, 0xf6, 0x48, 0x56, 0x71, 0xc7, 0x48, 0xd6, 0x37, 0xbf, 0x73, 0x24, 0xeb, 0x6b, 0x33, 0x92, 0x85, 0x02, 0xfa, + 0xd5, 0xaf, 0x89, 0x36, 0xe5, 0xd9, 0x45, 0x11, 0x76, 0x64, 0xe6, 0x04, 0xc8, 0x3a, 0x0c, 0x3b, 0xfd, 0xf5, 0x23, + 0x4c, 0x30, 0x51, 0x23, 0xbe, 0x44, 0x01, 0x25, 0x91, 0xec, 0x09, 0x6a, 0x45, 0x86, 0x73, 0xda, 0x3a, 0xab, 0xb2, + 0xf5, 0x50, 0x5d, 0x23, 0x03, 0xd7, 0xd7, 0xd5, 0xa1, 0xb6, 0xae, 0x0a, 0xf8, 0x04, 0xf4, 0x1d, 0x58, 0xdd, 0xb1, + 0xbb, 0xa9, 0xd2, 0xf9, 0xcc, 0x11, 0x7a, 0xea, 0x94, 0x46, 0x30, 0xd1, 0xc2, 0xfe, 0x2f, 0x87, 0x9d, 0xde, 0x76, + 0x67, 0x0a, 0xbd, 0x41, 0x81, 0xc3, 0x5b, 0xbb, 0xb7, 0xbd, 0x8d, 0x6f, 0x17, 0xea, 0xad, 0x8b, 0x6f, 0xb1, 0x7a, + 0xdb, 0xc1, 0xb7, 0xa1, 0x7a, 0x7b, 0x84, 0x6f, 0x23, 0xf5, 0xf6, 0x18, 0xdf, 0xce, 0xed, 0xf2, 0x90, 0x6b, 0xe0, + 0x1e, 0x03, 0x5f, 0x91, 0x37, 0x13, 0xa8, 0x32, 0xd8, 0xf4, 0x78, 0xfd, 0x32, 0x3a, 0x0b, 0x62, 0x4f, 0x78, 0x97, + 0x41, 0xee, 0x5d, 0x80, 0xc6, 0x09, 0x28, 0xdb, 0xf0, 0x39, 0x7e, 0x87, 0x03, 0x9c, 0xa4, 0x83, 0x78, 0xca, 0xd4, + 0x07, 0x89, 0x15, 0xd6, 0x60, 0xc0, 0x1e, 0xb6, 0x8f, 0xca, 0x9e, 0x5e, 0x27, 0x11, 0xcf, 0x52, 0xd9, 0x1c, 0xb4, + 0x72, 0x55, 0x9d, 0x98, 0xae, 0xa5, 0x57, 0x78, 0x8d, 0xfe, 0x32, 0xe2, 0x11, 0x63, 0x30, 0xcc, 0x5a, 0x97, 0xe0, + 0xc1, 0xae, 0xd4, 0x69, 0x08, 0x91, 0xd6, 0x69, 0x84, 0x93, 0x7e, 0x3b, 0x88, 0xce, 0xf4, 0xf3, 0x1b, 0xb0, 0xb4, + 0xa3, 0x33, 0xd9, 0x72, 0xbd, 0x0e, 0x23, 0x10, 0x4d, 0xfd, 0xa5, 0x80, 0x20, 0x53, 0x0c, 0x96, 0x06, 0x3d, 0x69, + 0xa9, 0xbf, 0x90, 0x3a, 0x75, 0x8d, 0x46, 0xd3, 0xd7, 0x8b, 0x80, 0xa2, 0x55, 0xc1, 0x2e, 0x18, 0xfc, 0x54, 0x2a, + 0x28, 0x0c, 0x15, 0x58, 0x20, 0xaa, 0xd7, 0xa8, 0x32, 0x1d, 0x6c, 0x58, 0xab, 0xd0, 0x2c, 0xa5, 0xcb, 0xcc, 0xd3, + 0x1d, 0x7d, 0xb4, 0xb3, 0x2c, 0x5e, 0x3f, 0xeb, 0x0c, 0xf1, 0x1f, 0x29, 0xbc, 0x3f, 0x1b, 0x8f, 0xc7, 0x37, 0xea, + 0xb6, 0xcf, 0x46, 0x63, 0xd6, 0x65, 0x3b, 0x3d, 0x8c, 0xfc, 0xb7, 0xa4, 0x38, 0xed, 0x94, 0x44, 0xbb, 0xc5, 0xdd, + 0x1a, 0xa3, 0xe4, 0x05, 0x75, 0x77, 0x77, 0x25, 0x58, 0x02, 0x55, 0x16, 0x20, 0xfc, 0xcf, 0xe2, 0x34, 0x68, 0x97, + 0xfe, 0xb9, 0xd4, 0x1a, 0x9f, 0x3d, 0x79, 0xf2, 0xa4, 0xf4, 0x47, 0xea, 0xad, 0x3d, 0x1a, 0x95, 0xfe, 0x70, 0xa1, + 0xd1, 0x68, 0xb7, 0xc7, 0xe3, 0xd2, 0x8f, 0x55, 0xc1, 0x76, 0x77, 0x38, 0xda, 0xee, 0x96, 0xfe, 0x85, 0xd1, 0xa2, + 0xf4, 0x99, 0x7c, 0xcb, 0xd9, 0xa8, 0x76, 0x7c, 0xf0, 0xb8, 0x0d, 0x95, 0x82, 0xd1, 0x16, 0xe8, 0x5d, 0x8a, 0xc7, + 0x20, 0x9a, 0xf3, 0x0c, 0x0c, 0xbb, 0xb2, 0x57, 0x80, 0x7c, 0x1e, 0x4b, 0x09, 0x2f, 0xbe, 0xf7, 0x8b, 0x52, 0xfd, + 0x95, 0x29, 0xd5, 0x91, 0x99, 0x49, 0x9a, 0x17, 0xa4, 0x0d, 0x9a, 0xd5, 0xc8, 0x59, 0x54, 0xfd, 0x2a, 0x2c, 0x2a, + 0x61, 0x8f, 0xd2, 0x06, 0x5b, 0x0a, 0x19, 0xff, 0xc3, 0x3a, 0x19, 0xff, 0xfd, 0xed, 0x32, 0xfe, 0xf4, 0x6e, 0x22, + 0xfe, 0xfb, 0xdf, 0x59, 0xc4, 0xff, 0x60, 0x8a, 0x78, 0x21, 0xc4, 0xf6, 0xc0, 0x74, 0x26, 0x9b, 0xf9, 0x34, 0xbb, + 0x6c, 0xe1, 0x96, 0xc8, 0x6d, 0x92, 0x9e, 0xd3, 0x3b, 0x09, 0xff, 0x15, 0xf9, 0x60, 0x6a, 0x30, 0xe3, 0xe3, 0xc1, + 0x3c, 0x3b, 0x3b, 0x4b, 0x98, 0x92, 0xf1, 0x46, 0x05, 0x99, 0xe3, 0xef, 0xd2, 0xd0, 0x7e, 0x07, 0x9e, 0xb1, 0x51, + 0x32, 0x1e, 0x43, 0xd1, 0x78, 0x6c, 0xab, 0x7c, 0x69, 0x90, 0x67, 0xd4, 0xea, 0x6d, 0xad, 0x84, 0x5a, 0x7d, 0xf1, + 0x85, 0x59, 0x66, 0x16, 0xc8, 0x90, 0x9e, 0x69, 0x8c, 0xc8, 0x9a, 0x51, 0x5c, 0xe0, 0x1e, 0xac, 0x3e, 0x76, 0x8c, + 0xf6, 0xce, 0x14, 0x94, 0x4a, 0x3c, 0xc4, 0x73, 0x91, 0xe6, 0x87, 0x65, 0x44, 0x6e, 0xfb, 0x32, 0x72, 0xd5, 0xf9, + 0xb7, 0xf1, 0x0d, 0xc3, 0xea, 0xcc, 0x1b, 0x16, 0x5f, 0xe6, 0xb7, 0x3c, 0xbd, 0x7a, 0x35, 0x72, 0xf6, 0xc0, 0x1a, + 0x8e, 0x8b, 0x77, 0x69, 0x23, 0x6f, 0x50, 0x80, 0x1d, 0x86, 0x26, 0xa6, 0xa5, 0x20, 0x58, 0x75, 0x81, 0xa2, 0xaa, + 0xec, 0x19, 0x9d, 0x64, 0x7a, 0x19, 0x0e, 0x39, 0xa8, 0x91, 0x25, 0x30, 0x07, 0x93, 0xba, 0x90, 0x3e, 0x66, 0x2f, + 0x92, 0x6e, 0xce, 0xe5, 0x57, 0xcf, 0xe9, 0x70, 0x66, 0x21, 0xf5, 0x87, 0x4c, 0xc7, 0xa8, 0x7a, 0xe2, 0x79, 0x88, + 0x98, 0x61, 0x54, 0xaa, 0x33, 0x10, 0x20, 0xdc, 0x0c, 0x3f, 0xd1, 0x24, 0x86, 0x50, 0x07, 0x05, 0x15, 0xf5, 0xae, + 0xaf, 0xcd, 0x2f, 0x85, 0xd6, 0xbe, 0x2a, 0xd9, 0xe0, 0x01, 0x86, 0x9f, 0xf8, 0x45, 0x6d, 0x90, 0xcd, 0xb9, 0xe3, + 0x50, 0x2b, 0xc7, 0x2d, 0xbd, 0x9d, 0x76, 0x1b, 0x54, 0x8c, 0x2f, 0xbe, 0x03, 0xe5, 0xe8, 0xce, 0x12, 0xdf, 0x75, + 0xe7, 0x12, 0x4b, 0xdf, 0x65, 0xd3, 0x24, 0xc6, 0x0f, 0xc7, 0x08, 0x44, 0x8d, 0xbb, 0x43, 0x6a, 0x11, 0x9b, 0xef, + 0xbe, 0xf2, 0x1d, 0x0d, 0xc2, 0xba, 0xab, 0x38, 0x58, 0xe6, 0xd6, 0xd6, 0x0b, 0xb1, 0xad, 0xb0, 0x6a, 0x96, 0xc1, + 0xb9, 0x45, 0x67, 0x16, 0x17, 0x46, 0x00, 0xbf, 0xb6, 0x0d, 0x4a, 0x15, 0xc1, 0x17, 0x61, 0xf8, 0x3d, 0x0c, 0x36, + 0x0b, 0xc7, 0x5b, 0x01, 0x5d, 0x77, 0x79, 0x0d, 0xc8, 0xd1, 0x19, 0xd6, 0x8c, 0xae, 0xaa, 0x54, 0x41, 0x69, 0x1e, + 0xc1, 0x18, 0xc8, 0x50, 0x24, 0x1d, 0xd6, 0x38, 0x15, 0x7a, 0x0b, 0xa6, 0x21, 0x01, 0xac, 0xfd, 0x3a, 0x74, 0x6b, + 0x6c, 0x05, 0xb6, 0x90, 0x16, 0xa0, 0xf4, 0xb0, 0x43, 0xdf, 0xaa, 0x81, 0x9e, 0x2e, 0x07, 0xe0, 0x6f, 0x74, 0xf2, + 0x4e, 0xfc, 0xe2, 0xc2, 0x83, 0xff, 0xac, 0x3f, 0x2c, 0x40, 0xca, 0x9f, 0x7e, 0x8a, 0x39, 0xd8, 0xd4, 0xb3, 0x16, + 0x86, 0x5f, 0x28, 0x4e, 0x2b, 0xd5, 0x21, 0x1d, 0x45, 0x8b, 0x2b, 0x63, 0xbd, 0x79, 0x81, 0xbe, 0x20, 0x39, 0x3d, + 0x41, 0x9a, 0xa5, 0xac, 0x57, 0x4f, 0x39, 0x30, 0xfd, 0x0e, 0x45, 0xac, 0xa3, 0x45, 0x86, 0xbe, 0x23, 0xbf, 0x02, + 0xdf, 0x51, 0xa8, 0xd1, 0xb6, 0x72, 0x3a, 0xda, 0x2b, 0xdb, 0x07, 0x92, 0xb6, 0x9b, 0x64, 0x2d, 0xe4, 0xcb, 0xce, + 0xd5, 0x3a, 0xe7, 0xe8, 0xb6, 0x03, 0x78, 0x0c, 0x0a, 0xab, 0xff, 0x8c, 0xcc, 0x85, 0x66, 0x31, 0x1d, 0xc0, 0xdf, + 0x05, 0xb2, 0x20, 0x1a, 0xe3, 0x17, 0x16, 0xef, 0xd2, 0xf2, 0x94, 0xb2, 0x5f, 0x17, 0xa8, 0xd6, 0x83, 0xce, 0x13, + 0xf0, 0xf6, 0xee, 0x3c, 0xfc, 0xcd, 0xe8, 0x97, 0x92, 0x46, 0xea, 0x12, 0xb3, 0x6d, 0xf7, 0x50, 0x5e, 0x24, 0xd1, + 0x15, 0x38, 0x9d, 0x64, 0x63, 0x9c, 0x62, 0xf4, 0xb8, 0x37, 0xcb, 0x64, 0x26, 0x49, 0xce, 0x12, 0xfa, 0x19, 0x13, + 0xb9, 0x14, 0xdb, 0x8f, 0x66, 0x97, 0x6a, 0x35, 0x3a, 0x8d, 0x0c, 0x91, 0xdf, 0x35, 0x11, 0x64, 0x7d, 0xe6, 0x49, + 0x3d, 0x99, 0x61, 0x07, 0x60, 0x10, 0x86, 0x4d, 0x2b, 0x17, 0x50, 0xb5, 0xa1, 0xc4, 0x48, 0x85, 0xa9, 0x06, 0xb2, + 0xfc, 0x6d, 0x50, 0x95, 0x51, 0xc1, 0x7a, 0xf8, 0xa9, 0xcb, 0x18, 0x5c, 0x5b, 0x69, 0x3c, 0x4d, 0xe3, 0xd1, 0x28, + 0x61, 0x3d, 0x65, 0x1f, 0x59, 0x9d, 0x47, 0x98, 0x49, 0x62, 0x2e, 0x59, 0x7d, 0x55, 0x0c, 0xe2, 0x69, 0x3a, 0x45, + 0xa7, 0x60, 0xaf, 0xe1, 0xf7, 0x2a, 0x57, 0x92, 0x53, 0xa6, 0x58, 0xb4, 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, 0x22, 0xea, 0xa2, 0xb2, 0x8e, 0xcc, + 0x5f, 0x67, 0xb7, 0x7c, 0xbe, 0x7a, 0xb7, 0x7c, 0xae, 0x76, 0xcb, 0xcd, 0x1c, 0xfb, 0xd9, 0xb8, 0x83, 0xff, 0xf4, + 0x2a, 0x84, 0x60, 0x55, 0x80, 0x1c, 0x16, 0xda, 0xc5, 0xad, 0x2e, 0xfc, 0x8f, 0x86, 0x6e, 0x7b, 0xf8, 0x8f, 0x0f, + 0x16, 0x60, 0xdb, 0xc2, 0x42, 0xfc, 0xaf, 0x5d, 0xab, 0xea, 0x3c, 0xc4, 0x3a, 0xec, 0xb5, 0xb3, 0x5c, 0xd7, 0xbd, + 0x79, 0xd3, 0x82, 0xbc, 0xe2, 0x4e, 0xa0, 0x84, 0x31, 0xb8, 0x6a, 0xd1, 0xe9, 0x29, 0x94, 0x8e, 0xb3, 0xe1, 0xbc, + 0xf8, 0x5b, 0x09, 0xbf, 0x24, 0xe2, 0x8d, 0x5b, 0xba, 0x31, 0x8e, 0xea, 0x2a, 0xd2, 0x92, 0xd4, 0x08, 0x0b, 0xbd, + 0x4e, 0x41, 0x01, 0x8c, 0xc9, 0x9c, 0xae, 0xff, 0x70, 0xc5, 0x26, 0xf8, 0xff, 0xb2, 0x36, 0x2b, 0x91, 0xf9, 0x8f, + 0x12, 0xe3, 0x46, 0x22, 0xfc, 0x2a, 0x1a, 0x98, 0x6b, 0xd8, 0x7e, 0xb2, 0x1a, 0xdc, 0x43, 0x35, 0xd3, 0x91, 0x52, + 0x0a, 0x52, 0xef, 0x80, 0x17, 0x10, 0xcd, 0x13, 0x7e, 0xf3, 0xa8, 0xeb, 0x38, 0x63, 0x69, 0xd4, 0x1b, 0x04, 0x7a, + 0xd5, 0xf6, 0x8e, 0x52, 0xfa, 0xb3, 0xcf, 0x1f, 0xe2, 0x3f, 0x22, 0x70, 0x76, 0x5a, 0xf9, 0x46, 0x22, 0x36, 0x80, + 0xbe, 0xd1, 0xb4, 0xe6, 0xfc, 0x08, 0x0d, 0x4e, 0xfe, 0xcf, 0x5d, 0x5b, 0xa3, 0xb1, 0x7e, 0xa7, 0xe6, 0xd2, 0x2a, + 0xfd, 0x55, 0xad, 0x7f, 0xdd, 0xe0, 0x77, 0x6c, 0x3b, 0x14, 0x0e, 0x41, 0xbd, 0xad, 0x8c, 0x07, 0x2e, 0x35, 0x56, + 0x14, 0xbf, 0x6b, 0xfb, 0xca, 0x24, 0xa6, 0x1e, 0xd3, 0xf0, 0x54, 0x3b, 0x91, 0xf2, 0xf0, 0x1e, 0x7b, 0x08, 0x3f, + 0xf2, 0x4b, 0x16, 0x3e, 0xc0, 0xaf, 0xb1, 0x59, 0x97, 0xd3, 0x24, 0x05, 0xb3, 0x6a, 0xc2, 0xf9, 0x2c, 0xd8, 0xda, + 0xba, 0xb8, 0xb8, 0xf0, 0x2f, 0xb6, 0xfd, 0x2c, 0x3f, 0xdb, 0xea, 0xb6, 0xdb, 0x6d, 0xfc, 0x88, 0x96, 0x6d, 0x9d, + 0xc7, 0xec, 0xe2, 0x29, 0xb8, 0x1f, 0xf6, 0x63, 0xeb, 0x89, 0xf5, 0x78, 0xdb, 0xda, 0x79, 0x64, 0x5b, 0xa4, 0x00, + 0xa0, 0x64, 0xdb, 0xb6, 0x84, 0x02, 0x08, 0x6d, 0x28, 0xee, 0xef, 0x9e, 0x29, 0x1b, 0x0e, 0x2f, 0x29, 0x08, 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, 0x85, 0x27, 0xcc, 0x6a, 0xb3, 0xe0, + 0xf9, 0x4d, 0xf7, 0x31, 0xe8, 0xb8, 0xf3, 0xd6, 0xc3, 0x61, 0xbb, 0xd5, 0xb1, 0x3a, 0xad, 0xae, 0xff, 0xd8, 0xea, + 0x8a, 0xff, 0x83, 0x8c, 0xdc, 0xb6, 0x3a, 0xf0, 0xb4, 0x6d, 0xc1, 0xfb, 0xf9, 0x43, 0x91, 0x5b, 0x12, 0xd9, 0x5b, + 0xfd, 0x5d, 0xfc, 0x4d, 0x29, 0x40, 0xea, 0x73, 0x5b, 0xfc, 0x0a, 0x9e, 0xfd, 0x99, 0x59, 0xda, 0x79, 0xb2, 0xb2, + 0xb8, 0xfb, 0x78, 0x65, 0xf1, 0xf6, 0xa3, 0x95, 0xc5, 0x0f, 0x77, 0xea, 0xc5, 0x5b, 0x67, 0xa2, 0x4a, 0xcb, 0x85, + 0xd0, 0x9e, 0x46, 0xc0, 0x28, 0x97, 0x4e, 0x07, 0xe0, 0x6c, 0x5b, 0x2d, 0xfc, 0xf3, 0xb8, 0xeb, 0xea, 0x5e, 0xa7, + 0xd8, 0x4b, 0x63, 0xf9, 0xf8, 0x09, 0x60, 0xf9, 0xb2, 0xfb, 0x68, 0x88, 0xed, 0x08, 0x51, 0xf8, 0xef, 0x7c, 0xfb, + 0xc9, 0x10, 0x34, 0x82, 0x85, 0xff, 0xc1, 0x3f, 0x93, 0x9d, 0xee, 0x50, 0xbc, 0xb4, 0xb1, 0xfe, 0xdb, 0xce, 0xe3, + 0x02, 0x9a, 0xe2, 0x3f, 0xbf, 0x68, 0x13, 0x1a, 0x0d, 0x78, 0x73, 0xdc, 0x87, 0x40, 0xa3, 0x27, 0x93, 0xae, 0xff, + 0xf9, 0xf9, 0x63, 0xff, 0xc9, 0xa4, 0xf3, 0xf8, 0x5b, 0xf1, 0x96, 0x00, 0x05, 0x3f, 0xc7, 0xff, 0xbe, 0xdd, 0x6e, + 0x4f, 0x5a, 0x1d, 0xff, 0xc9, 0xf9, 0xb6, 0xbf, 0x9d, 0xb4, 0x1e, 0xf9, 0x4f, 0xf0, 0xbf, 0x6a, 0xb8, 0x49, 0x36, + 0x65, 0xb6, 0x85, 0xeb, 0xdd, 0xf0, 0x7b, 0xcd, 0x39, 0xba, 0x0f, 0xad, 0x9d, 0x87, 0x2f, 0x9f, 0xc0, 0x1a, 0x4d, + 0x3a, 0x5d, 0xf8, 0xff, 0xba, 0xc7, 0x6f, 0x91, 0xf0, 0x72, 0xe0, 0x88, 0x61, 0x7a, 0xb1, 0x22, 0x1c, 0x7d, 0xd0, + 0xed, 0x81, 0xf7, 0xa7, 0x75, 0x01, 0x10, 0xc6, 0x6f, 0x0d, 0x80, 0x70, 0x7e, 0xb7, 0x08, 0x08, 0xfd, 0xda, 0xc0, + 0xef, 0x18, 0x01, 0xf9, 0x53, 0x33, 0xc8, 0x7d, 0xc9, 0x96, 0x02, 0x1d, 0x4d, 0x67, 0xed, 0x2d, 0x73, 0x0e, 0xbf, + 0x64, 0x47, 0x98, 0x4a, 0x0f, 0xad, 0x39, 0x37, 0xe3, 0x41, 0x19, 0x6e, 0xe4, 0x4b, 0x26, 0x76, 0x72, 0xc1, 0xd7, + 0x10, 0x24, 0xbe, 0x9d, 0x20, 0xdf, 0xde, 0x8d, 0x1e, 0xf1, 0xef, 0x4c, 0x8f, 0x82, 0x1b, 0xf4, 0xa8, 0x45, 0xdc, + 0x29, 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0x9c, 0xbe, 0xc5, 0xb6, 0xc5, 0xb0, 0xa8, 0xb0, 0x45, 0xce, 0xe6, + 0xd3, 0x5f, 0x73, 0x44, 0x20, 0xd2, 0xcd, 0x43, 0x5b, 0x46, 0x61, 0x66, 0xf8, 0xd1, 0x62, 0xf5, 0x72, 0x2e, 0xae, + 0x34, 0x85, 0x74, 0x1f, 0x71, 0x47, 0x47, 0x70, 0xf0, 0x06, 0x40, 0xb8, 0xc8, 0x78, 0x84, 0xbf, 0x8a, 0x05, 0xe4, + 0xa6, 0xdf, 0xcf, 0x8a, 0x79, 0x82, 0x97, 0xa6, 0xbd, 0xa1, 0xf8, 0x80, 0x2c, 0x3c, 0xca, 0xbb, 0x86, 0x98, 0xc2, + 0xfe, 0x0d, 0xa6, 0xdf, 0xab, 0xb3, 0x83, 0x29, 0xc6, 0x11, 0xde, 0xb0, 0x51, 0x1c, 0x39, 0xb6, 0x33, 0x83, 0x8d, + 0x0c, 0xb3, 0xb4, 0x6a, 0xb9, 0xef, 0x94, 0xf6, 0xee, 0xda, 0xea, 0xa7, 0x99, 0x72, 0xfc, 0xd4, 0x5d, 0x78, 0x28, + 0xe3, 0x8e, 0xb6, 0x74, 0x0c, 0x60, 0x7c, 0x55, 0x92, 0xa3, 0x0e, 0xa8, 0x8c, 0x09, 0x5b, 0x58, 0x13, 0x1d, 0xbf, + 0x0b, 0xde, 0x05, 0x15, 0xe3, 0xa7, 0xc3, 0xbe, 0x77, 0x5a, 0xdb, 0x60, 0xed, 0x18, 0xdd, 0xf4, 0x40, 0x47, 0xfa, + 0x97, 0x7e, 0xf4, 0xaf, 0xd1, 0xd5, 0x2f, 0x0c, 0xd8, 0x82, 0x23, 0x3e, 0x13, 0xb8, 0xdb, 0xf4, 0x89, 0x06, 0x99, + 0x50, 0x82, 0x17, 0xe6, 0xa0, 0xcc, 0x31, 0x7f, 0x95, 0x4c, 0x7c, 0x9a, 0x4c, 0xfc, 0x00, 0x61, 0x59, 0x35, 0x61, + 0xd5, 0xcf, 0x7f, 0x20, 0x05, 0x99, 0xa7, 0x67, 0x23, 0xea, 0x61, 0x86, 0x07, 0xfe, 0xad, 0x8a, 0xd5, 0x83, 0x8c, + 0x58, 0x81, 0x17, 0x8f, 0xbf, 0xe9, 0x42, 0x7f, 0x96, 0xe2, 0x61, 0x22, 0xca, 0xd1, 0x28, 0xad, 0x86, 0xaa, 0xe2, + 0x5e, 0xc5, 0xd3, 0xab, 0x03, 0xf9, 0x41, 0x03, 0x1b, 0x43, 0xd0, 0x74, 0xf4, 0x50, 0x7d, 0x4c, 0x6d, 0x13, 0xf4, + 0x1e, 0xfd, 0xc4, 0x29, 0x65, 0x0f, 0xa0, 0x6a, 0xc3, 0xfb, 0x04, 0x96, 0x74, 0x81, 0x42, 0x5b, 0x28, 0xb6, 0x11, + 0x3b, 0x8f, 0x87, 0x52, 0x3f, 0x79, 0x96, 0xbc, 0x07, 0xd5, 0x22, 0xba, 0x87, 0x1d, 0x4f, 0x04, 0x01, 0xe0, 0x05, + 0xd5, 0x73, 0x98, 0x66, 0x76, 0xff, 0x41, 0x6f, 0x1d, 0x65, 0xf1, 0xf7, 0x56, 0x0f, 0xc1, 0xe9, 0xfc, 0xdb, 0xf0, + 0x01, 0xfe, 0xe2, 0xea, 0x83, 0x23, 0xdb, 0xf5, 0x49, 0xba, 0x3f, 0xa8, 0x7e, 0x76, 0x15, 0x45, 0xdb, 0x26, 0x28, + 0x62, 0xef, 0xae, 0x1a, 0x59, 0x6a, 0xdf, 0xee, 0x4e, 0xa5, 0x7d, 0xe1, 0xd9, 0x10, 0xb7, 0xa0, 0x09, 0xba, 0xfe, + 0x8e, 0x21, 0xd3, 0xcf, 0x5b, 0xf8, 0xb7, 0x26, 0xd5, 0x1f, 0x42, 0x03, 0x25, 0xd6, 0x5f, 0x43, 0xf3, 0x6d, 0xa1, + 0x41, 0xa0, 0xdf, 0x0f, 0x24, 0x73, 0x85, 0xbc, 0xad, 0xf3, 0xf8, 0x8a, 0xd3, 0x30, 0x91, 0x69, 0x61, 0x7b, 0x46, + 0xe0, 0x4c, 0x6c, 0x39, 0x19, 0x16, 0x7a, 0x0e, 0x7d, 0x1d, 0xfd, 0x8d, 0xf2, 0x55, 0x75, 0x5e, 0x4d, 0x04, 0xac, + 0x98, 0x02, 0x37, 0x6d, 0xe3, 0xc4, 0xad, 0x27, 0x92, 0xb8, 0xf5, 0x47, 0x4e, 0xd6, 0x73, 0xab, 0xcc, 0xf6, 0x76, + 0x8d, 0xfd, 0xcf, 0xe9, 0x3b, 0xaa, 0x34, 0xc9, 0xab, 0x51, 0xd9, 0x9c, 0x1f, 0x6c, 0x16, 0xfc, 0xd1, 0xc9, 0xea, + 0x0a, 0x8f, 0xbc, 0x9b, 0x8b, 0xf9, 0x14, 0xa3, 0x38, 0xa7, 0x2b, 0xdf, 0x0a, 0xf4, 0x5a, 0x54, 0xb5, 0xa2, 0x12, + 0x89, 0x00, 0x56, 0x0c, 0x6c, 0x2c, 0xb2, 0x03, 0x99, 0xf5, 0x67, 0x7e, 0x48, 0xdc, 0xbc, 0x93, 0x3b, 0x12, 0x09, + 0x7f, 0xf8, 0x43, 0x0b, 0xb6, 0xa0, 0x8f, 0x0d, 0xa2, 0x74, 0xed, 0x2e, 0x21, 0x03, 0x0b, 0x71, 0xad, 0x7e, 0x39, + 0xcb, 0x94, 0x2e, 0xb6, 0x49, 0x68, 0x3d, 0x2e, 0x91, 0xd0, 0x95, 0x74, 0x3a, 0x65, 0x11, 0xf7, 0xa3, 0x94, 0x92, + 0xb3, 0x1c, 0x43, 0x06, 0x79, 0x1d, 0xb6, 0xed, 0x96, 0x20, 0xf8, 0x8c, 0x9f, 0x16, 0x13, 0x9b, 0xd9, 0x87, 0x42, + 0xfd, 0x59, 0xab, 0x7a, 0xa2, 0xf5, 0xa4, 0xdb, 0x7f, 0x77, 0xb0, 0x67, 0x89, 0x4d, 0xb9, 0xbb, 0x05, 0xaf, 0xbb, + 0xe4, 0x9e, 0x8b, 0x3c, 0x95, 0x50, 0xe4, 0xa9, 0x58, 0x22, 0xbb, 0x4d, 0x24, 0x26, 0x6f, 0x09, 0xb4, 0x6d, 0x8b, + 0xa5, 0x43, 0x11, 0x57, 0x9c, 0x82, 0x0b, 0x13, 0xe3, 0xc7, 0xe7, 0xb6, 0xb0, 0x6b, 0x0b, 0x17, 0xcc, 0x56, 0x29, + 0x3f, 0xca, 0x68, 0xe1, 0xa9, 0x8a, 0x42, 0x82, 0xa9, 0xc1, 0x54, 0xf6, 0x8f, 0x1c, 0x4a, 0x27, 0x1d, 0x2f, 0xb7, + 0x2e, 0xe6, 0xa7, 0x53, 0x10, 0x82, 0x2a, 0x63, 0xe7, 0xa3, 0xec, 0xb0, 0x4b, 0x53, 0xf5, 0x4f, 0x4a, 0x19, 0x26, + 0x55, 0x1f, 0x06, 0x6f, 0xfc, 0x88, 0xaa, 0xc0, 0x5e, 0x0a, 0x7d, 0x4c, 0x38, 0x99, 0x6c, 0x1b, 0x09, 0x27, 0x46, + 0x5d, 0x09, 0xa8, 0x6f, 0xf7, 0x4f, 0x82, 0x99, 0x1c, 0xef, 0x75, 0xb6, 0xf4, 0x83, 0xac, 0xa2, 0x3d, 0x28, 0x94, + 0x01, 0x25, 0x8f, 0x8b, 0x4b, 0x1b, 0x12, 0x60, 0x58, 0x41, 0x80, 0x49, 0xea, 0x77, 0x8b, 0xce, 0xb5, 0xed, 0x9d, + 0xb6, 0xca, 0xc9, 0x85, 0x32, 0xdc, 0x90, 0xa2, 0x8b, 0x31, 0x49, 0x2d, 0xb6, 0x3b, 0xe9, 0xf4, 0x77, 0x23, 0x69, + 0x39, 0xa2, 0xf0, 0x28, 0x40, 0x7a, 0x40, 0x67, 0x34, 0xcf, 0xfc, 0x38, 0xdb, 0xba, 0x60, 0xa7, 0xad, 0x68, 0x16, + 0x57, 0x81, 0x54, 0xb4, 0x23, 0xf4, 0x94, 0x59, 0x35, 0x13, 0x3e, 0x46, 0x0d, 0x24, 0x49, 0x70, 0x97, 0x32, 0x4a, + 0x4b, 0xe6, 0x37, 0xb0, 0x10, 0x50, 0x98, 0xe4, 0xba, 0x8a, 0xe6, 0x4a, 0x75, 0x5a, 0xda, 0xfd, 0xbf, 0xfc, 0xf3, + 0xff, 0x96, 0x01, 0x5a, 0xa0, 0x4a, 0x47, 0x8d, 0xd5, 0x20, 0x74, 0xb9, 0x8b, 0xf9, 0x4d, 0xd5, 0x11, 0x2e, 0xbb, + 0x04, 0x4f, 0x3f, 0x1e, 0xb5, 0x26, 0x51, 0x32, 0x06, 0xc0, 0xd6, 0x12, 0xc8, 0xcc, 0x7e, 0x90, 0x50, 0xd7, 0x8b, + 0x90, 0x05, 0x7f, 0x53, 0x96, 0xb5, 0xca, 0x6e, 0xa7, 0xdd, 0x6a, 0xe4, 0x5c, 0x1b, 0x1b, 0xaa, 0x96, 0x77, 0xad, + 0x7e, 0x95, 0x4c, 0x0a, 0x35, 0x56, 0x4b, 0xba, 0x86, 0x96, 0xfa, 0xa4, 0xe9, 0xdf, 0xff, 0xe5, 0x1f, 0xfe, 0x87, + 0x7a, 0xc5, 0x03, 0xa4, 0xbf, 0xfc, 0xd3, 0xdf, 0x61, 0x7e, 0xb3, 0xa5, 0x0f, 0x99, 0x48, 0x4e, 0x58, 0xd5, 0x09, + 0x93, 0x10, 0x18, 0x56, 0xe5, 0xd1, 0xd5, 0x93, 0xb3, 0xf7, 0x69, 0x42, 0xda, 0x6c, 0x12, 0x3a, 0xda, 0xb4, 0x65, + 0xc5, 0x23, 0x35, 0x92, 0x13, 0x2f, 0x42, 0x25, 0xd2, 0xfb, 0x4e, 0x99, 0x4f, 0xbe, 0x5e, 0x8d, 0x85, 0x0a, 0xff, + 0x61, 0x49, 0x59, 0x95, 0x5b, 0x18, 0x97, 0x5f, 0xe0, 0x6b, 0xd0, 0x35, 0x8a, 0x69, 0xf1, 0x6a, 0x7d, 0x7a, 0x3f, + 0xcd, 0x01, 0xfe, 0x31, 0x52, 0x5c, 0x04, 0x19, 0xe9, 0xcc, 0xb9, 0x85, 0x06, 0x5d, 0x72, 0x55, 0xd2, 0x28, 0xc2, + 0x0b, 0x7c, 0xf8, 0xe4, 0x6f, 0xca, 0x3f, 0x4e, 0xd1, 0x6c, 0xb2, 0x9c, 0x69, 0x74, 0x29, 0x7d, 0xc3, 0x47, 0xed, + 0xf6, 0xec, 0xd2, 0x5d, 0x54, 0x33, 0x78, 0xeb, 0x26, 0xa3, 0xc0, 0xa4, 0x39, 0x20, 0x1d, 0x56, 0xeb, 0x18, 0x28, + 0xb8, 0x43, 0x6d, 0x0c, 0x99, 0x95, 0xe5, 0x1f, 0x16, 0x14, 0x86, 0x8b, 0x7f, 0xc1, 0x43, 0x65, 0x19, 0xb1, 0x84, + 0x12, 0x03, 0x8b, 0x85, 0xd1, 0xab, 0x2b, 0x7a, 0x4d, 0x3a, 0xcb, 0x39, 0x41, 0xe6, 0xa1, 0xb8, 0x79, 0x9c, 0xfd, + 0x10, 0x0f, 0xa8, 0x27, 0x1d, 0x6f, 0xd2, 0x5d, 0xe8, 0xe1, 0x39, 0xcf, 0xa6, 0xe6, 0x29, 0x38, 0x8b, 0xd8, 0x90, + 0x8d, 0x55, 0xa4, 0x57, 0xd6, 0x8b, 0x13, 0xee, 0x72, 0xb2, 0xbd, 0x62, 0x2e, 0x09, 0x12, 0x9d, 0x7e, 0x03, 0x3c, + 0x9f, 0xe1, 0x06, 0x04, 0xfa, 0x67, 0x11, 0x0f, 0x88, 0x5f, 0x7b, 0xe6, 0x59, 0x7a, 0x84, 0x52, 0x26, 0x5b, 0x18, + 0xf0, 0xf4, 0x44, 0x53, 0x8c, 0xb9, 0xd6, 0x73, 0xb2, 0x4a, 0x9f, 0xba, 0x9b, 0x43, 0x89, 0x90, 0xcd, 0xb7, 0xf2, + 0x88, 0xfa, 0x69, 0x2d, 0xd6, 0x21, 0x55, 0x4c, 0xd7, 0xf5, 0x56, 0xd6, 0x0b, 0x4d, 0x2d, 0x6a, 0xbf, 0x05, 0x03, + 0x8c, 0xc0, 0xb4, 0x9b, 0xad, 0xa8, 0x10, 0x5b, 0x3d, 0x0d, 0xbf, 0xd5, 0x7e, 0x4d, 0x34, 0x9b, 0x51, 0x43, 0x17, + 0x98, 0x98, 0xac, 0x51, 0x94, 0x1d, 0x94, 0x7e, 0x21, 0xb2, 0x1d, 0x64, 0x1b, 0xb9, 0x11, 0xc4, 0x93, 0xcc, 0x83, + 0xa0, 0xdf, 0xb7, 0xff, 0x7f, 0x47, 0x48, 0x09, 0x5d, 0xf5, 0x7e, 0x00, 0x00}; } // namespace web_server } // namespace esphome From 314c1c8b5cc98acba1a4f8185b1aec6d47920754 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 22 Jun 2023 01:45:41 +0200 Subject: [PATCH 033/106] Migrate VOC sensors that use ppb to use volatile_organic_compounds_parts device class (#4982) --- esphome/components/airthings_wave_base/__init__.py | 2 ++ esphome/components/ccs811/sensor.py | 4 ++-- esphome/components/sgp30/sensor.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/esphome/components/airthings_wave_base/__init__.py b/esphome/components/airthings_wave_base/__init__.py index 3ff55fc6b0..c935ce108a 100644 --- a/esphome/components/airthings_wave_base/__init__.py +++ b/esphome/components/airthings_wave_base/__init__.py @@ -14,6 +14,7 @@ from esphome.const import ( CONF_TVOC, CONF_PRESSURE, CONF_TEMPERATURE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, UNIT_PARTS_PER_BILLION, ICON_RADIATOR, ) @@ -53,6 +54,7 @@ BASE_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, state_class=STATE_CLASS_MEASUREMENT, ), } diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index cb5c1108ba..af3e6574ab 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -6,7 +6,7 @@ from esphome.const import ( ICON_RADIATOR, ICON_RESTART, DEVICE_CLASS_CARBON_DIOXIDE, - DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, @@ -43,7 +43,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, - device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 0029e2c515..6f8ed42d25 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -11,7 +11,7 @@ from esphome.const import ( CONF_TVOC, ICON_RADIATOR, DEVICE_CLASS_CARBON_DIOXIDE, - DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, STATE_CLASS_MEASUREMENT, UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, @@ -49,7 +49,7 @@ CONFIG_SCHEMA = ( unit_of_measurement=UNIT_PARTS_PER_BILLION, icon=ICON_RADIATOR, accuracy_decimals=0, - device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( From f72b07eb0e1b431128aa6f6b231aadfb1d335376 Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Wed, 21 Jun 2023 20:48:17 -0300 Subject: [PATCH 034/106] dashboard: Adds "compressed=1" to /download.bin endpoint. (...) (#4966) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/dashboard/dashboard.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 8d8eb74b4b..22bbe0aae9 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -3,6 +3,7 @@ import binascii import codecs import collections import functools +import gzip import hashlib import hmac import json @@ -485,6 +486,7 @@ class DownloadBinaryRequestHandler(BaseHandler): @bind_config def get(self, configuration=None): type = self.get_argument("type", "firmware.bin") + compressed = self.get_argument("compressed", "0") == "1" storage_path = ext_storage_path(settings.config_dir, configuration) storage_json = StorageJSON.load(storage_path) @@ -534,6 +536,8 @@ class DownloadBinaryRequestHandler(BaseHandler): self.send_error(404) return + filename = filename + ".gz" if compressed else filename + self.set_header("Content-Type", "application/octet-stream") self.set_header("Content-Disposition", f'attachment; filename="{filename}"') self.set_header("Cache-Control", "no-cache") @@ -543,9 +547,20 @@ class DownloadBinaryRequestHandler(BaseHandler): with open(path, "rb") as f: while True: - data = f.read(16384) + # For a 528KB image used as benchmark: + # - using 256KB blocks resulted in the smallest file size. + # - blocks larger than 256KB didn't improve the size of compressed file. + # - blocks smaller than 256KB hindered compression, making the output file larger. + + # Read file in blocks of 256KB. + data = f.read(256 * 1024) + if not data: break + + if compressed: + data = gzip.compress(data, 9) + self.write(data) self.finish() From 52d7d2cae7a41100116b2e089cf2b6576808ad24 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Thu, 22 Jun 2023 06:09:00 +0200 Subject: [PATCH 035/106] Make ethernet_info work with esp-idf framework (#4976) --- .../components/ethernet_info/ethernet_info_text_sensor.cpp | 4 ++-- esphome/components/ethernet_info/ethernet_info_text_sensor.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp index e69872c290..f841875396 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.cpp @@ -1,7 +1,7 @@ #include "ethernet_info_text_sensor.h" #include "esphome/core/log.h" -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 namespace esphome { namespace ethernet_info { @@ -13,4 +13,4 @@ void IPAddressEthernetInfo::dump_config() { LOG_TEXT_SENSOR("", "EthernetInfo IP } // namespace ethernet_info } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 diff --git a/esphome/components/ethernet_info/ethernet_info_text_sensor.h b/esphome/components/ethernet_info/ethernet_info_text_sensor.h index aad8f362b5..2d46fe18eb 100644 --- a/esphome/components/ethernet_info/ethernet_info_text_sensor.h +++ b/esphome/components/ethernet_info/ethernet_info_text_sensor.h @@ -4,7 +4,7 @@ #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/ethernet/ethernet_component.h" -#ifdef USE_ESP32_FRAMEWORK_ARDUINO +#ifdef USE_ESP32 namespace esphome { namespace ethernet_info { @@ -30,4 +30,4 @@ class IPAddressEthernetInfo : public PollingComponent, public text_sensor::TextS } // namespace ethernet_info } // namespace esphome -#endif // USE_ESP32_FRAMEWORK_ARDUINO +#endif // USE_ESP32 From 85608a8ab7f20762dcc45e27876874cb0bbf8a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 22 Jun 2023 21:18:29 +0200 Subject: [PATCH 036/106] display: fix white screen on binary displays (#4991) --- esphome/components/display/display_buffer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 8d37d6536a..86e8624d33 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -12,7 +12,7 @@ namespace display { static const char *const TAG = "display"; -const Color COLOR_OFF(0, 0, 0, 255); +const Color COLOR_OFF(0, 0, 0, 0); const Color COLOR_ON(255, 255, 255, 255); void DisplayBuffer::init_internal_(uint32_t buffer_length) { From fc0e1a3cb9c401819284774ba27fccd0de061524 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 22 Jun 2023 18:03:31 -0700 Subject: [PATCH 037/106] remove unused static declarations (#4993) --- esphome/core/time.cpp | 20 ++++++++++---------- esphome/core/time.h | 4 ---- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/esphome/core/time.cpp b/esphome/core/time.cpp index c03506fd2a..bc5bfa173e 100644 --- a/esphome/core/time.cpp +++ b/esphome/core/time.cpp @@ -2,6 +2,16 @@ namespace esphome { +static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } + +static uint8_t days_in_month(uint8_t month, uint16_t year) { + static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + uint8_t days = DAYS_IN_MONTH[month]; + if (month == 2 && is_leap_year(year)) + return 29; + return days; +} + size_t ESPTime::strftime(char *buffer, size_t buffer_len, const char *format) { struct tm c_tm = this->to_c_tm(); return ::strftime(buffer, buffer_len, format, &c_tm); @@ -158,14 +168,4 @@ template bool increment_time_value(T ¤t, uint16_t begin, uint1 return false; } -static bool is_leap_year(uint32_t year) { return (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0); } - -static uint8_t days_in_month(uint8_t month, uint16_t year) { - static const uint8_t DAYS_IN_MONTH[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - uint8_t days = DAYS_IN_MONTH[month]; - if (month == 2 && is_leap_year(year)) - return 29; - return days; -} - } // namespace esphome diff --git a/esphome/core/time.h b/esphome/core/time.h index e1bdc8c839..e16e449f0b 100644 --- a/esphome/core/time.h +++ b/esphome/core/time.h @@ -8,10 +8,6 @@ namespace esphome { template bool increment_time_value(T ¤t, uint16_t begin, uint16_t end); -static bool is_leap_year(uint32_t year); - -static uint8_t days_in_month(uint8_t month, uint16_t year); - /// A more user-friendly version of struct tm from time.h struct ESPTime { /** seconds after the minute [0-60] From eb145757e5240e60044305d7b57ce3b73c9d7f87 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Fri, 23 Jun 2023 16:42:37 +1200 Subject: [PATCH 038/106] Fix rp2040 pio tool download (#4994) --- esphome/components/rp2040/__init__.py | 24 ++++------ esphome/components/rp2040/build_pio.py.script | 47 +++++++++++++++++++ esphome/components/rp2040_pio/__init__.py | 40 ++++++++++++++++ .../components/rp2040_pio_led_strip/light.py | 7 +-- 4 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 esphome/components/rp2040/build_pio.py.script create mode 100644 esphome/components/rp2040_pio/__init__.py diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index ad66ce6d18..030d586626 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -16,8 +16,7 @@ from esphome.const import ( KEY_TARGET_PLATFORM, ) from esphome.core import CORE, coroutine_with_priority, EsphomeError -from esphome.helpers import mkdir_p, write_file -import esphome.platformio_api as api +from esphome.helpers import mkdir_p, write_file, copy_file_if_changed from .const import KEY_BOARD, KEY_PIO_FILES, KEY_RP2040, rp2040_ns @@ -193,25 +192,20 @@ def generate_pio_files() -> bool: pio_path = CORE.relative_build_path(f"src/pio/{key}.pio") mkdir_p(os.path.dirname(pio_path)) write_file(pio_path, data) - _LOGGER.info("Assembling PIO assembly code") - retval = api.run_platformio_cli( - "pkg", - "exec", - "--package", - "earlephilhower/tool-pioasm-rp2040-earlephilhower", - "--", - "pioasm", - pio_path, - pio_path + ".h", - ) includes.append(f"pio/{key}.pio.h") - if retval != 0: - raise EsphomeError("PIO assembly failed") write_file( CORE.relative_build_path("src/pio_includes.h"), "#pragma once\n" + "\n".join([f'#include "{include}"' for include in includes]), ) + + dir = os.path.dirname(__file__) + build_pio_file = os.path.join(dir, "build_pio.py.script") + copy_file_if_changed( + build_pio_file, + CORE.relative_build_path("build_pio.py"), + ) + return True diff --git a/esphome/components/rp2040/build_pio.py.script b/esphome/components/rp2040/build_pio.py.script new file mode 100644 index 0000000000..c3e0767ed6 --- /dev/null +++ b/esphome/components/rp2040/build_pio.py.script @@ -0,0 +1,47 @@ +""" +Custom pioasm compiler script for platformio. +(c) 2022 by P.Z. + +Sourced 2023/06/23 from https://gist.github.com/hexeguitar/f4533bc697c956ac1245b6843e2ef438 + +Modified by jesserockz 2023/06/23 +""" + +from os.path import join +import glob +import sys + +import subprocess + +# pylint: disable=E0602 +Import("env") # noqa + +from SCons.Script import ARGUMENTS + + +platform = env.PioPlatform() +PROJ_SRC = env["PROJECT_SRC_DIR"] +PIO_FILES = glob.glob(join(PROJ_SRC, "**", "*.pio"), recursive=True) + +verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0"))) + + +if PIO_FILES: + if verbose: + print("==============================================") + print("PIO ASSEMBLY COMPILER") + try: + PIOASM_DIR = platform.get_package_dir("tool-pioasm-rp2040-earlephilhower") + except: + print("tool-pioasm-rp2040-earlephilhower not supported on your system!") + sys.exit() + + PIOASM_EXE = join(PIOASM_DIR, "pioasm") + if verbose: + print("PIO files found:") + for filename in PIO_FILES: + if verbose: + print(f" {filename}") + subprocess.run([PIOASM_EXE, "-o", "c-sdk", filename, f"{filename}.h"]) + if verbose: + print("==============================================") diff --git a/esphome/components/rp2040_pio/__init__.py b/esphome/components/rp2040_pio/__init__.py new file mode 100644 index 0000000000..af884d5ac2 --- /dev/null +++ b/esphome/components/rp2040_pio/__init__.py @@ -0,0 +1,40 @@ +import platform + +import esphome.codegen as cg + + +DEPENDENCIES = ["rp2040"] + + +PIOASM_REPO_VERSION = "1.5.0-b" +PIOASM_REPO_BASE = f"https://github.com/earlephilhower/pico-quick-toolchain/releases/download/{PIOASM_REPO_VERSION}" +PIOASM_VERSION = "pioasm-2e6142b.230216" +PIOASM_DOWNLOADS = { + "linux": { + "aarch64": f"aarch64-linux-gnu.{PIOASM_VERSION}.tar.gz", + "armv7l": f"arm-linux-gnueabihf.{PIOASM_VERSION}.tar.gz", + "x86_64": f"x86_64-linux-gnu.{PIOASM_VERSION}.tar.gz", + }, + "windows": { + "amd64": f"x86_64-w64-mingw32.{PIOASM_VERSION}.zip", + }, + "darwin": { + "x86_64": f"x86_64-apple-darwin14.{PIOASM_VERSION}.tar.gz", + "arm64": f"x86_64-apple-darwin14.{PIOASM_VERSION}.tar.gz", + }, +} + + +async def to_code(config): + # cg.add_platformio_option( + # "platform_packages", + # [ + # "earlephilhower/tool-pioasm-rp2040-earlephilhower", + # ], + # ) + file = PIOASM_DOWNLOADS[platform.system().lower()][platform.machine().lower()] + cg.add_platformio_option( + "platform_packages", + [f"earlephilhower/tool-pioasm-rp2040-earlephilhower@{PIOASM_REPO_BASE}/{file}"], + ) + cg.add_platformio_option("extra_scripts", ["pre:build_pio.py"]) diff --git a/esphome/components/rp2040_pio_led_strip/light.py b/esphome/components/rp2040_pio_led_strip/light.py index a2ba72318f..6c51b57e97 100644 --- a/esphome/components/rp2040_pio_led_strip/light.py +++ b/esphome/components/rp2040_pio_led_strip/light.py @@ -127,6 +127,7 @@ def time_to_cycles(time_us): CONF_PIO = "pio" +AUTO_LOAD = ["rp2040_pio"] CODEOWNERS = ["@Papa-DMan"] DEPENDENCIES = ["rp2040"] @@ -265,9 +266,3 @@ async def to_code(config): time_to_cycles(config[CONF_BIT1_LOW]), ), ) - cg.add_platformio_option( - "platform_packages", - [ - "earlephilhower/tool-pioasm-rp2040-earlephilhower", - ], - ) From 8a1c49a4ae629a6411a8442c26528db66607e38d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Sun, 25 Jun 2023 00:56:29 +0200 Subject: [PATCH 039/106] display: move `Image`, `Font` and `Animation` code into components (#4967) * display: move `Font` to `components/font` * display: move `Animation` to `components/animation` * display: move `Image` to `components/image` --- esphome/components/animation/__init__.py | 13 ++++++++----- .../{display => animation}/animation.cpp | 7 ++++--- .../components/{display => animation}/animation.h | 10 +++++----- esphome/components/font/__init__.py | 9 +++++---- esphome/components/{display => font}/font.cpp | 12 +++++++----- esphome/components/{display => font}/font.h | 14 +++++++------- esphome/components/graph/graph.cpp | 1 - esphome/components/graph/graph.h | 10 +++++----- esphome/components/image/__init__.py | 8 +++++--- esphome/components/{display => image}/image.cpp | 6 +++--- esphome/components/{display => image}/image.h | 12 ++++++------ 11 files changed, 55 insertions(+), 47 deletions(-) rename esphome/components/{display => animation}/animation.cpp (94%) rename esphome/components/{display => animation}/animation.h (89%) rename esphome/components/{display => font}/font.cpp (91%) rename esphome/components/{display => font}/font.h (79%) rename esphome/components/{display => image}/image.cpp (97%) rename esphome/components/{display => image}/image.h (79%) diff --git a/esphome/components/animation/__init__.py b/esphome/components/animation/__init__.py index 8a39ec5a87..82e724fa00 100644 --- a/esphome/components/animation/__init__.py +++ b/esphome/components/animation/__init__.py @@ -1,7 +1,7 @@ import logging from esphome import automation, core -from esphome.components import display, font +from esphome.components import font import esphome.components.image as espImage from esphome.components.image import CONF_USE_TRANSPARENCY import esphome.config_validation as cv @@ -18,6 +18,7 @@ from esphome.core import CORE, HexInt _LOGGER = logging.getLogger(__name__) +AUTO_LOAD = ["image"] CODEOWNERS = ["@syndlex"] DEPENDENCIES = ["display"] MULTI_CONF = True @@ -27,16 +28,18 @@ CONF_START_FRAME = "start_frame" CONF_END_FRAME = "end_frame" CONF_FRAME = "frame" -Animation_ = display.display_ns.class_("Animation", espImage.Image_) +animation_ns = cg.esphome_ns.namespace("animation") + +Animation_ = animation_ns.class_("Animation", espImage.Image_) # Actions -NextFrameAction = display.display_ns.class_( +NextFrameAction = animation_ns.class_( "AnimationNextFrameAction", automation.Action, cg.Parented.template(Animation_) ) -PrevFrameAction = display.display_ns.class_( +PrevFrameAction = animation_ns.class_( "AnimationPrevFrameAction", automation.Action, cg.Parented.template(Animation_) ) -SetFrameAction = display.display_ns.class_( +SetFrameAction = animation_ns.class_( "AnimationSetFrameAction", automation.Action, cg.Parented.template(Animation_) ) diff --git a/esphome/components/display/animation.cpp b/esphome/components/animation/animation.cpp similarity index 94% rename from esphome/components/display/animation.cpp rename to esphome/components/animation/animation.cpp index d68084b68d..7e0efa97e0 100644 --- a/esphome/components/display/animation.cpp +++ b/esphome/components/animation/animation.cpp @@ -3,9 +3,10 @@ #include "esphome/core/hal.h" namespace esphome { -namespace display { +namespace animation { -Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type) +Animation::Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, + image::ImageType type) : Image(data_start, width, height, type), animation_data_start_(data_start), current_frame_(0), @@ -65,5 +66,5 @@ void Animation::update_data_start_() { this->data_start_ = this->animation_data_start_ + image_size * this->current_frame_; } -} // namespace display +} // namespace animation } // namespace esphome diff --git a/esphome/components/display/animation.h b/esphome/components/animation/animation.h similarity index 89% rename from esphome/components/display/animation.h rename to esphome/components/animation/animation.h index 3371cd9e71..272c5153d1 100644 --- a/esphome/components/display/animation.h +++ b/esphome/components/animation/animation.h @@ -1,14 +1,14 @@ #pragma once -#include "image.h" +#include "esphome/components/image/image.h" #include "esphome/core/automation.h" namespace esphome { -namespace display { +namespace animation { -class Animation : public Image { +class Animation : public image::Image { public: - Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, ImageType type); + Animation(const uint8_t *data_start, int width, int height, uint32_t animation_frame_count, image::ImageType type); uint32_t get_animation_frame_count() const; int get_current_frame() const; @@ -63,5 +63,5 @@ template class AnimationSetFrameAction : public Action { Animation *parent_; }; -} // namespace display +} // namespace animation } // namespace esphome diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index aa165ebaa5..7a314bb032 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -7,7 +7,6 @@ import re import requests from esphome import core -from esphome.components import display import esphome.config_validation as cv import esphome.codegen as cg from esphome.helpers import copy_file_if_changed @@ -29,9 +28,11 @@ DOMAIN = "font" DEPENDENCIES = ["display"] MULTI_CONF = True -Font = display.display_ns.class_("Font") -Glyph = display.display_ns.class_("Glyph") -GlyphData = display.display_ns.struct("GlyphData") +font_ns = cg.esphome_ns.namespace("font") + +Font = font_ns.class_("Font") +Glyph = font_ns.class_("Glyph") +GlyphData = font_ns.struct("GlyphData") def validate_glyphs(value): diff --git a/esphome/components/display/font.cpp b/esphome/components/font/font.cpp similarity index 91% rename from esphome/components/display/font.cpp rename to esphome/components/font/font.cpp index 0a5881b48b..fcb2bb1750 100644 --- a/esphome/components/display/font.cpp +++ b/esphome/components/font/font.cpp @@ -2,13 +2,15 @@ #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/color.h" +#include "esphome/components/display/display_buffer.h" namespace esphome { -namespace display { +namespace font { -static const char *const TAG = "display"; +static const char *const TAG = "font"; -void Glyph::draw(int x_at, int y_start, DisplayBuffer *display, Color color) const { +void Glyph::draw(int x_at, int y_start, display::DisplayBuffer *display, Color color) const { int scan_x1, scan_y1, scan_width, scan_height; this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); @@ -116,7 +118,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in *x_offset = min_x; *width = x - min_x; } -void Font::print(int x_start, int y_start, DisplayBuffer *display, Color color, const char *text) { +void Font::print(int x_start, int y_start, display::DisplayBuffer *display, Color color, const char *text) { int i = 0; int x_at = x_start; while (text[i] != '\0') { @@ -143,5 +145,5 @@ void Font::print(int x_start, int y_start, DisplayBuffer *display, Color color, } } -} // namespace display +} // namespace font } // namespace esphome diff --git a/esphome/components/display/font.h b/esphome/components/font/font.h similarity index 79% rename from esphome/components/display/font.h rename to esphome/components/font/font.h index 5ba6685a1c..d88ebd9be6 100644 --- a/esphome/components/display/font.h +++ b/esphome/components/font/font.h @@ -1,12 +1,12 @@ #pragma once #include "esphome/core/datatypes.h" -#include "display_buffer.h" +#include "esphome/core/color.h" +#include "esphome/components/display/display_buffer.h" namespace esphome { -namespace display { +namespace font { -class DisplayBuffer; class Font; struct GlyphData { @@ -22,7 +22,7 @@ class Glyph { public: Glyph(const GlyphData *data) : glyph_data_(data) {} - void draw(int x, int y, DisplayBuffer *display, Color color) const; + void draw(int x, int y, display::DisplayBuffer *display, Color color) const; const char *get_char() const; @@ -38,7 +38,7 @@ class Glyph { const GlyphData *glyph_data_; }; -class Font : public BaseFont { +class Font : public display::BaseFont { public: /** Construct the font with the given glyphs. * @@ -50,7 +50,7 @@ class Font : public BaseFont { int match_next_glyph(const char *str, int *match_length); - void print(int x_start, int y_start, DisplayBuffer *display, Color color, const char *text) override; + void print(int x_start, int y_start, display::DisplayBuffer *display, Color color, const char *text) override; void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; inline int get_baseline() { return this->baseline_; } inline int get_height() { return this->height_; } @@ -63,5 +63,5 @@ class Font : public BaseFont { int height_; }; -} // namespace display +} // namespace font } // namespace esphome diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index 88850f4b92..c229f17dd8 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -1,6 +1,5 @@ #include "graph.h" #include "esphome/components/display/display_buffer.h" -#include "esphome/components/display/font.h" #include "esphome/core/color.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 69c1167f54..87c21fd7d1 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -11,7 +11,7 @@ namespace esphome { // forward declare DisplayBuffer namespace display { class DisplayBuffer; -class Font; +class BaseFont; } // namespace display namespace graph { @@ -45,8 +45,8 @@ enum ValuePositionType { class GraphLegend { public: void init(Graph *g); - void set_name_font(display::Font *font) { this->font_label_ = font; } - void set_value_font(display::Font *font) { this->font_value_ = font; } + void set_name_font(display::BaseFont *font) { this->font_label_ = font; } + void set_value_font(display::BaseFont *font) { this->font_value_ = font; } void set_width(uint32_t width) { this->width_ = width; } void set_height(uint32_t height) { this->height_ = height; } void set_border(bool val) { this->border_ = val; } @@ -63,8 +63,8 @@ class GraphLegend { ValuePositionType values_{VALUE_POSITION_TYPE_AUTO}; bool units_{true}; DirectionType direction_{DIRECTION_TYPE_AUTO}; - display::Font *font_label_{nullptr}; - display::Font *font_value_{nullptr}; + display::BaseFont *font_label_{nullptr}; + display::BaseFont *font_value_{nullptr}; // Calculated values Graph *parent_{nullptr}; // (x0) (xs,ys) (xs,ys) diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index e7cf492c7b..392efb18a2 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -6,7 +6,7 @@ import re import requests from esphome import core -from esphome.components import display, font +from esphome.components import font import esphome.config_validation as cv import esphome.codegen as cg from esphome.const import ( @@ -28,7 +28,9 @@ DOMAIN = "image" DEPENDENCIES = ["display"] MULTI_CONF = True -ImageType = display.display_ns.enum("ImageType") +image_ns = cg.esphome_ns.namespace("image") + +ImageType = image_ns.enum("ImageType") IMAGE_TYPE = { "BINARY": ImageType.IMAGE_TYPE_BINARY, "TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_BINARY, @@ -46,7 +48,7 @@ MDI_DOWNLOAD_TIMEOUT = 30 # seconds SOURCE_LOCAL = "local" SOURCE_MDI = "mdi" -Image_ = display.display_ns.class_("Image") +Image_ = image_ns.class_("Image") def _compute_local_icon_path(value) -> Path: diff --git a/esphome/components/display/image.cpp b/esphome/components/image/image.cpp similarity index 97% rename from esphome/components/display/image.cpp rename to esphome/components/image/image.cpp index 33c26ef127..66a085ebe2 100644 --- a/esphome/components/display/image.cpp +++ b/esphome/components/image/image.cpp @@ -3,9 +3,9 @@ #include "esphome/core/hal.h" namespace esphome { -namespace display { +namespace image { -void Image::draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) { +void Image::draw(int x, int y, display::DisplayBuffer *display, Color color_on, Color color_off) { switch (type_) { case IMAGE_TYPE_BINARY: { for (int img_x = 0; img_x < width_; img_x++) { @@ -130,5 +130,5 @@ ImageType Image::get_type() const { return this->type_; } Image::Image(const uint8_t *data_start, int width, int height, ImageType type) : width_(width), height_(height), type_(type), data_start_(data_start) {} -} // namespace display +} // namespace image } // namespace esphome diff --git a/esphome/components/display/image.h b/esphome/components/image/image.h similarity index 79% rename from esphome/components/display/image.h rename to esphome/components/image/image.h index b16828a5be..b0853d360d 100644 --- a/esphome/components/display/image.h +++ b/esphome/components/image/image.h @@ -1,9 +1,9 @@ #pragma once #include "esphome/core/color.h" -#include "display_buffer.h" +#include "esphome/components/display/display_buffer.h" namespace esphome { -namespace display { +namespace image { enum ImageType { IMAGE_TYPE_BINARY = 0, @@ -31,15 +31,15 @@ inline int image_type_to_bpp(ImageType type) { inline int image_type_to_width_stride(int width, ImageType type) { return (width * image_type_to_bpp(type) + 7u) / 8u; } -class Image : public BaseImage { +class Image : public display::BaseImage { public: Image(const uint8_t *data_start, int width, int height, ImageType type); - Color get_pixel(int x, int y, Color color_on = COLOR_ON, Color color_off = COLOR_OFF) const; + Color get_pixel(int x, int y, Color color_on = display::COLOR_ON, Color color_off = display::COLOR_OFF) const; int get_width() const override; int get_height() const override; ImageType get_type() const; - void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) override; + void draw(int x, int y, display::DisplayBuffer *display, Color color_on, Color color_off) override; void set_transparency(bool transparent) { transparent_ = transparent; } bool has_transparency() const { return transparent_; } @@ -58,5 +58,5 @@ class Image : public BaseImage { bool transparent_; }; -} // namespace display +} // namespace image } // namespace esphome From 2a2d20a7fc10ea7d27382a7372225b72e0139b34 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Sun, 25 Jun 2023 18:38:36 -0300 Subject: [PATCH 040/106] support empty schemas and one platform components (#4999) --- script/build_language_schema.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/script/build_language_schema.py b/script/build_language_schema.py index dd8eccde93..c6fcf5eb64 100644 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -461,8 +461,10 @@ def merge(source, destination): def is_platform_schema(schema_name): # added mostly because of schema_name == "microphone.MICROPHONE_SCHEMA" + # and "alarm_control_panel" # which is shrunk because there is only one component of the schema (i2s_audio) - return schema_name == "microphone.MICROPHONE_SCHEMA" + component = schema_name.split(".")[0] + return component in components and components[component].is_platform_component def shrink(): @@ -530,6 +532,10 @@ def shrink(): elif not key_s: for target in paths: target_s = get_arr_path_schema(target) + if S_SCHEMA not in target_s: + # an empty schema like speaker.SPEAKER_SCHEMA + target_s[S_EXTENDS].remove(x) + continue assert target_s[S_SCHEMA][S_EXTENDS] == [x] target_s.pop(S_SCHEMA) target_s.pop(S_TYPE) # undefined From ef84937fd62f1f81b535cd8af7a8690f87d312a9 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Mon, 26 Jun 2023 10:27:03 +1200 Subject: [PATCH 041/106] Update webserver to 56d73b5 (#5007) --- esphome/components/web_server/server_index.h | 1179 +++++++++--------- 1 file changed, 590 insertions(+), 589 deletions(-) diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h index 4e6e136f8c..2dbb839c5e 100644 --- a/esphome/components/web_server/server_index.h +++ b/esphome/components/web_server/server_index.h @@ -6,596 +6,597 @@ namespace esphome { namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3, - 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x28, 0x58, 0x5d, 0x05, 0x5c, 0x81, 0x10, 0x49, 0x95, 0xaa, 0xca, 0xa0, 0x40, 0x5e, - 0xd5, 0x62, 0x57, 0xd9, 0xb5, 0xb9, 0xa4, 0xb2, 0xaf, 0x2d, 0xeb, 0x4a, 0x10, 0x99, 0x14, 0xe1, 0x02, 0x01, 0x1a, - 0x48, 0x6a, 0x31, 0x85, 0x3e, 0xfd, 0xd4, 0x4f, 0x7d, 0xce, 0x6c, 0xfd, 0xd0, 0x0f, 0xd3, 0xa7, 0xfb, 0x61, 0x3e, - 0x62, 0x9e, 0xfb, 0x53, 0xee, 0x0f, 0x4c, 0x7f, 0xc2, 0x44, 0x44, 0x2e, 0x48, 0x80, 0xa4, 0x24, 0xbb, 0x7d, 0xe7, - 0x78, 0x11, 0x90, 0x6b, 0x44, 0x64, 0x64, 0x6c, 0x19, 0x09, 0xee, 0xde, 0x1b, 0x65, 0x43, 0x7e, 0x35, 0x63, 0xd6, - 0x84, 0x4f, 0x93, 0xfe, 0xae, 0xfc, 0x3f, 0x8b, 0x46, 0xfd, 0xdd, 0x24, 0x4e, 0x3f, 0x59, 0x39, 0x4b, 0xc2, 0x78, - 0x98, 0xa5, 0xd6, 0x24, 0x67, 0xe3, 0x70, 0x14, 0xf1, 0x28, 0x88, 0xa7, 0xd1, 0x19, 0xb3, 0xb6, 0xfa, 0xbb, 0x53, - 0xc6, 0x23, 0x6b, 0x38, 0x89, 0xf2, 0x82, 0xf1, 0xf0, 0xe3, 0xc1, 0x17, 0xad, 0x27, 0xfd, 0xdd, 0x62, 0x98, 0xc7, - 0x33, 0x6e, 0xe1, 0x90, 0xe1, 0x34, 0x1b, 0xcd, 0x13, 0xd6, 0x3f, 0x8f, 0x72, 0xeb, 0x05, 0x0b, 0xdf, 0x9d, 0xfe, - 0xc4, 0x86, 0xdc, 0x1f, 0xb1, 0x71, 0x9c, 0xb2, 0xf7, 0x79, 0x36, 0x63, 0x39, 0xbf, 0xf2, 0xf6, 0x57, 0x57, 0xc4, - 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x33, 0xc6, 0xdf, 0x5d, 0xa4, 0xaa, 0xcf, 0x73, 0x26, 0x26, 0xc9, 0xf2, 0xc2, 0x8b, - 0xd7, 0xb4, 0xd9, 0xbf, 0x9a, 0x9e, 0x66, 0x49, 0xe1, 0x7d, 0xd2, 0xf5, 0xb3, 0x3c, 0xe3, 0x19, 0x82, 0xe5, 0x4f, - 0xa2, 0xc2, 0x68, 0xe9, 0xbd, 0x5b, 0xd1, 0x64, 0x26, 0x2b, 0x5f, 0x15, 0x2f, 0xd2, 0xf9, 0x94, 0xe5, 0xd1, 0x69, - 0xc2, 0xbc, 0x9c, 0x85, 0x0e, 0xf3, 0xb8, 0x17, 0xbb, 0x61, 0x9f, 0x5b, 0x71, 0x6a, 0xb1, 0xc1, 0x0b, 0x46, 0x25, - 0x0b, 0xa6, 0x5b, 0x05, 0xf7, 0xda, 0x1e, 0x90, 0x6b, 0x1c, 0x9f, 0xcd, 0xf5, 0xfb, 0x45, 0x1e, 0x73, 0xf5, 0x7c, - 0x1e, 0x25, 0x73, 0x16, 0xc4, 0xa5, 0x1b, 0xb0, 0x43, 0x7e, 0x14, 0xc6, 0xde, 0x27, 0x1a, 0x14, 0x86, 0x5c, 0x8c, - 0xb3, 0xdc, 0x41, 0x5a, 0xc5, 0x38, 0x36, 0xbf, 0xbe, 0x76, 0x78, 0xb8, 0x28, 0x5d, 0xf7, 0x13, 0xf3, 0x87, 0x51, - 0x92, 0x38, 0x38, 0xf1, 0xfd, 0xfb, 0x39, 0xce, 0x18, 0x7b, 0xfc, 0x30, 0x3e, 0x72, 0x7b, 0xf1, 0xd8, 0x89, 0x99, - 0x5b, 0xf5, 0xcb, 0xc6, 0x56, 0xcc, 0x1c, 0xee, 0xba, 0xef, 0xd6, 0xf7, 0xc9, 0x19, 0x9f, 0xe7, 0x00, 0x7b, 0xe9, - 0xbd, 0x53, 0x33, 0xef, 0x63, 0xfd, 0x33, 0xea, 0xd8, 0x03, 0xd8, 0x0b, 0x6e, 0x7d, 0x11, 0x5e, 0xc4, 0xe9, 0x28, - 0xbb, 0xf0, 0xf7, 0x27, 0x11, 0xfc, 0xf9, 0x90, 0x65, 0xfc, 0xfe, 0x7d, 0xe7, 0x3c, 0x8b, 0x47, 0x56, 0x3b, 0x0c, - 0xcd, 0xca, 0xab, 0x67, 0xfb, 0xfb, 0xd7, 0xd7, 0x8d, 0x02, 0x3f, 0x8d, 0x78, 0x7c, 0xce, 0x44, 0x67, 0x00, 0xc0, - 0x86, 0xbf, 0x33, 0xce, 0x46, 0xfb, 0xfc, 0x2a, 0x81, 0x52, 0xc6, 0x78, 0x61, 0x03, 0x8e, 0xcf, 0xb3, 0x21, 0x90, - 0x2d, 0x35, 0x08, 0x0f, 0x4d, 0x73, 0x36, 0x4b, 0xa2, 0x21, 0xc3, 0x7a, 0x18, 0xa9, 0xea, 0x51, 0x35, 0xf2, 0xbe, - 0x0b, 0xc5, 0xf2, 0x3a, 0xae, 0x97, 0xb1, 0x30, 0x65, 0x17, 0xd6, 0x9b, 0x68, 0xd6, 0x1b, 0x26, 0x51, 0x51, 0x58, - 0x29, 0x5b, 0x10, 0x0a, 0xf9, 0x7c, 0x08, 0x0c, 0x42, 0x08, 0x2e, 0x80, 0x4c, 0x7c, 0x12, 0x17, 0xfe, 0xf1, 0xc6, - 0xb0, 0x28, 0x3e, 0xb0, 0x62, 0x9e, 0xf0, 0x8d, 0x10, 0xd6, 0x82, 0xdf, 0x0b, 0xc3, 0xef, 0x5c, 0x3e, 0xc9, 0xb3, - 0x0b, 0xeb, 0x45, 0x9e, 0x43, 0x73, 0x1b, 0xa6, 0x14, 0x0d, 0xac, 0x18, 0xc6, 0xca, 0xb8, 0xa5, 0x07, 0xc3, 0x05, - 0xf4, 0xad, 0x8f, 0x05, 0xb3, 0x4e, 0xe6, 0x69, 0x11, 0x8d, 0x19, 0x34, 0x3d, 0xb1, 0xb2, 0xdc, 0x3a, 0x81, 0x41, - 0x4f, 0x60, 0xc9, 0x0a, 0x0e, 0xbb, 0xc6, 0xb7, 0xdd, 0x1e, 0xcd, 0x05, 0x85, 0x07, 0xec, 0x92, 0x87, 0xac, 0x04, - 0xc6, 0xb4, 0x0a, 0x8d, 0x86, 0xe3, 0x2e, 0x12, 0x28, 0x60, 0x61, 0xc6, 0x90, 0x65, 0x1d, 0xb3, 0xb1, 0x5e, 0x9c, - 0x2f, 0xee, 0xdf, 0xd7, 0xb4, 0x06, 0x9a, 0x38, 0xd0, 0xb6, 0x68, 0xb4, 0xf5, 0x04, 0xe2, 0x35, 0x12, 0xb9, 0x1e, - 0xf3, 0x25, 0xf9, 0xf6, 0xaf, 0xd2, 0x61, 0x7d, 0x6c, 0xa8, 0x2c, 0x79, 0xb6, 0xcf, 0xf3, 0x38, 0x3d, 0x03, 0x20, - 0xe4, 0x4c, 0x66, 0x93, 0xb2, 0x14, 0x8b, 0xff, 0x9e, 0x85, 0x2c, 0xec, 0xe3, 0xe8, 0x29, 0x73, 0xec, 0x82, 0x7a, - 0xd8, 0x61, 0x88, 0xa4, 0x07, 0x06, 0x63, 0x03, 0x16, 0xb0, 0x4d, 0xdb, 0xf6, 0xbe, 0x73, 0xbd, 0x2b, 0xe4, 0x20, - 0xdf, 0xf7, 0x89, 0x7d, 0x45, 0xe7, 0x38, 0xec, 0x20, 0xd0, 0x7e, 0xc2, 0xd2, 0x33, 0x3e, 0x19, 0xb0, 0xc3, 0xf6, - 0x51, 0xc0, 0x01, 0xaa, 0xd1, 0x7c, 0xc8, 0x1c, 0xe4, 0x47, 0x2f, 0xc7, 0xed, 0xb3, 0xe9, 0xc0, 0x14, 0xb8, 0x30, - 0xf7, 0x08, 0xc7, 0xda, 0xd2, 0xb8, 0x8a, 0x45, 0x15, 0x60, 0xc8, 0xe7, 0x36, 0xec, 0xb0, 0x53, 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, 0x87, 0xf9, 0x66, 0xe7, 0xc8, 0x43, - 0x28, 0x35, 0xf1, 0x25, 0xe2, 0x31, 0x20, 0x58, 0x7a, 0x1f, 0x99, 0xde, 0x9e, 0x5f, 0x0c, 0x98, 0xbf, 0xcc, 0xc7, - 0x21, 0xf7, 0xa7, 0xd1, 0x0c, 0xb1, 0x61, 0xc4, 0x03, 0x51, 0x3a, 0x44, 0xe8, 0x6a, 0xeb, 0x82, 0x14, 0xf3, 0x2b, - 0x16, 0x70, 0x81, 0x20, 0xb0, 0x67, 0x5f, 0x44, 0xc3, 0x09, 0x6c, 0xf1, 0x8a, 0x70, 0x23, 0xb5, 0x1d, 0x86, 0x39, - 0x8b, 0x38, 0x7b, 0x91, 0x30, 0x7c, 0xc3, 0x15, 0x80, 0x9e, 0xb6, 0xeb, 0xe5, 0x6a, 0xdf, 0x25, 0x31, 0x7f, 0x9b, - 0xc1, 0x3c, 0x3d, 0xc1, 0x24, 0xc0, 0xc5, 0xf9, 0xfd, 0xfb, 0x31, 0xb2, 0xc8, 0x1e, 0x87, 0xd5, 0x3a, 0x9d, 0x73, - 0x58, 0xb7, 0x14, 0x5b, 0xd8, 0x40, 0x6d, 0x2f, 0xf6, 0x39, 0x10, 0xf1, 0x59, 0x96, 0x72, 0x18, 0x0e, 0xe0, 0xd5, - 0x1c, 0xe4, 0x47, 0xb3, 0x19, 0x4b, 0x47, 0xcf, 0x26, 0x71, 0x32, 0x02, 0x6a, 0x94, 0x80, 0x6f, 0xc2, 0x42, 0xc0, - 0x13, 0x90, 0x09, 0x6e, 0xc6, 0x88, 0x96, 0x0f, 0x19, 0x99, 0x87, 0xb6, 0xdd, 0x43, 0x09, 0x24, 0xb1, 0x40, 0x19, - 0x44, 0x0b, 0xf7, 0x01, 0x44, 0x7f, 0xe1, 0xf2, 0xcd, 0x30, 0xd6, 0xcb, 0x28, 0x09, 0xfc, 0x1e, 0x25, 0x0d, 0xd0, - 0x9f, 0x81, 0x0c, 0xec, 0xa1, 0xe0, 0xfa, 0x4a, 0x4a, 0x9d, 0x88, 0x29, 0x0c, 0x81, 0x00, 0x43, 0x94, 0x20, 0x92, - 0x06, 0xef, 0xb3, 0xe4, 0x6a, 0x1c, 0x27, 0xc9, 0xfe, 0x7c, 0x36, 0xcb, 0x72, 0xee, 0x7d, 0x1d, 0x2e, 0x78, 0x56, - 0xe1, 0x4a, 0x9b, 0xbc, 0xb8, 0x88, 0x39, 0x12, 0xd4, 0x5d, 0x0c, 0x23, 0x58, 0xea, 0xa7, 0x59, 0x96, 0xb0, 0x28, - 0x05, 0x34, 0xd8, 0xc0, 0xb6, 0x83, 0x74, 0x9e, 0x24, 0xbd, 0x53, 0x18, 0xf6, 0x53, 0x8f, 0xaa, 0x85, 0xc4, 0x0f, - 0xe8, 0x79, 0x2f, 0xcf, 0xa3, 0x2b, 0x68, 0x88, 0x6d, 0x80, 0x17, 0x61, 0xb5, 0xbe, 0xda, 0x7f, 0xf7, 0xd6, 0x17, - 0x8c, 0x1f, 0x8f, 0xaf, 0x00, 0xd0, 0xb2, 0x92, 0x9a, 0xe3, 0x3c, 0x9b, 0x36, 0xa6, 0x46, 0x3a, 0xc4, 0x21, 0xeb, - 0xad, 0x01, 0x21, 0xa6, 0x91, 0x61, 0x95, 0x98, 0x09, 0xc1, 0x5b, 0xe2, 0x67, 0x59, 0x89, 0x7b, 0x60, 0x80, 0x0f, - 0x81, 0x28, 0x86, 0x29, 0x6f, 0x86, 0x96, 0xe7, 0x57, 0x8b, 0x38, 0x24, 0x38, 0x67, 0xa8, 0x7f, 0x11, 0xc6, 0x61, - 0x04, 0xb3, 0x2f, 0xc4, 0x80, 0xa5, 0x82, 0x38, 0x2e, 0x4b, 0x6f, 0xa2, 0x99, 0x18, 0x25, 0x1e, 0x0a, 0x14, 0x0e, - 0xdb, 0xe8, 0xfa, 0x9a, 0xc1, 0x8b, 0xeb, 0x7d, 0x13, 0x2e, 0x22, 0x85, 0x0f, 0x6a, 0x28, 0xdc, 0x5f, 0x81, 0x90, - 0x13, 0xa8, 0xc9, 0xce, 0x41, 0x0f, 0x02, 0x9c, 0x5f, 0x83, 0xfa, 0x1b, 0x27, 0x08, 0xc5, 0xbd, 0x8e, 0x07, 0x1a, - 0xf4, 0xd9, 0x24, 0x4a, 0xcf, 0xd8, 0x28, 0x98, 0xb0, 0x52, 0x4a, 0xde, 0x3d, 0x0b, 0xd6, 0x18, 0xd8, 0xa9, 0xb0, - 0x5e, 0x1e, 0xbc, 0x79, 0x2d, 0x57, 0xae, 0x26, 0x8c, 0x61, 0x91, 0xe6, 0xa0, 0x56, 0x41, 0x6c, 0x4b, 0x71, 0xfc, - 0x82, 0x2b, 0xe9, 0x2d, 0x4a, 0xe2, 0xe2, 0xe3, 0x0c, 0x4c, 0x0c, 0xf6, 0x1e, 0x86, 0x81, 0xe9, 0x43, 0x98, 0x8a, - 0xca, 0x61, 0x3e, 0x51, 0x31, 0xd2, 0x45, 0xd0, 0x59, 0x60, 0x2a, 0x5e, 0x33, 0xc7, 0x2d, 0x81, 0x55, 0x79, 0x3c, - 0xb4, 0xa2, 0xd1, 0xe8, 0x55, 0x1a, 0xf3, 0x38, 0x4a, 0xe2, 0x5f, 0x88, 0x92, 0x0b, 0xe4, 0x31, 0xde, 0x93, 0x8b, - 0x00, 0xb8, 0x53, 0x8f, 0xc4, 0x55, 0x42, 0xf6, 0x1e, 0x11, 0x43, 0x48, 0xcb, 0x24, 0x3c, 0x3c, 0x92, 0xe0, 0x25, - 0xfe, 0x6c, 0x5e, 0x4c, 0x90, 0xb0, 0x72, 0x60, 0x14, 0xe4, 0xd9, 0x69, 0xc1, 0xf2, 0x73, 0x36, 0xd2, 0x1c, 0x50, - 0x00, 0x56, 0xd4, 0x1c, 0x8c, 0x17, 0x9a, 0xd1, 0x51, 0x3a, 0x94, 0xc1, 0x50, 0x3d, 0x53, 0xcc, 0x32, 0xc9, 0xcc, - 0xda, 0xc2, 0xd1, 0x52, 0xc0, 0x11, 0x46, 0x85, 0x94, 0x04, 0x79, 0xa8, 0x30, 0x9c, 0x80, 0x14, 0x02, 0xad, 0x60, - 0x6e, 0x73, 0xa5, 0xc9, 0x5e, 0xcc, 0x49, 0x25, 0xe4, 0xd0, 0x11, 0x36, 0x32, 0x41, 0x9a, 0xbb, 0xb0, 0xab, 0x40, - 0xca, 0x4b, 0x70, 0x85, 0x14, 0x51, 0x66, 0x0e, 0x32, 0x40, 0xf8, 0x8d, 0xd0, 0x85, 0x3e, 0xb6, 0x20, 0x36, 0xf0, - 0xf5, 0xca, 0x03, 0x61, 0x25, 0xde, 0x15, 0x22, 0xde, 0x1a, 0xb0, 0x71, 0x62, 0xe4, 0x27, 0xef, 0x1e, 0xf7, 0xd3, - 0x6c, 0x6f, 0x38, 0x64, 0x45, 0x91, 0x01, 0x6c, 0xf7, 0xa8, 0xfd, 0x3a, 0x43, 0x0b, 0x28, 0xe9, 0x6a, 0x59, 0x67, - 0x17, 0xa4, 0xc1, 0x4d, 0xb5, 0xa2, 0x74, 0x7a, 0x60, 0x1f, 0x1f, 0x83, 0xcc, 0xf6, 0x24, 0x19, 0x80, 0xea, 0xcb, - 0x86, 0x9f, 0xb0, 0x67, 0xea, 0x94, 0x59, 0x69, 0x5f, 0x3a, 0x75, 0x90, 0x3c, 0x18, 0xd6, 0x2d, 0x8d, 0x05, 0x5d, - 0x39, 0x34, 0xae, 0x86, 0x54, 0x90, 0x8b, 0x33, 0x52, 0xd9, 0xc6, 0x32, 0x82, 0xd5, 0x56, 0x7a, 0x44, 0x7a, 0x85, - 0x4d, 0x41, 0x80, 0x1e, 0xb2, 0xa3, 0x9e, 0xac, 0x0f, 0x73, 0x41, 0xb9, 0x9c, 0xfd, 0x3c, 0x67, 0x05, 0x17, 0xac, - 0x0b, 0xe3, 0x82, 0xb9, 0x0a, 0x22, 0xb6, 0x69, 0x1d, 0xd6, 0x6c, 0xc7, 0x55, 0xb0, 0xbd, 0x9b, 0xa1, 0x1e, 0x2b, - 0x90, 0x93, 0x6f, 0x66, 0x27, 0x84, 0x95, 0xb9, 0xd7, 0xd7, 0xdf, 0xa8, 0x41, 0xaa, 0xa5, 0xd4, 0x36, 0x50, 0x63, - 0x4d, 0x6c, 0xd5, 0x64, 0x64, 0xbb, 0x52, 0xa1, 0xde, 0xeb, 0xf4, 0x6a, 0x7c, 0x00, 0x7b, 0xae, 0xad, 0x59, 0xba, - 0x32, 0xb6, 0xdf, 0x2b, 0x9a, 0xbe, 0x13, 0x23, 0x93, 0x35, 0xca, 0x6e, 0xe7, 0x1e, 0xb5, 0xe3, 0xa1, 0xed, 0x52, - 0x5d, 0x25, 0x18, 0xe6, 0x75, 0xc1, 0xd0, 0x84, 0x7a, 0xa6, 0xbb, 0xd8, 0x9a, 0xa9, 0x58, 0xa8, 0xd6, 0x5a, 0x39, - 0x10, 0x3c, 0x3c, 0x04, 0xe3, 0x64, 0xa5, 0x7f, 0xf0, 0x36, 0x9a, 0x32, 0xa4, 0xa8, 0xb7, 0xae, 0x81, 0x74, 0x20, - 0xa0, 0xc9, 0x51, 0x53, 0xbd, 0x71, 0x57, 0x58, 0x4d, 0xf5, 0xfd, 0x15, 0x83, 0x15, 0x01, 0xf6, 0x75, 0xb9, 0x62, - 0x89, 0x48, 0x6f, 0x0a, 0x2e, 0xd1, 0xf4, 0x11, 0x65, 0x62, 0x4d, 0x48, 0xc1, 0x03, 0xf2, 0xb0, 0xfc, 0x8d, 0x85, - 0x93, 0xad, 0x98, 0xc2, 0x91, 0xa3, 0x4c, 0x01, 0x3a, 0x93, 0x12, 0x00, 0x71, 0x49, 0x7f, 0x6b, 0x1b, 0x0b, 0xc9, - 0xb6, 0x8f, 0x7c, 0xe0, 0x8f, 0x93, 0x88, 0x3b, 0x9d, 0xad, 0xb6, 0x0b, 0x7c, 0x08, 0x42, 0x1c, 0x74, 0x04, 0x98, - 0xf7, 0x15, 0x2a, 0x8c, 0xbc, 0x05, 0x97, 0xfb, 0x60, 0x14, 0x4d, 0xe2, 0x31, 0x77, 0x12, 0x54, 0x22, 0x6e, 0xc9, - 0x12, 0x50, 0x32, 0x7a, 0x5f, 0x81, 0x94, 0xe0, 0x42, 0xba, 0x88, 0x6a, 0x2d, 0xd0, 0x14, 0xa4, 0x24, 0xa5, 0x48, - 0x0b, 0x2a, 0x08, 0x0c, 0xa1, 0xd2, 0x53, 0x1c, 0x05, 0xfa, 0x2d, 0x1e, 0x88, 0x41, 0x83, 0x25, 0x8b, 0x32, 0x1e, - 0xc4, 0xcb, 0x85, 0xa0, 0x86, 0x7d, 0x9e, 0xbd, 0xce, 0x2e, 0x58, 0xfe, 0x2c, 0x42, 0xd8, 0x03, 0xd1, 0xbd, 0x04, - 0x49, 0x4f, 0x02, 0x9d, 0xf5, 0x14, 0xaf, 0x9c, 0x13, 0xd2, 0xb0, 0x10, 0xd3, 0x18, 0x15, 0x21, 0x68, 0x39, 0xa2, - 0x7d, 0x8a, 0x5b, 0x8a, 0xf6, 0x1e, 0xaa, 0x12, 0xa6, 0x79, 0x6b, 0xef, 0x75, 0x9d, 0xb7, 0x60, 0x84, 0x99, 0xe2, - 0xd6, 0xfa, 0x8e, 0x75, 0x3d, 0xa9, 0x9b, 0x1d, 0xc9, 0x5b, 0x86, 0x32, 0x03, 0xfd, 0x71, 0x7d, 0x5d, 0x19, 0xe9, - 0xa0, 0x4c, 0xb5, 0x34, 0x47, 0xcb, 0x49, 0x6c, 0x09, 0xb7, 0x04, 0x65, 0x84, 0x86, 0x57, 0x9e, 0x25, 0x89, 0xa1, - 0x8b, 0xbc, 0xb8, 0xe7, 0x34, 0xd4, 0x11, 0x40, 0x31, 0xad, 0x69, 0xa4, 0x01, 0x0f, 0x74, 0x05, 0x2a, 0x25, 0xa5, - 0x8d, 0xbc, 0xaa, 0x89, 0x80, 0x38, 0x1d, 0xb1, 0x5c, 0x38, 0x68, 0x52, 0x87, 0xc2, 0x84, 0x29, 0x30, 0x34, 0x1b, - 0x81, 0x84, 0x57, 0x08, 0x80, 0x79, 0xe2, 0x4f, 0xb2, 0x82, 0xeb, 0x3a, 0x13, 0xfa, 0xf8, 0xfa, 0x3a, 0x16, 0xfe, - 0x22, 0x32, 0x40, 0xce, 0xa6, 0xd9, 0x39, 0x5b, 0x01, 0x75, 0x4f, 0x0d, 0x66, 0x82, 0x6c, 0x0c, 0x03, 0x4a, 0x14, - 0x54, 0xcb, 0x2c, 0x89, 0x87, 0x4c, 0x6b, 0xa9, 0xa9, 0x0f, 0x06, 0x1d, 0xbb, 0x04, 0x19, 0xc1, 0xdc, 0x7e, 0xbf, - 0xdf, 0xf6, 0x3a, 0x6e, 0x29, 0x08, 0xbe, 0x58, 0xa2, 0xe8, 0x0d, 0xfa, 0x51, 0x9a, 0xe0, 0xab, 0x64, 0x01, 0x77, - 0x0d, 0xa5, 0xc8, 0x85, 0x9f, 0xe4, 0x49, 0x41, 0xec, 0x7a, 0x23, 0x18, 0x94, 0x33, 0x25, 0xb8, 0xd1, 0xc4, 0x15, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xdb, 0x76, 0xe3, 0x46, 0x92, 0xe0, 0xf3, + 0x9e, 0xb3, 0x7f, 0xb0, 0x2f, 0x28, 0x58, 0x53, 0x05, 0xb4, 0x40, 0x88, 0xa4, 0x4a, 0x55, 0x65, 0x50, 0x20, 0x5b, + 0x75, 0xb1, 0xab, 0xec, 0xba, 0xb9, 0xa4, 0xb2, 0xdb, 0x96, 0xd5, 0x12, 0x44, 0x26, 0x45, 0xb8, 0x40, 0x80, 0x06, + 0x92, 0xba, 0x98, 0xc2, 0x9c, 0x79, 0x9a, 0xa7, 0x39, 0x67, 0x6f, 0xf3, 0x30, 0x0f, 0x3b, 0x67, 0xe6, 0x61, 0x3f, + 0x62, 0x9f, 0xe7, 0x53, 0xfa, 0x07, 0x76, 0x3e, 0x61, 0x23, 0x22, 0x2f, 0x48, 0x80, 0xa4, 0x24, 0x7b, 0xdc, 0x7b, + 0xdc, 0xd5, 0x02, 0xf2, 0x1a, 0x11, 0x19, 0x19, 0xb7, 0x8c, 0x04, 0x77, 0xef, 0x8d, 0xb2, 0x21, 0xbf, 0x9a, 0x31, + 0x6b, 0xc2, 0xa7, 0x49, 0x7f, 0x57, 0xfe, 0x3f, 0x8b, 0x46, 0xfd, 0xdd, 0x24, 0x4e, 0x3f, 0x59, 0x39, 0x4b, 0xc2, + 0x78, 0x98, 0xa5, 0xd6, 0x24, 0x67, 0xe3, 0x70, 0x14, 0xf1, 0x28, 0x88, 0xa7, 0xd1, 0x19, 0xb3, 0xb6, 0xfa, 0xbb, + 0x53, 0xc6, 0x23, 0x6b, 0x38, 0x89, 0xf2, 0x82, 0xf1, 0xf0, 0xe3, 0xc1, 0x17, 0xad, 0x27, 0xfd, 0xdd, 0x62, 0x98, + 0xc7, 0x33, 0x6e, 0xe1, 0x90, 0xe1, 0x34, 0x1b, 0xcd, 0x13, 0xd6, 0x3f, 0x8f, 0x72, 0xeb, 0x05, 0x0b, 0xdf, 0x9d, + 0xfe, 0xc4, 0x86, 0xdc, 0x1f, 0xb1, 0x71, 0x9c, 0xb2, 0xf7, 0x79, 0x36, 0x63, 0x39, 0xbf, 0xf2, 0xf6, 0x57, 0x57, + 0xc4, 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x33, 0xc6, 0xdf, 0x5d, 0xa4, 0xaa, 0xcf, 0x73, 0x26, 0x26, 0xc9, 0xf2, 0xc2, + 0x8b, 0xd7, 0xb4, 0xd9, 0xbf, 0x9a, 0x9e, 0x66, 0x49, 0xe1, 0x7d, 0xd2, 0xf5, 0xb3, 0x3c, 0xe3, 0x19, 0x82, 0xe5, + 0x4f, 0xa2, 0xc2, 0x68, 0xe9, 0xbd, 0x5b, 0xd1, 0x64, 0x26, 0x2b, 0x5f, 0x15, 0x2f, 0xd2, 0xf9, 0x94, 0xe5, 0xd1, + 0x69, 0xc2, 0xbc, 0x9c, 0x85, 0x0e, 0xf7, 0x98, 0x17, 0xbb, 0x61, 0x9f, 0x59, 0x71, 0x6a, 0xf1, 0xc1, 0x0b, 0x46, + 0x25, 0x0b, 0xa6, 0x5b, 0x05, 0xf7, 0xda, 0x1e, 0x90, 0x6b, 0x1c, 0x9f, 0xcd, 0xf5, 0xfb, 0x45, 0x1e, 0x73, 0xf5, + 0x7c, 0x1e, 0x25, 0x73, 0x16, 0xc4, 0xa5, 0x1b, 0xf0, 0x43, 0x76, 0x14, 0xc6, 0xde, 0x27, 0x1a, 0x14, 0x86, 0x5c, + 0x8c, 0xb3, 0xdc, 0x41, 0x5a, 0xc5, 0x38, 0x36, 0xbb, 0xbe, 0x76, 0x58, 0xb8, 0x28, 0x5d, 0xf7, 0x13, 0xf3, 0x87, + 0x51, 0x92, 0x38, 0x38, 0xf1, 0xfd, 0xfb, 0x39, 0xce, 0x18, 0x7b, 0xec, 0x30, 0x3e, 0x72, 0x7b, 0xf1, 0xd8, 0x89, + 0x99, 0x5b, 0xf5, 0xcb, 0xc6, 0x56, 0xcc, 0x1c, 0xe6, 0xba, 0xef, 0xd6, 0xf7, 0xc9, 0x19, 0x9f, 0xe7, 0x00, 0x7b, + 0xe9, 0xbd, 0x53, 0x33, 0xef, 0x63, 0xfd, 0x33, 0xea, 0xd8, 0x03, 0xd8, 0x0b, 0x6e, 0x7d, 0x11, 0x5e, 0xc4, 0xe9, + 0x28, 0xbb, 0xf0, 0xf7, 0x27, 0x11, 0xfc, 0xf9, 0x90, 0x65, 0xfc, 0xfe, 0x7d, 0xe7, 0x3c, 0x8b, 0x47, 0x56, 0x3b, + 0x0c, 0xcd, 0xca, 0xab, 0x67, 0xfb, 0xfb, 0xd7, 0xd7, 0x8d, 0x02, 0x3f, 0x8d, 0x78, 0x7c, 0xce, 0x44, 0x67, 0x00, + 0xc0, 0x86, 0xbf, 0x33, 0xce, 0x46, 0xfb, 0xfc, 0x2a, 0x81, 0x52, 0xc6, 0x78, 0x61, 0x03, 0x8e, 0xcf, 0xb3, 0x21, + 0x90, 0x2d, 0x35, 0x08, 0x0f, 0x4d, 0x73, 0x36, 0x4b, 0xa2, 0x21, 0xc3, 0x7a, 0x18, 0xa9, 0xea, 0x51, 0x35, 0xf2, + 0xbe, 0x0b, 0xc5, 0xf2, 0x3a, 0xae, 0x97, 0xb1, 0x30, 0x65, 0x17, 0xd6, 0x9b, 0x68, 0xd6, 0x1b, 0x26, 0x51, 0x51, + 0x58, 0x29, 0x5b, 0x10, 0x0a, 0xf9, 0x7c, 0x08, 0x0c, 0x42, 0x08, 0x2e, 0x80, 0x4c, 0x7c, 0x12, 0x17, 0xfe, 0xf1, + 0xc6, 0xb0, 0x28, 0x3e, 0xb0, 0x62, 0x9e, 0xf0, 0x8d, 0x10, 0xd6, 0x82, 0xdd, 0x0b, 0xc3, 0xef, 0x5c, 0x3e, 0xc9, + 0xb3, 0x0b, 0xeb, 0x45, 0x9e, 0x43, 0x73, 0x1b, 0xa6, 0x14, 0x0d, 0xac, 0x18, 0xc6, 0xca, 0xb8, 0xa5, 0x07, 0xc3, + 0x05, 0xf4, 0xad, 0x8f, 0x05, 0xb3, 0x4e, 0xe6, 0x69, 0x11, 0x8d, 0x19, 0x34, 0x3d, 0xb1, 0xb2, 0xdc, 0x3a, 0x81, + 0x41, 0x4f, 0x60, 0xc9, 0x0a, 0x0e, 0xbb, 0xc6, 0xb7, 0xdd, 0x1e, 0xcd, 0x05, 0x85, 0x07, 0xec, 0x92, 0x87, 0xbc, + 0x04, 0xc6, 0xb4, 0x0a, 0x8d, 0x86, 0xe3, 0x2e, 0x12, 0x28, 0xe0, 0x61, 0xc6, 0x90, 0x65, 0x1d, 0xb3, 0xb1, 0x5e, + 0x9c, 0x2f, 0xee, 0xdf, 0xd7, 0xb4, 0x46, 0xc2, 0x43, 0xdb, 0xa2, 0xd1, 0xd6, 0xe3, 0x84, 0x78, 0x8d, 0x44, 0xae, + 0xc7, 0x7d, 0x49, 0xbe, 0xfd, 0xab, 0x74, 0x58, 0x1f, 0x1b, 0x2a, 0x4b, 0x9e, 0xed, 0xf3, 0x3c, 0x4e, 0xcf, 0x00, + 0x08, 0xc5, 0x06, 0x46, 0x93, 0xb2, 0x14, 0x8b, 0xff, 0x9e, 0x85, 0x3c, 0xec, 0xe3, 0xe8, 0x29, 0x73, 0xec, 0x82, + 0x7a, 0xd8, 0x00, 0x08, 0x90, 0x1e, 0x18, 0x8c, 0x0f, 0x78, 0xc0, 0x37, 0x6d, 0xdb, 0xfb, 0xce, 0xf5, 0xae, 0x90, + 0x83, 0x7c, 0xdf, 0x27, 0xf6, 0x15, 0x9d, 0xe3, 0xb0, 0x83, 0x40, 0xfb, 0x09, 0x4b, 0xcf, 0xf8, 0x64, 0xc0, 0x0f, + 0xdb, 0x47, 0x01, 0x03, 0xa8, 0x46, 0xf3, 0x21, 0x73, 0x90, 0x1f, 0xbd, 0x1c, 0xb7, 0xcf, 0xa6, 0x03, 0x53, 0xe0, + 0xc2, 0xdc, 0x23, 0x1c, 0x6b, 0x4b, 0xe3, 0x2a, 0xd8, 0x14, 0x60, 0xc8, 0xe7, 0x36, 0xec, 0xb0, 0x53, 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, 0xe4, 0x87, 0xf9, 0x66, 0xe7, 0xc8, + 0x43, 0x28, 0x35, 0xf1, 0x25, 0xe2, 0x31, 0x20, 0x58, 0x7a, 0x1f, 0x99, 0xde, 0x9e, 0x5f, 0x0c, 0xb8, 0xbf, 0xcc, + 0xc7, 0x21, 0xf3, 0xa7, 0xd1, 0x0c, 0xb1, 0xe1, 0xc4, 0x03, 0x51, 0x3a, 0x44, 0xe8, 0x6a, 0xeb, 0x82, 0x14, 0xf3, + 0x2b, 0x16, 0x70, 0x81, 0x20, 0xb0, 0x67, 0x5f, 0x44, 0xc3, 0x09, 0x6c, 0xf1, 0x8a, 0x70, 0x23, 0xb5, 0x1d, 0x86, + 0x39, 0x8b, 0x38, 0x7b, 0x91, 0x30, 0x7c, 0xc3, 0x15, 0x80, 0x9e, 0xb6, 0xeb, 0xe5, 0x6a, 0xdf, 0x25, 0x31, 0x7f, + 0x9b, 0xc1, 0x3c, 0x3d, 0xc1, 0x24, 0xc0, 0xc5, 0xf9, 0xfd, 0xfb, 0x31, 0xb2, 0xc8, 0x1e, 0x87, 0xd5, 0x3a, 0x9d, + 0x73, 0x58, 0xb7, 0x14, 0x5b, 0xd8, 0x40, 0x6d, 0x2f, 0xf6, 0x39, 0x10, 0xf1, 0x59, 0x96, 0x72, 0x18, 0x0e, 0xe0, + 0xd5, 0x1c, 0xe4, 0x47, 0xb3, 0x19, 0x4b, 0x47, 0xcf, 0x26, 0x71, 0x32, 0x02, 0x6a, 0x94, 0x80, 0x6f, 0xc2, 0x42, + 0xc0, 0x13, 0x90, 0x09, 0x6e, 0xc6, 0x88, 0x96, 0x0f, 0x19, 0x99, 0x85, 0xb6, 0xdd, 0x43, 0x09, 0x24, 0xb1, 0x40, + 0x19, 0x44, 0x0b, 0xf7, 0x01, 0x44, 0x7f, 0xe1, 0xb2, 0xcd, 0x30, 0xd6, 0xcb, 0x28, 0x09, 0xfc, 0x1e, 0x25, 0x0d, + 0xd0, 0x1f, 0x08, 0xc1, 0x7b, 0x28, 0xb8, 0xbe, 0x92, 0x52, 0x27, 0x62, 0x0a, 0x43, 0x20, 0xc0, 0x10, 0x25, 0x88, + 0xa4, 0xc1, 0xfb, 0x2c, 0xb9, 0x1a, 0xc7, 0x49, 0xb2, 0x3f, 0x9f, 0xcd, 0xb2, 0x9c, 0x7b, 0x5f, 0x87, 0x0b, 0x9e, + 0x55, 0xb8, 0xd2, 0x26, 0x2f, 0x2e, 0x62, 0x8e, 0x04, 0x75, 0x17, 0xc3, 0x08, 0x96, 0xfa, 0x69, 0x96, 0x25, 0x2c, + 0x4a, 0x01, 0x0d, 0x3e, 0xb0, 0xed, 0x20, 0x9d, 0x27, 0x49, 0xef, 0x14, 0x86, 0xfd, 0xd4, 0xa3, 0x6a, 0x21, 0xf1, + 0x03, 0x7a, 0xde, 0xcb, 0xf3, 0xe8, 0x0a, 0x1a, 0x62, 0x1b, 0x60, 0x2f, 0x58, 0xad, 0xaf, 0xf6, 0xdf, 0xbd, 0xf5, + 0x05, 0xe3, 0xc7, 0xe3, 0x2b, 0x00, 0xb4, 0xac, 0xa4, 0xe6, 0x38, 0xcf, 0xa6, 0x8d, 0xa9, 0x91, 0x0e, 0x71, 0xc8, + 0x7b, 0x6b, 0x40, 0x88, 0x69, 0x64, 0x58, 0x25, 0x6e, 0x42, 0xf0, 0x96, 0xf8, 0x59, 0x56, 0xe2, 0x1e, 0x18, 0xe0, + 0x43, 0x20, 0x8a, 0x61, 0xca, 0x5b, 0xa0, 0xcd, 0xaf, 0x16, 0x71, 0x48, 0x70, 0xce, 0x50, 0xff, 0x22, 0x8c, 0xc3, + 0x08, 0x66, 0x5f, 0x88, 0x01, 0x4b, 0x05, 0x71, 0x5c, 0x96, 0xde, 0x44, 0x33, 0x31, 0x4a, 0x3c, 0x14, 0x28, 0x2c, + 0x0c, 0x41, 0xc1, 0x70, 0x78, 0x71, 0xbd, 0x6f, 0xc2, 0x45, 0xa4, 0xf0, 0x41, 0x0d, 0x85, 0xfb, 0x2b, 0x10, 0x72, + 0x02, 0x35, 0xd9, 0x39, 0xe8, 0x41, 0x80, 0xf3, 0x6b, 0x50, 0x7f, 0xe3, 0x04, 0xa1, 0xb8, 0xd7, 0xf1, 0x40, 0x83, + 0x3e, 0x9b, 0x44, 0xe9, 0x19, 0x1b, 0x05, 0x13, 0x56, 0x4a, 0xc9, 0xbb, 0x67, 0xc1, 0x1a, 0x03, 0x3b, 0x15, 0xd6, + 0xcb, 0x83, 0x37, 0xaf, 0xe5, 0xca, 0xd5, 0x84, 0x31, 0x2c, 0xd2, 0x1c, 0xd4, 0x2a, 0x88, 0x6d, 0x29, 0x8e, 0x5f, + 0x70, 0x25, 0xbd, 0x45, 0x49, 0x5c, 0x7c, 0x9c, 0x81, 0x89, 0xc1, 0xde, 0xc3, 0x30, 0x30, 0x7d, 0x08, 0x53, 0x51, + 0x39, 0xcc, 0x27, 0x2a, 0x46, 0xba, 0x08, 0x3a, 0x0b, 0x4c, 0xc5, 0x6b, 0xe6, 0xb8, 0x25, 0xb0, 0x2a, 0x8f, 0x87, + 0x56, 0x34, 0x1a, 0xbd, 0x4a, 0x63, 0x1e, 0x47, 0x49, 0xfc, 0x0b, 0x51, 0x72, 0x81, 0x3c, 0xc6, 0x7a, 0x72, 0x11, + 0x00, 0x77, 0xea, 0x91, 0xb8, 0x4a, 0xc8, 0xde, 0x23, 0x62, 0x08, 0x69, 0x99, 0x84, 0x87, 0x47, 0x12, 0xbc, 0xc4, + 0x9f, 0xcd, 0x8b, 0x09, 0x12, 0x56, 0x0e, 0x8c, 0x82, 0x3c, 0x3b, 0x2d, 0x58, 0x7e, 0xce, 0x46, 0x9a, 0x03, 0x0a, + 0xc0, 0x8a, 0x9a, 0x83, 0xf1, 0x42, 0x33, 0x3a, 0x4a, 0x87, 0x72, 0x18, 0xaa, 0x67, 0x8a, 0x59, 0x26, 0x99, 0x59, + 0x5b, 0x38, 0x5a, 0x0a, 0x38, 0xc2, 0xa8, 0x90, 0x92, 0x20, 0x0f, 0x15, 0x86, 0x13, 0x90, 0x42, 0xcc, 0xad, 0x6d, + 0x73, 0xa5, 0xc9, 0x5e, 0xcc, 0x49, 0x25, 0xe4, 0xd0, 0x11, 0x36, 0x32, 0x41, 0x9a, 0xbb, 0xb0, 0xab, 0x40, 0xca, + 0x4b, 0x70, 0x85, 0x14, 0x51, 0x66, 0x0e, 0x32, 0x40, 0xf8, 0x0d, 0xe9, 0x42, 0x50, 0x26, 0xd0, 0x82, 0x21, 0x1b, + 0xf8, 0x7a, 0xe5, 0x81, 0xb0, 0x12, 0xef, 0x0a, 0x11, 0x6f, 0x0d, 0xd8, 0xa4, 0x8b, 0x00, 0x30, 0xef, 0x1e, 0xf3, + 0xd3, 0x6c, 0x6f, 0x38, 0x64, 0x45, 0x91, 0x01, 0x6c, 0xf7, 0xa8, 0xfd, 0x3a, 0x43, 0x0b, 0x28, 0xe9, 0x6a, 0x59, + 0x67, 0x17, 0xa4, 0xc1, 0x4d, 0xb5, 0xa2, 0x74, 0x7a, 0x60, 0x1f, 0x1f, 0x83, 0xcc, 0xf6, 0x24, 0x19, 0x80, 0xea, + 0xcb, 0x86, 0x9f, 0xb0, 0x67, 0xea, 0x94, 0x59, 0x69, 0x5f, 0x3a, 0x75, 0x90, 0x3c, 0x18, 0xd6, 0x2d, 0x8d, 0x05, + 0x5d, 0x39, 0x34, 0xae, 0x86, 0x54, 0x90, 0x8b, 0x33, 0x52, 0xd9, 0xc6, 0x32, 0x82, 0xd5, 0x56, 0x7a, 0x44, 0x7a, + 0x85, 0x4d, 0x41, 0x80, 0x1e, 0xf2, 0xa3, 0x9e, 0xac, 0x0f, 0x73, 0x41, 0xb9, 0x9c, 0xfd, 0x3c, 0x67, 0x05, 0x17, + 0xac, 0x0b, 0xe3, 0x82, 0xb9, 0x0a, 0x22, 0xb6, 0x69, 0x1d, 0xd6, 0x6c, 0xc7, 0x55, 0xb0, 0xbd, 0x9b, 0xa1, 0x1e, + 0x2b, 0x90, 0x93, 0x6f, 0x66, 0x27, 0xb2, 0x27, 0xdc, 0xeb, 0xeb, 0x6f, 0xd4, 0x20, 0xd5, 0x52, 0x6a, 0x1b, 0xa8, + 0xb1, 0x26, 0xb6, 0x6a, 0x32, 0xb2, 0x5d, 0xa9, 0x50, 0xef, 0x75, 0x7a, 0x35, 0x3e, 0x80, 0x3d, 0xd7, 0xd6, 0x2c, + 0x5d, 0x19, 0xdb, 0xef, 0x15, 0x4d, 0xdf, 0x89, 0x91, 0xc9, 0x1a, 0xe5, 0xb7, 0x73, 0x8f, 0xda, 0xf1, 0xd0, 0x76, + 0xa9, 0xae, 0x12, 0x0c, 0xf3, 0xba, 0x60, 0x68, 0x42, 0x3d, 0xd3, 0x5d, 0x6c, 0xcd, 0x54, 0x3c, 0x54, 0x6b, 0xad, + 0x1c, 0x08, 0x16, 0x1e, 0x82, 0x71, 0xb2, 0xd2, 0x3f, 0x78, 0x1b, 0x4d, 0x19, 0x52, 0xd4, 0x5b, 0xd7, 0x40, 0x3a, + 0x10, 0xd0, 0xe4, 0xa8, 0xa9, 0xde, 0x98, 0x2b, 0xac, 0xa6, 0xfa, 0xfe, 0x8a, 0xc1, 0x8a, 0x00, 0xfb, 0xba, 0x5c, + 0xb1, 0x44, 0xa4, 0x37, 0x05, 0x97, 0x68, 0xfa, 0x88, 0x32, 0xb1, 0x26, 0xa4, 0xe0, 0x01, 0x79, 0x58, 0xfe, 0xc6, + 0xc2, 0xa9, 0x56, 0x0a, 0x47, 0x86, 0x32, 0x05, 0xe8, 0x4c, 0x4a, 0x00, 0xc4, 0x25, 0xfd, 0xad, 0x6d, 0x2c, 0x24, + 0xdb, 0x3e, 0xf2, 0x81, 0x3f, 0x4e, 0x22, 0xee, 0x74, 0xb6, 0xda, 0x2e, 0xf0, 0x21, 0x08, 0x71, 0xd0, 0x11, 0x60, + 0xde, 0x57, 0xa8, 0x70, 0xf2, 0x16, 0x5c, 0xe6, 0x83, 0x51, 0x34, 0x89, 0xc7, 0xdc, 0x49, 0x50, 0x89, 0xb8, 0x25, + 0x4b, 0x40, 0xc9, 0xe8, 0x7d, 0x05, 0xca, 0x82, 0x09, 0xe9, 0x22, 0xaa, 0x95, 0x40, 0x63, 0x0a, 0x52, 0x92, 0x52, + 0xa4, 0x05, 0x15, 0x04, 0x86, 0x50, 0xe9, 0x29, 0x8e, 0x02, 0xfd, 0x16, 0x0f, 0xc4, 0xa0, 0xc1, 0x92, 0x45, 0x19, + 0x0f, 0xe2, 0xe5, 0x42, 0x50, 0xc3, 0x3e, 0xcf, 0x5e, 0x67, 0x17, 0x2c, 0x7f, 0x16, 0x21, 0xec, 0x81, 0xe8, 0x5e, + 0x82, 0xa4, 0x27, 0x81, 0xce, 0x7b, 0x8a, 0x57, 0xce, 0x09, 0x69, 0x58, 0x88, 0x69, 0x8c, 0x8a, 0x10, 0xec, 0x16, + 0xa2, 0x7d, 0x8a, 0x5b, 0x8a, 0xf6, 0x1e, 0xaa, 0x12, 0xae, 0x79, 0x6b, 0xef, 0x75, 0x9d, 0xb7, 0x60, 0x84, 0x99, + 0xe2, 0xd6, 0xfa, 0x8e, 0x75, 0x3d, 0xa9, 0x9b, 0x1d, 0xc9, 0x5b, 0x86, 0x32, 0x03, 0xfd, 0x71, 0x7d, 0x5d, 0x19, + 0xe9, 0xa0, 0x4c, 0xb5, 0x34, 0x47, 0x08, 0xc4, 0x96, 0x70, 0x4b, 0x50, 0x46, 0x68, 0x78, 0xe5, 0x59, 0x92, 0x18, + 0xba, 0xc8, 0x8b, 0x7b, 0x4e, 0x43, 0x1d, 0x01, 0x14, 0xd3, 0x9a, 0x46, 0x1a, 0xb0, 0x40, 0x57, 0xa0, 0x52, 0x52, + 0xda, 0xc8, 0xab, 0xd6, 0x46, 0x40, 0x9c, 0x8e, 0x58, 0x2e, 0x1c, 0x34, 0xa9, 0x43, 0x61, 0xc2, 0x14, 0x18, 0x9a, + 0x8d, 0x40, 0xc2, 0x2b, 0x04, 0xc0, 0x3c, 0xf1, 0x27, 0x59, 0xc1, 0x75, 0x9d, 0x09, 0x7d, 0x7c, 0x7d, 0x1d, 0x0b, + 0x7f, 0x11, 0x19, 0x20, 0x67, 0xd3, 0xec, 0x9c, 0xad, 0x80, 0xba, 0xa7, 0x06, 0x33, 0x41, 0x36, 0x86, 0x01, 0x25, + 0x0a, 0xaa, 0x65, 0x96, 0xc4, 0x60, 0xe9, 0xeb, 0x06, 0x3e, 0x18, 0x74, 0xec, 0x12, 0x65, 0x84, 0xdb, 0xef, 0xf7, + 0xdb, 0x5e, 0xc7, 0x2d, 0x05, 0xc1, 0x17, 0x4b, 0x14, 0xbd, 0x41, 0x3f, 0x4a, 0x13, 0x7c, 0x95, 0x2c, 0x60, 0xae, + 0xa1, 0x14, 0x39, 0xe9, 0x26, 0xe6, 0x49, 0x41, 0xec, 0x7a, 0x23, 0x18, 0x94, 0x33, 0x25, 0xb8, 0xd1, 0xc4, 0x15, 0xdb, 0xf6, 0x83, 0x26, 0x9b, 0x66, 0x27, 0xb5, 0xc3, 0xd4, 0xc2, 0xc8, 0x35, 0x2f, 0xb4, 0x07, 0x6c, 0x2e, 0x0f, - 0x5a, 0x89, 0x54, 0x0d, 0xbc, 0x0e, 0x10, 0x0a, 0x4f, 0xd7, 0x59, 0x42, 0xa9, 0xea, 0x2c, 0x85, 0xb8, 0xde, 0x40, - 0x1f, 0x99, 0x04, 0x73, 0x15, 0x09, 0xf6, 0xa5, 0x40, 0xe0, 0xe8, 0x91, 0x89, 0xf5, 0x7a, 0x06, 0xcb, 0x73, 0x1a, - 0x0d, 0x3f, 0x69, 0x70, 0x2b, 0xb2, 0x37, 0xd9, 0xc0, 0x69, 0x94, 0x84, 0x86, 0xb8, 0x32, 0xf1, 0x56, 0x12, 0xba, - 0xb6, 0x51, 0xc0, 0x21, 0x5b, 0x62, 0xfb, 0xe6, 0x42, 0x37, 0xb9, 0x5d, 0xb2, 0x87, 0xf2, 0x9f, 0x34, 0x97, 0xdc, - 0xc0, 0x72, 0x5c, 0x49, 0x03, 0xae, 0x18, 0x0f, 0x96, 0xa6, 0x01, 0x09, 0xf0, 0x5d, 0x39, 0x8a, 0x8b, 0xf5, 0x24, - 0xf8, 0x5d, 0xc1, 0x7c, 0x6e, 0xcc, 0x74, 0x2b, 0xa4, 0x5a, 0xc2, 0x49, 0x33, 0x58, 0x83, 0x26, 0x8d, 0x07, 0x25, - 0x6a, 0xbe, 0x46, 0x43, 0x85, 0x38, 0xfe, 0x4c, 0x54, 0xa1, 0x09, 0x86, 0x60, 0xe4, 0x5e, 0x21, 0x19, 0x2e, 0x5b, - 0x16, 0x2d, 0x52, 0xa6, 0xc6, 0xa4, 0x52, 0x35, 0xcb, 0x65, 0x60, 0x60, 0xd1, 0x6e, 0xf5, 0xa5, 0x25, 0xae, 0x44, - 0x6e, 0x1a, 0x6a, 0x61, 0x52, 0x28, 0x6f, 0xc2, 0xc9, 0xd1, 0xef, 0x52, 0xd6, 0xbb, 0x89, 0x4f, 0xae, 0xf0, 0xc9, - 0x7d, 0xc3, 0x87, 0x32, 0x79, 0xbb, 0x18, 0x14, 0xc1, 0xd7, 0xb5, 0x4a, 0xb4, 0x4f, 0x7d, 0x14, 0xcc, 0xae, 0x16, - 0xba, 0x20, 0x50, 0x24, 0x9b, 0xa4, 0x03, 0xc9, 0x6f, 0x28, 0x36, 0x2a, 0xcf, 0x28, 0x73, 0xc5, 0x06, 0xa9, 0x79, - 0xa5, 0x99, 0x97, 0xba, 0x0d, 0xfb, 0xbd, 0x2c, 0x25, 0x9d, 0xb8, 0xa0, 0x4c, 0xec, 0xdd, 0x44, 0x1b, 0x2f, 0x0d, - 0x33, 0x61, 0xfd, 0x0a, 0x63, 0xa7, 0x46, 0xa1, 0x54, 0x8a, 0x40, 0x1c, 0x1b, 0x5f, 0x2b, 0xcb, 0x20, 0xf3, 0x57, - 0xd8, 0x53, 0x00, 0x4a, 0x02, 0x8b, 0xaf, 0xa9, 0xe4, 0x45, 0x61, 0x9d, 0x8e, 0xf7, 0x88, 0x8e, 0x95, 0x08, 0xad, - 0x89, 0x7c, 0xad, 0xcf, 0x62, 0xbf, 0xe6, 0x12, 0x9a, 0x94, 0xcc, 0x07, 0x79, 0x60, 0xab, 0x40, 0x44, 0xa5, 0xdb, - 0x92, 0x41, 0x42, 0x0e, 0xe9, 0x32, 0xd1, 0x6b, 0x23, 0x19, 0xb4, 0x4e, 0x85, 0x44, 0x4b, 0x8f, 0xc2, 0xc8, 0x41, - 0xc7, 0x9d, 0xd6, 0x62, 0x89, 0x90, 0x4d, 0x7b, 0x93, 0x58, 0x11, 0x9d, 0xd3, 0x1c, 0x4d, 0x38, 0x53, 0xa7, 0x3b, - 0x0e, 0xa0, 0x03, 0x62, 0x7f, 0x89, 0xf5, 0x56, 0x9a, 0x9d, 0xae, 0x5f, 0x39, 0x7c, 0xd7, 0xd7, 0x13, 0xe4, 0x07, - 0x61, 0xf0, 0xc2, 0x9a, 0x0d, 0x94, 0xec, 0xdd, 0x7b, 0x8d, 0xad, 0xc8, 0xfe, 0xac, 0x4a, 0x2a, 0x4f, 0xa1, 0xc6, - 0xb9, 0xf5, 0x75, 0x62, 0x66, 0x68, 0x51, 0x55, 0xec, 0x1b, 0x52, 0x7d, 0x5f, 0x29, 0xec, 0x0a, 0xe5, 0x7d, 0x39, - 0x74, 0xec, 0xba, 0x6e, 0x90, 0x93, 0xf3, 0x72, 0x6f, 0x95, 0x0b, 0x79, 0xff, 0xbe, 0xe9, 0x33, 0x9d, 0xeb, 0xe1, - 0x9f, 0x39, 0xa8, 0x9c, 0x8b, 0xab, 0x94, 0x2c, 0x98, 0x67, 0x4a, 0x1d, 0x2d, 0x39, 0xa0, 0xed, 0x1e, 0x7a, 0xda, - 0xd1, 0x45, 0x14, 0x73, 0x4b, 0x8f, 0x22, 0x3c, 0x6d, 0x94, 0x4f, 0xd2, 0xe8, 0x00, 0xbc, 0xd0, 0x84, 0x24, 0x27, - 0xdc, 0xb4, 0x45, 0x8b, 0xe1, 0x84, 0x61, 0x08, 0x5c, 0xd9, 0x13, 0xa6, 0xec, 0xb9, 0x87, 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, 0x11, 0x27, 0x49, 0x55, 0xfa, - 0xbb, 0x0a, 0x78, 0x31, 0xec, 0x6d, 0xa2, 0x5d, 0x60, 0x34, 0x57, 0x20, 0xb8, 0xda, 0x08, 0xfb, 0xe8, 0xb8, 0xd5, - 0xba, 0x8b, 0x88, 0x23, 0x37, 0xa3, 0x11, 0x50, 0x8f, 0x11, 0x56, 0xcd, 0xda, 0x7b, 0x2f, 0x30, 0xa4, 0x66, 0xe0, - 0x83, 0xea, 0x8c, 0x8a, 0x7f, 0x95, 0x3d, 0xf5, 0x2b, 0xd1, 0xbb, 0x55, 0x75, 0x35, 0x03, 0x2a, 0x2a, 0xf0, 0x61, - 0x86, 0x58, 0xda, 0x2a, 0x10, 0x90, 0xeb, 0x61, 0x51, 0x0a, 0x98, 0xa4, 0xc1, 0x82, 0x52, 0x60, 0xad, 0x95, 0xdd, - 0xeb, 0xdb, 0x82, 0x39, 0x14, 0x0a, 0x17, 0xfd, 0x9f, 0x65, 0xd3, 0x19, 0x5a, 0x66, 0x0d, 0xa6, 0x86, 0x06, 0x1f, - 0x1b, 0xf5, 0xe5, 0x8a, 0xb2, 0x5a, 0x1f, 0xda, 0x91, 0x35, 0x7e, 0xd2, 0x8e, 0x32, 0x38, 0x54, 0x73, 0x5d, 0x54, - 0xb7, 0x9b, 0x9b, 0x22, 0x66, 0x15, 0x8f, 0xfb, 0xa4, 0xb7, 0xb5, 0x35, 0xe9, 0x69, 0x1a, 0x90, 0x4c, 0x92, 0x0c, - 0x6f, 0x32, 0x40, 0x59, 0x11, 0x67, 0x51, 0x36, 0xc8, 0xb7, 0x28, 0x4b, 0x5c, 0xbf, 0x1f, 0x7a, 0x7b, 0x35, 0xcf, - 0xda, 0xdb, 0x5b, 0xef, 0x22, 0x57, 0x75, 0xd2, 0x83, 0x3c, 0x3c, 0x82, 0xa2, 0x25, 0x9b, 0x32, 0x5c, 0x4c, 0xb3, - 0x11, 0x0b, 0x6c, 0xe8, 0x9e, 0xda, 0xa5, 0xdc, 0x34, 0x11, 0x6c, 0x8e, 0x88, 0x39, 0x8b, 0x0f, 0xf5, 0x48, 0x6a, - 0xb0, 0x07, 0x2c, 0xa0, 0xcd, 0x85, 0xaf, 0xc2, 0xb3, 0x24, 0x3b, 0x8d, 0x92, 0x03, 0xa1, 0xc0, 0x6b, 0x2d, 0xbf, - 0x05, 0x97, 0x91, 0x2c, 0x56, 0x43, 0x49, 0x7d, 0x35, 0xf8, 0x2a, 0xb8, 0xbd, 0x47, 0xe5, 0xad, 0xd8, 0x1d, 0xbf, - 0xed, 0x77, 0x6c, 0x15, 0x11, 0xfb, 0xc9, 0x9c, 0x0e, 0x34, 0x4e, 0x01, 0x94, 0x39, 0x00, 0x4d, 0x56, 0x78, 0x43, - 0x16, 0xfe, 0x34, 0xf8, 0x49, 0xb9, 0xd4, 0x19, 0xb8, 0x10, 0xe0, 0xe4, 0x27, 0x31, 0x6f, 0xe1, 0x79, 0xa4, 0xed, - 0x2d, 0x44, 0x05, 0xc6, 0x15, 0x29, 0x2e, 0x5d, 0x2a, 0x6f, 0xd0, 0x3b, 0x0e, 0x4f, 0xa0, 0xd9, 0xc6, 0xc6, 0xc2, - 0x79, 0x13, 0xf1, 0x89, 0x9f, 0x47, 0xe9, 0x28, 0x9b, 0x3a, 0xee, 0xa6, 0x6d, 0xbb, 0x7e, 0x41, 0x9e, 0xc8, 0xe7, - 0x6e, 0xb9, 0x71, 0x02, 0x7e, 0x40, 0x68, 0x0f, 0xec, 0xcd, 0x63, 0xef, 0x80, 0x85, 0x27, 0xbb, 0x1b, 0x8b, 0x11, - 0x2b, 0xfb, 0x27, 0xde, 0xa5, 0x8e, 0xb9, 0x7b, 0xef, 0x51, 0xca, 0x40, 0xaf, 0xb0, 0x7f, 0x29, 0xc1, 0x00, 0x76, - 0xa3, 0xf8, 0x3b, 0x48, 0xb9, 0x8f, 0x74, 0x20, 0x22, 0xe3, 0xb4, 0xd7, 0xd7, 0x76, 0x46, 0x11, 0x03, 0xfb, 0x9e, - 0x76, 0x56, 0xef, 0xdf, 0xaf, 0xd4, 0x7c, 0x55, 0xea, 0xcd, 0x59, 0x58, 0xf3, 0xd4, 0xbd, 0x97, 0x74, 0xb4, 0x52, - 0xdf, 0xc8, 0x73, 0x46, 0x4a, 0x73, 0xd9, 0x4e, 0x70, 0x8c, 0x2d, 0xbe, 0x7a, 0x5b, 0x1f, 0x8a, 0x28, 0x85, 0x1f, - 0x83, 0xf5, 0x12, 0x81, 0xfa, 0x06, 0x07, 0xc7, 0x3b, 0x08, 0xb7, 0x76, 0x9d, 0x41, 0xe0, 0xdc, 0x6b, 0xb5, 0xae, - 0x7f, 0xdc, 0x3a, 0xfc, 0x73, 0xd4, 0xfa, 0x65, 0xaf, 0xf5, 0xc3, 0x91, 0x7b, 0xed, 0xfc, 0xb8, 0x35, 0x38, 0x94, - 0x6f, 0x87, 0x7f, 0xee, 0xff, 0x58, 0x1c, 0xfd, 0x41, 0x14, 0x6e, 0xb8, 0xee, 0xd6, 0x99, 0x37, 0x63, 0xe1, 0x56, - 0xab, 0xd5, 0x87, 0xa7, 0x33, 0x78, 0xc2, 0xbf, 0x17, 0xf0, 0xe7, 0xfa, 0xd0, 0xfa, 0x4f, 0x3f, 0xa6, 0xff, 0xf9, - 0xc7, 0xfc, 0x08, 0xc7, 0x3c, 0xfc, 0xf3, 0x8f, 0x85, 0xfd, 0xa0, 0x1f, 0x6e, 0x1d, 0x6d, 0xba, 0x8e, 0xae, 0xf9, - 0x43, 0x58, 0x3d, 0x42, 0xab, 0xc3, 0x3f, 0xcb, 0x37, 0xfb, 0xc1, 0xc9, 0x6e, 0x3f, 0x3c, 0xba, 0x76, 0xec, 0xeb, - 0x07, 0xee, 0xb5, 0xeb, 0x5e, 0x6f, 0xe0, 0x3c, 0xe7, 0x30, 0xfa, 0x03, 0xf8, 0x3b, 0x86, 0xbf, 0x36, 0xfc, 0x9d, - 0xc2, 0xdf, 0x3f, 0x43, 0x37, 0x11, 0x7f, 0xbb, 0xa6, 0x58, 0xc8, 0x35, 0x1e, 0x58, 0x44, 0xb0, 0x0a, 0xee, 0xc6, - 0x56, 0xec, 0x6d, 0x10, 0xd1, 0x60, 0x1f, 0xfa, 0xbe, 0x8f, 0x61, 0x52, 0x67, 0x71, 0xbc, 0x01, 0x8b, 0x8e, 0x9c, - 0xb3, 0x11, 0x30, 0x4f, 0x44, 0x0e, 0x8a, 0x80, 0x8b, 0xb3, 0xd5, 0x02, 0x0f, 0x57, 0xbd, 0x61, 0xb8, 0xc1, 0x1c, - 0x30, 0x0a, 0xde, 0x32, 0x7c, 0xe8, 0xba, 0xde, 0x0b, 0x79, 0x66, 0x88, 0xfb, 0x5c, 0xb0, 0x56, 0x9a, 0x09, 0x93, - 0xc6, 0x76, 0xbd, 0xd9, 0x8a, 0x4a, 0xd8, 0xd6, 0xe9, 0x19, 0xd4, 0x9d, 0x8a, 0x83, 0xb6, 0xef, 0x58, 0xf4, 0x09, - 0xb7, 0xe4, 0x1b, 0xe3, 0x10, 0x78, 0xc9, 0x92, 0x6f, 0x1a, 0x8d, 0x86, 0x8d, 0x28, 0xdc, 0xb1, 0xa7, 0x0c, 0x66, - 0x58, 0x32, 0x11, 0x39, 0x29, 0x4d, 0x61, 0xd9, 0xc2, 0xe4, 0xef, 0xa3, 0x9c, 0x6f, 0x54, 0x86, 0x6d, 0x58, 0xb3, - 0x64, 0x9b, 0x96, 0xfe, 0x1d, 0xa6, 0x40, 0xd3, 0x92, 0xce, 0x3f, 0xcc, 0xf1, 0xc3, 0x94, 0xd0, 0x7a, 0xeb, 0x70, - 0xf0, 0xd0, 0x0b, 0x90, 0x3b, 0xa2, 0x9f, 0xf3, 0x1e, 0xd5, 0x18, 0xfc, 0x2b, 0xc3, 0x0c, 0x9e, 0x98, 0x0f, 0x43, - 0x34, 0x8b, 0x52, 0x07, 0xb7, 0x52, 0x14, 0xf7, 0xaf, 0x70, 0x67, 0xa4, 0xa5, 0xb7, 0x1f, 0xaa, 0x1d, 0x73, 0x90, - 0x33, 0xf6, 0x5d, 0x94, 0x7c, 0x62, 0xb9, 0x73, 0xe9, 0x75, 0xba, 0x9f, 0x53, 0x67, 0x0f, 0x6d, 0xb3, 0x0f, 0xd5, - 0x31, 0x9a, 0x32, 0x0b, 0xd4, 0x11, 0x61, 0xab, 0xe3, 0xe5, 0x18, 0xd5, 0x42, 0x12, 0x14, 0x5e, 0x16, 0x76, 0x89, - 0xc3, 0xed, 0xdd, 0xe2, 0xfc, 0xac, 0x6f, 0x07, 0xb6, 0x0d, 0x16, 0xff, 0x01, 0x85, 0xad, 0x84, 0x61, 0x01, 0x06, - 0xd9, 0x6e, 0xdc, 0xe3, 0x9b, 0x9b, 0x55, 0xc0, 0x09, 0x0f, 0xd2, 0xa9, 0x7b, 0xe2, 0x45, 0xde, 0x24, 0x84, 0x01, - 0x87, 0xd0, 0x0c, 0xbb, 0xf4, 0x86, 0xbb, 0xb1, 0x9c, 0x06, 0x63, 0x21, 0x7e, 0x12, 0x15, 0xfc, 0x15, 0xc6, 0x23, - 0xc2, 0x21, 0x1a, 0xfb, 0x3e, 0xbb, 0x64, 0x43, 0x65, 0x67, 0x00, 0xa1, 0x22, 0xb7, 0xe7, 0x0e, 0x43, 0xa3, 0x19, - 0xcc, 0x1d, 0x86, 0x07, 0x03, 0x1b, 0xf6, 0x12, 0xec, 0xca, 0x30, 0x3a, 0xec, 0x1c, 0x0d, 0xd2, 0x70, 0xc6, 0x02, - 0x4d, 0x5b, 0x59, 0x74, 0x56, 0x2b, 0xea, 0x1e, 0x0d, 0x9c, 0x29, 0x18, 0xe9, 0x60, 0x8b, 0x3b, 0xf8, 0x86, 0x11, - 0x8a, 0x22, 0xfc, 0xc0, 0xce, 0x5e, 0x5c, 0xce, 0x1c, 0x7b, 0x77, 0xcb, 0xde, 0xc4, 0x52, 0xcf, 0x06, 0xf6, 0x82, - 0xb9, 0xc3, 0x0b, 0xd7, 0xec, 0xbc, 0x7d, 0x84, 0xa0, 0x62, 0x21, 0x4e, 0x7e, 0x31, 0xb0, 0xfb, 0x62, 0xea, 0x36, - 0x0c, 0x9a, 0xca, 0xe5, 0xc7, 0x15, 0x3d, 0x20, 0x54, 0x55, 0x57, 0x05, 0x1d, 0x94, 0x75, 0x03, 0x67, 0x62, 0x22, - 0xd1, 0xc2, 0xc9, 0x24, 0x15, 0xc0, 0xe1, 0xc1, 0x66, 0x30, 0xa9, 0xd1, 0x6d, 0xfb, 0x68, 0x70, 0x11, 0x3c, 0xb0, - 0x1f, 0xa8, 0x97, 0x31, 0x20, 0xc3, 0xc4, 0xf4, 0x63, 0x90, 0x76, 0xf8, 0xf7, 0x9c, 0x01, 0x92, 0x17, 0x54, 0x34, - 0x93, 0x45, 0x67, 0x58, 0x74, 0x10, 0x20, 0xa8, 0x5e, 0xa1, 0xad, 0x3f, 0xb1, 0x26, 0xa3, 0x90, 0x60, 0xbf, 0x7f, - 0x1f, 0x96, 0x66, 0xb3, 0x73, 0x84, 0xe7, 0x0d, 0x39, 0x2f, 0xbe, 0x8b, 0x39, 0xa8, 0x84, 0xad, 0xbe, 0xed, 0x0e, - 0x6c, 0x0b, 0x97, 0xb6, 0x97, 0x6d, 0x86, 0x82, 0xc2, 0xf1, 0xe6, 0x01, 0x0b, 0x26, 0xfd, 0xb0, 0x3d, 0x70, 0x72, - 0x19, 0x6e, 0xc4, 0x73, 0x4b, 0x21, 0xc1, 0xdb, 0xde, 0x04, 0x04, 0x3a, 0x72, 0xee, 0x86, 0xbd, 0xa9, 0x0a, 0xa1, - 0xe8, 0x78, 0x73, 0xe4, 0x06, 0x31, 0xfc, 0x71, 0x5a, 0xc8, 0x34, 0x13, 0xdd, 0x57, 0x6b, 0x66, 0x37, 0x18, 0x29, - 0x8b, 0x3c, 0x09, 0xb3, 0x4d, 0x07, 0x23, 0xb4, 0x20, 0x69, 0x77, 0x07, 0x00, 0xc3, 0xa6, 0xa3, 0x38, 0x6d, 0x4b, - 0xb1, 0x9a, 0xb2, 0xcf, 0x0f, 0xf5, 0x72, 0x0c, 0xd9, 0x60, 0xc8, 0xfc, 0x4a, 0xfb, 0x00, 0x58, 0x41, 0xe2, 0xe5, - 0x47, 0xea, 0xcc, 0xeb, 0x65, 0xed, 0x7c, 0x6b, 0xa1, 0x44, 0x11, 0xf3, 0x0c, 0x09, 0xc5, 0x4b, 0xed, 0x86, 0x09, - 0x73, 0x7b, 0x86, 0xc4, 0xd0, 0x2c, 0x1f, 0xb6, 0x81, 0xe9, 0x55, 0x80, 0x3d, 0x35, 0xb7, 0x45, 0x12, 0x56, 0xcd, - 0xbd, 0x43, 0x60, 0xed, 0x23, 0xe0, 0x21, 0xda, 0x46, 0x3d, 0x15, 0xcd, 0x67, 0x49, 0xf8, 0xb2, 0x71, 0x5c, 0x1c, - 0xe1, 0x89, 0xd0, 0xbe, 0x3f, 0x9c, 0xe7, 0x20, 0x0f, 0xf8, 0x5b, 0xb0, 0x0c, 0x42, 0xd9, 0x14, 0x1d, 0x3d, 0x3c, - 0x02, 0xf6, 0x08, 0xf1, 0x46, 0xd8, 0xdc, 0xa8, 0x46, 0x8b, 0x92, 0x8c, 0x17, 0x3a, 0x18, 0xee, 0x71, 0xe9, 0xda, - 0xa3, 0x60, 0x90, 0x27, 0xc6, 0x0e, 0x9e, 0xf9, 0xfb, 0x43, 0xac, 0xc6, 0x09, 0x0a, 0xb7, 0xa4, 0xdd, 0x56, 0x89, - 0xbf, 0x7d, 0x3f, 0x05, 0x09, 0x8e, 0x75, 0xe0, 0x67, 0xdd, 0xbf, 0x9f, 0x48, 0xa4, 0x76, 0xd3, 0x1e, 0x9d, 0x44, - 0x60, 0x3c, 0x38, 0xf7, 0x53, 0xa8, 0x46, 0x12, 0x51, 0x51, 0x8e, 0x16, 0xa8, 0x79, 0xaa, 0x56, 0xc1, 0x77, 0x68, - 0x46, 0xe0, 0x39, 0x86, 0xad, 0xc9, 0x4f, 0xd5, 0x8d, 0x45, 0x2c, 0xdf, 0x75, 0xe9, 0x68, 0x0b, 0x0f, 0x20, 0x05, - 0xa3, 0x09, 0x86, 0x71, 0x29, 0x28, 0x59, 0xf1, 0xdf, 0xb1, 0x11, 0x2b, 0x9f, 0x1c, 0x66, 0x9b, 0x9b, 0x47, 0xe2, - 0xdc, 0x82, 0x18, 0x87, 0x1b, 0xd1, 0xd5, 0xb8, 0x02, 0xa0, 0x3e, 0x9d, 0x13, 0xd7, 0x03, 0xd3, 0x8a, 0x35, 0x5d, - 0x8a, 0x7d, 0x72, 0x98, 0x01, 0x28, 0xb8, 0xe5, 0x1c, 0xfa, 0x83, 0x3f, 0x1e, 0x81, 0x7b, 0xec, 0xff, 0xc1, 0xdd, - 0x52, 0x82, 0xa6, 0x27, 0xcf, 0x14, 0x17, 0x74, 0xc6, 0xda, 0xf1, 0x28, 0x36, 0x1a, 0x14, 0x5e, 0x0a, 0x18, 0x80, - 0x36, 0x07, 0x99, 0x50, 0x71, 0x10, 0x72, 0x54, 0x60, 0xfb, 0xb8, 0xf9, 0x39, 0xee, 0xec, 0xe7, 0x60, 0xe1, 0x0d, - 0xf4, 0xdb, 0x6b, 0x78, 0xfb, 0xa3, 0x7e, 0xfb, 0x89, 0x05, 0xbf, 0x94, 0x32, 0x74, 0x5f, 0x9b, 0xe2, 0x91, 0x9a, - 0xa2, 0x14, 0x4b, 0x64, 0xd0, 0x90, 0xb9, 0xf9, 0x52, 0xcc, 0x86, 0xbb, 0x25, 0x10, 0x43, 0x89, 0xae, 0xdc, 0xe7, - 0xd1, 0x19, 0x12, 0xd7, 0x35, 0x49, 0x61, 0xe4, 0x12, 0x98, 0x08, 0x57, 0x7c, 0x4b, 0xcc, 0xd9, 0x6f, 0x83, 0x0d, - 0x5e, 0xcb, 0x3b, 0x40, 0xfb, 0x8e, 0x4d, 0x67, 0xfc, 0x6a, 0x9f, 0x14, 0x7d, 0x20, 0xd3, 0x06, 0xc4, 0xd9, 0x79, - 0xbb, 0x17, 0xef, 0xf2, 0x5e, 0x0c, 0x52, 0x3d, 0x57, 0x2c, 0x86, 0x7b, 0xd5, 0x7b, 0x8f, 0x51, 0x4a, 0x93, 0x99, - 0xbc, 0x1a, 0x7a, 0x5d, 0x89, 0xde, 0xe6, 0x26, 0x20, 0xd8, 0x33, 0xba, 0x72, 0xd1, 0xb5, 0x2c, 0x05, 0x4d, 0x00, - 0xa2, 0x27, 0x75, 0x96, 0x23, 0x8e, 0xc3, 0x6c, 0x36, 0x28, 0x1e, 0x31, 0x77, 0xe5, 0xa8, 0x38, 0x26, 0x76, 0x97, - 0x09, 0x3b, 0x80, 0x19, 0x71, 0x79, 0xab, 0x23, 0xa2, 0xc3, 0xa2, 0xbf, 0x8e, 0x6f, 0x1f, 0x7b, 0x6c, 0xb3, 0xe3, - 0x82, 0x06, 0xa9, 0x8d, 0xf5, 0xb8, 0x1a, 0x0b, 0xea, 0xc3, 0x63, 0x4d, 0xa5, 0xb2, 0xd8, 0xdc, 0x2c, 0xeb, 0x47, - 0xb5, 0x6a, 0x07, 0xd7, 0x4e, 0x53, 0x2e, 0x9b, 0xd9, 0x20, 0x1c, 0x88, 0x98, 0x40, 0x81, 0x96, 0x56, 0x56, 0x0c, - 0x30, 0xa4, 0x2c, 0x47, 0xf9, 0x14, 0x32, 0x2f, 0x2e, 0x4b, 0x9d, 0xfa, 0xf2, 0x4c, 0x06, 0x1d, 0xf1, 0xd4, 0x93, - 0x8c, 0x15, 0x50, 0xb0, 0x5e, 0xea, 0x25, 0xb4, 0x44, 0x80, 0xf9, 0x0b, 0x95, 0x43, 0x23, 0x2c, 0x90, 0x28, 0x34, - 0xcc, 0x12, 0x65, 0x7c, 0x16, 0x61, 0x0c, 0xda, 0xfe, 0x59, 0x2d, 0xf6, 0x55, 0x28, 0xa3, 0xa3, 0x38, 0xcc, 0x8f, - 0x02, 0xaa, 0x9f, 0x4b, 0x09, 0x36, 0x09, 0x3f, 0x02, 0x1b, 0x55, 0x8e, 0x27, 0x09, 0xc2, 0xe7, 0x71, 0xce, 0xc8, - 0x53, 0xd8, 0x90, 0x30, 0x4b, 0xd3, 0x36, 0x52, 0xed, 0x22, 0x33, 0x08, 0xe5, 0xc2, 0xfc, 0x13, 0xe3, 0xec, 0x22, - 0x0b, 0x97, 0x5a, 0x83, 0xf9, 0xf1, 0xce, 0x04, 0x28, 0xbb, 0xbe, 0xce, 0x84, 0x8f, 0x1b, 0x91, 0xbd, 0xa1, 0x2b, - 0x26, 0x03, 0x85, 0x54, 0xe0, 0x44, 0x64, 0xf1, 0xd0, 0x19, 0x0a, 0x8d, 0x70, 0x40, 0xa7, 0xc8, 0xb9, 0x6b, 0x6c, - 0xfa, 0x7c, 0xa0, 0x7d, 0xa3, 0x34, 0x74, 0x12, 0x10, 0x02, 0x02, 0x77, 0xc3, 0x9a, 0x4a, 0x07, 0x69, 0x90, 0x50, - 0x29, 0xfa, 0x39, 0x80, 0x7f, 0x18, 0x49, 0x0a, 0x80, 0xfd, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05, 0x2e, 0x00, 0xcd, - 0xb5, 0x8f, 0x2b, 0xe1, 0x0b, 0x03, 0x15, 0xa6, 0xa7, 0x59, 0x79, 0x29, 0x94, 0xc8, 0xd3, 0x15, 0x29, 0x6b, 0x24, - 0x93, 0xcf, 0xd1, 0xe1, 0x53, 0xde, 0xf5, 0x5b, 0x89, 0x87, 0x2e, 0x78, 0x0e, 0xcb, 0xaa, 0x9e, 0xdf, 0x84, 0x9c, - 0x9c, 0x6b, 0xd0, 0x15, 0x52, 0xe8, 0x2f, 0x39, 0xc9, 0x7b, 0x6f, 0xfc, 0xaa, 0x96, 0x1a, 0x43, 0xd9, 0xc7, 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, 0x39, 0x58, 0x01, 0xc6, 0xa1, 0x96, 0x4a, 0xa6, 0x91, 0xc4, 0x97, 0x4a, 0x14, - 0x98, 0x72, 0x7f, 0x08, 0x7e, 0x6a, 0xf3, 0xa4, 0xeb, 0xd2, 0xf5, 0xe3, 0x29, 0xa6, 0xf6, 0x10, 0xe8, 0xb1, 0x77, - 0x0f, 0x4c, 0x89, 0xba, 0x0e, 0x2b, 0x88, 0x43, 0xb3, 0x9a, 0x66, 0x01, 0x33, 0xa6, 0x0d, 0x5a, 0xb2, 0x0d, 0xb6, - 0x5c, 0x0e, 0xf6, 0x91, 0xd8, 0x9e, 0xd5, 0x0a, 0x08, 0x5d, 0x83, 0x06, 0x86, 0xdc, 0xa5, 0x42, 0x0b, 0xf3, 0x5e, - 0x97, 0x8a, 0x70, 0x7f, 0x0e, 0xb8, 0xb4, 0x82, 0x33, 0x2f, 0xa3, 0x81, 0xf7, 0xe3, 0xd3, 0x04, 0x13, 0x5f, 0x10, - 0x2b, 0xb0, 0x83, 0x83, 0x4e, 0xb3, 0x29, 0x70, 0x2a, 0x2e, 0x52, 0x06, 0xcb, 0x8a, 0x52, 0x1b, 0xfe, 0x48, 0x91, - 0xad, 0xbb, 0x3c, 0xd2, 0x5d, 0x88, 0x05, 0xb0, 0xd3, 0x2f, 0x18, 0xf9, 0x96, 0xf5, 0x32, 0x60, 0x70, 0xae, 0x35, - 0x0e, 0x02, 0xbf, 0xb9, 0x99, 0x1c, 0x95, 0x29, 0xb1, 0x5d, 0x93, 0xd5, 0x05, 0xe4, 0x98, 0x04, 0xd8, 0xc0, 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, 0x3f, 0x70, 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, 0xdb, 0x64, 0x62, 0xda, 0x5b, - 0x42, 0xea, 0x08, 0xbb, 0x87, 0x72, 0x82, 0xbb, 0x9a, 0x67, 0x5f, 0x86, 0xb3, 0xf5, 0xcc, 0xbd, 0x67, 0x30, 0xf7, - 0xd3, 0x90, 0x1b, 0x8c, 0x1e, 0xcb, 0x84, 0x1f, 0x19, 0xfb, 0xc8, 0x55, 0xd5, 0xb3, 0xb3, 0xb0, 0x12, 0x59, 0xe2, - 0xc9, 0x38, 0xea, 0x30, 0x4e, 0x45, 0x6b, 0x82, 0xec, 0xfa, 0xba, 0x30, 0xf7, 0x02, 0x05, 0x4d, 0x3d, 0x5e, 0x8f, - 0xd3, 0x56, 0xec, 0x6c, 0x44, 0x22, 0xf7, 0xde, 0xd4, 0x22, 0x91, 0x15, 0x9f, 0xe3, 0x48, 0x6b, 0x0e, 0x72, 0x9f, - 0x9d, 0x2d, 0x6f, 0x52, 0xa1, 0x5b, 0x34, 0xda, 0xc6, 0x1e, 0xd5, 0x07, 0x92, 0x7a, 0x46, 0x05, 0x56, 0x35, 0xf6, - 0xfd, 0xfb, 0x1d, 0x91, 0x6e, 0xa9, 0x14, 0x1b, 0x2c, 0x2d, 0x8c, 0x66, 0x8c, 0x82, 0x41, 0x49, 0x91, 0x81, 0x1a, - 0xe5, 0x6b, 0x04, 0xc3, 0x1e, 0x35, 0x00, 0xc5, 0xb9, 0xba, 0xfa, 0x69, 0x29, 0xd9, 0x42, 0x40, 0xe2, 0x2e, 0x18, - 0x88, 0x35, 0xc1, 0xcc, 0xc8, 0x27, 0x1f, 0x81, 0xf3, 0x06, 0x0c, 0x1d, 0x03, 0xf0, 0x0b, 0xc4, 0xa6, 0x07, 0x13, - 0xdb, 0x26, 0xa2, 0xe8, 0xb3, 0x81, 0x97, 0x00, 0xec, 0xac, 0x0a, 0x8d, 0x7e, 0xa8, 0x52, 0xc0, 0x90, 0x0d, 0xdc, - 0x80, 0x55, 0x61, 0xb9, 0xbd, 0x97, 0xe0, 0x36, 0xc0, 0xeb, 0x0b, 0xd9, 0x7c, 0x03, 0xf3, 0x04, 0xab, 0xb3, 0x0b, - 0xbf, 0xb2, 0xac, 0xc5, 0xb9, 0xd3, 0x41, 0xa3, 0x5e, 0x51, 0x42, 0xd4, 0xee, 0x63, 0xed, 0x4b, 0x8c, 0xb0, 0x88, - 0xf7, 0x37, 0xf8, 0xae, 0xc7, 0x2d, 0xf7, 0x34, 0x5a, 0x84, 0xe9, 0x32, 0x69, 0x0c, 0x4a, 0xd6, 0xfd, 0x64, 0xc4, - 0xbd, 0xdc, 0x17, 0xb1, 0xe0, 0x0a, 0x47, 0x56, 0x85, 0x14, 0x1b, 0x48, 0xd2, 0xd3, 0x1e, 0x1d, 0xb0, 0x6f, 0x34, - 0x7b, 0x01, 0x65, 0x3e, 0x56, 0xa4, 0x92, 0x90, 0xd2, 0xec, 0x86, 0x48, 0x12, 0xd6, 0x8a, 0x3c, 0x75, 0xde, 0x77, - 0xb4, 0xcf, 0xad, 0x24, 0x82, 0x11, 0x9c, 0x84, 0xe9, 0x58, 0x79, 0xd0, 0x14, 0xe0, 0x2a, 0x3a, 0x62, 0xfa, 0x26, - 0x20, 0xbf, 0x19, 0xc8, 0xed, 0xa5, 0xe4, 0xda, 0x5c, 0xc3, 0xf0, 0x0c, 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, 0x9f, 0x32, 0x47, 0xc8, 0x5c, 0x61, 0x7d, 0xce, 0x9c, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x49, - 0x2d, 0x5e, 0xa7, 0x4d, 0x29, 0x11, 0x93, 0x12, 0xf3, 0x54, 0xa4, 0x62, 0x33, 0x25, 0xee, 0xdc, 0xfa, 0x46, 0x0b, - 0x69, 0xa3, 0x9d, 0x8a, 0x1c, 0x6c, 0x56, 0xc9, 0x7b, 0x02, 0xe3, 0xa5, 0x20, 0x7c, 0x89, 0x8c, 0xb5, 0x98, 0x33, - 0xc7, 0x44, 0xb0, 0x7a, 0x31, 0x15, 0xf9, 0x07, 0x47, 0xa7, 0xd9, 0x1b, 0xf4, 0x20, 0xf5, 0x06, 0x12, 0xb3, 0x26, - 0xbe, 0x0b, 0x69, 0xa8, 0x23, 0x04, 0x2a, 0xa3, 0x5a, 0xa6, 0xe3, 0xc4, 0x2a, 0x7c, 0x23, 0xf8, 0xea, 0xbd, 0x3e, - 0xce, 0x37, 0x9e, 0x1b, 0xab, 0x11, 0xc4, 0xe0, 0x2d, 0xe4, 0x47, 0x9e, 0x14, 0xe1, 0x40, 0xb8, 0x7c, 0x73, 0xb3, - 0x97, 0xef, 0xf2, 0x2a, 0x44, 0x52, 0xc1, 0x18, 0x63, 0x46, 0x31, 0xee, 0x89, 0x9a, 0x5a, 0xcc, 0x61, 0x60, 0xd9, - 0x3a, 0xcc, 0xf1, 0x00, 0x00, 0x5a, 0x9a, 0xd2, 0xab, 0xa6, 0x42, 0xe5, 0x79, 0x2e, 0xe1, 0x53, 0x1d, 0xa2, 0xaa, - 0xc6, 0xef, 0x57, 0x67, 0xa0, 0x10, 0xdc, 0xf7, 0x3a, 0x1e, 0x1e, 0x42, 0xc0, 0x2a, 0x0a, 0x59, 0xa0, 0x37, 0x68, - 0xaf, 0x4a, 0x84, 0x62, 0xe6, 0x64, 0x3d, 0x66, 0x38, 0xa9, 0x60, 0x0b, 0x95, 0xb0, 0x54, 0x5a, 0xe0, 0x57, 0x1b, - 0xa1, 0x79, 0xca, 0xb8, 0xf7, 0xa6, 0xc2, 0x19, 0xf4, 0x07, 0xf3, 0x96, 0x19, 0xf5, 0xfd, 0xd2, 0x89, 0x4c, 0x05, - 0x26, 0x6e, 0x66, 0xa9, 0xfd, 0x7e, 0x59, 0xa5, 0xfd, 0xbc, 0x42, 0xee, 0x73, 0xd2, 0x7c, 0x9d, 0x3b, 0x68, 0x3e, - 0x19, 0xee, 0x57, 0xca, 0x0f, 0x2d, 0x8c, 0x9a, 0xf2, 0xcb, 0xeb, 0xca, 0xaf, 0xf0, 0x54, 0x78, 0xab, 0xdf, 0x45, - 0xa1, 0x8b, 0xfa, 0x1c, 0x0c, 0x21, 0xfd, 0x08, 0xae, 0xa1, 0xc1, 0x83, 0x22, 0x59, 0x2c, 0xd6, 0x2e, 0x88, 0xeb, - 0x63, 0x4e, 0xb5, 0x43, 0x19, 0x63, 0xc4, 0xd3, 0x92, 0x83, 0x24, 0x83, 0x83, 0xf1, 0x1b, 0x18, 0x10, 0x93, 0x92, - 0x90, 0x0e, 0xa1, 0xb3, 0x32, 0x13, 0x51, 0xb9, 0x8b, 0xb7, 0x1b, 0x97, 0x35, 0x85, 0x22, 0xec, 0x04, 0x33, 0x95, - 0x52, 0x41, 0x20, 0x4d, 0xbe, 0x7b, 0x9d, 0x5a, 0x30, 0xb4, 0x70, 0x4d, 0x05, 0xe4, 0xb5, 0x5d, 0x0f, 0x9a, 0x7c, - 0xa4, 0x18, 0xfa, 0x2a, 0x35, 0xe2, 0x65, 0x06, 0x5f, 0xc3, 0xe6, 0xaf, 0x89, 0x92, 0x3c, 0x64, 0x22, 0xf6, 0x0a, - 0x3e, 0x11, 0xb2, 0x29, 0xd8, 0x99, 0x40, 0x3f, 0xb4, 0x2b, 0x7b, 0xe9, 0x6e, 0x51, 0xb9, 0xb4, 0x68, 0x6c, 0x25, - 0x6a, 0xd6, 0xfc, 0x30, 0xde, 0x4c, 0x61, 0x3f, 0x7b, 0x94, 0x40, 0x40, 0x9a, 0xca, 0x49, 0xaa, 0x79, 0x0f, 0xd3, - 0x23, 0x00, 0x09, 0x76, 0x3f, 0x81, 0x85, 0x7e, 0x53, 0x62, 0x82, 0x45, 0xd5, 0xd8, 0x6d, 0x06, 0x5a, 0x73, 0x46, - 0x9a, 0x6f, 0x86, 0x5a, 0x7b, 0x53, 0x59, 0xcf, 0x98, 0x1d, 0x60, 0xdb, 0xee, 0x66, 0x71, 0x98, 0x6e, 0x76, 0x8e, - 0x0c, 0xc1, 0x85, 0xc7, 0xff, 0x49, 0x89, 0x69, 0x20, 0xb9, 0xd4, 0x8d, 0x9f, 0x50, 0x87, 0xe1, 0xff, 0x16, 0xa4, - 0x80, 0x07, 0xb5, 0xd5, 0x58, 0x72, 0xee, 0x15, 0x47, 0xc9, 0x65, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0x8c, - 0x89, 0x62, 0x9e, 0x13, 0x00, 0xa3, 0xd8, 0xfc, 0x39, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4c, 0xed, 0xf6, 0x7d, 0x3f, - 0xca, 0xcf, 0xe8, 0x48, 0x45, 0x65, 0x73, 0x12, 0xf3, 0x6f, 0x0b, 0x30, 0xcd, 0x89, 0x0f, 0xf5, 0x5c, 0x47, 0xa1, - 0x00, 0x5f, 0xd9, 0x50, 0x6a, 0xb6, 0xd7, 0xbf, 0x75, 0xb6, 0x87, 0x92, 0x28, 0x82, 0x05, 0x1a, 0x74, 0x59, 0x83, - 0x2f, 0x60, 0x19, 0xdc, 0x91, 0x7e, 0x0a, 0xbe, 0x9f, 0xd6, 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, - 0x67, 0xae, 0xe1, 0xc6, 0xf7, 0x1a, 0xf9, 0xe3, 0x7b, 0x2f, 0x41, 0xb7, 0x13, 0x69, 0xef, 0xdf, 0xcf, 0xef, 0x91, - 0x85, 0x86, 0xf7, 0xc2, 0x66, 0xd0, 0x16, 0xe9, 0x92, 0xab, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, - 0x30, 0x03, 0x0c, 0xc1, 0x63, 0xa7, 0x32, 0xf9, 0x0c, 0x1b, 0x4d, 0xb1, 0x6b, 0x2e, 0x0c, 0x3e, 0x50, 0x95, 0x85, - 0xe4, 0xc5, 0x3a, 0xd9, 0x5e, 0x9c, 0xc3, 0xf3, 0xeb, 0xb8, 0x00, 0xea, 0x00, 0xfa, 0x15, 0x95, 0xc5, 0x06, 0x72, - 0x71, 0x53, 0xd6, 0x7a, 0x45, 0xa3, 0xd1, 0x8d, 0x5d, 0x58, 0x5d, 0x81, 0x4f, 0xa2, 0x74, 0x94, 0x88, 0x49, 0xcc, - 0xa4, 0xca, 0x15, 0xb9, 0x36, 0xba, 0x97, 0xb6, 0x68, 0x5e, 0x0a, 0x09, 0x5e, 0x11, 0xb8, 0x21, 0xf4, 0x95, 0xbe, - 0x5c, 0x6d, 0xa0, 0xe0, 0x51, 0x7b, 0x73, 0x11, 0x4c, 0x4c, 0x3c, 0x66, 0x48, 0x4d, 0xbf, 0x0e, 0xa7, 0x56, 0x16, - 0x4b, 0x0e, 0xbf, 0xce, 0x19, 0x6b, 0x28, 0x00, 0xe2, 0x93, 0x47, 0xeb, 0xdd, 0xa4, 0x37, 0x4a, 0x3b, 0x28, 0x8d, - 0x10, 0xdf, 0x55, 0xf8, 0xba, 0x0b, 0xc5, 0x57, 0xae, 0xba, 0xf7, 0x75, 0xcc, 0x8c, 0x0b, 0x46, 0x2f, 0xf9, 0x34, - 0x69, 0x5c, 0xbb, 0xa1, 0xbb, 0x3a, 0xdf, 0x7b, 0x5f, 0xca, 0xbc, 0x85, 0x63, 0x60, 0x93, 0x63, 0xe6, 0xbc, 0xf4, - 0xde, 0x1a, 0x27, 0xca, 0x3f, 0x98, 0x47, 0xbc, 0x72, 0x98, 0x55, 0x27, 0xc9, 0x3f, 0x0c, 0x7e, 0x08, 0xd6, 0xb7, - 0x34, 0x4e, 0x90, 0xbb, 0xea, 0x04, 0x99, 0x28, 0xb7, 0xa1, 0x37, 0xdc, 0xde, 0x5d, 0x05, 0x82, 0x38, 0x15, 0xd3, - 0x47, 0xe5, 0xb8, 0x7e, 0xb4, 0x40, 0xa5, 0x22, 0xe2, 0x73, 0x95, 0xbb, 0xb2, 0x36, 0x35, 0xd4, 0xe3, 0x3a, 0x99, - 0x85, 0xa6, 0x59, 0x91, 0x4b, 0xd9, 0xf4, 0x18, 0x99, 0x66, 0xa7, 0xda, 0xfc, 0xee, 0xda, 0x43, 0x3a, 0x86, 0xe6, - 0x62, 0xad, 0x16, 0xdc, 0xef, 0x2a, 0x0a, 0xef, 0x7a, 0xb1, 0x91, 0xca, 0x50, 0xb3, 0x1e, 0x45, 0x1f, 0xc7, 0x6d, - 0xe6, 0xf2, 0x28, 0xfb, 0xb3, 0x06, 0x80, 0xe9, 0x08, 0x8b, 0xee, 0xa6, 0x67, 0xec, 0x09, 0xf4, 0xf4, 0x44, 0x06, - 0x89, 0xde, 0xe8, 0x7c, 0xd5, 0x2a, 0xb1, 0x74, 0x05, 0x81, 0xdd, 0x1b, 0x32, 0x56, 0x25, 0xed, 0x96, 0xeb, 0x97, - 0xf3, 0x7c, 0x9e, 0xf3, 0xa5, 0x3c, 0x9f, 0x9a, 0x45, 0x77, 0xaf, 0xed, 0xde, 0x9c, 0x1a, 0x2a, 0xe6, 0x5a, 0xdd, - 0xe4, 0x37, 0x4c, 0xd7, 0xc1, 0x50, 0x8b, 0x20, 0xb3, 0xda, 0x55, 0x2f, 0xca, 0x72, 0xa3, 0x9e, 0xc9, 0xb1, 0x21, - 0x7c, 0x53, 0xe9, 0x0e, 0xd1, 0x0d, 0x53, 0x35, 0xd3, 0xf7, 0x8d, 0x6d, 0x21, 0xdb, 0xbc, 0xbc, 0x1a, 0xe5, 0x40, - 0x69, 0xb9, 0xbf, 0x4c, 0x18, 0xbe, 0xbf, 0xbe, 0xfe, 0x5e, 0xc8, 0xa9, 0xaa, 0xa3, 0xb7, 0x78, 0xad, 0x7b, 0x06, - 0x1b, 0xa5, 0x72, 0x22, 0x2e, 0xd8, 0xea, 0xc1, 0x9b, 0xbb, 0x57, 0xc0, 0x72, 0x01, 0xd8, 0x5d, 0x30, 0xa7, 0x31, - 0x54, 0xb5, 0x81, 0xbf, 0x5c, 0x3d, 0xd8, 0xaa, 0x3d, 0xfc, 0xe5, 0xe0, 0xcb, 0xe0, 0xc6, 0xc6, 0xc6, 0x36, 0xde, - 0xae, 0x25, 0x82, 0xbc, 0xc1, 0x03, 0x7d, 0xbc, 0xfa, 0x28, 0x68, 0xb9, 0x4a, 0x6c, 0x0f, 0x1c, 0x0a, 0x5b, 0x83, - 0x7c, 0x93, 0x32, 0x69, 0x38, 0x2f, 0x78, 0x36, 0x95, 0x33, 0x14, 0xf2, 0x9a, 0x8f, 0x83, 0xb6, 0x23, 0xfc, 0x1b, - 0x38, 0xb5, 0xe3, 0xe5, 0xc5, 0x27, 0xe8, 0x03, 0x9e, 0xae, 0x94, 0xa6, 0x22, 0x4e, 0x29, 0xb7, 0xe8, 0x72, 0x9d, - 0x07, 0x23, 0xc5, 0xc5, 0x04, 0x95, 0x8e, 0xbb, 0xb8, 0x71, 0x36, 0x72, 0xfa, 0x4b, 0xbc, 0xba, 0x48, 0x97, 0x8f, - 0x44, 0xb6, 0x6a, 0xe9, 0xfd, 0xac, 0x4f, 0xb7, 0xed, 0x29, 0xe3, 0x93, 0x6c, 0x44, 0x07, 0x33, 0x3e, 0x4e, 0x84, - 0xd7, 0x27, 0x46, 0xfa, 0x6e, 0x11, 0x98, 0x6e, 0x8e, 0x4d, 0x7e, 0x38, 0x5e, 0x6f, 0x36, 0x6b, 0xdc, 0xc1, 0x3b, - 0xe7, 0x93, 0xb3, 0x28, 0x31, 0xa2, 0xb2, 0xd0, 0xf0, 0x80, 0x56, 0x88, 0x9b, 0xf7, 0x4c, 0x60, 0x5c, 0x76, 0x45, - 0x52, 0xdb, 0x0d, 0x04, 0x2e, 0xf6, 0x38, 0x66, 0xc9, 0xc8, 0xf6, 0xa0, 0x3c, 0xd0, 0x17, 0xa3, 0xe9, 0x16, 0x30, - 0x2d, 0xaf, 0x9d, 0x5d, 0xa4, 0xb6, 0x57, 0x4d, 0x15, 0xc0, 0x2c, 0x59, 0x1e, 0x9f, 0x21, 0xeb, 0x7e, 0x0d, 0x5d, - 0xc4, 0x80, 0xb1, 0x71, 0x65, 0xce, 0x5d, 0xac, 0x5a, 0x11, 0xdf, 0x68, 0x22, 0x4d, 0xea, 0x43, 0xea, 0x7b, 0x14, - 0xd6, 0xea, 0x2a, 0x07, 0x09, 0xdc, 0x23, 0xef, 0x8e, 0xb8, 0xf4, 0xf4, 0x99, 0xc5, 0xb8, 0x4a, 0xdf, 0x52, 0xd7, - 0xe2, 0x9a, 0x61, 0xaf, 0x78, 0x00, 0xf6, 0x07, 0xc6, 0x2d, 0x62, 0x11, 0x6f, 0xe7, 0xb5, 0x14, 0xd6, 0xc6, 0x1c, - 0x68, 0x6e, 0xb8, 0xc1, 0xcf, 0xac, 0x5a, 0x33, 0x30, 0xc3, 0x8c, 0x33, 0x92, 0x0f, 0xc6, 0xbd, 0xaa, 0xb1, 0x23, - 0x57, 0x01, 0x44, 0xdf, 0x82, 0x2e, 0xc9, 0xe1, 0x95, 0x2c, 0x57, 0x9d, 0x21, 0xbf, 0x82, 0x75, 0xd6, 0x8b, 0x13, - 0x30, 0x93, 0xa6, 0xbc, 0xc4, 0xc4, 0x14, 0x71, 0xb9, 0x59, 0xc6, 0x3c, 0x4d, 0x9f, 0x45, 0x3b, 0x38, 0xb9, 0x91, - 0xc0, 0x11, 0xfb, 0xc6, 0x32, 0x34, 0x13, 0x36, 0x62, 0x22, 0x8d, 0x4a, 0x29, 0xe1, 0x03, 0xb9, 0xd4, 0x92, 0xbf, - 0xcc, 0xe5, 0xd5, 0x97, 0xdb, 0x04, 0x07, 0xe4, 0x35, 0xb0, 0x1c, 0x1a, 0xc7, 0x2d, 0x03, 0x89, 0x58, 0x0c, 0x88, - 0x51, 0xab, 0x72, 0x39, 0x19, 0xd5, 0xc9, 0x7c, 0x85, 0x5c, 0xa8, 0xc8, 0x83, 0x5b, 0x02, 0x25, 0x7f, 0x8e, 0xa9, - 0x83, 0x59, 0xa9, 0xdd, 0xb4, 0xd8, 0x24, 0x79, 0xcf, 0x0c, 0x48, 0xae, 0xbe, 0x86, 0x87, 0xc6, 0x2f, 0x5e, 0x99, - 0x53, 0xc2, 0x17, 0x65, 0x2c, 0x2d, 0x8d, 0xb9, 0xf4, 0xdf, 0xca, 0xfb, 0xb4, 0x12, 0xb0, 0x57, 0x20, 0xa6, 0x0c, - 0x5c, 0x62, 0xe3, 0x82, 0xa4, 0xbc, 0x96, 0xa7, 0xec, 0xbe, 0x86, 0xf2, 0x5d, 0x32, 0xe9, 0x2a, 0x95, 0xb5, 0xc6, - 0xaa, 0xfb, 0x79, 0xce, 0xf2, 0xab, 0x7d, 0x86, 0xb9, 0xc9, 0x68, 0x90, 0x2d, 0x99, 0xd9, 0x94, 0x5f, 0xed, 0xdd, - 0xf8, 0x95, 0x87, 0x92, 0x0e, 0xd5, 0x2a, 0xdd, 0xbc, 0x74, 0xc3, 0x31, 0x6e, 0xdc, 0x70, 0x04, 0xb0, 0x31, 0xec, - 0x54, 0x91, 0x5a, 0xe7, 0xbf, 0x2f, 0x87, 0x9f, 0x68, 0xaf, 0x1d, 0xe9, 0x5d, 0x77, 0xb4, 0x32, 0x3d, 0xfd, 0x06, - 0x54, 0x8d, 0x2c, 0xa1, 0x9b, 0x50, 0xc5, 0x64, 0x24, 0x4a, 0x4c, 0x57, 0x29, 0x8f, 0xfa, 0x1a, 0x71, 0x0e, 0xe2, - 0x86, 0xf2, 0x17, 0xff, 0x14, 0x5e, 0x9d, 0x04, 0x68, 0x44, 0x2d, 0xc6, 0x59, 0xca, 0x5b, 0xe3, 0x68, 0x1a, 0x27, - 0x57, 0xc1, 0x3c, 0x6e, 0x4d, 0xb3, 0x34, 0x2b, 0x66, 0xc0, 0x95, 0x5e, 0x71, 0x05, 0x36, 0xfc, 0xb4, 0x35, 0x8f, - 0xbd, 0x97, 0x2c, 0x39, 0x67, 0x3c, 0x1e, 0x46, 0x9e, 0xbd, 0x97, 0x83, 0x78, 0xb0, 0xde, 0x46, 0x79, 0x9e, 0x5d, - 0xd8, 0xde, 0x87, 0xec, 0x14, 0x98, 0xd6, 0x7b, 0x77, 0x79, 0x75, 0xc6, 0x52, 0xef, 0xe3, 0xe9, 0x3c, 0xe5, 0x73, - 0xaf, 0x88, 0xd2, 0xa2, 0x55, 0xb0, 0x3c, 0x1e, 0x83, 0x9a, 0x48, 0xb2, 0xbc, 0x85, 0xf9, 0xcf, 0x53, 0x16, 0x24, - 0xf1, 0xd9, 0x84, 0x5b, 0xa3, 0x28, 0xff, 0xd4, 0x6b, 0xb5, 0x66, 0x79, 0x3c, 0x8d, 0xf2, 0xab, 0x16, 0xb5, 0x08, - 0x3e, 0x6b, 0x6f, 0x47, 0x9f, 0x8f, 0x1f, 0xf6, 0x78, 0x0e, 0x7d, 0x63, 0xa4, 0x62, 0x00, 0xc2, 0xc7, 0xda, 0xde, - 0x69, 0x4f, 0x8b, 0x7b, 0xe2, 0x44, 0x29, 0x4a, 0x79, 0x79, 0xe2, 0x5d, 0x31, 0x80, 0xdb, 0x3f, 0xe5, 0xa9, 0x07, - 0xbe, 0x1c, 0xcf, 0xd2, 0xc5, 0x70, 0x9e, 0x17, 0x30, 0xc0, 0x2c, 0x8b, 0x53, 0xce, 0xf2, 0xde, 0x69, 0x96, 0x03, - 0xd9, 0x5a, 0x79, 0x34, 0x8a, 0xe7, 0x45, 0xf0, 0x70, 0x76, 0xd9, 0x43, 0x5b, 0xe1, 0x2c, 0xcf, 0xe6, 0xe9, 0x48, - 0xce, 0x15, 0xa7, 0xb0, 0x31, 0x62, 0x6e, 0x56, 0xd0, 0x97, 0x50, 0x00, 0xbe, 0x94, 0x45, 0x79, 0xeb, 0x0c, 0x3b, - 0xa3, 0xa1, 0xdf, 0x1e, 0xb1, 0x33, 0x2f, 0x3f, 0x3b, 0x8d, 0x9c, 0x4e, 0xf7, 0xb1, 0xa7, 0xfe, 0xf3, 0x77, 0x5c, - 0x30, 0xdc, 0x57, 0x16, 0x77, 0xda, 0xed, 0xbf, 0x71, 0x7b, 0x8d, 0x59, 0x08, 0xa0, 0xa0, 0x33, 0xbb, 0xb4, 0x8a, - 0x2c, 0x81, 0xf5, 0x59, 0xd5, 0xb3, 0x37, 0x03, 0xbf, 0x29, 0x4e, 0xcf, 0x82, 0xee, 0xec, 0xb2, 0x44, 0xec, 0x02, - 0x91, 0x90, 0x29, 0x91, 0x94, 0x6f, 0x8b, 0xdf, 0x0a, 0xf1, 0x93, 0xd5, 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd5, 0x5b, - 0x23, 0xd8, 0x07, 0x44, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x80, 0x13, 0x98, 0x2b, 0x38, 0xe8, 0xe5, 0x37, 0x83, 0xd1, - 0x5d, 0x0d, 0xc6, 0x93, 0xdb, 0xc0, 0xc8, 0xd3, 0xd1, 0xa2, 0xbe, 0xae, 0x1d, 0x70, 0x4e, 0x7b, 0x13, 0x86, 0xfc, - 0x14, 0x74, 0xf1, 0xf9, 0x22, 0x1e, 0xf1, 0x89, 0x78, 0x24, 0x76, 0xbe, 0x10, 0x75, 0x3b, 0xed, 0xb6, 0x78, 0x2f, - 0x40, 0xa1, 0x05, 0x1d, 0x1f, 0x1b, 0x00, 0x13, 0x7d, 0xb1, 0xee, 0x23, 0x36, 0xdf, 0xdd, 0xfa, 0xa5, 0x1a, 0x8f, - 0xa9, 0xbc, 0x41, 0xa1, 0x22, 0xd4, 0x37, 0x5b, 0x30, 0xe3, 0x2d, 0xef, 0x77, 0xf4, 0x41, 0xd5, 0xe0, 0x3b, 0x46, - 0x5a, 0x2f, 0xe0, 0x9e, 0x99, 0x0b, 0xd4, 0x4b, 0xfb, 0x18, 0x92, 0x6a, 0xb5, 0x5c, 0xd0, 0x1b, 0x0c, 0x43, 0x48, - 0x74, 0x20, 0xe8, 0xe4, 0x83, 0x82, 0xbe, 0xa9, 0x91, 0xb9, 0x41, 0xe1, 0x64, 0x2e, 0x6c, 0xf9, 0x4c, 0xcb, 0x75, - 0x50, 0xd2, 0xe0, 0x65, 0x7f, 0xc1, 0x64, 0x03, 0x90, 0xde, 0x95, 0xa4, 0xe5, 0xd5, 0xd1, 0x93, 0x72, 0xf9, 0xb2, - 0x21, 0x51, 0x0e, 0x7c, 0x7d, 0x3e, 0x41, 0xbf, 0x5b, 0x7f, 0x28, 0xc6, 0x48, 0xa9, 0xd9, 0xb2, 0xdd, 0x01, 0xd3, - 0x59, 0x59, 0x98, 0x7d, 0xc6, 0x4a, 0x1c, 0xe5, 0x2b, 0xb0, 0xa4, 0x31, 0xf4, 0xfa, 0x73, 0x28, 0xdc, 0x34, 0xe5, - 0xa4, 0x6d, 0xdc, 0x74, 0xfd, 0x1f, 0x56, 0x3c, 0xa6, 0x6c, 0x67, 0x15, 0x1b, 0x07, 0xd7, 0xe5, 0x78, 0x28, 0xae, - 0x1d, 0x16, 0x98, 0x2d, 0xfe, 0xdb, 0x3d, 0x09, 0x47, 0xa3, 0x55, 0x64, 0xf3, 0x7c, 0x48, 0xa1, 0xc1, 0xe5, 0x10, - 0x83, 0x4d, 0x1a, 0xde, 0xf6, 0x98, 0x56, 0x2c, 0xe8, 0x77, 0xd7, 0xbe, 0xaa, 0xc0, 0xe9, 0xd4, 0x45, 0x5c, 0x6a, - 0x90, 0x61, 0x15, 0x05, 0x36, 0xea, 0xca, 0x11, 0x25, 0xd8, 0xd1, 0x85, 0x4f, 0x7f, 0x9e, 0xc6, 0x20, 0x5a, 0x8f, - 0xe3, 0x11, 0x5d, 0x74, 0x89, 0x47, 0x74, 0xf2, 0xd1, 0xa2, 0x4c, 0x27, 0x0c, 0xa5, 0x43, 0x81, 0x24, 0x38, 0x3e, - 0xcb, 0xcc, 0x19, 0xbb, 0x65, 0xe3, 0xe9, 0x85, 0xa1, 0x9b, 0x47, 0xd9, 0x34, 0x8a, 0xd3, 0x00, 0x3f, 0x48, 0xe2, - 0xe9, 0x11, 0x03, 0xec, 0xe2, 0xc1, 0x5f, 0x45, 0xfb, 0x8e, 0xeb, 0xff, 0x04, 0x82, 0x8b, 0xfa, 0x97, 0xd2, 0xf1, - 0xd3, 0x70, 0xa9, 0x73, 0xe5, 0x7a, 0x29, 0x08, 0x3b, 0xae, 0x8c, 0x64, 0x46, 0x81, 0x95, 0x5d, 0x4e, 0x7f, 0x06, - 0xad, 0x4e, 0xa0, 0xae, 0xfe, 0x9b, 0x2b, 0x60, 0x5c, 0x0c, 0xa8, 0x56, 0x85, 0x4a, 0xe4, 0x1b, 0xcc, 0x21, 0xf9, - 0xf3, 0xfa, 0x5a, 0x7f, 0x3c, 0xa0, 0x71, 0x81, 0x56, 0xa4, 0xdf, 0xc8, 0x4b, 0x98, 0x84, 0x85, 0x7e, 0x16, 0x98, - 0x56, 0xef, 0x1a, 0x5b, 0x4f, 0x6e, 0x25, 0x8c, 0x39, 0x9d, 0xa5, 0x4e, 0x0d, 0x0d, 0x3a, 0xbe, 0x58, 0x33, 0x95, - 0x5b, 0x46, 0xc4, 0xdc, 0x4f, 0x49, 0xe6, 0xd4, 0xaf, 0x3f, 0xe1, 0x55, 0xa7, 0x7a, 0xd6, 0x96, 0x62, 0xef, 0xe1, - 0xc9, 0xae, 0x10, 0x52, 0x16, 0xb1, 0x6e, 0x68, 0x83, 0xd4, 0xb0, 0xad, 0x3f, 0x0e, 0x81, 0xce, 0x9f, 0x42, 0x7b, - 0x63, 0xe1, 0xa8, 0xbb, 0x00, 0x39, 0xcc, 0xb5, 0x27, 0x14, 0x35, 0x7d, 0x44, 0xc0, 0xee, 0x6f, 0x2c, 0x78, 0xb9, - 0xbb, 0x25, 0x7a, 0xf7, 0x4f, 0xca, 0x82, 0x74, 0xaa, 0x19, 0xfb, 0xab, 0xa6, 0x10, 0x75, 0x30, 0x2c, 0x65, 0x1c, - 0xe3, 0xb8, 0xb9, 0xb6, 0x13, 0x45, 0x90, 0x5b, 0x32, 0x6e, 0x81, 0x19, 0x56, 0x51, 0x0e, 0x62, 0x44, 0xe7, 0xd0, - 0x14, 0x22, 0x6d, 0xa4, 0xb7, 0x0c, 0xc5, 0x09, 0x42, 0x30, 0xd8, 0x58, 0xc4, 0x65, 0xb8, 0xb1, 0x60, 0xe9, 0x30, - 0x1b, 0xb1, 0x8f, 0x1f, 0x5e, 0xe1, 0x35, 0x89, 0x2c, 0x45, 0x79, 0x9a, 0xb9, 0xe5, 0x09, 0x18, 0x58, 0x08, 0x69, - 0xae, 0xbe, 0x52, 0x03, 0xc0, 0x88, 0x58, 0x91, 0x45, 0xa3, 0x22, 0x28, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x10, 0x1c, - 0x59, 0x2c, 0x00, 0x13, 0x94, 0x7a, 0x31, 0xc0, 0x4f, 0xb4, 0xee, 0xc3, 0x40, 0xbb, 0x5b, 0xa2, 0x11, 0xe0, 0x9a, - 0x23, 0x1a, 0x15, 0xaa, 0x98, 0x55, 0x64, 0xa2, 0x3b, 0x8a, 0xcf, 0x35, 0x39, 0x29, 0xc5, 0xba, 0xbf, 0x9b, 0x44, - 0xa7, 0x2c, 0x81, 0x21, 0x81, 0xaf, 0xda, 0x30, 0x92, 0x78, 0xb5, 0x76, 0xe3, 0x74, 0x36, 0x97, 0x5f, 0x0b, 0x83, - 0x89, 0x3b, 0x78, 0x80, 0x8b, 0x97, 0x19, 0x06, 0xea, 0x44, 0x32, 0x90, 0x03, 0x00, 0x88, 0x74, 0x18, 0x82, 0xd0, - 0x55, 0xac, 0x02, 0xa5, 0xf1, 0x68, 0xb9, 0x0c, 0xf6, 0xf7, 0x0c, 0x4b, 0x53, 0x78, 0x9e, 0xc6, 0x29, 0x3e, 0x16, - 0xf8, 0x18, 0x5d, 0xe2, 0x63, 0x06, 0x8f, 0x1a, 0xf7, 0xbc, 0xb4, 0xff, 0xaa, 0xab, 0x92, 0xc9, 0x15, 0xb0, 0x34, - 0x01, 0xb2, 0xeb, 0x6b, 0x50, 0x5b, 0x9a, 0x04, 0xbb, 0x5b, 0x40, 0x2c, 0xe4, 0x1e, 0xf1, 0xed, 0x18, 0x66, 0x92, - 0x91, 0x15, 0xb3, 0x96, 0x28, 0xb7, 0xc8, 0x38, 0x08, 0xc1, 0x77, 0xcc, 0x9d, 0x86, 0x0d, 0xe4, 0xc9, 0x2c, 0x99, - 0x67, 0xf8, 0xe2, 0xda, 0x96, 0xf8, 0xb8, 0x87, 0x20, 0x0a, 0x3d, 0x22, 0x86, 0xba, 0x8c, 0xcb, 0xcf, 0xf6, 0xc4, - 0xa1, 0x8d, 0xb3, 0x80, 0x19, 0x8a, 0xca, 0x8c, 0x47, 0x71, 0x22, 0x1a, 0xaf, 0xc0, 0xa7, 0x91, 0xee, 0x48, 0xe8, - 0xec, 0x6e, 0x55, 0xb0, 0x01, 0xf0, 0x4a, 0x22, 0x88, 0x54, 0x4e, 0x5b, 0x94, 0x53, 0x0a, 0x80, 0xdc, 0xe6, 0xd5, - 0x27, 0x9d, 0x80, 0x29, 0xc0, 0x88, 0x1e, 0x1d, 0xd3, 0x6c, 0x83, 0x21, 0x12, 0x0b, 0x67, 0x6c, 0x6c, 0x5d, 0xfb, - 0x2f, 0xff, 0xfc, 0x0f, 0xb6, 0x27, 0x40, 0xcc, 0xc6, 0x63, 0x90, 0x72, 0xd6, 0xba, 0x86, 0xff, 0xeb, 0x1f, 0xff, - 0xef, 0xff, 0xf9, 0xaf, 0xba, 0x6d, 0x0a, 0x4d, 0x4f, 0x02, 0x71, 0xb4, 0xa0, 0x49, 0x4a, 0x29, 0x9e, 0xf6, 0x38, - 0x4a, 0x57, 0x80, 0x74, 0x08, 0x54, 0x9a, 0x31, 0x36, 0xf2, 0x6c, 0x0b, 0x34, 0x81, 0x78, 0x3e, 0x4e, 0xd8, 0x39, - 0x93, 0x1f, 0x96, 0xd1, 0x83, 0xe8, 0xca, 0x21, 0x58, 0x30, 0x5c, 0xde, 0x79, 0x95, 0xdb, 0x40, 0xd1, 0x52, 0x52, - 0xbc, 0x4e, 0x30, 0xcf, 0x36, 0x06, 0x6d, 0xce, 0xd1, 0xae, 0x0f, 0xeb, 0x81, 0x4a, 0xb5, 0x6d, 0x01, 0x2f, 0x99, - 0xbd, 0xab, 0x20, 0x5e, 0x82, 0xeb, 0x34, 0xc7, 0xa6, 0x29, 0x2b, 0x8a, 0x55, 0x60, 0x01, 0x4d, 0x3c, 0xbb, 0x6a, - 0x62, 0xd7, 0x3a, 0x00, 0x00, 0xdd, 0x9d, 0x1d, 0x31, 0x2d, 0x54, 0xb0, 0xf1, 0x18, 0x36, 0x38, 0xea, 0xb6, 0x84, - 0xe3, 0xb1, 0x45, 0xd8, 0xb7, 0xdf, 0x82, 0x2c, 0xb1, 0xc1, 0x3f, 0x74, 0xf5, 0x01, 0x34, 0x4d, 0xaf, 0x84, 0x9d, - 0x31, 0x87, 0xe8, 0x6c, 0x0c, 0xa3, 0x9f, 0x0c, 0xa4, 0xb2, 0xe1, 0xa7, 0x55, 0x8c, 0xb1, 0x96, 0x11, 0xfe, 0xfd, - 0x5f, 0xfe, 0xf1, 0xbf, 0xc1, 0xd8, 0xd4, 0x6f, 0x3d, 0x17, 0x40, 0xab, 0xff, 0x09, 0xad, 0xe6, 0xe9, 0x2d, 0xed, - 0xfe, 0xf2, 0xf7, 0xff, 0x1d, 0x9a, 0xd1, 0x45, 0x29, 0xe0, 0x13, 0x82, 0x68, 0x88, 0xb6, 0xe9, 0xaf, 0x02, 0xa9, - 0x36, 0xc8, 0xda, 0x99, 0xfe, 0x09, 0xc1, 0x2e, 0x78, 0x36, 0xbb, 0x11, 0x1c, 0x84, 0x7a, 0x98, 0x64, 0x05, 0xd3, - 0xf0, 0x08, 0x7d, 0xf2, 0xeb, 0x00, 0xa2, 0xb9, 0x66, 0xb0, 0x6b, 0x0b, 0x4b, 0x8f, 0x23, 0x56, 0x68, 0xd5, 0x38, - 0x8d, 0x05, 0x2c, 0x18, 0x27, 0x74, 0x28, 0xdc, 0x03, 0x4b, 0x26, 0x9e, 0xe0, 0x81, 0x04, 0x9c, 0x5b, 0xff, 0xf8, - 0xda, 0xea, 0xc1, 0x34, 0xc3, 0x89, 0xb1, 0x44, 0x84, 0x4b, 0x8d, 0x00, 0x7f, 0x41, 0x08, 0x1f, 0xeb, 0xe7, 0xe8, - 0x52, 0x3f, 0xa3, 0xa0, 0x16, 0x13, 0x80, 0xbe, 0x9d, 0xa2, 0x31, 0x66, 0xce, 0x20, 0xb2, 0x33, 0x2a, 0xf7, 0xde, - 0x48, 0xf2, 0x11, 0xc2, 0xf8, 0x18, 0x73, 0x61, 0xf1, 0xe6, 0xd3, 0x3c, 0x67, 0xc7, 0x49, 0x76, 0x81, 0x31, 0x43, - 0x24, 0xd2, 0xba, 0xfa, 0xf2, 0xdf, 0xfe, 0xd5, 0xf7, 0xff, 0xed, 0x5f, 0xd7, 0x34, 0x98, 0xc0, 0x9e, 0x00, 0x23, - 0x9f, 0x87, 0x9a, 0xce, 0x0d, 0xb4, 0x56, 0x0f, 0x8a, 0x78, 0xae, 0xae, 0x91, 0x88, 0x63, 0xa9, 0xc4, 0x5b, 0x3e, - 0x12, 0xda, 0x9a, 0x29, 0x6e, 0x9f, 0x05, 0x21, 0x5b, 0x33, 0x0d, 0x56, 0xdd, 0x32, 0xcf, 0x89, 0x1b, 0xdc, 0x40, - 0x97, 0x5f, 0x89, 0xf1, 0x6a, 0x30, 0x6e, 0x85, 0xc0, 0x03, 0x6d, 0x26, 0xf4, 0xdd, 0x33, 0xa1, 0xad, 0x02, 0xb1, - 0x0c, 0x52, 0x77, 0xd5, 0x00, 0xf2, 0xac, 0x03, 0x9a, 0x80, 0x9a, 0xc4, 0x95, 0xad, 0x40, 0xe6, 0xd6, 0x69, 0xde, - 0x7f, 0x83, 0x97, 0x1d, 0x2d, 0xec, 0x8d, 0x96, 0x42, 0x41, 0x86, 0x0d, 0x27, 0xc3, 0x46, 0x6a, 0x54, 0xd3, 0xa6, - 0x40, 0xc7, 0x2f, 0x5b, 0x6d, 0x3b, 0x1c, 0x63, 0xf7, 0x9a, 0xf6, 0xe7, 0x52, 0xfb, 0xc7, 0xd2, 0xde, 0x97, 0xda, - 0x1f, 0x3f, 0x69, 0xd3, 0xd0, 0xfe, 0xf1, 0x5a, 0xed, 0x8f, 0x94, 0x1b, 0xe0, 0xc8, 0xa1, 0xbd, 0x89, 0xd1, 0x2d, - 0xc3, 0xd6, 0xe0, 0x68, 0x67, 0x0d, 0x27, 0x6c, 0xf8, 0x49, 0x9a, 0x59, 0x84, 0x00, 0x86, 0x77, 0xb4, 0x31, 0x29, - 0x30, 0x00, 0x93, 0xe1, 0xa4, 0xd4, 0x9b, 0x1e, 0x1f, 0x8d, 0x09, 0xb8, 0xbb, 0x18, 0x33, 0x14, 0xfd, 0xb0, 0x66, - 0x5f, 0xb1, 0x72, 0x0b, 0xc7, 0x11, 0x1b, 0x46, 0x3c, 0x03, 0x66, 0x5b, 0x38, 0xd8, 0x89, 0xb7, 0x10, 0xc1, 0xc2, - 0xc0, 0x7e, 0xff, 0x6e, 0xff, 0xc0, 0xf6, 0x4e, 0xb3, 0xd1, 0x55, 0x60, 0x83, 0x33, 0x06, 0xd6, 0x94, 0xeb, 0xf3, - 0x09, 0x4b, 0x1d, 0xe5, 0xf9, 0x64, 0x09, 0xb8, 0x9a, 0xd9, 0x99, 0xf8, 0xb6, 0x45, 0xf3, 0xa0, 0x03, 0x08, 0x4b, - 0x1f, 0xbf, 0xec, 0xef, 0x72, 0xf1, 0x5d, 0x58, 0x9e, 0xe3, 0x63, 0x1f, 0x53, 0x3d, 0x76, 0xb7, 0xe0, 0x01, 0x5f, - 0xf6, 0x51, 0xef, 0xd1, 0xdb, 0xc6, 0x62, 0xc9, 0x6d, 0x18, 0xe0, 0x10, 0x93, 0xbe, 0x40, 0xa1, 0xa0, 0x56, 0x27, - 0x01, 0x22, 0x06, 0x8f, 0x30, 0xd6, 0x96, 0x1a, 0x17, 0x21, 0x54, 0xfd, 0xb5, 0xe3, 0x52, 0xd9, 0xad, 0x34, 0xef, - 0x08, 0xcd, 0x52, 0x72, 0x5c, 0xb0, 0xf7, 0x48, 0x97, 0x08, 0x53, 0x87, 0x8a, 0xd6, 0x41, 0xa0, 0x6b, 0x2a, 0x73, - 0x45, 0x74, 0x30, 0x80, 0x21, 0x33, 0x57, 0x00, 0x02, 0x7f, 0x09, 0xed, 0x13, 0xf3, 0xfb, 0x6f, 0xe2, 0x53, 0x4d, - 0x9a, 0x38, 0x87, 0x7f, 0xf2, 0xae, 0x98, 0x77, 0x75, 0x42, 0x2d, 0x55, 0xb0, 0x01, 0xa3, 0x60, 0x18, 0x94, 0x69, - 0xab, 0xa8, 0x12, 0xd8, 0x69, 0x49, 0x34, 0x2b, 0x58, 0xa0, 0x1e, 0x64, 0xdc, 0x01, 0xc3, 0x17, 0xcb, 0x81, 0x1e, - 0xd3, 0x9e, 0x2b, 0xf9, 0x64, 0x61, 0x06, 0x26, 0x1e, 0xb5, 0xdb, 0x3d, 0xbc, 0x54, 0xd1, 0x8a, 0xc0, 0x3a, 0x48, - 0x83, 0x84, 0x8d, 0x79, 0xc9, 0xf1, 0xd6, 0xfe, 0x42, 0x45, 0x82, 0xfc, 0xee, 0x4e, 0xce, 0xa6, 0x96, 0x8f, 0xff, - 0xbf, 0x6d, 0xec, 0x51, 0x90, 0xf2, 0x49, 0x8b, 0xae, 0xf1, 0xe0, 0x15, 0x49, 0x80, 0xc8, 0x7c, 0x5f, 0x18, 0x13, - 0x0d, 0x19, 0x46, 0xc9, 0x4a, 0x9e, 0x83, 0xbc, 0xf7, 0x78, 0x6e, 0xb6, 0x03, 0x39, 0xbd, 0x14, 0x2a, 0x5b, 0x0e, - 0xd6, 0x6c, 0xbb, 0xd2, 0x3f, 0x5a, 0x6e, 0xac, 0x22, 0x5e, 0xf5, 0xb7, 0x25, 0x0a, 0x19, 0xb1, 0xb9, 0x52, 0xa8, - 0xa8, 0x85, 0xe8, 0x61, 0xe2, 0xb4, 0x1c, 0xb5, 0xbb, 0xd5, 0x62, 0x2e, 0x49, 0x5c, 0x1c, 0x92, 0xb8, 0x20, 0xf1, - 0x77, 0xb4, 0x10, 0x73, 0x0f, 0xa3, 0x64, 0xe8, 0x20, 0x00, 0x56, 0xcb, 0x7a, 0x02, 0xd4, 0x74, 0x55, 0xe4, 0xc8, - 0x7f, 0x8c, 0xc4, 0x2d, 0x85, 0xb0, 0x5c, 0x41, 0xa5, 0x93, 0xa3, 0xb2, 0xec, 0x31, 0xe6, 0x1c, 0x7e, 0x90, 0x97, - 0x40, 0xc4, 0xdd, 0x5f, 0xfd, 0xfd, 0xc4, 0x76, 0xe9, 0x1e, 0x79, 0x3f, 0x1b, 0x1f, 0xa5, 0xb3, 0x15, 0xb3, 0xdb, - 0x1e, 0x2c, 0x83, 0xd9, 0x53, 0x7e, 0x42, 0xf2, 0xa6, 0xbe, 0x26, 0x9b, 0x53, 0xff, 0x9f, 0x43, 0x1c, 0xe1, 0x8d, - 0x63, 0xa3, 0x89, 0x4e, 0x23, 0x5f, 0xb5, 0x88, 0x3f, 0x6d, 0xec, 0x2a, 0x8e, 0x40, 0xbe, 0x5e, 0x17, 0xc9, 0xfa, - 0xe6, 0xf6, 0x48, 0x56, 0x71, 0xc7, 0x48, 0xd6, 0x37, 0xbf, 0x73, 0x24, 0xeb, 0x6b, 0x33, 0x92, 0x85, 0x02, 0xfa, - 0xd5, 0xaf, 0x89, 0x36, 0xe5, 0xd9, 0x45, 0x11, 0x76, 0x64, 0xe6, 0x04, 0xc8, 0x3a, 0x0c, 0x3b, 0xfd, 0xf5, 0x23, - 0x4c, 0x30, 0x51, 0x23, 0xbe, 0x44, 0x01, 0x25, 0x91, 0xec, 0x09, 0x6a, 0x45, 0x86, 0x73, 0xda, 0x3a, 0xab, 0xb2, - 0xf5, 0x50, 0x5d, 0x23, 0x03, 0xd7, 0xd7, 0xd5, 0xa1, 0xb6, 0xae, 0x0a, 0xf8, 0x04, 0xf4, 0x1d, 0x58, 0xdd, 0xb1, - 0xbb, 0xa9, 0xd2, 0xf9, 0xcc, 0x11, 0x7a, 0xea, 0x94, 0x46, 0x30, 0xd1, 0xc2, 0xfe, 0x2f, 0x87, 0x9d, 0xde, 0x76, - 0x67, 0x0a, 0xbd, 0x41, 0x81, 0xc3, 0x5b, 0xbb, 0xb7, 0xbd, 0x8d, 0x6f, 0x17, 0xea, 0xad, 0x8b, 0x6f, 0xb1, 0x7a, - 0xdb, 0xc1, 0xb7, 0xa1, 0x7a, 0x7b, 0x84, 0x6f, 0x23, 0xf5, 0xf6, 0x18, 0xdf, 0xce, 0xed, 0xf2, 0x90, 0x6b, 0xe0, - 0x1e, 0x03, 0x5f, 0x91, 0x37, 0x13, 0xa8, 0x32, 0xd8, 0xf4, 0x78, 0xfd, 0x32, 0x3a, 0x0b, 0x62, 0x4f, 0x78, 0x97, - 0x41, 0xee, 0x5d, 0x80, 0xc6, 0x09, 0x28, 0xdb, 0xf0, 0x39, 0x7e, 0x87, 0x03, 0x9c, 0xa4, 0x83, 0x78, 0xca, 0xd4, - 0x07, 0x89, 0x15, 0xd6, 0x60, 0xc0, 0x1e, 0xb6, 0x8f, 0xca, 0x9e, 0x5e, 0x27, 0x11, 0xcf, 0x52, 0xd9, 0x1c, 0xb4, - 0x72, 0x55, 0x9d, 0x98, 0xae, 0xa5, 0x57, 0x78, 0x8d, 0xfe, 0x32, 0xe2, 0x11, 0x63, 0x30, 0xcc, 0x5a, 0x97, 0xe0, - 0xc1, 0xae, 0xd4, 0x69, 0x08, 0x91, 0xd6, 0x69, 0x84, 0x93, 0x7e, 0x3b, 0x88, 0xce, 0xf4, 0xf3, 0x1b, 0xb0, 0xb4, - 0xa3, 0x33, 0xd9, 0x72, 0xbd, 0x0e, 0x23, 0x10, 0x4d, 0xfd, 0xa5, 0x80, 0x20, 0x53, 0x0c, 0x96, 0x06, 0x3d, 0x69, - 0xa9, 0xbf, 0x90, 0x3a, 0x75, 0x8d, 0x46, 0xd3, 0xd7, 0x8b, 0x80, 0xa2, 0x55, 0xc1, 0x2e, 0x18, 0xfc, 0x54, 0x2a, - 0x28, 0x0c, 0x15, 0x58, 0x20, 0xaa, 0xd7, 0xa8, 0x32, 0x1d, 0x6c, 0x58, 0xab, 0xd0, 0x2c, 0xa5, 0xcb, 0xcc, 0xd3, - 0x1d, 0x7d, 0xb4, 0xb3, 0x2c, 0x5e, 0x3f, 0xeb, 0x0c, 0xf1, 0x1f, 0x29, 0xbc, 0x3f, 0x1b, 0x8f, 0xc7, 0x37, 0xea, - 0xb6, 0xcf, 0x46, 0x63, 0xd6, 0x65, 0x3b, 0x3d, 0x8c, 0xfc, 0xb7, 0xa4, 0x38, 0xed, 0x94, 0x44, 0xbb, 0xc5, 0xdd, - 0x1a, 0xa3, 0xe4, 0x05, 0x75, 0x77, 0x77, 0x25, 0x58, 0x02, 0x55, 0x16, 0x20, 0xfc, 0xcf, 0xe2, 0x34, 0x68, 0x97, - 0xfe, 0xb9, 0xd4, 0x1a, 0x9f, 0x3d, 0x79, 0xf2, 0xa4, 0xf4, 0x47, 0xea, 0xad, 0x3d, 0x1a, 0x95, 0xfe, 0x70, 0xa1, - 0xd1, 0x68, 0xb7, 0xc7, 0xe3, 0xd2, 0x8f, 0x55, 0xc1, 0x76, 0x77, 0x38, 0xda, 0xee, 0x96, 0xfe, 0x85, 0xd1, 0xa2, - 0xf4, 0x99, 0x7c, 0xcb, 0xd9, 0xa8, 0x76, 0x7c, 0xf0, 0xb8, 0x0d, 0x95, 0x82, 0xd1, 0x16, 0xe8, 0x5d, 0x8a, 0xc7, - 0x20, 0x9a, 0xf3, 0x0c, 0x0c, 0xbb, 0xb2, 0x57, 0x80, 0x7c, 0x1e, 0x4b, 0x09, 0x2f, 0xbe, 0xf7, 0x8b, 0x52, 0xfd, - 0x95, 0x29, 0xd5, 0x91, 0x99, 0x49, 0x9a, 0x17, 0xa4, 0x0d, 0x9a, 0xd5, 0xc8, 0x59, 0x54, 0xfd, 0x2a, 0x2c, 0x2a, - 0x61, 0x8f, 0xd2, 0x06, 0x5b, 0x0a, 0x19, 0xff, 0xc3, 0x3a, 0x19, 0xff, 0xfd, 0xed, 0x32, 0xfe, 0xf4, 0x6e, 0x22, - 0xfe, 0xfb, 0xdf, 0x59, 0xc4, 0xff, 0x60, 0x8a, 0x78, 0x21, 0xc4, 0xf6, 0xc0, 0x74, 0x26, 0x9b, 0xf9, 0x34, 0xbb, - 0x6c, 0xe1, 0x96, 0xc8, 0x6d, 0x92, 0x9e, 0xd3, 0x3b, 0x09, 0xff, 0x15, 0xf9, 0x60, 0x6a, 0x30, 0xe3, 0xe3, 0xc1, - 0x3c, 0x3b, 0x3b, 0x4b, 0x98, 0x92, 0xf1, 0x46, 0x05, 0x99, 0xe3, 0xef, 0xd2, 0xd0, 0x7e, 0x07, 0x9e, 0xb1, 0x51, - 0x32, 0x1e, 0x43, 0xd1, 0x78, 0x6c, 0xab, 0x7c, 0x69, 0x90, 0x67, 0xd4, 0xea, 0x6d, 0xad, 0x84, 0x5a, 0x7d, 0xf1, - 0x85, 0x59, 0x66, 0x16, 0xc8, 0x90, 0x9e, 0x69, 0x8c, 0xc8, 0x9a, 0x51, 0x5c, 0xe0, 0x1e, 0xac, 0x3e, 0x76, 0x8c, - 0xf6, 0xce, 0x14, 0x94, 0x4a, 0x3c, 0xc4, 0x73, 0x91, 0xe6, 0x87, 0x65, 0x44, 0x6e, 0xfb, 0x32, 0x72, 0xd5, 0xf9, - 0xb7, 0xf1, 0x0d, 0xc3, 0xea, 0xcc, 0x1b, 0x16, 0x5f, 0xe6, 0xb7, 0x3c, 0xbd, 0x7a, 0x35, 0x72, 0xf6, 0xc0, 0x1a, - 0x8e, 0x8b, 0x77, 0x69, 0x23, 0x6f, 0x50, 0x80, 0x1d, 0x86, 0x26, 0xa6, 0xa5, 0x20, 0x58, 0x75, 0x81, 0xa2, 0xaa, - 0xec, 0x19, 0x9d, 0x64, 0x7a, 0x19, 0x0e, 0x39, 0xa8, 0x91, 0x25, 0x30, 0x07, 0x93, 0xba, 0x90, 0x3e, 0x66, 0x2f, - 0x92, 0x6e, 0xce, 0xe5, 0x57, 0xcf, 0xe9, 0x70, 0x66, 0x21, 0xf5, 0x87, 0x4c, 0xc7, 0xa8, 0x7a, 0xe2, 0x79, 0x88, - 0x98, 0x61, 0x54, 0xaa, 0x33, 0x10, 0x20, 0xdc, 0x0c, 0x3f, 0xd1, 0x24, 0x86, 0x50, 0x07, 0x05, 0x15, 0xf5, 0xae, - 0xaf, 0xcd, 0x2f, 0x85, 0xd6, 0xbe, 0x2a, 0xd9, 0xe0, 0x01, 0x86, 0x9f, 0xf8, 0x45, 0x6d, 0x90, 0xcd, 0xb9, 0xe3, - 0x50, 0x2b, 0xc7, 0x2d, 0xbd, 0x9d, 0x76, 0x1b, 0x54, 0x8c, 0x2f, 0xbe, 0x03, 0xe5, 0xe8, 0xce, 0x12, 0xdf, 0x75, - 0xe7, 0x12, 0x4b, 0xdf, 0x65, 0xd3, 0x24, 0xc6, 0x0f, 0xc7, 0x08, 0x44, 0x8d, 0xbb, 0x43, 0x6a, 0x11, 0x9b, 0xef, - 0xbe, 0xf2, 0x1d, 0x0d, 0xc2, 0xba, 0xab, 0x38, 0x58, 0xe6, 0xd6, 0xd6, 0x0b, 0xb1, 0xad, 0xb0, 0x6a, 0x96, 0xc1, - 0xb9, 0x45, 0x67, 0x16, 0x17, 0x46, 0x00, 0xbf, 0xb6, 0x0d, 0x4a, 0x15, 0xc1, 0x17, 0x61, 0xf8, 0x3d, 0x0c, 0x36, - 0x0b, 0xc7, 0x5b, 0x01, 0x5d, 0x77, 0x79, 0x0d, 0xc8, 0xd1, 0x19, 0xd6, 0x8c, 0xae, 0xaa, 0x54, 0x41, 0x69, 0x1e, - 0xc1, 0x18, 0xc8, 0x50, 0x24, 0x1d, 0xd6, 0x38, 0x15, 0x7a, 0x0b, 0xa6, 0x21, 0x01, 0xac, 0xfd, 0x3a, 0x74, 0x6b, - 0x6c, 0x05, 0xb6, 0x90, 0x16, 0xa0, 0xf4, 0xb0, 0x43, 0xdf, 0xaa, 0x81, 0x9e, 0x2e, 0x07, 0xe0, 0x6f, 0x74, 0xf2, - 0x4e, 0xfc, 0xe2, 0xc2, 0x83, 0xff, 0xac, 0x3f, 0x2c, 0x40, 0xca, 0x9f, 0x7e, 0x8a, 0x39, 0xd8, 0xd4, 0xb3, 0x16, - 0x86, 0x5f, 0x28, 0x4e, 0x2b, 0xd5, 0x21, 0x1d, 0x45, 0x8b, 0x2b, 0x63, 0xbd, 0x79, 0x81, 0xbe, 0x20, 0x39, 0x3d, - 0x41, 0x9a, 0xa5, 0xac, 0x57, 0x4f, 0x39, 0x30, 0xfd, 0x0e, 0x45, 0xac, 0xa3, 0x45, 0x86, 0xbe, 0x23, 0xbf, 0x02, - 0xdf, 0x51, 0xa8, 0xd1, 0xb6, 0x72, 0x3a, 0xda, 0x2b, 0xdb, 0x07, 0x92, 0xb6, 0x9b, 0x64, 0x2d, 0xe4, 0xcb, 0xce, - 0xd5, 0x3a, 0xe7, 0xe8, 0xb6, 0x03, 0x78, 0x0c, 0x0a, 0xab, 0xff, 0x8c, 0xcc, 0x85, 0x66, 0x31, 0x1d, 0xc0, 0xdf, - 0x05, 0xb2, 0x20, 0x1a, 0xe3, 0x17, 0x16, 0xef, 0xd2, 0xf2, 0x94, 0xb2, 0x5f, 0x17, 0xa8, 0xd6, 0x83, 0xce, 0x13, - 0xf0, 0xf6, 0xee, 0x3c, 0xfc, 0xcd, 0xe8, 0x97, 0x92, 0x46, 0xea, 0x12, 0xb3, 0x6d, 0xf7, 0x50, 0x5e, 0x24, 0xd1, - 0x15, 0x38, 0x9d, 0x64, 0x63, 0x9c, 0x62, 0xf4, 0xb8, 0x37, 0xcb, 0x64, 0x26, 0x49, 0xce, 0x12, 0xfa, 0x19, 0x13, - 0xb9, 0x14, 0xdb, 0x8f, 0x66, 0x97, 0x6a, 0x35, 0x3a, 0x8d, 0x0c, 0x91, 0xdf, 0x35, 0x11, 0x64, 0x7d, 0xe6, 0x49, - 0x3d, 0x99, 0x61, 0x07, 0x60, 0x10, 0x86, 0x4d, 0x2b, 0x17, 0x50, 0xb5, 0xa1, 0xc4, 0x48, 0x85, 0xa9, 0x06, 0xb2, - 0xfc, 0x6d, 0x50, 0x95, 0x51, 0xc1, 0x7a, 0xf8, 0xa9, 0xcb, 0x18, 0x5c, 0x5b, 0x69, 0x3c, 0x4d, 0xe3, 0xd1, 0x28, - 0x61, 0x3d, 0x65, 0x1f, 0x59, 0x9d, 0x47, 0x98, 0x49, 0x62, 0x2e, 0x59, 0x7d, 0x55, 0x0c, 0xe2, 0x69, 0x3a, 0x45, - 0xa7, 0x60, 0xaf, 0xe1, 0xf7, 0x2a, 0x57, 0x92, 0x53, 0xa6, 0x58, 0xb4, 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, 0x22, 0xea, 0xa2, 0xb2, 0x8e, 0xcc, - 0x5f, 0x67, 0xb7, 0x7c, 0xbe, 0x7a, 0xb7, 0x7c, 0xae, 0x76, 0xcb, 0xcd, 0x1c, 0xfb, 0xd9, 0xb8, 0x83, 0xff, 0xf4, - 0x2a, 0x84, 0x60, 0x55, 0x80, 0x1c, 0x16, 0xda, 0xc5, 0xad, 0x2e, 0xfc, 0x8f, 0x86, 0x6e, 0x7b, 0xf8, 0x8f, 0x0f, - 0x16, 0x60, 0xdb, 0xc2, 0x42, 0xfc, 0xaf, 0x5d, 0xab, 0xea, 0x3c, 0xc4, 0x3a, 0xec, 0xb5, 0xb3, 0x5c, 0xd7, 0xbd, - 0x79, 0xd3, 0x82, 0xbc, 0xe2, 0x4e, 0xa0, 0x84, 0x31, 0xb8, 0x6a, 0xd1, 0xe9, 0x29, 0x94, 0x8e, 0xb3, 0xe1, 0xbc, - 0xf8, 0x5b, 0x09, 0xbf, 0x24, 0xe2, 0x8d, 0x5b, 0xba, 0x31, 0x8e, 0xea, 0x2a, 0xd2, 0x92, 0xd4, 0x08, 0x0b, 0xbd, - 0x4e, 0x41, 0x01, 0x8c, 0xc9, 0x9c, 0xae, 0xff, 0x70, 0xc5, 0x26, 0xf8, 0xff, 0xb2, 0x36, 0x2b, 0x91, 0xf9, 0x8f, - 0x12, 0xe3, 0x46, 0x22, 0xfc, 0x2a, 0x1a, 0x98, 0x6b, 0xd8, 0x7e, 0xb2, 0x1a, 0xdc, 0x43, 0x35, 0xd3, 0x91, 0x52, - 0x0a, 0x52, 0xef, 0x80, 0x17, 0x10, 0xcd, 0x13, 0x7e, 0xf3, 0xa8, 0xeb, 0x38, 0x63, 0x69, 0xd4, 0x1b, 0x04, 0x7a, - 0xd5, 0xf6, 0x8e, 0x52, 0xfa, 0xb3, 0xcf, 0x1f, 0xe2, 0x3f, 0x22, 0x70, 0x76, 0x5a, 0xf9, 0x46, 0x22, 0x36, 0x80, - 0xbe, 0xd1, 0xb4, 0xe6, 0xfc, 0x08, 0x0d, 0x4e, 0xfe, 0xcf, 0x5d, 0x5b, 0xa3, 0xb1, 0x7e, 0xa7, 0xe6, 0xd2, 0x2a, - 0xfd, 0x55, 0xad, 0x7f, 0xdd, 0xe0, 0x77, 0x6c, 0x3b, 0x14, 0x0e, 0x41, 0xbd, 0xad, 0x8c, 0x07, 0x2e, 0x35, 0x56, - 0x14, 0xbf, 0x6b, 0xfb, 0xca, 0x24, 0xa6, 0x1e, 0xd3, 0xf0, 0x54, 0x3b, 0x91, 0xf2, 0xf0, 0x1e, 0x7b, 0x08, 0x3f, - 0xf2, 0x4b, 0x16, 0x3e, 0xc0, 0xaf, 0xb1, 0x59, 0x97, 0xd3, 0x24, 0x05, 0xb3, 0x6a, 0xc2, 0xf9, 0x2c, 0xd8, 0xda, - 0xba, 0xb8, 0xb8, 0xf0, 0x2f, 0xb6, 0xfd, 0x2c, 0x3f, 0xdb, 0xea, 0xb6, 0xdb, 0x6d, 0xfc, 0x88, 0x96, 0x6d, 0x9d, - 0xc7, 0xec, 0xe2, 0x29, 0xb8, 0x1f, 0xf6, 0x63, 0xeb, 0x89, 0xf5, 0x78, 0xdb, 0xda, 0x79, 0x64, 0x5b, 0xa4, 0x00, - 0xa0, 0x64, 0xdb, 0xb6, 0x84, 0x02, 0x08, 0x6d, 0x28, 0xee, 0xef, 0x9e, 0x29, 0x1b, 0x0e, 0x2f, 0x29, 0x08, 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, 0x85, 0x27, 0xcc, 0x6a, 0xb3, 0xe0, - 0xf9, 0x4d, 0xf7, 0x31, 0xe8, 0xb8, 0xf3, 0xd6, 0xc3, 0x61, 0xbb, 0xd5, 0xb1, 0x3a, 0xad, 0xae, 0xff, 0xd8, 0xea, - 0x8a, 0xff, 0x83, 0x8c, 0xdc, 0xb6, 0x3a, 0xf0, 0xb4, 0x6d, 0xc1, 0xfb, 0xf9, 0x43, 0x91, 0x5b, 0x12, 0xd9, 0x5b, - 0xfd, 0x5d, 0xfc, 0x4d, 0x29, 0x40, 0xea, 0x73, 0x5b, 0xfc, 0x0a, 0x9e, 0xfd, 0x99, 0x59, 0xda, 0x79, 0xb2, 0xb2, - 0xb8, 0xfb, 0x78, 0x65, 0xf1, 0xf6, 0xa3, 0x95, 0xc5, 0x0f, 0x77, 0xea, 0xc5, 0x5b, 0x67, 0xa2, 0x4a, 0xcb, 0x85, - 0xd0, 0x9e, 0x46, 0xc0, 0x28, 0x97, 0x4e, 0x07, 0xe0, 0x6c, 0x5b, 0x2d, 0xfc, 0xf3, 0xb8, 0xeb, 0xea, 0x5e, 0xa7, - 0xd8, 0x4b, 0x63, 0xf9, 0xf8, 0x09, 0x60, 0xf9, 0xb2, 0xfb, 0x68, 0x88, 0xed, 0x08, 0x51, 0xf8, 0xef, 0x7c, 0xfb, - 0xc9, 0x10, 0x34, 0x82, 0x85, 0xff, 0xc1, 0x3f, 0x93, 0x9d, 0xee, 0x50, 0xbc, 0xb4, 0xb1, 0xfe, 0xdb, 0xce, 0xe3, - 0x02, 0x9a, 0xe2, 0x3f, 0xbf, 0x68, 0x13, 0x1a, 0x0d, 0x78, 0x73, 0xdc, 0x87, 0x40, 0xa3, 0x27, 0x93, 0xae, 0xff, - 0xf9, 0xf9, 0x63, 0xff, 0xc9, 0xa4, 0xf3, 0xf8, 0x5b, 0xf1, 0x96, 0x00, 0x05, 0x3f, 0xc7, 0xff, 0xbe, 0xdd, 0x6e, - 0x4f, 0x5a, 0x1d, 0xff, 0xc9, 0xf9, 0xb6, 0xbf, 0x9d, 0xb4, 0x1e, 0xf9, 0x4f, 0xf0, 0xbf, 0x6a, 0xb8, 0x49, 0x36, - 0x65, 0xb6, 0x85, 0xeb, 0xdd, 0xf0, 0x7b, 0xcd, 0x39, 0xba, 0x0f, 0xad, 0x9d, 0x87, 0x2f, 0x9f, 0xc0, 0x1a, 0x4d, - 0x3a, 0x5d, 0xf8, 0xff, 0xba, 0xc7, 0x6f, 0x91, 0xf0, 0x72, 0xe0, 0x88, 0x61, 0x7a, 0xb1, 0x22, 0x1c, 0x7d, 0xd0, - 0xed, 0x81, 0xf7, 0xa7, 0x75, 0x01, 0x10, 0xc6, 0x6f, 0x0d, 0x80, 0x70, 0x7e, 0xb7, 0x08, 0x08, 0xfd, 0xda, 0xc0, - 0xef, 0x18, 0x01, 0xf9, 0x53, 0x33, 0xc8, 0x7d, 0xc9, 0x96, 0x02, 0x1d, 0x4d, 0x67, 0xed, 0x2d, 0x73, 0x0e, 0xbf, - 0x64, 0x47, 0x98, 0x4a, 0x0f, 0xad, 0x39, 0x37, 0xe3, 0x41, 0x19, 0x6e, 0xe4, 0x4b, 0x26, 0x76, 0x72, 0xc1, 0xd7, - 0x10, 0x24, 0xbe, 0x9d, 0x20, 0xdf, 0xde, 0x8d, 0x1e, 0xf1, 0xef, 0x4c, 0x8f, 0x82, 0x1b, 0xf4, 0xa8, 0x45, 0xdc, - 0x29, 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0x9c, 0xbe, 0xc5, 0xb6, 0xc5, 0xb0, 0xa8, 0xb0, 0x45, 0xce, 0xe6, - 0xd3, 0x5f, 0x73, 0x44, 0x20, 0xd2, 0xcd, 0x43, 0x5b, 0x46, 0x61, 0x66, 0xf8, 0xd1, 0x62, 0xf5, 0x72, 0x2e, 0xae, - 0x34, 0x85, 0x74, 0x1f, 0x71, 0x47, 0x47, 0x70, 0xf0, 0x06, 0x40, 0xb8, 0xc8, 0x78, 0x84, 0xbf, 0x8a, 0x05, 0xe4, - 0xa6, 0xdf, 0xcf, 0x8a, 0x79, 0x82, 0x97, 0xa6, 0xbd, 0xa1, 0xf8, 0x80, 0x2c, 0x3c, 0xca, 0xbb, 0x86, 0x98, 0xc2, - 0xfe, 0x0d, 0xa6, 0xdf, 0xab, 0xb3, 0x83, 0x29, 0xc6, 0x11, 0xde, 0xb0, 0x51, 0x1c, 0x39, 0xb6, 0x33, 0x83, 0x8d, - 0x0c, 0xb3, 0xb4, 0x6a, 0xb9, 0xef, 0x94, 0xf6, 0xee, 0xda, 0xea, 0xa7, 0x99, 0x72, 0xfc, 0xd4, 0x5d, 0x78, 0x28, - 0xe3, 0x8e, 0xb6, 0x74, 0x0c, 0x60, 0x7c, 0x55, 0x92, 0xa3, 0x0e, 0xa8, 0x8c, 0x09, 0x5b, 0x58, 0x13, 0x1d, 0xbf, - 0x0b, 0xde, 0x05, 0x15, 0xe3, 0xa7, 0xc3, 0xbe, 0x77, 0x5a, 0xdb, 0x60, 0xed, 0x18, 0xdd, 0xf4, 0x40, 0x47, 0xfa, - 0x97, 0x7e, 0xf4, 0xaf, 0xd1, 0xd5, 0x2f, 0x0c, 0xd8, 0x82, 0x23, 0x3e, 0x13, 0xb8, 0xdb, 0xf4, 0x89, 0x06, 0x99, - 0x50, 0x82, 0x17, 0xe6, 0xa0, 0xcc, 0x31, 0x7f, 0x95, 0x4c, 0x7c, 0x9a, 0x4c, 0xfc, 0x00, 0x61, 0x59, 0x35, 0x61, - 0xd5, 0xcf, 0x7f, 0x20, 0x05, 0x99, 0xa7, 0x67, 0x23, 0xea, 0x61, 0x86, 0x07, 0xfe, 0xad, 0x8a, 0xd5, 0x83, 0x8c, - 0x58, 0x81, 0x17, 0x8f, 0xbf, 0xe9, 0x42, 0x7f, 0x96, 0xe2, 0x61, 0x22, 0xca, 0xd1, 0x28, 0xad, 0x86, 0xaa, 0xe2, - 0x5e, 0xc5, 0xd3, 0xab, 0x03, 0xf9, 0x41, 0x03, 0x1b, 0x43, 0xd0, 0x74, 0xf4, 0x50, 0x7d, 0x4c, 0x6d, 0x13, 0xf4, - 0x1e, 0xfd, 0xc4, 0x29, 0x65, 0x0f, 0xa0, 0x6a, 0xc3, 0xfb, 0x04, 0x96, 0x74, 0x81, 0x42, 0x5b, 0x28, 0xb6, 0x11, - 0x3b, 0x8f, 0x87, 0x52, 0x3f, 0x79, 0x96, 0xbc, 0x07, 0xd5, 0x22, 0xba, 0x87, 0x1d, 0x4f, 0x04, 0x01, 0xe0, 0x05, - 0xd5, 0x73, 0x98, 0x66, 0x76, 0xff, 0x41, 0x6f, 0x1d, 0x65, 0xf1, 0xf7, 0x56, 0x0f, 0xc1, 0xe9, 0xfc, 0xdb, 0xf0, - 0x01, 0xfe, 0xe2, 0xea, 0x83, 0x23, 0xdb, 0xf5, 0x49, 0xba, 0x3f, 0xa8, 0x7e, 0x76, 0x15, 0x45, 0xdb, 0x26, 0x28, - 0x62, 0xef, 0xae, 0x1a, 0x59, 0x6a, 0xdf, 0xee, 0x4e, 0xa5, 0x7d, 0xe1, 0xd9, 0x10, 0xb7, 0xa0, 0x09, 0xba, 0xfe, - 0x8e, 0x21, 0xd3, 0xcf, 0x5b, 0xf8, 0xb7, 0x26, 0xd5, 0x1f, 0x42, 0x03, 0x25, 0xd6, 0x5f, 0x43, 0xf3, 0x6d, 0xa1, - 0x41, 0xa0, 0xdf, 0x0f, 0x24, 0x73, 0x85, 0xbc, 0xad, 0xf3, 0xf8, 0x8a, 0xd3, 0x30, 0x91, 0x69, 0x61, 0x7b, 0x46, - 0xe0, 0x4c, 0x6c, 0x39, 0x19, 0x16, 0x7a, 0x0e, 0x7d, 0x1d, 0xfd, 0x8d, 0xf2, 0x55, 0x75, 0x5e, 0x4d, 0x04, 0xac, - 0x98, 0x02, 0x37, 0x6d, 0xe3, 0xc4, 0xad, 0x27, 0x92, 0xb8, 0xf5, 0x47, 0x4e, 0xd6, 0x73, 0xab, 0xcc, 0xf6, 0x76, - 0x8d, 0xfd, 0xcf, 0xe9, 0x3b, 0xaa, 0x34, 0xc9, 0xab, 0x51, 0xd9, 0x9c, 0x1f, 0x6c, 0x16, 0xfc, 0xd1, 0xc9, 0xea, - 0x0a, 0x8f, 0xbc, 0x9b, 0x8b, 0xf9, 0x14, 0xa3, 0x38, 0xa7, 0x2b, 0xdf, 0x0a, 0xf4, 0x5a, 0x54, 0xb5, 0xa2, 0x12, - 0x89, 0x00, 0x56, 0x0c, 0x6c, 0x2c, 0xb2, 0x03, 0x99, 0xf5, 0x67, 0x7e, 0x48, 0xdc, 0xbc, 0x93, 0x3b, 0x12, 0x09, - 0x7f, 0xf8, 0x43, 0x0b, 0xb6, 0xa0, 0x8f, 0x0d, 0xa2, 0x74, 0xed, 0x2e, 0x21, 0x03, 0x0b, 0x71, 0xad, 0x7e, 0x39, - 0xcb, 0x94, 0x2e, 0xb6, 0x49, 0x68, 0x3d, 0x2e, 0x91, 0xd0, 0x95, 0x74, 0x3a, 0x65, 0x11, 0xf7, 0xa3, 0x94, 0x92, - 0xb3, 0x1c, 0x43, 0x06, 0x79, 0x1d, 0xb6, 0xed, 0x96, 0x20, 0xf8, 0x8c, 0x9f, 0x16, 0x13, 0x9b, 0xd9, 0x87, 0x42, - 0xfd, 0x59, 0xab, 0x7a, 0xa2, 0xf5, 0xa4, 0xdb, 0x7f, 0x77, 0xb0, 0x67, 0x89, 0x4d, 0xb9, 0xbb, 0x05, 0xaf, 0xbb, - 0xe4, 0x9e, 0x8b, 0x3c, 0x95, 0x50, 0xe4, 0xa9, 0x58, 0x22, 0xbb, 0x4d, 0x24, 0x26, 0x6f, 0x09, 0xb4, 0x6d, 0x8b, - 0xa5, 0x43, 0x11, 0x57, 0x9c, 0x82, 0x0b, 0x13, 0xe3, 0xc7, 0xe7, 0xb6, 0xb0, 0x6b, 0x0b, 0x17, 0xcc, 0x56, 0x29, - 0x3f, 0xca, 0x68, 0xe1, 0xa9, 0x8a, 0x42, 0x82, 0xa9, 0xc1, 0x54, 0xf6, 0x8f, 0x1c, 0x4a, 0x27, 0x1d, 0x2f, 0xb7, - 0x2e, 0xe6, 0xa7, 0x53, 0x10, 0x82, 0x2a, 0x63, 0xe7, 0xa3, 0xec, 0xb0, 0x4b, 0x53, 0xf5, 0x4f, 0x4a, 0x19, 0x26, - 0x55, 0x1f, 0x06, 0x6f, 0xfc, 0x88, 0xaa, 0xc0, 0x5e, 0x0a, 0x7d, 0x4c, 0x38, 0x99, 0x6c, 0x1b, 0x09, 0x27, 0x46, - 0x5d, 0x09, 0xa8, 0x6f, 0xf7, 0x4f, 0x82, 0x99, 0x1c, 0xef, 0x75, 0xb6, 0xf4, 0x83, 0xac, 0xa2, 0x3d, 0x28, 0x94, - 0x01, 0x25, 0x8f, 0x8b, 0x4b, 0x1b, 0x12, 0x60, 0x58, 0x41, 0x80, 0x49, 0xea, 0x77, 0x8b, 0xce, 0xb5, 0xed, 0x9d, - 0xb6, 0xca, 0xc9, 0x85, 0x32, 0xdc, 0x90, 0xa2, 0x8b, 0x31, 0x49, 0x2d, 0xb6, 0x3b, 0xe9, 0xf4, 0x77, 0x23, 0x69, - 0x39, 0xa2, 0xf0, 0x28, 0x40, 0x7a, 0x40, 0x67, 0x34, 0xcf, 0xfc, 0x38, 0xdb, 0xba, 0x60, 0xa7, 0xad, 0x68, 0x16, - 0x57, 0x81, 0x54, 0xb4, 0x23, 0xf4, 0x94, 0x59, 0x35, 0x13, 0x3e, 0x46, 0x0d, 0x24, 0x49, 0x70, 0x97, 0x32, 0x4a, - 0x4b, 0xe6, 0x37, 0xb0, 0x10, 0x50, 0x98, 0xe4, 0xba, 0x8a, 0xe6, 0x4a, 0x75, 0x5a, 0xda, 0xfd, 0xbf, 0xfc, 0xf3, - 0xff, 0x96, 0x01, 0x5a, 0xa0, 0x4a, 0x47, 0x8d, 0xd5, 0x20, 0x74, 0xb9, 0x8b, 0xf9, 0x4d, 0xd5, 0x11, 0x2e, 0xbb, - 0x04, 0x4f, 0x3f, 0x1e, 0xb5, 0x26, 0x51, 0x32, 0x06, 0xc0, 0xd6, 0x12, 0xc8, 0xcc, 0x7e, 0x90, 0x50, 0xd7, 0x8b, - 0x90, 0x05, 0x7f, 0x53, 0x96, 0xb5, 0xca, 0x6e, 0xa7, 0xdd, 0x6a, 0xe4, 0x5c, 0x1b, 0x1b, 0xaa, 0x96, 0x77, 0xad, - 0x7e, 0x95, 0x4c, 0x0a, 0x35, 0x56, 0x4b, 0xba, 0x86, 0x96, 0xfa, 0xa4, 0xe9, 0xdf, 0xff, 0xe5, 0x1f, 0xfe, 0x87, - 0x7a, 0xc5, 0x03, 0xa4, 0xbf, 0xfc, 0xd3, 0xdf, 0x61, 0x7e, 0xb3, 0xa5, 0x0f, 0x99, 0x48, 0x4e, 0x58, 0xd5, 0x09, - 0x93, 0x10, 0x18, 0x56, 0xe5, 0xd1, 0xd5, 0x93, 0xb3, 0xf7, 0x69, 0x42, 0xda, 0x6c, 0x12, 0x3a, 0xda, 0xb4, 0x65, - 0xc5, 0x23, 0x35, 0x92, 0x13, 0x2f, 0x42, 0x25, 0xd2, 0xfb, 0x4e, 0x99, 0x4f, 0xbe, 0x5e, 0x8d, 0x85, 0x0a, 0xff, - 0x61, 0x49, 0x59, 0x95, 0x5b, 0x18, 0x97, 0x5f, 0xe0, 0x6b, 0xd0, 0x35, 0x8a, 0x69, 0xf1, 0x6a, 0x7d, 0x7a, 0x3f, - 0xcd, 0x01, 0xfe, 0x31, 0x52, 0x5c, 0x04, 0x19, 0xe9, 0xcc, 0xb9, 0x85, 0x06, 0x5d, 0x72, 0x55, 0xd2, 0x28, 0xc2, - 0x0b, 0x7c, 0xf8, 0xe4, 0x6f, 0xca, 0x3f, 0x4e, 0xd1, 0x6c, 0xb2, 0x9c, 0x69, 0x74, 0x29, 0x7d, 0xc3, 0x47, 0xed, - 0xf6, 0xec, 0xd2, 0x5d, 0x54, 0x33, 0x78, 0xeb, 0x26, 0xa3, 0xc0, 0xa4, 0x39, 0x20, 0x1d, 0x56, 0xeb, 0x18, 0x28, - 0xb8, 0x43, 0x6d, 0x0c, 0x99, 0x95, 0xe5, 0x1f, 0x16, 0x14, 0x86, 0x8b, 0x7f, 0xc1, 0x43, 0x65, 0x19, 0xb1, 0x84, - 0x12, 0x03, 0x8b, 0x85, 0xd1, 0xab, 0x2b, 0x7a, 0x4d, 0x3a, 0xcb, 0x39, 0x41, 0xe6, 0xa1, 0xb8, 0x79, 0x9c, 0xfd, - 0x10, 0x0f, 0xa8, 0x27, 0x1d, 0x6f, 0xd2, 0x5d, 0xe8, 0xe1, 0x39, 0xcf, 0xa6, 0xe6, 0x29, 0x38, 0x8b, 0xd8, 0x90, - 0x8d, 0x55, 0xa4, 0x57, 0xd6, 0x8b, 0x13, 0xee, 0x72, 0xb2, 0xbd, 0x62, 0x2e, 0x09, 0x12, 0x9d, 0x7e, 0x03, 0x3c, - 0x9f, 0xe1, 0x06, 0x04, 0xfa, 0x67, 0x11, 0x0f, 0x88, 0x5f, 0x7b, 0xe6, 0x59, 0x7a, 0x84, 0x52, 0x26, 0x5b, 0x18, - 0xf0, 0xf4, 0x44, 0x53, 0x8c, 0xb9, 0xd6, 0x73, 0xb2, 0x4a, 0x9f, 0xba, 0x9b, 0x43, 0x89, 0x90, 0xcd, 0xb7, 0xf2, - 0x88, 0xfa, 0x69, 0x2d, 0xd6, 0x21, 0x55, 0x4c, 0xd7, 0xf5, 0x56, 0xd6, 0x0b, 0x4d, 0x2d, 0x6a, 0xbf, 0x05, 0x03, - 0x8c, 0xc0, 0xb4, 0x9b, 0xad, 0xa8, 0x10, 0x5b, 0x3d, 0x0d, 0xbf, 0xd5, 0x7e, 0x4d, 0x34, 0x9b, 0x51, 0x43, 0x17, - 0x98, 0x98, 0xac, 0x51, 0x94, 0x1d, 0x94, 0x7e, 0x21, 0xb2, 0x1d, 0x64, 0x1b, 0xb9, 0x11, 0xc4, 0x93, 0xcc, 0x83, - 0xa0, 0xdf, 0xb7, 0xff, 0x7f, 0x47, 0x48, 0x09, 0x5d, 0xf5, 0x7e, 0x00, 0x00}; + 0xd9, 0xf4, 0x58, 0x0d, 0xbc, 0x0e, 0x10, 0x0a, 0x4f, 0xd7, 0x59, 0x42, 0xa9, 0xea, 0x2c, 0x85, 0xb8, 0xde, 0x40, + 0x1f, 0x99, 0x04, 0x73, 0x15, 0x09, 0xf6, 0xa5, 0x40, 0x60, 0xe8, 0x91, 0x89, 0xf5, 0x7a, 0x06, 0xcb, 0x73, 0x1a, + 0x0d, 0x3f, 0x69, 0x70, 0x2b, 0xde, 0x6b, 0xb2, 0x81, 0xd3, 0x28, 0x09, 0x0d, 0x71, 0x65, 0xe2, 0xad, 0x24, 0x74, + 0x6d, 0xa3, 0x80, 0x43, 0xb6, 0xc4, 0xf6, 0xcd, 0x85, 0x6e, 0x72, 0xbb, 0x64, 0x0f, 0xe5, 0x3f, 0x55, 0x5c, 0xb2, + 0x9e, 0xe5, 0x98, 0x92, 0x06, 0x4c, 0x31, 0x1e, 0x2c, 0x4d, 0x03, 0x12, 0xe0, 0xbb, 0x72, 0x14, 0x17, 0xeb, 0x49, + 0xf0, 0xbb, 0x82, 0xf9, 0xdc, 0x98, 0xe9, 0x56, 0x48, 0xb5, 0x84, 0x93, 0x66, 0xb0, 0x06, 0x4d, 0x1a, 0x0f, 0x4a, + 0xd4, 0x7c, 0x8d, 0x86, 0x0a, 0x71, 0xfc, 0x99, 0xa8, 0x42, 0x13, 0x0c, 0xc1, 0xc8, 0xbd, 0x42, 0x32, 0x5c, 0xb6, + 0x2c, 0x5a, 0xa4, 0x4c, 0x8d, 0x49, 0xa5, 0x6a, 0x96, 0xcb, 0xc0, 0xc0, 0xa2, 0xdd, 0xea, 0x4b, 0x4b, 0x5c, 0x89, + 0xdc, 0x34, 0xd4, 0xc2, 0xa4, 0x50, 0xde, 0x84, 0x93, 0xa3, 0xdf, 0xa5, 0xac, 0x77, 0x13, 0x9f, 0x5c, 0xe1, 0x93, + 0xfb, 0x86, 0x0f, 0x65, 0xf2, 0x76, 0x31, 0x28, 0x82, 0xaf, 0x6b, 0x95, 0x68, 0x9f, 0xfa, 0x28, 0x98, 0x5d, 0x2d, + 0x74, 0x41, 0xa0, 0x48, 0x36, 0x49, 0x07, 0x92, 0xdf, 0x50, 0x6c, 0x54, 0x9e, 0x51, 0xe6, 0x8a, 0x0d, 0x52, 0xf3, + 0x4a, 0x33, 0x2f, 0x75, 0x1b, 0xf6, 0x7b, 0x59, 0x4a, 0x3a, 0x31, 0x41, 0x99, 0xd8, 0xbb, 0x89, 0x36, 0x5e, 0x1a, + 0x66, 0xc2, 0xfa, 0x15, 0xc6, 0x4e, 0x8d, 0x42, 0xa9, 0x14, 0x81, 0x38, 0x36, 0xbe, 0x56, 0x96, 0x41, 0xe6, 0xaf, + 0xb0, 0xa7, 0x00, 0x94, 0x04, 0x16, 0x5f, 0x53, 0xc9, 0x8b, 0xc2, 0x3a, 0x1d, 0xef, 0x11, 0x1d, 0x2b, 0x11, 0x5a, + 0x13, 0xf9, 0x5a, 0x9f, 0xc5, 0x7e, 0xcd, 0x25, 0x34, 0x29, 0x99, 0x0f, 0xf2, 0xc0, 0x56, 0x81, 0x88, 0x4a, 0xb7, + 0x25, 0x83, 0x84, 0x1c, 0xd2, 0x65, 0xa2, 0xd7, 0x46, 0x32, 0x68, 0x9d, 0x0a, 0x89, 0x96, 0x1e, 0x85, 0x11, 0x8a, + 0x0d, 0xb1, 0x16, 0x4b, 0x84, 0x6c, 0xda, 0x9b, 0xc4, 0x8a, 0xe8, 0x9c, 0xe6, 0x68, 0xc2, 0x99, 0x3a, 0xdd, 0x71, + 0x00, 0x1d, 0x10, 0xfb, 0x4b, 0xac, 0xb7, 0xd2, 0xec, 0x74, 0xfd, 0xca, 0xe1, 0xbb, 0xbe, 0x9e, 0x00, 0x3f, 0x48, + 0x83, 0x17, 0xd6, 0x6c, 0xa0, 0x64, 0xef, 0xde, 0x6b, 0x6c, 0x45, 0xf6, 0x67, 0x55, 0x52, 0x79, 0x0a, 0x35, 0xce, + 0xad, 0xaf, 0x53, 0x2d, 0xb4, 0xa8, 0x2a, 0xf6, 0x0d, 0xa9, 0xbe, 0xaf, 0x14, 0x76, 0x85, 0xf2, 0xbe, 0x1c, 0x3a, + 0x76, 0x5d, 0x37, 0xc8, 0xc9, 0x79, 0xb9, 0xb7, 0xca, 0x85, 0xbc, 0x7f, 0xdf, 0xf4, 0x99, 0xce, 0xf5, 0xf0, 0xcf, + 0x1c, 0x54, 0xce, 0xc5, 0x55, 0x4a, 0x16, 0xcc, 0x33, 0xa5, 0x8e, 0x96, 0x1c, 0xd0, 0x76, 0x0f, 0x3d, 0xed, 0xe8, + 0x22, 0x8a, 0xb9, 0xa5, 0x47, 0x11, 0x9e, 0x36, 0xca, 0x27, 0x69, 0x74, 0x00, 0x5e, 0x68, 0x42, 0x92, 0x13, 0x6e, + 0xda, 0xa2, 0xc5, 0x70, 0xc2, 0x30, 0x04, 0xae, 0xec, 0x09, 0x53, 0xf6, 0xdc, 0x43, 0xbc, 0xe5, 0xc0, 0xab, 0x61, + 0x2f, 0x9b, 0xdd, 0x6b, 0xe6, 0x3f, 0xac, 0x11, 0xc8, 0xb6, 0xa9, 0xaa, 0x2b, 0x1b, 0xef, 0x52, 0x44, 0x62, 0x84, + 0x6d, 0xd5, 0xd8, 0xd2, 0xd6, 0xef, 0x35, 0xdc, 0xeb, 0xca, 0x31, 0xaf, 0x29, 0xd5, 0x86, 0x1e, 0x56, 0x6e, 0x0e, + 0x37, 0x1d, 0x79, 0xb1, 0x82, 0x6e, 0x4f, 0x04, 0x85, 0xc0, 0x89, 0x50, 0xf6, 0xa0, 0xe6, 0x06, 0x22, 0x25, 0x53, + 0x5a, 0x35, 0x9b, 0x27, 0x23, 0x09, 0x2c, 0xb8, 0xb0, 0x4c, 0xf2, 0xd1, 0x45, 0x9c, 0x24, 0x55, 0xe9, 0xef, 0x2a, + 0xe0, 0xc5, 0xb0, 0xb7, 0x89, 0x76, 0x81, 0xd1, 0x5c, 0x81, 0xe0, 0x6a, 0x23, 0xec, 0xa3, 0xe3, 0x56, 0xeb, 0x2e, + 0x22, 0x8e, 0xcc, 0x8c, 0x46, 0x7c, 0x44, 0x1b, 0xb2, 0x64, 0x9a, 0xb5, 0xf7, 0x5e, 0x60, 0x48, 0xcd, 0xc0, 0x07, + 0xd5, 0x19, 0x15, 0xff, 0x2a, 0x7b, 0xea, 0x57, 0xa2, 0x77, 0xab, 0xea, 0x6a, 0x06, 0x54, 0x54, 0xe0, 0xc3, 0x0c, + 0xb1, 0xb4, 0x55, 0x20, 0x20, 0xd7, 0xc3, 0x3a, 0xdc, 0xad, 0x91, 0x06, 0x0b, 0x4a, 0x81, 0xb5, 0x56, 0x76, 0xaf, + 0x6f, 0x0b, 0xe6, 0x50, 0x28, 0x5c, 0xf4, 0x7f, 0x96, 0x4d, 0x67, 0x68, 0x99, 0x35, 0x98, 0x1a, 0x1a, 0x7c, 0x6c, + 0xd4, 0x97, 0x2b, 0xca, 0x6a, 0x7d, 0x68, 0x47, 0xd6, 0xf8, 0x49, 0x3b, 0xca, 0xe0, 0x50, 0xcd, 0x75, 0x51, 0xdd, + 0x6e, 0x6e, 0x8a, 0x98, 0x55, 0x3c, 0xee, 0x93, 0xde, 0xd6, 0xd6, 0xa4, 0xa7, 0x69, 0x40, 0x32, 0x49, 0x32, 0xbc, + 0xc9, 0x00, 0x65, 0x45, 0x9c, 0x45, 0xd9, 0x20, 0xdf, 0xa2, 0x2c, 0x71, 0xfd, 0x7e, 0xe8, 0xed, 0xd5, 0x3c, 0x6b, + 0x6f, 0x6f, 0xbd, 0x8b, 0x5c, 0xd5, 0x49, 0x0f, 0xf2, 0xf0, 0x08, 0x8a, 0x96, 0x6c, 0xca, 0x70, 0x31, 0xcd, 0x46, + 0x2c, 0xb0, 0xa1, 0x7b, 0x6a, 0x97, 0x72, 0xd3, 0x44, 0xc0, 0x3d, 0x11, 0x73, 0x16, 0x1f, 0xea, 0x91, 0xd4, 0x60, + 0x0f, 0x58, 0x40, 0x9b, 0x0b, 0x5f, 0x85, 0x67, 0x49, 0x76, 0x1a, 0x25, 0x07, 0x42, 0x81, 0xd7, 0x5a, 0x7e, 0x0b, + 0x2e, 0x23, 0x59, 0xac, 0x86, 0x92, 0xfa, 0x6a, 0xf0, 0x55, 0x70, 0x7b, 0x8f, 0xca, 0x5b, 0xb1, 0x3b, 0x7e, 0xdb, + 0xef, 0xd8, 0x2a, 0x22, 0xf6, 0x93, 0x39, 0x1d, 0x68, 0x9c, 0x02, 0x28, 0x73, 0x00, 0x9a, 0xac, 0xf0, 0x86, 0x2c, + 0xfc, 0x69, 0xf0, 0x93, 0x72, 0xa9, 0x33, 0x70, 0x21, 0xc0, 0xc9, 0x4f, 0x62, 0xde, 0xc2, 0xf3, 0x48, 0xdb, 0x5b, + 0x88, 0x0a, 0x8c, 0x2b, 0x52, 0x5c, 0xba, 0x54, 0xde, 0xa0, 0x77, 0x1c, 0x9e, 0x40, 0xb3, 0x8d, 0x8d, 0x85, 0xf3, + 0x26, 0xe2, 0x13, 0x3f, 0x8f, 0xd2, 0x51, 0x36, 0x75, 0xdc, 0x4d, 0xdb, 0x76, 0xfd, 0x82, 0x3c, 0x91, 0xcf, 0xdd, + 0x72, 0xe3, 0x04, 0xfc, 0x80, 0xd0, 0x1e, 0xd8, 0x9b, 0xc7, 0xde, 0x01, 0x0b, 0x4f, 0x76, 0x37, 0x16, 0x23, 0x56, + 0xf6, 0x4f, 0xbc, 0x4b, 0x1d, 0x73, 0xf7, 0xde, 0xa3, 0x94, 0x81, 0x5e, 0x61, 0xff, 0x52, 0x82, 0x01, 0xec, 0x46, + 0xf1, 0x77, 0x90, 0x72, 0x1f, 0xe9, 0x40, 0x44, 0xc6, 0x69, 0xaf, 0xaf, 0xed, 0x8c, 0x22, 0x06, 0xf6, 0x3d, 0xed, + 0xac, 0xde, 0xbf, 0x5f, 0xa9, 0xf9, 0xaa, 0xd4, 0x9b, 0xb3, 0xb0, 0xe6, 0xa9, 0x7b, 0x2f, 0xe9, 0x68, 0xa5, 0xbe, + 0x91, 0xe7, 0x8c, 0x94, 0xe6, 0xb2, 0x9d, 0xe0, 0x18, 0x5b, 0x7c, 0xf5, 0xb6, 0x3e, 0x14, 0x51, 0x0a, 0x3f, 0x06, + 0xeb, 0x25, 0x02, 0xf5, 0x0d, 0x0e, 0x8e, 0x77, 0x10, 0x6e, 0xed, 0x3a, 0x83, 0xc0, 0xb9, 0xd7, 0x6a, 0x5d, 0xff, + 0xb8, 0x75, 0xf8, 0xe7, 0xa8, 0xf5, 0xcb, 0x5e, 0xeb, 0x87, 0x23, 0xf7, 0xda, 0xf9, 0x71, 0x6b, 0x70, 0x28, 0xdf, + 0x0e, 0xff, 0xdc, 0xff, 0xb1, 0x38, 0xfa, 0x83, 0x28, 0xdc, 0x70, 0xdd, 0xad, 0x33, 0x6f, 0xc6, 0xc2, 0xad, 0x56, + 0xab, 0x0f, 0x4f, 0x67, 0xf0, 0x84, 0x7f, 0x2f, 0xe0, 0xcf, 0xf5, 0xa1, 0xf5, 0x9f, 0x7e, 0x4c, 0xff, 0xf3, 0x8f, + 0xf9, 0x11, 0x8e, 0x79, 0xf8, 0xe7, 0x1f, 0x0b, 0xfb, 0x41, 0x3f, 0xdc, 0x3a, 0xda, 0x74, 0x1d, 0x5d, 0xf3, 0x87, + 0xb0, 0x7a, 0x84, 0x56, 0x87, 0x7f, 0x96, 0x6f, 0xf6, 0x83, 0x93, 0xdd, 0x7e, 0x78, 0x74, 0xed, 0xd8, 0xd7, 0x0f, + 0xdc, 0x6b, 0xd7, 0xbd, 0xde, 0xc0, 0x79, 0xce, 0x61, 0xf4, 0x07, 0xf0, 0x77, 0x0c, 0x7f, 0x6d, 0xf8, 0x3b, 0x85, + 0xbf, 0x7f, 0x86, 0x6e, 0x22, 0xfe, 0x76, 0x4d, 0xb1, 0x90, 0x6b, 0x3c, 0xb0, 0x88, 0x60, 0x15, 0xdc, 0x8d, 0xad, + 0xd8, 0xdb, 0x20, 0xa2, 0xc1, 0x3e, 0xf4, 0x7d, 0x1f, 0xc3, 0xa4, 0xce, 0xe2, 0x78, 0x03, 0x16, 0x1d, 0x39, 0x67, + 0x23, 0xe0, 0x9e, 0x88, 0x1c, 0x14, 0x01, 0x13, 0x67, 0xab, 0x05, 0x1e, 0xae, 0x7a, 0xc3, 0x70, 0x83, 0x39, 0x60, + 0x14, 0xbc, 0x65, 0xf8, 0xd0, 0x75, 0xbd, 0x17, 0xf2, 0xcc, 0x10, 0xf7, 0xb9, 0x60, 0xad, 0x34, 0x13, 0x26, 0x8d, + 0xed, 0x7a, 0xb3, 0x15, 0x95, 0xb0, 0xad, 0xd3, 0x33, 0xa8, 0x3b, 0x15, 0x27, 0x8c, 0xdf, 0xb1, 0xe8, 0x13, 0x6e, + 0xc9, 0x37, 0xc6, 0x21, 0xf0, 0x92, 0x25, 0xdf, 0x34, 0x1a, 0x0d, 0x1b, 0x51, 0xb8, 0x63, 0x4f, 0x19, 0xcc, 0xb0, + 0x64, 0x22, 0x32, 0x52, 0x9a, 0xc2, 0xb2, 0x85, 0xc9, 0xdf, 0x47, 0x39, 0xdf, 0xa8, 0x0c, 0xdb, 0xb0, 0x66, 0xc9, + 0x36, 0x2d, 0xfd, 0x3b, 0x4c, 0x81, 0xa6, 0x25, 0x9d, 0x7f, 0x98, 0xe3, 0x87, 0x29, 0xa1, 0xf5, 0xd6, 0x61, 0xe0, + 0xa1, 0x17, 0x20, 0x77, 0x44, 0x3f, 0xe7, 0x3d, 0xaa, 0x31, 0xf8, 0x9f, 0x0c, 0x33, 0x78, 0x62, 0x3e, 0x0c, 0xd1, + 0x2c, 0x4a, 0x1d, 0xdc, 0x4a, 0x51, 0xdc, 0xbf, 0xc2, 0x9d, 0x91, 0x96, 0xde, 0x7e, 0xa8, 0x76, 0xcc, 0x41, 0xce, + 0xd8, 0x77, 0x51, 0xf2, 0x89, 0xe5, 0xce, 0xa5, 0xd7, 0xe9, 0x7e, 0x4e, 0x9d, 0x3d, 0xb4, 0xcd, 0x3e, 0x54, 0xc7, + 0x68, 0xda, 0x2c, 0x90, 0x47, 0x84, 0xad, 0x8e, 0x97, 0x63, 0x54, 0x0b, 0x49, 0x50, 0x78, 0x59, 0xd8, 0x25, 0x0e, + 0xb7, 0x77, 0x8b, 0xf3, 0xb3, 0xbe, 0x1d, 0xd8, 0x36, 0x58, 0xfc, 0x07, 0x14, 0xb6, 0x12, 0x86, 0x45, 0xbb, 0xc7, + 0x76, 0xe3, 0x1e, 0xdb, 0xdc, 0xac, 0x02, 0x4e, 0x78, 0x90, 0x4e, 0xdd, 0x13, 0x2f, 0xf2, 0x26, 0x21, 0x0c, 0x38, + 0x84, 0x66, 0xd8, 0xa5, 0x37, 0xdc, 0x8d, 0xe5, 0x34, 0x18, 0x0b, 0xf1, 0x93, 0xa8, 0xe0, 0xaf, 0x30, 0x1e, 0x11, + 0x0e, 0xd1, 0xd8, 0xf7, 0xd9, 0x25, 0x1b, 0x2a, 0x3b, 0x03, 0x08, 0x15, 0xb9, 0x3d, 0x77, 0x18, 0x1a, 0xcd, 0x60, + 0xee, 0x30, 0x3c, 0x18, 0xd8, 0xb0, 0x97, 0x60, 0x57, 0x86, 0xd1, 0x61, 0xe7, 0x68, 0x90, 0x86, 0x33, 0x16, 0x68, + 0xda, 0xca, 0xa2, 0xb3, 0x5a, 0x51, 0xf7, 0x68, 0xe0, 0x4c, 0x99, 0xcf, 0xc1, 0x16, 0x77, 0xf0, 0x0d, 0x23, 0x14, + 0x45, 0xf8, 0x81, 0x9d, 0xbd, 0xb8, 0x9c, 0x39, 0xf6, 0xee, 0x96, 0xbd, 0x89, 0xa5, 0x9e, 0x0d, 0xec, 0x05, 0x73, + 0x87, 0x17, 0xae, 0xd9, 0x79, 0xfb, 0x08, 0x41, 0xc5, 0x42, 0x9c, 0xfc, 0x62, 0x60, 0xf7, 0xc5, 0xd4, 0x6d, 0x18, + 0x34, 0x95, 0xcb, 0x8f, 0x2b, 0x7a, 0x40, 0xa8, 0xaa, 0xae, 0x0a, 0x3a, 0x28, 0xeb, 0x06, 0xce, 0xc4, 0x44, 0xa2, + 0x85, 0x93, 0x49, 0x2a, 0x80, 0xc3, 0x83, 0xcd, 0x60, 0x52, 0xa3, 0xdb, 0xf6, 0xd1, 0xe0, 0x22, 0x78, 0x60, 0x3f, + 0x50, 0x2f, 0x63, 0x40, 0x86, 0x89, 0xe9, 0xc7, 0xa0, 0x45, 0xf0, 0xef, 0x39, 0x03, 0x24, 0x2f, 0xa8, 0x68, 0x26, + 0x8b, 0xce, 0xb0, 0xe8, 0x20, 0x40, 0x50, 0xbd, 0x42, 0x5b, 0x7f, 0x62, 0x4d, 0x46, 0x21, 0xc1, 0x0e, 0xb6, 0xd0, + 0x21, 0xdb, 0xec, 0x1c, 0xe1, 0x79, 0x43, 0xce, 0x8b, 0xef, 0x62, 0x0e, 0x2a, 0x61, 0xab, 0x6f, 0xbb, 0x03, 0xdb, + 0xc2, 0xa5, 0xed, 0x65, 0x9b, 0xa1, 0xa0, 0x70, 0xbc, 0x79, 0xc0, 0x82, 0x49, 0x3f, 0x6c, 0x0f, 0x9c, 0x5c, 0x86, + 0x1b, 0xf1, 0xdc, 0x52, 0x48, 0xf0, 0xb6, 0x37, 0x01, 0x81, 0x8e, 0x9c, 0xbb, 0x61, 0x6f, 0xaa, 0x42, 0x28, 0x3a, + 0xde, 0x1c, 0xb9, 0x41, 0x0c, 0x7f, 0x9c, 0x16, 0x32, 0xcd, 0x44, 0xf7, 0x55, 0x9a, 0x19, 0x90, 0x18, 0x29, 0x8b, + 0x3c, 0x09, 0xb3, 0x4d, 0x07, 0x23, 0xb4, 0x20, 0x69, 0x77, 0x07, 0x00, 0xc3, 0xa6, 0xa3, 0x38, 0x6d, 0x4b, 0xb1, + 0x9a, 0xb2, 0xcf, 0x0f, 0xf5, 0x72, 0x0c, 0xd9, 0x60, 0xc8, 0xfc, 0x4a, 0xfb, 0x00, 0x58, 0x41, 0xe2, 0xe5, 0x47, + 0xea, 0xcc, 0xeb, 0x65, 0xed, 0x7c, 0x6b, 0xa1, 0x44, 0x11, 0xf7, 0x0c, 0x09, 0xc5, 0x4a, 0xed, 0x86, 0x09, 0x73, + 0x7b, 0x86, 0xc4, 0xd0, 0x2c, 0x1f, 0xb6, 0x81, 0xe9, 0x55, 0x80, 0x3d, 0x35, 0xb7, 0x45, 0x12, 0x56, 0xcd, 0xbd, + 0x43, 0x60, 0xed, 0x23, 0xe0, 0x21, 0xda, 0x46, 0x3d, 0x15, 0xcd, 0x67, 0x49, 0xf8, 0xb2, 0x71, 0x5c, 0x1c, 0xe1, + 0x89, 0xd0, 0xbe, 0x3f, 0x9c, 0xe7, 0x20, 0x0f, 0xf8, 0x5b, 0xb0, 0x0c, 0x42, 0xd9, 0x14, 0x1d, 0x3d, 0x3c, 0x02, + 0xf6, 0x08, 0xf1, 0x46, 0xd8, 0xdc, 0xa8, 0x46, 0x8b, 0x92, 0x8c, 0x17, 0x3a, 0x18, 0xee, 0x31, 0xe9, 0xda, 0xa3, + 0x60, 0x90, 0x27, 0xc6, 0x0e, 0x9e, 0xf9, 0xfb, 0x43, 0xac, 0xc6, 0x09, 0x0a, 0xb7, 0xa4, 0xdd, 0x56, 0x89, 0xbf, + 0x7d, 0x3f, 0x05, 0x09, 0x8e, 0x75, 0xe0, 0x67, 0xdd, 0xbf, 0x9f, 0x48, 0xa4, 0x76, 0xd3, 0x1e, 0x9d, 0x44, 0x60, + 0x3c, 0x38, 0xf7, 0x53, 0xa8, 0x46, 0x12, 0x51, 0x51, 0x8e, 0x16, 0xa8, 0x79, 0xaa, 0x56, 0xc1, 0x77, 0x68, 0x46, + 0xe0, 0x19, 0x86, 0xad, 0xc9, 0x4f, 0xd5, 0x8d, 0x45, 0x2c, 0xdf, 0x75, 0xe9, 0x68, 0x0b, 0x0f, 0x20, 0x05, 0xa3, + 0x09, 0x86, 0x71, 0x29, 0x28, 0x59, 0xf1, 0xdf, 0xb1, 0x11, 0x2b, 0x9f, 0x1c, 0x66, 0x9b, 0x9b, 0x47, 0xe2, 0xdc, + 0x82, 0x18, 0x87, 0x19, 0xd1, 0xd5, 0xb8, 0x02, 0xa0, 0x3e, 0x9d, 0x13, 0xd7, 0x03, 0xd3, 0x8a, 0x35, 0x5d, 0x8a, + 0x7d, 0x72, 0x98, 0x01, 0x28, 0xb8, 0xe5, 0x1c, 0xfa, 0x83, 0x3f, 0x1e, 0x81, 0x7b, 0xec, 0xff, 0xc1, 0xdd, 0x52, + 0x82, 0xa6, 0x27, 0xcf, 0x14, 0x17, 0x74, 0xc6, 0xda, 0xf1, 0x28, 0x36, 0x1a, 0x14, 0x5e, 0x0a, 0x18, 0x80, 0x36, + 0x07, 0x99, 0x50, 0x71, 0x10, 0x72, 0x54, 0x60, 0xfb, 0xb8, 0xf9, 0x19, 0xee, 0xec, 0xe7, 0x60, 0xe1, 0x0d, 0xf4, + 0xdb, 0x6b, 0x78, 0xfb, 0xa3, 0x7e, 0xfb, 0x89, 0x05, 0xbf, 0x94, 0x32, 0x74, 0x5f, 0x9b, 0xe2, 0x91, 0x9a, 0xa2, + 0x14, 0x4b, 0x64, 0xd0, 0x90, 0xbb, 0xf9, 0x52, 0xcc, 0x86, 0xb9, 0x25, 0x10, 0x43, 0x89, 0xae, 0xdc, 0xe7, 0xd1, + 0x19, 0x12, 0xd7, 0x35, 0x49, 0x61, 0xe4, 0x12, 0x98, 0x08, 0x57, 0x7c, 0x8b, 0xf4, 0x64, 0xfd, 0x36, 0xd8, 0xe0, + 0xb5, 0xbc, 0x03, 0xb4, 0xef, 0xd8, 0x74, 0xc6, 0xaf, 0xf6, 0x49, 0xd1, 0x07, 0x32, 0x6d, 0x40, 0x9c, 0x9d, 0xb7, + 0x7b, 0xf1, 0x2e, 0xeb, 0xc5, 0x20, 0xd5, 0x73, 0xc5, 0x62, 0xb8, 0x57, 0xbd, 0xf7, 0x18, 0xa5, 0x34, 0x99, 0xc9, + 0xab, 0xa1, 0xd7, 0x95, 0xe8, 0x6d, 0x6e, 0x02, 0x82, 0x3d, 0xa3, 0x2b, 0x13, 0x5d, 0xcb, 0x52, 0xd0, 0x04, 0x20, + 0x7a, 0x52, 0x67, 0x39, 0xe2, 0x38, 0xcc, 0x66, 0x83, 0xe2, 0x11, 0x73, 0x57, 0x8e, 0x8a, 0x63, 0x62, 0x77, 0x99, + 0xb0, 0x03, 0x98, 0x11, 0x97, 0xb7, 0x3a, 0x22, 0x3a, 0x2c, 0xfa, 0xeb, 0xf8, 0xf6, 0xb1, 0xc7, 0x37, 0x3b, 0x2e, + 0x68, 0x90, 0xda, 0x58, 0x8f, 0xab, 0xb1, 0xa0, 0x3e, 0x3c, 0xd6, 0x54, 0x2a, 0x8b, 0xcd, 0xcd, 0xb2, 0x7e, 0x54, + 0xab, 0x76, 0x70, 0xed, 0x34, 0xe5, 0xb2, 0x99, 0x0d, 0xc2, 0x81, 0x88, 0x09, 0x14, 0x68, 0x69, 0x65, 0xc5, 0x00, + 0x43, 0xca, 0x72, 0x94, 0x4f, 0x21, 0xf7, 0xe2, 0xb2, 0xd4, 0xa9, 0x2f, 0xcf, 0x64, 0xd0, 0x11, 0x4f, 0x3d, 0xc9, + 0x58, 0x01, 0x05, 0xeb, 0xa5, 0x5e, 0x42, 0x4b, 0x04, 0x98, 0xbf, 0x50, 0x39, 0x34, 0xc2, 0x02, 0x89, 0x42, 0xc3, + 0x2c, 0x51, 0xc6, 0x67, 0x11, 0xc6, 0xa0, 0xed, 0x9f, 0xd5, 0x62, 0x5f, 0x85, 0x32, 0x3a, 0x8a, 0xc3, 0xfc, 0x28, + 0xa0, 0xfa, 0xb9, 0x94, 0x60, 0x93, 0xf0, 0x23, 0xb0, 0x51, 0xe5, 0x78, 0x92, 0x20, 0x7c, 0x1e, 0xe7, 0x8c, 0x3c, + 0x85, 0x0d, 0x09, 0xb3, 0x34, 0x6d, 0x23, 0xd5, 0x2e, 0x32, 0x83, 0x50, 0x2e, 0xcc, 0x3f, 0x31, 0xce, 0x2e, 0xb2, + 0x70, 0xa9, 0x35, 0x98, 0x1f, 0xef, 0x4c, 0x80, 0xb2, 0xeb, 0xeb, 0x4c, 0xf8, 0xb8, 0x11, 0xd9, 0x1b, 0xba, 0x62, + 0x32, 0x50, 0x48, 0x05, 0x4e, 0x44, 0x16, 0x0f, 0x9d, 0xa1, 0xd0, 0x08, 0x07, 0x74, 0x8a, 0x9c, 0xbb, 0xc6, 0xa6, + 0xcf, 0x07, 0xda, 0x37, 0x4a, 0x43, 0x27, 0x01, 0x21, 0x20, 0x70, 0x37, 0xac, 0xa9, 0x74, 0x90, 0x06, 0x09, 0x95, + 0xa2, 0x9f, 0x03, 0xf8, 0x87, 0x91, 0xa4, 0x00, 0xd8, 0x0f, 0xd5, 0x48, 0x11, 0x65, 0x59, 0xe0, 0x02, 0xd0, 0x5c, + 0xfb, 0xb8, 0x12, 0xbe, 0x30, 0x50, 0x61, 0x7a, 0x9a, 0x95, 0x95, 0x42, 0x89, 0x3c, 0x5d, 0x91, 0xb2, 0x46, 0x32, + 0xf9, 0x1c, 0x1d, 0x3e, 0xe5, 0x5d, 0xbf, 0x95, 0x78, 0xe8, 0x82, 0xe7, 0xb0, 0xac, 0xea, 0xf9, 0x4d, 0xc8, 0xc8, + 0xb9, 0x06, 0x5d, 0x21, 0x85, 0xfe, 0x92, 0x93, 0xbc, 0xf7, 0xc6, 0xaf, 0x6a, 0xa9, 0x31, 0x94, 0x7d, 0x5c, 0xd5, + 0x0c, 0xcb, 0xcb, 0x59, 0x15, 0xa6, 0x20, 0xe0, 0x16, 0x2c, 0x09, 0x16, 0x52, 0x43, 0x80, 0x85, 0xed, 0x91, 0x56, + 0x0a, 0xf2, 0x52, 0x87, 0x77, 0x9e, 0x83, 0x15, 0x60, 0x1c, 0x6a, 0xa9, 0x64, 0x1a, 0x49, 0x7c, 0x99, 0xd4, 0x04, + 0x4c, 0xb9, 0x3f, 0x04, 0x3f, 0xb5, 0x79, 0xd2, 0x75, 0xe9, 0xfa, 0xf1, 0x14, 0x53, 0x7b, 0x08, 0xf4, 0xd8, 0xbb, + 0x07, 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, 0x59, 0xaf, + 0x4b, 0x45, 0xb8, 0x3f, 0x07, 0x4c, 0x5a, 0xc1, 0x99, 0x97, 0xd1, 0xc0, 0xfb, 0xf1, 0x69, 0x82, 0x89, 0x2f, 0x88, + 0x15, 0xd8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x38, 0x15, 0x17, 0x29, 0x83, 0x65, 0x45, 0xa9, 0x0d, 0x7f, 0xa4, 0xc8, + 0xd6, 0x5d, 0x1e, 0xe9, 0x2e, 0xc4, 0x02, 0xd8, 0xe9, 0x17, 0x8c, 0x7c, 0xcb, 0x7a, 0x19, 0x30, 0x38, 0xd7, 0x1a, + 0x07, 0x81, 0xdf, 0xdc, 0x4c, 0x8e, 0xca, 0x94, 0xd8, 0xae, 0xc9, 0xea, 0x02, 0x72, 0x4c, 0x02, 0x6c, 0xe0, 0x0e, + 0xc2, 0x52, 0xd9, 0xe3, 0x45, 0x39, 0xc5, 0xe5, 0x52, 0x16, 0x72, 0x33, 0x1d, 0x8b, 0xe6, 0x73, 0x2b, 0xcd, 0xa6, + 0xe3, 0xad, 0xf8, 0xa2, 0xe0, 0x1f, 0x38, 0xb1, 0xb4, 0xea, 0x29, 0xb5, 0xc2, 0xa3, 0xcc, 0x2d, 0x59, 0xa7, 0xa4, + 0x56, 0xd7, 0x0d, 0x54, 0x23, 0x3c, 0x4d, 0xc3, 0x46, 0x20, 0xc4, 0x04, 0x17, 0xbf, 0x6d, 0x32, 0x31, 0xed, 0x2d, + 0x21, 0x75, 0x84, 0xdd, 0x43, 0x39, 0xc1, 0x5d, 0xcd, 0xb3, 0x2f, 0xc3, 0xd9, 0x7a, 0xe6, 0xde, 0x33, 0x98, 0xfb, + 0x69, 0xc8, 0x0c, 0x46, 0x8f, 0x65, 0xc2, 0x8f, 0x8c, 0x7d, 0xe4, 0xaa, 0xea, 0xd9, 0x59, 0x58, 0x89, 0x2c, 0xf1, + 0x64, 0x1c, 0x75, 0x18, 0xa7, 0xa2, 0x35, 0x41, 0x76, 0x7d, 0x5d, 0x98, 0x7b, 0x81, 0x82, 0xa6, 0x1e, 0xab, 0xc7, + 0x69, 0x2b, 0x76, 0x36, 0x22, 0x91, 0x7b, 0x6f, 0x6a, 0x91, 0xc8, 0x8a, 0xcf, 0x71, 0xa4, 0x35, 0x07, 0xb9, 0xcf, + 0xce, 0x96, 0x37, 0xa9, 0xd0, 0x2d, 0x1a, 0x6d, 0x63, 0x8f, 0xea, 0x03, 0x49, 0x3d, 0xa3, 0x02, 0xab, 0x1a, 0xfb, + 0xfe, 0xfd, 0x8e, 0x48, 0xb7, 0x54, 0x8a, 0x0d, 0x43, 0x5a, 0x21, 0x33, 0x46, 0xc1, 0xa0, 0xa4, 0xc8, 0x40, 0x8d, + 0xf2, 0x35, 0x82, 0x61, 0x8f, 0x1a, 0x80, 0xe2, 0x5c, 0x5d, 0xfd, 0xb4, 0x94, 0x6c, 0x21, 0x20, 0x01, 0xd9, 0x84, + 0x62, 0x8d, 0x98, 0x19, 0xf9, 0xe4, 0x23, 0x70, 0xde, 0x80, 0xa3, 0x63, 0x00, 0x7e, 0x81, 0xd8, 0xf4, 0x60, 0x62, + 0xdb, 0x44, 0x14, 0x7d, 0x36, 0xf0, 0x12, 0x80, 0x9d, 0x55, 0xa1, 0xd1, 0x0f, 0x55, 0x0a, 0x18, 0xb2, 0x81, 0x1b, + 0xf0, 0x2a, 0x2c, 0xb7, 0xf7, 0x12, 0xda, 0xc1, 0xeb, 0x0b, 0xd9, 0x7c, 0x03, 0xf3, 0x04, 0xab, 0xd8, 0x9d, 0x5f, + 0x59, 0xd6, 0xe2, 0xdc, 0xe9, 0xa0, 0x51, 0xaf, 0x28, 0x21, 0x6a, 0xf7, 0xb1, 0xf6, 0x25, 0x23, 0x18, 0xf1, 0xfd, + 0x0d, 0x65, 0x1d, 0xaa, 0x71, 0xcb, 0x3d, 0x8d, 0x16, 0x61, 0xba, 0x4c, 0x1a, 0x83, 0x92, 0x75, 0x3f, 0x19, 0x71, + 0x2f, 0xf7, 0x45, 0x2c, 0xb8, 0xc2, 0xd1, 0x08, 0x9b, 0x37, 0x90, 0xa4, 0xa7, 0x3d, 0x3a, 0x60, 0xdf, 0x68, 0xf6, + 0x02, 0xca, 0x7c, 0xac, 0x48, 0x25, 0x21, 0xa5, 0xd9, 0x0d, 0x91, 0x24, 0xac, 0x15, 0x79, 0xea, 0xbc, 0xef, 0x68, + 0x9f, 0x5b, 0x49, 0x04, 0x23, 0x38, 0x89, 0xd3, 0x95, 0x07, 0x4d, 0x01, 0xae, 0xa2, 0x23, 0xa6, 0x6f, 0x82, 0xf2, + 0x1b, 0xe4, 0xf6, 0x52, 0x72, 0x6d, 0xae, 0x61, 0x78, 0x86, 0x04, 0xab, 0x22, 0x11, 0x78, 0x44, 0x0d, 0x38, 0xe6, + 0xab, 0x3c, 0x0f, 0x30, 0xe1, 0x6b, 0x7b, 0x13, 0x00, 0xca, 0xc9, 0x55, 0x71, 0x96, 0x02, 0xdd, 0x80, 0xe5, 0xea, + 0x38, 0x35, 0x2a, 0x12, 0x17, 0x37, 0xa6, 0xab, 0x5b, 0xfa, 0x53, 0xb4, 0x9c, 0xc9, 0x10, 0xd3, 0x41, 0x10, 0x90, + 0xa9, 0x4f, 0x99, 0x23, 0x64, 0xae, 0xb0, 0x3e, 0x67, 0x4e, 0x6d, 0xea, 0x1e, 0xa7, 0x6e, 0x9e, 0xa4, 0x16, 0xab, + 0xd3, 0xa6, 0x94, 0x88, 0x49, 0x89, 0x79, 0x2a, 0x53, 0xb1, 0x95, 0xb8, 0x73, 0xeb, 0x1b, 0x2d, 0xa4, 0x8d, 0x76, + 0x2a, 0x73, 0xb0, 0xb5, 0xbc, 0x17, 0xa2, 0xfd, 0x25, 0x11, 0x9e, 0x95, 0xc8, 0x58, 0x8b, 0x39, 0x73, 0x4c, 0x04, + 0xab, 0x17, 0x53, 0x91, 0x7f, 0x70, 0x74, 0x9a, 0xbd, 0x41, 0x0f, 0x52, 0x6f, 0x20, 0x31, 0x6b, 0xe2, 0xbb, 0x90, + 0x86, 0x3a, 0x42, 0xa0, 0x32, 0xaa, 0x65, 0x3a, 0x4e, 0x2c, 0x15, 0x97, 0xe4, 0xab, 0xf7, 0xfa, 0x38, 0xdf, 0x78, + 0x6e, 0xac, 0x46, 0x10, 0x83, 0xb7, 0x90, 0x1f, 0x79, 0x52, 0x84, 0x03, 0xe1, 0xf2, 0xcd, 0xcd, 0x5e, 0xbe, 0xcb, + 0xaa, 0x10, 0x49, 0x05, 0x63, 0x8c, 0x19, 0xc5, 0xb8, 0x27, 0x6a, 0x6a, 0x31, 0x07, 0x54, 0x65, 0xeb, 0x30, 0xc7, + 0x03, 0x00, 0x68, 0x69, 0x4a, 0x2f, 0xb3, 0xad, 0x3a, 0xcf, 0x25, 0x7c, 0x8c, 0x3c, 0x14, 0xd9, 0xf8, 0xfd, 0x9a, + 0x0c, 0x14, 0x84, 0xfb, 0x5e, 0xc7, 0xc3, 0xc4, 0x38, 0x58, 0x45, 0x21, 0x0b, 0xf4, 0x06, 0xed, 0x55, 0x89, 0x50, + 0xdc, 0x9c, 0xac, 0xc7, 0x0d, 0x27, 0x15, 0x6c, 0xa1, 0x12, 0x96, 0x4a, 0x0b, 0xfc, 0x6a, 0x23, 0x34, 0x4f, 0x19, + 0xf7, 0xde, 0x54, 0x38, 0x83, 0xfe, 0xe0, 0xde, 0x32, 0xa3, 0xbe, 0x5f, 0x3a, 0x91, 0xa9, 0xc0, 0xc4, 0xcd, 0x2c, + 0xb5, 0xdf, 0x2f, 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, 0xf5, 0xbb, 0x28, 0x74, 0x51, 0x9f, + 0x83, 0x21, 0xa4, 0x1f, 0xc1, 0x35, 0x34, 0x78, 0x50, 0x24, 0x8b, 0xc5, 0xda, 0x05, 0x71, 0x7d, 0xcc, 0xa9, 0x76, + 0x28, 0x63, 0x8c, 0x78, 0x5a, 0x72, 0x90, 0x64, 0x70, 0x30, 0x7e, 0x03, 0x03, 0x62, 0x52, 0x12, 0xd2, 0x21, 0x74, + 0x56, 0x66, 0x22, 0x2a, 0x77, 0xf1, 0x76, 0xe3, 0xb2, 0xa6, 0x50, 0x84, 0x9d, 0x60, 0xa6, 0x52, 0x2a, 0x08, 0xa4, + 0xc9, 0x77, 0xaf, 0x53, 0x0b, 0x86, 0x82, 0x68, 0x30, 0x14, 0x90, 0xd7, 0x76, 0x3d, 0x68, 0xf2, 0x51, 0x1c, 0x3c, + 0xaf, 0x50, 0x23, 0x5e, 0x66, 0xf0, 0x35, 0x6c, 0xfe, 0x9a, 0x28, 0xc9, 0x43, 0x2e, 0x62, 0xaf, 0xe0, 0x13, 0x21, + 0x9b, 0xf2, 0xb0, 0x00, 0xfa, 0xa1, 0x5d, 0xd9, 0x4b, 0x77, 0x8b, 0xca, 0xa5, 0x45, 0x63, 0x2b, 0x51, 0xb3, 0xe6, + 0x87, 0xf1, 0x66, 0x7a, 0x04, 0x53, 0x53, 0x02, 0x01, 0x69, 0x2a, 0x27, 0xa9, 0xe6, 0x3d, 0x4c, 0x8f, 0x00, 0x24, + 0xd8, 0xfd, 0x04, 0x16, 0xfa, 0x4d, 0x89, 0x09, 0x16, 0x55, 0x63, 0xb7, 0x19, 0x68, 0xcd, 0x19, 0x69, 0xbe, 0x19, + 0x42, 0xb8, 0xa9, 0xac, 0x67, 0xcc, 0x0e, 0xb0, 0x6d, 0x77, 0xb3, 0x38, 0x4c, 0x37, 0x3b, 0x47, 0x86, 0xe0, 0xc2, + 0xe3, 0xff, 0xa4, 0xc4, 0x34, 0x90, 0x5c, 0xea, 0xc6, 0x4f, 0xa8, 0xc3, 0x3e, 0x91, 0x3a, 0x11, 0x03, 0x9a, 0xab, + 0xd1, 0x74, 0xee, 0x35, 0x47, 0xc9, 0x65, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0x8c, 0x89, 0x62, 0x9e, 0x13, + 0x00, 0xa3, 0xd8, 0xfc, 0x39, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4c, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0xcf, 0xe8, 0x48, + 0x45, 0x65, 0x73, 0x12, 0xf3, 0x6f, 0x4b, 0x30, 0x8d, 0x89, 0x0f, 0xf5, 0x5c, 0x47, 0xa1, 0x00, 0x5f, 0xd9, 0x50, + 0x6a, 0xb6, 0xd7, 0xbf, 0x75, 0xb6, 0x87, 0x72, 0x36, 0xc1, 0x02, 0x0d, 0xba, 0xac, 0xc1, 0x17, 0xb0, 0x0c, 0xee, + 0x48, 0x3f, 0x05, 0xdf, 0x4f, 0xeb, 0xe0, 0x33, 0xf6, 0xbf, 0x00, 0xb4, 0x2a, 0x30, 0xa0, 0xdc, 0x69, 0x1a, 0x56, + 0x42, 0x5c, 0xa2, 0xc2, 0xac, 0xe2, 0xfc, 0x71, 0x9d, 0xd7, 0x4d, 0xcb, 0x12, 0x83, 0xf2, 0x33, 0xd7, 0x70, 0xe3, + 0x7b, 0x8d, 0xfc, 0xf1, 0xbd, 0x97, 0xa0, 0xdb, 0x89, 0xb4, 0xf7, 0xef, 0xe7, 0xf7, 0xc8, 0x42, 0x03, 0x3f, 0x2c, + 0x9a, 0x41, 0x5b, 0xbc, 0x08, 0x90, 0xab, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x03, 0x0c, + 0xc1, 0x63, 0xa7, 0x32, 0xf9, 0x0c, 0x1b, 0x4d, 0xb1, 0x6b, 0x2e, 0x0c, 0x3e, 0x50, 0x95, 0x85, 0xe4, 0xc5, 0x3a, + 0xd9, 0x5e, 0x9c, 0xc3, 0xf3, 0xeb, 0xb8, 0x00, 0xea, 0x20, 0xfa, 0x9a, 0xca, 0x62, 0x03, 0xb9, 0xb8, 0x29, 0x6b, + 0xbd, 0xa2, 0xd1, 0xe8, 0xc6, 0x2e, 0xbc, 0xae, 0xc0, 0x27, 0x51, 0x3a, 0x4a, 0xc4, 0x24, 0x66, 0x52, 0xe5, 0x8a, + 0x5c, 0x1b, 0xdd, 0x4b, 0x5b, 0x34, 0x2f, 0x85, 0x04, 0xaf, 0x08, 0xdc, 0x10, 0xfa, 0x4a, 0x5f, 0xae, 0x36, 0x50, + 0xf0, 0xa8, 0xbd, 0xb9, 0x08, 0x26, 0x26, 0x1e, 0x37, 0xa4, 0xa6, 0x5f, 0x87, 0x53, 0x2b, 0x8b, 0x25, 0x87, 0x5f, + 0xe7, 0x8c, 0x35, 0x14, 0x00, 0xf1, 0xc9, 0xa3, 0xf5, 0x6e, 0xd2, 0x1b, 0xa5, 0x1d, 0x94, 0x46, 0x88, 0xef, 0x2a, + 0x7c, 0xdd, 0x85, 0xe2, 0x2b, 0x57, 0xdd, 0xfb, 0x3a, 0x66, 0xc6, 0x05, 0xa3, 0x97, 0x7c, 0x9a, 0x34, 0xae, 0xdd, + 0xd0, 0x5d, 0x9d, 0xef, 0xbd, 0x2f, 0x65, 0xde, 0xc2, 0x31, 0xb0, 0xc9, 0x31, 0x73, 0x5e, 0x7a, 0x6f, 0x8d, 0x13, + 0xe5, 0x1f, 0xcc, 0x23, 0x5e, 0x39, 0xcc, 0xaa, 0x93, 0xe4, 0x1f, 0x06, 0x3f, 0x04, 0xeb, 0x5b, 0x1a, 0x27, 0xc8, + 0x5d, 0x75, 0x82, 0x4c, 0x94, 0xdb, 0xd0, 0x1b, 0x6e, 0xef, 0xae, 0x02, 0x41, 0x9c, 0x8a, 0xe9, 0xa3, 0x72, 0x5c, + 0x3f, 0x5a, 0xa0, 0x52, 0x11, 0xf1, 0xb9, 0xca, 0x5d, 0x59, 0x9b, 0x1a, 0xea, 0x31, 0x9d, 0xcc, 0x42, 0xd3, 0xac, + 0xc8, 0xa5, 0x6c, 0x7a, 0x8c, 0x5c, 0xb3, 0x53, 0x6d, 0x7e, 0x77, 0xed, 0x21, 0x1d, 0xc7, 0xfb, 0x9e, 0xb5, 0x5a, + 0x70, 0xbf, 0xab, 0x28, 0xbc, 0xeb, 0xc5, 0x46, 0x2a, 0x43, 0xcd, 0x7a, 0x14, 0x7d, 0x1c, 0xb7, 0x99, 0xcb, 0xa3, + 0xec, 0xcf, 0x1a, 0x00, 0xa6, 0x23, 0x2c, 0xba, 0x9b, 0x9e, 0xb1, 0x27, 0xd0, 0xd3, 0x13, 0x19, 0x24, 0x7a, 0xa3, + 0xf3, 0x55, 0xab, 0xc4, 0xd2, 0x15, 0x04, 0x76, 0x6f, 0xc8, 0x58, 0x95, 0xb4, 0x5b, 0xae, 0x5f, 0xce, 0xf3, 0x79, + 0xce, 0x97, 0xf2, 0x7c, 0x6a, 0x16, 0xdd, 0xbd, 0xb6, 0x7b, 0x73, 0x6a, 0xa8, 0x98, 0x6b, 0x75, 0x93, 0xdf, 0x30, + 0x5d, 0x07, 0x43, 0x2d, 0x82, 0xcc, 0x6a, 0x57, 0xbd, 0x28, 0xcb, 0x8d, 0x7a, 0x26, 0xc7, 0x86, 0xf0, 0x4d, 0xa5, + 0x3b, 0x44, 0x37, 0x4c, 0xd5, 0x4c, 0xdf, 0x37, 0xb6, 0x85, 0x6c, 0xf3, 0xf2, 0x6a, 0x94, 0x03, 0xa5, 0xe5, 0xfe, + 0x32, 0x61, 0xf8, 0xfe, 0xfa, 0xfa, 0x7b, 0x21, 0xa7, 0xaa, 0x8e, 0xde, 0xe2, 0xb5, 0xee, 0x19, 0x6c, 0x94, 0xca, + 0x89, 0xb8, 0x60, 0xab, 0x07, 0x6f, 0xee, 0x5e, 0x01, 0xcb, 0x05, 0xec, 0xda, 0x0b, 0xe6, 0x34, 0x86, 0xaa, 0x36, + 0xf0, 0x97, 0xab, 0x07, 0x5b, 0xb5, 0x87, 0xbf, 0x1c, 0x7c, 0x19, 0xdc, 0xd8, 0xd8, 0xd8, 0xc6, 0xdb, 0xb5, 0x44, + 0x90, 0x37, 0x78, 0xa0, 0x8f, 0x57, 0x1f, 0x05, 0x2d, 0x57, 0x88, 0x6d, 0x36, 0x70, 0x28, 0x6c, 0x0d, 0xf2, 0x4d, + 0xca, 0xa4, 0xe1, 0xbc, 0xe0, 0xd9, 0x54, 0xce, 0x50, 0xc8, 0x6b, 0x3e, 0x0e, 0xda, 0x8e, 0xf0, 0xbf, 0xc0, 0xa9, + 0x1d, 0x2f, 0x2f, 0x3e, 0x41, 0x1f, 0xf0, 0x74, 0xa5, 0x34, 0xa5, 0x38, 0xa5, 0x0a, 0xea, 0x2c, 0xd7, 0x79, 0x30, + 0x52, 0x5c, 0x4c, 0x60, 0x71, 0xc1, 0x65, 0xb9, 0x71, 0x36, 0x72, 0xfa, 0x4b, 0xbc, 0xba, 0x48, 0x97, 0x8f, 0x44, + 0xb6, 0x6a, 0xe9, 0xfd, 0xac, 0x4f, 0xb7, 0xed, 0x29, 0xe3, 0x93, 0x6c, 0x44, 0x07, 0x33, 0x3e, 0x4e, 0x84, 0xd7, + 0x27, 0x46, 0xfa, 0x6e, 0x11, 0x98, 0x6e, 0x8e, 0x4d, 0x7e, 0x38, 0x5e, 0x6f, 0x36, 0x6b, 0xdc, 0xc1, 0x3b, 0xe7, + 0x93, 0xb3, 0x28, 0x31, 0xa2, 0xb2, 0xd0, 0xf0, 0x80, 0x56, 0x88, 0x9b, 0xf7, 0x4c, 0x60, 0x5c, 0x76, 0x45, 0x52, + 0xdb, 0x0d, 0x04, 0x2e, 0xf6, 0x38, 0x66, 0xc9, 0xc8, 0xf6, 0xa0, 0x3c, 0xd0, 0x17, 0xa3, 0xe9, 0x16, 0x30, 0x2d, + 0xaf, 0x9d, 0x5d, 0xa4, 0xb6, 0x57, 0x4d, 0x15, 0xc0, 0x2c, 0x59, 0x1e, 0x9f, 0x21, 0xeb, 0x7e, 0x0d, 0x5d, 0xc4, + 0x80, 0xb1, 0x71, 0x65, 0xce, 0x5d, 0xac, 0x5a, 0x11, 0xdf, 0x68, 0x22, 0x4d, 0xea, 0x43, 0xea, 0x7b, 0x14, 0xd6, + 0xea, 0x2a, 0x07, 0x09, 0xdc, 0x23, 0xef, 0x8e, 0xb8, 0xf4, 0xf4, 0x99, 0xc5, 0xb8, 0x4a, 0xdf, 0x52, 0xd7, 0xe2, + 0x9a, 0x61, 0xaf, 0x78, 0x00, 0xf6, 0x07, 0xc6, 0x2d, 0x62, 0x11, 0x6f, 0x67, 0xb5, 0x14, 0xd6, 0xc6, 0x1c, 0x68, + 0x6e, 0xb8, 0xc1, 0xcf, 0xac, 0x5a, 0x33, 0x30, 0xc3, 0x8c, 0x33, 0x92, 0x0f, 0xc6, 0xbd, 0xaa, 0xb1, 0x23, 0x57, + 0x01, 0x44, 0xdf, 0x82, 0x2e, 0xc9, 0xe1, 0x95, 0x2c, 0x57, 0x9d, 0x21, 0xbf, 0x82, 0x75, 0xd6, 0x8b, 0x13, 0x70, + 0x93, 0xa6, 0xac, 0xc4, 0xc4, 0x14, 0x71, 0xb9, 0x59, 0xc6, 0x3c, 0x4d, 0x9f, 0x45, 0x3b, 0x38, 0xb9, 0x91, 0xc0, + 0x11, 0xfb, 0xc6, 0x32, 0x34, 0x13, 0x36, 0x62, 0x22, 0x8d, 0x4a, 0x29, 0x61, 0x03, 0xb9, 0xd4, 0x92, 0xbf, 0xcc, + 0xe5, 0xd5, 0x97, 0xdb, 0x04, 0x07, 0xe4, 0x35, 0xb0, 0x1c, 0x1a, 0xc7, 0x2d, 0x03, 0x89, 0x58, 0x0c, 0x88, 0x51, + 0xab, 0x72, 0x39, 0x19, 0xd5, 0xc9, 0x7c, 0x85, 0x5c, 0xa8, 0xc8, 0x83, 0x5b, 0x02, 0x2f, 0x54, 0xe4, 0x98, 0x3a, + 0x98, 0x95, 0xda, 0x4d, 0x8b, 0x4d, 0x92, 0xf7, 0xcc, 0x80, 0xe4, 0xea, 0x6b, 0x78, 0x68, 0xfc, 0x32, 0xbc, 0xa1, + 0xe8, 0xe9, 0x18, 0x21, 0xa7, 0xa5, 0x31, 0x97, 0xfe, 0x5b, 0x79, 0x9f, 0x56, 0x02, 0xf6, 0x0a, 0xc4, 0x94, 0x81, + 0x4b, 0x6c, 0x5c, 0x90, 0x94, 0xd7, 0xf2, 0x94, 0xdd, 0xd7, 0x50, 0xbe, 0x4b, 0x26, 0x5d, 0xa5, 0xb2, 0xd6, 0x58, + 0x75, 0x3f, 0xcf, 0x59, 0x7e, 0xb5, 0xcf, 0x30, 0x37, 0x19, 0x0d, 0xb2, 0x25, 0x33, 0x9b, 0xf2, 0xab, 0xbd, 0x1b, + 0xbf, 0xf2, 0x50, 0xd2, 0xa1, 0x5a, 0xa5, 0x9b, 0x97, 0x6e, 0x38, 0xc6, 0x8d, 0x1b, 0x8e, 0x00, 0x36, 0x86, 0x9d, + 0x2a, 0x52, 0xeb, 0xfc, 0xf7, 0xa5, 0xf0, 0x93, 0xd8, 0x6b, 0x47, 0x7a, 0xd7, 0x1d, 0xad, 0x4c, 0x4f, 0xbf, 0x01, + 0x55, 0x23, 0x4b, 0xe8, 0x26, 0x54, 0x31, 0x19, 0x89, 0x12, 0xd3, 0x55, 0xca, 0xa3, 0xbe, 0x46, 0x9c, 0x83, 0xb8, + 0xa1, 0xfc, 0xc5, 0x3f, 0x85, 0x57, 0x27, 0x01, 0x1a, 0x51, 0x8b, 0x71, 0x96, 0xf2, 0xd6, 0x38, 0x9a, 0xc6, 0xc9, + 0x55, 0x30, 0x8f, 0x5b, 0xd3, 0x2c, 0xcd, 0x8a, 0x19, 0x70, 0xa5, 0x57, 0x5c, 0x81, 0x0d, 0x3f, 0x6d, 0xcd, 0x63, + 0xef, 0x25, 0x4b, 0xce, 0x19, 0x8f, 0x87, 0x91, 0x67, 0xef, 0xe5, 0x20, 0x1e, 0xac, 0xb7, 0x51, 0x9e, 0x67, 0x17, + 0xb6, 0xf7, 0x21, 0x3b, 0x05, 0xa6, 0xf5, 0xde, 0x5d, 0x5e, 0x9d, 0xb1, 0xd4, 0xfb, 0x78, 0x3a, 0x4f, 0xf9, 0xdc, + 0x2b, 0xa2, 0xb4, 0x68, 0x15, 0x2c, 0x8f, 0xc7, 0xa0, 0x26, 0x92, 0x2c, 0x6f, 0x61, 0xfe, 0xf3, 0x94, 0x05, 0x49, + 0x7c, 0x36, 0xe1, 0xd6, 0x28, 0xca, 0x3f, 0xf5, 0x5a, 0xad, 0x59, 0x1e, 0x4f, 0xa3, 0xfc, 0xaa, 0x45, 0x2d, 0x82, + 0xcf, 0xda, 0xdb, 0xd1, 0xe7, 0xe3, 0x87, 0x3d, 0x9e, 0x43, 0xdf, 0x18, 0xa9, 0x18, 0x80, 0xf0, 0xb1, 0xb6, 0x77, + 0xda, 0xd3, 0xe2, 0x9e, 0x38, 0x51, 0x8a, 0x52, 0x5e, 0x9e, 0x78, 0x57, 0x0c, 0xe0, 0xf6, 0x4f, 0x79, 0xea, 0x81, + 0x2f, 0xc7, 0xb3, 0x74, 0x31, 0x9c, 0xe7, 0x05, 0x0c, 0x30, 0xcb, 0xe2, 0x94, 0xb3, 0xbc, 0x77, 0x9a, 0xe5, 0x40, + 0xb6, 0x56, 0x1e, 0x8d, 0xe2, 0x79, 0x11, 0x3c, 0x9c, 0x5d, 0xf6, 0xd0, 0x56, 0x38, 0xcb, 0xb3, 0x79, 0x3a, 0x92, + 0x73, 0xc5, 0x29, 0x6c, 0x8c, 0x98, 0x9b, 0x15, 0xf4, 0x25, 0x14, 0x80, 0x2f, 0x65, 0x51, 0xde, 0x3a, 0xc3, 0xce, + 0x68, 0xe8, 0xb7, 0x47, 0xec, 0xcc, 0xcb, 0xcf, 0x4e, 0x23, 0xa7, 0xd3, 0x7d, 0xec, 0xa9, 0x7f, 0xfe, 0x8e, 0x0b, + 0x86, 0xfb, 0xca, 0xe2, 0x4e, 0xbb, 0xfd, 0x37, 0x6e, 0xaf, 0x31, 0x0b, 0x01, 0x14, 0x74, 0x66, 0x97, 0x56, 0x91, + 0x25, 0xb0, 0x3e, 0xab, 0x7a, 0xf6, 0x66, 0xe0, 0x37, 0xc5, 0xe9, 0x59, 0xd0, 0x9d, 0x5d, 0x96, 0x88, 0x5d, 0x20, + 0x12, 0x32, 0x25, 0x92, 0xf2, 0x6d, 0xf1, 0x5b, 0x21, 0x7e, 0xb2, 0x1a, 0xe2, 0xae, 0x82, 0xb8, 0xa2, 0x7a, 0x6b, + 0x04, 0xfb, 0x80, 0xc8, 0xdf, 0x29, 0x04, 0x20, 0x13, 0x70, 0x02, 0x73, 0x05, 0x07, 0xbd, 0xfc, 0x66, 0x30, 0xba, + 0xab, 0xc1, 0x78, 0x72, 0x1b, 0x18, 0x79, 0x3a, 0x5a, 0xd4, 0xd7, 0xb5, 0x03, 0xce, 0x69, 0x6f, 0xc2, 0x90, 0x9f, + 0x82, 0x2e, 0x3e, 0x5f, 0xc4, 0x23, 0x3e, 0x11, 0x8f, 0xc4, 0xce, 0x17, 0xa2, 0x6e, 0xa7, 0xdd, 0x16, 0xef, 0x05, + 0x28, 0xb4, 0xa0, 0xe3, 0x63, 0x03, 0x60, 0xa2, 0x2f, 0xd6, 0x7d, 0xc4, 0xe6, 0xbb, 0x5b, 0xbf, 0x54, 0xe3, 0x31, + 0x95, 0x37, 0x28, 0x54, 0x84, 0xfa, 0x66, 0x0b, 0x66, 0xbc, 0xe5, 0xfd, 0x8e, 0x3e, 0xa8, 0x1a, 0x7c, 0xc7, 0x48, + 0xeb, 0x05, 0xcc, 0x33, 0x73, 0x81, 0x7a, 0x69, 0x1f, 0x43, 0x52, 0xad, 0x96, 0x0b, 0x7a, 0x83, 0x63, 0x08, 0x89, + 0x0e, 0x04, 0x9d, 0x7c, 0x50, 0xd0, 0x37, 0x35, 0x32, 0x37, 0x28, 0x9c, 0xcc, 0x85, 0x2d, 0x9f, 0x69, 0xb9, 0x0e, + 0x4a, 0x1a, 0xbc, 0xec, 0x2f, 0x98, 0x6c, 0x00, 0xd2, 0xbb, 0x92, 0xb4, 0xbc, 0x3a, 0x7a, 0x52, 0x2e, 0x5f, 0x36, + 0x24, 0xca, 0x81, 0xaf, 0xcf, 0x27, 0xe8, 0x77, 0xeb, 0xab, 0xeb, 0x46, 0x4a, 0xcd, 0x96, 0xed, 0x0e, 0xb8, 0xce, + 0xca, 0xc2, 0xec, 0x33, 0x5e, 0xe2, 0x28, 0x5f, 0x81, 0x9c, 0xc5, 0xd0, 0xeb, 0xcf, 0xa1, 0x70, 0xd3, 0x94, 0x93, + 0xb6, 0x71, 0xd3, 0xf5, 0x7f, 0x58, 0xf1, 0x98, 0xb2, 0x9d, 0x55, 0x6c, 0x1c, 0x5c, 0x97, 0xe3, 0xa1, 0xb8, 0x76, + 0x58, 0x60, 0xb6, 0xf8, 0x6f, 0xf7, 0x24, 0x1c, 0x8d, 0x56, 0x91, 0xcd, 0xf3, 0x21, 0x26, 0xfd, 0xaf, 0x08, 0x31, + 0xd8, 0xa4, 0xe1, 0x6d, 0x8f, 0x6b, 0xc5, 0xc2, 0x30, 0x7f, 0xc2, 0xfc, 0xaa, 0x02, 0xa3, 0x53, 0x17, 0x71, 0xa9, + 0x41, 0x86, 0x55, 0x14, 0xd8, 0xa8, 0x2b, 0x47, 0x94, 0x60, 0x47, 0x17, 0x3e, 0xfd, 0x79, 0x1a, 0x83, 0x68, 0x3d, + 0x8e, 0x47, 0x74, 0xd1, 0x25, 0x1e, 0xd1, 0xc9, 0x47, 0x8b, 0x32, 0x9d, 0x30, 0x94, 0x0e, 0x05, 0x92, 0xe0, 0xf8, + 0x2c, 0x33, 0x67, 0xec, 0x96, 0x8d, 0xa7, 0x17, 0x86, 0x6e, 0x1e, 0x65, 0xd3, 0x28, 0x4e, 0x03, 0xfc, 0x20, 0x89, + 0xa7, 0x47, 0x0c, 0xb0, 0x8b, 0x07, 0x7f, 0x15, 0xed, 0x3b, 0xae, 0xff, 0x13, 0x08, 0x2e, 0xea, 0x5f, 0x4a, 0xc7, + 0x4f, 0xc3, 0xa5, 0xce, 0x95, 0xeb, 0xa5, 0x20, 0xec, 0xb8, 0xce, 0x6d, 0xa7, 0xc0, 0xca, 0x2e, 0xa3, 0x3f, 0x83, + 0x56, 0x27, 0xe8, 0xb8, 0xcb, 0x2b, 0x60, 0x5c, 0x0c, 0xa8, 0x56, 0x85, 0x4a, 0xe4, 0x1b, 0xcc, 0x21, 0xf9, 0xf3, + 0xfa, 0x5a, 0x7f, 0x3c, 0xa0, 0x71, 0x81, 0x56, 0xa4, 0xdf, 0xc8, 0x4b, 0x98, 0x84, 0x85, 0x7e, 0x16, 0x98, 0x56, + 0xef, 0x1a, 0x5b, 0x4f, 0x6e, 0x25, 0x8c, 0x39, 0x9d, 0xa5, 0x4e, 0x0d, 0x0d, 0x3a, 0xbe, 0x58, 0x33, 0x95, 0x5b, + 0x46, 0xc4, 0xdc, 0x4f, 0x49, 0xe6, 0xd4, 0xaf, 0x3f, 0xc5, 0x18, 0xb8, 0xaf, 0x65, 0x6d, 0x29, 0xf6, 0x1e, 0x9e, + 0xec, 0x0a, 0x21, 0x65, 0x11, 0xeb, 0x86, 0x36, 0x48, 0x0d, 0xdb, 0xfa, 0xe3, 0x10, 0xe8, 0xfc, 0x29, 0xb4, 0x37, + 0x16, 0x8e, 0xba, 0x0b, 0x90, 0xc3, 0x5c, 0x7b, 0x42, 0x51, 0xd3, 0x47, 0x04, 0xec, 0xfe, 0xc6, 0x82, 0x95, 0xbb, + 0x5b, 0xa2, 0x77, 0xff, 0xa4, 0x2c, 0x48, 0xa7, 0x9a, 0xb1, 0xbf, 0x6a, 0x0a, 0x51, 0x07, 0xc3, 0x52, 0xc6, 0x31, + 0x8e, 0x9b, 0x6b, 0x3b, 0x51, 0x04, 0xb9, 0x25, 0xe3, 0x16, 0x98, 0x61, 0x15, 0xe5, 0x20, 0x46, 0x74, 0x0e, 0x4d, + 0x21, 0xd2, 0x46, 0x7a, 0xcb, 0x50, 0x9c, 0x20, 0x04, 0x83, 0x8d, 0x45, 0x5c, 0x86, 0xf0, 0x94, 0x0e, 0xb3, 0x11, + 0xfb, 0xf8, 0xe1, 0x15, 0x5e, 0x93, 0xc8, 0x52, 0x94, 0xa7, 0x99, 0x5b, 0x9e, 0x80, 0x81, 0x85, 0x90, 0xe6, 0xea, + 0x2b, 0x35, 0x00, 0x8c, 0x88, 0x15, 0x59, 0x34, 0x2a, 0x82, 0xc2, 0x4b, 0xdb, 0x1a, 0x08, 0x08, 0xc1, 0x91, 0xc5, + 0x02, 0x30, 0x41, 0xa9, 0x17, 0x07, 0xfc, 0x44, 0xeb, 0x3e, 0x0c, 0xb4, 0xbb, 0x25, 0x1a, 0x01, 0xae, 0x39, 0xa2, + 0x51, 0xa1, 0x8a, 0x59, 0x45, 0x26, 0xba, 0xa3, 0xf8, 0x5c, 0x93, 0x93, 0x52, 0xac, 0xfb, 0xbb, 0x49, 0x74, 0xca, + 0x12, 0x18, 0x12, 0xf8, 0xaa, 0x0d, 0x23, 0x89, 0x57, 0x6b, 0x37, 0x4e, 0x67, 0x73, 0xf9, 0xb5, 0x30, 0x98, 0xb8, + 0x83, 0x07, 0xb8, 0x78, 0x99, 0x61, 0xa0, 0x4e, 0x24, 0x03, 0x39, 0x00, 0x80, 0x48, 0x87, 0x21, 0x08, 0x5d, 0xc5, + 0x2a, 0x50, 0x1a, 0x8f, 0x96, 0xcb, 0x60, 0x7f, 0xcf, 0xb0, 0x34, 0x85, 0xe7, 0x69, 0x9c, 0xe2, 0x63, 0x81, 0x8f, + 0xd1, 0x25, 0x3e, 0x66, 0xf0, 0xa8, 0x71, 0xcf, 0x4b, 0xfb, 0xaf, 0xba, 0x2a, 0x99, 0x5c, 0x01, 0x4b, 0x13, 0x20, + 0xbb, 0xbe, 0x06, 0xb5, 0xa5, 0x49, 0xb0, 0xbb, 0x05, 0xc4, 0x42, 0xee, 0x11, 0xdf, 0x8e, 0xe1, 0x26, 0x19, 0x59, + 0x31, 0x6b, 0x89, 0x72, 0x8b, 0x8c, 0x83, 0x10, 0x7c, 0xc7, 0xdc, 0x69, 0xd8, 0x40, 0x9e, 0xcc, 0x92, 0x79, 0x86, + 0x2f, 0xae, 0x6d, 0x89, 0x8f, 0x7b, 0x08, 0xa2, 0xd0, 0x23, 0x62, 0xa8, 0xcb, 0x98, 0xfc, 0x6c, 0x4f, 0x1c, 0xda, + 0x38, 0x0b, 0x98, 0xa1, 0xe8, 0x85, 0xf2, 0x28, 0x4e, 0x44, 0xe3, 0x15, 0xf8, 0x34, 0xd2, 0x1d, 0x09, 0x9d, 0xdd, + 0xad, 0x0a, 0x36, 0x00, 0x5e, 0x49, 0x04, 0x4e, 0x19, 0x37, 0xb6, 0x28, 0xa7, 0x14, 0x00, 0xb9, 0xcd, 0xab, 0x4f, + 0x3a, 0x01, 0x53, 0x80, 0x11, 0x3d, 0x3a, 0xa6, 0xd9, 0x06, 0x43, 0x20, 0x16, 0xcd, 0xd8, 0xd8, 0xba, 0xf6, 0x5f, + 0xfe, 0xf9, 0x1f, 0x6c, 0x4f, 0x80, 0x98, 0x8d, 0xc7, 0x20, 0xe5, 0xac, 0x75, 0x0d, 0xff, 0xd7, 0x3f, 0xfe, 0xdf, + 0xff, 0xf3, 0x5f, 0x75, 0xdb, 0x14, 0x9a, 0x9e, 0x04, 0xe2, 0x68, 0x41, 0x93, 0x94, 0x52, 0x3c, 0xed, 0x71, 0x94, + 0xae, 0x00, 0xe9, 0x10, 0xb3, 0x18, 0x19, 0x1b, 0x79, 0xb6, 0x05, 0x9a, 0x40, 0x3c, 0x1f, 0x27, 0xec, 0x9c, 0xc9, + 0x0f, 0xcb, 0xe8, 0x41, 0x74, 0xe5, 0x10, 0x2c, 0x18, 0x2e, 0xef, 0xbc, 0xca, 0x6d, 0xa0, 0x68, 0x29, 0x29, 0x5e, + 0x27, 0x98, 0x67, 0x1b, 0x83, 0x36, 0xe7, 0x68, 0xd7, 0x87, 0xf5, 0x40, 0xa5, 0xda, 0xb6, 0x80, 0x97, 0xcc, 0xde, + 0x95, 0x10, 0x37, 0xe1, 0x3a, 0xcd, 0xb1, 0x69, 0xca, 0x8a, 0x62, 0x15, 0x58, 0x40, 0x13, 0xcf, 0xae, 0x9a, 0xd8, + 0xb5, 0x0e, 0x00, 0x40, 0x77, 0x67, 0x47, 0x4c, 0x0b, 0x15, 0x6c, 0x3c, 0x86, 0x0d, 0x8e, 0xba, 0x2d, 0xe1, 0x18, + 0x84, 0x0f, 0xfb, 0xf6, 0x5b, 0x90, 0x25, 0x78, 0xa7, 0xc5, 0xd5, 0x9f, 0xf4, 0xa2, 0xe9, 0x95, 0xb0, 0x33, 0xe6, + 0x10, 0x9d, 0x8d, 0x61, 0xf4, 0x93, 0x81, 0x54, 0x36, 0xfc, 0xb4, 0x8a, 0x31, 0xd6, 0x32, 0xc2, 0xbf, 0xff, 0xcb, + 0x3f, 0xfe, 0x37, 0x18, 0x9b, 0xfa, 0xad, 0xe7, 0x02, 0x68, 0xf5, 0x3f, 0xa1, 0xd5, 0x3c, 0xbd, 0xa5, 0xdd, 0x5f, + 0xfe, 0xfe, 0xbf, 0x43, 0x33, 0xba, 0x28, 0x05, 0x7c, 0x42, 0x10, 0x0d, 0xd1, 0x36, 0xfd, 0x55, 0x20, 0xd5, 0x06, + 0x59, 0x3b, 0xd3, 0x3f, 0x21, 0xd8, 0x05, 0xcf, 0x66, 0x37, 0x82, 0x83, 0x50, 0x0f, 0x93, 0xac, 0x60, 0x1a, 0x1e, + 0xa1, 0x4f, 0x7e, 0x1d, 0x40, 0x34, 0xd7, 0x0c, 0x76, 0x6d, 0x61, 0xe9, 0x71, 0xc4, 0x0a, 0xad, 0xdc, 0x84, 0xf5, + 0x05, 0x2c, 0x18, 0x27, 0x74, 0x28, 0xdc, 0x03, 0x4b, 0x26, 0x9e, 0xe0, 0x81, 0x04, 0x9c, 0x5b, 0xff, 0xf8, 0xda, + 0xea, 0xc1, 0x34, 0xc3, 0x89, 0xb1, 0x44, 0x84, 0x4b, 0x8d, 0x00, 0x7f, 0x41, 0x08, 0x1f, 0xeb, 0xe7, 0xe8, 0x52, + 0x3f, 0xa3, 0xa0, 0x16, 0x13, 0x80, 0xbe, 0x9d, 0xa2, 0x31, 0x66, 0xce, 0x20, 0xb2, 0x33, 0x2a, 0xf7, 0xde, 0x48, + 0xf2, 0x11, 0xc2, 0xf8, 0x18, 0x73, 0x61, 0xf1, 0xe6, 0xd3, 0x3c, 0x67, 0xc7, 0x49, 0x76, 0x81, 0x31, 0x43, 0x22, + 0xd2, 0x9a, 0xfa, 0xf2, 0xdf, 0xfe, 0xd5, 0xf7, 0xff, 0xed, 0x5f, 0xd7, 0x34, 0x98, 0xc0, 0x9e, 0x00, 0x23, 0x9f, + 0x85, 0x9a, 0xce, 0x0d, 0xb4, 0x56, 0x0f, 0x8a, 0x78, 0xae, 0xae, 0x91, 0x88, 0x63, 0xa9, 0xc4, 0x5b, 0x3e, 0x12, + 0xda, 0x9a, 0x29, 0x6e, 0x9f, 0x05, 0x21, 0x5b, 0x33, 0x0d, 0x56, 0xdd, 0x32, 0xcf, 0x89, 0x1b, 0xdc, 0x40, 0x97, + 0x5f, 0x89, 0xf1, 0x6a, 0x30, 0x6e, 0x85, 0xc0, 0x03, 0x6d, 0x26, 0xf4, 0xdd, 0x33, 0xa1, 0xad, 0x02, 0xb1, 0x0c, + 0x52, 0x77, 0xd5, 0x00, 0xf2, 0xac, 0x03, 0x9a, 0x80, 0x9a, 0xc4, 0x95, 0xad, 0x40, 0xe6, 0xd6, 0x69, 0xde, 0x7f, + 0x83, 0x97, 0x1d, 0x91, 0x78, 0x64, 0x29, 0x14, 0x64, 0xd8, 0x30, 0x32, 0x6c, 0xa4, 0x46, 0x35, 0x6d, 0x0a, 0x74, + 0xfc, 0xb2, 0xd5, 0xb6, 0xc3, 0x31, 0x76, 0xaf, 0x69, 0x7f, 0x26, 0xb5, 0x7f, 0x2c, 0xed, 0x7d, 0xa9, 0xfd, 0xf1, + 0x93, 0x36, 0x0d, 0xed, 0x1f, 0xaf, 0xd5, 0xfe, 0x48, 0xb9, 0x01, 0x8e, 0x1c, 0xda, 0x9b, 0x18, 0xdd, 0x32, 0x6c, + 0x0d, 0xd4, 0xc4, 0x83, 0xe1, 0x84, 0x0d, 0x3f, 0x49, 0x33, 0x8b, 0x10, 0xc0, 0x40, 0x94, 0x36, 0x26, 0x05, 0x06, + 0x60, 0x32, 0x9c, 0x94, 0x7a, 0xd3, 0xe3, 0xa3, 0x31, 0x01, 0x73, 0x17, 0x63, 0x86, 0xa2, 0x1f, 0xd6, 0xec, 0x2b, + 0x56, 0x6e, 0xe1, 0x38, 0x62, 0xc3, 0x88, 0x67, 0xc0, 0x6c, 0x0b, 0x07, 0x3b, 0xf1, 0x16, 0x22, 0x58, 0x18, 0xd8, + 0xef, 0xdf, 0xed, 0x1f, 0xd8, 0xde, 0x69, 0x36, 0xba, 0x0a, 0x6c, 0x70, 0xc6, 0xc0, 0x9a, 0x72, 0x7d, 0x3e, 0x61, + 0xa9, 0xa3, 0x3c, 0x9f, 0x2c, 0x61, 0xe0, 0x00, 0x9e, 0x89, 0x6f, 0x5b, 0x34, 0x0f, 0x3a, 0x80, 0xb0, 0xf4, 0xf1, + 0xcb, 0xfe, 0x2e, 0x17, 0xdf, 0x85, 0xe5, 0x39, 0x3e, 0xf6, 0x31, 0xd5, 0x63, 0x77, 0x0b, 0x1e, 0xf0, 0x65, 0x1f, + 0xf5, 0x1e, 0xbd, 0x6d, 0x2c, 0x96, 0xdc, 0x86, 0x01, 0x0e, 0x31, 0xe9, 0x0b, 0x14, 0x0a, 0x6a, 0x75, 0x12, 0x20, + 0x62, 0xf0, 0x08, 0x63, 0x6d, 0xa9, 0x71, 0x11, 0x42, 0xd5, 0x5f, 0x3b, 0x2e, 0x95, 0xdd, 0x4a, 0xf3, 0x8e, 0xb0, + 0x01, 0x39, 0x2e, 0xd8, 0x7b, 0xa4, 0x4b, 0x84, 0xa9, 0x43, 0x45, 0xeb, 0x20, 0xd0, 0x35, 0x95, 0xb9, 0x22, 0x3a, + 0x18, 0xc0, 0x90, 0x99, 0x2b, 0x00, 0x81, 0xbf, 0x84, 0xf6, 0x89, 0xf9, 0xfd, 0x37, 0xf1, 0xa9, 0x26, 0x4d, 0x9c, + 0xc3, 0x3f, 0x79, 0x57, 0xcc, 0xbb, 0x3a, 0xa1, 0x96, 0x2a, 0xd8, 0x80, 0x51, 0x30, 0x0c, 0xca, 0xb4, 0x55, 0x54, + 0x09, 0xec, 0xb4, 0x24, 0x9a, 0x15, 0x2c, 0x50, 0x0f, 0x32, 0xee, 0x80, 0xe1, 0x8b, 0xe5, 0x40, 0x8f, 0x69, 0xcf, + 0x95, 0x7c, 0xb2, 0x30, 0x03, 0x13, 0x8f, 0xda, 0xed, 0x1e, 0x5e, 0xaa, 0x68, 0x45, 0x60, 0x1d, 0xa4, 0x41, 0xc2, + 0xc6, 0xbc, 0xe4, 0x78, 0x6b, 0x7f, 0xa1, 0x22, 0x41, 0x7e, 0x77, 0x27, 0x67, 0x53, 0xcb, 0xc7, 0xff, 0xbf, 0x6d, + 0xec, 0x51, 0x90, 0xf2, 0x49, 0x8b, 0xae, 0xf1, 0xe0, 0x15, 0x49, 0x80, 0xc8, 0x7c, 0x5f, 0x18, 0x13, 0x0d, 0x19, + 0x46, 0xc9, 0x4a, 0x0e, 0xce, 0x37, 0x88, 0x9b, 0xdc, 0x6c, 0x07, 0x72, 0x7a, 0x29, 0x54, 0xb6, 0x1c, 0xac, 0xd9, + 0x76, 0xa5, 0x7f, 0xb4, 0xdc, 0x58, 0x45, 0xbc, 0xea, 0x6f, 0x4b, 0x14, 0x32, 0x62, 0x73, 0xa5, 0x50, 0x51, 0x0b, + 0xd1, 0xc3, 0xc4, 0x69, 0x39, 0x6a, 0x77, 0xab, 0xc5, 0x5c, 0x92, 0xb8, 0x38, 0x24, 0x71, 0x41, 0xe2, 0xef, 0x68, + 0x21, 0xe6, 0x1e, 0x46, 0xc9, 0xd0, 0x41, 0x00, 0xac, 0x96, 0xf5, 0x04, 0xa8, 0xe9, 0xaa, 0xc8, 0x91, 0xff, 0x18, + 0x89, 0x5b, 0x0a, 0x61, 0xb9, 0x82, 0x4a, 0x27, 0x47, 0x65, 0xd9, 0x63, 0xcc, 0x39, 0xfc, 0x20, 0x2f, 0x81, 0x88, + 0xbb, 0xbf, 0xfa, 0xfb, 0x89, 0xed, 0xd2, 0x3d, 0xf2, 0x7e, 0x36, 0x3e, 0x4a, 0x67, 0x2b, 0x66, 0xb7, 0x3d, 0x58, + 0x06, 0xb3, 0xa7, 0xfc, 0x84, 0xe4, 0x4d, 0x7d, 0x4d, 0x36, 0xa7, 0xfe, 0x3f, 0x87, 0x38, 0xc2, 0x1b, 0xc7, 0x46, + 0x13, 0x9d, 0x46, 0xbe, 0x6a, 0x11, 0x7f, 0xda, 0xd8, 0x55, 0x1c, 0x81, 0x7c, 0xbd, 0x2e, 0x92, 0xf5, 0xcd, 0xed, + 0x91, 0xac, 0xe2, 0x8e, 0x91, 0xac, 0x6f, 0x7e, 0xe7, 0x48, 0xd6, 0xd7, 0x66, 0x24, 0x0b, 0x05, 0xf4, 0xab, 0x5f, + 0x13, 0x6d, 0xca, 0xb3, 0x8b, 0x22, 0xec, 0xc8, 0xcc, 0x09, 0x90, 0x75, 0x18, 0x76, 0xfa, 0xeb, 0x47, 0x98, 0x60, + 0xa2, 0x46, 0x7c, 0x89, 0x02, 0x4a, 0x22, 0xd9, 0x13, 0xd4, 0x8a, 0x0c, 0xe7, 0xb4, 0x75, 0x56, 0x65, 0xeb, 0xa1, + 0xba, 0x46, 0x06, 0xae, 0xaf, 0xab, 0x43, 0x6d, 0x5d, 0x15, 0xf0, 0x09, 0xe8, 0x3b, 0xb0, 0xba, 0x63, 0x77, 0x53, + 0xa5, 0xf3, 0x99, 0x23, 0xf4, 0xd4, 0x29, 0x8d, 0x60, 0xa2, 0x85, 0xfd, 0x5f, 0x0e, 0x3b, 0xbd, 0xed, 0xce, 0x14, + 0x7a, 0x83, 0x02, 0x87, 0xb7, 0x76, 0x6f, 0x7b, 0x1b, 0xdf, 0x2e, 0xd4, 0x5b, 0x17, 0xdf, 0x62, 0xf5, 0xb6, 0x83, + 0x6f, 0x43, 0xf5, 0xf6, 0x08, 0xdf, 0x46, 0xea, 0xed, 0x31, 0xbe, 0x9d, 0xdb, 0xe5, 0x21, 0xd3, 0xc0, 0x3d, 0x06, + 0xbe, 0x22, 0x6f, 0x26, 0x50, 0x65, 0xb0, 0xe9, 0xf1, 0xc3, 0x08, 0xd1, 0x59, 0x10, 0x7b, 0xc2, 0xbb, 0x0c, 0x72, + 0xef, 0x02, 0x34, 0x4e, 0x40, 0xd9, 0x86, 0xcf, 0xf1, 0x3b, 0x1c, 0xe0, 0x24, 0x1d, 0xc4, 0x53, 0xa6, 0x3e, 0x48, + 0xac, 0xb0, 0x06, 0x03, 0xf6, 0xb0, 0x7d, 0x54, 0xf6, 0xf4, 0x3a, 0x89, 0x78, 0x96, 0xca, 0xe6, 0xa0, 0x95, 0xab, + 0xea, 0xc4, 0x74, 0x2d, 0xbd, 0xc2, 0x6b, 0xf4, 0x97, 0x11, 0x8f, 0x18, 0x83, 0x61, 0xd6, 0xba, 0x04, 0x0f, 0x76, + 0xa5, 0x4e, 0x43, 0x88, 0xb4, 0x4e, 0x23, 0x9c, 0xf4, 0xdb, 0x41, 0x74, 0xa6, 0x9f, 0xdf, 0x80, 0xa5, 0x1d, 0x9d, + 0xc9, 0x96, 0xeb, 0x75, 0x18, 0x81, 0x68, 0xea, 0x2f, 0x05, 0x04, 0x99, 0x62, 0xb0, 0x34, 0xe8, 0x49, 0x4b, 0xfd, + 0x85, 0xd4, 0xa9, 0x6b, 0x34, 0x9a, 0xbe, 0x5e, 0x04, 0x14, 0xad, 0x0a, 0x76, 0xc1, 0xe0, 0xa7, 0x52, 0x41, 0x61, + 0xa8, 0xc0, 0x02, 0x51, 0xbd, 0x46, 0x95, 0xe9, 0x60, 0xc3, 0x5a, 0x85, 0x66, 0x29, 0x5d, 0x66, 0x9e, 0xee, 0xe8, + 0xa3, 0x9d, 0x65, 0xf1, 0xfa, 0x59, 0x67, 0x88, 0xff, 0x49, 0xe1, 0xfd, 0xd9, 0x78, 0x3c, 0xbe, 0x51, 0xb7, 0x7d, + 0x36, 0x1a, 0xb3, 0x2e, 0xdb, 0xe9, 0x61, 0xe4, 0xbf, 0x25, 0xc5, 0x69, 0xa7, 0x24, 0xda, 0x2d, 0xee, 0xd6, 0x18, + 0x25, 0x2f, 0xa8, 0xbb, 0xbb, 0x2b, 0xc1, 0x12, 0xa8, 0xb2, 0x00, 0xe1, 0x7f, 0x16, 0xa7, 0x41, 0xbb, 0xf4, 0xcf, + 0xa5, 0xd6, 0xf8, 0xec, 0xc9, 0x93, 0x27, 0xa5, 0x3f, 0x52, 0x6f, 0xed, 0xd1, 0xa8, 0xf4, 0x87, 0x0b, 0x8d, 0x46, + 0xbb, 0x3d, 0x1e, 0x97, 0x7e, 0xac, 0x0a, 0xb6, 0xbb, 0xc3, 0xd1, 0x76, 0xb7, 0xf4, 0x2f, 0x8c, 0x16, 0xa5, 0xcf, + 0xe4, 0x5b, 0xce, 0x46, 0xb5, 0xe3, 0x83, 0xc7, 0x6d, 0xa8, 0x14, 0x8c, 0xb6, 0x40, 0xef, 0x52, 0x3c, 0x06, 0xd1, + 0x9c, 0x67, 0x60, 0xd8, 0x95, 0xbd, 0x02, 0xe4, 0xf3, 0x58, 0x4a, 0x78, 0xf1, 0xbd, 0x5f, 0x94, 0xea, 0xaf, 0x4c, + 0xa9, 0x8e, 0xcc, 0x4c, 0xd2, 0xbc, 0x20, 0x6d, 0xd0, 0xac, 0x46, 0xce, 0xa2, 0xea, 0x57, 0x61, 0x51, 0x09, 0x7b, + 0x94, 0x36, 0xd8, 0x52, 0xc8, 0xf8, 0x1f, 0xd6, 0xc9, 0xf8, 0xef, 0x6f, 0x97, 0xf1, 0xa7, 0x77, 0x13, 0xf1, 0xdf, + 0xff, 0xce, 0x22, 0xfe, 0x07, 0x53, 0xc4, 0x0b, 0x21, 0xb6, 0x07, 0xa6, 0x33, 0xd9, 0xcc, 0xa7, 0xd9, 0x65, 0x0b, + 0xb7, 0x44, 0x6e, 0x93, 0xf4, 0x9c, 0xde, 0x49, 0xf8, 0xaf, 0xc8, 0x07, 0x53, 0x83, 0x19, 0x1f, 0x0f, 0xe6, 0xd9, + 0xd9, 0x59, 0xc2, 0x94, 0x8c, 0x37, 0x2a, 0xc8, 0x1c, 0x7f, 0x97, 0x86, 0xf6, 0x3b, 0xf4, 0x8c, 0xab, 0x92, 0xf1, + 0x18, 0x8a, 0xc6, 0x63, 0x5b, 0xe5, 0x4b, 0x83, 0x3c, 0xa3, 0x56, 0x6f, 0x6b, 0x25, 0xd4, 0xea, 0x8b, 0x2f, 0xcc, + 0x32, 0xb3, 0x40, 0x86, 0xf4, 0x4c, 0x63, 0x44, 0xd6, 0x8c, 0xe2, 0x02, 0xf7, 0x60, 0xf5, 0xb1, 0x63, 0xb4, 0x77, + 0xa6, 0xa0, 0x54, 0xe2, 0x21, 0x9e, 0x8b, 0x34, 0x3f, 0x2c, 0x23, 0x72, 0xdb, 0x97, 0x91, 0xab, 0xce, 0xbf, 0x8d, + 0x6f, 0x18, 0x56, 0x67, 0xde, 0xb0, 0xf8, 0x32, 0xbf, 0xe5, 0xe9, 0xd5, 0xab, 0x91, 0xb3, 0x87, 0x97, 0x7f, 0x8b, + 0x77, 0x69, 0x23, 0x6f, 0x50, 0x80, 0x1d, 0x86, 0x26, 0xa6, 0xa5, 0x20, 0x58, 0x75, 0x81, 0xa2, 0xaa, 0xec, 0x19, + 0x9d, 0x64, 0x7a, 0x19, 0x0e, 0x39, 0xa8, 0x91, 0x25, 0x30, 0x07, 0x93, 0xba, 0x90, 0x3e, 0x66, 0x2f, 0x92, 0x6e, + 0xce, 0xe5, 0x57, 0xcf, 0xe9, 0x70, 0x66, 0x21, 0xf5, 0x87, 0x4c, 0xc7, 0xa8, 0x7a, 0xd2, 0x79, 0x08, 0xcd, 0x30, + 0x2a, 0xd5, 0x19, 0x08, 0x10, 0x6e, 0x86, 0x9f, 0x68, 0x12, 0x43, 0xa8, 0x83, 0x82, 0x8a, 0x7a, 0xd7, 0xd7, 0xe6, + 0x97, 0x42, 0x6b, 0x5f, 0x95, 0x6c, 0xf0, 0x00, 0xc7, 0x4f, 0xfc, 0xa2, 0x36, 0xc8, 0xe6, 0xdc, 0xc1, 0x33, 0x80, + 0x05, 0x1e, 0x31, 0x78, 0x3b, 0xed, 0x36, 0xa8, 0x18, 0x5f, 0x7c, 0x07, 0xca, 0xd1, 0x9d, 0x05, 0xbe, 0x6c, 0xdd, + 0xb9, 0xc4, 0xd2, 0x77, 0xd9, 0x2a, 0x12, 0xdf, 0xbf, 0x2f, 0x11, 0x35, 0xee, 0x0e, 0xa9, 0x45, 0x6c, 0xbe, 0xfb, + 0xca, 0x77, 0x34, 0x08, 0xeb, 0xae, 0xe2, 0x60, 0x99, 0x5b, 0x5b, 0x2f, 0xc4, 0xb6, 0xc2, 0xaa, 0x59, 0x06, 0xe7, + 0x16, 0x9d, 0x59, 0x5c, 0x18, 0x01, 0xfc, 0xda, 0x36, 0x28, 0x55, 0x04, 0x5f, 0x84, 0xe1, 0xf7, 0xd0, 0xc5, 0x15, + 0x8e, 0xb7, 0x02, 0xba, 0xe1, 0xf2, 0x56, 0x90, 0xa3, 0x33, 0xac, 0x19, 0x5d, 0x55, 0xa9, 0x82, 0xd2, 0x3c, 0x82, + 0x31, 0x90, 0xa1, 0x48, 0x3a, 0xac, 0x71, 0x2a, 0xf4, 0x16, 0x4c, 0x43, 0x02, 0x58, 0xfb, 0x75, 0xe8, 0xd6, 0xd8, + 0x0a, 0x6c, 0x21, 0x2d, 0x40, 0xe9, 0x61, 0x87, 0xbe, 0x55, 0x03, 0x3d, 0x5d, 0x0e, 0xc0, 0xdf, 0xe8, 0xe4, 0x9d, + 0xf8, 0xc5, 0x85, 0x07, 0xff, 0xac, 0x3f, 0x2c, 0x40, 0xca, 0x9f, 0x7e, 0x8a, 0x39, 0xd8, 0xd4, 0xb3, 0x16, 0x86, + 0x5f, 0x28, 0x4e, 0x2b, 0xd5, 0x21, 0x1d, 0x45, 0x8b, 0x2b, 0x63, 0xbd, 0x79, 0x81, 0xbe, 0x20, 0x39, 0x3d, 0x41, + 0x9a, 0xa5, 0xac, 0x57, 0x4f, 0x39, 0x30, 0xfd, 0x0e, 0x45, 0xac, 0xa3, 0x45, 0x86, 0xbe, 0x23, 0xbf, 0x02, 0xdf, + 0x51, 0xa8, 0xd1, 0xb6, 0x72, 0x3a, 0xda, 0x2b, 0xdb, 0x07, 0x92, 0xb6, 0x9b, 0x64, 0x2d, 0xe4, 0xcb, 0xce, 0xd5, + 0x3a, 0xe7, 0xe8, 0xb6, 0x03, 0x78, 0x0c, 0x0a, 0xab, 0x7f, 0x46, 0xe6, 0x42, 0xb3, 0x98, 0x0e, 0xe0, 0xef, 0x02, + 0x59, 0x10, 0x8d, 0xf1, 0x0b, 0x8b, 0x77, 0x69, 0x79, 0x4a, 0xd9, 0xaf, 0x0b, 0x54, 0xeb, 0x41, 0xe7, 0x09, 0x78, + 0x7b, 0x77, 0x1e, 0xfe, 0x66, 0xf4, 0x4b, 0x49, 0x23, 0x75, 0x89, 0xd9, 0xb6, 0x7b, 0x28, 0x2f, 0x92, 0xe8, 0x0a, + 0x9c, 0x4e, 0xb2, 0x31, 0x4e, 0x31, 0x7a, 0xdc, 0x9b, 0x65, 0x32, 0x93, 0x24, 0x67, 0x09, 0xfd, 0x8c, 0x89, 0x5c, + 0x8a, 0xed, 0x47, 0xb3, 0x4b, 0xb5, 0x1a, 0x9d, 0x46, 0x86, 0xc8, 0xef, 0x9a, 0x08, 0xb2, 0x3e, 0xf3, 0xa4, 0x9e, + 0xcc, 0xb0, 0x03, 0x30, 0x08, 0xc3, 0xa6, 0x95, 0x0b, 0xa8, 0xda, 0x50, 0x62, 0xa4, 0xc2, 0x54, 0x03, 0x59, 0xfe, + 0x36, 0xa8, 0xca, 0xa8, 0x60, 0x3d, 0xfc, 0xd4, 0x65, 0x0c, 0xae, 0xad, 0x34, 0x9e, 0xa6, 0xf1, 0x68, 0x94, 0xb0, + 0x9e, 0xb2, 0x8f, 0xac, 0xce, 0x23, 0xcc, 0x24, 0x31, 0x97, 0xac, 0xbe, 0x2a, 0x06, 0xf1, 0x34, 0x9d, 0xa2, 0x53, + 0xb0, 0xd7, 0xf0, 0x7b, 0x95, 0x2b, 0xc9, 0x29, 0x53, 0x2c, 0xda, 0x15, 0xf1, 0xe8, 0xb9, 0x8e, 0xcb, 0x0e, 0x18, + 0x8b, 0xb4, 0xe0, 0xed, 0x1e, 0xcf, 0x66, 0x41, 0x6b, 0xbb, 0x8e, 0x08, 0x56, 0x69, 0x14, 0xbc, 0x15, 0x68, 0x79, + 0x68, 0x1d, 0x08, 0x2d, 0x67, 0xf9, 0x1d, 0x59, 0x46, 0x03, 0xe0, 0x37, 0x11, 0x75, 0x51, 0x59, 0x47, 0xe6, 0xaf, + 0xb3, 0x5b, 0x3e, 0x5f, 0xbd, 0x5b, 0x3e, 0x57, 0xbb, 0xe5, 0x66, 0x8e, 0xfd, 0x6c, 0xdc, 0xc1, 0xff, 0x7a, 0x15, + 0x42, 0xb0, 0x2a, 0x40, 0x0e, 0x0b, 0xed, 0xe2, 0x56, 0x17, 0xfe, 0x8f, 0x86, 0x6e, 0x7b, 0xf8, 0x9f, 0x0f, 0x16, + 0x60, 0xdb, 0xc2, 0x42, 0xfc, 0xd7, 0xae, 0x55, 0x75, 0x1e, 0x62, 0x1d, 0xf6, 0xda, 0x59, 0xae, 0xeb, 0xde, 0xbc, + 0x69, 0x41, 0x5e, 0x71, 0x27, 0x50, 0xc2, 0x18, 0x5c, 0xb5, 0xe8, 0xf4, 0x14, 0x4a, 0xc7, 0xd9, 0x70, 0x5e, 0xfc, + 0xad, 0x84, 0x5f, 0x12, 0xf1, 0xc6, 0x2d, 0xdd, 0x18, 0x47, 0x75, 0x15, 0x69, 0x49, 0x6a, 0x84, 0x85, 0x5e, 0xa7, + 0xa0, 0x00, 0xc6, 0x64, 0x4e, 0xd7, 0x7f, 0xb8, 0x62, 0x13, 0xfc, 0x7f, 0x59, 0x9b, 0x95, 0xc8, 0xfc, 0x47, 0x89, + 0x71, 0x23, 0x11, 0x7e, 0x15, 0x0d, 0xcc, 0x35, 0x6c, 0x3f, 0x59, 0x0d, 0xee, 0xa1, 0x9a, 0xe9, 0x48, 0x29, 0x05, + 0xa9, 0x77, 0xc0, 0x0b, 0x88, 0xe6, 0x09, 0xbf, 0x79, 0xd4, 0x75, 0x9c, 0xb1, 0x34, 0xea, 0x0d, 0x02, 0xbd, 0x6a, + 0x7b, 0x47, 0x29, 0xfd, 0xd9, 0xe7, 0x0f, 0xf1, 0x3f, 0x11, 0x38, 0x3b, 0xad, 0x7c, 0x23, 0x11, 0x1b, 0x40, 0xdf, + 0x68, 0x5a, 0x73, 0x7e, 0x84, 0x06, 0x27, 0xff, 0xe7, 0xae, 0xad, 0xd1, 0x58, 0xbf, 0x53, 0x73, 0x69, 0x95, 0xfe, + 0xaa, 0xd6, 0xbf, 0x6e, 0xf0, 0x3b, 0xb6, 0x1d, 0x0a, 0x87, 0xa0, 0xde, 0x56, 0xc6, 0x03, 0x97, 0x1a, 0x2b, 0x8a, + 0xdf, 0xb5, 0x7d, 0x65, 0x12, 0x53, 0x8f, 0x69, 0x78, 0xaa, 0x9d, 0x48, 0x79, 0x78, 0x8f, 0x3d, 0x84, 0x1f, 0xf9, + 0x25, 0x0b, 0x1f, 0xe0, 0xd7, 0xd8, 0xac, 0xcb, 0x69, 0x92, 0x82, 0x59, 0x35, 0xe1, 0x7c, 0x16, 0x6c, 0x6d, 0x5d, + 0x5c, 0x5c, 0xf8, 0x17, 0xdb, 0x7e, 0x96, 0x9f, 0x6d, 0x75, 0xdb, 0xed, 0x36, 0x7e, 0x44, 0xcb, 0xb6, 0xce, 0x63, + 0x76, 0xf1, 0x14, 0xdc, 0x0f, 0xfb, 0xb1, 0xf5, 0xc4, 0x7a, 0xbc, 0x6d, 0xed, 0x3c, 0xb2, 0x2d, 0x52, 0x00, 0x50, + 0xb2, 0x6d, 0x5b, 0x42, 0x01, 0x84, 0x36, 0x14, 0xf7, 0x77, 0xcf, 0x94, 0x0d, 0x87, 0x97, 0x14, 0x84, 0x85, 0x04, + 0xfe, 0x5b, 0xf6, 0x89, 0xd5, 0xb7, 0xba, 0x28, 0x6b, 0x49, 0x35, 0xa2, 0x5e, 0x71, 0xbf, 0x0f, 0xa3, 0x59, 0x40, + 0x6c, 0x64, 0x16, 0x62, 0x98, 0x4c, 0x94, 0xd2, 0x14, 0x68, 0x97, 0x9e, 0xc2, 0x13, 0x66, 0xb5, 0x59, 0xf0, 0xfc, + 0xa6, 0xfb, 0x18, 0x74, 0xdc, 0x79, 0xeb, 0xe1, 0xb0, 0xdd, 0xea, 0x58, 0x9d, 0x56, 0xd7, 0x7f, 0x6c, 0x75, 0xc5, + 0xff, 0x83, 0x8c, 0xdc, 0xb6, 0x3a, 0xf0, 0xb4, 0x6d, 0xc1, 0xfb, 0xf9, 0x43, 0x91, 0x5b, 0x12, 0xd9, 0x5b, 0xfd, + 0x5d, 0xfc, 0x4d, 0x29, 0x40, 0xea, 0x73, 0x5b, 0xfc, 0x0a, 0x9e, 0xfd, 0x99, 0x59, 0xda, 0x79, 0xb2, 0xb2, 0xb8, + 0xfb, 0x78, 0x65, 0xf1, 0xf6, 0xa3, 0x95, 0xc5, 0x0f, 0x77, 0xea, 0xc5, 0x5b, 0x67, 0xa2, 0x4a, 0xcb, 0x85, 0xd0, + 0x9e, 0x46, 0xc0, 0x28, 0x97, 0x4e, 0x07, 0xe0, 0x6c, 0x5b, 0x2d, 0xfc, 0xf3, 0xb8, 0xeb, 0xea, 0x5e, 0xa7, 0xd8, + 0x4b, 0x63, 0xf9, 0xf8, 0x09, 0x60, 0xf9, 0xb2, 0xfb, 0x68, 0x88, 0xed, 0x08, 0x51, 0xf8, 0x77, 0xbe, 0xfd, 0x64, + 0x08, 0x1a, 0xc1, 0xc2, 0x7f, 0xf0, 0xdf, 0x64, 0xa7, 0x3b, 0x14, 0x2f, 0x6d, 0xac, 0xff, 0xb6, 0xf3, 0xb8, 0x80, + 0xa6, 0xf8, 0xdf, 0x2f, 0xda, 0x84, 0x46, 0x03, 0xde, 0x1c, 0xf7, 0x21, 0xd0, 0xe8, 0xc9, 0xa4, 0xeb, 0x7f, 0x7e, + 0xfe, 0xd8, 0x7f, 0x32, 0xe9, 0x3c, 0xfe, 0x56, 0xbc, 0x25, 0x40, 0xc1, 0xcf, 0xf1, 0xdf, 0xb7, 0xdb, 0xed, 0x49, + 0xab, 0xe3, 0x3f, 0x39, 0xdf, 0xf6, 0xb7, 0x93, 0xd6, 0x23, 0xff, 0x09, 0xfe, 0xab, 0x86, 0x9b, 0x64, 0x53, 0x66, + 0x5b, 0xb8, 0xde, 0x0d, 0xbf, 0xd7, 0x9c, 0xa3, 0xfb, 0xd0, 0xda, 0x79, 0xf8, 0xf2, 0x09, 0xac, 0xd1, 0xa4, 0xd3, + 0x85, 0xff, 0x5f, 0xf7, 0xf8, 0x2d, 0x12, 0x5e, 0x0e, 0x1c, 0x31, 0x4c, 0x2f, 0x56, 0x84, 0xa3, 0x0f, 0xba, 0x3d, + 0xf0, 0xfe, 0xb4, 0x2e, 0x00, 0xc2, 0xf8, 0xad, 0x01, 0x10, 0xce, 0xef, 0x16, 0x01, 0xa1, 0x5f, 0x1b, 0xf8, 0x1d, + 0x23, 0x20, 0x7f, 0x6a, 0x06, 0xb9, 0x2f, 0xd9, 0x52, 0xa0, 0xa3, 0xe9, 0xac, 0xbd, 0x65, 0xce, 0xe1, 0x97, 0xf8, + 0xe3, 0x06, 0x65, 0x0f, 0x5a, 0x73, 0x6e, 0xc6, 0x83, 0x32, 0xdc, 0xc8, 0x97, 0xf2, 0xe2, 0x43, 0xc1, 0xd7, 0x10, + 0x24, 0xbe, 0x9d, 0x20, 0xdf, 0xde, 0x8d, 0x1e, 0xf1, 0xef, 0x4c, 0x8f, 0x82, 0x1b, 0xf4, 0xa8, 0x45, 0xdc, 0x29, + 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0x9c, 0xbe, 0xc5, 0xb6, 0xc5, 0xb0, 0xa8, 0xb0, 0x45, 0xce, 0xe6, 0xd3, + 0x5f, 0x73, 0x44, 0x20, 0xd2, 0xcd, 0x43, 0x5b, 0x46, 0x61, 0x66, 0xf8, 0xd1, 0x62, 0xf5, 0x72, 0x2e, 0xae, 0x34, + 0x85, 0x74, 0x1f, 0x71, 0x47, 0x47, 0x70, 0xf0, 0x06, 0x40, 0xb8, 0xc8, 0x78, 0x84, 0xbf, 0x8a, 0x05, 0xe4, 0xa6, + 0xdf, 0xcf, 0x8a, 0x79, 0xc2, 0x30, 0x9d, 0x66, 0x28, 0x3e, 0x20, 0x0b, 0x8f, 0xf2, 0xae, 0x21, 0xa6, 0xb0, 0x7f, + 0x83, 0xe9, 0xf7, 0xea, 0xec, 0x60, 0x8a, 0x71, 0x84, 0x37, 0x6c, 0x14, 0x47, 0x8e, 0xed, 0xcc, 0x60, 0x23, 0xc3, + 0x2c, 0xad, 0x5a, 0xee, 0x3b, 0xa5, 0xbd, 0xbb, 0xb6, 0xfa, 0x69, 0xa6, 0x1c, 0x3f, 0x75, 0x17, 0x1e, 0xca, 0xb8, + 0xa3, 0x2d, 0x1d, 0x03, 0x18, 0x5f, 0x95, 0xe4, 0xa8, 0x03, 0x2a, 0x63, 0xc2, 0x16, 0xd6, 0x44, 0xc7, 0xef, 0x82, + 0x77, 0x41, 0xc5, 0xf8, 0xe9, 0xb0, 0xef, 0x9d, 0xd6, 0x36, 0x58, 0x3b, 0x46, 0x37, 0x3d, 0xd0, 0x91, 0xfe, 0xa5, + 0x1f, 0xfd, 0x6b, 0x74, 0xf5, 0x0b, 0x03, 0xb6, 0xe0, 0x88, 0xcf, 0x04, 0xee, 0xb6, 0xf8, 0x44, 0x83, 0x48, 0x28, + 0xc1, 0x0b, 0x73, 0x50, 0xe6, 0x98, 0xbf, 0x4a, 0x26, 0x3e, 0x4d, 0x26, 0x7e, 0x80, 0xb0, 0xac, 0x9a, 0x70, 0x77, + 0x41, 0x67, 0x23, 0xf8, 0x23, 0x9a, 0x98, 0x68, 0x8a, 0xa1, 0xf2, 0xd0, 0xa0, 0x29, 0xbe, 0xbb, 0x35, 0x22, 0x73, + 0x4f, 0x03, 0x44, 0x04, 0x0e, 0xe5, 0xdf, 0xaa, 0x58, 0x3d, 0xc8, 0xa0, 0x16, 0x38, 0xfa, 0xf8, 0xb3, 0x2f, 0xf4, + 0x67, 0x29, 0x64, 0x26, 0x02, 0x21, 0x8d, 0xd2, 0x6a, 0xa8, 0x2a, 0x34, 0x56, 0x3c, 0xbd, 0x3a, 0x90, 0xdf, 0x3c, + 0xb0, 0x31, 0x4a, 0x4d, 0xa7, 0x13, 0xd5, 0xf7, 0xd6, 0x36, 0x41, 0x35, 0xd2, 0xaf, 0xa0, 0x52, 0x82, 0x01, 0x6a, + 0x3f, 0xbc, 0x72, 0x60, 0x49, 0x2f, 0x29, 0xb4, 0x85, 0xee, 0x1b, 0xb1, 0xf3, 0x78, 0x28, 0x55, 0x98, 0x67, 0xc9, + 0xab, 0x52, 0x2d, 0x5a, 0x9a, 0xb0, 0xe3, 0x89, 0x38, 0x01, 0xbc, 0xa0, 0x06, 0x0f, 0xd3, 0xcc, 0xee, 0x3f, 0xe8, + 0xad, 0x23, 0x3e, 0xfe, 0x24, 0xeb, 0x21, 0xf8, 0xa5, 0x7f, 0x1b, 0x3e, 0xc0, 0x1f, 0x65, 0x7d, 0x70, 0x64, 0xbb, + 0x3e, 0x29, 0x80, 0x07, 0xd5, 0x2f, 0xb3, 0xa2, 0xf4, 0xdb, 0x04, 0x5d, 0xed, 0xdd, 0x55, 0x69, 0x4b, 0x05, 0xdd, + 0xdd, 0xa9, 0x14, 0x34, 0x3c, 0x1b, 0x12, 0x19, 0x94, 0x45, 0xd7, 0xdf, 0x31, 0xc4, 0xfe, 0x79, 0x0b, 0xff, 0xd6, + 0x04, 0xff, 0x43, 0x68, 0xa0, 0x24, 0xff, 0x6b, 0x68, 0xbe, 0x2d, 0x94, 0x0c, 0xf4, 0xfb, 0x81, 0xc4, 0xb2, 0x10, + 0xc9, 0xf5, 0x6d, 0xb0, 0xe2, 0xc0, 0x4c, 0x24, 0x63, 0xd8, 0x9e, 0x11, 0x5b, 0x13, 0xbb, 0x52, 0x46, 0x8e, 0x9e, + 0x43, 0x5f, 0x47, 0x7f, 0xc6, 0x7c, 0x55, 0x9d, 0x57, 0x93, 0x12, 0x2b, 0xa6, 0xc0, 0x7d, 0xdd, 0x38, 0x94, 0xeb, + 0x89, 0x3c, 0x6f, 0xfd, 0x1d, 0x94, 0xf5, 0x0c, 0x2d, 0x13, 0xc2, 0x5d, 0x43, 0x44, 0x30, 0xfa, 0xd4, 0x2a, 0x4d, + 0xf2, 0x6a, 0x54, 0x36, 0xe7, 0x07, 0xb3, 0x06, 0x7f, 0x97, 0xb2, 0xba, 0xe5, 0x23, 0xaf, 0xef, 0x62, 0xca, 0xc5, + 0x28, 0xce, 0xe9, 0x56, 0xb8, 0x02, 0xbd, 0x16, 0x78, 0xad, 0xa8, 0x44, 0x52, 0x82, 0x15, 0x03, 0x1b, 0x8b, 0xec, + 0x40, 0x26, 0x06, 0x9a, 0xdf, 0x1a, 0x37, 0xaf, 0xed, 0x8e, 0x44, 0x4e, 0x20, 0xfe, 0x16, 0x83, 0x2d, 0xe8, 0x63, + 0x83, 0xb4, 0x5d, 0xbb, 0x4b, 0xc8, 0x06, 0x43, 0x5c, 0xab, 0x1f, 0xd7, 0x32, 0x05, 0x90, 0x6d, 0x12, 0x5a, 0x8f, + 0x4b, 0x24, 0x74, 0x25, 0x9d, 0x4e, 0x59, 0xc4, 0xfd, 0x28, 0xa5, 0xfc, 0x2d, 0xc7, 0x10, 0x53, 0x5e, 0x87, 0x6d, + 0xbb, 0x25, 0xc8, 0x46, 0xe3, 0xd7, 0xc7, 0xe4, 0xee, 0x86, 0x42, 0xfd, 0xe5, 0xab, 0x7a, 0x2e, 0xf6, 0xa4, 0xdb, + 0x7f, 0x77, 0xb0, 0x67, 0x89, 0x4d, 0xb9, 0xbb, 0x05, 0xaf, 0xbb, 0xe4, 0xc1, 0x8b, 0x54, 0x96, 0x50, 0xa4, 0xb2, + 0x58, 0x22, 0x01, 0x4e, 0xe4, 0x2e, 0x6f, 0x09, 0xb4, 0x6d, 0x8b, 0xa5, 0x43, 0x11, 0x7a, 0x9c, 0x82, 0x97, 0x13, + 0xe3, 0xf7, 0xe9, 0xb6, 0xb0, 0x6b, 0x0b, 0x17, 0xcc, 0x56, 0x59, 0x41, 0xca, 0xae, 0xe1, 0xa9, 0x0a, 0x54, 0x82, + 0x35, 0xc2, 0x54, 0x82, 0x90, 0x1c, 0x4a, 0xe7, 0x25, 0x2f, 0xb7, 0x2e, 0xe6, 0xa7, 0x53, 0x90, 0x93, 0x2a, 0xa9, + 0xe7, 0xa3, 0xec, 0xb0, 0x4b, 0x53, 0xf5, 0x4f, 0x4a, 0x19, 0x49, 0x55, 0xdf, 0x0e, 0x6f, 0xfc, 0xce, 0xaa, 0xc0, + 0x5e, 0xea, 0x05, 0xcc, 0x49, 0x99, 0x6c, 0x1b, 0x39, 0x29, 0x46, 0x5d, 0x09, 0xa8, 0x6f, 0xf7, 0x4f, 0x82, 0x99, + 0x1c, 0xef, 0x75, 0xb6, 0xf4, 0x9b, 0xad, 0x5a, 0x4e, 0x0e, 0x28, 0xbf, 0x5c, 0xdc, 0xeb, 0x90, 0x00, 0xc3, 0x0a, + 0x02, 0x4c, 0xd2, 0x04, 0xb0, 0xe8, 0xe8, 0xdb, 0xde, 0x69, 0xab, 0xb4, 0x5d, 0x28, 0xc3, 0x0d, 0x29, 0xba, 0x18, + 0x93, 0xd4, 0xc2, 0xbf, 0x93, 0x4e, 0x7f, 0x37, 0x92, 0xc6, 0x25, 0x0a, 0x8f, 0x02, 0xa4, 0x07, 0x74, 0x46, 0x0b, + 0xce, 0x8f, 0xb3, 0xad, 0x0b, 0x76, 0xda, 0x8a, 0x66, 0x71, 0x15, 0x6b, 0x45, 0x53, 0x43, 0x4f, 0x99, 0x55, 0x33, + 0xe1, 0x63, 0xd4, 0x40, 0x92, 0x04, 0x77, 0x29, 0x03, 0xb9, 0x64, 0xa1, 0x03, 0x0b, 0x01, 0x85, 0x49, 0xae, 0xab, + 0x80, 0xaf, 0xd4, 0xb8, 0xa5, 0xdd, 0xff, 0xcb, 0x3f, 0xff, 0x6f, 0x19, 0xc3, 0x05, 0xaa, 0x74, 0xd4, 0x58, 0x0d, + 0x42, 0x97, 0xbb, 0x98, 0x02, 0x55, 0x9d, 0xf2, 0xb2, 0xcb, 0xd6, 0x59, 0x1e, 0x8f, 0x5a, 0x93, 0x28, 0x19, 0x03, + 0x60, 0x6b, 0x09, 0x64, 0x26, 0x48, 0x48, 0xa8, 0xeb, 0x45, 0xc8, 0x82, 0xbf, 0x29, 0x11, 0x5b, 0x25, 0xc0, 0xd3, + 0x6e, 0x35, 0xd3, 0xb2, 0xab, 0x0d, 0x55, 0x4b, 0xcd, 0x56, 0x3f, 0x5c, 0xa6, 0x84, 0x5a, 0x2d, 0x2f, 0x1b, 0x5a, + 0xea, 0xc3, 0xa8, 0x7f, 0xff, 0x97, 0x7f, 0xf8, 0x1f, 0xea, 0x15, 0xcf, 0x98, 0xfe, 0xf2, 0x4f, 0x7f, 0x87, 0x29, + 0xd0, 0x96, 0x3e, 0x87, 0x22, 0x39, 0x61, 0x55, 0x87, 0x50, 0x42, 0x60, 0x58, 0x95, 0xd3, 0x57, 0xcf, 0xdf, 0xde, + 0xa7, 0x09, 0x69, 0xb3, 0x49, 0xe8, 0x68, 0xd3, 0x96, 0x15, 0x8f, 0xd4, 0x48, 0x4e, 0xbc, 0x08, 0x95, 0x48, 0xef, + 0x3b, 0x25, 0x47, 0xf9, 0x7a, 0x35, 0x16, 0x2a, 0x42, 0x88, 0x25, 0x65, 0x55, 0x6e, 0x61, 0xe8, 0x7e, 0x81, 0xaf, + 0x41, 0xd7, 0x28, 0xa6, 0xc5, 0xab, 0xf5, 0xe9, 0xfd, 0x34, 0x07, 0xf8, 0xc7, 0x48, 0x71, 0x11, 0x87, 0xa4, 0x63, + 0xe9, 0x16, 0xda, 0x7c, 0xc9, 0x55, 0x49, 0xa3, 0x08, 0x47, 0xf1, 0xe1, 0x93, 0xbf, 0x29, 0xff, 0x38, 0x45, 0xcb, + 0xca, 0x72, 0xa6, 0xd1, 0xa5, 0x74, 0x1f, 0x1f, 0xb5, 0xdb, 0xb3, 0x4b, 0x77, 0x51, 0xcd, 0xe0, 0xad, 0x9b, 0x8c, + 0x62, 0x97, 0xe6, 0x80, 0x74, 0x9e, 0xad, 0xc3, 0xa4, 0xe0, 0x31, 0xb5, 0x31, 0xaa, 0x56, 0x96, 0x7f, 0x58, 0x50, + 0xa4, 0x2e, 0xfe, 0x05, 0xcf, 0x9d, 0x65, 0x50, 0x13, 0x4a, 0x0c, 0x2c, 0x16, 0x46, 0xaf, 0xae, 0xe8, 0x35, 0xe9, + 0x2c, 0xa7, 0x0d, 0x99, 0xe7, 0xe6, 0xe6, 0x89, 0xf7, 0x43, 0x3c, 0xc3, 0x9e, 0x74, 0xbc, 0x49, 0x77, 0xa1, 0x87, + 0xe7, 0x3c, 0x9b, 0x9a, 0x07, 0xe5, 0x2c, 0x62, 0x43, 0x36, 0x56, 0xc1, 0x60, 0x59, 0x2f, 0x0e, 0xc1, 0xcb, 0xc9, + 0xf6, 0x8a, 0xb9, 0x24, 0x48, 0x74, 0x40, 0x0e, 0xf0, 0x7c, 0x86, 0x1b, 0x10, 0xe8, 0x9f, 0x45, 0x3c, 0x20, 0x7e, + 0xed, 0x99, 0xc7, 0xed, 0x11, 0x4a, 0x99, 0x6c, 0x61, 0xc0, 0xd3, 0x13, 0x4d, 0x31, 0x2c, 0x5b, 0x4f, 0xdb, 0x2a, + 0x7d, 0xea, 0x6e, 0x0e, 0x25, 0xa2, 0x3a, 0xdf, 0xca, 0x53, 0xec, 0xa7, 0xb5, 0x70, 0x88, 0x54, 0x31, 0x5d, 0xd7, + 0x5b, 0x59, 0x2f, 0x34, 0xb5, 0xa8, 0xfd, 0x16, 0x0c, 0x30, 0x02, 0xd3, 0x6e, 0xb6, 0xa2, 0x42, 0x6c, 0xf5, 0x34, + 0xfc, 0x56, 0xbb, 0x3e, 0xd1, 0x6c, 0x46, 0x0d, 0x5d, 0x60, 0x62, 0x32, 0x58, 0x51, 0x76, 0x50, 0x86, 0x86, 0x48, + 0x88, 0x90, 0x6d, 0xe4, 0x46, 0x10, 0x4f, 0x32, 0x55, 0x02, 0x7f, 0x72, 0xa2, 0xff, 0xff, 0x00, 0x69, 0x5b, 0x88, + 0x58, 0x18, 0x7f, 0x00, 0x00}; } // namespace web_server } // namespace esphome From d9398a91d1dac8e95e7a7e135a24be50e124bc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Vl=C3=A9rick?= Date: Mon, 26 Jun 2023 22:09:52 +0200 Subject: [PATCH 042/106] update dsmr to 0.7 (#5011) --- esphome/components/dsmr/__init__.py | 2 +- platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index f4f8305ba6..af41b2aa6f 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -84,7 +84,7 @@ async def to_code(config): cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID])) # DSMR Parser - cg.add_library("glmnet/Dsmr", "0.5") + cg.add_library("glmnet/Dsmr", "0.7") # Crypto cg.add_library("rweather/Crypto", "0.4.0") diff --git a/platformio.ini b/platformio.ini index ab16d47c6f..868880e1d7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -62,7 +62,7 @@ lib_deps = fastled/FastLED@3.3.2 ; fastled_base mikalhart/TinyGPSPlus@1.0.2 ; gps freekode/TM1651@1.0.1 ; tm1651 - glmnet/Dsmr@0.5 ; dsmr + glmnet/Dsmr@0.7 ; dsmr rweather/Crypto@0.4.0 ; dsmr dudanov/MideaUART@1.1.8 ; midea tonia/HeatpumpIR@1.0.20 ; heatpumpir From bd9a4ff8dedc397eca8159e6c809eeff7f43efb0 Mon Sep 17 00:00:00 2001 From: jerome992 <35580081+jerome992@users.noreply.github.com> Date: Tue, 27 Jun 2023 20:35:20 +0200 Subject: [PATCH 043/106] add water delivered to dsmr component (#4237) Co-authored-by: Jerome --- esphome/components/dsmr/__init__.py | 3 +++ esphome/components/dsmr/sensor.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/esphome/components/dsmr/__init__.py b/esphome/components/dsmr/__init__.py index af41b2aa6f..d3d20ca2a7 100644 --- a/esphome/components/dsmr/__init__.py +++ b/esphome/components/dsmr/__init__.py @@ -19,6 +19,7 @@ CONF_CRC_CHECK = "crc_check" CONF_DECRYPTION_KEY = "decryption_key" CONF_DSMR_ID = "dsmr_id" CONF_GAS_MBUS_ID = "gas_mbus_id" +CONF_WATER_MBUS_ID = "water_mbus_id" CONF_MAX_TELEGRAM_LENGTH = "max_telegram_length" CONF_REQUEST_INTERVAL = "request_interval" CONF_REQUEST_PIN = "request_pin" @@ -53,6 +54,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_DECRYPTION_KEY): _validate_key, cv.Optional(CONF_CRC_CHECK, default=True): cv.boolean, cv.Optional(CONF_GAS_MBUS_ID, default=1): cv.int_, + cv.Optional(CONF_WATER_MBUS_ID, default=2): cv.int_, cv.Optional(CONF_MAX_TELEGRAM_LENGTH, default=1500): cv.int_, cv.Optional(CONF_REQUEST_PIN): pins.gpio_output_pin_schema, cv.Optional( @@ -82,6 +84,7 @@ async def to_code(config): cg.add(var.set_receive_timeout(config[CONF_RECEIVE_TIMEOUT].total_milliseconds)) cg.add_build_flag("-DDSMR_GAS_MBUS_ID=" + str(config[CONF_GAS_MBUS_ID])) + cg.add_build_flag("-DDSMR_WATER_MBUS_ID=" + str(config[CONF_WATER_MBUS_ID])) # DSMR Parser cg.add_library("glmnet/Dsmr", "0.7") diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index 0b0439baa4..2e2050ecab 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -8,6 +8,7 @@ from esphome.const import ( DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_WATER, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, @@ -236,6 +237,12 @@ CONFIG_SCHEMA = cv.Schema( device_class=DEVICE_CLASS_GAS, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional("water_delivered"): sensor.sensor_schema( + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + device_class=DEVICE_CLASS_WATER, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), } ).extend(cv.COMPONENT_SCHEMA) From 8f4abf6a63dc2b674a0173d858ed5dace2688469 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jun 2023 09:34:31 +1200 Subject: [PATCH 044/106] Update sync workflow (#5017) --- .github/workflows/sync-device-classes.yml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/sync-device-classes.yml b/.github/workflows/sync-device-classes.yml index 896a0369ac..7067300826 100644 --- a/.github/workflows/sync-device-classes.yml +++ b/.github/workflows/sync-device-classes.yml @@ -6,14 +6,12 @@ on: schedule: - cron: '45 6 * * *' -permissions: - contents: write - pull-requests: write jobs: sync: name: Sync Device Classes runs-on: ubuntu-latest + if: github.repository == 'esphome/esphome' steps: - name: Checkout uses: actions/checkout@v3 @@ -38,15 +36,6 @@ jobs: 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@v5 with: @@ -56,5 +45,5 @@ jobs: branch: sync/device-classes delete-branch: true title: "Synchronise Device Classes from Home Assistant" - body: ${{ steps.pr-template-body.outputs.body }} + body-path: .github/PULL_REQUEST_TEMPLATE.md token: ${{ secrets.DEVICE_CLASS_SYNC_TOKEN }} From 9d21cccac13b366d63fd2eede5acafedaf34427a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 09:40:08 +1200 Subject: [PATCH 045/106] Bump aioesphomeapi from 14.1.0 to 15.0.0 (#5012) 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 a2ef604446..d4b96ada64 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ platformio==6.1.7 # When updating platformio, also update Dockerfile esptool==4.6 click==8.1.3 esphome-dashboard==20230621.0 -aioesphomeapi==14.1.0 +aioesphomeapi==15.0.0 zeroconf==0.69.0 # esp-idf requires this, but doesn't bundle it by default From 8ce98dd15a519d5472372d22e5648f7fde9295c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 09:41:02 +1200 Subject: [PATCH 046/106] Bump pyupgrade from 3.4.0 to 3.7.0 (#4971) 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 617d6f5d9f..9d2eb2908f 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: v3.4.0 + rev: v3.7.0 hooks: - id: pyupgrade args: [--py39-plus] diff --git a/requirements_test.txt b/requirements_test.txt index 66ce075f91..4f3a32456b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,7 +1,7 @@ pylint==2.17.4 flake8==6.0.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.4.0 # also change in .pre-commit-config.yaml when updating +pyupgrade==3.7.0 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests From 108fabe18f1c3a3853d714abbd65a01c07d2f0d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Jun 2023 09:41:39 +1200 Subject: [PATCH 047/106] Bump pytest from 7.3.2 to 7.4.0 (#5000) 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 4f3a32456b..23131fd64b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -5,7 +5,7 @@ pyupgrade==3.7.0 # also change in .pre-commit-config.yaml when updating pre-commit # Unit tests -pytest==7.3.2 +pytest==7.4.0 pytest-cov==4.1.0 pytest-mock==3.10.0 pytest-asyncio==0.21.0 From 9a149a7aba4b55b1715ea286bdc35fefda8398f1 Mon Sep 17 00:00:00 2001 From: esphomebot Date: Wed, 28 Jun 2023 10:19:36 +1200 Subject: [PATCH 048/106] Synchronise Device Classes from Home Assistant (#5018) --- esphome/components/button/__init__.py | 2 ++ esphome/const.py | 1 + 2 files changed, 3 insertions(+) diff --git a/esphome/components/button/__init__.py b/esphome/components/button/__init__.py index 55f2fe794a..a999c6d91e 100644 --- a/esphome/components/button/__init__.py +++ b/esphome/components/button/__init__.py @@ -12,6 +12,7 @@ from esphome.const import ( CONF_TRIGGER_ID, CONF_MQTT_ID, DEVICE_CLASS_EMPTY, + DEVICE_CLASS_IDENTIFY, DEVICE_CLASS_RESTART, DEVICE_CLASS_UPDATE, ) @@ -24,6 +25,7 @@ IS_PLATFORM_COMPONENT = True DEVICE_CLASSES = [ DEVICE_CLASS_EMPTY, + DEVICE_CLASS_IDENTIFY, DEVICE_CLASS_RESTART, DEVICE_CLASS_UPDATE, ] diff --git a/esphome/const.py b/esphome/const.py index c8b85fcdeb..3145255e20 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -968,6 +968,7 @@ DEVICE_CLASS_GAS = "gas" DEVICE_CLASS_GATE = "gate" DEVICE_CLASS_HEAT = "heat" DEVICE_CLASS_HUMIDITY = "humidity" +DEVICE_CLASS_IDENTIFY = "identify" DEVICE_CLASS_ILLUMINANCE = "illuminance" DEVICE_CLASS_IRRADIANCE = "irradiance" DEVICE_CLASS_LIGHT = "light" From c82be2cd60a660034a01f1157558844b2bfb903c Mon Sep 17 00:00:00 2001 From: "F.D.Castel" Date: Tue, 27 Jun 2023 20:13:14 -0300 Subject: [PATCH 049/106] Fixes compressed downloads (#5014) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- esphome/dashboard/dashboard.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 22bbe0aae9..dd800f534c 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -546,22 +546,11 @@ class DownloadBinaryRequestHandler(BaseHandler): return with open(path, "rb") as f: - while True: - # For a 528KB image used as benchmark: - # - using 256KB blocks resulted in the smallest file size. - # - blocks larger than 256KB didn't improve the size of compressed file. - # - blocks smaller than 256KB hindered compression, making the output file larger. + data = f.read() + if compressed: + data = gzip.compress(data, 9) + self.write(data) - # Read file in blocks of 256KB. - data = f.read(256 * 1024) - - if not data: - break - - if compressed: - data = gzip.compress(data, 9) - - self.write(data) self.finish() From 68119ddcd4a8cd6c5a09fb83507c423a9d307ba5 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:34:08 +1200 Subject: [PATCH 050/106] Attempt to fix script parameters (#4627) --- esphome/components/script/__init__.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/esphome/components/script/__init__.py b/esphome/components/script/__init__.py index 6337d89bcd..78b23e7b5e 100644 --- a/esphome/components/script/__init__.py +++ b/esphome/components/script/__init__.py @@ -33,6 +33,7 @@ SCRIPT_MODES = { PARAMETER_TYPE_TRANSLATIONS = { "string": "std::string", + "boolean": "bool", } @@ -149,6 +150,16 @@ async def to_code(config): ), ) async def script_execute_action_to_code(config, action_id, template_arg, args): + def convert(type: str): + def converter(value): + if type == "std::string": + return value + if type == "bool": + return cg.RawExpression(str(value).lower()) + return cg.RawExpression(str(value)) + + return converter + async def get_ordered_args(config, script_params): config_args = config.copy() config_args.pop(CONF_ID) @@ -160,7 +171,9 @@ async def script_execute_action_to_code(config, action_id, template_arg, args): raise EsphomeError( f"Missing parameter: '{name}' in script.execute {config[CONF_ID]}" ) - arg = await cg.templatable(config_args[name], args, type) + arg = await cg.templatable( + config_args[name], args, type, convert(str(type)) + ) script_args.append(arg) return script_args From 951157dc263787a68e7a69690fe89797001c6449 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jun 2023 11:35:35 +1200 Subject: [PATCH 051/106] Add CONFIG_BT_BLE_42_FEATURES_SUPPORTED for ble (#5008) --- esphome/components/esp32_ble/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/esp32_ble/__init__.py b/esphome/components/esp32_ble/__init__.py index f508cecb87..b4cb595da0 100644 --- a/esphome/components/esp32_ble/__init__.py +++ b/esphome/components/esp32_ble/__init__.py @@ -55,3 +55,4 @@ async def to_code(config): if CORE.using_esp_idf: add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True) + add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True) From ac5246e21dbf4d119b380f1621bf418b0523a631 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 28 Jun 2023 12:22:14 +1200 Subject: [PATCH 052/106] Remove yaml test cache (#5019) --- .github/workflows/ci.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 105f0f12b8..7775a996fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -241,12 +241,6 @@ jobs: with: python-version: ${{ env.DEFAULT_PYTHON }} cache-key: ${{ needs.common.outputs.cache-key }} - - name: Cache platformio - uses: actions/cache@v3.3.1 - with: - path: ~/.platformio - # yamllint disable-line rule:line-length - key: platformio-test${{ matrix.file }}-${{ hashFiles('platformio.ini') }} - name: Run esphome compile tests/test${{ matrix.file }}.yaml run: | . venv/bin/activate From 807621402daa2be70cf75f0b2a63d164dcb14aaa Mon Sep 17 00:00:00 2001 From: Ryan DeShone Date: Wed, 28 Jun 2023 19:42:39 -0400 Subject: [PATCH 053/106] [SCD30] Disable negative temperature offset (#4850) --- esphome/components/scd30/scd30.cpp | 19 ++++++++++++------- esphome/components/scd30/sensor.py | 5 ++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 01abca0a1f..3eeca23800 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -42,13 +42,18 @@ void SCD30Component::setup() { ESP_LOGD(TAG, "SCD30 Firmware v%0d.%02d", (uint16_t(raw_firmware_version[0]) >> 8), 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))) { - ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset."); - this->error_code_ = MEASUREMENT_INIT_FAILED; - this->mark_failed(); - return; - } + uint16_t temp_offset; + if (this->temperature_offset_ > 0) { + temp_offset = (this->temperature_offset_ * 100); + } else { + temp_offset = 0; + } + + if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, temp_offset)) { + ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset."); + this->error_code_ = MEASUREMENT_INIT_FAILED; + this->mark_failed(); + return; } #ifdef USE_ESP32 // According ESP32 clock stretching is typically 30ms and up to 150ms "due to diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py index 1ddf0f1e85..f72b43fd37 100644 --- a/esphome/components/scd30/sensor.py +++ b/esphome/components/scd30/sensor.py @@ -68,7 +68,10 @@ CONFIG_SCHEMA = ( cv.int_range(min=0, max=0xFFFF, max_included=False), ), cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure, - cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature, + cv.Optional(CONF_TEMPERATURE_OFFSET): cv.All( + cv.temperature, + cv.float_range(min=0, max=655.35), + ), cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All( cv.positive_time_period_seconds, cv.Range( From 0e93b8ee0da72eaf1af48244adff7d5dd441be3a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:44:10 +1200 Subject: [PATCH 054/106] Bump esptool from 4.6 to 4.6.2 (#4949) 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 d4b96ada64..c6564d55e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ tzlocal==5.0.1 # from time tzdata>=2021.1 # from time pyserial==3.5 platformio==6.1.7 # When updating platformio, also update Dockerfile -esptool==4.6 +esptool==4.6.2 click==8.1.3 esphome-dashboard==20230621.0 aioesphomeapi==15.0.0 From c5eb3941b9d1367383038ef2bf30702a18d16c52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:44:23 +1200 Subject: [PATCH 055/106] Bump pytest-mock from 3.10.0 to 3.11.1 (#4977) 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 23131fd64b..75f29ac8dd 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -7,7 +7,7 @@ pre-commit # Unit tests pytest==7.4.0 pytest-cov==4.1.0 -pytest-mock==3.10.0 +pytest-mock==3.11.1 pytest-asyncio==0.21.0 asyncmock==0.4.2 hypothesis==5.49.0 From cf98c497d5618384e771742e794becae0fd41e95 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Mon, 3 Jul 2023 02:35:53 +0400 Subject: [PATCH 056/106] binary_sensor removed unused filter (#5039) --- esphome/components/binary_sensor/filter.cpp | 9 --------- esphome/components/binary_sensor/filter.h | 8 -------- 2 files changed, 17 deletions(-) diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 53c2daf42d..836c341574 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -114,15 +114,6 @@ LambdaFilter::LambdaFilter(std::function(bool)> f) : f_(std::move optional LambdaFilter::new_value(bool value, bool is_initial) { return this->f_(value); } -optional UniqueFilter::new_value(bool value, bool is_initial) { - if (this->last_value_.has_value() && *this->last_value_ == value) { - return {}; - } else { - this->last_value_ = value; - return value; - } -} - } // namespace binary_sensor } // namespace esphome diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 64a33f6e34..0f0ab6875f 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -105,14 +105,6 @@ class LambdaFilter : public Filter { std::function(bool)> f_; }; -class UniqueFilter : public Filter { - public: - optional new_value(bool value, bool is_initial) override; - - protected: - optional last_value_{}; -}; - } // namespace binary_sensor } // namespace esphome From 099dc8d1d2f44f81d55f70d4f64bebc5b0948546 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Tue, 4 Jul 2023 04:18:51 +0400 Subject: [PATCH 057/106] fix template binary_sensor publish_initial_state option (#5033) --- .../binary_sensor/template_binary_sensor.cpp | 16 +++++++++++++--- .../binary_sensor/template_binary_sensor.h | 3 ++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.cpp b/esphome/components/template/binary_sensor/template_binary_sensor.cpp index 66ff4be4c4..fce11f63d6 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.cpp +++ b/esphome/components/template/binary_sensor/template_binary_sensor.cpp @@ -6,11 +6,21 @@ namespace template_ { static const char *const TAG = "template.binary_sensor"; -void TemplateBinarySensor::loop() { - if (!this->f_.has_value()) +void TemplateBinarySensor::setup() { + if (!this->publish_initial_state_) return; - auto s = (*this->f_)(); + if (this->f_ != nullptr) { + this->publish_initial_state(*this->f_()); + } else { + this->publish_initial_state(false); + } +} +void TemplateBinarySensor::loop() { + if (this->f_ == nullptr) + return; + + auto s = this->f_(); if (s.has_value()) { this->publish_state(*s); } diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.h b/esphome/components/template/binary_sensor/template_binary_sensor.h index a28929b122..5e5624d82e 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.h +++ b/esphome/components/template/binary_sensor/template_binary_sensor.h @@ -10,13 +10,14 @@ class TemplateBinarySensor : public Component, public binary_sensor::BinarySenso public: void set_template(std::function()> &&f) { this->f_ = f; } + void setup() override; void loop() override; void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } protected: - optional()>> f_{}; + std::function()> f_{nullptr}; }; } // namespace template_ From 5b2176562bf3b1e86fee1cdc274d4f1ba7d8f89e Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Tue, 4 Jul 2023 04:25:48 +0400 Subject: [PATCH 058/106] binary_sensor filters templatable delays (#5029) --- esphome/components/binary_sensor/__init__.py | 76 ++++++++++++++------ esphome/components/binary_sensor/filter.cpp | 11 ++- esphome/components/binary_sensor/filter.h | 21 +++--- tests/test1.yaml | 7 ++ 4 files changed, 79 insertions(+), 36 deletions(-) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index f4a5c95b12..41b4c5a0d7 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -95,6 +95,14 @@ DEVICE_CLASSES = [ IS_PLATFORM_COMPONENT = True +CONF_TIME_OFF = "time_off" +CONF_TIME_ON = "time_on" + +DEFAULT_DELAY = "1s" +DEFAULT_TIME_OFF = "100ms" +DEFAULT_TIME_ON = "900ms" + + binary_sensor_ns = cg.esphome_ns.namespace("binary_sensor") BinarySensor = binary_sensor_ns.class_("BinarySensor", cg.EntityBase) BinarySensorInitiallyOff = binary_sensor_ns.class_( @@ -138,47 +146,75 @@ FILTER_REGISTRY = Registry() validate_filters = cv.validate_registry("filter", FILTER_REGISTRY) -@FILTER_REGISTRY.register("invert", InvertFilter, {}) +def register_filter(name, filter_type, schema): + return FILTER_REGISTRY.register(name, filter_type, schema) + + +@register_filter("invert", InvertFilter, {}) async def invert_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id) -@FILTER_REGISTRY.register( - "delayed_on_off", DelayedOnOffFilter, cv.positive_time_period_milliseconds +@register_filter( + "delayed_on_off", + DelayedOnOffFilter, + cv.Any( + cv.templatable(cv.positive_time_period_milliseconds), + cv.Schema( + { + cv.Required(CONF_TIME_ON): cv.templatable( + cv.positive_time_period_milliseconds + ), + cv.Required(CONF_TIME_OFF): cv.templatable( + cv.positive_time_period_milliseconds + ), + } + ), + msg="'delayed_on_off' filter requires either a delay time to be used for both " + "turn-on and turn-off delays, or two parameters 'time_on' and 'time_off' if " + "different delay times are required.", + ), ) async def delayed_on_off_filter_to_code(config, filter_id): - var = cg.new_Pvariable(filter_id, config) + var = cg.new_Pvariable(filter_id) await cg.register_component(var, {}) + if isinstance(config, dict): + template_ = await cg.templatable(config[CONF_TIME_ON], [], cg.uint32) + cg.add(var.set_on_delay(template_)) + template_ = await cg.templatable(config[CONF_TIME_OFF], [], cg.uint32) + cg.add(var.set_off_delay(template_)) + else: + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_on_delay(template_)) + cg.add(var.set_off_delay(template_)) return var -@FILTER_REGISTRY.register( - "delayed_on", DelayedOnFilter, cv.positive_time_period_milliseconds +@register_filter( + "delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds) ) async def delayed_on_filter_to_code(config, filter_id): - var = cg.new_Pvariable(filter_id, config) + var = cg.new_Pvariable(filter_id) await cg.register_component(var, {}) + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_delay(template_)) return var -@FILTER_REGISTRY.register( - "delayed_off", DelayedOffFilter, cv.positive_time_period_milliseconds +@register_filter( + "delayed_off", + DelayedOffFilter, + cv.templatable(cv.positive_time_period_milliseconds), ) async def delayed_off_filter_to_code(config, filter_id): - var = cg.new_Pvariable(filter_id, config) + var = cg.new_Pvariable(filter_id) await cg.register_component(var, {}) + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_delay(template_)) return var -CONF_TIME_OFF = "time_off" -CONF_TIME_ON = "time_on" - -DEFAULT_DELAY = "1s" -DEFAULT_TIME_OFF = "100ms" -DEFAULT_TIME_ON = "900ms" - - -@FILTER_REGISTRY.register( +@register_filter( "autorepeat", AutorepeatFilter, cv.All( @@ -215,7 +251,7 @@ async def autorepeat_filter_to_code(config, filter_id): return var -@FILTER_REGISTRY.register("lambda", LambdaFilter, cv.returning_lambda) +@register_filter("lambda", LambdaFilter, cv.returning_lambda) async def lambda_filter_to_code(config, filter_id): lambda_ = await cg.process_lambda( config, [(bool, "x")], return_type=cg.optional.template(bool) diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 836c341574..46957383c3 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -26,22 +26,20 @@ void Filter::input(bool value, bool is_initial) { } } -DelayedOnOffFilter::DelayedOnOffFilter(uint32_t delay) : delay_(delay) {} optional DelayedOnOffFilter::new_value(bool value, bool is_initial) { if (value) { - this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); + this->set_timeout("ON_OFF", this->on_delay_.value(), [this, is_initial]() { this->output(true, is_initial); }); } else { - this->set_timeout("ON_OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); + this->set_timeout("ON_OFF", this->off_delay_.value(), [this, is_initial]() { this->output(false, is_initial); }); } return {}; } float DelayedOnOffFilter::get_setup_priority() const { return setup_priority::HARDWARE; } -DelayedOnFilter::DelayedOnFilter(uint32_t delay) : delay_(delay) {} optional DelayedOnFilter::new_value(bool value, bool is_initial) { if (value) { - this->set_timeout("ON", this->delay_, [this, is_initial]() { this->output(true, is_initial); }); + this->set_timeout("ON", this->delay_.value(), [this, is_initial]() { this->output(true, is_initial); }); return {}; } else { this->cancel_timeout("ON"); @@ -51,10 +49,9 @@ optional DelayedOnFilter::new_value(bool value, bool is_initial) { float DelayedOnFilter::get_setup_priority() const { return setup_priority::HARDWARE; } -DelayedOffFilter::DelayedOffFilter(uint32_t delay) : delay_(delay) {} optional DelayedOffFilter::new_value(bool value, bool is_initial) { if (!value) { - this->set_timeout("OFF", this->delay_, [this, is_initial]() { this->output(false, is_initial); }); + this->set_timeout("OFF", this->delay_.value(), [this, is_initial]() { this->output(false, is_initial); }); return {}; } else { this->cancel_timeout("OFF"); diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 0f0ab6875f..9514cb3fe2 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/automation.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -29,38 +30,40 @@ class Filter { class DelayedOnOffFilter : public Filter, public Component { public: - explicit DelayedOnOffFilter(uint32_t delay); - optional new_value(bool value, bool is_initial) override; float get_setup_priority() const override; + template void set_on_delay(T delay) { this->on_delay_ = delay; } + template void set_off_delay(T delay) { this->off_delay_ = delay; } + protected: - uint32_t delay_; + TemplatableValue on_delay_{}; + TemplatableValue off_delay_{}; }; class DelayedOnFilter : public Filter, public Component { public: - explicit DelayedOnFilter(uint32_t delay); - optional new_value(bool value, bool is_initial) override; float get_setup_priority() const override; + template void set_delay(T delay) { this->delay_ = delay; } + protected: - uint32_t delay_; + TemplatableValue delay_{}; }; class DelayedOffFilter : public Filter, public Component { public: - explicit DelayedOffFilter(uint32_t delay); - optional new_value(bool value, bool is_initial) override; float get_setup_priority() const override; + template void set_delay(T delay) { this->delay_ = delay; } + protected: - uint32_t delay_; + TemplatableValue delay_{}; }; class InvertFilter : public Filter { diff --git a/tests/test1.yaml b/tests/test1.yaml index f8928430f4..05ba07d1d8 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1355,8 +1355,15 @@ binary_sensor: device_class: window filters: - invert: + - delayed_on_off: 40ms + - delayed_on_off: + time_on: 10s + time_off: !lambda "return 1000;" - delayed_on: 40ms - delayed_off: 40ms + - delayed_on_off: !lambda "return 10;" + - delayed_on: !lambda "return 1000;" + - delayed_off: !lambda "return 0;" on_press: then: - lambda: >- From 4cc0f3fd535d9a51916d234982d8bd23b9d54e52 Mon Sep 17 00:00:00 2001 From: Graham Brown Date: Tue, 4 Jul 2023 02:28:19 +0200 Subject: [PATCH 059/106] Add alarm to reserved ids (#5042) --- esphome/config_validation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 0a6b2dfbb0..cf0b1d3aca 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -108,6 +108,7 @@ ROOT_CONFIG_PATH = object() RESERVED_IDS = [ # C++ keywords http://en.cppreference.com/w/cpp/keyword + "alarm", "alignas", "alignof", "and", From 63d3a0e8b3196c752ba6d16e6e57c15304a80893 Mon Sep 17 00:00:00 2001 From: guillempages Date: Tue, 4 Jul 2023 02:43:03 +0200 Subject: [PATCH 060/106] Improve the gamma settings for the S3-Box-lite display (#5046) --- esphome/components/ili9xxx/ili9xxx_init.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index a17e6b127c..15fbf92659 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -218,12 +218,12 @@ static const uint8_t PROGMEM INITCMD_S3BOXLITE[] = { ILI9XXX_DFUNCTR , 3, 0x08, 0x82, 0x27, // Display Function Control 0xF2, 1, 0x00, // 3Gamma Function Disable ILI9XXX_GAMMASET , 1, 0x01, // Gamma curve selected - ILI9XXX_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma - 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, - 0x0E, 0x09, 0x00, - ILI9XXX_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma - 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, - 0x31, 0x36, 0x0F, + ILI9XXX_GMCTRP1 , 14, 0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, // Set Gamma + 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14, + 0x18, 0x1B, + ILI9XXX_GMCTRN1 , 14, 0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, // Set Gamma + 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14, + 0x17, 0x1B, ILI9XXX_SLPOUT , 0x80, // Exit Sleep ILI9XXX_DISPON , 0x80, // Display on 0x00 // End of list From 25b9bde0a59f2fd4016b9ed56b30fbd633c0f8d2 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 4 Jul 2023 02:48:05 +0200 Subject: [PATCH 061/106] Prepare ethernet to work with esp idf 5.0 (#5037) --- .../components/ethernet/esp_eth_phy_jl1101.c | 16 ++++++++++++++ .../ethernet/ethernet_component.cpp | 21 +++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/esphome/components/ethernet/esp_eth_phy_jl1101.c b/esphome/components/ethernet/esp_eth_phy_jl1101.c index 6011795033..de2a6f4f35 100644 --- a/esphome/components/ethernet/esp_eth_phy_jl1101.c +++ b/esphome/components/ethernet/esp_eth_phy_jl1101.c @@ -19,7 +19,11 @@ #include #include "esp_log.h" #include "esp_eth.h" +#if ESP_IDF_VERSION_MAJOR >= 5 +#include "esp_eth_phy_802_3.h" +#else #include "eth_phy_regs_struct.h" +#endif #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" @@ -170,7 +174,11 @@ static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) { return ESP_OK; } +#if ESP_IDF_VERSION_MAJOR >= 5 +static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy, eth_phy_autoneg_cmd_t cmd, bool *nego_state) { +#else static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) { +#endif phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent); esp_eth_mediator_t *eth = jl1101->eth; /* in case any link status has changed, let's assume we're in link down status */ @@ -285,7 +293,11 @@ static esp_err_t jl1101_init(esp_eth_phy_t *phy) { esp_eth_mediator_t *eth = jl1101->eth; // Detect PHY address if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) { +#if ESP_IDF_VERSION_MAJOR >= 5 + PHY_CHECK(esp_eth_phy_802_3_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err); +#else PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err); +#endif } /* Power on Ethernet PHY */ PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err); @@ -324,7 +336,11 @@ esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) { jl1101->parent.init = jl1101_init; jl1101->parent.deinit = jl1101_deinit; jl1101->parent.set_mediator = jl1101_set_mediator; +#if ESP_IDF_VERSION_MAJOR >= 5 + jl1101->parent.autonego_ctrl = jl1101_negotiate; +#else jl1101->parent.negotiate = jl1101_negotiate; +#endif jl1101->parent.get_link = jl1101_get_link; jl1101->parent.pwrctl = jl1101_pwrctl; jl1101->parent.get_addr = jl1101_get_addr; diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 0487ea5498..fc1068f2a8 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -41,18 +41,27 @@ void EthernetComponent::setup() { this->eth_netif_ = esp_netif_new(&cfg); // Init MAC and PHY configs to default - eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); - phy_config.phy_addr = this->phy_addr_; phy_config.reset_gpio_num = this->power_pin_; + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); +#if ESP_IDF_VERSION_MAJOR >= 5 + eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); + esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_; + esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_; + esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_; + esp32_emac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; + + esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); +#else mac_config.smi_mdc_gpio_num = this->mdc_pin_; mac_config.smi_mdio_gpio_num = this->mdio_pin_; mac_config.clock_config.rmii.clock_mode = this->clk_mode_; mac_config.clock_config.rmii.clock_gpio = this->clk_gpio_; esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); +#endif switch (this->type_) { case ETHERNET_TYPE_LAN8720: { @@ -76,7 +85,11 @@ void EthernetComponent::setup() { break; } case ETHERNET_TYPE_KSZ8081: { +#if ESP_IDF_VERSION_MAJOR >= 5 + this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config); +#else this->phy_ = esp_eth_phy_new_ksz8081(&phy_config); +#endif break; } default: { @@ -221,13 +234,13 @@ void EthernetComponent::eth_event_handler(void *arg, esp_event_base_t event_base return; } - ESP_LOGV(TAG, "[Ethernet event] %s (num=%d)", event_name, event); + ESP_LOGV(TAG, "[Ethernet event] %s (num=%" PRId32 ")", event_name, event); } void EthernetComponent::got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { global_eth_component->connected_ = true; - ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%d)", event_id); + ESP_LOGV(TAG, "[Ethernet event] ETH Got IP (num=%" PRId32 ")", event_id); } void EthernetComponent::start_connect_() { From 87c0f48095a37a8f7edd5e762460bb47ff2fa4bf Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Tue, 4 Jul 2023 02:49:27 +0200 Subject: [PATCH 062/106] Prepare debug and logger component to work with idf 5.0 (#5036) --- esphome/components/debug/debug_component.cpp | 6 ++++-- esphome/core/scheduler.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 9843fa1c99..9b7e256147 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -5,6 +5,7 @@ #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "esphome/core/version.h" +#include #ifdef USE_ESP32 @@ -13,6 +14,7 @@ #if ESP_IDF_VERSION_MAJOR >= 4 #include +#include #else #include #endif @@ -61,7 +63,7 @@ void DebugComponent::dump_config() { device_info += ESPHOME_VERSION; this->free_heap_ = get_free_heap(); - ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); + ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); #ifdef USE_ARDUINO const char *flash_mode; @@ -289,7 +291,7 @@ void DebugComponent::loop() { uint32_t new_free_heap = get_free_heap(); if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; - ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); + ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); this->status_momentary_warning("heap", 1000); } diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 7c76c8490b..ab60f83ba5 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -29,7 +29,7 @@ void HOT Scheduler::set_timeout(Component *component, const std::string &name, u if (timeout == SCHEDULER_DONT_RUN) return; - ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%u)", name.c_str(), timeout); + ESP_LOGVV(TAG, "set_timeout(name='%s', timeout=%" PRIu32 ")", name.c_str(), timeout); auto item = make_unique(); item->component = component; @@ -60,7 +60,7 @@ void HOT Scheduler::set_interval(Component *component, const std::string &name, if (interval != 0) offset = (random_uint32() % interval) / 2; - ESP_LOGVV(TAG, "set_interval(name='%s', interval=%u, offset=%u)", name.c_str(), interval, offset); + ESP_LOGVV(TAG, "set_interval(name='%s', interval=%" PRIu32 ", offset=%" PRIu32 ")", name.c_str(), interval, offset); auto item = make_unique(); item->component = component; @@ -108,8 +108,8 @@ void HOT Scheduler::set_retry(Component *component, const std::string &name, uin if (initial_wait_time == SCHEDULER_DONT_RUN) return; - ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%u, max_attempts=%u, backoff_factor=%0.1f)", name.c_str(), - initial_wait_time, max_attempts, backoff_increase_factor); + ESP_LOGVV(TAG, "set_retry(name='%s', initial_wait_time=%" PRIu32 ", max_attempts=%u, backoff_factor=%0.1f)", + name.c_str(), initial_wait_time, max_attempts, backoff_increase_factor); if (backoff_increase_factor < 0.0001) { ESP_LOGE(TAG, @@ -222,8 +222,8 @@ void HOT Scheduler::call() { } #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE - ESP_LOGVV(TAG, "Running %s '%s' with interval=%u last_execution=%u (now=%u)", item->get_type_str(), - item->name.c_str(), item->interval, item->last_execution, now); + ESP_LOGVV(TAG, "Running %s '%s' with interval=%" PRIu32 " last_execution=%" PRIu32 " (now=%" PRIu32 ")", + item->get_type_str(), item->name.c_str(), item->interval, item->last_execution, now); #endif // Warning: During callback(), a lot of stuff can happen, including: From 2e2ac5307158a775092969b7e3d1658b17a530f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Jul 2023 19:52:42 -0500 Subject: [PATCH 063/106] Advertise noise is enabled (#5034) --- esphome/components/mdns/mdns_component.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index cdb9aa8e74..581758cf2d 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -57,6 +57,10 @@ void MDNSComponent::compile_records_() { service.txt_records.push_back({"network", "ethernet"}); #endif +#ifdef USE_API_NOISE + service.txt_records.push_back({"api_encryption", "Noise_NNpsk0_25519_ChaChaPoly_SHA256"}); +#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}); From e74ab00b3e116fd05fdbc280b8b4e06e4176214b Mon Sep 17 00:00:00 2001 From: Fabian Date: Tue, 4 Jul 2023 02:55:04 +0200 Subject: [PATCH 064/106] Mopeka std fixes (#5041) Co-authored-by: Your Name --- .../mopeka_std_check/mopeka_std_check.cpp | 32 +++++++++---------- tests/test2.yaml | 13 ++++++++ 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/esphome/components/mopeka_std_check/mopeka_std_check.cpp b/esphome/components/mopeka_std_check/mopeka_std_check.cpp index ae7b646b9d..67e749c68b 100644 --- a/esphome/components/mopeka_std_check/mopeka_std_check.cpp +++ b/esphome/components/mopeka_std_check/mopeka_std_check.cpp @@ -54,16 +54,16 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const auto &manu_datas = device.get_manufacturer_datas(); if (manu_datas.size() != 1) { - ESP_LOGE(TAG, "%s: Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size()); return false; } const auto &manu_data = manu_datas[0]; - ESP_LOGVV(TAG, "%s: Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str()); + ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(), format_hex_pretty(manu_data.data).c_str()); if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { - ESP_LOGE(TAG, "%s: Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size()); + ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size()); return false; } @@ -72,20 +72,20 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF; if (static_cast(hardware_id) != STANDARD && static_cast(hardware_id) != XL) { - ESP_LOGE(TAG, "%s: Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id); + ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id); return false; } - ESP_LOGVV(TAG, "%s: Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate); - ESP_LOGVV(TAG, "%s: Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed); + ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate); + ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed); for (u_int8_t i = 0; i < 3; i++) { - ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1, + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1, mopeka_data->val[i].value_0, mopeka_data->val[i].time_0); - ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2, + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2, mopeka_data->val[i].value_1, mopeka_data->val[i].time_1); - ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3, + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3, mopeka_data->val[i].value_2, mopeka_data->val[i].time_2); - ESP_LOGVV(TAG, "%s: %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4, + ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4, mopeka_data->val[i].value_3, mopeka_data->val[i].time_3); } @@ -146,19 +146,19 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) // This value is better than a previous one. best_value = measurements_value[i]; best_time = measurement_time; - // Reset measurement_time or next values. - measurement_time = 0; } + // Reset measurement_time or next values. + measurement_time = 0; } } } - ESP_LOGV(TAG, "%s: Found %u values with best data %u time %u.", device.address_str().c_str(), + ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(), number_of_usable_values, best_value, best_time); - if (number_of_usable_values < 2 || best_value < 2 || best_time < 2) { + if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) { // At least two measurement values must be present. - ESP_LOGW(TAG, "%s: Poor read quality. Setting distance to 0.", device.address_str().c_str()); + ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str()); if (this->distance_ != nullptr) { this->distance_->publish_state(0); } @@ -167,7 +167,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) } } else { float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c); - ESP_LOGV(TAG, "%s: Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound); + ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound); uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f; diff --git a/tests/test2.yaml b/tests/test2.yaml index aa3e467816..675fae6cf3 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -397,6 +397,19 @@ sensor: name: MICS-4514 C2H5OH ammonia: name: MICS-4514 NH3 + - platform: mopeka_std_check + mac_address: D3:75:F2:DC:16:91 + tank_type: CUSTOM + custom_distance_full: 40cm + custom_distance_empty: 10mm + temperature: + name: Propane test temp + level: + name: Propane test level + distance: + name: Propane test distance + battery_level: + name: Propane test battery level time: - platform: homeassistant From a74abb8ea84d2ba2ad32ef7443abba737a6268c7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 3 Jul 2023 19:57:44 -0500 Subject: [PATCH 065/106] Adjust signature for on_disconnect (#5009) --- esphome/components/api/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index c777c3be9d..819055ccf4 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -47,7 +47,7 @@ async def async_run_logs(config, address): except APIConnectionError: cli.disconnect() - async def on_disconnect(): + async def on_disconnect(expected_disconnect: bool) -> None: _LOGGER.warning("Disconnected from API") zc = zeroconf.Zeroconf() From d64d1650e39a8bc5b2555a26ede2f0b07a965000 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Jul 2023 13:45:06 +1200 Subject: [PATCH 066/106] Update webserver to ea86d81 (#5023) --- esphome/components/web_server/server_index.h | 1185 +++++++++--------- 1 file changed, 594 insertions(+), 591 deletions(-) diff --git a/esphome/components/web_server/server_index.h b/esphome/components/web_server/server_index.h index 2dbb839c5e..180dffab67 100644 --- a/esphome/components/web_server/server_index.h +++ b/esphome/components/web_server/server_index.h @@ -6,597 +6,600 @@ namespace esphome { namespace web_server { const uint8_t INDEX_GZ[] PROGMEM = { - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xbd, 0x7d, 0xdb, 0x76, 0xe3, 0x46, 0x92, 0xe0, 0xf3, - 0x9e, 0xb3, 0x7f, 0xb0, 0x2f, 0x28, 0x58, 0x53, 0x05, 0xb4, 0x40, 0x88, 0xa4, 0x4a, 0x55, 0x65, 0x50, 0x20, 0x5b, - 0x75, 0xb1, 0xab, 0xec, 0xba, 0xb9, 0xa4, 0xb2, 0xdb, 0x96, 0xd5, 0x12, 0x44, 0x26, 0x45, 0xb8, 0x40, 0x80, 0x06, - 0x92, 0xba, 0x98, 0xc2, 0x9c, 0x79, 0x9a, 0xa7, 0x39, 0x67, 0x6f, 0xf3, 0x30, 0x0f, 0x3b, 0x67, 0xe6, 0x61, 0x3f, - 0x62, 0x9f, 0xe7, 0x53, 0xfa, 0x07, 0x76, 0x3e, 0x61, 0x23, 0x22, 0x2f, 0x48, 0x80, 0xa4, 0x24, 0x7b, 0xdc, 0x7b, - 0xdc, 0xd5, 0x02, 0xf2, 0x1a, 0x11, 0x19, 0x19, 0xb7, 0x8c, 0x04, 0x77, 0xef, 0x8d, 0xb2, 0x21, 0xbf, 0x9a, 0x31, - 0x6b, 0xc2, 0xa7, 0x49, 0x7f, 0x57, 0xfe, 0x3f, 0x8b, 0x46, 0xfd, 0xdd, 0x24, 0x4e, 0x3f, 0x59, 0x39, 0x4b, 0xc2, - 0x78, 0x98, 0xa5, 0xd6, 0x24, 0x67, 0xe3, 0x70, 0x14, 0xf1, 0x28, 0x88, 0xa7, 0xd1, 0x19, 0xb3, 0xb6, 0xfa, 0xbb, - 0x53, 0xc6, 0x23, 0x6b, 0x38, 0x89, 0xf2, 0x82, 0xf1, 0xf0, 0xe3, 0xc1, 0x17, 0xad, 0x27, 0xfd, 0xdd, 0x62, 0x98, - 0xc7, 0x33, 0x6e, 0xe1, 0x90, 0xe1, 0x34, 0x1b, 0xcd, 0x13, 0xd6, 0x3f, 0x8f, 0x72, 0xeb, 0x05, 0x0b, 0xdf, 0x9d, - 0xfe, 0xc4, 0x86, 0xdc, 0x1f, 0xb1, 0x71, 0x9c, 0xb2, 0xf7, 0x79, 0x36, 0x63, 0x39, 0xbf, 0xf2, 0xf6, 0x57, 0x57, - 0xc4, 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x33, 0xc6, 0xdf, 0x5d, 0xa4, 0xaa, 0xcf, 0x73, 0x26, 0x26, 0xc9, 0xf2, 0xc2, - 0x8b, 0xd7, 0xb4, 0xd9, 0xbf, 0x9a, 0x9e, 0x66, 0x49, 0xe1, 0x7d, 0xd2, 0xf5, 0xb3, 0x3c, 0xe3, 0x19, 0x82, 0xe5, - 0x4f, 0xa2, 0xc2, 0x68, 0xe9, 0xbd, 0x5b, 0xd1, 0x64, 0x26, 0x2b, 0x5f, 0x15, 0x2f, 0xd2, 0xf9, 0x94, 0xe5, 0xd1, - 0x69, 0xc2, 0xbc, 0x9c, 0x85, 0x0e, 0xf7, 0x98, 0x17, 0xbb, 0x61, 0x9f, 0x59, 0x71, 0x6a, 0xf1, 0xc1, 0x0b, 0x46, - 0x25, 0x0b, 0xa6, 0x5b, 0x05, 0xf7, 0xda, 0x1e, 0x90, 0x6b, 0x1c, 0x9f, 0xcd, 0xf5, 0xfb, 0x45, 0x1e, 0x73, 0xf5, - 0x7c, 0x1e, 0x25, 0x73, 0x16, 0xc4, 0xa5, 0x1b, 0xf0, 0x43, 0x76, 0x14, 0xc6, 0xde, 0x27, 0x1a, 0x14, 0x86, 0x5c, - 0x8c, 0xb3, 0xdc, 0x41, 0x5a, 0xc5, 0x38, 0x36, 0xbb, 0xbe, 0x76, 0x58, 0xb8, 0x28, 0x5d, 0xf7, 0x13, 0xf3, 0x87, - 0x51, 0x92, 0x38, 0x38, 0xf1, 0xfd, 0xfb, 0x39, 0xce, 0x18, 0x7b, 0xec, 0x30, 0x3e, 0x72, 0x7b, 0xf1, 0xd8, 0x89, - 0x99, 0x5b, 0xf5, 0xcb, 0xc6, 0x56, 0xcc, 0x1c, 0xe6, 0xba, 0xef, 0xd6, 0xf7, 0xc9, 0x19, 0x9f, 0xe7, 0x00, 0x7b, - 0xe9, 0xbd, 0x53, 0x33, 0xef, 0x63, 0xfd, 0x33, 0xea, 0xd8, 0x03, 0xd8, 0x0b, 0x6e, 0x7d, 0x11, 0x5e, 0xc4, 0xe9, - 0x28, 0xbb, 0xf0, 0xf7, 0x27, 0x11, 0xfc, 0xf9, 0x90, 0x65, 0xfc, 0xfe, 0x7d, 0xe7, 0x3c, 0x8b, 0x47, 0x56, 0x3b, - 0x0c, 0xcd, 0xca, 0xab, 0x67, 0xfb, 0xfb, 0xd7, 0xd7, 0x8d, 0x02, 0x3f, 0x8d, 0x78, 0x7c, 0xce, 0x44, 0x67, 0x00, - 0xc0, 0x86, 0xbf, 0x33, 0xce, 0x46, 0xfb, 0xfc, 0x2a, 0x81, 0x52, 0xc6, 0x78, 0x61, 0x03, 0x8e, 0xcf, 0xb3, 0x21, - 0x90, 0x2d, 0x35, 0x08, 0x0f, 0x4d, 0x73, 0x36, 0x4b, 0xa2, 0x21, 0xc3, 0x7a, 0x18, 0xa9, 0xea, 0x51, 0x35, 0xf2, - 0xbe, 0x0b, 0xc5, 0xf2, 0x3a, 0xae, 0x97, 0xb1, 0x30, 0x65, 0x17, 0xd6, 0x9b, 0x68, 0xd6, 0x1b, 0x26, 0x51, 0x51, - 0x58, 0x29, 0x5b, 0x10, 0x0a, 0xf9, 0x7c, 0x08, 0x0c, 0x42, 0x08, 0x2e, 0x80, 0x4c, 0x7c, 0x12, 0x17, 0xfe, 0xf1, - 0xc6, 0xb0, 0x28, 0x3e, 0xb0, 0x62, 0x9e, 0xf0, 0x8d, 0x10, 0xd6, 0x82, 0xdd, 0x0b, 0xc3, 0xef, 0x5c, 0x3e, 0xc9, - 0xb3, 0x0b, 0xeb, 0x45, 0x9e, 0x43, 0x73, 0x1b, 0xa6, 0x14, 0x0d, 0xac, 0x18, 0xc6, 0xca, 0xb8, 0xa5, 0x07, 0xc3, - 0x05, 0xf4, 0xad, 0x8f, 0x05, 0xb3, 0x4e, 0xe6, 0x69, 0x11, 0x8d, 0x19, 0x34, 0x3d, 0xb1, 0xb2, 0xdc, 0x3a, 0x81, - 0x41, 0x4f, 0x60, 0xc9, 0x0a, 0x0e, 0xbb, 0xc6, 0xb7, 0xdd, 0x1e, 0xcd, 0x05, 0x85, 0x07, 0xec, 0x92, 0x87, 0xbc, - 0x04, 0xc6, 0xb4, 0x0a, 0x8d, 0x86, 0xe3, 0x2e, 0x12, 0x28, 0xe0, 0x61, 0xc6, 0x90, 0x65, 0x1d, 0xb3, 0xb1, 0x5e, - 0x9c, 0x2f, 0xee, 0xdf, 0xd7, 0xb4, 0x46, 0xc2, 0x43, 0xdb, 0xa2, 0xd1, 0xd6, 0xe3, 0x84, 0x78, 0x8d, 0x44, 0xae, - 0xc7, 0x7d, 0x49, 0xbe, 0xfd, 0xab, 0x74, 0x58, 0x1f, 0x1b, 0x2a, 0x4b, 0x9e, 0xed, 0xf3, 0x3c, 0x4e, 0xcf, 0x00, - 0x08, 0xc5, 0x06, 0x46, 0x93, 0xb2, 0x14, 0x8b, 0xff, 0x9e, 0x85, 0x3c, 0xec, 0xe3, 0xe8, 0x29, 0x73, 0xec, 0x82, - 0x7a, 0xd8, 0x00, 0x08, 0x90, 0x1e, 0x18, 0x8c, 0x0f, 0x78, 0xc0, 0x37, 0x6d, 0xdb, 0xfb, 0xce, 0xf5, 0xae, 0x90, - 0x83, 0x7c, 0xdf, 0x27, 0xf6, 0x15, 0x9d, 0xe3, 0xb0, 0x83, 0x40, 0xfb, 0x09, 0x4b, 0xcf, 0xf8, 0x64, 0xc0, 0x0f, - 0xdb, 0x47, 0x01, 0x03, 0xa8, 0x46, 0xf3, 0x21, 0x73, 0x90, 0x1f, 0xbd, 0x1c, 0xb7, 0xcf, 0xa6, 0x03, 0x53, 0xe0, - 0xc2, 0xdc, 0x23, 0x1c, 0x6b, 0x4b, 0xe3, 0x2a, 0xd8, 0x14, 0x60, 0xc8, 0xe7, 0x36, 0xec, 0xb0, 0x53, 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, 0xe4, 0x87, 0xf9, 0x66, 0xe7, 0xc8, - 0x43, 0x28, 0x35, 0xf1, 0x25, 0xe2, 0x31, 0x20, 0x58, 0x7a, 0x1f, 0x99, 0xde, 0x9e, 0x5f, 0x0c, 0xb8, 0xbf, 0xcc, - 0xc7, 0x21, 0xf3, 0xa7, 0xd1, 0x0c, 0xb1, 0xe1, 0xc4, 0x03, 0x51, 0x3a, 0x44, 0xe8, 0x6a, 0xeb, 0x82, 0x14, 0xf3, - 0x2b, 0x16, 0x70, 0x81, 0x20, 0xb0, 0x67, 0x5f, 0x44, 0xc3, 0x09, 0x6c, 0xf1, 0x8a, 0x70, 0x23, 0xb5, 0x1d, 0x86, - 0x39, 0x8b, 0x38, 0x7b, 0x91, 0x30, 0x7c, 0xc3, 0x15, 0x80, 0x9e, 0xb6, 0xeb, 0xe5, 0x6a, 0xdf, 0x25, 0x31, 0x7f, - 0x9b, 0xc1, 0x3c, 0x3d, 0xc1, 0x24, 0xc0, 0xc5, 0xf9, 0xfd, 0xfb, 0x31, 0xb2, 0xc8, 0x1e, 0x87, 0xd5, 0x3a, 0x9d, - 0x73, 0x58, 0xb7, 0x14, 0x5b, 0xd8, 0x40, 0x6d, 0x2f, 0xf6, 0x39, 0x10, 0xf1, 0x59, 0x96, 0x72, 0x18, 0x0e, 0xe0, - 0xd5, 0x1c, 0xe4, 0x47, 0xb3, 0x19, 0x4b, 0x47, 0xcf, 0x26, 0x71, 0x32, 0x02, 0x6a, 0x94, 0x80, 0x6f, 0xc2, 0x42, - 0xc0, 0x13, 0x90, 0x09, 0x6e, 0xc6, 0x88, 0x96, 0x0f, 0x19, 0x99, 0x85, 0xb6, 0xdd, 0x43, 0x09, 0x24, 0xb1, 0x40, - 0x19, 0x44, 0x0b, 0xf7, 0x01, 0x44, 0x7f, 0xe1, 0xb2, 0xcd, 0x30, 0xd6, 0xcb, 0x28, 0x09, 0xfc, 0x1e, 0x25, 0x0d, - 0xd0, 0x1f, 0x08, 0xc1, 0x7b, 0x28, 0xb8, 0xbe, 0x92, 0x52, 0x27, 0x62, 0x0a, 0x43, 0x20, 0xc0, 0x10, 0x25, 0x88, - 0xa4, 0xc1, 0xfb, 0x2c, 0xb9, 0x1a, 0xc7, 0x49, 0xb2, 0x3f, 0x9f, 0xcd, 0xb2, 0x9c, 0x7b, 0x5f, 0x87, 0x0b, 0x9e, - 0x55, 0xb8, 0xd2, 0x26, 0x2f, 0x2e, 0x62, 0x8e, 0x04, 0x75, 0x17, 0xc3, 0x08, 0x96, 0xfa, 0x69, 0x96, 0x25, 0x2c, - 0x4a, 0x01, 0x0d, 0x3e, 0xb0, 0xed, 0x20, 0x9d, 0x27, 0x49, 0xef, 0x14, 0x86, 0xfd, 0xd4, 0xa3, 0x6a, 0x21, 0xf1, - 0x03, 0x7a, 0xde, 0xcb, 0xf3, 0xe8, 0x0a, 0x1a, 0x62, 0x1b, 0x60, 0x2f, 0x58, 0xad, 0xaf, 0xf6, 0xdf, 0xbd, 0xf5, - 0x05, 0xe3, 0xc7, 0xe3, 0x2b, 0x00, 0xb4, 0xac, 0xa4, 0xe6, 0x38, 0xcf, 0xa6, 0x8d, 0xa9, 0x91, 0x0e, 0x71, 0xc8, - 0x7b, 0x6b, 0x40, 0x88, 0x69, 0x64, 0x58, 0x25, 0x6e, 0x42, 0xf0, 0x96, 0xf8, 0x59, 0x56, 0xe2, 0x1e, 0x18, 0xe0, - 0x43, 0x20, 0x8a, 0x61, 0xca, 0x5b, 0xa0, 0xcd, 0xaf, 0x16, 0x71, 0x48, 0x70, 0xce, 0x50, 0xff, 0x22, 0x8c, 0xc3, - 0x08, 0x66, 0x5f, 0x88, 0x01, 0x4b, 0x05, 0x71, 0x5c, 0x96, 0xde, 0x44, 0x33, 0x31, 0x4a, 0x3c, 0x14, 0x28, 0x2c, - 0x0c, 0x41, 0xc1, 0x70, 0x78, 0x71, 0xbd, 0x6f, 0xc2, 0x45, 0xa4, 0xf0, 0x41, 0x0d, 0x85, 0xfb, 0x2b, 0x10, 0x72, - 0x02, 0x35, 0xd9, 0x39, 0xe8, 0x41, 0x80, 0xf3, 0x6b, 0x50, 0x7f, 0xe3, 0x04, 0xa1, 0xb8, 0xd7, 0xf1, 0x40, 0x83, - 0x3e, 0x9b, 0x44, 0xe9, 0x19, 0x1b, 0x05, 0x13, 0x56, 0x4a, 0xc9, 0xbb, 0x67, 0xc1, 0x1a, 0x03, 0x3b, 0x15, 0xd6, - 0xcb, 0x83, 0x37, 0xaf, 0xe5, 0xca, 0xd5, 0x84, 0x31, 0x2c, 0xd2, 0x1c, 0xd4, 0x2a, 0x88, 0x6d, 0x29, 0x8e, 0x5f, - 0x70, 0x25, 0xbd, 0x45, 0x49, 0x5c, 0x7c, 0x9c, 0x81, 0x89, 0xc1, 0xde, 0xc3, 0x30, 0x30, 0x7d, 0x08, 0x53, 0x51, - 0x39, 0xcc, 0x27, 0x2a, 0x46, 0xba, 0x08, 0x3a, 0x0b, 0x4c, 0xc5, 0x6b, 0xe6, 0xb8, 0x25, 0xb0, 0x2a, 0x8f, 0x87, - 0x56, 0x34, 0x1a, 0xbd, 0x4a, 0x63, 0x1e, 0x47, 0x49, 0xfc, 0x0b, 0x51, 0x72, 0x81, 0x3c, 0xc6, 0x7a, 0x72, 0x11, - 0x00, 0x77, 0xea, 0x91, 0xb8, 0x4a, 0xc8, 0xde, 0x23, 0x62, 0x08, 0x69, 0x99, 0x84, 0x87, 0x47, 0x12, 0xbc, 0xc4, - 0x9f, 0xcd, 0x8b, 0x09, 0x12, 0x56, 0x0e, 0x8c, 0x82, 0x3c, 0x3b, 0x2d, 0x58, 0x7e, 0xce, 0x46, 0x9a, 0x03, 0x0a, - 0xc0, 0x8a, 0x9a, 0x83, 0xf1, 0x42, 0x33, 0x3a, 0x4a, 0x87, 0x72, 0x18, 0xaa, 0x67, 0x8a, 0x59, 0x26, 0x99, 0x59, - 0x5b, 0x38, 0x5a, 0x0a, 0x38, 0xc2, 0xa8, 0x90, 0x92, 0x20, 0x0f, 0x15, 0x86, 0x13, 0x90, 0x42, 0xcc, 0xad, 0x6d, - 0x73, 0xa5, 0xc9, 0x5e, 0xcc, 0x49, 0x25, 0xe4, 0xd0, 0x11, 0x36, 0x32, 0x41, 0x9a, 0xbb, 0xb0, 0xab, 0x40, 0xca, - 0x4b, 0x70, 0x85, 0x14, 0x51, 0x66, 0x0e, 0x32, 0x40, 0xf8, 0x0d, 0xe9, 0x42, 0x50, 0x26, 0xd0, 0x82, 0x21, 0x1b, - 0xf8, 0x7a, 0xe5, 0x81, 0xb0, 0x12, 0xef, 0x0a, 0x11, 0x6f, 0x0d, 0xd8, 0xa4, 0x8b, 0x00, 0x30, 0xef, 0x1e, 0xf3, - 0xd3, 0x6c, 0x6f, 0x38, 0x64, 0x45, 0x91, 0x01, 0x6c, 0xf7, 0xa8, 0xfd, 0x3a, 0x43, 0x0b, 0x28, 0xe9, 0x6a, 0x59, - 0x67, 0x17, 0xa4, 0xc1, 0x4d, 0xb5, 0xa2, 0x74, 0x7a, 0x60, 0x1f, 0x1f, 0x83, 0xcc, 0xf6, 0x24, 0x19, 0x80, 0xea, - 0xcb, 0x86, 0x9f, 0xb0, 0x67, 0xea, 0x94, 0x59, 0x69, 0x5f, 0x3a, 0x75, 0x90, 0x3c, 0x18, 0xd6, 0x2d, 0x8d, 0x05, - 0x5d, 0x39, 0x34, 0xae, 0x86, 0x54, 0x90, 0x8b, 0x33, 0x52, 0xd9, 0xc6, 0x32, 0x82, 0xd5, 0x56, 0x7a, 0x44, 0x7a, - 0x85, 0x4d, 0x41, 0x80, 0x1e, 0xf2, 0xa3, 0x9e, 0xac, 0x0f, 0x73, 0x41, 0xb9, 0x9c, 0xfd, 0x3c, 0x67, 0x05, 0x17, - 0xac, 0x0b, 0xe3, 0x82, 0xb9, 0x0a, 0x22, 0xb6, 0x69, 0x1d, 0xd6, 0x6c, 0xc7, 0x55, 0xb0, 0xbd, 0x9b, 0xa1, 0x1e, - 0x2b, 0x90, 0x93, 0x6f, 0x66, 0x27, 0xb2, 0x27, 0xdc, 0xeb, 0xeb, 0x6f, 0xd4, 0x20, 0xd5, 0x52, 0x6a, 0x1b, 0xa8, - 0xb1, 0x26, 0xb6, 0x6a, 0x32, 0xb2, 0x5d, 0xa9, 0x50, 0xef, 0x75, 0x7a, 0x35, 0x3e, 0x80, 0x3d, 0xd7, 0xd6, 0x2c, - 0x5d, 0x19, 0xdb, 0xef, 0x15, 0x4d, 0xdf, 0x89, 0x91, 0xc9, 0x1a, 0xe5, 0xb7, 0x73, 0x8f, 0xda, 0xf1, 0xd0, 0x76, - 0xa9, 0xae, 0x12, 0x0c, 0xf3, 0xba, 0x60, 0x68, 0x42, 0x3d, 0xd3, 0x5d, 0x6c, 0xcd, 0x54, 0x3c, 0x54, 0x6b, 0xad, - 0x1c, 0x08, 0x16, 0x1e, 0x82, 0x71, 0xb2, 0xd2, 0x3f, 0x78, 0x1b, 0x4d, 0x19, 0x52, 0xd4, 0x5b, 0xd7, 0x40, 0x3a, - 0x10, 0xd0, 0xe4, 0xa8, 0xa9, 0xde, 0x98, 0x2b, 0xac, 0xa6, 0xfa, 0xfe, 0x8a, 0xc1, 0x8a, 0x00, 0xfb, 0xba, 0x5c, - 0xb1, 0x44, 0xa4, 0x37, 0x05, 0x97, 0x68, 0xfa, 0x88, 0x32, 0xb1, 0x26, 0xa4, 0xe0, 0x01, 0x79, 0x58, 0xfe, 0xc6, - 0xc2, 0xa9, 0x56, 0x0a, 0x47, 0x86, 0x32, 0x05, 0xe8, 0x4c, 0x4a, 0x00, 0xc4, 0x25, 0xfd, 0xad, 0x6d, 0x2c, 0x24, - 0xdb, 0x3e, 0xf2, 0x81, 0x3f, 0x4e, 0x22, 0xee, 0x74, 0xb6, 0xda, 0x2e, 0xf0, 0x21, 0x08, 0x71, 0xd0, 0x11, 0x60, - 0xde, 0x57, 0xa8, 0x70, 0xf2, 0x16, 0x5c, 0xe6, 0x83, 0x51, 0x34, 0x89, 0xc7, 0xdc, 0x49, 0x50, 0x89, 0xb8, 0x25, - 0x4b, 0x40, 0xc9, 0xe8, 0x7d, 0x05, 0xca, 0x82, 0x09, 0xe9, 0x22, 0xaa, 0x95, 0x40, 0x63, 0x0a, 0x52, 0x92, 0x52, - 0xa4, 0x05, 0x15, 0x04, 0x86, 0x50, 0xe9, 0x29, 0x8e, 0x02, 0xfd, 0x16, 0x0f, 0xc4, 0xa0, 0xc1, 0x92, 0x45, 0x19, - 0x0f, 0xe2, 0xe5, 0x42, 0x50, 0xc3, 0x3e, 0xcf, 0x5e, 0x67, 0x17, 0x2c, 0x7f, 0x16, 0x21, 0xec, 0x81, 0xe8, 0x5e, - 0x82, 0xa4, 0x27, 0x81, 0xce, 0x7b, 0x8a, 0x57, 0xce, 0x09, 0x69, 0x58, 0x88, 0x69, 0x8c, 0x8a, 0x10, 0xec, 0x16, - 0xa2, 0x7d, 0x8a, 0x5b, 0x8a, 0xf6, 0x1e, 0xaa, 0x12, 0xae, 0x79, 0x6b, 0xef, 0x75, 0x9d, 0xb7, 0x60, 0x84, 0x99, - 0xe2, 0xd6, 0xfa, 0x8e, 0x75, 0x3d, 0xa9, 0x9b, 0x1d, 0xc9, 0x5b, 0x86, 0x32, 0x03, 0xfd, 0x71, 0x7d, 0x5d, 0x19, - 0xe9, 0xa0, 0x4c, 0xb5, 0x34, 0x47, 0x08, 0xc4, 0x96, 0x70, 0x4b, 0x50, 0x46, 0x68, 0x78, 0xe5, 0x59, 0x92, 0x18, - 0xba, 0xc8, 0x8b, 0x7b, 0x4e, 0x43, 0x1d, 0x01, 0x14, 0xd3, 0x9a, 0x46, 0x1a, 0xb0, 0x40, 0x57, 0xa0, 0x52, 0x52, - 0xda, 0xc8, 0xab, 0xd6, 0x46, 0x40, 0x9c, 0x8e, 0x58, 0x2e, 0x1c, 0x34, 0xa9, 0x43, 0x61, 0xc2, 0x14, 0x18, 0x9a, - 0x8d, 0x40, 0xc2, 0x2b, 0x04, 0xc0, 0x3c, 0xf1, 0x27, 0x59, 0xc1, 0x75, 0x9d, 0x09, 0x7d, 0x7c, 0x7d, 0x1d, 0x0b, - 0x7f, 0x11, 0x19, 0x20, 0x67, 0xd3, 0xec, 0x9c, 0xad, 0x80, 0xba, 0xa7, 0x06, 0x33, 0x41, 0x36, 0x86, 0x01, 0x25, - 0x0a, 0xaa, 0x65, 0x96, 0xc4, 0x60, 0xe9, 0xeb, 0x06, 0x3e, 0x18, 0x74, 0xec, 0x12, 0x65, 0x84, 0xdb, 0xef, 0xf7, - 0xdb, 0x5e, 0xc7, 0x2d, 0x05, 0xc1, 0x17, 0x4b, 0x14, 0xbd, 0x41, 0x3f, 0x4a, 0x13, 0x7c, 0x95, 0x2c, 0x60, 0xae, - 0xa1, 0x14, 0x39, 0xe9, 0x26, 0xe6, 0x49, 0x41, 0xec, 0x7a, 0x23, 0x18, 0x94, 0x33, 0x25, 0xb8, 0xd1, 0xc4, 0x15, - 0xdb, 0xf6, 0x83, 0x26, 0x9b, 0x66, 0x27, 0xb5, 0xc3, 0xd4, 0xc2, 0xc8, 0x35, 0x2f, 0xb4, 0x07, 0x6c, 0x2e, 0x0f, - 0xd9, 0xf4, 0x58, 0x0d, 0xbc, 0x0e, 0x10, 0x0a, 0x4f, 0xd7, 0x59, 0x42, 0xa9, 0xea, 0x2c, 0x85, 0xb8, 0xde, 0x40, - 0x1f, 0x99, 0x04, 0x73, 0x15, 0x09, 0xf6, 0xa5, 0x40, 0x60, 0xe8, 0x91, 0x89, 0xf5, 0x7a, 0x06, 0xcb, 0x73, 0x1a, - 0x0d, 0x3f, 0x69, 0x70, 0x2b, 0xde, 0x6b, 0xb2, 0x81, 0xd3, 0x28, 0x09, 0x0d, 0x71, 0x65, 0xe2, 0xad, 0x24, 0x74, - 0x6d, 0xa3, 0x80, 0x43, 0xb6, 0xc4, 0xf6, 0xcd, 0x85, 0x6e, 0x72, 0xbb, 0x64, 0x0f, 0xe5, 0x3f, 0x55, 0x5c, 0xb2, - 0x9e, 0xe5, 0x98, 0x92, 0x06, 0x4c, 0x31, 0x1e, 0x2c, 0x4d, 0x03, 0x12, 0xe0, 0xbb, 0x72, 0x14, 0x17, 0xeb, 0x49, - 0xf0, 0xbb, 0x82, 0xf9, 0xdc, 0x98, 0xe9, 0x56, 0x48, 0xb5, 0x84, 0x93, 0x66, 0xb0, 0x06, 0x4d, 0x1a, 0x0f, 0x4a, - 0xd4, 0x7c, 0x8d, 0x86, 0x0a, 0x71, 0xfc, 0x99, 0xa8, 0x42, 0x13, 0x0c, 0xc1, 0xc8, 0xbd, 0x42, 0x32, 0x5c, 0xb6, - 0x2c, 0x5a, 0xa4, 0x4c, 0x8d, 0x49, 0xa5, 0x6a, 0x96, 0xcb, 0xc0, 0xc0, 0xa2, 0xdd, 0xea, 0x4b, 0x4b, 0x5c, 0x89, - 0xdc, 0x34, 0xd4, 0xc2, 0xa4, 0x50, 0xde, 0x84, 0x93, 0xa3, 0xdf, 0xa5, 0xac, 0x77, 0x13, 0x9f, 0x5c, 0xe1, 0x93, - 0xfb, 0x86, 0x0f, 0x65, 0xf2, 0x76, 0x31, 0x28, 0x82, 0xaf, 0x6b, 0x95, 0x68, 0x9f, 0xfa, 0x28, 0x98, 0x5d, 0x2d, - 0x74, 0x41, 0xa0, 0x48, 0x36, 0x49, 0x07, 0x92, 0xdf, 0x50, 0x6c, 0x54, 0x9e, 0x51, 0xe6, 0x8a, 0x0d, 0x52, 0xf3, - 0x4a, 0x33, 0x2f, 0x75, 0x1b, 0xf6, 0x7b, 0x59, 0x4a, 0x3a, 0x31, 0x41, 0x99, 0xd8, 0xbb, 0x89, 0x36, 0x5e, 0x1a, - 0x66, 0xc2, 0xfa, 0x15, 0xc6, 0x4e, 0x8d, 0x42, 0xa9, 0x14, 0x81, 0x38, 0x36, 0xbe, 0x56, 0x96, 0x41, 0xe6, 0xaf, - 0xb0, 0xa7, 0x00, 0x94, 0x04, 0x16, 0x5f, 0x53, 0xc9, 0x8b, 0xc2, 0x3a, 0x1d, 0xef, 0x11, 0x1d, 0x2b, 0x11, 0x5a, - 0x13, 0xf9, 0x5a, 0x9f, 0xc5, 0x7e, 0xcd, 0x25, 0x34, 0x29, 0x99, 0x0f, 0xf2, 0xc0, 0x56, 0x81, 0x88, 0x4a, 0xb7, - 0x25, 0x83, 0x84, 0x1c, 0xd2, 0x65, 0xa2, 0xd7, 0x46, 0x32, 0x68, 0x9d, 0x0a, 0x89, 0x96, 0x1e, 0x85, 0x11, 0x8a, - 0x0d, 0xb1, 0x16, 0x4b, 0x84, 0x6c, 0xda, 0x9b, 0xc4, 0x8a, 0xe8, 0x9c, 0xe6, 0x68, 0xc2, 0x99, 0x3a, 0xdd, 0x71, - 0x00, 0x1d, 0x10, 0xfb, 0x4b, 0xac, 0xb7, 0xd2, 0xec, 0x74, 0xfd, 0xca, 0xe1, 0xbb, 0xbe, 0x9e, 0x00, 0x3f, 0x48, - 0x83, 0x17, 0xd6, 0x6c, 0xa0, 0x64, 0xef, 0xde, 0x6b, 0x6c, 0x45, 0xf6, 0x67, 0x55, 0x52, 0x79, 0x0a, 0x35, 0xce, - 0xad, 0xaf, 0x53, 0x2d, 0xb4, 0xa8, 0x2a, 0xf6, 0x0d, 0xa9, 0xbe, 0xaf, 0x14, 0x76, 0x85, 0xf2, 0xbe, 0x1c, 0x3a, - 0x76, 0x5d, 0x37, 0xc8, 0xc9, 0x79, 0xb9, 0xb7, 0xca, 0x85, 0xbc, 0x7f, 0xdf, 0xf4, 0x99, 0xce, 0xf5, 0xf0, 0xcf, - 0x1c, 0x54, 0xce, 0xc5, 0x55, 0x4a, 0x16, 0xcc, 0x33, 0xa5, 0x8e, 0x96, 0x1c, 0xd0, 0x76, 0x0f, 0x3d, 0xed, 0xe8, - 0x22, 0x8a, 0xb9, 0xa5, 0x47, 0x11, 0x9e, 0x36, 0xca, 0x27, 0x69, 0x74, 0x00, 0x5e, 0x68, 0x42, 0x92, 0x13, 0x6e, - 0xda, 0xa2, 0xc5, 0x70, 0xc2, 0x30, 0x04, 0xae, 0xec, 0x09, 0x53, 0xf6, 0xdc, 0x43, 0xbc, 0xe5, 0xc0, 0xab, 0x61, - 0x2f, 0x9b, 0xdd, 0x6b, 0xe6, 0x3f, 0xac, 0x11, 0xc8, 0xb6, 0xa9, 0xaa, 0x2b, 0x1b, 0xef, 0x52, 0x44, 0x62, 0x84, - 0x6d, 0xd5, 0xd8, 0xd2, 0xd6, 0xef, 0x35, 0xdc, 0xeb, 0xca, 0x31, 0xaf, 0x29, 0xd5, 0x86, 0x1e, 0x56, 0x6e, 0x0e, - 0x37, 0x1d, 0x79, 0xb1, 0x82, 0x6e, 0x4f, 0x04, 0x85, 0xc0, 0x89, 0x50, 0xf6, 0xa0, 0xe6, 0x06, 0x22, 0x25, 0x53, - 0x5a, 0x35, 0x9b, 0x27, 0x23, 0x09, 0x2c, 0xb8, 0xb0, 0x4c, 0xf2, 0xd1, 0x45, 0x9c, 0x24, 0x55, 0xe9, 0xef, 0x2a, - 0xe0, 0xc5, 0xb0, 0xb7, 0x89, 0x76, 0x81, 0xd1, 0x5c, 0x81, 0xe0, 0x6a, 0x23, 0xec, 0xa3, 0xe3, 0x56, 0xeb, 0x2e, - 0x22, 0x8e, 0xcc, 0x8c, 0x46, 0x7c, 0x44, 0x1b, 0xb2, 0x64, 0x9a, 0xb5, 0xf7, 0x5e, 0x60, 0x48, 0xcd, 0xc0, 0x07, - 0xd5, 0x19, 0x15, 0xff, 0x2a, 0x7b, 0xea, 0x57, 0xa2, 0x77, 0xab, 0xea, 0x6a, 0x06, 0x54, 0x54, 0xe0, 0xc3, 0x0c, - 0xb1, 0xb4, 0x55, 0x20, 0x20, 0xd7, 0xc3, 0x3a, 0xdc, 0xad, 0x91, 0x06, 0x0b, 0x4a, 0x81, 0xb5, 0x56, 0x76, 0xaf, - 0x6f, 0x0b, 0xe6, 0x50, 0x28, 0x5c, 0xf4, 0x7f, 0x96, 0x4d, 0x67, 0x68, 0x99, 0x35, 0x98, 0x1a, 0x1a, 0x7c, 0x6c, - 0xd4, 0x97, 0x2b, 0xca, 0x6a, 0x7d, 0x68, 0x47, 0xd6, 0xf8, 0x49, 0x3b, 0xca, 0xe0, 0x50, 0xcd, 0x75, 0x51, 0xdd, - 0x6e, 0x6e, 0x8a, 0x98, 0x55, 0x3c, 0xee, 0x93, 0xde, 0xd6, 0xd6, 0xa4, 0xa7, 0x69, 0x40, 0x32, 0x49, 0x32, 0xbc, - 0xc9, 0x00, 0x65, 0x45, 0x9c, 0x45, 0xd9, 0x20, 0xdf, 0xa2, 0x2c, 0x71, 0xfd, 0x7e, 0xe8, 0xed, 0xd5, 0x3c, 0x6b, - 0x6f, 0x6f, 0xbd, 0x8b, 0x5c, 0xd5, 0x49, 0x0f, 0xf2, 0xf0, 0x08, 0x8a, 0x96, 0x6c, 0xca, 0x70, 0x31, 0xcd, 0x46, - 0x2c, 0xb0, 0xa1, 0x7b, 0x6a, 0x97, 0x72, 0xd3, 0x44, 0xc0, 0x3d, 0x11, 0x73, 0x16, 0x1f, 0xea, 0x91, 0xd4, 0x60, - 0x0f, 0x58, 0x40, 0x9b, 0x0b, 0x5f, 0x85, 0x67, 0x49, 0x76, 0x1a, 0x25, 0x07, 0x42, 0x81, 0xd7, 0x5a, 0x7e, 0x0b, - 0x2e, 0x23, 0x59, 0xac, 0x86, 0x92, 0xfa, 0x6a, 0xf0, 0x55, 0x70, 0x7b, 0x8f, 0xca, 0x5b, 0xb1, 0x3b, 0x7e, 0xdb, - 0xef, 0xd8, 0x2a, 0x22, 0xf6, 0x93, 0x39, 0x1d, 0x68, 0x9c, 0x02, 0x28, 0x73, 0x00, 0x9a, 0xac, 0xf0, 0x86, 0x2c, - 0xfc, 0x69, 0xf0, 0x93, 0x72, 0xa9, 0x33, 0x70, 0x21, 0xc0, 0xc9, 0x4f, 0x62, 0xde, 0xc2, 0xf3, 0x48, 0xdb, 0x5b, - 0x88, 0x0a, 0x8c, 0x2b, 0x52, 0x5c, 0xba, 0x54, 0xde, 0xa0, 0x77, 0x1c, 0x9e, 0x40, 0xb3, 0x8d, 0x8d, 0x85, 0xf3, - 0x26, 0xe2, 0x13, 0x3f, 0x8f, 0xd2, 0x51, 0x36, 0x75, 0xdc, 0x4d, 0xdb, 0x76, 0xfd, 0x82, 0x3c, 0x91, 0xcf, 0xdd, - 0x72, 0xe3, 0x04, 0xfc, 0x80, 0xd0, 0x1e, 0xd8, 0x9b, 0xc7, 0xde, 0x01, 0x0b, 0x4f, 0x76, 0x37, 0x16, 0x23, 0x56, - 0xf6, 0x4f, 0xbc, 0x4b, 0x1d, 0x73, 0xf7, 0xde, 0xa3, 0x94, 0x81, 0x5e, 0x61, 0xff, 0x52, 0x82, 0x01, 0xec, 0x46, - 0xf1, 0x77, 0x90, 0x72, 0x1f, 0xe9, 0x40, 0x44, 0xc6, 0x69, 0xaf, 0xaf, 0xed, 0x8c, 0x22, 0x06, 0xf6, 0x3d, 0xed, - 0xac, 0xde, 0xbf, 0x5f, 0xa9, 0xf9, 0xaa, 0xd4, 0x9b, 0xb3, 0xb0, 0xe6, 0xa9, 0x7b, 0x2f, 0xe9, 0x68, 0xa5, 0xbe, - 0x91, 0xe7, 0x8c, 0x94, 0xe6, 0xb2, 0x9d, 0xe0, 0x18, 0x5b, 0x7c, 0xf5, 0xb6, 0x3e, 0x14, 0x51, 0x0a, 0x3f, 0x06, - 0xeb, 0x25, 0x02, 0xf5, 0x0d, 0x0e, 0x8e, 0x77, 0x10, 0x6e, 0xed, 0x3a, 0x83, 0xc0, 0xb9, 0xd7, 0x6a, 0x5d, 0xff, - 0xb8, 0x75, 0xf8, 0xe7, 0xa8, 0xf5, 0xcb, 0x5e, 0xeb, 0x87, 0x23, 0xf7, 0xda, 0xf9, 0x71, 0x6b, 0x70, 0x28, 0xdf, - 0x0e, 0xff, 0xdc, 0xff, 0xb1, 0x38, 0xfa, 0x83, 0x28, 0xdc, 0x70, 0xdd, 0xad, 0x33, 0x6f, 0xc6, 0xc2, 0xad, 0x56, - 0xab, 0x0f, 0x4f, 0x67, 0xf0, 0x84, 0x7f, 0x2f, 0xe0, 0xcf, 0xf5, 0xa1, 0xf5, 0x9f, 0x7e, 0x4c, 0xff, 0xf3, 0x8f, - 0xf9, 0x11, 0x8e, 0x79, 0xf8, 0xe7, 0x1f, 0x0b, 0xfb, 0x41, 0x3f, 0xdc, 0x3a, 0xda, 0x74, 0x1d, 0x5d, 0xf3, 0x87, - 0xb0, 0x7a, 0x84, 0x56, 0x87, 0x7f, 0x96, 0x6f, 0xf6, 0x83, 0x93, 0xdd, 0x7e, 0x78, 0x74, 0xed, 0xd8, 0xd7, 0x0f, - 0xdc, 0x6b, 0xd7, 0xbd, 0xde, 0xc0, 0x79, 0xce, 0x61, 0xf4, 0x07, 0xf0, 0x77, 0x0c, 0x7f, 0x6d, 0xf8, 0x3b, 0x85, - 0xbf, 0x7f, 0x86, 0x6e, 0x22, 0xfe, 0x76, 0x4d, 0xb1, 0x90, 0x6b, 0x3c, 0xb0, 0x88, 0x60, 0x15, 0xdc, 0x8d, 0xad, - 0xd8, 0xdb, 0x20, 0xa2, 0xc1, 0x3e, 0xf4, 0x7d, 0x1f, 0xc3, 0xa4, 0xce, 0xe2, 0x78, 0x03, 0x16, 0x1d, 0x39, 0x67, - 0x23, 0xe0, 0x9e, 0x88, 0x1c, 0x14, 0x01, 0x13, 0x67, 0xab, 0x05, 0x1e, 0xae, 0x7a, 0xc3, 0x70, 0x83, 0x39, 0x60, - 0x14, 0xbc, 0x65, 0xf8, 0xd0, 0x75, 0xbd, 0x17, 0xf2, 0xcc, 0x10, 0xf7, 0xb9, 0x60, 0xad, 0x34, 0x13, 0x26, 0x8d, - 0xed, 0x7a, 0xb3, 0x15, 0x95, 0xb0, 0xad, 0xd3, 0x33, 0xa8, 0x3b, 0x15, 0x27, 0x8c, 0xdf, 0xb1, 0xe8, 0x13, 0x6e, - 0xc9, 0x37, 0xc6, 0x21, 0xf0, 0x92, 0x25, 0xdf, 0x34, 0x1a, 0x0d, 0x1b, 0x51, 0xb8, 0x63, 0x4f, 0x19, 0xcc, 0xb0, - 0x64, 0x22, 0x32, 0x52, 0x9a, 0xc2, 0xb2, 0x85, 0xc9, 0xdf, 0x47, 0x39, 0xdf, 0xa8, 0x0c, 0xdb, 0xb0, 0x66, 0xc9, - 0x36, 0x2d, 0xfd, 0x3b, 0x4c, 0x81, 0xa6, 0x25, 0x9d, 0x7f, 0x98, 0xe3, 0x87, 0x29, 0xa1, 0xf5, 0xd6, 0x61, 0xe0, - 0xa1, 0x17, 0x20, 0x77, 0x44, 0x3f, 0xe7, 0x3d, 0xaa, 0x31, 0xf8, 0x9f, 0x0c, 0x33, 0x78, 0x62, 0x3e, 0x0c, 0xd1, - 0x2c, 0x4a, 0x1d, 0xdc, 0x4a, 0x51, 0xdc, 0xbf, 0xc2, 0x9d, 0x91, 0x96, 0xde, 0x7e, 0xa8, 0x76, 0xcc, 0x41, 0xce, - 0xd8, 0x77, 0x51, 0xf2, 0x89, 0xe5, 0xce, 0xa5, 0xd7, 0xe9, 0x7e, 0x4e, 0x9d, 0x3d, 0xb4, 0xcd, 0x3e, 0x54, 0xc7, - 0x68, 0xda, 0x2c, 0x90, 0x47, 0x84, 0xad, 0x8e, 0x97, 0x63, 0x54, 0x0b, 0x49, 0x50, 0x78, 0x59, 0xd8, 0x25, 0x0e, - 0xb7, 0x77, 0x8b, 0xf3, 0xb3, 0xbe, 0x1d, 0xd8, 0x36, 0x58, 0xfc, 0x07, 0x14, 0xb6, 0x12, 0x86, 0x45, 0xbb, 0xc7, - 0x76, 0xe3, 0x1e, 0xdb, 0xdc, 0xac, 0x02, 0x4e, 0x78, 0x90, 0x4e, 0xdd, 0x13, 0x2f, 0xf2, 0x26, 0x21, 0x0c, 0x38, - 0x84, 0x66, 0xd8, 0xa5, 0x37, 0xdc, 0x8d, 0xe5, 0x34, 0x18, 0x0b, 0xf1, 0x93, 0xa8, 0xe0, 0xaf, 0x30, 0x1e, 0x11, - 0x0e, 0xd1, 0xd8, 0xf7, 0xd9, 0x25, 0x1b, 0x2a, 0x3b, 0x03, 0x08, 0x15, 0xb9, 0x3d, 0x77, 0x18, 0x1a, 0xcd, 0x60, - 0xee, 0x30, 0x3c, 0x18, 0xd8, 0xb0, 0x97, 0x60, 0x57, 0x86, 0xd1, 0x61, 0xe7, 0x68, 0x90, 0x86, 0x33, 0x16, 0x68, - 0xda, 0xca, 0xa2, 0xb3, 0x5a, 0x51, 0xf7, 0x68, 0xe0, 0x4c, 0x99, 0xcf, 0xc1, 0x16, 0x77, 0xf0, 0x0d, 0x23, 0x14, - 0x45, 0xf8, 0x81, 0x9d, 0xbd, 0xb8, 0x9c, 0x39, 0xf6, 0xee, 0x96, 0xbd, 0x89, 0xa5, 0x9e, 0x0d, 0xec, 0x05, 0x73, - 0x87, 0x17, 0xae, 0xd9, 0x79, 0xfb, 0x08, 0x41, 0xc5, 0x42, 0x9c, 0xfc, 0x62, 0x60, 0xf7, 0xc5, 0xd4, 0x6d, 0x18, - 0x34, 0x95, 0xcb, 0x8f, 0x2b, 0x7a, 0x40, 0xa8, 0xaa, 0xae, 0x0a, 0x3a, 0x28, 0xeb, 0x06, 0xce, 0xc4, 0x44, 0xa2, - 0x85, 0x93, 0x49, 0x2a, 0x80, 0xc3, 0x83, 0xcd, 0x60, 0x52, 0xa3, 0xdb, 0xf6, 0xd1, 0xe0, 0x22, 0x78, 0x60, 0x3f, - 0x50, 0x2f, 0x63, 0x40, 0x86, 0x89, 0xe9, 0xc7, 0xa0, 0x45, 0xf0, 0xef, 0x39, 0x03, 0x24, 0x2f, 0xa8, 0x68, 0x26, - 0x8b, 0xce, 0xb0, 0xe8, 0x20, 0x40, 0x50, 0xbd, 0x42, 0x5b, 0x7f, 0x62, 0x4d, 0x46, 0x21, 0xc1, 0x0e, 0xb6, 0xd0, - 0x21, 0xdb, 0xec, 0x1c, 0xe1, 0x79, 0x43, 0xce, 0x8b, 0xef, 0x62, 0x0e, 0x2a, 0x61, 0xab, 0x6f, 0xbb, 0x03, 0xdb, - 0xc2, 0xa5, 0xed, 0x65, 0x9b, 0xa1, 0xa0, 0x70, 0xbc, 0x79, 0xc0, 0x82, 0x49, 0x3f, 0x6c, 0x0f, 0x9c, 0x5c, 0x86, - 0x1b, 0xf1, 0xdc, 0x52, 0x48, 0xf0, 0xb6, 0x37, 0x01, 0x81, 0x8e, 0x9c, 0xbb, 0x61, 0x6f, 0xaa, 0x42, 0x28, 0x3a, - 0xde, 0x1c, 0xb9, 0x41, 0x0c, 0x7f, 0x9c, 0x16, 0x32, 0xcd, 0x44, 0xf7, 0x55, 0x9a, 0x19, 0x90, 0x18, 0x29, 0x8b, - 0x3c, 0x09, 0xb3, 0x4d, 0x07, 0x23, 0xb4, 0x20, 0x69, 0x77, 0x07, 0x00, 0xc3, 0xa6, 0xa3, 0x38, 0x6d, 0x4b, 0xb1, - 0x9a, 0xb2, 0xcf, 0x0f, 0xf5, 0x72, 0x0c, 0xd9, 0x60, 0xc8, 0xfc, 0x4a, 0xfb, 0x00, 0x58, 0x41, 0xe2, 0xe5, 0x47, - 0xea, 0xcc, 0xeb, 0x65, 0xed, 0x7c, 0x6b, 0xa1, 0x44, 0x11, 0xf7, 0x0c, 0x09, 0xc5, 0x4a, 0xed, 0x86, 0x09, 0x73, - 0x7b, 0x86, 0xc4, 0xd0, 0x2c, 0x1f, 0xb6, 0x81, 0xe9, 0x55, 0x80, 0x3d, 0x35, 0xb7, 0x45, 0x12, 0x56, 0xcd, 0xbd, - 0x43, 0x60, 0xed, 0x23, 0xe0, 0x21, 0xda, 0x46, 0x3d, 0x15, 0xcd, 0x67, 0x49, 0xf8, 0xb2, 0x71, 0x5c, 0x1c, 0xe1, - 0x89, 0xd0, 0xbe, 0x3f, 0x9c, 0xe7, 0x20, 0x0f, 0xf8, 0x5b, 0xb0, 0x0c, 0x42, 0xd9, 0x14, 0x1d, 0x3d, 0x3c, 0x02, - 0xf6, 0x08, 0xf1, 0x46, 0xd8, 0xdc, 0xa8, 0x46, 0x8b, 0x92, 0x8c, 0x17, 0x3a, 0x18, 0xee, 0x31, 0xe9, 0xda, 0xa3, - 0x60, 0x90, 0x27, 0xc6, 0x0e, 0x9e, 0xf9, 0xfb, 0x43, 0xac, 0xc6, 0x09, 0x0a, 0xb7, 0xa4, 0xdd, 0x56, 0x89, 0xbf, - 0x7d, 0x3f, 0x05, 0x09, 0x8e, 0x75, 0xe0, 0x67, 0xdd, 0xbf, 0x9f, 0x48, 0xa4, 0x76, 0xd3, 0x1e, 0x9d, 0x44, 0x60, - 0x3c, 0x38, 0xf7, 0x53, 0xa8, 0x46, 0x12, 0x51, 0x51, 0x8e, 0x16, 0xa8, 0x79, 0xaa, 0x56, 0xc1, 0x77, 0x68, 0x46, - 0xe0, 0x19, 0x86, 0xad, 0xc9, 0x4f, 0xd5, 0x8d, 0x45, 0x2c, 0xdf, 0x75, 0xe9, 0x68, 0x0b, 0x0f, 0x20, 0x05, 0xa3, - 0x09, 0x86, 0x71, 0x29, 0x28, 0x59, 0xf1, 0xdf, 0xb1, 0x11, 0x2b, 0x9f, 0x1c, 0x66, 0x9b, 0x9b, 0x47, 0xe2, 0xdc, - 0x82, 0x18, 0x87, 0x19, 0xd1, 0xd5, 0xb8, 0x02, 0xa0, 0x3e, 0x9d, 0x13, 0xd7, 0x03, 0xd3, 0x8a, 0x35, 0x5d, 0x8a, - 0x7d, 0x72, 0x98, 0x01, 0x28, 0xb8, 0xe5, 0x1c, 0xfa, 0x83, 0x3f, 0x1e, 0x81, 0x7b, 0xec, 0xff, 0xc1, 0xdd, 0x52, - 0x82, 0xa6, 0x27, 0xcf, 0x14, 0x17, 0x74, 0xc6, 0xda, 0xf1, 0x28, 0x36, 0x1a, 0x14, 0x5e, 0x0a, 0x18, 0x80, 0x36, - 0x07, 0x99, 0x50, 0x71, 0x10, 0x72, 0x54, 0x60, 0xfb, 0xb8, 0xf9, 0x19, 0xee, 0xec, 0xe7, 0x60, 0xe1, 0x0d, 0xf4, - 0xdb, 0x6b, 0x78, 0xfb, 0xa3, 0x7e, 0xfb, 0x89, 0x05, 0xbf, 0x94, 0x32, 0x74, 0x5f, 0x9b, 0xe2, 0x91, 0x9a, 0xa2, - 0x14, 0x4b, 0x64, 0xd0, 0x90, 0xbb, 0xf9, 0x52, 0xcc, 0x86, 0xb9, 0x25, 0x10, 0x43, 0x89, 0xae, 0xdc, 0xe7, 0xd1, - 0x19, 0x12, 0xd7, 0x35, 0x49, 0x61, 0xe4, 0x12, 0x98, 0x08, 0x57, 0x7c, 0x8b, 0xf4, 0x64, 0xfd, 0x36, 0xd8, 0xe0, - 0xb5, 0xbc, 0x03, 0xb4, 0xef, 0xd8, 0x74, 0xc6, 0xaf, 0xf6, 0x49, 0xd1, 0x07, 0x32, 0x6d, 0x40, 0x9c, 0x9d, 0xb7, - 0x7b, 0xf1, 0x2e, 0xeb, 0xc5, 0x20, 0xd5, 0x73, 0xc5, 0x62, 0xb8, 0x57, 0xbd, 0xf7, 0x18, 0xa5, 0x34, 0x99, 0xc9, - 0xab, 0xa1, 0xd7, 0x95, 0xe8, 0x6d, 0x6e, 0x02, 0x82, 0x3d, 0xa3, 0x2b, 0x13, 0x5d, 0xcb, 0x52, 0xd0, 0x04, 0x20, - 0x7a, 0x52, 0x67, 0x39, 0xe2, 0x38, 0xcc, 0x66, 0x83, 0xe2, 0x11, 0x73, 0x57, 0x8e, 0x8a, 0x63, 0x62, 0x77, 0x99, - 0xb0, 0x03, 0x98, 0x11, 0x97, 0xb7, 0x3a, 0x22, 0x3a, 0x2c, 0xfa, 0xeb, 0xf8, 0xf6, 0xb1, 0xc7, 0x37, 0x3b, 0x2e, - 0x68, 0x90, 0xda, 0x58, 0x8f, 0xab, 0xb1, 0xa0, 0x3e, 0x3c, 0xd6, 0x54, 0x2a, 0x8b, 0xcd, 0xcd, 0xb2, 0x7e, 0x54, - 0xab, 0x76, 0x70, 0xed, 0x34, 0xe5, 0xb2, 0x99, 0x0d, 0xc2, 0x81, 0x88, 0x09, 0x14, 0x68, 0x69, 0x65, 0xc5, 0x00, - 0x43, 0xca, 0x72, 0x94, 0x4f, 0x21, 0xf7, 0xe2, 0xb2, 0xd4, 0xa9, 0x2f, 0xcf, 0x64, 0xd0, 0x11, 0x4f, 0x3d, 0xc9, - 0x58, 0x01, 0x05, 0xeb, 0xa5, 0x5e, 0x42, 0x4b, 0x04, 0x98, 0xbf, 0x50, 0x39, 0x34, 0xc2, 0x02, 0x89, 0x42, 0xc3, - 0x2c, 0x51, 0xc6, 0x67, 0x11, 0xc6, 0xa0, 0xed, 0x9f, 0xd5, 0x62, 0x5f, 0x85, 0x32, 0x3a, 0x8a, 0xc3, 0xfc, 0x28, - 0xa0, 0xfa, 0xb9, 0x94, 0x60, 0x93, 0xf0, 0x23, 0xb0, 0x51, 0xe5, 0x78, 0x92, 0x20, 0x7c, 0x1e, 0xe7, 0x8c, 0x3c, - 0x85, 0x0d, 0x09, 0xb3, 0x34, 0x6d, 0x23, 0xd5, 0x2e, 0x32, 0x83, 0x50, 0x2e, 0xcc, 0x3f, 0x31, 0xce, 0x2e, 0xb2, - 0x70, 0xa9, 0x35, 0x98, 0x1f, 0xef, 0x4c, 0x80, 0xb2, 0xeb, 0xeb, 0x4c, 0xf8, 0xb8, 0x11, 0xd9, 0x1b, 0xba, 0x62, - 0x32, 0x50, 0x48, 0x05, 0x4e, 0x44, 0x16, 0x0f, 0x9d, 0xa1, 0xd0, 0x08, 0x07, 0x74, 0x8a, 0x9c, 0xbb, 0xc6, 0xa6, - 0xcf, 0x07, 0xda, 0x37, 0x4a, 0x43, 0x27, 0x01, 0x21, 0x20, 0x70, 0x37, 0xac, 0xa9, 0x74, 0x90, 0x06, 0x09, 0x95, - 0xa2, 0x9f, 0x03, 0xf8, 0x87, 0x91, 0xa4, 0x00, 0xd8, 0x0f, 0xd5, 0x48, 0x11, 0x65, 0x59, 0xe0, 0x02, 0xd0, 0x5c, - 0xfb, 0xb8, 0x12, 0xbe, 0x30, 0x50, 0x61, 0x7a, 0x9a, 0x95, 0x95, 0x42, 0x89, 0x3c, 0x5d, 0x91, 0xb2, 0x46, 0x32, - 0xf9, 0x1c, 0x1d, 0x3e, 0xe5, 0x5d, 0xbf, 0x95, 0x78, 0xe8, 0x82, 0xe7, 0xb0, 0xac, 0xea, 0xf9, 0x4d, 0xc8, 0xc8, - 0xb9, 0x06, 0x5d, 0x21, 0x85, 0xfe, 0x92, 0x93, 0xbc, 0xf7, 0xc6, 0xaf, 0x6a, 0xa9, 0x31, 0x94, 0x7d, 0x5c, 0xd5, - 0x0c, 0xcb, 0xcb, 0x59, 0x15, 0xa6, 0x20, 0xe0, 0x16, 0x2c, 0x09, 0x16, 0x52, 0x43, 0x80, 0x85, 0xed, 0x91, 0x56, - 0x0a, 0xf2, 0x52, 0x87, 0x77, 0x9e, 0x83, 0x15, 0x60, 0x1c, 0x6a, 0xa9, 0x64, 0x1a, 0x49, 0x7c, 0x99, 0xd4, 0x04, - 0x4c, 0xb9, 0x3f, 0x04, 0x3f, 0xb5, 0x79, 0xd2, 0x75, 0xe9, 0xfa, 0xf1, 0x14, 0x53, 0x7b, 0x08, 0xf4, 0xd8, 0xbb, - 0x07, 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, 0x59, 0xaf, - 0x4b, 0x45, 0xb8, 0x3f, 0x07, 0x4c, 0x5a, 0xc1, 0x99, 0x97, 0xd1, 0xc0, 0xfb, 0xf1, 0x69, 0x82, 0x89, 0x2f, 0x88, - 0x15, 0xd8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x38, 0x15, 0x17, 0x29, 0x83, 0x65, 0x45, 0xa9, 0x0d, 0x7f, 0xa4, 0xc8, - 0xd6, 0x5d, 0x1e, 0xe9, 0x2e, 0xc4, 0x02, 0xd8, 0xe9, 0x17, 0x8c, 0x7c, 0xcb, 0x7a, 0x19, 0x30, 0x38, 0xd7, 0x1a, - 0x07, 0x81, 0xdf, 0xdc, 0x4c, 0x8e, 0xca, 0x94, 0xd8, 0xae, 0xc9, 0xea, 0x02, 0x72, 0x4c, 0x02, 0x6c, 0xe0, 0x0e, - 0xc2, 0x52, 0xd9, 0xe3, 0x45, 0x39, 0xc5, 0xe5, 0x52, 0x16, 0x72, 0x33, 0x1d, 0x8b, 0xe6, 0x73, 0x2b, 0xcd, 0xa6, - 0xe3, 0xad, 0xf8, 0xa2, 0xe0, 0x1f, 0x38, 0xb1, 0xb4, 0xea, 0x29, 0xb5, 0xc2, 0xa3, 0xcc, 0x2d, 0x59, 0xa7, 0xa4, - 0x56, 0xd7, 0x0d, 0x54, 0x23, 0x3c, 0x4d, 0xc3, 0x46, 0x20, 0xc4, 0x04, 0x17, 0xbf, 0x6d, 0x32, 0x31, 0xed, 0x2d, - 0x21, 0x75, 0x84, 0xdd, 0x43, 0x39, 0xc1, 0x5d, 0xcd, 0xb3, 0x2f, 0xc3, 0xd9, 0x7a, 0xe6, 0xde, 0x33, 0x98, 0xfb, - 0x69, 0xc8, 0x0c, 0x46, 0x8f, 0x65, 0xc2, 0x8f, 0x8c, 0x7d, 0xe4, 0xaa, 0xea, 0xd9, 0x59, 0x58, 0x89, 0x2c, 0xf1, - 0x64, 0x1c, 0x75, 0x18, 0xa7, 0xa2, 0x35, 0x41, 0x76, 0x7d, 0x5d, 0x98, 0x7b, 0x81, 0x82, 0xa6, 0x1e, 0xab, 0xc7, - 0x69, 0x2b, 0x76, 0x36, 0x22, 0x91, 0x7b, 0x6f, 0x6a, 0x91, 0xc8, 0x8a, 0xcf, 0x71, 0xa4, 0x35, 0x07, 0xb9, 0xcf, - 0xce, 0x96, 0x37, 0xa9, 0xd0, 0x2d, 0x1a, 0x6d, 0x63, 0x8f, 0xea, 0x03, 0x49, 0x3d, 0xa3, 0x02, 0xab, 0x1a, 0xfb, - 0xfe, 0xfd, 0x8e, 0x48, 0xb7, 0x54, 0x8a, 0x0d, 0x43, 0x5a, 0x21, 0x33, 0x46, 0xc1, 0xa0, 0xa4, 0xc8, 0x40, 0x8d, - 0xf2, 0x35, 0x82, 0x61, 0x8f, 0x1a, 0x80, 0xe2, 0x5c, 0x5d, 0xfd, 0xb4, 0x94, 0x6c, 0x21, 0x20, 0x01, 0xd9, 0x84, - 0x62, 0x8d, 0x98, 0x19, 0xf9, 0xe4, 0x23, 0x70, 0xde, 0x80, 0xa3, 0x63, 0x00, 0x7e, 0x81, 0xd8, 0xf4, 0x60, 0x62, - 0xdb, 0x44, 0x14, 0x7d, 0x36, 0xf0, 0x12, 0x80, 0x9d, 0x55, 0xa1, 0xd1, 0x0f, 0x55, 0x0a, 0x18, 0xb2, 0x81, 0x1b, - 0xf0, 0x2a, 0x2c, 0xb7, 0xf7, 0x12, 0xda, 0xc1, 0xeb, 0x0b, 0xd9, 0x7c, 0x03, 0xf3, 0x04, 0xab, 0xd8, 0x9d, 0x5f, - 0x59, 0xd6, 0xe2, 0xdc, 0xe9, 0xa0, 0x51, 0xaf, 0x28, 0x21, 0x6a, 0xf7, 0xb1, 0xf6, 0x25, 0x23, 0x18, 0xf1, 0xfd, - 0x0d, 0x65, 0x1d, 0xaa, 0x71, 0xcb, 0x3d, 0x8d, 0x16, 0x61, 0xba, 0x4c, 0x1a, 0x83, 0x92, 0x75, 0x3f, 0x19, 0x71, - 0x2f, 0xf7, 0x45, 0x2c, 0xb8, 0xc2, 0xd1, 0x08, 0x9b, 0x37, 0x90, 0xa4, 0xa7, 0x3d, 0x3a, 0x60, 0xdf, 0x68, 0xf6, - 0x02, 0xca, 0x7c, 0xac, 0x48, 0x25, 0x21, 0xa5, 0xd9, 0x0d, 0x91, 0x24, 0xac, 0x15, 0x79, 0xea, 0xbc, 0xef, 0x68, - 0x9f, 0x5b, 0x49, 0x04, 0x23, 0x38, 0x89, 0xd3, 0x95, 0x07, 0x4d, 0x01, 0xae, 0xa2, 0x23, 0xa6, 0x6f, 0x82, 0xf2, - 0x1b, 0xe4, 0xf6, 0x52, 0x72, 0x6d, 0xae, 0x61, 0x78, 0x86, 0x04, 0xab, 0x22, 0x11, 0x78, 0x44, 0x0d, 0x38, 0xe6, - 0xab, 0x3c, 0x0f, 0x30, 0xe1, 0x6b, 0x7b, 0x13, 0x00, 0xca, 0xc9, 0x55, 0x71, 0x96, 0x02, 0xdd, 0x80, 0xe5, 0xea, - 0x38, 0x35, 0x2a, 0x12, 0x17, 0x37, 0xa6, 0xab, 0x5b, 0xfa, 0x53, 0xb4, 0x9c, 0xc9, 0x10, 0xd3, 0x41, 0x10, 0x90, - 0xa9, 0x4f, 0x99, 0x23, 0x64, 0xae, 0xb0, 0x3e, 0x67, 0x4e, 0x6d, 0xea, 0x1e, 0xa7, 0x6e, 0x9e, 0xa4, 0x16, 0xab, - 0xd3, 0xa6, 0x94, 0x88, 0x49, 0x89, 0x79, 0x2a, 0x53, 0xb1, 0x95, 0xb8, 0x73, 0xeb, 0x1b, 0x2d, 0xa4, 0x8d, 0x76, - 0x2a, 0x73, 0xb0, 0xb5, 0xbc, 0x17, 0xa2, 0xfd, 0x25, 0x11, 0x9e, 0x95, 0xc8, 0x58, 0x8b, 0x39, 0x73, 0x4c, 0x04, - 0xab, 0x17, 0x53, 0x91, 0x7f, 0x70, 0x74, 0x9a, 0xbd, 0x41, 0x0f, 0x52, 0x6f, 0x20, 0x31, 0x6b, 0xe2, 0xbb, 0x90, - 0x86, 0x3a, 0x42, 0xa0, 0x32, 0xaa, 0x65, 0x3a, 0x4e, 0x2c, 0x15, 0x97, 0xe4, 0xab, 0xf7, 0xfa, 0x38, 0xdf, 0x78, - 0x6e, 0xac, 0x46, 0x10, 0x83, 0xb7, 0x90, 0x1f, 0x79, 0x52, 0x84, 0x03, 0xe1, 0xf2, 0xcd, 0xcd, 0x5e, 0xbe, 0xcb, - 0xaa, 0x10, 0x49, 0x05, 0x63, 0x8c, 0x19, 0xc5, 0xb8, 0x27, 0x6a, 0x6a, 0x31, 0x07, 0x54, 0x65, 0xeb, 0x30, 0xc7, - 0x03, 0x00, 0x68, 0x69, 0x4a, 0x2f, 0xb3, 0xad, 0x3a, 0xcf, 0x25, 0x7c, 0x8c, 0x3c, 0x14, 0xd9, 0xf8, 0xfd, 0x9a, - 0x0c, 0x14, 0x84, 0xfb, 0x5e, 0xc7, 0xc3, 0xc4, 0x38, 0x58, 0x45, 0x21, 0x0b, 0xf4, 0x06, 0xed, 0x55, 0x89, 0x50, - 0xdc, 0x9c, 0xac, 0xc7, 0x0d, 0x27, 0x15, 0x6c, 0xa1, 0x12, 0x96, 0x4a, 0x0b, 0xfc, 0x6a, 0x23, 0x34, 0x4f, 0x19, - 0xf7, 0xde, 0x54, 0x38, 0x83, 0xfe, 0xe0, 0xde, 0x32, 0xa3, 0xbe, 0x5f, 0x3a, 0x91, 0xa9, 0xc0, 0xc4, 0xcd, 0x2c, - 0xb5, 0xdf, 0x2f, 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, 0xf5, 0xbb, 0x28, 0x74, 0x51, 0x9f, - 0x83, 0x21, 0xa4, 0x1f, 0xc1, 0x35, 0x34, 0x78, 0x50, 0x24, 0x8b, 0xc5, 0xda, 0x05, 0x71, 0x7d, 0xcc, 0xa9, 0x76, - 0x28, 0x63, 0x8c, 0x78, 0x5a, 0x72, 0x90, 0x64, 0x70, 0x30, 0x7e, 0x03, 0x03, 0x62, 0x52, 0x12, 0xd2, 0x21, 0x74, - 0x56, 0x66, 0x22, 0x2a, 0x77, 0xf1, 0x76, 0xe3, 0xb2, 0xa6, 0x50, 0x84, 0x9d, 0x60, 0xa6, 0x52, 0x2a, 0x08, 0xa4, - 0xc9, 0x77, 0xaf, 0x53, 0x0b, 0x86, 0x82, 0x68, 0x30, 0x14, 0x90, 0xd7, 0x76, 0x3d, 0x68, 0xf2, 0x51, 0x1c, 0x3c, - 0xaf, 0x50, 0x23, 0x5e, 0x66, 0xf0, 0x35, 0x6c, 0xfe, 0x9a, 0x28, 0xc9, 0x43, 0x2e, 0x62, 0xaf, 0xe0, 0x13, 0x21, - 0x9b, 0xf2, 0xb0, 0x00, 0xfa, 0xa1, 0x5d, 0xd9, 0x4b, 0x77, 0x8b, 0xca, 0xa5, 0x45, 0x63, 0x2b, 0x51, 0xb3, 0xe6, - 0x87, 0xf1, 0x66, 0x7a, 0x04, 0x53, 0x53, 0x02, 0x01, 0x69, 0x2a, 0x27, 0xa9, 0xe6, 0x3d, 0x4c, 0x8f, 0x00, 0x24, - 0xd8, 0xfd, 0x04, 0x16, 0xfa, 0x4d, 0x89, 0x09, 0x16, 0x55, 0x63, 0xb7, 0x19, 0x68, 0xcd, 0x19, 0x69, 0xbe, 0x19, - 0x42, 0xb8, 0xa9, 0xac, 0x67, 0xcc, 0x0e, 0xb0, 0x6d, 0x77, 0xb3, 0x38, 0x4c, 0x37, 0x3b, 0x47, 0x86, 0xe0, 0xc2, - 0xe3, 0xff, 0xa4, 0xc4, 0x34, 0x90, 0x5c, 0xea, 0xc6, 0x4f, 0xa8, 0xc3, 0x3e, 0x91, 0x3a, 0x11, 0x03, 0x9a, 0xab, - 0xd1, 0x74, 0xee, 0x35, 0x47, 0xc9, 0x65, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0x8c, 0x89, 0x62, 0x9e, 0x13, - 0x00, 0xa3, 0xd8, 0xfc, 0x39, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4c, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0xcf, 0xe8, 0x48, - 0x45, 0x65, 0x73, 0x12, 0xf3, 0x6f, 0x4b, 0x30, 0x8d, 0x89, 0x0f, 0xf5, 0x5c, 0x47, 0xa1, 0x00, 0x5f, 0xd9, 0x50, - 0x6a, 0xb6, 0xd7, 0xbf, 0x75, 0xb6, 0x87, 0x72, 0x36, 0xc1, 0x02, 0x0d, 0xba, 0xac, 0xc1, 0x17, 0xb0, 0x0c, 0xee, - 0x48, 0x3f, 0x05, 0xdf, 0x4f, 0xeb, 0xe0, 0x33, 0xf6, 0xbf, 0x00, 0xb4, 0x2a, 0x30, 0xa0, 0xdc, 0x69, 0x1a, 0x56, - 0x42, 0x5c, 0xa2, 0xc2, 0xac, 0xe2, 0xfc, 0x71, 0x9d, 0xd7, 0x4d, 0xcb, 0x12, 0x83, 0xf2, 0x33, 0xd7, 0x70, 0xe3, - 0x7b, 0x8d, 0xfc, 0xf1, 0xbd, 0x97, 0xa0, 0xdb, 0x89, 0xb4, 0xf7, 0xef, 0xe7, 0xf7, 0xc8, 0x42, 0x03, 0x3f, 0x2c, - 0x9a, 0x41, 0x5b, 0xbc, 0x08, 0x90, 0xab, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x03, 0x0c, - 0xc1, 0x63, 0xa7, 0x32, 0xf9, 0x0c, 0x1b, 0x4d, 0xb1, 0x6b, 0x2e, 0x0c, 0x3e, 0x50, 0x95, 0x85, 0xe4, 0xc5, 0x3a, - 0xd9, 0x5e, 0x9c, 0xc3, 0xf3, 0xeb, 0xb8, 0x00, 0xea, 0x20, 0xfa, 0x9a, 0xca, 0x62, 0x03, 0xb9, 0xb8, 0x29, 0x6b, - 0xbd, 0xa2, 0xd1, 0xe8, 0xc6, 0x2e, 0xbc, 0xae, 0xc0, 0x27, 0x51, 0x3a, 0x4a, 0xc4, 0x24, 0x66, 0x52, 0xe5, 0x8a, - 0x5c, 0x1b, 0xdd, 0x4b, 0x5b, 0x34, 0x2f, 0x85, 0x04, 0xaf, 0x08, 0xdc, 0x10, 0xfa, 0x4a, 0x5f, 0xae, 0x36, 0x50, - 0xf0, 0xa8, 0xbd, 0xb9, 0x08, 0x26, 0x26, 0x1e, 0x37, 0xa4, 0xa6, 0x5f, 0x87, 0x53, 0x2b, 0x8b, 0x25, 0x87, 0x5f, - 0xe7, 0x8c, 0x35, 0x14, 0x00, 0xf1, 0xc9, 0xa3, 0xf5, 0x6e, 0xd2, 0x1b, 0xa5, 0x1d, 0x94, 0x46, 0x88, 0xef, 0x2a, - 0x7c, 0xdd, 0x85, 0xe2, 0x2b, 0x57, 0xdd, 0xfb, 0x3a, 0x66, 0xc6, 0x05, 0xa3, 0x97, 0x7c, 0x9a, 0x34, 0xae, 0xdd, - 0xd0, 0x5d, 0x9d, 0xef, 0xbd, 0x2f, 0x65, 0xde, 0xc2, 0x31, 0xb0, 0xc9, 0x31, 0x73, 0x5e, 0x7a, 0x6f, 0x8d, 0x13, - 0xe5, 0x1f, 0xcc, 0x23, 0x5e, 0x39, 0xcc, 0xaa, 0x93, 0xe4, 0x1f, 0x06, 0x3f, 0x04, 0xeb, 0x5b, 0x1a, 0x27, 0xc8, - 0x5d, 0x75, 0x82, 0x4c, 0x94, 0xdb, 0xd0, 0x1b, 0x6e, 0xef, 0xae, 0x02, 0x41, 0x9c, 0x8a, 0xe9, 0xa3, 0x72, 0x5c, - 0x3f, 0x5a, 0xa0, 0x52, 0x11, 0xf1, 0xb9, 0xca, 0x5d, 0x59, 0x9b, 0x1a, 0xea, 0x31, 0x9d, 0xcc, 0x42, 0xd3, 0xac, - 0xc8, 0xa5, 0x6c, 0x7a, 0x8c, 0x5c, 0xb3, 0x53, 0x6d, 0x7e, 0x77, 0xed, 0x21, 0x1d, 0xc7, 0xfb, 0x9e, 0xb5, 0x5a, - 0x70, 0xbf, 0xab, 0x28, 0xbc, 0xeb, 0xc5, 0x46, 0x2a, 0x43, 0xcd, 0x7a, 0x14, 0x7d, 0x1c, 0xb7, 0x99, 0xcb, 0xa3, - 0xec, 0xcf, 0x1a, 0x00, 0xa6, 0x23, 0x2c, 0xba, 0x9b, 0x9e, 0xb1, 0x27, 0xd0, 0xd3, 0x13, 0x19, 0x24, 0x7a, 0xa3, - 0xf3, 0x55, 0xab, 0xc4, 0xd2, 0x15, 0x04, 0x76, 0x6f, 0xc8, 0x58, 0x95, 0xb4, 0x5b, 0xae, 0x5f, 0xce, 0xf3, 0x79, - 0xce, 0x97, 0xf2, 0x7c, 0x6a, 0x16, 0xdd, 0xbd, 0xb6, 0x7b, 0x73, 0x6a, 0xa8, 0x98, 0x6b, 0x75, 0x93, 0xdf, 0x30, - 0x5d, 0x07, 0x43, 0x2d, 0x82, 0xcc, 0x6a, 0x57, 0xbd, 0x28, 0xcb, 0x8d, 0x7a, 0x26, 0xc7, 0x86, 0xf0, 0x4d, 0xa5, - 0x3b, 0x44, 0x37, 0x4c, 0xd5, 0x4c, 0xdf, 0x37, 0xb6, 0x85, 0x6c, 0xf3, 0xf2, 0x6a, 0x94, 0x03, 0xa5, 0xe5, 0xfe, - 0x32, 0x61, 0xf8, 0xfe, 0xfa, 0xfa, 0x7b, 0x21, 0xa7, 0xaa, 0x8e, 0xde, 0xe2, 0xb5, 0xee, 0x19, 0x6c, 0x94, 0xca, - 0x89, 0xb8, 0x60, 0xab, 0x07, 0x6f, 0xee, 0x5e, 0x01, 0xcb, 0x05, 0xec, 0xda, 0x0b, 0xe6, 0x34, 0x86, 0xaa, 0x36, - 0xf0, 0x97, 0xab, 0x07, 0x5b, 0xb5, 0x87, 0xbf, 0x1c, 0x7c, 0x19, 0xdc, 0xd8, 0xd8, 0xd8, 0xc6, 0xdb, 0xb5, 0x44, - 0x90, 0x37, 0x78, 0xa0, 0x8f, 0x57, 0x1f, 0x05, 0x2d, 0x57, 0x88, 0x6d, 0x36, 0x70, 0x28, 0x6c, 0x0d, 0xf2, 0x4d, - 0xca, 0xa4, 0xe1, 0xbc, 0xe0, 0xd9, 0x54, 0xce, 0x50, 0xc8, 0x6b, 0x3e, 0x0e, 0xda, 0x8e, 0xf0, 0xbf, 0xc0, 0xa9, - 0x1d, 0x2f, 0x2f, 0x3e, 0x41, 0x1f, 0xf0, 0x74, 0xa5, 0x34, 0xa5, 0x38, 0xa5, 0x0a, 0xea, 0x2c, 0xd7, 0x79, 0x30, - 0x52, 0x5c, 0x4c, 0x60, 0x71, 0xc1, 0x65, 0xb9, 0x71, 0x36, 0x72, 0xfa, 0x4b, 0xbc, 0xba, 0x48, 0x97, 0x8f, 0x44, - 0xb6, 0x6a, 0xe9, 0xfd, 0xac, 0x4f, 0xb7, 0xed, 0x29, 0xe3, 0x93, 0x6c, 0x44, 0x07, 0x33, 0x3e, 0x4e, 0x84, 0xd7, - 0x27, 0x46, 0xfa, 0x6e, 0x11, 0x98, 0x6e, 0x8e, 0x4d, 0x7e, 0x38, 0x5e, 0x6f, 0x36, 0x6b, 0xdc, 0xc1, 0x3b, 0xe7, - 0x93, 0xb3, 0x28, 0x31, 0xa2, 0xb2, 0xd0, 0xf0, 0x80, 0x56, 0x88, 0x9b, 0xf7, 0x4c, 0x60, 0x5c, 0x76, 0x45, 0x52, - 0xdb, 0x0d, 0x04, 0x2e, 0xf6, 0x38, 0x66, 0xc9, 0xc8, 0xf6, 0xa0, 0x3c, 0xd0, 0x17, 0xa3, 0xe9, 0x16, 0x30, 0x2d, - 0xaf, 0x9d, 0x5d, 0xa4, 0xb6, 0x57, 0x4d, 0x15, 0xc0, 0x2c, 0x59, 0x1e, 0x9f, 0x21, 0xeb, 0x7e, 0x0d, 0x5d, 0xc4, - 0x80, 0xb1, 0x71, 0x65, 0xce, 0x5d, 0xac, 0x5a, 0x11, 0xdf, 0x68, 0x22, 0x4d, 0xea, 0x43, 0xea, 0x7b, 0x14, 0xd6, - 0xea, 0x2a, 0x07, 0x09, 0xdc, 0x23, 0xef, 0x8e, 0xb8, 0xf4, 0xf4, 0x99, 0xc5, 0xb8, 0x4a, 0xdf, 0x52, 0xd7, 0xe2, - 0x9a, 0x61, 0xaf, 0x78, 0x00, 0xf6, 0x07, 0xc6, 0x2d, 0x62, 0x11, 0x6f, 0x67, 0xb5, 0x14, 0xd6, 0xc6, 0x1c, 0x68, - 0x6e, 0xb8, 0xc1, 0xcf, 0xac, 0x5a, 0x33, 0x30, 0xc3, 0x8c, 0x33, 0x92, 0x0f, 0xc6, 0xbd, 0xaa, 0xb1, 0x23, 0x57, - 0x01, 0x44, 0xdf, 0x82, 0x2e, 0xc9, 0xe1, 0x95, 0x2c, 0x57, 0x9d, 0x21, 0xbf, 0x82, 0x75, 0xd6, 0x8b, 0x13, 0x70, - 0x93, 0xa6, 0xac, 0xc4, 0xc4, 0x14, 0x71, 0xb9, 0x59, 0xc6, 0x3c, 0x4d, 0x9f, 0x45, 0x3b, 0x38, 0xb9, 0x91, 0xc0, - 0x11, 0xfb, 0xc6, 0x32, 0x34, 0x13, 0x36, 0x62, 0x22, 0x8d, 0x4a, 0x29, 0x61, 0x03, 0xb9, 0xd4, 0x92, 0xbf, 0xcc, - 0xe5, 0xd5, 0x97, 0xdb, 0x04, 0x07, 0xe4, 0x35, 0xb0, 0x1c, 0x1a, 0xc7, 0x2d, 0x03, 0x89, 0x58, 0x0c, 0x88, 0x51, - 0xab, 0x72, 0x39, 0x19, 0xd5, 0xc9, 0x7c, 0x85, 0x5c, 0xa8, 0xc8, 0x83, 0x5b, 0x02, 0x2f, 0x54, 0xe4, 0x98, 0x3a, - 0x98, 0x95, 0xda, 0x4d, 0x8b, 0x4d, 0x92, 0xf7, 0xcc, 0x80, 0xe4, 0xea, 0x6b, 0x78, 0x68, 0xfc, 0x32, 0xbc, 0xa1, - 0xe8, 0xe9, 0x18, 0x21, 0xa7, 0xa5, 0x31, 0x97, 0xfe, 0x5b, 0x79, 0x9f, 0x56, 0x02, 0xf6, 0x0a, 0xc4, 0x94, 0x81, - 0x4b, 0x6c, 0x5c, 0x90, 0x94, 0xd7, 0xf2, 0x94, 0xdd, 0xd7, 0x50, 0xbe, 0x4b, 0x26, 0x5d, 0xa5, 0xb2, 0xd6, 0x58, - 0x75, 0x3f, 0xcf, 0x59, 0x7e, 0xb5, 0xcf, 0x30, 0x37, 0x19, 0x0d, 0xb2, 0x25, 0x33, 0x9b, 0xf2, 0xab, 0xbd, 0x1b, - 0xbf, 0xf2, 0x50, 0xd2, 0xa1, 0x5a, 0xa5, 0x9b, 0x97, 0x6e, 0x38, 0xc6, 0x8d, 0x1b, 0x8e, 0x00, 0x36, 0x86, 0x9d, - 0x2a, 0x52, 0xeb, 0xfc, 0xf7, 0xa5, 0xf0, 0x93, 0xd8, 0x6b, 0x47, 0x7a, 0xd7, 0x1d, 0xad, 0x4c, 0x4f, 0xbf, 0x01, - 0x55, 0x23, 0x4b, 0xe8, 0x26, 0x54, 0x31, 0x19, 0x89, 0x12, 0xd3, 0x55, 0xca, 0xa3, 0xbe, 0x46, 0x9c, 0x83, 0xb8, - 0xa1, 0xfc, 0xc5, 0x3f, 0x85, 0x57, 0x27, 0x01, 0x1a, 0x51, 0x8b, 0x71, 0x96, 0xf2, 0xd6, 0x38, 0x9a, 0xc6, 0xc9, - 0x55, 0x30, 0x8f, 0x5b, 0xd3, 0x2c, 0xcd, 0x8a, 0x19, 0x70, 0xa5, 0x57, 0x5c, 0x81, 0x0d, 0x3f, 0x6d, 0xcd, 0x63, - 0xef, 0x25, 0x4b, 0xce, 0x19, 0x8f, 0x87, 0x91, 0x67, 0xef, 0xe5, 0x20, 0x1e, 0xac, 0xb7, 0x51, 0x9e, 0x67, 0x17, - 0xb6, 0xf7, 0x21, 0x3b, 0x05, 0xa6, 0xf5, 0xde, 0x5d, 0x5e, 0x9d, 0xb1, 0xd4, 0xfb, 0x78, 0x3a, 0x4f, 0xf9, 0xdc, - 0x2b, 0xa2, 0xb4, 0x68, 0x15, 0x2c, 0x8f, 0xc7, 0xa0, 0x26, 0x92, 0x2c, 0x6f, 0x61, 0xfe, 0xf3, 0x94, 0x05, 0x49, - 0x7c, 0x36, 0xe1, 0xd6, 0x28, 0xca, 0x3f, 0xf5, 0x5a, 0xad, 0x59, 0x1e, 0x4f, 0xa3, 0xfc, 0xaa, 0x45, 0x2d, 0x82, - 0xcf, 0xda, 0xdb, 0xd1, 0xe7, 0xe3, 0x87, 0x3d, 0x9e, 0x43, 0xdf, 0x18, 0xa9, 0x18, 0x80, 0xf0, 0xb1, 0xb6, 0x77, - 0xda, 0xd3, 0xe2, 0x9e, 0x38, 0x51, 0x8a, 0x52, 0x5e, 0x9e, 0x78, 0x57, 0x0c, 0xe0, 0xf6, 0x4f, 0x79, 0xea, 0x81, - 0x2f, 0xc7, 0xb3, 0x74, 0x31, 0x9c, 0xe7, 0x05, 0x0c, 0x30, 0xcb, 0xe2, 0x94, 0xb3, 0xbc, 0x77, 0x9a, 0xe5, 0x40, - 0xb6, 0x56, 0x1e, 0x8d, 0xe2, 0x79, 0x11, 0x3c, 0x9c, 0x5d, 0xf6, 0xd0, 0x56, 0x38, 0xcb, 0xb3, 0x79, 0x3a, 0x92, - 0x73, 0xc5, 0x29, 0x6c, 0x8c, 0x98, 0x9b, 0x15, 0xf4, 0x25, 0x14, 0x80, 0x2f, 0x65, 0x51, 0xde, 0x3a, 0xc3, 0xce, - 0x68, 0xe8, 0xb7, 0x47, 0xec, 0xcc, 0xcb, 0xcf, 0x4e, 0x23, 0xa7, 0xd3, 0x7d, 0xec, 0xa9, 0x7f, 0xfe, 0x8e, 0x0b, - 0x86, 0xfb, 0xca, 0xe2, 0x4e, 0xbb, 0xfd, 0x37, 0x6e, 0xaf, 0x31, 0x0b, 0x01, 0x14, 0x74, 0x66, 0x97, 0x56, 0x91, - 0x25, 0xb0, 0x3e, 0xab, 0x7a, 0xf6, 0x66, 0xe0, 0x37, 0xc5, 0xe9, 0x59, 0xd0, 0x9d, 0x5d, 0x96, 0x88, 0x5d, 0x20, - 0x12, 0x32, 0x25, 0x92, 0xf2, 0x6d, 0xf1, 0x5b, 0x21, 0x7e, 0xb2, 0x1a, 0xe2, 0xae, 0x82, 0xb8, 0xa2, 0x7a, 0x6b, - 0x04, 0xfb, 0x80, 0xc8, 0xdf, 0x29, 0x04, 0x20, 0x13, 0x70, 0x02, 0x73, 0x05, 0x07, 0xbd, 0xfc, 0x66, 0x30, 0xba, - 0xab, 0xc1, 0x78, 0x72, 0x1b, 0x18, 0x79, 0x3a, 0x5a, 0xd4, 0xd7, 0xb5, 0x03, 0xce, 0x69, 0x6f, 0xc2, 0x90, 0x9f, - 0x82, 0x2e, 0x3e, 0x5f, 0xc4, 0x23, 0x3e, 0x11, 0x8f, 0xc4, 0xce, 0x17, 0xa2, 0x6e, 0xa7, 0xdd, 0x16, 0xef, 0x05, - 0x28, 0xb4, 0xa0, 0xe3, 0x63, 0x03, 0x60, 0xa2, 0x2f, 0xd6, 0x7d, 0xc4, 0xe6, 0xbb, 0x5b, 0xbf, 0x54, 0xe3, 0x31, - 0x95, 0x37, 0x28, 0x54, 0x84, 0xfa, 0x66, 0x0b, 0x66, 0xbc, 0xe5, 0xfd, 0x8e, 0x3e, 0xa8, 0x1a, 0x7c, 0xc7, 0x48, - 0xeb, 0x05, 0xcc, 0x33, 0x73, 0x81, 0x7a, 0x69, 0x1f, 0x43, 0x52, 0xad, 0x96, 0x0b, 0x7a, 0x83, 0x63, 0x08, 0x89, - 0x0e, 0x04, 0x9d, 0x7c, 0x50, 0xd0, 0x37, 0x35, 0x32, 0x37, 0x28, 0x9c, 0xcc, 0x85, 0x2d, 0x9f, 0x69, 0xb9, 0x0e, - 0x4a, 0x1a, 0xbc, 0xec, 0x2f, 0x98, 0x6c, 0x00, 0xd2, 0xbb, 0x92, 0xb4, 0xbc, 0x3a, 0x7a, 0x52, 0x2e, 0x5f, 0x36, - 0x24, 0xca, 0x81, 0xaf, 0xcf, 0x27, 0xe8, 0x77, 0xeb, 0xab, 0xeb, 0x46, 0x4a, 0xcd, 0x96, 0xed, 0x0e, 0xb8, 0xce, - 0xca, 0xc2, 0xec, 0x33, 0x5e, 0xe2, 0x28, 0x5f, 0x81, 0x9c, 0xc5, 0xd0, 0xeb, 0xcf, 0xa1, 0x70, 0xd3, 0x94, 0x93, - 0xb6, 0x71, 0xd3, 0xf5, 0x7f, 0x58, 0xf1, 0x98, 0xb2, 0x9d, 0x55, 0x6c, 0x1c, 0x5c, 0x97, 0xe3, 0xa1, 0xb8, 0x76, - 0x58, 0x60, 0xb6, 0xf8, 0x6f, 0xf7, 0x24, 0x1c, 0x8d, 0x56, 0x91, 0xcd, 0xf3, 0x21, 0x26, 0xfd, 0xaf, 0x08, 0x31, - 0xd8, 0xa4, 0xe1, 0x6d, 0x8f, 0x6b, 0xc5, 0xc2, 0x30, 0x7f, 0xc2, 0xfc, 0xaa, 0x02, 0xa3, 0x53, 0x17, 0x71, 0xa9, - 0x41, 0x86, 0x55, 0x14, 0xd8, 0xa8, 0x2b, 0x47, 0x94, 0x60, 0x47, 0x17, 0x3e, 0xfd, 0x79, 0x1a, 0x83, 0x68, 0x3d, - 0x8e, 0x47, 0x74, 0xd1, 0x25, 0x1e, 0xd1, 0xc9, 0x47, 0x8b, 0x32, 0x9d, 0x30, 0x94, 0x0e, 0x05, 0x92, 0xe0, 0xf8, - 0x2c, 0x33, 0x67, 0xec, 0x96, 0x8d, 0xa7, 0x17, 0x86, 0x6e, 0x1e, 0x65, 0xd3, 0x28, 0x4e, 0x03, 0xfc, 0x20, 0x89, - 0xa7, 0x47, 0x0c, 0xb0, 0x8b, 0x07, 0x7f, 0x15, 0xed, 0x3b, 0xae, 0xff, 0x13, 0x08, 0x2e, 0xea, 0x5f, 0x4a, 0xc7, - 0x4f, 0xc3, 0xa5, 0xce, 0x95, 0xeb, 0xa5, 0x20, 0xec, 0xb8, 0xce, 0x6d, 0xa7, 0xc0, 0xca, 0x2e, 0xa3, 0x3f, 0x83, - 0x56, 0x27, 0xe8, 0xb8, 0xcb, 0x2b, 0x60, 0x5c, 0x0c, 0xa8, 0x56, 0x85, 0x4a, 0xe4, 0x1b, 0xcc, 0x21, 0xf9, 0xf3, - 0xfa, 0x5a, 0x7f, 0x3c, 0xa0, 0x71, 0x81, 0x56, 0xa4, 0xdf, 0xc8, 0x4b, 0x98, 0x84, 0x85, 0x7e, 0x16, 0x98, 0x56, - 0xef, 0x1a, 0x5b, 0x4f, 0x6e, 0x25, 0x8c, 0x39, 0x9d, 0xa5, 0x4e, 0x0d, 0x0d, 0x3a, 0xbe, 0x58, 0x33, 0x95, 0x5b, - 0x46, 0xc4, 0xdc, 0x4f, 0x49, 0xe6, 0xd4, 0xaf, 0x3f, 0xc5, 0x18, 0xb8, 0xaf, 0x65, 0x6d, 0x29, 0xf6, 0x1e, 0x9e, - 0xec, 0x0a, 0x21, 0x65, 0x11, 0xeb, 0x86, 0x36, 0x48, 0x0d, 0xdb, 0xfa, 0xe3, 0x10, 0xe8, 0xfc, 0x29, 0xb4, 0x37, - 0x16, 0x8e, 0xba, 0x0b, 0x90, 0xc3, 0x5c, 0x7b, 0x42, 0x51, 0xd3, 0x47, 0x04, 0xec, 0xfe, 0xc6, 0x82, 0x95, 0xbb, - 0x5b, 0xa2, 0x77, 0xff, 0xa4, 0x2c, 0x48, 0xa7, 0x9a, 0xb1, 0xbf, 0x6a, 0x0a, 0x51, 0x07, 0xc3, 0x52, 0xc6, 0x31, - 0x8e, 0x9b, 0x6b, 0x3b, 0x51, 0x04, 0xb9, 0x25, 0xe3, 0x16, 0x98, 0x61, 0x15, 0xe5, 0x20, 0x46, 0x74, 0x0e, 0x4d, - 0x21, 0xd2, 0x46, 0x7a, 0xcb, 0x50, 0x9c, 0x20, 0x04, 0x83, 0x8d, 0x45, 0x5c, 0x86, 0xf0, 0x94, 0x0e, 0xb3, 0x11, - 0xfb, 0xf8, 0xe1, 0x15, 0x5e, 0x93, 0xc8, 0x52, 0x94, 0xa7, 0x99, 0x5b, 0x9e, 0x80, 0x81, 0x85, 0x90, 0xe6, 0xea, - 0x2b, 0x35, 0x00, 0x8c, 0x88, 0x15, 0x59, 0x34, 0x2a, 0x82, 0xc2, 0x4b, 0xdb, 0x1a, 0x08, 0x08, 0xc1, 0x91, 0xc5, - 0x02, 0x30, 0x41, 0xa9, 0x17, 0x07, 0xfc, 0x44, 0xeb, 0x3e, 0x0c, 0xb4, 0xbb, 0x25, 0x1a, 0x01, 0xae, 0x39, 0xa2, - 0x51, 0xa1, 0x8a, 0x59, 0x45, 0x26, 0xba, 0xa3, 0xf8, 0x5c, 0x93, 0x93, 0x52, 0xac, 0xfb, 0xbb, 0x49, 0x74, 0xca, - 0x12, 0x18, 0x12, 0xf8, 0xaa, 0x0d, 0x23, 0x89, 0x57, 0x6b, 0x37, 0x4e, 0x67, 0x73, 0xf9, 0xb5, 0x30, 0x98, 0xb8, - 0x83, 0x07, 0xb8, 0x78, 0x99, 0x61, 0xa0, 0x4e, 0x24, 0x03, 0x39, 0x00, 0x80, 0x48, 0x87, 0x21, 0x08, 0x5d, 0xc5, - 0x2a, 0x50, 0x1a, 0x8f, 0x96, 0xcb, 0x60, 0x7f, 0xcf, 0xb0, 0x34, 0x85, 0xe7, 0x69, 0x9c, 0xe2, 0x63, 0x81, 0x8f, - 0xd1, 0x25, 0x3e, 0x66, 0xf0, 0xa8, 0x71, 0xcf, 0x4b, 0xfb, 0xaf, 0xba, 0x2a, 0x99, 0x5c, 0x01, 0x4b, 0x13, 0x20, - 0xbb, 0xbe, 0x06, 0xb5, 0xa5, 0x49, 0xb0, 0xbb, 0x05, 0xc4, 0x42, 0xee, 0x11, 0xdf, 0x8e, 0xe1, 0x26, 0x19, 0x59, - 0x31, 0x6b, 0x89, 0x72, 0x8b, 0x8c, 0x83, 0x10, 0x7c, 0xc7, 0xdc, 0x69, 0xd8, 0x40, 0x9e, 0xcc, 0x92, 0x79, 0x86, - 0x2f, 0xae, 0x6d, 0x89, 0x8f, 0x7b, 0x08, 0xa2, 0xd0, 0x23, 0x62, 0xa8, 0xcb, 0x98, 0xfc, 0x6c, 0x4f, 0x1c, 0xda, - 0x38, 0x0b, 0x98, 0xa1, 0xe8, 0x85, 0xf2, 0x28, 0x4e, 0x44, 0xe3, 0x15, 0xf8, 0x34, 0xd2, 0x1d, 0x09, 0x9d, 0xdd, - 0xad, 0x0a, 0x36, 0x00, 0x5e, 0x49, 0x04, 0x4e, 0x19, 0x37, 0xb6, 0x28, 0xa7, 0x14, 0x00, 0xb9, 0xcd, 0xab, 0x4f, - 0x3a, 0x01, 0x53, 0x80, 0x11, 0x3d, 0x3a, 0xa6, 0xd9, 0x06, 0x43, 0x20, 0x16, 0xcd, 0xd8, 0xd8, 0xba, 0xf6, 0x5f, - 0xfe, 0xf9, 0x1f, 0x6c, 0x4f, 0x80, 0x98, 0x8d, 0xc7, 0x20, 0xe5, 0xac, 0x75, 0x0d, 0xff, 0xd7, 0x3f, 0xfe, 0xdf, - 0xff, 0xf3, 0x5f, 0x75, 0xdb, 0x14, 0x9a, 0x9e, 0x04, 0xe2, 0x68, 0x41, 0x93, 0x94, 0x52, 0x3c, 0xed, 0x71, 0x94, - 0xae, 0x00, 0xe9, 0x10, 0xb3, 0x18, 0x19, 0x1b, 0x79, 0xb6, 0x05, 0x9a, 0x40, 0x3c, 0x1f, 0x27, 0xec, 0x9c, 0xc9, - 0x0f, 0xcb, 0xe8, 0x41, 0x74, 0xe5, 0x10, 0x2c, 0x18, 0x2e, 0xef, 0xbc, 0xca, 0x6d, 0xa0, 0x68, 0x29, 0x29, 0x5e, - 0x27, 0x98, 0x67, 0x1b, 0x83, 0x36, 0xe7, 0x68, 0xd7, 0x87, 0xf5, 0x40, 0xa5, 0xda, 0xb6, 0x80, 0x97, 0xcc, 0xde, - 0x95, 0x10, 0x37, 0xe1, 0x3a, 0xcd, 0xb1, 0x69, 0xca, 0x8a, 0x62, 0x15, 0x58, 0x40, 0x13, 0xcf, 0xae, 0x9a, 0xd8, - 0xb5, 0x0e, 0x00, 0x40, 0x77, 0x67, 0x47, 0x4c, 0x0b, 0x15, 0x6c, 0x3c, 0x86, 0x0d, 0x8e, 0xba, 0x2d, 0xe1, 0x18, - 0x84, 0x0f, 0xfb, 0xf6, 0x5b, 0x90, 0x25, 0x78, 0xa7, 0xc5, 0xd5, 0x9f, 0xf4, 0xa2, 0xe9, 0x95, 0xb0, 0x33, 0xe6, - 0x10, 0x9d, 0x8d, 0x61, 0xf4, 0x93, 0x81, 0x54, 0x36, 0xfc, 0xb4, 0x8a, 0x31, 0xd6, 0x32, 0xc2, 0xbf, 0xff, 0xcb, - 0x3f, 0xfe, 0x37, 0x18, 0x9b, 0xfa, 0xad, 0xe7, 0x02, 0x68, 0xf5, 0x3f, 0xa1, 0xd5, 0x3c, 0xbd, 0xa5, 0xdd, 0x5f, - 0xfe, 0xfe, 0xbf, 0x43, 0x33, 0xba, 0x28, 0x05, 0x7c, 0x42, 0x10, 0x0d, 0xd1, 0x36, 0xfd, 0x55, 0x20, 0xd5, 0x06, - 0x59, 0x3b, 0xd3, 0x3f, 0x21, 0xd8, 0x05, 0xcf, 0x66, 0x37, 0x82, 0x83, 0x50, 0x0f, 0x93, 0xac, 0x60, 0x1a, 0x1e, - 0xa1, 0x4f, 0x7e, 0x1d, 0x40, 0x34, 0xd7, 0x0c, 0x76, 0x6d, 0x61, 0xe9, 0x71, 0xc4, 0x0a, 0xad, 0xdc, 0x84, 0xf5, - 0x05, 0x2c, 0x18, 0x27, 0x74, 0x28, 0xdc, 0x03, 0x4b, 0x26, 0x9e, 0xe0, 0x81, 0x04, 0x9c, 0x5b, 0xff, 0xf8, 0xda, - 0xea, 0xc1, 0x34, 0xc3, 0x89, 0xb1, 0x44, 0x84, 0x4b, 0x8d, 0x00, 0x7f, 0x41, 0x08, 0x1f, 0xeb, 0xe7, 0xe8, 0x52, - 0x3f, 0xa3, 0xa0, 0x16, 0x13, 0x80, 0xbe, 0x9d, 0xa2, 0x31, 0x66, 0xce, 0x20, 0xb2, 0x33, 0x2a, 0xf7, 0xde, 0x48, - 0xf2, 0x11, 0xc2, 0xf8, 0x18, 0x73, 0x61, 0xf1, 0xe6, 0xd3, 0x3c, 0x67, 0xc7, 0x49, 0x76, 0x81, 0x31, 0x43, 0x22, - 0xd2, 0x9a, 0xfa, 0xf2, 0xdf, 0xfe, 0xd5, 0xf7, 0xff, 0xed, 0x5f, 0xd7, 0x34, 0x98, 0xc0, 0x9e, 0x00, 0x23, 0x9f, - 0x85, 0x9a, 0xce, 0x0d, 0xb4, 0x56, 0x0f, 0x8a, 0x78, 0xae, 0xae, 0x91, 0x88, 0x63, 0xa9, 0xc4, 0x5b, 0x3e, 0x12, - 0xda, 0x9a, 0x29, 0x6e, 0x9f, 0x05, 0x21, 0x5b, 0x33, 0x0d, 0x56, 0xdd, 0x32, 0xcf, 0x89, 0x1b, 0xdc, 0x40, 0x97, - 0x5f, 0x89, 0xf1, 0x6a, 0x30, 0x6e, 0x85, 0xc0, 0x03, 0x6d, 0x26, 0xf4, 0xdd, 0x33, 0xa1, 0xad, 0x02, 0xb1, 0x0c, - 0x52, 0x77, 0xd5, 0x00, 0xf2, 0xac, 0x03, 0x9a, 0x80, 0x9a, 0xc4, 0x95, 0xad, 0x40, 0xe6, 0xd6, 0x69, 0xde, 0x7f, - 0x83, 0x97, 0x1d, 0x91, 0x78, 0x64, 0x29, 0x14, 0x64, 0xd8, 0x30, 0x32, 0x6c, 0xa4, 0x46, 0x35, 0x6d, 0x0a, 0x74, - 0xfc, 0xb2, 0xd5, 0xb6, 0xc3, 0x31, 0x76, 0xaf, 0x69, 0x7f, 0x26, 0xb5, 0x7f, 0x2c, 0xed, 0x7d, 0xa9, 0xfd, 0xf1, - 0x93, 0x36, 0x0d, 0xed, 0x1f, 0xaf, 0xd5, 0xfe, 0x48, 0xb9, 0x01, 0x8e, 0x1c, 0xda, 0x9b, 0x18, 0xdd, 0x32, 0x6c, - 0x0d, 0xd4, 0xc4, 0x83, 0xe1, 0x84, 0x0d, 0x3f, 0x49, 0x33, 0x8b, 0x10, 0xc0, 0x40, 0x94, 0x36, 0x26, 0x05, 0x06, - 0x60, 0x32, 0x9c, 0x94, 0x7a, 0xd3, 0xe3, 0xa3, 0x31, 0x01, 0x73, 0x17, 0x63, 0x86, 0xa2, 0x1f, 0xd6, 0xec, 0x2b, - 0x56, 0x6e, 0xe1, 0x38, 0x62, 0xc3, 0x88, 0x67, 0xc0, 0x6c, 0x0b, 0x07, 0x3b, 0xf1, 0x16, 0x22, 0x58, 0x18, 0xd8, - 0xef, 0xdf, 0xed, 0x1f, 0xd8, 0xde, 0x69, 0x36, 0xba, 0x0a, 0x6c, 0x70, 0xc6, 0xc0, 0x9a, 0x72, 0x7d, 0x3e, 0x61, - 0xa9, 0xa3, 0x3c, 0x9f, 0x2c, 0x61, 0xe0, 0x00, 0x9e, 0x89, 0x6f, 0x5b, 0x34, 0x0f, 0x3a, 0x80, 0xb0, 0xf4, 0xf1, - 0xcb, 0xfe, 0x2e, 0x17, 0xdf, 0x85, 0xe5, 0x39, 0x3e, 0xf6, 0x31, 0xd5, 0x63, 0x77, 0x0b, 0x1e, 0xf0, 0x65, 0x1f, - 0xf5, 0x1e, 0xbd, 0x6d, 0x2c, 0x96, 0xdc, 0x86, 0x01, 0x0e, 0x31, 0xe9, 0x0b, 0x14, 0x0a, 0x6a, 0x75, 0x12, 0x20, - 0x62, 0xf0, 0x08, 0x63, 0x6d, 0xa9, 0x71, 0x11, 0x42, 0xd5, 0x5f, 0x3b, 0x2e, 0x95, 0xdd, 0x4a, 0xf3, 0x8e, 0xb0, - 0x01, 0x39, 0x2e, 0xd8, 0x7b, 0xa4, 0x4b, 0x84, 0xa9, 0x43, 0x45, 0xeb, 0x20, 0xd0, 0x35, 0x95, 0xb9, 0x22, 0x3a, - 0x18, 0xc0, 0x90, 0x99, 0x2b, 0x00, 0x81, 0xbf, 0x84, 0xf6, 0x89, 0xf9, 0xfd, 0x37, 0xf1, 0xa9, 0x26, 0x4d, 0x9c, - 0xc3, 0x3f, 0x79, 0x57, 0xcc, 0xbb, 0x3a, 0xa1, 0x96, 0x2a, 0xd8, 0x80, 0x51, 0x30, 0x0c, 0xca, 0xb4, 0x55, 0x54, - 0x09, 0xec, 0xb4, 0x24, 0x9a, 0x15, 0x2c, 0x50, 0x0f, 0x32, 0xee, 0x80, 0xe1, 0x8b, 0xe5, 0x40, 0x8f, 0x69, 0xcf, - 0x95, 0x7c, 0xb2, 0x30, 0x03, 0x13, 0x8f, 0xda, 0xed, 0x1e, 0x5e, 0xaa, 0x68, 0x45, 0x60, 0x1d, 0xa4, 0x41, 0xc2, - 0xc6, 0xbc, 0xe4, 0x78, 0x6b, 0x7f, 0xa1, 0x22, 0x41, 0x7e, 0x77, 0x27, 0x67, 0x53, 0xcb, 0xc7, 0xff, 0xbf, 0x6d, - 0xec, 0x51, 0x90, 0xf2, 0x49, 0x8b, 0xae, 0xf1, 0xe0, 0x15, 0x49, 0x80, 0xc8, 0x7c, 0x5f, 0x18, 0x13, 0x0d, 0x19, - 0x46, 0xc9, 0x4a, 0x0e, 0xce, 0x37, 0x88, 0x9b, 0xdc, 0x6c, 0x07, 0x72, 0x7a, 0x29, 0x54, 0xb6, 0x1c, 0xac, 0xd9, - 0x76, 0xa5, 0x7f, 0xb4, 0xdc, 0x58, 0x45, 0xbc, 0xea, 0x6f, 0x4b, 0x14, 0x32, 0x62, 0x73, 0xa5, 0x50, 0x51, 0x0b, - 0xd1, 0xc3, 0xc4, 0x69, 0x39, 0x6a, 0x77, 0xab, 0xc5, 0x5c, 0x92, 0xb8, 0x38, 0x24, 0x71, 0x41, 0xe2, 0xef, 0x68, - 0x21, 0xe6, 0x1e, 0x46, 0xc9, 0xd0, 0x41, 0x00, 0xac, 0x96, 0xf5, 0x04, 0xa8, 0xe9, 0xaa, 0xc8, 0x91, 0xff, 0x18, - 0x89, 0x5b, 0x0a, 0x61, 0xb9, 0x82, 0x4a, 0x27, 0x47, 0x65, 0xd9, 0x63, 0xcc, 0x39, 0xfc, 0x20, 0x2f, 0x81, 0x88, - 0xbb, 0xbf, 0xfa, 0xfb, 0x89, 0xed, 0xd2, 0x3d, 0xf2, 0x7e, 0x36, 0x3e, 0x4a, 0x67, 0x2b, 0x66, 0xb7, 0x3d, 0x58, - 0x06, 0xb3, 0xa7, 0xfc, 0x84, 0xe4, 0x4d, 0x7d, 0x4d, 0x36, 0xa7, 0xfe, 0x3f, 0x87, 0x38, 0xc2, 0x1b, 0xc7, 0x46, - 0x13, 0x9d, 0x46, 0xbe, 0x6a, 0x11, 0x7f, 0xda, 0xd8, 0x55, 0x1c, 0x81, 0x7c, 0xbd, 0x2e, 0x92, 0xf5, 0xcd, 0xed, - 0x91, 0xac, 0xe2, 0x8e, 0x91, 0xac, 0x6f, 0x7e, 0xe7, 0x48, 0xd6, 0xd7, 0x66, 0x24, 0x0b, 0x05, 0xf4, 0xab, 0x5f, - 0x13, 0x6d, 0xca, 0xb3, 0x8b, 0x22, 0xec, 0xc8, 0xcc, 0x09, 0x90, 0x75, 0x18, 0x76, 0xfa, 0xeb, 0x47, 0x98, 0x60, - 0xa2, 0x46, 0x7c, 0x89, 0x02, 0x4a, 0x22, 0xd9, 0x13, 0xd4, 0x8a, 0x0c, 0xe7, 0xb4, 0x75, 0x56, 0x65, 0xeb, 0xa1, - 0xba, 0x46, 0x06, 0xae, 0xaf, 0xab, 0x43, 0x6d, 0x5d, 0x15, 0xf0, 0x09, 0xe8, 0x3b, 0xb0, 0xba, 0x63, 0x77, 0x53, - 0xa5, 0xf3, 0x99, 0x23, 0xf4, 0xd4, 0x29, 0x8d, 0x60, 0xa2, 0x85, 0xfd, 0x5f, 0x0e, 0x3b, 0xbd, 0xed, 0xce, 0x14, - 0x7a, 0x83, 0x02, 0x87, 0xb7, 0x76, 0x6f, 0x7b, 0x1b, 0xdf, 0x2e, 0xd4, 0x5b, 0x17, 0xdf, 0x62, 0xf5, 0xb6, 0x83, - 0x6f, 0x43, 0xf5, 0xf6, 0x08, 0xdf, 0x46, 0xea, 0xed, 0x31, 0xbe, 0x9d, 0xdb, 0xe5, 0x21, 0xd3, 0xc0, 0x3d, 0x06, - 0xbe, 0x22, 0x6f, 0x26, 0x50, 0x65, 0xb0, 0xe9, 0xf1, 0xc3, 0x08, 0xd1, 0x59, 0x10, 0x7b, 0xc2, 0xbb, 0x0c, 0x72, - 0xef, 0x02, 0x34, 0x4e, 0x40, 0xd9, 0x86, 0xcf, 0xf1, 0x3b, 0x1c, 0xe0, 0x24, 0x1d, 0xc4, 0x53, 0xa6, 0x3e, 0x48, - 0xac, 0xb0, 0x06, 0x03, 0xf6, 0xb0, 0x7d, 0x54, 0xf6, 0xf4, 0x3a, 0x89, 0x78, 0x96, 0xca, 0xe6, 0xa0, 0x95, 0xab, - 0xea, 0xc4, 0x74, 0x2d, 0xbd, 0xc2, 0x6b, 0xf4, 0x97, 0x11, 0x8f, 0x18, 0x83, 0x61, 0xd6, 0xba, 0x04, 0x0f, 0x76, - 0xa5, 0x4e, 0x43, 0x88, 0xb4, 0x4e, 0x23, 0x9c, 0xf4, 0xdb, 0x41, 0x74, 0xa6, 0x9f, 0xdf, 0x80, 0xa5, 0x1d, 0x9d, - 0xc9, 0x96, 0xeb, 0x75, 0x18, 0x81, 0x68, 0xea, 0x2f, 0x05, 0x04, 0x99, 0x62, 0xb0, 0x34, 0xe8, 0x49, 0x4b, 0xfd, - 0x85, 0xd4, 0xa9, 0x6b, 0x34, 0x9a, 0xbe, 0x5e, 0x04, 0x14, 0xad, 0x0a, 0x76, 0xc1, 0xe0, 0xa7, 0x52, 0x41, 0x61, - 0xa8, 0xc0, 0x02, 0x51, 0xbd, 0x46, 0x95, 0xe9, 0x60, 0xc3, 0x5a, 0x85, 0x66, 0x29, 0x5d, 0x66, 0x9e, 0xee, 0xe8, - 0xa3, 0x9d, 0x65, 0xf1, 0xfa, 0x59, 0x67, 0x88, 0xff, 0x49, 0xe1, 0xfd, 0xd9, 0x78, 0x3c, 0xbe, 0x51, 0xb7, 0x7d, - 0x36, 0x1a, 0xb3, 0x2e, 0xdb, 0xe9, 0x61, 0xe4, 0xbf, 0x25, 0xc5, 0x69, 0xa7, 0x24, 0xda, 0x2d, 0xee, 0xd6, 0x18, - 0x25, 0x2f, 0xa8, 0xbb, 0xbb, 0x2b, 0xc1, 0x12, 0xa8, 0xb2, 0x00, 0xe1, 0x7f, 0x16, 0xa7, 0x41, 0xbb, 0xf4, 0xcf, - 0xa5, 0xd6, 0xf8, 0xec, 0xc9, 0x93, 0x27, 0xa5, 0x3f, 0x52, 0x6f, 0xed, 0xd1, 0xa8, 0xf4, 0x87, 0x0b, 0x8d, 0x46, - 0xbb, 0x3d, 0x1e, 0x97, 0x7e, 0xac, 0x0a, 0xb6, 0xbb, 0xc3, 0xd1, 0x76, 0xb7, 0xf4, 0x2f, 0x8c, 0x16, 0xa5, 0xcf, - 0xe4, 0x5b, 0xce, 0x46, 0xb5, 0xe3, 0x83, 0xc7, 0x6d, 0xa8, 0x14, 0x8c, 0xb6, 0x40, 0xef, 0x52, 0x3c, 0x06, 0xd1, - 0x9c, 0x67, 0x60, 0xd8, 0x95, 0xbd, 0x02, 0xe4, 0xf3, 0x58, 0x4a, 0x78, 0xf1, 0xbd, 0x5f, 0x94, 0xea, 0xaf, 0x4c, - 0xa9, 0x8e, 0xcc, 0x4c, 0xd2, 0xbc, 0x20, 0x6d, 0xd0, 0xac, 0x46, 0xce, 0xa2, 0xea, 0x57, 0x61, 0x51, 0x09, 0x7b, - 0x94, 0x36, 0xd8, 0x52, 0xc8, 0xf8, 0x1f, 0xd6, 0xc9, 0xf8, 0xef, 0x6f, 0x97, 0xf1, 0xa7, 0x77, 0x13, 0xf1, 0xdf, - 0xff, 0xce, 0x22, 0xfe, 0x07, 0x53, 0xc4, 0x0b, 0x21, 0xb6, 0x07, 0xa6, 0x33, 0xd9, 0xcc, 0xa7, 0xd9, 0x65, 0x0b, - 0xb7, 0x44, 0x6e, 0x93, 0xf4, 0x9c, 0xde, 0x49, 0xf8, 0xaf, 0xc8, 0x07, 0x53, 0x83, 0x19, 0x1f, 0x0f, 0xe6, 0xd9, - 0xd9, 0x59, 0xc2, 0x94, 0x8c, 0x37, 0x2a, 0xc8, 0x1c, 0x7f, 0x97, 0x86, 0xf6, 0x3b, 0xf4, 0x8c, 0xab, 0x92, 0xf1, - 0x18, 0x8a, 0xc6, 0x63, 0x5b, 0xe5, 0x4b, 0x83, 0x3c, 0xa3, 0x56, 0x6f, 0x6b, 0x25, 0xd4, 0xea, 0x8b, 0x2f, 0xcc, - 0x32, 0xb3, 0x40, 0x86, 0xf4, 0x4c, 0x63, 0x44, 0xd6, 0x8c, 0xe2, 0x02, 0xf7, 0x60, 0xf5, 0xb1, 0x63, 0xb4, 0x77, - 0xa6, 0xa0, 0x54, 0xe2, 0x21, 0x9e, 0x8b, 0x34, 0x3f, 0x2c, 0x23, 0x72, 0xdb, 0x97, 0x91, 0xab, 0xce, 0xbf, 0x8d, - 0x6f, 0x18, 0x56, 0x67, 0xde, 0xb0, 0xf8, 0x32, 0xbf, 0xe5, 0xe9, 0xd5, 0xab, 0x91, 0xb3, 0x87, 0x97, 0x7f, 0x8b, - 0x77, 0x69, 0x23, 0x6f, 0x50, 0x80, 0x1d, 0x86, 0x26, 0xa6, 0xa5, 0x20, 0x58, 0x75, 0x81, 0xa2, 0xaa, 0xec, 0x19, - 0x9d, 0x64, 0x7a, 0x19, 0x0e, 0x39, 0xa8, 0x91, 0x25, 0x30, 0x07, 0x93, 0xba, 0x90, 0x3e, 0x66, 0x2f, 0x92, 0x6e, - 0xce, 0xe5, 0x57, 0xcf, 0xe9, 0x70, 0x66, 0x21, 0xf5, 0x87, 0x4c, 0xc7, 0xa8, 0x7a, 0xd2, 0x79, 0x08, 0xcd, 0x30, - 0x2a, 0xd5, 0x19, 0x08, 0x10, 0x6e, 0x86, 0x9f, 0x68, 0x12, 0x43, 0xa8, 0x83, 0x82, 0x8a, 0x7a, 0xd7, 0xd7, 0xe6, - 0x97, 0x42, 0x6b, 0x5f, 0x95, 0x6c, 0xf0, 0x00, 0xc7, 0x4f, 0xfc, 0xa2, 0x36, 0xc8, 0xe6, 0xdc, 0xc1, 0x33, 0x80, - 0x05, 0x1e, 0x31, 0x78, 0x3b, 0xed, 0x36, 0xa8, 0x18, 0x5f, 0x7c, 0x07, 0xca, 0xd1, 0x9d, 0x05, 0xbe, 0x6c, 0xdd, - 0xb9, 0xc4, 0xd2, 0x77, 0xd9, 0x2a, 0x12, 0xdf, 0xbf, 0x2f, 0x11, 0x35, 0xee, 0x0e, 0xa9, 0x45, 0x6c, 0xbe, 0xfb, - 0xca, 0x77, 0x34, 0x08, 0xeb, 0xae, 0xe2, 0x60, 0x99, 0x5b, 0x5b, 0x2f, 0xc4, 0xb6, 0xc2, 0xaa, 0x59, 0x06, 0xe7, - 0x16, 0x9d, 0x59, 0x5c, 0x18, 0x01, 0xfc, 0xda, 0x36, 0x28, 0x55, 0x04, 0x5f, 0x84, 0xe1, 0xf7, 0xd0, 0xc5, 0x15, - 0x8e, 0xb7, 0x02, 0xba, 0xe1, 0xf2, 0x56, 0x90, 0xa3, 0x33, 0xac, 0x19, 0x5d, 0x55, 0xa9, 0x82, 0xd2, 0x3c, 0x82, - 0x31, 0x90, 0xa1, 0x48, 0x3a, 0xac, 0x71, 0x2a, 0xf4, 0x16, 0x4c, 0x43, 0x02, 0x58, 0xfb, 0x75, 0xe8, 0xd6, 0xd8, - 0x0a, 0x6c, 0x21, 0x2d, 0x40, 0xe9, 0x61, 0x87, 0xbe, 0x55, 0x03, 0x3d, 0x5d, 0x0e, 0xc0, 0xdf, 0xe8, 0xe4, 0x9d, - 0xf8, 0xc5, 0x85, 0x07, 0xff, 0xac, 0x3f, 0x2c, 0x40, 0xca, 0x9f, 0x7e, 0x8a, 0x39, 0xd8, 0xd4, 0xb3, 0x16, 0x86, - 0x5f, 0x28, 0x4e, 0x2b, 0xd5, 0x21, 0x1d, 0x45, 0x8b, 0x2b, 0x63, 0xbd, 0x79, 0x81, 0xbe, 0x20, 0x39, 0x3d, 0x41, - 0x9a, 0xa5, 0xac, 0x57, 0x4f, 0x39, 0x30, 0xfd, 0x0e, 0x45, 0xac, 0xa3, 0x45, 0x86, 0xbe, 0x23, 0xbf, 0x02, 0xdf, - 0x51, 0xa8, 0xd1, 0xb6, 0x72, 0x3a, 0xda, 0x2b, 0xdb, 0x07, 0x92, 0xb6, 0x9b, 0x64, 0x2d, 0xe4, 0xcb, 0xce, 0xd5, - 0x3a, 0xe7, 0xe8, 0xb6, 0x03, 0x78, 0x0c, 0x0a, 0xab, 0x7f, 0x46, 0xe6, 0x42, 0xb3, 0x98, 0x0e, 0xe0, 0xef, 0x02, - 0x59, 0x10, 0x8d, 0xf1, 0x0b, 0x8b, 0x77, 0x69, 0x79, 0x4a, 0xd9, 0xaf, 0x0b, 0x54, 0xeb, 0x41, 0xe7, 0x09, 0x78, - 0x7b, 0x77, 0x1e, 0xfe, 0x66, 0xf4, 0x4b, 0x49, 0x23, 0x75, 0x89, 0xd9, 0xb6, 0x7b, 0x28, 0x2f, 0x92, 0xe8, 0x0a, - 0x9c, 0x4e, 0xb2, 0x31, 0x4e, 0x31, 0x7a, 0xdc, 0x9b, 0x65, 0x32, 0x93, 0x24, 0x67, 0x09, 0xfd, 0x8c, 0x89, 0x5c, - 0x8a, 0xed, 0x47, 0xb3, 0x4b, 0xb5, 0x1a, 0x9d, 0x46, 0x86, 0xc8, 0xef, 0x9a, 0x08, 0xb2, 0x3e, 0xf3, 0xa4, 0x9e, - 0xcc, 0xb0, 0x03, 0x30, 0x08, 0xc3, 0xa6, 0x95, 0x0b, 0xa8, 0xda, 0x50, 0x62, 0xa4, 0xc2, 0x54, 0x03, 0x59, 0xfe, - 0x36, 0xa8, 0xca, 0xa8, 0x60, 0x3d, 0xfc, 0xd4, 0x65, 0x0c, 0xae, 0xad, 0x34, 0x9e, 0xa6, 0xf1, 0x68, 0x94, 0xb0, - 0x9e, 0xb2, 0x8f, 0xac, 0xce, 0x23, 0xcc, 0x24, 0x31, 0x97, 0xac, 0xbe, 0x2a, 0x06, 0xf1, 0x34, 0x9d, 0xa2, 0x53, - 0xb0, 0xd7, 0xf0, 0x7b, 0x95, 0x2b, 0xc9, 0x29, 0x53, 0x2c, 0xda, 0x15, 0xf1, 0xe8, 0xb9, 0x8e, 0xcb, 0x0e, 0x18, - 0x8b, 0xb4, 0xe0, 0xed, 0x1e, 0xcf, 0x66, 0x41, 0x6b, 0xbb, 0x8e, 0x08, 0x56, 0x69, 0x14, 0xbc, 0x15, 0x68, 0x79, - 0x68, 0x1d, 0x08, 0x2d, 0x67, 0xf9, 0x1d, 0x59, 0x46, 0x03, 0xe0, 0x37, 0x11, 0x75, 0x51, 0x59, 0x47, 0xe6, 0xaf, - 0xb3, 0x5b, 0x3e, 0x5f, 0xbd, 0x5b, 0x3e, 0x57, 0xbb, 0xe5, 0x66, 0x8e, 0xfd, 0x6c, 0xdc, 0xc1, 0xff, 0x7a, 0x15, - 0x42, 0xb0, 0x2a, 0x40, 0x0e, 0x0b, 0xed, 0xe2, 0x56, 0x17, 0xfe, 0x8f, 0x86, 0x6e, 0x7b, 0xf8, 0x9f, 0x0f, 0x16, - 0x60, 0xdb, 0xc2, 0x42, 0xfc, 0xd7, 0xae, 0x55, 0x75, 0x1e, 0x62, 0x1d, 0xf6, 0xda, 0x59, 0xae, 0xeb, 0xde, 0xbc, - 0x69, 0x41, 0x5e, 0x71, 0x27, 0x50, 0xc2, 0x18, 0x5c, 0xb5, 0xe8, 0xf4, 0x14, 0x4a, 0xc7, 0xd9, 0x70, 0x5e, 0xfc, - 0xad, 0x84, 0x5f, 0x12, 0xf1, 0xc6, 0x2d, 0xdd, 0x18, 0x47, 0x75, 0x15, 0x69, 0x49, 0x6a, 0x84, 0x85, 0x5e, 0xa7, - 0xa0, 0x00, 0xc6, 0x64, 0x4e, 0xd7, 0x7f, 0xb8, 0x62, 0x13, 0xfc, 0x7f, 0x59, 0x9b, 0x95, 0xc8, 0xfc, 0x47, 0x89, - 0x71, 0x23, 0x11, 0x7e, 0x15, 0x0d, 0xcc, 0x35, 0x6c, 0x3f, 0x59, 0x0d, 0xee, 0xa1, 0x9a, 0xe9, 0x48, 0x29, 0x05, - 0xa9, 0x77, 0xc0, 0x0b, 0x88, 0xe6, 0x09, 0xbf, 0x79, 0xd4, 0x75, 0x9c, 0xb1, 0x34, 0xea, 0x0d, 0x02, 0xbd, 0x6a, - 0x7b, 0x47, 0x29, 0xfd, 0xd9, 0xe7, 0x0f, 0xf1, 0x3f, 0x11, 0x38, 0x3b, 0xad, 0x7c, 0x23, 0x11, 0x1b, 0x40, 0xdf, - 0x68, 0x5a, 0x73, 0x7e, 0x84, 0x06, 0x27, 0xff, 0xe7, 0xae, 0xad, 0xd1, 0x58, 0xbf, 0x53, 0x73, 0x69, 0x95, 0xfe, - 0xaa, 0xd6, 0xbf, 0x6e, 0xf0, 0x3b, 0xb6, 0x1d, 0x0a, 0x87, 0xa0, 0xde, 0x56, 0xc6, 0x03, 0x97, 0x1a, 0x2b, 0x8a, - 0xdf, 0xb5, 0x7d, 0x65, 0x12, 0x53, 0x8f, 0x69, 0x78, 0xaa, 0x9d, 0x48, 0x79, 0x78, 0x8f, 0x3d, 0x84, 0x1f, 0xf9, - 0x25, 0x0b, 0x1f, 0xe0, 0xd7, 0xd8, 0xac, 0xcb, 0x69, 0x92, 0x82, 0x59, 0x35, 0xe1, 0x7c, 0x16, 0x6c, 0x6d, 0x5d, - 0x5c, 0x5c, 0xf8, 0x17, 0xdb, 0x7e, 0x96, 0x9f, 0x6d, 0x75, 0xdb, 0xed, 0x36, 0x7e, 0x44, 0xcb, 0xb6, 0xce, 0x63, - 0x76, 0xf1, 0x14, 0xdc, 0x0f, 0xfb, 0xb1, 0xf5, 0xc4, 0x7a, 0xbc, 0x6d, 0xed, 0x3c, 0xb2, 0x2d, 0x52, 0x00, 0x50, - 0xb2, 0x6d, 0x5b, 0x42, 0x01, 0x84, 0x36, 0x14, 0xf7, 0x77, 0xcf, 0x94, 0x0d, 0x87, 0x97, 0x14, 0x84, 0x85, 0x04, - 0xfe, 0x5b, 0xf6, 0x89, 0xd5, 0xb7, 0xba, 0x28, 0x6b, 0x49, 0x35, 0xa2, 0x5e, 0x71, 0xbf, 0x0f, 0xa3, 0x59, 0x40, - 0x6c, 0x64, 0x16, 0x62, 0x98, 0x4c, 0x94, 0xd2, 0x14, 0x68, 0x97, 0x9e, 0xc2, 0x13, 0x66, 0xb5, 0x59, 0xf0, 0xfc, - 0xa6, 0xfb, 0x18, 0x74, 0xdc, 0x79, 0xeb, 0xe1, 0xb0, 0xdd, 0xea, 0x58, 0x9d, 0x56, 0xd7, 0x7f, 0x6c, 0x75, 0xc5, - 0xff, 0x83, 0x8c, 0xdc, 0xb6, 0x3a, 0xf0, 0xb4, 0x6d, 0xc1, 0xfb, 0xf9, 0x43, 0x91, 0x5b, 0x12, 0xd9, 0x5b, 0xfd, - 0x5d, 0xfc, 0x4d, 0x29, 0x40, 0xea, 0x73, 0x5b, 0xfc, 0x0a, 0x9e, 0xfd, 0x99, 0x59, 0xda, 0x79, 0xb2, 0xb2, 0xb8, - 0xfb, 0x78, 0x65, 0xf1, 0xf6, 0xa3, 0x95, 0xc5, 0x0f, 0x77, 0xea, 0xc5, 0x5b, 0x67, 0xa2, 0x4a, 0xcb, 0x85, 0xd0, - 0x9e, 0x46, 0xc0, 0x28, 0x97, 0x4e, 0x07, 0xe0, 0x6c, 0x5b, 0x2d, 0xfc, 0xf3, 0xb8, 0xeb, 0xea, 0x5e, 0xa7, 0xd8, - 0x4b, 0x63, 0xf9, 0xf8, 0x09, 0x60, 0xf9, 0xb2, 0xfb, 0x68, 0x88, 0xed, 0x08, 0x51, 0xf8, 0x77, 0xbe, 0xfd, 0x64, - 0x08, 0x1a, 0xc1, 0xc2, 0x7f, 0xf0, 0xdf, 0x64, 0xa7, 0x3b, 0x14, 0x2f, 0x6d, 0xac, 0xff, 0xb6, 0xf3, 0xb8, 0x80, - 0xa6, 0xf8, 0xdf, 0x2f, 0xda, 0x84, 0x46, 0x03, 0xde, 0x1c, 0xf7, 0x21, 0xd0, 0xe8, 0xc9, 0xa4, 0xeb, 0x7f, 0x7e, - 0xfe, 0xd8, 0x7f, 0x32, 0xe9, 0x3c, 0xfe, 0x56, 0xbc, 0x25, 0x40, 0xc1, 0xcf, 0xf1, 0xdf, 0xb7, 0xdb, 0xed, 0x49, - 0xab, 0xe3, 0x3f, 0x39, 0xdf, 0xf6, 0xb7, 0x93, 0xd6, 0x23, 0xff, 0x09, 0xfe, 0xab, 0x86, 0x9b, 0x64, 0x53, 0x66, - 0x5b, 0xb8, 0xde, 0x0d, 0xbf, 0xd7, 0x9c, 0xa3, 0xfb, 0xd0, 0xda, 0x79, 0xf8, 0xf2, 0x09, 0xac, 0xd1, 0xa4, 0xd3, - 0x85, 0xff, 0x5f, 0xf7, 0xf8, 0x2d, 0x12, 0x5e, 0x0e, 0x1c, 0x31, 0x4c, 0x2f, 0x56, 0x84, 0xa3, 0x0f, 0xba, 0x3d, - 0xf0, 0xfe, 0xb4, 0x2e, 0x00, 0xc2, 0xf8, 0xad, 0x01, 0x10, 0xce, 0xef, 0x16, 0x01, 0xa1, 0x5f, 0x1b, 0xf8, 0x1d, - 0x23, 0x20, 0x7f, 0x6a, 0x06, 0xb9, 0x2f, 0xd9, 0x52, 0xa0, 0xa3, 0xe9, 0xac, 0xbd, 0x65, 0xce, 0xe1, 0x97, 0xf8, - 0xe3, 0x06, 0x65, 0x0f, 0x5a, 0x73, 0x6e, 0xc6, 0x83, 0x32, 0xdc, 0xc8, 0x97, 0xf2, 0xe2, 0x43, 0xc1, 0xd7, 0x10, - 0x24, 0xbe, 0x9d, 0x20, 0xdf, 0xde, 0x8d, 0x1e, 0xf1, 0xef, 0x4c, 0x8f, 0x82, 0x1b, 0xf4, 0xa8, 0x45, 0xdc, 0x29, - 0x62, 0x40, 0x8e, 0xfe, 0x3e, 0xbd, 0x3b, 0x9c, 0xbe, 0xc5, 0xb6, 0xc5, 0xb0, 0xa8, 0xb0, 0x45, 0xce, 0xe6, 0xd3, - 0x5f, 0x73, 0x44, 0x20, 0xd2, 0xcd, 0x43, 0x5b, 0x46, 0x61, 0x66, 0xf8, 0xd1, 0x62, 0xf5, 0x72, 0x2e, 0xae, 0x34, - 0x85, 0x74, 0x1f, 0x71, 0x47, 0x47, 0x70, 0xf0, 0x06, 0x40, 0xb8, 0xc8, 0x78, 0x84, 0xbf, 0x8a, 0x05, 0xe4, 0xa6, - 0xdf, 0xcf, 0x8a, 0x79, 0xc2, 0x30, 0x9d, 0x66, 0x28, 0x3e, 0x20, 0x0b, 0x8f, 0xf2, 0xae, 0x21, 0xa6, 0xb0, 0x7f, - 0x83, 0xe9, 0xf7, 0xea, 0xec, 0x60, 0x8a, 0x71, 0x84, 0x37, 0x6c, 0x14, 0x47, 0x8e, 0xed, 0xcc, 0x60, 0x23, 0xc3, - 0x2c, 0xad, 0x5a, 0xee, 0x3b, 0xa5, 0xbd, 0xbb, 0xb6, 0xfa, 0x69, 0xa6, 0x1c, 0x3f, 0x75, 0x17, 0x1e, 0xca, 0xb8, - 0xa3, 0x2d, 0x1d, 0x03, 0x18, 0x5f, 0x95, 0xe4, 0xa8, 0x03, 0x2a, 0x63, 0xc2, 0x16, 0xd6, 0x44, 0xc7, 0xef, 0x82, - 0x77, 0x41, 0xc5, 0xf8, 0xe9, 0xb0, 0xef, 0x9d, 0xd6, 0x36, 0x58, 0x3b, 0x46, 0x37, 0x3d, 0xd0, 0x91, 0xfe, 0xa5, - 0x1f, 0xfd, 0x6b, 0x74, 0xf5, 0x0b, 0x03, 0xb6, 0xe0, 0x88, 0xcf, 0x04, 0xee, 0xb6, 0xf8, 0x44, 0x83, 0x48, 0x28, - 0xc1, 0x0b, 0x73, 0x50, 0xe6, 0x98, 0xbf, 0x4a, 0x26, 0x3e, 0x4d, 0x26, 0x7e, 0x80, 0xb0, 0xac, 0x9a, 0x70, 0x77, - 0x41, 0x67, 0x23, 0xf8, 0x23, 0x9a, 0x98, 0x68, 0x8a, 0xa1, 0xf2, 0xd0, 0xa0, 0x29, 0xbe, 0xbb, 0x35, 0x22, 0x73, - 0x4f, 0x03, 0x44, 0x04, 0x0e, 0xe5, 0xdf, 0xaa, 0x58, 0x3d, 0xc8, 0xa0, 0x16, 0x38, 0xfa, 0xf8, 0xb3, 0x2f, 0xf4, - 0x67, 0x29, 0x64, 0x26, 0x02, 0x21, 0x8d, 0xd2, 0x6a, 0xa8, 0x2a, 0x34, 0x56, 0x3c, 0xbd, 0x3a, 0x90, 0xdf, 0x3c, - 0xb0, 0x31, 0x4a, 0x4d, 0xa7, 0x13, 0xd5, 0xf7, 0xd6, 0x36, 0x41, 0x35, 0xd2, 0xaf, 0xa0, 0x52, 0x82, 0x01, 0x6a, - 0x3f, 0xbc, 0x72, 0x60, 0x49, 0x2f, 0x29, 0xb4, 0x85, 0xee, 0x1b, 0xb1, 0xf3, 0x78, 0x28, 0x55, 0x98, 0x67, 0xc9, - 0xab, 0x52, 0x2d, 0x5a, 0x9a, 0xb0, 0xe3, 0x89, 0x38, 0x01, 0xbc, 0xa0, 0x06, 0x0f, 0xd3, 0xcc, 0xee, 0x3f, 0xe8, - 0xad, 0x23, 0x3e, 0xfe, 0x24, 0xeb, 0x21, 0xf8, 0xa5, 0x7f, 0x1b, 0x3e, 0xc0, 0x1f, 0x65, 0x7d, 0x70, 0x64, 0xbb, - 0x3e, 0x29, 0x80, 0x07, 0xd5, 0x2f, 0xb3, 0xa2, 0xf4, 0xdb, 0x04, 0x5d, 0xed, 0xdd, 0x55, 0x69, 0x4b, 0x05, 0xdd, - 0xdd, 0xa9, 0x14, 0x34, 0x3c, 0x1b, 0x12, 0x19, 0x94, 0x45, 0xd7, 0xdf, 0x31, 0xc4, 0xfe, 0x79, 0x0b, 0xff, 0xd6, - 0x04, 0xff, 0x43, 0x68, 0xa0, 0x24, 0xff, 0x6b, 0x68, 0xbe, 0x2d, 0x94, 0x0c, 0xf4, 0xfb, 0x81, 0xc4, 0xb2, 0x10, - 0xc9, 0xf5, 0x6d, 0xb0, 0xe2, 0xc0, 0x4c, 0x24, 0x63, 0xd8, 0x9e, 0x11, 0x5b, 0x13, 0xbb, 0x52, 0x46, 0x8e, 0x9e, - 0x43, 0x5f, 0x47, 0x7f, 0xc6, 0x7c, 0x55, 0x9d, 0x57, 0x93, 0x12, 0x2b, 0xa6, 0xc0, 0x7d, 0xdd, 0x38, 0x94, 0xeb, - 0x89, 0x3c, 0x6f, 0xfd, 0x1d, 0x94, 0xf5, 0x0c, 0x2d, 0x13, 0xc2, 0x5d, 0x43, 0x44, 0x30, 0xfa, 0xd4, 0x2a, 0x4d, - 0xf2, 0x6a, 0x54, 0x36, 0xe7, 0x07, 0xb3, 0x06, 0x7f, 0x97, 0xb2, 0xba, 0xe5, 0x23, 0xaf, 0xef, 0x62, 0xca, 0xc5, - 0x28, 0xce, 0xe9, 0x56, 0xb8, 0x02, 0xbd, 0x16, 0x78, 0xad, 0xa8, 0x44, 0x52, 0x82, 0x15, 0x03, 0x1b, 0x8b, 0xec, - 0x40, 0x26, 0x06, 0x9a, 0xdf, 0x1a, 0x37, 0xaf, 0xed, 0x8e, 0x44, 0x4e, 0x20, 0xfe, 0x16, 0x83, 0x2d, 0xe8, 0x63, - 0x83, 0xb4, 0x5d, 0xbb, 0x4b, 0xc8, 0x06, 0x43, 0x5c, 0xab, 0x1f, 0xd7, 0x32, 0x05, 0x90, 0x6d, 0x12, 0x5a, 0x8f, - 0x4b, 0x24, 0x74, 0x25, 0x9d, 0x4e, 0x59, 0xc4, 0xfd, 0x28, 0xa5, 0xfc, 0x2d, 0xc7, 0x10, 0x53, 0x5e, 0x87, 0x6d, - 0xbb, 0x25, 0xc8, 0x46, 0xe3, 0xd7, 0xc7, 0xe4, 0xee, 0x86, 0x42, 0xfd, 0xe5, 0xab, 0x7a, 0x2e, 0xf6, 0xa4, 0xdb, - 0x7f, 0x77, 0xb0, 0x67, 0x89, 0x4d, 0xb9, 0xbb, 0x05, 0xaf, 0xbb, 0xe4, 0xc1, 0x8b, 0x54, 0x96, 0x50, 0xa4, 0xb2, - 0x58, 0x22, 0x01, 0x4e, 0xe4, 0x2e, 0x6f, 0x09, 0xb4, 0x6d, 0x8b, 0xa5, 0x43, 0x11, 0x7a, 0x9c, 0x82, 0x97, 0x13, - 0xe3, 0xf7, 0xe9, 0xb6, 0xb0, 0x6b, 0x0b, 0x17, 0xcc, 0x56, 0x59, 0x41, 0xca, 0xae, 0xe1, 0xa9, 0x0a, 0x54, 0x82, - 0x35, 0xc2, 0x54, 0x82, 0x90, 0x1c, 0x4a, 0xe7, 0x25, 0x2f, 0xb7, 0x2e, 0xe6, 0xa7, 0x53, 0x90, 0x93, 0x2a, 0xa9, - 0xe7, 0xa3, 0xec, 0xb0, 0x4b, 0x53, 0xf5, 0x4f, 0x4a, 0x19, 0x49, 0x55, 0xdf, 0x0e, 0x6f, 0xfc, 0xce, 0xaa, 0xc0, - 0x5e, 0xea, 0x05, 0xcc, 0x49, 0x99, 0x6c, 0x1b, 0x39, 0x29, 0x46, 0x5d, 0x09, 0xa8, 0x6f, 0xf7, 0x4f, 0x82, 0x99, - 0x1c, 0xef, 0x75, 0xb6, 0xf4, 0x9b, 0xad, 0x5a, 0x4e, 0x0e, 0x28, 0xbf, 0x5c, 0xdc, 0xeb, 0x90, 0x00, 0xc3, 0x0a, - 0x02, 0x4c, 0xd2, 0x04, 0xb0, 0xe8, 0xe8, 0xdb, 0xde, 0x69, 0xab, 0xb4, 0x5d, 0x28, 0xc3, 0x0d, 0x29, 0xba, 0x18, - 0x93, 0xd4, 0xc2, 0xbf, 0x93, 0x4e, 0x7f, 0x37, 0x92, 0xc6, 0x25, 0x0a, 0x8f, 0x02, 0xa4, 0x07, 0x74, 0x46, 0x0b, - 0xce, 0x8f, 0xb3, 0xad, 0x0b, 0x76, 0xda, 0x8a, 0x66, 0x71, 0x15, 0x6b, 0x45, 0x53, 0x43, 0x4f, 0x99, 0x55, 0x33, - 0xe1, 0x63, 0xd4, 0x40, 0x92, 0x04, 0x77, 0x29, 0x03, 0xb9, 0x64, 0xa1, 0x03, 0x0b, 0x01, 0x85, 0x49, 0xae, 0xab, - 0x80, 0xaf, 0xd4, 0xb8, 0xa5, 0xdd, 0xff, 0xcb, 0x3f, 0xff, 0x6f, 0x19, 0xc3, 0x05, 0xaa, 0x74, 0xd4, 0x58, 0x0d, - 0x42, 0x97, 0xbb, 0x98, 0x02, 0x55, 0x9d, 0xf2, 0xb2, 0xcb, 0xd6, 0x59, 0x1e, 0x8f, 0x5a, 0x93, 0x28, 0x19, 0x03, - 0x60, 0x6b, 0x09, 0x64, 0x26, 0x48, 0x48, 0xa8, 0xeb, 0x45, 0xc8, 0x82, 0xbf, 0x29, 0x11, 0x5b, 0x25, 0xc0, 0xd3, - 0x6e, 0x35, 0xd3, 0xb2, 0xab, 0x0d, 0x55, 0x4b, 0xcd, 0x56, 0x3f, 0x5c, 0xa6, 0x84, 0x5a, 0x2d, 0x2f, 0x1b, 0x5a, - 0xea, 0xc3, 0xa8, 0x7f, 0xff, 0x97, 0x7f, 0xf8, 0x1f, 0xea, 0x15, 0xcf, 0x98, 0xfe, 0xf2, 0x4f, 0x7f, 0x87, 0x29, - 0xd0, 0x96, 0x3e, 0x87, 0x22, 0x39, 0x61, 0x55, 0x87, 0x50, 0x42, 0x60, 0x58, 0x95, 0xd3, 0x57, 0xcf, 0xdf, 0xde, - 0xa7, 0x09, 0x69, 0xb3, 0x49, 0xe8, 0x68, 0xd3, 0x96, 0x15, 0x8f, 0xd4, 0x48, 0x4e, 0xbc, 0x08, 0x95, 0x48, 0xef, - 0x3b, 0x25, 0x47, 0xf9, 0x7a, 0x35, 0x16, 0x2a, 0x42, 0x88, 0x25, 0x65, 0x55, 0x6e, 0x61, 0xe8, 0x7e, 0x81, 0xaf, - 0x41, 0xd7, 0x28, 0xa6, 0xc5, 0xab, 0xf5, 0xe9, 0xfd, 0x34, 0x07, 0xf8, 0xc7, 0x48, 0x71, 0x11, 0x87, 0xa4, 0x63, - 0xe9, 0x16, 0xda, 0x7c, 0xc9, 0x55, 0x49, 0xa3, 0x08, 0x47, 0xf1, 0xe1, 0x93, 0xbf, 0x29, 0xff, 0x38, 0x45, 0xcb, - 0xca, 0x72, 0xa6, 0xd1, 0xa5, 0x74, 0x1f, 0x1f, 0xb5, 0xdb, 0xb3, 0x4b, 0x77, 0x51, 0xcd, 0xe0, 0xad, 0x9b, 0x8c, - 0x62, 0x97, 0xe6, 0x80, 0x74, 0x9e, 0xad, 0xc3, 0xa4, 0xe0, 0x31, 0xb5, 0x31, 0xaa, 0x56, 0x96, 0x7f, 0x58, 0x50, - 0xa4, 0x2e, 0xfe, 0x05, 0xcf, 0x9d, 0x65, 0x50, 0x13, 0x4a, 0x0c, 0x2c, 0x16, 0x46, 0xaf, 0xae, 0xe8, 0x35, 0xe9, - 0x2c, 0xa7, 0x0d, 0x99, 0xe7, 0xe6, 0xe6, 0x89, 0xf7, 0x43, 0x3c, 0xc3, 0x9e, 0x74, 0xbc, 0x49, 0x77, 0xa1, 0x87, - 0xe7, 0x3c, 0x9b, 0x9a, 0x07, 0xe5, 0x2c, 0x62, 0x43, 0x36, 0x56, 0xc1, 0x60, 0x59, 0x2f, 0x0e, 0xc1, 0xcb, 0xc9, - 0xf6, 0x8a, 0xb9, 0x24, 0x48, 0x74, 0x40, 0x0e, 0xf0, 0x7c, 0x86, 0x1b, 0x10, 0xe8, 0x9f, 0x45, 0x3c, 0x20, 0x7e, - 0xed, 0x99, 0xc7, 0xed, 0x11, 0x4a, 0x99, 0x6c, 0x61, 0xc0, 0xd3, 0x13, 0x4d, 0x31, 0x2c, 0x5b, 0x4f, 0xdb, 0x2a, - 0x7d, 0xea, 0x6e, 0x0e, 0x25, 0xa2, 0x3a, 0xdf, 0xca, 0x53, 0xec, 0xa7, 0xb5, 0x70, 0x88, 0x54, 0x31, 0x5d, 0xd7, - 0x5b, 0x59, 0x2f, 0x34, 0xb5, 0xa8, 0xfd, 0x16, 0x0c, 0x30, 0x02, 0xd3, 0x6e, 0xb6, 0xa2, 0x42, 0x6c, 0xf5, 0x34, - 0xfc, 0x56, 0xbb, 0x3e, 0xd1, 0x6c, 0x46, 0x0d, 0x5d, 0x60, 0x62, 0x32, 0x58, 0x51, 0x76, 0x50, 0x86, 0x86, 0x48, - 0x88, 0x90, 0x6d, 0xe4, 0x46, 0x10, 0x4f, 0x32, 0x55, 0x02, 0x7f, 0x72, 0xa2, 0xff, 0xff, 0x00, 0x69, 0x5b, 0x88, - 0x58, 0x18, 0x7f, 0x00, 0x00}; + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc5, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3, + 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x10, 0x4a, 0xad, 0x02, 0xae, 0x40, 0x88, 0xa4, 0x6a, 0x33, 0x28, 0x90, 0x57, 0xb5, + 0xd8, 0x55, 0x76, 0x6d, 0x2e, 0xa9, 0xec, 0x6b, 0xcb, 0xb4, 0x04, 0x91, 0x49, 0x11, 0x2e, 0x10, 0xa0, 0x81, 0xa4, + 0x16, 0x53, 0xe8, 0x33, 0x4f, 0xf3, 0xd4, 0xe7, 0xcc, 0xd6, 0x0f, 0xfd, 0x30, 0x7d, 0xba, 0x1f, 0xe6, 0x23, 0xe6, + 0xb9, 0x3f, 0xe5, 0xfe, 0xc0, 0xf4, 0x27, 0x4c, 0x44, 0xe4, 0x82, 0x04, 0x17, 0x49, 0x5e, 0xba, 0xe7, 0xd8, 0x2a, + 0x12, 0xb9, 0x46, 0x44, 0x46, 0xc6, 0x96, 0x91, 0xe0, 0xde, 0xc6, 0x30, 0x1b, 0xf0, 0xab, 0x29, 0xb3, 0xc6, 0x7c, + 0x92, 0x74, 0xf7, 0xe4, 0xbf, 0x2c, 0x1a, 0x76, 0xf7, 0x92, 0x38, 0xfd, 0x64, 0xe5, 0x2c, 0x09, 0xe3, 0x41, 0x96, + 0x5a, 0xe3, 0x9c, 0x8d, 0xc2, 0x61, 0xc4, 0xa3, 0x20, 0x9e, 0x44, 0x67, 0xcc, 0xda, 0xe9, 0xee, 0x4d, 0x18, 0x8f, + 0xac, 0xc1, 0x38, 0xca, 0x0b, 0xc6, 0xc3, 0x8f, 0x87, 0x9f, 0x37, 0x9e, 0x74, 0xf7, 0x8a, 0x41, 0x1e, 0x4f, 0xb9, + 0x85, 0x43, 0x86, 0x93, 0x6c, 0x38, 0x4b, 0x58, 0xf7, 0x3c, 0xca, 0xad, 0x17, 0x3c, 0x7c, 0x77, 0xfa, 0x13, 0x1b, + 0x70, 0x7f, 0xc8, 0x46, 0x71, 0xca, 0xde, 0xe7, 0xd9, 0x94, 0xe5, 0xfc, 0xca, 0x3b, 0x58, 0x5d, 0x11, 0xb3, 0xc2, + 0x7b, 0xa6, 0xab, 0xce, 0x18, 0x7f, 0x77, 0x91, 0xaa, 0x3e, 0xcf, 0x99, 0x98, 0x24, 0xcb, 0x0b, 0xaf, 0x58, 0xd3, + 0xe6, 0xe0, 0x6a, 0x72, 0x9a, 0x25, 0x85, 0xf7, 0x49, 0xd7, 0x4f, 0xf3, 0x8c, 0x67, 0x08, 0x96, 0x3f, 0x8e, 0x0a, + 0xa3, 0xa5, 0xf7, 0x6e, 0x45, 0x93, 0xa9, 0xac, 0x7c, 0x55, 0xbc, 0x48, 0x67, 0x13, 0x96, 0x47, 0xa7, 0x09, 0xf3, + 0x72, 0x1e, 0x3a, 0xdc, 0x63, 0x5e, 0xec, 0x86, 0x5d, 0x66, 0xc5, 0xa9, 0xc5, 0x7b, 0x2f, 0x38, 0x95, 0xcc, 0x99, + 0x6e, 0x15, 0x6c, 0x34, 0x3d, 0x20, 0xd7, 0x28, 0x3e, 0x9b, 0xe9, 0xe7, 0x8b, 0x3c, 0xe6, 0xea, 0xfb, 0x79, 0x94, + 0xcc, 0x58, 0x10, 0x97, 0x6e, 0xc0, 0x8f, 0x58, 0x3f, 0x8c, 0xbd, 0x4f, 0x34, 0x28, 0x0c, 0x39, 0x1f, 0x65, 0xb9, + 0x83, 0xb4, 0x8a, 0x71, 0x6c, 0x76, 0x7d, 0xed, 0xb0, 0x70, 0x5e, 0xba, 0xee, 0x27, 0xee, 0x0f, 0xa2, 0x24, 0x71, + 0x70, 0xe2, 0xad, 0xad, 0x1c, 0x67, 0x8c, 0x3d, 0x76, 0x14, 0xf7, 0xdd, 0x4e, 0x3c, 0x72, 0x0a, 0xee, 0x56, 0xfd, + 0xb2, 0x91, 0x55, 0x70, 0x87, 0xb9, 0xee, 0xbb, 0xf5, 0x7d, 0x72, 0xc6, 0x67, 0x39, 0xc0, 0x5e, 0x7a, 0xef, 0xd4, + 0xcc, 0x07, 0x58, 0xff, 0x8c, 0x3a, 0x76, 0x00, 0xf6, 0x82, 0x5b, 0x9f, 0x87, 0x17, 0x71, 0x3a, 0xcc, 0x2e, 0xfc, + 0x83, 0x71, 0x04, 0x1f, 0x1f, 0xb2, 0x8c, 0x6f, 0x6d, 0x39, 0xe7, 0x59, 0x3c, 0xb4, 0x9a, 0x61, 0x68, 0x56, 0x5e, + 0x3d, 0x3b, 0x38, 0xb8, 0xbe, 0x5e, 0x28, 0xf0, 0xd3, 0x88, 0xc7, 0xe7, 0x4c, 0x74, 0x06, 0x00, 0x6c, 0xf8, 0x9c, + 0x72, 0x36, 0x3c, 0xe0, 0x57, 0x09, 0x94, 0x32, 0xc6, 0x0b, 0x1b, 0x70, 0x7c, 0x9e, 0x0d, 0x80, 0x6c, 0xa9, 0x41, + 0x78, 0x68, 0x9a, 0xb3, 0x69, 0x12, 0x0d, 0x18, 0xd6, 0xc3, 0x48, 0x55, 0x8f, 0xaa, 0x91, 0xf7, 0x6d, 0x28, 0x96, + 0xd7, 0x71, 0xbd, 0x94, 0x87, 0x29, 0xbb, 0xb0, 0xde, 0x44, 0xd3, 0xce, 0x20, 0x89, 0x8a, 0xc2, 0xca, 0xf8, 0x9c, + 0x50, 0xc8, 0x67, 0x03, 0x60, 0x10, 0x42, 0x70, 0x0e, 0x64, 0xe2, 0xe3, 0xb8, 0xf0, 0x8f, 0x37, 0x07, 0x45, 0xf1, + 0x81, 0x15, 0xb3, 0x84, 0x6f, 0x86, 0xb0, 0x16, 0x6c, 0x23, 0x0c, 0xbf, 0x75, 0xf9, 0x38, 0xcf, 0x2e, 0xac, 0x17, + 0x79, 0x0e, 0xcd, 0x6d, 0x98, 0x52, 0x34, 0xb0, 0xe2, 0xc2, 0x4a, 0x33, 0x6e, 0xe9, 0xc1, 0x70, 0x01, 0x7d, 0xeb, + 0x63, 0xc1, 0xac, 0x93, 0x59, 0x5a, 0x44, 0x23, 0x06, 0x4d, 0x4f, 0xac, 0x2c, 0xb7, 0x4e, 0x60, 0xd0, 0x13, 0x58, + 0xb2, 0x82, 0xc3, 0xae, 0xf1, 0x6d, 0xb7, 0x43, 0x73, 0x41, 0xe1, 0x21, 0xbb, 0xe4, 0x21, 0x2f, 0x81, 0x31, 0x61, + 0x55, 0x14, 0x1a, 0x8e, 0x3b, 0x4f, 0xa0, 0x00, 0xc0, 0x26, 0x96, 0x75, 0xcc, 0xc6, 0x7a, 0x71, 0x3e, 0xdf, 0xda, + 0xd2, 0xb4, 0x46, 0xc2, 0x43, 0xdb, 0x62, 0xa1, 0xad, 0x27, 0x10, 0xaf, 0x91, 0xc8, 0xf5, 0xb8, 0x2f, 0xc9, 0x77, + 0x70, 0x95, 0x0e, 0xea, 0x63, 0x43, 0x65, 0xc9, 0xb3, 0x03, 0x9e, 0xc7, 0xe9, 0x19, 0x00, 0xa1, 0xd8, 0xc0, 0x68, + 0x52, 0x96, 0x62, 0xf1, 0xdf, 0x03, 0xd4, 0x61, 0x17, 0x47, 0xcf, 0xb8, 0x63, 0x17, 0xd4, 0xc3, 0x06, 0x40, 0x80, + 0xf4, 0xc0, 0x60, 0xbc, 0xc7, 0x03, 0xbe, 0x6d, 0xdb, 0xde, 0xb7, 0xae, 0x77, 0x81, 0x1c, 0xe4, 0xfb, 0x3e, 0xb1, + 0xaf, 0xe8, 0x1c, 0x87, 0x2d, 0x04, 0xda, 0x4f, 0x58, 0x7a, 0xc6, 0xc7, 0x3d, 0x7e, 0xd4, 0xec, 0x07, 0x0c, 0xa0, + 0x1a, 0xce, 0x06, 0xcc, 0x41, 0x7e, 0xf4, 0x0a, 0xdc, 0x3e, 0xdb, 0x0e, 0x4c, 0x81, 0x0b, 0xb3, 0x41, 0x38, 0xd6, + 0x96, 0xc6, 0x55, 0xb0, 0x29, 0xc0, 0x90, 0xcf, 0x6d, 0xd8, 0x61, 0xa7, 0x2c, 0x37, 0xe0, 0xd0, 0xcd, 0x3a, 0xb5, + 0x15, 0x9c, 0xc1, 0x0a, 0x41, 0x3f, 0x6b, 0x34, 0x4b, 0x07, 0x3c, 0x06, 0xc1, 0x65, 0x6f, 0x03, 0xb8, 0x62, 0xe5, + 0xf4, 0xc2, 0xd9, 0x6e, 0xe9, 0x3a, 0xb1, 0xbb, 0xcd, 0x8f, 0x8a, 0xed, 0x56, 0xdf, 0x43, 0x28, 0x35, 0xf1, 0x25, + 0xe2, 0x31, 0x20, 0x58, 0x7a, 0x1f, 0xb9, 0xde, 0x9e, 0x9f, 0xf7, 0xb8, 0xbf, 0xcc, 0xc7, 0x21, 0xf3, 0x27, 0xd1, + 0x14, 0xb1, 0xe1, 0xc4, 0x03, 0x51, 0x3a, 0x40, 0xe8, 0x6a, 0xeb, 0x82, 0x14, 0xf3, 0x2b, 0x16, 0x70, 0x81, 0x20, + 0xb0, 0x67, 0x5f, 0x44, 0x83, 0x31, 0x6c, 0xf1, 0x8a, 0x70, 0x43, 0xb5, 0x1d, 0x06, 0x39, 0x8b, 0x38, 0x7b, 0x91, + 0x30, 0x7c, 0xc2, 0x15, 0x80, 0x9e, 0xb6, 0xeb, 0x15, 0x6a, 0xdf, 0x25, 0x31, 0x7f, 0x9b, 0xc1, 0x3c, 0x1d, 0xc1, + 0x24, 0xc0, 0xc5, 0xc5, 0xd6, 0x56, 0x8c, 0x2c, 0xb2, 0xcf, 0x61, 0xb5, 0x4e, 0x67, 0x9c, 0x01, 0xbd, 0xb0, 0x85, + 0x0d, 0xd4, 0xf6, 0x62, 0x9f, 0x03, 0x11, 0x9f, 0x65, 0x29, 0x87, 0xe1, 0x00, 0x5e, 0xcd, 0x41, 0x7e, 0x34, 0x9d, + 0xb2, 0x74, 0xf8, 0x6c, 0x1c, 0x27, 0x43, 0xa0, 0x46, 0x09, 0xf8, 0x26, 0x3c, 0x04, 0x3c, 0x01, 0x99, 0xe0, 0x66, + 0x8c, 0x68, 0xf9, 0x90, 0x91, 0x59, 0x68, 0xdb, 0x1d, 0x94, 0x40, 0x12, 0x0b, 0x94, 0x41, 0xb4, 0x70, 0x1f, 0x40, + 0xf4, 0x17, 0x2e, 0xdb, 0x0e, 0x63, 0xbd, 0x8c, 0x92, 0xc0, 0xef, 0x51, 0xd2, 0x00, 0xfd, 0x81, 0x10, 0xbc, 0x83, + 0x82, 0xeb, 0x4b, 0x29, 0x75, 0x22, 0xae, 0x30, 0x04, 0x02, 0x0c, 0x50, 0x82, 0x48, 0x1a, 0xbc, 0xcf, 0x92, 0xab, + 0x51, 0x9c, 0x24, 0x07, 0xb3, 0xe9, 0x34, 0xcb, 0xb9, 0xf7, 0x55, 0x38, 0xe7, 0x59, 0x85, 0x2b, 0x6d, 0xf2, 0xe2, + 0x22, 0xe6, 0x48, 0x50, 0x77, 0x3e, 0x88, 0x60, 0xa9, 0x9f, 0x66, 0x59, 0xc2, 0xa2, 0x14, 0xd0, 0xe0, 0x3d, 0xdb, + 0x0e, 0xd2, 0x59, 0x92, 0x74, 0x4e, 0x61, 0xd8, 0x4f, 0x1d, 0xaa, 0x16, 0x12, 0x3f, 0xa0, 0xef, 0xfb, 0x79, 0x1e, + 0x5d, 0x41, 0x43, 0x6c, 0x03, 0xec, 0x05, 0xab, 0xf5, 0xe5, 0xc1, 0xbb, 0xb7, 0xbe, 0x60, 0xfc, 0x78, 0x74, 0x05, + 0x80, 0x96, 0x95, 0xd4, 0x1c, 0xe5, 0xd9, 0x64, 0x61, 0x6a, 0xa4, 0x43, 0x1c, 0xf2, 0xce, 0x1a, 0x10, 0x62, 0x1a, + 0x19, 0x56, 0x89, 0x9b, 0x10, 0xbc, 0x25, 0x7e, 0x96, 0x95, 0xb8, 0x07, 0x7a, 0xf8, 0x25, 0x10, 0xc5, 0x30, 0xe5, + 0x2d, 0xd0, 0xe6, 0x57, 0xf3, 0x38, 0x24, 0x38, 0xa7, 0xa8, 0x7f, 0x11, 0xc6, 0x41, 0x04, 0xb3, 0xcf, 0xc5, 0x80, + 0xa5, 0x82, 0x38, 0x2e, 0x4b, 0x6f, 0xac, 0x99, 0x18, 0x25, 0x1e, 0x0a, 0x14, 0x16, 0x86, 0xa0, 0x60, 0x38, 0x3c, + 0xb8, 0xde, 0xd7, 0xe1, 0x3c, 0x52, 0xf8, 0xa0, 0x86, 0xc2, 0xfd, 0x15, 0x08, 0x39, 0x81, 0x9a, 0xec, 0x1c, 0xf4, + 0x20, 0xc0, 0xf9, 0x95, 0x07, 0xfa, 0x3f, 0x41, 0x28, 0x36, 0x5a, 0x1e, 0x68, 0xd0, 0x67, 0xe3, 0x28, 0x3d, 0x63, + 0xc3, 0x60, 0xcc, 0x4b, 0x29, 0x79, 0xf7, 0x2d, 0x58, 0x63, 0x60, 0xa7, 0xc2, 0x7a, 0x79, 0xf8, 0xe6, 0xb5, 0x5c, + 0xb9, 0x9a, 0x30, 0x86, 0x45, 0x9a, 0x81, 0x5a, 0x05, 0xb1, 0x2d, 0xc5, 0xf1, 0x0b, 0x2d, 0xbd, 0x45, 0x49, 0x5c, + 0x7c, 0x9c, 0x82, 0x89, 0xc1, 0xde, 0xc3, 0x30, 0x30, 0x7d, 0x08, 0x53, 0x51, 0x39, 0xcc, 0x27, 0x2a, 0x86, 0xba, + 0x08, 0x3a, 0x0b, 0x4c, 0xc5, 0x63, 0xe6, 0xb8, 0x25, 0xb0, 0x2a, 0x8f, 0x07, 0x56, 0x34, 0x1c, 0xbe, 0x4a, 0x63, + 0x1e, 0x47, 0x49, 0xfc, 0x0b, 0x51, 0x72, 0x8e, 0x3c, 0xc6, 0x3a, 0x72, 0x11, 0x00, 0x77, 0xea, 0x91, 0xb8, 0x4a, + 0xc8, 0x6e, 0x10, 0x31, 0x84, 0xb4, 0x4c, 0xc2, 0xa3, 0xbe, 0x04, 0x2f, 0xf1, 0xa7, 0xb3, 0x62, 0x8c, 0x84, 0x95, + 0x03, 0xa3, 0x20, 0xcf, 0x4e, 0x0b, 0x96, 0x9f, 0xb3, 0xa1, 0xe6, 0x80, 0x02, 0xb0, 0xa2, 0xe6, 0x60, 0xbc, 0xd0, + 0x8c, 0x8e, 0xd2, 0xa1, 0x1c, 0x86, 0xea, 0x98, 0x62, 0x96, 0x49, 0x66, 0xd6, 0x16, 0x8e, 0x96, 0x02, 0x8e, 0x30, + 0x2a, 0xa4, 0x24, 0x28, 0x42, 0x85, 0xe1, 0x18, 0xa4, 0x10, 0x73, 0x6b, 0xdb, 0x5c, 0x69, 0xb2, 0x17, 0x33, 0x52, + 0x09, 0x05, 0x74, 0x84, 0x8d, 0x4c, 0x90, 0x16, 0x2e, 0xec, 0x2a, 0x90, 0xf2, 0x12, 0x5c, 0x21, 0x45, 0x94, 0x99, + 0x83, 0x0c, 0x10, 0x7e, 0x4d, 0xba, 0x90, 0xf9, 0xd8, 0x82, 0x21, 0x1b, 0xf8, 0x7a, 0xe5, 0x81, 0xb0, 0x12, 0xef, + 0x0a, 0x11, 0x6f, 0x0d, 0xd8, 0xa4, 0x8b, 0x00, 0x30, 0x6f, 0x83, 0xf9, 0x69, 0xb6, 0x3f, 0x18, 0xb0, 0xa2, 0xc8, + 0xf2, 0xad, 0xad, 0x0d, 0x6a, 0xbf, 0xce, 0xd0, 0x02, 0x4a, 0xba, 0x5a, 0xd6, 0xd9, 0x05, 0x69, 0x70, 0x53, 0xad, + 0x28, 0x9d, 0x1e, 0xd8, 0xc7, 0xc7, 0x20, 0xb3, 0x3d, 0x49, 0x06, 0xa0, 0xfa, 0xb2, 0xe1, 0x27, 0xec, 0x99, 0x3a, + 0x65, 0x56, 0xda, 0x97, 0x4e, 0x1d, 0x24, 0x0f, 0x86, 0x75, 0x4b, 0x63, 0x41, 0x57, 0x0e, 0x8d, 0xab, 0x21, 0x15, + 0xe4, 0xfc, 0x8c, 0x54, 0xb6, 0xb1, 0x8c, 0x60, 0xb5, 0x95, 0x1e, 0x91, 0x5e, 0x61, 0x93, 0x13, 0xa0, 0x47, 0xbc, + 0xdf, 0x91, 0xf5, 0x61, 0x21, 0x28, 0x97, 0xb3, 0x9f, 0x67, 0xac, 0xe0, 0x82, 0x75, 0x61, 0xdc, 0x1c, 0xc6, 0x2d, + 0x97, 0xac, 0xc3, 0x9a, 0xed, 0xb8, 0x0a, 0xb6, 0x77, 0x53, 0xd4, 0x63, 0x05, 0x72, 0xf2, 0xcd, 0xec, 0x44, 0xf6, + 0x84, 0x7b, 0x7d, 0xfd, 0xb5, 0x1a, 0xa4, 0x5a, 0x4a, 0x6d, 0x03, 0x2d, 0xac, 0x89, 0xad, 0x9a, 0x0c, 0x6d, 0x57, + 0x2a, 0xd4, 0x8d, 0x56, 0xa7, 0xc6, 0x07, 0xb0, 0xe7, 0x9a, 0x9a, 0xa5, 0x2b, 0x63, 0xfb, 0xbd, 0xa2, 0xe9, 0x3b, + 0x31, 0x32, 0x59, 0xa3, 0xfc, 0x76, 0xee, 0x51, 0x3b, 0x1e, 0xda, 0x2e, 0xd5, 0x55, 0x82, 0x61, 0x56, 0x17, 0x0c, + 0x8b, 0x50, 0x4f, 0x75, 0x17, 0x5b, 0x33, 0x15, 0x0f, 0xd5, 0x5a, 0x2b, 0x07, 0x82, 0x85, 0x47, 0x60, 0x9c, 0xac, + 0xf4, 0x0f, 0xde, 0x46, 0x13, 0x86, 0x14, 0xf5, 0xd6, 0x35, 0x90, 0x0e, 0x04, 0x34, 0xe9, 0x2f, 0xaa, 0x37, 0xe6, + 0x0a, 0xab, 0xa9, 0xbe, 0xbf, 0x62, 0xb0, 0x22, 0xc0, 0xbe, 0x2e, 0x57, 0x2c, 0x11, 0xe9, 0x4d, 0xc9, 0xce, 0x8a, + 0x3e, 0xa2, 0x4c, 0xac, 0x09, 0x29, 0x78, 0x40, 0x1e, 0x96, 0x7f, 0x61, 0xe1, 0x54, 0x2b, 0x85, 0x23, 0x43, 0x99, + 0x02, 0x74, 0x26, 0x25, 0x00, 0xe2, 0x92, 0x3e, 0x6b, 0x1b, 0x0b, 0xc9, 0x76, 0x80, 0x7c, 0xe0, 0x8f, 0x92, 0x88, + 0x3b, 0xad, 0x9d, 0xa6, 0x0b, 0x7c, 0x08, 0x42, 0x1c, 0x74, 0x04, 0x98, 0xf7, 0x15, 0x2a, 0x1c, 0x51, 0x89, 0x5d, + 0xe6, 0x83, 0x51, 0x34, 0x8e, 0x47, 0xdc, 0x49, 0x90, 0x79, 0xdc, 0x92, 0x25, 0xa0, 0x64, 0xf4, 0xbe, 0x02, 0x65, + 0xc1, 0x84, 0x74, 0x11, 0xd5, 0x4a, 0xa0, 0x31, 0x05, 0x29, 0x49, 0x29, 0xd2, 0x82, 0x0a, 0x02, 0x43, 0xa8, 0x74, + 0x14, 0x47, 0x81, 0x7e, 0x8b, 0x7b, 0x62, 0xd0, 0x60, 0xc9, 0xa2, 0x8c, 0x7b, 0xf1, 0x72, 0x21, 0xa8, 0x61, 0x9f, + 0x67, 0xaf, 0xb3, 0x0b, 0x96, 0x3f, 0x8b, 0x10, 0xf6, 0x40, 0x74, 0x2f, 0x41, 0xd2, 0x93, 0x40, 0xe7, 0x1d, 0xc5, + 0x2b, 0xe7, 0x84, 0x34, 0x2c, 0xc4, 0x24, 0x46, 0x45, 0x08, 0x76, 0x0b, 0xd1, 0x3e, 0xc5, 0x2d, 0x45, 0x7b, 0x0f, + 0x55, 0x09, 0xd7, 0xbc, 0xb5, 0xff, 0xba, 0xce, 0x5b, 0x30, 0xc2, 0x54, 0x71, 0x6b, 0x7d, 0xc7, 0x82, 0x7b, 0x21, + 0x74, 0xb3, 0x23, 0x79, 0xcb, 0x50, 0x66, 0xa0, 0x3f, 0xae, 0xaf, 0x2b, 0x23, 0x1d, 0x94, 0xa9, 0x96, 0xe6, 0x08, + 0x81, 0xd8, 0x12, 0x6e, 0x09, 0xca, 0x08, 0x0d, 0xaf, 0x3c, 0x4b, 0x12, 0x43, 0x17, 0x79, 0x71, 0xc7, 0x59, 0x50, + 0x47, 0x00, 0xc5, 0xa4, 0xa6, 0x91, 0x7a, 0x2c, 0xd0, 0x15, 0xa8, 0x94, 0x94, 0x36, 0xf2, 0xaa, 0xb5, 0x11, 0x10, + 0xa7, 0x43, 0x96, 0x0b, 0x07, 0x4d, 0xea, 0x50, 0x98, 0x30, 0x05, 0x86, 0x66, 0x43, 0xf4, 0x1c, 0x24, 0x02, 0x60, + 0x9e, 0xf8, 0xe3, 0xac, 0xe0, 0xba, 0xce, 0x84, 0x3e, 0xbe, 0xbe, 0x8e, 0x85, 0xbf, 0x88, 0x0c, 0x90, 0xb3, 0x49, + 0x76, 0xce, 0x56, 0x40, 0xdd, 0x51, 0x83, 0x99, 0x20, 0x1b, 0xc3, 0x80, 0x12, 0x05, 0xd5, 0x32, 0x4d, 0x62, 0xb0, + 0xf4, 0x75, 0x03, 0x1f, 0x0c, 0x3a, 0x76, 0x89, 0x32, 0xc2, 0xed, 0x76, 0xbb, 0x4d, 0xaf, 0xe5, 0x96, 0x82, 0xe0, + 0xf3, 0x25, 0x8a, 0xde, 0xa0, 0x1f, 0xa5, 0x09, 0xbe, 0x4a, 0x16, 0x30, 0xd7, 0x50, 0x8a, 0xc2, 0x4f, 0x62, 0x9e, + 0x14, 0xc4, 0xae, 0x37, 0x84, 0x41, 0x39, 0x53, 0x82, 0x1b, 0x4d, 0x5c, 0xb1, 0x6d, 0x3f, 0x68, 0xb2, 0x69, 0x76, + 0x52, 0x3b, 0x4c, 0x2d, 0x8c, 0x5c, 0xf3, 0x42, 0x7b, 0xc0, 0xe6, 0xf2, 0x90, 0x4d, 0x8f, 0xd5, 0xc0, 0xeb, 0x00, + 0xa1, 0xf0, 0x74, 0x9d, 0x25, 0x94, 0xaa, 0xce, 0x52, 0x88, 0xeb, 0x0d, 0xf4, 0x51, 0x81, 0xb9, 0x8a, 0x04, 0x07, + 0x52, 0x20, 0x30, 0xf4, 0xc8, 0xc4, 0x7a, 0x3d, 0x83, 0xe5, 0x39, 0x8d, 0x06, 0x9f, 0x34, 0xb8, 0x15, 0xef, 0x2d, + 0xb2, 0x81, 0xb3, 0x50, 0x12, 0x1a, 0xe2, 0xca, 0xc4, 0x5b, 0x49, 0xe8, 0xda, 0x46, 0x01, 0x87, 0x6c, 0x89, 0xed, + 0x17, 0x17, 0x7a, 0x91, 0xdb, 0x25, 0x7b, 0x28, 0xff, 0xa9, 0xe2, 0x92, 0xf5, 0x2c, 0xc7, 0x94, 0x34, 0x60, 0x8a, + 0xf1, 0x60, 0x69, 0x16, 0x20, 0x01, 0xbe, 0x2b, 0x87, 0x71, 0xb1, 0x9e, 0x04, 0x7f, 0x28, 0x98, 0xcf, 0x8d, 0x99, + 0x6e, 0x85, 0x54, 0x4b, 0x38, 0x69, 0x06, 0x6b, 0xd0, 0xa4, 0xf1, 0xa0, 0x44, 0xcd, 0x57, 0x68, 0xa8, 0x10, 0xc7, + 0x9f, 0x89, 0x2a, 0x34, 0xc1, 0x10, 0x8c, 0xc2, 0xcb, 0x25, 0xc3, 0xa5, 0xcb, 0xa2, 0x45, 0xca, 0xd4, 0x98, 0x54, + 0xaa, 0x66, 0xb9, 0x14, 0x0c, 0x2c, 0xda, 0xad, 0xbe, 0xb4, 0xc4, 0x95, 0xc8, 0xcd, 0x42, 0x2d, 0x4c, 0x72, 0xe5, + 0x4d, 0x38, 0x05, 0xfa, 0x5d, 0xca, 0x7a, 0x37, 0xf1, 0x29, 0x14, 0x3e, 0x85, 0x6f, 0xf8, 0x50, 0x26, 0x6f, 0xe7, + 0x3d, 0x30, 0xf7, 0x6b, 0x95, 0x68, 0x9f, 0xfa, 0x28, 0x98, 0x5d, 0x2d, 0x74, 0x41, 0xa0, 0x48, 0x36, 0xc9, 0x7a, + 0x92, 0xdf, 0x50, 0x6c, 0x54, 0x9e, 0x51, 0xea, 0x8a, 0x0d, 0x52, 0xf3, 0x4a, 0x53, 0x2f, 0x73, 0x17, 0xec, 0xf7, + 0xb2, 0x94, 0x74, 0x62, 0x82, 0x32, 0xb1, 0x77, 0x13, 0x6d, 0xbc, 0x2c, 0x4c, 0x85, 0xf5, 0x2b, 0x8c, 0x9d, 0x1a, + 0x85, 0x32, 0x29, 0x02, 0x71, 0x6c, 0x7c, 0xac, 0x2c, 0x83, 0xd4, 0x5f, 0x61, 0x4f, 0x01, 0x28, 0x09, 0x2c, 0xbe, + 0xa6, 0x92, 0x17, 0x85, 0x75, 0x3a, 0x6e, 0x10, 0x1d, 0x2b, 0x11, 0x5a, 0x13, 0xf9, 0x5a, 0x9f, 0xc5, 0x7e, 0xcd, + 0x25, 0x34, 0x29, 0x59, 0xf4, 0x8a, 0xc0, 0x56, 0x81, 0x88, 0x4a, 0xb7, 0x25, 0xbd, 0x84, 0x1c, 0xd2, 0x65, 0xa2, + 0xd7, 0x46, 0x32, 0x68, 0x9d, 0x09, 0x89, 0x96, 0xf5, 0xc3, 0x08, 0xc5, 0x86, 0x58, 0x8b, 0x25, 0x42, 0x2e, 0xda, + 0x9b, 0xc4, 0x8a, 0xe8, 0x9c, 0x16, 0x68, 0xc2, 0x99, 0x3a, 0xdd, 0x71, 0x00, 0x1d, 0x10, 0xfb, 0x4b, 0xac, 0xb7, + 0xd2, 0xec, 0x74, 0xfd, 0xca, 0xe1, 0xbb, 0xbe, 0x1e, 0x73, 0xd7, 0x91, 0x06, 0x2f, 0xac, 0x59, 0x4f, 0xc9, 0xde, + 0xfd, 0xd7, 0xd8, 0x8a, 0xec, 0xcf, 0xaa, 0xa4, 0xf2, 0x14, 0x6a, 0x9c, 0x5b, 0x5f, 0xa7, 0x5a, 0x68, 0x51, 0x55, + 0x1c, 0x18, 0x52, 0xfd, 0x40, 0x29, 0xec, 0x0a, 0xe5, 0x03, 0x39, 0x74, 0xec, 0xba, 0x6e, 0x50, 0x90, 0xf3, 0xb2, + 0xb1, 0xca, 0x85, 0xdc, 0xda, 0x32, 0x7d, 0xa6, 0x73, 0x3d, 0xfc, 0x33, 0x07, 0x95, 0x73, 0x71, 0x95, 0x92, 0x05, + 0xf3, 0x4c, 0xa9, 0xa3, 0x25, 0x07, 0xb4, 0xd9, 0x41, 0x4f, 0x3b, 0xba, 0x88, 0x62, 0x6e, 0xe9, 0x51, 0x84, 0xa7, + 0x8d, 0xf2, 0x49, 0x1a, 0x1d, 0x80, 0x17, 0x9a, 0x90, 0xe4, 0x84, 0x9b, 0xb6, 0x68, 0x31, 0x18, 0x33, 0x0c, 0x81, + 0x2b, 0x7b, 0xc2, 0x94, 0x3d, 0x1b, 0x88, 0xb7, 0x1c, 0x78, 0x35, 0xec, 0xe5, 0x62, 0xf7, 0x9a, 0xf9, 0x0f, 0x6b, + 0x04, 0xb2, 0x6d, 0xa2, 0xea, 0xca, 0x85, 0x67, 0x29, 0x22, 0x31, 0xc2, 0xb6, 0x6a, 0x6c, 0x69, 0xeb, 0x77, 0x16, + 0xdc, 0xeb, 0xca, 0x31, 0xaf, 0x29, 0xd5, 0x05, 0x3d, 0xac, 0xdc, 0x1c, 0x6e, 0x3a, 0xf2, 0x62, 0x05, 0xdd, 0x8e, + 0x08, 0x0a, 0x81, 0x13, 0xa1, 0xec, 0x41, 0xcd, 0x0d, 0x44, 0x4a, 0xa6, 0xb4, 0x6a, 0x36, 0x4b, 0x86, 0x12, 0x58, + 0x70, 0x61, 0x99, 0xe4, 0xa3, 0x8b, 0x38, 0x49, 0xaa, 0xd2, 0x3f, 0x54, 0xc0, 0x8b, 0x61, 0x6f, 0x13, 0xed, 0x02, + 0xa3, 0x99, 0x02, 0xc1, 0xd5, 0x46, 0xd8, 0x47, 0xc7, 0xad, 0xd6, 0x5d, 0x44, 0x1c, 0x99, 0x19, 0x8d, 0xf8, 0x88, + 0x36, 0x64, 0xc9, 0x34, 0x6b, 0xef, 0xbf, 0xc0, 0x90, 0x9a, 0x81, 0x0f, 0xaa, 0x33, 0x2a, 0xfe, 0x55, 0xf6, 0xd4, + 0xaf, 0x44, 0xef, 0x56, 0xd5, 0xb5, 0x18, 0x50, 0x51, 0x81, 0x0f, 0x33, 0xc4, 0xd2, 0x54, 0x81, 0x80, 0x5c, 0x0f, + 0xeb, 0x70, 0xb7, 0x46, 0x1a, 0x2c, 0x28, 0x05, 0xd6, 0x5a, 0xd9, 0xbd, 0xbe, 0x2d, 0x98, 0x43, 0xa1, 0x70, 0xd1, + 0xff, 0x59, 0x36, 0x99, 0xa2, 0x65, 0xb6, 0xc0, 0xd4, 0xd0, 0xe0, 0xe3, 0x42, 0x7d, 0xb9, 0xa2, 0xac, 0xd6, 0x87, + 0x76, 0x64, 0x8d, 0x9f, 0xb4, 0xa3, 0x0c, 0x0e, 0xd5, 0x4c, 0x17, 0xd5, 0xed, 0xe6, 0x45, 0x11, 0xb3, 0x8a, 0xc7, + 0x7d, 0xd2, 0xdb, 0xda, 0x9a, 0xf4, 0x34, 0x0d, 0x48, 0x26, 0x49, 0x86, 0x37, 0x19, 0xa0, 0xac, 0x88, 0x33, 0x2f, + 0x17, 0xc8, 0x37, 0x2f, 0x4b, 0x5c, 0xbf, 0xef, 0x3b, 0xfb, 0x35, 0xcf, 0xda, 0xdb, 0x5f, 0xef, 0x22, 0x57, 0x75, + 0xd2, 0x83, 0x3c, 0xea, 0x43, 0xd1, 0x92, 0x4d, 0x19, 0xce, 0x27, 0xd9, 0x90, 0x05, 0x36, 0x74, 0x4f, 0xed, 0x52, + 0x6e, 0x9a, 0x08, 0x36, 0x07, 0xf8, 0x7f, 0xf3, 0x0f, 0xf5, 0x48, 0x6a, 0xb0, 0x0f, 0x2c, 0xa0, 0xcd, 0x85, 0x2f, + 0xc3, 0xb3, 0x24, 0x3b, 0x8d, 0x92, 0x43, 0xa1, 0xc0, 0x6b, 0x2d, 0xbf, 0x01, 0x97, 0x91, 0x2c, 0x56, 0x43, 0x49, + 0x7d, 0xd9, 0xfb, 0x32, 0xb8, 0xbd, 0x47, 0xe5, 0xad, 0xd8, 0x2d, 0xbf, 0xe9, 0xb7, 0x6c, 0x15, 0x11, 0xfb, 0xc9, + 0x9c, 0x0e, 0x34, 0x4e, 0x01, 0x94, 0x39, 0x04, 0x4d, 0x56, 0x78, 0x03, 0x1e, 0xfe, 0xd4, 0xfb, 0x49, 0xb9, 0xd4, + 0x19, 0xb8, 0x10, 0xe0, 0xe4, 0x27, 0x31, 0x6f, 0xe0, 0x79, 0xa4, 0xed, 0xcd, 0x45, 0x05, 0xc6, 0x15, 0x29, 0x2e, + 0x5d, 0x2a, 0x6f, 0xd0, 0x3b, 0x0e, 0x4f, 0xa0, 0xd9, 0xe6, 0xe6, 0xdc, 0x79, 0x13, 0xf1, 0xb1, 0x9f, 0x47, 0xe9, + 0x30, 0x9b, 0x38, 0xee, 0xb6, 0x6d, 0xbb, 0x7e, 0x41, 0x9e, 0xc8, 0x67, 0x6e, 0xb9, 0x79, 0xe2, 0x0d, 0x79, 0x68, + 0xf7, 0xec, 0xed, 0x63, 0xef, 0x90, 0x87, 0x27, 0x7b, 0x9b, 0xf3, 0x21, 0x2f, 0xbb, 0x27, 0xde, 0xa5, 0x8e, 0xb9, + 0x7b, 0xef, 0x51, 0xca, 0x40, 0xaf, 0xb0, 0x7b, 0x29, 0xc1, 0x00, 0x76, 0xa3, 0xf8, 0x3b, 0x48, 0xb9, 0x8f, 0x74, + 0x20, 0x22, 0xe3, 0xb4, 0xd7, 0xd7, 0x76, 0x46, 0x11, 0x03, 0x7b, 0x43, 0x3b, 0xab, 0x5b, 0x5b, 0x95, 0x9a, 0xaf, + 0x4a, 0xbd, 0x19, 0x0f, 0x6b, 0x9e, 0xba, 0xf7, 0x92, 0x8e, 0x56, 0xea, 0x1b, 0x79, 0x26, 0x82, 0x36, 0xcb, 0x76, + 0x82, 0x63, 0x6c, 0xf1, 0xd5, 0xdb, 0xfa, 0x48, 0x44, 0x29, 0xfc, 0x18, 0xac, 0x97, 0x08, 0xd4, 0x37, 0x38, 0x38, + 0xde, 0x61, 0xb8, 0xb3, 0xe7, 0xf4, 0x02, 0x67, 0xa3, 0xd1, 0xb8, 0xfe, 0x61, 0xe7, 0xe8, 0xc7, 0xa8, 0xf1, 0xcb, + 0x7e, 0xe3, 0xfb, 0xbe, 0x7b, 0xed, 0xfc, 0xb0, 0xd3, 0x3b, 0x92, 0x4f, 0x47, 0x3f, 0x76, 0x7f, 0x28, 0xfa, 0x7f, + 0x12, 0x85, 0x9b, 0xae, 0xbb, 0x73, 0xe6, 0x4d, 0x79, 0xb8, 0xd3, 0x68, 0x74, 0xe1, 0xdb, 0x19, 0x7c, 0xc3, 0xcf, + 0x53, 0xf8, 0xb8, 0x3e, 0xb2, 0xfe, 0xc3, 0x0f, 0xe9, 0x7f, 0xfc, 0x21, 0xef, 0xe3, 0x98, 0x47, 0x3f, 0xfe, 0x50, + 0xd8, 0xf7, 0xbb, 0xe1, 0x4e, 0x7f, 0xdb, 0x75, 0x74, 0xcd, 0x9f, 0xc2, 0xea, 0x2b, 0xb4, 0x3a, 0xfa, 0x51, 0x3e, + 0xd9, 0xf7, 0x4f, 0xf6, 0xba, 0x61, 0xff, 0xda, 0xb1, 0xaf, 0xef, 0xbb, 0xd7, 0xae, 0x7b, 0xbd, 0x89, 0xf3, 0x9c, + 0xc3, 0xe8, 0xf7, 0xe1, 0x73, 0x04, 0x9f, 0x36, 0x7c, 0x6e, 0xc2, 0xe7, 0x8f, 0xd0, 0x4d, 0xc4, 0xdf, 0xae, 0x29, + 0x16, 0x72, 0x8d, 0x07, 0x16, 0x11, 0xac, 0x82, 0xbb, 0xb9, 0x13, 0x7b, 0x13, 0x22, 0x1a, 0xec, 0x43, 0xdf, 0xf7, + 0x31, 0x4c, 0xea, 0xcc, 0x8f, 0x37, 0x61, 0xd1, 0x91, 0x73, 0x36, 0x03, 0xee, 0x89, 0xc8, 0x41, 0x11, 0x30, 0x71, + 0xb6, 0x5a, 0xe0, 0xe1, 0xaa, 0x37, 0x0c, 0x27, 0xdc, 0x01, 0xa3, 0xe0, 0x03, 0xc7, 0x2f, 0x6d, 0xd7, 0x7b, 0x21, + 0xcf, 0x0c, 0x71, 0x9f, 0x0b, 0xd6, 0x4a, 0x33, 0x61, 0xd2, 0xd8, 0xae, 0x37, 0x5d, 0x51, 0x09, 0xdb, 0x3a, 0x3d, + 0x83, 0xba, 0x63, 0x11, 0xa3, 0xfe, 0x96, 0x45, 0x9f, 0x70, 0x4b, 0xbe, 0x35, 0x0e, 0x81, 0x97, 0x2c, 0xf9, 0x45, + 0xa3, 0xd1, 0xb0, 0x11, 0x85, 0x3b, 0xf6, 0x94, 0xc1, 0x0c, 0x4b, 0x26, 0x22, 0x23, 0xa5, 0x29, 0x2c, 0x5b, 0x98, + 0xfc, 0x7d, 0x94, 0xf3, 0xcd, 0xca, 0xb0, 0x0d, 0xeb, 0x96, 0xec, 0x82, 0xa5, 0x7f, 0x87, 0x29, 0xd0, 0xb4, 0xa4, + 0xf3, 0x0f, 0x73, 0xfc, 0x30, 0x23, 0xb4, 0x3e, 0x38, 0x0c, 0x3c, 0xf4, 0x02, 0xe4, 0x8e, 0xe8, 0xe7, 0xbc, 0x47, + 0x35, 0x06, 0xff, 0xcb, 0x30, 0x83, 0x27, 0xe6, 0xc3, 0x10, 0xcd, 0xbc, 0xd4, 0xc1, 0xad, 0x0c, 0xc5, 0xfd, 0x2b, + 0xdc, 0x19, 0x59, 0xe9, 0x1d, 0x84, 0x6a, 0xc7, 0x1c, 0xe6, 0x8c, 0x7d, 0x1b, 0x25, 0x9f, 0x58, 0xee, 0x5c, 0x7a, + 0xad, 0xf6, 0x67, 0xd4, 0xd9, 0x43, 0xdb, 0xec, 0x4d, 0x75, 0x8c, 0xa6, 0xcd, 0x02, 0x79, 0x44, 0xd8, 0x68, 0x79, + 0x28, 0x31, 0x88, 0x04, 0xb9, 0x97, 0x86, 0x6d, 0xe2, 0x70, 0x7b, 0xaf, 0x38, 0x3f, 0xeb, 0xda, 0x81, 0x6d, 0x83, + 0xc5, 0x7f, 0x48, 0x61, 0x2b, 0x61, 0x58, 0x34, 0x3b, 0x6c, 0x2f, 0xee, 0xb0, 0xed, 0xed, 0x2a, 0xe0, 0x84, 0x07, + 0xe9, 0xd4, 0x3d, 0xf1, 0x22, 0x6f, 0x1c, 0xc2, 0x80, 0x03, 0x68, 0x86, 0x5d, 0x3a, 0x83, 0xbd, 0x58, 0x4e, 0x03, + 0xb2, 0x3e, 0xf3, 0x93, 0xa8, 0xe0, 0xaf, 0x30, 0x1e, 0x11, 0x0e, 0xc0, 0xd8, 0xcf, 0x7c, 0x76, 0xc9, 0x06, 0xca, + 0xce, 0x00, 0x42, 0x45, 0x6e, 0xc7, 0x1d, 0x84, 0x46, 0x33, 0x98, 0x3b, 0x0c, 0x0f, 0x7b, 0x36, 0xec, 0x25, 0xd8, + 0x95, 0x61, 0x74, 0xd4, 0xea, 0xf7, 0xb2, 0x70, 0xca, 0x03, 0x4d, 0x5b, 0x59, 0x74, 0x56, 0x2b, 0x6a, 0xf7, 0x7b, + 0xce, 0x26, 0x18, 0xe9, 0x60, 0x8b, 0x3b, 0xf8, 0x84, 0x11, 0x8a, 0x3c, 0xfc, 0xc0, 0xce, 0x5e, 0x5c, 0x4e, 0x1d, + 0x7b, 0x6f, 0xc7, 0xde, 0xc6, 0x52, 0xcf, 0x06, 0xf6, 0x02, 0x0a, 0x86, 0xa7, 0xae, 0xd9, 0x79, 0xb7, 0x8f, 0xa0, + 0x62, 0x21, 0x4e, 0x7e, 0xda, 0xb3, 0xbb, 0x62, 0xea, 0x26, 0x0c, 0x9a, 0xc9, 0xe5, 0xc7, 0x15, 0x3d, 0x24, 0x54, + 0x55, 0x57, 0x05, 0x1d, 0x94, 0xb5, 0x03, 0x67, 0x6c, 0x22, 0xd1, 0xc0, 0xc9, 0x24, 0x15, 0xc0, 0xe1, 0xc1, 0x66, + 0x30, 0xa9, 0xd1, 0x6d, 0xb7, 0xdf, 0x3b, 0x0d, 0xee, 0xdb, 0xf7, 0xd5, 0xc3, 0x08, 0x90, 0xe1, 0x62, 0xfa, 0x11, + 0x48, 0x3b, 0xfc, 0x3c, 0xe7, 0x80, 0xe4, 0x29, 0x15, 0x4d, 0x65, 0xd1, 0x19, 0x16, 0x1d, 0x06, 0x08, 0xaa, 0x97, + 0x6b, 0xeb, 0x4f, 0xac, 0xc9, 0x30, 0x24, 0xd8, 0xc1, 0x16, 0x3a, 0x62, 0xdb, 0xad, 0x3e, 0x9e, 0x37, 0xe4, 0xbc, + 0xf8, 0x36, 0xe6, 0xa0, 0x12, 0x76, 0xba, 0xb6, 0xdb, 0xb3, 0x2d, 0x5c, 0xda, 0x4e, 0xba, 0x1d, 0x0a, 0x0a, 0xc7, + 0xdb, 0x87, 0x3c, 0x18, 0x77, 0xc3, 0x66, 0xcf, 0x29, 0x64, 0xb8, 0x11, 0xcf, 0x2d, 0x85, 0x04, 0x6f, 0x7a, 0x63, + 0x10, 0xe8, 0xc8, 0xb9, 0x9b, 0xf6, 0xb6, 0x2a, 0x84, 0xa2, 0xe3, 0xed, 0xa1, 0x1b, 0xc4, 0xf0, 0xe1, 0x34, 0x90, + 0x69, 0xc6, 0xba, 0xaf, 0xd2, 0xcc, 0xcc, 0x0d, 0x86, 0xca, 0x22, 0x4f, 0xc2, 0x74, 0xdb, 0xc1, 0x08, 0x2d, 0x48, + 0xda, 0xbd, 0x1e, 0xc0, 0xb0, 0xed, 0x28, 0x4e, 0xdb, 0x51, 0xac, 0xa6, 0xec, 0xf3, 0x23, 0xbd, 0x1c, 0x03, 0xde, + 0x1b, 0xa8, 0xf3, 0x58, 0xd4, 0x3e, 0x00, 0x56, 0x90, 0x78, 0x45, 0x5f, 0x9d, 0x79, 0xbd, 0xac, 0x9d, 0x6f, 0xcd, + 0x95, 0x28, 0xe2, 0x9e, 0x21, 0xa1, 0x58, 0xa9, 0xdd, 0x30, 0x61, 0x6e, 0x4f, 0x91, 0x18, 0x9a, 0xe5, 0x43, 0xd8, + 0x63, 0xa1, 0x0a, 0xb0, 0x67, 0xe6, 0xb6, 0x48, 0xc2, 0xaa, 0xb9, 0x77, 0x04, 0xac, 0xdd, 0x0f, 0xdf, 0x08, 0x77, + 0xaa, 0xa3, 0xa2, 0xf9, 0x2c, 0x09, 0x5f, 0x2e, 0x1c, 0x17, 0x47, 0x78, 0x22, 0x74, 0xe0, 0x0f, 0x66, 0x39, 0xc8, + 0x03, 0xfe, 0x16, 0x2c, 0x83, 0x50, 0x36, 0x45, 0x47, 0x0f, 0x8f, 0x80, 0x3d, 0x42, 0x7c, 0x21, 0x6c, 0x6e, 0x54, + 0xa3, 0x45, 0x49, 0xc6, 0x0b, 0x1d, 0x0c, 0x77, 0x98, 0x74, 0xed, 0x51, 0x30, 0xc8, 0x13, 0x63, 0x07, 0xcf, 0xfc, + 0xfd, 0x01, 0x56, 0xe3, 0x04, 0x85, 0x5b, 0xd2, 0x6e, 0xab, 0xc4, 0xdf, 0x81, 0x9f, 0x82, 0x04, 0xc7, 0x3a, 0xf0, + 0xb3, 0xb6, 0xb6, 0x12, 0x89, 0xd4, 0x5e, 0xd6, 0xa1, 0x93, 0x08, 0x8c, 0x07, 0x17, 0x7e, 0x0a, 0xd5, 0x48, 0x22, + 0x2a, 0x22, 0x0b, 0xd4, 0x3c, 0x55, 0xab, 0xe0, 0x3b, 0x32, 0x23, 0xf0, 0x8c, 0x92, 0x5c, 0xd0, 0x50, 0xd4, 0x8d, + 0x45, 0x2c, 0xdf, 0x75, 0xe9, 0x68, 0x0b, 0x0f, 0x20, 0x05, 0xa3, 0x09, 0x86, 0x71, 0x29, 0x28, 0x59, 0xf1, 0xdf, + 0xb1, 0x11, 0x2b, 0x1f, 0x1f, 0xa5, 0xdb, 0xdb, 0x7d, 0x71, 0x6e, 0x41, 0x8c, 0xc3, 0x8c, 0xe8, 0x6a, 0x5c, 0x01, + 0x50, 0x9f, 0xce, 0x89, 0xeb, 0x81, 0x69, 0xc5, 0x9a, 0x2e, 0xc5, 0x3e, 0x39, 0xcc, 0x00, 0x14, 0xdc, 0x71, 0x8e, + 0xfc, 0xde, 0x9f, 0xfb, 0xe0, 0x1e, 0xfb, 0x7f, 0x72, 0x77, 0x94, 0xa0, 0xe9, 0xc8, 0x33, 0xc5, 0x39, 0x9d, 0xb1, + 0xb6, 0x3c, 0x8a, 0x8d, 0x06, 0x20, 0xf5, 0x00, 0x03, 0xd0, 0xe6, 0x20, 0x13, 0x2a, 0x0e, 0x42, 0x8e, 0x0a, 0x6c, + 0x1f, 0x37, 0x3f, 0xc3, 0x9d, 0xfd, 0x9c, 0x07, 0x60, 0xc1, 0xa8, 0xa7, 0xd7, 0xf0, 0xf4, 0x67, 0xfd, 0xf4, 0x13, + 0x0f, 0x7e, 0x29, 0x65, 0xe8, 0xbe, 0x36, 0xc5, 0x23, 0x35, 0x45, 0x29, 0x96, 0xc8, 0xa0, 0x21, 0x77, 0x97, 0x63, + 0x36, 0xcc, 0x2d, 0x81, 0x18, 0x4a, 0x74, 0x81, 0x8d, 0x16, 0x9d, 0x21, 0x71, 0x5d, 0x93, 0x14, 0x46, 0x2e, 0x81, + 0x89, 0x70, 0xc5, 0xb7, 0x48, 0x4f, 0xd6, 0x6d, 0xba, 0xf3, 0x5a, 0x5b, 0xb2, 0xef, 0xd8, 0x64, 0xca, 0xaf, 0x0e, + 0x48, 0xd1, 0x07, 0x32, 0x6d, 0x40, 0x9c, 0x9d, 0x37, 0x3b, 0xf1, 0x1e, 0xeb, 0xc4, 0x20, 0xd5, 0x0b, 0xc5, 0x62, + 0xb8, 0x57, 0xbd, 0xf7, 0x18, 0xa5, 0x34, 0x99, 0xc9, 0xab, 0xa1, 0xd7, 0x96, 0xe8, 0x6d, 0x6f, 0x03, 0x82, 0x1d, + 0xa3, 0x2b, 0x13, 0x5d, 0xcb, 0x52, 0xd0, 0x04, 0x20, 0x7a, 0x52, 0x67, 0x39, 0xe2, 0x38, 0xcc, 0x66, 0x83, 0xe2, + 0x21, 0x77, 0x57, 0x8e, 0x8a, 0x63, 0x62, 0x77, 0x99, 0xb0, 0x03, 0x98, 0x11, 0x97, 0x37, 0x5a, 0x22, 0x3a, 0x2c, + 0xfa, 0xeb, 0xf8, 0xf6, 0xb1, 0xc7, 0xb7, 0x5b, 0x2e, 0x68, 0x90, 0xda, 0x58, 0x8f, 0xab, 0xb1, 0xa0, 0x3e, 0x3c, + 0xd6, 0x54, 0x2a, 0xf3, 0xed, 0xed, 0xb2, 0x7e, 0x54, 0xab, 0x76, 0x70, 0xed, 0x34, 0xe5, 0x72, 0x31, 0x1b, 0x84, + 0x03, 0x11, 0x13, 0x28, 0xd0, 0xd2, 0xca, 0x8a, 0x01, 0x86, 0x94, 0xe5, 0x28, 0x9f, 0x42, 0xee, 0xc5, 0x65, 0xa9, + 0x53, 0x5f, 0x9e, 0xc9, 0xa0, 0x23, 0x9e, 0x7a, 0x92, 0xb1, 0x02, 0xac, 0xe6, 0x65, 0x5e, 0x42, 0x4b, 0x04, 0x98, + 0xbf, 0x50, 0x39, 0x34, 0xc2, 0x02, 0x89, 0x42, 0xc3, 0x2c, 0x51, 0xc6, 0x67, 0x1e, 0xc6, 0xa0, 0xed, 0x9f, 0xd5, + 0x62, 0x5f, 0xb9, 0x32, 0x3a, 0xf2, 0xa3, 0xa2, 0x1f, 0x50, 0xfd, 0x4c, 0x4a, 0xb0, 0x71, 0xf8, 0x11, 0xd8, 0xa8, + 0x72, 0x3c, 0x49, 0x10, 0x3e, 0x8f, 0x73, 0x46, 0x9e, 0xc2, 0xa6, 0x84, 0x59, 0x9a, 0xb6, 0x91, 0x6a, 0x17, 0x99, + 0x41, 0x28, 0x17, 0xe6, 0x1f, 0x1b, 0x67, 0x17, 0x69, 0xb8, 0xd4, 0x1a, 0xcc, 0x8f, 0x77, 0x26, 0x40, 0xe9, 0xf5, + 0x75, 0x2a, 0x7c, 0xdc, 0x88, 0xec, 0x0d, 0x5d, 0x31, 0xee, 0x29, 0xa4, 0x02, 0x27, 0x22, 0x8b, 0x87, 0xce, 0x50, + 0x68, 0x84, 0x43, 0x3a, 0x45, 0x2e, 0x5c, 0x63, 0xd3, 0x17, 0x3d, 0xed, 0x1b, 0x65, 0xa1, 0x93, 0x80, 0x10, 0x10, + 0xb8, 0x1b, 0xd6, 0x54, 0xd6, 0xcb, 0x82, 0x84, 0x4a, 0xd1, 0xcf, 0x01, 0xfc, 0xc3, 0x48, 0x52, 0x00, 0xec, 0x87, + 0x6a, 0xa4, 0x88, 0xb2, 0x2c, 0x70, 0x01, 0x68, 0xae, 0x03, 0x5c, 0x09, 0x5f, 0x18, 0xa8, 0x30, 0x3d, 0xcd, 0xca, + 0x4a, 0xa1, 0x44, 0x9e, 0xae, 0x48, 0x59, 0x23, 0x99, 0x7c, 0x8e, 0x0e, 0x9f, 0xf2, 0xae, 0xdf, 0x4a, 0x3c, 0x74, + 0xc1, 0x73, 0x58, 0x56, 0xf5, 0xfd, 0x4d, 0xc8, 0xc8, 0xb9, 0x06, 0x5d, 0x21, 0x85, 0xfe, 0x92, 0x93, 0xbc, 0xff, + 0xc6, 0xaf, 0x6a, 0xa9, 0x31, 0x94, 0x7d, 0x5c, 0xd5, 0x0c, 0xcb, 0xcb, 0x69, 0x15, 0xa6, 0x20, 0xe0, 0xe6, 0x2c, + 0x09, 0xe6, 0x52, 0x43, 0x80, 0x85, 0xed, 0x91, 0x56, 0x0a, 0x8a, 0x52, 0x87, 0x77, 0x9e, 0x83, 0x15, 0x60, 0x1c, + 0x6a, 0xa9, 0x64, 0x1a, 0x49, 0x7c, 0xa9, 0x44, 0x81, 0x29, 0x0f, 0x06, 0xe0, 0xa7, 0x2e, 0x9e, 0x74, 0x5d, 0xba, + 0x7e, 0x3c, 0xc1, 0xd4, 0x1e, 0x02, 0x3d, 0xf6, 0x36, 0xc0, 0x94, 0xa8, 0xeb, 0xb0, 0x9c, 0x38, 0x34, 0xad, 0x69, + 0x16, 0x30, 0x63, 0x9a, 0xa0, 0x25, 0x9b, 0x60, 0xcb, 0x15, 0x60, 0x1f, 0x89, 0xed, 0x59, 0xad, 0x80, 0xd0, 0x35, + 0x68, 0x60, 0xc8, 0x5d, 0x2a, 0xb4, 0x30, 0xeb, 0xb4, 0xa9, 0x08, 0xf7, 0x67, 0x8f, 0x49, 0x2b, 0x38, 0xf5, 0x52, + 0x1a, 0xf8, 0x20, 0x3e, 0x4d, 0x30, 0xf1, 0x05, 0xb1, 0x02, 0x3b, 0x38, 0x68, 0x2d, 0x36, 0x05, 0x4e, 0xc5, 0x45, + 0x4a, 0x61, 0x59, 0x51, 0x6a, 0xc3, 0x87, 0x14, 0xd9, 0xba, 0xcb, 0x23, 0xdd, 0x85, 0x58, 0x00, 0x3b, 0xfd, 0xc2, + 0xa1, 0x83, 0xac, 0x97, 0x01, 0x83, 0x73, 0xad, 0x71, 0x10, 0xf8, 0xed, 0xed, 0xa4, 0x5f, 0x66, 0x48, 0xb9, 0x25, + 0x56, 0x17, 0x90, 0xe3, 0x76, 0x58, 0xc0, 0x1d, 0x84, 0xa5, 0xb2, 0xc7, 0xf3, 0x72, 0x82, 0xcb, 0xa5, 0x2c, 0xe4, + 0xc5, 0x74, 0x2c, 0x9a, 0xcf, 0xad, 0x34, 0x9b, 0x8e, 0xb7, 0xe2, 0x83, 0x82, 0xbf, 0xe7, 0xc4, 0xd2, 0xaa, 0xa7, + 0xd4, 0x0a, 0x8f, 0x32, 0xb7, 0x64, 0x9d, 0x92, 0x5a, 0x6d, 0x37, 0x50, 0x8d, 0xf0, 0x34, 0x0d, 0x1b, 0x81, 0x10, + 0x13, 0x5c, 0xfc, 0x61, 0x91, 0x89, 0x69, 0x6f, 0x09, 0xa9, 0x23, 0xec, 0x1e, 0xca, 0x09, 0x6e, 0x6b, 0x9e, 0x7d, + 0x19, 0x4e, 0xd7, 0x33, 0xf7, 0xbe, 0xc1, 0xdc, 0x4f, 0x43, 0x66, 0x30, 0x7a, 0x2c, 0x13, 0x7e, 0x64, 0xec, 0xa3, + 0x50, 0x55, 0xcf, 0xce, 0xc2, 0x4a, 0x64, 0x89, 0x6f, 0xc6, 0x51, 0x87, 0x71, 0x2a, 0x5a, 0x13, 0x64, 0xd7, 0xd7, + 0xb9, 0xb9, 0x17, 0x28, 0x68, 0xea, 0xb1, 0x7a, 0x9c, 0xb6, 0x62, 0x67, 0x23, 0x12, 0xb9, 0xff, 0xa6, 0x16, 0x89, + 0xac, 0xf8, 0x1c, 0x47, 0x5a, 0x73, 0x90, 0xfb, 0xec, 0x6c, 0x79, 0x93, 0x0a, 0xdd, 0xa2, 0xd1, 0x36, 0xf6, 0xa8, + 0x3e, 0x90, 0xd4, 0x33, 0x2a, 0xb0, 0xaa, 0xb1, 0xb7, 0xb6, 0x5a, 0x22, 0xdd, 0x52, 0x29, 0x36, 0x0c, 0x69, 0x85, + 0xcc, 0x18, 0x05, 0x83, 0x92, 0x22, 0x03, 0x35, 0xca, 0xd7, 0x08, 0x86, 0x7d, 0x6a, 0x00, 0x8a, 0x73, 0x75, 0xf5, + 0xd3, 0x52, 0xb2, 0x85, 0x80, 0x04, 0x64, 0x13, 0x8a, 0x35, 0x62, 0x66, 0xe4, 0x93, 0x8f, 0xc0, 0x79, 0x3d, 0x8e, + 0x8e, 0x01, 0xc8, 0x60, 0xb1, 0xe9, 0xc1, 0xc4, 0xb6, 0x89, 0x28, 0xfa, 0x6c, 0xe0, 0x25, 0x00, 0x3b, 0xad, 0x42, + 0xa3, 0x1f, 0xaa, 0x14, 0x30, 0x64, 0x03, 0x37, 0xe0, 0x55, 0x58, 0x6e, 0xff, 0x25, 0xb4, 0x83, 0xc7, 0x17, 0xb2, + 0xf9, 0x26, 0xe6, 0x09, 0x56, 0xb1, 0x3b, 0xbf, 0xb2, 0xac, 0xc5, 0xb9, 0xd3, 0xe1, 0x42, 0xbd, 0xa2, 0x84, 0xa8, + 0x3d, 0xc0, 0xda, 0x97, 0x9c, 0x60, 0xc4, 0xe7, 0x37, 0x94, 0x75, 0xa8, 0xc6, 0x2d, 0xf7, 0x35, 0x5a, 0x84, 0xe9, + 0x32, 0x69, 0x0c, 0x4a, 0xd6, 0xfd, 0x64, 0xc4, 0xbd, 0x3c, 0x10, 0xb1, 0xe0, 0x0a, 0x47, 0x23, 0x6c, 0xbe, 0x80, + 0x24, 0x7d, 0xdb, 0xa7, 0x03, 0xf6, 0xcd, 0xc5, 0x5e, 0x40, 0x99, 0x8f, 0x15, 0xa9, 0x24, 0xa4, 0x34, 0xbb, 0x21, + 0x92, 0x84, 0xb5, 0x22, 0x4f, 0x9d, 0x0f, 0x1c, 0xed, 0x73, 0x2b, 0x89, 0x60, 0x04, 0x27, 0x71, 0xba, 0xf2, 0x70, + 0x51, 0x80, 0xab, 0xe8, 0x88, 0xe9, 0x9b, 0xa0, 0xfc, 0x06, 0xb9, 0xbd, 0x94, 0x5c, 0x5b, 0x68, 0x18, 0x9e, 0x21, + 0xc1, 0xaa, 0x48, 0x04, 0x3a, 0x0a, 0x80, 0xe3, 0x4a, 0xcf, 0x03, 0x4c, 0xf8, 0xda, 0xde, 0x04, 0x80, 0x44, 0x56, + 0x90, 0xb3, 0x14, 0xe8, 0x06, 0x2c, 0x57, 0xc7, 0xa9, 0x51, 0x91, 0xb8, 0xb8, 0x31, 0x5d, 0xdd, 0xd2, 0x9f, 0xa0, + 0xe5, 0x4c, 0x86, 0x98, 0x0e, 0x82, 0x80, 0x4c, 0x7d, 0xca, 0x9d, 0x9c, 0xa6, 0x13, 0xd6, 0xe7, 0xd4, 0xa9, 0x4d, + 0xdd, 0xe1, 0xd4, 0xcd, 0x93, 0xd4, 0x62, 0x75, 0xda, 0x94, 0x12, 0x31, 0x29, 0x31, 0x8f, 0x65, 0x2a, 0xb6, 0x12, + 0x77, 0x6e, 0x7d, 0xa3, 0x85, 0xb4, 0xd1, 0x8e, 0x65, 0x0e, 0xb6, 0x96, 0xf7, 0x42, 0xb4, 0xbf, 0x24, 0xc2, 0xb3, + 0x12, 0x19, 0x6b, 0x3e, 0xe3, 0x8e, 0x89, 0x60, 0xf5, 0x60, 0x2a, 0xf2, 0x0f, 0x8e, 0x4e, 0xb3, 0x37, 0xe8, 0x41, + 0xea, 0x0d, 0x24, 0x66, 0x4d, 0x7c, 0xe7, 0xd2, 0x50, 0x47, 0x08, 0x54, 0x46, 0xb5, 0x4c, 0xc7, 0x89, 0xa5, 0xe2, + 0x92, 0x7c, 0xf5, 0x5e, 0x1f, 0xe7, 0x1b, 0xdf, 0x17, 0x56, 0x23, 0x88, 0xc1, 0x5b, 0x28, 0xfa, 0x9e, 0x14, 0xe1, + 0x39, 0x2c, 0xcf, 0xf6, 0x76, 0xa7, 0xd8, 0x63, 0x55, 0x88, 0xa4, 0x82, 0x31, 0xc6, 0x8c, 0x62, 0xdc, 0x13, 0x35, + 0xb5, 0x88, 0xc4, 0x96, 0xad, 0xc3, 0x02, 0x0f, 0x00, 0xa0, 0xa5, 0x29, 0xbd, 0xcc, 0xb6, 0xea, 0x3c, 0x97, 0xf0, + 0x31, 0xf2, 0x50, 0x64, 0xe3, 0xf7, 0x6b, 0x32, 0x50, 0x10, 0xee, 0x8d, 0x96, 0x87, 0x89, 0x71, 0xb0, 0x8a, 0x42, + 0x16, 0xe8, 0x0d, 0xda, 0xa9, 0x12, 0xa1, 0xb8, 0x39, 0x59, 0x87, 0x1b, 0x4e, 0x2a, 0xd8, 0x42, 0x25, 0x2c, 0x95, + 0x16, 0xf8, 0xd5, 0x46, 0x58, 0x3c, 0x65, 0xdc, 0x7f, 0x53, 0xe1, 0x0c, 0xfa, 0x83, 0x7b, 0xcb, 0x8c, 0xfa, 0x7e, + 0xe9, 0x44, 0xa6, 0x02, 0x13, 0x37, 0xb3, 0xd4, 0x7e, 0xbf, 0xac, 0xd2, 0x7e, 0x5e, 0x2e, 0xf7, 0x39, 0x69, 0xbe, + 0xd6, 0x1d, 0x34, 0x9f, 0x0c, 0xf7, 0x2b, 0xe5, 0x87, 0x16, 0x46, 0x4d, 0xf9, 0xd5, 0x97, 0x34, 0xcc, 0x3d, 0x15, + 0xde, 0xea, 0xb6, 0x51, 0xe8, 0xa2, 0x3e, 0x07, 0x43, 0x48, 0x7f, 0x05, 0xd7, 0xd0, 0xe0, 0x41, 0x91, 0x2c, 0x16, + 0x6b, 0x17, 0xc4, 0xf5, 0x31, 0xa7, 0xda, 0xa1, 0x8c, 0x31, 0xe2, 0x69, 0xc9, 0x41, 0x92, 0xc1, 0xc1, 0xf8, 0x0d, + 0x0c, 0x88, 0x49, 0x49, 0x48, 0x87, 0xd0, 0x59, 0x99, 0x89, 0xa8, 0xdc, 0xc5, 0xdb, 0x8d, 0xcb, 0x9a, 0x42, 0x11, + 0x76, 0x82, 0x99, 0x4a, 0xa9, 0x20, 0x90, 0x26, 0xdf, 0x46, 0xab, 0x16, 0x0c, 0x05, 0xd1, 0x60, 0x28, 0x20, 0x0f, + 0xd3, 0x55, 0xc2, 0x8d, 0x8f, 0xe2, 0xe0, 0x79, 0x85, 0x1a, 0xf1, 0x52, 0x83, 0xaf, 0x61, 0xf3, 0xd7, 0x44, 0x49, + 0x11, 0x72, 0x11, 0x7b, 0x05, 0x9f, 0x08, 0xd9, 0x94, 0x87, 0x39, 0xd0, 0x0f, 0xed, 0xca, 0x4e, 0xb6, 0x97, 0x57, + 0x2e, 0x2d, 0x1a, 0x5b, 0x89, 0x9a, 0xb5, 0x38, 0x8a, 0xb7, 0xb3, 0x3e, 0x4c, 0x4d, 0x09, 0x04, 0xa4, 0xa9, 0x9c, + 0xa4, 0x9a, 0xf7, 0x28, 0xeb, 0x03, 0x48, 0xb0, 0xfb, 0x09, 0x2c, 0xf4, 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, + 0x53, 0xd0, 0x9a, 0x53, 0xd2, 0x7c, 0x53, 0x84, 0x70, 0x5b, 0x59, 0xcf, 0x98, 0x1d, 0x60, 0xdb, 0xee, 0x76, 0x7e, + 0x94, 0x6d, 0xb7, 0xfa, 0x86, 0xe0, 0xc2, 0xe3, 0xff, 0xa4, 0xc4, 0x34, 0x90, 0x42, 0xea, 0xc6, 0x4f, 0xa8, 0xc3, + 0x3e, 0x91, 0x3a, 0x11, 0x03, 0x9a, 0xab, 0xb1, 0xe8, 0xdc, 0x6b, 0x8e, 0x92, 0xcb, 0xaa, 0xda, 0xd5, 0x12, 0x34, + 0x74, 0x23, 0x19, 0x13, 0xc5, 0x3c, 0x27, 0x00, 0x46, 0xb1, 0xf9, 0x73, 0xae, 0x93, 0xbc, 0x7f, 0x59, 0x99, 0xda, + 0xed, 0xfb, 0x7e, 0x94, 0x9f, 0xd1, 0x91, 0x8a, 0xca, 0xe6, 0x24, 0xe6, 0xdf, 0x95, 0x60, 0x1a, 0x13, 0x1f, 0xe9, + 0xb9, 0xfa, 0xa1, 0x00, 0x5f, 0xd9, 0x50, 0x6a, 0xb6, 0xd7, 0xbf, 0x75, 0xb6, 0x07, 0x72, 0x36, 0xc1, 0x02, 0x0b, + 0x74, 0x59, 0x83, 0x2f, 0x60, 0x19, 0xdc, 0x91, 0x7e, 0x0a, 0xbe, 0x9f, 0xd6, 0xc1, 0x67, 0xec, 0x7f, 0x01, 0x68, + 0x55, 0x60, 0x40, 0xf9, 0x70, 0xd1, 0xb0, 0x12, 0xe2, 0x12, 0x15, 0x66, 0x15, 0xe7, 0x8f, 0xeb, 0xbc, 0x6e, 0x5a, + 0x96, 0x18, 0x94, 0x9f, 0xba, 0x86, 0x1b, 0xdf, 0x59, 0xc8, 0x1f, 0xdf, 0x7f, 0x09, 0xba, 0x9d, 0x48, 0xbb, 0xb5, + 0x55, 0x6c, 0x90, 0x85, 0x86, 0xf7, 0xc2, 0xa6, 0xd0, 0x16, 0x2f, 0x02, 0x14, 0xea, 0x3b, 0x16, 0xe3, 0x6d, 0x11, + 0x2a, 0xc3, 0x2f, 0x58, 0x30, 0x05, 0x0c, 0xc1, 0x63, 0xa7, 0x32, 0xf9, 0x1d, 0x36, 0x9a, 0x62, 0xd7, 0x42, 0x18, + 0x7c, 0x39, 0xa8, 0x4a, 0xc9, 0x8b, 0x75, 0xb2, 0xbd, 0x38, 0x87, 0xef, 0xaf, 0xe3, 0x02, 0xa8, 0x83, 0xe8, 0x6b, + 0x2a, 0x8b, 0x0d, 0xe4, 0xe2, 0xa6, 0xac, 0xf5, 0x8a, 0x86, 0xc3, 0x1b, 0xbb, 0xf0, 0xba, 0x02, 0x1f, 0x47, 0xe9, + 0x30, 0x11, 0x93, 0x98, 0x49, 0x95, 0x2b, 0x72, 0x6d, 0x74, 0x2f, 0x6d, 0xd1, 0xbc, 0x14, 0x12, 0xbc, 0x22, 0xf0, + 0x82, 0xd0, 0x57, 0xfa, 0x72, 0xb5, 0x81, 0x82, 0x47, 0xed, 0x8b, 0x8b, 0x60, 0x62, 0xe2, 0x71, 0x43, 0x6a, 0xfa, + 0x75, 0x38, 0xb5, 0xb2, 0x58, 0x72, 0xf8, 0x75, 0xce, 0xd8, 0x82, 0x02, 0x20, 0x3e, 0x79, 0xb4, 0xde, 0x4d, 0x7a, + 0xa3, 0xb4, 0x83, 0xd2, 0x08, 0xf1, 0x5d, 0x85, 0xaf, 0x3b, 0x57, 0x7c, 0xe5, 0xaa, 0x7b, 0x5f, 0x57, 0xdc, 0xb8, + 0x60, 0xf4, 0x92, 0x4f, 0x92, 0x85, 0x6b, 0x37, 0x74, 0x57, 0xe7, 0x3b, 0xef, 0x0b, 0x99, 0xb7, 0x70, 0x05, 0x76, + 0xfe, 0x15, 0x77, 0x5e, 0x7a, 0x1f, 0x8c, 0x13, 0xe5, 0xef, 0xcd, 0x23, 0x5e, 0x39, 0xcc, 0xaa, 0x93, 0xe4, 0xef, + 0x7b, 0xdf, 0x07, 0xeb, 0x5b, 0x1a, 0x27, 0xc8, 0x6d, 0x75, 0x82, 0x4c, 0x94, 0x1b, 0xe9, 0x0d, 0xb7, 0x7f, 0x57, + 0x81, 0x20, 0x4e, 0xc5, 0xf4, 0x51, 0x39, 0xae, 0x1f, 0x2d, 0x50, 0xa9, 0x88, 0xf8, 0x5c, 0xe5, 0xae, 0xac, 0x4d, + 0x0d, 0xf5, 0x98, 0x4e, 0x66, 0xa1, 0x69, 0x56, 0xe4, 0x52, 0x2e, 0x7a, 0x8c, 0x5c, 0xb3, 0x53, 0x6d, 0x7e, 0x77, + 0xed, 0x21, 0x1d, 0xc7, 0xfb, 0x9e, 0xb5, 0x5a, 0x70, 0xbf, 0xab, 0x28, 0xbc, 0xeb, 0xc5, 0x46, 0x2a, 0x43, 0xcd, + 0x7a, 0x14, 0x7d, 0x1c, 0x77, 0x31, 0x97, 0x47, 0xd9, 0x9f, 0x35, 0x00, 0x4c, 0x47, 0x58, 0x74, 0x37, 0x3d, 0x63, + 0x4f, 0xa0, 0xa7, 0x27, 0x32, 0x48, 0xf4, 0x56, 0xe7, 0xab, 0x56, 0x89, 0xa5, 0x2b, 0x08, 0xec, 0xde, 0x90, 0xb1, + 0x2a, 0x69, 0xb7, 0x5c, 0xbf, 0x9c, 0xe7, 0xf3, 0x9c, 0x2f, 0xe5, 0xf9, 0xd4, 0x2c, 0xba, 0x8d, 0xa6, 0x7b, 0x73, + 0x6a, 0xa8, 0x98, 0x6b, 0x75, 0x93, 0xdf, 0x30, 0x5d, 0x0b, 0x43, 0x2d, 0x82, 0xcc, 0x6a, 0x57, 0xbd, 0x28, 0xcb, + 0x51, 0x3d, 0x93, 0x63, 0x24, 0x7c, 0x53, 0xe9, 0x0e, 0xd1, 0x0d, 0x53, 0x35, 0xd3, 0x77, 0x0b, 0xdb, 0x42, 0xb6, + 0x79, 0x79, 0x35, 0xcc, 0x81, 0xd2, 0x72, 0x7f, 0x99, 0x30, 0x7c, 0x77, 0x7d, 0xfd, 0x9d, 0x90, 0x53, 0x55, 0x47, + 0x6f, 0xfe, 0x5a, 0xf7, 0x0c, 0x46, 0xa5, 0x72, 0x22, 0x4e, 0xf9, 0xea, 0xc1, 0x17, 0x77, 0xaf, 0x80, 0xe5, 0x14, + 0xb0, 0x3b, 0xe5, 0xce, 0xc2, 0x50, 0xd5, 0x06, 0xfe, 0x62, 0xf5, 0x60, 0xab, 0xf6, 0xf0, 0x17, 0xbd, 0x2f, 0x82, + 0x1b, 0x1b, 0x1b, 0xdb, 0x78, 0xb7, 0x96, 0x08, 0xf2, 0x16, 0x0f, 0xf4, 0xf1, 0xea, 0xa3, 0xa0, 0xe5, 0x0a, 0xb1, + 0xcd, 0x7a, 0x0e, 0x85, 0xad, 0x41, 0xbe, 0x49, 0x99, 0x34, 0x98, 0x15, 0x3c, 0x9b, 0xc8, 0x19, 0x0a, 0x79, 0xcd, + 0xc7, 0x41, 0xdb, 0x11, 0xfe, 0x0f, 0x9c, 0xda, 0xf1, 0xf2, 0xfc, 0x13, 0xf4, 0x01, 0x4f, 0x57, 0x4a, 0x53, 0x8a, + 0x53, 0xaa, 0xa0, 0xce, 0x72, 0x9d, 0x07, 0x23, 0xc5, 0xc5, 0x18, 0x16, 0x17, 0x5c, 0x96, 0x1b, 0x67, 0x23, 0xa7, + 0xbf, 0xc4, 0xab, 0x8b, 0x74, 0xf9, 0x48, 0x64, 0xab, 0x96, 0xde, 0x2b, 0x7d, 0xba, 0x6d, 0x4f, 0x18, 0x1f, 0x67, + 0x43, 0x3a, 0x98, 0xf1, 0x71, 0x22, 0xbc, 0x3e, 0x31, 0xd4, 0x77, 0x8b, 0xc0, 0x74, 0x73, 0x6c, 0xf2, 0xc3, 0xf1, + 0x7a, 0xb3, 0x59, 0xe3, 0xf6, 0xde, 0x39, 0x9f, 0x9c, 0x79, 0x89, 0x11, 0x95, 0xb9, 0x86, 0x07, 0xb4, 0x42, 0xbc, + 0x78, 0xcf, 0x04, 0xc6, 0x65, 0x57, 0x24, 0xb5, 0xdd, 0x40, 0xe0, 0x62, 0x8f, 0x62, 0x96, 0x0c, 0x6d, 0x0f, 0xca, + 0x03, 0x7d, 0x31, 0x9a, 0x6e, 0x01, 0xd3, 0xf2, 0xda, 0xd9, 0x45, 0x6a, 0x7b, 0xd5, 0x54, 0x01, 0xcc, 0x92, 0xe5, + 0xf1, 0x19, 0xb2, 0xee, 0x57, 0xd0, 0x45, 0x0c, 0x18, 0x1b, 0x57, 0xe6, 0xdc, 0xf9, 0xaa, 0x15, 0xf1, 0x8d, 0x26, + 0xd2, 0xa4, 0x3e, 0xa2, 0xbe, 0xfd, 0xb0, 0x56, 0x57, 0x39, 0x48, 0xe0, 0x1e, 0x79, 0x77, 0xc4, 0xa5, 0xa3, 0xcf, + 0x2c, 0x36, 0xab, 0xf4, 0x2d, 0x75, 0x2d, 0x6e, 0x31, 0xec, 0x15, 0xf7, 0xc0, 0xfe, 0xc0, 0xb8, 0x45, 0x2c, 0xe2, + 0xed, 0xac, 0x96, 0xc2, 0xba, 0x30, 0x47, 0x8e, 0xb1, 0xf6, 0xe0, 0x15, 0xaf, 0xd6, 0x0c, 0xcc, 0x30, 0xe3, 0x8c, + 0xe4, 0x8d, 0x71, 0xaf, 0x6a, 0xd3, 0x91, 0xab, 0x00, 0xa2, 0x6f, 0x4e, 0x97, 0xe4, 0xf0, 0x4a, 0x96, 0xab, 0xce, + 0x90, 0x7f, 0x86, 0x75, 0xd6, 0x8b, 0x13, 0x70, 0x93, 0xa6, 0xac, 0xc4, 0xc4, 0x14, 0x71, 0xb9, 0x59, 0xc6, 0x3c, + 0x4d, 0x9f, 0x45, 0x3b, 0x38, 0x85, 0x91, 0xc0, 0x11, 0xfb, 0xc6, 0x32, 0x2c, 0x26, 0x6c, 0xc4, 0x44, 0x1a, 0x95, + 0x52, 0xc2, 0x7a, 0x72, 0xa9, 0x25, 0x7f, 0x99, 0xcb, 0xab, 0x2f, 0xb7, 0x09, 0x0e, 0x28, 0x6a, 0x60, 0x39, 0x34, + 0x8e, 0x5b, 0x06, 0x12, 0xb1, 0x18, 0x10, 0xa3, 0x56, 0xe5, 0x72, 0x32, 0xaa, 0x93, 0xfa, 0x0a, 0xb9, 0x50, 0x91, + 0x07, 0xb7, 0x04, 0x4a, 0xfe, 0x02, 0x53, 0x07, 0xd3, 0x52, 0xbb, 0x69, 0xb1, 0x49, 0xf2, 0x8e, 0x19, 0x90, 0x5c, + 0x7d, 0x0d, 0x0f, 0x8d, 0x5f, 0x86, 0x37, 0x14, 0x3d, 0x1d, 0x23, 0xe4, 0xb4, 0x34, 0xe6, 0xd2, 0x7f, 0x23, 0xcf, + 0xbe, 0x24, 0x60, 0x3f, 0x83, 0x98, 0x32, 0x70, 0x89, 0x8d, 0x0b, 0x92, 0xf2, 0x5a, 0x9e, 0xb2, 0xfb, 0x16, 0x94, + 0xef, 0x92, 0x49, 0x57, 0xa9, 0xac, 0x35, 0x56, 0xdd, 0xcf, 0x33, 0x96, 0x5f, 0x1d, 0x30, 0xcc, 0x4d, 0x46, 0x83, + 0x6c, 0xc9, 0xcc, 0xa6, 0xfc, 0x6a, 0xef, 0xc6, 0xb7, 0x3c, 0x94, 0x74, 0xa8, 0x56, 0xe9, 0xe6, 0xa5, 0x1b, 0x8e, + 0xf1, 0xc2, 0x0d, 0xc7, 0xb8, 0x43, 0xe7, 0xca, 0x15, 0xa9, 0x75, 0xfe, 0xfb, 0x52, 0xf8, 0x49, 0xec, 0xb5, 0xbe, + 0xde, 0x75, 0xfd, 0x95, 0xe9, 0xe9, 0x37, 0xa0, 0x6a, 0x64, 0x09, 0xdd, 0x84, 0x2a, 0x26, 0x23, 0x51, 0x62, 0xba, + 0x4a, 0x79, 0xd4, 0xd7, 0x88, 0x0b, 0x10, 0x37, 0x94, 0xbf, 0xf8, 0x97, 0xf0, 0xe2, 0x24, 0x40, 0x23, 0x6a, 0x3e, + 0xca, 0x52, 0xde, 0x18, 0x45, 0x93, 0x38, 0xb9, 0x0a, 0x66, 0x71, 0x63, 0x92, 0xa5, 0x59, 0x31, 0x05, 0xae, 0xf4, + 0x8a, 0x2b, 0xb0, 0xe1, 0x27, 0x8d, 0x59, 0xec, 0xbd, 0x64, 0xc9, 0x39, 0xe3, 0xf1, 0x20, 0xf2, 0xec, 0xfd, 0x1c, + 0xc4, 0x83, 0xf5, 0x36, 0xca, 0xf3, 0xec, 0xc2, 0xf6, 0x3e, 0x64, 0xa7, 0xc0, 0xb4, 0xde, 0xbb, 0xcb, 0xab, 0x33, + 0x96, 0x7a, 0x1f, 0x4f, 0x67, 0x29, 0x9f, 0x79, 0x45, 0x94, 0x16, 0x8d, 0x82, 0xe5, 0xf1, 0x08, 0xd4, 0x44, 0x92, + 0xe5, 0x0d, 0xcc, 0x7f, 0x9e, 0xb0, 0x20, 0x89, 0xcf, 0xc6, 0xdc, 0x1a, 0x46, 0xf9, 0xa7, 0x4e, 0xa3, 0x31, 0xcd, + 0xe3, 0x49, 0x94, 0x5f, 0x35, 0xa8, 0x45, 0x70, 0xaf, 0xb9, 0x1b, 0x7d, 0x36, 0x7a, 0xd0, 0xe1, 0x39, 0xf4, 0x8d, + 0x91, 0x8a, 0x01, 0x08, 0x1f, 0x6b, 0xf7, 0x61, 0x73, 0x52, 0x6c, 0x88, 0x13, 0xa5, 0x28, 0xe5, 0xe5, 0x89, 0x77, + 0x01, 0xb6, 0xed, 0x89, 0x7f, 0xca, 0x53, 0x0f, 0x7c, 0x39, 0x9e, 0xa5, 0xf3, 0xc1, 0x2c, 0x2f, 0x60, 0x80, 0x69, + 0x16, 0xa7, 0x9c, 0xe5, 0x9d, 0xd3, 0x2c, 0x07, 0xb2, 0x35, 0xf2, 0x68, 0x18, 0xcf, 0x8a, 0xe0, 0xc1, 0xf4, 0xb2, + 0x83, 0xb6, 0xc2, 0x59, 0x9e, 0xcd, 0xd2, 0xa1, 0x9c, 0x2b, 0x4e, 0x61, 0x63, 0xc4, 0xdc, 0xac, 0xa0, 0x37, 0xa1, + 0x00, 0x7c, 0x29, 0x8b, 0xf2, 0xc6, 0x19, 0x76, 0x46, 0x43, 0xbf, 0x39, 0x64, 0x67, 0x5e, 0x7e, 0x76, 0x1a, 0x39, + 0xad, 0xf6, 0x63, 0x4f, 0xfd, 0xf9, 0x0f, 0x5d, 0x30, 0xdc, 0x57, 0x16, 0xb7, 0x9a, 0xcd, 0xbf, 0x71, 0x3b, 0x0b, + 0xb3, 0x10, 0x40, 0x41, 0x6b, 0x7a, 0x69, 0x15, 0x59, 0x02, 0xeb, 0xb3, 0xaa, 0x67, 0x67, 0x0a, 0x7e, 0x53, 0x9c, + 0x9e, 0x05, 0xed, 0xe9, 0x65, 0x89, 0xd8, 0x05, 0x22, 0x21, 0x53, 0x22, 0x29, 0x9f, 0xe6, 0xbf, 0x15, 0xe2, 0x27, + 0xab, 0x21, 0x6e, 0x2b, 0x88, 0x2b, 0xaa, 0x37, 0x86, 0xb0, 0x0f, 0x88, 0xfc, 0xad, 0x42, 0x00, 0x32, 0x06, 0x27, + 0x30, 0x57, 0x70, 0xd0, 0xc3, 0x6f, 0x06, 0xa3, 0xbd, 0x1a, 0x8c, 0x27, 0xb7, 0x81, 0x91, 0xa7, 0xc3, 0x79, 0x7d, + 0x5d, 0x5b, 0xe0, 0x9c, 0x76, 0xc6, 0x0c, 0xf9, 0x29, 0x68, 0xe3, 0xf7, 0x8b, 0x78, 0xc8, 0xc7, 0xe2, 0x2b, 0xb1, + 0xf3, 0x85, 0xa8, 0x7b, 0xd8, 0x6c, 0x8a, 0xe7, 0x02, 0x14, 0x5a, 0xd0, 0xf2, 0xb1, 0x01, 0x30, 0xd1, 0xe7, 0xeb, + 0x5e, 0x62, 0xf3, 0xed, 0xad, 0x6f, 0xaa, 0xf1, 0xb8, 0xca, 0x1b, 0x14, 0x2a, 0x42, 0xbd, 0xb3, 0x05, 0x33, 0xde, + 0x8a, 0x6e, 0x4b, 0x1f, 0x54, 0xf5, 0xbe, 0xe5, 0xa4, 0xf5, 0x02, 0xe6, 0x99, 0xb9, 0x40, 0x9d, 0xac, 0x8b, 0x21, + 0xa9, 0x46, 0xc3, 0x05, 0xbd, 0xc1, 0x31, 0x84, 0x44, 0x07, 0x82, 0x4e, 0xd1, 0xcb, 0xe9, 0x9d, 0x1a, 0xa9, 0x1b, + 0xe4, 0x4e, 0xea, 0xc2, 0x96, 0x4f, 0xb5, 0x5c, 0x2f, 0xb6, 0xb6, 0xc0, 0xcb, 0xfe, 0x9c, 0xcb, 0x06, 0x20, 0xbd, + 0x2b, 0x49, 0x6b, 0xbc, 0x87, 0x44, 0xb9, 0x7c, 0xd9, 0x80, 0x28, 0x07, 0xbe, 0x3e, 0x1f, 0xa3, 0xdf, 0xad, 0xaf, + 0xae, 0x1b, 0x29, 0x35, 0x3b, 0xb6, 0xdb, 0xe3, 0x3a, 0x2b, 0x0b, 0xb3, 0xcf, 0x78, 0x89, 0xa3, 0x7c, 0xc9, 0x43, + 0x1c, 0xd1, 0x7b, 0x15, 0x0a, 0x37, 0x4d, 0x39, 0x69, 0xa3, 0xbb, 0x3a, 0x69, 0xf0, 0x35, 0xa6, 0xcc, 0x67, 0x15, + 0x27, 0x07, 0x37, 0xe6, 0x78, 0x20, 0xae, 0x20, 0x16, 0x55, 0x96, 0x7d, 0x44, 0xd0, 0x0b, 0xbf, 0x0b, 0x94, 0x14, + 0x46, 0x2e, 0xbf, 0xe2, 0xbf, 0xc3, 0xe3, 0x70, 0x34, 0xfa, 0x45, 0x36, 0xcb, 0x07, 0x78, 0x39, 0x60, 0x45, 0x28, + 0xc2, 0x26, 0x4b, 0xc0, 0xf6, 0xb8, 0x56, 0x40, 0x0c, 0xf3, 0x2c, 0xcc, 0xb7, 0x2f, 0x30, 0x3a, 0x9d, 0x11, 0x97, + 0x1f, 0x64, 0xf8, 0x45, 0xa1, 0x84, 0x3a, 0x75, 0x48, 0x89, 0x78, 0x74, 0x31, 0xd4, 0x9f, 0xa5, 0x31, 0x88, 0xe0, + 0xe3, 0x78, 0x48, 0x17, 0x62, 0xe2, 0x21, 0x9d, 0x90, 0x34, 0x28, 0x23, 0x0a, 0x43, 0xee, 0x50, 0x20, 0x17, 0x06, + 0xbf, 0xcb, 0x0c, 0x1b, 0xbb, 0x61, 0xe3, 0x29, 0x87, 0xa1, 0xc3, 0x87, 0xd9, 0x24, 0x8a, 0xd3, 0x00, 0x5f, 0x5c, + 0xe2, 0xe9, 0x11, 0x03, 0xec, 0xe2, 0xc1, 0xa7, 0x5a, 0xa3, 0x96, 0xeb, 0xff, 0x04, 0x02, 0x8e, 0xfa, 0x63, 0x32, + 0x0b, 0x91, 0x55, 0x10, 0x31, 0x54, 0x64, 0xde, 0x57, 0x7a, 0xde, 0x33, 0xab, 0x55, 0xcc, 0xb4, 0xbe, 0x0e, 0xcd, + 0x85, 0xe5, 0xd2, 0x67, 0xd8, 0xf5, 0x52, 0x10, 0xac, 0x5c, 0xe7, 0xd1, 0x53, 0x10, 0x67, 0x8f, 0xd1, 0x47, 0xaf, + 0xd1, 0x0a, 0x5a, 0xda, 0x2f, 0xaf, 0x5d, 0xb5, 0x15, 0x89, 0x3a, 0xf2, 0xba, 0x26, 0xe1, 0xa1, 0xbf, 0x0b, 0x5c, + 0xab, 0x67, 0x8d, 0xaf, 0x27, 0x37, 0x1d, 0x46, 0xa7, 0xce, 0x52, 0xa7, 0x06, 0x04, 0x1d, 0x74, 0xac, 0x99, 0xca, + 0x2d, 0x2b, 0xbc, 0xb5, 0xf1, 0x67, 0x0b, 0xcd, 0x89, 0xaf, 0x1e, 0x90, 0x33, 0xd2, 0x2b, 0x9e, 0x56, 0xf0, 0x5d, + 0x29, 0x09, 0xb2, 0x78, 0x21, 0x7f, 0xa1, 0x99, 0x00, 0xe5, 0x4a, 0x1f, 0x64, 0x2f, 0xd4, 0x8a, 0x47, 0x26, 0x22, + 0xde, 0xab, 0x9b, 0x50, 0xd6, 0xd8, 0x32, 0x5c, 0xe8, 0x8b, 0x16, 0x5c, 0xc1, 0x8f, 0x06, 0xd3, 0x88, 0xe1, 0xbd, + 0x94, 0x93, 0xcd, 0xf9, 0x97, 0xbc, 0xdc, 0xd9, 0x9c, 0xab, 0x86, 0xe2, 0x7b, 0x3c, 0xc4, 0x4f, 0x06, 0xf2, 0x6b, + 0x2e, 0xac, 0xc7, 0xc0, 0x7e, 0xff, 0xee, 0xe0, 0xd0, 0xf6, 0x4e, 0xb3, 0xe1, 0x55, 0x60, 0xc3, 0xee, 0x64, 0x76, + 0xe9, 0xfa, 0x7c, 0xcc, 0x52, 0x47, 0xb1, 0x78, 0x96, 0x30, 0x90, 0x08, 0x67, 0xe2, 0xb2, 0xe3, 0xa2, 0xe7, 0x3b, + 0x3c, 0xd9, 0xa3, 0xb7, 0x21, 0x75, 0xf7, 0xb8, 0x78, 0x51, 0x18, 0xcf, 0xf1, 0x6b, 0x17, 0x63, 0xff, 0x7b, 0x3b, + 0xf0, 0x05, 0x1f, 0x0e, 0x70, 0xcf, 0xd0, 0xd3, 0xe6, 0x7c, 0x89, 0x93, 0x7a, 0x38, 0xc4, 0xb8, 0x2b, 0x50, 0x28, + 0xa8, 0xd5, 0x49, 0x30, 0x3c, 0x39, 0x29, 0xe1, 0x2b, 0x8c, 0xb5, 0xa3, 0xc6, 0x45, 0x08, 0x55, 0x7f, 0xcd, 0x5d, + 0xf2, 0x75, 0x3b, 0x38, 0x04, 0xce, 0x3b, 0xc4, 0x06, 0xc4, 0x5d, 0xd8, 0x7b, 0xa8, 0x4b, 0x68, 0xd3, 0x8a, 0xa2, + 0x75, 0x10, 0x88, 0x86, 0x15, 0xd3, 0x8b, 0x10, 0x61, 0xb5, 0xba, 0x0a, 0xa4, 0xa1, 0x09, 0xdd, 0x89, 0x8b, 0x9f, + 0x04, 0x19, 0x7c, 0x12, 0x1d, 0x4e, 0xcc, 0x37, 0x84, 0x88, 0xcb, 0xfc, 0x9a, 0x5a, 0x47, 0x7f, 0x01, 0xdb, 0xc3, + 0xbb, 0x38, 0xa1, 0x96, 0x4a, 0x1d, 0xa1, 0x9d, 0x84, 0x6a, 0xbb, 0xa9, 0xec, 0x0e, 0xd0, 0xfd, 0x49, 0x34, 0x2d, + 0x58, 0xa0, 0xbe, 0x48, 0xcd, 0x84, 0x0a, 0x6e, 0xd9, 0x14, 0x90, 0x79, 0x31, 0xcf, 0xd0, 0x60, 0x58, 0xb6, 0x53, + 0x40, 0xf4, 0x39, 0x8d, 0xc6, 0xa0, 0x71, 0x7a, 0xe6, 0x96, 0x7c, 0x3c, 0x37, 0xf5, 0xda, 0x23, 0xd0, 0x6b, 0x98, + 0x93, 0xd7, 0x00, 0x4f, 0xed, 0x2c, 0x0d, 0x12, 0x36, 0xe2, 0x25, 0xc7, 0x4b, 0x5f, 0x73, 0x65, 0x48, 0xf8, 0xed, + 0x87, 0xa0, 0xeb, 0x2c, 0x1f, 0xff, 0xbd, 0x79, 0x62, 0xe8, 0x18, 0xa4, 0xa0, 0x9b, 0x28, 0x0b, 0x14, 0x33, 0xec, + 0x01, 0x5c, 0xf3, 0x79, 0x6e, 0x4c, 0x34, 0x60, 0x68, 0x64, 0x95, 0x1c, 0x64, 0xf2, 0xd8, 0xe3, 0xb9, 0xd9, 0x2e, + 0x75, 0xe7, 0x4b, 0x18, 0x2c, 0xeb, 0xfa, 0x5d, 0xb7, 0x2c, 0xc8, 0x64, 0x5d, 0x6e, 0xac, 0x0c, 0xa6, 0xfa, 0xd3, + 0x12, 0xf9, 0x0c, 0xd3, 0xae, 0x14, 0xc1, 0xd2, 0xb9, 0xe8, 0x71, 0x17, 0x62, 0xd6, 0x8c, 0x4e, 0xcf, 0xec, 0xe1, + 0x96, 0x71, 0x3a, 0x9d, 0xf1, 0x23, 0x0a, 0xd4, 0xe6, 0x78, 0x9d, 0xa0, 0x3f, 0x17, 0x73, 0x83, 0x17, 0x3c, 0x70, + 0x10, 0x00, 0xab, 0x61, 0x3d, 0x01, 0x6a, 0xba, 0xca, 0xf0, 0xf0, 0x1f, 0x23, 0x71, 0x4b, 0x9f, 0x5a, 0xaf, 0xa0, + 0xd2, 0x09, 0x58, 0xdd, 0x1d, 0xce, 0x9d, 0xa3, 0x37, 0x8e, 0xdb, 0xf7, 0x5e, 0x19, 0x2f, 0x2f, 0xb1, 0xd5, 0x1e, + 0xb0, 0x3d, 0xa4, 0xf7, 0xca, 0x26, 0x26, 0x93, 0x53, 0xb3, 0x57, 0x21, 0x36, 0x7c, 0xeb, 0xd8, 0xac, 0x98, 0x36, + 0x84, 0x48, 0x6a, 0x10, 0x33, 0xda, 0xd8, 0x55, 0x05, 0x56, 0xbf, 0xe2, 0x73, 0x92, 0x36, 0x5c, 0xbf, 0x29, 0xe4, + 0x88, 0xf7, 0xcd, 0x5b, 0x2d, 0xb5, 0x80, 0x3a, 0xd4, 0xb9, 0x86, 0xe4, 0x83, 0x47, 0xf9, 0xd6, 0x1b, 0x25, 0x37, + 0x4e, 0xf6, 0xeb, 0x92, 0x0c, 0xf6, 0x59, 0xa9, 0xdf, 0xa8, 0x06, 0x5a, 0x18, 0xe7, 0x87, 0x8d, 0x24, 0xf7, 0xdd, + 0x53, 0xb2, 0x12, 0x55, 0x1c, 0x9c, 0xae, 0x2c, 0xaa, 0x13, 0x0d, 0xa1, 0x50, 0x63, 0x3c, 0x77, 0xad, 0x25, 0xdd, + 0x76, 0x2a, 0x59, 0x24, 0x6c, 0x4c, 0x8b, 0xf0, 0x08, 0x6d, 0x30, 0xfa, 0x6c, 0xeb, 0xcf, 0x03, 0x50, 0x7f, 0x9f, + 0x42, 0x7b, 0x73, 0xee, 0xb8, 0xab, 0xef, 0xcd, 0x29, 0xcf, 0x50, 0x49, 0x61, 0x23, 0x63, 0xb1, 0x26, 0x5c, 0xd1, + 0x41, 0xb5, 0xbb, 0x28, 0x3e, 0xf7, 0x76, 0xc4, 0x44, 0xb0, 0xdb, 0x8f, 0xe5, 0x8b, 0x9e, 0xb8, 0x29, 0x12, 0x91, + 0xbc, 0xa2, 0xdc, 0x22, 0x36, 0x09, 0xed, 0x5b, 0x79, 0xc7, 0xb6, 0x84, 0x94, 0x42, 0x40, 0x95, 0xc0, 0x02, 0xe0, + 0x75, 0x19, 0x93, 0xb0, 0xc7, 0x92, 0x0c, 0x36, 0xce, 0x05, 0x8a, 0x00, 0x03, 0x47, 0x3c, 0x8a, 0x13, 0xd1, 0x45, + 0x06, 0xf6, 0x94, 0x03, 0xa8, 0x31, 0xc2, 0x23, 0xf5, 0x3a, 0x2e, 0x75, 0x12, 0x12, 0x66, 0x7b, 0x3b, 0x15, 0xdc, + 0x84, 0x19, 0xed, 0x32, 0xf3, 0x00, 0xab, 0xc2, 0x50, 0xd4, 0x01, 0x71, 0xe9, 0xda, 0x0c, 0x02, 0x58, 0xa8, 0x60, + 0x87, 0x97, 0xaa, 0x2b, 0x2c, 0x02, 0x96, 0x1c, 0x13, 0x85, 0xc1, 0xc8, 0x63, 0x5c, 0x13, 0x36, 0x17, 0xd9, 0x8f, + 0x0a, 0xda, 0x74, 0x09, 0xda, 0xb4, 0x0e, 0xed, 0x09, 0x12, 0xbd, 0xb7, 0x39, 0x8f, 0xcb, 0x10, 0xbe, 0xa5, 0x83, + 0x6c, 0xc8, 0x3e, 0x7e, 0x78, 0x85, 0x77, 0x00, 0xa1, 0x3d, 0x38, 0x0b, 0x99, 0x5b, 0x9e, 0xc8, 0xc5, 0x31, 0x75, + 0x82, 0xd8, 0xdb, 0x16, 0xcd, 0x45, 0x74, 0x05, 0x8a, 0xf6, 0x04, 0xe4, 0x6c, 0x48, 0x05, 0x61, 0x98, 0x53, 0x2f, + 0x0e, 0x4b, 0x2a, 0x5a, 0x0b, 0x99, 0x2e, 0x1a, 0x21, 0x11, 0x68, 0x67, 0x56, 0x34, 0xc0, 0x9c, 0x59, 0x93, 0x0e, + 0xc3, 0xf8, 0x5c, 0x73, 0x1b, 0x5d, 0x20, 0xea, 0xee, 0x01, 0x43, 0xb3, 0x04, 0xc6, 0xcc, 0xaf, 0xaf, 0x9b, 0x30, + 0x94, 0x78, 0xb4, 0xf6, 0x48, 0x36, 0x88, 0x77, 0x61, 0xc2, 0xcc, 0x2d, 0x4c, 0x4f, 0xc2, 0xab, 0x7a, 0x3d, 0x95, + 0x6f, 0x13, 0xc8, 0x01, 0x00, 0x46, 0x3a, 0xea, 0x27, 0x3e, 0xd0, 0xe6, 0x0d, 0x94, 0xc6, 0xc3, 0xe5, 0x32, 0xb0, + 0x4a, 0xa7, 0x58, 0x9a, 0x5d, 0x5f, 0xb7, 0xe0, 0x71, 0x12, 0xa7, 0xf8, 0x04, 0x33, 0xd3, 0x0d, 0x38, 0x78, 0x04, + 0xd3, 0x1c, 0xd8, 0x16, 0x6a, 0xa2, 0x4b, 0xac, 0x49, 0x55, 0x4d, 0x74, 0x09, 0xf2, 0x48, 0x54, 0x69, 0xf2, 0x14, + 0xc8, 0x70, 0xff, 0x1f, 0x16, 0x34, 0x93, 0x8b, 0x67, 0x69, 0xd2, 0x01, 0x98, 0x20, 0x2d, 0x35, 0xf1, 0xf6, 0x76, + 0x80, 0xcc, 0xb0, 0x18, 0xd2, 0xfa, 0x91, 0x3b, 0xae, 0x7a, 0x8f, 0x91, 0x90, 0x64, 0x6e, 0x2d, 0x0d, 0x81, 0x8a, + 0xd0, 0x1a, 0x04, 0xdf, 0x62, 0x78, 0x4c, 0x9b, 0x03, 0xf4, 0xbc, 0xd4, 0xfe, 0x0b, 0xb2, 0xa6, 0xea, 0xe0, 0xd9, + 0x7f, 0xfd, 0xc7, 0xbf, 0xb3, 0x3d, 0xb1, 0xb9, 0xb2, 0xd1, 0x08, 0x4c, 0x65, 0xeb, 0x0e, 0x7d, 0xfe, 0xd7, 0xdf, + 0xff, 0xdf, 0xff, 0xf3, 0x5f, 0x75, 0xb7, 0x14, 0x7a, 0x9d, 0xc8, 0x83, 0x3f, 0x25, 0x1d, 0x0c, 0x30, 0x15, 0x1a, + 0xa3, 0x28, 0x5d, 0x87, 0xc3, 0x91, 0x89, 0x43, 0x31, 0x65, 0x6c, 0xe8, 0xd9, 0x96, 0xed, 0x2d, 0x95, 0x1e, 0x27, + 0xec, 0x9c, 0xc9, 0xb7, 0x9e, 0xad, 0x9a, 0x6a, 0x45, 0x8f, 0x01, 0x28, 0x34, 0x2e, 0xcf, 0x3f, 0x25, 0x6f, 0x9b, + 0xa8, 0x48, 0xa9, 0x52, 0xeb, 0x87, 0xb4, 0xab, 0x8b, 0x0b, 0xcf, 0x36, 0xa6, 0x5f, 0x0b, 0x57, 0x6f, 0x4d, 0x79, + 0xd0, 0xf4, 0x9a, 0xeb, 0x20, 0xf3, 0xc0, 0x8f, 0xb4, 0xed, 0xbe, 0xa2, 0x11, 0x85, 0x7b, 0xcc, 0x0b, 0xec, 0xeb, + 0x68, 0x75, 0x2b, 0xf6, 0xa7, 0x39, 0x0e, 0x95, 0xb2, 0xa2, 0xb8, 0x05, 0x79, 0x58, 0x3e, 0xcf, 0xae, 0x5a, 0xdb, + 0x6b, 0x46, 0x01, 0x14, 0xda, 0x0f, 0x1f, 0x0a, 0x70, 0x3d, 0x47, 0xbb, 0x90, 0x66, 0x63, 0x36, 0x1a, 0x81, 0x10, + 0x29, 0xdc, 0x2a, 0x1f, 0x74, 0x14, 0x27, 0x1c, 0xcf, 0xb3, 0xc3, 0xae, 0xfd, 0x16, 0x36, 0x06, 0x5e, 0x0f, 0x75, + 0xa5, 0x5f, 0xaf, 0x32, 0xfd, 0x94, 0xd0, 0x5d, 0x0d, 0x97, 0x18, 0xb2, 0x0e, 0x93, 0x9c, 0xe6, 0xfa, 0x5a, 0xf9, + 0xcb, 0xb5, 0xf2, 0x3a, 0x39, 0x33, 0x72, 0x88, 0x57, 0xef, 0x9b, 0xbb, 0xec, 0x8e, 0x7f, 0xfd, 0xa7, 0xbf, 0xff, + 0x6f, 0x00, 0x06, 0x8e, 0x73, 0xb7, 0xad, 0x01, 0x1d, 0xfe, 0x27, 0x74, 0x98, 0xa5, 0x77, 0xef, 0xf2, 0xd7, 0xff, + 0xf2, 0xdf, 0xa1, 0x07, 0x5d, 0x60, 0x86, 0x7d, 0xa4, 0x40, 0x1f, 0x60, 0xd8, 0xe8, 0x77, 0xc1, 0x5e, 0x1b, 0xf7, + 0x2e, 0x70, 0xfc, 0x03, 0xa2, 0x5a, 0xf0, 0x6c, 0x7a, 0x57, 0xb8, 0x11, 0xd3, 0x41, 0x92, 0x15, 0xcc, 0x04, 0x5c, + 0x58, 0x0a, 0xbf, 0x0f, 0x72, 0x82, 0x64, 0x0a, 0x12, 0xb4, 0xb0, 0xcc, 0xa1, 0x25, 0xaf, 0xdc, 0x28, 0x08, 0x57, + 0x32, 0x54, 0xc1, 0x38, 0x91, 0x82, 0xac, 0xb9, 0x1a, 0xd3, 0x88, 0xb2, 0x25, 0x5e, 0x22, 0xe9, 0xae, 0x25, 0x97, + 0xd0, 0x58, 0xb7, 0xcc, 0xbb, 0x62, 0x7f, 0x89, 0x69, 0xc5, 0x99, 0xd7, 0xf2, 0xf0, 0xb5, 0x12, 0x50, 0x5d, 0xc7, + 0x2b, 0x4a, 0xa3, 0xcb, 0x15, 0xa5, 0xa8, 0x04, 0x35, 0x6c, 0x60, 0xed, 0x4d, 0xc4, 0x4b, 0x2f, 0xf4, 0xeb, 0x2e, + 0x6a, 0xd0, 0x91, 0x2a, 0xc3, 0x53, 0xfc, 0xfa, 0x2b, 0x00, 0xe4, 0x50, 0x42, 0xad, 0x1d, 0xe3, 0xbd, 0x1a, 0xbc, + 0x45, 0x3d, 0xcb, 0x19, 0xec, 0x99, 0x0b, 0xf3, 0x68, 0xfe, 0xe6, 0xc6, 0x63, 0x10, 0x0f, 0x3d, 0xb0, 0x27, 0xf5, + 0xaa, 0xde, 0x38, 0x6e, 0xf9, 0x2f, 0xff, 0xec, 0xfb, 0xff, 0xf2, 0xcf, 0xb7, 0x36, 0xc5, 0x51, 0xc1, 0x65, 0xe7, + 0xd5, 0xb0, 0xeb, 0xa9, 0xbb, 0x7a, 0xa6, 0x3a, 0xb9, 0x57, 0xb7, 0x59, 0xa2, 0x3f, 0xd6, 0x2f, 0x91, 0x7f, 0xa9, + 0x50, 0x50, 0xdf, 0xfa, 0x2d, 0x80, 0x21, 0x5e, 0xb7, 0x42, 0x86, 0x8d, 0x7e, 0x17, 0x68, 0x27, 0x6e, 0x70, 0xa7, + 0x15, 0xf9, 0xed, 0x14, 0xbe, 0x0d, 0x87, 0xdf, 0x09, 0xbe, 0x48, 0x07, 0x06, 0xd0, 0x4e, 0xd4, 0x8d, 0xa9, 0x5a, + 0x57, 0xbc, 0x74, 0xd9, 0x5b, 0x2a, 0x91, 0x6a, 0x25, 0x68, 0xba, 0xdd, 0xe6, 0xd6, 0x96, 0x83, 0xdd, 0xdf, 0xe0, + 0x9b, 0x21, 0xf6, 0x4e, 0x73, 0x15, 0x03, 0xb9, 0x41, 0x34, 0xe0, 0x10, 0x75, 0xac, 0x68, 0xd0, 0x25, 0xb9, 0x80, + 0xa5, 0x98, 0x61, 0x8a, 0x60, 0x7a, 0x60, 0x0e, 0x0b, 0x7b, 0xed, 0x99, 0x70, 0x6c, 0x82, 0x45, 0xd6, 0x96, 0x0e, + 0x4f, 0x8d, 0xe8, 0x9e, 0x75, 0x48, 0xf4, 0xa2, 0xc6, 0xac, 0xb2, 0x97, 0xc9, 0x4b, 0x44, 0x03, 0xf1, 0x44, 0xbc, + 0x2b, 0xe3, 0xeb, 0x75, 0xf1, 0xf6, 0xef, 0x6f, 0x8f, 0xb7, 0xc7, 0x77, 0x8c, 0xb7, 0x7f, 0xff, 0x07, 0xc7, 0xdb, + 0xbf, 0x36, 0xe3, 0xed, 0xb8, 0x88, 0x3f, 0xdf, 0x29, 0x26, 0xae, 0x22, 0x95, 0xd9, 0x45, 0x11, 0xb6, 0xa4, 0xa5, + 0x04, 0x8e, 0x34, 0x06, 0xc4, 0xff, 0xed, 0xe3, 0xdb, 0x30, 0xd1, 0x42, 0x74, 0x9b, 0xc2, 0xd9, 0x92, 0x07, 0x99, + 0x0a, 0x26, 0x37, 0x75, 0xee, 0x77, 0xe3, 0x81, 0xba, 0xec, 0x0a, 0x2e, 0x8c, 0xab, 0x0f, 0x04, 0xda, 0x2a, 0xdc, + 0x1c, 0xd0, 0xdb, 0xaa, 0x75, 0xc7, 0xf6, 0xb6, 0x4a, 0x3a, 0x36, 0x47, 0xe8, 0xa8, 0xb3, 0x64, 0x71, 0x53, 0x72, + 0x6e, 0xff, 0xa7, 0xa3, 0x56, 0x67, 0xb7, 0x35, 0x81, 0xde, 0xc0, 0x87, 0xf0, 0xd4, 0xec, 0xec, 0xee, 0xe2, 0xd3, + 0x85, 0x7a, 0x6a, 0xe3, 0x53, 0xac, 0x9e, 0x1e, 0xe2, 0xd3, 0x40, 0x3d, 0x3d, 0xc2, 0xa7, 0xa1, 0x7a, 0x7a, 0x8c, + 0x4f, 0xe7, 0x76, 0x79, 0xc4, 0x34, 0x70, 0x8f, 0xdd, 0xbe, 0x27, 0x4c, 0x51, 0x55, 0xf6, 0xd8, 0x6b, 0x61, 0x40, + 0x3b, 0x3a, 0x0b, 0x62, 0x4f, 0x38, 0xd4, 0x41, 0xe1, 0x5d, 0x8c, 0x59, 0x1a, 0x50, 0x4e, 0xf4, 0x73, 0x7c, 0x5b, + 0x10, 0xd8, 0xc0, 0x87, 0xf1, 0x84, 0xa9, 0xd7, 0xa6, 0x2b, 0xac, 0x41, 0x25, 0x1f, 0x35, 0xfb, 0x65, 0x47, 0xaf, + 0x93, 0x88, 0x84, 0xab, 0xf4, 0x4e, 0x5a, 0xb9, 0xaa, 0x4e, 0x4c, 0xd7, 0xd0, 0x2b, 0xbc, 0x26, 0xa8, 0x6a, 0xf8, + 0x95, 0x23, 0x90, 0xcd, 0x8d, 0x4b, 0x70, 0x2c, 0x57, 0x06, 0x5a, 0x11, 0x22, 0x1d, 0x68, 0x25, 0x9c, 0xf4, 0xd3, + 0x61, 0x74, 0xa6, 0xbf, 0xbf, 0x01, 0xdb, 0x21, 0x3a, 0x93, 0x2d, 0xd7, 0x07, 0x56, 0x09, 0x44, 0x33, 0xa8, 0xaa, + 0x80, 0x40, 0xc7, 0x13, 0x97, 0x06, 0xc3, 0x04, 0x32, 0x56, 0x8a, 0xd4, 0xa9, 0x87, 0x59, 0x69, 0xfa, 0x7a, 0x11, + 0x50, 0xb4, 0x2a, 0xd8, 0x03, 0x13, 0x86, 0x4a, 0x05, 0x85, 0xa1, 0x02, 0x0b, 0x44, 0xf5, 0x9a, 0x70, 0xaa, 0x72, + 0xfd, 0xd6, 0x07, 0x55, 0x2d, 0x15, 0x4f, 0x35, 0xcf, 0xa0, 0xf5, 0x01, 0xf4, 0x72, 0x14, 0xef, 0x5e, 0x6b, 0x80, + 0xff, 0xc9, 0x18, 0xe1, 0xbd, 0xd1, 0x68, 0x74, 0x63, 0x7c, 0xf5, 0xde, 0x70, 0xc4, 0xda, 0xec, 0x61, 0x07, 0xcf, + 0x27, 0x1b, 0x32, 0x6a, 0xd7, 0x2a, 0x89, 0x76, 0xf3, 0xbb, 0x35, 0xc6, 0x00, 0x1f, 0x1f, 0xcf, 0xef, 0x1e, 0x6b, + 0x2d, 0x81, 0x2a, 0xf3, 0x09, 0x48, 0xc5, 0x38, 0x0d, 0x9a, 0xa5, 0x7f, 0x2e, 0x83, 0x93, 0xf7, 0x9e, 0x3c, 0x79, + 0x52, 0xfa, 0x43, 0xf5, 0xd4, 0x1c, 0x0e, 0x4b, 0x7f, 0x30, 0xd7, 0x68, 0x34, 0x9b, 0xa3, 0x51, 0xe9, 0xc7, 0xaa, + 0x60, 0xb7, 0x3d, 0x18, 0xee, 0xb6, 0x4b, 0xff, 0xc2, 0x68, 0x51, 0xfa, 0x4c, 0x3e, 0xe5, 0x6c, 0x58, 0x3b, 0xe4, + 0x7c, 0x0c, 0xde, 0xb6, 0x2f, 0x18, 0x6d, 0x8e, 0x86, 0xb6, 0xf8, 0x1a, 0x44, 0x33, 0x9e, 0xa1, 0x00, 0xee, 0x00, + 0x9f, 0x1f, 0x6d, 0xca, 0x6b, 0xcc, 0xe2, 0xad, 0xe4, 0x25, 0x6c, 0xa1, 0x9f, 0xcd, 0x60, 0x23, 0x32, 0x33, 0x05, + 0x19, 0x63, 0x15, 0x8b, 0xac, 0x55, 0x23, 0x67, 0x51, 0xf5, 0xcf, 0x61, 0x5c, 0xc5, 0x20, 0x51, 0xda, 0x60, 0x4b, + 0x91, 0x8c, 0xf3, 0xdd, 0x3a, 0x19, 0xff, 0xc5, 0xed, 0x32, 0xfe, 0xea, 0x6e, 0x22, 0xfe, 0x8b, 0x3f, 0x58, 0xc4, + 0x7f, 0x67, 0x8a, 0x78, 0x21, 0xc4, 0xf6, 0x79, 0x68, 0x0f, 0xc6, 0x6c, 0xf0, 0xe9, 0x34, 0xbb, 0x6c, 0xe0, 0x96, + 0xc8, 0x6d, 0x92, 0x9e, 0x93, 0xdf, 0x7a, 0x20, 0xaa, 0x06, 0x33, 0x5e, 0x71, 0x4e, 0x4a, 0xf2, 0x5d, 0x1a, 0xda, + 0xef, 0x94, 0xfd, 0x2e, 0x4a, 0x46, 0x23, 0x28, 0x1a, 0x8d, 0x6c, 0x75, 0x79, 0x03, 0xc4, 0x16, 0xb5, 0x7a, 0x5b, + 0x2b, 0xa1, 0x56, 0x9f, 0x7f, 0x6e, 0x96, 0x99, 0x05, 0x32, 0x64, 0x69, 0x86, 0x27, 0x65, 0xcd, 0x30, 0x2e, 0x70, + 0xab, 0xe1, 0x9b, 0xd7, 0x97, 0x5e, 0x69, 0x25, 0x02, 0xab, 0xcb, 0x00, 0x57, 0xf1, 0x55, 0xe3, 0xed, 0xa9, 0x55, + 0x84, 0x15, 0x16, 0x54, 0x66, 0xd6, 0x3d, 0xbd, 0x7a, 0x35, 0x74, 0xf6, 0xb9, 0x5b, 0xc6, 0xc5, 0xbb, 0x74, 0x21, + 0x63, 0x59, 0xc0, 0x18, 0x86, 0x26, 0x5a, 0x25, 0xcf, 0xce, 0xce, 0x92, 0xe5, 0x1c, 0x58, 0xd1, 0xbd, 0x57, 0xc3, + 0x37, 0x30, 0x3b, 0x4a, 0x5d, 0x46, 0x3f, 0x99, 0x21, 0x52, 0xfb, 0x28, 0x27, 0x5b, 0x1d, 0xed, 0xce, 0xa5, 0xfc, + 0x97, 0x49, 0x5f, 0x8c, 0x0e, 0x51, 0x69, 0xe0, 0x61, 0x59, 0xca, 0xcc, 0x5a, 0x20, 0xc4, 0x14, 0xdf, 0xff, 0x26, + 0x7a, 0xc6, 0xb7, 0x89, 0xf0, 0xe2, 0xc2, 0x88, 0x0b, 0xd6, 0x96, 0xab, 0x54, 0x81, 0x41, 0x11, 0xdd, 0xdb, 0xc7, + 0x10, 0xa5, 0x88, 0x11, 0x2a, 0x22, 0xda, 0x56, 0x8f, 0xbe, 0xca, 0x88, 0x65, 0x85, 0x21, 0x06, 0x33, 0xf5, 0x82, + 0xa8, 0x2a, 0x55, 0x50, 0x9a, 0x81, 0x6f, 0xaa, 0x11, 0xd4, 0xa2, 0x30, 0x1b, 0xc0, 0x9e, 0x0a, 0x31, 0x0a, 0xd3, + 0x90, 0x3c, 0xd8, 0x9c, 0x57, 0x2b, 0x0f, 0x5d, 0x25, 0xd8, 0x82, 0x79, 0x41, 0x06, 0x63, 0x87, 0xae, 0x55, 0x03, + 0x3d, 0x5d, 0x8a, 0xce, 0xdd, 0x7c, 0xee, 0x75, 0xe2, 0x17, 0x17, 0x1e, 0xfc, 0x59, 0x7f, 0x9a, 0x83, 0xd0, 0x39, + 0xfd, 0x14, 0xf3, 0x06, 0x8f, 0xa6, 0x0d, 0xb4, 0xee, 0x29, 0xc8, 0x23, 0xa5, 0x33, 0xe5, 0x6f, 0x88, 0x7b, 0x96, + 0x9d, 0x59, 0x81, 0xc7, 0x63, 0x64, 0xa3, 0x06, 0x69, 0x96, 0xb2, 0x4e, 0x3d, 0x4f, 0xc7, 0x3c, 0x6d, 0x51, 0xc4, + 0xea, 0xcf, 0x33, 0x3c, 0x4e, 0xe3, 0x57, 0x41, 0x53, 0x4a, 0xf5, 0xa6, 0x3a, 0x6a, 0x69, 0xae, 0x6c, 0x1f, 0x48, + 0xda, 0x6e, 0x93, 0xf2, 0xca, 0x97, 0x8f, 0x94, 0xd6, 0x1d, 0x09, 0xdd, 0x96, 0xb5, 0x82, 0xc1, 0x21, 0xf5, 0x67, + 0xa4, 0xfb, 0x2c, 0x16, 0x53, 0xd6, 0xca, 0x5d, 0x20, 0x0b, 0xa2, 0x11, 0xbe, 0x96, 0xf4, 0x2e, 0x2d, 0x4f, 0x29, + 0x65, 0x7c, 0x8e, 0x5a, 0x26, 0x68, 0x3d, 0x99, 0x5e, 0xde, 0x7d, 0xf8, 0x9b, 0xd1, 0x2f, 0x25, 0x8d, 0xd4, 0xcd, + 0x7f, 0xdb, 0xee, 0xe0, 0x3e, 0x48, 0xa2, 0xab, 0x20, 0x4e, 0x49, 0xe5, 0x9d, 0x62, 0x94, 0xa7, 0x33, 0xcd, 0x64, + 0xfa, 0x55, 0xce, 0x12, 0xfa, 0xed, 0x1f, 0xb9, 0x14, 0xbb, 0x8f, 0xa6, 0x97, 0x6a, 0x35, 0x5a, 0x0b, 0x69, 0x55, + 0x7f, 0x68, 0xf6, 0xd4, 0xfa, 0x74, 0xad, 0x7a, 0x06, 0xd0, 0x43, 0x80, 0x41, 0xe8, 0xd9, 0x46, 0x2e, 0xa0, 0x6a, + 0x42, 0x89, 0x91, 0x3f, 0x56, 0x0d, 0x64, 0xf9, 0xbb, 0x20, 0xb9, 0xa3, 0x82, 0x75, 0xf0, 0xfd, 0xb0, 0xf1, 0x20, + 0x4a, 0xa4, 0x2e, 0x9f, 0xc4, 0xc3, 0x61, 0xc2, 0x3a, 0x4a, 0x5d, 0x5b, 0xad, 0x47, 0x98, 0x7e, 0x65, 0x2e, 0x59, + 0x7d, 0x55, 0x0c, 0xe2, 0x69, 0x3a, 0x45, 0xa7, 0x60, 0x3e, 0xe0, 0x4b, 0x5e, 0x57, 0x92, 0x53, 0xe6, 0x25, 0x35, + 0x2b, 0xe2, 0xd1, 0xf7, 0x3a, 0x2e, 0x0f, 0xc1, 0x76, 0xa1, 0x05, 0x6f, 0x76, 0x78, 0x36, 0x0d, 0x1a, 0xbb, 0x75, + 0x44, 0xb0, 0x4a, 0xa3, 0xe0, 0xad, 0x40, 0xcb, 0x43, 0x65, 0x25, 0x04, 0xb4, 0xe5, 0xb7, 0x64, 0x19, 0x0d, 0x80, + 0x2f, 0x12, 0xd5, 0x45, 0x65, 0x1d, 0x99, 0x7f, 0x9b, 0xdd, 0xf2, 0xd9, 0xea, 0xdd, 0xf2, 0x99, 0xda, 0x2d, 0x37, + 0x73, 0xec, 0xbd, 0x51, 0x0b, 0xff, 0xeb, 0x54, 0x08, 0xc1, 0xaa, 0x00, 0x39, 0x2c, 0x34, 0xd3, 0x1a, 0x6d, 0xf8, + 0x87, 0x86, 0xc6, 0x18, 0x74, 0x13, 0xf3, 0xc9, 0xbc, 0xa6, 0x85, 0x85, 0xf8, 0xd7, 0xac, 0x55, 0xb5, 0x1e, 0x60, + 0x1d, 0xf6, 0x7a, 0xb8, 0x5c, 0xd7, 0xbe, 0x79, 0xd3, 0x82, 0xbc, 0xe2, 0x4e, 0xa0, 0x84, 0x31, 0x78, 0x0e, 0xd1, + 0xe9, 0x29, 0x94, 0x8e, 0xb2, 0xc1, 0xac, 0xf8, 0x5b, 0x09, 0xbf, 0x24, 0xe2, 0x8d, 0x5b, 0x7a, 0x61, 0x1c, 0xd5, + 0x55, 0xe4, 0xf2, 0xa9, 0x11, 0xe6, 0x7a, 0x9d, 0x82, 0x02, 0x18, 0x93, 0x39, 0x6d, 0xff, 0xc1, 0x8a, 0x4d, 0xf0, + 0xef, 0xb2, 0x36, 0x2b, 0x91, 0xf9, 0xbd, 0xc4, 0xb8, 0x91, 0x08, 0xbf, 0x8a, 0x06, 0xe6, 0x1a, 0x36, 0x9f, 0xac, + 0x06, 0xf7, 0x48, 0xcd, 0xd4, 0x57, 0x4a, 0x41, 0xea, 0x1d, 0x30, 0x4a, 0xa3, 0x59, 0xc2, 0x6f, 0x1e, 0x75, 0x1d, + 0x67, 0x2c, 0x8d, 0x7a, 0x83, 0x40, 0xaf, 0xda, 0xde, 0x51, 0x4a, 0xdf, 0xfb, 0xec, 0x01, 0xfe, 0x27, 0xd2, 0x05, + 0xae, 0x2a, 0x53, 0x5d, 0xb8, 0xaa, 0x68, 0xaa, 0x4f, 0x6a, 0xb6, 0xb8, 0xd0, 0xe0, 0x64, 0x8e, 0xdf, 0xb5, 0x35, + 0x1a, 0x95, 0x77, 0x6a, 0x2e, 0x8d, 0xac, 0x5f, 0xd5, 0xfa, 0xd7, 0x0d, 0x7e, 0xc7, 0xb6, 0x03, 0x61, 0xb8, 0xd6, + 0xdb, 0xca, 0xdf, 0x61, 0x5a, 0x6a, 0xac, 0x28, 0x4e, 0xed, 0x27, 0xe1, 0x95, 0xf6, 0x50, 0xc4, 0xb9, 0x12, 0x3a, + 0x29, 0x13, 0xe1, 0xa4, 0xfc, 0x85, 0x87, 0xf7, 0xf1, 0x85, 0x84, 0xd6, 0xe5, 0x24, 0x49, 0xc1, 0x48, 0x1a, 0x73, + 0x3e, 0x0d, 0x76, 0x76, 0x2e, 0x2e, 0x2e, 0xfc, 0x8b, 0x5d, 0x3f, 0xcb, 0xcf, 0x76, 0xda, 0xcd, 0x66, 0x13, 0xdf, + 0x23, 0x67, 0x5b, 0xe7, 0x31, 0xbb, 0x78, 0x0a, 0x76, 0xb0, 0xfd, 0xd8, 0x7a, 0x62, 0x3d, 0xde, 0xb5, 0x1e, 0x3e, + 0xb2, 0x2d, 0x12, 0xe7, 0x50, 0xb2, 0x6b, 0x5b, 0x42, 0x9c, 0x87, 0x36, 0x14, 0x77, 0xf7, 0xce, 0x94, 0x45, 0x86, + 0xf7, 0x74, 0x84, 0xbd, 0x03, 0xce, 0x41, 0xf6, 0x89, 0xd5, 0x37, 0xae, 0x28, 0x6b, 0x48, 0xa5, 0xa0, 0x1e, 0x71, + 0xf7, 0x0e, 0xa2, 0x69, 0x40, 0x4c, 0x61, 0x16, 0x62, 0x0c, 0x46, 0x94, 0xd2, 0x14, 0x68, 0x65, 0x9e, 0xc2, 0x37, + 0x4c, 0xec, 0xb4, 0xe0, 0xfb, 0x9b, 0xf6, 0x63, 0xd0, 0x58, 0xe7, 0x8d, 0x07, 0x83, 0x66, 0xa3, 0x65, 0xb5, 0x1a, + 0x6d, 0xff, 0xb1, 0xd5, 0x16, 0xff, 0x82, 0xc4, 0xdb, 0xb5, 0x5a, 0xf0, 0x6d, 0xd7, 0x82, 0xe7, 0xf3, 0x07, 0xe2, + 0x00, 0x3a, 0xb2, 0x77, 0xba, 0x7b, 0xf8, 0xb3, 0x6a, 0x80, 0xd4, 0x67, 0xb6, 0xf8, 0x21, 0x48, 0xfb, 0x9e, 0x59, + 0xda, 0x7a, 0xb2, 0xb2, 0xb8, 0xfd, 0x78, 0x65, 0xf1, 0xee, 0xa3, 0x95, 0xc5, 0x0f, 0x1e, 0xd6, 0x8b, 0x77, 0xce, + 0x44, 0x95, 0xde, 0xe5, 0xa1, 0x3d, 0x89, 0x60, 0xd9, 0x2f, 0x9d, 0x16, 0xc0, 0xd9, 0xb4, 0x1a, 0xf8, 0xf1, 0xb8, + 0xed, 0xea, 0x5e, 0xa7, 0xd8, 0x4b, 0x63, 0xf9, 0xf8, 0x09, 0x60, 0xf9, 0xb2, 0xfd, 0x68, 0x80, 0xed, 0x08, 0x51, + 0xf8, 0x3b, 0xdf, 0x7d, 0x32, 0x00, 0xf9, 0x6e, 0xe1, 0x1f, 0xfc, 0x37, 0x7e, 0xd8, 0x1e, 0x88, 0x87, 0x26, 0xd6, + 0x7f, 0xd3, 0x7a, 0x5c, 0x40, 0x53, 0xfc, 0xef, 0x17, 0x6d, 0x10, 0xa3, 0x39, 0x6e, 0x8e, 0xfb, 0x00, 0x68, 0xf4, + 0x64, 0xdc, 0xf6, 0x3f, 0x3b, 0x7f, 0xec, 0x3f, 0x19, 0xb7, 0x1e, 0x7f, 0x23, 0x9e, 0x12, 0xa0, 0xe0, 0x67, 0xf8, + 0xf7, 0xcd, 0x6e, 0x13, 0xbc, 0x4b, 0xff, 0xc9, 0xf9, 0xae, 0xbf, 0x9b, 0x34, 0x1e, 0xf9, 0x4f, 0xf0, 0xaf, 0x1a, + 0x6e, 0x9c, 0x4d, 0x98, 0x6d, 0xe1, 0x7a, 0x2f, 0x78, 0x5b, 0xe6, 0x1c, 0xed, 0x07, 0xd6, 0xc3, 0x07, 0x2f, 0x9f, + 0xc0, 0x1a, 0x8d, 0x5b, 0x6d, 0xf8, 0x77, 0xdd, 0xd7, 0x6f, 0x90, 0xf0, 0x72, 0xe0, 0x88, 0x61, 0x86, 0xbd, 0x22, + 0x1c, 0xbd, 0xd3, 0xf0, 0xbe, 0x07, 0x0e, 0xd4, 0x6a, 0xef, 0x9a, 0xb1, 0xdb, 0x23, 0xa8, 0xec, 0x6e, 0xee, 0x35, + 0x63, 0x7f, 0xac, 0x7b, 0xcd, 0xd9, 0x42, 0x04, 0xf5, 0x92, 0x2f, 0x79, 0xd1, 0x8b, 0xae, 0xd7, 0x07, 0xee, 0x1c, + 0xfd, 0x85, 0xf7, 0xf1, 0x36, 0x09, 0xb4, 0x8e, 0x99, 0x19, 0x6c, 0xc8, 0x70, 0x23, 0xe3, 0x8f, 0x2b, 0xd2, 0xdd, + 0x9f, 0x75, 0x04, 0xc9, 0x6f, 0x27, 0xc8, 0x37, 0x77, 0xa3, 0x47, 0xfe, 0x07, 0xd3, 0xa3, 0x30, 0xe9, 0x51, 0x0b, + 0xe7, 0x92, 0x3b, 0x4b, 0xee, 0xe8, 0x01, 0x3d, 0x3b, 0x98, 0x84, 0xbd, 0x6d, 0xef, 0x30, 0x2c, 0x2a, 0x6c, 0x71, + 0x88, 0xf0, 0xf4, 0xd7, 0xc4, 0x9f, 0xc5, 0x8d, 0x8b, 0xd0, 0x96, 0xbe, 0xff, 0x14, 0xdf, 0xdb, 0xad, 0x1e, 0xce, + 0xc5, 0xad, 0xbe, 0x90, 0xae, 0xe4, 0x3e, 0xd4, 0x71, 0x03, 0xbc, 0x04, 0x13, 0xce, 0x33, 0x1e, 0xe1, 0x0f, 0xc3, + 0x01, 0xb9, 0xe9, 0x27, 0xe4, 0x62, 0x9e, 0x30, 0x3c, 0x24, 0x1f, 0x88, 0x77, 0x28, 0xc3, 0x57, 0x79, 0xdd, 0x16, + 0x6f, 0x71, 0x7c, 0x8d, 0x37, 0x50, 0x54, 0x60, 0x7a, 0x82, 0x2e, 0xf5, 0x1b, 0x36, 0x8c, 0x23, 0xc7, 0x76, 0xa6, + 0xb0, 0x91, 0x61, 0x96, 0x46, 0xed, 0xfa, 0x07, 0xdd, 0xfc, 0x70, 0x6d, 0xf5, 0xeb, 0x64, 0x39, 0xbe, 0xed, 0x31, + 0x3c, 0x92, 0x41, 0x2d, 0x5b, 0x9a, 0xf9, 0x30, 0xbe, 0x2a, 0xc9, 0x51, 0xa2, 0x57, 0xa6, 0x81, 0x2d, 0x6c, 0x83, + 0x96, 0xdf, 0x06, 0x5f, 0x81, 0x8a, 0xf1, 0xed, 0x79, 0xdf, 0x39, 0x8d, 0x5d, 0xb0, 0x5d, 0x8c, 0x6e, 0x7a, 0xa0, + 0xbe, 0xfe, 0xb1, 0x2b, 0xfd, 0x83, 0x8c, 0xf5, 0x3b, 0x33, 0xb6, 0xe0, 0x88, 0x7b, 0x02, 0x77, 0x5b, 0xbc, 0xa5, + 0x84, 0xa8, 0x47, 0x77, 0x46, 0xa1, 0xcc, 0x31, 0x7f, 0x98, 0x4f, 0xbc, 0x9d, 0x4f, 0xfc, 0x06, 0x67, 0x59, 0x35, + 0xe1, 0xee, 0x9c, 0x02, 0xef, 0x98, 0x64, 0x8c, 0x57, 0x75, 0x31, 0x0e, 0x1b, 0x1a, 0x34, 0xc5, 0x67, 0xb7, 0x46, + 0x64, 0xee, 0x69, 0x80, 0x88, 0xc0, 0xa1, 0xfc, 0xac, 0x8a, 0xd5, 0x17, 0x19, 0x5d, 0x01, 0xb7, 0x1d, 0x7f, 0xf9, + 0x88, 0x3e, 0x96, 0x62, 0x37, 0xe2, 0xec, 0x60, 0xa1, 0xb4, 0x1a, 0xaa, 0x8a, 0xd1, 0x14, 0x4f, 0xaf, 0x0e, 0xe5, + 0x6b, 0x3f, 0x6c, 0x0c, 0x81, 0x52, 0xe8, 0xbb, 0x7a, 0xe5, 0xe0, 0x36, 0xa8, 0x46, 0xfa, 0x21, 0x60, 0xca, 0x60, + 0x42, 0xed, 0x87, 0xb7, 0x6e, 0x2c, 0xe9, 0xf3, 0x84, 0xb6, 0xd0, 0x7d, 0x43, 0x76, 0x1e, 0x0f, 0xa4, 0x0a, 0xf3, + 0x2c, 0x79, 0x5b, 0xb0, 0x41, 0x4b, 0x13, 0xb6, 0x3c, 0xe1, 0xf5, 0xc3, 0x03, 0xea, 0xe3, 0x30, 0xcd, 0xec, 0xee, + 0xfd, 0xce, 0x3a, 0xe2, 0xe3, 0xaf, 0x12, 0x1f, 0x81, 0x97, 0xf9, 0xb7, 0xe1, 0x7d, 0xfc, 0x5d, 0xe2, 0xfb, 0x7d, + 0xdb, 0xf5, 0x49, 0x01, 0xdc, 0xaf, 0x7e, 0x9c, 0x18, 0xa5, 0xdf, 0x36, 0xe8, 0x6a, 0xef, 0xae, 0x4a, 0x5b, 0x2a, + 0xe8, 0xf6, 0xc3, 0x4a, 0x41, 0xc3, 0x77, 0x43, 0x22, 0x83, 0xb2, 0x68, 0xfb, 0x0f, 0x0d, 0xb1, 0x7f, 0xde, 0xc0, + 0xcf, 0x9a, 0xe0, 0x7f, 0x00, 0x0d, 0x94, 0xe4, 0x7f, 0x0d, 0xcd, 0x77, 0x85, 0x92, 0x81, 0x7e, 0xdf, 0x93, 0x58, + 0x96, 0x22, 0xb9, 0xb6, 0x0d, 0x56, 0x9c, 0xc6, 0x88, 0x6c, 0x2c, 0xdb, 0x73, 0xf4, 0x2f, 0x1e, 0xc9, 0x5d, 0x29, + 0xe3, 0x40, 0xcf, 0xa1, 0xaf, 0xa3, 0xdf, 0xe4, 0xbf, 0xaa, 0xce, 0xab, 0x49, 0x89, 0x15, 0x53, 0xe0, 0xbe, 0x5e, + 0x38, 0xf1, 0xe9, 0x88, 0x2b, 0x0c, 0xfa, 0x55, 0x40, 0xeb, 0x19, 0x5a, 0xde, 0x75, 0x70, 0x0d, 0x11, 0xc1, 0xe8, + 0x6d, 0xc3, 0x34, 0xc9, 0xab, 0x61, 0xb9, 0x38, 0x3f, 0xa6, 0x83, 0xe5, 0x99, 0x71, 0xa7, 0x50, 0x46, 0xef, 0x30, + 0x59, 0x74, 0x18, 0xe7, 0xf4, 0x62, 0x04, 0x05, 0x7a, 0x2d, 0x02, 0x58, 0x51, 0x89, 0xa4, 0x04, 0x2b, 0x7a, 0x36, + 0x16, 0xd9, 0x81, 0x4d, 0xe1, 0x23, 0xdb, 0x7c, 0xdd, 0xbe, 0x79, 0x73, 0x9d, 0x38, 0x99, 0x12, 0xbb, 0x71, 0xaf, + 0x22, 0x7d, 0x6c, 0x90, 0xb6, 0x6b, 0x77, 0x09, 0xd9, 0x60, 0x88, 0x6b, 0xf5, 0xfb, 0x72, 0xa6, 0x00, 0xb2, 0x4d, + 0x42, 0xeb, 0x71, 0x89, 0x84, 0xae, 0xa4, 0xd3, 0x29, 0x8b, 0xb8, 0x1f, 0xa5, 0x22, 0x0b, 0xc1, 0x10, 0x53, 0x5e, + 0x8b, 0xed, 0xba, 0x25, 0xc8, 0x46, 0x23, 0x6f, 0x42, 0xee, 0x6e, 0x28, 0x54, 0x17, 0x3d, 0x18, 0xaf, 0xe5, 0xb3, + 0x8e, 0xdb, 0xdd, 0x77, 0x87, 0xfb, 0x96, 0xd8, 0x94, 0x7b, 0x3b, 0xf0, 0xb8, 0x47, 0xfe, 0xb8, 0x48, 0xde, 0x0f, + 0x45, 0xf2, 0xbe, 0x25, 0x6f, 0x71, 0x50, 0x86, 0xe3, 0x8e, 0x40, 0xdb, 0xb6, 0x58, 0x3a, 0x10, 0x81, 0xc4, 0x09, + 0xf8, 0x2c, 0x31, 0xbe, 0xa2, 0x71, 0x07, 0xbb, 0x36, 0x70, 0xc1, 0x80, 0x9b, 0x45, 0xd4, 0x51, 0xd9, 0x35, 0x3c, + 0x55, 0x61, 0x47, 0xb0, 0x46, 0x98, 0xca, 0x40, 0x94, 0x43, 0xe9, 0xe4, 0xc5, 0xe5, 0xd6, 0xc5, 0xec, 0x74, 0x02, + 0x72, 0x52, 0xe5, 0x10, 0x7e, 0x94, 0x1d, 0xf6, 0x68, 0xaa, 0xee, 0x49, 0x29, 0xe3, 0xa2, 0xea, 0xf5, 0xf9, 0x0b, + 0x3f, 0x35, 0x2c, 0xb0, 0x97, 0x7a, 0x01, 0xb3, 0xf0, 0xc7, 0xbb, 0x5d, 0x1d, 0x89, 0x34, 0xeb, 0x4a, 0x40, 0x7d, + 0xb7, 0x7b, 0x12, 0x4c, 0xe5, 0x78, 0xaf, 0xb3, 0xa5, 0x9f, 0x2d, 0xd6, 0x72, 0xb2, 0x47, 0xd9, 0xa9, 0xe2, 0x6a, + 0x93, 0x04, 0x18, 0x56, 0x10, 0x60, 0x92, 0x26, 0x80, 0x45, 0xe7, 0xaa, 0xf6, 0xc3, 0xa6, 0x4a, 0x78, 0x85, 0x32, + 0xdc, 0x90, 0xa2, 0x8b, 0x31, 0x49, 0x2d, 0x98, 0x3b, 0x6e, 0x75, 0xf7, 0x22, 0x69, 0x5c, 0xa2, 0xf0, 0x28, 0x40, + 0x7a, 0x40, 0x67, 0xb4, 0xe0, 0xfc, 0x38, 0xdb, 0xb9, 0x60, 0xa7, 0x8d, 0x68, 0x1a, 0x57, 0x91, 0x53, 0x34, 0x35, + 0xf4, 0x94, 0x59, 0x35, 0x13, 0x7e, 0x8d, 0x16, 0x90, 0x24, 0xc1, 0x5d, 0xca, 0xb0, 0x2c, 0x59, 0xe8, 0xc0, 0x42, + 0x40, 0x61, 0x92, 0xeb, 0x2a, 0x7c, 0x2b, 0x35, 0x6e, 0x69, 0x77, 0xff, 0xfa, 0x8f, 0xff, 0x5b, 0x46, 0x64, 0x81, + 0x2a, 0x2d, 0x35, 0xd6, 0x02, 0xa1, 0xcb, 0x3d, 0xba, 0xb5, 0xa2, 0x8f, 0x10, 0xd9, 0x25, 0xb8, 0xf6, 0xf1, 0xb0, + 0x31, 0x8e, 0x92, 0x11, 0x00, 0xb6, 0x96, 0x40, 0x66, 0x52, 0xb8, 0x84, 0xba, 0x5e, 0x84, 0x2c, 0xf8, 0x9b, 0xd2, + 0x9b, 0x55, 0x96, 0x2c, 0xed, 0x56, 0x33, 0xd9, 0xb9, 0xda, 0x50, 0xb5, 0x84, 0x67, 0xf5, 0xdb, 0x7d, 0x4a, 0xa8, + 0xd5, 0xf2, 0x9c, 0xa1, 0xa5, 0x3e, 0x02, 0xf9, 0xd7, 0x7f, 0xfa, 0xbb, 0xff, 0xa1, 0x1e, 0xf1, 0x64, 0xe3, 0xaf, + 0xff, 0xf0, 0x9f, 0x31, 0x1b, 0xd3, 0xd2, 0xa7, 0x1f, 0x24, 0x27, 0xac, 0xea, 0xe8, 0x43, 0x08, 0x0c, 0x0b, 0x53, + 0x9d, 0x26, 0x20, 0x06, 0xe3, 0x41, 0x3d, 0xf3, 0xf9, 0x80, 0x26, 0xa4, 0xcd, 0x26, 0xa1, 0xa3, 0x4d, 0x5b, 0x56, + 0x3c, 0x52, 0x23, 0x39, 0xf1, 0x22, 0x54, 0x22, 0xbd, 0xef, 0x74, 0xfb, 0xc3, 0xd7, 0xab, 0x31, 0x57, 0xf1, 0x3e, + 0x2c, 0x29, 0xab, 0x72, 0x0b, 0x03, 0xf1, 0x73, 0x7c, 0x0c, 0xda, 0x46, 0x31, 0x2d, 0x5e, 0xad, 0x4f, 0xe7, 0xa7, + 0x19, 0xc0, 0x3f, 0x42, 0x8a, 0x8b, 0xa8, 0x22, 0x9d, 0x79, 0x36, 0xd0, 0xe6, 0x4b, 0xae, 0x4a, 0x1a, 0x45, 0x38, + 0x8a, 0x0f, 0x9e, 0xfc, 0x4d, 0xf9, 0xe7, 0x09, 0x5a, 0x56, 0x96, 0x33, 0x89, 0x2e, 0xa5, 0xfb, 0xf8, 0xa8, 0xd9, + 0x9c, 0x5e, 0xba, 0xf3, 0x6a, 0x06, 0x6f, 0xdd, 0x64, 0x14, 0x89, 0x34, 0x07, 0xa4, 0xc3, 0x52, 0x1d, 0xf4, 0x04, + 0x8f, 0xa9, 0x89, 0x31, 0xb2, 0xb2, 0xfc, 0xd3, 0x9c, 0xe2, 0x6e, 0xf1, 0x2f, 0x78, 0xa8, 0x29, 0x43, 0x94, 0x50, + 0x62, 0x60, 0x31, 0x37, 0x7a, 0xb5, 0x45, 0xaf, 0x71, 0x6b, 0xf9, 0xea, 0x83, 0x79, 0x28, 0x6b, 0x1e, 0xa7, 0x3e, + 0xc0, 0x03, 0xd2, 0x71, 0xcb, 0x1b, 0xb7, 0xe7, 0x7a, 0x78, 0xce, 0xb3, 0x89, 0x79, 0x0a, 0xcb, 0x22, 0x36, 0x60, + 0x23, 0x15, 0xda, 0x95, 0xf5, 0xe2, 0x84, 0xb5, 0x1c, 0xef, 0xae, 0x98, 0x4b, 0x82, 0x44, 0xa7, 0xaf, 0x00, 0xcf, + 0x3d, 0xdc, 0x80, 0x40, 0xff, 0x2c, 0xe2, 0x01, 0xf1, 0x6b, 0xc7, 0x3c, 0xcb, 0x8d, 0x50, 0xca, 0x64, 0x73, 0x03, + 0x9e, 0x8e, 0x68, 0x8a, 0x41, 0xd6, 0xfa, 0xd5, 0x93, 0xd2, 0xa7, 0xee, 0xe6, 0x50, 0x22, 0x46, 0xf3, 0x8d, 0x3c, + 0x22, 0x7d, 0x5a, 0x0b, 0x6e, 0x48, 0x15, 0xd3, 0x76, 0xbd, 0x95, 0xf5, 0x42, 0x53, 0x8b, 0xda, 0x6f, 0xb8, 0x63, + 0x13, 0x98, 0xf6, 0x62, 0x2b, 0x2a, 0xc4, 0x56, 0x4f, 0xc3, 0x6f, 0xb4, 0xeb, 0x13, 0x4d, 0xa7, 0xd4, 0xd0, 0x05, + 0x26, 0x26, 0x83, 0x15, 0x65, 0x07, 0x1d, 0xff, 0x8b, 0xd3, 0x76, 0xd9, 0x46, 0x6e, 0x04, 0xf1, 0x4d, 0x9e, 0xc3, + 0xe3, 0xaf, 0xae, 0x74, 0xff, 0x1f, 0x1c, 0x1d, 0xa5, 0x5f, 0x1b, 0x82, 0x00, 0x00}; } // namespace web_server } // namespace esphome From fd9cca565bc0a702b60d856fc86d6055b7e76140 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Jul 2023 15:02:53 +1200 Subject: [PATCH 067/106] Log component long time message at warning level (#5048) --- esphome/core/component.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 49ef8ecde7..ae85d55498 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -201,8 +201,8 @@ WarnIfComponentBlockingGuard::~WarnIfComponentBlockingGuard() { uint32_t now = millis(); if (now - started_ > 50) { const char *src = component_ == nullptr ? "" : component_->get_component_source(); - ESP_LOGV(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f); - ESP_LOGV(TAG, "Components should block for at most 20-30ms."); + ESP_LOGW(TAG, "Component %s took a long time for an operation (%.2f s).", src, (now - started_) / 1e3f); + ESP_LOGW(TAG, "Components should block for at most 20-30ms."); ; } } From 45c72f1f2220e2e9cd63e0fac40f74b0a46c7670 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Tue, 4 Jul 2023 15:26:31 +1200 Subject: [PATCH 068/106] Log start of i2c setup (#5049) --- esphome/components/i2c/i2c_bus_esp_idf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 51688322f6..24c1860e6f 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -14,6 +14,7 @@ namespace i2c { static const char *const TAG = "i2c.idf"; void IDFI2CBus::setup() { + ESP_LOGCONFIG(TAG, "Setting up I2C bus..."); static i2c_port_t next_port = 0; port_ = next_port++; From fc3d558d479ec4a3a7a8118979b4bee92d4d2cfc Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Wed, 5 Jul 2023 00:28:12 +0200 Subject: [PATCH 069/106] Initial debug component support for rp2040 (#5056) --- esphome/components/debug/__init__.py | 1 - esphome/components/debug/debug_component.cpp | 13 ++++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 9742b3b19e..1955b5d22c 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -38,7 +38,6 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.polling_component_schema("60s")), - cv.only_on(["esp32", "esp8266"]), ) diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index 9b7e256147..8b6a97068b 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -22,8 +22,12 @@ #endif // USE_ESP32 #ifdef USE_ARDUINO +#ifdef USE_RP2040 +#include +#else #include #endif +#endif namespace esphome { namespace debug { @@ -35,6 +39,8 @@ static uint32_t get_free_heap() { return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) #elif defined(USE_ESP32) return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#elif defined(USE_RP2040) + return rp2040.getFreeHeap(); #endif } @@ -65,7 +71,7 @@ void DebugComponent::dump_config() { this->free_heap_ = get_free_heap(); ESP_LOGD(TAG, "Free Heap Size: %" PRIu32 " bytes", this->free_heap_); -#ifdef USE_ARDUINO +#if defined(USE_ARDUINO) && !defined(USE_RP2040) const char *flash_mode; switch (ESP.getFlashChipMode()) { // NOLINT(readability-static-accessed-through-instance) case FM_QIO: @@ -274,6 +280,11 @@ void DebugComponent::dump_config() { reset_reason = ESP.getResetReason().c_str(); #endif +#ifdef USE_RP2040 + ESP_LOGD(TAG, "CPU Frequency: %u", rp2040.f_cpu()); + device_info += "CPU Frequency: " + to_string(rp2040.f_cpu()); +#endif // USE_RP2040 + #ifdef USE_TEXT_SENSOR if (this->device_info_ != nullptr) { if (device_info.length() > 255) From 22a1134f0ee87ee3c462abfcac9d38d4dc15fba8 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 5 Jul 2023 10:31:58 +1200 Subject: [PATCH 070/106] Fix when idf component has broken symlinks (#5058) --- esphome/components/esp32/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 3ca140f0d4..903031c77a 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -547,6 +547,8 @@ def copy_files(): CORE.relative_build_path(f"components/{name}"), dirs_exist_ok=True, ignore=shutil.ignore_patterns(".git", ".github"), + symlinks=True, + ignore_dangling_symlinks=True, ) dir = os.path.dirname(__file__) From fe0404a084fbbf068c502a1816548884c674289f Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Wed, 5 Jul 2023 00:33:46 +0200 Subject: [PATCH 071/106] Some tests wasn't running (locally) (#5050) --- script/test | 3 +++ 1 file changed, 3 insertions(+) diff --git a/script/test b/script/test index 36be9118ed..36a58cd75a 100755 --- a/script/test +++ b/script/test @@ -9,6 +9,9 @@ set -x esphome compile tests/test1.yaml esphome compile tests/test2.yaml esphome compile tests/test3.yaml +esphome compile tests/test3.1.yaml esphome compile tests/test4.yaml esphome compile tests/test5.yaml +esphome compile tests/test6.yaml +esphome compile tests/test7.yaml esphome compile tests/test8.yaml From 5bf2fa5c56be1dc6c121a8e6153ae954aaa133e4 Mon Sep 17 00:00:00 2001 From: lnicolas83 Date: Wed, 5 Jul 2023 02:21:26 +0200 Subject: [PATCH 072/106] [ILI9xxx] Add ili9488_a (alternative gamma configuration for ILI9488) (#5027) * Add ili9488_a * Fix clang-tidy --- esphome/components/ili9xxx/display.py | 1 + .../components/ili9xxx/ili9xxx_display.cpp | 11 ++++++ esphome/components/ili9xxx/ili9xxx_display.h | 6 ++++ esphome/components/ili9xxx/ili9xxx_init.h | 34 +++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 29603eb30f..89676ddb9a 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -43,6 +43,7 @@ MODELS = { "ILI9481": ili9XXX_ns.class_("ILI9XXXILI9481", ili9XXXSPI), "ILI9486": ili9XXX_ns.class_("ILI9XXXILI9486", ili9XXXSPI), "ILI9488": ili9XXX_ns.class_("ILI9XXXILI9488", ili9XXXSPI), + "ILI9488_A": ili9XXX_ns.class_("ILI9XXXILI9488A", ili9XXXSPI), "ST7796": ili9XXX_ns.class_("ILI9XXXST7796", ili9XXXSPI), "S3BOX": ili9XXX_ns.class_("ILI9XXXS3Box", ili9XXXSPI), "S3BOX_LITE": ili9XXX_ns.class_("ILI9XXXS3BoxLite", ili9XXXSPI), diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 6fc6da3cdb..82217f8e40 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -411,6 +411,17 @@ void ILI9XXXILI9488::initialize() { this->is_18bitdisplay_ = true; } // 40_TFT display +void ILI9XXXILI9488A::initialize() { + this->init_lcd_(INITCMD_ILI9488_A); + if (this->width_ == 0) { + this->width_ = 480; + } + if (this->height_ == 0) { + this->height_ = 320; + } + this->is_18bitdisplay_ = true; +} +// 40_TFT display void ILI9XXXST7796::initialize() { this->init_lcd_(INITCMD_ST7796); if (this->width_ == 0) { diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index dc7bfdc6eb..9133c66a5b 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -128,6 +128,12 @@ class ILI9XXXILI9488 : public ILI9XXXDisplay { void initialize() override; }; +//----------- ILI9XXX_35_TFT origin colors rotated display -------------- +class ILI9XXXILI9488A : public ILI9XXXDisplay { + protected: + void initialize() override; +}; + //----------- ILI9XXX_35_TFT rotated display -------------- class ILI9XXXST7796 : public ILI9XXXDisplay { protected: diff --git a/esphome/components/ili9xxx/ili9xxx_init.h b/esphome/components/ili9xxx/ili9xxx_init.h index 15fbf92659..1856fb06ab 100644 --- a/esphome/components/ili9xxx/ili9xxx_init.h +++ b/esphome/components/ili9xxx/ili9xxx_init.h @@ -139,6 +139,40 @@ static const uint8_t PROGMEM INITCMD_ILI9488[] = { + // 5 frames + //ILI9XXX_ETMOD, 1, 0xC6, // + + + ILI9XXX_SLPOUT, 0x80, // Exit sleep mode + //ILI9XXX_INVON , 0, + ILI9XXX_DISPON, 0x80, // Set display on + 0x00 // end +}; + +static const uint8_t PROGMEM INITCMD_ILI9488_A[] = { + ILI9XXX_GMCTRP1,15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F, + ILI9XXX_GMCTRN1,15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F, + + ILI9XXX_PWCTR1, 2, 0x17, 0x15, // VRH1 VRH2 + ILI9XXX_PWCTR2, 1, 0x41, // VGH, VGL + ILI9XXX_VMCTR1, 3, 0x00, 0x12, 0x80, // nVM VCM_REG VCM_REG_EN + + ILI9XXX_IFMODE, 1, 0x00, + ILI9XXX_FRMCTR1, 1, 0xA0, // Frame rate = 60Hz + ILI9XXX_INVCTR, 1, 0x02, // Display Inversion Control = 2dot + + ILI9XXX_DFUNCTR, 2, 0x02, 0x02, // Nomal scan + + 0xE9, 1, 0x00, // Set Image Functio. Disable 24 bit data + + ILI9XXX_ADJCTL3, 4, 0xA9, 0x51, 0x2C, 0x82, // Adjust Control 3 + + ILI9XXX_MADCTL, 1, 0x28, + //ILI9XXX_PIXFMT, 1, 0x55, // Interface Pixel Format = 16bit + ILI9XXX_PIXFMT, 1, 0x66, //ILI9488 only supports 18-bit pixel format in 4/3 wire SPI mode + + + // 5 frames //ILI9XXX_ETMOD, 1, 0xC6, // From a326dcaf0ef71ca56ab567414ca26990f9c48ec3 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 5 Jul 2023 09:53:14 +0200 Subject: [PATCH 073/106] [ili9xxx] Allow config of spi data rate. (#4701) * Allow 80MHz ili9xxx display. * python foo. * update based on feedback. * Change python --------- Co-authored-by: Your Name --- esphome/components/ili9xxx/display.py | 5 +++++ esphome/components/ili9xxx/ili9xxx_display.h | 6 +++++- esphome/components/spi/__init__.py | 16 ++++++++++++++++ esphome/components/spi/spi.h | 1 + 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 89676ddb9a..6021da5eeb 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -13,6 +13,7 @@ from esphome.const import ( CONF_PAGES, CONF_RESET_PIN, CONF_DIMENSIONS, + CONF_DATA_RATE, ) DEPENDENCIES = ["spi"] @@ -98,6 +99,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_COLOR_PALETTE_IMAGES, default=[]): cv.ensure_list( cv.file_ ), + cv.Optional(CONF_DATA_RATE, default="40MHz"): spi.SPI_DATA_RATE_SCHEMA, } ) .extend(cv.polling_component_schema("1s")) @@ -176,3 +178,6 @@ async def to_code(config): if rhs is not None: prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) cg.add(var.set_palette(prog_arr)) + + spi_data_rate = str(spi.SPI_DATA_RATE_OPTIONS[config[CONF_DATA_RATE]]) + cg.add_define("ILI9XXXDisplay_DATA_RATE", cg.RawExpression(spi_data_rate)) diff --git a/esphome/components/ili9xxx/ili9xxx_display.h b/esphome/components/ili9xxx/ili9xxx_display.h index 9133c66a5b..15b08e6c76 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.h +++ b/esphome/components/ili9xxx/ili9xxx_display.h @@ -15,10 +15,14 @@ enum ILI9XXXColorMode { BITS_16 = 0x10, }; +#ifndef ILI9XXXDisplay_DATA_RATE +#define ILI9XXXDisplay_DATA_RATE spi::DATA_RATE_40MHZ +#endif // ILI9XXXDisplay_DATA_RATE + class ILI9XXXDisplay : public PollingComponent, public display::DisplayBuffer, public spi::SPIDevice { + spi::CLOCK_PHASE_LEADING, ILI9XXXDisplay_DATA_RATE> { public: void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } float get_setup_priority() const override; diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py index e0fc9efb42..1528a05734 100644 --- a/esphome/components/spi/__init__.py +++ b/esphome/components/spi/__init__.py @@ -16,6 +16,22 @@ CODEOWNERS = ["@esphome/core"] spi_ns = cg.esphome_ns.namespace("spi") SPIComponent = spi_ns.class_("SPIComponent", cg.Component) SPIDevice = spi_ns.class_("SPIDevice") +SPIDataRate = spi_ns.enum("SPIDataRate") + +SPI_DATA_RATE_OPTIONS = { + 80e6: SPIDataRate.DATA_RATE_80MHZ, + 40e6: SPIDataRate.DATA_RATE_40MHZ, + 20e6: SPIDataRate.DATA_RATE_20MHZ, + 10e6: SPIDataRate.DATA_RATE_10MHZ, + 5e6: SPIDataRate.DATA_RATE_5MHZ, + 2e6: SPIDataRate.DATA_RATE_2MHZ, + 1e6: SPIDataRate.DATA_RATE_1MHZ, + 2e5: SPIDataRate.DATA_RATE_200KHZ, + 75e3: SPIDataRate.DATA_RATE_75KHZ, + 1e3: SPIDataRate.DATA_RATE_1KHZ, +} +SPI_DATA_RATE_SCHEMA = cv.All(cv.frequency, cv.enum(SPI_DATA_RATE_OPTIONS)) + MULTI_CONF = True CONF_FORCE_SW = "force_sw" diff --git a/esphome/components/spi/spi.h b/esphome/components/spi/spi.h index bacdad723b..f19518caae 100644 --- a/esphome/components/spi/spi.h +++ b/esphome/components/spi/spi.h @@ -67,6 +67,7 @@ enum SPIDataRate : uint32_t { DATA_RATE_10MHZ = 10000000, DATA_RATE_20MHZ = 20000000, DATA_RATE_40MHZ = 40000000, + DATA_RATE_80MHZ = 80000000, }; class SPIComponent : public Component { From 979f0147997080d4fdd178abb4b3e80795297aa9 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Wed, 5 Jul 2023 12:05:27 +0200 Subject: [PATCH 074/106] Make scheduler debuging work with idf >= 5 (#5052) --- esphome/core/scheduler.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index ab60f83ba5..012c9af3c6 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -154,16 +154,16 @@ void HOT Scheduler::call() { if (now - last_print > 2000) { last_print = now; std::vector> old_items; - ESP_LOGVV(TAG, "Items: count=%u, now=%u", this->items_.size(), now); + ESP_LOGVV(TAG, "Items: count=%u, now=%" PRIu32, this->items_.size(), now); while (!this->empty_()) { this->lock_.lock(); auto item = std::move(this->items_[0]); this->pop_raw_(); this->lock_.unlock(); - ESP_LOGVV(TAG, " %s '%s' interval=%u last_execution=%u (%u) next=%u (%u)", item->get_type_str(), - item->name.c_str(), item->interval, item->last_execution, item->last_execution_major, - item->next_execution(), item->next_execution_major()); + ESP_LOGVV(TAG, " %s '%s' interval=%" PRIu32 " last_execution=%" PRIu32 " (%u) next=%" PRIu32 " (%u)", + item->get_type_str(), item->name.c_str(), item->interval, item->last_execution, + item->last_execution_major, item->next_execution(), item->next_execution_major()); old_items.push_back(std::move(item)); } From 301a78f983777ceb3dc71ab994c28886bb12c2dd Mon Sep 17 00:00:00 2001 From: Tobias Oort Date: Wed, 5 Jul 2023 21:32:00 +0200 Subject: [PATCH 075/106] Adds 1.54" e-ink display (gdew0154m09) support to waveshare_epaper component (#4939) * Added GDEW0154M09 in waveshare_epaper component * noop change - trigger workflow * Make linter happy * Update test4.yaml * linter doing linty things * revert the newline removal. * revert to prove unstable test * add code back into test. * no partial updates supported yet - removed from test. * Update esphome/components/waveshare_epaper/waveshare_epaper.cpp Co-authored-by: Keith Burzinski --------- Co-authored-by: Keith Burzinski --- .../components/waveshare_epaper/display.py | 2 + .../waveshare_epaper/waveshare_epaper.cpp | 140 ++++++++++++++++++ .../waveshare_epaper/waveshare_epaper.h | 40 +++++ tests/test4.yaml | 8 + 4 files changed, 190 insertions(+) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index d0276f119a..11deab5310 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -63,6 +63,7 @@ WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_( WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( "WaveshareEPaper2P13InDKE", WaveshareEPaper ) +GDEW0154M09 = waveshare_epaper_ns.class_("GDEW0154M09", WaveshareEPaper) WaveshareEPaperTypeAModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeAModel") WaveshareEPaperTypeBModel = waveshare_epaper_ns.enum("WaveshareEPaperTypeBModel") @@ -91,6 +92,7 @@ MODELS = { "7.50inv2alt": ("b", WaveshareEPaper7P5InV2alt), "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), + "1.54in-m5coreink-m09": ("c", GDEW0154M09), } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 42f5bc54e3..d64a5500dd 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -763,6 +763,146 @@ void GDEY029T94::dump_config() { LOG_UPDATE_INTERVAL(this); } +// ======================================================== +// Good Display 1.54in black/white/grey GDEW0154M09 +// As used in M5Stack Core Ink +// Datasheet: +// - https://v4.cecdn.yun300.cn/100001_1909185148/GDEW0154M09-200709.pdf +// - https://github.com/m5stack/M5Core-Ink +// Reference code from GoodDisplay: +// - https://github.com/GoodDisplay/E-paper-Display-Library-of-GoodDisplay/ +// -> /Monochrome_E-paper-Display/1.54inch_JD79653_GDEW0154M09_200x200/ESP32-Arduino%20IDE/GDEW0154M09_Arduino.ino +// M5Stack Core Ink spec: +// - https://docs.m5stack.com/en/core/coreink +// ======================================================== + +void GDEW0154M09::initialize() { + this->init_internal_(); + ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + this->lastbuff_ = allocator.allocate(this->get_buffer_length_()); + if (this->lastbuff_ != nullptr) { + memset(this->lastbuff_, 0xff, sizeof(uint8_t) * this->get_buffer_length_()); + } + this->clear_(); +} + +void GDEW0154M09::reset_() { + // RST is inverse from other einks in this project + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + } +} + +void GDEW0154M09::init_internal_() { + this->reset_(); + + // clang-format off + // 200x200 resolution: 11 + // LUT from OTP: 0 + // B/W mode (doesn't work): 1 + // scan-up: 1 + // shift-right: 1 + // booster ON: 1 + // no soft reset: 1 + const uint8_t panel_setting_1 = 0b11011111; + + // VCOM status off 0 + // Temp sensing default 1 + // VGL Power Off Floating 1 + // NORG expect refresh 1 + // VCOM Off on displ off 0 + const uint8_t panel_setting_2 = 0b01110; + + const uint8_t wf_t0154_cz_b3_list[] = { + 11, // 11 commands in list + CMD_PSR_PANEL_SETTING, 2, panel_setting_1, panel_setting_2, + CMD_UNDOCUMENTED_0x4D, 1, 0x55, + CMD_UNDOCUMENTED_0xAA, 1, 0x0f, + CMD_UNDOCUMENTED_0xE9, 1, 0x02, + CMD_UNDOCUMENTED_0xB6, 1, 0x11, + CMD_UNDOCUMENTED_0xF3, 1, 0x0a, + CMD_TRES_RESOLUTION_SETTING, 3, 0xc8, 0x00, 0xc8, + CMD_TCON_TCONSETTING, 1, 0x00, + CMD_CDI_VCOM_DATA_INTERVAL, 1, 0xd7, + CMD_PWS_POWER_SAVING, 1, 0x00, + CMD_PON_POWER_ON, 0 + }; + // clang-format on + + this->write_init_list_(wf_t0154_cz_b3_list); + delay(100); // NOLINT + this->wait_until_idle_(); +} + +void GDEW0154M09::write_init_list_(const uint8_t *list) { + uint8_t list_limit = list[0]; + uint8_t *start_ptr = ((uint8_t *) list + 1); + for (uint8_t i = 0; i < list_limit; i++) { + this->command(*(start_ptr + 0)); + for (uint8_t dnum = 0; dnum < *(start_ptr + 1); dnum++) { + this->data(*(start_ptr + 2 + dnum)); + } + start_ptr += (*(start_ptr + 1) + 2); + } +} + +void GDEW0154M09::clear_() { + uint32_t pixsize = this->get_buffer_length_(); + for (uint8_t j = 0; j < 2; j++) { + this->command(CMD_DTM1_DATA_START_TRANS); + for (int count = 0; count < pixsize; count++) { + this->data(0x00); + } + this->command(CMD_DTM2_DATA_START_TRANS2); + for (int count = 0; count < pixsize; count++) { + this->data(0xff); + } + this->command(CMD_DISPLAY_REFRESH); + delay(10); + this->wait_until_idle_(); + } +} + +void HOT GDEW0154M09::display() { + this->init_internal_(); + // "Mode 0 display" for now + this->command(CMD_DTM1_DATA_START_TRANS); + for (int i = 0; i < this->get_buffer_length_(); i++) { + this->data(0xff); + } + this->command(CMD_DTM2_DATA_START_TRANS2); // write 'new' data to SRAM + for (int i = 0; i < this->get_buffer_length_(); i++) { + this->data(this->buffer_[i]); + } + this->command(CMD_DISPLAY_REFRESH); + delay(10); + this->wait_until_idle_(); + this->deep_sleep(); +} + +void GDEW0154M09::deep_sleep() { + // COMMAND DEEP SLEEP + this->command(CMD_POF_POWER_OFF); + this->wait_until_idle_(); + delay(1000); // NOLINT + this->command(CMD_DSLP_DEEP_SLEEP); + this->data(DATA_DSLP_DEEP_SLEEP); +} + +int GDEW0154M09::get_width_internal() { return 200; } +int GDEW0154M09::get_height_internal() { return 200; } +void GDEW0154M09::dump_config() { + LOG_DISPLAY("", "M5Stack CoreInk E-Paper (Good Display)", this); + ESP_LOGCONFIG(TAG, " Model: 1.54in Greyscale GDEW0154M09"); + 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 1cb46bdb9d..a84a1d4541 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -170,6 +170,46 @@ class GDEY029T94 : public WaveshareEPaper { int get_height_internal() override; }; +class GDEW0154M09 : public WaveshareEPaper { + public: + void initialize() override; + void display() override; + void dump_config() override; + void deep_sleep() override; + + protected: + int get_width_internal() override; + int get_height_internal() override; + + private: + static const uint8_t CMD_DTM1_DATA_START_TRANS = 0x10; + static const uint8_t CMD_DTM2_DATA_START_TRANS2 = 0x13; + static const uint8_t CMD_DISPLAY_REFRESH = 0x12; + static const uint8_t CMD_AUTO_SEQ = 0x17; + static const uint8_t DATA_AUTO_PON_DSR_POF_DSLP = 0xA7; + static const uint8_t CMD_PSR_PANEL_SETTING = 0x00; + static const uint8_t CMD_UNDOCUMENTED_0x4D = 0x4D; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xAA = 0xaa; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xE9 = 0xe9; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xB6 = 0xb6; // NOLINT + static const uint8_t CMD_UNDOCUMENTED_0xF3 = 0xf3; // NOLINT + static const uint8_t CMD_TRES_RESOLUTION_SETTING = 0x61; + static const uint8_t CMD_TCON_TCONSETTING = 0x60; + static const uint8_t CMD_CDI_VCOM_DATA_INTERVAL = 0x50; + static const uint8_t CMD_POF_POWER_OFF = 0x02; + static const uint8_t CMD_DSLP_DEEP_SLEEP = 0x07; + static const uint8_t DATA_DSLP_DEEP_SLEEP = 0xA5; + static const uint8_t CMD_PWS_POWER_SAVING = 0xe3; + static const uint8_t CMD_PON_POWER_ON = 0x04; + static const uint8_t CMD_PTL_PARTIAL_WINDOW = 0x90; + + uint8_t *lastbuff_ = nullptr; + void reset_(); + void clear_(); + void write_init_list_(const uint8_t *list); + void init_internal_(); +}; + class WaveshareEPaper2P9InB : public WaveshareEPaper { public: void initialize() override; diff --git a/tests/test4.yaml b/tests/test4.yaml index 3c9f9f610c..2a8cb02413 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -504,6 +504,14 @@ display: full_update_every: 30 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); + - platform: waveshare_epaper + cs_pin: GPIO23 + dc_pin: GPIO23 + busy_pin: GPIO23 + reset_pin: GPIO23 + model: 1.54in-m5coreink-m09 + lambda: |- + it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: inkplate6 id: inkplate_display greyscale: false From 677b2c6618fbaf6551efab88060a05013f32d566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Wed, 5 Jul 2023 21:33:26 +0200 Subject: [PATCH 076/106] display: split `DisplayBuffer` and `Display` (#5001) --- esphome/components/display/display.cpp | 343 +++++++++++ esphome/components/display/display.h | 575 ++++++++++++++++++ esphome/components/display/display_buffer.cpp | 339 +---------- esphome/components/display/display_buffer.h | 547 +---------------- esphome/components/font/font.cpp | 4 +- esphome/components/font/font.h | 4 +- esphome/components/graph/graph.cpp | 6 +- esphome/components/graph/graph.h | 8 +- esphome/components/image/image.cpp | 2 +- esphome/components/image/image.h | 2 +- esphome/components/qr_code/qr_code.cpp | 4 +- esphome/components/qr_code/qr_code.h | 4 +- .../components/touchscreen/touchscreen.cpp | 11 + esphome/components/touchscreen/touchscreen.h | 11 +- script/ci-custom.py | 2 +- 15 files changed, 960 insertions(+), 902 deletions(-) create mode 100644 esphome/components/display/display.cpp create mode 100644 esphome/components/display/display.h diff --git a/esphome/components/display/display.cpp b/esphome/components/display/display.cpp new file mode 100644 index 0000000000..410ff58de3 --- /dev/null +++ b/esphome/components/display/display.cpp @@ -0,0 +1,343 @@ +#include "display.h" + +#include + +#include "esphome/core/log.h" + +namespace esphome { +namespace display { + +static const char *const TAG = "display"; + +const Color COLOR_OFF(0, 0, 0, 0); +const Color COLOR_ON(255, 255, 255, 255); + +void Display::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } +void Display::clear() { this->fill(COLOR_OFF); } +void Display::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; } +void HOT Display::line(int x1, int y1, int x2, int y2, Color color) { + const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; + const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1; + int32_t err = dx + dy; + + while (true) { + this->draw_pixel_at(x1, y1, color); + if (x1 == x2 && y1 == y2) + break; + int32_t e2 = 2 * err; + if (e2 >= dy) { + err += dy; + x1 += sx; + } + if (e2 <= dx) { + err += dx; + y1 += sy; + } + } +} +void HOT Display::horizontal_line(int x, int y, int width, Color color) { + // Future: Could be made more efficient by manipulating buffer directly in certain rotations. + for (int i = x; i < x + width; i++) + this->draw_pixel_at(i, y, color); +} +void HOT Display::vertical_line(int x, int y, int height, Color color) { + // Future: Could be made more efficient by manipulating buffer directly in certain rotations. + for (int i = y; i < y + height; i++) + this->draw_pixel_at(x, i, color); +} +void Display::rectangle(int x1, int y1, int width, int height, Color color) { + this->horizontal_line(x1, y1, width, color); + this->horizontal_line(x1, y1 + height - 1, width, color); + this->vertical_line(x1, y1, height, color); + this->vertical_line(x1 + width - 1, y1, height, color); +} +void Display::filled_rectangle(int x1, int y1, int width, int height, Color color) { + // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses. + for (int i = y1; i < y1 + height; i++) { + this->horizontal_line(x1, i, width, color); + } +} +void HOT Display::circle(int center_x, int center_xy, int radius, Color color) { + int dx = -radius; + int dy = 0; + int err = 2 - 2 * radius; + int e2; + + do { + this->draw_pixel_at(center_x - dx, center_xy + dy, color); + this->draw_pixel_at(center_x + dx, center_xy + dy, color); + this->draw_pixel_at(center_x + dx, center_xy - dy, color); + this->draw_pixel_at(center_x - dx, center_xy - dy, color); + e2 = err; + if (e2 < dy) { + err += ++dy * 2 + 1; + if (-dx == dy && e2 <= dx) { + e2 = 0; + } + } + if (e2 > dx) { + err += ++dx * 2 + 1; + } + } while (dx <= 0); +} +void Display::filled_circle(int center_x, int center_y, int radius, Color color) { + int dx = -int32_t(radius); + int dy = 0; + int err = 2 - 2 * radius; + int e2; + + do { + this->draw_pixel_at(center_x - dx, center_y + dy, color); + this->draw_pixel_at(center_x + dx, center_y + dy, color); + this->draw_pixel_at(center_x + dx, center_y - dy, color); + this->draw_pixel_at(center_x - dx, center_y - dy, color); + int hline_width = 2 * (-dx) + 1; + this->horizontal_line(center_x + dx, center_y + dy, hline_width, color); + this->horizontal_line(center_x + dx, center_y - dy, hline_width, color); + e2 = err; + if (e2 < dy) { + err += ++dy * 2 + 1; + if (-dx == dy && e2 <= dx) { + e2 = 0; + } + } + if (e2 > dx) { + err += ++dx * 2 + 1; + } + } while (dx <= 0); +} + +void Display::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { + int x_start, y_start; + int width, height; + this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height); + font->print(x_start, y_start, this, color, text); +} +void Display::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg) { + char buffer[256]; + int ret = vsnprintf(buffer, sizeof(buffer), format, arg); + if (ret > 0) + this->print(x, y, font, color, align, buffer); +} + +void Display::image(int x, int y, BaseImage *image, Color color_on, Color color_off) { + this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off); +} + +void Display::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) { + auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT))); + auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT))); + + switch (x_align) { + case ImageAlign::RIGHT: + x -= image->get_width(); + break; + case ImageAlign::CENTER_HORIZONTAL: + x -= image->get_width() / 2; + break; + case ImageAlign::LEFT: + default: + break; + } + + switch (y_align) { + case ImageAlign::BOTTOM: + y -= image->get_height(); + break; + case ImageAlign::CENTER_VERTICAL: + y -= image->get_height() / 2; + break; + case ImageAlign::TOP: + default: + break; + } + + image->draw(x, y, this, color_on, color_off); +} + +#ifdef USE_GRAPH +void Display::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); } +void Display::legend(int x, int y, graph::Graph *graph, Color color_on) { graph->draw_legend(this, x, y, color_on); } +#endif // USE_GRAPH + +#ifdef USE_QR_CODE +void Display::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) { + qr_code->draw(this, x, y, color_on, scale); +} +#endif // USE_QR_CODE + +void Display::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, + int *width, int *height) { + int x_offset, baseline; + font->measure(text, width, &x_offset, &baseline, height); + + auto x_align = TextAlign(int(align) & 0x18); + auto y_align = TextAlign(int(align) & 0x07); + + switch (x_align) { + case TextAlign::RIGHT: + *x1 = x - *width; + break; + case TextAlign::CENTER_HORIZONTAL: + *x1 = x - (*width) / 2; + break; + case TextAlign::LEFT: + default: + // LEFT + *x1 = x; + break; + } + + switch (y_align) { + case TextAlign::BOTTOM: + *y1 = y - *height; + break; + case TextAlign::BASELINE: + *y1 = y - baseline; + break; + case TextAlign::CENTER_VERTICAL: + *y1 = y - (*height) / 2; + break; + case TextAlign::TOP: + default: + *y1 = y; + break; + } +} +void Display::print(int x, int y, BaseFont *font, Color color, const char *text) { + this->print(x, y, font, color, TextAlign::TOP_LEFT, text); +} +void Display::print(int x, int y, BaseFont *font, TextAlign align, const char *text) { + this->print(x, y, font, COLOR_ON, align, text); +} +void Display::print(int x, int y, BaseFont *font, const char *text) { + this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text); +} +void Display::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) { + va_list arg; + va_start(arg, format); + this->vprintf_(x, y, font, color, align, format, arg); + va_end(arg); +} +void Display::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) { + va_list arg; + va_start(arg, format); + this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg); + va_end(arg); +} +void Display::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) { + va_list arg; + va_start(arg, format); + this->vprintf_(x, y, font, COLOR_ON, align, format, arg); + va_end(arg); +} +void Display::printf(int x, int y, BaseFont *font, const char *format, ...) { + va_list arg; + va_start(arg, format); + this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg); + va_end(arg); +} +void Display::set_writer(display_writer_t &&writer) { this->writer_ = writer; } +void Display::set_pages(std::vector pages) { + for (auto *page : pages) + page->set_parent(this); + + for (uint32_t i = 0; i < pages.size() - 1; i++) { + pages[i]->set_next(pages[i + 1]); + pages[i + 1]->set_prev(pages[i]); + } + pages[0]->set_prev(pages[pages.size() - 1]); + pages[pages.size() - 1]->set_next(pages[0]); + this->show_page(pages[0]); +} +void Display::show_page(DisplayPage *page) { + this->previous_page_ = this->page_; + this->page_ = page; + if (this->previous_page_ != this->page_) { + for (auto *t : on_page_change_triggers_) + t->process(this->previous_page_, this->page_); + } +} +void Display::show_next_page() { this->page_->show_next(); } +void Display::show_prev_page() { this->page_->show_prev(); } +void Display::do_update_() { + if (this->auto_clear_enabled_) { + this->clear(); + } + if (this->page_ != nullptr) { + this->page_->get_writer()(*this); + } else if (this->writer_.has_value()) { + (*this->writer_)(*this); + } + // remove all not ended clipping regions + while (is_clipping()) { + end_clipping(); + } +} +void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { + if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) + this->trigger(from, to); +} +void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) { + char buffer[64]; + size_t ret = time.strftime(buffer, sizeof(buffer), format); + if (ret > 0) + this->print(x, y, font, color, align, buffer); +} +void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { + this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); +} +void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) { + this->strftime(x, y, font, COLOR_ON, align, format, time); +} +void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) { + this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); +} + +void Display::start_clipping(Rect rect) { + if (!this->clipping_rectangle_.empty()) { + Rect r = this->clipping_rectangle_.back(); + rect.shrink(r); + } + this->clipping_rectangle_.push_back(rect); +} +void Display::end_clipping() { + if (this->clipping_rectangle_.empty()) { + ESP_LOGE(TAG, "clear: Clipping is not set."); + } else { + this->clipping_rectangle_.pop_back(); + } +} +void Display::extend_clipping(Rect add_rect) { + if (this->clipping_rectangle_.empty()) { + ESP_LOGE(TAG, "add: Clipping is not set."); + } else { + this->clipping_rectangle_.back().extend(add_rect); + } +} +void Display::shrink_clipping(Rect add_rect) { + if (this->clipping_rectangle_.empty()) { + ESP_LOGE(TAG, "add: Clipping is not set."); + } else { + this->clipping_rectangle_.back().shrink(add_rect); + } +} +Rect Display::get_clipping() { + if (this->clipping_rectangle_.empty()) { + return Rect(); + } else { + return this->clipping_rectangle_.back(); + } +} + +DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} +void DisplayPage::show() { this->parent_->show_page(this); } +void DisplayPage::show_next() { this->next_->show(); } +void DisplayPage::show_prev() { this->prev_->show(); } +void DisplayPage::set_parent(Display *parent) { this->parent_ = parent; } +void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; } +void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; } +const display_writer_t &DisplayPage::get_writer() const { return this->writer_; } + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h new file mode 100644 index 0000000000..2f6fb563cc --- /dev/null +++ b/esphome/components/display/display.h @@ -0,0 +1,575 @@ +#pragma once + +#include +#include + +#include "rect.h" + +#include "esphome/core/color.h" +#include "esphome/core/automation.h" +#include "esphome/core/time.h" + +#ifdef USE_GRAPH +#include "esphome/components/graph/graph.h" +#endif + +#ifdef USE_QR_CODE +#include "esphome/components/qr_code/qr_code.h" +#endif + +namespace esphome { +namespace display { + +/** TextAlign is used to tell the display class how to position a piece of text. By default + * the coordinates you enter for the print*() functions take the upper left corner of the text + * as the "anchor" point. You can customize this behavior to, for example, make the coordinates + * refer to the *center* of the text. + * + * All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis + * these options are allowed: + * + * - LEFT (x-coordinate of anchor point is on left) + * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text) + * - RIGHT (x-coordinate of anchor point is on right) + * + * For the Y-Axis alignment these options are allowed: + * + * - TOP (y-coordinate of anchor is on the top of the text) + * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text) + * - BASELINE (y-coordinate of anchor is on the baseline of the text) + * - BOTTOM (y-coordinate of anchor is on the bottom of the text) + * + * These options are then combined to create combined TextAlignment options like: + * - TOP_LEFT (default) + * - CENTER (anchor point is in the middle of the text bounds) + * - ... + */ +enum class TextAlign { + TOP = 0x00, + CENTER_VERTICAL = 0x01, + BASELINE = 0x02, + BOTTOM = 0x04, + + LEFT = 0x00, + CENTER_HORIZONTAL = 0x08, + RIGHT = 0x10, + + TOP_LEFT = TOP | LEFT, + TOP_CENTER = TOP | CENTER_HORIZONTAL, + TOP_RIGHT = TOP | RIGHT, + + CENTER_LEFT = CENTER_VERTICAL | LEFT, + CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL, + CENTER_RIGHT = CENTER_VERTICAL | RIGHT, + + BASELINE_LEFT = BASELINE | LEFT, + BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL, + BASELINE_RIGHT = BASELINE | RIGHT, + + BOTTOM_LEFT = BOTTOM | LEFT, + BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL, + BOTTOM_RIGHT = BOTTOM | RIGHT, +}; + +/** ImageAlign is used to tell the display class how to position a image. By default + * the coordinates you enter for the image() functions take the upper left corner of the image + * as the "anchor" point. You can customize this behavior to, for example, make the coordinates + * refer to the *center* of the image. + * + * All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis + * these options are allowed: + * + * - LEFT (x-coordinate of anchor point is on left) + * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image) + * - RIGHT (x-coordinate of anchor point is on right) + * + * For the Y-Axis alignment these options are allowed: + * + * - TOP (y-coordinate of anchor is on the top of the image) + * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image) + * - BOTTOM (y-coordinate of anchor is on the bottom of the image) + * + * These options are then combined to create combined TextAlignment options like: + * - TOP_LEFT (default) + * - CENTER (anchor point is in the middle of the image bounds) + * - ... + */ +enum class ImageAlign { + TOP = 0x00, + CENTER_VERTICAL = 0x01, + BOTTOM = 0x02, + + LEFT = 0x00, + CENTER_HORIZONTAL = 0x04, + RIGHT = 0x08, + + TOP_LEFT = TOP | LEFT, + TOP_CENTER = TOP | CENTER_HORIZONTAL, + TOP_RIGHT = TOP | RIGHT, + + CENTER_LEFT = CENTER_VERTICAL | LEFT, + CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL, + CENTER_RIGHT = CENTER_VERTICAL | RIGHT, + + BOTTOM_LEFT = BOTTOM | LEFT, + BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL, + BOTTOM_RIGHT = BOTTOM | RIGHT, + + HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT, + VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM +}; + +enum DisplayType { + DISPLAY_TYPE_BINARY = 1, + DISPLAY_TYPE_GRAYSCALE = 2, + DISPLAY_TYPE_COLOR = 3, +}; + +enum DisplayRotation { + DISPLAY_ROTATION_0_DEGREES = 0, + DISPLAY_ROTATION_90_DEGREES = 90, + DISPLAY_ROTATION_180_DEGREES = 180, + DISPLAY_ROTATION_270_DEGREES = 270, +}; + +class Display; +class DisplayBuffer; +class DisplayPage; +class DisplayOnPageChangeTrigger; + +using display_writer_t = std::function; +using display_buffer_writer_t = std::function; + +#define LOG_DISPLAY(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, prefix type); \ + ESP_LOGCONFIG(TAG, "%s Rotations: %d °", prefix, (obj)->rotation_); \ + ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \ + } + +/// Turn the pixel OFF. +extern const Color COLOR_OFF; +/// Turn the pixel ON. +extern const Color COLOR_ON; + +class BaseImage { + public: + virtual void draw(int x, int y, Display *display, Color color_on, Color color_off) = 0; + virtual int get_width() const = 0; + virtual int get_height() const = 0; +}; + +class BaseFont { + public: + virtual void print(int x, int y, Display *display, Color color, const char *text) = 0; + virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0; +}; + +class Display { + public: + /// Fill the entire screen with the given color. + virtual void fill(Color color); + /// Clear the entire screen by filling it with OFF pixels. + void clear(); + + /// Get the width of the image in pixels with rotation applied. + virtual int get_width() = 0; + /// Get the height of the image in pixels with rotation applied. + virtual int get_height() = 0; + + /// Set a single pixel at the specified coordinates to default color. + inline void draw_pixel_at(int x, int y) { this->draw_pixel_at(x, y, COLOR_ON); } + + /// Set a single pixel at the specified coordinates to the given color. + virtual void draw_pixel_at(int x, int y, Color color) = 0; + + /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color. + void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON); + + /// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color. + void horizontal_line(int x, int y, int width, Color color = COLOR_ON); + + /// Draw a vertical line from the point [x,y] to [x,y+width] with the given color. + void vertical_line(int x, int y, int height, Color color = COLOR_ON); + + /// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at + /// [x1+width,y1+height]. + void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON); + + /// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height]. + void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON); + + /// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color. + void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON); + + /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color. + void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON); + + /** Print `text` with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param align The alignment of the text. + * @param text The text to draw. + */ + void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text); + + /** Print `text` with the top left at [x,y] with `font`. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param text The text to draw. + */ + void print(int x, int y, BaseFont *font, Color color, const char *text); + + /** Print `text` with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param align The alignment of the text. + * @param text The text to draw. + */ + void print(int x, int y, BaseFont *font, TextAlign align, const char *text); + + /** Print `text` with the top left at [x,y] with `font`. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param font The font to draw the text with. + * @param text The text to draw. + */ + void print(int x, int y, BaseFont *font, const char *text); + + /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param align The alignment of the text. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) + __attribute__((format(printf, 7, 8))); + + /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7))); + + /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param align The alignment of the text. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) + __attribute__((format(printf, 6, 7))); + + /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param font The font to draw the text with. + * @param format The format to use. + * @param ... The arguments to use for the text formatting. + */ + void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6))); + + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param align The alignment of the text. + * @param format The strftime format to use. + * @param time The time to format. + */ + void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) + __attribute__((format(strftime, 7, 0))); + + /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param font The font to draw the text with. + * @param color The color to draw the text with. + * @param format The strftime format to use. + * @param time The time to format. + */ + void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) + __attribute__((format(strftime, 6, 0))); + + /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. + * + * @param x The x coordinate of the text alignment anchor point. + * @param y The y coordinate of the text alignment anchor point. + * @param font The font to draw the text with. + * @param align The alignment of the text. + * @param format The strftime format to use. + * @param time The time to format. + */ + void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) + __attribute__((format(strftime, 6, 0))); + + /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param font The font to draw the text with. + * @param format The strftime format to use. + * @param time The time to format. + */ + void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0))); + + /** Draw the `image` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param image The image to draw. + * @param color_on The color to replace in binary images for the on bits. + * @param color_off The color to replace in binary images for the off bits. + */ + void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); + + /** Draw the `image` at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param image The image to draw. + * @param align The alignment of the image. + * @param color_on The color to replace in binary images for the on bits. + * @param color_off The color to replace in binary images for the off bits. + */ + void image(int x, int y, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); + +#ifdef USE_GRAPH + /** Draw the `graph` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param graph The graph id to draw + * @param color_on The color to replace in binary images for the on bits. + */ + void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); + + /** Draw the `legend` for graph with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param graph The graph id for which the legend applies to + * @param graph The graph id for which the legend applies to + * @param graph The graph id for which the legend applies to + * @param name_font The font used for the trace name + * @param value_font The font used for the trace value and units + * @param color_on The color of the border + */ + void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); +#endif // USE_GRAPH + +#ifdef USE_QR_CODE + /** Draw the `qr_code` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param qr_code The qr_code to draw + * @param color_on The color to replace in binary images for the on bits. + */ + void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1); +#endif + + /** Get the text bounds of the given string. + * + * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions. + * @param y The y coordinate to place the string at, can be 0 if only interested in dimensions. + * @param text The text to measure. + * @param font The font to measure the text bounds with. + * @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions. + * @param x1 A pointer to store the returned x coordinate of the upper left corner in. + * @param y1 A pointer to store the returned y coordinate of the upper left corner in. + * @param width A pointer to store the returned text width in. + * @param height A pointer to store the returned text height in. + */ + void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width, + int *height); + + /// Internal method to set the display writer lambda. + void set_writer(display_writer_t &&writer); + void set_writer(const display_buffer_writer_t &writer) { + // Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef` + this->set_writer([writer](Display &display) { return writer((display::DisplayBuffer &) display); }); + } + + void show_page(DisplayPage *page); + void show_next_page(); + void show_prev_page(); + + void set_pages(std::vector pages); + + const DisplayPage *get_active_page() const { return this->page_; } + + void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); } + + /// Internal method to set the display rotation with. + void set_rotation(DisplayRotation rotation); + + // Internal method to set display auto clearing. + void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + + DisplayRotation get_rotation() const { return this->rotation_; } + + /** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays, + * returns the type the display is currently configured to. + */ + virtual DisplayType get_display_type() = 0; + + /** Set the clipping rectangle for further drawing + * + * @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen) + * + * return true if success, false if error + */ + void start_clipping(Rect rect); + void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) { + start_clipping(Rect(left, top, right - left, bottom - top)); + }; + + /** Add a rectangular region to the invalidation region + * - This is usually called when an element has been modified + * + * @param[in] rect: Rectangle to add to the invalidation region + */ + void extend_clipping(Rect rect); + void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) { + this->extend_clipping(Rect(left, top, right - left, bottom - top)); + }; + + /** substract a rectangular region to the invalidation region + * - This is usually called when an element has been modified + * + * @param[in] rect: Rectangle to add to the invalidation region + */ + void shrink_clipping(Rect rect); + void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { + this->shrink_clipping(Rect(left, top, right - left, bottom - top)); + }; + + /** Reset the invalidation region + */ + void end_clipping(); + + /** Get the current the clipping rectangle + * + * return rect for active clipping region + */ + Rect get_clipping(); + + bool is_clipping() const { return !this->clipping_rectangle_.empty(); } + + protected: + void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg); + + void do_update_(); + + DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES}; + optional writer_{}; + DisplayPage *page_{nullptr}; + DisplayPage *previous_page_{nullptr}; + std::vector on_page_change_triggers_; + bool auto_clear_enabled_{true}; + std::vector clipping_rectangle_; +}; + +class DisplayPage { + public: + DisplayPage(display_writer_t writer); + // Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef` + DisplayPage(const display_buffer_writer_t &writer) + : DisplayPage([writer](Display &display) { return writer((display::DisplayBuffer &) display); }) {} + void show(); + void show_next(); + void show_prev(); + void set_parent(Display *parent); + void set_prev(DisplayPage *prev); + void set_next(DisplayPage *next); + const display_writer_t &get_writer() const; + + protected: + Display *parent_; + display_writer_t writer_; + DisplayPage *prev_{nullptr}; + DisplayPage *next_{nullptr}; +}; + +template class DisplayPageShowAction : public Action { + public: + TEMPLATABLE_VALUE(DisplayPage *, page) + + void play(Ts... x) override { + auto *page = this->page_.value(x...); + if (page != nullptr) { + page->show(); + } + } +}; + +template class DisplayPageShowNextAction : public Action { + public: + DisplayPageShowNextAction(Display *buffer) : buffer_(buffer) {} + + void play(Ts... x) override { this->buffer_->show_next_page(); } + + Display *buffer_; +}; + +template class DisplayPageShowPrevAction : public Action { + public: + DisplayPageShowPrevAction(Display *buffer) : buffer_(buffer) {} + + void play(Ts... x) override { this->buffer_->show_prev_page(); } + + Display *buffer_; +}; + +template class DisplayIsDisplayingPageCondition : public Condition { + public: + DisplayIsDisplayingPageCondition(Display *parent) : parent_(parent) {} + + void set_page(DisplayPage *page) { this->page_ = page; } + bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; } + + protected: + Display *parent_; + DisplayPage *page_; +}; + +class DisplayOnPageChangeTrigger : public Trigger { + public: + explicit DisplayOnPageChangeTrigger(Display *parent) { parent->add_on_page_change_trigger(this); } + void process(DisplayPage *from, DisplayPage *to); + void set_from(DisplayPage *p) { this->from_ = p; } + void set_to(DisplayPage *p) { this->to_ = p; } + + protected: + DisplayPage *from_{nullptr}; + DisplayPage *to_{nullptr}; +}; + +} // namespace display +} // namespace esphome diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index 86e8624d33..3af1b63e01 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -1,10 +1,8 @@ #include "display_buffer.h" #include + #include "esphome/core/application.h" -#include "esphome/core/color.h" -#include "esphome/core/hal.h" -#include "esphome/core/helpers.h" #include "esphome/core/log.h" namespace esphome { @@ -12,9 +10,6 @@ namespace display { static const char *const TAG = "display"; -const Color COLOR_OFF(0, 0, 0, 0); -const Color COLOR_ON(255, 255, 255, 255); - void DisplayBuffer::init_internal_(uint32_t buffer_length) { ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); this->buffer_ = allocator.allocate(buffer_length); @@ -25,8 +20,6 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) { this->clear(); } -void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); } -void DisplayBuffer::clear() { this->fill(COLOR_OFF); } int DisplayBuffer::get_width() { switch (this->rotation_) { case DISPLAY_ROTATION_90_DEGREES: @@ -38,6 +31,7 @@ int DisplayBuffer::get_width() { return this->get_width_internal(); } } + int DisplayBuffer::get_height() { switch (this->rotation_) { case DISPLAY_ROTATION_0_DEGREES: @@ -49,7 +43,7 @@ int DisplayBuffer::get_height() { return this->get_width_internal(); } } -void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; } + void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) { if (!this->get_clipping().inside(x, y)) return; // NOLINT @@ -73,333 +67,6 @@ void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) { this->draw_absolute_pixel_internal(x, y, color); App.feed_wdt(); } -void HOT DisplayBuffer::line(int x1, int y1, int x2, int y2, Color color) { - const int32_t dx = abs(x2 - x1), sx = x1 < x2 ? 1 : -1; - const int32_t dy = -abs(y2 - y1), sy = y1 < y2 ? 1 : -1; - int32_t err = dx + dy; - - while (true) { - this->draw_pixel_at(x1, y1, color); - if (x1 == x2 && y1 == y2) - break; - int32_t e2 = 2 * err; - if (e2 >= dy) { - err += dy; - x1 += sx; - } - if (e2 <= dx) { - err += dx; - y1 += sy; - } - } -} -void HOT DisplayBuffer::horizontal_line(int x, int y, int width, Color color) { - // Future: Could be made more efficient by manipulating buffer directly in certain rotations. - for (int i = x; i < x + width; i++) - this->draw_pixel_at(i, y, color); -} -void HOT DisplayBuffer::vertical_line(int x, int y, int height, Color color) { - // Future: Could be made more efficient by manipulating buffer directly in certain rotations. - for (int i = y; i < y + height; i++) - this->draw_pixel_at(x, i, color); -} -void DisplayBuffer::rectangle(int x1, int y1, int width, int height, Color color) { - this->horizontal_line(x1, y1, width, color); - this->horizontal_line(x1, y1 + height - 1, width, color); - this->vertical_line(x1, y1, height, color); - this->vertical_line(x1 + width - 1, y1, height, color); -} -void DisplayBuffer::filled_rectangle(int x1, int y1, int width, int height, Color color) { - // Future: Use vertical_line and horizontal_line methods depending on rotation to reduce memory accesses. - for (int i = y1; i < y1 + height; i++) { - this->horizontal_line(x1, i, width, color); - } -} -void HOT DisplayBuffer::circle(int center_x, int center_xy, int radius, Color color) { - int dx = -radius; - int dy = 0; - int err = 2 - 2 * radius; - int e2; - - do { - this->draw_pixel_at(center_x - dx, center_xy + dy, color); - this->draw_pixel_at(center_x + dx, center_xy + dy, color); - this->draw_pixel_at(center_x + dx, center_xy - dy, color); - this->draw_pixel_at(center_x - dx, center_xy - dy, color); - e2 = err; - if (e2 < dy) { - err += ++dy * 2 + 1; - if (-dx == dy && e2 <= dx) { - e2 = 0; - } - } - if (e2 > dx) { - err += ++dx * 2 + 1; - } - } while (dx <= 0); -} -void DisplayBuffer::filled_circle(int center_x, int center_y, int radius, Color color) { - int dx = -int32_t(radius); - int dy = 0; - int err = 2 - 2 * radius; - int e2; - - do { - this->draw_pixel_at(center_x - dx, center_y + dy, color); - this->draw_pixel_at(center_x + dx, center_y + dy, color); - this->draw_pixel_at(center_x + dx, center_y - dy, color); - this->draw_pixel_at(center_x - dx, center_y - dy, color); - int hline_width = 2 * (-dx) + 1; - this->horizontal_line(center_x + dx, center_y + dy, hline_width, color); - this->horizontal_line(center_x + dx, center_y - dy, hline_width, color); - e2 = err; - if (e2 < dy) { - err += ++dy * 2 + 1; - if (-dx == dy && e2 <= dx) { - e2 = 0; - } - } - if (e2 > dx) { - err += ++dx * 2 + 1; - } - } while (dx <= 0); -} - -void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text) { - int x_start, y_start; - int width, height; - this->get_text_bounds(x, y, text, font, align, &x_start, &y_start, &width, &height); - font->print(x_start, y_start, this, color, text); -} -void DisplayBuffer::vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, - va_list arg) { - char buffer[256]; - int ret = vsnprintf(buffer, sizeof(buffer), format, arg); - if (ret > 0) - this->print(x, y, font, color, align, buffer); -} - -void DisplayBuffer::image(int x, int y, BaseImage *image, Color color_on, Color color_off) { - this->image(x, y, image, ImageAlign::TOP_LEFT, color_on, color_off); -} - -void DisplayBuffer::image(int x, int y, BaseImage *image, ImageAlign align, Color color_on, Color color_off) { - auto x_align = ImageAlign(int(align) & (int(ImageAlign::HORIZONTAL_ALIGNMENT))); - auto y_align = ImageAlign(int(align) & (int(ImageAlign::VERTICAL_ALIGNMENT))); - - switch (x_align) { - case ImageAlign::RIGHT: - x -= image->get_width(); - break; - case ImageAlign::CENTER_HORIZONTAL: - x -= image->get_width() / 2; - break; - case ImageAlign::LEFT: - default: - break; - } - - switch (y_align) { - case ImageAlign::BOTTOM: - y -= image->get_height(); - break; - case ImageAlign::CENTER_VERTICAL: - y -= image->get_height() / 2; - break; - case ImageAlign::TOP: - default: - break; - } - - image->draw(x, y, this, color_on, color_off); -} - -#ifdef USE_GRAPH -void DisplayBuffer::graph(int x, int y, graph::Graph *graph, Color color_on) { graph->draw(this, x, y, color_on); } -void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) { - graph->draw_legend(this, x, y, color_on); -} -#endif // USE_GRAPH - -#ifdef USE_QR_CODE -void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) { - qr_code->draw(this, x, y, color_on, scale); -} -#endif // USE_QR_CODE - -void DisplayBuffer::get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, - int *width, int *height) { - int x_offset, baseline; - font->measure(text, width, &x_offset, &baseline, height); - - auto x_align = TextAlign(int(align) & 0x18); - auto y_align = TextAlign(int(align) & 0x07); - - switch (x_align) { - case TextAlign::RIGHT: - *x1 = x - *width; - break; - case TextAlign::CENTER_HORIZONTAL: - *x1 = x - (*width) / 2; - break; - case TextAlign::LEFT: - default: - // LEFT - *x1 = x; - break; - } - - switch (y_align) { - case TextAlign::BOTTOM: - *y1 = y - *height; - break; - case TextAlign::BASELINE: - *y1 = y - baseline; - break; - case TextAlign::CENTER_VERTICAL: - *y1 = y - (*height) / 2; - break; - case TextAlign::TOP: - default: - *y1 = y; - break; - } -} -void DisplayBuffer::print(int x, int y, BaseFont *font, Color color, const char *text) { - this->print(x, y, font, color, TextAlign::TOP_LEFT, text); -} -void DisplayBuffer::print(int x, int y, BaseFont *font, TextAlign align, const char *text) { - this->print(x, y, font, COLOR_ON, align, text); -} -void DisplayBuffer::print(int x, int y, BaseFont *font, const char *text) { - this->print(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, text); -} -void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) { - va_list arg; - va_start(arg, format); - this->vprintf_(x, y, font, color, align, format, arg); - va_end(arg); -} -void DisplayBuffer::printf(int x, int y, BaseFont *font, Color color, const char *format, ...) { - va_list arg; - va_start(arg, format); - this->vprintf_(x, y, font, color, TextAlign::TOP_LEFT, format, arg); - va_end(arg); -} -void DisplayBuffer::printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) { - va_list arg; - va_start(arg, format); - this->vprintf_(x, y, font, COLOR_ON, align, format, arg); - va_end(arg); -} -void DisplayBuffer::printf(int x, int y, BaseFont *font, const char *format, ...) { - va_list arg; - va_start(arg, format); - this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg); - va_end(arg); -} -void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; } -void DisplayBuffer::set_pages(std::vector pages) { - for (auto *page : pages) - page->set_parent(this); - - for (uint32_t i = 0; i < pages.size() - 1; i++) { - pages[i]->set_next(pages[i + 1]); - pages[i + 1]->set_prev(pages[i]); - } - pages[0]->set_prev(pages[pages.size() - 1]); - pages[pages.size() - 1]->set_next(pages[0]); - this->show_page(pages[0]); -} -void DisplayBuffer::show_page(DisplayPage *page) { - this->previous_page_ = this->page_; - this->page_ = page; - if (this->previous_page_ != this->page_) { - for (auto *t : on_page_change_triggers_) - t->process(this->previous_page_, this->page_); - } -} -void DisplayBuffer::show_next_page() { this->page_->show_next(); } -void DisplayBuffer::show_prev_page() { this->page_->show_prev(); } -void DisplayBuffer::do_update_() { - if (this->auto_clear_enabled_) { - this->clear(); - } - if (this->page_ != nullptr) { - this->page_->get_writer()(*this); - } else if (this->writer_.has_value()) { - (*this->writer_)(*this); - } - // remove all not ended clipping regions - while (is_clipping()) { - end_clipping(); - } -} -void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) { - if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to)) - this->trigger(from, to); -} -void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, - ESPTime time) { - char buffer[64]; - size_t ret = time.strftime(buffer, sizeof(buffer), format); - if (ret > 0) - this->print(x, y, font, color, align, buffer); -} -void DisplayBuffer::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) { - this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time); -} -void DisplayBuffer::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, align, format, time); -} -void DisplayBuffer::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) { - this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time); -} - -void DisplayBuffer::start_clipping(Rect rect) { - if (!this->clipping_rectangle_.empty()) { - Rect r = this->clipping_rectangle_.back(); - rect.shrink(r); - } - this->clipping_rectangle_.push_back(rect); -} -void DisplayBuffer::end_clipping() { - if (this->clipping_rectangle_.empty()) { - ESP_LOGE(TAG, "clear: Clipping is not set."); - } else { - this->clipping_rectangle_.pop_back(); - } -} -void DisplayBuffer::extend_clipping(Rect add_rect) { - if (this->clipping_rectangle_.empty()) { - ESP_LOGE(TAG, "add: Clipping is not set."); - } else { - this->clipping_rectangle_.back().extend(add_rect); - } -} -void DisplayBuffer::shrink_clipping(Rect add_rect) { - if (this->clipping_rectangle_.empty()) { - ESP_LOGE(TAG, "add: Clipping is not set."); - } else { - this->clipping_rectangle_.back().shrink(add_rect); - } -} -Rect DisplayBuffer::get_clipping() { - if (this->clipping_rectangle_.empty()) { - return Rect(); - } else { - return this->clipping_rectangle_.back(); - } -} - -DisplayPage::DisplayPage(display_writer_t writer) : writer_(std::move(writer)) {} -void DisplayPage::show() { this->parent_->show_page(this); } -void DisplayPage::show_next() { this->next_->show(); } -void DisplayPage::show_prev() { this->prev_->show(); } -void DisplayPage::set_parent(DisplayBuffer *parent) { this->parent_ = parent; } -void DisplayPage::set_prev(DisplayPage *prev) { this->prev_ = prev; } -void DisplayPage::set_next(DisplayPage *next) { this->next_ = next; } -const display_writer_t &DisplayPage::get_writer() const { return this->writer_; } } // namespace display } // namespace esphome diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index 1a62da2323..869d97613a 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -2,568 +2,35 @@ #include #include -#include "rect.h" + +#include "display.h" #include "display_color_utils.h" -#include "esphome/core/automation.h" + #include "esphome/core/component.h" #include "esphome/core/defines.h" -#include "esphome/core/time.h" - -#ifdef USE_GRAPH -#include "esphome/components/graph/graph.h" -#endif - -#ifdef USE_QR_CODE -#include "esphome/components/qr_code/qr_code.h" -#endif namespace esphome { namespace display { -/** TextAlign is used to tell the display class how to position a piece of text. By default - * the coordinates you enter for the print*() functions take the upper left corner of the text - * as the "anchor" point. You can customize this behavior to, for example, make the coordinates - * refer to the *center* of the text. - * - * All text alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis - * these options are allowed: - * - * - LEFT (x-coordinate of anchor point is on left) - * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the text) - * - RIGHT (x-coordinate of anchor point is on right) - * - * For the Y-Axis alignment these options are allowed: - * - * - TOP (y-coordinate of anchor is on the top of the text) - * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the text) - * - BASELINE (y-coordinate of anchor is on the baseline of the text) - * - BOTTOM (y-coordinate of anchor is on the bottom of the text) - * - * These options are then combined to create combined TextAlignment options like: - * - TOP_LEFT (default) - * - CENTER (anchor point is in the middle of the text bounds) - * - ... - */ -enum class TextAlign { - TOP = 0x00, - CENTER_VERTICAL = 0x01, - BASELINE = 0x02, - BOTTOM = 0x04, - - LEFT = 0x00, - CENTER_HORIZONTAL = 0x08, - RIGHT = 0x10, - - TOP_LEFT = TOP | LEFT, - TOP_CENTER = TOP | CENTER_HORIZONTAL, - TOP_RIGHT = TOP | RIGHT, - - CENTER_LEFT = CENTER_VERTICAL | LEFT, - CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL, - CENTER_RIGHT = CENTER_VERTICAL | RIGHT, - - BASELINE_LEFT = BASELINE | LEFT, - BASELINE_CENTER = BASELINE | CENTER_HORIZONTAL, - BASELINE_RIGHT = BASELINE | RIGHT, - - BOTTOM_LEFT = BOTTOM | LEFT, - BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL, - BOTTOM_RIGHT = BOTTOM | RIGHT, -}; - -/** ImageAlign is used to tell the display class how to position a image. By default - * the coordinates you enter for the image() functions take the upper left corner of the image - * as the "anchor" point. You can customize this behavior to, for example, make the coordinates - * refer to the *center* of the image. - * - * All image alignments consist of an X and Y-coordinate alignment. For the alignment along the X-axis - * these options are allowed: - * - * - LEFT (x-coordinate of anchor point is on left) - * - CENTER_HORIZONTAL (x-coordinate of anchor point is in the horizontal center of the image) - * - RIGHT (x-coordinate of anchor point is on right) - * - * For the Y-Axis alignment these options are allowed: - * - * - TOP (y-coordinate of anchor is on the top of the image) - * - CENTER_VERTICAL (y-coordinate of anchor is in the vertical center of the image) - * - BOTTOM (y-coordinate of anchor is on the bottom of the image) - * - * These options are then combined to create combined TextAlignment options like: - * - TOP_LEFT (default) - * - CENTER (anchor point is in the middle of the image bounds) - * - ... - */ -enum class ImageAlign { - TOP = 0x00, - CENTER_VERTICAL = 0x01, - BOTTOM = 0x02, - - LEFT = 0x00, - CENTER_HORIZONTAL = 0x04, - RIGHT = 0x08, - - TOP_LEFT = TOP | LEFT, - TOP_CENTER = TOP | CENTER_HORIZONTAL, - TOP_RIGHT = TOP | RIGHT, - - CENTER_LEFT = CENTER_VERTICAL | LEFT, - CENTER = CENTER_VERTICAL | CENTER_HORIZONTAL, - CENTER_RIGHT = CENTER_VERTICAL | RIGHT, - - BOTTOM_LEFT = BOTTOM | LEFT, - BOTTOM_CENTER = BOTTOM | CENTER_HORIZONTAL, - BOTTOM_RIGHT = BOTTOM | RIGHT, - - HORIZONTAL_ALIGNMENT = LEFT | CENTER_HORIZONTAL | RIGHT, - VERTICAL_ALIGNMENT = TOP | CENTER_VERTICAL | BOTTOM -}; - -enum DisplayType { - DISPLAY_TYPE_BINARY = 1, - DISPLAY_TYPE_GRAYSCALE = 2, - DISPLAY_TYPE_COLOR = 3, -}; - -enum DisplayRotation { - DISPLAY_ROTATION_0_DEGREES = 0, - DISPLAY_ROTATION_90_DEGREES = 90, - DISPLAY_ROTATION_180_DEGREES = 180, - DISPLAY_ROTATION_270_DEGREES = 270, -}; - -class DisplayBuffer; -class DisplayPage; -class DisplayOnPageChangeTrigger; - -using display_writer_t = std::function; - -#define LOG_DISPLAY(prefix, type, obj) \ - if ((obj) != nullptr) { \ - ESP_LOGCONFIG(TAG, prefix type); \ - ESP_LOGCONFIG(TAG, "%s Rotations: %d °", prefix, (obj)->rotation_); \ - ESP_LOGCONFIG(TAG, "%s Dimensions: %dpx x %dpx", prefix, (obj)->get_width(), (obj)->get_height()); \ - } - -/// Turn the pixel OFF. -extern const Color COLOR_OFF; -/// Turn the pixel ON. -extern const Color COLOR_ON; - -class BaseImage { +class DisplayBuffer : public Display { public: - virtual void draw(int x, int y, DisplayBuffer *display, Color color_on, Color color_off) = 0; - virtual int get_width() const = 0; - virtual int get_height() const = 0; -}; - -class BaseFont { - public: - virtual void print(int x, int y, DisplayBuffer *display, Color color, const char *text) = 0; - virtual void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) = 0; -}; - -class DisplayBuffer { - public: - /// Fill the entire screen with the given color. - virtual void fill(Color color); - /// Clear the entire screen by filling it with OFF pixels. - void clear(); - /// Get the width of the image in pixels with rotation applied. - int get_width(); + int get_width() override; /// Get the height of the image in pixels with rotation applied. - int get_height(); + int get_height() override; /// Set a single pixel at the specified coordinates to the given color. - void draw_pixel_at(int x, int y, Color color = COLOR_ON); - - /// Draw a straight line from the point [x1,y1] to [x2,y2] with the given color. - void line(int x1, int y1, int x2, int y2, Color color = COLOR_ON); - - /// Draw a horizontal line from the point [x,y] to [x+width,y] with the given color. - void horizontal_line(int x, int y, int width, Color color = COLOR_ON); - - /// Draw a vertical line from the point [x,y] to [x,y+width] with the given color. - void vertical_line(int x, int y, int height, Color color = COLOR_ON); - - /// Draw the outline of a rectangle with the top left point at [x1,y1] and the bottom right point at - /// [x1+width,y1+height]. - void rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON); - - /// Fill a rectangle with the top left point at [x1,y1] and the bottom right point at [x1+width,y1+height]. - void filled_rectangle(int x1, int y1, int width, int height, Color color = COLOR_ON); - - /// Draw the outline of a circle centered around [center_x,center_y] with the radius radius with the given color. - void circle(int center_x, int center_xy, int radius, Color color = COLOR_ON); - - /// Fill a circle centered around [center_x,center_y] with the radius radius with the given color. - void filled_circle(int center_x, int center_y, int radius, Color color = COLOR_ON); - - /** Print `text` with the anchor point at [x,y] with `font`. - * - * @param x The x coordinate of the text alignment anchor point. - * @param y The y coordinate of the text alignment anchor point. - * @param font The font to draw the text with. - * @param color The color to draw the text with. - * @param align The alignment of the text. - * @param text The text to draw. - */ - void print(int x, int y, BaseFont *font, Color color, TextAlign align, const char *text); - - /** Print `text` with the top left at [x,y] with `font`. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param font The font to draw the text with. - * @param color The color to draw the text with. - * @param text The text to draw. - */ - void print(int x, int y, BaseFont *font, Color color, const char *text); - - /** Print `text` with the anchor point at [x,y] with `font`. - * - * @param x The x coordinate of the text alignment anchor point. - * @param y The y coordinate of the text alignment anchor point. - * @param font The font to draw the text with. - * @param align The alignment of the text. - * @param text The text to draw. - */ - void print(int x, int y, BaseFont *font, TextAlign align, const char *text); - - /** Print `text` with the top left at [x,y] with `font`. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param font The font to draw the text with. - * @param text The text to draw. - */ - void print(int x, int y, BaseFont *font, const char *text); - - /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. - * - * @param x The x coordinate of the text alignment anchor point. - * @param y The y coordinate of the text alignment anchor point. - * @param font The font to draw the text with. - * @param color The color to draw the text with. - * @param align The alignment of the text. - * @param format The format to use. - * @param ... The arguments to use for the text formatting. - */ - void printf(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ...) - __attribute__((format(printf, 7, 8))); - - /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param font The font to draw the text with. - * @param color The color to draw the text with. - * @param format The format to use. - * @param ... The arguments to use for the text formatting. - */ - void printf(int x, int y, BaseFont *font, Color color, const char *format, ...) __attribute__((format(printf, 6, 7))); - - /** Evaluate the printf-format `format` and print the result with the anchor point at [x,y] with `font`. - * - * @param x The x coordinate of the text alignment anchor point. - * @param y The y coordinate of the text alignment anchor point. - * @param font The font to draw the text with. - * @param align The alignment of the text. - * @param format The format to use. - * @param ... The arguments to use for the text formatting. - */ - void printf(int x, int y, BaseFont *font, TextAlign align, const char *format, ...) - __attribute__((format(printf, 6, 7))); - - /** Evaluate the printf-format `format` and print the result with the top left at [x,y] with `font`. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param font The font to draw the text with. - * @param format The format to use. - * @param ... The arguments to use for the text formatting. - */ - void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6))); - - /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. - * - * @param x The x coordinate of the text alignment anchor point. - * @param y The y coordinate of the text alignment anchor point. - * @param font The font to draw the text with. - * @param color The color to draw the text with. - * @param align The alignment of the text. - * @param format The strftime format to use. - * @param time The time to format. - */ - void strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) - __attribute__((format(strftime, 7, 0))); - - /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param font The font to draw the text with. - * @param color The color to draw the text with. - * @param format The strftime format to use. - * @param time The time to format. - */ - void strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) - __attribute__((format(strftime, 6, 0))); - - /** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`. - * - * @param x The x coordinate of the text alignment anchor point. - * @param y The y coordinate of the text alignment anchor point. - * @param font The font to draw the text with. - * @param align The alignment of the text. - * @param format The strftime format to use. - * @param time The time to format. - */ - void strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) - __attribute__((format(strftime, 6, 0))); - - /** Evaluate the strftime-format `format` and print the result with the top left at [x,y] with `font`. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param font The font to draw the text with. - * @param format The strftime format to use. - * @param time The time to format. - */ - void strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) __attribute__((format(strftime, 5, 0))); - - /** Draw the `image` with the top-left corner at [x,y] to the screen. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param image The image to draw. - * @param color_on The color to replace in binary images for the on bits. - * @param color_off The color to replace in binary images for the off bits. - */ - void image(int x, int y, BaseImage *image, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); - - /** Draw the `image` at [x,y] to the screen. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param image The image to draw. - * @param align The alignment of the image. - * @param color_on The color to replace in binary images for the on bits. - * @param color_off The color to replace in binary images for the off bits. - */ - void image(int x, int y, BaseImage *image, ImageAlign align, Color color_on = COLOR_ON, Color color_off = COLOR_OFF); - -#ifdef USE_GRAPH - /** Draw the `graph` with the top-left corner at [x,y] to the screen. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param graph The graph id to draw - * @param color_on The color to replace in binary images for the on bits. - */ - void graph(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); - - /** Draw the `legend` for graph with the top-left corner at [x,y] to the screen. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param graph The graph id for which the legend applies to - * @param graph The graph id for which the legend applies to - * @param graph The graph id for which the legend applies to - * @param name_font The font used for the trace name - * @param value_font The font used for the trace value and units - * @param color_on The color of the border - */ - void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); -#endif // USE_GRAPH - -#ifdef USE_QR_CODE - /** Draw the `qr_code` with the top-left corner at [x,y] to the screen. - * - * @param x The x coordinate of the upper left corner. - * @param y The y coordinate of the upper left corner. - * @param qr_code The qr_code to draw - * @param color_on The color to replace in binary images for the on bits. - */ - void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1); -#endif - - /** Get the text bounds of the given string. - * - * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions. - * @param y The y coordinate to place the string at, can be 0 if only interested in dimensions. - * @param text The text to measure. - * @param font The font to measure the text bounds with. - * @param align The alignment of the text. Set to TextAlign::TOP_LEFT if only interested in dimensions. - * @param x1 A pointer to store the returned x coordinate of the upper left corner in. - * @param y1 A pointer to store the returned y coordinate of the upper left corner in. - * @param width A pointer to store the returned text width in. - * @param height A pointer to store the returned text height in. - */ - void get_text_bounds(int x, int y, const char *text, BaseFont *font, TextAlign align, int *x1, int *y1, int *width, - int *height); - - /// Internal method to set the display writer lambda. - void set_writer(display_writer_t &&writer); - - void show_page(DisplayPage *page); - void show_next_page(); - void show_prev_page(); - - void set_pages(std::vector pages); - - const DisplayPage *get_active_page() const { return this->page_; } - - void add_on_page_change_trigger(DisplayOnPageChangeTrigger *t) { this->on_page_change_triggers_.push_back(t); } - - /// Internal method to set the display rotation with. - void set_rotation(DisplayRotation rotation); - - // Internal method to set display auto clearing. - void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + void draw_pixel_at(int x, int y, Color color) override; virtual int get_height_internal() = 0; virtual int get_width_internal() = 0; - DisplayRotation get_rotation() const { return this->rotation_; } - - /** Get the type of display that the buffer corresponds to. In case of dynamically configurable displays, - * returns the type the display is currently configured to. - */ - virtual DisplayType get_display_type() = 0; - - /** Set the clipping rectangle for further drawing - * - * @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen) - * - * return true if success, false if error - */ - void start_clipping(Rect rect); - void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) { - start_clipping(Rect(left, top, right - left, bottom - top)); - }; - - /** Add a rectangular region to the invalidation region - * - This is usually called when an element has been modified - * - * @param[in] rect: Rectangle to add to the invalidation region - */ - void extend_clipping(Rect rect); - void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) { - this->extend_clipping(Rect(left, top, right - left, bottom - top)); - }; - - /** substract a rectangular region to the invalidation region - * - This is usually called when an element has been modified - * - * @param[in] rect: Rectangle to add to the invalidation region - */ - void shrink_clipping(Rect rect); - void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) { - this->shrink_clipping(Rect(left, top, right - left, bottom - top)); - }; - - /** Reset the invalidation region - */ - void end_clipping(); - - /** Get the current the clipping rectangle - * - * return rect for active clipping region - */ - Rect get_clipping(); - - bool is_clipping() const { return !this->clipping_rectangle_.empty(); } protected: - void vprintf_(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, va_list arg); - virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; void init_internal_(uint32_t buffer_length); - void do_update_(); - uint8_t *buffer_{nullptr}; - DisplayRotation rotation_{DISPLAY_ROTATION_0_DEGREES}; - optional writer_{}; - DisplayPage *page_{nullptr}; - DisplayPage *previous_page_{nullptr}; - std::vector on_page_change_triggers_; - bool auto_clear_enabled_{true}; - std::vector clipping_rectangle_; -}; - -class DisplayPage { - public: - DisplayPage(display_writer_t writer); - void show(); - void show_next(); - void show_prev(); - void set_parent(DisplayBuffer *parent); - void set_prev(DisplayPage *prev); - void set_next(DisplayPage *next); - const display_writer_t &get_writer() const; - - protected: - DisplayBuffer *parent_; - display_writer_t writer_; - DisplayPage *prev_{nullptr}; - DisplayPage *next_{nullptr}; -}; - -template class DisplayPageShowAction : public Action { - public: - TEMPLATABLE_VALUE(DisplayPage *, page) - - void play(Ts... x) override { - auto *page = this->page_.value(x...); - if (page != nullptr) { - page->show(); - } - } -}; - -template class DisplayPageShowNextAction : public Action { - public: - DisplayPageShowNextAction(DisplayBuffer *buffer) : buffer_(buffer) {} - - void play(Ts... x) override { this->buffer_->show_next_page(); } - - DisplayBuffer *buffer_; -}; - -template class DisplayPageShowPrevAction : public Action { - public: - DisplayPageShowPrevAction(DisplayBuffer *buffer) : buffer_(buffer) {} - - void play(Ts... x) override { this->buffer_->show_prev_page(); } - - DisplayBuffer *buffer_; -}; - -template class DisplayIsDisplayingPageCondition : public Condition { - public: - DisplayIsDisplayingPageCondition(DisplayBuffer *parent) : parent_(parent) {} - - void set_page(DisplayPage *page) { this->page_ = page; } - bool check(Ts... x) override { return this->parent_->get_active_page() == this->page_; } - - protected: - DisplayBuffer *parent_; - DisplayPage *page_; -}; - -class DisplayOnPageChangeTrigger : public Trigger { - public: - explicit DisplayOnPageChangeTrigger(DisplayBuffer *parent) { parent->add_on_page_change_trigger(this); } - void process(DisplayPage *from, DisplayPage *to); - void set_from(DisplayPage *p) { this->from_ = p; } - void set_to(DisplayPage *p) { this->to_ = p; } - - protected: - DisplayPage *from_{nullptr}; - DisplayPage *to_{nullptr}; }; } // namespace display diff --git a/esphome/components/font/font.cpp b/esphome/components/font/font.cpp index fcb2bb1750..ef5b2b788d 100644 --- a/esphome/components/font/font.cpp +++ b/esphome/components/font/font.cpp @@ -10,7 +10,7 @@ namespace font { static const char *const TAG = "font"; -void Glyph::draw(int x_at, int y_start, display::DisplayBuffer *display, Color color) const { +void Glyph::draw(int x_at, int y_start, display::Display *display, Color color) const { int scan_x1, scan_y1, scan_width, scan_height; this->scan_area(&scan_x1, &scan_y1, &scan_width, &scan_height); @@ -118,7 +118,7 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in *x_offset = min_x; *width = x - min_x; } -void Font::print(int x_start, int y_start, display::DisplayBuffer *display, Color color, const char *text) { +void Font::print(int x_start, int y_start, display::Display *display, Color color, const char *text) { int i = 0; int x_at = x_start; while (text[i] != '\0') { diff --git a/esphome/components/font/font.h b/esphome/components/font/font.h index d88ebd9be6..03171a6126 100644 --- a/esphome/components/font/font.h +++ b/esphome/components/font/font.h @@ -22,7 +22,7 @@ class Glyph { public: Glyph(const GlyphData *data) : glyph_data_(data) {} - void draw(int x, int y, display::DisplayBuffer *display, Color color) const; + void draw(int x, int y, display::Display *display, Color color) const; const char *get_char() const; @@ -50,7 +50,7 @@ class Font : public display::BaseFont { int match_next_glyph(const char *str, int *match_length); - void print(int x_start, int y_start, display::DisplayBuffer *display, Color color, const char *text) override; + void print(int x_start, int y_start, display::Display *display, Color color, const char *text) override; void measure(const char *str, int *width, int *x_offset, int *baseline, int *height) override; inline int get_baseline() { return this->baseline_; } inline int get_height() { return this->height_; } diff --git a/esphome/components/graph/graph.cpp b/esphome/components/graph/graph.cpp index c229f17dd8..294e16dbb1 100644 --- a/esphome/components/graph/graph.cpp +++ b/esphome/components/graph/graph.cpp @@ -1,5 +1,5 @@ #include "graph.h" -#include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display.h" #include "esphome/core/color.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" @@ -56,7 +56,7 @@ void GraphTrace::init(Graph *g) { this->data_.set_update_time_ms(g->get_duration() * 1000 / g->get_width()); } -void Graph::draw(DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) { +void Graph::draw(Display *buff, uint16_t x_offset, uint16_t y_offset, Color color) { /// Plot border if (this->border_) { buff->horizontal_line(x_offset, y_offset, this->width_, color); @@ -303,7 +303,7 @@ void GraphLegend::init(Graph *g) { } } -void Graph::draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color) { +void Graph::draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color) { if (!legend_) return; diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index 87c21fd7d1..339a6f6d94 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -8,9 +8,9 @@ namespace esphome { -// forward declare DisplayBuffer +// forward declare Display namespace display { -class DisplayBuffer; +class Display; class BaseFont; } // namespace display @@ -133,8 +133,8 @@ class GraphTrace { class Graph : public Component { public: - void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color); - void draw_legend(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color); + void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color); + void draw_legend(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color); void setup() override; float get_setup_priority() const override { return setup_priority::PROCESSOR; } diff --git a/esphome/components/image/image.cpp b/esphome/components/image/image.cpp index 66a085ebe2..0ddb8110cb 100644 --- a/esphome/components/image/image.cpp +++ b/esphome/components/image/image.cpp @@ -5,7 +5,7 @@ namespace esphome { namespace image { -void Image::draw(int x, int y, display::DisplayBuffer *display, Color color_on, Color color_off) { +void Image::draw(int x, int y, display::Display *display, Color color_on, Color color_off) { switch (type_) { case IMAGE_TYPE_BINARY: { for (int img_x = 0; img_x < width_; img_x++) { diff --git a/esphome/components/image/image.h b/esphome/components/image/image.h index b0853d360d..4e869f5204 100644 --- a/esphome/components/image/image.h +++ b/esphome/components/image/image.h @@ -39,7 +39,7 @@ class Image : public display::BaseImage { int get_height() const override; ImageType get_type() const; - void draw(int x, int y, display::DisplayBuffer *display, Color color_on, Color color_off) override; + void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override; void set_transparency(bool transparent) { transparent_ = transparent; } bool has_transparency() const { return transparent_; } diff --git a/esphome/components/qr_code/qr_code.cpp b/esphome/components/qr_code/qr_code.cpp index a2efbdb804..aecf7628dc 100644 --- a/esphome/components/qr_code/qr_code.cpp +++ b/esphome/components/qr_code/qr_code.cpp @@ -1,5 +1,5 @@ #include "qr_code.h" -#include "esphome/components/display/display_buffer.h" +#include "esphome/components/display/display.h" #include "esphome/core/color.h" #include "esphome/core/log.h" @@ -33,7 +33,7 @@ void QrCode::generate_qr_code() { } } -void QrCode::draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale) { +void QrCode::draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale) { ESP_LOGV(TAG, "Drawing QR code at (%d, %d)", x_offset, y_offset); if (this->needs_update_) { diff --git a/esphome/components/qr_code/qr_code.h b/esphome/components/qr_code/qr_code.h index 58f3a70321..d88e0aa09a 100644 --- a/esphome/components/qr_code/qr_code.h +++ b/esphome/components/qr_code/qr_code.h @@ -9,13 +9,13 @@ namespace esphome { // forward declare DisplayBuffer namespace display { -class DisplayBuffer; +class Display; } // namespace display namespace qr_code { class QrCode : public Component { public: - void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale); + void draw(display::Display *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale); void dump_config() override; diff --git a/esphome/components/touchscreen/touchscreen.cpp b/esphome/components/touchscreen/touchscreen.cpp index 9b337fc02c..2eaa736171 100644 --- a/esphome/components/touchscreen/touchscreen.cpp +++ b/esphome/components/touchscreen/touchscreen.cpp @@ -7,6 +7,17 @@ namespace touchscreen { static const char *const TAG = "touchscreen"; +void Touchscreen::set_display(display::Display *display) { + this->display_ = display; + this->display_width_ = display->get_width(); + this->display_height_ = display->get_height(); + this->rotation_ = static_cast(display->get_rotation()); + + if (this->rotation_ == ROTATE_90_DEGREES || this->rotation_ == ROTATE_270_DEGREES) { + std::swap(this->display_width_, this->display_height_); + } +} + void Touchscreen::send_touch_(TouchPoint tp) { ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); this->touch_trigger_.trigger(tp); diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h index 0597759894..24b3191880 100644 --- a/esphome/components/touchscreen/touchscreen.h +++ b/esphome/components/touchscreen/touchscreen.h @@ -31,13 +31,8 @@ enum TouchRotation { class Touchscreen { public: - void set_display(display::DisplayBuffer *display) { - this->display_ = display; - this->display_width_ = display->get_width_internal(); - this->display_height_ = display->get_height_internal(); - this->rotation_ = static_cast(display->get_rotation()); - } - display::DisplayBuffer *get_display() const { return this->display_; } + void set_display(display::Display *display); + display::Display *get_display() const { return this->display_; } Trigger *get_touch_trigger() { return &this->touch_trigger_; } @@ -49,7 +44,7 @@ class Touchscreen { uint16_t display_width_; uint16_t display_height_; - display::DisplayBuffer *display_; + display::Display *display_; TouchRotation rotation_; Trigger touch_trigger_; std::vector touch_listeners_; diff --git a/script/ci-custom.py b/script/ci-custom.py index 44ed83f392..a731e2e5b8 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -607,7 +607,7 @@ def lint_trailing_whitespace(fname, match): "esphome/components/button/button.h", "esphome/components/climate/climate.h", "esphome/components/cover/cover.h", - "esphome/components/display/display_buffer.h", + "esphome/components/display/display.h", "esphome/components/fan/fan.h", "esphome/components/i2c/i2c.h", "esphome/components/lock/lock.h", From f9fc438de8800428cb19230345861cd2d7c09adc Mon Sep 17 00:00:00 2001 From: NP v/d Spek Date: Thu, 6 Jul 2023 03:58:04 +0200 Subject: [PATCH 077/106] Fixed ili9xxx_display update() method (#5013) There was an obsolete `if` statement left over from an other implementation. --- esphome/components/ili9xxx/ili9xxx_display.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/esphome/components/ili9xxx/ili9xxx_display.cpp b/esphome/components/ili9xxx/ili9xxx_display.cpp index 82217f8e40..750f629db2 100644 --- a/esphome/components/ili9xxx/ili9xxx_display.cpp +++ b/esphome/components/ili9xxx/ili9xxx_display.cpp @@ -152,12 +152,10 @@ void ILI9XXXDisplay::update() { this->need_update_ = true; return; } + this->prossing_update_ = true; do { - this->prossing_update_ = true; this->need_update_ = false; - if (!this->need_update_) { - this->do_update_(); - } + this->do_update_(); } while (this->need_update_); this->prossing_update_ = false; this->display_(); From e6834f25ed7355f6f9c27155e8da761da433e06f Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 9 Jul 2023 11:08:46 -1000 Subject: [PATCH 078/106] Fix bulk and single Bluetooth parser coexistence (#5073) --- .../bluetooth_proxy/bluetooth_connection.cpp | 4 ++ .../bluetooth_proxy/bluetooth_connection.h | 1 + .../bluetooth_proxy/bluetooth_proxy.cpp | 8 ++++ .../bluetooth_proxy/bluetooth_proxy.h | 1 + .../esp32_ble_tracker/esp32_ble_tracker.cpp | 42 +++++++++++++++---- .../esp32_ble_tracker/esp32_ble_tracker.h | 17 +++++--- 6 files changed, 60 insertions(+), 13 deletions(-) diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp index 26304325c1..97a25262cb 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.cpp @@ -275,6 +275,10 @@ esp_err_t BluetoothConnection::notify_characteristic(uint16_t handle, bool enabl return ESP_OK; } +esp32_ble_tracker::AdvertisementParserType BluetoothConnection::get_advertisement_parser_type() { + return this->proxy_->get_advertisement_parser_type(); +} + } // namespace bluetooth_proxy } // namespace esphome diff --git a/esphome/components/bluetooth_proxy/bluetooth_connection.h b/esphome/components/bluetooth_proxy/bluetooth_connection.h index 8b13f4d1c2..e6ab3cbccc 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_connection.h +++ b/esphome/components/bluetooth_proxy/bluetooth_connection.h @@ -14,6 +14,7 @@ class BluetoothConnection : public esp32_ble_client::BLEClientBase { bool 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; + esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; esp_err_t read_characteristic(uint16_t handle); esp_err_t write_characteristic(uint16_t handle, const std::string &data, bool response); diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp index b633fe2430..f188439d0e 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.cpp @@ -198,6 +198,12 @@ void BluetoothProxy::loop() { } } +esp32_ble_tracker::AdvertisementParserType BluetoothProxy::get_advertisement_parser_type() { + if (this->raw_advertisements_) + return esp32_ble_tracker::AdvertisementParserType::RAW_ADVERTISEMENTS; + return esp32_ble_tracker::AdvertisementParserType::PARSED_ADVERTISEMENTS; +} + BluetoothConnection *BluetoothProxy::get_connection_(uint64_t address, bool reserve) { for (auto *connection : this->connections_) { if (connection->get_address() == address) @@ -435,6 +441,7 @@ void BluetoothProxy::subscribe_api_connection(api::APIConnection *api_connection } this->api_connection_ = api_connection; this->raw_advertisements_ = flags & BluetoothProxySubscriptionFlag::SUBSCRIPTION_RAW_ADVERTISEMENTS; + this->parent_->recalculate_advertisement_parser_types(); } void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connection) { @@ -444,6 +451,7 @@ void BluetoothProxy::unsubscribe_api_connection(api::APIConnection *api_connecti } this->api_connection_ = nullptr; this->raw_advertisements_ = false; + this->parent_->recalculate_advertisement_parser_types(); } void BluetoothProxy::send_device_connection(uint64_t address, bool connected, uint16_t mtu, esp_err_t error) { diff --git a/esphome/components/bluetooth_proxy/bluetooth_proxy.h b/esphome/components/bluetooth_proxy/bluetooth_proxy.h index 97b6396b55..35a37f934a 100644 --- a/esphome/components/bluetooth_proxy/bluetooth_proxy.h +++ b/esphome/components/bluetooth_proxy/bluetooth_proxy.h @@ -51,6 +51,7 @@ class BluetoothProxy : public esp32_ble_tracker::ESPBTDeviceListener, public Com bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) override; void dump_config() override; void loop() override; + esp32_ble_tracker::AdvertisementParserType get_advertisement_parser_type() override; void register_connection(BluetoothConnection *connection) { this->connections_.push_back(connection); diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 0f6c4117d2..1569ea0dd5 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -107,16 +107,16 @@ void ESP32BLETracker::loop() { ESP_LOGW(TAG, "Too many BLE events to process. Some devices may not show up."); } - bool bulk_parsed = false; - - for (auto *listener : this->listeners_) { - bulk_parsed |= listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_); - } - for (auto *client : this->clients_) { - bulk_parsed |= client->parse_devices(this->scan_result_buffer_, this->scan_result_index_); + if (this->raw_advertisements_) { + for (auto *listener : this->listeners_) { + listener->parse_devices(this->scan_result_buffer_, this->scan_result_index_); + } + for (auto *client : this->clients_) { + client->parse_devices(this->scan_result_buffer_, this->scan_result_index_); + } } - if (!bulk_parsed) { + if (this->parse_advertisements_) { for (size_t i = 0; i < index; i++) { ESPBTDevice device; device.parse_scan_rst(this->scan_result_buffer_[i]); @@ -284,6 +284,32 @@ void ESP32BLETracker::end_of_scan_() { void ESP32BLETracker::register_client(ESPBTClient *client) { client->app_id = ++this->app_id_; this->clients_.push_back(client); + this->recalculate_advertisement_parser_types(); +} + +void ESP32BLETracker::register_listener(ESPBTDeviceListener *listener) { + listener->set_parent(this); + this->listeners_.push_back(listener); + this->recalculate_advertisement_parser_types(); +} + +void ESP32BLETracker::recalculate_advertisement_parser_types() { + this->raw_advertisements_ = false; + this->parse_advertisements_ = false; + for (auto *listener : this->listeners_) { + if (listener->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) { + this->parse_advertisements_ = true; + } else { + this->raw_advertisements_ = true; + } + } + for (auto *client : this->clients_) { + if (client->get_advertisement_parser_type() == AdvertisementParserType::PARSED_ADVERTISEMENTS) { + this->parse_advertisements_ = true; + } else { + this->raw_advertisements_ = true; + } + } } void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h index 43e88fbf2b..6efdded3ff 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.h @@ -27,6 +27,11 @@ using namespace esp32_ble; using adv_data_t = std::vector; +enum AdvertisementParserType { + PARSED_ADVERTISEMENTS, + RAW_ADVERTISEMENTS, +}; + struct ServiceData { ESPBTUUID uuid; adv_data_t data; @@ -116,6 +121,9 @@ class ESPBTDeviceListener { virtual bool parse_devices(esp_ble_gap_cb_param_t::ble_scan_result_evt_param *advertisements, size_t count) { return false; }; + virtual AdvertisementParserType get_advertisement_parser_type() { + return AdvertisementParserType::PARSED_ADVERTISEMENTS; + }; void set_parent(ESP32BLETracker *parent) { parent_ = parent; } protected: @@ -184,12 +192,9 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv void loop() override; - void register_listener(ESPBTDeviceListener *listener) { - listener->set_parent(this); - this->listeners_.push_back(listener); - } - + void register_listener(ESPBTDeviceListener *listener); void register_client(ESPBTClient *client); + void recalculate_advertisement_parser_types(); void print_bt_device_info(const ESPBTDevice &device); @@ -231,6 +236,8 @@ class ESP32BLETracker : public Component, public GAPEventHandler, public GATTcEv bool scan_continuous_; bool scan_active_; bool scanner_idle_; + bool raw_advertisements_{false}; + bool parse_advertisements_{false}; SemaphoreHandle_t scan_result_lock_; SemaphoreHandle_t scan_end_lock_; size_t scan_result_index_{0}; From 8739552c0b24005ae990067cd854ecbf63fcb71c Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Sun, 9 Jul 2023 17:55:02 -0400 Subject: [PATCH 079/106] binary_sensor: Validate max_length for on_click/on_double_click (#5068) --- esphome/components/binary_sensor/__init__.py | 58 +++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 41b4c5a0d7..95e35a45f2 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -359,6 +359,18 @@ def validate_multi_click_timing(value): validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") +def validate_click_timing(value): + for v in value: + min_length = v.get(CONF_MIN_LENGTH) + max_length = v.get(CONF_MAX_LENGTH) + if max_length < min_length: + raise cv.Invalid( + f"Max length ({max_length}) must be larger than min length ({min_length})." + ) + + return value + + BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.GenerateID(): cv.declare_id(BinarySensor), @@ -378,27 +390,33 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger), } ), - cv.Optional(CONF_ON_CLICK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), - cv.Optional( - CONF_MIN_LENGTH, default="50ms" - ): cv.positive_time_period_milliseconds, - cv.Optional( - CONF_MAX_LENGTH, default="350ms" - ): cv.positive_time_period_milliseconds, - } + cv.Optional(CONF_ON_CLICK): cv.All( + automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClickTrigger), + cv.Optional( + CONF_MIN_LENGTH, default="50ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_MAX_LENGTH, default="350ms" + ): cv.positive_time_period_milliseconds, + } + ), + validate_click_timing, ), - cv.Optional(CONF_ON_DOUBLE_CLICK): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), - cv.Optional( - CONF_MIN_LENGTH, default="50ms" - ): cv.positive_time_period_milliseconds, - cv.Optional( - CONF_MAX_LENGTH, default="350ms" - ): cv.positive_time_period_milliseconds, - } + cv.Optional(CONF_ON_DOUBLE_CLICK): cv.All( + automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DoubleClickTrigger), + cv.Optional( + CONF_MIN_LENGTH, default="50ms" + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_MAX_LENGTH, default="350ms" + ): cv.positive_time_period_milliseconds, + } + ), + validate_click_timing, ), cv.Optional(CONF_ON_MULTI_CLICK): automation.validate_automation( { From 8bf8892ab34cc3f5688ab006312652c0161c847a Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 10 Jul 2023 00:02:42 +0200 Subject: [PATCH 080/106] [Ethernet] ksz8081rna support (#4739) Co-authored-by: Your Name --- esphome/components/ethernet/__init__.py | 1 + .../ethernet/ethernet_component.cpp | 44 ++++++++++++++++++- .../components/ethernet/ethernet_component.h | 3 ++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index bedc0a4c30..6f0f3741dd 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -35,6 +35,7 @@ ETHERNET_TYPES = { "IP101": EthernetType.ETHERNET_TYPE_IP101, "JL1101": EthernetType.ETHERNET_TYPE_JL1101, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, + "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, } emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index fc1068f2a8..3b5804abdd 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -84,7 +84,8 @@ void EthernetComponent::setup() { this->phy_ = esp_eth_phy_new_jl1101(&phy_config); break; } - case ETHERNET_TYPE_KSZ8081: { + case ETHERNET_TYPE_KSZ8081: + case ETHERNET_TYPE_KSZ8081RNA: { #if ESP_IDF_VERSION_MAJOR >= 5 this->phy_ = esp_eth_phy_new_ksz80xx(&phy_config); #else @@ -102,6 +103,12 @@ void EthernetComponent::setup() { this->eth_handle_ = nullptr; err = esp_eth_driver_install(ð_config, &this->eth_handle_); ESPHL_ERROR_CHECK(err, "ETH driver install error"); + + if (this->type_ == ETHERNET_TYPE_KSZ8081RNA && this->clk_mode_ == EMAC_CLK_OUT) { + // KSZ8081RNA default is incorrect. It expects a 25MHz clock instead of the 50MHz we provide. + this->ksz8081_set_clock_reference_(mac); + } + /* attach Ethernet driver to TCP/IP stack */ err = esp_netif_attach(this->eth_netif_, esp_eth_new_netif_glue(this->eth_handle_)); ESPHL_ERROR_CHECK(err, "ETH netif attach error"); @@ -184,6 +191,10 @@ void EthernetComponent::dump_config() { eth_type = "KSZ8081"; break; + case ETHERNET_TYPE_KSZ8081RNA: + eth_type = "KSZ8081RNA"; + break; + default: eth_type = "Unknown"; break; @@ -385,6 +396,37 @@ bool EthernetComponent::powerdown() { return true; } +void EthernetComponent::ksz8081_set_clock_reference_(esp_eth_mac_t *mac) { +#define KSZ80XX_PC2R_REG_ADDR (0x1F) + + esp_err_t err; + + uint32_t phy_control_2; + err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); + ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str()); + + /* + * Bit 7 is `RMII Reference Clock Select`. Default is `0`. + * KSZ8081RNA: + * 0 - clock input to XI (Pin 8) is 25 MHz for RMII – 25 MHz clock mode. + * 1 - clock input to XI (Pin 8) is 50 MHz for RMII – 50 MHz clock mode. + * KSZ8081RND: + * 0 - clock input to XI (Pin 8) is 50 MHz for RMII – 50 MHz clock mode. + * 1 - clock input to XI (Pin 8) is 25 MHz (driven clock only, not a crystal) for RMII – 25 MHz clock mode. + */ + if ((phy_control_2 & (1 << 7)) != (1 << 7)) { + phy_control_2 |= 1 << 7; + err = mac->write_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, phy_control_2); + ESPHL_ERROR_CHECK(err, "Write PHY Control 2 failed"); + err = mac->read_phy_reg(mac, this->phy_addr_, KSZ80XX_PC2R_REG_ADDR, &(phy_control_2)); + ESPHL_ERROR_CHECK(err, "Read PHY Control 2 failed"); + ESP_LOGVV(TAG, "KSZ8081 PHY Control 2: %s", format_hex_pretty((u_int8_t *) &phy_control_2, 2).c_str()); + } + +#undef KSZ80XX_PC2R_REG_ADDR +} + } // namespace ethernet } // namespace esphome diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 918e47212f..f6b67f3f82 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -21,6 +21,7 @@ enum EthernetType { ETHERNET_TYPE_IP101, ETHERNET_TYPE_JL1101, ETHERNET_TYPE_KSZ8081, + ETHERNET_TYPE_KSZ8081RNA, }; struct ManualIP { @@ -67,6 +68,8 @@ class EthernetComponent : public Component { void start_connect_(); void dump_connect_params_(); + /// @brief Set `RMII Reference Clock Select` bit for KSZ8081. + void ksz8081_set_clock_reference_(esp_eth_mac_t *mac); std::string use_address_; uint8_t phy_addr_{0}; From 8ca9115dc8efd1274e441aa65d7249f12ba4d2df Mon Sep 17 00:00:00 2001 From: Trevor North Date: Sun, 9 Jul 2023 23:03:54 +0100 Subject: [PATCH 081/106] Improve BME680 BSEC sensor device classes (#4859) --- esphome/components/bme680_bsec/sensor.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/esphome/components/bme680_bsec/sensor.py b/esphome/components/bme680_bsec/sensor.py index 8d00012150..3bd082481e 100644 --- a/esphome/components/bme680_bsec/sensor.py +++ b/esphome/components/bme680_bsec/sensor.py @@ -6,8 +6,10 @@ from esphome.const import ( CONF_HUMIDITY, CONF_PRESSURE, CONF_TEMPERATURE, + DEVICE_CLASS_CARBON_DIOXIDE, DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, + DEVICE_CLASS_ATMOSPHERIC_PRESSURE, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -17,8 +19,6 @@ from esphome.const import ( UNIT_PERCENT, ICON_GAS_CYLINDER, ICON_GAUGE, - ICON_THERMOMETER, - ICON_WATER_PERCENT, ) from . import ( BME680BSECComponent, @@ -35,7 +35,6 @@ CONF_CO2_EQUIVALENT = "co2_equivalent" CONF_BREATH_VOC_EQUIVALENT = "breath_voc_equivalent" UNIT_IAQ = "IAQ" ICON_ACCURACY = "mdi:checkbox-marked-circle-outline" -ICON_TEST_TUBE = "mdi:test-tube" TYPES = [ CONF_TEMPERATURE, @@ -53,7 +52,6 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, - icon=ICON_THERMOMETER, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, @@ -62,16 +60,14 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Optional(CONF_PRESSURE): sensor.sensor_schema( unit_of_measurement=UNIT_HECTOPASCAL, - icon=ICON_GAUGE, accuracy_decimals=1, - device_class=DEVICE_CLASS_PRESSURE, + device_class=DEVICE_CLASS_ATMOSPHERIC_PRESSURE, state_class=STATE_CLASS_MEASUREMENT, ).extend( {cv.Optional(CONF_SAMPLE_RATE): cv.enum(SAMPLE_RATE_OPTIONS, upper=True)} ), cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, - icon=ICON_WATER_PERCENT, accuracy_decimals=1, device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, @@ -97,14 +93,14 @@ CONFIG_SCHEMA = cv.Schema( ), cv.Optional(CONF_CO2_EQUIVALENT): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_MILLION, - icon=ICON_TEST_TUBE, accuracy_decimals=1, + device_class=DEVICE_CLASS_CARBON_DIOXIDE, state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BREATH_VOC_EQUIVALENT): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_MILLION, - icon=ICON_TEST_TUBE, accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, state_class=STATE_CLASS_MEASUREMENT, ), } From c5aacdd68250a0c55136ec49c114aed14b2cad5b Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 10 Jul 2023 01:30:39 +0200 Subject: [PATCH 082/106] Update RP2040 Aruino framwork and platform to latest (#5025) --- esphome/components/rp2040/__init__.py | 8 ++++---- platformio.ini | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/esphome/components/rp2040/__init__.py b/esphome/components/rp2040/__init__.py index 030d586626..dafafc531c 100644 --- a/esphome/components/rp2040/__init__.py +++ b/esphome/components/rp2040/__init__.py @@ -62,19 +62,19 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/earlephilhower/arduino-pico/releases # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 6, 4) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 3, 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) +ARDUINO_PLATFORM_VERSION = cv.Version(1, 9, 0) def _arduino_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(2, 6, 4), "https://github.com/earlephilhower/arduino-pico"), - "latest": (cv.Version(2, 6, 4), None), + "dev": (cv.Version(3, 3, 0), "https://github.com/earlephilhower/arduino-pico"), + "latest": (cv.Version(3, 3, 0), None), "recommended": (RECOMMENDED_ARDUINO_FRAMEWORK_VERSION, None), } diff --git a/platformio.ini b/platformio.ini index 868880e1d7..b970ef7a69 100644 --- a/platformio.ini +++ b/platformio.ini @@ -157,7 +157,7 @@ board_build.filesystem_size = 0.5m platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform_packages = ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted - earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/2.6.2/rp2040-2.6.2.zip + earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.3.0/rp2040-3.3.0.zip framework = arduino lib_deps = From ddde1ee31ef96476c7a7d1c8b8fe306aa4d2e503 Mon Sep 17 00:00:00 2001 From: Jimmy Hedman Date: Mon, 10 Jul 2023 01:34:43 +0200 Subject: [PATCH 083/106] Allow pillow versions over 10 (#5071) --- esphome/components/font/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 7a314bb032..29150afe3f 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -3,6 +3,7 @@ from pathlib import Path import hashlib import os import re +from packaging import version import requests @@ -69,7 +70,7 @@ def validate_pillow_installed(value): "(pip install pillow)" ) from err - if PIL.__version__[0] < "4": + if version.parse(PIL.__version__) < version.parse("4.0.0"): raise cv.Invalid( "Please update your pillow installation to at least 4.0.x. " "(pip install -U pillow)" From 98fd092053bef74a6349cc92f70d5602217efeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Tue, 11 Jul 2023 06:38:28 +0900 Subject: [PATCH 084/106] display: rename `DisplayBufferRef` to `DisplayRef` (#5002) --- esphome/components/addressable_light/display.py | 2 +- esphome/components/display/__init__.py | 5 +++-- esphome/components/display/display.h | 9 --------- esphome/components/ili9xxx/display.py | 2 +- esphome/components/inkplate6/display.py | 2 +- esphome/components/pcd8544/display.py | 2 +- esphome/components/ssd1306_base/__init__.py | 2 +- esphome/components/ssd1322_base/__init__.py | 2 +- esphome/components/ssd1325_base/__init__.py | 2 +- esphome/components/ssd1327_base/__init__.py | 2 +- esphome/components/ssd1331_base/__init__.py | 2 +- esphome/components/ssd1351_base/__init__.py | 2 +- esphome/components/st7735/display.py | 2 +- esphome/components/st7789v/display.py | 2 +- esphome/components/waveshare_epaper/display.py | 2 +- 15 files changed, 16 insertions(+), 24 deletions(-) diff --git a/esphome/components/addressable_light/display.py b/esphome/components/addressable_light/display.py index 0684bf8dfc..5fdd84ac2d 100644 --- a/esphome/components/addressable_light/display.py +++ b/esphome/components/addressable_light/display.py @@ -58,6 +58,6 @@ async def to_code(config): if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py index 0d403f99f0..b7a8508fc8 100644 --- a/esphome/components/display/__init__.py +++ b/esphome/components/display/__init__.py @@ -18,10 +18,11 @@ from esphome.core import coroutine_with_priority IS_PLATFORM_COMPONENT = True display_ns = cg.esphome_ns.namespace("display") +Display = display_ns.class_("Display") DisplayBuffer = display_ns.class_("DisplayBuffer") DisplayPage = display_ns.class_("DisplayPage") DisplayPagePtr = DisplayPage.operator("ptr") -DisplayBufferRef = DisplayBuffer.operator("ref") +DisplayRef = Display.operator("ref") DisplayPageShowAction = display_ns.class_("DisplayPageShowAction", automation.Action) DisplayPageShowNextAction = display_ns.class_( "DisplayPageShowNextAction", automation.Action @@ -96,7 +97,7 @@ async def setup_display_core_(var, config): pages = [] for conf in config[CONF_PAGES]: lambda_ = await cg.process_lambda( - conf[CONF_LAMBDA], [(DisplayBufferRef, "it")], return_type=cg.void + conf[CONF_LAMBDA], [(DisplayRef, "it")], return_type=cg.void ) page = cg.new_Pvariable(conf[CONF_ID], lambda_) pages.append(page) diff --git a/esphome/components/display/display.h b/esphome/components/display/display.h index 2f6fb563cc..08d8c70e0d 100644 --- a/esphome/components/display/display.h +++ b/esphome/components/display/display.h @@ -133,12 +133,10 @@ enum DisplayRotation { }; class Display; -class DisplayBuffer; class DisplayPage; class DisplayOnPageChangeTrigger; using display_writer_t = std::function; -using display_buffer_writer_t = std::function; #define LOG_DISPLAY(prefix, type, obj) \ if ((obj) != nullptr) { \ @@ -411,10 +409,6 @@ class Display { /// Internal method to set the display writer lambda. void set_writer(display_writer_t &&writer); - void set_writer(const display_buffer_writer_t &writer) { - // Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef` - this->set_writer([writer](Display &display) { return writer((display::DisplayBuffer &) display); }); - } void show_page(DisplayPage *page); void show_next_page(); @@ -499,9 +493,6 @@ class Display { class DisplayPage { public: DisplayPage(display_writer_t writer); - // Temporary mapping to be removed once all lambdas are changed to use `display.DisplayRef` - DisplayPage(const display_buffer_writer_t &writer) - : DisplayPage([writer](Display &display) { return writer((display::DisplayBuffer &) display); }) {} void show(); void show_next(); void show_prev(); diff --git a/esphome/components/ili9xxx/display.py b/esphome/components/ili9xxx/display.py index 6021da5eeb..0435460b6a 100644 --- a/esphome/components/ili9xxx/display.py +++ b/esphome/components/ili9xxx/display.py @@ -121,7 +121,7 @@ async def to_code(config): if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index a17f37c920..7534731175 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -115,7 +115,7 @@ async def to_code(config): if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/pcd8544/display.py b/esphome/components/pcd8544/display.py index d0184a58d3..b4c8f432cf 100644 --- a/esphome/components/pcd8544/display.py +++ b/esphome/components/pcd8544/display.py @@ -52,6 +52,6 @@ async def to_code(config): if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py index 48143b9e1a..f4abd845c8 100644 --- a/esphome/components/ssd1306_base/__init__.py +++ b/esphome/components/ssd1306_base/__init__.py @@ -96,6 +96,6 @@ async def setup_ssd1306(var, config): cg.add(var.init_invert(config[CONF_INVERT])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1322_base/__init__.py b/esphome/components/ssd1322_base/__init__.py index 434caf4e35..97fb0d2a74 100644 --- a/esphome/components/ssd1322_base/__init__.py +++ b/esphome/components/ssd1322_base/__init__.py @@ -46,6 +46,6 @@ async def setup_ssd1322(var, config): cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1325_base/__init__.py b/esphome/components/ssd1325_base/__init__.py index 68be287d2a..1a6f7fb519 100644 --- a/esphome/components/ssd1325_base/__init__.py +++ b/esphome/components/ssd1325_base/__init__.py @@ -50,6 +50,6 @@ async def setup_ssd1325(var, config): cg.add(var.set_external_vcc(config[CONF_EXTERNAL_VCC])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1327_base/__init__.py b/esphome/components/ssd1327_base/__init__.py index eada66a6e3..af2eb3489d 100644 --- a/esphome/components/ssd1327_base/__init__.py +++ b/esphome/components/ssd1327_base/__init__.py @@ -37,6 +37,6 @@ async def setup_ssd1327(var, config): cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1331_base/__init__.py b/esphome/components/ssd1331_base/__init__.py index 067f55a252..169c0eed1a 100644 --- a/esphome/components/ssd1331_base/__init__.py +++ b/esphome/components/ssd1331_base/__init__.py @@ -28,6 +28,6 @@ async def setup_ssd1331(var, config): cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/ssd1351_base/__init__.py b/esphome/components/ssd1351_base/__init__.py index 555d6c5e2e..2988dd4bf3 100644 --- a/esphome/components/ssd1351_base/__init__.py +++ b/esphome/components/ssd1351_base/__init__.py @@ -38,6 +38,6 @@ async def setup_ssd1351(var, config): cg.add(var.init_brightness(config[CONF_BRIGHTNESS])) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/st7735/display.py b/esphome/components/st7735/display.py index ae31f604a5..652d31662d 100644 --- a/esphome/components/st7735/display.py +++ b/esphome/components/st7735/display.py @@ -77,7 +77,7 @@ async def setup_st7735(var, config): cg.add(var.set_reset_pin(reset)) if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/st7789v/display.py b/esphome/components/st7789v/display.py index a81101f2d1..16c1e790bd 100644 --- a/esphome/components/st7789v/display.py +++ b/esphome/components/st7789v/display.py @@ -121,7 +121,7 @@ async def to_code(config): if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 11deab5310..6113fe943c 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -153,7 +153,7 @@ async def to_code(config): if CONF_LAMBDA in config: lambda_ = await cg.process_lambda( - config[CONF_LAMBDA], [(display.DisplayBufferRef, "it")], return_type=cg.void + config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void ) cg.add(var.set_writer(lambda_)) if CONF_RESET_PIN in config: From a39181592155a15ae908fff657f7e26e0cdba935 Mon Sep 17 00:00:00 2001 From: kahrendt Date: Tue, 11 Jul 2023 00:24:18 -0400 Subject: [PATCH 085/106] Add Zio Ultrasonic Distance Sensor Component (#5059) --- CODEOWNERS | 1 + esphome/components/zio_ultrasonic/__init__.py | 0 esphome/components/zio_ultrasonic/sensor.py | 34 +++++++++++++++++++ .../zio_ultrasonic/zio_ultrasonic.cpp | 31 +++++++++++++++++ .../zio_ultrasonic/zio_ultrasonic.h | 22 ++++++++++++ tests/test1.yaml | 4 +++ 6 files changed, 92 insertions(+) create mode 100644 esphome/components/zio_ultrasonic/__init__.py create mode 100644 esphome/components/zio_ultrasonic/sensor.py create mode 100644 esphome/components/zio_ultrasonic/zio_ultrasonic.cpp create mode 100644 esphome/components/zio_ultrasonic/zio_ultrasonic.h diff --git a/CODEOWNERS b/CODEOWNERS index 94813f73dd..ca2c259624 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -321,3 +321,4 @@ esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_rtcgq02lm/* @jesserockz esphome/components/xl9535/* @mreditor97 esphome/components/xpt2046/* @nielsnl68 @numo68 +esphome/components/zio_ultrasonic/* @kahrendt diff --git a/esphome/components/zio_ultrasonic/__init__.py b/esphome/components/zio_ultrasonic/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/zio_ultrasonic/sensor.py b/esphome/components/zio_ultrasonic/sensor.py new file mode 100644 index 0000000000..c5eed14e64 --- /dev/null +++ b/esphome/components/zio_ultrasonic/sensor.py @@ -0,0 +1,34 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + DEVICE_CLASS_DISTANCE, + STATE_CLASS_MEASUREMENT, +) + +DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@kahrendt"] + +zio_ultrasonic_ns = cg.esphome_ns.namespace("zio_ultrasonic") + +ZioUltrasonicComponent = zio_ultrasonic_ns.class_( + "ZioUltrasonicComponent", cg.PollingComponent, i2c.I2CDevice, sensor.Sensor +) + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + ZioUltrasonicComponent, + unit_of_measurement="mm", + accuracy_decimals=0, + device_class=DEVICE_CLASS_DISTANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x00)) +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/zio_ultrasonic/zio_ultrasonic.cpp b/esphome/components/zio_ultrasonic/zio_ultrasonic.cpp new file mode 100644 index 0000000000..565bbe9b4f --- /dev/null +++ b/esphome/components/zio_ultrasonic/zio_ultrasonic.cpp @@ -0,0 +1,31 @@ + +#include "zio_ultrasonic.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace zio_ultrasonic { + +static const char *const TAG = "zio_ultrasonic"; + +void ZioUltrasonicComponent::dump_config() { + ESP_LOGCONFIG(TAG, "Zio Ultrasonic Sensor:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Sensor:", this); +} + +void ZioUltrasonicComponent::update() { + uint16_t distance; + + // Read an unsigned two byte integerfrom register 0x01 which gives distance in mm + if (!this->read_byte_16(0x01, &distance)) { + ESP_LOGE(TAG, "Error reading data from Zio Ultrasonic"); + this->publish_state(NAN); + } else { + this->publish_state(distance); + } +} + +} // namespace zio_ultrasonic +} // namespace esphome diff --git a/esphome/components/zio_ultrasonic/zio_ultrasonic.h b/esphome/components/zio_ultrasonic/zio_ultrasonic.h new file mode 100644 index 0000000000..84c8d44c65 --- /dev/null +++ b/esphome/components/zio_ultrasonic/zio_ultrasonic.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +static const char *const TAG = "Zio Ultrasonic"; + +namespace esphome { +namespace zio_ultrasonic { + +class ZioUltrasonicComponent : public i2c::I2CDevice, public PollingComponent, public sensor::Sensor { + public: + float get_setup_priority() const override { return setup_priority::DATA; } + + void dump_config() override; + + void update() override; +}; + +} // namespace zio_ultrasonic +} // namespace esphome diff --git a/tests/test1.yaml b/tests/test1.yaml index 05ba07d1d8..21379e807c 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -1310,6 +1310,10 @@ sensor: fault_count: 1 polarity: active_high function: comparator + - platform: zio_ultrasonic + name: "Distance" + update_interval: 60s + i2c_id: i2c_bus esp32_touch: setup_mode: false From f3cdcc008a01133dcf576ffe6bb712bca49fd05b Mon Sep 17 00:00:00 2001 From: jan-hofmeier <37298146+jan-hofmeier@users.noreply.github.com> Date: Tue, 11 Jul 2023 07:12:43 +0200 Subject: [PATCH 086/106] Add Alpha3 pump component (#3787) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/alpha3/__init__.py | 1 + esphome/components/alpha3/alpha3.cpp | 189 ++++++++++++++++++++++++++ esphome/components/alpha3/alpha3.h | 73 ++++++++++ esphome/components/alpha3/sensor.py | 85 ++++++++++++ esphome/const.py | 4 + tests/test1.yaml | 11 ++ 7 files changed, 364 insertions(+) create mode 100644 esphome/components/alpha3/__init__.py create mode 100644 esphome/components/alpha3/alpha3.cpp create mode 100644 esphome/components/alpha3/alpha3.h create mode 100644 esphome/components/alpha3/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index ca2c259624..68a8eb6d18 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -21,6 +21,7 @@ esphome/components/airthings_wave_base/* @jeromelaban @ncareau esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/alarm_control_panel/* @grahambrown11 +esphome/components/alpha3/* @jan-hofmeier esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix esphome/components/am43/sensor/* @buxtronix diff --git a/esphome/components/alpha3/__init__.py b/esphome/components/alpha3/__init__.py new file mode 100644 index 0000000000..7cd320c80f --- /dev/null +++ b/esphome/components/alpha3/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@jan-hofmeier"] diff --git a/esphome/components/alpha3/alpha3.cpp b/esphome/components/alpha3/alpha3.cpp new file mode 100644 index 0000000000..17899c31cb --- /dev/null +++ b/esphome/components/alpha3/alpha3.cpp @@ -0,0 +1,189 @@ +#include "alpha3.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" +#include //gives ntohl + +#ifdef USE_ESP32 + +namespace esphome { +namespace alpha3 { + +static const char *const TAG = "alpha3"; + +void Alpha3::dump_config() { + ESP_LOGCONFIG(TAG, "ALPHA3"); + LOG_SENSOR(" ", "Flow", this->flow_sensor_); + LOG_SENSOR(" ", "Head", this->head_sensor_); + LOG_SENSOR(" ", "Power", this->power_sensor_); + LOG_SENSOR(" ", "Current", this->current_sensor_); + LOG_SENSOR(" ", "Speed", this->speed_sensor_); + LOG_SENSOR(" ", "Voltage", this->voltage_sensor_); +} + +void Alpha3::setup() {} + +void Alpha3::extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset, + int16_t value_offset, sensor::Sensor *sensor, float factor) { + if (sensor == nullptr) + return; + // we need to handle cases where a value is split over two packets + const int16_t value_length = 4; // 32bit float + // offset inside current response packet + auto rel_offset = value_offset - response_offset; + if (rel_offset <= -value_length) + return; // aready passed the value completly + if (rel_offset >= length) + return; // value not in this packet + + auto start_offset = std::max(0, rel_offset); + auto end_offset = std::min((int16_t) (rel_offset + value_length), length); + auto copy_length = end_offset - start_offset; + auto buffer_offset = std::max(-rel_offset, 0); + std::memcpy(this->buffer_ + buffer_offset, response + start_offset, copy_length); + + if (rel_offset + value_length <= length) { + // we have the whole value + void *buffer = this->buffer_; // to prevent warnings when casting the pointer + *((int32_t *) buffer) = ntohl(*((int32_t *) buffer)); // values are big endian + float fvalue = *((float *) buffer); + sensor->publish_state(fvalue * factor); + } +} + +bool Alpha3::is_current_response_type_(const uint8_t *response_type) { + return !std::memcmp(this->response_type_, response_type, GENI_RESPONSE_TYPE_LENGTH); +} + +void Alpha3::handle_geni_response_(const uint8_t *response, uint16_t length) { + if (this->response_offset_ >= this->response_length_) { + ESP_LOGD(TAG, "[%s] GENI response begin", this->parent_->address_str().c_str()); + if (length < GENI_RESPONSE_HEADER_LENGTH) { + ESP_LOGW(TAG, "[%s] response to short", this->parent_->address_str().c_str()); + return; + } + if (response[0] != 36 || response[2] != 248 || response[3] != 231 || response[4] != 10) { + ESP_LOGW(TAG, "[%s] response bytes %d %d %d %d %d don't match GENI HEADER", this->parent_->address_str().c_str(), + response[0], response[1], response[2], response[3], response[4]); + return; + } + this->response_length_ = response[1] - GENI_RESPONSE_HEADER_LENGTH + 2; // maybe 2 byte checksum + this->response_offset_ = -GENI_RESPONSE_HEADER_LENGTH; + std::memcpy(this->response_type_, response + 5, GENI_RESPONSE_TYPE_LENGTH); + } + + auto extract_publish_sensor_value = [response, length, this](int16_t value_offset, sensor::Sensor *sensor, + float factor) { + this->extract_publish_sensor_value_(response, length, this->response_offset_, value_offset, sensor, factor); + }; + + if (this->is_current_response_type_(GENI_RESPONSE_TYPE_FLOW_HEAD)) { + ESP_LOGD(TAG, "[%s] FLOW HEAD Response", this->parent_->address_str().c_str()); + extract_publish_sensor_value(GENI_RESPONSE_FLOW_OFFSET, this->flow_sensor_, 3600.0F); + extract_publish_sensor_value(GENI_RESPONSE_HEAD_OFFSET, this->head_sensor_, .0001F); + } else if (this->is_current_response_type_(GENI_RESPONSE_TYPE_POWER)) { + ESP_LOGD(TAG, "[%s] POWER Response", this->parent_->address_str().c_str()); + extract_publish_sensor_value(GENI_RESPONSE_POWER_OFFSET, this->power_sensor_, 1.0F); + extract_publish_sensor_value(GENI_RESPONSE_CURRENT_OFFSET, this->current_sensor_, 1.0F); + extract_publish_sensor_value(GENI_RESPONSE_MOTOR_SPEED_OFFSET, this->speed_sensor_, 1.0F); + extract_publish_sensor_value(GENI_RESPONSE_VOLTAGE_AC_OFFSET, this->voltage_sensor_, 1.0F); + } else { + ESP_LOGW(TAG, "unkown GENI response Type %d %d %d %d %d %d %d %d", this->response_type_[0], this->response_type_[1], + this->response_type_[2], this->response_type_[3], this->response_type_[4], this->response_type_[5], + this->response_type_[6], this->response_type_[7]); + } + this->response_offset_ += length; +} + +void Alpha3::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: { + this->response_offset_ = 0; + this->response_length_ = 0; + ESP_LOGI(TAG, "[%s] connection open", this->parent_->address_str().c_str()); + break; + } + case ESP_GATTC_CONNECT_EVT: { + if (std::memcmp(param->connect.remote_bda, this->parent_->get_remote_bda(), 6) != 0) + return; + auto ret = esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT); + if (ret) { + ESP_LOGW(TAG, "esp_ble_set_encryption failed, status=%x", ret); + } + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + this->node_state = espbt::ClientState::IDLE; + if (this->flow_sensor_ != nullptr) + this->flow_sensor_->publish_state(NAN); + if (this->head_sensor_ != nullptr) + this->head_sensor_->publish_state(NAN); + if (this->power_sensor_ != nullptr) + this->power_sensor_->publish_state(NAN); + if (this->current_sensor_ != nullptr) + this->current_sensor_->publish_state(NAN); + if (this->speed_sensor_ != nullptr) + this->speed_sensor_->publish_state(NAN); + if (this->speed_sensor_ != nullptr) + this->voltage_sensor_->publish_state(NAN); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + auto *chr = this->parent_->get_characteristic(ALPHA3_GENI_SERVICE_UUID, ALPHA3_GENI_CHARACTERISTIC_UUID); + if (chr == nullptr) { + ESP_LOGE(TAG, "[%s] No GENI service found at device, not an Alpha3..?", this->parent_->address_str().c_str()); + break; + } + 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); + } + this->geni_handle_ = chr->handle; + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::ESTABLISHED; + this->update(); + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.handle == this->geni_handle_) { + this->handle_geni_response_(param->notify.value, param->notify.value_len); + } + break; + } + default: + break; + } +} + +void Alpha3::send_request_(uint8_t *request, size_t len) { + auto status = + esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->geni_handle_, len, + request, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + if (status) + ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); +} + +void Alpha3::update() { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->parent_->address_str().c_str()); + return; + } + + if (this->flow_sensor_ != nullptr || this->head_sensor_ != nullptr) { + uint8_t geni_request_flow_head[] = {39, 7, 231, 248, 10, 3, 93, 1, 33, 82, 31}; + this->send_request_(geni_request_flow_head, sizeof(geni_request_flow_head)); + delay(25); // need to wait between requests + } + if (this->power_sensor_ != nullptr || this->current_sensor_ != nullptr || this->speed_sensor_ != nullptr || + this->voltage_sensor_ != nullptr) { + uint8_t geni_request_power[] = {39, 7, 231, 248, 10, 3, 87, 0, 69, 138, 205}; + this->send_request_(geni_request_power, sizeof(geni_request_power)); + delay(25); // need to wait between requests + } +} +} // namespace alpha3 +} // namespace esphome + +#endif diff --git a/esphome/components/alpha3/alpha3.h b/esphome/components/alpha3/alpha3.h new file mode 100644 index 0000000000..325c70a538 --- /dev/null +++ b/esphome/components/alpha3/alpha3.h @@ -0,0 +1,73 @@ +#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 alpha3 { + +namespace espbt = esphome::esp32_ble_tracker; + +static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d); +static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = + espbt::ESPBTUUID::from_raw({static_cast(0xa9), 0x7b, static_cast(0xb8), static_cast(0x85), 0x0, + 0x1a, 0x28, static_cast(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast(0xd1), + static_cast(0xff), static_cast(0x9c), static_cast(0x85)}); +static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13; +static const size_t GENI_RESPONSE_TYPE_LENGTH = 8; + +static const uint8_t GENI_RESPONSE_TYPE_FLOW_HEAD[GENI_RESPONSE_TYPE_LENGTH] = {31, 0, 1, 48, 1, 0, 0, 24}; +static const int16_t GENI_RESPONSE_FLOW_OFFSET = 0; +static const int16_t GENI_RESPONSE_HEAD_OFFSET = 4; + +static const uint8_t GENI_RESPONSE_TYPE_POWER[GENI_RESPONSE_TYPE_LENGTH] = {44, 0, 1, 0, 1, 0, 0, 37}; +static const int16_t GENI_RESPONSE_VOLTAGE_AC_OFFSET = 0; +static const int16_t GENI_RESPONSE_VOLTAGE_DC_OFFSET = 4; +static const int16_t GENI_RESPONSE_CURRENT_OFFSET = 8; +static const int16_t GENI_RESPONSE_POWER_OFFSET = 12; +static const int16_t GENI_RESPONSE_MOTOR_POWER_OFFSET = 16; // not sure +static const int16_t GENI_RESPONSE_MOTOR_SPEED_OFFSET = 20; + +class Alpha3 : public esphome::ble_client::BLEClientNode, public PollingComponent { + public: + void setup() override; + void update() 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 dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_flow_sensor(sensor::Sensor *sensor) { this->flow_sensor_ = sensor; } + void set_head_sensor(sensor::Sensor *sensor) { this->head_sensor_ = sensor; } + void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; } + void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; } + void set_speed_sensor(sensor::Sensor *sensor) { this->speed_sensor_ = sensor; } + void set_voltage_sensor(sensor::Sensor *sensor) { this->voltage_sensor_ = sensor; } + + protected: + sensor::Sensor *flow_sensor_{nullptr}; + sensor::Sensor *head_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *speed_sensor_{nullptr}; + sensor::Sensor *voltage_sensor_{nullptr}; + uint16_t geni_handle_; + int16_t response_length_; + int16_t response_offset_; + uint8_t response_type_[GENI_RESPONSE_TYPE_LENGTH]; + uint8_t buffer_[4]; + void extract_publish_sensor_value_(const uint8_t *response, int16_t length, int16_t response_offset, + int16_t value_offset, sensor::Sensor *sensor, float factor); + void handle_geni_response_(const uint8_t *response, uint16_t length); + void send_request_(uint8_t *request, size_t len); + bool is_current_response_type_(const uint8_t *response_type); +}; +} // namespace alpha3 +} // namespace esphome + +#endif diff --git a/esphome/components/alpha3/sensor.py b/esphome/components/alpha3/sensor.py new file mode 100644 index 0000000000..ba4ca16a5a --- /dev/null +++ b/esphome/components/alpha3/sensor.py @@ -0,0 +1,85 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client +from esphome.const import ( + CONF_ID, + CONF_CURRENT, + CONF_FLOW, + CONF_HEAD, + CONF_POWER, + CONF_SPEED, + CONF_VOLTAGE, + UNIT_AMPERE, + UNIT_VOLT, + UNIT_WATT, + UNIT_METER, + UNIT_CUBIC_METER_PER_HOUR, + UNIT_REVOLUTIONS_PER_MINUTE, +) + +alpha3_ns = cg.esphome_ns.namespace("alpha3") +Alpha3 = alpha3_ns.class_("Alpha3", ble_client.BLEClientNode, cg.PollingComponent) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Alpha3), + cv.Optional(CONF_FLOW): sensor.sensor_schema( + unit_of_measurement=UNIT_CUBIC_METER_PER_HOUR, + accuracy_decimals=2, + ), + cv.Optional(CONF_HEAD): sensor.sensor_schema( + unit_of_measurement=UNIT_METER, + accuracy_decimals=2, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + ), + cv.Optional(CONF_SPEED): sensor.sensor_schema( + unit_of_measurement=UNIT_REVOLUTIONS_PER_MINUTE, + accuracy_decimals=2, + ), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + ), + } + ) + .extend(ble_client.BLE_CLIENT_SCHEMA) + .extend(cv.polling_component_schema("15s")) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) + + if CONF_FLOW in config: + sens = await sensor.new_sensor(config[CONF_FLOW]) + cg.add(var.set_flow_sensor(sens)) + + if CONF_HEAD in config: + sens = await sensor.new_sensor(config[CONF_HEAD]) + cg.add(var.set_head_sensor(sens)) + + if CONF_POWER in config: + sens = await sensor.new_sensor(config[CONF_POWER]) + cg.add(var.set_power_sensor(sens)) + + if CONF_CURRENT in config: + sens = await sensor.new_sensor(config[CONF_CURRENT]) + cg.add(var.set_current_sensor(sens)) + + if CONF_SPEED in config: + sens = await sensor.new_sensor(config[CONF_SPEED]) + cg.add(var.set_speed_sensor(sens)) + + if CONF_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_VOLTAGE]) + cg.add(var.set_voltage_sensor(sens)) diff --git a/esphome/const.py b/esphome/const.py index 3145255e20..3442c392c7 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -262,6 +262,7 @@ CONF_FINGER_ID = "finger_id" CONF_FINGERPRINT_COUNT = "fingerprint_count" CONF_FLASH_LENGTH = "flash_length" CONF_FLASH_TRANSITION_LENGTH = "flash_transition_length" +CONF_FLOW = "flow" CONF_FLOW_CONTROL_PIN = "flow_control_pin" CONF_FOR = "for" CONF_FORCE_UPDATE = "force_update" @@ -286,6 +287,7 @@ CONF_GPIO = "gpio" CONF_GREEN = "green" CONF_GROUP = "group" CONF_HARDWARE_UART = "hardware_uart" +CONF_HEAD = "head" CONF_HEARTBEAT = "heartbeat" CONF_HEAT_ACTION = "heat_action" CONF_HEAT_DEADBAND = "heat_deadband" @@ -891,6 +893,7 @@ UNIT_CENTIMETER = "cm" UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" UNIT_CUBIC_METER = "m³" +UNIT_CUBIC_METER_PER_HOUR = "m³/h" UNIT_DECIBEL = "dB" UNIT_DECIBEL_MILLIWATT = "dBm" UNIT_DEGREE_PER_SECOND = "°/s" @@ -928,6 +931,7 @@ UNIT_PERCENT = "%" UNIT_PH = "pH" UNIT_PULSES = "pulses" UNIT_PULSES_PER_MINUTE = "pulses/min" +UNIT_REVOLUTIONS_PER_MINUTE = "RPM" UNIT_SECOND = "s" UNIT_STEPS = "steps" UNIT_VOLT = "V" diff --git a/tests/test1.yaml b/tests/test1.yaml index 21379e807c..9d279b5e40 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -325,6 +325,7 @@ ble_client: accept: True - mac_address: C4:4F:33:11:22:33 id: my_bedjet_ble_client + bedjet: - ble_client_id: my_bedjet_ble_client id: my_bedjet_client @@ -1269,6 +1270,16 @@ sensor: pressure: name: "MPL3115A2 Pressure" update_interval: 10s + - platform: alpha3 + ble_client_id: ble_foo + flow: + name: "Radiator Pump Flow" + head: + name: "Radiator Pump Head" + power: + name: "Radiator Pump Power" + speed: + name: "Radiator Pump Speed" - platform: ld2410 moving_distance: name: "Moving distance (cm)" From 74139985c9c6c5ede69c7c629b48f0526284bc0f Mon Sep 17 00:00:00 2001 From: KoenBreeman <121864590+KoenBreeman@users.noreply.github.com> Date: Tue, 11 Jul 2023 23:19:28 +0200 Subject: [PATCH 087/106] RTC implementation of pcf8563 (#4998) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/pcf8563/__init__.py | 0 esphome/components/pcf8563/pcf8563.cpp | 109 ++++++++++++++++++++++ esphome/components/pcf8563/pcf8563.h | 124 +++++++++++++++++++++++++ esphome/components/pcf8563/time.py | 62 +++++++++++++ tests/test5.yaml | 1 + 6 files changed, 297 insertions(+) create mode 100644 esphome/components/pcf8563/__init__.py create mode 100644 esphome/components/pcf8563/pcf8563.cpp create mode 100644 esphome/components/pcf8563/pcf8563.h create mode 100644 esphome/components/pcf8563/time.py diff --git a/CODEOWNERS b/CODEOWNERS index 68a8eb6d18..82612fbd4a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -201,6 +201,7 @@ esphome/components/output/* @esphome/core esphome/components/pca6416a/* @Mat931 esphome/components/pca9554/* @hwstar esphome/components/pcf85063/* @brogon +esphome/components/pcf8563/* @KoenBreeman esphome/components/pid/* @OttoWinter esphome/components/pipsolar/* @andreashergert1984 esphome/components/pm1006/* @habbie diff --git a/esphome/components/pcf8563/__init__.py b/esphome/components/pcf8563/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/pcf8563/pcf8563.cpp b/esphome/components/pcf8563/pcf8563.cpp new file mode 100644 index 0000000000..f2a82735c5 --- /dev/null +++ b/esphome/components/pcf8563/pcf8563.cpp @@ -0,0 +1,109 @@ +#include "pcf8563.h" +#include "esphome/core/log.h" + +// Datasheet: +// - https://nl.mouser.com/datasheet/2/302/PCF8563-1127619.pdf + +namespace esphome { +namespace pcf8563 { + +static const char *const TAG = "PCF8563"; + +void PCF8563Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up PCF8563..."); + if (!this->read_rtc_()) { + this->mark_failed(); + } +} + +void PCF8563Component::update() { this->read_time(); } + +void PCF8563Component::dump_config() { + ESP_LOGCONFIG(TAG, "PCF8563:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with PCF8563 failed!"); + } + ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str()); +} + +float PCF8563Component::get_setup_priority() const { return setup_priority::DATA; } + +void PCF8563Component::read_time() { + if (!this->read_rtc_()) { + return; + } + if (pcf8563_.reg.stop) { + ESP_LOGW(TAG, "RTC halted, not syncing to system clock."); + return; + } + ESPTime rtc_time{ + .second = uint8_t(pcf8563_.reg.second + 10 * pcf8563_.reg.second_10), + .minute = uint8_t(pcf8563_.reg.minute + 10u * pcf8563_.reg.minute_10), + .hour = uint8_t(pcf8563_.reg.hour + 10u * pcf8563_.reg.hour_10), + .day_of_week = uint8_t(pcf8563_.reg.weekday), + .day_of_month = uint8_t(pcf8563_.reg.day + 10u * pcf8563_.reg.day_10), + .day_of_year = 1, // ignored by recalc_timestamp_utc(false) + .month = uint8_t(pcf8563_.reg.month + 10u * pcf8563_.reg.month_10), + .year = uint16_t(pcf8563_.reg.year + 10u * pcf8563_.reg.year_10 + 2000), + .is_dst = false, // not used + .timestamp = 0, // overwritten by recalc_timestamp_utc(false) + }; + rtc_time.recalc_timestamp_utc(false); + if (!rtc_time.is_valid()) { + ESP_LOGE(TAG, "Invalid RTC time, not syncing to system clock."); + return; + } + time::RealTimeClock::synchronize_epoch_(rtc_time.timestamp); +} + +void PCF8563Component::write_time() { + auto now = time::RealTimeClock::utcnow(); + if (!now.is_valid()) { + ESP_LOGE(TAG, "Invalid system time, not syncing to RTC."); + return; + } + pcf8563_.reg.year = (now.year - 2000) % 10; + pcf8563_.reg.year_10 = (now.year - 2000) / 10 % 10; + pcf8563_.reg.month = now.month % 10; + pcf8563_.reg.month_10 = now.month / 10; + pcf8563_.reg.day = now.day_of_month % 10; + pcf8563_.reg.day_10 = now.day_of_month / 10; + pcf8563_.reg.weekday = now.day_of_week; + pcf8563_.reg.hour = now.hour % 10; + pcf8563_.reg.hour_10 = now.hour / 10; + pcf8563_.reg.minute = now.minute % 10; + pcf8563_.reg.minute_10 = now.minute / 10; + pcf8563_.reg.second = now.second % 10; + pcf8563_.reg.second_10 = now.second / 10; + pcf8563_.reg.stop = false; + + this->write_rtc_(); +} + +bool PCF8563Component::read_rtc_() { + if (!this->read_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.raw))) { + ESP_LOGE(TAG, "Can't read I2C data."); + return false; + } + ESP_LOGD(TAG, "Read %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u STOP:%s CLKOUT:%0u", pcf8563_.reg.hour_10, + pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second, + pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10, + pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled); + + return true; +} + +bool PCF8563Component::write_rtc_() { + if (!this->write_bytes(0, this->pcf8563_.raw, sizeof(this->pcf8563_.raw))) { + ESP_LOGE(TAG, "Can't write I2C data."); + return false; + } + ESP_LOGD(TAG, "Write %0u%0u:%0u%0u:%0u%0u 20%0u%0u-%0u%0u-%0u%0u OSC:%s CLKOUT:%0u", pcf8563_.reg.hour_10, + pcf8563_.reg.hour, pcf8563_.reg.minute_10, pcf8563_.reg.minute, pcf8563_.reg.second_10, pcf8563_.reg.second, + pcf8563_.reg.year_10, pcf8563_.reg.year, pcf8563_.reg.month_10, pcf8563_.reg.month, pcf8563_.reg.day_10, + pcf8563_.reg.day, ONOFF(!pcf8563_.reg.stop), pcf8563_.reg.clkout_enabled); + return true; +} +} // namespace pcf8563 +} // namespace esphome diff --git a/esphome/components/pcf8563/pcf8563.h b/esphome/components/pcf8563/pcf8563.h new file mode 100644 index 0000000000..b6832efe72 --- /dev/null +++ b/esphome/components/pcf8563/pcf8563.h @@ -0,0 +1,124 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/time/real_time_clock.h" + +namespace esphome { +namespace pcf8563 { + +class PCF8563Component : public time::RealTimeClock, public i2c::I2CDevice { + public: + void setup() override; + void update() override; + void dump_config() override; + float get_setup_priority() const override; + void read_time(); + void write_time(); + + protected: + bool read_rtc_(); + bool write_rtc_(); + union PCF8563Reg { + struct { + // Control_1 register + bool : 3; + bool power_on_reset : 1; + bool : 1; + bool stop : 1; + bool : 1; + bool ext_test : 1; + + // Control_2 register + bool time_int : 1; + bool alarm_int : 1; + bool timer_flag : 1; + bool alarm_flag : 1; + bool timer_int_timer_pulse : 1; + bool : 3; + + // Seconds register + uint8_t second : 4; + uint8_t second_10 : 3; + bool clock_int : 1; + + // Minutes register + uint8_t minute : 4; + uint8_t minute_10 : 3; + uint8_t : 1; + + // Hours register + uint8_t hour : 4; + uint8_t hour_10 : 2; + uint8_t : 2; + + // Days register + uint8_t day : 4; + uint8_t day_10 : 2; + uint8_t : 2; + + // Weekdays register + uint8_t weekday : 3; + uint8_t unused_3 : 5; + + // Months register + uint8_t month : 4; + uint8_t month_10 : 1; + uint8_t : 2; + uint8_t century : 1; + + // Years register + uint8_t year : 4; + uint8_t year_10 : 4; + + // Minute Alarm register + uint8_t minute_alarm : 4; + uint8_t minute_alarm_10 : 3; + bool minute_alarm_enabled : 1; + + // Hour Alarm register + uint8_t hour_alarm : 4; + uint8_t hour_alarm_10 : 2; + uint8_t : 1; + bool hour_alarm_enabled : 1; + + // Day Alarm register + uint8_t day_alarm : 4; + uint8_t day_alarm_10 : 2; + uint8_t : 1; + bool day_alarm_enabled : 1; + + // Weekday Alarm register + uint8_t weekday_alarm : 3; + uint8_t : 4; + bool weekday_alarm_enabled : 1; + + // CLKout control register + uint8_t frequency_output : 2; + uint8_t : 5; + uint8_t clkout_enabled : 1; + + // Timer control register + uint8_t timer_source_frequency : 2; + uint8_t : 5; + uint8_t timer_enabled : 1; + + // Timer register + uint8_t countdown_period : 8; + + } reg; + mutable uint8_t raw[sizeof(reg)]; + } pcf8563_; +}; + +template class WriteAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->write_time(); } +}; + +template class ReadAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->read_time(); } +}; +} // namespace pcf8563 +} // namespace esphome diff --git a/esphome/components/pcf8563/time.py b/esphome/components/pcf8563/time.py new file mode 100644 index 0000000000..2e6456a72d --- /dev/null +++ b/esphome/components/pcf8563/time.py @@ -0,0 +1,62 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome import automation +from esphome.components import i2c, time +from esphome.const import CONF_ID + +CODEOWNERS = ["@KoenBreeman"] + +DEPENDENCIES = ["i2c"] + + +pcf8563_ns = cg.esphome_ns.namespace("pcf8563") +pcf8563Component = pcf8563_ns.class_( + "PCF8563Component", time.RealTimeClock, i2c.I2CDevice +) +WriteAction = pcf8563_ns.class_("WriteAction", automation.Action) +ReadAction = pcf8563_ns.class_("ReadAction", automation.Action) + + +CONFIG_SCHEMA = time.TIME_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(pcf8563Component), + } +).extend(i2c.i2c_device_schema(0xA3)) + + +@automation.register_action( + "pcf8563.write_time", + WriteAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(pcf8563Component), + } + ), +) +async def pcf8563_write_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +@automation.register_action( + "pcf8563.read_time", + ReadAction, + automation.maybe_simple_id( + { + cv.GenerateID(): cv.use_id(pcf8563Component), + } + ), +) +async def pcf8563_read_time_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + return var + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await time.register_time(var, config) diff --git a/tests/test5.yaml b/tests/test5.yaml index cb4b559b06..a2530d799a 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -590,6 +590,7 @@ display: time: - platform: pcf85063 + - platform: pcf8563 text_sensor: - platform: ezo_pmp From 7a551081ee5e08bf71bc52c2c30168992192bb89 Mon Sep 17 00:00:00 2001 From: dentra Date: Wed, 12 Jul 2023 03:08:03 +0300 Subject: [PATCH 088/106] web server esp idf suppport (#3500) * initial web_server_idf implementation * initial web_server_idf implementation * fix lint errors * fix lint errors * add captive_portal support * fix lint errors * fix lint errors * add url decode * Increase the max supported size of headers section in HTTP request * add ota support * add mulipart form data support (ota required) * make linter happy * make linter happy * make linter happy * fix review marks * add DefaultHeaders support * add DefaultHeaders support * unify file names * using std::isnan * parse multipart requests only when ota enabled * parse multipart requests only when ota enabled * parse multipart requests only when ota enabled * parse multipart requests only when ota enabled * parse multipart requests only when ota enabled * drop multipart request support * drop multipart request support * drop multipart request support * OTA is disabled by default * fail when OTA enabled on IDF framework * changing file permissions to remove execute bit * return back PGM_P and strncpy_P macro * temp web_server fix to be compat with 2022.12 * fix config handling w/o web_server * fix compilation with "local" * fully remove all idf ota * merge with esphome 2023.6 * add core/hal to web_server_base * Update esphome/components/web_server_base/__init__.py Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> * Update __init__.py * Update __init__.py --------- Co-authored-by: Keith Burzinski Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/captive_portal/__init__.py | 12 +- .../captive_portal/captive_portal.cpp | 8 +- .../captive_portal/captive_portal.h | 12 +- esphome/components/web_server/__init__.py | 12 +- .../components/web_server/list_entities.cpp | 4 - esphome/components/web_server/list_entities.h | 4 - esphome/components/web_server/web_server.cpp | 136 ++++--- esphome/components/web_server/web_server.h | 4 - .../components/web_server_base/__init__.py | 23 +- .../web_server_base/web_server_base.cpp | 25 +- .../web_server_base/web_server_base.h | 9 +- esphome/components/web_server_idf/__init__.py | 14 + .../web_server_idf/web_server_idf.cpp | 374 ++++++++++++++++++ .../web_server_idf/web_server_idf.h | 277 +++++++++++++ script/build_codeowners.py | 2 + 16 files changed, 814 insertions(+), 103 deletions(-) create mode 100644 esphome/components/web_server_idf/__init__.py create mode 100644 esphome/components/web_server_idf/web_server_idf.cpp create mode 100644 esphome/components/web_server_idf/web_server_idf.h diff --git a/CODEOWNERS b/CODEOWNERS index 82612fbd4a..8ebf51ceeb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -312,6 +312,7 @@ esphome/components/version/* @esphome/core esphome/components/voice_assistant/* @jesserockz esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter +esphome/components/web_server_idf/* @dentra esphome/components/whirlpool/* @glmnet esphome/components/whynter/* @aeonsablaze esphome/components/wiegand/* @ssieb diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py index ff5266e84f..db4a17f6f7 100644 --- a/esphome/components/captive_portal/__init__.py +++ b/esphome/components/captive_portal/__init__.py @@ -21,7 +21,6 @@ CONFIG_SCHEMA = cv.All( ), } ).extend(cv.COMPONENT_SCHEMA), - cv.only_with_arduino, cv.only_on(["esp32", "esp8266"]), ) @@ -34,8 +33,9 @@ async def to_code(config): await cg.register_component(var, config) cg.add_define("USE_CAPTIVE_PORTAL") - if CORE.is_esp32: - cg.add_library("DNSServer", None) - cg.add_library("WiFi", None) - if CORE.is_esp8266: - cg.add_library("DNSServer", None) + if CORE.using_arduino: + if CORE.is_esp32: + cg.add_library("DNSServer", None) + cg.add_library("WiFi", None) + if CORE.is_esp8266: + cg.add_library("DNSServer", None) diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index 3bfdea0ab5..74c6606fc0 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "captive_portal.h" #include "esphome/core/log.h" #include "esphome/core/application.h" @@ -46,10 +44,12 @@ void CaptivePortal::start() { this->base_->add_ota_handler(); } +#ifdef USE_ARDUINO this->dns_server_ = make_unique(); this->dns_server_->setErrorReplyCode(DNSReplyCode::NoError); network::IPAddress ip = wifi::global_wifi_component->wifi_soft_ap_ip(); this->dns_server_->start(53, "*", (uint32_t) ip); +#endif this->base_->get_server()->onNotFound([this](AsyncWebServerRequest *req) { if (!this->active_ || req->host().c_str() == wifi::global_wifi_component->wifi_soft_ap_ip().str()) { @@ -67,7 +67,7 @@ void CaptivePortal::start() { void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { if (req->url() == "/") { - AsyncWebServerResponse *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); + auto *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); response->addHeader("Content-Encoding", "gzip"); req->send(response); return; @@ -91,5 +91,3 @@ CaptivePortal *global_captive_portal = nullptr; // NOLINT(cppcoreguidelines-avo } // namespace captive_portal } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index c2aada171f..df45d40d12 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -1,9 +1,9 @@ #pragma once -#ifdef USE_ARDUINO - #include +#ifdef USE_ARDUINO #include +#endif #include "esphome/core/component.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" @@ -18,18 +18,22 @@ class CaptivePortal : public AsyncWebHandler, public Component { CaptivePortal(web_server_base::WebServerBase *base); void setup() override; void dump_config() override; +#ifdef USE_ARDUINO void loop() override { if (this->dns_server_ != nullptr) this->dns_server_->processNextRequest(); } +#endif float get_setup_priority() const override; void start(); bool is_active() const { return this->active_; } void end() { this->active_ = false; this->base_->deinit(); +#ifdef USE_ARDUINO this->dns_server_->stop(); this->dns_server_ = nullptr; +#endif } bool canHandle(AsyncWebServerRequest *request) override { @@ -58,12 +62,12 @@ class CaptivePortal : public AsyncWebHandler, public Component { web_server_base::WebServerBase *base_; bool initialized_{false}; bool active_{false}; +#ifdef USE_ARDUINO std::unique_ptr dns_server_{nullptr}; +#endif }; extern CaptivePortal *global_captive_portal; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace captive_portal } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 25c0254f90..ab54ae8582 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -47,6 +47,12 @@ def validate_local(config): return config +def validate_ota(config): + if CORE.using_esp_idf and config[CONF_OTA]: + raise cv.Invalid("Enabling 'ota' is not supported for IDF framework yet") + return config + + CONFIG_SCHEMA = cv.All( cv.Schema( { @@ -71,15 +77,17 @@ CONFIG_SCHEMA = cv.All( web_server_base.WebServerBase ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, - cv.Optional(CONF_OTA, default=True): cv.boolean, + cv.SplitDefault( + CONF_OTA, esp8266=True, esp32_arduino=True, esp32_idf=False + ): cv.boolean, cv.Optional(CONF_LOG, default=True): cv.boolean, cv.Optional(CONF_LOCAL): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA), - cv.only_with_arduino, cv.only_on(["esp32", "esp8266"]), default_url, validate_local, + validate_ota, ) diff --git a/esphome/components/web_server/list_entities.cpp b/esphome/components/web_server/list_entities.cpp index ce7b4be7f3..016dd37dd9 100644 --- a/esphome/components/web_server/list_entities.cpp +++ b/esphome/components/web_server/list_entities.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "list_entities.h" #include "esphome/core/application.h" #include "esphome/core/log.h" @@ -103,5 +101,3 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont } // namespace web_server } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/web_server/list_entities.h b/esphome/components/web_server/list_entities.h index 8ddca15edf..1569c8ac57 100644 --- a/esphome/components/web_server/list_entities.h +++ b/esphome/components/web_server/list_entities.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "esphome/core/component.h" #include "esphome/core/component_iterator.h" #include "esphome/core/defines.h" @@ -59,5 +57,3 @@ class ListEntitiesIterator : public ComponentIterator { } // namespace web_server } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index eb1858a09c..01057fead6 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -1,5 +1,3 @@ -#ifdef USE_ARDUINO - #include "web_server.h" #include "esphome/components/json/json_util.h" @@ -9,7 +7,9 @@ #include "esphome/core/log.h" #include "esphome/core/util.h" +#ifdef USE_ARDUINO #include "StreamString.h" +#endif #include @@ -181,7 +181,7 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(F("")); #endif if (strlen(this->css_url_) > 0) { - stream->print(F("print(F(R"(print(this->css_url_); stream->print(F("\">")); } @@ -381,7 +381,7 @@ 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; - if (isnan(value)) { + if (std::isnan(value)) { state = "NA"; } else { state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); @@ -524,11 +524,8 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc request->send(200); } else if (match.method == "turn_on") { auto call = obj->turn_on(); - if (request->hasParam("speed")) { - String speed = request->getParam("speed")->value(); - } if (request->hasParam("speed_level")) { - String speed_level = request->getParam("speed_level")->value(); + auto speed_level = request->getParam("speed_level")->value(); auto val = parse_number(speed_level.c_str()); if (!val.has_value()) { ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str()); @@ -537,7 +534,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc call.set_speed(*val); } if (request->hasParam("oscillation")) { - String speed = request->getParam("oscillation")->value(); + auto speed = request->getParam("oscillation")->value(); auto val = parse_on_off(speed.c_str()); switch (val) { case PARSE_ON: @@ -585,29 +582,54 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa request->send(200); } else if (match.method == "turn_on") { auto call = obj->turn_on(); - if (request->hasParam("brightness")) - call.set_brightness(request->getParam("brightness")->value().toFloat() / 255.0f); - if (request->hasParam("r")) - call.set_red(request->getParam("r")->value().toFloat() / 255.0f); - if (request->hasParam("g")) - call.set_green(request->getParam("g")->value().toFloat() / 255.0f); - if (request->hasParam("b")) - call.set_blue(request->getParam("b")->value().toFloat() / 255.0f); - if (request->hasParam("white_value")) - call.set_white(request->getParam("white_value")->value().toFloat() / 255.0f); - if (request->hasParam("color_temp")) - call.set_color_temperature(request->getParam("color_temp")->value().toFloat()); - + if (request->hasParam("brightness")) { + auto brightness = parse_number(request->getParam("brightness")->value().c_str()); + if (brightness.has_value()) { + call.set_brightness(*brightness / 255.0f); + } + } + if (request->hasParam("r")) { + auto r = parse_number(request->getParam("r")->value().c_str()); + if (r.has_value()) { + call.set_red(*r / 255.0f); + } + } + if (request->hasParam("g")) { + auto g = parse_number(request->getParam("g")->value().c_str()); + if (g.has_value()) { + call.set_green(*g / 255.0f); + } + } + if (request->hasParam("b")) { + auto b = parse_number(request->getParam("b")->value().c_str()); + if (b.has_value()) { + call.set_blue(*b / 255.0f); + } + } + if (request->hasParam("white_value")) { + auto white_value = parse_number(request->getParam("white_value")->value().c_str()); + if (white_value.has_value()) { + call.set_white(*white_value / 255.0f); + } + } + if (request->hasParam("color_temp")) { + auto color_temp = parse_number(request->getParam("color_temp")->value().c_str()); + if (color_temp.has_value()) { + call.set_color_temperature(*color_temp); + } + } if (request->hasParam("flash")) { - float length_s = request->getParam("flash")->value().toFloat(); - call.set_flash_length(static_cast(length_s * 1000)); + auto flash = parse_number(request->getParam("flash")->value().c_str()); + if (flash.has_value()) { + call.set_flash_length(*flash * 1000); + } } - if (request->hasParam("transition")) { - float length_s = request->getParam("transition")->value().toFloat(); - call.set_transition_length(static_cast(length_s * 1000)); + auto transition = parse_number(request->getParam("transition")->value().c_str()); + if (transition.has_value()) { + call.set_transition_length(*transition * 1000); + } } - if (request->hasParam("effect")) { const char *effect = request->getParam("effect")->value().c_str(); call.set_effect(effect); @@ -618,8 +640,10 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa } else if (match.method == "turn_off") { auto call = obj->turn_off(); if (request->hasParam("transition")) { - auto length = (uint32_t) request->getParam("transition")->value().toFloat() * 1000; - call.set_transition_length(length); + auto transition = parse_number(request->getParam("transition")->value().c_str()); + if (transition.has_value()) { + call.set_transition_length(*transition * 1000); + } } this->schedule_([call]() mutable { call.perform(); }); request->send(200); @@ -681,10 +705,18 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa return; } - if (request->hasParam("position")) - call.set_position(request->getParam("position")->value().toFloat()); - if (request->hasParam("tilt")) - call.set_tilt(request->getParam("tilt")->value().toFloat()); + if (request->hasParam("position")) { + auto position = parse_number(request->getParam("position")->value().c_str()); + if (position.has_value()) { + call.set_position(*position); + } + } + if (request->hasParam("tilt")) { + auto tilt = parse_number(request->getParam("tilt")->value().c_str()); + if (tilt.has_value()) { + call.set_tilt(*tilt); + } + } this->schedule_([call]() mutable { call.perform(); }); request->send(200); @@ -725,10 +757,9 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM auto call = obj->make_call(); if (request->hasParam("value")) { - String value = request->getParam("value")->value(); - optional value_f = parse_number(value.c_str()); - if (value_f.has_value()) - call.set_value(*value_f); + auto value = parse_number(request->getParam("value")->value().c_str()); + if (value.has_value()) + call.set_value(*value); } this->schedule_([call]() mutable { call.perform(); }); @@ -747,7 +778,7 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail root["step"] = obj->traits.get_step(); root["mode"] = (int) obj->traits.get_mode(); } - if (isnan(value)) { + if (std::isnan(value)) { root["value"] = "\"NaN\""; root["state"] = "NA"; } else { @@ -784,7 +815,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM auto call = obj->make_call(); if (request->hasParam("option")) { - String option = request->getParam("option")->value(); + auto option = request->getParam("option")->value(); call.set_option(option.c_str()); // NOLINT(clang-diagnostic-deprecated-declarations) } @@ -834,29 +865,26 @@ void WebServer::handle_climate_request(AsyncWebServerRequest *request, const Url auto call = obj->make_call(); if (request->hasParam("mode")) { - String mode = request->getParam("mode")->value(); + auto mode = request->getParam("mode")->value(); call.set_mode(mode.c_str()); } if (request->hasParam("target_temperature_high")) { - String value = request->getParam("target_temperature_high")->value(); - optional value_f = parse_number(value.c_str()); - if (value_f.has_value()) - call.set_target_temperature_high(*value_f); + auto target_temperature_high = parse_number(request->getParam("target_temperature_high")->value().c_str()); + if (target_temperature_high.has_value()) + call.set_target_temperature_high(*target_temperature_high); } if (request->hasParam("target_temperature_low")) { - String value = request->getParam("target_temperature_low")->value(); - optional value_f = parse_number(value.c_str()); - if (value_f.has_value()) - call.set_target_temperature_low(*value_f); + auto target_temperature_low = parse_number(request->getParam("target_temperature_low")->value().c_str()); + if (target_temperature_low.has_value()) + call.set_target_temperature_low(*target_temperature_low); } if (request->hasParam("target_temperature")) { - String value = request->getParam("target_temperature")->value(); - optional value_f = parse_number(value.c_str()); - if (value_f.has_value()) - call.set_target_temperature(*value_f); + auto target_temperature = parse_number(request->getParam("target_temperature")->value().c_str()); + if (target_temperature.has_value()) + call.set_target_temperature(*target_temperature); } this->schedule_([call]() mutable { call.perform(); }); @@ -1231,5 +1259,3 @@ void WebServer::schedule_(std::function &&f) { } // namespace web_server } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 83ebba205f..788e30ccf2 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -1,7 +1,5 @@ #pragma once -#ifdef USE_ARDUINO - #include "list_entities.h" #include "esphome/components/web_server_base/web_server_base.h" @@ -291,5 +289,3 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { } // namespace web_server } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 14fb033a56..87f23a990a 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -5,7 +5,15 @@ from esphome.core import coroutine_with_priority, CORE CODEOWNERS = ["@OttoWinter"] DEPENDENCIES = ["network"] -AUTO_LOAD = ["async_tcp"] + + +def AUTO_LOAD(): + if CORE.using_arduino: + return ["async_tcp"] + if CORE.using_esp_idf: + return ["web_server_idf"] + return [] + web_server_base_ns = cg.esphome_ns.namespace("web_server_base") WebServerBase = web_server_base_ns.class_("WebServerBase", cg.Component) @@ -23,9 +31,10 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - if CORE.is_esp32: - cg.add_library("WiFi", None) - cg.add_library("FS", None) - cg.add_library("Update", None) - # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json - cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0") + if CORE.using_arduino: + if CORE.is_esp32: + cg.add_library("WiFi", None) + cg.add_library("FS", None) + cg.add_library("Update", None) + # https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json + cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.1.0") diff --git a/esphome/components/web_server_base/web_server_base.cpp b/esphome/components/web_server_base/web_server_base.cpp index 3c269b28b8..997ce0798a 100644 --- a/esphome/components/web_server_base/web_server_base.cpp +++ b/esphome/components/web_server_base/web_server_base.cpp @@ -1,16 +1,17 @@ -#ifdef USE_ARDUINO - #include "web_server_base.h" #include "esphome/core/log.h" #include "esphome/core/application.h" -#include +#include "esphome/core/helpers.h" +#ifdef USE_ARDUINO +#include #ifdef USE_ESP32 #include #endif #ifdef USE_ESP8266 #include #endif +#endif namespace esphome { namespace web_server_base { @@ -24,18 +25,22 @@ void WebServerBase::add_handler(AsyncWebHandler *handler) { handler = new internal::AuthMiddlewareHandler(handler, &credentials_); } this->handlers_.push_back(handler); - if (this->server_ != nullptr) + if (this->server_ != nullptr) { this->server_->addHandler(handler); + } } void report_ota_error() { +#ifdef USE_ARDUINO StreamString ss; Update.printError(ss); ESP_LOGW(TAG, "OTA Update failed! Error: %s", ss.c_str()); +#endif } void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) { +#ifdef USE_ARDUINO bool success; if (index == 0) { ESP_LOGI(TAG, "OTA Update Start: %s", filename.c_str()); @@ -45,9 +50,10 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin // NOLINTNEXTLINE(readability-static-accessed-through-instance) success = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); #endif -#ifdef USE_ESP32 - if (Update.isRunning()) +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + if (Update.isRunning()) { Update.abort(); + } success = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH); #endif if (!success) { @@ -85,8 +91,10 @@ void OTARequestHandler::handleUpload(AsyncWebServerRequest *request, const Strin report_ota_error(); } } +#endif } void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { +#ifdef USE_ARDUINO AsyncWebServerResponse *response; if (!Update.hasError()) { response = request->beginResponse(200, "text/plain", "Update Successful!"); @@ -98,10 +106,13 @@ void OTARequestHandler::handleRequest(AsyncWebServerRequest *request) { } response->addHeader("Connection", "close"); request->send(response); +#endif } void WebServerBase::add_ota_handler() { +#ifdef USE_ARDUINO this->add_handler(new OTARequestHandler(this)); // NOLINT +#endif } float WebServerBase::get_setup_priority() const { // Before WiFi (captive portal) @@ -110,5 +121,3 @@ float WebServerBase::get_setup_priority() const { } // namespace web_server_base } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/web_server_base/web_server_base.h b/esphome/components/web_server_base/web_server_base.h index ae286b1e22..c312126472 100644 --- a/esphome/components/web_server_base/web_server_base.h +++ b/esphome/components/web_server_base/web_server_base.h @@ -1,14 +1,17 @@ #pragma once -#ifdef USE_ARDUINO - #include #include #include #include "esphome/core/component.h" +#ifdef USE_ARDUINO #include +#elif USE_ESP_IDF +#include "esphome/core/hal.h" +#include "esphome/components/web_server_idf/web_server_idf.h" +#endif namespace esphome { namespace web_server_base { @@ -141,5 +144,3 @@ class OTARequestHandler : public AsyncWebHandler { } // namespace web_server_base } // namespace esphome - -#endif // USE_ARDUINO diff --git a/esphome/components/web_server_idf/__init__.py b/esphome/components/web_server_idf/__init__.py new file mode 100644 index 0000000000..bd3db24bc6 --- /dev/null +++ b/esphome/components/web_server_idf/__init__.py @@ -0,0 +1,14 @@ +import esphome.config_validation as cv +from esphome.components.esp32 import add_idf_sdkconfig_option + +CODEOWNERS = ["@dentra"] + +CONFIG_SCHEMA = cv.All( + cv.Schema({}), + cv.only_with_esp_idf, +) + + +async def to_code(config): + # Increase the maximum supported size of headers section in HTTP request packet to be processed by the server + add_idf_sdkconfig_option("CONFIG_HTTPD_MAX_REQ_HDR_LEN", 1024) diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp new file mode 100644 index 0000000000..444e682460 --- /dev/null +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -0,0 +1,374 @@ +#ifdef USE_ESP_IDF + +#include + +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +#include "esp_tls_crypto.h" + +#include "web_server_idf.h" + +namespace esphome { +namespace web_server_idf { + +#ifndef HTTPD_409 +#define HTTPD_409 "409 Conflict" +#endif + +#define CRLF_STR "\r\n" +#define CRLF_LEN (sizeof(CRLF_STR) - 1) + +static const char *const TAG = "web_server_idf"; + +void AsyncWebServer::end() { + if (this->server_) { + httpd_stop(this->server_); + this->server_ = nullptr; + } +} + +void AsyncWebServer::begin() { + if (this->server_) { + this->end(); + } + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = this->port_; + config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; }; + if (httpd_start(&this->server_, &config) == ESP_OK) { + const httpd_uri_t handler_get = { + .uri = "", + .method = HTTP_GET, + .handler = AsyncWebServer::request_handler, + .user_ctx = this, + }; + httpd_register_uri_handler(this->server_, &handler_get); + + const httpd_uri_t handler_post = { + .uri = "", + .method = HTTP_POST, + .handler = AsyncWebServer::request_handler, + .user_ctx = this, + }; + httpd_register_uri_handler(this->server_, &handler_post); + } +} + +esp_err_t AsyncWebServer::request_handler(httpd_req_t *r) { + ESP_LOGV(TAG, "Enter AsyncWebServer::request_handler. method=%u, uri=%s", r->method, r->uri); + AsyncWebServerRequest req(r); + auto *server = static_cast(r->user_ctx); + for (auto *handler : server->handlers_) { + if (handler->canHandle(&req)) { + // At now process only basic requests. + // OTA requires multipart request support and handleUpload for it + handler->handleRequest(&req); + return ESP_OK; + } + } + if (server->on_not_found_) { + server->on_not_found_(&req); + return ESP_OK; + } + return ESP_ERR_NOT_FOUND; +} + +AsyncWebServerRequest::~AsyncWebServerRequest() { + delete this->rsp_; + for (const auto &pair : this->params_) { + delete pair.second; // NOLINT(cppcoreguidelines-owning-memory) + } +} + +optional AsyncWebServerRequest::get_header(const char *name) const { + size_t buf_len = httpd_req_get_hdr_value_len(*this, name); + if (buf_len == 0) { + return {}; + } + auto buf = std::unique_ptr(new char[++buf_len]); + if (!buf) { + ESP_LOGE(TAG, "No enough memory for get header %s", name); + return {}; + } + if (httpd_req_get_hdr_value_str(*this, name, buf.get(), buf_len) != ESP_OK) { + return {}; + } + return {buf.get()}; +} + +std::string AsyncWebServerRequest::url() const { + auto *str = strchr(this->req_->uri, '?'); + if (str == nullptr) { + return this->req_->uri; + } + return std::string(this->req_->uri, str - this->req_->uri); +} + +std::string AsyncWebServerRequest::host() const { return this->get_header("Host").value(); } + +void AsyncWebServerRequest::send(AsyncWebServerResponse *response) { + httpd_resp_send(*this, response->get_content_data(), response->get_content_size()); +} + +void AsyncWebServerRequest::send(int code, const char *content_type, const char *content) { + this->init_response_(nullptr, code, content_type); + if (content) { + httpd_resp_send(*this, content, HTTPD_RESP_USE_STRLEN); + } else { + httpd_resp_send(*this, nullptr, 0); + } +} + +void AsyncWebServerRequest::redirect(const std::string &url) { + httpd_resp_set_status(*this, "302 Found"); + httpd_resp_set_hdr(*this, "Location", url.c_str()); + httpd_resp_send(*this, nullptr, 0); +} + +void AsyncWebServerRequest::init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type) { + httpd_resp_set_status(*this, code == 200 ? HTTPD_200 + : code == 404 ? HTTPD_404 + : code == 409 ? HTTPD_409 + : to_string(code).c_str()); + + if (content_type && *content_type) { + httpd_resp_set_type(*this, content_type); + } + httpd_resp_set_hdr(*this, "Accept-Ranges", "none"); + + for (const auto &pair : DefaultHeaders::Instance().headers_) { + httpd_resp_set_hdr(*this, pair.first.c_str(), pair.second.c_str()); + } + + delete this->rsp_; + this->rsp_ = rsp; +} + +bool AsyncWebServerRequest::authenticate(const char *username, const char *password) const { + if (username == nullptr || password == nullptr || *username == 0) { + return true; + } + auto auth = this->get_header("Authorization"); + if (!auth.has_value()) { + return false; + } + + auto *auth_str = auth.value().c_str(); + + const auto auth_prefix_len = sizeof("Basic ") - 1; + if (strncmp("Basic ", auth_str, auth_prefix_len) != 0) { + ESP_LOGW(TAG, "Only Basic authorization supported yet"); + return false; + } + + std::string user_info; + user_info += username; + user_info += ':'; + user_info += password; + + size_t n = 0, out; + esp_crypto_base64_encode(nullptr, 0, &n, reinterpret_cast(user_info.c_str()), user_info.size()); + + auto digest = std::unique_ptr(new char[n + 1]); + esp_crypto_base64_encode(reinterpret_cast(digest.get()), n, &out, + reinterpret_cast(user_info.c_str()), user_info.size()); + + return strncmp(digest.get(), auth_str + auth_prefix_len, auth.value().size() - auth_prefix_len) == 0; +} + +void AsyncWebServerRequest::requestAuthentication(const char *realm) const { + httpd_resp_set_hdr(*this, "Connection", "keep-alive"); + auto auth_val = str_sprintf("Basic realm=\"%s\"", realm ? realm : "Login Required"); + httpd_resp_set_hdr(*this, "WWW-Authenticate", auth_val.c_str()); + httpd_resp_send_err(*this, HTTPD_401_UNAUTHORIZED, nullptr); +} + +static std::string url_decode(const std::string &in) { + std::string out; + out.reserve(in.size()); + for (std::size_t i = 0; i < in.size(); ++i) { + if (in[i] == '%') { + ++i; + if (i + 1 < in.size()) { + auto c = parse_hex(&in[i], 2); + if (c.has_value()) { + out += static_cast(*c); + ++i; + } else { + out += '%'; + out += in[i++]; + out += in[i]; + } + } else { + out += '%'; + out += in[i]; + } + } else if (in[i] == '+') { + out += ' '; + } else { + out += in[i]; + } + } + return out; +} + +AsyncWebParameter *AsyncWebServerRequest::getParam(const std::string &name) { + auto find = this->params_.find(name); + if (find != this->params_.end()) { + return find->second; + } + + auto query_len = httpd_req_get_url_query_len(this->req_); + if (query_len == 0) { + return nullptr; + } + + auto query_str = std::unique_ptr(new char[++query_len]); + if (!query_str) { + ESP_LOGE(TAG, "No enough memory for get query param"); + return nullptr; + } + + auto res = httpd_req_get_url_query_str(*this, query_str.get(), query_len); + if (res != ESP_OK) { + ESP_LOGW(TAG, "Can't get query for request: %s", esp_err_to_name(res)); + return nullptr; + } + + auto query_val = std::unique_ptr(new char[query_len]); + if (!query_val) { + ESP_LOGE(TAG, "No enough memory for get query param value"); + return nullptr; + } + + res = httpd_query_key_value(query_str.get(), name.c_str(), query_val.get(), query_len); + if (res != ESP_OK) { + this->params_.insert({name, nullptr}); + return nullptr; + } + query_str.release(); + auto decoded = url_decode(query_val.get()); + query_val.release(); + auto *param = new AsyncWebParameter(decoded); // NOLINT(cppcoreguidelines-owning-memory) + this->params_.insert(std::make_pair(name, param)); + return param; +} + +void AsyncWebServerResponse::addHeader(const char *name, const char *value) { + httpd_resp_set_hdr(*this->req_, name, value); +} + +void AsyncResponseStream::print(float value) { this->print(to_string(value)); } + +void AsyncResponseStream::printf(const char *fmt, ...) { + std::string str; + va_list args; + + va_start(args, fmt); + size_t length = vsnprintf(nullptr, 0, fmt, args); + va_end(args); + + str.resize(length); + va_start(args, fmt); + vsnprintf(&str[0], length + 1, fmt, args); + va_end(args); + + this->print(str); +} + +AsyncEventSource::~AsyncEventSource() { + for (auto *ses : this->sessions_) { + delete ses; // NOLINT(cppcoreguidelines-owning-memory) + } +} + +void AsyncEventSource::handleRequest(AsyncWebServerRequest *request) { + auto *rsp = new AsyncEventSourceResponse(request, this); // NOLINT(cppcoreguidelines-owning-memory) + if (this->on_connect_) { + this->on_connect_(rsp); + } + this->sessions_.insert(rsp); +} + +void AsyncEventSource::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) { + for (auto *ses : this->sessions_) { + ses->send(message, event, id, reconnect); + } +} + +AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server) + : server_(server) { + httpd_req_t *req = *request; + + httpd_resp_set_status(req, HTTPD_200); + httpd_resp_set_type(req, "text/event-stream"); + httpd_resp_set_hdr(req, "Cache-Control", "no-cache"); + httpd_resp_set_hdr(req, "Connection", "keep-alive"); + + httpd_resp_send_chunk(req, CRLF_STR, CRLF_LEN); + + req->sess_ctx = this; + req->free_ctx = AsyncEventSourceResponse::destroy; + + this->hd_ = req->handle; + this->fd_ = httpd_req_to_sockfd(req); +} + +void AsyncEventSourceResponse::destroy(void *ptr) { + auto *rsp = static_cast(ptr); + rsp->server_->sessions_.erase(rsp); + delete rsp; // NOLINT(cppcoreguidelines-owning-memory) +} + +void AsyncEventSourceResponse::send(const char *message, const char *event, uint32_t id, uint32_t reconnect) { + if (this->fd_ == 0) { + return; + } + + std::string ev; + + if (reconnect) { + ev.append("retry: ", sizeof("retry: ") - 1); + ev.append(to_string(reconnect)); + ev.append(CRLF_STR, CRLF_LEN); + } + + if (id) { + ev.append("id: ", sizeof("id: ") - 1); + ev.append(to_string(id)); + ev.append(CRLF_STR, CRLF_LEN); + } + + if (event && *event) { + ev.append("event: ", sizeof("event: ") - 1); + ev.append(event); + ev.append(CRLF_STR, CRLF_LEN); + } + + if (message && *message) { + ev.append("data: ", sizeof("data: ") - 1); + ev.append(message); + ev.append(CRLF_STR, CRLF_LEN); + } + + if (ev.empty()) { + return; + } + + ev.append(CRLF_STR, CRLF_LEN); + + // Sending chunked content prelude + auto cs = str_snprintf("%x" CRLF_STR, 4 * sizeof(ev.size()) + CRLF_LEN, ev.size()); + httpd_socket_send(this->hd_, this->fd_, cs.c_str(), cs.size(), 0); + + // Sendiing content chunk + httpd_socket_send(this->hd_, this->fd_, ev.c_str(), ev.size(), 0); + + // Indicate end of chunk + httpd_socket_send(this->hd_, this->fd_, CRLF_STR, CRLF_LEN, 0); +} + +} // namespace web_server_idf +} // namespace esphome + +#endif // !defined(USE_ESP_IDF) diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h new file mode 100644 index 0000000000..f3cecca16f --- /dev/null +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -0,0 +1,277 @@ +#pragma once +#ifdef USE_ESP_IDF + +#include + +#include +#include +#include +#include +#include + +namespace esphome { +namespace web_server_idf { + +#define F(string_literal) (string_literal) +#define PGM_P const char * +#define strncpy_P strncpy + +using String = std::string; + +class AsyncWebParameter { + public: + AsyncWebParameter(std::string value) : value_(std::move(value)) {} + const std::string &value() const { return this->value_; } + + protected: + std::string value_; +}; + +class AsyncWebServerRequest; + +class AsyncWebServerResponse { + public: + AsyncWebServerResponse(const AsyncWebServerRequest *req) : req_(req) {} + virtual ~AsyncWebServerResponse() {} + + // NOLINTNEXTLINE(readability-identifier-naming) + void addHeader(const char *name, const char *value); + + virtual const char *get_content_data() const = 0; + virtual size_t get_content_size() const = 0; + + protected: + const AsyncWebServerRequest *req_; +}; + +class AsyncWebServerResponseEmpty : public AsyncWebServerResponse { + public: + AsyncWebServerResponseEmpty(const AsyncWebServerRequest *req) : AsyncWebServerResponse(req) {} + + const char *get_content_data() const override { return nullptr; }; + size_t get_content_size() const override { return 0; }; +}; + +class AsyncWebServerResponseContent : public AsyncWebServerResponse { + public: + AsyncWebServerResponseContent(const AsyncWebServerRequest *req, std::string content) + : AsyncWebServerResponse(req), content_(std::move(content)) {} + + const char *get_content_data() const override { return this->content_.c_str(); }; + size_t get_content_size() const override { return this->content_.size(); }; + + protected: + std::string content_; +}; + +class AsyncResponseStream : public AsyncWebServerResponse { + public: + AsyncResponseStream(const AsyncWebServerRequest *req) : AsyncWebServerResponse(req) {} + + const char *get_content_data() const override { return this->content_.c_str(); }; + size_t get_content_size() const override { return this->content_.size(); }; + + void print(const char *str) { this->content_.append(str); } + void print(const std::string &str) { this->content_.append(str); } + void print(float value); + void printf(const char *fmt, ...) __attribute__((format(printf, 2, 3))); + + protected: + std::string content_; +}; + +class AsyncWebServerResponseProgmem : public AsyncWebServerResponse { + public: + AsyncWebServerResponseProgmem(const AsyncWebServerRequest *req, const uint8_t *data, const size_t size) + : AsyncWebServerResponse(req), data_(data), size_(size) {} + + const char *get_content_data() const override { return reinterpret_cast(this->data_); }; + size_t get_content_size() const override { return this->size_; }; + + protected: + const uint8_t *data_; + const size_t size_; +}; + +class AsyncWebServerRequest { + // FIXME friend class AsyncWebServerResponse; + friend class AsyncWebServer; + + public: + ~AsyncWebServerRequest(); + + http_method method() const { return static_cast(this->req_->method); } + std::string url() const; + std::string host() const; + // NOLINTNEXTLINE(readability-identifier-naming) + size_t contentLength() const { return this->req_->content_len; } + + bool authenticate(const char *username, const char *password) const; + // NOLINTNEXTLINE(readability-identifier-naming) + void requestAuthentication(const char *realm = nullptr) const; + + void redirect(const std::string &url); + + void send(AsyncWebServerResponse *response); + void send(int code, const char *content_type = nullptr, const char *content = nullptr); + // NOLINTNEXTLINE(readability-identifier-naming) + AsyncWebServerResponse *beginResponse(int code, const char *content_type) { + auto *res = new AsyncWebServerResponseEmpty(this); // NOLINT(cppcoreguidelines-owning-memory) + this->init_response_(res, 200, content_type); + return res; + } + // NOLINTNEXTLINE(readability-identifier-naming) + AsyncWebServerResponse *beginResponse(int code, const char *content_type, const std::string &content) { + auto *res = new AsyncWebServerResponseContent(this, content); // NOLINT(cppcoreguidelines-owning-memory) + this->init_response_(res, code, content_type); + return res; + } + // NOLINTNEXTLINE(readability-identifier-naming) + AsyncWebServerResponse *beginResponse_P(int code, const char *content_type, const uint8_t *data, + const size_t data_size) { + auto *res = new AsyncWebServerResponseProgmem(this, data, data_size); // NOLINT(cppcoreguidelines-owning-memory) + this->init_response_(res, code, content_type); + return res; + } + // NOLINTNEXTLINE(readability-identifier-naming) + AsyncResponseStream *beginResponseStream(const char *content_type) { + auto *res = new AsyncResponseStream(this); // NOLINT(cppcoreguidelines-owning-memory) + this->init_response_(res, 200, content_type); + return res; + } + + // NOLINTNEXTLINE(readability-identifier-naming) + bool hasParam(const std::string &name) { return this->getParam(name) != nullptr; } + // NOLINTNEXTLINE(readability-identifier-naming) + AsyncWebParameter *getParam(const std::string &name); + + // NOLINTNEXTLINE(readability-identifier-naming) + bool hasArg(const char *name) { return this->hasParam(name); } + std::string arg(const std::string &name) { + auto *param = this->getParam(name); + if (param) { + return param->value(); + } + return {}; + } + + operator httpd_req_t *() const { return this->req_; } + optional get_header(const char *name) const; + + protected: + httpd_req_t *req_; + AsyncWebServerResponse *rsp_{}; + std::map params_; + AsyncWebServerRequest(httpd_req_t *req) : req_(req) {} + void init_response_(AsyncWebServerResponse *rsp, int code, const char *content_type); +}; + +class AsyncWebHandler; + +class AsyncWebServer { + public: + AsyncWebServer(uint16_t port) : port_(port){}; + ~AsyncWebServer() { this->end(); } + + // NOLINTNEXTLINE(readability-identifier-naming) + void onNotFound(std::function fn) { on_not_found_ = std::move(fn); } + + void begin(); + void end(); + + // NOLINTNEXTLINE(readability-identifier-naming) + AsyncWebHandler &addHandler(AsyncWebHandler *handler) { + this->handlers_.push_back(handler); + return *handler; + } + + protected: + uint16_t port_{}; + httpd_handle_t server_{}; + static esp_err_t request_handler(httpd_req_t *r); + std::vector handlers_; + std::function on_not_found_{}; +}; + +class AsyncWebHandler { + public: + virtual ~AsyncWebHandler() {} + // NOLINTNEXTLINE(readability-identifier-naming) + virtual bool canHandle(AsyncWebServerRequest *request) { return false; } + // NOLINTNEXTLINE(readability-identifier-naming) + virtual void handleRequest(AsyncWebServerRequest *request) {} + // NOLINTNEXTLINE(readability-identifier-naming) + virtual void handleUpload(AsyncWebServerRequest *request, const std::string &filename, size_t index, uint8_t *data, + size_t len, bool final) {} + // NOLINTNEXTLINE(readability-identifier-naming) + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {} + // NOLINTNEXTLINE(readability-identifier-naming) + virtual bool isRequestHandlerTrivial() { return true; } +}; + +class AsyncEventSource; + +class AsyncEventSourceResponse { + friend class AsyncEventSource; + + public: + void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0); + + protected: + AsyncEventSourceResponse(const AsyncWebServerRequest *request, AsyncEventSource *server); + static void destroy(void *p); + AsyncEventSource *server_; + httpd_handle_t hd_{}; + int fd_{}; +}; + +using AsyncEventSourceClient = AsyncEventSourceResponse; + +class AsyncEventSource : public AsyncWebHandler { + friend class AsyncEventSourceResponse; + using connect_handler_t = std::function; + + public: + AsyncEventSource(std::string url) : url_(std::move(url)) {} + ~AsyncEventSource() override; + + // NOLINTNEXTLINE(readability-identifier-naming) + bool canHandle(AsyncWebServerRequest *request) override { + return request->method() == HTTP_GET && request->url() == this->url_; + } + // NOLINTNEXTLINE(readability-identifier-naming) + void handleRequest(AsyncWebServerRequest *request) override; + // NOLINTNEXTLINE(readability-identifier-naming) + void onConnect(connect_handler_t cb) { this->on_connect_ = std::move(cb); } + + void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0); + + protected: + std::string url_; + std::set sessions_; + connect_handler_t on_connect_{}; +}; + +class DefaultHeaders { + friend class AsyncWebServerRequest; + + public: + // NOLINTNEXTLINE(readability-identifier-naming) + void addHeader(const char *name, const char *value) { this->headers_.emplace_back(name, value); } + + // NOLINTNEXTLINE(readability-identifier-naming) + static DefaultHeaders &Instance() { + static DefaultHeaders instance; + return instance; + } + + protected: + std::vector> headers_; +}; + +} // namespace web_server_idf +} // namespace esphome + +using namespace esphome::web_server_idf; // NOLINT(google-global-names-in-headers) + +#endif // !defined(USE_ESP_IDF) diff --git a/script/build_codeowners.py b/script/build_codeowners.py index 2ee7521b91..22f3c1b4bc 100755 --- a/script/build_codeowners.py +++ b/script/build_codeowners.py @@ -7,6 +7,7 @@ from collections import defaultdict from esphome.helpers import write_file_if_changed from esphome.config import get_component, get_platform from esphome.core import CORE +from esphome.const import KEY_CORE, KEY_TARGET_FRAMEWORK parser = argparse.ArgumentParser() parser.add_argument( @@ -38,6 +39,7 @@ parts = [BASE] # Fake some directory so that get_component works CORE.config_path = str(root) +CORE.data[KEY_CORE] = {KEY_TARGET_FRAMEWORK: None} codeowners = defaultdict(list) From 5f531ac9b03f03d9c4f45cd05360b45bc57dc724 Mon Sep 17 00:00:00 2001 From: Stefan Rado Date: Wed, 12 Jul 2023 03:19:19 +0200 Subject: [PATCH 089/106] Add TT21100 touchscreen component (#4793) Co-authored-by: Rajan Patel Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + .../touchscreen_binary_sensor.cpp | 5 + .../binary_sensor/touchscreen_binary_sensor.h | 2 +- esphome/components/tt21100/__init__.py | 5 + .../tt21100/binary_sensor/__init__.py | 31 ++++ .../tt21100/binary_sensor/tt21100_button.cpp | 27 +++ .../tt21100/binary_sensor/tt21100_button.h | 28 +++ .../tt21100/touchscreen/__init__.py | 44 +++++ .../tt21100/touchscreen/tt21100.cpp | 175 ++++++++++++++++++ .../components/tt21100/touchscreen/tt21100.h | 49 +++++ tests/test8.yaml | 27 ++- 11 files changed, 392 insertions(+), 2 deletions(-) create mode 100644 esphome/components/tt21100/__init__.py create mode 100644 esphome/components/tt21100/binary_sensor/__init__.py create mode 100644 esphome/components/tt21100/binary_sensor/tt21100_button.cpp create mode 100644 esphome/components/tt21100/binary_sensor/tt21100_button.h create mode 100644 esphome/components/tt21100/touchscreen/__init__.py create mode 100644 esphome/components/tt21100/touchscreen/tt21100.cpp create mode 100644 esphome/components/tt21100/touchscreen/tt21100.h diff --git a/CODEOWNERS b/CODEOWNERS index 8ebf51ceeb..a2ddc84226 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -296,6 +296,7 @@ esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 esphome/components/touchscreen/* @jesserockz esphome/components/tsl2591/* @wjcarpenter +esphome/components/tt21100/* @kroimon esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/number/* @frankiboy1 diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp index 583392cce3..66df78b62a 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -3,6 +3,11 @@ namespace esphome { namespace touchscreen { +void TouchscreenBinarySensor::setup() { + this->parent_->register_listener(this); + this->publish_initial_state(false); +} + void TouchscreenBinarySensor::touch(TouchPoint tp) { bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index 701468aa1e..b56ae562b1 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -14,7 +14,7 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor, public TouchListener, public Parented { public: - void setup() override { this->parent_->register_listener(this); } + void setup() override; /// 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) { diff --git a/esphome/components/tt21100/__init__.py b/esphome/components/tt21100/__init__.py new file mode 100644 index 0000000000..a309d34beb --- /dev/null +++ b/esphome/components/tt21100/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@kroimon"] + +tt21100_ns = cg.esphome_ns.namespace("tt21100") diff --git a/esphome/components/tt21100/binary_sensor/__init__.py b/esphome/components/tt21100/binary_sensor/__init__.py new file mode 100644 index 0000000000..d5423a01b2 --- /dev/null +++ b/esphome/components/tt21100/binary_sensor/__init__.py @@ -0,0 +1,31 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import CONF_INDEX + +from .. import tt21100_ns +from ..touchscreen import TT21100Touchscreen, TT21100ButtonListener + +CONF_TT21100_ID = "tt21100_id" + +TT21100Button = tt21100_ns.class_( + "TT21100Button", + binary_sensor.BinarySensor, + cg.Component, + TT21100ButtonListener, + cg.Parented.template(TT21100Touchscreen), +) + +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(TT21100Button).extend( + { + cv.GenerateID(CONF_TT21100_ID): cv.use_id(TT21100Touchscreen), + cv.Required(CONF_INDEX): cv.int_range(min=0, max=3), + } +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_TT21100_ID]) + cg.add(var.set_index(config[CONF_INDEX])) diff --git a/esphome/components/tt21100/binary_sensor/tt21100_button.cpp b/esphome/components/tt21100/binary_sensor/tt21100_button.cpp new file mode 100644 index 0000000000..2d5ac22a83 --- /dev/null +++ b/esphome/components/tt21100/binary_sensor/tt21100_button.cpp @@ -0,0 +1,27 @@ +#include "tt21100_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tt21100 { + +static const char *const TAG = "tt21100.binary_sensor"; + +void TT21100Button::setup() { + this->parent_->register_button_listener(this); + this->publish_initial_state(false); +} + +void TT21100Button::dump_config() { + LOG_BINARY_SENSOR("", "TT21100 Button", this); + ESP_LOGCONFIG(TAG, " Index: %u", this->index_); +} + +void TT21100Button::update_button(uint8_t index, uint16_t state) { + if (index != this->index_) + return; + + this->publish_state(state > 0); +} + +} // namespace tt21100 +} // namespace esphome diff --git a/esphome/components/tt21100/binary_sensor/tt21100_button.h b/esphome/components/tt21100/binary_sensor/tt21100_button.h new file mode 100644 index 0000000000..90b55bb75a --- /dev/null +++ b/esphome/components/tt21100/binary_sensor/tt21100_button.h @@ -0,0 +1,28 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/tt21100/touchscreen/tt21100.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace tt21100 { + +class TT21100Button : public binary_sensor::BinarySensor, + public Component, + public TT21100ButtonListener, + public Parented { + public: + void setup() override; + void dump_config() override; + + void set_index(uint8_t index) { this->index_ = index; } + + void update_button(uint8_t index, uint16_t state) override; + + protected: + uint8_t index_; +}; + +} // namespace tt21100 +} // namespace esphome diff --git a/esphome/components/tt21100/touchscreen/__init__.py b/esphome/components/tt21100/touchscreen/__init__.py new file mode 100644 index 0000000000..d96d389e69 --- /dev/null +++ b/esphome/components/tt21100/touchscreen/__init__.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN + +from .. import tt21100_ns + +DEPENDENCIES = ["i2c"] + +TT21100Touchscreen = tt21100_ns.class_( + "TT21100Touchscreen", + touchscreen.Touchscreen, + cg.Component, + i2c.I2CDevice, +) +TT21100ButtonListener = tt21100_ns.class_("TT21100ButtonListener") + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(TT21100Touchscreen), + cv.Required(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema, + cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(i2c.i2c_device_schema(0x24)) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await touchscreen.register_touchscreen(var, config) + + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) + + if CONF_RESET_PIN in config: + rts_pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN]) + cg.add(var.set_reset_pin(rts_pin)) diff --git a/esphome/components/tt21100/touchscreen/tt21100.cpp b/esphome/components/tt21100/touchscreen/tt21100.cpp new file mode 100644 index 0000000000..28a8c2d754 --- /dev/null +++ b/esphome/components/tt21100/touchscreen/tt21100.cpp @@ -0,0 +1,175 @@ +#include "tt21100.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace tt21100 { + +static const char *const TAG = "tt21100"; + +static const uint8_t MAX_BUTTONS = 4; +static const uint8_t MAX_TOUCH_POINTS = 5; +static const uint8_t MAX_DATA_LEN = (7 + MAX_TOUCH_POINTS * 10); // 7 Header + (Points * 10 data bytes) + +struct TT21100ButtonReport { + uint16_t length; // Always 14 (0x000E) + uint8_t report_id; // Always 0x03 + uint16_t timestamp; // Number in units of 100 us + uint8_t btn_value; // Only use bit 0..3 + uint16_t btn_signal[MAX_BUTTONS]; +} __attribute__((packed)); + +struct TT21100TouchRecord { + uint8_t : 5; + uint8_t touch_type : 3; + uint8_t tip : 1; + uint8_t event_id : 2; + uint8_t touch_id : 5; + uint16_t x; + uint16_t y; + uint8_t pressure; + uint16_t major_axis_length; + uint8_t orientation; +} __attribute__((packed)); + +struct TT21100TouchReport { + uint16_t length; + uint8_t report_id; + uint16_t timestamp; + uint8_t : 2; + uint8_t large_object : 1; + uint8_t record_num : 5; + uint8_t report_counter : 2; + uint8_t : 3; + uint8_t noise_effect : 3; + TT21100TouchRecord touch_record[MAX_TOUCH_POINTS]; +} __attribute__((packed)); + +void TT21100TouchscreenStore::gpio_intr(TT21100TouchscreenStore *store) { store->touch = true; } + +float TT21100Touchscreen::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } + +void TT21100Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up TT21100 Touchscreen..."); + + // Register interrupt pin + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + this->store_.pin = this->interrupt_pin_->to_isr(); + this->interrupt_pin_->attach_interrupt(TT21100TouchscreenStore::gpio_intr, &this->store_, + gpio::INTERRUPT_FALLING_EDGE); + + // Perform reset if necessary + if (this->reset_pin_ != nullptr) { + this->reset_pin_->setup(); + this->reset_(); + } + + // Update display dimensions if they were updated during display setup + this->display_width_ = this->display_->get_width(); + this->display_height_ = this->display_->get_height(); + this->rotation_ = static_cast(this->display_->get_rotation()); + + // Trigger initial read to activate the interrupt + this->store_.touch = true; +} + +void TT21100Touchscreen::loop() { + if (!this->store_.touch) + return; + this->store_.touch = false; + + // Read report length + uint16_t data_len; + this->read((uint8_t *) &data_len, sizeof(data_len)); + + // Read report data + uint8_t data[MAX_DATA_LEN]; + if (data_len > 0 && data_len < sizeof(data)) { + this->read(data, data_len); + + if (data_len == 14) { + // Button event + auto *report = (TT21100ButtonReport *) data; + + ESP_LOGV(TAG, "Button report: Len=%d, ID=%d, Time=%5u, Value=[%u], Signal=[%04X][%04X][%04X][%04X]", + report->length, report->report_id, report->timestamp, report->btn_value, report->btn_signal[0], + report->btn_signal[1], report->btn_signal[2], report->btn_signal[3]); + + for (uint8_t i = 0; i < 4; i++) { + for (auto *listener : this->button_listeners_) + listener->update_button(i, report->btn_signal[i]); + } + + } else if (data_len >= 7) { + // Touch point event + auto *report = (TT21100TouchReport *) data; + + ESP_LOGV(TAG, + "Touch report: Len=%d, ID=%d, Time=%5u, LargeObject=%u, RecordNum=%u, RecordCounter=%u, NoiseEffect=%u", + report->length, report->report_id, report->timestamp, report->large_object, report->record_num, + report->report_counter, report->noise_effect); + + uint8_t touch_count = (data_len - (sizeof(*report) - sizeof(report->touch_record))) / sizeof(TT21100TouchRecord); + + if (touch_count == 0) { + for (auto *listener : this->touch_listeners_) + listener->release(); + return; + } + + for (int i = 0; i < touch_count; i++) { + auto *touch = &report->touch_record[i]; + + ESP_LOGV(TAG, + "Touch %d: Type=%u, Tip=%u, EventId=%u, TouchId=%u, X=%u, Y=%u, Pressure=%u, MajorAxisLen=%u, " + "Orientation=%u", + i, touch->touch_type, touch->tip, touch->event_id, touch->touch_id, touch->x, touch->y, + touch->pressure, touch->major_axis_length, touch->orientation); + + TouchPoint tp; + switch (this->rotation_) { + case ROTATE_0_DEGREES: + // Origin is top right, so mirror X by default + tp.x = this->display_width_ - touch->x; + tp.y = touch->y; + break; + case ROTATE_90_DEGREES: + tp.x = touch->y; + tp.y = touch->x; + break; + case ROTATE_180_DEGREES: + tp.x = touch->x; + tp.y = this->display_height_ - touch->y; + break; + case ROTATE_270_DEGREES: + tp.x = this->display_height_ - touch->y; + tp.y = this->display_width_ - touch->x; + break; + } + tp.id = touch->tip; + tp.state = touch->pressure; + + this->defer([this, tp]() { this->send_touch_(tp); }); + } + } + } +} + +void TT21100Touchscreen::reset_() { + if (this->reset_pin_ != nullptr) { + this->reset_pin_->digital_write(false); + delay(10); + this->reset_pin_->digital_write(true); + delay(10); + } +} + +void TT21100Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "TT21100 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" Reset Pin: ", this->reset_pin_); +} + +} // namespace tt21100 +} // namespace esphome diff --git a/esphome/components/tt21100/touchscreen/tt21100.h b/esphome/components/tt21100/touchscreen/tt21100.h new file mode 100644 index 0000000000..306360975f --- /dev/null +++ b/esphome/components/tt21100/touchscreen/tt21100.h @@ -0,0 +1,49 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace tt21100 { + +using namespace touchscreen; + +struct TT21100TouchscreenStore { + volatile bool touch; + ISRInternalGPIOPin pin; + + static void gpio_intr(TT21100TouchscreenStore *store); +}; + +class TT21100ButtonListener { + public: + virtual void update_button(uint8_t index, uint16_t state) = 0; +}; + +class TT21100Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } + + void register_button_listener(TT21100ButtonListener *listener) { this->button_listeners_.push_back(listener); } + + protected: + void reset_(); + + TT21100TouchscreenStore store_; + + InternalGPIOPin *interrupt_pin_; + GPIOPin *reset_pin_{nullptr}; + + std::vector button_listeners_; +}; + +} // namespace tt21100 +} // namespace esphome diff --git a/tests/test8.yaml b/tests/test8.yaml index 2430a0d1e6..8d031b033f 100644 --- a/tests/test8.yaml +++ b/tests/test8.yaml @@ -18,10 +18,35 @@ light: - platform: neopixelbus type: GRB variant: WS2812 - pin: 33 + pin: GPIO38 num_leds: 1 id: neopixel method: esp32_rmt name: neopixel-enable internal: false restore_mode: ALWAYS_OFF + +spi: + clk_pin: GPIO7 + mosi_pin: GPIO6 + +display: + - platform: ili9xxx + model: ili9342 + cs_pin: GPIO5 + dc_pin: GPIO4 + reset_pin: GPIO48 + +i2c: + scl: GPIO18 + sda: GPIO8 + +touchscreen: + - platform: tt21100 + interrupt_pin: GPIO3 + reset_pin: GPIO48 + +binary_sensor: + - platform: tt21100 + name: Home Button + index: 1 From 6ecc1c14d26657304dd3648468960236044a3ab0 Mon Sep 17 00:00:00 2001 From: kswt Date: Wed, 12 Jul 2023 04:28:48 +0300 Subject: [PATCH 090/106] tuya_light: fix float->int conversion while setting color temperature (#5067) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: kswt --- esphome/components/tuya/light/tuya_light.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 7b7a974de2..66931767b2 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -168,7 +168,7 @@ void TuyaLight::write_state(light::LightState *state) { if (brightness > 0.0f || !color_interlock_) { if (this->color_temperature_id_.has_value()) { - uint32_t color_temp_int = static_cast(color_temperature * this->color_temperature_max_value_); + uint32_t color_temp_int = static_cast(roundf(color_temperature * this->color_temperature_max_value_)); if (this->color_temperature_invert_) { color_temp_int = this->color_temperature_max_value_ - color_temp_int; } From 8a9352939a55ebef4c13cfb55c2d27470f78002c Mon Sep 17 00:00:00 2001 From: Stefan Klug Date: Wed, 12 Jul 2023 03:29:38 +0200 Subject: [PATCH 091/106] Fix typo in mpu6050.cpp (#5086) --- 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 51e3ec2383..64fcd3a2a8 100644 --- a/esphome/components/mpu6050/mpu6050.cpp +++ b/esphome/components/mpu6050/mpu6050.cpp @@ -77,7 +77,7 @@ void MPU6050Component::setup() { accel_config &= 0b11100111; accel_config |= (MPU6050_RANGE_2G << 3); ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); - if (!this->write_byte(MPU6050_REGISTER_GYRO_CONFIG, gyro_config)) { + if (!this->write_byte(MPU6050_REGISTER_ACCEL_CONFIG, accel_config)) { this->mark_failed(); return; } From cf65bd8ad742f3fe8f6801f89d1a579b0b2d6715 Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" Date: Tue, 11 Jul 2023 21:38:52 -0400 Subject: [PATCH 092/106] airthings_wave: Battery level reporting (#4979) --- CODEOWNERS | 2 +- .../airthings_wave_base/__init__.py | 60 ++++--- .../airthings_wave_base.cpp | 150 ++++++++++++++++-- .../airthings_wave_base/airthings_wave_base.h | 48 +++++- .../airthings_wave_mini.cpp | 14 +- .../airthings_wave_mini/airthings_wave_mini.h | 5 +- .../airthings_wave_plus.cpp | 14 +- .../airthings_wave_plus/airthings_wave_plus.h | 5 +- .../components/airthings_wave_plus/sensor.py | 12 +- tests/test2.yaml | 6 + 10 files changed, 258 insertions(+), 58 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index a2ddc84226..641911e84f 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -17,7 +17,7 @@ esphome/components/adc/* @esphome/core esphome/components/adc128s102/* @DeerMaximum esphome/components/addressable_light/* @justfalter esphome/components/airthings_ble/* @jeromelaban -esphome/components/airthings_wave_base/* @jeromelaban @ncareau +esphome/components/airthings_wave_base/* @jeromelaban @kpfleming @ncareau esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/alarm_control_panel/* @grahambrown11 diff --git a/esphome/components/airthings_wave_base/__init__.py b/esphome/components/airthings_wave_base/__init__.py index c935ce108a..d9b97f1c8d 100644 --- a/esphome/components/airthings_wave_base/__init__.py +++ b/esphome/components/airthings_wave_base/__init__.py @@ -3,26 +3,31 @@ import esphome.config_validation as cv from esphome.components import sensor, ble_client from esphome.const import ( - DEVICE_CLASS_HUMIDITY, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_PRESSURE, - STATE_CLASS_MEASUREMENT, - UNIT_PERCENT, - UNIT_CELSIUS, - UNIT_HECTOPASCAL, + CONF_BATTERY_VOLTAGE, CONF_HUMIDITY, - CONF_TVOC, CONF_PRESSURE, CONF_TEMPERATURE, + CONF_TVOC, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_HECTOPASCAL, UNIT_PARTS_PER_BILLION, - ICON_RADIATOR, + UNIT_PERCENT, + UNIT_VOLT, ) -CODEOWNERS = ["@ncareau", "@jeromelaban"] +CODEOWNERS = ["@ncareau", "@jeromelaban", "@kpfleming"] DEPENDENCIES = ["ble_client"] +CONF_BATTERY_UPDATE_INTERVAL = "battery_update_interval" + airthings_wave_base_ns = cg.esphome_ns.namespace("airthings_wave_base") AirthingsWaveBase = airthings_wave_base_ns.class_( "AirthingsWaveBase", cg.PollingComponent, ble_client.BLEClientNode @@ -34,9 +39,9 @@ BASE_SCHEMA = ( { cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, - accuracy_decimals=0, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, @@ -52,11 +57,21 @@ BASE_SCHEMA = ( ), cv.Optional(CONF_TVOC): sensor.sensor_schema( unit_of_measurement=UNIT_PARTS_PER_BILLION, - icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional( + CONF_BATTERY_UPDATE_INTERVAL, + default="24h", + ): cv.update_interval, } ) .extend(cv.polling_component_schema("5min")) @@ -69,15 +84,20 @@ async def wave_base_to_code(var, config): await ble_client.register_ble_node(var, config) - if CONF_HUMIDITY in config: - sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + if config_humidity := config.get(CONF_HUMIDITY): + sens = await sensor.new_sensor(config_humidity) cg.add(var.set_humidity(sens)) - if CONF_TEMPERATURE in config: - sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + if config_temperature := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(config_temperature) cg.add(var.set_temperature(sens)) - if CONF_PRESSURE in config: - sens = await sensor.new_sensor(config[CONF_PRESSURE]) + if config_pressure := config.get(CONF_PRESSURE): + sens = await sensor.new_sensor(config_pressure) cg.add(var.set_pressure(sens)) - if CONF_TVOC in config: - sens = await sensor.new_sensor(config[CONF_TVOC]) + if config_tvoc := config.get(CONF_TVOC): + sens = await sensor.new_sensor(config_tvoc) cg.add(var.set_tvoc(sens)) + if config_battery_voltage := config.get(CONF_BATTERY_VOLTAGE): + sens = await sensor.new_sensor(config_battery_voltage) + cg.add(var.set_battery_voltage(sens)) + if config_battery_update_interval := config.get(CONF_BATTERY_UPDATE_INTERVAL): + cg.add(var.set_battery_update_interval(config_battery_update_interval)) diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.cpp b/esphome/components/airthings_wave_base/airthings_wave_base.cpp index 349d8d58eb..eff466f413 100644 --- a/esphome/components/airthings_wave_base/airthings_wave_base.cpp +++ b/esphome/components/airthings_wave_base/airthings_wave_base.cpp @@ -1,5 +1,8 @@ #include "airthings_wave_base.h" +// All information related to reading battery information came from the sensors.airthings_wave +// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave) + #ifdef USE_ESP32 namespace esphome { @@ -18,22 +21,26 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } case ESP_GATTC_DISCONNECT_EVT: { + this->handle_ = 0; + this->acp_handle_ = 0; + this->cccd_handle_ = 0; ESP_LOGW(TAG, "Disconnected!"); break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - this->handle_ = 0; - auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); - if (chr == nullptr) { - ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), - this->sensors_data_characteristic_uuid_.to_string().c_str()); - break; + if (this->request_read_values_()) { + if (!this->read_battery_next_update_) { + this->node_state = espbt::ClientState::ESTABLISHED; + } else { + // delay setting node_state to ESTABLISHED until confirmation of the notify registration + this->request_battery_(); + } } - this->handle_ = chr->handle; - this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; - this->request_read_values_(); + // ensure that the client will be disconnected even if no responses arrive + this->set_response_timeout_(); + break; } @@ -50,6 +57,20 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt break; } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::ESTABLISHED; + break; + } + + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->parent()->get_conn_id()) + break; + if (param->notify.handle == this->acp_handle_) { + this->read_battery_(param->notify.value, param->notify.value_len); + } + break; + } + default: break; } @@ -58,7 +79,7 @@ void AirthingsWaveBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt bool AirthingsWaveBase::is_valid_voc_value_(uint16_t voc) { return 0 <= voc && voc <= 16383; } void AirthingsWaveBase::update() { - if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { + if (this->node_state != espbt::ClientState::ESTABLISHED) { if (!this->parent()->enabled) { ESP_LOGW(TAG, "Reconnecting to device"); this->parent()->set_enabled(true); @@ -69,12 +90,119 @@ void AirthingsWaveBase::update() { } } -void AirthingsWaveBase::request_read_values_() { +bool AirthingsWaveBase::request_read_values_() { + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->sensors_data_characteristic_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), + this->sensors_data_characteristic_uuid_.to_string().c_str()); + return false; + } + + this->handle_ = chr->handle; + 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); + return false; } + + this->response_pending_(); + return true; +} + +bool AirthingsWaveBase::request_battery_() { + uint8_t battery_command = ACCESS_CONTROL_POINT_COMMAND; + uint8_t cccd_value[2] = {1, 0}; + + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->access_control_point_characteristic_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "No access control point characteristic found at service %s char %s", + this->service_uuid_.to_string().c_str(), + this->access_control_point_characteristic_uuid_.to_string().c_str()); + return false; + } + + auto *descr = this->parent()->get_descriptor(this->service_uuid_, this->access_control_point_characteristic_uuid_, + CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID); + if (descr == nullptr) { + ESP_LOGW(TAG, "No CCC descriptor found at service %s char %s", this->service_uuid_.to_string().c_str(), + this->access_control_point_characteristic_uuid_.to_string().c_str()); + return false; + } + + auto reg_status = + esp_ble_gattc_register_for_notify(this->parent()->get_gattc_if(), this->parent()->get_remote_bda(), chr->handle); + if (reg_status) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", reg_status); + return false; + } + + this->acp_handle_ = chr->handle; + this->cccd_handle_ = descr->handle; + + auto descr_status = + esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->cccd_handle_, + 2, cccd_value, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + if (descr_status) { + ESP_LOGW(TAG, "Error sending CCC descriptor write request, status=%d", descr_status); + return false; + } + + auto chr_status = + esp_ble_gattc_write_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(), this->acp_handle_, 1, + &battery_command, ESP_GATT_WRITE_TYPE_RSP, ESP_GATT_AUTH_REQ_NONE); + if (chr_status) { + ESP_LOGW(TAG, "Error sending read request for battery, status=%d", chr_status); + return false; + } + + this->response_pending_(); + return true; +} + +void AirthingsWaveBase::read_battery_(uint8_t *raw_value, uint16_t value_len) { + auto *value = (AccessControlPointResponse *) (&raw_value[2]); + + if ((value_len >= (sizeof(AccessControlPointResponse) + 2)) && (raw_value[0] == ACCESS_CONTROL_POINT_COMMAND)) { + ESP_LOGD(TAG, "Battery received: %u mV", (unsigned int) value->battery); + + if (this->battery_voltage_ != nullptr) { + float voltage = value->battery / 1000.0f; + + this->battery_voltage_->publish_state(voltage); + } + + // read the battery again at the configured update interval + if (this->battery_update_interval_ != this->update_interval_) { + this->read_battery_next_update_ = false; + this->set_timeout("battery", this->battery_update_interval_, + [this]() { this->read_battery_next_update_ = true; }); + } + } + + this->response_received_(); +} + +void AirthingsWaveBase::response_pending_() { + this->responses_pending_++; + this->set_response_timeout_(); +} + +void AirthingsWaveBase::response_received_() { + if (--this->responses_pending_ == 0) { + // This instance must not stay connected + // so other clients can connect to it (e.g. the + // mobile app). + this->parent()->set_enabled(false); + } +} + +void AirthingsWaveBase::set_response_timeout_() { + this->set_timeout("response_timeout", 30 * 1000, [this]() { + this->responses_pending_ = 1; + this->response_received_(); + }); } } // namespace airthings_wave_base diff --git a/esphome/components/airthings_wave_base/airthings_wave_base.h b/esphome/components/airthings_wave_base/airthings_wave_base.h index 68c0b3497d..1dc2e1f71f 100644 --- a/esphome/components/airthings_wave_base/airthings_wave_base.h +++ b/esphome/components/airthings_wave_base/airthings_wave_base.h @@ -1,5 +1,8 @@ #pragma once +// All information related to reading battery levels came from the sensors.airthings_wave +// project by Sverre Hamre (https://github.com/sverrham/sensor.airthings_wave) + #ifdef USE_ESP32 #include @@ -14,6 +17,11 @@ namespace esphome { namespace airthings_wave_base { +namespace espbt = esphome::esp32_ble_tracker; + +static const uint8_t ACCESS_CONTROL_POINT_COMMAND = 0x6d; +static const auto CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_UUID = espbt::ESPBTUUID::from_uint16(0x2902); + class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientNode { public: AirthingsWaveBase() = default; @@ -27,21 +35,53 @@ class AirthingsWaveBase : public PollingComponent, public ble_client::BLEClientN void set_humidity(sensor::Sensor *humidity) { humidity_sensor_ = humidity; } void set_pressure(sensor::Sensor *pressure) { pressure_sensor_ = pressure; } void set_tvoc(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; } + void set_battery_voltage(sensor::Sensor *voltage) { + battery_voltage_ = voltage; + this->read_battery_next_update_ = true; + } + void set_battery_update_interval(uint32_t interval) { battery_update_interval_ = interval; } protected: bool is_valid_voc_value_(uint16_t voc); - virtual void read_sensors(uint8_t *value, uint16_t value_len) = 0; - void request_read_values_(); + bool request_read_values_(); + virtual void read_sensors(uint8_t *raw_value, uint16_t value_len) = 0; sensor::Sensor *temperature_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *pressure_sensor_{nullptr}; sensor::Sensor *tvoc_sensor_{nullptr}; + sensor::Sensor *battery_voltage_{nullptr}; uint16_t handle_; - esp32_ble_tracker::ESPBTUUID service_uuid_; - esp32_ble_tracker::ESPBTUUID sensors_data_characteristic_uuid_; + espbt::ESPBTUUID service_uuid_; + espbt::ESPBTUUID sensors_data_characteristic_uuid_; + + uint16_t acp_handle_{0}; + uint16_t cccd_handle_{0}; + espbt::ESPBTUUID access_control_point_characteristic_uuid_; + + uint8_t responses_pending_{0}; + void response_pending_(); + void response_received_(); + void set_response_timeout_(); + + // default to *not* reading battery voltage from the device; the + // set_* function for the battery sensor will set this to 'true' + bool read_battery_next_update_{false}; + bool request_battery_(); + void read_battery_(uint8_t *raw_value, uint16_t value_len); + uint32_t battery_update_interval_{}; + + struct AccessControlPointResponse { + uint32_t unused1; + uint8_t unused2; + uint8_t illuminance; + uint8_t unused3[10]; + uint16_t unused4[4]; + uint16_t battery; + uint16_t unused5; + }; }; } // namespace airthings_wave_base diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 331a13434f..873826d06c 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -26,12 +26,9 @@ void AirthingsWaveMini::read_sensors(uint8_t *raw_value, uint16_t value_len) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { this->tvoc_sensor_->publish_state(value->voc); } - - // This instance must not stay connected - // so other clients can connect to it (e.g. the - // mobile app). - this->parent()->set_enabled(false); } + + this->response_received_(); } void AirthingsWaveMini::dump_config() { @@ -42,11 +39,14 @@ void AirthingsWaveMini::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); + LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_); } AirthingsWaveMini::AirthingsWaveMini() { - this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); - this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); + this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID); + this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); + this->access_control_point_characteristic_uuid_ = + espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID); } } // namespace airthings_wave_mini diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.h b/esphome/components/airthings_wave_mini/airthings_wave_mini.h index ec4fd23e60..825ddbdc69 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.h +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.h @@ -7,8 +7,11 @@ namespace esphome { namespace airthings_wave_mini { +namespace espbt = esphome::esp32_ble_tracker; + static const char *const SERVICE_UUID = "b42e3882-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e3b98-ade7-11e4-89d3-123b93f75cba"; +static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e3ef4-ade7-11e4-89d3-123b93f75cba"; class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase { public: @@ -17,7 +20,7 @@ class AirthingsWaveMini : public airthings_wave_base::AirthingsWaveBase { void dump_config() override; protected: - void read_sensors(uint8_t *value, uint16_t value_len) override; + void read_sensors(uint8_t *raw_value, uint16_t value_len) override; struct WaveMiniReadings { uint16_t unused01; diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index acd3a4316d..e44d5fbcaa 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -43,15 +43,12 @@ void AirthingsWavePlus::read_sensors(uint8_t *raw_value, uint16_t value_len) { if ((this->tvoc_sensor_ != nullptr) && this->is_valid_voc_value_(value->voc)) { this->tvoc_sensor_->publish_state(value->voc); } - - // This instance must not stay connected - // so other clients can connect to it (e.g. the - // mobile app). - this->parent()->set_enabled(false); } else { ESP_LOGE(TAG, "Invalid payload version (%d != 1, newer version or not a Wave Plus?)", value->version); } } + + this->response_received_(); } bool AirthingsWavePlus::is_valid_radon_value_(uint16_t radon) { return 0 <= radon && radon <= 16383; } @@ -66,6 +63,7 @@ void AirthingsWavePlus::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); LOG_SENSOR(" ", "TVOC", this->tvoc_sensor_); + LOG_SENSOR(" ", "Battery Voltage", this->battery_voltage_); LOG_SENSOR(" ", "Radon", this->radon_sensor_); LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); @@ -73,8 +71,10 @@ void AirthingsWavePlus::dump_config() { } AirthingsWavePlus::AirthingsWavePlus() { - this->service_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID); - this->sensors_data_characteristic_uuid_ = esp32_ble_tracker::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); + this->service_uuid_ = espbt::ESPBTUUID::from_raw(SERVICE_UUID); + this->sensors_data_characteristic_uuid_ = espbt::ESPBTUUID::from_raw(CHARACTERISTIC_UUID); + this->access_control_point_characteristic_uuid_ = + espbt::ESPBTUUID::from_raw(ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID); } } // namespace airthings_wave_plus diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.h b/esphome/components/airthings_wave_plus/airthings_wave_plus.h index 4acfb9279a..23c8cbb166 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.h +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.h @@ -7,8 +7,11 @@ namespace esphome { namespace airthings_wave_plus { +namespace espbt = esphome::esp32_ble_tracker; + static const char *const SERVICE_UUID = "b42e1c08-ade7-11e4-89d3-123b93f75cba"; static const char *const CHARACTERISTIC_UUID = "b42e2a68-ade7-11e4-89d3-123b93f75cba"; +static const char *const ACCESS_CONTROL_POINT_CHARACTERISTIC_UUID = "b42e2d06-ade7-11e4-89d3-123b93f75cba"; class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { public: @@ -24,7 +27,7 @@ class AirthingsWavePlus : public airthings_wave_base::AirthingsWaveBase { bool is_valid_radon_value_(uint16_t radon); bool is_valid_co2_value_(uint16_t co2); - void read_sensors(uint8_t *value, uint16_t value_len) override; + void read_sensors(uint8_t *raw_value, uint16_t value_len) override; sensor::Sensor *radon_sensor_{nullptr}; sensor::Sensor *radon_long_term_sensor_{nullptr}; diff --git a/esphome/components/airthings_wave_plus/sensor.py b/esphome/components/airthings_wave_plus/sensor.py index a5903b1d42..643a2bfb68 100644 --- a/esphome/components/airthings_wave_plus/sensor.py +++ b/esphome/components/airthings_wave_plus/sensor.py @@ -53,12 +53,12 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await airthings_wave_base.wave_base_to_code(var, config) - if CONF_RADON in config: - sens = await sensor.new_sensor(config[CONF_RADON]) + if config_radon := config.get(CONF_RADON): + sens = await sensor.new_sensor(config_radon) cg.add(var.set_radon(sens)) - if CONF_RADON_LONG_TERM in config: - sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) + if config_radon_long_term := config.get(CONF_RADON_LONG_TERM): + sens = await sensor.new_sensor(config_radon_long_term) cg.add(var.set_radon_long_term(sens)) - if CONF_CO2 in config: - sens = await sensor.new_sensor(config[CONF_CO2]) + if config_co2 := config.get(CONF_CO2): + sens = await sensor.new_sensor(config_co2) cg.add(var.set_co2(sens)) diff --git a/tests/test2.yaml b/tests/test2.yaml index 675fae6cf3..31fd840f5c 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -316,6 +316,7 @@ sensor: platform: airthings_wave_plus ble_client_id: airthings01 update_interval: 5min + battery_update_interval: 12h temperature: name: Wave Plus Temperature radon: @@ -330,10 +331,13 @@ sensor: name: Wave Plus CO2 tvoc: name: Wave Plus VOC + battery_voltage: + name: Wave Plus Battery Voltage - id: airthingswm platform: airthings_wave_mini ble_client_id: airthingsmini01 update_interval: 5min + battery_update_interval: 12h temperature: name: Wave Mini Temperature humidity: @@ -342,6 +346,8 @@ sensor: name: Wave Mini Pressure tvoc: name: Wave Mini VOC + battery_voltage: + name: Wave Mini Battery Voltage - platform: ina260 address: 0x40 current: From e0fd8cd8508eb95acd786a57bb7b5fb2b3c9efd3 Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 12 Jul 2023 04:02:53 +0100 Subject: [PATCH 093/106] Add support for Grove tb6612 fng (#4797) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + .../components/grove_i2c_motor/__init__.py | 152 +++++++++++++ .../grove_i2c_motor/grove_i2c_motor.cpp | 171 ++++++++++++++ .../grove_i2c_motor/grove_i2c_motor.h | 208 ++++++++++++++++++ tests/test3.1.yaml | 15 ++ 5 files changed, 547 insertions(+) create mode 100644 esphome/components/grove_i2c_motor/__init__.py create mode 100644 esphome/components/grove_i2c_motor/grove_i2c_motor.cpp create mode 100644 esphome/components/grove_i2c_motor/grove_i2c_motor.h diff --git a/CODEOWNERS b/CODEOWNERS index 641911e84f..d9303523df 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -103,6 +103,7 @@ esphome/components/gp8403/* @jesserockz esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/graph/* @synco +esphome/components/grove_i2c_motor/* @max246 esphome/components/growatt_solar/* @leeuwte esphome/components/haier/* @paveldn esphome/components/havells_solar/* @sourabhjaiswal diff --git a/esphome/components/grove_i2c_motor/__init__.py b/esphome/components/grove_i2c_motor/__init__.py new file mode 100644 index 0000000000..f7888f7293 --- /dev/null +++ b/esphome/components/grove_i2c_motor/__init__.py @@ -0,0 +1,152 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import i2c + +from esphome.const import ( + CONF_ID, + CONF_CHANNEL, + CONF_SPEED, + CONF_DIRECTION, +) + +DEPENDENCIES = ["i2c"] + +CODEOWNERS = ["@max246"] + +grove_i2c_motor_ns = cg.esphome_ns.namespace("grove_i2c_motor") +GROVE_TB6612FNG = grove_i2c_motor_ns.class_( + "GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice +) +GROVETB6612FNGMotorRunAction = grove_i2c_motor_ns.class_( + "GROVETB6612FNGMotorRunAction", automation.Action +) +GROVETB6612FNGMotorBrakeAction = grove_i2c_motor_ns.class_( + "GROVETB6612FNGMotorBrakeAction", automation.Action +) +GROVETB6612FNGMotorStopAction = grove_i2c_motor_ns.class_( + "GROVETB6612FNGMotorStopAction", automation.Action +) +GROVETB6612FNGMotorStandbyAction = grove_i2c_motor_ns.class_( + "GROVETB6612FNGMotorStandbyAction", automation.Action +) +GROVETB6612FNGMotorNoStandbyAction = grove_i2c_motor_ns.class_( + "GROVETB6612FNGMotorNoStandbyAction", automation.Action +) + +DIRECTION_TYPE = { + "FORWARD": 1, + "BACKWARD": 2, +} + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(GROVE_TB6612FNG), + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x14)) +) + + +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) + + +@automation.register_action( + "grove_i2c_motor.run", + GROVETB6612FNGMotorRunAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG), + cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)), + cv.Required(CONF_SPEED): cv.templatable(cv.int_range(min=0, max=255)), + cv.Required(CONF_DIRECTION): cv.enum(DIRECTION_TYPE, upper=True), + } + ), +) +async def grove_i2c_motor_run_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + template_speed = await cg.templatable(config[CONF_SPEED], args, cg.uint16) + template_speed = ( + template_speed if config[CONF_DIRECTION] == "FORWARD" else -template_speed + ) + cg.add(var.set_channel(template_channel)) + cg.add(var.set_speed(template_speed)) + return var + + +@automation.register_action( + "grove_i2c_motor.break", + GROVETB6612FNGMotorBrakeAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG), + cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)), + } + ), +) +async def grove_i2c_motor_break_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + cg.add(var.set_channel(template_channel)) + return var + + +@automation.register_action( + "grove_i2c_motor.stop", + GROVETB6612FNGMotorStopAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG), + cv.Required(CONF_CHANNEL): cv.templatable(cv.int_range(min=0, max=1)), + } + ), +) +async def grove_i2c_motor_stop_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + template_channel = await cg.templatable(config[CONF_CHANNEL], args, int) + cg.add(var.set_channel(template_channel)) + return var + + +@automation.register_action( + "grove_i2c_motor.standby", + GROVETB6612FNGMotorStandbyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG), + } + ), +) +async def grove_i2c_motor_standby_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + return var + + +@automation.register_action( + "grove_i2c_motor.no_standby", + GROVETB6612FNGMotorNoStandbyAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(GROVE_TB6612FNG), + } + ), +) +async def grove_i2c_motor_no_standby_to_code(config, action_id, template_arg, args): + var = cg.new_Pvariable(action_id, template_arg) + await cg.register_parented(var, config[CONF_ID]) + + return var diff --git a/esphome/components/grove_i2c_motor/grove_i2c_motor.cpp b/esphome/components/grove_i2c_motor/grove_i2c_motor.cpp new file mode 100644 index 0000000000..669114aec0 --- /dev/null +++ b/esphome/components/grove_i2c_motor/grove_i2c_motor.cpp @@ -0,0 +1,171 @@ +#include "grove_i2c_motor.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace grove_i2c_motor { + +static const char *const TAG = "GroveMotorDriveTB6612FNG"; + +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_BRAKE = 0x00; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STOP = 0x01; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_CW = 0x02; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_CCW = 0x03; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STANDBY = 0x04; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_NOT_STANDBY = 0x05; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN = 0x06; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_STOP = 0x07; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_KEEP_RUN = 0x08; +static const uint8_t GROVE_MOTOR_DRIVER_I2C_CMD_SET_ADDR = 0x11; + +void GroveMotorDriveTB6612FNG::dump_config() { + ESP_LOGCONFIG(TAG, "GroveMotorDriveTB6612FNG:"); + LOG_I2C_DEVICE(this); +} + +void GroveMotorDriveTB6612FNG::setup() { + ESP_LOGCONFIG(TAG, "Setting up Grove Motor Drive TB6612FNG ..."); + if (!this->standby()) { + this->mark_failed(); + return; + } +} + +bool GroveMotorDriveTB6612FNG::standby() { + uint8_t status = 0; + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STANDBY, &status, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Set standby failed!"); + this->status_set_warning(); + return false; + } + return true; +} + +bool GroveMotorDriveTB6612FNG::not_standby() { + uint8_t status = 0; + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_NOT_STANDBY, &status, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Set not standby failed!"); + this->status_set_warning(); + return false; + } + return true; +} + +void GroveMotorDriveTB6612FNG::set_i2c_addr(uint8_t addr) { + if (addr == 0x00 || addr >= 0x80) { + return; + } + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_SET_ADDR, &addr, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Set new i2c address failed!"); + this->status_set_warning(); + return; + } + this->set_i2c_address(addr); +} + +void GroveMotorDriveTB6612FNG::dc_motor_run(uint8_t channel, int16_t speed) { + speed = clamp(speed, -255, 255); + + buffer_[0] = channel; + if (speed >= 0) { + buffer_[1] = speed; + } else { + buffer_[1] = (uint8_t) (-speed); + } + + if (speed >= 0) { + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_CW, buffer_, 2) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Run motor failed!"); + this->status_set_warning(); + return; + } + } else { + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_CCW, buffer_, 2) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Run motor failed!"); + this->status_set_warning(); + return; + } + } +} + +void GroveMotorDriveTB6612FNG::dc_motor_brake(uint8_t channel) { + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_BRAKE, &channel, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Break motor failed!"); + this->status_set_warning(); + return; + } +} + +void GroveMotorDriveTB6612FNG::dc_motor_stop(uint8_t channel) { + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STOP, &channel, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Stop dc motor failed!"); + this->status_set_warning(); + return; + } +} + +void GroveMotorDriveTB6612FNG::stepper_run(StepperModeTypeT mode, int16_t steps, uint16_t rpm) { + uint8_t cw = 0; + // 0.1ms_per_step + uint16_t ms_per_step = 0; + + if (steps > 0) { + cw = 1; + } + // stop + else if (steps == 0) { + this->stepper_stop(); + return; + } else if (steps == INT16_MIN) { + steps = INT16_MAX; + } else { + steps = -steps; + } + + rpm = clamp(rpm, 1, 300); + + ms_per_step = (uint16_t) (3000.0 / (float) rpm); + buffer_[0] = mode; + buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw + buffer_[2] = steps; + buffer_[3] = (steps >> 8); + buffer_[4] = ms_per_step; + buffer_[5] = (ms_per_step >> 8); + + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_RUN, buffer_, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Run stepper failed!"); + this->status_set_warning(); + return; + } +} + +void GroveMotorDriveTB6612FNG::stepper_stop() { + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_STOP, nullptr, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Send stop stepper failed!"); + this->status_set_warning(); + return; + } +} + +void GroveMotorDriveTB6612FNG::stepper_keep_run(StepperModeTypeT mode, uint16_t rpm, bool is_cw) { + // 4=>infinite ccw 5=>infinite cw + uint8_t cw = (is_cw) ? 5 : 4; + // 0.1ms_per_step + uint16_t ms_per_step = 0; + + rpm = clamp(rpm, 1, 300); + ms_per_step = (uint16_t) (3000.0 / (float) rpm); + + buffer_[0] = mode; + buffer_[1] = cw; //(cw=1) => cw; (cw=0) => ccw + buffer_[2] = ms_per_step; + buffer_[3] = (ms_per_step >> 8); + + if (this->write_register(GROVE_MOTOR_DRIVER_I2C_CMD_STEPPER_KEEP_RUN, buffer_, 4) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Write stepper keep run failed"); + this->status_set_warning(); + return; + } +} +} // namespace grove_i2c_motor +} // namespace esphome diff --git a/esphome/components/grove_i2c_motor/grove_i2c_motor.h b/esphome/components/grove_i2c_motor/grove_i2c_motor.h new file mode 100644 index 0000000000..8a0db77e70 --- /dev/null +++ b/esphome/components/grove_i2c_motor/grove_i2c_motor.h @@ -0,0 +1,208 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/core/automation.h" +//#include "esphome/core/helpers.h" + +/* + Grove_Motor_Driver_TB6612FNG.h + A library for the Grove - Motor Driver(TB6612FNG) + Copyright (c) 2018 seeed technology co., ltd. + Website : www.seeed.cc + Author : Jerry Yip + Create Time: 2018-06 + Version : 0.1 + Change Log : + The MIT License (MIT) + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +namespace esphome { +namespace grove_i2c_motor { + +enum MotorChannelTypeT { + MOTOR_CHA = 0, + MOTOR_CHB = 1, +}; + +enum StepperModeTypeT { + FULL_STEP = 0, + WAVE_DRIVE = 1, + HALF_STEP = 2, + MICRO_STEPPING = 3, +}; + +class GroveMotorDriveTB6612FNG : public Component, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + /************************************************************* + Description + Enter standby mode. Normally you don't need to call this, except that + you have called notStandby() before. + Parameter + Null. + Return + True/False. + *************************************************************/ + bool standby(); + + /************************************************************* + Description + Exit standby mode. Motor driver does't do any action at this mode. + Parameter + Null. + Return + True/False. + *************************************************************/ + bool not_standby(); + + /************************************************************* + Description + Set an new I2C address. + Parameter + addr: 0x01~0x7f + Return + Null. + *************************************************************/ + void set_i2c_addr(uint8_t addr); + + /************************************************************* + Description + Drive a motor. + Parameter + chl: MOTOR_CHA or MOTOR_CHB + speed: -255~255, if speed > 0, motor moves clockwise. + Note that there is always a starting speed(a starting voltage) for motor. + If the input voltage is 5V, the starting speed should larger than 100 or + smaller than -100. + Return + Null. + *************************************************************/ + void dc_motor_run(uint8_t channel, int16_t speed); + + /************************************************************* + Description + Brake, stop the motor immediately + Parameter + chl: MOTOR_CHA or MOTOR_CHB + Return + Null. + *************************************************************/ + void dc_motor_brake(uint8_t channel); + + /************************************************************* + Description + Stop the motor slowly. + Parameter + chl: MOTOR_CHA or MOTOR_CHB + Return + Null. + *************************************************************/ + void dc_motor_stop(uint8_t channel); + + /************************************************************* + Description + Drive a stepper. + Parameter + mode: 4 driver mode: FULL_STEP,WAVE_DRIVE, HALF_STEP, MICRO_STEPPING, + for more information: https://en.wikipedia.org/wiki/Stepper_motor#/media/File:Drive.png + steps: The number of steps to run, range from -32768 to 32767. + When steps = 0, the stepper stops. + When steps > 0, the stepper runs clockwise. When steps < 0, the stepper runs anticlockwise. + rpm: Revolutions per minute, the speed of a stepper, range from 1 to 300. + Note that high rpm will lead to step lose, so rpm should not be larger than 150. + Return + Null. + *************************************************************/ + void stepper_run(StepperModeTypeT mode, int16_t steps, uint16_t rpm); + + /************************************************************* + Description + Stop a stepper. + Parameter + Null. + Return + Null. + *************************************************************/ + void stepper_stop(); + + // keeps moving(direction same as the last move, default to clockwise) + /************************************************************* + Description + Keep a stepper running. + Parameter + mode: 4 driver mode: FULL_STEP,WAVE_DRIVE, HALF_STEP, MICRO_STEPPING, + for more information: https://en.wikipedia.org/wiki/Stepper_motor#/media/File:Drive.png + rpm: Revolutions per minute, the speed of a stepper, range from 1 to 300. + Note that high rpm will lead to step lose, so rpm should not be larger than 150. + is_cw: Set the running direction, true for clockwise and false for anti-clockwise. + Return + Null. + *************************************************************/ + void stepper_keep_run(StepperModeTypeT mode, uint16_t rpm, bool is_cw); + + private: + uint8_t buffer_[16]; +}; + +template +class GROVETB6612FNGMotorRunAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, channel) + TEMPLATABLE_VALUE(uint16_t, speed) + + void play(Ts... x) override { + auto channel = this->channel_.value(x...); + auto speed = this->speed_.value(x...); + this->parent_->dc_motor_run(channel, speed); + } +}; + +template +class GROVETB6612FNGMotorBrakeAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, channel) + + void play(Ts... x) override { this->parent_->dc_motor_brake(this->channel_.value(x...)); } +}; + +template +class GROVETB6612FNGMotorStopAction : public Action, public Parented { + public: + TEMPLATABLE_VALUE(uint8_t, channel) + + void play(Ts... x) override { this->parent_->dc_motor_stop(this->channel_.value(x...)); } +}; + +template +class GROVETB6612FNGMotorStandbyAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->standby(); } +}; + +template +class GROVETB6612FNGMotorNoStandbyAction : public Action, public Parented { + public: + void play(Ts... x) override { this->parent_->not_standby(); } +}; + +} // namespace grove_i2c_motor +} // namespace esphome diff --git a/tests/test3.1.yaml b/tests/test3.1.yaml index 0daf4e9671..a003c804c9 100644 --- a/tests/test3.1.yaml +++ b/tests/test3.1.yaml @@ -303,6 +303,10 @@ sm2135: rgb_current: 20mA cw_current: 60mA +grove_i2c_motor: + id: test_motor + address: 0x14 + switch: - platform: template name: mpr121_toggle @@ -353,6 +357,17 @@ switch: Content-Type: application/json body: Some data verify_ssl: false + - platform: template + name: open_vent + id: open_vent + optimistic: True + on_turn_on: + then: + - grove_i2c_motor.run: + channel: 1 + speed: 255 + direction: BACKWARD + id: test_motor custom_component: From ec37dece1294b117ed0833772bbc0d1bfdeb8951 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:12:48 +1000 Subject: [PATCH 094/106] Add MCP2515 12MHz xtal support (#5089) --- esphome/components/mcp2515/canbus.py | 1 + esphome/components/mcp2515/mcp2515.cpp | 98 ++++++++++++++++++++--- esphome/components/mcp2515/mcp2515.h | 2 +- esphome/components/mcp2515/mcp2515_defs.h | 56 +++++++++++++ 4 files changed, 143 insertions(+), 14 deletions(-) diff --git a/esphome/components/mcp2515/canbus.py b/esphome/components/mcp2515/canbus.py index c410c1af69..4353cd7bc6 100644 --- a/esphome/components/mcp2515/canbus.py +++ b/esphome/components/mcp2515/canbus.py @@ -16,6 +16,7 @@ McpMode = mcp2515_ns.enum("CANCTRL_REQOP_MODE") CAN_CLOCK = { "8MHZ": CanClock.MCP_8MHZ, + "12MHZ": CanClock.MCP_12MHZ, "16MHZ": CanClock.MCP_16MHZ, "20MHZ": CanClock.MCP_20MHZ, } diff --git a/esphome/components/mcp2515/mcp2515.cpp b/esphome/components/mcp2515/mcp2515.cpp index b90b4de66d..fe4a68b583 100644 --- a/esphome/components/mcp2515/mcp2515.cpp +++ b/esphome/components/mcp2515/mcp2515.cpp @@ -16,11 +16,14 @@ const struct MCP2515::RxBnRegs MCP2515::RXB[N_RXBUFFERS] = {{MCP_RXB0CTRL, MCP_R bool MCP2515::setup_internal() { this->spi_setup(); - if (this->reset_() == canbus::ERROR_FAIL) + if (this->reset_() != canbus::ERROR_OK) return false; - this->set_bitrate_(this->bit_rate_, this->mcp_clock_); - this->set_mode_(this->mcp_mode_); - ESP_LOGV(TAG, "setup done"); + if (this->set_bitrate_(this->bit_rate_, this->mcp_clock_) != canbus::ERROR_OK) + return false; + if (this->set_mode_(this->mcp_mode_) != canbus::ERROR_OK) + return false; + uint8_t err_flags = this->get_error_flags_(); + ESP_LOGD(TAG, "mcp2515 setup done, error_flags = %02X", err_flags); return true; } @@ -38,7 +41,7 @@ canbus::Error MCP2515::reset_() { set_registers_(MCP_TXB0CTRL, zeros, 14); set_registers_(MCP_TXB1CTRL, zeros, 14); set_registers_(MCP_TXB2CTRL, zeros, 14); - ESP_LOGD(TAG, "reset() CLEARED TXB registers"); + ESP_LOGV(TAG, "reset() CLEARED TXB registers"); set_register_(MCP_RXB0CTRL, 0); set_register_(MCP_RXB1CTRL, 0); @@ -114,16 +117,12 @@ canbus::Error MCP2515::set_mode_(const CanctrlReqopMode mode) { modify_register_(MCP_CANCTRL, CANCTRL_REQOP, mode); uint32_t end_time = millis() + 10; - bool mode_match = false; while (millis() < end_time) { - uint8_t new_mode = read_register_(MCP_CANSTAT); - new_mode &= CANSTAT_OPMOD; - mode_match = new_mode == mode; - if (mode_match) { - break; - } + if ((read_register_(MCP_CANSTAT) & CANSTAT_OPMOD) == mode) + return canbus::ERROR_OK; } - return mode_match ? canbus::ERROR_OK : canbus::ERROR_FAIL; + ESP_LOGE(TAG, "Failed to set mode"); + return canbus::ERROR_FAIL; } canbus::Error MCP2515::set_clk_out_(const CanClkOut divisor) { @@ -451,6 +450,78 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo } break; + case (MCP_12MHZ): + switch (can_speed) { + case (canbus::CAN_5KBPS): // 5Kbps + cfg1 = MCP_12MHZ_5KBPS_CFG1; + cfg2 = MCP_12MHZ_5KBPS_CFG2; + cfg3 = MCP_12MHZ_5KBPS_CFG3; + break; + case (canbus::CAN_10KBPS): // 10Kbps + cfg1 = MCP_12MHZ_10KBPS_CFG1; + cfg2 = MCP_12MHZ_10KBPS_CFG2; + cfg3 = MCP_12MHZ_10KBPS_CFG3; + break; + case (canbus::CAN_20KBPS): // 20Kbps + cfg1 = MCP_12MHZ_20KBPS_CFG1; + cfg2 = MCP_12MHZ_20KBPS_CFG2; + cfg3 = MCP_12MHZ_20KBPS_CFG3; + break; + case (canbus::CAN_33KBPS): // 33.333Kbps + cfg1 = MCP_12MHZ_33K3BPS_CFG1; + cfg2 = MCP_12MHZ_33K3BPS_CFG2; + cfg3 = MCP_12MHZ_33K3BPS_CFG3; + break; + case (canbus::CAN_40KBPS): // 40Kbps + cfg1 = MCP_12MHZ_40KBPS_CFG1; + cfg2 = MCP_12MHZ_40KBPS_CFG2; + cfg3 = MCP_12MHZ_40KBPS_CFG3; + break; + case (canbus::CAN_50KBPS): // 50Kbps + cfg2 = MCP_12MHZ_50KBPS_CFG2; + cfg3 = MCP_12MHZ_50KBPS_CFG3; + break; + case (canbus::CAN_80KBPS): // 80Kbps + cfg1 = MCP_12MHZ_80KBPS_CFG1; + cfg2 = MCP_12MHZ_80KBPS_CFG2; + cfg3 = MCP_12MHZ_80KBPS_CFG3; + break; + case (canbus::CAN_100KBPS): // 100Kbps + cfg1 = MCP_12MHZ_100KBPS_CFG1; + cfg2 = MCP_12MHZ_100KBPS_CFG2; + cfg3 = MCP_12MHZ_100KBPS_CFG3; + break; + case (canbus::CAN_125KBPS): // 125Kbps + cfg1 = MCP_12MHZ_125KBPS_CFG1; + cfg2 = MCP_12MHZ_125KBPS_CFG2; + cfg3 = MCP_12MHZ_125KBPS_CFG3; + break; + case (canbus::CAN_200KBPS): // 200Kbps + cfg1 = MCP_12MHZ_200KBPS_CFG1; + cfg2 = MCP_12MHZ_200KBPS_CFG2; + cfg3 = MCP_12MHZ_200KBPS_CFG3; + break; + case (canbus::CAN_250KBPS): // 250Kbps + cfg1 = MCP_12MHZ_250KBPS_CFG1; + cfg2 = MCP_12MHZ_250KBPS_CFG2; + cfg3 = MCP_12MHZ_250KBPS_CFG3; + break; + case (canbus::CAN_500KBPS): // 500Kbps + cfg1 = MCP_12MHZ_500KBPS_CFG1; + cfg2 = MCP_12MHZ_500KBPS_CFG2; + cfg3 = MCP_12MHZ_500KBPS_CFG3; + break; + case (canbus::CAN_1000KBPS): // 1Mbps + cfg1 = MCP_12MHZ_1000KBPS_CFG1; + cfg2 = MCP_12MHZ_1000KBPS_CFG2; + cfg3 = MCP_12MHZ_1000KBPS_CFG3; + break; + default: + set = 0; + break; + } + break; + case (MCP_16MHZ): switch (can_speed) { case (canbus::CAN_5KBPS): // 5Kbps @@ -602,6 +673,7 @@ canbus::Error MCP2515::set_bitrate_(canbus::CanSpeed can_speed, CanClock can_clo set_register_(MCP_CNF3, cfg3); // NOLINT return canbus::ERROR_OK; } else { + ESP_LOGE(TAG, "Invalid frequency/bitrate combination: %d/%d", can_clock, can_speed); return canbus::ERROR_FAIL; } } diff --git a/esphome/components/mcp2515/mcp2515.h b/esphome/components/mcp2515/mcp2515.h index 3b9797a78a..c77480ce7d 100644 --- a/esphome/components/mcp2515/mcp2515.h +++ b/esphome/components/mcp2515/mcp2515.h @@ -11,7 +11,7 @@ static const uint32_t SPI_CLOCK = 10000000; // 10MHz static const int N_TXBUFFERS = 3; static const int N_RXBUFFERS = 2; -enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_8MHZ }; +enum CanClock { MCP_20MHZ, MCP_16MHZ, MCP_12MHZ, MCP_8MHZ }; enum MASK { MASK0, MASK1 }; enum RXF { RXF0 = 0, RXF1 = 1, RXF2 = 2, RXF3 = 3, RXF4 = 4, RXF5 = 5 }; enum RXBn { RXB0 = 0, RXB1 = 1 }; diff --git a/esphome/components/mcp2515/mcp2515_defs.h b/esphome/components/mcp2515/mcp2515_defs.h index 454c760c6d..2f5cf2a238 100644 --- a/esphome/components/mcp2515/mcp2515_defs.h +++ b/esphome/components/mcp2515/mcp2515_defs.h @@ -207,6 +207,62 @@ static const uint8_t MCP_8MHZ_5KBPS_CFG1 = 0x1F; static const uint8_t MCP_8MHZ_5KBPS_CFG2 = 0xBF; static const uint8_t MCP_8MHZ_5KBPS_CFG3 = 0x87; +/* + * Speed 12M + */ + +static const uint8_t MCP_12MHZ_1000KBPS_CFG1 = 0x00; +static const uint8_t MCP_12MHZ_1000KBPS_CFG2 = 0x88; +static const uint8_t MCP_12MHZ_1000KBPS_CFG3 = 0x81; + +static const uint8_t MCP_12MHZ_500KBPS_CFG1 = 0x00; +static const uint8_t MCP_12MHZ_500KBPS_CFG2 = 0x9B; +static const uint8_t MCP_12MHZ_500KBPS_CFG3 = 0x82; + +static const uint8_t MCP_12MHZ_250KBPS_CFG1 = 0x01; +static const uint8_t MCP_12MHZ_250KBPS_CFG2 = 0x9B; +static const uint8_t MCP_12MHZ_250KBPS_CFG3 = 0x82; + +static const uint8_t MCP_12MHZ_200KBPS_CFG1 = 0x01; +static const uint8_t MCP_12MHZ_200KBPS_CFG2 = 0xA4; +static const uint8_t MCP_12MHZ_200KBPS_CFG3 = 0x83; + +static const uint8_t MCP_12MHZ_125KBPS_CFG1 = 0x03; +static const uint8_t MCP_12MHZ_125KBPS_CFG2 = 0x9B; +static const uint8_t MCP_12MHZ_125KBPS_CFG3 = 0x82; + +static const uint8_t MCP_12MHZ_100KBPS_CFG1 = 0x03; +static const uint8_t MCP_12MHZ_100KBPS_CFG2 = 0xA4; +static const uint8_t MCP_12MHZ_100KBPS_CFG3 = 0x83; + +static const uint8_t MCP_12MHZ_80KBPS_CFG1 = 0x04; +static const uint8_t MCP_12MHZ_80KBPS_CFG2 = 0xA4; +static const uint8_t MCP_12MHZ_80KBPS_CFG3 = 0x83; + +static const uint8_t MCP_12MHZ_50KBPS_CFG1 = 0x07; +static const uint8_t MCP_12MHZ_50KBPS_CFG2 = 0xA4; +static const uint8_t MCP_12MHZ_50KBPS_CFG3 = 0x83; + +static const uint8_t MCP_12MHZ_40KBPS_CFG1 = 0x09; +static const uint8_t MCP_12MHZ_40KBPS_CFG2 = 0xA4; +static const uint8_t MCP_12MHZ_40KBPS_CFG3 = 0x83; + +static const uint8_t MCP_12MHZ_33K3BPS_CFG1 = 0x08; +static const uint8_t MCP_12MHZ_33K3BPS_CFG2 = 0xB6; +static const uint8_t MCP_12MHZ_33K3BPS_CFG3 = 0x84; + +static const uint8_t MCP_12MHZ_20KBPS_CFG1 = 0x0E; +static const uint8_t MCP_12MHZ_20KBPS_CFG2 = 0xB6; +static const uint8_t MCP_12MHZ_20KBPS_CFG3 = 0x84; + +static const uint8_t MCP_12MHZ_10KBPS_CFG1 = 0x31; +static const uint8_t MCP_12MHZ_10KBPS_CFG2 = 0x9B; +static const uint8_t MCP_12MHZ_10KBPS_CFG3 = 0x82; + +static const uint8_t MCP_12MHZ_5KBPS_CFG1 = 0x3B; +static const uint8_t MCP_12MHZ_5KBPS_CFG2 = 0xB6; +static const uint8_t MCP_12MHZ_5KBPS_CFG3 = 0x84; + /* * speed 16M */ From 6d9dbf9e54dc66ac3d8bf0592ddb89a015eb8576 Mon Sep 17 00:00:00 2001 From: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com> Date: Wed, 12 Jul 2023 13:22:52 +1000 Subject: [PATCH 095/106] Correct message for standard transmission. (#5088) --- esphome/components/canbus/canbus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index 3fe0d50f06..6316c77ff4 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -30,7 +30,7 @@ void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transm if (use_extended_id) { ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); } else { - ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); + ESP_LOGD(TAG, "send standard id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size); } if (size > CAN_MAX_DATA_LENGTH) size = CAN_MAX_DATA_LENGTH; From 7e52d4f5d64380ef0cf0a8467615122856f925c0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 12 Jul 2023 15:28:20 +1200 Subject: [PATCH 096/106] Restrict pillow to versions before 10.0.0 (#5090) --- requirements_optional.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_optional.txt b/requirements_optional.txt index df6b3b387e..8bbf0a6809 100644 --- a/requirements_optional.txt +++ b/requirements_optional.txt @@ -1,3 +1,3 @@ -pillow>4.0.0 +pillow>4.0.0,<10.0.0 cairosvg>=2.2.0 cryptography>=2.0.0,<4 From c85f70a236192b5ff8c5c14930509a1938f30b8f Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Wed, 12 Jul 2023 16:02:37 +1200 Subject: [PATCH 097/106] Bump esphome-dashboard to 20230711.0 (#5085) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c6564d55e0..618fc94e0b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==6.1.7 # When updating platformio, also update Dockerfile esptool==4.6.2 click==8.1.3 -esphome-dashboard==20230621.0 +esphome-dashboard==20230711.0 aioesphomeapi==15.0.0 zeroconf==0.69.0 From bbf3d382e8be1a9039f1fc51eb4fd888eb08ee9b Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Wed, 12 Jul 2023 08:12:40 +0400 Subject: [PATCH 098/106] added uart final validate data bits (#5079) --- esphome/components/uart/__init__.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/esphome/components/uart/__init__.py b/esphome/components/uart/__init__.py index ed60a9f880..aea59d9d8b 100644 --- a/esphome/components/uart/__init__.py +++ b/esphome/components/uart/__init__.py @@ -246,6 +246,7 @@ def final_validate_device_schema( baud_rate: Optional[int] = None, require_tx: bool = False, require_rx: bool = False, + data_bits: Optional[int] = None, parity: Optional[str] = None, stop_bits: Optional[int] = None, ): @@ -268,6 +269,13 @@ def final_validate_device_schema( return validator + def validate_data_bits(value): + if value != data_bits: + raise cv.Invalid( + f"Component {name} requires {data_bits} data bits for the uart bus" + ) + return value + def validate_parity(value): if value != parity: raise cv.Invalid( @@ -278,7 +286,7 @@ def final_validate_device_schema( def validate_stop_bits(value): if value != stop_bits: raise cv.Invalid( - f"Component {name} requires stop bits {stop_bits} for the uart bus" + f"Component {name} requires {stop_bits} stop bits for the uart bus" ) return value @@ -304,6 +312,8 @@ def final_validate_device_schema( ] = validate_pin(CONF_RX_PIN, device) if baud_rate is not None: hub_schema[cv.Required(CONF_BAUD_RATE)] = validate_baud_rate + if data_bits is not None: + hub_schema[cv.Required(CONF_DATA_BITS)] = validate_data_bits if parity is not None: hub_schema[cv.Required(CONF_PARITY)] = validate_parity if stop_bits is not None: From 8c5978599aa67d358a85e5201ee77b85975b0116 Mon Sep 17 00:00:00 2001 From: danieltwagner Date: Wed, 12 Jul 2023 01:10:22 -0400 Subject: [PATCH 099/106] Add support for ATM90E26 (#4366) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/atm90e26/__init__.py | 1 + esphome/components/atm90e26/atm90e26.cpp | 235 +++++++++++++++++++++ esphome/components/atm90e26/atm90e26.h | 72 +++++++ esphome/components/atm90e26/atm90e26_reg.h | 70 ++++++ esphome/components/atm90e26/sensor.py | 157 ++++++++++++++ tests/test1.yaml | 19 ++ 7 files changed, 555 insertions(+) create mode 100644 esphome/components/atm90e26/__init__.py create mode 100644 esphome/components/atm90e26/atm90e26.cpp create mode 100644 esphome/components/atm90e26/atm90e26.h create mode 100644 esphome/components/atm90e26/atm90e26_reg.h create mode 100644 esphome/components/atm90e26/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index d9303523df..f5dc9a17f4 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -32,6 +32,7 @@ esphome/components/api/* @OttoWinter esphome/components/as7341/* @mrgnr esphome/components/async_tcp/* @OttoWinter esphome/components/atc_mithermometer/* @ahpohl +esphome/components/atm90e26/* @danieltwagner esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter diff --git a/esphome/components/atm90e26/__init__.py b/esphome/components/atm90e26/__init__.py new file mode 100644 index 0000000000..ac441a9c2d --- /dev/null +++ b/esphome/components/atm90e26/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@danieltwagner"] diff --git a/esphome/components/atm90e26/atm90e26.cpp b/esphome/components/atm90e26/atm90e26.cpp new file mode 100644 index 0000000000..42a52c4ccf --- /dev/null +++ b/esphome/components/atm90e26/atm90e26.cpp @@ -0,0 +1,235 @@ +#include "atm90e26.h" +#include "atm90e26_reg.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace atm90e26 { + +static const char *const TAG = "atm90e26"; + +void ATM90E26Component::update() { + if (this->read16_(ATM90E26_REGISTER_FUNCEN) != 0x0030) { + this->status_set_warning(); + return; + } + + if (this->voltage_sensor_ != nullptr) { + this->voltage_sensor_->publish_state(this->get_line_voltage_()); + } + if (this->current_sensor_ != nullptr) { + this->current_sensor_->publish_state(this->get_line_current_()); + } + if (this->power_sensor_ != nullptr) { + this->power_sensor_->publish_state(this->get_active_power_()); + } + if (this->reactive_power_sensor_ != nullptr) { + this->reactive_power_sensor_->publish_state(this->get_reactive_power_()); + } + if (this->power_factor_sensor_ != nullptr) { + this->power_factor_sensor_->publish_state(this->get_power_factor_()); + } + if (this->forward_active_energy_sensor_ != nullptr) { + this->forward_active_energy_sensor_->publish_state(this->get_forward_active_energy_()); + } + if (this->reverse_active_energy_sensor_ != nullptr) { + this->reverse_active_energy_sensor_->publish_state(this->get_reverse_active_energy_()); + } + if (this->freq_sensor_ != nullptr) { + this->freq_sensor_->publish_state(this->get_frequency_()); + } + this->status_clear_warning(); +} + +void ATM90E26Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up ATM90E26 Component..."); + this->spi_setup(); + + uint16_t mmode = 0x422; // default values for everything but L/N line current gains + mmode |= (gain_pga_ & 0x7) << 13; + mmode |= (n_line_gain_ & 0x3) << 11; + + this->write16_(ATM90E26_REGISTER_SOFTRESET, 0x789A); // Perform soft reset + this->write16_(ATM90E26_REGISTER_FUNCEN, + 0x0030); // Voltage sag irq=1, report on warnout pin=1, energy dir change irq=0 + uint16_t read = this->read16_(ATM90E26_REGISTER_LASTDATA); + if (read != 0x0030) { + ESP_LOGW(TAG, "Could not initialize ATM90E26 IC, check SPI settings: %d", read); + this->mark_failed(); + return; + } + // TODO: 100 * * sqrt(2) * / (4 * gain_voltage/32768) + this->write16_(ATM90E26_REGISTER_SAGTH, 0x17DD); // Voltage sag threshhold 0x1F2F + + // Set metering calibration values + this->write16_(ATM90E26_REGISTER_CALSTART, 0x5678); // CAL Metering calibration startup command + + // Configure + this->write16_(ATM90E26_REGISTER_MMODE, mmode); // Metering Mode Configuration (see above) + + this->write16_(ATM90E26_REGISTER_PLCONSTH, (pl_const_ >> 16)); // PL Constant MSB + this->write16_(ATM90E26_REGISTER_PLCONSTL, pl_const_ & 0xFFFF); // PL Constant LSB + + // Calibrate this to be 1 pulse per Wh + this->write16_(ATM90E26_REGISTER_LGAIN, gain_metering_); // L Line Calibration Gain (active power metering) + this->write16_(ATM90E26_REGISTER_LPHI, 0x0000); // L Line Calibration Angle + this->write16_(ATM90E26_REGISTER_NGAIN, 0x0000); // N Line Calibration Gain + this->write16_(ATM90E26_REGISTER_NPHI, 0x0000); // N Line Calibration Angle + this->write16_(ATM90E26_REGISTER_PSTARTTH, 0x08BD); // Active Startup Power Threshold (default) = 2237 + this->write16_(ATM90E26_REGISTER_PNOLTH, 0x0000); // Active No-Load Power Threshold + this->write16_(ATM90E26_REGISTER_QSTARTTH, 0x0AEC); // Reactive Startup Power Threshold (default) = 2796 + this->write16_(ATM90E26_REGISTER_QNOLTH, 0x0000); // Reactive No-Load Power Threshold + + // Compute Checksum for the registers we set above + // low byte = sum of all bytes + uint16_t cs = + ((mmode >> 8) + (mmode & 0xFF) + (pl_const_ >> 24) + ((pl_const_ >> 16) & 0xFF) + ((pl_const_ >> 8) & 0xFF) + + (pl_const_ & 0xFF) + (gain_metering_ >> 8) + (gain_metering_ & 0xFF) + 0x08 + 0xBD + 0x0A + 0xEC) & + 0xFF; + // high byte = XOR of all bytes + cs |= ((mmode >> 8) ^ (mmode & 0xFF) ^ (pl_const_ >> 24) ^ ((pl_const_ >> 16) & 0xFF) ^ ((pl_const_ >> 8) & 0xFF) ^ + (pl_const_ & 0xFF) ^ (gain_metering_ >> 8) ^ (gain_metering_ & 0xFF) ^ 0x08 ^ 0xBD ^ 0x0A ^ 0xEC) + << 8; + + this->write16_(ATM90E26_REGISTER_CS1, cs); + ESP_LOGVV(TAG, "Set CS1 to: 0x%04X", cs); + + // Set measurement calibration values + this->write16_(ATM90E26_REGISTER_ADJSTART, 0x5678); // Measurement calibration startup command, registers 31-3A + this->write16_(ATM90E26_REGISTER_UGAIN, gain_voltage_); // Voltage RMS gain + this->write16_(ATM90E26_REGISTER_IGAINL, gain_ct_); // L line current RMS gain + this->write16_(ATM90E26_REGISTER_IGAINN, 0x7530); // N Line Current RMS Gain + this->write16_(ATM90E26_REGISTER_UOFFSET, 0x0000); // Voltage Offset + this->write16_(ATM90E26_REGISTER_IOFFSETL, 0x0000); // L Line Current Offset + this->write16_(ATM90E26_REGISTER_IOFFSETN, 0x0000); // N Line Current Offse + this->write16_(ATM90E26_REGISTER_POFFSETL, 0x0000); // L Line Active Power Offset + this->write16_(ATM90E26_REGISTER_QOFFSETL, 0x0000); // L Line Reactive Power Offset + this->write16_(ATM90E26_REGISTER_POFFSETN, 0x0000); // N Line Active Power Offset + this->write16_(ATM90E26_REGISTER_QOFFSETN, 0x0000); // N Line Reactive Power Offset + + // Compute Checksum for the registers we set above + cs = ((gain_voltage_ >> 8) + (gain_voltage_ & 0xFF) + (gain_ct_ >> 8) + (gain_ct_ & 0xFF) + 0x75 + 0x30) & 0xFF; + cs |= ((gain_voltage_ >> 8) ^ (gain_voltage_ & 0xFF) ^ (gain_ct_ >> 8) ^ (gain_ct_ & 0xFF) ^ 0x75 ^ 0x30) << 8; + this->write16_(ATM90E26_REGISTER_CS2, cs); + ESP_LOGVV(TAG, "Set CS2 to: 0x%04X", cs); + + this->write16_(ATM90E26_REGISTER_CALSTART, + 0x8765); // Checks correctness of 21-2B registers and starts normal metering if ok + this->write16_(ATM90E26_REGISTER_ADJSTART, + 0x8765); // Checks correctness of 31-3A registers and starts normal measurement if ok + + uint16_t sys_status = this->read16_(ATM90E26_REGISTER_SYSSTATUS); + if (sys_status & 0xC000) { // Checksum 1 Error + + ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS1 was incorrect, expected: 0x%04X", + this->read16_(ATM90E26_REGISTER_CS1)); + this->mark_failed(); + } + if (sys_status & 0x3000) { // Checksum 2 Error + ESP_LOGW(TAG, "Could not initialize ATM90E26 IC: CS2 was incorrect, expected: 0x%04X", + this->read16_(ATM90E26_REGISTER_CS2)); + this->mark_failed(); + } +} + +void ATM90E26Component::dump_config() { + ESP_LOGCONFIG("", "ATM90E26:"); + LOG_PIN(" CS Pin: ", this->cs_); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with ATM90E26 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Voltage A", this->voltage_sensor_); + LOG_SENSOR(" ", "Current A", this->current_sensor_); + LOG_SENSOR(" ", "Power A", this->power_sensor_); + LOG_SENSOR(" ", "Reactive Power A", this->reactive_power_sensor_); + LOG_SENSOR(" ", "PF A", this->power_factor_sensor_); + LOG_SENSOR(" ", "Active Forward Energy A", this->forward_active_energy_sensor_); + LOG_SENSOR(" ", "Active Reverse Energy A", this->reverse_active_energy_sensor_); + LOG_SENSOR(" ", "Frequency", this->freq_sensor_); +} +float ATM90E26Component::get_setup_priority() const { return setup_priority::DATA; } + +uint16_t ATM90E26Component::read16_(uint8_t a_register) { + uint8_t data[2]; + uint16_t output; + + this->enable(); + delayMicroseconds(4); + this->write_byte(a_register | 0x80); + delayMicroseconds(4); + this->read_array(data, 2); + this->disable(); + + output = (uint16_t(data[0] & 0xFF) << 8) | (data[1] & 0xFF); + ESP_LOGVV(TAG, "read16_ 0x%04X output 0x%04X", a_register, output); + return output; +} + +void ATM90E26Component::write16_(uint8_t a_register, uint16_t val) { + ESP_LOGVV(TAG, "write16_ 0x%04X val 0x%04X", a_register, val); + this->enable(); + delayMicroseconds(4); + this->write_byte(a_register & 0x7F); + delayMicroseconds(4); + this->write_byte((val >> 8) & 0xFF); + this->write_byte(val & 0xFF); + this->disable(); +} + +float ATM90E26Component::get_line_current_() { + uint16_t current = this->read16_(ATM90E26_REGISTER_IRMS); + return current / 1000.0f; +} + +float ATM90E26Component::get_line_voltage_() { + uint16_t voltage = this->read16_(ATM90E26_REGISTER_URMS); + return voltage / 100.0f; +} + +float ATM90E26Component::get_active_power_() { + int16_t val = this->read16_(ATM90E26_REGISTER_PMEAN); // two's complement + return (float) val; +} + +float ATM90E26Component::get_reactive_power_() { + int16_t val = this->read16_(ATM90E26_REGISTER_QMEAN); // two's complement + return (float) val; +} + +float ATM90E26Component::get_power_factor_() { + uint16_t val = this->read16_(ATM90E26_REGISTER_POWERF); // signed + if (val & 0x8000) { + return -(val & 0x7FF) / 1000.0f; + } else { + return val / 1000.0f; + } +} + +float ATM90E26Component::get_forward_active_energy_() { + uint16_t val = this->read16_(ATM90E26_REGISTER_APENERGY); + if ((UINT32_MAX - this->cumulative_forward_active_energy_) > val) { + this->cumulative_forward_active_energy_ += val; + } else { + this->cumulative_forward_active_energy_ = val; + } + // The register holds thenths of pulses, we want to output Wh + return (this->cumulative_forward_active_energy_ * 100.0f / meter_constant_); +} + +float ATM90E26Component::get_reverse_active_energy_() { + uint16_t val = this->read16_(ATM90E26_REGISTER_ANENERGY); + if (UINT32_MAX - this->cumulative_reverse_active_energy_ > val) { + this->cumulative_reverse_active_energy_ += val; + } else { + this->cumulative_reverse_active_energy_ = val; + } + return (this->cumulative_reverse_active_energy_ * 100.0f / meter_constant_); +} + +float ATM90E26Component::get_frequency_() { + uint16_t freq = this->read16_(ATM90E26_REGISTER_FREQ); + return freq / 100.0f; +} + +} // namespace atm90e26 +} // namespace esphome diff --git a/esphome/components/atm90e26/atm90e26.h b/esphome/components/atm90e26/atm90e26.h new file mode 100644 index 0000000000..3c098d7e91 --- /dev/null +++ b/esphome/components/atm90e26/atm90e26.h @@ -0,0 +1,72 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/spi/spi.h" + +namespace esphome { +namespace atm90e26 { + +class ATM90E26Component : public PollingComponent, + public spi::SPIDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_voltage_sensor(sensor::Sensor *obj) { this->voltage_sensor_ = obj; } + void set_current_sensor(sensor::Sensor *obj) { this->current_sensor_ = obj; } + void set_power_sensor(sensor::Sensor *obj) { this->power_sensor_ = obj; } + void set_reactive_power_sensor(sensor::Sensor *obj) { this->reactive_power_sensor_ = obj; } + void set_forward_active_energy_sensor(sensor::Sensor *obj) { this->forward_active_energy_sensor_ = obj; } + void set_reverse_active_energy_sensor(sensor::Sensor *obj) { this->reverse_active_energy_sensor_ = obj; } + void set_power_factor_sensor(sensor::Sensor *obj) { this->power_factor_sensor_ = obj; } + void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; } + void set_line_freq(int freq) { line_freq_ = freq; } + void set_meter_constant(float val) { meter_constant_ = val; } + void set_pl_const(uint32_t pl_const) { pl_const_ = pl_const; } + void set_gain_metering(uint16_t gain) { this->gain_metering_ = gain; } + void set_gain_voltage(uint16_t gain) { this->gain_voltage_ = gain; } + void set_gain_ct(uint16_t gain) { this->gain_ct_ = gain; } + void set_gain_pga(uint16_t gain) { gain_pga_ = gain; } + void set_n_line_gain(uint16_t gain) { n_line_gain_ = gain; } + + protected: + uint16_t read16_(uint8_t a_register); + int read32_(uint8_t addr_h, uint8_t addr_l); + void write16_(uint8_t a_register, uint16_t val); + + float get_line_voltage_(); + float get_line_current_(); + float get_active_power_(); + float get_reactive_power_(); + float get_power_factor_(); + float get_forward_active_energy_(); + float get_reverse_active_energy_(); + float get_frequency_(); + float get_chip_temperature_(); + + sensor::Sensor *freq_sensor_{nullptr}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *reactive_power_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; + sensor::Sensor *forward_active_energy_sensor_{nullptr}; + sensor::Sensor *reverse_active_energy_sensor_{nullptr}; + uint32_t cumulative_forward_active_energy_{0}; + uint32_t cumulative_reverse_active_energy_{0}; + uint16_t gain_metering_{7481}; + uint16_t gain_voltage_{26400}; + uint16_t gain_ct_{31251}; + uint16_t gain_pga_{0x4}; + uint16_t n_line_gain_{0x2}; + int line_freq_{60}; + float meter_constant_{3200.0f}; + uint32_t pl_const_{1429876}; +}; + +} // namespace atm90e26 +} // namespace esphome diff --git a/esphome/components/atm90e26/atm90e26_reg.h b/esphome/components/atm90e26/atm90e26_reg.h new file mode 100644 index 0000000000..0a925f424e --- /dev/null +++ b/esphome/components/atm90e26/atm90e26_reg.h @@ -0,0 +1,70 @@ +#pragma once + +namespace esphome { +namespace atm90e26 { + +/* Status and Special Register */ +static const uint8_t ATM90E26_REGISTER_SOFTRESET = 0x00; // Software Reset +static const uint8_t ATM90E26_REGISTER_SYSSTATUS = 0x01; // System Status +static const uint8_t ATM90E26_REGISTER_FUNCEN = 0x02; // Function Enable +static const uint8_t ATM90E26_REGISTER_SAGTH = 0x03; // Voltage Sag Threshold +static const uint8_t ATM90E26_REGISTER_SMALLPMOD = 0x04; // Small-Power Mode +static const uint8_t ATM90E26_REGISTER_LASTDATA = 0x06; // Last Read/Write SPI/UART Value + +/* Metering Calibration and Configuration Register */ +static const uint8_t ATM90E26_REGISTER_LSB = 0x08; // RMS/Power 16-bit LSB +static const uint8_t ATM90E26_REGISTER_CALSTART = 0x20; // Calibration Start Command +static const uint8_t ATM90E26_REGISTER_PLCONSTH = 0x21; // High Word of PL_Constant +static const uint8_t ATM90E26_REGISTER_PLCONSTL = 0x22; // Low Word of PL_Constant +static const uint8_t ATM90E26_REGISTER_LGAIN = 0x23; // L Line Calibration Gain +static const uint8_t ATM90E26_REGISTER_LPHI = 0x24; // L Line Calibration Angle +static const uint8_t ATM90E26_REGISTER_NGAIN = 0x25; // N Line Calibration Gain +static const uint8_t ATM90E26_REGISTER_NPHI = 0x26; // N Line Calibration Angle +static const uint8_t ATM90E26_REGISTER_PSTARTTH = 0x27; // Active Startup Power Threshold +static const uint8_t ATM90E26_REGISTER_PNOLTH = 0x28; // Active No-Load Power Threshold +static const uint8_t ATM90E26_REGISTER_QSTARTTH = 0x29; // Reactive Startup Power Threshold +static const uint8_t ATM90E26_REGISTER_QNOLTH = 0x2A; // Reactive No-Load Power Threshold +static const uint8_t ATM90E26_REGISTER_MMODE = 0x2B; // Metering Mode Configuration +static const uint8_t ATM90E26_REGISTER_CS1 = 0x2C; // Checksum 1 + +/* Measurement Calibration Register */ +static const uint8_t ATM90E26_REGISTER_ADJSTART = 0x30; // Measurement Calibration Start Command +static const uint8_t ATM90E26_REGISTER_UGAIN = 0x31; // Voltage RMS Gain +static const uint8_t ATM90E26_REGISTER_IGAINL = 0x32; // L Line Current RMS Gain +static const uint8_t ATM90E26_REGISTER_IGAINN = 0x33; // N Line Current RMS Gain +static const uint8_t ATM90E26_REGISTER_UOFFSET = 0x34; // Voltage Offset +static const uint8_t ATM90E26_REGISTER_IOFFSETL = 0x35; // L Line Current Offset +static const uint8_t ATM90E26_REGISTER_IOFFSETN = 0x36; // N Line Current Offse +static const uint8_t ATM90E26_REGISTER_POFFSETL = 0x37; // L Line Active Power Offset +static const uint8_t ATM90E26_REGISTER_QOFFSETL = 0x38; // L Line Reactive Power Offset +static const uint8_t ATM90E26_REGISTER_POFFSETN = 0x39; // N Line Active Power Offset +static const uint8_t ATM90E26_REGISTER_QOFFSETN = 0x3A; // N Line Reactive Power Offset +static const uint8_t ATM90E26_REGISTER_CS2 = 0x3B; // Checksum 2 + +/* Energy Register */ +static const uint8_t ATM90E26_REGISTER_APENERGY = 0x40; // Forward Active Energy +static const uint8_t ATM90E26_REGISTER_ANENERGY = 0x41; // Reverse Active Energy +static const uint8_t ATM90E26_REGISTER_ATENERGY = 0x42; // Absolute Active Energy +static const uint8_t ATM90E26_REGISTER_RPENERGY = 0x43; // Forward (Inductive) Reactive Energy +static const uint8_t ATM90E26_REGISTER_RNENERG = 0x44; // Reverse (Capacitive) Reactive Energy +static const uint8_t ATM90E26_REGISTER_RTENERGY = 0x45; // Absolute Reactive Energy +static const uint8_t ATM90E26_REGISTER_ENSTATUS = 0x46; // Metering Status + +/* Measurement Register */ +static const uint8_t ATM90E26_REGISTER_IRMS = 0x48; // L Line Current RMS +static const uint8_t ATM90E26_REGISTER_URMS = 0x49; // Voltage RMS +static const uint8_t ATM90E26_REGISTER_PMEAN = 0x4A; // L Line Mean Active Power +static const uint8_t ATM90E26_REGISTER_QMEAN = 0x4B; // L Line Mean Reactive Power +static const uint8_t ATM90E26_REGISTER_FREQ = 0x4C; // Voltage Frequency +static const uint8_t ATM90E26_REGISTER_POWERF = 0x4D; // L Line Power Factor +static const uint8_t ATM90E26_REGISTER_PANGLE = 0x4E; // Phase Angle between Voltage and L Line Current +static const uint8_t ATM90E26_REGISTER_SMEAN = 0x4F; // L Line Mean Apparent Power +static const uint8_t ATM90E26_REGISTER_IRMS2 = 0x68; // N Line Current rms +static const uint8_t ATM90E26_REGISTER_PMEAN2 = 0x6A; // N Line Mean Active Power +static const uint8_t ATM90E26_REGISTER_QMEAN2 = 0x6B; // N Line Mean Reactive Power +static const uint8_t ATM90E26_REGISTER_POWERF2 = 0x6D; // N Line Power Factor +static const uint8_t ATM90E26_REGISTER_PANGLE2 = 0x6E; // Phase Angle between Voltage and N Line Current +static const uint8_t ATM90E26_REGISTER_SMEAN2 = 0x6F; // N Line Mean Apparent Power + +} // namespace atm90e26 +} // namespace esphome diff --git a/esphome/components/atm90e26/sensor.py b/esphome/components/atm90e26/sensor.py new file mode 100644 index 0000000000..a0d97ab5ae --- /dev/null +++ b/esphome/components/atm90e26/sensor.py @@ -0,0 +1,157 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, spi +from esphome.const import ( + CONF_ID, + CONF_REACTIVE_POWER, + CONF_VOLTAGE, + CONF_CURRENT, + CONF_POWER, + CONF_POWER_FACTOR, + CONF_FREQUENCY, + CONF_FORWARD_ACTIVE_ENERGY, + CONF_REVERSE_ACTIVE_ENERGY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_VOLTAGE, + ICON_LIGHTBULB, + ICON_CURRENT_AC, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_HERTZ, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + UNIT_VOLT_AMPS_REACTIVE, + UNIT_WATT_HOURS, +) + +CONF_LINE_FREQUENCY = "line_frequency" +CONF_METER_CONSTANT = "meter_constant" +CONF_PL_CONST = "pl_const" +CONF_GAIN_PGA = "gain_pga" +CONF_GAIN_METERING = "gain_metering" +CONF_GAIN_VOLTAGE = "gain_voltage" +CONF_GAIN_CT = "gain_ct" +LINE_FREQS = { + "50HZ": 50, + "60HZ": 60, +} +PGA_GAINS = { + "1X": 0x4, + "4X": 0x0, + "8X": 0x1, + "16X": 0x2, + "24X": 0x3, +} + +atm90e26_ns = cg.esphome_ns.namespace("atm90e26") +ATM90E26Component = atm90e26_ns.class_( + "ATM90E26Component", cg.PollingComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ATM90E26Component), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + 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=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + icon=ICON_LIGHTBULB, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), + cv.Required(CONF_METER_CONSTANT): cv.positive_float, + cv.Optional(CONF_PL_CONST, default=1429876): cv.uint32_t, + cv.Optional(CONF_GAIN_METERING, default=7481): cv.uint16_t, + cv.Optional(CONF_GAIN_VOLTAGE, default=26400): cv.int_range( + min=0, max=32767 + ), + cv.Optional(CONF_GAIN_CT, default=31251): cv.uint16_t, + cv.Optional(CONF_GAIN_PGA, default="1X"): cv.enum(PGA_GAINS, upper=True), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + if CONF_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_VOLTAGE]) + cg.add(var.set_voltage_sensor(sens)) + if CONF_CURRENT in config: + sens = await sensor.new_sensor(config[CONF_CURRENT]) + cg.add(var.set_current_sensor(sens)) + if CONF_POWER in config: + sens = await sensor.new_sensor(config[CONF_POWER]) + cg.add(var.set_power_sensor(sens)) + if CONF_REACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER]) + cg.add(var.set_reactive_power_sensor(sens)) + if CONF_POWER_FACTOR in config: + sens = await sensor.new_sensor(config[CONF_POWER_FACTOR]) + cg.add(var.set_power_factor_sensor(sens)) + if CONF_FORWARD_ACTIVE_ENERGY in config: + sens = await sensor.new_sensor(config[CONF_FORWARD_ACTIVE_ENERGY]) + cg.add(var.set_forward_active_energy_sensor(sens)) + if CONF_REVERSE_ACTIVE_ENERGY in config: + sens = await sensor.new_sensor(config[CONF_REVERSE_ACTIVE_ENERGY]) + cg.add(var.set_reverse_active_energy_sensor(sens)) + if CONF_FREQUENCY in config: + sens = await sensor.new_sensor(config[CONF_FREQUENCY]) + cg.add(var.set_freq_sensor(sens)) + cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) + cg.add(var.set_meter_constant(config[CONF_METER_CONSTANT])) + cg.add(var.set_pl_const(config[CONF_PL_CONST])) + cg.add(var.set_gain_metering(config[CONF_GAIN_METERING])) + cg.add(var.set_gain_voltage(config[CONF_GAIN_VOLTAGE])) + cg.add(var.set_gain_ct(config[CONF_GAIN_CT])) + cg.add(var.set_gain_pga(config[CONF_GAIN_PGA])) diff --git a/tests/test1.yaml b/tests/test1.yaml index 9d279b5e40..d0c9801933 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -483,6 +483,25 @@ sensor: nir: name: NIR i2c_id: i2c_bus + - platform: atm90e26 + cs_pin: 5 + voltage: + name: Line Voltage + current: + name: CT Amps + power: + name: Active Watts + power_factor: + name: Power Factor + frequency: + name: Line Frequency + line_frequency: 50Hz + meter_constant: 1000 + pl_const: 1429876 + gain_pga: 1X + gain_metering: 7481 + gain_voltage: 26400 + gain_ct: 31251 - platform: atm90e32 cs_pin: 5 phase_a: From 119bbba2549cb5574e1bb17ae87bec3be8075d0a Mon Sep 17 00:00:00 2001 From: Christian Date: Wed, 12 Jul 2023 21:13:50 +0100 Subject: [PATCH 100/106] Grove amend name (#5093) --- CODEOWNERS | 2 +- .../__init__.py | 34 +++++++++---------- .../grove_tb6612fng.cpp} | 6 ++-- .../grove_tb6612fng.h} | 4 +-- tests/test3.1.yaml | 4 +-- 5 files changed, 25 insertions(+), 25 deletions(-) rename esphome/components/{grove_i2c_motor => grove_tb6612fng}/__init__.py (80%) rename esphome/components/{grove_i2c_motor/grove_i2c_motor.cpp => grove_tb6612fng/grove_tb6612fng.cpp} (98%) rename esphome/components/{grove_i2c_motor/grove_i2c_motor.h => grove_tb6612fng/grove_tb6612fng.h} (99%) diff --git a/CODEOWNERS b/CODEOWNERS index f5dc9a17f4..cce94d4ccb 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -104,7 +104,7 @@ esphome/components/gp8403/* @jesserockz esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle esphome/components/graph/* @synco -esphome/components/grove_i2c_motor/* @max246 +esphome/components/grove_tb6612fng/* @max246 esphome/components/growatt_solar/* @leeuwte esphome/components/haier/* @paveldn esphome/components/havells_solar/* @sourabhjaiswal diff --git a/esphome/components/grove_i2c_motor/__init__.py b/esphome/components/grove_tb6612fng/__init__.py similarity index 80% rename from esphome/components/grove_i2c_motor/__init__.py rename to esphome/components/grove_tb6612fng/__init__.py index f7888f7293..75610ce9d3 100644 --- a/esphome/components/grove_i2c_motor/__init__.py +++ b/esphome/components/grove_tb6612fng/__init__.py @@ -14,23 +14,23 @@ DEPENDENCIES = ["i2c"] CODEOWNERS = ["@max246"] -grove_i2c_motor_ns = cg.esphome_ns.namespace("grove_i2c_motor") -GROVE_TB6612FNG = grove_i2c_motor_ns.class_( +grove_tb6612fng_ns = cg.esphome_ns.namespace("grove_tb6612fng") +GROVE_TB6612FNG = grove_tb6612fng_ns.class_( "GroveMotorDriveTB6612FNG", cg.Component, i2c.I2CDevice ) -GROVETB6612FNGMotorRunAction = grove_i2c_motor_ns.class_( +GROVETB6612FNGMotorRunAction = grove_tb6612fng_ns.class_( "GROVETB6612FNGMotorRunAction", automation.Action ) -GROVETB6612FNGMotorBrakeAction = grove_i2c_motor_ns.class_( +GROVETB6612FNGMotorBrakeAction = grove_tb6612fng_ns.class_( "GROVETB6612FNGMotorBrakeAction", automation.Action ) -GROVETB6612FNGMotorStopAction = grove_i2c_motor_ns.class_( +GROVETB6612FNGMotorStopAction = grove_tb6612fng_ns.class_( "GROVETB6612FNGMotorStopAction", automation.Action ) -GROVETB6612FNGMotorStandbyAction = grove_i2c_motor_ns.class_( +GROVETB6612FNGMotorStandbyAction = grove_tb6612fng_ns.class_( "GROVETB6612FNGMotorStandbyAction", automation.Action ) -GROVETB6612FNGMotorNoStandbyAction = grove_i2c_motor_ns.class_( +GROVETB6612FNGMotorNoStandbyAction = grove_tb6612fng_ns.class_( "GROVETB6612FNGMotorNoStandbyAction", automation.Action ) @@ -57,7 +57,7 @@ async def to_code(config): @automation.register_action( - "grove_i2c_motor.run", + "grove_tb6612fng.run", GROVETB6612FNGMotorRunAction, cv.Schema( { @@ -68,7 +68,7 @@ async def to_code(config): } ), ) -async def grove_i2c_motor_run_to_code(config, action_id, template_arg, args): +async def grove_tb6612fng_run_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) @@ -83,7 +83,7 @@ async def grove_i2c_motor_run_to_code(config, action_id, template_arg, args): @automation.register_action( - "grove_i2c_motor.break", + "grove_tb6612fng.break", GROVETB6612FNGMotorBrakeAction, cv.Schema( { @@ -92,7 +92,7 @@ async def grove_i2c_motor_run_to_code(config, action_id, template_arg, args): } ), ) -async def grove_i2c_motor_break_to_code(config, action_id, template_arg, args): +async def grove_tb6612fng_break_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) @@ -102,7 +102,7 @@ async def grove_i2c_motor_break_to_code(config, action_id, template_arg, args): @automation.register_action( - "grove_i2c_motor.stop", + "grove_tb6612fng.stop", GROVETB6612FNGMotorStopAction, cv.Schema( { @@ -111,7 +111,7 @@ async def grove_i2c_motor_break_to_code(config, action_id, template_arg, args): } ), ) -async def grove_i2c_motor_stop_to_code(config, action_id, template_arg, args): +async def grove_tb6612fng_stop_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) @@ -121,7 +121,7 @@ async def grove_i2c_motor_stop_to_code(config, action_id, template_arg, args): @automation.register_action( - "grove_i2c_motor.standby", + "grove_tb6612fng.standby", GROVETB6612FNGMotorStandbyAction, cv.Schema( { @@ -129,7 +129,7 @@ async def grove_i2c_motor_stop_to_code(config, action_id, template_arg, args): } ), ) -async def grove_i2c_motor_standby_to_code(config, action_id, template_arg, args): +async def grove_tb6612fng_standby_to_code(config, action_id, template_arg, args): var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_ID]) @@ -137,7 +137,7 @@ async def grove_i2c_motor_standby_to_code(config, action_id, template_arg, args) @automation.register_action( - "grove_i2c_motor.no_standby", + "grove_tb6612fng.no_standby", GROVETB6612FNGMotorNoStandbyAction, cv.Schema( { @@ -145,7 +145,7 @@ async def grove_i2c_motor_standby_to_code(config, action_id, template_arg, args) } ), ) -async def grove_i2c_motor_no_standby_to_code(config, action_id, template_arg, args): +async def grove_tb6612fng_no_standby_to_code(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/grove_i2c_motor/grove_i2c_motor.cpp b/esphome/components/grove_tb6612fng/grove_tb6612fng.cpp similarity index 98% rename from esphome/components/grove_i2c_motor/grove_i2c_motor.cpp rename to esphome/components/grove_tb6612fng/grove_tb6612fng.cpp index 669114aec0..621b7968a4 100644 --- a/esphome/components/grove_i2c_motor/grove_i2c_motor.cpp +++ b/esphome/components/grove_tb6612fng/grove_tb6612fng.cpp @@ -1,9 +1,9 @@ -#include "grove_i2c_motor.h" +#include "grove_tb6612fng.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" namespace esphome { -namespace grove_i2c_motor { +namespace grove_tb6612fng { static const char *const TAG = "GroveMotorDriveTB6612FNG"; @@ -167,5 +167,5 @@ void GroveMotorDriveTB6612FNG::stepper_keep_run(StepperModeTypeT mode, uint16_t return; } } -} // namespace grove_i2c_motor +} // namespace grove_tb6612fng } // namespace esphome diff --git a/esphome/components/grove_i2c_motor/grove_i2c_motor.h b/esphome/components/grove_tb6612fng/grove_tb6612fng.h similarity index 99% rename from esphome/components/grove_i2c_motor/grove_i2c_motor.h rename to esphome/components/grove_tb6612fng/grove_tb6612fng.h index 8a0db77e70..ccdab6472a 100644 --- a/esphome/components/grove_i2c_motor/grove_i2c_motor.h +++ b/esphome/components/grove_tb6612fng/grove_tb6612fng.h @@ -34,7 +34,7 @@ */ namespace esphome { -namespace grove_i2c_motor { +namespace grove_tb6612fng { enum MotorChannelTypeT { MOTOR_CHA = 0, @@ -204,5 +204,5 @@ class GROVETB6612FNGMotorNoStandbyAction : public Action, public Parented void play(Ts... x) override { this->parent_->not_standby(); } }; -} // namespace grove_i2c_motor +} // namespace grove_tb6612fng } // namespace esphome diff --git a/tests/test3.1.yaml b/tests/test3.1.yaml index a003c804c9..5f1d3ff28f 100644 --- a/tests/test3.1.yaml +++ b/tests/test3.1.yaml @@ -303,7 +303,7 @@ sm2135: rgb_current: 20mA cw_current: 60mA -grove_i2c_motor: +grove_tb6612fng: id: test_motor address: 0x14 @@ -363,7 +363,7 @@ switch: optimistic: True on_turn_on: then: - - grove_i2c_motor.run: + - grove_tb6612fng.run: channel: 1 speed: 255 direction: BACKWARD From e4a640844ccdf9f9df6aff8adbfdd022a947699a Mon Sep 17 00:00:00 2001 From: Pavlo Dudnytskyi Date: Wed, 12 Jul 2023 22:24:49 +0200 Subject: [PATCH 101/106] Fixing colon for tm1637 display if inverted set true (#5072) --- esphome/components/tm1637/tm1637.cpp | 29 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index 434c6e65f3..8d7630bd1d 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -300,6 +300,7 @@ uint8_t TM1637Display::read_byte_() { uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { // ESP_LOGV(TAG, "Print at %d: %s", start_pos, str); uint8_t pos = start_pos; + bool use_dot = false; for (; *str != '\0'; str++) { uint8_t data = TM1637_UNKNOWN_CHAR; if (*str >= ' ' && *str <= '~') @@ -312,14 +313,14 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { // XABCDEFG, but TM1637 is // XGFEDCBA if (this->inverted_) { // XABCDEFG > XGCBAFED - data = ((data & 0x80) ? 0x80 : 0) | // no move X - ((data & 0x40) ? 0x8 : 0) | // A - ((data & 0x20) ? 0x10 : 0) | // B - ((data & 0x10) ? 0x20 : 0) | // C - ((data & 0x8) ? 0x1 : 0) | // D - ((data & 0x4) ? 0x2 : 0) | // E - ((data & 0x2) ? 0x4 : 0) | // F - ((data & 0x1) ? 0x40 : 0); // G + data = ((data & 0x80) || use_dot ? 0x80 : 0) | // no move X + ((data & 0x40) ? 0x8 : 0) | // A + ((data & 0x20) ? 0x10 : 0) | // B + ((data & 0x10) ? 0x20 : 0) | // C + ((data & 0x8) ? 0x1 : 0) | // D + ((data & 0x4) ? 0x2 : 0) | // E + ((data & 0x2) ? 0x4 : 0) | // F + ((data & 0x1) ? 0x40 : 0); // G } else { // XABCDEFG > XGFEDCBA data = ((data & 0x80) ? 0x80 : 0) | // no move X @@ -331,18 +332,18 @@ uint8_t TM1637Display::print(uint8_t start_pos, const char *str) { ((data & 0x2) ? 0x20 : 0) | // F ((data & 0x1) ? 0x40 : 0); // G } - if (*str == '.') { - if (pos != start_pos) - pos--; - this->buffer_[pos] |= 0b10000000; + use_dot = *str == '.'; + if (use_dot) { + if ((!this->inverted_) && (pos != start_pos)) { + this->buffer_[pos - 1] |= 0b10000000; + } } else { if (pos >= 6) { ESP_LOGE(TAG, "String is too long for the display!"); break; } - this->buffer_[pos] = data; + this->buffer_[pos++] = data; } - pos++; } return pos - start_pos; } From eb859e83f82b48fc2ed311f889dcb90164373e40 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Thu, 13 Jul 2023 00:44:30 +0400 Subject: [PATCH 102/106] Fix use of optional (#5091) --- .../template/binary_sensor/template_binary_sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/template/binary_sensor/template_binary_sensor.cpp b/esphome/components/template/binary_sensor/template_binary_sensor.cpp index fce11f63d6..5ce8894a8a 100644 --- a/esphome/components/template/binary_sensor/template_binary_sensor.cpp +++ b/esphome/components/template/binary_sensor/template_binary_sensor.cpp @@ -11,7 +11,7 @@ void TemplateBinarySensor::setup() { return; if (this->f_ != nullptr) { - this->publish_initial_state(*this->f_()); + this->publish_initial_state(this->f_().value_or(false)); } else { this->publish_initial_state(false); } From a539197bc475e52146597298b7446878a61e9312 Mon Sep 17 00:00:00 2001 From: Sergey Dudanov Date: Thu, 13 Jul 2023 00:48:16 +0400 Subject: [PATCH 103/106] New 'Duty Time' sensor component (#5069) --- CODEOWNERS | 1 + esphome/components/duty_time/__init__.py | 1 + .../components/duty_time/duty_time_sensor.cpp | 103 +++++++++++++++ .../components/duty_time/duty_time_sensor.h | 88 +++++++++++++ esphome/components/duty_time/sensor.py | 121 ++++++++++++++++++ tests/test2.yaml | 23 ++++ 6 files changed, 337 insertions(+) create mode 100644 esphome/components/duty_time/__init__.py create mode 100644 esphome/components/duty_time/duty_time_sensor.cpp create mode 100644 esphome/components/duty_time/duty_time_sensor.h create mode 100644 esphome/components/duty_time/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index cce94d4ccb..122dc71b48 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -78,6 +78,7 @@ esphome/components/display_menu_base/* @numo68 esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/duty_time/* @dudanov esphome/components/ee895/* @Stock-M esphome/components/ektf2232/* @jesserockz esphome/components/ens210/* @itn3rd77 diff --git a/esphome/components/duty_time/__init__.py b/esphome/components/duty_time/__init__.py new file mode 100644 index 0000000000..b708cee80b --- /dev/null +++ b/esphome/components/duty_time/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@dudanov"] diff --git a/esphome/components/duty_time/duty_time_sensor.cpp b/esphome/components/duty_time/duty_time_sensor.cpp new file mode 100644 index 0000000000..045cbcceac --- /dev/null +++ b/esphome/components/duty_time/duty_time_sensor.cpp @@ -0,0 +1,103 @@ +#include "duty_time_sensor.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace duty_time_sensor { + +static const char *const TAG = "duty_time_sensor"; + +void DutyTimeSensor::set_sensor(binary_sensor::BinarySensor *const sensor) { + sensor->add_on_state_callback([this](bool state) { this->process_state_(state); }); +} + +void DutyTimeSensor::start() { + if (!this->last_state_) + this->process_state_(true); +} + +void DutyTimeSensor::stop() { + if (this->last_state_) + this->process_state_(false); +} + +void DutyTimeSensor::update() { + if (this->last_state_) + this->process_state_(true); +} + +void DutyTimeSensor::loop() { + if (this->func_ == nullptr) + return; + + const bool state = this->func_(); + + if (state != this->last_state_) + this->process_state_(state); +} + +void DutyTimeSensor::setup() { + uint32_t seconds = 0; + + if (this->restore_) { + this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); + this->pref_.load(&seconds); + } + + this->set_value_(seconds); +} + +void DutyTimeSensor::set_value_(const uint32_t sec) { + this->last_time_ = 0; + if (this->last_state_) + this->last_time_ = millis(); // last time with 0 ms correction + this->publish_and_save_(sec, 0); +} + +void DutyTimeSensor::process_state_(const bool state) { + const uint32_t now = millis(); + + if (this->last_state_) { + // update or falling edge + const uint32_t tm = now - this->last_time_; + const uint32_t ms = tm % 1000; + + this->publish_and_save_(this->total_sec_ + tm / 1000, ms); + this->last_time_ = now - ms; // store time with ms correction + + if (!state) { + // falling edge + this->last_time_ = ms; // temporary store ms correction only + this->last_state_ = false; + + if (this->last_duty_time_sensor_ != nullptr) { + const uint32_t turn_on_ms = now - this->edge_time_; + this->last_duty_time_sensor_->publish_state(turn_on_ms * 1e-3f); + } + } + + } else if (state) { + // rising edge + this->last_time_ = now - this->last_time_; // store time with ms correction + this->edge_time_ = now; // store turn-on start time + this->last_state_ = true; + } +} + +void DutyTimeSensor::publish_and_save_(const uint32_t sec, const uint32_t ms) { + this->total_sec_ = sec; + this->publish_state(sec + ms * 1e-3f); + + if (this->restore_) + this->pref_.save(&sec); +} + +void DutyTimeSensor::dump_config() { + ESP_LOGCONFIG(TAG, "Duty Time:"); + ESP_LOGCONFIG(TAG, " Update Interval: %dms", this->get_update_interval()); + ESP_LOGCONFIG(TAG, " Restore: %s", ONOFF(this->restore_)); + LOG_SENSOR(" ", "Duty Time Sensor:", this); + LOG_SENSOR(" ", "Last Duty Time Sensor:", this->last_duty_time_sensor_); +} + +} // namespace duty_time_sensor +} // namespace esphome diff --git a/esphome/components/duty_time/duty_time_sensor.h b/esphome/components/duty_time/duty_time_sensor.h new file mode 100644 index 0000000000..27fa383847 --- /dev/null +++ b/esphome/components/duty_time/duty_time_sensor.h @@ -0,0 +1,88 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/preferences.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace duty_time_sensor { + +class DutyTimeSensor : public sensor::Sensor, public PollingComponent { + public: + void setup() override; + void update() override; + void loop() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void start(); + void stop(); + bool is_running() const { return this->last_state_; } + void reset() { this->set_value_(0); } + + void set_lambda(std::function &&func) { this->func_ = func; } + void set_sensor(binary_sensor::BinarySensor *sensor); + void set_last_duty_time_sensor(sensor::Sensor *sensor) { this->last_duty_time_sensor_ = sensor; } + void set_restore(bool restore) { this->restore_ = restore; } + + protected: + void set_value_(uint32_t sec); + void process_state_(bool state); + void publish_and_save_(uint32_t sec, uint32_t ms); + + std::function func_{nullptr}; + sensor::Sensor *last_duty_time_sensor_{nullptr}; + ESPPreferenceObject pref_; + + uint32_t total_sec_; + uint32_t last_time_; + uint32_t edge_time_; + bool last_state_{false}; + bool restore_; +}; + +template class StartAction : public Action { + public: + explicit StartAction(DutyTimeSensor *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->start(); } + + protected: + DutyTimeSensor *parent_; +}; + +template class StopAction : public Action { + public: + explicit StopAction(DutyTimeSensor *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->stop(); } + + protected: + DutyTimeSensor *parent_; +}; + +template class ResetAction : public Action { + public: + explicit ResetAction(DutyTimeSensor *parent) : parent_(parent) {} + + void play(Ts... x) override { this->parent_->reset(); } + + protected: + DutyTimeSensor *parent_; +}; + +template class RunningCondition : public Condition { + public: + explicit RunningCondition(DutyTimeSensor *parent, bool state) : parent_(parent), state_(state) {} + + bool check(Ts... x) override { return this->parent_->is_running() == this->state_; } + + protected: + DutyTimeSensor *parent_; + bool state_; +}; + +} // namespace duty_time_sensor +} // namespace esphome diff --git a/esphome/components/duty_time/sensor.py b/esphome/components/duty_time/sensor.py new file mode 100644 index 0000000000..5f8582d481 --- /dev/null +++ b/esphome/components/duty_time/sensor.py @@ -0,0 +1,121 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.automation import ( + Action, + Condition, + maybe_simple_id, + register_action, + register_condition, +) +from esphome.components import binary_sensor, sensor +from esphome.const import ( + CONF_ID, + CONF_SENSOR, + CONF_RESTORE, + CONF_LAMBDA, + UNIT_SECOND, + STATE_CLASS_TOTAL, + STATE_CLASS_TOTAL_INCREASING, + DEVICE_CLASS_DURATION, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +CONF_LAST_TIME = "last_time" + +duty_time_sensor_ns = cg.esphome_ns.namespace("duty_time_sensor") +DutyTimeSensor = duty_time_sensor_ns.class_( + "DutyTimeSensor", sensor.Sensor, cg.PollingComponent +) +StartAction = duty_time_sensor_ns.class_("StartAction", Action) +StopAction = duty_time_sensor_ns.class_("StopAction", Action) +ResetAction = duty_time_sensor_ns.class_("ResetAction", Action) +SetAction = duty_time_sensor_ns.class_("SetAction", Action) +RunningCondition = duty_time_sensor_ns.class_("RunningCondition", Condition) + + +CONFIG_SCHEMA = cv.All( + sensor.sensor_schema( + DutyTimeSensor, + unit_of_measurement=UNIT_SECOND, + icon="mdi:timer-play-outline", + accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL_INCREASING, + device_class=DEVICE_CLASS_DURATION, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + .extend( + { + cv.Optional(CONF_SENSOR): cv.use_id(binary_sensor.BinarySensor), + cv.Optional(CONF_LAMBDA): cv.lambda_, + cv.Optional(CONF_RESTORE, default=False): cv.boolean, + cv.Optional(CONF_LAST_TIME): sensor.sensor_schema( + unit_of_measurement=UNIT_SECOND, + icon="mdi:timer-marker-outline", + accuracy_decimals=3, + state_class=STATE_CLASS_TOTAL, + device_class=DEVICE_CLASS_DURATION, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .extend(cv.polling_component_schema("60s")), + cv.has_at_most_one_key(CONF_SENSOR, CONF_LAMBDA), +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + cg.add(var.set_restore(config[CONF_RESTORE])) + if CONF_SENSOR in config: + sens = await cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_sensor(sens)) + if CONF_LAMBDA in config: + lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.bool_) + cg.add(var.set_lambda(lambda_)) + if CONF_LAST_TIME in config: + sens = await sensor.new_sensor(config[CONF_LAST_TIME]) + cg.add(var.set_last_duty_time_sensor(sens)) + + +# AUTOMATIONS + +DUTY_TIME_ID_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(DutyTimeSensor), + } +) + + +@register_action("sensor.duty_time.start", StartAction, DUTY_TIME_ID_SCHEMA) +async def sensor_runtime_start_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) + + +@register_action("sensor.duty_time.stop", StopAction, DUTY_TIME_ID_SCHEMA) +async def sensor_runtime_stop_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +@register_action("sensor.duty_time.reset", ResetAction, DUTY_TIME_ID_SCHEMA) +async def sensor_runtime_reset_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) + + +@register_condition( + "sensor.duty_time.is_running", RunningCondition, DUTY_TIME_ID_SCHEMA +) +async def duty_time_is_running_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren, True) + + +@register_condition( + "sensor.duty_time.is_not_running", RunningCondition, DUTY_TIME_ID_SCHEMA +) +async def duty_time_is_not_running_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren, False) diff --git a/tests/test2.yaml b/tests/test2.yaml index 31fd840f5c..291dc240dc 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -416,6 +416,18 @@ sensor: name: Propane test distance battery_level: name: Propane test battery level + - platform: duty_time + id: duty_time1 + name: Test Duty Time + restore: true + last_time: + name: Test Last Duty Time Sensor + sensor: ha_hello_world_binary + - platform: duty_time + id: duty_time2 + name: Test Duty Time 2 + restore: false + lambda: "return true;" time: - platform: homeassistant @@ -423,6 +435,17 @@ time: - at: "16:00:00" then: - logger.log: It's 16:00 + - if: + condition: + - sensor.duty_time.is_running: duty_time2 + then: + - sensor.duty_time.start: duty_time1 + - if: + condition: + - sensor.duty_time.is_not_running: duty_time1 + then: + - sensor.duty_time.stop: duty_time2 + - sensor.duty_time.reset: duty_time1 esp32_touch: setup_mode: true From 9344d85414d26b5b2ff51624dfd6ceb6b8ecd01c Mon Sep 17 00:00:00 2001 From: Lewis Baker Date: Thu, 13 Jul 2023 06:27:45 +0930 Subject: [PATCH 104/106] Fix PIDController::in_deadband() to give correct result when error is zero (#5078) --- esphome/components/pid/pid_controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/components/pid/pid_controller.cpp b/esphome/components/pid/pid_controller.cpp index afc2d91000..30f6038325 100644 --- a/esphome/components/pid/pid_controller.cpp +++ b/esphome/components/pid/pid_controller.cpp @@ -29,7 +29,7 @@ float PIDController::update(float setpoint, float process_value) { bool PIDController::in_deadband() { // return (fabs(error) < deadband_threshold); float err = -error_; - return ((err > 0 && err < threshold_high_) || (err < 0 && err > threshold_low_)); + return (threshold_low_ < err && err < threshold_high_); } void PIDController::calculate_proportional_term_() { From 844cf316e2410c1f92d67ab32cc549c6064ef12a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:38:24 +1200 Subject: [PATCH 105/106] Edit error message for pillow install to add version restrictions (#5094) --- esphome/components/font/__init__.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 29150afe3f..52f877d986 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -67,13 +67,18 @@ def validate_pillow_installed(value): except ImportError as err: raise cv.Invalid( "Please install the pillow python package to use this feature. " - "(pip install pillow)" + '(pip install pillow">4.0.0,<10.0.0")' ) from err if version.parse(PIL.__version__) < version.parse("4.0.0"): raise cv.Invalid( "Please update your pillow installation to at least 4.0.x. " - "(pip install -U pillow)" + '(pip install pillow">4.0.0,<10.0.0")' + ) + if version.parse(PIL.__version__) >= version.parse("10.0.0"): + raise cv.Invalid( + "Please downgrade your pillow installation to below 10.0.0. " + '(pip install pillow">4.0.0,<10.0.0")' ) return value From 76b438f79c6d3087d162a6eaaee29048c5105bb0 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:50:48 +1200 Subject: [PATCH 106/106] Bump version to 2023.7.0b1 --- esphome/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/const.py b/esphome/const.py index 3442c392c7..e74f23e9b4 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2023.7.0-dev" +__version__ = "2023.7.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = (