mirror of
https://github.com/esphome/esphome.git
synced 2025-09-16 18:22:22 +01:00
Merge branch 'ble_cleanups' into integration
This commit is contained in:
@@ -11,7 +11,7 @@ ci:
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.12.12
|
||||
rev: v0.13.0
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
@@ -66,7 +66,7 @@ esphome/components/binary_sensor/* @esphome/core
|
||||
esphome/components/bk72xx/* @kuba2k2
|
||||
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
|
||||
esphome/components/bl0939/* @ziceva
|
||||
esphome/components/bl0940/* @tobias-
|
||||
esphome/components/bl0940/* @dan-s-github @tobias-
|
||||
esphome/components/bl0942/* @dbuezas @dwmw2
|
||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||
esphome/components/bluetooth_proxy/* @bdraco @jesserockz
|
||||
@@ -234,7 +234,7 @@ esphome/components/ina2xx_base/* @latonita
|
||||
esphome/components/ina2xx_i2c/* @latonita
|
||||
esphome/components/ina2xx_spi/* @latonita
|
||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||
esphome/components/inkplate6/* @jesserockz
|
||||
esphome/components/inkplate/* @jesserockz @JosipKuci
|
||||
esphome/components/integration/* @OttoWinter
|
||||
esphome/components/internal_temperature/* @Mat931
|
||||
esphome/components/interval/* @esphome/core
|
||||
|
2
Doxyfile
2
Doxyfile
@@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome
|
||||
# could be handy for archiving the generated documentation or if some version
|
||||
# control system is used.
|
||||
|
||||
PROJECT_NUMBER = 2025.9.0-dev
|
||||
PROJECT_NUMBER = 2025.10.0-dev
|
||||
|
||||
# Using the PROJECT_BRIEF tag one can provide an optional one line description
|
||||
# for a project that appears at the top of each page and should give viewer a
|
||||
|
@@ -1 +1,6 @@
|
||||
CODEOWNERS = ["@tobias-"]
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@tobias-", "@dan-s-github"]
|
||||
|
||||
CONF_BL0940_ID = "bl0940_id"
|
||||
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
||||
|
@@ -7,28 +7,26 @@ namespace bl0940 {
|
||||
|
||||
static const char *const TAG = "bl0940";
|
||||
|
||||
static const uint8_t BL0940_READ_COMMAND = 0x50; // 0x58 according to documentation
|
||||
static const uint8_t BL0940_FULL_PACKET = 0xAA;
|
||||
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to documentation
|
||||
static const uint8_t BL0940_PACKET_HEADER = 0x55; // 0x58 according to en doc but 0x55 in cn doc
|
||||
|
||||
static const uint8_t BL0940_WRITE_COMMAND = 0xA0; // 0xA8 according to documentation
|
||||
static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10;
|
||||
static const uint8_t BL0940_REG_MODE = 0x18;
|
||||
static const uint8_t BL0940_REG_SOFT_RESET = 0x19;
|
||||
static const uint8_t BL0940_REG_USR_WRPROT = 0x1A;
|
||||
static const uint8_t BL0940_REG_TPS_CTRL = 0x1B;
|
||||
|
||||
const uint8_t BL0940_INIT[5][6] = {
|
||||
static const uint8_t BL0940_INIT[5][5] = {
|
||||
// Reset to default
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
||||
{BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38},
|
||||
// Enable User Operation Write
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
||||
{BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0},
|
||||
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
||||
{BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37},
|
||||
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
||||
{BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE},
|
||||
// 0x181C = Half cycle, Fast RMS threshold 6172
|
||||
{BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
||||
{BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
|
||||
|
||||
void BL0940::loop() {
|
||||
DataPacket buffer;
|
||||
@@ -36,8 +34,8 @@ void BL0940::loop() {
|
||||
return;
|
||||
}
|
||||
if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
|
||||
if (validate_checksum(&buffer)) {
|
||||
received_package_(&buffer);
|
||||
if (this->validate_checksum_(&buffer)) {
|
||||
this->received_package_(&buffer);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
||||
@@ -46,35 +44,151 @@ void BL0940::loop() {
|
||||
}
|
||||
}
|
||||
|
||||
bool BL0940::validate_checksum(const DataPacket *data) {
|
||||
uint8_t checksum = BL0940_READ_COMMAND;
|
||||
bool BL0940::validate_checksum_(DataPacket *data) {
|
||||
uint8_t checksum = this->read_command_;
|
||||
// Whole package but checksum
|
||||
for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
|
||||
checksum += data->raw[i];
|
||||
uint8_t *raw = (uint8_t *) data;
|
||||
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
|
||||
checksum += raw[i];
|
||||
}
|
||||
checksum ^= 0xFF;
|
||||
if (checksum != data->checksum) {
|
||||
ESP_LOGW(TAG, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
||||
ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
|
||||
}
|
||||
return checksum == data->checksum;
|
||||
}
|
||||
|
||||
void BL0940::update() {
|
||||
this->flush();
|
||||
this->write_byte(BL0940_READ_COMMAND);
|
||||
this->write_byte(this->read_command_);
|
||||
this->write_byte(BL0940_FULL_PACKET);
|
||||
}
|
||||
|
||||
void BL0940::setup() {
|
||||
#ifdef USE_NUMBER
|
||||
// add calibration callbacks
|
||||
if (this->voltage_calibration_number_ != nullptr) {
|
||||
this->voltage_calibration_number_->add_on_state_callback(
|
||||
[this](float state) { this->voltage_calibration_callback_(state); });
|
||||
if (this->voltage_calibration_number_->has_state()) {
|
||||
this->voltage_calibration_callback_(this->voltage_calibration_number_->state);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->current_calibration_number_ != nullptr) {
|
||||
this->current_calibration_number_->add_on_state_callback(
|
||||
[this](float state) { this->current_calibration_callback_(state); });
|
||||
if (this->current_calibration_number_->has_state()) {
|
||||
this->current_calibration_callback_(this->current_calibration_number_->state);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->power_calibration_number_ != nullptr) {
|
||||
this->power_calibration_number_->add_on_state_callback(
|
||||
[this](float state) { this->power_calibration_callback_(state); });
|
||||
if (this->power_calibration_number_->has_state()) {
|
||||
this->power_calibration_callback_(this->power_calibration_number_->state);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->energy_calibration_number_ != nullptr) {
|
||||
this->energy_calibration_number_->add_on_state_callback(
|
||||
[this](float state) { this->energy_calibration_callback_(state); });
|
||||
if (this->energy_calibration_number_->has_state()) {
|
||||
this->energy_calibration_callback_(this->energy_calibration_number_->state);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// calculate calibrated reference values
|
||||
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
|
||||
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
|
||||
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
|
||||
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
|
||||
|
||||
for (auto *i : BL0940_INIT) {
|
||||
this->write_array(i, 6);
|
||||
this->write_byte(this->write_command_), this->write_array(i, 5);
|
||||
delay(1);
|
||||
}
|
||||
this->flush();
|
||||
}
|
||||
|
||||
float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
||||
auto tb = (float) (temperature.h << 8 | temperature.l);
|
||||
float BL0940::calculate_power_reference_() {
|
||||
// calculate power reference based on voltage and current reference
|
||||
return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931;
|
||||
}
|
||||
|
||||
float BL0940::calculate_energy_reference_() {
|
||||
// formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2)
|
||||
// or: power_reference_ * 3600000 / (1638.4 * 256)
|
||||
return this->power_reference_cal_ * 3600000 / (1638.4 * 256);
|
||||
}
|
||||
|
||||
float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; }
|
||||
|
||||
void BL0940::reset_calibration() {
|
||||
#ifdef USE_NUMBER
|
||||
if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) {
|
||||
this->current_calibration_number_->make_call().set_value(0).perform();
|
||||
}
|
||||
if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) {
|
||||
this->voltage_calibration_number_->make_call().set_value(0).perform();
|
||||
}
|
||||
if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) {
|
||||
this->power_calibration_number_->make_call().set_value(0).perform();
|
||||
}
|
||||
if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) {
|
||||
this->energy_calibration_number_->make_call().set_value(0).perform();
|
||||
}
|
||||
#endif
|
||||
ESP_LOGD(TAG, "external calibration values restored to initial state");
|
||||
}
|
||||
|
||||
void BL0940::current_calibration_callback_(float state) {
|
||||
this->current_cal_ = this->calculate_calibration_value_(state);
|
||||
ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_);
|
||||
this->recalibrate_();
|
||||
}
|
||||
void BL0940::voltage_calibration_callback_(float state) {
|
||||
this->voltage_cal_ = this->calculate_calibration_value_(state);
|
||||
ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_);
|
||||
this->recalibrate_();
|
||||
}
|
||||
void BL0940::power_calibration_callback_(float state) {
|
||||
this->power_cal_ = this->calculate_calibration_value_(state);
|
||||
ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_);
|
||||
this->recalibrate_();
|
||||
}
|
||||
void BL0940::energy_calibration_callback_(float state) {
|
||||
this->energy_cal_ = this->calculate_calibration_value_(state);
|
||||
ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_);
|
||||
this->recalibrate_();
|
||||
}
|
||||
|
||||
void BL0940::recalibrate_() {
|
||||
ESP_LOGV(TAG, "Recalibrating reference values");
|
||||
this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_;
|
||||
this->current_reference_cal_ = this->current_reference_ / this->current_cal_;
|
||||
|
||||
if (this->voltage_cal_ != 1 || this->current_cal_ != 1) {
|
||||
this->power_reference_ = this->calculate_power_reference_();
|
||||
}
|
||||
this->power_reference_cal_ = this->power_reference_ / this->power_cal_;
|
||||
|
||||
if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) {
|
||||
this->energy_reference_ = this->calculate_energy_reference_();
|
||||
}
|
||||
this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_;
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"Recalibrated reference values:\n"
|
||||
"Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n",
|
||||
this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_,
|
||||
this->energy_reference_cal_);
|
||||
}
|
||||
|
||||
float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const {
|
||||
auto tb = (float) temperature;
|
||||
float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45;
|
||||
if (sensor != nullptr) {
|
||||
if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) {
|
||||
@@ -87,33 +201,40 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const {
|
||||
return converted_temp;
|
||||
}
|
||||
|
||||
void BL0940::received_package_(const DataPacket *data) const {
|
||||
void BL0940::received_package_(DataPacket *data) {
|
||||
// Bad header
|
||||
if (data->frame_header != BL0940_PACKET_HEADER) {
|
||||
ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
|
||||
return;
|
||||
}
|
||||
|
||||
float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
|
||||
float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_;
|
||||
float watt = (float) to_int32_t(data->watt) / power_reference_;
|
||||
uint32_t cf_cnt = to_uint32_t(data->cf_cnt);
|
||||
float total_energy_consumption = (float) cf_cnt / energy_reference_;
|
||||
// cf_cnt is only 24 bits, so track overflows
|
||||
uint32_t cf_cnt = (uint24_t) data->cf_cnt;
|
||||
cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
|
||||
if (cf_cnt < this->prev_cf_cnt_) {
|
||||
cf_cnt += 0x1000000;
|
||||
}
|
||||
this->prev_cf_cnt_ = cf_cnt;
|
||||
|
||||
float tps1 = update_temp_(internal_temperature_sensor_, data->tps1);
|
||||
float tps2 = update_temp_(external_temperature_sensor_, data->tps2);
|
||||
float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_;
|
||||
float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_;
|
||||
float watt = (int24_t) data->watt / this->power_reference_cal_;
|
||||
float total_energy_consumption = cf_cnt / this->energy_reference_cal_;
|
||||
|
||||
if (voltage_sensor_ != nullptr) {
|
||||
voltage_sensor_->publish_state(v_rms);
|
||||
float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1);
|
||||
float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2);
|
||||
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
this->voltage_sensor_->publish_state(v_rms);
|
||||
}
|
||||
if (current_sensor_ != nullptr) {
|
||||
current_sensor_->publish_state(i_rms);
|
||||
if (this->current_sensor_ != nullptr) {
|
||||
this->current_sensor_->publish_state(i_rms);
|
||||
}
|
||||
if (power_sensor_ != nullptr) {
|
||||
power_sensor_->publish_state(watt);
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(watt);
|
||||
}
|
||||
if (energy_sensor_ != nullptr) {
|
||||
energy_sensor_->publish_state(total_energy_consumption);
|
||||
if (this->energy_sensor_ != nullptr) {
|
||||
this->energy_sensor_->publish_state(total_energy_consumption);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt,
|
||||
@@ -121,7 +242,27 @@ void BL0940::received_package_(const DataPacket *data) const {
|
||||
}
|
||||
|
||||
void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexity)
|
||||
ESP_LOGCONFIG(TAG, "BL0940:");
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"BL0940:\n"
|
||||
" LEGACY MODE: %s\n"
|
||||
" READ CMD: 0x%02X\n"
|
||||
" WRITE CMD: 0x%02X\n"
|
||||
" ------------------\n"
|
||||
" Current reference: %f\n"
|
||||
" Energy reference: %f\n"
|
||||
" Power reference: %f\n"
|
||||
" Voltage reference: %f\n",
|
||||
TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_,
|
||||
this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_);
|
||||
#ifdef USE_NUMBER
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"BL0940:\n"
|
||||
" Current calibration: %f\n"
|
||||
" Energy calibration: %f\n"
|
||||
" Power calibration: %f\n"
|
||||
" Voltage calibration: %f\n",
|
||||
this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_);
|
||||
#endif
|
||||
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR("", "Current", this->current_sensor_);
|
||||
LOG_SENSOR("", "Power", this->power_sensor_);
|
||||
@@ -130,9 +271,5 @@ void BL0940::dump_config() { // NOLINT(readability-function-cognitive-complexit
|
||||
LOG_SENSOR("", "External temperature", this->external_temperature_sensor_);
|
||||
}
|
||||
|
||||
uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||
|
||||
int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
||||
|
@@ -1,66 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/datatypes.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BUTTON
|
||||
#include "esphome/components/button/button.h"
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
#include "esphome/components/number/number.h"
|
||||
#endif
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0940 {
|
||||
|
||||
static const float BL0940_PREF = 1430;
|
||||
static const float BL0940_UREF = 33000;
|
||||
static const float BL0940_IREF = 275000; // 2750 from tasmota. Seems to generate values 100 times too high
|
||||
|
||||
// Measured to 297J per click according to power consumption of 5 minutes
|
||||
// Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
||||
static const float BL0940_EREF = 3.6e6 / 297;
|
||||
|
||||
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||
uint8_t l;
|
||||
uint8_t m;
|
||||
uint8_t h;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct ube16_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||
uint8_t l;
|
||||
uint8_t h;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||
uint8_t l;
|
||||
uint8_t m;
|
||||
int8_t h;
|
||||
} __attribute__((packed));
|
||||
|
||||
// Caveat: All these values are big endian (low - middle - high)
|
||||
|
||||
union DataPacket { // NOLINT(altera-struct-pack-align)
|
||||
uint8_t raw[35];
|
||||
struct {
|
||||
uint8_t frame_header; // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins.
|
||||
ube24_t i_fast_rms; // 0x00
|
||||
ube24_t i_rms; // 0x04
|
||||
ube24_t RESERVED0; // reserved
|
||||
ube24_t v_rms; // 0x06
|
||||
ube24_t RESERVED1; // reserved
|
||||
sbe24_t watt; // 0x08
|
||||
ube24_t RESERVED2; // reserved
|
||||
ube24_t cf_cnt; // 0x0A
|
||||
ube24_t RESERVED3; // reserved
|
||||
ube16_t tps1; // 0x0c
|
||||
uint8_t RESERVED4; // value of 0x00
|
||||
ube16_t tps2; // 0x0c
|
||||
uint8_t RESERVED5; // value of 0x00
|
||||
uint8_t checksum; // checksum
|
||||
};
|
||||
struct DataPacket {
|
||||
uint8_t frame_header; // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests)
|
||||
uint24_le_t i_fast_rms; // Fast RMS current
|
||||
uint24_le_t i_rms; // RMS current
|
||||
uint24_t RESERVED0; // Reserved
|
||||
uint24_le_t v_rms; // RMS voltage
|
||||
uint24_t RESERVED1; // Reserved
|
||||
int24_le_t watt; // Active power (can be negative for bidirectional measurement)
|
||||
uint24_t RESERVED2; // Reserved
|
||||
uint24_le_t cf_cnt; // Energy pulse count
|
||||
uint24_t RESERVED3; // Reserved
|
||||
uint16_le_t tps1; // Internal temperature sensor 1
|
||||
uint8_t RESERVED4; // Reserved (should be 0x00)
|
||||
uint16_le_t tps2; // Internal temperature sensor 2
|
||||
uint8_t RESERVED5; // Reserved (should be 0x00)
|
||||
uint8_t checksum; // Packet checksum
|
||||
} __attribute__((packed));
|
||||
|
||||
class BL0940 : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
// Sensor setters
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
|
||||
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
|
||||
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
|
||||
|
||||
// Temperature sensor setters
|
||||
void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) {
|
||||
internal_temperature_sensor_ = internal_temperature_sensor;
|
||||
}
|
||||
@@ -68,42 +50,105 @@ class BL0940 : public PollingComponent, public uart::UARTDevice {
|
||||
external_temperature_sensor_ = external_temperature_sensor;
|
||||
}
|
||||
|
||||
void loop() override;
|
||||
// Configuration setters
|
||||
void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; }
|
||||
void set_read_command(uint8_t read_command) { this->read_command_ = read_command; }
|
||||
void set_write_command(uint8_t write_command) { this->write_command_ = write_command; }
|
||||
|
||||
// Reference value setters (used for calibration and conversion)
|
||||
void set_current_reference(float current_ref) { this->current_reference_ = current_ref; }
|
||||
void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; }
|
||||
void set_power_reference(float power_ref) { this->power_reference_ = power_ref; }
|
||||
void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; }
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
// Calibration number setters (for Home Assistant number entities)
|
||||
void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; }
|
||||
void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; }
|
||||
void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; }
|
||||
void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
// Resets all calibration values to defaults (can be triggered by a button)
|
||||
void reset_calibration();
|
||||
#endif
|
||||
|
||||
// Core component methods
|
||||
void loop() override;
|
||||
void update() override;
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
// NB This may be negative as the circuits is seemingly able to measure
|
||||
// power in both directions
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *energy_sensor_{nullptr};
|
||||
sensor::Sensor *internal_temperature_sensor_{nullptr};
|
||||
sensor::Sensor *external_temperature_sensor_{nullptr};
|
||||
// --- Sensor pointers ---
|
||||
sensor::Sensor *voltage_sensor_{nullptr}; // Voltage sensor
|
||||
sensor::Sensor *current_sensor_{nullptr}; // Current sensor
|
||||
sensor::Sensor *power_sensor_{nullptr}; // Power sensor (can be negative for bidirectional)
|
||||
sensor::Sensor *energy_sensor_{nullptr}; // Energy sensor
|
||||
sensor::Sensor *internal_temperature_sensor_{nullptr}; // Internal temperature sensor
|
||||
sensor::Sensor *external_temperature_sensor_{nullptr}; // External temperature sensor
|
||||
|
||||
// Max difference between two measurements of the temperature. Used to avoid noise.
|
||||
float max_temperature_diff_{0};
|
||||
// Divide by this to turn into Watt
|
||||
float power_reference_ = BL0940_PREF;
|
||||
// Divide by this to turn into Volt
|
||||
float voltage_reference_ = BL0940_UREF;
|
||||
// Divide by this to turn into Ampere
|
||||
float current_reference_ = BL0940_IREF;
|
||||
// Divide by this to turn into kWh
|
||||
float energy_reference_ = BL0940_EREF;
|
||||
#ifdef USE_NUMBER
|
||||
// --- Calibration number entities (for dynamic calibration via HA UI) ---
|
||||
number::Number *voltage_calibration_number_{nullptr};
|
||||
number::Number *current_calibration_number_{nullptr};
|
||||
number::Number *power_calibration_number_{nullptr};
|
||||
number::Number *energy_calibration_number_{nullptr};
|
||||
#endif
|
||||
|
||||
float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const;
|
||||
// --- Internal state ---
|
||||
uint32_t prev_cf_cnt_ = 0; // Previous energy pulse count (for energy calculation)
|
||||
float max_temperature_diff_{0}; // Max allowed temperature difference between two measurements (noise filter)
|
||||
|
||||
static uint32_t to_uint32_t(ube24_t input);
|
||||
// --- Reference values for conversion ---
|
||||
float power_reference_; // Divider for raw power to get Watts
|
||||
float power_reference_cal_; // Calibrated power reference
|
||||
float voltage_reference_; // Divider for raw voltage to get Volts
|
||||
float voltage_reference_cal_; // Calibrated voltage reference
|
||||
float current_reference_; // Divider for raw current to get Amperes
|
||||
float current_reference_cal_; // Calibrated current reference
|
||||
float energy_reference_; // Divider for raw energy to get kWh
|
||||
float energy_reference_cal_; // Calibrated energy reference
|
||||
|
||||
static int32_t to_int32_t(sbe24_t input);
|
||||
// --- Home Assistant calibration values (multipliers, default 1) ---
|
||||
float current_cal_{1};
|
||||
float voltage_cal_{1};
|
||||
float power_cal_{1};
|
||||
float energy_cal_{1};
|
||||
|
||||
static bool validate_checksum(const DataPacket *data);
|
||||
// --- Protocol commands ---
|
||||
uint8_t read_command_;
|
||||
uint8_t write_command_;
|
||||
|
||||
void received_package_(const DataPacket *data) const;
|
||||
// --- Mode flags ---
|
||||
bool legacy_mode_enabled_ = true;
|
||||
|
||||
// --- Methods ---
|
||||
// Converts packed temperature value to float and updates the sensor
|
||||
float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const;
|
||||
|
||||
// Validates the checksum of a received data packet
|
||||
bool validate_checksum_(DataPacket *data);
|
||||
|
||||
// Handles a received data packet
|
||||
void received_package_(DataPacket *data);
|
||||
|
||||
// Calculates reference values for calibration and conversion
|
||||
float calculate_energy_reference_();
|
||||
float calculate_power_reference_();
|
||||
float calculate_calibration_value_(float state);
|
||||
|
||||
// Calibration update callbacks (used with number entities)
|
||||
void current_calibration_callback_(float state);
|
||||
void voltage_calibration_callback_(float state);
|
||||
void power_calibration_callback_(float state);
|
||||
void energy_calibration_callback_(float state);
|
||||
void reset_calibration_callback_();
|
||||
|
||||
// Recalculates all reference values after calibration changes
|
||||
void recalibrate_();
|
||||
};
|
||||
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
||||
|
27
esphome/components/bl0940/button/__init__.py
Normal file
27
esphome/components/bl0940/button/__init__.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import button
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART
|
||||
|
||||
from .. import CONF_BL0940_ID, bl0940_ns
|
||||
from ..sensor import BL0940
|
||||
|
||||
CalibrationResetButton = bl0940_ns.class_(
|
||||
"CalibrationResetButton", button.Button, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
button.button_schema(
|
||||
CalibrationResetButton,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
icon=ICON_RESTART,
|
||||
)
|
||||
.extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await button.new_button(config)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_BL0940_ID])
|
@@ -0,0 +1,20 @@
|
||||
#include "calibration_reset_button.h"
|
||||
#include "../bl0940.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0940 {
|
||||
|
||||
static const char *const TAG = "bl0940.button.calibration_reset";
|
||||
|
||||
void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); }
|
||||
|
||||
void CalibrationResetButton::press_action() {
|
||||
ESP_LOGI(TAG, "Resetting calibration defaults...");
|
||||
this->parent_->reset_calibration();
|
||||
}
|
||||
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
19
esphome/components/bl0940/button/calibration_reset_button.h
Normal file
19
esphome/components/bl0940/button/calibration_reset_button.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/button/button.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0940 {
|
||||
|
||||
class BL0940; // Forward declaration of BL0940 class
|
||||
|
||||
class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
void press_action() override;
|
||||
};
|
||||
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
94
esphome/components/bl0940/number/__init__.py
Normal file
94
esphome/components/bl0940/number/__init__.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import number
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_MAX_VALUE,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_MODE,
|
||||
CONF_RESTORE_VALUE,
|
||||
CONF_STEP,
|
||||
ENTITY_CATEGORY_CONFIG,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
from .. import CONF_BL0940_ID, bl0940_ns
|
||||
from ..sensor import BL0940
|
||||
|
||||
# Define calibration types
|
||||
CONF_CURRENT_CALIBRATION = "current_calibration"
|
||||
CONF_VOLTAGE_CALIBRATION = "voltage_calibration"
|
||||
CONF_POWER_CALIBRATION = "power_calibration"
|
||||
CONF_ENERGY_CALIBRATION = "energy_calibration"
|
||||
|
||||
BL0940Number = bl0940_ns.class_("BL0940Number")
|
||||
|
||||
CalibrationNumber = bl0940_ns.class_(
|
||||
"CalibrationNumber", number.Number, cg.PollingComponent
|
||||
)
|
||||
|
||||
|
||||
def validate_min_max(config):
|
||||
if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]:
|
||||
raise cv.Invalid("max_value must be greater than min_value")
|
||||
return config
|
||||
|
||||
|
||||
CALIBRATION_SCHEMA = cv.All(
|
||||
number.number_schema(
|
||||
CalibrationNumber,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES),
|
||||
cv.Optional(CONF_MAX_VALUE, default=10): cv.All(
|
||||
cv.float_, cv.Range(max=50)
|
||||
),
|
||||
cv.Optional(CONF_MIN_VALUE, default=-10): cv.All(
|
||||
cv.float_, cv.Range(min=-50)
|
||||
),
|
||||
cv.Optional(CONF_STEP, default=0.1): cv.positive_float,
|
||||
cv.Optional(CONF_RESTORE_VALUE): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA),
|
||||
validate_min_max,
|
||||
)
|
||||
|
||||
# Configuration schema for BL0940 numbers
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BL0940Number),
|
||||
cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940),
|
||||
cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA,
|
||||
cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA,
|
||||
cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA,
|
||||
cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
# Get the BL0940 component instance
|
||||
bl0940 = await cg.get_variable(config[CONF_BL0940_ID])
|
||||
|
||||
# Process all calibration types
|
||||
for cal_type, setter_method in [
|
||||
(CONF_CURRENT_CALIBRATION, "set_current_calibration_number"),
|
||||
(CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"),
|
||||
(CONF_POWER_CALIBRATION, "set_power_calibration_number"),
|
||||
(CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"),
|
||||
]:
|
||||
if conf := config.get(cal_type):
|
||||
var = await number.new_number(
|
||||
conf,
|
||||
min_value=conf.get(CONF_MIN_VALUE),
|
||||
max_value=conf.get(CONF_MAX_VALUE),
|
||||
step=conf.get(CONF_STEP),
|
||||
)
|
||||
await cg.register_component(var, conf)
|
||||
|
||||
if restore_value := config.get(CONF_RESTORE_VALUE):
|
||||
cg.add(var.set_restore_value(restore_value))
|
||||
cg.add(getattr(bl0940, setter_method)(var))
|
29
esphome/components/bl0940/number/calibration_number.cpp
Normal file
29
esphome/components/bl0940/number/calibration_number.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "calibration_number.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0940 {
|
||||
|
||||
static const char *const TAG = "bl0940.number";
|
||||
|
||||
void CalibrationNumber::setup() {
|
||||
float value = 0.0f;
|
||||
if (this->restore_value_) {
|
||||
this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash());
|
||||
if (!this->pref_.load(&value)) {
|
||||
value = 0.0f;
|
||||
}
|
||||
}
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
void CalibrationNumber::control(float value) {
|
||||
this->publish_state(value);
|
||||
if (this->restore_value_)
|
||||
this->pref_.save(&value);
|
||||
}
|
||||
|
||||
void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); }
|
||||
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
26
esphome/components/bl0940/number/calibration_number.h
Normal file
26
esphome/components/bl0940/number/calibration_number.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/number/number.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace bl0940 {
|
||||
|
||||
class CalibrationNumber : public number::Number, public Component {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
|
||||
|
||||
protected:
|
||||
void control(float value) override;
|
||||
bool restore_value_{true};
|
||||
|
||||
ESPPreferenceObject pref_;
|
||||
};
|
||||
|
||||
} // namespace bl0940
|
||||
} // namespace esphome
|
@@ -8,6 +8,7 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INTERNAL_TEMPERATURE,
|
||||
CONF_POWER,
|
||||
CONF_REFERENCE_VOLTAGE,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
@@ -23,12 +24,133 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
from . import bl0940_ns
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
|
||||
bl0940_ns = cg.esphome_ns.namespace("bl0940")
|
||||
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)
|
||||
|
||||
CONF_LEGACY_MODE = "legacy_mode"
|
||||
|
||||
CONF_READ_COMMAND = "read_command"
|
||||
CONF_WRITE_COMMAND = "write_command"
|
||||
|
||||
CONF_RESISTOR_SHUNT = "resistor_shunt"
|
||||
CONF_RESISTOR_ONE = "resistor_one"
|
||||
CONF_RESISTOR_TWO = "resistor_two"
|
||||
|
||||
CONF_CURRENT_REFERENCE = "current_reference"
|
||||
CONF_ENERGY_REFERENCE = "energy_reference"
|
||||
CONF_POWER_REFERENCE = "power_reference"
|
||||
CONF_VOLTAGE_REFERENCE = "voltage_reference"
|
||||
|
||||
DEFAULT_BL0940_READ_COMMAND = 0x58
|
||||
DEFAULT_BL0940_WRITE_COMMAND = 0xA1
|
||||
|
||||
# Values according to BL0940 application note:
|
||||
# https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf
|
||||
DEFAULT_BL0940_VREF = 1.218 # Vref = 1.218
|
||||
DEFAULT_BL0940_RL = 1 # RL = 1 mΩ
|
||||
DEFAULT_BL0940_R1 = 0.51 # R1 = 0.51 kΩ
|
||||
DEFAULT_BL0940_R2 = 1950 # R2 = 5 x 390 kΩ -> 1950 kΩ
|
||||
|
||||
# ----------------------------------------------------
|
||||
# values from initial implementation
|
||||
DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50
|
||||
DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0
|
||||
|
||||
DEFAULT_BL0940_LEGACY_UREF = 33000
|
||||
DEFAULT_BL0940_LEGACY_IREF = 275000
|
||||
DEFAULT_BL0940_LEGACY_PREF = 1430
|
||||
# Measured to 297J per click according to power consumption of 5 minutes
|
||||
# Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4
|
||||
DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297
|
||||
# ----------------------------------------------------
|
||||
|
||||
|
||||
# methods to calculate voltage and current reference values
|
||||
def calculate_voltage_reference(vref, r_one, r_two):
|
||||
# formula: 79931 / Vref * (R1 * 1000) / (R1 + R2)
|
||||
return 79931 / vref * (r_one * 1000) / (r_one + r_two)
|
||||
|
||||
|
||||
def calculate_current_reference(vref, r_shunt):
|
||||
# formula: 324004 * RL / Vref
|
||||
return 324004 * r_shunt / vref
|
||||
|
||||
|
||||
def calculate_power_reference(voltage_reference, current_reference):
|
||||
# calculate power reference based on voltage and current reference
|
||||
return voltage_reference * current_reference * 4046 / 324004 / 79931
|
||||
|
||||
|
||||
def calculate_energy_reference(power_reference):
|
||||
# formula: power_reference * 3600000 / (1638.4 * 256)
|
||||
return power_reference * 3600000 / (1638.4 * 256)
|
||||
|
||||
|
||||
def validate_legacy_mode(config):
|
||||
# Only allow schematic calibration options if legacy_mode is False
|
||||
if config.get(CONF_LEGACY_MODE, True):
|
||||
forbidden = [
|
||||
CONF_REFERENCE_VOLTAGE,
|
||||
CONF_RESISTOR_SHUNT,
|
||||
CONF_RESISTOR_ONE,
|
||||
CONF_RESISTOR_TWO,
|
||||
]
|
||||
for key in forbidden:
|
||||
if key in config:
|
||||
raise cv.Invalid(
|
||||
f"Option '{key}' is only allowed when legacy_mode: false"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def set_command_defaults(config):
|
||||
# Set defaults for read_command and write_command based on legacy_mode
|
||||
legacy = config.get(CONF_LEGACY_MODE, True)
|
||||
if legacy:
|
||||
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND)
|
||||
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND)
|
||||
else:
|
||||
config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND)
|
||||
config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND)
|
||||
return config
|
||||
|
||||
|
||||
def set_reference_values(config):
|
||||
# Set default reference values based on legacy_mode
|
||||
if config.get(CONF_LEGACY_MODE, True):
|
||||
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
|
||||
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
|
||||
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
|
||||
else:
|
||||
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
|
||||
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
|
||||
r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2)
|
||||
r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL)
|
||||
|
||||
config.setdefault(
|
||||
CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two)
|
||||
)
|
||||
config.setdefault(
|
||||
CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt)
|
||||
)
|
||||
config.setdefault(
|
||||
CONF_POWER_REFERENCE,
|
||||
calculate_power_reference(
|
||||
config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE)
|
||||
),
|
||||
)
|
||||
config.setdefault(
|
||||
CONF_ENERGY_REFERENCE,
|
||||
calculate_energy_reference(config.get(CONF_POWER_REFERENCE)),
|
||||
)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
@@ -69,10 +191,24 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_,
|
||||
cv.Optional(CONF_RESISTOR_SHUNT): cv.float_,
|
||||
cv.Optional(CONF_RESISTOR_ONE): cv.float_,
|
||||
cv.Optional(CONF_RESISTOR_TWO): cv.float_,
|
||||
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
|
||||
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
|
||||
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
|
||||
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.add_extra(validate_legacy_mode)
|
||||
.add_extra(set_command_defaults)
|
||||
.add_extra(set_reference_values)
|
||||
)
|
||||
|
||||
|
||||
@@ -99,3 +235,16 @@ async def to_code(config):
|
||||
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
|
||||
sens = await sensor.new_sensor(external_temperature_config)
|
||||
cg.add(var.set_external_temperature_sensor(sens))
|
||||
|
||||
# enable legacy mode
|
||||
cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE)))
|
||||
|
||||
# Set bl0940 commands after validator has determined which defaults to use if not set
|
||||
cg.add(var.set_read_command(config.get(CONF_READ_COMMAND)))
|
||||
cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND)))
|
||||
|
||||
# Set reference values after validator has set the values either from defaults or calculated
|
||||
cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE)))
|
||||
cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE)))
|
||||
cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE)))
|
||||
cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE)))
|
||||
|
@@ -7,83 +7,83 @@ namespace esphome {
|
||||
namespace captive_portal {
|
||||
|
||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x95, 0x56, 0xeb, 0x6f, 0xd4, 0x38, 0x10, 0xff, 0xce,
|
||||
0x5f, 0xe1, 0x33, 0x8f, 0x26, 0xd0, 0x3c, 0xb7, 0xdb, 0x96, 0x6c, 0x12, 0x04, 0xdc, 0x21, 0x90, 0x28, 0x20, 0xb5,
|
||||
0x70, 0x1f, 0x10, 0x52, 0xbd, 0xc9, 0x64, 0x63, 0x9a, 0x38, 0x39, 0xdb, 0xfb, 0x62, 0xb5, 0xf7, 0xb7, 0xdf, 0x38,
|
||||
0xc9, 0x6e, 0xb7, 0x15, 0x9c, 0xee, 0x5a, 0x35, 0x1d, 0xdb, 0xf3, 0xf8, 0xcd, 0x78, 0x1e, 0x8e, 0x7f, 0xcb, 0x9b,
|
||||
0x4c, 0xaf, 0x5b, 0x20, 0xa5, 0xae, 0xab, 0x34, 0x36, 0x5f, 0x52, 0x31, 0x31, 0x4b, 0x40, 0xe0, 0x0a, 0x58, 0x9e,
|
||||
0xc6, 0x35, 0x68, 0x46, 0xb2, 0x92, 0x49, 0x05, 0x3a, 0xf9, 0x7c, 0xf5, 0xc6, 0x39, 0x4f, 0xe3, 0x8a, 0x8b, 0x1b,
|
||||
0x22, 0xa1, 0x4a, 0x78, 0xd6, 0x08, 0x52, 0x4a, 0x28, 0x92, 0x9c, 0x69, 0x16, 0xf1, 0x9a, 0xcd, 0x60, 0x10, 0x11,
|
||||
0xac, 0x86, 0x64, 0xc1, 0x61, 0xd9, 0x36, 0x52, 0x13, 0xe4, 0xd3, 0x20, 0x74, 0x42, 0x97, 0x3c, 0xd7, 0x65, 0x92,
|
||||
0xc3, 0x82, 0x67, 0xe0, 0x74, 0x8b, 0x63, 0x2e, 0xb8, 0xe6, 0xac, 0x72, 0x54, 0xc6, 0x2a, 0x48, 0x82, 0xe3, 0xb9,
|
||||
0x02, 0xd9, 0x2d, 0xd8, 0x14, 0xd7, 0xa2, 0xa1, 0x69, 0xac, 0x32, 0xc9, 0x5b, 0x4d, 0x0c, 0xd4, 0xa4, 0x6e, 0xf2,
|
||||
0x79, 0x05, 0xa9, 0xe7, 0x31, 0x85, 0x90, 0x94, 0xc7, 0x45, 0x0e, 0x2b, 0x77, 0xea, 0x67, 0x99, 0x0f, 0xe7, 0xe7,
|
||||
0xee, 0x77, 0xf5, 0x00, 0x9d, 0x9a, 0xd7, 0x68, 0xcd, 0xad, 0x9a, 0x8c, 0x69, 0xde, 0x08, 0x57, 0x01, 0x93, 0x59,
|
||||
0x99, 0x24, 0x09, 0x7d, 0xa1, 0xd8, 0x02, 0xe8, 0x93, 0x27, 0xd6, 0x9e, 0x69, 0x06, 0xfa, 0x8f, 0x0a, 0x0c, 0xa9,
|
||||
0x5e, 0xad, 0xaf, 0xd8, 0xec, 0x03, 0x02, 0xb7, 0x28, 0x53, 0x3c, 0x07, 0x6a, 0x7f, 0xf5, 0xbf, 0xb9, 0x4a, 0xaf,
|
||||
0x2b, 0x70, 0x73, 0xae, 0xda, 0x8a, 0xad, 0x13, 0x3a, 0x45, 0xad, 0x37, 0xd4, 0x9e, 0x14, 0x73, 0x91, 0x19, 0xe5,
|
||||
0x44, 0x59, 0x60, 0x6f, 0x2a, 0x40, 0x78, 0xc9, 0x05, 0xd3, 0xa5, 0x5b, 0xb3, 0x95, 0xd5, 0x13, 0x5c, 0x58, 0xe1,
|
||||
0x53, 0x0b, 0x9e, 0x05, 0xbe, 0x6f, 0x1f, 0x77, 0x1f, 0xdf, 0xf6, 0xf0, 0xff, 0x44, 0x82, 0x9e, 0x4b, 0x41, 0x98,
|
||||
0x75, 0x1d, 0xb7, 0xc8, 0x49, 0xf2, 0x84, 0x5e, 0x04, 0x21, 0x09, 0x9e, 0xbb, 0xe1, 0xf8, 0xbd, 0x7b, 0x46, 0x4e,
|
||||
0xf0, 0x7f, 0x76, 0xe6, 0x8c, 0x49, 0x70, 0x82, 0x9f, 0x30, 0x74, 0xc7, 0xc4, 0xff, 0x41, 0x49, 0xc1, 0xab, 0x2a,
|
||||
0xa1, 0xa2, 0x11, 0x40, 0x89, 0xd2, 0xb2, 0xb9, 0x81, 0x84, 0x66, 0x73, 0x29, 0x11, 0xfb, 0xeb, 0xa6, 0x6a, 0x24,
|
||||
0xf5, 0xd2, 0x07, 0xff, 0x4b, 0xa1, 0x96, 0x4c, 0xa8, 0xa2, 0x91, 0x75, 0x42, 0xbb, 0xe8, 0x5b, 0x8f, 0x36, 0x7a,
|
||||
0x4b, 0xcc, 0xc7, 0x3e, 0x38, 0x74, 0x1a, 0xc9, 0x67, 0x5c, 0x24, 0xd4, 0x68, 0x3c, 0x47, 0x23, 0xd7, 0xf6, 0x76,
|
||||
0xef, 0x3d, 0x33, 0xde, 0x0f, 0xfe, 0x34, 0xd6, 0xd7, 0xeb, 0x58, 0x2d, 0x66, 0x64, 0x55, 0x57, 0x42, 0x25, 0xb4,
|
||||
0xd4, 0xba, 0x8d, 0x3c, 0x6f, 0xb9, 0x5c, 0xba, 0xcb, 0x91, 0xdb, 0xc8, 0x99, 0x17, 0xfa, 0xbe, 0xef, 0x21, 0x07,
|
||||
0x25, 0x7d, 0x22, 0xd0, 0xf0, 0x84, 0x92, 0x12, 0xf8, 0xac, 0xd4, 0x1d, 0x9d, 0x3e, 0xda, 0xc0, 0x36, 0x36, 0x1c,
|
||||
0xe9, 0xf5, 0xb7, 0x03, 0x2b, 0xfc, 0xc0, 0x0a, 0xbc, 0x60, 0x16, 0xdd, 0xb9, 0x79, 0xd4, 0xb9, 0x79, 0xc6, 0x42,
|
||||
0x12, 0x12, 0xbf, 0xfb, 0x0d, 0x1d, 0x43, 0x0f, 0x2b, 0xe7, 0xde, 0x8a, 0x1c, 0xac, 0x0c, 0x55, 0x9f, 0x3a, 0xcf,
|
||||
0xf7, 0xb2, 0x81, 0xd9, 0x59, 0x04, 0xfe, 0xed, 0x86, 0x11, 0x78, 0x7b, 0x7a, 0xb8, 0x76, 0xc2, 0x2f, 0x87, 0x0c,
|
||||
0xc6, 0x5a, 0x19, 0x7c, 0x39, 0x65, 0x63, 0x32, 0x1e, 0x76, 0xc6, 0x8e, 0xa1, 0xf7, 0x2b, 0x32, 0x5e, 0x20, 0x47,
|
||||
0xed, 0x9c, 0x3a, 0x63, 0x36, 0x22, 0xa3, 0x01, 0x08, 0x52, 0xb8, 0x7d, 0x8a, 0x82, 0x07, 0x7b, 0xce, 0xe8, 0xc7,
|
||||
0x91, 0x97, 0x52, 0x3b, 0xa2, 0xf4, 0xd6, 0xf3, 0xe6, 0xd0, 0x73, 0xf7, 0x7b, 0x83, 0x39, 0x45, 0x29, 0x46, 0x06,
|
||||
0x74, 0x56, 0x5a, 0xd4, 0xc3, 0xc2, 0x2a, 0xf8, 0x0c, 0xb3, 0xbe, 0x11, 0xd4, 0x76, 0x75, 0x09, 0xc2, 0xda, 0x89,
|
||||
0x1a, 0x41, 0xe8, 0x4e, 0xac, 0xfb, 0x27, 0xda, 0xde, 0xec, 0xf3, 0x5f, 0x73, 0x8d, 0x65, 0xa6, 0x5d, 0x53, 0xb0,
|
||||
0xc7, 0xfb, 0xdd, 0x69, 0x93, 0xaf, 0x7f, 0x51, 0x1a, 0x65, 0xd0, 0xd7, 0x05, 0x17, 0x02, 0xe4, 0x15, 0xac, 0xf0,
|
||||
0xe6, 0x2e, 0x5e, 0xbe, 0x26, 0x2f, 0xf3, 0x5c, 0x82, 0x52, 0x11, 0xa1, 0xcf, 0x34, 0xd6, 0x40, 0xf6, 0xdf, 0x75,
|
||||
0x05, 0x77, 0x74, 0xfd, 0xc9, 0xdf, 0x70, 0xf2, 0x01, 0xf4, 0xb2, 0x91, 0x37, 0x83, 0x36, 0x03, 0x6d, 0x62, 0x2a,
|
||||
0x4c, 0x22, 0x4e, 0xd6, 0x2a, 0x57, 0x55, 0xd8, 0x3e, 0xac, 0xc0, 0x46, 0x3b, 0xed, 0xad, 0x57, 0x62, 0x17, 0xa8,
|
||||
0xeb, 0x38, 0xe7, 0x0b, 0x92, 0x55, 0xd8, 0x21, 0xb0, 0x5c, 0x7a, 0x55, 0x94, 0x3c, 0x20, 0xdd, 0x4f, 0x23, 0x32,
|
||||
0x94, 0xbe, 0x49, 0xe8, 0x4f, 0x3a, 0xc0, 0xab, 0xf5, 0xbb, 0xdc, 0x3a, 0x52, 0x58, 0xfb, 0x47, 0xb6, 0xbb, 0x60,
|
||||
0xd5, 0x1c, 0x48, 0x42, 0x74, 0xc9, 0xd5, 0x2d, 0xc0, 0xc9, 0x2f, 0xc5, 0x5a, 0x75, 0x83, 0x52, 0x05, 0x1e, 0x2b,
|
||||
0xcb, 0xa6, 0xe9, 0x60, 0x2e, 0x66, 0x7d, 0x83, 0xa4, 0x0f, 0xe9, 0x3d, 0x44, 0x4e, 0x05, 0x85, 0xde, 0xf3, 0x11,
|
||||
0x2c, 0x3b, 0x65, 0x09, 0x57, 0xa2, 0x75, 0x7b, 0xbb, 0xdf, 0x8c, 0x55, 0xcb, 0xc4, 0x7d, 0x41, 0x03, 0xd0, 0x94,
|
||||
0x0a, 0x36, 0x36, 0xa4, 0x4c, 0xbd, 0x20, 0xd3, 0xde, 0xa0, 0xc7, 0x76, 0xe4, 0xa3, 0x0d, 0x47, 0x8d, 0xa6, 0x5f,
|
||||
0xed, 0x35, 0xc6, 0x1e, 0x86, 0x26, 0xbd, 0xde, 0xda, 0xb7, 0x7e, 0xfc, 0x35, 0x07, 0xb9, 0xbe, 0x84, 0x0a, 0x32,
|
||||
0xdd, 0x48, 0x8b, 0x3e, 0x44, 0x2b, 0x98, 0x4a, 0x9d, 0xc3, 0x6f, 0xaf, 0x2e, 0xde, 0x27, 0x8d, 0x25, 0xed, 0xe3,
|
||||
0x5f, 0x71, 0x9b, 0x51, 0xf0, 0x15, 0x47, 0xc1, 0xdf, 0xc9, 0x91, 0x19, 0x06, 0x47, 0xdf, 0x50, 0xb4, 0xf3, 0xf7,
|
||||
0xfa, 0x76, 0x22, 0x98, 0x72, 0x7e, 0x86, 0x2d, 0xe1, 0xd8, 0x78, 0xe8, 0x9c, 0x8e, 0xed, 0x2d, 0xda, 0x47, 0x04,
|
||||
0x88, 0xbb, 0xeb, 0xeb, 0xd8, 0xdf, 0x4d, 0x8b, 0x4d, 0x9f, 0x6e, 0xa6, 0xcd, 0xca, 0x51, 0xfc, 0x07, 0x17, 0xb3,
|
||||
0x88, 0x8b, 0x12, 0x24, 0xd7, 0x5b, 0x84, 0x8b, 0x13, 0xa2, 0x9d, 0xeb, 0x4d, 0xcb, 0xf2, 0xdc, 0x9c, 0x8c, 0xdb,
|
||||
0xd5, 0xa4, 0xc0, 0x79, 0x62, 0x38, 0x21, 0x0a, 0xa0, 0xde, 0xf6, 0xe7, 0x5d, 0x47, 0x89, 0x9e, 0x8f, 0x1f, 0x6f,
|
||||
0x4d, 0xc2, 0x6d, 0x34, 0x5e, 0x96, 0xc3, 0x2a, 0x3e, 0x13, 0x51, 0x86, 0xc0, 0x41, 0xf6, 0x42, 0x05, 0xab, 0x79,
|
||||
0xb5, 0x8e, 0x14, 0xf6, 0x36, 0x07, 0x07, 0x0d, 0x2f, 0xb6, 0xd3, 0xb9, 0xd6, 0x8d, 0x40, 0xdb, 0x32, 0x07, 0x19,
|
||||
0xf9, 0x93, 0x9e, 0x70, 0x24, 0xcb, 0xf9, 0x5c, 0x45, 0xee, 0x48, 0x42, 0x3d, 0x99, 0xb2, 0xec, 0x66, 0x26, 0x9b,
|
||||
0xb9, 0xc8, 0x9d, 0xcc, 0x74, 0xda, 0xe8, 0x61, 0x50, 0xb0, 0x11, 0x64, 0x93, 0x61, 0x55, 0x14, 0xc5, 0x04, 0x43,
|
||||
0x01, 0x4e, 0xdf, 0xcb, 0xa2, 0xd0, 0x3d, 0x31, 0x62, 0x07, 0x30, 0xdd, 0xd0, 0x6c, 0xf4, 0x18, 0x71, 0x04, 0x3c,
|
||||
0x9e, 0xec, 0xdc, 0xf1, 0x27, 0xd8, 0xc2, 0x15, 0x2a, 0x69, 0xb1, 0xb6, 0x11, 0xe6, 0xb6, 0x66, 0x5c, 0x1c, 0xa2,
|
||||
0x37, 0x69, 0x32, 0x19, 0xc6, 0x0f, 0x86, 0xa5, 0x33, 0xd3, 0x0d, 0xa1, 0x09, 0x0e, 0x98, 0x7e, 0x86, 0x46, 0xe1,
|
||||
0xa9, 0xdf, 0xae, 0xb6, 0xee, 0x90, 0x20, 0x9b, 0x1d, 0x77, 0x51, 0xc1, 0x6a, 0xf2, 0x7d, 0xae, 0x34, 0x2f, 0xd6,
|
||||
0xce, 0x30, 0x83, 0x23, 0x4c, 0x16, 0x9c, 0xbd, 0x53, 0x64, 0x05, 0x10, 0x93, 0xce, 0x86, 0xc3, 0x35, 0xd4, 0x6a,
|
||||
0x88, 0xd3, 0x5e, 0x4d, 0x97, 0xa0, 0x77, 0x75, 0xfd, 0x1b, 0xb7, 0xc9, 0xc5, 0x4d, 0xcd, 0x24, 0x8e, 0x0a, 0x67,
|
||||
0xda, 0x60, 0x4c, 0xeb, 0xc8, 0x39, 0xc3, 0xbb, 0x1a, 0xb6, 0x8c, 0x32, 0xf4, 0x1c, 0x61, 0x76, 0xb3, 0x75, 0x17,
|
||||
0xef, 0xa0, 0x5d, 0x11, 0xd5, 0x54, 0x3c, 0x1f, 0xf8, 0x3a, 0x16, 0xe2, 0xef, 0xc3, 0x13, 0xe0, 0x75, 0x13, 0xb3,
|
||||
0xb7, 0x0b, 0xf5, 0x49, 0x71, 0xce, 0x02, 0xff, 0x27, 0x37, 0x92, 0x17, 0x45, 0x38, 0x2d, 0xf6, 0x91, 0x32, 0x63,
|
||||
0xd2, 0x94, 0x46, 0x97, 0x5a, 0xb1, 0xd7, 0xbf, 0x66, 0x4c, 0x66, 0xe0, 0x03, 0x05, 0x23, 0x8c, 0xef, 0x9b, 0x80,
|
||||
0xf0, 0x3c, 0xc1, 0x4e, 0x95, 0x1e, 0xb4, 0x2f, 0x64, 0x0c, 0x76, 0x47, 0x48, 0xdd, 0x69, 0x46, 0xfd, 0x59, 0x87,
|
||||
0x3e, 0x7d, 0xdd, 0x60, 0x7d, 0x60, 0xdb, 0x11, 0x33, 0xa2, 0x1b, 0x32, 0x84, 0xc0, 0x75, 0xdd, 0x78, 0x2a, 0xd3,
|
||||
0x4f, 0x15, 0x30, 0x05, 0x64, 0xc9, 0xb8, 0x76, 0xb1, 0x1a, 0x3b, 0xfe, 0xbe, 0x8e, 0x51, 0x29, 0xb2, 0xa6, 0x43,
|
||||
0xc1, 0xc6, 0xe5, 0xa8, 0x37, 0x70, 0x09, 0xda, 0x68, 0x32, 0x06, 0x46, 0x69, 0x6c, 0x46, 0x2e, 0x61, 0x5d, 0x4b,
|
||||
0x4b, 0xbc, 0x25, 0x2f, 0xb8, 0x79, 0xb2, 0xa4, 0x71, 0x97, 0xe4, 0x46, 0x83, 0x89, 0x73, 0xff, 0xbc, 0xea, 0xa8,
|
||||
0x0a, 0xc4, 0x0c, 0x27, 0xe9, 0x28, 0x24, 0xe8, 0x76, 0x06, 0x65, 0x53, 0x61, 0x58, 0x93, 0xcb, 0xcb, 0x77, 0xbf,
|
||||
0xa7, 0x06, 0xcc, 0xad, 0x1c, 0xf6, 0xa7, 0x5e, 0xcc, 0x10, 0x83, 0xd4, 0xe9, 0x49, 0xff, 0xa8, 0x6a, 0xb1, 0xbf,
|
||||
0xa0, 0x07, 0xf9, 0x1d, 0x1d, 0x9f, 0x86, 0xcd, 0x5e, 0x4f, 0xf7, 0xd7, 0x95, 0x4a, 0x7a, 0x89, 0x80, 0x62, 0x6f,
|
||||
0x58, 0xc4, 0x9e, 0x01, 0xdc, 0x9f, 0x97, 0x03, 0x1f, 0xc6, 0xe9, 0xe3, 0xd5, 0x4b, 0xf2, 0xb9, 0xc5, 0x26, 0x00,
|
||||
0x7d, 0xd8, 0x3a, 0xaf, 0xf0, 0x65, 0x58, 0x36, 0x79, 0xf2, 0xe9, 0xe3, 0xe5, 0xd5, 0xde, 0xc3, 0x79, 0xc7, 0x44,
|
||||
0x40, 0x64, 0xfd, 0xf3, 0x6e, 0x5e, 0x69, 0xde, 0x32, 0xa9, 0x3b, 0xb5, 0x8e, 0xe9, 0x22, 0x3b, 0x1f, 0xba, 0x73,
|
||||
0x7c, 0x03, 0x41, 0xef, 0x46, 0x2f, 0x98, 0x92, 0x1d, 0xaa, 0x9d, 0xb5, 0x7b, 0xb8, 0xbc, 0xfe, 0xb6, 0xbd, 0xfe,
|
||||
0xea, 0xbd, 0xee, 0xa5, 0xfb, 0x0f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x95, 0x16, 0x6b, 0x8f, 0xdb, 0x36, 0xf2, 0x7b, 0x7e,
|
||||
0x05, 0x8f, 0x49, 0xbb, 0x52, 0xb3, 0x7a, 0x7a, 0xed, 0x6c, 0x24, 0x51, 0x45, 0x9a, 0xbb, 0xa2, 0x05, 0x9a, 0x36,
|
||||
0xc0, 0x6e, 0x73, 0x1f, 0x82, 0x00, 0x4b, 0x53, 0x23, 0x8b, 0x31, 0x45, 0xea, 0x48, 0xca, 0x8f, 0x18, 0xbe, 0xdf,
|
||||
0x7e, 0xa0, 0x24, 0x7b, 0x9d, 0x45, 0x73, 0xb8, 0xb3, 0x60, 0x61, 0x38, 0xef, 0x19, 0xcd, 0x83, 0xc5, 0xdf, 0x2a,
|
||||
0xc5, 0xec, 0xbe, 0x03, 0xd4, 0xd8, 0x56, 0x94, 0x85, 0x7b, 0x23, 0x41, 0xe5, 0x8a, 0x80, 0x2c, 0x8b, 0x06, 0x68,
|
||||
0x55, 0x16, 0x2d, 0x58, 0x8a, 0x58, 0x43, 0xb5, 0x01, 0x4b, 0xfe, 0xbc, 0xff, 0x39, 0xb8, 0x2d, 0x0b, 0xc1, 0xe5,
|
||||
0x1a, 0x69, 0x10, 0x84, 0x33, 0x25, 0x51, 0xa3, 0xa1, 0x26, 0x15, 0xb5, 0x34, 0xe3, 0x2d, 0x5d, 0xc1, 0x24, 0x22,
|
||||
0x69, 0x0b, 0x64, 0xc3, 0x61, 0xdb, 0x29, 0x6d, 0x11, 0x53, 0xd2, 0x82, 0xb4, 0x04, 0x6f, 0x79, 0x65, 0x1b, 0x52,
|
||||
0xc1, 0x86, 0x33, 0x08, 0x86, 0xc3, 0x35, 0x97, 0xdc, 0x72, 0x2a, 0x02, 0xc3, 0xa8, 0x00, 0x92, 0x5c, 0xf7, 0x06,
|
||||
0xf4, 0x70, 0xa0, 0x4b, 0x01, 0x44, 0x2a, 0x5c, 0x16, 0x86, 0x69, 0xde, 0x59, 0xe4, 0x5c, 0x25, 0xad, 0xaa, 0x7a,
|
||||
0x01, 0x65, 0x14, 0x51, 0x63, 0xc0, 0x9a, 0x88, 0xcb, 0x0a, 0x76, 0xe1, 0x32, 0x66, 0x2c, 0x86, 0xdb, 0xdb, 0xf0,
|
||||
0xb3, 0x79, 0x56, 0x29, 0xd6, 0xb7, 0x20, 0x6d, 0x28, 0x14, 0xa3, 0x96, 0x2b, 0x19, 0x1a, 0xa0, 0x9a, 0x35, 0x84,
|
||||
0x10, 0xfc, 0xa3, 0xa1, 0x1b, 0xc0, 0xdf, 0x7f, 0xef, 0x9d, 0x99, 0x56, 0x60, 0xff, 0x21, 0xc0, 0x81, 0xe6, 0xa7,
|
||||
0xfd, 0x3d, 0x5d, 0xfd, 0x4e, 0x5b, 0xf0, 0x30, 0x35, 0xbc, 0x02, 0xec, 0x7f, 0x8c, 0x3f, 0x85, 0xc6, 0xee, 0x05,
|
||||
0x84, 0x15, 0x37, 0x9d, 0xa0, 0x7b, 0x82, 0x97, 0x42, 0xb1, 0x35, 0xf6, 0xf3, 0xba, 0x97, 0xcc, 0x29, 0x47, 0xc6,
|
||||
0x03, 0xff, 0x20, 0xc0, 0x22, 0x4b, 0xde, 0x51, 0xdb, 0x84, 0x2d, 0xdd, 0x79, 0x23, 0xc0, 0xa5, 0x97, 0xfe, 0xe0,
|
||||
0xc1, 0xcb, 0x24, 0x8e, 0xfd, 0xeb, 0xe1, 0x15, 0xfb, 0x51, 0x12, 0xc7, 0xb9, 0x06, 0xdb, 0x6b, 0x89, 0xa8, 0xf7,
|
||||
0x50, 0x74, 0xd4, 0x36, 0xa8, 0x22, 0xf8, 0x5d, 0x92, 0xa2, 0xe4, 0x75, 0x98, 0xce, 0x7f, 0x0b, 0x5f, 0xa1, 0x9b,
|
||||
0x30, 0x9d, 0xb3, 0x57, 0xc1, 0x1c, 0x25, 0x37, 0xc1, 0x1c, 0xa5, 0x69, 0x38, 0x47, 0xf1, 0x17, 0x8c, 0x6a, 0x2e,
|
||||
0x04, 0xc1, 0x52, 0x49, 0xc0, 0xc8, 0x58, 0xad, 0xd6, 0x40, 0x30, 0xeb, 0xb5, 0x06, 0x69, 0xdf, 0x2a, 0xa1, 0x34,
|
||||
0x8e, 0xca, 0x67, 0xff, 0x97, 0x42, 0xab, 0xa9, 0x34, 0xb5, 0xd2, 0x2d, 0xc1, 0x43, 0xf6, 0xbd, 0x17, 0x07, 0x7b,
|
||||
0x44, 0xee, 0xe5, 0x5f, 0x10, 0x03, 0xa5, 0xf9, 0x8a, 0x4b, 0x82, 0x9d, 0xc6, 0x5b, 0x1c, 0x95, 0x0f, 0xfe, 0xf1,
|
||||
0x1c, 0x3d, 0x75, 0xd1, 0x4f, 0xf1, 0x28, 0xef, 0xe3, 0x43, 0x61, 0x36, 0x2b, 0xb4, 0x6b, 0x85, 0x34, 0x04, 0x37,
|
||||
0xd6, 0x76, 0x59, 0x14, 0x6d, 0xb7, 0xdb, 0x70, 0x3b, 0x0b, 0x95, 0x5e, 0x45, 0x69, 0x1c, 0xc7, 0x91, 0xd9, 0xac,
|
||||
0x30, 0x1a, 0x0b, 0x01, 0xa7, 0x37, 0x18, 0x35, 0xc0, 0x57, 0x8d, 0x1d, 0xe0, 0xf2, 0xc5, 0x01, 0x8e, 0x85, 0xe3,
|
||||
0x28, 0x1f, 0x3e, 0x5d, 0x58, 0xe1, 0x17, 0x56, 0xe0, 0x47, 0xea, 0xe1, 0x53, 0x98, 0x57, 0x43, 0x98, 0xaf, 0x68,
|
||||
0x8a, 0x52, 0x14, 0x0f, 0x4f, 0x1a, 0x38, 0x78, 0x3a, 0x05, 0x4f, 0x4e, 0xe8, 0xe2, 0xe4, 0xa0, 0x76, 0x11, 0xbc,
|
||||
0x3e, 0xcb, 0x26, 0x0e, 0xb3, 0x49, 0xe2, 0x47, 0x84, 0x13, 0xf8, 0x65, 0x71, 0x79, 0x0e, 0xd2, 0x0f, 0x97, 0x0c,
|
||||
0xce, 0x5a, 0x93, 0x7c, 0x58, 0xd0, 0x39, 0x9a, 0x4f, 0x98, 0x79, 0xe0, 0xe0, 0xf3, 0x09, 0xcd, 0x37, 0x69, 0x93,
|
||||
0xb4, 0xc1, 0x22, 0x98, 0xd3, 0x19, 0x9a, 0x4d, 0x8e, 0xcc, 0xd0, 0x6c, 0x93, 0x36, 0x8b, 0x0f, 0x8b, 0x4b, 0x5c,
|
||||
0x30, 0xfb, 0x72, 0x15, 0x95, 0xd8, 0xcf, 0x30, 0x7e, 0x8c, 0x5c, 0x5d, 0x46, 0x1e, 0x7e, 0x56, 0x5c, 0x7a, 0x18,
|
||||
0xfb, 0xc7, 0x1a, 0x2c, 0x6b, 0x3c, 0x1c, 0x31, 0x25, 0x6b, 0xbe, 0x0a, 0x3f, 0x1b, 0x25, 0xb1, 0x1f, 0xda, 0x06,
|
||||
0xa4, 0x77, 0x12, 0x75, 0x82, 0x30, 0x50, 0xbc, 0xa7, 0x14, 0xeb, 0x1f, 0xce, 0xf5, 0x6f, 0xb9, 0x15, 0x40, 0x6c,
|
||||
0xe8, 0x1a, 0xf6, 0xfa, 0x8c, 0x5d, 0xaa, 0x6a, 0xff, 0x8d, 0xd6, 0x68, 0x92, 0xb1, 0x2f, 0xb8, 0x94, 0xa0, 0xef,
|
||||
0x61, 0x67, 0x09, 0x7e, 0xf7, 0xe6, 0x2d, 0x7a, 0x53, 0x55, 0x1a, 0x8c, 0xc9, 0x10, 0x7e, 0x69, 0xc3, 0x96, 0xb2,
|
||||
0xff, 0x5d, 0x57, 0xf2, 0x95, 0xae, 0x7f, 0xf2, 0x9f, 0x39, 0xfa, 0x1d, 0xec, 0x56, 0xe9, 0xf5, 0xa4, 0xcd, 0xb9,
|
||||
0x96, 0xbb, 0x0e, 0xd3, 0xc4, 0x86, 0xb4, 0x33, 0xa1, 0x11, 0x9c, 0x81, 0x97, 0xf8, 0x61, 0x4b, 0xbb, 0xc7, 0xa8,
|
||||
0xe4, 0x29, 0x51, 0x0f, 0x45, 0xc5, 0x37, 0x88, 0x09, 0x6a, 0x0c, 0xc1, 0x72, 0x54, 0x85, 0xd1, 0x33, 0x34, 0xfc,
|
||||
0x94, 0x64, 0x82, 0xb3, 0x35, 0xc1, 0x7f, 0x31, 0x01, 0x7e, 0xda, 0xff, 0x5a, 0x79, 0x57, 0xc6, 0xf0, 0xea, 0xca,
|
||||
0x0f, 0x37, 0x54, 0xf4, 0x80, 0x08, 0xb2, 0x0d, 0x37, 0x8f, 0x0e, 0xe6, 0xdf, 0x14, 0xeb, 0xcc, 0xfa, 0xca, 0x0f,
|
||||
0x6b, 0xc5, 0x7a, 0xe3, 0xf9, 0xb8, 0x9c, 0xcc, 0x15, 0x74, 0x1c, 0x90, 0xf8, 0x39, 0x7e, 0xe2, 0x51, 0x20, 0xa0,
|
||||
0xb6, 0x67, 0x3e, 0x84, 0x5e, 0x1c, 0x8c, 0x27, 0x43, 0x6d, 0x0c, 0xf7, 0x8f, 0x67, 0x64, 0x61, 0x3a, 0x2a, 0x9f,
|
||||
0x0a, 0x3a, 0x07, 0x5d, 0xab, 0xc8, 0xd0, 0x41, 0xae, 0x5f, 0x3a, 0x2a, 0xcf, 0x06, 0x23, 0x7a, 0x02, 0x5f, 0x1c,
|
||||
0xb8, 0x27, 0xdd, 0x14, 0x5c, 0x9f, 0x35, 0x16, 0x51, 0xc5, 0x37, 0xe5, 0xc3, 0xd1, 0x7f, 0x8c, 0xe3, 0x5f, 0x3d,
|
||||
0xe8, 0xfd, 0x1d, 0x08, 0x60, 0x56, 0x69, 0x0f, 0x3f, 0x97, 0x60, 0xb1, 0x3f, 0x06, 0xfc, 0xcb, 0xfd, 0xbb, 0xdf,
|
||||
0x88, 0xf2, 0xb4, 0x7f, 0xfd, 0x2d, 0x6e, 0xb7, 0x0a, 0x3e, 0x6a, 0x10, 0xff, 0x26, 0x57, 0x6e, 0x19, 0x5c, 0x7d,
|
||||
0xc2, 0x7e, 0x38, 0xc4, 0xfb, 0xf0, 0xb8, 0x11, 0x5c, 0x3b, 0xbf, 0xdc, 0xb5, 0xe2, 0xda, 0x45, 0x18, 0x2c, 0xe6,
|
||||
0xfe, 0xf1, 0xe1, 0xe8, 0x1f, 0xfd, 0xbc, 0x88, 0xc6, 0xb9, 0x5e, 0x16, 0xc3, 0x88, 0x2d, 0x7f, 0x38, 0x2c, 0xd5,
|
||||
0x2e, 0x30, 0xfc, 0x0b, 0x97, 0xab, 0x8c, 0xcb, 0x06, 0x34, 0xb7, 0xc7, 0x8a, 0x6f, 0xae, 0xb9, 0xec, 0x7a, 0x7b,
|
||||
0xe8, 0x68, 0x55, 0x39, 0xca, 0xbc, 0xdb, 0xe5, 0xb5, 0x92, 0xd6, 0x71, 0x42, 0x96, 0x40, 0x7b, 0x1c, 0xe9, 0xc3,
|
||||
0x44, 0xc9, 0x5e, 0xcf, 0xbf, 0x3b, 0xba, 0x82, 0x3b, 0x58, 0xd8, 0xd9, 0x80, 0x0a, 0xbe, 0x92, 0x19, 0x03, 0x69,
|
||||
0x41, 0x8f, 0x42, 0x35, 0x6d, 0xb9, 0xd8, 0x67, 0x86, 0x4a, 0x13, 0x18, 0xd0, 0xbc, 0x3e, 0x2e, 0x7b, 0x6b, 0x95,
|
||||
0x3c, 0x2c, 0x95, 0xae, 0x40, 0x67, 0x71, 0x3e, 0x02, 0x81, 0xa6, 0x15, 0xef, 0x4d, 0x16, 0xce, 0x34, 0xb4, 0xf9,
|
||||
0x92, 0xb2, 0xf5, 0x4a, 0xab, 0x5e, 0x56, 0x01, 0x73, 0x93, 0x36, 0x7b, 0x9e, 0xd4, 0x74, 0x06, 0x2c, 0x9f, 0x4e,
|
||||
0x75, 0x5d, 0xe7, 0x82, 0x4b, 0x08, 0xc6, 0x59, 0x96, 0xa5, 0xe1, 0x8d, 0x13, 0xbb, 0x70, 0x33, 0x4c, 0x1d, 0x62,
|
||||
0xf4, 0x31, 0x89, 0xe3, 0xef, 0xf2, 0x53, 0x38, 0x71, 0xce, 0x7a, 0x6d, 0x94, 0xce, 0x3a, 0xc5, 0x9d, 0x9b, 0xc7,
|
||||
0x96, 0x72, 0x79, 0xe9, 0xbd, 0x2b, 0x93, 0x7c, 0x5a, 0x3f, 0x19, 0x97, 0x83, 0x99, 0x61, 0x09, 0xe5, 0x2d, 0x97,
|
||||
0xe3, 0x0e, 0xcd, 0xd2, 0x45, 0xdc, 0xed, 0x8e, 0xe1, 0x54, 0x20, 0x87, 0x13, 0x77, 0x2d, 0x60, 0x97, 0x7f, 0xee,
|
||||
0x8d, 0xe5, 0xf5, 0x3e, 0x98, 0x76, 0x70, 0x66, 0x3a, 0xca, 0x20, 0x58, 0x82, 0xdd, 0x02, 0xc8, 0x7c, 0xb0, 0x11,
|
||||
0x70, 0x0b, 0xad, 0x99, 0xf2, 0x74, 0x56, 0x33, 0x14, 0xe8, 0xd7, 0xba, 0xfe, 0x1b, 0xb7, 0xab, 0xc5, 0x43, 0x4b,
|
||||
0xf5, 0x8a, 0xcb, 0x60, 0xa9, 0xac, 0x55, 0x6d, 0x16, 0xbc, 0xea, 0x76, 0xf9, 0x84, 0x72, 0xca, 0xb2, 0xc4, 0xb9,
|
||||
0x39, 0xec, 0xd6, 0x53, 0xbe, 0x93, 0x6e, 0x87, 0x8c, 0x12, 0xbc, 0x9a, 0xf8, 0x06, 0x16, 0x14, 0x9f, 0xd3, 0x93,
|
||||
0xcc, 0xbb, 0x1d, 0x72, 0xb8, 0x53, 0xaa, 0x6f, 0xea, 0x5b, 0x9a, 0xc4, 0x7f, 0xf1, 0x45, 0xaa, 0xba, 0x4e, 0x97,
|
||||
0xf5, 0x39, 0x53, 0x6e, 0x4d, 0xba, 0xd6, 0x18, 0x4a, 0xab, 0x88, 0xc6, 0xdb, 0x8c, 0xab, 0x8c, 0xb2, 0x70, 0x19,
|
||||
0x2e, 0x8b, 0x26, 0x41, 0xbc, 0x22, 0x2d, 0x65, 0xe5, 0xc5, 0xf8, 0x2a, 0xa2, 0x26, 0x39, 0x91, 0x9a, 0xa4, 0xfc,
|
||||
0x6a, 0x18, 0x8d, 0xb4, 0xc1, 0xfb, 0xf2, 0xad, 0x92, 0x12, 0x98, 0xe5, 0x72, 0x85, 0xac, 0x42, 0x53, 0x0a, 0xc2,
|
||||
0x30, 0x2c, 0x96, 0xba, 0x7c, 0x2f, 0x80, 0x1a, 0x40, 0x5b, 0xca, 0x6d, 0x58, 0x44, 0x23, 0xff, 0xd8, 0xc7, 0xbc,
|
||||
0x22, 0x12, 0x6c, 0x39, 0x35, 0x6c, 0xd1, 0xcc, 0x46, 0x03, 0x77, 0x60, 0x9d, 0x26, 0x67, 0x60, 0x56, 0x16, 0x6e,
|
||||
0xe5, 0x22, 0x3a, 0x8c, 0x34, 0x12, 0x6d, 0x79, 0xcd, 0xdd, 0x95, 0xa5, 0x2c, 0x86, 0x22, 0x77, 0x1a, 0x5c, 0x9e,
|
||||
0xc7, 0xeb, 0xd5, 0x00, 0x09, 0x90, 0x2b, 0xdb, 0x90, 0x59, 0x8a, 0x3a, 0x41, 0x19, 0x34, 0x4a, 0x54, 0xa0, 0xc9,
|
||||
0xdd, 0xdd, 0xaf, 0x7f, 0x2f, 0x9d, 0x33, 0x8f, 0x72, 0x9d, 0x59, 0x8f, 0x62, 0x0e, 0x98, 0xa4, 0x16, 0x37, 0xe3,
|
||||
0xa5, 0xaa, 0xa3, 0xc6, 0x6c, 0x95, 0xae, 0xbe, 0xd2, 0xf1, 0x7e, 0x42, 0x8e, 0x7a, 0x86, 0xff, 0xd0, 0x2a, 0xe5,
|
||||
0x1d, 0xdd, 0x40, 0x11, 0x4d, 0x87, 0x22, 0x72, 0x0e, 0x8f, 0xf4, 0x66, 0xe2, 0x6b, 0x92, 0xf2, 0x8f, 0xfb, 0x37,
|
||||
0xe8, 0xcf, 0xae, 0xa2, 0x16, 0xc6, 0xb4, 0x0d, 0x51, 0xb5, 0x60, 0x1b, 0x55, 0x91, 0xf7, 0x7f, 0xdc, 0xdd, 0x9f,
|
||||
0x23, 0xec, 0x07, 0x26, 0x04, 0x92, 0x8d, 0xd7, 0xbb, 0x5e, 0x58, 0xde, 0x51, 0x6d, 0x07, 0xb5, 0x81, 0x9b, 0x22,
|
||||
0xa7, 0x18, 0x06, 0x7a, 0xcd, 0x05, 0x8c, 0x61, 0x8c, 0x82, 0x25, 0x3a, 0x79, 0x75, 0xb2, 0xf6, 0xc4, 0xaf, 0x68,
|
||||
0xfc, 0xda, 0xd1, 0xf8, 0xe9, 0xa3, 0xe1, 0xa6, 0xfb, 0x1f, 0x53, 0x58, 0x46, 0xb2, 0xf9, 0x0a, 0x00, 0x00};
|
||||
|
||||
} // namespace captive_portal
|
||||
} // namespace esphome
|
||||
|
@@ -43,13 +43,6 @@ void BLEClientBase::setup() {
|
||||
void BLEClientBase::set_state(espbt::ClientState st) {
|
||||
ESP_LOGV(TAG, "[%d] [%s] Set state %d", this->connection_index_, this->address_str_.c_str(), (int) st);
|
||||
ESPBTClient::set_state(st);
|
||||
|
||||
if (st == espbt::ClientState::READY_TO_CONNECT) {
|
||||
// Enable loop for state processing
|
||||
this->enable_loop();
|
||||
// Connect immediately instead of waiting for next loop
|
||||
this->connect();
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClientBase::loop() {
|
||||
@@ -65,8 +58,8 @@ void BLEClientBase::loop() {
|
||||
}
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
}
|
||||
// If its idle, we can disable the loop as set_state
|
||||
// will enable it again when we need to connect.
|
||||
// If idle, we can disable the loop as connect()
|
||||
// will enable it again when a connection is needed.
|
||||
else if (this->state_ == espbt::ClientState::IDLE) {
|
||||
this->disable_loop();
|
||||
}
|
||||
@@ -108,9 +101,20 @@ bool BLEClientBase::parse_device(const espbt::ESPBTDevice &device) {
|
||||
#endif
|
||||
|
||||
void BLEClientBase::connect() {
|
||||
// Prevent duplicate connection attempts
|
||||
if (this->state_ == espbt::ClientState::CONNECTING || this->state_ == espbt::ClientState::CONNECTED ||
|
||||
this->state_ == espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGW(TAG, "[%d] [%s] Connection already in progress, state=%s", this->connection_index_,
|
||||
this->address_str_.c_str(), espbt::client_state_to_string(this->state_));
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "[%d] [%s] 0x%02x Connecting", this->connection_index_, this->address_str_.c_str(),
|
||||
this->remote_addr_type_);
|
||||
this->paired_ = false;
|
||||
// Enable loop for state processing
|
||||
this->enable_loop();
|
||||
// Immediately transition to CONNECTING to prevent duplicate connection attempts
|
||||
this->set_state(espbt::ClientState::CONNECTING);
|
||||
|
||||
// Determine connection parameters based on connection type
|
||||
if (this->connection_type_ == espbt::ConnectionType::V3_WITHOUT_CACHE) {
|
||||
@@ -168,7 +172,7 @@ void BLEClientBase::unconditional_disconnect() {
|
||||
this->log_gattc_warning_("esp_ble_gattc_close", err);
|
||||
}
|
||||
|
||||
if (this->state_ == espbt::ClientState::READY_TO_CONNECT || this->state_ == espbt::ClientState::DISCOVERED) {
|
||||
if (this->state_ == espbt::ClientState::DISCOVERED) {
|
||||
this->set_address(0);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
@@ -212,8 +216,6 @@ void BLEClientBase::handle_connection_result_(esp_err_t ret) {
|
||||
if (ret) {
|
||||
this->log_gattc_warning_("esp_ble_gattc_open", ret);
|
||||
this->set_state(espbt::ClientState::IDLE);
|
||||
} else {
|
||||
this->set_state(espbt::ClientState::CONNECTING);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -51,8 +51,6 @@ const char *client_state_to_string(ClientState state) {
|
||||
return "IDLE";
|
||||
case ClientState::DISCOVERED:
|
||||
return "DISCOVERED";
|
||||
case ClientState::READY_TO_CONNECT:
|
||||
return "READY_TO_CONNECT";
|
||||
case ClientState::CONNECTING:
|
||||
return "CONNECTING";
|
||||
case ClientState::CONNECTED:
|
||||
@@ -795,7 +793,7 @@ void ESP32BLETracker::try_promote_discovered_clients_() {
|
||||
#ifdef USE_ESP32_BLE_SOFTWARE_COEXISTENCE
|
||||
this->update_coex_preference_(true);
|
||||
#endif
|
||||
client->set_state(ClientState::READY_TO_CONNECT);
|
||||
client->connect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -159,8 +159,6 @@ enum class ClientState : uint8_t {
|
||||
IDLE,
|
||||
// Device advertisement found.
|
||||
DISCOVERED,
|
||||
// Device is discovered and the scanner is stopped
|
||||
READY_TO_CONNECT,
|
||||
// Connection in progress.
|
||||
CONNECTING,
|
||||
// Initial connection established.
|
||||
@@ -313,7 +311,6 @@ class ESP32BLETracker : public Component,
|
||||
counts.discovered++;
|
||||
break;
|
||||
case ClientState::CONNECTING:
|
||||
case ClientState::READY_TO_CONNECT:
|
||||
counts.connecting++;
|
||||
break;
|
||||
default:
|
||||
|
1
esphome/components/inkplate/__init__.py
Normal file
1
esphome/components/inkplate/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@jesserockz", "@JosipKuci"]
|
105
esphome/components/inkplate/const.py
Normal file
105
esphome/components/inkplate/const.py
Normal file
@@ -0,0 +1,105 @@
|
||||
WAVEFORMS = {
|
||||
"inkplate_6": (
|
||||
(0, 1, 1, 0, 0, 1, 1, 0, 0),
|
||||
(0, 1, 2, 1, 1, 2, 1, 0, 0),
|
||||
(1, 1, 1, 2, 2, 1, 0, 0, 0),
|
||||
(0, 0, 0, 1, 1, 1, 2, 0, 0),
|
||||
(2, 1, 1, 1, 2, 1, 2, 0, 0),
|
||||
(2, 2, 1, 1, 2, 1, 2, 0, 0),
|
||||
(1, 1, 1, 2, 1, 2, 2, 0, 0),
|
||||
(0, 0, 0, 0, 0, 0, 2, 0, 0),
|
||||
),
|
||||
"inkplate_10": (
|
||||
(0, 0, 0, 0, 0, 0, 0, 1, 0),
|
||||
(0, 0, 0, 2, 2, 2, 1, 1, 0),
|
||||
(0, 0, 2, 1, 1, 2, 2, 1, 0),
|
||||
(0, 1, 2, 2, 1, 2, 2, 1, 0),
|
||||
(0, 0, 2, 1, 2, 2, 2, 1, 0),
|
||||
(0, 2, 2, 2, 2, 2, 2, 1, 0),
|
||||
(0, 0, 0, 0, 0, 2, 1, 2, 0),
|
||||
(0, 0, 0, 2, 2, 2, 2, 2, 0),
|
||||
),
|
||||
"inkplate_6_plus": (
|
||||
(0, 0, 0, 0, 0, 2, 1, 1, 0),
|
||||
(0, 0, 2, 1, 1, 1, 2, 1, 0),
|
||||
(0, 2, 2, 2, 1, 1, 2, 1, 0),
|
||||
(0, 0, 2, 2, 2, 1, 2, 1, 0),
|
||||
(0, 0, 0, 0, 2, 2, 2, 1, 0),
|
||||
(0, 0, 2, 1, 2, 1, 1, 2, 0),
|
||||
(0, 0, 2, 2, 2, 1, 1, 2, 0),
|
||||
(0, 0, 0, 0, 2, 2, 2, 2, 0),
|
||||
),
|
||||
"inkplate_6_v2": (
|
||||
(1, 0, 1, 0, 1, 1, 1, 0, 0),
|
||||
(0, 0, 0, 1, 1, 1, 1, 0, 0),
|
||||
(1, 1, 1, 1, 0, 2, 1, 0, 0),
|
||||
(1, 1, 1, 2, 2, 1, 1, 0, 0),
|
||||
(1, 1, 1, 1, 2, 2, 1, 0, 0),
|
||||
(0, 1, 1, 1, 2, 2, 1, 0, 0),
|
||||
(0, 0, 0, 0, 1, 1, 2, 0, 0),
|
||||
(0, 0, 0, 0, 0, 1, 2, 0, 0),
|
||||
),
|
||||
"inkplate_5": (
|
||||
(0, 0, 1, 1, 0, 1, 1, 1, 0),
|
||||
(0, 1, 1, 1, 1, 2, 0, 1, 0),
|
||||
(1, 2, 2, 0, 2, 1, 1, 1, 0),
|
||||
(1, 1, 1, 2, 0, 1, 1, 2, 0),
|
||||
(0, 1, 1, 1, 2, 0, 1, 2, 0),
|
||||
(0, 0, 0, 1, 1, 2, 1, 2, 0),
|
||||
(1, 1, 1, 2, 0, 2, 1, 2, 0),
|
||||
(0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
),
|
||||
"inkplate_5_v2": (
|
||||
(0, 0, 1, 1, 2, 1, 1, 1, 0),
|
||||
(1, 1, 2, 2, 1, 2, 1, 1, 0),
|
||||
(0, 1, 2, 2, 1, 1, 2, 1, 0),
|
||||
(0, 0, 1, 1, 1, 1, 1, 2, 0),
|
||||
(1, 2, 1, 2, 1, 1, 1, 2, 0),
|
||||
(0, 1, 1, 1, 2, 0, 1, 2, 0),
|
||||
(1, 1, 1, 2, 2, 2, 1, 2, 0),
|
||||
(0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
),
|
||||
}
|
||||
|
||||
INKPLATE_10_CUSTOM_WAVEFORMS = (
|
||||
(
|
||||
(0, 0, 0, 0, 0, 0, 0, 0, 0),
|
||||
(0, 0, 0, 2, 1, 2, 1, 1, 0),
|
||||
(0, 0, 0, 2, 2, 1, 2, 1, 0),
|
||||
(0, 0, 2, 2, 1, 2, 2, 1, 0),
|
||||
(0, 0, 0, 2, 1, 1, 1, 2, 0),
|
||||
(0, 0, 2, 2, 2, 1, 1, 2, 0),
|
||||
(0, 0, 0, 0, 0, 1, 2, 2, 0),
|
||||
(0, 0, 0, 0, 2, 2, 2, 2, 0),
|
||||
),
|
||||
(
|
||||
(0, 3, 3, 3, 3, 3, 3, 3, 0),
|
||||
(0, 1, 2, 1, 1, 2, 2, 1, 0),
|
||||
(0, 2, 2, 2, 1, 2, 2, 1, 0),
|
||||
(0, 0, 2, 2, 2, 2, 2, 1, 0),
|
||||
(0, 3, 3, 2, 1, 1, 1, 2, 0),
|
||||
(0, 3, 3, 2, 2, 1, 1, 2, 0),
|
||||
(0, 2, 1, 2, 1, 2, 1, 2, 0),
|
||||
(0, 3, 3, 3, 2, 2, 2, 2, 0),
|
||||
),
|
||||
(
|
||||
(0, 0, 0, 0, 0, 0, 0, 1, 0),
|
||||
(0, 0, 0, 2, 2, 2, 1, 1, 0),
|
||||
(0, 0, 2, 1, 1, 2, 2, 1, 0),
|
||||
(1, 1, 2, 2, 1, 2, 2, 1, 0),
|
||||
(0, 0, 2, 1, 2, 2, 2, 1, 0),
|
||||
(0, 1, 2, 2, 2, 2, 2, 1, 0),
|
||||
(0, 0, 0, 2, 2, 2, 1, 2, 0),
|
||||
(0, 0, 0, 2, 2, 2, 2, 2, 0),
|
||||
),
|
||||
(
|
||||
(0, 0, 0, 0, 0, 0, 0, 1, 0),
|
||||
(0, 0, 0, 2, 2, 2, 1, 1, 0),
|
||||
(2, 2, 2, 1, 0, 2, 1, 0, 0),
|
||||
(2, 1, 1, 2, 1, 1, 1, 2, 0),
|
||||
(2, 2, 2, 1, 1, 1, 0, 2, 0),
|
||||
(2, 2, 2, 1, 1, 2, 1, 2, 0),
|
||||
(0, 0, 0, 0, 2, 1, 2, 2, 0),
|
||||
(0, 0, 0, 0, 2, 2, 2, 2, 0),
|
||||
),
|
||||
)
|
238
esphome/components/inkplate/display.py
Normal file
238
esphome/components/inkplate/display.py
Normal file
@@ -0,0 +1,238 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display, i2c
|
||||
from esphome.components.esp32 import CONF_CPU_FREQUENCY
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FULL_UPDATE_EVERY,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_OE_PIN,
|
||||
CONF_PAGES,
|
||||
CONF_TRANSFORM,
|
||||
CONF_WAKEUP_PIN,
|
||||
PLATFORM_ESP32,
|
||||
)
|
||||
import esphome.final_validate as fv
|
||||
|
||||
from .const import INKPLATE_10_CUSTOM_WAVEFORMS, WAVEFORMS
|
||||
|
||||
DEPENDENCIES = ["i2c", "esp32"]
|
||||
AUTO_LOAD = ["psram"]
|
||||
|
||||
CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin"
|
||||
CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin"
|
||||
CONF_DISPLAY_DATA_2_PIN = "display_data_2_pin"
|
||||
CONF_DISPLAY_DATA_3_PIN = "display_data_3_pin"
|
||||
CONF_DISPLAY_DATA_4_PIN = "display_data_4_pin"
|
||||
CONF_DISPLAY_DATA_5_PIN = "display_data_5_pin"
|
||||
CONF_DISPLAY_DATA_6_PIN = "display_data_6_pin"
|
||||
CONF_DISPLAY_DATA_7_PIN = "display_data_7_pin"
|
||||
|
||||
CONF_CL_PIN = "cl_pin"
|
||||
CONF_CKV_PIN = "ckv_pin"
|
||||
CONF_GREYSCALE = "greyscale"
|
||||
CONF_GMOD_PIN = "gmod_pin"
|
||||
CONF_GPIO0_ENABLE_PIN = "gpio0_enable_pin"
|
||||
CONF_LE_PIN = "le_pin"
|
||||
CONF_PARTIAL_UPDATING = "partial_updating"
|
||||
CONF_POWERUP_PIN = "powerup_pin"
|
||||
CONF_SPH_PIN = "sph_pin"
|
||||
CONF_SPV_PIN = "spv_pin"
|
||||
CONF_VCOM_PIN = "vcom_pin"
|
||||
|
||||
inkplate_ns = cg.esphome_ns.namespace("inkplate")
|
||||
Inkplate = inkplate_ns.class_(
|
||||
"Inkplate",
|
||||
cg.PollingComponent,
|
||||
i2c.I2CDevice,
|
||||
display.Display,
|
||||
display.DisplayBuffer,
|
||||
)
|
||||
|
||||
InkplateModel = inkplate_ns.enum("InkplateModel")
|
||||
|
||||
MODELS = {
|
||||
"inkplate_6": InkplateModel.INKPLATE_6,
|
||||
"inkplate_10": InkplateModel.INKPLATE_10,
|
||||
"inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS,
|
||||
"inkplate_6_v2": InkplateModel.INKPLATE_6_V2,
|
||||
"inkplate_5": InkplateModel.INKPLATE_5,
|
||||
"inkplate_5_v2": InkplateModel.INKPLATE_5_V2,
|
||||
}
|
||||
|
||||
CONF_CUSTOM_WAVEFORM = "custom_waveform"
|
||||
|
||||
|
||||
def _validate_custom_waveform(config):
|
||||
if CONF_CUSTOM_WAVEFORM in config and config[CONF_MODEL] != "inkplate_10":
|
||||
raise cv.Invalid("Custom waveforms are only supported on the Inkplate 10")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Inkplate),
|
||||
cv.Optional(CONF_GREYSCALE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_CUSTOM_WAVEFORM): cv.All(
|
||||
cv.uint8_t, cv.Range(min=1, max=len(INKPLATE_10_CUSTOM_WAVEFORMS))
|
||||
),
|
||||
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean,
|
||||
cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t,
|
||||
cv.Optional(CONF_MODEL, default="inkplate_6"): cv.enum(
|
||||
MODELS, lower=True, space="_"
|
||||
),
|
||||
# Control pins
|
||||
cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_GPIO0_ENABLE_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_OE_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_POWERUP_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_SPH_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema,
|
||||
# Data pins
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_0_PIN, default=4
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_1_PIN, default=5
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_2_PIN, default=18
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_3_PIN, default=19
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_4_PIN, default=23
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_5_PIN, default=25
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_6_PIN, default=26
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_7_PIN, default=27
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5s"))
|
||||
.extend(i2c.i2c_device_schema(0x48)),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||
_validate_custom_waveform,
|
||||
)
|
||||
|
||||
|
||||
def _validate_cpu_frequency(config):
|
||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||
if esp32_config[CONF_CPU_FREQUENCY] != "240MHZ":
|
||||
raise cv.Invalid(
|
||||
"Inkplate requires 240MHz CPU frequency (set in esp32 component)"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _validate_cpu_frequency
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
await display.register_display(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
|
||||
cg.add(var.set_greyscale(config[CONF_GREYSCALE]))
|
||||
if transform := config.get(CONF_TRANSFORM):
|
||||
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
|
||||
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
|
||||
cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING]))
|
||||
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
||||
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
|
||||
if custom_waveform := config.get(CONF_CUSTOM_WAVEFORM):
|
||||
waveform = INKPLATE_10_CUSTOM_WAVEFORMS[custom_waveform - 1]
|
||||
waveform = [element for tupl in waveform for element in tupl]
|
||||
cg.add(var.set_waveform(waveform, True))
|
||||
else:
|
||||
waveform = WAVEFORMS[config[CONF_MODEL]]
|
||||
waveform = [element for tupl in waveform for element in tupl]
|
||||
cg.add(var.set_waveform(waveform, False))
|
||||
|
||||
ckv = await cg.gpio_pin_expression(config[CONF_CKV_PIN])
|
||||
cg.add(var.set_ckv_pin(ckv))
|
||||
|
||||
gmod = await cg.gpio_pin_expression(config[CONF_GMOD_PIN])
|
||||
cg.add(var.set_gmod_pin(gmod))
|
||||
|
||||
gpio0_enable = await cg.gpio_pin_expression(config[CONF_GPIO0_ENABLE_PIN])
|
||||
cg.add(var.set_gpio0_enable_pin(gpio0_enable))
|
||||
|
||||
oe = await cg.gpio_pin_expression(config[CONF_OE_PIN])
|
||||
cg.add(var.set_oe_pin(oe))
|
||||
|
||||
powerup = await cg.gpio_pin_expression(config[CONF_POWERUP_PIN])
|
||||
cg.add(var.set_powerup_pin(powerup))
|
||||
|
||||
sph = await cg.gpio_pin_expression(config[CONF_SPH_PIN])
|
||||
cg.add(var.set_sph_pin(sph))
|
||||
|
||||
spv = await cg.gpio_pin_expression(config[CONF_SPV_PIN])
|
||||
cg.add(var.set_spv_pin(spv))
|
||||
|
||||
vcom = await cg.gpio_pin_expression(config[CONF_VCOM_PIN])
|
||||
cg.add(var.set_vcom_pin(vcom))
|
||||
|
||||
wakeup = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN])
|
||||
cg.add(var.set_wakeup_pin(wakeup))
|
||||
|
||||
cl = await cg.gpio_pin_expression(config[CONF_CL_PIN])
|
||||
cg.add(var.set_cl_pin(cl))
|
||||
|
||||
le = await cg.gpio_pin_expression(config[CONF_LE_PIN])
|
||||
cg.add(var.set_le_pin(le))
|
||||
|
||||
display_data_0 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_0_PIN])
|
||||
cg.add(var.set_display_data_0_pin(display_data_0))
|
||||
|
||||
display_data_1 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_1_PIN])
|
||||
cg.add(var.set_display_data_1_pin(display_data_1))
|
||||
|
||||
display_data_2 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_2_PIN])
|
||||
cg.add(var.set_display_data_2_pin(display_data_2))
|
||||
|
||||
display_data_3 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_3_PIN])
|
||||
cg.add(var.set_display_data_3_pin(display_data_3))
|
||||
|
||||
display_data_4 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_4_PIN])
|
||||
cg.add(var.set_display_data_4_pin(display_data_4))
|
||||
|
||||
display_data_5 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_5_PIN])
|
||||
cg.add(var.set_display_data_5_pin(display_data_5))
|
||||
|
||||
display_data_6 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_6_PIN])
|
||||
cg.add(var.set_display_data_6_pin(display_data_6))
|
||||
|
||||
display_data_7 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN])
|
||||
cg.add(var.set_display_data_7_pin(display_data_7))
|
@@ -6,11 +6,11 @@
|
||||
#include <hal/gpio_hal.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace inkplate6 {
|
||||
namespace inkplate {
|
||||
|
||||
static const char *const TAG = "inkplate";
|
||||
|
||||
void Inkplate6::setup() {
|
||||
void Inkplate::setup() {
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
this->pin_lut_[i] = ((i & 0b00000011) << 4) | (((i & 0b00001100) >> 2) << 18) | (((i & 0b00010000) >> 4) << 23) |
|
||||
(((i & 0b11100000) >> 5) << 25);
|
||||
@@ -56,7 +56,7 @@ void Inkplate6::setup() {
|
||||
/**
|
||||
* Allocate buffers. May be called after setup to re-initialise if e.g. greyscale is changed.
|
||||
*/
|
||||
void Inkplate6::initialize_() {
|
||||
void Inkplate::initialize_() {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
RAMAllocator<uint32_t> allocator32;
|
||||
uint32_t buffer_size = this->get_buffer_length_();
|
||||
@@ -81,29 +81,25 @@ void Inkplate6::initialize_() {
|
||||
return;
|
||||
}
|
||||
if (this->greyscale_) {
|
||||
uint8_t glut_size = 9;
|
||||
|
||||
this->glut_ = allocator32.allocate(256 * glut_size);
|
||||
this->glut_ = allocator32.allocate(256 * GLUT_SIZE);
|
||||
if (this->glut_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate glut!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->glut2_ = allocator32.allocate(256 * glut_size);
|
||||
this->glut2_ = allocator32.allocate(256 * GLUT_SIZE);
|
||||
if (this->glut2_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate glut2!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto *const waveform3_bit = waveform3BitAll[this->model_];
|
||||
|
||||
for (int i = 0; i < glut_size; i++) {
|
||||
for (uint8_t i = 0; i < GLUT_SIZE; i++) {
|
||||
for (uint32_t j = 0; j < 256; j++) {
|
||||
uint8_t z = (waveform3_bit[j & 0x07][i] << 2) | (waveform3_bit[(j >> 4) & 0x07][i]);
|
||||
uint8_t z = (this->waveform_[j & 0x07][i] << 2) | (this->waveform_[(j >> 4) & 0x07][i]);
|
||||
this->glut_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
|
||||
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
|
||||
z = ((waveform3_bit[j & 0x07][i] << 2) | (waveform3_bit[(j >> 4) & 0x07][i])) << 4;
|
||||
z = ((this->waveform_[j & 0x07][i] << 2) | (this->waveform_[(j >> 4) & 0x07][i])) << 4;
|
||||
this->glut2_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
|
||||
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
|
||||
}
|
||||
@@ -130,9 +126,9 @@ void Inkplate6::initialize_() {
|
||||
memset(this->buffer_, 0, buffer_size);
|
||||
}
|
||||
|
||||
float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
float Inkplate::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
|
||||
size_t Inkplate6::get_buffer_length_() {
|
||||
size_t Inkplate::get_buffer_length_() {
|
||||
if (this->greyscale_) {
|
||||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u;
|
||||
} else {
|
||||
@@ -140,7 +136,7 @@ size_t Inkplate6::get_buffer_length_() {
|
||||
}
|
||||
}
|
||||
|
||||
void Inkplate6::update() {
|
||||
void Inkplate::update() {
|
||||
this->do_update_();
|
||||
|
||||
if (this->full_update_every_ > 0 && this->partial_updates_ >= this->full_update_every_) {
|
||||
@@ -150,7 +146,7 @@ void Inkplate6::update() {
|
||||
this->display();
|
||||
}
|
||||
|
||||
void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
void HOT Inkplate::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
|
||||
return;
|
||||
|
||||
@@ -171,18 +167,18 @@ void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
// uint8_t gs = (uint8_t)(px*7);
|
||||
|
||||
uint8_t gs = ((color.red * 2126 / 10000) + (color.green * 7152 / 10000) + (color.blue * 722 / 10000)) >> 5;
|
||||
this->buffer_[pos] = (pixelMaskGLUT[x_sub] & current) | (x_sub ? gs : gs << 4);
|
||||
this->buffer_[pos] = (PIXEL_MASK_GLUT[x_sub] & current) | (x_sub ? gs : gs << 4);
|
||||
|
||||
} else {
|
||||
int x1 = x / 8;
|
||||
int x_sub = x % 8;
|
||||
uint32_t pos = (x1 + y * (this->get_width_internal() / 8));
|
||||
uint8_t current = this->partial_buffer_[pos];
|
||||
this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]);
|
||||
this->partial_buffer_[pos] = (~PIXEL_MASK_LUT[x_sub] & current) | (color.is_on() ? 0 : PIXEL_MASK_LUT[x_sub]);
|
||||
}
|
||||
}
|
||||
|
||||
void Inkplate6::dump_config() {
|
||||
void Inkplate::dump_config() {
|
||||
LOG_DISPLAY("", "Inkplate", this);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Greyscale: %s\n"
|
||||
@@ -214,7 +210,7 @@ void Inkplate6::dump_config() {
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void Inkplate6::eink_off_() {
|
||||
void Inkplate::eink_off_() {
|
||||
ESP_LOGV(TAG, "Eink off called");
|
||||
if (!panel_on_)
|
||||
return;
|
||||
@@ -242,7 +238,7 @@ void Inkplate6::eink_off_() {
|
||||
pins_z_state_();
|
||||
}
|
||||
|
||||
void Inkplate6::eink_on_() {
|
||||
void Inkplate::eink_on_() {
|
||||
ESP_LOGV(TAG, "Eink on called");
|
||||
if (panel_on_)
|
||||
return;
|
||||
@@ -284,7 +280,7 @@ void Inkplate6::eink_on_() {
|
||||
this->oe_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
bool Inkplate6::read_power_status_() {
|
||||
bool Inkplate::read_power_status_() {
|
||||
uint8_t data;
|
||||
auto err = this->read_register(0x0F, &data, 1);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
@@ -293,7 +289,7 @@ bool Inkplate6::read_power_status_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Inkplate6::fill(Color color) {
|
||||
void Inkplate::fill(Color color) {
|
||||
ESP_LOGV(TAG, "Fill called");
|
||||
uint32_t start_time = millis();
|
||||
|
||||
@@ -308,7 +304,7 @@ void Inkplate6::fill(Color color) {
|
||||
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::display() {
|
||||
void Inkplate::display() {
|
||||
ESP_LOGV(TAG, "Display called");
|
||||
uint32_t start_time = millis();
|
||||
|
||||
@@ -324,7 +320,7 @@ void Inkplate6::display() {
|
||||
ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::display1b_() {
|
||||
void Inkplate::display1b_() {
|
||||
ESP_LOGV(TAG, "Display1b called");
|
||||
uint32_t start_time = millis();
|
||||
|
||||
@@ -334,32 +330,71 @@ void Inkplate6::display1b_() {
|
||||
uint8_t buffer_value;
|
||||
const uint8_t *buffer_ptr;
|
||||
eink_on_();
|
||||
if (this->model_ == INKPLATE_6_PLUS) {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 15);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 15);
|
||||
} else {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
clean_fast_(2, 1);
|
||||
uint8_t rep = 4;
|
||||
switch (this->model_) {
|
||||
case INKPLATE_10:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 10);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 10);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 10);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 10);
|
||||
rep = 5;
|
||||
break;
|
||||
case INKPLATE_6_PLUS:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 15);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 15);
|
||||
break;
|
||||
case INKPLATE_6:
|
||||
case INKPLATE_6_V2:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 18);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 18);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 18);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 18);
|
||||
clean_fast_(2, 1);
|
||||
if (this->model_ == INKPLATE_6_V2)
|
||||
rep = 5;
|
||||
break;
|
||||
case INKPLATE_5:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 14);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 14);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 14);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 14);
|
||||
clean_fast_(2, 1);
|
||||
rep = 5;
|
||||
break;
|
||||
case INKPLATE_5_V2:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 11);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 11);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 11);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 11);
|
||||
rep = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t clock = (1 << this->cl_pin_->get_pin());
|
||||
uint32_t data_mask = this->get_data_pin_mask_();
|
||||
ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time);
|
||||
|
||||
int rep = (this->model_ == INKPLATE_6_V2) ? 5 : 4;
|
||||
|
||||
for (int k = 0; k < rep; k++) {
|
||||
for (uint8_t k = 0; k < rep; k++) {
|
||||
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
|
||||
vscan_start_();
|
||||
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
|
||||
@@ -452,28 +487,75 @@ void Inkplate6::display1b_() {
|
||||
ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::display3b_() {
|
||||
void Inkplate::display3b_() {
|
||||
ESP_LOGV(TAG, "Display3b called");
|
||||
uint32_t start_time = millis();
|
||||
|
||||
eink_on_();
|
||||
if (this->model_ == INKPLATE_6_PLUS) {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 15);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 15);
|
||||
} else {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
clean_fast_(2, 1);
|
||||
|
||||
switch (this->model_) {
|
||||
case INKPLATE_10:
|
||||
if (this->custom_waveform_) {
|
||||
clean_fast_(1, 1);
|
||||
clean_fast_(0, 7);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 7);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 12);
|
||||
} else {
|
||||
clean_fast_(1, 1);
|
||||
clean_fast_(0, 10);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 10);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 10);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 10);
|
||||
}
|
||||
break;
|
||||
case INKPLATE_6_PLUS:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 15);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 15);
|
||||
break;
|
||||
case INKPLATE_6:
|
||||
case INKPLATE_6_V2:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 18);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 18);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 18);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 18);
|
||||
clean_fast_(2, 1);
|
||||
break;
|
||||
case INKPLATE_5:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 14);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 14);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 14);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 14);
|
||||
clean_fast_(2, 1);
|
||||
break;
|
||||
case INKPLATE_5_V2:
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 11);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 11);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 11);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 11);
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t clock = (1 << this->cl_pin_->get_pin());
|
||||
@@ -518,7 +600,7 @@ void Inkplate6::display3b_() {
|
||||
ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
bool Inkplate6::partial_update_() {
|
||||
bool Inkplate::partial_update_() {
|
||||
ESP_LOGV(TAG, "Partial update called");
|
||||
uint32_t start_time = millis();
|
||||
if (this->greyscale_)
|
||||
@@ -560,7 +642,7 @@ bool Inkplate6::partial_update_() {
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
}
|
||||
// New Inkplate6 panel doesn't need last clock
|
||||
// New Inkplate panel doesn't need last clock
|
||||
if (this->model_ != INKPLATE_6_V2) {
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
@@ -580,7 +662,7 @@ bool Inkplate6::partial_update_() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Inkplate6::vscan_start_() {
|
||||
void Inkplate::vscan_start_() {
|
||||
this->ckv_pin_->digital_write(true);
|
||||
delayMicroseconds(7);
|
||||
this->spv_pin_->digital_write(false);
|
||||
@@ -604,7 +686,7 @@ void Inkplate6::vscan_start_() {
|
||||
this->ckv_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void Inkplate6::hscan_start_(uint32_t d) {
|
||||
void Inkplate::hscan_start_(uint32_t d) {
|
||||
uint8_t clock = (1 << this->cl_pin_->get_pin());
|
||||
this->sph_pin_->digital_write(false);
|
||||
GPIO.out_w1ts = d | clock;
|
||||
@@ -613,14 +695,14 @@ void Inkplate6::hscan_start_(uint32_t d) {
|
||||
this->ckv_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void Inkplate6::vscan_end_() {
|
||||
void Inkplate::vscan_end_() {
|
||||
this->ckv_pin_->digital_write(false);
|
||||
this->le_pin_->digital_write(true);
|
||||
this->le_pin_->digital_write(false);
|
||||
delayMicroseconds(0);
|
||||
}
|
||||
|
||||
void Inkplate6::clean() {
|
||||
void Inkplate::clean() {
|
||||
ESP_LOGV(TAG, "Clean called");
|
||||
uint32_t start_time = millis();
|
||||
|
||||
@@ -634,7 +716,7 @@ void Inkplate6::clean() {
|
||||
ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
void Inkplate::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep);
|
||||
uint32_t start_time = millis();
|
||||
|
||||
@@ -666,7 +748,7 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = clock;
|
||||
}
|
||||
// New Inkplate6 panel doesn't need last clock
|
||||
// New Inkplate panel doesn't need last clock
|
||||
if (this->model_ != INKPLATE_6_V2) {
|
||||
GPIO.out_w1ts = send | clock;
|
||||
GPIO.out_w1tc = clock;
|
||||
@@ -679,7 +761,7 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::pins_z_state_() {
|
||||
void Inkplate::pins_z_state_() {
|
||||
this->cl_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->le_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->ckv_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
@@ -699,7 +781,7 @@ void Inkplate6::pins_z_state_() {
|
||||
this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
}
|
||||
|
||||
void Inkplate6::pins_as_outputs_() {
|
||||
void Inkplate::pins_as_outputs_() {
|
||||
this->cl_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->le_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
@@ -719,5 +801,5 @@ void Inkplate6::pins_as_outputs_() {
|
||||
this->display_data_7_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
}
|
||||
|
||||
} // namespace inkplate6
|
||||
} // namespace inkplate
|
||||
} // namespace esphome
|
@@ -5,8 +5,10 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace esphome {
|
||||
namespace inkplate6 {
|
||||
namespace inkplate {
|
||||
|
||||
enum InkplateModel : uint8_t {
|
||||
INKPLATE_6 = 0,
|
||||
@@ -17,79 +19,35 @@ enum InkplateModel : uint8_t {
|
||||
INKPLATE_5_V2 = 5,
|
||||
};
|
||||
|
||||
class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
||||
static constexpr uint8_t GLUT_SIZE = 9;
|
||||
static constexpr uint8_t GLUT_COUNT = 8;
|
||||
|
||||
static constexpr uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95,
|
||||
0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55};
|
||||
static constexpr uint8_t LUTW[16] = {0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA,
|
||||
0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA};
|
||||
static constexpr uint8_t LUTB[16] = {0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5,
|
||||
0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55};
|
||||
|
||||
static constexpr uint8_t PIXEL_MASK_LUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
|
||||
static constexpr uint8_t PIXEL_MASK_GLUT[2] = {0x0F, 0xF0};
|
||||
|
||||
class Inkplate : public display::DisplayBuffer, public i2c::I2CDevice {
|
||||
public:
|
||||
const uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95,
|
||||
0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55};
|
||||
const uint8_t LUTW[16] = {0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA,
|
||||
0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA};
|
||||
const uint8_t LUTB[16] = {0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5,
|
||||
0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55};
|
||||
|
||||
const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
|
||||
const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0};
|
||||
|
||||
const uint8_t waveform3BitAll[6][8][9] = {// INKPLATE_6
|
||||
{{0, 1, 1, 0, 0, 1, 1, 0, 0},
|
||||
{0, 1, 2, 1, 1, 2, 1, 0, 0},
|
||||
{1, 1, 1, 2, 2, 1, 0, 0, 0},
|
||||
{0, 0, 0, 1, 1, 1, 2, 0, 0},
|
||||
{2, 1, 1, 1, 2, 1, 2, 0, 0},
|
||||
{2, 2, 1, 1, 2, 1, 2, 0, 0},
|
||||
{1, 1, 1, 2, 1, 2, 2, 0, 0},
|
||||
{0, 0, 0, 0, 0, 0, 2, 0, 0}},
|
||||
// INKPLATE_10
|
||||
{{0, 0, 0, 0, 0, 0, 0, 1, 0},
|
||||
{0, 0, 0, 2, 2, 2, 1, 1, 0},
|
||||
{0, 0, 2, 1, 1, 2, 2, 1, 0},
|
||||
{0, 1, 2, 2, 1, 2, 2, 1, 0},
|
||||
{0, 0, 2, 1, 2, 2, 2, 1, 0},
|
||||
{0, 2, 2, 2, 2, 2, 2, 1, 0},
|
||||
{0, 0, 0, 0, 0, 2, 1, 2, 0},
|
||||
{0, 0, 0, 2, 2, 2, 2, 2, 0}},
|
||||
// INKPLATE_6_PLUS
|
||||
{{0, 0, 0, 0, 0, 2, 1, 1, 0},
|
||||
{0, 0, 2, 1, 1, 1, 2, 1, 0},
|
||||
{0, 2, 2, 2, 1, 1, 2, 1, 0},
|
||||
{0, 0, 2, 2, 2, 1, 2, 1, 0},
|
||||
{0, 0, 0, 0, 2, 2, 2, 1, 0},
|
||||
{0, 0, 2, 1, 2, 1, 1, 2, 0},
|
||||
{0, 0, 2, 2, 2, 1, 1, 2, 0},
|
||||
{0, 0, 0, 0, 2, 2, 2, 2, 0}},
|
||||
// INKPLATE_6_V2
|
||||
{{1, 0, 1, 0, 1, 1, 1, 0, 0},
|
||||
{0, 0, 0, 1, 1, 1, 1, 0, 0},
|
||||
{1, 1, 1, 1, 0, 2, 1, 0, 0},
|
||||
{1, 1, 1, 2, 2, 1, 1, 0, 0},
|
||||
{1, 1, 1, 1, 2, 2, 1, 0, 0},
|
||||
{0, 1, 1, 1, 2, 2, 1, 0, 0},
|
||||
{0, 0, 0, 0, 1, 1, 2, 0, 0},
|
||||
{0, 0, 0, 0, 0, 1, 2, 0, 0}},
|
||||
// INKPLATE_5
|
||||
{{0, 0, 1, 1, 0, 1, 1, 1, 0},
|
||||
{0, 1, 1, 1, 1, 2, 0, 1, 0},
|
||||
{1, 2, 2, 0, 2, 1, 1, 1, 0},
|
||||
{1, 1, 1, 2, 0, 1, 1, 2, 0},
|
||||
{0, 1, 1, 1, 2, 0, 1, 2, 0},
|
||||
{0, 0, 0, 1, 1, 2, 1, 2, 0},
|
||||
{1, 1, 1, 2, 0, 2, 1, 2, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0}},
|
||||
// INKPLATE_5_V2
|
||||
{{0, 0, 1, 1, 2, 1, 1, 1, 0},
|
||||
{1, 1, 2, 2, 1, 2, 1, 1, 0},
|
||||
{0, 1, 2, 2, 1, 1, 2, 1, 0},
|
||||
{0, 0, 1, 1, 1, 1, 1, 2, 0},
|
||||
{1, 2, 1, 2, 1, 1, 1, 2, 0},
|
||||
{0, 1, 1, 1, 2, 0, 1, 2, 0},
|
||||
{1, 1, 1, 2, 2, 2, 1, 2, 0},
|
||||
{0, 0, 0, 0, 0, 0, 0, 0, 0}}};
|
||||
|
||||
void set_greyscale(bool greyscale) {
|
||||
this->greyscale_ = greyscale;
|
||||
this->block_partial_ = true;
|
||||
if (this->is_ready())
|
||||
this->initialize_();
|
||||
}
|
||||
|
||||
void set_waveform(const std::array<uint8_t, GLUT_COUNT * GLUT_SIZE> &waveform, bool is_custom) {
|
||||
static_assert(sizeof(this->waveform_) == sizeof(uint8_t) * GLUT_COUNT * GLUT_SIZE,
|
||||
"waveform_ buffer size must match input waveform array size");
|
||||
memmove(this->waveform_, waveform.data(), sizeof(this->waveform_));
|
||||
this->custom_waveform_ = is_custom;
|
||||
}
|
||||
|
||||
void set_mirror_y(bool mirror_y) { this->mirror_y_ = mirror_y; }
|
||||
void set_mirror_x(bool mirror_x) { this->mirror_x_ = mirror_x; }
|
||||
|
||||
@@ -225,6 +183,8 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
||||
bool mirror_y_{false};
|
||||
bool mirror_x_{false};
|
||||
bool partial_updating_;
|
||||
bool custom_waveform_{false};
|
||||
uint8_t waveform_[GLUT_COUNT][GLUT_SIZE];
|
||||
|
||||
InkplateModel model_;
|
||||
|
||||
@@ -250,5 +210,5 @@ class Inkplate6 : public display::DisplayBuffer, public i2c::I2CDevice {
|
||||
GPIOPin *wakeup_pin_;
|
||||
};
|
||||
|
||||
} // namespace inkplate6
|
||||
} // namespace inkplate
|
||||
} // namespace esphome
|
@@ -1 +0,0 @@
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
|
@@ -1,214 +1,5 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import display, i2c
|
||||
from esphome.components.esp32 import CONF_CPU_FREQUENCY
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FULL_UPDATE_EVERY,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_MIRROR_X,
|
||||
CONF_MIRROR_Y,
|
||||
CONF_MODEL,
|
||||
CONF_OE_PIN,
|
||||
CONF_PAGES,
|
||||
CONF_TRANSFORM,
|
||||
CONF_WAKEUP_PIN,
|
||||
PLATFORM_ESP32,
|
||||
|
||||
CONFIG_SCHEMA = cv.invalid(
|
||||
"The inkplate6 display component has been renamed to inkplate."
|
||||
)
|
||||
import esphome.final_validate as fv
|
||||
|
||||
DEPENDENCIES = ["i2c", "esp32"]
|
||||
AUTO_LOAD = ["psram"]
|
||||
|
||||
CONF_DISPLAY_DATA_0_PIN = "display_data_0_pin"
|
||||
CONF_DISPLAY_DATA_1_PIN = "display_data_1_pin"
|
||||
CONF_DISPLAY_DATA_2_PIN = "display_data_2_pin"
|
||||
CONF_DISPLAY_DATA_3_PIN = "display_data_3_pin"
|
||||
CONF_DISPLAY_DATA_4_PIN = "display_data_4_pin"
|
||||
CONF_DISPLAY_DATA_5_PIN = "display_data_5_pin"
|
||||
CONF_DISPLAY_DATA_6_PIN = "display_data_6_pin"
|
||||
CONF_DISPLAY_DATA_7_PIN = "display_data_7_pin"
|
||||
|
||||
CONF_CL_PIN = "cl_pin"
|
||||
CONF_CKV_PIN = "ckv_pin"
|
||||
CONF_GREYSCALE = "greyscale"
|
||||
CONF_GMOD_PIN = "gmod_pin"
|
||||
CONF_GPIO0_ENABLE_PIN = "gpio0_enable_pin"
|
||||
CONF_LE_PIN = "le_pin"
|
||||
CONF_PARTIAL_UPDATING = "partial_updating"
|
||||
CONF_POWERUP_PIN = "powerup_pin"
|
||||
CONF_SPH_PIN = "sph_pin"
|
||||
CONF_SPV_PIN = "spv_pin"
|
||||
CONF_VCOM_PIN = "vcom_pin"
|
||||
|
||||
inkplate6_ns = cg.esphome_ns.namespace("inkplate6")
|
||||
Inkplate6 = inkplate6_ns.class_(
|
||||
"Inkplate6",
|
||||
cg.PollingComponent,
|
||||
i2c.I2CDevice,
|
||||
display.Display,
|
||||
display.DisplayBuffer,
|
||||
)
|
||||
|
||||
InkplateModel = inkplate6_ns.enum("InkplateModel")
|
||||
|
||||
MODELS = {
|
||||
"inkplate_6": InkplateModel.INKPLATE_6,
|
||||
"inkplate_10": InkplateModel.INKPLATE_10,
|
||||
"inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS,
|
||||
"inkplate_6_v2": InkplateModel.INKPLATE_6_V2,
|
||||
"inkplate_5": InkplateModel.INKPLATE_5,
|
||||
"inkplate_5_v2": InkplateModel.INKPLATE_5_V2,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
display.FULL_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Inkplate6),
|
||||
cv.Optional(CONF_GREYSCALE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PARTIAL_UPDATING, default=True): cv.boolean,
|
||||
cv.Optional(CONF_FULL_UPDATE_EVERY, default=10): cv.uint32_t,
|
||||
cv.Optional(CONF_MODEL, default="inkplate_6"): cv.enum(
|
||||
MODELS, lower=True, space="_"
|
||||
),
|
||||
# Control pins
|
||||
cv.Required(CONF_CKV_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_GMOD_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_GPIO0_ENABLE_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_OE_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_POWERUP_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_SPH_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_SPV_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_VCOM_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Required(CONF_WAKEUP_PIN): pins.gpio_output_pin_schema,
|
||||
cv.Optional(CONF_CL_PIN, default=0): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(CONF_LE_PIN, default=2): pins.internal_gpio_output_pin_schema,
|
||||
# Data pins
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_0_PIN, default=4
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_1_PIN, default=5
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_2_PIN, default=18
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_3_PIN, default=19
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_4_PIN, default=23
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_5_PIN, default=25
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_6_PIN, default=26
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
cv.Optional(
|
||||
CONF_DISPLAY_DATA_7_PIN, default=27
|
||||
): pins.internal_gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5s"))
|
||||
.extend(i2c.i2c_device_schema(0x48)),
|
||||
cv.has_at_most_one_key(CONF_PAGES, CONF_LAMBDA),
|
||||
)
|
||||
|
||||
|
||||
def _validate_cpu_frequency(config):
|
||||
esp32_config = fv.full_config.get()[PLATFORM_ESP32]
|
||||
if esp32_config[CONF_CPU_FREQUENCY] != "240MHZ":
|
||||
raise cv.Invalid(
|
||||
"Inkplate requires 240MHz CPU frequency (set in esp32 component)"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _validate_cpu_frequency
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
await display.register_display(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
lambda_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [(display.DisplayRef, "it")], return_type=cg.void
|
||||
)
|
||||
cg.add(var.set_writer(lambda_))
|
||||
|
||||
cg.add(var.set_greyscale(config[CONF_GREYSCALE]))
|
||||
if transform := config.get(CONF_TRANSFORM):
|
||||
cg.add(var.set_mirror_x(transform[CONF_MIRROR_X]))
|
||||
cg.add(var.set_mirror_y(transform[CONF_MIRROR_Y]))
|
||||
cg.add(var.set_partial_updating(config[CONF_PARTIAL_UPDATING]))
|
||||
cg.add(var.set_full_update_every(config[CONF_FULL_UPDATE_EVERY]))
|
||||
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
|
||||
ckv = await cg.gpio_pin_expression(config[CONF_CKV_PIN])
|
||||
cg.add(var.set_ckv_pin(ckv))
|
||||
|
||||
gmod = await cg.gpio_pin_expression(config[CONF_GMOD_PIN])
|
||||
cg.add(var.set_gmod_pin(gmod))
|
||||
|
||||
gpio0_enable = await cg.gpio_pin_expression(config[CONF_GPIO0_ENABLE_PIN])
|
||||
cg.add(var.set_gpio0_enable_pin(gpio0_enable))
|
||||
|
||||
oe = await cg.gpio_pin_expression(config[CONF_OE_PIN])
|
||||
cg.add(var.set_oe_pin(oe))
|
||||
|
||||
powerup = await cg.gpio_pin_expression(config[CONF_POWERUP_PIN])
|
||||
cg.add(var.set_powerup_pin(powerup))
|
||||
|
||||
sph = await cg.gpio_pin_expression(config[CONF_SPH_PIN])
|
||||
cg.add(var.set_sph_pin(sph))
|
||||
|
||||
spv = await cg.gpio_pin_expression(config[CONF_SPV_PIN])
|
||||
cg.add(var.set_spv_pin(spv))
|
||||
|
||||
vcom = await cg.gpio_pin_expression(config[CONF_VCOM_PIN])
|
||||
cg.add(var.set_vcom_pin(vcom))
|
||||
|
||||
wakeup = await cg.gpio_pin_expression(config[CONF_WAKEUP_PIN])
|
||||
cg.add(var.set_wakeup_pin(wakeup))
|
||||
|
||||
cl = await cg.gpio_pin_expression(config[CONF_CL_PIN])
|
||||
cg.add(var.set_cl_pin(cl))
|
||||
|
||||
le = await cg.gpio_pin_expression(config[CONF_LE_PIN])
|
||||
cg.add(var.set_le_pin(le))
|
||||
|
||||
display_data_0 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_0_PIN])
|
||||
cg.add(var.set_display_data_0_pin(display_data_0))
|
||||
|
||||
display_data_1 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_1_PIN])
|
||||
cg.add(var.set_display_data_1_pin(display_data_1))
|
||||
|
||||
display_data_2 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_2_PIN])
|
||||
cg.add(var.set_display_data_2_pin(display_data_2))
|
||||
|
||||
display_data_3 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_3_PIN])
|
||||
cg.add(var.set_display_data_3_pin(display_data_3))
|
||||
|
||||
display_data_4 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_4_PIN])
|
||||
cg.add(var.set_display_data_4_pin(display_data_4))
|
||||
|
||||
display_data_5 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_5_PIN])
|
||||
cg.add(var.set_display_data_5_pin(display_data_5))
|
||||
|
||||
display_data_6 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_6_PIN])
|
||||
cg.add(var.set_display_data_6_pin(display_data_6))
|
||||
|
||||
display_data_7 = await cg.gpio_pin_expression(config[CONF_DISPLAY_DATA_7_PIN])
|
||||
cg.add(var.set_display_data_7_pin(display_data_7))
|
||||
|
@@ -196,8 +196,8 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"remote_receiver_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"remote_receiver_libretiny.cpp": {
|
||||
"remote_receiver.cpp": {
|
||||
PlatformFramework.ESP8266_ARDUINO,
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
|
@@ -3,12 +3,12 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#if defined(USE_LIBRETINY) || defined(USE_ESP8266)
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_receiver {
|
||||
|
||||
static const char *const TAG = "remote_receiver.esp8266";
|
||||
static const char *const TAG = "remote_receiver";
|
||||
|
||||
void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) {
|
||||
const uint32_t now = micros();
|
@@ -1,125 +0,0 @@
|
||||
#include "remote_receiver.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_receiver {
|
||||
|
||||
static const char *const TAG = "remote_receiver.libretiny";
|
||||
|
||||
void IRAM_ATTR HOT RemoteReceiverComponentStore::gpio_intr(RemoteReceiverComponentStore *arg) {
|
||||
const uint32_t now = micros();
|
||||
// If the lhs is 1 (rising edge) we should write to an uneven index and vice versa
|
||||
const uint32_t next = (arg->buffer_write_at + 1) % arg->buffer_size;
|
||||
const bool level = arg->pin.digital_read();
|
||||
if (level != next % 2)
|
||||
return;
|
||||
|
||||
// If next is buffer_read, we have hit an overflow
|
||||
if (next == arg->buffer_read_at)
|
||||
return;
|
||||
|
||||
const uint32_t last_change = arg->buffer[arg->buffer_write_at];
|
||||
const uint32_t time_since_change = now - last_change;
|
||||
if (time_since_change <= arg->filter_us)
|
||||
return;
|
||||
|
||||
arg->buffer[arg->buffer_write_at = next] = now;
|
||||
}
|
||||
|
||||
void RemoteReceiverComponent::setup() {
|
||||
this->pin_->setup();
|
||||
auto &s = this->store_;
|
||||
s.filter_us = this->filter_us_;
|
||||
s.pin = this->pin_->to_isr();
|
||||
s.buffer_size = this->buffer_size_;
|
||||
|
||||
this->high_freq_.start();
|
||||
if (s.buffer_size % 2 != 0) {
|
||||
// Make sure divisible by two. This way, we know that every 0bxxx0 index is a space and every 0bxxx1 index is a mark
|
||||
s.buffer_size++;
|
||||
}
|
||||
|
||||
s.buffer = new uint32_t[s.buffer_size];
|
||||
void *buf = (void *) s.buffer;
|
||||
memset(buf, 0, s.buffer_size * sizeof(uint32_t));
|
||||
|
||||
// First index is a space.
|
||||
if (this->pin_->digital_read()) {
|
||||
s.buffer_write_at = s.buffer_read_at = 1;
|
||||
} else {
|
||||
s.buffer_write_at = s.buffer_read_at = 0;
|
||||
}
|
||||
this->pin_->attach_interrupt(RemoteReceiverComponentStore::gpio_intr, &this->store_, gpio::INTERRUPT_ANY_EDGE);
|
||||
}
|
||||
void RemoteReceiverComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Remote Receiver:");
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
if (this->pin_->digital_read()) {
|
||||
ESP_LOGW(TAG, "Remote Receiver Signal starts with a HIGH value. Usually this means you have to "
|
||||
"invert the signal using 'inverted: True' in the pin schema!");
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Buffer Size: %u\n"
|
||||
" Tolerance: %u%s\n"
|
||||
" Filter out pulses shorter than: %u us\n"
|
||||
" Signal is done after %u us of no changes",
|
||||
this->buffer_size_, this->tolerance_,
|
||||
(this->tolerance_mode_ == remote_base::TOLERANCE_MODE_TIME) ? " us" : "%", this->filter_us_,
|
||||
this->idle_us_);
|
||||
}
|
||||
|
||||
void RemoteReceiverComponent::loop() {
|
||||
auto &s = this->store_;
|
||||
|
||||
// copy write at to local variables, as it's volatile
|
||||
const uint32_t write_at = s.buffer_write_at;
|
||||
const uint32_t dist = (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size;
|
||||
// signals must at least one rising and one leading edge
|
||||
if (dist <= 1)
|
||||
return;
|
||||
const uint32_t now = micros();
|
||||
if (now - s.buffer[write_at] < this->idle_us_) {
|
||||
// The last change was fewer than the configured idle time ago.
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "read_at=%u write_at=%u dist=%u now=%u end=%u", s.buffer_read_at, write_at, dist, now,
|
||||
s.buffer[write_at]);
|
||||
|
||||
// Skip first value, it's from the previous idle level
|
||||
s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size;
|
||||
uint32_t prev = s.buffer_read_at;
|
||||
s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size;
|
||||
const uint32_t reserve_size = 1 + (s.buffer_size + write_at - s.buffer_read_at) % s.buffer_size;
|
||||
this->temp_.clear();
|
||||
this->temp_.reserve(reserve_size);
|
||||
int32_t multiplier = s.buffer_read_at % 2 == 0 ? 1 : -1;
|
||||
|
||||
for (uint32_t i = 0; prev != write_at; i++) {
|
||||
int32_t delta = s.buffer[s.buffer_read_at] - s.buffer[prev];
|
||||
if (uint32_t(delta) >= this->idle_us_) {
|
||||
// already found a space longer than idle. There must have been two pulses
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, " i=%u buffer[%u]=%u - buffer[%u]=%u -> %d", i, s.buffer_read_at, s.buffer[s.buffer_read_at], prev,
|
||||
s.buffer[prev], multiplier * delta);
|
||||
this->temp_.push_back(multiplier * delta);
|
||||
prev = s.buffer_read_at;
|
||||
s.buffer_read_at = (s.buffer_read_at + 1) % s.buffer_size;
|
||||
multiplier *= -1;
|
||||
}
|
||||
s.buffer_read_at = (s.buffer_size + s.buffer_read_at - 1) % s.buffer_size;
|
||||
this->temp_.push_back(this->idle_us_ * multiplier);
|
||||
|
||||
this->call_listeners_dumpers_();
|
||||
}
|
||||
|
||||
} // namespace remote_receiver
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -131,8 +131,8 @@ FILTER_SOURCE_FILES = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
PlatformFramework.ESP32_IDF,
|
||||
},
|
||||
"remote_transmitter_esp8266.cpp": {PlatformFramework.ESP8266_ARDUINO},
|
||||
"remote_transmitter_libretiny.cpp": {
|
||||
"remote_transmitter.cpp": {
|
||||
PlatformFramework.ESP8266_ARDUINO,
|
||||
PlatformFramework.BK72XX_ARDUINO,
|
||||
PlatformFramework.RTL87XX_ARDUINO,
|
||||
PlatformFramework.LN882X_ARDUINO,
|
||||
|
@@ -2,10 +2,107 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#if defined(USE_LIBRETINY) || defined(USE_ESP8266)
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_transmitter {
|
||||
|
||||
static const char *const TAG = "remote_transmitter";
|
||||
|
||||
void RemoteTransmitterComponent::setup() {
|
||||
this->pin_->setup();
|
||||
this->pin_->digital_write(false);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Remote Transmitter:\n"
|
||||
" Carrier Duty: %u%%",
|
||||
this->carrier_duty_percent_);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period,
|
||||
uint32_t *off_time_period) {
|
||||
if (carrier_frequency == 0) {
|
||||
*on_time_period = 0;
|
||||
*off_time_period = 0;
|
||||
return;
|
||||
}
|
||||
uint32_t period = (1000000UL + carrier_frequency / 2) / carrier_frequency; // round(1000000/freq)
|
||||
period = std::max(uint32_t(1), period);
|
||||
*on_time_period = (period * this->carrier_duty_percent_) / 100;
|
||||
*off_time_period = period - *on_time_period;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::await_target_time_() {
|
||||
const uint32_t current_time = micros();
|
||||
if (this->target_time_ == 0) {
|
||||
this->target_time_ = current_time;
|
||||
} else if ((int32_t) (this->target_time_ - current_time) > 0) {
|
||||
delayMicroseconds(this->target_time_ - current_time);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
|
||||
const uint32_t target = this->target_time_ + usec;
|
||||
if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
|
||||
while (true) { // Modulate with carrier frequency
|
||||
this->target_time_ += on_time;
|
||||
if ((int32_t) (this->target_time_ - target) >= 0)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
this->target_time_ += off_time;
|
||||
if ((int32_t) (this->target_time_ - target) >= 0)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
this->target_time_ = target;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::space_(uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
this->target_time_ += usec;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::digital_write(bool value) { this->pin_->digital_write(value); }
|
||||
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
ESP_LOGD(TAG, "Sending remote code");
|
||||
uint32_t on_time, off_time;
|
||||
this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
|
||||
this->target_time_ = 0;
|
||||
this->transmit_trigger_->trigger();
|
||||
for (uint32_t i = 0; i < send_times; i++) {
|
||||
InterruptLock lock;
|
||||
for (int32_t item : this->temp_.get_data()) {
|
||||
if (item > 0) {
|
||||
const auto length = uint32_t(item);
|
||||
this->mark_(on_time, off_time, length);
|
||||
} else {
|
||||
const auto length = uint32_t(-item);
|
||||
this->space_(length);
|
||||
}
|
||||
App.feed_wdt();
|
||||
}
|
||||
this->await_target_time_(); // wait for duration of last pulse
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (i + 1 < send_times)
|
||||
this->target_time_ += send_wait;
|
||||
}
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
|
||||
} // namespace remote_transmitter
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
|
@@ -1,107 +0,0 @@
|
||||
#include "remote_transmitter.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_transmitter {
|
||||
|
||||
static const char *const TAG = "remote_transmitter";
|
||||
|
||||
void RemoteTransmitterComponent::setup() {
|
||||
this->pin_->setup();
|
||||
this->pin_->digital_write(false);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Remote Transmitter:\n"
|
||||
" Carrier Duty: %u%%",
|
||||
this->carrier_duty_percent_);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period,
|
||||
uint32_t *off_time_period) {
|
||||
if (carrier_frequency == 0) {
|
||||
*on_time_period = 0;
|
||||
*off_time_period = 0;
|
||||
return;
|
||||
}
|
||||
uint32_t period = (1000000UL + carrier_frequency / 2) / carrier_frequency; // round(1000000/freq)
|
||||
period = std::max(uint32_t(1), period);
|
||||
*on_time_period = (period * this->carrier_duty_percent_) / 100;
|
||||
*off_time_period = period - *on_time_period;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::await_target_time_() {
|
||||
const uint32_t current_time = micros();
|
||||
if (this->target_time_ == 0) {
|
||||
this->target_time_ = current_time;
|
||||
} else if ((int32_t) (this->target_time_ - current_time) > 0) {
|
||||
delayMicroseconds(this->target_time_ - current_time);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
|
||||
const uint32_t target = this->target_time_ + usec;
|
||||
if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
|
||||
while (true) { // Modulate with carrier frequency
|
||||
this->target_time_ += on_time;
|
||||
if ((int32_t) (this->target_time_ - target) >= 0)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
this->target_time_ += off_time;
|
||||
if ((int32_t) (this->target_time_ - target) >= 0)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
this->target_time_ = target;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::space_(uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
this->target_time_ += usec;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::digital_write(bool value) { this->pin_->digital_write(value); }
|
||||
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
ESP_LOGD(TAG, "Sending remote code");
|
||||
uint32_t on_time, off_time;
|
||||
this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
|
||||
this->target_time_ = 0;
|
||||
this->transmit_trigger_->trigger();
|
||||
for (uint32_t i = 0; i < send_times; i++) {
|
||||
for (int32_t item : this->temp_.get_data()) {
|
||||
if (item > 0) {
|
||||
const auto length = uint32_t(item);
|
||||
this->mark_(on_time, off_time, length);
|
||||
} else {
|
||||
const auto length = uint32_t(-item);
|
||||
this->space_(length);
|
||||
}
|
||||
App.feed_wdt();
|
||||
}
|
||||
this->await_target_time_(); // wait for duration of last pulse
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (i + 1 < send_times)
|
||||
this->target_time_ += send_wait;
|
||||
}
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
|
||||
} // namespace remote_transmitter
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -1,110 +0,0 @@
|
||||
#include "remote_transmitter.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_transmitter {
|
||||
|
||||
static const char *const TAG = "remote_transmitter";
|
||||
|
||||
void RemoteTransmitterComponent::setup() {
|
||||
this->pin_->setup();
|
||||
this->pin_->digital_write(false);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Remote Transmitter:\n"
|
||||
" Carrier Duty: %u%%",
|
||||
this->carrier_duty_percent_);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::calculate_on_off_time_(uint32_t carrier_frequency, uint32_t *on_time_period,
|
||||
uint32_t *off_time_period) {
|
||||
if (carrier_frequency == 0) {
|
||||
*on_time_period = 0;
|
||||
*off_time_period = 0;
|
||||
return;
|
||||
}
|
||||
uint32_t period = (1000000UL + carrier_frequency / 2) / carrier_frequency; // round(1000000/freq)
|
||||
period = std::max(uint32_t(1), period);
|
||||
*on_time_period = (period * this->carrier_duty_percent_) / 100;
|
||||
*off_time_period = period - *on_time_period;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::await_target_time_() {
|
||||
const uint32_t current_time = micros();
|
||||
if (this->target_time_ == 0) {
|
||||
this->target_time_ = current_time;
|
||||
} else {
|
||||
while ((int32_t) (this->target_time_ - micros()) > 0) {
|
||||
// busy loop that ensures micros is constantly called
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::mark_(uint32_t on_time, uint32_t off_time, uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
|
||||
const uint32_t target = this->target_time_ + usec;
|
||||
if (this->carrier_duty_percent_ < 100 && (on_time > 0 || off_time > 0)) {
|
||||
while (true) { // Modulate with carrier frequency
|
||||
this->target_time_ += on_time;
|
||||
if ((int32_t) (this->target_time_ - target) >= 0)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
this->target_time_ += off_time;
|
||||
if ((int32_t) (this->target_time_ - target) >= 0)
|
||||
break;
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(true);
|
||||
}
|
||||
}
|
||||
this->target_time_ = target;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::space_(uint32_t usec) {
|
||||
this->await_target_time_();
|
||||
this->pin_->digital_write(false);
|
||||
this->target_time_ += usec;
|
||||
}
|
||||
|
||||
void RemoteTransmitterComponent::digital_write(bool value) { this->pin_->digital_write(value); }
|
||||
|
||||
void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t send_wait) {
|
||||
ESP_LOGD(TAG, "Sending remote code");
|
||||
uint32_t on_time, off_time;
|
||||
this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
|
||||
this->target_time_ = 0;
|
||||
this->transmit_trigger_->trigger();
|
||||
for (uint32_t i = 0; i < send_times; i++) {
|
||||
InterruptLock lock;
|
||||
for (int32_t item : this->temp_.get_data()) {
|
||||
if (item > 0) {
|
||||
const auto length = uint32_t(item);
|
||||
this->mark_(on_time, off_time, length);
|
||||
} else {
|
||||
const auto length = uint32_t(-item);
|
||||
this->space_(length);
|
||||
}
|
||||
App.feed_wdt();
|
||||
}
|
||||
this->await_target_time_(); // wait for duration of last pulse
|
||||
this->pin_->digital_write(false);
|
||||
|
||||
if (i + 1 < send_times)
|
||||
this->target_time_ += send_wait;
|
||||
}
|
||||
this->complete_trigger_->trigger();
|
||||
}
|
||||
|
||||
} // namespace remote_transmitter
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
@@ -32,6 +32,7 @@ from esphome.const import (
|
||||
CONF_FAN_WITH_COOLING,
|
||||
CONF_FAN_WITH_HEATING,
|
||||
CONF_HEAT_ACTION,
|
||||
CONF_HEAT_COOL_MODE,
|
||||
CONF_HEAT_DEADBAND,
|
||||
CONF_HEAT_MODE,
|
||||
CONF_HEAT_OVERRUN,
|
||||
@@ -150,7 +151,7 @@ def generate_comparable_preset(config, name):
|
||||
def validate_thermostat(config):
|
||||
# verify corresponding action(s) exist(s) for any defined climate mode or action
|
||||
requirements = {
|
||||
CONF_AUTO_MODE: [
|
||||
CONF_HEAT_COOL_MODE: [
|
||||
CONF_COOL_ACTION,
|
||||
CONF_HEAT_ACTION,
|
||||
CONF_MIN_COOLING_OFF_TIME,
|
||||
@@ -540,6 +541,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_FAN_ONLY_MODE): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_HEAT_COOL_MODE): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_HEAT_MODE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_OFF_MODE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_FAN_MODE_ON_ACTION): automation.validate_automation(
|
||||
@@ -644,7 +648,6 @@ async def to_code(config):
|
||||
var = await climate.new_climate(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
heat_cool_mode_available = CONF_HEAT_ACTION in config and CONF_COOL_ACTION in config
|
||||
two_points_available = CONF_HEAT_ACTION in config and (
|
||||
CONF_COOL_ACTION in config
|
||||
or (config[CONF_FAN_ONLY_COOLING] and CONF_FAN_ONLY_ACTION in config)
|
||||
@@ -739,11 +742,6 @@ async def to_code(config):
|
||||
var.get_idle_action_trigger(), [], config[CONF_IDLE_ACTION]
|
||||
)
|
||||
|
||||
if heat_cool_mode_available is True:
|
||||
cg.add(var.set_supports_heat_cool(True))
|
||||
else:
|
||||
cg.add(var.set_supports_heat_cool(False))
|
||||
|
||||
if CONF_COOL_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_cool_action_trigger(), [], config[CONF_COOL_ACTION]
|
||||
@@ -780,6 +778,7 @@ async def to_code(config):
|
||||
await automation.build_automation(
|
||||
var.get_auto_mode_trigger(), [], config[CONF_AUTO_MODE]
|
||||
)
|
||||
cg.add(var.set_supports_auto(True))
|
||||
if CONF_COOL_MODE in config:
|
||||
await automation.build_automation(
|
||||
var.get_cool_mode_trigger(), [], config[CONF_COOL_MODE]
|
||||
@@ -800,6 +799,11 @@ async def to_code(config):
|
||||
var.get_heat_mode_trigger(), [], config[CONF_HEAT_MODE]
|
||||
)
|
||||
cg.add(var.set_supports_heat(True))
|
||||
if CONF_HEAT_COOL_MODE in config:
|
||||
await automation.build_automation(
|
||||
var.get_heat_cool_mode_trigger(), [], config[CONF_HEAT_COOL_MODE]
|
||||
)
|
||||
cg.add(var.set_supports_heat_cool(True))
|
||||
if CONF_OFF_MODE in config:
|
||||
await automation.build_automation(
|
||||
var.get_off_mode_trigger(), [], config[CONF_OFF_MODE]
|
||||
|
@@ -1,4 +1,6 @@
|
||||
#include "thermostat_climate.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -9,11 +11,11 @@ static const char *const TAG = "thermostat.climate";
|
||||
void ThermostatClimate::setup() {
|
||||
if (this->use_startup_delay_) {
|
||||
// start timers so that no actions are called for a moment
|
||||
this->start_timer_(thermostat::TIMER_COOLING_OFF);
|
||||
this->start_timer_(thermostat::TIMER_FANNING_OFF);
|
||||
this->start_timer_(thermostat::TIMER_HEATING_OFF);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_OFF);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_OFF);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_OFF);
|
||||
if (this->supports_fan_only_action_uses_fan_mode_timer_)
|
||||
this->start_timer_(thermostat::TIMER_FAN_MODE);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE);
|
||||
}
|
||||
// add a callback so that whenever the sensor state changes we can take action
|
||||
this->sensor_->add_on_state_callback([this](float state) {
|
||||
@@ -64,7 +66,7 @@ void ThermostatClimate::setup() {
|
||||
|
||||
void ThermostatClimate::loop() {
|
||||
for (auto &timer : this->timer_) {
|
||||
if (timer.active && (timer.started + timer.time < millis())) {
|
||||
if (timer.active && (timer.started + timer.time < App.get_loop_component_start_time())) {
|
||||
timer.active = false;
|
||||
timer.func();
|
||||
}
|
||||
@@ -127,26 +129,35 @@ bool ThermostatClimate::hysteresis_valid() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ThermostatClimate::limit_setpoints_for_heat_cool() {
|
||||
return this->mode == climate::CLIMATE_MODE_HEAT_COOL ||
|
||||
(this->mode == climate::CLIMATE_MODE_AUTO && this->supports_heat_cool_);
|
||||
}
|
||||
|
||||
void ThermostatClimate::validate_target_temperature() {
|
||||
if (std::isnan(this->target_temperature)) {
|
||||
// default to the midpoint between visual min and max
|
||||
this->target_temperature =
|
||||
((this->get_traits().get_visual_max_temperature() - this->get_traits().get_visual_min_temperature()) / 2) +
|
||||
this->get_traits().get_visual_min_temperature();
|
||||
} else {
|
||||
// target_temperature must be between the visual minimum and the visual maximum
|
||||
if (this->target_temperature < this->get_traits().get_visual_min_temperature())
|
||||
this->target_temperature = this->get_traits().get_visual_min_temperature();
|
||||
if (this->target_temperature > this->get_traits().get_visual_max_temperature())
|
||||
this->target_temperature = this->get_traits().get_visual_max_temperature();
|
||||
this->target_temperature = clamp(this->target_temperature, this->get_traits().get_visual_min_temperature(),
|
||||
this->get_traits().get_visual_max_temperature());
|
||||
}
|
||||
}
|
||||
|
||||
void ThermostatClimate::validate_target_temperatures() {
|
||||
if (this->supports_two_points_) {
|
||||
void ThermostatClimate::validate_target_temperatures(const bool pin_target_temperature_high) {
|
||||
if (!this->supports_two_points_) {
|
||||
this->validate_target_temperature();
|
||||
} else if (pin_target_temperature_high) {
|
||||
// if target_temperature_high is set less than target_temperature_low, move down target_temperature_low
|
||||
this->validate_target_temperature_low();
|
||||
this->validate_target_temperature_high();
|
||||
} else {
|
||||
this->validate_target_temperature();
|
||||
// if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high
|
||||
this->validate_target_temperature_high();
|
||||
this->validate_target_temperature_low();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,18 +165,13 @@ void ThermostatClimate::validate_target_temperature_low() {
|
||||
if (std::isnan(this->target_temperature_low)) {
|
||||
this->target_temperature_low = this->get_traits().get_visual_min_temperature();
|
||||
} else {
|
||||
// target_temperature_low must not be lower than the visual minimum
|
||||
if (this->target_temperature_low < this->get_traits().get_visual_min_temperature())
|
||||
this->target_temperature_low = this->get_traits().get_visual_min_temperature();
|
||||
// target_temperature_low must not be greater than the visual maximum minus set_point_minimum_differential_
|
||||
if (this->target_temperature_low >
|
||||
this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_) {
|
||||
this->target_temperature_low =
|
||||
this->get_traits().get_visual_max_temperature() - this->set_point_minimum_differential_;
|
||||
}
|
||||
// if target_temperature_low is set greater than target_temperature_high, move up target_temperature_high
|
||||
if (this->target_temperature_low > this->target_temperature_high - this->set_point_minimum_differential_)
|
||||
this->target_temperature_high = this->target_temperature_low + this->set_point_minimum_differential_;
|
||||
float target_temperature_low_upper_limit =
|
||||
this->limit_setpoints_for_heat_cool()
|
||||
? clamp(this->target_temperature_high - this->set_point_minimum_differential_,
|
||||
this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
|
||||
: this->get_traits().get_visual_max_temperature();
|
||||
this->target_temperature_low = clamp(this->target_temperature_low, this->get_traits().get_visual_min_temperature(),
|
||||
target_temperature_low_upper_limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,62 +179,64 @@ void ThermostatClimate::validate_target_temperature_high() {
|
||||
if (std::isnan(this->target_temperature_high)) {
|
||||
this->target_temperature_high = this->get_traits().get_visual_max_temperature();
|
||||
} else {
|
||||
// target_temperature_high must not be lower than the visual maximum
|
||||
if (this->target_temperature_high > this->get_traits().get_visual_max_temperature())
|
||||
this->target_temperature_high = this->get_traits().get_visual_max_temperature();
|
||||
// target_temperature_high must not be lower than the visual minimum plus set_point_minimum_differential_
|
||||
if (this->target_temperature_high <
|
||||
this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_) {
|
||||
this->target_temperature_high =
|
||||
this->get_traits().get_visual_min_temperature() + this->set_point_minimum_differential_;
|
||||
}
|
||||
// if target_temperature_high is set less than target_temperature_low, move down target_temperature_low
|
||||
if (this->target_temperature_high < this->target_temperature_low + this->set_point_minimum_differential_)
|
||||
this->target_temperature_low = this->target_temperature_high - this->set_point_minimum_differential_;
|
||||
float target_temperature_high_lower_limit =
|
||||
this->limit_setpoints_for_heat_cool()
|
||||
? clamp(this->target_temperature_low + this->set_point_minimum_differential_,
|
||||
this->get_traits().get_visual_min_temperature(), this->get_traits().get_visual_max_temperature())
|
||||
: this->get_traits().get_visual_min_temperature();
|
||||
this->target_temperature_high = clamp(this->target_temperature_high, target_temperature_high_lower_limit,
|
||||
this->get_traits().get_visual_max_temperature());
|
||||
}
|
||||
}
|
||||
|
||||
void ThermostatClimate::control(const climate::ClimateCall &call) {
|
||||
bool target_temperature_high_changed = false;
|
||||
|
||||
if (call.get_preset().has_value()) {
|
||||
// setup_complete_ blocks modifying/resetting the temps immediately after boot
|
||||
if (this->setup_complete_) {
|
||||
this->change_preset_(*call.get_preset());
|
||||
this->change_preset_(call.get_preset().value());
|
||||
} else {
|
||||
this->preset = *call.get_preset();
|
||||
this->preset = call.get_preset().value();
|
||||
}
|
||||
}
|
||||
if (call.get_custom_preset().has_value()) {
|
||||
// setup_complete_ blocks modifying/resetting the temps immediately after boot
|
||||
if (this->setup_complete_) {
|
||||
this->change_custom_preset_(*call.get_custom_preset());
|
||||
this->change_custom_preset_(call.get_custom_preset().value());
|
||||
} else {
|
||||
this->custom_preset = *call.get_custom_preset();
|
||||
this->custom_preset = call.get_custom_preset().value();
|
||||
}
|
||||
}
|
||||
|
||||
if (call.get_mode().has_value())
|
||||
this->mode = *call.get_mode();
|
||||
if (call.get_fan_mode().has_value())
|
||||
this->fan_mode = *call.get_fan_mode();
|
||||
if (call.get_swing_mode().has_value())
|
||||
this->swing_mode = *call.get_swing_mode();
|
||||
if (call.get_mode().has_value()) {
|
||||
this->mode = call.get_mode().value();
|
||||
}
|
||||
if (call.get_fan_mode().has_value()) {
|
||||
this->fan_mode = call.get_fan_mode().value();
|
||||
}
|
||||
if (call.get_swing_mode().has_value()) {
|
||||
this->swing_mode = call.get_swing_mode().value();
|
||||
}
|
||||
if (this->supports_two_points_) {
|
||||
if (call.get_target_temperature_low().has_value()) {
|
||||
this->target_temperature_low = *call.get_target_temperature_low();
|
||||
validate_target_temperature_low();
|
||||
this->target_temperature_low = call.get_target_temperature_low().value();
|
||||
}
|
||||
if (call.get_target_temperature_high().has_value()) {
|
||||
this->target_temperature_high = *call.get_target_temperature_high();
|
||||
validate_target_temperature_high();
|
||||
target_temperature_high_changed = this->target_temperature_high != call.get_target_temperature_high().value();
|
||||
this->target_temperature_high = call.get_target_temperature_high().value();
|
||||
}
|
||||
// ensure the two set points are valid and adjust one of them if necessary
|
||||
this->validate_target_temperatures(target_temperature_high_changed ||
|
||||
(this->prev_mode_ == climate::CLIMATE_MODE_COOL));
|
||||
} else {
|
||||
if (call.get_target_temperature().has_value()) {
|
||||
this->target_temperature = *call.get_target_temperature();
|
||||
validate_target_temperature();
|
||||
this->target_temperature = call.get_target_temperature().value();
|
||||
this->validate_target_temperature();
|
||||
}
|
||||
}
|
||||
// make any changes happen
|
||||
refresh();
|
||||
this->refresh();
|
||||
}
|
||||
|
||||
climate::ClimateTraits ThermostatClimate::traits() {
|
||||
@@ -237,47 +245,47 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
traits.set_supports_current_humidity(true);
|
||||
|
||||
if (supports_auto_)
|
||||
if (this->supports_auto_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_AUTO);
|
||||
if (supports_heat_cool_)
|
||||
if (this->supports_heat_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT_COOL);
|
||||
if (supports_cool_)
|
||||
if (this->supports_cool_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_COOL);
|
||||
if (supports_dry_)
|
||||
if (this->supports_dry_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_DRY);
|
||||
if (supports_fan_only_)
|
||||
if (this->supports_fan_only_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_FAN_ONLY);
|
||||
if (supports_heat_)
|
||||
if (this->supports_heat_)
|
||||
traits.add_supported_mode(climate::CLIMATE_MODE_HEAT);
|
||||
|
||||
if (supports_fan_mode_on_)
|
||||
if (this->supports_fan_mode_on_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_ON);
|
||||
if (supports_fan_mode_off_)
|
||||
if (this->supports_fan_mode_off_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_OFF);
|
||||
if (supports_fan_mode_auto_)
|
||||
if (this->supports_fan_mode_auto_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_AUTO);
|
||||
if (supports_fan_mode_low_)
|
||||
if (this->supports_fan_mode_low_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_LOW);
|
||||
if (supports_fan_mode_medium_)
|
||||
if (this->supports_fan_mode_medium_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MEDIUM);
|
||||
if (supports_fan_mode_high_)
|
||||
if (this->supports_fan_mode_high_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_HIGH);
|
||||
if (supports_fan_mode_middle_)
|
||||
if (this->supports_fan_mode_middle_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_MIDDLE);
|
||||
if (supports_fan_mode_focus_)
|
||||
if (this->supports_fan_mode_focus_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS);
|
||||
if (supports_fan_mode_diffuse_)
|
||||
if (this->supports_fan_mode_diffuse_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE);
|
||||
if (supports_fan_mode_quiet_)
|
||||
if (this->supports_fan_mode_quiet_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET);
|
||||
|
||||
if (supports_swing_mode_both_)
|
||||
if (this->supports_swing_mode_both_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
||||
if (supports_swing_mode_horizontal_)
|
||||
if (this->supports_swing_mode_horizontal_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_HORIZONTAL);
|
||||
if (supports_swing_mode_off_)
|
||||
if (this->supports_swing_mode_off_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_OFF);
|
||||
if (supports_swing_mode_vertical_)
|
||||
if (this->supports_swing_mode_vertical_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
|
||||
for (auto &it : this->preset_config_) {
|
||||
@@ -299,14 +307,15 @@ climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_time
|
||||
return climate::CLIMATE_ACTION_OFF;
|
||||
}
|
||||
// do not change the action if an "ON" timer is running
|
||||
if ((!ignore_timers) &&
|
||||
(timer_active_(thermostat::TIMER_IDLE_ON) || timer_active_(thermostat::TIMER_COOLING_ON) ||
|
||||
timer_active_(thermostat::TIMER_FANNING_ON) || timer_active_(thermostat::TIMER_HEATING_ON))) {
|
||||
if ((!ignore_timers) && (this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_FANNING_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_ON))) {
|
||||
return this->action;
|
||||
}
|
||||
|
||||
// ensure set point(s) is/are valid before computing the action
|
||||
this->validate_target_temperatures();
|
||||
this->validate_target_temperatures(this->prev_mode_ == climate::CLIMATE_MODE_COOL);
|
||||
// everything has been validated so we can now safely compute the action
|
||||
switch (this->mode) {
|
||||
// if the climate mode is OFF then the climate action must be OFF
|
||||
@@ -340,6 +349,22 @@ climate::ClimateAction ThermostatClimate::compute_action_(const bool ignore_time
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
}
|
||||
break;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
if (this->supports_two_points_) {
|
||||
if (this->cooling_required_() && this->heating_required_()) {
|
||||
// this is bad and should never happen, so just stop.
|
||||
// target_action = climate::CLIMATE_ACTION_IDLE;
|
||||
} else if (this->cooling_required_()) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
} else if (this->heating_required_()) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
}
|
||||
} else if (this->supports_cool_ && this->cooling_required_()) {
|
||||
target_action = climate::CLIMATE_ACTION_COOLING;
|
||||
} else if (this->supports_heat_ && this->heating_required_()) {
|
||||
target_action = climate::CLIMATE_ACTION_HEATING;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -362,7 +387,7 @@ climate::ClimateAction ThermostatClimate::compute_supplemental_action_() {
|
||||
}
|
||||
|
||||
// ensure set point(s) is/are valid before computing the action
|
||||
this->validate_target_temperatures();
|
||||
this->validate_target_temperatures(this->prev_mode_ == climate::CLIMATE_MODE_COOL);
|
||||
// everything has been validated so we can now safely compute the action
|
||||
switch (this->mode) {
|
||||
// if the climate mode is OFF then the climate action must be OFF
|
||||
@@ -420,18 +445,18 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
||||
case climate::CLIMATE_ACTION_OFF:
|
||||
case climate::CLIMATE_ACTION_IDLE:
|
||||
if (this->idle_action_ready_()) {
|
||||
this->start_timer_(thermostat::TIMER_IDLE_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_IDLE_ON);
|
||||
if (this->action == climate::CLIMATE_ACTION_COOLING)
|
||||
this->start_timer_(thermostat::TIMER_COOLING_OFF);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_OFF);
|
||||
if (this->action == climate::CLIMATE_ACTION_FAN) {
|
||||
if (this->supports_fan_only_action_uses_fan_mode_timer_) {
|
||||
this->start_timer_(thermostat::TIMER_FAN_MODE);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE);
|
||||
} else {
|
||||
this->start_timer_(thermostat::TIMER_FANNING_OFF);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_OFF);
|
||||
}
|
||||
}
|
||||
if (this->action == climate::CLIMATE_ACTION_HEATING)
|
||||
this->start_timer_(thermostat::TIMER_HEATING_OFF);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_OFF);
|
||||
// trig = this->idle_action_trigger_;
|
||||
ESP_LOGVV(TAG, "Switching to IDLE/OFF action");
|
||||
this->cooling_max_runtime_exceeded_ = false;
|
||||
@@ -441,10 +466,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_COOLING:
|
||||
if (this->cooling_action_ready_()) {
|
||||
this->start_timer_(thermostat::TIMER_COOLING_ON);
|
||||
this->start_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
|
||||
if (this->supports_fan_with_cooling_) {
|
||||
this->start_timer_(thermostat::TIMER_FANNING_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
|
||||
trig_fan = this->fan_only_action_trigger_;
|
||||
}
|
||||
this->cooling_max_runtime_exceeded_ = false;
|
||||
@@ -455,10 +480,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_HEATING:
|
||||
if (this->heating_action_ready_()) {
|
||||
this->start_timer_(thermostat::TIMER_HEATING_ON);
|
||||
this->start_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
|
||||
if (this->supports_fan_with_heating_) {
|
||||
this->start_timer_(thermostat::TIMER_FANNING_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
|
||||
trig_fan = this->fan_only_action_trigger_;
|
||||
}
|
||||
this->heating_max_runtime_exceeded_ = false;
|
||||
@@ -470,9 +495,9 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
||||
case climate::CLIMATE_ACTION_FAN:
|
||||
if (this->fanning_action_ready_()) {
|
||||
if (this->supports_fan_only_action_uses_fan_mode_timer_) {
|
||||
this->start_timer_(thermostat::TIMER_FAN_MODE);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE);
|
||||
} else {
|
||||
this->start_timer_(thermostat::TIMER_FANNING_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
|
||||
}
|
||||
trig = this->fan_only_action_trigger_;
|
||||
ESP_LOGVV(TAG, "Switching to FAN_ONLY action");
|
||||
@@ -481,8 +506,8 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_DRYING:
|
||||
if (this->drying_action_ready_()) {
|
||||
this->start_timer_(thermostat::TIMER_COOLING_ON);
|
||||
this->start_timer_(thermostat::TIMER_FANNING_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_ON);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
|
||||
trig = this->dry_action_trigger_;
|
||||
ESP_LOGVV(TAG, "Switching to DRYING action");
|
||||
action_ready = true;
|
||||
@@ -525,14 +550,14 @@ void ThermostatClimate::switch_to_supplemental_action_(climate::ClimateAction ac
|
||||
switch (action) {
|
||||
case climate::CLIMATE_ACTION_OFF:
|
||||
case climate::CLIMATE_ACTION_IDLE:
|
||||
this->cancel_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME);
|
||||
this->cancel_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME);
|
||||
this->cancel_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
|
||||
this->cancel_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_COOLING:
|
||||
this->cancel_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME);
|
||||
this->cancel_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_HEATING:
|
||||
this->cancel_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME);
|
||||
this->cancel_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
@@ -547,15 +572,15 @@ void ThermostatClimate::trigger_supplemental_action_() {
|
||||
|
||||
switch (this->supplemental_action_) {
|
||||
case climate::CLIMATE_ACTION_COOLING:
|
||||
if (!this->timer_active_(thermostat::TIMER_COOLING_MAX_RUN_TIME)) {
|
||||
this->start_timer_(thermostat::TIMER_COOLING_MAX_RUN_TIME);
|
||||
if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME)) {
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
|
||||
}
|
||||
trig = this->supplemental_cool_action_trigger_;
|
||||
ESP_LOGVV(TAG, "Calling supplemental COOLING action");
|
||||
break;
|
||||
case climate::CLIMATE_ACTION_HEATING:
|
||||
if (!this->timer_active_(thermostat::TIMER_HEATING_MAX_RUN_TIME)) {
|
||||
this->start_timer_(thermostat::TIMER_HEATING_MAX_RUN_TIME);
|
||||
if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME)) {
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
|
||||
}
|
||||
trig = this->supplemental_heat_action_trigger_;
|
||||
ESP_LOGVV(TAG, "Calling supplemental HEATING action");
|
||||
@@ -633,7 +658,7 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo
|
||||
this->prev_fan_mode_trigger_->stop_action();
|
||||
this->prev_fan_mode_trigger_ = nullptr;
|
||||
}
|
||||
this->start_timer_(thermostat::TIMER_FAN_MODE);
|
||||
this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE);
|
||||
if (trig != nullptr) {
|
||||
trig->trigger();
|
||||
}
|
||||
@@ -653,13 +678,13 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
|
||||
this->prev_mode_trigger_->stop_action();
|
||||
this->prev_mode_trigger_ = nullptr;
|
||||
}
|
||||
Trigger<> *trig = this->auto_mode_trigger_;
|
||||
Trigger<> *trig = this->off_mode_trigger_;
|
||||
switch (mode) {
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
trig = this->off_mode_trigger_;
|
||||
case climate::CLIMATE_MODE_AUTO:
|
||||
trig = this->auto_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_HEAT_COOL:
|
||||
// trig = this->auto_mode_trigger_;
|
||||
trig = this->heat_cool_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_COOL:
|
||||
trig = this->cool_mode_trigger_;
|
||||
@@ -673,11 +698,12 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
|
||||
case climate::CLIMATE_MODE_DRY:
|
||||
trig = this->dry_mode_trigger_;
|
||||
break;
|
||||
case climate::CLIMATE_MODE_OFF:
|
||||
default:
|
||||
// we cannot report an invalid mode back to HA (even if it asked for one)
|
||||
// and must assume some valid value
|
||||
mode = climate::CLIMATE_MODE_HEAT_COOL;
|
||||
// trig = this->auto_mode_trigger_;
|
||||
mode = climate::CLIMATE_MODE_OFF;
|
||||
// trig = this->off_mode_trigger_;
|
||||
}
|
||||
if (trig != nullptr) {
|
||||
trig->trigger();
|
||||
@@ -685,8 +711,9 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
|
||||
this->mode = mode;
|
||||
this->prev_mode_ = mode;
|
||||
this->prev_mode_trigger_ = trig;
|
||||
if (publish_state)
|
||||
if (publish_state) {
|
||||
this->publish_state();
|
||||
}
|
||||
}
|
||||
|
||||
void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mode, bool publish_state) {
|
||||
@@ -732,35 +759,44 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo
|
||||
|
||||
bool ThermostatClimate::idle_action_ready_() {
|
||||
if (this->supports_fan_only_action_uses_fan_mode_timer_) {
|
||||
return !(this->timer_active_(thermostat::TIMER_COOLING_ON) || this->timer_active_(thermostat::TIMER_FAN_MODE) ||
|
||||
this->timer_active_(thermostat::TIMER_HEATING_ON));
|
||||
return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_FAN_MODE) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_ON));
|
||||
}
|
||||
return !(this->timer_active_(thermostat::TIMER_COOLING_ON) || this->timer_active_(thermostat::TIMER_FANNING_ON) ||
|
||||
this->timer_active_(thermostat::TIMER_HEATING_ON));
|
||||
return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_FANNING_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_ON));
|
||||
}
|
||||
|
||||
bool ThermostatClimate::cooling_action_ready_() {
|
||||
return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_FANNING_OFF) ||
|
||||
this->timer_active_(thermostat::TIMER_COOLING_OFF) || this->timer_active_(thermostat::TIMER_HEATING_ON));
|
||||
return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_FANNING_OFF) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_OFF) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_ON));
|
||||
}
|
||||
|
||||
bool ThermostatClimate::drying_action_ready_() {
|
||||
return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_FANNING_OFF) ||
|
||||
this->timer_active_(thermostat::TIMER_COOLING_OFF) || this->timer_active_(thermostat::TIMER_HEATING_ON));
|
||||
return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_FANNING_OFF) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_OFF) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_ON));
|
||||
}
|
||||
|
||||
bool ThermostatClimate::fan_mode_ready_() { return !(this->timer_active_(thermostat::TIMER_FAN_MODE)); }
|
||||
bool ThermostatClimate::fan_mode_ready_() { return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_FAN_MODE)); }
|
||||
|
||||
bool ThermostatClimate::fanning_action_ready_() {
|
||||
if (this->supports_fan_only_action_uses_fan_mode_timer_) {
|
||||
return !(this->timer_active_(thermostat::TIMER_FAN_MODE));
|
||||
return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_FAN_MODE));
|
||||
}
|
||||
return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_FANNING_OFF));
|
||||
return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_FANNING_OFF));
|
||||
}
|
||||
|
||||
bool ThermostatClimate::heating_action_ready_() {
|
||||
return !(this->timer_active_(thermostat::TIMER_IDLE_ON) || this->timer_active_(thermostat::TIMER_COOLING_ON) ||
|
||||
this->timer_active_(thermostat::TIMER_FANNING_OFF) || this->timer_active_(thermostat::TIMER_HEATING_OFF));
|
||||
return !(this->timer_active_(thermostat::THERMOSTAT_TIMER_IDLE_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_ON) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_FANNING_OFF) ||
|
||||
this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_OFF));
|
||||
}
|
||||
|
||||
void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) {
|
||||
@@ -958,37 +994,25 @@ bool ThermostatClimate::supplemental_heating_required_() {
|
||||
(this->supplemental_action_ == climate::CLIMATE_ACTION_HEATING));
|
||||
}
|
||||
|
||||
void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
|
||||
bool is_default_preset) {
|
||||
ESP_LOGCONFIG(TAG, " %s Is Default: %s", preset_name, YESNO(is_default_preset));
|
||||
|
||||
void ThermostatClimate::dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config) {
|
||||
if (this->supports_heat_) {
|
||||
if (this->supports_two_points_) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature Low: %.1f°C", preset_name,
|
||||
config.default_temperature_low);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature Low: %.1f°C", preset_name, config.default_temperature);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature Low: %.1f°C",
|
||||
this->supports_two_points_ ? config.default_temperature_low : config.default_temperature);
|
||||
}
|
||||
if ((this->supports_cool_) || (this->supports_fan_only_)) {
|
||||
if (this->supports_two_points_) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature High: %.1f°C", preset_name,
|
||||
config.default_temperature_high);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Target Temperature High: %.1f°C", preset_name, config.default_temperature);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Default Target Temperature High: %.1f°C",
|
||||
this->supports_two_points_ ? config.default_temperature_high : config.default_temperature);
|
||||
}
|
||||
|
||||
if (config.mode_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Mode: %s", preset_name,
|
||||
LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
|
||||
ESP_LOGCONFIG(TAG, " Default Mode: %s", LOG_STR_ARG(climate::climate_mode_to_string(*config.mode_)));
|
||||
}
|
||||
if (config.fan_mode_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Fan Mode: %s", preset_name,
|
||||
ESP_LOGCONFIG(TAG, " Default Fan Mode: %s",
|
||||
LOG_STR_ARG(climate::climate_fan_mode_to_string(*config.fan_mode_)));
|
||||
}
|
||||
if (config.swing_mode_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " %s Default Swing Mode: %s", preset_name,
|
||||
ESP_LOGCONFIG(TAG, " Default Swing Mode: %s",
|
||||
LOG_STR_ARG(climate::climate_swing_mode_to_string(*config.swing_mode_)));
|
||||
}
|
||||
}
|
||||
@@ -1106,6 +1130,7 @@ ThermostatClimate::ThermostatClimate()
|
||||
heat_action_trigger_(new Trigger<>()),
|
||||
supplemental_heat_action_trigger_(new Trigger<>()),
|
||||
heat_mode_trigger_(new Trigger<>()),
|
||||
heat_cool_mode_trigger_(new Trigger<>()),
|
||||
auto_mode_trigger_(new Trigger<>()),
|
||||
idle_action_trigger_(new Trigger<>()),
|
||||
off_mode_trigger_(new Trigger<>()),
|
||||
@@ -1147,43 +1172,43 @@ void ThermostatClimate::set_heat_overrun(float overrun) { this->heating_overrun_
|
||||
void ThermostatClimate::set_supplemental_cool_delta(float delta) { this->supplemental_cool_delta_ = delta; }
|
||||
void ThermostatClimate::set_supplemental_heat_delta(float delta) { this->supplemental_heat_delta_ = delta; }
|
||||
void ThermostatClimate::set_cooling_maximum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_COOLING_MAX_RUN_TIME].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_cooling_minimum_off_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_COOLING_OFF].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_OFF].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_cooling_minimum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_COOLING_ON].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_COOLING_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_fan_mode_minimum_switching_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_FAN_MODE].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_FAN_MODE].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_fanning_minimum_off_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_FANNING_OFF].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_OFF].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_fanning_minimum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_FANNING_ON].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_FANNING_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_heating_maximum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_HEATING_MAX_RUN_TIME].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_heating_minimum_off_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_HEATING_OFF].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_OFF].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_heating_minimum_run_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_HEATING_ON].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_HEATING_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_idle_minimum_time_in_sec(uint32_t time) {
|
||||
this->timer_[thermostat::TIMER_IDLE_ON].time =
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time =
|
||||
1000 * (time < this->min_timer_duration_ ? this->min_timer_duration_ : time);
|
||||
}
|
||||
void ThermostatClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
|
||||
@@ -1274,6 +1299,7 @@ Trigger<> *ThermostatClimate::get_cool_mode_trigger() const { return this->cool_
|
||||
Trigger<> *ThermostatClimate::get_dry_mode_trigger() const { return this->dry_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() const { return this->fan_only_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_heat_mode_trigger() const { return this->heat_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() const { return this->heat_cool_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_off_mode_trigger() const { return this->off_mode_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() const { return this->fan_mode_on_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() const { return this->fan_mode_off_trigger_; }
|
||||
@@ -1295,64 +1321,69 @@ Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->p
|
||||
void ThermostatClimate::dump_config() {
|
||||
LOG_CLIMATE("", "Thermostat", this);
|
||||
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" On boot, restore from: %s\n"
|
||||
" Use Start-up Delay: %s",
|
||||
this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY",
|
||||
YESNO(this->use_startup_delay_));
|
||||
if (this->supports_two_points_) {
|
||||
ESP_LOGCONFIG(TAG, " Minimum Set Point Differential: %.1f°C", this->set_point_minimum_differential_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Use Start-up Delay: %s", YESNO(this->use_startup_delay_));
|
||||
if (this->supports_cool_) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Cooling Parameters:\n"
|
||||
" Deadband: %.1f°C\n"
|
||||
" Overrun: %.1f°C",
|
||||
this->cooling_deadband_, this->cooling_overrun_);
|
||||
if ((this->supplemental_cool_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Supplemental Delta: %.1f°C\n"
|
||||
" Maximum Run Time: %" PRIu32 "s",
|
||||
this->supplemental_cool_delta_,
|
||||
this->timer_duration_(thermostat::TIMER_COOLING_MAX_RUN_TIME) / 1000);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Overrun: %.1f°C\n"
|
||||
" Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Minimum Run Time: %" PRIu32 "s",
|
||||
this->timer_duration_(thermostat::TIMER_COOLING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::TIMER_COOLING_ON) / 1000);
|
||||
this->cooling_deadband_, this->cooling_overrun_,
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_COOLING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_COOLING_ON) / 1000);
|
||||
if ((this->supplemental_cool_delta_ > 0) ||
|
||||
(this->timer_duration_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Maximum Run Time: %" PRIu32 "s\n"
|
||||
" Supplemental Delta: %.1f°C",
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME) / 1000,
|
||||
this->supplemental_cool_delta_);
|
||||
}
|
||||
}
|
||||
if (this->supports_heat_) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Heating Parameters:\n"
|
||||
" Deadband: %.1f°C\n"
|
||||
" Overrun: %.1f°C",
|
||||
this->heating_deadband_, this->heating_overrun_);
|
||||
if ((this->supplemental_heat_delta_ > 0) || (this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Supplemental Delta: %.1f°C\n"
|
||||
" Maximum Run Time: %" PRIu32 "s",
|
||||
this->supplemental_heat_delta_,
|
||||
this->timer_duration_(thermostat::TIMER_HEATING_MAX_RUN_TIME) / 1000);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Overrun: %.1f°C\n"
|
||||
" Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Minimum Run Time: %" PRIu32 "s",
|
||||
this->timer_duration_(thermostat::TIMER_HEATING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::TIMER_HEATING_ON) / 1000);
|
||||
this->heating_deadband_, this->heating_overrun_,
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_HEATING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_HEATING_ON) / 1000);
|
||||
if ((this->supplemental_heat_delta_ > 0) ||
|
||||
(this->timer_duration_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME) > 0)) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Maximum Run Time: %" PRIu32 "s\n"
|
||||
" Supplemental Delta: %.1f°C",
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME) / 1000,
|
||||
this->supplemental_heat_delta_);
|
||||
}
|
||||
}
|
||||
if (this->supports_fan_only_) {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Fanning Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Fanning Minimum Run Time: %" PRIu32 "s",
|
||||
this->timer_duration_(thermostat::TIMER_FANNING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::TIMER_FANNING_ON) / 1000);
|
||||
" Fan Parameters:\n"
|
||||
" Minimum Off Time: %" PRIu32 "s\n"
|
||||
" Minimum Run Time: %" PRIu32 "s",
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_FANNING_OFF) / 1000,
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_FANNING_ON) / 1000);
|
||||
}
|
||||
if (this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ ||
|
||||
this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ ||
|
||||
this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_ ||
|
||||
this->supports_fan_mode_quiet_) {
|
||||
ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %" PRIu32 "s",
|
||||
this->timer_duration_(thermostat::TIMER_FAN_MODE) / 1000);
|
||||
this->timer_duration_(thermostat::THERMOSTAT_TIMER_FAN_MODE) / 1000);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Minimum Idle Time: %" PRIu32 "s", this->timer_[thermostat::TIMER_IDLE_ON].time / 1000);
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" Minimum Idle Time: %" PRIu32 "s\n"
|
||||
" Supported MODES:\n"
|
||||
" AUTO: %s\n"
|
||||
" HEAT/COOL: %s\n"
|
||||
@@ -1362,8 +1393,9 @@ void ThermostatClimate::dump_config() {
|
||||
" FAN_ONLY: %s\n"
|
||||
" FAN_ONLY_ACTION_USES_FAN_MODE_TIMER: %s\n"
|
||||
" FAN_ONLY_COOLING: %s",
|
||||
YESNO(this->supports_auto_), YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_),
|
||||
YESNO(this->supports_cool_), YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
|
||||
this->timer_[thermostat::THERMOSTAT_TIMER_IDLE_ON].time / 1000, YESNO(this->supports_auto_),
|
||||
YESNO(this->supports_heat_cool_), YESNO(this->supports_heat_), YESNO(this->supports_cool_),
|
||||
YESNO(this->supports_dry_), YESNO(this->supports_fan_only_),
|
||||
YESNO(this->supports_fan_only_action_uses_fan_mode_timer_), YESNO(this->supports_fan_only_cooling_));
|
||||
if (this->supports_cool_) {
|
||||
ESP_LOGCONFIG(TAG, " FAN_WITH_COOLING: %s", YESNO(this->supports_fan_with_cooling_));
|
||||
@@ -1382,40 +1414,39 @@ void ThermostatClimate::dump_config() {
|
||||
" MIDDLE: %s\n"
|
||||
" FOCUS: %s\n"
|
||||
" DIFFUSE: %s\n"
|
||||
" QUIET: %s",
|
||||
YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
|
||||
YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
|
||||
YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
|
||||
YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
|
||||
YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_));
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" QUIET: %s\n"
|
||||
" Supported SWING MODES:\n"
|
||||
" BOTH: %s\n"
|
||||
" OFF: %s\n"
|
||||
" HORIZONTAL: %s\n"
|
||||
" VERTICAL: %s\n"
|
||||
" Supports TWO SET POINTS: %s",
|
||||
YESNO(this->supports_fan_mode_on_), YESNO(this->supports_fan_mode_off_),
|
||||
YESNO(this->supports_fan_mode_auto_), YESNO(this->supports_fan_mode_low_),
|
||||
YESNO(this->supports_fan_mode_medium_), YESNO(this->supports_fan_mode_high_),
|
||||
YESNO(this->supports_fan_mode_middle_), YESNO(this->supports_fan_mode_focus_),
|
||||
YESNO(this->supports_fan_mode_diffuse_), YESNO(this->supports_fan_mode_quiet_),
|
||||
YESNO(this->supports_swing_mode_both_), YESNO(this->supports_swing_mode_off_),
|
||||
YESNO(this->supports_swing_mode_horizontal_), YESNO(this->supports_swing_mode_vertical_),
|
||||
YESNO(this->supports_two_points_));
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported PRESETS: ");
|
||||
for (auto &it : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true));
|
||||
this->dump_preset_config_(preset_name, it.second, it.first == this->default_preset_);
|
||||
if (!this->preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported PRESETS:");
|
||||
for (auto &it : this->preset_config_) {
|
||||
const auto *preset_name = LOG_STR_ARG(climate::climate_preset_to_string(it.first));
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS: ");
|
||||
for (auto &it : this->custom_preset_config_) {
|
||||
const auto *preset_name = it.first.c_str();
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Supports %s: %s", preset_name, YESNO(true));
|
||||
this->dump_preset_config_(preset_name, it.second, it.first == this->default_custom_preset_);
|
||||
if (!this->custom_preset_config_.empty()) {
|
||||
ESP_LOGCONFIG(TAG, " Supported CUSTOM PRESETS:");
|
||||
for (auto &it : this->custom_preset_config_) {
|
||||
const auto *preset_name = it.first.c_str();
|
||||
ESP_LOGCONFIG(TAG, " %s:%s", preset_name, it.first == this->default_custom_preset_ ? " (default)" : "");
|
||||
this->dump_preset_config_(preset_name, it.second);
|
||||
}
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " On boot, restore from: %s",
|
||||
this->on_boot_restore_from_ == thermostat::DEFAULT_PRESET ? "DEFAULT_PRESET" : "MEMORY");
|
||||
}
|
||||
|
||||
ThermostatClimateTargetTempConfig::ThermostatClimateTargetTempConfig() = default;
|
||||
|
@@ -6,24 +6,25 @@
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
namespace thermostat {
|
||||
|
||||
enum ThermostatClimateTimerIndex : uint8_t {
|
||||
TIMER_COOLING_MAX_RUN_TIME = 0,
|
||||
TIMER_COOLING_OFF = 1,
|
||||
TIMER_COOLING_ON = 2,
|
||||
TIMER_FAN_MODE = 3,
|
||||
TIMER_FANNING_OFF = 4,
|
||||
TIMER_FANNING_ON = 5,
|
||||
TIMER_HEATING_MAX_RUN_TIME = 6,
|
||||
TIMER_HEATING_OFF = 7,
|
||||
TIMER_HEATING_ON = 8,
|
||||
TIMER_IDLE_ON = 9,
|
||||
THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME = 0,
|
||||
THERMOSTAT_TIMER_COOLING_OFF = 1,
|
||||
THERMOSTAT_TIMER_COOLING_ON = 2,
|
||||
THERMOSTAT_TIMER_FAN_MODE = 3,
|
||||
THERMOSTAT_TIMER_FANNING_OFF = 4,
|
||||
THERMOSTAT_TIMER_FANNING_ON = 5,
|
||||
THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME = 6,
|
||||
THERMOSTAT_TIMER_HEATING_OFF = 7,
|
||||
THERMOSTAT_TIMER_HEATING_ON = 8,
|
||||
THERMOSTAT_TIMER_IDLE_ON = 9,
|
||||
THERMOSTAT_TIMER_COUNT = 10,
|
||||
};
|
||||
|
||||
enum OnBootRestoreFrom : uint8_t {
|
||||
@@ -131,6 +132,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *get_dry_mode_trigger() const;
|
||||
Trigger<> *get_fan_only_mode_trigger() const;
|
||||
Trigger<> *get_heat_mode_trigger() const;
|
||||
Trigger<> *get_heat_cool_mode_trigger() const;
|
||||
Trigger<> *get_off_mode_trigger() const;
|
||||
Trigger<> *get_fan_mode_on_trigger() const;
|
||||
Trigger<> *get_fan_mode_off_trigger() const;
|
||||
@@ -163,9 +165,10 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
/// Returns the fan mode that is locked in (check fan_mode_change_delayed(), first!)
|
||||
climate::ClimateFanMode locked_fan_mode();
|
||||
/// Set point and hysteresis validation
|
||||
bool hysteresis_valid(); // returns true if valid
|
||||
bool hysteresis_valid(); // returns true if valid
|
||||
bool limit_setpoints_for_heat_cool(); // returns true if set points should be further limited within visual range
|
||||
void validate_target_temperature();
|
||||
void validate_target_temperatures();
|
||||
void validate_target_temperatures(bool pin_target_temperature_high);
|
||||
void validate_target_temperature_low();
|
||||
void validate_target_temperature_high();
|
||||
|
||||
@@ -241,12 +244,28 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
bool supplemental_cooling_required_();
|
||||
bool supplemental_heating_required_();
|
||||
|
||||
void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config,
|
||||
bool is_default_preset);
|
||||
void dump_preset_config_(const char *preset_name, const ThermostatClimateTargetTempConfig &config);
|
||||
|
||||
/// Minimum allowable duration in seconds for action timers
|
||||
const uint8_t min_timer_duration_{1};
|
||||
|
||||
/// Store previously-known states
|
||||
///
|
||||
/// These are used to determine when a trigger/action needs to be called
|
||||
climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON};
|
||||
climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF};
|
||||
climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
|
||||
|
||||
/// The current supplemental action
|
||||
climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF};
|
||||
|
||||
/// Default standard preset to use on start up
|
||||
climate::ClimatePreset default_preset_{};
|
||||
|
||||
/// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
|
||||
/// state will attempt to be restored if possible
|
||||
OnBootRestoreFrom on_boot_restore_from_{OnBootRestoreFrom::MEMORY};
|
||||
|
||||
/// Whether the controller supports auto/cooling/drying/fanning/heating.
|
||||
///
|
||||
/// A false value for any given attribute means that the controller has no such action
|
||||
@@ -362,9 +381,15 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *supplemental_heat_action_trigger_{nullptr};
|
||||
Trigger<> *heat_mode_trigger_{nullptr};
|
||||
|
||||
/// The trigger to call when the controller should switch to heat/cool mode.
|
||||
///
|
||||
/// In heat/cool mode, the controller will enable heating/cooling as necessary and switch
|
||||
/// to idle when the temperature is within the thresholds/set points.
|
||||
Trigger<> *heat_cool_mode_trigger_{nullptr};
|
||||
|
||||
/// The trigger to call when the controller should switch to auto mode.
|
||||
///
|
||||
/// In auto mode, the controller will enable heating/cooling as necessary and switch
|
||||
/// In auto mode, the controller will enable heating/cooling as supported/necessary and switch
|
||||
/// to idle when the temperature is within the thresholds/set points.
|
||||
Trigger<> *auto_mode_trigger_{nullptr};
|
||||
|
||||
@@ -438,35 +463,21 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *prev_mode_trigger_{nullptr};
|
||||
Trigger<> *prev_swing_mode_trigger_{nullptr};
|
||||
|
||||
/// If set to DEFAULT_PRESET then the default preset is always used. When MEMORY prior
|
||||
/// state will attempt to be restored if possible
|
||||
OnBootRestoreFrom on_boot_restore_from_{OnBootRestoreFrom::MEMORY};
|
||||
|
||||
/// Store previously-known states
|
||||
///
|
||||
/// These are used to determine when a trigger/action needs to be called
|
||||
climate::ClimateAction supplemental_action_{climate::CLIMATE_ACTION_OFF};
|
||||
climate::ClimateFanMode prev_fan_mode_{climate::CLIMATE_FAN_ON};
|
||||
climate::ClimateMode prev_mode_{climate::CLIMATE_MODE_OFF};
|
||||
climate::ClimateSwingMode prev_swing_mode_{climate::CLIMATE_SWING_OFF};
|
||||
|
||||
/// Default standard preset to use on start up
|
||||
climate::ClimatePreset default_preset_{};
|
||||
/// Default custom preset to use on start up
|
||||
std::string default_custom_preset_{};
|
||||
|
||||
/// Climate action timers
|
||||
std::vector<ThermostatClimateTimer> timer_{
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)},
|
||||
{false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)},
|
||||
std::array<ThermostatClimateTimer, THERMOSTAT_TIMER_COUNT> timer_{
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)),
|
||||
ThermostatClimateTimer(false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)),
|
||||
};
|
||||
|
||||
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
|
||||
|
@@ -494,155 +494,155 @@ const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1c, 0x40, 0xc8, 0x12, 0x7c, 0xa6, 0xc1, 0x29, 0x21, 0xa4, 0xd5, 0x9f, 0x05, 0x5f, 0xe2, 0x9b, 0x98, 0xa6, 0xc1,
|
||||
0xbc, 0xe8, 0x96, 0x04, 0xa0, 0x22, 0xa6, 0x6f, 0x45, 0x79, 0x6f, 0x9c, 0xa4, 0x8a, 0xea, 0xb5, 0x82, 0xb3, 0x59,
|
||||
0x52, 0xcf, 0x96, 0x58, 0x9a, 0xe5, 0x93, 0x19, 0x25, 0xfc, 0xa6, 0x79, 0xeb, 0xf6, 0x36, 0xc7, 0xd7, 0x60, 0x76,
|
||||
0x65, 0x7c, 0x4d, 0x02, 0x5b, 0x3e, 0xbd, 0x0f, 0xc7, 0xe5, 0xef, 0x57, 0x34, 0xcf, 0xc3, 0xb1, 0xae, 0xb9, 0x3d,
|
||||
0x9e, 0x26, 0x41, 0xb4, 0x63, 0x69, 0x06, 0x08, 0x88, 0x89, 0x01, 0x46, 0xc0, 0xa7, 0xa1, 0x43, 0x64, 0x30, 0xf5,
|
||||
0x7a, 0x74, 0x4d, 0x0e, 0x5f, 0x2f, 0x12, 0xe1, 0xb8, 0x2a, 0x38, 0x99, 0x66, 0x54, 0x96, 0x2a, 0x34, 0x16, 0x27,
|
||||
0xfb, 0x50, 0xa0, 0x5e, 0x6f, 0x89, 0xa2, 0x19, 0x07, 0xca, 0xf6, 0x58, 0x9a, 0x63, 0xa2, 0x68, 0x76, 0xa2, 0x52,
|
||||
0x99, 0xa5, 0xb4, 0x1e, 0xbb, 0xf9, 0xbc, 0x3d, 0x84, 0x3f, 0x3a, 0x32, 0xf4, 0xf9, 0x68, 0x34, 0xba, 0x37, 0xaa,
|
||||
0xf6, 0x79, 0x34, 0xa2, 0x1d, 0x7a, 0xd4, 0x85, 0x24, 0x96, 0xa6, 0x8e, 0xc5, 0xb4, 0x0b, 0x89, 0xbb, 0xc5, 0xc3,
|
||||
0x2a, 0x43, 0xd8, 0x46, 0xc4, 0x8b, 0x87, 0x47, 0xd8, 0x8a, 0x69, 0x46, 0x17, 0x93, 0x30, 0x1b, 0xb3, 0x34, 0x68,
|
||||
0x15, 0xfe, 0x5c, 0x87, 0xa4, 0x3e, 0x3f, 0x3e, 0x3e, 0x2e, 0xfc, 0xc8, 0x3c, 0xb5, 0xa2, 0xa8, 0xf0, 0x87, 0x8b,
|
||||
0x72, 0x1a, 0xad, 0xd6, 0x68, 0x54, 0xf8, 0xcc, 0x14, 0x1c, 0x74, 0x86, 0xd1, 0x41, 0xa7, 0xf0, 0x6f, 0xac, 0x1a,
|
||||
0x85, 0x4f, 0xf5, 0x53, 0x46, 0xa3, 0x5a, 0x26, 0xcc, 0xe3, 0x56, 0xab, 0xf0, 0x15, 0xa1, 0x2d, 0xc0, 0x2c, 0x55,
|
||||
0x3f, 0x83, 0x70, 0x26, 0x38, 0x30, 0xf7, 0x6e, 0x22, 0xbc, 0xc1, 0xa5, 0xbe, 0x65, 0x44, 0x7d, 0x93, 0xa3, 0x40,
|
||||
0x17, 0xf8, 0x67, 0x3b, 0x78, 0x04, 0xc4, 0x2c, 0x83, 0x46, 0x89, 0x89, 0x2d, 0xd5, 0x5e, 0x03, 0x65, 0xc9, 0xd7,
|
||||
0x3f, 0x93, 0xa4, 0x8a, 0x29, 0x01, 0x27, 0x83, 0x9a, 0xea, 0x32, 0x3c, 0x4a, 0xb7, 0xc8, 0x0f, 0xf6, 0x69, 0xf9,
|
||||
0x71, 0xf7, 0x10, 0xf1, 0xc1, 0xfe, 0x70, 0xf1, 0x41, 0xa9, 0x25, 0x3e, 0x14, 0xf3, 0xb8, 0x13, 0xc4, 0x1d, 0xc6,
|
||||
0x74, 0xf8, 0xf1, 0x9a, 0xdf, 0x36, 0x61, 0x4b, 0x64, 0xae, 0x14, 0x2c, 0xbb, 0xbf, 0x35, 0x6b, 0xc6, 0x74, 0x66,
|
||||
0x7d, 0xd1, 0x43, 0xaa, 0x0f, 0x6f, 0x52, 0xe2, 0xbe, 0x31, 0xb6, 0xad, 0x2a, 0x19, 0x8d, 0x88, 0xfb, 0x66, 0x34,
|
||||
0x72, 0xcd, 0x59, 0xc9, 0x50, 0x50, 0x59, 0xeb, 0x75, 0xad, 0x44, 0xd6, 0xfa, 0xf2, 0x4b, 0xbb, 0xcc, 0x2e, 0xd0,
|
||||
0xa1, 0x27, 0x3b, 0xcc, 0xa4, 0xdf, 0x44, 0x2c, 0x87, 0xad, 0x06, 0x1f, 0x1a, 0xa9, 0xdf, 0xd5, 0x98, 0xd6, 0xae,
|
||||
0xd5, 0x2e, 0x01, 0xde, 0x70, 0x17, 0xf8, 0xea, 0x45, 0x01, 0x63, 0x6a, 0xf2, 0x16, 0x9f, 0xde, 0x7d, 0x15, 0x79,
|
||||
0x77, 0x02, 0x15, 0x2c, 0x7f, 0x93, 0xae, 0x1c, 0x02, 0x52, 0x30, 0x12, 0x62, 0x4f, 0xab, 0x10, 0x7c, 0x3c, 0x4e,
|
||||
0xe0, 0x5b, 0x2f, 0x8b, 0xda, 0xfd, 0xb1, 0xaa, 0x79, 0xbf, 0x36, 0xdf, 0xc0, 0x6e, 0xa8, 0x6f, 0x5b, 0x95, 0x9f,
|
||||
0x9e, 0x52, 0xc9, 0xe3, 0x73, 0xfd, 0x0d, 0x22, 0x69, 0x16, 0x2f, 0x34, 0x93, 0x5f, 0xa8, 0x94, 0x63, 0x01, 0xe9,
|
||||
0x36, 0xaa, 0xe3, 0xa8, 0x28, 0xf4, 0x61, 0x8d, 0x88, 0xe5, 0x53, 0xb8, 0xd7, 0x54, 0xb5, 0xa4, 0x9f, 0x62, 0xe1,
|
||||
0xf9, 0x8d, 0x15, 0xdf, 0xa9, 0x2d, 0x57, 0x61, 0x02, 0x3c, 0xca, 0x61, 0x7e, 0x27, 0x0a, 0x57, 0xfb, 0xdd, 0x0d,
|
||||
0x12, 0x5d, 0x47, 0xe1, 0x53, 0x45, 0x9e, 0xac, 0x19, 0x82, 0xf3, 0xbb, 0x5c, 0x10, 0xf3, 0xca, 0x14, 0x14, 0x76,
|
||||
0xfc, 0x52, 0xbe, 0x51, 0xd8, 0x92, 0xd1, 0x92, 0x7c, 0x1a, 0xa6, 0x8a, 0x8d, 0x12, 0x57, 0xf1, 0x83, 0xdd, 0x45,
|
||||
0xb5, 0xf2, 0x85, 0x6b, 0xc0, 0x56, 0xc4, 0xdb, 0x3b, 0xd9, 0x87, 0x06, 0x3d, 0xa7, 0x06, 0x7a, 0xba, 0x16, 0x64,
|
||||
0xf9, 0x44, 0xba, 0xc3, 0x95, 0x9f, 0xdf, 0x60, 0x3f, 0xbf, 0x71, 0xfe, 0xbc, 0x68, 0xde, 0xd0, 0xeb, 0x8f, 0x4c,
|
||||
0x34, 0x45, 0x38, 0x6d, 0x82, 0xe1, 0x23, 0x9d, 0xa3, 0x9a, 0x3d, 0xcb, 0x2c, 0x3f, 0x75, 0xd5, 0x41, 0x77, 0x96,
|
||||
0x43, 0x56, 0x84, 0x54, 0xdf, 0x83, 0x94, 0xa7, 0xb4, 0x5b, 0xcf, 0xe6, 0xb4, 0x83, 0xec, 0x06, 0x5b, 0x17, 0x0b,
|
||||
0x0e, 0x59, 0x14, 0xe2, 0x2e, 0x68, 0x69, 0xb6, 0xde, 0x32, 0x11, 0xf4, 0xd6, 0xc6, 0xfa, 0x81, 0x46, 0x6e, 0x43,
|
||||
0x4a, 0xaf, 0x6c, 0x3d, 0x93, 0x60, 0x5b, 0x26, 0xc0, 0xa7, 0x72, 0x1b, 0xc1, 0xa5, 0x6a, 0xfe, 0x5a, 0x49, 0xa1,
|
||||
0xab, 0xc5, 0x32, 0xb7, 0xf1, 0x21, 0x90, 0x05, 0xe1, 0x48, 0xd0, 0x0c, 0x3f, 0xa4, 0xe6, 0xb5, 0x3c, 0x86, 0xb4,
|
||||
0x00, 0x31, 0x13, 0xb4, 0x8f, 0xa7, 0xb7, 0x0f, 0xef, 0xfe, 0xfe, 0xe9, 0x17, 0x1a, 0x47, 0xe6, 0x5a, 0x1e, 0xd7,
|
||||
0xed, 0xc2, 0x46, 0x48, 0xc2, 0xbb, 0x80, 0xa5, 0x52, 0xe6, 0x5d, 0x83, 0x5f, 0xb4, 0x3b, 0xe5, 0x3a, 0x49, 0x37,
|
||||
0xa3, 0x89, 0xfc, 0x0a, 0x9f, 0x5e, 0x8a, 0x83, 0x47, 0xd3, 0x5b, 0xb3, 0x1a, 0xed, 0x95, 0xe4, 0xdb, 0x3f, 0x34,
|
||||
0xc7, 0x76, 0x7b, 0x52, 0x6f, 0x3d, 0x4f, 0xf4, 0x68, 0x7a, 0xdb, 0x55, 0x82, 0xb6, 0x99, 0x29, 0xa8, 0x5a, 0xd3,
|
||||
0x5b, 0x3b, 0xcb, 0xb8, 0xea, 0xc8, 0xf1, 0x0f, 0x72, 0x87, 0x86, 0x39, 0xed, 0xc2, 0xbd, 0xe3, 0x6c, 0x18, 0x26,
|
||||
0x5a, 0x98, 0x4f, 0x58, 0x14, 0x25, 0xb4, 0x6b, 0xe4, 0xb5, 0xd3, 0x7e, 0x04, 0x49, 0xba, 0xf6, 0x92, 0xd5, 0x57,
|
||||
0xc5, 0x42, 0x5e, 0x89, 0xa7, 0xf0, 0x3a, 0xe7, 0x09, 0x7c, 0xf4, 0x63, 0x23, 0x3a, 0x75, 0xf6, 0x6a, 0xab, 0x42,
|
||||
0x9e, 0xfc, 0x5d, 0x9f, 0xcb, 0x51, 0xeb, 0x4f, 0x5d, 0xb9, 0xe0, 0xad, 0xae, 0xe0, 0xd3, 0xa0, 0x79, 0x50, 0x9f,
|
||||
0x08, 0xbc, 0x2a, 0xa7, 0x80, 0x37, 0x4c, 0x0b, 0x83, 0xb4, 0x52, 0x7c, 0xda, 0xf1, 0xdb, 0xba, 0x4c, 0x76, 0x00,
|
||||
0x79, 0x61, 0x65, 0x51, 0x51, 0x9f, 0xcc, 0xbf, 0xcd, 0x6e, 0x79, 0xb2, 0x79, 0xb7, 0x3c, 0x31, 0xbb, 0xe5, 0x7e,
|
||||
0x8a, 0xfd, 0x7c, 0xd4, 0x86, 0x3f, 0xdd, 0x6a, 0x42, 0x41, 0xcb, 0x39, 0x98, 0xde, 0x3a, 0xa0, 0xa7, 0x35, 0x3b,
|
||||
0xd3, 0x5b, 0x95, 0x63, 0x0d, 0xb1, 0x9b, 0x16, 0x64, 0x1d, 0xe3, 0x96, 0x03, 0x85, 0xf0, 0xb7, 0x55, 0x7b, 0xd5,
|
||||
0x3e, 0x84, 0x77, 0xd0, 0xea, 0x68, 0xfd, 0x5d, 0xe7, 0xfe, 0x4d, 0x1b, 0xa4, 0x5c, 0x78, 0x81, 0xe1, 0xc6, 0xc8,
|
||||
0x17, 0xe1, 0xf5, 0x35, 0x8d, 0x82, 0x11, 0x1f, 0xce, 0xf2, 0x7f, 0xd2, 0xf0, 0x6b, 0x24, 0xde, 0xbb, 0xa5, 0x57,
|
||||
0xfa, 0x31, 0x4d, 0x55, 0xc6, 0xb7, 0xe9, 0x61, 0x51, 0xae, 0x53, 0x90, 0x0f, 0xc3, 0x84, 0x7a, 0x1d, 0xff, 0x70,
|
||||
0xc3, 0x26, 0xf8, 0x77, 0x59, 0x9b, 0x8d, 0x93, 0xf9, 0xbd, 0xc8, 0xb8, 0x17, 0x09, 0xbf, 0x0a, 0x07, 0xf6, 0x1a,
|
||||
0xb6, 0x8e, 0x37, 0x83, 0x3b, 0x30, 0x23, 0x5d, 0x18, 0xa1, 0xa0, 0xe5, 0x4e, 0x44, 0x47, 0xe1, 0x2c, 0x11, 0xf7,
|
||||
0xf7, 0xba, 0x8d, 0x32, 0xd6, 0x7a, 0xbd, 0x87, 0xa1, 0x57, 0x75, 0x1f, 0xc8, 0xa5, 0x3f, 0x7f, 0x72, 0x08, 0x7f,
|
||||
0x54, 0xfe, 0xd7, 0x5d, 0xa5, 0xab, 0x2b, 0xbb, 0x17, 0x74, 0xf5, 0xdd, 0x9a, 0x32, 0xae, 0x44, 0xb8, 0xd4, 0xc7,
|
||||
0x1f, 0x5a, 0x1b, 0xb4, 0xca, 0x07, 0x55, 0xd7, 0x5a, 0xd6, 0xaf, 0xaa, 0xfd, 0xeb, 0x3a, 0x7f, 0x60, 0xdd, 0xa1,
|
||||
0xd2, 0x5c, 0xeb, 0x75, 0xf5, 0x67, 0x08, 0xd7, 0x2a, 0x1b, 0x8c, 0xcb, 0xfa, 0xbb, 0xe4, 0xae, 0x34, 0x51, 0x54,
|
||||
0x34, 0x16, 0xac, 0x94, 0x5d, 0x65, 0xa5, 0xe4, 0x94, 0x5c, 0x9d, 0xf4, 0x6f, 0x27, 0x89, 0x33, 0x57, 0xc7, 0x25,
|
||||
0x89, 0xdb, 0xf6, 0x5b, 0xae, 0x23, 0xf3, 0x00, 0xe0, 0xd6, 0x76, 0x57, 0x7e, 0xde, 0xd6, 0xed, 0x83, 0xa6, 0x35,
|
||||
0x1f, 0x4b, 0xcd, 0xee, 0x65, 0x78, 0x47, 0xb3, 0xcb, 0x8e, 0xeb, 0x80, 0x9f, 0xa6, 0xa9, 0x52, 0x26, 0x64, 0x99,
|
||||
0xd3, 0x71, 0x9d, 0xdb, 0x49, 0x92, 0xe6, 0xc4, 0x8d, 0x85, 0x98, 0x06, 0xea, 0xfb, 0xb7, 0x37, 0x07, 0x3e, 0xcf,
|
||||
0xc6, 0xfb, 0x9d, 0x56, 0xab, 0x05, 0x17, 0xc0, 0xba, 0xce, 0x9c, 0xd1, 0x9b, 0xa7, 0xfc, 0x96, 0xb8, 0x2d, 0xa7,
|
||||
0xe5, 0xb4, 0x3b, 0xc7, 0x4e, 0xbb, 0x73, 0xe8, 0x3f, 0x3a, 0x76, 0x7b, 0x9f, 0x39, 0xce, 0x49, 0x44, 0x47, 0x39,
|
||||
0xfc, 0x70, 0x9c, 0x13, 0xa9, 0x78, 0xa9, 0xdf, 0x8e, 0xe3, 0x0f, 0x93, 0xbc, 0xd9, 0x76, 0x16, 0xfa, 0xd1, 0x71,
|
||||
0xe0, 0x50, 0x69, 0xe0, 0x7c, 0x3e, 0xea, 0x8c, 0x0e, 0x47, 0x4f, 0xba, 0xba, 0xb8, 0xf8, 0xac, 0x56, 0x1d, 0xab,
|
||||
0xff, 0x3b, 0x56, 0xb3, 0x5c, 0x64, 0xfc, 0x23, 0xd5, 0x39, 0x89, 0x0e, 0x88, 0x9e, 0x8d, 0x4d, 0x3b, 0xeb, 0x23,
|
||||
0xb5, 0x8f, 0xaf, 0x87, 0xa3, 0x4e, 0x55, 0x5d, 0xc2, 0xb8, 0x5f, 0x02, 0x79, 0xb2, 0x6f, 0x40, 0x3f, 0xb1, 0xd1,
|
||||
0xd4, 0x6e, 0x6e, 0x42, 0x54, 0xdb, 0xd5, 0x73, 0x1c, 0x9b, 0xf9, 0x9d, 0xc0, 0x19, 0x06, 0xa3, 0xab, 0x4a, 0x08,
|
||||
0x5c, 0x27, 0x22, 0xee, 0xab, 0x76, 0xe7, 0x18, 0xb7, 0xdb, 0x8f, 0xfc, 0x47, 0xc7, 0xc3, 0x16, 0x3e, 0xf4, 0x0f,
|
||||
0x9b, 0x07, 0xfe, 0x23, 0x7c, 0xdc, 0x3c, 0xc6, 0xc7, 0x2f, 0x8e, 0x87, 0xcd, 0x43, 0xff, 0x10, 0xb7, 0x9a, 0xc7,
|
||||
0x50, 0xd8, 0x3c, 0x6e, 0x1e, 0xcf, 0x9b, 0x87, 0xc7, 0xc3, 0x96, 0x2c, 0xed, 0xf8, 0x47, 0x47, 0xcd, 0x76, 0xcb,
|
||||
0x3f, 0x3a, 0xc2, 0x47, 0xfe, 0xa3, 0x47, 0xcd, 0xf6, 0x81, 0xff, 0xe8, 0xd1, 0xcb, 0xa3, 0x63, 0xff, 0x00, 0xde,
|
||||
0x1d, 0x1c, 0x0c, 0x0f, 0xfc, 0x76, 0xbb, 0x09, 0xff, 0xe0, 0x63, 0xbf, 0xa3, 0x7e, 0xb4, 0xdb, 0xfe, 0x41, 0x1b,
|
||||
0xb7, 0x92, 0xa3, 0x8e, 0xff, 0xe8, 0x09, 0x96, 0xff, 0xca, 0x6a, 0x58, 0xfe, 0x03, 0xdd, 0xe0, 0x27, 0x7e, 0xe7,
|
||||
0x91, 0xfa, 0x25, 0x3b, 0x9c, 0x1f, 0x1e, 0xff, 0xe0, 0xee, 0x6f, 0x9d, 0x43, 0x5b, 0xcd, 0xe1, 0xf8, 0xc8, 0x3f,
|
||||
0x38, 0xc0, 0x87, 0x6d, 0xff, 0xf8, 0x20, 0x6e, 0x1e, 0x76, 0xfc, 0x47, 0x8f, 0x87, 0xcd, 0xb6, 0xff, 0xf8, 0x31,
|
||||
0x6e, 0x35, 0x0f, 0xfc, 0x0e, 0x6e, 0xfb, 0x87, 0x07, 0xf2, 0xc7, 0x81, 0xdf, 0x99, 0x3f, 0x7e, 0xe2, 0x3f, 0x3a,
|
||||
0x8a, 0x1f, 0xf9, 0x87, 0xdf, 0x1e, 0x1e, 0xfb, 0x9d, 0x83, 0xf8, 0xe0, 0x91, 0xdf, 0x79, 0x3c, 0x7f, 0xe4, 0x1f,
|
||||
0xc6, 0xcd, 0xce, 0xa3, 0x7b, 0x5b, 0xb6, 0x3b, 0x3e, 0xe0, 0x48, 0xbe, 0x86, 0x17, 0x58, 0xbf, 0x80, 0xbf, 0xb1,
|
||||
0x6c, 0xfb, 0xef, 0xd8, 0x4d, 0xbe, 0xde, 0xf4, 0x89, 0x7f, 0xfc, 0x78, 0xa8, 0xaa, 0x43, 0x41, 0xd3, 0xd4, 0x80,
|
||||
0x26, 0xf3, 0xa6, 0x1a, 0x56, 0x76, 0xd7, 0x34, 0x1d, 0x99, 0xbf, 0x7a, 0xb0, 0x79, 0x13, 0x06, 0x56, 0xe3, 0xfe,
|
||||
0x87, 0xf6, 0x53, 0x2e, 0xf9, 0xc9, 0xfe, 0x58, 0x91, 0xfe, 0xb8, 0xf7, 0x99, 0xba, 0xdd, 0xf9, 0xb3, 0x2b, 0x9c,
|
||||
0x6e, 0x73, 0x7c, 0x64, 0x9f, 0x76, 0x7c, 0x70, 0xfa, 0x10, 0xcf, 0x47, 0xf6, 0x87, 0x7b, 0x3e, 0x52, 0xba, 0xe2,
|
||||
0x38, 0xbf, 0x16, 0x6b, 0x0e, 0x8e, 0x55, 0xab, 0xf8, 0xa9, 0xf0, 0x06, 0x39, 0x7c, 0x47, 0xac, 0xe8, 0x5e, 0x0b,
|
||||
0xc2, 0xa9, 0xed, 0x07, 0xe2, 0xc0, 0x62, 0xaf, 0x85, 0xe2, 0xb1, 0xc9, 0x36, 0x84, 0x84, 0x9f, 0x46, 0xc8, 0xb7,
|
||||
0x0f, 0xc1, 0x47, 0xf8, 0x87, 0xe3, 0x23, 0xb1, 0xf1, 0x51, 0xf3, 0xe5, 0x4b, 0x4f, 0x83, 0xf4, 0x14, 0x9c, 0xcb,
|
||||
0x67, 0x0f, 0x0e, 0x51, 0x35, 0xdc, 0x7d, 0x0a, 0x45, 0xb9, 0xab, 0x22, 0x5f, 0xef, 0x7e, 0x4d, 0xd8, 0x41, 0x9d,
|
||||
0x98, 0x24, 0xae, 0x76, 0xcb, 0x4c, 0xa5, 0xd4, 0xd1, 0x0f, 0xa5, 0x50, 0xea, 0xf8, 0x2d, 0xbf, 0x55, 0xba, 0x74,
|
||||
0xe0, 0x94, 0x2c, 0x59, 0x70, 0x11, 0xc2, 0x17, 0x6b, 0x13, 0x3e, 0x96, 0xdf, 0xb6, 0x85, 0xaf, 0x09, 0x40, 0xd2,
|
||||
0xcf, 0x50, 0x7d, 0xc8, 0x21, 0x70, 0x5d, 0x7d, 0xb7, 0x06, 0x9c, 0xc2, 0xfc, 0x06, 0x4e, 0xaa, 0x9a, 0xa8, 0xc4,
|
||||
0x04, 0xbc, 0x1d, 0xaf, 0x68, 0xc4, 0x42, 0xcf, 0xf5, 0xa6, 0x19, 0x1d, 0xd1, 0x2c, 0x6f, 0xd6, 0x8e, 0x6f, 0xca,
|
||||
0x93, 0x9b, 0xc8, 0x35, 0x9f, 0x46, 0xcd, 0xe0, 0x76, 0x6c, 0x32, 0xd0, 0xfe, 0x46, 0x57, 0x1b, 0x60, 0x6e, 0x81,
|
||||
0x4d, 0x49, 0x06, 0xb2, 0xb6, 0x52, 0xda, 0x5c, 0xa5, 0xb5, 0xb5, 0xfd, 0xce, 0x11, 0x72, 0x64, 0x31, 0xdc, 0x3b,
|
||||
0xfc, 0xbd, 0xd7, 0x3c, 0x68, 0xfd, 0x09, 0x59, 0xcd, 0xca, 0x8e, 0x2e, 0xb4, 0xbb, 0x2d, 0xad, 0xbe, 0x29, 0x5d,
|
||||
0x3f, 0x5b, 0xeb, 0x2a, 0x8a, 0xf8, 0x5c, 0xcd, 0xdd, 0x45, 0xdd, 0x54, 0x47, 0xb8, 0xd5, 0x0d, 0x11, 0x23, 0x36,
|
||||
0xf6, 0xec, 0x2f, 0x06, 0xab, 0x7b, 0x8d, 0xe5, 0x87, 0xc6, 0x51, 0x51, 0x55, 0x49, 0xd1, 0x42, 0xc6, 0x5b, 0x58,
|
||||
0xea, 0xa4, 0xcb, 0xa5, 0x97, 0x82, 0x8b, 0x9c, 0x58, 0x38, 0x85, 0x67, 0x54, 0x43, 0x72, 0x8a, 0x4b, 0x80, 0x24,
|
||||
0x82, 0x49, 0xaa, 0xfe, 0xaf, 0x8a, 0xcd, 0x0f, 0xed, 0xf8, 0xf2, 0x93, 0x30, 0x1d, 0x03, 0x15, 0x86, 0xe9, 0x78,
|
||||
0xcd, 0xad, 0xa6, 0x42, 0x46, 0x2b, 0xa5, 0x55, 0x57, 0x95, 0xfb, 0x2c, 0x7f, 0x7a, 0xf7, 0x5e, 0x5f, 0x80, 0xe6,
|
||||
0x82, 0x77, 0x5a, 0x46, 0x38, 0xaa, 0xcb, 0x9a, 0x1b, 0xe4, 0x8b, 0x93, 0x09, 0x15, 0xa1, 0xca, 0xd7, 0x04, 0x7d,
|
||||
0x02, 0x4e, 0xcd, 0x3a, 0xda, 0x1a, 0x25, 0xae, 0x94, 0xee, 0x24, 0xa2, 0x73, 0x36, 0xd4, 0xa2, 0x1e, 0x3b, 0xfa,
|
||||
0xe6, 0x80, 0xa6, 0x5c, 0x1a, 0xd2, 0xc6, 0xca, 0x1f, 0x33, 0x0c, 0x65, 0x46, 0x3e, 0x49, 0xb9, 0xdb, 0xfb, 0xa2,
|
||||
0xfc, 0xfa, 0xe9, 0xb6, 0x45, 0x48, 0x58, 0xfa, 0x71, 0x90, 0xd1, 0xe4, 0x9f, 0xc8, 0x17, 0x6c, 0xc8, 0xd3, 0x2f,
|
||||
0x2e, 0xe0, 0xab, 0xf4, 0x7e, 0x9c, 0xd1, 0x11, 0xf9, 0x02, 0x64, 0x7c, 0x20, 0xad, 0x0f, 0x60, 0x84, 0x8d, 0xdb,
|
||||
0x49, 0x82, 0xa5, 0xc6, 0xf4, 0x00, 0x85, 0x48, 0x81, 0xeb, 0x76, 0x8e, 0x5c, 0x47, 0xd9, 0xc4, 0xf2, 0x77, 0x4f,
|
||||
0x89, 0x53, 0xa9, 0x04, 0x38, 0xed, 0x8e, 0x7f, 0x14, 0x77, 0xfc, 0x27, 0xf3, 0xc7, 0xfe, 0x71, 0xdc, 0x7e, 0x3c,
|
||||
0x6f, 0xc2, 0xff, 0x1d, 0xff, 0x49, 0xd2, 0xec, 0xf8, 0x4f, 0xe0, 0xef, 0xb7, 0x87, 0xfe, 0x51, 0xdc, 0x6c, 0xfb,
|
||||
0xc7, 0xf3, 0x03, 0xff, 0xe0, 0x65, 0xbb, 0xe3, 0x1f, 0x38, 0x6d, 0x47, 0xb5, 0x03, 0x76, 0xad, 0xb8, 0xf3, 0x17,
|
||||
0x2b, 0x1b, 0x62, 0x43, 0x38, 0x4e, 0xe5, 0x9c, 0xba, 0xd8, 0x2b, 0xbf, 0xb1, 0xa8, 0xf7, 0xa7, 0x76, 0xd6, 0x3d,
|
||||
0x0b, 0x33, 0xf8, 0xd0, 0x4d, 0x7d, 0xef, 0xd6, 0xde, 0xe1, 0x1a, 0xbf, 0xd8, 0x30, 0x04, 0xec, 0x70, 0x17, 0xdb,
|
||||
0x47, 0xef, 0xe1, 0xdc, 0xba, 0xbc, 0x17, 0xdc, 0x5c, 0x8f, 0xb8, 0x9d, 0xb4, 0x55, 0x45, 0x73, 0x05, 0xa3, 0x64,
|
||||
0x16, 0x4c, 0x7e, 0x81, 0x41, 0x0e, 0xf2, 0x55, 0x54, 0xac, 0x8e, 0x0f, 0xa9, 0xaf, 0x19, 0xb7, 0x6e, 0x1f, 0xa0,
|
||||
0xd5, 0x81, 0x8d, 0x88, 0xc1, 0x7d, 0x11, 0x45, 0x61, 0x40, 0xaf, 0xb9, 0x69, 0x2b, 0x2c, 0x49, 0x7e, 0x41, 0xf3,
|
||||
0xbe, 0x0b, 0x45, 0x6e, 0xe0, 0x4a, 0x17, 0x9f, 0x5b, 0x7e, 0xec, 0xa7, 0x24, 0xec, 0xaa, 0x00, 0xcb, 0x43, 0x57,
|
||||
0xb0, 0x6b, 0x01, 0x3f, 0x2e, 0xda, 0xdb, 0xdb, 0xba, 0x5f, 0xa4, 0x02, 0x09, 0x73, 0xad, 0xbe, 0x11, 0x62, 0xb3,
|
||||
0x22, 0xd7, 0x46, 0x74, 0xd9, 0xaf, 0x44, 0x21, 0xd2, 0x78, 0xba, 0xa6, 0xa1, 0xf0, 0xc3, 0x54, 0x25, 0xd1, 0x58,
|
||||
0x0c, 0x0b, 0xb7, 0xe9, 0x01, 0x2a, 0xb8, 0x08, 0xad, 0xef, 0x00, 0xeb, 0x7d, 0xce, 0x45, 0x68, 0xce, 0xd2, 0x5a,
|
||||
0xd7, 0x06, 0x81, 0xa3, 0x37, 0xee, 0xf4, 0xde, 0xbc, 0x3f, 0x75, 0xd4, 0xf6, 0x3c, 0xd9, 0x8f, 0x3b, 0xbd, 0x13,
|
||||
0xe9, 0x33, 0x51, 0x27, 0xf1, 0x88, 0x3a, 0x89, 0xe7, 0xe8, 0x53, 0x99, 0x10, 0x49, 0x2b, 0xf6, 0xd5, 0xb4, 0xa5,
|
||||
0xcd, 0xa0, 0xbc, 0xbd, 0x93, 0x59, 0x22, 0x18, 0xdc, 0x71, 0xbd, 0x2f, 0x8f, 0xe1, 0xc1, 0x82, 0x95, 0x79, 0xd8,
|
||||
0x5a, 0x3b, 0xbc, 0x16, 0xa9, 0xf1, 0x0d, 0x8f, 0x58, 0x42, 0x4d, 0xe6, 0xb5, 0xee, 0xaa, 0x3c, 0x29, 0xb0, 0x5e,
|
||||
0x3b, 0x9f, 0x5d, 0x4f, 0x98, 0x70, 0xcd, 0x79, 0x86, 0x0f, 0xba, 0xc1, 0x89, 0x1c, 0xaa, 0x77, 0x55, 0x68, 0xe7,
|
||||
0xb5, 0xf9, 0x9a, 0x4f, 0x7d, 0x49, 0xf5, 0xec, 0xb5, 0x84, 0x80, 0x13, 0x72, 0xf1, 0x41, 0xaf, 0x74, 0x17, 0xdb,
|
||||
0xef, 0x8a, 0x93, 0xfd, 0xf8, 0xa0, 0x77, 0x15, 0x4c, 0x75, 0x7f, 0x2f, 0xf9, 0x78, 0x73, 0x5f, 0x09, 0x1f, 0xf7,
|
||||
0xe5, 0x51, 0x10, 0x75, 0x48, 0xd9, 0x28, 0xbf, 0x3c, 0x71, 0x7b, 0x27, 0x5a, 0x19, 0x70, 0x64, 0x60, 0xdd, 0x3d,
|
||||
0x6a, 0x99, 0xd3, 0x25, 0x09, 0x1f, 0xc3, 0x86, 0x54, 0x4d, 0xac, 0x41, 0x6a, 0x1e, 0xf7, 0xb8, 0xdd, 0x3b, 0x09,
|
||||
0x1d, 0xc9, 0x5b, 0x24, 0xf3, 0xc8, 0x83, 0x7d, 0x68, 0x1c, 0xf3, 0x09, 0xf5, 0x19, 0xdf, 0xbf, 0xa1, 0xd7, 0xcd,
|
||||
0x70, 0xca, 0x2a, 0xf7, 0x36, 0x28, 0x1d, 0xe5, 0x90, 0xdc, 0x78, 0xc4, 0xf5, 0xd9, 0xab, 0x4e, 0xe5, 0x6e, 0x3b,
|
||||
0x04, 0x9b, 0xc7, 0xb8, 0xe6, 0xa4, 0x4f, 0xce, 0x02, 0x8b, 0xf7, 0x4e, 0xf6, 0xc3, 0x15, 0x8c, 0x48, 0x7e, 0x5f,
|
||||
0x68, 0x47, 0x3b, 0x18, 0x36, 0x40, 0x6f, 0xae, 0xa3, 0xc4, 0x81, 0x71, 0xc8, 0x6b, 0x41, 0x5d, 0xb8, 0xbd, 0x7f,
|
||||
0xfd, 0x1f, 0xff, 0x4b, 0xfb, 0xd8, 0x4f, 0xf6, 0xe3, 0xb6, 0xe9, 0x6b, 0x65, 0x55, 0x8a, 0x13, 0x38, 0xee, 0x59,
|
||||
0x05, 0x85, 0xe9, 0x6d, 0x73, 0x9c, 0xb1, 0xa8, 0x19, 0x87, 0xc9, 0xc8, 0xed, 0x6d, 0xc7, 0xa6, 0x7d, 0x6c, 0x4b,
|
||||
0x43, 0x5d, 0x2f, 0x02, 0x7a, 0xfd, 0x4d, 0x07, 0x8f, 0xcc, 0xf9, 0x15, 0xb9, 0xb5, 0xed, 0x63, 0x48, 0xd5, 0xee,
|
||||
0xab, 0x1d, 0x45, 0x4a, 0xf5, 0x27, 0xc2, 0x34, 0x07, 0x4c, 0x6b, 0x27, 0x90, 0x0a, 0xd7, 0x29, 0x83, 0x5a, 0xff,
|
||||
0xf7, 0x7f, 0xfe, 0x97, 0xff, 0x66, 0x1e, 0x21, 0x56, 0xf5, 0xaf, 0xff, 0xfd, 0x3f, 0xff, 0x9f, 0xff, 0xfd, 0x5f,
|
||||
0xe1, 0xd4, 0x8a, 0x8e, 0x67, 0x49, 0xa6, 0xe2, 0x54, 0xc1, 0x2c, 0xc5, 0x5d, 0x1c, 0x48, 0xec, 0x9c, 0xb0, 0x5c,
|
||||
0xb0, 0x61, 0xfd, 0x4c, 0xd2, 0xb9, 0x1c, 0x50, 0xee, 0x4c, 0x0d, 0x9d, 0xdc, 0xe1, 0x45, 0x45, 0x50, 0x35, 0x94,
|
||||
0x4b, 0xc2, 0x2d, 0x4e, 0xf6, 0x01, 0xdf, 0x0f, 0x3b, 0xc6, 0xe9, 0x97, 0xcb, 0xb1, 0x30, 0x64, 0x02, 0x25, 0x45,
|
||||
0x55, 0xee, 0x40, 0x6c, 0x65, 0x01, 0x8f, 0x41, 0xc7, 0x2a, 0x96, 0xab, 0x57, 0x6b, 0xd3, 0xfd, 0x69, 0x96, 0x0b,
|
||||
0x36, 0x02, 0x94, 0x2b, 0x3f, 0xb1, 0x0c, 0x63, 0x37, 0x41, 0x57, 0x4c, 0xee, 0x0a, 0xd9, 0x8b, 0x22, 0xd0, 0xc3,
|
||||
0xe3, 0x3f, 0x15, 0x7f, 0x99, 0x80, 0x46, 0xe6, 0x78, 0x93, 0xf0, 0x56, 0x9b, 0xe7, 0x8f, 0x5a, 0xad, 0xe9, 0x2d,
|
||||
0x5a, 0x54, 0x23, 0xe0, 0x6d, 0x83, 0x49, 0x3a, 0xb6, 0x3b, 0x94, 0xf1, 0xef, 0xd2, 0x8d, 0xdd, 0x72, 0xc0, 0x17,
|
||||
0xee, 0xb4, 0x8a, 0xe2, 0xcf, 0x0b, 0xe9, 0x49, 0x65, 0xbf, 0x40, 0x9c, 0x5a, 0x3b, 0x9d, 0xaf, 0xb9, 0x3d, 0xb9,
|
||||
0x85, 0xd5, 0xaa, 0xa3, 0x5a, 0xc5, 0xed, 0xf5, 0xd3, 0x89, 0x76, 0x9c, 0xdd, 0x8e, 0x90, 0x1f, 0x42, 0xcc, 0x3b,
|
||||
0x6e, 0xe3, 0xb8, 0xb3, 0x28, 0xbb, 0x17, 0x82, 0x4f, 0xec, 0xc0, 0x3a, 0x0d, 0xe9, 0x90, 0x8e, 0x8c, 0xb3, 0x5e,
|
||||
0xbf, 0x57, 0x41, 0xf3, 0x22, 0x3e, 0xd8, 0x30, 0x96, 0x06, 0x49, 0x06, 0xd4, 0x9d, 0x56, 0xf1, 0x39, 0xec, 0xc0,
|
||||
0xc5, 0x28, 0xe1, 0xa1, 0x08, 0x24, 0xc1, 0x76, 0xed, 0xf0, 0x7c, 0x08, 0x3c, 0x89, 0x2f, 0x2c, 0x78, 0xba, 0xaa,
|
||||
0x2a, 0xb8, 0xcd, 0xeb, 0x67, 0x48, 0x0b, 0x5f, 0x36, 0xb7, 0xbb, 0x52, 0x5e, 0xb7, 0x6f, 0x75, 0xd4, 0xfb, 0x5d,
|
||||
0xcd, 0x5d, 0xa5, 0x05, 0x52, 0x07, 0x6d, 0x7e, 0xaf, 0xe4, 0xba, 0x7a, 0xfb, 0xb5, 0xf0, 0x5c, 0x09, 0xa6, 0xbb,
|
||||
0x5a, 0x4b, 0x16, 0x42, 0xad, 0x77, 0xe4, 0xdb, 0xd2, 0x64, 0x0a, 0xa7, 0x53, 0x59, 0x11, 0x75, 0x4f, 0xf6, 0x95,
|
||||
0xa6, 0x0b, 0xdc, 0x43, 0xa6, 0x74, 0xa8, 0x0c, 0x0a, 0x5d, 0x49, 0x6f, 0x05, 0xf5, 0x4b, 0xe7, 0x56, 0xc0, 0xa7,
|
||||
0xe3, 0x7a, 0xff, 0x0f, 0x82, 0x7a, 0x0b, 0xa7, 0xcf, 0x89, 0x00, 0x00};
|
||||
0x65, 0x7c, 0xed, 0x25, 0x00, 0x5b, 0x3e, 0xbd, 0x0f, 0xc7, 0xe5, 0xef, 0x57, 0x34, 0xcf, 0xc3, 0xb1, 0xae, 0xb9,
|
||||
0x3d, 0x9e, 0x26, 0x41, 0xb4, 0x63, 0x69, 0x06, 0x08, 0x88, 0x89, 0x01, 0x46, 0xc0, 0xa7, 0xa1, 0x43, 0x64, 0x30,
|
||||
0xf5, 0x7a, 0x74, 0x4d, 0xe2, 0xaa, 0x5e, 0x24, 0xc2, 0x71, 0x55, 0x70, 0x32, 0xcd, 0xa8, 0x2c, 0x55, 0x68, 0x2c,
|
||||
0x4e, 0xf6, 0xa1, 0x40, 0xbd, 0xde, 0x12, 0x45, 0x33, 0x0e, 0x94, 0xed, 0xb1, 0x34, 0xc7, 0x44, 0xd1, 0xec, 0x44,
|
||||
0xa5, 0x32, 0x4b, 0x69, 0x3d, 0x76, 0xf3, 0x79, 0x7b, 0x08, 0x7f, 0x74, 0x64, 0xe8, 0xf3, 0xd1, 0x68, 0x74, 0x6f,
|
||||
0x54, 0xed, 0xf3, 0x68, 0x44, 0x3b, 0xf4, 0xa8, 0x0b, 0x49, 0x2c, 0x4d, 0x1d, 0x8b, 0x69, 0x17, 0x12, 0x77, 0x8b,
|
||||
0x87, 0x55, 0x86, 0xb0, 0x8d, 0x88, 0x17, 0x0f, 0x8f, 0xb0, 0x15, 0xd3, 0x8c, 0x2e, 0x26, 0x61, 0x36, 0x66, 0x69,
|
||||
0xd0, 0x2a, 0xfc, 0xb9, 0x0e, 0x49, 0x7d, 0x7e, 0x7c, 0x7c, 0x5c, 0xf8, 0x91, 0x79, 0x6a, 0x45, 0x51, 0xe1, 0x0f,
|
||||
0x17, 0xe5, 0x34, 0x5a, 0xad, 0xd1, 0xa8, 0xf0, 0x99, 0x29, 0x38, 0xe8, 0x0c, 0xa3, 0x83, 0x4e, 0xe1, 0xdf, 0x58,
|
||||
0x35, 0x0a, 0x9f, 0xea, 0xa7, 0x8c, 0x46, 0xb5, 0x4c, 0x98, 0xc7, 0xad, 0x56, 0xe1, 0x2b, 0x42, 0x5b, 0x80, 0x59,
|
||||
0xaa, 0x7e, 0x06, 0xe1, 0x4c, 0x70, 0x60, 0xee, 0xdd, 0x44, 0x78, 0x83, 0x4b, 0x7d, 0xcb, 0x88, 0xfa, 0x26, 0x47,
|
||||
0x81, 0x2e, 0xf0, 0xcf, 0x76, 0xf0, 0x08, 0x88, 0x59, 0x06, 0x8d, 0x12, 0x13, 0x5b, 0xaa, 0xbd, 0x06, 0xca, 0x92,
|
||||
0xaf, 0x7f, 0x26, 0x49, 0x15, 0x53, 0x02, 0x4e, 0x06, 0x35, 0xd5, 0x65, 0x78, 0x94, 0x6e, 0x91, 0x1f, 0xec, 0xd3,
|
||||
0xf2, 0xe3, 0xee, 0x21, 0xe2, 0x83, 0xfd, 0xe1, 0xe2, 0x83, 0x52, 0x4b, 0x7c, 0x28, 0xe6, 0x71, 0x27, 0x88, 0x3b,
|
||||
0x8c, 0xe9, 0xf0, 0xe3, 0x35, 0xbf, 0x6d, 0xc2, 0x96, 0xc8, 0x5c, 0x29, 0x58, 0x76, 0x7f, 0x6b, 0xd6, 0x8c, 0xe9,
|
||||
0xcc, 0xfa, 0xa2, 0x87, 0x54, 0x1f, 0xde, 0xa4, 0xc4, 0x7d, 0x63, 0x6c, 0x5b, 0x55, 0x32, 0x1a, 0x11, 0xf7, 0xcd,
|
||||
0x68, 0xe4, 0x9a, 0xb3, 0x92, 0xa1, 0xa0, 0xb2, 0xd6, 0xeb, 0x5a, 0x89, 0xac, 0xf5, 0xe5, 0x97, 0x76, 0x99, 0x5d,
|
||||
0xa0, 0x43, 0x4f, 0x76, 0x98, 0x49, 0xbf, 0x89, 0x58, 0x0e, 0x5b, 0x0d, 0x3e, 0x34, 0x52, 0xbf, 0xab, 0x31, 0xad,
|
||||
0x5d, 0xab, 0x5d, 0x02, 0xbc, 0xe1, 0x2e, 0xf0, 0xd5, 0x8b, 0x02, 0xc6, 0xd4, 0xe4, 0x2d, 0x3e, 0xbd, 0xfb, 0x2a,
|
||||
0xf2, 0xee, 0x04, 0x2a, 0x58, 0xfe, 0x26, 0x5d, 0x39, 0x04, 0xa4, 0x60, 0x24, 0xc4, 0x9e, 0x56, 0x21, 0xf8, 0x78,
|
||||
0x9c, 0xc0, 0xb7, 0x5e, 0x16, 0xb5, 0xfb, 0x63, 0x55, 0xf3, 0x7e, 0x6d, 0xbe, 0x81, 0xdd, 0x50, 0xdf, 0xb6, 0x2a,
|
||||
0x3f, 0x3d, 0xa5, 0x92, 0xc7, 0xe7, 0xfa, 0x1b, 0x44, 0xd2, 0x2c, 0x5e, 0x68, 0x26, 0xbf, 0x50, 0x29, 0xc7, 0x02,
|
||||
0xd2, 0x6d, 0x54, 0xc7, 0x51, 0x51, 0xe8, 0xc3, 0x1a, 0x11, 0xcb, 0xa7, 0x70, 0xaf, 0xa9, 0x6a, 0x49, 0x3f, 0xc5,
|
||||
0xc2, 0xf3, 0x1b, 0x2b, 0xbe, 0x53, 0x5b, 0xae, 0xc2, 0x04, 0x78, 0x94, 0xc3, 0xfc, 0x4e, 0x14, 0xae, 0xf6, 0xbb,
|
||||
0x1b, 0x24, 0xba, 0x8e, 0xc2, 0xa7, 0x8a, 0x3c, 0x59, 0x33, 0x04, 0xe7, 0x77, 0xb9, 0x20, 0xe6, 0x95, 0x29, 0x28,
|
||||
0xec, 0xf8, 0xa5, 0x7c, 0xa3, 0xb0, 0x25, 0xa3, 0x25, 0xf9, 0x34, 0x4c, 0x15, 0x1b, 0x25, 0xae, 0xe2, 0x07, 0xbb,
|
||||
0x8b, 0x6a, 0xe5, 0x0b, 0xd7, 0x80, 0xad, 0x88, 0xb7, 0x77, 0xb2, 0x0f, 0x0d, 0x7a, 0x4e, 0x0d, 0xf4, 0x74, 0x2d,
|
||||
0xc8, 0xf2, 0x89, 0x74, 0x87, 0x2b, 0x3f, 0xbf, 0xc1, 0x7e, 0x7e, 0xe3, 0xfc, 0x79, 0xd1, 0xbc, 0xa1, 0xd7, 0x1f,
|
||||
0x99, 0x68, 0x8a, 0x70, 0xda, 0x04, 0xc3, 0x47, 0x3a, 0x47, 0x35, 0x7b, 0x96, 0x59, 0x7e, 0xea, 0xaa, 0x83, 0xee,
|
||||
0x2c, 0x87, 0xac, 0x08, 0xa9, 0xbe, 0x07, 0x29, 0x4f, 0x69, 0xb7, 0x9e, 0xcd, 0x69, 0x07, 0xd9, 0x0d, 0xb6, 0x2e,
|
||||
0x16, 0x1c, 0xb2, 0x28, 0xc4, 0x5d, 0xd0, 0xd2, 0x6c, 0xbd, 0x65, 0x22, 0xe8, 0xad, 0x8d, 0xf5, 0x03, 0x8d, 0xdc,
|
||||
0x86, 0x94, 0x5e, 0xd9, 0x7a, 0x26, 0xc1, 0xb6, 0x4c, 0x80, 0x4f, 0xe5, 0x36, 0x82, 0x4b, 0xd5, 0xfc, 0xb5, 0x92,
|
||||
0x42, 0x57, 0x8b, 0x65, 0x6e, 0xe3, 0x43, 0x20, 0x0b, 0xc2, 0x91, 0xa0, 0x19, 0x7e, 0x48, 0xcd, 0x6b, 0x79, 0x0c,
|
||||
0x69, 0x01, 0x62, 0x26, 0x68, 0x1f, 0x4f, 0x6f, 0x1f, 0xde, 0xfd, 0xfd, 0xd3, 0x2f, 0x34, 0x8e, 0xcc, 0xb5, 0x3c,
|
||||
0xae, 0xdb, 0x85, 0x8d, 0x90, 0x84, 0x77, 0x01, 0x4b, 0xa5, 0xcc, 0xbb, 0x06, 0xbf, 0x68, 0x77, 0xca, 0x75, 0x92,
|
||||
0x6e, 0x46, 0x13, 0xf9, 0x15, 0x3e, 0xbd, 0x14, 0x07, 0x8f, 0xa6, 0xb7, 0x66, 0x35, 0xda, 0x2b, 0xc9, 0xb7, 0x7f,
|
||||
0x68, 0x8e, 0xed, 0xf6, 0xa4, 0xde, 0x7a, 0x9e, 0xe8, 0xd1, 0xf4, 0xb6, 0xab, 0x04, 0x6d, 0x33, 0x53, 0x50, 0xb5,
|
||||
0xa6, 0xb7, 0x76, 0x96, 0x71, 0xd5, 0x91, 0xe3, 0x1f, 0xe4, 0x0e, 0x0d, 0x73, 0xda, 0x85, 0x7b, 0xc7, 0xd9, 0x30,
|
||||
0x4c, 0xb4, 0x30, 0x9f, 0xb0, 0x28, 0x4a, 0x68, 0xd7, 0xc8, 0x6b, 0xa7, 0xfd, 0x08, 0x92, 0x74, 0xed, 0x25, 0xab,
|
||||
0xaf, 0x8a, 0x85, 0xbc, 0x12, 0x4f, 0xe1, 0x75, 0xce, 0x13, 0xf8, 0xe8, 0xc7, 0x46, 0x74, 0xea, 0xec, 0xd5, 0x56,
|
||||
0x85, 0x3c, 0xf9, 0xbb, 0x3e, 0x97, 0xa3, 0xd6, 0x9f, 0xba, 0x72, 0xc1, 0x5b, 0x5d, 0xc1, 0xa7, 0x41, 0xf3, 0xa0,
|
||||
0x3e, 0x11, 0x78, 0x55, 0x4e, 0x01, 0x6f, 0x98, 0x16, 0x06, 0x69, 0xa5, 0xf8, 0xb4, 0xe3, 0xb7, 0x75, 0x99, 0xec,
|
||||
0x00, 0xf2, 0xc2, 0xca, 0xa2, 0xa2, 0x3e, 0x99, 0x7f, 0x9b, 0xdd, 0xf2, 0x64, 0xf3, 0x6e, 0x79, 0x62, 0x76, 0xcb,
|
||||
0xfd, 0x14, 0xfb, 0xf9, 0xa8, 0x0d, 0x7f, 0xba, 0xd5, 0x84, 0x82, 0x96, 0x73, 0x30, 0xbd, 0x75, 0x40, 0x4f, 0x6b,
|
||||
0x76, 0xa6, 0xb7, 0x2a, 0xc7, 0x1a, 0x62, 0x37, 0x2d, 0xc8, 0x3a, 0xc6, 0x2d, 0x07, 0x0a, 0xe1, 0x6f, 0xab, 0xf6,
|
||||
0xaa, 0x7d, 0x08, 0xef, 0xa0, 0xd5, 0xd1, 0xfa, 0xbb, 0xce, 0xfd, 0x9b, 0x36, 0x48, 0xb9, 0xf0, 0x02, 0xc3, 0x8d,
|
||||
0x91, 0x2f, 0xc2, 0xeb, 0x6b, 0x1a, 0x05, 0x23, 0x3e, 0x9c, 0xe5, 0xff, 0xa4, 0xe1, 0xd7, 0x48, 0xbc, 0x77, 0x4b,
|
||||
0xaf, 0xf4, 0x63, 0x9a, 0xaa, 0x8c, 0x6f, 0xd3, 0xc3, 0xa2, 0x5c, 0xa7, 0x20, 0x1f, 0x86, 0x09, 0xf5, 0x3a, 0xfe,
|
||||
0xe1, 0x86, 0x4d, 0xf0, 0xef, 0xb2, 0x36, 0x1b, 0x27, 0xf3, 0x7b, 0x91, 0x71, 0x2f, 0x12, 0x7e, 0x15, 0x0e, 0xec,
|
||||
0x35, 0x6c, 0x1d, 0x6f, 0x06, 0x77, 0x60, 0x46, 0xba, 0x30, 0x42, 0x41, 0xcb, 0x9d, 0x88, 0x8e, 0xc2, 0x59, 0x22,
|
||||
0xee, 0xef, 0x75, 0x1b, 0x65, 0xac, 0xf5, 0x7a, 0x0f, 0x43, 0xaf, 0xea, 0x3e, 0x90, 0x4b, 0x7f, 0xfe, 0xe4, 0x10,
|
||||
0xfe, 0xa8, 0xfc, 0xaf, 0xbb, 0x4a, 0x57, 0x57, 0x76, 0x2f, 0xe8, 0xea, 0xbb, 0x35, 0x65, 0x5c, 0x89, 0x70, 0xa9,
|
||||
0x8f, 0x3f, 0xb4, 0x36, 0x68, 0x95, 0x0f, 0xaa, 0xae, 0xb5, 0xac, 0x5f, 0x55, 0xfb, 0xd7, 0x75, 0xfe, 0xc0, 0xba,
|
||||
0x43, 0xa5, 0xb9, 0xd6, 0xeb, 0xea, 0xcf, 0x10, 0xae, 0x55, 0x36, 0x18, 0x97, 0xf5, 0x77, 0xc9, 0x5d, 0x69, 0xa2,
|
||||
0xa8, 0x68, 0x2c, 0x58, 0x29, 0xbb, 0xca, 0x4a, 0xc9, 0x29, 0xb9, 0x3a, 0xe9, 0xdf, 0x4e, 0x12, 0x67, 0xae, 0x8e,
|
||||
0x4b, 0x12, 0xb7, 0xed, 0xb7, 0x5c, 0x47, 0xe6, 0x01, 0xc0, 0xad, 0xed, 0xae, 0xfc, 0xbc, 0xad, 0xdb, 0x07, 0x4d,
|
||||
0x6b, 0x3e, 0x96, 0x9a, 0xdd, 0xcb, 0xf0, 0x8e, 0x66, 0x97, 0x1d, 0xd7, 0x01, 0x3f, 0x4d, 0x53, 0xa5, 0x4c, 0xc8,
|
||||
0x32, 0xa7, 0xe3, 0x3a, 0xb7, 0x93, 0x24, 0xcd, 0x89, 0x1b, 0x0b, 0x31, 0x0d, 0xd4, 0xf7, 0x6f, 0x6f, 0x0e, 0x7c,
|
||||
0x9e, 0x8d, 0xf7, 0x3b, 0xad, 0x56, 0x0b, 0x2e, 0x80, 0x75, 0x9d, 0x39, 0xa3, 0x37, 0x4f, 0xf9, 0x2d, 0x71, 0x5b,
|
||||
0x4e, 0xcb, 0x69, 0x77, 0x8e, 0x9d, 0x76, 0xe7, 0xd0, 0x7f, 0x74, 0xec, 0xf6, 0x3e, 0x73, 0x9c, 0x93, 0x88, 0x8e,
|
||||
0x72, 0xf8, 0xe1, 0x38, 0x27, 0x52, 0xf1, 0x52, 0xbf, 0x1d, 0xc7, 0x1f, 0x26, 0x79, 0xb3, 0xed, 0x2c, 0xf4, 0xa3,
|
||||
0xe3, 0xc0, 0xa1, 0xd2, 0xc0, 0xf9, 0x7c, 0xd4, 0x19, 0x1d, 0x8e, 0x9e, 0x74, 0x75, 0x71, 0xf1, 0x59, 0xad, 0x3a,
|
||||
0x56, 0xff, 0x77, 0xac, 0x66, 0xb9, 0xc8, 0xf8, 0x47, 0xaa, 0x73, 0x12, 0x1d, 0x10, 0x3d, 0x1b, 0x9b, 0x76, 0xd6,
|
||||
0x47, 0x6a, 0x1f, 0x5f, 0x0f, 0x47, 0x9d, 0xaa, 0xba, 0x84, 0x71, 0xbf, 0x04, 0xf2, 0x64, 0xdf, 0x80, 0x7e, 0x62,
|
||||
0xa3, 0xa9, 0xdd, 0xdc, 0x84, 0xa8, 0xb6, 0xab, 0xe7, 0x38, 0x36, 0xf3, 0x3b, 0x81, 0x33, 0x0c, 0x46, 0x57, 0x95,
|
||||
0x10, 0xb8, 0x4e, 0x44, 0xdc, 0x57, 0xed, 0xce, 0x31, 0x6e, 0xb7, 0x1f, 0xf9, 0x8f, 0x8e, 0x87, 0x2d, 0x7c, 0xe8,
|
||||
0x1f, 0x36, 0x0f, 0xfc, 0x47, 0xf8, 0xb8, 0x79, 0x8c, 0x8f, 0x5f, 0x1c, 0x0f, 0x9b, 0x87, 0xfe, 0x21, 0x6e, 0x35,
|
||||
0x8f, 0xa1, 0xb0, 0x79, 0xdc, 0x3c, 0x9e, 0x37, 0x0f, 0x8f, 0x87, 0x2d, 0x59, 0xda, 0xf1, 0x8f, 0x8e, 0x9a, 0xed,
|
||||
0x96, 0x7f, 0x74, 0x84, 0x8f, 0xfc, 0x47, 0x8f, 0x9a, 0xed, 0x03, 0xff, 0xd1, 0xa3, 0x97, 0x47, 0xc7, 0xfe, 0x01,
|
||||
0xbc, 0x3b, 0x38, 0x18, 0x1e, 0xf8, 0xed, 0x76, 0x13, 0xfe, 0xc1, 0xc7, 0x7e, 0x47, 0xfd, 0x68, 0xb7, 0xfd, 0x83,
|
||||
0x36, 0x6e, 0x25, 0x47, 0x1d, 0xff, 0xd1, 0x13, 0x2c, 0xff, 0x95, 0xd5, 0xb0, 0xfc, 0x07, 0xba, 0xc1, 0x4f, 0xfc,
|
||||
0xce, 0x23, 0xf5, 0x4b, 0x76, 0x38, 0x3f, 0x3c, 0xfe, 0xc1, 0xdd, 0xdf, 0x3a, 0x87, 0xb6, 0x9a, 0xc3, 0xf1, 0x91,
|
||||
0x7f, 0x70, 0x80, 0x0f, 0xdb, 0xfe, 0xf1, 0x41, 0xdc, 0x3c, 0xec, 0xf8, 0x8f, 0x1e, 0x0f, 0x9b, 0x6d, 0xff, 0xf1,
|
||||
0x63, 0xdc, 0x6a, 0x1e, 0xf8, 0x1d, 0xdc, 0xf6, 0x0f, 0x0f, 0xe4, 0x8f, 0x03, 0xbf, 0x33, 0x7f, 0xfc, 0xc4, 0x7f,
|
||||
0x74, 0x14, 0x3f, 0xf2, 0x0f, 0xbf, 0x3d, 0x3c, 0xf6, 0x3b, 0x07, 0xf1, 0xc1, 0x23, 0xbf, 0xf3, 0x78, 0xfe, 0xc8,
|
||||
0x3f, 0x8c, 0x9b, 0x9d, 0x47, 0xf7, 0xb6, 0x6c, 0x77, 0x7c, 0xc0, 0x91, 0x7c, 0x0d, 0x2f, 0xb0, 0x7e, 0x01, 0x7f,
|
||||
0x63, 0xd9, 0xf6, 0xdf, 0xb1, 0x9b, 0x7c, 0xbd, 0xe9, 0x13, 0xff, 0xf8, 0xf1, 0x50, 0x55, 0x87, 0x82, 0xa6, 0xa9,
|
||||
0x01, 0x4d, 0xe6, 0x4d, 0x35, 0xac, 0xec, 0xae, 0x69, 0x3a, 0x32, 0x7f, 0xf5, 0x60, 0xf3, 0x26, 0x0c, 0xac, 0xc6,
|
||||
0xfd, 0x0f, 0xed, 0xa7, 0x5c, 0xf2, 0x93, 0xfd, 0xb1, 0x22, 0xfd, 0x71, 0xef, 0x33, 0x75, 0xbb, 0xf3, 0x67, 0x57,
|
||||
0x38, 0xdd, 0xe6, 0xf8, 0xc8, 0x3e, 0xed, 0xf8, 0xe0, 0xf4, 0x21, 0x9e, 0x8f, 0xec, 0x0f, 0xf7, 0x7c, 0xa4, 0x74,
|
||||
0xc5, 0x71, 0x7e, 0x2d, 0xd6, 0x1c, 0x1c, 0xab, 0x56, 0xf1, 0x53, 0xe1, 0x0d, 0x72, 0xf8, 0x8e, 0x58, 0xd1, 0xbd,
|
||||
0x16, 0x84, 0x53, 0xdb, 0x0f, 0xc4, 0x81, 0xc5, 0x5e, 0x0b, 0xc5, 0x63, 0x93, 0x6d, 0x08, 0x09, 0x3f, 0x8d, 0x90,
|
||||
0x6f, 0x1f, 0x82, 0x8f, 0xf0, 0x0f, 0xc7, 0x47, 0x62, 0xe3, 0xa3, 0xe6, 0xcb, 0x97, 0x9e, 0x06, 0xe9, 0x29, 0x38,
|
||||
0x97, 0xcf, 0x1e, 0x1c, 0xa2, 0x6a, 0xb8, 0xfb, 0x14, 0x8a, 0x72, 0x57, 0x45, 0xbe, 0xde, 0xfd, 0x9a, 0xb0, 0x83,
|
||||
0x3a, 0x31, 0x49, 0x5c, 0xed, 0x96, 0x99, 0x4a, 0xa9, 0xa3, 0x1f, 0x4a, 0xa1, 0xd4, 0xf1, 0x5b, 0x7e, 0xab, 0x74,
|
||||
0xe9, 0xc0, 0x29, 0x59, 0xb2, 0xe0, 0x22, 0x84, 0x2f, 0xd6, 0x26, 0x7c, 0x2c, 0xbf, 0x6d, 0x0b, 0x5f, 0x13, 0x80,
|
||||
0xa4, 0x9f, 0xa1, 0xfa, 0x90, 0x43, 0xe0, 0xba, 0xfa, 0x6e, 0x0d, 0x38, 0x85, 0xf9, 0x0d, 0x9c, 0x54, 0x35, 0x51,
|
||||
0x89, 0x09, 0x78, 0x3b, 0x5e, 0xd1, 0x88, 0x85, 0x9e, 0xeb, 0x4d, 0x33, 0x3a, 0xa2, 0x59, 0xde, 0xac, 0x1d, 0xdf,
|
||||
0x94, 0x27, 0x37, 0x91, 0x6b, 0x3e, 0x8d, 0x9a, 0xc1, 0xed, 0xd8, 0x64, 0xa0, 0xfd, 0x8d, 0xae, 0x36, 0xc0, 0xdc,
|
||||
0x02, 0x9b, 0x92, 0x0c, 0x64, 0x6d, 0xa5, 0xb4, 0xb9, 0x4a, 0x6b, 0x6b, 0xfb, 0x9d, 0x23, 0xe4, 0xc8, 0x62, 0xb8,
|
||||
0x77, 0xf8, 0x7b, 0xaf, 0x79, 0xd0, 0xfa, 0x13, 0xb2, 0x9a, 0x95, 0x1d, 0x5d, 0x68, 0x77, 0x5b, 0x5a, 0x7d, 0x53,
|
||||
0xba, 0x7e, 0xb6, 0xd6, 0x55, 0x14, 0xf1, 0xb9, 0x9a, 0xbb, 0x8b, 0xba, 0xa9, 0x8e, 0x70, 0xab, 0x1b, 0x22, 0x46,
|
||||
0x6c, 0xec, 0xd9, 0x5f, 0x0c, 0x56, 0xf7, 0x1a, 0xcb, 0x0f, 0x8d, 0xa3, 0xa2, 0xaa, 0x92, 0xa2, 0x85, 0x8c, 0xb7,
|
||||
0xb0, 0xd4, 0x49, 0x97, 0x4b, 0x2f, 0x05, 0x17, 0x39, 0xb1, 0x70, 0x0a, 0xcf, 0xa8, 0x86, 0xe4, 0x14, 0x97, 0x00,
|
||||
0x49, 0x04, 0x93, 0x54, 0xfd, 0x5f, 0x15, 0x9b, 0x1f, 0xda, 0xf1, 0xe5, 0x27, 0x61, 0x3a, 0x06, 0x2a, 0x0c, 0xd3,
|
||||
0xf1, 0x9a, 0x5b, 0x4d, 0x85, 0x8c, 0x56, 0x4a, 0xab, 0xae, 0x2a, 0xf7, 0x59, 0xfe, 0xf4, 0xee, 0xbd, 0xbe, 0x00,
|
||||
0xcd, 0x05, 0xef, 0xb4, 0x8c, 0x70, 0x54, 0x97, 0x35, 0x37, 0xc8, 0x17, 0x27, 0x13, 0x2a, 0x42, 0x95, 0xaf, 0x09,
|
||||
0xfa, 0x04, 0x9c, 0x9a, 0x75, 0xb4, 0x35, 0x4a, 0x5c, 0x29, 0xdd, 0x49, 0x44, 0xe7, 0x6c, 0xa8, 0x45, 0x3d, 0x76,
|
||||
0xf4, 0xcd, 0x01, 0x4d, 0xb9, 0x34, 0xa4, 0x8d, 0x95, 0x3f, 0x66, 0x18, 0xca, 0x8c, 0x7c, 0x92, 0x72, 0xb7, 0xf7,
|
||||
0x45, 0xf9, 0xf5, 0xd3, 0x6d, 0x8b, 0x90, 0xb0, 0xf4, 0xe3, 0x20, 0xa3, 0xc9, 0x3f, 0x91, 0x2f, 0xd8, 0x90, 0xa7,
|
||||
0x5f, 0x5c, 0xc0, 0x57, 0xe9, 0xfd, 0x38, 0xa3, 0x23, 0xf2, 0x05, 0xc8, 0xf8, 0x40, 0x5a, 0x1f, 0xc0, 0x08, 0x1b,
|
||||
0xb7, 0x93, 0x04, 0x4b, 0x8d, 0xe9, 0x01, 0x0a, 0x91, 0x02, 0xd7, 0xed, 0x1c, 0xb9, 0x8e, 0xb2, 0x89, 0xe5, 0xef,
|
||||
0x9e, 0x12, 0xa7, 0x52, 0x09, 0x70, 0xda, 0x1d, 0xff, 0x28, 0xee, 0xf8, 0x4f, 0xe6, 0x8f, 0xfd, 0xe3, 0xb8, 0xfd,
|
||||
0x78, 0xde, 0x84, 0xff, 0x3b, 0xfe, 0x93, 0xa4, 0xd9, 0xf1, 0x9f, 0xc0, 0xdf, 0x6f, 0x0f, 0xfd, 0xa3, 0xb8, 0xd9,
|
||||
0xf6, 0x8f, 0xe7, 0x07, 0xfe, 0xc1, 0xcb, 0x76, 0xc7, 0x3f, 0x70, 0xda, 0x8e, 0x6a, 0x07, 0xec, 0x5a, 0x71, 0xe7,
|
||||
0x2f, 0x56, 0x36, 0xc4, 0x86, 0x70, 0x9c, 0xca, 0x39, 0x75, 0xb1, 0x57, 0x7e, 0x63, 0x51, 0xef, 0x4f, 0xed, 0xac,
|
||||
0x7b, 0x16, 0x66, 0xf0, 0xa1, 0x9b, 0xfa, 0xde, 0xad, 0xbd, 0xc3, 0x35, 0x7e, 0xb1, 0x61, 0x08, 0xd8, 0xe1, 0x2e,
|
||||
0xb6, 0x8f, 0xde, 0xc3, 0xb9, 0x75, 0x79, 0x2f, 0xb8, 0xb9, 0x1e, 0x71, 0x3b, 0x69, 0xab, 0x8a, 0xe6, 0x0a, 0x46,
|
||||
0xc9, 0x2c, 0x98, 0xfc, 0x02, 0x83, 0x1c, 0xe4, 0xab, 0xa8, 0x58, 0x1d, 0x1f, 0x52, 0x5f, 0x33, 0x6e, 0xdd, 0x3e,
|
||||
0x40, 0xab, 0x03, 0x1b, 0x11, 0x83, 0xfb, 0x22, 0x8a, 0xc2, 0x80, 0x5e, 0x73, 0xd3, 0x56, 0x58, 0x92, 0xfc, 0x82,
|
||||
0xe6, 0x7d, 0x17, 0x8a, 0xdc, 0xc0, 0x95, 0x2e, 0x3e, 0xb7, 0xfc, 0xd8, 0x4f, 0x49, 0xd8, 0x55, 0x01, 0x96, 0x87,
|
||||
0xae, 0x60, 0xd7, 0x02, 0x7e, 0x5c, 0xb4, 0xb7, 0xb7, 0x75, 0xbf, 0x48, 0x05, 0x12, 0xe6, 0x5a, 0x7d, 0x23, 0xc4,
|
||||
0x66, 0x45, 0xae, 0x8d, 0xe8, 0xb2, 0x5f, 0x89, 0x42, 0xa4, 0xf1, 0x74, 0x4d, 0x43, 0xe1, 0x87, 0xa9, 0x4a, 0xa2,
|
||||
0xb1, 0x18, 0x16, 0x6e, 0xd3, 0x03, 0x54, 0x70, 0x11, 0x5a, 0xdf, 0x01, 0xd6, 0xfb, 0x9c, 0x8b, 0xd0, 0x9c, 0xa5,
|
||||
0xb5, 0xae, 0x0d, 0x02, 0x47, 0x6f, 0xdc, 0xe9, 0xbd, 0x79, 0x7f, 0xea, 0xa8, 0xed, 0x79, 0xb2, 0x1f, 0x77, 0x7a,
|
||||
0x27, 0xd2, 0x67, 0xa2, 0x4e, 0xe2, 0x11, 0x75, 0x12, 0xcf, 0xd1, 0xa7, 0x32, 0x21, 0x92, 0x56, 0xec, 0xab, 0x69,
|
||||
0x4b, 0x9b, 0x41, 0x79, 0x7b, 0x27, 0xb3, 0x44, 0x30, 0xb8, 0xe3, 0x7a, 0x5f, 0x1e, 0xc3, 0x83, 0x05, 0x2b, 0xf3,
|
||||
0xb0, 0xb5, 0x76, 0x78, 0x2d, 0x52, 0xe3, 0x1b, 0x1e, 0xb1, 0x84, 0x9a, 0xcc, 0x6b, 0xdd, 0x55, 0x79, 0x52, 0x60,
|
||||
0xbd, 0x76, 0x3e, 0xbb, 0x9e, 0x30, 0xe1, 0x9a, 0xf3, 0x0c, 0x1f, 0x74, 0x83, 0x13, 0x39, 0x54, 0xef, 0xaa, 0xd0,
|
||||
0xce, 0x6b, 0xf3, 0x35, 0x9f, 0xfa, 0x92, 0xea, 0xd9, 0x6b, 0x09, 0x01, 0x27, 0xe4, 0xe2, 0x83, 0x5e, 0xe9, 0x2e,
|
||||
0xb6, 0xdf, 0x15, 0x27, 0xfb, 0xf1, 0x41, 0xef, 0x2a, 0x98, 0xea, 0xfe, 0x5e, 0xf2, 0xf1, 0xe6, 0xbe, 0x12, 0x3e,
|
||||
0xee, 0xcb, 0xa3, 0x20, 0xea, 0x90, 0xb2, 0x51, 0x7e, 0x79, 0xe2, 0xf6, 0x4e, 0xb4, 0x32, 0xe0, 0xc8, 0xc0, 0xba,
|
||||
0x7b, 0xd4, 0x32, 0xa7, 0x4b, 0x12, 0x3e, 0x86, 0x0d, 0xa9, 0x9a, 0x58, 0x83, 0xd4, 0x3c, 0xee, 0x71, 0xbb, 0x77,
|
||||
0x12, 0x3a, 0x92, 0xb7, 0x48, 0xe6, 0x91, 0x07, 0xfb, 0xd0, 0x38, 0xe6, 0x13, 0xea, 0x33, 0xbe, 0x7f, 0x43, 0xaf,
|
||||
0x9b, 0xe1, 0x94, 0x55, 0xee, 0x6d, 0x50, 0x3a, 0xca, 0x21, 0xb9, 0xf1, 0x88, 0xeb, 0xb3, 0x57, 0x9d, 0xca, 0xdd,
|
||||
0x76, 0x08, 0x36, 0x8f, 0x71, 0xcd, 0x49, 0x9f, 0x9c, 0x05, 0x16, 0xef, 0x9d, 0xec, 0x87, 0x2b, 0x18, 0x91, 0xfc,
|
||||
0xbe, 0xd0, 0x8e, 0x76, 0x30, 0x6c, 0x80, 0xde, 0x5c, 0x47, 0x89, 0x03, 0xe3, 0x90, 0xd7, 0x82, 0xba, 0x70, 0x7b,
|
||||
0xff, 0xfa, 0x3f, 0xfe, 0x97, 0xf6, 0xb1, 0x9f, 0xec, 0xc7, 0x6d, 0xd3, 0xd7, 0xca, 0xaa, 0x14, 0x27, 0x70, 0xdc,
|
||||
0xb3, 0x0a, 0x0a, 0xd3, 0xdb, 0xe6, 0x38, 0x63, 0x51, 0x33, 0x0e, 0x93, 0x91, 0xdb, 0xdb, 0x8e, 0x4d, 0xfb, 0xd8,
|
||||
0x96, 0x86, 0xba, 0x5e, 0x04, 0xf4, 0xfa, 0x9b, 0x0e, 0x1e, 0x99, 0xf3, 0x2b, 0x72, 0x6b, 0xdb, 0xc7, 0x90, 0xaa,
|
||||
0xdd, 0x57, 0x3b, 0x8a, 0x94, 0xea, 0x4f, 0x84, 0x69, 0x0e, 0x98, 0xd6, 0x4e, 0x20, 0x15, 0xae, 0x53, 0x06, 0xb5,
|
||||
0xfe, 0xef, 0xff, 0xfc, 0x2f, 0xff, 0xcd, 0x3c, 0x42, 0xac, 0xea, 0x5f, 0xff, 0xfb, 0x7f, 0xfe, 0x3f, 0xff, 0xfb,
|
||||
0xbf, 0xc2, 0xa9, 0x15, 0x1d, 0xcf, 0x92, 0x4c, 0xc5, 0xa9, 0x82, 0x59, 0x8a, 0xbb, 0x38, 0x90, 0xd8, 0x39, 0x61,
|
||||
0xb9, 0x60, 0xc3, 0xfa, 0x99, 0xa4, 0x73, 0x39, 0xa0, 0xdc, 0x99, 0x1a, 0x3a, 0xb9, 0xc3, 0x8b, 0x8a, 0xa0, 0x6a,
|
||||
0x28, 0x97, 0x84, 0x5b, 0x9c, 0xec, 0x03, 0xbe, 0x1f, 0x76, 0x8c, 0xd3, 0x2f, 0x97, 0x63, 0x61, 0xc8, 0x04, 0x4a,
|
||||
0x8a, 0xaa, 0xdc, 0x81, 0xd8, 0xca, 0x02, 0x1e, 0x83, 0x8e, 0x55, 0x2c, 0x57, 0xaf, 0xd6, 0xa6, 0xfb, 0xd3, 0x2c,
|
||||
0x17, 0x6c, 0x04, 0x28, 0x57, 0x7e, 0x62, 0x19, 0xc6, 0x6e, 0x82, 0xae, 0x98, 0xdc, 0x15, 0xb2, 0x17, 0x45, 0xa0,
|
||||
0x87, 0xc7, 0x7f, 0x2a, 0xfe, 0x32, 0x01, 0x8d, 0xcc, 0xf1, 0x26, 0xe1, 0xad, 0x36, 0xcf, 0x1f, 0xb5, 0x5a, 0xd3,
|
||||
0x5b, 0xb4, 0xa8, 0x46, 0xc0, 0xdb, 0x06, 0x93, 0x74, 0x6c, 0x77, 0x28, 0xe3, 0xdf, 0xa5, 0x1b, 0xbb, 0xe5, 0x80,
|
||||
0x2f, 0xdc, 0x69, 0x15, 0xc5, 0x9f, 0x17, 0xd2, 0x93, 0xca, 0x7e, 0x81, 0x38, 0xb5, 0x76, 0x3a, 0x5f, 0x73, 0x7b,
|
||||
0x72, 0x0b, 0xab, 0x55, 0x47, 0xb5, 0x8a, 0xdb, 0xeb, 0xa7, 0x13, 0xed, 0x38, 0xbb, 0x1d, 0x21, 0x3f, 0x84, 0x98,
|
||||
0x77, 0xdc, 0xc6, 0x71, 0x67, 0x51, 0x76, 0x2f, 0x04, 0x9f, 0xd8, 0x81, 0x75, 0x1a, 0xd2, 0x21, 0x1d, 0x19, 0x67,
|
||||
0xbd, 0x7e, 0xaf, 0x82, 0xe6, 0x45, 0x7c, 0xb0, 0x61, 0x2c, 0x0d, 0x92, 0x0c, 0xa8, 0x3b, 0xad, 0xe2, 0x73, 0xd8,
|
||||
0x81, 0x8b, 0x51, 0xc2, 0x43, 0x11, 0x48, 0x82, 0xed, 0xda, 0xe1, 0xf9, 0x10, 0x78, 0x12, 0x5f, 0x58, 0xf0, 0x74,
|
||||
0x55, 0x55, 0x70, 0x9b, 0xd7, 0xcf, 0x90, 0x16, 0xbe, 0x6c, 0x6e, 0x77, 0xa5, 0xbc, 0x6e, 0xdf, 0xea, 0xa8, 0xf7,
|
||||
0xbb, 0x9a, 0xbb, 0x4a, 0x0b, 0xa4, 0x0e, 0xda, 0xfc, 0x5e, 0xc9, 0x75, 0xf5, 0xf6, 0x6b, 0xe1, 0xb9, 0x12, 0x4c,
|
||||
0x77, 0xb5, 0x96, 0x2c, 0x84, 0x5a, 0xef, 0xc8, 0xb7, 0xa5, 0xc9, 0x14, 0x4e, 0xa7, 0xb2, 0x22, 0xea, 0x9e, 0xec,
|
||||
0x2b, 0x4d, 0x17, 0xb8, 0x87, 0x4c, 0xe9, 0x50, 0x19, 0x14, 0xba, 0x92, 0xde, 0x0a, 0xea, 0x97, 0xce, 0xad, 0x80,
|
||||
0x4f, 0xc7, 0xf5, 0xfe, 0x1f, 0xe7, 0xe0, 0x1c, 0x12, 0xcf, 0x89, 0x00, 0x00};
|
||||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
@@ -4,7 +4,7 @@ from enum import Enum
|
||||
|
||||
from esphome.enum import StrEnum
|
||||
|
||||
__version__ = "2025.9.0-dev"
|
||||
__version__ = "2025.10.0-dev"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
@@ -424,6 +424,7 @@ CONF_HEAD = "head"
|
||||
CONF_HEADING = "heading"
|
||||
CONF_HEARTBEAT = "heartbeat"
|
||||
CONF_HEAT_ACTION = "heat_action"
|
||||
CONF_HEAT_COOL_MODE = "heat_cool_mode"
|
||||
CONF_HEAT_DEADBAND = "heat_deadband"
|
||||
CONF_HEAT_MODE = "heat_mode"
|
||||
CONF_HEAT_OVERRUN = "heat_overrun"
|
||||
|
@@ -272,12 +272,15 @@ class OrderedDict(collections.OrderedDict):
|
||||
return dict(self).__repr__()
|
||||
|
||||
|
||||
def list_yaml_files(folders: list[str]) -> list[str]:
|
||||
files = filter_yaml_files(
|
||||
[os.path.join(folder, p) for folder in folders for p in os.listdir(folder)]
|
||||
)
|
||||
files.sort()
|
||||
return files
|
||||
def list_yaml_files(configs: list[str]) -> list[str]:
|
||||
files: list[str] = []
|
||||
for config in configs:
|
||||
if os.path.isfile(config):
|
||||
files.append(config)
|
||||
else:
|
||||
files.extend(os.path.join(config, p) for p in os.listdir(config))
|
||||
files = filter_yaml_files(files)
|
||||
return sorted(files)
|
||||
|
||||
|
||||
def filter_yaml_files(files: list[str]) -> list[str]:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
pylint==3.3.8
|
||||
flake8==7.3.0 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.12.12 # also change in .pre-commit-config.yaml when updating
|
||||
ruff==0.13.0 # also change in .pre-commit-config.yaml when updating
|
||||
pyupgrade==3.20.0 # also change in .pre-commit-config.yaml when updating
|
||||
pre-commit
|
||||
|
||||
|
@@ -4,8 +4,14 @@ uart:
|
||||
rx_pin: ${rx_pin}
|
||||
baud_rate: 9600
|
||||
|
||||
button:
|
||||
- platform: bl0940
|
||||
bl0940_id: test_id
|
||||
name: Cal Reset
|
||||
|
||||
sensor:
|
||||
- platform: bl0940
|
||||
id: test_id
|
||||
voltage:
|
||||
name: BL0940 Voltage
|
||||
current:
|
||||
@@ -18,3 +24,18 @@ sensor:
|
||||
name: BL0940 Internal temperature
|
||||
external_temperature:
|
||||
name: BL0940 External temperature
|
||||
|
||||
number:
|
||||
- platform: bl0940
|
||||
id: bl0940_number_id
|
||||
bl0940_id: test_id
|
||||
current_calibration:
|
||||
name: Cal Current
|
||||
min_value: -5
|
||||
max_value: 5
|
||||
voltage_calibration:
|
||||
name: Cal Voltage
|
||||
step: 0.01
|
||||
power_calibration:
|
||||
name: Cal Power
|
||||
disabled_by_default: true
|
||||
|
@@ -1,5 +1,5 @@
|
||||
i2c:
|
||||
- id: i2c_inkplate6
|
||||
- id: i2c_inkplate
|
||||
scl: 16
|
||||
sda: 17
|
||||
|
||||
@@ -7,7 +7,7 @@ esp32:
|
||||
cpu_frequency: 240MHz
|
||||
|
||||
display:
|
||||
- platform: inkplate6
|
||||
- platform: inkplate
|
||||
id: inkplate_display
|
||||
greyscale: false
|
||||
partial_updating: false
|
@@ -36,3 +36,10 @@ def fixture_path() -> Path:
|
||||
Location of all fixture files.
|
||||
"""
|
||||
return here / "fixtures"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def setup_core(tmp_path: Path) -> Path:
|
||||
"""Set up CORE with test paths."""
|
||||
CORE.config_path = str(tmp_path / "test.yaml")
|
||||
return tmp_path
|
||||
|
187
tests/unit_tests/test_config_validation_paths.py
Normal file
187
tests/unit_tests/test_config_validation_paths.py
Normal file
@@ -0,0 +1,187 @@
|
||||
"""Tests for config_validation.py path-related functions."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from esphome import config_validation as cv
|
||||
|
||||
|
||||
def test_directory_valid_path(setup_core: Path) -> None:
|
||||
"""Test directory validator with valid directory."""
|
||||
test_dir = setup_core / "test_directory"
|
||||
test_dir.mkdir()
|
||||
|
||||
result = cv.directory("test_directory")
|
||||
|
||||
assert result == "test_directory"
|
||||
|
||||
|
||||
def test_directory_absolute_path(setup_core: Path) -> None:
|
||||
"""Test directory validator with absolute path."""
|
||||
test_dir = setup_core / "test_directory"
|
||||
test_dir.mkdir()
|
||||
|
||||
result = cv.directory(str(test_dir))
|
||||
|
||||
assert result == str(test_dir)
|
||||
|
||||
|
||||
def test_directory_nonexistent_path(setup_core: Path) -> None:
|
||||
"""Test directory validator raises error for non-existent directory."""
|
||||
with pytest.raises(
|
||||
vol.Invalid, match="Could not find directory.*nonexistent_directory"
|
||||
):
|
||||
cv.directory("nonexistent_directory")
|
||||
|
||||
|
||||
def test_directory_file_instead_of_directory(setup_core: Path) -> None:
|
||||
"""Test directory validator raises error when path is a file."""
|
||||
test_file = setup_core / "test_file.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
with pytest.raises(vol.Invalid, match="is not a directory"):
|
||||
cv.directory("test_file.txt")
|
||||
|
||||
|
||||
def test_directory_with_parent_directory(setup_core: Path) -> None:
|
||||
"""Test directory validator with nested directory structure."""
|
||||
nested_dir = setup_core / "parent" / "child" / "grandchild"
|
||||
nested_dir.mkdir(parents=True)
|
||||
|
||||
result = cv.directory("parent/child/grandchild")
|
||||
|
||||
assert result == "parent/child/grandchild"
|
||||
|
||||
|
||||
def test_file_valid_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator with valid file."""
|
||||
test_file = setup_core / "test_file.yaml"
|
||||
test_file.write_text("test content")
|
||||
|
||||
result = cv.file_("test_file.yaml")
|
||||
|
||||
assert result == "test_file.yaml"
|
||||
|
||||
|
||||
def test_file_absolute_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator with absolute path."""
|
||||
test_file = setup_core / "test_file.yaml"
|
||||
test_file.write_text("test content")
|
||||
|
||||
result = cv.file_(str(test_file))
|
||||
|
||||
assert result == str(test_file)
|
||||
|
||||
|
||||
def test_file_nonexistent_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator raises error for non-existent file."""
|
||||
with pytest.raises(vol.Invalid, match="Could not find file.*nonexistent_file.yaml"):
|
||||
cv.file_("nonexistent_file.yaml")
|
||||
|
||||
|
||||
def test_file_directory_instead_of_file(setup_core: Path) -> None:
|
||||
"""Test file_ validator raises error when path is a directory."""
|
||||
test_dir = setup_core / "test_directory"
|
||||
test_dir.mkdir()
|
||||
|
||||
with pytest.raises(vol.Invalid, match="is not a file"):
|
||||
cv.file_("test_directory")
|
||||
|
||||
|
||||
def test_file_with_parent_directory(setup_core: Path) -> None:
|
||||
"""Test file_ validator with file in nested directory."""
|
||||
nested_dir = setup_core / "configs" / "sensors"
|
||||
nested_dir.mkdir(parents=True)
|
||||
test_file = nested_dir / "temperature.yaml"
|
||||
test_file.write_text("sensor config")
|
||||
|
||||
result = cv.file_("configs/sensors/temperature.yaml")
|
||||
|
||||
assert result == "configs/sensors/temperature.yaml"
|
||||
|
||||
|
||||
def test_directory_handles_trailing_slash(setup_core: Path) -> None:
|
||||
"""Test directory validator handles trailing slashes correctly."""
|
||||
test_dir = setup_core / "test_dir"
|
||||
test_dir.mkdir()
|
||||
|
||||
result = cv.directory("test_dir/")
|
||||
assert result == "test_dir/"
|
||||
|
||||
result = cv.directory("test_dir")
|
||||
assert result == "test_dir"
|
||||
|
||||
|
||||
def test_file_handles_various_extensions(setup_core: Path) -> None:
|
||||
"""Test file_ validator works with different file extensions."""
|
||||
yaml_file = setup_core / "config.yaml"
|
||||
yaml_file.write_text("yaml content")
|
||||
assert cv.file_("config.yaml") == "config.yaml"
|
||||
|
||||
yml_file = setup_core / "config.yml"
|
||||
yml_file.write_text("yml content")
|
||||
assert cv.file_("config.yml") == "config.yml"
|
||||
|
||||
txt_file = setup_core / "readme.txt"
|
||||
txt_file.write_text("text content")
|
||||
assert cv.file_("readme.txt") == "readme.txt"
|
||||
|
||||
no_ext_file = setup_core / "LICENSE"
|
||||
no_ext_file.write_text("license content")
|
||||
assert cv.file_("LICENSE") == "LICENSE"
|
||||
|
||||
|
||||
def test_directory_with_symlink(setup_core: Path) -> None:
|
||||
"""Test directory validator follows symlinks."""
|
||||
actual_dir = setup_core / "actual_directory"
|
||||
actual_dir.mkdir()
|
||||
|
||||
symlink_dir = setup_core / "symlink_directory"
|
||||
symlink_dir.symlink_to(actual_dir)
|
||||
|
||||
result = cv.directory("symlink_directory")
|
||||
assert result == "symlink_directory"
|
||||
|
||||
|
||||
def test_file_with_symlink(setup_core: Path) -> None:
|
||||
"""Test file_ validator follows symlinks."""
|
||||
actual_file = setup_core / "actual_file.txt"
|
||||
actual_file.write_text("content")
|
||||
|
||||
symlink_file = setup_core / "symlink_file.txt"
|
||||
symlink_file.symlink_to(actual_file)
|
||||
|
||||
result = cv.file_("symlink_file.txt")
|
||||
assert result == "symlink_file.txt"
|
||||
|
||||
|
||||
def test_directory_error_shows_full_path(setup_core: Path) -> None:
|
||||
"""Test directory validator error message includes full path."""
|
||||
with pytest.raises(vol.Invalid, match=".*missing_dir.*full path:.*"):
|
||||
cv.directory("missing_dir")
|
||||
|
||||
|
||||
def test_file_error_shows_full_path(setup_core: Path) -> None:
|
||||
"""Test file_ validator error message includes full path."""
|
||||
with pytest.raises(vol.Invalid, match=".*missing_file.yaml.*full path:.*"):
|
||||
cv.file_("missing_file.yaml")
|
||||
|
||||
|
||||
def test_directory_with_spaces_in_name(setup_core: Path) -> None:
|
||||
"""Test directory validator handles spaces in directory names."""
|
||||
dir_with_spaces = setup_core / "my test directory"
|
||||
dir_with_spaces.mkdir()
|
||||
|
||||
result = cv.directory("my test directory")
|
||||
assert result == "my test directory"
|
||||
|
||||
|
||||
def test_file_with_spaces_in_name(setup_core: Path) -> None:
|
||||
"""Test file_ validator handles spaces in file names."""
|
||||
file_with_spaces = setup_core / "my test file.yaml"
|
||||
file_with_spaces.write_text("content")
|
||||
|
||||
result = cv.file_("my test file.yaml")
|
||||
assert result == "my test file.yaml"
|
196
tests/unit_tests/test_external_files.py
Normal file
196
tests/unit_tests/test_external_files.py
Normal file
@@ -0,0 +1,196 @@
|
||||
"""Tests for external_files.py functions."""
|
||||
|
||||
from pathlib import Path
|
||||
import time
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from esphome import external_files
|
||||
from esphome.config_validation import Invalid
|
||||
from esphome.core import CORE, TimePeriod
|
||||
|
||||
|
||||
def test_compute_local_file_dir(setup_core: Path) -> None:
|
||||
"""Test compute_local_file_dir creates and returns correct path."""
|
||||
domain = "font"
|
||||
|
||||
result = external_files.compute_local_file_dir(domain)
|
||||
|
||||
assert isinstance(result, Path)
|
||||
assert result == Path(CORE.data_dir) / domain
|
||||
assert result.exists()
|
||||
assert result.is_dir()
|
||||
|
||||
|
||||
def test_compute_local_file_dir_nested(setup_core: Path) -> None:
|
||||
"""Test compute_local_file_dir works with nested domains."""
|
||||
domain = "images/icons"
|
||||
|
||||
result = external_files.compute_local_file_dir(domain)
|
||||
|
||||
assert result == Path(CORE.data_dir) / "images" / "icons"
|
||||
assert result.exists()
|
||||
assert result.is_dir()
|
||||
|
||||
|
||||
def test_is_file_recent_with_recent_file(setup_core: Path) -> None:
|
||||
"""Test is_file_recent returns True for recently created file."""
|
||||
test_file = setup_core / "recent.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
refresh = TimePeriod(seconds=3600)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_is_file_recent_with_old_file(setup_core: Path) -> None:
|
||||
"""Test is_file_recent returns False for old file."""
|
||||
test_file = setup_core / "old.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
old_time = time.time() - 7200
|
||||
|
||||
with patch("os.path.getctime", return_value=old_time):
|
||||
refresh = TimePeriod(seconds=3600)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_file_recent_nonexistent_file(setup_core: Path) -> None:
|
||||
"""Test is_file_recent returns False for non-existent file."""
|
||||
test_file = setup_core / "nonexistent.txt"
|
||||
refresh = TimePeriod(seconds=3600)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_is_file_recent_with_zero_refresh(setup_core: Path) -> None:
|
||||
"""Test is_file_recent with zero refresh period returns False."""
|
||||
test_file = setup_core / "test.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
# Mock getctime to return a time 10 seconds ago
|
||||
with patch("os.path.getctime", return_value=time.time() - 10):
|
||||
refresh = TimePeriod(seconds=0)
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
assert result is False
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_not_modified(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed returns False when file not modified."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 304
|
||||
mock_head.return_value = mock_response
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
result = external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
assert result is False
|
||||
mock_head.assert_called_once()
|
||||
|
||||
call_args = mock_head.call_args
|
||||
headers = call_args[1]["headers"]
|
||||
assert external_files.IF_MODIFIED_SINCE in headers
|
||||
assert external_files.CACHE_CONTROL in headers
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_modified(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed returns True when file modified."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 200
|
||||
mock_head.return_value = mock_response
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
result = external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_has_remote_file_changed_no_local_file(setup_core: Path) -> None:
|
||||
"""Test has_remote_file_changed returns True when local file doesn't exist."""
|
||||
test_file = setup_core / "nonexistent.txt"
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
result = external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_network_error(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed handles network errors gracefully."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_head.side_effect = requests.exceptions.RequestException("Network error")
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
|
||||
with pytest.raises(Invalid, match="Could not check if.*Network error"):
|
||||
external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
|
||||
@patch("esphome.external_files.requests.head")
|
||||
def test_has_remote_file_changed_timeout(
|
||||
mock_head: MagicMock, setup_core: Path
|
||||
) -> None:
|
||||
"""Test has_remote_file_changed respects timeout."""
|
||||
test_file = setup_core / "cached.txt"
|
||||
test_file.write_text("cached content")
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.status_code = 304
|
||||
mock_head.return_value = mock_response
|
||||
|
||||
url = "https://example.com/file.txt"
|
||||
external_files.has_remote_file_changed(url, str(test_file))
|
||||
|
||||
call_args = mock_head.call_args
|
||||
assert call_args[1]["timeout"] == external_files.NETWORK_TIMEOUT
|
||||
|
||||
|
||||
def test_compute_local_file_dir_creates_parent_dirs(setup_core: Path) -> None:
|
||||
"""Test compute_local_file_dir creates parent directories."""
|
||||
domain = "level1/level2/level3/level4"
|
||||
|
||||
result = external_files.compute_local_file_dir(domain)
|
||||
|
||||
assert result.exists()
|
||||
assert result.is_dir()
|
||||
assert result.parent.name == "level3"
|
||||
assert result.parent.parent.name == "level2"
|
||||
assert result.parent.parent.parent.name == "level1"
|
||||
|
||||
|
||||
def test_is_file_recent_handles_float_seconds(setup_core: Path) -> None:
|
||||
"""Test is_file_recent works with float seconds in TimePeriod."""
|
||||
test_file = setup_core / "test.txt"
|
||||
test_file.write_text("content")
|
||||
|
||||
refresh = TimePeriod(seconds=3600.5)
|
||||
|
||||
result = external_files.is_file_recent(str(test_file), refresh)
|
||||
|
||||
assert result is True
|
129
tests/unit_tests/test_platformio_api.py
Normal file
129
tests/unit_tests/test_platformio_api.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""Tests for platformio_api.py path functions."""
|
||||
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from esphome import platformio_api
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def test_idedata_firmware_elf_path(setup_core: Path) -> None:
|
||||
"""Test IDEData.firmware_elf_path returns correct path."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {"prog_path": "/path/to/firmware.elf"}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
assert idedata.firmware_elf_path == "/path/to/firmware.elf"
|
||||
|
||||
|
||||
def test_idedata_firmware_bin_path(setup_core: Path) -> None:
|
||||
"""Test IDEData.firmware_bin_path returns Path with .bin extension."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
prog_path = str(Path("/path/to/firmware.elf"))
|
||||
raw_data = {"prog_path": prog_path}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
result = idedata.firmware_bin_path
|
||||
assert isinstance(result, str)
|
||||
expected = str(Path("/path/to/firmware.bin"))
|
||||
assert result == expected
|
||||
assert result.endswith(".bin")
|
||||
|
||||
|
||||
def test_idedata_firmware_bin_path_preserves_directory(setup_core: Path) -> None:
|
||||
"""Test firmware_bin_path preserves the directory structure."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
prog_path = str(Path("/complex/path/to/build/firmware.elf"))
|
||||
raw_data = {"prog_path": prog_path}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
result = idedata.firmware_bin_path
|
||||
expected = str(Path("/complex/path/to/build/firmware.bin"))
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_idedata_extra_flash_images(setup_core: Path) -> None:
|
||||
"""Test IDEData.extra_flash_images returns list of FlashImage objects."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {
|
||||
"prog_path": "/path/to/firmware.elf",
|
||||
"extra": {
|
||||
"flash_images": [
|
||||
{"path": "/path/to/bootloader.bin", "offset": "0x1000"},
|
||||
{"path": "/path/to/partition.bin", "offset": "0x8000"},
|
||||
]
|
||||
},
|
||||
}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
images = idedata.extra_flash_images
|
||||
assert len(images) == 2
|
||||
assert all(isinstance(img, platformio_api.FlashImage) for img in images)
|
||||
assert images[0].path == "/path/to/bootloader.bin"
|
||||
assert images[0].offset == "0x1000"
|
||||
assert images[1].path == "/path/to/partition.bin"
|
||||
assert images[1].offset == "0x8000"
|
||||
|
||||
|
||||
def test_idedata_extra_flash_images_empty(setup_core: Path) -> None:
|
||||
"""Test extra_flash_images returns empty list when no extra images."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {"prog_path": "/path/to/firmware.elf", "extra": {"flash_images": []}}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
images = idedata.extra_flash_images
|
||||
assert images == []
|
||||
|
||||
|
||||
def test_idedata_cc_path(setup_core: Path) -> None:
|
||||
"""Test IDEData.cc_path returns compiler path."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
raw_data = {
|
||||
"prog_path": "/path/to/firmware.elf",
|
||||
"cc_path": "/Users/test/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-gcc",
|
||||
}
|
||||
idedata = platformio_api.IDEData(raw_data)
|
||||
|
||||
assert (
|
||||
idedata.cc_path
|
||||
== "/Users/test/.platformio/packages/toolchain-xtensa32/bin/xtensa-esp32-elf-gcc"
|
||||
)
|
||||
|
||||
|
||||
def test_flash_image_dataclass() -> None:
|
||||
"""Test FlashImage dataclass stores path and offset correctly."""
|
||||
image = platformio_api.FlashImage(path="/path/to/image.bin", offset="0x10000")
|
||||
|
||||
assert image.path == "/path/to/image.bin"
|
||||
assert image.offset == "0x10000"
|
||||
|
||||
|
||||
def test_load_idedata_returns_dict(setup_core: Path) -> None:
|
||||
"""Test _load_idedata returns parsed idedata dict when successful."""
|
||||
CORE.build_path = str(setup_core / "build" / "test")
|
||||
CORE.name = "test"
|
||||
|
||||
# Create required files
|
||||
platformio_ini = setup_core / "build" / "test" / "platformio.ini"
|
||||
platformio_ini.parent.mkdir(parents=True, exist_ok=True)
|
||||
platformio_ini.touch()
|
||||
|
||||
idedata_path = setup_core / ".esphome" / "idedata" / "test.json"
|
||||
idedata_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
idedata_path.write_text('{"prog_path": "/test/firmware.elf"}')
|
||||
|
||||
with patch("esphome.platformio_api.run_platformio_cli_run") as mock_run:
|
||||
mock_run.return_value = '{"prog_path": "/test/firmware.elf"}'
|
||||
|
||||
config = {"name": "test"}
|
||||
result = platformio_api._load_idedata(config)
|
||||
|
||||
assert result is not None
|
||||
assert isinstance(result, dict)
|
||||
assert result["prog_path"] == "/test/firmware.elf"
|
182
tests/unit_tests/test_storage_json.py
Normal file
182
tests/unit_tests/test_storage_json.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""Tests for storage_json.py path functions."""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import storage_json
|
||||
from esphome.core import CORE
|
||||
|
||||
|
||||
def test_storage_path(setup_core: Path) -> None:
|
||||
"""Test storage_path returns correct path for current config."""
|
||||
CORE.config_path = str(setup_core / "my_device.yaml")
|
||||
|
||||
result = storage_json.storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "storage" / "my_device.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_ext_storage_path(setup_core: Path) -> None:
|
||||
"""Test ext_storage_path returns correct path for given filename."""
|
||||
result = storage_json.ext_storage_path("other_device.yaml")
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "storage" / "other_device.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_ext_storage_path_handles_various_extensions(setup_core: Path) -> None:
|
||||
"""Test ext_storage_path works with different file extensions."""
|
||||
result_yml = storage_json.ext_storage_path("device.yml")
|
||||
assert result_yml.endswith("device.yml.json")
|
||||
|
||||
result_no_ext = storage_json.ext_storage_path("device")
|
||||
assert result_no_ext.endswith("device.json")
|
||||
|
||||
result_path = storage_json.ext_storage_path("my/device.yaml")
|
||||
assert result_path.endswith("device.yaml.json")
|
||||
|
||||
|
||||
def test_esphome_storage_path(setup_core: Path) -> None:
|
||||
"""Test esphome_storage_path returns correct path."""
|
||||
result = storage_json.esphome_storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "esphome.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_ignored_devices_storage_path(setup_core: Path) -> None:
|
||||
"""Test ignored_devices_storage_path returns correct path."""
|
||||
result = storage_json.ignored_devices_storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "ignored-devices.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_trash_storage_path(setup_core: Path) -> None:
|
||||
"""Test trash_storage_path returns correct path."""
|
||||
CORE.config_path = str(setup_core / "configs" / "device.yaml")
|
||||
|
||||
result = storage_json.trash_storage_path()
|
||||
|
||||
expected = str(setup_core / "configs" / "trash")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_archive_storage_path(setup_core: Path) -> None:
|
||||
"""Test archive_storage_path returns correct path."""
|
||||
CORE.config_path = str(setup_core / "configs" / "device.yaml")
|
||||
|
||||
result = storage_json.archive_storage_path()
|
||||
|
||||
expected = str(setup_core / "configs" / "archive")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_storage_path_with_subdirectory(setup_core: Path) -> None:
|
||||
"""Test storage paths work correctly when config is in subdirectory."""
|
||||
subdir = setup_core / "configs" / "basement"
|
||||
subdir.mkdir(parents=True, exist_ok=True)
|
||||
CORE.config_path = str(subdir / "sensor.yaml")
|
||||
|
||||
result = storage_json.storage_path()
|
||||
|
||||
data_dir = Path(CORE.data_dir)
|
||||
expected = str(data_dir / "storage" / "sensor.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_storage_json_firmware_bin_path_property(setup_core: Path) -> None:
|
||||
"""Test StorageJSON firmware_bin_path property."""
|
||||
storage = storage_json.StorageJSON(
|
||||
storage_version=1,
|
||||
name="test_device",
|
||||
friendly_name="Test Device",
|
||||
comment=None,
|
||||
esphome_version="2024.1.0",
|
||||
src_version=None,
|
||||
address="192.168.1.100",
|
||||
web_port=80,
|
||||
target_platform="ESP32",
|
||||
build_path="build/test_device",
|
||||
firmware_bin_path="/path/to/firmware.bin",
|
||||
loaded_integrations={"wifi", "api"},
|
||||
loaded_platforms=set(),
|
||||
no_mdns=False,
|
||||
)
|
||||
|
||||
assert storage.firmware_bin_path == "/path/to/firmware.bin"
|
||||
|
||||
|
||||
def test_storage_json_save_creates_directory(setup_core: Path, tmp_path: Path) -> None:
|
||||
"""Test StorageJSON.save creates storage directory if it doesn't exist."""
|
||||
storage_dir = tmp_path / "new_data" / "storage"
|
||||
storage_file = storage_dir / "test.json"
|
||||
|
||||
assert not storage_dir.exists()
|
||||
|
||||
storage = storage_json.StorageJSON(
|
||||
storage_version=1,
|
||||
name="test",
|
||||
friendly_name="Test",
|
||||
comment=None,
|
||||
esphome_version="2024.1.0",
|
||||
src_version=None,
|
||||
address="test.local",
|
||||
web_port=None,
|
||||
target_platform="ESP8266",
|
||||
build_path=None,
|
||||
firmware_bin_path=None,
|
||||
loaded_integrations=set(),
|
||||
loaded_platforms=set(),
|
||||
no_mdns=False,
|
||||
)
|
||||
|
||||
with patch("esphome.storage_json.write_file_if_changed") as mock_write:
|
||||
storage.save(str(storage_file))
|
||||
mock_write.assert_called_once()
|
||||
call_args = mock_write.call_args[0]
|
||||
assert call_args[0] == str(storage_file)
|
||||
|
||||
|
||||
def test_storage_json_from_wizard(setup_core: Path) -> None:
|
||||
"""Test StorageJSON.from_wizard creates correct storage object."""
|
||||
storage = storage_json.StorageJSON.from_wizard(
|
||||
name="my_device",
|
||||
friendly_name="My Device",
|
||||
address="my_device.local",
|
||||
platform="ESP32",
|
||||
)
|
||||
|
||||
assert storage.name == "my_device"
|
||||
assert storage.friendly_name == "My Device"
|
||||
assert storage.address == "my_device.local"
|
||||
assert storage.target_platform == "ESP32"
|
||||
assert storage.build_path is None
|
||||
assert storage.firmware_bin_path is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="HA addons don't run on Windows")
|
||||
@patch("esphome.core.is_ha_addon")
|
||||
def test_storage_paths_with_ha_addon(mock_is_ha_addon: bool, tmp_path: Path) -> None:
|
||||
"""Test storage paths when running as Home Assistant addon."""
|
||||
mock_is_ha_addon.return_value = True
|
||||
|
||||
CORE.config_path = str(tmp_path / "test.yaml")
|
||||
|
||||
result = storage_json.storage_path()
|
||||
# When is_ha_addon is True, CORE.data_dir returns "/data"
|
||||
# This is the standard mount point for HA addon containers
|
||||
expected = str(Path("/data") / "storage" / "test.yaml.json")
|
||||
assert result == expected
|
||||
|
||||
result = storage_json.esphome_storage_path()
|
||||
expected = str(Path("/data") / "esphome.json")
|
||||
assert result == expected
|
143
tests/unit_tests/test_util.py
Normal file
143
tests/unit_tests/test_util.py
Normal file
@@ -0,0 +1,143 @@
|
||||
"""Tests for esphome.util module."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome import util
|
||||
|
||||
|
||||
def test_list_yaml_files_with_files_and_directories(tmp_path: Path) -> None:
|
||||
"""Test that list_yaml_files handles both files and directories."""
|
||||
# Create directory structure
|
||||
dir1 = tmp_path / "configs"
|
||||
dir1.mkdir()
|
||||
dir2 = tmp_path / "more_configs"
|
||||
dir2.mkdir()
|
||||
|
||||
# Create YAML files in directories
|
||||
(dir1 / "config1.yaml").write_text("test: 1")
|
||||
(dir1 / "config2.yml").write_text("test: 2")
|
||||
(dir1 / "not_yaml.txt").write_text("not yaml")
|
||||
|
||||
(dir2 / "config3.yaml").write_text("test: 3")
|
||||
|
||||
# Create standalone YAML files
|
||||
standalone1 = tmp_path / "standalone.yaml"
|
||||
standalone1.write_text("test: 4")
|
||||
standalone2 = tmp_path / "another.yml"
|
||||
standalone2.write_text("test: 5")
|
||||
|
||||
# Test with mixed input (directories and files)
|
||||
configs = [
|
||||
str(dir1),
|
||||
str(standalone1),
|
||||
str(dir2),
|
||||
str(standalone2),
|
||||
]
|
||||
|
||||
result = util.list_yaml_files(configs)
|
||||
|
||||
# Should include all YAML files but not the .txt file
|
||||
assert set(result) == {
|
||||
str(dir1 / "config1.yaml"),
|
||||
str(dir1 / "config2.yml"),
|
||||
str(dir2 / "config3.yaml"),
|
||||
str(standalone1),
|
||||
str(standalone2),
|
||||
}
|
||||
# Check that results are sorted
|
||||
assert result == sorted(result)
|
||||
|
||||
|
||||
def test_list_yaml_files_only_directories(tmp_path: Path) -> None:
|
||||
"""Test list_yaml_files with only directories."""
|
||||
dir1 = tmp_path / "dir1"
|
||||
dir1.mkdir()
|
||||
dir2 = tmp_path / "dir2"
|
||||
dir2.mkdir()
|
||||
|
||||
(dir1 / "a.yaml").write_text("test: a")
|
||||
(dir1 / "b.yml").write_text("test: b")
|
||||
(dir2 / "c.yaml").write_text("test: c")
|
||||
|
||||
result = util.list_yaml_files([str(dir1), str(dir2)])
|
||||
|
||||
assert set(result) == {
|
||||
str(dir1 / "a.yaml"),
|
||||
str(dir1 / "b.yml"),
|
||||
str(dir2 / "c.yaml"),
|
||||
}
|
||||
assert result == sorted(result)
|
||||
|
||||
|
||||
def test_list_yaml_files_only_files(tmp_path: Path) -> None:
|
||||
"""Test list_yaml_files with only files."""
|
||||
file1 = tmp_path / "file1.yaml"
|
||||
file2 = tmp_path / "file2.yml"
|
||||
file3 = tmp_path / "file3.yaml"
|
||||
non_yaml = tmp_path / "not_yaml.json"
|
||||
|
||||
file1.write_text("test: 1")
|
||||
file2.write_text("test: 2")
|
||||
file3.write_text("test: 3")
|
||||
non_yaml.write_text("{}")
|
||||
|
||||
# Include a non-YAML file to test filtering
|
||||
result = util.list_yaml_files(
|
||||
[
|
||||
str(file1),
|
||||
str(file2),
|
||||
str(file3),
|
||||
str(non_yaml),
|
||||
]
|
||||
)
|
||||
|
||||
assert set(result) == {
|
||||
str(file1),
|
||||
str(file2),
|
||||
str(file3),
|
||||
}
|
||||
assert result == sorted(result)
|
||||
|
||||
|
||||
def test_list_yaml_files_empty_directory(tmp_path: Path) -> None:
|
||||
"""Test list_yaml_files with an empty directory."""
|
||||
empty_dir = tmp_path / "empty"
|
||||
empty_dir.mkdir()
|
||||
|
||||
result = util.list_yaml_files([str(empty_dir)])
|
||||
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_list_yaml_files_nonexistent_path(tmp_path: Path) -> None:
|
||||
"""Test list_yaml_files with a nonexistent path raises an error."""
|
||||
nonexistent = tmp_path / "nonexistent"
|
||||
existing = tmp_path / "existing.yaml"
|
||||
existing.write_text("test: 1")
|
||||
|
||||
# Should raise an error for non-existent directory
|
||||
with pytest.raises(FileNotFoundError):
|
||||
util.list_yaml_files([str(nonexistent), str(existing)])
|
||||
|
||||
|
||||
def test_list_yaml_files_mixed_extensions(tmp_path: Path) -> None:
|
||||
"""Test that both .yaml and .yml extensions are recognized."""
|
||||
dir1 = tmp_path / "configs"
|
||||
dir1.mkdir()
|
||||
|
||||
yaml_file = dir1 / "config.yaml"
|
||||
yml_file = dir1 / "config.yml"
|
||||
other_file = dir1 / "config.txt"
|
||||
|
||||
yaml_file.write_text("test: yaml")
|
||||
yml_file.write_text("test: yml")
|
||||
other_file.write_text("test: txt")
|
||||
|
||||
result = util.list_yaml_files([str(dir1)])
|
||||
|
||||
assert set(result) == {
|
||||
str(yaml_file),
|
||||
str(yml_file),
|
||||
}
|
Reference in New Issue
Block a user