mirror of
https://github.com/esphome/esphome.git
synced 2025-11-03 00:21:56 +00:00
Compare commits
51 Commits
jesserockz
...
jesserockz
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0475dd8af4 | ||
|
|
e04743e381 | ||
|
|
a6957b9d3b | ||
|
|
9816c27031 | ||
|
|
ea06740b46 | ||
|
|
9a5ec1b9e6 | ||
|
|
6dcbd1a8ae | ||
|
|
63b0930ae8 | ||
|
|
5382bd2a97 | ||
|
|
de1fbd390b | ||
|
|
af23357dca | ||
|
|
0fbe6c0d8b | ||
|
|
4e1ff31342 | ||
|
|
df4224e779 | ||
|
|
5877c57a35 | ||
|
|
7f2ca800c1 | ||
|
|
ce7ff15c8a | ||
|
|
88742e0399 | ||
|
|
c187cb547c | ||
|
|
42bc960a36 | ||
|
|
ba63d266d8 | ||
|
|
90baba4db7 | ||
|
|
1656ced351 | ||
|
|
1dfd15e607 | ||
|
|
5dcaf1241f | ||
|
|
7aa54b6879 | ||
|
|
444e162c92 | ||
|
|
bb27eaaf1e | ||
|
|
517f659da8 | ||
|
|
5a92e24662 | ||
|
|
437b236a4d | ||
|
|
14eac3dbce | ||
|
|
132a096ae7 | ||
|
|
440080a753 | ||
|
|
f15e3cfb9b | ||
|
|
9d000e9abf | ||
|
|
97fd7493b5 | ||
|
|
4c87658503 | ||
|
|
c80e035bd5 | ||
|
|
c8ec0bb7ea | ||
|
|
86ae1c5931 | ||
|
|
d0958f7cf2 | ||
|
|
982ce1db72 | ||
|
|
f042c6e643 | ||
|
|
5fcd26bfe9 | ||
|
|
5717d557f5 | ||
|
|
3bac45e737 | ||
|
|
e623989878 | ||
|
|
39cbc6b183 | ||
|
|
749a5e3348 | ||
|
|
b0e3ac01e8 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -355,6 +355,7 @@ 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
|
||||
|
||||
@@ -169,7 +169,7 @@ ARG BUILD_VERSION=dev
|
||||
LABEL \
|
||||
org.opencontainers.image.authors="The ESPHome Authors" \
|
||||
org.opencontainers.image.title="ESPHome" \
|
||||
org.opencontainers.image.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \
|
||||
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" \
|
||||
@@ -206,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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
24
esphome/components/adc/adc_sensor_common.cpp
Normal file
24
esphome/components/adc/adc_sensor_common.cpp
Normal 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
|
||||
@@ -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
|
||||
58
esphome/components/adc/adc_sensor_esp8266.cpp
Normal file
58
esphome/components/adc/adc_sensor_esp8266.cpp
Normal 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
|
||||
48
esphome/components/adc/adc_sensor_libretiny.cpp
Normal file
48
esphome/components/adc/adc_sensor_libretiny.cpp
Normal 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
|
||||
93
esphome/components/adc/adc_sensor_rp2040.cpp
Normal file
93
esphome/components/adc/adc_sensor_rp2040.cpp
Normal 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
|
||||
@@ -30,109 +30,162 @@ static const char *const TAG = "debug";
|
||||
|
||||
std::string DebugComponent::get_reset_reason_() {
|
||||
std::string reset_reason;
|
||||
switch (rtc_get_reset_reason(0)) {
|
||||
case POWERON_RESET:
|
||||
reset_reason = "Power On Reset";
|
||||
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";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case SW_RESET:
|
||||
case SW_RESET:
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case RTC_SW_SYS_RESET:
|
||||
case RTC_SW_SYS_RESET:
|
||||
#endif
|
||||
reset_reason = "Software Reset Digital Core";
|
||||
break;
|
||||
reset_reason = "Software Reset Digital Core";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case OWDT_RESET:
|
||||
reset_reason = "Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case OWDT_RESET:
|
||||
reset_reason = "Watch Dog Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
case DEEPSLEEP_RESET:
|
||||
reset_reason = "Deep Sleep Reset Digital Core";
|
||||
break;
|
||||
case DEEPSLEEP_RESET:
|
||||
reset_reason = "Deep Sleep Reset Digital Core";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case SDIO_RESET:
|
||||
reset_reason = "SLC Module Reset Digital Core";
|
||||
break;
|
||||
case SDIO_RESET:
|
||||
reset_reason = "SLC Module Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
case TG0WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case TG1WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case RTCWDT_SYS_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case TG0WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case TG1WDT_SYS_RESET:
|
||||
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
|
||||
break;
|
||||
case RTCWDT_SYS_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core";
|
||||
break;
|
||||
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||
case INTRUSION_RESET:
|
||||
reset_reason = "Intrusion Reset CPU";
|
||||
break;
|
||||
case INTRUSION_RESET:
|
||||
reset_reason = "Intrusion Reset CPU";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case TGWDT_CPU_RESET:
|
||||
reset_reason = "Timer Group Reset CPU";
|
||||
break;
|
||||
case TGWDT_CPU_RESET:
|
||||
reset_reason = "Timer Group Reset CPU";
|
||||
break;
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case TG0WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 0 Reset CPU";
|
||||
break;
|
||||
case TG0WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 0 Reset CPU";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case SW_CPU_RESET:
|
||||
case SW_CPU_RESET:
|
||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case RTC_SW_CPU_RESET:
|
||||
case RTC_SW_CPU_RESET:
|
||||
#endif
|
||||
reset_reason = "Software Reset CPU";
|
||||
break;
|
||||
case RTCWDT_CPU_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset CPU";
|
||||
break;
|
||||
reset_reason = "Software Reset CPU";
|
||||
break;
|
||||
case RTCWDT_CPU_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset CPU";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||
case EXT_CPU_RESET:
|
||||
reset_reason = "External CPU Reset";
|
||||
break;
|
||||
case EXT_CPU_RESET:
|
||||
reset_reason = "External CPU Reset";
|
||||
break;
|
||||
#endif
|
||||
case RTCWDT_BROWN_OUT_RESET:
|
||||
reset_reason = "Voltage Unstable Reset";
|
||||
break;
|
||||
case RTCWDT_RTC_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case RTCWDT_BROWN_OUT_RESET:
|
||||
reset_reason = "Voltage Unstable Reset";
|
||||
break;
|
||||
case RTCWDT_RTC_RESET:
|
||||
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||
defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case TG1WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 1 Reset CPU";
|
||||
break;
|
||||
case SUPER_WDT_RESET:
|
||||
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case EFUSE_RESET:
|
||||
reset_reason = "eFuse Reset Digital Core";
|
||||
break;
|
||||
case TG1WDT_CPU_RESET:
|
||||
reset_reason = "Timer Group 1 Reset CPU";
|
||||
break;
|
||||
case SUPER_WDT_RESET:
|
||||
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case EFUSE_RESET:
|
||||
reset_reason = "eFuse Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case GLITCH_RTC_RESET:
|
||||
reset_reason = "Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case GLITCH_RTC_RESET:
|
||||
reset_reason = "Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||
case USB_UART_CHIP_RESET:
|
||||
reset_reason = "USB UART Reset Digital Core";
|
||||
break;
|
||||
case USB_JTAG_CHIP_RESET:
|
||||
reset_reason = "USB JTAG Reset Digital Core";
|
||||
break;
|
||||
case USB_UART_CHIP_RESET:
|
||||
reset_reason = "USB UART Reset Digital Core";
|
||||
break;
|
||||
case USB_JTAG_CHIP_RESET:
|
||||
reset_reason = "USB JTAG Reset Digital Core";
|
||||
break;
|
||||
#endif
|
||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||
case POWER_GLITCH_RESET:
|
||||
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
case POWER_GLITCH_RESET:
|
||||
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
reset_reason = "Unknown 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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -670,7 +670,7 @@ void Display::strftime(int x, int y, BaseFont *font, Color color, Color backgrou
|
||||
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, TextAlign::TOP_LEFT, format, 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, COLOR_OFF, TextAlign::TOP_LEFT, format, time);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import update
|
||||
from esphome.const import (
|
||||
CONF_SOURCE,
|
||||
)
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SOURCE
|
||||
|
||||
from .. import http_request_ns, CONF_HTTP_REQUEST_ID, HttpRequestComponent
|
||||
from .. import CONF_HTTP_REQUEST_ID, HttpRequestComponent, http_request_ns
|
||||
from ..ota import OtaHttpRequestComponent
|
||||
|
||||
|
||||
AUTO_LOAD = ["json"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["ota.http_request"]
|
||||
@@ -25,7 +21,7 @@ CONFIG_SCHEMA = update.UPDATE_SCHEMA.extend(
|
||||
cv.GenerateID(): cv.declare_id(HttpRequestUpdate),
|
||||
cv.GenerateID(CONF_OTA_ID): cv.use_id(OtaHttpRequestComponent),
|
||||
cv.GenerateID(CONF_HTTP_REQUEST_ID): cv.use_id(HttpRequestComponent),
|
||||
cv.Required(CONF_SOURCE): cv.url,
|
||||
cv.Required(CONF_SOURCE): cv.ensure_list(cv.url),
|
||||
}
|
||||
).extend(cv.polling_component_schema("6h"))
|
||||
|
||||
@@ -37,7 +33,7 @@ async def to_code(config):
|
||||
request_parent = await cg.get_variable(config[CONF_HTTP_REQUEST_ID])
|
||||
cg.add(var.set_request_parent(request_parent))
|
||||
|
||||
cg.add(var.set_source_url(config[CONF_SOURCE]))
|
||||
cg.add(var.set_source_urls(config[CONF_SOURCE]))
|
||||
|
||||
cg.add_define("USE_OTA_STATE_CALLBACK")
|
||||
|
||||
|
||||
@@ -21,19 +21,33 @@ void HttpRequestUpdate::setup() {
|
||||
this->update_info_.progress = progress;
|
||||
this->publish_state();
|
||||
} else if (state == ota::OTAState::OTA_ABORT || state == ota::OTAState::OTA_ERROR) {
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
this->status_set_error("Failed to install firmware");
|
||||
this->publish_state();
|
||||
if (this->current_source_ + 1 < this->source_urls_.size()) {
|
||||
this->current_source_++;
|
||||
this->defer("update", [this]() {
|
||||
this->update();
|
||||
this->perform(true);
|
||||
});
|
||||
} else {
|
||||
this->current_source_ = 0;
|
||||
this->state_ = update::UPDATE_STATE_AVAILABLE;
|
||||
this->status_set_error("Failed to install firmware");
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HttpRequestUpdate::update() {
|
||||
auto container = this->request_parent_->get(this->source_url_);
|
||||
std::string current_source = this->source_urls_[this->current_source_];
|
||||
auto container = this->request_parent_->get(current_source);
|
||||
|
||||
if (container == nullptr || container->status_code != HTTP_STATUS_OK) {
|
||||
std::string msg = str_sprintf("Failed to fetch manifest from %s", this->source_url_.c_str());
|
||||
std::string msg = str_sprintf("Failed to fetch manifest from %s", current_source.c_str());
|
||||
this->status_set_error(msg.c_str());
|
||||
if (this->current_source_ + 1 < this->source_urls_.size()) {
|
||||
this->current_source_++;
|
||||
this->defer("update", [this]() { this->update(); });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -99,7 +113,7 @@ void HttpRequestUpdate::update() {
|
||||
});
|
||||
|
||||
if (!valid) {
|
||||
std::string msg = str_sprintf("Failed to parse JSON from %s", this->source_url_.c_str());
|
||||
std::string msg = str_sprintf("Failed to parse JSON from %s", current_source.c_str());
|
||||
this->status_set_error(msg.c_str());
|
||||
return;
|
||||
}
|
||||
@@ -108,10 +122,10 @@ void HttpRequestUpdate::update() {
|
||||
if (this->update_info_.firmware_url.find("http") == std::string::npos) {
|
||||
std::string path = this->update_info_.firmware_url;
|
||||
if (path[0] == '/') {
|
||||
std::string domain = this->source_url_.substr(0, this->source_url_.find('/', 8));
|
||||
std::string domain = current_source.substr(0, current_source.find('/', 8));
|
||||
this->update_info_.firmware_url = domain + path;
|
||||
} else {
|
||||
std::string domain = this->source_url_.substr(0, this->source_url_.rfind('/') + 1);
|
||||
std::string domain = current_source.substr(0, current_source.rfind('/') + 1);
|
||||
this->update_info_.firmware_url = domain + path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
|
||||
void perform(bool force) override;
|
||||
void check() override { this->update(); }
|
||||
|
||||
void set_source_url(const std::string &source_url) { this->source_url_ = source_url; }
|
||||
void set_source_url(const std::string &source_urls) { this->source_urls_ = {source_urls}; }
|
||||
void set_source_urls(const std::vector<std::string> &source_urls) { this->source_urls_ = source_urls; }
|
||||
|
||||
void set_request_parent(HttpRequestComponent *request_parent) { this->request_parent_ = request_parent; }
|
||||
void set_ota_parent(OtaHttpRequestComponent *ota_parent) { this->ota_parent_ = ota_parent; }
|
||||
@@ -28,7 +29,8 @@ class HttpRequestUpdate : public update::UpdateEntity, public PollingComponent {
|
||||
protected:
|
||||
HttpRequestComponent *request_parent_;
|
||||
OtaHttpRequestComponent *ota_parent_;
|
||||
std::string source_url_;
|
||||
std::vector<std::string> source_urls_;
|
||||
size_t current_source_{0};
|
||||
};
|
||||
|
||||
} // namespace http_request
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -247,7 +247,7 @@ void I2SAudioSpeaker::speaker_task(void *params) {
|
||||
|
||||
// Ensure ring buffer is at least as large as the total size of the DMA buffers
|
||||
const size_t ring_buffer_size =
|
||||
std::min((uint32_t) dma_buffers_size, this_speaker->buffer_duration_ms_ * bytes_per_ms);
|
||||
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
|
||||
|
||||
@@ -168,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)
|
||||
|
||||
@@ -79,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()
|
||||
|
||||
@@ -152,9 +152,9 @@ 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(this->register_ranges_), end(this->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 == this->register_ranges_.end()) {
|
||||
ESP_LOGE(TAG, "No matching range for sensor found - start_address : 0x%X", start_address);
|
||||
@@ -375,12 +375,12 @@ void ModbusController::loop() {
|
||||
if (!this->incoming_queue_.empty()) {
|
||||
auto &message = this->incoming_queue_.front();
|
||||
if (message != nullptr)
|
||||
process_modbus_data_(message.get());
|
||||
this->process_modbus_data_(message.get());
|
||||
this->incoming_queue_.pop();
|
||||
|
||||
} else {
|
||||
// all messages processed send pending commands
|
||||
send_next_command_();
|
||||
this->send_next_command_();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ void ModbusSwitch::write_state(bool state) {
|
||||
}
|
||||
}
|
||||
this->parent_->queue_command(cmd);
|
||||
publish_state(state);
|
||||
this->publish_state(state);
|
||||
}
|
||||
// ModbusSwitch end
|
||||
} // namespace modbus_controller
|
||||
|
||||
@@ -119,17 +119,17 @@ async def to_code(config):
|
||||
cg.add_library("ESP8266HTTPClient", None)
|
||||
|
||||
if CONF_TOUCH_SLEEP_TIMEOUT in config:
|
||||
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT]))
|
||||
cg.add(var.set_touch_sleep_timeout(config[CONF_TOUCH_SLEEP_TIMEOUT]))
|
||||
|
||||
if CONF_WAKE_UP_PAGE in config:
|
||||
cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE]))
|
||||
cg.add(var.set_wake_up_page(config[CONF_WAKE_UP_PAGE]))
|
||||
|
||||
if CONF_START_UP_PAGE in config:
|
||||
cg.add(var.set_start_up_page_internal(config[CONF_START_UP_PAGE]))
|
||||
cg.add(var.set_start_up_page(config[CONF_START_UP_PAGE]))
|
||||
|
||||
cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH]))
|
||||
cg.add(var.set_auto_wake_on_touch(config[CONF_AUTO_WAKE_ON_TOUCH]))
|
||||
|
||||
cg.add(var.set_exit_reparse_on_start_internal(config[CONF_EXIT_REPARSE_ON_START]))
|
||||
cg.add(var.set_exit_reparse_on_start(config[CONF_EXIT_REPARSE_ON_START]))
|
||||
|
||||
cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE]))
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) {
|
||||
}
|
||||
|
||||
bool Nextion::check_connect_() {
|
||||
if (this->get_is_connected_())
|
||||
if (this->is_connected_)
|
||||
return true;
|
||||
|
||||
// Check if the handshake should be skipped for the Nextion connection
|
||||
@@ -280,14 +280,6 @@ void Nextion::loop() {
|
||||
this->goto_page(this->start_up_page_);
|
||||
}
|
||||
|
||||
// This could probably be removed from the loop area, as those are redundant.
|
||||
this->set_auto_wake_on_touch(this->auto_wake_on_touch_);
|
||||
this->set_exit_reparse_on_start(this->exit_reparse_on_start_);
|
||||
|
||||
if (this->touch_sleep_timeout_ != 0) {
|
||||
this->set_touch_sleep_timeout(this->touch_sleep_timeout_);
|
||||
}
|
||||
|
||||
if (this->wake_up_page_ != -1) {
|
||||
this->set_wake_up_page(this->wake_up_page_);
|
||||
}
|
||||
|
||||
@@ -856,76 +856,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
*/
|
||||
void set_backlight_brightness(float brightness);
|
||||
|
||||
/**
|
||||
* Set the touch sleep timeout of the display.
|
||||
* @param timeout Timeout in seconds.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_touch_sleep_timeout(30);
|
||||
* ```
|
||||
*
|
||||
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
|
||||
* `thup`.
|
||||
*/
|
||||
void set_touch_sleep_timeout(uint16_t timeout);
|
||||
|
||||
/**
|
||||
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
|
||||
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
||||
* wakes up to current page.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_wake_up_page(2);
|
||||
* ```
|
||||
*
|
||||
* The display will wake up to page 2.
|
||||
*/
|
||||
void set_wake_up_page(uint8_t page_id = 255);
|
||||
|
||||
/**
|
||||
* Sets which page Nextion loads when connecting to ESPHome.
|
||||
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
||||
* wakes up to current page.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_start_up_page(2);
|
||||
* ```
|
||||
*
|
||||
* The display will go to page 2 when it establishes a connection to ESPHome.
|
||||
*/
|
||||
void set_start_up_page(uint8_t page_id = 255);
|
||||
|
||||
/**
|
||||
* Sets if Nextion should auto-wake from sleep when touch press occurs.
|
||||
* @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode,
|
||||
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_auto_wake_on_touch(true);
|
||||
* ```
|
||||
*
|
||||
* The display will wake up by touch.
|
||||
*/
|
||||
void set_auto_wake_on_touch(bool auto_wake);
|
||||
|
||||
/**
|
||||
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
|
||||
* @param exit_reparse True or false. When exit_reparse is true, the exit reparse command
|
||||
* will be sent before requesting the connection from Nextion.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_exit_reparse_on_start(true);
|
||||
* ```
|
||||
*
|
||||
* The display will be requested to leave active reparse mode before setup.
|
||||
*/
|
||||
void set_exit_reparse_on_start(bool exit_reparse);
|
||||
|
||||
/**
|
||||
* Sets whether the Nextion display should skip the connection handshake process.
|
||||
* @param skip_handshake True or false. When skip_connection_handshake is true,
|
||||
@@ -1172,15 +1102,75 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
|
||||
void update_components_by_prefix(const std::string &prefix);
|
||||
|
||||
void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) {
|
||||
this->touch_sleep_timeout_ = touch_sleep_timeout;
|
||||
}
|
||||
void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; }
|
||||
void set_start_up_page_internal(uint8_t start_up_page) { this->start_up_page_ = start_up_page; }
|
||||
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; }
|
||||
void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) {
|
||||
this->exit_reparse_on_start_ = exit_reparse_on_start;
|
||||
}
|
||||
/**
|
||||
* Set the touch sleep timeout of the display.
|
||||
* @param timeout Timeout in seconds.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_touch_sleep_timeout(30);
|
||||
* ```
|
||||
*
|
||||
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
|
||||
* `thup`.
|
||||
*/
|
||||
void set_touch_sleep_timeout(uint32_t touch_sleep_timeout);
|
||||
|
||||
/**
|
||||
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
|
||||
* @param wake_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
||||
* wakes up to current page.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_wake_up_page(2);
|
||||
* ```
|
||||
*
|
||||
* The display will wake up to page 2.
|
||||
*/
|
||||
void set_wake_up_page(uint8_t wake_up_page = 255);
|
||||
|
||||
/**
|
||||
* Sets which page Nextion loads when connecting to ESPHome.
|
||||
* @param start_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
||||
* wakes up to current page.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_start_up_page(2);
|
||||
* ```
|
||||
*
|
||||
* The display will go to page 2 when it establishes a connection to ESPHome.
|
||||
*/
|
||||
void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; }
|
||||
|
||||
/**
|
||||
* Sets if Nextion should auto-wake from sleep when touch press occurs.
|
||||
* @param auto_wake_on_touch True or false. When auto_wake is true and Nextion is in sleep mode,
|
||||
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_auto_wake_on_touch(true);
|
||||
* ```
|
||||
*
|
||||
* The display will wake up by touch.
|
||||
*/
|
||||
void set_auto_wake_on_touch(bool auto_wake_on_touch);
|
||||
|
||||
/**
|
||||
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
|
||||
* @param exit_reparse_on_start True or false. When exit_reparse_on_start is true, the exit reparse command
|
||||
* will be sent before requesting the connection from Nextion.
|
||||
*
|
||||
* Example:
|
||||
* ```cpp
|
||||
* it.set_exit_reparse_on_start(true);
|
||||
* ```
|
||||
*
|
||||
* The display will be requested to leave active reparse mode before setup.
|
||||
*/
|
||||
void set_exit_reparse_on_start(bool exit_reparse_on_start) { this->exit_reparse_on_start_ = exit_reparse_on_start; }
|
||||
|
||||
/**
|
||||
* @brief Retrieves the number of commands pending in the Nextion command queue.
|
||||
@@ -1217,6 +1207,25 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
*/
|
||||
bool is_updating() override;
|
||||
|
||||
/**
|
||||
* @brief Check if the Nextion display is successfully connected.
|
||||
*
|
||||
* This method returns whether a successful connection has been established with
|
||||
* the Nextion display. A connection is considered established when:
|
||||
*
|
||||
* - The initial handshake with the display is completed successfully, or
|
||||
* - The handshake is skipped via skip_connection_handshake_ flag
|
||||
*
|
||||
* The connection status is particularly useful when:
|
||||
* - Troubleshooting communication issues
|
||||
* - Ensuring the display is ready before sending commands
|
||||
* - Implementing connection-dependent behaviors
|
||||
*
|
||||
* @return true if the Nextion display is connected and ready to receive commands
|
||||
* @return false if the display is not yet connected or connection was lost
|
||||
*/
|
||||
bool is_connected() { return this->is_connected_; }
|
||||
|
||||
protected:
|
||||
std::deque<NextionQueue *> nextion_queue_;
|
||||
std::deque<NextionQueue *> waveform_queue_;
|
||||
@@ -1315,8 +1324,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||
|
||||
#endif // USE_NEXTION_TFT_UPLOAD
|
||||
|
||||
bool get_is_connected_() { return this->is_connected_; }
|
||||
|
||||
bool check_connect_();
|
||||
|
||||
std::vector<NextionComponentBase *> touch_;
|
||||
|
||||
@@ -10,19 +10,19 @@ static const char *const TAG = "nextion";
|
||||
// Sleep safe commands
|
||||
void Nextion::soft_reset() { this->send_command_("rest"); }
|
||||
|
||||
void Nextion::set_wake_up_page(uint8_t page_id) {
|
||||
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true);
|
||||
void Nextion::set_wake_up_page(uint8_t wake_up_page) {
|
||||
this->wake_up_page_ = wake_up_page;
|
||||
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true);
|
||||
}
|
||||
|
||||
void Nextion::set_start_up_page(uint8_t page_id) { this->start_up_page_ = page_id; }
|
||||
|
||||
void Nextion::set_touch_sleep_timeout(uint16_t timeout) {
|
||||
if (timeout < 3) {
|
||||
void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
|
||||
if (touch_sleep_timeout < 3) {
|
||||
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
|
||||
return;
|
||||
}
|
||||
|
||||
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true);
|
||||
this->touch_sleep_timeout_ = touch_sleep_timeout;
|
||||
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", touch_sleep_timeout, true);
|
||||
}
|
||||
|
||||
void Nextion::sleep(bool sleep) {
|
||||
@@ -54,7 +54,6 @@ bool Nextion::set_protocol_reparse_mode(bool active_mode) {
|
||||
this->ignore_is_setup_ = false;
|
||||
return all_commands_sent;
|
||||
}
|
||||
void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; }
|
||||
|
||||
// Set Colors - Background
|
||||
void Nextion::set_component_background_color(const char *component, uint16_t color) {
|
||||
@@ -191,8 +190,9 @@ void Nextion::set_backlight_brightness(float brightness) {
|
||||
this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100));
|
||||
}
|
||||
|
||||
void Nextion::set_auto_wake_on_touch(bool auto_wake) {
|
||||
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0);
|
||||
void Nextion::set_auto_wake_on_touch(bool auto_wake_on_touch) {
|
||||
this->auto_wake_on_touch_ = auto_wake_on_touch;
|
||||
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake_on_touch ? 1 : 0);
|
||||
}
|
||||
|
||||
// General Component
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from typing import Any
|
||||
|
||||
import logging
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266
|
||||
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266, CONF_TRIGGER_ID
|
||||
from . import const, schema, validate, generate
|
||||
|
||||
CODEOWNERS = ["@olegtarasov"]
|
||||
@@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active"
|
||||
CONF_SUMMER_MODE_ACTIVE = "summer_mode_active"
|
||||
CONF_DHW_BLOCK = "dhw_block"
|
||||
CONF_SYNC_MODE = "sync_mode"
|
||||
CONF_OPENTHERM_VERSION = "opentherm_version"
|
||||
CONF_OPENTHERM_VERSION = "opentherm_version" # Deprecated, will be removed
|
||||
CONF_BEFORE_SEND = "before_send"
|
||||
CONF_BEFORE_PROCESS_RESPONSE = "before_process_response"
|
||||
|
||||
# Triggers
|
||||
BeforeSendTrigger = generate.opentherm_ns.class_(
|
||||
"BeforeSendTrigger",
|
||||
automation.Trigger.template(generate.OpenthermData.operator("ref")),
|
||||
)
|
||||
BeforeProcessResponseTrigger = generate.opentherm_ns.class_(
|
||||
"BeforeProcessResponseTrigger",
|
||||
automation.Trigger.template(generate.OpenthermData.operator("ref")),
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
@@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean,
|
||||
cv.Optional(CONF_DHW_BLOCK, False): cv.boolean,
|
||||
cv.Optional(CONF_SYNC_MODE, False): cv.boolean,
|
||||
cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float,
|
||||
cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, # Deprecated
|
||||
cv.Optional(CONF_BEFORE_SEND): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BeforeSendTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_BEFORE_PROCESS_RESPONSE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BeforeProcessResponseTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(
|
||||
@@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All(
|
||||
schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor))
|
||||
)
|
||||
)
|
||||
.extend(
|
||||
validate.create_entities_schema(
|
||||
schema.SETTINGS, (lambda s: s.validation_schema)
|
||||
)
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
|
||||
)
|
||||
@@ -60,18 +93,33 @@ async def to_code(config: dict[str, Any]) -> None:
|
||||
out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN])
|
||||
cg.add(var.set_out_pin(out_pin))
|
||||
|
||||
non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN}
|
||||
non_sensors = {
|
||||
CONF_ID,
|
||||
CONF_IN_PIN,
|
||||
CONF_OUT_PIN,
|
||||
CONF_BEFORE_SEND,
|
||||
CONF_BEFORE_PROCESS_RESPONSE,
|
||||
}
|
||||
input_sensors = []
|
||||
settings = []
|
||||
for key, value in config.items():
|
||||
if key in non_sensors:
|
||||
continue
|
||||
if key in schema.INPUTS:
|
||||
input_sensor = await cg.get_variable(value)
|
||||
cg.add(
|
||||
getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor)
|
||||
)
|
||||
cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor))
|
||||
input_sensors.append(key)
|
||||
elif key in schema.SETTINGS:
|
||||
if value == schema.SETTINGS[key].default_value:
|
||||
continue
|
||||
cg.add(getattr(var, f"set_{key}_{const.SETTING}")(value))
|
||||
settings.append(key)
|
||||
else:
|
||||
if key == CONF_OPENTHERM_VERSION:
|
||||
_LOGGER.warning(
|
||||
"opentherm_version is deprecated and will be removed in esphome 2025.2.0\n"
|
||||
"Please change to 'opentherm_version_controller'."
|
||||
)
|
||||
cg.add(getattr(var, f"set_{key}")(value))
|
||||
|
||||
if len(input_sensors) > 0:
|
||||
@@ -81,3 +129,21 @@ async def to_code(config: dict[str, Any]) -> None:
|
||||
)
|
||||
generate.define_readers(const.INPUT_SENSOR, input_sensors)
|
||||
generate.add_messages(var, input_sensors, schema.INPUTS)
|
||||
|
||||
if len(settings) > 0:
|
||||
generate.define_has_settings(settings, schema.SETTINGS)
|
||||
generate.define_message_handler(const.SETTING, settings, schema.SETTINGS)
|
||||
generate.define_setting_readers(const.SETTING, settings)
|
||||
generate.add_messages(var, settings, schema.SETTINGS)
|
||||
|
||||
for conf in config.get(CONF_BEFORE_SEND, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
|
||||
)
|
||||
|
||||
for conf in config.get(CONF_BEFORE_PROCESS_RESPONSE, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(
|
||||
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
|
||||
)
|
||||
|
||||
25
esphome/components/opentherm/automation.h
Normal file
25
esphome/components/opentherm/automation.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "hub.h"
|
||||
#include "opentherm.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace opentherm {
|
||||
|
||||
class BeforeSendTrigger : public Trigger<OpenthermData &> {
|
||||
public:
|
||||
BeforeSendTrigger(OpenthermHub *hub) {
|
||||
hub->add_on_before_send_callback([this](OpenthermData &x) { this->trigger(x); });
|
||||
}
|
||||
};
|
||||
|
||||
class BeforeProcessResponseTrigger : public Trigger<OpenthermData &> {
|
||||
public:
|
||||
BeforeProcessResponseTrigger(OpenthermHub *hub) {
|
||||
hub->add_on_before_process_response_callback([this](OpenthermData &x) { this->trigger(x); });
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace opentherm
|
||||
} // namespace esphome
|
||||
@@ -9,3 +9,4 @@ SWITCH = "switch"
|
||||
NUMBER = "number"
|
||||
OUTPUT = "output"
|
||||
INPUT_SENSOR = "input_sensor"
|
||||
SETTING = "setting"
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from collections.abc import Awaitable
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Optional
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID
|
||||
from . import const
|
||||
from .schema import TSchema
|
||||
from .schema import TSchema, SettingSchema
|
||||
|
||||
opentherm_ns = cg.esphome_ns.namespace("opentherm")
|
||||
OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
|
||||
OpenthermData = opentherm_ns.class_("OpenthermData")
|
||||
|
||||
|
||||
def define_has_component(component_type: str, keys: list[str]) -> None:
|
||||
@@ -21,6 +22,24 @@ def define_has_component(component_type: str, keys: list[str]) -> None:
|
||||
cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}")
|
||||
|
||||
|
||||
# We need a separate set of macros for settings because there are different backing field types we need to take
|
||||
# into account
|
||||
def define_has_settings(keys: list[str], schemas: dict[str, SettingSchema]) -> None:
|
||||
cg.add_define(
|
||||
"OPENTHERM_SETTING_LIST(F, sep)",
|
||||
cg.RawExpression(
|
||||
" sep ".join(
|
||||
map(
|
||||
lambda key: f"F({schemas[key].backing_type}, {key}_setting, {schemas[key].default_value})",
|
||||
keys,
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
for key in keys:
|
||||
cg.add_define(f"OPENTHERM_HAS_SETTING_{key}")
|
||||
|
||||
|
||||
def define_message_handler(
|
||||
component_type: str, keys: list[str], schemas: dict[str, TSchema]
|
||||
) -> None:
|
||||
@@ -74,16 +93,30 @@ def define_readers(component_type: str, keys: list[str]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
|
||||
messages: set[tuple[str, bool]] = set()
|
||||
def define_setting_readers(component_type: str, keys: list[str]) -> None:
|
||||
for key in keys:
|
||||
messages.add((schemas[key].message, schemas[key].keep_updated))
|
||||
for msg, keep_updated in messages:
|
||||
cg.add_define(
|
||||
f"OPENTHERM_READ_{key}",
|
||||
cg.RawExpression(f"this->{key}_{component_type.lower()}"),
|
||||
)
|
||||
|
||||
|
||||
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
|
||||
messages: dict[str, tuple[bool, Optional[int]]] = {}
|
||||
for key in keys:
|
||||
messages[schemas[key].message] = (
|
||||
schemas[key].keep_updated,
|
||||
schemas[key].order if hasattr(schemas[key], "order") else None,
|
||||
)
|
||||
for msg, (keep_updated, order) in messages.items():
|
||||
msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
|
||||
if keep_updated:
|
||||
cg.add(hub.add_repeating_message(msg_expr))
|
||||
else:
|
||||
cg.add(hub.add_initial_message(msg_expr))
|
||||
if order is not None:
|
||||
cg.add(hub.add_initial_message(msg_expr, order))
|
||||
else:
|
||||
cg.add(hub.add_initial_message(msg_expr))
|
||||
|
||||
|
||||
def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None:
|
||||
|
||||
@@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); }
|
||||
OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
||||
OpenthermData data;
|
||||
data.type = 0;
|
||||
data.id = 0;
|
||||
data.id = request_id;
|
||||
data.valueHB = 0;
|
||||
data.valueLB = 0;
|
||||
|
||||
@@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
||||
// NOLINTEND
|
||||
|
||||
data.type = MessageType::READ_DATA;
|
||||
data.id = MessageId::STATUS;
|
||||
data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) |
|
||||
(summer_mode_is_active << 5) | (dhw_blocked << 6);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Another special case is OpenTherm version number which is configured at hub level as a constant
|
||||
if (request_id == MessageId::OT_VERSION_CONTROLLER) {
|
||||
data.type = MessageType::WRITE_DATA;
|
||||
data.id = MessageId::OT_VERSION_CONTROLLER;
|
||||
data.f88(this->opentherm_version_);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Disable incomplete switch statement warnings, because the cases in each
|
||||
// switch are generated based on the configured sensors and inputs.
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wswitch"
|
||||
|
||||
// Next, we start with the write requests from switches and other inputs,
|
||||
// Next, we start with write requests from switches and other inputs,
|
||||
// because we would want to write that data if it is available, rather than
|
||||
// request a read for that type (in the case that both read and write are
|
||||
// supported).
|
||||
@@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
||||
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
||||
OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, ,
|
||||
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
||||
OPENTHERM_SETTING_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_SETTING, ,
|
||||
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Finally, handle the simple read requests, which only change with the message id.
|
||||
switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) }
|
||||
switch (request_id) {
|
||||
OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (request_id) {
|
||||
OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// And if we get here, a message was requested which somehow wasn't handled.
|
||||
// This shouldn't happen due to the way the defines are configured, so we
|
||||
@@ -163,19 +157,37 @@ void OpenthermHub::setup() {
|
||||
// communicate at least once every second. Sending the status request is
|
||||
// good practice anyway.
|
||||
this->add_repeating_message(MessageId::STATUS);
|
||||
|
||||
// Also ensure that we start communication with the STATUS message
|
||||
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::STATUS);
|
||||
|
||||
if (this->opentherm_version_ > 0.0f) {
|
||||
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::OT_VERSION_CONTROLLER);
|
||||
}
|
||||
|
||||
this->current_message_iterator_ = this->initial_messages_.begin();
|
||||
this->write_initial_messages_(this->messages_);
|
||||
this->message_iterator_ = this->messages_.begin();
|
||||
}
|
||||
|
||||
void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
|
||||
|
||||
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
|
||||
void OpenthermHub::write_initial_messages_(std::vector<MessageId> &target) { // NOLINT
|
||||
std::vector<std::pair<MessageId, uint8_t>> sorted;
|
||||
std::copy_if(this->configured_messages_.begin(), this->configured_messages_.end(), std::back_inserter(sorted),
|
||||
[](const std::pair<MessageId, uint8_t> &pair) { return pair.second < REPEATING_MESSAGE_ORDER; });
|
||||
std::sort(sorted.begin(), sorted.end(),
|
||||
[](const std::pair<MessageId, uint8_t> &a, const std::pair<MessageId, uint8_t> &b) {
|
||||
return a.second < b.second;
|
||||
});
|
||||
|
||||
target.clear();
|
||||
std::transform(sorted.begin(), sorted.end(), std::back_inserter(target),
|
||||
[](const std::pair<MessageId, uint8_t> &pair) { return pair.first; });
|
||||
}
|
||||
|
||||
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
|
||||
void OpenthermHub::write_repeating_messages_(std::vector<MessageId> &target) { // NOLINT
|
||||
target.clear();
|
||||
for (auto const &pair : this->configured_messages_) {
|
||||
if (pair.second == REPEATING_MESSAGE_ORDER) {
|
||||
target.push_back(pair.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenthermHub::loop() {
|
||||
if (this->sync_mode_) {
|
||||
this->sync_loop_();
|
||||
@@ -184,29 +196,18 @@ void OpenthermHub::loop() {
|
||||
|
||||
auto cur_time = millis();
|
||||
auto const cur_mode = this->opentherm_->get_mode();
|
||||
|
||||
if (this->handle_error_(cur_mode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (cur_mode) {
|
||||
case OperationMode::WRITE:
|
||||
case OperationMode::READ:
|
||||
case OperationMode::LISTEN:
|
||||
if (!this->check_timings_(cur_time)) {
|
||||
break;
|
||||
}
|
||||
this->last_mode_ = cur_mode;
|
||||
break;
|
||||
case OperationMode::ERROR_PROTOCOL:
|
||||
if (this->last_mode_ == OperationMode::WRITE) {
|
||||
this->handle_protocol_write_error_();
|
||||
} else if (this->last_mode_ == OperationMode::READ) {
|
||||
this->handle_protocol_read_error_();
|
||||
}
|
||||
|
||||
this->stop_opentherm_();
|
||||
break;
|
||||
case OperationMode::ERROR_TIMEOUT:
|
||||
this->handle_timeout_error_();
|
||||
this->stop_opentherm_();
|
||||
break;
|
||||
case OperationMode::IDLE:
|
||||
this->check_timings_(cur_time);
|
||||
if (this->should_skip_loop_(cur_time)) {
|
||||
break;
|
||||
}
|
||||
@@ -219,6 +220,28 @@ void OpenthermHub::loop() {
|
||||
case OperationMode::RECEIVED:
|
||||
this->read_response_();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this->last_mode_ = cur_mode;
|
||||
}
|
||||
|
||||
bool OpenthermHub::handle_error_(OperationMode mode) {
|
||||
switch (mode) {
|
||||
case OperationMode::ERROR_PROTOCOL:
|
||||
// Protocol error can happen only while reading boiler response.
|
||||
this->handle_protocol_error_();
|
||||
return true;
|
||||
case OperationMode::ERROR_TIMEOUT:
|
||||
// Timeout error might happen while we wait for device to respond.
|
||||
this->handle_timeout_error_();
|
||||
return true;
|
||||
case OperationMode::ERROR_TIMER:
|
||||
// Timer error can happen only on ESP32.
|
||||
this->handle_timer_error_();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,16 +260,20 @@ void OpenthermHub::sync_loop_() {
|
||||
}
|
||||
|
||||
this->start_conversation_();
|
||||
// There may be a timer error at this point
|
||||
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Spin while message is being sent to device
|
||||
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
|
||||
ESP_LOGE(TAG, "Hub timeout triggered during send");
|
||||
this->stop_opentherm_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->opentherm_->is_error()) {
|
||||
this->handle_protocol_write_error_();
|
||||
this->stop_opentherm_();
|
||||
// Check for errors and ensure we are in the right state (message sent successfully)
|
||||
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||
return;
|
||||
} else if (!this->opentherm_->is_sent()) {
|
||||
ESP_LOGW(TAG, "Unexpected state after sending request: %s",
|
||||
@@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() {
|
||||
|
||||
// Listen for the response
|
||||
this->opentherm_->listen();
|
||||
// There may be a timer error at this point
|
||||
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Spin while response is being received
|
||||
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
|
||||
ESP_LOGE(TAG, "Hub timeout triggered during receive");
|
||||
this->stop_opentherm_();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->opentherm_->is_timeout()) {
|
||||
this->handle_timeout_error_();
|
||||
this->stop_opentherm_();
|
||||
return;
|
||||
} else if (this->opentherm_->is_protocol_error()) {
|
||||
this->handle_protocol_read_error_();
|
||||
this->stop_opentherm_();
|
||||
// Check for errors and ensure we are in the right state (message received successfully)
|
||||
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||
return;
|
||||
} else if (!this->opentherm_->has_message()) {
|
||||
ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
|
||||
@@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() {
|
||||
this->read_response_();
|
||||
}
|
||||
|
||||
bool OpenthermHub::check_timings_(uint32_t cur_time) {
|
||||
void OpenthermHub::check_timings_(uint32_t cur_time) {
|
||||
if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) {
|
||||
ESP_LOGW(TAG,
|
||||
"%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other "
|
||||
"components that might slow the loop down.",
|
||||
(int) (cur_time - this->last_conversation_start_));
|
||||
this->stop_opentherm_();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
|
||||
@@ -304,14 +328,17 @@ bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
|
||||
}
|
||||
|
||||
void OpenthermHub::start_conversation_() {
|
||||
if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) {
|
||||
this->sending_initial_ = false;
|
||||
this->current_message_iterator_ = this->repeating_messages_.begin();
|
||||
} else if (this->current_message_iterator_ == this->repeating_messages_.end()) {
|
||||
this->current_message_iterator_ = this->repeating_messages_.begin();
|
||||
if (this->message_iterator_ == this->messages_.end()) {
|
||||
if (this->sending_initial_) {
|
||||
this->sending_initial_ = false;
|
||||
this->write_repeating_messages_(this->messages_);
|
||||
}
|
||||
this->message_iterator_ = this->messages_.begin();
|
||||
}
|
||||
|
||||
auto request = this->build_request_(*this->current_message_iterator_);
|
||||
auto request = this->build_request_(*this->message_iterator_);
|
||||
|
||||
this->before_send_callback_.call(request);
|
||||
|
||||
ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
|
||||
this->opentherm_->message_id_to_str((MessageId) request.id));
|
||||
@@ -331,37 +358,48 @@ void OpenthermHub::read_response_() {
|
||||
|
||||
this->stop_opentherm_();
|
||||
|
||||
this->before_process_response_callback_.call(response);
|
||||
this->process_response(response);
|
||||
|
||||
this->current_message_iterator_++;
|
||||
this->message_iterator_++;
|
||||
}
|
||||
|
||||
void OpenthermHub::stop_opentherm_() {
|
||||
this->opentherm_->stop();
|
||||
this->last_conversation_end_ = millis();
|
||||
}
|
||||
void OpenthermHub::handle_protocol_write_error_() {
|
||||
ESP_LOGW(TAG, "Error while sending request: %s",
|
||||
this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
|
||||
this->opentherm_->debug_data(this->last_request_);
|
||||
}
|
||||
void OpenthermHub::handle_protocol_read_error_() {
|
||||
|
||||
void OpenthermHub::handle_protocol_error_() {
|
||||
OpenThermError error;
|
||||
this->opentherm_->get_protocol_error(error);
|
||||
ESP_LOGW(TAG, "Protocol error occured while receiving response: %s",
|
||||
this->opentherm_->protocol_error_to_to_str(error.error_type));
|
||||
this->opentherm_->protocol_error_to_str(error.error_type));
|
||||
this->opentherm_->debug_error(error);
|
||||
}
|
||||
void OpenthermHub::handle_timeout_error_() {
|
||||
ESP_LOGW(TAG, "Receive response timed out at a protocol level");
|
||||
this->stop_opentherm_();
|
||||
}
|
||||
|
||||
void OpenthermHub::handle_timeout_error_() {
|
||||
ESP_LOGW(TAG, "Timeout while waiting for response from device");
|
||||
this->stop_opentherm_();
|
||||
}
|
||||
|
||||
void OpenthermHub::handle_timer_error_() {
|
||||
this->opentherm_->report_and_reset_timer_error();
|
||||
this->stop_opentherm_();
|
||||
// Timer error is critical, there is no point in retrying.
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
void OpenthermHub::dump_config() {
|
||||
std::vector<MessageId> initial_messages;
|
||||
std::vector<MessageId> repeating_messages;
|
||||
this->write_initial_messages_(initial_messages);
|
||||
this->write_repeating_messages_(repeating_messages);
|
||||
|
||||
ESP_LOGCONFIG(TAG, "OpenTherm:");
|
||||
LOG_PIN(" In: ", this->in_pin_);
|
||||
LOG_PIN(" Out: ", this->out_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_);
|
||||
ESP_LOGCONFIG(TAG, " Sync mode: %s", YESNO(this->sync_mode_));
|
||||
ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, )));
|
||||
ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )));
|
||||
ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, )));
|
||||
@@ -369,12 +407,12 @@ void OpenthermHub::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, )));
|
||||
ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, )));
|
||||
ESP_LOGCONFIG(TAG, " Initial requests:");
|
||||
for (auto type : this->initial_messages_) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
|
||||
for (auto type : initial_messages) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Repeating requests:");
|
||||
for (auto type : this->repeating_messages_) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
|
||||
for (auto type : repeating_messages) {
|
||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
namespace esphome {
|
||||
namespace opentherm {
|
||||
|
||||
static const uint8_t REPEATING_MESSAGE_ORDER = 255;
|
||||
static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254;
|
||||
|
||||
// OpenTherm component for ESPHome
|
||||
class OpenthermHub : public Component {
|
||||
protected:
|
||||
@@ -58,15 +61,12 @@ class OpenthermHub : public Component {
|
||||
|
||||
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
|
||||
|
||||
// The set of initial messages to send on starting communication with the boiler
|
||||
std::vector<MessageId> initial_messages_;
|
||||
// and the repeating messages which are sent repeatedly to update various sensors
|
||||
// and boiler parameters (like the setpoint).
|
||||
std::vector<MessageId> repeating_messages_;
|
||||
// Indicates if we are still working on the initial requests or not
|
||||
OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, )
|
||||
|
||||
bool sending_initial_ = true;
|
||||
// Index for the current request in one of the _requests sets.
|
||||
std::vector<MessageId>::const_iterator current_message_iterator_;
|
||||
std::unordered_map<MessageId, uint8_t> configured_messages_;
|
||||
std::vector<MessageId> messages_;
|
||||
std::vector<MessageId>::const_iterator message_iterator_;
|
||||
|
||||
uint32_t last_conversation_start_ = 0;
|
||||
uint32_t last_conversation_end_ = 0;
|
||||
@@ -78,20 +78,25 @@ class OpenthermHub : public Component {
|
||||
// Very likely to happen while using Dallas temperature sensors.
|
||||
bool sync_mode_ = false;
|
||||
|
||||
float opentherm_version_ = 0.0f;
|
||||
CallbackManager<void(OpenthermData &)> before_send_callback_;
|
||||
CallbackManager<void(OpenthermData &)> before_process_response_callback_;
|
||||
|
||||
// Create OpenTherm messages based on the message id
|
||||
OpenthermData build_request_(MessageId request_id) const;
|
||||
void handle_protocol_write_error_();
|
||||
void handle_protocol_read_error_();
|
||||
bool handle_error_(OperationMode mode);
|
||||
void handle_protocol_error_();
|
||||
void handle_timeout_error_();
|
||||
void handle_timer_error_();
|
||||
void stop_opentherm_();
|
||||
void start_conversation_();
|
||||
void read_response_();
|
||||
bool check_timings_(uint32_t cur_time);
|
||||
void check_timings_(uint32_t cur_time);
|
||||
bool should_skip_loop_(uint32_t cur_time) const;
|
||||
void sync_loop_();
|
||||
|
||||
void write_initial_messages_(std::vector<MessageId> &target);
|
||||
void write_repeating_messages_(std::vector<MessageId> &target);
|
||||
|
||||
template<typename F> bool spin_wait_(uint32_t timeout, F func) {
|
||||
auto start_time = millis();
|
||||
while (func()) {
|
||||
@@ -127,13 +132,18 @@ class OpenthermHub : public Component {
|
||||
|
||||
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, )
|
||||
|
||||
OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, )
|
||||
|
||||
// Add a request to the vector of initial requests
|
||||
void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); }
|
||||
void add_initial_message(MessageId message_id) {
|
||||
this->configured_messages_[message_id] = INITIAL_UNORDERED_MESSAGE_ORDER;
|
||||
}
|
||||
void add_initial_message(MessageId message_id, uint8_t order) { this->configured_messages_[message_id] = order; }
|
||||
// Add a request to the set of repeating requests. Note that a large number of repeating
|
||||
// requests will slow down communication with the boiler. Each request may take up to 1 second,
|
||||
// so with all sensors enabled, it may take about half a minute before a change in setpoint
|
||||
// will be processed.
|
||||
void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); }
|
||||
void add_repeating_message(MessageId message_id) { this->configured_messages_[message_id] = REPEATING_MESSAGE_ORDER; }
|
||||
|
||||
// There are seven status variables, which can either be set as a simple variable,
|
||||
// or using a switch. ch_enable and dhw_enable default to true, the others to false.
|
||||
@@ -149,7 +159,13 @@ class OpenthermHub : public Component {
|
||||
void set_summer_mode_active(bool value) { this->summer_mode_active = value; }
|
||||
void set_dhw_block(bool value) { this->dhw_block = value; }
|
||||
void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; }
|
||||
void set_opentherm_version(float value) { this->opentherm_version_ = value; }
|
||||
|
||||
void add_on_before_send_callback(std::function<void(OpenthermData &)> &&callback) {
|
||||
this->before_send_callback_.add(std::move(callback));
|
||||
}
|
||||
void add_on_before_process_response_callback(std::function<void(OpenthermData &)> &&callback) {
|
||||
this->before_process_response_callback_.add(std::move(callback));
|
||||
}
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
|
||||
@@ -52,7 +52,9 @@ bool OpenTherm::initialize() {
|
||||
OpenTherm::instance = this;
|
||||
#endif
|
||||
this->in_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->in_pin_->setup();
|
||||
this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->out_pin_->setup();
|
||||
this->out_pin_->digital_write(true);
|
||||
|
||||
#if defined(ESP32) || defined(USE_ESP_IDF)
|
||||
@@ -182,7 +184,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
|
||||
}
|
||||
arg->capture_ = 1; // reset counter
|
||||
} else if (arg->capture_ > 0xFF) {
|
||||
// no change for too long, invalid mancheter encoding
|
||||
// no change for too long, invalid manchester encoding
|
||||
arg->mode_ = OperationMode::ERROR_PROTOCOL;
|
||||
arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
|
||||
arg->stop_timer_();
|
||||
@@ -312,21 +314,31 @@ bool OpenTherm::init_esp32_timer_() {
|
||||
}
|
||||
|
||||
void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
|
||||
esp_err_t result;
|
||||
// We will report timer errors outside of interrupt handler
|
||||
this->timer_error_ = ESP_OK;
|
||||
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
|
||||
|
||||
result = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error);
|
||||
this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
|
||||
if (this->timer_error_ != ESP_OK) {
|
||||
this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_ERROR;
|
||||
return;
|
||||
}
|
||||
this->timer_error_ = timer_start(this->timer_group_, this->timer_idx_);
|
||||
if (this->timer_error_ != ESP_OK) {
|
||||
this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
void OpenTherm::report_and_reset_timer_error() {
|
||||
if (this->timer_error_ == ESP_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
result = timer_start(this->timer_group_, this->timer_idx_);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error);
|
||||
return;
|
||||
}
|
||||
ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_),
|
||||
esp_err_to_name(this->timer_error_));
|
||||
|
||||
this->timer_error_ = ESP_OK;
|
||||
this->timer_error_type_ = NO_TIMER_ERROR;
|
||||
}
|
||||
|
||||
// 5 kHz timer_
|
||||
@@ -343,21 +355,18 @@ void IRAM_ATTR OpenTherm::start_write_timer_() {
|
||||
|
||||
void IRAM_ATTR OpenTherm::stop_timer_() {
|
||||
InterruptLock const lock;
|
||||
// We will report timer errors outside of interrupt handler
|
||||
this->timer_error_ = ESP_OK;
|
||||
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
|
||||
|
||||
esp_err_t result;
|
||||
|
||||
result = timer_pause(this->timer_group_, this->timer_idx_);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error);
|
||||
this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_);
|
||||
if (this->timer_error_ != ESP_OK) {
|
||||
this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
|
||||
return;
|
||||
}
|
||||
|
||||
result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
|
||||
if (result != ESP_OK) {
|
||||
const auto *error = esp_err_to_name(result);
|
||||
ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error);
|
||||
return;
|
||||
this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
|
||||
if (this->timer_error_ != ESP_OK) {
|
||||
this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,6 +395,9 @@ void IRAM_ATTR OpenTherm::stop_timer_() {
|
||||
timer1_detachInterrupt();
|
||||
}
|
||||
|
||||
// There is nothing to report on ESP8266
|
||||
void OpenTherm::report_and_reset_timer_error() {}
|
||||
|
||||
#endif // END ESP8266
|
||||
|
||||
// https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
|
||||
@@ -412,11 +424,12 @@ const char *OpenTherm::operation_mode_to_str(OperationMode mode) {
|
||||
TO_STRING_MEMBER(SENT)
|
||||
TO_STRING_MEMBER(ERROR_PROTOCOL)
|
||||
TO_STRING_MEMBER(ERROR_TIMEOUT)
|
||||
TO_STRING_MEMBER(ERROR_TIMER)
|
||||
default:
|
||||
return "<INVALID>";
|
||||
}
|
||||
}
|
||||
const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
|
||||
const char *OpenTherm::protocol_error_to_str(ProtocolErrorType error_type) {
|
||||
switch (error_type) {
|
||||
TO_STRING_MEMBER(NO_ERROR)
|
||||
TO_STRING_MEMBER(NO_TRANSITION)
|
||||
@@ -427,6 +440,17 @@ const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
|
||||
return "<INVALID>";
|
||||
}
|
||||
}
|
||||
const char *OpenTherm::timer_error_to_str(TimerErrorType error_type) {
|
||||
switch (error_type) {
|
||||
TO_STRING_MEMBER(NO_TIMER_ERROR)
|
||||
TO_STRING_MEMBER(SET_ALARM_VALUE_ERROR)
|
||||
TO_STRING_MEMBER(TIMER_START_ERROR)
|
||||
TO_STRING_MEMBER(TIMER_PAUSE_ERROR)
|
||||
TO_STRING_MEMBER(SET_COUNTER_VALUE_ERROR)
|
||||
default:
|
||||
return "<INVALID>";
|
||||
}
|
||||
}
|
||||
const char *OpenTherm::message_type_to_str(MessageType message_type) {
|
||||
switch (message_type) {
|
||||
TO_STRING_MEMBER(READ_DATA)
|
||||
|
||||
@@ -36,11 +36,12 @@ enum OperationMode {
|
||||
READ = 2, // reading 32-bit data frame
|
||||
RECEIVED = 3, // data frame received with valid start and stop bit
|
||||
|
||||
WRITE = 4, // writing data with timer_
|
||||
WRITE = 4, // writing data to output
|
||||
SENT = 5, // all data written to output
|
||||
|
||||
ERROR_PROTOCOL = 8, // manchester protocol data transfer error
|
||||
ERROR_TIMEOUT = 9 // read timeout
|
||||
ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
|
||||
ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
|
||||
ERROR_TIMER = 10 // error operating the ESP32 timer
|
||||
};
|
||||
|
||||
enum ProtocolErrorType {
|
||||
@@ -51,6 +52,14 @@ enum ProtocolErrorType {
|
||||
NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
|
||||
};
|
||||
|
||||
enum TimerErrorType {
|
||||
NO_TIMER_ERROR = 0, // No error
|
||||
SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
|
||||
TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
|
||||
TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
|
||||
SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
|
||||
};
|
||||
|
||||
enum MessageType {
|
||||
READ_DATA = 0,
|
||||
READ_ACK = 4,
|
||||
@@ -299,7 +308,9 @@ class OpenTherm {
|
||||
*
|
||||
* @return true if last listen() or send() operation ends up with an error.
|
||||
*/
|
||||
bool is_error() { return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL; }
|
||||
bool is_error() {
|
||||
return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether last listen() or send() operation ends up with a *timeout* error
|
||||
@@ -313,14 +324,22 @@ class OpenTherm {
|
||||
*/
|
||||
bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; }
|
||||
|
||||
/**
|
||||
* Indicates whether start_esp32_timer_() or stop_timer_() had an error. Only relevant when used on ESP32.
|
||||
* @return true if there was an error.
|
||||
*/
|
||||
bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
|
||||
|
||||
bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
|
||||
|
||||
OperationMode get_mode() { return mode_; }
|
||||
|
||||
void debug_data(OpenthermData &data);
|
||||
void debug_error(OpenThermError &error) const;
|
||||
void report_and_reset_timer_error();
|
||||
|
||||
const char *protocol_error_to_to_str(ProtocolErrorType error_type);
|
||||
const char *protocol_error_to_str(ProtocolErrorType error_type);
|
||||
const char *timer_error_to_str(TimerErrorType error_type);
|
||||
const char *message_type_to_str(MessageType message_type);
|
||||
const char *operation_mode_to_str(OperationMode mode);
|
||||
const char *message_id_to_str(MessageId id);
|
||||
@@ -349,10 +368,12 @@ class OpenTherm {
|
||||
uint32_t data_;
|
||||
uint8_t bit_pos_;
|
||||
int32_t timeout_counter_; // <0 no timeout
|
||||
|
||||
int32_t device_timeout_;
|
||||
|
||||
#if defined(ESP32) || defined(USE_ESP_IDF)
|
||||
esp_err_t timer_error_ = ESP_OK;
|
||||
TimerErrorType timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
|
||||
|
||||
bool init_esp32_timer_();
|
||||
void start_esp32_timer_(uint64_t alarm_value);
|
||||
#endif
|
||||
|
||||
@@ -28,6 +28,9 @@ namespace opentherm {
|
||||
#ifndef OPENTHERM_INPUT_SENSOR_LIST
|
||||
#define OPENTHERM_INPUT_SENSOR_LIST(F, sep)
|
||||
#endif
|
||||
#ifndef OPENTHERM_SETTING_LIST
|
||||
#define OPENTHERM_SETTING_LIST(F, sep)
|
||||
#endif
|
||||
|
||||
// Use macros to create fields for every entity specified in the ESPHome configuration
|
||||
#define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity;
|
||||
@@ -36,6 +39,7 @@ namespace opentherm {
|
||||
#define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity;
|
||||
#define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity;
|
||||
#define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity;
|
||||
#define OPENTHERM_DECLARE_SETTING(type, entity, def) type entity = def;
|
||||
|
||||
// Setter macros
|
||||
#define OPENTHERM_SET_SENSOR(entity) \
|
||||
@@ -56,6 +60,9 @@ namespace opentherm {
|
||||
#define OPENTHERM_SET_INPUT_SENSOR(entity) \
|
||||
void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; }
|
||||
|
||||
#define OPENTHERM_SET_SETTING(type, entity, def) \
|
||||
void set_##entity(type value) { this->entity = value; }
|
||||
|
||||
// ===== hub.cpp macros =====
|
||||
|
||||
// *_MESSAGE_HANDLERS are generated in defines.h and look like this:
|
||||
@@ -85,6 +92,9 @@ namespace opentherm {
|
||||
#ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS
|
||||
#define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
|
||||
#endif
|
||||
#ifndef OPENTHERM_SETTING_MESSAGE_HANDLERS
|
||||
#define OPENTHERM_SETTING_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
|
||||
#endif
|
||||
|
||||
// Write data request builders
|
||||
#define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \
|
||||
@@ -92,6 +102,7 @@ namespace opentherm {
|
||||
data.type = MessageType::WRITE_DATA; \
|
||||
data.id = request_id;
|
||||
#define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data);
|
||||
#define OPENTHERM_MESSAGE_WRITE_SETTING(key, msg_data) message_data::write_##msg_data(this->key, data);
|
||||
#define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \
|
||||
return data; \
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
# inputs of the OpenTherm component.
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, TypeVar
|
||||
from typing import Optional, TypeVar, Any
|
||||
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
@@ -64,6 +65,7 @@ class SensorSchema(EntitySchema):
|
||||
icon: Optional[str] = None
|
||||
device_class: Optional[str] = None
|
||||
disabled_by_default: bool = False
|
||||
order: Optional[int] = None
|
||||
|
||||
|
||||
SENSORS: dict[str, SensorSchema] = {
|
||||
@@ -399,6 +401,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="OT_VERSION_DEVICE",
|
||||
keep_updated=False,
|
||||
message_data="f88",
|
||||
order=2,
|
||||
),
|
||||
"device_type": SensorSchema(
|
||||
description="Device product type",
|
||||
@@ -409,6 +412,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="VERSION_DEVICE",
|
||||
keep_updated=False,
|
||||
message_data="u8_hb",
|
||||
order=0,
|
||||
),
|
||||
"device_version": SensorSchema(
|
||||
description="Device product version",
|
||||
@@ -419,6 +423,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="VERSION_DEVICE",
|
||||
keep_updated=False,
|
||||
message_data="u8_lb",
|
||||
order=0,
|
||||
),
|
||||
"device_id": SensorSchema(
|
||||
description="Device ID code",
|
||||
@@ -429,6 +434,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="u8_lb",
|
||||
order=4,
|
||||
),
|
||||
"otc_hc_ratio_ub": SensorSchema(
|
||||
description="OTC heat curve ratio upper bound",
|
||||
@@ -457,6 +463,7 @@ SENSORS: dict[str, SensorSchema] = {
|
||||
class BinarySensorSchema(EntitySchema):
|
||||
icon: Optional[str] = None
|
||||
device_class: Optional[str] = None
|
||||
order: Optional[int] = None
|
||||
|
||||
|
||||
BINARY_SENSORS: dict[str, BinarySensorSchema] = {
|
||||
@@ -525,48 +532,56 @@ BINARY_SENSORS: dict[str, BinarySensorSchema] = {
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_0",
|
||||
order=4,
|
||||
),
|
||||
"control_type_on_off": BinarySensorSchema(
|
||||
description="Configuration: Control type is on/off",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_1",
|
||||
order=4,
|
||||
),
|
||||
"cooling_supported": BinarySensorSchema(
|
||||
description="Configuration: Cooling supported",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_2",
|
||||
order=4,
|
||||
),
|
||||
"dhw_storage_tank": BinarySensorSchema(
|
||||
description="Configuration: DHW storage tank",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_3",
|
||||
order=4,
|
||||
),
|
||||
"controller_pump_control_allowed": BinarySensorSchema(
|
||||
description="Configuration: Controller pump control allowed",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_4",
|
||||
order=4,
|
||||
),
|
||||
"ch2_present": BinarySensorSchema(
|
||||
description="Configuration: CH2 present",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_5",
|
||||
order=4,
|
||||
),
|
||||
"water_filling": BinarySensorSchema(
|
||||
description="Configuration: Remote water filling",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_6",
|
||||
order=4,
|
||||
),
|
||||
"heat_mode": BinarySensorSchema(
|
||||
description="Configuration: Heating or cooling",
|
||||
message="DEVICE_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="flag8_hb_7",
|
||||
order=4,
|
||||
),
|
||||
"dhw_setpoint_transfer_enabled": BinarySensorSchema(
|
||||
description="Remote boiler parameters: DHW setpoint transfer enabled",
|
||||
@@ -812,3 +827,65 @@ INPUTS: dict[str, InputSchema] = {
|
||||
auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class SettingSchema(EntitySchema):
|
||||
backing_type: str
|
||||
validation_schema: cv.Schema
|
||||
default_value: Any
|
||||
order: Optional[int] = None
|
||||
|
||||
|
||||
SETTINGS: dict[str, SettingSchema] = {
|
||||
"controller_product_type": SettingSchema(
|
||||
description="Controller product type",
|
||||
message="VERSION_CONTROLLER",
|
||||
keep_updated=False,
|
||||
message_data="u8_hb",
|
||||
backing_type="uint8_t",
|
||||
validation_schema=cv.int_range(min=0, max=255),
|
||||
default_value=0,
|
||||
order=1,
|
||||
),
|
||||
"controller_product_version": SettingSchema(
|
||||
description="Controller product version",
|
||||
message="VERSION_CONTROLLER",
|
||||
keep_updated=False,
|
||||
message_data="u8_lb",
|
||||
backing_type="uint8_t",
|
||||
validation_schema=cv.int_range(min=0, max=255),
|
||||
default_value=0,
|
||||
order=1,
|
||||
),
|
||||
"opentherm_version_controller": SettingSchema(
|
||||
description="Version of OpenTherm implemented by controller",
|
||||
message="OT_VERSION_CONTROLLER",
|
||||
keep_updated=False,
|
||||
message_data="f88",
|
||||
backing_type="float",
|
||||
validation_schema=cv.positive_float,
|
||||
default_value=0,
|
||||
order=3,
|
||||
),
|
||||
"controller_configuration": SettingSchema(
|
||||
description="Controller configuration",
|
||||
message="CONTROLLER_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="u8_hb",
|
||||
backing_type="uint8_t",
|
||||
validation_schema=cv.int_range(min=0, max=255),
|
||||
default_value=0,
|
||||
order=5,
|
||||
),
|
||||
"controller_id": SettingSchema(
|
||||
description="Controller ID code",
|
||||
message="CONTROLLER_CONFIG",
|
||||
keep_updated=False,
|
||||
message_data="u8_lb",
|
||||
backing_type="uint8_t",
|
||||
validation_schema=cv.int_range(min=0, max=255),
|
||||
default_value=0,
|
||||
order=5,
|
||||
),
|
||||
}
|
||||
|
||||
@@ -9,12 +9,17 @@ from .schema import TSchema
|
||||
|
||||
|
||||
def create_entities_schema(
|
||||
entities: dict[str, schema.EntitySchema],
|
||||
entities: dict[str, TSchema],
|
||||
get_entity_validation_schema: Callable[[TSchema], cv.Schema],
|
||||
) -> Schema:
|
||||
entity_schema = {}
|
||||
for key, entity in entities.items():
|
||||
entity_schema[cv.Optional(key)] = get_entity_validation_schema(entity)
|
||||
schema_key = (
|
||||
cv.Optional(key, entity.default_value)
|
||||
if hasattr(entity, "default_value")
|
||||
else cv.Optional(key)
|
||||
)
|
||||
entity_schema[schema_key] = get_entity_validation_schema(entity)
|
||||
return cv.Schema(entity_schema)
|
||||
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@ PulseCounterStorageBase *get_storage(bool hw_pcnt) {
|
||||
return (hw_pcnt ? (PulseCounterStorageBase *) (new HwPulseCounterStorage)
|
||||
: (PulseCounterStorageBase *) (new BasicPulseCounterStorage));
|
||||
}
|
||||
#else
|
||||
#else // HAS_PCNT
|
||||
PulseCounterStorageBase *get_storage(bool) { return new BasicPulseCounterStorage; }
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg) {
|
||||
const uint32_t now = micros();
|
||||
@@ -28,14 +28,17 @@ void IRAM_ATTR BasicPulseCounterStorage::gpio_intr(BasicPulseCounterStorage *arg
|
||||
switch (mode) {
|
||||
case PULSE_COUNTER_DISABLE:
|
||||
break;
|
||||
case PULSE_COUNTER_INCREMENT:
|
||||
arg->counter++;
|
||||
break;
|
||||
case PULSE_COUNTER_DECREMENT:
|
||||
arg->counter--;
|
||||
break;
|
||||
case PULSE_COUNTER_INCREMENT: {
|
||||
auto x = arg->counter + 1;
|
||||
arg->counter = x;
|
||||
} break;
|
||||
case PULSE_COUNTER_DECREMENT: {
|
||||
auto x = arg->counter - 1;
|
||||
arg->counter = x;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
this->pin = pin;
|
||||
this->pin->setup();
|
||||
@@ -43,6 +46,7 @@ bool BasicPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
this->pin->attach_interrupt(BasicPulseCounterStorage::gpio_intr, this, gpio::INTERRUPT_ANY_EDGE);
|
||||
return true;
|
||||
}
|
||||
|
||||
pulse_counter_t BasicPulseCounterStorage::read_raw_value() {
|
||||
pulse_counter_t counter = this->counter;
|
||||
pulse_counter_t ret = counter - this->last_value;
|
||||
@@ -141,6 +145,7 @@ bool HwPulseCounterStorage::pulse_counter_setup(InternalGPIOPin *pin) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pulse_counter_t HwPulseCounterStorage::read_raw_value() {
|
||||
pulse_counter_t counter;
|
||||
pcnt_get_counter_value(this->pcnt_unit, &counter);
|
||||
@@ -148,7 +153,7 @@ pulse_counter_t HwPulseCounterStorage::read_raw_value() {
|
||||
this->last_value = counter;
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
void PulseCounterSensor::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up pulse counter '%s'...", this->name_.c_str());
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#if defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
#include <driver/pcnt.h>
|
||||
#define HAS_PCNT
|
||||
#endif
|
||||
#endif // defined(USE_ESP32) && !defined(USE_ESP32_VARIANT_ESP32C3)
|
||||
|
||||
namespace esphome {
|
||||
namespace pulse_counter {
|
||||
@@ -22,9 +22,9 @@ enum PulseCounterCountMode {
|
||||
|
||||
#ifdef HAS_PCNT
|
||||
using pulse_counter_t = int16_t;
|
||||
#else
|
||||
#else // HAS_PCNT
|
||||
using pulse_counter_t = int32_t;
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
struct PulseCounterStorageBase {
|
||||
virtual bool pulse_counter_setup(InternalGPIOPin *pin) = 0;
|
||||
@@ -57,7 +57,7 @@ struct HwPulseCounterStorage : public PulseCounterStorageBase {
|
||||
pcnt_unit_t pcnt_unit;
|
||||
pcnt_channel_t pcnt_channel;
|
||||
};
|
||||
#endif
|
||||
#endif // HAS_PCNT
|
||||
|
||||
PulseCounterStorageBase *get_storage(bool hw_pcnt = false);
|
||||
|
||||
|
||||
@@ -81,16 +81,39 @@ void QMC5883LComponent::dump_config() {
|
||||
}
|
||||
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
void QMC5883LComponent::update() {
|
||||
i2c::ErrorCode err;
|
||||
uint8_t status = false;
|
||||
this->read_byte(QMC5883L_REGISTER_STATUS, &status);
|
||||
// Status byte gets cleared when data is read, so we have to read this first.
|
||||
// If status and two axes are desired, it's possible to save one byte of traffic by enabling
|
||||
// ROL_PNT in setup and reading 7 bytes starting at the status register.
|
||||
// If status and all three axes are desired, using ROL_PNT saves you 3 bytes.
|
||||
// But simply not reading status saves you 4 bytes always and is much simpler.
|
||||
if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG) {
|
||||
err = this->read_register(QMC5883L_REGISTER_STATUS, &status, 1);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("status read failed (%d)", err).c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Always request X,Y,Z regardless if there are sensors for them
|
||||
// to avoid https://github.com/esphome/issues/issues/5731
|
||||
uint16_t raw_x, raw_y, raw_z;
|
||||
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) ||
|
||||
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) ||
|
||||
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) {
|
||||
this->status_set_warning();
|
||||
uint16_t raw[3] = {0};
|
||||
// Z must always be requested, otherwise the data registers will remain locked against updates.
|
||||
// Skipping the Y axis if X and Z are needed actually requires an additional byte of comms.
|
||||
// Starting partway through the axes does save you traffic.
|
||||
uint8_t start, dest;
|
||||
if (this->heading_sensor_ != nullptr || this->x_sensor_ != nullptr) {
|
||||
start = QMC5883L_REGISTER_DATA_X_LSB;
|
||||
dest = 0;
|
||||
} else if (this->y_sensor_ != nullptr) {
|
||||
start = QMC5883L_REGISTER_DATA_Y_LSB;
|
||||
dest = 1;
|
||||
} else {
|
||||
start = QMC5883L_REGISTER_DATA_Z_LSB;
|
||||
dest = 2;
|
||||
}
|
||||
err = this->read_bytes_16_le_(start, &raw[dest], 3 - dest);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("mag read failed (%d)", err).c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,17 +130,18 @@ void QMC5883LComponent::update() {
|
||||
}
|
||||
|
||||
// in µT
|
||||
const float x = int16_t(raw_x) * mg_per_bit * 0.1f;
|
||||
const float y = int16_t(raw_y) * mg_per_bit * 0.1f;
|
||||
const float z = int16_t(raw_z) * mg_per_bit * 0.1f;
|
||||
const float x = int16_t(raw[0]) * mg_per_bit * 0.1f;
|
||||
const float y = int16_t(raw[1]) * mg_per_bit * 0.1f;
|
||||
const float z = int16_t(raw[2]) * mg_per_bit * 0.1f;
|
||||
|
||||
float heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
|
||||
|
||||
float temp = NAN;
|
||||
if (this->temperature_sensor_ != nullptr) {
|
||||
uint16_t raw_temp;
|
||||
if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) {
|
||||
this->status_set_warning();
|
||||
err = this->read_bytes_16_le_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
this->status_set_warning(str_sprintf("temp read failed (%d)", err).c_str());
|
||||
return;
|
||||
}
|
||||
temp = int16_t(raw_temp) * 0.01f;
|
||||
@@ -138,11 +162,13 @@ void QMC5883LComponent::update() {
|
||||
this->temperature_sensor_->publish_state(temp);
|
||||
}
|
||||
|
||||
bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) {
|
||||
if (!this->read_byte_16(a_register, data))
|
||||
return false;
|
||||
*data = (*data & 0x00FF) << 8 | (*data & 0xFF00) >> 8; // Flip Byte order, LSB first;
|
||||
return true;
|
||||
i2c::ErrorCode QMC5883LComponent::read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len) {
|
||||
i2c::ErrorCode err = this->read_register(a_register, reinterpret_cast<uint8_t *>(data), len * 2);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return err;
|
||||
for (size_t i = 0; i < len; i++)
|
||||
data[i] = convert_little_endian(data[i]);
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace qmc5883l
|
||||
|
||||
@@ -55,7 +55,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
NONE = 0,
|
||||
COMMUNICATION_FAILED,
|
||||
} error_code_;
|
||||
bool read_byte_16_(uint8_t a_register, uint16_t *data);
|
||||
i2c::ErrorCode read_bytes_16_le_(uint8_t a_register, uint16_t *data, uint8_t len = 1);
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import remote_base, esp32_rmt
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import esp32_rmt, remote_base
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUFFER_SIZE,
|
||||
CONF_CLOCK_DIVIDER,
|
||||
CONF_DUMP,
|
||||
CONF_FILTER,
|
||||
CONF_ID,
|
||||
CONF_IDLE,
|
||||
CONF_MEMORY_BLOCKS,
|
||||
CONF_PIN,
|
||||
CONF_RMT_CHANNEL,
|
||||
CONF_TOLERANCE,
|
||||
CONF_TYPE,
|
||||
CONF_MEMORY_BLOCKS,
|
||||
CONF_RMT_CHANNEL,
|
||||
CONF_VALUE,
|
||||
)
|
||||
from esphome.core import CORE, TimePeriod
|
||||
|
||||
CONF_CLOCK_DIVIDER = "clock_divider"
|
||||
|
||||
AUTO_LOAD = ["remote_base"]
|
||||
remote_receiver_ns = cg.esphome_ns.namespace("remote_receiver")
|
||||
remote_base_ns = cg.esphome_ns.namespace("remote_base")
|
||||
|
||||
@@ -93,13 +93,17 @@ void IRAM_ATTR HOT RotaryEncoderSensorStore::gpio_intr(RotaryEncoderSensorStore
|
||||
int8_t rotation_dir = 0;
|
||||
uint16_t new_state = STATE_LOOKUP_TABLE[input_state];
|
||||
if ((new_state & arg->resolution & STATE_HAS_INCREMENTED) != 0) {
|
||||
if (arg->counter < arg->max_value)
|
||||
arg->counter++;
|
||||
if (arg->counter < arg->max_value) {
|
||||
auto x = arg->counter + 1;
|
||||
arg->counter = x;
|
||||
}
|
||||
rotation_dir = 1;
|
||||
}
|
||||
if ((new_state & arg->resolution & STATE_HAS_DECREMENTED) != 0) {
|
||||
if (arg->counter > arg->min_value)
|
||||
arg->counter--;
|
||||
if (arg->counter > arg->min_value) {
|
||||
auto x = arg->counter - 1;
|
||||
arg->counter = x;
|
||||
}
|
||||
rotation_dir = -1;
|
||||
}
|
||||
|
||||
|
||||
41
esphome/components/seeed_mr60bha2/__init__.py
Normal file
41
esphome/components/seeed_mr60bha2/__init__.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@limengdu"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
MULTI_CONF = True
|
||||
|
||||
mr60bha2_ns = cg.esphome_ns.namespace("seeed_mr60bha2")
|
||||
|
||||
MR60BHA2Component = mr60bha2_ns.class_(
|
||||
"MR60BHA2Component", cg.Component, uart.UARTDevice
|
||||
)
|
||||
|
||||
CONF_MR60BHA2_ID = "mr60bha2_id"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MR60BHA2Component),
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"seeed_mr60bha2",
|
||||
require_tx=True,
|
||||
require_rx=True,
|
||||
baud_rate=115200,
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
173
esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp
Normal file
173
esphome/components/seeed_mr60bha2/seeed_mr60bha2.cpp
Normal file
@@ -0,0 +1,173 @@
|
||||
#include "seeed_mr60bha2.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace esphome {
|
||||
namespace seeed_mr60bha2 {
|
||||
|
||||
static const char *const TAG = "seeed_mr60bha2";
|
||||
|
||||
// Prints the component's configuration data. dump_config() prints all of the component's configuration
|
||||
// items in an easy-to-read format, including the configuration key-value pairs.
|
||||
void MR60BHA2Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MR60BHA2:");
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Breath Rate Sensor", this->breath_rate_sensor_);
|
||||
LOG_SENSOR(" ", "Heart Rate Sensor", this->heart_rate_sensor_);
|
||||
LOG_SENSOR(" ", "Distance Sensor", this->distance_sensor_);
|
||||
#endif
|
||||
}
|
||||
|
||||
// main loop
|
||||
void MR60BHA2Component::loop() {
|
||||
uint8_t byte;
|
||||
|
||||
// Is there data on the serial port
|
||||
while (this->available()) {
|
||||
this->read_byte(&byte);
|
||||
this->rx_message_.push_back(byte);
|
||||
if (!this->validate_message_()) {
|
||||
this->rx_message_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calculate the checksum for a byte array.
|
||||
*
|
||||
* This function calculates the checksum for the provided byte array using an
|
||||
* XOR-based checksum algorithm.
|
||||
*
|
||||
* @param data The byte array to calculate the checksum for.
|
||||
* @param len The length of the byte array.
|
||||
* @return The calculated checksum.
|
||||
*/
|
||||
static uint8_t calculate_checksum(const uint8_t *data, size_t len) {
|
||||
uint8_t checksum = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
checksum ^= data[i];
|
||||
}
|
||||
checksum = ~checksum;
|
||||
return checksum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validate the checksum of a byte array.
|
||||
*
|
||||
* This function validates the checksum of the provided byte array by comparing
|
||||
* it to the expected checksum.
|
||||
*
|
||||
* @param data The byte array to validate.
|
||||
* @param len The length of the byte array.
|
||||
* @param expected_checksum The expected checksum.
|
||||
* @return True if the checksum is valid, false otherwise.
|
||||
*/
|
||||
static bool validate_checksum(const uint8_t *data, size_t len, uint8_t expected_checksum) {
|
||||
return calculate_checksum(data, len) == expected_checksum;
|
||||
}
|
||||
|
||||
bool MR60BHA2Component::validate_message_() {
|
||||
size_t at = this->rx_message_.size() - 1;
|
||||
auto *data = &this->rx_message_[0];
|
||||
uint8_t new_byte = data[at];
|
||||
|
||||
if (at == 0) {
|
||||
return new_byte == FRAME_HEADER_BUFFER;
|
||||
}
|
||||
|
||||
if (at <= 2) {
|
||||
return true;
|
||||
}
|
||||
uint16_t frame_id = encode_uint16(data[1], data[2]);
|
||||
|
||||
if (at <= 4) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t length = encode_uint16(data[3], data[4]);
|
||||
|
||||
if (at <= 6) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t frame_type = encode_uint16(data[5], data[6]);
|
||||
|
||||
if (frame_type != BREATH_RATE_TYPE_BUFFER && frame_type != HEART_RATE_TYPE_BUFFER &&
|
||||
frame_type != DISTANCE_TYPE_BUFFER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t header_checksum = new_byte;
|
||||
|
||||
if (at == 7) {
|
||||
if (!validate_checksum(data, 7, header_checksum)) {
|
||||
ESP_LOGE(TAG, "HEAD_CKSUM_FRAME ERROR: 0x%02x", header_checksum);
|
||||
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8).c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wait until all data is read
|
||||
if (at - 8 < length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t data_checksum = new_byte;
|
||||
if (at == 8 + length) {
|
||||
if (!validate_checksum(data + 8, length, data_checksum)) {
|
||||
ESP_LOGE(TAG, "DATA_CKSUM_FRAME ERROR: 0x%02x", data_checksum);
|
||||
ESP_LOGV(TAG, "GET FRAME: %s", format_hex_pretty(data, 8 + length).c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t *frame_data = data + 8;
|
||||
ESP_LOGV(TAG, "Received Frame: ID: 0x%04x, Type: 0x%04x, Data: [%s] Raw Data: [%s]", frame_id, frame_type,
|
||||
format_hex_pretty(frame_data, length).c_str(), format_hex_pretty(this->rx_message_).c_str());
|
||||
this->process_frame_(frame_id, frame_type, data + 8, length);
|
||||
|
||||
// Return false to reset rx buffer
|
||||
return false;
|
||||
}
|
||||
|
||||
void MR60BHA2Component::process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length) {
|
||||
switch (frame_type) {
|
||||
case BREATH_RATE_TYPE_BUFFER:
|
||||
if (this->breath_rate_sensor_ != nullptr && length >= 4) {
|
||||
uint32_t current_breath_rate_int = encode_uint32(data[3], data[2], data[1], data[0]);
|
||||
if (current_breath_rate_int != 0) {
|
||||
float breath_rate_float;
|
||||
memcpy(&breath_rate_float, ¤t_breath_rate_int, sizeof(float));
|
||||
this->breath_rate_sensor_->publish_state(breath_rate_float);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HEART_RATE_TYPE_BUFFER:
|
||||
if (this->heart_rate_sensor_ != nullptr && length >= 4) {
|
||||
uint32_t current_heart_rate_int = encode_uint32(data[3], data[2], data[1], data[0]);
|
||||
if (current_heart_rate_int != 0) {
|
||||
float heart_rate_float;
|
||||
memcpy(&heart_rate_float, ¤t_heart_rate_int, sizeof(float));
|
||||
this->heart_rate_sensor_->publish_state(heart_rate_float);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DISTANCE_TYPE_BUFFER:
|
||||
if (!data[0]) {
|
||||
if (this->distance_sensor_ != nullptr && length >= 8) {
|
||||
uint32_t current_distance_int = encode_uint32(data[7], data[6], data[5], data[4]);
|
||||
float distance_float;
|
||||
memcpy(&distance_float, ¤t_distance_int, sizeof(float));
|
||||
this->distance_sensor_->publish_state(distance_float);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace seeed_mr60bha2
|
||||
} // namespace esphome
|
||||
61
esphome/components/seeed_mr60bha2/seeed_mr60bha2.h
Normal file
61
esphome/components/seeed_mr60bha2/seeed_mr60bha2.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace seeed_mr60bha2 {
|
||||
|
||||
static const uint8_t DATA_BUF_MAX_SIZE = 12;
|
||||
static const uint8_t FRAME_BUF_MAX_SIZE = 21;
|
||||
static const uint8_t LEN_TO_HEAD_CKSUM = 8;
|
||||
static const uint8_t LEN_TO_DATA_FRAME = 9;
|
||||
|
||||
static const uint8_t FRAME_HEADER_BUFFER = 0x01;
|
||||
static const uint16_t BREATH_RATE_TYPE_BUFFER = 0x0A14;
|
||||
static const uint16_t HEART_RATE_TYPE_BUFFER = 0x0A15;
|
||||
static const uint16_t DISTANCE_TYPE_BUFFER = 0x0A16;
|
||||
|
||||
enum FrameLocation {
|
||||
LOCATE_FRAME_HEADER,
|
||||
LOCATE_ID_FRAME1,
|
||||
LOCATE_ID_FRAME2,
|
||||
LOCATE_LENGTH_FRAME_H,
|
||||
LOCATE_LENGTH_FRAME_L,
|
||||
LOCATE_TYPE_FRAME1,
|
||||
LOCATE_TYPE_FRAME2,
|
||||
LOCATE_HEAD_CKSUM_FRAME, // Header checksum: [from the first byte to the previous byte of the HEAD_CKSUM bit]
|
||||
LOCATE_DATA_FRAME,
|
||||
LOCATE_DATA_CKSUM_FRAME, // Data checksum: [from the first to the previous byte of the DATA_CKSUM bit]
|
||||
LOCATE_PROCESS_FRAME,
|
||||
};
|
||||
|
||||
class MR60BHA2Component : public Component,
|
||||
public uart::UARTDevice { // The class name must be the name defined by text_sensor.py
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(breath_rate);
|
||||
SUB_SENSOR(heart_rate);
|
||||
SUB_SENSOR(distance);
|
||||
#endif
|
||||
|
||||
public:
|
||||
float get_setup_priority() const override { return esphome::setup_priority::LATE; }
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
protected:
|
||||
bool validate_message_();
|
||||
void process_frame_(uint16_t frame_id, uint16_t frame_type, const uint8_t *data, size_t length);
|
||||
|
||||
std::vector<uint8_t> rx_message_;
|
||||
};
|
||||
|
||||
} // namespace seeed_mr60bha2
|
||||
} // namespace esphome
|
||||
57
esphome/components/seeed_mr60bha2/sensor.py
Normal file
57
esphome/components/seeed_mr60bha2/sensor.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_DISTANCE,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
ICON_HEART_PULSE,
|
||||
ICON_PULSE,
|
||||
ICON_SIGNAL,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_BEATS_PER_MINUTE,
|
||||
UNIT_CENTIMETER,
|
||||
)
|
||||
|
||||
from . import CONF_MR60BHA2_ID, MR60BHA2Component
|
||||
|
||||
DEPENDENCIES = ["seeed_mr60bha2"]
|
||||
|
||||
CONF_BREATH_RATE = "breath_rate"
|
||||
CONF_HEART_RATE = "heart_rate"
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_MR60BHA2_ID): cv.use_id(MR60BHA2Component),
|
||||
cv.Optional(CONF_BREATH_RATE): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
icon=ICON_PULSE,
|
||||
),
|
||||
cv.Optional(CONF_HEART_RATE): sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
icon=ICON_HEART_PULSE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_BEATS_PER_MINUTE,
|
||||
),
|
||||
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
unit_of_measurement=UNIT_CENTIMETER,
|
||||
accuracy_decimals=2,
|
||||
icon=ICON_SIGNAL,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
mr60bha2_component = await cg.get_variable(config[CONF_MR60BHA2_ID])
|
||||
if breath_rate_config := config.get(CONF_BREATH_RATE):
|
||||
sens = await sensor.new_sensor(breath_rate_config)
|
||||
cg.add(mr60bha2_component.set_breath_rate_sensor(sens))
|
||||
if heart_rate_config := config.get(CONF_HEART_RATE):
|
||||
sens = await sensor.new_sensor(heart_rate_config)
|
||||
cg.add(mr60bha2_component.set_heart_rate_sensor(sens))
|
||||
if distance_config := config.get(CONF_DISTANCE):
|
||||
sens = await sensor.new_sensor(distance_config)
|
||||
cg.add(mr60bha2_component.set_distance_sensor(sens))
|
||||
@@ -1,19 +1,19 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensirion_common, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_OFFSET,
|
||||
CONF_PM_1_0,
|
||||
CONF_PM_10_0,
|
||||
CONF_PM_2_5,
|
||||
CONF_PM_4_0,
|
||||
CONF_PM_10_0,
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TEMPERATURE_COMPENSATION,
|
||||
DEVICE_CLASS_AQI,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_PM1,
|
||||
@@ -51,7 +51,6 @@ CONF_LEARNING_TIME_OFFSET_HOURS = "learning_time_offset_hours"
|
||||
CONF_NORMALIZED_OFFSET_SLOPE = "normalized_offset_slope"
|
||||
CONF_NOX = "nox"
|
||||
CONF_STD_INITIAL = "std_initial"
|
||||
CONF_TEMPERATURE_COMPENSATION = "temperature_compensation"
|
||||
CONF_TIME_CONSTANT = "time_constant"
|
||||
CONF_VOC = "voc"
|
||||
CONF_VOC_BASELINE = "voc_baseline"
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensirion_common, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
|
||||
from esphome.const import (
|
||||
CONF_COMPENSATION,
|
||||
CONF_ID,
|
||||
CONF_BASELINE,
|
||||
CONF_COMPENSATION,
|
||||
CONF_ECO2,
|
||||
CONF_ID,
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE_SOURCE,
|
||||
CONF_TVOC,
|
||||
ICON_RADIATOR,
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS_PARTS,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_MOLECULE_CO2,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_MOLECULE_CO2,
|
||||
ICON_RADIATOR,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
@@ -77,7 +76,7 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("1s"))
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x58))
|
||||
)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#include "sgp30.h"
|
||||
#include <cinttypes>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace sgp30 {
|
||||
@@ -295,10 +295,6 @@ void SGP30Component::update() {
|
||||
if (this->tvoc_sensor_ != nullptr)
|
||||
this->tvoc_sensor_->publish_state(tvoc);
|
||||
|
||||
if (this->get_update_interval() != 1000) {
|
||||
ESP_LOGW(TAG, "Update interval for SGP30 sensor must be set to 1s for optimized readout");
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
this->send_env_data_();
|
||||
this->read_iaq_baseline_();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome import automation
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_EC,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
CONF_TEMPERATURE_COMPENSATION,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_EMPTY,
|
||||
@@ -18,7 +19,6 @@ DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_SOLUTION = "solution"
|
||||
CONF_TEMPERATURE_SENSOR = "temperature_sensor"
|
||||
CONF_TEMPERATURE_COMPENSATION = "temperature_compensation"
|
||||
CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient"
|
||||
|
||||
ufire_ec_ns = cg.esphome_ns.namespace("ufire_ec")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2024.12.0-dev"
|
||||
__version__ = "2025.1.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
@@ -131,7 +131,9 @@ CONF_CLIENT_CERTIFICATE = "client_certificate"
|
||||
CONF_CLIENT_CERTIFICATE_KEY = "client_certificate_key"
|
||||
CONF_CLIENT_ID = "client_id"
|
||||
CONF_CLK_PIN = "clk_pin"
|
||||
CONF_CLOCK_DIVIDER = "clock_divider"
|
||||
CONF_CLOCK_PIN = "clock_pin"
|
||||
CONF_CLOCK_RESOLUTION = "clock_resolution"
|
||||
CONF_CLOSE_ACTION = "close_action"
|
||||
CONF_CLOSE_DURATION = "close_duration"
|
||||
CONF_CLOSE_ENDSTOP = "close_endstop"
|
||||
@@ -739,6 +741,7 @@ CONF_RGB_ORDER = "rgb_order"
|
||||
CONF_RGBW = "rgbw"
|
||||
CONF_RISING_EDGE = "rising_edge"
|
||||
CONF_RMT_CHANNEL = "rmt_channel"
|
||||
CONF_RMT_SYMBOLS = "rmt_symbols"
|
||||
CONF_ROTATION = "rotation"
|
||||
CONF_ROW = "row"
|
||||
CONF_RS_PIN = "rs_pin"
|
||||
@@ -864,6 +867,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi
|
||||
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic"
|
||||
CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic"
|
||||
CONF_TEMPERATURE = "temperature"
|
||||
CONF_TEMPERATURE_COMPENSATION = "temperature_compensation"
|
||||
CONF_TEMPERATURE_OFFSET = "temperature_offset"
|
||||
CONF_TEMPERATURE_SOURCE = "temperature_source"
|
||||
CONF_TEMPERATURE_STEP = "temperature_step"
|
||||
@@ -917,6 +921,7 @@ CONF_UPDATE_ON_BOOT = "update_on_boot"
|
||||
CONF_URL = "url"
|
||||
CONF_USE_ABBREVIATIONS = "use_abbreviations"
|
||||
CONF_USE_ADDRESS = "use_address"
|
||||
CONF_USE_DMA = "use_dma"
|
||||
CONF_USE_FAHRENHEIT = "use_fahrenheit"
|
||||
CONF_USERNAME = "username"
|
||||
CONF_UUID = "uuid"
|
||||
@@ -1001,6 +1006,7 @@ ICON_GRAIN = "mdi:grain"
|
||||
ICON_GYROSCOPE_X = "mdi:axis-x-rotate-clockwise"
|
||||
ICON_GYROSCOPE_Y = "mdi:axis-y-rotate-clockwise"
|
||||
ICON_GYROSCOPE_Z = "mdi:axis-z-rotate-clockwise"
|
||||
ICON_HEART_PULSE = "mdi:heart-pulse"
|
||||
ICON_HEATING_COIL = "mdi:heating-coil"
|
||||
ICON_KEY_PLUS = "mdi:key-plus"
|
||||
ICON_LIGHTBULB = "mdi:lightbulb"
|
||||
@@ -1040,6 +1046,7 @@ ICON_WEATHER_WINDY = "mdi:weather-windy"
|
||||
ICON_WIFI = "mdi:wifi"
|
||||
|
||||
UNIT_AMPERE = "A"
|
||||
UNIT_BEATS_PER_MINUTE = "bpm"
|
||||
UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³"
|
||||
UNIT_BYTES = "B"
|
||||
UNIT_CELSIUS = "°C"
|
||||
|
||||
@@ -767,7 +767,8 @@ bool mac_address_is_valid(const uint8_t *mac) {
|
||||
return !(is_all_zeros || is_all_ones);
|
||||
}
|
||||
|
||||
void delay_microseconds_safe(uint32_t us) { // avoids CPU locks that could trigger WDT or affect WiFi/BT stability
|
||||
void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us) {
|
||||
// avoids CPU locks that could trigger WDT or affect WiFi/BT stability
|
||||
uint32_t start = micros();
|
||||
|
||||
const uint32_t lag = 5000; // microseconds, specifies the maximum time for a CPU busy-loop.
|
||||
|
||||
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
||||
[project]
|
||||
name = "esphome"
|
||||
license = {text = "MIT"}
|
||||
description = "Make creating custom firmwares for ESP32/ESP8266 super easy."
|
||||
description = "ESPHome is a system to configure your microcontrollers by simple yet powerful configuration files and control them remotely through Home Automation systems."
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{name = "The ESPHome Authors", email = "esphome@nabucasa.com"}
|
||||
|
||||
4
tests/components/adc/test.bk72xx-ard.yaml
Normal file
4
tests/components/adc/test.bk72xx-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
sensor:
|
||||
- platform: adc
|
||||
pin: P23
|
||||
name: Basic ADC Test
|
||||
@@ -165,6 +165,11 @@ lvgl:
|
||||
- Nov
|
||||
- Dec
|
||||
selected_index: 1
|
||||
on_change:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "Roller changed = %d: %s"
|
||||
args: [x, text.c_str()]
|
||||
on_value:
|
||||
then:
|
||||
- logger.log:
|
||||
@@ -451,6 +456,7 @@ lvgl:
|
||||
src: cat_image
|
||||
align: top_left
|
||||
y: "50"
|
||||
mode: real
|
||||
- tileview:
|
||||
id: tileview_id
|
||||
scrollbar_mode: active
|
||||
@@ -647,6 +653,7 @@ lvgl:
|
||||
grid_cell_row_pos: 0
|
||||
grid_cell_column_pos: 0
|
||||
src: !lambda return dog_image;
|
||||
mode: virtual
|
||||
on_click:
|
||||
then:
|
||||
- lvgl.tabview.select:
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
esphome:
|
||||
on_boot:
|
||||
- lambda: 'ESP_LOGD("display","is_connected(): %s", YESNO(id(main_lcd).is_connected()));'
|
||||
|
||||
# Binary sensor publish action tests
|
||||
- binary_sensor.nextion.publish:
|
||||
id: r0_sensor
|
||||
|
||||
@@ -16,6 +16,19 @@ opentherm:
|
||||
summer_mode_active: true
|
||||
dhw_block: true
|
||||
sync_mode: true
|
||||
controller_product_type: 63
|
||||
controller_product_version: 1
|
||||
opentherm_version_controller: 2.2
|
||||
controller_id: 1
|
||||
controller_configuration: 1
|
||||
before_send:
|
||||
then:
|
||||
- lambda: |-
|
||||
ESP_LOGW("OT", ">> Sending message %d", x.id);
|
||||
before_process_response:
|
||||
then:
|
||||
- lambda: |-
|
||||
ESP_LOGW("OT", "<< Processing response %d", x.id);
|
||||
|
||||
output:
|
||||
- platform: opentherm
|
||||
|
||||
19
tests/components/seeed_mr60bha2/common.yaml
Normal file
19
tests/components/seeed_mr60bha2/common.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
uart:
|
||||
- id: seeed_mr60fda2_uart
|
||||
tx_pin: ${uart_tx_pin}
|
||||
rx_pin: ${uart_rx_pin}
|
||||
baud_rate: 115200
|
||||
parity: NONE
|
||||
stop_bits: 1
|
||||
|
||||
seeed_mr60bha2:
|
||||
id: my_seeed_mr60bha2
|
||||
|
||||
sensor:
|
||||
- platform: seeed_mr60bha2
|
||||
breath_rate:
|
||||
name: "Real-time respiratory rate"
|
||||
heart_rate:
|
||||
name: "Real-time heart rate"
|
||||
distance:
|
||||
name: "Distance to detection object"
|
||||
5
tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml
Normal file
5
tests/components/seeed_mr60bha2/test.esp32-c3-ard.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
uart_tx_pin: GPIO5
|
||||
uart_rx_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
5
tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml
Normal file
5
tests/components/seeed_mr60bha2/test.esp32-c3-idf.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
substitutions:
|
||||
uart_tx_pin: GPIO5
|
||||
uart_rx_pin: GPIO4
|
||||
|
||||
<<: !include common.yaml
|
||||
Reference in New Issue
Block a user