diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index aa90ef365f..25411c19f5 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -16,6 +16,7 @@ Quick description and explanation of changes
## Test Environment
- [ ] ESP32
+- [ ] ESP32 IDF
- [ ] ESP8266
## Example entry for `config.yaml`:
diff --git a/.github/issue-close-app.yml b/.github/issue-close-app.yml
deleted file mode 100644
index 5f5fb7572d..0000000000
--- a/.github/issue-close-app.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-comment: >-
- https://github.com/esphome/esphome/issues/430
-issueConfigs:
-- content:
- - "OTHERWISE THE ISSUE WILL BE CLOSED AUTOMATICALLY"
-
-caseInsensitive: false
diff --git a/.github/lock.yml b/.github/lock.yml
deleted file mode 100644
index 0680577b2e..0000000000
--- a/.github/lock.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-# Configuration for Lock Threads - https://github.com/dessant/lock-threads
-
-# Number of days of inactivity before a closed issue or pull request is locked
-daysUntilLock: 7
-
-# Skip issues and pull requests created before a given timestamp. Timestamp must
-# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
-skipCreatedBefore: false
-
-# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
-exemptLabels:
- - keep-open
-
-# Label to add before locking, such as `outdated`. Set to `false` to disable
-lockLabel: false
-
-# Comment to post before locking. Set to `false` to disable
-lockComment: false
-
-# Assign `resolved` as the reason for locking. Set to `false` to disable
-setLockReason: false
-
-# Limit to only `issues` or `pulls`
-# only: issues
-
-# Optionally, specify configuration settings just for `issues` or `pulls`
-# issues:
-# exemptLabels:
-# - help-wanted
-# lockLabel: outdated
-
-# pulls:
-# daysUntilLock: 30
-
-# Repository to extend settings from
-# _extends: repo
diff --git a/.github/workflows/ci-docker.yml b/.github/workflows/ci-docker.yml
index 12f5a7dfc2..1d1cc169b2 100644
--- a/.github/workflows/ci-docker.yml
+++ b/.github/workflows/ci-docker.yml
@@ -17,6 +17,10 @@ on:
- 'requirements*.txt'
- 'platformio.ini'
+permissions:
+ contents: read
+ packages: read
+
jobs:
check-docker:
name: Build docker containers
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 45e2f2735c..02b64d2bf5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,6 +8,9 @@ on:
pull_request:
+permissions:
+ contents: read
+
jobs:
ci:
name: ${{ matrix.name }}
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
index 375b8f1db4..ceb45b2a91 100644
--- a/.github/workflows/lock.yml
+++ b/.github/workflows/lock.yml
@@ -9,13 +9,19 @@ permissions:
issues: write
pull-requests: write
+concurrency:
+ group: lock
+
jobs:
lock:
runs-on: ubuntu-latest
steps:
- - uses: dessant/lock-threads@v2
+ - uses: dessant/lock-threads@v3
with:
- github-token: ${{ github.token }}
- pr-lock-inactive-days: "1"
+ pr-inactive-days: "1"
pr-lock-reason: ""
- process-only: prs
+ exclude-any-pr-labels: keep-open
+
+ issue-inactive-days: "7"
+ issue-lock-reason: ""
+ exclude-any-issue-labels: keep-open
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index afd893d065..d6895becc0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -7,6 +7,9 @@ on:
schedule:
- cron: "0 2 * * *"
+permissions:
+ contents: read
+
jobs:
init:
name: Initialize build
@@ -52,6 +55,9 @@ jobs:
deploy-docker:
name: Build and publish docker containers
if: github.repository == 'esphome/esphome'
+ permissions:
+ contents: read
+ packages: write
runs-on: ubuntu-latest
needs: [init]
strategy:
@@ -93,6 +99,9 @@ jobs:
deploy-docker-manifest:
if: github.repository == 'esphome/esphome'
+ permissions:
+ contents: read
+ packages: write
runs-on: ubuntu-latest
needs: [init, deploy-docker]
strategy:
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 712ae1a289..c3e450d0cf 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -9,13 +9,15 @@ permissions:
issues: write
pull-requests: write
+concurrency:
+ group: lock
+
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v4
with:
- repo-token: ${{ github.token }}
days-before-pr-stale: 90
days-before-pr-close: 7
days-before-issue-stale: -1
@@ -28,3 +30,19 @@ jobs:
pull request has been automatically marked as stale because of that
and will be closed if no further activity occurs within 7 days.
Thank you for your contributions.
+
+ # Use stale to automatically close issues with a reference to the issue tracker
+ close-issues:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/stale@v4
+ with:
+ days-before-pr-stale: -1
+ days-before-pr-close: -1
+ days-before-issue-stale: 1
+ days-before-issue-close: 1
+ remove-stale-when-updated: true
+ stale-issue-label: "stale"
+ exempt-issue-labels: "not-stale"
+ stale-issue-message: >
+ https://github.com/esphome/esphome/issues/430
diff --git a/CODEOWNERS b/CODEOWNERS
index a7cf3a1b68..18b4564280 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -31,6 +31,7 @@ esphome/components/binary_sensor/* @esphome/core
esphome/components/ble_client/* @buxtronix
esphome/components/bme680_bsec/* @trvrnrth
esphome/components/canbus/* @danielschramm @mvturnho
+esphome/components/cap1188/* @MrEditor97
esphome/components/captive_portal/* @OttoWinter
esphome/components/ccs811/* @habbie
esphome/components/climate/* @esphome/core
@@ -39,6 +40,7 @@ esphome/components/color_temperature/* @jesserockz
esphome/components/coolix/* @glmnet
esphome/components/cover/* @esphome/core
esphome/components/cs5460a/* @balrog-kun
+esphome/components/cse7761/* @berfenger
esphome/components/ct_clamp/* @jesserockz
esphome/components/current_based/* @djwmarcx
esphome/components/daly_bms/* @s1lvi0
@@ -51,6 +53,7 @@ esphome/components/dsmr/* @glmnet @zuidwijk
esphome/components/esp32/* @esphome/core
esphome/components/esp32_ble/* @jesserockz
esphome/components/esp32_ble_server/* @jesserockz
+esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_improv/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/exposure_notifications/* @OttoWinter
@@ -70,6 +73,7 @@ esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey
esphome/components/i2c/* @esphome/core
esphome/components/improv/* @jesserockz
+esphome/components/improv_serial/* @esphome/core
esphome/components/inkbird_ibsth1_mini/* @fkirill
esphome/components/inkplate6/* @jesserockz
esphome/components/integration/* @OttoWinter
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 7928ff8901..0d30bb0267 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -5,12 +5,12 @@
# One of "docker", "hassio"
ARG BASEIMGTYPE=docker
-FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.0 AS base-hassio-amd64
-FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.0 AS base-hassio-arm64
-FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.0 AS base-hassio-armv7
-FROM debian:bullseye-20210902-slim AS base-docker-amd64
-FROM debian:bullseye-20210902-slim AS base-docker-arm64
-FROM debian:bullseye-20210902-slim AS base-docker-armv7
+FROM ghcr.io/hassio-addons/debian-base/amd64:5.1.1 AS base-hassio-amd64
+FROM ghcr.io/hassio-addons/debian-base/aarch64:5.1.1 AS base-hassio-arm64
+FROM ghcr.io/hassio-addons/debian-base/armv7:5.1.1 AS base-hassio-armv7
+FROM debian:bullseye-20211011-slim AS base-docker-amd64
+FROM debian:bullseye-20211011-slim AS base-docker-arm64
+FROM debian:bullseye-20211011-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
@@ -43,7 +43,7 @@ RUN \
# Ubuntu python3-pip is missing wheel
pip3 install --no-cache-dir \
wheel==0.36.2 \
- platformio==5.2.1 \
+ platformio==5.2.2 \
# Change some platformio settings
&& platformio settings set enable_telemetry No \
&& platformio settings set check_libraries_interval 1000000 \
diff --git a/esphome/automation.py b/esphome/automation.py
index 0768bf8869..fab998527f 100644
--- a/esphome/automation.py
+++ b/esphome/automation.py
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.const import (
CONF_AUTOMATION_ID,
CONF_CONDITION,
+ CONF_COUNT,
CONF_ELSE,
CONF_ID,
CONF_THEN,
@@ -66,6 +67,7 @@ DelayAction = cg.esphome_ns.class_("DelayAction", Action, cg.Component)
LambdaAction = cg.esphome_ns.class_("LambdaAction", Action)
IfAction = cg.esphome_ns.class_("IfAction", Action)
WhileAction = cg.esphome_ns.class_("WhileAction", Action)
+RepeatAction = cg.esphome_ns.class_("RepeatAction", Action)
WaitUntilAction = cg.esphome_ns.class_("WaitUntilAction", Action, cg.Component)
UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation")
@@ -241,6 +243,25 @@ async def while_action_to_code(config, action_id, template_arg, args):
return var
+@register_action(
+ "repeat",
+ RepeatAction,
+ cv.Schema(
+ {
+ cv.Required(CONF_COUNT): cv.templatable(cv.positive_not_null_int),
+ cv.Required(CONF_THEN): validate_action_list,
+ }
+ ),
+)
+async def repeat_action_to_code(config, action_id, template_arg, args):
+ var = cg.new_Pvariable(action_id, template_arg)
+ count_template = await cg.templatable(config[CONF_COUNT], args, cg.uint32)
+ cg.add(var.set_count(count_template))
+ actions = await build_action_list(config[CONF_THEN], template_arg, args)
+ cg.add(var.add_then(actions))
+ return var
+
+
def validate_wait_until(value):
schema = cv.Schema(
{
diff --git a/esphome/codegen.py b/esphome/codegen.py
index 4f9f67245d..5e1e934e58 100644
--- a/esphome/codegen.py
+++ b/esphome/codegen.py
@@ -81,4 +81,5 @@ from esphome.cpp_types import ( # noqa
GPIOPin,
InternalGPIOPin,
gpio_Flags,
+ EntityCategory,
)
diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp
index c8f8b0e0f6..c8242ce008 100644
--- a/esphome/components/adc/adc_sensor.cpp
+++ b/esphome/components/adc/adc_sensor.cpp
@@ -1,5 +1,6 @@
#include "adc_sensor.h"
#include "esphome/core/log.h"
+#include "esphome/core/helpers.h"
#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC
@@ -15,50 +16,6 @@ namespace adc {
static const char *const TAG = "adc";
-#ifdef USE_ESP32
-void ADCSensor::set_attenuation(adc_atten_t attenuation) { this->attenuation_ = attenuation; }
-
-inline adc1_channel_t gpio_to_adc1(uint8_t pin) {
-#if CONFIG_IDF_TARGET_ESP32
- switch (pin) {
- case 36:
- return ADC1_CHANNEL_0;
- case 37:
- return ADC1_CHANNEL_1;
- case 38:
- return ADC1_CHANNEL_2;
- case 39:
- return ADC1_CHANNEL_3;
- case 32:
- return ADC1_CHANNEL_4;
- case 33:
- return ADC1_CHANNEL_5;
- case 34:
- return ADC1_CHANNEL_6;
- case 35:
- return ADC1_CHANNEL_7;
- default:
- return ADC1_CHANNEL_MAX;
- }
-#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
- switch (pin) {
- case 0:
- return ADC1_CHANNEL_0;
- case 1:
- return ADC1_CHANNEL_1;
- case 2:
- return ADC1_CHANNEL_2;
- case 3:
- return ADC1_CHANNEL_3;
- case 4:
- return ADC1_CHANNEL_4;
- default:
- return ADC1_CHANNEL_MAX;
- }
-#endif
-}
-#endif
-
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
@@ -66,13 +23,36 @@ void ADCSensor::setup() {
#endif
#ifdef USE_ESP32
- adc1_config_channel_atten(gpio_to_adc1(pin_->get_pin()), attenuation_);
adc1_config_width(ADC_WIDTH_BIT_12);
-#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2
- adc_gpio_init(ADC_UNIT_1, (adc_channel_t) gpio_to_adc1(pin_->get_pin()));
-#endif
+ 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,
+ 1100, // default vref
+ &cal_characteristics_[i]);
+ switch (cal_value) {
+ case ESP_ADC_CAL_VAL_EFUSE_VREF:
+ ESP_LOGV(TAG, "Using eFuse Vref for calibration");
+ break;
+ case ESP_ADC_CAL_VAL_EFUSE_TP:
+ ESP_LOGV(TAG, "Using two-point eFuse Vref for calibration");
+ break;
+ case ESP_ADC_CAL_VAL_DEFAULT_VREF:
+ default:
+ break;
+ }
+ }
+
+ // adc_gpio_init doesn't exist on ESP32-C3 or ESP32-H2
+#if !defined(USE_ESP32_VARIANT_ESP32C3) && !defined(USE_ESP32_VARIANT_ESP32H2)
+ adc_gpio_init(ADC_UNIT_1, (adc_channel_t) channel_);
#endif
+#endif // USE_ESP32
}
+
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ESP8266
@@ -81,84 +61,107 @@ void ADCSensor::dump_config() {
#else
LOG_PIN(" Pin: ", pin_);
#endif
-#endif
+#endif // USE_ESP8266
+
#ifdef USE_ESP32
LOG_PIN(" Pin: ", pin_);
- switch (this->attenuation_) {
- case ADC_ATTEN_DB_0:
- ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
- break;
- case ADC_ATTEN_DB_2_5:
- ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
- break;
- case ADC_ATTEN_DB_6:
- ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
- break;
- case ADC_ATTEN_DB_11:
- ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
- break;
- default: // This is to satisfy the unused ADC_ATTEN_MAX
- break;
- }
-#endif
+ if (autorange_)
+ ESP_LOGCONFIG(TAG, " Attenuation: auto");
+ else
+ switch (this->attenuation_) {
+ case ADC_ATTEN_DB_0:
+ ESP_LOGCONFIG(TAG, " Attenuation: 0db (max 1.1V)");
+ break;
+ case ADC_ATTEN_DB_2_5:
+ ESP_LOGCONFIG(TAG, " Attenuation: 2.5db (max 1.5V)");
+ break;
+ case ADC_ATTEN_DB_6:
+ ESP_LOGCONFIG(TAG, " Attenuation: 6db (max 2.2V)");
+ break;
+ case ADC_ATTEN_DB_11:
+ ESP_LOGCONFIG(TAG, " Attenuation: 11db (max 3.9V)");
+ break;
+ default: // This is to satisfy the unused ADC_ATTEN_MAX
+ break;
+ }
+#endif // USE_ESP32
LOG_UPDATE_INTERVAL(this);
}
+
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() {
float value_v = this->sample();
- ESP_LOGD(TAG, "'%s': Got voltage=%.2fV", this->get_name().c_str(), value_v);
+ ESP_LOGD(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
-float ADCSensor::sample() {
-#ifdef USE_ESP32
- int raw = adc1_get_raw(gpio_to_adc1(pin_->get_pin()));
- float value_v = raw / 4095.0f;
-#if CONFIG_IDF_TARGET_ESP32
- switch (this->attenuation_) {
- case ADC_ATTEN_DB_0:
- value_v *= 1.1;
- break;
- case ADC_ATTEN_DB_2_5:
- value_v *= 1.5;
- break;
- case ADC_ATTEN_DB_6:
- value_v *= 2.2;
- break;
- case ADC_ATTEN_DB_11:
- value_v *= 3.9;
- break;
- default: // This is to satisfy the unused ADC_ATTEN_MAX
- break;
- }
-#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2
- switch (this->attenuation_) {
- case ADC_ATTEN_DB_0:
- value_v *= 0.84;
- break;
- case ADC_ATTEN_DB_2_5:
- value_v *= 1.13;
- break;
- case ADC_ATTEN_DB_6:
- value_v *= 1.56;
- break;
- case ADC_ATTEN_DB_11:
- value_v *= 3.0;
- break;
- default: // This is to satisfy the unused ADC_ATTEN_MAX
- break;
- }
-#endif
- return value_v;
-#endif
#ifdef USE_ESP8266
+float ADCSensor::sample() {
#ifdef USE_ADC_SENSOR_VCC
- return ESP.getVcc() / 1024.0f; // NOLINT(readability-static-accessed-through-instance)
+ int raw = ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else
- return analogRead(this->pin_->get_pin()) / 1024.0f; // NOLINT
-#endif
+ int raw = analogRead(this->pin_->get_pin()); // NOLINT
#endif
+ if (output_raw_) {
+ return raw;
+ }
+ return raw / 1024.0f;
}
+#endif
+
+#ifdef USE_ESP32
+float ADCSensor::sample() {
+ if (!autorange_) {
+ int raw = adc1_get_raw(channel_);
+ if (raw == -1) {
+ return NAN;
+ }
+ if (output_raw_) {
+ return raw;
+ }
+ uint32_t mv = esp_adc_cal_raw_to_voltage(raw, &cal_characteristics_[(int) attenuation_]);
+ return mv / 1000.0f;
+ }
+
+ int raw11, raw6 = 4095, raw2 = 4095, raw0 = 4095;
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_11);
+ raw11 = adc1_get_raw(channel_);
+ if (raw11 < 4095) {
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_6);
+ raw6 = adc1_get_raw(channel_);
+ if (raw6 < 4095) {
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_2_5);
+ raw2 = adc1_get_raw(channel_);
+ if (raw2 < 4095) {
+ adc1_config_channel_atten(channel_, ADC_ATTEN_DB_0);
+ raw0 = adc1_get_raw(channel_);
+ }
+ }
+ }
+
+ if (raw0 == -1 || raw2 == -1 || raw6 == -1 || raw11 == -1) {
+ return NAN;
+ }
+
+ uint32_t mv11 = esp_adc_cal_raw_to_voltage(raw11, &cal_characteristics_[(int) ADC_ATTEN_DB_11]);
+ uint32_t mv6 = esp_adc_cal_raw_to_voltage(raw6, &cal_characteristics_[(int) ADC_ATTEN_DB_6]);
+ uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &cal_characteristics_[(int) ADC_ATTEN_DB_2_5]);
+ uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &cal_characteristics_[(int) ADC_ATTEN_DB_0]);
+
+ // Contribution of each value, in range 0-2048
+ uint32_t c11 = std::min(raw11, 2048);
+ uint32_t c6 = 2048 - std::abs(raw6 - 2048);
+ uint32_t c2 = 2048 - std::abs(raw2 - 2048);
+ uint32_t c0 = std::min(4095 - raw0, 2048);
+ // max theoretical csum value is 2048*4 = 8192
+ uint32_t csum = c11 + c6 + c2 + c0;
+
+ // each mv is max 3900; so max value is 3900*2048*4, fits in unsigned
+ uint32_t mv_scaled = (mv11 * c11) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
+ return mv_scaled / (float) (csum * 1000U);
+}
+#endif // USE_ESP32
+
#ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif
diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h
index b8c702be4e..12272a1577 100644
--- a/esphome/components/adc/adc_sensor.h
+++ b/esphome/components/adc/adc_sensor.h
@@ -8,6 +8,7 @@
#ifdef USE_ESP32
#include "driver/adc.h"
+#include
#endif
namespace esphome {
@@ -17,7 +18,9 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
public:
#ifdef USE_ESP32
/// Set the attenuation for this pin. Only available on the ESP32.
- void set_attenuation(adc_atten_t attenuation);
+ void set_attenuation(adc_atten_t attenuation) { attenuation_ = attenuation; }
+ void set_channel(adc1_channel_t channel) { channel_ = channel; }
+ void set_autorange(bool autorange) { autorange_ = autorange; }
#endif
/// Update adc values.
@@ -28,6 +31,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
/// `HARDWARE_LATE` setup priority.
float get_setup_priority() const override;
void set_pin(InternalGPIOPin *pin) { this->pin_ = pin; }
+ void set_output_raw(bool output_raw) { output_raw_ = output_raw; }
float sample() override;
#ifdef USE_ESP8266
@@ -36,9 +40,13 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
protected:
InternalGPIOPin *pin_;
+ bool output_raw_{false};
#ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
+ adc1_channel_t channel_{};
+ bool autorange_{false};
+ esp_adc_cal_characteristics_t cal_characteristics_[(int) ADC_ATTEN_MAX] = {};
#endif
};
diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py
index 26ef504c1a..c812e67a68 100644
--- a/esphome/components/adc/sensor.py
+++ b/esphome/components/adc/sensor.py
@@ -4,14 +4,24 @@ from esphome import pins
from esphome.components import sensor, voltage_sampler
from esphome.const import (
CONF_ATTENUATION,
+ CONF_RAW,
CONF_ID,
CONF_INPUT,
+ CONF_NUMBER,
CONF_PIN,
DEVICE_CLASS_VOLTAGE,
STATE_CLASS_MEASUREMENT,
UNIT_VOLT,
)
from esphome.core import CORE
+from esphome.components.esp32 import get_esp32_variant
+from esphome.components.esp32.const import (
+ VARIANT_ESP32,
+ VARIANT_ESP32C3,
+ VARIANT_ESP32H2,
+ VARIANT_ESP32S2,
+ VARIANT_ESP32S3,
+)
AUTO_LOAD = ["voltage_sampler"]
@@ -21,6 +31,62 @@ ATTENUATION_MODES = {
"2.5db": cg.global_ns.ADC_ATTEN_DB_2_5,
"6db": cg.global_ns.ADC_ATTEN_DB_6,
"11db": cg.global_ns.ADC_ATTEN_DB_11,
+ "auto": "auto",
+}
+
+adc1_channel_t = cg.global_ns.enum("adc1_channel_t")
+
+# From https://github.com/espressif/esp-idf/blob/master/components/driver/include/driver/adc_common.h
+# pin to adc1 channel mapping
+ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
+ VARIANT_ESP32: {
+ 36: adc1_channel_t.ADC1_CHANNEL_0,
+ 37: adc1_channel_t.ADC1_CHANNEL_1,
+ 38: adc1_channel_t.ADC1_CHANNEL_2,
+ 39: adc1_channel_t.ADC1_CHANNEL_3,
+ 32: adc1_channel_t.ADC1_CHANNEL_4,
+ 33: adc1_channel_t.ADC1_CHANNEL_5,
+ 34: adc1_channel_t.ADC1_CHANNEL_6,
+ 35: adc1_channel_t.ADC1_CHANNEL_7,
+ },
+ VARIANT_ESP32S2: {
+ 1: adc1_channel_t.ADC1_CHANNEL_0,
+ 2: adc1_channel_t.ADC1_CHANNEL_1,
+ 3: adc1_channel_t.ADC1_CHANNEL_2,
+ 4: adc1_channel_t.ADC1_CHANNEL_3,
+ 5: adc1_channel_t.ADC1_CHANNEL_4,
+ 6: adc1_channel_t.ADC1_CHANNEL_5,
+ 7: adc1_channel_t.ADC1_CHANNEL_6,
+ 8: adc1_channel_t.ADC1_CHANNEL_7,
+ 9: adc1_channel_t.ADC1_CHANNEL_8,
+ 10: adc1_channel_t.ADC1_CHANNEL_9,
+ },
+ VARIANT_ESP32S3: {
+ 1: adc1_channel_t.ADC1_CHANNEL_0,
+ 2: adc1_channel_t.ADC1_CHANNEL_1,
+ 3: adc1_channel_t.ADC1_CHANNEL_2,
+ 4: adc1_channel_t.ADC1_CHANNEL_3,
+ 5: adc1_channel_t.ADC1_CHANNEL_4,
+ 6: adc1_channel_t.ADC1_CHANNEL_5,
+ 7: adc1_channel_t.ADC1_CHANNEL_6,
+ 8: adc1_channel_t.ADC1_CHANNEL_7,
+ 9: adc1_channel_t.ADC1_CHANNEL_8,
+ 10: adc1_channel_t.ADC1_CHANNEL_9,
+ },
+ VARIANT_ESP32C3: {
+ 0: adc1_channel_t.ADC1_CHANNEL_0,
+ 1: adc1_channel_t.ADC1_CHANNEL_1,
+ 2: adc1_channel_t.ADC1_CHANNEL_2,
+ 3: adc1_channel_t.ADC1_CHANNEL_3,
+ 4: adc1_channel_t.ADC1_CHANNEL_4,
+ },
+ VARIANT_ESP32H2: {
+ 0: adc1_channel_t.ADC1_CHANNEL_0,
+ 1: adc1_channel_t.ADC1_CHANNEL_1,
+ 2: adc1_channel_t.ADC1_CHANNEL_2,
+ 3: adc1_channel_t.ADC1_CHANNEL_3,
+ 4: adc1_channel_t.ADC1_CHANNEL_4,
+ },
}
@@ -29,15 +95,16 @@ def validate_adc_pin(value):
return cv.only_on_esp8266("VCC")
if CORE.is_esp32:
- from esphome.components.esp32 import is_esp32c3
-
value = pins.internal_gpio_input_pin_number(value)
- if is_esp32c3():
- if not (0 <= value <= 4): # ADC1
- raise cv.Invalid("ESP32-C3: Only pins 0 though 4 support ADC.")
- elif not (32 <= value <= 39): # ADC1
- raise cv.Invalid("ESP32: Only pins 32 though 39 support ADC.")
- elif CORE.is_esp8266:
+ variant = get_esp32_variant()
+ if variant not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL:
+ raise cv.Invalid(f"This ESP32 variant ({variant}) is not supported")
+
+ if value not in ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant]:
+ raise cv.Invalid(f"{variant} doesn't support ADC on this pin")
+ return pins.internal_gpio_input_pin_schema(value)
+
+ if CORE.is_esp8266:
from esphome.components.esp8266.gpio import CONF_ANALOG
value = pins.internal_gpio_pin_number({CONF_ANALOG: True, CONF_INPUT: True})(
@@ -49,10 +116,14 @@ def validate_adc_pin(value):
return pins.gpio_pin_schema(
{CONF_ANALOG: True, CONF_INPUT: True}, internal=True
)(value)
- else:
- raise NotImplementedError
- return pins.internal_gpio_input_pin_schema(value)
+ raise NotImplementedError
+
+
+def validate_config(config):
+ if config[CONF_RAW] and config.get(CONF_ATTENUATION, None) == "auto":
+ raise cv.Invalid("Automatic attenuation cannot be used when raw output is set.")
+ return config
adc_ns = cg.esphome_ns.namespace("adc")
@@ -60,7 +131,7 @@ ADCSensor = adc_ns.class_(
"ADCSensor", sensor.Sensor, cg.PollingComponent, voltage_sampler.VoltageSampler
)
-CONFIG_SCHEMA = (
+CONFIG_SCHEMA = cv.All(
sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=2,
@@ -71,12 +142,14 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(ADCSensor),
cv.Required(CONF_PIN): validate_adc_pin,
+ cv.Optional(CONF_RAW, default=False): cv.boolean,
cv.SplitDefault(CONF_ATTENUATION, esp32="0db"): cv.All(
cv.only_on_esp32, cv.enum(ATTENUATION_MODES, lower=True)
),
}
)
- .extend(cv.polling_component_schema("60s"))
+ .extend(cv.polling_component_schema("60s")),
+ validate_config,
)
@@ -91,5 +164,17 @@ async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN])
cg.add(var.set_pin(pin))
+ if CONF_RAW in config:
+ cg.add(var.set_output_raw(config[CONF_RAW]))
+
if CONF_ATTENUATION in config:
- cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
+ if config[CONF_ATTENUATION] == "auto":
+ cg.add(var.set_autorange(cg.global_ns.true))
+ else:
+ cg.add(var.set_attenuation(config[CONF_ATTENUATION]))
+
+ if CORE.is_esp32:
+ variant = get_esp32_variant()
+ pin_num = config[CONF_PIN][CONF_NUMBER]
+ chan = ESP32_VARIANT_ADC1_PIN_TO_CHANNEL[variant][pin_num]
+ cg.add(var.set_channel(chan))
diff --git a/esphome/components/aht10/aht10.cpp b/esphome/components/aht10/aht10.cpp
index 713199212c..e5e04ac181 100644
--- a/esphome/components/aht10/aht10.cpp
+++ b/esphome/components/aht10/aht10.cpp
@@ -73,7 +73,7 @@ void AHT10Component::update() {
bool success = false;
for (int i = 0; i < AHT10_ATTEMPTS; ++i) {
ESP_LOGVV(TAG, "Attempt %d at %6u", i, millis());
- delay_microseconds_accurate(4);
+ delayMicroseconds(4);
uint8_t reg = 0;
if (this->write(®, 1) != i2c::ERROR_OK) {
diff --git a/esphome/components/am43/sensor.py b/esphome/components/am43/sensor.py
index c88e529a0c..68c85d0e9c 100644
--- a/esphome/components/am43/sensor.py
+++ b/esphome/components/am43/sensor.py
@@ -4,7 +4,8 @@ from esphome.components import sensor, ble_client
from esphome.const import (
CONF_ID,
CONF_BATTERY_LEVEL,
- ICON_BATTERY,
+ DEVICE_CLASS_BATTERY,
+ ENTITY_CATEGORY_DIAGNOSTIC,
CONF_ILLUMINANCE,
ICON_BRIGHTNESS_5,
UNIT_PERCENT,
@@ -20,10 +21,15 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(Am43),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
- UNIT_PERCENT, ICON_BATTERY, 0
+ unit_of_measurement=UNIT_PERCENT,
+ device_class=DEVICE_CLASS_BATTERY,
+ accuracy_decimals=0,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
- UNIT_PERCENT, ICON_BRIGHTNESS_5, 0
+ unit_of_measurement=UNIT_PERCENT,
+ icon=ICON_BRIGHTNESS_5,
+ accuracy_decimals=0,
),
}
)
diff --git a/esphome/components/anova/anova_base.cpp b/esphome/components/anova/anova_base.cpp
index 811a34a27a..d55404089e 100644
--- a/esphome/components/anova/anova_base.cpp
+++ b/esphome/components/anova/anova_base.cpp
@@ -103,21 +103,21 @@ void AnovaCodec::decode(const uint8_t *data, uint16_t length) {
break;
}
case READ_TARGET_TEMPERATURE: {
- this->target_temp_ = strtof(this->buf_, nullptr);
+ this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case SET_TARGET_TEMPERATURE: {
- this->target_temp_ = strtof(this->buf_, nullptr);
+ this->target_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
if (this->fahrenheit_)
this->target_temp_ = ftoc(this->target_temp_);
this->has_target_temp_ = true;
break;
}
case READ_CURRENT_TEMPERATURE: {
- this->current_temp_ = strtof(this->buf_, nullptr);
+ this->current_temp_ = parse_number(this->buf_, sizeof(this->buf_)).value_or(0.0f);
if (this->fahrenheit_)
this->current_temp_ = ftoc(this->current_temp_);
this->has_current_temp_ = true;
diff --git a/esphome/components/api/api.proto b/esphome/components/api/api.proto
index 5a6eba004c..0eb7ead735 100644
--- a/esphome/components/api/api.proto
+++ b/esphome/components/api/api.proto
@@ -182,6 +182,8 @@ message DeviceInfoResponse {
// The esphome project details if set
string project_name = 8;
string project_version = 9;
+
+ uint32 webserver_port = 10;
}
message ListEntitiesRequest {
@@ -201,6 +203,14 @@ message SubscribeStatesRequest {
// Empty
}
+// ==================== COMMON =====================
+
+enum EntityCategory {
+ ENTITY_CATEGORY_NONE = 0;
+ ENTITY_CATEGORY_CONFIG = 1;
+ ENTITY_CATEGORY_DIAGNOSTIC = 2;
+}
+
// ==================== BINARY SENSOR ====================
message ListEntitiesBinarySensorResponse {
option (id) = 12;
@@ -216,6 +226,7 @@ message ListEntitiesBinarySensorResponse {
bool is_status_binary_sensor = 6;
bool disabled_by_default = 7;
string icon = 8;
+ EntityCategory entity_category = 9;
}
message BinarySensorStateResponse {
option (id) = 21;
@@ -247,6 +258,7 @@ message ListEntitiesCoverResponse {
string device_class = 8;
bool disabled_by_default = 9;
string icon = 10;
+ EntityCategory entity_category = 11;
}
enum LegacyCoverState {
@@ -316,6 +328,7 @@ message ListEntitiesFanResponse {
int32 supported_speed_count = 8;
bool disabled_by_default = 9;
string icon = 10;
+ EntityCategory entity_category = 11;
}
enum FanSpeed {
FAN_SPEED_LOW = 0;
@@ -392,6 +405,7 @@ message ListEntitiesLightResponse {
repeated string effects = 11;
bool disabled_by_default = 13;
string icon = 14;
+ EntityCategory entity_category = 15;
}
message LightStateResponse {
option (id) = 24;
@@ -480,6 +494,7 @@ message ListEntitiesSensorResponse {
// Last reset type removed in 2021.9.0
SensorLastResetType legacy_last_reset_type = 11;
bool disabled_by_default = 12;
+ EntityCategory entity_category = 13;
}
message SensorStateResponse {
option (id) = 25;
@@ -508,6 +523,7 @@ message ListEntitiesSwitchResponse {
string icon = 5;
bool assumed_state = 6;
bool disabled_by_default = 7;
+ EntityCategory entity_category = 8;
}
message SwitchStateResponse {
option (id) = 26;
@@ -541,6 +557,7 @@ message ListEntitiesTextSensorResponse {
string icon = 5;
bool disabled_by_default = 6;
+ EntityCategory entity_category = 7;
}
message TextSensorStateResponse {
option (id) = 27;
@@ -701,6 +718,8 @@ message ListEntitiesCameraResponse {
string name = 3;
string unique_id = 4;
bool disabled_by_default = 5;
+ string icon = 6;
+ EntityCategory entity_category = 7;
}
message CameraImageResponse {
@@ -795,6 +814,7 @@ message ListEntitiesClimateResponse {
repeated string supported_custom_presets = 17;
bool disabled_by_default = 18;
string icon = 19;
+ EntityCategory entity_category = 20;
}
message ClimateStateResponse {
option (id) = 47;
@@ -863,6 +883,7 @@ message ListEntitiesNumberResponse {
float max_value = 7;
float step = 8;
bool disabled_by_default = 9;
+ EntityCategory entity_category = 10;
}
message NumberStateResponse {
option (id) = 50;
@@ -900,6 +921,7 @@ message ListEntitiesSelectResponse {
string icon = 5;
repeated string options = 6;
bool disabled_by_default = 7;
+ EntityCategory entity_category = 8;
}
message SelectStateResponse {
option (id) = 53;
diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp
index c87ccf4dc0..715a4f48c1 100644
--- a/esphome/components/api/api_connection.cpp
+++ b/esphome/components/api/api_connection.cpp
@@ -184,6 +184,7 @@ bool APIConnection::send_binary_sensor_info(binary_sensor::BinarySensor *binary_
msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor();
msg.disabled_by_default = binary_sensor->is_disabled_by_default();
msg.icon = binary_sensor->get_icon();
+ msg.entity_category = static_cast(binary_sensor->get_entity_category());
return this->send_list_entities_binary_sensor_response(msg);
}
#endif
@@ -217,6 +218,7 @@ bool APIConnection::send_cover_info(cover::Cover *cover) {
msg.device_class = cover->get_device_class();
msg.disabled_by_default = cover->is_disabled_by_default();
msg.icon = cover->get_icon();
+ msg.entity_category = static_cast(cover->get_entity_category());
return this->send_list_entities_cover_response(msg);
}
void APIConnection::cover_command(const CoverCommandRequest &msg) {
@@ -283,6 +285,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.supported_speed_count = traits.supported_speed_count();
msg.disabled_by_default = fan->is_disabled_by_default();
msg.icon = fan->get_icon();
+ msg.entity_category = static_cast(fan->get_entity_category());
return this->send_list_entities_fan_response(msg);
}
void APIConnection::fan_command(const FanCommandRequest &msg) {
@@ -346,6 +349,7 @@ bool APIConnection::send_light_info(light::LightState *light) {
msg.disabled_by_default = light->is_disabled_by_default();
msg.icon = light->get_icon();
+ msg.entity_category = static_cast(light->get_entity_category());
for (auto mode : traits.get_supported_color_modes())
msg.supported_color_modes.push_back(static_cast(mode));
@@ -432,7 +436,7 @@ bool APIConnection::send_sensor_info(sensor::Sensor *sensor) {
msg.device_class = sensor->get_device_class();
msg.state_class = static_cast(sensor->get_state_class());
msg.disabled_by_default = sensor->is_disabled_by_default();
-
+ msg.entity_category = static_cast(sensor->get_entity_category());
return this->send_list_entities_sensor_response(msg);
}
#endif
@@ -456,6 +460,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
msg.icon = a_switch->get_icon();
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());
return this->send_list_entities_switch_response(msg);
}
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
@@ -491,6 +496,7 @@ bool APIConnection::send_text_sensor_info(text_sensor::TextSensor *text_sensor)
msg.unique_id = get_default_unique_id("text_sensor", text_sensor);
msg.icon = text_sensor->get_icon();
msg.disabled_by_default = text_sensor->is_disabled_by_default();
+ msg.entity_category = static_cast(text_sensor->get_entity_category());
return this->send_list_entities_text_sensor_response(msg);
}
#endif
@@ -537,6 +543,7 @@ bool APIConnection::send_climate_info(climate::Climate *climate) {
msg.disabled_by_default = climate->is_disabled_by_default();
msg.icon = climate->get_icon();
+ msg.entity_category = static_cast(climate->get_entity_category());
msg.supports_current_temperature = traits.get_supports_current_temperature();
msg.supports_two_point_target_temperature = traits.get_supports_two_point_target_temperature();
@@ -611,6 +618,7 @@ bool APIConnection::send_number_info(number::Number *number) {
msg.unique_id = get_default_unique_id("number", number);
msg.icon = number->get_icon();
msg.disabled_by_default = number->is_disabled_by_default();
+ msg.entity_category = static_cast(number->get_entity_category());
msg.min_value = number->traits.get_min_value();
msg.max_value = number->traits.get_max_value();
@@ -648,6 +656,7 @@ bool APIConnection::send_select_info(select::Select *select) {
msg.unique_id = get_default_unique_id("select", select);
msg.icon = select->get_icon();
msg.disabled_by_default = select->is_disabled_by_default();
+ msg.entity_category = static_cast(select->get_entity_category());
for (const auto &option : select->traits.get_options())
msg.options.push_back(option);
@@ -680,6 +689,8 @@ bool APIConnection::send_camera_info(esp32_camera::ESP32Camera *camera) {
msg.name = camera->get_name();
msg.unique_id = get_default_unique_id("camera", camera);
msg.disabled_by_default = camera->is_disabled_by_default();
+ msg.icon = camera->get_icon();
+ msg.entity_category = static_cast(camera->get_entity_category());
return this->send_list_entities_camera_response(msg);
}
void APIConnection::camera_image(const CameraImageRequest &msg) {
@@ -758,6 +769,9 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
#ifdef ESPHOME_PROJECT_NAME
resp.project_name = ESPHOME_PROJECT_NAME;
resp.project_version = ESPHOME_PROJECT_VERSION;
+#endif
+#ifdef USE_WEBSERVER
+ resp.webserver_port = WEBSERVER_PORT;
#endif
return resp;
}
diff --git a/esphome/components/api/api_pb2.cpp b/esphome/components/api/api_pb2.cpp
index 6a87238186..7bfa1e9edb 100644
--- a/esphome/components/api/api_pb2.cpp
+++ b/esphome/components/api/api_pb2.cpp
@@ -6,6 +6,18 @@
namespace esphome {
namespace api {
+template<> const char *proto_enum_to_string(enums::EntityCategory value) {
+ switch (value) {
+ case enums::ENTITY_CATEGORY_NONE:
+ return "ENTITY_CATEGORY_NONE";
+ case enums::ENTITY_CATEGORY_CONFIG:
+ return "ENTITY_CATEGORY_CONFIG";
+ case enums::ENTITY_CATEGORY_DIAGNOSTIC:
+ return "ENTITY_CATEGORY_DIAGNOSTIC";
+ default:
+ return "UNKNOWN";
+ }
+}
template<> const char *proto_enum_to_string(enums::LegacyCoverState value) {
switch (value) {
case enums::LEGACY_COVER_STATE_OPEN:
@@ -396,6 +408,10 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->has_deep_sleep = value.as_bool();
return true;
}
+ case 10: {
+ this->webserver_port = value.as_uint32();
+ return true;
+ }
default:
return false;
}
@@ -444,6 +460,7 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(7, this->has_deep_sleep);
buffer.encode_string(8, this->project_name);
buffer.encode_string(9, this->project_version);
+ buffer.encode_uint32(10, this->webserver_port);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void DeviceInfoResponse::dump_to(std::string &out) const {
@@ -484,6 +501,11 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append(" project_version: ");
out.append("'").append(this->project_version).append("'");
out.append("\n");
+
+ out.append(" webserver_port: ");
+ sprintf(buffer, "%u", this->webserver_port);
+ out.append(buffer);
+ out.append("\n");
out.append("}");
}
#endif
@@ -509,6 +531,10 @@ bool ListEntitiesBinarySensorResponse::decode_varint(uint32_t field_id, ProtoVar
this->disabled_by_default = value.as_bool();
return true;
}
+ case 9: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -558,6 +584,7 @@ void ListEntitiesBinarySensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(6, this->is_status_binary_sensor);
buffer.encode_bool(7, this->disabled_by_default);
buffer.encode_string(8, this->icon);
+ buffer.encode_enum(9, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
@@ -595,6 +622,10 @@ void ListEntitiesBinarySensorResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
#endif
@@ -664,6 +695,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool();
return true;
}
+ case 11: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -715,6 +750,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
+ buffer.encode_enum(11, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
@@ -760,6 +796,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
#endif
@@ -948,6 +988,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->disabled_by_default = value.as_bool();
return true;
}
+ case 11: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -995,6 +1039,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_int32(8, this->supported_speed_count);
buffer.encode_bool(9, this->disabled_by_default);
buffer.encode_string(10, this->icon);
+ buffer.encode_enum(11, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesFanResponse::dump_to(std::string &out) const {
@@ -1041,6 +1086,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
#endif
@@ -1267,6 +1316,10 @@ bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt val
this->disabled_by_default = value.as_bool();
return true;
}
+ case 15: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -1334,6 +1387,7 @@ void ListEntitiesLightResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_bool(13, this->disabled_by_default);
buffer.encode_string(14, this->icon);
+ buffer.encode_enum(15, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesLightResponse::dump_to(std::string &out) const {
@@ -1401,6 +1455,10 @@ void ListEntitiesLightResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
#endif
@@ -1860,6 +1918,10 @@ bool ListEntitiesSensorResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
+ case 13: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -1917,6 +1979,7 @@ void ListEntitiesSensorResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum(10, this->state_class);
buffer.encode_enum(11, this->legacy_last_reset_type);
buffer.encode_bool(12, this->disabled_by_default);
+ buffer.encode_enum(13, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSensorResponse::dump_to(std::string &out) const {
@@ -1971,6 +2034,10 @@ void ListEntitiesSensorResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -2033,6 +2100,10 @@ bool ListEntitiesSwitchResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
+ case 8: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -2077,6 +2148,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->assumed_state);
buffer.encode_bool(7, this->disabled_by_default);
+ buffer.encode_enum(8, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
@@ -2110,6 +2182,10 @@ void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -2197,6 +2273,10 @@ bool ListEntitiesTextSensorResponse::decode_varint(uint32_t field_id, ProtoVarIn
this->disabled_by_default = value.as_bool();
return true;
}
+ case 7: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -2240,6 +2320,7 @@ void ListEntitiesTextSensorResponse::encode(ProtoWriteBuffer buffer) const {
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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
@@ -2269,6 +2350,10 @@ void ListEntitiesTextSensorResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -2892,6 +2977,10 @@ bool ListEntitiesCameraResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
+ case 7: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -2910,6 +2999,10 @@ bool ListEntitiesCameraResponse::decode_length(uint32_t field_id, ProtoLengthDel
this->unique_id = value.as_string();
return true;
}
+ case 6: {
+ this->icon = value.as_string();
+ return true;
+ }
default:
return false;
}
@@ -2930,6 +3023,8 @@ void ListEntitiesCameraResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->disabled_by_default);
+ buffer.encode_string(6, this->icon);
+ buffer.encode_enum(7, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesCameraResponse::dump_to(std::string &out) const {
@@ -2955,6 +3050,14 @@ void ListEntitiesCameraResponse::dump_to(std::string &out) const {
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
+
+ out.append(" icon: ");
+ out.append("'").append(this->icon).append("'");
+ out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
#endif
@@ -3082,6 +3185,10 @@ bool ListEntitiesClimateResponse::decode_varint(uint32_t field_id, ProtoVarInt v
this->disabled_by_default = value.as_bool();
return true;
}
+ case 20: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -3170,6 +3277,7 @@ void ListEntitiesClimateResponse::encode(ProtoWriteBuffer buffer) const {
}
buffer.encode_bool(18, this->disabled_by_default);
buffer.encode_string(19, this->icon);
+ buffer.encode_enum(20, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesClimateResponse::dump_to(std::string &out) const {
@@ -3266,6 +3374,10 @@ void ListEntitiesClimateResponse::dump_to(std::string &out) const {
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
+
+ out.append(" entity_category: ");
+ out.append(proto_enum_to_string(this->entity_category));
+ out.append("\n");
out.append("}");
}
#endif
@@ -3642,6 +3754,10 @@ bool ListEntitiesNumberResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
+ case 10: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -3700,6 +3816,7 @@ void ListEntitiesNumberResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_float(7, this->max_value);
buffer.encode_float(8, this->step);
buffer.encode_bool(9, this->disabled_by_default);
+ buffer.encode_enum(10, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesNumberResponse::dump_to(std::string &out) const {
@@ -3744,6 +3861,10 @@ void ListEntitiesNumberResponse::dump_to(std::string &out) const {
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("}");
}
#endif
@@ -3836,6 +3957,10 @@ bool ListEntitiesSelectResponse::decode_varint(uint32_t field_id, ProtoVarInt va
this->disabled_by_default = value.as_bool();
return true;
}
+ case 8: {
+ this->entity_category = value.as_enum();
+ return true;
+ }
default:
return false;
}
@@ -3886,6 +4011,7 @@ void ListEntitiesSelectResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(6, it, true);
}
buffer.encode_bool(7, this->disabled_by_default);
+ buffer.encode_enum(8, this->entity_category);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesSelectResponse::dump_to(std::string &out) const {
@@ -3921,6 +4047,10 @@ void ListEntitiesSelectResponse::dump_to(std::string &out) const {
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("}");
}
#endif
diff --git a/esphome/components/api/api_pb2.h b/esphome/components/api/api_pb2.h
index 13a21c4772..ec11732c7d 100644
--- a/esphome/components/api/api_pb2.h
+++ b/esphome/components/api/api_pb2.h
@@ -9,6 +9,11 @@ namespace api {
namespace enums {
+enum EntityCategory : uint32_t {
+ ENTITY_CATEGORY_NONE = 0,
+ ENTITY_CATEGORY_CONFIG = 1,
+ ENTITY_CATEGORY_DIAGNOSTIC = 2,
+};
enum LegacyCoverState : uint32_t {
LEGACY_COVER_STATE_OPEN = 0,
LEGACY_COVER_STATE_CLOSED = 1,
@@ -224,6 +229,7 @@ class DeviceInfoResponse : public ProtoMessage {
bool has_deep_sleep{false};
std::string project_name{};
std::string project_version{};
+ uint32_t webserver_port{0};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -270,6 +276,7 @@ class ListEntitiesBinarySensorResponse : public ProtoMessage {
bool is_status_binary_sensor{false};
bool disabled_by_default{false};
std::string icon{};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -306,6 +313,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
std::string device_class{};
bool disabled_by_default{false};
std::string icon{};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -363,6 +371,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
int32_t supported_speed_count{0};
bool disabled_by_default{false};
std::string icon{};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -428,6 +437,7 @@ class ListEntitiesLightResponse : public ProtoMessage {
std::vector effects{};
bool disabled_by_default{false};
std::string icon{};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -516,6 +526,7 @@ class ListEntitiesSensorResponse : public ProtoMessage {
enums::SensorStateClass state_class{};
enums::SensorLastResetType legacy_last_reset_type{};
bool disabled_by_default{false};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -549,6 +560,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
std::string icon{};
bool assumed_state{false};
bool disabled_by_default{false};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -593,6 +605,7 @@ class ListEntitiesTextSensorResponse : public ProtoMessage {
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -803,6 +816,8 @@ class ListEntitiesCameraResponse : public ProtoMessage {
std::string name{};
std::string unique_id{};
bool disabled_by_default{false};
+ std::string icon{};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -861,6 +876,7 @@ class ListEntitiesClimateResponse : public ProtoMessage {
std::vector supported_custom_presets{};
bool disabled_by_default{false};
std::string icon{};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -940,6 +956,7 @@ class ListEntitiesNumberResponse : public ProtoMessage {
float max_value{0.0f};
float step{0.0f};
bool disabled_by_default{false};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
@@ -985,6 +1002,7 @@ class ListEntitiesSelectResponse : public ProtoMessage {
std::string icon{};
std::vector options{};
bool disabled_by_default{false};
+ enums::EntityCategory entity_category{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp
index 4e2899d94f..25081a809a 100644
--- a/esphome/components/api/api_server.cpp
+++ b/esphome/components/api/api_server.cpp
@@ -77,7 +77,7 @@ void APIServer::setup() {
this->last_connected_ = millis();
#ifdef USE_ESP32_CAMERA
- if (esp32_camera::global_esp32_camera != nullptr) {
+ 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_)
diff --git a/esphome/components/api/client.py b/esphome/components/api/client.py
index 4a3944d33e..b2920f239b 100644
--- a/esphome/components/api/client.py
+++ b/esphome/components/api/client.py
@@ -23,7 +23,6 @@ async def async_run_logs(config, address):
_LOGGER.info("Starting log output from %s using esphome API", address)
zc = zeroconf.Zeroconf()
cli = APIClient(
- asyncio.get_event_loop(),
address,
port,
password,
diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h
index 8a72765195..26269bcae4 100644
--- a/esphome/components/api/homeassistant_service.h
+++ b/esphome/components/api/homeassistant_service.h
@@ -8,6 +8,18 @@
namespace esphome {
namespace api {
+template class TemplatableStringValue : public TemplatableValue {
+ public:
+ TemplatableStringValue() : TemplatableValue() {}
+
+ template::value, int> = 0>
+ TemplatableStringValue(F value) : TemplatableValue(value) {}
+
+ template::value, int> = 0>
+ TemplatableStringValue(F f)
+ : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {}
+};
+
template class TemplatableKeyValuePair {
public:
template TemplatableKeyValuePair(std::string key, T value) : key(std::move(key)), value(value) {}
@@ -19,7 +31,8 @@ template class HomeAssistantServiceCallAction : public Action void set_service(T service) { this->service_ = service; }
+
template void add_data(std::string key, T value) {
this->data_.push_back(TemplatableKeyValuePair(key, value));
}
@@ -58,6 +71,7 @@ template class HomeAssistantServiceCallAction : public Action service_{};
std::vector> data_;
std::vector> data_template_;
std::vector> variables_;
diff --git a/esphome/components/atc_mithermometer/sensor.py b/esphome/components/atc_mithermometer/sensor.py
index 0f6cc1abcb..bde83c28b6 100644
--- a/esphome/components/atc_mithermometer/sensor.py
+++ b/esphome/components/atc_mithermometer/sensor.py
@@ -12,6 +12,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -49,12 +50,14 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/atm90e32/sensor.py b/esphome/components/atm90e32/sensor.py
index 05e5250d89..9c876bb62c 100644
--- a/esphome/components/atm90e32/sensor.py
+++ b/esphome/components/atm90e32/sensor.py
@@ -17,6 +17,7 @@ from esphome.const import (
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
ICON_LIGHTBULB,
ICON_CURRENT_AC,
STATE_CLASS_MEASUREMENT,
@@ -125,6 +126,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True),
cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum(
diff --git a/esphome/components/b_parasite/sensor.py b/esphome/components/b_parasite/sensor.py
index d51c48c602..201685adc4 100644
--- a/esphome/components/b_parasite/sensor.py
+++ b/esphome/components/b_parasite/sensor.py
@@ -13,6 +13,7 @@ from esphome.const import (
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_LUX,
@@ -51,6 +52,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
diff --git a/esphome/components/bh1750/bh1750.cpp b/esphome/components/bh1750/bh1750.cpp
index 951fe3670c..4e6bb3c563 100644
--- a/esphome/components/bh1750/bh1750.cpp
+++ b/esphome/components/bh1750/bh1750.cpp
@@ -79,6 +79,9 @@ void BH1750Sensor::read_data_() {
float lx = float(raw_value) / 1.2f;
lx *= 69.0f / this->measurement_duration_;
+ if (this->resolution_ == BH1750_RESOLUTION_0P5_LX) {
+ lx /= 2.0f;
+ }
ESP_LOGD(TAG, "'%s': Got illuminance=%.1flx", this->get_name().c_str(), lx);
this->publish_state(lx);
this->status_clear_warning();
diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py
index ec199cc5fa..faafcddd06 100644
--- a/esphome/components/binary_sensor/__init__.py
+++ b/esphome/components/binary_sensor/__init__.py
@@ -313,6 +313,7 @@ def validate_multi_click_timing(value):
device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
+
BINARY_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(BinarySensor),
diff --git a/esphome/components/bme680_bsec/__init__.py b/esphome/components/bme680_bsec/__init__.py
index 2f844fa666..83e519f8aa 100644
--- a/esphome/components/bme680_bsec/__init__.py
+++ b/esphome/components/bme680_bsec/__init__.py
@@ -2,7 +2,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID
-from esphome.core import CORE
CODEOWNERS = ["@trvrnrth"]
DEPENDENCIES = ["i2c"]
@@ -62,9 +61,8 @@ async def to_code(config):
var.set_state_save_interval(config[CONF_STATE_SAVE_INTERVAL].total_milliseconds)
)
- if CORE.is_esp32:
- # Although this component does not use SPI, the BSEC library requires the SPI library
- cg.add_library("SPI", None)
+ # Although this component does not use SPI, the BSEC library requires the SPI library
+ cg.add_library("SPI", None)
cg.add_define("USE_BSEC")
cg.add_library("boschsensortec/BSEC Software Library", "1.6.1480")
diff --git a/esphome/components/cap1188/__init__.py b/esphome/components/cap1188/__init__.py
new file mode 100644
index 0000000000..80794c5146
--- /dev/null
+++ b/esphome/components/cap1188/__init__.py
@@ -0,0 +1,45 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import i2c
+from esphome.const import CONF_ID, CONF_RESET_PIN
+from esphome import pins
+
+CONF_TOUCH_THRESHOLD = "touch_threshold"
+CONF_ALLOW_MULTIPLE_TOUCHES = "allow_multiple_touches"
+
+DEPENDENCIES = ["i2c"]
+AUTO_LOAD = ["binary_sensor", "output"]
+CODEOWNERS = ["@MrEditor97"]
+
+cap1188_ns = cg.esphome_ns.namespace("cap1188")
+CONF_CAP1188_ID = "cap1188_id"
+CAP1188Component = cap1188_ns.class_("CAP1188Component", cg.Component, i2c.I2CDevice)
+
+MULTI_CONF = True
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(CAP1188Component),
+ cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
+ cv.Optional(CONF_TOUCH_THRESHOLD, default=0x20): cv.int_range(
+ min=0x01, max=0x80
+ ),
+ cv.Optional(CONF_ALLOW_MULTIPLE_TOUCHES, default=False): cv.boolean,
+ }
+ )
+ .extend(cv.COMPONENT_SCHEMA)
+ .extend(i2c.i2c_device_schema(0x29))
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
+ cg.add(var.set_allow_multiple_touches(config[CONF_ALLOW_MULTIPLE_TOUCHES]))
+
+ if CONF_RESET_PIN in config:
+ pin = await cg.gpio_pin_expression(config[CONF_RESET_PIN])
+ cg.add(var.set_reset_pin(pin))
+
+ await cg.register_component(var, config)
+ await i2c.register_i2c_device(var, config)
diff --git a/esphome/components/cap1188/binary_sensor.py b/esphome/components/cap1188/binary_sensor.py
new file mode 100644
index 0000000000..c249eb7330
--- /dev/null
+++ b/esphome/components/cap1188/binary_sensor.py
@@ -0,0 +1,25 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import binary_sensor
+from esphome.const import CONF_CHANNEL, CONF_ID
+from . import cap1188_ns, CAP1188Component, CONF_CAP1188_ID
+
+DEPENDENCIES = ["cap1188"]
+CAP1188Channel = cap1188_ns.class_("CAP1188Channel", binary_sensor.BinarySensor)
+
+CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
+ {
+ cv.GenerateID(): cv.declare_id(CAP1188Channel),
+ cv.GenerateID(CONF_CAP1188_ID): cv.use_id(CAP1188Component),
+ cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=7),
+ }
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await binary_sensor.register_binary_sensor(var, config)
+ hub = await cg.get_variable(config[CONF_CAP1188_ID])
+ cg.add(var.set_channel(config[CONF_CHANNEL]))
+
+ cg.add(hub.register_channel(var))
diff --git a/esphome/components/cap1188/cap1188.cpp b/esphome/components/cap1188/cap1188.cpp
new file mode 100644
index 0000000000..10d8325537
--- /dev/null
+++ b/esphome/components/cap1188/cap1188.cpp
@@ -0,0 +1,88 @@
+#include "cap1188.h"
+#include "esphome/core/log.h"
+#include "esphome/core/hal.h"
+
+namespace esphome {
+namespace cap1188 {
+
+static const char *const TAG = "cap1188";
+
+void CAP1188Component::setup() {
+ ESP_LOGCONFIG(TAG, "Setting up CAP1188...");
+
+ // Reset device using the reset pin
+ if (this->reset_pin_ != nullptr) {
+ this->reset_pin_->setup();
+ this->reset_pin_->digital_write(false);
+ delay(100); // NOLINT
+ this->reset_pin_->digital_write(true);
+ delay(100); // NOLINT
+ this->reset_pin_->digital_write(false);
+ delay(100); // NOLINT
+ }
+
+ // Check if CAP1188 is actually connected
+ this->read_byte(CAP1188_PRODUCT_ID, &this->cap1188_product_id_);
+ this->read_byte(CAP1188_MANUFACTURE_ID, &this->cap1188_manufacture_id_);
+ this->read_byte(CAP1188_REVISION, &this->cap1188_revision_);
+
+ if ((this->cap1188_product_id_ != 0x50) || (this->cap1188_manufacture_id_ != 0x5D)) {
+ this->error_code_ = COMMUNICATION_FAILED;
+ this->mark_failed();
+ return;
+ }
+
+ // Set sensitivity
+ uint8_t sensitivity = 0;
+ this->read_byte(CAP1188_SENSITVITY, &sensitivity);
+ sensitivity = sensitivity & 0x0f;
+ this->write_byte(CAP1188_SENSITVITY, sensitivity | this->touch_threshold_);
+
+ // Allow multiple touches
+ this->write_byte(CAP1188_MULTI_TOUCH, this->allow_multiple_touches_);
+
+ // Have LEDs follow touches
+ this->write_byte(CAP1188_LED_LINK, 0xFF);
+
+ // Speed up a bit
+ this->write_byte(CAP1188_STAND_BY_CONFIGURATION, 0x30);
+}
+
+void CAP1188Component::dump_config() {
+ ESP_LOGCONFIG(TAG, "CAP1188:");
+ LOG_I2C_DEVICE(this);
+ LOG_PIN(" Reset Pin: ", this->reset_pin_);
+ ESP_LOGCONFIG(TAG, " Product ID: 0x%x", this->cap1188_product_id_);
+ ESP_LOGCONFIG(TAG, " Manufacture ID: 0x%x", this->cap1188_manufacture_id_);
+ ESP_LOGCONFIG(TAG, " Revision ID: 0x%x", this->cap1188_revision_);
+
+ switch (this->error_code_) {
+ case COMMUNICATION_FAILED:
+ ESP_LOGE(TAG, "Product ID or Manufacture ID of the connected device does not match a known CAP1188.");
+ break;
+ case NONE:
+ default:
+ break;
+ }
+}
+
+void CAP1188Component::loop() {
+ uint8_t touched = 0;
+
+ this->read_register(CAP1188_SENSOR_INPUT_STATUS, &touched, 1);
+
+ if (touched) {
+ uint8_t data = 0;
+ this->read_register(CAP1188_MAIN, &data, 1);
+ data = data & ~CAP1188_MAIN_INT;
+
+ this->write_register(CAP1188_MAIN, &data, 2);
+ }
+
+ for (auto *channel : this->channels_) {
+ channel->process(touched);
+ }
+}
+
+} // namespace cap1188
+} // namespace esphome
diff --git a/esphome/components/cap1188/cap1188.h b/esphome/components/cap1188/cap1188.h
new file mode 100644
index 0000000000..a1433deb0f
--- /dev/null
+++ b/esphome/components/cap1188/cap1188.h
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "esphome/core/hal.h"
+#include "esphome/components/i2c/i2c.h"
+#include "esphome/components/output/binary_output.h"
+#include "esphome/components/binary_sensor/binary_sensor.h"
+
+namespace esphome {
+namespace cap1188 {
+
+enum {
+ CAP1188_I2CADDR = 0x29,
+ CAP1188_SENSOR_INPUT_STATUS = 0x3,
+ CAP1188_MULTI_TOUCH = 0x2A,
+ CAP1188_LED_LINK = 0x72,
+ CAP1188_PRODUCT_ID = 0xFD,
+ CAP1188_MANUFACTURE_ID = 0xFE,
+ CAP1188_STAND_BY_CONFIGURATION = 0x41,
+ CAP1188_REVISION = 0xFF,
+ CAP1188_MAIN = 0x00,
+ CAP1188_MAIN_INT = 0x01,
+ CAP1188_LEDPOL = 0x73,
+ CAP1188_INTERUPT_REPEAT = 0x28,
+ CAP1188_SENSITVITY = 0x1f,
+};
+
+class CAP1188Channel : public binary_sensor::BinarySensor {
+ public:
+ void set_channel(uint8_t channel) { channel_ = channel; }
+ void process(uint8_t data) { this->publish_state(static_cast(data & (1 << this->channel_))); }
+
+ protected:
+ uint8_t channel_{0};
+};
+
+class CAP1188Component : public Component, public i2c::I2CDevice {
+ public:
+ void register_channel(CAP1188Channel *channel) { this->channels_.push_back(channel); }
+ void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
+ void set_allow_multiple_touches(bool allow_multiple_touches) {
+ this->allow_multiple_touches_ = allow_multiple_touches ? 0x41 : 0x80;
+ };
+ void set_reset_pin(GPIOPin *reset_pin) { this->reset_pin_ = reset_pin; }
+ void setup() override;
+ void dump_config() override;
+ float get_setup_priority() const override { return setup_priority::DATA; }
+ void loop() override;
+
+ protected:
+ std::vector channels_{};
+ uint8_t touch_threshold_{0x20};
+ uint8_t allow_multiple_touches_{0x80};
+
+ GPIOPin *reset_pin_{nullptr};
+
+ uint8_t cap1188_product_id_{0};
+ uint8_t cap1188_manufacture_id_{0};
+ uint8_t cap1188_revision_{0};
+
+ enum ErrorCode {
+ NONE = 0,
+ COMMUNICATION_FAILED,
+ } error_code_{NONE};
+};
+
+} // namespace cap1188
+} // namespace esphome
diff --git a/esphome/components/captive_portal/__init__.py b/esphome/components/captive_portal/__init__.py
index 384a3f23a0..f024c94b01 100644
--- a/esphome/components/captive_portal/__init__.py
+++ b/esphome/components/captive_portal/__init__.py
@@ -36,3 +36,5 @@ async def to_code(config):
if CORE.is_esp32:
cg.add_library("DNSServer", None)
cg.add_library("WiFi", None)
+ if CORE.is_esp8266:
+ cg.add_library("DNSServer", None)
diff --git a/esphome/components/captive_portal/captive_portal.cpp b/esphome/components/captive_portal/captive_portal.cpp
index 9e00adae3d..ad4c32bb1f 100644
--- a/esphome/components/captive_portal/captive_portal.cpp
+++ b/esphome/components/captive_portal/captive_portal.cpp
@@ -67,6 +67,7 @@ void CaptivePortal::handle_wifisave(AsyncWebServerRequest *request) {
ESP_LOGI(TAG, " SSID='%s'", ssid.c_str());
ESP_LOGI(TAG, " Password=" LOG_SECRET("'%s'"), psk.c_str());
wifi::global_wifi_component->save_wifi_sta(ssid, psk);
+ wifi::global_wifi_component->start_scanning();
request->redirect("/?save=true");
}
diff --git a/esphome/components/ccs811/ccs811.cpp b/esphome/components/ccs811/ccs811.cpp
index 11a66f5100..f8cee79c55 100644
--- a/esphome/components/ccs811/ccs811.cpp
+++ b/esphome/components/ccs811/ccs811.cpp
@@ -52,7 +52,7 @@ void CCS811Component::setup() {
if (this->baseline_.has_value()) {
// baseline available, write to sensor
- this->write_bytes(0x11, decode_uint16(*this->baseline_));
+ this->write_bytes(0x11, decode_value(*this->baseline_));
}
auto hardware_version_data = this->read_bytes<1>(0x21);
diff --git a/esphome/components/cse7761/__init__.py b/esphome/components/cse7761/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/esphome/components/cse7761/cse7761.cpp b/esphome/components/cse7761/cse7761.cpp
new file mode 100644
index 0000000000..3b8364f0bc
--- /dev/null
+++ b/esphome/components/cse7761/cse7761.cpp
@@ -0,0 +1,244 @@
+#include "cse7761.h"
+
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace cse7761 {
+
+static const char *const TAG = "cse7761";
+
+/*********************************************************************************************\
+ * CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
+ *
+ * Based on Tasmota source code
+ * See https://github.com/arendst/Tasmota/discussions/10793
+ * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
+\*********************************************************************************************/
+
+static const int CSE7761_UREF = 42563; // RmsUc
+static const int CSE7761_IREF = 52241; // RmsIAC
+static const int CSE7761_PREF = 44513; // PowerPAC
+
+static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
+static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
+static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
+static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
+
+static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
+static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
+static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
+static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
+static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
+static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
+
+static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
+static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
+
+static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
+static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
+static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
+static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
+
+enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
+
+void CSE7761Component::setup() {
+ ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
+ this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
+ uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
+ if ((0x0A04 == syscon) && this->chip_init_()) {
+ this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
+ ESP_LOGD(TAG, "CSE7761 found");
+ this->data_.ready = true;
+ } else {
+ this->mark_failed();
+ }
+}
+
+void CSE7761Component::dump_config() {
+ ESP_LOGCONFIG(TAG, "CSE7761:");
+ if (this->is_failed()) {
+ ESP_LOGE(TAG, "Communication with CSE7761 failed!");
+ }
+ LOG_UPDATE_INTERVAL(this);
+ this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
+}
+
+float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
+
+void CSE7761Component::update() {
+ if (this->data_.ready) {
+ this->get_data_();
+ }
+}
+
+void CSE7761Component::write_(uint8_t reg, uint16_t data) {
+ uint8_t buffer[5];
+
+ buffer[0] = 0xA5;
+ buffer[1] = reg;
+ uint32_t len = 2;
+ if (data) {
+ if (data < 0xFF) {
+ buffer[2] = data & 0xFF;
+ len = 3;
+ } else {
+ buffer[2] = (data >> 8) & 0xFF;
+ buffer[3] = data & 0xFF;
+ len = 4;
+ }
+ uint8_t crc = 0;
+ for (uint32_t i = 0; i < len; i++) {
+ crc += buffer[i];
+ }
+ buffer[len] = ~crc;
+ len++;
+ }
+
+ this->write_array(buffer, len);
+}
+
+bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
+ while (this->available()) {
+ this->read();
+ }
+
+ this->write_(reg, 0);
+
+ uint8_t buffer[8] = {0};
+ uint32_t rcvd = 0;
+
+ for (uint32_t i = 0; i <= size; i++) {
+ int value = this->read();
+ if (value > -1 && rcvd < sizeof(buffer) - 1) {
+ buffer[rcvd++] = value;
+ }
+ }
+
+ if (!rcvd) {
+ ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
+ return false;
+ }
+
+ rcvd--;
+ uint32_t result = 0;
+ // CRC check
+ uint8_t crc = 0xA5 + reg;
+ for (uint32_t i = 0; i < rcvd; i++) {
+ result = (result << 8) | buffer[i];
+ crc += buffer[i];
+ }
+ crc = ~crc;
+ if (crc != buffer[rcvd]) {
+ return false;
+ }
+
+ *value = result;
+ return true;
+}
+
+uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
+ bool result = false; // Start loop
+ uint8_t retry = 3; // Retry up to three times
+ uint32_t value = 0; // Default no value
+ while (!result && retry > 0) {
+ retry--;
+ if (this->read_once_(reg, size, &value))
+ return value;
+ }
+ ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
+ return value;
+}
+
+uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
+ switch (unit) {
+ case RMS_UC:
+ return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
+ case RMS_IAC:
+ return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
+ case POWER_PAC:
+ return 0x80000000 / this->data_.coefficient[POWER_PAC];
+ }
+ return 0;
+}
+
+bool CSE7761Component::chip_init_() {
+ uint16_t calc_chksum = 0xFFFF;
+ for (uint32_t i = 0; i < 8; i++) {
+ this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
+ calc_chksum += this->data_.coefficient[i];
+ }
+ calc_chksum = ~calc_chksum;
+ uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
+ if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
+ ESP_LOGD(TAG, "Default calibration");
+ this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
+ this->data_.coefficient[RMS_UC] = CSE7761_UREF;
+ this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
+ }
+
+ this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
+
+ uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
+ if (sys_status & 0x10) { // Write enable to protected registers (WREN)
+ this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
+ this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
+ this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
+ this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
+ } else {
+ ESP_LOGD(TAG, "Write failed at chip_init");
+ return false;
+ }
+ return true;
+}
+
+void CSE7761Component::get_data_() {
+ // The effective value of current and voltage Rms is a 24-bit signed number,
+ // the highest bit is 0 for valid data,
+ // and when the highest bit is 1, the reading will be processed as zero
+ // The active power parameter PowerA/B is in two’s complement format, 32-bit
+ // data, the highest bit is Sign bit.
+ uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
+ this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
+
+ value = this->read_(CSE7761_REG_RMSIA, 3);
+ this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
+ value = this->read_(CSE7761_REG_POWERPA, 4);
+ this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
+
+ value = this->read_(CSE7761_REG_RMSIB, 3);
+ this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
+ value = this->read_(CSE7761_REG_POWERPB, 4);
+ this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
+
+ // convert values and publish to sensors
+
+ float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
+ if (this->voltage_sensor_ != nullptr) {
+ this->voltage_sensor_->publish_state(voltage);
+ }
+
+ for (uint32_t channel = 0; channel < 2; channel++) {
+ // Active power = PowerPA * PowerPAC * 1000 / 0x80000000
+ float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
+ float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
+ ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
+ if (channel == 0) {
+ if (this->power_sensor_1_ != nullptr) {
+ this->power_sensor_1_->publish_state(active_power);
+ }
+ if (this->current_sensor_1_ != nullptr) {
+ this->current_sensor_1_->publish_state(amps);
+ }
+ } else if (channel == 1) {
+ if (this->power_sensor_2_ != nullptr) {
+ this->power_sensor_2_->publish_state(active_power);
+ }
+ if (this->current_sensor_2_ != nullptr) {
+ this->current_sensor_2_->publish_state(amps);
+ }
+ }
+ }
+}
+
+} // namespace cse7761
+} // namespace esphome
diff --git a/esphome/components/cse7761/cse7761.h b/esphome/components/cse7761/cse7761.h
new file mode 100644
index 0000000000..71846cdcab
--- /dev/null
+++ b/esphome/components/cse7761/cse7761.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "esphome/components/sensor/sensor.h"
+#include "esphome/components/uart/uart.h"
+#include "esphome/core/component.h"
+
+namespace esphome {
+namespace cse7761 {
+
+struct CSE7761DataStruct {
+ uint32_t frequency = 0;
+ uint32_t voltage_rms = 0;
+ uint32_t current_rms[2] = {0};
+ uint32_t energy[2] = {0};
+ uint32_t active_power[2] = {0};
+ uint16_t coefficient[8] = {0};
+ uint8_t energy_update = 0;
+ bool ready = false;
+};
+
+/// This class implements support for the CSE7761 UART power sensor.
+class CSE7761Component : public PollingComponent, public uart::UARTDevice {
+ public:
+ void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
+ void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
+ void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
+ void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
+ void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
+ void setup() override;
+ void dump_config() override;
+ float get_setup_priority() const override;
+ void update() override;
+
+ protected:
+ // Sensors
+ sensor::Sensor *voltage_sensor_{nullptr};
+ sensor::Sensor *power_sensor_1_{nullptr};
+ sensor::Sensor *current_sensor_1_{nullptr};
+ sensor::Sensor *power_sensor_2_{nullptr};
+ sensor::Sensor *current_sensor_2_{nullptr};
+ CSE7761DataStruct data_;
+
+ void write_(uint8_t reg, uint16_t data);
+ bool read_once_(uint8_t reg, uint8_t size, uint32_t *value);
+ uint32_t read_(uint8_t reg, uint8_t size);
+ uint32_t coefficient_by_unit_(uint32_t unit);
+ bool chip_init_();
+ void get_data_();
+};
+
+} // namespace cse7761
+} // namespace esphome
diff --git a/esphome/components/cse7761/sensor.py b/esphome/components/cse7761/sensor.py
new file mode 100644
index 0000000000..c5ec3e5b71
--- /dev/null
+++ b/esphome/components/cse7761/sensor.py
@@ -0,0 +1,90 @@
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.components import sensor, uart
+from esphome.const import (
+ CONF_ID,
+ CONF_VOLTAGE,
+ DEVICE_CLASS_CURRENT,
+ DEVICE_CLASS_POWER,
+ DEVICE_CLASS_VOLTAGE,
+ STATE_CLASS_MEASUREMENT,
+ UNIT_VOLT,
+ UNIT_AMPERE,
+ UNIT_WATT,
+)
+
+CODEOWNERS = ["@berfenger"]
+DEPENDENCIES = ["uart"]
+
+cse7761_ns = cg.esphome_ns.namespace("cse7761")
+CSE7761Component = cse7761_ns.class_(
+ "CSE7761Component", cg.PollingComponent, uart.UARTDevice
+)
+
+CONF_CURRENT_1 = "current_1"
+CONF_CURRENT_2 = "current_2"
+CONF_ACTIVE_POWER_1 = "active_power_1"
+CONF_ACTIVE_POWER_2 = "active_power_2"
+
+CONFIG_SCHEMA = (
+ cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(CSE7761Component),
+ cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
+ unit_of_measurement=UNIT_VOLT,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_VOLTAGE,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_CURRENT_1): sensor.sensor_schema(
+ unit_of_measurement=UNIT_AMPERE,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_CURRENT,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_CURRENT_2): sensor.sensor_schema(
+ unit_of_measurement=UNIT_AMPERE,
+ accuracy_decimals=2,
+ device_class=DEVICE_CLASS_CURRENT,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema(
+ unit_of_measurement=UNIT_WATT,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_POWER,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ cv.Optional(CONF_ACTIVE_POWER_2): sensor.sensor_schema(
+ unit_of_measurement=UNIT_WATT,
+ accuracy_decimals=1,
+ device_class=DEVICE_CLASS_POWER,
+ state_class=STATE_CLASS_MEASUREMENT,
+ ),
+ }
+ )
+ .extend(cv.polling_component_schema("60s"))
+ .extend(uart.UART_DEVICE_SCHEMA)
+)
+
+FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
+ "cse7761", baud_rate=38400, require_rx=True, require_tx=True
+)
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
+ await uart.register_uart_device(var, config)
+
+ for key in [
+ CONF_VOLTAGE,
+ CONF_CURRENT_1,
+ CONF_CURRENT_2,
+ CONF_ACTIVE_POWER_1,
+ CONF_ACTIVE_POWER_2,
+ ]:
+ if key not in config:
+ continue
+ conf = config[key]
+ sens = await sensor.new_sensor(conf)
+ cg.add(getattr(var, f"set_{key}_sensor")(sens))
diff --git a/esphome/components/dallas/__init__.py b/esphome/components/dallas/__init__.py
index 2dbc69b8e2..0f71399a7c 100644
--- a/esphome/components/dallas/__init__.py
+++ b/esphome/components/dallas/__init__.py
@@ -6,26 +6,20 @@ from esphome.const import CONF_ID, CONF_PIN
MULTI_CONF = True
AUTO_LOAD = ["sensor"]
-CONF_ONE_WIRE_ID = "one_wire_id"
dallas_ns = cg.esphome_ns.namespace("dallas")
DallasComponent = dallas_ns.class_("DallasComponent", cg.PollingComponent)
-ESPOneWire = dallas_ns.class_("ESPOneWire")
-CONFIG_SCHEMA = cv.All(
- cv.Schema(
- {
- cv.GenerateID(): cv.declare_id(DallasComponent),
- cv.GenerateID(CONF_ONE_WIRE_ID): cv.declare_id(ESPOneWire),
- cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
- }
- ).extend(cv.polling_component_schema("60s")),
- # pin_mode call logs in esp-idf, but InterruptLock is active -> crash
- cv.only_with_arduino,
-)
+CONFIG_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(DallasComponent),
+ cv.Required(CONF_PIN): pins.internal_gpio_output_pin_schema,
+ }
+).extend(cv.polling_component_schema("60s"))
async def to_code(config):
- pin = await cg.gpio_pin_expression(config[CONF_PIN])
- one_wire = cg.new_Pvariable(config[CONF_ONE_WIRE_ID], pin)
- var = cg.new_Pvariable(config[CONF_ID], one_wire)
+ var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
+
+ pin = await cg.gpio_pin_expression(config[CONF_PIN])
+ cg.add(var.set_pin(pin))
diff --git a/esphome/components/dallas/dallas_component.cpp b/esphome/components/dallas/dallas_component.cpp
index 0fc4108687..8d7f2e4deb 100644
--- a/esphome/components/dallas/dallas_component.cpp
+++ b/esphome/components/dallas/dallas_component.cpp
@@ -31,12 +31,11 @@ uint16_t DallasTemperatureSensor::millis_to_wait_for_conversion() const {
void DallasComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up DallasComponent...");
- yield();
+ pin_->setup();
+ one_wire_ = new ESPOneWire(pin_); // NOLINT(cppcoreguidelines-owning-memory)
+
std::vector raw_sensors;
- {
- InterruptLock lock;
- raw_sensors = this->one_wire_->search_vec();
- }
+ raw_sensors = this->one_wire_->search_vec();
for (auto &address : raw_sensors) {
std::string s = uint64_to_string(address);
@@ -70,7 +69,7 @@ void DallasComponent::setup() {
}
void DallasComponent::dump_config() {
ESP_LOGCONFIG(TAG, "DallasComponent:");
- LOG_PIN(" Pin: ", this->one_wire_->get_pin());
+ LOG_PIN(" Pin: ", this->pin_);
LOG_UPDATE_INTERVAL(this);
if (this->found_sensors_.empty()) {
@@ -102,15 +101,12 @@ void DallasComponent::update() {
this->status_clear_warning();
bool result;
- {
- InterruptLock lock;
- if (!this->one_wire_->reset()) {
- result = false;
- } else {
- result = true;
- this->one_wire_->skip();
- this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
- }
+ if (!this->one_wire_->reset()) {
+ result = false;
+ } else {
+ result = true;
+ this->one_wire_->skip();
+ this->one_wire_->write8(DALLAS_COMMAND_START_CONVERSION);
}
if (!result) {
@@ -121,11 +117,7 @@ void DallasComponent::update() {
for (auto *sensor : this->sensors_) {
this->set_timeout(sensor->get_address_name(), sensor->millis_to_wait_for_conversion(), [this, sensor] {
- bool res;
- {
- InterruptLock lock;
- res = sensor->read_scratch_pad();
- }
+ bool res = sensor->read_scratch_pad();
if (!res) {
ESP_LOGW(TAG, "'%s' - Resetting bus for read failed!", sensor->get_name().c_str());
@@ -146,7 +138,6 @@ void DallasComponent::update() {
});
}
}
-DallasComponent::DallasComponent(ESPOneWire *one_wire) : one_wire_(one_wire) {}
void DallasTemperatureSensor::set_address(uint64_t address) { this->address_ = address; }
uint8_t DallasTemperatureSensor::get_resolution() const { return this->resolution_; }
@@ -162,7 +153,7 @@ const std::string &DallasTemperatureSensor::get_address_name() {
return this->address_name_;
}
bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
- ESPOneWire *wire = this->parent_->one_wire_;
+ auto *wire = this->parent_->one_wire_;
if (!wire->reset()) {
return false;
}
@@ -176,11 +167,7 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
return true;
}
bool DallasTemperatureSensor::setup_sensor() {
- bool r;
- {
- InterruptLock lock;
- r = this->read_scratch_pad();
- }
+ bool r = this->read_scratch_pad();
if (!r) {
ESP_LOGE(TAG, "Reading scratchpad failed: reset");
@@ -214,21 +201,18 @@ bool DallasTemperatureSensor::setup_sensor() {
break;
}
- ESPOneWire *wire = this->parent_->one_wire_;
- {
- InterruptLock lock;
- if (wire->reset()) {
- wire->select(this->address_);
- wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
- wire->write8(this->scratch_pad_[2]); // high alarm temp
- wire->write8(this->scratch_pad_[3]); // low alarm temp
- wire->write8(this->scratch_pad_[4]); // resolution
- wire->reset();
+ auto *wire = this->parent_->one_wire_;
+ if (wire->reset()) {
+ wire->select(this->address_);
+ wire->write8(DALLAS_COMMAND_WRITE_SCRATCH_PAD);
+ wire->write8(this->scratch_pad_[2]); // high alarm temp
+ wire->write8(this->scratch_pad_[3]); // low alarm temp
+ wire->write8(this->scratch_pad_[4]); // resolution
+ wire->reset();
- // write value to EEPROM
- wire->select(this->address_);
- wire->write8(0x48);
- }
+ // write value to EEPROM
+ wire->select(this->address_);
+ wire->write8(0x48);
}
delay(20); // allow it to finish operation
diff --git a/esphome/components/dallas/dallas_component.h b/esphome/components/dallas/dallas_component.h
index 8d405f6eab..37c098283a 100644
--- a/esphome/components/dallas/dallas_component.h
+++ b/esphome/components/dallas/dallas_component.h
@@ -11,8 +11,7 @@ class DallasTemperatureSensor;
class DallasComponent : public PollingComponent {
public:
- explicit DallasComponent(ESPOneWire *one_wire);
-
+ void set_pin(InternalGPIOPin *pin) { pin_ = pin; }
void register_sensor(DallasTemperatureSensor *sensor);
void setup() override;
@@ -24,6 +23,7 @@ class DallasComponent : public PollingComponent {
protected:
friend DallasTemperatureSensor;
+ InternalGPIOPin *pin_;
ESPOneWire *one_wire_;
std::vector sensors_;
std::vector found_sensors_;
diff --git a/esphome/components/dallas/esp_one_wire.cpp b/esphome/components/dallas/esp_one_wire.cpp
index 9278b83f7f..a0ab10f8a4 100644
--- a/esphome/components/dallas/esp_one_wire.cpp
+++ b/esphome/components/dallas/esp_one_wire.cpp
@@ -10,115 +10,123 @@ static const char *const TAG = "dallas.one_wire";
const uint8_t ONE_WIRE_ROM_SELECT = 0x55;
const int ONE_WIRE_ROM_SEARCH = 0xF0;
-ESPOneWire::ESPOneWire(GPIOPin *pin) : pin_(pin) {}
+ESPOneWire::ESPOneWire(InternalGPIOPin *pin) { pin_ = pin->to_isr(); }
bool HOT IRAM_ATTR ESPOneWire::reset() {
- uint8_t retries = 125;
+ // See reset here:
+ // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
+ InterruptLock lock;
- // Wait for communication to clear
- this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
+ // Wait for communication to clear (delay G)
+ pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
+ uint8_t retries = 125;
do {
if (--retries == 0)
return false;
delayMicroseconds(2);
- } while (!this->pin_->digital_read());
+ } while (!pin_.digital_read());
- // Send 480µs LOW TX reset pulse
- this->pin_->pin_mode(gpio::FLAG_OUTPUT);
- this->pin_->digital_write(false);
+ // Send 480µs LOW TX reset pulse (drive bus low, delay H)
+ pin_.pin_mode(gpio::FLAG_OUTPUT);
+ pin_.digital_write(false);
delayMicroseconds(480);
- // Switch into RX mode, letting the pin float
- this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
- // after 15µs-60µs wait time, responder pulls low for 60µs-240µs
- // let's have 70µs just in case
+ // Release the bus, delay I
+ pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(70);
- bool r = !this->pin_->digital_read();
+ // sample bus, 0=device(s) present, 1=no device present
+ bool r = !pin_.digital_read();
+ // delay J
delayMicroseconds(410);
return r;
}
void HOT IRAM_ATTR ESPOneWire::write_bit(bool bit) {
- // Initiate write/read by pulling low.
- this->pin_->pin_mode(gpio::FLAG_OUTPUT);
- this->pin_->digital_write(false);
+ // See write 1/0 bit here:
+ // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
+ InterruptLock lock;
- // bus sampled within 15µs and 60µs after pulling LOW.
- if (bit) {
- // pull high/release within 15µs
- delayMicroseconds(10);
- this->pin_->digital_write(true);
- // in total minimum of 60µs long
- delayMicroseconds(55);
- } else {
- // continue pulling LOW for at least 60µs
- delayMicroseconds(65);
- this->pin_->digital_write(true);
- // grace period, 1µs recovery time
- delayMicroseconds(5);
- }
+ // drive bus low
+ pin_.pin_mode(gpio::FLAG_OUTPUT);
+ pin_.digital_write(false);
+
+ uint32_t delay0 = bit ? 10 : 65;
+ uint32_t delay1 = bit ? 55 : 5;
+
+ // delay A/C
+ delayMicroseconds(delay0);
+ // release bus
+ pin_.digital_write(true);
+ // delay B/D
+ delayMicroseconds(delay1);
}
bool HOT IRAM_ATTR ESPOneWire::read_bit() {
- // Initiate read slot by pulling LOW for at least 1µs
- this->pin_->pin_mode(gpio::FLAG_OUTPUT);
- this->pin_->digital_write(false);
+ // See read bit here:
+ // https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html
+ InterruptLock lock;
+
+ // drive bus low, delay A
+ pin_.pin_mode(gpio::FLAG_OUTPUT);
+ pin_.digital_write(false);
delayMicroseconds(3);
- // release bus, we have to sample within 15µs of pulling low
- this->pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
+ // release bus, delay E
+ pin_.pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
delayMicroseconds(10);
- bool r = this->pin_->digital_read();
- // read time slot at least 60µs long + 1µs recovery time between slots
+ // sample bus to read bit from peer
+ bool r = pin_.digital_read();
+
+ // delay F
delayMicroseconds(53);
return r;
}
-void IRAM_ATTR ESPOneWire::write8(uint8_t val) {
+void ESPOneWire::write8(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
this->write_bit(bool((1u << i) & val));
}
}
-void IRAM_ATTR ESPOneWire::write64(uint64_t val) {
+void ESPOneWire::write64(uint64_t val) {
for (uint8_t i = 0; i < 64; i++) {
this->write_bit(bool((1ULL << i) & val));
}
}
-uint8_t IRAM_ATTR ESPOneWire::read8() {
+uint8_t ESPOneWire::read8() {
uint8_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint8_t(this->read_bit()) << i);
}
return ret;
}
-uint64_t IRAM_ATTR ESPOneWire::read64() {
+uint64_t ESPOneWire::read64() {
uint64_t ret = 0;
for (uint8_t i = 0; i < 8; i++) {
ret |= (uint64_t(this->read_bit()) << i);
}
return ret;
}
-void IRAM_ATTR ESPOneWire::select(uint64_t address) {
+void ESPOneWire::select(uint64_t address) {
this->write8(ONE_WIRE_ROM_SELECT);
this->write64(address);
}
-void IRAM_ATTR ESPOneWire::reset_search() {
+void ESPOneWire::reset_search() {
this->last_discrepancy_ = 0;
this->last_device_flag_ = false;
this->last_family_discrepancy_ = 0;
this->rom_number_ = 0;
}
-uint64_t HOT IRAM_ATTR ESPOneWire::search() {
+uint64_t ESPOneWire::search() {
if (this->last_device_flag_) {
return 0u;
}
if (!this->reset()) {
- // Reset failed
+ // Reset failed or no devices present
this->reset_search();
return 0u;
}
@@ -196,7 +204,7 @@ uint64_t HOT IRAM_ATTR ESPOneWire::search() {
return this->rom_number_;
}
-std::vector IRAM_ATTR ESPOneWire::search_vec() {
+std::vector ESPOneWire::search_vec() {
std::vector res;
this->reset_search();
@@ -206,10 +214,9 @@ std::vector IRAM_ATTR ESPOneWire::search_vec() {
return res;
}
-void IRAM_ATTR ESPOneWire::skip() {
+void ESPOneWire::skip() {
this->write8(0xCC); // skip ROM
}
-GPIOPin *ESPOneWire::get_pin() { return this->pin_; }
uint8_t IRAM_ATTR *ESPOneWire::rom_number8_() { return reinterpret_cast(&this->rom_number_); }
diff --git a/esphome/components/dallas/esp_one_wire.h b/esphome/components/dallas/esp_one_wire.h
index 728fa127d3..ef6f079f02 100644
--- a/esphome/components/dallas/esp_one_wire.h
+++ b/esphome/components/dallas/esp_one_wire.h
@@ -1,6 +1,5 @@
#pragma once
-#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include
@@ -12,7 +11,7 @@ extern const int ONE_WIRE_ROM_SEARCH;
class ESPOneWire {
public:
- explicit ESPOneWire(GPIOPin *pin);
+ explicit ESPOneWire(InternalGPIOPin *pin);
/** Reset the bus, should be done before all write operations.
*
@@ -55,13 +54,11 @@ class ESPOneWire {
/// Helper that wraps search in a std::vector.
std::vector search_vec();
- GPIOPin *get_pin();
-
protected:
/// Helper to get the internal 64-bit unsigned rom number as a 8-bit integer pointer.
inline uint8_t *rom_number8_();
- GPIOPin *pin_;
+ ISRInternalGPIOPin pin_;
uint8_t last_discrepancy_{0};
uint8_t last_family_discrepancy_{0};
bool last_device_flag_{false};
diff --git a/esphome/components/dashboard_import/__init__.py b/esphome/components/dashboard_import/__init__.py
index 2b884d3b9a..d483c77c61 100644
--- a/esphome/components/dashboard_import/__init__.py
+++ b/esphome/components/dashboard_import/__init__.py
@@ -29,6 +29,14 @@ CONFIG_SCHEMA = cv.Schema(
}
)
+WIFI_MESSAGE = """
+
+# Do not forget to add your own wifi configuration before installing this configuration
+# wifi:
+# ssid: !secret wifi_ssid
+# password: !secret wifi_password
+"""
+
async def to_code(config):
cg.add_define("USE_DASHBOARD_IMPORT")
@@ -41,5 +49,12 @@ def import_config(path: str, name: str, project_name: str, import_url: str) -> N
if p.exists():
raise FileExistsError
- config = {"substitutions": {"name": name}, "packages": {project_name: import_url}}
- p.write_text(dump(config), encoding="utf8")
+ config = {
+ "substitutions": {"name": name},
+ "packages": {project_name: import_url},
+ "esphome": {"name_add_mac_suffix": False},
+ }
+ p.write_text(
+ dump(config) + WIFI_MESSAGE,
+ encoding="utf8",
+ )
diff --git a/esphome/components/deep_sleep/deep_sleep_component.cpp b/esphome/components/deep_sleep/deep_sleep_component.cpp
index e4b1edfb7b..0998a57af3 100644
--- a/esphome/components/deep_sleep/deep_sleep_component.cpp
+++ b/esphome/components/deep_sleep/deep_sleep_component.cpp
@@ -78,8 +78,9 @@ void DeepSleepComponent::begin_sleep(bool manual) {
esp_sleep_enable_timer_wakeup(*this->sleep_duration_);
if (this->wakeup_pin_ != nullptr) {
bool level = this->wakeup_pin_->is_inverted();
- if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && this->wakeup_pin_->digital_read())
+ if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_INVERT_WAKEUP && !this->wakeup_pin_->digital_read()) {
level = !level;
+ }
esp_sleep_enable_ext0_wakeup(gpio_num_t(this->wakeup_pin_->get_pin()), level);
}
if (this->ext1_wakeup_.has_value()) {
diff --git a/esphome/components/display/__init__.py b/esphome/components/display/__init__.py
index 947b09a258..0d403f99f0 100644
--- a/esphome/components/display/__init__.py
+++ b/esphome/components/display/__init__.py
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome import core, automation
from esphome.automation import maybe_simple_id
from esphome.const import (
+ CONF_AUTO_CLEAR_ENABLED,
CONF_ID,
CONF_LAMBDA,
CONF_PAGES,
@@ -79,6 +80,7 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
cv.Optional(CONF_TO): cv.use_id(DisplayPage),
}
),
+ cv.Optional(CONF_AUTO_CLEAR_ENABLED, default=True): cv.boolean,
}
)
@@ -86,6 +88,10 @@ FULL_DISPLAY_SCHEMA = BASIC_DISPLAY_SCHEMA.extend(
async def setup_display_core_(var, config):
if CONF_ROTATION in config:
cg.add(var.set_rotation(DISPLAY_ROTATIONS[config[CONF_ROTATION]]))
+
+ if CONF_AUTO_CLEAR_ENABLED in config:
+ cg.add(var.set_auto_clear(config[CONF_AUTO_CLEAR_ENABLED]))
+
if CONF_PAGES in config:
pages = []
for conf in config[CONF_PAGES]:
diff --git a/esphome/components/display/display_buffer.cpp b/esphome/components/display/display_buffer.cpp
index 2ee06e379f..ac806611b5 100644
--- a/esphome/components/display/display_buffer.cpp
+++ b/esphome/components/display/display_buffer.cpp
@@ -336,7 +336,9 @@ void DisplayBuffer::show_page(DisplayPage *page) {
void DisplayBuffer::show_next_page() { this->page_->show_next(); }
void DisplayBuffer::show_prev_page() { this->page_->show_prev(); }
void DisplayBuffer::do_update_() {
- this->clear();
+ if (this->auto_clear_enabled_) {
+ this->clear();
+ }
if (this->page_ != nullptr) {
this->page_->get_writer()(*this);
} else if (this->writer_.has_value()) {
diff --git a/esphome/components/display/display_buffer.h b/esphome/components/display/display_buffer.h
index 54488f18f7..c803180a2d 100644
--- a/esphome/components/display/display_buffer.h
+++ b/esphome/components/display/display_buffer.h
@@ -333,6 +333,9 @@ class DisplayBuffer {
/// Internal method to set the display rotation with.
void set_rotation(DisplayRotation rotation);
+ // Internal method to set display auto clearing.
+ void set_auto_clear(bool auto_clear_enabled) { this->auto_clear_enabled_ = auto_clear_enabled; }
+
protected:
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
@@ -352,6 +355,7 @@ class DisplayBuffer {
DisplayPage *page_{nullptr};
DisplayPage *previous_page_{nullptr};
std::vector on_page_change_triggers_;
+ bool auto_clear_enabled_{true};
};
class DisplayPage {
diff --git a/esphome/components/dsmr/dsmr.cpp b/esphome/components/dsmr/dsmr.cpp
index b798fe5d44..ea852e626e 100644
--- a/esphome/components/dsmr/dsmr.cpp
+++ b/esphome/components/dsmr/dsmr.cpp
@@ -19,14 +19,30 @@ void Dsmr::loop() {
this->receive_encrypted_();
}
+bool Dsmr::available_within_timeout_() {
+ uint8_t tries = READ_TIMEOUT_MS / 5;
+ while (tries--) {
+ delay(5);
+ if (available()) {
+ return true;
+ }
+ }
+ return false;
+}
+
void Dsmr::receive_telegram_() {
- int count = MAX_BYTES_PER_LOOP;
- while (available() && count-- > 0) {
+ while (true) {
+ if (!available()) {
+ if (!header_found_ || !available_within_timeout_()) {
+ return;
+ }
+ }
+
const char c = read();
// Find a new telegram header, i.e. forward slash.
if (c == '/') {
- ESP_LOGV(TAG, "Header found");
+ ESP_LOGV(TAG, "Header of telegram found");
header_found_ = true;
footer_found_ = false;
telegram_len_ = 0;
@@ -38,7 +54,7 @@ void Dsmr::receive_telegram_() {
if (telegram_len_ >= MAX_TELEGRAM_LENGTH) {
header_found_ = false;
footer_found_ = false;
- ESP_LOGE(TAG, "Error: Message larger than buffer");
+ ESP_LOGE(TAG, "Error: telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
return;
}
@@ -54,16 +70,17 @@ void Dsmr::receive_telegram_() {
// Check for a footer, i.e. exlamation mark, followed by a hex checksum.
if (c == '!') {
- ESP_LOGV(TAG, "Footer found");
+ ESP_LOGV(TAG, "Footer of telegram found");
footer_found_ = true;
continue;
}
// Check for the end of the hex checksum, i.e. a newline.
if (footer_found_ && c == '\n') {
- header_found_ = false;
// Parse the telegram and publish sensor values.
- if (parse_telegram())
- return;
+ parse_telegram();
+
+ header_found_ = false;
+ return;
}
}
}
@@ -72,41 +89,46 @@ void Dsmr::receive_encrypted_() {
// Encrypted buffer
uint8_t buffer[MAX_TELEGRAM_LENGTH];
size_t buffer_length = 0;
-
size_t packet_size = 0;
- while (available()) {
- const char c = read();
- if (!header_found_) {
- if ((uint8_t) c == 0xdb) {
- ESP_LOGV(TAG, "Start byte 0xDB found");
- header_found_ = true;
+ while (true) {
+ if (!available()) {
+ if (!header_found_) {
+ return;
+ }
+ if (!available_within_timeout_()) {
+ ESP_LOGW(TAG, "Timeout while reading data for encrypted telegram");
+ return;
}
}
- // Sanity check
- if (!header_found_ || buffer_length >= MAX_TELEGRAM_LENGTH) {
- if (buffer_length == 0) {
- ESP_LOGE(TAG, "First byte of encrypted telegram should be 0xDB, aborting.");
- } else {
- ESP_LOGW(TAG, "Unexpected data");
+ const char c = read();
+
+ // Find a new telegram start byte.
+ if (!header_found_) {
+ if ((uint8_t) c != 0xDB) {
+ continue;
}
- this->status_momentary_warning("unexpected_data");
- this->flush();
- while (available())
- read();
+ ESP_LOGV(TAG, "Start byte 0xDB of encrypted telegram found");
+ header_found_ = true;
+ }
+
+ // Check for buffer overflow.
+ if (buffer_length >= MAX_TELEGRAM_LENGTH) {
+ header_found_ = false;
+ ESP_LOGE(TAG, "Error: encrypted telegram larger than buffer (%d bytes)", MAX_TELEGRAM_LENGTH);
return;
}
buffer[buffer_length++] = c;
if (packet_size == 0 && buffer_length > 20) {
- // Complete header + a few bytes of data
- packet_size = buffer[11] << 8 | buffer[12];
+ // Complete header + data bytes
+ packet_size = 13 + (buffer[11] << 8 | buffer[12]);
+ ESP_LOGV(TAG, "Encrypted telegram size: %d bytes", packet_size);
}
- if (buffer_length == packet_size + 13 && packet_size > 0) {
- ESP_LOGV(TAG, "Encrypted data: %d bytes", buffer_length);
-
+ if (buffer_length == packet_size && packet_size > 0) {
+ ESP_LOGV(TAG, "End of encrypted telegram found");
GCM *gcmaes128{new GCM()};
gcmaes128->setKey(this->decryption_key_.data(), gcmaes128->keySize());
// the iv is 8 bytes of the system title + 4 bytes frame counter
@@ -123,28 +145,21 @@ void Dsmr::receive_encrypted_() {
delete gcmaes128; // NOLINT(cppcoreguidelines-owning-memory)
telegram_len_ = strnlen(this->telegram_, sizeof(this->telegram_));
- ESP_LOGV(TAG, "Decrypted data length: %d", telegram_len_);
- ESP_LOGVV(TAG, "Decrypted data %s", this->telegram_);
+ ESP_LOGV(TAG, "Decrypted telegram size: %d bytes", telegram_len_);
+ ESP_LOGVV(TAG, "Decrypted telegram: %s", this->telegram_);
parse_telegram();
+
+ header_found_ = false;
telegram_len_ = 0;
return;
}
-
- if (!available()) {
- // baud rate is 115200 for encrypted data, this means a few byte should arrive every time
- // program runs faster than buffer loading then available() might return false in the middle
- delay(4); // Wait for data
- }
- }
- if (buffer_length > 0) {
- ESP_LOGW(TAG, "Timeout while waiting for encrypted data or invalid data received.");
}
}
bool Dsmr::parse_telegram() {
MyData data;
- ESP_LOGV(TAG, "Trying to parse");
+ ESP_LOGV(TAG, "Trying to parse telegram");
::dsmr::ParseResult res =
::dsmr::P1Parser::parse(&data, telegram_, telegram_len_, false,
this->crc_check_); // Parse telegram according to data definition. Ignore unknown values.
@@ -161,7 +176,7 @@ bool Dsmr::parse_telegram() {
}
void Dsmr::dump_config() {
- ESP_LOGCONFIG(TAG, "dsmr:");
+ ESP_LOGCONFIG(TAG, "DSMR:");
#define DSMR_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s_##s##_);
DSMR_SENSOR_LIST(DSMR_LOG_SENSOR, )
@@ -178,12 +193,12 @@ void Dsmr::set_decryption_key(const std::string &decryption_key) {
}
if (decryption_key.length() != 32) {
- ESP_LOGE(TAG, "Error, decryption key must be 32 character long.");
+ ESP_LOGE(TAG, "Error, decryption key must be 32 character long");
return;
}
this->decryption_key_.clear();
- ESP_LOGI(TAG, "Decryption key is set.");
+ ESP_LOGI(TAG, "Decryption key is set");
// Verbose level prints decryption key
ESP_LOGV(TAG, "Using decryption key: %s", decryption_key.c_str());
diff --git a/esphome/components/dsmr/dsmr.h b/esphome/components/dsmr/dsmr.h
index 4f9a66b3d0..ca2c0f0877 100644
--- a/esphome/components/dsmr/dsmr.h
+++ b/esphome/components/dsmr/dsmr.h
@@ -17,8 +17,7 @@ namespace esphome {
namespace dsmr {
static constexpr uint32_t MAX_TELEGRAM_LENGTH = 1500;
-static constexpr uint32_t MAX_BYTES_PER_LOOP = 50;
-static constexpr uint32_t POLL_TIMEOUT = 1000;
+static constexpr uint32_t READ_TIMEOUT_MS = 200;
using namespace ::dsmr::fields;
@@ -86,6 +85,17 @@ class Dsmr : public Component, public uart::UARTDevice {
void receive_telegram_();
void receive_encrypted_();
+ /// Wait for UART data to become available within the read timeout.
+ ///
+ /// The smart meter might provide data in chunks, causing available() to
+ /// return 0. When we're already reading a telegram, then we don't return
+ /// right away (to handle further data in an upcoming loop) but wait a
+ /// little while using this method to see if more data are incoming.
+ /// By not returning, we prevent other components from taking so much
+ /// time that the UART RX buffer overflows and bytes of the telegram get
+ /// lost in the process.
+ bool available_within_timeout_();
+
// Telegram buffer
char telegram_[MAX_TELEGRAM_LENGTH];
int telegram_len_{0};
diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.cpp b/esphome/components/duty_cycle/duty_cycle_sensor.cpp
index 3d7f731d5d..aed22312a7 100644
--- a/esphome/components/duty_cycle/duty_cycle_sensor.cpp
+++ b/esphome/components/duty_cycle/duty_cycle_sensor.cpp
@@ -12,7 +12,6 @@ void DutyCycleSensor::setup() {
this->pin_->setup();
this->store_.pin = this->pin_->to_isr();
this->store_.last_level = this->pin_->digital_read();
- this->last_update_ = micros();
this->store_.last_interrupt = micros();
this->pin_->attach_interrupt(DutyCycleSensorStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
@@ -24,19 +23,20 @@ void DutyCycleSensor::dump_config() {
}
void DutyCycleSensor::update() {
const uint32_t now = micros();
- const bool level = this->store_.last_level;
- const uint32_t last_interrupt = this->store_.last_interrupt;
- uint32_t on_time = this->store_.on_time;
+ if (this->last_update_ != 0) {
+ const bool level = this->store_.last_level;
+ const uint32_t last_interrupt = this->store_.last_interrupt;
+ uint32_t on_time = this->store_.on_time;
- if (level)
- on_time += now - last_interrupt;
+ if (level)
+ on_time += now - last_interrupt;
- const float total_time = float(now - this->last_update_);
-
- const float value = (on_time / total_time) * 100.0f;
- ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
- this->publish_state(value);
+ const float total_time = float(now - this->last_update_);
+ const float value = (on_time / total_time) * 100.0f;
+ ESP_LOGD(TAG, "'%s' Got duty cycle=%.1f%%", this->get_name().c_str(), value);
+ this->publish_state(value);
+ }
this->store_.on_time = 0;
this->store_.last_interrupt = now;
this->last_update_ = now;
diff --git a/esphome/components/duty_cycle/duty_cycle_sensor.h b/esphome/components/duty_cycle/duty_cycle_sensor.h
index 22d3588fb7..ffb1802e14 100644
--- a/esphome/components/duty_cycle/duty_cycle_sensor.h
+++ b/esphome/components/duty_cycle/duty_cycle_sensor.h
@@ -30,7 +30,7 @@ class DutyCycleSensor : public sensor::Sensor, public PollingComponent {
InternalGPIOPin *pin_;
DutyCycleSensorStore store_{};
- uint32_t last_update_;
+ uint32_t last_update_{0};
};
} // namespace duty_cycle
diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py
index e1128ff227..1c249476e7 100644
--- a/esphome/components/esp32/__init__.py
+++ b/esphome/components/esp32/__init__.py
@@ -28,6 +28,7 @@ from .const import ( # noqa
KEY_SDKCONFIG_OPTIONS,
KEY_VARIANT,
VARIANT_ESP32C3,
+ VARIANT_FRIENDLY,
VARIANTS,
)
from .boards import BOARD_TO_VARIANT
@@ -147,8 +148,9 @@ def _arduino_check_versions(value):
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_arduino_version(version)
- platform_version = value.get(CONF_PLATFORM_VERSION, ARDUINO_PLATFORM_VERSION)
- value[CONF_PLATFORM_VERSION] = str(platform_version)
+ value[CONF_PLATFORM_VERSION] = value.get(
+ CONF_PLATFORM_VERSION, _parse_platform_version(str(ARDUINO_PLATFORM_VERSION))
+ )
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
@@ -184,8 +186,9 @@ def _esp_idf_check_versions(value):
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
- platform_version = value.get(CONF_PLATFORM_VERSION, ESP_IDF_PLATFORM_VERSION)
- value[CONF_PLATFORM_VERSION] = str(platform_version)
+ value[CONF_PLATFORM_VERSION] = value.get(
+ CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
+ )
if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
_LOGGER.warning(
@@ -196,6 +199,15 @@ def _esp_idf_check_versions(value):
return value
+def _parse_platform_version(value):
+ try:
+ # if platform version is a valid version constraint, prefix the default package
+ cv.platformio_version_constraint(value)
+ return f"platformio/espressif32 @ {value}"
+ except cv.Invalid:
+ return value
+
+
def _detect_variant(value):
if CONF_VARIANT not in value:
board = value[CONF_BOARD]
@@ -218,7 +230,7 @@ ARDUINO_FRAMEWORK_SCHEMA = cv.All(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
- cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
+ cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
}
),
_arduino_check_versions,
@@ -230,7 +242,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
- cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
+ cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
cv.string_strict: cv.string_strict
},
@@ -276,14 +288,14 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP32")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
cg.add_build_flag(f"-DUSE_ESP32_VARIANT_{config[CONF_VARIANT]}")
+ cg.add_define("ESPHOME_VARIANT", VARIANT_FRIENDLY[config[CONF_VARIANT]])
cg.add_platformio_option("lib_ldf_mode", "off")
conf = config[CONF_FRAMEWORK]
+ cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
+
if conf[CONF_TYPE] == FRAMEWORK_ESP_IDF:
- cg.add_platformio_option(
- "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
- )
cg.add_platformio_option("framework", "espidf")
cg.add_build_flag("-DUSE_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
@@ -299,6 +311,8 @@ async def to_code(config):
)
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_DEFAULT", False)
add_idf_sdkconfig_option("CONFIG_COMPILER_OPTIMIZATION_SIZE", True)
+ # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
+ add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
cg.add_platformio_option("board_build.partitions", "partitions.csv")
@@ -312,9 +326,6 @@ async def to_code(config):
)
elif conf[CONF_TYPE] == FRAMEWORK_ARDUINO:
- cg.add_platformio_option(
- "platform", f"espressif32 @ {conf[CONF_PLATFORM_VERSION]}"
- )
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
diff --git a/esphome/components/esp32/const.py b/esphome/components/esp32/const.py
index b82f03bf68..d92b449ee9 100644
--- a/esphome/components/esp32/const.py
+++ b/esphome/components/esp32/const.py
@@ -18,4 +18,12 @@ VARIANTS = [
VARIANT_ESP32H2,
]
+VARIANT_FRIENDLY = {
+ VARIANT_ESP32: "ESP32",
+ VARIANT_ESP32S2: "ESP32-S2",
+ VARIANT_ESP32S3: "ESP32-S3",
+ VARIANT_ESP32C3: "ESP32-C3",
+ VARIANT_ESP32H2: "ESP32-H2",
+}
+
esp32_ns = cg.esphome_ns.namespace("esp32")
diff --git a/esphome/components/esp32/core.cpp b/esphome/components/esp32/core.cpp
index 96047df535..359999120f 100644
--- a/esphome/components/esp32/core.cpp
+++ b/esphome/components/esp32/core.cpp
@@ -21,11 +21,7 @@ void IRAM_ATTR HOT yield() { vPortYield(); }
uint32_t IRAM_ATTR HOT millis() { return (uint32_t)(esp_timer_get_time() / 1000ULL); }
void IRAM_ATTR HOT delay(uint32_t ms) { vTaskDelay(ms / portTICK_PERIOD_MS); }
uint32_t IRAM_ATTR HOT micros() { return (uint32_t) esp_timer_get_time(); }
-void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
- auto start = (uint64_t) esp_timer_get_time();
- while (((uint64_t) esp_timer_get_time()) - start < us)
- ;
-}
+void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() {
esp_restart();
// restart() doesn't always end execution
diff --git a/esphome/components/esp32/gpio.py b/esphome/components/esp32/gpio.py
index 93ab17db22..5819943f37 100644
--- a/esphome/components/esp32/gpio.py
+++ b/esphome/components/esp32/gpio.py
@@ -1,4 +1,5 @@
-import logging
+from dataclasses import dataclass
+from typing import Any
from esphome.const import (
CONF_ID,
@@ -17,10 +18,24 @@ import esphome.config_validation as cv
import esphome.codegen as cg
from . import boards
-from .const import KEY_BOARD, KEY_ESP32, esp32_ns
+from .const import (
+ KEY_BOARD,
+ KEY_ESP32,
+ KEY_VARIANT,
+ VARIANT_ESP32,
+ VARIANT_ESP32C3,
+ VARIANT_ESP32S2,
+ VARIANT_ESP32S3,
+ VARIANT_ESP32H2,
+ esp32_ns,
+)
-_LOGGER = logging.getLogger(__name__)
+from .gpio_esp32 import esp32_validate_gpio_pin, esp32_validate_supports
+from .gpio_esp32_s2 import esp32_s2_validate_gpio_pin, esp32_s2_validate_supports
+from .gpio_esp32_c3 import esp32_c3_validate_gpio_pin, esp32_c3_validate_supports
+from .gpio_esp32_s3 import esp32_s3_validate_gpio_pin, esp32_s3_validate_supports
+from .gpio_esp32_h2 import esp32_h2_validate_gpio_pin, esp32_h2_validate_supports
IDFInternalGPIOPin = esp32_ns.class_("IDFInternalGPIOPin", cg.InternalGPIOPin)
@@ -59,65 +74,61 @@ def _translate_pin(value):
return _lookup_pin(value)
-_ESP_SDIO_PINS = {
- 6: "Flash Clock",
- 7: "Flash Data 0",
- 8: "Flash Data 1",
- 11: "Flash Command",
+@dataclass
+class ESP32ValidationFunctions:
+ pin_validation: Any
+ usage_validation: Any
+
+
+_esp32_validations = {
+ VARIANT_ESP32: ESP32ValidationFunctions(
+ pin_validation=esp32_validate_gpio_pin, usage_validation=esp32_validate_supports
+ ),
+ VARIANT_ESP32S2: ESP32ValidationFunctions(
+ pin_validation=esp32_s2_validate_gpio_pin,
+ usage_validation=esp32_s2_validate_supports,
+ ),
+ VARIANT_ESP32C3: ESP32ValidationFunctions(
+ pin_validation=esp32_c3_validate_gpio_pin,
+ usage_validation=esp32_c3_validate_supports,
+ ),
+ VARIANT_ESP32S3: ESP32ValidationFunctions(
+ pin_validation=esp32_s3_validate_gpio_pin,
+ usage_validation=esp32_s3_validate_supports,
+ ),
+ VARIANT_ESP32H2: ESP32ValidationFunctions(
+ pin_validation=esp32_h2_validate_gpio_pin,
+ usage_validation=esp32_h2_validate_supports,
+ ),
}
def validate_gpio_pin(value):
value = _translate_pin(value)
- if value < 0 or value > 39:
- raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
- if value in _ESP_SDIO_PINS:
- raise cv.Invalid(
- f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
- )
- if 9 <= value <= 10:
- _LOGGER.warning(
- "Pin %s (9-10) might already be used by the "
- "flash interface in QUAD IO flash mode.",
- value,
- )
- if value in (20, 24, 28, 29, 30, 31):
- # These pins are not exposed in GPIO mux (reason unknown)
- # but they're missing from IO_MUX list in datasheet
- raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
- return value
+ variant = CORE.data[KEY_ESP32][KEY_VARIANT]
+ if variant not in _esp32_validations:
+ raise cv.Invalid("Unsupported ESP32 variant {variant}")
+
+ return _esp32_validations[variant].pin_validation(value)
def validate_supports(value):
- num = value[CONF_NUMBER]
mode = value[CONF_MODE]
is_input = mode[CONF_INPUT]
is_output = mode[CONF_OUTPUT]
is_open_drain = mode[CONF_OPEN_DRAIN]
is_pullup = mode[CONF_PULLUP]
is_pulldown = mode[CONF_PULLDOWN]
+ variant = CORE.data[KEY_ESP32][KEY_VARIANT]
+ if variant not in _esp32_validations:
+ raise cv.Invalid("Unsupported ESP32 variant {variant}")
- if is_input:
- # All ESP32 pins support input mode
- pass
- if is_output and 34 <= num <= 39:
- raise cv.Invalid(
- f"GPIO{num} (34-39) does not support output pin mode.",
- [CONF_MODE, CONF_OUTPUT],
- )
if is_open_drain and not is_output:
raise cv.Invalid(
"Open-drain only works with output mode", [CONF_MODE, CONF_OPEN_DRAIN]
)
- if is_pullup and 34 <= num <= 39:
- raise cv.Invalid(
- f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
- )
- if is_pulldown and 34 <= num <= 39:
- raise cv.Invalid(
- f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
- )
+ value = _esp32_validations[variant].usage_validation(value)
if CORE.using_arduino:
# (input, output, open_drain, pullup, pulldown)
supported_modes = {
@@ -138,7 +149,6 @@ def validate_supports(value):
"This pin mode is not supported on ESP32 for arduino frameworks",
[CONF_MODE],
)
-
return value
diff --git a/esphome/components/esp32/gpio_arduino.cpp b/esphome/components/esp32/gpio_arduino.cpp
index c4bb21a0aa..ba92894f97 100644
--- a/esphome/components/esp32/gpio_arduino.cpp
+++ b/esphome/components/esp32/gpio_arduino.cpp
@@ -9,6 +9,22 @@ namespace esp32 {
static const char *const TAG = "esp32";
+static int IRAM_ATTR flags_to_mode(gpio::Flags flags) {
+ if (flags == gpio::FLAG_INPUT) {
+ return INPUT;
+ } else if (flags == gpio::FLAG_OUTPUT) {
+ return OUTPUT;
+ } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
+ return INPUT_PULLUP;
+ } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
+ return INPUT_PULLDOWN;
+ } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
+ return OUTPUT_OPEN_DRAIN;
+ } else {
+ return 0;
+ }
+}
+
struct ISRPinArg {
uint8_t pin;
bool inverted;
@@ -43,22 +59,9 @@ void ArduinoInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, g
attachInterruptArg(pin_, func, arg, arduino_mode);
}
+
void ArduinoInternalGPIOPin::pin_mode(gpio::Flags flags) {
- uint8_t mode;
- if (flags == gpio::FLAG_INPUT) {
- mode = INPUT;
- } else if (flags == gpio::FLAG_OUTPUT) {
- mode = OUTPUT;
- } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
- mode = INPUT_PULLUP;
- } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
- mode = INPUT_PULLDOWN;
- } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
- mode = OUTPUT_OPEN_DRAIN;
- } else {
- return;
- }
- pinMode(pin_, mode); // NOLINT
+ pinMode(pin_, flags_to_mode(flags)); // NOLINT
}
std::string ArduinoInternalGPIOPin::dump_summary() const {
@@ -101,6 +104,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
}
#endif
}
+void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
+ auto *arg = reinterpret_cast(arg_);
+ pinMode(arg->pin, flags_to_mode(flags)); // NOLINT
+}
} // namespace esphome
diff --git a/esphome/components/esp32/gpio_esp32.py b/esphome/components/esp32/gpio_esp32.py
new file mode 100644
index 0000000000..425d77b343
--- /dev/null
+++ b/esphome/components/esp32/gpio_esp32.py
@@ -0,0 +1,77 @@
+import logging
+
+from esphome.const import (
+ CONF_INPUT,
+ CONF_MODE,
+ CONF_NUMBER,
+ CONF_OUTPUT,
+ CONF_PULLDOWN,
+ CONF_PULLUP,
+)
+import esphome.config_validation as cv
+
+
+_ESP_SDIO_PINS = {
+ 6: "Flash Clock",
+ 7: "Flash Data 0",
+ 8: "Flash Data 1",
+ 11: "Flash Command",
+}
+
+_ESP32_STRAPPING_PINS = {0, 2, 4, 15}
+_LOGGER = logging.getLogger(__name__)
+
+
+def esp32_validate_gpio_pin(value):
+ if value < 0 or value > 39:
+ raise cv.Invalid(f"Invalid pin number: {value} (must be 0-39)")
+ if value in _ESP_SDIO_PINS:
+ raise cv.Invalid(
+ f"This pin cannot be used on ESP32s and is already used by the flash interface (function: {_ESP_SDIO_PINS[value]})"
+ )
+ if 9 <= value <= 10:
+ _LOGGER.warning(
+ "Pin %s (9-10) might already be used by the "
+ "flash interface in QUAD IO flash mode.",
+ value,
+ )
+ if value in _ESP32_STRAPPING_PINS:
+ _LOGGER.warning(
+ "GPIO%d is a Strapping PIN and should be avoided.\n"
+ "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
+ "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
+ value,
+ )
+ if value in (20, 24, 28, 29, 30, 31):
+ # These pins are not exposed in GPIO mux (reason unknown)
+ # but they're missing from IO_MUX list in datasheet
+ raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32s.")
+ return value
+
+
+def esp32_validate_supports(value):
+ num = value[CONF_NUMBER]
+ mode = value[CONF_MODE]
+ is_input = mode[CONF_INPUT]
+ is_output = mode[CONF_OUTPUT]
+ is_pullup = mode[CONF_PULLUP]
+ is_pulldown = mode[CONF_PULLDOWN]
+
+ if is_input:
+ # All ESP32 pins support input mode
+ pass
+ if is_output and 34 <= num <= 39:
+ raise cv.Invalid(
+ f"GPIO{num} (34-39) does not support output pin mode.",
+ [CONF_MODE, CONF_OUTPUT],
+ )
+ if is_pullup and 34 <= num <= 39:
+ raise cv.Invalid(
+ f"GPIO{num} (34-39) does not support pullups.", [CONF_MODE, CONF_PULLUP]
+ )
+ if is_pulldown and 34 <= num <= 39:
+ raise cv.Invalid(
+ f"GPIO{num} (34-39) does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
+ )
+
+ return value
diff --git a/esphome/components/esp32/gpio_esp32_c3.py b/esphome/components/esp32/gpio_esp32_c3.py
new file mode 100644
index 0000000000..fc1cef29e5
--- /dev/null
+++ b/esphome/components/esp32/gpio_esp32_c3.py
@@ -0,0 +1,53 @@
+import logging
+
+from esphome.const import (
+ CONF_INPUT,
+ CONF_MODE,
+ CONF_NUMBER,
+)
+import esphome.config_validation as cv
+
+_ESP32C3_SPI_PSRAM_PINS = {
+ 12: "SPIHD",
+ 13: "SPIWP",
+ 14: "SPICS0",
+ 15: "SPICLK",
+ 16: "SPID",
+ 17: "SPIQ",
+}
+
+_ESP32C3_STRAPPING_PINS = {2, 8, 9}
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def esp32_c3_validate_gpio_pin(value):
+ if value < 0 or value > 21:
+ raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
+ if value in _ESP32C3_SPI_PSRAM_PINS:
+ raise cv.Invalid(
+ f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
+ )
+ if value in _ESP32C3_STRAPPING_PINS:
+ _LOGGER.warning(
+ "GPIO%d is a Strapping PIN and should be avoided.\n"
+ "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
+ "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
+ value,
+ )
+
+ return value
+
+
+def esp32_c3_validate_supports(value):
+ num = value[CONF_NUMBER]
+ mode = value[CONF_MODE]
+ is_input = mode[CONF_INPUT]
+
+ if num < 0 or num > 21:
+ raise cv.Invalid(f"Invalid pin number: {value} (must be 0-21)")
+
+ if is_input:
+ # All ESP32 pins support input mode
+ pass
+ return value
diff --git a/esphome/components/esp32/gpio_esp32_h2.py b/esphome/components/esp32/gpio_esp32_h2.py
new file mode 100644
index 0000000000..5196ef0c09
--- /dev/null
+++ b/esphome/components/esp32/gpio_esp32_h2.py
@@ -0,0 +1,11 @@
+import esphome.config_validation as cv
+
+
+def esp32_h2_validate_gpio_pin(value):
+ # ESP32-H2 not yet supported
+ raise cv.Invalid("ESP32-H2 isn't supported yet")
+
+
+def esp32_h2_validate_supports(value):
+ # ESP32-H2 not yet supported
+ raise cv.Invalid("ESP32-H2 isn't supported yet")
diff --git a/esphome/components/esp32/gpio_esp32_s2.py b/esphome/components/esp32/gpio_esp32_s2.py
new file mode 100644
index 0000000000..db244b6259
--- /dev/null
+++ b/esphome/components/esp32/gpio_esp32_s2.py
@@ -0,0 +1,80 @@
+import logging
+
+from esphome.const import (
+ CONF_INPUT,
+ CONF_MODE,
+ CONF_NUMBER,
+ CONF_OUTPUT,
+ CONF_PULLDOWN,
+ CONF_PULLUP,
+)
+
+import esphome.config_validation as cv
+
+_ESP32S2_SPI_PSRAM_PINS = {
+ 26: "SPICS1",
+ 27: "SPIHD",
+ 28: "SPIWP",
+ 29: "SPICS0",
+ 30: "SPICLK",
+ 31: "SPIQ",
+ 32: "SPID",
+}
+
+_ESP32S2_STRAPPING_PINS = {0, 45, 46}
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def esp32_s2_validate_gpio_pin(value):
+ if value < 0 or value > 46:
+ raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
+
+ if value in _ESP32S2_SPI_PSRAM_PINS:
+ raise cv.Invalid(
+ f"This pin cannot be used on ESP32-S2s and is already used by the SPI/PSRAM interface (function: {_ESP32S2_SPI_PSRAM_PINS[value]})"
+ )
+ if value in _ESP32S2_STRAPPING_PINS:
+ _LOGGER.warning(
+ "GPIO%d is a Strapping PIN and should be avoided.\n"
+ "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
+ "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
+ value,
+ )
+
+ if value in (22, 23, 24, 25):
+ # These pins are not exposed in GPIO mux (reason unknown)
+ # but they're missing from IO_MUX list in datasheet
+ raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S2s.")
+
+ return value
+
+
+def esp32_s2_validate_supports(value):
+ num = value[CONF_NUMBER]
+ mode = value[CONF_MODE]
+ is_input = mode[CONF_INPUT]
+ is_output = mode[CONF_OUTPUT]
+ is_pullup = mode[CONF_PULLUP]
+ is_pulldown = mode[CONF_PULLDOWN]
+
+ if num < 0 or num > 46:
+ raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
+ if is_input:
+ # All ESP32 pins support input mode
+ pass
+ if is_output and num == 46:
+ raise cv.Invalid(
+ f"GPIO{num} does not support output pin mode.",
+ [CONF_MODE, CONF_OUTPUT],
+ )
+ if is_pullup and num == 46:
+ raise cv.Invalid(
+ f"GPIO{num} does not support pullups.", [CONF_MODE, CONF_PULLUP]
+ )
+ if is_pulldown and num == 46:
+ raise cv.Invalid(
+ f"GPIO{num} does not support pulldowns.", [CONF_MODE, CONF_PULLDOWN]
+ )
+
+ return value
diff --git a/esphome/components/esp32/gpio_esp32_s3.py b/esphome/components/esp32/gpio_esp32_s3.py
new file mode 100644
index 0000000000..f729a757c2
--- /dev/null
+++ b/esphome/components/esp32/gpio_esp32_s3.py
@@ -0,0 +1,74 @@
+import logging
+
+from esphome.const import (
+ CONF_INPUT,
+ CONF_MODE,
+ CONF_NUMBER,
+)
+
+import esphome.config_validation as cv
+
+_ESP_32S3_SPI_PSRAM_PINS = {
+ 26: "SPICS1",
+ 27: "SPIHD",
+ 28: "SPIWP",
+ 29: "SPICS0",
+ 30: "SPICLK",
+ 31: "SPIQ",
+ 32: "SPID",
+}
+
+_ESP_32_ESP32_S3R8_PSRAM_PINS = {
+ 33: "SPIIO4",
+ 34: "SPIIO5",
+ 35: "SPIIO6",
+ 36: "SPIIO7",
+ 37: "SPIDQS",
+}
+
+_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46}
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def esp32_s3_validate_gpio_pin(value):
+ if value < 0 or value > 48:
+ raise cv.Invalid(f"Invalid pin number: {value} (must be 0-46)")
+
+ if value in _ESP_32S3_SPI_PSRAM_PINS:
+ raise cv.Invalid(
+ f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})"
+ )
+ if value in _ESP_32_ESP32_S3R8_PSRAM_PINS:
+ _LOGGER.warning(
+ "GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models",
+ value,
+ )
+
+ if value in _ESP_32S3_STRAPPING_PINS:
+ _LOGGER.warning(
+ "GPIO%d is a Strapping PIN and should be avoided.\n"
+ "Attaching external pullup/down resistors to strapping pins can cause unexpected failures.\n"
+ "See https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins",
+ value,
+ )
+
+ if value in (22, 23, 24, 25):
+ # These pins are not exposed in GPIO mux (reason unknown)
+ # but they're missing from IO_MUX list in datasheet
+ raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.")
+
+ return value
+
+
+def esp32_s3_validate_supports(value):
+ num = value[CONF_NUMBER]
+ mode = value[CONF_MODE]
+ is_input = mode[CONF_INPUT]
+
+ if num < 0 or num > 48:
+ raise cv.Invalid(f"Invalid pin number: {num} (must be 0-46)")
+ if is_input:
+ # All ESP32 pins support input mode
+ pass
+ return value
diff --git a/esphome/components/esp32/gpio_idf.cpp b/esphome/components/esp32/gpio_idf.cpp
index d1853e1f8b..498843ebff 100644
--- a/esphome/components/esp32/gpio_idf.cpp
+++ b/esphome/components/esp32/gpio_idf.cpp
@@ -10,38 +10,7 @@ static const char *const TAG = "esp32";
bool IDFInternalGPIOPin::isr_service_installed = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
-struct ISRPinArg {
- gpio_num_t pin;
- bool inverted;
-};
-
-ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
- auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
- arg->pin = pin_;
- arg->inverted = inverted_;
- return ISRInternalGPIOPin((void *) arg);
-}
-
-void IDFInternalGPIOPin::setup() {
- pin_mode(flags_);
- gpio_set_drive_capability(pin_, drive_strength_);
-}
-
-void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
- gpio_config_t conf{};
- conf.pin_bit_mask = 1ULL << static_cast(pin_);
- conf.mode = flags_to_mode(flags);
- conf.pull_up_en = flags & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
- conf.pull_down_en = flags & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
- conf.intr_type = GPIO_INTR_DISABLE;
- gpio_config(&conf);
-}
-
-bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
-
-void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
-
-gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
+static gpio_mode_t IRAM_ATTR flags_to_mode(gpio::Flags flags) {
flags = (gpio::Flags)(flags & ~(gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN));
if (flags == gpio::FLAG_NONE) {
return GPIO_MODE_DISABLE;
@@ -61,6 +30,18 @@ gpio_mode_t IDFInternalGPIOPin::flags_to_mode(gpio::Flags flags) {
}
}
+struct ISRPinArg {
+ gpio_num_t pin;
+ bool inverted;
+};
+
+ISRInternalGPIOPin IDFInternalGPIOPin::to_isr() const {
+ auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
+ arg->pin = pin_;
+ arg->inverted = inverted_;
+ return ISRInternalGPIOPin((void *) arg);
+}
+
void IDFInternalGPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const {
gpio_int_type_t idf_type = GPIO_INTR_ANYEDGE;
switch (type) {
@@ -99,6 +80,35 @@ std::string IDFInternalGPIOPin::dump_summary() const {
return buffer;
}
+void IDFInternalGPIOPin::setup() {
+ gpio_config_t conf{};
+ conf.pin_bit_mask = 1ULL << static_cast(pin_);
+ conf.mode = flags_to_mode(flags_);
+ conf.pull_up_en = flags_ & gpio::FLAG_PULLUP ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE;
+ conf.pull_down_en = flags_ & gpio::FLAG_PULLDOWN ? GPIO_PULLDOWN_ENABLE : GPIO_PULLDOWN_DISABLE;
+ conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_config(&conf);
+ gpio_set_drive_capability(pin_, drive_strength_);
+}
+
+void IDFInternalGPIOPin::pin_mode(gpio::Flags flags) {
+ // can't call gpio_config here because that logs in esp-idf which may cause issues
+ gpio_set_direction(pin_, flags_to_mode(flags));
+ gpio_pull_mode_t pull_mode = GPIO_FLOATING;
+ if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
+ pull_mode = GPIO_PULLUP_PULLDOWN;
+ } else if (flags & gpio::FLAG_PULLUP) {
+ pull_mode = GPIO_PULLUP_ONLY;
+ } else if (flags & gpio::FLAG_PULLDOWN) {
+ pull_mode = GPIO_PULLDOWN_ONLY;
+ }
+ gpio_set_pull_mode(pin_, pull_mode);
+}
+
+bool IDFInternalGPIOPin::digital_read() { return bool(gpio_get_level(pin_)) != inverted_; }
+void IDFInternalGPIOPin::digital_write(bool value) { gpio_set_level(pin_, value != inverted_ ? 1 : 0); }
+void IDFInternalGPIOPin::detach_interrupt() const { gpio_intr_disable(pin_); }
+
} // namespace esp32
using namespace esp32;
@@ -114,6 +124,19 @@ void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
// not supported
}
+void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
+ auto *arg = reinterpret_cast(arg_);
+ gpio_set_direction(arg->pin, flags_to_mode(flags));
+ gpio_pull_mode_t pull_mode = GPIO_FLOATING;
+ if (flags & (gpio::FLAG_PULLUP | gpio::FLAG_PULLDOWN)) {
+ pull_mode = GPIO_PULLUP_PULLDOWN;
+ } else if (flags & gpio::FLAG_PULLUP) {
+ pull_mode = GPIO_PULLUP_ONLY;
+ } else if (flags & gpio::FLAG_PULLDOWN) {
+ pull_mode = GPIO_PULLDOWN_ONLY;
+ }
+ gpio_set_pull_mode(arg->pin, pull_mode);
+}
} // namespace esphome
diff --git a/esphome/components/esp32/gpio_idf.h b/esphome/components/esp32/gpio_idf.h
index a99571cc46..a07d11378a 100644
--- a/esphome/components/esp32/gpio_idf.h
+++ b/esphome/components/esp32/gpio_idf.h
@@ -18,13 +18,12 @@ class IDFInternalGPIOPin : public InternalGPIOPin {
bool digital_read() override;
void digital_write(bool value) override;
std::string dump_summary() const override;
- void detach_interrupt() const override { gpio_intr_disable(pin_); }
+ void detach_interrupt() const override;
ISRInternalGPIOPin to_isr() const override;
uint8_t get_pin() const override { return (uint8_t) pin_; }
bool is_inverted() const override { return inverted_; }
protected:
- static gpio_mode_t flags_to_mode(gpio::Flags flags);
void attach_interrupt(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
gpio_num_t pin_;
diff --git a/esphome/components/esp32_ble_tracker/__init__.py b/esphome/components/esp32_ble_tracker/__init__.py
index e3d52f345a..e647b74a8f 100644
--- a/esphome/components/esp32_ble_tracker/__init__.py
+++ b/esphome/components/esp32_ble_tracker/__init__.py
@@ -18,7 +18,6 @@ from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
DEPENDENCIES = ["esp32"]
-AUTO_LOAD = ["xiaomi_ble", "ruuvi_ble"]
CONF_ESP32_BLE_ID = "esp32_ble_id"
CONF_SCAN_PARAMETERS = "scan_parameters"
diff --git a/esphome/components/esp32_camera/__init__.py b/esphome/components/esp32_camera/__init__.py
index 7f3aebe238..2b1890267f 100644
--- a/esphome/components/esp32_camera/__init__.py
+++ b/esphome/components/esp32_camera/__init__.py
@@ -2,10 +2,8 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import (
- CONF_DISABLED_BY_DEFAULT,
CONF_FREQUENCY,
CONF_ID,
- CONF_NAME,
CONF_PIN,
CONF_SCL,
CONF_SDA,
@@ -17,8 +15,9 @@ from esphome.const import (
)
from esphome.core import CORE
from esphome.components.esp32 import add_idf_sdkconfig_option
+from esphome.cpp_helpers import setup_entity
-DEPENDENCIES = ["esp32", "api"]
+DEPENDENCIES = ["esp32"]
esp32_camera_ns = cg.esphome_ns.namespace("esp32_camera")
ESP32Camera = esp32_camera_ns.class_("ESP32Camera", cg.PollingComponent, cg.EntityBase)
@@ -63,11 +62,9 @@ CONF_TEST_PATTERN = "test_pattern"
camera_range_param = cv.int_range(min=-2, max=2)
-CONFIG_SCHEMA = cv.Schema(
+CONFIG_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ESP32Camera),
- cv.Required(CONF_NAME): cv.string,
- cv.Optional(CONF_DISABLED_BY_DEFAULT, default=False): cv.boolean,
cv.Required(CONF_DATA_PINS): cv.All(
[pins.internal_gpio_input_pin_number], cv.Length(min=8, max=8)
),
@@ -127,8 +124,8 @@ SETTERS = {
async def to_code(config):
- var = cg.new_Pvariable(config[CONF_ID], config[CONF_NAME])
- cg.add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
+ var = cg.new_Pvariable(config[CONF_ID])
+ await setup_entity(var, config)
await cg.register_component(var, config)
for key, setter in SETTERS.items():
diff --git a/esphome/components/esp32_camera/esp32_camera.cpp b/esphome/components/esp32_camera/esp32_camera.cpp
index babfda4113..6f93532f47 100644
--- a/esphome/components/esp32_camera/esp32_camera.cpp
+++ b/esphome/components/esp32_camera/esp32_camera.cpp
@@ -45,6 +45,7 @@ void ESP32Camera::dump_config() {
auto conf = this->config_;
ESP_LOGCONFIG(TAG, "ESP32 Camera:");
ESP_LOGCONFIG(TAG, " Name: %s", this->name_.c_str());
+ ESP_LOGCONFIG(TAG, " Internal: %s", YESNO(this->internal_));
#ifdef USE_ARDUINO
ESP_LOGCONFIG(TAG, " Board Has PSRAM: %s", YESNO(psramFound()));
#endif // USE_ARDUINO
@@ -185,6 +186,7 @@ ESP32Camera::ESP32Camera(const std::string &name) : EntityBase(name) {
global_esp32_camera = this;
}
+ESP32Camera::ESP32Camera() : ESP32Camera("") {}
void ESP32Camera::set_data_pins(std::array pins) {
this->config_.pin_d0 = pins[0];
this->config_.pin_d1 = pins[1];
diff --git a/esphome/components/esp32_camera/esp32_camera.h b/esphome/components/esp32_camera/esp32_camera.h
index d0445607a4..84f8d9cbea 100644
--- a/esphome/components/esp32_camera/esp32_camera.h
+++ b/esphome/components/esp32_camera/esp32_camera.h
@@ -54,6 +54,7 @@ enum ESP32CameraFrameSize {
class ESP32Camera : public Component, public EntityBase {
public:
ESP32Camera(const std::string &name);
+ ESP32Camera();
void set_data_pins(std::array pins);
void set_vsync_pin(uint8_t pin);
void set_href_pin(uint8_t pin);
diff --git a/esphome/components/esp32_camera_web_server/__init__.py b/esphome/components/esp32_camera_web_server/__init__.py
new file mode 100644
index 0000000000..d8afea27b4
--- /dev/null
+++ b/esphome/components/esp32_camera_web_server/__init__.py
@@ -0,0 +1,28 @@
+import esphome.config_validation as cv
+import esphome.codegen as cg
+from esphome.const import CONF_ID, CONF_PORT, CONF_MODE
+
+CODEOWNERS = ["@ayufan"]
+DEPENDENCIES = ["esp32_camera"]
+MULTI_CONF = True
+
+esp32_camera_web_server_ns = cg.esphome_ns.namespace("esp32_camera_web_server")
+CameraWebServer = esp32_camera_web_server_ns.class_("CameraWebServer", cg.Component)
+Mode = esp32_camera_web_server_ns.enum("Mode")
+
+MODES = {"STREAM": Mode.STREAM, "SNAPSHOT": Mode.SNAPSHOT}
+
+CONFIG_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(CameraWebServer),
+ cv.Required(CONF_PORT): cv.port,
+ cv.Required(CONF_MODE): cv.enum(MODES, upper=True),
+ },
+).extend(cv.COMPONENT_SCHEMA)
+
+
+async def to_code(config):
+ server = cg.new_Pvariable(config[CONF_ID])
+ cg.add(server.set_port(config[CONF_PORT]))
+ cg.add(server.set_mode(config[CONF_MODE]))
+ await cg.register_component(server, config)
diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.cpp b/esphome/components/esp32_camera_web_server/camera_web_server.cpp
new file mode 100644
index 0000000000..c9a684c7e5
--- /dev/null
+++ b/esphome/components/esp32_camera_web_server/camera_web_server.cpp
@@ -0,0 +1,240 @@
+#ifdef USE_ESP32
+
+#include "camera_web_server.h"
+#include "esphome/core/application.h"
+#include "esphome/core/hal.h"
+#include "esphome/core/helpers.h"
+#include "esphome/core/log.h"
+#include "esphome/core/util.h"
+
+#include
+#include
+#include
+
+namespace esphome {
+namespace esp32_camera_web_server {
+
+static const int IMAGE_REQUEST_TIMEOUT = 2000;
+static const char *const TAG = "esp32_camera_web_server";
+
+#define PART_BOUNDARY "123456789000000000000987654321"
+#define CONTENT_TYPE "image/jpeg"
+#define CONTENT_LENGTH "Content-Length"
+
+static const char *const STREAM_HEADER =
+ "HTTP/1.1 200\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: multipart/x-mixed-replace;boundary=" PART_BOUNDARY
+ "\r\n";
+static const char *const STREAM_500 = "HTTP/1.1 500\r\nContent-Type: text/plain\r\n\r\nNo frames send.\r\n";
+static const char *const STREAM_BOUNDARY = "\r\n--" PART_BOUNDARY "\r\n";
+static const char *const STREAM_PART = "Content-Type: " CONTENT_TYPE "\r\n" CONTENT_LENGTH ": %u\r\n\r\n";
+
+CameraWebServer::CameraWebServer() {}
+
+CameraWebServer::~CameraWebServer() {}
+
+void CameraWebServer::setup() {
+ if (!esp32_camera::global_esp32_camera || esp32_camera::global_esp32_camera->is_failed()) {
+ this->mark_failed();
+ return;
+ }
+
+ this->semaphore_ = xSemaphoreCreateBinary();
+
+ httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+ config.server_port = this->port_;
+ config.ctrl_port = this->port_;
+ config.max_open_sockets = 1;
+ config.backlog_conn = 2;
+
+ if (httpd_start(&this->httpd_, &config) != ESP_OK) {
+ mark_failed();
+ return;
+ }
+
+ httpd_uri_t uri = {
+ .uri = "/",
+ .method = HTTP_GET,
+ .handler = [](struct httpd_req *req) { return ((CameraWebServer *) req->user_ctx)->handler_(req); },
+ .user_ctx = this};
+
+ httpd_register_uri_handler(this->httpd_, &uri);
+
+ esp32_camera::global_esp32_camera->add_image_callback([this](std::shared_ptr image) {
+ if (this->running_) {
+ this->image_ = std::move(image);
+ xSemaphoreGive(this->semaphore_);
+ }
+ });
+}
+
+void CameraWebServer::on_shutdown() {
+ this->running_ = false;
+ this->image_ = nullptr;
+ httpd_stop(this->httpd_);
+ this->httpd_ = nullptr;
+ vSemaphoreDelete(this->semaphore_);
+ this->semaphore_ = nullptr;
+}
+
+void CameraWebServer::dump_config() {
+ ESP_LOGCONFIG(TAG, "ESP32 Camera Web Server:");
+ ESP_LOGCONFIG(TAG, " Port: %d", this->port_);
+ if (this->mode_ == STREAM)
+ ESP_LOGCONFIG(TAG, " Mode: stream");
+ else
+ ESP_LOGCONFIG(TAG, " Mode: snapshot");
+
+ if (this->is_failed()) {
+ ESP_LOGE(TAG, " Setup Failed");
+ }
+}
+
+float CameraWebServer::get_setup_priority() const { return setup_priority::LATE; }
+
+void CameraWebServer::loop() {
+ if (!this->running_) {
+ this->image_ = nullptr;
+ }
+}
+
+std::shared_ptr CameraWebServer::wait_for_image_() {
+ std::shared_ptr image;
+ image.swap(this->image_);
+
+ if (!image) {
+ // retry as we might still be fetching image
+ xSemaphoreTake(this->semaphore_, IMAGE_REQUEST_TIMEOUT / portTICK_PERIOD_MS);
+ image.swap(this->image_);
+ }
+
+ return image;
+}
+
+esp_err_t CameraWebServer::handler_(struct httpd_req *req) {
+ esp_err_t res = ESP_FAIL;
+
+ this->image_ = nullptr;
+ this->running_ = true;
+
+ switch (this->mode_) {
+ case STREAM:
+ res = this->streaming_handler_(req);
+ break;
+
+ case SNAPSHOT:
+ res = this->snapshot_handler_(req);
+ break;
+ }
+
+ this->running_ = false;
+ this->image_ = nullptr;
+ return res;
+}
+
+static esp_err_t httpd_send_all(httpd_req_t *r, const char *buf, size_t buf_len) {
+ int ret;
+
+ while (buf_len > 0) {
+ ret = httpd_send(r, buf, buf_len);
+ if (ret < 0) {
+ return ESP_FAIL;
+ }
+ buf += ret;
+ buf_len -= ret;
+ }
+ return ESP_OK;
+}
+
+esp_err_t CameraWebServer::streaming_handler_(struct httpd_req *req) {
+ esp_err_t res = ESP_OK;
+ char part_buf[64];
+
+ // This manually constructs HTTP response to avoid chunked encoding
+ // which is not supported by some clients
+
+ res = httpd_send_all(req, STREAM_HEADER, strlen(STREAM_HEADER));
+ if (res != ESP_OK) {
+ ESP_LOGW(TAG, "STREAM: failed to set HTTP header");
+ return res;
+ }
+
+ uint32_t last_frame = millis();
+ uint32_t frames = 0;
+
+ while (res == ESP_OK && this->running_) {
+ if (esp32_camera::global_esp32_camera != nullptr) {
+ esp32_camera::global_esp32_camera->request_stream();
+ }
+
+ auto image = this->wait_for_image_();
+
+ if (!image) {
+ ESP_LOGW(TAG, "STREAM: failed to acquire frame");
+ res = ESP_FAIL;
+ }
+ if (res == ESP_OK) {
+ res = httpd_send_all(req, STREAM_BOUNDARY, strlen(STREAM_BOUNDARY));
+ }
+ if (res == ESP_OK) {
+ size_t hlen = snprintf(part_buf, 64, STREAM_PART, image->get_data_length());
+ res = httpd_send_all(req, part_buf, hlen);
+ }
+ if (res == ESP_OK) {
+ res = httpd_send_all(req, (const char *) image->get_data_buffer(), image->get_data_length());
+ }
+ if (res == ESP_OK) {
+ frames++;
+ int64_t frame_time = millis() - last_frame;
+ last_frame = millis();
+
+ ESP_LOGD(TAG, "MJPG: %uB %ums (%.1ffps)", (uint32_t) image->get_data_length(), (uint32_t) frame_time,
+ 1000.0 / (uint32_t) frame_time);
+ }
+ }
+
+ if (!frames) {
+ res = httpd_send_all(req, STREAM_500, strlen(STREAM_500));
+ }
+
+ ESP_LOGI(TAG, "STREAM: closed. Frames: %u", frames);
+
+ return res;
+}
+
+esp_err_t CameraWebServer::snapshot_handler_(struct httpd_req *req) {
+ esp_err_t res = ESP_OK;
+
+ if (esp32_camera::global_esp32_camera != nullptr) {
+ esp32_camera::global_esp32_camera->request_image();
+ }
+
+ auto image = this->wait_for_image_();
+
+ if (!image) {
+ ESP_LOGW(TAG, "SNAPSHOT: failed to acquire frame");
+ httpd_resp_send_500(req);
+ res = ESP_FAIL;
+ return res;
+ }
+
+ res = httpd_resp_set_type(req, CONTENT_TYPE);
+ if (res != ESP_OK) {
+ ESP_LOGW(TAG, "SNAPSHOT: failed to set HTTP response type");
+ return res;
+ }
+
+ httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
+
+ if (res == ESP_OK) {
+ res = httpd_resp_set_hdr(req, CONTENT_LENGTH, esphome::to_string(image->get_data_length()).c_str());
+ }
+ if (res == ESP_OK) {
+ res = httpd_resp_send(req, (const char *) image->get_data_buffer(), image->get_data_length());
+ }
+ return res;
+}
+
+} // namespace esp32_camera_web_server
+} // namespace esphome
+
+#endif // USE_ESP32
diff --git a/esphome/components/esp32_camera_web_server/camera_web_server.h b/esphome/components/esp32_camera_web_server/camera_web_server.h
new file mode 100644
index 0000000000..df30a43ed2
--- /dev/null
+++ b/esphome/components/esp32_camera_web_server/camera_web_server.h
@@ -0,0 +1,51 @@
+#pragma once
+
+#ifdef USE_ESP32
+
+#include
+#include
+
+#include "esphome/components/esp32_camera/esp32_camera.h"
+#include "esphome/core/component.h"
+#include "esphome/core/helpers.h"
+#include "esphome/core/preferences.h"
+
+struct httpd_req;
+
+namespace esphome {
+namespace esp32_camera_web_server {
+
+enum Mode { STREAM, SNAPSHOT };
+
+class CameraWebServer : public Component {
+ public:
+ CameraWebServer();
+ ~CameraWebServer();
+
+ void setup() override;
+ void on_shutdown() override;
+ void dump_config() override;
+ float get_setup_priority() const override;
+ void set_port(uint16_t port) { this->port_ = port; }
+ void set_mode(Mode mode) { this->mode_ = mode; }
+ void loop() override;
+
+ protected:
+ std::shared_ptr wait_for_image_();
+ esp_err_t handler_(struct httpd_req *req);
+ 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_;
+ std::shared_ptr image_;
+ bool running_{false};
+ Mode mode_{STREAM};
+};
+
+} // namespace esp32_camera_web_server
+} // namespace esphome
+
+#endif // USE_ESP32
diff --git a/esphome/components/esp8266/__init__.py b/esphome/components/esp8266/__init__.py
index b2706bd4fb..34c792499d 100644
--- a/esphome/components/esp8266/__init__.py
+++ b/esphome/components/esp8266/__init__.py
@@ -93,12 +93,12 @@ def _arduino_check_versions(value):
platform_version = value.get(CONF_PLATFORM_VERSION)
if platform_version is None:
if version >= cv.Version(3, 0, 0):
- platform_version = ARDUINO_3_PLATFORM_VERSION
+ platform_version = _parse_platform_version(str(ARDUINO_3_PLATFORM_VERSION))
elif version >= cv.Version(2, 5, 0):
- platform_version = ARDUINO_2_PLATFORM_VERSION
+ platform_version = _parse_platform_version(str(ARDUINO_2_PLATFORM_VERSION))
else:
- platform_version = cv.Version(1, 8, 0)
- value[CONF_PLATFORM_VERSION] = str(platform_version)
+ platform_version = _parse_platform_version(str(cv.Version(1, 8, 0)))
+ value[CONF_PLATFORM_VERSION] = platform_version
if version != RECOMMENDED_ARDUINO_FRAMEWORK_VERSION:
_LOGGER.warning(
@@ -109,13 +109,22 @@ def _arduino_check_versions(value):
return value
+def _parse_platform_version(value):
+ try:
+ # if platform version is a valid version constraint, prefix the default package
+ cv.platformio_version_constraint(value)
+ return f"platformio/espressif8266 @ {value}"
+ except cv.Invalid:
+ return value
+
+
CONF_PLATFORM_VERSION = "platform_version"
ARDUINO_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
- cv.Optional(CONF_PLATFORM_VERSION): cv.string_strict,
+ cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
}
),
_arduino_check_versions,
@@ -142,21 +151,22 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config):
cg.add(esp8266_ns.setup_preferences())
+ cg.add_platformio_option("lib_ldf_mode", "off")
+
cg.add_platformio_option("board", config[CONF_BOARD])
cg.add_build_flag("-DUSE_ESP8266")
cg.add_define("ESPHOME_BOARD", config[CONF_BOARD])
+ cg.add_define("ESPHOME_VARIANT", "ESP8266")
conf = config[CONF_FRAMEWORK]
cg.add_platformio_option("framework", "arduino")
cg.add_build_flag("-DUSE_ARDUINO")
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
+ cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
)
- cg.add_platformio_option(
- "platform", f"platformio/espressif8266 @ {conf[CONF_PLATFORM_VERSION]}"
- )
# Default for platformio is LWIP2_LOW_MEMORY with:
# - MSS=536
diff --git a/esphome/components/esp8266/core.cpp b/esphome/components/esp8266/core.cpp
index b78600e7a3..51f3ca50ec 100644
--- a/esphome/components/esp8266/core.cpp
+++ b/esphome/components/esp8266/core.cpp
@@ -12,7 +12,7 @@ void IRAM_ATTR HOT yield() { ::yield(); }
uint32_t IRAM_ATTR HOT millis() { return ::millis(); }
void IRAM_ATTR HOT delay(uint32_t ms) { ::delay(ms); }
uint32_t IRAM_ATTR HOT micros() { return ::micros(); }
-void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { ::delayMicroseconds(us); }
+void IRAM_ATTR HOT delayMicroseconds(uint32_t us) { delay_microseconds_safe(us); }
void arch_restart() {
ESP.restart(); // NOLINT(readability-static-accessed-through-instance)
// restart() doesn't always end execution
diff --git a/esphome/components/esp8266/gpio.cpp b/esphome/components/esp8266/gpio.cpp
index 7805889b40..2660318182 100644
--- a/esphome/components/esp8266/gpio.cpp
+++ b/esphome/components/esp8266/gpio.cpp
@@ -8,6 +8,29 @@ namespace esp8266 {
static const char *const TAG = "esp8266";
+static int IRAM_ATTR flags_to_mode(gpio::Flags flags, uint8_t pin) {
+ if (flags == gpio::FLAG_INPUT) {
+ return INPUT;
+ } else if (flags == gpio::FLAG_OUTPUT) {
+ return OUTPUT;
+ } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
+ if (pin == 16) {
+ // GPIO16 doesn't have a pullup, so pinMode would fail.
+ // However, sometimes this method is called with pullup mode anyway
+ // for example from dallas one_wire. For those cases convert this
+ // to a INPUT mode.
+ return INPUT;
+ }
+ return INPUT_PULLUP;
+ } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
+ return INPUT_PULLDOWN_16;
+ } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
+ return OUTPUT_OPEN_DRAIN;
+ } else {
+ return 0;
+ }
+}
+
struct ISRPinArg {
uint8_t pin;
bool inverted;
@@ -43,28 +66,7 @@ void ESP8266GPIOPin::attach_interrupt(void (*func)(void *), void *arg, gpio::Int
attachInterruptArg(pin_, func, arg, arduino_mode);
}
void ESP8266GPIOPin::pin_mode(gpio::Flags flags) {
- uint8_t mode;
- if (flags == gpio::FLAG_INPUT) {
- mode = INPUT;
- } else if (flags == gpio::FLAG_OUTPUT) {
- mode = OUTPUT;
- } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
- mode = INPUT_PULLUP;
- if (pin_ == 16) {
- // GPIO16 doesn't have a pullup, so pinMode would fail.
- // However, sometimes this method is called with pullup mode anyway
- // for example from dallas one_wire. For those cases convert this
- // to a INPUT mode.
- mode = INPUT;
- }
- } else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLDOWN)) {
- mode = INPUT_PULLDOWN_16;
- } else if (flags == (gpio::FLAG_OUTPUT | gpio::FLAG_OPEN_DRAIN)) {
- mode = OUTPUT_OPEN_DRAIN;
- } else {
- return;
- }
- pinMode(pin_, mode); // NOLINT
+ pinMode(pin_, flags_to_mode(flags, pin_)); // NOLINT
}
std::string ESP8266GPIOPin::dump_summary() const {
@@ -97,6 +99,10 @@ void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
auto *arg = reinterpret_cast(arg_);
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1UL << arg->pin);
}
+void IRAM_ATTR ISRInternalGPIOPin::pin_mode(gpio::Flags flags) {
+ auto *arg = reinterpret_cast(arg_);
+ pinMode(arg->pin, flags_to_mode(flags, arg->pin)); // NOLINT
+}
} // namespace esphome
diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp
index d55db0a7d8..00f68df2b4 100644
--- a/esphome/components/ethernet/ethernet_component.cpp
+++ b/esphome/components/ethernet/ethernet_component.cpp
@@ -168,7 +168,9 @@ void EthernetComponent::start_connect_() {
esp_err_t err;
err = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_ETH, App.get_name().c_str());
- ESPHL_ERROR_CHECK(err, "ETH set hostname error");
+ if (err != ERR_OK) {
+ ESP_LOGW(TAG, "tcpip_adapter_set_hostname failed: %s", esp_err_to_name(err));
+ }
tcpip_adapter_ip_info_t info;
if (this->manual_ip_.has_value()) {
diff --git a/esphome/components/external_components/__init__.py b/esphome/components/external_components/__init__.py
index 110a8d95ed..e0548e8981 100644
--- a/esphome/components/external_components/__init__.py
+++ b/esphome/components/external_components/__init__.py
@@ -43,19 +43,27 @@ def validate_source_shorthand(value):
# Regex for GitHub repo name with optional branch/tag
# Note: git allows other branch/tag names as well, but never seen them used before
m = re.match(
- r"github://([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?",
+ r"github://(?:([a-zA-Z0-9\-]+)/([a-zA-Z0-9\-\._]+)(?:@([a-zA-Z0-9\-_.\./]+))?|pr#([0-9]+))",
value,
)
if m is None:
raise cv.Invalid(
- "Source is not a file system path or in expected github://username/name[@branch-or-tag] format!"
+ "Source is not a file system path, in expected github://username/name[@branch-or-tag] or github://pr#1234 format!"
)
- conf = {
- CONF_TYPE: TYPE_GIT,
- CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
- }
- if m.group(3):
- conf[CONF_REF] = m.group(3)
+ if m.group(4):
+ conf = {
+ CONF_TYPE: TYPE_GIT,
+ CONF_URL: "https://github.com/esphome/esphome.git",
+ CONF_REF: f"pull/{m.group(4)}/head",
+ }
+ else:
+ conf = {
+ CONF_TYPE: TYPE_GIT,
+ CONF_URL: f"https://github.com/{m.group(1)}/{m.group(2)}.git",
+ }
+ if m.group(3):
+ conf[CONF_REF] = m.group(3)
+
return SOURCE_SCHEMA(conf)
diff --git a/esphome/components/ezo/ezo.cpp b/esphome/components/ezo/ezo.cpp
index 81597f3466..7f7a41fb41 100644
--- a/esphome/components/ezo/ezo.cpp
+++ b/esphome/components/ezo/ezo.cpp
@@ -74,7 +74,7 @@ void EZOSensor::loop() {
if (buf[0] != 1)
return;
- float val = strtof((char *) &buf[1], nullptr);
+ float val = parse_number((char *) &buf[1], sizeof(buf) - 1).value_or(0);
this->publish_state(val);
}
diff --git a/esphome/components/fingerprint_grow/sensor.py b/esphome/components/fingerprint_grow/sensor.py
index f359a10348..4ae670743d 100644
--- a/esphome/components/fingerprint_grow/sensor.py
+++ b/esphome/components/fingerprint_grow/sensor.py
@@ -8,6 +8,7 @@ from esphome.const import (
CONF_LAST_FINGER_ID,
CONF_SECURITY_LEVEL,
CONF_STATUS,
+ ENTITY_CATEGORY_DIAGNOSTIC,
ICON_ACCOUNT,
ICON_ACCOUNT_CHECK,
ICON_DATABASE,
@@ -26,30 +27,36 @@ CONFIG_SCHEMA = cv.Schema(
icon=ICON_FINGERPRINT,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_STATUS): sensor.sensor_schema(
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_CAPACITY): sensor.sensor_schema(
icon=ICON_DATABASE,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_SECURITY_LEVEL): sensor.sensor_schema(
icon=ICON_SECURITY,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_FINGER_ID): sensor.sensor_schema(
icon=ICON_ACCOUNT,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_LAST_CONFIDENCE): sensor.sensor_schema(
icon=ICON_ACCOUNT_CHECK,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp
index b6f2acdbe4..f5e73c8854 100644
--- a/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp
+++ b/esphome/components/homeassistant/sensor/homeassistant_sensor.cpp
@@ -10,7 +10,7 @@ static const char *const TAG = "homeassistant.sensor";
void HomeassistantSensor::setup() {
api::global_api_server->subscribe_home_assistant_state(
this->entity_id_, this->attribute_, [this](const std::string &state) {
- auto val = parse_float(state);
+ auto val = parse_number(state);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
this->publish_state(NAN);
diff --git a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp
index cf6c9eea65..bd1c82c96b 100644
--- a/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp
+++ b/esphome/components/hrxl_maxsonar_wr/hrxl_maxsonar_wr.cpp
@@ -44,7 +44,7 @@ void HrxlMaxsonarWrComponent::check_buffer_() {
if (this->buffer_.length() == MAX_DATA_LENGTH_BYTES && this->buffer_[0] == 'R' &&
this->buffer_.back() == static_cast(ASCII_CR)) {
- int millimeters = strtol(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2).c_str(), nullptr, 10);
+ int millimeters = parse_number(this->buffer_.substr(1, MAX_DATA_LENGTH_BYTES - 2)).value_or(0);
float meters = float(millimeters) / 1000.0;
ESP_LOGV(TAG, "Distance from sensor: %d mm, %f m", millimeters, meters);
this->publish_state(meters);
diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py
index 6e249c4247..774d6a0f91 100644
--- a/esphome/components/http_request/__init__.py
+++ b/esphome/components/http_request/__init__.py
@@ -96,6 +96,8 @@ async def to_code(config):
if CORE.is_esp32:
cg.add_library("WiFiClientSecure", None)
cg.add_library("HTTPClient", None)
+ if CORE.is_esp8266:
+ cg.add_library("ESP8266HTTPClient", None)
await cg.register_component(var, config)
diff --git a/esphome/components/htu21d/htu21d.cpp b/esphome/components/htu21d/htu21d.cpp
index b53284ae3f..a38ec73019 100644
--- a/esphome/components/htu21d/htu21d.cpp
+++ b/esphome/components/htu21d/htu21d.cpp
@@ -9,8 +9,8 @@ static const char *const TAG = "htu21d";
static const uint8_t HTU21D_ADDRESS = 0x40;
static const uint8_t HTU21D_REGISTER_RESET = 0xFE;
-static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xE3;
-static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xE5;
+static const uint8_t HTU21D_REGISTER_TEMPERATURE = 0xF3;
+static const uint8_t HTU21D_REGISTER_HUMIDITY = 0xF5;
static const uint8_t HTU21D_REGISTER_STATUS = 0xE7;
void HTU21DComponent::setup() {
diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h
index 7ee4cdd811..50a0b3ae50 100644
--- a/esphome/components/i2c/i2c.h
+++ b/esphome/components/i2c/i2c.h
@@ -1,6 +1,7 @@
#pragma once
#include "i2c_bus.h"
+#include "esphome/core/helpers.h"
#include "esphome/core/optional.h"
#include
#include
@@ -32,16 +33,8 @@ class I2CRegister {
// like ntohs/htons but without including networking headers.
// ("i2c" byte order is big-endian)
-inline uint16_t i2ctohs(uint16_t i2cshort) {
- union {
- uint16_t x;
- uint8_t y[2];
- } conv;
- conv.x = i2cshort;
- return ((uint16_t) conv.y[0] << 8) | ((uint16_t) conv.y[1] << 0);
-}
-
-inline uint16_t htoi2cs(uint16_t hostshort) { return i2ctohs(hostshort); }
+inline uint16_t i2ctohs(uint16_t i2cshort) { return convert_big_endian(i2cshort); }
+inline uint16_t htoi2cs(uint16_t hostshort) { return convert_big_endian(hostshort); }
class I2CDevice {
public:
diff --git a/esphome/components/improv/improv.cpp b/esphome/components/improv/improv.cpp
index 4f6ed7702d..94068bc626 100644
--- a/esphome/components/improv/improv.cpp
+++ b/esphome/components/improv/improv.cpp
@@ -7,11 +7,13 @@ ImprovCommand parse_improv_data(const std::vector &data) {
}
ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
+ ImprovCommand improv_command;
Command command = (Command) data[0];
uint8_t data_length = data[1];
if (data_length != length - 3) {
- return {.command = UNKNOWN};
+ improv_command.command = UNKNOWN;
+ return improv_command;
}
uint8_t checksum = data[length - 1];
@@ -22,7 +24,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
}
if ((uint8_t) calculated_checksum != checksum) {
- return {.command = BAD_CHECKSUM};
+ improv_command.command = BAD_CHECKSUM;
+ return improv_command;
}
if (command == WIFI_SETTINGS) {
@@ -39,9 +42,8 @@ ImprovCommand parse_improv_data(const uint8_t *data, size_t length) {
return {.command = command, .ssid = ssid, .password = password};
}
- return {
- .command = command,
- };
+ improv_command.command = command;
+ return improv_command;
}
std::vector build_rpc_response(Command command, const std::vector &datum) {
diff --git a/esphome/components/improv/improv.h b/esphome/components/improv/improv.h
index 0ead80e2cf..542eb82bd3 100644
--- a/esphome/components/improv/improv.h
+++ b/esphome/components/improv/improv.h
@@ -1,8 +1,8 @@
#pragma once
-#ifdef USE_ARDUINO
+#ifdef ARDUINO
#include "WString.h"
-#endif // USE_ARDUINO
+#endif // ARDUINO
#include
#include
@@ -38,6 +38,8 @@ enum Command : uint8_t {
UNKNOWN = 0x00,
WIFI_SETTINGS = 0x01,
IDENTIFY = 0x02,
+ GET_CURRENT_STATE = 0x02,
+ GET_DEVICE_INFO = 0x03,
BAD_CHECKSUM = 0xFF,
};
@@ -53,8 +55,8 @@ ImprovCommand parse_improv_data(const std::vector &data);
ImprovCommand parse_improv_data(const uint8_t *data, size_t length);
std::vector build_rpc_response(Command command, const std::vector &datum);
-#ifdef USE_ARDUINO
+#ifdef ARDUINO
std::vector build_rpc_response(Command command, const std::vector &datum);
-#endif // USE_ARDUINO
+#endif // ARDUINO
} // namespace improv
diff --git a/esphome/components/improv_serial/__init__.py b/esphome/components/improv_serial/__init__.py
new file mode 100644
index 0000000000..b1cdc2d93e
--- /dev/null
+++ b/esphome/components/improv_serial/__init__.py
@@ -0,0 +1,33 @@
+from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_LOGGER
+import esphome.codegen as cg
+import esphome.config_validation as cv
+import esphome.final_validate as fv
+
+CODEOWNERS = ["@esphome/core"]
+DEPENDENCIES = ["logger", "wifi"]
+AUTO_LOAD = ["improv"]
+
+improv_serial_ns = cg.esphome_ns.namespace("improv_serial")
+
+ImprovSerialComponent = improv_serial_ns.class_("ImprovSerialComponent", cg.Component)
+
+CONFIG_SCHEMA = cv.Schema(
+ {
+ cv.GenerateID(): cv.declare_id(ImprovSerialComponent),
+ }
+).extend(cv.COMPONENT_SCHEMA)
+
+
+def validate_logger_baud_rate(config):
+ logger_conf = fv.full_config.get()[CONF_LOGGER]
+ if logger_conf[CONF_BAUD_RATE] == 0:
+ raise cv.Invalid("improv_serial requires the logger baud_rate to be not 0")
+ return config
+
+
+FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
+
+
+async def to_code(config):
+ var = cg.new_Pvariable(config[CONF_ID])
+ await cg.register_component(var, config)
diff --git a/esphome/components/improv_serial/improv_serial_component.cpp b/esphome/components/improv_serial/improv_serial_component.cpp
new file mode 100644
index 0000000000..a12f1bd83b
--- /dev/null
+++ b/esphome/components/improv_serial/improv_serial_component.cpp
@@ -0,0 +1,250 @@
+#include "improv_serial_component.h"
+
+#include "esphome/core/application.h"
+#include "esphome/core/defines.h"
+#include "esphome/core/hal.h"
+#include "esphome/core/log.h"
+#include "esphome/core/version.h"
+
+#include "esphome/components/logger/logger.h"
+
+namespace esphome {
+namespace improv_serial {
+
+static const char *const TAG = "improv_serial";
+
+void ImprovSerialComponent::setup() {
+ global_improv_serial_component = this;
+#ifdef USE_ARDUINO
+ this->hw_serial_ = logger::global_logger->get_hw_serial();
+#endif
+#ifdef USE_ESP_IDF
+ this->uart_num_ = logger::global_logger->get_uart_num();
+#endif
+
+ if (wifi::global_wifi_component->has_sta()) {
+ this->state_ = improv::STATE_PROVISIONED;
+ }
+}
+
+void ImprovSerialComponent::dump_config() { ESP_LOGCONFIG(TAG, "Improv Serial:"); }
+
+int ImprovSerialComponent::available_() {
+#ifdef USE_ARDUINO
+ return this->hw_serial_->available();
+#endif
+#ifdef USE_ESP_IDF
+ size_t available;
+ uart_get_buffered_data_len(this->uart_num_, &available);
+ return available;
+#endif
+}
+
+uint8_t ImprovSerialComponent::read_byte_() {
+ uint8_t data;
+#ifdef USE_ARDUINO
+ this->hw_serial_->readBytes(&data, 1);
+#endif
+#ifdef USE_ESP_IDF
+ uart_read_bytes(this->uart_num_, &data, 1, 20 / portTICK_RATE_MS);
+#endif
+ return data;
+}
+
+void ImprovSerialComponent::write_data_(std::vector &data) {
+ data.push_back('\n');
+#ifdef USE_ARDUINO
+ this->hw_serial_->write(data.data(), data.size());
+#endif
+#ifdef USE_ESP_IDF
+ uart_write_bytes(this->uart_num_, data.data(), data.size());
+#endif
+}
+
+void ImprovSerialComponent::loop() {
+ const uint32_t now = millis();
+ if (now - this->last_read_byte_ > 50) {
+ this->rx_buffer_.clear();
+ this->last_read_byte_ = now;
+ }
+
+ while (this->available_()) {
+ uint8_t byte = this->read_byte_();
+ if (this->parse_improv_serial_byte_(byte)) {
+ this->last_read_byte_ = now;
+ } else {
+ this->rx_buffer_.clear();
+ }
+ }
+
+ if (this->state_ == improv::STATE_PROVISIONING) {
+ if (wifi::global_wifi_component->is_connected()) {
+ wifi::global_wifi_component->save_wifi_sta(this->connecting_sta_.get_ssid(),
+ this->connecting_sta_.get_password());
+ this->connecting_sta_ = {};
+ this->cancel_timeout("wifi-connect-timeout");
+ this->set_state_(improv::STATE_PROVISIONED);
+
+ std::vector url = this->build_rpc_settings_response_(improv::WIFI_SETTINGS);
+ this->send_response_(url);
+ }
+ }
+}
+
+std::vector ImprovSerialComponent::build_rpc_settings_response_(improv::Command command) {
+ std::string url = "https://my.home-assistant.io/redirect/config_flow_start?domain=esphome";
+ std::vector urls = {url};
+#ifdef USE_WEBSERVER
+ auto ip = wifi::global_wifi_component->wifi_sta_ip();
+ std::string webserver_url = "http://" + ip.str() + ":" + to_string(WEBSERVER_PORT);
+ urls.push_back(webserver_url);
+#endif
+ std::vector data = improv::build_rpc_response(command, urls);
+ return data;
+}
+
+std::vector ImprovSerialComponent::build_version_info_() {
+ std::vector infos = {"ESPHome", ESPHOME_VERSION, ESPHOME_VARIANT, App.get_name()};
+ std::vector data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos);
+ return data;
+};
+
+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);
+ 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) {
+ if (type == TYPE_RPC) {
+ this->set_error_(improv::ERROR_NONE);
+ auto command = improv::parse_improv_data(&raw[9], data_len);
+ return this->parse_improv_payload_(command);
+ }
+ }
+ return true;
+}
+
+bool ImprovSerialComponent::parse_improv_payload_(improv::ImprovCommand &command) {
+ switch (command.command) {
+ case improv::BAD_CHECKSUM:
+ ESP_LOGW(TAG, "Error decoding Improv payload");
+ this->set_error_(improv::ERROR_INVALID_RPC);
+ return false;
+ case improv::WIFI_SETTINGS: {
+ wifi::WiFiAP sta{};
+ sta.set_ssid(command.ssid);
+ sta.set_password(command.password);
+ this->connecting_sta_ = sta;
+
+ wifi::global_wifi_component->set_sta(sta);
+ wifi::global_wifi_component->start_scanning();
+ this->set_state_(improv::STATE_PROVISIONING);
+ ESP_LOGD(TAG, "Received Improv wifi settings ssid=%s, password=" LOG_SECRET("%s"), command.ssid.c_str(),
+ command.password.c_str());
+
+ auto f = std::bind(&ImprovSerialComponent::on_wifi_connect_timeout_, this);
+ this->set_timeout("wifi-connect-timeout", 30000, f);
+ return true;
+ }
+ case improv::GET_CURRENT_STATE:
+ this->set_state_(this->state_);
+ if (this->state_ == improv::STATE_PROVISIONED) {
+ std::vector url = this->build_rpc_settings_response_(improv::GET_CURRENT_STATE);
+ this->send_response_(url);
+ }
+ return true;
+ case improv::GET_DEVICE_INFO: {
+ std::vector info = this->build_version_info_();
+ this->send_response_(info);
+ return true;
+ }
+ default: {
+ ESP_LOGW(TAG, "Unknown Improv payload");
+ this->set_error_(improv::ERROR_UNKNOWN_RPC);
+ return false;
+ }
+ }
+}
+
+void ImprovSerialComponent::set_state_(improv::State state) {
+ this->state_ = state;
+
+ std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+ data.resize(11);
+ data[6] = IMPROV_SERIAL_VERSION;
+ data[7] = TYPE_CURRENT_STATE;
+ data[8] = 1;
+ data[9] = state;
+
+ uint8_t checksum = 0x00;
+ for (uint8_t d : data)
+ checksum += d;
+ data[10] = checksum;
+
+ this->write_data_(data);
+}
+
+void ImprovSerialComponent::set_error_(improv::Error error) {
+ std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+ data.resize(11);
+ data[6] = IMPROV_SERIAL_VERSION;
+ data[7] = TYPE_ERROR_STATE;
+ data[8] = 1;
+ data[9] = error;
+
+ uint8_t checksum = 0x00;
+ for (uint8_t d : data)
+ checksum += d;
+ data[10] = checksum;
+ this->write_data_(data);
+}
+
+void ImprovSerialComponent::send_response_(std::vector &response) {
+ std::vector data = {'I', 'M', 'P', 'R', 'O', 'V'};
+ data.resize(9);
+ data[6] = IMPROV_SERIAL_VERSION;
+ data[7] = TYPE_RPC_RESPONSE;
+ data[8] = response.size();
+ data.insert(data.end(), response.begin(), response.end());
+ this->write_data_(data);
+}
+
+void ImprovSerialComponent::on_wifi_connect_timeout_() {
+ this->set_error_(improv::ERROR_UNABLE_TO_CONNECT);
+ this->set_state_(improv::STATE_AUTHORIZED);
+ ESP_LOGW(TAG, "Timed out trying to connect to given WiFi network");
+ wifi::global_wifi_component->clear_sta();
+}
+
+ImprovSerialComponent *global_improv_serial_component = // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+ nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
+} // namespace improv_serial
+} // namespace esphome
diff --git a/esphome/components/improv_serial/improv_serial_component.h b/esphome/components/improv_serial/improv_serial_component.h
new file mode 100644
index 0000000000..539674e2d3
--- /dev/null
+++ b/esphome/components/improv_serial/improv_serial_component.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "esphome/components/improv/improv.h"
+#include "esphome/components/wifi/wifi_component.h"
+#include "esphome/core/component.h"
+#include "esphome/core/defines.h"
+#include "esphome/core/helpers.h"
+
+#ifdef USE_ARDUINO
+#include
+#endif
+#ifdef USE_ESP_IDF
+#include
+#endif
+
+namespace esphome {
+namespace improv_serial {
+
+enum ImprovSerialType : uint8_t {
+ TYPE_CURRENT_STATE = 0x01,
+ TYPE_ERROR_STATE = 0x02,
+ TYPE_RPC = 0x03,
+ TYPE_RPC_RESPONSE = 0x04
+};
+
+static const uint8_t IMPROV_SERIAL_VERSION = 1;
+
+class ImprovSerialComponent : public Component {
+ public:
+ void setup() override;
+ void loop() override;
+ void dump_config() override;
+
+ float get_setup_priority() const override { return setup_priority::HARDWARE; }
+
+ protected:
+ bool parse_improv_serial_byte_(uint8_t byte);
+ bool parse_improv_payload_(improv::ImprovCommand &command);
+
+ void set_state_(improv::State state);
+ void set_error_(improv::Error error);
+ void send_response_(std::vector &response);
+ void on_wifi_connect_timeout_();
+
+ std::vector build_rpc_settings_response_(improv::Command command);
+ std::vector build_version_info_();
+
+ int available_();
+ uint8_t read_byte_();
+ void write_data_(std::vector &data);
+
+#ifdef USE_ARDUINO
+ HardwareSerial *hw_serial_{nullptr};
+#endif
+#ifdef USE_ESP_IDF
+ uart_port_t uart_num_;
+#endif
+
+ std::vector rx_buffer_;
+ uint32_t last_read_byte_{0};
+ wifi::WiFiAP connecting_sta_;
+ improv::State state_{improv::STATE_AUTHORIZED};
+};
+
+extern ImprovSerialComponent
+ *global_improv_serial_component; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
+
+} // namespace improv_serial
+} // namespace esphome
diff --git a/esphome/components/inkbird_ibsth1_mini/sensor.py b/esphome/components/inkbird_ibsth1_mini/sensor.py
index 0ab9f8b3e0..aa11fb3172 100644
--- a/esphome/components/inkbird_ibsth1_mini/sensor.py
+++ b/esphome/components/inkbird_ibsth1_mini/sensor.py
@@ -9,6 +9,7 @@ from esphome.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -53,6 +54,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp
index 97ad4c2cb9..11c0733701 100644
--- a/esphome/components/logger/logger.cpp
+++ b/esphome/components/logger/logger.cpp
@@ -221,7 +221,7 @@ UARTSelection Logger::get_uart() const { return this->uart_; }
void Logger::add_on_log_callback(std::function &&callback) {
this->log_callback_.add(std::move(callback));
}
-float Logger::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
+float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; }
const char *const LOG_LEVELS[] = {"NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"};
#ifdef USE_ESP32
const char *const UART_SELECTIONS[] = {"UART0", "UART1", "UART2"};
diff --git a/esphome/components/max7219digit/display.py b/esphome/components/max7219digit/display.py
index e1ca128699..2753f70eef 100644
--- a/esphome/components/max7219digit/display.py
+++ b/esphome/components/max7219digit/display.py
@@ -13,10 +13,20 @@ CONF_SCROLL_DELAY = "scroll_delay"
CONF_SCROLL_ENABLE = "scroll_enable"
CONF_SCROLL_MODE = "scroll_mode"
CONF_REVERSE_ENABLE = "reverse_enable"
+CONF_NUM_CHIP_LINES = "num_chip_lines"
+CONF_CHIP_LINES_STYLE = "chip_lines_style"
+integration_ns = cg.esphome_ns.namespace("max7219digit")
+ChipLinesStyle = integration_ns.enum("ChipLinesStyle")
+CHIP_LINES_STYLE = {
+ "ZIGZAG": ChipLinesStyle.ZIGZAG,
+ "SNAKE": ChipLinesStyle.SNAKE,
+}
+
+ScrollMode = integration_ns.enum("ScrollMode")
SCROLL_MODES = {
- "CONTINUOUS": 0,
- "STOP": 1,
+ "CONTINUOUS": ScrollMode.CONTINUOUS,
+ "STOP": ScrollMode.STOP,
}
CHIP_MODES = {
@@ -37,6 +47,10 @@ CONFIG_SCHEMA = (
{
cv.GenerateID(): cv.declare_id(MAX7219Component),
cv.Optional(CONF_NUM_CHIPS, default=4): cv.int_range(min=1, max=255),
+ cv.Optional(CONF_NUM_CHIP_LINES, default=1): cv.int_range(min=1, max=255),
+ cv.Optional(CONF_CHIP_LINES_STYLE, default="SNAKE"): cv.enum(
+ CHIP_LINES_STYLE, upper=True
+ ),
cv.Optional(CONF_INTENSITY, default=15): cv.int_range(min=0, max=15),
cv.Optional(CONF_ROTATE_CHIP, default="0"): cv.enum(CHIP_MODES, upper=True),
cv.Optional(CONF_SCROLL_MODE, default="CONTINUOUS"): cv.enum(
@@ -67,6 +81,8 @@ async def to_code(config):
await display.register_display(var, config)
cg.add(var.set_num_chips(config[CONF_NUM_CHIPS]))
+ cg.add(var.set_num_chip_lines(config[CONF_NUM_CHIP_LINES]))
+ cg.add(var.set_chip_lines_style(config[CONF_CHIP_LINES_STYLE]))
cg.add(var.set_intensity(config[CONF_INTENSITY]))
cg.add(var.set_chip_orientation(config[CONF_ROTATE_CHIP]))
cg.add(var.set_scroll_speed(config[CONF_SCROLL_SPEED]))
diff --git a/esphome/components/max7219digit/max7219digit.cpp b/esphome/components/max7219digit/max7219digit.cpp
index 4fedd3d312..0f86ac635c 100644
--- a/esphome/components/max7219digit/max7219digit.cpp
+++ b/esphome/components/max7219digit/max7219digit.cpp
@@ -26,9 +26,12 @@ void MAX7219Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up MAX7219_DIGITS...");
this->spi_setup();
this->stepsleft_ = 0;
- this->max_displaybuffer_.reserve(500); // Create base space to write buffer
- // Initialize buffer with 0 for display so all non written pixels are blank
- this->max_displaybuffer_.resize(this->num_chips_ * 8, 0);
+ for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
+ std::vector vec(1);
+ this->max_displaybuffer_.push_back(vec);
+ // Initialize buffer with 0 for display so all non written pixels are blank
+ this->max_displaybuffer_[chip_line].resize(get_width_internal(), 0);
+ }
// let's assume the user has all 8 digits connected, only important in daisy chained setups anyway
this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7);
// let's use our own ASCII -> led pattern encoding
@@ -46,6 +49,8 @@ void MAX7219Component::setup() {
void MAX7219Component::dump_config() {
ESP_LOGCONFIG(TAG, "MAX7219DIGIT:");
ESP_LOGCONFIG(TAG, " Number of Chips: %u", this->num_chips_);
+ ESP_LOGCONFIG(TAG, " Number of Chips Lines: %u", this->num_chip_lines_);
+ ESP_LOGCONFIG(TAG, " Chips Lines Style : %u", this->chip_lines_style_);
ESP_LOGCONFIG(TAG, " Intensity: %u", this->intensity_);
ESP_LOGCONFIG(TAG, " Scroll Mode: %u", this->scroll_mode_);
ESP_LOGCONFIG(TAG, " Scroll Speed: %u", this->scroll_speed_);
@@ -59,19 +64,19 @@ void MAX7219Component::loop() {
uint32_t now = millis();
// check if the buffer has shrunk past the current position since last update
- if ((this->max_displaybuffer_.size() >= this->old_buffer_size_ + 3) ||
- (this->max_displaybuffer_.size() <= this->old_buffer_size_ - 3)) {
+ if ((this->max_displaybuffer_[0].size() >= this->old_buffer_size_ + 3) ||
+ (this->max_displaybuffer_[0].size() <= this->old_buffer_size_ - 3)) {
this->stepsleft_ = 0;
this->display();
- this->old_buffer_size_ = this->max_displaybuffer_.size();
+ this->old_buffer_size_ = this->max_displaybuffer_[0].size();
}
// Reset the counter back to 0 when full string has been displayed.
- if (this->stepsleft_ > this->max_displaybuffer_.size())
+ if (this->stepsleft_ > this->max_displaybuffer_[0].size())
this->stepsleft_ = 0;
// Return if there is no need to scroll or scroll is off
- if (!this->scroll_ || (this->max_displaybuffer_.size() <= this->num_chips_ * 8)) {
+ if (!this->scroll_ || (this->max_displaybuffer_[0].size() <= get_width_internal())) {
this->display();
return;
}
@@ -82,8 +87,8 @@ void MAX7219Component::loop() {
}
// Dwell time at end of string in case of stop at end
- if (this->scroll_mode_ == 1) {
- if (this->stepsleft_ >= this->max_displaybuffer_.size() - this->num_chips_ * 8 + 1) {
+ if (this->scroll_mode_ == ScrollMode::STOP) {
+ if (this->stepsleft_ >= this->max_displaybuffer_[0].size() - get_width_internal() + 1) {
if (now - this->last_scroll_ >= this->scroll_dwell_) {
this->stepsleft_ = 0;
this->last_scroll_ = now;
@@ -107,30 +112,53 @@ void MAX7219Component::display() {
// Run this routine for the rows of every chip 8x row 0 top to 7 bottom
// Fill the pixel parameter with display data
// Send the data to the chip
- for (uint8_t i = 0; i < this->num_chips_; i++) {
- for (uint8_t j = 0; j < 8; j++) {
- if (this->reverse_) {
- pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j];
- } else {
- pixels[j] = this->max_displaybuffer_[i * 8 + j];
+ for (uint8_t chip = 0; chip < this->num_chips_ / this->num_chip_lines_; chip++) {
+ for (uint8_t chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
+ for (uint8_t j = 0; j < 8; j++) {
+ bool reverse =
+ chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE ? !this->reverse_ : this->reverse_;
+ if (reverse) {
+ pixels[j] =
+ this->max_displaybuffer_[chip_line][(this->num_chips_ / this->num_chip_lines_ - chip - 1) * 8 + j];
+ } else {
+ pixels[j] = this->max_displaybuffer_[chip_line][chip * 8 + j];
+ }
}
+ if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
+ this->orientation_ = orientation_180_();
+ this->send64pixels(chip_line * this->num_chips_ / this->num_chip_lines_ + chip, pixels);
+ if (chip_line % 2 != 0 && this->chip_lines_style_ == ChipLinesStyle::SNAKE)
+ this->orientation_ = orientation_180_();
}
- this->send64pixels(i, pixels);
+ }
+}
+
+uint8_t MAX7219Component::orientation_180_() {
+ switch (this->orientation_) {
+ case 0:
+ return 2;
+ case 1:
+ return 3;
+ case 2:
+ return 0;
+ case 3:
+ return 1;
+ default:
+ return 0;
}
}
int MAX7219Component::get_height_internal() {
- return 8; // TO BE DONE -> STACK TWO DISPLAYS ON TOP OF EACH OTHER
- // TO BE DONE -> CREATE Virtual size of screen and scroll
+ return 8 * this->num_chip_lines_; // TO BE DONE -> CREATE Virtual size of screen and scroll
}
-int MAX7219Component::get_width_internal() { return this->num_chips_ * 8; }
-
-size_t MAX7219Component::get_buffer_length_() { return this->num_chips_ * 8; }
+int MAX7219Component::get_width_internal() { return this->num_chips_ / this->num_chip_lines_ * 8; }
void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color color) {
- if (x + 1 > this->max_displaybuffer_.size()) { // Extend the display buffer in case required
- this->max_displaybuffer_.resize(x + 1, this->bckgrnd_);
+ if (x + 1 > this->max_displaybuffer_[0].size()) { // Extend the display buffer in case required
+ for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
+ this->max_displaybuffer_[chip_line].resize(x + 1, this->bckgrnd_);
+ }
}
if ((y >= this->get_height_internal()) || (y < 0) || (x < 0)) // If pixel is outside display then dont draw
@@ -140,9 +168,9 @@ void HOT MAX7219Component::draw_absolute_pixel_internal(int x, int y, Color colo
uint8_t subpos = y; // Y is starting at 0 top left
if (color.is_on()) {
- this->max_displaybuffer_[pos] |= (1 << subpos);
+ this->max_displaybuffer_[subpos / 8][pos] |= (1 << subpos % 8);
} else {
- this->max_displaybuffer_[pos] &= ~(1 << subpos);
+ this->max_displaybuffer_[subpos / 8][pos] &= ~(1 << subpos % 8);
}
}
@@ -158,8 +186,10 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) {
}
void MAX7219Component::update() {
this->update_ = true;
- this->max_displaybuffer_.clear();
- this->max_displaybuffer_.resize(this->num_chips_ * 8, this->bckgrnd_);
+ for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
+ this->max_displaybuffer_[chip_line].clear();
+ this->max_displaybuffer_[chip_line].resize(get_width_internal(), this->bckgrnd_);
+ }
if (this->writer_local_.has_value()) // insert Labda function if available
(*this->writer_local_)(*this);
}
@@ -175,7 +205,7 @@ void MAX7219Component::turn_on_off(bool on_off) {
}
}
-void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
+void MAX7219Component::scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell) {
this->set_scroll(on_off);
this->set_scroll_mode(mode);
this->set_scroll_speed(speed);
@@ -183,7 +213,7 @@ void MAX7219Component::scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_
this->set_scroll_delay(delay);
}
-void MAX7219Component::scroll(bool on_off, uint8_t mode) {
+void MAX7219Component::scroll(bool on_off, ScrollMode mode) {
this->set_scroll(on_off);
this->set_scroll_mode(mode);
}
@@ -196,24 +226,26 @@ void MAX7219Component::intensity(uint8_t intensity) {
void MAX7219Component::scroll(bool on_off) { this->set_scroll(on_off); }
void MAX7219Component::scroll_left() {
- if (this->update_) {
- this->max_displaybuffer_.push_back(this->bckgrnd_);
- for (uint16_t i = 0; i < this->stepsleft_; i++) {
- this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
- this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
- this->update_ = false;
+ for (int chip_line = 0; chip_line < this->num_chip_lines_; chip_line++) {
+ if (this->update_) {
+ this->max_displaybuffer_[chip_line].push_back(this->bckgrnd_);
+ for (uint16_t i = 0; i < this->stepsleft_; i++) {
+ this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
+ this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
+ }
+ } else {
+ this->max_displaybuffer_[chip_line].push_back(this->max_displaybuffer_[chip_line].front());
+ this->max_displaybuffer_[chip_line].erase(this->max_displaybuffer_[chip_line].begin());
}
- } else {
- this->max_displaybuffer_.push_back(this->max_displaybuffer_.front());
- this->max_displaybuffer_.erase(this->max_displaybuffer_.begin());
}
+ this->update_ = false;
this->stepsleft_++;
}
void MAX7219Component::send_char(uint8_t chip, uint8_t data) {
// get this character from PROGMEM
for (uint8_t i = 0; i < 8; i++)
- this->max_displaybuffer_[chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
+ this->max_displaybuffer_[0][chip * 8 + i] = progmem_read_byte(&MAX7219_DOT_MATRIX_FONT[data][i]);
} // end of send_char
// send one character (data) to position (chip)
diff --git a/esphome/components/max7219digit/max7219digit.h b/esphome/components/max7219digit/max7219digit.h
index 02fe8b6f42..3bf934632f 100644
--- a/esphome/components/max7219digit/max7219digit.h
+++ b/esphome/components/max7219digit/max7219digit.h
@@ -12,6 +12,16 @@
namespace esphome {
namespace max7219digit {
+enum ChipLinesStyle {
+ ZIGZAG = 0,
+ SNAKE,
+};
+
+enum ScrollMode {
+ CONTINUOUS = 0,
+ STOP,
+};
+
class MAX7219Component;
using max7219_writer_t = std::function;
@@ -46,20 +56,22 @@ class MAX7219Component : public PollingComponent,
void set_intensity(uint8_t intensity) { this->intensity_ = intensity; };
void set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; };
+ void set_num_chip_lines(uint8_t num_chip_lines) { this->num_chip_lines_ = num_chip_lines; };
+ void set_chip_lines_style(ChipLinesStyle chip_lines_style) { this->chip_lines_style_ = chip_lines_style; };
void set_chip_orientation(uint8_t rotate) { this->orientation_ = rotate; };
void set_scroll_speed(uint16_t speed) { this->scroll_speed_ = speed; };
void set_scroll_dwell(uint16_t dwell) { this->scroll_dwell_ = dwell; };
void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; };
void set_scroll(bool on_off) { this->scroll_ = on_off; };
- void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; };
+ void set_scroll_mode(ScrollMode mode) { this->scroll_mode_ = mode; };
void set_reverse(bool on_off) { this->reverse_ = on_off; };
void send_char(uint8_t chip, uint8_t data);
void send64pixels(uint8_t chip, const uint8_t pixels[8]);
void scroll_left();
- void scroll(bool on_off, uint8_t mode, uint16_t speed, uint16_t delay, uint16_t dwell);
- void scroll(bool on_off, uint8_t mode);
+ void scroll(bool on_off, ScrollMode mode, uint16_t speed, uint16_t delay, uint16_t dwell);
+ void scroll(bool on_off, ScrollMode mode);
void scroll(bool on_off);
void intensity(uint8_t intensity);
@@ -84,9 +96,12 @@ class MAX7219Component : public PollingComponent,
protected:
void send_byte_(uint8_t a_register, uint8_t data);
void send_to_all_(uint8_t a_register, uint8_t data);
+ uint8_t orientation_180_();
uint8_t intensity_; /// Intensity of the display from 0 to 15 (most)
uint8_t num_chips_;
+ uint8_t num_chip_lines_;
+ ChipLinesStyle chip_lines_style_;
bool scroll_;
bool reverse_;
bool update_{false};
@@ -94,11 +109,11 @@ class MAX7219Component : public PollingComponent,
uint16_t scroll_delay_;
uint16_t scroll_dwell_;
uint16_t old_buffer_size_ = 0;
- uint8_t scroll_mode_;
+ ScrollMode scroll_mode_;
bool invert_ = false;
uint8_t orientation_;
uint8_t bckgrnd_ = 0x0;
- std::vector max_displaybuffer_;
+ std::vector> max_displaybuffer_;
uint32_t last_scroll_ = 0;
uint16_t stepsleft_;
size_t get_buffer_length_();
diff --git a/esphome/components/mdns/mdns_component.cpp b/esphome/components/mdns/mdns_component.cpp
index 631af9eba9..915c640b06 100644
--- a/esphome/components/mdns/mdns_component.cpp
+++ b/esphome/components/mdns/mdns_component.cpp
@@ -2,6 +2,7 @@
#include "esphome/core/defines.h"
#include "esphome/core/version.h"
#include "esphome/core/application.h"
+#include "esphome/core/log.h"
#ifdef USE_API
#include "esphome/components/api/api_server.h"
@@ -13,13 +14,16 @@
namespace esphome {
namespace mdns {
+static const char *const TAG = "mdns";
+
#ifndef WEBSERVER_PORT
#define WEBSERVER_PORT 80 // NOLINT
#endif
-std::vector MDNSComponent::compile_services_() {
- std::vector res;
+void MDNSComponent::compile_records_() {
+ this->hostname_ = App.get_name();
+ this->services_.clear();
#ifdef USE_API
if (api::global_api_server != nullptr) {
MDNSService service{};
@@ -50,7 +54,7 @@ std::vector MDNSComponent::compile_services_() {
service.txt_records.push_back({"package_import_url", dashboard_import::get_package_import_url()});
#endif
- res.push_back(service);
+ this->services_.push_back(service);
}
#endif // USE_API
@@ -60,11 +64,11 @@ std::vector MDNSComponent::compile_services_() {
service.service_type = "_prometheus-http";
service.proto = "_tcp";
service.port = WEBSERVER_PORT;
- res.push_back(service);
+ this->services_.push_back(service);
}
#endif
- if (res.empty()) {
+ if (this->services_.empty()) {
// Publish "http" service if not using native API
// This is just to have *some* mDNS service so that .local resolution works
MDNSService service{};
@@ -72,11 +76,21 @@ std::vector MDNSComponent::compile_services_() {
service.proto = "_tcp";
service.port = WEBSERVER_PORT;
service.txt_records.push_back({"version", ESPHOME_VERSION});
- res.push_back(service);
+ this->services_.push_back(service);
+ }
+}
+
+void MDNSComponent::dump_config() {
+ ESP_LOGCONFIG(TAG, "mDNS:");
+ ESP_LOGCONFIG(TAG, " Hostname: %s", this->hostname_.c_str());
+ ESP_LOGV(TAG, " Services:");
+ for (const auto &service : this->services_) {
+ ESP_LOGV(TAG, " - %s, %s, %d", service.service_type.c_str(), service.proto.c_str(), service.port);
+ for (const auto &record : service.txt_records) {
+ ESP_LOGV(TAG, " TXT: %s = %s", record.key.c_str(), record.value.c_str());
+ }
}
- return res;
}
-std::string MDNSComponent::compile_hostname_() { return App.get_name(); }
} // namespace mdns
} // namespace esphome
diff --git a/esphome/components/mdns/mdns_component.h b/esphome/components/mdns/mdns_component.h
index 679fb1a768..45614d509a 100644
--- a/esphome/components/mdns/mdns_component.h
+++ b/esphome/components/mdns/mdns_component.h
@@ -26,6 +26,7 @@ struct MDNSService {
class MDNSComponent : public Component {
public:
void setup() override;
+ void dump_config() override;
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
void loop() override;
@@ -33,8 +34,9 @@ class MDNSComponent : public Component {
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
protected:
- std::vector compile_services_();
- std::string compile_hostname_();
+ std::vector services_{};
+ std::string hostname_;
+ void compile_records_();
};
} // namespace mdns
diff --git a/esphome/components/mdns/mdns_esp32_arduino.cpp b/esphome/components/mdns/mdns_esp32_arduino.cpp
index 4d13b7321a..6a66beef92 100644
--- a/esphome/components/mdns/mdns_esp32_arduino.cpp
+++ b/esphome/components/mdns/mdns_esp32_arduino.cpp
@@ -7,13 +7,12 @@
namespace esphome {
namespace mdns {
-static const char *const TAG = "mdns";
-
void MDNSComponent::setup() {
- MDNS.begin(compile_hostname_().c_str());
+ this->compile_records_();
- auto services = compile_services_();
- for (const auto &service : services) {
+ MDNS.begin(this->hostname_.c_str());
+
+ for (const auto &service : this->services_) {
MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port);
for (const auto &record : service.txt_records) {
MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str());
diff --git a/esphome/components/mdns/mdns_esp8266.cpp b/esphome/components/mdns/mdns_esp8266.cpp
index 1a73e4b5b3..ff305f907a 100644
--- a/esphome/components/mdns/mdns_esp8266.cpp
+++ b/esphome/components/mdns/mdns_esp8266.cpp
@@ -9,14 +9,13 @@
namespace esphome {
namespace mdns {
-static const char *const TAG = "mdns";
-
void MDNSComponent::setup() {
- network::IPAddress addr = network::get_ip_address();
- MDNS.begin(compile_hostname_().c_str(), (uint32_t) addr);
+ this->compile_records_();
- auto services = compile_services_();
- for (const auto &service : services) {
+ network::IPAddress addr = network::get_ip_address();
+ MDNS.begin(this->hostname_.c_str(), (uint32_t) addr);
+
+ for (const auto &service : this->services_) {
// Strip the leading underscore from the proto and service_type. While it is
// part of the wire protocol to have an underscore, and for example ESP-IDF
// expects the underscore to be there, the ESP8266 implementation always adds
diff --git a/esphome/components/mdns/mdns_esp_idf.cpp b/esphome/components/mdns/mdns_esp_idf.cpp
index 17874f1ffe..40d9f1d5f3 100644
--- a/esphome/components/mdns/mdns_esp_idf.cpp
+++ b/esphome/components/mdns/mdns_esp_idf.cpp
@@ -11,18 +11,19 @@ namespace mdns {
static const char *const TAG = "mdns";
void MDNSComponent::setup() {
+ this->compile_records_();
+
esp_err_t err = mdns_init();
if (err != ESP_OK) {
- ESP_LOGW(TAG, "MDNS init failed: %s", esp_err_to_name(err));
+ ESP_LOGW(TAG, "mDNS init failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
}
- mdns_hostname_set(compile_hostname_().c_str());
- mdns_instance_name_set(compile_hostname_().c_str());
+ mdns_hostname_set(this->hostname_.c_str());
+ mdns_instance_name_set(this->hostname_.c_str());
- auto services = compile_services_();
- for (const auto &service : services) {
+ for (const auto &service : this->services_) {
std::vector txt_records;
for (const auto &record : service.txt_records) {
mdns_txt_item_t it{};
diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp
index 45c5bfb603..9524f9daf4 100644
--- a/esphome/components/modbus/modbus.cpp
+++ b/esphome/components/modbus/modbus.cpp
@@ -139,7 +139,9 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
uint8_t payload_len, const uint8_t *payload) {
static const size_t MAX_VALUES = 128;
- if (number_of_entities > MAX_VALUES) {
+ // Only check max number of registers for standard function codes
+ // Some devices use non standard codes like 0x43
+ if (number_of_entities > MAX_VALUES && function_code <= 0x10) {
ESP_LOGE(TAG, "send too many values %d max=%zu", number_of_entities, MAX_VALUES);
return;
}
diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h
index 4b5f4337db..222ebbd020 100644
--- a/esphome/components/modbus_controller/modbus_controller.h
+++ b/esphome/components/modbus_controller/modbus_controller.h
@@ -260,35 +260,11 @@ struct SensorItem {
virtual void parse_and_publish(const std::vector &data) = 0;
uint64_t getkey() const { return calc_key(register_type, start_address, offset, bitmask); }
-
size_t virtual get_register_size() const {
- size_t size = 0;
- switch (sensor_value_type) {
- case SensorValueType::BIT:
- size = 1;
- break;
- case SensorValueType::U_WORD:
- case SensorValueType::S_WORD:
- size = 2;
- break;
- case SensorValueType::U_DWORD:
- case SensorValueType::S_DWORD:
- case SensorValueType::U_DWORD_R:
- case SensorValueType::S_DWORD_R:
- case SensorValueType::FP32:
- case SensorValueType::FP32_R:
- size = 4;
- break;
- case SensorValueType::U_QWORD:
- case SensorValueType::U_QWORD_R:
- case SensorValueType::S_QWORD:
- case SensorValueType::S_QWORD_R:
- size = 8;
- break;
- case SensorValueType::RAW:
- size = this->register_count * 2;
- }
- return size;
+ if (register_type == ModbusRegisterType::COIL || register_type == ModbusRegisterType::DISCRETE_INPUT)
+ return 1;
+ else
+ return register_count * 2;
}
};
diff --git a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h
index 28d0f0b241..77b5b9363a 100644
--- a/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h
+++ b/esphome/components/modbus_controller/text_sensor/modbus_textsensor.h
@@ -25,13 +25,6 @@ class ModbusTextSensor : public Component, public text_sensor::TextSensor, publi
this->sensor_value_type = SensorValueType::RAW;
this->force_new_range = force_new_range;
}
- size_t get_register_size() const override {
- if (sensor_value_type == SensorValueType::RAW) {
- return this->response_bytes_;
- } else {
- return SensorItem::get_register_size();
- }
- }
void dump_config() override;
diff --git a/esphome/components/mqtt/__init__.py b/esphome/components/mqtt/__init__.py
index 3d52dab67f..0f7d246473 100644
--- a/esphome/components/mqtt/__init__.py
+++ b/esphome/components/mqtt/__init__.py
@@ -34,6 +34,7 @@ from esphome.const import (
CONF_TOPIC,
CONF_TOPIC_PREFIX,
CONF_TRIGGER_ID,
+ CONF_USE_ABBREVIATIONS,
CONF_USERNAME,
CONF_WILL_MESSAGE,
)
@@ -152,6 +153,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(
CONF_DISCOVERY_PREFIX, default="homeassistant"
): cv.publish_topic,
+ 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,
cv.Optional(CONF_SHUTDOWN_MESSAGE): MQTT_MESSAGE_SCHEMA,
@@ -239,6 +241,9 @@ async def to_code(config):
cg.add(var.set_topic_prefix(config[CONF_TOPIC_PREFIX]))
+ if config[CONF_USE_ABBREVIATIONS]:
+ cg.add_define("USE_MQTT_ABBREVIATIONS")
+
birth_message = config[CONF_BIRTH_MESSAGE]
if not birth_message:
cg.add(var.disable_birth_message())
diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp
index 188df0f7b9..0a161f89a1 100644
--- a/esphome/components/mqtt/mqtt_binary_sensor.cpp
+++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp
@@ -1,6 +1,8 @@
#include "mqtt_binary_sensor.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_BINARY_SENSOR
@@ -29,11 +31,11 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor
void MQTTBinarySensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
if (!this->binary_sensor_->get_device_class().empty())
- root["device_class"] = this->binary_sensor_->get_device_class();
+ root[MQTT_DEVICE_CLASS] = this->binary_sensor_->get_device_class();
if (this->binary_sensor_->is_status_binary_sensor())
- root["payload_on"] = mqtt::global_mqtt_client->get_availability().payload_available;
+ root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available;
if (this->binary_sensor_->is_status_binary_sensor())
- root["payload_off"] = mqtt::global_mqtt_client->get_availability().payload_not_available;
+ root[MQTT_PAYLOAD_OFF] = mqtt::global_mqtt_client->get_availability().payload_not_available;
config.command_topic = false;
}
bool MQTTBinarySensorComponent::send_initial_state() {
diff --git a/esphome/components/mqtt/mqtt_climate.cpp b/esphome/components/mqtt/mqtt_climate.cpp
index 47b6684dec..ebc708f444 100644
--- a/esphome/components/mqtt/mqtt_climate.cpp
+++ b/esphome/components/mqtt/mqtt_climate.cpp
@@ -1,6 +1,8 @@
#include "mqtt_climate.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_CLIMATE
@@ -16,14 +18,14 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
// current_temperature_topic
if (traits.get_supports_current_temperature()) {
// current_temperature_topic
- root["curr_temp_t"] = this->get_current_temperature_state_topic();
+ root[MQTT_CURRENT_TEMPERATURE_TOPIC] = this->get_current_temperature_state_topic();
}
// mode_command_topic
- root["mode_cmd_t"] = this->get_mode_command_topic();
+ root[MQTT_MODE_COMMAND_TOPIC] = this->get_mode_command_topic();
// mode_state_topic
- root["mode_stat_t"] = this->get_mode_state_topic();
+ root[MQTT_MODE_STATE_TOPIC] = this->get_mode_state_topic();
// modes
- JsonArray &modes = root.createNestedArray("modes");
+ JsonArray &modes = root.createNestedArray(MQTT_MODES);
// sort array for nice UI in HA
if (traits.supports_mode(CLIMATE_MODE_AUTO))
modes.add("auto");
@@ -41,45 +43,45 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
if (traits.get_supports_two_point_target_temperature()) {
// temperature_low_command_topic
- root["temp_lo_cmd_t"] = this->get_target_temperature_low_command_topic();
+ root[MQTT_TEMPERATURE_LOW_COMMAND_TOPIC] = this->get_target_temperature_low_command_topic();
// temperature_low_state_topic
- root["temp_lo_stat_t"] = this->get_target_temperature_low_state_topic();
+ root[MQTT_TEMPERATURE_LOW_STATE_TOPIC] = this->get_target_temperature_low_state_topic();
// temperature_high_command_topic
- root["temp_hi_cmd_t"] = this->get_target_temperature_high_command_topic();
+ root[MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC] = this->get_target_temperature_high_command_topic();
// temperature_high_state_topic
- root["temp_hi_stat_t"] = this->get_target_temperature_high_state_topic();
+ root[MQTT_TEMPERATURE_HIGH_STATE_TOPIC] = this->get_target_temperature_high_state_topic();
} else {
// temperature_command_topic
- root["temp_cmd_t"] = this->get_target_temperature_command_topic();
+ root[MQTT_TEMPERATURE_COMMAND_TOPIC] = this->get_target_temperature_command_topic();
// temperature_state_topic
- root["temp_stat_t"] = this->get_target_temperature_state_topic();
+ root[MQTT_TEMPERATURE_STATE_TOPIC] = this->get_target_temperature_state_topic();
}
// min_temp
- root["min_temp"] = traits.get_visual_min_temperature();
+ root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature();
// max_temp
- root["max_temp"] = traits.get_visual_max_temperature();
+ root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature();
// temp_step
root["temp_step"] = traits.get_visual_temperature_step();
// temperature units are always coerced to Celsius internally
- root["temp_unit"] = "C";
+ root[MQTT_TEMPERATURE_UNIT] = "C";
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
// away_mode_command_topic
- root["away_mode_cmd_t"] = this->get_away_command_topic();
+ root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
// away_mode_state_topic
- root["away_mode_stat_t"] = this->get_away_state_topic();
+ root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
}
if (traits.get_supports_action()) {
// action_topic
- root["act_t"] = this->get_action_state_topic();
+ root[MQTT_ACTION_TOPIC] = this->get_action_state_topic();
}
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
// fan_mode_command_topic
- root["fan_mode_cmd_t"] = this->get_fan_mode_command_topic();
+ root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic();
// fan_mode_state_topic
- root["fan_mode_stat_t"] = this->get_fan_mode_state_topic();
+ root[MQTT_FAN_MODE_STATE_TOPIC] = this->get_fan_mode_state_topic();
// fan_modes
JsonArray &fan_modes = root.createNestedArray("fan_modes");
if (traits.supports_fan_mode(CLIMATE_FAN_ON))
@@ -106,9 +108,9 @@ void MQTTClimateComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryC
if (traits.get_supports_swing_modes()) {
// swing_mode_command_topic
- root["swing_mode_cmd_t"] = this->get_swing_mode_command_topic();
+ root[MQTT_SWING_MODE_COMMAND_TOPIC] = this->get_swing_mode_command_topic();
// swing_mode_state_topic
- root["swing_mode_stat_t"] = this->get_swing_mode_state_topic();
+ root[MQTT_SWING_MODE_STATE_TOPIC] = this->get_swing_mode_state_topic();
// swing_modes
JsonArray &swing_modes = root.createNestedArray("swing_modes");
if (traits.supports_swing_mode(CLIMATE_SWING_OFF))
@@ -135,7 +137,7 @@ void MQTTClimateComponent::setup() {
if (traits.get_supports_two_point_target_temperature()) {
this->subscribe(this->get_target_temperature_low_command_topic(),
[this](const std::string &topic, const std::string &payload) {
- auto val = parse_float(payload);
+ auto val = parse_number(payload);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
return;
@@ -146,7 +148,7 @@ void MQTTClimateComponent::setup() {
});
this->subscribe(this->get_target_temperature_high_command_topic(),
[this](const std::string &topic, const std::string &payload) {
- auto val = parse_float(payload);
+ auto val = parse_number(payload);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
return;
@@ -158,7 +160,7 @@ void MQTTClimateComponent::setup() {
} else {
this->subscribe(this->get_target_temperature_command_topic(),
[this](const std::string &topic, const std::string &payload) {
- auto val = parse_float(payload);
+ auto val = parse_number(payload);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
return;
diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp
index 0ece4b3501..e3ae4dea50 100644
--- a/esphome/components/mqtt/mqtt_component.cpp
+++ b/esphome/components/mqtt/mqtt_component.cpp
@@ -7,6 +7,8 @@
#include "esphome/core/helpers.h"
#include "esphome/core/version.h"
+#include "mqtt_const.h"
+
namespace esphome {
namespace mqtt {
@@ -15,7 +17,7 @@ static const char *const TAG = "mqtt.component";
void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
- std::string sanitized_name = sanitize_string_allowlist(App.get_name(), HOSTNAME_CHARACTER_ALLOWLIST);
+ std::string sanitized_name = str_sanitize(App.get_name());
return discovery_info.prefix + "/" + this->component_type() + "/" + sanitized_name + "/" +
this->get_default_object_id_() + "/config";
}
@@ -69,49 +71,60 @@ bool MQTTComponent::send_discovery_() {
this->send_discovery(root, config);
// Fields from EntityBase
- root["name"] = this->friendly_name();
+ root[MQTT_NAME] = this->friendly_name();
if (this->is_disabled_by_default())
- root["enabled_by_default"] = false;
+ root[MQTT_ENABLED_BY_DEFAULT] = false;
if (!this->get_icon().empty())
- root["icon"] = this->get_icon();
+ root[MQTT_ICON] = this->get_icon();
+
+ switch (this->get_entity()->get_entity_category()) {
+ case ENTITY_CATEGORY_NONE:
+ break;
+ case ENTITY_CATEGORY_CONFIG:
+ root[MQTT_ENTITY_CATEGORY] = "config";
+ break;
+ case ENTITY_CATEGORY_DIAGNOSTIC:
+ root[MQTT_ENTITY_CATEGORY] = "diagnostic";
+ break;
+ }
if (config.state_topic)
- root["state_topic"] = this->get_state_topic_();
+ root[MQTT_STATE_TOPIC] = this->get_state_topic_();
if (config.command_topic)
- root["command_topic"] = this->get_command_topic_();
+ root[MQTT_COMMAND_TOPIC] = this->get_command_topic_();
if (this->availability_ == nullptr) {
if (!global_mqtt_client->get_availability().topic.empty()) {
- root["availability_topic"] = global_mqtt_client->get_availability().topic;
+ root[MQTT_AVAILABILITY_TOPIC] = global_mqtt_client->get_availability().topic;
if (global_mqtt_client->get_availability().payload_available != "online")
- root["payload_available"] = global_mqtt_client->get_availability().payload_available;
+ root[MQTT_PAYLOAD_AVAILABLE] = global_mqtt_client->get_availability().payload_available;
if (global_mqtt_client->get_availability().payload_not_available != "offline")
- root["payload_not_available"] = global_mqtt_client->get_availability().payload_not_available;
+ root[MQTT_PAYLOAD_NOT_AVAILABLE] = global_mqtt_client->get_availability().payload_not_available;
}
} else if (!this->availability_->topic.empty()) {
- root["availability_topic"] = this->availability_->topic;
+ root[MQTT_AVAILABILITY_TOPIC] = this->availability_->topic;
if (this->availability_->payload_available != "online")
- root["payload_available"] = this->availability_->payload_available;
+ root[MQTT_PAYLOAD_AVAILABLE] = this->availability_->payload_available;
if (this->availability_->payload_not_available != "offline")
- root["payload_not_available"] = this->availability_->payload_not_available;
+ 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();
if (!unique_id.empty()) {
- root["unique_id"] = unique_id;
+ root[MQTT_UNIQUE_ID] = unique_id;
} else {
// default to almost-unique ID. It's a hack but the only way to get that
// gorgeous device registry view.
- root["unique_id"] = "ESP" + this->component_type() + this->get_default_object_id_();
+ root[MQTT_UNIQUE_ID] = "ESP" + this->component_type() + this->get_default_object_id_();
}
- JsonObject &device_info = root.createNestedObject("device");
- device_info["identifiers"] = get_mac_address();
- device_info["name"] = node_name;
- device_info["sw_version"] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time();
- device_info["model"] = ESPHOME_BOARD;
- device_info["manufacturer"] = "espressif";
+ JsonObject &device_info = root.createNestedObject(MQTT_DEVICE);
+ device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address();
+ device_info[MQTT_DEVICE_NAME] = node_name;
+ device_info[MQTT_DEVICE_SW_VERSION] = "esphome v" ESPHOME_VERSION " " + App.get_compilation_time();
+ device_info[MQTT_DEVICE_MODEL] = ESPHOME_BOARD;
+ device_info[MQTT_DEVICE_MANUFACTURER] = "espressif";
},
0, discovery_info.retain);
}
@@ -123,7 +136,7 @@ bool MQTTComponent::is_discovery_enabled() const {
}
std::string MQTTComponent::get_default_object_id_() const {
- return sanitize_string_allowlist(to_lowercase_underscore(this->friendly_name()), HOSTNAME_CHARACTER_ALLOWLIST);
+ return str_sanitize(str_snake_case(this->friendly_name()));
}
void MQTTComponent::subscribe(const std::string &topic, mqtt_callback_t callback, uint8_t qos) {
diff --git a/esphome/components/mqtt/mqtt_const.h b/esphome/components/mqtt/mqtt_const.h
new file mode 100644
index 0000000000..1d5e22efde
--- /dev/null
+++ b/esphome/components/mqtt/mqtt_const.h
@@ -0,0 +1,521 @@
+#pragma once
+
+#include "esphome/core/defines.h"
+
+#ifdef USE_MQTT
+
+namespace esphome {
+namespace mqtt {
+
+#ifdef USE_MQTT_ABBREVIATIONS
+
+constexpr const char *const MQTT_ACTION_TOPIC = "act_t";
+constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl";
+constexpr const char *const MQTT_AUTOMATION_TYPE = "atype";
+constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t";
+constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl";
+constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_stat_t";
+constexpr const char *const MQTT_AVAILABILITY = "avty";
+constexpr const char *const MQTT_AVAILABILITY_MODE = "avty_mode";
+constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t";
+constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t";
+constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl";
+constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t";
+constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl";
+constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t";
+constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl";
+constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t";
+constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl";
+constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl";
+constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl";
+constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t";
+constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl";
+constexpr const char *const MQTT_CONFIGURATION_URL = "cu";
+constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t";
+constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl";
+constexpr const char *const MQTT_COLOR_MODE = "clrm";
+constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t";
+constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl";
+constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t";
+constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t";
+constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl";
+constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl";
+constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t";
+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_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";
+constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t";
+constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl";
+constexpr const char *const MQTT_DEVICE = "dev";
+constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla";
+constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t";
+constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl";
+constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en";
+constexpr const char *const MQTT_ERROR_TOPIC = "err_t";
+constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl";
+constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t";
+constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl";
+constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst";
+constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng";
+constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht";
+constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t";
+constexpr const char *const MQTT_EFFECT_LIST = "fx_list";
+constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t";
+constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl";
+constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl";
+constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft";
+constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl";
+constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t";
+constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl";
+constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t";
+constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd";
+constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl";
+constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl";
+constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_cmd_t";
+constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_stat_tpl";
+constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_stat_t";
+constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_cmd_t";
+constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t";
+constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl";
+constexpr const char *const MQTT_ICON = "ic";
+constexpr const char *const MQTT_INITIAL = "init";
+constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t";
+constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl";
+constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t";
+constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl";
+constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr";
+constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t";
+constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl";
+constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t";
+constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl";
+constexpr const char *const MQTT_MAX = "max";
+constexpr const char *const MQTT_MIN = "min";
+constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum";
+constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum";
+constexpr const char *const MQTT_MAX_MIREDS = "max_mirs";
+constexpr const char *const MQTT_MIN_MIREDS = "min_mirs";
+constexpr const char *const MQTT_MAX_TEMP = "max_temp";
+constexpr const char *const MQTT_MIN_TEMP = "min_temp";
+constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl";
+constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t";
+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_OFF_DELAY = "off_dly";
+constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type";
+constexpr const char *const MQTT_OPTIONS = "ops";
+constexpr const char *const MQTT_OPTIMISTIC = "opt";
+constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t";
+constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl";
+constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t";
+constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl";
+constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t";
+constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl";
+constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t";
+constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl";
+constexpr const char *const MQTT_PAYLOAD = "pl";
+constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away";
+constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home";
+constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite";
+constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation";
+constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b";
+constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail";
+constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp";
+constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls";
+constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm";
+constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd";
+constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home";
+constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock";
+constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc";
+constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd";
+constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd";
+constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail";
+constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "pl_not_home";
+constexpr const char *const MQTT_PAYLOAD_OFF = "pl_off";
+constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "pl_off_spd";
+constexpr const char *const MQTT_PAYLOAD_ON = "pl_on";
+constexpr const char *const MQTT_PAYLOAD_OPEN = "pl_open";
+constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "pl_osc_off";
+constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "pl_osc_on";
+constexpr const char *const MQTT_PAYLOAD_PAUSE = "pl_paus";
+constexpr const char *const MQTT_PAYLOAD_RESET = "pl_rst";
+constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum";
+constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode";
+constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct";
+constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode";
+constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop";
+constexpr const char *const MQTT_PAYLOAD_START = "pl_strt";
+constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa";
+constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret";
+constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff";
+constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton";
+constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk";
+constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd";
+constexpr const char *const MQTT_POSITION_OPEN = "pos_open";
+constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t";
+constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t";
+constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl";
+constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t";
+constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl";
+constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t";
+constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl";
+constexpr const char *const MQTT_PRESET_MODES = "pr_modes";
+constexpr const char *const MQTT_RED_TEMPLATE = "r_tpl";
+constexpr const char *const MQTT_RETAIN = "ret";
+constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_cmd_tpl";
+constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_cmd_t";
+constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_stat_t";
+constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_val_tpl";
+constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_cmd_tpl";
+constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_cmd_t";
+constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_stat_t";
+constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_val_tpl";
+constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_cmd_tpl";
+constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_cmd_t";
+constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_stat_t";
+constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_val_tpl";
+constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_cmd_t";
+constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off";
+constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t";
+constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl";
+constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t";
+constexpr const char *const MQTT_POSITION_TOPIC = "pos_t";
+constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl";
+constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t";
+constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t";
+constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min";
+constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max";
+constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl";
+constexpr const char *const MQTT_SPEEDS = "spds";
+constexpr const char *const MQTT_SOURCE_TYPE = "src_type";
+constexpr const char *const MQTT_STATE_CLASS = "stat_cla";
+constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd";
+constexpr const char *const MQTT_STATE_CLOSING = "stat_closing";
+constexpr const char *const MQTT_STATE_OFF = "stat_off";
+constexpr const char *const MQTT_STATE_ON = "stat_on";
+constexpr const char *const MQTT_STATE_OPEN = "stat_open";
+constexpr const char *const MQTT_STATE_OPENING = "stat_opening";
+constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped";
+constexpr const char *const MQTT_STATE_LOCKED = "stat_locked";
+constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked";
+constexpr const char *const MQTT_STATE_TOPIC = "stat_t";
+constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl";
+constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl";
+constexpr const char *const MQTT_STEP = "step";
+constexpr const char *const MQTT_SUBTYPE = "stype";
+constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat";
+constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm";
+constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl";
+constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t";
+constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl";
+constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t";
+constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl";
+constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temp_hi_cmd_t";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temp_hi_stat_tpl";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temp_hi_stat_t";
+constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temp_lo_cmd_tpl";
+constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temp_lo_cmd_t";
+constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temp_lo_stat_tpl";
+constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temp_lo_stat_t";
+constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl";
+constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t";
+constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit";
+constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val";
+constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t";
+constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl";
+constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat";
+constexpr const char *const MQTT_TILT_MAX = "tilt_max";
+constexpr const char *const MQTT_TILT_MIN = "tilt_min";
+constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val";
+constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt";
+constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t";
+constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl";
+constexpr const char *const MQTT_TOPIC = "t";
+constexpr const char *const MQTT_UNIQUE_ID = "uniq_id";
+constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas";
+constexpr const char *const MQTT_VALUE_TEMPLATE = "val_tpl";
+constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "whit_cmd_t";
+constexpr const char *const MQTT_WHITE_SCALE = "whit_scl";
+constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "whit_val_cmd_t";
+constexpr const char *const MQTT_WHITE_VALUE_SCALE = "whit_val_scl";
+constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "whit_val_stat_t";
+constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "whit_val_tpl";
+constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t";
+constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t";
+constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl";
+
+constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns";
+constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids";
+constexpr const char *const MQTT_DEVICE_NAME = "name";
+constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf";
+constexpr const char *const MQTT_DEVICE_MODEL = "mdl";
+constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw";
+constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa";
+
+#else
+
+constexpr const char *const MQTT_ACTION_TOPIC = "action_topic";
+constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template";
+constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type";
+constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic";
+constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template";
+constexpr const char *const MQTT_AUX_STATE_TOPIC = "aux_state_topic";
+constexpr const char *const MQTT_AVAILABILITY = "availability";
+constexpr const char *const MQTT_AVAILABILITY_MODE = "availability_mode";
+constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic";
+constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic";
+constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template";
+constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic";
+constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template";
+constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic";
+constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale";
+constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic";
+constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template";
+constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template";
+constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template";
+constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic";
+constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template";
+constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url";
+constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic";
+constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template";
+constexpr const char *const MQTT_COLOR_MODE = "color_mode";
+constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic";
+constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template";
+constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic";
+constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic";
+constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template";
+constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template";
+constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic";
+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_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";
+constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic";
+constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template";
+constexpr const char *const MQTT_DEVICE = "device";
+constexpr const char *const MQTT_DEVICE_CLASS = "device_class";
+constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic";
+constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template";
+constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default";
+constexpr const char *const MQTT_ERROR_TOPIC = "error_topic";
+constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template";
+constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic";
+constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template";
+constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list";
+constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long";
+constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short";
+constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic";
+constexpr const char *const MQTT_EFFECT_LIST = "effect_list";
+constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic";
+constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template";
+constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template";
+constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after";
+constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template";
+constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic";
+constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template";
+constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic";
+constexpr const char *const MQTT_FORCE_UPDATE = "force_update";
+constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template";
+constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template";
+constexpr const char *const MQTT_HOLD_COMMAND_TOPIC = "hold_command_topic";
+constexpr const char *const MQTT_HOLD_STATE_TEMPLATE = "hold_state_template";
+constexpr const char *const MQTT_HOLD_STATE_TOPIC = "hold_state_topic";
+constexpr const char *const MQTT_HS_COMMAND_TOPIC = "hs_command_topic";
+constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic";
+constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template";
+constexpr const char *const MQTT_ICON = "icon";
+constexpr const char *const MQTT_INITIAL = "initial";
+constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic";
+constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template";
+constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic";
+constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template";
+constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes";
+constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic";
+constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template";
+constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic";
+constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template";
+constexpr const char *const MQTT_MAX = "max";
+constexpr const char *const MQTT_MIN = "min";
+constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity";
+constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity";
+constexpr const char *const MQTT_MAX_MIREDS = "max_mireds";
+constexpr const char *const MQTT_MIN_MIREDS = "min_mireds";
+constexpr const char *const MQTT_MAX_TEMP = "max_temp";
+constexpr const char *const MQTT_MIN_TEMP = "min_temp";
+constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template";
+constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic";
+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_OFF_DELAY = "off_delay";
+constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type";
+constexpr const char *const MQTT_OPTIONS = "options";
+constexpr const char *const MQTT_OPTIMISTIC = "optimistic";
+constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic";
+constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template";
+constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic";
+constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template";
+constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic";
+constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template";
+constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic";
+constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template";
+constexpr const char *const MQTT_PAYLOAD = "payload";
+constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away";
+constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home";
+constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night";
+constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation";
+constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass";
+constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available";
+constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot";
+constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close";
+constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm";
+constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed";
+constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home";
+constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock";
+constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate";
+constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed";
+constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed";
+constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available";
+constexpr const char *const MQTT_PAYLOAD_NOT_HOME = "payload_not_home";
+constexpr const char *const MQTT_PAYLOAD_OFF = "payload_off";
+constexpr const char *const MQTT_PAYLOAD_OFF_SPEED = "payload_off_speed";
+constexpr const char *const MQTT_PAYLOAD_ON = "payload_on";
+constexpr const char *const MQTT_PAYLOAD_OPEN = "payload_open";
+constexpr const char *const MQTT_PAYLOAD_OSCILLATION_OFF = "payload_oscillation_off";
+constexpr const char *const MQTT_PAYLOAD_OSCILLATION_ON = "payload_oscillation_on";
+constexpr const char *const MQTT_PAYLOAD_PAUSE = "payload_pause";
+constexpr const char *const MQTT_PAYLOAD_RESET = "payload_reset";
+constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidity";
+constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode";
+constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage";
+constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode";
+constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop";
+constexpr const char *const MQTT_PAYLOAD_START = "payload_start";
+constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause";
+constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base";
+constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off";
+constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on";
+constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock";
+constexpr const char *const MQTT_POSITION_CLOSED = "position_closed";
+constexpr const char *const MQTT_POSITION_OPEN = "position_open";
+constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic";
+constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic";
+constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template";
+constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic";
+constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template";
+constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic";
+constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template";
+constexpr const char *const MQTT_PRESET_MODES = "preset_modes";
+constexpr const char *const MQTT_RED_TEMPLATE = "red_template";
+constexpr const char *const MQTT_RETAIN = "retain";
+constexpr const char *const MQTT_RGB_COMMAND_TEMPLATE = "rgb_command_template";
+constexpr const char *const MQTT_RGB_COMMAND_TOPIC = "rgb_command_topic";
+constexpr const char *const MQTT_RGB_STATE_TOPIC = "rgb_state_topic";
+constexpr const char *const MQTT_RGB_VALUE_TEMPLATE = "rgb_value_template";
+constexpr const char *const MQTT_RGBW_COMMAND_TEMPLATE = "rgbw_command_template";
+constexpr const char *const MQTT_RGBW_COMMAND_TOPIC = "rgbw_command_topic";
+constexpr const char *const MQTT_RGBW_STATE_TOPIC = "rgbw_state_topic";
+constexpr const char *const MQTT_RGBW_VALUE_TEMPLATE = "rgbw_value_template";
+constexpr const char *const MQTT_RGBWW_COMMAND_TEMPLATE = "rgbww_command_template";
+constexpr const char *const MQTT_RGBWW_COMMAND_TOPIC = "rgbww_command_topic";
+constexpr const char *const MQTT_RGBWW_STATE_TOPIC = "rgbww_state_topic";
+constexpr const char *const MQTT_RGBWW_VALUE_TEMPLATE = "rgbww_value_template";
+constexpr const char *const MQTT_SEND_COMMAND_TOPIC = "send_command_topic";
+constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off";
+constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic";
+constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template";
+constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic";
+constexpr const char *const MQTT_POSITION_TOPIC = "position_topic";
+constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template";
+constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic";
+constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic";
+constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min";
+constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max";
+constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template";
+constexpr const char *const MQTT_SPEEDS = "speeds";
+constexpr const char *const MQTT_SOURCE_TYPE = "source_type";
+constexpr const char *const MQTT_STATE_CLASS = "state_class";
+constexpr const char *const MQTT_STATE_CLOSED = "state_closed";
+constexpr const char *const MQTT_STATE_CLOSING = "state_closing";
+constexpr const char *const MQTT_STATE_OFF = "state_off";
+constexpr const char *const MQTT_STATE_ON = "state_on";
+constexpr const char *const MQTT_STATE_OPEN = "state_open";
+constexpr const char *const MQTT_STATE_OPENING = "state_opening";
+constexpr const char *const MQTT_STATE_STOPPED = "state_stopped";
+constexpr const char *const MQTT_STATE_LOCKED = "state_locked";
+constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked";
+constexpr const char *const MQTT_STATE_TOPIC = "state_topic";
+constexpr const char *const MQTT_STATE_TEMPLATE = "state_template";
+constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template";
+constexpr const char *const MQTT_STEP = "step";
+constexpr const char *const MQTT_SUBTYPE = "subtype";
+constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features";
+constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes";
+constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template";
+constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic";
+constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template";
+constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic";
+constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template";
+constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TOPIC = "temperature_high_command_topic";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TEMPLATE = "temperature_high_state_template";
+constexpr const char *const MQTT_TEMPERATURE_HIGH_STATE_TOPIC = "temperature_high_state_topic";
+constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TEMPLATE = "temperature_low_command_template";
+constexpr const char *const MQTT_TEMPERATURE_LOW_COMMAND_TOPIC = "temperature_low_command_topic";
+constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TEMPLATE = "temperature_low_state_template";
+constexpr const char *const MQTT_TEMPERATURE_LOW_STATE_TOPIC = "temperature_low_state_topic";
+constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template";
+constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic";
+constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit";
+constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value";
+constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic";
+constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template";
+constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state";
+constexpr const char *const MQTT_TILT_MAX = "tilt_max";
+constexpr const char *const MQTT_TILT_MIN = "tilt_min";
+constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value";
+constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic";
+constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic";
+constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template";
+constexpr const char *const MQTT_TOPIC = "topic";
+constexpr const char *const MQTT_UNIQUE_ID = "unique_id";
+constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement";
+constexpr const char *const MQTT_VALUE_TEMPLATE = "value_template";
+constexpr const char *const MQTT_WHITE_COMMAND_TOPIC = "white_command_topic";
+constexpr const char *const MQTT_WHITE_SCALE = "white_scale";
+constexpr const char *const MQTT_WHITE_VALUE_COMMAND_TOPIC = "white_value_command_topic";
+constexpr const char *const MQTT_WHITE_VALUE_SCALE = "white_value_scale";
+constexpr const char *const MQTT_WHITE_VALUE_STATE_TOPIC = "white_value_state_topic";
+constexpr const char *const MQTT_WHITE_VALUE_TEMPLATE = "white_value_template";
+constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic";
+constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic";
+constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template";
+
+constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections";
+constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers";
+constexpr const char *const MQTT_DEVICE_NAME = "name";
+constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer";
+constexpr const char *const MQTT_DEVICE_MODEL = "model";
+constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version";
+constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area";
+#endif
+
+// Additional MQTT fields where no abbreviation is defined in HA source
+constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category";
+
+} // namespace mqtt
+} // namespace esphome
+
+#endif // USE_MQTT
diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp
index e8bc7f0e30..7e42abcd05 100644
--- a/esphome/components/mqtt/mqtt_cover.cpp
+++ b/esphome/components/mqtt/mqtt_cover.cpp
@@ -1,6 +1,8 @@
#include "mqtt_cover.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_COVER
@@ -22,7 +24,7 @@ void MQTTCoverComponent::setup() {
});
if (traits.get_supports_position()) {
this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) {
- auto value = parse_float(payload);
+ auto value = parse_number(payload);
if (!value.has_value()) {
ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str());
return;
@@ -34,7 +36,7 @@ void MQTTCoverComponent::setup() {
}
if (traits.get_supports_tilt()) {
this->subscribe(this->get_tilt_command_topic(), [this](const std::string &topic, const std::string &payload) {
- auto value = parse_float(payload);
+ auto value = parse_number(payload);
if (!value.has_value()) {
ESP_LOGW(TAG, "Invalid tilt value: '%s'", payload.c_str());
return;
@@ -63,20 +65,20 @@ void MQTTCoverComponent::dump_config() {
}
void MQTTCoverComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
if (!this->cover_->get_device_class().empty())
- root["device_class"] = this->cover_->get_device_class();
+ root[MQTT_DEVICE_CLASS] = this->cover_->get_device_class();
auto traits = this->cover_->get_traits();
if (traits.get_is_assumed_state()) {
- root["optimistic"] = true;
+ root[MQTT_OPTIMISTIC] = true;
}
if (traits.get_supports_position()) {
config.state_topic = false;
- root["position_topic"] = this->get_position_state_topic();
- root["set_position_topic"] = this->get_position_command_topic();
+ root[MQTT_POSITION_TOPIC] = this->get_position_state_topic();
+ root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic();
}
if (traits.get_supports_tilt()) {
- root["tilt_status_topic"] = this->get_tilt_state_topic();
- root["tilt_command_topic"] = this->get_tilt_command_topic();
+ root[MQTT_TILT_STATUS_TOPIC] = this->get_tilt_state_topic();
+ root[MQTT_TILT_COMMAND_TOPIC] = this->get_tilt_command_topic();
}
if (traits.get_supports_tilt() && !traits.get_supports_position()) {
config.command_topic = false;
diff --git a/esphome/components/mqtt/mqtt_fan.cpp b/esphome/components/mqtt/mqtt_fan.cpp
index 1703343a77..d58e3abc88 100644
--- a/esphome/components/mqtt/mqtt_fan.cpp
+++ b/esphome/components/mqtt/mqtt_fan.cpp
@@ -1,6 +1,8 @@
#include "mqtt_fan.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_FAN
#include "esphome/components/fan/fan_helpers.h"
@@ -69,7 +71,7 @@ void MQTTFanComponent::setup() {
if (this->state_->get_traits().supports_speed()) {
this->subscribe(this->get_speed_level_command_topic(),
[this](const std::string &topic, const std::string &payload) {
- optional speed_level_opt = parse_int(payload);
+ optional speed_level_opt = parse_number(payload);
if (speed_level_opt.has_value()) {
const int speed_level = speed_level_opt.value();
if (speed_level >= 0 && speed_level <= this->state_->get_traits().supported_speed_count()) {
@@ -120,14 +122,14 @@ bool MQTTFanComponent::send_initial_state() { return this->publish_state(); }
void MQTTFanComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
if (this->state_->get_traits().supports_oscillation()) {
- root["oscillation_command_topic"] = this->get_oscillation_command_topic();
- root["oscillation_state_topic"] = this->get_oscillation_state_topic();
+ root[MQTT_OSCILLATION_COMMAND_TOPIC] = this->get_oscillation_command_topic();
+ root[MQTT_OSCILLATION_STATE_TOPIC] = this->get_oscillation_state_topic();
}
if (this->state_->get_traits().supports_speed()) {
root["speed_level_command_topic"] = this->get_speed_level_command_topic();
root["speed_level_state_topic"] = this->get_speed_level_state_topic();
- root["speed_command_topic"] = this->get_speed_command_topic();
- root["speed_state_topic"] = this->get_speed_state_topic();
+ root[MQTT_SPEED_COMMAND_TOPIC] = this->get_speed_command_topic();
+ root[MQTT_SPEED_STATE_TOPIC] = this->get_speed_state_topic();
}
}
bool MQTTFanComponent::publish_state() {
diff --git a/esphome/components/mqtt/mqtt_light.cpp b/esphome/components/mqtt/mqtt_light.cpp
index a88358a6b2..54204a9e7f 100644
--- a/esphome/components/mqtt/mqtt_light.cpp
+++ b/esphome/components/mqtt/mqtt_light.cpp
@@ -1,6 +1,8 @@
#include "mqtt_light.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_LIGHT
@@ -38,7 +40,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover
root["schema"] = "json";
auto traits = this->state_->get_traits();
- root["color_mode"] = true;
+ root[MQTT_COLOR_MODE] = true;
JsonArray &color_modes = root.createNestedArray("supported_color_modes");
if (traits.supports_color_mode(ColorMode::ON_OFF))
color_modes.add("onoff");
@@ -64,7 +66,7 @@ void MQTTJSONLightComponent::send_discovery(JsonObject &root, mqtt::SendDiscover
if (this->state_->supports_effects()) {
root["effect"] = true;
- JsonArray &effect_list = root.createNestedArray("effect_list");
+ JsonArray &effect_list = root.createNestedArray(MQTT_EFFECT_LIST);
for (auto *effect : this->state_->get_effects())
effect_list.add(effect->get_name());
effect_list.add("None");
diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp
index 674fd77bdf..337013055a 100644
--- a/esphome/components/mqtt/mqtt_number.cpp
+++ b/esphome/components/mqtt/mqtt_number.cpp
@@ -1,6 +1,8 @@
#include "mqtt_number.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_NUMBER
@@ -15,7 +17,7 @@ MQTTNumberComponent::MQTTNumberComponent(Number *number) : MQTTComponent(), numb
void MQTTNumberComponent::setup() {
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &state) {
- auto val = parse_float(state);
+ auto val = parse_number(state);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", state.c_str());
return;
@@ -38,9 +40,9 @@ const EntityBase *MQTTNumberComponent::get_entity() const { return this->number_
void MQTTNumberComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
const auto &traits = number_->traits;
// https://www.home-assistant.io/integrations/number.mqtt/
- root["min"] = traits.get_min_value();
- root["max"] = traits.get_max_value();
- root["step"] = traits.get_step();
+ root[MQTT_MIN] = traits.get_min_value();
+ root[MQTT_MAX] = traits.get_max_value();
+ root[MQTT_STEP] = traits.get_step();
config.command_topic = true;
}
diff --git a/esphome/components/mqtt/mqtt_select.cpp b/esphome/components/mqtt/mqtt_select.cpp
index b499636006..b8371de00e 100644
--- a/esphome/components/mqtt/mqtt_select.cpp
+++ b/esphome/components/mqtt/mqtt_select.cpp
@@ -1,6 +1,8 @@
#include "mqtt_select.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_SELECT
@@ -33,7 +35,7 @@ const EntityBase *MQTTSelectComponent::get_entity() const { return this->select_
void MQTTSelectComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
const auto &traits = select_->traits;
// https://www.home-assistant.io/integrations/select.mqtt/
- JsonArray &options = root.createNestedArray("options");
+ JsonArray &options = root.createNestedArray(MQTT_OPTIONS);
for (const auto &option : traits.get_options())
options.add(option);
diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp
index 78710ff403..dd6423e8f3 100644
--- a/esphome/components/mqtt/mqtt_sensor.cpp
+++ b/esphome/components/mqtt/mqtt_sensor.cpp
@@ -1,6 +1,8 @@
#include "mqtt_sensor.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_SENSOR
@@ -42,19 +44,19 @@ void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; }
void MQTTSensorComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
if (!this->sensor_->get_device_class().empty())
- root["device_class"] = this->sensor_->get_device_class();
+ root[MQTT_DEVICE_CLASS] = this->sensor_->get_device_class();
if (!this->sensor_->get_unit_of_measurement().empty())
- root["unit_of_measurement"] = this->sensor_->get_unit_of_measurement();
+ root[MQTT_UNIT_OF_MEASUREMENT] = this->sensor_->get_unit_of_measurement();
if (this->get_expire_after() > 0)
- root["expire_after"] = this->get_expire_after() / 1000;
+ root[MQTT_EXPIRE_AFTER] = this->get_expire_after() / 1000;
if (this->sensor_->get_force_update())
- root["force_update"] = true;
+ root[MQTT_FORCE_UPDATE] = true;
if (this->sensor_->get_state_class() != STATE_CLASS_NONE)
- root["state_class"] = state_class_to_string(this->sensor_->get_state_class());
+ root[MQTT_STATE_CLASS] = state_class_to_string(this->sensor_->get_state_class());
config.command_topic = false;
}
diff --git a/esphome/components/mqtt/mqtt_switch.cpp b/esphome/components/mqtt/mqtt_switch.cpp
index 16cf102f7e..edaa6e7859 100644
--- a/esphome/components/mqtt/mqtt_switch.cpp
+++ b/esphome/components/mqtt/mqtt_switch.cpp
@@ -1,6 +1,8 @@
#include "mqtt_switch.h"
#include "esphome/core/log.h"
+#include "mqtt_const.h"
+
#ifdef USE_MQTT
#ifdef USE_SWITCH
@@ -44,7 +46,7 @@ std::string MQTTSwitchComponent::component_type() const { return "switch"; }
const EntityBase *MQTTSwitchComponent::get_entity() const { return this->switch_; }
void MQTTSwitchComponent::send_discovery(JsonObject &root, mqtt::SendDiscoveryConfig &config) {
if (this->switch_->assumed_state())
- root["optimistic"] = true;
+ root[MQTT_OPTIMISTIC] = true;
}
bool MQTTSwitchComponent::send_initial_state() { return this->publish_state(this->switch_->state); }
diff --git a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp
index e1accf3c70..273de10376 100644
--- a/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp
+++ b/esphome/components/mqtt_subscribe/sensor/mqtt_subscribe_sensor.cpp
@@ -13,7 +13,7 @@ void MQTTSubscribeSensor::setup() {
mqtt::global_mqtt_client->subscribe(
this->topic_,
[this](const std::string &topic, const std::string &payload) {
- auto val = parse_float(payload);
+ auto val = parse_number(payload);
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", payload.c_str());
this->publish_state(NAN);
diff --git a/esphome/components/ms5611/ms5611.cpp b/esphome/components/ms5611/ms5611.cpp
index 1d7516dbe8..4b34e1d71a 100644
--- a/esphome/components/ms5611/ms5611.cpp
+++ b/esphome/components/ms5611/ms5611.cpp
@@ -75,30 +75,48 @@ void MS5611Component::read_pressure_(uint32_t raw_temperature) {
const uint32_t raw_pressure = (uint32_t(bytes[0]) << 16) | (uint32_t(bytes[1]) << 8) | (uint32_t(bytes[2]));
this->calculate_values_(raw_temperature, raw_pressure);
}
+
+// Calculations are taken from the datasheet which can be found here:
+// https://www.te.com/commerce/DocumentDelivery/DDEController?Action=showdoc&DocId=Data+Sheet%7FMS5611-01BA03%7FB3%7Fpdf%7FEnglish%7FENG_DS_MS5611-01BA03_B3.pdf%7FCAT-BLPS0036
+// Sections PRESSURE AND TEMPERATURE CALCULATION and SECOND ORDER TEMPERATURE COMPENSATION
+// Variable names below match variable names from the datasheet but lowercased
void MS5611Component::calculate_values_(uint32_t raw_temperature, uint32_t raw_pressure) {
- const int32_t d_t = int32_t(raw_temperature) - (uint32_t(this->prom_[4]) << 8);
- float temperature = (2000 + (int64_t(d_t) * this->prom_[5]) / 8388608.0f) / 100.0f;
+ const uint32_t c1 = uint32_t(this->prom_[0]);
+ const uint32_t c2 = uint32_t(this->prom_[1]);
+ const uint16_t c3 = uint16_t(this->prom_[2]);
+ const uint16_t c4 = uint16_t(this->prom_[3]);
+ const int32_t c5 = int32_t(this->prom_[4]);
+ const uint16_t c6 = uint16_t(this->prom_[5]);
+ const uint32_t d1 = raw_pressure;
+ const int32_t d2 = raw_temperature;
- float pressure_offset = (uint32_t(this->prom_[1]) << 16) + ((this->prom_[3] * d_t) >> 7);
- float pressure_sensitivity = (uint32_t(this->prom_[0]) << 15) + ((this->prom_[2] * d_t) >> 8);
+ // Promote dt to 64 bit here to make the math below cleaner
+ const int64_t dt = d2 - (c5 << 8);
+ int32_t temp = (2000 + ((dt * c6) >> 23));
- if (temperature < 20.0f) {
- const float t2 = (d_t * d_t) / 2147483648.0f;
- const float temp20 = (temperature - 20.0f) * 100.0f;
- float pressure_offset_2 = 2.5f * temp20 * temp20;
- float pressure_sensitivity_2 = 1.25f * temp20 * temp20;
- if (temp20 < -15.0f) {
- const float temp15 = (temperature + 15.0f) * 100.0f;
- pressure_offset_2 += 7.0f * temp15;
- pressure_sensitivity_2 += 5.5f * temp15;
+ int64_t off = (c2 << 16) + ((dt * c4) >> 7);
+ int64_t sens = (c1 << 15) + ((dt * c3) >> 8);
+
+ if (temp < 2000) {
+ const int32_t t2 = (dt * dt) >> 31;
+ int32_t off2 = ((5 * (temp - 2000) * (temp - 2000)) >> 1);
+ int32_t sens2 = ((5 * (temp - 2000) * (temp - 2000)) >> 2);
+ if (temp < -1500) {
+ off2 = (off2 + 7 * (temp + 1500) * (temp + 1500));
+ sens2 = sens2 + ((11 * (temp + 1500) * (temp + 1500)) >> 1);
}
- temperature -= t2;
- pressure_offset -= pressure_offset_2;
- pressure_sensitivity -= pressure_sensitivity_2;
+ temp = temp - t2;
+ off = off - off2;
+ sens = sens - sens2;
}
- const float pressure = ((raw_pressure * pressure_sensitivity) / 2097152.0f - pressure_offset) / 3276800.0f;
+ // Here we multiply unsigned 32-bit by signed 64-bit using signed 64-bit math.
+ // Possible ranges of D1 and SENS from the datasheet guarantee
+ // that this multiplication does not overflow
+ const int32_t p = ((((d1 * sens) >> 21) - off) >> 15);
+ const float temperature = temp / 100.0f;
+ const float pressure = p / 100.0f;
ESP_LOGD(TAG, "Got temperature=%0.02f°C pressure=%0.01fhPa", temperature, pressure);
if (this->temperature_sensor_ != nullptr)
diff --git a/esphome/components/neopixelbus/_methods.py b/esphome/components/neopixelbus/_methods.py
new file mode 100644
index 0000000000..b03544f246
--- /dev/null
+++ b/esphome/components/neopixelbus/_methods.py
@@ -0,0 +1,418 @@
+from dataclasses import dataclass
+from typing import Any, List
+import esphome.codegen as cg
+import esphome.config_validation as cv
+from esphome.const import (
+ CONF_CHANNEL,
+ CONF_CLOCK_PIN,
+ CONF_DATA_PIN,
+ CONF_METHOD,
+ CONF_PIN,
+ CONF_SPEED,
+)
+from esphome.components.esp32 import get_esp32_variant
+from esphome.components.esp32.const import (
+ VARIANT_ESP32,
+ VARIANT_ESP32S2,
+ VARIANT_ESP32C3,
+)
+from esphome.core import CORE
+from .const import (
+ CONF_ASYNC,
+ CONF_BUS,
+ CHIP_400KBPS,
+ CHIP_800KBPS,
+ CHIP_APA106,
+ CHIP_DOTSTAR,
+ CHIP_LC8812,
+ CHIP_LPD6803,
+ CHIP_LPD8806,
+ CHIP_P9813,
+ CHIP_SK6812,
+ CHIP_TM1814,
+ CHIP_TM1829,
+ CHIP_TM1914,
+ CHIP_WS2801,
+ CHIP_WS2811,
+ CHIP_WS2812,
+ CHIP_WS2812X,
+ CHIP_WS2813,
+ ONE_WIRE_CHIPS,
+ TWO_WIRE_CHIPS,
+)
+
+METHOD_BIT_BANG = "bit_bang"
+METHOD_ESP8266_UART = "esp8266_uart"
+METHOD_ESP8266_DMA = "esp8266_dma"
+METHOD_ESP32_RMT = "esp32_rmt"
+METHOD_ESP32_I2S = "esp32_i2s"
+METHOD_SPI = "spi"
+
+CHANNEL_DYNAMIC = "dynamic"
+BUS_DYNAMIC = "dynamic"
+SPI_BUS_VSPI = "vspi"
+SPI_BUS_HSPI = "hspi"
+SPI_SPEEDS = [40e6, 20e6, 10e6, 5e6, 2e6, 1e6, 500e3]
+
+
+def _esp32_rmt_default_channel():
+ return {
+ VARIANT_ESP32S2: 1,
+ VARIANT_ESP32C3: 1,
+ }.get(get_esp32_variant(), 6)
+
+
+def _validate_esp32_rmt_channel(value):
+ if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC:
+ value = CHANNEL_DYNAMIC
+ else:
+ value = cv.int_(value)
+ variant_channels = {
+ VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7, CHANNEL_DYNAMIC],
+ VARIANT_ESP32S2: [0, 1, 2, 3, CHANNEL_DYNAMIC],
+ VARIANT_ESP32C3: [0, 1, CHANNEL_DYNAMIC],
+ }
+ variant = get_esp32_variant()
+ if variant not in variant_channels:
+ raise cv.Invalid(f"{variant} does not support the rmt method")
+ if value not in variant_channels[variant]:
+ raise cv.Invalid(f"{variant} does not support rmt channel {value}")
+ return value
+
+
+def _esp32_i2s_default_bus():
+ return {
+ VARIANT_ESP32: 1,
+ VARIANT_ESP32S2: 0,
+ }.get(get_esp32_variant(), 0)
+
+
+def _validate_esp32_i2s_bus(value):
+ if isinstance(value, str) and value.lower() == CHANNEL_DYNAMIC:
+ value = CHANNEL_DYNAMIC
+ else:
+ value = cv.int_(value)
+ variant_buses = {
+ VARIANT_ESP32: [0, 1, BUS_DYNAMIC],
+ VARIANT_ESP32S2: [0, BUS_DYNAMIC],
+ }
+ variant = get_esp32_variant()
+ if variant not in variant_buses:
+ raise cv.Invalid(f"{variant} does not support the i2s method")
+ if value not in variant_buses[variant]:
+ raise cv.Invalid(f"{variant} does not support i2s bus {value}")
+ return value
+
+
+neo_ns = cg.global_ns
+
+
+def _bit_bang_to_code(config, chip: str, inverted: bool):
+ # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEspBitBangMethod.h
+ # Some chips are only aliases
+ chip = {
+ CHIP_WS2813: CHIP_WS2812X,
+ CHIP_LC8812: CHIP_SK6812,
+ CHIP_TM1914: CHIP_TM1814,
+ CHIP_WS2812: CHIP_800KBPS,
+ }.get(chip, chip)
+
+ lookup = {
+ CHIP_WS2811: (neo_ns.NeoEspBitBangSpeedWs2811, False),
+ CHIP_WS2812X: (neo_ns.NeoEspBitBangSpeedWs2812x, False),
+ CHIP_SK6812: (neo_ns.NeoEspBitBangSpeedSk6812, False),
+ CHIP_TM1814: (neo_ns.NeoEspBitBangSpeedTm1814, True),
+ CHIP_TM1829: (neo_ns.NeoEspBitBangSpeedTm1829, True),
+ CHIP_800KBPS: (neo_ns.NeoEspBitBangSpeed800Kbps, False),
+ CHIP_400KBPS: (neo_ns.NeoEspBitBangSpeed400Kbps, False),
+ CHIP_APA106: (neo_ns.NeoEspBitBangSpeedApa106, False),
+ }
+ # For tm variants opposite of inverted is needed
+ speed, pinset_inverted = lookup[chip]
+ pinset = {
+ False: neo_ns.NeoEspPinset,
+ True: neo_ns.NeoEspPinsetInverted,
+ }[inverted != pinset_inverted]
+ return neo_ns.NeoEspBitBangMethodBase.template(speed, pinset)
+
+
+def _bit_bang_extra_validate(config):
+ pin = config[CONF_PIN]
+ if CORE.is_esp8266 and not (0 <= pin <= 15):
+ # Due to use of w1ts
+ raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO15 on ESP8266")
+ if CORE.is_esp32 and not (0 <= pin <= 31):
+ raise cv.Invalid("Bit bang only supports pins GPIO0-GPIO31 on ESP32")
+
+
+def _esp8266_uart_to_code(config, chip: str, inverted: bool):
+ # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266UartMethod.h
+ uart_context, uart_base = {
+ False: (neo_ns.NeoEsp8266UartContext, neo_ns.NeoEsp8266Uart),
+ True: (neo_ns.NeoEsp8266UartInterruptContext, neo_ns.NeoEsp8266AsyncUart),
+ }[config[CONF_ASYNC]]
+ uart_feature = {
+ 0: neo_ns.UartFeature0,
+ 1: neo_ns.UartFeature1,
+ }[config[CONF_BUS]]
+ # Some chips are only aliases
+ chip = {
+ CHIP_WS2811: CHIP_WS2812X,
+ CHIP_WS2813: CHIP_WS2812X,
+ CHIP_LC8812: CHIP_SK6812,
+ CHIP_TM1914: CHIP_TM1814,
+ CHIP_WS2812: CHIP_800KBPS,
+ }.get(chip, chip)
+
+ lookup = {
+ CHIP_WS2812X: (neo_ns.NeoEsp8266UartSpeedWs2812x, False),
+ CHIP_SK6812: (neo_ns.NeoEsp8266UartSpeedSk6812, False),
+ CHIP_TM1814: (neo_ns.NeoEsp8266UartSpeedTm1814, True),
+ CHIP_TM1829: (neo_ns.NeoEsp8266UartSpeedTm1829, True),
+ CHIP_800KBPS: (neo_ns.NeoEsp8266UartSpeed800Kbps, False),
+ CHIP_400KBPS: (neo_ns.NeoEsp8266UartSpeed400Kbps, False),
+ CHIP_APA106: (neo_ns.NeoEsp8266UartSpeedApa106, False),
+ }
+ speed, uart_inverted = lookup[chip]
+ # For tm variants opposite of inverted is needed
+ inv = {
+ False: neo_ns.NeoEsp8266UartNotInverted,
+ True: neo_ns.NeoEsp8266UartInverted,
+ }[inverted != uart_inverted]
+ return neo_ns.NeoEsp8266UartMethodBase.template(
+ speed, uart_base.template(uart_feature, uart_context), inv
+ )
+
+
+def _esp8266_uart_extra_validate(config):
+ pin = config[CONF_PIN]
+ bus = config[CONF_METHOD][CONF_BUS]
+ right_pin = {
+ 0: 1, # U0TXD
+ 1: 2, # U1TXD
+ }[bus]
+ if pin != right_pin:
+ raise cv.Invalid(f"ESP8266 uart bus {bus} only supports pin GPIO{right_pin}")
+
+
+def _esp8266_dma_to_code(config, chip: str, inverted: bool):
+ # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp8266DmaMethod.h
+ # Some chips are only aliases
+ chip = {
+ CHIP_WS2811: CHIP_WS2812X,
+ CHIP_WS2813: CHIP_WS2812X,
+ CHIP_LC8812: CHIP_SK6812,
+ CHIP_TM1914: CHIP_TM1814,
+ CHIP_WS2812: CHIP_800KBPS,
+ }.get(chip, chip)
+
+ lookup = {
+ (CHIP_WS2812X, False): neo_ns.NeoEsp8266DmaSpeedWs2812x,
+ (CHIP_SK6812, False): neo_ns.NeoEsp8266DmaSpeedSk6812,
+ (CHIP_TM1814, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1814,
+ (CHIP_TM1829, True): neo_ns.NeoEsp8266DmaInvertedSpeedTm1829,
+ (CHIP_800KBPS, False): neo_ns.NeoEsp8266DmaSpeed800Kbps,
+ (CHIP_400KBPS, False): neo_ns.NeoEsp8266DmaSpeed400Kbps,
+ (CHIP_APA106, False): neo_ns.NeoEsp8266DmaSpeedApa106,
+ (CHIP_WS2812X, True): neo_ns.NeoEsp8266DmaInvertedSpeedWs2812x,
+ (CHIP_SK6812, True): neo_ns.NeoEsp8266DmaInvertedSpeedSk6812,
+ (CHIP_TM1814, False): neo_ns.NeoEsp8266DmaSpeedTm1814,
+ (CHIP_TM1829, False): neo_ns.NeoEsp8266DmaSpeedTm1829,
+ (CHIP_800KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed800Kbps,
+ (CHIP_400KBPS, True): neo_ns.NeoEsp8266DmaInvertedSpeed400Kbps,
+ (CHIP_APA106, True): neo_ns.NeoEsp8266DmaInvertedSpeedApa106,
+ }
+ speed = lookup[(chip, inverted)]
+ return neo_ns.NeoEsp8266DmaMethodBase.template(speed)
+
+
+def _esp8266_dma_extra_validate(config):
+ if config[CONF_PIN] != 3:
+ raise cv.Invalid("ESP8266 dma method only supports pin GPIO3")
+
+
+def _esp32_rmt_to_code(config, chip: str, inverted: bool):
+ # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32RmtMethod.h
+ channel = {
+ 0: neo_ns.NeoEsp32RmtChannel0,
+ 1: neo_ns.NeoEsp32RmtChannel1,
+ 2: neo_ns.NeoEsp32RmtChannel2,
+ 3: neo_ns.NeoEsp32RmtChannel3,
+ 4: neo_ns.NeoEsp32RmtChannel4,
+ 5: neo_ns.NeoEsp32RmtChannel5,
+ 6: neo_ns.NeoEsp32RmtChannel6,
+ 7: neo_ns.NeoEsp32RmtChannel7,
+ CHANNEL_DYNAMIC: neo_ns.NeoEsp32RmtChannelN,
+ }[config[CONF_CHANNEL]]
+ # Some chips are only aliases
+ chip = {
+ CHIP_WS2813: CHIP_WS2812X,
+ CHIP_LC8812: CHIP_SK6812,
+ CHIP_WS2812: CHIP_800KBPS,
+ }.get(chip, chip)
+
+ lookup = {
+ (CHIP_WS2811, False): neo_ns.NeoEsp32RmtSpeedWs2811,
+ (CHIP_WS2812X, False): neo_ns.NeoEsp32RmtSpeedWs2812x,
+ (CHIP_SK6812, False): neo_ns.NeoEsp32RmtSpeedSk6812,
+ (CHIP_TM1814, False): neo_ns.NeoEsp32RmtSpeedTm1814,
+ (CHIP_TM1829, False): neo_ns.NeoEsp32RmtSpeedTm1829,
+ (CHIP_TM1914, False): neo_ns.NeoEsp32RmtSpeedTm1914,
+ (CHIP_800KBPS, False): neo_ns.NeoEsp32RmtSpeed800Kbps,
+ (CHIP_400KBPS, False): neo_ns.NeoEsp32RmtSpeed400Kbps,
+ (CHIP_APA106, False): neo_ns.NeoEsp32RmtSpeedApa106,
+ (CHIP_WS2811, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2811,
+ (CHIP_WS2812X, True): neo_ns.NeoEsp32RmtInvertedSpeedWs2812x,
+ (CHIP_SK6812, True): neo_ns.NeoEsp32RmtInvertedSpeedSk6812,
+ (CHIP_TM1814, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1814,
+ (CHIP_TM1829, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1829,
+ (CHIP_TM1914, True): neo_ns.NeoEsp32RmtInvertedSpeedTm1914,
+ (CHIP_800KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed800Kbps,
+ (CHIP_400KBPS, True): neo_ns.NeoEsp32RmtInvertedSpeed400Kbps,
+ (CHIP_APA106, True): neo_ns.NeoEsp32RmtInvertedSpeedApa106,
+ }
+ speed = lookup[(chip, inverted)]
+ return neo_ns.NeoEsp32RmtMethodBase.template(speed, channel)
+
+
+def _esp32_i2s_to_code(config, chip: str, inverted: bool):
+ # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/NeoEsp32I2sMethod.h
+ bus = {
+ 0: neo_ns.NeoEsp32I2sBusZero,
+ 1: neo_ns.NeoEsp32I2sBusOne,
+ BUS_DYNAMIC: neo_ns.NeoEsp32I2sBusN,
+ }[config[CONF_BUS]]
+ # Some chips are only aliases
+ chip = {
+ CHIP_WS2811: CHIP_WS2812X,
+ CHIP_WS2813: CHIP_WS2812X,
+ CHIP_LC8812: CHIP_SK6812,
+ CHIP_WS2812: CHIP_800KBPS,
+ }.get(chip, chip)
+
+ lookup = {
+ CHIP_WS2812X: (neo_ns.NeoEsp32I2sSpeedWs2812x, False),
+ CHIP_SK6812: (neo_ns.NeoEsp32I2sSpeedSk6812, False),
+ CHIP_TM1814: (neo_ns.NeoEsp32I2sSpeedTm1814, True),
+ CHIP_TM1914: (neo_ns.NeoEsp32I2sSpeedTm1914, True),
+ CHIP_TM1829: (neo_ns.NeoEsp32I2sSpeedTm1829, True),
+ CHIP_800KBPS: (neo_ns.NeoEsp32I2sSpeed800Kbps, False),
+ CHIP_400KBPS: (neo_ns.NeoEsp32I2sSpeed400Kbps, False),
+ CHIP_APA106: (neo_ns.NeoEsp32I2sSpeedApa106, False),
+ }
+ speed, inv_inverted = lookup[chip]
+ # For tm variants opposite of inverted is needed
+ inv = {
+ False: neo_ns.NeoEsp32I2sNotInverted,
+ True: neo_ns.NeoEsp32I2sInverted,
+ }[inverted != inv_inverted]
+ return neo_ns.NeoEsp32I2sMethodBase.template(speed, bus, inv)
+
+
+def _spi_to_code(config, chip: str, inverted: bool):
+ # https://github.com/Makuna/NeoPixelBus/blob/master/src/internal/TwoWireSpiImple.h
+ spi_imple = {
+ None: neo_ns.TwoWireSpiImple,
+ SPI_BUS_VSPI: neo_ns.TwoWireSpiImple,
+ SPI_BUS_HSPI: neo_ns.TwoWireHspiImple,
+ }[config.get(CONF_BUS)]
+ spi_speed = {
+ 40e6: neo_ns.SpiSpeed40Mhz,
+ 20e6: neo_ns.SpiSpeed20Mhz,
+ 10e6: neo_ns.SpiSpeed10Mhz,
+ 5e6: neo_ns.SpiSpeed5Mhz,
+ 2e6: neo_ns.SpiSpeed2Mhz,
+ 1e6: neo_ns.SpiSpeed1Mhz,
+ 500e3: neo_ns.SpiSpeed500Khz,
+ }[config[CONF_SPEED]]
+ chip_method_base = {
+ CHIP_DOTSTAR: neo_ns.DotStarMethodBase,
+ CHIP_LPD6803: neo_ns.Lpd6803MethodBase,
+ CHIP_LPD8806: neo_ns.Lpd8806MethodBase,
+ CHIP_WS2801: neo_ns.Ws2801MethodBase,
+ CHIP_P9813: neo_ns.P9813MethodBase,
+ }[chip]
+ return chip_method_base.template(spi_imple.template(spi_speed))
+
+
+def _spi_extra_validate(config):
+ if CORE.is_esp32:
+ return
+
+ if config[CONF_DATA_PIN] != 13 and config[CONF_CLOCK_PIN] != 14:
+ raise cv.Invalid(
+ "SPI only supports pins GPIO13 for data and GPIO14 for clock on ESP8266"
+ )
+
+
+@dataclass
+class MethodDescriptor:
+ method_schema: Any
+ to_code: Any
+ supported_chips: List[str]
+ extra_validate: Any = None
+
+
+METHODS = {
+ METHOD_BIT_BANG: MethodDescriptor(
+ method_schema={},
+ to_code=_bit_bang_to_code,
+ extra_validate=_bit_bang_extra_validate,
+ supported_chips=ONE_WIRE_CHIPS,
+ ),
+ METHOD_ESP8266_UART: MethodDescriptor(
+ method_schema=cv.All(
+ cv.only_on_esp8266,
+ {
+ cv.Optional(CONF_ASYNC, default=False): cv.boolean,
+ cv.Optional(CONF_BUS, default=1): cv.int_range(min=0, max=1),
+ },
+ ),
+ extra_validate=_esp8266_uart_extra_validate,
+ to_code=_esp8266_uart_to_code,
+ supported_chips=ONE_WIRE_CHIPS,
+ ),
+ METHOD_ESP8266_DMA: MethodDescriptor(
+ method_schema=cv.All(cv.only_on_esp8266, {}),
+ extra_validate=_esp8266_dma_extra_validate,
+ to_code=_esp8266_dma_to_code,
+ supported_chips=ONE_WIRE_CHIPS,
+ ),
+ METHOD_ESP32_RMT: MethodDescriptor(
+ method_schema=cv.All(
+ cv.only_on_esp32,
+ {
+ cv.Optional(
+ CONF_CHANNEL, default=_esp32_rmt_default_channel
+ ): _validate_esp32_rmt_channel,
+ },
+ ),
+ to_code=_esp32_rmt_to_code,
+ supported_chips=ONE_WIRE_CHIPS,
+ ),
+ METHOD_ESP32_I2S: MethodDescriptor(
+ method_schema=cv.All(
+ cv.only_on_esp32,
+ {
+ cv.Optional(
+ CONF_BUS, default=_esp32_i2s_default_bus
+ ): _validate_esp32_i2s_bus,
+ },
+ ),
+ to_code=_esp32_i2s_to_code,
+ supported_chips=ONE_WIRE_CHIPS,
+ ),
+ METHOD_SPI: MethodDescriptor(
+ method_schema={
+ cv.Optional(CONF_BUS): cv.All(
+ cv.only_on_esp32, cv.one_of(SPI_BUS_VSPI, SPI_BUS_HSPI, lower=True)
+ ),
+ cv.Optional(CONF_SPEED, default="10MHz"): cv.All(
+ cv.frequency, cv.one_of(*SPI_SPEEDS)
+ ),
+ },
+ to_code=_spi_to_code,
+ extra_validate=_spi_extra_validate,
+ supported_chips=TWO_WIRE_CHIPS,
+ ),
+}
diff --git a/esphome/components/neopixelbus/const.py b/esphome/components/neopixelbus/const.py
new file mode 100644
index 0000000000..ec1bd74c29
--- /dev/null
+++ b/esphome/components/neopixelbus/const.py
@@ -0,0 +1,42 @@
+CHIP_DOTSTAR = "dotstar"
+CHIP_WS2801 = "ws2801"
+CHIP_WS2811 = "ws2811"
+CHIP_WS2812 = "ws2812"
+CHIP_WS2812X = "ws2812x"
+CHIP_WS2813 = "ws2813"
+CHIP_SK6812 = "sk6812"
+CHIP_TM1814 = "tm1814"
+CHIP_TM1829 = "tm1829"
+CHIP_TM1914 = "tm1914"
+CHIP_800KBPS = "800kbps"
+CHIP_400KBPS = "400kbps"
+CHIP_APA106 = "apa106"
+CHIP_LC8812 = "lc8812"
+CHIP_LPD8806 = "lpd8806"
+CHIP_LPD6803 = "lpd6803"
+CHIP_P9813 = "p9813"
+
+ONE_WIRE_CHIPS = [
+ CHIP_WS2811,
+ CHIP_WS2812,
+ CHIP_WS2812X,
+ CHIP_WS2813,
+ CHIP_SK6812,
+ CHIP_TM1814,
+ CHIP_TM1829,
+ CHIP_TM1914,
+ CHIP_800KBPS,
+ CHIP_400KBPS,
+ CHIP_APA106,
+ CHIP_LC8812,
+]
+TWO_WIRE_CHIPS = [
+ CHIP_DOTSTAR,
+ CHIP_WS2801,
+ CHIP_LPD6803,
+ CHIP_LPD8806,
+ CHIP_P9813,
+]
+CHIP_TYPES = [*ONE_WIRE_CHIPS, *TWO_WIRE_CHIPS]
+CONF_ASYNC = "async"
+CONF_BUS = "bus"
diff --git a/esphome/components/neopixelbus/light.py b/esphome/components/neopixelbus/light.py
index 0117f1b063..6bb1bc8f99 100644
--- a/esphome/components/neopixelbus/light.py
+++ b/esphome/components/neopixelbus/light.py
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome import pins
from esphome.components import light
from esphome.const import (
+ CONF_CHANNEL,
CONF_CLOCK_PIN,
CONF_DATA_PIN,
CONF_METHOD,
@@ -13,7 +14,26 @@ from esphome.const import (
CONF_OUTPUT_ID,
CONF_INVERT,
)
+from esphome.components.esp32 import get_esp32_variant
+from esphome.components.esp32.const import (
+ VARIANT_ESP32C3,
+)
from esphome.core import CORE
+from ._methods import (
+ METHODS,
+ METHOD_SPI,
+ METHOD_ESP8266_UART,
+ METHOD_BIT_BANG,
+ METHOD_ESP32_I2S,
+ METHOD_ESP32_RMT,
+ METHOD_ESP8266_DMA,
+)
+from .const import (
+ CHIP_TYPES,
+ CONF_ASYNC,
+ CONF_BUS,
+ ONE_WIRE_CHIPS,
+)
neopixelbus_ns = cg.esphome_ns.namespace("neopixelbus")
NeoPixelBusLightOutputBase = neopixelbus_ns.class_(
@@ -46,127 +66,115 @@ def validate_type(value):
return value
-def validate_variant(value):
- value = cv.string(value).upper()
- if value == "WS2813":
- value = "WS2812X"
- if value == "WS2812":
- value = "800KBPS"
- if value == "LC8812":
- value = "SK6812"
- return cv.one_of(*VARIANTS)(value)
+def _choose_default_method(config):
+ if CONF_METHOD in config:
+ return config
+ config = config.copy()
+ if CONF_PIN not in config:
+ config[CONF_METHOD] = _validate_method(METHOD_SPI)
+ return config
-
-def validate_method(value):
- if value is None:
- if CORE.is_esp32:
- return "ESP32_I2S_1"
- if CORE.is_esp8266:
- return "ESP8266_DMA"
- raise NotImplementedError
-
- if CORE.is_esp32:
- return cv.one_of(*ESP32_METHODS, upper=True, space="_")(value)
+ pin = config[CONF_PIN]
if CORE.is_esp8266:
- return cv.one_of(*ESP8266_METHODS, upper=True, space="_")(value)
- raise NotImplementedError
-
-
-def validate_method_pin(value):
- method = value[CONF_METHOD]
- method_pins = {
- "ESP8266_DMA": [3],
- "ESP8266_UART0": [1],
- "ESP8266_ASYNC_UART0": [1],
- "ESP8266_UART1": [2],
- "ESP8266_ASYNC_UART1": [2],
- "ESP32_I2S_0": list(range(0, 32)),
- "ESP32_I2S_1": list(range(0, 32)),
- }
- if CORE.is_esp8266:
- method_pins["BIT_BANG"] = list(range(0, 16))
- elif CORE.is_esp32:
- method_pins["BIT_BANG"] = list(range(0, 32))
- pins_ = method_pins.get(method)
- if pins_ is None:
- # all pins allowed for this method
- return value
-
- for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN):
- if opt in value and value[opt] not in pins_:
- raise cv.Invalid(
- f"Method {method} only supports pin(s) {', '.join(f'GPIO{x}' for x in pins_)}",
- path=[CONF_METHOD],
+ if pin == 3:
+ config[CONF_METHOD] = _validate_method(METHOD_ESP8266_DMA)
+ elif pin == 1:
+ config[CONF_METHOD] = _validate_method(
+ {
+ CONF_TYPE: METHOD_ESP8266_UART,
+ CONF_BUS: 0,
+ }
+ )
+ elif pin == 2:
+ config[CONF_METHOD] = _validate_method(
+ {
+ CONF_TYPE: METHOD_ESP8266_UART,
+ CONF_BUS: 1,
+ }
)
- return value
-
-
-VARIANTS = {
- "WS2812X": "Ws2812x",
- "SK6812": "Sk6812",
- "800KBPS": "800Kbps",
- "400KBPS": "400Kbps",
-}
-
-ESP8266_METHODS = {
- "ESP8266_DMA": "NeoEsp8266Dma{}Method",
- "ESP8266_UART0": "NeoEsp8266Uart0{}Method",
- "ESP8266_UART1": "NeoEsp8266Uart1{}Method",
- "ESP8266_ASYNC_UART0": "NeoEsp8266AsyncUart0{}Method",
- "ESP8266_ASYNC_UART1": "NeoEsp8266AsyncUart1{}Method",
- "BIT_BANG": "NeoEsp8266BitBang{}Method",
-}
-ESP32_METHODS = {
- "ESP32_I2S_0": "NeoEsp32I2s0{}Method",
- "ESP32_I2S_1": "NeoEsp32I2s1{}Method",
- "ESP32_RMT_0": "NeoEsp32Rmt0{}Method",
- "ESP32_RMT_1": "NeoEsp32Rmt1{}Method",
- "ESP32_RMT_2": "NeoEsp32Rmt2{}Method",
- "ESP32_RMT_3": "NeoEsp32Rmt3{}Method",
- "ESP32_RMT_4": "NeoEsp32Rmt4{}Method",
- "ESP32_RMT_5": "NeoEsp32Rmt5{}Method",
- "ESP32_RMT_6": "NeoEsp32Rmt6{}Method",
- "ESP32_RMT_7": "NeoEsp32Rmt7{}Method",
- "BIT_BANG": "NeoEsp32BitBang{}Method",
-}
-
-
-def format_method(config):
- variant = VARIANTS[config[CONF_VARIANT]]
- method = config[CONF_METHOD]
-
- if config[CONF_INVERT]:
- if method == "ESP8266_DMA":
- variant = f"Inverted{variant}"
else:
- variant += "Inverted"
+ config[CONF_METHOD] = _validate_method(METHOD_BIT_BANG)
- if CORE.is_esp8266:
- return ESP8266_METHODS[method].format(variant)
if CORE.is_esp32:
- return ESP32_METHODS[method].format(variant)
- raise NotImplementedError
+ if get_esp32_variant() == VARIANT_ESP32C3:
+ config[CONF_METHOD] = _validate_method(METHOD_ESP32_RMT)
+ else:
+ config[CONF_METHOD] = _validate_method(METHOD_ESP32_I2S)
+
+ return config
def _validate(config):
- if CONF_PIN in config:
+ variant = config[CONF_VARIANT]
+ if variant in ONE_WIRE_CHIPS:
+ if CONF_PIN not in config:
+ raise cv.Invalid(
+ f"Chip {variant} is a 1-wire chip and needs the [pin] option."
+ )
if CONF_CLOCK_PIN in config or CONF_DATA_PIN in config:
- raise cv.Invalid("Cannot specify both 'pin' and 'clock_pin'+'data_pin'")
- return config
- if CONF_CLOCK_PIN in config:
- if CONF_DATA_PIN not in config:
- raise cv.Invalid("If you give clock_pin, you must also specify data_pin")
- return config
- raise cv.Invalid("Must specify at least one of 'pin' or 'clock_pin'+'data_pin'")
+ raise cv.Invalid(
+ f"Chip {variant} is a 1-wire chip, you need to set [pin] instead of ."
+ )
+ else:
+ if CONF_PIN in config:
+ raise cv.Invalid(
+ f"Chip {variant} is a 2-wire chip and needs the [data_pin]+[clock_pin] option instead of [pin]."
+ )
+ if CONF_CLOCK_PIN not in config or CONF_DATA_PIN not in config:
+ raise cv.Invalid(
+ f"Chip {variant} is a 2-wire chip, you need to set [data_pin]+[clock_pin]."
+ )
+
+ method_type = config[CONF_METHOD][CONF_TYPE]
+ method_desc = METHODS[method_type]
+ if variant not in method_desc.supported_chips:
+ raise cv.Invalid(f"Method {method_type} does not support {variant}")
+ if method_desc.extra_validate is not None:
+ method_desc.extra_validate(config)
+
+ return config
+
+
+def _validate_method(value):
+ if value is None:
+ # default method is determined afterwards because it depends on the chip type chosen
+ return None
+
+ compat_methods = {}
+ for bus in [0, 1]:
+ for is_async in [False, True]:
+ compat_methods[f"ESP8266{'_ASYNC' if is_async else ''}_UART{bus}"] = {
+ CONF_TYPE: METHOD_ESP8266_UART,
+ CONF_BUS: bus,
+ CONF_ASYNC: is_async,
+ }
+ compat_methods[f"ESP32_I2S_{bus}"] = {
+ CONF_TYPE: METHOD_ESP32_I2S,
+ CONF_BUS: bus,
+ }
+ for channel in range(8):
+ compat_methods[f"ESP32_RMT_{channel}"] = {
+ CONF_TYPE: METHOD_ESP32_RMT,
+ CONF_CHANNEL: channel,
+ }
+
+ if isinstance(value, str):
+ if value.upper() in compat_methods:
+ return _validate_method(compat_methods[value.upper()])
+ return _validate_method({CONF_TYPE: value})
+ return cv.typed_schema(
+ {k: v.method_schema for k, v in METHODS.items()}, lower=True
+ )(value)
CONFIG_SCHEMA = cv.All(
+ cv.only_with_arduino,
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(NeoPixelBusLightOutputBase),
cv.Optional(CONF_TYPE, default="GRB"): validate_type,
- cv.Optional(CONF_VARIANT, default="800KBPS"): validate_variant,
- cv.Optional(CONF_METHOD, default=None): validate_method,
+ cv.Required(CONF_VARIANT): cv.one_of(*CHIP_TYPES, lower=True),
+ cv.Optional(CONF_METHOD): _validate_method,
cv.Optional(CONF_INVERT, default="no"): cv.boolean,
cv.Optional(CONF_PIN): pins.internal_gpio_output_pin_number,
cv.Optional(CONF_CLOCK_PIN): pins.internal_gpio_output_pin_number,
@@ -174,19 +182,23 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
}
).extend(cv.COMPONENT_SCHEMA),
+ _choose_default_method,
_validate,
- validate_method_pin,
- cv.only_with_arduino,
)
async def to_code(config):
has_white = "W" in config[CONF_TYPE]
- template = cg.TemplateArguments(getattr(cg.global_ns, format_method(config)))
+ method = config[CONF_METHOD]
+
+ method_template = METHODS[method[CONF_TYPE]].to_code(
+ method, config[CONF_VARIANT], config[CONF_INVERT]
+ )
+
if has_white:
- out_type = NeoPixelRGBWLightOutput.template(template)
+ out_type = NeoPixelRGBWLightOutput.template(method_template)
else:
- out_type = NeoPixelRGBLightOutput.template(template)
+ out_type = NeoPixelRGBLightOutput.template(method_template)
rhs = out_type.new()
var = cg.Pvariable(config[CONF_OUTPUT_ID], rhs, out_type)
await light.register_light(var, config)
@@ -204,4 +216,4 @@ async def to_code(config):
cg.add(var.set_pixel_order(getattr(ESPNeoPixelOrder, config[CONF_TYPE])))
# https://github.com/Makuna/NeoPixelBus/blob/master/library.json
- cg.add_library("makuna/NeoPixelBus", "2.6.7")
+ cg.add_library("makuna/NeoPixelBus", "2.6.9")
diff --git a/esphome/components/number/__init__.py b/esphome/components/number/__init__.py
index 2856a25ee7..bb3427e4bd 100644
--- a/esphome/components/number/__init__.py
+++ b/esphome/components/number/__init__.py
@@ -41,7 +41,6 @@ NumberInRangeCondition = number_ns.class_(
icon = cv.icon
-
NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
diff --git a/esphome/components/output/switch/output_switch.h b/esphome/components/output/switch/output_switch.h
index fc9540fede..a184a342fe 100644
--- a/esphome/components/output/switch/output_switch.h
+++ b/esphome/components/output/switch/output_switch.h
@@ -12,7 +12,7 @@ class OutputSwitch : public switch_::Switch, public Component {
void set_output(BinaryOutput *output) { output_ = output; }
void setup() override;
- float get_setup_priority() const override { return setup_priority::HARDWARE; }
+ float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
void dump_config() override;
protected:
diff --git a/esphome/components/pipsolar/pipsolar.cpp b/esphome/components/pipsolar/pipsolar.cpp
index 7dbbd798ad..9f8b57003a 100644
--- a/esphome/components/pipsolar/pipsolar.cpp
+++ b/esphome/components/pipsolar/pipsolar.cpp
@@ -656,7 +656,7 @@ void Pipsolar::loop() {
case 32:
fc = tmp[i];
fc += tmp[i + 1];
- this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10);
+ this->value_fault_code_ = parse_number(fc).value_or(0);
break;
case 34:
this->value_warnung_low_pv_energy_ = enabled;
diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.cpp b/esphome/components/pulse_counter/pulse_counter_sensor.cpp
index f538a4c905..d9f198f4fc 100644
--- a/esphome/components/pulse_counter/pulse_counter_sensor.cpp
+++ b/esphome/components/pulse_counter/pulse_counter_sensor.cpp
@@ -155,16 +155,20 @@ void PulseCounterSensor::dump_config() {
void PulseCounterSensor::update() {
pulse_counter_t raw = this->storage_.read_raw_value();
- float value = (60000.0f * raw) / float(this->get_update_interval()); // per minute
-
- ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value);
- this->publish_state(value);
+ uint32_t now = millis();
+ if (this->last_time_ != 0) {
+ uint32_t interval = now - this->last_time_;
+ float value = (60000.0f * raw) / float(interval); // per minute
+ ESP_LOGD(TAG, "'%s': Retrieved counter: %0.2f pulses/min", this->get_name().c_str(), value);
+ this->publish_state(value);
+ }
if (this->total_sensor_ != nullptr) {
current_total_ += raw;
ESP_LOGD(TAG, "'%s': Total : %i pulses", this->get_name().c_str(), current_total_);
this->total_sensor_->publish_state(current_total_);
}
+ this->last_time_ = now;
}
} // namespace pulse_counter
diff --git a/esphome/components/pulse_counter/pulse_counter_sensor.h b/esphome/components/pulse_counter/pulse_counter_sensor.h
index 94e37bc232..9ed2159ae3 100644
--- a/esphome/components/pulse_counter/pulse_counter_sensor.h
+++ b/esphome/components/pulse_counter/pulse_counter_sensor.h
@@ -65,7 +65,8 @@ class PulseCounterSensor : public sensor::Sensor, public PollingComponent {
protected:
InternalGPIOPin *pin_;
PulseCounterStorage storage_;
- uint32_t current_total_ = 0;
+ uint32_t last_time_{0};
+ uint32_t current_total_{0};
sensor::Sensor *total_sensor_;
};
diff --git a/esphome/components/pvvx_mithermometer/sensor.py b/esphome/components/pvvx_mithermometer/sensor.py
index b17878f01b..12090bddba 100644
--- a/esphome/components/pvvx_mithermometer/sensor.py
+++ b/esphome/components/pvvx_mithermometer/sensor.py
@@ -12,6 +12,7 @@ from esphome.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -49,12 +50,14 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
unit_of_measurement=UNIT_VOLT,
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/remote_base/__init__.py b/esphome/components/remote_base/__init__.py
index d2b848600d..914ce42efe 100644
--- a/esphome/components/remote_base/__init__.py
+++ b/esphome/components/remote_base/__init__.py
@@ -439,6 +439,49 @@ async def pioneer_action(var, config, args):
cg.add(var.set_rc_code_2(template_))
+# Pronto
+(
+ ProntoData,
+ ProntoBinarySensor,
+ ProntoTrigger,
+ ProntoAction,
+ ProntoDumper,
+) = declare_protocol("Pronto")
+PRONTO_SCHEMA = cv.Schema(
+ {
+ cv.Required(CONF_DATA): cv.string,
+ }
+)
+
+
+@register_binary_sensor("pronto", ProntoBinarySensor, PRONTO_SCHEMA)
+def pronto_binary_sensor(var, config):
+ cg.add(
+ var.set_data(
+ cg.StructInitializer(
+ ProntoData,
+ ("data", config[CONF_DATA]),
+ )
+ )
+ )
+
+
+@register_trigger("pronto", ProntoTrigger, ProntoData)
+def pronto_trigger(var, config):
+ pass
+
+
+@register_dumper("pronto", ProntoDumper)
+def pronto_dumper(var, config):
+ pass
+
+
+@register_action("pronto", ProntoAction, PRONTO_SCHEMA)
+async def pronto_action(var, config, args):
+ template_ = await cg.templatable(config[CONF_DATA], args, cg.std_string)
+ cg.add(var.set_data(template_))
+
+
# Sony
SonyData, SonyBinarySensor, SonyTrigger, SonyAction, SonyDumper = declare_protocol(
"Sony"
diff --git a/esphome/components/remote_base/pronto_protocol.cpp b/esphome/components/remote_base/pronto_protocol.cpp
new file mode 100644
index 0000000000..11aebb6c5d
--- /dev/null
+++ b/esphome/components/remote_base/pronto_protocol.cpp
@@ -0,0 +1,135 @@
+/*
+ * @file irPronto.cpp
+ * @brief In this file, the functions IRrecv::compensateAndPrintPronto and IRsend::sendPronto are defined.
+ *
+ * See http://www.harctoolbox.org/Glossary.html#ProntoSemantics
+ * Pronto database http://www.remotecentral.com/search.htm
+ *
+ * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
+ *
+ ************************************************************************************
+ * MIT License
+ *
+ * Copyright (c) 2020 Bengt Martensson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is furnished
+ * to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
+ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ ************************************************************************************
+ */
+
+#include "pronto_protocol.h"
+#include "esphome/core/log.h"
+
+namespace esphome {
+namespace remote_base {
+
+static const char *const TAG = "remote.pronto";
+
+// DO NOT EXPORT from this file
+static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU;
+static const uint16_t LEARNED_TOKEN = 0x0000U;
+static const uint16_t LEARNED_NON_MODULATED_TOKEN = 0x0100U;
+static const uint16_t BITS_IN_HEXADECIMAL = 4U;
+static const uint16_t DIGITS_IN_PRONTO_NUMBER = 4U;
+static const uint16_t NUMBERS_IN_PREAMBLE = 4U;
+static const uint16_t HEX_MASK = 0xFU;
+static const uint32_t REFERENCE_FREQUENCY = 4145146UL;
+static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0;
+static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL;
+static const uint16_t PRONTO_DEFAULT_GAP = 45000;
+
+static uint16_t to_frequency_k_hz(uint16_t code) {
+ if (code == 0)
+ return 0;
+
+ return ((REFERENCE_FREQUENCY / code) + 500) / 1000;
+}
+
+/*
+ * Parse the string given as Pronto Hex, and send it a number of times given as argument.
+ */
+void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector &data) {
+ if (data.size() < 4)
+ return;
+
+ uint16_t timebase = (MICROSECONDS_IN_SECONDS * data[1] + REFERENCE_FREQUENCY / 2) / REFERENCE_FREQUENCY;
+ uint16_t khz;
+ switch (data[0]) {
+ case LEARNED_TOKEN: // normal, "learned"
+ khz = to_frequency_k_hz(data[1]);
+ break;
+ case LEARNED_NON_MODULATED_TOKEN: // non-demodulated, "learned"
+ khz = 0U;
+ break;
+ default:
+ return; // There are other types, but they are not handled yet.
+ }
+ ESP_LOGD(TAG, "Send Pronto: frequency=%dkHz", khz);
+ dst->set_carrier_frequency(khz * 1000);
+
+ uint16_t intros = 2 * data[2];
+ uint16_t repeats = 2 * data[3];
+ ESP_LOGD(TAG, "Send Pronto: intros=%d", intros);
+ ESP_LOGD(TAG, "Send Pronto: repeats=%d", repeats);
+ if (NUMBERS_IN_PREAMBLE + intros + repeats != data.size()) { // inconsistent sizes
+ return;
+ }
+
+ /*
+ * Generate a new microseconds timing array for sendRaw.
+ * If recorded by IRremote, intro contains the whole IR data and repeat is empty
+ */
+ dst->reserve(intros + repeats);
+
+ for (uint16_t i = 0; i < intros + repeats; i += 2) {
+ uint32_t duration0 = ((uint32_t) data[i + 0 + NUMBERS_IN_PREAMBLE]) * timebase;
+ duration0 = duration0 < MICROSECONDS_T_MAX ? duration0 : MICROSECONDS_T_MAX;
+
+ uint32_t duration1 = ((uint32_t) data[i + 1 + NUMBERS_IN_PREAMBLE]) * timebase;
+ duration1 = duration1 < MICROSECONDS_T_MAX ? duration1 : MICROSECONDS_T_MAX;
+
+ dst->item(duration0, duration1);
+ }
+}
+
+void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) {
+ size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1;
+ std::vector data;
+ const char *p = str.c_str();
+ char *endptr[1];
+
+ for (uint16_t i = 0; i < len; i++) {
+ uint16_t x = strtol(p, endptr, 16);
+ if (x == 0 && i >= NUMBERS_IN_PREAMBLE) {
+ // Alignment error?, bail immediately (often right result).
+ break;
+ }
+ data.push_back(x); // If input is conforming, there can be no overflow!
+ p = *endptr;
+ }
+ send_pronto_(dst, data);
+}
+
+void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); }
+
+optional ProntoProtocol::decode(RemoteReceiveData src) { return {}; }
+
+void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); }
+
+} // namespace remote_base
+} // namespace esphome
diff --git a/esphome/components/remote_base/pronto_protocol.h b/esphome/components/remote_base/pronto_protocol.h
new file mode 100644
index 0000000000..e96511383f
--- /dev/null
+++ b/esphome/components/remote_base/pronto_protocol.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "esphome/core/component.h"
+#include "remote_base.h"
+
+namespace esphome {
+namespace remote_base {
+
+struct ProntoData {
+ std::string data;
+
+ bool operator==(const ProntoData &rhs) const { return data == rhs.data; }
+};
+
+class ProntoProtocol : public RemoteProtocol {
+ private:
+ void send_pronto_(RemoteTransmitData *dst, const std::vector &data);
+ void send_pronto_(RemoteTransmitData *dst, const std::string &str);
+
+ public:
+ void encode(RemoteTransmitData *dst, const ProntoData &data) override;
+ optional decode(RemoteReceiveData src) override;
+ void dump(const ProntoData &data) override;
+};
+
+DECLARE_REMOTE_PROTOCOL(Pronto)
+
+template class ProntoAction : public RemoteTransmitterActionBase {
+ public:
+ TEMPLATABLE_VALUE(std::string, data)
+
+ void encode(RemoteTransmitData *dst, Ts... x) override {
+ ProntoData data{};
+ data.data = this->data_.value(x...);
+ ProntoProtocol().encode(dst, data);
+ }
+};
+
+} // namespace remote_base
+} // namespace esphome
diff --git a/esphome/components/remote_base/rc_switch_protocol.cpp b/esphome/components/remote_base/rc_switch_protocol.cpp
index 6b7d1b725a..1dc094d552 100644
--- a/esphome/components/remote_base/rc_switch_protocol.cpp
+++ b/esphome/components/remote_base/rc_switch_protocol.cpp
@@ -101,10 +101,13 @@ bool RCSwitchBase::expect_sync(RemoteReceiveData &src) const {
if (!src.peek_space(this->sync_low_, 1))
return false;
} else {
- if (!src.peek_space(this->sync_high_))
- return false;
- if (!src.peek_mark(this->sync_low_, 1))
+ // We cant peek a space at the beginning because signals starts with a low to high transition.
+ // this long space at the beginning is the separation between the transmissions itself, so it is actually
+ // added at the end kind of artificially (by the value given to "idle:" option by the user in the yaml)
+ if (!src.peek_mark(this->sync_low_))
return false;
+ src.advance(1);
+ return true;
}
src.advance(2);
return true;
diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp
index dde9b843c9..5a7fb3c985 100644
--- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp
+++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp
@@ -78,6 +78,7 @@ void RemoteReceiverComponent::loop() {
if (this->temp_.empty())
return;
+ this->temp_.push_back(-this->idle_us_);
this->call_listeners_dumpers_();
}
}
@@ -86,9 +87,10 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
uint32_t prev_length = 0;
this->temp_.clear();
int32_t multiplier = this->pin_->is_inverted() ? -1 : 1;
+ size_t item_count = len / sizeof(rmt_item32_t);
ESP_LOGVV(TAG, "START:");
- for (size_t i = 0; i < len; i++) {
+ for (size_t i = 0; i < item_count; i++) {
if (item[i].level0) {
ESP_LOGVV(TAG, "%u A: ON %uus (%u ticks)", i, this->to_microseconds_(item[i].duration0), item[i].duration0);
} else {
@@ -102,8 +104,8 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
}
ESP_LOGVV(TAG, "\n");
- this->temp_.reserve(len / 4);
- for (size_t i = 0; i < len; i++) {
+ this->temp_.reserve(item_count * 2); // each RMT item has 2 pulses
+ for (size_t i = 0; i < item_count; i++) {
if (item[i].duration0 == 0u) {
// Do nothing
} else if (bool(item[i].level0) == prev_level) {
@@ -120,10 +122,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
prev_length = item[i].duration0;
}
- if (this->to_microseconds_(prev_length) > this->idle_us_) {
- break;
- }
-
if (item[i].duration1 == 0u) {
// Do nothing
} else if (bool(item[i].level1) == prev_level) {
@@ -139,10 +137,6 @@ void RemoteReceiverComponent::decode_rmt_(rmt_item32_t *item, size_t len) {
prev_level = bool(item[i].level1);
prev_length = item[i].duration1;
}
-
- if (this->to_microseconds_(prev_length) > this->idle_us_) {
- break;
- }
}
if (prev_length > 0) {
if (prev_level) {
diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp
index 500d7193f3..c3b61b72c2 100644
--- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp
+++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp
@@ -121,10 +121,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
} else {
this->status_clear_warning();
}
- if (i + 1 < send_times) {
- delay(send_wait / 1000UL);
- delayMicroseconds(send_wait % 1000UL);
- }
+ if (i + 1 < send_times)
+ delayMicroseconds(send_wait);
}
}
diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
index 33c01985d7..74e62d4e3b 100644
--- a/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
+++ b/esphome/components/remote_transmitter/remote_transmitter_esp8266.cpp
@@ -36,7 +36,7 @@ void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequen
void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
if (this->carrier_duty_percent_ == 100 || (on_time == 0 && off_time == 0)) {
this->pin_->digital_write(true);
- delay_microseconds_accurate(usec);
+ delayMicroseconds(usec);
this->pin_->digital_write(false);
return;
}
@@ -48,19 +48,19 @@ void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint
const uint32_t elapsed = current_time - start_time;
this->pin_->digital_write(true);
- delay_microseconds_accurate(std::min(on_time, usec - elapsed));
+ delayMicroseconds(std::min(on_time, usec - elapsed));
this->pin_->digital_write(false);
if (elapsed + on_time >= usec)
return;
- delay_microseconds_accurate(std::min(usec - elapsed - on_time, off_time));
+ delayMicroseconds(std::min(usec - elapsed - on_time, off_time));
current_time = micros();
}
}
void RemoteTransmitterComponent::space_(uint32_t usec) {
this->pin_->digital_write(false);
- delay_microseconds_accurate(usec);
+ delayMicroseconds(usec);
}
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
ESP_LOGD(TAG, "Sending remote code...");
@@ -81,9 +81,8 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
}
}
- if (i + 1 < send_times) {
- delay_microseconds_accurate(send_wait);
- }
+ if (i + 1 < send_times)
+ delayMicroseconds(send_wait);
}
}
diff --git a/esphome/components/restart/switch.py b/esphome/components/restart/switch.py
index 4f1904e273..de30392b45 100644
--- a/esphome/components/restart/switch.py
+++ b/esphome/components/restart/switch.py
@@ -1,7 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
-from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_RESTART
+from esphome.const import (
+ CONF_ENTITY_CATEGORY,
+ CONF_ID,
+ CONF_INVERTED,
+ CONF_ICON,
+ ENTITY_CATEGORY_CONFIG,
+ ICON_RESTART,
+)
restart_ns = cg.esphome_ns.namespace("restart")
RestartSwitch = restart_ns.class_("RestartSwitch", switch.Switch, cg.Component)
@@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
"Restart switches do not support inverted mode!"
),
cv.Optional(CONF_ICON, default=ICON_RESTART): switch.icon,
+ cv.Optional(
+ CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
+ ): cv.entity_category,
}
).extend(cv.COMPONENT_SCHEMA)
diff --git a/esphome/components/rotary_encoder/rotary_encoder.cpp b/esphome/components/rotary_encoder/rotary_encoder.cpp
index 7c95fac98e..aff8fc381c 100644
--- a/esphome/components/rotary_encoder/rotary_encoder.cpp
+++ b/esphome/components/rotary_encoder/rotary_encoder.cpp
@@ -125,6 +125,22 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore
void RotaryEncoderSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up Rotary Encoder '%s'...", this->name_.c_str());
+
+ int32_t initial_value = 0;
+ switch (this->restore_mode_) {
+ case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO:
+ this->rtc_ = global_preferences->make_preference(this->get_object_id_hash());
+ if (!this->rtc_.load(&initial_value)) {
+ initial_value = 0;
+ }
+ break;
+ case ROTARY_ENCODER_ALWAYS_ZERO:
+ initial_value = 0;
+ break;
+ }
+ this->store_.counter = initial_value;
+ this->store_.last_read = initial_value;
+
this->pin_a_->setup();
this->store_.pin_a = this->pin_a_->to_isr();
this->pin_b_->setup();
@@ -142,6 +158,18 @@ void RotaryEncoderSensor::dump_config() {
LOG_PIN(" Pin A: ", this->pin_a_);
LOG_PIN(" Pin B: ", this->pin_b_);
LOG_PIN(" Pin I: ", this->pin_i_);
+
+ const LogString *restore_mode = LOG_STR("");
+ switch (this->restore_mode_) {
+ case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO:
+ restore_mode = LOG_STR("Restore (Defaults to zero)");
+ break;
+ case ROTARY_ENCODER_ALWAYS_ZERO:
+ restore_mode = LOG_STR("Always zero");
+ break;
+ }
+ ESP_LOGCONFIG(TAG, " Restore Mode: %s", LOG_STR_ARG(restore_mode));
+
switch (this->store_.resolution) {
case ROTARY_ENCODER_1_PULSE_PER_CYCLE:
ESP_LOGCONFIG(TAG, " Resolution: 1 Pulse Per Cycle");
@@ -189,13 +217,20 @@ void RotaryEncoderSensor::loop() {
this->store_.counter = 0;
}
int counter = this->store_.counter;
- if (this->store_.last_read != counter) {
+ if (this->store_.last_read != counter || this->publish_initial_value_) {
+ if (this->restore_mode_ == ROTARY_ENCODER_RESTORE_DEFAULT_ZERO) {
+ this->rtc_.save(&counter);
+ }
this->store_.last_read = counter;
this->publish_state(counter);
+ this->publish_initial_value_ = false;
}
}
float RotaryEncoderSensor::get_setup_priority() const { return setup_priority::DATA; }
+void RotaryEncoderSensor::set_restore_mode(RotaryEncoderRestoreMode restore_mode) {
+ this->restore_mode_ = restore_mode;
+}
void RotaryEncoderSensor::set_resolution(RotaryEncoderResolution mode) { this->store_.resolution = mode; }
void RotaryEncoderSensor::set_min_value(int32_t min_value) { this->store_.min_value = min_value; }
void RotaryEncoderSensor::set_max_value(int32_t max_value) { this->store_.max_value = max_value; }
diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h
index 4825e472a1..a69d738fa8 100644
--- a/esphome/components/rotary_encoder/rotary_encoder.h
+++ b/esphome/components/rotary_encoder/rotary_encoder.h
@@ -10,6 +10,12 @@
namespace esphome {
namespace rotary_encoder {
+/// All possible restore modes for the rotary encoder
+enum RotaryEncoderRestoreMode {
+ ROTARY_ENCODER_RESTORE_DEFAULT_ZERO, /// try to restore counter, otherwise set to zero
+ ROTARY_ENCODER_ALWAYS_ZERO, /// do not restore counter, always set to zero
+};
+
/// All possible resolutions for the rotary encoder
enum RotaryEncoderResolution {
ROTARY_ENCODER_1_PULSE_PER_CYCLE =
@@ -40,6 +46,15 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component {
void set_pin_a(InternalGPIOPin *pin_a) { pin_a_ = pin_a; }
void set_pin_b(InternalGPIOPin *pin_b) { pin_b_ = pin_b; }
+ /** Set the restore mode of the rotary encoder.
+ *
+ * By default (if possible) the last known counter state is restored. Otherwise the value 0 is used.
+ * Restoring the state can also be turned off.
+ *
+ * @param restore_mode The restore mode to use.
+ */
+ void set_restore_mode(RotaryEncoderRestoreMode restore_mode);
+
/** Set the resolution of the rotary encoder.
*
* By default, this component will increment the counter by 1 with every A-B input cycle.
@@ -58,6 +73,7 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component {
void set_reset_pin(GPIOPin *pin_i) { this->pin_i_ = pin_i; }
void set_min_value(int32_t min_value);
void set_max_value(int32_t max_value);
+ void set_publish_initial_value(bool publish_initial_value) { publish_initial_value_ = publish_initial_value; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
@@ -79,6 +95,9 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component {
InternalGPIOPin *pin_a_;
InternalGPIOPin *pin_b_;
GPIOPin *pin_i_{nullptr}; /// Index pin, if this is not nullptr, the counter will reset to 0 once this pin is HIGH.
+ bool publish_initial_value_;
+ ESPPreferenceObject rtc_;
+ RotaryEncoderRestoreMode restore_mode_{ROTARY_ENCODER_RESTORE_DEFAULT_ZERO};
RotaryEncoderSensorStore store_{};
diff --git a/esphome/components/rotary_encoder/sensor.py b/esphome/components/rotary_encoder/sensor.py
index ef1110c6d8..cd747264b3 100644
--- a/esphome/components/rotary_encoder/sensor.py
+++ b/esphome/components/rotary_encoder/sensor.py
@@ -14,9 +14,17 @@ from esphome.const import (
CONF_PIN_A,
CONF_PIN_B,
CONF_TRIGGER_ID,
+ CONF_RESTORE_MODE,
)
rotary_encoder_ns = cg.esphome_ns.namespace("rotary_encoder")
+
+RotaryEncoderRestoreMode = rotary_encoder_ns.enum("RotaryEncoderRestoreMode")
+RESTORE_MODES = {
+ "RESTORE_DEFAULT_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_RESTORE_DEFAULT_ZERO,
+ "ALWAYS_ZERO": RotaryEncoderRestoreMode.ROTARY_ENCODER_ALWAYS_ZERO,
+}
+
RotaryEncoderResolution = rotary_encoder_ns.enum("RotaryEncoderResolution")
RESOLUTIONS = {
1: RotaryEncoderResolution.ROTARY_ENCODER_1_PULSE_PER_CYCLE,
@@ -27,6 +35,7 @@ RESOLUTIONS = {
CONF_PIN_RESET = "pin_reset"
CONF_ON_CLOCKWISE = "on_clockwise"
CONF_ON_ANTICLOCKWISE = "on_anticlockwise"
+CONF_PUBLISH_INITIAL_VALUE = "publish_initial_value"
RotaryEncoderSensor = rotary_encoder_ns.class_(
"RotaryEncoderSensor", sensor.Sensor, cg.Component
@@ -70,6 +79,10 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_RESOLUTION, default=1): cv.enum(RESOLUTIONS, int=True),
cv.Optional(CONF_MIN_VALUE): cv.int_,
cv.Optional(CONF_MAX_VALUE): cv.int_,
+ cv.Optional(CONF_PUBLISH_INITIAL_VALUE, default=False): cv.boolean,
+ cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_ZERO"): cv.enum(
+ RESTORE_MODES, upper=True, space="_"
+ ),
cv.Optional(CONF_ON_CLOCKWISE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
@@ -99,6 +112,8 @@ async def to_code(config):
cg.add(var.set_pin_a(pin_a))
pin_b = await cg.gpio_pin_expression(config[CONF_PIN_B])
cg.add(var.set_pin_b(pin_b))
+ cg.add(var.set_publish_initial_value(config[CONF_PUBLISH_INITIAL_VALUE]))
+ cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
if CONF_PIN_RESET in config:
pin_i = await cg.gpio_pin_expression(config[CONF_PIN_RESET])
diff --git a/esphome/components/ruuvitag/sensor.py b/esphome/components/ruuvitag/sensor.py
index 342a5eff24..2bb9549195 100644
--- a/esphome/components/ruuvitag/sensor.py
+++ b/esphome/components/ruuvitag/sensor.py
@@ -19,6 +19,7 @@ from esphome.const import (
DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_NONE,
UNIT_CELSIUS,
@@ -95,22 +96,26 @@ CONFIG_SCHEMA = (
accuracy_decimals=3,
device_class=DEVICE_CLASS_VOLTAGE,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_TX_POWER): sensor.sensor_schema(
unit_of_measurement=UNIT_DECIBEL_MILLIWATT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_MOVEMENT_COUNTER): sensor.sensor_schema(
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_MEASUREMENT_SEQUENCE_NUMBER): sensor.sensor_schema(
icon=ICON_GAUGE,
accuracy_decimals=0,
state_class=STATE_CLASS_NONE,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/safe_mode/switch/__init__.py b/esphome/components/safe_mode/switch/__init__.py
index 0ad814ff4f..b6c3e852f6 100644
--- a/esphome/components/safe_mode/switch/__init__.py
+++ b/esphome/components/safe_mode/switch/__init__.py
@@ -3,10 +3,12 @@ import esphome.config_validation as cv
from esphome.components import switch
from esphome.components.ota import OTAComponent
from esphome.const import (
+ CONF_ENTITY_CATEGORY,
CONF_ID,
CONF_INVERTED,
CONF_ICON,
CONF_OTA,
+ ENTITY_CATEGORY_CONFIG,
ICON_RESTART_ALERT,
)
from .. import safe_mode_ns
@@ -23,6 +25,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
"Safe Mode Restart switches do not support inverted mode!"
),
cv.Optional(CONF_ICON, default=ICON_RESTART_ALERT): switch.icon,
+ cv.Optional(
+ CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
+ ): cv.entity_category,
}
).extend(cv.COMPONENT_SCHEMA)
diff --git a/esphome/components/scd30/scd30.cpp b/esphome/components/scd30/scd30.cpp
index d1246d9766..e6d6ec1c1a 100644
--- a/esphome/components/scd30/scd30.cpp
+++ b/esphome/components/scd30/scd30.cpp
@@ -60,7 +60,7 @@ void SCD30Component::setup() {
// According ESP32 clock stretching is typically 30ms and up to 150ms "due to
// internal calibration processes". The I2C peripheral only supports 13ms (at
// least when running at 80MHz).
- // In practise it seems that clock stretching occurs during this calibration
+ // In practice it seems that clock stretching occurs during this calibration
// calls. It also seems that delays in between calls makes them
// disappear/shorter. Hence work around with delays for ESP32.
//
@@ -69,6 +69,16 @@ void SCD30Component::setup() {
delay(30);
#endif
+ if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
+ ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
+ this->error_code_ = MEASUREMENT_INIT_FAILED;
+ this->mark_failed();
+ return;
+ }
+#ifdef USE_ESP32
+ delay(30);
+#endif
+
// The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
if (this->altitude_compensation_ != 0xFFFF) {
if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
@@ -99,6 +109,12 @@ void SCD30Component::setup() {
this->mark_failed();
return;
}
+
+ // check each 500ms if data is ready, and read it in that case
+ this->set_interval("status-check", 500, [this]() {
+ if (this->is_data_ready_())
+ this->update();
+ });
}
void SCD30Component::dump_config() {
@@ -128,19 +144,13 @@ void SCD30Component::dump_config() {
ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_));
ESP_LOGCONFIG(TAG, " Ambient pressure compensation: %dmBar", this->ambient_pressure_compensation_);
ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_);
- LOG_UPDATE_INTERVAL(this);
+ ESP_LOGCONFIG(TAG, " Update interval: %ds", this->update_interval_);
LOG_SENSOR(" ", "CO2", this->co2_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
void SCD30Component::update() {
- /// Check if measurement is ready before reading the value
- if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) {
- this->status_set_warning();
- return;
- }
-
uint16_t raw_read_status[1];
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
this->status_set_warning();
@@ -186,6 +196,17 @@ void SCD30Component::update() {
});
}
+bool SCD30Component::is_data_ready_() {
+ if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) {
+ return false;
+ }
+ uint16_t is_data_ready;
+ if (!this->read_data_(&is_data_ready, 1)) {
+ return false;
+ }
+ return is_data_ready == 1;
+}
+
bool SCD30Component::write_command_(uint16_t command) {
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
return this->write_byte(command >> 8, command & 0xFF);
diff --git a/esphome/components/scd30/scd30.h b/esphome/components/scd30/scd30.h
index f11b7cc1f4..64193d0cb6 100644
--- a/esphome/components/scd30/scd30.h
+++ b/esphome/components/scd30/scd30.h
@@ -8,7 +8,7 @@ namespace esphome {
namespace scd30 {
/// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors.
-class SCD30Component : public PollingComponent, public i2c::I2CDevice {
+class SCD30Component : public Component, public i2c::I2CDevice {
public:
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
@@ -19,9 +19,10 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice {
ambient_pressure_compensation_ = (uint16_t)(pressure * 1000);
}
void set_temperature_offset(float offset) { temperature_offset_ = offset; }
+ void set_update_interval(uint16_t interval) { update_interval_ = interval; }
void setup() override;
- void update() override;
+ void update();
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
@@ -30,6 +31,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice {
bool write_command_(uint16_t command, uint16_t data);
bool read_data_(uint16_t *data, uint8_t len);
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
+ bool is_data_ready_();
enum ErrorCode {
COMMUNICATION_FAILED,
@@ -41,6 +43,7 @@ class SCD30Component : public PollingComponent, public i2c::I2CDevice {
uint16_t altitude_compensation_{0xFFFF};
uint16_t ambient_pressure_compensation_{0x0000};
float temperature_offset_{0.0};
+ uint16_t update_interval_{0xFFFF};
sensor::Sensor *co2_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
diff --git a/esphome/components/scd30/sensor.py b/esphome/components/scd30/sensor.py
index c0317c96e0..cd25649f2a 100644
--- a/esphome/components/scd30/sensor.py
+++ b/esphome/components/scd30/sensor.py
@@ -1,3 +1,4 @@
+from esphome import core
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
@@ -6,6 +7,7 @@ from esphome.const import (
CONF_HUMIDITY,
CONF_TEMPERATURE,
CONF_CO2,
+ CONF_UPDATE_INTERVAL,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
@@ -18,7 +20,7 @@ from esphome.const import (
DEPENDENCIES = ["i2c"]
scd30_ns = cg.esphome_ns.namespace("scd30")
-SCD30Component = scd30_ns.class_("SCD30Component", cg.PollingComponent, i2c.I2CDevice)
+SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice)
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
@@ -55,9 +57,15 @@ CONFIG_SCHEMA = (
),
cv.Optional(CONF_AMBIENT_PRESSURE_COMPENSATION, default=0): cv.pressure,
cv.Optional(CONF_TEMPERATURE_OFFSET): cv.temperature,
+ cv.Optional(CONF_UPDATE_INTERVAL, default="60s"): cv.All(
+ cv.positive_time_period_seconds,
+ cv.Range(
+ min=core.TimePeriod(seconds=1), max=core.TimePeriod(seconds=1800)
+ ),
+ ),
}
)
- .extend(cv.polling_component_schema("60s"))
+ .extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x61))
)
@@ -81,6 +89,8 @@ async def to_code(config):
if CONF_TEMPERATURE_OFFSET in config:
cg.add(var.set_temperature_offset(config[CONF_TEMPERATURE_OFFSET]))
+ cg.add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
+
if CONF_CO2 in config:
sens = await sensor.new_sensor(config[CONF_CO2])
cg.add(var.set_co2_sensor(sens))
diff --git a/esphome/components/sdp3x/sdp3x.cpp b/esphome/components/sdp3x/sdp3x.cpp
index ba7a028f8e..b0d8bcc6c4 100644
--- a/esphome/components/sdp3x/sdp3x.cpp
+++ b/esphome/components/sdp3x/sdp3x.cpp
@@ -1,5 +1,6 @@
#include "sdp3x.h"
#include "esphome/core/log.h"
+#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
namespace esphome {
@@ -25,7 +26,7 @@ void SDP3XComponent::setup() {
ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason
}
- delay_microseconds_accurate(20000);
+ delayMicroseconds(20000);
if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
diff --git a/esphome/components/select/__init__.py b/esphome/components/select/__init__.py
index 7e4047d3c8..8ea159d657 100644
--- a/esphome/components/select/__init__.py
+++ b/esphome/components/select/__init__.py
@@ -30,7 +30,6 @@ SelectSetAction = select_ns.class_("SelectSetAction", automation.Action)
icon = cv.icon
-
SELECT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSelectComponent),
diff --git a/esphome/components/sensor/__init__.py b/esphome/components/sensor/__init__.py
index 4b2e9dc019..d9d226aab6 100644
--- a/esphome/components/sensor/__init__.py
+++ b/esphome/components/sensor/__init__.py
@@ -10,6 +10,7 @@ from esphome.const import (
CONF_ACCURACY_DECIMALS,
CONF_ALPHA,
CONF_BELOW,
+ CONF_ENTITY_CATEGORY,
CONF_EXPIRE_AFTER,
CONF_FILTERS,
CONF_FROM,
@@ -133,7 +134,6 @@ def validate_datapoint(value):
# Base
-sensor_ns = cg.esphome_ns.namespace("sensor")
Sensor = sensor_ns.class_("Sensor", cg.EntityBase)
SensorPtr = Sensor.operator("ptr")
@@ -226,6 +226,7 @@ def sensor_schema(
accuracy_decimals: int = _UNDEF,
device_class: str = _UNDEF,
state_class: str = _UNDEF,
+ entity_category: str = _UNDEF,
) -> cv.Schema:
schema = SENSOR_SCHEMA
if unit_of_measurement is not _UNDEF:
@@ -258,6 +259,14 @@ def sensor_schema(
schema = schema.extend(
{cv.Optional(CONF_STATE_CLASS, default=state_class): validate_state_class}
)
+ if entity_category is not _UNDEF:
+ schema = schema.extend(
+ {
+ cv.Optional(
+ CONF_ENTITY_CATEGORY, default=entity_category
+ ): cv.entity_category
+ }
+ )
return schema
diff --git a/esphome/components/shutdown/switch.py b/esphome/components/shutdown/switch.py
index 30c2bc2b74..49970b4c2f 100644
--- a/esphome/components/shutdown/switch.py
+++ b/esphome/components/shutdown/switch.py
@@ -1,7 +1,14 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
-from esphome.const import CONF_ID, CONF_INVERTED, CONF_ICON, ICON_POWER
+from esphome.const import (
+ CONF_ENTITY_CATEGORY,
+ CONF_ID,
+ CONF_INVERTED,
+ CONF_ICON,
+ ENTITY_CATEGORY_CONFIG,
+ ICON_POWER,
+)
shutdown_ns = cg.esphome_ns.namespace("shutdown")
ShutdownSwitch = shutdown_ns.class_("ShutdownSwitch", switch.Switch, cg.Component)
@@ -13,6 +20,9 @@ CONFIG_SCHEMA = switch.SWITCH_SCHEMA.extend(
"Shutdown switches do not support inverted mode!"
),
cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon,
+ cv.Optional(
+ CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG
+ ): cv.entity_category,
}
).extend(cv.COMPONENT_SCHEMA)
diff --git a/esphome/components/sim800l/sim800l.cpp b/esphome/components/sim800l/sim800l.cpp
index e48b1ac9bd..eb6d62ca33 100644
--- a/esphome/components/sim800l/sim800l.cpp
+++ b/esphome/components/sim800l/sim800l.cpp
@@ -128,7 +128,7 @@ void Sim800LComponent::parse_cmd_(std::string message) {
if (message.compare(0, 5, "+CSQ:") == 0) {
size_t comma = message.find(',', 6);
if (comma != 6) {
- this->rssi_ = strtol(message.substr(6, comma - 6).c_str(), nullptr, 10);
+ this->rssi_ = parse_number(message.substr(6, comma - 6)).value_or(0);
ESP_LOGD(TAG, "RSSI: %d", this->rssi_);
}
}
@@ -146,7 +146,7 @@ void Sim800LComponent::parse_cmd_(std::string message) {
while (end != start) {
item++;
if (item == 1) { // Slot Index
- this->parse_index_ = strtol(message.substr(start, end - start).c_str(), nullptr, 10);
+ this->parse_index_ = parse_number(message.substr(start, end - start)).value_or(0);
}
// item 2 = STATUS, usually "REC UNERAD"
if (item == 3) { // recipient
diff --git a/esphome/components/sntp/sntp_component.cpp b/esphome/components/sntp/sntp_component.cpp
index 2b6cd10e80..96be0e9709 100644
--- a/esphome/components/sntp/sntp_component.cpp
+++ b/esphome/components/sntp/sntp_component.cpp
@@ -3,6 +3,9 @@
#ifdef USE_ESP32
#include "lwip/apps/sntp.h"
+#ifdef USE_ESP_IDF
+#include "esp_sntp.h"
+#endif
#endif
#ifdef USE_ESP8266
#include "sntp.h"
@@ -37,6 +40,9 @@ void SNTPComponent::setup() {
if (!this->server_3_.empty()) {
sntp_setservername(2, strdup(this->server_3_.c_str()));
}
+#ifdef USE_ESP_IDF
+ sntp_set_sync_interval(this->get_update_interval());
+#endif
sntp_init();
}
@@ -47,7 +53,16 @@ void SNTPComponent::dump_config() {
ESP_LOGCONFIG(TAG, " Server 3: '%s'", this->server_3_.c_str());
ESP_LOGCONFIG(TAG, " Timezone: '%s'", this->timezone_.c_str());
}
-void SNTPComponent::update() {}
+void SNTPComponent::update() {
+#ifndef USE_ESP_IDF
+ // force resync
+ if (sntp_enabled()) {
+ sntp_stop();
+ this->has_time_ = false;
+ sntp_init();
+ }
+#endif
+}
void SNTPComponent::loop() {
if (this->has_time_)
return;
@@ -56,7 +71,7 @@ void SNTPComponent::loop() {
if (!time.is_valid())
return;
- ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%d:%d", time.year, time.month, time.day_of_month, time.hour,
+ ESP_LOGD(TAG, "Synchronized time: %d-%d-%d %d:%02d:%02d", time.year, time.month, time.day_of_month, time.hour,
time.minute, time.second);
this->time_sync_callback_.call();
this->has_time_ = true;
diff --git a/esphome/components/spi/__init__.py b/esphome/components/spi/__init__.py
index 3a96cce99b..c917fe1ad8 100644
--- a/esphome/components/spi/__init__.py
+++ b/esphome/components/spi/__init__.py
@@ -46,7 +46,9 @@ async def to_code(config):
mosi = await cg.gpio_pin_expression(config[CONF_MOSI_PIN])
cg.add(var.set_mosi(mosi))
- if CORE.is_esp32:
+ if CORE.is_esp32 and CORE.using_arduino:
+ cg.add_library("SPI", None)
+ if CORE.is_esp8266:
cg.add_library("SPI", None)
diff --git a/esphome/components/ssd1306_base/__init__.py b/esphome/components/ssd1306_base/__init__.py
index bc2e558f1b..e4f62e5ff9 100644
--- a/esphome/components/ssd1306_base/__init__.py
+++ b/esphome/components/ssd1306_base/__init__.py
@@ -26,6 +26,7 @@ MODELS = {
"SSD1306_128X64": SSD1306Model.SSD1306_MODEL_128_64,
"SSD1306_96X16": SSD1306Model.SSD1306_MODEL_96_16,
"SSD1306_64X48": SSD1306Model.SSD1306_MODEL_64_48,
+ "SSD1306_64X32": SSD1306Model.SSD1306_MODEL_64_32,
"SH1106_128X32": SSD1306Model.SH1106_MODEL_128_32,
"SH1106_128X64": SSD1306Model.SH1106_MODEL_128_64,
"SH1106_96X16": SSD1306Model.SH1106_MODEL_96_16,
@@ -84,7 +85,7 @@ async def setup_ssd1306(var, config):
if CONF_FLIP_X in config:
cg.add(var.init_flip_x(config[CONF_FLIP_X]))
if CONF_FLIP_Y in config:
- cg.add(var.init_flip_y(config[CONF_FLIP_X]))
+ cg.add(var.init_flip_y(config[CONF_FLIP_Y]))
if CONF_OFFSET_X in config:
cg.add(var.init_offset_x(config[CONF_OFFSET_X]))
if CONF_OFFSET_Y in config:
diff --git a/esphome/components/ssd1306_base/ssd1306_base.cpp b/esphome/components/ssd1306_base/ssd1306_base.cpp
index b1a2538ebd..2537133605 100644
--- a/esphome/components/ssd1306_base/ssd1306_base.cpp
+++ b/esphome/components/ssd1306_base/ssd1306_base.cpp
@@ -94,6 +94,7 @@ void SSD1306::setup() {
case SSD1306_MODEL_128_64:
case SH1106_MODEL_128_64:
case SSD1306_MODEL_64_48:
+ case SSD1306_MODEL_64_32:
case SH1106_MODEL_64_48:
case SSD1305_MODEL_128_32:
case SSD1305_MODEL_128_64:
@@ -141,6 +142,7 @@ void SSD1306::display() {
this->command(SSD1306_COMMAND_COLUMN_ADDRESS);
switch (this->model_) {
case SSD1306_MODEL_64_48:
+ case SSD1306_MODEL_64_32:
this->command(0x20 + this->offset_x_);
this->command(0x20 + this->offset_x_ + this->get_width_internal() - 1);
break;
@@ -197,6 +199,7 @@ void SSD1306::turn_off() {
int SSD1306::get_height_internal() {
switch (this->model_) {
case SSD1306_MODEL_128_32:
+ case SSD1306_MODEL_64_32:
case SH1106_MODEL_128_32:
case SSD1305_MODEL_128_32:
return 32;
@@ -227,6 +230,7 @@ int SSD1306::get_width_internal() {
case SH1106_MODEL_96_16:
return 96;
case SSD1306_MODEL_64_48:
+ case SSD1306_MODEL_64_32:
case SH1106_MODEL_64_48:
return 64;
default:
@@ -271,6 +275,8 @@ const char *SSD1306::model_str_() {
return "SSD1306 128x32";
case SSD1306_MODEL_128_64:
return "SSD1306 128x64";
+ case SSD1306_MODEL_64_32:
+ return "SSD1306 64x32";
case SSD1306_MODEL_96_16:
return "SSD1306 96x16";
case SSD1306_MODEL_64_48:
diff --git a/esphome/components/ssd1306_base/ssd1306_base.h b/esphome/components/ssd1306_base/ssd1306_base.h
index 09417a2c10..c77b1985e4 100644
--- a/esphome/components/ssd1306_base/ssd1306_base.h
+++ b/esphome/components/ssd1306_base/ssd1306_base.h
@@ -12,6 +12,7 @@ enum SSD1306Model {
SSD1306_MODEL_128_64,
SSD1306_MODEL_96_16,
SSD1306_MODEL_64_48,
+ SSD1306_MODEL_64_32,
SH1106_MODEL_128_32,
SH1106_MODEL_128_64,
SH1106_MODEL_96_16,
diff --git a/esphome/components/status/binary_sensor.py b/esphome/components/status/binary_sensor.py
index e462bc5385..9367706388 100644
--- a/esphome/components/status/binary_sensor.py
+++ b/esphome/components/status/binary_sensor.py
@@ -1,7 +1,13 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
-from esphome.const import CONF_ID, CONF_DEVICE_CLASS, DEVICE_CLASS_CONNECTIVITY
+from esphome.const import (
+ CONF_ENTITY_CATEGORY,
+ CONF_ID,
+ CONF_DEVICE_CLASS,
+ DEVICE_CLASS_CONNECTIVITY,
+ ENTITY_CATEGORY_DIAGNOSTIC,
+)
status_ns = cg.esphome_ns.namespace("status")
StatusBinarySensor = status_ns.class_(
@@ -14,6 +20,9 @@ CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
cv.Optional(
CONF_DEVICE_CLASS, default=DEVICE_CLASS_CONNECTIVITY
): binary_sensor.device_class,
+ cv.Optional(
+ CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
+ ): cv.entity_category,
}
).extend(cv.COMPONENT_SCHEMA)
diff --git a/esphome/components/switch/__init__.py b/esphome/components/switch/__init__.py
index 88341e0add..08cbccbe35 100644
--- a/esphome/components/switch/__init__.py
+++ b/esphome/components/switch/__init__.py
@@ -36,6 +36,7 @@ SwitchTurnOffTrigger = switch_ns.class_(
icon = cv.icon
+
SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTSwitchComponent),
diff --git a/esphome/components/tcs34725/sensor.py b/esphome/components/tcs34725/sensor.py
index 6c74c86faf..fcc56e395f 100644
--- a/esphome/components/tcs34725/sensor.py
+++ b/esphome/components/tcs34725/sensor.py
@@ -6,6 +6,7 @@ from esphome.const import (
CONF_GAIN,
CONF_ID,
CONF_ILLUMINANCE,
+ CONF_GLASS_ATTENUATION_FACTOR,
CONF_INTEGRATION_TIME,
DEVICE_CLASS_ILLUMINANCE,
ICON_LIGHTBULB,
@@ -34,8 +35,20 @@ TCS34725_INTEGRATION_TIMES = {
"24ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_24MS,
"50ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_50MS,
"101ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_101MS,
+ "120ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_120MS,
"154ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_154MS,
- "700ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_700MS,
+ "180ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_180MS,
+ "199ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_199MS,
+ "240ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_240MS,
+ "300ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_300MS,
+ "360ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_360MS,
+ "401ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_401MS,
+ "420ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_420MS,
+ "480ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_480MS,
+ "499ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_499MS,
+ "540ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_540MS,
+ "600ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_600MS,
+ "614ms": TCS34725IntegrationTime.TCS34725_INTEGRATION_TIME_614MS,
}
TCS34725Gain = tcs34725_ns.enum("TCS34725Gain")
@@ -79,6 +92,9 @@ CONFIG_SCHEMA = (
TCS34725_INTEGRATION_TIMES, lower=True
),
cv.Optional(CONF_GAIN, default="1X"): cv.enum(TCS34725_GAINS, upper=True),
+ cv.Optional(CONF_GLASS_ATTENUATION_FACTOR, default=1.0): cv.float_range(
+ min=1.0
+ ),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -93,6 +109,7 @@ async def to_code(config):
cg.add(var.set_integration_time(config[CONF_INTEGRATION_TIME]))
cg.add(var.set_gain(config[CONF_GAIN]))
+ cg.add(var.set_glass_attenuation_factor(config[CONF_GLASS_ATTENUATION_FACTOR]))
if CONF_RED_CHANNEL in config:
sens = await sensor.new_sensor(config[CONF_RED_CHANNEL])
diff --git a/esphome/components/tcs34725/tcs34725.cpp b/esphome/components/tcs34725/tcs34725.cpp
index 564d3dcda7..f7ffe2a97d 100644
--- a/esphome/components/tcs34725/tcs34725.cpp
+++ b/esphome/components/tcs34725/tcs34725.cpp
@@ -26,10 +26,8 @@ void TCS34725Component::setup() {
return;
}
- uint8_t integration_reg = this->integration_time_;
- uint8_t gain_reg = this->gain_;
- if (!this->write_byte(TCS34725_REGISTER_ATIME, integration_reg) ||
- !this->write_byte(TCS34725_REGISTER_CONTROL, gain_reg)) {
+ if (!this->write_byte(TCS34725_REGISTER_ATIME, this->integration_reg_) ||
+ !this->write_byte(TCS34725_REGISTER_CONTROL, this->gain_reg_)) {
this->mark_failed();
return;
}
@@ -61,6 +59,114 @@ void TCS34725Component::dump_config() {
LOG_SENSOR(" ", "Color Temperature", this->color_temperature_sensor_);
}
float TCS34725Component::get_setup_priority() const { return setup_priority::DATA; }
+
+/*!
+ * @brief Converts the raw R/G/B values to color temperature in degrees
+ * Kelvin using the algorithm described in DN40 from Taos (now AMS).
+ * @param r
+ * Red value
+ * @param g
+ * Green value
+ * @param b
+ * Blue value
+ * @param c
+ * Clear channel value
+ * @return Color temperature in degrees Kelvin
+ */
+void TCS34725Component::calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c) {
+ float r2, g2, b2; /* RGB values minus IR component */
+ float sat; /* Digital saturation level */
+ float ir; /* Inferred IR content */
+
+ this->illuminance_ = 0; // Assign 0 value before calculation
+ this->color_temperature_ = 0;
+
+ const float ga = this->glass_attenuation_; // Glass Attenuation Factor
+ static const float DF = 310.f; // Device Factor
+ static const float R_COEF = 0.136f; //
+ static const float G_COEF = 1.f; // used in lux computation
+ static const float B_COEF = -0.444f; //
+ static const float CT_COEF = 3810.f; // Color Temperature Coefficient
+ static const float CT_OFFSET = 1391.f; // Color Temperatuer Offset
+
+ if (c == 0) {
+ return;
+ }
+
+ /* Analog/Digital saturation:
+ *
+ * (a) As light becomes brighter, the clear channel will tend to
+ * saturate first since R+G+B is approximately equal to C.
+ * (b) The TCS34725 accumulates 1024 counts per 2.4ms of integration
+ * time, up to a maximum values of 65535. This means analog
+ * saturation can occur up to an integration time of 153.6ms
+ * (64*2.4ms=153.6ms).
+ * (c) If the integration time is > 153.6ms, digital saturation will
+ * occur before analog saturation. Digital saturation occurs when
+ * the count reaches 65535.
+ */
+ if ((256 - this->integration_reg_) > 63) {
+ /* Track digital saturation */
+ sat = 65535.f;
+ } else {
+ /* Track analog saturation */
+ sat = 1024.f * (256.f - this->integration_reg_);
+ }
+
+ /* Ripple rejection:
+ *
+ * (a) An integration time of 50ms or multiples of 50ms are required to
+ * reject both 50Hz and 60Hz ripple.
+ * (b) If an integration time faster than 50ms is required, you may need
+ * to average a number of samples over a 50ms period to reject ripple
+ * from fluorescent and incandescent light sources.
+ *
+ * Ripple saturation notes:
+ *
+ * (a) If there is ripple in the received signal, the value read from C
+ * will be less than the max, but still have some effects of being
+ * saturated. This means that you can be below the 'sat' value, but
+ * still be saturating. At integration times >150ms this can be
+ * ignored, but <= 150ms you should calculate the 75% saturation
+ * level to avoid this problem.
+ */
+ if (this->integration_time_ < 150) {
+ /* 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) {
+ return;
+ }
+
+ /* AMS RGB sensors have no IR channel, so the IR content must be */
+ /* calculated indirectly. */
+ ir = ((r + g + b) > c) ? (r + g + b - c) / 2 : 0;
+
+ /* Remove the IR component from the raw RGB values */
+ r2 = r - ir;
+ g2 = g - ir;
+ b2 = b - ir;
+
+ if (r2 == 0) {
+ return;
+ }
+
+ // Lux Calculation (DN40 3.2)
+
+ float g1 = R_COEF * r2 + G_COEF * g2 + B_COEF * b2;
+ float cpl = (this->integration_time_ * this->gain_) / (ga * DF);
+ this->illuminance_ = g1 / cpl;
+
+ // Color Temperature Calculation (DN40)
+ /* A simple method of measuring color temp is to use the ratio of blue */
+ /* to red light, taking IR cancellation into account. */
+ this->color_temperature_ = (CT_COEF * b2) / /** Color temp coefficient. */
+ r2 +
+ CT_OFFSET; /** Color temp offset. */
+}
+
void TCS34725Component::update() {
uint16_t raw_c;
uint16_t raw_r;
@@ -74,6 +180,12 @@ void TCS34725Component::update() {
return;
}
+ // 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;
@@ -87,38 +199,54 @@ void TCS34725Component::update() {
if (this->blue_sensor_ != nullptr)
this->blue_sensor_->publish_state(channel_b);
- // Formulae taken from Adafruit TCS35725 library
- float illuminance = (-0.32466f * channel_r) + (1.57837f * channel_g) + (-0.73191f * channel_b);
+ if (this->illuminance_sensor_ || this->color_temperature_sensor_) {
+ calculate_temperature_and_lux_(raw_r, raw_g, raw_b, raw_c);
+ }
+
if (this->illuminance_sensor_ != nullptr)
- this->illuminance_sensor_->publish_state(illuminance);
+ this->illuminance_sensor_->publish_state(this->illuminance_);
- // Color temperature
- // 1. Convert RGB to XYZ color space
- const float x = (-0.14282f * raw_r) + (1.54924f * raw_g) + (-0.95641f * raw_b);
- const float y = (-0.32466f * raw_r) + (1.57837f * raw_g) + (-0.73191f * raw_b);
- const float z = (-0.68202f * raw_r) + (0.77073f * raw_g) + (0.56332f * raw_b);
-
- // 2. Calculate chromacity coordinates
- const float xc = (x) / (x + y + z);
- const float yc = (y) / (x + y + z);
-
- // 3. Use McCamy's formula to determine the color temperature
- const float n = (xc - 0.3320f) / (0.1858f - yc);
-
- // 4. final color temperature in Kelvin.
- const float color_temperature = (449.0f * powf(n, 3.0f)) + (3525.0f * powf(n, 2.0f)) + (6823.3f * n) + 5520.33f;
if (this->color_temperature_sensor_ != nullptr)
- this->color_temperature_sensor_->publish_state(color_temperature);
+ 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, illuminance, color_temperature);
+ channel_g, channel_b, channel_c, this->illuminance_, this->color_temperature_);
this->status_clear_warning();
}
void TCS34725Component::set_integration_time(TCS34725IntegrationTime integration_time) {
- this->integration_time_ = integration_time;
+ this->integration_reg_ = integration_time;
+ this->integration_time_ = (256.f - integration_time) * 2.4f;
+}
+void TCS34725Component::set_gain(TCS34725Gain gain) {
+ this->gain_reg_ = gain;
+ switch (gain) {
+ case TCS34725Gain::TCS34725_GAIN_1X:
+ this->gain_ = 1.f;
+ break;
+ case TCS34725Gain::TCS34725_GAIN_4X:
+ this->gain_ = 4.f;
+ break;
+ case TCS34725Gain::TCS34725_GAIN_16X:
+ this->gain_ = 16.f;
+ break;
+ case TCS34725Gain::TCS34725_GAIN_60X:
+ this->gain_ = 60.f;
+ break;
+ default:
+ this->gain_ = 1.f;
+ break;
+ }
+}
+
+void TCS34725Component::set_glass_attenuation_factor(float ga) {
+ // The Glass Attenuation (FA) factor used to compensate for lower light
+ // levels at the device due to the possible presence of glass. The GA is
+ // the inverse of the glass transmissivity (T), so GA = 1/T. A transmissivity
+ // of 50% gives GA = 1 / 0.50 = 2. If no glass is present, use GA = 1.
+ // See Application Note: DN40-Rev 1.0
+ this->glass_attenuation_ = ga;
}
-void TCS34725Component::set_gain(TCS34725Gain gain) { this->gain_ = gain; }
} // namespace tcs34725
} // namespace esphome
diff --git a/esphome/components/tcs34725/tcs34725.h b/esphome/components/tcs34725/tcs34725.h
index b914db0eb0..47ed2959c6 100644
--- a/esphome/components/tcs34725/tcs34725.h
+++ b/esphome/components/tcs34725/tcs34725.h
@@ -12,8 +12,20 @@ enum TCS34725IntegrationTime {
TCS34725_INTEGRATION_TIME_24MS = 0xF6,
TCS34725_INTEGRATION_TIME_50MS = 0xEB,
TCS34725_INTEGRATION_TIME_101MS = 0xD5,
+ TCS34725_INTEGRATION_TIME_120MS = 0xCE,
TCS34725_INTEGRATION_TIME_154MS = 0xC0,
- TCS34725_INTEGRATION_TIME_700MS = 0x00,
+ TCS34725_INTEGRATION_TIME_180MS = 0xB5,
+ TCS34725_INTEGRATION_TIME_199MS = 0xAD,
+ TCS34725_INTEGRATION_TIME_240MS = 0x9C,
+ TCS34725_INTEGRATION_TIME_300MS = 0x83,
+ TCS34725_INTEGRATION_TIME_360MS = 0x6A,
+ TCS34725_INTEGRATION_TIME_401MS = 0x59,
+ TCS34725_INTEGRATION_TIME_420MS = 0x51,
+ TCS34725_INTEGRATION_TIME_480MS = 0x38,
+ TCS34725_INTEGRATION_TIME_499MS = 0x30,
+ TCS34725_INTEGRATION_TIME_540MS = 0x1F,
+ TCS34725_INTEGRATION_TIME_600MS = 0x06,
+ TCS34725_INTEGRATION_TIME_614MS = 0x00,
};
enum TCS34725Gain {
@@ -27,6 +39,7 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice {
public:
void set_integration_time(TCS34725IntegrationTime integration_time);
void set_gain(TCS34725Gain gain);
+ void set_glass_attenuation_factor(float ga);
void set_clear_sensor(sensor::Sensor *clear_sensor) { clear_sensor_ = clear_sensor; }
void set_red_sensor(sensor::Sensor *red_sensor) { red_sensor_ = red_sensor; }
@@ -49,8 +62,16 @@ class TCS34725Component : public PollingComponent, public i2c::I2CDevice {
sensor::Sensor *blue_sensor_{nullptr};
sensor::Sensor *illuminance_sensor_{nullptr};
sensor::Sensor *color_temperature_sensor_{nullptr};
- TCS34725IntegrationTime integration_time_{TCS34725_INTEGRATION_TIME_2_4MS};
- TCS34725Gain gain_{TCS34725_GAIN_1X};
+ float integration_time_{2.4};
+ float gain_{1.0};
+ float glass_attenuation_{1.0};
+ float illuminance_;
+ float color_temperature_;
+
+ private:
+ void calculate_temperature_and_lux_(uint16_t r, uint16_t g, uint16_t b, uint16_t c);
+ uint8_t integration_reg_{TCS34725_INTEGRATION_TIME_2_4MS};
+ uint8_t gain_reg_{TCS34725_GAIN_1X};
};
} // namespace tcs34725
diff --git a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp
index 4e4cd9f9e6..ad9c6dae00 100644
--- a/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp
+++ b/esphome/components/teleinfo/sensor/teleinfo_sensor.cpp
@@ -6,8 +6,8 @@ namespace teleinfo {
static const char *const TAG = "teleinfo_sensor";
TeleInfoSensor::TeleInfoSensor(const char *tag) { this->tag = std::string(tag); }
void TeleInfoSensor::publish_val(const std::string &val) {
- auto newval = parse_float(val);
- publish_state(*newval);
+ auto newval = parse_number(val).value_or(0.0f);
+ publish_state(newval);
}
void TeleInfoSensor::dump_config() { LOG_SENSOR(" ", "Teleinfo Sensor", this); }
} // namespace teleinfo
diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py
index 5c739e1d0a..a070e6f86d 100644
--- a/esphome/components/text_sensor/__init__.py
+++ b/esphome/components/text_sensor/__init__.py
@@ -108,6 +108,7 @@ async def substitute_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),
diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py
index 5c2155d764..2d73d0aef9 100644
--- a/esphome/components/time/__init__.py
+++ b/esphome/components/time/__init__.py
@@ -1,7 +1,6 @@
import logging
from importlib import resources
from typing import Optional
-from datetime import timezone
import tzlocal
@@ -66,14 +65,11 @@ def _extract_tz_string(tzfile: bytes) -> str:
def detect_tz() -> str:
- localzone = tzlocal.get_localzone()
- if localzone is timezone.utc:
- return "UTC0"
- if not hasattr(localzone, "key"):
+ iana_key = tzlocal.get_localzone_name()
+ if iana_key is None:
raise cv.Invalid(
"Could not automatically determine timezone, please set timezone manually."
)
- iana_key = localzone.key
_LOGGER.info("Detected timezone '%s'", iana_key)
tzfile = _load_tzdata(iana_key)
if tzfile is None:
diff --git a/esphome/components/uptime/sensor.py b/esphome/components/uptime/sensor.py
index 7989f3befc..16a1e4c125 100644
--- a/esphome/components/uptime/sensor.py
+++ b/esphome/components/uptime/sensor.py
@@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_ID,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_TOTAL_INCREASING,
UNIT_SECOND,
ICON_TIMER,
@@ -17,6 +18,7 @@ CONFIG_SCHEMA = (
icon=ICON_TIMER,
accuracy_decimals=0,
state_class=STATE_CLASS_TOTAL_INCREASING,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
)
.extend(
{
diff --git a/esphome/components/version/text_sensor.py b/esphome/components/version/text_sensor.py
index e67f881d32..4835caf35b 100644
--- a/esphome/components/version/text_sensor.py
+++ b/esphome/components/version/text_sensor.py
@@ -1,7 +1,14 @@
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, ICON_NEW_BOX, CONF_HIDE_TIMESTAMP
+from esphome.const import (
+ CONF_ENTITY_CATEGORY,
+ CONF_ID,
+ CONF_ICON,
+ ENTITY_CATEGORY_DIAGNOSTIC,
+ ICON_NEW_BOX,
+ CONF_HIDE_TIMESTAMP,
+)
version_ns = cg.esphome_ns.namespace("version")
VersionTextSensor = version_ns.class_(
@@ -13,6 +20,9 @@ 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)
diff --git a/esphome/components/web_server/__init__.py b/esphome/components/web_server/__init__.py
index 240ba7c8a0..dc652e0312 100644
--- a/esphome/components/web_server/__init__.py
+++ b/esphome/components/web_server/__init__.py
@@ -12,6 +12,7 @@ from esphome.const import (
CONF_AUTH,
CONF_USERNAME,
CONF_PASSWORD,
+ CONF_OTA,
)
from esphome.core import CORE, coroutine_with_priority
@@ -41,6 +42,7 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_WEB_SERVER_BASE_ID): cv.use_id(
web_server_base.WebServerBase
),
+ cv.Optional(CONF_OTA, default=True): cv.boolean,
}
).extend(cv.COMPONENT_SCHEMA)
@@ -52,10 +54,14 @@ async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID], paren)
await cg.register_component(var, 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(var.set_css_url(config[CONF_CSS_URL]))
cg.add(var.set_js_url(config[CONF_JS_URL]))
+ cg.add(var.set_allow_ota(config[CONF_OTA]))
if CONF_AUTH in config:
cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME]))
cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD]))
diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp
index 44ace38990..6f47f460af 100644
--- a/esphome/components/web_server/web_server.cpp
+++ b/esphome/components/web_server/web_server.cpp
@@ -152,7 +152,9 @@ void WebServer::setup() {
#endif
this->base_->add_handler(&this->events_);
this->base_->add_handler(this);
- this->base_->add_ota_handler();
+
+ if (this->allow_ota_)
+ this->base_->add_ota_handler();
this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
}
@@ -240,10 +242,14 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
#endif
stream->print(F("See ESPHome Web API for "
- "REST API documentation.
"
- "OTA Update
"
- "Debug Log
"));
+ "REST API documentation.
"));
+ if (this->allow_ota_) {
+ stream->print(
+ F("OTA Update
"));
+ }
+ stream->print(F("Debug Log
"));
+
#ifdef WEBSERVER_JS_INCLUDE
if (this->js_include_ != nullptr) {
stream->print(F(""));
@@ -458,7 +464,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
}
if (request->hasParam("speed_level")) {
String speed_level = request->getParam("speed_level")->value();
- auto val = parse_int(speed_level.c_str());
+ auto val = parse_number(speed_level.c_str());
if (!val.has_value()) {
ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str());
return;
diff --git a/esphome/components/web_server/web_server.h b/esphome/components/web_server/web_server.h
index 021d5a0646..cdfec51cf1 100644
--- a/esphome/components/web_server/web_server.h
+++ b/esphome/components/web_server/web_server.h
@@ -58,6 +58,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
*/
void set_js_include(const char *js_include);
+ /** Set whether or not the webserver should expose the OTA form and handler.
+ *
+ * @param allow_ota.
+ */
+ void set_allow_ota(bool allow_ota) { this->allow_ota_ = allow_ota; }
+
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Setup the internal web server and register handlers.
@@ -182,6 +188,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
const char *css_include_{nullptr};
const char *js_url_{nullptr};
const char *js_include_{nullptr};
+ bool allow_ota_{true};
};
} // namespace web_server
diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py
index 4da94d990a..dc1a2bc2f0 100644
--- a/esphome/components/web_server_base/__init__.py
+++ b/esphome/components/web_server_base/__init__.py
@@ -28,4 +28,4 @@ async def to_code(config):
cg.add_library("FS", None)
cg.add_library("Update", None)
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
- cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.0")
+ cg.add_library("esphome/ESPAsyncWebServer-esphome", "2.0.1")
diff --git a/esphome/components/wifi/__init__.py b/esphome/components/wifi/__init__.py
index faf3cca280..7a9319f5e0 100644
--- a/esphome/components/wifi/__init__.py
+++ b/esphome/components/wifi/__init__.py
@@ -140,7 +140,8 @@ def final_validate(config):
has_sta = bool(config.get(CONF_NETWORKS, True))
has_ap = CONF_AP in config
has_improv = "esp32_improv" in fv.full_config.get()
- if (not has_sta) and (not has_ap) and (not has_improv):
+ has_improv_serial = "improv_serial" in fv.full_config.get()
+ if not (has_sta or has_ap or has_improv or has_improv_serial):
raise cv.Invalid(
"Please specify at least an SSID or an Access Point to create."
)
diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp
index 703afa99bc..36944e3633 100644
--- a/esphome/components/wifi/wifi_component.cpp
+++ b/esphome/components/wifi/wifi_component.cpp
@@ -239,8 +239,6 @@ void WiFiComponent::save_wifi_sta(const std::string &ssid, const std::string &pa
sta.set_ssid(ssid);
sta.set_password(password);
this->set_sta(sta);
-
- this->start_scanning();
}
void WiFiComponent::start_connecting(const WiFiAP &ap, bool two) {
diff --git a/esphome/components/wifi_info/text_sensor.py b/esphome/components/wifi_info/text_sensor.py
index 1922502204..706a8967be 100644
--- a/esphome/components/wifi_info/text_sensor.py
+++ b/esphome/components/wifi_info/text_sensor.py
@@ -3,11 +3,13 @@ import esphome.config_validation as cv
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,
CONF_MAC_ADDRESS,
+ ENTITY_CATEGORY_DIAGNOSTIC,
)
DEPENDENCIES = ["wifi"]
@@ -32,26 +34,41 @@ 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,
}
).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,
}
),
}
diff --git a/esphome/components/wifi_signal/sensor.py b/esphome/components/wifi_signal/sensor.py
index 37bee75928..2097c21bd7 100644
--- a/esphome/components/wifi_signal/sensor.py
+++ b/esphome/components/wifi_signal/sensor.py
@@ -4,6 +4,7 @@ from esphome.components import sensor
from esphome.const import (
CONF_ID,
DEVICE_CLASS_SIGNAL_STRENGTH,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_DECIBEL_MILLIWATT,
)
@@ -20,6 +21,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
)
.extend(
{
diff --git a/esphome/components/xiaomi_cgd1/sensor.py b/esphome/components/xiaomi_cgd1/sensor.py
index 774c87fee9..5b88121d7c 100644
--- a/esphome/components/xiaomi_cgd1/sensor.py
+++ b/esphome/components/xiaomi_cgd1/sensor.py
@@ -10,6 +10,7 @@ from esphome.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -47,6 +48,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_cgdk2/sensor.py b/esphome/components/xiaomi_cgdk2/sensor.py
index d4e7230fd0..ac487d87fc 100644
--- a/esphome/components/xiaomi_cgdk2/sensor.py
+++ b/esphome/components/xiaomi_cgdk2/sensor.py
@@ -9,6 +9,7 @@ from esphome.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -47,6 +48,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_cgg1/sensor.py b/esphome/components/xiaomi_cgg1/sensor.py
index 4e606d95f8..a4f9a39aff 100644
--- a/esphome/components/xiaomi_cgg1/sensor.py
+++ b/esphome/components/xiaomi_cgg1/sensor.py
@@ -11,6 +11,7 @@ from esphome.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -47,6 +48,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_cgpr1/binary_sensor.py b/esphome/components/xiaomi_cgpr1/binary_sensor.py
index a7f6c41225..7f0aac873d 100644
--- a/esphome/components/xiaomi_cgpr1/binary_sensor.py
+++ b/esphome/components/xiaomi_cgpr1/binary_sensor.py
@@ -10,6 +10,8 @@ from esphome.const import (
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_ILLUMINANCE,
+ DEVICE_CLASS_MOTION,
+ ENTITY_CATEGORY_DIAGNOSTIC,
ICON_EMPTY,
UNIT_PERCENT,
CONF_IDLE_TIME,
@@ -37,13 +39,21 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_BINDKEY): cv.bind_key,
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(
- CONF_DEVICE_CLASS, default="motion"
+ CONF_DEVICE_CLASS,
+ default=DEVICE_CLASS_MOTION,
): binary_sensor.device_class,
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
- UNIT_PERCENT, ICON_EMPTY, 0, DEVICE_CLASS_BATTERY
+ unit_of_measurement=UNIT_PERCENT,
+ accuracy_decimals=0,
+ device_class=DEVICE_CLASS_BATTERY,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_IDLE_TIME): sensor.sensor_schema(
- UNIT_MINUTE, ICON_TIMELAPSE, 0, DEVICE_CLASS_EMPTY
+ unit_of_measurement=UNIT_MINUTE,
+ icon=ICON_TIMELAPSE,
+ accuracy_decimals=0,
+ device_class=DEVICE_CLASS_EMPTY,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
UNIT_LUX, ICON_EMPTY, 0, DEVICE_CLASS_ILLUMINANCE
diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py
index 1818731a0f..535316e246 100644
--- a/esphome/components/xiaomi_hhccjcy01/sensor.py
+++ b/esphome/components/xiaomi_hhccjcy01/sensor.py
@@ -6,6 +6,7 @@ from esphome.const import (
CONF_TEMPERATURE,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
ICON_WATER_PERCENT,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
@@ -63,6 +64,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_jqjcy01ym/sensor.py b/esphome/components/xiaomi_jqjcy01ym/sensor.py
index 40991c3d0f..f4d2b342fd 100644
--- a/esphome/components/xiaomi_jqjcy01ym/sensor.py
+++ b/esphome/components/xiaomi_jqjcy01ym/sensor.py
@@ -9,6 +9,7 @@ from esphome.const import (
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -54,6 +55,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_lywsd02/sensor.py b/esphome/components/xiaomi_lywsd02/sensor.py
index 339c5e673a..20629a0a9c 100644
--- a/esphome/components/xiaomi_lywsd02/sensor.py
+++ b/esphome/components/xiaomi_lywsd02/sensor.py
@@ -7,6 +7,7 @@ from esphome.const import (
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
DEVICE_CLASS_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -45,6 +46,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_lywsd03mmc/sensor.py b/esphome/components/xiaomi_lywsd03mmc/sensor.py
index f27cee3800..b2784e58fc 100644
--- a/esphome/components/xiaomi_lywsd03mmc/sensor.py
+++ b/esphome/components/xiaomi_lywsd03mmc/sensor.py
@@ -6,6 +6,7 @@ from esphome.const import (
CONF_HUMIDITY,
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -49,6 +50,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_lywsdcgq/sensor.py b/esphome/components/xiaomi_lywsdcgq/sensor.py
index 39a207327e..80f24ac0ef 100644
--- a/esphome/components/xiaomi_lywsdcgq/sensor.py
+++ b/esphome/components/xiaomi_lywsdcgq/sensor.py
@@ -6,6 +6,7 @@ from esphome.const import (
CONF_HUMIDITY,
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -45,6 +46,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_mhoc401/sensor.py b/esphome/components/xiaomi_mhoc401/sensor.py
index 57b2190150..9e92e34230 100644
--- a/esphome/components/xiaomi_mhoc401/sensor.py
+++ b/esphome/components/xiaomi_mhoc401/sensor.py
@@ -6,6 +6,7 @@ from esphome.const import (
CONF_HUMIDITY,
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
@@ -48,6 +49,7 @@ CONFIG_SCHEMA = (
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py
index fd4bae60c1..1bedae26cf 100644
--- a/esphome/components/xiaomi_mjyd02yla/binary_sensor.py
+++ b/esphome/components/xiaomi_mjyd02yla/binary_sensor.py
@@ -10,6 +10,7 @@ from esphome.const import (
CONF_BATTERY_LEVEL,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_ILLUMINANCE,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
STATE_CLASS_NONE,
UNIT_PERCENT,
@@ -51,6 +52,7 @@ CONFIG_SCHEMA = cv.All(
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
diff --git a/esphome/components/xiaomi_wx08zm/binary_sensor.py b/esphome/components/xiaomi_wx08zm/binary_sensor.py
index d2b353beff..8667794923 100644
--- a/esphome/components/xiaomi_wx08zm/binary_sensor.py
+++ b/esphome/components/xiaomi_wx08zm/binary_sensor.py
@@ -6,6 +6,7 @@ from esphome.const import (
CONF_MAC_ADDRESS,
CONF_TABLET,
DEVICE_CLASS_BATTERY,
+ ENTITY_CATEGORY_DIAGNOSTIC,
STATE_CLASS_MEASUREMENT,
UNIT_PERCENT,
ICON_BUG,
@@ -40,6 +41,7 @@ CONFIG_SCHEMA = cv.All(
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
diff --git a/esphome/config_validation.py b/esphome/config_validation.py
index fcec74b245..2bb45487fa 100644
--- a/esphome/config_validation.py
+++ b/esphome/config_validation.py
@@ -12,12 +12,14 @@ from string import ascii_letters, digits
import voluptuous as vol
from esphome import core
+import esphome.codegen as cg
from esphome.const import (
ALLOWED_NAME_CHARS,
CONF_AVAILABILITY,
CONF_COMMAND_TOPIC,
CONF_DISABLED_BY_DEFAULT,
CONF_DISCOVERY,
+ CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_INTERNAL,
@@ -35,6 +37,9 @@ from esphome.const import (
CONF_UPDATE_INTERVAL,
CONF_TYPE_ID,
CONF_TYPE,
+ ENTITY_CATEGORY_CONFIG,
+ ENTITY_CATEGORY_DIAGNOSTIC,
+ ENTITY_CATEGORY_NONE,
KEY_CORE,
KEY_FRAMEWORK_VERSION,
KEY_TARGET_FRAMEWORK,
@@ -903,21 +908,9 @@ def validate_bytes(value):
def hostname(value):
value = string(value)
- warned_underscore = False
- if len(value) > 63:
- raise Invalid("Hostnames can only be 63 characters long")
- for c in value:
- if not (c.isalnum() or c in "-_"):
- raise Invalid("Hostname can only have alphanumeric characters and -")
- if c in "_" and not warned_underscore:
- _LOGGER.warning(
- "'%s': Using the '_' (underscore) character in the hostname is discouraged "
- "as it can cause problems with some DHCP and local name services. "
- "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name",
- value,
- )
- warned_underscore = True
- return value
+ if re.match(r"^[a-z0-9-]{1,63}$", value, re.IGNORECASE) is not None:
+ return value
+ raise Invalid(f"Invalid hostname: {value}")
def domain(value):
@@ -1406,7 +1399,7 @@ def typed_schema(schemas, **kwargs):
if schema_option is None:
raise Invalid(f"{key} not specified!")
key_v = key_validator(schema_option)
- value = schemas[key_v](value)
+ value = Schema(schemas[key_v])(value)
value[key] = key_v
return value
@@ -1563,6 +1556,17 @@ def maybe_simple_value(*validators, **kwargs):
return validate
+_ENTITY_CATEGORIES = {
+ ENTITY_CATEGORY_NONE: cg.EntityCategory.ENTITY_CATEGORY_NONE,
+ ENTITY_CATEGORY_CONFIG: cg.EntityCategory.ENTITY_CATEGORY_CONFIG,
+ ENTITY_CATEGORY_DIAGNOSTIC: cg.EntityCategory.ENTITY_CATEGORY_DIAGNOSTIC,
+}
+
+
+def entity_category(value):
+ return enum(_ENTITY_CATEGORIES, lower=True)(value)
+
+
MQTT_COMPONENT_AVAILABILITY_SCHEMA = Schema(
{
Required(CONF_TOPIC): subscribe_topic,
@@ -1594,6 +1598,7 @@ ENTITY_BASE_SCHEMA = Schema(
Optional(CONF_INTERNAL): boolean,
Optional(CONF_DISABLED_BY_DEFAULT, default=False): boolean,
Optional(CONF_ICON): icon,
+ Optional(CONF_ENTITY_CATEGORY): entity_category,
}
)
@@ -1680,6 +1685,25 @@ def version_number(value):
raise Invalid("Not a version number") from e
+def platformio_version_constraint(value):
+ # for documentation on valid version constraints:
+ # https://docs.platformio.org/en/latest/core/userguide/platforms/cmd_install.html#cmd-platform-install
+
+ value = string_strict(value)
+ constraints = []
+ for item in value.split(","):
+ # find and strip prefix operator
+ op = None
+ for test_op in ("^", "~", ">=", ">", "<=", "<", "!="):
+ if item.startswith(test_op):
+ op = test_op
+ item = item[len(test_op) :]
+ break
+
+ constraints.append((op, version_number(item)))
+ return constraints
+
+
def require_framework_version(
*,
esp_idf=None,
diff --git a/esphome/const.py b/esphome/const.py
index d90ff04e7f..26fd514173 100644
--- a/esphome/const.py
+++ b/esphome/const.py
@@ -1,6 +1,6 @@
"""Constants used by esphome."""
-__version__ = "2021.10.3"
+__version__ = "2021.11.0b1"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
@@ -59,6 +59,7 @@ CONF_AT = "at"
CONF_ATTENUATION = "attenuation"
CONF_ATTRIBUTE = "attribute"
CONF_AUTH = "auth"
+CONF_AUTO_CLEAR_ENABLED = "auto_clear_enabled"
CONF_AUTO_MODE = "auto_mode"
CONF_AUTOCONF = "autoconf"
CONF_AUTOMATION_ID = "automation_id"
@@ -202,6 +203,7 @@ CONF_ELSE = "else"
CONF_ENABLE_PIN = "enable_pin"
CONF_ENABLE_TIME = "enable_time"
CONF_ENERGY = "energy"
+CONF_ENTITY_CATEGORY = "entity_category"
CONF_ENTITY_ID = "entity_id"
CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support"
CONF_ESPHOME = "esphome"
@@ -709,6 +711,7 @@ CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement"
CONF_UPDATE_INTERVAL = "update_interval"
CONF_UPDATE_ON_BOOT = "update_on_boot"
CONF_URL = "url"
+CONF_USE_ABBREVIATIONS = "use_abbreviations"
CONF_USE_ADDRESS = "use_address"
CONF_USERNAME = "username"
CONF_UUID = "uuid"
@@ -917,3 +920,12 @@ KEY_CORE = "core"
KEY_TARGET_PLATFORM = "target_platform"
KEY_TARGET_FRAMEWORK = "target_framework"
KEY_FRAMEWORK_VERSION = "framework_version"
+
+# Entity categories
+ENTITY_CATEGORY_NONE = ""
+
+# The entity category for configuration values/controls
+ENTITY_CATEGORY_CONFIG = "config"
+
+# The entity category for read only diagnostic values, for example RSSI, uptime or MAC Address
+ENTITY_CATEGORY_DIAGNOSTIC = "diagnostic"
diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py
index 8bdef3a4ea..addecf1326 100644
--- a/esphome/core/__init__.py
+++ b/esphome/core/__init__.py
@@ -10,6 +10,7 @@ from esphome.const import (
CONF_USE_ADDRESS,
CONF_ETHERNET,
CONF_WIFI,
+ CONF_PORT,
KEY_CORE,
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
@@ -519,6 +520,19 @@ class EsphomeCore:
return None
+ @property
+ def web_port(self) -> Optional[int]:
+ if self.config is None:
+ raise ValueError("Config has not been loaded yet")
+
+ if "web_server" in self.config:
+ try:
+ return self.config["web_server"][CONF_PORT]
+ except KeyError:
+ return 80
+
+ return None
+
@property
def comment(self) -> Optional[str]:
if self.config is None:
diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp
index a4d61f819c..f67fc826cf 100644
--- a/esphome/core/application.cpp
+++ b/esphome/core/application.cpp
@@ -109,8 +109,8 @@ void Application::loop() {
void IRAM_ATTR HOT Application::feed_wdt() {
static uint32_t last_feed = 0;
- uint32_t now = millis();
- if (now - last_feed > 3) {
+ uint32_t now = micros();
+ if (now - last_feed > 3000) {
arch_feed_wdt();
last_feed = now;
#ifdef USE_STATUS_LED
diff --git a/esphome/core/automation.h b/esphome/core/automation.h
index 6d79480f0f..e5460bef34 100644
--- a/esphome/core/automation.h
+++ b/esphome/core/automation.h
@@ -17,14 +17,50 @@ namespace esphome {
#define TEMPLATABLE_VALUE(type, name) TEMPLATABLE_VALUE_(type, name)
-#define TEMPLATABLE_STRING_VALUE_(name) \
- protected: \
- TemplatableStringValue name##_{}; \
-\
- public: \
- template void set_##name(V name) { this->name##_ = name; }
+template class TemplatableValue {
+ public:
+ TemplatableValue() : type_(EMPTY) {}
-#define TEMPLATABLE_STRING_VALUE(name) TEMPLATABLE_STRING_VALUE_(name)
+ template::value, int> = 0>
+ TemplatableValue(F value) : type_(VALUE), value_(value) {}
+
+ template::value, int> = 0>
+ TemplatableValue(F f) : type_(LAMBDA), f_(f) {}
+
+ bool has_value() { return this->type_ != EMPTY; }
+
+ T value(X... x) {
+ if (this->type_ == LAMBDA) {
+ return this->f_(x...);
+ }
+ // return value also when empty
+ return this->value_;
+ }
+
+ optional optional_value(X... x) {
+ if (!this->has_value()) {
+ return {};
+ }
+ return this->value(x...);
+ }
+
+ T value_or(X... x, T default_value) {
+ if (!this->has_value()) {
+ return default_value;
+ }
+ return this->value(x...);
+ }
+
+ protected:
+ enum {
+ EMPTY,
+ VALUE,
+ LAMBDA,
+ } type_;
+
+ T value_{};
+ std::function f_{};
+};
/** Base class for all automation conditions.
*
diff --git a/esphome/core/base_automation.h b/esphome/core/base_automation.h
index d97d369d33..e87a4a2765 100644
--- a/esphome/core/base_automation.h
+++ b/esphome/core/base_automation.h
@@ -224,6 +224,39 @@ template class WhileAction : public Action {
std::tuple var_{};
};
+template class RepeatAction : public Action {
+ public:
+ TEMPLATABLE_VALUE(uint32_t, count)
+
+ void add_then(const std::vector *> &actions) {
+ this->then_.add_actions(actions);
+ this->then_.add_action(new LambdaAction([this](Ts... x) {
+ this->iteration_++;
+ if (this->iteration_ == this->count_.value(x...))
+ this->play_next_tuple_(this->var_);
+ else
+ this->then_.play_tuple(this->var_);
+ }));
+ }
+
+ void play_complex(Ts... x) override {
+ this->num_running_++;
+ this->var_ = std::make_tuple(x...);
+ this->iteration_ = 0;
+ this->then_.play_tuple(this->var_);
+ }
+
+ void play(Ts... x) override { /* ignore - see play_complex */
+ }
+
+ void stop() override { this->then_.stop(); }
+
+ protected:
+ uint32_t iteration_;
+ ActionList then_;
+ std::tuple var_;
+};
+
template class WaitUntilAction : public Action, public Component {
public:
WaitUntilAction(Condition *condition) : condition_(condition) {}
diff --git a/esphome/core/config.py b/esphome/core/config.py
index 262451df88..68c253f7b4 100644
--- a/esphome/core/config.py
+++ b/esphome/core/config.py
@@ -55,6 +55,24 @@ CONF_NAME_ADD_MAC_SUFFIX = "name_add_mac_suffix"
VALID_INCLUDE_EXTS = {".h", ".hpp", ".tcc", ".ino", ".cpp", ".c"}
+def validate_hostname(config):
+ max_length = 31
+ if config[CONF_NAME_ADD_MAC_SUFFIX]:
+ max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used
+ if len(config[CONF_NAME]) > max_length:
+ raise cv.Invalid(
+ f"Hostnames can only be {max_length} characters long", path=[CONF_NAME]
+ )
+ if "_" in config[CONF_NAME]:
+ _LOGGER.warning(
+ "'%s': Using the '_' (underscore) character in the hostname is discouraged "
+ "as it can cause problems with some DHCP and local name services. "
+ "For more information, see https://esphome.io/guides/faq.html#why-shouldn-t-i-use-underscores-in-my-device-name",
+ config[CONF_NAME],
+ )
+ return config
+
+
def valid_include(value):
try:
return cv.directory(value)
@@ -79,42 +97,47 @@ def valid_project_name(value: str):
CONF_ESP8266_RESTORE_FROM_FLASH = "esp8266_restore_from_flash"
-CONFIG_SCHEMA = cv.Schema(
- {
- cv.Required(CONF_NAME): cv.hostname,
- cv.Optional(CONF_COMMENT): cv.string,
- cv.Required(CONF_BUILD_PATH): cv.string,
- cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
- {
- cv.string_strict: cv.Any([cv.string], cv.string),
- }
- ),
- cv.Optional(CONF_ON_BOOT): automation.validate_automation(
- {
- cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger),
- cv.Optional(CONF_PRIORITY, default=600.0): cv.float_,
- }
- ),
- cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation(
- {
- cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger),
- }
- ),
- cv.Optional(CONF_ON_LOOP): automation.validate_automation(
- {
- cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger),
- }
- ),
- cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include),
- cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict),
- cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean,
- cv.Optional(CONF_PROJECT): cv.Schema(
- {
- cv.Required(CONF_NAME): cv.All(cv.string_strict, valid_project_name),
- cv.Required(CONF_VERSION): cv.string_strict,
- }
- ),
- }
+CONFIG_SCHEMA = cv.All(
+ cv.Schema(
+ {
+ cv.Required(CONF_NAME): cv.valid_name,
+ cv.Optional(CONF_COMMENT): cv.string,
+ cv.Required(CONF_BUILD_PATH): cv.string,
+ cv.Optional(CONF_PLATFORMIO_OPTIONS, default={}): cv.Schema(
+ {
+ cv.string_strict: cv.Any([cv.string], cv.string),
+ }
+ ),
+ cv.Optional(CONF_ON_BOOT): automation.validate_automation(
+ {
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StartupTrigger),
+ cv.Optional(CONF_PRIORITY, default=600.0): cv.float_,
+ }
+ ),
+ cv.Optional(CONF_ON_SHUTDOWN): automation.validate_automation(
+ {
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ShutdownTrigger),
+ }
+ ),
+ cv.Optional(CONF_ON_LOOP): automation.validate_automation(
+ {
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LoopTrigger),
+ }
+ ),
+ cv.Optional(CONF_INCLUDES, default=[]): cv.ensure_list(valid_include),
+ cv.Optional(CONF_LIBRARIES, default=[]): cv.ensure_list(cv.string_strict),
+ cv.Optional(CONF_NAME_ADD_MAC_SUFFIX, default=False): cv.boolean,
+ cv.Optional(CONF_PROJECT): cv.Schema(
+ {
+ cv.Required(CONF_NAME): cv.All(
+ cv.string_strict, valid_project_name
+ ),
+ cv.Required(CONF_VERSION): cv.string_strict,
+ }
+ ),
+ }
+ ),
+ validate_hostname,
)
PRELOAD_CONFIG_SCHEMA = cv.Schema(
@@ -142,7 +165,7 @@ def preload_core_config(config, result):
CORE.data[KEY_CORE] = {}
if CONF_BUILD_PATH not in conf:
- conf[CONF_BUILD_PATH] = CORE.name
+ conf[CONF_BUILD_PATH] = f".esphome/build/{CORE.name}"
CORE.build_path = CORE.relative_config_path(conf[CONF_BUILD_PATH])
has_oldstyle = CONF_PLATFORM in conf
diff --git a/esphome/core/defines.h b/esphome/core/defines.h
index b44987a768..94fac73906 100644
--- a/esphome/core/defines.h
+++ b/esphome/core/defines.h
@@ -9,6 +9,7 @@
#define ESPHOME_BOARD "dummy_board"
#define ESPHOME_PROJECT_NAME "dummy project"
#define ESPHOME_PROJECT_VERSION "v2"
+#define ESPHOME_VARIANT "ESP32"
// Feature flags
#define USE_API
@@ -36,8 +37,11 @@
#define USE_SWITCH
#define USE_TEXT_SENSOR
#define USE_TIME
+#define USE_WEBSERVER
#define USE_WIFI
+#define WEBSERVER_PORT 80 // NOLINT
+
// Arduino-specific feature flags
#ifdef USE_ARDUINO
#define USE_CAPTIVE_PORTAL
diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp
index bc94da85fe..a9e1414018 100644
--- a/esphome/core/entity_base.cpp
+++ b/esphome/core/entity_base.cpp
@@ -26,12 +26,16 @@ void EntityBase::set_disabled_by_default(bool disabled_by_default) { this->disab
const std::string &EntityBase::get_icon() const { return this->icon_; }
void EntityBase::set_icon(const std::string &name) { this->icon_ = name; }
+// Entity Category
+EntityCategory EntityBase::get_entity_category() const { return this->entity_category_; }
+void EntityBase::set_entity_category(EntityCategory entity_category) { this->entity_category_ = entity_category; }
+
// Entity Object ID
const std::string &EntityBase::get_object_id() { return this->object_id_; }
// Calculate Object ID Hash from Entity Name
void EntityBase::calc_object_id_() {
- this->object_id_ = sanitize_string_allowlist(to_lowercase_underscore(this->name_), HOSTNAME_CHARACTER_ALLOWLIST);
+ this->object_id_ = str_sanitize(str_snake_case(this->name_));
// FNV-1 hash
this->object_id_hash_ = fnv1_hash(this->object_id_);
}
diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h
index 263747b721..c489d71910 100644
--- a/esphome/core/entity_base.h
+++ b/esphome/core/entity_base.h
@@ -5,6 +5,12 @@
namespace esphome {
+enum EntityCategory : uint8_t {
+ ENTITY_CATEGORY_NONE = 0,
+ ENTITY_CATEGORY_CONFIG = 1,
+ ENTITY_CATEGORY_DIAGNOSTIC = 2,
+};
+
// The generic Entity base class that provides an interface common to all Entities.
class EntityBase {
public:
@@ -31,6 +37,10 @@ class EntityBase {
bool is_disabled_by_default() const;
void set_disabled_by_default(bool disabled_by_default);
+ // Get/set the entity category.
+ EntityCategory get_entity_category() const;
+ void set_entity_category(EntityCategory entity_category);
+
// Get/set this entity's icon
const std::string &get_icon() const;
void set_icon(const std::string &name);
@@ -45,6 +55,7 @@ class EntityBase {
uint32_t object_id_hash_;
bool internal_{false};
bool disabled_by_default_{false};
+ EntityCategory entity_category_{ENTITY_CATEGORY_NONE};
};
} // namespace esphome
diff --git a/esphome/core/gpio.h b/esphome/core/gpio.h
index 1d3fb89805..04658d567c 100644
--- a/esphome/core/gpio.h
+++ b/esphome/core/gpio.h
@@ -70,6 +70,7 @@ class ISRInternalGPIOPin {
bool digital_read();
void digital_write(bool value);
void clear_interrupt();
+ void pin_mode(gpio::Flags flags);
protected:
void *arg_ = nullptr;
diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp
index bc97259a71..daca3ffd32 100644
--- a/esphome/core/helpers.cpp
+++ b/esphome/core/helpers.cpp
@@ -6,7 +6,9 @@
#include
#if defined(USE_ESP8266)
+#ifdef USE_WIFI
#include
+#endif
#include
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO)
#include
@@ -39,7 +41,7 @@ void get_mac_address_raw(uint8_t *mac) {
esp_efuse_mac_get_default(mac);
#endif
#endif
-#ifdef USE_ESP8266
+#if (defined USE_ESP8266 && defined USE_WIFI)
WiFi.macAddress(mac);
#endif
}
@@ -48,7 +50,11 @@ std::string get_mac_address() {
char tmp[20];
uint8_t mac[6];
get_mac_address_raw(mac);
+#ifdef USE_WIFI
sprintf(tmp, "%02x%02x%02x%02x%02x%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+#else
+ return "";
+#endif
return std::string(tmp);
}
@@ -122,31 +128,6 @@ float gamma_uncorrect(float value, float gamma) {
return powf(value, 1 / gamma);
}
-std::string to_lowercase_underscore(std::string s) {
- std::transform(s.begin(), s.end(), s.begin(), ::tolower);
- std::replace(s.begin(), s.end(), ' ', '_');
- return s;
-}
-
-std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist) {
- std::string out(s);
- out.erase(std::remove_if(out.begin(), out.end(),
- [&allowlist](const char &c) { return allowlist.find(c) == std::string::npos; }),
- out.end());
- return out;
-}
-
-std::string sanitize_hostname(const std::string &hostname) {
- std::string s = sanitize_string_allowlist(hostname, HOSTNAME_CHARACTER_ALLOWLIST);
- return truncate_string(s, 63);
-}
-
-std::string truncate_string(const std::string &s, size_t length) {
- if (s.length() > length)
- return s.substr(0, length);
- return s;
-}
-
std::string value_accuracy_to_string(float value, int8_t accuracy_decimals) {
if (accuracy_decimals < 0) {
auto multiplier = powf(10.0f, accuracy_decimals);
@@ -185,8 +166,6 @@ ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
return PARSE_NONE;
}
-const char *const HOSTNAME_CHARACTER_ALLOWLIST = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_";
-
uint8_t crc8(uint8_t *data, uint8_t len) {
uint8_t crc = 0;
@@ -203,17 +182,18 @@ uint8_t crc8(uint8_t *data, uint8_t len) {
return crc;
}
-void delay_microseconds_accurate(uint32_t usec) {
- if (usec == 0)
- return;
- if (usec < 5000UL) {
- delayMicroseconds(usec);
- return;
- }
- uint32_t start = micros();
- while (micros() - start < usec) {
- delay(0);
+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
+ ;
}
uint8_t reverse_bits_8(uint8_t x) {
@@ -272,20 +252,6 @@ std::string to_string(long double val) {
sprintf(buf, "%Lf", val);
return buf;
}
-optional parse_float(const std::string &str) {
- char *end;
- float value = ::strtof(str.c_str(), &end);
- if (end == nullptr || end != str.end().base())
- return {};
- return value;
-}
-optional parse_int(const std::string &str) {
- char *end;
- int value = ::strtol(str.c_str(), &end, 10);
- if (end == nullptr || end != str.end().base())
- return {};
- return value;
-}
optional parse_hex(const char chr) {
int out = chr;
@@ -384,17 +350,6 @@ std::string str_sprintf(const char *fmt, ...) {
return str;
}
-uint16_t encode_uint16(uint8_t msb, uint8_t lsb) { return (uint16_t(msb) << 8) | uint16_t(lsb); }
-std::array decode_uint16(uint16_t value) {
- uint8_t msb = (value >> 8) & 0xFF;
- uint8_t lsb = (value >> 0) & 0xFF;
- return {msb, lsb};
-}
-
-uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb) {
- return (uint32_t(msb) << 24) | (uint32_t(byte2) << 16) | (uint32_t(byte3) << 8) | uint32_t(lsb);
-}
-
std::string hexencode(const uint8_t *data, uint32_t len) {
char buf[20];
std::string res;
@@ -475,12 +430,37 @@ void hsv_to_rgb(int hue, float saturation, float value, float &red, float &green
}
#ifdef USE_ESP8266
+#ifdef USE_WIFI
IRAM_ATTR InterruptLock::InterruptLock() { xt_state_ = xt_rsil(15); }
IRAM_ATTR InterruptLock::~InterruptLock() { xt_wsr_ps(xt_state_); }
+#else
+IRAM_ATTR InterruptLock::InterruptLock() {}
+IRAM_ATTR InterruptLock::~InterruptLock() {}
+#endif
#endif
#ifdef USE_ESP32
IRAM_ATTR InterruptLock::InterruptLock() { portDISABLE_INTERRUPTS(); }
IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); }
#endif
+// ---------------------------------------------------------------------------------------------------------------------
+
+std::string str_truncate(const std::string &str, size_t length) {
+ return str.length() > length ? str.substr(0, length) : 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;
+}
+
} // namespace esphome
diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h
index 61cc9a9e4a..c67ad8eea3 100644
--- a/esphome/core/helpers.h
+++ b/esphome/core/helpers.h
@@ -1,5 +1,7 @@
#pragma once
+#include
+
#include
#include
#include
@@ -23,9 +25,6 @@
namespace esphome {
-/// The characters that are allowed in a hostname.
-extern const char *const HOSTNAME_CHARACTER_ALLOWLIST;
-
/// Read the raw MAC address into the provided byte array (6 bytes).
void get_mac_address_raw(uint8_t *mac);
@@ -51,18 +50,8 @@ std::string to_string(unsigned long long val); // NOLINT
std::string to_string(float val);
std::string to_string(double val);
std::string to_string(long double val);
-optional parse_float(const std::string &str);
-optional parse_int(const std::string &str);
optional parse_hex(const std::string &str, size_t start, size_t length);
optional parse_hex(char chr);
-/// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars.
-std::string sanitize_hostname(const std::string &hostname);
-
-/// Truncate a string to a specific length
-std::string truncate_string(const std::string &s, size_t length);
-
-/// Convert the string to lowercase_underscore.
-std::string to_lowercase_underscore(std::string s);
/// 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);
@@ -145,20 +134,10 @@ std::string uint64_to_string(uint64_t num);
/// Convert a uint32_t to a hex string
std::string uint32_to_string(uint32_t num);
-/// Sanitizes the input string with the allowlist.
-std::string sanitize_string_allowlist(const std::string &s, const std::string &allowlist);
-
uint8_t reverse_bits_8(uint8_t x);
uint16_t reverse_bits_16(uint16_t x);
uint32_t reverse_bits_32(uint32_t x);
-/// Encode a 16-bit unsigned integer given a most and least-significant byte.
-uint16_t encode_uint16(uint8_t msb, uint8_t lsb);
-/// Decode a 16-bit unsigned integer into an array of two values: most significant byte, least significant byte.
-std::array decode_uint16(uint16_t value);
-/// Encode a 32-bit unsigned integer given four bytes in MSB -> LSB order
-uint32_t encode_uint32(uint8_t msb, uint8_t byte2, uint8_t byte3, uint8_t lsb);
-
/// 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)
@@ -255,64 +234,7 @@ struct is_callable // NOLINT
static constexpr auto value = decltype(test(nullptr))::value; // NOLINT
};
-template class TemplatableValue {
- public:
- TemplatableValue() : type_(EMPTY) {}
-
- template::value, int> = 0>
- TemplatableValue(F value) : type_(VALUE), value_(value) {}
-
- template::value, int> = 0>
- TemplatableValue(F f) : type_(LAMBDA), f_(f) {}
-
- bool has_value() { return this->type_ != EMPTY; }
-
- T value(X... x) {
- if (this->type_ == LAMBDA) {
- return this->f_(x...);
- }
- // return value also when empty
- return this->value_;
- }
-
- optional optional_value(X... x) {
- if (!this->has_value()) {
- return {};
- }
- return this->value(x...);
- }
-
- T value_or(X... x, T default_value) {
- if (!this->has_value()) {
- return default_value;
- }
- return this->value(x...);
- }
-
- protected:
- enum {
- EMPTY,
- VALUE,
- LAMBDA,
- } type_;
-
- T value_{};
- std::function f_{};
-};
-
-template class TemplatableStringValue : public TemplatableValue {
- public:
- TemplatableStringValue() : TemplatableValue() {}
-
- template::value, int> = 0>
- TemplatableStringValue(F value) : TemplatableValue(value) {}
-
- template::value, int> = 0>
- TemplatableStringValue(F f)
- : TemplatableValue([f](X... x) -> std::string { return to_string(f(x...)); }) {}
-};
-
-void delay_microseconds_accurate(uint32_t usec);
+void delay_microseconds_safe(uint32_t us);
template class Deduplicator {
public:
@@ -361,4 +283,126 @@ template T *new_buffer(size_t length) {
return buffer;
}
+// ---------------------------------------------------------------------------------------------------------------------
+
+/// @name STL backports
+///@{
+
+// std::byteswap is from C++23 and technically should be a template, but this will do for now.
+constexpr uint8_t byteswap(uint8_t n) { return n; }
+constexpr uint16_t byteswap(uint16_t n) { return __builtin_bswap16(n); }
+constexpr uint32_t byteswap(uint32_t n) { return __builtin_bswap32(n); }
+constexpr uint64_t byteswap(uint64_t n) { return __builtin_bswap64(n); }
+
+///@}
+
+/// @name Bit manipulation
+///@{
+
+/// Encode a 16-bit value given the most and least significant byte.
+constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb) {
+ return (static_cast(msb) << 8) | (static_cast(lsb));
+}
+/// Encode a 32-bit value given four bytes in most to least significant byte order.
+constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4) {
+ return (static_cast(byte1) << 24) | (static_cast(byte2) << 16) |
+ (static_cast(byte3) << 8) | (static_cast(byte4));
+}
+
+/// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T).
+template::value, int> = 0> inline T encode_value(const uint8_t *bytes) {
+ T val = 0;
+ for (size_t i = 0; i < sizeof(T); i++) {
+ val <<= 8;
+ val |= bytes[i];
+ }
+ return val;
+}
+/// Encode a value from its constituent bytes (from most to least significant) in an std::array with length sizeof(T).
+template::value, int> = 0>
+inline T encode_value(const std::array bytes) {
+ return encode_value(bytes.data());
+}
+/// Decode a value into its constituent bytes (from most to least significant).
+template::value, int> = 0>
+inline std::array decode_value(T val) {
+ std::array ret{};
+ for (size_t i = sizeof(T); i > 0; i--) {
+ ret[i - 1] = val & 0xFF;
+ val >>= 8;
+ }
+ return ret;
+}
+
+/// Convert a value between host byte order and big endian (most significant byte first) order.
+template::value, int> = 0> constexpr T convert_big_endian(T val) {
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+ return byteswap(val);
+#else
+ return val;
+#endif
+}
+
+///@}
+
+/// @name Strings
+///@{
+
+/// Truncate a string to a specific length.
+std::string str_truncate(const std::string &str, size_t length);
+
+/// Convert the string to snake case (lowercase with underscores).
+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);
+
+///@}
+
+/// @name Parsing & formatting
+///@{
+
+/// Parse a unsigned decimal number.
+template::value && std::is_unsigned::value), int> = 0>
+optional parse_number(const char *str, size_t len) {
+ char *end = nullptr;
+ unsigned long value = ::strtoul(str, &end, 10); // NOLINT(google-runtime-int)
+ if (end == nullptr || end != str + len || value > std::numeric_limits::max())
+ return {};
+ return value;
+}
+template::value && std::is_unsigned::value), int> = 0>
+optional parse_number(const std::string &str) {
+ return parse_number(str.c_str(), str.length());
+}
+/// Parse a signed decimal number.
+template::value && std::is_signed::value), int> = 0>
+optional parse_number(const char *str, size_t len) {
+ char *end = nullptr;
+ signed long value = ::strtol(str, &end, 10); // NOLINT(google-runtime-int)
+ if (end == nullptr || end != str + len || value < std::numeric_limits::min() ||
+ value > std::numeric_limits::max())
+ return {};
+ return value;
+}
+template::value && std::is_signed::value), int> = 0>
+optional parse_number(const std::string &str) {
+ return parse_number(str.c_str(), str.length());
+}
+/// Parse a decimal floating-point number.
+template::value), int> = 0>
+optional parse_number(const char *str, size_t len) {
+ char *end = nullptr;
+ float value = ::strtof(str, &end);
+ if (end == nullptr || end != str + len || value == HUGE_VALF)
+ return {};
+ return value;
+}
+template::value), int> = 0>
+optional parse_number(const std::string &str) {
+ return parse_number(str.c_str(), str.length());
+}
+
+///@}
+
} // namespace esphome
diff --git a/esphome/cpp_helpers.py b/esphome/cpp_helpers.py
index 5b081698ad..9127f88e39 100644
--- a/esphome/cpp_helpers.py
+++ b/esphome/cpp_helpers.py
@@ -2,6 +2,7 @@ import logging
from esphome.const import (
CONF_DISABLED_BY_DEFAULT,
+ CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_INTERNAL,
CONF_NAME,
@@ -102,6 +103,8 @@ async def setup_entity(var, config):
add(var.set_internal(config[CONF_INTERNAL]))
if CONF_ICON in config:
add(var.set_icon(config[CONF_ICON]))
+ if CONF_ENTITY_CATEGORY in config:
+ add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
def extract_registry_entry_config(registry, full_config):
diff --git a/esphome/cpp_types.py b/esphome/cpp_types.py
index 888c319024..13d088e1cb 100644
--- a/esphome/cpp_types.py
+++ b/esphome/cpp_types.py
@@ -34,3 +34,4 @@ GPIOPin = esphome_ns.class_("GPIOPin")
InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin)
gpio_ns = esphome_ns.namespace("gpio")
gpio_Flags = gpio_ns.enum("Flags", is_class=True)
+EntityCategory = esphome_ns.enum("EntityCategory")
diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py
index 63378a38b5..11571ec889 100644
--- a/esphome/dashboard/dashboard.py
+++ b/esphome/dashboard/dashboard.py
@@ -509,6 +509,12 @@ class DashboardEntry:
return None
return self.storage.address
+ @property
+ def web_port(self):
+ if self.storage is None:
+ return None
+ return self.storage.web_port
+
@property
def name(self):
if self.storage is None:
@@ -569,6 +575,7 @@ class ListDevicesHandler(BaseHandler):
"path": entry.path,
"comment": entry.comment,
"address": entry.address,
+ "web_port": entry.web_port,
"target_platform": entry.target_platform,
}
for entry in entries
@@ -963,7 +970,7 @@ def start_web_server(args):
server.add_socket(socket)
else:
_LOGGER.info(
- "Starting dashboard web server on port %s and configuration dir %s...",
+ "Starting dashboard web server on http://0.0.0.0:%s and configuration dir %s...",
args.port,
settings.config_dir,
)
diff --git a/esphome/git.py b/esphome/git.py
index 12c6b41648..25d893b2f5 100644
--- a/esphome/git.py
+++ b/esphome/git.py
@@ -40,15 +40,23 @@ def clone_or_update(
) -> Path:
key = f"{url}@{ref}"
repo_dir = _compute_destination_path(key, domain)
+ fetch_pr_branch = ref is not None and ref.startswith("pull/")
if not repo_dir.is_dir():
_LOGGER.info("Cloning %s", key)
_LOGGER.debug("Location: %s", repo_dir)
cmd = ["git", "clone", "--depth=1"]
- if ref is not None:
+ if ref is not None and not fetch_pr_branch:
cmd += ["--branch", ref]
cmd += ["--", url, str(repo_dir)]
run_git_command(cmd)
+ if fetch_pr_branch:
+ # We need to fetch the PR branch first, otherwise git will complain
+ # about missing objects
+ _LOGGER.info("Fetching %s", ref)
+ run_git_command(["git", "fetch", "--", "origin", ref], str(repo_dir))
+ run_git_command(["git", "reset", "--hard", "FETCH_HEAD"], str(repo_dir))
+
else:
# Check refresh needed
file_timestamp = Path(repo_dir / ".git" / "FETCH_HEAD")
diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py
index 70e4430e71..2072e25ec5 100644
--- a/esphome/platformio_api.py
+++ b/esphome/platformio_api.py
@@ -125,7 +125,7 @@ def _run_idedata(config):
def _load_idedata(config):
platformio_ini = Path(CORE.relative_build_path("platformio.ini"))
- temp_idedata = Path(CORE.relative_internal_path(CORE.name, "idedata.json"))
+ temp_idedata = Path(CORE.relative_internal_path("idedata", f"{CORE.name}.json"))
changed = False
if not platformio_ini.is_file() or not temp_idedata.is_file():
diff --git a/esphome/storage_json.py b/esphome/storage_json.py
index 3262559116..207a3edf57 100644
--- a/esphome/storage_json.py
+++ b/esphome/storage_json.py
@@ -41,6 +41,7 @@ class StorageJSON:
esphome_version,
src_version,
address,
+ web_port,
target_platform,
build_path,
firmware_bin_path,
@@ -60,6 +61,9 @@ class StorageJSON:
self.src_version = src_version # type: int
# Address of the ESP, for example livingroom.local or a static IP
self.address = address # type: str
+ # Web server port of the ESP, for example 80
+ assert web_port is None or isinstance(web_port, int)
+ self.web_port = web_port # type: int
# The type of ESP in use, either ESP32 or ESP8266
self.target_platform = target_platform # type: str
# The absolute path to the platformio project
@@ -78,6 +82,7 @@ class StorageJSON:
"esphome_version": self.esphome_version,
"src_version": self.src_version,
"address": self.address,
+ "web_port": self.web_port,
"esp_platform": self.target_platform,
"build_path": self.build_path,
"firmware_bin_path": self.firmware_bin_path,
@@ -101,6 +106,7 @@ class StorageJSON:
esphome_version=const.__version__,
src_version=1,
address=esph.address,
+ web_port=esph.web_port,
target_platform=esph.target_platform,
build_path=esph.build_path,
firmware_bin_path=esph.firmware_bin,
@@ -117,6 +123,7 @@ class StorageJSON:
esphome_version=const.__version__,
src_version=1,
address=address,
+ web_port=None,
target_platform=esp_platform,
build_path=None,
firmware_bin_path=None,
@@ -135,6 +142,7 @@ class StorageJSON:
)
src_version = storage.get("src_version")
address = storage.get("address")
+ web_port = storage.get("web_port")
esp_platform = storage.get("esp_platform")
build_path = storage.get("build_path")
firmware_bin_path = storage.get("firmware_bin_path")
@@ -146,6 +154,7 @@ class StorageJSON:
esphome_version,
src_version,
address,
+ web_port,
esp_platform,
build_path,
firmware_bin_path,
diff --git a/esphome/wizard.py b/esphome/wizard.py
index 5c35fac73a..6c87b66453 100644
--- a/esphome/wizard.py
+++ b/esphome/wizard.py
@@ -367,10 +367,9 @@ def wizard(path):
)
safe_print()
safe_print("Next steps:")
+ safe_print(" > Follow the rest of the getting started guide:")
safe_print(
- ' > Check your Home Assistant "integrations" screen. If all goes well, you '
- "should see your ESP being discovered automatically."
+ " > https://esphome.io/guides/getting_started_command_line.html#adding-some-features"
)
- safe_print(" > Then follow the rest of the getting started guide:")
- safe_print(" > https://esphome.io/guides/getting_started_command_line.html")
+ safe_print(" > to learn how to customize ESPHome and install it to your device.")
return 0
diff --git a/esphome/writer.py b/esphome/writer.py
index 29532d4f64..8963572752 100644
--- a/esphome/writer.py
+++ b/esphome/writer.py
@@ -38,10 +38,8 @@ CPP_BASE_FORMAT = (
""""
void setup() {
- // ===== DO NOT EDIT ANYTHING BELOW THIS LINE =====
""",
"""
- // ========= YOU CAN EDIT AFTER THIS LINE =========
App.setup();
}
@@ -59,10 +57,8 @@ lib_deps =
build_flags =
upload_flags =
-; ===== DO NOT EDIT ANYTHING BELOW THIS LINE =====
""",
"""
-; ========= YOU CAN EDIT AFTER THIS LINE =========
""",
)
@@ -102,61 +98,6 @@ def replace_file_content(text, pattern, repl):
return content_new, count
-def migrate_src_version_0_to_1():
- main_cpp = CORE.relative_build_path("src", "main.cpp")
- if not os.path.isfile(main_cpp):
- return
-
- content = read_file(main_cpp)
-
- if CPP_INCLUDE_BEGIN in content:
- return
-
- content, count = replace_file_content(content, r"\s*delay\((?:16|20)\);", "")
- if count != 0:
- _LOGGER.info(
- "Migration: Removed %s occurrence of 'delay(16);' in %s", count, main_cpp
- )
-
- content, count = replace_file_content(content, r"using namespace esphomelib;", "")
- if count != 0:
- _LOGGER.info(
- "Migration: Removed %s occurrence of 'using namespace esphomelib;' "
- "in %s",
- count,
- main_cpp,
- )
-
- if CPP_INCLUDE_BEGIN not in content:
- content, count = replace_file_content(
- content,
- r'#include "esphomelib/application.h"',
- f"{CPP_INCLUDE_BEGIN}\n{CPP_INCLUDE_END}",
- )
- if count == 0:
- _LOGGER.error(
- "Migration failed. ESPHome 1.10.0 needs to have a new auto-generated "
- "include section in the %s file. Please remove %s and let it be "
- "auto-generated again.",
- main_cpp,
- main_cpp,
- )
- _LOGGER.info("Migration: Added include section to %s", main_cpp)
-
- write_file_if_changed(main_cpp, content)
-
-
-def migrate_src_version(old, new):
- if old == new:
- return
- if old > new:
- _LOGGER.warning("The source version rolled backwards! Ignoring.")
- return
-
- if old == 0:
- migrate_src_version_0_to_1()
-
-
def storage_should_clean(old, new): # type: (StorageJSON, StorageJSON) -> bool
if old is None:
return True
@@ -175,9 +116,6 @@ def update_storage_json():
if old == new:
return
- old_src_version = old.src_version if old is not None else 0
- migrate_src_version(old_src_version, new.src_version)
-
if storage_should_clean(old, new):
_LOGGER.info("Core config or version changed, cleaning build files...")
clean_build()
@@ -277,12 +215,12 @@ VERSION_H_TARGET = "esphome/core/version.h"
ESPHOME_README_TXT = """
THIS DIRECTORY IS AUTO-GENERATED, DO NOT MODIFY
-ESPHome automatically populates the esphome/ directory, and any
+ESPHome automatically populates the build directory, and any
changes to this directory will be removed the next time esphome is
run.
-For modifying esphome's core files, please use a development esphome install
-or use the custom_components folder.
+For modifying esphome's core files, please use a development esphome install,
+the custom_components folder or the external_components feature.
"""
@@ -339,9 +277,7 @@ def copy_src_tree():
write_file_if_changed(
CORE.relative_src_path("esphome", "core", "defines.h"), generate_defines_h()
)
- write_file_if_changed(
- CORE.relative_src_path("esphome", "README.txt"), ESPHOME_README_TXT
- )
+ write_file_if_changed(CORE.relative_build_path("README.txt"), ESPHOME_README_TXT)
write_file_if_changed(
CORE.relative_src_path("esphome.h"), ESPHOME_H_FORMAT.format(include_s)
)
@@ -413,11 +349,6 @@ GITIGNORE_CONTENT = """# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
-**/.pioenvs/
-**/.piolibdeps/
-**/lib/
-**/src/
-**/platformio.ini
/secrets.yaml
"""
diff --git a/platformio.ini b/platformio.ini
index ee895ed882..0dd32268e0 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -27,7 +27,7 @@ build_flags =
[common]
lib_deps =
esphome/noise-c@0.1.4 ; api
- makuna/NeoPixelBus@2.6.7 ; neopixelbus
+ makuna/NeoPixelBus@2.6.9 ; neopixelbus
build_flags =
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
src_filter =
@@ -41,7 +41,7 @@ lib_deps =
${common.lib_deps}
ottowinter/AsyncMqttClient-esphome@0.8.6 ; mqtt
ottowinter/ArduinoJson-esphomelib@5.13.3 ; json
- esphome/ESPAsyncWebServer-esphome@2.0.0 ; web_server_base
+ esphome/ESPAsyncWebServer-esphome@2.0.1 ; web_server_base
fastled/FastLED@3.3.2 ; fastled_base
mikalhart/TinyGPSPlus@1.0.2 ; gps
freekode/TM1651@1.0.1 ; tm1651
diff --git a/requirements.txt b/requirements.txt
index 51bbb445d7..6e1fe56057 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,16 +1,16 @@
voluptuous==0.12.2
-PyYAML==5.4.1
-paho-mqtt==1.5.1
+PyYAML==6.0
+paho-mqtt==1.6.1
colorama==0.4.4
tornado==6.1
-tzlocal==3.0 # from time
+tzlocal==4.1 # from time
tzdata>=2021.1 # from time
pyserial==3.5
-platformio==5.2.1 # When updating platformio, also update Dockerfile
-esptool==3.1
+platformio==5.2.2 # When updating platformio, also update Dockerfile
+esptool==3.2
click==8.0.3
esphome-dashboard==20211021.1
-aioesphomeapi==9.1.5
+aioesphomeapi==10.2.0
# esp-idf requires this, but doesn't bundle it by default
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
diff --git a/requirements_test.txt b/requirements_test.txt
index 8ebcf24d4d..03879c5d0e 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -1,6 +1,6 @@
pylint==2.11.1
flake8==4.0.1
-black==21.9b0
+black==21.10b0
pexpect==4.8.0
pre-commit
@@ -8,6 +8,6 @@ pre-commit
pytest==6.2.5
pytest-cov==3.0.0
pytest-mock==3.6.1
-pytest-asyncio==0.15.1
+pytest-asyncio==0.16.0
asyncmock==0.4.2
hypothesis==5.49.0
diff --git a/script/ci-custom.py b/script/ci-custom.py
index 8e9ca487a6..89550afd3d 100755
--- a/script/ci-custom.py
+++ b/script/ci-custom.py
@@ -263,7 +263,11 @@ def highlight(s):
@lint_re_check(
r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL,
include=cpp_include,
- exclude=["esphome/core/log.h", "esphome/components/socket/headers.h"],
+ exclude=[
+ "esphome/core/log.h",
+ "esphome/components/socket/headers.h",
+ "esphome/core/defines.h",
+ ],
)
def lint_no_defines(fname, match):
s = highlight(
diff --git a/script/clang-tidy b/script/clang-tidy
index 87ba1c84b5..ad5fdfeb04 100755
--- a/script/clang-tidy
+++ b/script/clang-tidy
@@ -32,6 +32,7 @@ def clang_options(idedata):
'-D_PGMSPACE_H_',
'-Dpgm_read_byte(s)=(*(const uint8_t *)(s))',
'-Dpgm_read_byte_near(s)=(*(const uint8_t *)(s))',
+ '-Dpgm_read_word(s)=(*(const uint16_t *)(s))',
'-Dpgm_read_dword(s)=(*(const uint32_t *)(s))',
'-DPROGMEM=',
'-DPGM_P=const char *',
diff --git a/sdkconfig.defaults b/sdkconfig.defaults
index 6b2d6f8f2e..26db4705b8 100644
--- a/sdkconfig.defaults
+++ b/sdkconfig.defaults
@@ -8,6 +8,7 @@ CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_PARTITION_TABLE_CUSTOM=y
#CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
CONFIG_PARTITION_TABLE_SINGLE_APP=n
+CONFIG_FREERTOS_HZ=1000
# esp32_ble
CONFIG_BT_ENABLED=y
diff --git a/tests/test1.yaml b/tests/test1.yaml
index 157ccfc5d1..dee7493bf2 100644
--- a/tests/test1.yaml
+++ b/tests/test1.yaml
@@ -94,6 +94,7 @@ mqtt:
username: 'debug'
password: 'debug'
client_id: someclient
+ use_abbreviations: false
discovery: True
discovery_retain: False
discovery_prefix: discovery
@@ -233,6 +234,7 @@ logger:
web_server:
port: 8080
+ ota: true
css_url: https://esphome.io/_static/webserver-v1.min.css
js_url: https://esphome.io/_static/webserver-v1.min.js
@@ -2213,6 +2215,31 @@ display:
row_start: 0
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
+ - platform: ili9341
+ model: "TFT 2.4"
+ cs_pin: GPIO5
+ dc_pin: GPIO4
+ reset_pin: GPIO22
+ led_pin:
+ number: GPIO15
+ inverted: true
+ lambda: |-
+ it.rectangle(0, 0, it.get_width(), it.get_height());
+ - platform: ili9341
+ model: "TFT 2.4"
+ cs_pin: GPIO5
+ dc_pin: GPIO4
+ reset_pin: GPIO22
+ led_pin:
+ number: GPIO15
+ inverted: true
+ auto_clear_enabled: false
+ rotation: 90
+ lambda: |-
+ if (!id(glob_bool_processed)) {
+ it.fill(Color::WHITE);
+ id(glob_bool_processed) = true;
+ }
tm1651:
id: tm1651_battery
@@ -2392,6 +2419,10 @@ globals:
type: std::string
restore_value: no
# initial_value: ""
+ - id: glob_bool_processed
+ type: bool
+ restore_value: no
+ initial_value: 'false'
text_sensor:
- platform: mqtt_subscribe
diff --git a/tests/test2.yaml b/tests/test2.yaml
index 7e71d1ab4e..f90e522b1e 100644
--- a/tests/test2.yaml
+++ b/tests/test2.yaml
@@ -401,6 +401,11 @@ ble_client:
airthings_ble:
+ruuvi_ble:
+
+xiaomi_ble:
+
+
#esp32_ble_beacon:
# type: iBeacon
# uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98'
@@ -499,3 +504,9 @@ interval:
display:
+cap1188:
+ id: cap1188_component
+ address: 0x29
+ touch_threshold: 0x20
+ allow_multiple_touches: true
+ reset_pin: 14
diff --git a/tests/test3.yaml b/tests/test3.yaml
index f7cba5e787..cf80c06aa8 100644
--- a/tests/test3.yaml
+++ b/tests/test3.yaml
@@ -250,6 +250,10 @@ uart:
tx_pin: GPIO4
rx_pin: GPIO5
baud_rate: 9600
+ - id: uart7
+ tx_pin: GPIO4
+ rx_pin: GPIO5
+ baud_rate: 38400
modbus:
uart_id: uart1
@@ -264,6 +268,8 @@ logger:
level: DEBUG
esp8266_store_log_strings_in_flash: true
+improv_serial:
+
deep_sleep:
run_duration: 20s
sleep_duration: 50s
@@ -403,7 +409,7 @@ sensor:
name: Illuminance
color_temperature:
name: Color Temperature
- integration_time: 700ms
+ integration_time: 614ms
gain: 60x
- platform: custom
lambda: |-
@@ -549,6 +555,18 @@ sensor:
name: 'PMS Humidity'
formaldehyde:
name: 'PMS Formaldehyde Concentration'
+ - platform: cse7761
+ uart_id: uart7
+ voltage:
+ name: 'CSE7761 Voltage'
+ current_1:
+ name: 'CSE7761 Current 1'
+ current_2:
+ name: 'CSE7761 Current 2'
+ active_power_1:
+ name: 'CSE7761 Active Power 1'
+ active_power_2:
+ name: 'CSE7761 Active Power 2'
- platform: cse7766
uart_id: uart3
voltage:
diff --git a/tests/test4.yaml b/tests/test4.yaml
index bc249c5ecb..938145235a 100644
--- a/tests/test4.yaml
+++ b/tests/test4.yaml
@@ -45,6 +45,7 @@ logger:
level: DEBUG
web_server:
+ ota: false
auth:
username: admin
password: admin
@@ -481,6 +482,12 @@ esp32_camera:
resolution: 640x480
jpeg_quality: 10
+esp32_camera_web_server:
+ - port: 8080
+ mode: stream
+ - port: 8081
+ mode: snapshot
+
external_components:
- source: github://esphome/esphome@dev
refresh: 1d
diff --git a/tests/test5.yaml b/tests/test5.yaml
index 72df3ed212..f1fb786fe5 100644
--- a/tests/test5.yaml
+++ b/tests/test5.yaml
@@ -173,3 +173,11 @@ sensor:
uart_id: uart2
co2:
name: CO2 Sensor
+
+script:
+ - id: automation_test
+ then:
+ - repeat:
+ count: 5
+ then:
+ - logger.log: "looping!"
diff --git a/tests/unit_tests/test_config_validation.py b/tests/unit_tests/test_config_validation.py
index e34c7064fa..16cfb16e94 100644
--- a/tests/unit_tests/test_config_validation.py
+++ b/tests/unit_tests/test_config_validation.py
@@ -40,28 +40,6 @@ def test_valid_name__invalid(value):
config_validation.valid_name(value)
-@pytest.mark.parametrize("value", ("foo", "bar123", "foo-bar"))
-def test_hostname__valid(value):
- actual = config_validation.hostname(value)
-
- assert actual == value
-
-
-@pytest.mark.parametrize("value", ("foo bar", "foobar ", "foo#bar"))
-def test_hostname__invalid(value):
- with pytest.raises(Invalid):
- config_validation.hostname(value)
-
-
-def test_hostname__warning(caplog):
- actual = config_validation.hostname("foo_bar")
- assert actual == "foo_bar"
- assert (
- "Using the '_' (underscore) character in the hostname is discouraged"
- in caplog.text
- )
-
-
@given(one_of(integers(), text()))
def test_string__valid(value):
actual = config_validation.string(value)