diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4add58dfbe..7f99701e39 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -9,4 +9,3 @@ contact_links: - name: Frequently Asked Question url: https://esphome.io/guides/faq.html about: Please view the FAQ for common questions and what to include in a bug report. - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 25411c19f5..f9c8cce0af 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -# What does this implement/fix? +# What does this implement/fix? Quick description and explanation of changes @@ -35,6 +35,6 @@ Quick description and explanation of changes ## Checklist: - [ ] The code change is tested and works locally. - [ ] Tests have been added to verify that the new code works (under `tests/` folder). - + If user exposed functionality or configuration variables are added/changed: - [ ] Documentation added/updated in [esphome-docs](https://github.com/esphome/esphome-docs). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9473dc87dc..c2ad769156 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,7 @@ jobs: uses: actions/setup-python@v2 id: python with: - python-version: '3.7' + python-version: '3.8' - name: Cache virtualenv uses: actions/cache@v2 diff --git a/.gitignore b/.gitignore index 57b8478bd7..110437c368 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ venv/ ENV/ env.bak/ venv.bak/ +venv-*/ # mypy .mypy_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e38717fe5b..9549d5cedc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,3 +25,8 @@ repos: - --branch=dev - --branch=release - --branch=beta + - repo: https://github.com/asottile/pyupgrade + rev: v2.31.0 + hooks: + - id: pyupgrade + args: [--py38-plus] diff --git a/CODEOWNERS b/CODEOWNERS index 165ce52485..c111fa7816 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -19,6 +19,7 @@ esphome/components/airthings_wave_mini/* @ncareau esphome/components/airthings_wave_plus/* @jeromelaban esphome/components/am43/* @buxtronix esphome/components/am43/cover/* @buxtronix +esphome/components/analog_threshold/* @ianchi esphome/components/animation/* @syndlex esphome/components/anova/* @buxtronix esphome/components/api/* @OttoWinter @@ -27,6 +28,7 @@ esphome/components/atc_mithermometer/* @ahpohl esphome/components/b_parasite/* @rbaron esphome/components/ballu/* @bazuchan esphome/components/bang_bang/* @OttoWinter +esphome/components/bh1750/* @OttoWinter esphome/components/binary_sensor/* @esphome/core esphome/components/bl0940/* @tobias- esphome/components/ble_client/* @buxtronix @@ -42,6 +44,7 @@ esphome/components/climate/* @esphome/core esphome/components/climate_ir/* @glmnet esphome/components/color_temperature/* @jesserockz esphome/components/coolix/* @glmnet +esphome/components/copy/* @OttoWinter esphome/components/cover/* @esphome/core esphome/components/cs5460a/* @balrog-kun esphome/components/cse7761/* @berfenger @@ -77,6 +80,7 @@ esphome/components/hbridge/light/* @DotNetDann esphome/components/heatpumpir/* @rob-deutsch esphome/components/hitachi_ac424/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter +esphome/components/honeywellabp/* @RubyBailey esphome/components/hrxl_maxsonar_wr/* @netmikey esphome/components/i2c/* @esphome/core esphome/components/improv_serial/* @esphome/core @@ -93,6 +97,7 @@ esphome/components/lilygo_t5_47/touchscreen/* @jesserockz esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny +esphome/components/max44009/* @berfenger esphome/components/max7219digit/* @rspaargaren esphome/components/max9611/* @mckaymatthew esphome/components/mcp23008/* @jesserockz @@ -104,6 +109,7 @@ esphome/components/mcp23x17_base/* @jesserockz esphome/components/mcp23xxx_base/* @jesserockz esphome/components/mcp2515/* @danielschramm @mvturnho esphome/components/mcp3204/* @rsumner +esphome/components/mcp4728/* @berfenger esphome/components/mcp47a1/* @jesserockz esphome/components/mcp9808/* @k7hpn esphome/components/md5/* @esphome/core @@ -120,6 +126,9 @@ esphome/components/modbus_controller/select/* @martgras @stegm esphome/components/modbus_controller/sensor/* @martgras esphome/components/modbus_controller/switch/* @martgras esphome/components/modbus_controller/text_sensor/* @martgras +esphome/components/mopeka_ble/* @spbrogan +esphome/components/mopeka_pro_check/* @spbrogan +esphome/components/mpu6886/* @fabaff esphome/components/network/* @esphome/core esphome/components/nextion/* @senexcrenshaw esphome/components/nextion/binary_sensor/* @senexcrenshaw @@ -140,7 +149,7 @@ esphome/components/pn532_spi/* @OttoWinter @jesserockz esphome/components/power_supply/* @esphome/core esphome/components/preferences/* @esphome/core esphome/components/psram/* @esphome/core -esphome/components/pulse_meter/* @stevebaxter +esphome/components/pulse_meter/* @cstaahl @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz esphome/components/qr_code/* @wjtje esphome/components/radon_eye_ble/* @jeffeb3 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e96bb5745b..ec23656763 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ For a detailed guide, please see https://esphome.io/guides/contributing.html#con Things to note when contributing: - Please test your changes :) - - If a new feature is added or an existing user-facing feature is changed, you should also + - If a new feature is added or an existing user-facing feature is changed, you should also update the [docs](https://github.com/esphome/esphome-docs). See [contributing to esphome-docs](https://esphome.io/guides/contributing.html#contributing-to-esphomedocs) for more information. - Please also update the tests in the `tests/` folder. You can do so by just adding a line in one of the YAML files diff --git a/docker/Dockerfile b/docker/Dockerfile index 6735037c1e..65e831f89b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,9 +5,11 @@ # One of "docker", "hassio" ARG BASEIMGTYPE=docker +# https://github.com/hassio-addons/addon-debian-base/releases FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64 FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64 FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7 +# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye FROM debian:bullseye-20220125-slim AS base-docker-amd64 FROM debian:bullseye-20220125-slim AS base-docker-arm64 FROM debian:bullseye-20220125-slim AS base-docker-armv7 @@ -52,16 +54,16 @@ RUN \ && mkdir -p /piolibs - -# ======================= docker-type image ======================= -FROM base AS docker - # First install requirements to leverage caching when requirements don't change COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / RUN \ pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ && /platformio_install_deps.py /platformio.ini + +# ======================= docker-type image ======================= +FROM base AS docker + # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome @@ -93,7 +95,7 @@ RUN \ apt-get update \ # Use pinned versions so that we get updates with build caching && apt-get install -y --no-install-recommends \ - nginx=1.18.0-6.1 \ + nginx-light=1.18.0-6.1 \ && rm -rf \ /tmp/* \ /var/{cache,log}/* \ @@ -104,12 +106,6 @@ ARG BUILD_VERSION=dev # Copy root filesystem COPY docker/ha-addon-rootfs/ / -# First install requirements to leverage caching when requirements don't change -COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ - && /platformio_install_deps.py /platformio.ini - # Copy esphome and install COPY . /esphome RUN pip3 install --no-cache-dir --no-use-pep517 -e /esphome @@ -147,10 +143,8 @@ RUN \ /var/{cache,log}/* \ /var/lib/apt/lists/* -COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py platformio.ini / -RUN \ - pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ - && /platformio_install_deps.py /platformio.ini +COPY requirements_test.txt / +RUN pip3 install --no-cache-dir -r /requirements_test.txt VOLUME ["/esphome"] WORKDIR /esphome diff --git a/esphome/__main__.py b/esphome/__main__.py index a64f096d54..85cf4ede85 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -778,10 +778,10 @@ def run_esphome(argv): _LOGGER.warning("Please instead use:") _LOGGER.warning(" esphome %s", " ".join(args.deprecated_argv_suggestion)) - if sys.version_info < (3, 7, 0): + if sys.version_info < (3, 8, 0): _LOGGER.error( - "You're running ESPHome with Python <3.7. ESPHome is no longer compatible " - "with this Python version. Please reinstall ESPHome with Python 3.7+" + "You're running ESPHome with Python <3.8. ESPHome is no longer compatible " + "with this Python version. Please reinstall ESPHome with Python 3.8+" ) return 1 diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py index c812e67a68..5443b9875a 100644 --- a/esphome/components/adc/sensor.py +++ b/esphome/components/adc/sensor.py @@ -133,6 +133,7 @@ ADCSensor = adc_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + ADCSensor, unit_of_measurement=UNIT_VOLT, accuracy_decimals=2, device_class=DEVICE_CLASS_VOLTAGE, @@ -140,7 +141,6 @@ CONFIG_SCHEMA = cv.All( ) .extend( { - cv.GenerateID(): cv.declare_id(ADCSensor), cv.Required(CONF_PIN): validate_adc_pin, cv.Optional(CONF_RAW, default=False): cv.boolean, cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All( diff --git a/esphome/components/ads1115/sensor.py b/esphome/components/ads1115/sensor.py index da33a39041..190e641ca3 100644 --- a/esphome/components/ads1115/sensor.py +++ b/esphome/components/ads1115/sensor.py @@ -52,6 +52,7 @@ ADS1115Sensor = ads1115_ns.class_( CONF_ADS1115_ID = "ads1115_id" CONFIG_SCHEMA = ( sensor.sensor_schema( + ADS1115Sensor, unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, @@ -59,7 +60,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(ADS1115Sensor), cv.GenerateID(CONF_ADS1115_ID): cv.use_id(ADS1115Component), cv.Required(CONF_MULTIPLEXER): cv.enum(MUX, upper=True, space="_"), cv.Required(CONF_GAIN): validate_gain, diff --git a/esphome/components/analog_threshold/__init__.py b/esphome/components/analog_threshold/__init__.py new file mode 100644 index 0000000000..9ae2df986d --- /dev/null +++ b/esphome/components/analog_threshold/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@ianchi"] diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp new file mode 100644 index 0000000000..f679b9994f --- /dev/null +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.cpp @@ -0,0 +1,40 @@ +#include "analog_threshold_binary_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace analog_threshold { + +static const char *const TAG = "analog_threshold.binary_sensor"; + +void AnalogThresholdBinarySensor::setup() { + float sensor_value = this->sensor_->get_state(); + + // TRUE state is defined to be when sensor is >= threshold + // so when undefined sensor value initialize to FALSE + if (std::isnan(sensor_value)) { + this->publish_initial_state(false); + } else { + this->publish_initial_state(sensor_value >= (this->lower_threshold_ + this->upper_threshold_) / 2.0f); + } +} + +void AnalogThresholdBinarySensor::set_sensor(sensor::Sensor *analog_sensor) { + this->sensor_ = analog_sensor; + + this->sensor_->add_on_state_callback([this](float sensor_value) { + // if there is an invalid sensor reading, ignore the change and keep the current state + if (!std::isnan(sensor_value)) { + this->publish_state(sensor_value >= (this->state ? this->lower_threshold_ : this->upper_threshold_)); + } + }); +} + +void AnalogThresholdBinarySensor::dump_config() { + LOG_BINARY_SENSOR("", "Analog Threshold Binary Sensor", this); + LOG_SENSOR(" ", "Sensor", this->sensor_); + ESP_LOGCONFIG(TAG, " Upper threshold: %.11f", this->upper_threshold_); + ESP_LOGCONFIG(TAG, " Lower threshold: %.11f", this->lower_threshold_); +} + +} // namespace analog_threshold +} // namespace esphome diff --git a/esphome/components/analog_threshold/analog_threshold_binary_sensor.h b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h new file mode 100644 index 0000000000..619aef1075 --- /dev/null +++ b/esphome/components/analog_threshold/analog_threshold_binary_sensor.h @@ -0,0 +1,29 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace analog_threshold { + +class AnalogThresholdBinarySensor : public Component, public binary_sensor::BinarySensor { + public: + void dump_config() override; + void setup() override; + + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_sensor(sensor::Sensor *analog_sensor); + void set_upper_threshold(float threshold) { this->upper_threshold_ = threshold; } + void set_lower_threshold(float threshold) { this->lower_threshold_ = threshold; } + + protected: + sensor::Sensor *sensor_{nullptr}; + + float upper_threshold_; + float lower_threshold_; +}; + +} // namespace analog_threshold +} // namespace esphome diff --git a/esphome/components/analog_threshold/binary_sensor.py b/esphome/components/analog_threshold/binary_sensor.py new file mode 100644 index 0000000000..ef4a6044bf --- /dev/null +++ b/esphome/components/analog_threshold/binary_sensor.py @@ -0,0 +1,44 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor, sensor +from esphome.const import ( + CONF_SENSOR_ID, + CONF_THRESHOLD, +) + +analog_threshold_ns = cg.esphome_ns.namespace("analog_threshold") + +AnalogThresholdBinarySensor = analog_threshold_ns.class_( + "AnalogThresholdBinarySensor", binary_sensor.BinarySensor, cg.Component +) + +CONF_UPPER = "upper" +CONF_LOWER = "lower" + +CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(AnalogThresholdBinarySensor), + cv.Required(CONF_SENSOR_ID): cv.use_id(sensor.Sensor), + cv.Required(CONF_THRESHOLD): cv.Any( + cv.float_, + cv.Schema( + {cv.Required(CONF_UPPER): cv.float_, cv.Required(CONF_LOWER): cv.float_} + ), + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + sens = await cg.get_variable(config[CONF_SENSOR_ID]) + cg.add(var.set_sensor(sens)) + + if isinstance(config[CONF_THRESHOLD], float): + cg.add(var.set_upper_threshold(config[CONF_THRESHOLD])) + cg.add(var.set_lower_threshold(config[CONF_THRESHOLD])) + else: + cg.add(var.set_upper_threshold(config[CONF_THRESHOLD][CONF_UPPER])) + cg.add(var.set_lower_threshold(config[CONF_THRESHOLD][CONF_LOWER])) diff --git a/esphome/components/apds9960/binary_sensor.py b/esphome/components/apds9960/binary_sensor.py index 4a5c69f6a9..04dc6f4d5d 100644 --- a/esphome/components/apds9960/binary_sensor.py +++ b/esphome/components/apds9960/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_DIRECTION, CONF_DEVICE_CLASS, DEVICE_CLASS_MOVING +from esphome.const import CONF_DIRECTION, DEVICE_CLASS_MOVING from . import APDS9960, CONF_APDS9960_ID DEPENDENCIES = ["apds9960"] @@ -13,13 +13,12 @@ DIRECTIONS = { "RIGHT": "set_right_direction", } -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_MOVING +).extend( { - cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), cv.GenerateID(CONF_APDS9960_ID): cv.use_id(APDS9960), - cv.Optional( - CONF_DEVICE_CLASS, default=DEVICE_CLASS_MOVING - ): binary_sensor.device_class, + cv.Required(CONF_DIRECTION): cv.one_of(*DIRECTIONS, upper=True), } ) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index d9ce6cd79e..b998ef5929 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -105,6 +105,7 @@ void APIConnection::loop() { ESP_LOGW(TAG, "%s didn't respond to ping request in time. Disconnecting...", this->client_info_.c_str()); } } else if (now - this->last_traffic_ > keepalive) { + ESP_LOGVV(TAG, "Sending keepalive PING..."); this->sent_ping_ = true; this->send_ping_request(PingRequest()); } @@ -908,7 +909,7 @@ bool APIConnection::send_buffer(ProtoWriteBuffer buffer, uint32_t message_type) } return false; } - this->last_traffic_ = millis(); + // Do not set last_traffic_ on send return true; } void APIConnection::on_unauthenticated_access() { diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py index b2920f239b..c777c3be9d 100644 --- a/esphome/components/api/client.py +++ b/esphome/components/api/client.py @@ -21,7 +21,6 @@ async def async_run_logs(config, address): if CONF_ENCRYPTION in conf: noise_psk = conf[CONF_ENCRYPTION][CONF_KEY] _LOGGER.info("Starting log output from %s using esphome API", address) - zc = zeroconf.Zeroconf() cli = APIClient( address, port, diff --git a/esphome/components/as3935/binary_sensor.py b/esphome/components/as3935/binary_sensor.py index 11b2ac812c..3081d2115f 100644 --- a/esphome/components/as3935/binary_sensor.py +++ b/esphome/components/as3935/binary_sensor.py @@ -5,7 +5,7 @@ from . import AS3935, CONF_AS3935_ID DEPENDENCIES = ["as3935"] -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend( { cv.GenerateID(CONF_AS3935_ID): cv.use_id(AS3935), } diff --git a/esphome/components/as3935/sensor.py b/esphome/components/as3935/sensor.py index 271a29e0fc..5a3967ed7e 100644 --- a/esphome/components/as3935/sensor.py +++ b/esphome/components/as3935/sensor.py @@ -4,7 +4,6 @@ from esphome.components import sensor from esphome.const import ( CONF_DISTANCE, CONF_LIGHTNING_ENERGY, - STATE_CLASS_NONE, UNIT_KILOMETER, ICON_SIGNAL_DISTANCE_VARIANT, ICON_FLASH, @@ -20,12 +19,10 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_KILOMETER, icon=ICON_SIGNAL_DISTANCE_VARIANT, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LIGHTNING_ENERGY): sensor.sensor_schema( icon=ICON_FLASH, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp index 4e6bb3c563..de6d811ed2 100644 --- a/esphome/components/bh1750/bh1750.cpp +++ b/esphome/components/bh1750/bh1750.cpp @@ -9,18 +9,109 @@ static const char *const TAG = "bh1750.sensor"; static const uint8_t BH1750_COMMAND_POWER_ON = 0b00000001; static const uint8_t BH1750_COMMAND_MT_REG_HI = 0b01000000; // last 3 bits static const uint8_t BH1750_COMMAND_MT_REG_LO = 0b01100000; // last 5 bits +static const uint8_t BH1750_COMMAND_ONE_TIME_L = 0b00100011; +static const uint8_t BH1750_COMMAND_ONE_TIME_H = 0b00100000; +static const uint8_t BH1750_COMMAND_ONE_TIME_H2 = 0b00100001; + +/* +bh1750 properties: + +L-resolution mode: +- resolution 4lx (@ mtreg=69) +- measurement time: typ=16ms, max=24ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) lx +H-resolution mode: +- resolution 1lx (@ mtreg=69) +- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) lx +H-resolution mode2: +- resolution 0.5lx (@ mtreg=69) +- measurement time: typ=120ms, max=180ms, scaled by MTreg value divided by 69 +- formula: counts / 1.2 * (69 / MTreg) / 2 lx + +MTreg: +- min=31, default=69, max=254 + +-> only reason to use l-resolution is faster, but offers no higher range +-> below ~7000lx, makes sense to use H-resolution2 @ MTreg=254 +-> try to maximize MTreg to get lowest noise level +*/ void BH1750Sensor::setup() { ESP_LOGCONFIG(TAG, "Setting up BH1750 '%s'...", this->name_.c_str()); - if (!this->write_bytes(BH1750_COMMAND_POWER_ON, nullptr, 0)) { + uint8_t turn_on = BH1750_COMMAND_POWER_ON; + if (this->write(&turn_on, 1) != i2c::ERROR_OK) { this->mark_failed(); return; } +} - uint8_t mtreg_hi = (this->measurement_duration_ >> 5) & 0b111; - uint8_t mtreg_lo = (this->measurement_duration_ >> 0) & 0b11111; - this->write_bytes(BH1750_COMMAND_MT_REG_HI | mtreg_hi, nullptr, 0); - this->write_bytes(BH1750_COMMAND_MT_REG_LO | mtreg_lo, nullptr, 0); +void BH1750Sensor::read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f) { + // turn on (after one-shot sensor automatically powers down) + uint8_t turn_on = BH1750_COMMAND_POWER_ON; + if (this->write(&turn_on, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Turning on BH1750 failed"); + f(NAN); + return; + } + + if (active_mtreg_ != mtreg) { + // set mtreg + uint8_t mtreg_hi = BH1750_COMMAND_MT_REG_HI | ((mtreg >> 5) & 0b111); + uint8_t mtreg_lo = BH1750_COMMAND_MT_REG_LO | ((mtreg >> 0) & 0b11111); + if (this->write(&mtreg_hi, 1) != i2c::ERROR_OK || this->write(&mtreg_lo, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Setting measurement time for BH1750 failed"); + active_mtreg_ = 0; + f(NAN); + return; + } + active_mtreg_ = mtreg; + } + + uint8_t cmd; + uint16_t meas_time; + switch (mode) { + case BH1750_MODE_L: + cmd = BH1750_COMMAND_ONE_TIME_L; + meas_time = 24 * mtreg / 69; + break; + case BH1750_MODE_H: + cmd = BH1750_COMMAND_ONE_TIME_H; + meas_time = 180 * mtreg / 69; + break; + case BH1750_MODE_H2: + cmd = BH1750_COMMAND_ONE_TIME_H2; + meas_time = 180 * mtreg / 69; + break; + default: + f(NAN); + return; + } + if (this->write(&cmd, 1) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Starting measurement for BH1750 failed"); + f(NAN); + return; + } + + // probably not needed, but adjust for rounding + meas_time++; + + this->set_timeout("read", meas_time, [this, mode, mtreg, f]() { + uint16_t raw_value; + if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { + ESP_LOGW(TAG, "Reading BH1750 data failed"); + f(NAN); + return; + } + raw_value = i2c::i2ctohs(raw_value); + + float lx = float(raw_value) / 1.2f; + lx *= 69.0f / mtreg; + if (mode == BH1750_MODE_H2) + lx /= 2.0f; + + f(lx); + }); } void BH1750Sensor::dump_config() { @@ -30,64 +121,49 @@ void BH1750Sensor::dump_config() { ESP_LOGE(TAG, "Communication with BH1750 failed!"); } - const char *resolution_s; - switch (this->resolution_) { - case BH1750_RESOLUTION_0P5_LX: - resolution_s = "0.5"; - break; - case BH1750_RESOLUTION_1P0_LX: - resolution_s = "1"; - break; - case BH1750_RESOLUTION_4P0_LX: - resolution_s = "4"; - break; - default: - resolution_s = "Unknown"; - break; - } - ESP_LOGCONFIG(TAG, " Resolution: %s", resolution_s); LOG_UPDATE_INTERVAL(this); } void BH1750Sensor::update() { - if (!this->write_bytes(this->resolution_, nullptr, 0)) - return; + // first do a quick measurement in L-mode with full range + // to find right range + this->read_lx_(BH1750_MODE_L, 31, [this](float val) { + if (std::isnan(val)) { + this->status_set_warning(); + this->publish_state(NAN); + return; + } - uint32_t wait = 0; - // use max conversion times - switch (this->resolution_) { - case BH1750_RESOLUTION_0P5_LX: - case BH1750_RESOLUTION_1P0_LX: - wait = 180; - break; - case BH1750_RESOLUTION_4P0_LX: - wait = 24; - break; - } + BH1750Mode use_mode; + uint8_t use_mtreg; + if (val <= 7000) { + use_mode = BH1750_MODE_H2; + use_mtreg = 254; + } else { + use_mode = BH1750_MODE_H; + // lx = counts / 1.2 * (69 / mtreg) + // -> mtreg = counts / 1.2 * (69 / lx) + // calculate for counts=50000 (allow some range to not saturate, but maximize mtreg) + // -> mtreg = 50000*(10/12)*(69/lx) + int ideal_mtreg = 50000 * 10 * 69 / (12 * (int) val); + use_mtreg = std::min(254, std::max(31, ideal_mtreg)); + } + ESP_LOGV(TAG, "L result: %f -> Calculated mode=%d, mtreg=%d", val, (int) use_mode, use_mtreg); - this->set_timeout("illuminance", wait, [this]() { this->read_data_(); }); + this->read_lx_(use_mode, use_mtreg, [this](float val) { + if (std::isnan(val)) { + this->status_set_warning(); + this->publish_state(NAN); + return; + } + ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), val); + this->status_clear_warning(); + this->publish_state(val); + }); + }); } float BH1750Sensor::get_setup_priority() const { return setup_priority::DATA; } -void BH1750Sensor::read_data_() { - uint16_t raw_value; - if (this->read(reinterpret_cast(&raw_value), 2) != i2c::ERROR_OK) { - this->status_set_warning(); - return; - } - raw_value = i2c::i2ctohs(raw_value); - - float lx = float(raw_value) / 1.2f; - lx *= 69.0f / this->measurement_duration_; - if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) { - lx /= 2.0f; - } - ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx); - this->publish_state(lx); - this->status_clear_warning(); -} - -void BH1750Sensor::set_resolution(BH1750Resolution resolution) { this->resolution_ = resolution; } } // namespace bh1750 } // namespace esphome diff --git a/esphome/components/bh1750/bh1750.h b/esphome/components/bh1750/bh1750.h index c88fa10832..a31eb33609 100644 --- a/esphome/components/bh1750/bh1750.h +++ b/esphome/components/bh1750/bh1750.h @@ -7,29 +7,15 @@ namespace esphome { namespace bh1750 { -/// Enum listing all resolutions that can be used with the BH1750 -enum BH1750Resolution { - BH1750_RESOLUTION_4P0_LX = 0b00100011, // one-time low resolution mode - BH1750_RESOLUTION_1P0_LX = 0b00100000, // one-time high resolution mode 1 - BH1750_RESOLUTION_0P5_LX = 0b00100001, // one-time high resolution mode 2 +enum BH1750Mode { + BH1750_MODE_L, + BH1750_MODE_H, + BH1750_MODE_H2, }; /// This class implements support for the i2c-based BH1750 ambient light sensor. class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { public: - /** Set the resolution of this sensor. - * - * Possible values are: - * - * - `BH1750_RESOLUTION_4P0_LX` - * - `BH1750_RESOLUTION_1P0_LX` - * - `BH1750_RESOLUTION_0P5_LX` (default) - * - * @param resolution The new resolution of the sensor. - */ - void set_resolution(BH1750Resolution resolution); - void set_measurement_duration(uint8_t measurement_duration) { measurement_duration_ = measurement_duration; } - // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) void setup() override; @@ -38,10 +24,9 @@ class BH1750Sensor : public sensor::Sensor, public PollingComponent, public i2c: float get_setup_priority() const override; protected: - void read_data_(); + void read_lx_(BH1750Mode mode, uint8_t mtreg, const std::function &f); - BH1750Resolution resolution_{BH1750_RESOLUTION_0P5_LX}; - uint8_t measurement_duration_; + uint8_t active_mtreg_{0}; }; } // namespace bh1750 diff --git a/esphome/components/bh1750/sensor.py b/esphome/components/bh1750/sensor.py index 156c7bb375..69778f49ce 100644 --- a/esphome/components/bh1750/sensor.py +++ b/esphome/components/bh1750/sensor.py @@ -2,31 +2,23 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, - CONF_RESOLUTION, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, UNIT_LUX, - CONF_MEASUREMENT_DURATION, ) DEPENDENCIES = ["i2c"] +CODEOWNERS = ["@OttoWinter"] bh1750_ns = cg.esphome_ns.namespace("bh1750") -BH1750Resolution = bh1750_ns.enum("BH1750Resolution") -BH1750_RESOLUTIONS = { - 4.0: BH1750Resolution.BH1750_RESOLUTION_4P0_LX, - 1.0: BH1750Resolution.BH1750_RESOLUTION_1P0_LX, - 0.5: BH1750Resolution.BH1750_RESOLUTION_0P5_LX, -} BH1750Sensor = bh1750_ns.class_( "BH1750Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice ) -CONF_MEASUREMENT_TIME = "measurement_time" CONFIG_SCHEMA = ( sensor.sensor_schema( + BH1750Sensor, unit_of_measurement=UNIT_LUX, accuracy_decimals=1, device_class=DEVICE_CLASS_ILLUMINANCE, @@ -34,15 +26,11 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(BH1750Sensor), - cv.Optional(CONF_RESOLUTION, default=0.5): cv.enum( - BH1750_RESOLUTIONS, float=True + cv.Optional("resolution"): cv.invalid( + "The 'resolution' option has been removed. The optimal value is now dynamically calculated." ), - cv.Optional(CONF_MEASUREMENT_DURATION, default=69): cv.int_range( - min=31, max=254 - ), - cv.Optional(CONF_MEASUREMENT_TIME): cv.invalid( - "The 'measurement_time' option has been replaced with 'measurement_duration' in 1.18.0" + cv.Optional("measurement_duration"): cv.invalid( + "The 'measurement_duration' option has been removed. The optimal value is now dynamically calculated." ), } ) @@ -52,10 +40,6 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) - - cg.add(var.set_resolution(config[CONF_RESOLUTION])) - cg.add(var.set_measurement_duration(config[CONF_MEASUREMENT_DURATION])) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 1eab76d54e..40f95d72f9 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -1,5 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome import automation, core from esphome.automation import Condition, maybe_simple_id @@ -7,7 +8,9 @@ from esphome.components import mqtt from esphome.const import ( CONF_DELAY, CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, CONF_FILTERS, + CONF_ICON, CONF_ID, CONF_INVALID_COOLDOWN, CONF_INVERTED, @@ -22,7 +25,6 @@ from esphome.const import ( CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, - CONF_NAME, CONF_MQTT_ID, DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, @@ -315,7 +317,7 @@ def validate_multi_click_timing(value): return timings -device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") +validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_") BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( @@ -324,7 +326,7 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id( mqtt.MQTTBinarySensorComponent ), - cv.Optional(CONF_DEVICE_CLASS): device_class, + cv.Optional(CONF_DEVICE_CLASS): validate_device_class, cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_PRESS): automation.validate_automation( { @@ -377,6 +379,39 @@ BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).ex } ) +_UNDEF = object() + + +def binary_sensor_schema( + class_: MockObjClass = _UNDEF, + *, + icon: str = _UNDEF, + entity_category: str = _UNDEF, + device_class: str = _UNDEF, +) -> cv.Schema: + schema = BINARY_SENSOR_SCHEMA + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) + if icon is not _UNDEF: + schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) + if entity_category is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_ENTITY_CATEGORY, default=entity_category + ): cv.entity_category + } + ) + if device_class is not _UNDEF: + schema = schema.extend( + { + cv.Optional( + CONF_DEVICE_CLASS, default=device_class + ): validate_device_class + } + ) + return schema + async def setup_binary_sensor_core_(var, config): await setup_entity(var, config) @@ -443,7 +478,7 @@ async def register_binary_sensor(var, config): async def new_binary_sensor(config): - var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME]) + var = cg.new_Pvariable(config[CONF_ID]) await register_binary_sensor(var, config) return var diff --git a/esphome/components/binary_sensor/binary_sensor.cpp b/esphome/components/binary_sensor/binary_sensor.cpp index 71422609d7..02735feaae 100644 --- a/esphome/components/binary_sensor/binary_sensor.cpp +++ b/esphome/components/binary_sensor/binary_sensor.cpp @@ -42,8 +42,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) { } } std::string BinarySensor::device_class() { return ""; } -BinarySensor::BinarySensor(const std::string &name) : EntityBase(name), state(false) {} -BinarySensor::BinarySensor() : BinarySensor("") {} +BinarySensor::BinarySensor() : state(false) {} void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } std::string BinarySensor::get_device_class() { if (this->device_class_.has_value()) diff --git a/esphome/components/binary_sensor/binary_sensor.h b/esphome/components/binary_sensor/binary_sensor.h index 591f444387..b5d1244bce 100644 --- a/esphome/components/binary_sensor/binary_sensor.h +++ b/esphome/components/binary_sensor/binary_sensor.h @@ -26,11 +26,6 @@ namespace binary_sensor { class BinarySensor : public EntityBase { public: explicit BinarySensor(); - /** Construct a binary sensor with the specified name - * - * @param name Name of this binary sensor. - */ - explicit BinarySensor(const std::string &name); /** Add a callback to be notified of state changes. * diff --git a/esphome/components/binary_sensor_map/sensor.py b/esphome/components/binary_sensor_map/sensor.py index 946e2f9e62..7ddf0ecf2a 100644 --- a/esphome/components/binary_sensor_map/sensor.py +++ b/esphome/components/binary_sensor_map/sensor.py @@ -3,14 +3,12 @@ import esphome.config_validation as cv from esphome.components import sensor, binary_sensor from esphome.const import ( - CONF_ID, CONF_CHANNELS, CONF_VALUE, CONF_TYPE, ICON_CHECK_CIRCLE_OUTLINE, CONF_BINARY_SENSOR, CONF_GROUP, - STATE_CLASS_NONE, ) DEPENDENCIES = ["binary_sensor"] @@ -33,12 +31,11 @@ entry = { CONFIG_SCHEMA = cv.typed_schema( { CONF_GROUP: sensor.sensor_schema( + BinarySensorMap, icon=ICON_CHECK_CIRCLE_OUTLINE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ).extend( { - cv.GenerateID(): cv.declare_id(BinarySensorMap), cv.Required(CONF_CHANNELS): cv.All( cv.ensure_list(entry), cv.Length(min=1) ), @@ -50,9 +47,8 @@ CONFIG_SCHEMA = cv.typed_schema( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) constant = SENSOR_MAP_TYPES[config[CONF_TYPE]] cg.add(var.set_sensor_type(constant)) diff --git a/esphome/components/bl0940/sensor.py b/esphome/components/bl0940/sensor.py index ce630b7408..9f516a8691 100644 --- a/esphome/components/bl0940/sensor.py +++ b/esphome/components/bl0940/sensor.py @@ -12,9 +12,7 @@ from esphome.const import ( DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_TEMPERATURE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_AMPERE, UNIT_CELSIUS, UNIT_KILOWATT_HOURS, @@ -35,38 +33,39 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(BL0940), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_EMPTY, - 2, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_POWER): sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_ENERGY): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 0, - DEVICE_CLASS_ENERGY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, ), cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_EMPTY, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), } ) diff --git a/esphome/components/ble_client/sensor/__init__.py b/esphome/components/ble_client/sensor/__init__.py index 4aa6a92ba5..71cfb03ae0 100644 --- a/esphome/components/ble_client/sensor/__init__.py +++ b/esphome/components/ble_client/sensor/__init__.py @@ -2,9 +2,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, ble_client, esp32_ble_tracker from esphome.const import ( - CONF_ID, CONF_LAMBDA, - STATE_CLASS_NONE, CONF_TRIGGER_ID, CONF_SERVICE_UUID, ) @@ -31,12 +29,11 @@ BLESensorNotifyTrigger = ble_client_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + BLESensor, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(BLESensor), cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, @@ -57,7 +54,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): cg.add( var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) @@ -124,7 +121,6 @@ async def to_code(config): await cg.register_component(var, config) await ble_client.register_ble_node(var, config) cg.add(var.set_enable_notify(config[CONF_NOTIFY])) - await sensor.register_sensor(var, config) for conf in config.get(CONF_ON_NOTIFY, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) await ble_client.register_ble_node(trigger, config) diff --git a/esphome/components/ble_client/text_sensor/__init__.py b/esphome/components/ble_client/text_sensor/__init__.py new file mode 100644 index 0000000000..e1f97e4a01 --- /dev/null +++ b/esphome/components/ble_client/text_sensor/__init__.py @@ -0,0 +1,121 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor, ble_client, esp32_ble_tracker +from esphome.const import ( + CONF_ID, + CONF_TRIGGER_ID, + CONF_SERVICE_UUID, +) +from esphome import automation +from .. import ble_client_ns + +DEPENDENCIES = ["ble_client"] + +CONF_CHARACTERISTIC_UUID = "characteristic_uuid" +CONF_DESCRIPTOR_UUID = "descriptor_uuid" + +CONF_NOTIFY = "notify" +CONF_ON_NOTIFY = "on_notify" + +adv_data_t = cg.std_vector.template(cg.uint8) +adv_data_t_const_ref = adv_data_t.operator("ref").operator("const") + +BLETextSensor = ble_client_ns.class_( + "BLETextSensor", + text_sensor.TextSensor, + cg.PollingComponent, + ble_client.BLEClientNode, +) +BLETextSensorNotifyTrigger = ble_client_ns.class_( + "BLETextSensorNotifyTrigger", automation.Trigger.template(cg.std_string) +) + +CONFIG_SCHEMA = cv.All( + text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(BLETextSensor), + cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, + cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_DESCRIPTOR_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_NOTIFY, default=False): cv.boolean, + cv.Optional(CONF_ON_NOTIFY): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( + BLETextSensorNotifyTrigger + ), + } + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(ble_client.BLE_CLIENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + if len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_service_uuid16(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid32_format): + cg.add( + var.set_service_uuid32(esp32_ble_tracker.as_hex(config[CONF_SERVICE_UUID])) + ) + elif len(config[CONF_SERVICE_UUID]) == len(esp32_ble_tracker.bt_uuid128_format): + uuid128 = esp32_ble_tracker.as_reversed_hex_array(config[CONF_SERVICE_UUID]) + cg.add(var.set_service_uuid128(uuid128)) + + if len(config[CONF_CHARACTERISTIC_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_char_uuid16( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid32_format + ): + cg.add( + var.set_char_uuid32( + esp32_ble_tracker.as_hex(config[CONF_CHARACTERISTIC_UUID]) + ) + ) + elif len(config[CONF_CHARACTERISTIC_UUID]) == len( + esp32_ble_tracker.bt_uuid128_format + ): + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_CHARACTERISTIC_UUID] + ) + cg.add(var.set_char_uuid128(uuid128)) + + if CONF_DESCRIPTOR_UUID in config: + if len(config[CONF_DESCRIPTOR_UUID]) == len(esp32_ble_tracker.bt_uuid16_format): + cg.add( + var.set_descr_uuid16( + esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) + ) + ) + elif len(config[CONF_DESCRIPTOR_UUID]) == len( + esp32_ble_tracker.bt_uuid32_format + ): + cg.add( + var.set_descr_uuid32( + esp32_ble_tracker.as_hex(config[CONF_DESCRIPTOR_UUID]) + ) + ) + elif len(config[CONF_DESCRIPTOR_UUID]) == len( + esp32_ble_tracker.bt_uuid128_format + ): + uuid128 = esp32_ble_tracker.as_reversed_hex_array( + config[CONF_DESCRIPTOR_UUID] + ) + cg.add(var.set_descr_uuid128(uuid128)) + + await cg.register_component(var, config) + await ble_client.register_ble_node(var, config) + cg.add(var.set_enable_notify(config[CONF_NOTIFY])) + await text_sensor.register_text_sensor(var, config) + for conf in config.get(CONF_ON_NOTIFY, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await ble_client.register_ble_node(trigger, config) + await automation.build_automation(trigger, [(cg.std_string, "x")], conf) diff --git a/esphome/components/ble_client/text_sensor/automation.h b/esphome/components/ble_client/text_sensor/automation.h new file mode 100644 index 0000000000..be85892c5a --- /dev/null +++ b/esphome/components/ble_client/text_sensor/automation.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/core/automation.h" +#include "esphome/components/ble_client/text_sensor/ble_text_sensor.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +class BLETextSensorNotifyTrigger : public Trigger, public BLETextSensor { + public: + explicit BLETextSensorNotifyTrigger(BLETextSensor *sensor) { sensor_ = sensor; } + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override { + switch (event) { + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->sensor_->node_state = espbt::ClientState::ESTABLISHED; + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->sensor_->parent()->conn_id || param->notify.handle != this->sensor_->handle) + break; + this->trigger(this->sensor_->parse_data(param->notify.value, param->notify.value_len)); + } + default: + break; + } + } + + protected: + BLETextSensor *sensor_; +}; + +} // namespace ble_client +} // namespace esphome + +#endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp new file mode 100644 index 0000000000..c4d175faa4 --- /dev/null +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.cpp @@ -0,0 +1,137 @@ +#include "ble_text_sensor.h" + +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/core/application.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace ble_client { + +static const char *const TAG = "ble_text_sensor"; + +static const std::string EMPTY = ""; + +uint32_t BLETextSensor::hash_base() { return 193967603UL; } + +void BLETextSensor::loop() {} + +void BLETextSensor::dump_config() { + LOG_TEXT_SENSOR("", "BLE Text Sensor", this); + ESP_LOGCONFIG(TAG, " MAC address : %s", this->parent()->address_str().c_str()); + ESP_LOGCONFIG(TAG, " Service UUID : %s", this->service_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Characteristic UUID: %s", this->char_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Descriptor UUID : %s", this->descr_uuid_.to_string().c_str()); + ESP_LOGCONFIG(TAG, " Notifications : %s", YESNO(this->notify_)); + LOG_UPDATE_INTERVAL(this); +} + +void BLETextSensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + if (param->open.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "[%s] Connected successfully!", this->get_name().c_str()); + break; + } + break; + } + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "[%s] Disconnected!", this->get_name().c_str()); + this->status_set_warning(); + this->publish_state(EMPTY); + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->handle = 0; + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + if (chr == nullptr) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", this->service_uuid_.to_string().c_str(), + this->char_uuid_.to_string().c_str()); + break; + } + this->handle = chr->handle; + if (this->descr_uuid_.get_uuid().len > 0) { + auto *descr = chr->get_descriptor(this->descr_uuid_); + if (descr == nullptr) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "No sensor descriptor found at service %s char %s descr %s", + this->service_uuid_.to_string().c_str(), this->char_uuid_.to_string().c_str(), + this->descr_uuid_.to_string().c_str()); + break; + } + this->handle = descr->handle; + } + if (this->notify_) { + auto status = + esp_ble_gattc_register_for_notify(this->parent()->gattc_if, this->parent()->remote_bda, chr->handle); + if (status) { + ESP_LOGW(TAG, "esp_ble_gattc_register_for_notify failed, status=%d", status); + } + } else { + this->node_state = espbt::ClientState::ESTABLISHED; + } + break; + } + case ESP_GATTC_READ_CHAR_EVT: { + if (param->read.conn_id != this->parent()->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->status_clear_warning(); + this->publish_state(this->parse_data(param->read.value, param->read.value_len)); + } + break; + } + case ESP_GATTC_NOTIFY_EVT: { + if (param->notify.conn_id != this->parent()->conn_id || param->notify.handle != this->handle) + break; + ESP_LOGV(TAG, "[%s] ESP_GATTC_NOTIFY_EVT: handle=0x%x, value=0x%x", this->get_name().c_str(), + param->notify.handle, param->notify.value[0]); + this->publish_state(this->parse_data(param->notify.value, param->notify.value_len)); + break; + } + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + this->node_state = espbt::ClientState::ESTABLISHED; + break; + } + default: + break; + } +} + +std::string BLETextSensor::parse_data(uint8_t *value, uint16_t value_len) { + std::string text(value, value + value_len); + return text; +} + +void BLETextSensor::update() { + if (this->node_state != espbt::ClientState::ESTABLISHED) { + ESP_LOGW(TAG, "[%s] Cannot poll, not connected", this->get_name().c_str()); + return; + } + if (this->handle == 0) { + ESP_LOGW(TAG, "[%s] Cannot poll, no service or characteristic found", this->get_name().c_str()); + return; + } + + auto status = + esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->handle, ESP_GATT_AUTH_REQ_NONE); + if (status) { + this->status_set_warning(); + this->publish_state(EMPTY); + ESP_LOGW(TAG, "[%s] Error sending read request for sensor, status=%d", this->get_name().c_str(), status); + } +} + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/esphome/components/ble_client/text_sensor/ble_text_sensor.h b/esphome/components/ble_client/text_sensor/ble_text_sensor.h new file mode 100644 index 0000000000..37537307de --- /dev/null +++ b/esphome/components/ble_client/text_sensor/ble_text_sensor.h @@ -0,0 +1,47 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/text_sensor/text_sensor.h" + +#ifdef USE_ESP32 +#include + +namespace esphome { +namespace ble_client { + +namespace espbt = esphome::esp32_ble_tracker; + +class BLETextSensor : public text_sensor::TextSensor, public PollingComponent, public BLEClientNode { + public: + void loop() override; + void update() override; + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void set_service_uuid16(uint16_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_service_uuid32(uint32_t uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_service_uuid128(uint8_t *uuid) { this->service_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_char_uuid16(uint16_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_char_uuid32(uint32_t uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_descr_uuid16(uint16_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint16(uuid); } + void set_descr_uuid32(uint32_t uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_uint32(uuid); } + void set_descr_uuid128(uint8_t *uuid) { this->descr_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } + void set_enable_notify(bool notify) { this->notify_ = notify; } + std::string parse_data(uint8_t *value, uint16_t value_len); + uint16_t handle; + + protected: + uint32_t hash_base() override; + bool notify_; + espbt::ESPBTUUID service_uuid_; + espbt::ESPBTUUID char_uuid_; + espbt::ESPBTUUID descr_uuid_; +}; + +} // namespace ble_client +} // namespace esphome +#endif diff --git a/esphome/components/ble_presence/binary_sensor.py b/esphome/components/ble_presence/binary_sensor.py index 2a242c3aca..67f2c3516f 100644 --- a/esphome/components/ble_presence/binary_sensor.py +++ b/esphome/components/ble_presence/binary_sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_IBEACON_MAJOR, CONF_IBEACON_MINOR, CONF_IBEACON_UUID, - CONF_ID, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -30,9 +29,9 @@ def _validate(config): CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(BLEPresenceDevice) + .extend( { - cv.GenerateID(): cv.declare_id(BLEPresenceDevice), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Optional(CONF_IBEACON_MAJOR): cv.uint16_t, @@ -48,10 +47,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) if CONF_MAC_ADDRESS in config: cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/ble_rssi/sensor.py b/esphome/components/ble_rssi/sensor.py index 0c4308b11a..fd2c2e5cb1 100644 --- a/esphome/components/ble_rssi/sensor.py +++ b/esphome/components/ble_rssi/sensor.py @@ -4,7 +4,6 @@ from esphome.components import sensor, esp32_ble_tracker from esphome.const import ( CONF_SERVICE_UUID, CONF_MAC_ADDRESS, - CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, STATE_CLASS_MEASUREMENT, UNIT_DECIBEL, @@ -19,6 +18,7 @@ BLERSSISensor = ble_rssi_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + BLERSSISensor, unit_of_measurement=UNIT_DECIBEL, accuracy_decimals=0, device_class=DEVICE_CLASS_SIGNAL_STRENGTH, @@ -26,7 +26,6 @@ CONFIG_SCHEMA = cv.All( ) .extend( { - cv.GenerateID(): cv.declare_id(BLERSSISensor), cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, } @@ -38,10 +37,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await sensor.register_sensor(var, config) if CONF_MAC_ADDRESS in config: cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/ble_scanner/text_sensor.py b/esphome/components/ble_scanner/text_sensor.py index 31dccdf119..743403c6a4 100644 --- a/esphome/components/ble_scanner/text_sensor.py +++ b/esphome/components/ble_scanner/text_sensor.py @@ -13,7 +13,7 @@ BLEScanner = ble_scanner_ns.class_( ) CONFIG_SCHEMA = cv.All( - text_sensor.text_sensor_schema(klass=BLEScanner) + text_sensor.text_sensor_schema(BLEScanner) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA) ) diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index d57b46e9aa..0a5c6567bc 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -18,11 +18,7 @@ void Button::add_on_press_callback(std::function &&callback) { this->pre uint32_t Button::hash_base() { return 1495763804UL; } void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } -std::string Button::get_device_class() { - if (this->device_class_.has_value()) - return *this->device_class_; - return ""; -} +std::string Button::get_device_class() { return this->device_class_; } } // namespace button } // namespace esphome diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index b21a96b8e1..f60c2a2bc5 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -45,12 +45,12 @@ class Button : public EntityBase { protected: /** You should implement this virtual method if you want to create your own button. */ - virtual void press_action(){}; + virtual void press_action() = 0; uint32_t hash_base() override; CallbackManager press_callback_{}; - optional device_class_{}; + std::string device_class_{}; }; } // namespace button diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 808b31d1d2..5f614eb0a4 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -8,6 +8,7 @@ CODEOWNERS = ["@mvturnho", "@danielschramm"] IS_PLATFORM_COMPONENT = True CONF_CAN_ID = "can_id" +CONF_CAN_ID_MASK = "can_id_mask" CONF_USE_EXTENDED_ID = "use_extended_id" CONF_CANBUS_ID = "canbus_id" CONF_BIT_RATE = "bit_rate" @@ -38,7 +39,7 @@ canbus_ns = cg.esphome_ns.namespace("canbus") CanbusComponent = canbus_ns.class_("CanbusComponent", cg.Component) CanbusTrigger = canbus_ns.class_( "CanbusTrigger", - automation.Trigger.template(cg.std_vector.template(cg.uint8)), + automation.Trigger.template(cg.std_vector.template(cg.uint8), cg.uint32), cg.Component, ) CanSpeed = canbus_ns.enum("CAN_SPEED") @@ -72,6 +73,9 @@ CANBUS_SCHEMA = cv.Schema( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Optional(CONF_CAN_ID_MASK, default=0x1FFFFFFF): cv.int_range( + min=0, max=0x1FFFFFFF + ), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, }, validate_id, @@ -90,11 +94,16 @@ async def setup_canbus_core_(var, config): for conf in config.get(CONF_ON_FRAME, []): can_id = conf[CONF_CAN_ID] + can_id_mask = conf[CONF_CAN_ID_MASK] ext_id = conf[CONF_USE_EXTENDED_ID] - trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) + trigger = cg.new_Pvariable( + conf[CONF_TRIGGER_ID], var, can_id, can_id_mask, ext_id + ) await cg.register_component(trigger, conf) await automation.build_automation( - trigger, [(cg.std_vector.template(cg.uint8), "x")], conf + trigger, + [(cg.std_vector.template(cg.uint8), "x"), (cg.uint32, "can_id")], + conf, ) @@ -126,7 +135,6 @@ async def canbus_action_to_code(config, action_id, template_arg, args): if CONF_CAN_ID in config: can_id = await cg.templatable(config[CONF_CAN_ID], args, cg.uint32) cg.add(var.set_can_id(can_id)) - use_extended_id = await cg.templatable( config[CONF_USE_EXTENDED_ID], args, cg.uint32 ) diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index 731682c277..14dc1544cf 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -56,13 +56,15 @@ void Canbus::add_trigger(CanbusTrigger *trigger) { void Canbus::loop() { struct CanFrame can_message; - // readmessage - if (this->read_message(&can_message) == canbus::ERROR_OK) { + // read all messages until queue is empty + int message_counter = 0; + while (this->read_message(&can_message) == canbus::ERROR_OK) { + message_counter++; if (can_message.use_extended_id) { - ESP_LOGD(TAG, "received can message extended can_id=0x%x size=%d", can_message.can_id, + ESP_LOGD(TAG, "received can message (#%d) extended can_id=0x%x size=%d", message_counter, can_message.can_id, can_message.can_data_length_code); } else { - ESP_LOGD(TAG, "received can message std can_id=0x%x size=%d", can_message.can_id, + ESP_LOGD(TAG, "received can message (#%d) std can_id=0x%x size=%d", message_counter, can_message.can_id, can_message.can_data_length_code); } @@ -76,8 +78,9 @@ void Canbus::loop() { // fire all triggers for (auto *trigger : this->triggers_) { - if ((trigger->can_id_ == can_message.can_id) && (trigger->use_extended_id_ == can_message.use_extended_id)) { - trigger->trigger(data); + if ((trigger->can_id_ == (can_message.can_id & trigger->can_id_mask_)) && + (trigger->use_extended_id_ == can_message.use_extended_id)) { + trigger->trigger(data, can_message.can_id); } } } diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index 37adf0bc9c..0491e8d3c1 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -116,17 +116,19 @@ template class CanbusSendAction : public Action, public P std::vector data_static_{}; }; -class CanbusTrigger : public Trigger>, public Component { +class CanbusTrigger : public Trigger, uint32_t>, public Component { friend class Canbus; public: - explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const bool use_extended_id) - : parent_(parent), can_id_(can_id), use_extended_id_(use_extended_id){}; + explicit CanbusTrigger(Canbus *parent, const std::uint32_t can_id, const std::uint32_t can_id_mask, + const bool use_extended_id) + : parent_(parent), can_id_(can_id), can_id_mask_(can_id_mask), use_extended_id_(use_extended_id){}; void setup() override { this->parent_->add_trigger(this); } protected: Canbus *parent_; uint32_t can_id_; + uint32_t can_id_mask_; bool use_extended_id_; }; diff --git a/esphome/components/cap1188/binary_sensor.py b/esphome/components/cap1188/binary_sensor.py index c249eb7330..7950774340 100644 --- a/esphome/components/cap1188/binary_sensor.py +++ b/esphome/components/cap1188/binary_sensor.py @@ -1,15 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.const import CONF_CHANNEL from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID DEPENDENCIES = ["cap1188"] CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(CAP1188Channel).extend( { - cv.GenerateID(): cv.declare_id(CAP1188Channel), cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7), } @@ -17,8 +16,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_CAP1188_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/captive_portal/captive_index.h b/esphome/components/captive_portal/captive_index.h new file mode 100644 index 0000000000..bf2e6e6e8b --- /dev/null +++ b/esphome/components/captive_portal/captive_index.h @@ -0,0 +1,107 @@ +#pragma once +// Generated from https://github.com/esphome/esphome-webserver +#include "esphome/core/hal.h" +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}; + +} // namespace captive_portal +} // namespace esphome diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp index d4e37f62f2..3bfdea0ab5 100644 --- a/esphome/components/captive_portal/captive_portal.cpp +++ b/esphome/components/captive_portal/captive_portal.cpp @@ -4,60 +4,27 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" #include "esphome/components/wifi/wifi_component.h" +#include "captive_index.h" namespace esphome { namespace captive_portal { static const char *const TAG = "captive_portal"; -void CaptivePortal::handle_index(AsyncWebServerRequest *request) { - AsyncResponseStream *stream = request->beginResponseStream("text/html"); - stream->print(F("")); - stream->print(App.get_name().c_str()); - stream->print(F("")); - stream->print(F("")); - stream->print(F("")); - stream->print(F("

WiFi Networks

")); - - if (request->hasArg("save")) { - stream->print(F("
The ESP will now try to connect to the network...
Please give it some " - "time to connect.
Note: Copy the changed network to your YAML file - the next OTA update will " - "overwrite these settings.
")); - } +void CaptivePortal::handle_config(AsyncWebServerRequest *request) { + AsyncResponseStream *stream = request->beginResponseStream("application/json"); + stream->addHeader("cache-control", "public, max-age=0, must-revalidate"); + stream->printf(R"({"name":"%s","aps":[{})", App.get_name().c_str()); for (auto &scan : wifi::global_wifi_component->get_scan_result()) { if (scan.get_is_hidden()) continue; - stream->print(F("")); + // Assumes no " in ssid, possible unicode isses? + stream->printf(R"(,{"ssid":"%s","rssi":%d,"lock":%d})", scan.get_ssid().c_str(), scan.get_rssi(), + scan.get_with_auth()); } - - stream->print(F("

WiFi Settings







")); - stream->print(F("

OTA Update

")); - stream->print(F("
")); + stream->print(F("]}")); request->send(stream); } void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { @@ -68,7 +35,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) { ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str()); wifi::global_wifi_component->save_wifi_sta(ssid, psk); wifi::global_wifi_component->start_scanning(); - request->redirect("/?save=true"); + request->redirect("/?save"); } void CaptivePortal::setup() {} @@ -98,44 +65,21 @@ void CaptivePortal::start() { this->active_ = true; } -const char STYLESHEET_CSS[] PROGMEM = - R"(*{box-sizing:inherit}div,input{padding:5px;font-size:1em}input{width:95%}body{text-align:center;font-family:sans-serif}button{border:0;border-radius:.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;padding:0}.main{text-align:left;display:inline-block;min-width:260px}.network{display:flex;justify-content:space-between;align-items:center}.network-left{display:flex;align-items:center}.network-ssid{margin-bottom:-7px;margin-left:10px}.info{border:1px solid;margin:10px 0;padding:15px 10px;color:#4f8a10;background-color:#dff2bf})"; -const char LOCK_SVG[] PROGMEM = - R"()"; - void CaptivePortal::handleRequest(AsyncWebServerRequest *req) { if (req->url() == "/") { - this->handle_index(req); + AsyncWebServerResponse *response = req->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); + response->addHeader("Content-Encoding", "gzip"); + req->send(response); + return; + } else if (req->url() == "/config.json") { + this->handle_config(req); return; } else if (req->url() == "/wifisave") { this->handle_wifisave(req); return; - } else if (req->url() == "/stylesheet.css") { - req->send_P(200, "text/css", STYLESHEET_CSS); - return; - } else if (req->url() == "/lock.svg") { - req->send_P(200, "image/svg+xml", LOCK_SVG); - return; } - - AsyncResponseStream *stream = req->beginResponseStream("image/svg+xml"); - stream->print(F("url() == "/wifi-strength-4.svg") { - stream->print(F("3z")); - } else { - if (req->url() == "/wifi-strength-1.svg") { - stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-5.1 6.36a8.43 8.43 0 0 0-7.22-.01L3.27 7.4")); - } else if (req->url() == "/wifi-strength-2.svg") { - stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-3.21 3.98a11.32 11.32 0 0 0-11 0L3.27 7.4")); - } else if (req->url() == "/wifi-strength-3.svg") { - stream->print(F("3m0 2c3.07 0 6.09.86 8.71 2.45l-1.94 2.43A13.6 13.6 0 0 0 12 8C9 8 6.68 9 5.21 9.84l-1.94-2.")); - } - stream->print(F("4A16.94 16.94 0 0 1 12 5z")); - } - stream->print(F("\"/>")); - req->send(stream); } + CaptivePortal::CaptivePortal(web_server_base::WebServerBase *base) : base_(base) { global_captive_portal = this; } float CaptivePortal::get_setup_priority() const { // Before WiFi diff --git a/esphome/components/captive_portal/captive_portal.h b/esphome/components/captive_portal/captive_portal.h index b308de42b7..0e68bc9cef 100644 --- a/esphome/components/captive_portal/captive_portal.h +++ b/esphome/components/captive_portal/captive_portal.h @@ -58,7 +58,7 @@ class CaptivePortal : public AsyncWebHandler, public Component { return false; } - void handle_index(AsyncWebServerRequest *request); + void handle_config(AsyncWebServerRequest *request); void handle_wifisave(AsyncWebServerRequest *request); diff --git a/esphome/components/captive_portal/index.html b/esphome/components/captive_portal/index.html deleted file mode 100644 index 627bf81215..0000000000 --- a/esphome/components/captive_portal/index.html +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - {{ App.get_name() }} - - - - -
-

WiFi Networks

-
- The ESP will now try to connect to the network...
- Please give it some time to connect.
- Note: Copy the changed network to your YAML file - the next OTA update will overwrite these settings. -
- - - -

WiFi Settings

-
-
-
-
- -
-

-
- -

OTA Update

-
- - -
-
- - diff --git a/esphome/components/captive_portal/lock.svg b/esphome/components/captive_portal/lock.svg deleted file mode 100644 index 743a1cc55a..0000000000 --- a/esphome/components/captive_portal/lock.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/stylesheet.css b/esphome/components/captive_portal/stylesheet.css deleted file mode 100644 index 73f82f05f1..0000000000 --- a/esphome/components/captive_portal/stylesheet.css +++ /dev/null @@ -1,58 +0,0 @@ -* { - box-sizing: inherit; -} - -div, input { - padding: 5px; - font-size: 1em; -} - -input { - width: 95%; -} - -body { - text-align: center; - font-family: sans-serif; -} - -button { - border: 0; - border-radius: 0.3rem; - background-color: #1fa3ec; - color: #fff; - line-height: 2.4rem; - font-size: 1.2rem; - width: 100%; - padding: 0; -} - -.main { - text-align: left; - display: inline-block; - min-width: 260px; -} - -.network { - display: flex; - justify-content: space-between; - align-items: center; -} - -.network-left { - display: flex; - align-items: center; -} - -.network-ssid { - margin-bottom: -7px; - margin-left: 10px; -} - -.info { - border: 1px solid; - margin: 10px 0px; - padding: 15px 10px; - color: #4f8a10; - background-color: #dff2bf; -} diff --git a/esphome/components/captive_portal/wifi-strength-1.svg b/esphome/components/captive_portal/wifi-strength-1.svg deleted file mode 100644 index 189a38193c..0000000000 --- a/esphome/components/captive_portal/wifi-strength-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-2.svg b/esphome/components/captive_portal/wifi-strength-2.svg deleted file mode 100644 index 9b4b2d2396..0000000000 --- a/esphome/components/captive_portal/wifi-strength-2.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-3.svg b/esphome/components/captive_portal/wifi-strength-3.svg deleted file mode 100644 index 44b7532bb7..0000000000 --- a/esphome/components/captive_portal/wifi-strength-3.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/captive_portal/wifi-strength-4.svg b/esphome/components/captive_portal/wifi-strength-4.svg deleted file mode 100644 index a22b0b8281..0000000000 --- a/esphome/components/captive_portal/wifi-strength-4.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/esphome/components/cd74hc4067/sensor.py b/esphome/components/cd74hc4067/sensor.py index 7c7cf9ccb7..3eee34b85e 100644 --- a/esphome/components/cd74hc4067/sensor.py +++ b/esphome/components/cd74hc4067/sensor.py @@ -25,6 +25,7 @@ CONF_CD74HC4067_ID = "cd74hc4067_id" CONFIG_SCHEMA = ( sensor.sensor_schema( + CD74HC4067Sensor, unit_of_measurement=UNIT_VOLT, accuracy_decimals=3, device_class=DEVICE_CLASS_VOLTAGE, @@ -33,7 +34,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(CD74HC4067Sensor), cv.GenerateID(CONF_CD74HC4067_ID): cv.use_id(CD74HC4067Component), cv.Required(CONF_NUMBER): cv.int_range(0, 15), cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), @@ -47,8 +47,8 @@ async def to_code(config): parent = await cg.get_variable(config[CONF_CD74HC4067_ID]) var = cg.new_Pvariable(config[CONF_ID], parent) - await cg.register_component(var, config) await sensor.register_sensor(var, config) + await cg.register_component(var, config) cg.add(var.set_pin(config[CONF_NUMBER])) sens = await cg.get_variable(config[CONF_SENSOR]) diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index d113510eeb..3ec51bc3c2 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -141,7 +141,7 @@ class ClimateTraits { } bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); } bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); } - std::set get_supported_swing_modes() { return supported_swing_modes_; } + std::set get_supported_swing_modes() const { return supported_swing_modes_; } float get_visual_min_temperature() const { return visual_min_temperature_; } void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; } diff --git a/esphome/components/copy/__init__.py b/esphome/components/copy/__init__.py new file mode 100644 index 0000000000..7594894650 --- /dev/null +++ b/esphome/components/copy/__init__.py @@ -0,0 +1,5 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@OttoWinter"] + +copy_ns = cg.esphome_ns.namespace("copy") diff --git a/esphome/components/copy/binary_sensor/__init__.py b/esphome/components/copy/binary_sensor/__init__.py new file mode 100644 index 0000000000..1b6836fae7 --- /dev/null +++ b/esphome/components/copy/binary_sensor/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyBinarySensor = copy_ns.class_( + "CopyBinarySensor", binary_sensor.BinarySensor, cg.Component +) + + +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(CopyBinarySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(binary_sensor.BinarySensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp new file mode 100644 index 0000000000..0d96f58750 --- /dev/null +++ b/esphome/components/copy/binary_sensor/copy_binary_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_binary_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.binary_sensor"; + +void CopyBinarySensor::setup() { + source_->add_on_state_callback([this](bool value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Copy Binary Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/binary_sensor/copy_binary_sensor.h b/esphome/components/copy/binary_sensor/copy_binary_sensor.h new file mode 100644 index 0000000000..d62ed13c76 --- /dev/null +++ b/esphome/components/copy/binary_sensor/copy_binary_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" + +namespace esphome { +namespace copy { + +class CopyBinarySensor : public binary_sensor::BinarySensor, public Component { + public: + void set_source(binary_sensor::BinarySensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + binary_sensor::BinarySensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/button/__init__.py b/esphome/components/copy/button/__init__.py new file mode 100644 index 0000000000..65d956601a --- /dev/null +++ b/esphome/components/copy/button/__init__.py @@ -0,0 +1,42 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyButton = copy_ns.class_("CopyButton", button.Button, cg.Component) + + +CONFIG_SCHEMA = ( + button.button_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(CopyButton), + cv.Required(CONF_SOURCE_ID): cv.use_id(button.Button), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await button.register_button(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/button/copy_button.cpp b/esphome/components/copy/button/copy_button.cpp new file mode 100644 index 0000000000..595388775c --- /dev/null +++ b/esphome/components/copy/button/copy_button.cpp @@ -0,0 +1,14 @@ +#include "copy_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.button"; + +void CopyButton::dump_config() { LOG_BUTTON("", "Copy Button", this); } + +void CopyButton::press_action() { source_->press(); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/button/copy_button.h b/esphome/components/copy/button/copy_button.h new file mode 100644 index 0000000000..9996ca0c65 --- /dev/null +++ b/esphome/components/copy/button/copy_button.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace copy { + +class CopyButton : public button::Button, public Component { + public: + void set_source(button::Button *source) { source_ = source; } + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void press_action() override; + + button::Button *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/cover/__init__.py b/esphome/components/copy/cover/__init__.py new file mode 100644 index 0000000000..155e22883b --- /dev/null +++ b/esphome/components/copy/cover/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import cover +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyCover = copy_ns.class_("CopyCover", cover.Cover, cg.Component) + + +CONFIG_SCHEMA = cover.COVER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyCover), + cv.Required(CONF_SOURCE_ID): cv.use_id(cover.Cover), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cover.register_cover(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/cover/copy_cover.cpp b/esphome/components/copy/cover/copy_cover.cpp new file mode 100644 index 0000000000..cf50473018 --- /dev/null +++ b/esphome/components/copy/cover/copy_cover.cpp @@ -0,0 +1,50 @@ +#include "copy_cover.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.cover"; + +void CopyCover::setup() { + source_->add_on_state_callback([this]() { + this->current_operation = this->source_->current_operation; + this->position = this->source_->position; + this->tilt = this->source_->tilt; + this->publish_state(); + }); + + this->current_operation = this->source_->current_operation; + this->position = this->source_->position; + this->tilt = this->source_->tilt; + this->publish_state(); +} + +void CopyCover::dump_config() { LOG_COVER("", "Copy Cover", this); } + +cover::CoverTraits CopyCover::get_traits() { + auto base = source_->get_traits(); + cover::CoverTraits traits{}; + // copy traits manually so it doesn't break when new options are added + // but the control() method hasn't implemented them yet. + traits.set_is_assumed_state(base.get_is_assumed_state()); + traits.set_supports_position(base.get_supports_position()); + traits.set_supports_tilt(base.get_supports_tilt()); + traits.set_supports_toggle(base.get_supports_toggle()); + return traits; +} + +void CopyCover::control(const cover::CoverCall &call) { + auto call2 = source_->make_call(); + call2.set_stop(call.get_stop()); + if (call.get_tilt().has_value()) + call2.set_tilt(*call.get_tilt()); + if (call.get_position().has_value()) + call2.set_position(*call.get_position()); + if (call.get_tilt().has_value()) + call2.set_tilt(*call.get_tilt()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/cover/copy_cover.h b/esphome/components/copy/cover/copy_cover.h new file mode 100644 index 0000000000..fb278523ff --- /dev/null +++ b/esphome/components/copy/cover/copy_cover.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace copy { + +class CopyCover : public cover::Cover, public Component { + public: + void set_source(cover::Cover *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + cover::CoverTraits get_traits() override; + + protected: + void control(const cover::CoverCall &call) override; + + cover::Cover *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/fan/__init__.py b/esphome/components/copy/fan/__init__.py new file mode 100644 index 0000000000..22672c02d8 --- /dev/null +++ b/esphome/components/copy/fan/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import fan +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyFan = copy_ns.class_("CopyFan", fan.Fan, cg.Component) + + +CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyFan), + cv.Required(CONF_SOURCE_ID): cv.use_id(fan.Fan), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await fan.register_fan(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/fan/copy_fan.cpp b/esphome/components/copy/fan/copy_fan.cpp new file mode 100644 index 0000000000..74d9da279f --- /dev/null +++ b/esphome/components/copy/fan/copy_fan.cpp @@ -0,0 +1,53 @@ +#include "copy_fan.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.fan"; + +void CopyFan::setup() { + source_->add_on_state_callback([this]() { + this->state = source_->state; + this->oscillating = source_->oscillating; + this->speed = source_->speed; + this->direction = source_->direction; + this->publish_state(); + }); + + this->state = source_->state; + this->oscillating = source_->oscillating; + this->speed = source_->speed; + this->direction = source_->direction; + this->publish_state(); +} + +void CopyFan::dump_config() { LOG_FAN("", "Copy Fan", this); } + +fan::FanTraits CopyFan::get_traits() { + fan::FanTraits traits; + auto base = source_->get_traits(); + // copy traits manually so it doesn't break when new options are added + // but the control() method hasn't implemented them yet. + traits.set_oscillation(base.supports_oscillation()); + traits.set_speed(base.supports_speed()); + traits.set_supported_speed_count(base.supported_speed_count()); + traits.set_direction(base.supports_direction()); + return traits; +} + +void CopyFan::control(const fan::FanCall &call) { + auto call2 = source_->make_call(); + if (call.get_state().has_value()) + call2.set_state(*call.get_state()); + if (call.get_oscillating().has_value()) + call2.set_oscillating(*call.get_oscillating()); + if (call.get_speed().has_value()) + call2.set_speed(*call.get_speed()); + if (call.get_direction().has_value()) + call2.set_direction(*call.get_direction()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/fan/copy_fan.h b/esphome/components/copy/fan/copy_fan.h new file mode 100644 index 0000000000..1a69810510 --- /dev/null +++ b/esphome/components/copy/fan/copy_fan.h @@ -0,0 +1,26 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/fan/fan.h" + +namespace esphome { +namespace copy { + +class CopyFan : public fan::Fan, public Component { + public: + void set_source(fan::Fan *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + fan::FanTraits get_traits() override; + + protected: + void control(const fan::FanCall &call) override; + ; + + fan::Fan *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/lock/__init__.py b/esphome/components/copy/lock/__init__.py new file mode 100644 index 0000000000..d19e4a5807 --- /dev/null +++ b/esphome/components/copy/lock/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import lock +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyLock = copy_ns.class_("CopyLock", lock.Lock, cg.Component) + + +CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyLock), + cv.Required(CONF_SOURCE_ID): cv.use_id(lock.Lock), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await lock.register_lock(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/lock/copy_lock.cpp b/esphome/components/copy/lock/copy_lock.cpp new file mode 100644 index 0000000000..67a8acffec --- /dev/null +++ b/esphome/components/copy/lock/copy_lock.cpp @@ -0,0 +1,29 @@ +#include "copy_lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.lock"; + +void CopyLock::setup() { + source_->add_on_state_callback([this]() { this->publish_state(source_->state); }); + + traits.set_assumed_state(source_->traits.get_assumed_state()); + traits.set_requires_code(source_->traits.get_requires_code()); + traits.set_supported_states(source_->traits.get_supported_states()); + traits.set_supports_open(source_->traits.get_supports_open()); + + this->publish_state(source_->state); +} + +void CopyLock::dump_config() { LOG_LOCK("", "Copy Lock", this); } + +void CopyLock::control(const lock::LockCall &call) { + auto call2 = source_->make_call(); + call2.set_state(call.get_state()); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/lock/copy_lock.h b/esphome/components/copy/lock/copy_lock.h new file mode 100644 index 0000000000..0554013674 --- /dev/null +++ b/esphome/components/copy/lock/copy_lock.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace copy { + +class CopyLock : public lock::Lock, public Component { + public: + void set_source(lock::Lock *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(const lock::LockCall &call) override; + + lock::Lock *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/number/__init__.py b/esphome/components/copy/number/__init__.py new file mode 100644 index 0000000000..4e78627a1f --- /dev/null +++ b/esphome/components/copy/number/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import number +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_MODE, + CONF_SOURCE_ID, + CONF_UNIT_OF_MEASUREMENT, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyNumber = copy_ns.class_("CopyNumber", number.Number, cg.Component) + + +CONFIG_SCHEMA = number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopyNumber), + cv.Required(CONF_SOURCE_ID): cv.use_id(number.Number), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_UNIT_OF_MEASUREMENT, CONF_SOURCE_ID), + inherit_property_from(CONF_MODE, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await number.new_number(config, min_value=0, max_value=0, step=0) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/number/copy_number.cpp b/esphome/components/copy/number/copy_number.cpp new file mode 100644 index 0000000000..46dc200b73 --- /dev/null +++ b/esphome/components/copy/number/copy_number.cpp @@ -0,0 +1,29 @@ +#include "copy_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.number"; + +void CopyNumber::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + + traits.set_min_value(source_->traits.get_min_value()); + traits.set_max_value(source_->traits.get_max_value()); + traits.set_step(source_->traits.get_step()); + + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyNumber::dump_config() { LOG_NUMBER("", "Copy Number", this); } + +void CopyNumber::control(float value) { + auto call2 = source_->make_call(); + call2.set_value(value); + call2.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/number/copy_number.h b/esphome/components/copy/number/copy_number.h new file mode 100644 index 0000000000..1ad956fec4 --- /dev/null +++ b/esphome/components/copy/number/copy_number.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace copy { + +class CopyNumber : public number::Number, public Component { + public: + void set_source(number::Number *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(float value) override; + + number::Number *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/select/__init__.py b/esphome/components/copy/select/__init__.py new file mode 100644 index 0000000000..7d4c1c7705 --- /dev/null +++ b/esphome/components/copy/select/__init__.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import select +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySelect = copy_ns.class_("CopySelect", select.Select, cg.Component) + + +CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopySelect), + cv.Required(CONF_SOURCE_ID): cv.use_id(select.Select), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await select.register_select(var, config, options=[]) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/select/copy_select.cpp b/esphome/components/copy/select/copy_select.cpp new file mode 100644 index 0000000000..0f01c2692c --- /dev/null +++ b/esphome/components/copy/select/copy_select.cpp @@ -0,0 +1,27 @@ +#include "copy_select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.select"; + +void CopySelect::setup() { + source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + + traits.set_options(source_->traits.get_options()); + + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopySelect::dump_config() { LOG_SELECT("", "Copy Select", this); } + +void CopySelect::control(const std::string &value) { + auto call = source_->make_call(); + call.set_option(value); + call.perform(); +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/select/copy_select.h b/esphome/components/copy/select/copy_select.h new file mode 100644 index 0000000000..c8666cd394 --- /dev/null +++ b/esphome/components/copy/select/copy_select.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace copy { + +class CopySelect : public select::Select, public Component { + public: + void set_source(select::Select *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void control(const std::string &value) override; + + select::Select *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/sensor/__init__.py b/esphome/components/copy/sensor/__init__.py new file mode 100644 index 0000000000..8e78cda7c7 --- /dev/null +++ b/esphome/components/copy/sensor/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, + CONF_STATE_CLASS, + CONF_UNIT_OF_MEASUREMENT, + CONF_ACCURACY_DECIMALS, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySensor = copy_ns.class_("CopySensor", sensor.Sensor, cg.Component) + + +CONFIG_SCHEMA = ( + sensor.sensor_schema(CopySensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(sensor.Sensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_UNIT_OF_MEASUREMENT, CONF_SOURCE_ID), + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ACCURACY_DECIMALS, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), + inherit_property_from(CONF_STATE_CLASS, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await sensor.new_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/sensor/copy_sensor.cpp b/esphome/components/copy/sensor/copy_sensor.cpp new file mode 100644 index 0000000000..a47a0cf22b --- /dev/null +++ b/esphome/components/copy/sensor/copy_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.sensor"; + +void CopySensor::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopySensor::dump_config() { LOG_SENSOR("", "Copy Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/sensor/copy_sensor.h b/esphome/components/copy/sensor/copy_sensor.h new file mode 100644 index 0000000000..1ae790ada3 --- /dev/null +++ b/esphome/components/copy/sensor/copy_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace copy { + +class CopySensor : public sensor::Sensor, public Component { + public: + void set_source(sensor::Sensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + sensor::Sensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/switch/__init__.py b/esphome/components/copy/switch/__init__.py new file mode 100644 index 0000000000..6622412123 --- /dev/null +++ b/esphome/components/copy/switch/__init__.py @@ -0,0 +1,38 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import switch +from esphome.const import ( + CONF_DEVICE_CLASS, + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopySwitch = copy_ns.class_("CopySwitch", switch.Switch, cg.Component) + + +CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(CopySwitch), + cv.Required(CONF_SOURCE_ID): cv.use_id(switch.Switch), + } +).extend(cv.COMPONENT_SCHEMA) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), + inherit_property_from(CONF_DEVICE_CLASS, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await switch.register_switch(var, config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/switch/copy_switch.cpp b/esphome/components/copy/switch/copy_switch.cpp new file mode 100644 index 0000000000..8a9fbb03dd --- /dev/null +++ b/esphome/components/copy/switch/copy_switch.cpp @@ -0,0 +1,26 @@ +#include "copy_switch.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.switch"; + +void CopySwitch::setup() { + source_->add_on_state_callback([this](float value) { this->publish_state(value); }); + + this->publish_state(source_->state); +} + +void CopySwitch::dump_config() { LOG_SWITCH("", "Copy Switch", this); } + +void CopySwitch::write_state(bool state) { + if (state) { + source_->turn_on(); + } else { + source_->turn_off(); + } +} + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/switch/copy_switch.h b/esphome/components/copy/switch/copy_switch.h new file mode 100644 index 0000000000..26cb254ab3 --- /dev/null +++ b/esphome/components/copy/switch/copy_switch.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace copy { + +class CopySwitch : public switch_::Switch, public Component { + public: + void set_source(switch_::Switch *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void write_state(bool state) override; + + switch_::Switch *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/text_sensor/__init__.py b/esphome/components/copy/text_sensor/__init__.py new file mode 100644 index 0000000000..5b59f21319 --- /dev/null +++ b/esphome/components/copy/text_sensor/__init__.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import text_sensor +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_SOURCE_ID, +) +from esphome.core.entity_helpers import inherit_property_from + +from .. import copy_ns + +CopyTextSensor = copy_ns.class_("CopyTextSensor", text_sensor.TextSensor, cg.Component) + + +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema(CopyTextSensor) + .extend( + { + cv.Required(CONF_SOURCE_ID): cv.use_id(text_sensor.TextSensor), + } + ) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = cv.All( + inherit_property_from(CONF_ICON, CONF_SOURCE_ID), + inherit_property_from(CONF_ENTITY_CATEGORY, CONF_SOURCE_ID), +) + + +async def to_code(config): + var = await text_sensor.new_text_sensor(config) + await cg.register_component(var, config) + + source = await cg.get_variable(config[CONF_SOURCE_ID]) + cg.add(var.set_source(source)) diff --git a/esphome/components/copy/text_sensor/copy_text_sensor.cpp b/esphome/components/copy/text_sensor/copy_text_sensor.cpp new file mode 100644 index 0000000000..95fa6d7a1b --- /dev/null +++ b/esphome/components/copy/text_sensor/copy_text_sensor.cpp @@ -0,0 +1,18 @@ +#include "copy_text_sensor.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace copy { + +static const char *const TAG = "copy.text_sensor"; + +void CopyTextSensor::setup() { + source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); }); + if (source_->has_state()) + this->publish_state(source_->state); +} + +void CopyTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Copy Sensor", this); } + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/copy/text_sensor/copy_text_sensor.h b/esphome/components/copy/text_sensor/copy_text_sensor.h new file mode 100644 index 0000000000..fe91fe948b --- /dev/null +++ b/esphome/components/copy/text_sensor/copy_text_sensor.h @@ -0,0 +1,21 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/text_sensor/text_sensor.h" + +namespace esphome { +namespace copy { + +class CopyTextSensor : public text_sensor::TextSensor, public Component { + public: + void set_source(text_sensor::TextSensor *source) { source_ = source; } + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + text_sensor::TextSensor *source_; +}; + +} // namespace copy +} // namespace esphome diff --git a/esphome/components/ct_clamp/sensor.py b/esphome/components/ct_clamp/sensor.py index 049905d0a7..18ea5877d2 100644 --- a/esphome/components/ct_clamp/sensor.py +++ b/esphome/components/ct_clamp/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import sensor, voltage_sampler from esphome.const import ( CONF_SENSOR, - CONF_ID, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT, UNIT_AMPERE, @@ -19,6 +18,7 @@ CTClampSensor = ct_clamp_ns.class_("CTClampSensor", sensor.Sensor, cg.PollingCom CONFIG_SCHEMA = ( sensor.sensor_schema( + CTClampSensor, unit_of_measurement=UNIT_AMPERE, accuracy_decimals=2, device_class=DEVICE_CLASS_CURRENT, @@ -26,7 +26,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(CTClampSensor), cv.Required(CONF_SENSOR): cv.use_id(voltage_sampler.VoltageSampler), cv.Optional( CONF_SAMPLE_DURATION, default="200ms" @@ -38,9 +37,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_source(sens)) diff --git a/esphome/components/custom/binary_sensor/__init__.py b/esphome/components/custom/binary_sensor/__init__.py index 18d613d4c1..8d6d621b3a 100644 --- a/esphome/components/custom/binary_sensor/__init__.py +++ b/esphome/components/custom/binary_sensor/__init__.py @@ -11,7 +11,7 @@ CONFIG_SCHEMA = cv.Schema( cv.GenerateID(): cv.declare_id(CustomBinarySensorConstructor), cv.Required(CONF_LAMBDA): cv.returning_lambda, cv.Required(CONF_BINARY_SENSORS): cv.ensure_list( - binary_sensor.BINARY_SENSOR_SCHEMA + binary_sensor.binary_sensor_schema() ), } ) diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 6240983798..b1d28b8b4c 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -32,6 +32,11 @@ void DallasComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up DallasComponent..."); pin_->setup(); + + // clear bus with 480µs high, otherwise initial reset in search_vec() fails + pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + delayMicroseconds(480); + one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory) std::vector raw_sensors; @@ -99,14 +104,10 @@ void DallasComponent::update() { this->status_clear_warning(); bool result; - if (!this->one_wire_->reset()) { - result = false; - } else { - result = true; - this->one_wire_->skip(); - this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); + { + InterruptLock lock; + result = this->one_wire_->reset(); } - if (!result) { ESP_LOGE(TAG, "Requesting conversion failed"); this->status_set_warning(); @@ -116,6 +117,12 @@ void DallasComponent::update() { return; } + { + InterruptLock lock; + this->one_wire_->skip(); + this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION); + } + for (auto *sensor : this->sensors_) { this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] { bool res = sensor->read_scratch_pad(); @@ -155,16 +162,26 @@ const std::string &DallasTemperatureSensor::get_address_name() { } bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() { auto *wire = this->parent_->one_wire_; - if (!wire->reset()) { - return false; + + { + InterruptLock lock; + + if (!wire->reset()) { + return false; + } } - wire->select(this->address_); - wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD); + { + InterruptLock lock; - for (unsigned char &i : this->scratch_pad_) { - i = wire->read8(); + wire->select(this->address_); + wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD); + + for (unsigned char &i : this->scratch_pad_) { + i = wire->read8(); + } } + return true; } bool DallasTemperatureSensor::setup_sensor() { @@ -203,17 +220,20 @@ bool DallasTemperatureSensor::setup_sensor() { } auto *wire = this->parent_->one_wire_; - if (wire->reset()) { - wire->select(this->address_); - wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); - wire->write8(this->scratch_pad_[2]); // high alarm temp - wire->write8(this->scratch_pad_[3]); // low alarm temp - wire->write8(this->scratch_pad_[4]); // resolution - wire->reset(); + { + InterruptLock lock; + if (wire->reset()) { + wire->select(this->address_); + wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD); + wire->write8(this->scratch_pad_[2]); // high alarm temp + wire->write8(this->scratch_pad_[3]); // low alarm temp + wire->write8(this->scratch_pad_[4]); // resolution + wire->reset(); - // write value to EEPROM - wire->select(this->address_); - wire->write8(0x48); + // write value to EEPROM + wire->select(this->address_); + wire->write8(0x48); + } } delay(20); // allow it to finish operation diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index 6dc085a0bf..885846e5e5 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -15,8 +15,6 @@ ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); } bool HOT IRAM_ATTR ESPOneWire::reset() { // See reset here: // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html - InterruptLock lock; - // Wait for communication to clear (delay G) pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); uint8_t retries = 125; @@ -43,16 +41,18 @@ bool HOT IRAM_ATTR ESPOneWire::reset() { } void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { - // See write 1/0 bit here: - // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html - InterruptLock lock; - // drive bus low pin_.pin_mode(gpio::FLAG_OUTPUT); pin_.digital_write(false); - uint32_t delay0 = bit ? 10 : 65; - uint32_t delay1 = bit ? 55 : 5; + // from datasheet: + // write 0 low time: t_low0: min=60µs, max=120µs + // write 1 low time: t_low1: min=1µs, max=15µs + // time slot: t_slot: min=60µs, max=120µs + // recovery time: t_rec: min=1µs + // ds18b20 appears to read the bus after roughly 14µs + uint32_t delay0 = bit ? 6 : 60; + uint32_t delay1 = bit ? 54 : 5; // delay A/C delayMicroseconds(delay0); @@ -63,72 +63,100 @@ void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) { } bool HOT IRAM_ATTR ESPOneWire::read_bit() { - // See read bit here: - // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html - InterruptLock lock; - - // drive bus low, delay A + // drive bus low pin_.pin_mode(gpio::FLAG_OUTPUT); pin_.digital_write(false); + + // note: for reading we'll need very accurate timing, as the + // timing for the digital_read() is tight; according to the datasheet, + // we should read at the end of 16µs starting from the bus low + // typically, the ds18b20 pulls the line high after 11µs for a logical 1 + // and 29µs for a logical 0 + + uint32_t start = micros(); + // datasheet says >1µs delayMicroseconds(3); // release bus, delay E pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); - delayMicroseconds(10); + + // Unfortunately some frameworks have different characteristics than others + // esp32 arduino appears to pull the bus low only after the digital_write(false), + // whereas on esp-idf it already happens during the pin_mode(OUTPUT) + // manually correct for this with these constants. + +#ifdef USE_ESP32_FRAMEWORK_ARDUINO + uint32_t timing_constant = 14; +#elif defined(USE_ESP32_FRAMEWORK_ESP_IDF) + uint32_t timing_constant = 12; +#else + uint32_t timing_constant = 14; +#endif + + // measure from start value directly, to get best accurate timing no matter + // how long pin_mode/delayMicroseconds took + while (micros() - start < timing_constant) + ; // sample bus to read bit from peer bool r = pin_.digital_read(); - // delay F - delayMicroseconds(53); + // read slot is at least 60µs; get as close to 60µs to spend less time with interrupts locked + uint32_t now = micros(); + if (now - start < 60) + delayMicroseconds(60 - (now - start)); + return r; } -void ESPOneWire::write8(uint8_t val) { +void IRAM_ATTR ESPOneWire::write8(uint8_t val) { for (uint8_t i = 0; i < 8; i++) { this->write_bit(bool((1u << i) & val)); } } -void ESPOneWire::write64(uint64_t val) { +void IRAM_ATTR ESPOneWire::write64(uint64_t val) { for (uint8_t i = 0; i < 64; i++) { this->write_bit(bool((1ULL << i) & val)); } } -uint8_t ESPOneWire::read8() { +uint8_t IRAM_ATTR ESPOneWire::read8() { uint8_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint8_t(this->read_bit()) << i); } return ret; } -uint64_t ESPOneWire::read64() { +uint64_t IRAM_ATTR ESPOneWire::read64() { uint64_t ret = 0; for (uint8_t i = 0; i < 8; i++) { ret |= (uint64_t(this->read_bit()) << i); } return ret; } -void ESPOneWire::select(uint64_t address) { +void IRAM_ATTR ESPOneWire::select(uint64_t address) { this->write8(ONE_WIRE_ROM_SELECT); this->write64(address); } -void ESPOneWire::reset_search() { +void IRAM_ATTR ESPOneWire::reset_search() { this->last_discrepancy_ = 0; this->last_device_flag_ = false; this->last_family_discrepancy_ = 0; this->rom_number_ = 0; } -uint64_t ESPOneWire::search() { +uint64_t IRAM_ATTR ESPOneWire::search() { if (this->last_device_flag_) { return 0u; } - if (!this->reset()) { - // Reset failed or no devices present - this->reset_search(); - return 0u; + { + InterruptLock lock; + if (!this->reset()) { + // Reset failed or no devices present + this->reset_search(); + return 0u; + } } uint8_t id_bit_number = 1; @@ -137,58 +165,61 @@ uint64_t ESPOneWire::search() { bool search_result = false; uint8_t rom_byte_mask = 1; - // Initiate search - this->write8(ONE_WIRE_ROM_SEARCH); - do { - // read bit - bool id_bit = this->read_bit(); - // read its complement - bool cmp_id_bit = this->read_bit(); + { + InterruptLock lock; + // Initiate search + this->write8(ONE_WIRE_ROM_SEARCH); + do { + // read bit + bool id_bit = this->read_bit(); + // read its complement + bool cmp_id_bit = this->read_bit(); - if (id_bit && cmp_id_bit) { - // No devices participating in search - break; - } - - bool branch; - - if (id_bit != cmp_id_bit) { - // only chose one branch, the other one doesn't have any devices. - branch = id_bit; - } else { - // there are devices with both 0s and 1s at this bit - if (id_bit_number < this->last_discrepancy_) { - branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0; - } else { - branch = id_bit_number == this->last_discrepancy_; + if (id_bit && cmp_id_bit) { + // No devices participating in search + break; } - if (!branch) { - last_zero = id_bit_number; - if (last_zero < 9) { - this->last_discrepancy_ = last_zero; + bool branch; + + if (id_bit != cmp_id_bit) { + // only chose one branch, the other one doesn't have any devices. + branch = id_bit; + } else { + // there are devices with both 0s and 1s at this bit + if (id_bit_number < this->last_discrepancy_) { + branch = (this->rom_number8_()[rom_byte_number] & rom_byte_mask) > 0; + } else { + branch = id_bit_number == this->last_discrepancy_; + } + + if (!branch) { + last_zero = id_bit_number; + if (last_zero < 9) { + this->last_discrepancy_ = last_zero; + } } } - } - if (branch) { - // set bit - this->rom_number8_()[rom_byte_number] |= rom_byte_mask; - } else { - // clear bit - this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask; - } + if (branch) { + // set bit + this->rom_number8_()[rom_byte_number] |= rom_byte_mask; + } else { + // clear bit + this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask; + } - // choose/announce branch - this->write_bit(branch); - id_bit_number++; - rom_byte_mask <<= 1; - if (rom_byte_mask == 0u) { - // go to next byte - rom_byte_number++; - rom_byte_mask = 1; - } - } while (rom_byte_number < 8); // loop through all bytes + // choose/announce branch + this->write_bit(branch); + id_bit_number++; + rom_byte_mask <<= 1; + if (rom_byte_mask == 0u) { + // go to next byte + rom_byte_number++; + rom_byte_mask = 1; + } + } while (rom_byte_number < 8); // loop through all bytes + } if (id_bit_number >= 65) { this->last_discrepancy_ = last_zero; @@ -217,7 +248,7 @@ std::vector ESPOneWire::search_vec() { return res; } -void ESPOneWire::skip() { +void IRAM_ATTR ESPOneWire::skip() { this->write8(0xCC); // skip ROM } diff --git a/esphome/components/dallas/sensor.py b/esphome/components/dallas/sensor.py index 14ad0efa7b..9288f0a3a6 100644 --- a/esphome/components/dallas/sensor.py +++ b/esphome/components/dallas/sensor.py @@ -9,7 +9,6 @@ from esphome.const import ( DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, - CONF_ID, ) from . import DallasComponent, dallas_ns @@ -17,13 +16,13 @@ DallasTemperatureSensor = dallas_ns.class_("DallasTemperatureSensor", sensor.Sen CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + DallasTemperatureSensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ).extend( { - cv.GenerateID(): cv.declare_id(DallasTemperatureSensor), cv.GenerateID(CONF_DALLAS_ID): cv.use_id(DallasComponent), cv.Optional(CONF_ADDRESS): cv.hex_int, cv.Optional(CONF_INDEX): cv.positive_int, @@ -36,7 +35,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): hub = await cg.get_variable(config[CONF_DALLAS_ID]) - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) if CONF_ADDRESS in config: cg.add(var.set_address(config[CONF_ADDRESS])) @@ -49,4 +48,3 @@ async def to_code(config): cg.add(var.set_parent(hub)) cg.add(hub.register_sensor(var)) - await sensor.register_sensor(var, config) diff --git a/esphome/components/daly_bms/binary_sensor.py b/esphome/components/daly_bms/binary_sensor.py index 23330cd945..7b252b5e89 100644 --- a/esphome/components/daly_bms/binary_sensor.py +++ b/esphome/components/daly_bms/binary_sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID from . import DalyBmsComponent, CONF_BMS_DALY_ID CONF_CHARGING_MOS_ENABLED = "charging_mos_enabled" @@ -18,18 +17,10 @@ CONFIG_SCHEMA = cv.All( cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), cv.Optional( CONF_CHARGING_MOS_ENABLED - ): binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor), - } - ), + ): binary_sensor.binary_sensor_schema(), cv.Optional( CONF_DISCHARGING_MOS_ENABLED - ): binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(binary_sensor.BinarySensor), - } - ), + ): binary_sensor.binary_sensor_schema(), } ).extend(cv.COMPONENT_SCHEMA) ) @@ -38,9 +29,8 @@ CONFIG_SCHEMA = cv.All( async def setup_conf(config, key, hub): if key in config: conf = config[key] - sens = cg.new_Pvariable(conf[CONF_ID]) - await binary_sensor.register_binary_sensor(sens, conf) - cg.add(getattr(hub, f"set_{key}_binary_sensor")(sens)) + var = await binary_sensor.new_binary_sensor(conf) + cg.add(getattr(hub, f"set_{key}_binary_sensor")(var)) async def to_code(config): diff --git a/esphome/components/daly_bms/daly_bms.cpp b/esphome/components/daly_bms/daly_bms.cpp index 44c05f0686..f2b4c0e92b 100644 --- a/esphome/components/daly_bms/daly_bms.cpp +++ b/esphome/components/daly_bms/daly_bms.cpp @@ -16,6 +16,7 @@ static const uint8_t DALY_REQUEST_MIN_MAX_VOLTAGE = 0x91; static const uint8_t DALY_REQUEST_MIN_MAX_TEMPERATURE = 0x92; static const uint8_t DALY_REQUEST_MOS = 0x93; static const uint8_t DALY_REQUEST_STATUS = 0x94; +static const uint8_t DALY_REQUEST_CELL_VOLTAGE = 0x95; static const uint8_t DALY_REQUEST_TEMPERATURE = 0x96; void DalyBmsComponent::setup() {} @@ -31,6 +32,7 @@ void DalyBmsComponent::update() { this->request_data_(DALY_REQUEST_MIN_MAX_TEMPERATURE); this->request_data_(DALY_REQUEST_MOS); this->request_data_(DALY_REQUEST_STATUS); + this->request_data_(DALY_REQUEST_CELL_VOLTAGE); this->request_data_(DALY_REQUEST_TEMPERATURE); std::vector get_battery_level_data; @@ -166,6 +168,71 @@ void DalyBmsComponent::decode_data_(std::vector data) { } break; + case DALY_REQUEST_CELL_VOLTAGE: + switch (it[4]) { + case 1: + if (this->cell_1_voltage_) { + this->cell_1_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_2_voltage_) { + this->cell_2_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_3_voltage_) { + this->cell_3_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 2: + if (this->cell_4_voltage_) { + this->cell_4_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_5_voltage_) { + this->cell_5_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_6_voltage_) { + this->cell_6_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 3: + if (this->cell_7_voltage_) { + this->cell_7_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_8_voltage_) { + this->cell_8_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_9_voltage_) { + this->cell_9_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 4: + if (this->cell_10_voltage_) { + this->cell_10_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_11_voltage_) { + this->cell_11_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_12_voltage_) { + this->cell_12_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 5: + if (this->cell_13_voltage_) { + this->cell_13_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + if (this->cell_14_voltage_) { + this->cell_14_voltage_->publish_state((float) encode_uint16(it[7], it[8]) / 1000); + } + if (this->cell_15_voltage_) { + this->cell_15_voltage_->publish_state((float) encode_uint16(it[9], it[10]) / 1000); + } + break; + case 6: + if (this->cell_16_voltage_) { + this->cell_16_voltage_->publish_state((float) encode_uint16(it[5], it[6]) / 1000); + } + break; + } + break; + default: break; } diff --git a/esphome/components/daly_bms/daly_bms.h b/esphome/components/daly_bms/daly_bms.h index b5d4c8ae39..90faab77f7 100644 --- a/esphome/components/daly_bms/daly_bms.h +++ b/esphome/components/daly_bms/daly_bms.h @@ -37,6 +37,23 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { void set_cells_number_sensor(sensor::Sensor *cells_number) { cells_number_ = cells_number; } void set_temperature_1_sensor(sensor::Sensor *temperature_1_sensor) { temperature_1_sensor_ = temperature_1_sensor; } void set_temperature_2_sensor(sensor::Sensor *temperature_2_sensor) { temperature_2_sensor_ = temperature_2_sensor; } + void set_cell_1_voltage_sensor(sensor::Sensor *cell_1_voltage) { cell_1_voltage_ = cell_1_voltage; } + void set_cell_2_voltage_sensor(sensor::Sensor *cell_2_voltage) { cell_2_voltage_ = cell_2_voltage; } + void set_cell_3_voltage_sensor(sensor::Sensor *cell_3_voltage) { cell_3_voltage_ = cell_3_voltage; } + void set_cell_4_voltage_sensor(sensor::Sensor *cell_4_voltage) { cell_4_voltage_ = cell_4_voltage; } + void set_cell_5_voltage_sensor(sensor::Sensor *cell_5_voltage) { cell_5_voltage_ = cell_5_voltage; } + void set_cell_6_voltage_sensor(sensor::Sensor *cell_6_voltage) { cell_6_voltage_ = cell_6_voltage; } + void set_cell_7_voltage_sensor(sensor::Sensor *cell_7_voltage) { cell_7_voltage_ = cell_7_voltage; } + void set_cell_8_voltage_sensor(sensor::Sensor *cell_8_voltage) { cell_8_voltage_ = cell_8_voltage; } + void set_cell_9_voltage_sensor(sensor::Sensor *cell_9_voltage) { cell_9_voltage_ = cell_9_voltage; } + void set_cell_10_voltage_sensor(sensor::Sensor *cell_10_voltage) { cell_10_voltage_ = cell_10_voltage; } + void set_cell_11_voltage_sensor(sensor::Sensor *cell_11_voltage) { cell_11_voltage_ = cell_11_voltage; } + void set_cell_12_voltage_sensor(sensor::Sensor *cell_12_voltage) { cell_12_voltage_ = cell_12_voltage; } + void set_cell_13_voltage_sensor(sensor::Sensor *cell_13_voltage) { cell_13_voltage_ = cell_13_voltage; } + void set_cell_14_voltage_sensor(sensor::Sensor *cell_14_voltage) { cell_14_voltage_ = cell_14_voltage; } + void set_cell_15_voltage_sensor(sensor::Sensor *cell_15_voltage) { cell_15_voltage_ = cell_15_voltage; } + void set_cell_16_voltage_sensor(sensor::Sensor *cell_16_voltage) { cell_16_voltage_ = cell_16_voltage; } + // TEXT_SENSORS void set_status_text_sensor(text_sensor::TextSensor *status_text_sensor) { status_text_sensor_ = status_text_sensor; } // BINARY_SENSORS @@ -72,6 +89,22 @@ class DalyBmsComponent : public PollingComponent, public uart::UARTDevice { sensor::Sensor *cells_number_{nullptr}; sensor::Sensor *temperature_1_sensor_{nullptr}; sensor::Sensor *temperature_2_sensor_{nullptr}; + sensor::Sensor *cell_1_voltage_{nullptr}; + sensor::Sensor *cell_2_voltage_{nullptr}; + sensor::Sensor *cell_3_voltage_{nullptr}; + sensor::Sensor *cell_4_voltage_{nullptr}; + sensor::Sensor *cell_5_voltage_{nullptr}; + sensor::Sensor *cell_6_voltage_{nullptr}; + sensor::Sensor *cell_7_voltage_{nullptr}; + sensor::Sensor *cell_8_voltage_{nullptr}; + sensor::Sensor *cell_9_voltage_{nullptr}; + sensor::Sensor *cell_10_voltage_{nullptr}; + sensor::Sensor *cell_11_voltage_{nullptr}; + sensor::Sensor *cell_12_voltage_{nullptr}; + sensor::Sensor *cell_13_voltage_{nullptr}; + sensor::Sensor *cell_14_voltage_{nullptr}; + sensor::Sensor *cell_15_voltage_{nullptr}; + sensor::Sensor *cell_16_voltage_{nullptr}; text_sensor::TextSensor *status_text_sensor_{nullptr}; diff --git a/esphome/components/daly_bms/sensor.py b/esphome/components/daly_bms/sensor.py index 1d0ee89914..e2e8528317 100644 --- a/esphome/components/daly_bms/sensor.py +++ b/esphome/components/daly_bms/sensor.py @@ -11,14 +11,11 @@ from esphome.const import ( DEVICE_CLASS_CURRENT, DEVICE_CLASS_BATTERY, DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_EMPTY, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_VOLT, UNIT_AMPERE, UNIT_PERCENT, UNIT_CELSIUS, - UNIT_EMPTY, ICON_FLASH, ICON_PERCENT, ICON_COUNTER, @@ -39,6 +36,22 @@ CONF_REMAINING_CAPACITY = "remaining_capacity" CONF_TEMPERATURE_1 = "temperature_1" CONF_TEMPERATURE_2 = "temperature_2" +CONF_CELL_1_VOLTAGE = "cell_1_voltage" +CONF_CELL_2_VOLTAGE = "cell_2_voltage" +CONF_CELL_3_VOLTAGE = "cell_3_voltage" +CONF_CELL_4_VOLTAGE = "cell_4_voltage" +CONF_CELL_5_VOLTAGE = "cell_5_voltage" +CONF_CELL_6_VOLTAGE = "cell_6_voltage" +CONF_CELL_7_VOLTAGE = "cell_7_voltage" +CONF_CELL_8_VOLTAGE = "cell_8_voltage" +CONF_CELL_9_VOLTAGE = "cell_9_voltage" +CONF_CELL_10_VOLTAGE = "cell_10_voltage" +CONF_CELL_11_VOLTAGE = "cell_11_voltage" +CONF_CELL_12_VOLTAGE = "cell_12_voltage" +CONF_CELL_13_VOLTAGE = "cell_13_voltage" +CONF_CELL_14_VOLTAGE = "cell_14_voltage" +CONF_CELL_15_VOLTAGE = "cell_15_voltage" +CONF_CELL_16_VOLTAGE = "cell_16_voltage" ICON_CURRENT_DC = "mdi:current-dc" ICON_BATTERY_OUTLINE = "mdi:battery-outline" ICON_THERMOMETER_CHEVRON_UP = "mdi:thermometer-chevron-up" @@ -63,117 +76,140 @@ TYPES = [ CONF_REMAINING_CAPACITY, CONF_TEMPERATURE_1, CONF_TEMPERATURE_2, + CONF_CELL_1_VOLTAGE, + CONF_CELL_2_VOLTAGE, + CONF_CELL_3_VOLTAGE, + CONF_CELL_4_VOLTAGE, + CONF_CELL_5_VOLTAGE, + CONF_CELL_6_VOLTAGE, + CONF_CELL_7_VOLTAGE, + CONF_CELL_8_VOLTAGE, + CONF_CELL_9_VOLTAGE, + CONF_CELL_10_VOLTAGE, + CONF_CELL_11_VOLTAGE, + CONF_CELL_12_VOLTAGE, + CONF_CELL_13_VOLTAGE, + CONF_CELL_14_VOLTAGE, + CONF_CELL_15_VOLTAGE, + CONF_CELL_16_VOLTAGE, ] +CELL_VOLTAGE_SCHEMA = sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, +) + CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, - ICON_FLASH, - 1, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CURRENT): sensor.sensor_schema( - UNIT_AMPERE, - ICON_CURRENT_DC, - 1, - DEVICE_CLASS_CURRENT, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_DC, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( - UNIT_PERCENT, - ICON_PERCENT, - 1, - DEVICE_CLASS_BATTERY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_PERCENT, + icon=ICON_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, - ICON_FLASH, - 2, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MAX_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema( - UNIT_VOLT, - ICON_FLASH, - 2, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MIN_CELL_VOLTAGE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_MAX_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER_CHEVRON_UP, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER_CHEVRON_UP, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MAX_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_MIN_TEMPERATURE): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER_CHEVRON_DOWN, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER_CHEVRON_DOWN, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_MIN_TEMPERATURE_PROBE_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_REMAINING_CAPACITY): sensor.sensor_schema( - UNIT_AMPERE_HOUR, - ICON_GAUGE, - 2, - DEVICE_CLASS_VOLTAGE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE_HOUR, + icon=ICON_GAUGE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CELLS_NUMBER): sensor.sensor_schema( - UNIT_EMPTY, - ICON_COUNTER, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + icon=ICON_COUNTER, + accuracy_decimals=0, ), cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema( - UNIT_CELSIUS, - ICON_THERMOMETER, - 0, - DEVICE_CLASS_TEMPERATURE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_CELL_1_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_2_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_3_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_4_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_5_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_6_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_7_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_8_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_9_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_10_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_11_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_12_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_13_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_14_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_15_VOLTAGE): CELL_VOLTAGE_SCHEMA, + cv.Optional(CONF_CELL_16_VOLTAGE): CELL_VOLTAGE_SCHEMA, } ).extend(cv.COMPONENT_SCHEMA) ) diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index f20a96ebd4..7aa742919a 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -132,12 +132,8 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(DemoBinarySensor).extend( cv.polling_component_schema("60s") - ).extend( - { - cv.GenerateID(): cv.declare_id(DemoBinarySensor), - } ) ], cv.Optional( @@ -337,12 +333,8 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - sensor.sensor_schema(accuracy_decimals=0) - .extend(cv.polling_component_schema("60s")) - .extend( - { - cv.GenerateID(): cv.declare_id(DemoSensor), - } + sensor.sensor_schema(DemoSensor, accuracy_decimals=0).extend( + cv.polling_component_schema("60s") ) ], cv.Optional( @@ -376,7 +368,7 @@ CONFIG_SCHEMA = cv.Schema( }, ], ): [ - text_sensor.text_sensor_schema(klass=DemoTextSensor).extend( + text_sensor.text_sensor_schema(DemoTextSensor).extend( cv.polling_component_schema("60s") ) ], @@ -386,9 +378,8 @@ CONFIG_SCHEMA = cv.Schema( async def to_code(config): for conf in config[CONF_BINARY_SENSORS]: - var = cg.new_Pvariable(conf[CONF_ID]) + var = await binary_sensor.new_binary_sensor(conf) await cg.register_component(var, conf) - await binary_sensor.register_binary_sensor(var, conf) for conf in config[CONF_CLIMATES]: var = cg.new_Pvariable(conf[CONF_ID]) @@ -427,9 +418,8 @@ async def to_code(config): cg.add(var.set_type(conf[CONF_TYPE])) for conf in config[CONF_SENSORS]: - var = cg.new_Pvariable(conf[CONF_ID]) + var = await sensor.new_sensor(conf) await cg.register_component(var, conf) - await sensor.register_sensor(var, conf) for conf in config[CONF_SWITCHES]: var = cg.new_Pvariable(conf[CONF_ID]) diff --git a/esphome/components/dsmr/sensor.py b/esphome/components/dsmr/sensor.py index d809d0d105..bb4722655c 100644 --- a/esphome/components/dsmr/sensor.py +++ b/esphome/components/dsmr/sensor.py @@ -2,19 +2,16 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( + CONF_ID, DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ENERGY, DEVICE_CLASS_GAS, DEVICE_CLASS_POWER, DEVICE_CLASS_VOLTAGE, - ICON_EMPTY, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, STATE_CLASS_TOTAL_INCREASING, UNIT_AMPERE, UNIT_CUBIC_METER, - UNIT_EMPTY, UNIT_KILOWATT, UNIT_KILOWATT_HOURS, UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, @@ -30,202 +27,214 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), cv.Optional("energy_delivered_lux"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff1"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_delivered_tariff2"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_lux"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff1"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("energy_returned_tariff2"): sensor.sensor_schema( - UNIT_KILOWATT_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_ENERGY, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_KILOWATT_HOURS, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("total_imported_energy"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, ), cv.Optional("total_exported_energy"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_NONE, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE_HOURS, + accuracy_decimals=3, ), cv.Optional("power_delivered"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_POWER, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("electricity_threshold"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=3, ), cv.Optional("electricity_switch_position"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=3, ), cv.Optional("electricity_failures"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_long_failures"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_sags_l1"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_sags_l2"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_sags_l3"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_swells_l1"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_swells_l2"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("electricity_swells_l3"): sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 0, DEVICE_CLASS_EMPTY, STATE_CLASS_NONE + accuracy_decimals=0, ), cv.Optional("current_l1"): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l2"): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("current_l3"): sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l1"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l2"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_delivered_l3"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l1"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l2"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("power_returned_l3"): sensor.sensor_schema( - UNIT_KILOWATT, ICON_EMPTY, 3, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + unit_of_measurement=UNIT_KILOWATT, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l2"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_delivered_l3"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l1"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l2"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("reactive_power_returned_l3"): sensor.sensor_schema( - UNIT_KILOVOLT_AMPS_REACTIVE, - ICON_EMPTY, - 3, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_KILOVOLT_AMPS_REACTIVE, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l1"): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l2"): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("voltage_l3"): sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE, STATE_CLASS_NONE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional("gas_delivered"): sensor.sensor_schema( - UNIT_CUBIC_METER, - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + device_class=DEVICE_CLASS_GAS, + state_class=STATE_CLASS_TOTAL_INCREASING, ), cv.Optional("gas_delivered_be"): sensor.sensor_schema( - UNIT_CUBIC_METER, - ICON_EMPTY, - 3, - DEVICE_CLASS_GAS, - STATE_CLASS_TOTAL_INCREASING, + unit_of_measurement=UNIT_CUBIC_METER, + accuracy_decimals=3, + device_class=DEVICE_CLASS_GAS, + state_class=STATE_CLASS_TOTAL_INCREASING, ), } ).extend(cv.COMPONENT_SCHEMA) @@ -238,10 +247,10 @@ async def to_code(config): for key, conf in config.items(): if not isinstance(conf, dict): continue - id = conf.get("id") + id = conf[CONF_ID] if id and id.type == sensor.Sensor: - s = await sensor.new_sensor(conf) - cg.add(getattr(hub, f"set_{key}")(s)) + sens = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}")(sens)) sensors.append(f"F({key})") if sensors: diff --git a/esphome/components/duty_cycle/sensor.py b/esphome/components/duty_cycle/sensor.py index 6a367328e6..3dcdf7a818 100644 --- a/esphome/components/duty_cycle/sensor.py +++ b/esphome/components/duty_cycle/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor from esphome.const import ( - CONF_ID, CONF_PIN, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, @@ -17,25 +16,20 @@ DutyCycleSensor = duty_cycle_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + DutyCycleSensor, unit_of_measurement=UNIT_PERCENT, icon=ICON_PERCENT, accuracy_decimals=1, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(DutyCycleSensor), - cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), - } - ) + .extend({cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema)}) .extend(cv.polling_component_schema("60s")) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 1229675ad8..478c9701b8 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -61,12 +61,30 @@ def set_core_data(config): return config -def get_esp32_variant(): - return CORE.data[KEY_ESP32][KEY_VARIANT] +def get_esp32_variant(core_obj=None): + return (core_obj or CORE).data[KEY_ESP32][KEY_VARIANT] -def is_esp32c3(): - return get_esp32_variant() == VARIANT_ESP32C3 +def only_on_variant(*, supported=None, unsupported=None): + """Config validator for features only available on some ESP32 variants.""" + if supported is not None and not isinstance(supported, list): + supported = [supported] + if unsupported is not None and not isinstance(unsupported, list): + unsupported = [unsupported] + + def validator_(obj): + variant = get_esp32_variant() + if supported is not None and variant not in supported: + raise cv.Invalid( + f"This feature is only available on {', '.join(supported)}" + ) + if unsupported is not None and variant in unsupported: + raise cv.Invalid( + f"This feature is not available on {', '.join(unsupported)}" + ) + return obj + + return validator_ @dataclass diff --git a/esphome/components/esp32_hall/sensor.py b/esphome/components/esp32_hall/sensor.py index a752da2c97..0c94224ef8 100644 --- a/esphome/components/esp32_hall/sensor.py +++ b/esphome/components/esp32_hall/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_MICROTESLA, ICON_MAGNET, @@ -15,23 +14,15 @@ ESP32HallSensor = esp32_hall_ns.class_( "ESP32HallSensor", sensor.Sensor, cg.PollingComponent ) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_MICROTESLA, - icon=ICON_MAGNET, - accuracy_decimals=1, - state_class=STATE_CLASS_MEASUREMENT, - ) - .extend( - { - cv.GenerateID(): cv.declare_id(ESP32HallSensor), - } - ) - .extend(cv.polling_component_schema("60s")) -) +CONFIG_SCHEMA = sensor.sensor_schema( + ESP32HallSensor, + unit_of_measurement=UNIT_MICROTESLA, + icon=ICON_MAGNET, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, +).extend(cv.polling_component_schema("60s")) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/esp32_touch/binary_sensor.py b/esphome/components/esp32_touch/binary_sensor.py index bd3e06545d..326f559830 100644 --- a/esphome/components/esp32_touch/binary_sensor.py +++ b/esphome/components/esp32_touch/binary_sensor.py @@ -39,9 +39,8 @@ ESP32TouchBinarySensor = esp32_touch_ns.class_( "ESP32TouchBinarySensor", binary_sensor.BinarySensor ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(ESP32TouchBinarySensor).extend( { - cv.GenerateID(): cv.declare_id(ESP32TouchBinarySensor), cv.GenerateID(CONF_ESP32_TOUCH_ID): cv.use_id(ESP32TouchComponent), cv.Required(CONF_PIN): validate_touch_pad, cv.Required(CONF_THRESHOLD): cv.uint16_t, diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 3c83400c1d..7b1be32e38 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -17,11 +17,16 @@ import esphome.config_validation as cv import esphome.codegen as cg from esphome.helpers import copy_file_if_changed -from .const import CONF_RESTORE_FROM_FLASH, KEY_BOARD, KEY_ESP8266, esp8266_ns +from .const import ( + CONF_RESTORE_FROM_FLASH, + KEY_BOARD, + KEY_ESP8266, + KEY_PIN_INITIAL_STATES, + esp8266_ns, +) from .boards import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS -# force import gpio to register pin schema -from .gpio import esp8266_pin_to_code # noqa +from .gpio import PinInitialState, add_pin_initial_states_array CODEOWNERS = ["@esphome/core"] @@ -37,6 +42,9 @@ def set_core_data(config): config[CONF_FRAMEWORK][CONF_VERSION] ) CORE.data[KEY_ESP8266][KEY_BOARD] = config[CONF_BOARD] + CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES] = [ + PinInitialState() for _ in range(16) + ] return config @@ -221,6 +229,8 @@ async def to_code(config): if ld_script is not None: cg.add_platformio_option("board_build.ldscript", ld_script) + CORE.add_job(add_pin_initial_states_array) + # Called by writer.py def copy_files(): diff --git a/esphome/components/esp8266/const.py b/esphome/components/esp8266/const.py index 16a050360c..70429297e0 100644 --- a/esphome/components/esp8266/const.py +++ b/esphome/components/esp8266/const.py @@ -2,6 +2,7 @@ import esphome.codegen as cg KEY_ESP8266 = "esp8266" KEY_BOARD = "board" +KEY_PIN_INITIAL_STATES = "pin_initial_states" CONF_RESTORE_FROM_FLASH = "restore_from_flash" # esp8266 namespace is already defined by arduino, manually prefix esphome diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp index 828d71a3bd..a9460f51f2 100644 --- a/esphome/components/esp8266/core.cpp +++ b/esphome/components/esp8266/core.cpp @@ -1,5 +1,6 @@ #ifdef USE_ESP8266 +#include "core.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" #include "preferences.h" @@ -53,6 +54,15 @@ extern "C" void resetPins() { // NOLINT // however, not strictly needed as we set up the pins properly // ourselves and this causes pins to toggle during reboot. force_link_symbols(); + + for (int i = 0; i < 16; i++) { + uint8_t mode = ESPHOME_ESP8266_GPIO_INITIAL_MODE[i]; + uint8_t level = ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[i]; + if (mode != 255) + pinMode(i, mode); // NOLINT + if (level != 255) + digitalWrite(i, level); // NOLINT + } } } // namespace esphome diff --git a/esphome/components/esp8266/core.h b/esphome/components/esp8266/core.h new file mode 100644 index 0000000000..ac33305669 --- /dev/null +++ b/esphome/components/esp8266/core.h @@ -0,0 +1,14 @@ +#pragma once + +#ifdef USE_ESP8266 + +#include + +extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16]; +extern const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16]; + +namespace esphome { +namespace esp8266 {} // namespace esp8266 +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/gpio.py b/esphome/components/esp8266/gpio.py index fa5c94dff5..cf33ec126b 100644 --- a/esphome/components/esp8266/gpio.py +++ b/esphome/components/esp8266/gpio.py @@ -1,4 +1,6 @@ import logging +from dataclasses import dataclass +from typing import List from esphome.const import ( CONF_ID, @@ -12,12 +14,12 @@ from esphome.const import ( CONF_PULLUP, ) from esphome import pins -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority import esphome.config_validation as cv import esphome.codegen as cg from . import boards -from .const import KEY_BOARD, KEY_ESP8266, esp8266_ns +from .const import KEY_BOARD, KEY_ESP8266, KEY_PIN_INITIAL_STATES, esp8266_ns _LOGGER = logging.getLogger(__name__) @@ -160,11 +162,57 @@ ESP8266_PIN_SCHEMA = cv.All( ) +@dataclass +class PinInitialState: + mode = 255 + level: int = 255 + + @pins.PIN_SCHEMA_REGISTRY.register("esp8266", ESP8266_PIN_SCHEMA) async def esp8266_pin_to_code(config): var = cg.new_Pvariable(config[CONF_ID]) num = config[CONF_NUMBER] + mode = config[CONF_MODE] cg.add(var.set_pin(num)) cg.add(var.set_inverted(config[CONF_INVERTED])) - cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE]))) + cg.add(var.set_flags(pins.gpio_flags_expr(mode))) + if num < 16: + initial_state: PinInitialState = CORE.data[KEY_ESP8266][KEY_PIN_INITIAL_STATES][ + num + ] + if mode[CONF_INPUT]: + if mode[CONF_PULLDOWN]: + initial_state.mode = cg.global_ns.INPUT_PULLDOWN_16 + elif mode[CONF_PULLUP]: + initial_state.mode = cg.global_ns.INPUT_PULLUP + else: + initial_state.mode = cg.global_ns.INPUT + elif mode[CONF_OUTPUT]: + if mode[CONF_OPEN_DRAIN]: + initial_state.mode = cg.global_ns.OUTPUT_OPEN_DRAIN + else: + initial_state.mode = cg.global_ns.OUTPUT + initial_state.level = int(config[CONF_INVERTED]) + return var + + +@coroutine_with_priority(-999.0) +async def add_pin_initial_states_array(): + # Add includes at the very end, so that they override everything + initial_states: List[PinInitialState] = CORE.data[KEY_ESP8266][ + KEY_PIN_INITIAL_STATES + ] + initial_modes_s = ", ".join(str(x.mode) for x in initial_states) + initial_levels_s = ", ".join(str(x.level) for x in initial_states) + + cg.add_global( + cg.RawExpression( + f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_MODE[16] = {{{initial_modes_s}}}" + ) + ) + cg.add_global( + cg.RawExpression( + f"const uint8_t ESPHOME_ESP8266_GPIO_INITIAL_LEVEL[16] = {{{initial_levels_s}}}" + ) + ) diff --git a/esphome/components/fingerprint_grow/binary_sensor.py b/esphome/components/fingerprint_grow/binary_sensor.py index f432ef92cc..8572919e36 100644 --- a/esphome/components/fingerprint_grow/binary_sensor.py +++ b/esphome/components/fingerprint_grow/binary_sensor.py @@ -6,7 +6,7 @@ from . import CONF_FINGERPRINT_GROW_ID, FingerprintGrowComponent DEPENDENCIES = ["fingerprint_grow"] -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema().extend( { cv.GenerateID(CONF_FINGERPRINT_GROW_ID): cv.use_id(FingerprintGrowComponent), cv.Optional(CONF_ICON, default=ICON_KEY_PLUS): cv.icon, diff --git a/esphome/components/fingerprint_grow/sensor.py b/esphome/components/fingerprint_grow/sensor.py index 4ae670743d..ed4e431dcc 100644 --- a/esphome/components/fingerprint_grow/sensor.py +++ b/esphome/components/fingerprint_grow/sensor.py @@ -14,7 +14,6 @@ from esphome.const import ( ICON_DATABASE, ICON_FINGERPRINT, ICON_SECURITY, - STATE_CLASS_NONE, ) from . import CONF_FINGERPRINT_GROW_ID, FingerprintGrowComponent @@ -26,36 +25,30 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_FINGERPRINT_COUNT): sensor.sensor_schema( icon=ICON_FINGERPRINT, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_STATUS): sensor.sensor_schema( accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_CAPACITY): sensor.sensor_schema( icon=ICON_DATABASE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema( icon=ICON_SECURITY, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema( icon=ICON_ACCOUNT, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema( icon=ICON_ACCOUNT_CHECK, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } diff --git a/esphome/components/fujitsu_general/fujitsu_general.h b/esphome/components/fujitsu_general/fujitsu_general.h index 8dc7a3e484..ee83ae9d19 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.h +++ b/esphome/components/fujitsu_general/fujitsu_general.h @@ -17,29 +17,29 @@ const uint8_t FUJITSU_GENERAL_TEMP_MAX = 30; // Celsius * turn * on temp mode fan swing * * | | | | | | * - * + * * temperatures 1 1248 124 124 1 * auto auto 18 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000100 00000000 00000000 00000000 00000000 00000000 00000100 11110001 * auto auto 19 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10001100 00000000 00000000 00000000 00000000 00000000 00000100 11111110 * auto auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00000000 00000000 00000000 00000000 00000000 00000100 11110011 - * + * * on flag: * on at 16 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000000 00100000 00000000 00000000 00000000 00000000 00000100 11010101 * down to 16 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000000 00100000 00000000 00000000 00000000 00000000 00000100 00110101 - * + * * mode options: * auto auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00000000 00000000 00000000 00000000 00000000 00000100 11110011 * cool auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 10000000 00000000 00000000 00000000 00000000 00000100 01110011 * dry auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 01000000 00000000 00000000 00000000 00000000 00000100 10110011 * fan (auto) (30) 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 11000000 00000000 00000000 00000000 00000000 00000100 00110011 * heat auto 30 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00100000 00000000 00000000 00000000 00000000 00000100 11010011 - * + * * fan options: * heat 30 high 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 10000111 00100000 10000000 00000000 00000000 00000000 00000100 01010011 * heat 30 med 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 01000000 00000000 00000000 00000000 00000100 01010011 * heat 30 low 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 11000000 00000000 00000000 00000000 00000100 10010011 * heat 30 quiet 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 00100000 00000000 00000000 00000000 00000100 00010011 - * + * * swing options: * heat 30 swing vert 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 00101000 00000000 00000000 00000000 00000100 00011101 * heat 30 noswing 00101000 11000110 00000000 00001000 00001000 01111111 10010000 00001100 00000111 00100000 00100000 00000000 00000000 00000000 00000100 00010011 diff --git a/esphome/components/gpio/binary_sensor/__init__.py b/esphome/components/gpio/binary_sensor/__init__.py index 4d91b81a44..786c3f4b96 100644 --- a/esphome/components/gpio/binary_sensor/__init__.py +++ b/esphome/components/gpio/binary_sensor/__init__.py @@ -2,25 +2,27 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import pins from esphome.components import binary_sensor -from esphome.const import CONF_ID, CONF_PIN +from esphome.const import CONF_PIN from .. import gpio_ns GPIOBinarySensor = gpio_ns.class_( "GPIOBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(GPIOBinarySensor), - cv.Required(CONF_PIN): pins.gpio_input_pin_schema, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(GPIOBinarySensor) + .extend( + { + cv.Required(CONF_PIN): pins.gpio_input_pin_schema, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/gps/__init__.py b/esphome/components/gps/__init__.py index e485373175..d4cf79b49e 100644 --- a/esphome/components/gps/__init__.py +++ b/esphome/components/gps/__init__.py @@ -11,7 +11,6 @@ from esphome.const import ( CONF_ALTITUDE, CONF_SATELLITES, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_DEGREES, UNIT_KILOMETER_PER_HOUR, UNIT_METER, @@ -35,27 +34,22 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_LATITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, accuracy_decimals=6, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_LONGITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, accuracy_decimals=6, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_SPEED): sensor.sensor_schema( unit_of_measurement=UNIT_KILOMETER_PER_HOUR, accuracy_decimals=6, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_COURSE): sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, accuracy_decimals=2, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_ALTITUDE): sensor.sensor_schema( unit_of_measurement=UNIT_METER, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_SATELLITES): sensor.sensor_schema( accuracy_decimals=0, diff --git a/esphome/components/hmc5883l/sensor.py b/esphome/components/hmc5883l/sensor.py index 9d8701079e..26e8e2b60c 100644 --- a/esphome/components/hmc5883l/sensor.py +++ b/esphome/components/hmc5883l/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_RANGE, ICON_MAGNET, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, @@ -88,7 +87,6 @@ heading_schema = sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SCREEN_ROTATION, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/homeassistant/binary_sensor/__init__.py b/esphome/components/homeassistant/binary_sensor/__init__.py index 4972466aac..a4f854c16e 100644 --- a/esphome/components/homeassistant/binary_sensor/__init__.py +++ b/esphome/components/homeassistant/binary_sensor/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID +from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID from .. import homeassistant_ns DEPENDENCIES = ["api"] @@ -9,19 +9,21 @@ HomeassistantBinarySensor = homeassistant_ns.class_( "HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(HomeassistantBinarySensor), - cv.Required(CONF_ENTITY_ID): cv.entity_id, - cv.Optional(CONF_ATTRIBUTE): cv.string, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(HomeassistantBinarySensor) + .extend( + { + cv.Required(CONF_ENTITY_ID): cv.entity_id, + cv.Optional(CONF_ATTRIBUTE): cv.string, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) if CONF_ATTRIBUTE in config: diff --git a/esphome/components/homeassistant/sensor/__init__.py b/esphome/components/homeassistant/sensor/__init__.py index cf29db8bb8..28fee9f7f6 100644 --- a/esphome/components/homeassistant/sensor/__init__.py +++ b/esphome/components/homeassistant/sensor/__init__.py @@ -5,7 +5,6 @@ from esphome.const import ( CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID, - STATE_CLASS_NONE, ) from .. import homeassistant_ns @@ -15,12 +14,8 @@ HomeassistantSensor = homeassistant_ns.class_( "HomeassistantSensor", sensor.Sensor, cg.Component ) -CONFIG_SCHEMA = sensor.sensor_schema( - accuracy_decimals=1, - state_class=STATE_CLASS_NONE, -).extend( +CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend( { - cv.GenerateID(): cv.declare_id(HomeassistantSensor), cv.Required(CONF_ENTITY_ID): cv.entity_id, cv.Optional(CONF_ATTRIBUTE): cv.string, } diff --git a/esphome/components/honeywellabp/__init__.py b/esphome/components/honeywellabp/__init__.py new file mode 100644 index 0000000000..03440d675d --- /dev/null +++ b/esphome/components/honeywellabp/__init__.py @@ -0,0 +1 @@ +"""Support for Honeywell ABP""" diff --git a/esphome/components/honeywellabp/honeywellabp.cpp b/esphome/components/honeywellabp/honeywellabp.cpp new file mode 100644 index 0000000000..910c39e8c8 --- /dev/null +++ b/esphome/components/honeywellabp/honeywellabp.cpp @@ -0,0 +1,102 @@ +#include "honeywellabp.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace honeywellabp { + +static const char *const TAG = "honeywellabp"; + +const float MIN_COUNT = 1638.4; // 1638 counts (10% of 2^14 counts or 0x0666) +const float MAX_COUNT = 14745.6; // 14745 counts (90% of 2^14 counts or 0x3999) + +void HONEYWELLABPSensor::setup() { + ESP_LOGD(TAG, "Setting up Honeywell ABP Sensor "); + this->spi_setup(); +} + +uint8_t HONEYWELLABPSensor::readsensor_() { + // Polls the sensor for new data. + // transfer 4 bytes (the last two are temperature only used by some sensors) + this->enable(); + buf_[0] = this->read_byte(); + buf_[1] = this->read_byte(); + buf_[2] = this->read_byte(); + buf_[3] = this->read_byte(); + this->disable(); + + // Check the status codes: + // status = 0 : normal operation + // status = 1 : device in command mode + // status = 2 : stale data + // status = 3 : diagnostic condition + status_ = buf_[0] >> 6 & 0x3; + ESP_LOGV(TAG, "Sensor status %d", status_); + + // if device is normal and there is new data, bitmask and save the raw data + if (status_ == 0) { + // 14 - bit pressure is the last 6 bits of byte 0 (high bits) & all of byte 1 (lowest 8 bits) + pressure_count_ = ((uint16_t)(buf_[0]) << 8 & 0x3F00) | ((uint16_t)(buf_[1]) & 0xFF); + // 11 - bit temperature is all of byte 2 (lowest 8 bits) and the first three bits of byte 3 + temperature_count_ = (((uint16_t)(buf_[2]) << 3) & 0x7F8) | (((uint16_t)(buf_[3]) >> 5) & 0x7); + ESP_LOGV(TAG, "Sensor pressure_count_ %d", pressure_count_); + ESP_LOGV(TAG, "Sensor temperature_count_ %d", temperature_count_); + } + return status_; +} + +// returns status +uint8_t HONEYWELLABPSensor::readstatus_() { return status_; } + +// The pressure value from the most recent reading in raw counts +int HONEYWELLABPSensor::rawpressure_() { return pressure_count_; } + +// The temperature value from the most recent reading in raw counts +int HONEYWELLABPSensor::rawtemperature_() { return temperature_count_; } + +// Converts a digital pressure measurement in counts to pressure measured +float HONEYWELLABPSensor::countstopressure_(const int counts, const float min_pressure, const float max_pressure) { + return ((((float) counts - MIN_COUNT) * (max_pressure - min_pressure)) / (MAX_COUNT - MIN_COUNT)) + min_pressure; +} + +// Converts a digital temperature measurement in counts to temperature in C +// This will be invalid if sensore daoes not have temperature measurement capability +float HONEYWELLABPSensor::countstotemperatures_(const int counts) { return (((float) counts / 2047.0) * 200.0) - 50.0; } + +// Pressure value from the most recent reading in units +float HONEYWELLABPSensor::read_pressure_() { + return countstopressure_(pressure_count_, honeywellabp_min_pressure_, honeywellabp_max_pressure_); +} + +// Temperature value from the most recent reading in degrees C +float HONEYWELLABPSensor::read_temperature_() { return countstotemperatures_(temperature_count_); } + +void HONEYWELLABPSensor::update() { + ESP_LOGV(TAG, "Update Honeywell ABP Sensor"); + if (readsensor_() == 0) { + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(read_pressure_() * 1.0); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(read_temperature_() * 1.0); + } +} + +float HONEYWELLABPSensor::get_setup_priority() const { return setup_priority::LATE; } + +void HONEYWELLABPSensor::dump_config() { + // LOG_SENSOR("", "HONEYWELLABP", this); + LOG_PIN(" CS Pin: ", this->cs_); + ESP_LOGCONFIG(TAG, " Min Pressure Range: %0.1f", honeywellabp_min_pressure_); + ESP_LOGCONFIG(TAG, " Max Pressure Range: %0.1f", honeywellabp_max_pressure_); + LOG_UPDATE_INTERVAL(this); +} + +void HONEYWELLABPSensor::set_honeywellabp_min_pressure(float min_pressure) { + this->honeywellabp_min_pressure_ = min_pressure; +} + +void HONEYWELLABPSensor::set_honeywellabp_max_pressure(float max_pressure) { + this->honeywellabp_max_pressure_ = max_pressure; +} + +} // namespace honeywellabp +} // namespace esphome diff --git a/esphome/components/honeywellabp/honeywellabp.h b/esphome/components/honeywellabp/honeywellabp.h new file mode 100644 index 0000000000..44d5952ca6 --- /dev/null +++ b/esphome/components/honeywellabp/honeywellabp.h @@ -0,0 +1,45 @@ +// for Honeywell ABP sensor +// adapting code from https://github.com/vwls/Honeywell_pressure_sensors +#pragma once + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/spi/spi.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace honeywellabp { + +class HONEYWELLABPSensor : public PollingComponent, + public spi::SPIDevice { + public: + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void setup() override; + void update() override; + float get_setup_priority() const override; + void dump_config() override; + void set_honeywellabp_min_pressure(float min_pressure); + void set_honeywellabp_max_pressure(float max_pressure); + + protected: + float honeywellabp_min_pressure_ = 0.0; + float honeywellabp_max_pressure_ = 0.0; + uint8_t buf_[4]; // buffer to hold sensor data + uint8_t status_ = 0; // byte to hold status information. + int pressure_count_ = 0; // hold raw pressure data (14 - bit, 0 - 16384) + int temperature_count_ = 0; // hold raw temperature data (11 - bit, 0 - 2048) + sensor::Sensor *pressure_sensor_; + sensor::Sensor *temperature_sensor_; + uint8_t readsensor_(); + uint8_t readstatus_(); + int rawpressure_(); + int rawtemperature_(); + float countstopressure_(int counts, float min_pressure, float max_pressure); + float countstotemperatures_(int counts); + float read_pressure_(); + float read_temperature_(); +}; + +} // namespace honeywellabp +} // namespace esphome diff --git a/esphome/components/honeywellabp/sensor.py b/esphome/components/honeywellabp/sensor.py new file mode 100644 index 0000000000..720a96b93c --- /dev/null +++ b/esphome/components/honeywellabp/sensor.py @@ -0,0 +1,70 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.components import spi +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, +) + +DEPENDENCIES = ["spi"] +CODEOWNERS = ["@RubyBailey"] + +CONF_MIN_PRESSURE = "min_pressure" +CONF_MAX_PRESSURE = "max_pressure" + +honeywellabp_ns = cg.esphome_ns.namespace("honeywellabp") +HONEYWELLABPSensor = honeywellabp_ns.class_( + "HONEYWELLABPSensor", sensor.Sensor, cg.PollingComponent, spi.SPIDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HONEYWELLABPSensor), + cv.Optional(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement="psi", + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + { + cv.Required(CONF_MIN_PRESSURE): cv.float_, + cv.Required(CONF_MAX_PRESSURE): cv.float_, + } + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema(cs_pin_required=True)) +) + + +async def to_code(config): + + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + if CONF_PRESSURE in config: + conf = config[CONF_PRESSURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_pressure_sensor(sens)) + cg.add(var.set_honeywellabp_min_pressure(conf[CONF_MIN_PRESSURE])) + cg.add(var.set_honeywellabp_max_pressure(conf[CONF_MAX_PRESSURE])) + + if CONF_TEMPERATURE in config: + conf = config[CONF_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/hrxl_maxsonar_wr/sensor.py b/esphome/components/hrxl_maxsonar_wr/sensor.py index dd43bd84a7..a78ae574b1 100644 --- a/esphome/components/hrxl_maxsonar_wr/sensor.py +++ b/esphome/components/hrxl_maxsonar_wr/sensor.py @@ -1,8 +1,6 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor, uart from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -16,24 +14,16 @@ HrxlMaxsonarWrComponent = hrxlmaxsonarwr_ns.class_( "HrxlMaxsonarWrComponent", sensor.Sensor, cg.Component, uart.UARTDevice ) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_METER, - icon=ICON_ARROW_EXPAND_VERTICAL, - accuracy_decimals=3, - state_class=STATE_CLASS_MEASUREMENT, - ) - .extend( - { - cv.GenerateID(): cv.declare_id(HrxlMaxsonarWrComponent), - } - ) - .extend(uart.UART_DEVICE_SCHEMA) -) +CONFIG_SCHEMA = sensor.sensor_schema( + HrxlMaxsonarWrComponent, + unit_of_measurement=UNIT_METER, + icon=ICON_ARROW_EXPAND_VERTICAL, + accuracy_decimals=3, + state_class=STATE_CLASS_MEASUREMENT, +).extend(uart.UART_DEVICE_SCHEMA) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await uart.register_uart_device(var, config) diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index 64f3f97de9..4e1cfe94b3 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -120,10 +120,16 @@ void HttpRequestComponent::close() { } const char *HttpRequestComponent::get_string() { - // The static variable is here because HTTPClient::getString() returns a String on ESP32, and we need something to - // to keep a buffer alive. - static std::string str; - str = this->client_.getString().c_str(); +#if defined(ESP32) + // The static variable is here because HTTPClient::getString() returns a String on ESP32, + // and we need something to keep a buffer alive. + static String str; +#else + // However on ESP8266, HTTPClient::getString() returns a String& to a member variable. + // Leaving this the default so that any new platform either doesn't copy, or encounters a compilation error. + auto & +#endif + str = this->client_.getString(); return str.c_str(); } diff --git a/esphome/components/hx711/sensor.py b/esphome/components/hx711/sensor.py index cd06cc770f..88a0bb85b7 100644 --- a/esphome/components/hx711/sensor.py +++ b/esphome/components/hx711/sensor.py @@ -5,7 +5,6 @@ from esphome.components import sensor from esphome.const import ( CONF_CLK_PIN, CONF_GAIN, - CONF_ID, ICON_SCALE, STATE_CLASS_MEASUREMENT, ) @@ -24,13 +23,13 @@ GAINS = { CONFIG_SCHEMA = ( sensor.sensor_schema( + HX711Sensor, icon=ICON_SCALE, accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, ) .extend( { - cv.GenerateID(): cv.declare_id(HX711Sensor), cv.Required(CONF_DOUT_PIN): pins.gpio_input_pin_schema, cv.Required(CONF_CLK_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_GAIN, default=128): cv.enum(GAINS, int=True), @@ -41,9 +40,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) dout_pin = await cg.gpio_pin_expression(config[CONF_DOUT_PIN]) cg.add(var.set_dout_pin(dout_pin)) diff --git a/esphome/components/ili9341/ili9341_init.h b/esphome/components/ili9341/ili9341_init.h index 9282895e2e..b4f67ff19a 100644 --- a/esphome/components/ili9341/ili9341_init.h +++ b/esphome/components/ili9341/ili9341_init.h @@ -25,10 +25,10 @@ static const uint8_t PROGMEM INITCMD_M5STACK[] = { 0xF2, 1, 0x00, // 3Gamma Function Disable ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma - 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma - 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, ILI9341_SLPOUT , 0x80, // Exit Sleep ILI9341_DISPON , 0x80, // Display on @@ -55,10 +55,10 @@ static const uint8_t PROGMEM INITCMD_TFT[] = { 0xF2, 1, 0x00, // 3Gamma Function Disable ILI9341_GAMMASET , 1, 0x01, // Gamma curve selected ILI9341_GMCTRP1 , 15, 0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, // Set Gamma - 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, + 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, ILI9341_GMCTRN1 , 15, 0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, // Set Gamma - 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, + 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, ILI9341_SLPOUT , 0x80, // Exit Sleep ILI9341_DISPON , 0x80, // Display on diff --git a/esphome/components/lcd_gpio/display.py b/esphome/components/lcd_gpio/display.py index 9fb635eafa..bfef402058 100644 --- a/esphome/components/lcd_gpio/display.py +++ b/esphome/components/lcd_gpio/display.py @@ -43,7 +43,7 @@ async def to_code(config): await lcd_base.setup_lcd_display(var, config) pins_ = [] for conf in config[CONF_DATA_PINS]: - pins_.append((await cg.gpio_pin_expression(conf))) + pins_.append(await cg.gpio_pin_expression(conf)) cg.add(var.set_data_pins(*pins_)) enable = await cg.gpio_pin_expression(config[CONF_ENABLE_PIN]) cg.add(var.set_enable_pin(enable)) diff --git a/esphome/components/light/__init__.py b/esphome/components/light/__init__.py index fe8a90b8db..c397910ec4 100644 --- a/esphome/components/light/__init__.py +++ b/esphome/components/light/__init__.py @@ -52,6 +52,8 @@ RESTORE_MODES = { "ALWAYS_ON": LightRestoreMode.LIGHT_ALWAYS_ON, "RESTORE_INVERTED_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_OFF, "RESTORE_INVERTED_DEFAULT_ON": LightRestoreMode.LIGHT_RESTORE_INVERTED_DEFAULT_ON, + "RESTORE_AND_OFF": LightRestoreMode.LIGHT_RESTORE_AND_OFF, + "RESTORE_AND_ON": LightRestoreMode.LIGHT_RESTORE_AND_ON, } LIGHT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 151bc58a1c..dadad38235 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -68,6 +68,12 @@ void LightState::setup() { recovered.state = !recovered.state; } break; + case LIGHT_RESTORE_AND_OFF: + case LIGHT_RESTORE_AND_ON: + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); + this->rtc_.load(&recovered); + recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON); + break; case LIGHT_ALWAYS_OFF: recovered.state = false; break; diff --git a/esphome/components/light/light_state.h b/esphome/components/light/light_state.h index ae3711234d..2e523eda5c 100644 --- a/esphome/components/light/light_state.h +++ b/esphome/components/light/light_state.h @@ -22,6 +22,8 @@ enum LightRestoreMode { LIGHT_ALWAYS_ON, LIGHT_RESTORE_INVERTED_DEFAULT_OFF, LIGHT_RESTORE_INVERTED_DEFAULT_ON, + LIGHT_RESTORE_AND_OFF, + LIGHT_RESTORE_AND_ON, }; /** This class represents the communication layer between the front-end MQTT layer and the diff --git a/esphome/components/ltr390/sensor.py b/esphome/components/ltr390/sensor.py index 0e70f7bb1b..0a765dbe3d 100644 --- a/esphome/components/ltr390/sensor.py +++ b/esphome/components/ltr390/sensor.py @@ -52,16 +52,28 @@ CONFIG_SCHEMA = cv.All( { cv.GenerateID(): cv.declare_id(LTR390Component), cv.Optional(CONF_LIGHT): sensor.sensor_schema( - UNIT_LUX, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_AMBIENT_LIGHT): sensor.sensor_schema( - UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_UV_INDEX): sensor.sensor_schema( - UNIT_UVI, ICON_BRIGHTNESS_5, 5, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_UVI, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=5, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_UV): sensor.sensor_schema( - UNIT_COUNTS, ICON_BRIGHTNESS_5, 1, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_COUNTS, + icon=ICON_BRIGHTNESS_5, + accuracy_decimals=1, + device_class=DEVICE_CLASS_ILLUMINANCE, ), cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS), cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS), diff --git a/esphome/components/max31855/sensor.py b/esphome/components/max31855/sensor.py index c7732dfbe3..0cdedb5464 100644 --- a/esphome/components/max31855/sensor.py +++ b/esphome/components/max31855/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, CONF_REFERENCE_TEMPERATURE, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -16,13 +15,13 @@ MAX31855Sensor = max31855_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX31855Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, ) .extend( { - cv.GenerateID(): cv.declare_id(MAX31855Sensor), cv.Optional(CONF_REFERENCE_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=2, @@ -37,10 +36,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) if CONF_REFERENCE_TEMPERATURE in config: tc_ref = await sensor.new_sensor(config[CONF_REFERENCE_TEMPERATURE]) cg.add(var.set_reference_sensor(tc_ref)) diff --git a/esphome/components/max31856/sensor.py b/esphome/components/max31856/sensor.py index 083d2ac30c..71f1f3bfa5 100644 --- a/esphome/components/max31856/sensor.py +++ b/esphome/components/max31856/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, CONF_MAINS_FILTER, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -22,6 +21,7 @@ FILTER = { CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX31856Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, @@ -29,7 +29,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(MAX31856Sensor), cv.Optional(CONF_MAINS_FILTER, default="60HZ"): cv.enum( FILTER, upper=True, space="" ), @@ -41,8 +40,7 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_filter(config[CONF_MAINS_FILTER])) diff --git a/esphome/components/max31865/sensor.py b/esphome/components/max31865/sensor.py index 33d9c42be3..6eb8bd400d 100644 --- a/esphome/components/max31865/sensor.py +++ b/esphome/components/max31865/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, CONF_MAINS_FILTER, CONF_REFERENCE_RESISTANCE, CONF_RTD_NOMINAL_RESISTANCE, @@ -25,6 +24,7 @@ FILTER = { CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX31865Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=2, device_class=DEVICE_CLASS_TEMPERATURE, @@ -32,7 +32,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(MAX31865Sensor), cv.Required(CONF_REFERENCE_RESISTANCE): cv.All( cv.resistance, cv.Range(min=100, max=10000) ), @@ -51,10 +50,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_reference_resistance(config[CONF_REFERENCE_RESISTANCE])) cg.add(var.set_nominal_resistance(config[CONF_RTD_NOMINAL_RESISTANCE])) cg.add(var.set_filter(config[CONF_MAINS_FILTER])) diff --git a/esphome/components/max44009/__init__.py b/esphome/components/max44009/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/max44009/max44009.cpp b/esphome/components/max44009/max44009.cpp new file mode 100644 index 0000000000..6f12fb6583 --- /dev/null +++ b/esphome/components/max44009/max44009.cpp @@ -0,0 +1,144 @@ +#include "max44009.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace max44009 { + +static const char *const TAG = "max44009.sensor"; + +// REGISTERS +static const uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02; +static const uint8_t MAX44009_LUX_READING_HIGH = 0x03; +static const uint8_t MAX44009_LUX_READING_LOW = 0x04; +// CONFIGURATION MASKS +static const uint8_t MAX44009_CFG_CONTINUOUS = 0x80; +// ERROR CODES +static const uint8_t MAX44009_OK = 0; +static const uint8_t MAX44009_ERROR_WIRE_REQUEST = -10; +static const uint8_t MAX44009_ERROR_OVERFLOW = -20; +static const uint8_t MAX44009_ERROR_HIGH_BYTE = -30; +static const uint8_t MAX44009_ERROR_LOW_BYTE = -31; + +void MAX44009Sensor::setup() { + ESP_LOGCONFIG(TAG, "Setting up MAX44009..."); + bool state_ok = false; + if (this->mode_ == MAX44009Mode::MAX44009_MODE_LOW_POWER) { + state_ok = this->set_low_power_mode(); + } else if (this->mode_ == MAX44009Mode::MAX44009_MODE_CONTINUOUS) { + state_ok = this->set_continuous_mode(); + } else { + /* + * Mode AUTO: Set mode depending on update interval + * - On low power mode, the IC measures lux intensity only once every 800ms + * regardless of integration time + * - On continuous mode, the IC continuously measures lux intensity + */ + if (this->get_update_interval() < 800) { + state_ok = this->set_continuous_mode(); + } else { + state_ok = this->set_low_power_mode(); + } + } + if (!state_ok) + this->mark_failed(); +} + +void MAX44009Sensor::dump_config() { + ESP_LOGCONFIG(TAG, "MAX44009:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MAX44009 failed!"); + } +} + +float MAX44009Sensor::get_setup_priority() const { return setup_priority::DATA; } + +void MAX44009Sensor::update() { + // update sensor illuminance value + float lux = this->read_illuminance_(); + if (this->error_ != MAX44009_OK) { + this->status_set_warning(); + this->publish_state(NAN); + } else { + this->status_clear_warning(); + this->publish_state(lux); + } +} + +float MAX44009Sensor::read_illuminance_() { + uint8_t datahigh = this->read_(MAX44009_LUX_READING_HIGH); + if (error_ != MAX44009_OK) { + this->error_ = MAX44009_ERROR_HIGH_BYTE; + return this->error_; + } + uint8_t datalow = this->read_(MAX44009_LUX_READING_LOW); + if (error_ != MAX44009_OK) { + this->error_ = MAX44009_ERROR_LOW_BYTE; + return this->error_; + } + uint8_t exponent = datahigh >> 4; + if (exponent == 0x0F) { + this->error_ = MAX44009_ERROR_OVERFLOW; + return this->error_; + } + + return this->convert_to_lux_(datahigh, datalow); +} + +float MAX44009Sensor::convert_to_lux_(uint8_t data_high, uint8_t data_low) { + uint8_t exponent = data_high >> 4; + uint32_t mantissa = ((data_high & 0x0F) << 4) + (data_low & 0x0F); + return ((0x0001 << exponent) * 0.045) * mantissa; +} + +bool MAX44009Sensor::set_continuous_mode() { + uint8_t config = this->read_(MAX44009_REGISTER_CONFIGURATION); + if (this->error_ == MAX44009_OK) { + config |= MAX44009_CFG_CONTINUOUS; + this->write_(MAX44009_REGISTER_CONFIGURATION, config); + this->status_clear_warning(); + ESP_LOGV(TAG, "set to continuous mode"); + return true; + } else { + this->status_set_warning(); + return false; + } +} + +bool MAX44009Sensor::set_low_power_mode() { + uint8_t config = this->read_(MAX44009_REGISTER_CONFIGURATION); + if (this->error_ == MAX44009_OK) { + config &= ~MAX44009_CFG_CONTINUOUS; + this->write_(MAX44009_REGISTER_CONFIGURATION, config); + this->status_clear_warning(); + ESP_LOGV(TAG, "set to low power mode"); + return true; + } else { + this->status_set_warning(); + return false; + } +} + +uint8_t MAX44009Sensor::read_(uint8_t reg) { + uint8_t data = 0; + if (!this->read_byte(reg, &data)) { + this->error_ = MAX44009_ERROR_WIRE_REQUEST; + } else { + this->error_ = MAX44009_OK; + } + return data; +} + +void MAX44009Sensor::write_(uint8_t reg, uint8_t value) { + if (!this->write_byte(reg, value)) { + this->error_ = MAX44009_ERROR_WIRE_REQUEST; + } else { + this->error_ = MAX44009_OK; + } +} + +void MAX44009Sensor::set_mode(MAX44009Mode mode) { this->mode_ = mode; } + +} // namespace max44009 +} // namespace esphome diff --git a/esphome/components/max44009/max44009.h b/esphome/components/max44009/max44009.h new file mode 100644 index 0000000000..c85d1c1028 --- /dev/null +++ b/esphome/components/max44009/max44009.h @@ -0,0 +1,37 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace max44009 { + +enum MAX44009Mode { MAX44009_MODE_AUTO, MAX44009_MODE_LOW_POWER, MAX44009_MODE_CONTINUOUS }; + +/// This class implements support for the MAX44009 Illuminance i2c sensor. +class MAX44009Sensor : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice { + public: + MAX44009Sensor() {} + + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void set_mode(MAX44009Mode mode); + bool set_continuous_mode(); + bool set_low_power_mode(); + + protected: + /// Read the illuminance value + float read_illuminance_(); + float convert_to_lux_(uint8_t data_high, uint8_t data_low); + uint8_t read_(uint8_t reg); + void write_(uint8_t reg, uint8_t value); + + int error_; + MAX44009Mode mode_; +}; + +} // namespace max44009 +} // namespace esphome diff --git a/esphome/components/max44009/sensor.py b/esphome/components/max44009/sensor.py new file mode 100644 index 0000000000..498cccb77b --- /dev/null +++ b/esphome/components/max44009/sensor.py @@ -0,0 +1,53 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, i2c +from esphome.const import ( + CONF_ID, + CONF_MODE, + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + UNIT_LUX, +) + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["i2c"] + +max44009_ns = cg.esphome_ns.namespace("max44009") +MAX44009Sensor = max44009_ns.class_( + "MAX44009Sensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice +) + +MAX44009Mode = max44009_ns.enum("MAX44009Mode") +MODE_OPTIONS = { + "auto": MAX44009Mode.MAX44009_MODE_AUTO, + "low_power": MAX44009Mode.MAX44009_MODE_LOW_POWER, + "continuous": MAX44009Mode.MAX44009_MODE_CONTINUOUS, +} + +CONFIG_SCHEMA = ( + sensor.sensor_schema( + unit_of_measurement=UNIT_LUX, + accuracy_decimals=3, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(MAX44009Sensor), + cv.Optional(CONF_MODE, default="low_power"): cv.enum( + MODE_OPTIONS, lower=True + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x4A)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await sensor.register_sensor(var, config) + + cg.add(var.set_mode(config[CONF_MODE])) diff --git a/esphome/components/max6675/sensor.py b/esphome/components/max6675/sensor.py index dff8360226..23fc86d2c2 100644 --- a/esphome/components/max6675/sensor.py +++ b/esphome/components/max6675/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, spi from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -15,23 +14,18 @@ MAX6675Sensor = max6675_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MAX6675Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(MAX6675Sensor), - } - ) .extend(cv.polling_component_schema("60s")) .extend(spi.spi_device_schema()) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await spi.register_spi_device(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/mcp4728/__init__.py b/esphome/components/mcp4728/__init__.py new file mode 100644 index 0000000000..d130ceb738 --- /dev/null +++ b/esphome/components/mcp4728/__init__.py @@ -0,0 +1,29 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c +from esphome.const import CONF_ID + +CODEOWNERS = ["@berfenger"] +DEPENDENCIES = ["i2c"] +MULTI_CONF = True +CONF_STORE_IN_EEPROM = "store_in_eeprom" + +mcp4728_ns = cg.esphome_ns.namespace("mcp4728") +MCP4728Component = mcp4728_ns.class_("MCP4728Component", cg.Component, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MCP4728Component), + cv.Optional(CONF_STORE_IN_EEPROM, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(i2c.i2c_device_schema(0x60)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID], config[CONF_STORE_IN_EEPROM]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) diff --git a/esphome/components/mcp4728/mcp4728_output.cpp b/esphome/components/mcp4728/mcp4728_output.cpp new file mode 100644 index 0000000000..d011967624 --- /dev/null +++ b/esphome/components/mcp4728/mcp4728_output.cpp @@ -0,0 +1,121 @@ +#include "mcp4728_output.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mcp4728 { + +static const char *const TAG = "mcp4728"; + +void MCP4728Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MCP4728 (0x%02X)...", this->address_); + auto err = this->write(nullptr, 0); + if (err != i2c::ERROR_OK) { + this->mark_failed(); + return; + } +} + +void MCP4728Component::dump_config() { + ESP_LOGCONFIG(TAG, "MCP4728:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MCP4728 failed!"); + } +} + +void MCP4728Component::loop() { + if (this->update_) { + this->update_ = false; + if (this->store_in_eeprom_) { + if (!this->seq_write_()) { + this->status_set_error(); + } else { + this->status_clear_error(); + } + } else { + if (!this->multi_write_()) { + this->status_set_error(); + } else { + this->status_clear_error(); + } + } + } +} + +void MCP4728Component::set_channel_value_(MCP4728ChannelIdx channel, uint16_t value) { + uint8_t cn = 0; + if (channel == MCP4728_CHANNEL_A) { + cn = 'A'; + } else if (channel == MCP4728_CHANNEL_B) { + cn = 'B'; + } else if (channel == MCP4728_CHANNEL_C) { + cn = 'C'; + } else { + cn = 'D'; + } + ESP_LOGV(TAG, "Setting MCP4728 channel %c to %d!", cn, value); + reg_[channel].data = value; + this->update_ = true; +} + +bool MCP4728Component::multi_write_() { + i2c::ErrorCode err[4]; + for (uint8_t i = 0; i < 4; ++i) { + uint8_t wd[3]; + wd[0] = ((uint8_t) CMD::MULTI_WRITE | (i << 1)) & 0xFE; + wd[1] = ((uint8_t) reg_[i].vref << 7) | ((uint8_t) reg_[i].pd << 5) | ((uint8_t) reg_[i].gain << 4) | + (reg_[i].data >> 8); + wd[2] = reg_[i].data & 0xFF; + err[i] = this->write(wd, sizeof(wd)); + } + bool ok = true; + for (auto &e : err) { + if (e != i2c::ERROR_OK) { + ok = false; + break; + } + } + return ok; +} + +bool MCP4728Component::seq_write_() { + uint8_t wd[9]; + wd[0] = (uint8_t) CMD::SEQ_WRITE; + for (uint8_t i = 0; i < 4; i++) { + wd[i * 2 + 1] = ((uint8_t) reg_[i].vref << 7) | ((uint8_t) reg_[i].pd << 5) | ((uint8_t) reg_[i].gain << 4) | + (reg_[i].data >> 8); + wd[i * 2 + 2] = reg_[i].data & 0xFF; + } + auto err = this->write(wd, sizeof(wd)); + return err == i2c::ERROR_OK; +} + +void MCP4728Component::select_vref_(MCP4728ChannelIdx channel, MCP4728Vref vref) { + reg_[channel].vref = vref; + + this->update_ = true; +} + +void MCP4728Component::select_power_down_(MCP4728ChannelIdx channel, MCP4728PwrDown pd) { + reg_[channel].pd = pd; + + this->update_ = true; +} + +void MCP4728Component::select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain) { + reg_[channel].gain = gain; + + this->update_ = true; +} + +void MCP4728Channel::write_state(float state) { + const uint16_t max_duty = 4095; + const float duty_rounded = roundf(state * max_duty); + auto duty = static_cast(duty_rounded); + this->parent_->set_channel_value_(this->channel_, duty); +} + +} // namespace mcp4728 +} // namespace esphome diff --git a/esphome/components/mcp4728/mcp4728_output.h b/esphome/components/mcp4728/mcp4728_output.h new file mode 100644 index 0000000000..55bcfdccb6 --- /dev/null +++ b/esphome/components/mcp4728/mcp4728_output.h @@ -0,0 +1,91 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/output/float_output.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mcp4728 { + +enum class CMD { + FAST_WRITE = 0x00, + MULTI_WRITE = 0x40, + SINGLE_WRITE = 0x58, + SEQ_WRITE = 0x50, + SELECT_VREF = 0x80, + SELECT_GAIN = 0xC0, + SELECT_POWER_DOWN = 0xA0 +}; + +enum MCP4728Vref { MCP4728_VREF_VDD = 0, MCP4728_VREF_INTERNAL_2_8V = 1 }; + +enum MCP4728PwrDown { + MCP4728_PD_NORMAL = 0, + MCP4728_PD_GND_1KOHM = 1, + MCP4728_PD_GND_100KOHM = 2, + MCP4728_PD_GND_500KOHM = 3 +}; + +enum MCP4728Gain { MCP4728_GAIN_X1 = 0, MCP4728_GAIN_X2 = 1 }; + +enum MCP4728ChannelIdx { MCP4728_CHANNEL_A = 0, MCP4728_CHANNEL_B = 1, MCP4728_CHANNEL_C = 2, MCP4728_CHANNEL_D = 3 }; + +struct DACInputData { + MCP4728Vref vref; + MCP4728PwrDown pd; + MCP4728Gain gain; + uint16_t data; +}; + +class MCP4728Channel; + +/// MCP4728 float output component. +class MCP4728Component : public Component, public i2c::I2CDevice { + public: + MCP4728Component(bool store_in_eeprom) : store_in_eeprom_(store_in_eeprom) {} + + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::HARDWARE; } + void loop() override; + + protected: + friend MCP4728Channel; + void set_channel_value_(MCP4728ChannelIdx channel, uint16_t value); + bool multi_write_(); + bool seq_write_(); + void select_vref_(MCP4728ChannelIdx channel, MCP4728Vref vref); + void select_power_down_(MCP4728ChannelIdx channel, MCP4728PwrDown pd); + void select_gain_(MCP4728ChannelIdx channel, MCP4728Gain gain); + + private: + DACInputData reg_[4]; + bool store_in_eeprom_ = false; + bool update_ = false; +}; + +class MCP4728Channel : public output::FloatOutput { + public: + MCP4728Channel(MCP4728Component *parent, MCP4728ChannelIdx channel, MCP4728Vref vref, MCP4728Gain gain, + MCP4728PwrDown pwrdown) + : parent_(parent), channel_(channel), vref_(vref), gain_(gain), pwrdown_(pwrdown) { + // update VREF + parent->select_vref_(channel, vref_); + // update PD + parent->select_power_down_(channel, pwrdown_); + // update GAIN + parent->select_gain_(channel, gain_); + } + + protected: + void write_state(float state) override; + + MCP4728Component *parent_; + MCP4728ChannelIdx channel_; + MCP4728Vref vref_; + MCP4728Gain gain_; + MCP4728PwrDown pwrdown_; +}; + +} // namespace mcp4728 +} // namespace esphome diff --git a/esphome/components/mcp4728/output.py b/esphome/components/mcp4728/output.py new file mode 100644 index 0000000000..e0913ab98a --- /dev/null +++ b/esphome/components/mcp4728/output.py @@ -0,0 +1,63 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output +from esphome.const import CONF_CHANNEL, CONF_ID, CONF_GAIN +from . import MCP4728Component, mcp4728_ns + +DEPENDENCIES = ["mcp4728"] + +MCP4728Channel = mcp4728_ns.class_("MCP4728Channel", output.FloatOutput) +CONF_MCP4728_ID = "mcp4728_id" +CONF_VREF = "vref" +CONF_POWER_DOWN = "power_down" + +MCP4728Vref = mcp4728_ns.enum("MCP4728Vref") +VREF_OPTIONS = { + "vdd": MCP4728Vref.MCP4728_VREF_VDD, + "internal": MCP4728Vref.MCP4728_VREF_INTERNAL_2_8V, +} + +MCP4728Gain = mcp4728_ns.enum("MCP4728Gain") +GAIN_OPTIONS = {"X1": MCP4728Gain.MCP4728_GAIN_X1, "X2": MCP4728Gain.MCP4728_GAIN_X2} + +MCP4728PwrDown = mcp4728_ns.enum("MCP4728PwrDown") +PWRDOWN_OPTIONS = { + "normal": MCP4728PwrDown.MCP4728_PD_NORMAL, + "gnd_1k": MCP4728PwrDown.MCP4728_PD_GND_1KOHM, + "gnd_100k": MCP4728PwrDown.MCP4728_PD_GND_100KOHM, + "gnd_500k": MCP4728PwrDown.MCP4728_PD_GND_500KOHM, +} + +MCP4728ChannelIdx = mcp4728_ns.enum("MCP4728ChannelIdx") +CHANNEL_OPTIONS = { + "A": MCP4728ChannelIdx.MCP4728_CHANNEL_A, + "B": MCP4728ChannelIdx.MCP4728_CHANNEL_B, + "C": MCP4728ChannelIdx.MCP4728_CHANNEL_C, + "D": MCP4728ChannelIdx.MCP4728_CHANNEL_D, +} + +CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(MCP4728Channel), + cv.GenerateID(CONF_MCP4728_ID): cv.use_id(MCP4728Component), + cv.Required(CONF_CHANNEL): cv.enum(CHANNEL_OPTIONS, upper=True), + cv.Optional(CONF_VREF, default="vdd"): cv.enum(VREF_OPTIONS, upper=False), + cv.Optional(CONF_POWER_DOWN, default="normal"): cv.enum( + PWRDOWN_OPTIONS, upper=False + ), + cv.Optional(CONF_GAIN, default="X1"): cv.enum(GAIN_OPTIONS, upper=True), + } +) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_MCP4728_ID]) + var = cg.new_Pvariable( + config[CONF_ID], + paren, + config[CONF_CHANNEL], + config[CONF_VREF], + config[CONF_GAIN], + config[CONF_POWER_DOWN], + ) + await output.register_output(var, config) diff --git a/esphome/components/mcp9808/sensor.py b/esphome/components/mcp9808/sensor.py index c7f6226e0b..2d7874fe20 100644 --- a/esphome/components/mcp9808/sensor.py +++ b/esphome/components/mcp9808/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -18,23 +17,18 @@ MCP9808Sensor = mcp9808_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MCP9808Sensor, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(MCP9808Sensor), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x18)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/modbus_controller/binary_sensor/__init__.py b/esphome/components/modbus_controller/binary_sensor/__init__.py index 557d76479d..5315167479 100644 --- a/esphome/components/modbus_controller/binary_sensor/__init__.py +++ b/esphome/components/modbus_controller/binary_sensor/__init__.py @@ -29,11 +29,11 @@ ModbusBinarySensor = modbus_controller_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + binary_sensor.binary_sensor_schema(ModbusBinarySensor) + .extend(cv.COMPONENT_SCHEMA) .extend(ModbusItemBaseSchema) .extend( { - cv.GenerateID(): cv.declare_id(ModbusBinarySensor), cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE), } ), diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 56ec734315..37a39ff334 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -11,6 +11,7 @@ from esphome.const import ( ) from .. import ( + MODBUS_WRITE_REGISTER_TYPE, add_modbus_base_properties, modbus_controller_ns, modbus_calc_properties, @@ -24,6 +25,7 @@ from ..const import ( CONF_CUSTOM_COMMAND, CONF_FORCE_NEW_RANGE, CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_TYPE, CONF_SKIP_UPDATES, CONF_USE_WRITE_MULTIPLE, CONF_VALUE_TYPE, @@ -61,6 +63,9 @@ CONFIG_SCHEMA = cv.All( number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend( { cv.GenerateID(): cv.declare_id(ModbusNumber), + cv.Optional(CONF_REGISTER_TYPE, default="holding"): cv.enum( + MODBUS_WRITE_REGISTER_TYPE + ), cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, # 24 bits are the maximum value for fp32 before precison is lost @@ -81,6 +86,7 @@ async def to_code(config): byte_offset, reg_count = modbus_calc_properties(config) var = cg.new_Pvariable( config[CONF_ID], + config[CONF_REGISTER_TYPE], config[CONF_ADDRESS], byte_offset, config[CONF_BITMASK], diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index 0c525d9c89..aa5c8d1500 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -11,9 +11,9 @@ using value_to_data_t = std::function(float); class ModbusNumber : public number::Number, public Component, public SensorItem { public: - ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count, - uint8_t skip_updates, bool force_new_range) { - this->register_type = ModbusRegisterType::HOLDING; + ModbusNumber(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, + SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) { + this->register_type = register_type; this->start_address = start_address; this->offset = offset; this->bitmask = bitmask; diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py index 7d03064fa5..6f194ef2a3 100644 --- a/esphome/components/modbus_controller/select/__init__.py +++ b/esphome/components/modbus_controller/select/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import select -from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA +from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC from esphome.jsonschema import jschema_composite from .. import ( @@ -79,6 +79,7 @@ CONFIG_SCHEMA = cv.All( cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean, cv.Required(CONF_OPTIONSMAP): ensure_option_map(), cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, cv.Optional(CONF_LAMBDA): cv.returning_lambda, cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, }, @@ -112,6 +113,7 @@ async def to_code(config): cg.add(parent.add_sensor_item(var)) cg.add(var.set_parent(parent)) cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp index 2c6b32f545..33cef39a18 100644 --- a/esphome/components/modbus_controller/select/modbus_select.cpp +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -80,6 +80,9 @@ void ModbusSelect::control(const std::string &value) { } parent_->queue_command(write_cmd); + + if (this->optimistic_) + this->publish_state(value); } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h index 0875194768..2a31dfd7cc 100644 --- a/esphome/components/modbus_controller/select/modbus_select.h +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -32,6 +32,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem void set_parent(ModbusController *const parent) { this->parent_ = parent; } void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_template(transform_func_t &&f) { this->transform_func_ = f; } void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } @@ -43,6 +44,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem std::vector mapping_; ModbusController *parent_; bool use_write_multiple_{false}; + bool optimistic_{false}; optional transform_func_; optional write_transform_func_; }; diff --git a/esphome/components/mopeka_ble/__init__.py b/esphome/components/mopeka_ble/__init__.py new file mode 100644 index 0000000000..47396435a8 --- /dev/null +++ b/esphome/components/mopeka_ble/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import esp32_ble_tracker +from esphome.const import CONF_ID + +CODEOWNERS = ["@spbrogan"] +DEPENDENCIES = ["esp32_ble_tracker"] + +mopeka_ble_ns = cg.esphome_ns.namespace("mopeka_ble") +MopekaListener = mopeka_ble_ns.class_( + "MopekaListener", esp32_ble_tracker.ESPBTDeviceListener +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(MopekaListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/mopeka_ble/mopeka_ble.cpp b/esphome/components/mopeka_ble/mopeka_ble.cpp new file mode 100644 index 0000000000..844d3a7dfd --- /dev/null +++ b/esphome/components/mopeka_ble/mopeka_ble.cpp @@ -0,0 +1,50 @@ +#include "mopeka_ble.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace mopeka_ble { + +static const char *const TAG = "mopeka_ble"; +static const uint8_t MANUFACTURER_DATA_LENGTH = 10; +static const uint16_t MANUFACTURER_ID = 0x0059; + +/** + * Parse all incoming BLE payloads to see if it is a Mopeka BLE advertisement. + * Currently this supports the following products: + * + * Mopeka Pro Check. + * If the sync button is pressed, report the MAC so a user can add this as a sensor. + */ + +bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + const auto &manu_datas = device.get_manufacturer_datas(); + + if (manu_datas.size() != 1) { + return false; + } + + const auto &manu_data = manu_datas[0]; + + if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { + return false; + } + + if (manu_data.uuid != esp32_ble_tracker::ESPBTUUID::from_uint16(MANUFACTURER_ID)) { + return false; + } + + if (this->parse_sync_button_(manu_data.data)) { + // button pressed + ESP_LOGI(TAG, "SENSOR FOUND: %s", device.address_str().c_str()); + } + return false; +} + +bool MopekaListener::parse_sync_button_(const std::vector &message) { return (message[2] & 0x80) != 0; } + +} // namespace mopeka_ble +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_ble/mopeka_ble.h b/esphome/components/mopeka_ble/mopeka_ble.h new file mode 100644 index 0000000000..7b797a3bbe --- /dev/null +++ b/esphome/components/mopeka_ble/mopeka_ble.h @@ -0,0 +1,22 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace mopeka_ble { + +class MopekaListener : public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + + protected: + bool parse_sync_button_(const std::vector &message); +}; + +} // namespace mopeka_ble +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_pro_check/__init__.py b/esphome/components/mopeka_pro_check/__init__.py new file mode 100644 index 0000000000..c57f60f521 --- /dev/null +++ b/esphome/components/mopeka_pro_check/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@spbrogan"] diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp new file mode 100644 index 0000000000..bcfe0a80ce --- /dev/null +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.cpp @@ -0,0 +1,136 @@ +#include "mopeka_pro_check.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace mopeka_pro_check { + +static const char *const TAG = "mopeka_pro_check"; +static const uint8_t MANUFACTURER_DATA_LENGTH = 10; +static const uint16_t MANUFACTURER_ID = 0x0059; +static const double MOPEKA_LPG_COEF[] = {0.573045, -0.002822, -0.00000535}; // Magic numbers provided by Mopeka + +void MopekaProCheck::dump_config() { + ESP_LOGCONFIG(TAG, "Mopeka Pro Check"); + LOG_SENSOR(" ", "Level", this->level_); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); + LOG_SENSOR(" ", "Reading Distance", this->distance_); +} + +/** + * Main parse function that gets called for all ble advertisements. + * Check if advertisement is for our sensor and if so decode it and + * update the sensor state data. + */ +bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + return false; + } + + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + const auto &manu_datas = device.get_manufacturer_datas(); + + if (manu_datas.size() != 1) { + ESP_LOGE(TAG, "Unexpected manu_datas size (%d)", manu_datas.size()); + return false; + } + + const auto &manu_data = manu_datas[0]; + + ESP_LOGVV(TAG, "Manufacturer data:"); + for (const uint8_t byte : manu_data.data) { + ESP_LOGVV(TAG, "0x%02x", byte); + } + + if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) { + ESP_LOGE(TAG, "Unexpected manu_data size (%d)", manu_data.data.size()); + return false; + } + + // Now parse the data - See Datasheet for definition + + if (static_cast(manu_data.data[0]) != STANDARD_BOTTOM_UP) { + ESP_LOGE(TAG, "Unsupported Sensor Type (0x%X)", manu_data.data[0]); + return false; + } + + // Get battery level first + if (this->battery_level_ != nullptr) { + uint8_t level = this->parse_battery_level_(manu_data.data); + this->battery_level_->publish_state(level); + } + + // Get distance and level if either are sensors + if ((this->distance_ != nullptr) || (this->level_ != nullptr)) { + uint32_t distance_value = this->parse_distance_(manu_data.data); + SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data); + ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%dmm)", quality_value, distance_value); + if (quality_value < QUALITY_HIGH) { + ESP_LOGW(TAG, "Poor read quality."); + } + if (quality_value < QUALITY_MED) { + // if really bad reading set to 0 + ESP_LOGW(TAG, "Setting distance to 0"); + distance_value = 0; + } + + // update distance sensor + if (this->distance_ != nullptr) { + this->distance_->publish_state(distance_value); + } + + // update level sensor + if (this->level_ != nullptr) { + uint8_t tank_level = 0; + if (distance_value >= this->full_mm_) { + tank_level = 100; // cap at 100% + } else if (distance_value > this->empty_mm_) { + tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_)); + } + this->level_->publish_state(tank_level); + } + } + + // Get temperature of sensor + if (this->temperature_ != nullptr) { + uint8_t temp_in_c = this->parse_temperature_(manu_data.data); + this->temperature_->publish_state(temp_in_c); + } + + return true; +} + +uint8_t MopekaProCheck::parse_battery_level_(const std::vector &message) { + float v = (float) ((message[1] & 0x7F) / 32.0f); + // convert voltage and scale for CR2032 + float percent = (v - 2.2f) / 0.65f * 100.0f; + if (percent < 0.0f) { + return 0; + } + if (percent > 100.0f) { + return 100; + } + return (uint8_t) percent; +} + +uint32_t MopekaProCheck::parse_distance_(const std::vector &message) { + uint16_t raw = (message[4] * 256) + message[3]; + double raw_level = raw & 0x3FFF; + double raw_t = (message[2] & 0x7F); + + return (uint32_t)(raw_level * (MOPEKA_LPG_COEF[0] + MOPEKA_LPG_COEF[1] * raw_t + MOPEKA_LPG_COEF[2] * raw_t * raw_t)); +} + +uint8_t MopekaProCheck::parse_temperature_(const std::vector &message) { return (message[2] & 0x7F) - 40; } + +SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector &message) { + return static_cast(message[4] >> 6); +} + +} // namespace mopeka_pro_check +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_pro_check/mopeka_pro_check.h b/esphome/components/mopeka_pro_check/mopeka_pro_check.h new file mode 100644 index 0000000000..59d33f7763 --- /dev/null +++ b/esphome/components/mopeka_pro_check/mopeka_pro_check.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace mopeka_pro_check { + +enum SensorType { + STANDARD_BOTTOM_UP = 0x03, + TOP_DOWN_AIR_ABOVE = 0x04, + BOTTOM_UP_WATER = 0x05 + // all other values are reserved +}; + +// Sensor read quality. If sensor is poorly placed or tank level +// gets too low the read quality will show and the distanace +// measurement may be inaccurate. +enum SensorReadQuality { QUALITY_HIGH = 0x3, QUALITY_MED = 0x2, QUALITY_LOW = 0x1, QUALITY_NONE = 0x0 }; + +class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceListener { + public: + void set_address(uint64_t address) { address_ = address; }; + + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + + void set_level(sensor::Sensor *level) { level_ = level; }; + void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }; + void set_battery_level(sensor::Sensor *bat) { battery_level_ = bat; }; + void set_distance(sensor::Sensor *distance) { distance_ = distance; }; + void set_tank_full(float full) { full_mm_ = full; }; + void set_tank_empty(float empty) { empty_mm_ = empty; }; + + protected: + uint64_t address_; + sensor::Sensor *level_{nullptr}; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *distance_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; + + uint32_t full_mm_; + uint32_t empty_mm_; + + uint8_t parse_battery_level_(const std::vector &message); + uint32_t parse_distance_(const std::vector &message); + uint8_t parse_temperature_(const std::vector &message); + SensorReadQuality parse_read_quality_(const std::vector &message); +}; + +} // namespace mopeka_pro_check +} // namespace esphome + +#endif diff --git a/esphome/components/mopeka_pro_check/sensor.py b/esphome/components/mopeka_pro_check/sensor.py new file mode 100644 index 0000000000..4cd90227ab --- /dev/null +++ b/esphome/components/mopeka_pro_check/sensor.py @@ -0,0 +1,131 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_DISTANCE, + CONF_MAC_ADDRESS, + CONF_ID, + ICON_THERMOMETER, + ICON_RULER, + UNIT_PERCENT, + CONF_LEVEL, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + UNIT_CELSIUS, + STATE_CLASS_MEASUREMENT, + CONF_BATTERY_LEVEL, + DEVICE_CLASS_BATTERY, +) + +CONF_TANK_TYPE = "tank_type" +CONF_CUSTOM_DISTANCE_FULL = "custom_distance_full" +CONF_CUSTOM_DISTANCE_EMPTY = "custom_distance_empty" + +ICON_PROPANE_TANK = "mdi:propane-tank" + +TANK_TYPE_CUSTOM = "CUSTOM" + +UNIT_MILLIMETER = "mm" + + +def small_distance(value): + """small_distance is stored in mm""" + meters = cv.distance(value) + return meters * 1000 + + +# +# Map of standard tank types to their +# empty and full distance values. +# Format is - tank name: (empty distance in mm, full distance in mm) +# +CONF_SUPPORTED_TANKS_MAP = { + TANK_TYPE_CUSTOM: (0, 100), + "20LB_V": (38, 254), # empty/full readings for 20lb US tank + "30LB_V": (38, 381), + "40LB_V": (38, 508), +} + +CODEOWNERS = ["@spbrogan"] +DEPENDENCIES = ["esp32_ble_tracker"] + +mopeka_pro_check_ns = cg.esphome_ns.namespace("mopeka_pro_check") +MopekaProCheck = mopeka_pro_check_ns.class_( + "MopekaProCheck", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MopekaProCheck), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_CUSTOM_DISTANCE_FULL): small_distance, + cv.Optional(CONF_CUSTOM_DISTANCE_EMPTY): small_distance, + cv.Required(CONF_TANK_TYPE): cv.enum(CONF_SUPPORTED_TANKS_MAP, upper=True), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_PROPANE_TANK, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISTANCE): sensor.sensor_schema( + unit_of_measurement=UNIT_MILLIMETER, + icon=ICON_RULER, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if config[CONF_TANK_TYPE] == TANK_TYPE_CUSTOM: + # Support custom tank min/max + if CONF_CUSTOM_DISTANCE_EMPTY in config: + cg.add(var.set_tank_empty(config[CONF_CUSTOM_DISTANCE_EMPTY])) + else: + cg.add(var.set_tank_empty(CONF_SUPPORTED_TANKS_MAP[TANK_TYPE_CUSTOM][0])) + if CONF_CUSTOM_DISTANCE_FULL in config: + cg.add(var.set_tank_full(config[CONF_CUSTOM_DISTANCE_FULL])) + else: + cg.add(var.set_tank_full(CONF_SUPPORTED_TANKS_MAP[TANK_TYPE_CUSTOM][1])) + else: + # Set the Tank empty and full based on map - User is requesting standard tank + t = config[CONF_TANK_TYPE] + cg.add(var.set_tank_empty(CONF_SUPPORTED_TANKS_MAP[t][0])) + cg.add(var.set_tank_full(CONF_SUPPORTED_TANKS_MAP[t][1])) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_LEVEL in config: + sens = await sensor.new_sensor(config[CONF_LEVEL]) + cg.add(var.set_level(sens)) + if CONF_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_DISTANCE]) + cg.add(var.set_distance(sens)) + if CONF_BATTERY_LEVEL in config: + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) diff --git a/esphome/components/mpr121/binary_sensor.py b/esphome/components/mpr121/binary_sensor.py index 20b80e063e..131fbcfc5b 100644 --- a/esphome/components/mpr121/binary_sensor.py +++ b/esphome/components/mpr121/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.const import CONF_CHANNEL from . import ( mpr121_ns, MPR121Component, @@ -13,9 +13,8 @@ from . import ( DEPENDENCIES = ["mpr121"] MPR121Channel = mpr121_ns.class_("MPR121Channel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(MPR121Channel).extend( { - cv.GenerateID(): cv.declare_id(MPR121Channel), cv.GenerateID(CONF_MPR121_ID): cv.use_id(MPR121Component), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=11), cv.Optional(CONF_TOUCH_THRESHOLD): cv.int_range(min=0x05, max=0x30), @@ -25,8 +24,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_MPR121_ID]) cg.add(var.set_channel(config[CONF_CHANNEL])) diff --git a/esphome/components/mpu6886/__init__.py b/esphome/components/mpu6886/__init__.py new file mode 100644 index 0000000000..933f71ccd7 --- /dev/null +++ b/esphome/components/mpu6886/__init__.py @@ -0,0 +1 @@ +"""Support for MPC-6886.""" diff --git a/esphome/components/mpu6886/mpu6886.cpp b/esphome/components/mpu6886/mpu6886.cpp new file mode 100644 index 0000000000..c296653e6b --- /dev/null +++ b/esphome/components/mpu6886/mpu6886.cpp @@ -0,0 +1,153 @@ +#include "mpu6886.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mpu6886 { + +static const char *const TAG = "mpu6886"; + +const uint8_t MPU6886_REGISTER_WHO_AM_I = 0x75; +const uint8_t MPU6886_REGISTER_POWER_MANAGEMENT_1 = 0x6B; +const uint8_t MPU6886_REGISTER_GYRO_CONFIG = 0x1B; +const uint8_t MPU6886_REGISTER_ACCEL_CONFIG = 0x1C; +const uint8_t MPU6886_REGISTER_ACCEL_XOUT_H = 0x3B; +const uint8_t MPU6886_CLOCK_SOURCE_X_GYRO = 0b001; +const uint8_t MPU6886_SCALE_2000_DPS = 0b11; +const uint8_t MPU6886_WHO_AM_I_IDENTIFIER = 0x19; +const float MPU6886_SCALE_DPS_PER_DIGIT_2000 = 0.060975f; +const uint8_t MPU6886_RANGE_2G = 0b00; +const float MPU6886_RANGE_PER_DIGIT_2G = 0.000061f; +const uint8_t MPU6886_BIT_SLEEP_ENABLED = 6; +const uint8_t MPU6886_BIT_TEMPERATURE_DISABLED = 3; +const float GRAVITY_EARTH = 9.80665f; +// See https://github.com/m5stack/M5-Schematic/blob/master/datasheet/MPU-6886-000193%2Bv1.1_GHIC.PDF.pdf +// p. 43 +const float TEMPERATURE_SENSITIVITY = 326.8; +const float TEMPERATURE_OFFSET = 25.0; + +void MPU6886Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up MPU6886..."); + uint8_t who_am_i; + if (!this->read_byte(MPU6886_REGISTER_WHO_AM_I, &who_am_i) || who_am_i != MPU6886_WHO_AM_I_IDENTIFIER) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Power Management..."); + // Setup power management + uint8_t power_management; + if (!this->read_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, &power_management)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management)); + // Set clock source - X-Gyro + power_management &= 0b11111000; + power_management |= MPU6886_CLOCK_SOURCE_X_GYRO; + // Disable sleep + power_management &= ~(1 << MPU6886_BIT_SLEEP_ENABLED); + // Enable temperature + power_management &= ~(1 << MPU6886_BIT_TEMPERATURE_DISABLED); + ESP_LOGV(TAG, " Output power_management: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(power_management)); + if (!this->write_byte(MPU6886_REGISTER_POWER_MANAGEMENT_1, power_management)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Gyroscope Config..."); + // Set scale - 2000DPS + uint8_t gyro_config; + if (!this->read_byte(MPU6886_REGISTER_GYRO_CONFIG, &gyro_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input gyroscope_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config)); + gyro_config &= 0b11100111; + gyro_config |= MPU6886_SCALE_2000_DPS << 3; + ESP_LOGV(TAG, " Output gyro_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(gyro_config)); + if (!this->write_byte(MPU6886_REGISTER_GYRO_CONFIG, gyro_config)) { + this->mark_failed(); + return; + } + + ESP_LOGV(TAG, " Setting up Accelerometer Config..."); + // Set range - 2G + uint8_t accel_config; + if (!this->read_byte(MPU6886_REGISTER_ACCEL_CONFIG, &accel_config)) { + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Input accelerometer_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); + accel_config &= 0b11100111; + accel_config |= (MPU6886_RANGE_2G << 3); + ESP_LOGV(TAG, " Output accel_config: 0b" BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(accel_config)); + if (!this->write_byte(MPU6886_REGISTER_GYRO_CONFIG, gyro_config)) { + this->mark_failed(); + return; + } +} + +void MPU6886Component::dump_config() { + ESP_LOGCONFIG(TAG, "MPU6886:"); + LOG_I2C_DEVICE(this); + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MPU6886 failed!"); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Acceleration X", this->accel_x_sensor_); + LOG_SENSOR(" ", "Acceleration Y", this->accel_y_sensor_); + LOG_SENSOR(" ", "Acceleration Z", this->accel_z_sensor_); + LOG_SENSOR(" ", "Gyro X", this->gyro_x_sensor_); + LOG_SENSOR(" ", "Gyro Y", this->gyro_y_sensor_); + LOG_SENSOR(" ", "Gyro Z", this->gyro_z_sensor_); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); +} + +void MPU6886Component::update() { + ESP_LOGV(TAG, " Updating MPU6886..."); + uint16_t raw_data[7]; + if (!this->read_bytes_16(MPU6886_REGISTER_ACCEL_XOUT_H, raw_data, 7)) { + this->status_set_warning(); + return; + } + auto *data = reinterpret_cast(raw_data); + + float accel_x = data[0] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + float accel_y = data[1] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + float accel_z = data[2] * MPU6886_RANGE_PER_DIGIT_2G * GRAVITY_EARTH; + + float temperature = data[3] / TEMPERATURE_SENSITIVITY + TEMPERATURE_OFFSET; + + float gyro_x = data[4] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + float gyro_y = data[5] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + float gyro_z = data[6] * MPU6886_SCALE_DPS_PER_DIGIT_2000; + + ESP_LOGD(TAG, + "Got accel={x=%.3f m/s², y=%.3f m/s², z=%.3f m/s²}, " + "gyro={x=%.3f °/s, y=%.3f °/s, z=%.3f °/s}, temp=%.3f°C", + accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z, temperature); + + if (this->accel_x_sensor_ != nullptr) + this->accel_x_sensor_->publish_state(accel_x); + if (this->accel_y_sensor_ != nullptr) + this->accel_y_sensor_->publish_state(accel_y); + if (this->accel_z_sensor_ != nullptr) + this->accel_z_sensor_->publish_state(accel_z); + + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + + if (this->gyro_x_sensor_ != nullptr) + this->gyro_x_sensor_->publish_state(gyro_x); + if (this->gyro_y_sensor_ != nullptr) + this->gyro_y_sensor_->publish_state(gyro_y); + if (this->gyro_z_sensor_ != nullptr) + this->gyro_z_sensor_->publish_state(gyro_z); + + this->status_clear_warning(); +} + +float MPU6886Component::get_setup_priority() const { return setup_priority::DATA; } + +} // namespace mpu6886 +} // namespace esphome diff --git a/esphome/components/mpu6886/mpu6886.h b/esphome/components/mpu6886/mpu6886.h new file mode 100644 index 0000000000..04551ae56d --- /dev/null +++ b/esphome/components/mpu6886/mpu6886.h @@ -0,0 +1,39 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace mpu6886 { + +class MPU6886Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + + void update() override; + + float get_setup_priority() const override; + + void set_accel_x_sensor(sensor::Sensor *accel_x_sensor) { accel_x_sensor_ = accel_x_sensor; } + void set_accel_y_sensor(sensor::Sensor *accel_y_sensor) { accel_y_sensor_ = accel_y_sensor; } + void set_accel_z_sensor(sensor::Sensor *accel_z_sensor) { accel_z_sensor_ = accel_z_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_gyro_x_sensor(sensor::Sensor *gyro_x_sensor) { gyro_x_sensor_ = gyro_x_sensor; } + void set_gyro_y_sensor(sensor::Sensor *gyro_y_sensor) { gyro_y_sensor_ = gyro_y_sensor; } + void set_gyro_z_sensor(sensor::Sensor *gyro_z_sensor) { gyro_z_sensor_ = gyro_z_sensor; } + + protected: + sensor::Sensor *accel_x_sensor_{nullptr}; + sensor::Sensor *accel_y_sensor_{nullptr}; + sensor::Sensor *accel_z_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *gyro_x_sensor_{nullptr}; + sensor::Sensor *gyro_y_sensor_{nullptr}; + sensor::Sensor *gyro_z_sensor_{nullptr}; +}; +; + +} // namespace mpu6886 +} // namespace esphome diff --git a/esphome/components/mpu6886/sensor.py b/esphome/components/mpu6886/sensor.py new file mode 100644 index 0000000000..535007d008 --- /dev/null +++ b/esphome/components/mpu6886/sensor.py @@ -0,0 +1,85 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + ICON_BRIEFCASE_DOWNLOAD, + STATE_CLASS_MEASUREMENT, + UNIT_METER_PER_SECOND_SQUARED, + ICON_SCREEN_ROTATION, + UNIT_DEGREE_PER_SECOND, + UNIT_CELSIUS, +) + +CODEOWNERS = ["@fabaff"] +DEPENDENCIES = ["i2c"] + +CONF_ACCEL_X = "accel_x" +CONF_ACCEL_Y = "accel_y" +CONF_ACCEL_Z = "accel_z" +CONF_GYRO_X = "gyro_x" +CONF_GYRO_Y = "gyro_y" +CONF_GYRO_Z = "gyro_z" + +mpu6886_ns = cg.esphome_ns.namespace("mpu6886") +MPU6886Component = mpu6886_ns.class_( + "MPU6886Component", cg.PollingComponent, i2c.I2CDevice +) + +accel_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_METER_PER_SECOND_SQUARED, + icon=ICON_BRIEFCASE_DOWNLOAD, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +gyro_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREE_PER_SECOND, + icon=ICON_SCREEN_ROTATION, + accuracy_decimals=2, + state_class=STATE_CLASS_MEASUREMENT, +) +temperature_schema = sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MPU6886Component), + cv.Optional(CONF_ACCEL_X): accel_schema, + cv.Optional(CONF_ACCEL_Y): accel_schema, + cv.Optional(CONF_ACCEL_Z): accel_schema, + cv.Optional(CONF_GYRO_X): gyro_schema, + cv.Optional(CONF_GYRO_Y): gyro_schema, + cv.Optional(CONF_GYRO_Z): gyro_schema, + cv.Optional(CONF_TEMPERATURE): temperature_schema, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x68)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + for d in ["x", "y", "z"]: + accel_key = f"accel_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(var, f"set_accel_{d}_sensor")(sens)) + accel_key = f"gyro_{d}" + if accel_key in config: + sens = await sensor.new_sensor(config[accel_key]) + cg.add(getattr(var, f"set_gyro_{d}_sensor")(sens)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 148316672a..1fea0c80cc 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -1,5 +1,4 @@ #include "mqtt_client.h" -#define USE_MQTT #ifdef USE_MQTT diff --git a/esphome/components/mqtt_subscribe/sensor/__init__.py b/esphome/components/mqtt_subscribe/sensor/__init__.py index 420d4f152c..6fe0c48ae0 100644 --- a/esphome/components/mqtt_subscribe/sensor/__init__.py +++ b/esphome/components/mqtt_subscribe/sensor/__init__.py @@ -2,10 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import mqtt, sensor from esphome.const import ( - CONF_ID, CONF_QOS, CONF_TOPIC, - STATE_CLASS_NONE, ) from .. import mqtt_subscribe_ns @@ -18,12 +16,11 @@ MQTTSubscribeSensor = mqtt_subscribe_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + MQTTSubscribeSensor, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(MQTTSubscribeSensor), cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent), cv.Required(CONF_TOPIC): cv.subscribe_topic, cv.Optional(CONF_QOS, default=0): cv.mqtt_qos, @@ -34,9 +31,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID]) cg.add(var.set_parent(parent)) diff --git a/esphome/components/nextion/binary_sensor/__init__.py b/esphome/components/nextion/binary_sensor/__init__.py index 090fae3429..8b4a45cc60 100644 --- a/esphome/components/nextion/binary_sensor/__init__.py +++ b/esphome/components/nextion/binary_sensor/__init__.py @@ -20,9 +20,9 @@ NextionBinarySensor = nextion_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(NextionBinarySensor) + .extend( { - cv.GenerateID(): cv.declare_id(NextionBinarySensor), cv.Optional(CONF_PAGE_ID): cv.uint8_t, cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, } diff --git a/esphome/components/nextion/sensor/__init__.py b/esphome/components/nextion/sensor/__init__.py index 8a32adc1f6..b022007ddd 100644 --- a/esphome/components/nextion/sensor/__init__.py +++ b/esphome/components/nextion/sensor/__init__.py @@ -44,11 +44,11 @@ def _validate(config): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + NextionSensor, accuracy_decimals=2, ) .extend( { - cv.GenerateID(): cv.declare_id(NextionSensor), cv.Optional(CONF_PRECISION, default=0): cv.int_range(min=0, max=8), cv.Optional(CONF_WAVE_CHANNEL_ID): CheckWaveID, cv.Optional(CONF_COMPONENT_ID): cv.uint8_t, diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py index 9b8518d8c4..826ff2354e 100644 --- a/esphome/components/nextion/text_sensor/__init__.py +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -17,7 +17,7 @@ NextionTextSensor = nextion_ns.class_( ) CONFIG_SCHEMA = ( - text_sensor.text_sensor_schema(klass=NextionTextSensor) + text_sensor.text_sensor_schema(NextionTextSensor) .extend(CONFIG_TEXT_COMPONENT_SCHEMA) .extend(cv.polling_component_schema("never")) ) diff --git a/esphome/components/ntc/sensor.py b/esphome/components/ntc/sensor.py index 660208635c..ba8d3df9d8 100644 --- a/esphome/components/ntc/sensor.py +++ b/esphome/components/ntc/sensor.py @@ -5,7 +5,6 @@ import esphome.codegen as cg from esphome.components import sensor from esphome.const import ( CONF_CALIBRATION, - CONF_ID, CONF_REFERENCE_RESISTANCE, CONF_REFERENCE_TEMPERATURE, CONF_SENSOR, @@ -117,6 +116,7 @@ def process_calibration(value): CONFIG_SCHEMA = ( sensor.sensor_schema( + NTC, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, @@ -124,7 +124,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(NTC), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_CALIBRATION): process_calibration, } @@ -134,9 +133,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) diff --git a/esphome/components/pid/sensor/__init__.py b/esphome/components/pid/sensor/__init__.py index d1007fcbc4..d1c65dfb39 100644 --- a/esphome/components/pid/sensor/__init__.py +++ b/esphome/components/pid/sensor/__init__.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_GAUGE, @@ -29,6 +28,7 @@ PID_CLIMATE_SENSOR_TYPES = { CONF_CLIMATE_ID = "climate_id" CONFIG_SCHEMA = ( sensor.sensor_schema( + PIDClimateSensor, unit_of_measurement=UNIT_PERCENT, icon=ICON_GAUGE, accuracy_decimals=1, @@ -36,7 +36,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(PIDClimateSensor), cv.GenerateID(CONF_CLIMATE_ID): cv.use_id(PIDClimate), cv.Required(CONF_TYPE): cv.enum(PID_CLIMATE_SENSOR_TYPES, upper=True), } @@ -47,8 +46,7 @@ CONFIG_SCHEMA = ( async def to_code(config): parent = await cg.get_variable(config[CONF_CLIMATE_ID]) - var = cg.new_Pvariable(config[CONF_ID]) - await sensor.register_sensor(var, config) + var = await sensor.new_sensor(config) await cg.register_component(var, config) cg.add(var.set_parent(parent)) diff --git a/esphome/components/pipsolar/binary_sensor/__init__.py b/esphome/components/pipsolar/binary_sensor/__init__.py index 5c6af3bffc..f4b34fd594 100644 --- a/esphome/components/pipsolar/binary_sensor/__init__.py +++ b/esphome/components/pipsolar/binary_sensor/__init__.py @@ -1,9 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import ( - CONF_ID, -) + from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID DEPENDENCIES = ["uart"] @@ -130,7 +128,7 @@ TYPES = [ ] CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( - {cv.Optional(type): binary_sensor.BINARY_SENSOR_SCHEMA for type in TYPES} + {cv.Optional(type): binary_sensor.binary_sensor_schema() for type in TYPES} ) @@ -139,6 +137,5 @@ async def to_code(config): for type in TYPES: if type in config: conf = config[type] - sens = cg.new_Pvariable(conf[CONF_ID]) - await binary_sensor.register_binary_sensor(sens, conf) - cg.add(getattr(paren, f"set_{type}")(sens)) + var = await binary_sensor.new_binary_sensor(conf) + cg.add(getattr(paren, f"set_{type}")(var)) diff --git a/esphome/components/pipsolar/sensor/__init__.py b/esphome/components/pipsolar/sensor/__init__.py index a206e41988..3a6f94d6ac 100644 --- a/esphome/components/pipsolar/sensor/__init__.py +++ b/esphome/components/pipsolar/sensor/__init__.py @@ -3,18 +3,15 @@ import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( DEVICE_CLASS_CURRENT, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_VOLTAGE, ICON_CURRENT_AC, - ICON_EMPTY, UNIT_AMPERE, UNIT_CELSIUS, UNIT_HERTZ, UNIT_PERCENT, UNIT_VOLT, - UNIT_EMPTY, UNIT_VOLT_AMPS, UNIT_WATT, CONF_BUS_VOLTAGE, @@ -74,134 +71,196 @@ CONF_PV_CHARGING_POWER = "pv_charging_power" TYPES = { CONF_GRID_RATING_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_GRID_RATING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema( - UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, ), CONF_AC_OUTPUT_RATING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, ), CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, ), CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_RECHARGE_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_UNDER_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_BULK_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_FLOAT_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_TYPE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PARALLEL_MAX_NUM: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_MACHINE_TYPE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, + ), + CONF_TOPOLOGY: sensor.sensor_schema( + accuracy_decimals=1, ), - CONF_TOPOLOGY: sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY), CONF_OUTPUT_MODE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PV_POWER_BALANCE: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_GRID_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_GRID_FREQUENCY: sensor.sensor_schema( - UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, ), CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema( - UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, ), CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema( - UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=1, ), CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, ), CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, ), CONF_BUS_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema( - UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, ), CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema( - UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, ), CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema( - UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=1, + device_class=DEVICE_CLASS_CURRENT, ), CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema( - UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, ), CONF_EEPROM_VERSION: sensor.sensor_schema( - UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY + accuracy_decimals=1, ), CONF_PV_CHARGING_POWER: sensor.sensor_schema( - UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER + unit_of_measurement=UNIT_WATT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_POWER, ), } diff --git a/esphome/components/pm1006/sensor.py b/esphome/components/pm1006/sensor.py index 1e648be199..2df9edbf45 100644 --- a/esphome/components/pm1006/sensor.py +++ b/esphome/components/pm1006/sensor.py @@ -16,7 +16,9 @@ CODEOWNERS = ["@habbie"] DEPENDENCIES = ["uart"] pm1006_ns = cg.esphome_ns.namespace("pm1006") -PM1006Component = pm1006_ns.class_("PM1006Component", uart.UARTDevice, cg.Component) +PM1006Component = pm1006_ns.class_( + "PM1006Component", uart.UARTDevice, cg.PollingComponent +) CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index 56a91d22fc..b731e48e31 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -22,7 +22,6 @@ from esphome.const import ( DEVICE_CLASS_PM1, DEVICE_CLASS_PM10, DEVICE_CLASS_PM25, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, ICON_CHEMICAL_WEAPON, @@ -75,22 +74,22 @@ CONFIG_SCHEMA = ( cv.GenerateID(): cv.declare_id(PMSX003Component), cv.Required(CONF_TYPE): cv.enum(PMSX003_TYPES, upper=True), cv.Optional(CONF_PM_1_0_STD): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_PM1, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + device_class=DEVICE_CLASS_PM1, ), cv.Optional(CONF_PM_2_5_STD): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_PM25, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + device_class=DEVICE_CLASS_PM25, ), cv.Optional(CONF_PM_10_0_STD): sensor.sensor_schema( - UNIT_MICROGRAMS_PER_CUBIC_METER, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_PM10, + unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, + device_class=DEVICE_CLASS_PM10, ), cv.Optional(CONF_PM_1_0): sensor.sensor_schema( unit_of_measurement=UNIT_MICROGRAMS_PER_CUBIC_METER, @@ -111,40 +110,34 @@ CONFIG_SCHEMA = ( state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_PM_0_3UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_0_5UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_1_0UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_2_5UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_5_0UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_PM_10_0UM): sensor.sensor_schema( - UNIT_COUNT_DECILITRE, - ICON_CHEMICAL_WEAPON, - 0, - DEVICE_CLASS_EMPTY, + unit_of_measurement=UNIT_COUNT_DECILITRE, + icon=ICON_CHEMICAL_WEAPON, + accuracy_decimals=0, ), cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( unit_of_measurement=UNIT_CELSIUS, diff --git a/esphome/components/pn532/binary_sensor.py b/esphome/components/pn532/binary_sensor.py index 9a5816b322..9bcae30750 100644 --- a/esphome/components/pn532/binary_sensor.py +++ b/esphome/components/pn532/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_UID, CONF_ID +from esphome.const import CONF_UID from esphome.core import HexInt from . import pn532_ns, PN532, CONF_PN532_ID @@ -31,9 +31,8 @@ def validate_uid(value): PN532BinarySensor = pn532_ns.class_("PN532BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(PN532BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(PN532BinarySensor), cv.GenerateID(CONF_PN532_ID): cv.use_id(PN532), cv.Required(CONF_UID): validate_uid, } @@ -41,8 +40,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_PN532_ID]) cg.add(hub.register_tag(var)) diff --git a/esphome/components/pulse_counter/sensor.py b/esphome/components/pulse_counter/sensor.py index c7b89d41b0..6dcb974a1f 100644 --- a/esphome/components/pulse_counter/sensor.py +++ b/esphome/components/pulse_counter/sensor.py @@ -5,7 +5,6 @@ from esphome.components import sensor from esphome.const import ( CONF_COUNT_MODE, CONF_FALLING_EDGE, - CONF_ID, CONF_INTERNAL_FILTER, CONF_PIN, CONF_RISING_EDGE, @@ -66,6 +65,7 @@ def validate_count_mode(value): CONFIG_SCHEMA = ( sensor.sensor_schema( + PulseCounterSensor, unit_of_measurement=UNIT_PULSES_PER_MINUTE, icon=ICON_PULSE, accuracy_decimals=2, @@ -73,7 +73,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(PulseCounterSensor), cv.Required(CONF_PIN): validate_pulse_counter_pin, cv.Optional( CONF_COUNT_MODE, @@ -104,9 +103,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index 7d526b241b..f747f9ee40 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -12,18 +12,53 @@ void PulseMeterSensor::setup() { this->pin_->attach_interrupt(PulseMeterSensor::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE); this->last_detected_edge_us_ = 0; - this->last_valid_edge_us_ = 0; + this->last_valid_low_edge_us_ = 0; + this->last_valid_high_edge_us_ = 0; + this->sensor_is_high_ = this->isr_pin_.digital_read(); } void PulseMeterSensor::loop() { const uint32_t now = micros(); + // Check to see if we should filter this edge out + if (this->filter_mode_ == FILTER_EDGE) { + if ((this->last_detected_edge_us_ - this->last_valid_high_edge_us_) >= this->filter_us_) { + // Don't measure the first valid pulse (we need at least two pulses to measure the width) + if (this->last_valid_high_edge_us_ != 0) { + this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); + } + this->total_pulses_++; + this->last_valid_high_edge_us_ = this->last_detected_edge_us_; + } + } else { + // Make sure the signal has been stable long enough + if ((now - this->last_detected_edge_us_) >= this->filter_us_) { + // Only consider HIGH pulses and "new" edges if sensor state is LOW + if (!this->sensor_is_high_ && this->isr_pin_.digital_read() && + (this->last_detected_edge_us_ != this->last_valid_high_edge_us_)) { + // Don't measure the first valid pulse (we need at least two pulses to measure the width) + if (this->last_valid_high_edge_us_ != 0) { + this->pulse_width_us_ = (this->last_detected_edge_us_ - this->last_valid_high_edge_us_); + } + this->sensor_is_high_ = true; + this->total_pulses_++; + this->last_valid_high_edge_us_ = this->last_detected_edge_us_; + } + // Only consider LOW pulses and "new" edges if sensor state is HIGH + else if (this->sensor_is_high_ && !this->isr_pin_.digital_read() && + (this->last_detected_edge_us_ != this->last_valid_low_edge_us_)) { + this->sensor_is_high_ = false; + this->last_valid_low_edge_us_ = this->last_detected_edge_us_; + } + } + } + // If we've exceeded our timeout interval without receiving any pulses, assume 0 pulses/min until // we get at least two valid pulses. - const uint32_t time_since_valid_edge_us = now - this->last_valid_edge_us_; - if ((this->last_valid_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_)) { + const uint32_t time_since_valid_edge_us = now - this->last_valid_high_edge_us_; + if ((this->last_valid_high_edge_us_ != 0) && (time_since_valid_edge_us > this->timeout_us_) && + (this->pulse_width_us_ != 0)) { ESP_LOGD(TAG, "No pulse detected for %us, assuming 0 pulses/min", time_since_valid_edge_us / 1000000); - this->last_valid_edge_us_ = 0; this->pulse_width_us_ = 0; } @@ -52,7 +87,11 @@ void PulseMeterSensor::set_total_pulses(uint32_t pulses) { this->total_pulses_ = void PulseMeterSensor::dump_config() { LOG_SENSOR("", "Pulse Meter", this); LOG_PIN(" Pin: ", this->pin_); - ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + if (this->filter_mode_ == FILTER_EDGE) { + ESP_LOGCONFIG(TAG, " Filtering rising edges less than %u µs apart", this->filter_us_); + } else { + ESP_LOGCONFIG(TAG, " Filtering pulses shorter than %u µs", this->filter_us_); + } ESP_LOGCONFIG(TAG, " Assuming 0 pulses/min after not receiving a pulse for %us", this->timeout_us_ / 1000000); } @@ -62,23 +101,14 @@ void IRAM_ATTR PulseMeterSensor::gpio_intr(PulseMeterSensor *sensor) { // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); - // We only look at rising edges - if (!sensor->isr_pin_.digital_read()) { - return; - } - - // Check to see if we should filter this edge out - if ((now - sensor->last_detected_edge_us_) >= sensor->filter_us_) { - // Don't measure the first valid pulse (we need at least two pulses to measure the width) - if (sensor->last_valid_edge_us_ != 0) { - sensor->pulse_width_us_ = (now - sensor->last_valid_edge_us_); + // We only look at rising edges in EDGE mode, and all edges in PULSE mode + if (sensor->filter_mode_ == FILTER_EDGE) { + if (sensor->isr_pin_.digital_read()) { + sensor->last_detected_edge_us_ = now; } - - sensor->total_pulses_++; - sensor->last_valid_edge_us_ = now; + } else { + sensor->last_detected_edge_us_ = now; } - - sensor->last_detected_edge_us_ = now; } } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index 1cebc1748e..cf08f8c92d 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -10,8 +10,14 @@ namespace pulse_meter { class PulseMeterSensor : public sensor::Sensor, public Component { public: + enum InternalFilterMode { + FILTER_EDGE = 0, + FILTER_PULSE, + }; + void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; } void set_filter_us(uint32_t filter) { this->filter_us_ = filter; } + void set_filter_mode(InternalFilterMode mode) { this->filter_mode_ = mode; } void set_timeout_us(uint32_t timeout) { this->timeout_us_ = timeout; } void set_total_sensor(sensor::Sensor *sensor) { this->total_sensor_ = sensor; } @@ -30,14 +36,17 @@ class PulseMeterSensor : public sensor::Sensor, public Component { uint32_t filter_us_ = 0; uint32_t timeout_us_ = 1000000UL * 60UL * 5UL; sensor::Sensor *total_sensor_ = nullptr; + InternalFilterMode filter_mode_{FILTER_EDGE}; Deduplicator pulse_width_dedupe_; Deduplicator total_dedupe_; volatile uint32_t last_detected_edge_us_ = 0; - volatile uint32_t last_valid_edge_us_ = 0; + volatile uint32_t last_valid_low_edge_us_ = 0; + volatile uint32_t last_valid_high_edge_us_ = 0; volatile uint32_t pulse_width_us_ = 0; volatile uint32_t total_pulses_ = 0; + volatile bool sensor_is_high_ = false; }; } // namespace pulse_meter diff --git a/esphome/components/pulse_meter/sensor.py b/esphome/components/pulse_meter/sensor.py index 454cb3a69d..26bc6b189b 100644 --- a/esphome/components/pulse_meter/sensor.py +++ b/esphome/components/pulse_meter/sensor.py @@ -5,6 +5,7 @@ from esphome.components import sensor from esphome.const import ( CONF_ID, CONF_INTERNAL_FILTER, + CONF_INTERNAL_FILTER_MODE, CONF_PIN, CONF_NUMBER, CONF_TIMEOUT, @@ -18,14 +19,21 @@ from esphome.const import ( ) from esphome.core import CORE -CODEOWNERS = ["@stevebaxter"] +CODEOWNERS = ["@stevebaxter", "@cstaahl"] pulse_meter_ns = cg.esphome_ns.namespace("pulse_meter") + PulseMeterSensor = pulse_meter_ns.class_( "PulseMeterSensor", sensor.Sensor, cg.Component ) +PulseMeterInternalFilterMode = PulseMeterSensor.enum("InternalFilterMode") +FILTER_MODES = { + "EDGE": PulseMeterInternalFilterMode.FILTER_EDGE, + "PULSE": PulseMeterInternalFilterMode.FILTER_PULSE, +} + SetTotalPulsesAction = pulse_meter_ns.class_("SetTotalPulsesAction", automation.Action) @@ -50,13 +58,13 @@ def validate_pulse_meter_pin(value): CONFIG_SCHEMA = sensor.sensor_schema( + PulseMeterSensor, unit_of_measurement=UNIT_PULSES_PER_MINUTE, icon=ICON_PULSE, accuracy_decimals=2, state_class=STATE_CLASS_MEASUREMENT, ).extend( { - cv.GenerateID(): cv.declare_id(PulseMeterSensor), cv.Required(CONF_PIN): validate_pulse_meter_pin, cv.Optional(CONF_INTERNAL_FILTER, default="13us"): validate_internal_filter, cv.Optional(CONF_TIMEOUT, default="5min"): validate_timeout, @@ -66,19 +74,22 @@ CONFIG_SCHEMA = sensor.sensor_schema( accuracy_decimals=0, state_class=STATE_CLASS_TOTAL_INCREASING, ), + cv.Optional(CONF_INTERNAL_FILTER_MODE, default="EDGE"): cv.enum( + FILTER_MODES, upper=True + ), } ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) cg.add(var.set_filter_us(config[CONF_INTERNAL_FILTER])) cg.add(var.set_timeout_us(config[CONF_TIMEOUT])) + cg.add(var.set_filter_mode(config[CONF_INTERNAL_FILTER_MODE])) if CONF_TOTAL in config: sens = await sensor.new_sensor(config[CONF_TOTAL]) diff --git a/esphome/components/pulse_width/sensor.py b/esphome/components/pulse_width/sensor.py index b090647627..47d70166d3 100644 --- a/esphome/components/pulse_width/sensor.py +++ b/esphome/components/pulse_width/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome import pins from esphome.components import sensor from esphome.const import ( - CONF_ID, CONF_PIN, STATE_CLASS_MEASUREMENT, UNIT_SECOND, @@ -18,6 +17,7 @@ PulseWidthSensor = pulse_width_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + PulseWidthSensor, unit_of_measurement=UNIT_SECOND, icon=ICON_TIMER, accuracy_decimals=3, @@ -25,7 +25,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(PulseWidthSensor), cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } ) @@ -34,9 +33,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) pin = await cg.gpio_pin_expression(config[CONF_PIN]) cg.add(var.set_pin(pin)) diff --git a/esphome/components/qmc5883l/sensor.py b/esphome/components/qmc5883l/sensor.py index 27d1df5b29..100ac93520 100644 --- a/esphome/components/qmc5883l/sensor.py +++ b/esphome/components/qmc5883l/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_RANGE, ICON_MAGNET, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION, @@ -79,7 +78,6 @@ heading_schema = sensor.sensor_schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SCREEN_ROTATION, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) CONFIG_SCHEMA = ( diff --git a/esphome/components/rc522/binary_sensor.py b/esphome/components/rc522/binary_sensor.py index 67d3068599..716c0eca76 100644 --- a/esphome/components/rc522/binary_sensor.py +++ b/esphome/components/rc522/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_UID, CONF_ID +from esphome.const import CONF_UID from esphome.core import HexInt from . import rc522_ns, RC522, CONF_RC522_ID @@ -31,9 +31,8 @@ def validate_uid(value): RC522BinarySensor = rc522_ns.class_("RC522BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(RC522BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(RC522BinarySensor), cv.GenerateID(CONF_RC522_ID): cv.use_id(RC522), cv.Required(CONF_UID): validate_uid, } @@ -41,8 +40,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_RC522_ID]) cg.add(hub.register_tag(var)) diff --git a/esphome/components/rdm6300/binary_sensor.py b/esphome/components/rdm6300/binary_sensor.py index c99a2bfc06..cd808b92cc 100644 --- a/esphome/components/rdm6300/binary_sensor.py +++ b/esphome/components/rdm6300/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, rdm6300 -from esphome.const import CONF_UID, CONF_ID +from esphome.const import CONF_UID from . import rdm6300_ns DEPENDENCIES = ["rdm6300"] @@ -11,9 +11,8 @@ RDM6300BinarySensor = rdm6300_ns.class_( "RDM6300BinarySensor", binary_sensor.BinarySensor ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(RDM6300BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(RDM6300BinarySensor), cv.GenerateID(CONF_RDM6300_ID): cv.use_id(rdm6300.RDM6300Component), cv.Required(CONF_UID): cv.uint32_t, } @@ -21,8 +20,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_RDM6300_ID]) cg.add(hub.register_card(var)) diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py index 72a91a99dd..f1b3e32c18 100644 --- a/esphome/components/remote_base/__init__.py +++ b/esphome/components/remote_base/__init__.py @@ -27,6 +27,8 @@ from esphome.const import ( CONF_CARRIER_FREQUENCY, CONF_RC_CODE_1, CONF_RC_CODE_2, + CONF_MAGNITUDE, + CONF_WAND_ID, CONF_LEVEL, ) from esphome.core import coroutine @@ -165,7 +167,7 @@ def declare_protocol(name): BINARY_SENSOR_REGISTRY = Registry( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema().extend( { cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase), } @@ -391,6 +393,54 @@ async def lg_action(var, config, args): cg.add(var.set_nbits(template_)) +# MagiQuest +( + MagiQuestData, + MagiQuestBinarySensor, + MagiQuestTrigger, + MagiQuestAction, + MagiQuestDumper, +) = declare_protocol("MagiQuest") + +MAGIQUEST_SCHEMA = cv.Schema( + { + cv.Required(CONF_WAND_ID): cv.hex_uint32_t, + cv.Optional(CONF_MAGNITUDE, default=0xFFFF): cv.hex_uint16_t, + } +) + + +@register_binary_sensor("magiquest", MagiQuestBinarySensor, MAGIQUEST_SCHEMA) +def magiquest_binary_sensor(var, config): + cg.add( + var.set_data( + cg.StructInitializer( + MagiQuestData, + ("magnitude", config[CONF_MAGNITUDE]), + ("wand_id", config[CONF_WAND_ID]), + ) + ) + ) + + +@register_trigger("magiquest", MagiQuestTrigger, MagiQuestData) +def magiquest_trigger(var, config): + pass + + +@register_dumper("magiquest", MagiQuestDumper) +def magiquest_dumper(var, config): + pass + + +@register_action("magiquest", MagiQuestAction, MAGIQUEST_SCHEMA) +async def magiquest_action(var, config, args): + template_ = await cg.templatable(config[CONF_WAND_ID], args, cg.uint32) + cg.add(var.set_wand_id(template_)) + template_ = await cg.templatable(config[CONF_MAGNITUDE], args, cg.uint16) + cg.add(var.set_magnitude(template_)) + + # NEC NECData, NECBinarySensor, NECTrigger, NECAction, NECDumper = declare_protocol("NEC") NEC_SCHEMA = cv.Schema( @@ -847,7 +897,7 @@ async def rc_switch_raw_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add(var.set_code((await cg.templatable(config[CONF_CODE], args, cg.std_string)))) + cg.add(var.set_code(await cg.templatable(config[CONF_CODE], args, cg.std_string))) @register_binary_sensor( @@ -868,13 +918,11 @@ async def rc_switch_type_a_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) cg.add( - var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.std_string))) + var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.std_string)) ) - cg.add( - var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.std_string))) - ) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -897,13 +945,9 @@ async def rc_switch_type_b_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add( - var.set_address((await cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) - ) - cg.add( - var.set_channel((await cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) - ) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_address(await cg.templatable(config[CONF_ADDRESS], args, cg.uint8))) + cg.add(var.set_channel(await cg.templatable(config[CONF_CHANNEL], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -932,11 +976,11 @@ async def rc_switch_type_c_action(var, config, args): ) cg.add(var.set_protocol(proto)) cg.add( - var.set_family((await cg.templatable(config[CONF_FAMILY], args, cg.std_string))) + var.set_family(await cg.templatable(config[CONF_FAMILY], args, cg.std_string)) ) - cg.add(var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.uint8)))) - cg.add(var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.uint8))) + cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_binary_sensor( @@ -959,11 +1003,9 @@ async def rc_switch_type_d_action(var, config, args): config[CONF_PROTOCOL], args, RCSwitchBase, to_exp=build_rc_switch_protocol ) cg.add(var.set_protocol(proto)) - cg.add( - var.set_group((await cg.templatable(config[CONF_GROUP], args, cg.std_string))) - ) - cg.add(var.set_device((await cg.templatable(config[CONF_DEVICE], args, cg.uint8)))) - cg.add(var.set_state((await cg.templatable(config[CONF_STATE], args, bool)))) + cg.add(var.set_group(await cg.templatable(config[CONF_GROUP], args, cg.std_string))) + cg.add(var.set_device(await cg.templatable(config[CONF_DEVICE], args, cg.uint8))) + cg.add(var.set_state(await cg.templatable(config[CONF_STATE], args, bool))) @register_trigger("rc_switch", RCSwitchTrigger, RCSwitchData) diff --git a/esphome/components/remote_base/magiquest_protocol.cpp b/esphome/components/remote_base/magiquest_protocol.cpp new file mode 100644 index 0000000000..20b40ef201 --- /dev/null +++ b/esphome/components/remote_base/magiquest_protocol.cpp @@ -0,0 +1,83 @@ +#include "magiquest_protocol.h" +#include "esphome/core/log.h" + +/* Based on protocol analysis from + * https://arduino-irremote.github.io/Arduino-IRremote/ir__MagiQuest_8cpp_source.html + */ + +namespace esphome { +namespace remote_base { + +static const char *const TAG = "remote.magiquest"; + +static const uint32_t MAGIQUEST_UNIT = 288; // us +static const uint32_t MAGIQUEST_ONE_MARK = 2 * MAGIQUEST_UNIT; +static const uint32_t MAGIQUEST_ONE_SPACE = 2 * MAGIQUEST_UNIT; +static const uint32_t MAGIQUEST_ZERO_MARK = MAGIQUEST_UNIT; +static const uint32_t MAGIQUEST_ZERO_SPACE = 3 * MAGIQUEST_UNIT; + +void MagiQuestProtocol::encode(RemoteTransmitData *dst, const MagiQuestData &data) { + dst->reserve(101); // 2 start bits, 48 data bits, 1 stop bit + dst->set_carrier_frequency(38000); + + // 2 start bits + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + for (uint32_t mask = 1 << 31; mask; mask >>= 1) { + if (data.wand_id & mask) { + dst->item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE); + } else { + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + } + } + + for (uint16_t mask = 1 << 15; mask; mask >>= 1) { + if (data.magnitude & mask) { + dst->item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE); + } else { + dst->item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE); + } + } + + dst->mark(MAGIQUEST_UNIT); +} +optional MagiQuestProtocol::decode(RemoteReceiveData src) { + MagiQuestData data{ + .magnitude = 0, + .wand_id = 0, + }; + // Two start bits + if (!src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE) || + !src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) { + return {}; + } + + for (uint32_t mask = 1 << 31; mask; mask >>= 1) { + if (src.expect_item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE)) { + data.wand_id |= mask; + } else if (src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) { + data.wand_id &= ~mask; + } else { + return {}; + } + } + + for (uint16_t mask = 1 << 15; mask; mask >>= 1) { + if (src.expect_item(MAGIQUEST_ONE_MARK, MAGIQUEST_ONE_SPACE)) { + data.magnitude |= mask; + } else if (src.expect_item(MAGIQUEST_ZERO_MARK, MAGIQUEST_ZERO_SPACE)) { + data.magnitude &= ~mask; + } else { + return {}; + } + } + + src.expect_mark(MAGIQUEST_UNIT); + return data; +} +void MagiQuestProtocol::dump(const MagiQuestData &data) { + ESP_LOGD(TAG, "Received MagiQuest: wand_id=0x%08X, magnitude=0x%04X", data.wand_id, data.magnitude); +} + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/remote_base/magiquest_protocol.h b/esphome/components/remote_base/magiquest_protocol.h new file mode 100644 index 0000000000..909be346d0 --- /dev/null +++ b/esphome/components/remote_base/magiquest_protocol.h @@ -0,0 +1,50 @@ +#pragma once + +#include "remote_base.h" + +/* Based on protocol analysis from + * https://arduino-irremote.github.io/Arduino-IRremote/ir__MagiQuest_8cpp_source.html + */ + +namespace esphome { +namespace remote_base { + +struct MagiQuestData { + uint16_t magnitude; + uint32_t wand_id; + + bool operator==(const MagiQuestData &rhs) const { + // Treat 0xffff as a special, wildcard magnitude + // In testing, the wand never produces this value, and this allows us to match + // on just the wand_id if wanted. + if (rhs.wand_id != this->wand_id) { + return false; + } + return (this->wand_id == 0xffff || rhs.wand_id == 0xffff || this->wand_id == rhs.wand_id); + } +}; + +class MagiQuestProtocol : public RemoteProtocol { + public: + void encode(RemoteTransmitData *dst, const MagiQuestData &data) override; + optional decode(RemoteReceiveData src) override; + void dump(const MagiQuestData &data) override; +}; + +DECLARE_REMOTE_PROTOCOL(MagiQuest) + +template class MagiQuestAction : public RemoteTransmitterActionBase { + public: + TEMPLATABLE_VALUE(uint16_t, magnitude) + TEMPLATABLE_VALUE(uint32_t, wand_id) + + void encode(RemoteTransmitData *dst, Ts... x) override { + MagiQuestData data{}; + data.magnitude = this->magnitude_.value(x...); + data.wand_id = this->wand_id_.value(x...); + MagiQuestProtocol().encode(dst, data); + } +}; + +} // namespace remote_base +} // namespace esphome diff --git a/esphome/components/resistance/sensor.py b/esphome/components/resistance/sensor.py index 329192e902..55e7ddfc81 100644 --- a/esphome/components/resistance/sensor.py +++ b/esphome/components/resistance/sensor.py @@ -6,7 +6,6 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_OHM, ICON_FLASH, - CONF_ID, ) resistance_ns = cg.esphome_ns.namespace("resistance") @@ -24,6 +23,7 @@ CONFIGURATIONS = { CONFIG_SCHEMA = ( sensor.sensor_schema( + ResistanceSensor, unit_of_measurement=UNIT_OHM, icon=ICON_FLASH, accuracy_decimals=1, @@ -31,7 +31,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(ResistanceSensor), cv.Required(CONF_SENSOR): cv.use_id(sensor.Sensor), cv.Required(CONF_CONFIGURATION): cv.enum(CONFIGURATIONS, upper=True), cv.Required(CONF_RESISTOR): cv.resistance, @@ -43,9 +42,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_SENSOR]) cg.add(var.set_sensor(sens)) diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py index cd747264b3..ae6b0ae3bf 100644 --- a/esphome/components/rotary_encoder/sensor.py +++ b/esphome/components/rotary_encoder/sensor.py @@ -7,7 +7,6 @@ from esphome.const import ( CONF_RESOLUTION, CONF_MIN_VALUE, CONF_MAX_VALUE, - STATE_CLASS_NONE, UNIT_STEPS, ICON_ROTATE_RIGHT, CONF_VALUE, @@ -65,14 +64,13 @@ def validate_min_max_value(config): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + RotaryEncoderSensor, unit_of_measurement=UNIT_STEPS, icon=ICON_ROTATE_RIGHT, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(RotaryEncoderSensor), cv.Required(CONF_PIN_A): cv.All(pins.internal_gpio_input_pin_schema), cv.Required(CONF_PIN_B): cv.All(pins.internal_gpio_input_pin_schema), cv.Optional(CONF_PIN_RESET): pins.internal_gpio_output_pin_schema, @@ -105,9 +103,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) + pin_a = await cg.gpio_pin_expression(config[CONF_PIN_A]) cg.add(var.set_pin_a(pin_a)) pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B]) diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py index 2bb9549195..a46daf88ac 100644 --- a/esphome/components/ruuvitag/sensor.py +++ b/esphome/components/ruuvitag/sensor.py @@ -21,7 +21,6 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_CELSIUS, UNIT_PERCENT, UNIT_VOLT, @@ -108,13 +107,11 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema( icon=ICON_GAUGE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), } diff --git a/esphome/components/sdp3x/sensor.py b/esphome/components/sdp3x/sensor.py index 45f5cc4d9a..66ee475b11 100644 --- a/esphome/components/sdp3x/sensor.py +++ b/esphome/components/sdp3x/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_PRESSURE, STATE_CLASS_MEASUREMENT, UNIT_HECTOPASCAL, @@ -24,6 +23,7 @@ CONF_MEASUREMENT_MODE = "measurement_mode" CONFIG_SCHEMA = ( sensor.sensor_schema( + SDP3XComponent, unit_of_measurement=UNIT_HECTOPASCAL, accuracy_decimals=3, device_class=DEVICE_CLASS_PRESSURE, @@ -31,7 +31,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(SDP3XComponent), cv.Optional( CONF_MEASUREMENT_MODE, default="differential_pressure" ): cv.enum(MEASUREMENT_MODE), @@ -43,8 +42,7 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_measurement_mode(config[CONF_MEASUREMENT_MODE])) diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py index 14a15da2f1..65ae7b2168 100644 --- a/esphome/components/sensor/__init__.py +++ b/esphome/components/sensor/__init__.py @@ -58,6 +58,7 @@ from esphome.const import ( DEVICE_CLASS_VOLTAGE, ) from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_generator import MockObjClass from esphome.cpp_helpers import setup_entity from esphome.util import Registry @@ -223,6 +224,8 @@ _UNDEF = object() def sensor_schema( + class_: MockObjClass = _UNDEF, + *, unit_of_measurement: str = _UNDEF, icon: str = _UNDEF, accuracy_decimals: int = _UNDEF, @@ -231,6 +234,8 @@ def sensor_schema( entity_category: str = _UNDEF, ) -> cv.Schema: schema = SENSOR_SCHEMA + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) if unit_of_measurement is not _UNDEF: schema = schema.extend( { @@ -403,18 +408,30 @@ async def sliding_window_moving_average_filter_to_code(config, filter_id): ) -@FILTER_REGISTRY.register( - "exponential_moving_average", - ExponentialMovingAverageFilter, +EXPONENTIAL_AVERAGE_SCHEMA = cv.All( cv.Schema( { cv.Optional(CONF_ALPHA, default=0.1): cv.positive_float, cv.Optional(CONF_SEND_EVERY, default=15): cv.positive_not_null_int, + cv.Optional(CONF_SEND_FIRST_AT, default=1): cv.positive_not_null_int, } ), + validate_send_first_at, +) + + +@FILTER_REGISTRY.register( + "exponential_moving_average", + ExponentialMovingAverageFilter, + EXPONENTIAL_AVERAGE_SCHEMA, ) async def exponential_moving_average_filter_to_code(config, filter_id): - return cg.new_Pvariable(filter_id, config[CONF_ALPHA], config[CONF_SEND_EVERY]) + return cg.new_Pvariable( + filter_id, + config[CONF_ALPHA], + config[CONF_SEND_EVERY], + config[CONF_SEND_FIRST_AT], + ) @FILTER_REGISTRY.register( diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 49d2c648b0..d4a7a52fa1 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -200,8 +200,8 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { } // ExponentialMovingAverageFilter -ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every) - : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} +ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size_t send_every, size_t send_first_at) + : send_every_(send_every), send_at_(send_every - send_first_at), alpha_(alpha) {} optional ExponentialMovingAverageFilter::new_value(float value) { if (!std::isnan(value)) { if (this->first_value_) { diff --git a/esphome/components/sensor/filter.h b/esphome/components/sensor/filter.h index 0ed7ce4801..a39c1ba25a 100644 --- a/esphome/components/sensor/filter.h +++ b/esphome/components/sensor/filter.h @@ -194,7 +194,7 @@ class SlidingWindowMovingAverageFilter : public Filter { */ class ExponentialMovingAverageFilter : public Filter { public: - ExponentialMovingAverageFilter(float alpha, size_t send_every); + ExponentialMovingAverageFilter(float alpha, size_t send_every, size_t send_first_at); optional new_value(float value) override; diff --git a/esphome/components/sgp30/sensor.py b/esphome/components/sgp30/sensor.py index 2596e0065d..14a078b501 100644 --- a/esphome/components/sgp30/sensor.py +++ b/esphome/components/sgp30/sensor.py @@ -13,6 +13,7 @@ from esphome.const import ( UNIT_PARTS_PER_MILLION, UNIT_PARTS_PER_BILLION, ICON_MOLECULE_CO2, + ENTITY_CATEGORY_DIAGNOSTIC, ) DEPENDENCIES = ["i2c"] @@ -49,10 +50,12 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema( icon=ICON_MOLECULE_CO2, accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_TVOC_BASELINE): sensor.sensor_schema( icon=ICON_RADIATOR, accuracy_decimals=0, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_BASELINE): cv.Schema( diff --git a/esphome/components/sgp40/sensor.py b/esphome/components/sgp40/sensor.py index 7b96f867af..6f27b54fb0 100644 --- a/esphome/components/sgp40/sensor.py +++ b/esphome/components/sgp40/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, ICON_RADIATOR, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, STATE_CLASS_MEASUREMENT, @@ -25,6 +24,7 @@ CONF_VOC_BASELINE = "voc_baseline" CONFIG_SCHEMA = ( sensor.sensor_schema( + SGP40Component, icon=ICON_RADIATOR, accuracy_decimals=0, device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, @@ -32,7 +32,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(SGP40Component), cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean, cv.Optional(CONF_VOC_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_COMPENSATION): cv.Schema( @@ -49,10 +48,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) if CONF_COMPENSATION in config: compensation_config = config[CONF_COMPENSATION] diff --git a/esphome/components/sim800l/__init__.py b/esphome/components/sim800l/__init__.py index 4143627084..564b685b37 100644 --- a/esphome/components/sim800l/__init__.py +++ b/esphome/components/sim800l/__init__.py @@ -1,7 +1,10 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome import automation -from esphome.const import CONF_ID, CONF_TRIGGER_ID +from esphome.const import ( + CONF_ID, + CONF_TRIGGER_ID, +) from esphome.components import uart DEPENDENCIES = ["uart"] @@ -20,6 +23,7 @@ Sim800LReceivedMessageTrigger = sim800l_ns.class_( Sim800LSendSmsAction = sim800l_ns.class_("Sim800LSendSmsAction", automation.Action) Sim800LDialAction = sim800l_ns.class_("Sim800LDialAction", automation.Action) +CONF_SIM800L_ID = "sim800l_id" CONF_ON_SMS_RECEIVED = "on_sms_received" CONF_RECIPIENT = "recipient" CONF_MESSAGE = "message" diff --git a/esphome/components/sim800l/binary_sensor.py b/esphome/components/sim800l/binary_sensor.py new file mode 100644 index 0000000000..f046d031ea --- /dev/null +++ b/esphome/components/sim800l/binary_sensor.py @@ -0,0 +1,28 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor +from esphome.const import ( + DEVICE_CLASS_CONNECTIVITY, + ENTITY_CATEGORY_DIAGNOSTIC, +) +from . import CONF_SIM800L_ID, Sim800LComponent + +DEPENDENCIES = ["sim800l"] + +CONF_REGISTERED = "registered" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_SIM800L_ID): cv.use_id(Sim800LComponent), + cv.Optional(CONF_REGISTERED): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_CONNECTIVITY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + sim800l_component = await cg.get_variable(config[CONF_SIM800L_ID]) + + if CONF_REGISTERED in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_REGISTERED]) + cg.add(sim800l_component.set_registered_binary_sensor(sens)) diff --git a/esphome/components/sim800l/sensor.py b/esphome/components/sim800l/sensor.py new file mode 100644 index 0000000000..156bd6a040 --- /dev/null +++ b/esphome/components/sim800l/sensor.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + DEVICE_CLASS_SIGNAL_STRENGTH, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_DECIBEL_MILLIWATT, +) +from . import CONF_SIM800L_ID, Sim800LComponent + +DEPENDENCIES = ["sim800l"] + +CONF_RSSI = "rssi" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_SIM800L_ID): cv.use_id(Sim800LComponent), + cv.Optional(CONF_RSSI): sensor.sensor_schema( + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), +} + + +async def to_code(config): + sim800l_component = await cg.get_variable(config[CONF_SIM800L_ID]) + + if CONF_RSSI in config: + sens = await sensor.new_sensor(config[CONF_RSSI]) + cg.add(sim800l_component.set_rssi_sensor(sens)) diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp index eb6d62ca33..709e241491 100644 --- a/esphome/components/sim800l/sim800l.cpp +++ b/esphome/components/sim800l/sim800l.cpp @@ -117,7 +117,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->state_ = STATE_CREG; } } - this->registered_ = registered; + set_registered_(registered); break; } case STATE_CSQ: @@ -128,8 +128,17 @@ void Sim800LComponent::parse_cmd_(std::string message) { if (message.compare(0, 5, "+CSQ:") == 0) { size_t comma = message.find(',', 6); if (comma != 6) { - this->rssi_ = parse_number(message.substr(6, comma - 6)).value_or(0); - ESP_LOGD(TAG, "RSSI: %d", this->rssi_); + int rssi = parse_number(message.substr(6, comma - 6)).value_or(0); + +#ifdef USE_SENSOR + if (this->rssi_sensor_ != nullptr) { + this->rssi_sensor_->publish_state(rssi); + } else { + ESP_LOGD(TAG, "RSSI: %d", rssi); + } +#else + ESP_LOGD(TAG, "RSSI: %d", rssi); +#endif } } this->expect_ack_ = true; @@ -201,7 +210,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->write(26); this->state_ = STATE_SENDINGSMS3; } else { - this->registered_ = false; + set_registered_(false); this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); @@ -226,7 +235,7 @@ void Sim800LComponent::parse_cmd_(std::string message) { this->state_ = STATE_INIT; this->dial_pending_ = false; } else { - this->registered_ = false; + this->set_registered_(false); this->state_ = STATE_INIT; this->send_cmd_("AT+CMEE=2"); this->write(26); @@ -277,7 +286,12 @@ void Sim800LComponent::send_sms(const std::string &recipient, const std::string } void Sim800LComponent::dump_config() { ESP_LOGCONFIG(TAG, "SIM800L:"); - ESP_LOGCONFIG(TAG, " RSSI: %d dB", this->rssi_); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "Registered", this->registered_binary_sensor_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Rssi", this->rssi_sensor_); +#endif } void Sim800LComponent::dial(const std::string &recipient) { ESP_LOGD(TAG, "Dialing %s", recipient.c_str()); @@ -286,5 +300,13 @@ void Sim800LComponent::dial(const std::string &recipient) { this->update(); } +void Sim800LComponent::set_registered_(bool registered) { + this->registered_ = registered; +#ifdef USE_BINARY_SENSOR + if (this->registered_binary_sensor_ != nullptr) + this->registered_binary_sensor_->publish_state(registered); +#endif +} + } // namespace sim800l } // namespace esphome diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 4f738b0a8c..3535b96283 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -2,7 +2,14 @@ #include +#include "esphome/core/defines.h" #include "esphome/core/component.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif #include "esphome/components/uart/uart.h" #include "esphome/core/automation.h" @@ -42,6 +49,14 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { void update() override; void loop() override; void dump_config() override; +#ifdef USE_BINARY_SENSOR + void set_registered_binary_sensor(binary_sensor::BinarySensor *registered_binary_sensor) { + registered_binary_sensor_ = registered_binary_sensor; + } +#endif +#ifdef USE_SENSOR + void set_rssi_sensor(sensor::Sensor *rssi_sensor) { rssi_sensor_ = rssi_sensor; } +#endif void add_on_sms_received_callback(std::function callback) { this->callback_.add(std::move(callback)); } @@ -51,7 +66,15 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { protected: void send_cmd_(const std::string &message); void parse_cmd_(std::string message); + void set_registered_(bool registered); +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *registered_binary_sensor_{nullptr}; +#endif + +#ifdef USE_SENSOR + sensor::Sensor *rssi_sensor_{nullptr}; +#endif std::string sender_; char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; size_t read_pos_{0}; @@ -60,7 +83,6 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { bool expect_ack_{false}; sim800l::State state_{STATE_IDLE}; bool registered_{false}; - int rssi_{0}; std::string recipient_; std::string outgoing_message_; diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py index 9367706388..1f2b7c9d18 100644 --- a/esphome/components/status/binary_sensor.py +++ b/esphome/components/status/binary_sensor.py @@ -2,9 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor from esphome.const import ( - CONF_ENTITY_CATEGORY, - CONF_ID, - CONF_DEVICE_CLASS, DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC, ) @@ -14,20 +11,13 @@ StatusBinarySensor = status_ns.class_( "StatusBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(StatusBinarySensor), - cv.Optional( - CONF_DEVICE_CLASS, default=DEVICE_CLASS_CONNECTIVITY - ): binary_sensor.device_class, - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC - ): cv.entity_category, - } +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema( + StatusBinarySensor, + device_class=DEVICE_CLASS_CONNECTIVITY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ).extend(cv.COMPONENT_SCHEMA) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) diff --git a/esphome/components/sts3x/sensor.py b/esphome/components/sts3x/sensor.py index b02c835ef8..aa7573aaf2 100644 --- a/esphome/components/sts3x/sensor.py +++ b/esphome/components/sts3x/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -18,23 +17,18 @@ STS3XComponent = sts3x_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + STS3XComponent, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(STS3XComponent), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x4A)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/sun/sensor/__init__.py b/esphome/components/sun/sensor/__init__.py index 236acfadef..10c0237327 100644 --- a/esphome/components/sun/sensor/__init__.py +++ b/esphome/components/sun/sensor/__init__.py @@ -2,10 +2,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - STATE_CLASS_NONE, UNIT_DEGREES, ICON_WEATHER_SUNSET, - CONF_ID, CONF_TYPE, ) from .. import sun_ns, CONF_SUN_ID, Sun @@ -21,14 +19,13 @@ TYPES = { CONFIG_SCHEMA = ( sensor.sensor_schema( + SunSensor, unit_of_measurement=UNIT_DEGREES, icon=ICON_WEATHER_SUNSET, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(SunSensor), cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun), cv.Required(CONF_TYPE): cv.enum(TYPES, lower=True), } @@ -38,9 +35,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_type(config[CONF_TYPE])) paren = await cg.get_variable(config[CONF_SUN_ID]) diff --git a/esphome/components/sx1509/binary_sensor/__init__.py b/esphome/components/sx1509/binary_sensor/__init__.py index 1560af8e99..bbf0e5d0bc 100644 --- a/esphome/components/sx1509/binary_sensor/__init__.py +++ b/esphome/components/sx1509/binary_sensor/__init__.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID + from .. import SX1509Component, sx1509_ns, CONF_SX1509_ID CONF_ROW = "row" @@ -11,9 +11,8 @@ DEPENDENCIES = ["sx1509"] SX1509BinarySensor = sx1509_ns.class_("SX1509BinarySensor", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(SX1509BinarySensor).extend( { - cv.GenerateID(): cv.declare_id(SX1509BinarySensor), cv.GenerateID(CONF_SX1509_ID): cv.use_id(SX1509Component), cv.Required(CONF_ROW): cv.int_range(min=0, max=4), cv.Required(CONF_COL): cv.int_range(min=0, max=4), @@ -22,8 +21,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_SX1509_ID]) cg.add(var.set_row_col(config[CONF_ROW], config[CONF_COL])) diff --git a/esphome/components/teleinfo/sensor/__init__.py b/esphome/components/teleinfo/sensor/__init__.py index aa875be157..ff2d81c95e 100644 --- a/esphome/components/teleinfo/sensor/__init__.py +++ b/esphome/components/teleinfo/sensor/__init__.py @@ -1,5 +1,4 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import sensor from esphome.const import CONF_ID, ICON_FLASH, UNIT_WATT_HOURS @@ -13,13 +12,12 @@ from .. import ( TeleInfoSensor = teleinfo_ns.class_("TeleInfoSensor", sensor.Sensor, cg.Component) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0 - ) - .extend({cv.GenerateID(): cv.declare_id(TeleInfoSensor)}) - .extend(TELEINFO_LISTENER_SCHEMA) -) +CONFIG_SCHEMA = sensor.sensor_schema( + TeleInfoSensor, + unit_of_measurement=UNIT_WATT_HOURS, + icon=ICON_FLASH, + accuracy_decimals=0, +).extend(TELEINFO_LISTENER_SCHEMA) async def to_code(config): diff --git a/esphome/components/teleinfo/text_sensor/__init__.py b/esphome/components/teleinfo/text_sensor/__init__.py index 848b08d742..df8e4c21fc 100644 --- a/esphome/components/teleinfo/text_sensor/__init__.py +++ b/esphome/components/teleinfo/text_sensor/__init__.py @@ -8,7 +8,7 @@ TeleInfoTextSensor = teleinfo_ns.class_( "TeleInfoTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.text_sensor_schema(klass=TeleInfoTextSensor).extend( +CONFIG_SCHEMA = text_sensor.text_sensor_schema(TeleInfoTextSensor).extend( TELEINFO_LISTENER_SCHEMA ) diff --git a/esphome/components/template/binary_sensor/__init__.py b/esphome/components/template/binary_sensor/__init__.py index 8f551e3215..4ce89503de 100644 --- a/esphome/components/template/binary_sensor/__init__.py +++ b/esphome/components/template/binary_sensor/__init__.py @@ -9,18 +9,20 @@ TemplateBinarySensor = template_ns.class_( "TemplateBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TemplateBinarySensor), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(TemplateBinarySensor) + .extend( + { + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/template/button/__init__.py b/esphome/components/template/button/__init__.py index aa192d118e..a8bf595942 100644 --- a/esphome/components/template/button/__init__.py +++ b/esphome/components/template/button/__init__.py @@ -1,10 +1,13 @@ import esphome.config_validation as cv from esphome.components import button +from .. import template_ns + +TemplateButton = template_ns.class_("TemplateButton", button.Button) CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( { - cv.GenerateID(): cv.declare_id(button.Button), + cv.GenerateID(): cv.declare_id(TemplateButton), } ).extend(cv.COMPONENT_SCHEMA) diff --git a/esphome/components/template/button/template_button.h b/esphome/components/template/button/template_button.h new file mode 100644 index 0000000000..68e976f64b --- /dev/null +++ b/esphome/components/template/button/template_button.h @@ -0,0 +1,15 @@ +#pragma once + +#include "esphome/components/button/button.h" + +namespace esphome { +namespace template_ { + +class TemplateButton : public button::Button { + public: + // Implements the abstract `press_action` but the `on_press` trigger already handles the press. + void press_action() override{}; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/sensor/__init__.py b/esphome/components/template/sensor/__init__.py index 75fb505d91..9ed7a83848 100644 --- a/esphome/components/template/sensor/__init__.py +++ b/esphome/components/template/sensor/__init__.py @@ -6,7 +6,6 @@ from esphome.const import ( CONF_ID, CONF_LAMBDA, CONF_STATE, - STATE_CLASS_NONE, ) from .. import template_ns @@ -16,12 +15,11 @@ TemplateSensor = template_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TemplateSensor, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ) .extend( { - cv.GenerateID(): cv.declare_id(TemplateSensor), cv.Optional(CONF_LAMBDA): cv.returning_lambda, } ) @@ -30,9 +28,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) if CONF_LAMBDA in config: template_ = await cg.process_lambda( diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index de72579402..f2f382ceaa 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -137,13 +137,14 @@ _UNDEF = object() def text_sensor_schema( - klass: MockObjClass = _UNDEF, + class_: MockObjClass = _UNDEF, + *, icon: str = _UNDEF, entity_category: str = _UNDEF, ) -> cv.Schema: schema = TEXT_SENSOR_SCHEMA - if klass is not _UNDEF: - schema = schema.extend({cv.GenerateID(): cv.declare_id(klass)}) + if class_ is not _UNDEF: + schema = schema.extend({cv.GenerateID(): cv.declare_id(class_)}) if icon is not _UNDEF: schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon}) if entity_category is not _UNDEF: diff --git a/esphome/components/tmp102/sensor.py b/esphome/components/tmp102/sensor.py index c5ffbb8df5..57d0afd5a1 100644 --- a/esphome/components/tmp102/sensor.py +++ b/esphome/components/tmp102/sensor.py @@ -11,7 +11,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, UNIT_CELSIUS, @@ -27,23 +26,18 @@ TMP102Component = tmp102_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TMP102Component, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(TMP102Component), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x48)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/tmp117/sensor.py b/esphome/components/tmp117/sensor.py index 054864dd83..fb97258bc1 100644 --- a/esphome/components/tmp117/sensor.py +++ b/esphome/components/tmp117/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, CONF_UPDATE_INTERVAL, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT, @@ -19,16 +18,12 @@ TMP117Component = tmp117_ns.class_( CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + TMP117Component, unit_of_measurement=UNIT_CELSIUS, accuracy_decimals=1, device_class=DEVICE_CLASS_TEMPERATURE, state_class=STATE_CLASS_MEASUREMENT, ) - .extend( - { - cv.GenerateID(): cv.declare_id(TMP117Component), - } - ) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x48)) ) @@ -77,10 +72,9 @@ def determine_config_register(polling_period): async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) update_period = config[CONF_UPDATE_INTERVAL].total_seconds cg.add(var.set_config(determine_config_register(update_period))) diff --git a/esphome/components/tof10120/sensor.py b/esphome/components/tof10120/sensor.py index 2d3add2399..21d6d48659 100644 --- a/esphome/components/tof10120/sensor.py +++ b/esphome/components/tof10120/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -18,19 +17,18 @@ TOF10120Sensor = tof10120_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TOF10120Sensor, unit_of_measurement=UNIT_METER, icon=ICON_ARROW_EXPAND_VERTICAL, accuracy_decimals=3, state_class=STATE_CLASS_MEASUREMENT, ) - .extend({cv.GenerateID(): cv.declare_id(TOF10120Sensor)}) .extend(cv.polling_component_schema("60s")) .extend(i2c.i2c_device_schema(0x52)) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/total_daily_energy/sensor.py b/esphome/components/total_daily_energy/sensor.py index 1af8db8332..3698563aff 100644 --- a/esphome/components/total_daily_energy/sensor.py +++ b/esphome/components/total_daily_energy/sensor.py @@ -40,12 +40,12 @@ def inherit_accuracy_decimals(decimals, config): CONFIG_SCHEMA = ( sensor.sensor_schema( + TotalDailyEnergy, device_class=DEVICE_CLASS_ENERGY, state_class=STATE_CLASS_TOTAL_INCREASING, ) .extend( { - cv.GenerateID(): cv.declare_id(TotalDailyEnergy), cv.GenerateID(CONF_TIME_ID): cv.use_id(time.RealTimeClock), cv.Required(CONF_POWER_ID): cv.use_id(sensor.Sensor), cv.Optional(CONF_RESTORE, default=True): cv.boolean, @@ -82,10 +82,8 @@ FINAL_VALIDATE_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) sens = await cg.get_variable(config[CONF_POWER_ID]) cg.add(var.set_parent(sens)) diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py index 8246b95187..125103e2b8 100644 --- a/esphome/components/touchscreen/__init__.py +++ b/esphome/components/touchscreen/__init__.py @@ -6,6 +6,8 @@ from esphome import automation from esphome.const import CONF_ON_TOUCH CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["display"] + IS_PLATFORM_COMPONENT = True touchscreen_ns = cg.esphome_ns.namespace("touchscreen") diff --git a/esphome/components/touchscreen/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py index 9dba821d4d..800bc4c2a9 100644 --- a/esphome/components/touchscreen/binary_sensor/__init__.py +++ b/esphome/components/touchscreen/binary_sensor/__init__.py @@ -1,8 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import binary_sensor -from esphome.const import CONF_ID +from esphome.components import binary_sensor, display +from esphome.const import CONF_PAGE_ID from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener @@ -34,23 +34,24 @@ def validate_coords(config): CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(TouchscreenBinarySensor) + .extend( { - cv.GenerateID(): cv.declare_id(TouchscreenBinarySensor), cv.GenerateID(CONF_TOUCHSCREEN_ID): cv.use_id(Touchscreen), cv.Required(CONF_X_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_X_MAX): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000), cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000), + cv.Optional(CONF_PAGE_ID): cv.use_id(display.DisplayPage), } - ).extend(cv.COMPONENT_SCHEMA), + ) + .extend(cv.COMPONENT_SCHEMA), validate_coords, ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await cg.register_parented(var, config[CONF_TOUCHSCREEN_ID]) @@ -62,3 +63,7 @@ async def to_code(config): config[CONF_Y_MAX], ) ) + + if CONF_PAGE_ID in config: + page = await cg.get_variable(config[CONF_PAGE_ID]) + cg.add(var.set_page(page)) diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp index ba12aeeae0..583392cce3 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -6,6 +6,10 @@ namespace touchscreen { void TouchscreenBinarySensor::touch(TouchPoint tp) { bool touched = (tp.x >= this->x_min_ && tp.x <= this->x_max_ && tp.y >= this->y_min_ && tp.y <= this->y_max_); + if (this->page_ != nullptr) { + touched &= this->page_ == this->parent_->get_display()->get_active_page(); + } + if (touched) { this->publish_state(true); } else { diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h index 7b8cac5c4c..d7e53962e2 100644 --- a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -1,6 +1,7 @@ #pragma once #include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/display/display_buffer.h" #include "esphome/components/touchscreen/touchscreen.h" #include "esphome/core/component.h" #include "esphome/core/helpers.h" @@ -23,11 +24,14 @@ class TouchscreenBinarySensor : public binary_sensor::BinarySensor, this->y_max_ = y_max; } + void set_page(display::DisplayPage *page) { this->page_ = page; } + void touch(TouchPoint tp) override; void release() override; protected: int16_t x_min_, x_max_, y_min_, y_max_; + display::DisplayPage *page_{nullptr}; }; } // namespace touchscreen diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h index 2c0ec9e268..0597759894 100644 --- a/esphome/components/touchscreen/touchscreen.h +++ b/esphome/components/touchscreen/touchscreen.h @@ -37,6 +37,7 @@ class Touchscreen { this->display_height_ = display->get_height_internal(); this->rotation_ = static_cast(display->get_rotation()); } + display::DisplayBuffer *get_display() const { return this->display_; } Trigger *get_touch_trigger() { return &this->touch_trigger_; } diff --git a/esphome/components/tsl2561/sensor.py b/esphome/components/tsl2561/sensor.py index cf3837cb4d..fb2c00697b 100644 --- a/esphome/components/tsl2561/sensor.py +++ b/esphome/components/tsl2561/sensor.py @@ -3,7 +3,6 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( CONF_GAIN, - CONF_ID, CONF_INTEGRATION_TIME, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, @@ -40,6 +39,7 @@ TSL2561Sensor = tsl2561_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + TSL2561Sensor, unit_of_measurement=UNIT_LUX, accuracy_decimals=1, device_class=DEVICE_CLASS_ILLUMINANCE, @@ -47,7 +47,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(TSL2561Sensor), cv.Optional( CONF_INTEGRATION_TIME, default="402ms" ): validate_integration_time, @@ -61,10 +60,9 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) await i2c.register_i2c_device(var, config) - await sensor.register_sensor(var, config) cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME])) cg.add(var.set_gain(config[CONF_GAIN])) diff --git a/esphome/components/tsl2591/sensor.py b/esphome/components/tsl2591/sensor.py index 1ec37b5f93..63a0733365 100644 --- a/esphome/components/tsl2591/sensor.py +++ b/esphome/components/tsl2591/sensor.py @@ -35,10 +35,8 @@ from esphome.const import ( CONF_DEVICE_FACTOR, CONF_GLASS_ATTENUATION_FACTOR, ICON_BRIGHTNESS_6, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_ILLUMINANCE, STATE_CLASS_MEASUREMENT, - UNIT_EMPTY, UNIT_LUX, ) @@ -87,32 +85,26 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(TSL2591Component), cv.Optional(CONF_INFRARED): sensor.sensor_schema( - UNIT_EMPTY, - ICON_BRIGHTNESS_6, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_VISIBLE): sensor.sensor_schema( - UNIT_EMPTY, - ICON_BRIGHTNESS_6, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_FULL_SPECTRUM): sensor.sensor_schema( - UNIT_EMPTY, - ICON_BRIGHTNESS_6, - 0, - DEVICE_CLASS_EMPTY, - STATE_CLASS_MEASUREMENT, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional(CONF_CALCULATED_LUX): sensor.sensor_schema( - UNIT_LUX, - ICON_BRIGHTNESS_6, - 4, - DEVICE_CLASS_ILLUMINANCE, - STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_LUX, + icon=ICON_BRIGHTNESS_6, + accuracy_decimals=4, + device_class=DEVICE_CLASS_ILLUMINANCE, + state_class=STATE_CLASS_MEASUREMENT, ), cv.Optional( CONF_INTEGRATION_TIME, default="100ms" diff --git a/esphome/components/ttp229_bsf/binary_sensor.py b/esphome/components/ttp229_bsf/binary_sensor.py index 75540fe0e8..8a0c7fce48 100644 --- a/esphome/components/ttp229_bsf/binary_sensor.py +++ b/esphome/components/ttp229_bsf/binary_sensor.py @@ -1,15 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.const import CONF_CHANNEL from . import ttp229_bsf_ns, TTP229BSFComponent, CONF_TTP229_ID DEPENDENCIES = ["ttp229_bsf"] TTP229BSFChannel = ttp229_bsf_ns.class_("TTP229BSFChannel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(TTP229BSFChannel).extend( { - cv.GenerateID(): cv.declare_id(TTP229BSFChannel), cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229BSFComponent), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), } @@ -17,8 +16,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) cg.add(var.set_channel(config[CONF_CHANNEL])) hub = await cg.get_variable(config[CONF_TTP229_ID]) diff --git a/esphome/components/ttp229_lsf/binary_sensor.py b/esphome/components/ttp229_lsf/binary_sensor.py index b52a9e8575..5fba0096de 100644 --- a/esphome/components/ttp229_lsf/binary_sensor.py +++ b/esphome/components/ttp229_lsf/binary_sensor.py @@ -1,15 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_CHANNEL, CONF_ID +from esphome.const import CONF_CHANNEL from . import ttp229_lsf_ns, TTP229LSFComponent, CONF_TTP229_ID DEPENDENCIES = ["ttp229_lsf"] TTP229Channel = ttp229_lsf_ns.class_("TTP229Channel", binary_sensor.BinarySensor) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(TTP229Channel).extend( { - cv.GenerateID(): cv.declare_id(TTP229Channel), cv.GenerateID(CONF_TTP229_ID): cv.use_id(TTP229LSFComponent), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=15), } @@ -17,8 +16,7 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) cg.add(var.set_channel(config[CONF_CHANNEL])) hub = await cg.get_variable(config[CONF_TTP229_ID]) diff --git a/esphome/components/tuya/binary_sensor/__init__.py b/esphome/components/tuya/binary_sensor/__init__.py index cd4a2db89f..856b5eb323 100644 --- a/esphome/components/tuya/binary_sensor/__init__.py +++ b/esphome/components/tuya/binary_sensor/__init__.py @@ -1,7 +1,8 @@ from esphome.components import binary_sensor import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT +from esphome.const import CONF_SENSOR_DATAPOINT + from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ["tuya"] @@ -11,19 +12,21 @@ TuyaBinarySensor = tuya_ns.class_( "TuyaBinarySensor", binary_sensor.BinarySensor, cg.Component ) -CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TuyaBinarySensor), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + binary_sensor.binary_sensor_schema(TuyaBinarySensor) + .extend( + { + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) - await binary_sensor.register_binary_sensor(var, config) paren = await cg.get_variable(config[CONF_TUYA_ID]) cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 275a87edd3..7d4b37ad22 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -36,31 +36,25 @@ def validate_temperature_multipliers(value): or CONF_TARGET_TEMPERATURE_MULTIPLIER in value ): raise cv.Invalid( - ( - f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" - ) + f"Cannot have {CONF_TEMPERATURE_MULTIPLIER} at the same time as " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} and " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" ) if ( CONF_CURRENT_TEMPERATURE_MULTIPLIER in value and CONF_TARGET_TEMPERATURE_MULTIPLIER not in value ): raise cv.Invalid( - ( - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}" - ) + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER}" ) if ( CONF_TARGET_TEMPERATURE_MULTIPLIER in value and CONF_CURRENT_TEMPERATURE_MULTIPLIER not in value ): raise cv.Invalid( - ( - f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " - f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" - ) + f"{CONF_CURRENT_TEMPERATURE_MULTIPLIER} required if using " + f"{CONF_TARGET_TEMPERATURE_MULTIPLIER}" ) keys = ( CONF_TEMPERATURE_MULTIPLIER, @@ -76,18 +70,14 @@ def validate_active_state_values(value): if CONF_ACTIVE_STATE_DATAPOINT not in value: if CONF_ACTIVE_STATE_COOLING_VALUE in value: raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " - f"{CONF_ACTIVE_STATE_COOLING_VALUE}" - ) + f"{CONF_ACTIVE_STATE_DATAPOINT} required if using " + f"{CONF_ACTIVE_STATE_COOLING_VALUE}" ) else: if value[CONF_SUPPORTS_COOL] and CONF_ACTIVE_STATE_COOLING_VALUE not in value: raise cv.Invalid( - ( - f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " - f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" - ) + f"{CONF_ACTIVE_STATE_COOLING_VALUE} required if using " + f"{CONF_ACTIVE_STATE_DATAPOINT} and device supports cooling" ) return value diff --git a/esphome/components/tx20/sensor.py b/esphome/components/tx20/sensor.py index 84df82b5e6..f8a0b08d99 100644 --- a/esphome/components/tx20/sensor.py +++ b/esphome/components/tx20/sensor.py @@ -8,7 +8,6 @@ from esphome.const import ( CONF_PIN, CONF_WIND_DIRECTION_DEGREES, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_KILOMETER_PER_HOUR, ICON_WEATHER_WINDY, ICON_SIGN_DIRECTION, @@ -31,7 +30,6 @@ CONFIG_SCHEMA = cv.Schema( unit_of_measurement=UNIT_DEGREES, icon=ICON_SIGN_DIRECTION, accuracy_decimals=1, - state_class=STATE_CLASS_NONE, ), cv.Required(CONF_PIN): cv.All(pins.internal_gpio_input_pin_schema), } diff --git a/esphome/components/ultrasonic/sensor.py b/esphome/components/ultrasonic/sensor.py index f7026e884c..afbd1128c2 100644 --- a/esphome/components/ultrasonic/sensor.py +++ b/esphome/components/ultrasonic/sensor.py @@ -4,7 +4,6 @@ from esphome import pins from esphome.components import sensor from esphome.const import ( CONF_ECHO_PIN, - CONF_ID, CONF_TRIGGER_PIN, CONF_TIMEOUT, STATE_CLASS_MEASUREMENT, @@ -21,6 +20,7 @@ UltrasonicSensorComponent = ultrasonic_ns.class_( CONFIG_SCHEMA = ( sensor.sensor_schema( + UltrasonicSensorComponent, unit_of_measurement=UNIT_METER, icon=ICON_ARROW_EXPAND_VERTICAL, accuracy_decimals=2, @@ -28,7 +28,6 @@ CONFIG_SCHEMA = ( ) .extend( { - cv.GenerateID(): cv.declare_id(UltrasonicSensorComponent), cv.Required(CONF_TRIGGER_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_ECHO_PIN): pins.internal_gpio_input_pin_schema, cv.Optional(CONF_TIMEOUT, default="2m"): cv.distance, @@ -42,9 +41,8 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) trigger = await cg.gpio_pin_expression(config[CONF_TRIGGER_PIN]) cg.add(var.set_trigger_pin(trigger)) diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py index 16a1e4c125..103bc3a666 100644 --- a/esphome/components/uptime/sensor.py +++ b/esphome/components/uptime/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_ID, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_TOTAL_INCREASING, UNIT_SECOND, @@ -12,24 +11,16 @@ from esphome.const import ( uptime_ns = cg.esphome_ns.namespace("uptime") UptimeSensor = uptime_ns.class_("UptimeSensor", sensor.Sensor, cg.PollingComponent) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_SECOND, - icon=ICON_TIMER, - accuracy_decimals=0, - state_class=STATE_CLASS_TOTAL_INCREASING, - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - ) - .extend( - { - cv.GenerateID(): cv.declare_id(UptimeSensor), - } - ) - .extend(cv.polling_component_schema("60s")) -) +CONFIG_SCHEMA = sensor.sensor_schema( + UptimeSensor, + unit_of_measurement=UNIT_SECOND, + icon=ICON_TIMER, + accuracy_decimals=0, + state_class=STATE_CLASS_TOTAL_INCREASING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, +).extend(cv.polling_component_schema("60s")) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/vl53l0x/sensor.py b/esphome/components/vl53l0x/sensor.py index 0ce3197366..7b485e3887 100644 --- a/esphome/components/vl53l0x/sensor.py +++ b/esphome/components/vl53l0x/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import i2c, sensor from esphome.const import ( - CONF_ID, STATE_CLASS_MEASUREMENT, UNIT_METER, ICON_ARROW_EXPAND_VERTICAL, @@ -41,6 +40,7 @@ def check_timeout(value): CONFIG_SCHEMA = cv.All( sensor.sensor_schema( + VL53L0XSensor, unit_of_measurement=UNIT_METER, icon=ICON_ARROW_EXPAND_VERTICAL, accuracy_decimals=2, @@ -48,7 +48,6 @@ CONFIG_SCHEMA = cv.All( ) .extend( { - cv.GenerateID(): cv.declare_id(VL53L0XSensor), cv.Optional(CONF_SIGNAL_RATE_LIMIT, default=0.25): cv.float_range( min=0.0, max=512.0, min_included=False, max_included=False ), @@ -64,7 +63,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) cg.add(var.set_signal_rate_limit(config[CONF_SIGNAL_RATE_LIMIT])) cg.add(var.set_long_range(config[CONF_LONG_RANGE])) @@ -74,5 +73,4 @@ async def to_code(config): enable = await cg.gpio_pin_expression(config[CONF_ENABLE_PIN]) cg.add(var.set_enable_pin(enable)) - await sensor.register_sensor(var, config) await i2c.register_i2c_device(var, config) diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 44120ebbc5..fe5b51290e 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -50,6 +50,9 @@ WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) +WaveshareEPaper7P5InHDB = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InHDB", WaveshareEPaper +) WaveshareEPaper2P13InDKE = waveshare_epaper_ns.class_( "WaveshareEPaper2P13InDKE", WaveshareEPaper ) @@ -76,6 +79,7 @@ MODELS = { "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), + "7.50in-hd-b": ("b", WaveshareEPaper7P5InHDB), "2.13in-ttgo-dke": ("c", WaveshareEPaper2P13InDKE), } diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.cpp b/esphome/components/waveshare_epaper/waveshare_epaper.cpp index 71e3b22e7d..59b3e90b03 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -1241,6 +1241,107 @@ void WaveshareEPaper7P5InBC::dump_config() { LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper7P5InHDB::initialize() { + this->command(0x12); // SWRESET + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x46); // Auto Write RAM + this->data(0xF7); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x47); // Auto Write RAM + this->data(0xF7); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x0C); // Soft start setting + this->data(0xAE); + this->data(0xC7); + this->data(0xC3); + this->data(0xC0); + this->data(0x40); + + this->command(0x01); // Set MUX as 527 + this->data(0xAF); + this->data(0x02); + this->data(0x01); + + this->command(0x11); // Data entry mode + this->data(0x01); + + this->command(0x44); + this->data(0x00); // RAM x address start at 0 + this->data(0x00); + this->data(0x6F); // RAM x address end at 36Fh -> 879 + this->data(0x03); + + this->command(0x45); + this->data(0xAF); // RAM y address start at 20Fh; + this->data(0x02); + this->data(0x00); // RAM y address end at 00h; + this->data(0x00); + + this->command(0x3C); // VBD + this->data(0x01); // LUT1, for white + + this->command(0x18); + this->data(0X80); + + this->command(0x22); + this->data(0XB1); // Load Temperature and waveform setting. + + this->command(0x20); + + this->wait_until_idle_(); // waiting for the electronic paper IC to release the idle signal + + this->command(0x4E); + this->data(0x00); + this->data(0x00); + + this->command(0x4F); + this->data(0xAF); + this->data(0x02); +} + +void HOT WaveshareEPaper7P5InHDB::display() { + this->command(0x4F); + this->data(0xAf); + this->data(0x02); + + // BLACK + this->command(0x24); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + + // RED + this->command(0x26); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + + this->command(0x22); + this->data(0xC7); + this->command(0x20); + delay(100); // NOLINT +} + +int WaveshareEPaper7P5InHDB::get_width_internal() { return 880; } + +int WaveshareEPaper7P5InHDB::get_height_internal() { return 528; } + +void WaveshareEPaper7P5InHDB::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-HD-b"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} + static const uint8_t LUT_SIZE_TTGO_DKE_PART = 153; static const uint8_t PART_UPDATE_LUT_TTGO_DKE[LUT_SIZE_TTGO_DKE_PART] = { diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 96bd2fc782..41b93978ab 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -350,6 +350,26 @@ class WaveshareEPaper7P5InV2 : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper7P5InHDB : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // deep sleep + this->command(0x10); + this->data(0x01); + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper2P13InDKE : public WaveshareEPaper { public: void initialize() override; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 1e5f341717..42683c8d77 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -14,6 +14,8 @@ from esphome.const import ( CONF_PASSWORD, CONF_INCLUDE_INTERNAL, CONF_OTA, + CONF_VERSION, + CONF_LOCAL, ) from esphome.core import CORE, coroutine_with_priority @@ -22,18 +24,37 @@ AUTO_LOAD = ["json", "web_server_base"] web_server_ns = cg.esphome_ns.namespace("web_server") WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller) + +def default_url(config): + config = config.copy() + if config[CONF_VERSION] == 1: + if not (CONF_CSS_URL in config): + config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css" + if not (CONF_JS_URL in config): + config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js" + if config[CONF_VERSION] == 2: + if not (CONF_CSS_URL in config): + config[CONF_CSS_URL] = "" + if not (CONF_JS_URL in config): + config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js" + return config + + +def validate_local(config): + if CONF_LOCAL in config and config[CONF_VERSION] == 1: + raise cv.Invalid("'local' is not supported in version 1") + return config + + CONFIG_SCHEMA = cv.All( cv.Schema( { cv.GenerateID(): cv.declare_id(WebServer), cv.Optional(CONF_PORT, default=80): cv.port, - cv.Optional( - CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css" - ): cv.string, + cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2), + cv.Optional(CONF_CSS_URL): cv.string, cv.Optional(CONF_CSS_INCLUDE): cv.file_, - cv.Optional( - CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js" - ): cv.string, + cv.Optional(CONF_JS_URL): cv.string, cv.Optional(CONF_JS_INCLUDE): cv.file_, cv.Optional(CONF_AUTH): cv.Schema( { @@ -50,9 +71,12 @@ CONFIG_SCHEMA = cv.All( ), cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean, cv.Optional(CONF_OTA, default=True): cv.boolean, - }, + cv.Optional(CONF_LOCAL): cv.boolean, + } ).extend(cv.COMPONENT_SCHEMA), cv.only_with_arduino, + default_url, + validate_local, ) @@ -68,6 +92,7 @@ async def to_code(config): 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(var.set_allow_ota(config[CONF_OTA])) @@ -75,13 +100,15 @@ async def to_code(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("WEBSERVER_CSS_INCLUDE") + cg.add_define("USE_WEBSERVER_CSS_INCLUDE") path = CORE.relative_config_path(config[CONF_CSS_INCLUDE]) - with open(file=path, mode="r", encoding="utf-8") as myfile: + with open(file=path, encoding="utf-8") as myfile: cg.add(var.set_css_include(myfile.read())) if CONF_JS_INCLUDE in config: - cg.add_define("WEBSERVER_JS_INCLUDE") + cg.add_define("USE_WEBSERVER_JS_INCLUDE") path = CORE.relative_config_path(config[CONF_JS_INCLUDE]) - with open(file=path, mode="r", encoding="utf-8") as myfile: + with open(file=path, encoding="utf-8") as myfile: cg.add(var.set_js_include(myfile.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 new file mode 100644 index 0000000000..719a804d0c --- /dev/null +++ b/esphome/components/web_server/server_index.h @@ -0,0 +1,573 @@ +#pragma once +// Generated from https://github.com/esphome/esphome-webserver +#include "esphome/core/hal.h" +namespace esphome { + +namespace web_server { + +const uint8_t INDEX_GZ[] PROGMEM = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3, + 0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x10, 0xac, 0x56, 0x01, 0x16, 0x08, 0x91, 0x54, 0x6d, 0x06, 0x05, 0xf2, 0xca, 0x55, + 0xe5, 0x5b, 0x65, 0xd7, 0xe6, 0x92, 0xaa, 0xbc, 0xc8, 0x74, 0x09, 0x22, 0x93, 0x22, 0x5c, 0x20, 0x40, 0x03, 0x49, + 0x2d, 0xa6, 0xd0, 0xa7, 0x9f, 0xfa, 0x69, 0xce, 0x99, 0xf5, 0xa1, 0x5f, 0xe6, 0xf4, 0xcb, 0x7c, 0xc4, 0x7c, 0xcf, + 0xfd, 0x81, 0xe9, 0x4f, 0x98, 0x88, 0xc8, 0x05, 0x09, 0x90, 0x5a, 0xec, 0xf6, 0xdc, 0x53, 0x8b, 0x80, 0x5c, 0x23, + 0x23, 0x23, 0x63, 0x4f, 0x68, 0x6f, 0x63, 0x9c, 0x8d, 0xf8, 0xe5, 0x9c, 0x59, 0x53, 0x3e, 0x4b, 0xfa, 0x7b, 0xf2, + 0x7f, 0x16, 0x8d, 0xfb, 0x7b, 0x49, 0x9c, 0x7e, 0xb2, 0x72, 0x96, 0x84, 0xf1, 0x28, 0x4b, 0xad, 0x69, 0xce, 0x26, + 0xe1, 0x38, 0xe2, 0x51, 0x10, 0xcf, 0xa2, 0x53, 0x66, 0xed, 0xf4, 0xf7, 0x66, 0x8c, 0x47, 0xd6, 0x68, 0x1a, 0xe5, + 0x05, 0xe3, 0xe1, 0xfb, 0xc3, 0xaf, 0x5a, 0x8f, 0xfb, 0x7b, 0xc5, 0x28, 0x8f, 0xe7, 0xdc, 0xc2, 0x21, 0xc3, 0x59, + 0x36, 0x5e, 0x24, 0xac, 0x7f, 0x16, 0xe5, 0xd6, 0x3e, 0x0b, 0xdf, 0x9c, 0xfc, 0xc2, 0x46, 0xdc, 0x1f, 0xb3, 0x49, + 0x9c, 0xb2, 0xb7, 0x79, 0x36, 0x67, 0x39, 0xbf, 0xf4, 0x2e, 0xd6, 0x57, 0xc4, 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x53, + 0xc6, 0xdf, 0x9c, 0xa7, 0xaa, 0xcf, 0x53, 0x26, 0x26, 0xc9, 0xf2, 0xc2, 0xe3, 0xd7, 0xb4, 0x39, 0xb8, 0x9c, 0x9d, + 0x64, 0x49, 0xe1, 0x1d, 0xe8, 0xfa, 0x79, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0xa7, 0x51, 0x61, 0xb4, 0xf4, 0x3e, 0xad, + 0x69, 0x32, 0x97, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x62, 0xc6, 0xf2, 0xe8, 0x24, 0x61, 0x5e, 0xc1, 0x42, 0x87, 0x79, + 0xdc, 0x8b, 0xdd, 0xb0, 0xcf, 0xad, 0x38, 0xb5, 0xd8, 0x60, 0x9f, 0x51, 0xc9, 0x92, 0xe9, 0x56, 0xc1, 0x46, 0xdb, + 0x03, 0x74, 0x4d, 0xe2, 0xd3, 0x85, 0x7e, 0x3f, 0xcf, 0x63, 0xae, 0x9e, 0xcf, 0xa2, 0x64, 0xc1, 0x82, 0xb8, 0x74, + 0x03, 0x76, 0xc4, 0x87, 0x61, 0xec, 0x3d, 0xa1, 0x41, 0x61, 0xc8, 0xe5, 0x24, 0xcb, 0x1d, 0xc4, 0x55, 0x8c, 0x63, + 0xf3, 0xab, 0x2b, 0x87, 0x87, 0xcb, 0xd2, 0x75, 0x0f, 0x98, 0x3f, 0x8a, 0x92, 0xc4, 0xc1, 0x89, 0xb7, 0xb6, 0x0a, + 0x9c, 0x31, 0xf6, 0xf8, 0x51, 0x3c, 0x74, 0x7b, 0xf1, 0xc4, 0xe1, 0xcc, 0xad, 0xfa, 0x65, 0x13, 0x8b, 0x33, 0x87, + 0xbb, 0xee, 0xa7, 0xeb, 0xfb, 0xe4, 0x8c, 0x2f, 0x72, 0x80, 0xbd, 0xf4, 0xde, 0xa8, 0x99, 0x2f, 0xb0, 0xfe, 0x19, + 0x75, 0xec, 0x01, 0xec, 0x05, 0xb7, 0x3e, 0x84, 0xe7, 0x71, 0x3a, 0xce, 0xce, 0xfd, 0x83, 0x69, 0x04, 0x3f, 0xde, + 0x65, 0x19, 0xdf, 0xda, 0x72, 0xce, 0xb2, 0x78, 0x6c, 0xb5, 0xc3, 0xd0, 0xac, 0xbc, 0x7c, 0x72, 0x70, 0x70, 0x75, + 0xd5, 0x28, 0xf0, 0xd3, 0x88, 0xc7, 0x67, 0x4c, 0x74, 0x06, 0x00, 0x6c, 0xf8, 0x39, 0xe7, 0x6c, 0x7c, 0xc0, 0x2f, + 0x13, 0x28, 0x65, 0x8c, 0x17, 0x36, 0xac, 0xf1, 0x69, 0x36, 0x02, 0xb4, 0xa5, 0x06, 0xe2, 0xa1, 0x69, 0xce, 0xe6, + 0x49, 0x34, 0x62, 0x58, 0x0f, 0x23, 0x55, 0x3d, 0xaa, 0x46, 0xde, 0x57, 0xa1, 0xd8, 0x5e, 0xc7, 0xf5, 0x62, 0x16, + 0xa6, 0xec, 0xdc, 0x7a, 0x15, 0xcd, 0x7b, 0xa3, 0x24, 0x2a, 0x0a, 0xa0, 0xd7, 0x25, 0x2d, 0x21, 0x5f, 0x8c, 0x80, + 0x40, 0x68, 0x81, 0x4b, 0x44, 0xd3, 0x34, 0x2e, 0xfc, 0x8f, 0x9b, 0xa3, 0xa2, 0x78, 0xc7, 0x8a, 0x45, 0xc2, 0x37, + 0x43, 0xd8, 0x0b, 0xbe, 0x11, 0x86, 0x5f, 0xb9, 0x7c, 0x9a, 0x67, 0xe7, 0xd6, 0xb3, 0x3c, 0x87, 0xe6, 0x36, 0x4c, + 0x29, 0x1a, 0x58, 0x71, 0x61, 0xa5, 0x19, 0xb7, 0xf4, 0x60, 0xb8, 0x81, 0xbe, 0xf5, 0xbe, 0x60, 0xd6, 0xf1, 0x22, + 0x2d, 0xa2, 0x09, 0x83, 0xa6, 0xc7, 0x56, 0x96, 0x5b, 0xc7, 0x30, 0xe8, 0x31, 0x6c, 0x59, 0xc1, 0xe1, 0xd4, 0xf8, + 0xb6, 0xdb, 0xa3, 0xb9, 0xa0, 0xf0, 0x90, 0x5d, 0xf0, 0x90, 0x95, 0x40, 0x98, 0x56, 0xa1, 0x97, 0xe1, 0xb8, 0xcb, + 0x04, 0x0a, 0x58, 0x18, 0x33, 0x24, 0x59, 0xc7, 0x6c, 0xac, 0x37, 0xe7, 0xc3, 0xd6, 0x96, 0xc6, 0x35, 0xe0, 0xc4, + 0x81, 0xb6, 0x45, 0xa3, 0xad, 0x27, 0x16, 0x5e, 0x43, 0x91, 0xeb, 0x31, 0x5f, 0xa2, 0xef, 0xe0, 0x32, 0x1d, 0xd5, + 0xc7, 0x86, 0xca, 0x92, 0x67, 0x07, 0x3c, 0x8f, 0xd3, 0x53, 0x00, 0x42, 0xce, 0x64, 0x36, 0x29, 0x4b, 0xb1, 0xf9, + 0x4f, 0x58, 0xc8, 0xc2, 0x3e, 0x8e, 0x9e, 0x33, 0xc7, 0x2e, 0xa8, 0x87, 0x1d, 0x86, 0x88, 0x7a, 0x20, 0x30, 0x36, + 0x60, 0x01, 0xdb, 0xb6, 0x6d, 0xef, 0x2b, 0xd7, 0x3b, 0x47, 0x0a, 0xf2, 0x7d, 0x9f, 0xc8, 0x57, 0x74, 0x8e, 0xc3, + 0x0e, 0x02, 0xed, 0x27, 0x2c, 0x3d, 0xe5, 0xd3, 0x01, 0x3b, 0x6a, 0x0f, 0x03, 0x0e, 0x50, 0x8d, 0x17, 0x23, 0xe6, + 0x20, 0x3d, 0x7a, 0x05, 0x1e, 0x9f, 0x6d, 0x07, 0xa6, 0xc0, 0x8d, 0xd9, 0xa0, 0x35, 0xd6, 0xb6, 0xc6, 0x55, 0x24, + 0xaa, 0x00, 0x43, 0x3a, 0xb7, 0xe1, 0x84, 0x9d, 0xb0, 0xdc, 0x80, 0x43, 0x37, 0xeb, 0xd5, 0x76, 0x70, 0x01, 0x3b, + 0x04, 0xfd, 0xac, 0xc9, 0x22, 0x1d, 0xf1, 0x18, 0x18, 0x97, 0xbd, 0x0d, 0xe0, 0x8a, 0x9d, 0xd3, 0x1b, 0x67, 0xbb, + 0xa5, 0xeb, 0xc4, 0xee, 0x36, 0x3b, 0x2a, 0xb6, 0x3b, 0x43, 0x0f, 0xa1, 0xd4, 0xc8, 0x97, 0x0b, 0x8f, 0x61, 0x81, + 0x70, 0x46, 0x98, 0x3e, 0x9e, 0x1f, 0x06, 0xcc, 0x5f, 0xa5, 0xe3, 0x90, 0xfb, 0xb3, 0x68, 0x8e, 0xab, 0x61, 0x44, + 0x03, 0x51, 0x3a, 0x42, 0xe8, 0x6a, 0xfb, 0x82, 0x18, 0xf3, 0x2b, 0x12, 0x70, 0x01, 0x21, 0x70, 0x66, 0x9f, 0x45, + 0xa3, 0x29, 0x1c, 0xf1, 0x0a, 0x71, 0x63, 0x75, 0x1c, 0x46, 0x39, 0x8b, 0x38, 0x7b, 0x96, 0x30, 0x7c, 0xc3, 0x1d, + 0x80, 0x9e, 0xb6, 0xeb, 0x15, 0xea, 0xdc, 0x25, 0x31, 0x7f, 0x9d, 0xc1, 0x3c, 0x3d, 0x41, 0x24, 0x40, 0xc5, 0xc5, + 0xd6, 0x56, 0x8c, 0x24, 0xb2, 0xcf, 0x61, 0xb7, 0x4e, 0x16, 0xc0, 0x04, 0xec, 0x14, 0x5b, 0xd8, 0x80, 0x6d, 0x2f, + 0xf6, 0x39, 0x20, 0xf1, 0x49, 0x96, 0x72, 0x18, 0x0e, 0xe0, 0xd5, 0x14, 0xe4, 0x47, 0xf3, 0x39, 0x4b, 0xc7, 0x4f, + 0xa6, 0x71, 0x32, 0x06, 0x6c, 0x94, 0xb0, 0xde, 0x8c, 0x85, 0xb0, 0x4e, 0x58, 0x4c, 0x70, 0xf3, 0x8a, 0x68, 0xfb, + 0x90, 0x90, 0x79, 0x68, 0xdb, 0x3d, 0xe4, 0x40, 0x72, 0x15, 0xc8, 0x83, 0x68, 0xe3, 0xde, 0x01, 0xeb, 0x2f, 0x5c, + 0xbe, 0x1d, 0xc6, 0x7a, 0x1b, 0x25, 0x82, 0x9f, 0x20, 0xa7, 0x01, 0xfc, 0x33, 0xe0, 0x81, 0x3d, 0x64, 0x5c, 0xdf, + 0x49, 0xae, 0x93, 0x32, 0xb5, 0x42, 0x40, 0xc0, 0x08, 0x39, 0x88, 0xc4, 0xc1, 0xdb, 0x2c, 0xb9, 0x9c, 0xc4, 0x49, + 0x72, 0xb0, 0x98, 0xcf, 0xb3, 0x9c, 0x7b, 0x5f, 0x87, 0x4b, 0x9e, 0x55, 0x6b, 0xa5, 0x43, 0x5e, 0x9c, 0xc7, 0x1c, + 0x11, 0xea, 0x2e, 0x47, 0x11, 0x6c, 0xf5, 0x97, 0x59, 0x96, 0xb0, 0x28, 0x85, 0x65, 0xb0, 0x81, 0x6d, 0x07, 0xe9, + 0x22, 0x49, 0x7a, 0x27, 0x30, 0xec, 0xa7, 0x1e, 0x55, 0x0b, 0x8e, 0x1f, 0xd0, 0xf3, 0x7e, 0x9e, 0x47, 0x97, 0xd0, + 0x10, 0xdb, 0x00, 0x2d, 0xc2, 0x6e, 0x7d, 0x7d, 0xf0, 0xe6, 0xb5, 0x2f, 0x08, 0x3f, 0x9e, 0x5c, 0x02, 0xa0, 0x65, + 0xc5, 0x35, 0x27, 0x79, 0x36, 0x6b, 0x4c, 0x8d, 0x78, 0x88, 0x43, 0xd6, 0xbb, 0x06, 0x84, 0x98, 0x46, 0x86, 0x5d, + 0x62, 0x26, 0x04, 0xaf, 0x89, 0x9e, 0x65, 0x25, 0x9e, 0x81, 0x01, 0x3e, 0x04, 0xa2, 0x18, 0xa6, 0xbc, 0x19, 0x5a, + 0x9e, 0x5f, 0x2e, 0xe3, 0x90, 0xe0, 0x9c, 0xa3, 0xfc, 0x45, 0x18, 0x47, 0x11, 0xcc, 0xbe, 0x14, 0x03, 0x96, 0x0a, + 0xe2, 0xb8, 0x2c, 0xbd, 0x44, 0x13, 0x31, 0x72, 0x3c, 0x64, 0x28, 0x1c, 0x8e, 0xd1, 0xd5, 0x15, 0x83, 0x17, 0xd7, + 0xfb, 0x26, 0x5c, 0x46, 0x6a, 0x3d, 0x28, 0xa1, 0xf0, 0x7c, 0x05, 0x82, 0x4f, 0xa0, 0x24, 0x3b, 0x03, 0x39, 0x08, + 0x70, 0x7e, 0xed, 0x81, 0xfc, 0x4f, 0x10, 0x8a, 0x8d, 0x8e, 0x07, 0x12, 0xf4, 0xc9, 0x34, 0x4a, 0x4f, 0xd9, 0x38, + 0x48, 0x58, 0x29, 0x39, 0xef, 0xbe, 0x05, 0x7b, 0x0c, 0xe4, 0x54, 0x58, 0xcf, 0x0f, 0x5f, 0xbd, 0x94, 0x3b, 0x57, + 0x63, 0xc6, 0xb0, 0x49, 0x0b, 0x10, 0xab, 0xc0, 0xb6, 0x25, 0x3b, 0x7e, 0xc6, 0x15, 0xf7, 0x16, 0x25, 0x71, 0xf1, + 0x7e, 0x0e, 0x2a, 0x06, 0x7b, 0x0b, 0xc3, 0xc0, 0xf4, 0x21, 0x4c, 0x45, 0xe5, 0x30, 0x9f, 0xa8, 0x18, 0xeb, 0x22, + 0xe8, 0x2c, 0x56, 0x2a, 0x5e, 0x33, 0xc7, 0x2d, 0x81, 0x54, 0x79, 0x3c, 0xb2, 0xa2, 0xf1, 0xf8, 0x45, 0x1a, 0xf3, + 0x38, 0x4a, 0xe2, 0xdf, 0x08, 0x93, 0x4b, 0xa4, 0x31, 0xde, 0x93, 0x9b, 0x00, 0x6b, 0xa7, 0x1e, 0x89, 0xab, 0x98, + 0xec, 0x06, 0x21, 0x43, 0x70, 0xcb, 0x24, 0x3c, 0x1a, 0x4a, 0xf0, 0x12, 0x7f, 0xbe, 0x28, 0xa6, 0x88, 0x58, 0x39, + 0x30, 0x32, 0xf2, 0xec, 0xa4, 0x60, 0xf9, 0x19, 0x1b, 0x6b, 0x0a, 0x28, 0x60, 0x55, 0xd4, 0x1c, 0x94, 0x17, 0x9a, + 0xd1, 0x51, 0x32, 0x94, 0xc1, 0x50, 0x3d, 0x93, 0xcd, 0x32, 0x49, 0xcc, 0x5a, 0xc3, 0xd1, 0x5c, 0xc0, 0x11, 0x4a, + 0x85, 0xe4, 0x04, 0x45, 0xa8, 0x56, 0x38, 0x05, 0x2e, 0x04, 0x52, 0xc1, 0x3c, 0xe6, 0x4a, 0x92, 0x3d, 0x5b, 0x90, + 0x48, 0x28, 0xa0, 0x23, 0x1c, 0x64, 0x82, 0xb4, 0x70, 0xe1, 0x54, 0x01, 0x97, 0x97, 0xe0, 0x0a, 0x2e, 0xa2, 0xd4, + 0x1c, 0x24, 0x80, 0xf0, 0x1b, 0x21, 0x0b, 0x7d, 0x6c, 0x41, 0x64, 0xe0, 0xeb, 0x9d, 0x07, 0xc4, 0xca, 0x75, 0x57, + 0x0b, 0xf1, 0xae, 0x01, 0x1b, 0x27, 0x46, 0x7a, 0xf2, 0x36, 0xb8, 0x9f, 0x66, 0xfb, 0xa3, 0x11, 0x2b, 0x8a, 0x2c, + 0xdf, 0xda, 0xda, 0xa0, 0xf6, 0xd7, 0x29, 0x5a, 0x80, 0x49, 0x57, 0xf3, 0x3a, 0xbb, 0x20, 0x09, 0x6e, 0x8a, 0x15, + 0x25, 0xd3, 0x03, 0xfb, 0xe3, 0x47, 0xe0, 0xd9, 0x9e, 0x44, 0x03, 0x60, 0x7d, 0x55, 0xf1, 0x13, 0xfa, 0x4c, 0x1d, + 0x33, 0x6b, 0xf5, 0x4b, 0xa7, 0x0e, 0x92, 0x07, 0xc3, 0xba, 0xa5, 0xb1, 0xa1, 0x6b, 0x87, 0xc6, 0xdd, 0x90, 0x02, + 0x72, 0x79, 0x4a, 0x22, 0xdb, 0xd8, 0x46, 0xd0, 0xda, 0x4a, 0x8f, 0x50, 0xaf, 0x56, 0x93, 0x13, 0xa0, 0x47, 0x6c, + 0xd8, 0x93, 0xf5, 0x61, 0x21, 0x30, 0x97, 0xb3, 0x5f, 0x17, 0xac, 0xe0, 0x82, 0x74, 0x61, 0xdc, 0x1c, 0xc6, 0x2d, + 0x57, 0xb4, 0xc3, 0x9a, 0xee, 0xb8, 0x0e, 0xb6, 0x37, 0x73, 0x94, 0x63, 0x05, 0x52, 0xf2, 0xcd, 0xe4, 0x84, 0xb0, + 0x32, 0xf7, 0xea, 0xea, 0x1b, 0x35, 0x48, 0xb5, 0x95, 0x5a, 0x07, 0x6a, 0xec, 0x89, 0xad, 0x9a, 0x8c, 0x6d, 0x57, + 0x0a, 0xd4, 0x8d, 0x4e, 0xaf, 0x46, 0x07, 0x70, 0xe6, 0xda, 0x9a, 0xa4, 0x2b, 0x65, 0xfb, 0xad, 0xc2, 0xe9, 0x1b, + 0x31, 0x32, 0x69, 0xa3, 0xec, 0x76, 0xea, 0x51, 0x27, 0x1e, 0xda, 0xae, 0xd4, 0x55, 0x8c, 0x61, 0x51, 0x67, 0x0c, + 0x4d, 0xa8, 0xe7, 0xba, 0x8b, 0xad, 0x89, 0x8a, 0x85, 0x6a, 0xaf, 0x95, 0x01, 0xc1, 0xc3, 0x23, 0x50, 0x4e, 0xd6, + 0xda, 0x07, 0xaf, 0xa3, 0x19, 0x43, 0x8c, 0x7a, 0xd7, 0x35, 0x90, 0x06, 0x04, 0x34, 0x19, 0x36, 0xc5, 0x1b, 0x77, + 0x85, 0xd6, 0x54, 0x3f, 0x5f, 0x31, 0x68, 0x11, 0xa0, 0x5f, 0x97, 0x6b, 0xb6, 0x88, 0xe4, 0xa6, 0x24, 0x67, 0x85, + 0x1f, 0x51, 0x26, 0xf6, 0x84, 0x04, 0x3c, 0x2c, 0x1e, 0xb6, 0xbf, 0xb1, 0x71, 0xb2, 0x15, 0x53, 0x6b, 0xe4, 0xc8, + 0x53, 0x00, 0xcf, 0x24, 0x04, 0x80, 0x5d, 0xd2, 0xcf, 0xda, 0xc1, 0x42, 0xb4, 0x1d, 0x20, 0x1d, 0xf8, 0x93, 0x24, + 0xe2, 0x4e, 0x67, 0xa7, 0xed, 0x02, 0x1d, 0x02, 0x13, 0x07, 0x19, 0x01, 0xea, 0x7d, 0xb5, 0x14, 0x86, 0x4b, 0x89, + 0x5d, 0xee, 0x83, 0x52, 0x34, 0x8d, 0x27, 0xdc, 0xc9, 0x50, 0x88, 0xb8, 0x25, 0x4b, 0x40, 0xc8, 0xe8, 0x73, 0x05, + 0x5c, 0x82, 0x0b, 0xee, 0x22, 0xaa, 0x35, 0x43, 0x53, 0x90, 0x12, 0x97, 0x22, 0x29, 0xa8, 0x20, 0x30, 0x98, 0x4a, + 0x4f, 0x51, 0x14, 0xc8, 0xb7, 0x78, 0x20, 0x06, 0x0d, 0x56, 0x34, 0xca, 0x78, 0x10, 0xaf, 0x16, 0x82, 0x18, 0xf6, + 0x79, 0xf6, 0x32, 0x3b, 0x67, 0xf9, 0x93, 0x08, 0x61, 0x0f, 0x44, 0xf7, 0x12, 0x38, 0x3d, 0x31, 0x74, 0xd6, 0x53, + 0xb4, 0x72, 0x46, 0x8b, 0x86, 0x8d, 0x98, 0xc5, 0x28, 0x08, 0x41, 0xca, 0x11, 0xee, 0x53, 0x3c, 0x52, 0x74, 0xf6, + 0x50, 0x94, 0x30, 0x4d, 0x5b, 0xfb, 0x2f, 0xeb, 0xb4, 0x05, 0x23, 0xcc, 0x15, 0xb5, 0xd6, 0x4f, 0xac, 0xeb, 0x49, + 0xd9, 0xec, 0x48, 0xda, 0x32, 0x84, 0x19, 0xc8, 0x8f, 0xab, 0xab, 0x4a, 0x49, 0x07, 0x61, 0xaa, 0xb9, 0x39, 0x6a, + 0x4e, 0xe2, 0x48, 0xb8, 0x25, 0x08, 0x23, 0x54, 0xbc, 0xf2, 0x2c, 0x49, 0x0c, 0x59, 0xe4, 0xc5, 0x3d, 0xa7, 0x21, + 0x8e, 0x00, 0x8a, 0x59, 0x4d, 0x22, 0x0d, 0x78, 0xa0, 0x2b, 0x50, 0x28, 0x29, 0x69, 0xe4, 0x55, 0x4d, 0x04, 0xc4, + 0xe9, 0x98, 0xe5, 0xc2, 0x40, 0x93, 0x32, 0x14, 0x26, 0x4c, 0x81, 0xa0, 0xd9, 0x18, 0x38, 0xbc, 0x5a, 0x00, 0xa8, + 0x27, 0xfe, 0x34, 0x2b, 0xb8, 0xae, 0x33, 0xa1, 0x8f, 0xaf, 0xae, 0x62, 0x61, 0x2f, 0x22, 0x01, 0xe4, 0x6c, 0x96, + 0x9d, 0xb1, 0x35, 0x50, 0xf7, 0xd4, 0x60, 0x26, 0xc8, 0xc6, 0x30, 0x20, 0x44, 0x41, 0xb4, 0xcc, 0x93, 0x78, 0xc4, + 0xb4, 0x94, 0x9a, 0xf9, 0xa0, 0xd0, 0xb1, 0x0b, 0xe0, 0x11, 0xcc, 0xed, 0xf7, 0xfb, 0x6d, 0xaf, 0xe3, 0x96, 0x02, + 0xe1, 0xcb, 0x15, 0x8c, 0xde, 0x20, 0x1f, 0xa5, 0x0a, 0xbe, 0x8e, 0x17, 0x70, 0xd7, 0x10, 0x8a, 0x5c, 0xd8, 0x49, + 0x9e, 0x64, 0xc4, 0xae, 0x37, 0x86, 0x41, 0x39, 0x53, 0x8c, 0x1b, 0x55, 0x5c, 0x71, 0x6c, 0xdf, 0x69, 0xb4, 0x69, + 0x72, 0x52, 0x27, 0x4c, 0x6d, 0x8c, 0xdc, 0xf3, 0x42, 0x5b, 0xc0, 0xe6, 0xf6, 0xa0, 0x96, 0x48, 0xd5, 0x40, 0xeb, + 0x00, 0xa1, 0xb0, 0x74, 0x9d, 0x95, 0x25, 0x55, 0x9d, 0x25, 0x13, 0xd7, 0x07, 0xe8, 0x0d, 0x93, 0x60, 0xae, 0x43, + 0xc1, 0x81, 0x64, 0x08, 0x1c, 0x2d, 0x32, 0xb1, 0x5f, 0x4f, 0x60, 0x7b, 0x4e, 0xa2, 0xd1, 0x27, 0x0d, 0x6e, 0x85, + 0xf6, 0x26, 0x19, 0x38, 0x8d, 0x92, 0xd0, 0x60, 0x57, 0xe6, 0xba, 0x15, 0x87, 0xae, 0x1d, 0x14, 0x30, 0xc8, 0x56, + 0xc8, 0xbe, 0xb9, 0xd1, 0x4d, 0x6a, 0x97, 0xe4, 0xa1, 0xec, 0x27, 0x4d, 0x25, 0x37, 0x90, 0x1c, 0x57, 0xdc, 0x80, + 0x2b, 0xc2, 0x83, 0xad, 0x69, 0x40, 0x02, 0x74, 0x57, 0x8e, 0xe3, 0xe2, 0x7a, 0x14, 0xfc, 0xa9, 0x60, 0x3e, 0x35, + 0x66, 0xba, 0x15, 0x52, 0xcd, 0xe1, 0xa4, 0x1a, 0xac, 0x41, 0x93, 0xca, 0x83, 0x62, 0x35, 0xdf, 0xa0, 0xa2, 0x42, + 0x14, 0x7f, 0x2a, 0xaa, 0x50, 0x05, 0x43, 0x30, 0x0a, 0x2f, 0x97, 0x04, 0x97, 0xad, 0xb2, 0x16, 0xc9, 0x53, 0x63, + 0x12, 0xa9, 0x9a, 0xe4, 0x32, 0x50, 0xb0, 0xe8, 0xb4, 0xfa, 0x52, 0x13, 0x57, 0x2c, 0x37, 0x0d, 0x35, 0x33, 0xc9, + 0x95, 0x35, 0xe1, 0x14, 0x68, 0x77, 0x29, 0xed, 0xdd, 0x5c, 0x4f, 0xa1, 0xd6, 0x53, 0xf8, 0x86, 0x0d, 0x65, 0xd2, + 0x76, 0x3e, 0x00, 0x75, 0xbf, 0x56, 0x89, 0xfa, 0xa9, 0x8f, 0x8c, 0xd9, 0xd5, 0x4c, 0x17, 0x18, 0x8a, 0x24, 0x93, + 0x74, 0x20, 0xe9, 0x0d, 0xd9, 0x46, 0x65, 0x19, 0x65, 0xae, 0x38, 0x20, 0x35, 0xab, 0x34, 0xf3, 0x52, 0xb7, 0xa1, + 0xbf, 0x97, 0xa5, 0xc4, 0x13, 0x17, 0x98, 0x89, 0xbd, 0x9b, 0x70, 0xe3, 0xa5, 0x61, 0x26, 0xb4, 0x5f, 0xa1, 0xec, + 0xd4, 0x30, 0x94, 0x4a, 0x16, 0x88, 0x63, 0xe3, 0x6b, 0xa5, 0x19, 0x64, 0xfe, 0x1a, 0x7d, 0x0a, 0x40, 0x49, 0x60, + 0xf3, 0x35, 0x96, 0xbc, 0x28, 0xac, 0xe3, 0x71, 0x83, 0xf0, 0x58, 0xb1, 0xd0, 0x1a, 0xcb, 0xd7, 0xf2, 0x2c, 0xf6, + 0x6b, 0x26, 0xa1, 0x89, 0xc9, 0x62, 0x50, 0x04, 0xb6, 0x72, 0x44, 0x54, 0xb2, 0x2d, 0x19, 0x24, 0x64, 0x90, 0xae, + 0x22, 0xbd, 0x36, 0x92, 0x81, 0xeb, 0x54, 0x70, 0xb4, 0x74, 0x18, 0x46, 0x0e, 0x1a, 0xee, 0xb4, 0x17, 0x2b, 0x88, + 0x6c, 0xea, 0x9b, 0x44, 0x8a, 0x68, 0x9c, 0x16, 0xa8, 0xc2, 0x99, 0x32, 0xdd, 0x71, 0x60, 0x39, 0xc0, 0xf6, 0x57, + 0x48, 0x6f, 0xad, 0xda, 0xe9, 0xfa, 0x95, 0xc1, 0x77, 0x75, 0x95, 0x20, 0x3d, 0x08, 0x85, 0x17, 0xf6, 0x6c, 0xa0, + 0x78, 0xef, 0xfe, 0x4b, 0x6c, 0x45, 0xfa, 0x67, 0x55, 0x52, 0x59, 0x0a, 0x35, 0xca, 0xad, 0xef, 0x13, 0x33, 0x5d, + 0x8b, 0xaa, 0xe2, 0xc0, 0xe0, 0xea, 0x07, 0x4a, 0x60, 0x57, 0x4b, 0x3e, 0x90, 0x43, 0xc7, 0xae, 0xeb, 0x06, 0x05, + 0x19, 0x2f, 0x1b, 0xeb, 0x4c, 0xc8, 0xad, 0x2d, 0xd3, 0x66, 0x3a, 0xd3, 0xc3, 0x3f, 0x71, 0x50, 0x38, 0x17, 0x97, + 0x29, 0x69, 0x30, 0x4f, 0x94, 0x38, 0x5a, 0x31, 0x40, 0xdb, 0x3d, 0xb4, 0xb4, 0xa3, 0xf3, 0x28, 0xe6, 0x96, 0x1e, + 0x45, 0x58, 0xda, 0xc8, 0x9f, 0xa4, 0xd2, 0x01, 0xeb, 0x42, 0x15, 0x92, 0x8c, 0x70, 0x53, 0x17, 0x2d, 0x46, 0x53, + 0x86, 0x2e, 0x70, 0xa5, 0x4f, 0x98, 0xbc, 0x67, 0x03, 0xd7, 0x2d, 0x06, 0x66, 0xeb, 0x61, 0x2f, 0x9b, 0xdd, 0x6b, + 0xea, 0x3f, 0xec, 0x11, 0xf0, 0xb6, 0x99, 0xaa, 0x2b, 0x1b, 0xef, 0x92, 0x45, 0xa2, 0x87, 0x6d, 0xdd, 0xd8, 0x52, + 0xd7, 0xef, 0x35, 0xcc, 0xeb, 0xca, 0x30, 0xaf, 0x09, 0xd5, 0x86, 0x1c, 0x56, 0x66, 0x0e, 0x33, 0x0d, 0x79, 0xb1, + 0x83, 0x6e, 0x4f, 0x38, 0x85, 0xc0, 0x88, 0xd0, 0xfa, 0xa0, 0xa2, 0x06, 0x42, 0x25, 0x57, 0x52, 0x35, 0x5b, 0x24, + 0x63, 0x09, 0x2c, 0x98, 0xb0, 0x5c, 0xd2, 0xd1, 0x79, 0x9c, 0x24, 0x55, 0xe9, 0x9f, 0xca, 0xe0, 0xc5, 0xb0, 0xb7, + 0xb1, 0x76, 0xb1, 0xa2, 0x85, 0x02, 0xc1, 0xd5, 0x4a, 0xd8, 0x7b, 0xc7, 0xad, 0xf6, 0x5d, 0x78, 0x1c, 0xb9, 0xe9, + 0x8d, 0x80, 0x7a, 0xf4, 0xb0, 0x6a, 0xd2, 0xde, 0x7f, 0x86, 0x2e, 0x35, 0x63, 0x3d, 0x28, 0xce, 0xa8, 0xf8, 0x77, + 0xe9, 0x53, 0xbf, 0x73, 0x79, 0xb7, 0x8a, 0xae, 0xa6, 0x43, 0x45, 0x39, 0x3e, 0x4c, 0x17, 0x4b, 0x5b, 0x39, 0x02, + 0x72, 0x3d, 0x2c, 0x72, 0x01, 0x13, 0x35, 0x58, 0x50, 0x8a, 0x55, 0x6b, 0x61, 0xf7, 0xf2, 0x36, 0x67, 0x0e, 0xb9, + 0xc2, 0x45, 0xff, 0x27, 0xd9, 0x6c, 0x8e, 0x9a, 0x59, 0x83, 0xa8, 0xa1, 0xc1, 0xfb, 0x46, 0x7d, 0xb9, 0xa6, 0xac, + 0xd6, 0x87, 0x4e, 0x64, 0x8d, 0x9e, 0xb4, 0xa1, 0x0c, 0x06, 0xd5, 0x42, 0x17, 0xd5, 0xf5, 0xe6, 0x26, 0x8b, 0x59, + 0x47, 0xe3, 0x3e, 0xc9, 0x6d, 0xad, 0x4d, 0x7a, 0x1a, 0x07, 0xc4, 0x93, 0x24, 0xc1, 0x9b, 0x04, 0x50, 0x56, 0xc8, + 0x59, 0x96, 0x0d, 0xf4, 0x2d, 0xcb, 0x12, 0xf7, 0xef, 0xdb, 0xde, 0x7e, 0xcd, 0xb2, 0xf6, 0xf6, 0xaf, 0x37, 0x91, + 0xab, 0x3a, 0x69, 0x41, 0x1e, 0x0d, 0xa1, 0x68, 0x45, 0xa7, 0x0c, 0x97, 0xb3, 0x6c, 0xcc, 0x02, 0x1b, 0xba, 0xa7, + 0x76, 0xa9, 0xa4, 0x32, 0x1c, 0x8e, 0x94, 0x39, 0xcb, 0x77, 0x75, 0x4f, 0x6a, 0xb0, 0x0f, 0x24, 0xa0, 0xd5, 0x85, + 0xef, 0xc2, 0xd3, 0x24, 0x3b, 0x89, 0x92, 0x43, 0x21, 0xc0, 0x6b, 0x2d, 0x3f, 0x80, 0xc9, 0x48, 0x1a, 0xab, 0x21, + 0xa4, 0xbe, 0x1b, 0x7c, 0x17, 0xdc, 0xde, 0xa3, 0xb2, 0x56, 0xec, 0x8e, 0xdf, 0xf6, 0x3b, 0xb6, 0xf2, 0x88, 0xbd, + 0x34, 0xa7, 0x03, 0x89, 0x53, 0x00, 0x66, 0x0e, 0x41, 0x92, 0x15, 0x5e, 0xc4, 0xc2, 0x97, 0x83, 0x97, 0xca, 0xa4, + 0xce, 0xc0, 0x84, 0x00, 0x23, 0x3f, 0x89, 0x79, 0x0b, 0xe3, 0x91, 0xb6, 0xb7, 0x14, 0x15, 0xe8, 0x57, 0x24, 0xbf, + 0x74, 0xa9, 0xac, 0x41, 0xef, 0x63, 0x78, 0x0c, 0xcd, 0x36, 0x37, 0x97, 0xce, 0xab, 0x88, 0x4f, 0xfd, 0x3c, 0x4a, + 0xc7, 0xd9, 0xcc, 0x71, 0xb7, 0x6d, 0xdb, 0xf5, 0x0b, 0xb2, 0x44, 0xbe, 0x70, 0xcb, 0xcd, 0x63, 0x6f, 0xca, 0x42, + 0x7b, 0x60, 0x6f, 0x7f, 0xf4, 0xde, 0xb2, 0xf0, 0x78, 0x6f, 0x73, 0x39, 0x65, 0x65, 0xff, 0xd8, 0xbb, 0xd0, 0x3e, + 0x77, 0xef, 0x2d, 0x72, 0x19, 0xe8, 0x15, 0xf6, 0x2f, 0x24, 0x18, 0x40, 0x6e, 0xe4, 0x7f, 0x07, 0x2e, 0xf7, 0x9e, + 0x02, 0x22, 0xd2, 0x4f, 0x7b, 0x75, 0x65, 0x67, 0xe4, 0x31, 0xb0, 0x37, 0xb4, 0xb1, 0xba, 0xb5, 0x55, 0x89, 0xf9, + 0xaa, 0xd4, 0x1b, 0xb1, 0xb0, 0x66, 0xa9, 0x7b, 0xef, 0x29, 0xb4, 0x52, 0x3f, 0xc8, 0x23, 0x46, 0x42, 0x73, 0x55, + 0x4f, 0x70, 0x8c, 0x23, 0xbe, 0xfe, 0x58, 0x1f, 0x09, 0x2f, 0x85, 0x1f, 0x83, 0xf6, 0x12, 0x81, 0xf8, 0x06, 0x03, + 0xc7, 0x3b, 0x0c, 0x77, 0xf6, 0x9c, 0x41, 0xe0, 0x6c, 0xb4, 0x5a, 0x57, 0x3f, 0xed, 0x1c, 0xfd, 0x1c, 0xb5, 0x7e, + 0xdb, 0x6f, 0xfd, 0x38, 0x74, 0xaf, 0x9c, 0x9f, 0x76, 0x06, 0x47, 0xf2, 0xed, 0xe8, 0xe7, 0xfe, 0x4f, 0xc5, 0xf0, + 0x73, 0x51, 0xb8, 0xe9, 0xba, 0x3b, 0xa7, 0x60, 0x29, 0x85, 0x3b, 0xad, 0x56, 0x1f, 0x9e, 0x16, 0xf0, 0x84, 0x3f, + 0x2f, 0xe1, 0xc7, 0xd5, 0x91, 0xf5, 0x1f, 0x7e, 0x4a, 0xff, 0xe3, 0x4f, 0xf9, 0x10, 0xc7, 0x3c, 0xfa, 0xf9, 0xa7, + 0xc2, 0xbe, 0xd7, 0x0f, 0x77, 0x86, 0xdb, 0xae, 0xa3, 0x6b, 0x3e, 0x0f, 0xab, 0x47, 0x68, 0x75, 0xf4, 0xb3, 0x7c, + 0xb3, 0xef, 0x1d, 0xef, 0xf5, 0xc3, 0xe1, 0x95, 0x63, 0x5f, 0xdd, 0x73, 0xaf, 0x5c, 0xf7, 0x6a, 0x13, 0xe7, 0x99, + 0xc3, 0xe8, 0xf7, 0xe0, 0xe7, 0x19, 0xfc, 0xb4, 0xe1, 0xe7, 0x29, 0xfc, 0xfc, 0x19, 0xba, 0x09, 0xff, 0xdb, 0x15, + 0xf9, 0x42, 0xae, 0x30, 0x60, 0x11, 0xc1, 0x2e, 0xb8, 0x9b, 0x3b, 0xb1, 0x37, 0x21, 0xa4, 0xc1, 0x39, 0xf4, 0x7d, + 0x1f, 0xdd, 0xa4, 0xce, 0xf2, 0xe3, 0x26, 0x6c, 0x3a, 0x52, 0xce, 0x66, 0xc0, 0x3c, 0xe1, 0x39, 0x28, 0x02, 0x2e, + 0x62, 0xab, 0x05, 0x06, 0x57, 0xbd, 0x45, 0x38, 0x61, 0x0e, 0x28, 0x05, 0x87, 0x0c, 0x1f, 0xba, 0xae, 0xf7, 0x4c, + 0xc6, 0x0c, 0xf1, 0x9c, 0x0b, 0xd2, 0x4a, 0x33, 0xa1, 0xd2, 0xd8, 0xae, 0x37, 0x5f, 0x53, 0x09, 0xc7, 0x3a, 0x3d, + 0x85, 0xba, 0x4d, 0x11, 0x68, 0xfb, 0x8e, 0x45, 0x9f, 0xf0, 0x48, 0x3e, 0x37, 0x82, 0xc0, 0x2b, 0x9a, 0x7c, 0x53, + 0x69, 0x34, 0x74, 0x44, 0x61, 0x8e, 0x7d, 0xc9, 0x60, 0x86, 0x15, 0x15, 0x91, 0x93, 0xd0, 0x14, 0x9a, 0x2d, 0x4c, + 0xfe, 0x36, 0xca, 0xf9, 0x66, 0xa5, 0xd8, 0x86, 0x35, 0x4d, 0xb6, 0xa9, 0xe9, 0xdf, 0x61, 0x0a, 0x54, 0x2d, 0x29, + 0xfe, 0x61, 0x8e, 0x1f, 0xa6, 0xb4, 0xac, 0xd7, 0x0e, 0x07, 0x0b, 0xbd, 0x00, 0xbe, 0x23, 0xfa, 0x39, 0x6f, 0x51, + 0x8c, 0xc1, 0x5f, 0xe9, 0x66, 0xf0, 0xc4, 0x7c, 0xe8, 0xa2, 0x59, 0x96, 0xda, 0xb9, 0x95, 0x22, 0xbb, 0x7f, 0x81, + 0x27, 0x23, 0x2d, 0xbd, 0x83, 0x50, 0x9d, 0x98, 0xc3, 0x9c, 0xb1, 0xef, 0xa2, 0xe4, 0x13, 0xcb, 0x9d, 0x0b, 0xaf, + 0xd3, 0xfd, 0x82, 0x3a, 0x7b, 0xa8, 0x9b, 0xbd, 0xae, 0xc2, 0x68, 0x4a, 0x2d, 0x50, 0x21, 0xc2, 0x56, 0xc7, 0x43, + 0x8e, 0x41, 0x28, 0xc8, 0xbd, 0x2c, 0xec, 0x12, 0x85, 0xdb, 0x7b, 0xc5, 0xd9, 0x69, 0xdf, 0x0e, 0x6c, 0x1b, 0x34, + 0xfe, 0x43, 0x72, 0x5b, 0x09, 0xc5, 0x02, 0x14, 0xb2, 0xbd, 0xb8, 0xc7, 0xb7, 0xb7, 0x2b, 0x87, 0x13, 0x06, 0xd2, + 0xa9, 0x7b, 0xe2, 0x45, 0xde, 0x34, 0x84, 0x01, 0x47, 0xd0, 0x0c, 0xbb, 0xf4, 0x46, 0x7b, 0xb1, 0x9c, 0x06, 0x7d, + 0x21, 0x7e, 0x12, 0x15, 0xfc, 0x05, 0xfa, 0x23, 0xc2, 0x11, 0x2a, 0xfb, 0x3e, 0xbb, 0x60, 0x23, 0xa5, 0x67, 0x00, + 0xa2, 0x22, 0xb7, 0xe7, 0x8e, 0x42, 0xa3, 0x19, 0xcc, 0x1d, 0x86, 0x87, 0x03, 0x1b, 0xce, 0x12, 0x9c, 0xca, 0x30, + 0x3a, 0xea, 0x0c, 0x07, 0x69, 0x08, 0xbc, 0x56, 0xe3, 0x56, 0x16, 0x2d, 0x6a, 0x45, 0xdd, 0xe1, 0xc0, 0x39, 0x05, + 0x25, 0x1d, 0x74, 0x71, 0x07, 0xdf, 0xd0, 0x43, 0x91, 0x87, 0xef, 0xd8, 0xe9, 0xb3, 0x8b, 0xb9, 0x63, 0xef, 0xed, + 0xd8, 0xdb, 0x58, 0xea, 0xd9, 0x40, 0x5e, 0x30, 0x77, 0x78, 0xe9, 0x9a, 0x9d, 0x77, 0x87, 0x08, 0x2a, 0x16, 0xe2, + 0xe4, 0x97, 0x03, 0xbb, 0x2f, 0xa6, 0x6e, 0xc3, 0xa0, 0xa9, 0xdc, 0x7e, 0xdc, 0xd1, 0x43, 0x5a, 0xaa, 0xea, 0xaa, + 0xa0, 0x83, 0xb2, 0x6e, 0xe0, 0x4c, 0xcd, 0x45, 0xb4, 0x70, 0x32, 0x89, 0x05, 0x30, 0x78, 0xb0, 0x19, 0x4c, 0x6a, + 0x74, 0xdb, 0x1d, 0x0e, 0x2e, 0x83, 0x7b, 0xf6, 0x3d, 0xf5, 0x72, 0xc6, 0x02, 0xb0, 0x2e, 0x68, 0xfa, 0x33, 0x94, + 0x22, 0xf0, 0x73, 0xce, 0x60, 0x91, 0x97, 0x54, 0x34, 0x96, 0x45, 0x0b, 0x2c, 0x3a, 0x0c, 0x10, 0x54, 0x2f, 0xd7, + 0xda, 0x9f, 0xd8, 0x93, 0x71, 0x48, 0xb0, 0x6f, 0x6d, 0xc1, 0xd6, 0x6c, 0x77, 0x86, 0x18, 0x6f, 0xc8, 0x79, 0xf1, + 0x5d, 0xcc, 0x41, 0x24, 0xec, 0xf4, 0x6d, 0x77, 0x60, 0x5b, 0xb8, 0xb5, 0xbd, 0x6c, 0x3b, 0x14, 0x18, 0x8e, 0xb7, + 0xdf, 0xb2, 0x60, 0xda, 0x0f, 0xdb, 0x03, 0xa7, 0x10, 0xa2, 0x23, 0xc1, 0xb8, 0xa5, 0xe0, 0xe0, 0x6d, 0x6f, 0x0a, + 0x0c, 0x1d, 0x29, 0x77, 0xd3, 0xde, 0x56, 0x85, 0x50, 0xf4, 0x71, 0x7b, 0xec, 0x06, 0x31, 0xfc, 0x70, 0x5a, 0x48, + 0x34, 0x53, 0xdd, 0x57, 0x4b, 0x66, 0x37, 0x18, 0x2b, 0x8d, 0x3c, 0x09, 0xb3, 0x6d, 0x07, 0x3d, 0xb4, 0xc0, 0x69, + 0xf7, 0x06, 0x00, 0xc3, 0xb6, 0xa3, 0x28, 0x6d, 0x47, 0x91, 0x9a, 0xd2, 0xcf, 0x8f, 0xaa, 0xed, 0x60, 0x83, 0x88, + 0xf9, 0x95, 0xf4, 0x01, 0xb0, 0x82, 0xc4, 0x2b, 0x86, 0x2a, 0xe6, 0xf5, 0xbc, 0x16, 0xdf, 0x5a, 0x2a, 0x56, 0xc4, + 0x3c, 0x83, 0x43, 0xf1, 0x52, 0x9b, 0x61, 0x42, 0xdd, 0x9e, 0x23, 0x32, 0x34, 0xc9, 0x87, 0x6d, 0x20, 0x7a, 0xe5, + 0x60, 0x4f, 0xcd, 0x63, 0x91, 0x84, 0x55, 0x73, 0xef, 0x08, 0x48, 0x7b, 0x18, 0xbe, 0x16, 0x11, 0xc7, 0x9e, 0xf2, + 0xe6, 0xb3, 0x24, 0x7c, 0xde, 0x08, 0x17, 0x47, 0x18, 0x11, 0x3a, 0xf0, 0x47, 0x8b, 0x1c, 0xf8, 0x01, 0x7f, 0x0d, + 0x9a, 0x41, 0x28, 0x9b, 0xa2, 0xa1, 0x87, 0x21, 0x60, 0x8f, 0x16, 0xde, 0x70, 0x9b, 0x1b, 0xd5, 0xa8, 0x51, 0x92, + 0xf2, 0x42, 0x81, 0xe1, 0x1e, 0x97, 0xa6, 0x3d, 0x32, 0x06, 0x19, 0x31, 0x76, 0x30, 0xe6, 0xef, 0x8f, 0xb0, 0x1a, + 0x27, 0x28, 0xdc, 0x92, 0x4e, 0x5b, 0xc5, 0xfe, 0x0e, 0xfc, 0x14, 0x38, 0x38, 0xd6, 0x81, 0x9d, 0xb5, 0xb5, 0x95, + 0xc8, 0x45, 0xed, 0xa5, 0x3d, 0x8a, 0x44, 0xa0, 0x3f, 0xb8, 0xf0, 0x53, 0xa8, 0x46, 0x14, 0x51, 0x11, 0x69, 0xa0, + 0x66, 0x54, 0xad, 0x82, 0xef, 0xc8, 0xf4, 0xc0, 0x73, 0x74, 0x5b, 0x93, 0xa2, 0xa8, 0x1b, 0x0b, 0x5f, 0xbe, 0xeb, + 0x52, 0x68, 0x0b, 0x03, 0x90, 0x82, 0xd0, 0x04, 0xc1, 0xb8, 0xe4, 0x94, 0xac, 0xe8, 0xef, 0xa3, 0xe1, 0x2b, 0x9f, + 0x1e, 0x65, 0xdb, 0xdb, 0x43, 0x11, 0xb7, 0x20, 0xc2, 0xe1, 0x86, 0x77, 0x35, 0xae, 0x00, 0xa8, 0x4f, 0xe7, 0xc4, + 0x75, 0xc7, 0xb4, 0x22, 0x4d, 0x97, 0x7c, 0x9f, 0x1c, 0x66, 0x00, 0x0c, 0xee, 0x38, 0x47, 0xfe, 0xe0, 0x2f, 0x43, + 0x30, 0x8f, 0xfd, 0xcf, 0xdd, 0x1d, 0xc5, 0x68, 0x7a, 0x32, 0xa6, 0xb8, 0xa4, 0x18, 0x6b, 0xc7, 0x23, 0xdf, 0x68, + 0x90, 0x7b, 0x29, 0xac, 0x00, 0xa4, 0x39, 0xf0, 0x84, 0x8a, 0x82, 0x90, 0xa2, 0x02, 0xdb, 0xc7, 0xc3, 0xcf, 0xf1, + 0x64, 0xbf, 0x03, 0x0d, 0x6f, 0xa0, 0xdf, 0x9e, 0xc2, 0xdb, 0x5f, 0xf4, 0xdb, 0x97, 0x2c, 0xf8, 0xa5, 0x94, 0xae, + 0xfb, 0xda, 0x14, 0x0f, 0xd5, 0x14, 0xa5, 0xd8, 0x22, 0x03, 0x87, 0xcc, 0x5d, 0xf5, 0xd9, 0x70, 0xb7, 0x04, 0x64, + 0x28, 0xd6, 0x05, 0x3a, 0x5a, 0x74, 0x8a, 0xc8, 0x75, 0x4d, 0x54, 0x18, 0xb9, 0x04, 0xe6, 0x82, 0x2b, 0xba, 0x25, + 0xe2, 0xec, 0xb7, 0xdd, 0x65, 0xad, 0x2d, 0xe9, 0x77, 0x6c, 0x36, 0xe7, 0x97, 0x07, 0x24, 0xe8, 0x03, 0x99, 0x36, + 0x20, 0x62, 0xe7, 0xed, 0x5e, 0xbc, 0xc7, 0x7b, 0x31, 0x70, 0xf5, 0x42, 0x91, 0x18, 0x9e, 0x55, 0xef, 0x2d, 0x7a, + 0x29, 0x4d, 0x62, 0xf2, 0x6a, 0xcb, 0xeb, 0xca, 0xe5, 0x6d, 0x6f, 0xc3, 0x02, 0x7b, 0x46, 0x57, 0x2e, 0xba, 0x96, + 0xa5, 0xc0, 0x09, 0x40, 0xf4, 0xb8, 0x4e, 0x72, 0x44, 0x71, 0x98, 0xcd, 0x86, 0x8c, 0x83, 0xb9, 0x6b, 0x47, 0xc5, + 0x31, 0xb1, 0xbb, 0x4c, 0xd8, 0x81, 0x95, 0x11, 0x95, 0xb7, 0x3a, 0xc2, 0x3b, 0x2c, 0xfa, 0x6b, 0xff, 0xf6, 0x47, + 0x8f, 0x6d, 0x77, 0x5c, 0x90, 0x20, 0xb5, 0xb1, 0x1e, 0x55, 0x63, 0x41, 0x7d, 0xf8, 0x51, 0x63, 0xa9, 0xcc, 0xb7, + 0xb7, 0xcb, 0x7a, 0xa8, 0x56, 0x9d, 0xe0, 0x5a, 0x34, 0xe5, 0xa2, 0x99, 0x0d, 0xc2, 0x01, 0x89, 0x09, 0x14, 0x68, + 0x6e, 0x65, 0xc5, 0x00, 0x43, 0xca, 0x72, 0xe4, 0x4f, 0x21, 0xf3, 0xe2, 0xb2, 0xd4, 0xa9, 0x2f, 0xd2, 0x1f, 0x19, + 0x62, 0xd4, 0x93, 0x94, 0x15, 0x10, 0xb0, 0x5e, 0xea, 0x25, 0xb4, 0x45, 0xb0, 0xf2, 0x67, 0x2a, 0x87, 0x46, 0x68, + 0x20, 0x51, 0x68, 0xa8, 0x25, 0x4a, 0xf9, 0xcc, 0xc3, 0x18, 0xa4, 0xfd, 0x93, 0x9a, 0xef, 0x2b, 0x57, 0x4a, 0x47, + 0x7e, 0x54, 0x0c, 0x03, 0xaa, 0x5f, 0x48, 0x0e, 0x36, 0x0d, 0xdf, 0x03, 0x19, 0x55, 0x86, 0x27, 0x31, 0xc2, 0xa7, + 0x71, 0xce, 0xc8, 0x52, 0xd8, 0x94, 0x30, 0x4b, 0xd5, 0x36, 0x52, 0xed, 0x22, 0xd3, 0x09, 0xe5, 0xc2, 0xfc, 0x53, + 0x23, 0x76, 0x91, 0x85, 0x2b, 0xad, 0x41, 0xfd, 0x78, 0x63, 0x02, 0x94, 0x5d, 0x5d, 0x65, 0xc2, 0xc6, 0x8d, 0x48, + 0xdf, 0xd0, 0x15, 0xd3, 0x81, 0x5a, 0x54, 0xe0, 0x44, 0xa4, 0xf1, 0x50, 0x0c, 0x85, 0x46, 0x38, 0xa4, 0x28, 0x72, + 0xe1, 0x1a, 0x87, 0xbe, 0x18, 0x68, 0xdb, 0x28, 0x0d, 0x9d, 0x04, 0x98, 0x80, 0x58, 0xbb, 0xa1, 0x4d, 0xa5, 0x83, + 0x34, 0x48, 0xa8, 0x14, 0xed, 0x1c, 0x58, 0x7f, 0x18, 0x49, 0x0c, 0x80, 0xfe, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05, + 0x6e, 0x00, 0xcd, 0x75, 0x80, 0x3b, 0xe1, 0x0b, 0x05, 0x15, 0xa6, 0xa7, 0x59, 0x79, 0x29, 0x84, 0xc8, 0xab, 0x35, + 0x29, 0x6b, 0xc4, 0x93, 0xcf, 0xd0, 0xe0, 0x53, 0xd6, 0xf5, 0x6b, 0xb9, 0x0e, 0x5d, 0xf0, 0x14, 0xb6, 0x55, 0x3d, + 0xbf, 0x0a, 0x39, 0x19, 0xd7, 0x20, 0x2b, 0x24, 0xd3, 0x5f, 0x31, 0x92, 0xf7, 0x5f, 0xf9, 0x55, 0x2d, 0x35, 0x86, + 0xb2, 0xf7, 0xeb, 0x9a, 0x61, 0x79, 0x39, 0xaf, 0xdc, 0x14, 0x04, 0xdc, 0x92, 0x25, 0xc1, 0x52, 0x4a, 0x08, 0xd0, + 0xb0, 0x3d, 0x92, 0x4a, 0x41, 0x51, 0x6a, 0xf7, 0xce, 0x53, 0xd0, 0x02, 0x8c, 0xa0, 0x96, 0x4a, 0xa6, 0x91, 0xc8, + 0x97, 0x42, 0x14, 0x88, 0xf2, 0x60, 0x04, 0x76, 0x6a, 0x33, 0xd2, 0x75, 0xe1, 0xfa, 0xf1, 0x0c, 0x53, 0x7b, 0x08, + 0xf4, 0xd8, 0xdb, 0x00, 0x55, 0xa2, 0x2e, 0xc3, 0x72, 0xa2, 0xd0, 0xac, 0x26, 0x59, 0x40, 0x8d, 0x69, 0x83, 0x94, + 0x6c, 0x83, 0x2e, 0x57, 0x80, 0x7e, 0x24, 0x8e, 0x67, 0xb5, 0x03, 0x42, 0xd6, 0xa0, 0x82, 0x21, 0x4f, 0xa9, 0x90, + 0xc2, 0xbc, 0xd7, 0xa5, 0x22, 0x3c, 0x9f, 0x03, 0x2e, 0xb5, 0xe0, 0xcc, 0xcb, 0x68, 0xe0, 0x83, 0xf8, 0x24, 0xc1, + 0xc4, 0x17, 0x5c, 0x15, 0xe8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x28, 0x15, 0x37, 0x29, 0x83, 0x6d, 0x45, 0xae, 0x0d, + 0x3f, 0x24, 0xcb, 0xd6, 0x5d, 0x1e, 0xea, 0x2e, 0x44, 0x02, 0xd8, 0xe9, 0x25, 0x7a, 0xbe, 0x65, 0xbd, 0x74, 0x18, + 0x9c, 0x69, 0x89, 0x83, 0xc0, 0x6f, 0x6f, 0x27, 0xc3, 0x32, 0x25, 0xb2, 0x6b, 0x92, 0xba, 0x80, 0x1c, 0x86, 0x6a, + 0xae, 0x1d, 0x98, 0xa5, 0xd2, 0xc7, 0xf3, 0x72, 0x86, 0xdb, 0xa5, 0x34, 0xe4, 0x66, 0xbc, 0x9a, 0xe6, 0x73, 0x2b, + 0xc9, 0xa6, 0xfd, 0xad, 0xf8, 0xa2, 0xe0, 0x1f, 0x38, 0xb1, 0xd4, 0xea, 0x29, 0xb5, 0xc2, 0xa3, 0xcc, 0x2d, 0x59, + 0xa7, 0xb8, 0x56, 0xd7, 0x0d, 0x54, 0x23, 0x8c, 0xa6, 0x61, 0x23, 0x60, 0x62, 0x82, 0x8a, 0x5f, 0x37, 0x89, 0x98, + 0xce, 0x96, 0xe0, 0x3a, 0x42, 0xef, 0xa1, 0x9c, 0xe0, 0xae, 0xa6, 0xd9, 0xe7, 0xe1, 0xfc, 0x7a, 0xe2, 0xde, 0x37, + 0x88, 0xfb, 0xcb, 0x90, 0x1b, 0x84, 0x1e, 0xcb, 0x84, 0x1f, 0xe9, 0xfb, 0x28, 0x54, 0xd5, 0x93, 0xd3, 0xb0, 0x62, + 0x59, 0xe2, 0xc9, 0x08, 0x75, 0x18, 0x51, 0xd1, 0x1a, 0x23, 0xbb, 0xba, 0xca, 0xcd, 0xb3, 0x40, 0x4e, 0x53, 0x8f, + 0xd7, 0xfd, 0xb4, 0x15, 0x39, 0x1b, 0x9e, 0xc8, 0xfd, 0x57, 0x35, 0x4f, 0x64, 0x45, 0xe7, 0x38, 0xd2, 0x35, 0x81, + 0xdc, 0x27, 0xa7, 0xab, 0x87, 0x54, 0xc8, 0x16, 0xbd, 0x6c, 0xe3, 0x8c, 0xea, 0x80, 0xa4, 0x9e, 0x51, 0x81, 0x55, + 0x8d, 0xbd, 0xb5, 0xd5, 0x11, 0xe9, 0x96, 0x4a, 0xb0, 0xc1, 0xd6, 0xc2, 0x68, 0xc6, 0x28, 0xe8, 0x94, 0x14, 0x19, + 0xa8, 0x51, 0x7e, 0x0d, 0x63, 0xd8, 0xa7, 0x06, 0x20, 0x38, 0xd7, 0x57, 0x7f, 0x59, 0x4a, 0xb2, 0x10, 0x90, 0xb8, + 0x4b, 0x06, 0x6c, 0x4d, 0x10, 0x33, 0xd2, 0xc9, 0x7b, 0xa0, 0xbc, 0x01, 0x43, 0x1b, 0x01, 0xec, 0x02, 0x71, 0xe8, + 0x41, 0xc5, 0xb6, 0x09, 0x29, 0x3a, 0x36, 0xf0, 0x1c, 0x80, 0x9d, 0x57, 0xae, 0xd1, 0x77, 0x55, 0x0a, 0x18, 0x92, + 0x81, 0x1b, 0xb0, 0xca, 0x2d, 0xb7, 0xff, 0x1c, 0xcc, 0x06, 0x78, 0x7d, 0x26, 0x9b, 0x6f, 0x62, 0x9e, 0x60, 0x15, + 0xbb, 0xf0, 0x2b, 0xcd, 0x5a, 0xc4, 0x9d, 0x0e, 0x1b, 0xf5, 0x0a, 0x13, 0xa2, 0xf6, 0x00, 0x6b, 0xdf, 0xa3, 0x87, + 0x45, 0xbc, 0xbf, 0xc2, 0x77, 0x3d, 0x6e, 0xb9, 0xaf, 0x97, 0x45, 0x2b, 0x5d, 0x45, 0x8d, 0x81, 0xc9, 0xba, 0x9d, + 0x8c, 0x6b, 0x2f, 0x0f, 0x84, 0x2f, 0xb8, 0x5a, 0x23, 0xab, 0x5c, 0x8a, 0x8d, 0x45, 0xd2, 0xd3, 0x3e, 0x05, 0xd8, + 0x37, 0x9b, 0xbd, 0x00, 0x33, 0xef, 0x2b, 0x54, 0x49, 0x48, 0x69, 0x76, 0x83, 0x25, 0x09, 0x6d, 0x45, 0x46, 0x9d, + 0x0f, 0x1c, 0x6d, 0x73, 0x2b, 0x8e, 0x60, 0x38, 0x27, 0x61, 0x3a, 0x56, 0x1e, 0x36, 0x19, 0xb8, 0xf2, 0x8e, 0x98, + 0xb6, 0x09, 0xf0, 0x6f, 0x06, 0x7c, 0x7b, 0x25, 0xb9, 0xb6, 0xd0, 0x30, 0x3c, 0x41, 0x84, 0x55, 0x9e, 0x08, 0x34, + 0x14, 0x60, 0x8d, 0x6b, 0x2d, 0x0f, 0x50, 0xe1, 0x6b, 0x67, 0x13, 0x00, 0x12, 0x59, 0x41, 0xce, 0x8a, 0xa3, 0x1b, + 0x56, 0xb9, 0xde, 0x4f, 0x8d, 0x82, 0xc4, 0xc5, 0x83, 0xe9, 0xea, 0x96, 0xfe, 0x0c, 0x35, 0x67, 0x52, 0xc4, 0xb4, + 0x13, 0x04, 0xfd, 0xa3, 0xcc, 0xc9, 0x69, 0x3a, 0xa1, 0x7d, 0xce, 0x9d, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x89, + 0x2d, 0x5e, 0xc7, 0x4d, 0x29, 0x17, 0x26, 0x39, 0xe6, 0xa6, 0x48, 0xc5, 0x66, 0x8a, 0xdd, 0xb9, 0xf5, 0x83, 0x16, + 0xd2, 0x41, 0xdb, 0x14, 0x39, 0xd8, 0xac, 0xe2, 0xf7, 0x04, 0xc6, 0x73, 0x81, 0xf8, 0xf2, 0x15, 0x25, 0xe9, 0x30, + 0xc7, 0x5c, 0x60, 0xf5, 0x62, 0x0a, 0xf2, 0x77, 0x8e, 0x4e, 0xb3, 0x37, 0xf0, 0x41, 0xe2, 0x0d, 0x38, 0x66, 0x8d, + 0x7d, 0xe7, 0x52, 0x51, 0x47, 0x08, 0x54, 0x46, 0xb5, 0x4c, 0xc7, 0x89, 0x95, 0xfb, 0x46, 0xd0, 0xd5, 0x5b, 0x1d, + 0xce, 0x37, 0x9e, 0x1b, 0xbb, 0x11, 0xc4, 0x60, 0x2d, 0x14, 0x43, 0x4f, 0xb2, 0xf0, 0x1c, 0xb6, 0x67, 0x7b, 0xbb, + 0x57, 0xec, 0xf1, 0xca, 0x45, 0x52, 0xc1, 0x18, 0x63, 0x46, 0x31, 0x9e, 0x89, 0x9a, 0x58, 0x44, 0x64, 0xcb, 0xd6, + 0x61, 0x81, 0x01, 0x00, 0x68, 0x69, 0x72, 0xaf, 0x9a, 0x08, 0x95, 0xf1, 0x5c, 0x5a, 0x4f, 0x15, 0x44, 0x55, 0x8d, + 0xdf, 0xae, 0xcf, 0x40, 0x21, 0xb8, 0x37, 0x3a, 0x1e, 0x06, 0x21, 0x60, 0x17, 0x05, 0x2f, 0xd0, 0x07, 0xb4, 0x57, + 0x25, 0x42, 0x31, 0x73, 0xb2, 0x1e, 0x33, 0x8c, 0x54, 0xd0, 0x85, 0x4a, 0xd8, 0x2a, 0xcd, 0xf0, 0xab, 0x83, 0xd0, + 0x8c, 0x32, 0xee, 0xbf, 0xaa, 0xd6, 0x0c, 0xf2, 0x83, 0x79, 0xab, 0x84, 0xfa, 0x76, 0x25, 0x22, 0x53, 0x81, 0x89, + 0x87, 0x59, 0x4a, 0xbf, 0x5f, 0xd6, 0x49, 0x3f, 0x2f, 0x97, 0xe7, 0x9c, 0x24, 0x5f, 0xe7, 0x0e, 0x92, 0x4f, 0xba, + 0xfb, 0x95, 0xf0, 0x43, 0x0d, 0xa3, 0x26, 0xfc, 0xea, 0x5b, 0x1a, 0xe6, 0x9e, 0x72, 0x6f, 0xf5, 0xbb, 0xc8, 0x74, + 0x51, 0x9e, 0x83, 0x22, 0xa4, 0x1f, 0xc1, 0x34, 0x34, 0x68, 0x50, 0x24, 0x8b, 0xc5, 0xda, 0x04, 0x71, 0x7d, 0xcc, + 0xa9, 0x76, 0x28, 0x63, 0x8c, 0x68, 0x5a, 0x52, 0x90, 0x24, 0x70, 0x50, 0x7e, 0x03, 0x03, 0x62, 0x12, 0x12, 0xd2, + 0x20, 0x74, 0xd6, 0x66, 0x22, 0x2a, 0x73, 0xf1, 0x76, 0xe5, 0xb2, 0x26, 0x50, 0x84, 0x9e, 0x60, 0xa6, 0x52, 0x2a, + 0x08, 0xa4, 0xca, 0xb7, 0xd1, 0xa9, 0x39, 0x43, 0x73, 0xd7, 0x14, 0x40, 0x5e, 0xdb, 0xf5, 0xa0, 0xc9, 0x7b, 0xf2, + 0xa1, 0xaf, 0x13, 0x23, 0x5e, 0x66, 0xd0, 0x35, 0x1c, 0xfe, 0x1a, 0x2b, 0x29, 0x42, 0x26, 0x7c, 0xaf, 0x60, 0x13, + 0x21, 0x99, 0x82, 0x9e, 0x09, 0xf8, 0x43, 0xbd, 0xb2, 0x97, 0xee, 0xe5, 0x95, 0x49, 0x8b, 0xca, 0x56, 0xa2, 0x66, + 0x2d, 0x8e, 0xe2, 0xed, 0x14, 0xce, 0xb3, 0x47, 0x09, 0x04, 0x24, 0xa9, 0x9c, 0xa4, 0x9a, 0xf7, 0x28, 0x1d, 0x02, + 0x48, 0x70, 0xfa, 0x09, 0x2c, 0xb4, 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, 0x73, 0x90, 0x9a, 0x73, 0x92, 0x7c, + 0x73, 0x94, 0xda, 0xdb, 0x4a, 0x7b, 0xc6, 0xec, 0x00, 0xdb, 0x76, 0xb7, 0xf3, 0xa3, 0x74, 0xbb, 0x33, 0x34, 0x18, + 0x17, 0x86, 0xff, 0x93, 0x12, 0xd3, 0x40, 0x0a, 0x29, 0x1b, 0x3f, 0xa1, 0x0c, 0xc3, 0xff, 0x96, 0x24, 0x80, 0x07, + 0xb5, 0xdd, 0x58, 0x31, 0xee, 0x15, 0x45, 0xc9, 0x6d, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0xf4, 0x89, 0x62, + 0x9e, 0x13, 0x00, 0xa3, 0xc8, 0xfc, 0x1d, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4d, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0x4f, + 0x29, 0xa4, 0xa2, 0xb2, 0x39, 0x89, 0xf8, 0x77, 0x05, 0x98, 0xe6, 0xc4, 0x47, 0x7a, 0xae, 0x61, 0x28, 0xc0, 0x57, + 0x3a, 0x94, 0x9a, 0xed, 0xe9, 0x1f, 0x9d, 0xed, 0xbe, 0x44, 0x8a, 0x20, 0x81, 0x06, 0x5e, 0xae, 0x59, 0x2f, 0xac, + 0x32, 0xb8, 0x23, 0xfe, 0x14, 0x7c, 0x5f, 0x5e, 0x07, 0x9f, 0x71, 0xfe, 0x05, 0xa0, 0x55, 0x81, 0x01, 0xe5, 0x83, + 0xa6, 0x62, 0x25, 0xd8, 0x25, 0x0a, 0xcc, 0xca, 0xcf, 0x1f, 0xd7, 0x69, 0xdd, 0xd4, 0x2c, 0xd1, 0x29, 0x3f, 0x77, + 0x0d, 0x33, 0xbe, 0xd7, 0xc8, 0x1f, 0xdf, 0x7f, 0x0e, 0xb2, 0x9d, 0x50, 0xbb, 0xb5, 0x55, 0x6c, 0x90, 0x86, 0x86, + 0xf7, 0xc2, 0xe6, 0xd0, 0x16, 0xf1, 0x52, 0xa8, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x87, + 0x15, 0x82, 0xc5, 0x4e, 0x65, 0xf2, 0x19, 0x0e, 0x9a, 0x22, 0xd7, 0x42, 0x28, 0x7c, 0x39, 0x88, 0x4a, 0x49, 0x8b, + 0x75, 0xb4, 0x3d, 0x3b, 0x83, 0xe7, 0x97, 0x71, 0x01, 0xd8, 0x81, 0xe5, 0x57, 0x58, 0x16, 0x07, 0xc8, 0xc5, 0x43, + 0x59, 0xeb, 0x15, 0x8d, 0xc7, 0x37, 0x76, 0x61, 0x75, 0x01, 0x3e, 0x8d, 0xd2, 0x71, 0x22, 0x26, 0x31, 0x93, 0x2a, + 0xd7, 0xe4, 0xda, 0xe8, 0x5e, 0x5a, 0xa3, 0x79, 0x2e, 0x38, 0x78, 0x85, 0xe0, 0x06, 0xd3, 0x57, 0xf2, 0x72, 0xbd, + 0x82, 0x82, 0xa1, 0xf6, 0xe6, 0x26, 0x98, 0x2b, 0xf1, 0x98, 0xc1, 0x35, 0xfd, 0x3a, 0x9c, 0x8a, 0x6e, 0x5e, 0xae, + 0x18, 0xfc, 0x3a, 0x67, 0xac, 0x21, 0x00, 0x88, 0x4e, 0x1e, 0x5e, 0x6f, 0x26, 0xbd, 0x52, 0xd2, 0x41, 0x49, 0x84, + 0xf8, 0xae, 0xcc, 0xd7, 0x5d, 0x2a, 0xba, 0x72, 0xd5, 0xbd, 0xaf, 0x19, 0x33, 0x2e, 0x18, 0x3d, 0xe7, 0xb3, 0xa4, + 0x71, 0xed, 0x86, 0xee, 0xea, 0xfc, 0xe8, 0xfd, 0x20, 0xf3, 0x16, 0x66, 0x40, 0x26, 0x20, 0x0a, 0x9e, 0x7b, 0xaf, + 0x8d, 0x88, 0xf2, 0xb7, 0x66, 0x88, 0x57, 0x0e, 0xb3, 0x2e, 0x92, 0xfc, 0xed, 0xe0, 0xdb, 0xe0, 0xfa, 0x96, 0x46, + 0x04, 0xb9, 0xab, 0x22, 0xc8, 0x84, 0xb9, 0x99, 0x3e, 0x70, 0xfb, 0x77, 0x65, 0x08, 0x22, 0x2a, 0xa6, 0x43, 0xe5, + 0xb8, 0x7f, 0xb4, 0x41, 0xa5, 0x42, 0xe2, 0x53, 0x95, 0xbb, 0x72, 0x6d, 0x6a, 0xa8, 0xc7, 0x75, 0x32, 0x0b, 0x4d, + 0xb3, 0x26, 0x97, 0xb2, 0x69, 0x31, 0x32, 0x4d, 0x4e, 0xb5, 0xf9, 0xdd, 0x6b, 0x83, 0x74, 0x0c, 0xd5, 0xc5, 0x5a, + 0x2d, 0x98, 0xdf, 0x95, 0x17, 0xde, 0xf5, 0x62, 0x23, 0x95, 0xa1, 0xa6, 0x3d, 0x8a, 0x3e, 0x8e, 0xdb, 0xcc, 0xe5, + 0x51, 0xfa, 0x67, 0x0d, 0x00, 0xd3, 0x10, 0x16, 0xdd, 0x4d, 0xcb, 0xd8, 0x13, 0xcb, 0xd3, 0x13, 0x19, 0x28, 0x7a, + 0xae, 0xf3, 0x55, 0xab, 0xc4, 0xd2, 0x35, 0x08, 0x76, 0x6f, 0xc8, 0x58, 0x95, 0xb8, 0x5b, 0xad, 0x5f, 0xcd, 0xf3, + 0x79, 0xca, 0x57, 0xf2, 0x7c, 0x6a, 0x1a, 0xdd, 0x46, 0xdb, 0xbd, 0x39, 0x35, 0x54, 0xcc, 0xb5, 0xbe, 0xc9, 0x1f, + 0x98, 0xae, 0x83, 0xae, 0x16, 0x81, 0x66, 0x75, 0xaa, 0x9e, 0x95, 0xe5, 0xac, 0x9e, 0xc9, 0x31, 0x13, 0xb6, 0xa9, + 0x34, 0x87, 0xe8, 0x86, 0xa9, 0x9a, 0xe9, 0xc7, 0xc6, 0xb1, 0x90, 0x6d, 0x9e, 0x5f, 0x8e, 0x73, 0xc0, 0xb4, 0x3c, + 0x5f, 0x26, 0x0c, 0x3f, 0x5e, 0x5d, 0xfd, 0x28, 0xf8, 0x54, 0xd5, 0xd1, 0x5b, 0xbe, 0xd4, 0x3d, 0x83, 0x59, 0xa9, + 0x8c, 0x88, 0x13, 0xb6, 0x7e, 0xf0, 0xe6, 0xe9, 0x15, 0xb0, 0x9c, 0xc0, 0xea, 0x4e, 0x98, 0xd3, 0x18, 0xaa, 0x3a, + 0xc0, 0x3f, 0xac, 0x1f, 0x6c, 0xdd, 0x19, 0xfe, 0x61, 0xf0, 0x43, 0x70, 0x63, 0x63, 0xe3, 0x18, 0xef, 0xd6, 0x12, + 0x41, 0x5e, 0x61, 0x40, 0x1f, 0xaf, 0x3e, 0x0a, 0x5c, 0xae, 0x63, 0xdb, 0x03, 0x87, 0xdc, 0xd6, 0xc0, 0xdf, 0x24, + 0x4f, 0x1a, 0x2d, 0x0a, 0x9e, 0xcd, 0xe4, 0x0c, 0x85, 0xbc, 0xe6, 0xe3, 0xa0, 0xee, 0x08, 0x7f, 0x03, 0xa7, 0x16, + 0x5e, 0x5e, 0x7e, 0x82, 0x3e, 0x60, 0xe9, 0x4a, 0x6e, 0x2a, 0xfc, 0x94, 0xf2, 0x88, 0xae, 0xd6, 0x79, 0x30, 0x52, + 0x5c, 0x4c, 0x51, 0xe8, 0xb8, 0xcb, 0x1b, 0x67, 0x23, 0xa3, 0xbf, 0xc4, 0xab, 0x8b, 0x74, 0xf9, 0x48, 0x64, 0xab, + 0x96, 0xde, 0x2f, 0x3a, 0xba, 0x6d, 0xcf, 0x18, 0x9f, 0x66, 0x63, 0x0a, 0xcc, 0xf8, 0x38, 0x11, 0x5e, 0x9f, 0x18, + 0xeb, 0xbb, 0x45, 0xa0, 0xba, 0x39, 0x36, 0xd9, 0xe1, 0x78, 0xbd, 0xd9, 0xac, 0x71, 0x07, 0x6f, 0x9c, 0x27, 0xce, + 0xb2, 0x44, 0x8f, 0xca, 0x52, 0xc3, 0x03, 0x52, 0x21, 0x6e, 0xde, 0x33, 0x81, 0x71, 0xd9, 0x25, 0x71, 0x6d, 0x37, + 0x10, 0x6b, 0xb1, 0x27, 0x31, 0x4b, 0xc6, 0xb6, 0x07, 0xe5, 0x81, 0xbe, 0x18, 0x4d, 0xb7, 0x80, 0x69, 0x7b, 0xed, + 0xec, 0x3c, 0xb5, 0xbd, 0x6a, 0xaa, 0x00, 0x66, 0xc9, 0xf2, 0xf8, 0x14, 0x49, 0xf7, 0x1b, 0xe8, 0x22, 0x06, 0x8c, + 0x8d, 0x2b, 0x73, 0xee, 0x72, 0xdd, 0x8e, 0xf8, 0x46, 0x13, 0xa9, 0x52, 0x1f, 0x51, 0xdf, 0x61, 0x58, 0xab, 0xab, + 0x0c, 0x24, 0x30, 0x8f, 0xbc, 0x3b, 0xae, 0xa5, 0xa7, 0x63, 0x16, 0x93, 0x2a, 0x7d, 0x4b, 0x5d, 0x8b, 0x6b, 0xba, + 0xbd, 0xe2, 0x01, 0xe8, 0x1f, 0xe8, 0xb7, 0x88, 0x85, 0xbf, 0x9d, 0xd7, 0x52, 0x58, 0x1b, 0x73, 0xe4, 0xe8, 0x6b, + 0x0f, 0x7e, 0x61, 0xd5, 0x9e, 0x81, 0x1a, 0x66, 0xc4, 0x48, 0x7e, 0x33, 0xee, 0x55, 0x4d, 0x1c, 0xb9, 0x0b, 0xc0, + 0xfa, 0x96, 0x74, 0x49, 0x0e, 0xaf, 0x64, 0xb9, 0x2a, 0x86, 0xfc, 0x1b, 0xec, 0xb3, 0xde, 0x9c, 0x80, 0x99, 0x38, + 0xe5, 0x25, 0x26, 0xa6, 0x88, 0xcb, 0xcd, 0xd2, 0xe7, 0x69, 0xda, 0x2c, 0xda, 0xc0, 0x29, 0x8c, 0x04, 0x8e, 0xd8, + 0x37, 0xb6, 0xa1, 0x99, 0xb0, 0x11, 0x13, 0x6a, 0x54, 0x4a, 0x09, 0x1f, 0xc8, 0xad, 0x96, 0xf4, 0x65, 0x6e, 0xaf, + 0xbe, 0xdc, 0x26, 0x28, 0xa0, 0xa8, 0x81, 0xe5, 0xd0, 0x38, 0x6e, 0x19, 0xc8, 0x85, 0xc5, 0xb0, 0x30, 0x6a, 0x55, + 0xae, 0x26, 0xa3, 0x3a, 0x99, 0xaf, 0x16, 0x17, 0x2a, 0xf4, 0xe0, 0x91, 0x40, 0xce, 0x5f, 0x60, 0xea, 0x60, 0x56, + 0x6a, 0x33, 0x2d, 0x36, 0x51, 0xde, 0x33, 0x1d, 0x92, 0xeb, 0xaf, 0xe1, 0xa1, 0xf2, 0x8b, 0x57, 0xe6, 0x14, 0xf3, + 0x45, 0x1e, 0x4b, 0x5b, 0x63, 0x6e, 0xfd, 0xaf, 0xf2, 0x3e, 0xad, 0x04, 0xec, 0x37, 0x60, 0x53, 0xc6, 0x5a, 0x62, + 0xe3, 0x82, 0xa4, 0xbc, 0x96, 0xa7, 0xf4, 0xbe, 0x86, 0xf0, 0x5d, 0x51, 0xe9, 0x2a, 0x91, 0x75, 0x8d, 0x56, 0xf7, + 0xeb, 0x82, 0xe5, 0x97, 0x07, 0x0c, 0x73, 0x93, 0x51, 0x21, 0x5b, 0x51, 0xb3, 0x29, 0xbf, 0xda, 0xbb, 0xf1, 0x2b, + 0x0f, 0x25, 0x05, 0xd5, 0x2a, 0xd9, 0xbc, 0x72, 0xc3, 0x31, 0x6e, 0xdc, 0x70, 0x8c, 0x7b, 0x14, 0x57, 0xae, 0x50, + 0xad, 0xf3, 0xdf, 0x57, 0xdd, 0x4f, 0x74, 0xd6, 0x86, 0xfa, 0xd4, 0x0d, 0xd7, 0xa6, 0xa7, 0xdf, 0xb0, 0x54, 0x23, + 0x4b, 0xe8, 0xa6, 0xa5, 0x62, 0x32, 0x12, 0x25, 0xa6, 0xab, 0x94, 0x47, 0x7d, 0x8d, 0xb8, 0x00, 0x76, 0x43, 0xf9, + 0x8b, 0x7f, 0x0d, 0xcf, 0x8f, 0x03, 0x54, 0xa2, 0x96, 0x93, 0x2c, 0xe5, 0xad, 0x49, 0x34, 0x8b, 0x93, 0xcb, 0x60, + 0x11, 0xb7, 0x66, 0x59, 0x9a, 0x15, 0x73, 0xa0, 0x4a, 0xaf, 0xb8, 0x04, 0x1d, 0x7e, 0xd6, 0x5a, 0xc4, 0xde, 0x73, + 0x96, 0x9c, 0x31, 0x1e, 0x8f, 0x22, 0xcf, 0xde, 0xcf, 0x81, 0x3d, 0x58, 0xaf, 0xa3, 0x3c, 0xcf, 0xce, 0x6d, 0xef, + 0x5d, 0x76, 0x02, 0x44, 0xeb, 0xbd, 0xb9, 0xb8, 0x3c, 0x65, 0xa9, 0xf7, 0xfe, 0x64, 0x91, 0xf2, 0x85, 0x57, 0x44, + 0x69, 0xd1, 0x2a, 0x58, 0x1e, 0x4f, 0x40, 0x4c, 0x24, 0x59, 0xde, 0xc2, 0xfc, 0xe7, 0x19, 0x0b, 0x92, 0xf8, 0x74, + 0xca, 0xad, 0x71, 0x94, 0x7f, 0xea, 0xb5, 0x5a, 0xf3, 0x3c, 0x9e, 0x45, 0xf9, 0x65, 0x8b, 0x5a, 0x04, 0x9f, 0xb5, + 0x77, 0xa3, 0x2f, 0x26, 0xf7, 0x7b, 0x3c, 0x87, 0xbe, 0x31, 0x62, 0x31, 0x00, 0xe6, 0x63, 0xed, 0x3e, 0x68, 0xcf, + 0x8a, 0x0d, 0x11, 0x51, 0x8a, 0x52, 0x5e, 0x1e, 0x7b, 0x1f, 0x41, 0xb7, 0x3d, 0xf6, 0x4f, 0x78, 0xea, 0x81, 0x2d, + 0xc7, 0xb3, 0x74, 0x39, 0x5a, 0xe4, 0x05, 0x0c, 0x30, 0xcf, 0xe2, 0x94, 0xb3, 0xbc, 0x77, 0x92, 0xe5, 0x80, 0xb6, + 0x56, 0x1e, 0x8d, 0xe3, 0x45, 0x11, 0xdc, 0x9f, 0x5f, 0xf4, 0x50, 0x57, 0x38, 0xcd, 0xb3, 0x45, 0x3a, 0x96, 0x73, + 0xc5, 0x29, 0x1c, 0x8c, 0x98, 0x9b, 0x15, 0xf4, 0x25, 0x14, 0x80, 0x2f, 0x65, 0x51, 0xde, 0x3a, 0xc5, 0xce, 0xa8, + 0xe8, 0xb7, 0xc7, 0xec, 0xd4, 0xcb, 0x4f, 0x4f, 0x22, 0xa7, 0xd3, 0x7d, 0xe4, 0xa9, 0x7f, 0xfe, 0x03, 0x17, 0x14, + 0xf7, 0xb5, 0xc5, 0x9d, 0x76, 0xfb, 0x1f, 0xdc, 0x5e, 0x63, 0x16, 0x02, 0x28, 0xe8, 0xcc, 0x2f, 0xac, 0x22, 0x4b, + 0x60, 0x7f, 0xd6, 0xf5, 0xec, 0xcd, 0xc1, 0x6e, 0x8a, 0xd3, 0xd3, 0xa0, 0x3b, 0xbf, 0x28, 0x71, 0x75, 0x81, 0x48, + 0xc8, 0x94, 0x8b, 0x94, 0x6f, 0xcb, 0x3f, 0x0a, 0xf1, 0xe3, 0xf5, 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd6, 0x5b, 0x63, + 0x38, 0x07, 0x84, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x82, 0x11, 0x98, 0x2b, 0x38, 0xe8, 0xe5, 0x0f, 0x83, 0xd1, 0x5d, + 0x0f, 0xc6, 0xe3, 0xdb, 0xc0, 0xc8, 0xd3, 0xf1, 0xb2, 0xbe, 0xaf, 0x1d, 0x30, 0x4e, 0x7b, 0x53, 0x86, 0xf4, 0x14, + 0x74, 0xf1, 0xf9, 0x3c, 0x1e, 0xf3, 0xa9, 0x78, 0x24, 0x72, 0x3e, 0x17, 0x75, 0x0f, 0xda, 0x6d, 0xf1, 0x5e, 0x80, + 0x40, 0x0b, 0x3a, 0x3e, 0x36, 0x00, 0x22, 0x7a, 0x71, 0xdd, 0x47, 0x6c, 0x3e, 0xdc, 0xfa, 0xa5, 0x1a, 0xef, 0x52, + 0xe5, 0x0d, 0x0a, 0x11, 0xa1, 0xbe, 0xd9, 0x82, 0x19, 0x6f, 0x45, 0xbf, 0xa3, 0x03, 0x55, 0x83, 0x0f, 0x8c, 0xa4, + 0x5e, 0xc0, 0x3d, 0x33, 0x17, 0xa8, 0x97, 0xf6, 0xd1, 0x25, 0xd5, 0x6a, 0xb9, 0x20, 0x37, 0x18, 0xba, 0x90, 0x28, + 0x20, 0xe8, 0x14, 0x83, 0x9c, 0xbe, 0xa9, 0x91, 0xb9, 0x41, 0xee, 0x64, 0x2e, 0x1c, 0xf9, 0x4c, 0xf3, 0xf5, 0x62, + 0x6b, 0x0b, 0xac, 0xec, 0x17, 0x4c, 0x36, 0x00, 0xee, 0x4d, 0xae, 0xae, 0xef, 0x43, 0x61, 0x4a, 0x29, 0x43, 0x6a, + 0x76, 0xd3, 0x15, 0x7d, 0xd8, 0x95, 0x98, 0x32, 0x92, 0x8f, 0x86, 0xff, 0x0e, 0xc5, 0xde, 0xd1, 0x86, 0x65, 0x91, + 0x2d, 0xf2, 0x11, 0x79, 0xea, 0x56, 0x2d, 0x7e, 0x9b, 0x04, 0xae, 0xed, 0x31, 0xcd, 0xe7, 0xd1, 0x0c, 0xae, 0x7d, + 0xe4, 0x80, 0x53, 0x10, 0x44, 0xdc, 0x31, 0x90, 0x5e, 0x0e, 0x05, 0x21, 0x8a, 0xae, 0x31, 0xe5, 0xbb, 0xd1, 0xfd, + 0x4b, 0x7f, 0x91, 0xc6, 0xc0, 0xe9, 0x3e, 0xc6, 0x63, 0xba, 0x77, 0x12, 0x8f, 0x29, 0x10, 0xd1, 0xa2, 0xc4, 0x23, + 0xf4, 0x6c, 0x43, 0x81, 0xfa, 0x0e, 0x0b, 0x3c, 0xcb, 0x44, 0x16, 0xbb, 0x65, 0x63, 0x30, 0xc1, 0x10, 0x95, 0xe3, + 0x6c, 0x16, 0xc5, 0x69, 0x80, 0xdf, 0x07, 0xf1, 0xf4, 0x88, 0x01, 0x76, 0xf1, 0xe0, 0x27, 0x93, 0xb9, 0x68, 0x1d, + 0xd7, 0xff, 0x05, 0xf8, 0x08, 0xf5, 0x2f, 0xa5, 0x1d, 0xa6, 0xe1, 0x52, 0x61, 0xde, 0x7a, 0x29, 0xf0, 0x1e, 0xae, + 0x74, 0x56, 0x46, 0x7e, 0x8e, 0x3d, 0x4e, 0x3f, 0x06, 0xad, 0x4e, 0xd0, 0xd1, 0xa6, 0x6b, 0xed, 0x36, 0xaa, 0xc8, + 0x65, 0x91, 0x37, 0x1a, 0x09, 0x06, 0xfd, 0x2c, 0xe0, 0xac, 0xde, 0x35, 0xac, 0x9e, 0xa4, 0x4b, 0x74, 0xe0, 0x9c, + 0xa6, 0x4e, 0x0d, 0x08, 0x8a, 0x05, 0x5c, 0x33, 0x95, 0x5b, 0x46, 0x24, 0x94, 0xbe, 0xa4, 0x03, 0x5c, 0xbf, 0x4b, + 0x84, 0xf7, 0x86, 0xea, 0x29, 0x50, 0x8a, 0xe4, 0x16, 0xc7, 0x7b, 0xe2, 0xc4, 0x5b, 0x44, 0x63, 0xa1, 0x0d, 0x47, + 0xd0, 0xb6, 0xfe, 0x32, 0x02, 0x2c, 0x7d, 0x0a, 0xed, 0xcd, 0xa5, 0xa3, 0x12, 0xeb, 0x73, 0x98, 0x6b, 0x5f, 0x48, + 0x3d, 0xba, 0x91, 0x6f, 0xf7, 0x37, 0x97, 0xbc, 0xdc, 0xdb, 0x11, 0xbd, 0xfb, 0xc7, 0x65, 0x41, 0x02, 0xca, 0x74, + 0xa4, 0x55, 0x53, 0x88, 0x3a, 0x18, 0x96, 0xd2, 0x77, 0x71, 0xdc, 0x42, 0x2b, 0x5d, 0xc2, 0x63, 0x2c, 0xc9, 0x2e, + 0xc7, 0x74, 0xa5, 0x28, 0x87, 0x33, 0xa9, 0x13, 0x52, 0x72, 0x91, 0x83, 0xd1, 0x5b, 0x85, 0xe2, 0x18, 0x21, 0x18, + 0x6c, 0x2e, 0xe3, 0x32, 0xdc, 0x5c, 0x66, 0xe5, 0x31, 0x68, 0x26, 0x08, 0x55, 0xa1, 0x3e, 0xef, 0x02, 0x13, 0x0b, + 0x27, 0x8b, 0x45, 0x23, 0xe0, 0xb4, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x06, 0x2c, 0x40, 0x2c, 0x00, 0xdd, 0x8d, 0x7a, + 0x31, 0x58, 0x8b, 0x68, 0xdd, 0x87, 0x81, 0xf6, 0x76, 0x44, 0x23, 0x58, 0x57, 0x8e, 0x20, 0x57, 0xcb, 0xc2, 0x74, + 0x1c, 0x73, 0x69, 0x49, 0x74, 0xc2, 0x12, 0xe8, 0x9f, 0x5f, 0x5d, 0xb5, 0xa1, 0x9b, 0x78, 0xb5, 0xf6, 0xe2, 0x74, + 0xbe, 0x90, 0xdf, 0xd4, 0x82, 0x59, 0x3a, 0x18, 0xe6, 0xc4, 0x94, 0xff, 0x81, 0x8a, 0xdb, 0x05, 0x36, 0x8d, 0x6b, + 0x03, 0x3c, 0x14, 0x32, 0x40, 0x50, 0x2a, 0x1a, 0x80, 0xd2, 0x78, 0xbc, 0x5a, 0xa6, 0x97, 0x51, 0xc0, 0x0b, 0x9c, + 0xc1, 0x39, 0x3e, 0xa7, 0xf0, 0x3c, 0x8b, 0x53, 0x7c, 0xcc, 0xf1, 0x31, 0xba, 0xc0, 0xc7, 0xac, 0xb4, 0xff, 0x2e, + 0xe8, 0xb6, 0x34, 0x02, 0xb2, 0xab, 0x2b, 0x60, 0xee, 0x1a, 0x05, 0x40, 0x10, 0xe2, 0xdb, 0x2a, 0xcc, 0xc4, 0x16, + 0x2b, 0xe6, 0x2d, 0x51, 0x6e, 0x91, 0xf0, 0x0c, 0xc1, 0xb6, 0xca, 0x9d, 0x86, 0x8e, 0xe0, 0xc9, 0x2c, 0x92, 0x27, + 0xf8, 0xe2, 0xda, 0x96, 0xf8, 0xf8, 0x85, 0x40, 0x07, 0x3d, 0xe2, 0xda, 0x74, 0x19, 0x97, 0x9f, 0xb5, 0x89, 0x43, + 0x1b, 0x67, 0x01, 0x35, 0x0d, 0x99, 0x3d, 0x8f, 0xe2, 0x44, 0x34, 0x5e, 0xb3, 0x92, 0x46, 0x3a, 0x20, 0x2d, 0x64, + 0x6f, 0xa7, 0x82, 0x0d, 0x80, 0x1f, 0x89, 0xcb, 0xd4, 0x15, 0xf4, 0xb6, 0xa8, 0xa2, 0x28, 0xb9, 0x3c, 0xbc, 0x03, + 0xe1, 0x0f, 0xd7, 0xeb, 0x1c, 0x82, 0x5d, 0x17, 0xa5, 0xf5, 0x16, 0x00, 0xf1, 0x9c, 0xb1, 0xb1, 0x67, 0x5b, 0xc0, + 0x26, 0xc5, 0xf3, 0xc7, 0x84, 0x9d, 0x31, 0xf9, 0x11, 0x14, 0xdd, 0x57, 0x57, 0x8e, 0x40, 0xda, 0x72, 0x79, 0x3f, + 0x53, 0x52, 0x9e, 0x5a, 0x97, 0x5c, 0x7d, 0x1d, 0x78, 0xcf, 0x36, 0x06, 0x6d, 0xce, 0xd1, 0xae, 0x0f, 0xeb, 0x75, + 0x40, 0x91, 0xb5, 0x01, 0x4c, 0xd2, 0xcf, 0x6e, 0x5a, 0x0a, 0xf4, 0x63, 0x93, 0x09, 0x1c, 0x00, 0x15, 0xb7, 0xd0, + 0xa7, 0x5b, 0x00, 0x03, 0x66, 0xa6, 0x67, 0x8b, 0x16, 0x76, 0xd5, 0x56, 0x3f, 0x21, 0x2a, 0x92, 0x6c, 0xf4, 0xa9, + 0x36, 0xc5, 0x02, 0x09, 0x08, 0xc7, 0x6a, 0xf0, 0x29, 0xfb, 0xdf, 0xfe, 0xf5, 0x7f, 0xfe, 0x57, 0x18, 0x8e, 0x3a, + 0xb8, 0xa5, 0x75, 0x7d, 0xab, 0xff, 0x01, 0xad, 0x16, 0xe9, 0x2d, 0xed, 0xfe, 0xf6, 0xcf, 0xff, 0x0d, 0x9a, 0xd1, + 0xcd, 0x1a, 0xb7, 0x3c, 0x0e, 0xec, 0x11, 0x6a, 0x32, 0x77, 0x03, 0xa4, 0xd6, 0xf5, 0xda, 0xf1, 0xff, 0x05, 0x81, + 0x2d, 0x78, 0x36, 0xbf, 0x11, 0x08, 0x84, 0x75, 0x94, 0x64, 0x05, 0x13, 0x50, 0x08, 0x36, 0x79, 0x47, 0x30, 0x68, + 0x86, 0x39, 0x90, 0x6c, 0x61, 0x89, 0xde, 0x02, 0xfb, 0xb5, 0xde, 0x8d, 0x5d, 0x29, 0x18, 0x27, 0xd0, 0xc9, 0x03, + 0x00, 0xfb, 0x20, 0x9e, 0xe0, 0x81, 0x4e, 0x33, 0x6c, 0xbb, 0xce, 0x17, 0x68, 0x0c, 0xa1, 0x89, 0x4c, 0x8c, 0x20, + 0x5c, 0x1d, 0xaa, 0x1f, 0xfc, 0x04, 0xd6, 0xf2, 0x51, 0x3f, 0x47, 0x17, 0xfa, 0x19, 0xd9, 0x0f, 0x0c, 0x0b, 0x82, + 0x62, 0x86, 0x3a, 0x40, 0x73, 0x61, 0xea, 0xa4, 0x56, 0xfc, 0x81, 0xa9, 0xe4, 0xb0, 0x8f, 0x98, 0x0f, 0x89, 0xb7, + 0x5f, 0x16, 0x39, 0xab, 0x38, 0x26, 0x36, 0x10, 0xac, 0xc8, 0xac, 0xff, 0x98, 0x64, 0xe7, 0xe5, 0x75, 0x75, 0x53, + 0xa0, 0xe2, 0x72, 0x6f, 0x1c, 0x9f, 0xf5, 0x25, 0x22, 0x1b, 0x6b, 0x59, 0xed, 0xd2, 0x5c, 0x18, 0x56, 0xc9, 0x75, + 0xc9, 0x47, 0x5c, 0x96, 0xd7, 0x46, 0x01, 0x80, 0xe3, 0xee, 0x9d, 0xe4, 0x7d, 0xb9, 0x80, 0x57, 0x78, 0x61, 0x8b, + 0x20, 0x41, 0x3e, 0x2e, 0x64, 0x0c, 0x27, 0x19, 0x63, 0xb2, 0x7a, 0xd4, 0x5a, 0x33, 0xc5, 0xd2, 0xb1, 0x61, 0x8d, + 0x0b, 0x73, 0xc9, 0x85, 0x63, 0xa9, 0x0e, 0x49, 0x2e, 0x8c, 0x1f, 0xe0, 0x68, 0x70, 0xe1, 0xf8, 0x5a, 0x2e, 0x8c, + 0x6b, 0x1b, 0xe0, 0xc8, 0xa1, 0xbd, 0x8d, 0xb6, 0xb8, 0x21, 0x15, 0x38, 0x0a, 0x37, 0x30, 0xc0, 0x46, 0x9f, 0xa4, + 0x6c, 0x23, 0x50, 0xd1, 0x18, 0x95, 0xd2, 0x9a, 0x04, 0x9b, 0x64, 0xcf, 0xc1, 0xe2, 0x18, 0x64, 0x9b, 0x39, 0x32, + 0x58, 0xc2, 0x13, 0x86, 0xc7, 0xff, 0x78, 0x07, 0xfb, 0x8a, 0xcd, 0x2c, 0xe9, 0x19, 0xa4, 0xcf, 0x0e, 0x0d, 0xe0, + 0x2d, 0x85, 0x3b, 0x23, 0xb0, 0xdf, 0xbe, 0x39, 0x38, 0xb4, 0xbd, 0x93, 0x6c, 0x7c, 0x19, 0xd8, 0xa0, 0x8a, 0x82, + 0x24, 0x73, 0x7d, 0x3e, 0x65, 0xa9, 0xa3, 0x94, 0xc1, 0x2c, 0x01, 0x65, 0x38, 0x3b, 0x15, 0xb7, 0xef, 0x9b, 0xae, + 0x58, 0x40, 0x1b, 0x7d, 0x9e, 0xaf, 0xbf, 0xc7, 0xc5, 0x97, 0x2b, 0x79, 0x8e, 0x8f, 0x7d, 0x0c, 0x46, 0xef, 0xed, + 0xc0, 0x03, 0xbe, 0x1c, 0x20, 0x05, 0xe9, 0x37, 0x01, 0x67, 0x21, 0xde, 0x77, 0xb0, 0xfd, 0x8e, 0xea, 0x8b, 0x50, + 0x28, 0x1a, 0xd0, 0xfa, 0x5a, 0xa5, 0x04, 0xd0, 0xd8, 0x63, 0x22, 0x41, 0xdc, 0x18, 0xc0, 0x01, 0x1f, 0xeb, 0x12, + 0x41, 0xa6, 0x46, 0x11, 0x8d, 0x52, 0xb1, 0x7f, 0x59, 0x85, 0x13, 0x12, 0xfa, 0xc4, 0x64, 0xf0, 0x93, 0xc0, 0x3f, + 0x36, 0xbf, 0x34, 0x25, 0x3e, 0x0a, 0xa3, 0x17, 0x79, 0xf4, 0x57, 0xb0, 0x61, 0xbd, 0xf3, 0x63, 0x6a, 0xa9, 0xcc, + 0x1a, 0xb4, 0xb7, 0xd1, 0xfc, 0x6b, 0x2b, 0xfb, 0x15, 0x24, 0x5e, 0x12, 0xcd, 0x0b, 0x16, 0xa8, 0x07, 0x69, 0xe1, + 0xa0, 0xa1, 0xb4, 0x6a, 0x52, 0x9a, 0x92, 0xb1, 0xe4, 0xd3, 0xa5, 0x69, 0x02, 0x3d, 0x04, 0x13, 0x08, 0xd3, 0xb7, + 0x5b, 0x11, 0xb0, 0xf7, 0x34, 0x48, 0xd8, 0x84, 0x97, 0x1c, 0xef, 0x07, 0x2f, 0x95, 0xcd, 0xe9, 0x77, 0x1f, 0x80, + 0x59, 0x64, 0xf9, 0xf8, 0xff, 0x6d, 0x63, 0x8f, 0x83, 0x14, 0xcc, 0x18, 0xba, 0x30, 0x80, 0x97, 0xb1, 0x00, 0x22, + 0xf3, 0x7d, 0x69, 0x4c, 0x34, 0x62, 0x68, 0x8f, 0x97, 0x3c, 0xb7, 0xf8, 0xd4, 0xe3, 0xb9, 0xd9, 0x0e, 0x34, 0xa5, + 0x15, 0xa3, 0x7c, 0xd5, 0x2c, 0xdc, 0x75, 0xa5, 0xf2, 0xb8, 0xda, 0x58, 0xd9, 0xd6, 0xf5, 0xb7, 0x15, 0x0c, 0x19, + 0x5e, 0x80, 0x52, 0x70, 0xbe, 0xa5, 0xe8, 0x61, 0xae, 0x69, 0xd5, 0x3f, 0x70, 0xab, 0xee, 0x51, 0xd2, 0xd9, 0x3e, + 0xa2, 0xb3, 0x4d, 0xcc, 0x65, 0xb8, 0x14, 0x73, 0x8f, 0xa2, 0x64, 0xe4, 0x20, 0x00, 0x56, 0xcb, 0xba, 0x0f, 0xd8, + 0x04, 0x2e, 0x3d, 0x2c, 0xcb, 0xde, 0x25, 0x73, 0x8e, 0x7e, 0x93, 0x79, 0xe4, 0xe2, 0xfa, 0xa0, 0xfe, 0x04, 0x5b, + 0xbb, 0x74, 0x87, 0xde, 0xf7, 0xc6, 0x77, 0xad, 0x6c, 0x45, 0xa9, 0xb6, 0x07, 0xf8, 0xfd, 0x3e, 0xc4, 0xbe, 0xaf, + 0x1c, 0x1b, 0xb5, 0x10, 0xaa, 0xb9, 0x6c, 0x11, 0xe1, 0xd8, 0xd8, 0x4d, 0x78, 0x41, 0xbf, 0xba, 0xce, 0x98, 0xfd, + 0xee, 0x76, 0x63, 0x96, 0xdd, 0xd1, 0x98, 0xfd, 0xee, 0x4f, 0x36, 0x66, 0xbf, 0x6a, 0x1a, 0xb3, 0xbf, 0xfe, 0x1e, + 0x63, 0x36, 0xcf, 0xce, 0x8b, 0xb0, 0x23, 0x83, 0xa7, 0xc0, 0x4c, 0xfe, 0x3e, 0x56, 0x2d, 0x4c, 0xd4, 0xb0, 0x69, + 0xc9, 0x88, 0x15, 0xf9, 0x5e, 0xc0, 0xab, 0xa5, 0x09, 0xd9, 0xd6, 0x89, 0x55, 0xad, 0xfb, 0xea, 0x26, 0x09, 0xe8, + 0xf5, 0xae, 0xbe, 0x03, 0xd5, 0x55, 0x46, 0x66, 0x40, 0x9f, 0x82, 0xd4, 0x1d, 0xbb, 0xdb, 0x2a, 0xa3, 0xc7, 0x1c, + 0xa1, 0xa7, 0x1c, 0xb5, 0x82, 0x7c, 0x96, 0xf6, 0x7f, 0x3a, 0xea, 0xf4, 0x76, 0x3b, 0x33, 0xe8, 0x0d, 0x72, 0x0b, + 0xde, 0xda, 0xbd, 0xdd, 0x5d, 0x7c, 0x3b, 0x57, 0x6f, 0x5d, 0x7c, 0x8b, 0xd5, 0xdb, 0x03, 0x7c, 0x1b, 0xa9, 0xb7, + 0x87, 0xf8, 0x36, 0x56, 0x6f, 0x8f, 0xf0, 0xed, 0xcc, 0x2e, 0x8f, 0xb8, 0x06, 0xee, 0x11, 0xd0, 0x15, 0x29, 0x89, + 0x81, 0x2a, 0x83, 0xd3, 0x88, 0x37, 0xb0, 0xa2, 0xd3, 0x20, 0xf6, 0x84, 0x02, 0x1d, 0x14, 0xde, 0x39, 0xb0, 0xf4, + 0x80, 0x12, 0x8e, 0x9e, 0xe2, 0x55, 0x7c, 0xd0, 0x3d, 0x0f, 0xe3, 0x19, 0x53, 0xdf, 0x24, 0x55, 0xab, 0x06, 0x35, + 0x05, 0xec, 0xed, 0xb2, 0xa7, 0xf7, 0x49, 0xd8, 0xd0, 0x2a, 0x77, 0x82, 0x76, 0xae, 0xaa, 0x13, 0xd3, 0xb5, 0xf4, + 0x0e, 0x5f, 0x23, 0x20, 0x40, 0x00, 0x2b, 0xa3, 0x74, 0x02, 0x6a, 0x40, 0xeb, 0x02, 0x94, 0xf4, 0xb5, 0x42, 0x03, + 0x21, 0xd2, 0x62, 0x82, 0xd6, 0xa4, 0xdf, 0x0e, 0xa3, 0x53, 0xfd, 0xfc, 0x0a, 0xf4, 0xa9, 0xe8, 0x94, 0xdd, 0x26, + 0x40, 0x08, 0x44, 0x53, 0x78, 0x28, 0x20, 0x48, 0x0b, 0x81, 0xad, 0x41, 0x63, 0x41, 0x0a, 0x0f, 0xc4, 0x4e, 0x5d, + 0x9c, 0xd0, 0xf4, 0xf5, 0x22, 0xc0, 0x68, 0x55, 0xb0, 0x07, 0x6a, 0x1d, 0x95, 0x0a, 0x0c, 0x43, 0x05, 0x16, 0xdc, + 0x28, 0x63, 0x84, 0x2a, 0x72, 0x93, 0xa4, 0xb1, 0x94, 0x90, 0x31, 0x1d, 0xbc, 0xda, 0xbb, 0xbb, 0xca, 0xf7, 0x3e, + 0xeb, 0x8c, 0xf0, 0x8f, 0xe4, 0xaa, 0x9f, 0x4d, 0x26, 0x93, 0x1b, 0x85, 0xce, 0x67, 0xe3, 0x09, 0xeb, 0xb2, 0x07, + 0x3d, 0x74, 0xfe, 0xb5, 0xa4, 0x2f, 0xae, 0x53, 0x12, 0xee, 0x96, 0x77, 0x6b, 0x8c, 0xce, 0x38, 0x90, 0x43, 0x77, + 0x97, 0x4e, 0x25, 0x60, 0x65, 0x09, 0x5c, 0xf9, 0x34, 0x4e, 0x83, 0x76, 0xe9, 0x9f, 0x49, 0x76, 0xfe, 0xd9, 0xe3, + 0xc7, 0x8f, 0x4b, 0x7f, 0xac, 0xde, 0xda, 0xe3, 0x71, 0xe9, 0x8f, 0x96, 0x7a, 0x19, 0xed, 0xf6, 0x64, 0x52, 0xfa, + 0xb1, 0x2a, 0xd8, 0xed, 0x8e, 0xc6, 0xbb, 0xdd, 0xd2, 0x3f, 0x37, 0x5a, 0x94, 0x3e, 0x93, 0x6f, 0x39, 0x1b, 0xd7, + 0x3c, 0x88, 0x8f, 0xc0, 0x78, 0xf5, 0x05, 0xa1, 0x2d, 0xd1, 0x64, 0x10, 0x8f, 0x41, 0xb4, 0xe0, 0x60, 0xeb, 0x02, + 0x6f, 0x67, 0xc0, 0x9f, 0x27, 0x92, 0xb7, 0x8b, 0x4f, 0x7e, 0x22, 0x47, 0xff, 0xd5, 0xe4, 0xe8, 0x48, 0xcc, 0xc4, + 0xcd, 0x19, 0xc9, 0x81, 0x66, 0x35, 0x52, 0x16, 0x55, 0xff, 0x1a, 0xb2, 0x8a, 0xd9, 0x23, 0xb7, 0xc1, 0x96, 0x82, + 0xc7, 0x7f, 0x7d, 0x1d, 0x8f, 0xff, 0xe6, 0x76, 0x1e, 0x7f, 0x72, 0x37, 0x16, 0xff, 0xcd, 0x9f, 0xcc, 0xe2, 0xbf, + 0x6e, 0xb2, 0xf8, 0xcd, 0x3b, 0xb1, 0xf8, 0x35, 0x89, 0x1f, 0xa4, 0x9a, 0xbe, 0x49, 0x43, 0xfb, 0x0d, 0xd8, 0x30, + 0x46, 0xc9, 0x64, 0x02, 0x45, 0x93, 0x89, 0xad, 0x92, 0x1d, 0x81, 0x13, 0x51, 0xab, 0xd7, 0xb5, 0x12, 0x6a, 0xf5, + 0xd5, 0x57, 0x66, 0x99, 0x59, 0x20, 0xfd, 0x0d, 0xa6, 0x7c, 0x57, 0x35, 0x52, 0x65, 0x56, 0x9f, 0x06, 0x19, 0xc7, + 0x05, 0x9e, 0x26, 0x2c, 0x28, 0x79, 0x76, 0x7a, 0x9a, 0x30, 0xfd, 0xed, 0x33, 0xd5, 0xd2, 0x7c, 0x33, 0xe7, 0x33, + 0xcb, 0x07, 0x26, 0xb4, 0x41, 0x0d, 0xd0, 0x9e, 0x70, 0x64, 0xd2, 0xe7, 0xa0, 0x45, 0xd8, 0xfa, 0x4c, 0x7e, 0x37, + 0x98, 0xfc, 0xa9, 0x4b, 0xc9, 0x7e, 0x65, 0x40, 0xb3, 0xea, 0x8a, 0x2e, 0x4c, 0x91, 0x02, 0x32, 0x2e, 0x95, 0xdb, + 0x12, 0xa0, 0x9d, 0xe3, 0x47, 0x4e, 0x74, 0xca, 0xd2, 0xca, 0x37, 0x85, 0x34, 0x9b, 0xc0, 0x8f, 0x1e, 0x88, 0x29, + 0xc4, 0x67, 0x02, 0xf5, 0xb8, 0x22, 0x0e, 0xe8, 0xd4, 0xd6, 0x68, 0xac, 0x2a, 0x0c, 0xcd, 0xa5, 0xa8, 0x9c, 0x93, + 0xd5, 0x79, 0xd6, 0x8a, 0xe6, 0xeb, 0x85, 0xf2, 0xdd, 0xa6, 0xbb, 0x45, 0x34, 0x14, 0xe7, 0x76, 0x5f, 0xdb, 0x98, + 0x35, 0x9a, 0x29, 0xeb, 0x5e, 0x38, 0x9a, 0xe8, 0x24, 0xbb, 0xa8, 0xdb, 0x48, 0x26, 0x0c, 0x68, 0x3e, 0xe9, 0xbd, + 0x57, 0x75, 0xaa, 0xa0, 0x34, 0xbd, 0xa2, 0x22, 0xd3, 0x8b, 0x48, 0x83, 0x7c, 0x60, 0xb0, 0x03, 0xa9, 0x60, 0xca, + 0x30, 0x0f, 0x71, 0x17, 0x6d, 0x47, 0xa0, 0x32, 0x6d, 0x2b, 0x60, 0x51, 0x3a, 0xe4, 0xe8, 0x6b, 0xc2, 0x0e, 0x7d, + 0xab, 0x06, 0x70, 0xaa, 0x6d, 0xb3, 0xdb, 0x19, 0x3e, 0x98, 0x16, 0xe7, 0xc7, 0x7e, 0x71, 0xee, 0xc1, 0x3f, 0xeb, + 0xf3, 0x25, 0xb0, 0xb0, 0x93, 0x4f, 0x31, 0x07, 0x85, 0x71, 0xde, 0x42, 0xa3, 0x98, 0xdc, 0x3b, 0x92, 0xd7, 0x53, + 0xa8, 0x45, 0x5c, 0x89, 0xe8, 0x2d, 0x0a, 0xb4, 0x40, 0x48, 0xd5, 0x0e, 0xd2, 0x2c, 0x65, 0xbd, 0x7a, 0x48, 0xcd, + 0xd4, 0x76, 0x15, 0xb6, 0x86, 0xcb, 0x0c, 0x2d, 0x16, 0x7e, 0x09, 0x16, 0x8b, 0x90, 0x11, 0x6d, 0x15, 0x8e, 0x69, + 0xaf, 0x6d, 0x1f, 0x48, 0x64, 0x6e, 0x93, 0x28, 0xcc, 0x57, 0x55, 0xfa, 0xeb, 0x54, 0xf2, 0xdb, 0x02, 0x4c, 0xdd, + 0x07, 0x0f, 0x3c, 0xf5, 0xcf, 0x88, 0xcc, 0x35, 0x8b, 0x29, 0xc0, 0x74, 0x17, 0xc8, 0x82, 0x68, 0x82, 0x5f, 0x10, + 0xbb, 0x4b, 0xcb, 0x13, 0xca, 0xee, 0x5a, 0xa2, 0xcc, 0x0a, 0x3a, 0x8f, 0xc1, 0xc6, 0xb8, 0xf3, 0xf0, 0x37, 0x2f, + 0xbf, 0x94, 0x38, 0x52, 0x97, 0xf4, 0x6c, 0xbb, 0x87, 0xa7, 0x39, 0x89, 0x2e, 0xc1, 0xd4, 0x21, 0x01, 0x7a, 0x82, + 0xce, 0xae, 0xde, 0x3c, 0x93, 0x91, 0xd2, 0x9c, 0x25, 0xf4, 0x99, 0x7e, 0xb9, 0x15, 0xbb, 0x0f, 0xe7, 0x17, 0x6a, + 0x37, 0x3a, 0x8d, 0x08, 0xe8, 0x9f, 0x1a, 0xe8, 0xbc, 0x3e, 0xb2, 0x5a, 0x0f, 0xd6, 0x3d, 0x00, 0x18, 0x84, 0xd4, + 0x6e, 0xe5, 0x02, 0xaa, 0x36, 0x94, 0x18, 0xa1, 0xde, 0x6a, 0x20, 0xcb, 0xdf, 0x05, 0x09, 0x11, 0x81, 0xbd, 0x8b, + 0x9f, 0x72, 0x8b, 0xc1, 0xa0, 0x92, 0x9a, 0xc1, 0x2c, 0x1e, 0x8f, 0x13, 0xd6, 0x53, 0xc2, 0xdf, 0xea, 0x3c, 0xc4, + 0x48, 0xa9, 0xb9, 0x65, 0xf5, 0x5d, 0x31, 0x90, 0xa7, 0xf1, 0x14, 0x9d, 0x80, 0x32, 0x82, 0xdf, 0x63, 0x5b, 0x8b, + 0x4e, 0x19, 0x42, 0x6c, 0x57, 0xc8, 0xa3, 0xe7, 0xfa, 0x5a, 0x1e, 0x80, 0x26, 0x44, 0x1b, 0x0e, 0x46, 0x75, 0x36, + 0x0f, 0x5a, 0xbb, 0xf5, 0x85, 0x60, 0x95, 0x5e, 0x82, 0xb7, 0x66, 0x59, 0x1e, 0xd0, 0x44, 0x4b, 0x7c, 0xf8, 0xc7, + 0xf2, 0x3b, 0xb2, 0x8c, 0x06, 0xc0, 0x6f, 0x7e, 0xe9, 0xa2, 0xb2, 0xbe, 0x98, 0xff, 0x3f, 0xa7, 0xe5, 0x8b, 0xf5, + 0xa7, 0xe5, 0x0b, 0x75, 0x5a, 0x6e, 0xa6, 0xd8, 0xcf, 0x26, 0x1d, 0xfc, 0xd3, 0xab, 0x16, 0x04, 0xbb, 0x02, 0xe8, + 0xb0, 0x50, 0xe9, 0x6b, 0x75, 0xe1, 0x3f, 0x1a, 0xba, 0xed, 0xe1, 0x1f, 0x1f, 0xd4, 0x9b, 0xb6, 0x85, 0x85, 0xf8, + 0xaf, 0x5d, 0xab, 0xea, 0xdc, 0xc7, 0x3a, 0xec, 0xf5, 0x60, 0xb5, 0xae, 0x7b, 0xf3, 0xa1, 0x05, 0x7e, 0xc5, 0x9d, + 0x40, 0x31, 0x63, 0xb0, 0x43, 0xa2, 0x93, 0x13, 0x28, 0x9d, 0x64, 0xa3, 0x45, 0xf1, 0x8f, 0x12, 0x7e, 0x89, 0xc4, + 0x1b, 0x8f, 0x74, 0x63, 0x1c, 0xd5, 0x55, 0x84, 0xdd, 0xd5, 0x08, 0x4b, 0xbd, 0x4f, 0x41, 0x01, 0x84, 0xc9, 0x9c, + 0xae, 0x7f, 0x7f, 0xcd, 0x21, 0xf8, 0xbb, 0xec, 0xcd, 0xda, 0xc5, 0xfc, 0x7b, 0x91, 0x71, 0x23, 0x12, 0x7e, 0x17, + 0x0e, 0xcc, 0x3d, 0x6c, 0x3f, 0x5e, 0x0f, 0xee, 0x91, 0x9a, 0x69, 0xa8, 0x84, 0x82, 0x94, 0x3b, 0xa0, 0xe2, 0x46, + 0x8b, 0x84, 0xdf, 0x3c, 0xea, 0x75, 0x94, 0xb1, 0x32, 0xea, 0x0d, 0x0c, 0xbd, 0x6a, 0x7b, 0x47, 0x2e, 0xfd, 0xd9, + 0x17, 0xf7, 0xf1, 0x8f, 0xf0, 0xea, 0x9c, 0x54, 0x8a, 0xbf, 0x30, 0x7c, 0x51, 0xf1, 0xdf, 0xac, 0x69, 0xf6, 0x42, + 0x82, 0x93, 0x72, 0x7f, 0xd7, 0xd6, 0xa8, 0xcf, 0xde, 0xa9, 0xb9, 0xd4, 0x83, 0x7e, 0x57, 0xeb, 0xdf, 0x37, 0xf8, + 0x1d, 0xdb, 0x8e, 0x84, 0xce, 0x5c, 0x6f, 0x2b, 0x7f, 0x65, 0xc2, 0x6a, 0x63, 0x81, 0xe7, 0xbb, 0x36, 0x57, 0x1b, + 0x44, 0xed, 0x37, 0xc3, 0x13, 0x6d, 0x1e, 0xc9, 0xb0, 0x1b, 0xb6, 0x17, 0x16, 0xd2, 0xb7, 0x2c, 0xbc, 0x87, 0x9f, + 0x1a, 0xb2, 0x2e, 0x66, 0x49, 0x0a, 0x3a, 0xd5, 0x94, 0xf3, 0x79, 0xb0, 0xb3, 0x73, 0x7e, 0x7e, 0xee, 0x9f, 0xef, + 0xfa, 0x59, 0x7e, 0xba, 0xd3, 0x6d, 0xb7, 0xdb, 0xf8, 0x85, 0x18, 0xdb, 0x3a, 0x8b, 0xd9, 0xf9, 0x97, 0xd9, 0x45, + 0x68, 0x3f, 0xb2, 0x1e, 0x5b, 0x8f, 0x76, 0xad, 0x07, 0x0f, 0x6d, 0x8b, 0xb8, 0x3f, 0x94, 0xec, 0xda, 0x96, 0xe0, + 0xfe, 0xa1, 0x0d, 0xc5, 0xfd, 0xbd, 0x53, 0xa5, 0xc0, 0x61, 0x06, 0xae, 0x50, 0x8f, 0xc0, 0x66, 0xc9, 0x3e, 0xb1, + 0xfa, 0x39, 0x17, 0x65, 0x2d, 0x29, 0x43, 0xd4, 0x2b, 0x1e, 0xf6, 0x51, 0x34, 0x0f, 0x88, 0x86, 0xcc, 0x42, 0x74, + 0x00, 0x89, 0x52, 0x9a, 0x02, 0xa3, 0xba, 0x27, 0xf0, 0x04, 0x1a, 0xfb, 0xd4, 0x82, 0xe7, 0x57, 0xdd, 0x47, 0x20, + 0xe0, 0xce, 0x5a, 0xf7, 0x47, 0xed, 0x56, 0xc7, 0xea, 0xb4, 0xba, 0xfe, 0x23, 0xab, 0x2b, 0xfe, 0x07, 0x06, 0xb9, + 0x6b, 0x75, 0xe0, 0x69, 0xd7, 0x82, 0xf7, 0xb3, 0xfb, 0x22, 0x24, 0x1c, 0xd9, 0x3b, 0xfd, 0x3d, 0xfc, 0x85, 0x29, + 0xb0, 0xa8, 0x2f, 0x6c, 0xf1, 0x2b, 0x9e, 0xec, 0xcf, 0xcc, 0xd2, 0xce, 0xe3, 0xb5, 0xc5, 0xdd, 0x47, 0x6b, 0x8b, + 0x77, 0x1f, 0xae, 0x2d, 0xbe, 0xff, 0xa0, 0x5e, 0xbc, 0x73, 0x2a, 0xaa, 0x34, 0x53, 0x08, 0xed, 0x59, 0x04, 0x54, + 0x72, 0xe1, 0x74, 0x00, 0xce, 0xb6, 0xd5, 0xc2, 0x1f, 0x8f, 0xba, 0xae, 0xee, 0x75, 0x82, 0xbd, 0xf4, 0x2a, 0x1f, + 0x3d, 0x86, 0x55, 0x3e, 0xef, 0x3e, 0x1c, 0x61, 0x3b, 0x5a, 0x28, 0xfc, 0x3b, 0xdb, 0x7d, 0x3c, 0x02, 0x71, 0x60, + 0xe1, 0x3f, 0xf8, 0x33, 0x7d, 0xd0, 0x1d, 0x89, 0x97, 0x36, 0xd6, 0x7f, 0xe8, 0x3c, 0x2a, 0xa0, 0x29, 0xfe, 0xf9, + 0x4d, 0xeb, 0xcf, 0xa8, 0xbe, 0x9b, 0xe3, 0xde, 0x07, 0x1c, 0x3d, 0x9e, 0x76, 0xfd, 0x2f, 0xce, 0x1e, 0xf9, 0x8f, + 0xa7, 0x9d, 0x47, 0x1f, 0xc4, 0x5b, 0x02, 0x18, 0xfc, 0x02, 0xff, 0x7d, 0xd8, 0x6d, 0x83, 0x69, 0xeb, 0x3f, 0x3e, + 0xdb, 0xf5, 0x77, 0x93, 0xd6, 0x43, 0xff, 0x31, 0xfe, 0xab, 0x86, 0x9b, 0x66, 0x33, 0x66, 0x5b, 0xb8, 0xdf, 0x0d, + 0xbb, 0xd0, 0x9c, 0xa3, 0x7b, 0xdf, 0x7a, 0x70, 0xff, 0xf9, 0x63, 0xd8, 0xa3, 0x69, 0xa7, 0x0b, 0xff, 0x5f, 0xf7, + 0xf8, 0x01, 0x11, 0x2f, 0x07, 0x8e, 0x18, 0xe6, 0xce, 0x29, 0xc4, 0xd1, 0xd7, 0x8a, 0xee, 0x79, 0x3f, 0x5e, 0x67, + 0xda, 0xff, 0x70, 0xbb, 0x69, 0xff, 0xd7, 0x3b, 0xba, 0x6f, 0x7f, 0xf8, 0x93, 0x6d, 0xfb, 0x1f, 0x9b, 0xb6, 0xfd, + 0x39, 0x5b, 0x31, 0xee, 0x9b, 0xf6, 0xd9, 0x21, 0x73, 0x8e, 0xbe, 0x65, 0x43, 0xcc, 0x13, 0x85, 0xd6, 0x7f, 0xad, + 0x79, 0x3a, 0x32, 0x3c, 0xc8, 0xe7, 0x4c, 0x9c, 0xe4, 0xef, 0xaf, 0x43, 0x08, 0xe3, 0xb7, 0x22, 0xe4, 0xc5, 0xdd, + 0xf0, 0x41, 0x9f, 0x16, 0xff, 0x13, 0xf1, 0xf1, 0xbd, 0x89, 0x8f, 0x9a, 0x2f, 0x99, 0x8c, 0x79, 0xb2, 0xc1, 0x0f, + 0xe8, 0xdd, 0xb1, 0x77, 0x18, 0xbe, 0x15, 0xb6, 0x48, 0x7e, 0x7a, 0xf7, 0x7b, 0xfc, 0xde, 0x22, 0x8d, 0x32, 0xb4, + 0xa5, 0x83, 0x62, 0x8e, 0x1f, 0xe3, 0x54, 0x2f, 0x67, 0x22, 0x55, 0x3f, 0xa4, 0x7b, 0x36, 0xf7, 0xb5, 0x73, 0x03, + 0x33, 0x5b, 0xc3, 0x65, 0xc6, 0x23, 0xfc, 0x6d, 0x2f, 0x3c, 0xe6, 0x09, 0xde, 0x02, 0x94, 0x37, 0x66, 0x30, 0x11, + 0xf3, 0x5b, 0x4c, 0x22, 0x55, 0xee, 0xef, 0x19, 0x3a, 0x0c, 0x5e, 0xb1, 0x71, 0x1c, 0x39, 0xb6, 0x33, 0x87, 0x13, + 0x0b, 0x63, 0xb6, 0x6a, 0x19, 0x9c, 0x94, 0xbc, 0xe9, 0xda, 0xea, 0x17, 0x8c, 0xe4, 0xf8, 0xc1, 0xa6, 0xf0, 0x48, + 0xba, 0xce, 0x6c, 0xa9, 0xfe, 0xc3, 0xf8, 0xaa, 0x24, 0x47, 0xd6, 0x5d, 0xa9, 0x0c, 0xb6, 0xd0, 0x19, 0x3a, 0x7e, + 0x17, 0x6c, 0x08, 0x2a, 0xc6, 0x0f, 0xe0, 0xfc, 0xe0, 0xb4, 0x76, 0x41, 0xa7, 0x31, 0xba, 0xe9, 0x81, 0x86, 0x2b, + 0x1f, 0xdf, 0x14, 0x7e, 0x83, 0x46, 0xa9, 0xa7, 0x7f, 0xe3, 0x12, 0x50, 0x86, 0xca, 0xf5, 0xff, 0xf2, 0xf2, 0x50, + 0x5e, 0x72, 0xb5, 0xd1, 0x27, 0x49, 0xbe, 0xe8, 0xea, 0x03, 0x3b, 0xdb, 0x20, 0x2e, 0xe8, 0xd7, 0xde, 0x51, 0x50, + 0x16, 0x25, 0x02, 0xe6, 0x98, 0x5a, 0xd2, 0x6c, 0x08, 0x6d, 0x21, 0x0f, 0xc6, 0xec, 0x2c, 0x1e, 0x49, 0xb6, 0xee, + 0x59, 0x32, 0x37, 0xbe, 0x45, 0xab, 0x08, 0x3b, 0x9e, 0x30, 0x9c, 0xe1, 0x05, 0x65, 0x54, 0x98, 0x66, 0x76, 0xff, + 0x5e, 0x4f, 0x43, 0x52, 0x4f, 0xcf, 0xb5, 0xf1, 0x77, 0xf0, 0x1d, 0x81, 0xa1, 0xf6, 0x8f, 0xe1, 0x3d, 0xfc, 0x2d, + 0x7c, 0xf7, 0x86, 0xb6, 0xeb, 0x13, 0x53, 0xbc, 0x57, 0xfd, 0x2a, 0x3e, 0xe4, 0x08, 0xdb, 0x20, 0xbf, 0xbc, 0xbb, + 0x0a, 0x32, 0x29, 0xb4, 0xba, 0x0f, 0x2a, 0xa1, 0x05, 0xcf, 0x06, 0x97, 0x02, 0x06, 0xda, 0xf5, 0x1f, 0x18, 0xac, + 0xf0, 0xac, 0x85, 0x3f, 0x6b, 0xcc, 0xf0, 0x3e, 0x34, 0x50, 0xdc, 0xf0, 0x25, 0x34, 0xdf, 0x15, 0x8c, 0x17, 0xfa, + 0xfd, 0x48, 0xac, 0x4a, 0xb0, 0xa9, 0x3a, 0xc5, 0xac, 0x09, 0x8f, 0x88, 0x78, 0xb6, 0xed, 0x39, 0xfa, 0xfb, 0xfe, + 0x92, 0x5c, 0xe5, 0xe5, 0xa4, 0xa7, 0xd0, 0xd7, 0xd1, 0xdf, 0xad, 0x5d, 0x57, 0xe7, 0xd5, 0x4e, 0xce, 0x9a, 0x29, + 0x90, 0xe0, 0x1b, 0x21, 0x18, 0xca, 0xd5, 0x16, 0xdf, 0x6f, 0x12, 0xc7, 0xb8, 0xfa, 0xc2, 0xd5, 0x9a, 0x74, 0x43, + 0xf3, 0x50, 0xb0, 0x8a, 0x68, 0xe8, 0x5c, 0x00, 0x23, 0xa0, 0x9f, 0x55, 0xb1, 0x7a, 0x90, 0x04, 0xe5, 0x27, 0x11, + 0xfe, 0xfa, 0x09, 0xfa, 0x51, 0x56, 0x07, 0x90, 0xd3, 0x07, 0xfa, 0x08, 0xd2, 0x17, 0xe3, 0xb2, 0xb9, 0x08, 0xd0, + 0x17, 0xf0, 0xb7, 0x99, 0x55, 0xb9, 0xe1, 0xf2, 0xd2, 0x17, 0x86, 0xc1, 0xc7, 0x71, 0x4e, 0x77, 0x09, 0xd5, 0xfa, + 0x6b, 0xd7, 0xfc, 0x2a, 0x54, 0xd3, 0xa9, 0x64, 0xc5, 0xc0, 0xc6, 0x22, 0x5b, 0x65, 0xe9, 0x98, 0x5f, 0xa8, 0x35, + 0x2f, 0x7b, 0x8d, 0x45, 0x9a, 0x0e, 0x7e, 0xc1, 0xdb, 0x16, 0x48, 0xb6, 0x81, 0x8d, 0x5d, 0xbb, 0x26, 0x52, 0x6e, + 0xf0, 0x8e, 0x54, 0xf5, 0x2b, 0x59, 0xcc, 0x03, 0x6f, 0x9b, 0xbb, 0xa5, 0xc7, 0xa5, 0x7d, 0x70, 0xa5, 0xa7, 0xf0, + 0x84, 0x45, 0xdc, 0x8f, 0x52, 0xca, 0xf7, 0x70, 0x0c, 0xb6, 0xe0, 0x75, 0xd8, 0xae, 0x5b, 0x02, 0xe7, 0x31, 0x7e, + 0x67, 0x8d, 0x40, 0xbd, 0x0f, 0x85, 0x6e, 0xe5, 0xb5, 0x9b, 0x76, 0xfb, 0x6f, 0x0e, 0xf7, 0x2d, 0x71, 0x9a, 0xf7, + 0x76, 0xe0, 0x75, 0x8f, 0x6c, 0x61, 0x91, 0x52, 0x10, 0x8a, 0x94, 0x02, 0x4b, 0x64, 0xc3, 0x84, 0xf6, 0x8e, 0x58, + 0xa6, 0x6d, 0xb1, 0x74, 0x24, 0x3c, 0x78, 0x33, 0xb0, 0x15, 0x62, 0xfc, 0x8a, 0xd1, 0x0e, 0x76, 0x6b, 0xe1, 0x4e, + 0xc3, 0x11, 0x10, 0x3e, 0x3e, 0xa5, 0x20, 0xf0, 0xd4, 0x96, 0xfe, 0x3e, 0x10, 0xeb, 0x4c, 0x65, 0x62, 0xc8, 0xa1, + 0x74, 0x5e, 0xde, 0x6a, 0xeb, 0x62, 0x71, 0x32, 0x03, 0x3e, 0xa4, 0x92, 0x29, 0xde, 0xcb, 0x0e, 0x7b, 0x34, 0x15, + 0x66, 0x01, 0xae, 0x3a, 0x21, 0xa7, 0x9d, 0xfe, 0x5e, 0x24, 0xf5, 0x1d, 0x3c, 0xbb, 0x05, 0x1c, 0x5e, 0x10, 0x73, + 0xa8, 0x54, 0xf8, 0x71, 0xb6, 0x73, 0xce, 0x4e, 0x5a, 0xd1, 0x3c, 0xae, 0x7c, 0x7f, 0x28, 0xfd, 0xfa, 0x7b, 0x4a, + 0x10, 0xca, 0x84, 0x33, 0xf9, 0x18, 0x19, 0x89, 0x07, 0x88, 0x38, 0x22, 0xd0, 0x52, 0x3a, 0x16, 0x49, 0x69, 0x04, + 0xe4, 0x03, 0xac, 0x44, 0xbf, 0xca, 0x01, 0x29, 0x25, 0x41, 0x69, 0xf7, 0xff, 0xf6, 0xbf, 0xfe, 0xb7, 0xf4, 0x29, + 0x02, 0x5a, 0x01, 0x2c, 0xcc, 0xdc, 0xa8, 0x62, 0x67, 0xec, 0x02, 0xac, 0xd0, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04, + 0x20, 0x28, 0x98, 0xb8, 0xbb, 0x21, 0xeb, 0x81, 0x0a, 0x24, 0x58, 0x66, 0xd8, 0x59, 0x82, 0x57, 0x2f, 0xc2, 0x1d, + 0xfb, 0x43, 0x19, 0x7c, 0x2a, 0xb7, 0x94, 0x08, 0xda, 0xc8, 0xe7, 0x33, 0x68, 0xae, 0x96, 0xd3, 0xa7, 0x7e, 0x23, + 0x8c, 0x64, 0x1e, 0xac, 0x96, 0xd0, 0x07, 0x2d, 0x75, 0xa0, 0xe0, 0xdf, 0xfe, 0xf5, 0x3f, 0xff, 0x77, 0xf5, 0x8a, + 0xfe, 0xff, 0xbf, 0xfd, 0xcb, 0x3f, 0xfd, 0xdf, 0xff, 0xf3, 0x5f, 0x30, 0x39, 0x52, 0xc6, 0x08, 0xe8, 0x28, 0x59, + 0x55, 0x80, 0x40, 0x9c, 0xa9, 0x7a, 0xb6, 0xdf, 0x01, 0xcd, 0x42, 0x04, 0x29, 0x41, 0x22, 0x62, 0xa6, 0x24, 0x50, + 0x42, 0xd5, 0x0d, 0x38, 0x83, 0xfd, 0xb3, 0x28, 0x4a, 0x6d, 0x3f, 0x68, 0xdb, 0xd5, 0x9e, 0xf6, 0x8d, 0xbe, 0x3b, + 0xb8, 0x1b, 0x77, 0xca, 0x14, 0xf1, 0xf5, 0x5e, 0x2d, 0x95, 0xe3, 0x0a, 0x4b, 0xca, 0xaa, 0xdc, 0x42, 0x8f, 0xf2, + 0x12, 0x5f, 0x83, 0xae, 0x51, 0x4c, 0x5b, 0x5b, 0xeb, 0xd3, 0xfb, 0x65, 0x51, 0xf0, 0x78, 0x82, 0xfb, 0x21, 0xdc, + 0x63, 0x14, 0x0a, 0x6c, 0xa1, 0x4a, 0x92, 0x5c, 0x96, 0x34, 0x8a, 0x30, 0x61, 0xee, 0x3f, 0xfe, 0x87, 0xf2, 0x2f, + 0x33, 0x54, 0x05, 0x2c, 0x67, 0x16, 0x5d, 0x48, 0xc3, 0xe6, 0x61, 0xbb, 0x3d, 0xbf, 0x70, 0x97, 0xd5, 0x0c, 0xde, + 0x75, 0x93, 0x91, 0x4b, 0xcd, 0x1c, 0x90, 0x62, 0x88, 0xda, 0x7b, 0x07, 0xba, 0x7c, 0x1b, 0x9d, 0x3d, 0x65, 0xf9, + 0xf9, 0x92, 0x1c, 0x48, 0xf1, 0x6f, 0x18, 0xeb, 0x93, 0xbe, 0x36, 0x28, 0x31, 0x56, 0xb1, 0x34, 0x7a, 0x75, 0x45, + 0xaf, 0x69, 0x67, 0x35, 0xd3, 0xc4, 0x8c, 0x55, 0x9a, 0x51, 0x46, 0xcc, 0xc3, 0x80, 0x0e, 0xde, 0xb4, 0xbb, 0xd4, + 0xc3, 0x73, 0x9e, 0xcd, 0xcc, 0xe0, 0x24, 0x8b, 0xd8, 0x88, 0x4d, 0x94, 0x8f, 0x52, 0xd6, 0x8b, 0xc0, 0x63, 0xf9, + 0x19, 0x9e, 0x31, 0xc0, 0x6d, 0x16, 0xf1, 0x80, 0x28, 0xb5, 0x67, 0x86, 0x2f, 0x23, 0x0c, 0x0c, 0x67, 0x4b, 0x63, + 0xae, 0x9e, 0x68, 0x8a, 0x9e, 0xc0, 0x7a, 0x7e, 0x4a, 0xe9, 0x53, 0x77, 0x73, 0x28, 0xe1, 0x48, 0x78, 0x51, 0x65, + 0x87, 0x54, 0x26, 0xf6, 0xbb, 0x9a, 0x39, 0x2e, 0x99, 0x31, 0x18, 0xc1, 0xb7, 0x37, 0x16, 0x52, 0x52, 0x34, 0xfd, + 0x15, 0x94, 0x1f, 0x5a, 0x80, 0xdd, 0x6c, 0x45, 0x85, 0xd8, 0xea, 0x5d, 0xf8, 0x42, 0xab, 0xe2, 0xd1, 0x7c, 0x4e, + 0x0d, 0x5d, 0xa0, 0x53, 0x52, 0xa9, 0x91, 0x71, 0x50, 0x2c, 0x5c, 0x84, 0x9e, 0x65, 0x1b, 0x49, 0xd0, 0xe2, 0x49, + 0x06, 0xa5, 0xe9, 0xf7, 0x0d, 0xff, 0x3f, 0xdf, 0x8d, 0x21, 0x2b, 0x85, 0x78, 0x00, 0x00}; + +} // namespace web_server +} // namespace esphome diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 44d044750e..278aeab937 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -24,11 +24,20 @@ #include "esphome/components/fan/fan_helpers.h" #endif +#ifdef USE_CLIMATE +#include "esphome/components/climate/climate.h" +#endif + +#ifdef USE_WEBSERVER_LOCAL +#include "server_index.h" +#endif + namespace esphome { namespace web_server { static const char *const TAG = "web_server"; +#if USE_WEBSERVER_VERSION == 1 void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action, const std::function &action_func = nullptr) { stream->print("print(""); stream->print(""); } +#endif UrlMatch match_url(const std::string &url, bool only_domain = false) { UrlMatch match; @@ -87,76 +97,94 @@ void WebServer::setup() { this->base_->init(); this->events_.onConnect([this](AsyncEventSourceClient *client) { - // Configure reconnect timeout - client->send("", "ping", millis(), 30000); + // Configure reconnect timeout and send config + + client->send(json::build_json([this](JsonObject root) { + root["title"] = App.get_name(); + root["ota"] = this->allow_ota_; + root["lang"] = "en"; + }).c_str(), + "ping", millis(), 30000); #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->sensor_json(obj, obj->state).c_str(), "state"); + client->send(this->sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_SWITCH for (auto *obj : App.get_switches()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->switch_json(obj, obj->state).c_str(), "state"); + client->send(this->switch_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif +#ifdef USE_BUTTON + for (auto *obj : App.get_buttons()) + client->send(this->button_json(obj, DETAIL_ALL).c_str(), "state"); +#endif + #ifdef USE_BINARY_SENSOR for (auto *obj : App.get_binary_sensors()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state"); + client->send(this->binary_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_FAN for (auto *obj : App.get_fans()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->fan_json(obj).c_str(), "state"); + client->send(this->fan_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_LIGHT for (auto *obj : App.get_lights()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->light_json(obj).c_str(), "state"); + client->send(this->light_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_TEXT_SENSOR for (auto *obj : App.get_text_sensors()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->text_sensor_json(obj, obj->state).c_str(), "state"); + client->send(this->text_sensor_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_COVER for (auto *obj : App.get_covers()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->cover_json(obj).c_str(), "state"); + client->send(this->cover_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_NUMBER for (auto *obj : App.get_numbers()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->number_json(obj, obj->state).c_str(), "state"); + client->send(this->number_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_SELECT for (auto *obj : App.get_selects()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->select_json(obj, obj->state).c_str(), "state"); + client->send(this->select_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); + } +#endif + +#ifdef USE_CLIMATE + for (auto *obj : App.get_climates()) { + if (this->include_internal_ || !obj->is_internal()) + client->send(this->climate_json(obj, DETAIL_ALL).c_str(), "state"); } #endif #ifdef USE_LOCK for (auto *obj : App.get_locks()) { if (this->include_internal_ || !obj->is_internal()) - client->send(this->lock_json(obj, obj->state).c_str(), "state"); + client->send(this->lock_json(obj, obj->state, DETAIL_ALL).c_str(), "state"); } #endif }); @@ -181,15 +209,27 @@ void WebServer::dump_config() { } float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; } +#ifdef USE_WEBSERVER_LOCAL +void WebServer::handle_index_request(AsyncWebServerRequest *request) { + AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ)); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); +} +#else void WebServer::handle_index_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/html"); - std::string title = App.get_name() + " Web Server"; - stream->print(F("" - "")); + // 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("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta " + "name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>")); stream->print(title.c_str()); stream->print(F("")); -#ifdef WEBSERVER_CSS_INCLUDE +#else + stream->print(F("")); +#endif +#ifdef USE_WEBSERVER_CSS_INCLUDE stream->print(F("")); #endif if (strlen(this->css_url_) > 0) { @@ -197,11 +237,12 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->print(this->css_url_); stream->print(F("\">")); } - stream->print(F("

")); + stream->print(F("")); +#if USE_WEBSERVER_VERSION == 1 + stream->print(F("

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

States

")); - // All content is controlled and created by user - so allowing all origins is fine here. - stream->addHeader("Access-Control-Allow-Origin", "*"); + stream->print(F("")); + stream->print(F("

States

NameStateActions
")); #ifdef USE_SENSOR for (auto *obj : App.get_sensors()) { @@ -308,6 +349,13 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { } #endif +#ifdef USE_CLIMATE + for (auto *obj : App.get_climates()) { + if (this->include_internal_ || !obj->is_internal()) + write_row(stream, obj, "climate", ""); + } +#endif + stream->print(F("
NameStateActions

See ESPHome Web API for " "REST API documentation.

")); if (this->allow_ota_) { @@ -316,23 +364,30 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { "type=\"file\" name=\"update\">")); } stream->print(F("

Debug Log

"));
-
-#ifdef WEBSERVER_JS_INCLUDE
+#endif
+#ifdef USE_WEBSERVER_JS_INCLUDE
   if (this->js_include_ != nullptr) {
-    stream->print(F(""));
+    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); } - -#ifdef WEBSERVER_CSS_INCLUDE +#endif +#ifdef USE_WEBSERVER_CSS_INCLUDE void WebServer::handle_css_request(AsyncWebServerRequest *request) { AsyncResponseStream *stream = request->beginResponseStream("text/css"); if (this->css_include_ != nullptr) { @@ -343,10 +398,11 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) { } #endif -#ifdef WEBSERVER_JS_INCLUDE +#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_); } @@ -354,64 +410,75 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) { } #endif +#define set_json_id(root, obj, sensor, start_config) \ + (root)["id"] = sensor; \ + if (((start_config) == DETAIL_ALL)) \ + (root)["name"] = (obj)->get_name(); + +#define set_json_value(root, obj, sensor, value, start_config) \ + set_json_id((root), (obj), sensor, start_config)(root)["value"] = value; + +#define set_json_state_value(root, obj, sensor, state, value, start_config) \ + set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; + +#define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \ + set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; \ + if (((start_config) == DETAIL_ALL)) \ + (root)["icon"] = (obj)->get_icon(); + #ifdef USE_SENSOR void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { - this->events_.send(this->sensor_json(obj, state).c_str(), "state"); + this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (sensor::Sensor *obj : App.get_sensors()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->sensor_json(obj, obj->state); + std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } request->send(404); } -std::string WebServer::sensor_json(sensor::Sensor *obj, float value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "sensor-" + obj->get_object_id(); +std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals()); if (!obj->get_unit_of_measurement().empty()) state += " " + obj->get_unit_of_measurement(); - root["state"] = state; - root["value"] = value; + set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config); }); } #endif #ifdef USE_TEXT_SENSOR void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { - this->events_.send(this->text_sensor_json(obj, state).c_str(), "state"); + this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (text_sensor::TextSensor *obj : App.get_text_sensors()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->text_sensor_json(obj, obj->state); + std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } request->send(404); } -std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "text_sensor-" + obj->get_object_id(); - root["state"] = value; - root["value"] = value; +std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, + JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config); }); } #endif #ifdef USE_SWITCH void WebServer::on_switch_update(switch_::Switch *obj, bool state) { - this->events_.send(this->switch_json(obj, state).c_str(), "state"); + this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::switch_json(switch_::Switch *obj, bool value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "switch-" + obj->get_object_id(); - root["state"] = value ? "ON" : "OFF"; - root["value"] = value; +std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); }); } void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -420,7 +487,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET) { - std::string data = this->switch_json(obj, obj->state); + std::string data = this->switch_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle(); }); @@ -441,14 +508,19 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM #endif #ifdef USE_BUTTON +std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) { + return json::build_json( + [obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); }); +} + void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (button::Button *obj : App.get_buttons()) { if (obj->get_object_id() != match.id) continue; - if (request->method() == HTTP_POST && match.method == "press") { this->defer([obj]() { obj->press(); }); request->send(200); + return; } else { request->send(404); } @@ -460,20 +532,18 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM #ifdef USE_BINARY_SENSOR void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { - this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state"); + this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "binary_sensor-" + obj->get_object_id(); - root["state"] = value ? "ON" : "OFF"; - root["value"] = value; +std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config); }); } void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) { if (obj->get_object_id() != match.id) continue; - std::string data = this->binary_sensor_json(obj, obj->state); + std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } @@ -482,15 +552,15 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } -std::string WebServer::fan_json(fan::Fan *obj) { - return json::build_json([obj](JsonObject root) { - root["id"] = "fan-" + obj->get_object_id(); - root["state"] = obj->state ? "ON" : "OFF"; - root["value"] = obj->state; +void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); } +std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config); const auto traits = obj->get_traits(); if (traits.supports_speed()) { root["speed_level"] = obj->speed; + root["speed_count"] = traits.supported_speed_count(); + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations) @@ -517,7 +587,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc continue; if (request->method() == HTTP_GET) { - std::string data = this->fan_json(obj); + std::string data = this->fan_json(obj, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle().perform(); }); @@ -573,14 +643,16 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc #endif #ifdef USE_LIGHT -void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); } +void WebServer::on_light_update(light::LightState *obj) { + this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state"); +} void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (light::LightState *obj : App.get_lights()) { if (obj->get_object_id() != match.id) continue; if (request->method() == HTTP_GET) { - std::string data = this->light_json(obj); + std::string data = this->light_json(obj, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "toggle") { this->defer([obj]() { obj->toggle().perform(); }); @@ -632,24 +704,34 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa } request->send(404); } -std::string WebServer::light_json(light::LightState *obj) { - return json::build_json([obj](JsonObject root) { - root["id"] = "light-" + obj->get_object_id(); +std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "light-" + obj->get_object_id(), start_config); root["state"] = obj->remote_values.is_on() ? "ON" : "OFF"; + light::LightJSONSchema::dump_json(*obj, root); + if (start_config == DETAIL_ALL) { + JsonArray opt = root.createNestedArray("effects"); + opt.add("None"); + for (auto const &option : obj->get_effects()) { + opt.add(option->get_name()); + } + } }); } #endif #ifdef USE_COVER -void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); } +void WebServer::on_cover_update(cover::Cover *obj) { + this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state"); +} void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (cover::Cover *obj : App.get_covers()) { if (obj->get_object_id() != match.id) continue; if (request->method() == HTTP_GET) { - std::string data = this->cover_json(obj); + std::string data = this->cover_json(obj, DETAIL_STATE); request->send(200, "text/json", data.c_str()); continue; } @@ -684,11 +766,10 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa } request->send(404); } -std::string WebServer::cover_json(cover::Cover *obj) { - return json::build_json([obj](JsonObject root) { - root["id"] = "cover-" + obj->get_object_id(); - root["state"] = obj->is_fully_closed() ? "CLOSED" : "OPEN"; - root["value"] = obj->position; +std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN", + obj->position, start_config); root["current_operation"] = cover::cover_operation_to_str(obj->current_operation); if (obj->get_traits().get_supports_tilt()) @@ -699,7 +780,7 @@ std::string WebServer::cover_json(cover::Cover *obj) { #ifdef USE_NUMBER void WebServer::on_number_update(number::Number *obj, float state) { - this->events_.send(this->number_json(obj, state).c_str(), "state"); + this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_numbers()) { @@ -707,18 +788,16 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET) { - std::string data = this->number_json(obj, obj->state); + std::string data = this->number_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } - if (match.method != "set") { request->send(404); return; } auto call = obj->make_call(); - if (request->hasParam("value")) { String value = request->getParam("value")->value(); optional value_f = parse_number(value.c_str()); @@ -732,19 +811,30 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } -std::string WebServer::number_json(number::Number *obj, float value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "number-" + obj->get_object_id(); + +std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_id(root, obj, "number-" + obj->get_object_id(), start_config); + if (start_config == DETAIL_ALL) { + root["min_value"] = obj->traits.get_min_value(); + root["max_value"] = obj->traits.get_max_value(); + root["step"] = obj->traits.get_step(); + root["mode"] = (int) obj->traits.get_mode(); + } std::string state = str_sprintf("%f", value); root["state"] = state; - root["value"] = value; + if (isnan(value)) { + root["value"] = "\"NaN\""; + } else { + root["value"] = value; + } }); } #endif #ifdef USE_SELECT void WebServer::on_select_update(select::Select *obj, const std::string &state) { - this->events_.send(this->select_json(obj, state).c_str(), "state"); + this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state"); } void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { for (auto *obj : App.get_selects()) { @@ -752,7 +842,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM continue; if (request->method() == HTTP_GET) { - std::string data = this->select_json(obj, obj->state); + std::string data = this->select_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); return; } @@ -775,24 +865,160 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM } request->send(404); } -std::string WebServer::select_json(select::Select *obj, const std::string &value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "select-" + obj->get_object_id(); - root["state"] = value; - root["value"] = value; +std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config); + if (start_config == DETAIL_ALL) { + JsonArray opt = root.createNestedArray("option"); + for (auto &option : obj->traits.get_options()) { + opt.add(option); + } + } + }); +} +#endif + +#ifdef USE_CLIMATE +void WebServer::on_climate_update(climate::Climate *obj) { + this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state"); +} + +void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (auto *obj : App.get_climates()) { + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_GET) { + std::string data = this->climate_json(obj, DETAIL_STATE); + request->send(200, "text/json", data.c_str()); + return; + } + + if (match.method != "set") { + request->send(404); + return; + } + + auto call = obj->make_call(); + + if (request->hasParam("mode")) { + String mode = request->getParam("mode")->value(); + call.set_mode(mode.c_str()); + } + + if (request->hasParam("target_temperature_high")) { + String value = request->getParam("target_temperature_high")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_target_temperature_high(*value_f); + } + + if (request->hasParam("target_temperature_low")) { + String value = request->getParam("target_temperature_low")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_target_temperature_low(*value_f); + } + + if (request->hasParam("target_temperature")) { + String value = request->getParam("target_temperature")->value(); + optional value_f = parse_number(value.c_str()); + if (value_f.has_value()) + call.set_target_temperature(*value_f); + } + + this->defer([call]() mutable { call.perform(); }); + request->send(200); + return; + } + request->send(404); +} + +// Longest: HORIZONTAL +#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P)((mode_s)), 15) + +std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) { + return json::build_json([obj, start_config](JsonObject root) { + set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config); + const auto traits = obj->get_traits(); + char __buf[16]; + + if (start_config == DETAIL_ALL) { + JsonArray opt = root.createNestedArray("modes"); + for (climate::ClimateMode m : traits.get_supported_modes()) + opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m))); + if (!traits.get_supported_custom_fan_modes().empty()) { + JsonArray opt = root.createNestedArray("fan_modes"); + for (climate::ClimateFanMode m : traits.get_supported_fan_modes()) + opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m))); + } + + if (!traits.get_supported_custom_fan_modes().empty()) { + JsonArray opt = root.createNestedArray("custom_fan_modes"); + for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes()) + opt.add(custom_fan_mode); + } + if (traits.get_supports_swing_modes()) { + JsonArray opt = root.createNestedArray("swing_modes"); + for (auto swing_mode : traits.get_supported_swing_modes()) + opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode))); + } + if (traits.get_supports_presets() && obj->preset.has_value()) { + JsonArray opt = root.createNestedArray("presets"); + for (climate::ClimatePreset m : traits.get_supported_presets()) + opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m))); + } + if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { + JsonArray opt = root.createNestedArray("custom_presets"); + for (auto const &custom_preset : traits.get_supported_custom_presets()) + opt.add(custom_preset); + } + } + + root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode)); + root["max_temp"] = traits.get_visual_max_temperature(); + root["min_temp"] = traits.get_visual_min_temperature(); + root["step"] = traits.get_visual_temperature_step(); + if (traits.get_supports_action()) { + root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action)); + } + if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) { + root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value())); + } + if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) { + root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str(); + } + if (traits.get_supports_presets() && obj->preset.has_value()) { + root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value())); + } + if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) { + root["custom_preset"] = obj->custom_preset.value().c_str(); + } + if (traits.get_supports_swing_modes()) { + root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode)); + } + if (traits.get_supports_current_temperature()) { + root["current_temperature"] = obj->current_temperature; + } + if (traits.get_supports_two_point_target_temperature()) { + root["current_temperature_low"] = obj->target_temperature_low; + root["current_temperature_high"] = obj->target_temperature_low; + } else { + root["target_temperature"] = obj->target_temperature; + root["state"] = obj->target_temperature; + } }); } #endif #ifdef USE_LOCK void WebServer::on_lock_update(lock::Lock *obj) { - this->events_.send(this->lock_json(obj, obj->state).c_str(), "state"); + this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state"); } -std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) { - return json::build_json([obj, value](JsonObject root) { - root["id"] = "lock-" + obj->get_object_id(); - root["state"] = lock::lock_state_to_string(value); - root["value"] = value; +std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { + return json::build_json([obj, value, start_config](JsonObject root) { + set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value, + start_config); }); } void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { @@ -801,7 +1027,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat continue; if (request->method() == HTTP_GET) { - std::string data = this->lock_json(obj, obj->state); + std::string data = this->lock_json(obj, obj->state, DETAIL_STATE); request->send(200, "text/json", data.c_str()); } else if (match.method == "lock") { this->defer([obj]() { obj->lock(); }); @@ -825,12 +1051,12 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; -#ifdef WEBSERVER_CSS_INCLUDE +#ifdef USE_WEBSERVER_CSS_INCLUDE if (request->url() == "/0.css") return true; #endif -#ifdef WEBSERVER_JS_INCLUDE +#ifdef USE_WEBSERVER_JS_INCLUDE if (request->url() == "/0.js") return true; #endif @@ -888,6 +1114,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_CLIMATE + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate") + return true; +#endif + #ifdef USE_LOCK if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock") return true; @@ -901,14 +1132,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } -#ifdef WEBSERVER_CSS_INCLUDE +#ifdef USE_WEBSERVER_CSS_INCLUDE if (request->url() == "/0.css") { this->handle_css_request(request); return; } #endif -#ifdef WEBSERVER_JS_INCLUDE +#ifdef USE_WEBSERVER_JS_INCLUDE if (request->url() == "/0.js") { this->handle_js_request(request); return; @@ -986,9 +1217,17 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { } #endif +#ifdef USE_CLIMATE + if (match.domain == "climate") { + this->handle_climate_request(request, match); + return; + } +#endif + #ifdef USE_LOCK if (match.domain == "lock") { this->handle_lock_request(request, match); + return; } #endif diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 3dd5c93f59..2717997f60 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -2,9 +2,9 @@ #ifdef USE_ARDUINO +#include "esphome/components/web_server_base/web_server_base.h" #include "esphome/core/component.h" #include "esphome/core/controller.h" -#include "esphome/components/web_server_base/web_server_base.h" #include @@ -19,6 +19,8 @@ struct UrlMatch { bool valid; ///< Whether this match is valid }; +enum JsonDetail { DETAIL_ALL, DETAIL_STATE }; + /** This class allows users to create a web server with their ESP nodes. * * Behind the scenes it's using AsyncWebServer to set up the server. It exposes 3 things: @@ -99,7 +101,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the sensor state with its value as a JSON string. - std::string sensor_json(sensor::Sensor *obj, float value); + std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config); #endif #ifdef USE_SWITCH @@ -109,12 +111,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the switch state with its value as a JSON string. - std::string switch_json(switch_::Switch *obj, bool value); + std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config); #endif #ifdef USE_BUTTON /// Handle a button request under '/button//press'. void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the button details with its value as a JSON string. + std::string button_json(button::Button *obj, JsonDetail start_config); #endif #ifdef USE_BINARY_SENSOR @@ -124,7 +129,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the binary sensor state with its value as a JSON string. - std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value); + std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config); #endif #ifdef USE_FAN @@ -134,7 +139,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the fan state as a JSON string. - std::string fan_json(fan::Fan *obj); + std::string fan_json(fan::Fan *obj, JsonDetail start_config); #endif #ifdef USE_LIGHT @@ -144,7 +149,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the light state as a JSON string. - std::string light_json(light::LightState *obj); + std::string light_json(light::LightState *obj, JsonDetail start_config); #endif #ifdef USE_TEXT_SENSOR @@ -154,7 +159,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the text sensor state with its value as a JSON string. - std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value); + std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config); #endif #ifdef USE_COVER @@ -164,7 +169,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the cover state as a JSON string. - std::string cover_json(cover::Cover *obj); + std::string cover_json(cover::Cover *obj, JsonDetail start_config); #endif #ifdef USE_NUMBER @@ -173,7 +178,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the number state with its value as a JSON string. - std::string number_json(number::Number *obj, float value); + std::string number_json(number::Number *obj, float value, JsonDetail start_config); #endif #ifdef USE_SELECT @@ -181,8 +186,17 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { /// Handle a select request under '/select/'. void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match); - /// Dump the number state with its value as a JSON string. - std::string select_json(select::Select *obj, const std::string &value); + /// Dump the select state with its value as a JSON string. + std::string select_json(select::Select *obj, const std::string &value, JsonDetail start_config); +#endif + +#ifdef USE_CLIMATE + void on_climate_update(climate::Climate *obj) override; + /// Handle a climate request under '/climate/'. + void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match); + + /// Dump the climate details + std::string climate_json(climate::Climate *obj, JsonDetail start_config); #endif #ifdef USE_LOCK @@ -192,7 +206,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the lock state with its value as a JSON string. - std::string lock_json(lock::Lock *obj, lock::LockState value); + std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config); #endif /// Override the web handler's canHandle method. diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py index a24791b458..20f43cb450 100644 --- a/esphome/components/wifi/__init__.py +++ b/esphome/components/wifi/__init__.py @@ -225,11 +225,11 @@ def _validate(config): if CONF_MANUAL_IP in config: use_address = str(config[CONF_MANUAL_IP][CONF_STATIC_IP]) elif CONF_NETWORKS in config: - ips = set( + ips = { str(net[CONF_MANUAL_IP][CONF_STATIC_IP]) for net in config[CONF_NETWORKS] if CONF_MANUAL_IP in net - ) + } if len(ips) > 1: raise cv.Invalid( "Must specify use_address when using multiple static IP addresses." diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 58250c3759..54993d48ee 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -32,19 +32,19 @@ MacAddressWifiInfo = wifi_info_ns.class_( CONFIG_SCHEMA = cv.Schema( { cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( - klass=IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema( - klass=ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("60s")), cv.Optional(CONF_SSID): text_sensor.text_sensor_schema( - klass=SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema( - klass=BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ).extend(cv.polling_component_schema("1s")), cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( - klass=MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC + MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), } ) diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py index 2097c21bd7..77fabf272e 100644 --- a/esphome/components/wifi_signal/sensor.py +++ b/esphome/components/wifi_signal/sensor.py @@ -2,7 +2,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor from esphome.const import ( - CONF_ID, DEVICE_CLASS_SIGNAL_STRENGTH, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, @@ -15,24 +14,16 @@ WiFiSignalSensor = wifi_signal_ns.class_( "WiFiSignalSensor", sensor.Sensor, cg.PollingComponent ) -CONFIG_SCHEMA = ( - sensor.sensor_schema( - unit_of_measurement=UNIT_DECIBEL_MILLIWATT, - accuracy_decimals=0, - device_class=DEVICE_CLASS_SIGNAL_STRENGTH, - state_class=STATE_CLASS_MEASUREMENT, - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, - ) - .extend( - { - cv.GenerateID(): cv.declare_id(WiFiSignalSensor), - } - ) - .extend(cv.polling_component_schema("60s")) -) +CONFIG_SCHEMA = sensor.sensor_schema( + WiFiSignalSensor, + unit_of_measurement=UNIT_DECIBEL_MILLIWATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, +).extend(cv.polling_component_schema("60s")) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await sensor.new_sensor(config) await cg.register_component(var, config) - await sensor.register_sensor(var, config) diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py index 7f0aac873d..1b878ca800 100644 --- a/esphome/components/xiaomi_cgpr1/binary_sensor.py +++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py @@ -4,15 +4,11 @@ from esphome.components import sensor, binary_sensor, esp32_ble_tracker from esphome.const import ( CONF_BATTERY_LEVEL, CONF_BINDKEY, - CONF_DEVICE_CLASS, CONF_MAC_ADDRESS, - CONF_ID, - DEVICE_CLASS_EMPTY, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_MOTION, ENTITY_CATEGORY_DIAGNOSTIC, - ICON_EMPTY, UNIT_PERCENT, CONF_IDLE_TIME, CONF_ILLUMINANCE, @@ -33,15 +29,11 @@ XiaomiCGPR1 = xiaomi_cgpr1_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(XiaomiCGPR1, device_class=DEVICE_CLASS_MOTION) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiCGPR1), cv.Required(CONF_BINDKEY): cv.bind_key, cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional( - CONF_DEVICE_CLASS, - default=DEVICE_CLASS_MOTION, - ): binary_sensor.device_class, cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, accuracy_decimals=0, @@ -52,11 +44,12 @@ CONFIG_SCHEMA = cv.All( unit_of_measurement=UNIT_MINUTE, icon=ICON_TIMELAPSE, accuracy_decimals=0, - device_class=DEVICE_CLASS_EMPTY, entity_category=ENTITY_CATEGORY_DIAGNOSTIC, ), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema( - UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE + unit_of_measurement=UNIT_LUX, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ILLUMINANCE, ), } ) @@ -65,21 +58,20 @@ CONFIG_SCHEMA = cv.All( ) -def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield esp32_ble_tracker.register_ble_device(var, config) - yield binary_sensor.register_binary_sensor(var, config) +async def to_code(config): + var = await binary_sensor.new_binary_sensor(config) + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_bindkey(config[CONF_BINDKEY])) if CONF_IDLE_TIME in config: - sens = yield sensor.new_sensor(config[CONF_IDLE_TIME]) + sens = await sensor.new_sensor(config[CONF_IDLE_TIME]) cg.add(var.set_idle_time(sens)) if CONF_BATTERY_LEVEL in config: - sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) cg.add(var.set_battery_level(sens)) if CONF_ILLUMINANCE in config: - sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE]) + sens = await sensor.new_sensor(config[CONF_ILLUMINANCE]) cg.add(var.set_illuminance(sens)) diff --git a/esphome/components/xiaomi_miscale/sensor.py b/esphome/components/xiaomi_miscale/sensor.py index 517870cc01..a2a2f3bdfc 100644 --- a/esphome/components/xiaomi_miscale/sensor.py +++ b/esphome/components/xiaomi_miscale/sensor.py @@ -11,6 +11,7 @@ from esphome.const import ( UNIT_OHM, CONF_IMPEDANCE, ICON_OMEGA, + CONF_CLEAR_IMPEDANCE, ) DEPENDENCIES = ["esp32_ble_tracker"] @@ -25,6 +26,7 @@ CONFIG_SCHEMA = ( { cv.GenerateID(): cv.declare_id(XiaomiMiscale), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_CLEAR_IMPEDANCE, default=False): cv.boolean, cv.Optional(CONF_WEIGHT): sensor.sensor_schema( unit_of_measurement=UNIT_KILOGRAM, icon=ICON_SCALE_BATHROOM, @@ -50,6 +52,7 @@ async def to_code(config): await esp32_ble_tracker.register_ble_device(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + cg.add(var.set_clear_impedance(config[CONF_CLEAR_IMPEDANCE])) if CONF_WEIGHT in config: sens = await sensor.new_sensor(config[CONF_WEIGHT]) diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index 6c3bd61cac..4ed5f587de 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -24,25 +24,30 @@ bool XiaomiMiscale::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { bool success = false; for (auto &service_data : device.get_service_datas()) { auto res = parse_header_(service_data); - if (!res.has_value()) { + if (!res.has_value()) continue; - } - if (!(parse_message_(service_data.data, *res))) { + if (!parse_message_(service_data.data, *res)) continue; - } - if (!(report_results_(res, device.address_str()))) { + if (!report_results_(res, device.address_str())) continue; - } if (res->weight.has_value() && this->weight_ != nullptr) this->weight_->publish_state(*res->weight); - if (res->version == 1 && this->impedance_ != nullptr) { - ESP_LOGW(TAG, "Impedance is only supported on version 2. Your scale was identified as verison 1."); - } else if (res->impedance.has_value() && this->impedance_ != nullptr) - this->impedance_->publish_state(*res->impedance); + if (this->impedance_ != nullptr) { + if (res->version == 1) { + ESP_LOGW(TAG, "Impedance is only supported on version 2. Your scale was identified as verison 1."); + } else { + if (res->impedance.has_value()) { + this->impedance_->publish_state(*res->impedance); + } else { + if (clear_impedance_) + this->impedance_->publish_state(NAN); + } + } + } success = true; } diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.h b/esphome/components/xiaomi_miscale/xiaomi_miscale.h index 3e51405ddc..cff61f153b 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.h +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.h @@ -24,11 +24,13 @@ class XiaomiMiscale : public Component, public esp32_ble_tracker::ESPBTDeviceLis float get_setup_priority() const override { return setup_priority::DATA; } void set_weight(sensor::Sensor *weight) { weight_ = weight; } void set_impedance(sensor::Sensor *impedance) { impedance_ = impedance; } + void set_clear_impedance(bool clear_impedance) { clear_impedance_ = clear_impedance; } protected: uint64_t address_; sensor::Sensor *weight_{nullptr}; sensor::Sensor *impedance_{nullptr}; + bool clear_impedance_{false}; optional parse_header_(const esp32_ble_tracker::ServiceData &service_data); bool parse_message_(const std::vector &message, ParseResult &result); diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py index 1bedae26cf..9a4b50df91 100644 --- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py +++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py @@ -3,16 +3,15 @@ import esphome.config_validation as cv from esphome.components import sensor, binary_sensor, esp32_ble_tracker from esphome.const import ( CONF_MAC_ADDRESS, - CONF_ID, CONF_BINDKEY, - CONF_DEVICE_CLASS, CONF_LIGHT, CONF_BATTERY_LEVEL, DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_LIGHT, + DEVICE_CLASS_MOTION, ENTITY_CATEGORY_DIAGNOSTIC, STATE_CLASS_MEASUREMENT, - STATE_CLASS_NONE, UNIT_PERCENT, CONF_IDLE_TIME, CONF_ILLUMINANCE, @@ -22,7 +21,7 @@ from esphome.const import ( ) DEPENDENCIES = ["esp32_ble_tracker"] -AUTO_LOAD = ["xiaomi_ble"] +AUTO_LOAD = ["xiaomi_ble", "sensor"] xiaomi_mjyd02yla_ns = cg.esphome_ns.namespace("xiaomi_mjyd02yla") XiaomiMJYD02YLA = xiaomi_mjyd02yla_ns.class_( @@ -33,19 +32,17 @@ XiaomiMJYD02YLA = xiaomi_mjyd02yla_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema( + XiaomiMJYD02YLA, device_class=DEVICE_CLASS_MOTION + ) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiMJYD02YLA), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Required(CONF_BINDKEY): cv.bind_key, - cv.Optional( - CONF_DEVICE_CLASS, default="motion" - ): binary_sensor.device_class, cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema( unit_of_measurement=UNIT_MINUTE, icon=ICON_TIMELAPSE, accuracy_decimals=0, - state_class=STATE_CLASS_NONE, ), cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, @@ -60,12 +57,8 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_ILLUMINANCE, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_LIGHT): binary_sensor.BINARY_SENSOR_SCHEMA.extend( - { - cv.Optional( - CONF_DEVICE_CLASS, default="light" - ): binary_sensor.device_class, - } + cv.Optional(CONF_LIGHT): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_LIGHT ), } ) @@ -75,10 +68,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_bindkey(config[CONF_BINDKEY])) diff --git a/esphome/components/xiaomi_mue4094rt/binary_sensor.py b/esphome/components/xiaomi_mue4094rt/binary_sensor.py index 5d19263c9c..94d85213ff 100644 --- a/esphome/components/xiaomi_mue4094rt/binary_sensor.py +++ b/esphome/components/xiaomi_mue4094rt/binary_sensor.py @@ -1,7 +1,11 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, CONF_DEVICE_CLASS, CONF_TIMEOUT, CONF_ID +from esphome.const import ( + CONF_MAC_ADDRESS, + CONF_TIMEOUT, + DEVICE_CLASS_MOTION, +) DEPENDENCIES = ["esp32_ble_tracker"] @@ -16,13 +20,12 @@ XiaomiMUE4094RT = xiaomi_mue4094rt_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema( + XiaomiMUE4094RT, device_class=DEVICE_CLASS_MOTION + ) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiMUE4094RT), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, - cv.Optional( - CONF_DEVICE_CLASS, default="motion" - ): binary_sensor.device_class, cv.Optional( CONF_TIMEOUT, default="5s" ): cv.positive_time_period_milliseconds, @@ -34,10 +37,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) cg.add(var.set_time(config[CONF_TIMEOUT])) diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py index 8667794923..504dff9d66 100644 --- a/esphome/components/xiaomi_wx08zm/binary_sensor.py +++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py @@ -10,12 +10,11 @@ from esphome.const import ( STATE_CLASS_MEASUREMENT, UNIT_PERCENT, ICON_BUG, - CONF_ID, ) DEPENDENCIES = ["esp32_ble_tracker"] -AUTO_LOAD = ["xiaomi_ble"] +AUTO_LOAD = ["xiaomi_ble", "sensor"] xiaomi_wx08zm_ns = cg.esphome_ns.namespace("xiaomi_wx08zm") XiaomiWX08ZM = xiaomi_wx08zm_ns.class_( @@ -26,9 +25,9 @@ XiaomiWX08ZM = xiaomi_wx08zm_ns.class_( ) CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(XiaomiWX08ZM) + .extend( { - cv.GenerateID(): cv.declare_id(XiaomiWX08ZM), cv.Required(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_TABLET): sensor.sensor_schema( unit_of_measurement=UNIT_PERCENT, @@ -51,10 +50,9 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await binary_sensor.new_binary_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await binary_sensor.register_binary_sensor(var, config) cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) diff --git a/esphome/components/xpt2046/binary_sensor.py b/esphome/components/xpt2046/binary_sensor.py index 6959d6c342..6ec09a2295 100644 --- a/esphome/components/xpt2046/binary_sensor.py +++ b/esphome/components/xpt2046/binary_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID + from . import ( xpt2046_ns, XPT2046Component, @@ -27,9 +27,8 @@ def validate_xpt2046_button(config): CONFIG_SCHEMA = cv.All( - binary_sensor.BINARY_SENSOR_SCHEMA.extend( + binary_sensor.binary_sensor_schema(XPT2046Button).extend( { - cv.GenerateID(): cv.declare_id(XPT2046Button), cv.GenerateID(CONF_XPT2046_ID): cv.use_id(XPT2046Component), cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095), cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095), @@ -42,8 +41,7 @@ CONFIG_SCHEMA = cv.All( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await binary_sensor.register_binary_sensor(var, config) + var = await binary_sensor.new_binary_sensor(config) hub = await cg.get_variable(config[CONF_XPT2046_ID]) cg.add( var.set_area( diff --git a/esphome/const.py b/esphome/const.py index e49f468c30..579cefd507 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.2.6" +__version__ = "2022.3.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" @@ -89,6 +89,7 @@ CONF_CHANGE_MODE_EVERY = "change_mode_every" CONF_CHANNEL = "channel" CONF_CHANNELS = "channels" CONF_CHIPSET = "chipset" +CONF_CLEAR_IMPEDANCE = "clear_impedance" CONF_CLIENT_ID = "client_id" CONF_CLK_PIN = "clk_pin" CONF_CLOCK_PIN = "clock_pin" @@ -306,6 +307,7 @@ CONF_INTENSITY = "intensity" CONF_INTERLOCK = "interlock" CONF_INTERNAL = "internal" CONF_INTERNAL_FILTER = "internal_filter" +CONF_INTERNAL_FILTER_MODE = "internal_filter_mode" CONF_INTERRUPT = "interrupt" CONF_INTERVAL = "interval" CONF_INVALID_COOLDOWN = "invalid_cooldown" @@ -344,6 +346,7 @@ CONF_LOOP_TIME = "loop_time" CONF_LOW = "low" CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference" CONF_MAC_ADDRESS = "mac_address" +CONF_MAGNITUDE = "magnitude" CONF_MAINS_FILTER = "mains_filter" CONF_MAKE_ID = "make_id" CONF_MANUAL_IP = "manual_ip" @@ -619,6 +622,7 @@ CONF_SLEEP_PIN = "sleep_pin" CONF_SLEEP_WHEN_DONE = "sleep_when_done" CONF_SONY = "sony" CONF_SOURCE = "source" +CONF_SOURCE_ID = "source_id" CONF_SPEED = "speed" CONF_SPEED_COMMAND_TOPIC = "speed_command_topic" CONF_SPEED_COUNT = "speed_count" @@ -735,6 +739,7 @@ CONF_VOLTAGE_DIVIDER = "voltage_divider" CONF_WAIT_TIME = "wait_time" CONF_WAIT_UNTIL = "wait_until" CONF_WAKEUP_PIN = "wakeup_pin" +CONF_WAND_ID = "wand_id" CONF_WARM_WHITE = "warm_white" CONF_WARM_WHITE_COLOR_TEMPERATURE = "warm_white_color_temperature" CONF_WATCHDOG_THRESHOLD = "watchdog_threshold" diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 574a8dcafe..aabb5510f4 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -57,6 +57,7 @@ // ESP32-specific feature flags #ifdef USE_ESP32 +#define USE_ESP32_BLE_CLIENT #define USE_ESP32_BLE_SERVER #define USE_ESP32_CAMERA #define USE_ESP32_IGNORE_EFUSE_MAC_CRC diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 6d399c4064..b03d890ad8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -213,6 +213,25 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) { } std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } +std::string format_hex_pretty(const uint16_t *data, size_t length) { + if (length == 0) + return ""; + std::string ret; + ret.resize(5 * length - 1); + for (size_t i = 0; i < length; i++) { + ret[5 * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12); + ret[5 * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8); + ret[5 * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4); + ret[5 * i + 3] = format_hex_pretty_char(data[i] & 0x000F); + if (i != length - 1) + ret[5 * i + 2] = '.'; + } + if (length > 4) + return ret + " (" + to_string(length) + ")"; + return ret; +} +std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } + ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { if (on == nullptr && strcasecmp(str, "on") == 0) return PARSE_ON; @@ -328,6 +347,8 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } #elif defined(USE_ESP32) +// only affects the executing core +// so should not be used as a mutex lock, only to get accurate timing IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index e0763d2c71..074bea6fd1 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -386,8 +386,12 @@ template::value, int> = 0> std::stri /// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. std::string format_hex_pretty(const uint8_t *data, size_t length); +/// Format the word array \p data of length \p len in pretty-printed, human-readable hex. +std::string format_hex_pretty(const uint16_t *data, size_t length); /// Format the vector \p data in pretty-printed, human-readable hex. std::string format_hex_pretty(const std::vector &data); +/// Format the vector \p data in pretty-printed, human-readable hex. +std::string format_hex_pretty(const std::vector &data); /// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. template::value, int> = 0> std::string format_hex_pretty(T val) { val = convert_big_endian(val); diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 82deec70ec..42828450e8 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -62,14 +62,13 @@ class RawExpression(Expression): class AssignmentExpression(Expression): - __slots__ = ("type", "modifier", "name", "rhs", "obj") + __slots__ = ("type", "modifier", "name", "rhs") - def __init__(self, type_, modifier, name, rhs, obj): + def __init__(self, type_, modifier, name, rhs): self.type = type_ self.modifier = modifier self.name = name self.rhs = safe_exp(rhs) - self.obj = obj def __str__(self): if self.type is None: @@ -427,8 +426,8 @@ class LineComment(Statement): class ProgmemAssignmentExpression(AssignmentExpression): __slots__ = () - def __init__(self, type_, name, rhs, obj): - super().__init__(type_, "", name, rhs, obj) + def __init__(self, type_, name, rhs): + super().__init__(type_, "", name, rhs) def __str__(self): return f"static const {self.type} {self.name}[] PROGMEM = {self.rhs}" @@ -437,8 +436,8 @@ class ProgmemAssignmentExpression(AssignmentExpression): class StaticConstAssignmentExpression(AssignmentExpression): __slots__ = () - def __init__(self, type_, name, rhs, obj): - super().__init__(type_, "", name, rhs, obj) + def __init__(self, type_, name, rhs): + super().__init__(type_, "", name, rhs) def __str__(self): return f"static const {self.type} {self.name}[] = {self.rhs}" @@ -447,7 +446,7 @@ class StaticConstAssignmentExpression(AssignmentExpression): def progmem_array(id_, rhs) -> "MockObj": rhs = safe_exp(rhs) obj = MockObj(id_, ".") - assignment = ProgmemAssignmentExpression(id_.type, id_, rhs, obj) + assignment = ProgmemAssignmentExpression(id_.type, id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -456,7 +455,7 @@ def progmem_array(id_, rhs) -> "MockObj": def static_const_array(id_, rhs) -> "MockObj": rhs = safe_exp(rhs) obj = MockObj(id_, ".") - assignment = StaticConstAssignmentExpression(id_.type, id_, rhs, obj) + assignment = StaticConstAssignmentExpression(id_.type, id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -484,7 +483,7 @@ def variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": obj = MockObj(id_, ".") if type_ is not None: id_.type = type_ - assignment = AssignmentExpression(id_.type, "", id_, rhs, obj) + assignment = AssignmentExpression(id_.type, "", id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -507,7 +506,7 @@ def new_variable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj id_.type = type_ decl = VariableDeclarationExpression(id_.type, "", id_) CORE.add_global(decl) - assignment = AssignmentExpression(None, "", id_, rhs, obj) + assignment = AssignmentExpression(None, "", id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -529,7 +528,7 @@ def Pvariable(id_: ID, rhs: SafeExpType, type_: "MockObj" = None) -> "MockObj": id_.type = type_ decl = VariableDeclarationExpression(id_.type, "*", id_) CORE.add_global(decl) - assignment = AssignmentExpression(None, None, id_, rhs, obj) + assignment = AssignmentExpression(None, None, id_, rhs) CORE.add(assignment) CORE.register_variable(id_, obj) return obj @@ -955,7 +954,7 @@ class MockObjEnum(MockObj): base = kwargs.pop("base") if self._is_class: base = f"{base}::{self._enum}" - kwargs["op"] = "::" + kwargs["op"] = "::" kwargs["base"] = base MockObj.__init__(self, *args, **kwargs) diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index 0ace97f10e..af68f2ae08 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -733,7 +733,7 @@ class EditRequestHandler(BaseHandler): content = "" if os.path.isfile(filename): # pylint: disable=no-value-for-parameter - with open(file=filename, mode="r", encoding="utf-8") as f: + with open(file=filename, encoding="utf-8") as f: content = f.read() self.write(content) diff --git a/esphome/storage_json.py b/esphome/storage_json.py index 207a3edf57..a941fca0af 100644 --- a/esphome/storage_json.py +++ b/esphome/storage_json.py @@ -64,7 +64,7 @@ class StorageJSON: # Web server port of the ESP, for example 80 assert web_port is None or isinstance(web_port, int) self.web_port = web_port # type: int - # The type of ESP in use, either ESP32 or ESP8266 + # The type of hardware in use, like "ESP32", "ESP32C3", "ESP8266", etc. self.target_platform = target_platform # type: str # The absolute path to the platformio project self.build_path = build_path # type: str @@ -99,6 +99,11 @@ class StorageJSON: def from_esphome_core( esph, old ): # type: (CoreType, Optional[StorageJSON]) -> StorageJSON + hardware = esph.target_platform.upper() + if esph.is_esp32: + from esphome.components import esp32 + + hardware = esp32.get_esp32_variant(esph) return StorageJSON( storage_version=1, name=esph.name, @@ -107,15 +112,14 @@ class StorageJSON: src_version=1, address=esph.address, web_port=esph.web_port, - target_platform=esph.target_platform, + target_platform=hardware, build_path=esph.build_path, firmware_bin_path=esph.firmware_bin, loaded_integrations=list(esph.loaded_integrations), ) @staticmethod - def from_wizard(name, address, esp_platform): - # type: (str, str, str) -> StorageJSON + def from_wizard(name: str, address: str, esp_platform: str) -> "StorageJSON": return StorageJSON( storage_version=1, name=name, diff --git a/esphome/wizard.py b/esphome/wizard.py index c64ad3a583..34930ff66f 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -67,6 +67,27 @@ esp32: type: arduino """ +ESP32S2_CONFIG = """ +esp32: + board: {board} + framework: + type: esp-idf +""" + +ESP32C3_CONFIG = """ +esp32: + board: {board} + framework: + type: esp-idf +""" + +HARDWARE_BASE_CONFIGS = { + "ESP8266": ESP8266_CONFIG, + "ESP32": ESP32_CONFIG, + "ESP32S2": ESP32S2_CONFIG, + "ESP32C3": ESP32C3_CONFIG, +} + def sanitize_double_quotes(value): return value.replace("\\", "\\\\").replace('"', '\\"') @@ -83,11 +104,7 @@ def wizard_file(**kwargs): config = BASE_CONFIG.format(**kwargs) - config += ( - ESP8266_CONFIG.format(**kwargs) - if kwargs["platform"] == "ESP8266" - else ESP32_CONFIG.format(**kwargs) - ) + config += HARDWARE_BASE_CONFIGS[kwargs["platform"]].format(**kwargs) config += LOGGER_API_CONFIG @@ -119,16 +136,26 @@ def wizard_file(**kwargs): """ # pylint: disable=consider-using-f-string - config += """ + if kwargs["platform"] in ["ESP8266", "ESP32"]: + config += """ # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "{fallback_name}" password: "{fallback_psk}" captive_portal: -""".format( - **kwargs - ) + """.format( + **kwargs + ) + else: + config += """ + # Enable fallback hotspot in case wifi connection fails + ap: + ssid: "{fallback_name}" + password: "{fallback_psk}" + """.format( + **kwargs + ) return config @@ -147,10 +174,10 @@ def wizard_write(path, **kwargs): kwargs["platform"] = ( "ESP8266" if board in esp8266_boards.ESP8266_BOARD_PINS else "ESP32" ) - platform = kwargs["platform"] + hardware = kwargs["platform"] write_file(path, wizard_file(**kwargs)) - storage = StorageJSON.from_wizard(name, f"{name}.local", platform) + storage = StorageJSON.from_wizard(name, f"{name}.local", hardware) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) storage.save(storage_path) diff --git a/requirements.txt b/requirements.txt index acbf1d9984..739ad79098 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ pyserial==3.5 platformio==5.2.5 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20220209.0 +esphome-dashboard==20220309.0 aioesphomeapi==10.8.2 zeroconf==0.38.3 diff --git a/requirements_test.txt b/requirements_test.txt index 1e5a5c2ebc..afc4fd9d2a 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,12 +1,13 @@ pylint==2.12.2 flake8==4.0.1 black==22.1.0 +pyupgrade==2.31.0 pre-commit # Unit tests -pytest==7.0.0 +pytest==7.0.1 pytest-cov==3.0.0 pytest-mock==3.7.0 -pytest-asyncio==0.18.0 +pytest-asyncio==0.18.1 asyncmock==0.4.2 hypothesis==5.49.0 diff --git a/script/build_jsonschema.py b/script/build_jsonschema.py index 7a3257411c..7673519916 100644 --- a/script/build_jsonschema.py +++ b/script/build_jsonschema.py @@ -170,7 +170,7 @@ def get_logger_tags(): ] for x in os.walk(CORE_COMPONENTS_PATH): for y in glob.glob(os.path.join(x[0], "*.cpp")): - with open(y, "r") as file: + with open(y) as file: data = file.read() match = pattern.search(data) if match: diff --git a/script/ci-custom.py b/script/ci-custom.py index d1efa22d85..c0737da103 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -281,9 +281,7 @@ def highlight(s): ], ) def lint_no_defines(fname, match): - s = highlight( - "static const uint8_t {} = {};".format(match.group(1), match.group(2)) - ) + s = highlight(f"static const uint8_t {match.group(1)} = {match.group(2)};") return ( "#define macros for integer constants are not allowed, please use " "{} style instead (replace uint8_t with the appropriate " @@ -593,6 +591,11 @@ def lint_inclusive_language(fname, match): ) +@lint_re_check(r"[\t\r\f\v ]+$") +def lint_trailing_whitespace(fname, match): + return "Trailing whitespace detected" + + @lint_content_find_check( "ESP_LOG", include=["*.h", "*.tcc"], diff --git a/script/clang-format b/script/clang-format index 515df4c027..ae807262f1 100755 --- a/script/clang-format +++ b/script/clang-format @@ -17,14 +17,14 @@ def run_format(args, queue, lock, failed_files): """Takes filenames out of queue and runs clang-format on them.""" while True: path = queue.get() - invocation = ['clang-format-11'] + invocation = ["clang-format-11"] if args.inplace: - invocation.append('-i') + invocation.append("-i") else: - invocation.extend(['--dry-run', '-Werror']) + invocation.extend(["--dry-run", "-Werror"]) invocation.append(path) - proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") if proc.returncode != 0: with lock: print_error_for_file(path, proc.stderr) @@ -33,28 +33,36 @@ def run_format(args, queue, lock, failed_files): def progress_bar_show(value): - return value if value is not None else '' + return value if value is not None else "" def main(): colorama.init() parser = argparse.ArgumentParser() - parser.add_argument('-j', '--jobs', type=int, - default=multiprocessing.cpu_count(), - help='number of format instances to be run in parallel.') - parser.add_argument('files', nargs='*', default=[], - help='files to be processed (regex on path)') - parser.add_argument('-i', '--inplace', action='store_true', - help='reformat files in-place') - parser.add_argument('-c', '--changed', action='store_true', - help='only run on changed files') + parser.add_argument( + "-j", + "--jobs", + type=int, + default=multiprocessing.cpu_count(), + help="number of format instances to be run in parallel.", + ) + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument( + "-i", "--inplace", action="store_true", help="reformat files in-place" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="only run on changed files" + ) args = parser.parse_args() try: - get_output('clang-format-11', '-version') + get_output("clang-format-11", "-version") except: - print(""" + print( + """ Oops. It looks like clang-format is not installed. Please check you can run "clang-format-11 -version" in your terminal and install @@ -62,16 +70,17 @@ def main(): Note you can also upload your code as a pull request on GitHub and see the CI check output to apply clang-format. - """) + """ + ) return 1 files = [] - for path in git_ls_files(['*.cpp', '*.h', '*.tcc']): + for path in git_ls_files(["*.cpp", "*.h", "*.tcc"]): files.append(os.path.relpath(path, os.getcwd())) if args.files: # Match against files specified on command-line - file_name_re = re.compile('|'.join(args.files)) + file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -84,14 +93,16 @@ def main(): task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): - t = threading.Thread(target=run_format, - args=(args, task_queue, lock, failed_files)) + t = threading.Thread( + target=run_format, args=(args, task_queue, lock, failed_files) + ) t.daemon = True t.start() # Fill the queue with files. - with click.progressbar(files, width=30, file=sys.stderr, - item_show_func=progress_bar_show) as bar: + with click.progressbar( + files, width=30, file=sys.stderr, item_show_func=progress_bar_show + ) as bar: for name in bar: task_queue.put(name) @@ -100,11 +111,11 @@ def main(): except KeyboardInterrupt: print() - print('Ctrl-C detected, goodbye.') + print("Ctrl-C detected, goodbye.") os.kill(0, 9) sys.exit(len(failed_files)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/clang-tidy b/script/clang-tidy index 8a7d229887..327b593008 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -1,7 +1,17 @@ #!/usr/bin/env python3 -from helpers import print_error_for_file, get_output, filter_grep, \ - build_all_include, temp_header_file, git_ls_files, filter_changed, load_idedata, root_path, basepath +from helpers import ( + print_error_for_file, + get_output, + filter_grep, + build_all_include, + temp_header_file, + git_ls_files, + filter_changed, + load_idedata, + root_path, + basepath, +) import argparse import click import colorama @@ -20,67 +30,81 @@ def clang_options(idedata): cmd = [] # extract target architecture from triplet in g++ filename - triplet = os.path.basename(idedata['cxx_path'])[:-4] + triplet = os.path.basename(idedata["cxx_path"])[:-4] if triplet.startswith("xtensa-"): # clang doesn't support Xtensa (yet?), so compile in 32-bit mode and pretend we're the Xtensa compiler - cmd.append('-m32') - cmd.append('-D__XTENSA__') + cmd.append("-m32") + cmd.append("-D__XTENSA__") else: - cmd.append(f'--target={triplet}') + cmd.append(f"--target={triplet}") # set flags - cmd.extend([ - # disable built-in include directories from the host - '-nostdinc', - '-nostdinc++', - # replace pgmspace.h, as it uses GNU extensions clang doesn't support - # https://github.com/earlephilhower/newlib-xtensa/pull/18 - '-D_PGMSPACE_H_', - '-Dpgm_read_byte(s)=(*(const uint8_t *)(s))', - '-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))', - '-Dpgm_read_word(s)=(*(const uint16_t *)(s))', - '-Dpgm_read_dword(s)=(*(const uint32_t *)(s))', - '-DPROGMEM=', - '-DPGM_P=const char *', - '-DPSTR(s)=(s)', - # this next one is also needed with upstream pgmspace.h - # suppress warning about identifier naming in expansion of this macro - '-DPSTRN(s, n)=(s)', - # suppress warning about attribute cannot be applied to type - # https://github.com/esp8266/Arduino/pull/8258 - '-Ddeprecated(x)=', - # allow to condition code on the presence of clang-tidy - '-DCLANG_TIDY', - # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know - '-D__XTENSA_API_H__', - # (esp-idf) Fix __once_callable in some libstdc++ headers - '-D_GLIBCXX_HAVE_TLS', - ]) + cmd.extend( + [ + # disable built-in include directories from the host + "-nostdinc", + "-nostdinc++", + # replace pgmspace.h, as it uses GNU extensions clang doesn't support + # https://github.com/earlephilhower/newlib-xtensa/pull/18 + "-D_PGMSPACE_H_", + "-Dpgm_read_byte(s)=(*(const uint8_t *)(s))", + "-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))", + "-Dpgm_read_word(s)=(*(const uint16_t *)(s))", + "-Dpgm_read_dword(s)=(*(const uint32_t *)(s))", + "-DPROGMEM=", + "-DPGM_P=const char *", + "-DPSTR(s)=(s)", + # this next one is also needed with upstream pgmspace.h + # suppress warning about identifier naming in expansion of this macro + "-DPSTRN(s, n)=(s)", + # suppress warning about attribute cannot be applied to type + # https://github.com/esp8266/Arduino/pull/8258 + "-Ddeprecated(x)=", + # allow to condition code on the presence of clang-tidy + "-DCLANG_TIDY", + # (esp-idf) Disable this header because they use asm with registers clang-tidy doesn't know + "-D__XTENSA_API_H__", + # (esp-idf) Fix __once_callable in some libstdc++ headers + "-D_GLIBCXX_HAVE_TLS", + ] + ) # copy compiler flags, except those clang doesn't understand. - cmd.extend(flag for flag in idedata['cxx_flags'].split(' ') - if flag not in ('-free', '-fipa-pta', '-fstrict-volatile-bitfields', - '-mlongcalls', '-mtext-section-literals', - '-mfix-esp32-psram-cache-issue', '-mfix-esp32-psram-cache-strategy=memw')) + cmd.extend( + flag + for flag in idedata["cxx_flags"].split(" ") + if flag + not in ( + "-free", + "-fipa-pta", + "-fstrict-volatile-bitfields", + "-mlongcalls", + "-mtext-section-literals", + "-mfix-esp32-psram-cache-issue", + "-mfix-esp32-psram-cache-strategy=memw", + ) + ) # defines - cmd.extend(f'-D{define}' for define in idedata['defines']) + cmd.extend(f"-D{define}" for define in idedata["defines"]) # add toolchain include directories using -isystem to suppress their errors # idedata contains include directories for all toolchains of this platform, only use those from the one in use toolchain_dir = os.path.normpath(f"{idedata['cxx_path']}/../../") - for directory in idedata['includes']['toolchain']: + for directory in idedata["includes"]["toolchain"]: if directory.startswith(toolchain_dir): - cmd.extend(['-isystem', directory]) + cmd.extend(["-isystem", directory]) # add library include directories using -isystem to suppress their errors - for directory in sorted(set(idedata['includes']['build'])): + for directory in sorted(set(idedata["includes"]["build"])): # skip our own directories, we add those later - if not directory.startswith(f"{root_path}/") or directory.startswith(f"{root_path}/.pio/"): - cmd.extend(['-isystem', directory]) + if not directory.startswith(f"{root_path}/") or directory.startswith( + f"{root_path}/.pio/" + ): + cmd.extend(["-isystem", directory]) # add the esphome include directory using -I - cmd.extend(['-I', root_path]) + cmd.extend(["-I", root_path]) return cmd @@ -88,28 +112,28 @@ def clang_options(idedata): def run_tidy(args, options, tmpdir, queue, lock, failed_files): while True: path = queue.get() - invocation = ['clang-tidy-11'] + invocation = ["clang-tidy-11"] if tmpdir is not None: - invocation.append('--export-fixes') + invocation.append("--export-fixes") # Get a temporary file. We immediately close the handle so clang-tidy can # overwrite it. - (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + (handle, name) = tempfile.mkstemp(suffix=".yaml", dir=tmpdir) os.close(handle) invocation.append(name) if args.quiet: - invocation.append('--quiet') + invocation.append("--quiet") if sys.stdout.isatty(): - invocation.append('--use-color') + invocation.append("--use-color") invocation.append(f"--header-filter={os.path.abspath(basepath)}/.*") invocation.append(os.path.abspath(path)) - invocation.append('--') + invocation.append("--") invocation.extend(options) - proc = subprocess.run(invocation, capture_output=True, encoding='utf-8') + proc = subprocess.run(invocation, capture_output=True, encoding="utf-8") if proc.returncode != 0: with lock: print_error_for_file(path, proc.stdout) @@ -119,43 +143,60 @@ def run_tidy(args, options, tmpdir, queue, lock, failed_files): def progress_bar_show(value): if value is None: - return '' + return "" def split_list(a, n): k, m = divmod(len(a), n) - return [a[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(n)] + return [a[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] for i in range(n)] def main(): colorama.init() parser = argparse.ArgumentParser() - parser.add_argument('-j', '--jobs', type=int, - default=multiprocessing.cpu_count(), - help='number of tidy instances to be run in parallel.') - parser.add_argument('-e', '--environment', default='esp32-arduino-tidy', - help='the PlatformIO environment to use (as defined in platformio.ini)') - parser.add_argument('files', nargs='*', default=[], - help='files to be processed (regex on path)') - parser.add_argument('--fix', action='store_true', help='apply fix-its') - parser.add_argument('-q', '--quiet', action='store_false', - help='run clang-tidy in quiet mode') - parser.add_argument('-c', '--changed', action='store_true', - help='only run on changed files') - parser.add_argument('-g', '--grep', help='only run on files containing value') - parser.add_argument('--split-num', type=int, help='split the files into X jobs.', - default=None) - parser.add_argument('--split-at', type=int, help='which split is this? starts at 1', - default=None) - parser.add_argument('--all-headers', action='store_true', - help='create a dummy file that checks all headers') + parser.add_argument( + "-j", + "--jobs", + type=int, + default=multiprocessing.cpu_count(), + help="number of tidy instances to be run in parallel.", + ) + parser.add_argument( + "-e", + "--environment", + default="esp32-arduino-tidy", + help="the PlatformIO environment to use (as defined in platformio.ini)", + ) + parser.add_argument( + "files", nargs="*", default=[], help="files to be processed (regex on path)" + ) + parser.add_argument("--fix", action="store_true", help="apply fix-its") + parser.add_argument( + "-q", "--quiet", action="store_false", help="run clang-tidy in quiet mode" + ) + parser.add_argument( + "-c", "--changed", action="store_true", help="only run on changed files" + ) + parser.add_argument("-g", "--grep", help="only run on files containing value") + parser.add_argument( + "--split-num", type=int, help="split the files into X jobs.", default=None + ) + parser.add_argument( + "--split-at", type=int, help="which split is this? starts at 1", default=None + ) + parser.add_argument( + "--all-headers", + action="store_true", + help="create a dummy file that checks all headers", + ) args = parser.parse_args() try: - get_output('clang-tidy-11', '-version') + get_output("clang-tidy-11", "-version") except: - print(""" + print( + """ Oops. It looks like clang-tidy-11 is not installed. Please check you can run "clang-tidy-11 -version" in your terminal and install @@ -163,19 +204,20 @@ def main(): Note you can also upload your code as a pull request on GitHub and see the CI check output to apply clang-tidy. - """) + """ + ) return 1 idedata = load_idedata(args.environment) options = clang_options(idedata) files = [] - for path in git_ls_files(['*.cpp']): + for path in git_ls_files(["*.cpp"]): files.append(os.path.relpath(path, os.getcwd())) if args.files: # Match against files specified on command-line - file_name_re = re.compile('|'.join(args.files)) + file_name_re = re.compile("|".join(args.files)) files = [p for p in files if file_name_re.search(p)] if args.changed: @@ -202,14 +244,17 @@ def main(): task_queue = queue.Queue(args.jobs) lock = threading.Lock() for _ in range(args.jobs): - t = threading.Thread(target=run_tidy, - args=(args, options, tmpdir, task_queue, lock, failed_files)) + t = threading.Thread( + target=run_tidy, + args=(args, options, tmpdir, task_queue, lock, failed_files), + ) t.daemon = True t.start() # Fill the queue with files. - with click.progressbar(files, width=30, file=sys.stderr, - item_show_func=progress_bar_show) as bar: + with click.progressbar( + files, width=30, file=sys.stderr, item_show_func=progress_bar_show + ) as bar: for name in bar: task_queue.put(name) @@ -218,21 +263,21 @@ def main(): except KeyboardInterrupt: print() - print('Ctrl-C detected, goodbye.') + print("Ctrl-C detected, goodbye.") if tmpdir: shutil.rmtree(tmpdir) os.kill(0, 9) if args.fix and failed_files: - print('Applying fixes ...') + print("Applying fixes ...") try: - subprocess.call(['clang-apply-replacements-11', tmpdir]) + subprocess.call(["clang-apply-replacements-11", tmpdir]) except: - print('Error applying fixes.\n', file=sys.stderr) + print("Error applying fixes.\n", file=sys.stderr) raise sys.exit(len(failed_files)) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/script/helpers.py b/script/helpers.py index abf970b8a2..c042362aeb 100644 --- a/script/helpers.py +++ b/script/helpers.py @@ -12,13 +12,16 @@ temp_header_file = os.path.join(temp_folder, "all-include.cpp") def styled(color, msg, reset=True): - prefix = ''.join(color) if isinstance(color, tuple) else color - suffix = colorama.Style.RESET_ALL if reset else '' + prefix = "".join(color) if isinstance(color, tuple) else color + suffix = colorama.Style.RESET_ALL if reset else "" return prefix + msg + suffix def print_error_for_file(file, body): - print(styled(colorama.Fore.GREEN, "### File ") + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file)) + print( + styled(colorama.Fore.GREEN, "### File ") + + styled((colorama.Fore.GREEN, colorama.Style.BRIGHT), file) + ) print() if body is not None: print(body) @@ -100,7 +103,7 @@ def filter_changed(files): def filter_grep(files, value): matched = [] for file in files: - with open(file, "r") as handle: + with open(file) as handle: contents = handle.read() if value in contents: matched.append(file) diff --git a/script/lint-python b/script/lint-python index 8ee038a661..90b5dcd59f 100755 --- a/script/lint-python +++ b/script/lint-python @@ -1,7 +1,13 @@ #!/usr/bin/env python3 -from __future__ import print_function -from helpers import styled, print_error_for_file, get_output, get_err, git_ls_files, filter_changed +from helpers import ( + styled, + print_error_for_file, + get_output, + get_err, + git_ls_files, + filter_changed, +) import argparse import colorama import os @@ -34,6 +40,12 @@ def main(): parser.add_argument( "-c", "--changed", action="store_true", help="Only run on changed files" ) + parser.add_argument( + "-a", + "--apply", + action="store_true", + help="Apply changes to files where possible", + ) args = parser.parse_args() files = [] @@ -56,7 +68,7 @@ def main(): errors = 0 - cmd = ["black", "--verbose", "--check"] + files + cmd = ["black", "--verbose"] + ([] if args.apply else ["--check"]) + files print("Running black...") print() log = get_err(*cmd) @@ -97,6 +109,21 @@ def main(): print_error(file_, linno, msg) errors += 1 + PYUPGRADE_TARGET = "--py38-plus" + cmd = ["pyupgrade", PYUPGRADE_TARGET] + files + print() + print("Running pyupgrade...") + print() + log = get_err(*cmd) + for line in log.splitlines(): + REWRITING = "Rewriting" + if line.startswith(REWRITING): + file_ = line[len(REWRITING) + 1 :] + print_error( + file_, None, f"Please run pyupgrade {PYUPGRADE_TARGET} on this file" + ) + errors += 1 + sys.exit(errors) diff --git a/setup.py b/setup.py index 967eadd70f..941c8089ec 100755 --- a/setup.py +++ b/setup.py @@ -17,11 +17,11 @@ PROJECT_EMAIL = "esphome@nabucasa.com" PROJECT_GITHUB_USERNAME = "esphome" PROJECT_GITHUB_REPOSITORY = "esphome" -PYPI_URL = "https://pypi.python.org/pypi/{}".format(PROJECT_PACKAGE_NAME) -GITHUB_PATH = "{}/{}".format(PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY) -GITHUB_URL = "https://github.com/{}".format(GITHUB_PATH) +PYPI_URL = f"https://pypi.python.org/pypi/{PROJECT_PACKAGE_NAME}" +GITHUB_PATH = f"{PROJECT_GITHUB_USERNAME}/{PROJECT_GITHUB_REPOSITORY}" +GITHUB_URL = f"https://github.com/{GITHUB_PATH}" -DOWNLOAD_URL = "{}/archive/{}.zip".format(GITHUB_URL, const.__version__) +DOWNLOAD_URL = f"{GITHUB_URL}/archive/{const.__version__}.zip" here = os.path.abspath(os.path.dirname(__file__)) @@ -74,7 +74,7 @@ setup( zip_safe=False, platforms="any", test_suite="tests", - python_requires=">=3.7,<4.0", + python_requires=">=3.8,<4.0", install_requires=REQUIRES, keywords=["home", "automation"], entry_points={"console_scripts": ["esphome = esphome.__main__:main"]}, diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml index 3eac129e8c..32d2e8d93b 100644 --- a/tests/component_tests/button/test_button.yaml +++ b/tests/component_tests/button/test_button.yaml @@ -18,4 +18,3 @@ button: name: wol_test_2 id: wol_2 internal: false - diff --git a/tests/test1.yaml b/tests/test1.yaml index b9799bdb36..3763fd3fa5 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -247,9 +247,7 @@ logger: web_server: port: 8080 - ota: true - css_url: https://esphome.io/_static/webserver-v1.min.css - js_url: https://esphome.io/_static/webserver-v1.min.js + version: 2 power_supply: id: "atx_power_supply" @@ -358,6 +356,7 @@ sensor: - exponential_moving_average: alpha: 0.1 send_every: 15 + send_first_at: 15 - throttle_average: 60s - throttle: 1s - heartbeat: 5s @@ -456,12 +455,17 @@ sensor: name: "Living Room Brightness 3" internal: true address: 0x23 - resolution: 1.0 update_interval: 30s retain: False availability: state_topic: livingroom/custom_state_topic - measurement_duration: 31 + i2c_id: i2c_bus + - platform: max44009 + name: "Outside Brightness 1" + internal: true + address: 0x4A + update_interval: 30s + mode: low_power i2c_id: i2c_bus - platform: bme280 temperature: @@ -594,6 +598,14 @@ sensor: oversampling: 8x update_interval: 15s i2c_id: i2c_bus + - platform: honeywellabp + pressure: + name: "Honeywell pressure" + min_pressure: 0 + max_pressure: 15 + temperature: + name: "Honeywell temperature" + cs_pin: GPIO5 - platform: qmc5883l address: 0x0D field_strength_x: @@ -719,6 +731,23 @@ sensor: temperature: name: "MPU6050 Temperature" i2c_id: i2c_bus + - platform: mpu6886 + address: 0x68 + accel_x: + name: "MPU6886 Accel X" + accel_y: + name: "MPU6886 Accel Y" + accel_z: + name: "MPU6886 Accel z" + gyro_x: + name: "MPU6886 Gyro X" + gyro_y: + name: "MPU6886 Gyro Y" + gyro_z: + name: "MPU6886 Gyro z" + temperature: + name: "MPU6886 Temperature" + i2c_id: i2c_bus - platform: ms5611 temperature: name: "Outside Temperature" @@ -1090,8 +1119,8 @@ sensor: temperature: name: Max9611 Temp update_interval: 1s - - + + esp32_touch: setup_mode: False iir_filter: 10ms @@ -1307,6 +1336,21 @@ binary_sensor: ] - platform: as3935 name: "Storm Alert" + - platform: analog_threshold + name: Analog Trheshold 1 + sensor_id: template_sensor + threshold: + upper: 110 + lower: 90 + filters: + - delayed_on: 0s + - delayed_off: 10s + - platform: analog_threshold + name: Analog Trheshold 2 + sensor_id: template_sensor + threshold: 100 + filters: + - invert: pca9685: frequency: 500 @@ -1463,6 +1507,28 @@ output: - platform: mcp4725 id: mcp4725_dac_output i2c_id: i2c_bus + - platform: mcp4728 + id: mcp4728_dac_output_a + channel: A + vref: vdd + power_down: normal + - platform: mcp4728 + id: mcp4728_dac_output_b + channel: B + vref: internal + gain: X1 + power_down: gnd_1k + - platform: mcp4728 + id: mcp4728_dac_output_c + channel: C + vref: vdd + power_down: gnd_100k + - platform: mcp4728 + id: mcp4728_dac_output_d + channel: D + vref: internal + gain: X2 + power_down: gnd_500k e131: @@ -1846,6 +1912,11 @@ switch: turn_on_action: remote_transmitter.transmit_jvc: data: 0x10EF + - platform: template + name: MagiQuest + turn_on_action: + remote_transmitter.transmit_magiquest: + wand_id: 0x01234567 - platform: template name: NEC id: living_room_lights_off @@ -1970,6 +2041,9 @@ switch: - output.set_level: id: mcp4725_dac_output level: !lambda "return 0.5;" + - output.set_level: + id: mcp4728_dac_output_a + level: !lambda "return 0.5;" turn_off_action: - switch.turn_on: living_room_lights_off restore_state: False @@ -2079,6 +2153,9 @@ fan: on_speed_set: then: - logger.log: "Fan speed was changed!" + - platform: copy + source_id: fan_speed + name: "Fan Speed Copy" interval: - interval: 10s @@ -2347,6 +2424,12 @@ rc522_i2c: ESP_LOGD("main", "Found tag %s", x.c_str()); i2c_id: i2c_bus +mcp4728: + - id: mcp4728_dac + store_in_eeprom: False + address: 0x60 + i2c_id: i2c_bus + gps: uart_id: uart0 @@ -2476,6 +2559,18 @@ globals: initial_value: "false" text_sensor: + - platform: ble_client + ble_client_id: ble_foo + name: 'Sensor Location' + service_uuid: '180d' + characteristic_uuid: '2a38' + descriptor_uuid: '2902' + notify: true + update_interval: never + on_notify: + then: + - lambda: |- + ESP_LOGD("green_btn", "Location changed: %s", x.c_str()); - platform: mqtt_subscribe name: "MQTT Subscribe Text" topic: "the/topic" @@ -2543,7 +2638,7 @@ canbus: then: - lambda: |- std::string b(x.begin(), x.end()); - ESP_LOGD("canid 500", "%s", &b[0] ); + ESP_LOGD("canid 500", "%s", b.c_str()); - can_id: 23 then: - if: @@ -2551,6 +2646,25 @@ canbus: lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } - platform: esp32_can id: esp32_internal_can rx_pin: GPIO04 @@ -2562,7 +2676,7 @@ canbus: then: - lambda: |- std::string b(x.begin(), x.end()); - ESP_LOGD("canid 500", "%s", &b[0] ); + ESP_LOGD("canid 500", "%s", b.c_str() ); - can_id: 23 then: - if: @@ -2570,6 +2684,25 @@ canbus: lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights + - can_id: 0b00000000000000000000001000000 + can_id_mask: 0b11111000000000011111111000000 + use_extended_id: true + then: + - lambda: |- + auto pdo_id = can_id >> 14; + switch (pdo_id) + { + case 117: + ESP_LOGD("canbus", "exhaust_fan_duty"); + break; + case 118: + ESP_LOGD("canbus", "supply_fan_duty"); + break; + case 119: + ESP_LOGD("canbus", "supply_fan_flow"); + break; + // to be continued... + } teleinfo: id: myteleinfo @@ -2596,6 +2729,9 @@ select: - one - two optimistic: true + - platform: copy + source_id: test_select + name: Test Select Copy qr_code: - id: homepage_qr @@ -2625,3 +2761,6 @@ lock: name: "Generic Output Lock" id: test_lock2 output: pca_6 + - platform: copy + source_id: test_lock2 + name: Generic Output Lock Copy diff --git a/tests/test2.yaml b/tests/test2.yaml index 76b9775c54..ec3ccff70c 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -331,6 +331,19 @@ sensor: name: "RD200 Radon" radon_long_term: name: "RD200 Radon Long Term" + - platform: mopeka_pro_check + mac_address: D3:75:F2:DC:16:91 + tank_type: CUSTOM + custom_distance_full: 40cm + custom_distance_empty: 10mm + temperature: + name: "Propane test temp" + level: + name: "Propane test level" + distance: + name: "Propane test distance" + battery_level: + name: "Propane test battery level" time: - platform: homeassistant @@ -442,6 +455,7 @@ ruuvi_ble: xiaomi_ble: +mopeka_ble: #esp32_ble_beacon: # type: iBeacon diff --git a/tests/test4.yaml b/tests/test4.yaml index 998db8ed2d..54412222b5 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -221,6 +221,10 @@ sensor: - platform: mcp3204 name: "MCP3204 Pin 1" number: 1 + id: mcp_sensor + - platform: copy + source_id: mcp_sensor + name: "MCP binary sensor copy" # # platform sensor.apds9960 requires component apds9960 @@ -364,6 +368,9 @@ switch: name: inverter0_pv_ok_condition_for_parallel pv_power_balance: name: inverter0_pv_power_balance + - platform: copy + source_id: tuya_switch + name: Tuya Switch Copy light: - platform: fastled_clockless @@ -391,6 +398,9 @@ cover: - platform: tuya id: tuya_cover position_datapoint: 2 + - platform: copy + source_id: tuya_cover + name: "Tuya Cover copy" display: - platform: addressable_light @@ -465,6 +475,9 @@ number: min_value: 0 max_value: 17 step: 1 + - platform: copy + source_id: tuya_number + name: Tuya Number Copy text_sensor: - platform: pipsolar @@ -484,6 +497,9 @@ text_sensor: last_qflag: id: inverter0_last_qflag name: inverter0_last_qflag + - platform: copy + source_id: inverter0_device_mode + name: "Inverter Text Sensor Copy" output: - platform: pipsolar @@ -552,6 +568,11 @@ button: name: Safe Mode Button - platform: shutdown name: Shutdown Button + id: shutdown_btn + - platform: copy + source_id: shutdown_btn + name: Shutdown Button Copy + touchscreen: - platform: ektf2232 diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 5a8087ffa9..331c500c04 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -13,9 +13,9 @@ class TestExpressions: "target, expected", ( (cg.RawExpression("foo && bar"), "foo && bar"), - (cg.AssignmentExpression(None, None, "foo", "bar", None), 'foo = "bar"'), - (cg.AssignmentExpression(ct.float_, "*", "foo", 1, None), "float *foo = 1"), - (cg.AssignmentExpression(ct.float_, "", "foo", 1, None), "float foo = 1"), + (cg.AssignmentExpression(None, None, "foo", "bar"), 'foo = "bar"'), + (cg.AssignmentExpression(ct.float_, "*", "foo", 1), "float *foo = 1"), + (cg.AssignmentExpression(ct.float_, "", "foo", 1), "float foo = 1"), (cg.VariableDeclarationExpression(ct.int32, "*", "foo"), "int32_t *foo"), (cg.VariableDeclarationExpression(ct.int32, "", "foo"), "int32_t foo"), (cg.ParameterExpression(ct.std_string, "foo"), "std::string foo"), @@ -274,7 +274,7 @@ class TestStatements: "// Help help\n// I'm being repressed", ), ( - cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar", None), + cg.ProgmemAssignmentExpression(ct.uint16, "foo", "bar"), 'static const uint16_t foo[] PROGMEM = "bar"', ), ), diff --git a/tests/unit_tests/test_wizard.py b/tests/unit_tests/test_wizard.py index 59fcfbff60..79a5894075 100644 --- a/tests/unit_tests/test_wizard.py +++ b/tests/unit_tests/test_wizard.py @@ -3,14 +3,14 @@ import esphome.wizard as wz import pytest from esphome.components.esp8266.boards import ESP8266_BOARD_PINS -from mock import MagicMock +from unittest.mock import MagicMock @pytest.fixture def default_config(): return { "name": "test-name", - "platform": "test_platform", + "platform": "ESP8266", "board": "esp01_1m", "ssid": "test_ssid", "psk": "test_psk",