Merge branch 'dev' into cse7761-sonoffpow-fix

This commit is contained in:
mick96 2024-12-19 14:25:32 +01:00 committed by GitHub
commit 317c5ac57a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
270 changed files with 5624 additions and 2145 deletions

View File

@ -7,28 +7,39 @@ Checks: >-
-boost-*,
-bugprone-easily-swappable-parameters,
-bugprone-implicit-widening-of-multiplication-result,
-bugprone-multi-level-implicit-pointer-conversion,
-bugprone-narrowing-conversions,
-bugprone-signed-char-misuse,
-bugprone-switch-missing-default-case,
-cert-dcl50-cpp,
-cert-err33-c,
-cert-err58-cpp,
-cert-oop57-cpp,
-cert-str34-c,
-clang-analyzer-optin.core.EnumCastOutOfRange,
-clang-analyzer-optin.cplusplus.UninitializedObject,
-clang-analyzer-osx.*,
-clang-diagnostic-delete-abstract-non-virtual-dtor,
-clang-diagnostic-delete-non-abstract-non-virtual-dtor,
-clang-diagnostic-deprecated-declarations,
-clang-diagnostic-ignored-optimization-argument,
-clang-diagnostic-missing-field-initializers,
-clang-diagnostic-shadow-field,
-clang-diagnostic-unused-const-variable,
-clang-diagnostic-unused-parameter,
-clang-diagnostic-vla-cxx-extension,
-concurrency-*,
-cppcoreguidelines-avoid-c-arrays,
-cppcoreguidelines-avoid-const-or-ref-data-members,
-cppcoreguidelines-avoid-do-while,
-cppcoreguidelines-avoid-magic-numbers,
-cppcoreguidelines-init-variables,
-cppcoreguidelines-macro-to-enum,
-cppcoreguidelines-macro-usage,
-cppcoreguidelines-missing-std-forward,
-cppcoreguidelines-narrowing-conversions,
-cppcoreguidelines-non-private-member-variables-in-classes,
-cppcoreguidelines-owning-memory,
-cppcoreguidelines-prefer-member-initializer,
-cppcoreguidelines-pro-bounds-array-to-pointer-decay,
-cppcoreguidelines-pro-bounds-constant-array-index,
@ -40,7 +51,9 @@ Checks: >-
-cppcoreguidelines-pro-type-static-cast-downcast,
-cppcoreguidelines-pro-type-union-access,
-cppcoreguidelines-pro-type-vararg,
-cppcoreguidelines-rvalue-reference-param-not-moved,
-cppcoreguidelines-special-member-functions,
-cppcoreguidelines-use-default-member-init,
-cppcoreguidelines-virtual-class-destructor,
-fuchsia-multiple-inheritance,
-fuchsia-overloaded-operator,
@ -60,20 +73,32 @@ Checks: >-
-llvm-include-order,
-llvm-qualified-auto,
-llvmlibc-*,
-misc-non-private-member-variables-in-classes,
-misc-const-correctness,
-misc-include-cleaner,
-misc-no-recursion,
-misc-non-private-member-variables-in-classes,
-misc-unused-parameters,
-modernize-avoid-c-arrays,
-misc-use-anonymous-namespace,
-modernize-avoid-bind,
-modernize-avoid-c-arrays,
-modernize-concat-nested-namespaces,
-modernize-macro-to-enum,
-modernize-return-braced-init-list,
-modernize-type-traits,
-modernize-use-auto,
-modernize-use-constraints,
-modernize-use-default-member-init,
-modernize-use-equals-default,
-modernize-use-trailing-return-type,
-modernize-use-nodiscard,
-modernize-use-nullptr,
-modernize-use-nodiscard,
-modernize-use-nullptr,
-modernize-use-trailing-return-type,
-mpi-*,
-objc-*,
-performance-enum-size,
-readability-avoid-nested-conditional-operator,
-readability-container-contains,
-readability-container-data-pointer,
-readability-convert-member-functions-to-static,
-readability-else-after-return,
@ -82,11 +107,14 @@ Checks: >-
-readability-isolate-declaration,
-readability-magic-numbers,
-readability-make-member-function-const,
-readability-named-parameter,
-readability-redundant-casting,
-readability-redundant-inline-specifier,
-readability-redundant-member-init,
-readability-redundant-string-init,
-readability-uppercase-literal-suffix,
-readability-use-anyofallof,
WarningsAsErrors: '*'
AnalyzeTemporaryDtors: false
FormatStyle: google
CheckOptions:
- key: google-readability-function-size.StatementThreshold

View File

@ -46,7 +46,7 @@ runs:
- name: Build and push to ghcr by digest
id: build-ghcr
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v6.10.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false
@ -72,7 +72,7 @@ runs:
- name: Build and push to dockerhub by digest
id: build-dockerhub
uses: docker/build-push-action@v6.9.0
uses: docker/build-push-action@v6.10.0
env:
DOCKER_BUILD_SUMMARY: false
DOCKER_BUILD_RECORD_UPLOAD: false

View File

@ -22,7 +22,7 @@ runs:
python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache/restore@v4.1.2
uses: actions/cache/restore@v4.2.0
with:
path: venv
# yamllint disable-line rule:line-length

View File

@ -46,7 +46,7 @@ jobs:
with:
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1
uses: docker/setup-buildx-action@v3.8.0
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0

View File

@ -13,6 +13,7 @@ on:
- ".github/workflows/ci.yml"
- "!.yamllint"
- "!.github/dependabot.yml"
- "!docker/**"
merge_group:
permissions:
@ -30,7 +31,7 @@ concurrency:
jobs:
common:
name: Create common environment
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
outputs:
cache-key: ${{ steps.cache-key.outputs.key }}
steps:
@ -46,7 +47,7 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment
id: cache-venv
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.0
with:
path: venv
# yamllint disable-line rule:line-length
@ -62,7 +63,7 @@ jobs:
black:
name: Check black
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -83,7 +84,7 @@ jobs:
flake8:
name: Check flake8
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -104,7 +105,7 @@ jobs:
pylint:
name: Check pylint
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -125,7 +126,7 @@ jobs:
pyupgrade:
name: Check pyupgrade
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -146,7 +147,7 @@ jobs:
ci-custom:
name: Run script/ci-custom
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -225,7 +226,7 @@ jobs:
clang-format:
name: Check clang-format
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
steps:
@ -251,7 +252,7 @@ jobs:
clang-tidy:
name: ${{ matrix.name }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- black
@ -302,23 +303,18 @@ jobs:
- name: Cache platformio
if: github.ref == 'refs/heads/dev'
uses: actions/cache@v4.1.2
uses: actions/cache@v4.2.0
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}
- name: Cache platformio
if: github.ref != 'refs/heads/dev'
uses: actions/cache/restore@v4.1.2
uses: actions/cache/restore@v4.2.0
with:
path: ~/.platformio
key: platformio-${{ matrix.pio_cache_key }}
- name: Install clang-tidy
run: |
sudo apt-get update
sudo apt-get install clang-tidy-14
- name: Register problem matchers
run: |
echo "::add-matcher::.github/workflows/matchers/gcc.json"
@ -345,7 +341,7 @@ jobs:
if: always()
list-components:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
if: github.event_name == 'pull_request'
@ -387,7 +383,7 @@ jobs:
test-build-components:
name: Component test ${{ matrix.file }}
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- list-components
@ -421,7 +417,7 @@ jobs:
test-build-components-splitter:
name: Split components for testing into 20 groups maximum
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- list-components
@ -439,7 +435,7 @@ jobs:
test-build-components-split:
name: Test split components
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- list-components
@ -483,7 +479,7 @@ jobs:
ci-status:
name: CI Status
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
needs:
- common
- black

View File

@ -65,7 +65,7 @@ jobs:
pip3 install build
python3 -m build
- name: Publish
uses: pypa/gh-action-pypi-publish@v1.12.2
uses: pypa/gh-action-pypi-publish@v1.12.3
deploy-docker:
name: Build ESPHome ${{ matrix.platform }}
@ -90,7 +90,7 @@ jobs:
python-version: "3.9"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1
uses: docker/setup-buildx-action@v3.8.0
- name: Set up QEMU
if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.2.0
@ -141,7 +141,7 @@ jobs:
echo name=$(cat /tmp/platform) >> $GITHUB_OUTPUT
- name: Upload digests
uses: actions/upload-artifact@v4.4.3
uses: actions/upload-artifact@v4.5.0
with:
name: digests-${{ steps.sanitize.outputs.name }}
path: /tmp/digests
@ -184,7 +184,7 @@ jobs:
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.7.1
uses: docker/setup-buildx-action@v3.8.0
- name: Log in to docker hub
if: matrix.registry == 'dockerhub'

View File

@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
@ -354,6 +355,8 @@ esphome/components/sdl/* @clydebarrow
esphome/components/sdm_meter/* @jesserockz @polyfaces
esphome/components/sdp3x/* @Azimath
esphome/components/seeed_mr24hpc1/* @limengdu
esphome/components/seeed_mr60bha2/* @limengdu
esphome/components/seeed_mr60fda2/* @limengdu
esphome/components/selec_meter/* @sourabhjaiswal
esphome/components/select/* @esphome/core
esphome/components/sen0321/* @notjj

View File

@ -163,6 +163,18 @@ ENTRYPOINT ["/entrypoint.sh"]
CMD ["dashboard", "/config"]
ARG BUILD_VERSION=dev
# Labels
LABEL \
org.opencontainers.image.authors="The ESPHome Authors" \
org.opencontainers.image.title="ESPHome" \
org.opencontainers.image.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
org.opencontainers.image.url="https://esphome.io/" \
org.opencontainers.image.documentation="https://esphome.io/" \
org.opencontainers.image.source="https://github.com/esphome/esphome" \
org.opencontainers.image.licenses="ESPHome" \
org.opencontainers.image.version=${BUILD_VERSION}
# ======================= hassio-type image =======================
@ -194,7 +206,7 @@ RUN if [ "$TARGETARCH$TARGETVARIANT" = "armv7" ]; then \
# Labels
LABEL \
io.hass.name="ESPHome" \
io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
io.hass.description="ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems" \
io.hass.type="addon" \
io.hass.version="${BUILD_VERSION}"
# io.hass.arch is inherited from addon-debian-base
@ -209,17 +221,22 @@ ENV \
PLATFORMIO_CORE_DIR=/esphome/.temp/platformio
RUN \
apt-get update \
curl -L https://apt.llvm.org/llvm-snapshot.gpg.key -o /etc/apt/trusted.gpg.d/apt.llvm.org.asc \
&& echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-18 main" > /etc/apt/sources.list.d/llvm.sources.list \
&& apt-get update \
# Use pinned versions so that we get updates with build caching
&& apt-get install -y --no-install-recommends \
clang-format-13=1:13.0.1-11+b2 \
clang-tidy-14=1:14.0.6-12 \
patch=2.7.6-7 \
software-properties-common=0.99.30-4.1~deb12u1 \
nano=7.2-1+deb12u1 \
build-essential=12.9 \
python3-dev=3.11.2-1+b1 \
&& rm -rf \
&& if [ "$TARGETARCH$TARGETVARIANT" != "armv7" ]; then \
# move this up after armv7 is retired
apt-get install -y --no-install-recommends clang-tidy-18=1:18.1.8~++20240731024826+3b5b5c1ec4a3-1~exp1~20240731144843.145 ; \
fi; \
rm -rf \
/tmp/* \
/var/{cache,log}/* \
/var/lib/apt/lists/*

View File

@ -363,7 +363,7 @@ def upload_program(config, args, host):
from esphome import espota2
remote_port = ota_conf[CONF_PORT]
remote_port = int(ota_conf[CONF_PORT])
password = ota_conf.get(CONF_PASSWORD, "")
if (

View File

@ -1,11 +1,6 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER
from esphome.core import CORE
import esphome.codegen as cg
from esphome.components.esp32 import get_esp32_variant
from esphome.const import PLATFORM_ESP8266
from esphome.components.esp32.const import (
VARIANT_ESP32,
VARIANT_ESP32C2,
@ -15,6 +10,9 @@ from esphome.components.esp32.const import (
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
import esphome.config_validation as cv
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
from esphome.core import CORE
CODEOWNERS = ["@esphome/core"]
@ -102,11 +100,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
6: adc1_channel_t.ADC1_CHANNEL_6,
},
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,
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,
},
}

View File

@ -3,13 +3,12 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/voltage_sampler/voltage_sampler.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/hal.h"
#ifdef USE_ESP32
#include <esp_adc_cal.h>
#include "driver/adc.h"
#endif
#endif // USE_ESP32
namespace esphome {
namespace adc {
@ -43,7 +42,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
this->channel1_ = ADC1_CHANNEL_MAX;
}
void set_autorange(bool autorange) { this->autorange_ = autorange; }
#endif
#endif // USE_ESP32
/// Update ADC values
void update() override;
@ -59,11 +58,11 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_ESP8266
std::string unique_id() override;
#endif
#endif // USE_ESP8266
#ifdef USE_RP2040
void set_is_temperature() { this->is_temperature_ = true; }
#endif
#endif // USE_RP2040
protected:
InternalGPIOPin *pin_;
@ -72,7 +71,7 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
#ifdef USE_RP2040
bool is_temperature_{false};
#endif
#endif // USE_RP2040
#ifdef USE_ESP32
adc_atten_t attenuation_{ADC_ATTEN_DB_0};
@ -83,8 +82,8 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
esp_adc_cal_characteristics_t cal_characteristics_[SOC_ADC_ATTEN_NUM] = {};
#else
esp_adc_cal_characteristics_t cal_characteristics_[ADC_ATTEN_MAX] = {};
#endif
#endif
#endif // ESP_IDF_VERSION_MAJOR
#endif // USE_ESP32
};
} // namespace adc

View File

@ -0,0 +1,24 @@
#include "adc_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc {
static const char *const TAG = "adc.common";
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
void ADCSensor::set_sample_count(uint8_t sample_count) {
if (sample_count != 0) {
this->sample_count_ = sample_count;
}
}
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
} // namespace adc
} // namespace esphome

View File

@ -1,30 +1,13 @@
#ifdef USE_ESP32
#include "adc_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ESP8266
#ifdef USE_ADC_SENSOR_VCC
#include <Esp.h>
ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#endif
#endif
#ifdef USE_RP2040
#ifdef CYW43_USES_VSYS_PIN
#include "pico/cyw43_arch.h"
#endif
#include <hardware/adc.h>
#endif
namespace esphome {
namespace adc {
static const char *const TAG = "adc";
static const char *const TAG = "adc.esp32";
// 13-bit for S2, 12-bit for all other ESP32 variants
#ifdef USE_ESP32
static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_width_t>(ADC_WIDTH_MAX - 1);
#ifndef SOC_ADC_RTC_MAX_BITWIDTH
@ -32,24 +15,15 @@ static const adc_bits_width_t ADC_WIDTH_MAX_SOC_BITS = static_cast<adc_bits_widt
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 13;
#else
static const int32_t SOC_ADC_RTC_MAX_BITWIDTH = 12;
#endif
#endif
#endif // USE_ESP32_VARIANT_ESP32S2
#endif // SOC_ADC_RTC_MAX_BITWIDTH
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1; // 4095 (12 bit) or 8191 (13 bit)
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1; // 2048 (12 bit) or 4096 (13 bit)
#endif
static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;
static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;
#ifdef USE_RP2040
extern "C"
#endif
void
ADCSensor::setup() {
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
this->pin_->setup();
#endif
#ifdef USE_ESP32
if (this->channel1_ != ADC1_CHANNEL_MAX) {
adc1_config_width(ADC_WIDTH_MAX_SOC_BITS);
if (!this->autorange_) {
@ -61,7 +35,6 @@ extern "C"
}
}
// load characteristics for each attenuation
for (int32_t i = 0; i <= ADC_ATTEN_DB_12_COMPAT; i++) {
auto adc_unit = this->channel1_ != ADC1_CHANNEL_MAX ? ADC_UNIT_1 : ADC_UNIT_2;
auto cal_value = esp_adc_cal_characterize(adc_unit, (adc_atten_t) i, ADC_WIDTH_MAX_SOC_BITS,
@ -79,31 +52,10 @@ extern "C"
break;
}
}
#endif // USE_ESP32
#ifdef USE_RP2040
static bool initialized = false;
if (!initialized) {
adc_init();
initialized = true;
}
#endif
ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#if defined(USE_ESP8266) || defined(USE_LIBRETINY)
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif
#endif // USE_ESP8266 || USE_LIBRETINY
#ifdef USE_ESP32
LOG_PIN(" Pin: ", this->pin_);
if (this->autorange_) {
ESP_LOGCONFIG(TAG, " Attenuation: auto");
@ -125,55 +77,10 @@ void ADCSensor::dump_config() {
break;
}
}
#endif // USE_ESP32
#ifdef USE_RP2040
if (this->is_temperature_) {
ESP_LOGCONFIG(TAG, " Pin: Temperature");
} else {
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
}
#endif // USE_RP2040
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::get_setup_priority() const { return setup_priority::DATA; }
void ADCSensor::update() {
float value_v = this->sample();
ESP_LOGV(TAG, "'%s': Got voltage=%.4fV", this->get_name().c_str(), value_v);
this->publish_state(value_v);
}
void ADCSensor::set_sample_count(uint8_t sample_count) {
if (sample_count != 0) {
this->sample_count_ = sample_count;
}
}
#ifdef USE_ESP8266
float ADCSensor::sample() {
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
#ifdef USE_ADC_SENSOR_VCC
raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else
raw += analogRead(this->pin_->get_pin()); // NOLINT
#endif
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (this->output_raw_) {
return raw;
}
return raw / 1024.0f;
}
#endif
#ifdef USE_ESP32
float ADCSensor::sample() {
if (!this->autorange_) {
uint32_t sum = 0;
@ -240,93 +147,17 @@ float ADCSensor::sample() {
uint32_t mv2 = esp_adc_cal_raw_to_voltage(raw2, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_2_5]);
uint32_t mv0 = esp_adc_cal_raw_to_voltage(raw0, &this->cal_characteristics_[(int32_t) ADC_ATTEN_DB_0]);
// Contribution of each value, in range 0-2048 (12 bit ADC) or 0-4096 (13 bit ADC)
uint32_t c12 = std::min(raw12, ADC_HALF);
uint32_t c6 = ADC_HALF - std::abs(raw6 - ADC_HALF);
uint32_t c2 = ADC_HALF - std::abs(raw2 - ADC_HALF);
uint32_t c0 = std::min(ADC_MAX - raw0, ADC_HALF);
// max theoretical csum value is 4096*4 = 16384
uint32_t csum = c12 + c6 + c2 + c0;
// each mv is max 3900; so max value is 3900*4096*4, fits in unsigned32
uint32_t mv_scaled = (mv12 * c12) + (mv6 * c6) + (mv2 * c2) + (mv0 * c0);
return mv_scaled / (float) (csum * 1000U);
}
#endif // USE_ESP32
#ifdef USE_RP2040
float ADCSensor::sample() {
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true);
delay(1);
adc_select_input(4);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
adc_set_temp_sensor_enabled(false);
if (this->output_raw_) {
return raw;
}
return raw * 3.3f / 4096.0f;
} else {
uint8_t pin = this->pin_->get_pin();
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
// VSYS ADC both share GPIO29
cyw43_thread_enter();
}
#endif // CYW43_USES_VSYS_PIN
adc_gpio_init(pin);
adc_select_input(pin - 26);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
cyw43_thread_exit();
}
#endif // CYW43_USES_VSYS_PIN
if (this->output_raw_) {
return raw;
}
float coeff = pin == PICO_VSYS_PIN ? 3.0 : 1.0;
return raw * 3.3f / 4096.0f * coeff;
}
}
#endif
#ifdef USE_LIBRETINY
float ADCSensor::sample() {
uint32_t raw = 0;
if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogRead(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw;
}
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw / 1000.0f;
}
#endif // USE_LIBRETINY
#ifdef USE_ESP8266
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
#endif
} // namespace adc
} // namespace esphome
#endif // USE_ESP32

View File

@ -0,0 +1,58 @@
#ifdef USE_ESP8266
#include "adc_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#ifdef USE_ADC_SENSOR_VCC
#include <Esp.h>
ADC_MODE(ADC_VCC)
#else
#include <Arduino.h>
#endif // USE_ADC_SENSOR_VCC
namespace esphome {
namespace adc {
static const char *const TAG = "adc.esp8266";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
#ifdef USE_ADC_SENSOR_VCC
raw += ESP.getVcc(); // NOLINT(readability-static-accessed-through-instance)
#else
raw += analogRead(this->pin_->get_pin()); // NOLINT
#endif // USE_ADC_SENSOR_VCC
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
if (this->output_raw_) {
return raw;
}
return raw / 1024.0f;
}
std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
} // namespace adc
} // namespace esphome
#endif // USE_ESP8266

View File

@ -0,0 +1,48 @@
#ifdef USE_LIBRETINY
#include "adc_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace adc {
static const char *const TAG = "adc.libretiny";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
#ifndef USE_ADC_SENSOR_VCC
this->pin_->setup();
#endif // !USE_ADC_SENSOR_VCC
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else // USE_ADC_SENSOR_VCC
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
uint32_t raw = 0;
if (this->output_raw_) {
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogRead(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw;
}
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += analogReadVoltage(this->pin_->get_pin()); // NOLINT
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
return raw / 1000.0f;
}
} // namespace adc
} // namespace esphome
#endif // USE_LIBRETINY

View File

@ -0,0 +1,93 @@
#ifdef USE_RP2040
#include "adc_sensor.h"
#include "esphome/core/log.h"
#ifdef CYW43_USES_VSYS_PIN
#include "pico/cyw43_arch.h"
#endif // CYW43_USES_VSYS_PIN
#include <hardware/adc.h>
namespace esphome {
namespace adc {
static const char *const TAG = "adc.rp2040";
void ADCSensor::setup() {
ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
static bool initialized = false;
if (!initialized) {
adc_init();
initialized = true;
}
}
void ADCSensor::dump_config() {
LOG_SENSOR("", "ADC Sensor", this);
if (this->is_temperature_) {
ESP_LOGCONFIG(TAG, " Pin: Temperature");
} else {
#ifdef USE_ADC_SENSOR_VCC
ESP_LOGCONFIG(TAG, " Pin: VCC");
#else
LOG_PIN(" Pin: ", this->pin_);
#endif // USE_ADC_SENSOR_VCC
}
ESP_LOGCONFIG(TAG, " Samples: %i", this->sample_count_);
LOG_UPDATE_INTERVAL(this);
}
float ADCSensor::sample() {
if (this->is_temperature_) {
adc_set_temp_sensor_enabled(true);
delay(1);
adc_select_input(4);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
adc_set_temp_sensor_enabled(false);
if (this->output_raw_) {
return raw;
}
return raw * 3.3f / 4096.0f;
}
uint8_t pin = this->pin_->get_pin();
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
// Measuring VSYS on Raspberry Pico W needs to be wrapped with
// `cyw43_thread_enter()`/`cyw43_thread_exit()` as discussed in
// https://github.com/raspberrypi/pico-sdk/issues/1222, since Wifi chip and
// VSYS ADC both share GPIO29
cyw43_thread_enter();
}
#endif // CYW43_USES_VSYS_PIN
adc_gpio_init(pin);
adc_select_input(pin - 26);
uint32_t raw = 0;
for (uint8_t sample = 0; sample < this->sample_count_; sample++) {
raw += adc_read();
}
raw = (raw + (this->sample_count_ >> 1)) / this->sample_count_; // NOLINT(clang-analyzer-core.DivideZero)
#ifdef CYW43_USES_VSYS_PIN
if (pin == PICO_VSYS_PIN) {
cyw43_thread_exit();
}
#endif // CYW43_USES_VSYS_PIN
if (this->output_raw_) {
return raw;
}
float coeff = pin == PICO_VSYS_PIN ? 3.0f : 1.0f;
return raw * 3.3f / 4096.0f * coeff;
}
} // namespace adc
} // namespace esphome
#endif // USE_RP2040

View File

@ -72,10 +72,9 @@ void AlarmControlPanelCall::validate_() {
this->state_.reset();
return;
}
if (state == ACP_STATE_DISARMED &&
!(this->parent_->is_state_armed(this->parent_->get_state()) ||
this->parent_->get_state() == ACP_STATE_PENDING || this->parent_->get_state() == ACP_STATE_ARMING ||
this->parent_->get_state() == ACP_STATE_TRIGGERED)) {
if (state == ACP_STATE_DISARMED && !this->parent_->is_state_armed(this->parent_->get_state()) &&
this->parent_->get_state() != ACP_STATE_PENDING && this->parent_->get_state() != ACP_STATE_ARMING &&
this->parent_->get_state() != ACP_STATE_TRIGGERED) {
ESP_LOGW(TAG, "Cannot disarm when not armed");
this->state_.reset();
return;

View File

@ -2,7 +2,6 @@ import logging
from esphome import automation, core
import esphome.codegen as cg
from esphome.components import font
import esphome.components.image as espImage
from esphome.components.image import (
CONF_USE_TRANSPARENCY,
@ -131,7 +130,7 @@ ANIMATION_SCHEMA = cv.Schema(
)
)
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, ANIMATION_SCHEMA)
CONFIG_SCHEMA = ANIMATION_SCHEMA
NEXT_FRAME_SCHEMA = automation.maybe_simple_id(
{

View File

@ -122,7 +122,8 @@ void APDS9306::update() {
this->status_clear_warning();
if (!(status &= 0b00001000)) { // No new data
status &= 0b00001000;
if (!status) { // No new data
return;
}

View File

@ -15,7 +15,7 @@ from esphome.components.libretiny.const import (
)
from esphome.core import CORE
from .boards import BK72XX_BOARDS, BK72XX_BOARD_PINS
from .boards import BK72XX_BOARD_PINS, BK72XX_BOARDS
CODEOWNERS = ["@kuba2k2"]
AUTO_LOAD = ["libretiny"]

View File

@ -43,7 +43,7 @@ bool CSE7766Component::check_byte_() {
uint8_t index = this->raw_data_index_;
uint8_t byte = this->raw_data_[index];
if (index == 0) {
return !((byte != 0x55) && ((byte & 0xF0) != 0xF0) && (byte != 0xAA));
return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA);
}
if (index == 1) {

View File

@ -60,9 +60,7 @@ ESPTime DateTimeEntity::state_as_esptime() const {
obj.hour = this->hour_;
obj.minute = this->minute_;
obj.second = this->second_;
obj.day_of_week = 1; // Required to be valid for recalc_timestamp_local but not used.
obj.day_of_year = 1; // Required to be valid for recalc_timestamp_local but not used.
obj.recalc_timestamp_local(false);
obj.recalc_timestamp_local();
return obj;
}

View File

@ -30,6 +30,57 @@ static const char *const TAG = "debug";
std::string DebugComponent::get_reset_reason_() {
std::string reset_reason;
switch (esp_reset_reason()) {
case ESP_RST_POWERON:
reset_reason = "Reset due to power-on event";
break;
case ESP_RST_EXT:
reset_reason = "Reset by external pin";
break;
case ESP_RST_SW:
reset_reason = "Software reset via esp_restart";
break;
case ESP_RST_PANIC:
reset_reason = "Software reset due to exception/panic";
break;
case ESP_RST_INT_WDT:
reset_reason = "Reset (software or hardware) due to interrupt watchdog";
break;
case ESP_RST_TASK_WDT:
reset_reason = "Reset due to task watchdog";
break;
case ESP_RST_WDT:
reset_reason = "Reset due to other watchdogs";
break;
case ESP_RST_DEEPSLEEP:
reset_reason = "Reset after exiting deep sleep mode";
break;
case ESP_RST_BROWNOUT:
reset_reason = "Brownout reset (software or hardware)";
break;
case ESP_RST_SDIO:
reset_reason = "Reset over SDIO";
break;
#ifdef USE_ESP32_VARIANT_ESP32
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4))
case ESP_RST_USB:
reset_reason = "Reset by USB peripheral";
break;
case ESP_RST_JTAG:
reset_reason = "Reset by JTAG";
break;
case ESP_RST_EFUSE:
reset_reason = "Reset due to efuse error";
break;
case ESP_RST_PWR_GLITCH:
reset_reason = "Reset due to power glitch detected";
break;
case ESP_RST_CPU_LOCKUP:
reset_reason = "Reset due to CPU lock up (double exception)";
break;
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)
#endif // USE_ESP32_VARIANT_ESP32
default: // Includes ESP_RST_UNKNOWN
switch (rtc_get_reset_reason(0)) {
case POWERON_RESET:
reset_reason = "Power On Reset";
@ -134,6 +185,8 @@ std::string DebugComponent::get_reset_reason_() {
default:
reset_reason = "Unknown Reset Reason";
}
break;
}
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
return reset_reason;
}
@ -294,4 +347,4 @@ void DebugComponent::update_platform_() {
} // namespace debug
} // namespace esphome
#endif
#endif // USE_ESP32

View File

@ -52,11 +52,11 @@ void DeepSleepComponent::dump_config_platform_() {
bool DeepSleepComponent::prepare_to_sleep_() {
if (this->wakeup_pin_mode_ == WAKEUP_PIN_MODE_KEEP_AWAKE && this->wakeup_pin_ != nullptr &&
!this->sleep_duration_.has_value() && this->wakeup_pin_->digital_read()) {
this->wakeup_pin_->digital_read()) {
// Defer deep sleep until inactive
if (!this->next_enter_deep_sleep_) {
this->status_set_warning();
ESP_LOGW(TAG, "Waiting for pin_ to switch state to enter deep sleep...");
ESP_LOGW(TAG, "Waiting wakeup pin state change to enter deep sleep...");
}
this->next_enter_deep_sleep_ = true;
return false;

View File

@ -135,7 +135,8 @@ bool HOT IRAM_ATTR DHT::read_sensor_(float *temperature, float *humidity, bool r
// Wait for falling edge
while (this->pin_->digital_read()) {
if ((end_time = micros()) - start_time > 90) {
end_time = micros();
if (end_time - start_time > 90) {
if (i < 0) {
error_code = 3;
} else {

View File

@ -1,6 +1,6 @@
#include "display.h"
#include "display_color_utils.h"
#include <utility>
#include "display_color_utils.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
@ -662,20 +662,24 @@ void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
this->trigger(from, to);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
void Display::strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
ESPTime time) {
char buffer[64];
size_t ret = time.strftime(buffer, sizeof(buffer), format);
if (ret > 0)
this->print(x, y, font, color, align, buffer);
this->print(x, y, font, color, align, buffer, background);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, color, COLOR_OFF, align, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, Color color, const char *format, ESPTime time) {
this->strftime(x, y, font, color, TextAlign::TOP_LEFT, format, time);
this->strftime(x, y, font, color, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, TextAlign align, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, align, format, time);
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, align, format, time);
}
void Display::strftime(int x, int y, BaseFont *font, const char *format, ESPTime time) {
this->strftime(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, time);
this->strftime(x, y, font, COLOR_ON, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
}
void Display::start_clipping(Rect rect) {

View File

@ -437,6 +437,20 @@ class Display : public PollingComponent {
*/
void printf(int x, int y, BaseFont *font, const char *format, ...) __attribute__((format(printf, 5, 6)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.
* @param y The y coordinate of the text alignment anchor point.
* @param font The font to draw the text with.
* @param color The color to draw the text with.
* @param background The background color to draw the text with.
* @param align The alignment of the text.
* @param format The format to use.
* @param ... The arguments to use for the text formatting.
*/
void strftime(int x, int y, BaseFont *font, Color color, Color background, TextAlign align, const char *format,
ESPTime time) __attribute__((format(strftime, 8, 0)));
/** Evaluate the strftime-format `format` and print the result with the anchor point at [x,y] with `font`.
*
* @param x The x coordinate of the text alignment anchor point.

View File

@ -280,7 +280,7 @@ bool DisplayMenuComponent::cursor_down_() {
bool DisplayMenuComponent::enter_menu_() {
this->displayed_item_->on_leave();
this->displayed_item_ = static_cast<MenuItemMenu *>(this->get_selected_item_());
this->selection_stack_.push_front({this->top_index_, this->cursor_index_});
this->selection_stack_.emplace_front(this->top_index_, this->cursor_index_);
this->cursor_index_ = this->top_index_ = 0;
this->displayed_item_->on_enter();

View File

@ -296,7 +296,7 @@ void Dsmr::dump_config() {
}
void Dsmr::set_decryption_key(const std::string &decryption_key) {
if (decryption_key.length() == 0) {
if (decryption_key.empty()) {
ESP_LOGI(TAG, "Disabling decryption");
this->decryption_key_.clear();
if (this->crypt_telegram_ != nullptr) {

View File

@ -65,6 +65,8 @@ _LOGGER = logging.getLogger(__name__)
CODEOWNERS = ["@esphome/core"]
AUTO_LOAD = ["preferences"]
CONF_RELEASE = "release"
def set_core_data(config):
CORE.data[KEY_ESP32] = {}
@ -216,11 +218,17 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
def _format_framework_espidf_version(ver: cv.Version) -> str:
def _format_framework_espidf_version(
ver: cv.Version, release: str, for_platformio: bool
) -> str:
# format the given arduino (https://github.com/espressif/esp-idf/releases) version to
# a PIO platformio/framework-espidf value
# List of package versions: https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
return f"~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
if for_platformio:
return f"platformio/framework-espidf@~3.{ver.major}{ver.minor:02d}{ver.patch:02d}.0"
if release:
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}.{release}/esp-idf-v{str(ver)}.zip"
return f"pioarduino/framework-espidf@https://github.com/pioarduino/esp-idf/releases/download/v{str(ver)}/esp-idf-v{str(ver)}.zip"
# NOTE: Keep this in mind when updating the recommended version:
@ -241,11 +249,33 @@ ARDUINO_PLATFORM_VERSION = cv.Version(5, 4, 0)
# The default/recommended esp-idf framework version
# - https://github.com/espressif/esp-idf/releases
# - https://api.registry.platformio.org/v3/packages/platformio/tool/framework-espidf
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(4, 4, 8)
RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION = cv.Version(5, 1, 5)
# The platformio/espressif32 version to use for esp-idf frameworks
# - https://github.com/platformio/platform-espressif32/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/espressif32
ESP_IDF_PLATFORM_VERSION = cv.Version(5, 4, 0)
ESP_IDF_PLATFORM_VERSION = cv.Version(51, 3, 7)
# List based on https://registry.platformio.org/tools/platformio/framework-espidf/versions
SUPPORTED_PLATFORMIO_ESP_IDF_5X = [
cv.Version(5, 3, 1),
cv.Version(5, 3, 0),
cv.Version(5, 2, 2),
cv.Version(5, 2, 1),
cv.Version(5, 1, 2),
cv.Version(5, 1, 1),
cv.Version(5, 1, 0),
cv.Version(5, 0, 2),
cv.Version(5, 0, 1),
cv.Version(5, 0, 0),
]
# pioarduino versions that don't require a release number
# List based on https://github.com/pioarduino/esp-idf/releases
SUPPORTED_PIOARDUINO_ESP_IDF_5X = [
cv.Version(5, 3, 1),
cv.Version(5, 3, 0),
cv.Version(5, 1, 5),
]
def _arduino_check_versions(value):
@ -286,8 +316,8 @@ def _arduino_check_versions(value):
def _esp_idf_check_versions(value):
value = value.copy()
lookups = {
"dev": (cv.Version(5, 1, 2), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 2), None),
"dev": (cv.Version(5, 1, 5), "https://github.com/espressif/esp-idf.git"),
"latest": (cv.Version(5, 1, 5), None),
"recommended": (RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION, None),
}
@ -305,13 +335,51 @@ def _esp_idf_check_versions(value):
if version < cv.Version(4, 0, 0):
raise cv.Invalid("Only ESP-IDF 4.0+ is supported.")
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(version)
# flag this for later *before* we set value[CONF_PLATFORM_VERSION] below
has_platform_ver = CONF_PLATFORM_VERSION in value
value[CONF_PLATFORM_VERSION] = value.get(
CONF_PLATFORM_VERSION, _parse_platform_version(str(ESP_IDF_PLATFORM_VERSION))
)
if (
(is_platformio := _platform_is_platformio(value[CONF_PLATFORM_VERSION]))
and version.major >= 5
and version not in SUPPORTED_PLATFORMIO_ESP_IDF_5X
):
raise cv.Invalid(
f"ESP-IDF {str(version)} not supported by platformio/espressif32"
)
if (
version.major < 5
or (
version in SUPPORTED_PLATFORMIO_ESP_IDF_5X
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
)
) and not has_platform_ver:
raise cv.Invalid(
f"ESP-IDF {value[CONF_VERSION]} may be supported by platformio/espressif32; please specify '{CONF_PLATFORM_VERSION}'"
)
if (
not is_platformio
and CONF_RELEASE not in value
and version not in SUPPORTED_PIOARDUINO_ESP_IDF_5X
):
raise cv.Invalid(
f"ESP-IDF {value[CONF_VERSION]} is not available with pioarduino; you may need to specify '{CONF_RELEASE}'"
)
value[CONF_VERSION] = str(version)
value[CONF_SOURCE] = source or _format_framework_espidf_version(
version, value.get(CONF_RELEASE, None), is_platformio
)
if value[CONF_SOURCE].startswith("http"):
# prefix is necessary or platformio will complain with a cryptic error
value[CONF_SOURCE] = f"framework-espidf@{value[CONF_SOURCE]}"
if version != RECOMMENDED_ESP_IDF_FRAMEWORK_VERSION:
_LOGGER.warning(
"The selected ESP-IDF framework version is not the recommended one. "
@ -323,6 +391,12 @@ def _esp_idf_check_versions(value):
def _parse_platform_version(value):
try:
ver = cv.Version.parse(cv.version_number(value))
if ver.major >= 50: # a pioarduino version
if "-" in value:
# maybe a release candidate?...definitely not our default, just use it as-is...
return f"https://github.com/pioarduino/platform-espressif32.git#{value}"
return f"https://github.com/pioarduino/platform-espressif32.git#{ver.major}.{ver.minor:02d}.{ver.patch:02d}"
# if platform version is a valid version constraint, prefix the default package
cv.platformio_version_constraint(value)
return f"platformio/espressif32@{value}"
@ -330,6 +404,14 @@ def _parse_platform_version(value):
return value
def _platform_is_platformio(value):
try:
ver = cv.Version.parse(cv.version_number(value))
return ver.major < 50
except cv.Invalid:
return "platformio" in value
def _detect_variant(value):
board = value[CONF_BOARD]
if board in BOARDS:
@ -355,24 +437,20 @@ def _detect_variant(value):
def final_validate(config):
if CONF_PLATFORMIO_OPTIONS not in fv.full_config.get()[CONF_ESPHOME]:
if not (
pio_options := fv.full_config.get()[CONF_ESPHOME].get(CONF_PLATFORMIO_OPTIONS)
):
# Not specified or empty
return config
pio_flash_size_key = "board_upload.flash_size"
pio_partitions_key = "board_build.partitions"
if (
CONF_PARTITIONS in config
and pio_partitions_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
if CONF_PARTITIONS in config and pio_partitions_key in pio_options:
raise cv.Invalid(
f"Do not specify '{pio_partitions_key}' in '{CONF_PLATFORMIO_OPTIONS}' with '{CONF_PARTITIONS}' in esp32"
)
if (
pio_flash_size_key
in fv.full_config.get()[CONF_ESPHOME][CONF_PLATFORMIO_OPTIONS]
):
if pio_flash_size_key in pio_options:
raise cv.Invalid(
f"Please specify {CONF_FLASH_SIZE} within esp32 configuration only"
)
@ -412,6 +490,7 @@ ESP_IDF_FRAMEWORK_SCHEMA = cv.All(
cv.Schema(
{
cv.Optional(CONF_VERSION, default="recommended"): cv.string_strict,
cv.Optional(CONF_RELEASE): cv.string_strict,
cv.Optional(CONF_SOURCE): cv.string_strict,
cv.Optional(CONF_PLATFORM_VERSION): _parse_platform_version,
cv.Optional(CONF_SDKCONFIG_OPTIONS, default={}): {
@ -515,10 +594,9 @@ async def to_code(config):
cg.add_build_flag("-DUSE_ESP_IDF")
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ESP_IDF")
cg.add_build_flag("-Wno-nonnull-compare")
cg.add_platformio_option(
"platform_packages",
[f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
)
cg.add_platformio_option("platform_packages", [conf[CONF_SOURCE]])
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
# This is espressif's own published version which is more up to date.
cg.add_platformio_option(

View File

@ -1,4 +1,12 @@
from .const import VARIANT_ESP32, VARIANT_ESP32C3, VARIANT_ESP32S2, VARIANT_ESP32S3
from .const import (
VARIANT_ESP32,
VARIANT_ESP32C2,
VARIANT_ESP32C3,
VARIANT_ESP32C6,
VARIANT_ESP32H2,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
)
ESP32_BASE_PINS = {
"TX": 1,
@ -1344,6 +1352,26 @@ done | sort
"""
BOARDS = {
"4d_systems_esp32s3_gen4_r8n16": {
"name": "4D Systems GEN4-ESP32 16MB (ESP32S3-R8N16)",
"variant": VARIANT_ESP32S3,
},
"adafruit_camera_esp32s3": {
"name": "Adafruit pyCamera S3",
"variant": VARIANT_ESP32S3,
},
"adafruit_feather_esp32c6": {
"name": "Adafruit Feather ESP32-C6",
"variant": VARIANT_ESP32C6,
},
"adafruit_feather_esp32s2": {
"name": "Adafruit Feather ESP32-S2",
"variant": VARIANT_ESP32S2,
},
"adafruit_feather_esp32s2_reversetft": {
"name": "Adafruit Feather ESP32-S2 Reverse TFT",
"variant": VARIANT_ESP32S2,
},
"adafruit_feather_esp32s2_tft": {
"name": "Adafruit Feather ESP32-S2 TFT",
"variant": VARIANT_ESP32S2,
@ -1356,6 +1384,10 @@ BOARDS = {
"name": "Adafruit Feather ESP32-S3 No PSRAM",
"variant": VARIANT_ESP32S3,
},
"adafruit_feather_esp32s3_reversetft": {
"name": "Adafruit Feather ESP32-S3 Reverse TFT",
"variant": VARIANT_ESP32S3,
},
"adafruit_feather_esp32s3_tft": {
"name": "Adafruit Feather ESP32-S3 TFT",
"variant": VARIANT_ESP32S3,
@ -1376,10 +1408,18 @@ BOARDS = {
"name": "Adafruit MagTag 2.9",
"variant": VARIANT_ESP32S2,
},
"adafruit_matrixportal_esp32s3": {
"name": "Adafruit MatrixPortal ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"adafruit_metro_esp32s2": {
"name": "Adafruit Metro ESP32-S2",
"variant": VARIANT_ESP32S2,
},
"adafruit_metro_esp32s3": {
"name": "Adafruit Metro ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"adafruit_qtpy_esp32c3": {
"name": "Adafruit QT Py ESP32-C3",
"variant": VARIANT_ESP32C3,
@ -1392,10 +1432,18 @@ BOARDS = {
"name": "Adafruit QT Py ESP32-S2",
"variant": VARIANT_ESP32S2,
},
"adafruit_qtpy_esp32s3_n4r2": {
"name": "Adafruit QT Py ESP32-S3 (4M Flash 2M PSRAM)",
"variant": VARIANT_ESP32S3,
},
"adafruit_qtpy_esp32s3_nopsram": {
"name": "Adafruit QT Py ESP32-S3 No PSRAM",
"variant": VARIANT_ESP32S3,
},
"adafruit_qualia_s3_rgb666": {
"name": "Adafruit Qualia ESP32-S3 RGB666",
"variant": VARIANT_ESP32S3,
},
"airm2m_core_esp32c3": {
"name": "AirM2M CORE ESP32C3",
"variant": VARIANT_ESP32C3,
@ -1404,14 +1452,30 @@ BOARDS = {
"name": "ALKS ESP32",
"variant": VARIANT_ESP32,
},
"arduino_nano_esp32": {
"name": "Arduino Nano ESP32",
"variant": VARIANT_ESP32S3,
},
"atd147_s3": {
"name": "ArtronShop ATD1.47-S3",
"variant": VARIANT_ESP32S3,
},
"atmegazero_esp32s2": {
"name": "EspinalLab ATMegaZero ESP32-S2",
"variant": VARIANT_ESP32S2,
},
"aventen_s3_sync": {
"name": "Aventen S3 Sync",
"variant": VARIANT_ESP32S3,
},
"az-delivery-devkit-v4": {
"name": "AZ-Delivery ESP-32 Dev Kit C V4",
"variant": VARIANT_ESP32,
},
"bee_data_logger": {
"name": "Smart Bee Data Logger",
"variant": VARIANT_ESP32S3,
},
"bee_motion_mini": {
"name": "Smart Bee Motion Mini",
"variant": VARIANT_ESP32C3,
@ -1436,14 +1500,6 @@ BOARDS = {
"name": "BPI-Leaf-S3",
"variant": VARIANT_ESP32S3,
},
"briki_abc_esp32": {
"name": "Briki ABC (MBC-WB) - ESP32",
"variant": VARIANT_ESP32,
},
"briki_mbc-wb_esp32": {
"name": "Briki MBC-WB - ESP32",
"variant": VARIANT_ESP32,
},
"cnrs_aw2eth": {
"name": "CNRS AW2ETH",
"variant": VARIANT_ESP32,
@ -1496,18 +1552,38 @@ BOARDS = {
"name": "DFRobot Beetle ESP32-C3",
"variant": VARIANT_ESP32C3,
},
"dfrobot_firebeetle2_esp32e": {
"name": "DFRobot Firebeetle 2 ESP32-E",
"variant": VARIANT_ESP32,
},
"dfrobot_firebeetle2_esp32s3": {
"name": "DFRobot Firebeetle 2 ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"dfrobot_romeo_esp32s3": {
"name": "DFRobot Romeo ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"dpu_esp32": {
"name": "TAMC DPU ESP32",
"variant": VARIANT_ESP32,
},
"edgebox-esp-100": {
"name": "Seeed Studio Edgebox-ESP-100",
"variant": VARIANT_ESP32S3,
},
"esp320": {
"name": "Electronic SweetPeas ESP320",
"variant": VARIANT_ESP32,
},
"esp32-c2-devkitm-1": {
"name": "Espressif ESP32-C2-DevKitM-1",
"variant": VARIANT_ESP32C2,
},
"esp32-c3-devkitc-02": {
"name": "Espressif ESP32-C3-DevKitC-02",
"variant": VARIANT_ESP32C3,
},
"esp32-c3-devkitm-1": {
"name": "Espressif ESP32-C3-DevKitM-1",
"variant": VARIANT_ESP32C3,
@ -1516,6 +1592,14 @@ BOARDS = {
"name": "Ai-Thinker ESP-C3-M1-I-Kit",
"variant": VARIANT_ESP32C3,
},
"esp32-c6-devkitc-1": {
"name": "Espressif ESP32-C6-DevKitC-1",
"variant": VARIANT_ESP32C6,
},
"esp32-c6-devkitm-1": {
"name": "Espressif ESP32-C6-DevKitM-1",
"variant": VARIANT_ESP32C6,
},
"esp32cam": {
"name": "AI Thinker ESP32-CAM",
"variant": VARIANT_ESP32,
@ -1544,6 +1628,14 @@ BOARDS = {
"name": "OLIMEX ESP32-GATEWAY",
"variant": VARIANT_ESP32,
},
"esp32-h2-devkitm-1": {
"name": "Espressif ESP32-H2-DevKit",
"variant": VARIANT_ESP32H2,
},
"esp32-pico-devkitm-2": {
"name": "Espressif ESP32-PICO-DevKitM-2",
"variant": VARIANT_ESP32,
},
"esp32-poe-iso": {
"name": "OLIMEX ESP32-PoE-ISO",
"variant": VARIANT_ESP32,
@ -1580,10 +1672,22 @@ BOARDS = {
"name": "Espressif ESP32-S3-DevKitC-1-N8 (8 MB QD, No PSRAM)",
"variant": VARIANT_ESP32S3,
},
"esp32-s3-korvo-2": {
"name": "Espressif ESP32-S3-Korvo-2",
"esp32-s3-devkitm-1": {
"name": "Espressif ESP32-S3-DevKitM-1",
"variant": VARIANT_ESP32S3,
},
"esp32s3_powerfeather": {
"name": "ESP32-S3 PowerFeather",
"variant": VARIANT_ESP32S3,
},
"esp32s3usbotg": {
"name": "Espressif ESP32-S3-USB-OTG",
"variant": VARIANT_ESP32S3,
},
"esp32-solo1": {
"name": "Espressif Generic ESP32-solo1 4M Flash",
"variant": VARIANT_ESP32,
},
"esp32thing": {
"name": "SparkFun ESP32 Thing",
"variant": VARIANT_ESP32,
@ -1652,9 +1756,9 @@ BOARDS = {
"name": "Heltec WiFi Kit 32",
"variant": VARIANT_ESP32,
},
"heltec_wifi_kit_32_v2": {
"name": "Heltec WiFi Kit 32 (V2)",
"variant": VARIANT_ESP32,
"heltec_wifi_kit_32_V3": {
"name": "Heltec WiFi Kit 32 (V3)",
"variant": VARIANT_ESP32S3,
},
"heltec_wifi_lora_32": {
"name": "Heltec WiFi LoRa 32",
@ -1664,6 +1768,10 @@ BOARDS = {
"name": "Heltec WiFi LoRa 32 (V2)",
"variant": VARIANT_ESP32,
},
"heltec_wifi_lora_32_V3": {
"name": "Heltec WiFi LoRa 32 (V3)",
"variant": VARIANT_ESP32S3,
},
"heltec_wireless_stick_lite": {
"name": "Heltec Wireless Stick Lite",
"variant": VARIANT_ESP32,
@ -1708,6 +1816,14 @@ BOARDS = {
"name": "oddWires IoT-Bus Proteus",
"variant": VARIANT_ESP32,
},
"ioxesp32": {
"name": "ArtronShop IOXESP32",
"variant": VARIANT_ESP32,
},
"ioxesp32ps": {
"name": "ArtronShop IOXESP32PS",
"variant": VARIANT_ESP32,
},
"kb32-ft": {
"name": "MakerAsia KB32-FT",
"variant": VARIANT_ESP32,
@ -1720,10 +1836,26 @@ BOARDS = {
"name": "Labplus mPython",
"variant": VARIANT_ESP32,
},
"lilka_v2": {
"name": "Lilka v2",
"variant": VARIANT_ESP32S3,
},
"lilygo-t-display": {
"name": "LilyGo T-Display",
"variant": VARIANT_ESP32,
},
"lilygo-t-display-s3": {
"name": "LilyGo T-Display-S3",
"variant": VARIANT_ESP32S3,
},
"lionbit": {
"name": "Lion:Bit Dev Board",
"variant": VARIANT_ESP32,
},
"lionbits3": {
"name": "Lion:Bit S3 STEM Dev Board",
"variant": VARIANT_ESP32S3,
},
"lolin32_lite": {
"name": "WEMOS LOLIN32 Lite",
"variant": VARIANT_ESP32,
@ -1752,10 +1884,18 @@ BOARDS = {
"name": "WEMOS LOLIN S2 PICO",
"variant": VARIANT_ESP32S2,
},
"lolin_s3_mini": {
"name": "WEMOS LOLIN S3 Mini",
"variant": VARIANT_ESP32S3,
},
"lolin_s3": {
"name": "WEMOS LOLIN S3",
"variant": VARIANT_ESP32S3,
},
"lolin_s3_pro": {
"name": "WEMOS LOLIN S3 PRO",
"variant": VARIANT_ESP32S3,
},
"lopy4": {
"name": "Pycom LoPy4",
"variant": VARIANT_ESP32,
@ -1768,10 +1908,18 @@ BOARDS = {
"name": "M5Stack-ATOM",
"variant": VARIANT_ESP32,
},
"m5stack-atoms3": {
"name": "M5Stack AtomS3",
"variant": VARIANT_ESP32S3,
},
"m5stack-core2": {
"name": "M5Stack Core2",
"variant": VARIANT_ESP32,
},
"m5stack-core-esp32-16M": {
"name": "M5Stack Core ESP32 16M",
"variant": VARIANT_ESP32,
},
"m5stack-core-esp32": {
"name": "M5Stack Core ESP32",
"variant": VARIANT_ESP32,
@ -1780,6 +1928,10 @@ BOARDS = {
"name": "M5Stack-Core Ink",
"variant": VARIANT_ESP32,
},
"m5stack-cores3": {
"name": "M5Stack CoreS3",
"variant": VARIANT_ESP32S3,
},
"m5stack-fire": {
"name": "M5Stack FIRE",
"variant": VARIANT_ESP32,
@ -1788,6 +1940,14 @@ BOARDS = {
"name": "M5Stack GREY ESP32",
"variant": VARIANT_ESP32,
},
"m5stack_paper": {
"name": "M5Stack Paper",
"variant": VARIANT_ESP32,
},
"m5stack-stamps3": {
"name": "M5Stack StampS3",
"variant": VARIANT_ESP32S3,
},
"m5stack-station": {
"name": "M5Stack Station",
"variant": VARIANT_ESP32,
@ -1796,6 +1956,10 @@ BOARDS = {
"name": "M5Stack Timer CAM",
"variant": VARIANT_ESP32,
},
"m5stamp-pico": {
"name": "M5Stamp-Pico",
"variant": VARIANT_ESP32,
},
"m5stick-c": {
"name": "M5Stick-C",
"variant": VARIANT_ESP32,
@ -1832,10 +1996,26 @@ BOARDS = {
"name": "Deparment of Alchemy MiniMain ESP32-S2",
"variant": VARIANT_ESP32S2,
},
"motorgo_mini_1": {
"name": "MotorGo Mini 1 (ESP32-S3)",
"variant": VARIANT_ESP32S3,
},
"namino_arancio": {
"name": "Namino Arancio",
"variant": VARIANT_ESP32S3,
},
"namino_rosso": {
"name": "Namino Rosso",
"variant": VARIANT_ESP32S3,
},
"nano32": {
"name": "MakerAsia Nano32",
"variant": VARIANT_ESP32,
},
"nebulas3": {
"name": "Kinetic Dynamics Nebula S3",
"variant": VARIANT_ESP32S3,
},
"nina_w10": {
"name": "u-blox NINA-W10 series",
"variant": VARIANT_ESP32,
@ -1896,10 +2076,22 @@ BOARDS = {
"name": "Munich Labs RedPill ESP32-S3",
"variant": VARIANT_ESP32S3,
},
"roboheart_hercules": {
"name": "RoboHeart Hercules",
"variant": VARIANT_ESP32,
},
"seeed_xiao_esp32c3": {
"name": "Seeed Studio XIAO ESP32C3",
"variant": VARIANT_ESP32C3,
},
"seeed_xiao_esp32s3": {
"name": "Seeed Studio XIAO ESP32S3",
"variant": VARIANT_ESP32S3,
},
"sensebox_mcu_esp32s2": {
"name": "senseBox MCU-S2 ESP32-S2",
"variant": VARIANT_ESP32S2,
},
"sensesiot_weizen": {
"name": "LOGISENSES Senses Weizen",
"variant": VARIANT_ESP32,
@ -1912,6 +2104,10 @@ BOARDS = {
"name": "S.ODI Ultra v1",
"variant": VARIANT_ESP32,
},
"sparkfun_esp32c6_thing_plus": {
"name": "Sparkfun ESP32-C6 Thing Plus",
"variant": VARIANT_ESP32C6,
},
"sparkfun_esp32_iot_redboard": {
"name": "SparkFun ESP32 IoT RedBoard",
"variant": VARIANT_ESP32,
@ -2004,6 +2200,10 @@ BOARDS = {
"name": "Unexpected Maker FeatherS3",
"variant": VARIANT_ESP32S3,
},
"um_nanos3": {
"name": "Unexpected Maker NanoS3",
"variant": VARIANT_ESP32S3,
},
"um_pros3": {
"name": "Unexpected Maker PROS3",
"variant": VARIANT_ESP32S3,
@ -2040,6 +2240,14 @@ BOARDS = {
"name": "uPesy ESP32 Wrover DevKit",
"variant": VARIANT_ESP32,
},
"valtrack_v4_mfw_esp32_c3": {
"name": "Valetron Systems VALTRACK-V4MVF",
"variant": VARIANT_ESP32C3,
},
"valtrack_v4_vts_esp32_c3": {
"name": "Valetron Systems VALTRACK-V4VTS",
"variant": VARIANT_ESP32C3,
},
"vintlabs-devkit-v1": {
"name": "VintLabs ESP32 Devkit",
"variant": VARIANT_ESP32,

View File

@ -2,8 +2,10 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components.esp32 import add_idf_sdkconfig_option, const, get_esp32_variant
import esphome.config_validation as cv
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ID
from esphome.const import CONF_ENABLE_ON_BOOT, CONF_ESPHOME, CONF_ID, CONF_NAME
from esphome.core import CORE
from esphome.core.config import CONF_NAME_ADD_MAC_SUFFIX
import esphome.final_validate as fv
DEPENDENCIES = ["esp32"]
CODEOWNERS = ["@jesserockz", "@Rapsssito"]
@ -50,6 +52,7 @@ TX_POWER_LEVELS = {
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(ESP32BLE),
cv.Optional(CONF_NAME): cv.All(cv.string, cv.Length(max=20)),
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
IO_CAPABILITY, lower=True
),
@ -67,7 +70,22 @@ def validate_variant(_):
raise cv.Invalid(f"{variant} does not support Bluetooth")
FINAL_VALIDATE_SCHEMA = validate_variant
def final_validation(config):
validate_variant(config)
if (name := config.get(CONF_NAME)) is not None:
full_config = fv.full_config.get()
max_length = 20
if full_config[CONF_ESPHOME][CONF_NAME_ADD_MAC_SUFFIX]:
max_length -= 7 # "-AABBCC" is appended when add mac suffix option is used
if len(name) > max_length:
raise cv.Invalid(
f"Name '{name}' is too long, maximum length is {max_length} characters"
)
return config
FINAL_VALIDATE_SCHEMA = final_validation
async def to_code(config):
@ -75,6 +93,8 @@ async def to_code(config):
cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
cg.add(var.set_advertising_cycle_time(config[CONF_ADVERTISING_CYCLE_TIME]))
if (name := config.get(CONF_NAME)) is not None:
cg.add(var.set_name(name))
await cg.register_component(var, config)
if CORE.using_esp_idf:

View File

@ -27,6 +27,9 @@ namespace esp32_ble {
static const char *const TAG = "esp32_ble";
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
void ESP32BLE::setup() {
global_ble = this;
ESP_LOGCONFIG(TAG, "Setting up BLE...");
@ -188,7 +191,14 @@ bool ESP32BLE::ble_setup_() {
}
}
std::string name = App.get_name();
std::string name;
if (this->name_.has_value()) {
name = this->name_.value();
if (App.is_name_add_mac_suffix_enabled()) {
name += "-" + get_mac_address().substr(6);
}
} else {
name = App.get_name();
if (name.length() > 20) {
if (App.is_name_add_mac_suffix_enabled()) {
name.erase(name.begin() + 13, name.end() - 7); // Remove characters between 13 and the mac address
@ -196,6 +206,7 @@ bool ESP32BLE::ble_setup_() {
name = name.substr(0, 20);
}
}
}
err = esp_ble_gap_set_device_name(name.c_str());
if (err != ESP_OK) {
@ -314,7 +325,8 @@ void ESP32BLE::loop() {
default:
break;
}
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
ble_event->~BLEEvent();
EVENT_ALLOCATOR.deallocate(ble_event, 1);
ble_event = this->ble_events_.pop();
}
if (this->advertising_ != nullptr) {
@ -323,9 +335,14 @@ void ESP32BLE::loop() {
}
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
BLEEvent *new_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory)
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
if (new_event == nullptr) {
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
return;
}
new (new_event) BLEEvent(event, param);
global_ble->ble_events_.push(new_event);
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
} // NOLINT(clang-analyzer-unix.Malloc)
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
@ -336,9 +353,14 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
BLEEvent *new_event = new BLEEvent(event, gatts_if, param); // NOLINT(cppcoreguidelines-owning-memory)
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
if (new_event == nullptr) {
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
return;
}
new (new_event) BLEEvent(event, gatts_if, param);
global_ble->ble_events_.push(new_event);
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
} // NOLINT(clang-analyzer-unix.Malloc)
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t *param) {
@ -350,9 +372,14 @@ void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {
BLEEvent *new_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory)
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
if (new_event == nullptr) {
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
return;
}
new (new_event) BLEEvent(event, gattc_if, param);
global_ble->ble_events_.push(new_event);
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
} // NOLINT(clang-analyzer-unix.Malloc)
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
esp_ble_gattc_cb_param_t *param) {

View File

@ -90,6 +90,7 @@ class ESP32BLE : public Component {
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
void set_name(const std::string &name) { this->name_ = name; }
void advertising_start();
void advertising_set_service_data(const std::vector<uint8_t> &data);
@ -131,6 +132,7 @@ class ESP32BLE : public Component {
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
uint32_t advertising_cycle_time_;
bool enable_on_boot_;
optional<std::string> name_;
};
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -83,7 +83,7 @@ esp_err_t BLEAdvertising::services_advertisement_() {
esp_err_t err;
this->advertising_data_.set_scan_rsp = false;
this->advertising_data_.include_name = !this->scan_response_;
this->advertising_data_.include_name = true;
this->advertising_data_.include_txpower = !this->scan_response_;
err = esp_ble_gap_config_adv_data(&this->advertising_data_);
if (err != ESP_OK) {

View File

@ -34,7 +34,7 @@ ESPBTUUID ESPBTUUID::from_raw(const uint8_t *data) {
ESPBTUUID ESPBTUUID::from_raw_reversed(const uint8_t *data) {
ESPBTUUID ret;
ret.uuid_.len = ESP_UUID_LEN_128;
for (int i = 0; i < ESP_UUID_LEN_128; i++)
for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++)
ret.uuid_.uuid.uuid128[ESP_UUID_LEN_128 - 1 - i] = data[i];
return ret;
}
@ -43,30 +43,30 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
if (data.length() == 4) {
ret.uuid_.len = ESP_UUID_LEN_16;
ret.uuid_.uuid.uuid16 = 0;
for (int i = 0; i < data.length();) {
for (uint i = 0; i < data.length(); i += 2) {
uint8_t msb = data.c_str()[i];
uint8_t lsb = data.c_str()[i + 1];
uint8_t lsb_shift = i <= 2 ? (2 - i) * 4 : 0;
if (msb > '9')
msb -= 7;
if (lsb > '9')
lsb -= 7;
ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (2 - i) * 4;
i += 2;
ret.uuid_.uuid.uuid16 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift;
}
} else if (data.length() == 8) {
ret.uuid_.len = ESP_UUID_LEN_32;
ret.uuid_.uuid.uuid32 = 0;
for (int i = 0; i < data.length();) {
for (uint i = 0; i < data.length(); i += 2) {
uint8_t msb = data.c_str()[i];
uint8_t lsb = data.c_str()[i + 1];
uint8_t lsb_shift = i <= 6 ? (6 - i) * 4 : 0;
if (msb > '9')
msb -= 7;
if (lsb > '9')
lsb -= 7;
ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << (6 - i) * 4;
i += 2;
ret.uuid_.uuid.uuid32 += (((msb & 0x0F) << 4) | (lsb & 0x0F)) << lsb_shift;
}
} else if (data.length() == 16) { // how we can have 16 byte length string reprezenting 128 bit uuid??? needs to be
// investigated (lack of time)
@ -77,7 +77,7 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
// UUID format.
ret.uuid_.len = ESP_UUID_LEN_128;
int n = 0;
for (int i = 0; i < data.length();) {
for (uint i = 0; i < data.length(); i += 2) {
if (data.c_str()[i] == '-')
i++;
uint8_t msb = data.c_str()[i];
@ -88,7 +88,6 @@ ESPBTUUID ESPBTUUID::from_raw(const std::string &data) {
if (lsb > '9')
lsb -= 7;
ret.uuid_.uuid.uuid128[15 - n++] = ((msb & 0x0F) << 4) | (lsb & 0x0F);
i += 2;
}
} else {
ESP_LOGE(TAG, "ERROR: UUID value not 2, 4, 16 or 36 bytes - %s", data.c_str());
@ -155,7 +154,7 @@ bool ESPBTUUID::operator==(const ESPBTUUID &uuid) const {
}
break;
case ESP_UUID_LEN_128:
for (int i = 0; i < ESP_UUID_LEN_128; i++) {
for (uint8_t i = 0; i < ESP_UUID_LEN_128; i++) {
if (uuid.uuid_.uuid.uuid128[i] != this->uuid_.uuid.uuid128[i]) {
return false;
}

View File

@ -432,7 +432,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "Parse Result:");
const char *address_type = "";
const char *address_type;
switch (this->address_type_) {
case BLE_ADDR_TYPE_PUBLIC:
address_type = "PUBLIC";
@ -446,6 +446,9 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e
case BLE_ADDR_TYPE_RPA_RANDOM:
address_type = "RPA_RANDOM";
break;
default:
address_type = "UNKNOWN";
break;
}
ESP_LOGVV(TAG, " Address: %02X:%02X:%02X:%02X:%02X:%02X (%s)", this->address_[0], this->address_[1],
this->address_[2], this->address_[3], this->address_[4], this->address_[5], address_type);

View File

@ -11,7 +11,7 @@
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
struct httpd_req;
struct httpd_req; // NOLINT(readability-identifier-naming)
namespace esphome {
namespace esp32_camera_web_server {

View File

@ -1,7 +1,8 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import esp32
import esphome.config_validation as cv
from esphome.const import KEY_CORE, KEY_FRAMEWORK_VERSION
from esphome.core import CORE
CODEOWNERS = ["@jesserockz"]
@ -36,8 +37,32 @@ RMT_CHANNEL_ENUMS = {
}
def validate_rmt_channel(*, tx: bool):
def use_new_rmt_driver():
framework_version = CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]
if CORE.using_esp_idf and framework_version >= cv.Version(5, 0, 0):
return True
return False
def validate_clock_resolution():
def _validator(value):
cv.only_on_esp32(value)
value = cv.int_(value)
variant = esp32.get_esp32_variant()
if variant == esp32.const.VARIANT_ESP32H2 and value > 32000000:
raise cv.Invalid(
f"ESP32 variant {variant} has a max clock_resolution of 32000000."
)
if value > 80000000:
raise cv.Invalid(
f"ESP32 variant {variant} has a max clock_resolution of 80000000."
)
return value
return _validator
def validate_rmt_channel(*, tx: bool):
rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS
def _validator(value):

View File

@ -1,5 +1,5 @@
#include <cinttypes>
#include "led_strip.h"
#include <cinttypes>
#ifdef USE_ESP32
@ -13,9 +13,13 @@ namespace esp32_rmt_led_strip {
static const char *const TAG = "esp32_rmt_led_strip";
#ifdef USE_ESP32_VARIANT_ESP32H2
static const uint32_t RMT_CLK_FREQ = 32000000;
static const uint8_t RMT_CLK_DIV = 1;
#else
static const uint32_t RMT_CLK_FREQ = 80000000;
static const uint8_t RMT_CLK_DIV = 2;
#endif
void ESP32RMTLEDStripLightOutput::setup() {
ESP_LOGCONFIG(TAG, "Setting up ESP32 LED Strip...");
@ -37,9 +41,48 @@ void ESP32RMTLEDStripLightOutput::setup() {
return;
}
#if ESP_IDF_VERSION_MAJOR >= 5
RAMAllocator<rmt_symbol_word_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_symbol_word_t>::ALLOC_INTERNAL);
// 8 bits per byte, 1 rmt_symbol_word_t per bit + 1 rmt_symbol_word_t for reset
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
rmt_tx_channel_config_t channel;
memset(&channel, 0, sizeof(channel));
channel.clk_src = RMT_CLK_SRC_DEFAULT;
channel.resolution_hz = RMT_CLK_FREQ / RMT_CLK_DIV;
channel.gpio_num = gpio_num_t(this->pin_);
channel.mem_block_symbols = this->rmt_symbols_;
channel.trans_queue_depth = 1;
channel.flags.io_loop_back = 0;
channel.flags.io_od_mode = 0;
channel.flags.invert_out = 0;
channel.flags.with_dma = 0;
channel.intr_priority = 0;
if (rmt_new_tx_channel(&channel, &this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Channel creation failed");
this->mark_failed();
return;
}
rmt_copy_encoder_config_t encoder;
memset(&encoder, 0, sizeof(encoder));
if (rmt_new_copy_encoder(&encoder, &this->encoder_) != ESP_OK) {
ESP_LOGE(TAG, "Encoder creation failed");
this->mark_failed();
return;
}
if (rmt_enable(this->channel_) != ESP_OK) {
ESP_LOGE(TAG, "Enabling channel failed");
this->mark_failed();
return;
}
#else
RAMAllocator<rmt_item32_t> rmt_allocator(this->use_psram_ ? 0 : RAMAllocator<rmt_item32_t>::ALLOC_INTERNAL);
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 +
1); // 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset
// 8 bits per byte, 1 rmt_item32_t per bit + 1 rmt_item32_t for reset
this->rmt_buf_ = rmt_allocator.allocate(buffer_size * 8 + 1);
rmt_config_t config;
memset(&config, 0, sizeof(config));
@ -64,6 +107,7 @@ void ESP32RMTLEDStripLightOutput::setup() {
this->mark_failed();
return;
}
#endif
}
void ESP32RMTLEDStripLightOutput::set_led_params(uint32_t bit0_high, uint32_t bit0_low, uint32_t bit1_high,
@ -100,7 +144,12 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
ESP_LOGVV(TAG, "Writing RGB values to bus...");
if (rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000)) != ESP_OK) {
#if ESP_IDF_VERSION_MAJOR >= 5
esp_err_t error = rmt_tx_wait_all_done(this->channel_, 1000);
#else
esp_err_t error = rmt_wait_tx_done(this->channel_, pdMS_TO_TICKS(1000));
#endif
if (error != ESP_OK) {
ESP_LOGE(TAG, "RMT TX timeout");
this->status_set_warning();
return;
@ -112,7 +161,11 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
size_t size = 0;
size_t len = 0;
uint8_t *psrc = this->buf_;
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_symbol_word_t *pdest = this->rmt_buf_;
#else
rmt_item32_t *pdest = this->rmt_buf_;
#endif
while (size < buffer_size) {
uint8_t b = *psrc;
for (int i = 0; i < 8; i++) {
@ -130,7 +183,16 @@ void ESP32RMTLEDStripLightOutput::write_state(light::LightState *state) {
len++;
}
if (rmt_write_items(this->channel_, this->rmt_buf_, len, false) != ESP_OK) {
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_transmit_config_t config;
memset(&config, 0, sizeof(config));
config.loop_count = 0;
config.flags.eot_level = 0;
error = rmt_transmit(this->channel_, this->encoder_, this->rmt_buf_, len * sizeof(rmt_symbol_word_t), &config);
#else
error = rmt_write_items(this->channel_, this->rmt_buf_, len, false);
#endif
if (error != ESP_OK) {
ESP_LOGE(TAG, "RMT TX error");
this->status_set_warning();
return;
@ -186,7 +248,11 @@ light::ESPColorView ESP32RMTLEDStripLightOutput::get_view_internal(int32_t index
void ESP32RMTLEDStripLightOutput::dump_config() {
ESP_LOGCONFIG(TAG, "ESP32 RMT LED Strip:");
ESP_LOGCONFIG(TAG, " Pin: %u", this->pin_);
#if ESP_IDF_VERSION_MAJOR >= 5
ESP_LOGCONFIG(TAG, " RMT Symbols: %" PRIu32, this->rmt_symbols_);
#else
ESP_LOGCONFIG(TAG, " Channel: %u", this->channel_);
#endif
const char *rgb_order;
switch (this->rgb_order_) {
case ORDER_RGB:

View File

@ -9,8 +9,14 @@
#include "esphome/core/helpers.h"
#include <driver/gpio.h>
#include <driver/rmt.h>
#include <esp_err.h>
#include <esp_idf_version.h>
#if ESP_IDF_VERSION_MAJOR >= 5
#include <driver/rmt_tx.h>
#else
#include <driver/rmt.h>
#endif
namespace esphome {
namespace esp32_rmt_led_strip {
@ -54,7 +60,11 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint32_t reset_time_high, uint32_t reset_time_low);
void set_rgb_order(RGBOrder rgb_order) { this->rgb_order_ = rgb_order; }
#if ESP_IDF_VERSION_MAJOR >= 5
void set_rmt_symbols(uint32_t rmt_symbols) { this->rmt_symbols_ = rmt_symbols; }
#else
void set_rmt_channel(rmt_channel_t channel) { this->channel_ = channel; }
#endif
void clear_effect_data() override {
for (int i = 0; i < this->size(); i++)
@ -70,7 +80,17 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
uint8_t *buf_{nullptr};
uint8_t *effect_data_{nullptr};
#if ESP_IDF_VERSION_MAJOR >= 5
rmt_channel_handle_t channel_{nullptr};
rmt_encoder_handle_t encoder_{nullptr};
rmt_symbol_word_t *rmt_buf_{nullptr};
rmt_symbol_word_t bit0_, bit1_, reset_;
uint32_t rmt_symbols_;
#else
rmt_item32_t *rmt_buf_{nullptr};
rmt_item32_t bit0_, bit1_, reset_;
rmt_channel_t channel_{RMT_CHANNEL_0};
#endif
uint8_t pin_;
uint16_t num_leds_;
@ -78,9 +98,7 @@ class ESP32RMTLEDStripLightOutput : public light::AddressableLight {
bool is_wrgb_;
bool use_psram_;
rmt_item32_t bit0_, bit1_, reset_;
RGBOrder rgb_order_;
rmt_channel_t channel_;
uint32_t last_refresh_{0};
optional<uint32_t> max_refresh_rate_{};

View File

@ -1,9 +1,9 @@
from dataclasses import dataclass
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
import esphome.codegen as cg
from esphome.components import esp32_rmt, light
import esphome.config_validation as cv
from esphome.const import (
CONF_CHIPSET,
CONF_IS_RGBW,
@ -13,6 +13,7 @@ from esphome.const import (
CONF_PIN,
CONF_RGB_ORDER,
CONF_RMT_CHANNEL,
CONF_RMT_SYMBOLS,
)
CODEOWNERS = ["@jesserockz"]
@ -23,8 +24,6 @@ ESP32RMTLEDStripLightOutput = esp32_rmt_led_strip_ns.class_(
"ESP32RMTLEDStripLightOutput", light.AddressableLight
)
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
RGBOrder = esp32_rmt_led_strip_ns.enum("RGBOrder")
RGB_ORDERS = {
@ -65,6 +64,13 @@ CONF_RESET_HIGH = "reset_high"
CONF_RESET_LOW = "reset_low"
def final_validation(config):
if not esp32_rmt.use_new_rmt_driver() and CONF_RMT_CHANNEL not in config:
raise cv.Invalid("rmt_channel is a required option.")
FINAL_VALIDATE_SCHEMA = final_validation
CONFIG_SCHEMA = cv.All(
light.ADDRESSABLE_LIGHT_SCHEMA.extend(
{
@ -72,7 +78,18 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
cv.Required(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True),
cv.Optional(CONF_RMT_CHANNEL): cv.All(
cv.only_with_arduino, esp32_rmt.validate_rmt_channel(tx=True)
),
cv.SplitDefault(
CONF_RMT_SYMBOLS,
esp32_idf=64,
esp32_s2_idf=64,
esp32_s3_idf=48,
esp32_c3_idf=48,
esp32_c6_idf=48,
esp32_h2_idf=48,
): cv.All(cv.only_with_esp_idf, cv.int_range(min=2)),
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,
@ -103,7 +120,7 @@ CONFIG_SCHEMA = cv.All(
default="0 us",
): cv.positive_time_period_nanoseconds,
}
),
).extend(cv.COMPONENT_SCHEMA),
cv.has_exactly_one_key(CONF_CHIPSET, CONF_BIT0_HIGH),
)
@ -148,6 +165,10 @@ async def to_code(config):
cg.add(var.set_is_wrgb(config[CONF_IS_WRGB]))
cg.add(var.set_use_psram(config[CONF_USE_PSRAM]))
if esp32_rmt.use_new_rmt_driver():
cg.add(var.set_rmt_symbols(config[CONF_RMT_SYMBOLS]))
else:
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
cg.add(
var.set_rmt_channel(
getattr(rmt_channel_t, f"RMT_CHANNEL_{config[CONF_RMT_CHANNEL]}")

View File

@ -1,10 +1,13 @@
import logging
import os
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import (
CONF_BOARD,
CONF_BOARD_FLASH_MODE,
CONF_FRAMEWORK,
CONF_PLATFORM_VERSION,
CONF_SOURCE,
CONF_VERSION,
KEY_CORE,
@ -12,27 +15,22 @@ from esphome.const import (
KEY_TARGET_FRAMEWORK,
KEY_TARGET_PLATFORM,
PLATFORM_ESP8266,
CONF_PLATFORM_VERSION,
)
from esphome.core import CORE, coroutine_with_priority
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.helpers import copy_file_if_changed
from .boards import BOARDS, ESP8266_LD_SCRIPTS
from .const import (
CONF_RESTORE_FROM_FLASH,
CONF_EARLY_PIN_INIT,
CONF_RESTORE_FROM_FLASH,
KEY_BOARD,
KEY_ESP8266,
KEY_FLASH_SIZE,
KEY_PIN_INITIAL_STATES,
esp8266_ns,
)
from .boards import BOARDS, ESP8266_LD_SCRIPTS
from .gpio import PinInitialState, add_pin_initial_states_array
CODEOWNERS = ["@esphome/core"]
_LOGGER = logging.getLogger(__name__)
AUTO_LOAD = ["preferences"]

View File

@ -111,11 +111,11 @@ void EZOSensor::loop() {
if (buf[0] == 1) {
std::string payload = reinterpret_cast<char *>(&buf[1]);
if (!payload.empty()) {
auto start_location = payload.find(',');
switch (to_run->command_type) {
case EzoCommandType::EZO_READ: {
// some sensors return multiple comma-separated values, terminate string after first one
int start_location = 0;
if ((start_location = payload.find(',')) != std::string::npos) {
if (start_location != std::string::npos) {
payload.erase(start_location);
}
auto val = parse_number<float>(payload);
@ -126,49 +126,37 @@ void EZOSensor::loop() {
}
break;
}
case EzoCommandType::EZO_LED: {
case EzoCommandType::EZO_LED:
this->led_callback_.call(payload.back() == '1');
break;
}
case EzoCommandType::EZO_DEVICE_INFORMATION: {
int start_location = 0;
if ((start_location = payload.find(',')) != std::string::npos) {
case EzoCommandType::EZO_DEVICE_INFORMATION:
if (start_location != std::string::npos) {
this->device_infomation_callback_.call(payload.substr(start_location + 1));
}
break;
}
case EzoCommandType::EZO_SLOPE: {
int start_location = 0;
if ((start_location = payload.find(',')) != std::string::npos) {
case EzoCommandType::EZO_SLOPE:
if (start_location != std::string::npos) {
this->slope_callback_.call(payload.substr(start_location + 1));
}
break;
}
case EzoCommandType::EZO_CALIBRATION: {
int start_location = 0;
if ((start_location = payload.find(',')) != std::string::npos) {
case EzoCommandType::EZO_CALIBRATION:
if (start_location != std::string::npos) {
this->calibration_callback_.call(payload.substr(start_location + 1));
}
break;
}
case EzoCommandType::EZO_T: {
int start_location = 0;
if ((start_location = payload.find(',')) != std::string::npos) {
case EzoCommandType::EZO_T:
if (start_location != std::string::npos) {
this->t_callback_.call(payload.substr(start_location + 1));
}
break;
}
case EzoCommandType::EZO_CUSTOM: {
case EzoCommandType::EZO_CUSTOM:
this->custom_callback_.call(payload);
break;
}
default: {
default:
break;
}
}
}
}
this->commands_.pop_front();
}
@ -178,7 +166,7 @@ void EZOSensor::add_command_(const std::string &command, EzoCommandType command_
ezo_command->command_type = command_type;
ezo_command->delay_ms = delay_ms;
this->commands_.push_back(std::move(ezo_command));
};
}
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
std::string payload = str_sprintf("Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);

View File

@ -1,4 +1,3 @@
from collections.abc import Iterable
import functools
import hashlib
import logging
@ -8,7 +7,6 @@ import re
import freetype
import glyphsets
from packaging import version
import requests
from esphome import core, external_files
@ -53,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
# Cache loaded freetype fonts
class FontCache(dict):
def __missing__(self, key):
try:
res = self[key] = freetype.Face(key)
return res
except freetype.FT_Exception as e:
raise cv.Invalid(f"Could not load Font file {key}: {e}") from e
FONT_CACHE = FontCache()
@ -88,7 +89,7 @@ def flatten(lists) -> list:
return list(chain.from_iterable(lists))
def check_missing_glyphs(file, codepoints: Iterable, warning: bool = False):
def check_missing_glyphs(file, codepoints, warning: bool = False):
"""
Check that the given font file actually contains the requested glyphs
:param file: A Truetype font file
@ -177,24 +178,6 @@ def validate_glyphs(config):
return config
def validate_pillow_installed(value):
try:
import PIL
except ImportError as err:
raise cv.Invalid(
"Please install the pillow python package to use this feature. "
'(pip install "pillow==10.4.0")'
) from err
if version.parse(PIL.__version__) != version.parse("10.4.0"):
raise cv.Invalid(
"Please update your pillow installation to 10.4.0. "
'(pip install "pillow==10.4.0")'
)
return value
FONT_EXTENSIONS = (".ttf", ".woff", ".otf")
@ -393,7 +376,9 @@ def font_file_schema(value):
# Default if no glyphs or glyphsets are provided
DEFAULT_GLYPHSET = "GF_Latin_Kernel"
# default for bitmap fonts
DEFAULT_GLYPHS = ' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz<C2><B0>'
DEFAULT_GLYPHS = (
' !"%()+=,-.:/?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
)
CONF_RAW_GLYPH_ID = "raw_glyph_id"
@ -421,7 +406,7 @@ FONT_SCHEMA = cv.Schema(
},
)
CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA, validate_glyphs)
CONFIG_SCHEMA = cv.All(FONT_SCHEMA, validate_glyphs)
# PIL doesn't provide a consistent interface for both TrueType and bitmap

View File

@ -133,9 +133,11 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
auto diff_r = (float) color.r - (float) background.r;
auto diff_g = (float) color.g - (float) background.g;
auto diff_b = (float) color.b - (float) background.b;
auto diff_w = (float) color.w - (float) background.w;
auto b_r = (float) background.r;
auto b_g = (float) background.g;
auto b_b = (float) background.g;
auto b_b = (float) background.b;
auto b_w = (float) background.w;
for (int glyph_y = y_start + scan_y1; glyph_y != max_y; glyph_y++) {
for (int glyph_x = x_at + scan_x1; glyph_x != max_x; glyph_x++) {
uint8_t pixel = 0;
@ -153,8 +155,8 @@ void Font::print(int x_start, int y_start, display::Display *display, Color colo
display->draw_pixel_at(glyph_x, glyph_y, color);
} else if (pixel != 0) {
auto on = (float) pixel / (float) bpp_max;
auto blended =
Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g), (uint8_t) (diff_b * on + b_b));
auto blended = Color((uint8_t) (diff_r * on + b_r), (uint8_t) (diff_g * on + b_g),
(uint8_t) (diff_b * on + b_b), (uint8_t) (diff_w * on + b_w));
display->draw_pixel_at(glyph_x, glyph_y, blended);
}
}

View File

@ -35,7 +35,9 @@ void HonClimate::set_beeper_state(bool state) {
if (state != this->settings_.beeper_state) {
this->settings_.beeper_state = state;
#ifdef USE_SWITCH
if (this->beeper_switch_ != nullptr) {
this->beeper_switch_->publish_state(state);
}
#endif
this->hon_rtc_.save(&this->settings_);
}
@ -45,10 +47,17 @@ bool HonClimate::get_beeper_state() const { return this->settings_.beeper_state;
void HonClimate::set_quiet_mode_state(bool state) {
if (state != this->get_quiet_mode_state()) {
if ((this->mode != ClimateMode::CLIMATE_MODE_OFF) && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
this->quiet_mode_state_ = state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->force_send_control_ = true;
} else {
this->quiet_mode_state_ = state ? SwitchState::ON : SwitchState::OFF;
}
this->settings_.quiet_mode_state = state;
#ifdef USE_SWITCH
if (this->quiet_mode_switch_ != nullptr) {
this->quiet_mode_switch_->publish_state(state);
}
#endif
this->hon_rtc_.save(&this->settings_);
}
@ -509,7 +518,7 @@ void HonClimate::initialization() {
}
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::PENDING_ON : SwitchState::PENDING_OFF;
this->quiet_mode_state_ = this->settings_.quiet_mode_state ? SwitchState::ON : SwitchState::OFF;
}
haier_protocol::HaierMessage HonClimate::get_control_message() {
@ -932,7 +941,7 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
if (this->mode == CLIMATE_MODE_OFF) {
// AC just turned on from remote need to turn off display
this->force_send_control_ = true;
} else if ((((uint8_t) this->health_mode_) & 0b10) == 0) {
} else if ((((uint8_t) this->display_status_) & 0b10) == 0) {
this->display_status_ = disp_status ? SwitchState::ON : SwitchState::OFF;
}
}
@ -1004,6 +1013,11 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
if (new_quiet_mode != this->get_quiet_mode_state()) {
this->quiet_mode_state_ = new_quiet_mode ? SwitchState::ON : SwitchState::OFF;
this->settings_.quiet_mode_state = new_quiet_mode;
#ifdef USE_SWITCH
if (this->quiet_mode_switch_ != nullptr) {
this->quiet_mode_switch_->publish_state(new_quiet_mode);
}
#endif // USE_SWITCH
this->hon_rtc_.save(&this->settings_);
}
}
@ -1069,19 +1083,17 @@ void HonClimate::fill_control_messages_queue_() {
climate_control = this->current_hvac_settings_;
// Beeper command
{
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::BEEPER_STATUS,
this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2));
this->get_beeper_state() ? ZERO_BUF : ONE_BUF, 2);
}
// Health mode
{
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::HEALTH_MODE,
this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2));
this->get_health_mode() ? ONE_BUF : ZERO_BUF, 2);
this->health_mode_ = (SwitchState) ((uint8_t) this->health_mode_ & 0b01);
}
// Climate mode
@ -1099,51 +1111,46 @@ void HonClimate::fill_control_messages_queue_() {
case CLIMATE_MODE_HEAT_COOL:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::AUTO;
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::AC_MODE,
buffer, 2));
buffer, 2);
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
case CLIMATE_MODE_HEAT:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::HEAT;
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::AC_MODE,
buffer, 2));
buffer, 2);
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
case CLIMATE_MODE_DRY:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::DRY;
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::AC_MODE,
buffer, 2));
buffer, 2);
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
case CLIMATE_MODE_FAN_ONLY:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::FAN;
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::AC_MODE,
buffer, 2));
buffer, 2);
fan_mode_buf[1] = this->other_modes_fan_speed_; // Auto doesn't work in fan only mode
break;
case CLIMATE_MODE_COOL:
new_power = true;
buffer[1] = (uint8_t) hon_protocol::ConditioningMode::COOL;
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::AC_MODE,
buffer, 2));
buffer, 2);
fan_mode_buf[1] = this->other_modes_fan_speed_;
break;
default:
@ -1153,11 +1160,10 @@ void HonClimate::fill_control_messages_queue_() {
}
// Climate power
{
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::AC_POWER,
new_power ? ONE_BUF : ZERO_BUF, 2));
new_power ? ONE_BUF : ZERO_BUF, 2);
}
// CLimate preset
{
@ -1199,36 +1205,32 @@ void HonClimate::fill_control_messages_queue_() {
}
auto presets = this->traits_.get_supported_presets();
if (quiet_mode_buf[1] != 0xFF) {
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::QUIET_MODE,
quiet_mode_buf, 2));
quiet_mode_buf, 2);
}
if ((fast_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_BOOST) != presets.end()))) {
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::FAST_MODE,
fast_mode_buf, 2));
fast_mode_buf, 2);
}
if ((away_mode_buf[1] != 0xFF) && ((presets.find(climate::ClimatePreset::CLIMATE_PRESET_AWAY) != presets.end()))) {
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::TEN_DEGREE,
away_mode_buf, 2));
away_mode_buf, 2);
}
}
// Target temperature
if (climate_control.target_temperature.has_value() && (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)) {
uint8_t buffer[2] = {0x00, 0x00};
buffer[1] = ((uint8_t) climate_control.target_temperature.value()) - 16;
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::SET_POINT,
buffer, 2));
buffer, 2);
}
// Vertical swing mode
if (climate_control.swing_mode.has_value()) {
@ -1248,16 +1250,14 @@ void HonClimate::fill_control_messages_queue_() {
case CLIMATE_SWING_BOTH:
break;
}
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::HORIZONTAL_SWING_MODE,
horizontal_swing_buf, 2));
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
horizontal_swing_buf, 2);
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::VERTICAL_SWING_MODE,
vertical_swing_buf, 2));
vertical_swing_buf, 2);
}
// Fan mode
if (climate_control.fan_mode.has_value()) {
@ -1280,11 +1280,10 @@ void HonClimate::fill_control_messages_queue_() {
break;
}
if (fan_mode_buf[1] != 0xFF) {
this->control_messages_queue_.push(
haier_protocol::HaierMessage(haier_protocol::FrameType::CONTROL,
this->control_messages_queue_.emplace(haier_protocol::FrameType::CONTROL,
(uint16_t) hon_protocol::SubcommandsControl::SET_SINGLE_PARAMETER +
(uint8_t) hon_protocol::DataParameters::FAN_MODE,
fan_mode_buf, 2));
fan_mode_buf, 2);
}
}
}

View File

@ -0,0 +1,44 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_OPTIMISTIC, CONF_PULSE_LENGTH, CONF_WAIT_TIME
from .. import hbridge_ns
HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component)
CODEOWNERS = ["@dwmw2"]
CONF_OFF_PIN = "off_pin"
CONF_ON_PIN = "on_pin"
CONFIG_SCHEMA = (
switch.switch_schema(HBridgeSwitch)
.extend(
{
cv.Required(CONF_ON_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_OFF_PIN): pins.gpio_output_pin_schema,
cv.Optional(
CONF_PULSE_LENGTH, default="100ms"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_WAIT_TIME): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = await switch.new_switch(config)
await cg.register_component(var, config)
on_pin = await cg.gpio_pin_expression(config[CONF_ON_PIN])
cg.add(var.set_on_pin(on_pin))
off_pin = await cg.gpio_pin_expression(config[CONF_OFF_PIN])
cg.add(var.set_off_pin(off_pin))
cg.add(var.set_pulse_length(config[CONF_PULSE_LENGTH]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if wait_time := config.get(CONF_WAIT_TIME):
cg.add(var.set_wait_time(wait_time))

View File

@ -0,0 +1,95 @@
#include "hbridge_switch.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace hbridge {
static const char *const TAG = "switch.hbridge";
float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
void HBridgeSwitch::setup() {
ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str());
optional<bool> initial_state = this->get_initial_state_with_restore_mode().value_or(false);
// Like GPIOSwitch does, set the pin state both before and after pin setup()
this->on_pin_->digital_write(false);
this->on_pin_->setup();
this->on_pin_->digital_write(false);
this->off_pin_->digital_write(false);
this->off_pin_->setup();
this->off_pin_->digital_write(false);
if (initial_state.has_value())
this->write_state(initial_state);
}
void HBridgeSwitch::dump_config() {
LOG_SWITCH("", "H-Bridge Switch", this);
LOG_PIN(" On Pin: ", this->on_pin_);
LOG_PIN(" Off Pin: ", this->off_pin_);
ESP_LOGCONFIG(TAG, " Pulse length: %" PRId32 " ms", this->pulse_length_);
if (this->wait_time_)
ESP_LOGCONFIG(TAG, " Wait time %" PRId32 " ms", this->wait_time_);
}
void HBridgeSwitch::write_state(bool state) {
this->desired_state_ = state;
if (!this->timer_running_)
this->timer_fn_();
}
void HBridgeSwitch::timer_fn_() {
uint32_t next_timeout = 0;
while ((uint8_t) this->desired_state_ != this->relay_state_) {
switch (this->relay_state_) {
case RELAY_STATE_ON:
case RELAY_STATE_OFF:
case RELAY_STATE_UNKNOWN:
if (this->desired_state_) {
this->on_pin_->digital_write(true);
this->relay_state_ = RELAY_STATE_SWITCHING_ON;
} else {
this->off_pin_->digital_write(true);
this->relay_state_ = RELAY_STATE_SWITCHING_OFF;
}
next_timeout = this->pulse_length_;
if (!this->optimistic_)
this->publish_state(this->desired_state_);
break;
case RELAY_STATE_SWITCHING_ON:
this->on_pin_->digital_write(false);
this->relay_state_ = RELAY_STATE_ON;
if (this->optimistic_)
this->publish_state(true);
next_timeout = this->wait_time_;
break;
case RELAY_STATE_SWITCHING_OFF:
this->off_pin_->digital_write(false);
this->relay_state_ = RELAY_STATE_OFF;
if (this->optimistic_)
this->publish_state(false);
next_timeout = this->wait_time_;
break;
}
if (next_timeout) {
this->timer_running_ = true;
this->set_timeout(next_timeout, [this]() { this->timer_fn_(); });
return;
}
// In the case where ON/OFF state has been reached but we need to
// immediately change back again to reach desired_state_, we loop.
}
this->timer_running_ = false;
}
} // namespace hbridge
} // namespace esphome

View File

@ -0,0 +1,50 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/switch/switch.h"
#include <vector>
namespace esphome {
namespace hbridge {
enum RelayState : uint8_t {
RELAY_STATE_OFF = 0,
RELAY_STATE_ON = 1,
RELAY_STATE_SWITCHING_ON = 2,
RELAY_STATE_SWITCHING_OFF = 3,
RELAY_STATE_UNKNOWN = 4,
};
class HBridgeSwitch : public switch_::Switch, public Component {
public:
void set_on_pin(GPIOPin *pin) { this->on_pin_ = pin; }
void set_off_pin(GPIOPin *pin) { this->off_pin_ = pin; }
void set_pulse_length(uint32_t pulse_length) { this->pulse_length_ = pulse_length; }
void set_wait_time(uint32_t wait_time) { this->wait_time_ = wait_time; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
float get_setup_priority() const override;
void setup() override;
void dump_config() override;
protected:
void write_state(bool state) override;
void timer_fn_();
bool timer_running_{false};
bool desired_state_{false};
RelayState relay_state_{RELAY_STATE_UNKNOWN};
GPIOPin *on_pin_{nullptr};
GPIOPin *off_pin_{nullptr};
uint32_t pulse_length_{0};
uint32_t wait_time_{0};
bool optimistic_{false};
};
} // namespace hbridge
} // namespace esphome

View File

@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() {
}
void HitachiClimate::set_swing_h_(uint8_t position) {
if (position > HITACHI_AC344_SWINGH_LEFT_MAX)
return set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
if (position > HITACHI_AC344_SWINGH_LEFT_MAX) {
set_swing_h_(HITACHI_AC344_SWINGH_MIDDLE);
return;
}
set_bits(&remote_state_[HITACHI_AC344_SWINGH_BYTE], HITACHI_AC344_SWINGH_OFFSET, HITACHI_AC344_SWINGH_SIZE, position);
set_button_(HITACHI_AC344_BUTTON_SWINGH);
}

View File

@ -133,8 +133,10 @@ bool HitachiClimate::get_swing_v_() {
}
void HitachiClimate::set_swing_h_(uint8_t position) {
if (position > HITACHI_AC424_SWINGH_LEFT_MAX)
return set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE);
if (position > HITACHI_AC424_SWINGH_LEFT_MAX) {
set_swing_h_(HITACHI_AC424_SWINGH_MIDDLE);
return;
}
set_bits(&remote_state_[HITACHI_AC424_SWINGH_BYTE], HITACHI_AC424_SWINGH_OFFSET, HITACHI_AC424_SWINGH_SIZE, position);
set_button_(HITACHI_AC424_BUTTON_SWINGH);
}

View File

@ -27,6 +27,7 @@ void HomeassistantNumber::min_retrieved_(const std::string &min) {
auto min_value = parse_number<float>(min);
if (!min_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'min' value '%s' to number!", this->entity_id_.c_str(), min.c_str());
return;
}
ESP_LOGD(TAG, "'%s': Min retrieved: %s", get_name().c_str(), min.c_str());
this->traits.set_min_value(min_value.value());
@ -36,6 +37,7 @@ void HomeassistantNumber::max_retrieved_(const std::string &max) {
auto max_value = parse_number<float>(max);
if (!max_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'max' value '%s' to number!", this->entity_id_.c_str(), max.c_str());
return;
}
ESP_LOGD(TAG, "'%s': Max retrieved: %s", get_name().c_str(), max.c_str());
this->traits.set_max_value(max_value.value());
@ -45,6 +47,7 @@ void HomeassistantNumber::step_retrieved_(const std::string &step) {
auto step_value = parse_number<float>(step);
if (!step_value.has_value()) {
ESP_LOGE(TAG, "'%s': Can't convert 'step' value '%s' to number!", this->entity_id_.c_str(), step.c_str());
return;
}
ESP_LOGD(TAG, "'%s': Step Retrieved %s", get_name().c_str(), step.c_str());
this->traits.set_step(step_value.value());

View File

@ -15,7 +15,7 @@ static const char *const TAG = "honeywellabp2";
void HONEYWELLABP2Sensor::read_sensor_data() {
if (this->read(raw_data_, 7) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with ABP2 failed!");
this->mark_failed();
this->status_set_warning("couldn't read sensor data");
return;
}
float press_counts = encode_uint24(raw_data_[1], raw_data_[2], raw_data_[3]); // calculate digital pressure counts
@ -25,12 +25,13 @@ void HONEYWELLABP2Sensor::read_sensor_data() {
(this->max_pressure_ - this->min_pressure_)) +
this->min_pressure_;
this->last_temperature_ = (temp_counts * 200 / 16777215) - 50;
this->status_clear_warning();
}
void HONEYWELLABP2Sensor::start_measurement() {
if (this->write(i2c_cmd_, 3) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with ABP2 failed!");
this->mark_failed();
this->status_set_warning("couldn't start measurement");
return;
}
this->measurement_running_ = true;
@ -39,7 +40,7 @@ void HONEYWELLABP2Sensor::start_measurement() {
bool HONEYWELLABP2Sensor::is_measurement_ready() {
if (this->read(raw_data_, 1) != i2c::ERROR_OK) {
ESP_LOGE(TAG, "Communication with ABP2 failed!");
this->mark_failed();
this->status_set_warning("couldn't check measurement");
return false;
}
if ((raw_data_[0] & (0x1 << STATUS_BIT_BUSY)) > 0) {
@ -52,7 +53,7 @@ bool HONEYWELLABP2Sensor::is_measurement_ready() {
void HONEYWELLABP2Sensor::measurement_timeout() {
ESP_LOGE(TAG, "Timeout!");
this->measurement_running_ = false;
this->mark_failed();
this->status_set_warning("measurement timed out");
}
float HONEYWELLABP2Sensor::get_pressure() { return this->last_pressure_; }
@ -79,7 +80,7 @@ void HONEYWELLABP2Sensor::update() {
ESP_LOGV(TAG, "Update Honeywell ABP2 Sensor");
this->start_measurement();
this->set_timeout("meas_timeout", 50, [this] { this->measurement_timeout(); });
this->set_timeout("meas_timeout", 100, [this] { this->measurement_timeout(); });
}
void HONEYWELLABP2Sensor::dump_config() {

View File

@ -53,7 +53,7 @@ bool HX711Sensor::read_sensor_(uint32_t *result) {
}
// Cycle clock pin for gain setting
for (uint8_t i = 0; i < this->gain_; i++) {
for (uint8_t i = 0; i < static_cast<uint8_t>(this->gain_); i++) {
this->sck_pin_->digital_write(true);
delayMicroseconds(1);
this->sck_pin_->digital_write(false);

View File

@ -9,7 +9,7 @@
namespace esphome {
namespace hx711 {
enum HX711Gain {
enum HX711Gain : uint8_t {
HX711_GAIN_128 = 1,
HX711_GAIN_32 = 2,
HX711_GAIN_64 = 3,

View File

@ -17,14 +17,14 @@ void IDFI2CBus::setup() {
ESP_LOGCONFIG(TAG, "Setting up I2C bus...");
static i2c_port_t next_port = I2C_NUM_0;
port_ = next_port;
#if I2C_NUM_MAX > 1
#if SOC_I2C_NUM > 1
next_port = (next_port == I2C_NUM_0) ? I2C_NUM_1 : I2C_NUM_MAX;
#else
next_port = I2C_NUM_MAX;
#endif
if (port_ == I2C_NUM_MAX) {
ESP_LOGE(TAG, "Too many I2C buses configured");
ESP_LOGE(TAG, "Too many I2C buses configured. Max %u supported.", SOC_I2C_NUM);
this->mark_failed();
return;
}

View File

@ -33,14 +33,15 @@ enum SpeakerEventGroupBits : uint32_t {
STATE_RUNNING = (1 << 11),
STATE_STOPPING = (1 << 12),
STATE_STOPPED = (1 << 13),
ERR_INVALID_FORMAT = (1 << 14),
ERR_TASK_FAILED_TO_START = (1 << 15),
ERR_ESP_INVALID_STATE = (1 << 16),
ERR_TASK_FAILED_TO_START = (1 << 14),
ERR_ESP_INVALID_STATE = (1 << 15),
ERR_ESP_NOT_SUPPORTED = (1 << 16),
ERR_ESP_INVALID_ARG = (1 << 17),
ERR_ESP_INVALID_SIZE = (1 << 18),
ERR_ESP_NO_MEM = (1 << 19),
ERR_ESP_FAIL = (1 << 20),
ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE | ERR_ESP_NO_MEM | ERR_ESP_FAIL,
ALL_ERR_ESP_BITS = ERR_ESP_INVALID_STATE | ERR_ESP_NOT_SUPPORTED | ERR_ESP_INVALID_ARG | ERR_ESP_INVALID_SIZE |
ERR_ESP_NO_MEM | ERR_ESP_FAIL,
ALL_BITS = 0x00FFFFFF, // All valid FreeRTOS event group bits
};
@ -55,6 +56,8 @@ static esp_err_t err_bit_to_esp_err(uint32_t bit) {
return ESP_ERR_INVALID_SIZE;
case SpeakerEventGroupBits::ERR_ESP_NO_MEM:
return ESP_ERR_NO_MEM;
case SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED:
return ESP_ERR_NOT_SUPPORTED;
default:
return ESP_FAIL;
}
@ -135,19 +138,19 @@ void I2SAudioSpeaker::loop() {
xEventGroupClearBits(this->event_group_, SpeakerEventGroupBits::ERR_TASK_FAILED_TO_START);
}
if (event_group_bits & SpeakerEventGroupBits::ERR_INVALID_FORMAT) {
if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
this->status_set_warning();
}
if (event_group_bits & SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED) {
this->status_set_error("Failed to adjust I2S bus to match the incoming audio");
ESP_LOGE(TAG,
"Incompatible audio format: sample rate = %" PRIu32 ", channels = %" PRIu8 ", bits per sample = %" PRIu8,
this->audio_stream_info_.sample_rate, this->audio_stream_info_.channels,
this->audio_stream_info_.bits_per_sample);
}
if (event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS) {
uint32_t error_bits = event_group_bits & SpeakerEventGroupBits::ALL_ERR_ESP_BITS;
ESP_LOGW(TAG, "Error writing to I2S: %s", esp_err_to_name(err_bit_to_esp_err(error_bits)));
this->status_set_warning();
}
}
void I2SAudioSpeaker::set_volume(float volume) {
@ -236,13 +239,15 @@ void I2SAudioSpeaker::speaker_task(void *params) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STARTING);
audio::AudioStreamInfo audio_stream_info = this_speaker->audio_stream_info_;
const ssize_t bytes_per_sample = audio_stream_info.get_bytes_per_sample();
const uint8_t number_of_channels = audio_stream_info.channels;
const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * this_speaker->sample_rate_ / 1000 *
bytes_per_sample * number_of_channels;
const uint32_t bytes_per_ms =
audio_stream_info.channels * audio_stream_info.get_bytes_per_sample() * audio_stream_info.sample_rate / 1000;
const size_t dma_buffers_size = DMA_BUFFERS_COUNT * DMA_BUFFER_DURATION_MS * bytes_per_ms;
// Ensure ring buffer is at least as large as the total size of the DMA buffers
const size_t ring_buffer_size =
this_speaker->buffer_duration_ms_ * this_speaker->sample_rate_ / 1000 * bytes_per_sample * number_of_channels;
std::max((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms);
if (this_speaker->send_esp_err_to_event_group_(this_speaker->allocate_buffers_(dma_buffers_size, ring_buffer_size))) {
// Failed to allocate buffers
@ -250,14 +255,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
this_speaker->delete_task_(dma_buffers_size);
}
if (this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_())) {
// Failed to start I2S driver
this_speaker->delete_task_(dma_buffers_size);
}
if (!this_speaker->send_esp_err_to_event_group_(this_speaker->reconfigure_i2s_stream_info_(audio_stream_info))) {
// Successfully set the I2S stream info, ready to write audio data to the I2S port
if (!this_speaker->send_esp_err_to_event_group_(this_speaker->start_i2s_driver_(audio_stream_info))) {
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_RUNNING);
bool stop_gracefully = false;
@ -275,6 +273,12 @@ void I2SAudioSpeaker::speaker_task(void *params) {
stop_gracefully = true;
}
if (this_speaker->audio_stream_info_ != audio_stream_info) {
// Audio stream info has changed, stop the speaker task so it will restart with the proper settings.
break;
}
i2s_event_t i2s_event;
while (xQueueReceive(this_speaker->i2s_event_queue_, &i2s_event, 0)) {
if (i2s_event.type == I2S_EVENT_TX_Q_OVF) {
@ -316,17 +320,14 @@ void I2SAudioSpeaker::speaker_task(void *params) {
}
}
}
} else {
// Couldn't configure the I2S port to be compatible with the incoming audio
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::ERR_INVALID_FORMAT);
}
i2s_zero_dma_buffer(this_speaker->parent_->get_port());
xEventGroupSetBits(this_speaker->event_group_, SpeakerEventGroupBits::STATE_STOPPING);
i2s_driver_uninstall(this_speaker->parent_->get_port());
this_speaker->parent_->unlock();
}
this_speaker->delete_task_(dma_buffers_size);
}
@ -382,6 +383,9 @@ bool I2SAudioSpeaker::send_esp_err_to_event_group_(esp_err_t err) {
case ESP_ERR_NO_MEM:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NO_MEM);
return true;
case ESP_ERR_NOT_SUPPORTED:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_NOT_SUPPORTED);
return true;
default:
xEventGroupSetBits(this->event_group_, SpeakerEventGroupBits::ERR_ESP_FAIL);
return true;
@ -411,18 +415,40 @@ esp_err_t I2SAudioSpeaker::allocate_buffers_(size_t data_buffer_size, size_t rin
return ESP_OK;
}
esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
esp_err_t I2SAudioSpeaker::start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info) {
if ((this->i2s_mode_ & I2S_MODE_SLAVE) && (this->sample_rate_ != audio_stream_info.sample_rate)) { // NOLINT
// Can't reconfigure I2S bus, so the sample rate must match the configured value
return ESP_ERR_NOT_SUPPORTED;
}
if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) {
// Currently can't handle the case when the incoming audio has more bits per sample than the configured value
return ESP_ERR_NOT_SUPPORTED;
}
if (!this->parent_->try_lock()) {
return ESP_ERR_INVALID_STATE;
}
i2s_channel_fmt_t channel = this->channel_;
if (audio_stream_info.channels == 1) {
if (this->channel_ == I2S_CHANNEL_FMT_ONLY_LEFT) {
channel = I2S_CHANNEL_FMT_ONLY_LEFT;
} else {
channel = I2S_CHANNEL_FMT_ONLY_RIGHT;
}
} else if (audio_stream_info.channels == 2) {
channel = I2S_CHANNEL_FMT_RIGHT_LEFT;
}
int dma_buffer_length = DMA_BUFFER_DURATION_MS * this->sample_rate_ / 1000;
i2s_driver_config_t config = {
.mode = (i2s_mode_t) (this->i2s_mode_ | I2S_MODE_TX),
.sample_rate = this->sample_rate_,
.sample_rate = audio_stream_info.sample_rate,
.bits_per_sample = this->bits_per_sample_,
.channel_format = this->channel_,
.channel_format = channel,
.communication_format = this->i2s_comm_fmt_,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
.dma_buf_count = DMA_BUFFERS_COUNT,
@ -477,30 +503,6 @@ esp_err_t I2SAudioSpeaker::start_i2s_driver_() {
return err;
}
esp_err_t I2SAudioSpeaker::reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info) {
if (this->i2s_mode_ & I2S_MODE_MASTER) {
// ESP controls for the the I2S bus, so adjust the sample rate and bits per sample to match the incoming audio
this->sample_rate_ = audio_stream_info.sample_rate;
this->bits_per_sample_ = (i2s_bits_per_sample_t) audio_stream_info.bits_per_sample;
} else if (this->sample_rate_ != audio_stream_info.sample_rate) {
// Can't reconfigure I2S bus, so the sample rate must match the configured value
return ESP_ERR_INVALID_ARG;
}
if ((i2s_bits_per_sample_t) audio_stream_info.bits_per_sample > this->bits_per_sample_) {
// Currently can't handle the case when the incoming audio has more bits per sample than the configured value
return ESP_ERR_INVALID_ARG;
}
if (audio_stream_info.channels == 1) {
return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_MONO);
} else if (audio_stream_info.channels == 2) {
return i2s_set_clk(this->parent_->get_port(), this->sample_rate_, this->bits_per_sample_, I2S_CHANNEL_STEREO);
}
return ESP_ERR_INVALID_ARG;
}
void I2SAudioSpeaker::delete_task_(size_t buffer_size) {
this->audio_ring_buffer_.reset(); // Releases onwership of the shared_ptr

View File

@ -91,24 +91,15 @@ class I2SAudioSpeaker : public I2SAudioOut, public speaker::Speaker, public Comp
esp_err_t allocate_buffers_(size_t data_buffer_size, size_t ring_buffer_size);
/// @brief Starts the ESP32 I2S driver.
/// Attempts to lock the I2S port, starts the I2S driver, and sets the data out pin. If it fails, it will unlock
/// the I2S port and uninstall the driver, if necessary.
/// @return ESP_ERR_INVALID_STATE if the I2S port is already locked.
/// ESP_ERR_INVALID_ARG if installing the driver or setting the data out pin fails due to a parameter error.
/// Attempts to lock the I2S port, starts the I2S driver using the passed in stream information, and sets the data out
/// pin. If it fails, it will unlock the I2S port and uninstall the driver, if necessary.
/// @param audio_stream_info Stream information for the I2S driver.
/// @return ESP_ERR_NOT_ALLOWED if the I2S port can't play the incoming audio stream.
/// ESP_ERR_INVALID_STATE if the I2S port is already locked.
/// ESP_ERR_INVALID_ARG if nstalling the driver or setting the data outpin fails due to a parameter error.
/// ESP_ERR_NO_MEM if the driver fails to install due to a memory allocation error.
/// ESP_FAIL if setting the data out pin fails due to an IO error
/// ESP_OK if successful
esp_err_t start_i2s_driver_();
/// @brief Adjusts the I2S driver configuration to match the incoming audio stream.
/// Modifies I2S driver's sample rate, bits per sample, and number of channel settings. If the I2S is in secondary
/// mode, it only modifies the number of channels.
/// @param audio_stream_info Describes the incoming audio stream
/// @return ESP_ERR_INVALID_ARG if there is a parameter error, if there is more than 2 channels in the stream, or if
/// the audio settings are incompatible with the configuration.
/// ESP_ERR_NO_MEM if the driver fails to reconfigure due to a memory allocation error.
/// ESP_OK if successful.
esp_err_t reconfigure_i2s_stream_info_(audio::AudioStreamInfo &audio_stream_info);
/// ESP_FAIL if setting the data out pin fails due to an IO error ESP_OK if successful
esp_err_t start_i2s_driver_(audio::AudioStreamInfo &audio_stream_info);
/// @brief Deletes the speaker's task.
/// Deallocates the data_buffer_ and audio_ring_buffer_, if necessary, and deletes the task. Should only be called by

View File

@ -1,6 +1,6 @@
from esphome import core, pins
import esphome.codegen as cg
from esphome.components import display, font, spi
from esphome.components import display, spi
from esphome.components.display import validate_rotation
import esphome.config_validation as cv
from esphome.const import (
@ -147,7 +147,6 @@ def _validate(config):
CONFIG_SCHEMA = cv.All(
font.validate_pillow_installed,
display.FULL_DISPLAY_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ILI9XXXDisplay),

View File

@ -313,8 +313,9 @@ void ILI9XXXDisplay::draw_pixels_at(int x_start, int y_start, int w, int h, cons
// do color conversion pixel-by-pixel into the buffer and draw it later. If this is happening the user has not
// configured the renderer well.
if (this->rotation_ != display::DISPLAY_ROTATION_0_DEGREES || bitness != display::COLOR_BITNESS_565 || !big_endian) {
return display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
display::Display::draw_pixels_at(x_start, y_start, w, h, ptr, order, bitness, big_endian, x_offset, y_offset,
x_pad);
return;
}
this->set_addr_window_(x_start, y_start, x_start + w - 1, y_start + h - 1);
// x_ and y_offset are offsets into the source buffer, unrelated to our own offsets into the display.

View File

@ -10,7 +10,6 @@ import puremagic
from esphome import core, external_files
import esphome.codegen as cg
from esphome.components import font
import esphome.config_validation as cv
from esphome.const import (
CONF_DITHER,
@ -233,7 +232,7 @@ IMAGE_SCHEMA = cv.Schema(
)
)
CONFIG_SCHEMA = cv.All(font.validate_pillow_installed, IMAGE_SCHEMA)
CONFIG_SCHEMA = IMAGE_SCHEMA
def load_svg_image(file: bytes, resize: tuple[int, int]):

View File

@ -1,38 +1,20 @@
#include "json_util.h"
#include "esphome/core/log.h"
#ifdef USE_ESP8266
#include <Esp.h>
#endif
#ifdef USE_ESP32
#include <esp_heap_caps.h>
#endif
#ifdef USE_RP2040
#include <Arduino.h>
#endif
namespace esphome {
namespace json {
static const char *const TAG = "json";
static std::vector<char> global_json_build_buffer; // NOLINT
static const auto ALLOCATOR = RAMAllocator<uint8_t>(RAMAllocator<uint8_t>::ALLOC_INTERNAL);
std::string build_json(const json_build_t &f) {
// Here we are allocating up to 5kb of memory,
// with the heap size minus 2kb to be safe if less than 5kb
// as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
auto free_heap = ALLOCATOR.get_max_free_block_size();
size_t request_size = std::min(free_heap, (size_t) 512);
while (true) {
ESP_LOGV(TAG, "Attempting to allocate %u bytes for JSON serialization", request_size);
@ -67,20 +49,12 @@ bool parse_json(const std::string &data, const json_parse_t &f) {
// with the heap size minus 2kb to be safe if less than that
// as we can not have a true dynamic sized document.
// The excess memory is freed below with `shrinkToFit()`
#ifdef USE_ESP8266
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
#elif defined(USE_ESP32)
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
#elif defined(USE_RP2040)
const size_t free_heap = rp2040.getFreeHeap();
#elif defined(USE_LIBRETINY)
const size_t free_heap = lt_heap_get_free();
#endif
auto free_heap = ALLOCATOR.get_max_free_block_size();
size_t request_size = std::min(free_heap, (size_t) (data.size() * 1.5));
while (true) {
DynamicJsonDocument json_document(request_size);
if (json_document.capacity() == 0) {
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %zu bytes, free heap: %zu", request_size,
free_heap);
return false;
}

View File

@ -47,7 +47,7 @@ void Logger::write_header_(int level, const char *tag, int line) {
if (current_task == main_task_) {
this->printf_to_buffer_("%s[%s][%s:%03u]: ", color, letter, tag, line);
} else {
const char *thread_name = "";
const char *thread_name = ""; // NOLINT(clang-analyzer-deadcode.DeadStores)
#if defined(USE_ESP32)
thread_name = pcTaskGetName(current_task);
#elif defined(USE_LIBRETINY)

View File

@ -23,7 +23,7 @@ bool operator==(const GainTimePair &lhs, const GainTimePair &rhs) {
}
bool operator!=(const GainTimePair &lhs, const GainTimePair &rhs) {
return !(lhs.gain == rhs.gain && lhs.time == rhs.time);
return lhs.gain != rhs.gain || lhs.time != rhs.time;
}
template<typename T, size_t size> T get_next(const T (&array)[size], const T val) {

View File

@ -23,7 +23,7 @@ from esphome.helpers import write_file_if_changed
from . import defines as df, helpers, lv_validation as lvalid
from .automation import disp_update, focused_widgets, update_to_code
from .defines import add_define
from .defines import CONF_DRAW_ROUNDING, add_define
from .encoders import (
ENCODERS_CONFIG,
encoders_to_code,
@ -205,6 +205,10 @@ def final_validation(configs):
raise cv.Invalid(
"Using auto_clear_enabled: true in display config not compatible with LVGL"
)
if draw_rounding := display.get(CONF_DRAW_ROUNDING):
config[CONF_DRAW_ROUNDING] = max(
draw_rounding, config[CONF_DRAW_ROUNDING]
)
buffer_frac = config[CONF_BUFFER_SIZE]
if CORE.is_esp32 and buffer_frac > 0.5 and "psram" not in global_config:
LOGGER.warning("buffer_size: may need to be reduced without PSRAM")

View File

@ -8,7 +8,7 @@ import logging
from esphome import codegen as cg, config_validation as cv
from esphome.const import CONF_ITEMS
from esphome.core import Lambda
from esphome.core import ID, Lambda
from esphome.cpp_generator import LambdaExpression, MockObj
from esphome.cpp_types import uint32
from esphome.schema_extractors import SCHEMA_EXTRACT, schema_extractor
@ -38,7 +38,7 @@ def literal(arg):
def call_lambda(lamb: LambdaExpression):
expr = lamb.content.strip()
if expr.startswith("return") and expr.endswith(";"):
return expr[7:][:-1]
return expr[6:][:-1].strip()
return f"{lamb}()"
@ -72,6 +72,12 @@ class LValidator:
)
if self.retmapper is not None:
return self.retmapper(value)
if isinstance(value, ID):
return await cg.get_variable(value)
if isinstance(value, list):
value = [
await cg.get_variable(x) if isinstance(x, ID) else x for x in value
]
return cg.safe_exp(value)
@ -162,6 +168,7 @@ LV_EVENT_MAP = {
"READY": "READY",
"CANCEL": "CANCEL",
"ALL_EVENTS": "ALL",
"CHANGE": "VALUE_CHANGED",
}
LV_EVENT_TRIGGERS = tuple(f"on_{x.lower()}" for x in LV_EVENT_MAP)

View File

@ -1,6 +1,7 @@
from typing import Union
import esphome.codegen as cg
from esphome.components import image
from esphome.components.color import CONF_HEX, ColorStruct, from_rgbw
from esphome.components.font import Font
from esphome.components.image import Image_
@ -31,7 +32,7 @@ from .defines import (
literal,
)
from .helpers import add_lv_use, esphome_fonts_used, lv_fonts_used, requires_component
from .types import lv_font_t, lv_gradient_t, lv_img_t
from .types import lv_font_t, lv_gradient_t
opacity_consts = LvConstant("LV_OPA_", "TRANSP", "COVER")
@ -332,8 +333,12 @@ def image_validator(value):
lv_image = LValidator(
image_validator,
lv_img_t,
retmapper=lambda x: MockObj(x, "->").get_lv_img_dsc(),
image.Image_.operator("ptr"),
requires="image",
)
lv_image_list = LValidator(
cv.ensure_list(image_validator),
cg.std_vector.template(image.Image_.operator("ptr")),
requires="image",
)
lv_bool = LValidator(cv.boolean, cg.bool_, retmapper=literal)

View File

@ -279,7 +279,7 @@ std::string LvSelectable::get_selected_text() {
static std::string join_string(std::vector<std::string> options) {
return std::accumulate(
options.begin(), options.end(), std::string(),
[](const std::string &a, const std::string &b) -> std::string { return a + (a.length() > 0 ? "\n" : "") + b; });
[](const std::string &a, const std::string &b) -> std::string { return a + (!a.empty() ? "\n" : "") + b; });
}
void LvSelectable::set_selected_text(const std::string &text, lv_anim_enable_t anim) {

View File

@ -56,7 +56,26 @@ static const display::ColorBitness LV_BITNESS = display::ColorBitness::COLOR_BIT
inline void lv_img_set_src(lv_obj_t *obj, esphome::image::Image *image) {
lv_img_set_src(obj, image->get_lv_img_dsc());
}
inline void lv_disp_set_bg_image(lv_disp_t *disp, esphome::image::Image *image) {
lv_disp_set_bg_image(disp, image->get_lv_img_dsc());
}
#endif // USE_LVGL_IMAGE
#ifdef USE_LVGL_ANIMIMG
inline void lv_animimg_set_src(lv_obj_t *img, std::vector<image::Image *> images) {
auto *dsc = static_cast<std::vector<lv_img_dsc_t *> *>(lv_obj_get_user_data(img));
if (dsc == nullptr) {
// object will be lazily allocated but never freed.
dsc = new std::vector<lv_img_dsc_t *>(images.size()); // NOLINT
lv_obj_set_user_data(img, dsc);
}
dsc->clear();
for (auto &image : images) {
dsc->push_back(image->get_lv_img_dsc());
}
lv_animimg_set_src(img, (const void **) dsc->data(), dsc->size());
}
#endif // USE_LVGL_ANIMIMG
// Parent class for things that wrap an LVGL object
class LvCompound {

View File

@ -1,20 +1,18 @@
from esphome import automation
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_DURATION, CONF_ID
from ..automation import action_to_code
from ..defines import CONF_AUTO_START, CONF_MAIN, CONF_REPEAT_COUNT, CONF_SRC
from ..helpers import lvgl_components_required
from ..lv_validation import lv_image, lv_milliseconds
from ..lv_validation import lv_image_list, lv_milliseconds
from ..lvcode import lv
from ..types import LvType, ObjUpdateAction, void_ptr
from ..types import LvType, ObjUpdateAction
from . import Widget, WidgetType, get_widgets
from .img import CONF_IMAGE
from .label import CONF_LABEL
CONF_ANIMIMG = "animimg"
CONF_SRC_LIST_ID = "src_list_id"
def lv_repeat_count(value):
@ -32,14 +30,14 @@ ANIMIMG_BASE_SCHEMA = cv.Schema(
ANIMIMG_SCHEMA = ANIMIMG_BASE_SCHEMA.extend(
{
cv.Required(CONF_DURATION): lv_milliseconds,
cv.Required(CONF_SRC): cv.ensure_list(lv_image),
cv.GenerateID(CONF_SRC_LIST_ID): cv.declare_id(void_ptr),
cv.Required(CONF_SRC): lv_image_list,
}
)
ANIMIMG_MODIFY_SCHEMA = ANIMIMG_BASE_SCHEMA.extend(
{
cv.Optional(CONF_DURATION): lv_milliseconds,
cv.Optional(CONF_SRC): lv_image_list,
}
)
@ -59,17 +57,14 @@ class AnimimgType(WidgetType):
async def to_code(self, w: Widget, config):
lvgl_components_required.add(CONF_IMAGE)
lvgl_components_required.add(CONF_ANIMIMG)
if CONF_SRC in config:
srcs = [
await lv_image.process(await cg.get_variable(x))
for x in config[CONF_SRC]
]
src_id = cg.static_const_array(config[CONF_SRC_LIST_ID], srcs)
count = len(config[CONF_SRC])
lv.animimg_set_src(w.obj, src_id, count)
lv.animimg_set_repeat_count(w.obj, config[CONF_REPEAT_COUNT])
lv.animimg_set_duration(w.obj, config[CONF_DURATION])
if config.get(CONF_AUTO_START):
if srcs := config.get(CONF_SRC):
srcs = await lv_image_list.process(srcs)
lv.animimg_set_src(w.obj, srcs)
if repeat_count := config.get(CONF_REPEAT_COUNT):
lv.animimg_set_repeat_count(w.obj, repeat_count)
if duration := config.get(CONF_DURATION):
lv.animimg_set_duration(w.obj, duration)
if config[CONF_AUTO_START]:
lv.animimg_start(w.obj)
def get_uses(self):

View File

@ -1,4 +1,3 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ANGLE, CONF_MODE
@ -65,7 +64,6 @@ class ImgType(WidgetType):
async def to_code(self, w: Widget, config):
if src := config.get(CONF_SRC):
src = await cg.get_variable(src)
lv.img_set_src(w.obj, await lv_image.process(src))
if (cf_angle := config.get(CONF_ANGLE)) is not None:
pivot_x = config[CONF_PIVOT_X]
@ -81,7 +79,7 @@ class ImgType(WidgetType):
if CONF_ANTIALIAS in config:
lv.img_set_antialias(w.obj, config[CONF_ANTIALIAS])
if mode := config.get(CONF_MODE):
lv.img_set_mode(w.obj, mode)
await w.set_property("size_mode", mode)
img_spec = ImgType()

View File

@ -35,6 +35,11 @@ LINE_SCHEMA = {
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
}
LINE_MODIFY_SCHEMA = {
cv.Optional(CONF_POINTS): cv_point_list,
cv.GenerateID(CONF_POINT_LIST_ID): cv.declare_id(lv_point_t),
}
class LineType(WidgetType):
def __init__(self):
@ -43,6 +48,7 @@ class LineType(WidgetType):
LvType("lv_line_t"),
(CONF_MAIN,),
LINE_SCHEMA,
modify_schema=LINE_MODIFY_SCHEMA,
)
async def to_code(self, w: Widget, config):

View File

@ -29,7 +29,7 @@ from ..lvcode import (
)
from ..schemas import STYLE_SCHEMA, STYLED_TEXT_SCHEMA, container_schema, part_schema
from ..types import LV_EVENT, char_ptr, lv_obj_t
from . import Widget, set_obj_properties
from . import Widget, add_widgets, set_obj_properties
from .button import button_spec
from .buttonmatrix import (
BUTTONMATRIX_BUTTON_SCHEMA,
@ -119,6 +119,7 @@ async def msgbox_to_code(top_layer, conf):
button_style = {CONF_ITEMS: button_style}
await set_obj_properties(buttonmatrix_widget, button_style)
await set_obj_properties(msgbox_widget, conf)
await add_widgets(msgbox_widget, conf)
async with LambdaContext(EVENT_ARG, where=messagebox_id) as close_action:
outer_widget.add_flag("LV_OBJ_FLAG_HIDDEN")
if close_button:

View File

@ -1,8 +1,8 @@
from esphome import automation, pins
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import key_provider
from esphome.const import CONF_ID, CONF_PIN
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_KEY, CONF_PIN, CONF_TRIGGER_ID
CODEOWNERS = ["@ssieb"]
@ -14,6 +14,9 @@ matrix_keypad_ns = cg.esphome_ns.namespace("matrix_keypad")
MatrixKeypad = matrix_keypad_ns.class_(
"MatrixKeypad", key_provider.KeyProvider, cg.Component
)
MatrixKeyTrigger = matrix_keypad_ns.class_(
"MatrixKeyTrigger", automation.Trigger.template(cg.uint8)
)
CONF_KEYPAD_ID = "keypad_id"
CONF_ROWS = "rows"
@ -47,6 +50,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_DEBOUNCE_TIME, default=1): cv.int_range(min=1, max=100),
cv.Optional(CONF_HAS_DIODES): cv.boolean,
cv.Optional(CONF_HAS_PULLDOWNS): cv.boolean,
cv.Optional(CONF_ON_KEY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(MatrixKeyTrigger),
}
),
}
),
check_keys,
@ -73,3 +81,7 @@ async def to_code(config):
cg.add(var.set_has_diodes(config[CONF_HAS_DIODES]))
if CONF_HAS_PULLDOWNS in config:
cg.add(var.set_has_pulldowns(config[CONF_HAS_PULLDOWNS]))
for conf in config.get(CONF_ON_KEY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
cg.add(var.register_key_trigger(trigger))
await automation.build_automation(trigger, [(cg.uint8, "x")], conf)

View File

@ -86,6 +86,8 @@ void MatrixKeypad::loop() {
if (!this->keys_.empty()) {
uint8_t keycode = this->keys_[key];
ESP_LOGD(TAG, "key '%c' pressed", keycode);
for (auto &trigger : this->key_triggers_)
trigger->trigger(keycode);
for (auto &listener : this->listeners_)
listener->key_pressed(keycode);
this->send_key_(keycode);
@ -107,5 +109,7 @@ void MatrixKeypad::dump_config() {
void MatrixKeypad::register_listener(MatrixKeypadListener *listener) { this->listeners_.push_back(listener); }
void MatrixKeypad::register_key_trigger(MatrixKeyTrigger *trig) { this->key_triggers_.push_back(trig); }
} // namespace matrix_keypad
} // namespace esphome

View File

@ -1,6 +1,7 @@
#pragma once
#include "esphome/components/key_provider/key_provider.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
@ -18,6 +19,8 @@ class MatrixKeypadListener {
virtual void key_released(uint8_t key){};
};
class MatrixKeyTrigger : public Trigger<uint8_t> {};
class MatrixKeypad : public key_provider::KeyProvider, public Component {
public:
void setup() override;
@ -31,6 +34,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
void set_has_pulldowns(int has_pulldowns) { has_pulldowns_ = has_pulldowns; };
void register_listener(MatrixKeypadListener *listener);
void register_key_trigger(MatrixKeyTrigger *trig);
protected:
std::vector<GPIOPin *> rows_;
@ -42,6 +46,7 @@ class MatrixKeypad : public key_provider::KeyProvider, public Component {
int pressed_key_ = -1;
std::vector<MatrixKeypadListener *> listeners_{};
std::vector<MatrixKeyTrigger *> key_triggers_;
};
} // namespace matrix_keypad

View File

@ -106,7 +106,8 @@ void MAX31865Sensor::read_data_() {
// Check faults
const uint8_t faults = this->read_register_(FAULT_STATUS_REG);
if ((has_fault_ = faults & 0b00111100)) {
has_fault_ = faults & 0b00111100;
if (has_fault_) {
if (faults & (1 << 2)) {
ESP_LOGE(TAG, "Overvoltage/undervoltage fault");
}
@ -125,7 +126,8 @@ void MAX31865Sensor::read_data_() {
} else {
this->status_clear_error();
}
if ((has_warn_ = faults & 0b11000000)) {
has_warn_ = faults & 0b11000000;
if (has_warn_) {
if (faults & (1 << 6)) {
ESP_LOGW(TAG, "RTD Low Threshold");
}

View File

@ -19,10 +19,12 @@ template<typename... Ts> class MideaActionBase : public Action<Ts...> {
template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> {
TEMPLATABLE_VALUE(float, temperature)
TEMPLATABLE_VALUE(bool, use_fahrenheit)
TEMPLATABLE_VALUE(bool, beeper)
void play(Ts... x) override {
this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...));
this->parent_->do_follow_me(this->temperature_.value(x...), this->use_fahrenheit_.value(x...),
this->beeper_.value(x...));
}
};

View File

@ -1,6 +1,7 @@
#ifdef USE_ARDUINO
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "air_conditioner.h"
#include "ac_adapter.h"
#include <cmath>
@ -121,7 +122,7 @@ void AirConditioner::dump_config() {
/* ACTIONS */
void AirConditioner::do_follow_me(float temperature, bool beeper) {
void AirConditioner::do_follow_me(float temperature, bool use_fahrenheit, bool beeper) {
#ifdef USE_REMOTE_TRANSMITTER
// Check if temperature is finite (not NaN or infinite)
if (!std::isfinite(temperature)) {
@ -131,13 +132,14 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
// Round and convert temperature to long, then clamp and convert it to uint8_t
uint8_t temp_uint8 =
static_cast<uint8_t>(std::max(0L, std::min(static_cast<long>(UINT8_MAX), std::lroundf(temperature))));
static_cast<uint8_t>(esphome::clamp<long>(std::lroundf(temperature), 0L, static_cast<long>(UINT8_MAX)));
ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %f °C, rounded to: %u °C", temperature,
temp_uint8);
char temp_symbol = use_fahrenheit ? 'F' : 'C';
ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %.5f °%c, rounded to: %u °%c", temperature,
temp_symbol, temp_uint8, temp_symbol);
// Create and transmit the data
IrFollowMeData data(temp_uint8, beeper);
IrFollowMeData data(temp_uint8, use_fahrenheit, beeper);
this->transmitter_.transmit(data);
#else
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");

View File

@ -32,7 +32,7 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
/* ### ACTIONS ### */
/* ############### */
void do_follow_me(float temperature, bool beeper = false);
void do_follow_me(float temperature, bool use_fahrenheit, bool beeper = false);
void do_display_toggle();
void do_swing_step();
void do_beeper_on() { this->set_beeper_feedback(true); }

View File

@ -18,6 +18,7 @@ from esphome.const import (
CONF_SUPPORTED_SWING_MODES,
CONF_TIMEOUT,
CONF_TEMPERATURE,
CONF_USE_FAHRENHEIT,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_HUMIDITY,
@ -172,11 +173,10 @@ MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
)
# FollowMe action
MIDEA_FOLLOW_ME_MIN = 0
MIDEA_FOLLOW_ME_MAX = 37
MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
{
cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature),
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.templatable(cv.boolean),
cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean),
}
)
@ -186,6 +186,8 @@ MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
async def follow_me_to_code(var, config, args):
template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
cg.add(var.set_beeper(template_))
template_ = await cg.templatable(config[CONF_USE_FAHRENHEIT], args, cg.bool_)
cg.add(var.set_use_fahrenheit(template_))
template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
cg.add(var.set_temperature(template_))

View File

@ -16,22 +16,53 @@ class IrFollowMeData : public IrData {
IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
// Copy from Base
IrFollowMeData(const IrData &data) : IrData(data) {}
// Direct from temperature and beeper values
// Direct from temperature in celsius and beeper values
IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() {
this->set_temp(temp);
this->set_temp(temp, false);
this->set_beeper(beeper);
}
// Direct from temperature, fahrenheit and beeper values
IrFollowMeData(uint8_t temp, bool fahrenheit, bool beeper) : IrFollowMeData() {
this->set_temp(temp, fahrenheit);
this->set_beeper(beeper);
}
/* TEMPERATURE */
uint8_t temp() const { return this->get_value_(4) - 1; }
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); }
uint8_t temp() const {
if (this->fahrenheit()) {
return this->get_value_(4) + 31;
}
return this->get_value_(4) - 1;
}
void set_temp(uint8_t val, bool fahrenheit = false) {
this->set_fahrenheit(fahrenheit);
if (this->fahrenheit()) {
// see https://github.com/esphome/feature-requests/issues/1627#issuecomment-1365639966
val = esphome::clamp<uint8_t>(val, MIN_TEMP_F, MAX_TEMP_F) - 31;
} else {
val = esphome::clamp<uint8_t>(val, MIN_TEMP_C, MAX_TEMP_C) + 1;
}
this->set_value_(4, val);
}
/* BEEPER */
bool beeper() const { return this->get_value_(3, 128); }
void set_beeper(bool val) { this->set_mask_(3, val, 128); }
/* FAHRENHEIT */
bool fahrenheit() const { return this->get_value_(2, 32); }
void set_fahrenheit(bool val) { this->set_mask_(2, val, 32); }
protected:
static const uint8_t MAX_TEMP = 37;
static const uint8_t MIN_TEMP_C = 0;
static const uint8_t MAX_TEMP_C = 37;
// see
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L116
static const uint8_t MIN_TEMP_F = 32;
// see
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L117
static const uint8_t MAX_TEMP_F = 99;
};
class IrSpecialData : public IrData {

View File

@ -152,11 +152,11 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t
}
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
return (r.start_address == start_address && r.register_type == register_type);
});
auto reg_it = std::find_if(
std::begin(this->register_ranges_), std::end(this->register_ranges_),
[=](RegisterRange const &r) { return (r.start_address == start_address && r.register_type == register_type); });
if (reg_it == register_ranges_.end()) {
if (reg_it == this->register_ranges_.end()) {
ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
} else {
return reg_it->sensors;
@ -240,18 +240,18 @@ void ModbusController::update() {
// walk through the sensors and determine the register ranges to read
size_t ModbusController::create_register_ranges_() {
register_ranges_.clear();
if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) {
this->register_ranges_.clear();
if (this->parent_->role == modbus::ModbusRole::CLIENT && this->sensorset_.empty()) {
ESP_LOGW(TAG, "No sensors registered");
return 0;
}
// iterator is sorted see SensorItemsComparator for details
auto ix = sensorset_.begin();
auto ix = this->sensorset_.begin();
RegisterRange r = {};
uint8_t buffer_offset = 0;
SensorItem *prev = nullptr;
while (ix != sensorset_.end()) {
while (ix != this->sensorset_.end()) {
SensorItem *curr = *ix;
ESP_LOGV(TAG, "Register: 0x%X %d %d %d offset=%u skip=%u addr=%p", curr->start_address, curr->register_count,
@ -278,12 +278,12 @@ size_t ModbusController::create_register_ranges_() {
// this register can re-use the data from the previous register
// remove this sensore because start_address is changed (sort-order)
ix = sensorset_.erase(ix);
ix = this->sensorset_.erase(ix);
curr->start_address = r.start_address;
curr->offset += prev->offset;
sensorset_.insert(curr);
this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later
ix--;
@ -293,14 +293,14 @@ size_t ModbusController::create_register_ranges_() {
// this register can extend the current range
// remove this sensore because start_address is changed (sort-order)
ix = sensorset_.erase(ix);
ix = this->sensorset_.erase(ix);
curr->start_address = r.start_address;
curr->offset += buffer_offset;
buffer_offset += curr->get_register_size();
r.register_count += curr->register_count;
sensorset_.insert(curr);
this->sensorset_.insert(curr);
// move iterator backwards because it will be incremented later
ix--;
@ -327,7 +327,7 @@ size_t ModbusController::create_register_ranges_() {
ix++;
} else {
ESP_LOGV(TAG, "Add range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
register_ranges_.push_back(r);
this->register_ranges_.push_back(r);
r = {};
buffer_offset = 0;
// do not increment the iterator here because the current sensor has to be re-evaluated
@ -339,10 +339,10 @@ size_t ModbusController::create_register_ranges_() {
if (r.register_count > 0) {
// Add the last range
ESP_LOGV(TAG, "Add last range 0x%X %d skip:%d", r.start_address, r.register_count, r.skip_updates);
register_ranges_.push_back(r);
this->register_ranges_.push_back(r);
}
return register_ranges_.size();
return this->register_ranges_.size();
}
void ModbusController::dump_config() {
@ -352,18 +352,18 @@ void ModbusController::dump_config() {
ESP_LOGCONFIG(TAG, " Offline Skip Updates: %d", this->offline_skip_updates_);
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGCONFIG(TAG, "sensormap");
for (auto &it : sensorset_) {
for (auto &it : this->sensorset_) {
ESP_LOGCONFIG(TAG, " Sensor type=%zu start=0x%X offset=0x%X count=%d size=%d",
static_cast<uint8_t>(it->register_type), it->start_address, it->offset, it->register_count,
it->get_register_size());
}
ESP_LOGCONFIG(TAG, "ranges");
for (auto &it : register_ranges_) {
for (auto &it : this->register_ranges_) {
ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
it.start_address, it.register_count, it.skip_updates);
}
ESP_LOGCONFIG(TAG, "server registers");
for (auto &r : server_registers_) {
for (auto &r : this->server_registers_) {
ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
static_cast<uint8_t>(r->value_type), r->register_count);
}
@ -372,15 +372,15 @@ void ModbusController::dump_config() {
void ModbusController::loop() {
// Incoming data to process?
if (!incoming_queue_.empty()) {
auto &message = incoming_queue_.front();
if (!this->incoming_queue_.empty()) {
auto &message = this->incoming_queue_.front();
if (message != nullptr)
process_modbus_data_(message.get());
incoming_queue_.pop();
this->process_modbus_data_(message.get());
this->incoming_queue_.pop();
} else {
// all messages processed send pending commands
send_next_command_();
this->send_next_command_();
}
}
@ -391,7 +391,7 @@ void ModbusController::on_write_register_response(ModbusRegisterType register_ty
void ModbusController::dump_sensors_() {
ESP_LOGV(TAG, "sensors");
for (auto &it : sensorset_) {
for (auto &it : this->sensorset_) {
ESP_LOGV(TAG, " Sensor start=0x%X count=%d size=%d offset=%d", it->start_address, it->register_count,
it->get_register_size(), it->offset);
}

View File

@ -240,14 +240,14 @@ class SensorItem {
}
// Override register size for modbus devices not using 1 register for one dword
void set_register_size(uint8_t register_size) { response_bytes = register_size; }
ModbusRegisterType register_type;
SensorValueType sensor_value_type;
uint16_t start_address;
uint32_t bitmask;
uint8_t offset;
uint8_t register_count;
ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
SensorValueType sensor_value_type{SensorValueType::RAW};
uint16_t start_address{0};
uint32_t bitmask{0};
uint8_t offset{0};
uint8_t register_count{0};
uint8_t response_bytes{0};
uint16_t skip_updates;
uint16_t skip_updates{0};
std::vector<uint8_t> custom_data{};
bool force_new_range{false};
};
@ -261,9 +261,9 @@ class ServerRegister {
this->register_count = register_count;
this->read_lambda = std::move(read_lambda);
}
uint16_t address;
SensorValueType value_type;
uint8_t register_count;
uint16_t address{0};
SensorValueType value_type{SensorValueType::RAW};
uint8_t register_count{0};
std::function<float()> read_lambda;
};
@ -312,11 +312,11 @@ struct RegisterRange {
class ModbusCommandItem {
public:
static const size_t MAX_PAYLOAD_BYTES = 240;
ModbusController *modbusdevice;
uint16_t register_address;
uint16_t register_count;
ModbusFunctionCode function_code;
ModbusRegisterType register_type;
ModbusController *modbusdevice{nullptr};
uint16_t register_address{0};
uint16_t register_count{0};
ModbusFunctionCode function_code{ModbusFunctionCode::CUSTOM};
ModbusRegisterType register_type{ModbusRegisterType::CUSTOM};
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
on_data_func;
std::vector<uint8_t> payload = {};
@ -493,23 +493,23 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
/// Collection of all sensors for this component
SensorSet sensorset_;
/// Collection of all server registers for this component
std::vector<ServerRegister *> server_registers_;
std::vector<ServerRegister *> server_registers_{};
/// Continuous range of modbus registers
std::vector<RegisterRange> register_ranges_;
std::vector<RegisterRange> register_ranges_{};
/// Hold the pending requests to be sent
std::list<std::unique_ptr<ModbusCommandItem>> command_queue_;
/// modbus response data waiting to get processed
std::queue<std::unique_ptr<ModbusCommandItem>> incoming_queue_;
/// if duplicate commands can be sent
bool allow_duplicate_commands_;
bool allow_duplicate_commands_{false};
/// when was the last send operation
uint32_t last_command_timestamp_;
uint32_t last_command_timestamp_{0};
/// min time in ms between sending modbus commands
uint16_t command_throttle_;
uint16_t command_throttle_{0};
/// if module didn't respond the last command
bool module_offline_;
bool module_offline_{false};
/// how many updates to skip if module is offline
uint16_t offline_skip_updates_;
uint16_t offline_skip_updates_{0};
/// How many times we will retry a command if we get no response
uint8_t max_cmd_retries_{4};
/// Command sent callback

View File

@ -8,7 +8,7 @@ namespace modbus_controller {
static const char *const TAG = "modbus.number";
void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) {
float result = payload_to_float(data, *this) / multiply_by_;
float result = payload_to_float(data, *this) / this->multiply_by_;
// Is there a lambda registered
// call it with the pre converted value and the raw data array
@ -43,7 +43,7 @@ void ModbusNumber::control(float value) {
return;
}
} else {
write_value = multiply_by_ * write_value;
write_value = this->multiply_by_ * write_value;
}
if (!data.empty()) {
@ -63,21 +63,21 @@ void ModbusNumber::control(float value) {
// Create and send the write command
if (this->register_count == 1 && !this->use_write_multiple_) {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
write_cmd =
ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]);
write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2,
data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2,
this->register_count, data);
write_cmd = ModbusCommandItem::create_write_multiple_command(
this->parent_, this->start_address + this->offset / 2, this->register_count, data);
}
// publish new value
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
const std::vector<uint8_t> &data) {
// gets called when the write command is ack'd from the device
parent_->on_write_register_response(write_cmd.register_type, start_address, data);
this->parent_->on_write_register_response(write_cmd.register_type, start_address, data);
this->publish_state(value);
};
}
parent_->queue_command(write_cmd);
this->parent_->queue_command(write_cmd);
this->publish_state(value);
}
void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }

View File

@ -29,7 +29,7 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
void parse_and_publish(const std::vector<uint8_t> &data) override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
void set_parent(ModbusController *parent) { this->parent_ = parent; }
void set_write_multiply(float factor) { multiply_by_ = factor; }
void set_write_multiply(float factor) { this->multiply_by_ = factor; }
using transform_func_t = std::function<optional<float>(ModbusNumber *, float, const std::vector<uint8_t> &)>;
using write_transform_func_t = std::function<optional<float>(ModbusNumber *, float, std::vector<uint16_t> &)>;
@ -39,9 +39,9 @@ class ModbusNumber : public number::Number, public Component, public SensorItem
protected:
void control(float value) override;
optional<transform_func_t> transform_func_;
optional<write_transform_func_t> write_transform_func_;
ModbusController *parent_;
optional<transform_func_t> transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_{nullptr};
float multiply_by_{1.0};
bool use_write_multiple_{false};
};

View File

@ -27,7 +27,7 @@ void ModbusFloatOutput::write_state(float value) {
return;
}
} else {
value = multiply_by_ * value;
value = this->multiply_by_ * value;
}
// lambda didn't set payload
if (data.empty()) {
@ -40,12 +40,13 @@ void ModbusFloatOutput::write_state(float value) {
// Create and send the write command
ModbusCommandItem write_cmd;
if (this->register_count == 1 && !this->use_write_multiple_) {
write_cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset, data[0]);
write_cmd =
ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset, data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset,
write_cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset,
this->register_count, data);
}
parent_->queue_command(write_cmd);
this->parent_->queue_command(write_cmd);
}
void ModbusFloatOutput::dump_config() {
@ -90,9 +91,9 @@ void ModbusBinaryOutput::write_state(bool state) {
// offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) {
std::vector<bool> states{state};
cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states);
} else {
cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state);
}
}
this->parent_->queue_command(cmd);

View File

@ -25,7 +25,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
void dump_config() override;
void set_parent(ModbusController *parent) { this->parent_ = parent; }
void set_write_multiply(float factor) { multiply_by_ = factor; }
void set_write_multiply(float factor) { this->multiply_by_ = factor; }
// Do nothing
void parse_and_publish(const std::vector<uint8_t> &data) override{};
@ -37,9 +37,9 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
void write_state(float value) override;
optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_;
ModbusController *parent_{nullptr};
float multiply_by_{1.0};
bool use_write_multiple_;
bool use_write_multiple_{false};
};
class ModbusBinaryOutput : public output::BinaryOutput, public Component, public SensorItem {
@ -68,8 +68,8 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
void write_state(bool state) override;
optional<write_transform_func_t> write_transform_func_{nullopt};
ModbusController *parent_;
bool use_write_multiple_;
ModbusController *parent_{nullptr};
bool use_write_multiple_{false};
};
} // namespace modbus_controller

View File

@ -74,12 +74,13 @@ void ModbusSelect::control(const std::string &value) {
const uint16_t write_address = this->start_address + this->offset / 2;
ModbusCommandItem write_cmd;
if ((this->register_count == 1) && (!this->use_write_multiple_)) {
write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]);
write_cmd = ModbusCommandItem::create_write_single_command(this->parent_, write_address, data[0]);
} else {
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data);
write_cmd =
ModbusCommandItem::create_write_multiple_command(this->parent_, write_address, this->register_count, data);
}
parent_->queue_command(write_cmd);
this->parent_->queue_command(write_cmd);
if (this->optimistic_)
this->publish_state(value);

View File

@ -42,12 +42,12 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
void control(const std::string &value) override;
protected:
std::vector<int64_t> mapping_;
ModbusController *parent_;
std::vector<int64_t> mapping_{};
ModbusController *parent_{nullptr};
bool use_write_multiple_{false};
bool optimistic_{false};
optional<transform_func_t> transform_func_;
optional<write_transform_func_t> write_transform_func_;
optional<transform_func_t> transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_{nullopt};
};
} // namespace modbus_controller

View File

@ -80,24 +80,24 @@ void ModbusSwitch::write_state(bool state) {
// offset for coil and discrete inputs is the coil/register number not bytes
if (this->use_write_multiple_) {
std::vector<bool> states{state};
cmd = ModbusCommandItem::create_write_multiple_coils(parent_, this->start_address + this->offset, states);
cmd = ModbusCommandItem::create_write_multiple_coils(this->parent_, this->start_address + this->offset, states);
} else {
cmd = ModbusCommandItem::create_write_single_coil(parent_, this->start_address + this->offset, state);
cmd = ModbusCommandItem::create_write_single_coil(this->parent_, this->start_address + this->offset, state);
}
} else {
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
if (this->use_write_multiple_) {
std::vector<uint16_t> bool_states(1, state ? (0xFFFF & this->bitmask) : 0);
cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2, 1,
cmd = ModbusCommandItem::create_write_multiple_command(this->parent_, this->start_address + this->offset / 2, 1,
bool_states);
} else {
cmd = ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2,
cmd = ModbusCommandItem::create_write_single_command(this->parent_, this->start_address + this->offset / 2,
state ? 0xFFFF & this->bitmask : 0u);
}
}
}
this->parent_->queue_command(cmd);
publish_state(state);
this->publish_state(state);
}
// ModbusSwitch end
} // namespace modbus_controller

View File

@ -40,8 +40,8 @@ class ModbusSwitch : public Component, public switch_::Switch, public SensorItem
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
protected:
ModbusController *parent_;
bool use_write_multiple_;
ModbusController *parent_{nullptr};
bool use_write_multiple_{false};
optional<transform_func_t> publish_transform_func_{nullopt};
optional<write_transform_func_t> write_transform_func_{nullopt};
};

View File

@ -49,6 +49,7 @@ from esphome.const import (
CONF_USE_ABBREVIATIONS,
CONF_USERNAME,
CONF_WILL_MESSAGE,
CONF_PUBLISH_NAN_AS_NONE,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
@ -296,6 +297,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
}
),
cv.Optional(CONF_PUBLISH_NAN_AS_NONE, default=False): cv.boolean,
}
),
validate_config,
@ -449,6 +451,8 @@ async def to_code(config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
cg.add(var.set_publish_nan_as_none(config[CONF_PUBLISH_NAN_AS_NONE]))
MQTT_PUBLISH_ACTION_SCHEMA = cv.Schema(
{

View File

@ -80,8 +80,7 @@ const EntityBase *MQTTAlarmControlPanelComponent::get_entity() const { return th
bool MQTTAlarmControlPanelComponent::send_initial_state() { return this->publish_state(); }
bool MQTTAlarmControlPanelComponent::publish_state() {
bool success = true;
const char *state_s = "";
const char *state_s;
switch (this->alarm_control_panel_->get_state()) {
case ACP_STATE_DISARMED:
state_s = "disarmed";
@ -116,9 +115,7 @@ bool MQTTAlarmControlPanelComponent::publish_state() {
default:
state_s = "unknown";
}
if (!this->publish(this->get_state_topic_(), state_s))
success = false;
return success;
return this->publish(this->get_state_topic_(), state_s);
}
} // namespace mqtt

View File

@ -151,11 +151,11 @@ void MQTTBackendESP32::mqtt_event_handler_(const Event &event) {
break;
case MQTT_EVENT_DATA: {
static std::string topic;
if (event.topic.length() > 0) {
if (!event.topic.empty()) {
topic = event.topic;
}
ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
this->on_message_.call(event.topic.length() > 0 ? topic.c_str() : nullptr, event.data.data(), event.data.size(),
this->on_message_.call(!event.topic.empty() ? topic.c_str() : nullptr, event.data.data(), event.data.size(),
event.current_data_offset, event.total_data_len);
} break;
case MQTT_EVENT_ERROR:
@ -184,7 +184,7 @@ void MQTTBackendESP32::mqtt_event_handler(void *handler_args, esp_event_base_t b
// queue event to decouple processing
if (instance) {
auto event = *static_cast<esp_mqtt_event_t *>(event_data);
instance->mqtt_events_.push(Event(event));
instance->mqtt_events_.emplace(event);
}
}

View File

@ -608,6 +608,10 @@ void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; }
const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; }
void MQTTClientComponent::set_publish_nan_as_none(bool publish_nan_as_none) {
this->publish_nan_as_none_ = publish_nan_as_none;
}
bool MQTTClientComponent::is_publish_nan_as_none() const { return this->publish_nan_as_none_; }
void MQTTClientComponent::disable_birth_message() {
this->birth_message_.topic = "";
this->recalculate_availability_();

View File

@ -263,6 +263,10 @@ class MQTTClientComponent : public Component {
void set_on_connect(mqtt_on_connect_callback_t &&callback);
void set_on_disconnect(mqtt_on_disconnect_callback_t &&callback);
// Publish None state instead of NaN for Home Assistant
void set_publish_nan_as_none(bool publish_nan_as_none);
bool is_publish_nan_as_none() const;
protected:
void send_device_info_();
@ -328,6 +332,8 @@ class MQTTClientComponent : public Component {
uint32_t connect_begin_;
uint32_t last_connected_{0};
optional<MQTTClientDisconnectReason> disconnect_reason_{};
bool publish_nan_as_none_{false};
};
extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -257,7 +257,7 @@ const EntityBase *MQTTClimateComponent::get_entity() const { return this->device
bool MQTTClimateComponent::publish_state_() {
auto traits = this->device_->get_traits();
// mode
const char *mode_s = "";
const char *mode_s;
switch (this->device_->mode) {
case CLIMATE_MODE_OFF:
mode_s = "off";
@ -280,6 +280,8 @@ bool MQTTClimateComponent::publish_state_() {
case CLIMATE_MODE_HEAT_COOL:
mode_s = "heat_cool";
break;
default:
mode_s = "unknown";
}
bool success = true;
if (!this->publish(this->get_mode_state_topic(), mode_s))
@ -343,6 +345,8 @@ bool MQTTClimateComponent::publish_state_() {
case CLIMATE_PRESET_ACTIVITY:
payload = "activity";
break;
default:
payload = "unknown";
}
}
if (this->device_->custom_preset.has_value())
@ -352,7 +356,7 @@ bool MQTTClimateComponent::publish_state_() {
}
if (traits.get_supports_action()) {
const char *payload = "unknown";
const char *payload;
switch (this->device_->action) {
case CLIMATE_ACTION_OFF:
payload = "off";
@ -372,6 +376,8 @@ bool MQTTClimateComponent::publish_state_() {
case CLIMATE_ACTION_FAN:
payload = "fan";
break;
default:
payload = "unknown";
}
if (!this->publish(this->get_action_state_topic(), payload))
success = false;
@ -411,6 +417,8 @@ bool MQTTClimateComponent::publish_state_() {
case CLIMATE_FAN_QUIET:
payload = "quiet";
break;
default:
payload = "unknown";
}
}
if (this->device_->custom_fan_mode.has_value())
@ -420,7 +428,7 @@ bool MQTTClimateComponent::publish_state_() {
}
if (traits.get_supports_swing_modes()) {
const char *payload = "";
const char *payload;
switch (this->device_->swing_mode) {
case CLIMATE_SWING_OFF:
payload = "off";
@ -434,6 +442,8 @@ bool MQTTClimateComponent::publish_state_() {
case CLIMATE_SWING_HORIZONTAL:
payload = "horizontal";
break;
default:
payload = "unknown";
}
if (!this->publish(this->get_swing_mode_state_topic(), payload))
success = false;

View File

@ -69,6 +69,8 @@ bool MQTTSensorComponent::send_initial_state() {
}
}
bool MQTTSensorComponent::publish_state(float value) {
if (mqtt::global_mqtt_client->is_publish_nan_as_none() && std::isnan(value))
return this->publish(this->get_state_topic_(), "None");
int8_t accuracy = this->sensor_->get_accuracy_decimals();
return this->publish(this->get_state_topic_(), value_accuracy_to_string(value, accuracy));
}

Some files were not shown because too many files have changed in this diff Show More