diff --git a/.clang-tidy b/.clang-tidy index b40e60612188..c9b77b57201b 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -68,8 +68,6 @@ Checks: >- -modernize-use-nodiscard, -mpi-*, -objc-*, - -readability-braces-around-statements, - -readability-const-return-type, -readability-convert-member-functions-to-static, -readability-else-after-return, -readability-function-cognitive-complexity, @@ -77,10 +75,6 @@ Checks: >- -readability-isolate-declaration, -readability-magic-numbers, -readability-make-member-function-const, - -readability-named-parameter, - -readability-qualified-auto, - -readability-redundant-access-specifiers, - -readability-redundant-member-init, -readability-redundant-string-init, -readability-uppercase-literal-suffix, -readability-use-anyofallof, @@ -114,6 +108,8 @@ CheckOptions: value: 'make_unique' - key: modernize-make-unique.MakeSmartPtrFunctionHeader value: 'esphome/core/helpers.h' + - key: readability-braces-around-statements.ShortStatementLines + value: 2 - key: readability-identifier-naming.LocalVariableCase value: 'lower_case' - key: readability-identifier-naming.ClassCase @@ -160,3 +156,5 @@ CheckOptions: value: 'lower_case' - key: readability-identifier-naming.VirtualMethodSuffix value: '' + - key: readability-qualified-auto.AddConstToQualified + value: 0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d6895becc086..02a55494e9e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -137,7 +137,7 @@ jobs: --build-type "${{ matrix.build_type }}" \ manifest - deploy-hassio-repo: + deploy-ha-addon-repo: if: github.repository == 'esphome/esphome' && github.event_name == 'release' runs-on: ubuntu-latest needs: [deploy-docker] @@ -150,5 +150,5 @@ jobs: -u ":$TOKEN" \ -X POST \ -H "Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \ + https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \ -d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}" diff --git a/.gitpod.yml b/.gitpod.yml index 2ff20a036670..e3f786a40316 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -3,4 +3,4 @@ ports: onOpen: open-preview tasks: - before: pyenv local $(pyenv version | grep '^3\.' | cut -d ' ' -f 1) && script/setup - command: python -m esphome config dashboard + command: python -m esphome dashboard config diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a821c21fa736..e38717fe5b85 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/ambv/black - rev: 20.8b1 + rev: 22.1.0 hooks: - id: black args: @@ -10,7 +10,7 @@ repos: - --quiet files: ^((esphome|script|tests)/.+)?[^/]+\.py$ - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + rev: 4.0.1 hooks: - id: flake8 additional_dependencies: diff --git a/CODEOWNERS b/CODEOWNERS index 351d9f5fc969..165ce5248500 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -54,6 +54,7 @@ esphome/components/dfplayer/* @glmnet esphome/components/dht/* @OttoWinter esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/ektf2232/* @jesserockz esphome/components/esp32/* @esphome/core esphome/components/esp32_ble/* @jesserockz esphome/components/esp32_ble_server/* @jesserockz @@ -88,9 +89,12 @@ esphome/components/json/* @OttoWinter esphome/components/kalman_combinator/* @Cat-Ion esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core +esphome/components/lilygo_t5_47/touchscreen/* @jesserockz +esphome/components/lock/* @esphome/core esphome/components/logger/* @esphome/core esphome/components/ltr390/* @sjtrny esphome/components/max7219digit/* @rspaargaren +esphome/components/max9611/* @mckaymatthew esphome/components/mcp23008/* @jesserockz esphome/components/mcp23017/* @jesserockz esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz @@ -107,10 +111,12 @@ esphome/components/mdns/* @esphome/core esphome/components/midea/* @dudanov esphome/components/midea_ir/* @dudanov esphome/components/mitsubishi/* @RubyBailey +esphome/components/mlx90393/* @functionpointer esphome/components/modbus_controller/* @martgras esphome/components/modbus_controller/binary_sensor/* @martgras esphome/components/modbus_controller/number/* @martgras esphome/components/modbus_controller/output/* @martgras +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 @@ -136,6 +142,9 @@ esphome/components/preferences/* @esphome/core esphome/components/psram/* @esphome/core esphome/components/pulse_meter/* @stevebaxter esphome/components/pvvx_mithermometer/* @pasiz +esphome/components/qr_code/* @wjtje +esphome/components/radon_eye_ble/* @jeffeb3 +esphome/components/radon_eye_rd200/* @jeffeb3 esphome/components/rc522/* @glmnet esphome/components/rc522_i2c/* @glmnet esphome/components/rc522_spi/* @glmnet @@ -187,6 +196,7 @@ esphome/components/tmp102/* @timsavage esphome/components/tmp117/* @Azimath esphome/components/tof10120/* @wstrzalka esphome/components/toshiba/* @kbx81 +esphome/components/touchscreen/* @jesserockz esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz @@ -197,8 +207,10 @@ esphome/components/tuya/text_sensor/* @dentra esphome/components/uart/* @esphome/core esphome/components/ultrasonic/* @OttoWinter esphome/components/version/* @esphome/core +esphome/components/wake_on_lan/* @willwill2will54 esphome/components/web_server_base/* @OttoWinter esphome/components/whirlpool/* @glmnet esphome/components/xiaomi_lywsd03mmc/* @ahpohl +esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xpt2046/* @numo68 diff --git a/MANIFEST.in b/MANIFEST.in index 0fe80762b393..a3126404f268 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,4 +4,5 @@ include requirements.txt include esphome/dashboard/templates/*.html recursive-include esphome/dashboard/static *.ico *.js *.css *.woff* LICENSE recursive-include esphome *.cpp *.h *.tcc +recursive-include esphome *.py.script recursive-include esphome LICENSE.txt diff --git a/docker/Dockerfile b/docker/Dockerfile index 330901a776bf..5d9decbf1b3e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,9 +8,9 @@ ARG BASEIMGTYPE=docker 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 -FROM debian:bullseye-20211220-slim AS base-docker-amd64 -FROM debian:bullseye-20211220-slim AS base-docker-arm64 -FROM debian:bullseye-20211220-slim AS base-docker-armv7 +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 # Use TARGETARCH/TARGETVARIANT defined by docker # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope @@ -23,7 +23,7 @@ RUN \ python3=3.9.2-3 \ python3-pip=20.3.4-4 \ python3-setuptools=52.0.0-4 \ - python3-pil=8.1.2+dfsg-0.3 \ + python3-pil=8.1.2+dfsg-0.3+deb11u1 \ python3-cryptography=3.3.2-1 \ iputils-ping=3:20210202-1 \ git=1:2.30.2-1 \ @@ -102,7 +102,7 @@ RUN \ ARG BUILD_VERSION=dev # Copy root filesystem -COPY docker/hassio-rootfs/ / +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 / diff --git a/docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh b/docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh similarity index 100% rename from docker/hassio-rootfs/etc/cont-init.d/10-requirements.sh rename to docker/ha-addon-rootfs/etc/cont-init.d/10-requirements.sh diff --git a/docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh b/docker/ha-addon-rootfs/etc/cont-init.d/20-nginx.sh similarity index 100% rename from docker/hassio-rootfs/etc/cont-init.d/20-nginx.sh rename to docker/ha-addon-rootfs/etc/cont-init.d/20-nginx.sh diff --git a/docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh b/docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh similarity index 100% rename from docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh rename to docker/ha-addon-rootfs/etc/cont-init.d/30-dirs.sh diff --git a/docker/hassio-rootfs/etc/nginx/includes/mime.types b/docker/ha-addon-rootfs/etc/nginx/includes/mime.types similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/mime.types rename to docker/ha-addon-rootfs/etc/nginx/includes/mime.types diff --git a/docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf b/docker/ha-addon-rootfs/etc/nginx/includes/proxy_params.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/proxy_params.conf rename to docker/ha-addon-rootfs/etc/nginx/includes/proxy_params.conf diff --git a/docker/hassio-rootfs/etc/nginx/includes/server_params.conf b/docker/ha-addon-rootfs/etc/nginx/includes/server_params.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/server_params.conf rename to docker/ha-addon-rootfs/etc/nginx/includes/server_params.conf diff --git a/docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf b/docker/ha-addon-rootfs/etc/nginx/includes/ssl_params.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/includes/ssl_params.conf rename to docker/ha-addon-rootfs/etc/nginx/includes/ssl_params.conf diff --git a/docker/hassio-rootfs/etc/nginx/nginx.conf b/docker/ha-addon-rootfs/etc/nginx/nginx.conf similarity index 100% rename from docker/hassio-rootfs/etc/nginx/nginx.conf rename to docker/ha-addon-rootfs/etc/nginx/nginx.conf diff --git a/docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled b/docker/ha-addon-rootfs/etc/nginx/servers/direct-ssl.disabled similarity index 93% rename from docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled rename to docker/ha-addon-rootfs/etc/nginx/servers/direct-ssl.disabled index c2b8d6567dba..4ebc435dbb20 100644 --- a/docker/hassio-rootfs/etc/nginx/servers/direct-ssl.disabled +++ b/docker/ha-addon-rootfs/etc/nginx/servers/direct-ssl.disabled @@ -10,7 +10,7 @@ server { ssl_certificate_key /ssl/%%keyfile%%; # Clear Hass.io Ingress header - proxy_set_header X-Hassio-Ingress ""; + proxy_set_header X-HA-Ingress ""; # Redirect http requests to https on the same port. # https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/ diff --git a/docker/hassio-rootfs/etc/nginx/servers/direct.disabled b/docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled similarity index 85% rename from docker/hassio-rootfs/etc/nginx/servers/direct.disabled rename to docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled index 51f57cab880e..80300fc6aae6 100644 --- a/docker/hassio-rootfs/etc/nginx/servers/direct.disabled +++ b/docker/ha-addon-rootfs/etc/nginx/servers/direct.disabled @@ -4,7 +4,7 @@ server { include /etc/nginx/includes/server_params.conf; include /etc/nginx/includes/proxy_params.conf; # Clear Hass.io Ingress header - proxy_set_header X-Hassio-Ingress ""; + proxy_set_header X-HA-Ingress ""; location / { proxy_pass http://esphome; diff --git a/docker/hassio-rootfs/etc/nginx/servers/ingress.conf b/docker/ha-addon-rootfs/etc/nginx/servers/ingress.conf similarity index 79% rename from docker/hassio-rootfs/etc/nginx/servers/ingress.conf rename to docker/ha-addon-rootfs/etc/nginx/servers/ingress.conf index 3a800d97e797..9d0d2d3e661b 100644 --- a/docker/hassio-rootfs/etc/nginx/servers/ingress.conf +++ b/docker/ha-addon-rootfs/etc/nginx/servers/ingress.conf @@ -3,8 +3,8 @@ server { include /etc/nginx/includes/server_params.conf; include /etc/nginx/includes/proxy_params.conf; - # Set Hass.io Ingress header - proxy_set_header X-Hassio-Ingress "YES"; + # Set Home Assistant Ingress header + proxy_set_header X-HA-Ingress "YES"; location / { # Only allow from Hass.io supervisor diff --git a/docker/hassio-rootfs/etc/services.d/esphome/finish b/docker/ha-addon-rootfs/etc/services.d/esphome/finish similarity index 100% rename from docker/hassio-rootfs/etc/services.d/esphome/finish rename to docker/ha-addon-rootfs/etc/services.d/esphome/finish diff --git a/docker/hassio-rootfs/etc/services.d/esphome/run b/docker/ha-addon-rootfs/etc/services.d/esphome/run similarity index 96% rename from docker/hassio-rootfs/etc/services.d/esphome/run rename to docker/ha-addon-rootfs/etc/services.d/esphome/run index a0f20d63d640..2c821bf4ee5b 100755 --- a/docker/hassio-rootfs/etc/services.d/esphome/run +++ b/docker/ha-addon-rootfs/etc/services.d/esphome/run @@ -4,7 +4,7 @@ # Runs the ESPHome dashboard # ============================================================================== -export ESPHOME_IS_HASSIO=true +export ESPHOME_IS_HA_ADDON=true if bashio::config.true 'leave_front_door_open'; then export DISABLE_HA_AUTHENTICATION=true @@ -32,4 +32,4 @@ export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache" export PLATFORMIO_GLOBALLIB_DIR=/piolibs bashio::log.info "Starting ESPHome dashboard..." -exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio +exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon diff --git a/docker/hassio-rootfs/etc/services.d/nginx/finish b/docker/ha-addon-rootfs/etc/services.d/nginx/finish similarity index 100% rename from docker/hassio-rootfs/etc/services.d/nginx/finish rename to docker/ha-addon-rootfs/etc/services.d/nginx/finish diff --git a/docker/hassio-rootfs/etc/services.d/nginx/run b/docker/ha-addon-rootfs/etc/services.d/nginx/run similarity index 100% rename from docker/hassio-rootfs/etc/services.d/nginx/run rename to docker/ha-addon-rootfs/etc/services.d/nginx/run diff --git a/esphome/__main__.py b/esphome/__main__.py index 6f57791480a1..a64f096d5425 100644 --- a/esphome/__main__.py +++ b/esphome/__main__.py @@ -661,7 +661,7 @@ def parse_args(argv): "--open-ui", help="Open the dashboard UI in a browser.", action="store_true" ) parser_dashboard.add_argument( - "--hassio", help=argparse.SUPPRESS, action="store_true" + "--ha-addon", help=argparse.SUPPRESS, action="store_true" ) parser_dashboard.add_argument( "--socket", help="Make the dashboard serve under a unix socket", type=str diff --git a/esphome/codegen.py b/esphome/codegen.py index 3ea3df87064d..b862a8ce864d 100644 --- a/esphome/codegen.py +++ b/esphome/codegen.py @@ -63,6 +63,7 @@ uint32, uint64, int32, + int64, const_char_ptr, NAN, esphome_ns, @@ -81,4 +82,5 @@ InternalGPIOPin, gpio_Flags, EntityCategory, + Parented, ) diff --git a/esphome/components/ac_dimmer/ac_dimmer.cpp b/esphome/components/ac_dimmer/ac_dimmer.cpp index a7f1e6f3a9a2..e9af828a9dfc 100644 --- a/esphome/components/ac_dimmer/ac_dimmer.cpp +++ b/esphome/components/ac_dimmer/ac_dimmer.cpp @@ -52,10 +52,10 @@ uint32_t IRAM_ATTR HOT AcDimmerDataStore::timer_intr(uint32_t now) { this->gate_pin.digital_write(false); } - if (time_since_zc < this->enable_time_us) + if (time_since_zc < this->enable_time_us) { // Next event is enable, return time until that event return this->enable_time_us - time_since_zc; - else if (time_since_zc < disable_time_us) { + } else if (time_since_zc < disable_time_us) { // Next event is disable, return time until that event return this->disable_time_us - time_since_zc; } @@ -74,9 +74,10 @@ uint32_t IRAM_ATTR HOT timer_interrupt() { uint32_t min_dt_us = 1000; uint32_t now = micros(); for (auto *dimmer : all_dimmers) { - if (dimmer == nullptr) + if (dimmer == nullptr) { // no more dimmers break; + } uint32_t res = dimmer->timer_intr(now); if (res != 0 && res < min_dt_us) min_dt_us = res; @@ -212,12 +213,13 @@ void AcDimmer::dump_config() { LOG_PIN(" Zero-Cross Pin: ", this->zero_cross_pin_); ESP_LOGCONFIG(TAG, " Min Power: %.1f%%", this->store_.min_power / 10.0f); ESP_LOGCONFIG(TAG, " Init with half cycle: %s", YESNO(this->init_with_half_cycle_)); - if (method_ == DIM_METHOD_LEADING_PULSE) + if (method_ == DIM_METHOD_LEADING_PULSE) { ESP_LOGCONFIG(TAG, " Method: leading pulse"); - else if (method_ == DIM_METHOD_LEADING) + } else if (method_ == DIM_METHOD_LEADING) { ESP_LOGCONFIG(TAG, " Method: leading"); - else + } else { ESP_LOGCONFIG(TAG, " Method: trailing"); + } LOG_FLOAT_OUTPUT(this); ESP_LOGV(TAG, " Estimated Frequency: %.3fHz", 1e6f / this->store_.cycle_time_us / 2); diff --git a/esphome/components/adalight/adalight_light_effect.h b/esphome/components/adalight/adalight_light_effect.h index b757191864fc..72faf4426963 100644 --- a/esphome/components/adalight/adalight_light_effect.h +++ b/esphome/components/adalight/adalight_light_effect.h @@ -13,7 +13,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U public: AdalightLightEffect(const std::string &name); - public: void start() override; void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; @@ -30,7 +29,6 @@ class AdalightLightEffect : public light::AddressableLightEffect, public uart::U void blank_all_leds_(light::AddressableLight &it); Frame parse_frame_(light::AddressableLight &it); - protected: uint32_t last_ack_{0}; uint32_t last_byte_{0}; uint32_t last_reset_{0}; diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp index 0a439f8b8da5..ad9cf29b6fdc 100644 --- a/esphome/components/adc/adc_sensor.cpp +++ b/esphome/components/adc/adc_sensor.cpp @@ -15,6 +15,11 @@ namespace esphome { namespace adc { static const char *const TAG = "adc"; +// 13 bits for S3 / 12 bit for all other esp32 variants +// create a const to avoid the repated cast to enum +#ifdef USE_ESP32 +static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast(ADC_WIDTH_MAX - 1); +#endif void ADCSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str()); @@ -23,14 +28,14 @@ void ADCSensor::setup() { #endif #ifdef USE_ESP32 - adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_width(ADC_WIDTH_MAX_SOC_BITS); if (!autorange_) { adc1_config_channel_atten(channel_, attenuation_); } // load characteristics for each attenuation for (int i = 0; i < (int) ADC_ATTEN_MAX; i++) { - auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_BIT_12, + auto cal_value = esp_adc_cal_characterize(ADC_UNIT_1, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS, 1100, // default vref &cal_characteristics_[i]); switch (cal_value) { @@ -65,9 +70,9 @@ void ADCSensor::dump_config() { #ifdef USE_ESP32 LOG_PIN(" Pin: ", pin_); - if (autorange_) + if (autorange_) { ESP_LOGCONFIG(TAG, " Attenuation: auto"); - else + } else { switch (this->attenuation_) { case ADC_ATTEN_DB_0: ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)"); @@ -84,6 +89,7 @@ void ADCSensor::dump_config() { default: // This is to satisfy the unused ADC_ATTEN_MAX break; } + } #endif // USE_ESP32 LOG_UPDATE_INTERVAL(this); } diff --git a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp index 6b6418f7e611..2e7a1fb02497 100644 --- a/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp +++ b/esphome/components/airthings_wave_mini/airthings_wave_mini.cpp @@ -24,7 +24,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt case ESP_GATTC_SEARCH_CMPL_EVT: { this->handle_ = 0; - auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); + auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); if (chr == nullptr) { ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), sensors_data_characteristic_uuid_.to_string().c_str()); @@ -56,7 +56,7 @@ void AirthingsWaveMini::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } void AirthingsWaveMini::read_sensors_(uint8_t *raw_value, uint16_t value_len) { - auto value = (WaveMiniReadings *) raw_value; + auto *value = (WaveMiniReadings *) raw_value; if (sizeof(WaveMiniReadings) <= value_len) { this->humidity_sensor_->publish_state(value->humidity / 100.0f); diff --git a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp index 79f2cb774158..02ed33b87a84 100644 --- a/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp +++ b/esphome/components/airthings_wave_plus/airthings_wave_plus.cpp @@ -24,7 +24,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt case ESP_GATTC_SEARCH_CMPL_EVT: { this->handle_ = 0; - auto chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); + auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_data_characteristic_uuid_); if (chr == nullptr) { ESP_LOGW(TAG, "No sensor characteristic found at service %s char %s", service_uuid_.to_string().c_str(), sensors_data_characteristic_uuid_.to_string().c_str()); @@ -56,7 +56,7 @@ void AirthingsWavePlus::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt } void AirthingsWavePlus::read_sensors_(uint8_t *raw_value, uint16_t value_len) { - auto value = (WavePlusReadings *) raw_value; + auto *value = (WavePlusReadings *) raw_value; if (sizeof(WavePlusReadings) <= value_len) { ESP_LOGD(TAG, "version = %d", value->version); diff --git a/esphome/components/am2320/am2320.cpp b/esphome/components/am2320/am2320.cpp index c06a2a34d767..8ab48a348edd 100644 --- a/esphome/components/am2320/am2320.cpp +++ b/esphome/components/am2320/am2320.cpp @@ -19,12 +19,14 @@ uint16_t crc_16(uint8_t *ptr, uint8_t length) { //------------------------------ while (length--) { crc ^= *ptr++; - for (i = 0; i < 8; i++) + for (i = 0; i < 8; i++) { if ((crc & 0x01) != 0) { crc >>= 1; crc ^= 0xA001; - } else + } else { crc >>= 1; + } + } } return crc; } diff --git a/esphome/components/am43/am43.cpp b/esphome/components/am43/am43.cpp index a62e3bb6dfab..1c4bad64c332 100644 --- a/esphome/components/am43/am43.cpp +++ b/esphome/components/am43/am43.cpp @@ -39,7 +39,7 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); if (chr == nullptr) { if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", @@ -75,13 +75,14 @@ void Am43::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_i if (this->current_sensor_ > 0) { if (this->illuminance_ != nullptr) { - auto packet = this->encoder_->get_light_level_request(); + auto *packet = this->encoder_->get_light_level_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) + if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } } this->current_sensor_ = 0; } @@ -99,7 +100,7 @@ void Am43::update() { } if (this->current_sensor_ == 0) { if (this->battery_ != nullptr) { - auto packet = this->encoder_->get_battery_level_request(); + auto *packet = this->encoder_->get_battery_level_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); diff --git a/esphome/components/am43/cover/__init__.py b/esphome/components/am43/cover/__init__.py index 1ab0edbe7822..79eeb2eef365 100644 --- a/esphome/components/am43/cover/__init__.py +++ b/esphome/components/am43/cover/__init__.py @@ -5,7 +5,7 @@ CODEOWNERS = ["@buxtronix"] DEPENDENCIES = ["ble_client"] -AUTO_LOAD = ["am43"] +AUTO_LOAD = ["am43", "sensor"] CONF_INVERT_POSITION = "invert_position" diff --git a/esphome/components/am43/cover/am43_cover.cpp b/esphome/components/am43/cover/am43_cover.cpp index 274c527760fa..39089e73c01c 100644 --- a/esphome/components/am43/cover/am43_cover.cpp +++ b/esphome/components/am43/cover/am43_cover.cpp @@ -25,15 +25,16 @@ void Am43Component::setup() { void Am43Component::loop() { if (this->node_state == espbt::ClientState::ESTABLISHED && !this->logged_in_) { - auto packet = this->encoder_->get_send_pin_request(this->pin_); + auto *packet = this->encoder_->get_send_pin_request(this->pin_); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); ESP_LOGI(TAG, "[%s] Logging into AM43", this->get_name().c_str()); - if (status) + if (status) { ESP_LOGW(TAG, "[%s] Error writing set_pin to device, error = %d", this->get_name().c_str(), status); - else + } else { this->logged_in_ = true; + } } } @@ -51,7 +52,7 @@ void Am43Component::control(const CoverCall &call) { return; } if (call.get_stop()) { - auto packet = this->encoder_->get_stop_request(); + auto *packet = this->encoder_->get_stop_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); @@ -63,7 +64,7 @@ void Am43Component::control(const CoverCall &call) { if (this->invert_position_) pos = 1 - pos; - auto packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); + auto *packet = this->encoder_->get_set_position_request(100 - (uint8_t)(pos * 100)); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); @@ -80,7 +81,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - auto chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); + auto *chr = this->parent_->get_characteristic(AM43_SERVICE_UUID, AM43_CHARACTERISTIC_UUID); if (chr == nullptr) { if (this->parent_->get_characteristic(AM43_TUYA_SERVICE_UUID, AM43_TUYA_CHARACTERISTIC_UUID) != nullptr) { ESP_LOGE(TAG, "[%s] Detected a Tuya AM43 which is not supported, sorry.", this->get_name().c_str()); @@ -120,7 +121,7 @@ void Am43Component::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_ if (this->decoder_->has_pin_response()) { if (this->decoder_->pin_ok_) { ESP_LOGI(TAG, "[%s] AM43 pin accepted.", this->get_name().c_str()); - auto packet = this->encoder_->get_position_request(); + auto *packet = this->encoder_->get_position_request(); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, packet->length, packet->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); diff --git a/esphome/components/anova/anova.cpp b/esphome/components/anova/anova.cpp index 5d9afddc74d5..39b1187caf76 100644 --- a/esphome/components/anova/anova.cpp +++ b/esphome/components/anova/anova.cpp @@ -40,7 +40,7 @@ void Anova::control(const ClimateCall &call) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); } if (call.get_target_temperature().has_value()) { - auto pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); + auto *pkt = this->codec_->get_set_target_temp_request(*call.get_target_temperature()); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); if (status) @@ -57,7 +57,7 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ break; } case ESP_GATTC_SEARCH_CMPL_EVT: { - auto chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); + auto *chr = this->parent_->get_characteristic(ANOVA_SERVICE_UUID, ANOVA_CHARACTERISTIC_UUID); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] No control service found at device, not an Anova..?", this->get_name().c_str()); ESP_LOGW(TAG, "[%s] Note, this component does not currently support Anova Nano.", this->get_name().c_str()); @@ -114,9 +114,10 @@ void Anova::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_ auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, pkt->length, pkt->data, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); - if (status) + if (status) { ESP_LOGW(TAG, "[%s] esp_ble_gattc_write_char failed, status=%d", this->parent_->address_str().c_str(), status); + } } } break; @@ -133,7 +134,7 @@ void Anova::update() { return; if (this->current_request_ < 2) { - auto pkt = this->codec_->get_read_device_status_request(); + auto *pkt = this->codec_->get_read_device_status_request(); if (this->current_request_ == 0) this->codec_->get_set_unit_request(this->fahrenheit_ ? 'f' : 'c'); auto status = esp_ble_gattc_write_char(this->parent_->gattc_if, this->parent_->conn_id, this->char_handle_, diff --git a/esphome/components/anova/anova.h b/esphome/components/anova/anova.h index 4f8f0d0ee229..3d1394980ab6 100644 --- a/esphome/components/anova/anova.h +++ b/esphome/components/anova/anova.h @@ -36,7 +36,7 @@ class Anova : public climate::Climate, public esphome::ble_client::BLEClientNode traits.set_visual_temperature_step(0.1); return traits; } - void set_unit_of_measurement(const char *); + void set_unit_of_measurement(const char *unit); protected: std::unique_ptr codec_; diff --git a/esphome/components/apds9960/apds9960.cpp b/esphome/components/apds9960/apds9960.cpp index 9ee873ac6424..5ba3afbaccbf 100644 --- a/esphome/components/apds9960/apds9960.cpp +++ b/esphome/components/apds9960/apds9960.cpp @@ -225,9 +225,10 @@ void APDS9960::read_gesture_data_() { uint8_t fifo_level; APDS9960_WARNING_CHECK(this->read_byte(0xAE, &fifo_level), "Reading FIFO level failed."); - if (fifo_level == 0) + if (fifo_level == 0) { // no data to process return; + } APDS9960_WARNING_CHECK(fifo_level <= 32, "FIFO level has invalid value.") diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto index dca722dca569..bd39893825ad 100644 --- a/esphome/components/api/api.proto +++ b/esphome/components/api/api.proto @@ -41,6 +41,7 @@ service APIConnection { rpc number_command (NumberCommandRequest) returns (void) {} rpc select_command (SelectCommandRequest) returns (void) {} rpc button_command (ButtonCommandRequest) returns (void) {} + rpc lock_command (LockCommandRequest) returns (void) {} } @@ -95,6 +96,9 @@ message HelloResponse { // and only exists for debugging/logging purposes. // For example "ESPHome v1.10.0 on ESP8266" string server_info = 3; + + // The name of the server (App.get_name()) + string name = 4; } // Message sent at the beginning of each connection to authenticate the client @@ -525,6 +529,7 @@ message ListEntitiesSwitchResponse { bool assumed_state = 6; bool disabled_by_default = 7; EntityCategory entity_category = 8; + string device_class = 9; } message SwitchStateResponse { option (id) = 26; @@ -953,6 +958,63 @@ message SelectCommandRequest { string state = 2; } + +// ==================== LOCK ==================== +enum LockState { + LOCK_STATE_NONE = 0; + LOCK_STATE_LOCKED = 1; + LOCK_STATE_UNLOCKED = 2; + LOCK_STATE_JAMMED = 3; + LOCK_STATE_LOCKING = 4; + LOCK_STATE_UNLOCKING = 5; +} +enum LockCommand { + LOCK_UNLOCK = 0; + LOCK_LOCK = 1; + LOCK_OPEN = 2; +} +message ListEntitiesLockResponse { + option (id) = 58; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_LOCK"; + + string object_id = 1; + fixed32 key = 2; + string name = 3; + string unique_id = 4; + + string icon = 5; + bool disabled_by_default = 6; + EntityCategory entity_category = 7; + bool assumed_state = 8; + + bool supports_open = 9; + bool requires_code = 10; + + # Not yet implemented: + string code_format = 11; +} +message LockStateResponse { + option (id) = 59; + option (source) = SOURCE_SERVER; + option (ifdef) = "USE_LOCK"; + option (no_delay) = true; + fixed32 key = 1; + LockState state = 2; +} +message LockCommandRequest { + option (id) = 60; + option (source) = SOURCE_CLIENT; + option (ifdef) = "USE_LOCK"; + option (no_delay) = true; + fixed32 key = 1; + LockCommand command = 2; + + # Not yet implemented: + bool has_code = 3; + string code = 4; +} + // ==================== BUTTON ==================== message ListEntitiesButtonResponse { option (id) = 61; @@ -977,3 +1039,4 @@ message ButtonCommandRequest { fixed32 key = 1; } + diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 1f629c2c8510..d9ce6cd79e5b 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -255,7 +255,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { // Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit. #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -bool APIConnection::send_fan_state(fan::FanState *fan) { +bool APIConnection::send_fan_state(fan::Fan *fan) { if (!this->state_subscription_) return false; @@ -273,7 +273,7 @@ bool APIConnection::send_fan_state(fan::FanState *fan) { resp.direction = static_cast(fan->direction); return this->send_fan_state_response(resp); } -bool APIConnection::send_fan_info(fan::FanState *fan) { +bool APIConnection::send_fan_info(fan::Fan *fan) { auto traits = fan->get_traits(); ListEntitiesFanResponse msg; msg.key = fan->get_object_id_hash(); @@ -290,7 +290,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { return this->send_list_entities_fan_response(msg); } void APIConnection::fan_command(const FanCommandRequest &msg) { - fan::FanState *fan = App.get_fan_by_key(msg.key); + fan::Fan *fan = App.get_fan_by_key(msg.key); if (fan == nullptr) return; @@ -462,6 +462,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) { msg.assumed_state = a_switch->assumed_state(); msg.disabled_by_default = a_switch->is_disabled_by_default(); msg.entity_category = static_cast(a_switch->get_entity_category()); + msg.device_class = a_switch->get_device_class(); return this->send_list_entities_switch_response(msg); } void APIConnection::switch_command(const SwitchCommandRequest &msg) { @@ -469,10 +470,11 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) { if (a_switch == nullptr) return; - if (msg.state) + if (msg.state) { a_switch->turn_on(); - else + } else { a_switch->turn_off(); + } } #endif @@ -699,6 +701,49 @@ void APIConnection::button_command(const ButtonCommandRequest &msg) { } #endif +#ifdef USE_LOCK +bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) { + if (!this->state_subscription_) + return false; + + LockStateResponse resp{}; + resp.key = a_lock->get_object_id_hash(); + resp.state = static_cast(state); + return this->send_lock_state_response(resp); +} +bool APIConnection::send_lock_info(lock::Lock *a_lock) { + ListEntitiesLockResponse msg; + msg.key = a_lock->get_object_id_hash(); + msg.object_id = a_lock->get_object_id(); + msg.name = a_lock->get_name(); + msg.unique_id = get_default_unique_id("lock", a_lock); + msg.icon = a_lock->get_icon(); + msg.assumed_state = a_lock->traits.get_assumed_state(); + msg.disabled_by_default = a_lock->is_disabled_by_default(); + msg.entity_category = static_cast(a_lock->get_entity_category()); + msg.supports_open = a_lock->traits.get_supports_open(); + msg.requires_code = a_lock->traits.get_requires_code(); + return this->send_list_entities_lock_response(msg); +} +void APIConnection::lock_command(const LockCommandRequest &msg) { + lock::Lock *a_lock = App.get_lock_by_key(msg.key); + if (a_lock == nullptr) + return; + + switch (msg.command) { + case enums::LOCK_UNLOCK: + a_lock->unlock(); + break; + case enums::LOCK_LOCK: + a_lock->lock(); + break; + case enums::LOCK_OPEN: + a_lock->open(); + break; + } +} +#endif + #ifdef USE_ESP32_CAMERA void APIConnection::send_camera_state(std::shared_ptr image) { if (!this->state_subscription_) @@ -766,6 +811,8 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) { resp.api_version_major = 1; resp.api_version_minor = 6; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; + resp.name = App.get_name(); + this->connection_state_ = ConnectionState::CONNECTED; return resp; } @@ -803,15 +850,16 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) { resp.project_version = ESPHOME_PROJECT_VERSION; #endif #ifdef USE_WEBSERVER - resp.webserver_port = WEBSERVER_PORT; + resp.webserver_port = USE_WEBSERVER_PORT; #endif return resp; } void APIConnection::on_home_assistant_state_response(const HomeAssistantStateResponse &msg) { - for (auto &it : this->parent_->get_state_subs()) + for (auto &it : this->parent_->get_state_subs()) { if (it.entity_id == msg.entity_id && it.attribute.value() == msg.attribute) { it.callback(msg.state); } + } } void APIConnection::execute_service(const ExecuteServiceRequest &msg) { bool found = false; diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 72697b59110a..10f0becc54a4 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -32,8 +32,8 @@ class APIConnection : public APIServerConnection { void cover_command(const CoverCommandRequest &msg) override; #endif #ifdef USE_FAN - bool send_fan_state(fan::FanState *fan); - bool send_fan_info(fan::FanState *fan); + bool send_fan_state(fan::Fan *fan); + bool send_fan_info(fan::Fan *fan); void fan_command(const FanCommandRequest &msg) override; #endif #ifdef USE_LIGHT @@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection { #ifdef USE_BUTTON bool send_button_info(button::Button *button); void button_command(const ButtonCommandRequest &msg) override; +#endif +#ifdef USE_LOCK + bool send_lock_state(lock::Lock *a_lock, lock::LockState state); + bool send_lock_info(lock::Lock *a_lock); + void lock_command(const LockCommandRequest &msg) override; #endif bool send_log_message(int level, const char *tag, const char *line); void send_homeassistant_service_call(const HomeassistantServiceResponse &call) { diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index d9eadb2aaae8..9f0ab82a5287 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -3,6 +3,7 @@ #include "esphome/core/log.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/application.h" #include "proto.h" #include @@ -302,9 +303,16 @@ APIError APINoiseFrameHelper::state_action_() { } if (state_ == State::SERVER_HELLO) { // send server hello - uint8_t msg[1]; - msg[0] = 0x01; // chosen proto - aerr = write_frame_(msg, 1); + std::vector msg; + // chosen proto + msg.push_back(0x01); + + // node name, terminated by null byte + const std::string &name = App.get_name(); + const uint8_t *name_ptr = reinterpret_cast(name.c_str()); + msg.insert(msg.end(), name_ptr, name_ptr + name.size() + 1); + + aerr = write_frame_(msg.data(), msg.size()); if (aerr != APIError::OK) return aerr; diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp index 5b6853c27603..5a78587473b8 100644 --- a/esphome/components/api/api_pb2.cpp +++ b/esphome/components/api/api_pb2.cpp @@ -278,6 +278,36 @@ template<> const char *proto_enum_to_string(enums::NumberMode return "UNKNOWN"; } } +template<> const char *proto_enum_to_string(enums::LockState value) { + switch (value) { + case enums::LOCK_STATE_NONE: + return "LOCK_STATE_NONE"; + case enums::LOCK_STATE_LOCKED: + return "LOCK_STATE_LOCKED"; + case enums::LOCK_STATE_UNLOCKED: + return "LOCK_STATE_UNLOCKED"; + case enums::LOCK_STATE_JAMMED: + return "LOCK_STATE_JAMMED"; + case enums::LOCK_STATE_LOCKING: + return "LOCK_STATE_LOCKING"; + case enums::LOCK_STATE_UNLOCKING: + return "LOCK_STATE_UNLOCKING"; + default: + return "UNKNOWN"; + } +} +template<> const char *proto_enum_to_string(enums::LockCommand value) { + switch (value) { + case enums::LOCK_UNLOCK: + return "LOCK_UNLOCK"; + case enums::LOCK_LOCK: + return "LOCK_LOCK"; + case enums::LOCK_OPEN: + return "LOCK_OPEN"; + default: + return "UNKNOWN"; + } +} bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { switch (field_id) { case 1: { @@ -319,6 +349,10 @@ bool HelloResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) this->server_info = value.as_string(); return true; } + case 4: { + this->name = value.as_string(); + return true; + } default: return false; } @@ -327,6 +361,7 @@ void HelloResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_uint32(1, this->api_version_major); buffer.encode_uint32(2, this->api_version_minor); buffer.encode_string(3, this->server_info); + buffer.encode_string(4, this->name); } #ifdef HAS_PROTO_MESSAGE_DUMP void HelloResponse::dump_to(std::string &out) const { @@ -345,6 +380,10 @@ void HelloResponse::dump_to(std::string &out) const { out.append(" server_info: "); out.append("'").append(this->server_info).append("'"); out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -2138,6 +2177,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel this->icon = value.as_string(); return true; } + case 9: { + this->device_class = value.as_string(); + return true; + } default: return false; } @@ -2161,6 +2204,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(6, this->assumed_state); buffer.encode_bool(7, this->disabled_by_default); buffer.encode_enum(8, this->entity_category); + buffer.encode_string(9, this->device_class); } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesSwitchResponse::dump_to(std::string &out) const { @@ -2198,6 +2242,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const { out.append(" entity_category: "); out.append(proto_enum_to_string(this->entity_category)); out.append("\n"); + + out.append(" device_class: "); + out.append("'").append(this->device_class).append("'"); + out.append("\n"); out.append("}"); } #endif @@ -4177,6 +4225,234 @@ void SelectCommandRequest::dump_to(std::string &out) const { out.append("}"); } #endif +bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 6: { + this->disabled_by_default = value.as_bool(); + return true; + } + case 7: { + this->entity_category = value.as_enum(); + return true; + } + case 8: { + this->assumed_state = value.as_bool(); + return true; + } + case 9: { + this->supports_open = value.as_bool(); + return true; + } + case 10: { + this->requires_code = value.as_bool(); + return true; + } + default: + return false; + } +} +bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 1: { + this->object_id = value.as_string(); + return true; + } + case 3: { + this->name = value.as_string(); + return true; + } + case 4: { + this->unique_id = value.as_string(); + return true; + } + case 5: { + this->icon = value.as_string(); + return true; + } + case 11: { + this->code_format = value.as_string(); + return true; + } + default: + return false; + } +} +bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 2: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_string(1, this->object_id); + buffer.encode_fixed32(2, this->key); + buffer.encode_string(3, this->name); + buffer.encode_string(4, this->unique_id); + buffer.encode_string(5, this->icon); + buffer.encode_bool(6, this->disabled_by_default); + buffer.encode_enum(7, this->entity_category); + buffer.encode_bool(8, this->assumed_state); + buffer.encode_bool(9, this->supports_open); + buffer.encode_bool(10, this->requires_code); + buffer.encode_string(11, this->code_format); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void ListEntitiesLockResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("ListEntitiesLockResponse {\n"); + out.append(" object_id: "); + out.append("'").append(this->object_id).append("'"); + out.append("\n"); + + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" name: "); + out.append("'").append(this->name).append("'"); + out.append("\n"); + + out.append(" unique_id: "); + out.append("'").append(this->unique_id).append("'"); + out.append("\n"); + + out.append(" icon: "); + out.append("'").append(this->icon).append("'"); + out.append("\n"); + + out.append(" disabled_by_default: "); + out.append(YESNO(this->disabled_by_default)); + out.append("\n"); + + out.append(" entity_category: "); + out.append(proto_enum_to_string(this->entity_category)); + out.append("\n"); + + out.append(" assumed_state: "); + out.append(YESNO(this->assumed_state)); + out.append("\n"); + + out.append(" supports_open: "); + out.append(YESNO(this->supports_open)); + out.append("\n"); + + out.append(" requires_code: "); + out.append(YESNO(this->requires_code)); + out.append("\n"); + + out.append(" code_format: "); + out.append("'").append(this->code_format).append("'"); + out.append("\n"); + out.append("}"); +} +#endif +bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->state = value.as_enum(); + return true; + } + default: + return false; + } +} +bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void LockStateResponse::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_enum(2, this->state); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void LockStateResponse::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("LockStateResponse {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" state: "); + out.append(proto_enum_to_string(this->state)); + out.append("\n"); + out.append("}"); +} +#endif +bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { + switch (field_id) { + case 2: { + this->command = value.as_enum(); + return true; + } + case 3: { + this->has_code = value.as_bool(); + return true; + } + default: + return false; + } +} +bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) { + switch (field_id) { + case 4: { + this->code = value.as_string(); + return true; + } + default: + return false; + } +} +bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { + switch (field_id) { + case 1: { + this->key = value.as_fixed32(); + return true; + } + default: + return false; + } +} +void LockCommandRequest::encode(ProtoWriteBuffer buffer) const { + buffer.encode_fixed32(1, this->key); + buffer.encode_enum(2, this->command); + buffer.encode_bool(3, this->has_code); + buffer.encode_string(4, this->code); +} +#ifdef HAS_PROTO_MESSAGE_DUMP +void LockCommandRequest::dump_to(std::string &out) const { + __attribute__((unused)) char buffer[64]; + out.append("LockCommandRequest {\n"); + out.append(" key: "); + sprintf(buffer, "%u", this->key); + out.append(buffer); + out.append("\n"); + + out.append(" command: "); + out.append(proto_enum_to_string(this->command)); + out.append("\n"); + + out.append(" has_code: "); + out.append(YESNO(this->has_code)); + out.append("\n"); + + out.append(" code: "); + out.append("'").append(this->code).append("'"); + out.append("\n"); + out.append("}"); +} +#endif bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { switch (field_id) { case 6: { @@ -4239,7 +4515,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const { } #ifdef HAS_PROTO_MESSAGE_DUMP void ListEntitiesButtonResponse::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ListEntitiesButtonResponse {\n"); out.append(" object_id: "); out.append("'").append(this->object_id).append("'"); @@ -4289,7 +4565,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) { void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); } #ifdef HAS_PROTO_MESSAGE_DUMP void ButtonCommandRequest::dump_to(std::string &out) const { - char buffer[64]; + __attribute__((unused)) char buffer[64]; out.append("ButtonCommandRequest {\n"); out.append(" key: "); sprintf(buffer, "%u", this->key); diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h index e92b2fa4b666..28c0a7ce887d 100644 --- a/esphome/components/api/api_pb2.h +++ b/esphome/components/api/api_pb2.h @@ -128,6 +128,19 @@ enum NumberMode : uint32_t { NUMBER_MODE_BOX = 1, NUMBER_MODE_SLIDER = 2, }; +enum LockState : uint32_t { + LOCK_STATE_NONE = 0, + LOCK_STATE_LOCKED = 1, + LOCK_STATE_UNLOCKED = 2, + LOCK_STATE_JAMMED = 3, + LOCK_STATE_LOCKING = 4, + LOCK_STATE_UNLOCKING = 5, +}; +enum LockCommand : uint32_t { + LOCK_UNLOCK = 0, + LOCK_LOCK = 1, + LOCK_OPEN = 2, +}; } // namespace enums @@ -147,6 +160,7 @@ class HelloResponse : public ProtoMessage { uint32_t api_version_major{0}; uint32_t api_version_minor{0}; std::string server_info{}; + std::string name{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -566,6 +580,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage { bool assumed_state{false}; bool disabled_by_default{false}; enums::EntityCategory entity_category{}; + std::string device_class{}; void encode(ProtoWriteBuffer buffer) const override; #ifdef HAS_PROTO_MESSAGE_DUMP void dump_to(std::string &out) const override; @@ -1048,6 +1063,58 @@ class SelectCommandRequest : public ProtoMessage { bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; }; +class ListEntitiesLockResponse : public ProtoMessage { + public: + std::string object_id{}; + uint32_t key{0}; + std::string name{}; + std::string unique_id{}; + std::string icon{}; + bool disabled_by_default{false}; + enums::EntityCategory entity_category{}; + bool assumed_state{false}; + bool supports_open{false}; + bool requires_code{false}; + std::string code_format{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class LockStateResponse : public ProtoMessage { + public: + uint32_t key{0}; + enums::LockState state{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; +class LockCommandRequest : public ProtoMessage { + public: + uint32_t key{0}; + enums::LockCommand command{}; + bool has_code{false}; + std::string code{}; + void encode(ProtoWriteBuffer buffer) const override; +#ifdef HAS_PROTO_MESSAGE_DUMP + void dump_to(std::string &out) const override; +#endif + + protected: + bool decode_32bit(uint32_t field_id, Proto32Bit value) override; + bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; + bool decode_varint(uint32_t field_id, ProtoVarInt value) override; +}; class ListEntitiesButtonResponse : public ProtoMessage { public: std::string object_id{}; diff --git a/esphome/components/api/api_pb2_service.cpp b/esphome/components/api/api_pb2_service.cpp index 567fbf02c9dd..d981a3bf4ea9 100644 --- a/esphome/components/api/api_pb2_service.cpp +++ b/esphome/components/api/api_pb2_service.cpp @@ -282,6 +282,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon #endif #ifdef USE_SELECT #endif +#ifdef USE_LOCK +bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_list_entities_lock_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 58); +} +#endif +#ifdef USE_LOCK +bool APIServerConnectionBase::send_lock_state_response(const LockStateResponse &msg) { +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "send_lock_state_response: %s", msg.dump().c_str()); +#endif + return this->send_message_(msg, 59); +} +#endif +#ifdef USE_LOCK +#endif #ifdef USE_BUTTON bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) { #ifdef HAS_PROTO_MESSAGE_DUMP @@ -523,6 +541,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str()); #endif this->on_select_command_request(msg); +#endif + break; + } + case 60: { +#ifdef USE_LOCK + LockCommandRequest msg; + msg.decode(msg_data, msg_size); +#ifdef HAS_PROTO_MESSAGE_DUMP + ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str()); +#endif + this->on_lock_command_request(msg); #endif break; } @@ -771,6 +800,19 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest & this->button_command(msg); } #endif +#ifdef USE_LOCK +void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) { + if (!this->is_connection_setup()) { + this->on_no_setup_connection(); + return; + } + if (!this->is_authenticated()) { + this->on_unauthenticated_access(); + return; + } + this->lock_command(msg); +} +#endif } // namespace api } // namespace esphome diff --git a/esphome/components/api/api_pb2_service.h b/esphome/components/api/api_pb2_service.h index 50b08d3ec4d5..5aaf831c9108 100644 --- a/esphome/components/api/api_pb2_service.h +++ b/esphome/components/api/api_pb2_service.h @@ -130,6 +130,15 @@ class APIServerConnectionBase : public ProtoService { #ifdef USE_SELECT virtual void on_select_command_request(const SelectCommandRequest &value){}; #endif +#ifdef USE_LOCK + bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg); +#endif +#ifdef USE_LOCK + bool send_lock_state_response(const LockStateResponse &msg); +#endif +#ifdef USE_LOCK + virtual void on_lock_command_request(const LockCommandRequest &value){}; +#endif #ifdef USE_BUTTON bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg); #endif @@ -180,6 +189,9 @@ class APIServerConnection : public APIServerConnectionBase { #endif #ifdef USE_BUTTON virtual void button_command(const ButtonCommandRequest &msg) = 0; +#endif +#ifdef USE_LOCK + virtual void lock_command(const LockCommandRequest &msg) = 0; #endif protected: void on_hello_request(const HelloRequest &msg) override; @@ -221,6 +233,9 @@ class APIServerConnection : public APIServerConnectionBase { #ifdef USE_BUTTON void on_button_command_request(const ButtonCommandRequest &msg) override; #endif +#ifdef USE_LOCK + void on_lock_command_request(const LockCommandRequest &msg) override; +#endif }; } // namespace api diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 25081a809a91..4521cc5bfc06 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -24,7 +24,7 @@ static const char *const TAG = "api"; void APIServer::setup() { ESP_LOGCONFIG(TAG, "Setting up Home Assistant API server..."); this->setup_controller(); - socket_ = socket::socket(AF_INET, SOCK_STREAM, 0); + socket_ = socket::socket_ip(SOCK_STREAM, 0); if (socket_ == nullptr) { ESP_LOGW(TAG, "Could not create socket."); this->mark_failed(); @@ -43,13 +43,16 @@ void APIServer::setup() { return; } - struct sockaddr_in server; - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_addr.s_addr = ESPHOME_INADDR_ANY; - server.sin_port = htons(this->port_); + struct sockaddr_storage server; - err = socket_->bind((struct sockaddr *) &server, sizeof(server)); + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + if (sl == 0) { + ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + this->mark_failed(); + return; + } + + err = socket_->bind((struct sockaddr *) &server, sl); if (err != 0) { ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); this->mark_failed(); @@ -80,9 +83,10 @@ void APIServer::setup() { if (esp32_camera::global_esp32_camera != nullptr && !esp32_camera::global_esp32_camera->is_internal()) { esp32_camera::global_esp32_camera->add_image_callback( [this](const std::shared_ptr &image) { - for (auto &c : this->clients_) + for (auto &c : this->clients_) { if (!c->remove_) c->send_camera_state(image); + } }); } #endif @@ -188,7 +192,7 @@ void APIServer::on_cover_update(cover::Cover *obj) { #endif #ifdef USE_FAN -void APIServer::on_fan_update(fan::FanState *obj) { +void APIServer::on_fan_update(fan::Fan *obj) { if (obj->is_internal()) return; for (auto &c : this->clients_) @@ -259,6 +263,15 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state) } #endif +#ifdef USE_LOCK +void APIServer::on_lock_update(lock::Lock *obj) { + if (obj->is_internal()) + return; + for (auto &c : this->clients_) + c->send_lock_state(obj, obj->state); +} +#endif + float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; } void APIServer::set_port(uint16_t port) { this->port_ = port; } APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 056d9f54f23d..3214da5b3d62 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -44,7 +44,7 @@ class APIServer : public Component, public Controller { void on_cover_update(cover::Cover *obj) override; #endif #ifdef USE_FAN - void on_fan_update(fan::FanState *obj) override; + void on_fan_update(fan::Fan *obj) override; #endif #ifdef USE_LIGHT void on_light_update(light::LightState *obj) override; @@ -66,6 +66,9 @@ class APIServer : public Component, public Controller { #endif #ifdef USE_SELECT void on_select_update(select::Select *obj, const std::string &state) override; +#endif +#ifdef USE_LOCK + void on_lock_update(lock::Lock *obj) override; #endif void send_homeassistant_service_call(const HomeassistantServiceResponse &call); void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); } diff --git a/esphome/components/api/list_entities.cpp b/esphome/components/api/list_entities.cpp index cb97df8ca1fe..fb0dfa3d0537 100644 --- a/esphome/components/api/list_entities.cpp +++ b/esphome/components/api/list_entities.cpp @@ -16,7 +16,7 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); } #endif #ifdef USE_FAN -bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); } +bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); } #endif #ifdef USE_LIGHT bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); } @@ -35,6 +35,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) return this->client_->send_text_sensor_info(text_sensor); } #endif +#ifdef USE_LOCK +bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); } +#endif bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); } ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client) diff --git a/esphome/components/api/list_entities.h b/esphome/components/api/list_entities.h index 714edaa91f1f..bfceb39ebf75 100644 --- a/esphome/components/api/list_entities.h +++ b/esphome/components/api/list_entities.h @@ -19,7 +19,7 @@ class ListEntitiesIterator : public ComponentIterator { bool on_cover(cover::Cover *cover) override; #endif #ifdef USE_FAN - bool on_fan(fan::FanState *fan) override; + bool on_fan(fan::Fan *fan) override; #endif #ifdef USE_LIGHT bool on_light(light::LightState *light) override; @@ -48,6 +48,9 @@ class ListEntitiesIterator : public ComponentIterator { #endif #ifdef USE_SELECT bool on_select(select::Select *select) override; +#endif +#ifdef USE_LOCK + bool on_lock(lock::Lock *a_lock) override; #endif bool on_end() override; diff --git a/esphome/components/api/proto.h b/esphome/components/api/proto.h index edbc916b012b..38fd98b48932 100644 --- a/esphome/components/api/proto.h +++ b/esphome/components/api/proto.h @@ -55,17 +55,19 @@ class ProtoVarInt { } int32_t as_sint32() const { // with ZigZag encoding - if (this->value_ & 1) + if (this->value_ & 1) { return static_cast(~(this->value_ >> 1)); - else + } else { return static_cast(this->value_ >> 1); + } } int64_t as_sint64() const { // with ZigZag encoding - if (this->value_ & 1) + if (this->value_ & 1) { return static_cast(~(this->value_ >> 1)); - else + } else { return static_cast(this->value_ >> 1); + } } void encode(std::vector &out) { uint32_t val = this->value_; @@ -220,10 +222,11 @@ class ProtoWriteBuffer { } void encode_sint32(uint32_t field_id, int32_t value, bool force = false) { uint32_t uvalue; - if (value < 0) + if (value < 0) { uvalue = ~(value << 1); - else + } else { uvalue = value << 1; + } this->encode_uint32(field_id, uvalue, force); } template void encode_message(uint32_t field_id, const C &value, bool force = false) { diff --git a/esphome/components/api/subscribe_state.cpp b/esphome/components/api/subscribe_state.cpp index 1b8453f233de..10416ecc5ca4 100644 --- a/esphome/components/api/subscribe_state.cpp +++ b/esphome/components/api/subscribe_state.cpp @@ -14,7 +14,7 @@ bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); } #endif #ifdef USE_FAN -bool InitialStateIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_state(fan); } +bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); } #endif #ifdef USE_LIGHT bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); } @@ -47,6 +47,9 @@ bool InitialStateIterator::on_select(select::Select *select) { return this->client_->send_select_state(select, select->state); } #endif +#ifdef USE_LOCK +bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); } +#endif InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client) : ComponentIterator(server), client_(client) {} diff --git a/esphome/components/api/subscribe_state.h b/esphome/components/api/subscribe_state.h index d3f2d3aa456a..caea013f841c 100644 --- a/esphome/components/api/subscribe_state.h +++ b/esphome/components/api/subscribe_state.h @@ -20,7 +20,7 @@ class InitialStateIterator : public ComponentIterator { bool on_cover(cover::Cover *cover) override; #endif #ifdef USE_FAN - bool on_fan(fan::FanState *fan) override; + bool on_fan(fan::Fan *fan) override; #endif #ifdef USE_LIGHT bool on_light(light::LightState *light) override; @@ -45,6 +45,9 @@ class InitialStateIterator : public ComponentIterator { #endif #ifdef USE_SELECT bool on_select(select::Select *select) override; +#endif +#ifdef USE_LOCK + bool on_lock(lock::Lock *a_lock) override; #endif protected: APIConnection *client_; diff --git a/esphome/components/api/user_services.h b/esphome/components/api/user_services.h index 1f9ffc591418..df6f6924aafc 100644 --- a/esphome/components/api/user_services.h +++ b/esphome/components/api/user_services.h @@ -52,7 +52,7 @@ template class UserServiceBase : public UserServiceDescriptor { protected: virtual void execute(Ts... x) = 0; - template void execute_(std::vector args, seq) { + template void execute_(std::vector args, seq type) { this->execute((get_execute_arg_value(args[S]))...); } diff --git a/esphome/components/api/util.cpp b/esphome/components/api/util.cpp index f5fd752101eb..fd55f89f9b06 100644 --- a/esphome/components/api/util.cpp +++ b/esphome/components/api/util.cpp @@ -212,6 +212,21 @@ void ComponentIterator::advance() { } } break; +#endif +#ifdef USE_LOCK + case IteratorState::LOCK: + if (this->at_ >= App.get_locks().size()) { + advance_platform = true; + } else { + auto *a_lock = App.get_locks()[this->at_]; + if (a_lock->is_internal()) { + success = true; + break; + } else { + success = this->on_lock(a_lock); + } + } + break; #endif case IteratorState::MAX: if (this->on_end()) { diff --git a/esphome/components/api/util.h b/esphome/components/api/util.h index 7849b3e028ca..9204b0829e5b 100644 --- a/esphome/components/api/util.h +++ b/esphome/components/api/util.h @@ -27,7 +27,7 @@ class ComponentIterator { virtual bool on_cover(cover::Cover *cover) = 0; #endif #ifdef USE_FAN - virtual bool on_fan(fan::FanState *fan) = 0; + virtual bool on_fan(fan::Fan *fan) = 0; #endif #ifdef USE_LIGHT virtual bool on_light(light::LightState *light) = 0; @@ -56,6 +56,9 @@ class ComponentIterator { #endif #ifdef USE_SELECT virtual bool on_select(select::Select *select) = 0; +#endif +#ifdef USE_LOCK + virtual bool on_lock(lock::Lock *a_lock) = 0; #endif virtual bool on_end(); @@ -99,6 +102,9 @@ class ComponentIterator { #endif #ifdef USE_SELECT SELECT, +#endif +#ifdef USE_LOCK + LOCK, #endif MAX, } state_{IteratorState::NONE}; diff --git a/esphome/components/as3935/as3935.cpp b/esphome/components/as3935/as3935.cpp index 1cc400bb7b3e..b36856218a5a 100644 --- a/esphome/components/as3935/as3935.cpp +++ b/esphome/components/as3935/as3935.cpp @@ -58,10 +58,11 @@ void AS3935Component::loop() { void AS3935Component::write_indoor(bool indoor) { ESP_LOGV(TAG, "Setting indoor to %d", indoor); - if (indoor) + if (indoor) { this->write_register(AFE_GAIN, GAIN_MASK, INDOOR, 1); - else + } else { this->write_register(AFE_GAIN, GAIN_MASK, OUTDOOR, 1); + } } // REG0x01, bits[3:0], manufacturer default: 0010 (2). // This setting determines the threshold for events that trigger the diff --git a/esphome/components/ballu/ballu.cpp b/esphome/components/ballu/ballu.cpp index e2703a79fb5a..b33ad11c1fbc 100644 --- a/esphome/components/ballu/ballu.cpp +++ b/esphome/components/ballu/ballu.cpp @@ -97,7 +97,7 @@ void BalluClimate::transmit_state() { // Send code auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); @@ -130,10 +130,10 @@ bool BalluClimate::on_receive(remote_base::RemoteReceiveData data) { for (int i = 0; i < BALLU_STATE_LENGTH; i++) { // Read bit for (int j = 0; j < 8; j++) { - if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) + if (data.expect_item(BALLU_BIT_MARK, BALLU_ONE_SPACE)) { remote_state[i] |= 1 << j; - else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) { + } else if (!data.expect_item(BALLU_BIT_MARK, BALLU_ZERO_SPACE)) { ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); return false; } diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index 4a95f8c33915..20cb87025a2c 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -21,12 +21,13 @@ void BangBangClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - if (supports_cool_ && supports_heat_) + if (supports_cool_ && supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT_COOL; - else if (supports_cool_) + } else if (supports_cool_) { this->mode = climate::CLIMATE_MODE_COOL; - else if (supports_heat_) + } else if (supports_heat_) { this->mode = climate::CLIMATE_MODE_HEAT; + } this->change_away_(false); } } @@ -56,11 +57,12 @@ climate::ClimateTraits BangBangClimate::traits() { if (supports_cool_ && supports_heat_) traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL); traits.set_supports_two_point_target_temperature(true); - if (supports_away_) + if (supports_away_) { traits.set_supported_presets({ climate::CLIMATE_PRESET_HOME, climate::CLIMATE_PRESET_AWAY, }); + } traits.set_supports_action(true); return traits; } @@ -82,17 +84,19 @@ void BangBangClimate::compute_state_() { if (too_cold) { // too cold -> enable heating if possible and enabled, else idle if (this->supports_heat_ && - (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) + (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_HEAT)) { target_action = climate::CLIMATE_ACTION_HEATING; - else + } else { target_action = climate::CLIMATE_ACTION_IDLE; + } } else if (too_hot) { // too hot -> enable cooling if possible and enabled, else idle if (this->supports_cool_ && - (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) + (this->mode == climate::CLIMATE_MODE_HEAT_COOL || this->mode == climate::CLIMATE_MODE_COOL)) { target_action = climate::CLIMATE_ACTION_COOLING; - else + } else { target_action = climate::CLIMATE_ACTION_IDLE; + } } else { // neither too hot nor too cold -> in range if (this->supports_cool_ && this->supports_heat_ && this->mode == climate::CLIMATE_MODE_HEAT_COOL) { @@ -107,9 +111,10 @@ void BangBangClimate::compute_state_() { this->switch_to_action_(target_action); } void BangBangClimate::switch_to_action_(climate::ClimateAction action) { - if (action == this->action) + if (action == this->action) { // already in target mode return; + } if ((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) || (action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) { diff --git a/esphome/components/binary/fan/__init__.py b/esphome/components/binary/fan/__init__.py index e6c8d9bfe953..6edfa885c918 100644 --- a/esphome/components/binary/fan/__init__.py +++ b/esphome/components/binary/fan/__init__.py @@ -9,7 +9,7 @@ ) from .. import binary_ns -BinaryFan = binary_ns.class_("BinaryFan", cg.Component) +BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component) CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( { @@ -24,9 +24,8 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) await cg.register_component(var, config) + await fan.register_fan(var, config) - fan_ = await fan.create_fan_state(config) - cg.add(var.set_fan(fan_)) output_ = await cg.get_variable(config[CONF_OUTPUT]) cg.add(var.set_output(output_)) diff --git a/esphome/components/binary/fan/binary_fan.cpp b/esphome/components/binary/fan/binary_fan.cpp index 2201fe576eab..a2f75242de15 100644 --- a/esphome/components/binary/fan/binary_fan.cpp +++ b/esphome/components/binary/fan/binary_fan.cpp @@ -6,59 +6,35 @@ namespace binary { static const char *const TAG = "binary.fan"; -void binary::BinaryFan::dump_config() { - ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str()); - if (this->fan_->get_traits().supports_oscillation()) { - ESP_LOGCONFIG(TAG, " Oscillation: YES"); - } - if (this->fan_->get_traits().supports_direction()) { - ESP_LOGCONFIG(TAG, " Direction: YES"); +void BinaryFan::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + this->write_state_(); } } -void BinaryFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); - this->fan_->set_traits(traits); - this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); +void BinaryFan::dump_config() { LOG_FAN("", "Binary Fan", this); } +fan::FanTraits BinaryFan::get_traits() { + return fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); } -void BinaryFan::loop() { - if (!this->next_update_) { - return; - } - this->next_update_ = false; - - { - bool enable = this->fan_->state; - if (enable) - this->output_->turn_on(); - else - this->output_->turn_off(); - ESP_LOGD(TAG, "Setting binary state: %s", ONOFF(enable)); - } - - if (this->oscillating_ != nullptr) { - bool enable = this->fan_->oscillating; - if (enable) { - this->oscillating_->turn_on(); - } else { - this->oscillating_->turn_off(); - } - ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); - } - - if (this->direction_ != nullptr) { - bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; - if (enable) { - this->direction_->turn_on(); - } else { - this->direction_->turn_off(); - } - ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); - } +void BinaryFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); + + this->write_state_(); + this->publish_state(); +} +void BinaryFan::write_state_() { + this->output_->set_state(this->state); + if (this->oscillating_ != nullptr) + this->oscillating_->set_state(this->oscillating); + if (this->direction_ != nullptr) + this->direction_->set_state(this->direction == fan::FanDirection::REVERSE); } - -// We need a higher priority than the FanState component to make sure that the traits are set -// when that component sets itself up. -float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } } // namespace binary } // namespace esphome diff --git a/esphome/components/binary/fan/binary_fan.h b/esphome/components/binary/fan/binary_fan.h index 93294b8deeec..16bce2e6af2b 100644 --- a/esphome/components/binary/fan/binary_fan.h +++ b/esphome/components/binary/fan/binary_fan.h @@ -2,28 +2,29 @@ #include "esphome/core/component.h" #include "esphome/components/output/binary_output.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace binary { -class BinaryFan : public Component { +class BinaryFan : public Component, public fan::Fan { public: - void set_fan(fan::FanState *fan) { fan_ = fan; } - void set_output(output::BinaryOutput *output) { output_ = output; } void setup() override; - void loop() override; void dump_config() override; - float get_setup_priority() const override; + + void set_output(output::BinaryOutput *output) { this->output_ = output; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } + fan::FanTraits get_traits() override; + protected: - fan::FanState *fan_; + void control(const fan::FanCall &call) override; + void write_state_(); + output::BinaryOutput *output_; output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *direction_{nullptr}; - bool next_update_{true}; }; } // namespace binary diff --git a/esphome/components/bl0940/bl0940.cpp b/esphome/components/bl0940/bl0940.cpp index 19672e98d032..ed193b23f385 100644 --- a/esphome/components/bl0940/bl0940.cpp +++ b/esphome/components/bl0940/bl0940.cpp @@ -65,7 +65,7 @@ void BL0940::update() { } void BL0940::setup() { - for (auto i : BL0940_INIT) { + for (auto *i : BL0940_INIT) { this->write_array(i, 6); delay(1); } diff --git a/esphome/components/ble_client/ble_client.cpp b/esphome/components/ble_client/ble_client.cpp index 407f1a1d17b6..5b2701d77a88 100644 --- a/esphome/components/ble_client/ble_client.cpp +++ b/esphome/components/ble_client/ble_client.cpp @@ -167,7 +167,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es break; } case ESP_GATTC_REG_FOR_NOTIFY_EVT: { - auto descr = this->get_config_descriptor(param->reg_for_notify.handle); + auto *descr = this->get_config_descriptor(param->reg_for_notify.handle); if (descr == nullptr) { ESP_LOGW(TAG, "No descriptor found for notify of handle 0x%x", param->reg_for_notify.handle); break; @@ -252,16 +252,17 @@ float BLEClient::parse_char_value(uint8_t *value, uint16_t length) { } BLEService *BLEClient::get_service(espbt::ESPBTUUID uuid) { - for (auto svc : this->services_) + for (auto *svc : this->services_) { if (svc->uuid == uuid) return svc; + } return nullptr; } BLEService *BLEClient::get_service(uint16_t uuid) { return this->get_service(espbt::ESPBTUUID::from_uint16(uuid)); } BLECharacteristic *BLEClient::get_characteristic(espbt::ESPBTUUID service, espbt::ESPBTUUID chr) { - auto svc = this->get_service(service); + auto *svc = this->get_service(service); if (svc == nullptr) return nullptr; return svc->get_characteristic(chr); @@ -272,19 +273,24 @@ BLECharacteristic *BLEClient::get_characteristic(uint16_t service, uint16_t chr) } BLEDescriptor *BLEClient::get_config_descriptor(uint16_t handle) { - for (auto &svc : this->services_) - for (auto &chr : svc->characteristics) - if (chr->handle == handle) - for (auto &desc : chr->descriptors) + for (auto &svc : this->services_) { + for (auto &chr : svc->characteristics) { + if (chr->handle == handle) { + for (auto &desc : chr->descriptors) { if (desc->uuid == espbt::ESPBTUUID::from_uint16(0x2902)) return desc; + } + } + } + } return nullptr; } BLECharacteristic *BLEService::get_characteristic(espbt::ESPBTUUID uuid) { - for (auto &chr : this->characteristics) + for (auto &chr : this->characteristics) { if (chr->uuid == uuid) return chr; + } return nullptr; } @@ -293,10 +299,10 @@ BLECharacteristic *BLEService::get_characteristic(uint16_t uuid) { } BLEDescriptor *BLEClient::get_descriptor(espbt::ESPBTUUID service, espbt::ESPBTUUID chr, espbt::ESPBTUUID descr) { - auto svc = this->get_service(service); + auto *svc = this->get_service(service); if (svc == nullptr) return nullptr; - auto ch = svc->get_characteristic(chr); + auto *ch = svc->get_characteristic(chr); if (ch == nullptr) return nullptr; return ch->get_descriptor(descr); @@ -379,24 +385,29 @@ void BLECharacteristic::parse_descriptors() { } BLEDescriptor *BLECharacteristic::get_descriptor(espbt::ESPBTUUID uuid) { - for (auto &desc : this->descriptors) + for (auto &desc : this->descriptors) { if (desc->uuid == uuid) return desc; + } return nullptr; } BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) { return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid)); } -void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { - auto client = this->service->client; +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) { + auto *client = this->service->client; auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val, - ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE); + write_type, ESP_GATT_AUTH_REQ_NONE); if (status) { ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status); } } +void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) { + write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP); +} + } // namespace ble_client } // namespace esphome diff --git a/esphome/components/ble_client/ble_client.h b/esphome/components/ble_client/ble_client.h index 5680b69f723f..e0a1bf61b900 100644 --- a/esphome/components/ble_client/ble_client.h +++ b/esphome/components/ble_client/ble_client.h @@ -60,6 +60,7 @@ class BLECharacteristic { BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid); BLEDescriptor *get_descriptor(uint16_t uuid); void write_value(uint8_t *new_val, int16_t new_val_size); + void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type); BLEService *service; }; @@ -126,9 +127,10 @@ class BLEClient : public espbt::ESPBTClient, public Component { bool all_nodes_established_() { if (this->state() != espbt::ClientState::ESTABLISHED) return false; - for (auto &node : nodes_) + for (auto &node : nodes_) { if (node->node_state != espbt::ClientState::ESTABLISHED) return false; + } return true; } diff --git a/esphome/components/ble_client/output/__init__.py b/esphome/components/ble_client/output/__init__.py index fe5835ca82d1..e28421d1a7ed 100644 --- a/esphome/components/ble_client/output/__init__.py +++ b/esphome/components/ble_client/output/__init__.py @@ -1,13 +1,14 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import output, ble_client, esp32_ble_tracker +from esphome.components import ble_client, esp32_ble_tracker, output from esphome.const import CONF_ID, CONF_SERVICE_UUID -from .. import ble_client_ns +from .. import ble_client_ns DEPENDENCIES = ["ble_client"] CONF_CHARACTERISTIC_UUID = "characteristic_uuid" +CONF_REQUIRE_RESPONSE = "require_response" BLEBinaryOutput = ble_client_ns.class_( "BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component @@ -19,6 +20,7 @@ cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput), cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid, cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid, + cv.Optional(CONF_REQUIRE_RESPONSE, default=False): cv.boolean, } ) .extend(cv.COMPONENT_SCHEMA) @@ -61,7 +63,7 @@ def to_code(config): config[CONF_CHARACTERISTIC_UUID] ) cg.add(var.set_char_uuid128(uuid128)) - + cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE])) yield output.register_output(var, config) yield ble_client.register_ble_node(var, config) yield cg.register_component(var, config) diff --git a/esphome/components/ble_client/output/ble_binary_output.cpp b/esphome/components/ble_client/output/ble_binary_output.cpp index ff3711e8423a..670980393646 100644 --- a/esphome/components/ble_client/output/ble_binary_output.cpp +++ b/esphome/components/ble_client/output/ble_binary_output.cpp @@ -32,7 +32,7 @@ void BLEBinaryOutput::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_i break; } - auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] Characteristic not found.", this->char_uuid_.to_string().c_str()); break; @@ -54,7 +54,7 @@ void BLEBinaryOutput::write_state(bool state) { return; } - auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { ESP_LOGW(TAG, "[%s] Characteristic not found. State update can not be written.", this->char_uuid_.to_string().c_str()); @@ -63,7 +63,11 @@ void BLEBinaryOutput::write_state(bool state) { uint8_t state_as_uint = (uint8_t) state; ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint); - chr->write_value(&state_as_uint, sizeof(state_as_uint)); + if (this->require_response_) { + chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP); + } else { + chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP); + } } } // namespace ble_client diff --git a/esphome/components/ble_client/output/ble_binary_output.h b/esphome/components/ble_client/output/ble_binary_output.h index e1d62a267b39..83eabcf5f2e7 100644 --- a/esphome/components/ble_client/output/ble_binary_output.h +++ b/esphome/components/ble_client/output/ble_binary_output.h @@ -25,9 +25,11 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); } void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) override; + void set_require_response(bool response) { this->require_response_ = response; } protected: void write_state(bool state) override; + bool require_response_; espbt::ESPBTUUID service_uuid_; espbt::ESPBTUUID char_uuid_; espbt::ClientState client_state_; diff --git a/esphome/components/ble_client/sensor/ble_sensor.cpp b/esphome/components/ble_client/sensor/ble_sensor.cpp index 7a2e3ddc8bee..f8eee43137b4 100644 --- a/esphome/components/ble_client/sensor/ble_sensor.cpp +++ b/esphome/components/ble_client/sensor/ble_sensor.cpp @@ -43,7 +43,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } case ESP_GATTC_SEARCH_CMPL_EVT: { this->handle = 0; - auto chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); + auto *chr = this->parent()->get_characteristic(this->service_uuid_, this->char_uuid_); if (chr == nullptr) { this->status_set_warning(); this->publish_state(NAN); @@ -53,7 +53,7 @@ void BLESensor::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t ga } this->handle = chr->handle; if (this->descr_uuid_.get_uuid().len > 0) { - auto descr = chr->get_descriptor(this->descr_uuid_); + auto *descr = chr->get_descriptor(this->descr_uuid_); if (descr == nullptr) { this->status_set_warning(); this->publish_state(NAN); diff --git a/esphome/components/ble_scanner/text_sensor.py b/esphome/components/ble_scanner/text_sensor.py index 0e140aa701e2..31dccdf119a1 100644 --- a/esphome/components/ble_scanner/text_sensor.py +++ b/esphome/components/ble_scanner/text_sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor, esp32_ble_tracker -from esphome.const import CONF_ID DEPENDENCIES = ["esp32_ble_tracker"] @@ -14,18 +13,13 @@ ) CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(BLEScanner), - } - ) + text_sensor.text_sensor_schema(klass=BLEScanner) .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) .extend(cv.COMPONENT_SCHEMA) ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) await esp32_ble_tracker.register_ble_device(var, config) - await text_sensor.register_text_sensor(var, config) diff --git a/esphome/components/bme280/bme280.cpp b/esphome/components/bme280/bme280.cpp index d3a228328b1a..fcb293afa0be 100644 --- a/esphome/components/bme280/bme280.cpp +++ b/esphome/components/bme280/bme280.cpp @@ -214,9 +214,10 @@ void BME280Component::update() { float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { int32_t adc = ((data[3] & 0xFF) << 16) | ((data[4] & 0xFF) << 8) | (data[5] & 0xFF); adc >>= 4; - if (adc == 0x80000) + if (adc == 0x80000) { // temperature was disabled return NAN; + } const int32_t t1 = this->calibration_.t1; const int32_t t2 = this->calibration_.t2; @@ -233,9 +234,10 @@ float BME280Component::read_temperature_(const uint8_t *data, int32_t *t_fine) { float BME280Component::read_pressure_(const uint8_t *data, int32_t t_fine) { int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; - if (adc == 0x80000) + if (adc == 0x80000) { // pressure was disabled return NAN; + } const int64_t p1 = this->calibration_.p1; const int64_t p2 = this->calibration_.p2; const int64_t p3 = this->calibration_.p3; diff --git a/esphome/components/bme680/bme680.cpp b/esphome/components/bme680/bme680.cpp index 99e0b6f860e2..64770ac0d511 100644 --- a/esphome/components/bme680/bme680.cpp +++ b/esphome/components/bme680/bme680.cpp @@ -95,7 +95,7 @@ void BME680Component::setup() { this->calibration_.t3 = cal1[3]; this->calibration_.h1 = cal2[2] << 4 | (cal2[1] & 0x0F); - this->calibration_.h2 = cal2[0] << 4 | cal2[1]; + this->calibration_.h2 = cal2[0] << 4 | cal2[1] >> 4; this->calibration_.h3 = cal2[3]; this->calibration_.h4 = cal2[4]; this->calibration_.h5 = cal2[5]; @@ -420,10 +420,11 @@ float BME680Component::calc_humidity_(uint16_t raw_humidity) { calc_hum = var2 + (var3 + var4 * temp_comp) * var2 * var2; - if (calc_hum > 100.0f) + if (calc_hum > 100.0f) { calc_hum = 100.0f; - else if (calc_hum < 0.0f) + } else if (calc_hum < 0.0f) { calc_hum = 0.0f; + } return calc_hum; } diff --git a/esphome/components/bme680_bsec/text_sensor.py b/esphome/components/bme680_bsec/text_sensor.py index 96020544e750..2d93c90818c9 100644 --- a/esphome/components/bme680_bsec/text_sensor.py +++ b/esphome/components/bme680_bsec/text_sensor.py @@ -1,7 +1,6 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ID, CONF_ICON from . import BME680BSECComponent, CONF_BME680_BSEC_ID DEPENDENCIES = ["bme680_bsec"] @@ -14,11 +13,8 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent), - cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon, - } + cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema( + icon=ICON_ACCURACY ), } ) @@ -27,8 +23,7 @@ async def setup_conf(config, key, hub): if key in config: conf = config[key] - sens = cg.new_Pvariable(conf[CONF_ID]) - await text_sensor.register_text_sensor(sens, conf) + sens = await text_sensor.new_text_sensor(conf) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) diff --git a/esphome/components/bmp280/bmp280.cpp b/esphome/components/bmp280/bmp280.cpp index b4348e8a74fa..bda34e6c2bb9 100644 --- a/esphome/components/bmp280/bmp280.cpp +++ b/esphome/components/bmp280/bmp280.cpp @@ -161,9 +161,10 @@ float BMP280Component::read_temperature_(int32_t *t_fine) { return NAN; int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; - if (adc == 0x80000) + if (adc == 0x80000) { // temperature was disabled return NAN; + } const int32_t t1 = this->calibration_.t1; const int32_t t2 = this->calibration_.t2; @@ -183,9 +184,10 @@ float BMP280Component::read_pressure_(int32_t t_fine) { return NAN; int32_t adc = ((data[0] & 0xFF) << 16) | ((data[1] & 0xFF) << 8) | (data[2] & 0xFF); adc >>= 4; - if (adc == 0x80000) + if (adc == 0x80000) { // pressure was disabled return NAN; + } const int64_t p1 = this->calibration_.p1; const int64_t p2 = this->calibration_.p2; const int64_t p3 = this->calibration_.p3; diff --git a/esphome/components/canbus/__init__.py b/esphome/components/canbus/__init__.py index 3a3cece57970..808b31d1d29f 100644 --- a/esphome/components/canbus/__init__.py +++ b/esphome/components/canbus/__init__.py @@ -14,10 +14,14 @@ CONF_ON_FRAME = "on_frame" -def validate_id(id_value, id_ext): - if not id_ext: - if id_value > 0x7FF: - raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") +def validate_id(config): + if CONF_CAN_ID in config: + id_value = config[CONF_CAN_ID] + id_ext = config[CONF_USE_EXTENDED_ID] + if not id_ext: + if id_value > 0x7FF: + raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)") + return config def validate_raw_data(value): @@ -67,23 +71,18 @@ def validate_raw_data(value): cv.Optional(CONF_ON_FRAME): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), - cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), + cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - cv.Optional(CONF_ON_FRAME): automation.validate_automation( - { - cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger), - cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF), - cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, - } - ), - } + }, + validate_id, ), - } + }, ).extend(cv.COMPONENT_SCHEMA) +CANBUS_SCHEMA.add_extra(validate_id) + async def setup_canbus_core_(var, config): - validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) await cg.register_component(var, config) cg.add(var.set_can_id([config[CONF_CAN_ID]])) cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]])) @@ -92,7 +91,6 @@ async def setup_canbus_core_(var, config): for conf in config.get(CONF_ON_FRAME, []): can_id = conf[CONF_CAN_ID] ext_id = conf[CONF_USE_EXTENDED_ID] - validate_id(can_id, ext_id) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id) await cg.register_component(trigger, conf) await automation.build_automation( @@ -117,11 +115,11 @@ async def register_canbus(var, config): cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean, cv.Required(CONF_DATA): cv.templatable(validate_raw_data), }, + validate_id, key=CONF_DATA, ), ) async def canbus_action_to_code(config, action_id, template_arg, args): - validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID]) var = cg.new_Pvariable(action_id, template_arg) await cg.register_parented(var, config[CONF_CANBUS_ID]) diff --git a/esphome/components/canbus/canbus.cpp b/esphome/components/canbus/canbus.cpp index b8b6b9e65f68..731682c2775e 100644 --- a/esphome/components/canbus/canbus.cpp +++ b/esphome/components/canbus/canbus.cpp @@ -75,7 +75,7 @@ void Canbus::loop() { } // fire all triggers - for (auto trigger : this->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); } diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp index f8cee79c5513..5c60989afa6a 100644 --- a/esphome/components/ccs811/ccs811.cpp +++ b/esphome/components/ccs811/ccs811.cpp @@ -39,14 +39,15 @@ void CCS811Component::setup() { // set MEAS_MODE (page 5) uint8_t meas_mode = 0; uint32_t interval = this->get_update_interval(); - if (interval >= 60 * 1000) + if (interval >= 60 * 1000) { meas_mode = 3 << 4; // sensor takes a reading every 60 seconds - else if (interval >= 10 * 1000) + } else if (interval >= 10 * 1000) { meas_mode = 2 << 4; // sensor takes a reading every 10 seconds - else if (interval >= 1 * 1000) + } else if (interval >= 1 * 1000) { meas_mode = 1 << 4; // sensor takes a reading every second - else + } else { meas_mode = 4 << 4; // sensor takes a reading every 250ms + } CHECKED_IO(this->write_byte(0x01, meas_mode)) diff --git a/esphome/components/ccs811/sensor.py b/esphome/components/ccs811/sensor.py index bb8200273dd1..cb5c1108ba44 100644 --- a/esphome/components/ccs811/sensor.py +++ b/esphome/components/ccs811/sensor.py @@ -2,7 +2,6 @@ import esphome.config_validation as cv from esphome.components import i2c, sensor, text_sensor from esphome.const import ( - CONF_ICON, CONF_ID, ICON_RADIATOR, ICON_RESTART, @@ -47,11 +46,8 @@ device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, state_class=STATE_CLASS_MEASUREMENT, ), - cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon, - } + cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( + icon=ICON_RESTART ), cv.Optional(CONF_BASELINE): cv.hex_uint16_t, cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor), @@ -74,8 +70,7 @@ async def to_code(config): cg.add(var.set_tvoc(sens)) if CONF_VERSION in config: - sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID]) - await text_sensor.register_text_sensor(sens, config[CONF_VERSION]) + sens = await text_sensor.new_text_sensor(config[CONF_VERSION]) cg.add(var.set_version(sens)) if CONF_BASELINE in config: diff --git a/esphome/components/cd74hc4067/__init__.py b/esphome/components/cd74hc4067/__init__.py index f8efdf4b2a43..4fb15d1bf32b 100644 --- a/esphome/components/cd74hc4067/__init__.py +++ b/esphome/components/cd74hc4067/__init__.py @@ -6,8 +6,9 @@ CONF_ID, ) -CODEOWNERS = ["@asoehlke"] AUTO_LOAD = ["sensor", "voltage_sampler"] +CODEOWNERS = ["@asoehlke"] +MULTI_CONF = True cd74hc4067_ns = cg.esphome_ns.namespace("cd74hc4067") diff --git a/esphome/components/climate/climate_traits.h b/esphome/components/climate/climate_traits.h index 903ce085d894..d113510eeb19 100644 --- a/esphome/components/climate/climate_traits.h +++ b/esphome/components/climate/climate_traits.h @@ -65,7 +65,7 @@ class ClimateTraits { ESPDEPRECATED("This method is deprecated, use set_supported_modes() instead", "v1.20") void set_supports_dry_mode(bool supports_dry_mode) { set_mode_support_(CLIMATE_MODE_DRY, supports_dry_mode); } bool supports_mode(ClimateMode mode) const { return supported_modes_.count(mode); } - const std::set get_supported_modes() const { return supported_modes_; } + std::set get_supported_modes() const { return supported_modes_; } void set_supports_action(bool supports_action) { supports_action_ = supports_action; } bool get_supports_action() const { return supports_action_; } @@ -93,7 +93,7 @@ class ClimateTraits { void set_supports_fan_mode_diffuse(bool supported) { set_fan_mode_support_(CLIMATE_FAN_DIFFUSE, supported); } bool supports_fan_mode(ClimateFanMode fan_mode) const { return supported_fan_modes_.count(fan_mode); } bool get_supports_fan_modes() const { return !supported_fan_modes_.empty() || !supported_custom_fan_modes_.empty(); } - const std::set get_supported_fan_modes() const { return supported_fan_modes_; } + std::set get_supported_fan_modes() const { return supported_fan_modes_; } void set_supported_custom_fan_modes(std::set supported_custom_fan_modes) { supported_custom_fan_modes_ = std::move(supported_custom_fan_modes); @@ -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(); } - const std::set get_supported_swing_modes() { return supported_swing_modes_; } + std::set get_supported_swing_modes() { 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/climate_ir_lg/climate_ir_lg.cpp b/esphome/components/climate_ir_lg/climate_ir_lg.cpp index cbb1f7699b3a..a41aad1bd087 100644 --- a/esphome/components/climate_ir_lg/climate_ir_lg.cpp +++ b/esphome/components/climate_ir_lg/climate_ir_lg.cpp @@ -137,11 +137,11 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->swing_mode = this->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { - if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) + if ((remote_state & COMMAND_MASK) == COMMAND_AUTO) { this->mode = climate::CLIMATE_MODE_HEAT_COOL; - else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) + } else if ((remote_state & COMMAND_MASK) == COMMAND_DRY_FAN) { this->mode = climate::CLIMATE_MODE_DRY; - else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { + } else if ((remote_state & COMMAND_MASK) == COMMAND_HEAT) { this->mode = climate::CLIMATE_MODE_HEAT; } else { this->mode = climate::CLIMATE_MODE_COOL; @@ -156,14 +156,15 @@ bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) { this->fan_mode = climate::CLIMATE_FAN_AUTO; } else if (this->mode == climate::CLIMATE_MODE_COOL || this->mode == climate::CLIMATE_MODE_HEAT || this->mode == climate::CLIMATE_MODE_DRY) { - if ((remote_state & FAN_MASK) == FAN_AUTO) + if ((remote_state & FAN_MASK) == FAN_AUTO) { this->fan_mode = climate::CLIMATE_FAN_AUTO; - else if ((remote_state & FAN_MASK) == FAN_MIN) + } else if ((remote_state & FAN_MASK) == FAN_MIN) { this->fan_mode = climate::CLIMATE_FAN_LOW; - else if ((remote_state & FAN_MASK) == FAN_MED) + } else if ((remote_state & FAN_MASK) == FAN_MED) { this->fan_mode = climate::CLIMATE_FAN_MEDIUM; - else if ((remote_state & FAN_MASK) == FAN_MAX) + } else if ((remote_state & FAN_MASK) == FAN_MAX) { this->fan_mode = climate::CLIMATE_FAN_HIGH; + } } } this->publish_state(); @@ -175,7 +176,7 @@ void LgIrClimate::transmit_(uint32_t value) { ESP_LOGD(TAG, "Sending climate_lg_ir code: 0x%02X", value); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); data->reserve(2 + BITS * 2u); diff --git a/esphome/components/coolix/coolix.cpp b/esphome/components/coolix/coolix.cpp index 76ec1627c24a..738fd8d00df9 100644 --- a/esphome/components/coolix/coolix.cpp +++ b/esphome/components/coolix/coolix.cpp @@ -104,7 +104,7 @@ void CoolixClimate::transmit_state() { ESP_LOGV(TAG, "Sending coolix code: 0x%06X", remote_state); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); remote_base::CoolixProtocol().encode(data, remote_state); transmit.perform(); } @@ -125,34 +125,37 @@ bool CoolixClimate::on_coolix(climate::Climate *parent, remote_base::RemoteRecei parent->swing_mode = parent->swing_mode == climate::CLIMATE_SWING_OFF ? climate::CLIMATE_SWING_VERTICAL : climate::CLIMATE_SWING_OFF; } else { - if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) + if ((remote_state & COOLIX_MODE_MASK) == COOLIX_HEAT) { parent->mode = climate::CLIMATE_MODE_HEAT; - else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) + } else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_AUTO) { parent->mode = climate::CLIMATE_MODE_HEAT_COOL; - else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { - if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) + } else if ((remote_state & COOLIX_MODE_MASK) == COOLIX_DRY_FAN) { + if ((remote_state & COOLIX_FAN_MASK) == COOLIX_FAN_MODE_AUTO_DRY) { parent->mode = climate::CLIMATE_MODE_DRY; - else + } else { parent->mode = climate::CLIMATE_MODE_FAN_ONLY; + } } else parent->mode = climate::CLIMATE_MODE_COOL; // Fan Speed if ((remote_state & COOLIX_FAN_AUTO) == COOLIX_FAN_AUTO || parent->mode == climate::CLIMATE_MODE_HEAT_COOL || - parent->mode == climate::CLIMATE_MODE_DRY) + parent->mode == climate::CLIMATE_MODE_DRY) { parent->fan_mode = climate::CLIMATE_FAN_AUTO; - else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) + } else if ((remote_state & COOLIX_FAN_MIN) == COOLIX_FAN_MIN) { parent->fan_mode = climate::CLIMATE_FAN_LOW; - else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) + } else if ((remote_state & COOLIX_FAN_MED) == COOLIX_FAN_MED) { parent->fan_mode = climate::CLIMATE_FAN_MEDIUM; - else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) + } else if ((remote_state & COOLIX_FAN_MAX) == COOLIX_FAN_MAX) { parent->fan_mode = climate::CLIMATE_FAN_HIGH; + } // Temperature uint8_t temperature_code = remote_state & COOLIX_TEMP_MASK; - for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) + for (uint8_t i = 0; i < COOLIX_TEMP_RANGE; i++) { if (COOLIX_TEMP_MAP[i] == temperature_code) parent->target_temperature = i + COOLIX_TEMP_MIN; + } } parent->publish_state(); diff --git a/esphome/components/cover/__init__.py b/esphome/components/cover/__init__.py index 0fd27f3f27be..d2421f07d966 100644 --- a/esphome/components/cover/__init__.py +++ b/esphome/components/cover/__init__.py @@ -7,6 +7,7 @@ CONF_ID, CONF_DEVICE_CLASS, CONF_STATE, + CONF_ON_OPEN, CONF_POSITION, CONF_POSITION_COMMAND_TOPIC, CONF_POSITION_STATE_TOPIC, @@ -74,7 +75,6 @@ "CoverClosedTrigger", automation.Trigger.template() ) -CONF_ON_OPEN = "on_open" CONF_ON_CLOSED = "on_closed" COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( diff --git a/esphome/components/cs5460a/sensor.py b/esphome/components/cs5460a/sensor.py index 82df881bfc23..d6e3c2ba4809 100644 --- a/esphome/components/cs5460a/sensor.py +++ b/esphome/components/cs5460a/sensor.py @@ -49,8 +49,8 @@ def validate_config(config): if current_gain == 0.0 or voltage_gain == 0.0: raise cv.Invalid("The gains can't be zero") - max_energy = (0.25 * 0.25 / 3600 / (2 ** -4)) / (voltage_gain * current_gain) - min_energy = (0.25 * 0.25 / 3600 / (2 ** 18)) / (voltage_gain * current_gain) + max_energy = (0.25 * 0.25 / 3600 / (2**-4)) / (voltage_gain * current_gain) + min_energy = (0.25 * 0.25 / 3600 / (2**18)) / (voltage_gain * current_gain) mech_min_energy = (0.25 * 0.25 / 3600 / 7.8) / (voltage_gain * current_gain) if pulse_energy < min_energy or pulse_energy > max_energy: raise cv.Invalid( diff --git a/esphome/components/custom/text_sensor/__init__.py b/esphome/components/custom/text_sensor/__init__.py index 5b6d41643675..70728af60438 100644 --- a/esphome/components/custom/text_sensor/__init__.py +++ b/esphome/components/custom/text_sensor/__init__.py @@ -11,11 +11,7 @@ cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor), cv.Required(CONF_LAMBDA): cv.returning_lambda, cv.Required(CONF_TEXT_SENSORS): cv.ensure_list( - text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ) + text_sensor.text_sensor_schema() ), } ) diff --git a/esphome/components/daikin/daikin.cpp b/esphome/components/daikin/daikin.cpp index 83d0253691b1..bb8587fbeb8a 100644 --- a/esphome/components/daikin/daikin.cpp +++ b/esphome/components/daikin/daikin.cpp @@ -23,7 +23,7 @@ void DaikinClimate::transmit_state() { } auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(DAIKIN_IR_FREQUENCY); data->mark(DAIKIN_HEADER_MARK); @@ -175,14 +175,15 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) { } uint8_t fan_mode = frame[8]; uint8_t swing_mode = frame[9]; - if (fan_mode & 0xF && swing_mode & 0xF) + if (fan_mode & 0xF && swing_mode & 0xF) { this->swing_mode = climate::CLIMATE_SWING_BOTH; - else if (fan_mode & 0xF) + } else if (fan_mode & 0xF) { this->swing_mode = climate::CLIMATE_SWING_VERTICAL; - else if (swing_mode & 0xF) + } else if (swing_mode & 0xF) { this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; - else + } else { this->swing_mode = climate::CLIMATE_SWING_OFF; + } switch (fan_mode & 0xF0) { case DAIKIN_FAN_1: case DAIKIN_FAN_2: @@ -212,9 +213,9 @@ bool DaikinClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t pos = 0; pos < DAIKIN_STATE_FRAME_SIZE; pos++) { uint8_t byte = 0; for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) + if (data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ONE_SPACE)) { byte |= 1 << bit; - else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) { + } else if (!data.expect_item(DAIKIN_BIT_MARK, DAIKIN_ZERO_SPACE)) { return false; } } diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp index 3610e7944731..1eed2ebf78df 100644 --- a/esphome/components/dallas/dallas_component.cpp +++ b/esphome/components/dallas/dallas_component.cpp @@ -52,7 +52,7 @@ void DallasComponent::setup() { this->found_sensors_.push_back(address); } - for (auto sensor : this->sensors_) { + for (auto *sensor : this->sensors_) { if (sensor->get_index().has_value()) { if (*sensor->get_index() >= this->found_sensors_.size()) { this->status_set_error(); @@ -235,7 +235,7 @@ float DallasTemperatureSensor::get_temp_c() { return temp / 128.0f; } -std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_upper_case(format_hex(this->address_)); } +std::string DallasTemperatureSensor::unique_id() { return "dallas-" + str_lower_case(format_hex(this->address_)); } } // namespace dallas } // namespace esphome diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp index a0ab10f8a4c7..6dc085a0bfd7 100644 --- a/esphome/components/dallas/esp_one_wire.cpp +++ b/esphome/components/dallas/esp_one_wire.cpp @@ -145,9 +145,10 @@ uint64_t ESPOneWire::search() { // read its complement bool cmp_id_bit = this->read_bit(); - if (id_bit && cmp_id_bit) + if (id_bit && cmp_id_bit) { // No devices participating in search break; + } bool branch; @@ -170,12 +171,13 @@ uint64_t ESPOneWire::search() { } } - if (branch) + if (branch) { // set bit this->rom_number8_()[rom_byte_number] |= rom_byte_mask; - else + } else { // clear bit this->rom_number8_()[rom_byte_number] &= ~rom_byte_mask; + } // choose/announce branch this->write_bit(branch); @@ -190,9 +192,10 @@ uint64_t ESPOneWire::search() { if (id_bit_number >= 65) { this->last_discrepancy_ = last_zero; - if (this->last_discrepancy_ == 0) + if (this->last_discrepancy_ == 0) { // we're at root and have no choices left, so this was the last one. this->last_device_flag_ = true; + } search_result = true; } diff --git a/esphome/components/daly_bms/text_sensor.py b/esphome/components/daly_bms/text_sensor.py index de49a0b4b902..9f23e5f3730a 100644 --- a/esphome/components/daly_bms/text_sensor.py +++ b/esphome/components/daly_bms/text_sensor.py @@ -1,7 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS +from esphome.const import CONF_STATUS from . import DalyBmsComponent, CONF_BMS_DALY_ID ICON_CAR_BATTERY = "mdi:car-battery" @@ -14,11 +14,8 @@ cv.Schema( { cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent), - cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon, - } + cv.Optional(CONF_STATUS): text_sensor.text_sensor_schema( + icon=ICON_CAR_BATTERY ), } ).extend(cv.COMPONENT_SCHEMA) @@ -28,8 +25,7 @@ async def setup_conf(config, key, hub): if key in config: conf = config[key] - sens = cg.new_Pvariable(conf[CONF_ID]) - await text_sensor.register_text_sensor(sens, conf) + sens = await text_sensor.new_text_sensor(conf) cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) diff --git a/esphome/components/debug/__init__.py b/esphome/components/debug/__init__.py index 32c43395300e..ff9b9c531446 100644 --- a/esphome/components/debug/__init__.py +++ b/esphome/components/debug/__init__.py @@ -1,17 +1,42 @@ import esphome.config_validation as cv import esphome.codegen as cg -from esphome.const import CONF_ID +from esphome.const import ( + CONF_ID, + CONF_DEVICE, + CONF_FREE, + CONF_FRAGMENTATION, + CONF_BLOCK, + CONF_LOOP_TIME, +) CODEOWNERS = ["@OttoWinter"] DEPENDENCIES = ["logger"] +CONF_DEBUG_ID = "debug_id" debug_ns = cg.esphome_ns.namespace("debug") -DebugComponent = debug_ns.class_("DebugComponent", cg.Component) +DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent) + + CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(): cv.declare_id(DebugComponent), + cv.Optional(CONF_DEVICE): cv.invalid( + "The 'device' option has been moved to the 'debug' text_sensor component" + ), + cv.Optional(CONF_FREE): cv.invalid( + "The 'free' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_BLOCK): cv.invalid( + "The 'block' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_FRAGMENTATION): cv.invalid( + "The 'fragmentation' option has been moved to the 'debug' sensor component" + ), + cv.Optional(CONF_LOOP_TIME): cv.invalid( + "The 'loop_time' option has been moved to the 'debug' sensor component" + ), } -).extend(cv.COMPONENT_SCHEMA) +).extend(cv.polling_component_schema("60s")) async def to_code(config): diff --git a/esphome/components/debug/debug_component.cpp b/esphome/components/debug/debug_component.cpp index f3d0bded1374..a2697084bd09 100644 --- a/esphome/components/debug/debug_component.cpp +++ b/esphome/components/debug/debug_component.cpp @@ -1,21 +1,23 @@ #include "debug_component.h" + +#include #include "esphome/core/log.h" +#include "esphome/core/hal.h" #include "esphome/core/helpers.h" -#include "esphome/core/defines.h" #include "esphome/core/version.h" -#ifdef USE_ESP_IDF +#ifdef USE_ESP32 + #include #include -#endif -#ifdef USE_ESP32 #if ESP_IDF_VERSION_MAJOR >= 4 #include #else #include #endif -#endif + +#endif // USE_ESP32 #ifdef USE_ARDUINO #include @@ -26,19 +28,40 @@ namespace debug { static const char *const TAG = "debug"; +static uint32_t get_free_heap() { +#if defined(USE_ESP8266) + return ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) +#elif defined(USE_ESP32) + return heap_caps_get_free_size(MALLOC_CAP_INTERNAL); +#endif +} + void DebugComponent::dump_config() { + std::string device_info; + device_info.reserve(256); + #ifndef ESPHOME_LOG_HAS_DEBUG ESP_LOGE(TAG, "Debug Component requires debug log level!"); this->status_set_error(); return; #endif + ESP_LOGCONFIG(TAG, "Debug component:"); +#ifdef USE_TEXT_SENSOR + LOG_TEXT_SENSOR(" ", "Device info", this->device_info_); +#endif // USE_TEXT_SENSOR +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Free space on heap", this->free_sensor_); + LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_); +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_); +#endif // defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) +#endif // USE_SENSOR + ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION); -#ifdef USE_ARDUINO - this->free_heap_ = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP_IDF) - this->free_heap_ = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); -#endif + device_info += ESPHOME_VERSION; + + this->free_heap_ = get_free_heap(); ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); #ifdef USE_ARDUINO @@ -67,9 +90,12 @@ void DebugComponent::dump_config() { default: flash_mode = "UNKNOWN"; } - // NOLINTNEXTLINE(readability-static-accessed-through-instance) - ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", ESP.getFlashChipSize() / 1024, - ESP.getFlashChipSpeed() / 1000000, flash_mode); + ESP_LOGD(TAG, "Flash Chip: Size=%ukB Speed=%uMHz Mode=%s", + ESP.getFlashChipSize() / 1024, // NOLINT + ESP.getFlashChipSpeed() / 1000000, flash_mode); // NOLINT + device_info += "|Flash: " + to_string(ESP.getFlashChipSize() / 1024) + // NOLINT + "kB Speed:" + to_string(ESP.getFlashChipSpeed() / 1000000) + "MHz Mode:"; // NOLINT + device_info += flash_mode; #endif // USE_ARDUINO #ifdef USE_ESP32 @@ -104,10 +130,21 @@ void DebugComponent::dump_config() { features += "Other:" + format_hex(info.features); ESP_LOGD(TAG, "Chip: Model=%s, Features=%s Cores=%u, Revision=%u", model, features.c_str(), info.cores, info.revision); + device_info += "|Chip: "; + device_info += model; + device_info += " Features:"; + device_info += features; + device_info += " Cores:" + to_string(info.cores); + device_info += " Revision:" + to_string(info.revision); ESP_LOGD(TAG, "ESP-IDF Version: %s", esp_get_idf_version()); + device_info += "|ESP-IDF: "; + device_info += esp_get_idf_version(); - ESP_LOGD(TAG, "EFuse MAC: %s", get_mac_address_pretty().c_str()); + std::string mac = get_mac_address_pretty(); + ESP_LOGD(TAG, "EFuse MAC: %s", mac.c_str()); + device_info += "|EFuse MAC: "; + device_info += mac; const char *reset_reason; switch (rtc_get_reset_reason(0)) { @@ -160,6 +197,8 @@ void DebugComponent::dump_config() { reset_reason = "Unknown Reset Reason"; } ESP_LOGD(TAG, "Reset Reason: %s", reset_reason); + device_info += "|Reset: "; + device_info += reset_reason; const char *wakeup_reason; switch (rtc_get_wakeup_cause()) { @@ -203,6 +242,8 @@ void DebugComponent::dump_config() { wakeup_reason = "Unknown"; } ESP_LOGD(TAG, "Wakeup Reason: %s", wakeup_reason); + device_info += "|Wakeup: "; + device_info += wakeup_reason; #endif #if defined(USE_ESP8266) && !defined(CLANG_TIDY) @@ -214,20 +255,81 @@ void DebugComponent::dump_config() { ESP_LOGD(TAG, "Flash Chip ID=0x%08X", ESP.getFlashChipId()); ESP_LOGD(TAG, "Reset Reason: %s", ESP.getResetReason().c_str()); ESP_LOGD(TAG, "Reset Info: %s", ESP.getResetInfo().c_str()); + + device_info += "|Chip: 0x" + format_hex(ESP.getChipId()); + device_info += "|SDK: "; + device_info += ESP.getSdkVersion(); + device_info += "|Core: "; + device_info += ESP.getCoreVersion().c_str(); + device_info += "|Boot: "; + device_info += to_string(ESP.getBootVersion()); + device_info += "|Mode: " + to_string(ESP.getBootMode()); + device_info += "|CPU: " + to_string(ESP.getCpuFreqMHz()); + device_info += "|Flash: 0x" + format_hex(ESP.getFlashChipId()); + device_info += "|Reset: "; + device_info += ESP.getResetReason().c_str(); + device_info += "|"; + device_info += ESP.getResetInfo().c_str(); #endif + +#ifdef USE_TEXT_SENSOR + if (this->device_info_ != nullptr) { + if (device_info.length() > 255) + device_info.resize(255); + this->device_info_->publish_state(device_info); + } +#endif // USE_TEXT_SENSOR } + void DebugComponent::loop() { -#ifdef USE_ARDUINO - uint32_t new_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) -#elif defined(USE_ESP_IDF) - uint32_t new_free_heap = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); -#endif + // log when free heap space has halved + uint32_t new_free_heap = get_free_heap(); if (new_free_heap < this->free_heap_ / 2) { this->free_heap_ = new_free_heap; ESP_LOGD(TAG, "Free Heap Size: %u bytes", this->free_heap_); this->status_momentary_warning("heap", 1000); } + +#ifdef USE_SENSOR + // calculate loop time - from last call to this one + if (this->loop_time_sensor_ != nullptr) { + uint32_t now = millis(); + uint32_t loop_time = now - this->last_loop_timetag_; + this->max_loop_time_ = std::max(this->max_loop_time_, loop_time); + this->last_loop_timetag_ = now; + } +#endif // USE_SENSOR } + +void DebugComponent::update() { +#ifdef USE_SENSOR + if (this->free_sensor_ != nullptr) { + this->free_sensor_->publish_state(get_free_heap()); + } + + if (this->block_sensor_ != nullptr) { +#if defined(USE_ESP8266) + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->block_sensor_->publish_state(ESP.getMaxFreeBlockSize()); +#elif defined(USE_ESP32) + this->block_sensor_->publish_state(heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL)); +#endif + } + +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + if (this->fragmentation_sensor_ != nullptr) { + // NOLINTNEXTLINE(readability-static-accessed-through-instance) + this->fragmentation_sensor_->publish_state(ESP.getHeapFragmentation()); + } +#endif + + if (this->loop_time_sensor_ != nullptr) { + this->loop_time_sensor_->publish_state(this->max_loop_time_); + this->max_loop_time_ = 0; + } +#endif // USE_SENSOR +} + float DebugComponent::get_setup_priority() const { return setup_priority::LATE; } } // namespace debug diff --git a/esphome/components/debug/debug_component.h b/esphome/components/debug/debug_component.h index e3d3d8f81053..f966b4fafc02 100644 --- a/esphome/components/debug/debug_component.h +++ b/esphome/components/debug/debug_component.h @@ -1,18 +1,56 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/macros.h" +#include "esphome/core/helpers.h" + +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif namespace esphome { namespace debug { -class DebugComponent : public Component { +class DebugComponent : public PollingComponent { public: void loop() override; + void update() override; float get_setup_priority() const override; void dump_config() override; +#ifdef USE_TEXT_SENSOR + void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; } +#endif // USE_TEXT_SENSOR +#ifdef USE_SENSOR + void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; } + void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; } +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; } +#endif + void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; } +#endif // USE_SENSOR protected: uint32_t free_heap_{}; + +#ifdef USE_SENSOR + uint32_t last_loop_timetag_{0}; + uint32_t max_loop_time_{0}; + + sensor::Sensor *free_sensor_{nullptr}; + sensor::Sensor *block_sensor_{nullptr}; +#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2) + sensor::Sensor *fragmentation_sensor_{nullptr}; +#endif + sensor::Sensor *loop_time_sensor_{nullptr}; +#endif // USE_SENSOR + +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *device_info_{nullptr}; +#endif // USE_TEXT_SENSOR }; } // namespace debug diff --git a/esphome/components/debug/sensor.py b/esphome/components/debug/sensor.py new file mode 100644 index 000000000000..deea6fd5ed9f --- /dev/null +++ b/esphome/components/debug/sensor.py @@ -0,0 +1,49 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor +from esphome.const import ( + CONF_FREE, + CONF_FRAGMENTATION, + CONF_BLOCK, + CONF_LOOP_TIME, + UNIT_MILLISECOND, + UNIT_PERCENT, + UNIT_BYTES, + ICON_COUNTER, + ICON_TIMER, +) +from . import CONF_DEBUG_ID, DebugComponent + +DEPENDENCIES = ["debug"] + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), + cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0), + cv.Optional(CONF_FRAGMENTATION): cv.All( + cv.only_on_esp8266, + cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)), + sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1), + ), + cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(UNIT_MILLISECOND, ICON_TIMER, 0), +} + + +async def to_code(config): + debug_component = await cg.get_variable(config[CONF_DEBUG_ID]) + + if CONF_FREE in config: + sens = await sensor.new_sensor(config[CONF_FREE]) + cg.add(debug_component.set_free_sensor(sens)) + + if CONF_BLOCK in config: + sens = await sensor.new_sensor(config[CONF_BLOCK]) + cg.add(debug_component.set_block_sensor(sens)) + + if CONF_FRAGMENTATION in config: + sens = await sensor.new_sensor(config[CONF_FRAGMENTATION]) + cg.add(debug_component.set_fragmentation_sensor(sens)) + + if CONF_LOOP_TIME in config: + sens = await sensor.new_sensor(config[CONF_LOOP_TIME]) + cg.add(debug_component.set_loop_time_sensor(sens)) diff --git a/esphome/components/debug/text_sensor.py b/esphome/components/debug/text_sensor.py new file mode 100644 index 000000000000..f8d1016fbfe4 --- /dev/null +++ b/esphome/components/debug/text_sensor.py @@ -0,0 +1,24 @@ +from esphome.components import text_sensor +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_DEVICE + +from . import CONF_DEBUG_ID, DebugComponent + +DEPENDENCIES = ["debug"] + + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent), + cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(), + } +) + + +async def to_code(config): + debug_component = await cg.get_variable(config[CONF_DEBUG_ID]) + + if CONF_DEVICE in config: + sens = await text_sensor.new_text_sensor(config[CONF_DEVICE]) + cg.add(debug_component.set_device_info_sensor(sens)) diff --git a/esphome/components/demo/__init__.py b/esphome/components/demo/__init__.py index fae8a2b07d65..f20a96ebd44d 100644 --- a/esphome/components/demo/__init__.py +++ b/esphome/components/demo/__init__.py @@ -37,12 +37,10 @@ DEVICE_CLASS_TEMPERATURE, ICON_BLUETOOTH, ICON_BLUR, - ICON_EMPTY, ICON_THERMOMETER, STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, UNIT_CELSIUS, - UNIT_EMPTY, UNIT_PERCENT, UNIT_WATT_HOURS, ) @@ -67,7 +65,7 @@ DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True) DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component) DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True) -DemoFan = demo_ns.class_("DemoFan", cg.Component) +DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component) DemoFanType = demo_ns.enum("DemoFanType", is_class=True) DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component) DemoLightType = demo_ns.enum("DemoLightType", is_class=True) @@ -339,7 +337,7 @@ }, ], ): [ - sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0) + sensor.sensor_schema(accuracy_decimals=0) .extend(cv.polling_component_schema("60s")) .extend( { @@ -378,12 +376,8 @@ }, ], ): [ - text_sensor.TEXT_SENSOR_SCHEMA.extend( + text_sensor.text_sensor_schema(klass=DemoTextSensor).extend( cv.polling_component_schema("60s") - ).extend( - { - cv.GenerateID(): cv.declare_id(DemoTextSensor), - } ) ], } @@ -411,8 +405,7 @@ async def to_code(config): for conf in config[CONF_FANS]: var = cg.new_Pvariable(conf[CONF_OUTPUT_ID]) await cg.register_component(var, conf) - fan_ = await fan.create_fan_state(conf) - cg.add(var.set_fan(fan_)) + await fan.register_fan(var, conf) cg.add(var.set_type(conf[CONF_TYPE])) for conf in config[CONF_LIGHTS]: @@ -444,6 +437,5 @@ async def to_code(config): await switch.register_switch(var, conf) for conf in config[CONF_TEXT_SENSORS]: - var = cg.new_Pvariable(conf[CONF_ID]) + var = await text_sensor.new_text_sensor(conf) await cg.register_component(var, conf) - await text_sensor.register_text_sensor(var, conf) diff --git a/esphome/components/demo/demo_fan.h b/esphome/components/demo/demo_fan.h index e926f68edb98..09edc4e0b7fb 100644 --- a/esphome/components/demo/demo_fan.h +++ b/esphome/components/demo/demo_fan.h @@ -1,7 +1,7 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace demo { @@ -13,11 +13,10 @@ enum class DemoFanType { TYPE_4, }; -class DemoFan : public Component { +class DemoFan : public fan::Fan, public Component { public: void set_type(DemoFanType type) { type_ = type; } - void set_fan(fan::FanState *fan) { fan_ = fan; } - void setup() override { + fan::FanTraits get_traits() override { fan::FanTraits traits{}; // oscillation @@ -43,10 +42,23 @@ class DemoFan : public Component { break; } - this->fan_->set_traits(traits); + return traits; + } + + protected: + void control(const fan::FanCall &call) override { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_speed().has_value()) + this->speed = *call.get_speed(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); + + this->publish_state(); } - fan::FanState *fan_; DemoFanType type_; }; diff --git a/esphome/components/dht/dht.cpp b/esphome/components/dht/dht.cpp index 2a4ccf1529bd..c70b22733080 100644 --- a/esphome/components/dht/dht.cpp +++ b/esphome/components/dht/dht.cpp @@ -114,10 +114,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r // Wait for rising edge while (!this->pin_->digital_read()) { if (micros() - start_time > 90) { - if (i < 0) + if (i < 0) { error_code = 1; - else + } else { error_code = 2; + } break; } } @@ -130,10 +131,11 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r // Wait for falling edge while (this->pin_->digital_read()) { if ((end_time = micros()) - start_time > 90) { - if (i < 0) + if (i < 0) { error_code = 3; - else + } else { error_code = 4; + } break; } } diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp index b97fb4ae23dd..4ad353a254f9 100644 --- a/esphome/components/display/display_buffer.cpp +++ b/esphome/components/display/display_buffer.cpp @@ -176,9 +176,10 @@ void DisplayBuffer::print(int x, int y, Font *font, Color color, TextAlign align ESP_LOGW(TAG, "Encountered character without representation in font: '%c'", text[i]); if (!font->get_glyphs().empty()) { uint8_t glyph_width = font->get_glyphs()[0].glyph_data_->width; - for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) + for (int glyph_x = 0; glyph_x < glyph_width; glyph_x++) { for (int glyph_y = 0; glyph_y < height; glyph_y++) this->draw_pixel_at(glyph_x + x_at, glyph_y + y_start, color); + } x_at += glyph_width; } @@ -233,6 +234,14 @@ void DisplayBuffer::image(int x, int y, Image *image, Color color_on, Color colo } } break; + case IMAGE_TYPE_TRANSPARENT_BINARY: + for (int img_x = 0; img_x < image->get_width(); img_x++) { + for (int img_y = 0; img_y < image->get_height(); img_y++) { + if (image->get_pixel(img_x, img_y)) + this->draw_pixel_at(x + img_x, y + img_y, color_on); + } + } + break; } } @@ -243,6 +252,12 @@ void DisplayBuffer::legend(int x, int y, graph::Graph *graph, Color color_on) { } #endif // USE_GRAPH +#ifdef USE_QR_CODE +void DisplayBuffer::qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on, int scale) { + qr_code->draw(this, x, y, color_on, scale); +} +#endif // USE_QR_CODE + void DisplayBuffer::get_text_bounds(int x, int y, const char *text, Font *font, TextAlign align, int *x1, int *y1, int *width, int *height) { int x_offset, baseline; @@ -417,10 +432,11 @@ int Font::match_next_glyph(const char *str, int *match_length) { int hi = this->glyphs_.size() - 1; while (lo != hi) { int mid = (lo + hi + 1) / 2; - if (this->glyphs_[mid].compare_to(str)) + if (this->glyphs_[mid].compare_to(str)) { lo = mid; - else + } else { hi = mid - 1; + } } *match_length = this->glyphs_[lo].match_length(str); if (*match_length <= 0) @@ -446,10 +462,11 @@ void Font::measure(const char *str, int *width, int *x_offset, int *baseline, in } const Glyph &glyph = this->glyphs_[glyph_n]; - if (!has_char) + if (!has_char) { min_x = glyph.glyph_data_->offset_x; - else + } else { min_x = std::min(min_x, x + glyph.glyph_data_->offset_x); + } x += glyph.glyph_data_->width + glyph.glyph_data_->offset_x; i += match_length; diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h index c803180a2d51..8ee1cd87792a 100644 --- a/esphome/components/display/display_buffer.h +++ b/esphome/components/display/display_buffer.h @@ -14,6 +14,10 @@ #include "esphome/components/graph/graph.h" #endif +#ifdef USE_QR_CODE +#include "esphome/components/qr_code/qr_code.h" +#endif + namespace esphome { namespace display { @@ -73,7 +77,12 @@ extern const Color COLOR_OFF; /// Turn the pixel ON. extern const Color COLOR_ON; -enum ImageType { IMAGE_TYPE_BINARY = 0, IMAGE_TYPE_GRAYSCALE = 1, IMAGE_TYPE_RGB24 = 2 }; +enum ImageType { + IMAGE_TYPE_BINARY = 0, + IMAGE_TYPE_GRAYSCALE = 1, + IMAGE_TYPE_RGB24 = 2, + IMAGE_TYPE_TRANSPARENT_BINARY = 3, +}; enum DisplayRotation { DISPLAY_ROTATION_0_DEGREES = 0, @@ -302,6 +311,17 @@ class DisplayBuffer { void legend(int x, int y, graph::Graph *graph, Color color_on = COLOR_ON); #endif // USE_GRAPH +#ifdef USE_QR_CODE + /** Draw the `qr_code` with the top-left corner at [x,y] to the screen. + * + * @param x The x coordinate of the upper left corner. + * @param y The y coordinate of the upper left corner. + * @param qr_code The qr_code to draw + * @param color_on The color to replace in binary images for the on bits. + */ + void qr_code(int x, int y, qr_code::QrCode *qr_code, Color color_on = COLOR_ON, int scale = 1); +#endif + /** Get the text bounds of the given string. * * @param x The x coordinate to place the string at, can be 0 if only interested in dimensions. @@ -336,15 +356,15 @@ class DisplayBuffer { // Internal method to set display auto clearing. void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; } + virtual int get_height_internal() = 0; + virtual int get_width_internal() = 0; + DisplayRotation get_rotation() const { return this->rotation_; } + protected: void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg); virtual void draw_absolute_pixel_internal(int x, int y, Color color) = 0; - virtual int get_height_internal() = 0; - - virtual int get_width_internal() = 0; - void init_internal_(uint32_t buffer_length); void do_update_(); diff --git a/esphome/components/dsmr/text_sensor.py b/esphome/components/dsmr/text_sensor.py index 339eea711f70..202cc070201f 100644 --- a/esphome/components/dsmr/text_sensor.py +++ b/esphome/components/dsmr/text_sensor.py @@ -1,9 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import ( - CONF_ID, -) + from . import Dsmr, CONF_DSMR_ID AUTO_LOAD = ["dsmr"] @@ -11,71 +9,19 @@ CONFIG_SCHEMA = cv.Schema( { cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr), - cv.Optional("identification"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("p1_version"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("p1_version_be"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("timestamp"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("electricity_tariff"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("electricity_failure_log"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("message_short"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("message_long"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("gas_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("thermal_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("water_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("sub_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), - cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), - } - ), + cv.Optional("identification"): text_sensor.text_sensor_schema(), + cv.Optional("p1_version"): text_sensor.text_sensor_schema(), + cv.Optional("p1_version_be"): text_sensor.text_sensor_schema(), + cv.Optional("timestamp"): text_sensor.text_sensor_schema(), + cv.Optional("electricity_tariff"): text_sensor.text_sensor_schema(), + cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(), + cv.Optional("message_short"): text_sensor.text_sensor_schema(), + cv.Optional("message_long"): text_sensor.text_sensor_schema(), + cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(), + cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(), } ).extend(cv.COMPONENT_SCHEMA) @@ -89,8 +35,7 @@ async def to_code(config): continue id = conf.get("id") if id and id.type == text_sensor.TextSensor: - var = cg.new_Pvariable(conf[CONF_ID]) - await text_sensor.register_text_sensor(var, conf) + var = await text_sensor.new_text_sensor(conf) cg.add(getattr(hub, f"set_{key}")(var)) text_sensors.append(f"F({key})") diff --git a/esphome/components/e131/e131.cpp b/esphome/components/e131/e131.cpp index 35510fe20469..6d584687ced0 100644 --- a/esphome/components/e131/e131.cpp +++ b/esphome/components/e131/e131.cpp @@ -97,7 +97,7 @@ bool E131Component::process_(int universe, const E131Packet &packet) { ESP_LOGV(TAG, "Received E1.31 packet for %d universe, with %d bytes", universe, packet.count); - for (auto light_effect : light_effects_) { + for (auto *light_effect : light_effects_) { handled = light_effect->process_(universe, packet) || handled; } diff --git a/esphome/components/e131/e131.h b/esphome/components/e131/e131.h index 3819e522a542..648cfb458551 100644 --- a/esphome/components/e131/e131.h +++ b/esphome/components/e131/e131.h @@ -33,11 +33,9 @@ class E131Component : public esphome::Component { void loop() override; float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } - public: void add_effect(E131AddressableLightEffect *light_effect); void remove_effect(E131AddressableLightEffect *light_effect); - public: void set_method(E131ListenMethod listen_method) { this->listen_method_ = listen_method; } protected: @@ -47,7 +45,6 @@ class E131Component : public esphome::Component { void join_(int universe); void leave_(int universe); - protected: E131ListenMethod listen_method_{E131_MULTICAST}; std::unique_ptr udp_; std::set light_effects_; diff --git a/esphome/components/e131/e131_addressable_light_effect.cpp b/esphome/components/e131/e131_addressable_light_effect.cpp index 371f3b9cbf6c..7a3e71808ebb 100644 --- a/esphome/components/e131/e131_addressable_light_effect.cpp +++ b/esphome/components/e131/e131_addressable_light_effect.cpp @@ -47,7 +47,7 @@ void E131AddressableLightEffect::apply(light::AddressableLight &it, const Color } bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet) { - auto it = get_addressable_(); + auto *it = get_addressable_(); // check if this is our universe and data are valid if (universe < first_universe_ || universe > get_last_universe()) @@ -57,7 +57,7 @@ bool E131AddressableLightEffect::process_(int universe, const E131Packet &packet // limit amount of lights per universe and received int output_end = std::min(it->size(), std::min(output_offset + get_lights_per_universe(), output_offset + packet.count - 1)); - auto input_data = packet.values + 1; + auto *input_data = packet.values + 1; ESP_LOGV(TAG, "Applying data for '%s' on %d universe, for %d-%d.", get_name().c_str(), universe, output_offset, output_end); diff --git a/esphome/components/e131/e131_addressable_light_effect.h b/esphome/components/e131/e131_addressable_light_effect.h index e78f6bb0e0e9..b3e481e43b55 100644 --- a/esphome/components/e131/e131_addressable_light_effect.h +++ b/esphome/components/e131/e131_addressable_light_effect.h @@ -17,19 +17,16 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { public: E131AddressableLightEffect(const std::string &name); - public: void start() override; void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; - public: int get_data_per_universe() const; int get_lights_per_universe() const; int get_first_universe() const; int get_last_universe() const; int get_universe_count() const; - public: void set_first_universe(int universe) { this->first_universe_ = universe; } void set_channels(E131LightChannels channels) { this->channels_ = channels; } void set_e131(E131Component *e131) { this->e131_ = e131; } @@ -37,7 +34,6 @@ class E131AddressableLightEffect : public light::AddressableLightEffect { protected: bool process_(int universe, const E131Packet &packet); - protected: int first_universe_{0}; int last_universe_{0}; E131LightChannels channels_{E131_RGB}; diff --git a/esphome/components/e131/e131_packet.cpp b/esphome/components/e131/e131_packet.cpp index b20eb9f666e8..f199d3574b2f 100644 --- a/esphome/components/e131/e131_packet.cpp +++ b/esphome/components/e131/e131_packet.cpp @@ -116,7 +116,7 @@ bool E131Component::packet_(const std::vector &data, int &universe, E13 if (data.size() < E131_MIN_PACKET_SIZE) return false; - auto sbuff = reinterpret_cast(&data[0]); + auto *sbuff = reinterpret_cast(&data[0]); if (memcmp(sbuff->acn_id, ACN_ID, sizeof(sbuff->acn_id)) != 0) return false; diff --git a/esphome/components/ektf2232/__init__.py b/esphome/components/ektf2232/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/esphome/components/ektf2232/ektf2232.cpp b/esphome/components/ektf2232/ektf2232.cpp new file mode 100644 index 000000000000..8df25fce2488 --- /dev/null +++ b/esphome/components/ektf2232/ektf2232.cpp @@ -0,0 +1,166 @@ +#include "ektf2232.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ektf2232 { + +static const char *const TAG = "ektf2232"; + +static const uint8_t SOFT_RESET_CMD[4] = {0x77, 0x77, 0x77, 0x77}; +static const uint8_t HELLO[4] = {0x55, 0x55, 0x55, 0x55}; +static const uint8_t GET_X_RES[4] = {0x53, 0x60, 0x00, 0x00}; +static const uint8_t GET_Y_RES[4] = {0x53, 0x63, 0x00, 0x00}; +static const uint8_t GET_POWER_STATE_CMD[4] = {0x53, 0x50, 0x00, 0x01}; + +void EKTF2232TouchscreenStore::gpio_intr(EKTF2232TouchscreenStore *store) { store->touch = true; } + +void EKTF2232Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up EKT2232 Touchscreen..."); + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + + this->store_.pin = this->interrupt_pin_->to_isr(); + this->interrupt_pin_->attach_interrupt(EKTF2232TouchscreenStore::gpio_intr, &this->store_, + gpio::INTERRUPT_FALLING_EDGE); + + this->rts_pin_->setup(); + + this->hard_reset_(); + if (!this->soft_reset_()) { + ESP_LOGE(TAG, "Failed to soft reset EKT2232!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + + // Get touch resolution + uint8_t received[4]; + this->write(GET_X_RES, 4); + if (this->read(received, 4)) { + ESP_LOGE(TAG, "Failed to read X resolution!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + this->x_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); + + this->write(GET_Y_RES, 4); + if (this->read(received, 4)) { + ESP_LOGE(TAG, "Failed to read Y resolution!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + this->y_resolution_ = ((received[2])) | ((received[3] & 0xf0) << 4); + this->store_.touch = false; + + this->set_power_state(true); +} + +void EKTF2232Touchscreen::loop() { + if (!this->store_.touch) + return; + this->store_.touch = false; + + uint8_t touch_count = 0; + std::vector touches; + + uint8_t raw[8]; + this->read(raw, 8); + for (int i = 0; i < 8; i++) { + if (raw[7] & (1 << i)) + touch_count++; + } + + if (touch_count == 0) { + for (auto *listener : this->touch_listeners_) + listener->release(); + return; + } + + touch_count = std::min(touch_count, 2); + + ESP_LOGV(TAG, "Touch count: %d", touch_count); + + for (int i = 0; i < touch_count; i++) { + uint8_t *d = raw + 1 + (i * 3); + uint32_t raw_x = (d[0] & 0xF0) << 4 | d[1]; + uint32_t raw_y = (d[0] & 0x0F) << 8 | d[2]; + + raw_x = raw_x * this->display_height_ - 1; + raw_y = raw_y * this->display_width_ - 1; + + TouchPoint tp; + switch (this->rotation_) { + case ROTATE_0_DEGREES: + tp.y = raw_x / this->x_resolution_; + tp.x = this->display_width_ - 1 - (raw_y / this->y_resolution_); + break; + case ROTATE_90_DEGREES: + tp.x = raw_x / this->x_resolution_; + tp.y = raw_y / this->y_resolution_; + break; + case ROTATE_180_DEGREES: + tp.y = this->display_height_ - 1 - (raw_x / this->x_resolution_); + tp.x = raw_y / this->y_resolution_; + break; + case ROTATE_270_DEGREES: + tp.x = this->display_height_ - 1 - (raw_x / this->x_resolution_); + tp.y = this->display_width_ - 1 - (raw_y / this->y_resolution_); + break; + } + + this->defer([this, tp]() { this->send_touch_(tp); }); + } +} + +void EKTF2232Touchscreen::set_power_state(bool enable) { + uint8_t data[] = {0x54, 0x50, 0x00, 0x01}; + data[1] |= (enable << 3); + this->write(data, 4); +} + +bool EKTF2232Touchscreen::get_power_state() { + uint8_t received[4]; + this->write(GET_POWER_STATE_CMD, 4); + this->store_.touch = false; + this->read(received, 4); + return (received[1] >> 3) & 1; +} + +void EKTF2232Touchscreen::hard_reset_() { + this->rts_pin_->digital_write(false); + delay(15); + this->rts_pin_->digital_write(true); + delay(15); +} + +bool EKTF2232Touchscreen::soft_reset_() { + auto err = this->write(SOFT_RESET_CMD, 4); + if (err != i2c::ERROR_OK) + return false; + + uint8_t received[4]; + uint16_t timeout = 1000; + while (!this->store_.touch && timeout > 0) { + delay(1); + timeout--; + } + if (timeout > 0) + this->store_.touch = true; + this->read(received, 4); + this->store_.touch = false; + + return !memcmp(received, HELLO, 4); +} + +void EKTF2232Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "EKT2232 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); + LOG_PIN(" RTS Pin: ", this->rts_pin_); +} + +} // namespace ektf2232 +} // namespace esphome diff --git a/esphome/components/ektf2232/ektf2232.h b/esphome/components/ektf2232/ektf2232.h new file mode 100644 index 000000000000..e880b77f9913 --- /dev/null +++ b/esphome/components/ektf2232/ektf2232.h @@ -0,0 +1,45 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace ektf2232 { + +struct EKTF2232TouchscreenStore { + volatile bool touch; + ISRInternalGPIOPin pin; + + static void gpio_intr(EKTF2232TouchscreenStore *store); +}; + +using namespace touchscreen; + +class EKTF2232Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + void set_rts_pin(GPIOPin *pin) { this->rts_pin_ = pin; } + + void set_power_state(bool enable); + bool get_power_state(); + + protected: + void hard_reset_(); + bool soft_reset_(); + + InternalGPIOPin *interrupt_pin_; + GPIOPin *rts_pin_; + EKTF2232TouchscreenStore store_; + uint16_t x_resolution_; + uint16_t y_resolution_; +}; + +} // namespace ektf2232 +} // namespace esphome diff --git a/esphome/components/ektf2232/touchscreen.py b/esphome/components/ektf2232/touchscreen.py new file mode 100644 index 000000000000..b3513b2670fb --- /dev/null +++ b/esphome/components/ektf2232/touchscreen.py @@ -0,0 +1,48 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +ektf2232_ns = cg.esphome_ns.namespace("ektf2232") +EKTF2232Touchscreen = ektf2232_ns.class_( + "EKTF2232Touchscreen", + touchscreen.Touchscreen, + cg.Component, + i2c.I2CDevice, +) + +CONF_EKTF2232_ID = "ektf2232_id" +CONF_INTERRUPT_PIN = "interrupt_pin" +CONF_RTS_PIN = "rts_pin" + + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EKTF2232Touchscreen), + cv.Required(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + cv.Required(CONF_RTS_PIN): pins.gpio_output_pin_schema, + } + ) + .extend(i2c.i2c_device_schema(0x15)) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await touchscreen.register_touchscreen(var, config) + + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) + rts_pin = await cg.gpio_pin_expression(config[CONF_RTS_PIN]) + cg.add(var.set_rts_pin(rts_pin)) diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 8214886f8cb7..1229675ad8b4 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -18,6 +18,7 @@ KEY_FRAMEWORK_VERSION, KEY_TARGET_FRAMEWORK, KEY_TARGET_PLATFORM, + __version__, ) from esphome.core import CORE, HexInt import esphome.config_validation as cv @@ -106,7 +107,6 @@ def _format_framework_espidf_version(ver: cv.Version) -> str: # The new version needs to be thoroughly validated before changing the # recommended version as otherwise a bunch of devices could be bricked # * For all constants below, update platformio.ini (in this repo) -# and platformio.ini/platformio-lint.ini in the esphome-docker-base repository # The default/recommended arduino framework version # - https://github.com/espressif/arduino-esp32/releases @@ -115,16 +115,16 @@ def _format_framework_espidf_version(ver: cv.Version) -> str: # The platformio/espressif32 version to use for arduino frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ARDUINO_PLATFORM_VERSION = cv.Version(3, 3, 2) +ARDUINO_PLATFORM_VERSION = cv.Version(3, 5, 0) # The default/recommended esp-idf framework version # - https://github.com/espressif/esp-idf/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf -RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 0) +RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 3, 2) # The platformio/espressif32 version to use for esp-idf frameworks # - https://github.com/platformio/platform-espressif32/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32 -ESP_IDF_PLATFORM_VERSION = cv.Version(3, 3, 2) +ESP_IDF_PLATFORM_VERSION = cv.Version(3, 5, 0) def _arduino_check_versions(value): @@ -165,8 +165,8 @@ def _arduino_check_versions(value): def _esp_idf_check_versions(value): value = value.copy() lookups = { - "dev": (cv.Version(4, 3, 1), "https://github.com/espressif/esp-idf.git"), - "latest": (cv.Version(4, 3, 0), None), + "dev": (cv.Version(5, 0, 0), "https://github.com/espressif/esp-idf.git"), + "latest": (cv.Version(4, 3, 2), None), "recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None), } @@ -293,6 +293,8 @@ async def to_code(config): cg.add_platformio_option("lib_ldf_mode", "off") + framework_ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + conf = config[CONF_FRAMEWORK] cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) @@ -335,6 +337,13 @@ async def to_code(config): "CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE", False ) + cg.add_define( + "USE_ESP_IDF_VERSION_CODE", + cg.RawExpression( + f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" + ), + ) + elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO: cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") @@ -346,6 +355,13 @@ async def to_code(config): cg.add_platformio_option("board_build.partitions", "partitions.csv") + cg.add_define( + "USE_ARDUINO_VERSION_CODE", + cg.RawExpression( + f"VERSION_CODE({framework_ver.major}, {framework_ver.minor}, {framework_ver.patch})" + ), + ) + ARDUINO_PARTITIONS_CSV = """\ nvs, data, nvs, 0x009000, 0x005000, @@ -415,6 +431,14 @@ def copy_files(): CORE.relative_build_path("partitions.csv"), IDF_PARTITIONS_CSV, ) + # IDF build scripts look for version string to put in the build. + # However, if the build path does not have an initialized git repo, + # and no version.txt file exists, the CMake script fails for some setups. + # Fix by manually pasting a version.txt file, containing the ESPHome version + write_file_if_changed( + CORE.relative_build_path("version.txt"), + __version__, + ) dir = os.path.dirname(__file__) post_build_file = os.path.join(dir, "post_build.py.script") diff --git a/esphome/components/esp32_ble/ble_advertising.cpp b/esphome/components/esp32_ble/ble_advertising.cpp index 31b1f4c3839a..2083bf5f08d4 100644 --- a/esphome/components/esp32_ble/ble_advertising.cpp +++ b/esphome/components/esp32_ble/ble_advertising.cpp @@ -42,6 +42,11 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) { this->advertising_uuids_.end()); } +void BLEAdvertising::set_manufacturer_data(uint8_t *data, uint16_t size) { + this->advertising_data_.p_manufacturer_data = data; + this->advertising_data_.manufacturer_len = size; +} + void BLEAdvertising::start() { int num_services = this->advertising_uuids_.size(); if (num_services == 0) { diff --git a/esphome/components/esp32_ble/ble_advertising.h b/esphome/components/esp32_ble/ble_advertising.h index 01e2ba129576..079bd6c14c11 100644 --- a/esphome/components/esp32_ble/ble_advertising.h +++ b/esphome/components/esp32_ble/ble_advertising.h @@ -20,6 +20,7 @@ class BLEAdvertising { void remove_service_uuid(ESPBTUUID uuid); void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; } void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; } + void set_manufacturer_data(uint8_t *data, uint16_t size); void start(); void stop(); diff --git a/esphome/components/esp32_ble_server/ble_characteristic.cpp b/esphome/components/esp32_ble_server/ble_characteristic.cpp index fae8c13934af..df822ac0b921 100644 --- a/esphome/components/esp32_ble_server/ble_characteristic.cpp +++ b/esphome/components/esp32_ble_server/ble_characteristic.cpp @@ -147,40 +147,46 @@ bool BLECharacteristic::is_failed() { } void BLECharacteristic::set_broadcast_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_BROADCAST); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_BROADCAST); + } } void BLECharacteristic::set_indicate_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_INDICATE); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_INDICATE); + } } void BLECharacteristic::set_notify_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_NOTIFY); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_NOTIFY); + } } void BLECharacteristic::set_read_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_READ); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_READ); + } } void BLECharacteristic::set_write_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE); + } } void BLECharacteristic::set_write_no_response_property(bool value) { - if (value) + if (value) { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ | ESP_GATT_CHAR_PROP_BIT_WRITE_NR); - else + } else { this->properties_ = (esp_gatt_char_prop_t)(this->properties_ & ~ESP_GATT_CHAR_PROP_BIT_WRITE_NR); + } } void BLECharacteristic::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, diff --git a/esphome/components/esp32_ble_server/ble_service.cpp b/esphome/components/esp32_ble_server/ble_service.cpp index 281164f0f58d..4fcd2e3e7968 100644 --- a/esphome/components/esp32_ble_server/ble_service.cpp +++ b/esphome/components/esp32_ble_server/ble_service.cpp @@ -18,9 +18,10 @@ BLEService::~BLEService() { } BLECharacteristic *BLEService::get_characteristic(ESPBTUUID uuid) { - for (auto *chr : this->characteristics_) + for (auto *chr : this->characteristics_) { if (chr->get_uuid() == uuid) return chr; + } return nullptr; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 084dab4c840b..7614e33979b8 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -58,11 +58,12 @@ void ESP32BLETracker::setup() { void ESP32BLETracker::loop() { BLEEvent *ble_event = this->ble_events_.pop(); while (ble_event != nullptr) { - if (ble_event->type_) + if (ble_event->type_) { this->real_gattc_event_handler_(ble_event->event_.gattc.gattc_event, ble_event->event_.gattc.gattc_if, &ble_event->event_.gattc.gattc_param); - else + } else { this->real_gap_event_handler_(ble_event->event_.gap.gap_event, &ble_event->event_.gap.gap_param); + } delete ble_event; // NOLINT(cppcoreguidelines-owning-memory) ble_event = this->ble_events_.pop(); } @@ -89,11 +90,12 @@ void ESP32BLETracker::loop() { device.parse_scan_rst(this->scan_result_buffer_[i]); bool found = false; - for (auto *listener : this->listeners_) + for (auto *listener : this->listeners_) { if (listener->parse_device(device)) found = true; + } - for (auto *client : this->clients_) + for (auto *client : this->clients_) { if (client->parse_device(device)) { found = true; if (client->state() == ClientState::DISCOVERED) { @@ -103,6 +105,7 @@ void ESP32BLETracker::loop() { } } } + } if (!found) { this->print_bt_device_info(device); diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py index d42d4f5de30d..912e70576619 100644 --- a/esphome/components/esp32_camera/__init__.py +++ b/esphome/components/esp32_camera/__init__.py @@ -23,6 +23,7 @@ esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera") ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase) + ESP32CameraFrameSize = esp32_camera_ns.enum("ESP32CameraFrameSize") FRAME_SIZES = { "160X120": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_160X120, @@ -46,30 +47,76 @@ "1600X1200": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, "UXGA": ESP32CameraFrameSize.ESP32_CAMERA_SIZE_1600X1200, } +ESP32GainControlMode = esp32_camera_ns.enum("ESP32GainControlMode") +ENUM_GAIN_CONTROL_MODE = { + "MANUAL": ESP32GainControlMode.ESP32_GC_MODE_MANU, + "AUTO": ESP32GainControlMode.ESP32_GC_MODE_AUTO, +} +ESP32AgcGainCeiling = esp32_camera_ns.enum("ESP32AgcGainCeiling") +ENUM_GAIN_CEILING = { + "2X": ESP32AgcGainCeiling.ESP32_GAINCEILING_2X, + "4X": ESP32AgcGainCeiling.ESP32_GAINCEILING_4X, + "8X": ESP32AgcGainCeiling.ESP32_GAINCEILING_8X, + "16X": ESP32AgcGainCeiling.ESP32_GAINCEILING_16X, + "32X": ESP32AgcGainCeiling.ESP32_GAINCEILING_32X, + "64X": ESP32AgcGainCeiling.ESP32_GAINCEILING_64X, + "128X": ESP32AgcGainCeiling.ESP32_GAINCEILING_128X, +} +ESP32WhiteBalanceMode = esp32_camera_ns.enum("ESP32WhiteBalanceMode") +ENUM_WB_MODE = { + "AUTO": ESP32WhiteBalanceMode.ESP32_WB_MODE_AUTO, + "SUNNY": ESP32WhiteBalanceMode.ESP32_WB_MODE_SUNNY, + "CLOUDY": ESP32WhiteBalanceMode.ESP32_WB_MODE_CLOUDY, + "OFFICE": ESP32WhiteBalanceMode.ESP32_WB_MODE_OFFICE, + "HOME": ESP32WhiteBalanceMode.ESP32_WB_MODE_HOME, +} +ESP32SpecialEffect = esp32_camera_ns.enum("ESP32SpecialEffect") +ENUM_SPECIAL_EFFECT = { + "NONE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NONE, + "NEGATIVE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_NEGATIVE, + "GRAYSCALE": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GRAYSCALE, + "RED_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_RED_TINT, + "GREEN_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_GREEN_TINT, + "BLUE_TINT": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_BLUE_TINT, + "SEPIA": ESP32SpecialEffect.ESP32_SPECIAL_EFFECT_SEPIA, +} +# pin assignment CONF_VSYNC_PIN = "vsync_pin" CONF_HREF_PIN = "href_pin" CONF_PIXEL_CLOCK_PIN = "pixel_clock_pin" CONF_EXTERNAL_CLOCK = "external_clock" CONF_I2C_PINS = "i2c_pins" CONF_POWER_DOWN_PIN = "power_down_pin" - -CONF_MAX_FRAMERATE = "max_framerate" -CONF_IDLE_FRAMERATE = "idle_framerate" +# image CONF_JPEG_QUALITY = "jpeg_quality" CONF_VERTICAL_FLIP = "vertical_flip" CONF_HORIZONTAL_MIRROR = "horizontal_mirror" +CONF_SATURATION = "saturation" +CONF_SPECIAL_EFFECT = "special_effect" +# exposure +CONF_AEC_MODE = "aec_mode" CONF_AEC2 = "aec2" CONF_AE_LEVEL = "ae_level" CONF_AEC_VALUE = "aec_value" -CONF_SATURATION = "saturation" +# gains +CONF_AGC_MODE = "agc_mode" +CONF_AGC_VALUE = "agc_value" +CONF_AGC_GAIN_CEILING = "agc_gain_ceiling" +# white balance +CONF_WB_MODE = "wb_mode" +# test pattern CONF_TEST_PATTERN = "test_pattern" +# framerates +CONF_MAX_FRAMERATE = "max_framerate" +CONF_IDLE_FRAMERATE = "idle_framerate" camera_range_param = cv.int_range(min=-2, max=2) CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend( { cv.GenerateID(): cv.declare_id(ESP32Camera), + # pin assignment cv.Required(CONF_DATA_PINS): cv.All( [pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8) ), @@ -92,12 +139,7 @@ ), cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number, cv.Optional(CONF_POWER_DOWN_PIN): pins.internal_gpio_output_pin_number, - cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( - cv.framerate, cv.Range(min=0, min_included=False, max=60) - ), - cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( - cv.framerate, cv.Range(min=0, max=1) - ), + # image cv.Optional(CONF_RESOLUTION, default="640X480"): cv.enum( FRAME_SIZES, upper=True ), @@ -107,29 +149,66 @@ cv.Optional(CONF_SATURATION, default=0): camera_range_param, cv.Optional(CONF_VERTICAL_FLIP, default=True): cv.boolean, cv.Optional(CONF_HORIZONTAL_MIRROR, default=True): cv.boolean, + cv.Optional(CONF_SPECIAL_EFFECT, default="NONE"): cv.enum( + ENUM_SPECIAL_EFFECT, upper=True + ), + # exposure + cv.Optional(CONF_AGC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), cv.Optional(CONF_AEC2, default=False): cv.boolean, cv.Optional(CONF_AE_LEVEL, default=0): camera_range_param, cv.Optional(CONF_AEC_VALUE, default=300): cv.int_range(min=0, max=1200), + # gains + cv.Optional(CONF_AEC_MODE, default="AUTO"): cv.enum( + ENUM_GAIN_CONTROL_MODE, upper=True + ), + cv.Optional(CONF_AGC_VALUE, default=0): cv.int_range(min=0, max=30), + cv.Optional(CONF_AGC_GAIN_CEILING, default="2X"): cv.enum( + ENUM_GAIN_CEILING, upper=True + ), + # white balance + cv.Optional(CONF_WB_MODE, default="AUTO"): cv.enum(ENUM_WB_MODE, upper=True), + # test pattern cv.Optional(CONF_TEST_PATTERN, default=False): cv.boolean, + # framerates + cv.Optional(CONF_MAX_FRAMERATE, default="10 fps"): cv.All( + cv.framerate, cv.Range(min=0, min_included=False, max=60) + ), + cv.Optional(CONF_IDLE_FRAMERATE, default="0.1 fps"): cv.All( + cv.framerate, cv.Range(min=0, max=1) + ), } ).extend(cv.COMPONENT_SCHEMA) SETTERS = { + # pin assignment CONF_DATA_PINS: "set_data_pins", CONF_VSYNC_PIN: "set_vsync_pin", CONF_HREF_PIN: "set_href_pin", CONF_PIXEL_CLOCK_PIN: "set_pixel_clock_pin", CONF_RESET_PIN: "set_reset_pin", CONF_POWER_DOWN_PIN: "set_power_down_pin", + # image CONF_JPEG_QUALITY: "set_jpeg_quality", CONF_VERTICAL_FLIP: "set_vertical_flip", CONF_HORIZONTAL_MIRROR: "set_horizontal_mirror", - CONF_AEC2: "set_aec2", - CONF_AE_LEVEL: "set_ae_level", - CONF_AEC_VALUE: "set_aec_value", CONF_CONTRAST: "set_contrast", CONF_BRIGHTNESS: "set_brightness", CONF_SATURATION: "set_saturation", + CONF_SPECIAL_EFFECT: "set_special_effect", + # exposure + CONF_AEC_MODE: "set_aec_mode", + CONF_AEC2: "set_aec2", + CONF_AE_LEVEL: "set_ae_level", + CONF_AEC_VALUE: "set_aec_value", + # gains + CONF_AGC_MODE: "set_agc_mode", + CONF_AGC_VALUE: "set_agc_value", + CONF_AGC_GAIN_CEILING: "set_agc_gain_ceiling", + # white balance + CONF_WB_MODE: "set_wb_mode", + # test pattern CONF_TEST_PATTERN: "set_test_pattern", } diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp index 7d11f98d0999..851926b08363 100644 --- a/esphome/components/esp32_camera/esp32_camera.cpp +++ b/esphome/components/esp32_camera/esp32_camera.cpp @@ -11,10 +11,14 @@ namespace esp32_camera { static const char *const TAG = "esp32_camera"; +/* ---------------- public API (derivated) ---------------- */ void ESP32Camera::setup() { global_esp32_camera = this; + /* initialize time to now */ this->last_update_ = millis(); + + /* initialize camera */ esp_err_t err = esp_camera_init(&this->config_); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_camera_init failed: %s", esp_err_to_name(err)); @@ -23,16 +27,10 @@ void ESP32Camera::setup() { return; } - sensor_t *s = esp_camera_sensor_get(); - s->set_vflip(s, this->vertical_flip_); - s->set_hmirror(s, this->horizontal_mirror_); - s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable - s->set_ae_level(s, this->ae_level_); // -2 to 2 - s->set_aec_value(s, this->aec_value_); // 0 to 1200 - s->set_contrast(s, this->contrast_); - s->set_brightness(s, this->brightness_); - s->set_saturation(s, this->saturation_); - s->set_colorbar(s, this->test_pattern_); + /* initialize camera parameters */ + this->update_camera_parameters(); + + /* initialize RTOS */ this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, @@ -44,6 +42,7 @@ void ESP32Camera::setup() { 1 // core ); } + void ESP32Camera::dump_config() { auto conf = this->config_; ESP_LOGCONFIG(TAG, "ESP32 Camera:"); @@ -106,17 +105,17 @@ void ESP32Camera::dump_config() { ESP_LOGCONFIG(TAG, " Saturation: %d", st.saturation); ESP_LOGCONFIG(TAG, " Vertical Flip: %s", ONOFF(st.vflip)); ESP_LOGCONFIG(TAG, " Horizontal Mirror: %s", ONOFF(st.hmirror)); - // ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect); - // ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode); + ESP_LOGCONFIG(TAG, " Special Effect: %u", st.special_effect); + ESP_LOGCONFIG(TAG, " White Balance Mode: %u", st.wb_mode); // ESP_LOGCONFIG(TAG, " Auto White Balance: %u", st.awb); // ESP_LOGCONFIG(TAG, " Auto White Balance Gain: %u", st.awb_gain); - // ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec); + ESP_LOGCONFIG(TAG, " Auto Exposure Control: %u", st.aec); ESP_LOGCONFIG(TAG, " Auto Exposure Control 2: %u", st.aec2); ESP_LOGCONFIG(TAG, " Auto Exposure Level: %d", st.ae_level); ESP_LOGCONFIG(TAG, " Auto Exposure Value: %u", st.aec_value); - // ESP_LOGCONFIG(TAG, " AGC: %u", st.agc); - // ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain); - // ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling); + ESP_LOGCONFIG(TAG, " AGC: %u", st.agc); + ESP_LOGCONFIG(TAG, " AGC Gain: %u", st.agc_gain); + ESP_LOGCONFIG(TAG, " Gain Ceiling: %u", st.gainceiling); // ESP_LOGCONFIG(TAG, " BPC: %u", st.bpc); // ESP_LOGCONFIG(TAG, " WPC: %u", st.wpc); // ESP_LOGCONFIG(TAG, " RAW_GMA: %u", st.raw_gma); @@ -124,6 +123,7 @@ void ESP32Camera::dump_config() { // ESP_LOGCONFIG(TAG, " DCW: %u", st.dcw); ESP_LOGCONFIG(TAG, " Test Pattern: %s", YESNO(st.colorbar)); } + void ESP32Camera::loop() { // check if we can return the image if (this->can_return_image_()) { @@ -170,15 +170,10 @@ void ESP32Camera::loop() { this->last_update_ = now; this->single_requesters_ = 0; } -void ESP32Camera::framebuffer_task(void *pv) { - while (true) { - camera_fb_t *framebuffer = esp_camera_fb_get(); - xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); - // return is no-op for config with 1 fb - xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); - esp_camera_fb_return(framebuffer); - } -} + +float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } + +/* ---------------- constructors ---------------- */ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { this->config_.pin_pwdn = -1; this->config_.pin_reset = -1; @@ -193,6 +188,9 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) { global_esp32_camera = this; } ESP32Camera::ESP32Camera() : ESP32Camera("") {} + +/* ---------------- setters ---------------- */ +/* set pin assignment */ void ESP32Camera::set_data_pins(std::array pins) { this->config_.pin_d0 = pins[0]; this->config_.pin_d1 = pins[1]; @@ -214,6 +212,10 @@ void ESP32Camera::set_i2c_pins(uint8_t sda, uint8_t scl) { this->config_.pin_sscb_sda = sda; this->config_.pin_sscb_scl = scl; } +void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } +void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } + +/* set image parameters */ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { switch (size) { case ESP32_CAMERA_SIZE_160X120: @@ -249,36 +251,81 @@ void ESP32Camera::set_frame_size(ESP32CameraFrameSize size) { } } void ESP32Camera::set_jpeg_quality(uint8_t quality) { this->config_.jpeg_quality = quality; } -void ESP32Camera::set_reset_pin(uint8_t pin) { this->config_.pin_reset = pin; } -void ESP32Camera::set_power_down_pin(uint8_t pin) { this->config_.pin_pwdn = pin; } -void ESP32Camera::add_image_callback(std::function)> &&f) { - this->new_image_callback_.add(std::move(f)); -} void ESP32Camera::set_vertical_flip(bool vertical_flip) { this->vertical_flip_ = vertical_flip; } void ESP32Camera::set_horizontal_mirror(bool horizontal_mirror) { this->horizontal_mirror_ = horizontal_mirror; } -void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; } -void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; } -void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; } void ESP32Camera::set_contrast(int contrast) { this->contrast_ = contrast; } void ESP32Camera::set_brightness(int brightness) { this->brightness_ = brightness; } void ESP32Camera::set_saturation(int saturation) { this->saturation_ = saturation; } -float ESP32Camera::get_setup_priority() const { return setup_priority::DATA; } -uint32_t ESP32Camera::hash_base() { return 3010542557UL; } -void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= 1 << requester; } -void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= 1 << requester; } -void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1 << requester); } -bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; } -bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; } +void ESP32Camera::set_special_effect(ESP32SpecialEffect effect) { this->special_effect_ = effect; } +/* set exposure parameters */ +void ESP32Camera::set_aec_mode(ESP32GainControlMode mode) { this->aec_mode_ = mode; } +void ESP32Camera::set_aec2(bool aec2) { this->aec2_ = aec2; } +void ESP32Camera::set_ae_level(int ae_level) { this->ae_level_ = ae_level; } +void ESP32Camera::set_aec_value(uint32_t aec_value) { this->aec_value_ = aec_value; } +/* set gains parameters */ +void ESP32Camera::set_agc_mode(ESP32GainControlMode mode) { this->agc_mode_ = mode; } +void ESP32Camera::set_agc_value(uint8_t agc_value) { this->agc_value_ = agc_value; } +void ESP32Camera::set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling) { this->agc_gain_ceiling_ = gain_ceiling; } +/* set white balance */ +void ESP32Camera::set_wb_mode(ESP32WhiteBalanceMode mode) { this->wb_mode_ = mode; } +/* set test mode */ +void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; } +/* set fps */ void ESP32Camera::set_max_update_interval(uint32_t max_update_interval) { this->max_update_interval_ = max_update_interval; } void ESP32Camera::set_idle_update_interval(uint32_t idle_update_interval) { this->idle_update_interval_ = idle_update_interval; } -void ESP32Camera::set_test_pattern(bool test_pattern) { this->test_pattern_ = test_pattern; } + +/* ---------------- public API (specific) ---------------- */ +void ESP32Camera::add_image_callback(std::function)> &&f) { + this->new_image_callback_.add(std::move(f)); +} +void ESP32Camera::start_stream(CameraRequester requester) { this->stream_requesters_ |= (1U << requester); } +void ESP32Camera::stop_stream(CameraRequester requester) { this->stream_requesters_ &= ~(1U << requester); } +void ESP32Camera::request_image(CameraRequester requester) { this->single_requesters_ |= (1U << requester); } +void ESP32Camera::update_camera_parameters() { + sensor_t *s = esp_camera_sensor_get(); + /* update image */ + s->set_vflip(s, this->vertical_flip_); + s->set_hmirror(s, this->horizontal_mirror_); + s->set_contrast(s, this->contrast_); + s->set_brightness(s, this->brightness_); + s->set_saturation(s, this->saturation_); + s->set_special_effect(s, (int) this->special_effect_); // 0 to 6 + /* update exposure */ + s->set_exposure_ctrl(s, (bool) this->aec_mode_); + s->set_aec2(s, this->aec2_); // 0 = disable , 1 = enable + s->set_ae_level(s, this->ae_level_); // -2 to 2 + s->set_aec_value(s, this->aec_value_); // 0 to 1200 + /* update gains */ + s->set_gain_ctrl(s, (bool) this->agc_mode_); + s->set_agc_gain(s, (int) this->agc_value_); // 0 to 30 + s->set_gainceiling(s, (gainceiling_t) this->agc_gain_ceiling_); + /* update white balance mode */ + s->set_wb_mode(s, (int) this->wb_mode_); // 0 to 4 + /* update test patern */ + s->set_colorbar(s, this->test_pattern_); +} + +/* ---------------- Internal methods ---------------- */ +uint32_t ESP32Camera::hash_base() { return 3010542557UL; } +bool ESP32Camera::has_requested_image_() const { return this->single_requesters_ || this->stream_requesters_; } +bool ESP32Camera::can_return_image_() const { return this->current_image_.use_count() == 1; } +void ESP32Camera::framebuffer_task(void *pv) { + while (true) { + camera_fb_t *framebuffer = esp_camera_fb_get(); + xQueueSend(global_esp32_camera->framebuffer_get_queue_, &framebuffer, portMAX_DELAY); + // return is no-op for config with 1 fb + xQueueReceive(global_esp32_camera->framebuffer_return_queue_, &framebuffer, portMAX_DELAY); + esp_camera_fb_return(framebuffer); + } +} ESP32Camera *global_esp32_camera; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +/* ---------------- CameraImageReader class ---------------- */ void CameraImageReader::set_image(std::shared_ptr image) { this->image_ = std::move(image); this->offset_ = 0; @@ -293,13 +340,15 @@ void CameraImageReader::return_image() { this->image_.reset(); } void CameraImageReader::consume_data(size_t consumed) { this->offset_ += consumed; } uint8_t *CameraImageReader::peek_data_buffer() { return this->image_->get_data_buffer() + this->offset_; } +/* ---------------- CameraImage class ---------------- */ +CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {} + camera_fb_t *CameraImage::get_raw_buffer() { return this->buffer_; } uint8_t *CameraImage::get_data_buffer() { return this->buffer_->buf; } size_t CameraImage::get_data_length() { return this->buffer_->len; } bool CameraImage::was_requested_by(CameraRequester requester) const { return (this->requesters_ & (1 << requester)) != 0; } -CameraImage::CameraImage(camera_fb_t *buffer, uint8_t requesters) : buffer_(buffer), requesters_(requesters) {} } // namespace esp32_camera } // namespace esphome diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h index b2670078f383..743b5bde5f04 100644 --- a/esphome/components/esp32_camera/esp32_camera.h +++ b/esphome/components/esp32_camera/esp32_camera.h @@ -14,8 +14,56 @@ namespace esp32_camera { class ESP32Camera; +/* ---------------- enum classes ---------------- */ enum CameraRequester { IDLE, API_REQUESTER, WEB_REQUESTER }; +enum ESP32CameraFrameSize { + ESP32_CAMERA_SIZE_160X120, // QQVGA + ESP32_CAMERA_SIZE_176X144, // QCIF + ESP32_CAMERA_SIZE_240X176, // HQVGA + ESP32_CAMERA_SIZE_320X240, // QVGA + ESP32_CAMERA_SIZE_400X296, // CIF + ESP32_CAMERA_SIZE_640X480, // VGA + ESP32_CAMERA_SIZE_800X600, // SVGA + ESP32_CAMERA_SIZE_1024X768, // XGA + ESP32_CAMERA_SIZE_1280X1024, // SXGA + ESP32_CAMERA_SIZE_1600X1200, // UXGA +}; + +enum ESP32AgcGainCeiling { + ESP32_GAINCEILING_2X = GAINCEILING_2X, + ESP32_GAINCEILING_4X = GAINCEILING_4X, + ESP32_GAINCEILING_8X = GAINCEILING_8X, + ESP32_GAINCEILING_16X = GAINCEILING_16X, + ESP32_GAINCEILING_32X = GAINCEILING_32X, + ESP32_GAINCEILING_64X = GAINCEILING_64X, + ESP32_GAINCEILING_128X = GAINCEILING_128X, +}; + +enum ESP32GainControlMode { + ESP32_GC_MODE_MANU = false, + ESP32_GC_MODE_AUTO = true, +}; + +enum ESP32WhiteBalanceMode { + ESP32_WB_MODE_AUTO = 0U, + ESP32_WB_MODE_SUNNY = 1U, + ESP32_WB_MODE_CLOUDY = 2U, + ESP32_WB_MODE_OFFICE = 3U, + ESP32_WB_MODE_HOME = 4U, +}; + +enum ESP32SpecialEffect { + ESP32_SPECIAL_EFFECT_NONE = 0U, + ESP32_SPECIAL_EFFECT_NEGATIVE = 1U, + ESP32_SPECIAL_EFFECT_GRAYSCALE = 2U, + ESP32_SPECIAL_EFFECT_RED_TINT = 3U, + ESP32_SPECIAL_EFFECT_GREEN_TINT = 4U, + ESP32_SPECIAL_EFFECT_BLUE_TINT = 5U, + ESP32_SPECIAL_EFFECT_SEPIA = 6U, +}; + +/* ---------------- CameraImage class ---------------- */ class CameraImage { public: CameraImage(camera_fb_t *buffer, uint8_t requester); @@ -29,6 +77,7 @@ class CameraImage { uint8_t requesters_; }; +/* ---------------- CameraImageReader class ---------------- */ class CameraImageReader { public: void set_image(std::shared_ptr image); @@ -42,70 +91,94 @@ class CameraImageReader { size_t offset_{0}; }; -enum ESP32CameraFrameSize { - ESP32_CAMERA_SIZE_160X120, // QQVGA - ESP32_CAMERA_SIZE_176X144, // QCIF - ESP32_CAMERA_SIZE_240X176, // HQVGA - ESP32_CAMERA_SIZE_320X240, // QVGA - ESP32_CAMERA_SIZE_400X296, // CIF - ESP32_CAMERA_SIZE_640X480, // VGA - ESP32_CAMERA_SIZE_800X600, // SVGA - ESP32_CAMERA_SIZE_1024X768, // XGA - ESP32_CAMERA_SIZE_1280X1024, // SXGA - ESP32_CAMERA_SIZE_1600X1200, // UXGA -}; - +/* ---------------- ESP32Camera class ---------------- */ class ESP32Camera : public Component, public EntityBase { public: ESP32Camera(const std::string &name); ESP32Camera(); + + /* setters */ + /* -- pin assignment */ void set_data_pins(std::array pins); void set_vsync_pin(uint8_t pin); void set_href_pin(uint8_t pin); void set_pixel_clock_pin(uint8_t pin); void set_external_clock(uint8_t pin, uint32_t frequency); void set_i2c_pins(uint8_t sda, uint8_t scl); - void set_frame_size(ESP32CameraFrameSize size); - void set_jpeg_quality(uint8_t quality); void set_reset_pin(uint8_t pin); void set_power_down_pin(uint8_t pin); + /* -- image */ + void set_frame_size(ESP32CameraFrameSize size); + void set_jpeg_quality(uint8_t quality); void set_vertical_flip(bool vertical_flip); void set_horizontal_mirror(bool horizontal_mirror); - void set_aec2(bool aec2); - void set_ae_level(int ae_level); - void set_aec_value(uint32_t aec_value); void set_contrast(int contrast); void set_brightness(int brightness); void set_saturation(int saturation); + void set_special_effect(ESP32SpecialEffect effect); + /* -- exposure */ + void set_aec_mode(ESP32GainControlMode mode); + void set_aec2(bool aec2); + void set_ae_level(int ae_level); + void set_aec_value(uint32_t aec_value); + /* -- gains */ + void set_agc_mode(ESP32GainControlMode mode); + void set_agc_value(uint8_t agc_value); + void set_agc_gain_ceiling(ESP32AgcGainCeiling gain_ceiling); + /* -- white balance */ + void set_wb_mode(ESP32WhiteBalanceMode mode); + /* -- test */ + void set_test_pattern(bool test_pattern); + /* -- framerates */ void set_max_update_interval(uint32_t max_update_interval); void set_idle_update_interval(uint32_t idle_update_interval); - void set_test_pattern(bool test_pattern); + + /* public API (derivated) */ void setup() override; void loop() override; void dump_config() override; - void add_image_callback(std::function)> &&f); float get_setup_priority() const override; + /* public API (specific) */ + void add_image_callback(std::function)> &&f); void start_stream(CameraRequester requester); void stop_stream(CameraRequester requester); void request_image(CameraRequester requester); + void update_camera_parameters(); protected: + /* internal methods */ uint32_t hash_base() override; bool has_requested_image_() const; bool can_return_image_() const; static void framebuffer_task(void *pv); + /* attributes */ + /* camera configuration */ camera_config_t config_{}; + /* -- image */ bool vertical_flip_{true}; bool horizontal_mirror_{true}; - bool aec2_{false}; - int ae_level_{0}; - uint32_t aec_value_{300}; int contrast_{0}; int brightness_{0}; int saturation_{0}; + ESP32SpecialEffect special_effect_{ESP32_SPECIAL_EFFECT_NONE}; + /* -- exposure */ + ESP32GainControlMode aec_mode_{ESP32_GC_MODE_AUTO}; + bool aec2_{false}; + int ae_level_{0}; + uint32_t aec_value_{300}; + /* -- gains */ + ESP32GainControlMode agc_mode_{ESP32_GC_MODE_AUTO}; + uint8_t agc_value_{0}; + ESP32AgcGainCeiling agc_gain_ceiling_{ESP32_GAINCEILING_2X}; + /* -- white balance */ + ESP32WhiteBalanceMode wb_mode_{ESP32_WB_MODE_AUTO}; + /* -- Test */ bool test_pattern_{false}; + /* -- framerates */ + uint32_t max_update_interval_{1000}; + uint32_t idle_update_interval_{15000}; esp_err_t init_error_{ESP_OK}; std::shared_ptr current_image_; @@ -114,8 +187,7 @@ class ESP32Camera : public Component, public EntityBase { QueueHandle_t framebuffer_get_queue_; QueueHandle_t framebuffer_return_queue_; CallbackManager)> new_image_callback_; - uint32_t max_update_interval_{1000}; - uint32_t idle_update_interval_{15000}; + uint32_t last_idle_request_{0}; uint32_t last_update_{0}; }; diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp index 39b110bc856f..3210989ff56b 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.cpp +++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp @@ -87,10 +87,11 @@ void CameraWebServer::on_shutdown() { void CameraWebServer::dump_config() { ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:"); ESP_LOGCONFIG(TAG, " Port: %d", this->port_); - if (this->mode_ == STREAM) + if (this->mode_ == STREAM) { ESP_LOGCONFIG(TAG, " Mode: stream"); - else + } else { ESP_LOGCONFIG(TAG, " Mode: snapshot"); + } if (this->is_failed()) { ESP_LOGE(TAG, " Setup Failed"); diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h index df30a43ed2c1..509ca8159218 100644 --- a/esphome/components/esp32_camera_web_server/camera_web_server.h +++ b/esphome/components/esp32_camera_web_server/camera_web_server.h @@ -36,7 +36,6 @@ class CameraWebServer : public Component { esp_err_t streaming_handler_(struct httpd_req *req); esp_err_t snapshot_handler_(struct httpd_req *req); - protected: uint16_t port_{0}; void *httpd_{nullptr}; SemaphoreHandle_t semaphore_; diff --git a/esphome/components/esp32_improv/__init__.py b/esphome/components/esp32_improv/__init__.py index 80c53f7c2a0f..9f8438f78576 100644 --- a/esphome/components/esp32_improv/__init__.py +++ b/esphome/components/esp32_improv/__init__.py @@ -56,7 +56,7 @@ async def to_code(config): cg.add(ble_server.register_service_component(var)) cg.add_define("USE_IMPROV") - cg.add_library("esphome/Improv", "1.0.0") + cg.add_library("esphome/Improv", "1.2.1") cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION])) cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION])) diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 788e7a94606c..956934abc177 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -128,7 +128,7 @@ void ESP32ImprovComponent::loop() { std::vector urls = {ESPHOME_MY_LINK}; #ifdef USE_WEBSERVER auto ip = wifi::global_wifi_component->wifi_sta_ip(); - std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); urls.push_back(webserver_url); #endif std::vector data = improv::build_rpc_response(improv::WIFI_SETTINGS, urls); diff --git a/esphome/components/esp32_touch/esp32_touch.cpp b/esphome/components/esp32_touch/esp32_touch.cpp index b225ae1a8a86..0e3d3d9fd543 100644 --- a/esphome/components/esp32_touch/esp32_touch.cpp +++ b/esphome/components/esp32_touch/esp32_touch.cpp @@ -162,7 +162,7 @@ void ESP32TouchComponent::on_shutdown() { } ESP32TouchBinarySensor::ESP32TouchBinarySensor(touch_pad_t touch_pad, uint16_t threshold, uint16_t wakeup_threshold) - : BinarySensor(), touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} + : touch_pad_(touch_pad), threshold_(threshold), wakeup_threshold_(wakeup_threshold) {} } // namespace esp32_touch } // namespace esphome diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py index 718204277005..3c83400c1d5f 100644 --- a/esphome/components/esp8266/__init__.py +++ b/esphome/components/esp8266/__init__.py @@ -61,7 +61,7 @@ def _format_framework_arduino_version(ver: cv.Version) -> str: # The default/recommended arduino framework version # - https://github.com/esp8266/Arduino/releases # - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduinoespressif8266 -RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(2, 7, 4) +RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 0, 2) # The platformio/espressif8266 version to use for arduino 2 framework versions # - https://github.com/platformio/platform-espressif8266/releases # - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif8266 @@ -166,6 +166,7 @@ async def to_code(config): cg.add_platformio_option("framework", "arduino") cg.add_build_flag("-DUSE_ARDUINO") cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO") + cg.add_build_flag("-Wno-nonnull-compare") cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION]) cg.add_platformio_option( "platform_packages", @@ -198,10 +199,15 @@ async def to_code(config): cg.add_platformio_option("board_build.flash_mode", config[CONF_BOARD_FLASH_MODE]) + ver: cv.Version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] + cg.add_define( + "USE_ARDUINO_VERSION_CODE", + cg.RawExpression(f"VERSION_CODE({ver.major}, {ver.minor}, {ver.patch})"), + ) + if config[CONF_BOARD] in ESP8266_FLASH_SIZES: flash_size = ESP8266_FLASH_SIZES[config[CONF_BOARD]] ld_scripts = ESP8266_LD_SCRIPTS[flash_size] - ver = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] if ver <= cv.Version(2, 3, 0): # No ld script support diff --git a/esphome/components/esp8266/boards.py b/esphome/components/esp8266/boards.py index 410e934615ad..8b0a23a00f79 100644 --- a/esphome/components/esp8266/boards.py +++ b/esphome/components/esp8266/boards.py @@ -1,4 +1,4 @@ -FLASH_SIZE_1_MB = 2 ** 20 +FLASH_SIZE_1_MB = 2**20 FLASH_SIZE_512_KB = FLASH_SIZE_1_MB // 2 FLASH_SIZE_2_MB = 2 * FLASH_SIZE_1_MB FLASH_SIZE_4_MB = 4 * FLASH_SIZE_1_MB diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index a8f8bd0d4150..0e42cea576a2 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -98,16 +98,18 @@ static bool load_from_flash(size_t offset, uint32_t *data, size_t len) { } static bool save_to_rtc(size_t offset, const uint32_t *data, size_t len) { - for (uint32_t i = 0; i < len; i++) + for (uint32_t i = 0; i < len; i++) { if (!esp_rtc_user_mem_write(offset + i, data[i])) return false; + } return true; } static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { - for (uint32_t i = 0; i < len; i++) + for (uint32_t i = 0; i < len; i++) { if (!esp_rtc_user_mem_read(offset + i, &data[i])) return false; + } return true; } diff --git a/esphome/components/esp8266_pwm/esp8266_pwm.cpp b/esphome/components/esp8266_pwm/esp8266_pwm.cpp index e472edf2a7ac..8b3d8613b07d 100644 --- a/esphome/components/esp8266_pwm/esp8266_pwm.cpp +++ b/esphome/components/esp8266_pwm/esp8266_pwm.cpp @@ -2,13 +2,10 @@ #include "esp8266_pwm.h" #include "esphome/core/macros.h" +#include "esphome/core/defines.h" #include "esphome/core/log.h" #include "esphome/core/helpers.h" -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) -#error ESP8266 PWM requires at least arduino_version 2.4.0 -#endif - #include namespace esphome { diff --git a/esphome/components/esp8266_pwm/output.py b/esphome/components/esp8266_pwm/output.py index 3d52e5af16a3..7feee79ff25b 100644 --- a/esphome/components/esp8266_pwm/output.py +++ b/esphome/components/esp8266_pwm/output.py @@ -23,15 +23,20 @@ def valid_pwm_pin(value): SetFrequencyAction = esp8266_pwm_ns.class_("SetFrequencyAction", automation.Action) validate_frequency = cv.All(cv.frequency, cv.Range(min=1.0e-6)) -CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( - { - cv.Required(CONF_ID): cv.declare_id(ESP8266PWM), - cv.Required(CONF_PIN): cv.All( - pins.internal_gpio_output_pin_schema, valid_pwm_pin - ), - cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = cv.All( + output.FLOAT_OUTPUT_SCHEMA.extend( + { + cv.Required(CONF_ID): cv.declare_id(ESP8266PWM), + cv.Required(CONF_PIN): cv.All( + pins.internal_gpio_output_pin_schema, valid_pwm_pin + ), + cv.Optional(CONF_FREQUENCY, default="1kHz"): validate_frequency, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 4, 0), + ), +) async def to_code(config): diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp index 2ee5782ff6af..12f88a0f66d2 100644 --- a/esphome/components/ezo/ezo.cpp +++ b/esphome/components/ezo/ezo.cpp @@ -75,9 +75,10 @@ void EZOSensor::loop() { return; // some sensors return multiple comma-separated values, terminate string after first one - for (size_t i = 1; i < sizeof(buf) - 1; i++) + for (size_t i = 1; i < sizeof(buf) - 1; i++) { if (buf[i] == ',') buf[i] = '\0'; + } float val = parse_number((char *) &buf[1]).value_or(0); this->publish_state(val); diff --git a/esphome/components/fan/__init__.py b/esphome/components/fan/__init__.py index 52bec3b5b666..eb67bbcbd76c 100644 --- a/esphome/components/fan/__init__.py +++ b/esphome/components/fan/__init__.py @@ -19,6 +19,7 @@ CONF_ON_TURN_ON, CONF_TRIGGER_ID, CONF_DIRECTION, + CONF_RESTORE_MODE, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity @@ -26,13 +27,24 @@ IS_PLATFORM_COMPONENT = True fan_ns = cg.esphome_ns.namespace("fan") -FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component) -MakeFan = cg.Application.struct("MakeFan") +Fan = fan_ns.class_("Fan", cg.EntityBase) +FanState = fan_ns.class_("Fan", Fan, cg.Component) -FanDirection = fan_ns.enum("FanDirection") +FanDirection = fan_ns.enum("FanDirection", is_class=True) FAN_DIRECTION_ENUM = { - "FORWARD": FanDirection.FAN_DIRECTION_FORWARD, - "REVERSE": FanDirection.FAN_DIRECTION_REVERSE, + "FORWARD": FanDirection.FORWARD, + "REVERSE": FanDirection.REVERSE, +} + +FanRestoreMode = fan_ns.enum("FanRestoreMode", is_class=True) +RESTORE_MODES = { + "NO_RESTORE": FanRestoreMode.NO_RESTORE, + "ALWAYS_OFF": FanRestoreMode.ALWAYS_OFF, + "ALWAYS_ON": FanRestoreMode.ALWAYS_ON, + "RESTORE_DEFAULT_OFF": FanRestoreMode.RESTORE_DEFAULT_OFF, + "RESTORE_DEFAULT_ON": FanRestoreMode.RESTORE_DEFAULT_ON, + "RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.RESTORE_INVERTED_DEFAULT_OFF, + "RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.RESTORE_INVERTED_DEFAULT_ON, } # Actions @@ -50,7 +62,10 @@ FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( { - cv.GenerateID(): cv.declare_id(FanState), + cv.GenerateID(): cv.declare_id(Fan), + cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( + RESTORE_MODES, upper=True, space="_" + ), cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), cv.Optional(CONF_OSCILLATION_STATE_TOPIC): cv.All( cv.requires_component("mqtt"), cv.publish_topic @@ -92,6 +107,8 @@ async def setup_fan_core_(var, config): await setup_entity(var, config) + cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE])) + if CONF_MQTT_ID in config: mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) @@ -142,19 +159,19 @@ async def register_fan(var, config): if not CORE.has_id(config[CONF_ID]): var = cg.Pvariable(config[CONF_ID], var) cg.add(cg.App.register_fan(var)) - await cg.register_component(var, config) await setup_fan_core_(var, config) async def create_fan_state(config): var = cg.new_Pvariable(config[CONF_ID]) await register_fan(var, config) + await cg.register_component(var, config) return var FAN_ACTION_SCHEMA = maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), } ) @@ -176,7 +193,7 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args): TurnOnAction, maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean), cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)), cv.Optional(CONF_DIRECTION): cv.templatable( @@ -211,7 +228,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args): FanIsOnCondition, automation.maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), } ), ) @@ -220,7 +237,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args): FanIsOffCondition, automation.maybe_simple_id( { - cv.Required(CONF_ID): cv.use_id(FanState), + cv.Required(CONF_ID): cv.use_id(Fan), } ), ) diff --git a/esphome/components/fan/automation.cpp b/esphome/components/fan/automation.cpp deleted file mode 100644 index 79e583fc573b..000000000000 --- a/esphome/components/fan/automation.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "automation.h" -#include "esphome/core/log.h" - -namespace esphome { -namespace fan { - -static const char *const TAG = "fan.automation"; - -} // namespace fan -} // namespace esphome diff --git a/esphome/components/fan/automation.h b/esphome/components/fan/automation.h index 608f772b75a6..23fb70a95b1c 100644 --- a/esphome/components/fan/automation.h +++ b/esphome/components/fan/automation.h @@ -9,7 +9,7 @@ namespace fan { template class TurnOnAction : public Action { public: - explicit TurnOnAction(FanState *state) : state_(state) {} + explicit TurnOnAction(Fan *state) : state_(state) {} TEMPLATABLE_VALUE(bool, oscillating) TEMPLATABLE_VALUE(int, speed) @@ -29,30 +29,30 @@ template class TurnOnAction : public Action { call.perform(); } - FanState *state_; + Fan *state_; }; template class TurnOffAction : public Action { public: - explicit TurnOffAction(FanState *state) : state_(state) {} + explicit TurnOffAction(Fan *state) : state_(state) {} void play(Ts... x) override { this->state_->turn_off().perform(); } - FanState *state_; + Fan *state_; }; template class ToggleAction : public Action { public: - explicit ToggleAction(FanState *state) : state_(state) {} + explicit ToggleAction(Fan *state) : state_(state) {} void play(Ts... x) override { this->state_->toggle().perform(); } - FanState *state_; + Fan *state_; }; template class CycleSpeedAction : public Action { public: - explicit CycleSpeedAction(FanState *state) : state_(state) {} + explicit CycleSpeedAction(Fan *state) : state_(state) {} void play(Ts... x) override { // check to see if fan supports speeds and is on @@ -83,29 +83,29 @@ template class CycleSpeedAction : public Action { } } - FanState *state_; + Fan *state_; }; template class FanIsOnCondition : public Condition { public: - explicit FanIsOnCondition(FanState *state) : state_(state) {} + explicit FanIsOnCondition(Fan *state) : state_(state) {} bool check(Ts... x) override { return this->state_->state; } protected: - FanState *state_; + Fan *state_; }; template class FanIsOffCondition : public Condition { public: - explicit FanIsOffCondition(FanState *state) : state_(state) {} + explicit FanIsOffCondition(Fan *state) : state_(state) {} bool check(Ts... x) override { return !this->state_->state; } protected: - FanState *state_; + Fan *state_; }; class FanTurnOnTrigger : public Trigger<> { public: - FanTurnOnTrigger(FanState *state) { + FanTurnOnTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto is_on = state->state; auto should_trigger = is_on && !this->last_on_; @@ -123,7 +123,7 @@ class FanTurnOnTrigger : public Trigger<> { class FanTurnOffTrigger : public Trigger<> { public: - FanTurnOffTrigger(FanState *state) { + FanTurnOffTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto is_on = state->state; auto should_trigger = !is_on && this->last_on_; @@ -141,7 +141,7 @@ class FanTurnOffTrigger : public Trigger<> { class FanSpeedSetTrigger : public Trigger<> { public: - FanSpeedSetTrigger(FanState *state) { + FanSpeedSetTrigger(Fan *state) { state->add_on_state_callback([this, state]() { auto speed = state->speed; auto should_trigger = speed != !this->last_speed_; diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp new file mode 100644 index 000000000000..5f9660f6d6fa --- /dev/null +++ b/esphome/components/fan/fan.cpp @@ -0,0 +1,175 @@ +#include "fan.h" +#include "fan_helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace fan { + +static const char *const TAG = "fan"; + +const LogString *fan_direction_to_string(FanDirection direction) { + switch (direction) { + case FanDirection::FORWARD: + return LOG_STR("FORWARD"); + case FanDirection::REVERSE: + return LOG_STR("REVERSE"); + default: + return LOG_STR("UNKNOWN"); + } +} + +void FanCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str()); + this->validate_(); + if (this->binary_state_.has_value()) + ESP_LOGD(TAG, " State: %s", ONOFF(*this->binary_state_)); + if (this->oscillating_.has_value()) + ESP_LOGD(TAG, " Oscillating: %s", YESNO(*this->oscillating_)); + if (this->speed_.has_value()) + ESP_LOGD(TAG, " Speed: %d", *this->speed_); + if (this->direction_.has_value()) + ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); + + this->parent_.control(*this); +} +void FanCall::validate_() { + auto traits = this->parent_.get_traits(); + + if (this->speed_.has_value()) + this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count()); + + if (this->binary_state_.has_value() && *this->binary_state_) { + // when turning on, if current speed is zero, set speed to 100% + if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0) { + this->speed_ = traits.supported_speed_count(); + } + } + + if (this->oscillating_.has_value() && !traits.supports_oscillation()) { + ESP_LOGW(TAG, "'%s' - This fan does not support oscillation!", this->parent_.get_name().c_str()); + this->oscillating_.reset(); + } + + if (this->speed_.has_value() && !traits.supports_speed()) { + ESP_LOGW(TAG, "'%s' - This fan does not support speeds!", this->parent_.get_name().c_str()); + this->speed_.reset(); + } + + if (this->direction_.has_value() && !traits.supports_direction()) { + ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str()); + this->direction_.reset(); + } +} + +// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +FanCall &FanCall::set_speed(const char *legacy_speed) { + const auto supported_speed_count = this->parent_.get_traits().supported_speed_count(); + if (strcasecmp(legacy_speed, "low") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); + } else if (strcasecmp(legacy_speed, "medium") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); + } else if (strcasecmp(legacy_speed, "high") == 0) { + this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); + } + return *this; +} +#pragma GCC diagnostic pop + +FanCall FanRestoreState::to_call(Fan &fan) { + auto call = fan.make_call(); + call.set_state(this->state); + call.set_oscillating(this->oscillating); + call.set_speed(this->speed); + call.set_direction(this->direction); + return call; +} +void FanRestoreState::apply(Fan &fan) { + fan.state = this->state; + fan.oscillating = this->oscillating; + fan.speed = this->speed; + fan.direction = this->direction; + fan.publish_state(); +} + +Fan::Fan() : EntityBase("") {} +Fan::Fan(const std::string &name) : EntityBase(name) {} + +FanCall Fan::turn_on() { return this->make_call().set_state(true); } +FanCall Fan::turn_off() { return this->make_call().set_state(false); } +FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } +FanCall Fan::make_call() { return FanCall(*this); } + +void Fan::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +void Fan::publish_state() { + auto traits = this->get_traits(); + + ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); + ESP_LOGD(TAG, " State: %s", ONOFF(this->state)); + if (traits.supports_speed()) + ESP_LOGD(TAG, " Speed: %d", this->speed); + if (traits.supports_oscillation()) + ESP_LOGD(TAG, " Oscillating: %s", YESNO(this->oscillating)); + if (traits.supports_direction()) + ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); + + this->state_callback_.call(); + this->save_state_(); +} + +// Random 32-bit value, change this every time the layout of the FanRestoreState struct changes. +constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA; +optional Fan::restore_state_() { + FanRestoreState recovered{}; + this->rtc_ = global_preferences->make_preference(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); + bool restored = this->rtc_.load(&recovered); + + switch (this->restore_mode_) { + case FanRestoreMode::NO_RESTORE: + return {}; + case FanRestoreMode::ALWAYS_OFF: + recovered.state = false; + return recovered; + case FanRestoreMode::ALWAYS_ON: + recovered.state = true; + return recovered; + case FanRestoreMode::RESTORE_DEFAULT_OFF: + recovered.state = restored ? recovered.state : false; + return recovered; + case FanRestoreMode::RESTORE_DEFAULT_ON: + recovered.state = restored ? recovered.state : true; + return recovered; + case FanRestoreMode::RESTORE_INVERTED_DEFAULT_OFF: + recovered.state = restored ? !recovered.state : false; + return recovered; + case FanRestoreMode::RESTORE_INVERTED_DEFAULT_ON: + recovered.state = restored ? !recovered.state : true; + return recovered; + } + + return {}; +} +void Fan::save_state_() { + FanRestoreState state{}; + state.state = this->state; + state.oscillating = this->oscillating; + state.speed = this->speed; + state.direction = this->direction; + this->rtc_.save(&state); +} + +void Fan::dump_traits_(const char *tag, const char *prefix) { + if (this->get_traits().supports_speed()) { + ESP_LOGCONFIG(tag, "%s Speed: YES", prefix); + ESP_LOGCONFIG(tag, "%s Speed count: %d", prefix, this->get_traits().supported_speed_count()); + } + if (this->get_traits().supports_oscillation()) + ESP_LOGCONFIG(tag, "%s Oscillation: YES", prefix); + if (this->get_traits().supports_direction()) + ESP_LOGCONFIG(tag, "%s Direction: YES", prefix); +} +uint32_t Fan::hash_base() { return 418001110UL; } + +} // namespace fan +} // namespace esphome diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h new file mode 100644 index 000000000000..cafb5843d16b --- /dev/null +++ b/esphome/components/fan/fan.h @@ -0,0 +1,154 @@ +#pragma once + +#include "esphome/core/entity_base.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include "esphome/core/optional.h" +#include "esphome/core/preferences.h" +#include "fan_traits.h" + +namespace esphome { +namespace fan { + +#define LOG_FAN(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + (obj)->dump_traits_(TAG, prefix); \ + } + +/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon +enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { + FAN_SPEED_LOW = 0, ///< The fan is running on low speed. + FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. + FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. +}; + +/// Simple enum to represent the direction of a fan. +enum class FanDirection { FORWARD = 0, REVERSE = 1 }; + +/// Restore mode of a fan. +enum class FanRestoreMode { + NO_RESTORE, + ALWAYS_OFF, + ALWAYS_ON, + RESTORE_DEFAULT_OFF, + RESTORE_DEFAULT_ON, + RESTORE_INVERTED_DEFAULT_OFF, + RESTORE_INVERTED_DEFAULT_ON, +}; + +const LogString *fan_direction_to_string(FanDirection direction); + +class Fan; + +class FanCall { + public: + explicit FanCall(Fan &parent) : parent_(parent) {} + + FanCall &set_state(bool binary_state) { + this->binary_state_ = binary_state; + return *this; + } + FanCall &set_state(optional binary_state) { + this->binary_state_ = binary_state; + return *this; + } + optional get_state() const { return this->binary_state_; } + FanCall &set_oscillating(bool oscillating) { + this->oscillating_ = oscillating; + return *this; + } + FanCall &set_oscillating(optional oscillating) { + this->oscillating_ = oscillating; + return *this; + } + optional get_oscillating() const { return this->oscillating_; } + FanCall &set_speed(int speed) { + this->speed_ = speed; + return *this; + } + ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") + FanCall &set_speed(const char *legacy_speed); + optional get_speed() const { return this->speed_; } + FanCall &set_direction(FanDirection direction) { + this->direction_ = direction; + return *this; + } + FanCall &set_direction(optional direction) { + this->direction_ = direction; + return *this; + } + optional get_direction() const { return this->direction_; } + + void perform(); + + protected: + void validate_(); + + Fan &parent_; + optional binary_state_; + optional oscillating_; + optional speed_; + optional direction_{}; +}; + +struct FanRestoreState { + bool state; + int speed; + bool oscillating; + FanDirection direction; + + /// Convert this struct to a fan call that can be performed. + FanCall to_call(Fan &fan); + /// Apply these settings to the fan. + void apply(Fan &fan); +} __attribute__((packed)); + +class Fan : public EntityBase { + public: + Fan(); + /// Construct the fan with name. + explicit Fan(const std::string &name); + + /// The current on/off state of the fan. + bool state{false}; + /// The current oscillation state of the fan. + bool oscillating{false}; + /// The current fan speed level + int speed{0}; + /// The current direction of the fan + FanDirection direction{FanDirection::FORWARD}; + + FanCall turn_on(); + FanCall turn_off(); + FanCall toggle(); + FanCall make_call(); + + /// Register a callback that will be called each time the state changes. + void add_on_state_callback(std::function &&callback); + + void publish_state(); + + virtual FanTraits get_traits() = 0; + + /// Set the restore mode of this fan. + void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } + + protected: + friend FanCall; + + virtual void control(const FanCall &call) = 0; + + optional restore_state_(); + void save_state_(); + + void dump_traits_(const char *tag, const char *prefix); + uint32_t hash_base() override; + + CallbackManager state_callback_{}; + ESPPreferenceObject rtc_; + FanRestoreMode restore_mode_; +}; + +} // namespace fan +} // namespace esphome diff --git a/esphome/components/fan/fan_helpers.h b/esphome/components/fan/fan_helpers.h index 009505601ed2..8e8e3859bd67 100644 --- a/esphome/components/fan/fan_helpers.h +++ b/esphome/components/fan/fan_helpers.h @@ -1,5 +1,6 @@ #pragma once -#include "fan_state.h" + +#include "fan.h" namespace esphome { namespace fan { diff --git a/esphome/components/fan/fan_state.cpp b/esphome/components/fan/fan_state.cpp index 6ff4d3a83367..7c1658fb2e2c 100644 --- a/esphome/components/fan/fan_state.cpp +++ b/esphome/components/fan/fan_state.cpp @@ -1,85 +1,16 @@ #include "fan_state.h" -#include "fan_helpers.h" -#include "esphome/core/log.h" namespace esphome { namespace fan { static const char *const TAG = "fan"; -const FanTraits &FanState::get_traits() const { return this->traits_; } -void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; } -void FanState::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} -FanState::FanState(const std::string &name) : EntityBase(name) {} - -FanStateCall FanState::turn_on() { return this->make_call().set_state(true); } -FanStateCall FanState::turn_off() { return this->make_call().set_state(false); } -FanStateCall FanState::toggle() { return this->make_call().set_state(!this->state); } -FanStateCall FanState::make_call() { return FanStateCall(this); } - -struct FanStateRTCState { - bool state; - int speed; - bool oscillating; - FanDirection direction; -}; - void FanState::setup() { - this->rtc_ = global_preferences->make_preference(this->get_object_id_hash()); - FanStateRTCState recovered{}; - if (!this->rtc_.load(&recovered)) - return; - - auto call = this->make_call(); - call.set_state(recovered.state); - call.set_speed(recovered.speed); - call.set_oscillating(recovered.oscillating); - call.set_direction(recovered.direction); - call.perform(); + auto restore = this->restore_state_(); + if (restore) + restore->to_call(*this).perform(); } float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; } -uint32_t FanState::hash_base() { return 418001110UL; } - -void FanStateCall::perform() const { - if (this->binary_state_.has_value()) { - this->state_->state = *this->binary_state_; - } - if (this->oscillating_.has_value()) { - this->state_->oscillating = *this->oscillating_; - } - if (this->direction_.has_value()) { - this->state_->direction = *this->direction_; - } - if (this->speed_.has_value()) { - const int speed_count = this->state_->get_traits().supported_speed_count(); - this->state_->speed = clamp(*this->speed_, 1, speed_count); - } - - FanStateRTCState saved{}; - saved.state = this->state_->state; - saved.speed = this->state_->speed; - saved.oscillating = this->state_->oscillating; - saved.direction = this->state_->direction; - this->state_->rtc_.save(&saved); - - this->state_->state_callback_.call(); -} - -// This whole method is deprecated, don't warn about usage of deprecated methods inside of it. -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" -FanStateCall &FanStateCall::set_speed(const char *legacy_speed) { - const auto supported_speed_count = this->state_->get_traits().supported_speed_count(); - if (strcasecmp(legacy_speed, "low") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "medium") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); - } else if (strcasecmp(legacy_speed, "high") == 0) { - this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); - } - return *this; -} } // namespace fan } // namespace esphome diff --git a/esphome/components/fan/fan_state.h b/esphome/components/fan/fan_state.h index c5a6f59ac4f1..044ee5973606 100644 --- a/esphome/components/fan/fan_state.h +++ b/esphome/components/fan/fan_state.h @@ -1,111 +1,34 @@ #pragma once #include "esphome/core/component.h" -#include "esphome/core/entity_base.h" -#include "esphome/core/helpers.h" -#include "esphome/core/preferences.h" -#include "esphome/core/log.h" -#include "fan_traits.h" +#include "fan.h" namespace esphome { namespace fan { -/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon -enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { - FAN_SPEED_LOW = 0, ///< The fan is running on low speed. - FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed. - FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. +enum ESPDEPRECATED("LegacyFanDirection members are deprecated, use FanDirection instead.", + "2022.2") LegacyFanDirection { + FAN_DIRECTION_FORWARD = 0, + FAN_DIRECTION_REVERSE = 1 }; -/// Simple enum to represent the direction of a fan -enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 }; - -class FanState; - -class FanStateCall { - public: - explicit FanStateCall(FanState *state) : state_(state) {} - - FanStateCall &set_state(bool binary_state) { - this->binary_state_ = binary_state; - return *this; - } - FanStateCall &set_state(optional binary_state) { - this->binary_state_ = binary_state; - return *this; - } - FanStateCall &set_oscillating(bool oscillating) { - this->oscillating_ = oscillating; - return *this; - } - FanStateCall &set_oscillating(optional oscillating) { - this->oscillating_ = oscillating; - return *this; - } - FanStateCall &set_speed(int speed) { - this->speed_ = speed; - return *this; - } - ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") - FanStateCall &set_speed(const char *legacy_speed); - FanStateCall &set_direction(FanDirection direction) { - this->direction_ = direction; - return *this; - } - FanStateCall &set_direction(optional direction) { - this->direction_ = direction; - return *this; - } - - void perform() const; - - protected: - FanState *const state_; - optional binary_state_; - optional oscillating_; - optional speed_; - optional direction_{}; -}; - -class FanState : public EntityBase, public Component { +class ESPDEPRECATED("FanState is deprecated, use Fan instead.", "2022.2") FanState : public Fan, public Component { public: FanState() = default; - /// Construct the fan state with name. - explicit FanState(const std::string &name); - - /// Register a callback that will be called each time the state changes. - void add_on_state_callback(std::function &&callback); + explicit FanState(const std::string &name) : Fan(name) {} - /// Get the traits of this fan (i.e. what features it supports). - const FanTraits &get_traits() const; + /// Get the traits of this fan. + FanTraits get_traits() override { return this->traits_; } /// Set the traits of this fan (i.e. what features it supports). - void set_traits(const FanTraits &traits); - - /// The current ON/OFF state of the fan. - bool state{false}; - /// The current oscillation state of the fan. - bool oscillating{false}; - /// The current fan speed level - int speed{}; - /// The current direction of the fan - FanDirection direction{FAN_DIRECTION_FORWARD}; - - FanStateCall turn_on(); - FanStateCall turn_off(); - FanStateCall toggle(); - FanStateCall make_call(); + void set_traits(const FanTraits &traits) { this->traits_ = traits; } void setup() override; float get_setup_priority() const override; protected: - friend FanStateCall; - - uint32_t hash_base() override; + void control(const FanCall &call) override { this->publish_state(); } FanTraits traits_{}; - CallbackManager state_callback_{}; - ESPPreferenceObject rtc_; }; } // namespace fan diff --git a/esphome/components/fingerprint_grow/__init__.py b/esphome/components/fingerprint_grow/__init__.py index 757a633e097d..ecbbc3d477d5 100644 --- a/esphome/components/fingerprint_grow/__init__.py +++ b/esphome/components/fingerprint_grow/__init__.py @@ -80,6 +80,10 @@ "RED": AuraLEDColor.RED, "BLUE": AuraLEDColor.BLUE, "PURPLE": AuraLEDColor.PURPLE, + "GREEN": AuraLEDColor.GREEN, + "YELLOW": AuraLEDColor.YELLOW, + "CYAN": AuraLEDColor.CYAN, + "WHITE": AuraLEDColor.WHITE, } validate_aura_led_colors = cv.enum(AURA_LED_COLORS, upper=True) diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.cpp b/esphome/components/fingerprint_grow/fingerprint_grow.cpp index be17e29de301..3b8c52fea22c 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.cpp +++ b/esphome/components/fingerprint_grow/fingerprint_grow.cpp @@ -278,10 +278,11 @@ void FingerprintGrowComponent::delete_all_fingerprints() { void FingerprintGrowComponent::led_control(bool state) { ESP_LOGD(TAG, "Setting LED"); - if (state) + if (state) { this->data_ = {LED_ON}; - else + } else { this->data_ = {LED_OFF}; + } switch (this->send_command_()) { case OK: ESP_LOGD(TAG, "LED set"); diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index e7d734777a86..7ec253ff3a96 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -76,6 +76,10 @@ enum GrowAuraLEDColor { RED = 0x01, BLUE = 0x02, PURPLE = 0x03, + GREEN = 0x04, + YELLOW = 0x05, + CYAN = 0x06, + WHITE = 0x07, }; class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevice { diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp index 9e58f672c752..291af8c8cd6d 100644 --- a/esphome/components/fujitsu_general/fujitsu_general.cpp +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -207,7 +207,7 @@ void FujitsuGeneralClimate::transmit_(uint8_t const *message, uint8_t length) { ESP_LOGV(TAG, "Transmit message length %d", length); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY); diff --git a/esphome/components/gpio/switch/gpio_switch.cpp b/esphome/components/gpio/switch/gpio_switch.cpp index 56e0087eae6b..714f2ea6d8fa 100644 --- a/esphome/components/gpio/switch/gpio_switch.cpp +++ b/esphome/components/gpio/switch/gpio_switch.cpp @@ -33,16 +33,18 @@ void GPIOSwitch::setup() { } // write state before setup - if (initial_state) + if (initial_state) { this->turn_on(); - else + } else { this->turn_off(); + } this->pin_->setup(); // write after setup again for other IOs - if (initial_state) + if (initial_state) { this->turn_on(); - else + } else { this->turn_off(); + } } void GPIOSwitch::dump_config() { LOG_SWITCH("", "GPIO Switch", this); diff --git a/esphome/components/graph/graph.h b/esphome/components/graph/graph.h index f935917c5711..15d2d1c7c455 100644 --- a/esphome/components/graph/graph.h +++ b/esphome/components/graph/graph.h @@ -115,7 +115,7 @@ class GraphTrace { void set_line_type(enum LineType val) { this->line_type_ = val; } Color get_line_color() { return this->line_color_; } void set_line_color(Color val) { this->line_color_ = val; } - const std::string get_name() { return name_; } + std::string get_name() { return name_; } const HistoryData *get_tracedata() { return &data_; } protected: diff --git a/esphome/components/hbridge/fan/__init__.py b/esphome/components/hbridge/fan/__init__.py index b169978acd3f..421883a1ffff 100644 --- a/esphome/components/hbridge/fan/__init__.py +++ b/esphome/components/hbridge/fan/__init__.py @@ -17,7 +17,7 @@ CODEOWNERS = ["@WeekendWarrior"] -HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState) +HBridgeFan = hbridge_ns.class_("HBridgeFan", cg.Component, fan.Fan) DecayMode = hbridge_ns.enum("DecayMode") DECAY_MODE_OPTIONS = { @@ -59,6 +59,7 @@ async def to_code(config): config[CONF_SPEED_COUNT], config[CONF_DECAY_MODE], ) + await cg.register_component(var, config) await fan.register_fan(var, config) pin_a_ = await cg.get_variable(config[CONF_PIN_A]) cg.add(var.set_pin_a(pin_a_)) diff --git a/esphome/components/hbridge/fan/hbridge_fan.cpp b/esphome/components/hbridge/fan/hbridge_fan.cpp index a4e5429ff4b2..52d2b3d8b71e 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.cpp +++ b/esphome/components/hbridge/fan/hbridge_fan.cpp @@ -22,47 +22,49 @@ void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable) ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable); } -fan::FanStateCall HBridgeFan::brake() { +fan::FanCall HBridgeFan::brake() { ESP_LOGD(TAG, "Braking"); (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f) : this->set_hbridge_levels_(1.0f, 1.0f, 1.0f); return this->make_call().set_state(false); } -void HBridgeFan::dump_config() { - ESP_LOGCONFIG(TAG, "Fan '%s':", this->get_name().c_str()); - if (this->get_traits().supports_oscillation()) { - ESP_LOGCONFIG(TAG, " Oscillation: YES"); - } - if (this->get_traits().supports_direction()) { - ESP_LOGCONFIG(TAG, " Direction: YES"); +void HBridgeFan::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + this->write_state_(); } +} +void HBridgeFan::dump_config() { + LOG_FAN("", "H-Bridge Fan", this); if (this->decay_mode_ == DECAY_MODE_SLOW) { ESP_LOGCONFIG(TAG, " Decay Mode: Slow"); } else { ESP_LOGCONFIG(TAG, " Decay Mode: Fast"); } } -void HBridgeFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); - this->set_traits(traits); - this->add_on_state_callback([this]() { this->next_update_ = true; }); +fan::FanTraits HBridgeFan::get_traits() { + return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); } -void HBridgeFan::loop() { - if (!this->next_update_) { - return; - } - this->next_update_ = false; +void HBridgeFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_speed().has_value()) + this->speed = *call.get_speed(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); - float speed = 0.0f; - if (this->state) { - speed = static_cast(this->speed) / static_cast(this->speed_count_); - } + this->write_state_(); + this->publish_state(); +} +void HBridgeFan::write_state_() { + float speed = this->state ? static_cast(this->speed) / static_cast(this->speed_count_) : 0.0f; if (speed == 0.0f) { // off means idle (this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, speed) : this->set_hbridge_levels_(speed, speed, speed); - return; - } - if (this->direction == fan::FAN_DIRECTION_FORWARD) { + } else if (this->direction == fan::FanDirection::FORWARD) { if (this->decay_mode_ == DECAY_MODE_SLOW) { (this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f - speed, 1.0f) : this->set_hbridge_levels_(1.0f - speed, 1.0f, 1.0f); @@ -79,6 +81,9 @@ void HBridgeFan::loop() { : this->set_hbridge_levels_(1.0f, 0.0f, speed); } } + + if (this->oscillating_ != nullptr) + this->oscillating_->set_state(this->oscillating); } } // namespace hbridge diff --git a/esphome/components/hbridge/fan/hbridge_fan.h b/esphome/components/hbridge/fan/hbridge_fan.h index 984318c8d67c..4389b97ccb5f 100644 --- a/esphome/components/hbridge/fan/hbridge_fan.h +++ b/esphome/components/hbridge/fan/hbridge_fan.h @@ -3,7 +3,7 @@ #include "esphome/core/automation.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace hbridge { @@ -13,7 +13,7 @@ enum DecayMode { DECAY_MODE_FAST = 1, }; -class HBridgeFan : public fan::FanState { +class HBridgeFan : public Component, public fan::Fan { public: HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {} @@ -22,25 +22,22 @@ class HBridgeFan : public fan::FanState { void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; } void setup() override; - void loop() override; void dump_config() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + fan::FanTraits get_traits() override; - fan::FanStateCall brake(); - - int get_speed_count() { return this->speed_count_; } - // update Hbridge without a triggered FanState change, eg. for acceleration/deceleration ramping - void internal_update() { this->next_update_ = true; } + fan::FanCall brake(); protected: output::FloatOutput *pin_a_; output::FloatOutput *pin_b_; output::FloatOutput *enable_{nullptr}; output::BinaryOutput *oscillating_{nullptr}; - bool next_update_{true}; int speed_count_{}; DecayMode decay_mode_{DECAY_MODE_SLOW}; + void control(const fan::FanCall &call) override; + void write_state_(); + void set_hbridge_levels_(float a_level, float b_level); void set_hbridge_levels_(float a_level, float b_level, float enable); }; diff --git a/esphome/components/heatpumpir/ir_sender_esphome.cpp b/esphome/components/heatpumpir/ir_sender_esphome.cpp index 24c793356388..173d5951190a 100644 --- a/esphome/components/heatpumpir/ir_sender_esphome.cpp +++ b/esphome/components/heatpumpir/ir_sender_esphome.cpp @@ -6,20 +6,20 @@ namespace esphome { namespace heatpumpir { void IRSenderESPHome::setFrequency(int frequency) { // NOLINT(readability-identifier-naming) - auto data = transmit_.get_data(); + auto *data = transmit_.get_data(); data->set_carrier_frequency(1000 * frequency); } // Send an IR 'mark' symbol, i.e. transmitter ON void IRSenderESPHome::mark(int mark_length) { - auto data = transmit_.get_data(); + auto *data = transmit_.get_data(); data->mark(mark_length); } // Send an IR 'space' symbol, i.e. transmitter OFF void IRSenderESPHome::space(int space_length) { if (space_length) { - auto data = transmit_.get_data(); + auto *data = transmit_.get_data(); data->space(space_length); } else { transmit_.perform(); diff --git a/esphome/components/hitachi_ac344/hitachi_ac344.cpp b/esphome/components/hitachi_ac344/hitachi_ac344.cpp index 7702baf3123a..7b93b00503ab 100644 --- a/esphome/components/hitachi_ac344/hitachi_ac344.cpp +++ b/esphome/components/hitachi_ac344/hitachi_ac344.cpp @@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con void set_bit(uint8_t *const data, const uint8_t position, const bool on) { uint8_t mask = 1 << position; - if (on) + if (on) { *data |= mask; - else + } else { *data &= ~mask; + } } uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) { @@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) { temp = std::min(celsius, HITACHI_AC344_TEMP_MAX); temp = std::max(temp, HITACHI_AC344_TEMP_MIN); set_bits(&remote_state_[HITACHI_AC344_TEMP_BYTE], HITACHI_AC344_TEMP_OFFSET, HITACHI_AC344_TEMP_SIZE, temp); - if (previous_temp_ > temp) + if (previous_temp_ > temp) { set_button_(HITACHI_AC344_BUTTON_TEMP_DOWN); - else if (previous_temp_ < temp) + } else if (previous_temp_ < temp) { set_button_(HITACHI_AC344_BUTTON_TEMP_UP); + } if (set_previous) previous_temp_ = temp; } @@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) { void HitachiClimate::set_swing_v_toggle_(bool on) { uint8_t button = get_button_(); // Get the current button value. - if (on) - button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV. - else if (button == HITACHI_AC344_BUTTON_SWINGV) // Asked to unset it + if (on) { + button = HITACHI_AC344_BUTTON_SWINGV; // Set the button to SwingV. + } else if (button == HITACHI_AC344_BUTTON_SWINGV) { // Asked to unset it // It was set previous, so use Power as a default button = HITACHI_AC344_BUTTON_POWER; + } set_button_(button); } @@ -211,7 +214,7 @@ void HitachiClimate::transmit_state() { invert_byte_pairs(remote_state_ + 3, HITACHI_AC344_STATE_LENGTH - 3); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(HITACHI_AC344_FREQ); uint8_t repeat = 0; @@ -320,9 +323,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t pos = 0; pos < HITACHI_AC344_STATE_LENGTH; pos++) { // Read bit for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE)) + if (data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ONE_SPACE)) { recv_state[pos] |= 1 << bit; - else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) { + } else if (!data.expect_item(HITACHI_AC344_BIT_MARK, HITACHI_AC344_ZERO_SPACE)) { ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit); return false; } diff --git a/esphome/components/hitachi_ac424/hitachi_ac424.cpp b/esphome/components/hitachi_ac424/hitachi_ac424.cpp index 713bc0be25a4..65cfaa417501 100644 --- a/esphome/components/hitachi_ac424/hitachi_ac424.cpp +++ b/esphome/components/hitachi_ac424/hitachi_ac424.cpp @@ -19,10 +19,11 @@ void set_bits(uint8_t *const dst, const uint8_t offset, const uint8_t nbits, con void set_bit(uint8_t *const data, const uint8_t position, const bool on) { uint8_t mask = 1 << position; - if (on) + if (on) { *data |= mask; - else + } else { *data &= ~mask; + } } uint8_t *invert_byte_pairs(uint8_t *ptr, const uint16_t length) { @@ -69,10 +70,11 @@ void HitachiClimate::set_temp_(uint8_t celsius, bool set_previous) { temp = std::min(celsius, HITACHI_AC424_TEMP_MAX); temp = std::max(temp, HITACHI_AC424_TEMP_MIN); set_bits(&remote_state_[HITACHI_AC424_TEMP_BYTE], HITACHI_AC424_TEMP_OFFSET, HITACHI_AC424_TEMP_SIZE, temp); - if (previous_temp_ > temp) + if (previous_temp_ > temp) { set_button_(HITACHI_AC424_BUTTON_TEMP_DOWN); - else if (previous_temp_ < temp) + } else if (previous_temp_ < temp) { set_button_(HITACHI_AC424_BUTTON_TEMP_UP); + } if (set_previous) previous_temp_ = temp; } @@ -110,11 +112,12 @@ void HitachiClimate::set_fan_(uint8_t speed) { void HitachiClimate::set_swing_v_toggle_(bool on) { uint8_t button = get_button_(); // Get the current button value. - if (on) - button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV. - else if (button == HITACHI_AC424_BUTTON_SWINGV) // Asked to unset it + if (on) { + button = HITACHI_AC424_BUTTON_SWINGV; // Set the button to SwingV. + } else if (button == HITACHI_AC424_BUTTON_SWINGV) { // Asked to unset it // It was set previous, so use Power as a default button = HITACHI_AC424_BUTTON_POWER; + } set_button_(button); } @@ -212,7 +215,7 @@ void HitachiClimate::transmit_state() { invert_byte_pairs(remote_state_ + 3, HITACHI_AC424_STATE_LENGTH - 3); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(HITACHI_AC424_FREQ); uint8_t repeat = 0; @@ -321,9 +324,9 @@ bool HitachiClimate::on_receive(remote_base::RemoteReceiveData data) { for (uint8_t pos = 0; pos < HITACHI_AC424_STATE_LENGTH; pos++) { // Read bit for (int8_t bit = 0; bit < 8; bit++) { - if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) + if (data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ONE_SPACE)) { recv_state[pos] |= 1 << bit; - else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) { + } else if (!data.expect_item(HITACHI_AC424_BIT_MARK, HITACHI_AC424_ZERO_SPACE)) { ESP_LOGVV(TAG, "Byte %d bit %d fail", pos, bit); return false; } diff --git a/esphome/components/hm3301/hm3301.h b/esphome/components/hm3301/hm3301.h index e13ffa466e5e..bccdd1d35bc0 100644 --- a/esphome/components/hm3301/hm3301.h +++ b/esphome/components/hm3301/hm3301.h @@ -44,8 +44,8 @@ class HM3301Component : public PollingComponent, public i2c::I2CDevice { AQICalculatorType aqi_calc_type_; AQICalculatorFactory aqi_calculator_factory_ = AQICalculatorFactory(); - bool validate_checksum_(const uint8_t *); - uint16_t get_sensor_value_(const uint8_t *, uint8_t); + bool validate_checksum_(const uint8_t *data); + uint16_t get_sensor_value_(const uint8_t *data, uint8_t i); }; } // namespace hm3301 diff --git a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp index cea02f072a3d..a36fcb204a39 100644 --- a/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp +++ b/esphome/components/homeassistant/binary_sensor/homeassistant_binary_sensor.cpp @@ -25,10 +25,11 @@ void HomeassistantBinarySensor::setup() { } else { ESP_LOGD(TAG, "'%s': Got state %s", this->entity_id_.c_str(), ONOFF(new_state)); } - if (this->initial_) + if (this->initial_) { this->publish_initial_state(new_state); - else + } else { this->publish_state(new_state); + } break; } this->initial_ = false; diff --git a/esphome/components/homeassistant/text_sensor/__init__.py b/esphome/components/homeassistant/text_sensor/__init__.py index b63d45b9ce80..be59bab676a8 100644 --- a/esphome/components/homeassistant/text_sensor/__init__.py +++ b/esphome/components/homeassistant/text_sensor/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_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"] @@ -10,7 +11,7 @@ "HomeassistantTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( +CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend( { cv.GenerateID(): cv.declare_id(HomeassistantTextSensor), cv.Required(CONF_ENTITY_ID): cv.entity_id, @@ -20,9 +21,8 @@ async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) cg.add(var.set_entity_id(config[CONF_ENTITY_ID])) if CONF_ATTRIBUTE in config: diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index e044e5feced9..c8c0ca536974 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -31,6 +31,8 @@ CONF_JSON = "json" CONF_VERIFY_SSL = "verify_ssl" CONF_ON_RESPONSE = "on_response" +CONF_FOLLOW_REDIRECTS = "follow_redirects" +CONF_REDIRECT_LIMIT = "redirect_limit" def validate_url(value): @@ -71,6 +73,8 @@ def validate_secure_url(config): { cv.GenerateID(): cv.declare_id(HttpRequestComponent), cv.Optional(CONF_USERAGENT, "ESPHome"): cv.string, + cv.Optional(CONF_FOLLOW_REDIRECTS, True): cv.boolean, + cv.Optional(CONF_REDIRECT_LIMIT, 3): cv.int_, cv.Optional( CONF_TIMEOUT, default="5s" ): cv.positive_time_period_milliseconds, @@ -90,6 +94,9 @@ async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) cg.add(var.set_timeout(config[CONF_TIMEOUT])) cg.add(var.set_useragent(config[CONF_USERAGENT])) + cg.add(var.set_follow_redirects(config[CONF_FOLLOW_REDIRECTS])) + cg.add(var.set_redirect_limit(config[CONF_REDIRECT_LIMIT])) + if CORE.is_esp8266 and not config[CONF_ESP8266_DISABLE_SSL_SUPPORT]: cg.add_define("USE_HTTP_REQUEST_ESP8266_HTTPS") diff --git a/esphome/components/http_request/http_request.cpp b/esphome/components/http_request/http_request.cpp index a80d09583504..64f3f97de9e0 100644 --- a/esphome/components/http_request/http_request.cpp +++ b/esphome/components/http_request/http_request.cpp @@ -1,7 +1,7 @@ #ifdef USE_ARDUINO #include "http_request.h" -#include "esphome/core/macros.h" +#include "esphome/core/defines.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" @@ -14,6 +14,8 @@ void HttpRequestComponent::dump_config() { ESP_LOGCONFIG(TAG, "HTTP Request:"); ESP_LOGCONFIG(TAG, " Timeout: %ums", this->timeout_); ESP_LOGCONFIG(TAG, " User-Agent: %s", this->useragent_); + ESP_LOGCONFIG(TAG, " Follow Redirects: %d", this->follow_redirects_); + ESP_LOGCONFIG(TAG, " Redirect limit: %d", this->redirect_limit_); } void HttpRequestComponent::set_url(std::string url) { @@ -38,18 +40,21 @@ void HttpRequestComponent::send(const std::vector bool begin_status = false; const String url = this->url_.c_str(); -#ifdef USE_ESP32 - begin_status = this->client_.begin(url); -#endif -#ifdef USE_ESP8266 -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) - this->client_.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - this->client_.setFollowRedirects(true); +#if defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0)) +#if defined(USE_ESP32) || USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) + if (this->follow_redirects_) { + this->client_.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); + } else { + this->client_.setFollowRedirects(HTTPC_DISABLE_FOLLOW_REDIRECTS); + } +#else + this->client_.setFollowRedirects(this->follow_redirects_); #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) - this->client_.setRedirectLimit(3); + this->client_.setRedirectLimit(this->redirect_limit_); #endif +#if defined(USE_ESP32) + begin_status = this->client_.begin(url); +#elif defined(USE_ESP8266) begin_status = this->client_.begin(*this->get_wifi_client_(), url); #endif diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index a38bdf9c9585..4590163f2cde 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -40,6 +40,8 @@ class HttpRequestComponent : public Component { void set_method(const char *method) { this->method_ = method; } void set_useragent(const char *useragent) { this->useragent_ = useragent; } void set_timeout(uint16_t timeout) { this->timeout_ = timeout; } + void set_follow_redirects(bool follow_redirects) { this->follow_redirects_ = follow_redirects; } + void set_redirect_limit(uint16_t limit) { this->redirect_limit_ = limit; } void set_body(const std::string &body) { this->body_ = body; } void set_headers(std::list
headers) { this->headers_ = std::move(headers); } void send(const std::vector &response_triggers); @@ -53,6 +55,8 @@ class HttpRequestComponent : public Component { const char *method_; const char *useragent_{nullptr}; bool secure_; + bool follow_redirects_; + uint16_t redirect_limit_; uint16_t timeout_{5000}; std::string body_; std::list
headers_; diff --git a/esphome/components/i2c/i2c_bus_arduino.cpp b/esphome/components/i2c/i2c_bus_arduino.cpp index b6059286920b..693b869bf7fd 100644 --- a/esphome/components/i2c/i2c_bus_arduino.cpp +++ b/esphome/components/i2c/i2c_bus_arduino.cpp @@ -16,10 +16,11 @@ void ArduinoI2CBus::setup() { #ifdef USE_ESP32 static uint8_t next_bus_num = 0; - if (next_bus_num == 0) + if (next_bus_num == 0) { wire_ = &Wire; - else + } else { wire_ = new TwoWire(next_bus_num); // NOLINT(cppcoreguidelines-owning-memory) + } next_bus_num++; #else wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer) @@ -55,10 +56,11 @@ void ArduinoI2CBus::dump_config() { ESP_LOGI(TAG, "Found no i2c devices!"); } else { for (const auto &s : scan_results_) { - if (s.second) + if (s.second) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); - else + } else { ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } diff --git a/esphome/components/i2c/i2c_bus_esp_idf.cpp b/esphome/components/i2c/i2c_bus_esp_idf.cpp index 109c3f890d8d..606583fd7c3d 100644 --- a/esphome/components/i2c/i2c_bus_esp_idf.cpp +++ b/esphome/components/i2c/i2c_bus_esp_idf.cpp @@ -65,10 +65,11 @@ void IDFI2CBus::dump_config() { ESP_LOGI(TAG, "Found no i2c devices!"); } else { for (const auto &s : scan_results_) { - if (s.second) + if (s.second) { ESP_LOGI(TAG, "Found i2c device at address 0x%02X", s.first); - else + } else { ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first); + } } } } diff --git a/esphome/components/image/__init__.py b/esphome/components/image/__init__.py index a721263dff6a..70d77dfd1427 100644 --- a/esphome/components/image/__init__.py +++ b/esphome/components/image/__init__.py @@ -24,6 +24,7 @@ "BINARY": ImageType.IMAGE_TYPE_BINARY, "GRAYSCALE": ImageType.IMAGE_TYPE_GRAYSCALE, "RGB24": ImageType.IMAGE_TYPE_RGB24, + "TRANSPARENT_BINARY": ImageType.IMAGE_TYPE_TRANSPARENT_BINARY, } Image_ = display.display_ns.class_("Image") @@ -99,6 +100,17 @@ async def to_code(config): pos = x + y * width8 data[pos // 8] |= 0x80 >> (pos % 8) + elif config[CONF_TYPE] == "TRANSPARENT_BINARY": + image = image.convert("RGBA") + width8 = ((width + 7) // 8) * 8 + data = [0 for _ in range(height * width8 // 8)] + for y in range(height): + for x in range(width): + if not image.getpixel((x, y))[3]: + continue + pos = x + y * width8 + data[pos // 8] |= 0x80 >> (pos % 8) + rhs = [HexInt(x) for x in data] prog_arr = cg.progmem_array(config[CONF_RAW_DATA_ID], rhs) cg.new_Pvariable( diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py index ed7c382a2fb2..21073a8ab3bb 100644 --- a/esphome/components/improv_serial/__init__.py +++ b/esphome/components/improv_serial/__init__.py @@ -30,4 +30,4 @@ def validate_logger_baud_rate(config): async def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) await cg.register_component(var, config) - cg.add_library("esphome/Improv", "1.0.0") + cg.add_library("esphome/Improv", "1.2.1") diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp index b4d1d88370ab..0dab71060c4b 100644 --- a/esphome/components/improv_serial/improv_serial_component.cpp +++ b/esphome/components/improv_serial/improv_serial_component.cpp @@ -24,6 +24,8 @@ void ImprovSerialComponent::setup() { if (wifi::global_wifi_component->has_sta()) { this->state_ = improv::STATE_PROVISIONED; + } else { + wifi::global_wifi_component->start_scanning(); } } @@ -95,7 +97,7 @@ std::vector ImprovSerialComponent::build_rpc_settings_response_(improv: std::vector urls; #ifdef USE_WEBSERVER auto ip = wifi::global_wifi_component->wifi_sta_ip(); - std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT); + std::string webserver_url = "http://" + ip.str() + ":" + to_string(USE_WEBSERVER_PORT); urls.push_back(webserver_url); #endif std::vector data = improv::build_rpc_response(command, urls, false); @@ -111,58 +113,15 @@ std::vector ImprovSerialComponent::build_version_info_() { bool ImprovSerialComponent::parse_improv_serial_byte_(uint8_t byte) { size_t at = this->rx_buffer_.size(); this->rx_buffer_.push_back(byte); - ESP_LOGD(TAG, "Improv Serial byte: 0x%02X", byte); + ESP_LOGV(TAG, "Improv Serial byte: 0x%02X", byte); const uint8_t *raw = &this->rx_buffer_[0]; - if (at == 0) - return byte == 'I'; - if (at == 1) - return byte == 'M'; - if (at == 2) - return byte == 'P'; - if (at == 3) - return byte == 'R'; - if (at == 4) - return byte == 'O'; - if (at == 5) - return byte == 'V'; - - if (at == 6) - return byte == IMPROV_SERIAL_VERSION; - - if (at == 7) - return true; - uint8_t type = raw[7]; - - if (at == 8) - return true; - uint8_t data_len = raw[8]; - - if (at < 8 + data_len) - return true; - - if (at == 8 + data_len) - return true; - - if (at == 8 + data_len + 1) { - uint8_t checksum = 0x00; - for (size_t i = 0; i < at; i++) - checksum += raw[i]; - - if (checksum != byte) { - ESP_LOGW(TAG, "Error decoding Improv payload"); - this->set_error_(improv::ERROR_INVALID_RPC); - return false; - } - - if (type == TYPE_RPC) { - this->set_error_(improv::ERROR_NONE); - auto command = improv::parse_improv_data(&raw[9], data_len, false); - return this->parse_improv_payload_(command); - } - } - // If we got here then the command coming is is improv, but not an RPC command - return false; + return improv::parse_improv_serial_byte( + at, byte, raw, [this](improv::ImprovCommand command) -> bool { return this->parse_improv_payload_(command); }, + [this](improv::Error error) -> void { + ESP_LOGW(TAG, "Error decoding Improv payload"); + this->set_error_(error); + }); } bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) { @@ -195,6 +154,27 @@ bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command this->send_response_(info); return true; } + case improv::GET_WIFI_NETWORKS: { + std::vector networks; + auto results = wifi::global_wifi_component->get_scan_result(); + for (auto &scan : results) { + if (scan.get_is_hidden()) + continue; + const std::string &ssid = scan.get_ssid(); + if (std::find(networks.begin(), networks.end(), ssid) != networks.end()) + continue; + // Send each ssid separately to avoid overflowing the buffer + std::vector data = improv::build_rpc_response( + improv::GET_WIFI_NETWORKS, {ssid, str_sprintf("%d", scan.get_rssi()), YESNO(scan.get_with_auth())}, false); + this->send_response_(data); + networks.push_back(ssid); + } + // Send empty response to signify the end of the list. + std::vector data = + improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector{}, false); + this->send_response_(data); + return true; + } default: { ESP_LOGW(TAG, "Unknown Improv payload"); this->set_error_(improv::ERROR_UNKNOWN_RPC); diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h index 304afdaf75f0..c6b980ab99f9 100644 --- a/esphome/components/improv_serial/improv_serial_component.h +++ b/esphome/components/improv_serial/improv_serial_component.h @@ -32,7 +32,7 @@ class ImprovSerialComponent : public Component { void loop() override; void dump_config() override; - float get_setup_priority() const override { return setup_priority::HARDWARE; } + float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } protected: bool parse_improv_serial_byte_(uint8_t byte); diff --git a/esphome/components/inkplate6/display.py b/esphome/components/inkplate6/display.py index dca764c6eda6..a17f37c92006 100644 --- a/esphome/components/inkplate6/display.py +++ b/esphome/components/inkplate6/display.py @@ -47,6 +47,7 @@ MODELS = { "inkplate_6": InkplateModel.INKPLATE_6, "inkplate_10": InkplateModel.INKPLATE_10, + "inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS, } CONFIG_SCHEMA = cv.All( diff --git a/esphome/components/inkplate6/inkplate.cpp b/esphome/components/inkplate6/inkplate.cpp index e62e594a49fc..e6fb9b773cc1 100644 --- a/esphome/components/inkplate6/inkplate.cpp +++ b/esphome/components/inkplate6/inkplate.cpp @@ -13,6 +13,11 @@ namespace inkplate6 { static const char *const TAG = "inkplate"; void Inkplate6::setup() { + for (uint32_t i = 0; i < 256; i++) { + this->pin_lut_[i] = ((i & 0b00000011) << 4) | (((i & 0b00001100) >> 2) << 18) | (((i & 0b00010000) >> 4) << 23) | + (((i & 0b11100000) >> 5) << 25); + } + this->initialize_(); this->vcom_pin_->setup(); @@ -38,11 +43,21 @@ void Inkplate6::setup() { this->display_data_6_pin_->setup(); this->display_data_7_pin_->setup(); - this->clean(); - this->display(); + this->wakeup_pin_->digital_write(true); + delay(1); + this->write_bytes(0x09, { + 0b00011011, // Power up seq. + 0b00000000, // Power up delay (3mS per rail) + 0b00011011, // Power down seq. + 0b00000000, // Power down delay (6mS per rail) + }); + delay(1); + this->wakeup_pin_->digital_write(false); } + void Inkplate6::initialize_() { ExternalRAMAllocator allocator(ExternalRAMAllocator::ALLOW_FAILURE); + ExternalRAMAllocator allocator32(ExternalRAMAllocator::ALLOW_FAILURE); uint32_t buffer_size = this->get_buffer_length_(); if (buffer_size == 0) return; @@ -53,6 +68,10 @@ void Inkplate6::initialize_() { allocator.deallocate(this->partial_buffer_2_, buffer_size * 2); if (this->buffer_ != nullptr) allocator.deallocate(this->buffer_, buffer_size); + if (this->glut_ != nullptr) + allocator32.deallocate(this->glut_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8)); + if (this->glut2_ != nullptr) + allocator32.deallocate(this->glut2_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8)); this->buffer_ = allocator.allocate(buffer_size); if (this->buffer_ == nullptr) { @@ -60,7 +79,34 @@ void Inkplate6::initialize_() { this->mark_failed(); return; } - if (!this->greyscale_) { + if (this->greyscale_) { + uint8_t glut_size = (this->model_ == INKPLATE_6_PLUS ? 9 : 8); + + this->glut_ = allocator32.allocate(256 * glut_size); + if (this->glut_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate glut!"); + this->mark_failed(); + return; + } + this->glut2_ = allocator32.allocate(256 * glut_size); + if (this->glut2_ == nullptr) { + ESP_LOGE(TAG, "Could not allocate glut2!"); + this->mark_failed(); + return; + } + + for (int i = 0; i < glut_size; i++) { + for (uint32_t j = 0; j < 256; j++) { + uint8_t z = (waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i]); + this->glut_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) | + (((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25); + z = ((waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i])) << 4; + this->glut2_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) | + (((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25); + } + } + + } else { this->partial_buffer_ = allocator.allocate(buffer_size); if (this->partial_buffer_ == nullptr) { ESP_LOGE(TAG, "Could not allocate partial buffer for display!"); @@ -73,13 +119,16 @@ void Inkplate6::initialize_() { this->mark_failed(); return; } + memset(this->partial_buffer_, 0, buffer_size); memset(this->partial_buffer_2_, 0, buffer_size * 2); } memset(this->buffer_, 0, buffer_size); } + float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; } + size_t Inkplate6::get_buffer_length_() { if (this->greyscale_) { return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u; @@ -87,6 +136,7 @@ size_t Inkplate6::get_buffer_length_() { return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u; } } + void Inkplate6::update() { this->do_update_(); @@ -96,6 +146,7 @@ void Inkplate6::update() { this->display(); } + void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) { if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0) return; @@ -121,6 +172,7 @@ void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) { this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]); } } + void Inkplate6::dump_config() { LOG_DISPLAY("", "Inkplate", this); ESP_LOGCONFIG(TAG, " Greyscale: %s", YESNO(this->greyscale_)); @@ -150,44 +202,51 @@ void Inkplate6::dump_config() { LOG_UPDATE_INTERVAL(this); } + void Inkplate6::eink_off_() { ESP_LOGV(TAG, "Eink off called"); - if (panel_on_ == 0) + if (!panel_on_) return; - panel_on_ = 0; - this->gmod_pin_->digital_write(false); - this->oe_pin_->digital_write(false); + panel_on_ = false; - GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin())); + this->oe_pin_->digital_write(false); + this->gmod_pin_->digital_write(false); + GPIO.out &= ~(this->get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin())); + this->ckv_pin_->digital_write(false); this->sph_pin_->digital_write(false); this->spv_pin_->digital_write(false); - this->powerup_pin_->digital_write(false); - this->wakeup_pin_->digital_write(false); this->vcom_pin_->digital_write(false); + this->write_byte(0x01, 0x6F); // Put TPS65186 into standby mode + + delay(100); // NOLINT + + this->write_byte(0x01, 0x4f); // Disable 3V3 to the panel + + if (this->model_ != INKPLATE_6_PLUS) + this->wakeup_pin_->digital_write(false); + pins_z_state_(); } + void Inkplate6::eink_on_() { ESP_LOGV(TAG, "Eink on called"); - if (panel_on_ == 1) + if (panel_on_) return; - panel_on_ = 1; - pins_as_outputs_(); + this->panel_on_ = true; + + this->pins_as_outputs_(); this->wakeup_pin_->digital_write(true); - this->powerup_pin_->digital_write(true); this->vcom_pin_->digital_write(true); + delay(2); - this->write_byte(0x01, 0x3F); - - delay(40); - - this->write_byte(0x0D, 0x80); + this->write_byte(0x01, 0b00101111); // Enable all rails - delay(2); + delay(1); - this->read_register(0x00, nullptr, 0); + this->write_byte(0x01, 0b10101111); // Switch TPS65186 into active mode this->le_pin_->digital_write(false); this->oe_pin_->digital_write(false); @@ -196,8 +255,33 @@ void Inkplate6::eink_on_() { this->gmod_pin_->digital_write(true); this->spv_pin_->digital_write(true); this->ckv_pin_->digital_write(false); + this->oe_pin_->digital_write(false); + + uint32_t timer = millis(); + do { + delay(1); + } while (!this->read_power_status_() && ((millis() - timer) < 250)); + if ((millis() - timer) >= 250) { + ESP_LOGW(TAG, "Power supply not detected"); + this->wakeup_pin_->digital_write(false); + this->vcom_pin_->digital_write(false); + this->powerup_pin_->digital_write(false); + this->panel_on_ = false; + return; + } + this->oe_pin_->digital_write(true); } + +bool Inkplate6::read_power_status_() { + uint8_t data; + auto err = this->read_register(0x0F, &data, 1); + if (err == i2c::ERROR_OK) { + return data == 0b11111010; + } + return false; +} + void Inkplate6::fill(Color color) { ESP_LOGV(TAG, "Fill called"); uint32_t start_time = millis(); @@ -212,6 +296,7 @@ void Inkplate6::fill(Color color) { ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time); } + void Inkplate6::display() { ESP_LOGV(TAG, "Display called"); uint32_t start_time = millis(); @@ -227,201 +312,185 @@ void Inkplate6::display() { } ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time); } + void Inkplate6::display1b_() { ESP_LOGV(TAG, "Display1b called"); uint32_t start_time = millis(); memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_()); - uint32_t send; uint8_t data; uint8_t buffer_value; const uint8_t *buffer_ptr; eink_on_(); - clean_fast_(0, 1); - clean_fast_(1, 5); - clean_fast_(2, 1); - clean_fast_(0, 5); - clean_fast_(2, 1); - clean_fast_(1, 12); - clean_fast_(2, 1); - clean_fast_(0, 11); + if (this->model_ == INKPLATE_6_PLUS) { + clean_fast_(0, 1); + clean_fast_(1, 15); + clean_fast_(2, 1); + clean_fast_(0, 5); + clean_fast_(2, 1); + clean_fast_(1, 15); + } else { + clean_fast_(0, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + clean_fast_(2, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + } uint32_t clock = (1 << this->cl_pin_->get_pin()); + uint32_t data_mask = this->get_data_pin_mask_(); ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time); - for (int k = 0; k < 3; k++) { + + for (int k = 0; k < 4; k++) { buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); - for (int i = 0; i < this->get_height_internal(); i++) { + for (int i = 0, im = this->get_height_internal(); i < im; i++) { buffer_value = *(buffer_ptr--); - data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); - data = LUTB[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F]; + hscan_start_(this->pin_lut_[data]); + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); - data = LUTB[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - data = LUTB[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; + data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); } - ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 3, millis() - start_time); + ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 4, millis() - start_time); buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; vscan_start_(); - for (int i = 0; i < this->get_height_internal(); i++) { + for (int i = 0, im = this->get_height_internal(); i < im; i++) { buffer_value = *(buffer_ptr--); - data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); - data = LUT2[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F]; + hscan_start_(this->pin_lut_[data] | clock); + data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; + for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { buffer_value = *(buffer_ptr--); - data = LUT2[(buffer_value >> 4) & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - data = LUT2[buffer_value & 0x0F]; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; + data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F]; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time); - vscan_start_(); - for (int i = 0; i < this->get_height_internal(); i++) { - data = 0b00000000; - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); - send |= clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + if (this->model_ == INKPLATE_6_PLUS) { + clean_fast_(2, 2); + clean_fast_(3, 1); + } else { + uint32_t send = this->pin_lut_[0]; + vscan_start_(); + for (int i = 0, im = this->get_height_internal(); i < im; i++) { + hscan_start_(send); + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + } + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = data_mask | clock; + vscan_end_(); } - GPIO.out_w1ts = clock; - GPIO.out_w1tc = get_data_pin_mask_() | clock; - vscan_end_(); + delayMicroseconds(230); + ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time); } - delayMicroseconds(230); - ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time); - vscan_start_(); eink_off_(); this->block_partial_ = false; this->partial_updates_ = 0; ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time); } + void Inkplate6::display3b_() { ESP_LOGV(TAG, "Display3b called"); uint32_t start_time = millis(); eink_on_(); - clean_fast_(0, 1); - clean_fast_(1, 12); - clean_fast_(2, 1); - clean_fast_(0, 11); - clean_fast_(2, 1); - clean_fast_(1, 12); - clean_fast_(2, 1); - clean_fast_(0, 11); + if (this->model_ == INKPLATE_6_PLUS) { + clean_fast_(0, 1); + clean_fast_(1, 15); + clean_fast_(2, 1); + clean_fast_(0, 5); + clean_fast_(2, 1); + clean_fast_(1, 15); + } else { + clean_fast_(0, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + clean_fast_(2, 1); + clean_fast_(1, 21); + clean_fast_(2, 1); + clean_fast_(0, 12); + } uint32_t clock = (1 << this->cl_pin_->get_pin()); - for (int k = 0; k < 8; k++) { - const uint8_t *buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1]; - uint32_t send; - uint8_t pix1; - uint8_t pix2; - uint8_t pix3; - uint8_t pix4; - uint8_t pixel; - uint8_t pixel2; - + uint32_t data_mask = this->get_data_pin_mask_(); + uint32_t pos; + uint32_t data; + uint8_t glut_size = this->model_ == INKPLATE_6_PLUS ? 9 : 8; + for (int k = 0; k < glut_size; k++) { + pos = this->get_buffer_length_(); vscan_start_(); for (int i = 0; i < this->get_height_internal(); i++) { - pix1 = (*buffer_ptr--); - pix2 = (*buffer_ptr--); - pix3 = (*buffer_ptr--); - pix4 = (*buffer_ptr--); - pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); - pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); - - send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | - (((pixel & 0b11100000) >> 5) << 25); - hscan_start_(send); - send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | - (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - - for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) { - pix1 = (*buffer_ptr--); - pix2 = (*buffer_ptr--); - pix3 = (*buffer_ptr--); - pix4 = (*buffer_ptr--); - pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0); - pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) | - (waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0); - - send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) | - (((pixel & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; - - send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) | - (((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + hscan_start_(data); + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + GPIO.out_w1ts = data | clock; + GPIO.out_w1tc = data_mask | clock; + + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + GPIO.out_w1ts = data | clock; + GPIO.out_w1tc = data_mask | clock; + data = this->glut2_[k * 256 + this->buffer_[--pos]]; + data |= this->glut_[k * 256 + this->buffer_[--pos]]; + GPIO.out_w1ts = data | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); } - clean_fast_(2, 1); clean_fast_(3, 1); vscan_start_(); eink_off_(); ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time); } + bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update called"); uint32_t start_time = millis(); @@ -432,16 +501,15 @@ bool Inkplate6::partial_update_() { this->partial_updates_++; - uint16_t pos = this->get_buffer_length_() - 1; - uint32_t send; + uint32_t pos = this->get_buffer_length_() - 1; uint8_t data; uint8_t diffw, diffb; uint32_t n = (this->get_buffer_length_() * 2) - 1; for (int i = 0, im = this->get_height_internal(); i < im; i++) { for (int j = 0, jm = (this->get_width_internal() / 8); j < jm; j++) { - diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]); - diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos]; + diffw = this->buffer_[pos] & ~(this->partial_buffer_[pos]); + diffb = ~(this->buffer_[pos]) & this->partial_buffer_[pos]; pos--; this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4]; this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F]; @@ -451,23 +519,20 @@ bool Inkplate6::partial_update_() { eink_on_(); uint32_t clock = (1 << this->cl_pin_->get_pin()); + uint32_t data_mask = this->get_data_pin_mask_(); for (int k = 0; k < 5; k++) { vscan_start_(); const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1]; for (int i = 0; i < this->get_height_internal(); i++) { data = *(data_ptr--); - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25); - hscan_start_(send); + hscan_start_(this->pin_lut_[data]); for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) { data = *(data_ptr--); - send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | - (((data & 0b11100000) >> 5) << 25) | clock; - GPIO.out_w1ts = send; - GPIO.out_w1tc = send; + GPIO.out_w1ts = this->pin_lut_[data] | clock; + GPIO.out_w1tc = data_mask | clock; } - GPIO.out_w1ts = send; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = clock; + GPIO.out_w1tc = data_mask | clock; vscan_end_(); } delayMicroseconds(230); @@ -482,6 +547,7 @@ bool Inkplate6::partial_update_() { ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time); return true; } + void Inkplate6::vscan_start_() { this->ckv_pin_->digital_write(true); delayMicroseconds(7); @@ -505,30 +571,23 @@ void Inkplate6::vscan_start_() { delayMicroseconds(0); this->ckv_pin_->digital_write(true); } -void Inkplate6::vscan_write_() { - this->ckv_pin_->digital_write(false); - this->le_pin_->digital_write(true); - this->le_pin_->digital_write(false); - delayMicroseconds(0); - this->sph_pin_->digital_write(false); - this->cl_pin_->digital_write(true); - this->cl_pin_->digital_write(false); - this->sph_pin_->digital_write(true); - this->ckv_pin_->digital_write(true); -} + void Inkplate6::hscan_start_(uint32_t d) { + uint8_t clock = (1 << this->cl_pin_->get_pin()); this->sph_pin_->digital_write(false); - GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin()); - GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()); + GPIO.out_w1ts = d | clock; + GPIO.out_w1tc = this->get_data_pin_mask_() | clock; this->sph_pin_->digital_write(true); + this->ckv_pin_->digital_write(true); } + void Inkplate6::vscan_end_() { this->ckv_pin_->digital_write(false); this->le_pin_->digital_write(true); this->le_pin_->digital_write(false); - delayMicroseconds(1); - this->ckv_pin_->digital_write(true); + delayMicroseconds(0); } + void Inkplate6::clean() { ESP_LOGV(TAG, "Clean called"); uint32_t start_time = millis(); @@ -542,20 +601,22 @@ void Inkplate6::clean() { clean_fast_(1, 10); // White to White ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time); } + void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep); uint32_t start_time = millis(); eink_on_(); uint8_t data = 0; - if (c == 0) // White + if (c == 0) { // White data = 0b10101010; - else if (c == 1) // Black + } else if (c == 1) { // Black data = 0b01010101; - else if (c == 2) // Discharge + } else if (c == 2) { // Discharge data = 0b00000000; - else if (c == 3) // Skip + } else if (c == 3) { // Skip data = 0b11111111; + } uint32_t send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) | (((data & 0b11100000) >> 5) << 25); @@ -567,14 +628,14 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { hscan_start_(send); GPIO.out_w1ts = send | clock; GPIO.out_w1tc = clock; - for (int j = 0, jm = this->get_width_internal() / 8; j < jm; j++) { + for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) { GPIO.out_w1ts = clock; GPIO.out_w1tc = clock; GPIO.out_w1ts = clock; GPIO.out_w1tc = clock; } - GPIO.out_w1ts = clock; - GPIO.out_w1tc = get_data_pin_mask_() | clock; + GPIO.out_w1ts = send | clock; + GPIO.out_w1tc = clock; vscan_end_(); } delayMicroseconds(230); @@ -582,7 +643,10 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) { } ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time); } + void Inkplate6::pins_z_state_() { + this->cl_pin_->pin_mode(gpio::FLAG_INPUT); + this->le_pin_->pin_mode(gpio::FLAG_INPUT); this->ckv_pin_->pin_mode(gpio::FLAG_INPUT); this->sph_pin_->pin_mode(gpio::FLAG_INPUT); @@ -599,7 +663,10 @@ void Inkplate6::pins_z_state_() { this->display_data_6_pin_->pin_mode(gpio::FLAG_INPUT); this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT); } + void Inkplate6::pins_as_outputs_() { + this->cl_pin_->pin_mode(gpio::FLAG_OUTPUT); + this->le_pin_->pin_mode(gpio::FLAG_OUTPUT); this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT); this->sph_pin_->pin_mode(gpio::FLAG_OUTPUT); diff --git a/esphome/components/inkplate6/inkplate.h b/esphome/components/inkplate6/inkplate.h index 2dac12a0c409..9355154c5ab6 100644 --- a/esphome/components/inkplate6/inkplate.h +++ b/esphome/components/inkplate6/inkplate.h @@ -13,32 +13,28 @@ namespace inkplate6 { enum InkplateModel : uint8_t { INKPLATE_6 = 0, INKPLATE_10 = 1, + INKPLATE_6_PLUS = 2, }; class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice { public: - const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001, - 0b10010110, 0b10010101, 0b01101010, 0b01101001, 0b01100110, 0b01100101, - 0b01011010, 0b01011001, 0b01010110, 0b01010101}; - const uint8_t LUTW[16] = {0b11111111, 0b11111110, 0b11111011, 0b11111010, 0b11101111, 0b11101110, - 0b11101011, 0b11101010, 0b10111111, 0b10111110, 0b10111011, 0b10111010, - 0b10101111, 0b10101110, 0b10101011, 0b10101010}; - const uint8_t LUTB[16] = {0b11111111, 0b11111101, 0b11110111, 0b11110101, 0b11011111, 0b11011101, - 0b11010111, 0b11010101, 0b01111111, 0b01111101, 0b01110111, 0b01110101, - 0b01011111, 0b01011101, 0b01010111, 0b01010101}; - const uint8_t pixelMaskLUT[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000, - 0b00010000, 0b00100000, 0b01000000, 0b10000000}; - const uint8_t pixelMaskGLUT[2] = {0b00001111, 0b11110000}; - const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, - {0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, + const uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95, + 0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55}; + const uint8_t LUTW[16] = {0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA, + 0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA}; + const uint8_t LUTB[16] = {0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5, + 0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55}; + + const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80}; + const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0}; + + const uint8_t waveform3Bit[8][8] = {{0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, {1, 1, 1, 2, 2, 1, 0, 0}, + {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, {2, 2, 1, 1, 2, 1, 2, 0}, {1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}}; - const uint32_t waveform[50] = { - 0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868, - 0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68, - 0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595, - 0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484, - 0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9, - 0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111}; + const uint8_t waveform3Bit6Plus[8][9] = {{0, 0, 0, 0, 0, 2, 1, 1, 0}, {0, 0, 2, 1, 1, 1, 2, 1, 0}, + {0, 2, 2, 2, 1, 1, 2, 1, 0}, {0, 0, 2, 2, 2, 1, 2, 1, 0}, + {0, 0, 0, 0, 2, 2, 2, 1, 0}, {0, 0, 2, 1, 2, 1, 1, 2, 0}, + {0, 0, 2, 2, 2, 1, 1, 2, 0}, {0, 0, 0, 0, 2, 2, 2, 2, 0}}; void set_greyscale(bool greyscale) { this->greyscale_ = greyscale; @@ -88,6 +84,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public bool get_partial_updating() { return this->partial_updating_; } uint8_t get_temperature() { return this->temperature_; } + void block_partial() { this->block_partial_ = true; } + protected: void draw_absolute_pixel_internal(int x, int y, Color color) override; void display1b_(); @@ -99,28 +97,34 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public void hscan_start_(uint32_t d); void vscan_end_(); void vscan_start_(); - void vscan_write_(); void eink_off_(); void eink_on_(); + bool read_power_status_(); void setup_pins_(); void pins_z_state_(); void pins_as_outputs_(); int get_width_internal() override { - if (this->model_ == INKPLATE_6) + if (this->model_ == INKPLATE_6) { return 800; - else if (this->model_ == INKPLATE_10) + } else if (this->model_ == INKPLATE_10) { return 1200; + } else if (this->model_ == INKPLATE_6_PLUS) { + return 1024; + } return 0; } int get_height_internal() override { - if (this->model_ == INKPLATE_6) + if (this->model_ == INKPLATE_6) { return 600; - else if (this->model_ == INKPLATE_10) + } else if (this->model_ == INKPLATE_10) { return 825; + } else if (this->model_ == INKPLATE_6_PLUS) { + return 758; + } return 0; } @@ -139,16 +143,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public return data; } - uint8_t panel_on_ = 0; + bool panel_on_{false}; uint8_t temperature_; uint8_t *partial_buffer_{nullptr}; uint8_t *partial_buffer_2_{nullptr}; + uint32_t *glut_{nullptr}; + uint32_t *glut2_{nullptr}; + uint32_t pin_lut_[256]; + uint32_t full_update_every_; uint32_t partial_updates_{0}; - bool block_partial_; + bool block_partial_{true}; bool greyscale_; bool partial_updating_; diff --git a/esphome/components/kalman_combinator/kalman_combinator.cpp b/esphome/components/kalman_combinator/kalman_combinator.cpp index d55f26126f97..50d8f03a930d 100644 --- a/esphome/components/kalman_combinator/kalman_combinator.cpp +++ b/esphome/components/kalman_combinator/kalman_combinator.cpp @@ -28,7 +28,7 @@ void KalmanCombinatorComponent::add_source(Sensor *sensor, std::functionadd_source(sensor, std::function{[stddev](float) -> float { return stddev; }}); + this->add_source(sensor, std::function{[stddev](float x) -> float { return stddev; }}); } void KalmanCombinatorComponent::update_variance_() { diff --git a/esphome/components/ledc/output.py b/esphome/components/ledc/output.py index 895dcc998b7a..f6dc89cd9bfb 100644 --- a/esphome/components/ledc/output.py +++ b/esphome/components/ledc/output.py @@ -13,12 +13,12 @@ def calc_max_frequency(bit_depth): - return 80e6 / (2 ** bit_depth) + return 80e6 / (2**bit_depth) def calc_min_frequency(bit_depth): - max_div_num = ((2 ** 20) - 1) / 256.0 - return 80e6 / (max_div_num * (2 ** bit_depth)) + max_div_num = ((2**20) - 1) / 256.0 + return 80e6 / (max_div_num * (2**bit_depth)) def validate_frequency(value): diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 5ab9f66ce42e..a6ab299308e9 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -102,21 +102,24 @@ class RandomLightEffect : public LightEffect { class LambdaLightEffect : public LightEffect { public: - LambdaLightEffect(const std::string &name, std::function f, uint32_t update_interval) + LambdaLightEffect(const std::string &name, std::function f, uint32_t update_interval) : LightEffect(name), f_(std::move(f)), update_interval_(update_interval) {} + void start() override { this->initial_run_ = true; } void apply() override { const uint32_t now = millis(); if (now - this->last_run_ >= this->update_interval_) { this->last_run_ = now; - this->f_(); + this->f_(this->initial_run_); + this->initial_run_ = false; } } protected: - std::function f_; + std::function f_; uint32_t update_interval_; uint32_t last_run_{0}; + bool initial_run_; }; class AutomationLightEffect : public LightEffect { diff --git a/esphome/components/light/effects.py b/esphome/components/light/effects.py index 4b2209c83386..a987e0fc96e4 100644 --- a/esphome/components/light/effects.py +++ b/esphome/components/light/effects.py @@ -141,7 +141,9 @@ def register_addressable_effect( }, ) async def lambda_effect_to_code(config, effect_id): - lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.void) + lambda_ = await cg.process_lambda( + config[CONF_LAMBDA], [(bool, "initial_run")], return_type=cg.void + ) return cg.new_Pvariable( effect_id, config[CONF_NAME], lambda_, config[CONF_UPDATE_INTERVAL] ) diff --git a/esphome/components/light/light_call.cpp b/esphome/components/light/light_call.cpp index 3f1b8aef30bc..fb4e45b3b6c5 100644 --- a/esphome/components/light/light_call.cpp +++ b/esphome/components/light/light_call.cpp @@ -98,10 +98,11 @@ void LightCall::perform() { // EFFECT auto effect = this->effect_; const char *effect_s; - if (effect == 0u) + if (effect == 0u) { effect_s = "None"; - else + } else { effect_s = this->parent_->effects_[*this->effect_ - 1]->get_name().c_str(); + } if (this->publish_) { ESP_LOGD(TAG, " Effect: '%s'", effect_s); @@ -445,9 +446,10 @@ std::set LightCall::get_suitable_color_modes_() { }; auto key = KEY(has_white, has_ct, has_cwww, has_rgb); - for (auto &item : lookup_table) + for (auto &item : lookup_table) { if (std::get<0>(item) == key) return std::get<1>(item); + } // This happens if there are conflicting flags given. return {}; diff --git a/esphome/components/light/light_state.cpp b/esphome/components/light/light_state.cpp index 5f16585c36da..151bc58a1c5d 100644 --- a/esphome/components/light/light_state.cpp +++ b/esphome/components/light/light_state.cpp @@ -145,10 +145,11 @@ void LightState::publish_state() { this->remote_values_callback_.call(); } LightOutput *LightState::get_output() const { return this->output_; } std::string LightState::get_effect_name() { - if (this->active_effect_index_ > 0) + if (this->active_effect_index_ > 0) { return this->effects_[this->active_effect_index_ - 1]->get_name(); - else + } else { return "None"; + } } void LightState::add_new_remote_values_callback(std::function &&send_callback) { @@ -219,10 +220,11 @@ void LightState::start_effect_(uint32_t effect_index) { effect->start_internal(); } LightEffect *LightState::get_active_effect_() { - if (this->active_effect_index_ == 0) + if (this->active_effect_index_ == 0) { return nullptr; - else + } else { return this->effects_[this->active_effect_index_ - 1]; + } } void LightState::stop_effect_() { auto *effect = this->get_active_effect_(); diff --git a/esphome/components/lilygo_t5_47/__init__.py b/esphome/components/lilygo_t5_47/__init__.py new file mode 100644 index 000000000000..5499d096a91a --- /dev/null +++ b/esphome/components/lilygo_t5_47/__init__.py @@ -0,0 +1,3 @@ +import esphome.codegen as cg + +lilygo_t5_47_ns = cg.esphome_ns.namespace("lilygo_t5_47") diff --git a/esphome/components/lilygo_t5_47/touchscreen/__init__.py b/esphome/components/lilygo_t5_47/touchscreen/__init__.py new file mode 100644 index 000000000000..9ec6c925ee82 --- /dev/null +++ b/esphome/components/lilygo_t5_47/touchscreen/__init__.py @@ -0,0 +1,45 @@ +import esphome.codegen as cg +import esphome.config_validation as cv + +from esphome import pins +from esphome.components import i2c, touchscreen +from esphome.const import CONF_ID + +from .. import lilygo_t5_47_ns + +CODEOWNERS = ["@jesserockz"] +DEPENDENCIES = ["i2c"] + +LilygoT547Touchscreen = lilygo_t5_47_ns.class_( + "LilygoT547Touchscreen", + touchscreen.Touchscreen, + cg.Component, + i2c.I2CDevice, +) + +CONF_LILYGO_T5_47_TOUCHSCREEN_ID = "lilygo_t5_47_touchscreen_id" +CONF_INTERRUPT_PIN = "interrupt_pin" + + +CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LilygoT547Touchscreen), + cv.Required(CONF_INTERRUPT_PIN): cv.All( + pins.internal_gpio_input_pin_schema + ), + } + ) + .extend(i2c.i2c_device_schema(0x5A)) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + await touchscreen.register_touchscreen(var, config) + + interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN]) + cg.add(var.set_interrupt_pin(interrupt_pin)) diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp new file mode 100644 index 000000000000..b5cf63980bfa --- /dev/null +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.cpp @@ -0,0 +1,141 @@ +#include "lilygo_t5_47_touchscreen.h" + +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace lilygo_t5_47 { + +static const char *const TAG = "lilygo_t5_47.touchscreen"; + +static const uint8_t POWER_REGISTER = 0xD6; +static const uint8_t TOUCH_REGISTER = 0xD0; + +static const uint8_t WAKEUP_CMD[1] = {0x06}; +static const uint8_t READ_FLAGS[1] = {0x00}; +static const uint8_t CLEAR_FLAGS[2] = {0x00, 0xAB}; +static const uint8_t READ_TOUCH[1] = {0x07}; + +#define ERROR_CHECK(err) \ + if ((err) != i2c::ERROR_OK) { \ + ESP_LOGE(TAG, "Failed to communicate!"); \ + this->status_set_warning(); \ + return; \ + } + +void Store::gpio_intr(Store *store) { store->touch = true; } + +void LilygoT547Touchscreen::setup() { + ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen..."); + this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); + this->interrupt_pin_->setup(); + + this->store_.pin = this->interrupt_pin_->to_isr(); + this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE); + + if (this->write(nullptr, 0) != i2c::ERROR_OK) { + ESP_LOGE(TAG, "Failed to communicate!"); + this->interrupt_pin_->detach_interrupt(); + this->mark_failed(); + return; + } + + this->write_register(POWER_REGISTER, WAKEUP_CMD, 1); +} + +void LilygoT547Touchscreen::loop() { + if (!this->store_.touch) { + for (auto *listener : this->touch_listeners_) + listener->release(); + return; + } + this->store_.touch = false; + + uint8_t point = 0; + uint8_t buffer[40] = {0}; + uint32_t sum_l = 0, sum_h = 0; + + i2c::ErrorCode err; + err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1); + ERROR_CHECK(err); + + err = this->read(buffer, 7); + ERROR_CHECK(err); + + if (buffer[0] == 0xAB) { + this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2); + return; + } + + point = buffer[5] & 0xF; + + if (point == 0) { + for (auto *listener : this->touch_listeners_) + listener->release(); + return; + } else if (point == 1) { + err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); + ERROR_CHECK(err); + err = this->read(&buffer[5], 2); + ERROR_CHECK(err); + + sum_l = buffer[5] << 8 | buffer[6]; + } else if (point > 1) { + err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1); + ERROR_CHECK(err); + err = this->read(&buffer[5], 5 * (point - 1) + 3); + ERROR_CHECK(err); + + sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2]; + } + + this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2); + + for (int i = 0; i < 5 * point; i++) + sum_h += buffer[i]; + + if (sum_l != sum_h) + point = 0; + + if (point) { + uint8_t offset; + for (int i = 0; i < point; i++) { + if (i == 0) { + offset = 0; + } else { + offset = 4; + } + + TouchPoint tp; + + tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F; + tp.state = buffer[i * 5 + offset] & 0x0F; + if (tp.state == 0x06) + tp.state = 0x07; + + tp.y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F)); + tp.x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F)); + + this->defer([this, tp]() { this->send_touch_(tp); }); + } + } else { + TouchPoint tp; + tp.id = (buffer[0] >> 4) & 0x0F; + tp.state = 0x06; + tp.y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F)); + tp.x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F)); + + this->defer([this, tp]() { this->send_touch_(tp); }); + } + + this->status_clear_warning(); +} + +void LilygoT547Touchscreen::dump_config() { + ESP_LOGCONFIG(TAG, "Lilygo T5 47 Touchscreen:"); + LOG_I2C_DEVICE(this); + LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_); +} + +} // namespace lilygo_t5_47 +} // namespace esphome diff --git a/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h new file mode 100644 index 000000000000..3d00e0b11737 --- /dev/null +++ b/esphome/components/lilygo_t5_47/touchscreen/lilygo_t5_47_touchscreen.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/automation.h" +#include "esphome/core/component.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace lilygo_t5_47 { + +struct Store { + volatile bool touch; + ISRInternalGPIOPin pin; + + static void gpio_intr(Store *store); +}; + +using namespace touchscreen; + +class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice { + public: + void setup() override; + void loop() override; + void dump_config() override; + + void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } + + protected: + InternalGPIOPin *interrupt_pin_; + Store store_; +}; + +} // namespace lilygo_t5_47 +} // namespace esphome diff --git a/esphome/components/lock/__init__.py b/esphome/components/lock/__init__.py new file mode 100644 index 000000000000..f659c48a6e9f --- /dev/null +++ b/esphome/components/lock/__init__.py @@ -0,0 +1,102 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.automation import Condition, maybe_simple_id +from esphome.components import mqtt +from esphome.const import ( + CONF_ID, + CONF_ON_LOCK, + CONF_ON_UNLOCK, + CONF_TRIGGER_ID, + CONF_MQTT_ID, +) +from esphome.core import CORE, coroutine_with_priority +from esphome.cpp_helpers import setup_entity + +CODEOWNERS = ["@esphome/core"] +IS_PLATFORM_COMPONENT = True + +lock_ns = cg.esphome_ns.namespace("lock") +Lock = lock_ns.class_("Lock", cg.EntityBase) +LockPtr = Lock.operator("ptr") +LockCall = lock_ns.class_("LockCall") + +UnlockAction = lock_ns.class_("UnlockAction", automation.Action) +LockAction = lock_ns.class_("LockAction", automation.Action) +OpenAction = lock_ns.class_("OpenAction", automation.Action) +LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action) + +LockCondition = lock_ns.class_("LockCondition", Condition) +LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template()) +LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template()) + +LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( + { + cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent), + cv.Optional(CONF_ON_LOCK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger), + } + ), + cv.Optional(CONF_ON_UNLOCK): automation.validate_automation( + { + cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger), + } + ), + } +) + + +async def setup_lock_core_(var, config): + await setup_entity(var, config) + + for conf in config.get(CONF_ON_LOCK, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + for conf in config.get(CONF_ON_UNLOCK, []): + trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) + await automation.build_automation(trigger, [], conf) + + if CONF_MQTT_ID in config: + mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) + await mqtt.register_mqtt_component(mqtt_, config) + + +async def register_lock(var, config): + if not CORE.has_id(config[CONF_ID]): + var = cg.Pvariable(config[CONF_ID], var) + cg.add(cg.App.register_lock(var)) + await setup_lock_core_(var, config) + + +LOCK_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(Lock), + } +) + + +@automation.register_action("lock.unlock", UnlockAction, LOCK_ACTION_SCHEMA) +@automation.register_action("lock.lock", LockAction, LOCK_ACTION_SCHEMA) +@automation.register_action("lock.open", OpenAction, LOCK_ACTION_SCHEMA) +async def lock_action_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) + + +@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA) +async def lock_is_on_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren, True) + + +@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA) +async def lock_is_off_to_code(config, condition_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(condition_id, template_arg, paren, False) + + +@coroutine_with_priority(100.0) +async def to_code(config): + cg.add_global(lock_ns.using) + cg.add_define("USE_LOCK") diff --git a/esphome/components/lock/automation.h b/esphome/components/lock/automation.h new file mode 100644 index 000000000000..74cfbe2ef6df --- /dev/null +++ b/esphome/components/lock/automation.h @@ -0,0 +1,87 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace lock { + +template class LockAction : public Action { + public: + explicit LockAction(Lock *a_lock) : lock_(a_lock) {} + + void play(Ts... x) override { this->lock_->lock(); } + + protected: + Lock *lock_; +}; + +template class UnlockAction : public Action { + public: + explicit UnlockAction(Lock *a_lock) : lock_(a_lock) {} + + void play(Ts... x) override { this->lock_->unlock(); } + + protected: + Lock *lock_; +}; + +template class OpenAction : public Action { + public: + explicit OpenAction(Lock *a_lock) : lock_(a_lock) {} + + void play(Ts... x) override { this->lock_->open(); } + + protected: + Lock *lock_; +}; + +template class LockCondition : public Condition { + public: + LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {} + bool check(Ts... x) override { + auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED; + return this->parent_->state == check_state; + } + + protected: + Lock *parent_; + bool state_; +}; + +class LockLockTrigger : public Trigger<> { + public: + LockLockTrigger(Lock *a_lock) { + a_lock->add_on_state_callback([this, a_lock]() { + if (a_lock->state == LockState::LOCK_STATE_LOCKED) { + this->trigger(); + } + }); + } +}; + +class LockUnlockTrigger : public Trigger<> { + public: + LockUnlockTrigger(Lock *a_lock) { + a_lock->add_on_state_callback([this, a_lock]() { + if (a_lock->state == LockState::LOCK_STATE_UNLOCKED) { + this->trigger(); + } + }); + } +}; + +template class LockPublishAction : public Action { + public: + LockPublishAction(Lock *a_lock) : lock_(a_lock) {} + TEMPLATABLE_VALUE(LockState, state) + + void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); } + + protected: + Lock *lock_; +}; + +} // namespace lock +} // namespace esphome diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp new file mode 100644 index 000000000000..e32ab6d0a6b1 --- /dev/null +++ b/esphome/components/lock/lock.cpp @@ -0,0 +1,109 @@ +#include "lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace lock { + +static const char *const TAG = "lock"; + +const char *lock_state_to_string(LockState state) { + switch (state) { + case LOCK_STATE_LOCKED: + return "LOCKED"; + case LOCK_STATE_UNLOCKED: + return "UNLOCKED"; + case LOCK_STATE_JAMMED: + return "JAMMED"; + case LOCK_STATE_LOCKING: + return "LOCKING"; + case LOCK_STATE_UNLOCKING: + return "UNLOCKING"; + case LOCK_STATE_NONE: + default: + return "UNKNOWN"; + } +} + +Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {} +Lock::Lock() : Lock("") {} +LockCall Lock::make_call() { return LockCall(this); } + +void Lock::lock() { + auto call = this->make_call(); + call.set_state(LOCK_STATE_LOCKED); + this->control(call); +} +void Lock::unlock() { + auto call = this->make_call(); + call.set_state(LOCK_STATE_UNLOCKED); + this->control(call); +} +void Lock::open() { + if (traits.get_supports_open()) { + ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str()); + this->open_latch(); + } else { + ESP_LOGW(TAG, "'%s' Does not support Open.", this->get_name().c_str()); + } +} +void Lock::publish_state(LockState state) { + if (!this->publish_dedup_.next(state)) + return; + + this->state = state; + this->rtc_.save(&this->state); + ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state)); + this->state_callback_.call(); +} + +void Lock::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } +uint32_t Lock::hash_base() { return 856245656UL; } + +void LockCall::perform() { + ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); + this->validate_(); + if (this->state_.has_value()) { + const char *state_s = lock_state_to_string(*this->state_); + ESP_LOGD(TAG, " State: %s", state_s); + } + this->parent_->control(*this); +} +void LockCall::validate_() { + if (this->state_.has_value()) { + auto state = *this->state_; + if (!this->parent_->traits.supports_state(state)) { + ESP_LOGW(TAG, " State %s is not supported by this device!", lock_state_to_string(*this->state_)); + this->state_.reset(); + } + } +} +LockCall &LockCall::set_state(LockState state) { + this->state_ = state; + return *this; +} +LockCall &LockCall::set_state(optional state) { + this->state_ = state; + return *this; +} +LockCall &LockCall::set_state(const std::string &state) { + if (str_equals_case_insensitive(state, "LOCKED")) { + this->set_state(LOCK_STATE_LOCKED); + } else if (str_equals_case_insensitive(state, "UNLOCKED")) { + this->set_state(LOCK_STATE_UNLOCKED); + } else if (str_equals_case_insensitive(state, "JAMMED")) { + this->set_state(LOCK_STATE_JAMMED); + } else if (str_equals_case_insensitive(state, "LOCKING")) { + this->set_state(LOCK_STATE_LOCKING); + } else if (str_equals_case_insensitive(state, "UNLOCKING")) { + this->set_state(LOCK_STATE_UNLOCKING); + } else if (str_equals_case_insensitive(state, "NONE")) { + this->set_state(LOCK_STATE_NONE); + } else { + ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state.c_str()); + } + return *this; +} +const optional &LockCall::get_state() const { return this->state_; } + +} // namespace lock +} // namespace esphome diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h new file mode 100644 index 000000000000..f11035c03eb6 --- /dev/null +++ b/esphome/components/lock/lock.h @@ -0,0 +1,178 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/entity_base.h" +#include "esphome/core/preferences.h" +#include "esphome/core/helpers.h" +#include "esphome/core/log.h" +#include + +namespace esphome { +namespace lock { + +class Lock; + +#define LOG_LOCK(prefix, type, obj) \ + if ((obj) != nullptr) { \ + ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ + if (!(obj)->get_icon().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \ + } \ + if ((obj)->traits.get_assumed_state()) { \ + ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \ + } \ + } +/// Enum for all states a lock can be in. +enum LockState : uint8_t { + LOCK_STATE_NONE = 0, + LOCK_STATE_LOCKED = 1, + LOCK_STATE_UNLOCKED = 2, + LOCK_STATE_JAMMED = 3, + LOCK_STATE_LOCKING = 4, + LOCK_STATE_UNLOCKING = 5 +}; +const char *lock_state_to_string(LockState state); + +class LockTraits { + public: + LockTraits() = default; + + bool get_supports_open() const { return this->supports_open_; } + void set_supports_open(bool supports_open) { this->supports_open_ = supports_open; } + bool get_requires_code() const { return this->requires_code_; } + void set_requires_code(bool requires_code) { this->requires_code_ = requires_code; } + bool get_assumed_state() const { return this->assumed_state_; } + void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } + + bool supports_state(LockState state) const { return supported_states_.count(state); } + std::set get_supported_states() const { return supported_states_; } + void set_supported_states(std::set states) { supported_states_ = std::move(states); } + void add_supported_state(LockState state) { supported_states_.insert(state); } + + protected: + bool supports_open_{false}; + bool requires_code_{false}; + bool assumed_state_{false}; + std::set supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED}; +}; + +/** This class is used to encode all control actions on a lock device. + * + * It is supposed to be used by all code that wishes to control a lock device (mqtt, api, lambda etc). + * Create an instance of this class by calling `id(lock_device).make_call();`. Then set all attributes + * with the `set_x` methods. Finally, to apply the changes call `.perform();`. + * + * The integration that implements the lock device receives this instance with the `control` method. + * It should check all the properties it implements and apply them as needed. It should do so by + * getting all properties it controls with the getter methods in this class. If the optional value is + * set (check with `.has_value()`) that means the user wants to control this property. Get the value + * of the optional with the star operator (`*call.get_state()`) and apply it. + */ +class LockCall { + public: + LockCall(Lock *parent) : parent_(parent) {} + + /// Set the state of the lock device. + LockCall &set_state(LockState state); + /// Set the state of the lock device. + LockCall &set_state(optional state); + /// Set the state of the lock device based on a string. + LockCall &set_state(const std::string &state); + + void perform(); + + const optional &get_state() const; + + protected: + void validate_(); + + Lock *const parent_; + optional state_; +}; + +/** Base class for all locks. + * + * A lock is basically a switch with a combination of a binary sensor (for reporting lock values) + * and a write_state method that writes a state to the hardware. Locks can also have an "open" + * method to unlatch. + * + * For integrations: Integrations must implement the method control(). + * Control will be called with the arguments supplied by the user and should be used + * to control all values of the lock. + */ +class Lock : public EntityBase { + public: + explicit Lock(); + explicit Lock(const std::string &name); + + /** Make a lock device control call, this is used to control the lock device, see the LockCall description + * for more info. + * @return A new LockCall instance targeting this lock device. + */ + LockCall make_call(); + + /** Publish a state to the front-end from the back-end. + * + * Then the internal value member is set and finally the callbacks are called. + * + * @param state The new state. + */ + void publish_state(LockState state); + + /// The current reported state of the lock. + LockState state{LOCK_STATE_NONE}; + + LockTraits traits; + + /** Turn this lock on. This is called by the front-end. + * + * For implementing locks, please override control. + */ + void lock(); + /** Turn this lock off. This is called by the front-end. + * + * For implementing locks, please override control. + */ + void unlock(); + /** Open (unlatch) this lock. This is called by the front-end. + * + * For implementing locks, please override control. + */ + void open(); + + /** Set callback for state changes. + * + * @param callback The void(bool) callback. + */ + void add_on_state_callback(std::function &&callback); + + protected: + friend LockCall; + + /** Perform the open latch action with hardware. This method is optional to implement + * when creating a new lock. + * + * In the implementation of this method, it is recommended you also call + * publish_state with "unlock" to acknowledge that the state was written to the hardware. + */ + virtual void open_latch() { unlock(); }; + + /** Control the lock device, this is a virtual method that each lock integration must implement. + * + * See more info in LockCall. The integration should check all of its values in this method and + * set them accordingly. At the end of the call, the integration must call `publish_state()` to + * notify the frontend of a changed state. + * + * @param call The LockCall instance encoding all attribute changes. + */ + virtual void control(const LockCall &call) = 0; + + uint32_t hash_base() override; + + CallbackManager state_callback_{}; + Deduplicator publish_dedup_; + ESPPreferenceObject rtc_; +}; + +} // namespace lock +} // namespace esphome diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 960ac58071ca..b50a78eb9676 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -143,11 +143,13 @@ void MAX7219Component::dump_config() { void MAX7219Component::display() { for (uint8_t i = 0; i < 8; i++) { this->enable(); - for (uint8_t j = 0; j < this->num_chips_; j++) - if (reverse_) + for (uint8_t j = 0; j < this->num_chips_; j++) { + if (reverse_) { this->send_byte_(8 - i, buffer_[(num_chips_ - j - 1) * 8 + i]); - else + } else { this->send_byte_(8 - i, buffer_[j * 8 + i]); + } + } this->disable(); } } diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp index 2368c1744801..1b9ae230f7f3 100644 --- a/esphome/components/max7219digit/max7219digit.cpp +++ b/esphome/components/max7219digit/max7219digit.cpp @@ -253,10 +253,11 @@ void MAX7219Component::send_char(uint8_t chip, uint8_t data) { void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) { for (uint8_t col = 0; col < 8; col++) { // RUN THIS LOOP 8 times until column is 7 this->enable(); // start sending by enabling SPI - for (uint8_t i = 0; i < chip; i++) // send extra NOPs to push the pixels out to extra displays + for (uint8_t i = 0; i < chip; i++) { // send extra NOPs to push the pixels out to extra displays this->send_byte_(MAX7219_REGISTER_NOOP, MAX7219_REGISTER_NOOP); // run this loop unit the matching chip is reached - uint8_t b = 0; // rotate pixels 90 degrees -- set byte to 0 + } + uint8_t b = 0; // rotate pixels 90 degrees -- set byte to 0 if (this->orientation_ == 0) { for (uint8_t i = 0; i < 8; i++) { // run this loop 8 times for all the pixels[8] received diff --git a/esphome/components/max9611/__init__.py b/esphome/components/max9611/__init__.py new file mode 100644 index 000000000000..9b0ea14b57e7 --- /dev/null +++ b/esphome/components/max9611/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@mckaymatthew"] diff --git a/esphome/components/max9611/max9611.cpp b/esphome/components/max9611/max9611.cpp new file mode 100644 index 000000000000..70a94c4ad9bb --- /dev/null +++ b/esphome/components/max9611/max9611.cpp @@ -0,0 +1,93 @@ +#include "max9611.h" +#include "esphome/core/log.h" +#include "esphome/components/i2c/i2c_bus.h" +namespace esphome { +namespace max9611 { +using namespace esphome::i2c; +// Sign extend +// http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend +template inline T signextend(const T x) { + struct { + T x : B; + } s; + return s.x = x; +} +// Map the gain register to in uV/LSB +float gain_to_lsb(MAX9611Multiplexer gain) { + float lsb = 0.0; + if (gain == MAX9611_MULTIPLEXER_CSA_GAIN1) { + lsb = 107.50; + } else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN4) { + lsb = 26.88; + } else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN8) { + lsb = 13.44; + } + return lsb; +} +static const char *const TAG = "max9611"; +static const uint8_t SETUP_DELAY = 4; // Wait 2 integration periods. +static const float VOUT_LSB = 14.0 / 1000.0; // 14mV/LSB +static const float TEMP_LSB = 0.48; // 0.48C/LSB +static const float MICRO_VOLTS_PER_VOLT = 1000000.0; +void MAX9611Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up max9611..."); + // Perform dummy-read + uint8_t value; + this->read(&value, 1); + // Configuration Stage. + // First send an integration request with the specified gain + const uint8_t setup_dat[] = {CONTROL_REGISTER_1_ADRR, static_cast(gain_)}; + // Then send a request that samples all channels as fast as possible, using the last provided gain + const uint8_t fast_mode_dat[] = {CONTROL_REGISTER_1_ADRR, MAX9611Multiplexer::MAX9611_MULTIPLEXER_FAST_MODE}; + + if (this->write(reinterpret_cast(&setup_dat), sizeof(setup_dat)) != ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Failed to setup Max9611 during GAIN SET"); + return; + } + delay(SETUP_DELAY); + if (this->write(reinterpret_cast(&fast_mode_dat), sizeof(fast_mode_dat)) != ErrorCode::ERROR_OK) { + ESP_LOGE(TAG, "Failed to setup Max9611 during FAST MODE SET"); + return; + } +} +void MAX9611Component::dump_config() { + ESP_LOGCONFIG(TAG, "Dump Config max9611..."); + ESP_LOGCONFIG(TAG, " CSA Gain Register: %x", gain_); + LOG_I2C_DEVICE(this); +} +void MAX9611Component::update() { + // Setup read from 0x0 register base + const uint8_t reg_base = 0x0; + const ErrorCode write_result = this->write(®_base, 1); + // Just read the entire register map in a bulk read, faster than individually querying register. + const ErrorCode read_result = this->read(register_map_, sizeof(register_map_)); + if (write_result != ErrorCode::ERROR_OK || read_result != ErrorCode::ERROR_OK) { + ESP_LOGW(TAG, "MAX9611 Update FAILED!"); + return; + } + uint16_t csa_register = ((register_map_[CSA_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[CSA_DATA_BYTE_LSB_ADRR])) >> 4; + uint16_t rs_register = ((register_map_[RS_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[RS_DATA_BYTE_LSB_ADRR])) >> 4; + uint16_t t_register = ((register_map_[TEMP_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[TEMP_DATA_BYTE_LSB_ADRR])) >> 7; + float voltage = rs_register * VOUT_LSB; + float shunt_voltage = (csa_register * gain_to_lsb(gain_)) / MICRO_VOLTS_PER_VOLT; + float temp = signextend(t_register) * TEMP_LSB; + float amps = shunt_voltage / current_resistor_; + float watts = amps * voltage; + + if (voltage_sensor_ != nullptr) { + voltage_sensor_->publish_state(voltage); + } + if (current_sensor_ != nullptr) { + current_sensor_->publish_state(amps); + } + if (watt_sensor_ != nullptr) { + watt_sensor_->publish_state(watts); + } + if (temperature_sensor_ != nullptr) { + temperature_sensor_->publish_state(temp); + } + + ESP_LOGD(TAG, "V: %f, A: %f, W: %f, Deg C: %f", voltage, amps, watts, temp); +} +} // namespace max9611 +} // namespace esphome diff --git a/esphome/components/max9611/max9611.h b/esphome/components/max9611/max9611.h new file mode 100644 index 000000000000..017f56b1a7ff --- /dev/null +++ b/esphome/components/max9611/max9611.h @@ -0,0 +1,62 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/hal.h" + +namespace esphome { +namespace max9611 { + +enum MAX9611Multiplexer { + MAX9611_MULTIPLEXER_CSA_GAIN1 = 0b000, + MAX9611_MULTIPLEXER_CSA_GAIN4 = 0b001, + MAX9611_MULTIPLEXER_CSA_GAIN8 = 0b010, + MAX9611_MULTIPLEXER_RS = 0b011, + MAX9611_MULTIPLEXER_OUT = 0b100, + MAX9611_MULTIPLEXER_SET = 0b101, + MAX9611_MULTIPLEXER_TEMP = 0b110, + MAX9611_MULTIPLEXER_FAST_MODE = 0b111, +}; + +enum MAX9611RegisterMap { + CSA_DATA_BYTE_MSB_ADRR = 0x00, + CSA_DATA_BYTE_LSB_ADRR = 0x01, + RS_DATA_BYTE_MSB_ADRR = 0x02, + RS_DATA_BYTE_LSB_ADRR = 0x03, + OUT_DATA_BYTE_MSB_ADRR = 0x04, // Unused Op-Amp + OUT_DATA_BYTE_LSB_ADRR = 0x05, // Unused Op-Amp + SET_DATA_BYTE_MSB_ADRR = 0x06, // Unused Op-Amp + SET_DATA_BYTE_LSB_ADRR = 0x07, // Unused Op-Amp + TEMP_DATA_BYTE_MSB_ADRR = 0x08, + TEMP_DATA_BYTE_LSB_ADRR = 0x09, + CONTROL_REGISTER_1_ADRR = 0x0A, + CONTROL_REGISTER_2_ADRR = 0x0B, +}; + +class MAX9611Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override { return setup_priority::DATA; } + void update() override; + void set_voltage_sensor(sensor::Sensor *vs) { voltage_sensor_ = vs; } + void set_current_sensor(sensor::Sensor *cs) { current_sensor_ = cs; } + void set_watt_sensor(sensor::Sensor *ws) { watt_sensor_ = ws; } + void set_temp_sensor(sensor::Sensor *ts) { temperature_sensor_ = ts; } + + void set_current_resistor(float r) { current_resistor_ = r; } + void set_gain(MAX9611Multiplexer g) { gain_ = g; } + + protected: + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *watt_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + float current_resistor_; + uint8_t register_map_[0x0C]; + MAX9611Multiplexer gain_; +}; + +} // namespace max9611 +} // namespace esphome diff --git a/esphome/components/max9611/sensor.py b/esphome/components/max9611/sensor.py new file mode 100644 index 000000000000..246d332a866d --- /dev/null +++ b/esphome/components/max9611/sensor.py @@ -0,0 +1,92 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_SHUNT_RESISTANCE, + CONF_GAIN, + CONF_VOLTAGE, + CONF_CURRENT, + CONF_POWER, + CONF_TEMPERATURE, + UNIT_VOLT, + UNIT_AMPERE, + UNIT_WATT, + UNIT_CELSIUS, + DEVICE_CLASS_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, +) + +DEPENDENCIES = ["i2c"] +max9611_ns = cg.esphome_ns.namespace("max9611") +max9611Gain = max9611_ns.enum("MAX9611Multiplexer") +MAX9611_GAIN = { + "8X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN8, + "4X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN4, + "1X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN1, +} +MAX9611Component = max9611_ns.class_( + "MAX9611Component", cg.PollingComponent, i2c.I2CDevice +) +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MAX9611Component), + cv.Required(CONF_SHUNT_RESISTANCE): cv.resistance, + cv.Required(CONF_GAIN): cv.enum(MAX9611_GAIN, upper=True), + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x70)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + cg.add(var.set_current_resistor(config[CONF_SHUNT_RESISTANCE])) + cg.add(var.set_gain(config[CONF_GAIN])) + if CONF_VOLTAGE in config: + conf = config[CONF_VOLTAGE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_voltage_sensor(sens)) + if CONF_CURRENT in config: + conf = config[CONF_CURRENT] + sens = await sensor.new_sensor(conf) + cg.add(var.set_current_sensor(sens)) + if CONF_POWER in config: + conf = config[CONF_POWER] + sens = await sensor.new_sensor(conf) + cg.add(var.set_watt_sensor(sens)) + if CONF_TEMPERATURE in config: + conf = config[CONF_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_temp_sensor(sens)) diff --git a/esphome/components/mcp23016/mcp23016.cpp b/esphome/components/mcp23016/mcp23016.cpp index a8df4e1745c8..9787da6faafb 100644 --- a/esphome/components/mcp23016/mcp23016.cpp +++ b/esphome/components/mcp23016/mcp23016.cpp @@ -62,10 +62,11 @@ void MCP23016::update_reg_(uint8_t pin, bool pin_value, uint8_t reg_addr) { this->read_reg_(reg_addr, ®_value); } - if (pin_value) + if (pin_value) { reg_value |= 1 << bit; - else + } else { reg_value &= ~(1 << bit); + } this->write_reg_(reg_addr, reg_value); diff --git a/esphome/components/mcp23x08_base/mcp23x08_base.cpp b/esphome/components/mcp23x08_base/mcp23x08_base.cpp index 2137b369211e..0c20e902c4a1 100644 --- a/esphome/components/mcp23x08_base/mcp23x08_base.cpp +++ b/esphome/components/mcp23x08_base/mcp23x08_base.cpp @@ -67,10 +67,11 @@ void MCP23X08Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { this->read_reg(reg_addr, ®_value); } - if (pin_value) + if (pin_value) { reg_value |= 1 << bit; - else + } else { reg_value &= ~(1 << bit); + } this->write_reg(reg_addr, reg_value); diff --git a/esphome/components/mcp23x17_base/mcp23x17_base.cpp b/esphome/components/mcp23x17_base/mcp23x17_base.cpp index 744f2fbe9c46..99064f8880b7 100644 --- a/esphome/components/mcp23x17_base/mcp23x17_base.cpp +++ b/esphome/components/mcp23x17_base/mcp23x17_base.cpp @@ -70,10 +70,11 @@ void MCP23X17Base::update_reg(uint8_t pin, bool pin_value, uint8_t reg_addr) { this->read_reg(reg_addr, ®_value); } - if (pin_value) + if (pin_value) { reg_value |= 1 << bit; - else + } else { reg_value &= ~(1 << bit); + } this->write_reg(reg_addr, reg_value); diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index 0528a87d0e2f..8d4bac1fd21c 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -6,6 +6,7 @@ namespace esphome { namespace md5 { +#ifdef USE_ARDUINO void MD5Digest::init() { memset(this->digest_, 0, 16); MD5Init(&this->ctx_); @@ -14,6 +15,18 @@ void MD5Digest::init() { void MD5Digest::add(const uint8_t *data, size_t len) { MD5Update(&this->ctx_, data, len); } void MD5Digest::calculate() { MD5Final(this->digest_, &this->ctx_); } +#endif // USE_ARDUINO + +#ifdef USE_ESP_IDF +void MD5Digest::init() { + memset(this->digest_, 0, 16); + esp_rom_md5_init(&this->ctx_); +} + +void MD5Digest::add(const uint8_t *data, size_t len) { esp_rom_md5_update(&this->ctx_, data, len); } + +void MD5Digest::calculate() { esp_rom_md5_final(this->digest_, &this->ctx_); } +#endif // USE_ESP_IDF void MD5Digest::get_bytes(uint8_t *output) { memcpy(output, this->digest_, 16); } diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index 1c15c9e57d41..a9628c924272 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -3,8 +3,8 @@ #include "esphome/core/defines.h" #ifdef USE_ESP_IDF -#include "esp32/rom/md5_hash.h" -#define MD5_CTX_TYPE MD5Context +#include "esp_rom_md5.h" +#define MD5_CTX_TYPE md5_context_t #endif #if defined(USE_ARDUINO) && defined(USE_ESP32) diff --git a/esphome/components/mdns/__init__.py b/esphome/components/mdns/__init__.py index b95469d9da3e..b5be153d5aa1 100644 --- a/esphome/components/mdns/__init__.py +++ b/esphome/components/mdns/__init__.py @@ -1,7 +1,7 @@ from esphome.const import CONF_ID import esphome.codegen as cg import esphome.config_validation as cv -from esphome.core import CORE +from esphome.core import CORE, coroutine_with_priority CODEOWNERS = ["@esphome/core"] DEPENDENCIES = ["network"] @@ -29,6 +29,7 @@ def _remove_id_if_disabled(value): ) +@coroutine_with_priority(55.0) async def to_code(config): if CORE.using_arduino: if CORE.is_esp32: diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp index 915c640b065d..168eaf3ae1b5 100644 --- a/esphome/components/mdns/mdns_component.cpp +++ b/esphome/components/mdns/mdns_component.cpp @@ -16,8 +16,8 @@ namespace mdns { static const char *const TAG = "mdns"; -#ifndef WEBSERVER_PORT -#define WEBSERVER_PORT 80 // NOLINT +#ifndef USE_WEBSERVER_PORT +#define USE_WEBSERVER_PORT 80 // NOLINT #endif void MDNSComponent::compile_records_() { @@ -63,7 +63,7 @@ void MDNSComponent::compile_records_() { MDNSService service{}; service.service_type = "_prometheus-http"; service.proto = "_tcp"; - service.port = WEBSERVER_PORT; + service.port = USE_WEBSERVER_PORT; this->services_.push_back(service); } #endif @@ -74,7 +74,7 @@ void MDNSComponent::compile_records_() { MDNSService service{}; service.service_type = "_http"; service.proto = "_tcp"; - service.port = WEBSERVER_PORT; + service.port = USE_WEBSERVER_PORT; service.txt_records.push_back({"version", ESPHOME_VERSION}); this->services_.push_back(service); } diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp index ff305f907ac5..b91f60cb6b43 100644 --- a/esphome/components/mdns/mdns_esp8266.cpp +++ b/esphome/components/mdns/mdns_esp8266.cpp @@ -20,11 +20,11 @@ void MDNSComponent::setup() { // part of the wire protocol to have an underscore, and for example ESP-IDF // expects the underscore to be there, the ESP8266 implementation always adds // the underscore itself. - auto proto = service.proto.c_str(); + auto *proto = service.proto.c_str(); while (*proto == '_') { proto++; } - auto service_type = service.service_type.c_str(); + auto *service_type = service.service_type.c_str(); while (*service_type == '_') { service_type++; } diff --git a/esphome/components/midea/air_conditioner.cpp b/esphome/components/midea/air_conditioner.cpp index dd48f640a249..1ad5ade53d43 100644 --- a/esphome/components/midea/air_conditioner.cpp +++ b/esphome/components/midea/air_conditioner.cpp @@ -59,14 +59,16 @@ void AirConditioner::control(const ClimateCall &call) { ctrl.swingMode = Converters::to_midea_swing_mode(call.get_swing_mode().value()); if (call.get_mode().has_value()) ctrl.mode = Converters::to_midea_mode(call.get_mode().value()); - if (call.get_preset().has_value()) + if (call.get_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_preset().value()); - else if (call.get_custom_preset().has_value()) + } else if (call.get_custom_preset().has_value()) { ctrl.preset = Converters::to_midea_preset(call.get_custom_preset().value()); - if (call.get_fan_mode().has_value()) + } + if (call.get_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_fan_mode().value()); - else if (call.get_custom_fan_mode().has_value()) + } else if (call.get_custom_fan_mode().has_value()) { ctrl.fanMode = Converters::to_midea_fan_mode(call.get_custom_fan_mode().value()); + } this->base_.control(ctrl); } diff --git a/esphome/components/midea_ir/midea_ir.cpp b/esphome/components/midea_ir/midea_ir.cpp index 5e507cbbb0ff..aa5e2b46f5c5 100644 --- a/esphome/components/midea_ir/midea_ir.cpp +++ b/esphome/components/midea_ir/midea_ir.cpp @@ -172,10 +172,11 @@ bool MideaIR::on_midea_(const MideaData &data) { this->target_temperature = status.get_temp(); this->mode = status.get_mode(); this->fan_mode = status.get_fan_mode(); - if (status.get_sleep_preset()) + if (status.get_sleep_preset()) { this->preset = climate::CLIMATE_PRESET_SLEEP; - else if (this->preset == climate::CLIMATE_PRESET_SLEEP) + } else if (this->preset == climate::CLIMATE_PRESET_SLEEP) { this->preset = climate::CLIMATE_PRESET_NONE; + } this->publish_state(); return true; } diff --git a/esphome/components/mitsubishi/mitsubishi.cpp b/esphome/components/mitsubishi/mitsubishi.cpp index 43397770d1b2..99ca6d1cc5ef 100644 --- a/esphome/components/mitsubishi/mitsubishi.cpp +++ b/esphome/components/mitsubishi/mitsubishi.cpp @@ -54,7 +54,7 @@ void MitsubishiClimate::transmit_state() { } auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); // repeat twice @@ -63,12 +63,13 @@ void MitsubishiClimate::transmit_state() { data->mark(MITSUBISHI_HEADER_MARK); data->space(MITSUBISHI_HEADER_SPACE); // Data - for (uint8_t i : remote_state) + for (uint8_t i : remote_state) { for (uint8_t j = 0; j < 8; j++) { data->mark(MITSUBISHI_BIT_MARK); bool bit = i & (1 << j); data->space(bit ? MITSUBISHI_ONE_SPACE : MITSUBISHI_ZERO_SPACE); } + } // Footer if (r == 0) { data->mark(MITSUBISHI_BIT_MARK); diff --git a/esphome/components/mlx90393/__init__.py b/esphome/components/mlx90393/__init__.py new file mode 100644 index 000000000000..fc92f02120c0 --- /dev/null +++ b/esphome/components/mlx90393/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@functionpointer"] diff --git a/esphome/components/mlx90393/sensor.py b/esphome/components/mlx90393/sensor.py new file mode 100644 index 000000000000..92ba30bea324 --- /dev/null +++ b/esphome/components/mlx90393/sensor.py @@ -0,0 +1,135 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + UNIT_MICROTESLA, + UNIT_CELSIUS, + STATE_CLASS_MEASUREMENT, + ICON_MAGNET, + ICON_THERMOMETER, + CONF_GAIN, + CONF_RESOLUTION, + CONF_OVERSAMPLING, + CONF_FILTER, + CONF_TEMPERATURE, +) +from esphome import pins + +CODEOWNERS = ["@functionpointer"] +DEPENDENCIES = ["i2c"] + +mlx90393_ns = cg.esphome_ns.namespace("mlx90393") + +MLX90393Component = mlx90393_ns.class_( + "MLX90393Cls", cg.PollingComponent, i2c.I2CDevice +) + +GAIN = { + "1X": 7, + "1_33X": 6, + "1_67X": 5, + "2X": 4, + "2_5X": 3, + "3X": 2, + "4X": 1, + "5X": 0, +} + +RESOLUTION = { + "16BIT": 0, + "17BIT": 1, + "18BIT": 2, + "19BIT": 3, +} + +CONF_X_AXIS = "x_axis" +CONF_Y_AXIS = "y_axis" +CONF_Z_AXIS = "z_axis" +CONF_DRDY_PIN = "drdy_pin" + + +def mlx90393_axis_schema(default_resolution: str): + return sensor.sensor_schema( + unit_of_measurement=UNIT_MICROTESLA, + accuracy_decimals=0, + icon=ICON_MAGNET, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + cv.Schema( + { + cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum( + RESOLUTION, upper=True, space="_" + ) + } + ) + ) + + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(MLX90393Component), + cv.Optional(CONF_GAIN, default="2_5X"): cv.enum( + GAIN, upper=True, space="_" + ), + cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema, + cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3), + cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7), + cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"), + cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"), + cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + icon=ICON_THERMOMETER, + state_class=STATE_CLASS_MEASUREMENT, + ).extend( + cv.Schema( + { + cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range( + min=0, max=3 + ), + } + ) + ), + }, + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x0C)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if CONF_DRDY_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN]) + cg.add(var.set_drdy_pin(pin)) + cg.add(var.set_gain(GAIN[config[CONF_GAIN]])) + cg.add(var.set_oversampling(config[CONF_OVERSAMPLING])) + cg.add(var.set_filter(config[CONF_FILTER])) + + if CONF_X_AXIS in config: + sens = await sensor.new_sensor(config[CONF_X_AXIS]) + cg.add(var.set_x_sensor(sens)) + cg.add(var.set_resolution(0, RESOLUTION[config[CONF_X_AXIS][CONF_RESOLUTION]])) + if CONF_Y_AXIS in config: + sens = await sensor.new_sensor(config[CONF_Y_AXIS]) + cg.add(var.set_y_sensor(sens)) + cg.add(var.set_resolution(1, RESOLUTION[config[CONF_Y_AXIS][CONF_RESOLUTION]])) + if CONF_Z_AXIS in config: + sens = await sensor.new_sensor(config[CONF_Z_AXIS]) + cg.add(var.set_z_sensor(sens)) + cg.add(var.set_resolution(2, RESOLUTION[config[CONF_Z_AXIS][CONF_RESOLUTION]])) + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_t_sensor(sens)) + cg.add(var.set_t_oversampling(config[CONF_TEMPERATURE][CONF_OVERSAMPLING])) + if CONF_DRDY_PIN in config: + pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN]) + cg.add(var.set_drdy_gpio(pin)) + + cg.add_library("functionpointer/arduino-MLX90393", "1.0.0") diff --git a/esphome/components/mlx90393/sensor_mlx90393.cpp b/esphome/components/mlx90393/sensor_mlx90393.cpp new file mode 100644 index 000000000000..d4431a733434 --- /dev/null +++ b/esphome/components/mlx90393/sensor_mlx90393.cpp @@ -0,0 +1,91 @@ +#include "sensor_mlx90393.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace mlx90393 { + +static const char *const TAG = "mlx90393"; + +bool MLX90393Cls::transceive(const uint8_t *request, size_t request_size, uint8_t *response, size_t response_size) { + i2c::ErrorCode e = this->write(request, request_size); + if (e != i2c::ErrorCode::ERROR_OK) { + return false; + } + e = this->read(response, response_size); + return e == i2c::ErrorCode::ERROR_OK; +} + +bool MLX90393Cls::has_drdy_pin() { return this->drdy_pin_ != nullptr; } + +bool MLX90393Cls::read_drdy_pin() { + if (this->drdy_pin_ == nullptr) { + return false; + } else { + return this->drdy_pin_->digital_read(); + } +} +void MLX90393Cls::sleep_millis(uint32_t millis) { delay(millis); } +void MLX90393Cls::sleep_micros(uint32_t micros) { delayMicroseconds(micros); } + +void MLX90393Cls::setup() { + ESP_LOGCONFIG(TAG, "Setting up MLX90393..."); + // note the two arguments A0 and A1 which are used to construct an i2c address + // we can hard-code these because we never actually use the constructed address + // see the transceive function above, which uses the address from I2CComponent + this->mlx_.begin_with_hal(this, 0, 0); + + this->mlx_.setGainSel(this->gain_); + + this->mlx_.setResolution(this->resolutions_[0], this->resolutions_[1], this->resolutions_[2]); + + this->mlx_.setOverSampling(this->oversampling_); + + this->mlx_.setDigitalFiltering(this->filter_); + + this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_); +} + +void MLX90393Cls::dump_config() { + ESP_LOGCONFIG(TAG, "MLX90393:"); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with MLX90393 failed!"); + return; + } + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "X Axis", this->x_sensor_); + LOG_SENSOR(" ", "Y Axis", this->y_sensor_); + LOG_SENSOR(" ", "Z Axis", this->z_sensor_); + LOG_SENSOR(" ", "Temperature", this->t_sensor_); +} + +float MLX90393Cls::get_setup_priority() const { return setup_priority::DATA; } + +void MLX90393Cls::update() { + MLX90393::txyz data; + + if (this->mlx_.readData(data) == MLX90393::STATUS_OK) { + ESP_LOGD(TAG, "received %f %f %f", data.x, data.y, data.z); + if (this->x_sensor_ != nullptr) { + this->x_sensor_->publish_state(data.x); + } + if (this->y_sensor_ != nullptr) { + this->y_sensor_->publish_state(data.y); + } + if (this->z_sensor_ != nullptr) { + this->z_sensor_->publish_state(data.z); + } + if (this->t_sensor_ != nullptr) { + this->t_sensor_->publish_state(data.t); + } + this->status_clear_warning(); + } else { + ESP_LOGE(TAG, "failed to read data"); + this->status_set_warning(); + } +} + +} // namespace mlx90393 +} // namespace esphome diff --git a/esphome/components/mlx90393/sensor_mlx90393.h b/esphome/components/mlx90393/sensor_mlx90393.h new file mode 100644 index 000000000000..fc33ad1aa83f --- /dev/null +++ b/esphome/components/mlx90393/sensor_mlx90393.h @@ -0,0 +1,59 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/core/hal.h" +#include +#include + +namespace esphome { +namespace mlx90393 { + +class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90393Hal { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_drdy_gpio(GPIOPin *pin) { drdy_pin_ = pin; } + + void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; } + void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } + void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } + void set_t_sensor(sensor::Sensor *t_sensor) { t_sensor_ = t_sensor; } + + void set_oversampling(uint8_t osr) { oversampling_ = osr; } + void set_t_oversampling(uint8_t osr2) { temperature_oversampling_ = osr2; } + void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; } + void set_filter(uint8_t filter) { filter_ = filter; } + void set_gain(uint8_t gain_sel) { gain_ = gain_sel; } + + // overrides for MLX library + + // disable lint because it keeps suggesting const uint8_t *response. + // this->read() writes data into response, so it can't be const + bool transceive(const uint8_t *request, size_t request_size, uint8_t *response, + size_t response_size) override; // NOLINT + bool has_drdy_pin() override; + bool read_drdy_pin() override; + void sleep_millis(uint32_t millis) override; + void sleep_micros(uint32_t micros) override; + + protected: + MLX90393 mlx_; + sensor::Sensor *x_sensor_{nullptr}; + sensor::Sensor *y_sensor_{nullptr}; + sensor::Sensor *z_sensor_{nullptr}; + sensor::Sensor *t_sensor_{nullptr}; + uint8_t gain_; + uint8_t oversampling_; + uint8_t temperature_oversampling_ = 0; + uint8_t filter_; + uint8_t resolutions_[3] = {0}; + GPIOPin *drdy_pin_ = nullptr; +}; + +} // namespace mlx90393 +} // namespace esphome diff --git a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h index 21afbc70537a..3a8e175c260f 100644 --- a/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h +++ b/esphome/components/modbus_controller/binary_sensor/modbus_binarysensor.h @@ -10,8 +10,7 @@ namespace modbus_controller { class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, public SensorItem { public: ModbusBinarySensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, - uint8_t skip_updates, bool force_new_range) - : Component(), binary_sensor::BinarySensor() { + uint8_t skip_updates, bool force_new_range) { this->register_type = register_type; this->start_address = start_address; this->offset = offset; @@ -20,10 +19,11 @@ class ModbusBinarySensor : public Component, public binary_sensor::BinarySensor, this->skip_updates = skip_updates; this->force_new_range = force_new_range; - if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) { this->register_count = offset + 1; - else + } else { this->register_count = 1; + } } void parse_and_publish(const std::vector &data) override; diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index d07a6d533548..64046b9578fa 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -99,7 +99,7 @@ void ModbusController::on_register_data(ModbusRegisterType register_type, uint16 // loop through all sensors with the same start address auto sensors = find_sensors_(register_type, start_address); - for (auto sensor : sensors) { + for (auto *sensor : sensors) { sensor->parse_and_publish(data); } } @@ -110,7 +110,8 @@ void ModbusController::queue_command(const ModbusCommandItem &command) { for (auto &item : command_queue_) { if (item->register_address == command.register_address && item->register_count == command.register_count && item->register_type == command.register_type && item->function_code == command.function_code) { - ESP_LOGW(TAG, "Duplicate modbus command found"); + ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u", + static_cast(command.register_type), command.register_address, command.register_count); // update the payload of the queued command // replaces a previous command item->payload = command.payload; @@ -360,8 +361,9 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusControl modbusdevice->on_write_register_response(cmd.register_type, start_address, data); }; for (auto v : values) { - cmd.payload.push_back((v / 256) & 0xFF); - cmd.payload.push_back(v & 0xFF); + auto decoded_value = decode_value(v); + cmd.payload.push_back(decoded_value[0]); + cmd.payload.push_back(decoded_value[1]); } return cmd; } @@ -416,7 +418,7 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusControlle } ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, - int16_t value) { + uint16_t value) { ModbusCommandItem cmd; cmd.modbusdevice = modbusdevice; cmd.register_type = ModbusRegisterType::HOLDING; @@ -427,8 +429,10 @@ ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusControlle const std::vector &data) { modbusdevice->on_write_register_response(cmd.register_type, start_address, data); }; - cmd.payload.push_back((value / 256) & 0xFF); - cmd.payload.push_back((value % 256) & 0xFF); + + auto decoded_value = decode_value(value); + cmd.payload.push_back(decoded_value[0]); + cmd.payload.push_back(decoded_value[1]); return cmd; } @@ -440,7 +444,7 @@ ModbusCommandItem ModbusCommandItem::create_custom_command( cmd.modbusdevice = modbusdevice; cmd.function_code = ModbusFunctionCode::CUSTOM; if (handler == nullptr) { - cmd.on_data_func = [](ModbusRegisterType, uint16_t, const std::vector &data) { + cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { ESP_LOGI(TAG, "Custom Command sent"); }; } else { @@ -463,84 +467,70 @@ bool ModbusCommandItem::send() { return true; } -std::vector float_to_payload(float value, SensorValueType value_type) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - - std::vector data; - int32_t val; - +void number_to_payload(std::vector &data, int64_t value, SensorValueType value_type) { switch (value_type) { case SensorValueType::U_WORD: case SensorValueType::S_WORD: - // cast truncates the float do some rounding here - data.push_back(lroundf(value) & 0xFFFF); + data.push_back(value & 0xFFFF); break; case SensorValueType::U_DWORD: case SensorValueType::S_DWORD: - val = lroundf(value); - data.push_back((val & 0xFFFF0000) >> 16); - data.push_back(val & 0xFFFF); + case SensorValueType::FP32: + case SensorValueType::FP32_R: + data.push_back((value & 0xFFFF0000) >> 16); + data.push_back(value & 0xFFFF); break; case SensorValueType::U_DWORD_R: case SensorValueType::S_DWORD_R: - val = lroundf(value); - data.push_back(val & 0xFFFF); - data.push_back((val & 0xFFFF0000) >> 16); + data.push_back(value & 0xFFFF); + data.push_back((value & 0xFFFF0000) >> 16); break; - case SensorValueType::FP32: - raw_to_float.float_value = value; - data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16); - data.push_back(raw_to_float.raw & 0xFFFF); + case SensorValueType::U_QWORD: + case SensorValueType::S_QWORD: + data.push_back((value & 0xFFFF000000000000) >> 48); + data.push_back((value & 0xFFFF00000000) >> 32); + data.push_back((value & 0xFFFF0000) >> 16); + data.push_back(value & 0xFFFF); break; - case SensorValueType::FP32_R: - raw_to_float.float_value = value; - data.push_back(raw_to_float.raw & 0xFFFF); - data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16); + case SensorValueType::U_QWORD_R: + case SensorValueType::S_QWORD_R: + data.push_back(value & 0xFFFF); + data.push_back((value & 0xFFFF0000) >> 16); + data.push_back((value & 0xFFFF00000000) >> 32); + data.push_back((value & 0xFFFF000000000000) >> 48); break; default: - ESP_LOGE(TAG, "Invalid data type for modbus float to payload conversation"); + ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d", + static_cast(value_type)); break; } - return data; } -float payload_to_float(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, - uint32_t bitmask) { - union { - float float_value; - uint32_t raw; - } raw_to_float; - +int64_t payload_to_number(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask) { int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits - float result = NAN; switch (sensor_value_type) { case SensorValueType::U_WORD: value = mask_and_shift_by_rightbit(get_data(data, offset), bitmask); // default is 0xFFFF ; - result = static_cast(value); break; case SensorValueType::U_DWORD: + case SensorValueType::FP32: value = get_data(data, offset); value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); - result = static_cast(value); break; case SensorValueType::U_DWORD_R: + case SensorValueType::FP32_R: value = get_data(data, offset); value = static_cast(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16; value = mask_and_shift_by_rightbit((uint32_t) value, bitmask); - result = static_cast(value); break; case SensorValueType::S_WORD: value = mask_and_shift_by_rightbit(get_data(data, offset), bitmask); // default is 0xFFFF ; - result = static_cast(value); break; case SensorValueType::S_DWORD: value = mask_and_shift_by_rightbit(get_data(data, offset), bitmask); - result = static_cast(value); break; case SensorValueType::S_DWORD_R: { value = get_data(data, offset); @@ -549,18 +539,14 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ uint32_t sign_bit = (value & 0x8000) << 16; value = mask_and_shift_by_rightbit( static_cast(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask); - result = static_cast(value); } break; case SensorValueType::U_QWORD: // Ignore bitmask for U_QWORD value = get_data(data, offset); - result = static_cast(value); break; - case SensorValueType::S_QWORD: // Ignore bitmask for S_QWORD value = get_data(data, offset); - result = static_cast(value); break; case SensorValueType::U_QWORD_R: // Ignore bitmask for U_QWORD @@ -568,32 +554,16 @@ float payload_to_float(const std::vector &data, SensorValueType sensor_ value = static_cast(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 | static_cast(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 | static_cast(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16; - result = static_cast(value); break; - case SensorValueType::S_QWORD_R: // Ignore bitmask for S_QWORD value = get_data(data, offset); - result = static_cast(value); - break; - case SensorValueType::FP32: - raw_to_float.raw = get_data(data, offset); - ESP_LOGD(TAG, "FP32 = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); - result = raw_to_float.float_value; break; - case SensorValueType::FP32_R: { - auto tmp = get_data(data, offset); - raw_to_float.raw = static_cast(tmp & 0xFFFF) << 16 | (tmp & 0xFFFF0000) >> 16; - ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value); - result = raw_to_float.float_value; - } break; case SensorValueType::RAW: - result = NAN; - break; default: break; } - return result; + return value; } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index 6dbabac71ed6..09395f29b3c5 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -195,7 +195,7 @@ inline bool coil_from_vector(int coil, const std::vector &data) { */ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { auto result = (mask & data); - if (result == 0) { + if (result == 0 || mask == 0xFFFFFFFF) { return result; } for (size_t pos = 0; pos < sizeof(N) << 3; pos++) { @@ -205,22 +205,23 @@ template N mask_and_shift_by_rightbit(N data, uint32_t mask) { return 0; } -/** convert float value to vector suitable for sending - * @param value float value to cconvert +/** Convert float value to vector suitable for sending + * @param data target for payload + * @param value float value to convert * @param value_type defines if 16/32 or FP32 is used * @return vector containing the modbus register words in correct order */ -std::vector float_to_payload(float value, SensorValueType value_type); +void number_to_payload(std::vector &data, int64_t value, SensorValueType value_type); -/** convert vector response payload to float - * @param value float value to cconvert +/** Convert vector response payload to number. + * @param data payload with the data to convert * @param sensor_value_type defines if 16/32/64 bits or FP32 is used * @param offset offset to the data in data * @param bitmask bitmask used for masking and shifting - * @return float version of the input + * @return 64-bit number of the payload */ -float payload_to_float(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, - uint32_t bitmask); +int64_t payload_to_number(const std::vector &data, SensorValueType sensor_value_type, uint8_t offset, + uint32_t bitmask); class ModbusController; @@ -230,10 +231,11 @@ class SensorItem { void set_custom_data(const std::vector &data) { custom_data = data; } size_t virtual get_register_size() const { - if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) + if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT) { return 1; - else // if CONF_RESPONSE_BYTES is used override the default + } else { // if CONF_RESPONSE_BYTES is used override the default return response_bytes > 0 ? response_bytes : register_count * 2; + } } // Override register size for modbus devices not using 1 register for one dword void set_register_size(uint8_t register_size) { response_bytes = register_size; } @@ -347,11 +349,11 @@ class ModbusCommandItem { * @param modbusdevice pointer to the device to execute the command * @param start_address modbus address of the first register to read * @param register_count number of registers to read - * @param values uint16_t array to be written to the registers + * @param value uint16_t single register value to write * @return ModbusCommandItem with the prepared command */ static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address, - int16_t value); + uint16_t value); /** Create modbus write single registers command * Function 05 (05hex) Write Single Coil * @param modbusdevice pointer to the device to execute the command @@ -393,7 +395,7 @@ class ModbusCommandItem { class ModbusController : public PollingComponent, public modbus::ModbusDevice { public: - ModbusController(uint16_t throttle = 0) : modbus::ModbusDevice(), command_throttle_(throttle){}; + ModbusController(uint16_t throttle = 0) : command_throttle_(throttle){}; void dump_config() override; void loop() override; void setup() override; @@ -445,13 +447,36 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { uint16_t command_throttle_; }; -/** convert vector response payload to float - * @param value float value to cconvert +/** Convert vector response payload to float. + * @param data payload with data * @param item SensorItem object - * @return float version of the input + * @return float value of data */ inline float payload_to_float(const std::vector &data, const SensorItem &item) { - return payload_to_float(data, item.sensor_value_type, item.offset, item.bitmask); + int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask); + + float float_value; + if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) { + float_value = bit_cast(static_cast(number)); + } else { + float_value = static_cast(number); + } + + return float_value; +} + +inline std::vector float_to_payload(float value, SensorValueType value_type) { + int64_t val; + + if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) { + val = bit_cast(value); + } else { + val = llroundf(value); + } + + std::vector data; + number_to_payload(data, val, value_type); + return data; } } // namespace modbus_controller diff --git a/esphome/components/modbus_controller/number/__init__.py b/esphome/components/modbus_controller/number/__init__.py index 4ad6601fee83..56ec734315a1 100644 --- a/esphome/components/modbus_controller/number/__init__.py +++ b/esphome/components/modbus_controller/number/__init__.py @@ -58,8 +58,7 @@ def validate_modbus_number(config): CONFIG_SCHEMA = cv.All( - number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema) - .extend( + number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend( { cv.GenerateID(): cv.declare_id(ModbusNumber), cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE), @@ -72,8 +71,7 @@ def validate_modbus_number(config): cv.Optional(CONF_MULTIPLY, default=1.0): cv.float_, cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean, } - ) - .extend(cv.polling_component_schema("60s")), + ), validate_min_max, validate_modbus_number, ) diff --git a/esphome/components/modbus_controller/number/modbus_number.cpp b/esphome/components/modbus_controller/number/modbus_number.cpp index 5e977f5df42c..a0e990d2727f 100644 --- a/esphome/components/modbus_controller/number/modbus_number.cpp +++ b/esphome/components/modbus_controller/number/modbus_number.cpp @@ -57,9 +57,11 @@ void ModbusNumber::control(float value) { // Create and send the write command ModbusCommandItem write_cmd; if (this->register_count == 1 && !this->use_write_multiple_) { - write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]); + // since offset is in bytes and a register is 16 bits we get the start by adding offset/2 + write_cmd = + ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]); } else { - write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset, + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, this->register_count, data); } // publish new value diff --git a/esphome/components/modbus_controller/number/modbus_number.h b/esphome/components/modbus_controller/number/modbus_number.h index c678cd00cc0c..0c525d9c89b4 100644 --- a/esphome/components/modbus_controller/number/modbus_number.h +++ b/esphome/components/modbus_controller/number/modbus_number.h @@ -12,8 +12,7 @@ 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) - : number::Number(), Component(), SensorItem() { + uint8_t skip_updates, bool force_new_range) { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; @@ -27,7 +26,6 @@ class ModbusNumber : public number::Number, public Component, public SensorItem void dump_config() override; void parse_and_publish(const std::vector &data) override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - void set_update_interval(int) {} void set_parent(ModbusController *parent) { this->parent_ = parent; } void set_write_multiply(float factor) { multiply_by_ = factor; } diff --git a/esphome/components/modbus_controller/output/modbus_output.h b/esphome/components/modbus_controller/output/modbus_output.h index 6237805d2407..f089775c0cda 100644 --- a/esphome/components/modbus_controller/output/modbus_output.h +++ b/esphome/components/modbus_controller/output/modbus_output.h @@ -9,8 +9,7 @@ namespace modbus_controller { class ModbusFloatOutput : public output::FloatOutput, public Component, public SensorItem { public: - ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) - : output::FloatOutput(), Component() { + ModbusFloatOutput(uint16_t start_address, uint8_t offset, SensorValueType value_type, int register_count) { this->register_type = ModbusRegisterType::HOLDING; this->start_address = start_address; this->offset = offset; @@ -43,7 +42,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem { public: - ModbusBinaryOutput(uint16_t start_address, uint8_t offset) : output::BinaryOutput(), Component() { + ModbusBinaryOutput(uint16_t start_address, uint8_t offset) { this->register_type = ModbusRegisterType::COIL; this->start_address = start_address; this->bitmask = bitmask; diff --git a/esphome/components/modbus_controller/select/__init__.py b/esphome/components/modbus_controller/select/__init__.py new file mode 100644 index 000000000000..7d03064fa59d --- /dev/null +++ b/esphome/components/modbus_controller/select/__init__.py @@ -0,0 +1,142 @@ +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.jsonschema import jschema_composite + +from .. import ( + SENSOR_VALUE_TYPE, + TYPE_REGISTER_MAP, + ModbusController, + SensorItem, + modbus_controller_ns, +) +from ..const import ( + CONF_FORCE_NEW_RANGE, + CONF_MODBUS_CONTROLLER_ID, + CONF_REGISTER_COUNT, + CONF_SKIP_UPDATES, + CONF_USE_WRITE_MULTIPLE, + CONF_VALUE_TYPE, + CONF_WRITE_LAMBDA, +) + +DEPENDENCIES = ["modbus_controller"] +CODEOWNERS = ["@martgras", "@stegm"] +CONF_OPTIONSMAP = "optionsmap" + +ModbusSelect = modbus_controller_ns.class_( + "ModbusSelect", cg.Component, select.Select, SensorItem +) + + +@jschema_composite +def ensure_option_map(): + def validator(value): + cv.check_not_templatable(value) + option = cv.All(cv.string_strict) + mapping = cv.All(cv.int_range(-(2**63), 2**63 - 1)) + options_map_schema = cv.Schema({option: mapping}) + value = options_map_schema(value) + + all_values = list(value.values()) + unique_values = set(value.values()) + if len(all_values) != len(unique_values): + raise cv.Invalid("Mapping values must be unique.") + + return value + + return validator + + +def register_count_value_type_min(value): + reg_count = value.get(CONF_REGISTER_COUNT) + if reg_count is not None: + value_type = value[CONF_VALUE_TYPE] + min_register_count = TYPE_REGISTER_MAP[value_type] + if min_register_count > reg_count: + raise cv.Invalid( + f"Value type {value_type} needs at least {min_register_count} registers" + ) + return value + + +INTEGER_SENSOR_VALUE_TYPE = { + key: value for key, value in SENSOR_VALUE_TYPE.items() if not key.startswith("FP") +} + +CONFIG_SCHEMA = cv.All( + select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + { + cv.GenerateID(): cv.declare_id(ModbusSelect), + cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController), + cv.Required(CONF_ADDRESS): cv.positive_int, + cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum( + INTEGER_SENSOR_VALUE_TYPE + ), + cv.Optional(CONF_REGISTER_COUNT): cv.positive_int, + cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int, + 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_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda, + }, + ), + register_count_value_type_min, +) + + +async def to_code(config): + value_type = config[CONF_VALUE_TYPE] + reg_count = config.get(CONF_REGISTER_COUNT) + if reg_count is None: + reg_count = TYPE_REGISTER_MAP[value_type] + + options_map = config[CONF_OPTIONSMAP] + + var = cg.new_Pvariable( + config[CONF_ID], + value_type, + config[CONF_ADDRESS], + reg_count, + config[CONF_SKIP_UPDATES], + config[CONF_FORCE_NEW_RANGE], + list(options_map.values()), + ) + + await cg.register_component(var, config) + await select.register_select(var, config, options=list(options_map.keys())) + + parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID]) + 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])) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], + [ + (ModbusSelect.operator("const_ptr"), "item"), + (cg.int64, "x"), + ( + cg.std_vector.template(cg.uint8).operator("const").operator("ref"), + "data", + ), + ], + return_type=cg.optional.template(cg.std_string), + ) + cg.add(var.set_template(template_)) + + if CONF_WRITE_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_WRITE_LAMBDA], + [ + (ModbusSelect.operator("const_ptr"), "item"), + (cg.std_string.operator("const").operator("ref"), "x"), + (cg.int64, "value"), + (cg.std_vector.template(cg.uint16).operator("ref"), "payload"), + ], + return_type=cg.optional.template(cg.int64), + ) + cg.add(var.set_write_template(template_)) diff --git a/esphome/components/modbus_controller/select/modbus_select.cpp b/esphome/components/modbus_controller/select/modbus_select.cpp new file mode 100644 index 000000000000..2c6b32f54526 --- /dev/null +++ b/esphome/components/modbus_controller/select/modbus_select.cpp @@ -0,0 +1,86 @@ +#include "modbus_select.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace modbus_controller { + +static const char *const TAG = "modbus_controller.select"; + +void ModbusSelect::dump_config() { LOG_SELECT(TAG, "Modbus Controller Select", this); } + +void ModbusSelect::parse_and_publish(const std::vector &data) { + int64_t value = payload_to_number(data, this->sensor_value_type, this->offset, this->bitmask); + + ESP_LOGD(TAG, "New select value %lld from payload", value); + + optional new_state; + + if (this->transform_func_.has_value()) { + auto val = (*this->transform_func_)(this, value, data); + if (val.has_value()) { + new_state = *val; + ESP_LOGV(TAG, "lambda returned option %s", new_state->c_str()); + } + } + + if (!new_state.has_value()) { + auto map_it = std::find(this->mapping_.cbegin(), this->mapping_.cend(), value); + + if (map_it != this->mapping_.cend()) { + size_t idx = std::distance(this->mapping_.cbegin(), map_it); + new_state = this->traits.get_options()[idx]; + ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value); + } else { + ESP_LOGE(TAG, "No option found for mapping %lld", value); + } + } + + if (new_state.has_value()) { + this->publish_state(new_state.value()); + } +} + +void ModbusSelect::control(const std::string &value) { + auto options = this->traits.get_options(); + auto opt_it = std::find(options.cbegin(), options.cend(), value); + size_t idx = std::distance(options.cbegin(), opt_it); + optional mapval = this->mapping_[idx]; + ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str()); + + std::vector data; + + if (this->write_transform_func_.has_value()) { + auto val = (*this->write_transform_func_)(this, value, *mapval, data); + if (val.has_value()) { + mapval = *val; + ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval); + } else { + ESP_LOGD(TAG, "Communication handled by write_lambda - exiting control"); + return; + } + } + + if (data.empty()) { + number_to_payload(data, *mapval, this->sensor_value_type); + } else { + ESP_LOGV(TAG, "Using payload from write lambda"); + } + + if (data.empty()) { + ESP_LOGW(TAG, "No payload was created for updating select"); + return; + } + + const uint16_t write_address = this->start_address + this->offset / 2; + ModbusCommandItem write_cmd; + if ((this->register_count == 1) && (!this->use_write_multiple_)) { + write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]); + } else { + write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data); + } + + parent_->queue_command(write_cmd); +} + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/select/modbus_select.h b/esphome/components/modbus_controller/select/modbus_select.h new file mode 100644 index 000000000000..0875194768fa --- /dev/null +++ b/esphome/components/modbus_controller/select/modbus_select.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include "esphome/components/modbus_controller/modbus_controller.h" +#include "esphome/components/select/select.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace modbus_controller { + +class ModbusSelect : public Component, public select::Select, public SensorItem { + public: + ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint8_t skip_updates, + bool force_new_range, std::vector mapping) { + this->register_type = ModbusRegisterType::HOLDING; // not configurable + this->sensor_value_type = sensor_value_type; + this->start_address = start_address; + this->offset = 0; // not configurable + this->bitmask = 0xFFFFFFFF; // not configurable + this->register_count = register_count; + this->response_bytes = 0; // not configurable + this->skip_updates = skip_updates; + this->force_new_range = force_new_range; + this->mapping_ = std::move(mapping); + } + + using transform_func_t = + std::function(ModbusSelect *const, int64_t, const std::vector &)>; + using write_transform_func_t = + std::function(ModbusSelect *const, const std::string &, int64_t, std::vector &)>; + + 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_template(transform_func_t &&f) { this->transform_func_ = f; } + void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; } + + void dump_config() override; + void parse_and_publish(const std::vector &data) override; + void control(const std::string &value) override; + + protected: + std::vector mapping_; + ModbusController *parent_; + bool use_write_multiple_{false}; + optional transform_func_; + optional write_transform_func_; +}; + +} // namespace modbus_controller +} // namespace esphome diff --git a/esphome/components/modbus_controller/sensor/modbus_sensor.h b/esphome/components/modbus_controller/sensor/modbus_sensor.h index 37ea9d0dd0b1..ababcc33d201 100644 --- a/esphome/components/modbus_controller/sensor/modbus_sensor.h +++ b/esphome/components/modbus_controller/sensor/modbus_sensor.h @@ -10,8 +10,7 @@ namespace modbus_controller { class ModbusSensor : public Component, public sensor::Sensor, public SensorItem { public: ModbusSensor(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) - : Component(), sensor::Sensor() { + 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; diff --git a/esphome/components/modbus_controller/switch/modbus_switch.h b/esphome/components/modbus_controller/switch/modbus_switch.h index 6732c01eef38..eccfc3b64a39 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.h +++ b/esphome/components/modbus_controller/switch/modbus_switch.h @@ -10,8 +10,7 @@ namespace modbus_controller { class ModbusSwitch : public Component, public switch_::Switch, public SensorItem { public: ModbusSwitch(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask, - uint8_t skip_updates, bool force_new_range) - : Component(), switch_::Switch() { + uint8_t skip_updates, bool force_new_range) { this->register_type = register_type; this->start_address = start_address; this->offset = offset; diff --git a/esphome/components/modbus_controller/text_sensor/__init__.py b/esphome/components/modbus_controller/text_sensor/__init__.py index 5cc85af5bc45..763336e1048f 100644 --- a/esphome/components/modbus_controller/text_sensor/__init__.py +++ b/esphome/components/modbus_controller/text_sensor/__init__.py @@ -40,7 +40,8 @@ } CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA) + text_sensor.text_sensor_schema() + .extend(cv.COMPONENT_SCHEMA) .extend(ModbusItemBaseSchema) .extend( { diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h index 3db4d94a4505..c52c6cd072a1 100644 --- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h @@ -12,8 +12,7 @@ enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2 }; class ModbusTextSensor : public Component, public text_sensor::TextSensor, public SensorItem { public: ModbusTextSensor(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint8_t register_count, - uint16_t response_bytes, RawEncoding encode, uint8_t skip_updates, bool force_new_range) - : Component() { + uint16_t response_bytes, RawEncoding encode, uint8_t skip_updates, bool force_new_range) { this->register_type = register_type; this->start_address = start_address; this->offset = offset; @@ -36,7 +35,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi protected: optional transform_func_{nullopt}; - protected: RawEncoding encode_; }; diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py index 755b0c685c75..901b77474dbf 100644 --- a/esphome/components/mqtt/__init__.py +++ b/esphome/components/mqtt/__init__.py @@ -11,10 +11,12 @@ CONF_BROKER, CONF_CLIENT_ID, CONF_COMMAND_TOPIC, + CONF_COMMAND_RETAIN, CONF_DISCOVERY, CONF_DISCOVERY_PREFIX, CONF_DISCOVERY_RETAIN, CONF_DISCOVERY_UNIQUE_ID_GENERATOR, + CONF_DISCOVERY_OBJECT_ID_GENERATOR, CONF_ID, CONF_KEEPALIVE, CONF_LEVEL, @@ -96,6 +98,7 @@ def validate_message_just_topic(value): MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) +MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent) MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator") MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = { @@ -103,6 +106,12 @@ def validate_message_just_topic(value): "mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, } +MQTTDiscoveryObjectIdGenerator = mqtt_ns.enum("MQTTDiscoveryObjectIdGenerator") +MQTT_DISCOVERY_OBJECT_ID_GENERATOR_OPTIONS = { + "none": MQTTDiscoveryObjectIdGenerator.MQTT_NONE_OBJECT_ID_GENERATOR, + "device_name": MQTTDiscoveryObjectIdGenerator.MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR, +} + def validate_config(value): # Populate default fields @@ -164,6 +173,9 @@ def validate_fingerprint(value): cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum( MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS ), + cv.Optional(CONF_DISCOVERY_OBJECT_ID_GENERATOR, default="none"): cv.enum( + MQTT_DISCOVERY_OBJECT_ID_GENERATOR_OPTIONS + ), cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean, cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA, cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA, @@ -243,19 +255,27 @@ async def to_code(config): discovery_retain = config[CONF_DISCOVERY_RETAIN] discovery_prefix = config[CONF_DISCOVERY_PREFIX] discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR] + discovery_object_id_generator = config[CONF_DISCOVERY_OBJECT_ID_GENERATOR] if not discovery: cg.add(var.disable_discovery()) elif discovery == "CLEAN": cg.add( var.set_discovery_info( - discovery_prefix, discovery_unique_id_generator, discovery_retain, True + discovery_prefix, + discovery_unique_id_generator, + discovery_object_id_generator, + discovery_retain, + True, ) ) elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config: cg.add( var.set_discovery_info( - discovery_prefix, discovery_unique_id_generator, discovery_retain + discovery_prefix, + discovery_unique_id_generator, + discovery_object_id_generator, + discovery_retain, ) ) @@ -392,6 +412,8 @@ async def register_mqtt_component(var, config): cg.add(var.set_custom_state_topic(config[CONF_STATE_TOPIC])) if CONF_COMMAND_TOPIC in config: cg.add(var.set_custom_command_topic(config[CONF_COMMAND_TOPIC])) + if CONF_COMMAND_RETAIN in config: + cg.add(var.set_command_retain(config[CONF_COMMAND_RETAIN])) if CONF_AVAILABILITY in config: availability = config[CONF_AVAILABILITY] if not availability: diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 0bf3b751fd06..79e6989a8f68 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -23,7 +23,7 @@ void MQTTBinarySensorComponent::dump_config() { LOG_MQTT_COMPONENT(true, false) } MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor *binary_sensor) - : MQTTComponent(), binary_sensor_(binary_sensor) { + : binary_sensor_(binary_sensor) { if (this->binary_sensor_->is_status_binary_sensor()) { this->set_custom_state_topic(mqtt::global_mqtt_client->get_availability().topic); } diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 52df63093aea..204f60fe6785 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.button"; using namespace esphome::button; -MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : MQTTComponent(), button_(button) {} +MQTTButtonComponent::MQTTButtonComponent(button::Button *button) : button_(button) {} void MQTTButtonComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index de25c5b2e372..148316672a53 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -373,10 +373,11 @@ void MQTTClientComponent::unsubscribe(const std::string &topic) { auto it = subscriptions_.begin(); while (it != subscriptions_.end()) { - if (it->topic == topic) + if (it->topic == topic) { it = subscriptions_.erase(it); - else + } else { ++it; + } } } @@ -484,9 +485,10 @@ void MQTTClientComponent::on_message(const std::string &topic, const std::string // in an ISR. this->defer([this, topic, payload]() { #endif - for (auto &subscription : this->subscriptions_) + for (auto &subscription : this->subscriptions_) { if (topic_match(topic.c_str(), subscription.topic.c_str())) subscription.callback(topic, payload); + } #ifdef USE_ESP8266 }); #endif @@ -536,9 +538,11 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) { void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); } void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, - bool retain, bool clean) { + MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, + bool clean) { this->discovery_info_.prefix = std::move(prefix); this->discovery_info_.unique_id_generator = unique_id_generator; + this->discovery_info_.object_id_generator = object_id_generator; this->discovery_info_.retain = retain; this->discovery_info_.clean = clean; } diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index a6a7025c6f60..58a4fbe16637 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -61,6 +61,12 @@ enum MQTTDiscoveryUniqueIdGenerator { MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR, }; +/// available discovery object_id generators +enum MQTTDiscoveryObjectIdGenerator { + MQTT_NONE_OBJECT_ID_GENERATOR = 0, + MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR, +}; + /** Internal struct for MQTT Home Assistant discovery * * See MQTT Discovery. @@ -70,6 +76,7 @@ struct MQTTDiscoveryInfo { bool retain; ///< Whether to retain discovery messages. bool clean; MQTTDiscoveryUniqueIdGenerator unique_id_generator; + MQTTDiscoveryObjectIdGenerator object_id_generator; }; enum MQTTClientState { @@ -106,10 +113,11 @@ class MQTTClientComponent : public Component { * See MQTT Discovery. * @param prefix The Home Assistant discovery prefix. * @param unique_id_generator Controls how UniqueId is generated. + * @param object_id_generator Controls how ObjectId is generated. * @param retain Whether to retain discovery messages. */ - void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain, - bool clean = false); + void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, + MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool clean = false); /// Get Home Assistant discovery info. const MQTTDiscoveryInfo &get_discovery_info() const; /// Globally disable Home Assistant discovery. diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp index f6ef3a5e8fef..7c3c414b3aac 100644 --- a/esphome/components/mqtt/mqtt_climate.cpp +++ b/esphome/components/mqtt/mqtt_climate.cpp @@ -299,7 +299,7 @@ bool MQTTClimateComponent::publish_state_() { if (traits.get_supports_fan_modes()) { std::string payload; - if (this->device_->fan_mode.has_value()) + if (this->device_->fan_mode.has_value()) { switch (this->device_->fan_mode.value()) { case CLIMATE_FAN_ON: payload = "on"; @@ -329,6 +329,7 @@ bool MQTTClimateComponent::publish_state_() { payload = "diffuse"; break; } + } if (this->device_->custom_fan_mode.has_value()) payload = this->device_->custom_fan_mode.value(); if (!this->publish(this->get_fan_mode_state_topic(), payload)) diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 62dbae3bcc0c..cf228efd1b60 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -27,13 +27,13 @@ std::string MQTTComponent::get_default_topic_for_(const std::string &suffix) con "/" + suffix; } -const std::string MQTTComponent::get_state_topic_() const { +std::string MQTTComponent::get_state_topic_() const { if (this->custom_state_topic_.empty()) return this->get_default_topic_for_("state"); return this->custom_state_topic_; } -const std::string MQTTComponent::get_command_topic_() const { +std::string MQTTComponent::get_command_topic_() const { if (this->custom_command_topic_.empty()) return this->get_default_topic_for_("command"); return this->custom_command_topic_; @@ -92,6 +92,8 @@ bool MQTTComponent::send_discovery_() { root[MQTT_STATE_TOPIC] = this->get_state_topic_(); if (config.command_topic) root[MQTT_COMMAND_TOPIC] = this->get_command_topic_(); + if (this->command_retain_) + root[MQTT_COMMAND_RETAIN] = true; if (this->availability_ == nullptr) { if (!global_mqtt_client->get_availability().topic.empty()) { @@ -109,12 +111,11 @@ bool MQTTComponent::send_discovery_() { root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available; } - const std::string &node_name = App.get_name(); std::string unique_id = this->unique_id(); + const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); if (!unique_id.empty()) { root[MQTT_UNIQUE_ID] = unique_id; } else { - const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info(); if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) { char friendly_name_hash[9]; sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name())); @@ -127,6 +128,10 @@ bool MQTTComponent::send_discovery_() { } } + const std::string &node_name = App.get_name(); + if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR) + root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_(); + JsonObject device_info = root.createNestedObject(MQTT_DEVICE); device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address(); device_info[MQTT_DEVICE_NAME] = node_name; @@ -165,6 +170,7 @@ void MQTTComponent::set_custom_state_topic(const std::string &custom_state_topic void MQTTComponent::set_custom_command_topic(const std::string &custom_command_topic) { this->custom_command_topic_ = custom_command_topic; } +void MQTTComponent::set_command_retain(bool command_retain) { this->command_retain_ = command_retain; } void MQTTComponent::set_availability(std::string topic, std::string payload_available, std::string payload_not_available) { diff --git a/esphome/components/mqtt/mqtt_component.h b/esphome/components/mqtt/mqtt_component.h index e83523a71225..16a00cfddeae 100644 --- a/esphome/components/mqtt/mqtt_component.h +++ b/esphome/components/mqtt/mqtt_component.h @@ -33,7 +33,7 @@ struct SendDiscoveryConfig { \ public: \ void set_custom_##name##_##type##_topic(const std::string &topic) { this->custom_##name##_##type##_topic_ = topic; } \ - const std::string get_##name##_##type##_topic() const { \ + std::string get_##name##_##type##_topic() const { \ if (this->custom_##name##_##type##_topic_.empty()) \ return this->get_default_topic_for_(#name "/" #type); \ return this->custom_##name##_##type##_topic_; \ @@ -91,6 +91,8 @@ class MQTTComponent : public Component { void set_custom_state_topic(const std::string &custom_state_topic); /// Set a custom command topic. Set to "" for default behavior. void set_custom_command_topic(const std::string &custom_command_topic); + /// Set whether command message should be retained. + void set_command_retain(bool command_retain); /// MQTT_COMPONENT setup priority. float get_setup_priority() const override; @@ -171,10 +173,10 @@ class MQTTComponent : public Component { virtual bool is_disabled_by_default() const; /// Get the MQTT topic that new states will be shared to. - const std::string get_state_topic_() const; + std::string get_state_topic_() const; /// Get the MQTT topic for listening to commands. - const std::string get_command_topic_() const; + std::string get_command_topic_() const; bool is_connected_() const; @@ -186,9 +188,9 @@ class MQTTComponent : public Component { /// Generate the Home Assistant MQTT discovery object id by automatically transforming the friendly name. std::string get_default_object_id_() const; - protected: std::string custom_state_topic_{}; std::string custom_command_topic_{}; + bool command_retain_{false}; bool retain_{true}; bool discovery_enabled_{true}; std::unique_ptr availability_; diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h index 8134a6b53edb..7f74197ab4cf 100644 --- a/esphome/components/mqtt/mqtt_const.h +++ b/esphome/components/mqtt/mqtt_const.h @@ -45,6 +45,7 @@ constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl"; constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl"; constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl"; constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t"; +constexpr const char *const MQTT_COMMAND_RETAIN = "ret"; constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl"; constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req"; constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req"; @@ -106,6 +107,7 @@ constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t"; constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl"; constexpr const char *const MQTT_MODES = "modes"; constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OBJECT_ID = "obj_id"; constexpr const char *const MQTT_OFF_DELAY = "off_dly"; constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type"; constexpr const char *const MQTT_OPTIONS = "ops"; @@ -297,6 +299,7 @@ constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template"; constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template"; constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template"; constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic"; +constexpr const char *const MQTT_COMMAND_RETAIN = "retain"; constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template"; constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required"; constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required"; @@ -358,6 +361,7 @@ constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic"; constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template"; constexpr const char *const MQTT_MODES = "modes"; constexpr const char *const MQTT_NAME = "name"; +constexpr const char *const MQTT_OBJECT_ID = "object_id"; constexpr const char *const MQTT_OFF_DELAY = "off_delay"; constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type"; constexpr const char *const MQTT_OPTIONS = "options"; diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp index 0f2eb6535f1d..e4d867843c05 100644 --- a/esphome/components/mqtt/mqtt_fan.cpp +++ b/esphome/components/mqtt/mqtt_fan.cpp @@ -14,9 +14,9 @@ static const char *const TAG = "mqtt.fan"; using namespace esphome::fan; -MQTTFanComponent::MQTTFanComponent(FanState *state) : MQTTComponent(), state_(state) {} +MQTTFanComponent::MQTTFanComponent(Fan *state) : state_(state) {} -FanState *MQTTFanComponent::get_state() const { return this->state_; } +Fan *MQTTFanComponent::get_state() const { return this->state_; } std::string MQTTFanComponent::component_type() const { return "fan"; } const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } diff --git a/esphome/components/mqtt/mqtt_fan.h b/esphome/components/mqtt/mqtt_fan.h index 9d15a6cd0ef1..12286b9f0145 100644 --- a/esphome/components/mqtt/mqtt_fan.h +++ b/esphome/components/mqtt/mqtt_fan.h @@ -13,7 +13,7 @@ namespace mqtt { class MQTTFanComponent : public mqtt::MQTTComponent { public: - explicit MQTTFanComponent(fan::FanState *state); + explicit MQTTFanComponent(fan::Fan *state); MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, command) MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, state) @@ -37,12 +37,12 @@ class MQTTFanComponent : public mqtt::MQTTComponent { /// 'fan' component type for discovery. std::string component_type() const override; - fan::FanState *get_state() const; + fan::Fan *get_state() const; protected: const EntityBase *get_entity() const override; - fan::FanState *state_; + fan::Fan *state_; }; } // namespace mqtt diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp index ee1cc36af74b..e2480acd62fa 100644 --- a/esphome/components/mqtt/mqtt_light.cpp +++ b/esphome/components/mqtt/mqtt_light.cpp @@ -28,7 +28,7 @@ void MQTTJSONLightComponent::setup() { this->state_->add_new_remote_values_callback([this, f]() { this->defer("send", f); }); } -MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : MQTTComponent(), state_(state) {} +MQTTJSONLightComponent::MQTTJSONLightComponent(LightState *state) : state_(state) {} bool MQTTJSONLightComponent::publish_state_() { return this->publish_json(this->get_state_topic_(), diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp new file mode 100644 index 000000000000..197d0c32d4ae --- /dev/null +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -0,0 +1,55 @@ +#include "mqtt_lock.h" +#include "esphome/core/log.h" + +#include "mqtt_const.h" + +#ifdef USE_MQTT +#ifdef USE_LOCK + +namespace esphome { +namespace mqtt { + +static const char *const TAG = "mqtt.lock"; + +using namespace esphome::lock; + +MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {} + +void MQTTLockComponent::setup() { + this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { + if (strcasecmp(payload.c_str(), "LOCK") == 0) { + this->lock_->lock(); + } else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) { + this->lock_->unlock(); + } else if (strcasecmp(payload.c_str(), "OPEN") == 0) { + this->lock_->open(); + } else { + ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str()); + this->status_momentary_warning("state", 5000); + } + }); + this->lock_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); }); +} +void MQTTLockComponent::dump_config() { + ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str()); + LOG_MQTT_COMPONENT(true, true); +} + +std::string MQTTLockComponent::component_type() const { return "lock"; } +const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; } +void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { + if (this->lock_->traits.get_assumed_state()) + root[MQTT_OPTIMISTIC] = true; +} +bool MQTTLockComponent::send_initial_state() { return this->publish_state(); } + +bool MQTTLockComponent::publish_state() { + std::string payload = lock_state_to_string(this->lock_->state); + return this->publish(this->get_state_topic_(), payload); +} + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_lock.h b/esphome/components/mqtt/mqtt_lock.h new file mode 100644 index 000000000000..789f74c79555 --- /dev/null +++ b/esphome/components/mqtt/mqtt_lock.h @@ -0,0 +1,41 @@ +#pragma once + +#include "esphome/core/defines.h" + +#ifdef USE_MQTT +#ifdef USE_LOCK + +#include "esphome/components/lock/lock.h" +#include "mqtt_component.h" + +namespace esphome { +namespace mqtt { + +class MQTTLockComponent : public mqtt::MQTTComponent { + public: + explicit MQTTLockComponent(lock::Lock *a_lock); + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + void dump_config() override; + + void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override; + + bool send_initial_state() override; + + bool publish_state(); + + protected: + /// "lock" component type. + std::string component_type() const override; + const EntityBase *get_entity() const override; + + lock::Lock *lock_; +}; + +} // namespace mqtt +} // namespace esphome + +#endif +#endif // USE_MQTT diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index 73d37f7cd319..70187922830e 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.number"; using namespace esphome::number; -MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), number_(number) {} +MQTTNumberComponent::MQTTNumberComponent(Number *number) : number_(number) {} void MQTTNumberComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp index cb4c9c905298..7ecbf9425e1d 100644 --- a/esphome/components/mqtt/mqtt_select.cpp +++ b/esphome/components/mqtt/mqtt_select.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.select"; using namespace esphome::select; -MQTTSelectComponent::MQTTSelectComponent(Select *select) : MQTTComponent(), select_(select) {} +MQTTSelectComponent::MQTTSelectComponent(Select *select) : select_(select) {} void MQTTSelectComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) { diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index 303aa0e7533b..4946cfb92471 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -17,7 +17,7 @@ static const char *const TAG = "mqtt.sensor"; using namespace esphome::sensor; -MQTTSensorComponent::MQTTSensorComponent(Sensor *sensor) : MQTTComponent(), sensor_(sensor) {} +MQTTSensorComponent::MQTTSensorComponent(Sensor *sensor) : sensor_(sensor) {} void MQTTSensorComponent::setup() { this->sensor_->add_on_state_callback([this](float state) { this->publish_state(state); }); diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp index 2e91f8e502d6..3fd578825a9d 100644 --- a/esphome/components/mqtt/mqtt_switch.cpp +++ b/esphome/components/mqtt/mqtt_switch.cpp @@ -13,7 +13,7 @@ static const char *const TAG = "mqtt.switch"; using namespace esphome::switch_; -MQTTSwitchComponent::MQTTSwitchComponent(switch_::Switch *a_switch) : MQTTComponent(), switch_(a_switch) {} +MQTTSwitchComponent::MQTTSwitchComponent(switch_::Switch *a_switch) : switch_(a_switch) {} void MQTTSwitchComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index 010364e2217c..d0d3174bfe0c 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -11,7 +11,7 @@ static const char *const TAG = "mqtt.text_sensor"; using namespace esphome::text_sensor; -MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : MQTTComponent(), sensor_(sensor) {} +MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { config.command_topic = false; } diff --git a/esphome/components/mqtt_subscribe/text_sensor/__init__.py b/esphome/components/mqtt_subscribe/text_sensor/__init__.py index 477e4dec451a..5b5c0ae17f21 100644 --- a/esphome/components/mqtt_subscribe/text_sensor/__init__.py +++ b/esphome/components/mqtt_subscribe/text_sensor/__init__.py @@ -1,7 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor, mqtt -from esphome.const import CONF_ID, CONF_QOS, CONF_TOPIC +from esphome.const import CONF_QOS, CONF_TOPIC + from .. import mqtt_subscribe_ns DEPENDENCIES = ["mqtt"] @@ -11,20 +12,23 @@ "MQTTSubscribeTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor), - 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, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor), + 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, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID]) cg.add(var.set_parent(parent)) diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py index 6bb1bc8f99ba..722e6f5b0642 100644 --- a/esphome/components/neopixelbus/light.py +++ b/esphome/components/neopixelbus/light.py @@ -169,6 +169,10 @@ def _validate_method(value): CONFIG_SCHEMA = cv.All( cv.only_with_arduino, + cv.require_framework_version( + esp8266_arduino=cv.Version(2, 4, 0), + esp32_arduino=cv.Version(0, 0, 0), + ), light.ADDRESSABLE_LIGHT_SCHEMA.extend( { cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase), diff --git a/esphome/components/neopixelbus/neopixelbus_light.h b/esphome/components/neopixelbus/neopixelbus_light.h index 34e10f2cfee4..5233886075dc 100644 --- a/esphome/components/neopixelbus/neopixelbus_light.h +++ b/esphome/components/neopixelbus/neopixelbus_light.h @@ -9,10 +9,6 @@ #include "esphome/components/light/light_output.h" #include "esphome/components/light/addressable_light.h" -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) -#error The NeoPixelBus library requires at least arduino_version 2.4.x -#endif - #include "NeoPixelBus.h" namespace esphome { diff --git a/esphome/components/network/__init__.py b/esphome/components/network/__init__.py index 6d39023a80e1..40d420c48c44 100644 --- a/esphome/components/network/__init__.py +++ b/esphome/components/network/__init__.py @@ -1,7 +1,27 @@ import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components.esp32 import add_idf_sdkconfig_option + +from esphome.const import ( + CONF_ENABLE_IPV6, +) CODEOWNERS = ["@esphome/core"] AUTO_LOAD = ["mdns"] network_ns = cg.esphome_ns.namespace("network") IPAddress = network_ns.class_("IPAddress") + +CONFIG_SCHEMA = cv.Schema( + { + cv.SplitDefault(CONF_ENABLE_IPV6, esp32_idf=False): cv.All( + cv.only_with_esp_idf, cv.boolean + ), + } +) + + +async def to_code(config): + if CONF_ENABLE_IPV6 in config and config[CONF_ENABLE_IPV6]: + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6", True) + add_idf_sdkconfig_option("CONFIG_LWIP_IPV6_AUTOCONFIG", True) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index fcb3885db9af..46c063e5ee41 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -688,7 +688,7 @@ void Nextion::process_nextion_commands_() { int index = 0; int found = -1; for (auto &nb : this->nextion_queue_) { - auto component = nb->component; + auto *component = nb->component; if (component->get_queue_type() == NextionQueueType::WAVEFORM_SENSOR) { size_t buffer_to_send = component->get_wave_buffer().size() < 255 ? component->get_wave_buffer().size() : 255; // ADDT command can only send 255 @@ -737,9 +737,10 @@ void Nextion::process_nextion_commands_() { for (size_t i = 0; i < this->nextion_queue_.size(); i++) { NextionComponentBase *component = this->nextion_queue_[i]->component; if (this->nextion_queue_[i]->queue_time + this->max_q_age_ms_ < ms) { - if (this->nextion_queue_[i]->queue_time == 0) + if (this->nextion_queue_[i]->queue_time == 0) { ESP_LOGD(TAG, "Removing old queue type \"%s\" name \"%s\" queue_time 0", component->get_queue_type_string().c_str(), component->get_variable_name().c_str()); + } if (component->get_variable_name() == "sleep_wake") { this->is_sleeping_ = false; @@ -873,9 +874,9 @@ uint16_t Nextion::recv_ret_string_(std::string &response, uint32_t timeout, bool while ((timeout == 0 && this->available()) || millis() - start <= timeout) { this->read_byte(&c); - if (c == 0xFF) + if (c == 0xFF) { nr_of_ff_bytes++; - else { + } else { nr_of_ff_bytes = 0; ff_flag = false; } diff --git a/esphome/components/nextion/nextion_upload.cpp b/esphome/components/nextion/nextion_upload.cpp index 1b60034bd18b..9e6884398cb6 100644 --- a/esphome/components/nextion/nextion_upload.cpp +++ b/esphome/components/nextion/nextion_upload.cpp @@ -3,7 +3,7 @@ #ifdef USE_NEXTION_TFT_UPLOAD #include "esphome/core/application.h" -#include "esphome/core/macros.h" +#include "esphome/core/defines.h" #include "esphome/core/util.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" @@ -32,12 +32,12 @@ int Nextion::upload_by_chunks_(HTTPClient *http, int range_start) { range_end = this->tft_size_; #ifdef USE_ESP8266 -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http->setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http->setFollowRedirects(true); #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http->setRedirectLimit(3); #endif #endif @@ -148,12 +148,12 @@ void Nextion::upload_tft() { begin_status = http.begin(this->tft_url_.c_str()); #endif #ifdef USE_ESP8266 -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 7, 0) http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); -#elif ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#elif USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http.setFollowRedirects(true); #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 6, 0) http.setRedirectLimit(3); #endif begin_status = http.begin(*this->get_wifi_client_(), this->tft_url_.c_str()); diff --git a/esphome/components/nextion/text_sensor/__init__.py b/esphome/components/nextion/text_sensor/__init__.py index 9c170dd8076e..9b8518d8c46a 100644 --- a/esphome/components/nextion/text_sensor/__init__.py +++ b/esphome/components/nextion/text_sensor/__init__.py @@ -17,11 +17,7 @@ ) CONFIG_SCHEMA = ( - text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(NextionTextSensor), - } - ) + text_sensor.text_sensor_schema(klass=NextionTextSensor) .extend(CONFIG_TEXT_COMPONENT_SCHEMA) .extend(cv.polling_component_schema("never")) ) @@ -30,8 +26,8 @@ async def to_code(config): hub = await cg.get_variable(config[CONF_NEXTION_ID]) var = cg.new_Pvariable(config[CONF_ID], hub) - await cg.register_component(var, config) await text_sensor.register_text_sensor(var, config) + await cg.register_component(var, config) cg.add(hub.register_textsensor_component(var)) diff --git a/esphome/components/ota/ota_component.cpp b/esphome/components/ota/ota_component.cpp index 92256eb1b63d..37da3bdc4464 100644 --- a/esphome/components/ota/ota_component.cpp +++ b/esphome/components/ota/ota_component.cpp @@ -36,7 +36,7 @@ std::unique_ptr make_ota_backend() { } void OTAComponent::setup() { - server_ = socket::socket(AF_INET, SOCK_STREAM, 0); + server_ = socket::socket_ip(SOCK_STREAM, 0); if (server_ == nullptr) { ESP_LOGW(TAG, "Could not create socket."); this->mark_failed(); @@ -55,11 +55,14 @@ void OTAComponent::setup() { return; } - struct sockaddr_in server; - memset(&server, 0, sizeof(server)); - server.sin_family = AF_INET; - server.sin_addr.s_addr = ESPHOME_INADDR_ANY; - server.sin_port = htons(this->port_); + struct sockaddr_storage server; + + socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), htons(this->port_)); + if (sl == 0) { + ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); + this->mark_failed(); + return; + } err = server_->bind((struct sockaddr *) &server, sizeof(server)); if (err != 0) { @@ -452,10 +455,11 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_ bool is_manual_safe_mode = this->safe_mode_rtc_value_ == esphome::ota::OTAComponent::ENTER_SAFE_MODE_MAGIC; - if (is_manual_safe_mode) + if (is_manual_safe_mode) { ESP_LOGI(TAG, "Safe mode has been entered manually"); - else + } else { ESP_LOGCONFIG(TAG, "There have been %u suspected unsuccessful boot attempts.", this->safe_mode_rtc_value_); + } if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) { this->clean_rtc(); diff --git a/esphome/components/output/binary_output.h b/esphome/components/output/binary_output.h index 2697b236169f..7a15bc7b51c8 100644 --- a/esphome/components/output/binary_output.h +++ b/esphome/components/output/binary_output.h @@ -30,6 +30,15 @@ class BinaryOutput { void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } #endif + /// Enable or disable this binary output. + virtual void set_state(bool state) { + if (state) { + this->turn_on(); + } else { + this->turn_off(); + } + } + /// Enable this binary output. virtual void turn_on() { #ifdef USE_POWER_SUPPLY diff --git a/esphome/components/output/button/__init__.py b/esphome/components/output/button/__init__.py new file mode 100644 index 000000000000..4b81cedc0c89 --- /dev/null +++ b/esphome/components/output/button/__init__.py @@ -0,0 +1,26 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import button, output +from esphome.const import CONF_ID, CONF_OUTPUT, CONF_DURATION +from .. import output_ns + +OutputButton = output_ns.class_("OutputButton", button.Button, cg.Component) + +CONFIG_SCHEMA = button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OutputButton), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + cv.Required(CONF_DURATION): cv.positive_time_period_milliseconds, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_duration(config[CONF_DURATION])) + + output_ = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_output(output_)) + + await cg.register_component(var, config) + await button.register_button(var, config) diff --git a/esphome/components/output/button/output_button.cpp b/esphome/components/output/button/output_button.cpp new file mode 100644 index 000000000000..4dd7ec249b1f --- /dev/null +++ b/esphome/components/output/button/output_button.cpp @@ -0,0 +1,21 @@ +#include "output_button.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace output { + +static const char *const TAG = "output.button"; + +void OutputButton::dump_config() { + LOG_BUTTON("", "Output Button", this); + ESP_LOGCONFIG(TAG, " Duration: %.1fs", this->duration_ / 1e3f); +} +void OutputButton::press_action() { + this->output_->turn_on(); + + // Use a named timeout so that it's automatically cancelled if button is pressed again before it's reset + this->set_timeout("reset", this->duration_, [this]() { this->output_->turn_off(); }); +} + +} // namespace output +} // namespace esphome diff --git a/esphome/components/output/button/output_button.h b/esphome/components/output/button/output_button.h new file mode 100644 index 000000000000..8802c9754dc2 --- /dev/null +++ b/esphome/components/output/button/output_button.h @@ -0,0 +1,25 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" +#include "esphome/components/output/binary_output.h" + +namespace esphome { +namespace output { + +class OutputButton : public button::Button, public Component { + public: + void dump_config() override; + + void set_output(BinaryOutput *output) { output_ = output; } + void set_duration(uint32_t duration) { duration_ = duration; } + + protected: + void press_action() override; + + output::BinaryOutput *output_; + uint32_t duration_; +}; + +} // namespace output +} // namespace esphome diff --git a/esphome/components/output/lock/__init__.py b/esphome/components/output/lock/__init__.py new file mode 100644 index 000000000000..3be2cb09aa23 --- /dev/null +++ b/esphome/components/output/lock/__init__.py @@ -0,0 +1,23 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import output, lock +from esphome.const import CONF_ID, CONF_OUTPUT +from .. import output_ns + +OutputLock = output_ns.class_("OutputLock", lock.Lock, cg.Component) + +CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(OutputLock), + cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await lock.register_lock(var, config) + + output_ = await cg.get_variable(config[CONF_OUTPUT]) + cg.add(var.set_output(output_)) diff --git a/esphome/components/output/lock/output_lock.cpp b/esphome/components/output/lock/output_lock.cpp new file mode 100644 index 000000000000..2545f6248118 --- /dev/null +++ b/esphome/components/output/lock/output_lock.cpp @@ -0,0 +1,22 @@ +#include "output_lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace output { + +static const char *const TAG = "output.lock"; + +void OutputLock::dump_config() { LOG_LOCK("", "Output Lock", this); } + +void OutputLock::control(const lock::LockCall &call) { + auto state = *call.get_state(); + if (state == lock::LOCK_STATE_LOCKED) { + this->output_->turn_on(); + } else if (state == lock::LOCK_STATE_UNLOCKED) { + this->output_->turn_off(); + } + this->publish_state(state); +} + +} // namespace output +} // namespace esphome diff --git a/esphome/components/output/lock/output_lock.h b/esphome/components/output/lock/output_lock.h new file mode 100644 index 000000000000..c183c3a3ea94 --- /dev/null +++ b/esphome/components/output/lock/output_lock.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/lock/lock.h" +#include "esphome/components/output/binary_output.h" + +namespace esphome { +namespace output { + +class OutputLock : public lock::Lock, public Component { + public: + void set_output(BinaryOutput *output) { output_ = output; } + + float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; } + void dump_config() override; + + protected: + void control(const lock::LockCall &call) override; + + output::BinaryOutput *output_; +}; + +} // namespace output +} // namespace esphome diff --git a/esphome/components/output/switch/output_switch.cpp b/esphome/components/output/switch/output_switch.cpp index 3691896cbe5c..ec9c8afc0119 100644 --- a/esphome/components/output/switch/output_switch.cpp +++ b/esphome/components/output/switch/output_switch.cpp @@ -30,10 +30,11 @@ void OutputSwitch::setup() { break; } - if (initial_state) + if (initial_state) { this->turn_on(); - else + } else { this->turn_off(); + } } void OutputSwitch::write_state(bool state) { if (state) { diff --git a/esphome/components/packages/__init__.py b/esphome/components/packages/__init__.py index 7483d65b9d9d..67220cae0853 100644 --- a/esphome/components/packages/__init__.py +++ b/esphome/components/packages/__init__.py @@ -1,6 +1,7 @@ import re from pathlib import Path from esphome.core import EsphomeError +from esphome.config_helpers import merge_config from esphome import git, yaml_util from esphome.const import ( @@ -18,26 +19,6 @@ DOMAIN = CONF_PACKAGES -def _merge_package(full_old, full_new): - def merge(old, new): - # pylint: disable=no-else-return - if isinstance(new, dict): - if not isinstance(old, dict): - return new - res = old.copy() - for k, v in new.items(): - res[k] = merge(old[k], v) if k in old else v - return res - elif isinstance(new, list): - if not isinstance(old, list): - return new - return old + new - - return new - - return merge(full_old, full_new) - - def validate_git_package(config: dict): new_config = config for key, conf in config.items(): @@ -167,7 +148,7 @@ def do_packages_pass(config: dict): package_config = _process_base_package(package_config) if isinstance(package_config, dict): recursive_package = do_packages_pass(package_config) - config = _merge_package(recursive_package, config) + config = merge_config(recursive_package, config) del config[CONF_PACKAGES] return config diff --git a/esphome/components/partition/light.py b/esphome/components/partition/light.py index 822b7ac306b2..73cda2c92624 100644 --- a/esphome/components/partition/light.py +++ b/esphome/components/partition/light.py @@ -104,7 +104,6 @@ async def to_code(config): ) light_state = cg.new_Pvariable(conf[CONF_LIGHT_ID], "", wrapper) await cg.register_component(light_state, conf) - cg.add(cg.App.register_light(light_state)) segments.append(AddressableSegment(light_state, 0, 1, False)) else: diff --git a/esphome/components/partition/light_partition.h b/esphome/components/partition/light_partition.h index f74001cf75a5..5790fd2b3956 100644 --- a/esphome/components/partition/light_partition.h +++ b/esphome/components/partition/light_partition.h @@ -78,10 +78,11 @@ class PartitionLightOutput : public light::AddressableLight { int32_t seg_off = index - seg.get_dst_offset(); // offset within the src int32_t src_off; - if (seg.is_reversed()) + if (seg.is_reversed()) { src_off = seg.get_src_offset() + seg.get_size() - seg_off - 1; - else + } else { src_off = seg.get_src_offset() + seg_off; + } auto view = (*seg.get_src())[src_off]; view.raw_set_color_correction(&this->correction_); diff --git a/esphome/components/pid/pid_climate.cpp b/esphome/components/pid/pid_climate.cpp index f5c779278201..3f8377c720f1 100644 --- a/esphome/components/pid/pid_climate.cpp +++ b/esphome/components/pid/pid_climate.cpp @@ -20,12 +20,13 @@ void PIDClimate::setup() { restore->to_call(this).perform(); } else { // restore from defaults, change_away handles those for us - if (supports_heat_() && supports_cool_()) + if (supports_heat_() && supports_cool_()) { this->mode = climate::CLIMATE_MODE_HEAT_COOL; - else if (supports_cool_()) + } else if (supports_cool_()) { this->mode = climate::CLIMATE_MODE_COOL; - else if (supports_heat_()) + } else if (supports_heat_()) { this->mode = climate::CLIMATE_MODE_HEAT; + } this->target_temperature = this->default_target_temperature_; } } @@ -83,14 +84,15 @@ void PIDClimate::write_output_(float value) { // Update action variable for user feedback what's happening climate::ClimateAction new_action; - if (this->supports_cool_() && value < 0) + if (this->supports_cool_() && value < 0) { new_action = climate::CLIMATE_ACTION_COOLING; - else if (this->supports_heat_() && value > 0) + } else if (this->supports_heat_() && value > 0) { new_action = climate::CLIMATE_ACTION_HEATING; - else if (this->mode == climate::CLIMATE_MODE_OFF) + } else if (this->mode == climate::CLIMATE_MODE_OFF) { new_action = climate::CLIMATE_ACTION_OFF; - else + } else { new_action = climate::CLIMATE_ACTION_IDLE; + } if (new_action != this->action) { this->action = new_action; diff --git a/esphome/components/pipsolar/__init__.py b/esphome/components/pipsolar/__init__.py index 20e4672125bc..875de0571316 100644 --- a/esphome/components/pipsolar/__init__.py +++ b/esphome/components/pipsolar/__init__.py @@ -13,7 +13,7 @@ pipsolar_ns = cg.esphome_ns.namespace("pipsolar") PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component) -PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend( +PIPSOLAR_COMPONENT_SCHEMA = cv.Schema( { cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent), } diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp index 13a08bbd1662..c1935509f098 100644 --- a/esphome/components/pipsolar/pipsolar.cpp +++ b/esphome/components/pipsolar/pipsolar.cpp @@ -908,10 +908,11 @@ uint16_t Pipsolar::crc_xmodem_update_(uint16_t crc, uint8_t data) { int i; crc = crc ^ ((uint16_t) data << 8); for (i = 0; i < 8; i++) { - if (crc & 0x8000) + if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; //(polynomial = 0x1021) - else + } else { crc <<= 1; + } } return crc; } diff --git a/esphome/components/pipsolar/text_sensor/__init__.py b/esphome/components/pipsolar/text_sensor/__init__.py index fe6c4979f319..856352f8cb9f 100644 --- a/esphome/components/pipsolar/text_sensor/__init__.py +++ b/esphome/components/pipsolar/text_sensor/__init__.py @@ -1,8 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import text_sensor -from esphome.const import CONF_ID -from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns +from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA DEPENDENCIES = ["uart"] @@ -15,10 +14,6 @@ CONF_LAST_QT = "last_qt" CONF_LAST_QMN = "last_qmn" -PipsolarTextSensor = pipsolar_ns.class_( - "PipsolarTextSensor", text_sensor.TextSensor, cg.Component -) - TYPES = [ CONF_DEVICE_MODE, CONF_LAST_QPIGS, @@ -31,12 +26,7 @@ ] CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend( - { - cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend( - {cv.GenerateID(): cv.declare_id(PipsolarTextSensor)} - ) - for type in TYPES - } + {cv.Optional(type): text_sensor.text_sensor_schema() for type in TYPES} ) @@ -46,7 +36,5 @@ async def to_code(config): for type in TYPES: if type in config: conf = config[type] - var = cg.new_Pvariable(conf[CONF_ID]) - await text_sensor.register_text_sensor(var, conf) - await cg.register_component(var, conf) + var = await text_sensor.new_text_sensor(conf) cg.add(getattr(paren, f"set_{type}")(var)) diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp deleted file mode 100644 index ee1fe2d1d8ff..000000000000 --- a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "pipsolar_textsensor.h" -#include "esphome/core/log.h" -#include "esphome/core/application.h" - -namespace esphome { -namespace pipsolar { - -static const char *const TAG = "pipsolar.text_sensor"; - -void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); } - -} // namespace pipsolar -} // namespace esphome diff --git a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h b/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h deleted file mode 100644 index 871f6d8dee2a..000000000000 --- a/esphome/components/pipsolar/text_sensor/pipsolar_textsensor.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "../pipsolar.h" -#include "esphome/components/text_sensor/text_sensor.h" -#include "esphome/core/component.h" - -namespace esphome { -namespace pipsolar { -class Pipsolar; -class PipsolarTextSensor : public Component, public text_sensor::TextSensor { - public: - void set_parent(Pipsolar *parent) { this->parent_ = parent; }; - void dump_config() override; - - protected: - Pipsolar *parent_; -}; - -} // namespace pipsolar -} // namespace esphome diff --git a/esphome/components/prometheus/prometheus_handler.cpp b/esphome/components/prometheus/prometheus_handler.cpp index 618c866d5b6e..e4dd6b9043ef 100644 --- a/esphome/components/prometheus/prometheus_handler.cpp +++ b/esphome/components/prometheus/prometheus_handler.cpp @@ -45,6 +45,12 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) { this->switch_row_(stream, obj); #endif +#ifdef USE_LOCK + this->lock_type_(stream); + for (auto *obj : App.get_locks()) + this->lock_row_(stream, obj); +#endif + req->send(stream); } @@ -127,7 +133,7 @@ void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { stream->print(F("#TYPE esphome_fan_speed GAUGE\n")); stream->print(F("#TYPE esphome_fan_oscillation GAUGE\n")); } -void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::FanState *obj) { +void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { if (obj->is_internal()) return; stream->print(F("esphome_fan_failed{id=\"")); @@ -310,6 +316,30 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch } #endif +#ifdef USE_LOCK +void PrometheusHandler::lock_type_(AsyncResponseStream *stream) { + stream->print(F("#TYPE esphome_lock_value GAUGE\n")); + stream->print(F("#TYPE esphome_lock_failed GAUGE\n")); +} +void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) { + if (obj->is_internal()) + return; + stream->print(F("esphome_lock_failed{id=\"")); + stream->print(obj->get_object_id().c_str()); + stream->print(F("\",name=\"")); + stream->print(obj->get_name().c_str()); + stream->print(F("\"} 0\n")); + // Data itself + stream->print(F("esphome_lock_value{id=\"")); + stream->print(obj->get_object_id().c_str()); + stream->print(F("\",name=\"")); + stream->print(obj->get_name().c_str()); + stream->print(F("\"} ")); + stream->print(obj->state); + stream->print('\n'); +} +#endif + } // namespace prometheus } // namespace esphome diff --git a/esphome/components/prometheus/prometheus_handler.h b/esphome/components/prometheus/prometheus_handler.h index 5076883ba63b..5c8d51c60ffd 100644 --- a/esphome/components/prometheus/prometheus_handler.h +++ b/esphome/components/prometheus/prometheus_handler.h @@ -52,7 +52,7 @@ class PrometheusHandler : public AsyncWebHandler, public Component { /// Return the type for prometheus void fan_type_(AsyncResponseStream *stream); /// Return the sensor state as prometheus data point - void fan_row_(AsyncResponseStream *stream, fan::FanState *obj); + void fan_row_(AsyncResponseStream *stream, fan::Fan *obj); #endif #ifdef USE_LIGHT @@ -76,6 +76,13 @@ class PrometheusHandler : public AsyncWebHandler, public Component { void switch_row_(AsyncResponseStream *stream, switch_::Switch *obj); #endif +#ifdef USE_LOCK + /// Return the type for prometheus + void lock_type_(AsyncResponseStream *stream); + /// Return the lock Values state as prometheus data point + void lock_row_(AsyncResponseStream *stream, lock::Lock *obj); +#endif + web_server_base::WebServerBase *base_; }; diff --git a/esphome/components/qr_code/__init__.py b/esphome/components/qr_code/__init__.py new file mode 100644 index 000000000000..855db863352e --- /dev/null +++ b/esphome/components/qr_code/__init__.py @@ -0,0 +1,41 @@ +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_ID, CONF_VALUE + +CONF_SCALE = "scale" +CONF_ECC = "ecc" + +CODEOWNERS = ["@wjtje"] + +DEPENDENCIES = ["display"] +MULTI_CONF = True + +qr_code_ns = cg.esphome_ns.namespace("qr_code") +QRCode = qr_code_ns.class_("QrCode", cg.Component) + +qrcodegen_Ecc = cg.esphome_ns.enum("qrcodegen_Ecc") +ECC = { + "LOW": qrcodegen_Ecc.qrcodegen_Ecc_LOW, + "MEDIUM": qrcodegen_Ecc.qrcodegen_Ecc_MEDIUM, + "QUARTILE": qrcodegen_Ecc.qrcodegen_Ecc_QUARTILE, + "HIGH": qrcodegen_Ecc.qrcodegen_Ecc_HIGH, +} + +CONFIG_SCHEMA = cv.Schema( + { + cv.Required(CONF_ID): cv.declare_id(QRCode), + cv.Required(CONF_VALUE): cv.string, + cv.Optional(CONF_ECC, default="LOW"): cv.enum(ECC, upper=True), + } +) + + +async def to_code(config): + cg.add_library("wjtje/qr-code-generator-library", "^1.7.0") + + var = cg.new_Pvariable(config[CONF_ID]) + cg.add(var.set_value(config[CONF_VALUE])) + cg.add(var.set_ecc(ECC[config[CONF_ECC]])) + await cg.register_component(var, config) + + cg.add_define("USE_QR_CODE") diff --git a/esphome/components/qr_code/qr_code.cpp b/esphome/components/qr_code/qr_code.cpp new file mode 100644 index 000000000000..a2efbdb804ad --- /dev/null +++ b/esphome/components/qr_code/qr_code.cpp @@ -0,0 +1,55 @@ +#include "qr_code.h" +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/color.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace qr_code { + +static const char *const TAG = "qr_code"; + +void QrCode::dump_config() { + ESP_LOGCONFIG(TAG, "QR code:"); + ESP_LOGCONFIG(TAG, " Value: '%s'", this->value_.c_str()); +} + +void QrCode::set_value(const std::string &value) { + this->value_ = value; + this->needs_update_ = true; +} + +void QrCode::set_ecc(qrcodegen_Ecc ecc) { + this->ecc_ = ecc; + this->needs_update_ = true; +} + +void QrCode::generate_qr_code() { + ESP_LOGV(TAG, "Generating QR code..."); + uint8_t tempbuffer[qrcodegen_BUFFER_LEN_MAX]; + + if (!qrcodegen_encodeText(this->value_.c_str(), tempbuffer, this->qr_, this->ecc_, qrcodegen_VERSION_MIN, + qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true)) { + ESP_LOGE(TAG, "Failed to generate QR code"); + } +} + +void QrCode::draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale) { + ESP_LOGV(TAG, "Drawing QR code at (%d, %d)", x_offset, y_offset); + + if (this->needs_update_) { + this->generate_qr_code(); + this->needs_update_ = false; + } + + uint8_t qrcode_width = qrcodegen_getSize(this->qr_); + + for (int y = 0; y < qrcode_width * scale; y++) { + for (int x = 0; x < qrcode_width * scale; x++) { + if (qrcodegen_getModule(this->qr_, x / scale, y / scale)) { + buff->draw_pixel_at(x_offset + x, y_offset + y, color); + } + } + } +} +} // namespace qr_code +} // namespace esphome diff --git a/esphome/components/qr_code/qr_code.h b/esphome/components/qr_code/qr_code.h new file mode 100644 index 000000000000..58f3a7032180 --- /dev/null +++ b/esphome/components/qr_code/qr_code.h @@ -0,0 +1,34 @@ +#pragma once +#include "esphome/core/component.h" +#include "esphome/core/color.h" + +#include + +#include "qrcodegen.h" + +namespace esphome { +// forward declare DisplayBuffer +namespace display { +class DisplayBuffer; +} // namespace display + +namespace qr_code { +class QrCode : public Component { + public: + void draw(display::DisplayBuffer *buff, uint16_t x_offset, uint16_t y_offset, Color color, int scale); + + void dump_config() override; + + void set_value(const std::string &value); + void set_ecc(qrcodegen_Ecc ecc); + + void generate_qr_code(); + + protected: + std::string value_; + qrcodegen_Ecc ecc_; + bool needs_update_ = true; + uint8_t qr_[qrcodegen_BUFFER_LEN_MAX]; +}; +} // namespace qr_code +} // namespace esphome diff --git a/esphome/components/radon_eye_ble/__init__.py b/esphome/components/radon_eye_ble/__init__.py new file mode 100644 index 000000000000..ffe434d19ba1 --- /dev/null +++ b/esphome/components/radon_eye_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 + +DEPENDENCIES = ["esp32_ble_tracker"] +CODEOWNERS = ["@jeffeb3"] + +radon_eye_ble_ns = cg.esphome_ns.namespace("radon_eye_ble") +RadonEyeListener = radon_eye_ble_ns.class_( + "RadonEyeListener", esp32_ble_tracker.ESPBTDeviceListener +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.declare_id(RadonEyeListener), + } +).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield esp32_ble_tracker.register_ble_device(var, config) diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.cpp b/esphome/components/radon_eye_ble/radon_eye_listener.cpp new file mode 100644 index 000000000000..b10986c9cba3 --- /dev/null +++ b/esphome/components/radon_eye_ble/radon_eye_listener.cpp @@ -0,0 +1,25 @@ +#include "radon_eye_listener.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace radon_eye_ble { + +static const char *const TAG = "radon_eye_ble"; + +bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (not device.get_name().empty()) { + if (device.get_name().rfind("FR:R20:SN", 0) == 0) { + // This is an RD200, I think + ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(), + device.address_str().c_str()); + } + } + return false; +} + +} // namespace radon_eye_ble +} // namespace esphome + +#endif diff --git a/esphome/components/radon_eye_ble/radon_eye_listener.h b/esphome/components/radon_eye_ble/radon_eye_listener.h new file mode 100644 index 000000000000..26d0233c56ee --- /dev/null +++ b/esphome/components/radon_eye_ble/radon_eye_listener.h @@ -0,0 +1,19 @@ +#pragma once + +#ifdef USE_ESP32 + +#include "esphome/core/component.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" + +namespace esphome { +namespace radon_eye_ble { + +class RadonEyeListener : public esp32_ble_tracker::ESPBTDeviceListener { + public: + bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override; +}; + +} // namespace radon_eye_ble +} // namespace esphome + +#endif diff --git a/esphome/components/radon_eye_rd200/__init__.py b/esphome/components/radon_eye_rd200/__init__.py new file mode 100644 index 000000000000..0740f6967b27 --- /dev/null +++ b/esphome/components/radon_eye_rd200/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@jeffeb3"] diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp new file mode 100644 index 000000000000..6bb17f0508fb --- /dev/null +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.cpp @@ -0,0 +1,179 @@ +#include "radon_eye_rd200.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace radon_eye_rd200 { + +static const char *const TAG = "radon_eye_rd200"; + +void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) { + switch (event) { + case ESP_GATTC_OPEN_EVT: { + if (param->open.status == ESP_GATT_OK) { + ESP_LOGI(TAG, "Connected successfully!"); + } + break; + } + + case ESP_GATTC_DISCONNECT_EVT: { + ESP_LOGW(TAG, "Disconnected!"); + break; + } + + case ESP_GATTC_SEARCH_CMPL_EVT: { + this->read_handle_ = 0; + auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_); + if (chr == nullptr) { + ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(), + sensors_read_characteristic_uuid_.to_string().c_str()); + break; + } + this->read_handle_ = chr->handle; + + // Write a 0x50 to the write characteristic. + auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_); + if (write_chr == nullptr) { + ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(), + sensors_read_characteristic_uuid_.to_string().c_str()); + break; + } + this->write_handle_ = write_chr->handle; + + this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED; + + write_query_message_(); + + request_read_values_(); + 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->read_handle_) { + read_sensors_(param->read.value, param->read.value_len); + } + break; + } + + default: + break; + } +} + +void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) { + if (value_len < 20) { + ESP_LOGD(TAG, "Invalid read"); + return; + } + + // Example data + // [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000 + ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X", + value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], + value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], + value[19]); + + if (value[0] != 0x50) { + // This isn't a sensor reading. + return; + } + + // Convert from pCi/L to Bq/m³ + constexpr float convert_to_bwpm3 = 37.0; + + RadonValue radon_value; + radon_value.chars[0] = value[2]; + radon_value.chars[1] = value[3]; + radon_value.chars[2] = value[4]; + radon_value.chars[3] = value[5]; + float radon_now = radon_value.number * convert_to_bwpm3; + if (is_valid_radon_value_(radon_now)) { + radon_sensor_->publish_state(radon_now); + } + + radon_value.chars[0] = value[6]; + radon_value.chars[1] = value[7]; + radon_value.chars[2] = value[8]; + radon_value.chars[3] = value[9]; + float radon_day = radon_value.number * convert_to_bwpm3; + + radon_value.chars[0] = value[10]; + radon_value.chars[1] = value[11]; + radon_value.chars[2] = value[12]; + radon_value.chars[3] = value[13]; + float radon_month = radon_value.number * convert_to_bwpm3; + + if (is_valid_radon_value_(radon_month)) { + ESP_LOGV(TAG, "Radon Long Term based on month"); + radon_long_term_sensor_->publish_state(radon_month); + } else if (is_valid_radon_value_(radon_day)) { + ESP_LOGV(TAG, "Radon Long Term based on day"); + radon_long_term_sensor_->publish_state(radon_day); + } + + ESP_LOGV(TAG, " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f", radon_now, radon_day, radon_month); + + ESP_LOGV(TAG, " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", radon_now / convert_to_bwpm3, + radon_day / convert_to_bwpm3, radon_month / convert_to_bwpm3); + + // This instance must not stay connected + // so other clients can connect to it (e.g. the + // mobile app). + parent()->set_enabled(false); +} + +bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; } + +void RadonEyeRD200::update() { + if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) { + if (!parent()->enabled) { + ESP_LOGW(TAG, "Reconnecting to device"); + parent()->set_enabled(true); + parent()->connect(); + } else { + ESP_LOGW(TAG, "Connection in progress"); + } + } +} + +void RadonEyeRD200::write_query_message_() { + ESP_LOGV(TAG, "writing 0x50 to write service"); + int request = 0x50; + auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_, + sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status); + } +} + +void RadonEyeRD200::request_read_values_() { + auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_, + ESP_GATT_AUTH_REQ_NONE); + if (status) { + ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status); + } +} + +void RadonEyeRD200::dump_config() { + LOG_SENSOR(" ", "Radon", this->radon_sensor_); + LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_); +} + +RadonEyeRD200::RadonEyeRD200() + : PollingComponent(10000), + service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)), + sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)), + sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {} + +} // namespace radon_eye_rd200 +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/radon_eye_rd200/radon_eye_rd200.h b/esphome/components/radon_eye_rd200/radon_eye_rd200.h new file mode 100644 index 000000000000..7b29be7bd84a --- /dev/null +++ b/esphome/components/radon_eye_rd200/radon_eye_rd200.h @@ -0,0 +1,59 @@ +#pragma once + +#ifdef USE_ESP32 + +#include +#include +#include +#include "esphome/components/ble_client/ble_client.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace radon_eye_rd200 { + +static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123"; +static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123"; +static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123"; + +class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode { + public: + RadonEyeRD200(); + + void dump_config() override; + void update() override; + + void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, + esp_ble_gattc_cb_param_t *param) override; + + void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; } + void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; } + + protected: + bool is_valid_radon_value_(float radon); + + void read_sensors_(uint8_t *value, uint16_t value_len); + void write_query_message_(); + void request_read_values_(); + + sensor::Sensor *radon_sensor_{nullptr}; + sensor::Sensor *radon_long_term_sensor_{nullptr}; + + uint16_t read_handle_; + uint16_t write_handle_; + esp32_ble_tracker::ESPBTUUID service_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_; + esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_; + + union RadonValue { + char chars[4]; + float number; + }; +}; + +} // namespace radon_eye_rd200 +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/radon_eye_rd200/sensor.py b/esphome/components/radon_eye_rd200/sensor.py new file mode 100644 index 000000000000..a9667869b862 --- /dev/null +++ b/esphome/components/radon_eye_rd200/sensor.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, ble_client + +from esphome.const import ( + STATE_CLASS_MEASUREMENT, + UNIT_BECQUEREL_PER_CUBIC_METER, + CONF_ID, + CONF_RADON, + CONF_RADON_LONG_TERM, + ICON_RADIOACTIVE, +) + +DEPENDENCIES = ["ble_client"] + +radon_eye_rd200_ns = cg.esphome_ns.namespace("radon_eye_rd200") +RadonEyeRD200 = radon_eye_rd200_ns.class_( + "RadonEyeRD200", cg.PollingComponent, ble_client.BLEClientNode +) + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(RadonEyeRD200), + cv.Optional(CONF_RADON): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema( + unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER, + icon=ICON_RADIOACTIVE, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("5min")) + .extend(ble_client.BLE_CLIENT_SCHEMA), +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + await ble_client.register_ble_node(var, config) + + if CONF_RADON in config: + sens = await sensor.new_sensor(config[CONF_RADON]) + cg.add(var.set_radon(sens)) + if CONF_RADON_LONG_TERM in config: + sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM]) + cg.add(var.set_radon_long_term(sens)) diff --git a/esphome/components/rc522/rc522.cpp b/esphome/components/rc522/rc522.cpp index d203b3ce8f38..5bfeb4015609 100644 --- a/esphome/components/rc522/rc522.cpp +++ b/esphome/components/rc522/rc522.cpp @@ -139,10 +139,11 @@ void RC522::loop() { StatusCode status = STATUS_ERROR; // For lint passing. TODO: refactor this if (awaiting_comm_) { - if (state_ == STATE_SELECT_SERIAL_DONE) + if (state_ == STATE_SELECT_SERIAL_DONE) { status = await_crc_(); - else + } else { status = await_transceive_(); + } if (status == STATUS_WAITING) { return; @@ -210,11 +211,12 @@ void RC522::loop() { } case STATE_READ_SERIAL_DONE: { if (status != STATUS_OK || back_length_ != 3) { - if (status == STATUS_TIMEOUT) + if (status == STATUS_TIMEOUT) { ESP_LOGV(TAG, "STATE_READ_SERIAL_DONE -> TIMEOUT (no tag present) %d", status); - else + } else { ESP_LOGW(TAG, "Unexpected response. Read status is %d. Read bytes: %d (%s)", status, back_length_, format_buffer(buffer_, 9).c_str()); + } state_ = STATE_DONE; uid_idx_ = 0; @@ -476,9 +478,9 @@ RC522::StatusCode RC522::await_crc_() { bool RC522BinarySensor::process(std::vector &data) { bool result = true; - if (data.size() != this->uid_.size()) + if (data.size() != this->uid_.size()) { result = false; - else { + } else { for (size_t i = 0; i < data.size(); i++) { if (data[i] != this->uid_[i]) { result = false; diff --git a/esphome/components/remote_base/coolix_protocol.cpp b/esphome/components/remote_base/coolix_protocol.cpp index 3e6e7e185a32..252b6f0e91fa 100644 --- a/esphome/components/remote_base/coolix_protocol.cpp +++ b/esphome/components/remote_base/coolix_protocol.cpp @@ -51,10 +51,11 @@ static bool decode_data(RemoteReceiveData &src, CoolixData &dst) { for (uint32_t mask = 1 << 7; mask; mask >>= 1) { if (!src.expect_mark(BIT_MARK_US)) return false; - if (src.expect_space(BIT_ONE_SPACE_US)) + if (src.expect_space(BIT_ONE_SPACE_US)) { data |= mask; - else if (!src.expect_space(BIT_ZERO_SPACE_US)) + } else if (!src.expect_space(BIT_ZERO_SPACE_US)) { return false; + } } // Check for inverse byte for (uint32_t mask = 1 << 7; mask; mask >>= 1) { diff --git a/esphome/components/remote_base/dish_protocol.cpp b/esphome/components/remote_base/dish_protocol.cpp index 1257e22a45df..47bfdc5c5819 100644 --- a/esphome/components/remote_base/dish_protocol.cpp +++ b/esphome/components/remote_base/dish_protocol.cpp @@ -24,18 +24,20 @@ void DishProtocol::encode(RemoteTransmitData *dst, const DishData &data) { for (uint i = 0; i < 4; i++) { // COMMAND (function, in MSB) for (uint8_t mask = 1UL << 5; mask; mask >>= 1) { - if (data.command & mask) + if (data.command & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } // ADDRESS (unit code, in LSB) for (uint8_t mask = 1UL; mask < 1UL << 4; mask <<= 1) { - if ((data.address - 1) & mask) + if ((data.address - 1) & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } // PADDING for (uint j = 0; j < 6; j++) diff --git a/esphome/components/remote_base/jvc_protocol.cpp b/esphome/components/remote_base/jvc_protocol.cpp index f43a28bdc52f..169b1d00bf10 100644 --- a/esphome/components/remote_base/jvc_protocol.cpp +++ b/esphome/components/remote_base/jvc_protocol.cpp @@ -20,10 +20,11 @@ void JVCProtocol::encode(RemoteTransmitData *dst, const JVCData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) { - if (data.data & mask) + if (data.data & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); diff --git a/esphome/components/remote_base/lg_protocol.cpp b/esphome/components/remote_base/lg_protocol.cpp index a3e7f9828b5b..8040b0f3fc71 100644 --- a/esphome/components/remote_base/lg_protocol.cpp +++ b/esphome/components/remote_base/lg_protocol.cpp @@ -19,10 +19,11 @@ void LGProtocol::encode(RemoteTransmitData *dst, const LGData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << (data.nbits - 1); mask != 0; mask >>= 1) { - if (data.data & mask) + if (data.data & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); diff --git a/esphome/components/remote_base/midea_protocol.cpp b/esphome/components/remote_base/midea_protocol.cpp index bf6742900193..f619d201bc4a 100644 --- a/esphome/components/remote_base/midea_protocol.cpp +++ b/esphome/components/remote_base/midea_protocol.cpp @@ -31,14 +31,16 @@ void MideaProtocol::encode(RemoteTransmitData *dst, const MideaData &src) { dst->set_carrier_frequency(38000); dst->reserve(2 + 48 * 2 + 2 + 2 + 48 * 2 + 1); dst->item(HEADER_MARK_US, HEADER_SPACE_US); - for (unsigned idx = 0; idx < 6; idx++) + for (unsigned idx = 0; idx < 6; idx++) { for (uint8_t mask = 1 << 7; mask; mask >>= 1) dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ONE_SPACE_US : BIT_ZERO_SPACE_US); + } dst->item(FOOTER_MARK_US, FOOTER_SPACE_US); dst->item(HEADER_MARK_US, HEADER_SPACE_US); - for (unsigned idx = 0; idx < 6; idx++) + for (unsigned idx = 0; idx < 6; idx++) { for (uint8_t mask = 1 << 7; mask; mask >>= 1) dst->item(BIT_MARK_US, (src[idx] & mask) ? BIT_ZERO_SPACE_US : BIT_ONE_SPACE_US); + } dst->mark(FOOTER_MARK_US); } @@ -48,10 +50,11 @@ static bool decode_data(RemoteReceiveData &src, MideaData &dst) { for (uint8_t mask = 1 << 7; mask; mask >>= 1) { if (!src.expect_mark(BIT_MARK_US)) return false; - if (src.expect_space(BIT_ONE_SPACE_US)) + if (src.expect_space(BIT_ONE_SPACE_US)) { data |= mask; - else if (!src.expect_space(BIT_ZERO_SPACE_US)) + } else if (!src.expect_space(BIT_ZERO_SPACE_US)) { return false; + } } dst[idx] = data; } diff --git a/esphome/components/remote_base/nec_protocol.cpp b/esphome/components/remote_base/nec_protocol.cpp index 47b4d676dd66..ee3fec59920b 100644 --- a/esphome/components/remote_base/nec_protocol.cpp +++ b/esphome/components/remote_base/nec_protocol.cpp @@ -18,17 +18,19 @@ void NECProtocol::encode(RemoteTransmitData *dst, const NECData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint16_t mask = 1; mask; mask <<= 1) { - if (data.address & mask) + if (data.address & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (uint16_t mask = 1; mask; mask <<= 1) { - if (data.command & mask) + if (data.command & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); diff --git a/esphome/components/remote_base/nexa_protocol.cpp b/esphome/components/remote_base/nexa_protocol.cpp index 814b46135a6c..dbdc835fe39e 100644 --- a/esphome/components/remote_base/nexa_protocol.cpp +++ b/esphome/components/remote_base/nexa_protocol.cpp @@ -41,43 +41,48 @@ void NexaProtocol::encode(RemoteTransmitData *dst, const NexaData &data) { // Device (26 bits) for (int16_t i = 26 - 1; i >= 0; i--) { - if (data.device & (1 << i)) + if (data.device & (1 << i)) { this->one(dst); - else + } else { this->zero(dst); + } } // Group (1 bit) - if (data.group != 0) + if (data.group != 0) { this->one(dst); - else + } else { this->zero(dst); + } // State (1 bit) if (data.state == 2) { // Special case for dimmers...send 00 as state dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); - } else if (data.state == 1) + } else if (data.state == 1) { this->one(dst); - else + } else { this->zero(dst); + } // Channel (4 bits) for (int16_t i = 4 - 1; i >= 0; i--) { - if (data.channel & (1 << i)) + if (data.channel & (1 << i)) { this->one(dst); - else + } else { this->zero(dst); + } } // Level (4 bits) if (data.state == 2) { for (int16_t i = 4 - 1; i >= 0; i--) { - if (data.level & (1 << i)) + if (data.level & (1 << i)) { this->one(dst); - else + } else { this->zero(dst); + } } } diff --git a/esphome/components/remote_base/panasonic_protocol.cpp b/esphome/components/remote_base/panasonic_protocol.cpp index fd4f7c4bf759..fe1060e935f4 100644 --- a/esphome/components/remote_base/panasonic_protocol.cpp +++ b/esphome/components/remote_base/panasonic_protocol.cpp @@ -19,17 +19,19 @@ void PanasonicProtocol::encode(RemoteTransmitData *dst, const PanasonicData &dat uint32_t mask; for (mask = 1UL << 15; mask != 0; mask >>= 1) { - if (data.address & mask) + if (data.address & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (mask = 1UL << 31; mask != 0; mask >>= 1) { - if (data.command & mask) + if (data.command & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); } diff --git a/esphome/components/remote_base/pioneer_protocol.cpp b/esphome/components/remote_base/pioneer_protocol.cpp index 74a3998f116a..5c10eab48d3a 100644 --- a/esphome/components/remote_base/pioneer_protocol.cpp +++ b/esphome/components/remote_base/pioneer_protocol.cpp @@ -42,26 +42,29 @@ void PioneerProtocol::encode(RemoteTransmitData *dst, const PioneerData &data) { command1 = (command1 << 8) | ((~command1) & 0xff); command2 = (command2 << 8) | ((~command2) & 0xff); - if (data.rc_code_2 == 0) + if (data.rc_code_2 == 0) { dst->reserve(68); - else + } else { dst->reserve((68 * 2) + 1); + } dst->set_carrier_frequency(40000); dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (address1 & mask) + if (address1 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (command1 & mask) + if (command1 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); @@ -70,17 +73,19 @@ void PioneerProtocol::encode(RemoteTransmitData *dst, const PioneerData &data) { dst->space(TRAILER_SPACE_US); dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (address2 & mask) + if (address2 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } for (uint32_t mask = 1UL << 15; mask; mask >>= 1) { - if (command2 & mask) + if (command2 & mask) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->mark(BIT_HIGH_US); @@ -140,10 +145,11 @@ optional PioneerProtocol::decode(RemoteReceiveData src) { return data; } void PioneerProtocol::dump(const PioneerData &data) { - if (data.rc_code_2 == 0) + if (data.rc_code_2 == 0) { ESP_LOGD(TAG, "Received Pioneer: rc_code_X=0x%04X", data.rc_code_1); - else + } else { ESP_LOGD(TAG, "Received Pioneer: rc_code_1=0x%04X, rc_code_2=0x%04X", data.rc_code_1, data.rc_code_2); + } } } // namespace remote_base diff --git a/esphome/components/remote_base/raw_protocol.h b/esphome/components/remote_base/raw_protocol.h index 1d9f1c5accf5..054f02ff7cf3 100644 --- a/esphome/components/remote_base/raw_protocol.h +++ b/esphome/components/remote_base/raw_protocol.h @@ -50,10 +50,11 @@ template class RawAction : public RemoteTransmitterActionBasecode_static_ != nullptr) { for (size_t i = 0; i < this->code_static_len_; i++) { auto val = this->code_static_[i]; - if (val < 0) + if (val < 0) { dst->space(static_cast(-val)); - else + } else { dst->mark(static_cast(val)); + } } } else { dst->set_data(this->code_func_(x...)); diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp index 1dc094d552ed..b353f0254e09 100644 --- a/esphome/components/remote_base/rc_switch_protocol.cpp +++ b/esphome/components/remote_base/rc_switch_protocol.cpp @@ -57,10 +57,11 @@ void RCSwitchBase::transmit(RemoteTransmitData *dst, uint64_t code, uint8_t len) dst->set_carrier_frequency(0); this->sync(dst); for (int16_t i = len - 1; i >= 0; i--) { - if (code & ((uint64_t) 1 << i)) + if (code & ((uint64_t) 1 << i)) { this->one(dst); - else + } else { this->zero(dst); + } } } @@ -148,10 +149,11 @@ void RCSwitchBase::simple_code_to_tristate(uint16_t code, uint8_t nbits, uint64_ *out_code = 0; for (int8_t i = nbits - 1; i >= 0; i--) { *out_code <<= 2; - if (code & (1 << i)) + if (code & (1 << i)) { *out_code |= 0b01; - else + } else { *out_code |= 0b00; + } } } void RCSwitchBase::type_a_code(uint8_t switch_group, uint8_t switch_device, bool state, uint64_t *out_code, diff --git a/esphome/components/remote_base/remote_base.h b/esphome/components/remote_base/remote_base.h index e1af41274e1a..3c76da84e3c9 100644 --- a/esphome/components/remote_base/remote_base.h +++ b/esphome/components/remote_base/remote_base.h @@ -284,7 +284,7 @@ class RemoteReceiverBinarySensorBase : public binary_sensor::BinarySensorInitial public Component, public RemoteReceiverListener { public: - explicit RemoteReceiverBinarySensorBase() : BinarySensorInitiallyOff() {} + explicit RemoteReceiverBinarySensorBase() {} void dump_config() override; virtual bool matches(RemoteReceiveData src) = 0; bool on_receive(RemoteReceiveData src) override { diff --git a/esphome/components/remote_base/samsung_protocol.cpp b/esphome/components/remote_base/samsung_protocol.cpp index 4571f332b353..20e8285a03f1 100644 --- a/esphome/components/remote_base/samsung_protocol.cpp +++ b/esphome/components/remote_base/samsung_protocol.cpp @@ -22,10 +22,11 @@ void SamsungProtocol::encode(RemoteTransmitData *dst, const SamsungData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint8_t bit = data.nbits; bit > 0; bit--) { - if ((data.data >> (bit - 1)) & 1) + if ((data.data >> (bit - 1)) & 1) { dst->item(BIT_HIGH_US, BIT_ONE_LOW_US); - else + } else { dst->item(BIT_HIGH_US, BIT_ZERO_LOW_US); + } } dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); diff --git a/esphome/components/remote_base/sony_protocol.cpp b/esphome/components/remote_base/sony_protocol.cpp index 3bb643266a77..8634cf7d6188 100644 --- a/esphome/components/remote_base/sony_protocol.cpp +++ b/esphome/components/remote_base/sony_protocol.cpp @@ -19,10 +19,11 @@ void SonyProtocol::encode(RemoteTransmitData *dst, const SonyData &data) { dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint32_t mask = 1UL << (data.nbits - 1); mask != 0; mask >>= 1) { - if (data.data & mask) + if (data.data & mask) { dst->item(BIT_ONE_HIGH_US, BIT_LOW_US); - else + } else { dst->item(BIT_ZERO_HIGH_US, BIT_LOW_US); + } } } optional SonyProtocol::decode(RemoteReceiveData src) { diff --git a/esphome/components/remote_base/toshiba_ac_protocol.cpp b/esphome/components/remote_base/toshiba_ac_protocol.cpp index bd1d2a8f5b93..1a19f534f87e 100644 --- a/esphome/components/remote_base/toshiba_ac_protocol.cpp +++ b/esphome/components/remote_base/toshiba_ac_protocol.cpp @@ -24,10 +24,11 @@ void ToshibaAcProtocol::encode(RemoteTransmitData *dst, const ToshibaAcData &dat dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint8_t bit = 48; bit > 0; bit--) { dst->mark(BIT_HIGH_US); - if ((data.rc_code_1 >> (bit - 1)) & 1) + if ((data.rc_code_1 >> (bit - 1)) & 1) { dst->space(BIT_ONE_LOW_US); - else + } else { dst->space(BIT_ZERO_LOW_US); + } } dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); } @@ -36,10 +37,11 @@ void ToshibaAcProtocol::encode(RemoteTransmitData *dst, const ToshibaAcData &dat dst->item(HEADER_HIGH_US, HEADER_LOW_US); for (uint8_t bit = 48; bit > 0; bit--) { dst->mark(BIT_HIGH_US); - if ((data.rc_code_2 >> (bit - 1)) & 1) + if ((data.rc_code_2 >> (bit - 1)) & 1) { dst->space(BIT_ONE_LOW_US); - else + } else { dst->space(BIT_ZERO_LOW_US); + } } dst->item(FOOTER_HIGH_US, FOOTER_LOW_US); } @@ -102,10 +104,11 @@ optional ToshibaAcProtocol::decode(RemoteReceiveData src) { } void ToshibaAcProtocol::dump(const ToshibaAcData &data) { - if (data.rc_code_2 != 0) + if (data.rc_code_2 != 0) { ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64 ", rc_code_2=0x%" PRIX64, data.rc_code_1, data.rc_code_2); - else + } else { ESP_LOGD(TAG, "Received Toshiba AC: rc_code_1=0x%" PRIX64, data.rc_code_1); + } } } // namespace remote_base diff --git a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp index cf2c15402ed6..8700fcf0bbc9 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp8266.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp8266.cpp @@ -79,9 +79,10 @@ void RemoteReceiverComponent::loop() { if (dist <= 1) return; const uint32_t now = micros(); - if (now - s.buffer[write_at] < this->idle_us_) + if (now - s.buffer[write_at] < this->idle_us_) { // The last change was fewer than the configured idle time ago. return; + } ESP_LOGVV(TAG, "read_at=%u write_at=%u dist=%u now=%u end=%u", s.buffer_read_at, write_at, dist, now, s.buffer[write_at]); diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp index 39752cac5b4f..1c0eb94e6140 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp @@ -35,10 +35,11 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen void RemoteTransmitterComponent::await_target_time_() { const uint32_t current_time = micros(); - if (this->target_time_ == 0) + if (this->target_time_ == 0) { this->target_time_ = current_time; - else if (this->target_time_ > current_time) + } else if (this->target_time_ > current_time) { delayMicroseconds(this->target_time_ - current_time); + } } void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) { diff --git a/esphome/components/resistance/resistance_sensor.cpp b/esphome/components/resistance/resistance_sensor.cpp index 4d3dfa5928bf..e13959fd3775 100644 --- a/esphome/components/resistance/resistance_sensor.cpp +++ b/esphome/components/resistance/resistance_sensor.cpp @@ -20,16 +20,18 @@ void ResistanceSensor::process_(float value) { float res = 0; switch (this->configuration_) { case UPSTREAM: - if (value == 0.0f) + if (value == 0.0f) { res = NAN; - else + } else { res = (this->reference_voltage_ - value) / value; + } break; case DOWNSTREAM: - if (value == this->reference_voltage_) + if (value == this->reference_voltage_) { res = NAN; - else + } else { res = value / (this->reference_voltage_ - value); + } break; } diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp index aff8fc381c2c..c5227b41a721 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.cpp +++ b/esphome/components/rotary_encoder/rotary_encoder.cpp @@ -104,7 +104,7 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore } if (rotation_dir != 0) { - auto first_zero = std::find(arg->rotation_events.begin(), arg->rotation_events.end(), 0); // find first zero + auto *first_zero = std::find(arg->rotation_events.begin(), arg->rotation_events.end(), 0); // find first zero if (first_zero == arg->rotation_events.begin() // are we at the start (first event this loop iteration) || std::signbit(*std::prev(first_zero)) != std::signbit(rotation_dir) // or is the last stored event the wrong direction diff --git a/esphome/components/rtttl/rtttl.cpp b/esphome/components/rtttl/rtttl.cpp index c76d4a89b01f..6274e69ba34c 100644 --- a/esphome/components/rtttl/rtttl.cpp +++ b/esphome/components/rtttl/rtttl.cpp @@ -104,10 +104,11 @@ void Rtttl::loop() { // first, get note duration, if available uint8_t num = this->get_integer_(); - if (num) + if (num) { note_duration_ = wholenote_ / num; - else + } else { note_duration_ = wholenote_ / default_duration_; // we will need to check if we are a dotted note after + } uint8_t note; diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp index 272ee75e303b..8603072bd5a3 100644 --- a/esphome/components/scd30/scd30.cpp +++ b/esphome/components/scd30/scd30.cpp @@ -229,18 +229,20 @@ uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index eacb39edf14b..4bd512394fe1 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -233,18 +233,20 @@ uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp index 107ed2902f7f..eb1543f2c2ea 100644 --- a/esphome/components/sdp3x/sdp3x.cpp +++ b/esphome/components/sdp3x/sdp3x.cpp @@ -138,10 +138,11 @@ bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t chec for (int i = 0; i < size; i++) { crc ^= (data[i]); for (uint8_t bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x31; - else + } else { crc = (crc << 1); + } } } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index 6113cca1fd2a..db655ea34e7c 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -38,7 +38,7 @@ class SelectCall { class SelectTraits { public: void set_options(std::vector options) { this->options_ = std::move(options); } - const std::vector get_options() const { return this->options_; } + std::vector get_options() const { return this->options_; } protected: std::vector options_; diff --git a/esphome/components/sensor/filter.cpp b/esphome/components/sensor/filter.cpp index 7a8a5572730d..49d2c648b0d2 100644 --- a/esphome/components/sensor/filter.cpp +++ b/esphome/components/sensor/filter.cpp @@ -176,10 +176,11 @@ optional SlidingWindowMovingAverageFilter::new_value(float value) { this->sum_ += value; } float average; - if (this->queue_.empty()) + if (this->queue_.empty()) { average = 0.0f; - else + } else { average = this->sum_ / this->queue_.size(); + } ESP_LOGVV(TAG, "SlidingWindowMovingAverageFilter(%p)::new_value(%f) -> %f", this, value, average); if (++this->send_at_ % this->send_every_ == 0) { @@ -203,10 +204,11 @@ ExponentialMovingAverageFilter::ExponentialMovingAverageFilter(float alpha, size : send_every_(send_every), send_at_(send_every - 1), alpha_(alpha) {} optional ExponentialMovingAverageFilter::new_value(float value) { if (!std::isnan(value)) { - if (this->first_value_) + if (this->first_value_) { this->accumulator_ = value; - else + } else { this->accumulator_ = (this->alpha_ * value) + (1.0f - this->alpha_) * this->accumulator_; + } this->first_value_ = false; } @@ -274,25 +276,26 @@ FilterOutValueFilter::FilterOutValueFilter(float value_to_filter_out) : value_to optional FilterOutValueFilter::new_value(float value) { if (std::isnan(this->value_to_filter_out_)) { - if (std::isnan(value)) + if (std::isnan(value)) { return {}; - else + } else { return value; + } } else { int8_t accuracy = this->parent_->get_accuracy_decimals(); float accuracy_mult = powf(10.0f, accuracy); float rounded_filter_out = roundf(accuracy_mult * this->value_to_filter_out_); float rounded_value = roundf(accuracy_mult * value); - if (rounded_filter_out == rounded_value) + if (rounded_filter_out == rounded_value) { return {}; - else + } else { return value; + } } } // ThrottleFilter -ThrottleFilter::ThrottleFilter(uint32_t min_time_between_inputs) - : Filter(), min_time_between_inputs_(min_time_between_inputs) {} +ThrottleFilter::ThrottleFilter(uint32_t min_time_between_inputs) : min_time_between_inputs_(min_time_between_inputs) {} optional ThrottleFilter::new_value(float value) { const uint32_t now = millis(); if (this->last_input_ == 0 || now - this->last_input_ >= min_time_between_inputs_) { diff --git a/esphome/components/servo/servo.cpp b/esphome/components/servo/servo.cpp index 2e1ba587a279..78fc45c67938 100644 --- a/esphome/components/servo/servo.cpp +++ b/esphome/components/servo/servo.cpp @@ -65,10 +65,11 @@ void Servo::write(float value) { void Servo::internal_write(float value) { value = clamp(value, -1.0f, 1.0f); float level; - if (value < 0.0) + if (value < 0.0) { level = lerp(-value, this->idle_level_, this->min_level_); - else + } else { level = lerp(value, this->idle_level_, this->max_level_); + } this->output_->set_level(level); if (this->target_value_ == this->current_value_) { this->save_level_(level); diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 4157fd55cf83..b55097fcd0a2 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -220,9 +220,10 @@ void SGP30Component::write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_b data[6] = sht_crc_(data[4], data[5]); if (!this->write_bytes(SGP30_CMD_SET_IAQ_BASELINE >> 8, data, 7)) { ESP_LOGE(TAG, "Error applying eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, tvoc_baseline); - } else + } else { ESP_LOGI(TAG, "Initial baselines applied successfully! eCO2 baseline: 0x%04X, TVOC baseline: 0x%04X", eco2_baseline, tvoc_baseline); + } } void SGP30Component::dump_config() { @@ -315,18 +316,20 @@ uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sgp40/sgp40.cpp b/esphome/components/sgp40/sgp40.cpp index da6659c90fed..829c00a2180c 100644 --- a/esphome/components/sgp40/sgp40.cpp +++ b/esphome/components/sgp40/sgp40.cpp @@ -229,10 +229,11 @@ uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) { for (uint8_t i = 0; i < datalen; i++) { crc ^= data[i]; for (uint8_t b = 0; b < 8; b++) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL; - else + } else { crc <<= 1; + } } } return crc; @@ -303,18 +304,20 @@ uint8_t SGP40Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sht3xd/sht3xd.cpp b/esphome/components/sht3xd/sht3xd.cpp index 56a43d516126..e7981b64cfe0 100644 --- a/esphome/components/sht3xd/sht3xd.cpp +++ b/esphome/components/sht3xd/sht3xd.cpp @@ -82,18 +82,20 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/shtcx/shtcx.cpp b/esphome/components/shtcx/shtcx.cpp index f2fb6bd5c3bd..867c26df1df9 100644 --- a/esphome/components/shtcx/shtcx.cpp +++ b/esphome/components/shtcx/shtcx.cpp @@ -112,18 +112,20 @@ uint8_t sht_crc(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index 21e9ac4a50c7..4f738b0a8c48 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -49,8 +49,8 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { void dial(const std::string &recipient); protected: - void send_cmd_(const std::string &); - void parse_cmd_(std::string); + void send_cmd_(const std::string &message); + void parse_cmd_(std::string message); std::string sender_; char read_buffer_[SIM800L_READ_BUFFER_LENGTH]; diff --git a/esphome/components/slow_pwm/output.py b/esphome/components/slow_pwm/output.py index 0ce1c9f9e213..9cce35254b17 100644 --- a/esphome/components/slow_pwm/output.py +++ b/esphome/components/slow_pwm/output.py @@ -15,6 +15,7 @@ SlowPWMOutput = slow_pwm_ns.class_("SlowPWMOutput", output.FloatOutput, cg.Component) CONF_STATE_CHANGE_ACTION = "state_change_action" +CONF_RESTART_CYCLE_ON_STATE_CHANGE = "restart_cycle_on_state_change" CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( { @@ -37,6 +38,7 @@ cv.positive_time_period_milliseconds, cv.Range(min=core.TimePeriod(milliseconds=100)), ), + cv.Optional(CONF_RESTART_CYCLE_ON_STATE_CHANGE, default=False): cv.boolean, } ).extend(cv.COMPONENT_SCHEMA) @@ -63,3 +65,8 @@ async def to_code(config): ) cg.add(var.set_period(config[CONF_PERIOD])) + cg.add( + var.set_restart_cycle_on_state_change( + config[CONF_RESTART_CYCLE_ON_STATE_CHANGE] + ) + ) diff --git a/esphome/components/slow_pwm/slow_pwm_output.cpp b/esphome/components/slow_pwm/slow_pwm_output.cpp index 573adbe3dc7d..9cfeb54153fe 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.cpp +++ b/esphome/components/slow_pwm/slow_pwm_output.cpp @@ -18,6 +18,12 @@ void SlowPWMOutput::set_output_state_(bool new_state) { this->pin_->digital_write(new_state); } if (new_state != current_state_) { + if (this->pin_) { + ESP_LOGV(TAG, "Switching output pin %s to %s", this->pin_->dump_summary().c_str(), ONOFF(new_state)); + } else { + ESP_LOGV(TAG, "Switching to %s", ONOFF(new_state)); + } + if (this->state_change_trigger_) { this->state_change_trigger_->trigger(new_state); } @@ -36,16 +42,12 @@ void SlowPWMOutput::loop() { uint32_t now = millis(); float scaled_state = this->state_ * this->period_; - if (now - this->period_start_time_ >= this->period_) { + if (now >= this->period_start_time_ + this->period_) { ESP_LOGVV(TAG, "End of period. State: %f, Scaled state: %f", this->state_, scaled_state); this->period_start_time_ += this->period_; } - if (scaled_state > now - this->period_start_time_) { - this->set_output_state_(true); - } else { - this->set_output_state_(false); - } + this->set_output_state_(now < this->period_start_time_ + scaled_state); } void SlowPWMOutput::dump_config() { @@ -58,8 +60,15 @@ void SlowPWMOutput::dump_config() { if (this->turn_off_trigger_) ESP_LOGCONFIG(TAG, " Turn off automation configured"); ESP_LOGCONFIG(TAG, " Period: %d ms", this->period_); + ESP_LOGCONFIG(TAG, " Restart cycle on state change: %s", YESNO(this->restart_cycle_on_state_change_)); LOG_FLOAT_OUTPUT(this); } +void SlowPWMOutput::write_state(float state) { + this->state_ = state; + if (this->restart_cycle_on_state_change_) + this->period_start_time_ = millis(); +} + } // namespace slow_pwm } // namespace esphome diff --git a/esphome/components/slow_pwm/slow_pwm_output.h b/esphome/components/slow_pwm/slow_pwm_output.h index d5c5883f2501..be45736864c0 100644 --- a/esphome/components/slow_pwm/slow_pwm_output.h +++ b/esphome/components/slow_pwm/slow_pwm_output.h @@ -11,6 +11,10 @@ class SlowPWMOutput : public output::FloatOutput, public Component { public: void set_pin(GPIOPin *pin) { pin_ = pin; }; void set_period(unsigned int period) { period_ = period; }; + void set_restart_cycle_on_state_change(bool restart_cycle_on_state_change) { + restart_cycle_on_state_change_ = restart_cycle_on_state_change; + } + /// Initialize pin void setup() override; void dump_config() override; @@ -37,7 +41,7 @@ class SlowPWMOutput : public output::FloatOutput, public Component { protected: void loop() override; - void write_state(float state) override { state_ = state; } + void write_state(float state) override; /// turn on/off the configured output void set_output_state_(bool state); @@ -48,7 +52,8 @@ class SlowPWMOutput : public output::FloatOutput, public Component { float state_{0}; bool current_state_{false}; unsigned int period_start_time_{0}; - unsigned int period_{5000}; + unsigned int period_; + bool restart_cycle_on_state_change_; }; } // namespace slow_pwm diff --git a/esphome/components/socket/bsd_sockets_impl.cpp b/esphome/components/socket/bsd_sockets_impl.cpp index 1db24973e79c..6636bcb3ebc5 100644 --- a/esphome/components/socket/bsd_sockets_impl.cpp +++ b/esphome/components/socket/bsd_sockets_impl.cpp @@ -35,7 +35,7 @@ std::string format_sockaddr(const struct sockaddr_storage &storage) { class BSDSocketImpl : public Socket { public: - BSDSocketImpl(int fd) : Socket(), fd_(fd) {} + BSDSocketImpl(int fd) : fd_(fd) {} ~BSDSocketImpl() override { if (!closed_) { close(); // NOLINT(clang-analyzer-optin.cplusplus.VirtualCall) @@ -88,9 +88,10 @@ class BSDSocketImpl : public Socket { for (int i = 0; i < iovcnt; i++) { ssize_t err = this->read(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); if (err == -1) { - if (ret != 0) + if (ret != 0) { // if we already read some don't return an error break; + } return err; } ret += err; @@ -115,9 +116,10 @@ class BSDSocketImpl : public Socket { ssize_t err = this->send(reinterpret_cast(iov[i].iov_base), iov[i].iov_len, i == iovcnt - 1 ? 0 : MSG_MORE); if (err == -1) { - if (ret != 0) + if (ret != 0) { // if we already wrote some don't return an error break; + } return err; } ret += err; diff --git a/esphome/components/socket/lwip_raw_tcp_impl.cpp b/esphome/components/socket/lwip_raw_tcp_impl.cpp index d57413c739af..f5bb57bb9306 100644 --- a/esphome/components/socket/lwip_raw_tcp_impl.cpp +++ b/esphome/components/socket/lwip_raw_tcp_impl.cpp @@ -28,7 +28,7 @@ static const char *const TAG = "socket.lwip"; class LWIPRawImpl : public Socket { public: - LWIPRawImpl(struct tcp_pcb *pcb) : pcb_(pcb) {} + LWIPRawImpl(sa_family_t family, struct tcp_pcb *pcb) : pcb_(pcb), family_(family) {} ~LWIPRawImpl() override { if (pcb_ != nullptr) { LWIP_LOG("tcp_abort(%p)", pcb_); @@ -73,10 +73,9 @@ class LWIPRawImpl : public Socket { } ip_addr_t ip; in_port_t port; - auto family = name->sa_family; #if LWIP_IPV6 - if (family == AF_INET) { - if (addrlen < sizeof(sockaddr_in6)) { + if (family_ == AF_INET) { + if (addrlen < sizeof(sockaddr_in)) { errno = EINVAL; return -1; } @@ -84,30 +83,31 @@ class LWIPRawImpl : public Socket { port = ntohs(addr4->sin_port); ip.type = IPADDR_TYPE_V4; ip.u_addr.ip4.addr = addr4->sin_addr.s_addr; - - } else if (family == AF_INET6) { - if (addrlen < sizeof(sockaddr_in)) { + LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip4addr_ntoa(&ip.u_addr.ip4), port); + } else if (family_ == AF_INET6) { + if (addrlen < sizeof(sockaddr_in6)) { errno = EINVAL; return -1; } auto *addr6 = reinterpret_cast(name); port = ntohs(addr6->sin6_port); - ip.type = IPADDR_TYPE_V6; + ip.type = IPADDR_TYPE_ANY; memcpy(&ip.u_addr.ip6.addr, &addr6->sin6_addr.un.u8_addr, 16); + LWIP_LOG("tcp_bind(%p ip=%s port=%u)", pcb_, ip6addr_ntoa(&ip.u_addr.ip6), port); } else { errno = EINVAL; return -1; } #else - if (family != AF_INET) { + if (family_ != AF_INET) { errno = EINVAL; return -1; } auto *addr4 = reinterpret_cast(name); port = ntohs(addr4->sin_port); ip.addr = addr4->sin_addr.s_addr; -#endif LWIP_LOG("tcp_bind(%p ip=%u port=%u)", pcb_, ip.addr, port); +#endif err_t err = tcp_bind(pcb_, &ip, port); if (err == ERR_USE) { LWIP_LOG(" -> err ERR_USE"); @@ -178,26 +178,22 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - if (*addrlen < sizeof(struct sockaddr_in)) { - errno = EINVAL; - return -1; - } - struct sockaddr_in *addr = reinterpret_cast(name); - addr->sin_family = AF_INET; - *addrlen = addr->sin_len = sizeof(struct sockaddr_in); - addr->sin_port = pcb_->remote_port; - addr->sin_addr.s_addr = pcb_->remote_ip.addr; - return 0; + return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); } std::string getpeername() override { if (pcb_ == nullptr) { errno = ECONNRESET; return ""; } - char buffer[24]; - uint32_t ip4 = pcb_->remote_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, - (ip4 >> 24) & 0xFF); + char buffer[50] = {}; + if (IP_IS_V4_VAL(pcb_->remote_ip)) { + inet_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer)); + } +#if LWIP_IPV6 + else if (IP_IS_V6_VAL(pcb_->remote_ip)) { + inet6_ntoa_r(pcb_->remote_ip, buffer, sizeof(buffer)); + } +#endif return std::string(buffer); } int getsockname(struct sockaddr *name, socklen_t *addrlen) override { @@ -209,26 +205,22 @@ class LWIPRawImpl : public Socket { errno = EINVAL; return -1; } - if (*addrlen < sizeof(struct sockaddr_in)) { - errno = EINVAL; - return -1; - } - struct sockaddr_in *addr = reinterpret_cast(name); - addr->sin_family = AF_INET; - *addrlen = addr->sin_len = sizeof(struct sockaddr_in); - addr->sin_port = pcb_->local_port; - addr->sin_addr.s_addr = pcb_->local_ip.addr; - return 0; + return this->ip2sockaddr_(&pcb_->local_ip, pcb_->local_port, name, addrlen); } std::string getsockname() override { if (pcb_ == nullptr) { errno = ECONNRESET; return ""; } - char buffer[24]; - uint32_t ip4 = pcb_->local_ip.addr; - snprintf(buffer, sizeof(buffer), "%d.%d.%d.%d", (ip4 >> 0) & 0xFF, (ip4 >> 8) & 0xFF, (ip4 >> 16) & 0xFF, - (ip4 >> 24) & 0xFF); + char buffer[50] = {}; + if (IP_IS_V4_VAL(pcb_->local_ip)) { + inet_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer)); + } +#if LWIP_IPV6 + else if (IP_IS_V6_VAL(pcb_->local_ip)) { + inet6_ntoa_r(pcb_->local_ip, buffer, sizeof(buffer)); + } +#endif return std::string(buffer); } int getsockopt(int level, int optname, void *optval, socklen_t *optlen) override { @@ -497,7 +489,7 @@ class LWIPRawImpl : public Socket { // nothing to do here, we just don't push it to the queue return ERR_OK; } - auto sock = make_unique(newpcb); + auto sock = make_unique(family_, newpcb); sock->init(); accepted_sockets_.push(std::move(sock)); return ERR_OK; @@ -549,6 +541,46 @@ class LWIPRawImpl : public Socket { } protected: + int ip2sockaddr_(ip_addr_t *ip, uint16_t port, struct sockaddr *name, socklen_t *addrlen) { + if (family_ == AF_INET) { + if (*addrlen < sizeof(struct sockaddr_in)) { + errno = EINVAL; + return -1; + } + + struct sockaddr_in *addr = reinterpret_cast(name); + addr->sin_family = AF_INET; + *addrlen = addr->sin_len = sizeof(struct sockaddr_in); + addr->sin_port = port; + inet_addr_from_ip4addr(&addr->sin_addr, ip_2_ip4(ip)); + return 0; + } +#if LWIP_IPV6 + else if (family_ == AF_INET6) { + if (*addrlen < sizeof(struct sockaddr_in6)) { + errno = EINVAL; + return -1; + } + + struct sockaddr_in6 *addr = reinterpret_cast(name); + addr->sin6_family = AF_INET6; + *addrlen = addr->sin6_len = sizeof(struct sockaddr_in6); + addr->sin6_port = port; + + // AF_INET6 sockets are bound to IPv4 as well, so we may encounter IPv4 addresses that must be converted to IPv6. + if (IP_IS_V4(ip)) { + ip_addr_t mapped; + ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&mapped), ip_2_ip4(ip)); + inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(&mapped)); + } else { + inet6_addr_from_ip6addr(&addr->sin6_addr, ip_2_ip6(ip)); + } + return 0; + } +#endif + return -1; + } + struct tcp_pcb *pcb_; std::queue> accepted_sockets_; bool rx_closed_ = false; @@ -557,13 +589,14 @@ class LWIPRawImpl : public Socket { // don't use lwip nodelay flag, it sometimes causes reconnect // instead use it for determining whether to call lwip_output bool nodelay_ = false; + sa_family_t family_ = 0; }; std::unique_ptr socket(int domain, int type, int protocol) { auto *pcb = tcp_new(); if (pcb == nullptr) return nullptr; - auto *sock = new LWIPRawImpl(pcb); // NOLINT(cppcoreguidelines-owning-memory) + auto *sock = new LWIPRawImpl((sa_family_t) domain, pcb); // NOLINT(cppcoreguidelines-owning-memory) sock->init(); return std::unique_ptr{sock}; } diff --git a/esphome/components/socket/socket.cpp b/esphome/components/socket/socket.cpp new file mode 100644 index 000000000000..22a4c11df88d --- /dev/null +++ b/esphome/components/socket/socket.cpp @@ -0,0 +1,43 @@ +#include "socket.h" +#include "esphome/core/log.h" +#include +#include + +namespace esphome { +namespace socket { + +std::unique_ptr socket_ip(int type, int protocol) { +#if LWIP_IPV6 + return socket(AF_INET6, type, protocol); +#else + return socket(AF_INET, type, protocol); +#endif +} + +socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port) { +#if LWIP_IPV6 + if (addrlen < sizeof(sockaddr_in6)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in6)); + server->sin6_family = AF_INET6; + server->sin6_port = port; + server->sin6_addr = in6addr_any; + return sizeof(sockaddr_in6); +#else + if (addrlen < sizeof(sockaddr_in)) { + errno = EINVAL; + return 0; + } + auto *server = reinterpret_cast(addr); + memset(server, 0, sizeof(sockaddr_in)); + server->sin_family = AF_INET; + server->sin_addr.s_addr = ESPHOME_INADDR_ANY; + server->sin_port = port; + return sizeof(sockaddr_in); +#endif +} +} // namespace socket +} // namespace esphome diff --git a/esphome/components/socket/socket.h b/esphome/components/socket/socket.h index 9920610bf550..ecf117deeb5b 100644 --- a/esphome/components/socket/socket.h +++ b/esphome/components/socket/socket.h @@ -38,7 +38,14 @@ class Socket { virtual int loop() { return 0; }; }; +/// Create a socket of the given domain, type and protocol. std::unique_ptr socket(int domain, int type, int protocol); +/// Create a socket in the newest available IP domain (IPv6 or IPv4) of the given type and protocol. +std::unique_ptr socket_ip(int type, int protocol); + +/// Set a sockaddr to the any address for the IP version used by socket_ip(). +socklen_t set_sockaddr_any(struct sockaddr *addr, socklen_t addrlen, uint16_t port); + } // namespace socket } // namespace esphome diff --git a/esphome/components/speed/fan/__init__.py b/esphome/components/speed/fan/__init__.py index 0ee31c76a03c..978e68d1e91c 100644 --- a/esphome/components/speed/fan/__init__.py +++ b/esphome/components/speed/fan/__init__.py @@ -11,7 +11,7 @@ ) from .. import speed_ns -SpeedFan = speed_ns.class_("SpeedFan", cg.Component) +SpeedFan = speed_ns.class_("SpeedFan", cg.Component, fan.Fan) CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( { @@ -29,11 +29,9 @@ async def to_code(config): output_ = await cg.get_variable(config[CONF_OUTPUT]) - state = await fan.create_fan_state(config) - var = cg.new_Pvariable( - config[CONF_OUTPUT_ID], state, output_, config[CONF_SPEED_COUNT] - ) + var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT]) await cg.register_component(var, config) + await fan.register_fan(var, config) if CONF_OSCILLATION_OUTPUT in config: oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) diff --git a/esphome/components/speed/fan/speed_fan.cpp b/esphome/components/speed/fan/speed_fan.cpp index cb10db4ed44e..9ed201982a29 100644 --- a/esphome/components/speed/fan/speed_fan.cpp +++ b/esphome/components/speed/fan/speed_fan.cpp @@ -7,59 +7,39 @@ namespace speed { static const char *const TAG = "speed.fan"; -void SpeedFan::dump_config() { - ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str()); - if (this->fan_->get_traits().supports_oscillation()) { - ESP_LOGCONFIG(TAG, " Oscillation: YES"); - } - if (this->fan_->get_traits().supports_direction()) { - ESP_LOGCONFIG(TAG, " Direction: YES"); +void SpeedFan::setup() { + auto restore = this->restore_state_(); + if (restore.has_value()) { + restore->apply(*this); + this->write_state_(); } } -void SpeedFan::setup() { - auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); - this->fan_->set_traits(traits); - this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); +void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); } +fan::FanTraits SpeedFan::get_traits() { + return fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); } -void SpeedFan::loop() { - if (!this->next_update_) { - return; - } - this->next_update_ = false; - - { - float speed = 0.0f; - if (this->fan_->state) { - speed = static_cast(this->fan_->speed) / static_cast(this->speed_count_); - } - ESP_LOGD(TAG, "Setting speed: %.2f", speed); - this->output_->set_level(speed); - } - - if (this->oscillating_ != nullptr) { - bool enable = this->fan_->oscillating; - if (enable) { - this->oscillating_->turn_on(); - } else { - this->oscillating_->turn_off(); - } - ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); - } - - if (this->direction_ != nullptr) { - bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; - if (enable) { - this->direction_->turn_on(); - } else { - this->direction_->turn_off(); - } - ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); - } +void SpeedFan::control(const fan::FanCall &call) { + if (call.get_state().has_value()) + this->state = *call.get_state(); + if (call.get_speed().has_value()) + this->speed = *call.get_speed(); + if (call.get_oscillating().has_value()) + this->oscillating = *call.get_oscillating(); + if (call.get_direction().has_value()) + this->direction = *call.get_direction(); + + this->write_state_(); + this->publish_state(); +} +void SpeedFan::write_state_() { + float speed = this->state ? static_cast(this->speed) / static_cast(this->speed_count_) : 0.0f; + this->output_->set_level(speed); + + if (this->oscillating_ != nullptr) + this->oscillating_->set_state(this->oscillating); + if (this->direction_ != nullptr) + this->direction_->set_state(this->direction == fan::FanDirection::REVERSE); } - -// We need a higher priority than the FanState component to make sure that the traits are set -// when that component sets itself up. -float SpeedFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } } // namespace speed } // namespace esphome diff --git a/esphome/components/speed/fan/speed_fan.h b/esphome/components/speed/fan/speed_fan.h index 6b7fa0b0f2a6..1fad53813a7f 100644 --- a/esphome/components/speed/fan/speed_fan.h +++ b/esphome/components/speed/fan/speed_fan.h @@ -3,28 +3,27 @@ #include "esphome/core/component.h" #include "esphome/components/output/binary_output.h" #include "esphome/components/output/float_output.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace speed { -class SpeedFan : public Component { +class SpeedFan : public Component, public fan::Fan { public: - SpeedFan(fan::FanState *fan, output::FloatOutput *output, int speed_count) - : fan_(fan), output_(output), speed_count_(speed_count) {} + SpeedFan(output::FloatOutput *output, int speed_count) : output_(output), speed_count_(speed_count) {} void setup() override; - void loop() override; void dump_config() override; - float get_setup_priority() const override; void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } + fan::FanTraits get_traits() override; protected: - fan::FanState *fan_; + void control(const fan::FanCall &call) override; + void write_state_(); + output::FloatOutput *output_; output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *direction_{nullptr}; - bool next_update_{true}; int speed_count_{}; }; diff --git a/esphome/components/spi/spi.cpp b/esphome/components/spi/spi.cpp index d427e2c91b62..95280f23b9db 100644 --- a/esphome/components/spi/spi.cpp +++ b/esphome/components/spi/spi.cpp @@ -129,10 +129,11 @@ uint8_t HOT SPIComponent::transfer_(uint8_t data) { for (uint8_t i = 0; i < 8; i++) { uint8_t shift; - if (BIT_ORDER == BIT_ORDER_MSB_FIRST) + if (BIT_ORDER == BIT_ORDER_MSB_FIRST) { shift = 7 - i; - else + } else { shift = i; + } if (CLOCK_PHASE == CLOCK_PHASE_LEADING) { // sampling on leading edge diff --git a/esphome/components/sps30/sps30.cpp b/esphome/components/sps30/sps30.cpp index 6160120564ef..2bd7bcb4584f 100644 --- a/esphome/components/sps30/sps30.cpp +++ b/esphome/components/sps30/sps30.cpp @@ -216,18 +216,20 @@ uint8_t SPS30Component::sht_crc_(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp index 5ff220fce94d..2ba990637e42 100644 --- a/esphome/components/ssd1306_base/ssd1306_base.cpp +++ b/esphome/components/ssd1306_base/ssd1306_base.cpp @@ -66,10 +66,11 @@ void SSD1306::setup() { if (!this->is_ssd1305_()) { // Enable charge pump (0x8D) this->command(SSD1306_COMMAND_CHARGE_PUMP); - if (this->external_vcc_) + if (this->external_vcc_) { this->command(0x10); - else + } else { this->command(0x14); + } } // Set addressing mode to horizontal (0x20) @@ -105,10 +106,11 @@ void SSD1306::setup() { // Pre-charge period (0xD9) this->command(SSD1306_COMMAND_SET_PRE_CHARGE); - if (this->external_vcc_) + if (this->external_vcc_) { this->command(0x22); - else + } else { this->command(0xF1); + } // Set V_COM (0xDB) this->command(SSD1306_COMMAND_SET_VCOM_DETECT); diff --git a/esphome/components/ssd1325_base/ssd1325_base.cpp b/esphome/components/ssd1325_base/ssd1325_base.cpp index 60e46f573fcf..711f9e5197ff 100644 --- a/esphome/components/ssd1325_base/ssd1325_base.cpp +++ b/esphome/components/ssd1325_base/ssd1325_base.cpp @@ -51,24 +51,27 @@ void SSD1325::setup() { this->command(SSD1325_SETCLOCK); // set osc division this->command(0xF1); // 145 this->command(SSD1325_SETMULTIPLEX); // multiplex ratio - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(0x7f); // duty = height - 1 - else - this->command(0x3f); // duty = 1/64 + } else { + this->command(0x3f); // duty = 1/64 + } this->command(SSD1325_SETOFFSET); // set display offset - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(0x00); // 0 - else - this->command(0x4C); // 76 + } else { + this->command(0x4C); // 76 + } this->command(SSD1325_SETSTARTLINE); // set start line this->command(0x00); // ... this->command(SSD1325_MASTERCONFIG); // Set Master Config DC/DC Converter this->command(0x02); this->command(SSD1325_SETREMAP); // set segment remapping - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(0x53); // COM bottom-up, split odd/even, enable column and nibble remapping - else - this->command(0x50); // COM bottom-up, split odd/even + } else { + this->command(0x50); // COM bottom-up, split odd/even + } this->command(SSD1325_SETCURRENT + 0x2); // Set Full Current Range this->command(SSD1325_SETGRAYTABLE); // gamma ~2.2 @@ -122,10 +125,11 @@ void SSD1325::display() { this->command(0x3F); // set column end address this->command(SSD1325_SETROWADDR); // set row address this->command(0x00); // set row start address - if (this->model_ == SSD1327_MODEL_128_128) + if (this->model_ == SSD1327_MODEL_128_128) { this->command(127); // set last row - else + } else { this->command(63); // set last row + } this->write_display_data(); } @@ -135,12 +139,13 @@ void SSD1325::update() { } void SSD1325::set_brightness(float brightness) { // validation - if (brightness > 1) + if (brightness > 1) { this->brightness_ = 1.0; - else if (brightness < 0) + } else if (brightness < 0) { this->brightness_ = 0; - else + } else { this->brightness_ = brightness; + } // now write the new brightness level to the display this->command(SSD1325_SETCONTRAST); this->command(int(SSD1325_MAX_CONTRAST * (this->brightness_))); diff --git a/esphome/components/ssd1331_base/ssd1331_base.cpp b/esphome/components/ssd1331_base/ssd1331_base.cpp index 88764c3d90df..14f94dee4faf 100644 --- a/esphome/components/ssd1331_base/ssd1331_base.cpp +++ b/esphome/components/ssd1331_base/ssd1331_base.cpp @@ -129,12 +129,13 @@ void HOT SSD1331::draw_absolute_pixel_internal(int x, int y, Color color) { } void SSD1331::fill(Color color) { const uint32_t color565 = display::ColorUtil::color_to_565(color); - for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { this->buffer_[i] = color565 & 0xff; } else { this->buffer_[i] = (color565 >> 8) & 0xff; } + } } void SSD1331::init_reset_() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/ssd1351_base/ssd1351_base.cpp b/esphome/components/ssd1351_base/ssd1351_base.cpp index f26cd7c69712..036a6a0e82a7 100644 --- a/esphome/components/ssd1351_base/ssd1351_base.cpp +++ b/esphome/components/ssd1351_base/ssd1351_base.cpp @@ -105,12 +105,13 @@ void SSD1351::update() { } void SSD1351::set_brightness(float brightness) { // validation - if (brightness > 1) + if (brightness > 1) { this->brightness_ = 1.0; - else if (brightness < 0) + } else if (brightness < 0) { this->brightness_ = 0; - else + } else { this->brightness_ = brightness; + } // now write the new brightness level to the display this->command(SSD1351_CONTRASTMASTER); this->data(int(SSD1351_MAX_CONTRAST * (this->brightness_))); @@ -157,12 +158,13 @@ void HOT SSD1351::draw_absolute_pixel_internal(int x, int y, Color color) { } void SSD1351::fill(Color color) { const uint32_t color565 = display::ColorUtil::color_to_565(color); - for (uint32_t i = 0; i < this->get_buffer_length_(); i++) + for (uint32_t i = 0; i < this->get_buffer_length_(); i++) { if (i & 1) { this->buffer_[i] = color565 & 0xff; } else { this->buffer_[i] = (color565 >> 8) & 0xff; } + } } void SSD1351::init_reset_() { if (this->reset_pin_ != nullptr) { diff --git a/esphome/components/sts3x/sts3x.cpp b/esphome/components/sts3x/sts3x.cpp index b1ecbc98f800..ce166f205580 100644 --- a/esphome/components/sts3x/sts3x.cpp +++ b/esphome/components/sts3x/sts3x.cpp @@ -78,18 +78,20 @@ uint8_t sts3x_crc(uint8_t data1, uint8_t data2) { crc ^= data1; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } crc ^= data2; for (bit = 8; bit > 0; --bit) { - if (crc & 0x80) + if (crc & 0x80) { crc = (crc << 1) ^ 0x131; - else + } else { crc = (crc << 1); + } } return crc; diff --git a/esphome/components/substitutions/__init__.py b/esphome/components/substitutions/__init__.py index 0cef417c154f..6188b14b350e 100644 --- a/esphome/components/substitutions/__init__.py +++ b/esphome/components/substitutions/__init__.py @@ -5,6 +5,7 @@ from esphome import core from esphome.const import CONF_SUBSTITUTIONS from esphome.yaml_util import ESPHomeDataBase, make_data_base +from esphome.config_helpers import merge_config CODEOWNERS = ["@esphome/core"] _LOGGER = logging.getLogger(__name__) @@ -108,7 +109,7 @@ def _substitute_item(substitutions, item, path): if sub is not None: item[k] = sub for old, new in replace_keys: - item[new] = item[old] + item[new] = merge_config(item.get(old), item.get(new)) del item[old] elif isinstance(item, str): sub = _expand_substitutions(substitutions, item, path) diff --git a/esphome/components/sun/sun.h b/esphome/components/sun/sun.h index 0a2e6bcf97fe..efc6a1ab0a1d 100644 --- a/esphome/components/sun/sun.h +++ b/esphome/components/sun/sun.h @@ -110,10 +110,11 @@ template class SunCondition : public Condition, public Pa bool check(Ts... x) override { double elevation = this->elevation_.value(x...); double current = this->parent_->elevation(); - if (this->above_) + if (this->above_) { return current > elevation; - else + } else { return current < elevation; + } } protected: diff --git a/esphome/components/sun/text_sensor/__init__.py b/esphome/components/sun/text_sensor/__init__.py index ac1ce223d1da..80737bb9f224 100644 --- a/esphome/components/sun/text_sensor/__init__.py +++ b/esphome/components/sun/text_sensor/__init__.py @@ -6,7 +6,6 @@ ICON_WEATHER_SUNSET_DOWN, ICON_WEATHER_SUNSET_UP, CONF_TYPE, - CONF_ID, CONF_FORMAT, ) from .. import sun_ns, CONF_SUN_ID, Sun, CONF_ELEVATION, elevation, DEFAULT_ELEVATION @@ -33,7 +32,8 @@ def validate_optional_icon(config): CONFIG_SCHEMA = cv.All( - text_sensor.TEXT_SENSOR_SCHEMA.extend( + text_sensor.text_sensor_schema() + .extend( { cv.GenerateID(): cv.declare_id(SunTextSensor), cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun), @@ -41,15 +41,15 @@ def validate_optional_icon(config): cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation, cv.Optional(CONF_FORMAT, default="%X"): cv.string_strict, } - ).extend(cv.polling_component_schema("60s")), + ) + .extend(cv.polling_component_schema("60s")), validate_optional_icon, ) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_sensor(var, config) paren = await cg.get_variable(config[CONF_SUN_ID]) cg.add(var.set_parent(paren)) diff --git a/esphome/components/sun/text_sensor/sun_text_sensor.h b/esphome/components/sun/text_sensor/sun_text_sensor.h index e4f5beca9c9c..ad01d64ff12e 100644 --- a/esphome/components/sun/text_sensor/sun_text_sensor.h +++ b/esphome/components/sun/text_sensor/sun_text_sensor.h @@ -16,10 +16,11 @@ class SunTextSensor : public text_sensor::TextSensor, public PollingComponent { void update() override { optional res; - if (this->sunrise_) + if (this->sunrise_) { res = this->parent_->sunrise(this->elevation_); - else + } else { res = this->parent_->sunset(this->elevation_); + } if (!res) { this->publish_state(""); return; diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py index 08cbccbe35b5..71a16439cd0b 100644 --- a/esphome/components/switch/__init__.py +++ b/esphome/components/switch/__init__.py @@ -4,18 +4,27 @@ from esphome.automation import Condition, maybe_simple_id from esphome.components import mqtt from esphome.const import ( + CONF_DEVICE_CLASS, CONF_ID, CONF_INVERTED, + CONF_MQTT_ID, CONF_ON_TURN_OFF, CONF_ON_TURN_ON, CONF_TRIGGER_ID, - CONF_MQTT_ID, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_OUTLET, + DEVICE_CLASS_SWITCH, ) from esphome.core import CORE, coroutine_with_priority from esphome.cpp_helpers import setup_entity CODEOWNERS = ["@esphome/core"] IS_PLATFORM_COMPONENT = True +DEVICE_CLASSES = [ + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_OUTLET, + DEVICE_CLASS_SWITCH, +] switch_ns = cg.esphome_ns.namespace("switch_") Switch = switch_ns.class_("Switch", cg.EntityBase) @@ -51,6 +60,7 @@ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger), } ), + cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True), } ) @@ -71,6 +81,9 @@ async def setup_switch_core_(var, config): mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var) await mqtt.register_mqtt_component(mqtt_, config) + if CONF_DEVICE_CLASS in config: + cg.add(var.set_device_class(config[CONF_DEVICE_CLASS])) + async def register_switch(var, config): if not CORE.has_id(config[CONF_ID]): diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index b9b99b414754..ca36e6feb943 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -46,5 +46,12 @@ void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; } uint32_t Switch::hash_base() { return 3129890955UL; } bool Switch::is_inverted() const { return this->inverted_; } +std::string Switch::get_device_class() { + if (this->device_class_.has_value()) + return *this->device_class_; + return ""; +} +void Switch::set_device_class(const std::string &device_class) { this->device_class_ = device_class; } + } // namespace switch_ } // namespace esphome diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index 071393003a65..dda24e85fa5f 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -20,6 +20,9 @@ namespace switch_ { if ((obj)->is_inverted()) { \ ESP_LOGCONFIG(TAG, "%s Inverted: YES", prefix); \ } \ + if (!(obj)->get_device_class().empty()) { \ + ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \ + } \ } /** Base class for all switches. @@ -88,6 +91,11 @@ class Switch : public EntityBase { bool is_inverted() const; + /// Get the device class for this switch. + std::string get_device_class(); + /// Set the Home Assistant device class for this switch. + void set_device_class(const std::string &device_class); + protected: /** Write the given state to hardware. You should implement this * abstract method if you want to create your own switch. @@ -105,6 +113,7 @@ class Switch : public EntityBase { bool inverted_{false}; Deduplicator publish_dedup_; ESPPreferenceObject rtc_; + optional device_class_; }; } // namespace switch_ diff --git a/esphome/components/sx1509/sx1509.cpp b/esphome/components/sx1509/sx1509.cpp index 9095dfeffa9f..16540d0dbf3c 100644 --- a/esphome/components/sx1509/sx1509.cpp +++ b/esphome/components/sx1509/sx1509.cpp @@ -64,10 +64,11 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { // If the pin is an output, write high/low uint16_t temp_reg_data = 0; this->read_byte_16(REG_DATA_B, &temp_reg_data); - if (bit_value) + if (bit_value) { temp_reg_data |= (1 << pin); - else + } else { temp_reg_data &= ~(1 << pin); + } this->write_byte_16(REG_DATA_B, temp_reg_data); } else { // Otherwise the pin is an input, pull-up/down @@ -94,10 +95,11 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) { void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) { this->read_byte_16(REG_DIR_B, &this->ddr_mask_); - if (flags == gpio::FLAG_OUTPUT) + if (flags == gpio::FLAG_OUTPUT) { this->ddr_mask_ &= ~(1 << pin); - else + } else { this->ddr_mask_ |= (1 << pin); + } this->write_byte_16(REG_DIR_B, this->ddr_mask_); if (flags & gpio::FLAG_PULLUP) diff --git a/esphome/components/tcl112/tcl112.cpp b/esphome/components/tcl112/tcl112.cpp index 5b938ba0c3e8..a88e8e96a7ec 100644 --- a/esphome/components/tcl112/tcl112.cpp +++ b/esphome/components/tcl112/tcl112.cpp @@ -79,11 +79,12 @@ void Tcl112Climate::transmit_state() { safecelsius = std::min(safecelsius, TCL112_TEMP_MAX); // Convert to integer nr. of half degrees. auto half_degrees = static_cast(safecelsius * 2); - if (half_degrees & 1) // Do we have a half degree celsius? + if (half_degrees & 1) { // Do we have a half degree celsius? remote_state[12] |= TCL112_HALF_DEGREE; // Add 0.5 degrees - else + } else { remote_state[12] &= ~TCL112_HALF_DEGREE; // Clear the half degree. - remote_state[7] &= 0xF0; // Clear temp bits. + } + remote_state[7] &= 0xF0; // Clear temp bits. remote_state[7] |= ((uint8_t) TCL112_TEMP_MAX - half_degrees / 2); // Set fan @@ -120,7 +121,7 @@ void Tcl112Climate::transmit_state() { remote_state[13]); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); @@ -128,12 +129,13 @@ void Tcl112Climate::transmit_state() { data->mark(TCL112_HEADER_MARK); data->space(TCL112_HEADER_SPACE); // Data - for (uint8_t i : remote_state) + for (uint8_t i : remote_state) { for (uint8_t j = 0; j < 8; j++) { data->mark(TCL112_BIT_MARK); bool bit = i & (1 << j); data->space(bit ? TCL112_ONE_SPACE : TCL112_ZERO_SPACE); } + } // Footer data->mark(TCL112_BIT_MARK); data->space(TCL112_GAP); @@ -153,9 +155,9 @@ bool Tcl112Climate::on_receive(remote_base::RemoteReceiveData data) { for (int i = 0; i < TCL112_STATE_LENGTH; i++) { // Read bit for (int j = 0; j < 8; j++) { - if (data.expect_item(TCL112_BIT_MARK, TCL112_ONE_SPACE)) + if (data.expect_item(TCL112_BIT_MARK, TCL112_ONE_SPACE)) { remote_state[i] |= 1 << j; - else if (!data.expect_item(TCL112_BIT_MARK, TCL112_ZERO_SPACE)) { + } else if (!data.expect_item(TCL112_BIT_MARK, TCL112_ZERO_SPACE)) { ESP_LOGVV(TAG, "Byte %d bit %d fail", i, j); return false; } diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp index f7ffe2a97d03..825f7da4cc0e 100644 --- a/esphome/components/tcs34725/tcs34725.cpp +++ b/esphome/components/tcs34725/tcs34725.cpp @@ -21,23 +21,23 @@ static const uint8_t TCS34725_REGISTER_BDATAL = TCS34725_COMMAND_BIT | 0x1A; void TCS34725Component::setup() { ESP_LOGCONFIG(TAG, "Setting up TCS34725..."); uint8_t id; - if (!this->read_byte(TCS34725_REGISTER_ID, &id)) { + if (this->read_register(TCS34725_REGISTER_ID, &id, 1) != i2c::ERROR_OK) { this->mark_failed(); return; } - - if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) || - !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) { + if (this->write_config_register_(TCS34725_REGISTER_ATIME, this->integration_reg_) != i2c::ERROR_OK || + this->write_config_register_(TCS34725_REGISTER_CONTROL, this->gain_reg_) != i2c::ERROR_OK) { this->mark_failed(); return; } - - if (!this->write_byte(TCS34725_REGISTER_ENABLE, 0x01)) { // Power on (internal oscillator on) + if (this->write_config_register_(TCS34725_REGISTER_ENABLE, 0x01) != + i2c::ERROR_OK) { // Power on (internal oscillator on) this->mark_failed(); return; } delay(3); - if (!this->write_byte(TCS34725_REGISTER_ENABLE, 0x03)) { // Power on (internal oscillator on) + RGBC ADC Enable + if (this->write_config_register_(TCS34725_REGISTER_ENABLE, 0x03) != + i2c::ERROR_OK) { // Power on (internal oscillator on) + RGBC ADC Enable this->mark_failed(); return; } @@ -134,9 +134,9 @@ void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, u /* Adjust sat to 75% to avoid analog saturation if atime < 153.6ms */ sat -= sat / 4.f; } - /* Check for saturation and mark the sample as invalid if true */ if (c >= sat) { + ESP_LOGW(TAG, "Saturation too high, discarding sample with saturation %.1f and clear %d", sat, c); return; } @@ -173,23 +173,40 @@ void TCS34725Component::update() { uint16_t raw_g; uint16_t raw_b; - if (!this->read_byte_16(TCS34725_REGISTER_CDATAL, &raw_c) || !this->read_byte_16(TCS34725_REGISTER_RDATAL, &raw_r) || - !this->read_byte_16(TCS34725_REGISTER_GDATAL, &raw_g) || !this->read_byte_16(TCS34725_REGISTER_BDATAL, &raw_b)) { - ESP_LOGW(TAG, "Reading data from TCS34725 failed!"); + if (this->read_data_register_(TCS34725_REGISTER_CDATAL, raw_c) != i2c::ERROR_OK) { this->status_set_warning(); return; } + if (this->read_data_register_(TCS34725_REGISTER_RDATAL, raw_r) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + if (this->read_data_register_(TCS34725_REGISTER_GDATAL, raw_g) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + if (this->read_data_register_(TCS34725_REGISTER_BDATAL, raw_b) != i2c::ERROR_OK) { + this->status_set_warning(); + return; + } + ESP_LOGV(TAG, "Raw values clear=%x red=%x green=%x blue=%x", raw_c, raw_r, raw_g, raw_b); + + float channel_c; + float channel_r; + float channel_g; + float channel_b; + // avoid division by 0 and return black if clear is 0 + if (raw_c == 0) { + channel_c = channel_r = channel_g = channel_b = 0.0f; + } else { + float max_count = this->integration_time_ * 1024.0f / 2.4; + float sum = raw_c; + channel_r = raw_r / sum * 100.0f; + channel_g = raw_g / sum * 100.0f; + channel_b = raw_b / sum * 100.0f; + channel_c = raw_c / max_count * 100.0f; + } - // May need to fix endianness as the data read over I2C is big-endian, but most ESP platforms are little-endian - raw_c = i2c::i2ctohs(raw_c); - raw_r = i2c::i2ctohs(raw_r); - raw_g = i2c::i2ctohs(raw_g); - raw_b = i2c::i2ctohs(raw_b); - - const float channel_c = raw_c / 655.35f; - const float channel_r = raw_r / 655.35f; - const float channel_g = raw_g / 655.35f; - const float channel_b = raw_b / 655.35f; if (this->clear_sensor_ != nullptr) this->clear_sensor_->publish_state(channel_c); if (this->red_sensor_ != nullptr) @@ -209,8 +226,8 @@ void TCS34725Component::update() { if (this->color_temperature_sensor_ != nullptr) this->color_temperature_sensor_->publish_state(this->color_temperature_); - ESP_LOGD(TAG, "Got R=%.1f%%,G=%.1f%%,B=%.1f%%,C=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", channel_r, - channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); + ESP_LOGD(TAG, "Got Red=%.1f%%,Green=%.1f%%,Blue=%.1f%%,Clear=%.1f%% Illuminance=%.1flx Color Temperature=%.1fK", + channel_r, channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_); this->status_clear_warning(); } diff --git a/esphome/components/tcs34725/tcs34725.h b/esphome/components/tcs34725/tcs34725.h index 47ed2959c666..04565d948e54 100644 --- a/esphome/components/tcs34725/tcs34725.h +++ b/esphome/components/tcs34725/tcs34725.h @@ -56,6 +56,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice { void dump_config() override; protected: + i2c::ErrorCode read_data_register_(uint8_t a_register, uint16_t &data) { + uint8_t buffer[2]; + auto retval = this->read_register(a_register, buffer, 2); + if (retval == i2c::ERROR_OK) + data = (uint16_t(buffer[1]) << 8) | (uint16_t(buffer[0]) & 0xFF); + return retval; + } + i2c::ErrorCode write_config_register_(uint8_t a_register, uint8_t data) { + return this->write_register(a_register, &data, 1); + } sensor::Sensor *clear_sensor_{nullptr}; sensor::Sensor *red_sensor_{nullptr}; sensor::Sensor *green_sensor_{nullptr}; diff --git a/esphome/components/teleinfo/__init__.py b/esphome/components/teleinfo/__init__.py index 9a5712e10f97..33b748a031e5 100644 --- a/esphome/components/teleinfo/__init__.py +++ b/esphome/components/teleinfo/__init__.py @@ -7,9 +7,20 @@ teleinfo_ns = cg.esphome_ns.namespace("teleinfo") TeleInfo = teleinfo_ns.class_("TeleInfo", cg.PollingComponent, uart.UARTDevice) -CONF_TELEINFO_ID = "teleinfo_id" +CONF_TELEINFO_ID = "teleinfo_id" +CONF_TAG_NAME = "tag_name" CONF_HISTORICAL_MODE = "historical_mode" + + +TELEINFO_LISTENER_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo), + cv.Required(CONF_TAG_NAME): cv.string, + } +) + + CONFIG_SCHEMA = ( cv.Schema( { diff --git a/esphome/components/teleinfo/sensor/__init__.py b/esphome/components/teleinfo/sensor/__init__.py index e7cc2fcb1bfe..aa875be15708 100644 --- a/esphome/components/teleinfo/sensor/__init__.py +++ b/esphome/components/teleinfo/sensor/__init__.py @@ -3,20 +3,22 @@ from esphome.components import sensor from esphome.const import CONF_ID, ICON_FLASH, UNIT_WATT_HOURS -from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID +from .. import ( + CONF_TAG_NAME, + TELEINFO_LISTENER_SCHEMA, + teleinfo_ns, + CONF_TELEINFO_ID, +) -CONF_TAG_NAME = "tag_name" 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), - cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo), - cv.Required(CONF_TAG_NAME): cv.string, - } +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) ) diff --git a/esphome/components/teleinfo/teleinfo.cpp b/esphome/components/teleinfo/teleinfo.cpp index d9f80134f49a..4d617ae4e61e 100644 --- a/esphome/components/teleinfo/teleinfo.cpp +++ b/esphome/components/teleinfo/teleinfo.cpp @@ -179,7 +179,7 @@ void TeleInfo::loop() { } } void TeleInfo::publish_value_(const std::string &tag, const std::string &val) { - for (auto element : teleinfo_listeners_) { + for (auto *element : teleinfo_listeners_) { if (tag != element->tag) continue; element->publish_val(val); diff --git a/esphome/components/teleinfo/text_sensor/__init__.py b/esphome/components/teleinfo/text_sensor/__init__.py index 3bd73ff2727a..848b08d74202 100644 --- a/esphome/components/teleinfo/text_sensor/__init__.py +++ b/esphome/components/teleinfo/text_sensor/__init__.py @@ -1,22 +1,15 @@ import esphome.codegen as cg -import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import CONF_ID -from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID - -CONF_TAG_NAME = "tag_name" +from .. import CONF_TAG_NAME, TELEINFO_LISTENER_SCHEMA, teleinfo_ns, CONF_TELEINFO_ID TeleInfoTextSensor = teleinfo_ns.class_( "TeleInfoTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TeleInfoTextSensor), - cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo), - cv.Required(CONF_TAG_NAME): cv.string, - } +CONFIG_SCHEMA = text_sensor.text_sensor_schema(klass=TeleInfoTextSensor).extend( + TELEINFO_LISTENER_SCHEMA ) diff --git a/esphome/components/template/lock/__init__.py b/esphome/components/template/lock/__init__.py new file mode 100644 index 000000000000..24709ff4f21a --- /dev/null +++ b/esphome/components/template/lock/__init__.py @@ -0,0 +1,103 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome import automation +from esphome.components import lock +from esphome.const import ( + CONF_ASSUMED_STATE, + CONF_ID, + CONF_LAMBDA, + CONF_LOCK_ACTION, + CONF_OPEN_ACTION, + CONF_OPTIMISTIC, + CONF_STATE, + CONF_UNLOCK_ACTION, +) +from .. import template_ns + +TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component) + +LockState = lock.lock_ns.enum("LockState") + +LOCK_STATES = { + "LOCKED": LockState.LOCK_STATE_LOCKED, + "UNLOCKED": LockState.LOCK_STATE_UNLOCKED, + "JAMMED": LockState.LOCK_STATE_JAMMED, + "LOCKING": LockState.LOCK_STATE_LOCKING, + "UNLOCKING": LockState.LOCK_STATE_UNLOCKING, +} + +validate_lock_state = cv.enum(LOCK_STATES, upper=True) + + +def validate(config): + if not config[CONF_OPTIMISTIC] and ( + CONF_LOCK_ACTION not in config or CONF_UNLOCK_ACTION not in config + ): + raise cv.Invalid( + "Either optimistic mode must be enabled, or lock_action and unlock_action must be set, " + "to handle the lock being changed." + ) + return config + + +CONFIG_SCHEMA = cv.All( + lock.LOCK_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TemplateLock), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean, + cv.Optional(CONF_UNLOCK_ACTION): automation.validate_automation( + single=True + ), + cv.Optional(CONF_LOCK_ACTION): automation.validate_automation(single=True), + cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True), + } + ).extend(cv.COMPONENT_SCHEMA), + validate, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await lock.register_lock(var, config) + + if CONF_LAMBDA in config: + template_ = await cg.process_lambda( + config[CONF_LAMBDA], [], return_type=cg.optional.template(LockState) + ) + cg.add(var.set_state_lambda(template_)) + if CONF_UNLOCK_ACTION in config: + await automation.build_automation( + var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION] + ) + if CONF_LOCK_ACTION in config: + await automation.build_automation( + var.get_lock_trigger(), [], config[CONF_LOCK_ACTION] + ) + if CONF_OPEN_ACTION in config: + await automation.build_automation( + var.get_open_trigger(), [], config[CONF_OPEN_ACTION] + ) + cg.add(var.traits.set_supports_open(CONF_OPEN_ACTION in config)) + cg.add(var.traits.set_assumed_state(config[CONF_ASSUMED_STATE])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) + + +@automation.register_action( + "lock.template.publish", + lock.LockPublishAction, + cv.Schema( + { + cv.Required(CONF_ID): cv.use_id(lock.Lock), + cv.Required(CONF_STATE): cv.templatable(validate_lock_state), + } + ), +) +async def lock_template_publish_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, paren) + template_ = await cg.templatable(config[CONF_STATE], args, LockState) + cg.add(var.set_state(template_)) + return var diff --git a/esphome/components/template/lock/template_lock.cpp b/esphome/components/template/lock/template_lock.cpp new file mode 100644 index 000000000000..87ba1046ebe7 --- /dev/null +++ b/esphome/components/template/lock/template_lock.cpp @@ -0,0 +1,59 @@ +#include "template_lock.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace template_ { + +using namespace esphome::lock; + +static const char *const TAG = "template.lock"; + +TemplateLock::TemplateLock() + : lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {} + +void TemplateLock::loop() { + if (!this->f_.has_value()) + return; + auto val = (*this->f_)(); + if (!val.has_value()) + return; + + this->publish_state(*val); +} +void TemplateLock::control(const lock::LockCall &call) { + if (this->prev_trigger_ != nullptr) { + this->prev_trigger_->stop_action(); + } + + auto state = *call.get_state(); + if (state == LOCK_STATE_LOCKED) { + this->prev_trigger_ = this->lock_trigger_; + this->lock_trigger_->trigger(); + } else if (state == LOCK_STATE_UNLOCKED) { + this->prev_trigger_ = this->unlock_trigger_; + this->unlock_trigger_->trigger(); + } + + if (this->optimistic_) + this->publish_state(state); +} +void TemplateLock::open_latch() { + if (this->prev_trigger_ != nullptr) { + this->prev_trigger_->stop_action(); + } + this->prev_trigger_ = this->open_trigger_; + this->open_trigger_->trigger(); +} +void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } +void TemplateLock::set_state_lambda(std::function()> &&f) { this->f_ = f; } +float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; } +Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; } +Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; } +Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; } +void TemplateLock::dump_config() { + LOG_LOCK("", "Template Lock", this); + ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); +} + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/lock/template_lock.h b/esphome/components/template/lock/template_lock.h new file mode 100644 index 000000000000..4f798eca8144 --- /dev/null +++ b/esphome/components/template/lock/template_lock.h @@ -0,0 +1,38 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/lock/lock.h" + +namespace esphome { +namespace template_ { + +class TemplateLock : public lock::Lock, public Component { + public: + TemplateLock(); + + void dump_config() override; + + void set_state_lambda(std::function()> &&f); + Trigger<> *get_lock_trigger() const; + Trigger<> *get_unlock_trigger() const; + Trigger<> *get_open_trigger() const; + void set_optimistic(bool optimistic); + void loop() override; + + float get_setup_priority() const override; + + protected: + void control(const lock::LockCall &call) override; + void open_latch() override; + + optional()>> f_; + bool optimistic_{false}; + Trigger<> *lock_trigger_; + Trigger<> *unlock_trigger_; + Trigger<> *open_trigger_; + Trigger<> *prev_trigger_{nullptr}; +}; + +} // namespace template_ +} // namespace esphome diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index a5b015c44dc7..aaf5b27a71ce 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -16,10 +16,11 @@ void TemplateNumber::setup() { } else { this->pref_ = global_preferences->make_preference(this->get_object_id_hash()); if (!this->pref_.load(&value)) { - if (!std::isnan(this->initial_value_)) + if (!std::isnan(this->initial_value_)) { value = this->initial_value_; - else + } else { value = this->traits.get_min_value(); + } } } this->publish_state(value); diff --git a/esphome/components/template/text_sensor/__init__.py b/esphome/components/template/text_sensor/__init__.py index 2e098a77c27f..9bd603bbca03 100644 --- a/esphome/components/template/text_sensor/__init__.py +++ b/esphome/components/template/text_sensor/__init__.py @@ -10,18 +10,21 @@ "TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TemplateTextSensor), - cv.Optional(CONF_LAMBDA): cv.returning_lambda, - } -).extend(cv.polling_component_schema("60s")) +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(TemplateTextSensor), + cv.Optional(CONF_LAMBDA): cv.returning_lambda, + } + ) + .extend(cv.polling_component_schema("60s")) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_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 e0fc6af19c65..de725794023d 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -3,7 +3,9 @@ from esphome import automation from esphome.components import mqtt from esphome.const import ( + CONF_ENTITY_CATEGORY, CONF_FILTERS, + CONF_ICON, CONF_ID, CONF_ON_VALUE, CONF_ON_RAW_VALUE, @@ -14,6 +16,7 @@ CONF_TO, ) 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 @@ -110,12 +113,10 @@ async def map_filter_to_code(config, filter_id): ) -icon = cv.icon - - TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend( { cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor), + cv.GenerateID(): cv.declare_id(TextSensor), cv.Optional(CONF_FILTERS): validate_filters, cv.Optional(CONF_ON_VALUE): automation.validate_automation( { @@ -132,6 +133,29 @@ async def map_filter_to_code(config, filter_id): } ) +_UNDEF = object() + + +def text_sensor_schema( + klass: 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 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 + } + ) + return schema + async def build_filters(config): return await cg.build_registry_list(FILTER_REGISTRY, config) @@ -164,6 +188,12 @@ async def register_text_sensor(var, config): await setup_text_sensor_core_(var, config) +async def new_text_sensor(config): + var = cg.new_Pvariable(config[CONF_ID]) + await register_text_sensor(var, config) + return var + + @coroutine_with_priority(100.0) async def to_code(config): cg.add_define("USE_TEXT_SENSOR") diff --git a/esphome/components/text_sensor/filter.cpp b/esphome/components/text_sensor/filter.cpp index c6cbcd7c1e89..ba77913d1b63 100644 --- a/esphome/components/text_sensor/filter.cpp +++ b/esphome/components/text_sensor/filter.cpp @@ -64,9 +64,10 @@ optional PrependFilter::new_value(std::string value) { return this- // Substitute optional SubstituteFilter::new_value(std::string value) { std::size_t pos; - for (size_t i = 0; i < this->from_strings_.size(); i++) + for (size_t i = 0; i < this->from_strings_.size(); i++) { while ((pos = value.find(this->from_strings_[i])) != std::string::npos) value.replace(pos, this->from_strings_[i].size(), this->to_strings_[i]); + } return value; } diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index ce15c53bbe34..760525e2cd56 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -129,9 +129,10 @@ void ThermostatClimate::validate_target_temperature_low() { this->target_temperature_low = this->get_traits().get_visual_min_temperature(); // target_temperature_low must not be greater than the visual maximum minus set_point_minimum_differential_ if (this->target_temperature_low > - this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_) + this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_) { this->target_temperature_low = this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_; + } // if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high if (this->target_temperature_low > this->target_temperature_high - this->set_point_minimum_differential_) this->target_temperature_high = this->target_temperature_low + this->set_point_minimum_differential_; @@ -147,9 +148,10 @@ void ThermostatClimate::validate_target_temperature_high() { this->target_temperature_high = this->get_traits().get_visual_max_temperature(); // target_temperature_high must not be lower than the visual minimum plus set_point_minimum_differential_ if (this->target_temperature_high < - this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_) + this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_) { this->target_temperature_high = this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_; + } // if target_temperature_high is set less than target_temperature_low, move down target_temperature_low if (this->target_temperature_high < this->target_temperature_low + this->set_point_minimum_differential_) this->target_temperature_low = this->target_temperature_high - this->set_point_minimum_differential_; @@ -348,9 +350,10 @@ climate::ClimateAction ThermostatClimate::compute_supplemental_action_() { void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot - if ((action == this->action) && this->setup_complete_) + if ((action == this->action) && this->setup_complete_) { // already in target mode return; + } if (((action == climate::CLIMATE_ACTION_OFF && this->action == climate::CLIMATE_ACTION_IDLE) || (action == climate::CLIMATE_ACTION_IDLE && this->action == climate::CLIMATE_ACTION_OFF)) && @@ -373,10 +376,11 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu if (this->action == climate::CLIMATE_ACTION_COOLING) this->start_timer_(thermostat::TIMER_COOLING_OFF); if (this->action == climate::CLIMATE_ACTION_FAN) { - if (this->supports_fan_only_action_uses_fan_mode_timer_) + if (this->supports_fan_only_action_uses_fan_mode_timer_) { this->start_timer_(thermostat::TIMER_FAN_MODE); - else + } else { this->start_timer_(thermostat::TIMER_FANNING_OFF); + } } if (this->action == climate::CLIMATE_ACTION_HEATING) this->start_timer_(thermostat::TIMER_HEATING_OFF); @@ -415,10 +419,11 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu break; case climate::CLIMATE_ACTION_FAN: if (this->fanning_action_ready_()) { - if (this->supports_fan_only_action_uses_fan_mode_timer_) + if (this->supports_fan_only_action_uses_fan_mode_timer_) { this->start_timer_(thermostat::TIMER_FAN_MODE); - else + } else { this->start_timer_(thermostat::TIMER_FANNING_ON); + } trig = this->fan_only_action_trigger_; ESP_LOGVV(TAG, "Switching to FAN_ONLY action"); action_ready = true; @@ -461,9 +466,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu void ThermostatClimate::switch_to_supplemental_action_(climate::ClimateAction action) { // setup_complete_ helps us ensure an action is called immediately after boot - if ((action == this->supplemental_action_) && this->setup_complete_) + if ((action == this->supplemental_action_) && this->setup_complete_) { // already in target mode return; + } switch (action) { case climate::CLIMATE_ACTION_OFF: @@ -515,9 +521,10 @@ void ThermostatClimate::trigger_supplemental_action_() { void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot - if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) + if ((fan_mode == this->prev_fan_mode_) && this->setup_complete_) { // already in target mode return; + } this->fan_mode = fan_mode; if (publish_state) @@ -582,9 +589,10 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot - if ((mode == this->prev_mode_) && this->setup_complete_) + if ((mode == this->prev_mode_) && this->setup_complete_) { // already in target mode return; + } if (this->prev_mode_trigger_ != nullptr) { this->prev_mode_trigger_->stop_action(); @@ -627,9 +635,10 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state) { // setup_complete_ helps us ensure an action is called immediately after boot - if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) + if ((swing_mode == this->prev_swing_mode_) && this->setup_complete_) { // already in target mode return; + } if (this->prev_swing_mode_trigger_ != nullptr) { this->prev_swing_mode_trigger_->stop_action(); @@ -1107,16 +1116,18 @@ Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return th void ThermostatClimate::dump_config() { LOG_CLIMATE("", "Thermostat", this); if (this->supports_heat_) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature_low); - else + } else { ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C", this->normal_config_.default_temperature); + } } if ((this->supports_cool_) || (this->supports_fan_only_ && this->supports_fan_only_cooling_)) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature_high); - else + } else { ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C", this->normal_config_.default_temperature); + } } if (this->supports_two_points_) ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_); @@ -1186,18 +1197,20 @@ void ThermostatClimate::dump_config() { ESP_LOGCONFIG(TAG, " Supports AWAY mode: %s", YESNO(this->supports_away_)); if (this->supports_away_) { if (this->supports_heat_) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Away Default Target Temperature Low: %.1f°C", this->away_config_.default_temperature_low); - else + } else { ESP_LOGCONFIG(TAG, " Away Default Target Temperature Low: %.1f°C", this->away_config_.default_temperature); + } } if ((this->supports_cool_) || (this->supports_fan_only_)) { - if (this->supports_two_points_) + if (this->supports_two_points_) { ESP_LOGCONFIG(TAG, " Away Default Target Temperature High: %.1f°C", this->away_config_.default_temperature_high); - else + } else { ESP_LOGCONFIG(TAG, " Away Default Target Temperature High: %.1f°C", this->away_config_.default_temperature); + } } } } diff --git a/esphome/components/time/real_time_clock.cpp b/esphome/components/time/real_time_clock.cpp index 6f6739d2939a..0469ba2c377f 100644 --- a/esphome/components/time/real_time_clock.cpp +++ b/esphome/components/time/real_time_clock.cpp @@ -131,6 +131,23 @@ void ESPTime::increment_second() { this->year++; } } +void ESPTime::increment_day() { + this->timestamp += 86400; + + // increment day + increment_time_value(this->day_of_week, 1, 8); + + if (increment_time_value(this->day_of_month, 1, days_in_month(this->month, this->year) + 1)) { + // day of month roll-over, increment month + increment_time_value(this->month, 1, 13); + } + + uint16_t days_in_year = (this->year % 4 == 0) ? 366 : 365; + if (increment_time_value(this->day_of_year, 1, days_in_year + 1)) { + // day of year roll-over, increment year + this->year++; + } +} void ESPTime::recalc_timestamp_utc(bool use_day_of_year) { time_t res = 0; diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index 0c6fa6f3a0b8..c45deb0be51b 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -90,6 +90,8 @@ struct ESPTime { /// Increment this clock instance by one second. void increment_second(); + /// Increment this clock instance by one day. + void increment_day(); bool operator<(ESPTime other); bool operator<=(ESPTime other); bool operator==(ESPTime other); diff --git a/esphome/components/tm1637/tm1637.cpp b/esphome/components/tm1637/tm1637.cpp index a21d2d438d13..44f0a841b8f5 100644 --- a/esphome/components/tm1637/tm1637.cpp +++ b/esphome/components/tm1637/tm1637.cpp @@ -202,10 +202,11 @@ bool TM1637Display::send_byte_(uint8_t b) { this->bit_delay_(); // Set data bit - if (data & 0x01) + if (data & 0x01) { this->dio_pin_->pin_mode(gpio::FLAG_INPUT); - else + } else { this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT); + } this->bit_delay_(); diff --git a/esphome/components/tm1651/tm1651.h b/esphome/components/tm1651/tm1651.h index 72849bc8eb64..eb65ed186dd9 100644 --- a/esphome/components/tm1651/tm1651.h +++ b/esphome/components/tm1651/tm1651.h @@ -21,9 +21,9 @@ class TM1651Display : public Component { void setup() override; void dump_config() override; - void set_level_percent(uint8_t); - void set_level(uint8_t); - void set_brightness(uint8_t); + void set_level_percent(uint8_t new_level); + void set_level(uint8_t new_level); + void set_brightness(uint8_t new_brightness); void turn_on(); void turn_off(); @@ -39,8 +39,8 @@ class TM1651Display : public Component { void repaint_(); - uint8_t calculate_level_(uint8_t); - uint8_t calculate_brightness_(uint8_t); + uint8_t calculate_level_(uint8_t new_level); + uint8_t calculate_brightness_(uint8_t new_brightness); }; template class SetLevelPercentAction : public Action, public Parented { diff --git a/esphome/components/toshiba/toshiba.cpp b/esphome/components/toshiba/toshiba.cpp index 975a149b5246..c7a5b7285279 100644 --- a/esphome/components/toshiba/toshiba.cpp +++ b/esphome/components/toshiba/toshiba.cpp @@ -197,7 +197,7 @@ void ToshibaClimate::transmit_generic_() { // Transmit auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); encode_(data, message, message_length, 1); @@ -210,7 +210,7 @@ void ToshibaClimate::transmit_rac_pt1411hwru_() { clamp(this->target_temperature, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX); float temp_adjd = temperature - TOSHIBA_RAC_PT1411HWRU_TEMP_C_MIN; auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); // Byte 0: Header upper (0xB2) message[0] = RAC_PT1411HWRU_MESSAGE_HEADER0; @@ -357,7 +357,7 @@ void ToshibaClimate::transmit_rac_pt1411hwru_temp_(const bool cs_state, const bo uint8_t message[RAC_PT1411HWRU_MESSAGE_LENGTH] = {0}; float temperature = clamp(this->current_temperature, 0.0, TOSHIBA_RAC_PT1411HWRU_TEMP_C_MAX + 1); auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); // "Comfort Sense" feature notes // IR Code: 0xBA45 xxXX yyYY // xx: Temperature in °C @@ -542,10 +542,11 @@ bool ToshibaClimate::on_receive(remote_base::RemoteReceiveData data) { // case RAC_PT1411HWRU_MODE_DRY: case RAC_PT1411HWRU_MODE_FAN: - if ((message[4] >> 4) == RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY) + if ((message[4] >> 4) == RAC_PT1411HWRU_TEMPERATURE_FAN_ONLY) { this->mode = climate::CLIMATE_MODE_FAN_ONLY; - else + } else { this->mode = climate::CLIMATE_MODE_DRY; + } break; case RAC_PT1411HWRU_MODE_HEAT: diff --git a/esphome/components/touchscreen/__init__.py b/esphome/components/touchscreen/__init__.py new file mode 100644 index 000000000000..8246b9518735 --- /dev/null +++ b/esphome/components/touchscreen/__init__.py @@ -0,0 +1,39 @@ +import esphome.config_validation as cv +import esphome.codegen as cg + +from esphome.components import display +from esphome import automation +from esphome.const import CONF_ON_TOUCH + +CODEOWNERS = ["@jesserockz"] +IS_PLATFORM_COMPONENT = True + +touchscreen_ns = cg.esphome_ns.namespace("touchscreen") + +Touchscreen = touchscreen_ns.class_("Touchscreen") +TouchRotation = touchscreen_ns.enum("TouchRotation") +TouchPoint = touchscreen_ns.struct("TouchPoint") +TouchListener = touchscreen_ns.class_("TouchListener") + +CONF_DISPLAY = "display" +CONF_TOUCHSCREEN_ID = "touchscreen_id" + + +TOUCHSCREEN_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DISPLAY): cv.use_id(display.DisplayBuffer), + cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True), + } +) + + +async def register_touchscreen(var, config): + disp = await cg.get_variable(config[CONF_DISPLAY]) + cg.add(var.set_display(disp)) + + if CONF_ON_TOUCH in config: + await automation.build_automation( + var.get_touch_trigger(), + [(TouchPoint, "touch")], + config[CONF_ON_TOUCH], + ) diff --git a/esphome/components/touchscreen/binary_sensor/__init__.py b/esphome/components/touchscreen/binary_sensor/__init__.py new file mode 100644 index 000000000000..9dba821d4d3e --- /dev/null +++ b/esphome/components/touchscreen/binary_sensor/__init__.py @@ -0,0 +1,64 @@ +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 touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener + +DEPENDENCIES = ["touchscreen"] + +TouchscreenBinarySensor = touchscreen_ns.class_( + "TouchscreenBinarySensor", + binary_sensor.BinarySensor, + cg.Component, + TouchListener, + cg.Parented.template(Touchscreen), +) + +CONF_X_MIN = "x_min" +CONF_X_MAX = "x_max" +CONF_Y_MIN = "y_min" +CONF_Y_MAX = "y_max" + + +def validate_coords(config): + if ( + config[CONF_X_MAX] < config[CONF_X_MIN] + or config[CONF_Y_MAX] < config[CONF_Y_MIN] + ): + raise cv.Invalid( + f"{CONF_X_MAX} is less than {CONF_X_MIN} or {CONF_Y_MAX} is less than {CONF_Y_MIN}" + ) + return config + + +CONFIG_SCHEMA = cv.All( + binary_sensor.BINARY_SENSOR_SCHEMA.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), + } + ).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) + await cg.register_component(var, config) + await cg.register_parented(var, config[CONF_TOUCHSCREEN_ID]) + + cg.add( + var.set_area( + config[CONF_X_MIN], + config[CONF_X_MAX], + config[CONF_Y_MIN], + config[CONF_Y_MAX], + ) + ) diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp new file mode 100644 index 000000000000..ba12aeeae022 --- /dev/null +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp @@ -0,0 +1,19 @@ +#include "touchscreen_binary_sensor.h" + +namespace esphome { +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 (touched) { + this->publish_state(true); + } else { + release(); + } +} + +void TouchscreenBinarySensor::release() { this->publish_state(false); } + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h new file mode 100644 index 000000000000..7b8cac5c4cb6 --- /dev/null +++ b/esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/touchscreen/touchscreen.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace touchscreen { + +class TouchscreenBinarySensor : public binary_sensor::BinarySensor, + public Component, + public TouchListener, + public Parented { + public: + void setup() override { this->parent_->register_listener(this); } + + /// Set the touch screen area where the button will detect the touch. + void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) { + this->x_min_ = x_min; + this->x_max_ = x_max; + this->y_min_ = y_min; + this->y_max_ = y_max; + } + + void touch(TouchPoint tp) override; + void release() override; + + protected: + int16_t x_min_, x_max_, y_min_, y_max_; +}; + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/components/touchscreen/touchscreen.cpp b/esphome/components/touchscreen/touchscreen.cpp new file mode 100644 index 000000000000..9b337fc02c17 --- /dev/null +++ b/esphome/components/touchscreen/touchscreen.cpp @@ -0,0 +1,18 @@ +#include "touchscreen.h" + +#include "esphome/core/log.h" + +namespace esphome { +namespace touchscreen { + +static const char *const TAG = "touchscreen"; + +void Touchscreen::send_touch_(TouchPoint tp) { + ESP_LOGV(TAG, "Touch (x=%d, y=%d)", tp.x, tp.y); + this->touch_trigger_.trigger(tp); + for (auto *listener : this->touch_listeners_) + listener->touch(tp); +} + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/components/touchscreen/touchscreen.h b/esphome/components/touchscreen/touchscreen.h new file mode 100644 index 000000000000..2c0ec9e2684e --- /dev/null +++ b/esphome/components/touchscreen/touchscreen.h @@ -0,0 +1,58 @@ +#pragma once + +#include "esphome/components/display/display_buffer.h" +#include "esphome/core/automation.h" +#include "esphome/core/hal.h" + +#include + +namespace esphome { +namespace touchscreen { + +struct TouchPoint { + uint16_t x; + uint16_t y; + uint8_t id; + uint8_t state; +}; + +class TouchListener { + public: + virtual void touch(TouchPoint tp) = 0; + virtual void release() {} +}; + +enum TouchRotation { + ROTATE_0_DEGREES = 0, + ROTATE_90_DEGREES = 90, + ROTATE_180_DEGREES = 180, + ROTATE_270_DEGREES = 270, +}; + +class Touchscreen { + public: + void set_display(display::DisplayBuffer *display) { + this->display_ = display; + this->display_width_ = display->get_width_internal(); + this->display_height_ = display->get_height_internal(); + this->rotation_ = static_cast(display->get_rotation()); + } + + Trigger *get_touch_trigger() { return &this->touch_trigger_; } + + void register_listener(TouchListener *listener) { this->touch_listeners_.push_back(listener); } + + protected: + /// Call this function to send touch points to the `on_touch` listener and the binary_sensors. + void send_touch_(TouchPoint tp); + + uint16_t display_width_; + uint16_t display_height_; + display::DisplayBuffer *display_; + TouchRotation rotation_; + Trigger touch_trigger_; + std::vector touch_listeners_; +}; + +} // namespace touchscreen +} // namespace esphome diff --git a/esphome/components/tsl2591/sensor.py b/esphome/components/tsl2591/sensor.py index 095a8c886cbf..1ec37b5f93ce 100644 --- a/esphome/components/tsl2591/sensor.py +++ b/esphome/components/tsl2591/sensor.py @@ -56,18 +56,19 @@ 600: TSL2591IntegrationTime.TSL2591_INTEGRATION_TIME_600MS, } -TSL2591Gain = tsl2591_ns.enum("TSL2591Gain") +TSL2591ComponentGain = tsl2591_ns.enum("TSL2591ComponentGain") GAINS = { - "1X": TSL2591Gain.TSL2591_GAIN_LOW, - "LOW": TSL2591Gain.TSL2591_GAIN_LOW, - "25X": TSL2591Gain.TSL2591_GAIN_MED, - "MED": TSL2591Gain.TSL2591_GAIN_MED, - "MEDIUM": TSL2591Gain.TSL2591_GAIN_MED, - "400X": TSL2591Gain.TSL2591_GAIN_HIGH, - "HIGH": TSL2591Gain.TSL2591_GAIN_HIGH, - "9500X": TSL2591Gain.TSL2591_GAIN_MAX, - "MAX": TSL2591Gain.TSL2591_GAIN_MAX, - "MAXIMUM": TSL2591Gain.TSL2591_GAIN_MAX, + "1X": TSL2591ComponentGain.TSL2591_CGAIN_LOW, + "LOW": TSL2591ComponentGain.TSL2591_CGAIN_LOW, + "25X": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "MED": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "MEDIUM": TSL2591ComponentGain.TSL2591_CGAIN_MED, + "400X": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, + "HIGH": TSL2591ComponentGain.TSL2591_CGAIN_HIGH, + "9500X": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "MAX": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "MAXIMUM": TSL2591ComponentGain.TSL2591_CGAIN_MAX, + "AUTO": TSL2591ComponentGain.TSL2591_CGAIN_AUTO, } @@ -117,7 +118,7 @@ def validate_integration_time(value): CONF_INTEGRATION_TIME, default="100ms" ): validate_integration_time, cv.Optional(CONF_NAME, default="TLS2591"): cv.string, - cv.Optional(CONF_GAIN, default="MEDIUM"): cv.enum(GAINS, upper=True), + cv.Optional(CONF_GAIN, default="AUTO"): cv.enum(GAINS, upper=True), cv.Optional(CONF_POWER_SAVE_MODE, default=True): cv.boolean, cv.Optional(CONF_DEVICE_FACTOR, default=53.0): cv.float_with_unit( "device_factor", "", True diff --git a/esphome/components/tsl2591/tsl2591.cpp b/esphome/components/tsl2591/tsl2591.cpp index 7755437de25c..8a540c5f13a0 100644 --- a/esphome/components/tsl2591/tsl2591.cpp +++ b/esphome/components/tsl2591/tsl2591.cpp @@ -43,6 +43,8 @@ void TSL2591Component::disable_if_power_saving_() { } void TSL2591Component::setup() { + if (this->component_gain_ == TSL2591_CGAIN_AUTO) + this->gain_ = TSL2591_GAIN_MED; uint8_t address = this->address_; ESP_LOGI(TAG, "Setting up TSL2591 sensor at I2C address 0x%02X", address); uint8_t id; @@ -73,26 +75,30 @@ void TSL2591Component::dump_config() { } ESP_LOGCONFIG(TAG, " Name: %s", this->name_); - TSL2591Gain raw_gain = this->gain_; + TSL2591ComponentGain raw_gain = this->component_gain_; int gain = 0; std::string gain_word = "unknown"; switch (raw_gain) { - case TSL2591_GAIN_LOW: + case TSL2591_CGAIN_LOW: gain = 1; gain_word = "low"; break; - case TSL2591_GAIN_MED: + case TSL2591_CGAIN_MED: gain = 25; gain_word = "medium"; break; - case TSL2591_GAIN_HIGH: + case TSL2591_CGAIN_HIGH: gain = 400; gain_word = "high"; break; - case TSL2591_GAIN_MAX: + case TSL2591_CGAIN_MAX: gain = 9500; gain_word = "maximum"; break; + case TSL2591_CGAIN_AUTO: + gain = -1; + gain_word = "auto"; + break; } ESP_LOGCONFIG(TAG, " Gain: %dx (%s)", gain, gain_word.c_str()); TSL2591IntegrationTime raw_timing = this->integration_time_; @@ -129,6 +135,9 @@ void TSL2591Component::process_update_() { if (this->calculated_lux_sensor_ != nullptr) { this->calculated_lux_sensor_->publish_state(lux); } + if (this->component_gain_ == TSL2591_CGAIN_AUTO) { + this->automatic_gain_update(full); + } this->status_clear_warning(); } @@ -183,7 +192,7 @@ void TSL2591Component::set_integration_time(TSL2591IntegrationTime integration_t this->integration_time_ = integration_time; } -void TSL2591Component::set_gain(TSL2591Gain gain) { this->gain_ = gain; } +void TSL2591Component::set_gain(TSL2591ComponentGain gain) { this->component_gain_ = gain; } void TSL2591Component::set_device_and_glass_attenuation_factors(float device_factor, float glass_attenuation_factor) { this->device_factor_ = device_factor; @@ -366,5 +375,59 @@ float TSL2591Component::get_calculated_lux(uint16_t full_spectrum, uint16_t infr return std::max(lux, 0.0F); } +/** Calculates and updates the sensor gain setting, trying to keep the full spectrum counts near + * the middle of the range + * + * It's hard to tell how far down to turn the gain when it's at the top of the scale, so decrease + * the gain by up to 2 steps if it's near the top to be sure we get a good reading next time. + * Increase gain by max 2 steps per reading. + * + * If integration time is 100 MS, divide the upper thresholds by 2 to account for ADC saturation + * + * @param full_spectrum The ADC reading for TSL2591 channel 0. + * + * 1/3 FS = 21,845 + */ +void TSL2591Component::automatic_gain_update(uint16_t full_spectrum) { + TSL2591Gain new_gain = this->gain_; + uint fs_divider = (this->integration_time_ == TSL2591_INTEGRATION_TIME_100MS) ? 2 : 1; + + switch (this->gain_) { + case TSL2591_GAIN_LOW: + if (full_spectrum < 54) { // 1/3 FS / GAIN_HIGH + new_gain = TSL2591_GAIN_HIGH; + } else if (full_spectrum < 875) { // 1/3 FS / GAIN_MED + new_gain = TSL2591_GAIN_MED; + } + break; + case TSL2591_GAIN_MED: + if (full_spectrum < 57) { // 1/3 FS / (GAIN_MAX/GAIN_MED) + new_gain = TSL2591_GAIN_MAX; + } else if (full_spectrum < 1365) { // 1/3 FS / (GAIN_HIGH/GAIN_MED) + new_gain = TSL2591_GAIN_HIGH; + } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_LOW/GAIN_MED) clipped to 95% FS + new_gain = TSL2591_GAIN_LOW; + } + break; + case TSL2591_GAIN_HIGH: + if (full_spectrum < 920) { // 1/3 FS / (GAIN_MAX/GAIN_HIGH) + new_gain = TSL2591_GAIN_MAX; + } else if (full_spectrum > 62000 / fs_divider) { // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS + new_gain = TSL2591_GAIN_LOW; + } + break; + case TSL2591_GAIN_MAX: + if (full_spectrum > 62000 / fs_divider) // 2/3 FS / (GAIN_MED/GAIN_HIGH) clipped to 95% FS + new_gain = TSL2591_GAIN_MED; + break; + } + + if (this->gain_ != new_gain) { + this->gain_ = new_gain; + this->set_integration_time_and_gain(this->integration_time_, this->gain_); + } + ESP_LOGD(TAG, "Gain setting: %d", this->gain_); +} + } // namespace tsl2591 } // namespace esphome diff --git a/esphome/components/tsl2591/tsl2591.h b/esphome/components/tsl2591/tsl2591.h index 19352a15c5f4..d82dbc395f95 100644 --- a/esphome/components/tsl2591/tsl2591.h +++ b/esphome/components/tsl2591/tsl2591.h @@ -21,6 +21,19 @@ enum TSL2591IntegrationTime { TSL2591_INTEGRATION_TIME_600MS = 0b101, }; +/** Enum listing all gain settings for the TSL2591 component. + * + * Enum constants are used by the component to allow auto gain, not directly to registers + * Higher values are better for low light situations, but can increase noise. + */ +enum TSL2591ComponentGain { + TSL2591_CGAIN_LOW, // 1x + TSL2591_CGAIN_MED, // 25x + TSL2591_CGAIN_HIGH, // 400x + TSL2591_CGAIN_MAX, // 9500x + TSL2591_CGAIN_AUTO +}; + /** Enum listing all gain settings for the TSL2591. * * Specific values of the enum constants are register values taken from the TSL2591 datasheet. @@ -200,6 +213,13 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { */ void disable(); + /** Updates the gain setting based on the most recent full spectrum reading + * + * This gets called on update and tries to keep the ADC readings in the middle of the range + */ + + void automatic_gain_update(uint16_t full_spectrum); + // ========== INTERNAL METHODS ========== // (In most use cases you won't need these. They're for ESPHome integration use.) /** Used by ESPHome framework. */ @@ -213,7 +233,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { /** Used by ESPHome framework. Does NOT actually set the value on the device. */ void set_integration_time(TSL2591IntegrationTime integration_time); /** Used by ESPHome framework. Does NOT actually set the value on the device. */ - void set_gain(TSL2591Gain gain); + void set_gain(TSL2591ComponentGain gain); /** Used by ESPHome framework. */ void setup() override; /** Used by ESPHome framework. */ @@ -230,6 +250,7 @@ class TSL2591Component : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *visible_sensor_; sensor::Sensor *calculated_lux_sensor_; TSL2591IntegrationTime integration_time_; + TSL2591ComponentGain component_gain_; TSL2591Gain gain_; bool power_save_mode_enabled_; float device_factor_; diff --git a/esphome/components/tuya/fan/__init__.py b/esphome/components/tuya/fan/__init__.py index 6d660e6d2995..4832fd863854 100644 --- a/esphome/components/tuya/fan/__init__.py +++ b/esphome/components/tuya/fan/__init__.py @@ -10,7 +10,7 @@ CONF_OSCILLATION_DATAPOINT = "oscillation_datapoint" CONF_DIRECTION_DATAPOINT = "direction_datapoint" -TuyaFan = tuya_ns.class_("TuyaFan", cg.Component) +TuyaFan = tuya_ns.class_("TuyaFan", cg.Component, fan.Fan) CONFIG_SCHEMA = cv.All( fan.FAN_SCHEMA.extend( @@ -30,12 +30,10 @@ async def to_code(config): parent = await cg.get_variable(config[CONF_TUYA_ID]) - state = await fan.create_fan_state(config) - var = cg.new_Pvariable( - config[CONF_OUTPUT_ID], parent, state, config[CONF_SPEED_COUNT] - ) + var = cg.new_Pvariable(config[CONF_OUTPUT_ID], parent, config[CONF_SPEED_COUNT]) await cg.register_component(var, config) + await fan.register_fan(var, config) if CONF_SPEED_DATAPOINT in config: cg.add(var.set_speed_id(config[CONF_SPEED_DATAPOINT])) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp index d0c88095645f..019b504debac 100644 --- a/esphome/components/tuya/fan/tuya_fan.cpp +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -8,52 +8,48 @@ namespace tuya { static const char *const TAG = "tuya.fan"; void TuyaFan::setup() { - auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), - this->direction_id_.has_value(), this->speed_count_); - this->fan_->set_traits(traits); - if (this->speed_id_.has_value()) { this->parent_->register_listener(*this->speed_id_, [this](const TuyaDatapoint &datapoint) { ESP_LOGV(TAG, "MCU reported speed of: %d", datapoint.value_enum); - auto call = this->fan_->make_call(); - if (datapoint.value_enum < this->speed_count_) - call.set_speed(datapoint.value_enum + 1); - else - ESP_LOGCONFIG(TAG, "Speed has invalid value %d", datapoint.value_enum); - call.perform(); + if (datapoint.value_enum >= this->speed_count_) { + ESP_LOGE(TAG, "Speed has invalid value %d", datapoint.value_enum); + } else { + this->speed = datapoint.value_enum + 1; + this->publish_state(); + } }); } if (this->switch_id_.has_value()) { this->parent_->register_listener(*this->switch_id_, [this](const TuyaDatapoint &datapoint) { ESP_LOGV(TAG, "MCU reported switch is: %s", ONOFF(datapoint.value_bool)); - auto call = this->fan_->make_call(); - call.set_state(datapoint.value_bool); - call.perform(); + this->state = datapoint.value_bool; + this->publish_state(); }); } if (this->oscillation_id_.has_value()) { this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) { ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool)); - auto call = this->fan_->make_call(); - call.set_oscillating(datapoint.value_bool); - call.perform(); + this->oscillating = datapoint.value_bool; + this->publish_state(); }); } if (this->direction_id_.has_value()) { this->parent_->register_listener(*this->direction_id_, [this](const TuyaDatapoint &datapoint) { - auto call = this->fan_->make_call(); - call.set_direction(datapoint.value_bool ? fan::FAN_DIRECTION_REVERSE : fan::FAN_DIRECTION_FORWARD); - call.perform(); ESP_LOGD(TAG, "MCU reported reverse direction is: %s", ONOFF(datapoint.value_bool)); + this->direction = datapoint.value_bool ? fan::FanDirection::REVERSE : fan::FanDirection::FORWARD; + this->publish_state(); }); } - this->fan_->add_on_state_callback([this]() { this->write_state(); }); + this->parent_->add_on_initialized_callback([this]() { + auto restored = this->restore_state_(); + if (restored) + restored->to_call(*this).perform(); + }); } void TuyaFan::dump_config() { - ESP_LOGCONFIG(TAG, "Tuya Fan:"); - ESP_LOGCONFIG(TAG, " Speed count %d", this->speed_count_); + LOG_FAN("", "Tuya Fan", this); if (this->speed_id_.has_value()) ESP_LOGCONFIG(TAG, " Speed has datapoint ID %u", *this->speed_id_); if (this->switch_id_.has_value()) @@ -64,29 +60,26 @@ void TuyaFan::dump_config() { ESP_LOGCONFIG(TAG, " Direction has datapoint ID %u", *this->direction_id_); } -void TuyaFan::write_state() { - if (this->switch_id_.has_value()) { - ESP_LOGV(TAG, "Setting switch: %s", ONOFF(this->fan_->state)); - this->parent_->set_boolean_datapoint_value(*this->switch_id_, this->fan_->state); +fan::FanTraits TuyaFan::get_traits() { + return fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), this->direction_id_.has_value(), + this->speed_count_); +} + +void TuyaFan::control(const fan::FanCall &call) { + if (this->switch_id_.has_value() && call.get_state().has_value()) { + this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state()); } - if (this->oscillation_id_.has_value()) { - ESP_LOGV(TAG, "Setting oscillating: %s", ONOFF(this->fan_->oscillating)); - this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, this->fan_->oscillating); + if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) { + this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); } - if (this->direction_id_.has_value()) { - bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; - ESP_LOGV(TAG, "Setting reverse direction: %s", ONOFF(enable)); + if (this->direction_id_.has_value() && call.get_direction().has_value()) { + bool enable = *call.get_direction() == fan::FanDirection::REVERSE; this->parent_->set_enum_datapoint_value(*this->direction_id_, enable); } - if (this->speed_id_.has_value()) { - ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); - this->parent_->set_enum_datapoint_value(*this->speed_id_, this->fan_->speed - 1); + if (this->speed_id_.has_value() && call.get_speed().has_value()) { + this->parent_->set_enum_datapoint_value(*this->speed_id_, *call.get_speed() - 1); } } -// We need a higher priority than the FanState component to make sure that the traits are set -// when that component sets itself up. -float TuyaFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } - } // namespace tuya } // namespace esphome diff --git a/esphome/components/tuya/fan/tuya_fan.h b/esphome/components/tuya/fan/tuya_fan.h index e96770d8c375..4aba1e1c0775 100644 --- a/esphome/components/tuya/fan/tuya_fan.h +++ b/esphome/components/tuya/fan/tuya_fan.h @@ -2,35 +2,31 @@ #include "esphome/core/component.h" #include "esphome/components/tuya/tuya.h" -#include "esphome/components/fan/fan_state.h" +#include "esphome/components/fan/fan.h" namespace esphome { namespace tuya { -class TuyaFan : public Component { +class TuyaFan : public Component, public fan::Fan { public: - TuyaFan(Tuya *parent, fan::FanState *fan, int speed_count) : parent_(parent), fan_(fan), speed_count_(speed_count) {} + TuyaFan(Tuya *parent, int speed_count) : parent_(parent), speed_count_(speed_count) {} void setup() override; - float get_setup_priority() const override; void dump_config() override; void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; } void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } void set_oscillation_id(uint8_t oscillation_id) { this->oscillation_id_ = oscillation_id; } void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } - void write_state(); + + fan::FanTraits get_traits() override; protected: - void update_speed_(uint32_t value); - void update_switch_(uint32_t value); - void update_oscillation_(uint32_t value); - void update_direction_(uint32_t value); + void control(const fan::FanCall &call) override; Tuya *parent_; optional speed_id_{}; optional switch_id_{}; optional oscillation_id_{}; optional direction_id_{}; - fan::FanState *fan_; int speed_count_{}; }; diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index ecd380283958..7facc52946d4 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -71,31 +71,34 @@ void TuyaLight::dump_config() { ESP_LOGCONFIG(TAG, " Dimmer has datapoint ID %u", *this->dimmer_id_); if (this->switch_id_.has_value()) ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_); - if (this->rgb_id_.has_value()) + if (this->rgb_id_.has_value()) { ESP_LOGCONFIG(TAG, " RGB has datapoint ID %u", *this->rgb_id_); - else if (this->hsv_id_.has_value()) + } else if (this->hsv_id_.has_value()) { ESP_LOGCONFIG(TAG, " HSV has datapoint ID %u", *this->hsv_id_); + } } light::LightTraits TuyaLight::get_traits() { auto traits = light::LightTraits(); if (this->color_temperature_id_.has_value() && this->dimmer_id_.has_value()) { if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { - if (this->color_interlock_) + if (this->color_interlock_) { traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::COLOR_TEMPERATURE}); - else + } else { traits.set_supported_color_modes( {light::ColorMode::RGB_COLOR_TEMPERATURE, light::ColorMode::COLOR_TEMPERATURE}); + } } else traits.set_supported_color_modes({light::ColorMode::COLOR_TEMPERATURE}); traits.set_min_mireds(this->cold_white_temperature_); traits.set_max_mireds(this->warm_white_temperature_); } else if (this->rgb_id_.has_value() || this->hsv_id_.has_value()) { if (this->dimmer_id_.has_value()) { - if (this->color_interlock_) + if (this->color_interlock_) { traits.set_supported_color_modes({light::ColorMode::RGB, light::ColorMode::WHITE}); - else + } else { traits.set_supported_color_modes({light::ColorMode::RGB_WHITE}); + } } else traits.set_supported_color_modes({light::ColorMode::RGB}); } else if (this->dimmer_id_.has_value()) { diff --git a/esphome/components/tuya/text_sensor/__init__.py b/esphome/components/tuya/text_sensor/__init__.py index 1989ca10e3a4..bc603693773d 100644 --- a/esphome/components/tuya/text_sensor/__init__.py +++ b/esphome/components/tuya/text_sensor/__init__.py @@ -1,7 +1,7 @@ from esphome.components import text_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"] @@ -9,19 +9,22 @@ TuyaTextSensor = tuya_ns.class_("TuyaTextSensor", text_sensor.TextSensor, cg.Component) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(TuyaTextSensor), - cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema() + .extend( + { + cv.GenerateID(): cv.declare_id(TuyaTextSensor), + 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 text_sensor.new_text_sensor(config) await cg.register_component(var, config) - await text_sensor.register_text_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/tuya.cpp b/esphome/components/tuya/tuya.cpp index 7ff8c66c4488..1fbca7796d9f 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -33,20 +33,21 @@ void Tuya::dump_config() { return; } for (auto &info : this->datapoints_) { - if (info.type == TuyaDatapointType::RAW) + if (info.type == TuyaDatapointType::RAW) { ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str()); - else if (info.type == TuyaDatapointType::BOOLEAN) + } else if (info.type == TuyaDatapointType::BOOLEAN) { ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool)); - else if (info.type == TuyaDatapointType::INTEGER) + } else if (info.type == TuyaDatapointType::INTEGER) { ESP_LOGCONFIG(TAG, " Datapoint %u: int value (value: %d)", info.id, info.value_int); - else if (info.type == TuyaDatapointType::STRING) + } else if (info.type == TuyaDatapointType::STRING) { ESP_LOGCONFIG(TAG, " Datapoint %u: string value (value: %s)", info.id, info.value_string.c_str()); - else if (info.type == TuyaDatapointType::ENUM) + } else if (info.type == TuyaDatapointType::ENUM) { ESP_LOGCONFIG(TAG, " Datapoint %u: enum (value: %d)", info.id, info.value_enum); - else if (info.type == TuyaDatapointType::BITMASK) + } else if (info.type == TuyaDatapointType::BITMASK) { ESP_LOGCONFIG(TAG, " Datapoint %u: bitmask (value: %x)", info.id, info.value_bitmask); - else + } else { ESP_LOGCONFIG(TAG, " Datapoint %u: unknown", info.id); + } } if ((this->gpio_status_ != -1) || (this->gpio_reset_ != -1)) { ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", this->gpio_status_, @@ -80,9 +81,10 @@ bool Tuya::validate_message_() { // Byte 4: LENGTH1 // Byte 5: LENGTH2 - if (at <= 5) + if (at <= 5) { // no validation for these fields return true; + } uint16_t length = (uint16_t(data[4]) << 8) | (uint16_t(data[5])); @@ -197,7 +199,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); this->initialized_callback_.call(); } - this->handle_datapoint_(buffer, len); + this->handle_datapoints_(buffer, len); break; case TuyaCommandType::DATAPOINT_QUERY: break; @@ -208,7 +210,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff #ifdef USE_TIME if (this->time_id_.has_value()) { this->send_local_time_(); - auto time_id = *this->time_id_; + auto *time_id = *this->time_id_; time_id->add_on_time_sync_callback([this] { this->send_local_time_(); }); } else { ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured"); @@ -222,105 +224,111 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } } -void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { - if (len < 2) - return; - - TuyaDatapoint datapoint{}; - datapoint.id = buffer[0]; - datapoint.type = (TuyaDatapointType) buffer[1]; - datapoint.value_uint = 0; - - // Drop update if datapoint is in ignore_mcu_datapoint_update list - for (uint8_t i : this->ignore_mcu_update_on_datapoints_) { - if (datapoint.id == i) { - ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id); +void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { + while (len >= 4) { + TuyaDatapoint datapoint{}; + datapoint.id = buffer[0]; + datapoint.type = (TuyaDatapointType) buffer[1]; + datapoint.value_uint = 0; + + size_t data_size = (buffer[2] << 8) + buffer[3]; + const uint8_t *data = buffer + 4; + size_t data_len = len - 4; + if (data_size > data_len) { + ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len); return; } - } - size_t data_size = (buffer[2] << 8) + buffer[3]; - const uint8_t *data = buffer + 4; - size_t data_len = len - 4; - if (data_size > data_len) { - ESP_LOGW(TAG, "Datapoint %u has extra bytes that will be ignored (%zu > %zu)", datapoint.id, data_size, data_len); - } else if (data_size < data_len) { - ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu < %zu)", datapoint.id, data_size, data_len); - return; - } - datapoint.len = data_len; + datapoint.len = data_size; - switch (datapoint.type) { - case TuyaDatapointType::RAW: - datapoint.value_raw = std::vector(data, data + data_len); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); - break; - case TuyaDatapointType::BOOLEAN: - if (data_len != 1) { - ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_len); - return; - } - datapoint.value_bool = data[0]; - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool)); - break; - case TuyaDatapointType::INTEGER: - if (data_len != 4) { - ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_len); - return; - } - datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]); - ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int); - break; - case TuyaDatapointType::STRING: - datapoint.value_string = std::string(reinterpret_cast(data), data_len); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str()); - break; - case TuyaDatapointType::ENUM: - if (data_len != 1) { - ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_len); + switch (datapoint.type) { + case TuyaDatapointType::RAW: + datapoint.value_raw = std::vector(data, data + data_size); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); + break; + case TuyaDatapointType::BOOLEAN: + if (data_size != 1) { + ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size); + return; + } + datapoint.value_bool = data[0]; + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool)); + break; + case TuyaDatapointType::INTEGER: + if (data_size != 4) { + ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size); + return; + } + datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]); + ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int); + break; + case TuyaDatapointType::STRING: + datapoint.value_string = std::string(reinterpret_cast(data), data_size); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str()); + break; + case TuyaDatapointType::ENUM: + if (data_size != 1) { + ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size); + return; + } + datapoint.value_enum = data[0]; + ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum); + break; + case TuyaDatapointType::BITMASK: + switch (data_size) { + case 1: + datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]); + break; + case 2: + datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]); + break; + case 4: + datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]); + break; + default: + ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size); + return; + } + ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); + break; + default: + ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); return; + } + + len -= data_size + 4; + buffer = data + data_size; + + // drop update if datapoint is in ignore_mcu_datapoint_update list + bool skip = false; + for (auto i : this->ignore_mcu_update_on_datapoints_) { + if (datapoint.id == i) { + ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id); + skip = true; + break; } - datapoint.value_enum = data[0]; - ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum); - break; - case TuyaDatapointType::BITMASK: - switch (data_len) { - case 1: - datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]); - break; - case 2: - datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]); - break; - case 4: - datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]); - break; - default: - ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_len); - return; + } + if (skip) + continue; + + // Update internal datapoints + bool found = false; + for (auto &other : this->datapoints_) { + if (other.id == datapoint.id) { + other = datapoint; + found = true; } - ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); - break; - default: - ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); - return; - } + } + if (!found) { + this->datapoints_.push_back(datapoint); + } - // Update internal datapoints - bool found = false; - for (auto &other : this->datapoints_) { - if (other.id == datapoint.id) { - other = datapoint; - found = true; + // Run through listeners + for (auto &listener : this->listeners_) { + if (listener.datapoint_id == datapoint.id) + listener.on_datapoint(datapoint); } } - if (!found) { - this->datapoints_.push_back(datapoint); - } - - // Run through listeners - for (auto &listener : this->listeners_) - if (listener.datapoint_id == datapoint.id) - listener.on_datapoint(datapoint); } void Tuya::send_raw_command_(TuyaCommand command) { @@ -414,7 +422,7 @@ void Tuya::send_wifi_status_() { #ifdef USE_TIME void Tuya::send_local_time_() { std::vector payload; - auto time_id = *this->time_id_; + auto *time_id = *this->time_id_; time::ESPTime now = time_id->now(); if (now.is_valid()) { uint8_t year = now.year - 2000; @@ -488,9 +496,10 @@ void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t valu } optional Tuya::get_datapoint_(uint8_t datapoint_id) { - for (auto &datapoint : this->datapoints_) + for (auto &datapoint : this->datapoints_) { if (datapoint.id == datapoint_id) return datapoint; + } return {}; } @@ -578,9 +587,10 @@ void Tuya::register_listener(uint8_t datapoint_id, const std::functionlisteners_.push_back(listener); // Run through existing datapoints - for (auto &datapoint : this->datapoints_) + for (auto &datapoint : this->datapoints_) { if (datapoint.id == datapoint_id) func(datapoint); + } } TuyaInitState Tuya::get_init_state() { return this->init_state_; } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index c46d61119e66..3828c49b48e1 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -101,7 +101,7 @@ class Tuya : public Component, public uart::UARTDevice { protected: void handle_char_(uint8_t c); - void handle_datapoint_(const uint8_t *buffer, size_t len); + void handle_datapoints_(const uint8_t *buffer, size_t len); optional get_datapoint_(uint8_t datapoint_id); bool validate_message_(); diff --git a/esphome/components/uart/uart_component_esp32_arduino.cpp b/esphome/components/uart/uart_component_esp32_arduino.cpp index 95cdde4a43ad..a67e5354fba3 100644 --- a/esphome/components/uart/uart_component_esp32_arduino.cpp +++ b/esphome/components/uart/uart_component_esp32_arduino.cpp @@ -41,10 +41,11 @@ uint32_t ESP32ArduinoUARTComponent::get_config() { * tick_ref_always_on:27 select the clock.1:apb clock:ref_tick */ - if (this->parity_ == UART_CONFIG_PARITY_EVEN) + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { config |= UART_PARITY_EVEN | UART_PARITY_ENABLE; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { config |= UART_PARITY_ODD | UART_PARITY_ENABLE; + } switch (this->data_bits_) { case 5: @@ -61,10 +62,11 @@ uint32_t ESP32ArduinoUARTComponent::get_config() { break; } - if (this->stop_bits_ == 1) + if (this->stop_bits_ == 1) { config |= UART_NB_STOP_BIT_1; - else + } else { config |= UART_NB_STOP_BIT_2; + } config |= UART_TICK_APB_CLOCK; diff --git a/esphome/components/uart/uart_component_esp8266.cpp b/esphome/components/uart/uart_component_esp8266.cpp index 370adad77977..529108f439f3 100644 --- a/esphome/components/uart/uart_component_esp8266.cpp +++ b/esphome/components/uart/uart_component_esp8266.cpp @@ -18,12 +18,13 @@ bool ESP8266UartComponent::serial0_in_use = false; // NOLINT(cppcoreguidelines- uint32_t ESP8266UartComponent::get_config() { uint32_t config = 0; - if (this->parity_ == UART_CONFIG_PARITY_NONE) + if (this->parity_ == UART_CONFIG_PARITY_NONE) { config |= UART_PARITY_NONE; - else if (this->parity_ == UART_CONFIG_PARITY_EVEN) + } else if (this->parity_ == UART_CONFIG_PARITY_EVEN) { config |= UART_PARITY_EVEN; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { config |= UART_PARITY_ODD; + } switch (this->data_bits_) { case 5: @@ -40,10 +41,11 @@ uint32_t ESP8266UartComponent::get_config() { break; } - if (this->stop_bits_ == 1) + if (this->stop_bits_ == 1) { config |= UART_NB_STOP_BIT_1; - else + } else { config |= UART_NB_STOP_BIT_2; + } if (this->tx_pin_ != nullptr && this->tx_pin_->is_inverted()) config |= BIT(22); @@ -234,12 +236,13 @@ void IRAM_ATTR HOT ESP8266SoftwareSerial::write_byte(uint8_t data) { } bool parity_bit = false; bool need_parity_bit = true; - if (this->parity_ == UART_CONFIG_PARITY_EVEN) + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { parity_bit = false; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { parity_bit = true; - else + } else { need_parity_bit = false; + } { InterruptLock lock; diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 4d6a6af0fc16..80255ccddf0f 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -16,10 +16,11 @@ static const char *const TAG = "uart.idf"; uart_config_t IDFUARTComponent::get_config_() { uart_parity_t parity = UART_PARITY_DISABLE; - if (this->parity_ == UART_CONFIG_PARITY_EVEN) + if (this->parity_ == UART_CONFIG_PARITY_EVEN) { parity = UART_PARITY_EVEN; - else if (this->parity_ == UART_CONFIG_PARITY_ODD) + } else if (this->parity_ == UART_CONFIG_PARITY_ODD) { parity = UART_PARITY_ODD; + } uart_word_length_t data_bits; switch (this->data_bits_) { @@ -141,9 +142,9 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; xSemaphoreTake(this->lock_, portMAX_DELAY); - if (this->has_peek_) + if (this->has_peek_) { *data = this->peek_byte_; - else { + } else { int len = uart_read_bytes(this->uart_num_, data, 1, 20 / portTICK_RATE_MS); if (len == 0) { *data = 0; diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py index 4835caf35bf6..c8774bb322e5 100644 --- a/esphome/components/version/text_sensor.py +++ b/esphome/components/version/text_sensor.py @@ -2,9 +2,6 @@ import esphome.config_validation as cv from esphome.components import text_sensor from esphome.const import ( - CONF_ENTITY_CATEGORY, - CONF_ID, - CONF_ICON, ENTITY_CATEGORY_DIAGNOSTIC, ICON_NEW_BOX, CONF_HIDE_TIMESTAMP, @@ -15,20 +12,22 @@ "VersionTextSensor", text_sensor.TextSensor, cg.Component ) -CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(VersionTextSensor), - cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon, - cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC - ): cv.entity_category, - } -).extend(cv.COMPONENT_SCHEMA) +CONFIG_SCHEMA = ( + text_sensor.text_sensor_schema( + icon=ICON_NEW_BOX, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ) + .extend( + { + cv.GenerateID(): cv.declare_id(VersionTextSensor), + cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean, + } + ) + .extend(cv.COMPONENT_SCHEMA) +) async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) - await text_sensor.register_text_sensor(var, config) + var = await text_sensor.new_text_sensor(config) await cg.register_component(var, config) cg.add(var.set_hide_timestamp(config[CONF_HIDE_TIMESTAMP])) diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.cpp b/esphome/components/vl53l0x/vl53l0x_sensor.cpp index d68d69b79c7d..171484f6f2bb 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.cpp +++ b/esphome/components/vl53l0x/vl53l0x_sensor.cpp @@ -124,10 +124,11 @@ void VL53L0XSensor::setup() { uint8_t &val = ref_spad_map[i / 8]; uint8_t mask = 1 << (i % 8); - if (i < first_spad_to_enable || spads_enabled == spad_count) + if (i < first_spad_to_enable || spads_enabled == spad_count) { val &= ~mask; - else if (val & mask) + } else if (val & mask) { spads_enabled += 1; + } } this->write_bytes(0xB0, ref_spad_map, 6); diff --git a/esphome/components/vl53l0x/vl53l0x_sensor.h b/esphome/components/vl53l0x/vl53l0x_sensor.h index a2e24e755006..85b5e3b31dd9 100644 --- a/esphome/components/vl53l0x/vl53l0x_sensor.h +++ b/esphome/components/vl53l0x/vl53l0x_sensor.h @@ -60,10 +60,11 @@ class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c if (enables.tcc) budget_us += (timeouts.msrc_dss_tcc_us + tcc_overhead); - if (enables.dss) + if (enables.dss) { budget_us += 2 * (timeouts.msrc_dss_tcc_us + dss_overhead); - else if (enables.msrc) + } else if (enables.msrc) { budget_us += (timeouts.msrc_dss_tcc_us + msrc_overhead); + } if (enables.pre_range) budget_us += (timeouts.pre_range_us + pre_range_overhead); @@ -191,12 +192,13 @@ class VL53L0XSensor : public sensor::Sensor, public PollingComponent, public i2c uint8_t get_vcsel_pulse_period_(VcselPeriodType type) { uint8_t vcsel; - if (type == VCSEL_PERIOD_PRE_RANGE) + if (type == VCSEL_PERIOD_PRE_RANGE) { vcsel = reg(0x50).get(); - else if (type == VCSEL_PERIOD_FINAL_RANGE) + } else if (type == VCSEL_PERIOD_FINAL_RANGE) { vcsel = reg(0x70).get(); - else + } else { return 255; + } return (vcsel + 1) << 1; } diff --git a/esphome/components/wake_on_lan/__init__.py b/esphome/components/wake_on_lan/__init__.py new file mode 100644 index 000000000000..3548fb02f45c --- /dev/null +++ b/esphome/components/wake_on_lan/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@willwill2will54"] diff --git a/esphome/components/wake_on_lan/button.py b/esphome/components/wake_on_lan/button.py new file mode 100644 index 000000000000..2710eb3df904 --- /dev/null +++ b/esphome/components/wake_on_lan/button.py @@ -0,0 +1,32 @@ +import esphome.codegen as cg +from esphome.components import button +import esphome.config_validation as cv +from esphome.const import CONF_ID + +CONF_TARGET_MAC_ADDRESS = "target_mac_address" + +wake_on_lan_ns = cg.esphome_ns.namespace("wake_on_lan") + +WakeOnLanButton = wake_on_lan_ns.class_("WakeOnLanButton", button.Button, cg.Component) + +DEPENDENCIES = ["network"] + +CONFIG_SCHEMA = cv.All( + button.BUTTON_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend( + cv.Schema( + { + cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address, + cv.GenerateID(): cv.declare_id(WakeOnLanButton), + } + ), + ), + cv.only_with_arduino, +) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + + yield cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts)) + yield cg.register_component(var, config) + yield button.register_button(var, config) diff --git a/esphome/components/wake_on_lan/wake_on_lan.cpp b/esphome/components/wake_on_lan/wake_on_lan.cpp new file mode 100644 index 000000000000..893aa758951b --- /dev/null +++ b/esphome/components/wake_on_lan/wake_on_lan.cpp @@ -0,0 +1,58 @@ +#ifdef USE_ARDUINO + +#include "wake_on_lan.h" +#include "esphome/core/log.h" +#include "esphome/components/network/ip_address.h" +#include "esphome/components/network/util.h" + +namespace esphome { +namespace wake_on_lan { + +static const char *const TAG = "wake_on_lan.button"; +static const uint8_t PREFIX[6] = {255, 255, 255, 255, 255, 255}; + +void WakeOnLanButton::set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f) { + macaddr_[0] = a; + macaddr_[1] = b; + macaddr_[2] = c; + macaddr_[3] = d; + macaddr_[4] = e; + macaddr_[5] = f; +} + +void WakeOnLanButton::dump_config() { + LOG_BUTTON("", "Wake-on-LAN Button", this); + ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", macaddr_[0], macaddr_[1], macaddr_[2], + macaddr_[3], macaddr_[4], macaddr_[5]); +} + +void WakeOnLanButton::press_action() { + ESP_LOGI(TAG, "Sending Wake-on-LAN Packet..."); + bool begin_status = false; + bool end_status = false; + uint32_t interface = esphome::network::get_ip_address(); + IPAddress interface_ip = IPAddress(interface); + IPAddress broadcast = IPAddress(255, 255, 255, 255); +#ifdef USE_ESP8266 + begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, interface_ip, 128); +#endif +#ifdef USE_ESP32 + begin_status = this->udp_client_.beginPacket(broadcast, 9); +#endif + + if (begin_status) { + this->udp_client_.write(PREFIX, 6); + for (size_t i = 0; i < 16; i++) { + this->udp_client_.write(macaddr_, 6); + } + end_status = this->udp_client_.endPacket(); + } + if (!begin_status || end_status) { + ESP_LOGE(TAG, "Sending Wake-on-LAN Packet Failed!"); + } +} + +} // namespace wake_on_lan +} // namespace esphome + +#endif diff --git a/esphome/components/wake_on_lan/wake_on_lan.h b/esphome/components/wake_on_lan/wake_on_lan.h new file mode 100644 index 000000000000..72f900e3fa77 --- /dev/null +++ b/esphome/components/wake_on_lan/wake_on_lan.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef USE_ARDUINO + +#include "esphome/components/button/button.h" +#include "esphome/core/component.h" +#include "WiFiUdp.h" + +namespace esphome { +namespace wake_on_lan { + +class WakeOnLanButton : public button::Button, public Component { + public: + void set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f); + + void dump_config() override; + + protected: + WiFiUDP udp_client_{}; + void press_action() override; + uint8_t macaddr_[6]; +}; + +} // namespace wake_on_lan +} // namespace esphome + +#endif diff --git a/esphome/components/waveshare_epaper/display.py b/esphome/components/waveshare_epaper/display.py index 1d1644dc251c..44120ebbc536 100644 --- a/esphome/components/waveshare_epaper/display.py +++ b/esphome/components/waveshare_epaper/display.py @@ -44,6 +44,9 @@ WaveshareEPaper7P5InBC = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InBC", WaveshareEPaper ) +WaveshareEPaper7P5InBV2 = waveshare_epaper_ns.class_( + "WaveshareEPaper7P5InBV2", WaveshareEPaper +) WaveshareEPaper7P5InV2 = waveshare_epaper_ns.class_( "WaveshareEPaper7P5InV2", WaveshareEPaper ) @@ -70,6 +73,7 @@ "4.20in-bv2": ("b", WaveshareEPaper4P2InBV2), "5.83in": ("b", WaveshareEPaper5P8In), "7.50in": ("b", WaveshareEPaper7P5In), + "7.50in-bv2": ("b", WaveshareEPaper7P5InBV2), "7.50in-bc": ("b", WaveshareEPaper7P5InBC), "7.50inv2": ("b", WaveshareEPaper7P5InV2), "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 322c375f0e62..71e3b22e7d47 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.cpp +++ b/esphome/components/waveshare_epaper/waveshare_epaper.cpp @@ -140,10 +140,11 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, Color color const uint32_t pos = (x + y * this->get_width_internal()) / 8u; const uint8_t subpos = x & 0x07; // flip logic - if (!color.is_on()) + if (!color.is_on()) { this->buffer_[pos] |= 0x80 >> subpos; - else + } else { this->buffer_[pos] &= ~(0x80 >> subpos); + } } uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal() / 8u; } void WaveshareEPaper::start_command_() { @@ -901,18 +902,20 @@ void HOT WaveshareEPaper5P8In::display() { uint8_t temp1 = this->buffer_[i]; for (uint8_t j = 0; j < 8; j++) { uint8_t temp2; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 = 0x03; - else + } else { temp2 = 0x00; + } temp2 <<= 4; temp1 <<= 1; j++; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 |= 0x03; - else + } else { temp2 |= 0x00; + } temp1 <<= 1; this->write_byte(temp2); } @@ -934,6 +937,74 @@ void WaveshareEPaper5P8In::dump_config() { LOG_PIN(" Busy Pin: ", this->busy_pin_); LOG_UPDATE_INTERVAL(this); } +void WaveshareEPaper7P5InBV2::initialize() { + // COMMAND POWER SETTING + this->command(0x01); + this->data(0x07); + this->data(0x07); // VGH=20V,VGL=-20V + this->data(0x3f); // VDH=15V + this->data(0x3f); // VDL=-15V + // COMMAND POWER ON + this->command(0x04); + delay(100); // NOLINT + this->wait_until_idle_(); + // COMMAND PANEL SETTING + this->command(0x00); + this->data(0x0F); // KW3f, KWR-2F, BWROTP 0f, BWOTP 1f + this->command(0x61); // tres + this->data(0x03); // 800px + this->data(0x20); + this->data(0x01); // 400px + this->data(0xE0); + this->command(0x15); + this->data(0x00); + // COMMAND VCOM AND DATA INTERVAL SETTING + this->command(0x50); + this->data(0x11); + this->data(0x07); + // COMMAND TCON SETTING + this->command(0x60); + this->data(0x22); + // COMMAND RESOLUTION SETTING + this->command(0x65); + this->data(0x00); + this->data(0x00); // 800*480 + this->data(0x00); + this->data(0x00); +} +void HOT WaveshareEPaper7P5InBV2::display() { + // COMMAND DATA START TRANSMISSION 1 (B/W data) + this->command(0x10); + delay(2); + this->start_data_(); + this->write_array(this->buffer_, this->get_buffer_length_()); + this->end_data_(); + delay(2); + + // COMMAND DATA START TRANSMISSION 2 (RED data) + this->command(0x13); + delay(2); + this->start_data_(); + for (size_t i = 0; i < this->get_buffer_length_(); i++) + this->write_byte(0x00); + this->end_data_(); + delay(2); + + // COMMAND DISPLAY REFRESH + this->command(0x12); + delay(100); // NOLINT + this->wait_until_idle_(); +} +int WaveshareEPaper7P5InBV2::get_width_internal() { return 800; } +int WaveshareEPaper7P5InBV2::get_height_internal() { return 480; } +void WaveshareEPaper7P5InBV2::dump_config() { + LOG_DISPLAY("", "Waveshare E-Paper", this); + ESP_LOGCONFIG(TAG, " Model: 7.5in-bv2"); + LOG_PIN(" Reset Pin: ", this->reset_pin_); + LOG_PIN(" DC Pin: ", this->dc_pin_); + LOG_PIN(" Busy Pin: ", this->busy_pin_); + LOG_UPDATE_INTERVAL(this); +} void WaveshareEPaper7P5In::initialize() { // COMMAND POWER SETTING this->command(0x01); @@ -984,17 +1055,19 @@ void HOT WaveshareEPaper7P5In::display() { uint8_t temp1 = this->buffer_[i]; for (uint8_t j = 0; j < 8; j++) { uint8_t temp2; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 = 0x03; - else + } else { temp2 = 0x00; + } temp2 <<= 4; temp1 <<= 1; j++; - if (temp1 & 0x80) + if (temp1 & 0x80) { temp2 |= 0x03; - else + } else { temp2 |= 0x00; + } temp1 <<= 1; this->write_byte(temp2); } @@ -1187,10 +1260,11 @@ void HOT WaveshareEPaper2P13InDKE::display() { bool partial = this->at_update_ != 0; this->at_update_ = (this->at_update_ + 1) % this->full_update_every_; - if (partial) + if (partial) { ESP_LOGI(TAG, "Performing partial e-paper update."); - else + } else { ESP_LOGI(TAG, "Performing full e-paper update."); + } // start and set up data format this->command(0x12); diff --git a/esphome/components/waveshare_epaper/waveshare_epaper.h b/esphome/components/waveshare_epaper/waveshare_epaper.h index 4de2ac7d9705..96bd2fc782c3 100644 --- a/esphome/components/waveshare_epaper/waveshare_epaper.h +++ b/esphome/components/waveshare_epaper/waveshare_epaper.h @@ -121,6 +121,7 @@ enum WaveshareEPaperTypeBModel { WAVESHARE_EPAPER_4_2_IN_B_V2, WAVESHARE_EPAPER_7_5_IN, WAVESHARE_EPAPER_7_5_INV2, + WAVESHARE_EPAPER_7_5_IN_B_V2, }; class WaveshareEPaper2P7In : public WaveshareEPaper { @@ -280,6 +281,29 @@ class WaveshareEPaper7P5In : public WaveshareEPaper { int get_height_internal() override; }; +class WaveshareEPaper7P5InBV2 : public WaveshareEPaper { + public: + void initialize() override; + + void display() override; + + void dump_config() override; + + void deep_sleep() override { + // COMMAND POWER OFF + this->command(0x02); + this->wait_until_idle_(); + // COMMAND DEEP SLEEP + this->command(0x07); // deep sleep + this->data(0xA5); // check byte + } + + protected: + int get_width_internal() override; + + int get_height_internal() override; +}; + class WaveshareEPaper7P5InBC : public WaveshareEPaper { public: void initialize() override; diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py index 62d5ec6f141b..1e5f341717e4 100644 --- a/esphome/components/web_server/__init__.py +++ b/esphome/components/web_server/__init__.py @@ -66,8 +66,8 @@ async def to_code(config): cg.add_define("USE_WEBSERVER") cg.add(paren.set_port(config[CONF_PORT])) - cg.add_define("WEBSERVER_PORT", config[CONF_PORT]) cg.add_define("USE_WEBSERVER") + cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT]) 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])) diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index 7413af67c4c6..44d044750eb0 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -91,64 +91,81 @@ void WebServer::setup() { client->send("", "ping", millis(), 30000); #ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) + for (auto *obj : App.get_sensors()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->sensor_json(obj, obj->state).c_str(), "state"); + } #endif #ifdef USE_SWITCH - for (auto *obj : App.get_switches()) + for (auto *obj : App.get_switches()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->switch_json(obj, obj->state).c_str(), "state"); + } #endif #ifdef USE_BINARY_SENSOR - for (auto *obj : App.get_binary_sensors()) + 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"); + } #endif #ifdef USE_FAN - for (auto *obj : App.get_fans()) + for (auto *obj : App.get_fans()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->fan_json(obj).c_str(), "state"); + } #endif #ifdef USE_LIGHT - for (auto *obj : App.get_lights()) + for (auto *obj : App.get_lights()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->light_json(obj).c_str(), "state"); + } #endif #ifdef USE_TEXT_SENSOR - for (auto *obj : App.get_text_sensors()) + 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"); + } #endif #ifdef USE_COVER - for (auto *obj : App.get_covers()) + for (auto *obj : App.get_covers()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->cover_json(obj).c_str(), "state"); + } #endif #ifdef USE_NUMBER - for (auto *obj : App.get_numbers()) + for (auto *obj : App.get_numbers()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->number_json(obj, obj->state).c_str(), "state"); + } #endif #ifdef USE_SELECT - for (auto *obj : App.get_selects()) + for (auto *obj : App.get_selects()) { if (this->include_internal_ || !obj->is_internal()) client->send(this->select_json(obj, obj->state).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"); + } #endif }); #ifdef USE_LOGGER - if (logger::global_logger != nullptr) + if (logger::global_logger != nullptr) { logger::global_logger->add_on_log_callback( [this](int level, const char *tag, const char *message) { this->events_.send(message, "log", millis()); }); + } #endif this->base_->add_handler(&this->events_); this->base_->add_handler(this); @@ -187,15 +204,17 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { stream->addHeader("Access-Control-Allow-Origin", "*"); #ifdef USE_SENSOR - for (auto *obj : App.get_sensors()) + for (auto *obj : App.get_sensors()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "sensor", ""); + } #endif #ifdef USE_SWITCH - for (auto *obj : App.get_switches()) + for (auto *obj : App.get_switches()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "switch", ""); + } #endif #ifdef USE_BUTTON @@ -204,38 +223,43 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) { #endif #ifdef USE_BINARY_SENSOR - for (auto *obj : App.get_binary_sensors()) + for (auto *obj : App.get_binary_sensors()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "binary_sensor", ""); + } #endif #ifdef USE_FAN - for (auto *obj : App.get_fans()) + for (auto *obj : App.get_fans()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "fan", ""); + } #endif #ifdef USE_LIGHT - for (auto *obj : App.get_lights()) + for (auto *obj : App.get_lights()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "light", ""); + } #endif #ifdef USE_TEXT_SENSOR - for (auto *obj : App.get_text_sensors()) + for (auto *obj : App.get_text_sensors()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "text_sensor", ""); + } #endif #ifdef USE_COVER - for (auto *obj : App.get_covers()) + for (auto *obj : App.get_covers()) { if (this->include_internal_ || !obj->is_internal()) write_row(stream, obj, "cover", ""); + } #endif #ifdef USE_NUMBER - for (auto *obj : App.get_numbers()) - if (this->include_internal_ || !obj->is_internal()) + for (auto *obj : App.get_numbers()) { + if (this->include_internal_ || !obj->is_internal()) { write_row(stream, obj, "number", "", [](AsyncResponseStream &stream, EntityBase *obj) { number::Number *number = (number::Number *) obj; stream.print(R"(state); stream.print(R"("/>)"); }); + } + } #endif #ifdef USE_SELECT - for (auto *obj : App.get_selects()) - if (this->include_internal_ || !obj->is_internal()) + for (auto *obj : App.get_selects()) { + if (this->include_internal_ || !obj->is_internal()) { write_row(stream, obj, "select", "", [](AsyncResponseStream &stream, EntityBase *obj) { select::Select *select = (select::Select *) obj; stream.print(""); }); + } + } +#endif + +#ifdef USE_LOCK + for (auto *obj : App.get_locks()) { + if (this->include_internal_ || !obj->is_internal()) { + write_row(stream, obj, "lock", "", [](AsyncResponseStream &stream, EntityBase *obj) { + lock::Lock *lock = (lock::Lock *) obj; + stream.print(""); + if (lock->traits.get_supports_open()) { + stream.print(""); + } + }); + } + } #endif stream->print(F("

See ESPHome Web API for " @@ -440,8 +482,8 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con #endif #ifdef USE_FAN -void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } -std::string WebServer::fan_json(fan::FanState *obj) { +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"; @@ -470,7 +512,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { }); } void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { - for (fan::FanState *obj : App.get_fans()) { + for (fan::Fan *obj : App.get_fans()) { if (obj->get_object_id() != match.id) continue; @@ -516,7 +558,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc return; } } - this->defer([call]() { call.perform(); }); + this->defer([call]() mutable { call.perform(); }); request->send(200); } else if (match.method == "turn_off") { this->defer([obj]() { obj->turn_off().perform(); }); @@ -742,6 +784,43 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value } #endif +#ifdef USE_LOCK +void WebServer::on_lock_update(lock::Lock *obj) { + this->events_.send(this->lock_json(obj, obj->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; + }); +} +void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) { + for (lock::Lock *obj : App.get_locks()) { + if (obj->get_object_id() != match.id) + continue; + + if (request->method() == HTTP_GET) { + std::string data = this->lock_json(obj, obj->state); + request->send(200, "text/json", data.c_str()); + } else if (match.method == "lock") { + this->defer([obj]() { obj->lock(); }); + request->send(200); + } else if (match.method == "unlock") { + this->defer([obj]() { obj->unlock(); }); + request->send(200); + } else if (match.method == "open") { + this->defer([obj]() { obj->open(); }); + request->send(200); + } else { + request->send(404); + } + return; + } + request->send(404); +} +#endif + bool WebServer::canHandle(AsyncWebServerRequest *request) { if (request->url() == "/") return true; @@ -809,6 +888,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) { return true; #endif +#ifdef USE_LOCK + if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock") + return true; +#endif + return false; } void WebServer::handleRequest(AsyncWebServerRequest *request) { @@ -901,6 +985,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) { return; } #endif + +#ifdef USE_LOCK + if (match.domain == "lock") { + this->handle_lock_request(request, match); + return; + } +#endif } bool WebServer::isRequestHandlerTrivial() { return false; } diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h index 8edb4237a290..3dd5c93f5990 100644 --- a/esphome/components/web_server/web_server.h +++ b/esphome/components/web_server/web_server.h @@ -128,13 +128,13 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { #endif #ifdef USE_FAN - void on_fan_update(fan::FanState *obj) override; + void on_fan_update(fan::Fan *obj) override; /// Handle a fan request under '/fan//'. void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match); /// Dump the fan state as a JSON string. - std::string fan_json(fan::FanState *obj); + std::string fan_json(fan::Fan *obj); #endif #ifdef USE_LIGHT @@ -185,6 +185,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { std::string select_json(select::Select *obj, const std::string &value); #endif +#ifdef USE_LOCK + void on_lock_update(lock::Lock *obj) override; + + /// Handle a lock request under '/lock//'. + 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); +#endif + /// Override the web handler's canHandle method. bool canHandle(AsyncWebServerRequest *request) override; /// Override the web handler's handleRequest method. diff --git a/esphome/components/whirlpool/whirlpool.cpp b/esphome/components/whirlpool/whirlpool.cpp index d705b42a8c36..f354ab070d4b 100644 --- a/esphome/components/whirlpool/whirlpool.cpp +++ b/esphome/components/whirlpool/whirlpool.cpp @@ -120,7 +120,7 @@ void WhirlpoolClimate::transmit_state() { // Send code auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(38000); @@ -164,10 +164,10 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) { return false; } for (int j = 0; j < 8; j++) { - if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) + if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) { remote_state[i] |= 1 << j; - else if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ZERO_SPACE)) { + } else if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ZERO_SPACE)) { ESP_LOGV(TAG, "Byte %d bit %d fail", i, j); return false; } diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 36944e3633eb..1348b54b372f 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -78,9 +78,10 @@ void WiFiComponent::setup() { #endif } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) + if (esp32_improv::global_improv_component != nullptr) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); + } #endif this->wifi_apply_hostname_(); } @@ -142,10 +143,12 @@ void WiFiComponent::loop() { } #ifdef USE_IMPROV - if (esp32_improv::global_improv_component != nullptr) - if (!this->is_connected()) + if (esp32_improv::global_improv_component != nullptr) { + if (!this->is_connected()) { if (this->wifi_mode_(true, {})) esp32_improv::global_improv_component->start(); + } + } #endif diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index d04f15695d74..615a6905bc4e 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -1,6 +1,5 @@ #pragma once -#include "esphome/core/macros.h" #include "esphome/core/component.h" #include "esphome/core/defines.h" #include "esphome/core/automation.h" @@ -18,7 +17,7 @@ #include #include -#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) +#if defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE < VERSION_CODE(2, 4, 0) extern "C" { #include }; @@ -228,23 +227,26 @@ class WiFiComponent : public Component { network::IPAddress wifi_soft_ap_ip(); bool has_sta_priority(const bssid_t &bssid) { - for (auto &it : this->sta_priorities_) + for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) return true; + } return false; } float get_sta_priority(const bssid_t bssid) { - for (auto &it : this->sta_priorities_) + for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) return it.priority; + } return 0.0f; } void set_sta_priority(const bssid_t bssid, float priority) { - for (auto &it : this->sta_priorities_) + for (auto &it : this->sta_priorities_) { if (it.bssid == bssid) { it.priority = priority; return; } + } this->sta_priorities_.push_back(WiFiSTAPriority{ .bssid = bssid, .priority = priority, @@ -299,7 +301,7 @@ class WiFiComponent : public Component { void wifi_scan_done_callback_(); #endif #ifdef USE_ESP_IDF - void wifi_process_event_(IDFWiFiEvent *); + void wifi_process_event_(IDFWiFiEvent *data); #endif std::string use_address_; diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 202177320958..de4253fe41eb 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -1,5 +1,5 @@ #include "wifi_component.h" -#include "esphome/core/macros.h" +#include "esphome/core/defines.h" #ifdef USE_ESP8266 @@ -20,7 +20,7 @@ extern "C" { #if LWIP_IPV6 #include "lwip/netif.h" // struct netif #endif -#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) #include "LwipDhcpServer.h" #define wifi_softap_set_dhcps_lease(lease) dhcpSoftAP.set_dhcps_lease(lease) #define wifi_softap_set_dhcps_lease_time(time) dhcpSoftAP.set_dhcps_lease_time(time) @@ -238,7 +238,7 @@ bool WiFiComponent::wifi_sta_connect_(const WiFiAP &ap) { conf.bssid_set = 0; } -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) if (ap.get_password().empty()) { conf.threshold.authmode = AUTH_OPEN; } else { @@ -528,7 +528,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { ESP_LOGVV(TAG, "Event: AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); break; } -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) case EVENT_OPMODE_CHANGED: { auto it = event->event_info.opmode_changed; ESP_LOGV(TAG, "Event: Changed Mode old=%s new=%s", LOG_STR_ARG(get_op_mode_str(it.old_opmode)), @@ -614,7 +614,7 @@ bool WiFiComponent::wifi_scan_start_() { config.bssid = nullptr; config.channel = 0; config.show_hidden = 1; -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) config.scan_type = WIFI_SCAN_TYPE_ACTIVE; if (first_scan) { config.scan_time.active.min = 100; @@ -693,7 +693,7 @@ bool WiFiComponent::wifi_ap_ip_config_(optional manual_ip) { return false; } -#if ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0) dhcpSoftAP.begin(&info); #endif diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index 5a81fd0a39fc..b838e42b0d82 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -56,6 +56,7 @@ struct IDFWiFiEvent { wifi_event_ap_probe_req_rx_t ap_probe_req_rx; wifi_event_bss_rssi_low_t bss_rssi_low; ip_event_got_ip_t ip_got_ip; + ip_event_got_ip6_t ip_got_ip6; ip_event_ap_staipassigned_t ip_ap_staipassigned; } data; }; @@ -79,6 +80,8 @@ void event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, voi memcpy(&event.data.sta_disconnected, event_data, sizeof(wifi_event_sta_disconnected_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) { memcpy(&event.data.ip_got_ip, event_data, sizeof(ip_event_got_ip_t)); + } else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) { + memcpy(&event.data.ip_got_ip6, event_data, sizeof(ip_event_got_ip6_t)); } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP) { // NOLINT(bugprone-branch-clone) // no data } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { @@ -182,14 +185,15 @@ bool WiFiComponent::wifi_mode_(optional sta, optional ap) { bool set_ap = ap.has_value() ? *ap : current_ap; wifi_mode_t set_mode; - if (set_sta && set_ap) + if (set_sta && set_ap) { set_mode = WIFI_MODE_APSTA; - else if (set_sta && !set_ap) + } else if (set_sta && !set_ap) { set_mode = WIFI_MODE_STA; - else if (!set_sta && set_ap) + } else if (!set_sta && set_ap) { set_mode = WIFI_MODE_AP; - else + } else { set_mode = WIFI_MODE_NULL; + } if (current_mode == set_mode) return true; @@ -496,12 +500,8 @@ const char *get_auth_mode_str(uint8_t mode) { } } -std::string format_ip4_addr(const esp_ip4_addr_t &ip) { - char buf[20]; - snprintf(buf, sizeof(buf), "%u.%u.%u.%u", uint8_t(ip.addr >> 0), uint8_t(ip.addr >> 8), uint8_t(ip.addr >> 16), - uint8_t(ip.addr >> 24)); - return buf; -} +std::string format_ip4_addr(const esp_ip4_addr_t &ip) { return str_snprintf(IPSTR, 15, IP2STR(&ip)); } +std::string format_ip6_addr(const esp_ip6_addr_t &ip) { return str_snprintf(IPV6STR, 39, IPV62STR(ip)); } const char *get_disconnect_reason_str(uint8_t reason) { switch (reason) { case WIFI_REASON_AUTH_EXPIRE: @@ -634,10 +634,17 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_GOT_IP) { const auto &it = data->data.ip_got_ip; +#ifdef LWIP_IPV6_AUTOCONFIG + tcpip_adapter_create_ip6_linklocal(TCPIP_ADAPTER_IF_STA); +#endif ESP_LOGV(TAG, "Event: Got IP static_ip=%s gateway=%s", format_ip4_addr(it.ip_info.ip).c_str(), format_ip4_addr(it.ip_info.gw).c_str()); s_sta_got_ip = true; + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_GOT_IP6) { + const auto &it = data->data.ip_got_ip6; + ESP_LOGV(TAG, "Event: Got IPv6 address=%s", format_ip6_addr(it.ip6_info.ip).c_str()); + } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_STA_LOST_IP) { ESP_LOGV(TAG, "Event: Lost IP"); s_sta_got_ip = false; diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py index 706a8967beff..58250c3759a0 100644 --- a/esphome/components/wifi_info/text_sensor.py +++ b/esphome/components/wifi_info/text_sensor.py @@ -3,8 +3,6 @@ from esphome.components import text_sensor from esphome.const import ( CONF_BSSID, - CONF_ENTITY_CATEGORY, - CONF_ID, CONF_IP_ADDRESS, CONF_SCAN_RESULTS, CONF_SSID, @@ -16,14 +14,16 @@ wifi_info_ns = cg.esphome_ns.namespace("wifi_info") IPAddressWiFiInfo = wifi_info_ns.class_( - "IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component + "IPAddressWiFiInfo", text_sensor.TextSensor, cg.PollingComponent ) ScanResultsWiFiInfo = wifi_info_ns.class_( "ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent ) -SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component) +SSIDWiFiInfo = wifi_info_ns.class_( + "SSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent +) BSSIDWiFiInfo = wifi_info_ns.class_( - "BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component + "BSSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent ) MacAddressWifiInfo = wifi_info_ns.class_( "MacAddressWifiInfo", text_sensor.TextSensor, cg.Component @@ -31,45 +31,20 @@ CONFIG_SCHEMA = cv.Schema( { - cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo), - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC - ): cv.entity_category, - } - ), - cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo), - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC - ): cv.entity_category, - } + cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema( + klass=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 ).extend(cv.polling_component_schema("60s")), - cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(SSIDWiFiInfo), - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC - ): cv.entity_category, - } - ), - cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo), - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC - ): cv.entity_category, - } - ), - cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend( - { - cv.GenerateID(): cv.declare_id(MacAddressWifiInfo), - cv.Optional( - CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC - ): cv.entity_category, - } + cv.Optional(CONF_SSID): text_sensor.text_sensor_schema( + klass=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 + ).extend(cv.polling_component_schema("1s")), + cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( + klass=MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC ), } ) @@ -78,9 +53,8 @@ async def setup_conf(config, key): if key in config: conf = config[key] - var = cg.new_Pvariable(conf[CONF_ID]) + var = await text_sensor.new_text_sensor(conf) await cg.register_component(var, conf) - await text_sensor.register_text_sensor(var, conf) async def to_code(config): diff --git a/esphome/components/wifi_info/wifi_info_text_sensor.h b/esphome/components/wifi_info/wifi_info_text_sensor.h index 5b54451ed072..e5b0fa322326 100644 --- a/esphome/components/wifi_info/wifi_info_text_sensor.h +++ b/esphome/components/wifi_info/wifi_info_text_sensor.h @@ -7,9 +7,9 @@ namespace esphome { namespace wifi_info { -class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor { +class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: - void loop() override { + void update() override { auto ip = wifi::global_wifi_component->wifi_sta_ip(); if (ip != this->last_ip_) { this->last_ip_ = ip; @@ -53,9 +53,9 @@ class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSen std::string last_scan_results_; }; -class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { +class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: - void loop() override { + void update() override { std::string ssid = wifi::global_wifi_component->wifi_ssid(); if (this->last_ssid_ != ssid) { this->last_ssid_ = ssid; @@ -70,9 +70,9 @@ class SSIDWiFiInfo : public Component, public text_sensor::TextSensor { std::string last_ssid_; }; -class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor { +class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor { public: - void loop() override { + void update() override { wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid(); if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) { std::copy(bssid.begin(), bssid.end(), last_bssid_.begin()); diff --git a/esphome/components/wled/wled_light_effect.h b/esphome/components/wled/wled_light_effect.h index f0021ca978ba..8f239276d736 100644 --- a/esphome/components/wled/wled_light_effect.h +++ b/esphome/components/wled/wled_light_effect.h @@ -17,7 +17,6 @@ class WLEDLightEffect : public light::AddressableLightEffect { public: WLEDLightEffect(const std::string &name); - public: void start() override; void stop() override; void apply(light::AddressableLight &it, const Color ¤t_color) override; @@ -32,7 +31,6 @@ class WLEDLightEffect : public light::AddressableLightEffect { bool parse_drgbw_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); bool parse_dnrgb_frame_(light::AddressableLight &it, const uint8_t *payload, uint16_t size); - protected: uint16_t port_{0}; std::unique_ptr udp_; uint32_t blank_at_{0}; diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 583b68a77b51..bdd745b859d0 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -198,6 +198,9 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service result.name = "MJYD02YLA"; if (raw.size() == 19) result.raw_offset -= 6; + } else if ((raw[2] == 0xd3) && (raw[3] == 0x06)) { // rectangular body, e-ink display with alarm + result.type = XiaomiParseResult::TYPE_MHOC303; + result.name = "MHOC303"; } else if ((raw[2] == 0x87) && (raw[3] == 0x03)) { // square body, e-ink display result.type = XiaomiParseResult::TYPE_MHOC401; result.name = "MHOC401"; diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.h b/esphome/components/xiaomi_ble/xiaomi_ble.h index 54ab9a144fea..ee65d7c82f23 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.h +++ b/esphome/components/xiaomi_ble/xiaomi_ble.h @@ -23,6 +23,7 @@ struct XiaomiParseResult { TYPE_MUE4094RT, TYPE_WX08ZM, TYPE_MJYD02YLA, + TYPE_MHOC303, TYPE_MHOC401, TYPE_CGPR1 } type; diff --git a/esphome/components/xiaomi_mhoc303/__init__.py b/esphome/components/xiaomi_mhoc303/__init__.py new file mode 100644 index 000000000000..e7005b3957c7 --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/__init__.py @@ -0,0 +1 @@ +CODEOWNERS = ["@drug123"] diff --git a/esphome/components/xiaomi_mhoc303/sensor.py b/esphome/components/xiaomi_mhoc303/sensor.py new file mode 100644 index 000000000000..18f5ad776483 --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/sensor.py @@ -0,0 +1,73 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, esp32_ble_tracker +from esphome.const import ( + CONF_BATTERY_LEVEL, + CONF_HUMIDITY, + CONF_MAC_ADDRESS, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + ENTITY_CATEGORY_DIAGNOSTIC, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + UNIT_PERCENT, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_BATTERY, + CONF_ID, +) + +DEPENDENCIES = ["esp32_ble_tracker"] +AUTO_LOAD = ["xiaomi_ble"] + +xiaomi_mhoc303_ns = cg.esphome_ns.namespace("xiaomi_mhoc303") +XiaomiMHOC303 = xiaomi_mhoc303_ns.class_( + "XiaomiMHOC303", esp32_ble_tracker.ESPBTDeviceListener, cg.Component +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(XiaomiMHOC303), + cv.Required(CONF_MAC_ADDRESS): cv.mac_address, + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HUMIDITY): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_HUMIDITY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await esp32_ble_tracker.register_ble_device(var, config) + + cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex)) + + if CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature(sens)) + if CONF_HUMIDITY in config: + sens = await sensor.new_sensor(config[CONF_HUMIDITY]) + cg.add(var.set_humidity(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/xiaomi_mhoc303/xiaomi_mhoc303.cpp b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp new file mode 100644 index 000000000000..e613faec7e08 --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.cpp @@ -0,0 +1,59 @@ +#include "xiaomi_mhoc303.h" +#include "esphome/core/log.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_mhoc303 { + +static const char *const TAG = "xiaomi_mhoc303"; + +void XiaomiMHOC303::dump_config() { + ESP_LOGCONFIG(TAG, "Xiaomi MHOC303"); + LOG_SENSOR(" ", "Temperature", this->temperature_); + LOG_SENSOR(" ", "Humidity", this->humidity_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); +} + +bool XiaomiMHOC303::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { + if (device.address_uint64() != this->address_) { + ESP_LOGVV(TAG, "parse_device(): unknown MAC address."); + return false; + } + ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str()); + + bool success = false; + for (auto &service_data : device.get_service_datas()) { + auto res = xiaomi_ble::parse_xiaomi_header(service_data); + if (!res.has_value()) { + continue; + } + if (res->is_duplicate) { + continue; + } + if (res->has_encryption) { + ESP_LOGVV(TAG, "parse_device(): payload decryption is currently not supported on this device."); + continue; + } + if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) { + continue; + } + if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) { + continue; + } + if (res->temperature.has_value() && this->temperature_ != nullptr) + this->temperature_->publish_state(*res->temperature); + if (res->humidity.has_value() && this->humidity_ != nullptr) + this->humidity_->publish_state(*res->humidity); + if (res->battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*res->battery_level); + success = true; + } + + return success; +} + +} // namespace xiaomi_mhoc303 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h new file mode 100644 index 000000000000..d0304f78942f --- /dev/null +++ b/esphome/components/xiaomi_mhoc303/xiaomi_mhoc303.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h" +#include "esphome/components/xiaomi_ble/xiaomi_ble.h" + +#ifdef USE_ESP32 + +namespace esphome { +namespace xiaomi_mhoc303 { + +class XiaomiMHOC303 : 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_temperature(sensor::Sensor *temperature) { temperature_ = temperature; } + void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; } + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } + + protected: + uint64_t address_; + sensor::Sensor *temperature_{nullptr}; + sensor::Sensor *humidity_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; +}; + +} // namespace xiaomi_mhoc303 +} // namespace esphome + +#endif diff --git a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp index de77e6146b6f..6c3bd61cacb1 100644 --- a/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp +++ b/esphome/components/xiaomi_miscale/xiaomi_miscale.cpp @@ -87,12 +87,13 @@ bool XiaomiMiscale::parse_message_v1_(const std::vector &message, Parse // weight, 2 bytes, 16-bit unsigned integer, 1 kg const int16_t weight = uint16_t(data[1]) | (uint16_t(data[2]) << 8); - if (data[0] == 0x22 || data[0] == 0xa2) + if (data[0] == 0x22 || data[0] == 0xa2) { result.weight = weight * 0.01f / 2.0f; // unit 'kg' - else if (data[0] == 0x12 || data[0] == 0xb2) + } else if (data[0] == 0x12 || data[0] == 0xb2) { result.weight = weight * 0.01f * 0.6f; // unit 'jin' - else if (data[0] == 0x03 || data[0] == 0xb3) + } else if (data[0] == 0x03 || data[0] == 0xb3) { result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + } return true; } @@ -120,10 +121,11 @@ bool XiaomiMiscale::parse_message_v2_(const std::vector &message, Parse // weight, 2 bytes, 16-bit unsigned integer, 1 kg const int16_t weight = uint16_t(data[11]) | (uint16_t(data[12]) << 8); - if (data[0] == 0x02) + if (data[0] == 0x02) { result.weight = weight * 0.01f / 2.0f; // unit 'kg' - else if (data[0] == 0x03) + } else if (data[0] == 0x03) { result.weight = weight * 0.01f * 0.453592f; // unit 'lbs' + } if (has_impedance) { // impedance, 2 bytes, 16-bit diff --git a/esphome/components/yashima/yashima.cpp b/esphome/components/yashima/yashima.cpp index 8d588127b0e2..493c689b4213 100644 --- a/esphome/components/yashima/yashima.cpp +++ b/esphome/components/yashima/yashima.cpp @@ -171,7 +171,7 @@ void YashimaClimate::transmit_state_() { remote_state[1] |= YASHIMA_TEMP_MAP_BYTE1[safecelsius - YASHIMA_TEMP_MIN]; auto transmit = this->transmitter_->transmit(); - auto data = transmit.get_data(); + auto *data = transmit.get_data(); data->set_carrier_frequency(YASHIMA_CARRIER_FREQUENCY); @@ -179,12 +179,13 @@ void YashimaClimate::transmit_state_() { data->mark(YASHIMA_HEADER_MARK); data->space(YASHIMA_HEADER_SPACE); // Data (sent from the MSB to the LSB) - for (uint8_t i : remote_state) + for (uint8_t i : remote_state) { for (int8_t j = 7; j >= 0; j--) { data->mark(YASHIMA_BIT_MARK); bool bit = i & (1 << j); data->space(bit ? YASHIMA_ONE_SPACE : YASHIMA_ZERO_SPACE); } + } // Footer data->mark(YASHIMA_BIT_MARK); data->space(YASHIMA_GAP); diff --git a/esphome/config_helpers.py b/esphome/config_helpers.py index a88c5983b54f..39b57e441b21 100644 --- a/esphome/config_helpers.py +++ b/esphome/config_helpers.py @@ -23,3 +23,25 @@ def read_config_file(path): return data["content"] return read_file(path) + + +def merge_config(full_old, full_new): + def merge(old, new): + # pylint: disable=no-else-return + if isinstance(new, dict): + if not isinstance(old, dict): + return new + res = old.copy() + for k, v in new.items(): + res[k] = merge(old[k], v) if k in old else v + return res + elif isinstance(new, list): + if not isinstance(old, list): + return new + return old + new + elif new is None: + return old + + return new + + return merge(full_old, full_new) diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 8df74ba86169..2e7a4e567763 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -17,6 +17,7 @@ ALLOWED_NAME_CHARS, CONF_AVAILABILITY, CONF_COMMAND_TOPIC, + CONF_COMMAND_RETAIN, CONF_DISABLED_BY_DEFAULT, CONF_DISCOVERY, CONF_ENTITY_CATEGORY, @@ -62,7 +63,7 @@ jschema_registry, jschema_typed, ) - +from esphome.util import parse_esphome_version from esphome.voluptuous_schema import _Schema from esphome.yaml_util import make_data_base @@ -1591,6 +1592,7 @@ def entity_category(value): MQTT_COMMAND_COMPONENT_SCHEMA = MQTT_COMPONENT_SCHEMA.extend( { Optional(CONF_COMMAND_TOPIC): All(requires_component("mqtt"), subscribe_topic), + Optional(CONF_COMMAND_RETAIN): All(requires_component("mqtt"), boolean), } ) @@ -1740,6 +1742,19 @@ def validator(value): return validator +def require_esphome_version(year, month, patch): + def validator(value): + esphome_version = parse_esphome_version() + if esphome_version < (year, month, patch): + requires_version = f"{year}.{month}.{patch}" + raise Invalid( + f"This component requires at least ESPHome version {requires_version}" + ) + return value + + return validator + + @contextmanager def suppress_invalid(): try: diff --git a/esphome/const.py b/esphome/const.py index a94902d94602..d3b78ca44a54 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1,6 +1,6 @@ """Constants used by esphome.""" -__version__ = "2022.1.0b4" +__version__ = "2022.2.0b1" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" @@ -62,6 +62,7 @@ CONF_BINDKEY = "bindkey" CONF_BIRTH_MESSAGE = "birth_message" CONF_BIT_DEPTH = "bit_depth" +CONF_BLOCK = "block" CONF_BLUE = "blue" CONF_BOARD = "board" CONF_BOARD_FLASH_MODE = "board_flash_mode" @@ -106,6 +107,7 @@ CONF_COLOR_TEMPERATURE = "color_temperature" CONF_COLORS = "colors" CONF_COMMAND = "command" +CONF_COMMAND_RETAIN = "command_retain" CONF_COMMAND_TOPIC = "command_topic" CONF_COMMENT = "comment" CONF_COMMIT = "commit" @@ -166,6 +168,7 @@ CONF_DIRECTION_OUTPUT = "direction_output" CONF_DISABLED_BY_DEFAULT = "disabled_by_default" CONF_DISCOVERY = "discovery" +CONF_DISCOVERY_OBJECT_ID_GENERATOR = "discovery_object_id_generator" CONF_DISCOVERY_PREFIX = "discovery_prefix" CONF_DISCOVERY_RETAIN = "discovery_retain" CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator" @@ -187,6 +190,7 @@ CONF_EFFECT = "effect" CONF_EFFECTS = "effects" CONF_ELSE = "else" +CONF_ENABLE_IPV6 = "enable_ipv6" CONF_ENABLE_PIN = "enable_pin" CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" @@ -237,7 +241,9 @@ CONF_FORMALDEHYDE = "formaldehyde" CONF_FORMAT = "format" CONF_FORWARD_ACTIVE_ENERGY = "forward_active_energy" +CONF_FRAGMENTATION = "fragmentation" CONF_FRAMEWORK = "framework" +CONF_FREE = "free" CONF_FREQUENCY = "frequency" CONF_FROM = "from" CONF_FULL_SPECTRUM = "full_spectrum" @@ -329,10 +335,12 @@ CONF_LINE_TYPE = "line_type" CONF_LOADED_INTEGRATIONS = "loaded_integrations" CONF_LOCAL = "local" +CONF_LOCK_ACTION = "lock_action" CONF_LOG_TOPIC = "log_topic" CONF_LOGGER = "logger" CONF_LOGS = "logs" CONF_LONGITUDE = "longitude" +CONF_LOOP_TIME = "loop_time" CONF_LOW = "low" CONF_LOW_VOLTAGE_REFERENCE = "low_voltage_reference" CONF_MAC_ADDRESS = "mac_address" @@ -420,9 +428,11 @@ CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched" CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched" CONF_ON_JSON_MESSAGE = "on_json_message" +CONF_ON_LOCK = "on_lock" CONF_ON_LOOP = "on_loop" CONF_ON_MESSAGE = "on_message" CONF_ON_MULTI_CLICK = "on_multi_click" +CONF_ON_OPEN = "on_open" CONF_ON_PRESS = "on_press" CONF_ON_RAW_VALUE = "on_raw_value" CONF_ON_RELEASE = "on_release" @@ -433,8 +443,10 @@ CONF_ON_TAG_REMOVED = "on_tag_removed" CONF_ON_TIME = "on_time" CONF_ON_TIME_SYNC = "on_time_sync" +CONF_ON_TOUCH = "on_touch" CONF_ON_TURN_OFF = "on_turn_off" CONF_ON_TURN_ON = "on_turn_on" +CONF_ON_UNLOCK = "on_unlock" CONF_ON_VALUE = "on_value" CONF_ON_VALUE_RANGE = "on_value_range" CONF_ONE = "one" @@ -702,6 +714,7 @@ CONF_UID = "uid" CONF_UNIQUE = "unique" CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement" +CONF_UNLOCK_ACTION = "unlock_action" CONF_UPDATE_INTERVAL = "update_interval" CONF_UPDATE_ON_BOOT = "update_on_boot" CONF_URL = "url" @@ -804,6 +817,7 @@ UNIT_AMPERE = "A" UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" +UNIT_BYTES = "B" UNIT_CELSIUS = "°C" UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" @@ -832,6 +846,7 @@ UNIT_MICROSIEMENS_PER_CENTIMETER = "µS/cm" UNIT_MICROTESLA = "µT" UNIT_MILLIGRAMS_PER_CUBIC_METER = "mg/m³" +UNIT_MILLISECOND = "ms" UNIT_MINUTE = "min" UNIT_OHM = "Ω" UNIT_PARTS_PER_BILLION = "ppb" @@ -906,6 +921,9 @@ DEVICE_CLASS_UPDATE = "update" # device classes of button component DEVICE_CLASS_RESTART = "restart" +# device classes of switch component +DEVICE_CLASS_OUTLET = "outlet" +DEVICE_CLASS_SWITCH = "switch" # state classes diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index addecf132643..a4867915bbde 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -20,7 +20,7 @@ # pylint: disable=unused-import from esphome.coroutine import coroutine, coroutine_with_priority # noqa -from esphome.helpers import ensure_unique_string, is_hassio +from esphome.helpers import ensure_unique_string, is_ha_addon from esphome.util import OrderedDict if TYPE_CHECKING: @@ -568,12 +568,12 @@ def relative_src_path(self, *path): return self.relative_build_path("src", *path) def relative_pioenvs_path(self, *path): - if is_hassio(): + if is_ha_addon(): return os.path.join("/data", self.name, ".pioenvs", *path) return self.relative_build_path(".pioenvs", *path) def relative_piolibdeps_path(self, *path): - if is_hassio(): + if is_ha_addon(): return os.path.join("/data", self.name, ".piolibdeps", *path) return self.relative_build_path(".piolibdeps", *path) diff --git a/esphome/core/application.h b/esphome/core/application.h index 2a20793c194e..cec6e7baa93f 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -42,6 +42,9 @@ #ifdef USE_SELECT #include "esphome/components/select/select.h" #endif +#ifdef USE_LOCK +#include "esphome/components/lock/lock.h" +#endif namespace esphome { @@ -81,7 +84,7 @@ class Application { #endif #ifdef USE_FAN - void register_fan(fan::FanState *state) { this->fans_.push_back(state); } + void register_fan(fan::Fan *state) { this->fans_.push_back(state); } #endif #ifdef USE_COVER @@ -104,6 +107,10 @@ class Application { void register_select(select::Select *select) { this->selects_.push_back(select); } #endif +#ifdef USE_LOCK + void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); } +#endif + /// Register the component in this Application instance. template C *register_component(C *c) { static_assert(std::is_base_of::value, "Only Component subclasses can be registered"); @@ -204,8 +211,8 @@ class Application { } #endif #ifdef USE_FAN - const std::vector &get_fans() { return this->fans_; } - fan::FanState *get_fan_by_key(uint32_t key, bool include_internal = false) { + const std::vector &get_fans() { return this->fans_; } + fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) { for (auto *obj : this->fans_) if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) return obj; @@ -257,6 +264,15 @@ class Application { return nullptr; } #endif +#ifdef USE_LOCK + const std::vector &get_locks() { return this->locks_; } + lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) { + for (auto *obj : this->locks_) + if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) + return obj; + return nullptr; + } +#endif Scheduler scheduler; @@ -288,7 +304,7 @@ class Application { std::vector text_sensors_{}; #endif #ifdef USE_FAN - std::vector fans_{}; + std::vector fans_{}; #endif #ifdef USE_COVER std::vector covers_{}; @@ -305,6 +321,9 @@ class Application { #ifdef USE_SELECT std::vector selects_{}; #endif +#ifdef USE_LOCK + std::vector locks_{}; +#endif std::string name_; std::string compilation_time_; diff --git a/esphome/core/automation.h b/esphome/core/automation.h index f43fb98f20e2..92bc32247b2a 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -8,6 +8,11 @@ namespace esphome { +// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 +template struct seq {}; // NOLINT +template struct gens : gens {}; // NOLINT +template struct gens<0, S...> { using type = seq; }; // NOLINT + #define TEMPLATABLE_VALUE_(type, name) \ protected: \ TemplatableValue name##_{}; \ diff --git a/esphome/core/component.cpp b/esphome/core/component.cpp index 591c9943b542..5cb063cbec5f 100644 --- a/esphome/core/component.cpp +++ b/esphome/core/component.cpp @@ -177,7 +177,7 @@ bool Component::has_overridden_loop() const { return loop_overridden || call_loop_overridden; } -PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {} +PollingComponent::PollingComponent(uint32_t update_interval) : update_interval_(update_interval) {} void PollingComponent::call_setup() { // Let the polling component subclass setup their HW. diff --git a/esphome/core/controller.cpp b/esphome/core/controller.cpp index 6d3a76a292ec..dfcef5e4c1fe 100644 --- a/esphome/core/controller.cpp +++ b/esphome/core/controller.cpp @@ -65,6 +65,12 @@ void Controller::setup_controller(bool include_internal) { obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); }); } #endif +#ifdef USE_LOCK + for (auto *obj : App.get_locks()) { + if (include_internal || !obj->is_internal()) + obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); }); + } +#endif } } // namespace esphome diff --git a/esphome/core/controller.h b/esphome/core/controller.h index 0c3722855c7a..0be854828bbe 100644 --- a/esphome/core/controller.h +++ b/esphome/core/controller.h @@ -34,6 +34,9 @@ #ifdef USE_SELECT #include "esphome/components/select/select.h" #endif +#ifdef USE_LOCK +#include "esphome/components/lock/lock.h" +#endif namespace esphome { @@ -44,7 +47,7 @@ class Controller { virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; #endif #ifdef USE_FAN - virtual void on_fan_update(fan::FanState *obj){}; + virtual void on_fan_update(fan::Fan *obj){}; #endif #ifdef USE_LIGHT virtual void on_light_update(light::LightState *obj){}; @@ -70,6 +73,9 @@ class Controller { #ifdef USE_SELECT virtual void on_select_update(select::Select *obj, const std::string &state){}; #endif +#ifdef USE_LOCK + virtual void on_lock_update(lock::Lock *obj){}; +#endif }; } // namespace esphome diff --git a/esphome/core/defines.h b/esphome/core/defines.h index a74755f65159..574a8dcafef6 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -5,6 +5,8 @@ // // This file is only used by static analyzers and IDEs. +#include "esphome/core/macros.h" + // Informative flags #define ESPHOME_BOARD "dummy_board" #define ESPHOME_PROJECT_NAME "dummy project" @@ -24,12 +26,14 @@ #define USE_GRAPH #define USE_HOMEASSISTANT_TIME #define USE_LIGHT +#define USE_LOCK #define USE_LOGGER #define USE_MDNS #define USE_NUMBER #define USE_OTA_PASSWORD #define USE_OTA_STATE_CALLBACK #define USE_POWER_SUPPLY +#define USE_QR_CODE #define USE_SELECT #define USE_SENSOR #define USE_STATUS_LED @@ -47,8 +51,8 @@ #define USE_MQTT #define USE_PROMETHEUS #define USE_WEBSERVER +#define USE_WEBSERVER_PORT 80 // NOLINT #define USE_WIFI_WPA2_EAP -#define WEBSERVER_PORT 80 // NOLINT #endif // ESP32-specific feature flags @@ -60,13 +64,19 @@ #define USE_SOCKET_IMPL_BSD_SOCKETS #ifdef USE_ARDUINO +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(1, 0, 6) #define USE_ETHERNET #endif + +#ifdef USE_ESP_IDF +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(4, 3, 0) +#endif #endif // ESP8266-specific feature flags #ifdef USE_ESP8266 #define USE_ADC_SENSOR_VCC +#define USE_ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 2) #define USE_ESP8266_PREFERENCES_FLASH #define USE_HTTP_REQUEST_ESP8266_HTTPS #define USE_SOCKET_IMPL_LWIP_TCP diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 5f29abe57937..6d399c4064bd 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -1,10 +1,14 @@ #include "esphome/core/helpers.h" + #include "esphome/core/defines.h" +#include "esphome/core/hal.h" + #include #include #include #include #include +#include #if defined(USE_ESP8266) #include @@ -18,95 +22,31 @@ #include #include #endif + #ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC #include "esp_efuse.h" #include "esp_efuse_table.h" #endif -#include "esphome/core/log.h" -#include "esphome/core/hal.h" - namespace esphome { -static const char *const TAG = "helpers"; - -void get_mac_address_raw(uint8_t *mac) { -#if defined(USE_ESP32) -#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) - // On some devices, the MAC address that is burnt into EFuse does not - // match the CRC that goes along with it. For those devices, this - // work-around reads and uses the MAC address as-is from EFuse, - // without doing the CRC check. - esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); -#else - esp_efuse_mac_get_default(mac); -#endif -#elif defined(USE_ESP8266) - wifi_get_macaddr(STATION_IF, mac); +// STL backports + +#if _GLIBCXX_RELEASE < 7 +std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT +std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT +std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT +std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT +std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT +std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT +std::string to_string(float value) { return str_snprintf("%f", 32, value); } +std::string to_string(double value) { return str_snprintf("%f", 32, value); } +std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); } #endif -} - -std::string get_mac_address() { - uint8_t mac[6]; - get_mac_address_raw(mac); - return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -} - -std::string get_mac_address_pretty() { - uint8_t mac[6]; - get_mac_address_raw(mac); - return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -} - -#ifdef USE_ESP32 -void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } -#endif - -std::string generate_hostname(const std::string &base) { return base + std::string("-") + get_mac_address(); } - -float gamma_correct(float value, float gamma) { - if (value <= 0.0f) - return 0.0f; - if (gamma <= 0.0f) - return value; - - return powf(value, gamma); -} -float gamma_uncorrect(float value, float gamma) { - if (value <= 0.0f) - return 0.0f; - if (gamma <= 0.0f) - return value; - return powf(value, 1 / gamma); -} - -std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { - if (accuracy_decimals < 0) { - auto multiplier = powf(10.0f, accuracy_decimals); - value = roundf(value * multiplier) / multiplier; - accuracy_decimals = 0; - } - char tmp[32]; // should be enough, but we should maybe improve this at some point. - snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); - return std::string(tmp); -} - -ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { - if (on == nullptr && strcasecmp(str, "on") == 0) - return PARSE_ON; - if (on != nullptr && strcasecmp(str, on) == 0) - return PARSE_ON; - if (off == nullptr && strcasecmp(str, "off") == 0) - return PARSE_OFF; - if (off != nullptr && strcasecmp(str, off) == 0) - return PARSE_OFF; - if (strcasecmp(str, "toggle") == 0) - return PARSE_TOGGLE; - - return PARSE_NONE; -} +// Mathematics +float lerp(float completion, float start, float end) { return start + (end - start) * completion; } uint8_t crc8(uint8_t *data, uint8_t len) { uint8_t crc = 0; @@ -122,21 +62,6 @@ uint8_t crc8(uint8_t *data, uint8_t len) { } return crc; } - -void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability - auto start = micros(); - const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. - // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) - // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known - if (us > lag) { - delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep - while (micros() - start < us - lag) - delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks - } - while (micros() - start < us) // fine delay the remaining usecs - ; -} - uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { @@ -145,42 +70,79 @@ uint32_t fnv1_hash(const std::string &str) { } return hash; } -bool str_equals_case_insensitive(const std::string &a, const std::string &b) { - return strcasecmp(a.c_str(), b.c_str()) == 0; -} - -static int high_freq_num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -void HighFrequencyLoopRequester::start() { - if (this->started_) - return; - high_freq_num_requests++; - this->started_ = true; +uint32_t random_uint32() { +#ifdef USE_ESP32 + return esp_random(); +#elif defined(USE_ESP8266) + return os_random(); +#else +#error "No random source available for this configuration." +#endif } -void HighFrequencyLoopRequester::stop() { - if (!this->started_) - return; - high_freq_num_requests--; - this->started_ = false; +float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } +bool random_bytes(uint8_t *data, size_t len) { +#ifdef USE_ESP32 + esp_fill_random(data, len); + return true; +#elif defined(USE_ESP8266) + return os_get_random(data, len) == 0; +#else +#error "No random source available for this configuration." +#endif } -bool HighFrequencyLoopRequester::is_high_frequency() { return high_freq_num_requests > 0; } -float lerp(float completion, float start, float end) { return start + (end - start) * completion; } +// Strings -bool str_startswith(const std::string &full, const std::string &start) { return full.rfind(start, 0) == 0; } -bool str_endswith(const std::string &full, const std::string &ending) { - return full.rfind(ending) == (full.size() - ending.size()); +bool str_equals_case_insensitive(const std::string &a, const std::string &b) { + return strcasecmp(a.c_str(), b.c_str()) == 0; +} +bool str_startswith(const std::string &str, const std::string &start) { return str.rfind(start, 0) == 0; } +bool str_endswith(const std::string &str, const std::string &end) { + return str.rfind(end) == (str.size() - end.size()); +} +std::string str_truncate(const std::string &str, size_t length) { + return str.length() > length ? str.substr(0, length) : str; +} +std::string str_until(const char *str, char ch) { + char *pos = strchr(str, ch); + return pos == nullptr ? std::string(str) : std::string(str, pos - str); +} +std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); } +// wrapper around std::transform to run safely on functions from the ctype.h header +// see https://en.cppreference.com/w/cpp/string/byte/toupper#Notes +template std::string str_ctype_transform(const std::string &str) { + std::string result; + result.resize(str.length()); + std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); }); + return result; } -std::string str_snprintf(const char *fmt, size_t length, ...) { +std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } +std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } +std::string str_snake_case(const std::string &str) { + std::string result; + result.resize(str.length()); + std::transform(str.begin(), str.end(), result.begin(), ::tolower); + std::replace(result.begin(), result.end(), ' ', '_'); + return result; +} +std::string str_sanitize(const std::string &str) { + std::string out; + std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) { + return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + }); + return out; +} +std::string str_snprintf(const char *fmt, size_t len, ...) { std::string str; va_list args; - str.resize(length); - va_start(args, length); - size_t out_length = vsnprintf(&str[0], length + 1, fmt, args); + str.resize(len); + va_start(args, len); + size_t out_length = vsnprintf(&str[0], len + 1, fmt, args); va_end(args); - if (out_length < length) + if (out_length < len) str.resize(out_length); return str; @@ -201,28 +163,124 @@ std::string str_sprintf(const char *fmt, ...) { return str; } +// Parsing & formatting + +size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { + uint8_t val; + size_t chars = std::min(length, 2 * count); + for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) { + if (*str >= '0' && *str <= '9') { + val = *str - '0'; + } else if (*str >= 'A' && *str <= 'F') { + val = 10 + (*str - 'A'); + } else if (*str >= 'a' && *str <= 'f') { + val = 10 + (*str - 'a'); + } else { + return 0; + } + data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; + } + return chars; +} + +static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } +std::string format_hex(const uint8_t *data, size_t length) { + std::string ret; + ret.resize(length * 2); + for (size_t i = 0; i < length; i++) { + ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4); + ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); + } + return ret; +} +std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } + +static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +std::string format_hex_pretty(const uint8_t *data, size_t length) { + if (length == 0) + return ""; + std::string ret; + ret.resize(3 * length - 1); + for (size_t i = 0; i < length; i++) { + ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (i != length - 1) + ret[3 * 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; + if (on != nullptr && strcasecmp(str, on) == 0) + return PARSE_ON; + if (off == nullptr && strcasecmp(str, "off") == 0) + return PARSE_OFF; + if (off != nullptr && strcasecmp(str, off) == 0) + return PARSE_OFF; + if (strcasecmp(str, "toggle") == 0) + return PARSE_TOGGLE; + + return PARSE_NONE; +} + +std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) { + if (accuracy_decimals < 0) { + auto multiplier = powf(10.0f, accuracy_decimals); + value = roundf(value * multiplier) / multiplier; + accuracy_decimals = 0; + } + char tmp[32]; // should be enough, but we should maybe improve this at some point. + snprintf(tmp, sizeof(tmp), "%.*f", accuracy_decimals, value); + return std::string(tmp); +} + +// Colors + +float gamma_correct(float value, float gamma) { + if (value <= 0.0f) + return 0.0f; + if (gamma <= 0.0f) + return value; + + return powf(value, gamma); +} +float gamma_uncorrect(float value, float gamma) { + if (value <= 0.0f) + return 0.0f; + if (gamma <= 0.0f) + return value; + + return powf(value, 1 / gamma); +} + void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { float max_color_value = std::max(std::max(red, green), blue); float min_color_value = std::min(std::min(red, green), blue); float delta = max_color_value - min_color_value; - if (delta == 0) + if (delta == 0) { hue = 0; - else if (max_color_value == red) + } else if (max_color_value == red) { hue = int(fmod(((60 * ((green - blue) / delta)) + 360), 360)); - else if (max_color_value == green) + } else if (max_color_value == green) { hue = int(fmod(((60 * ((blue - red) / delta)) + 120), 360)); - else if (max_color_value == blue) + } else if (max_color_value == blue) { hue = int(fmod(((60 * ((red - green) / delta)) + 240), 360)); + } - if (max_color_value == 0) + if (max_color_value == 0) { saturation = 0; - else + } else { saturation = delta / max_color_value; + } value = max_color_value; } - void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue) { float chroma = value * saturation; float hue_prime = fmod(hue / 60.0, 6); @@ -264,122 +322,72 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green blue += delta; } -#ifdef USE_ESP8266 +// System APIs + +#if defined(USE_ESP8266) IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); } IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); } -#endif -#ifdef USE_ESP32 +#elif defined(USE_ESP32) IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); } IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } #endif -// --------------------------------------------------------------------------------------------------------------------- - -// Mathematics +uint8_t HighFrequencyLoopRequester::num_requests = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +void HighFrequencyLoopRequester::start() { + if (this->started_) + return; + num_requests++; + this->started_ = true; +} +void HighFrequencyLoopRequester::stop() { + if (!this->started_) + return; + num_requests--; + this->started_ = false; +} +bool HighFrequencyLoopRequester::is_high_frequency() { return num_requests > 0; } -uint32_t random_uint32() { -#ifdef USE_ESP32 - return esp_random(); -#elif defined(USE_ESP8266) - return os_random(); +void get_mac_address_raw(uint8_t *mac) { +#if defined(USE_ESP32) +#if defined(USE_ESP32_IGNORE_EFUSE_MAC_CRC) + // On some devices, the MAC address that is burnt into EFuse does not + // match the CRC that goes along with it. For those devices, this + // work-around reads and uses the MAC address as-is from EFuse, + // without doing the CRC check. + esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, mac, 48); #else -#error "No random source available for this configuration." + esp_efuse_mac_get_default(mac); #endif -} -float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } -bool random_bytes(uint8_t *data, size_t len) { -#ifdef USE_ESP32 - esp_fill_random(data, len); - return true; #elif defined(USE_ESP8266) - return os_get_random(data, len) == 0; -#else -#error "No random source available for this configuration." + wifi_get_macaddr(STATION_IF, mac); #endif } - -// Strings - -std::string str_truncate(const std::string &str, size_t length) { - return str.length() > length ? str.substr(0, length) : str; -} -std::string str_until(const char *str, char ch) { - char *pos = strchr(str, ch); - return pos == nullptr ? std::string(str) : std::string(str, pos - str); -} -std::string str_until(const std::string &str, char ch) { return str.substr(0, str.find(ch)); } -// wrapper around std::transform to run safely on functions from the ctype.h header -// see https://en.cppreference.com/w/cpp/string/byte/toupper#Notes -template std::string str_ctype_transform(const std::string &str) { - std::string result; - result.resize(str.length()); - std::transform(str.begin(), str.end(), result.begin(), [](unsigned char ch) { return fn(ch); }); - return result; -} -std::string str_lower_case(const std::string &str) { return str_ctype_transform(str); } -std::string str_upper_case(const std::string &str) { return str_ctype_transform(str); } -std::string str_snake_case(const std::string &str) { - std::string result; - result.resize(str.length()); - std::transform(str.begin(), str.end(), result.begin(), ::tolower); - std::replace(result.begin(), result.end(), ' ', '_'); - return result; -} -std::string str_sanitize(const std::string &str) { - std::string out; - std::copy_if(str.begin(), str.end(), std::back_inserter(out), [](const char &c) { - return c == '-' || c == '_' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); - }); - return out; -} - -// Parsing & formatting - -size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { - uint8_t val; - size_t chars = std::min(length, 2 * count); - for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) { - if (*str >= '0' && *str <= '9') - val = *str - '0'; - else if (*str >= 'A' && *str <= 'F') - val = 10 + (*str - 'A'); - else if (*str >= 'a' && *str <= 'f') - val = 10 + (*str - 'a'); - else - return 0; - data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; - } - return chars; +std::string get_mac_address() { + uint8_t mac[6]; + get_mac_address_raw(mac); + return str_snprintf("%02x%02x%02x%02x%02x%02x", 12, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } - -static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } -std::string format_hex(const uint8_t *data, size_t length) { - std::string ret; - ret.resize(length * 2); - for (size_t i = 0; i < length; i++) { - ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4); - ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); - } - return ret; +std::string get_mac_address_pretty() { + uint8_t mac[6]; + get_mac_address_raw(mac); + return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } -std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } +#ifdef USE_ESP32 +void set_mac_address(uint8_t *mac) { esp_base_mac_addr_set(mac); } +#endif -static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } -std::string format_hex_pretty(const uint8_t *data, size_t length) { - if (length == 0) - return ""; - std::string ret; - ret.resize(3 * length - 1); - for (size_t i = 0; i < length; i++) { - ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); - ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); - if (i != length - 1) - ret[3 * i + 2] = '.'; +void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability + uint32_t start = micros(); + const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop. + // it must be larger than the worst-case duration of a delay(1) call (hardware tasks) + // 5ms is conservative, it could be reduced when exact BT/WiFi stack delays are known + if (us > lag) { + delay((us - lag) / 1000UL); // note: in disabled-interrupt contexts delay() won't actually sleep + while (micros() - start < us - lag) + delay(1); // in those cases, this loop allows to yield for BT/WiFi stack tasks } - if (length > 4) - return ret + " (" + to_string(length) + ")"; - return ret; + while (micros() - start < us) // fine delay the remaining usecs + ; } -std::string format_hex_pretty(const std::vector &data) { return format_hex_pretty(data.data(), data.size()); } } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index c9a27a2fab04..e0763d2c716d 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -2,19 +2,18 @@ #include #include - -#include #include -#include #include +#include #include +#include + +#include "esphome/core/optional.h" #ifdef USE_ESP32 #include #endif -#include "esphome/core/optional.h" - #define HOT __attribute__((hot)) #define ESPDEPRECATED(msg, when) __attribute__((deprecated(msg))) #define ALWAYS_INLINE __attribute__((always_inline)) @@ -30,209 +29,26 @@ namespace esphome { -/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). -void get_mac_address_raw(uint8_t *mac); - -/// Get the device MAC address as a string, in lowercase hex notation. -std::string get_mac_address(); - -/// Get the device MAC address as a string, in colon-separated uppercase hex notation. -std::string get_mac_address_pretty(); - -#ifdef USE_ESP32 -/// Set the MAC address to use from the provided byte array (6 bytes). -void set_mac_address(uint8_t *mac); -#endif - -/// Compare string a to string b (ignoring case) and return whether they are equal. -bool str_equals_case_insensitive(const std::string &a, const std::string &b); -bool str_startswith(const std::string &full, const std::string &start); -bool str_endswith(const std::string &full, const std::string &ending); - -/// snprintf-like function returning std::string with a given maximum length. -std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t length, ...); - -/// sprintf-like function returning std::string. -std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); - -class HighFrequencyLoopRequester { - public: - void start(); - void stop(); - - static bool is_high_frequency(); - - protected: - bool started_{false}; -}; - -/** Linearly interpolate between end start and end by completion. - * - * @tparam T The input/output typename. - * @param start The start value. - * @param end The end value. - * @param completion The completion. 0 is start value, 1 is end value. - * @return The linearly interpolated value. - */ -float lerp(float completion, float start, float end); - -// Not all platforms we support target C++14 yet, so we can't unconditionally use std::make_unique. Provide our own -// implementation if needed, and otherwise pull std::make_unique into scope so that we have a uniform API. -#if __cplusplus >= 201402L -using std::make_unique; -#else -template std::unique_ptr make_unique(Args &&...args) { - return std::unique_ptr(new T(std::forward(args)...)); -} -#endif - -/// Applies gamma correction with the provided gamma to value. -float gamma_correct(float value, float gamma); -/// Reverts gamma correction with the provided gamma to value. -float gamma_uncorrect(float value, float gamma); - -/// Create a string from a value and an accuracy in decimals. -std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); - -/// Convert RGB floats (0-1) to hue (0-360) & saturation/value percentage (0-1) -void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); -/// Convert hue (0-360) & saturation/value percentage (0-1) to RGB floats (0-1) -void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); - -/// Convert degrees Celsius to degrees Fahrenheit. -static inline float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; } -/// Convert degrees Fahrenheit to degrees Celsius. -static inline float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; } - -/*** - * An interrupt helper class. - * - * This behaves like std::lock_guard. As long as the value is visible in the current stack, all interrupts - * (including flash reads) will be disabled. - * - * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into - * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being - * pulled from flash). - * - * Example: - * - * ```cpp - * // interrupts are enabled - * { - * InterruptLock lock; - * // do something - * // interrupts are disabled - * } - * // interrupts are enabled - * ``` - */ -class InterruptLock { - public: - InterruptLock(); - ~InterruptLock(); - - protected: -#ifdef USE_ESP8266 - uint32_t xt_state_; -#endif -}; - -/// Calculate a crc8 of data with the provided data length. -uint8_t crc8(uint8_t *data, uint8_t len); - -enum ParseOnOffState { - PARSE_NONE = 0, - PARSE_ON, - PARSE_OFF, - PARSE_TOGGLE, -}; - -ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); - -// https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 -template struct seq {}; // NOLINT -template struct gens : gens {}; // NOLINT -template struct gens<0, S...> { using type = seq; }; // NOLINT - -template using enable_if_t = typename std::enable_if::type; - -template::value, int> = 0> T id(T value) { return value; } -template::value, int> = 0> T &id(T *value) { return *value; } - -template class CallbackManager; - -/** Simple helper class to allow having multiple subscribers to a signal. - * - * @tparam Ts The arguments for the callback, wrapped in void(). - */ -template class CallbackManager { - public: - /// Add a callback to the internal callback list. - void add(std::function &&callback) { this->callbacks_.push_back(std::move(callback)); } - - /// Call all callbacks in this manager. - void call(Ts... args) { - for (auto &cb : this->callbacks_) - cb(args...); - } - - protected: - std::vector> callbacks_; -}; - -void delay_microseconds_safe(uint32_t us); - -template class Deduplicator { - public: - bool next(T value) { - if (this->has_value_) { - if (this->last_value_ == value) - return false; - } - this->has_value_ = true; - this->last_value_ = value; - return true; - } - bool has_value() const { return this->has_value_; } - - protected: - bool has_value_{false}; - T last_value_{}; -}; - -template class Parented { - public: - Parented() {} - Parented(T *parent) : parent_(parent) {} - - T *get_parent() const { return parent_; } - void set_parent(T *parent) { parent_ = parent; } - - protected: - T *parent_{nullptr}; -}; - -uint32_t fnv1_hash(const std::string &str); - -// --------------------------------------------------------------------------------------------------------------------- - /// @name STL backports ///@{ +// Backports for various STL features we like to use. Pull in the STL implementation wherever available, to avoid +// ambiguity and to provide a uniform API. + // std::to_string() from C++11, available from libstdc++/g++ 8 // See https://github.com/espressif/esp-idf/issues/1445 #if _GLIBCXX_RELEASE >= 8 using std::to_string; #else -inline std::string to_string(int value) { return str_snprintf("%d", 32, value); } // NOLINT -inline std::string to_string(long value) { return str_snprintf("%ld", 32, value); } // NOLINT -inline std::string to_string(long long value) { return str_snprintf("%lld", 32, value); } // NOLINT -inline std::string to_string(unsigned value) { return str_snprintf("%u", 32, value); } // NOLINT -inline std::string to_string(unsigned long value) { return str_snprintf("%lu", 32, value); } // NOLINT -inline std::string to_string(unsigned long long value) { return str_snprintf("%llu", 32, value); } // NOLINT -inline std::string to_string(float value) { return str_snprintf("%f", 32, value); } -inline std::string to_string(double value) { return str_snprintf("%f", 32, value); } -inline std::string to_string(long double value) { return str_snprintf("%Lf", 32, value); } +std::string to_string(int value); // NOLINT +std::string to_string(long value); // NOLINT +std::string to_string(long long value); // NOLINT +std::string to_string(unsigned value); // NOLINT +std::string to_string(unsigned long value); // NOLINT +std::string to_string(unsigned long long value); // NOLINT +std::string to_string(float value); +std::string to_string(double value); +std::string to_string(long double value); #endif // std::is_trivially_copyable from C++11, implemented in libstdc++/g++ 5.1 (but minor releases can't be detected) @@ -245,6 +61,22 @@ using std::is_trivially_copyable; template struct is_trivially_copyable : public std::integral_constant {}; #endif +// std::make_unique() from C++14 +#if __cpp_lib_make_unique >= 201304 +using std::make_unique; +#else +template std::unique_ptr make_unique(Args &&...args) { + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +// std::enable_if_t from C++14 +#if __cplusplus >= 201402L +using std::enable_if_t; +#else +template using enable_if_t = typename std::enable_if::type; +#endif + // std::clamp from C++17 #if __cpp_lib_clamp >= 201603 using std::clamp; @@ -306,6 +138,20 @@ template<> constexpr14 int64_t byteswap(int64_t n) { return __builtin_bswap64(n) /// @name Mathematics ///@{ +/// Linearly interpolate between \p start and \p end by \p completion (between 0 and 1). +float lerp(float completion, float start, float end); + +/// Remap \p value from the range (\p min, \p max) to (\p min_out, \p max_out). +template T remap(U value, U min, U max, T min_out, T max_out) { + return (value - min) * (max_out - min_out) / (max - min) + min_out; +} + +/// Calculate a CRC-8 checksum of \p data with size \p len. +uint8_t crc8(uint8_t *data, uint8_t len); + +/// Calculate a FNV-1 hash of \p str. +uint32_t fnv1_hash(const std::string &str); + /// Return a random 32-bit unsigned integer. uint32_t random_uint32(); /// Return a random float between 0 and 1. @@ -394,6 +240,14 @@ template constexpr14 T convert_little_endian(T val) { /// @name Strings ///@{ +/// Compare strings for equality in case-insensitive manner. +bool str_equals_case_insensitive(const std::string &a, const std::string &b); + +/// Check whether a string starts with a value. +bool str_startswith(const std::string &str, const std::string &start); +/// Check whether a string ends with a value. +bool str_endswith(const std::string &str, const std::string &end); + /// Convert the value to a string (added as extra overload so that to_string() can be used on all stringifiable types). inline std::string to_string(const std::string &val) { return val; } @@ -416,6 +270,12 @@ std::string str_snake_case(const std::string &str); /// Sanitizes the input string by removing all characters but alphanumerics, dashes and underscores. std::string str_sanitize(const std::string &str); +/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator). +std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...); + +/// sprintf-like function returning std::string. +std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); + ///@} /// @name Parsing & formatting @@ -534,15 +394,181 @@ template::value, int> = 0> std::stri return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); } +/// Return values for parse_on_off(). +enum ParseOnOffState { + PARSE_NONE = 0, + PARSE_ON, + PARSE_OFF, + PARSE_TOGGLE, +}; +/// Parse a string that contains either on, off or toggle. +ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); + +/// Create a string from a value and an accuracy in decimals. +std::string value_accuracy_to_string(float value, int8_t accuracy_decimals); + ///@} -/// @name Number manipulation +/// @name Colors ///@{ -/// Remap a number from one range to another. -template constexpr T remap(U value, U min, U max, T min_out, T max_out) { - return (value - min) * (max_out - min_out) / (max - min) + min_out; -} +/// Applies gamma correction of \p gamma to \p value. +float gamma_correct(float value, float gamma); +/// Reverts gamma correction of \p gamma to \p value. +float gamma_uncorrect(float value, float gamma); + +/// Convert \p red, \p green and \p blue (all 0-1) values to \p hue (0-360), \p saturation (0-1) and \p value (0-1). +void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value); +/// Convert \p hue (0-360), \p saturation (0-1) and \p value (0-1) to \p red, \p green and \p blue (all 0-1). +void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green, float &blue); + +///@} + +/// @name Units +///@{ + +/// Convert degrees Celsius to degrees Fahrenheit. +constexpr float celsius_to_fahrenheit(float value) { return value * 1.8f + 32.0f; } +/// Convert degrees Fahrenheit to degrees Celsius. +constexpr float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.8f; } + +///@} + +/// @name Utilities +/// @{ + +template class CallbackManager; + +/** Helper class to allow having multiple subscribers to a callback. + * + * @tparam Ts The arguments for the callbacks, wrapped in void(). + */ +template class CallbackManager { + public: + /// Add a callback to the list. + void add(std::function &&callback) { this->callbacks_.push_back(std::move(callback)); } + + /// Call all callbacks in this manager. + void call(Ts... args) { + for (auto &cb : this->callbacks_) + cb(args...); + } + + /// Call all callbacks in this manager. + void operator()(Ts... args) { call(args...); } + + protected: + std::vector> callbacks_; +}; + +/// Helper class to deduplicate items in a series of values. +template class Deduplicator { + public: + /// Feeds the next item in the series to the deduplicator and returns whether this is a duplicate. + bool next(T value) { + if (this->has_value_) { + if (this->last_value_ == value) + return false; + } + this->has_value_ = true; + this->last_value_ = value; + return true; + } + /// Returns whether this deduplicator has processed any items so far. + bool has_value() const { return this->has_value_; } + + protected: + bool has_value_{false}; + T last_value_{}; +}; + +/// Helper class to easily give an object a parent of type \p T. +template class Parented { + public: + Parented() {} + Parented(T *parent) : parent_(parent) {} + + /// Get the parent of this object. + T *get_parent() const { return parent_; } + /// Set the parent of this object. + void set_parent(T *parent) { parent_ = parent; } + + protected: + T *parent_{nullptr}; +}; + +/// @} + +/// @name System APIs +///@{ + +/** Helper class to disable interrupts. + * + * This behaves like std::lock_guard: as long as the object is alive, all interrupts are disabled. + * + * Please note all functions called when the interrupt lock must be marked IRAM_ATTR (loading code into + * instruction cache is done via interrupts; disabling interrupts prevents data not already in cache from being + * pulled from flash). + * + * Example usage: + * + * \code{.cpp} + * // interrupts are enabled + * { + * InterruptLock lock; + * // do something + * // interrupts are disabled + * } + * // interrupts are enabled + * \endcode + */ +class InterruptLock { + public: + InterruptLock(); + ~InterruptLock(); + + protected: +#ifdef USE_ESP8266 + uint32_t xt_state_; +#endif +}; + +/** Helper class to request `loop()` to be called as fast as possible. + * + * Usually the ESPHome main loop runs at 60 Hz, sleeping in between invocations of `loop()` if necessary. When a higher + * execution frequency is necessary, you can use this class to make the loop run continuously without waiting. + */ +class HighFrequencyLoopRequester { + public: + /// Start running the loop continuously. + void start(); + /// Stop running the loop continuously. + void stop(); + + /// Check whether the loop is running continuously. + static bool is_high_frequency(); + + protected: + bool started_{false}; + static uint8_t num_requests; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +}; + +/// Get the device MAC address as raw bytes, written into the provided byte array (6 bytes). +void get_mac_address_raw(uint8_t *mac); + +/// Get the device MAC address as a string, in lowercase hex notation. +std::string get_mac_address(); + +/// Get the device MAC address as a string, in colon-separated uppercase hex notation. +std::string get_mac_address_pretty(); + +#ifdef USE_ESP32 +/// Set the MAC address to use from the provided byte array (6 bytes). +void set_mac_address(uint8_t *mac); +#endif + +/// Delay for the given amount of microseconds, possibly yielding to other processes during the wait. +void delay_microseconds_safe(uint32_t us); ///@} @@ -591,6 +617,22 @@ template class ExternalRAMAllocator { /// @} +/// @name Internal functions +///@{ + +/** Helper function to make `id(var)` known from lambdas work in custom components. + * + * This function is not called from lambdas, the code generator replaces calls to it with the appropriate variable. + */ +template::value, int> = 0> T id(T value) { return value; } +/** Helper function to make `id(var)` known from lambdas work in custom components. + * + * This function is not called from lambdas, the code generator replaces calls to it with the appropriate variable. + */ +template::value, int> = 0> T &id(T *value) { return *value; } + +///@} + /// @name Deprecated functions ///@{ diff --git a/esphome/core/log.h b/esphome/core/log.h index 1e93ed42193d..b1b1cf91152f 100644 --- a/esphome/core/log.h +++ b/esphome/core/log.h @@ -6,10 +6,9 @@ #ifdef USE_STORE_LOG_STR_IN_FLASH #include "WString.h" +#include "esphome/core/defines.h" // for USE_ARDUINO_VERSION_CODE #endif -#include "esphome/core/macros.h" - // Include ESP-IDF/Arduino based logging methods here so they don't undefine ours later #if defined(USE_ESP32_FRAMEWORK_ARDUINO) || defined(USE_ESP_IDF) #include @@ -19,8 +18,6 @@ #include #endif -#include "esphome/core/macros.h" - namespace esphome { #define ESPHOME_LOG_LEVEL_NONE 0 @@ -176,7 +173,7 @@ struct LogString; #include -#if ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 0) +#if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 0) #define LOG_STR_ARG(s) ((PGM_P)(s)) #else // Pre-Arduino 2.5, we can't pass a PSTR() to printf(). Emulate support by copying the message to a diff --git a/esphome/core/macros.h b/esphome/core/macros.h index b0027a276c0e..70ceaf58f493 100644 --- a/esphome/core/macros.h +++ b/esphome/core/macros.h @@ -1,56 +1,4 @@ #pragma once +// Helper macro to define a version code, whos evalue can be compared against other version codes. #define VERSION_CODE(major, minor, patch) ((major) << 16 | (minor) << 8 | (patch)) - -#if defined(USE_ESP8266) - -#include -#if defined(ARDUINO_ESP8266_MAJOR) && defined(ARDUINO_ESP8266_MINOR) && defined(ARDUINO_ESP8266_REVISION) // v3.0.1+ -#define ARDUINO_VERSION_CODE VERSION_CODE(ARDUINO_ESP8266_MAJOR, ARDUINO_ESP8266_MINOR, ARDUINO_ESP8266_REVISION) -#elif ARDUINO_ESP8266_GIT_VER == 0xefb0341a // version defines were screwed up in v3.0.0 -#define ARDUINO_VERSION_CODE VERSION_CODE(3, 0, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_4) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 4) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_3) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 3) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_7_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 7, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_6_3) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 3) -#elif defined(ARDUINO_ESP8266_RELEASE_2_6_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_6_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 6, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_5_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_5_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_5_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 5, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_4_2) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 2) -#elif defined(ARDUINO_ESP8266_RELEASE_2_4_1) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 1) -#elif defined(ARDUINO_ESP8266_RELEASE_2_4_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 4, 0) -#elif defined(ARDUINO_ESP8266_RELEASE_2_3_0) -#define ARDUINO_VERSION_CODE VERSION_CODE(2, 3, 0) -#else -#warning "Could not determine Arduino framework version, update esphome/core/macros.h!" -#endif - -#elif defined(USE_ESP32_FRAMEWORK_ARDUINO) - -#if defined(IDF_VER) // identifies v2, needed since v1 doesn't have the esp_arduino_version.h header -#include -#define ARDUINO_VERSION_CODE \ - VERSION_CODE(ESP_ARDUINO_VERSION_MAJOR, ESP_ARDUINO_VERSION_MINOR, ESP_ARDUINO_VERSION_PATH) -#else -#define ARDUINO_VERSION_CODE VERSION_CODE(1, 0, 0) // there are no defines identifying minor/patch version -#endif - -#endif diff --git a/esphome/core/scheduler.cpp b/esphome/core/scheduler.cpp index 3fe07f94b530..7f0ed0b17c23 100644 --- a/esphome/core/scheduler.cpp +++ b/esphome/core/scheduler.cpp @@ -167,9 +167,10 @@ void IRAM_ATTR HOT Scheduler::call() { { // Don't copy-by value yet auto &item = this->items_[0]; - if ((now - item->last_execution) < item->interval) + if ((now - item->last_execution) < item->interval) { // Not reached timeout yet, done for this call break; + } uint8_t major = item->next_execution_major(); if (this->millis_major_ - major > 1) break; @@ -190,10 +191,11 @@ void IRAM_ATTR HOT Scheduler::call() { // - timeouts/intervals get cancelled { WarnIfComponentBlockingGuard guard{item->component}; - if (item->type == SchedulerItem::RETRY) + if (item->type == SchedulerItem::RETRY) { retry_result = item->retry_callback(); - else + } else { item->void_callback(); + } } } @@ -257,17 +259,19 @@ void HOT Scheduler::pop_raw_() { void HOT Scheduler::push_(std::unique_ptr item) { this->to_add_.push_back(std::move(item)); } bool HOT Scheduler::cancel_item_(Component *component, const std::string &name, Scheduler::SchedulerItem::Type type) { bool ret = false; - for (auto &it : this->items_) + for (auto &it : this->items_) { if (it->component == component && it->name == name && it->type == type && !it->remove) { to_remove_++; it->remove = true; ret = true; } - for (auto &it : this->to_add_) + } + for (auto &it : this->to_add_) { if (it->component == component && it->name == name && it->type == type) { it->remove = true; ret = true; } + } return ret; } diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index 937b6cceb490..82deec70ec8a 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -773,9 +773,11 @@ def operator(self, name: str) -> "MockObj": return MockObj(f"{self.base} &", "") if name == "ptr": return MockObj(f"{self.base} *", "") + if name == "const_ptr": + return MockObj(f"{self.base} *const", "") if name == "const": return MockObj(f"const {self.base}", "") - raise ValueError("Expected one of ref, ptr, const.") + raise ValueError("Expected one of ref, ptr, const_ptr, const.") @property def using(self) -> "MockObj": diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py index 806a2d832ca8..2323b2578fe7 100644 --- a/esphome/cpp_types.py +++ b/esphome/cpp_types.py @@ -15,6 +15,7 @@ uint32 = global_ns.namespace("uint32_t") uint64 = global_ns.namespace("uint64_t") int32 = global_ns.namespace("int32_t") +int64 = global_ns.namespace("int64_t") const_char_ptr = global_ns.namespace("const char *") NAN = global_ns.namespace("NAN") esphome_ns = global_ns # using namespace esphome; @@ -34,3 +35,4 @@ gpio_ns = esphome_ns.namespace("gpio") gpio_Flags = gpio_ns.enum("Flags", is_class=True) EntityCategory = esphome_ns.enum("EntityCategory") +Parented = esphome_ns.class_("Parented") diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index c68d037fe6a4..0ace97f10e06 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -55,13 +55,13 @@ def __init__(self): self.password_hash = "" self.username = "" self.using_password = False - self.on_hassio = False + self.on_ha_addon = False self.cookie_secret = None def parse_args(self, args): - self.on_hassio = args.hassio + self.on_ha_addon = args.ha_addon password = args.password or os.getenv("PASSWORD", "") - if not self.on_hassio: + if not self.on_ha_addon: self.username = args.username or os.getenv("USERNAME", "") self.using_password = bool(password) if self.using_password: @@ -77,14 +77,14 @@ def status_use_ping(self): return get_bool_env("ESPHOME_DASHBOARD_USE_PING") @property - def using_hassio_auth(self): - if not self.on_hassio: + def using_ha_addon_auth(self): + if not self.on_ha_addon: return False return not get_bool_env("DISABLE_HA_AUTHENTICATION") @property def using_auth(self): - return self.using_password or self.using_hassio_auth + return self.using_password or self.using_ha_addon_auth def check_password(self, username, password): if not self.using_auth: @@ -138,10 +138,10 @@ def decorator(self, *args, **kwargs): def is_authenticated(request_handler): - if settings.on_hassio: + if settings.on_ha_addon: # Handle ingress - disable auth on ingress port - # X-Hassio-Ingress is automatically stripped on the non-ingress server in nginx - header = request_handler.request.headers.get("X-Hassio-Ingress", "NO") + # X-HA-Ingress is automatically stripped on the non-ingress server in nginx + header = request_handler.request.headers.get("X-HA-Ingress", "NO") if str(header) == "YES": return True if settings.using_auth: @@ -792,23 +792,23 @@ def render_login_page(self, error=None): self.render( "login.template.html", error=error, - hassio=settings.using_hassio_auth, + ha_addon=settings.using_ha_addon_auth, has_username=bool(settings.username), **template_args(), ) - def post_hassio_login(self): + def post_ha_addon_login(self): import requests headers = { - "X-HASSIO-KEY": os.getenv("HASSIO_TOKEN"), + "Authentication": f"Bearer {os.getenv('SUPERVISOR_TOKEN')}", } data = { "username": self.get_argument("username", ""), "password": self.get_argument("password", ""), } try: - req = requests.post("http://hassio/auth", headers=headers, data=data) + req = requests.post("http://supervisor/auth", headers=headers, data=data) if req.status_code == 200: self.set_secure_cookie("authenticated", cookie_authenticated_yes) self.redirect("/") @@ -835,8 +835,8 @@ def post_native_login(self): self.render_login_page(error=error_str) def post(self): - if settings.using_hassio_auth: - self.post_hassio_login() + if settings.using_ha_addon_auth: + self.post_ha_addon_login() else: self.post_native_login() diff --git a/esphome/helpers.py b/esphome/helpers.py index 1193d61eaadd..76158a1bfd91 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -1,4 +1,5 @@ import codecs +from contextlib import suppress import logging import os @@ -143,8 +144,8 @@ def get_bool_env(var, default=False): return bool(os.getenv(var, default)) -def is_hassio(): - return get_bool_env("ESPHOME_IS_HASSIO") +def is_ha_addon(): + return get_bool_env("ESPHOME_IS_HA_ADDON") def walk_files(path): @@ -233,8 +234,20 @@ def copy_file_if_changed(src: os.PathLike, dst: os.PathLike) -> None: return mkdir_p(os.path.dirname(dst)) try: - shutil.copy(src, dst) + shutil.copyfile(src, dst) except OSError as err: + if isinstance(err, PermissionError): + # Older esphome versions copied over the src file permissions too. + # So when the dst file had 444 permissions, the dst file would have those + # too and subsequent writes would fail + + # -> delete file (it would be overwritten anyway), and try again + # if that fails, use normal error handler + with suppress(OSError): + os.unlink(dst) + shutil.copyfile(src, dst) + return + from esphome.core import EsphomeError raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err diff --git a/esphome/mqtt.py b/esphome/mqtt.py index 07602e8ced82..0ddd976072e0 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -47,7 +47,7 @@ def on_disconnect(client, userdata, result_code): except OSError: pass - wait_time = min(2 ** tries, 300) + wait_time = min(2**tries, 300) _LOGGER.warning( "Disconnected from MQTT (%s). Trying to reconnect in %s s", result_code, diff --git a/esphome/util.py b/esphome/util.py index b2ba0c22c3c0..9975f5fc7295 100644 --- a/esphome/util.py +++ b/esphome/util.py @@ -1,3 +1,4 @@ +import typing from typing import Union, List import collections @@ -242,6 +243,13 @@ def is_dev_esphome_version(): return "dev" in const.__version__ +def parse_esphome_version() -> typing.Tuple[int, int, int]: + match = re.match(r"^(\d+).(\d+).(\d+)(-dev\d*|b\d*)?$", const.__version__) + if match is None: + raise ValueError(f"Failed to parse ESPHome version '{const.__version__}'") + return int(match.group(1)), int(match.group(2)), int(match.group(3)) + + # Custom OrderedDict with nicer repr method for debugging class OrderedDict(collections.OrderedDict): def __repr__(self): diff --git a/esphome/writer.py b/esphome/writer.py index 89a074683a71..31b47e243e5a 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -202,6 +202,7 @@ def write_platformio_project(): DEFINES_H_FORMAT = ESPHOME_H_FORMAT = """\ #pragma once +#include "esphome/core/macros.h" {} """ VERSION_H_FORMAT = """\ diff --git a/platformio.ini b/platformio.ini index 589624a71d73..8775b2815643 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,22 +33,27 @@ build_flags = ; This are common settings for all environments. [common] lib_deps = - esphome/noise-c@0.1.4 ; api - makuna/NeoPixelBus@2.6.9 ; neopixelbus - esphome/Improv@1.0.0 ; improv_serial / esp32_improv - bblanchon/ArduinoJson@6.18.5 ; json + esphome/noise-c@0.1.4 ; api + makuna/NeoPixelBus@2.6.9 ; neopixelbus + esphome/Improv@1.2.1 ; improv_serial / esp32_improv + bblanchon/ArduinoJson@6.18.5 ; json + wjtje/qr-code-generator-library@1.7.0 ; qr_code + functionpointer/arduino-MLX90393@1.0.0 ; mlx90393 build_flags = -DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE src_filter = +<./> +<../tests/dummy_main.cpp> +<../.temp/all-include.cpp> +lib_ldf_mode = off ; This are common settings for all Arduino-framework based environments. [common:arduino] extends = common lib_deps = ${common.lib_deps} + SPI ; spi (Arduino built-in) + Wire ; i2c (Arduino built-int) ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt esphome/ESPAsyncWebServer-esphome@2.1.0 ; web_server_base fastled/FastLED@3.3.2 ; fastled_base @@ -74,7 +79,6 @@ build_flags = ; This are common settings for the ESP8266 using Arduino. [common:esp8266-arduino] extends = common:arduino -; when changing this also copy it to esphome-docker-base images platform = platformio/espressif8266 @ 3.2.0 platform_packages = platformio/framework-arduinoespressif8266 @ ~3.30002.0 @@ -85,38 +89,49 @@ lib_deps = ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ottowinter/ESPAsyncTCP-esphome@1.2.3 ; async_tcp + ESP8266HTTPClient ; http_request (Arduino built-in) + ESP8266mDNS ; mdns (Arduino built-in) + DNSServer ; captive_portal (Arduino built-in) build_flags = ${common:arduino.build_flags} + -Wno-nonnull-compare -DUSE_ESP8266 -DUSE_ESP8266_FRAMEWORK_ARDUINO -extra_scripts = post:esphome/components/esp8266/post_build.py +extra_scripts = post:esphome/components/esp8266/post_build.py.script ; This are common settings for the ESP32 (all variants) using Arduino. [common:esp32-arduino] extends = common:arduino -; when changing this also copy it to esphome-docker-base images -platform = platformio/espressif32 @ 3.3.2 +platform = platformio/espressif32 @ 3.5.0 platform_packages = platformio/framework-arduinoespressif32 @ ~3.10006.0 framework = arduino board = nodemcu-32s lib_deps = + ; order matters with lib-deps; some of the libs in common:arduino.lib_deps + ; don't declare built-in libraries as dependencies, so they have to be declared first + FS ; web_server_base (Arduino built-in) + WiFi ; wifi,web_server_base,ethernet (Arduino built-in) + Update ; ota,web_server_base (Arduino built-in) ${common:arduino.lib_deps} esphome/AsyncTCP-esphome@1.2.2 ; async_tcp + WiFiClientSecure ; http_request,nextion (Arduino built-in) + HTTPClient ; http_request,nextion (Arduino built-in) + ESPmDNS ; mdns (Arduino built-in) + DNSServer ; captive_portal (Arduino built-in) build_flags = ${common:arduino.build_flags} -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ARDUINO -extra_scripts = post:esphome/components/esp32/post_build.py +extra_scripts = post:esphome/components/esp32/post_build.py.script ; This are common settings for the ESP32 (all variants) using IDF. [common:esp32-idf] extends = common:idf -; when changing this also copy it to esphome-docker-base images -platform = platformio/espressif32 @ 3.3.2 +platform = platformio/espressif32 @ 3.5.0 platform_packages = - platformio/framework-espidf @ ~3.40300.0 + platformio/framework-espidf @ ~3.40302.0 framework = espidf lib_deps = @@ -127,7 +142,7 @@ build_flags = -Wno-nonnull-compare -DUSE_ESP32 -DUSE_ESP32_FRAMEWORK_ESP_IDF -extra_scripts = post:esphome/components/esp32/post_build.py +extra_scripts = post:esphome/components/esp32/post_build.py.script ; All the actual environments are defined below. [env:esp8266-arduino] diff --git a/requirements.txt b/requirements.txt index 9add417bdf45..a33461b79b8e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,8 @@ pyserial==3.5 platformio==5.2.4 # When updating platformio, also update Dockerfile esptool==3.2 click==8.0.3 -esphome-dashboard==20220116.0 -aioesphomeapi==10.6.0 +esphome-dashboard==20220209.0 +aioesphomeapi==10.8.1 zeroconf==0.37.0 # esp-idf requires this, but doesn't bundle it by default diff --git a/requirements_test.txt b/requirements_test.txt index 4d5c40296fcc..1e5a5c2ebce2 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,12 +1,12 @@ pylint==2.12.2 flake8==4.0.1 -black==21.12b0 +black==22.1.0 pre-commit # Unit tests -pytest==6.2.5 +pytest==7.0.0 pytest-cov==3.0.0 -pytest-mock==3.6.1 -pytest-asyncio==0.16.0 +pytest-mock==3.7.0 +pytest-asyncio==0.18.0 asyncmock==0.4.2 hypothesis==5.49.0 diff --git a/script/ci-custom.py b/script/ci-custom.py index 52ac4025ca25..d1efa22d852f 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -20,7 +20,7 @@ def find_all(a_str, sub): # Optimization: If str is not in whole text, then do not try # on each line return - for i, line in enumerate(a_str.split('\n')): + for i, line in enumerate(a_str.split("\n")): column = 0 while True: column = line.find(sub, column) @@ -222,7 +222,13 @@ def lint_ext_check(fname): @lint_file_check( - exclude=["**.sh", "docker/hassio-rootfs/**", "docker/*.py", "script/*", "setup.py"] + exclude=[ + "**.sh", + "docker/ha-addon-rootfs/**", + "docker/*.py", + "script/*", + "setup.py", + ] ) def lint_executable_bit(fname): ex = EXECUTABLE_BIT[fname] @@ -592,9 +598,13 @@ def lint_inclusive_language(fname, match): include=["*.h", "*.tcc"], exclude=[ "esphome/components/binary_sensor/binary_sensor.h", + "esphome/components/button/button.h", + "esphome/components/climate/climate.h", "esphome/components/cover/cover.h", "esphome/components/display/display_buffer.h", + "esphome/components/fan/fan.h", "esphome/components/i2c/i2c.h", + "esphome/components/lock/lock.h", "esphome/components/mqtt/mqtt_component.h", "esphome/components/number/number.h", "esphome/components/output/binary_output.h", @@ -605,8 +615,6 @@ def lint_inclusive_language(fname, match): "esphome/components/stepper/stepper.h", "esphome/components/switch/switch.h", "esphome/components/text_sensor/text_sensor.h", - "esphome/components/climate/climate.h", - "esphome/components/button/button.h", "esphome/core/component.h", "esphome/core/gpio.h", "esphome/core/log.h", @@ -665,7 +673,10 @@ def add_errors(fname, errs): for f, errs in sorted(errors.items()): bold = functools.partial(styled, colorama.Style.BRIGHT) bold_red = functools.partial(styled, (colorama.Style.BRIGHT, colorama.Fore.RED)) - err_str = (f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" for lineno, col, msg in errs) + err_str = ( + f"{bold(f'{f}:{lineno}:{col}:')} {bold_red('lint:')} {msg}\n" + for lineno, col, msg in errs + ) print_error_for_file(f, "\n".join(err_str)) if args.print_slowest: diff --git a/tests/component_tests/button/test_button.py b/tests/component_tests/button/test_button.py new file mode 100644 index 000000000000..c84715dcd88b --- /dev/null +++ b/tests/component_tests/button/test_button.py @@ -0,0 +1,46 @@ +"""Tests for the button component""" + + +def test_button_is_setup(generate_main): + """ + When the button is set in the yaml file if should be registered in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/button/test_button.yaml") + + # Then + assert "new wake_on_lan::WakeOnLanButton();" in main_cpp + assert "App.register_button" in main_cpp + assert "App.register_component" in main_cpp + + +def test_button_sets_mandatory_fields(generate_main): + """ + When the mandatory fields are set in the yaml, they should be set in main + """ + # Given + + # When + main_cpp = generate_main("tests/component_tests/button/test_button.yaml") + + # Then + assert 'wol_1->set_name("wol_test_1");' in main_cpp + assert "wol_2->set_macaddr(18, 52, 86, 120, 144, 171);" in main_cpp + + +def test_button_config_value_internal_set(generate_main): + """ + Test that the "internal" config value is correctly set + """ + # Given + + # When + main_cpp = generate_main( + "tests/component_tests/button/test_button.yaml" + ) + + # Then + assert "wol_1->set_internal(true);" in main_cpp + assert "wol_2->set_internal(false);" in main_cpp diff --git a/tests/component_tests/button/test_button.yaml b/tests/component_tests/button/test_button.yaml new file mode 100644 index 000000000000..3eac129e8ccd --- /dev/null +++ b/tests/component_tests/button/test_button.yaml @@ -0,0 +1,21 @@ +esphome: + name: test + platform: ESP8266 + board: d1_mini_lite + +wifi: + ssid: SomeNetwork + password: SomePassword + +button: + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_1 + id: wol_1 + internal: true + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_2 + id: wol_2 + internal: false + diff --git a/tests/test1.yaml b/tests/test1.yaml index d3351e3b12f7..d8fea223dc30 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -29,7 +29,7 @@ esphome: range_from: 0 range_to: 100 red: 100% - green: !lambda 'return 255;' + green: !lambda "return 255;" blue: 0% white: 100% - http_request.get: @@ -43,18 +43,18 @@ esphome: json: key: !lambda |- return id(${textname}_text).state; - greeting: 'Hello World' + greeting: "Hello World" - http_request.send: method: PUT url: https://esphome.io headers: Content-Type: application/json - body: 'Some data' + body: "Some data" verify_ssl: false on_response: then: - logger.log: - format: 'Response status: %d' + format: "Response status: %d" args: - status_code build_path: build/test1 @@ -65,12 +65,12 @@ packages: wifi: networks: - - ssid: 'MySSID' - password: 'password1' - - ssid: 'MySSID2' - password: '' + - ssid: "MySSID" + password: "password1" + - ssid: "MySSID2" + password: "" channel: 14 - bssid: 'A1:63:95:47:D3:1D' + bssid: "A1:63:95:47:D3:1D" manual_ip: static_ip: 192.168.178.230 gateway: 192.168.178.1 @@ -89,10 +89,10 @@ http_request: timeout: 10s mqtt: - broker: '192.168.178.84' + broker: "192.168.178.84" port: 1883 - username: 'debug' - password: 'debug' + username: "debug" + password: "debug" client_id: someclient use_abbreviations: false discovery: True @@ -153,7 +153,7 @@ mqtt: return effect; - light.control: id: ${roomname}_lights - brightness: !lambda 'return id(${roomname}_lights).current_values.get_brightness() + 0.5;' + brightness: !lambda "return id(${roomname}_lights).current_values.get_brightness() + 0.5;" - light.dim_relative: id: ${roomname}_lights relative_brightness: 5% @@ -215,7 +215,7 @@ uart: ota: safe_mode: True - password: 'superlongpasswordthatnoonewillknow' + password: "superlongpasswordthatnoonewillknow" port: 3286 reboot_timeout: 2min num_attempts: 5 @@ -252,7 +252,7 @@ web_server: js_url: https://esphome.io/_static/webserver-v1.min.js power_supply: - id: 'atx_power_supply' + id: "atx_power_supply" enable_time: 20ms keep_on_time: 10s pin: @@ -294,22 +294,22 @@ ble_client: then: - switch.turn_on: ble1_status mcp23s08: - - id: 'mcp23s08_hub' + - id: "mcp23s08_hub" cs_pin: GPIO12 deviceaddress: 0 mcp23s17: - - id: 'mcp23s17_hub' + - id: "mcp23s17_hub" cs_pin: GPIO12 deviceaddress: 1 sensor: - platform: ble_client ble_client_id: ble_foo - name: 'Green iTag btn' - service_uuid: 'ffe0' - characteristic_uuid: 'ffe1' - descriptor_uuid: 'ffe2' + name: "Green iTag btn" + service_uuid: "ffe0" + characteristic_uuid: "ffe1" + descriptor_uuid: "ffe2" notify: true update_interval: never lambda: |- @@ -321,11 +321,11 @@ sensor: ESP_LOGD("green_btn", "Button was pressed, val%f", x); - platform: adc pin: A0 - name: 'Living Room Brightness' - update_interval: '1:01' + name: "Living Room Brightness" + update_interval: "1:01" attenuation: 2.5db - unit_of_measurement: '°C' - icon: 'mdi:water-percent' + unit_of_measurement: "°C" + icon: "mdi:water-percent" accuracy_decimals: 5 expire_after: 120s setup_priority: -100 @@ -390,9 +390,9 @@ sensor: ESP_LOGD("main", "Got raw value %f", x); - logger.log: level: DEBUG - format: 'Got raw value %f' - args: ['x'] - - logger.log: 'Got raw value NAN' + format: "Got raw value %f" + args: ["x"] + - logger.log: "Got raw value NAN" - mqtt.publish: topic: some/topic payload: Hello @@ -401,7 +401,7 @@ sensor: - platform: esp32_hall name: ESP32 Hall Sensor - platform: ads1115 - multiplexer: 'A0_A1' + multiplexer: "A0_A1" gain: 1.024 id: ${sensorname}_sensor filters: @@ -412,48 +412,48 @@ sensor: cs_pin: 5 phase_a: voltage: - name: 'EMON Line Voltage A' + name: "EMON Line Voltage A" current: - name: 'EMON CT1 Current' + name: "EMON CT1 Current" power: - name: 'EMON Active Power CT1' + name: "EMON Active Power CT1" reactive_power: - name: 'EMON Reactive Power CT1' + name: "EMON Reactive Power CT1" power_factor: - name: 'EMON Power Factor CT1' + name: "EMON Power Factor CT1" gain_voltage: 7305 gain_ct: 27961 phase_b: current: - name: 'EMON CT2 Current' + name: "EMON CT2 Current" power: - name: 'EMON Active Power CT2' + name: "EMON Active Power CT2" reactive_power: - name: 'EMON Reactive Power CT2' + name: "EMON Reactive Power CT2" power_factor: - name: 'EMON Power Factor CT2' + name: "EMON Power Factor CT2" gain_voltage: 7305 gain_ct: 27961 phase_c: current: - name: 'EMON CT3 Current' + name: "EMON CT3 Current" power: - name: 'EMON Active Power CT3' + name: "EMON Active Power CT3" reactive_power: - name: 'EMON Reactive Power CT3' + name: "EMON Reactive Power CT3" power_factor: - name: 'EMON Power Factor CT3' + name: "EMON Power Factor CT3" gain_voltage: 7305 gain_ct: 27961 frequency: - name: 'EMON Line Frequency' + name: "EMON Line Frequency" chip_temperature: - name: 'EMON Chip Temp A' + name: "EMON Chip Temp A" line_frequency: 60Hz current_phases: 3 gain_pga: 2X - platform: bh1750 - name: 'Living Room Brightness 3' + name: "Living Room Brightness 3" internal: true address: 0x23 resolution: 1.0 @@ -465,13 +465,13 @@ sensor: i2c_id: i2c_bus - platform: bme280 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" oversampling: 16x pressure: - name: 'Outside Pressure' + name: "Outside Pressure" oversampling: none humidity: - name: 'Outside Humidity' + name: "Outside Humidity" oversampling: 8x address: 0x77 iir_filter: 16x @@ -479,14 +479,14 @@ sensor: i2c_id: i2c_bus - platform: bme680 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" oversampling: 16x pressure: - name: 'Outside Pressure' + name: "Outside Pressure" humidity: - name: 'Outside Humidity' + name: "Outside Humidity" gas_resistance: - name: 'Outside Gas Sensor' + name: "Outside Gas Sensor" address: 0x77 heater: temperature: 320 @@ -495,9 +495,9 @@ sensor: i2c_id: i2c_bus - platform: bmp085 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" pressure: - name: 'Outside Pressure' + name: "Outside Pressure" filters: - lambda: >- return x / powf(1.0 - (x / 44330.0), 5.255); @@ -505,47 +505,47 @@ sensor: i2c_id: i2c_bus - platform: bmp280 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" oversampling: 16x pressure: - name: 'Outside Pressure' + name: "Outside Pressure" address: 0x77 update_interval: 15s iir_filter: 16x i2c_id: i2c_bus - platform: dallas address: 0x1C0000031EDD2A28 - name: 'Living Room Temperature' + name: "Living Room Temperature" resolution: 9 - platform: dallas index: 1 - name: 'Living Room Temperature 2' + name: "Living Room Temperature 2" - platform: dht pin: GPIO26 temperature: - name: 'Living Room Temperature 3' + name: "Living Room Temperature 3" humidity: - name: 'Living Room Humidity 3' + name: "Living Room Humidity 3" model: AM2302 update_interval: 15s - platform: dht12 temperature: - name: 'Living Room Temperature 4' + name: "Living Room Temperature 4" humidity: - name: 'Living Room Humidity 4' + name: "Living Room Humidity 4" update_interval: 15s i2c_id: i2c_bus - platform: duty_cycle pin: GPIO25 name: Duty Cycle Sensor - platform: esp32_hall - name: 'ESP32 Hall Sensor' + name: "ESP32 Hall Sensor" update_interval: 15s - platform: hdc1080 temperature: - name: 'Living Room Temperature 5' + name: "Living Room Temperature 5" humidity: - name: 'Living Room Pressure 5' + name: "Living Room Pressure 5" update_interval: 15s i2c_id: i2c_bus - platform: hlw8012 @@ -553,14 +553,14 @@ sensor: cf_pin: 14 cf1_pin: 13 current: - name: 'HLW8012 Current' + name: "HLW8012 Current" voltage: - name: 'HLW8012 Voltage' + name: "HLW8012 Voltage" power: - name: 'HLW8012 Power' + name: "HLW8012 Power" id: hlw8012_power energy: - name: 'HLW8012 Energy' + name: "HLW8012 Energy" id: hlw8012_energy update_interval: 15s current_resistor: 0.001 ohm @@ -570,26 +570,26 @@ sensor: model: hlw8012 - platform: total_daily_energy power_id: hlw8012_power - name: 'HLW8012 Total Daily Energy' + name: "HLW8012 Total Daily Energy" - platform: integration sensor: hlw8012_power - name: 'Integration Sensor' + name: "Integration Sensor" time_unit: s - platform: integration sensor: hlw8012_power - name: 'Integration Sensor lazy' + name: "Integration Sensor lazy" time_unit: s min_save_interval: 60s - platform: hmc5883l address: 0x68 field_strength_x: - name: 'HMC5883L Field Strength X' + name: "HMC5883L Field Strength X" field_strength_y: - name: 'HMC5883L Field Strength Y' + name: "HMC5883L Field Strength Y" field_strength_z: - name: 'HMC5883L Field Strength Z' + name: "HMC5883L Field Strength Z" heading: - name: 'HMC5883L Heading' + name: "HMC5883L Heading" range: 130uT oversampling: 8x update_interval: 15s @@ -597,19 +597,19 @@ sensor: - platform: qmc5883l address: 0x0D field_strength_x: - name: 'QMC5883L Field Strength X' + name: "QMC5883L Field Strength X" field_strength_y: - name: 'QMC5883L Field Strength Y' + name: "QMC5883L Field Strength Y" field_strength_z: - name: 'QMC5883L Field Strength Z' + name: "QMC5883L Field Strength Z" heading: - name: 'QMC5883L Heading' + name: "QMC5883L Heading" range: 800uT oversampling: 256x update_interval: 15s i2c_id: i2c_bus - platform: hx711 - name: 'HX711 Value' + name: "HX711 Value" dout_pin: GPIO23 clk_pin: GPIO25 gain: 128 @@ -618,13 +618,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: 'INA219 Current' + name: "INA219 Current" power: - name: 'INA219 Power' + name: "INA219 Power" bus_voltage: - name: 'INA219 Bus Voltage' + name: "INA219 Bus Voltage" shunt_voltage: - name: 'INA219 Shunt Voltage' + name: "INA219 Shunt Voltage" max_voltage: 32.0V max_current: 3.2A update_interval: 15s @@ -633,13 +633,13 @@ sensor: address: 0x40 shunt_resistance: 0.1 ohm current: - name: 'INA226 Current' + name: "INA226 Current" power: - name: 'INA226 Power' + name: "INA226 Power" bus_voltage: - name: 'INA226 Bus Voltage' + name: "INA226 Bus Voltage" shunt_voltage: - name: 'INA226 Shunt Voltage' + name: "INA226 Shunt Voltage" max_current: 3.2A update_interval: 15s i2c_id: i2c_bus @@ -648,13 +648,13 @@ sensor: channel_1: shunt_resistance: 0.1 ohm current: - name: 'INA3221 Channel 1 Current' + name: "INA3221 Channel 1 Current" power: - name: 'INA3221 Channel 1 Power' + name: "INA3221 Channel 1 Power" bus_voltage: - name: 'INA3221 Channel 1 Bus Voltage' + name: "INA3221 Channel 1 Bus Voltage" shunt_voltage: - name: 'INA3221 Channel 1 Shunt Voltage' + name: "INA3221 Channel 1 Shunt Voltage" update_interval: 15s i2c_id: i2c_bus - platform: kalman_combinator @@ -668,62 +668,62 @@ sensor: error: 1.5 - platform: htu21d temperature: - name: 'Living Room Temperature 6' + name: "Living Room Temperature 6" humidity: - name: 'Living Room Humidity 6' + name: "Living Room Humidity 6" update_interval: 15s i2c_id: i2c_bus - platform: max6675 - name: 'Living Room Temperature' + name: "Living Room Temperature" cs_pin: GPIO23 update_interval: 15s - platform: max31855 - name: 'Den Temperature' + name: "Den Temperature" cs_pin: GPIO23 update_interval: 15s reference_temperature: - name: 'MAX31855 Internal Temperature' + name: "MAX31855 Internal Temperature" - platform: max31856 - name: 'BBQ Temperature' + name: "BBQ Temperature" cs_pin: GPIO17 update_interval: 15s mains_filter: 50Hz - platform: max31865 - name: 'Water Tank Temperature' + name: "Water Tank Temperature" cs_pin: GPIO23 update_interval: 15s - reference_resistance: '430 Ω' - rtd_nominal_resistance: '100 Ω' + reference_resistance: "430 Ω" + rtd_nominal_resistance: "100 Ω" - platform: mhz19 uart_id: uart0 co2: - name: 'MH-Z19 CO2 Value' + name: "MH-Z19 CO2 Value" temperature: - name: 'MH-Z19 Temperature' + name: "MH-Z19 Temperature" update_interval: 15s automatic_baseline_calibration: false - platform: mpu6050 address: 0x68 accel_x: - name: 'MPU6050 Accel X' + name: "MPU6050 Accel X" accel_y: - name: 'MPU6050 Accel Y' + name: "MPU6050 Accel Y" accel_z: - name: 'MPU6050 Accel z' + name: "MPU6050 Accel z" gyro_x: - name: 'MPU6050 Gyro X' + name: "MPU6050 Gyro X" gyro_y: - name: 'MPU6050 Gyro Y' + name: "MPU6050 Gyro Y" gyro_z: - name: 'MPU6050 Gyro z' + name: "MPU6050 Gyro z" temperature: - name: 'MPU6050 Temperature' + name: "MPU6050 Temperature" i2c_id: i2c_bus - platform: ms5611 temperature: - name: 'Outside Temperature' + name: "Outside Temperature" pressure: - name: 'Outside Pressure' + name: "Outside Pressure" address: 0x77 update_interval: 15s i2c_id: i2c_bus @@ -750,7 +750,7 @@ sensor: standard_units: True i2c_id: i2c_bus - platform: pulse_counter - name: 'Pulse Counter' + name: "Pulse Counter" pin: GPIO12 count_mode: rising_edge: INCREMENT @@ -758,7 +758,7 @@ sensor: internal_filter: 13us update_interval: 15s - platform: pulse_meter - name: 'Pulse Meter' + name: "Pulse Meter" id: pulse_meter_sensor pin: GPIO12 internal_filter: 100ms @@ -768,9 +768,9 @@ sensor: id: pulse_meter_sensor value: 12345 total: - name: 'Pulse Meter Total' + name: "Pulse Meter Total" - platform: rotary_encoder - name: 'Rotary Encoder' + name: "Rotary Encoder" id: rotary_encoder1 pin_a: GPIO23 pin_b: GPIO25 @@ -788,51 +788,51 @@ sensor: value: 10 - sensor.rotary_encoder.set_value: id: rotary_encoder1 - value: !lambda 'return -1;' + value: !lambda "return -1;" on_clockwise: - - logger.log: 'Clockwise' + - logger.log: "Clockwise" on_anticlockwise: - - logger.log: 'Anticlockwise' + - logger.log: "Anticlockwise" - platform: pulse_width name: Pulse Width pin: GPIO12 - platform: sm300d2 uart_id: uart0 co2: - name: 'SM300D2 CO2 Value' + name: "SM300D2 CO2 Value" formaldehyde: - name: 'SM300D2 Formaldehyde Value' + name: "SM300D2 Formaldehyde Value" tvoc: - name: 'SM300D2 TVOC Value' + name: "SM300D2 TVOC Value" pm_2_5: - name: 'SM300D2 PM2.5 Value' + name: "SM300D2 PM2.5 Value" pm_10_0: - name: 'SM300D2 PM10 Value' + name: "SM300D2 PM10 Value" temperature: - name: 'SM300D2 Temperature Value' + name: "SM300D2 Temperature Value" humidity: - name: 'SM300D2 Humidity Value' + name: "SM300D2 Humidity Value" update_interval: 60s - platform: sht3xd temperature: - name: 'Living Room Temperature 8' + name: "Living Room Temperature 8" humidity: - name: 'Living Room Humidity 8' + name: "Living Room Humidity 8" address: 0x44 i2c_id: i2c_bus update_interval: 15s - platform: sts3x - name: 'Living Room Temperature 9' + name: "Living Room Temperature 9" address: 0x4A i2c_id: i2c_bus - platform: scd30 co2: - name: 'Living Room CO2 9' + name: "Living Room CO2 9" temperature: id: scd30_temperature - name: 'Living Room Temperature 9' + name: "Living Room Temperature 9" humidity: - name: 'Living Room Humidity 9' + name: "Living Room Humidity 9" address: 0x61 update_interval: 15s automatic_self_calibration: true @@ -856,63 +856,63 @@ sensor: i2c_id: i2c_bus - platform: sgp30 eco2: - name: 'Workshop eCO2' + name: "Workshop eCO2" accuracy_decimals: 1 tvoc: - name: 'Workshop TVOC' + name: "Workshop TVOC" accuracy_decimals: 1 address: 0x58 update_interval: 5s i2c_id: i2c_bus - platform: sps30 pm_1_0: - name: 'Workshop PM <1µm Weight concentration' - id: 'workshop_PM_1_0' + name: "Workshop PM <1µm Weight concentration" + id: "workshop_PM_1_0" pm_2_5: - name: 'Workshop PM <2.5µm Weight concentration' - id: 'workshop_PM_2_5' + name: "Workshop PM <2.5µm Weight concentration" + id: "workshop_PM_2_5" pm_4_0: - name: 'Workshop PM <4µm Weight concentration' - id: 'workshop_PM_4_0' + name: "Workshop PM <4µm Weight concentration" + id: "workshop_PM_4_0" pm_10_0: - name: 'Workshop PM <10µm Weight concentration' - id: 'workshop_PM_10_0' + name: "Workshop PM <10µm Weight concentration" + id: "workshop_PM_10_0" pmc_0_5: - name: 'Workshop PM <0.5µm Number concentration' - id: 'workshop_PMC_0_5' + name: "Workshop PM <0.5µm Number concentration" + id: "workshop_PMC_0_5" pmc_1_0: - name: 'Workshop PM <1µm Number concentration' - id: 'workshop_PMC_1_0' + name: "Workshop PM <1µm Number concentration" + id: "workshop_PMC_1_0" pmc_2_5: - name: 'Workshop PM <2.5µm Number concentration' - id: 'workshop_PMC_2_5' + name: "Workshop PM <2.5µm Number concentration" + id: "workshop_PMC_2_5" pmc_4_0: - name: 'Workshop PM <4µm Number concentration' - id: 'workshop_PMC_4_0' + name: "Workshop PM <4µm Number concentration" + id: "workshop_PMC_4_0" pmc_10_0: - name: 'Workshop PM <10µm Number concentration' - id: 'workshop_PMC_10_0' + name: "Workshop PM <10µm Number concentration" + id: "workshop_PMC_10_0" address: 0x69 update_interval: 10s i2c_id: i2c_bus - platform: sht4x temperature: - name: 'SHT4X Temperature' + name: "SHT4X Temperature" humidity: - name: 'SHT4X Humidity' + name: "SHT4X Humidity" address: 0x44 update_interval: 15s i2c_id: i2c_bus - platform: shtcx temperature: - name: 'Living Room Temperature 10' + name: "Living Room Temperature 10" humidity: - name: 'Living Room Humidity 10' + name: "Living Room Humidity 10" address: 0x70 update_interval: 15s i2c_id: i2c_bus - platform: template - name: 'Template Sensor' + name: "Template Sensor" state_class: measurement id: template_sensor lambda: |- @@ -928,9 +928,9 @@ sensor: state: 43.0 - sensor.template.publish: id: template_sensor - state: !lambda 'return NAN;' + state: !lambda "return NAN;" - platform: tsl2561 - name: 'TSL2561 Ambient Light' + name: "TSL2561 Ambient Light" address: 0x39 update_interval: 15s is_cs_package: true @@ -946,7 +946,7 @@ sensor: visible: name: "tsl2591 visible" id: tsl2591_vis - unit_of_measurement: 'pH' + unit_of_measurement: "pH" infrared: name: "tsl2591 infrared" id: tsl2591_ir @@ -962,17 +962,17 @@ sensor: echo_pin: number: GPIO23 inverted: true - name: 'Ultrasonic Sensor' + name: "Ultrasonic Sensor" timeout: 5.5m id: ultrasonic_sensor1 - platform: uptime name: Uptime Sensor - platform: wifi_signal - name: 'WiFi Signal Sensor' + name: "WiFi Signal Sensor" update_interval: 15s - platform: mqtt_subscribe - name: 'MQTT Subscribe Sensor 1' - topic: 'mqtt/topic' + name: "MQTT Subscribe Sensor 1" + topic: "mqtt/topic" id: the_sensor qos: 2 on_value: @@ -984,9 +984,9 @@ sensor: - platform: sds011 uart_id: uart0 pm_2_5: - name: 'SDS011 PM2.5' + name: "SDS011 PM2.5" pm_10_0: - name: 'SDS011 PM10.0' + name: "SDS011 PM10.0" update_interval: 5min rx_only: false - platform: ccs811 @@ -999,9 +999,9 @@ sensor: i2c_id: i2c_bus - platform: tx20 wind_speed: - name: 'Windspeed' + name: "Windspeed" wind_direction_degrees: - name: 'Winddirection Degrees' + name: "Winddirection Degrees" pin: number: GPIO04 mode: INPUT @@ -1009,30 +1009,30 @@ sensor: clock_pin: GPIO5 data_pin: GPIO4 co2: - name: 'ZyAura CO2' + name: "ZyAura CO2" temperature: - name: 'ZyAura Temperature' + name: "ZyAura Temperature" humidity: - name: 'ZyAura Humidity' + name: "ZyAura Humidity" - platform: as3935 lightning_energy: - name: 'Lightning Energy' + name: "Lightning Energy" distance: - name: 'Distance Storm' + name: "Distance Storm" - platform: tmp117 - name: 'TMP117 Temperature' + name: "TMP117 Temperature" update_interval: 5s i2c_id: i2c_bus - platform: hm3301 pm_1_0: - name: 'PM1.0' + name: "PM1.0" pm_2_5: - name: 'PM2.5' + name: "PM2.5" pm_10_0: - name: 'PM10.0' + name: "PM10.0" aqi: - name: 'AQI' - calculation_type: 'CAQI' + name: "AQI" + calculation_type: "CAQI" i2c_id: i2c_bus - platform: teleinfo tag_name: "HCHC" @@ -1041,13 +1041,13 @@ sensor: icon: mdi:flash teleinfo_id: myteleinfo - platform: mcp9808 - name: 'MCP9808 Temperature' + name: "MCP9808 Temperature" update_interval: 15s i2c_id: i2c_bus - platform: ezo id: ph_ezo address: 99 - unit_of_measurement: 'pH' + unit_of_measurement: "pH" i2c_id: i2c_bus - platform: sdp3x name: "HVAC Filter Pressure drop" @@ -1077,7 +1077,21 @@ sensor: cs_pin: mcp23xxx: mcp23017_hub number: 14 - + - platform: max9611 + i2c_id: i2c_bus + shunt_resistance: 0.2 ohm + gain: '1X' + voltage: + name: Max9611 Voltage + current: + name: Max9611 Current + power: + name: Max9611 Watts + temperature: + name: Max9611 Temp + update_interval: 1s + + esp32_touch: setup_mode: False iir_filter: 10ms @@ -1089,7 +1103,7 @@ esp32_touch: binary_sensor: - platform: gpio - name: 'MCP23S08 Pin #1' + name: "MCP23S08 Pin #1" pin: mcp23xxx: mcp23s08_hub # Use pin number 1 @@ -1098,7 +1112,7 @@ binary_sensor: mode: INPUT_PULLUP inverted: False - platform: gpio - name: 'MCP23S17 Pin #1' + name: "MCP23S17 Pin #1" pin: mcp23xxx: mcp23s17_hub # Use pin number 1 @@ -1107,7 +1121,7 @@ binary_sensor: mode: INPUT_PULLUP inverted: False - platform: gpio - name: 'MCP23S17 Pin #1 with interrupt' + name: "MCP23S17 Pin #1 with interrupt" pin: mcp23xxx: mcp23s17_hub # Use pin number 1 @@ -1118,7 +1132,7 @@ binary_sensor: interrupt: FALLING - platform: gpio pin: GPIO9 - name: 'Living Room Window' + name: "Living Room Window" device_class: window filters: - invert: @@ -1158,7 +1172,7 @@ binary_sensor: - OFF for at least 0.2s then: - logger.log: - format: 'Multi Clicked TWO' + format: "Multi Clicked TWO" level: warn - timing: - OFF for 1s to 2s @@ -1166,30 +1180,30 @@ binary_sensor: - OFF for at least 0.5s then: - logger.log: - format: 'Multi Clicked LONG SINGLE' + format: "Multi Clicked LONG SINGLE" level: warn - timing: - ON for at most 1s - OFF for at least 0.5s then: - logger.log: - format: 'Multi Clicked SINGLE' + format: "Multi Clicked SINGLE" level: warn id: binary_sensor1 - platform: gpio pin: number: GPIO9 mode: INPUT_PULLUP - name: 'Living Room Window 2' + name: "Living Room Window 2" - platform: status - name: 'Living Room Status' + name: "Living Room Status" - platform: esp32_touch - name: 'ESP32 Touch Pad GPIO27' + name: "ESP32 Touch Pad GPIO27" pin: GPIO27 threshold: 1000 id: btn_left - platform: template - name: 'Garage Door Open' + name: "Garage Door Open" id: garage_door lambda: |- if (isnan(id(${sensorname}_sensor).state)) { @@ -1213,37 +1227,37 @@ binary_sensor: frequency: 500.0Hz - output.ledc.set_frequency: id: gpio_19 - frequency: !lambda 'return 500.0;' + frequency: !lambda "return 500.0;" - platform: pn532 pn532_id: pn532_bs uid: 74-10-37-94 - name: 'PN532 NFC Tag' + name: "PN532 NFC Tag" - platform: rdm6300 uid: 7616525 - name: 'RDM6300 NFC Tag' + name: "RDM6300 NFC Tag" - platform: gpio - name: 'PCF binary sensor' + name: "PCF binary sensor" pin: pcf8574: pcf8574_hub number: 1 mode: INPUT inverted: True - platform: gpio - name: 'MCP21 binary sensor' + name: "MCP21 binary sensor" pin: mcp23xxx: mcp23017_hub number: 1 mode: INPUT inverted: True - platform: gpio - name: 'MCP22 binary sensor' + name: "MCP22 binary sensor" pin: mcp23xxx: mcp23008_hub number: 7 mode: INPUT_PULLUP inverted: False - platform: gpio - name: 'MCP23 binary sensor' + name: "MCP23 binary sensor" pin: mcp23016: mcp23016_hub number: 7 @@ -1251,7 +1265,7 @@ binary_sensor: inverted: False - platform: remote_receiver - name: 'Raw Remote Receiver Test' + name: "Raw Remote Receiver Test" raw: code: [ @@ -1292,7 +1306,7 @@ binary_sensor: 1709, ] - platform: as3935 - name: 'Storm Alert' + name: "Storm Alert" pca9685: frequency: 500 @@ -1356,39 +1370,39 @@ output: - platform: tlc59208f id: tlc_0 channel: 0 - tlc59208f_id: 'tlc59208f_1' + tlc59208f_id: "tlc59208f_1" - platform: tlc59208f id: tlc_1 channel: 1 - tlc59208f_id: 'tlc59208f_1' + tlc59208f_id: "tlc59208f_1" - platform: tlc59208f id: tlc_2 channel: 2 - tlc59208f_id: 'tlc59208f_1' + tlc59208f_id: "tlc59208f_1" - platform: tlc59208f id: tlc_3 channel: 0 - tlc59208f_id: 'tlc59208f_2' + tlc59208f_id: "tlc59208f_2" - platform: tlc59208f id: tlc_4 channel: 1 - tlc59208f_id: 'tlc59208f_2' + tlc59208f_id: "tlc59208f_2" - platform: tlc59208f id: tlc_5 channel: 2 - tlc59208f_id: 'tlc59208f_2' + tlc59208f_id: "tlc59208f_2" - platform: tlc59208f id: tlc_6 channel: 0 - tlc59208f_id: 'tlc59208f_3' + tlc59208f_id: "tlc59208f_3" - platform: tlc59208f id: tlc_7 channel: 1 - tlc59208f_id: 'tlc59208f_3' + tlc59208f_id: "tlc59208f_3" - platform: tlc59208f id: tlc_8 channel: 2 - tlc59208f_id: 'tlc59208f_3' + tlc59208f_id: "tlc59208f_3" - platform: gpio id: id2 pin: @@ -1454,12 +1468,12 @@ e131: light: - platform: binary - name: 'Desk Lamp' + name: "Desk Lamp" output: gpio_26 effects: - strobe: - strobe: - name: 'My Strobe' + name: "My Strobe" colors: - state: True duration: 250ms @@ -1474,7 +1488,7 @@ light: id: livingroom_lights state: yes - platform: monochromatic - name: 'Kitchen Lights' + name: "Kitchen Lights" id: kitchen output: gpio_19 gamma_correct: 2.8 @@ -1483,7 +1497,7 @@ light: - strobe: - flicker: - flicker: - name: 'My Flicker' + name: "My Flicker" alpha: 98% intensity: 1.5% - lambda: @@ -1495,20 +1509,20 @@ light: if (state == 4) state = 0; - platform: rgb - name: 'Living Room Lights' + name: "Living Room Lights" id: ${roomname}_lights red: pca_0 green: pca_1 blue: pca_2 - platform: rgbw - name: 'Living Room Lights 2' + name: "Living Room Lights 2" red: pca_3 green: pca_4 blue: pca_5 white: pca_6 color_interlock: true - platform: rgbww - name: 'Living Room Lights 2' + name: "Living Room Lights 2" red: pca_3 green: pca_4 blue: pca_5 @@ -1518,7 +1532,7 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: rgbct - name: 'Living Room Lights 2' + name: "Living Room Lights 2" red: pca_3 green: pca_4 blue: pca_5 @@ -1528,14 +1542,14 @@ light: warm_white_color_temperature: 500 mireds color_interlock: true - platform: cwww - name: 'Living Room Lights 2' + name: "Living Room Lights 2" cold_white: pca_6 warm_white: pca_6 cold_white_color_temperature: 153 mireds warm_white_color_temperature: 500 mireds constant_brightness: true - platform: color_temperature - name: 'Living Room Lights 2' + name: "Living Room Lights 2" color_temperature: pca_6 brightness: pca_6 cold_white_color_temperature: 153 mireds @@ -1549,7 +1563,7 @@ light: max_refresh_rate: 20ms power_supply: atx_power_supply color_correct: [75%, 100%, 50%] - name: 'FastLED WS2811 Light' + name: "FastLED WS2811 Light" effects: - addressable_color_wipe: - addressable_color_wipe: @@ -1592,7 +1606,7 @@ light: update_interval: 16ms intensity: 5% - addressable_lambda: - name: 'Test For Custom Lambda Effect' + name: "Test For Custom Lambda Effect" lambda: |- if (initial_run) { it[0] = current_color; @@ -1628,10 +1642,10 @@ light: data_rate: 2MHz num_leds: 60 rgb_order: BRG - name: 'FastLED SPI Light' + name: "FastLED SPI Light" - platform: neopixelbus id: addr3 - name: 'Neopixelbus Light' + name: "Neopixelbus Light" gamma_correct: 2.8 color_correct: [0.0, 0.0, 0.0, 0.0] default_transition_length: 10s @@ -1647,7 +1661,7 @@ light: num_leds: 60 pin: GPIO23 - platform: partition - name: 'Partition Light' + name: "Partition Light" segments: - id: addr1 from: 0 @@ -1677,7 +1691,7 @@ climate: away_state_topic: away/state/topic current_temperature_state_topic: current/temperature/state/topic fan_mode_command_topic: fan_mode/mode/command/topic - fan_mode_state_topic: fan_mode/mode/state/topic + fan_mode_state_topic: fan_mode/mode/state/topic mode_command_topic: mode/command/topic mode_state_topic: mode/state/topic swing_mode_command_topic: swing_mode/command/topic @@ -1803,7 +1817,7 @@ switch: remote_transmitter.transmit_midea: code: [0xA2, 0x08, 0xFF, 0xFF, 0xFF] - platform: gpio - name: 'MCP23S08 Pin #0' + name: "MCP23S08 Pin #0" pin: mcp23xxx: mcp23s08_hub # Use pin number 0 @@ -1811,7 +1825,7 @@ switch: mode: OUTPUT inverted: False - platform: gpio - name: 'MCP23S17 Pin #0' + name: "MCP23S17 Pin #0" pin: mcp23xxx: mcp23s17_hub # Use pin number 0 @@ -1820,10 +1834,11 @@ switch: inverted: False - platform: gpio pin: GPIO25 - name: 'Living Room Dehumidifier' - icon: 'mdi:restart' + name: "Living Room Dehumidifier" + icon: "mdi:restart" inverted: True command_topic: custom_command_topic + command_retain: true restore_mode: ALWAYS_OFF - platform: template name: JVC Off @@ -1885,14 +1900,14 @@ switch: name: RC Switch Raw turn_on_action: remote_transmitter.transmit_rc_switch_raw: - code: '00101001100111110101xxxx' + code: "00101001100111110101xxxx" protocol: 1 - platform: template name: RC Switch Type A turn_on_action: remote_transmitter.transmit_rc_switch_type_a: - group: '11001' - device: '01000' + group: "11001" + device: "01000" state: True protocol: pulse_length: 175 @@ -1911,7 +1926,7 @@ switch: name: RC Switch Type C turn_on_action: remote_transmitter.transmit_rc_switch_type_c: - family: 'a' + family: "a" group: 1 device: 2 state: True @@ -1919,7 +1934,7 @@ switch: name: RC Switch Type D turn_on_action: remote_transmitter.transmit_rc_switch_type_d: - group: 'a' + group: "a" device: 2 state: True - platform: template @@ -1945,16 +1960,16 @@ switch: level: 50% - output.set_level: id: gpio_19 - level: !lambda 'return 0.5;' + level: !lambda "return 0.5;" - output.set_level: id: dac_output level: 50% - output.set_level: id: dac_output - level: !lambda 'return 0.5;' + level: !lambda "return 0.5;" - output.set_level: id: mcp4725_dac_output - level: !lambda 'return 0.5;' + level: !lambda "return 0.5;" turn_off_action: - switch.turn_on: living_room_lights_off restore_state: False @@ -1963,16 +1978,16 @@ switch: id: livingroom_lights state: yes - platform: restart - name: 'Living Room Restart' + name: "Living Room Restart" - platform: safe_mode - name: 'Living Room Restart (Safe Mode)' + name: "Living Room Restart (Safe Mode)" - platform: shutdown - name: 'Living Room Shutdown' + name: "Living Room Shutdown" - platform: output - name: 'Generic Output' + name: "Generic Output" output: pca_6 - platform: template - name: 'Template Switch' + name: "Template Switch" id: my_switch lambda: |- if (id(binary_sensor1).state) { @@ -1995,18 +2010,18 @@ switch: on_turn_off: - switch.template.publish: id: my_switch - state: !lambda 'return false;' + state: !lambda "return false;" - platform: uart uart_id: uart0 - name: 'UART String Output' - data: 'DataToSend' + name: "UART String Output" + data: "DataToSend" - platform: uart uart_id: uart0 - name: 'UART Bytes Output' + name: "UART Bytes Output" data: [0xDE, 0xAD, 0xBE, 0xEF] - platform: uart uart_id: uart0 - name: 'UART Recurring Output' + name: "UART Recurring Output" data: [0xDE, 0xAD, 0xBE, 0xEF] send_every: 1s - platform: template @@ -2027,7 +2042,7 @@ switch: position: 0 - platform: gpio - name: 'SN74HC595 Pin #0' + name: "SN74HC595 Pin #0" pin: sn74hc595: sn74hc595_hub # Use pin number 0 @@ -2036,11 +2051,15 @@ switch: - platform: template id: ble1_status optimistic: true + - platform: template + id: outlet_switch + optimistic: true + device_class: outlet fan: - platform: binary output: gpio_26 - name: 'Living Room Fan 1' + name: "Living Room Fan 1" oscillation_output: gpio_19 direction_output: gpio_26 - platform: speed @@ -2048,7 +2067,7 @@ fan: icon: mdi:weather-windy output: pca_6 speed_count: 10 - name: 'Living Room Fan 2' + name: "Living Room Fan 2" oscillation_output: gpio_19 direction_output: gpio_26 oscillation_state_topic: oscillation/state/topic @@ -2084,7 +2103,7 @@ interval: id: display1 page_id: page1 then: - - logger.log: 'Seeing page 1' + - logger.log: "Seeing page 1" color: - id: kbx_red @@ -2145,7 +2164,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1306_i2c - model: 'SSD1306_128X64' + model: "SSD1306_128X64" reset_pin: GPIO23 address: 0x3C id: display1 @@ -2153,6 +2172,7 @@ display: pages: - id: page1 lambda: |- + it.qr_code(0, 0, id(homepage_qr)); it.rectangle(0, 0, it.get_width(), it.get_height()); - id: page2 lambda: |- @@ -2165,28 +2185,28 @@ display: ESP_LOGD("display", "1 -> 2"); i2c_id: i2c_bus - platform: ssd1306_spi - model: 'SSD1306 128x64' + model: "SSD1306 128x64" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1322_spi - model: 'SSD1322 256x64' + model: "SSD1322 256x64" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1325_spi - model: 'SSD1325 128x64' + model: "SSD1325 128x64" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1327_i2c - model: 'SSD1327 128X128' + model: "SSD1327 128X128" reset_pin: GPIO23 address: 0x3D id: display1327 @@ -2200,7 +2220,7 @@ display: // Nothing i2c_id: i2c_bus - platform: ssd1327_spi - model: 'SSD1327 128x128' + model: "SSD1327 128x128" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2213,7 +2233,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: ssd1351_spi - model: 'SSD1351 128x128' + model: "SSD1351 128x128" cs_pin: GPIO23 dc_pin: GPIO23 reset_pin: GPIO23 @@ -2235,7 +2255,7 @@ display: lambda: |- it.rectangle(0, 0, it.get_width(), it.get_height()); - platform: st7735 - model: 'INITR_BLACKTAB' + model: "INITR_BLACKTAB" cs_pin: GPIO5 dc_pin: GPIO16 reset_pin: GPIO23 @@ -2293,13 +2313,13 @@ pn532_spi: ESP_LOGD("main", "Found tag %s", x.c_str()); - mqtt.publish: topic: the/topic - payload: !lambda 'return x;' + payload: !lambda "return x;" on_tag_removed: - lambda: |- ESP_LOGD("main", "Removed tag %s", x.c_str()); - mqtt.publish: topic: the/topic - payload: !lambda 'return x;' + payload: !lambda "return x;" pn532_i2c: i2c_id: i2c_bus @@ -2338,7 +2358,7 @@ time: - 1.pool.ntp.org - 192.168.178.1 on_time: - cron: '/30 0-30,30/5 * ? JAN-DEC MON,SAT-SUN,TUE-FRI' + cron: "/30 0-30,30/5 * ? JAN-DEC MON,SAT-SUN,TUE-FRI" then: - lambda: 'ESP_LOGD("main", "time");' - platform: gps @@ -2356,7 +2376,7 @@ time: cover: - platform: template - name: 'Template Cover' + name: "Template Cover" id: template_cover lambda: |- if (id(binary_sensor1).state) { @@ -2373,7 +2393,7 @@ cover: has_position: yes position_state_topic: position/state/topic position_command_topic: position/command/topic - tilt_lambda: !lambda 'return 0.5;' + tilt_lambda: !lambda "return 0.5;" tilt_state_topic: tilt/state/topic tilt_command_topic: tilt/command/topic on_open: @@ -2383,7 +2403,7 @@ cover: then: - lambda: 'ESP_LOGD("cover", "closed");' - platform: am43 - name: 'Test AM43' + name: "Test AM43" id: am43_test ble_client_id: ble_foo icon: mdi:blinds @@ -2402,24 +2422,24 @@ tca9548a: i2c_id: multiplex0_chan0 pcf8574: - - id: 'pcf8574_hub' + - id: "pcf8574_hub" address: 0x21 pcf8575: False i2c_id: i2c_bus mcp23017: - - id: 'mcp23017_hub' - open_drain_interrupt: 'true' + - id: "mcp23017_hub" + open_drain_interrupt: "true" i2c_id: i2c_bus mcp23008: - - id: 'mcp23008_hub' + - id: "mcp23008_hub" address: 0x22 - open_drain_interrupt: 'true' + open_drain_interrupt: "true" i2c_id: i2c_bus mcp23016: - - id: 'mcp23016_hub' + - id: "mcp23016_hub" address: 0x23 i2c_id: i2c_bus @@ -2437,15 +2457,15 @@ globals: - id: glob_int type: int restore_value: yes - initial_value: '0' + initial_value: "0" - id: glob_float type: float restore_value: yes - initial_value: '0.0f' + initial_value: "0.0f" - id: glob_bool type: bool restore_value: no - initial_value: 'true' + initial_value: "true" - id: glob_string type: std::string restore_value: no @@ -2453,12 +2473,12 @@ globals: - id: glob_bool_processed type: bool restore_value: no - initial_value: 'false' + initial_value: "false" text_sensor: - platform: mqtt_subscribe - name: 'MQTT Subscribe Text' - topic: 'the/topic' + name: "MQTT Subscribe Text" + topic: "the/topic" qos: 2 on_value: - text_sensor.template.publish: @@ -2470,7 +2490,7 @@ text_sensor: return "Hello World2"; - globals.set: id: glob_int - value: '0' + value: "0" - canbus.send: canbus_id: mcp2515_can can_id: 23 @@ -2484,17 +2504,17 @@ text_sensor: id: ${textname}_text - platform: wifi_info scan_results: - name: 'Scan Results' + name: "Scan Results" ip_address: - name: 'IP Address' + name: "IP Address" ssid: - name: 'SSID' + name: "SSID" bssid: - name: 'BSSID' + name: "BSSID" mac_address: - name: 'Mac Address' + name: "Mac Address" - platform: version - name: 'ESPHome Version No Timestamp' + name: "ESPHome Version No Timestamp" hide_timestamp: True - platform: teleinfo tag_name: "OPTARIF" @@ -2502,7 +2522,7 @@ text_sensor: teleinfo_id: myteleinfo sn74hc595: - - id: 'sn74hc595_hub' + - id: "sn74hc595_hub" data_pin: GPIO21 clock_pin: GPIO23 latch_pin: GPIO22 @@ -2528,7 +2548,7 @@ canbus: then: - if: condition: - lambda: 'return x[0] == 0x11;' + lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights - platform: esp32_can @@ -2547,7 +2567,7 @@ canbus: then: - if: condition: - lambda: 'return x[0] == 0x11;' + lambda: "return x[0] == 0x11;" then: light.toggle: ${roomname}_lights @@ -2576,3 +2596,32 @@ select: - one - two optimistic: true + +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html + +lock: + - platform: template + id: test_lock1 + name: "Template Switch" + lambda: |- + if (id(binary_sensor1).state) { + return LOCK_STATE_LOCKED; + }else{ + return LOCK_STATE_UNLOCKED; + } + optimistic: true + assumed_state: no + on_unlock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_UNLOCKED;" + on_lock: + - lock.template.publish: + id: test_lock1 + state: !lambda "return LOCK_STATE_LOCKED;" + - platform: output + name: "Generic Output Lock" + id: test_lock2 + output: pca_6 diff --git a/tests/test2.yaml b/tests/test2.yaml index 7920bf3fe39d..2a122b971f4d 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -227,6 +227,14 @@ sensor: name: 'JQJCY01YM Formaldehyde' battery_level: name: 'JQJCY01YM Battery Level' + - platform: xiaomi_mhoc303 + mac_address: 'E7:50:59:32:A0:1C' + temperature: + name: 'MHO-C303 Temperature' + humidity: + name: 'MHO-C303 Humidity' + battery_level: + name: 'MHO-C303 Battery Level' - platform: atc_mithermometer mac_address: 'A4:C1:38:4E:16:78' temperature: @@ -316,6 +324,13 @@ sensor: bus_voltage: name: "INA260 Voltage" update_interval: 60s + - platform: radon_eye_rd200 + ble_client_id: radon_eye_ble_id + update_interval: 10min + radon: + name: "RD200 Radon" + radon_long_term: + name: "RD200 Radon Long Term" time: - platform: homeassistant @@ -415,10 +430,14 @@ ble_client: id: airthings01 - mac_address: 01:02:03:04:05:06 id: airthingsmini01 + - mac_address: 01:02:03:04:05:06 + id: radon_eye_ble_id airthings_ble: +radon_eye_ble: + ruuvi_ble: xiaomi_ble: diff --git a/tests/test3.yaml b/tests/test3.yaml index 607d985704f2..853d7bd38985 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -692,6 +692,20 @@ sensor: name: 'testwave' component_id: 2 wave_channel_id: 1 + - platform: mlx90393 + oversampling: 1 + filter: 0 + gain: "3X" + x_axis: + name: "mlxxaxis" + y_axis: + name: "mlxyaxis" + z_axis: + name: "mlxzaxis" + resolution: 17BIT + temperature: + name: "mlxtemp" + oversampling: 2 time: - platform: homeassistant @@ -1148,6 +1162,7 @@ output: pin: GPIO5 id: my_slow_pwm period: 15s + restart_cycle_on_state_change: false - platform: sm2135 id: sm2135_0 channel: 0 @@ -1350,6 +1365,20 @@ daly_bms: update_interval: 20s uart_id: uart1 +qr_code: + - id: homepage_qr + value: https://esphome.io/index.html + +button: + - platform: output + id: output_button + output: out + duration: 100ms + - platform: wake_on_lan + target_mac_address: 12:34:56:78:90:ab + name: wol_test_1 + id: wol_1 + cd74hc4067: pin_s0: GPIO12 pin_s1: GPIO13 diff --git a/tests/test4.yaml b/tests/test4.yaml index eec1c2eb5ee6..998db8ed2dd2 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -313,7 +313,7 @@ binary_sensor: then: - lambda: 'ESP_LOGI("ar1:", "%d", x);' - platform: xpt2046 - xpt2046_id: touchscreen + xpt2046_id: xpt_touchscreen id: touch_key0 x_min: 80 x_max: 160 @@ -327,6 +327,17 @@ binary_sensor: sx1509: sx1509_hub number: 3 + - platform: touchscreen + touchscreen_id: lilygo_touchscreen + id: touch_key1 + x_min: 0 + x_max: 100 + y_min: 0 + y_max: 100 + on_press: + - logger.log: "Touched" + + climate: - platform: tuya id: tuya_climate @@ -509,7 +520,7 @@ external_components: - source: ../esphome/components components: ["sntp"] xpt2046: - id: touchscreen + id: xpt_touchscreen cs_pin: 17 irq_pin: 16 update_interval: 50ms @@ -526,12 +537,12 @@ xpt2046: - lambda: |- ESP_LOGI("main", "args x=%d, y=%d, touched=%s", x, y, (touched ? "touch" : "release")); ESP_LOGI("main", "member x=%d, y=%d, touched=%d, x_raw=%d, y_raw=%d, z_raw=%d", - id(touchscreen).x, - id(touchscreen).y, - (int) id(touchscreen).touched, - id(touchscreen).x_raw, - id(touchscreen).y_raw, - id(touchscreen).z_raw + id(xpt_touchscreen).x, + id(xpt_touchscreen).y, + (int) id(xpt_touchscreen).touched, + id(xpt_touchscreen).x_raw, + id(xpt_touchscreen).y_raw, + id(xpt_touchscreen).z_raw ); button: @@ -541,3 +552,22 @@ button: name: Safe Mode Button - platform: shutdown name: Shutdown Button + +touchscreen: + - platform: ektf2232 + interrupt_pin: GPIO36 + rts_pin: GPIO5 + display: inkplate_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: ["touch.x", "touch.y"] + + - platform: lilygo_t5_47 + id: lilygo_touchscreen + interrupt_pin: GPIO36 + display: inkplate_display + on_touch: + - logger.log: + format: Touch at (%d, %d) + args: ["touch.x", "touch.y"] diff --git a/tests/test5.yaml b/tests/test5.yaml index d6acbf1e652e..9bfd395538fc 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -14,8 +14,8 @@ esp32: wifi: networks: - - ssid: 'MySSID' - password: 'password1' + - ssid: "MySSID" + password: "password1" manual_ip: static_ip: 192.168.1.23 gateway: 192.168.1.1 @@ -39,7 +39,6 @@ uart: i2c: - modbus: uart_id: uart1 flow_control_pin: 5 @@ -50,13 +49,20 @@ modbus_controller: address: 0x2 modbus_id: mod_bus1 - binary_sensor: - platform: gpio pin: GPIO0 id: io0_button icon: mdi:gesture-tap-button + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_binsensortest + register_type: read + address: 0x3200 + bitmask: 0x80 #(bit 8) + lambda: !lambda "{ return x ;}" + tlc5947: data_pin: GPIO12 clock_pin: GPIO14 @@ -75,6 +81,14 @@ output: - platform: mcp47a1 id: output_mcp47a1 + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_output_test + lambda: |- + return x * 1.0 ; + address: 0x9001 + value_type: U_WORD + demo: esp32_ble: @@ -104,9 +118,20 @@ number: max_value: 100 min_value: 0 step: 5 - unit_of_measurement: '%' + unit_of_measurement: "%" mode: slider + - id: modbus_numbertest + platform: modbus_controller + modbus_controller_id: modbus_controller_test + name: "ModbusNumber" + address: 0x9002 + value_type: U_WORD + lambda: "return x * 1.0; " + write_lambda: |- + return x * 1.0 ; + multiply: 1.0 + select: - platform: template name: My template select @@ -170,8 +195,7 @@ sensor: name: "SelecEM2M Maximum Demand Apparent Power" disabled_by_default: true - - id: battery_voltage - name: "Battery voltage2" + - id: modbus_sensortest platform: modbus_controller modbus_controller_id: modbus_controller_test address: 0x331A @@ -199,3 +223,11 @@ script: count: 5 then: - logger.log: "looping!" + +switch: + - platform: modbus_controller + modbus_controller_id: modbus_controller_test + id: modbus_switch_test + register_type: coil + address: 2 + bitmask: 1 diff --git a/tests/unit_tests/test_helpers.py b/tests/unit_tests/test_helpers.py index 00a6b08133d2..f883b8b44fed 100644 --- a/tests/unit_tests/test_helpers.py +++ b/tests/unit_tests/test_helpers.py @@ -124,13 +124,13 @@ def test_get_bool_env(monkeypatch, var, value, default, expected): @pytest.mark.parametrize("value, expected", ((None, False), ("Yes", True))) -def test_is_hassio(monkeypatch, value, expected): +def test_is_ha_addon(monkeypatch, value, expected): if value is None: - monkeypatch.delenv("ESPHOME_IS_HASSIO", raising=False) + monkeypatch.delenv("ESPHOME_IS_HA_ADDON", raising=False) else: - monkeypatch.setenv("ESPHOME_IS_HASSIO", value) + monkeypatch.setenv("ESPHOME_IS_HA_ADDON", value) - actual = helpers.is_hassio() + actual = helpers.is_ha_addon() assert actual == expected