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: 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/CODEOWNERS b/CODEOWNERS index cef045468c..be8a740eff 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 @@ -102,7 +103,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 @@ -319,4 +320,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/airthings_wave_base/__init__.py b/esphome/components/airthings_wave_base/__init__.py new file mode 100644 index 0000000000..c935ce108a --- /dev/null +++ b/esphome/components/airthings_wave_base/__init__.py @@ -0,0 +1,83 @@ +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, + DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, + 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, + device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS, + 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/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/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])) 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/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/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..3371cd9e71 --- /dev/null +++ b/esphome/components/display/animation.h @@ -0,0 +1,67 @@ +#pragma once +#include "image.h" + +#include "esphome/core/automation.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_; +}; + +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 diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index c8dc7b62e2..86e8624d33 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -12,115 +12,9 @@ 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); -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; - 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); @@ -271,54 +165,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) @@ -326,6 +180,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); } @@ -342,7 +227,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); @@ -380,34 +265,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); @@ -454,19 +339,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); } @@ -505,286 +391,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..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" @@ -70,17 +71,52 @@ 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; +/** 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, -enum ImageType { - IMAGE_TYPE_BINARY = 0, - IMAGE_TYPE_GRAYSCALE = 1, - IMAGE_TYPE_RGB24 = 2, - IMAGE_TYPE_RGB565 = 3, - IMAGE_TYPE_RGBA = 4, + 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 { @@ -96,35 +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 BaseImage; -class Font; class DisplayBuffer; class DisplayPage; class DisplayOnPageChangeTrigger; @@ -138,6 +145,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. @@ -184,7 +209,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`. * @@ -194,7 +219,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`. * @@ -204,7 +229,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`. * @@ -213,7 +238,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`. * @@ -225,7 +250,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`. @@ -237,7 +262,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`. * @@ -248,7 +273,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`. * @@ -258,7 +284,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`. * @@ -270,7 +296,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`. @@ -282,7 +308,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`. @@ -294,7 +320,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`. @@ -305,18 +331,29 @@ 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. * * @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. * @@ -364,7 +401,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. @@ -439,7 +476,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; @@ -475,123 +512,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..0a5881b48b --- /dev/null +++ b/esphome/components/display/font.cpp @@ -0,0 +1,147 @@ +#include "font.h" + +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace display { + +static const char *const TAG = "display"; + +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 unsigned char *data = this->glyph_data_->data; + const int max_x = x_at + scan_x1 + scan_width; + const int max_y = y_start + scan_y1 + scan_height; + + for (int glyph_y = y_start + scan_y1; glyph_y < max_y; glyph_y++) { + for (int glyph_x = x_at + scan_x1; glyph_x < max_x; data++, glyph_x += 8) { + uint8_t pixel_data = progmem_read_byte(data); + const int pixel_max_x = std::min(max_x, glyph_x + 8); + + for (int pixel_x = glyph_x; pixel_x < pixel_max_x && pixel_data; pixel_x++, pixel_data <<= 1) { + if (pixel_data & 0x80) { + display->draw_pixel_at(pixel_x, glyph_y, color); + } + } + } + } +} +const char *Glyph::get_char() const { return this->glyph_data_->a_char; } +bool Glyph::compare_to(const char *str) const { + // 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; +} + +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; + 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; +} +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 +} // namespace esphome diff --git a/esphome/components/display/font.h b/esphome/components/display/font.h new file mode 100644 index 0000000000..5ba6685a1c --- /dev/null +++ b/esphome/components/display/font.h @@ -0,0 +1,67 @@ +#pragma once + +#include "esphome/core/datatypes.h" +#include "display_buffer.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) {} + + void draw(int x, int y, DisplayBuffer *display, Color color) 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; + + const GlyphData *glyph_data_; +}; + +class Font : public BaseFont { + 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 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_; } + + 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..33c26ef127 --- /dev/null +++ b/esphome/components/display/image.cpp @@ -0,0 +1,134 @@ +#include "image.h" + +#include "esphome/core/hal.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..b16828a5be --- /dev/null +++ b/esphome/components/display/image.h @@ -0,0 +1,62 @@ +#pragma once +#include "esphome/core/color.h" +#include "display_buffer.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; } + +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/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 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 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}) {} 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" 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}; 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/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 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, 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) } 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(); 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", - ], - ) 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( 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) { diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index d8343c6c39..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), @@ -81,6 +83,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,27 +122,32 @@ 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])) + 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])) 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/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 diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 1ac94375c2..eb1858a09c 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -90,10 +90,27 @@ 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 + +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..."); @@ -102,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()); }); } @@ -159,20 +169,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 +186,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 +311,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..83ebba205f 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. @@ -77,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) @@ -92,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); @@ -240,12 +270,19 @@ 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}; + bool expose_log_{true}; #ifdef USE_ESP32 std::deque> to_schedule_; SemaphoreHandle_t to_schedule_lock_; 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(); 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"); 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/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" 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}; 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] 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() diff --git a/platformio.ini b/platformio.ini index b9d8981c2b..0f1432a283 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/requirements.txt b/requirements.txt index b994de1932..a2ef604446 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,9 +9,9 @@ pyserial==3.5 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 -zeroconf==0.63.0 +esphome-dashboard==20230621.0 +aioesphomeapi==14.1.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 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 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 diff --git a/tests/test2.yaml b/tests/test2.yaml index 2873d0e8e0..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: @@ -695,6 +697,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 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 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