mirror of
https://github.com/esphome/esphome.git
synced 2024-11-30 18:54:14 +01:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
b9ac2bb8a0
125 changed files with 1865 additions and 713 deletions
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -96,12 +96,12 @@ jobs:
|
||||||
uses: docker/setup-qemu-action@v3.0.0
|
uses: docker/setup-qemu-action@v3.0.0
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
uses: docker/login-action@v3.1.0
|
uses: docker/login-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
uses: docker/login-action@v3.1.0
|
uses: docker/login-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
@ -188,13 +188,13 @@ jobs:
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
uses: docker/login-action@v3.1.0
|
uses: docker/login-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Log in to the GitHub container registry
|
- name: Log in to the GitHub container registry
|
||||||
if: matrix.registry == 'ghcr'
|
if: matrix.registry == 'ghcr'
|
||||||
uses: docker/login-action@v3.1.0
|
uses: docker/login-action@v3.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
|
@ -36,7 +36,7 @@ jobs:
|
||||||
python ./script/sync-device_class.py
|
python ./script/sync-device_class.py
|
||||||
|
|
||||||
- name: Commit changes
|
- name: Commit changes
|
||||||
uses: peter-evans/create-pull-request@v6.0.4
|
uses: peter-evans/create-pull-request@v6.0.5
|
||||||
with:
|
with:
|
||||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||||
committer: esphomebot <esphome@nabucasa.com>
|
committer: esphomebot <esphome@nabucasa.com>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||||
rev: 24.2.0
|
rev: 24.4.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args:
|
args:
|
||||||
|
|
|
@ -153,6 +153,10 @@ esphome/components/grove_tb6612fng/* @max246
|
||||||
esphome/components/growatt_solar/* @leeuwte
|
esphome/components/growatt_solar/* @leeuwte
|
||||||
esphome/components/gt911/* @clydebarrow @jesserockz
|
esphome/components/gt911/* @clydebarrow @jesserockz
|
||||||
esphome/components/haier/* @paveldn
|
esphome/components/haier/* @paveldn
|
||||||
|
esphome/components/haier/binary_sensor/* @paveldn
|
||||||
|
esphome/components/haier/button/* @paveldn
|
||||||
|
esphome/components/haier/sensor/* @paveldn
|
||||||
|
esphome/components/haier/text_sensor/* @paveldn
|
||||||
esphome/components/havells_solar/* @sourabhjaiswal
|
esphome/components/havells_solar/* @sourabhjaiswal
|
||||||
esphome/components/hbridge/fan/* @WeekendWarrior
|
esphome/components/hbridge/fan/* @WeekendWarrior
|
||||||
esphome/components/hbridge/light/* @DotNetDann
|
esphome/components/hbridge/light/* @DotNetDann
|
||||||
|
@ -307,7 +311,7 @@ esphome/components/rp2040_pwm/* @jesserockz
|
||||||
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
esphome/components/rpi_dpi_rgb/* @clydebarrow
|
||||||
esphome/components/rtl87xx/* @kuba2k2
|
esphome/components/rtl87xx/* @kuba2k2
|
||||||
esphome/components/rtttl/* @glmnet
|
esphome/components/rtttl/* @glmnet
|
||||||
esphome/components/safe_mode/* @jsuanet @paulmonigatti
|
esphome/components/safe_mode/* @jsuanet @kbx81 @paulmonigatti
|
||||||
esphome/components/scd4x/* @martgras @sjtrny
|
esphome/components/scd4x/* @martgras @sjtrny
|
||||||
esphome/components/script/* @esphome/core
|
esphome/components/script/* @esphome/core
|
||||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||||
|
@ -411,7 +415,7 @@ esphome/components/veml3235/* @kbx81
|
||||||
esphome/components/veml7700/* @latonita
|
esphome/components/veml7700/* @latonita
|
||||||
esphome/components/version/* @esphome/core
|
esphome/components/version/* @esphome/core
|
||||||
esphome/components/voice_assistant/* @jesserockz
|
esphome/components/voice_assistant/* @jesserockz
|
||||||
esphome/components/wake_on_lan/* @willwill2will54
|
esphome/components/wake_on_lan/* @clydebarrow @willwill2will54
|
||||||
esphome/components/waveshare_epaper/* @clydebarrow
|
esphome/components/waveshare_epaper/* @clydebarrow
|
||||||
esphome/components/web_server_base/* @OttoWinter
|
esphome/components/web_server_base/* @OttoWinter
|
||||||
esphome/components/web_server_idf/* @dentra
|
esphome/components/web_server_idf/* @dentra
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include "ade7880_registers.h"
|
#include "ade7880_registers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ade7880 {
|
namespace ade7880 {
|
||||||
|
|
||||||
|
@ -156,7 +158,7 @@ void ADE7880::update() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "update took %u ms", millis() - start);
|
ESP_LOGD(TAG, "update took %" PRIu32 " ms", millis() - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADE7880::dump_config() {
|
void ADE7880::dump_config() {
|
||||||
|
@ -176,9 +178,9 @@ void ADE7880::dump_config() {
|
||||||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy);
|
LOG_SENSOR(" ", "Forward Active Energy", this->channel_a_->forward_active_energy);
|
||||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
|
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_a_->reverse_active_energy);
|
||||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_a_->current_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_a_->current_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_a_->voltage_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_a_->voltage_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_a_->power_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_a_->power_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration);
|
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_a_->phase_angle_calibration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,9 +194,9 @@ void ADE7880::dump_config() {
|
||||||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy);
|
LOG_SENSOR(" ", "Forward Active Energy", this->channel_b_->forward_active_energy);
|
||||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
|
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_b_->reverse_active_energy);
|
||||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_b_->current_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_b_->current_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_b_->voltage_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_b_->voltage_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_b_->power_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_b_->power_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration);
|
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_b_->phase_angle_calibration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,9 +210,9 @@ void ADE7880::dump_config() {
|
||||||
LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy);
|
LOG_SENSOR(" ", "Forward Active Energy", this->channel_c_->forward_active_energy);
|
||||||
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
|
LOG_SENSOR(" ", "Reverse Active Energy", this->channel_c_->reverse_active_energy);
|
||||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_c_->current_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_c_->current_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Voltage: %d", this->channel_c_->voltage_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Voltage: %" PRId32, this->channel_c_->voltage_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Power: %d", this->channel_c_->power_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Power: %" PRId32, this->channel_c_->power_gain_calibration);
|
||||||
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration);
|
ESP_LOGCONFIG(TAG, " Phase Angle: %u", this->channel_c_->phase_angle_calibration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,7 +220,7 @@ void ADE7880::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Neutral:");
|
ESP_LOGCONFIG(TAG, " Neutral:");
|
||||||
LOG_SENSOR(" ", "Current", this->channel_n_->current);
|
LOG_SENSOR(" ", "Current", this->channel_n_->current);
|
||||||
ESP_LOGCONFIG(TAG, " Calibration:");
|
ESP_LOGCONFIG(TAG, " Calibration:");
|
||||||
ESP_LOGCONFIG(TAG, " Current: %u", this->channel_n_->current_gain_calibration);
|
ESP_LOGCONFIG(TAG, " Current: %" PRId32, this->channel_n_->current_gain_calibration);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "ade7953_base.h"
|
#include "ade7953_base.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ade7953_base {
|
namespace ade7953_base {
|
||||||
|
|
||||||
|
@ -105,7 +107,7 @@ void ADE7953::update() {
|
||||||
this->last_update_ = now;
|
this->last_update_ = now;
|
||||||
// prevent DIV/0
|
// prevent DIV/0
|
||||||
pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000;
|
pf = ADE_WATTSEC_POWER_FACTOR * (diff < 10 ? 10 : diff) / 1000;
|
||||||
ESP_LOGVV(TAG, "ADE7953::update() diff=%d pf=%f", diff, pf);
|
ESP_LOGVV(TAG, "ADE7953::update() diff=%" PRIu32 " pf=%f", diff, pf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apparent power
|
// Apparent power
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "ags10.h"
|
#include "ags10.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ags10 {
|
namespace ags10 {
|
||||||
static const char *const TAG = "ags10";
|
static const char *const TAG = "ags10";
|
||||||
|
@ -35,7 +37,7 @@ void AGS10Component::setup() {
|
||||||
|
|
||||||
auto resistance = this->read_resistance_();
|
auto resistance = this->read_resistance_();
|
||||||
if (resistance) {
|
if (resistance) {
|
||||||
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08X", *resistance);
|
ESP_LOGD(TAG, "AGS10 Sensor Resistance: 0x%08" PRIX32, *resistance);
|
||||||
if (this->resistance_ != nullptr) {
|
if (this->resistance_ != nullptr) {
|
||||||
this->resistance_->publish_state(*resistance);
|
this->resistance_->publish_state(*resistance);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "ct_clamp_sensor.h"
|
#include "ct_clamp_sensor.h"
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include <cinttypes>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -37,8 +38,8 @@ void CTClampSensor::update() {
|
||||||
float rms_ac = 0;
|
float rms_ac = 0;
|
||||||
if (rms_ac_squared > 0)
|
if (rms_ac_squared > 0)
|
||||||
rms_ac = std::sqrt(rms_ac_squared);
|
rms_ac = std::sqrt(rms_ac_squared);
|
||||||
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %d different samples (%d SPS)", this->name_.c_str(), rms_ac,
|
ESP_LOGD(TAG, "'%s' - Raw AC Value: %.3fA after %" PRIu32 " different samples (%" PRIu32 " SPS)",
|
||||||
this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
|
this->name_.c_str(), rms_ac, this->num_samples_, 1000 * this->num_samples_ / this->sample_duration_);
|
||||||
this->publish_state(rms_ac);
|
this->publish_state(rms_ac);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ std::string DebugComponent::get_reset_reason_() { return lt_get_reboot_reason_na
|
||||||
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
|
uint32_t DebugComponent::get_free_heap_() { return lt_heap_get_free(); }
|
||||||
|
|
||||||
void DebugComponent::get_device_info_(std::string &device_info) {
|
void DebugComponent::get_device_info_(std::string &device_info) {
|
||||||
reset_reason = get_reset_reason_();
|
str::string reset_reason = get_reset_reason_();
|
||||||
ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
|
ESP_LOGD(TAG, "LibreTiny Version: %s", lt_get_version());
|
||||||
ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
|
ESP_LOGD(TAG, "Chip: %s (%04x) @ %u MHz", lt_cpu_get_model_name(), lt_cpu_get_model(), lt_cpu_get_freq_mhz());
|
||||||
ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
|
ESP_LOGD(TAG, "Chip ID: 0x%06X", lt_cpu_get_mac_id());
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
from esphome.cpp_generator import RawExpression
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
from esphome.components.ota import BASE_OTA_SCHEMA, ota_to_code, OTAComponent
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_NUM_ATTEMPTS,
|
CONF_NUM_ATTEMPTS,
|
||||||
CONF_OTA,
|
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
CONF_REBOOT_TIMEOUT,
|
CONF_REBOOT_TIMEOUT,
|
||||||
CONF_SAFE_MODE,
|
CONF_SAFE_MODE,
|
||||||
CONF_VERSION,
|
CONF_VERSION,
|
||||||
KEY_PAST_SAFE_MODE,
|
|
||||||
)
|
)
|
||||||
from esphome.core import CORE, coroutine_with_priority
|
from esphome.core import coroutine_with_priority
|
||||||
|
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
@ -28,7 +25,6 @@ CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(ESPHomeOTAComponent),
|
cv.GenerateID(): cv.declare_id(ESPHomeOTAComponent),
|
||||||
cv.Optional(CONF_SAFE_MODE, default=True): cv.boolean,
|
|
||||||
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2, int=True),
|
||||||
cv.SplitDefault(
|
cv.SplitDefault(
|
||||||
CONF_PORT,
|
CONF_PORT,
|
||||||
|
@ -39,10 +35,15 @@ CONFIG_SCHEMA = (
|
||||||
rtl87xx=8892,
|
rtl87xx=8892,
|
||||||
): cv.port,
|
): cv.port,
|
||||||
cv.Optional(CONF_PASSWORD): cv.string,
|
cv.Optional(CONF_PASSWORD): cv.string,
|
||||||
cv.Optional(
|
cv.Optional(CONF_NUM_ATTEMPTS): cv.invalid(
|
||||||
CONF_REBOOT_TIMEOUT, default="5min"
|
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||||
): cv.positive_time_period_milliseconds,
|
),
|
||||||
cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int,
|
cv.Optional(CONF_REBOOT_TIMEOUT): cv.invalid(
|
||||||
|
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_SAFE_MODE): cv.invalid(
|
||||||
|
f"'{CONF_SAFE_MODE}' (and its related configuration variables) has moved from 'ota' to its own component. See https://esphome.io/components/safe_mode"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(BASE_OTA_SCHEMA)
|
.extend(BASE_OTA_SCHEMA)
|
||||||
|
@ -50,10 +51,8 @@ CONFIG_SCHEMA = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(50.0)
|
@coroutine_with_priority(52.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
CORE.data[CONF_OTA] = {}
|
|
||||||
|
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await ota_to_code(var, config)
|
await ota_to_code(var, config)
|
||||||
cg.add(var.set_port(config[CONF_PORT]))
|
cg.add(var.set_port(config[CONF_PORT]))
|
||||||
|
@ -63,10 +62,3 @@ async def to_code(config):
|
||||||
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
|
cg.add_define("USE_OTA_VERSION", config[CONF_VERSION])
|
||||||
|
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
if config[CONF_SAFE_MODE]:
|
|
||||||
condition = var.should_enter_safe_mode(
|
|
||||||
config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT]
|
|
||||||
)
|
|
||||||
cg.add(RawExpression(f"if ({condition}) return"))
|
|
||||||
CORE.data[CONF_OTA][KEY_PAST_SAFE_MODE] = True
|
|
||||||
|
|
|
@ -78,23 +78,9 @@ void ESPHomeOTAComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Password configured");
|
ESP_LOGCONFIG(TAG, " Password configured");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (this->has_safe_mode_ && this->safe_mode_rtc_value_ > 1 &&
|
|
||||||
this->safe_mode_rtc_value_ != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
|
||||||
ESP_LOGW(TAG, "Last reset occurred too quickly; safe mode will be invoked in %" PRIu32 " restarts",
|
|
||||||
this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESPHomeOTAComponent::loop() {
|
void ESPHomeOTAComponent::loop() { this->handle_(); }
|
||||||
this->handle_();
|
|
||||||
|
|
||||||
if (this->has_safe_mode_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) {
|
|
||||||
this->has_safe_mode_ = false;
|
|
||||||
// successful boot, reset counter
|
|
||||||
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
|
||||||
this->clean_rtc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
static const uint8_t FEATURE_SUPPORTS_COMPRESSION = 0x01;
|
||||||
|
|
||||||
|
@ -423,86 +409,4 @@ bool ESPHomeOTAComponent::writeall_(const uint8_t *buf, size_t len) {
|
||||||
float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
float ESPHomeOTAComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||||
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
|
uint16_t ESPHomeOTAComponent::get_port() const { return this->port_; }
|
||||||
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
|
void ESPHomeOTAComponent::set_port(uint16_t port) { this->port_ = port; }
|
||||||
|
|
||||||
void ESPHomeOTAComponent::set_safe_mode_pending(const bool &pending) {
|
|
||||||
if (!this->has_safe_mode_)
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint32_t current_rtc = this->read_rtc_();
|
|
||||||
|
|
||||||
if (pending && current_rtc != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
|
||||||
ESP_LOGI(TAG, "Device will enter safe mode on next boot");
|
|
||||||
this->write_rtc_(ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pending && current_rtc == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC) {
|
|
||||||
ESP_LOGI(TAG, "Safe mode pending has been cleared");
|
|
||||||
this->clean_rtc();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool ESPHomeOTAComponent::get_safe_mode_pending() {
|
|
||||||
return this->has_safe_mode_ && this->read_rtc_() == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ESPHomeOTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
|
||||||
this->has_safe_mode_ = true;
|
|
||||||
this->safe_mode_start_time_ = millis();
|
|
||||||
this->safe_mode_enable_time_ = enable_time;
|
|
||||||
this->safe_mode_num_attempts_ = num_attempts;
|
|
||||||
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
|
||||||
this->safe_mode_rtc_value_ = this->read_rtc_();
|
|
||||||
|
|
||||||
bool is_manual_safe_mode = this->safe_mode_rtc_value_ == ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC;
|
|
||||||
|
|
||||||
if (is_manual_safe_mode) {
|
|
||||||
ESP_LOGI(TAG, "Safe mode has been entered manually");
|
|
||||||
} else {
|
|
||||||
ESP_LOGCONFIG(TAG, "There have been %" PRIu32 " suspected unsuccessful boot attempts", this->safe_mode_rtc_value_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) {
|
|
||||||
this->clean_rtc();
|
|
||||||
|
|
||||||
if (!is_manual_safe_mode) {
|
|
||||||
ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode");
|
|
||||||
}
|
|
||||||
|
|
||||||
this->status_set_error();
|
|
||||||
this->set_timeout(enable_time, []() {
|
|
||||||
ESP_LOGE(TAG, "No OTA attempt made, restarting");
|
|
||||||
App.reboot();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised.
|
|
||||||
delay(300); // NOLINT
|
|
||||||
App.setup();
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Waiting for OTA attempt");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
// increment counter
|
|
||||||
this->write_rtc_(this->safe_mode_rtc_value_ + 1);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::write_rtc_(uint32_t val) {
|
|
||||||
this->rtc_.save(&val);
|
|
||||||
global_preferences->sync();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t ESPHomeOTAComponent::read_rtc_() {
|
|
||||||
uint32_t val;
|
|
||||||
if (!this->rtc_.load(&val))
|
|
||||||
return 0;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::clean_rtc() { this->write_rtc_(0); }
|
|
||||||
|
|
||||||
void ESPHomeOTAComponent::on_safe_shutdown() {
|
|
||||||
if (this->has_safe_mode_ && this->read_rtc_() != ESPHomeOTAComponent::ENTER_SAFE_MODE_MAGIC)
|
|
||||||
this->clean_rtc();
|
|
||||||
}
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -15,17 +15,9 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||||
void set_auth_password(const std::string &password) { password_ = password; }
|
void set_auth_password(const std::string &password) { password_ = password; }
|
||||||
#endif // USE_OTA_PASSWORD
|
#endif // USE_OTA_PASSWORD
|
||||||
|
|
||||||
/// Manually set the port OTA should listen on.
|
/// Manually set the port OTA should listen on
|
||||||
void set_port(uint16_t port);
|
void set_port(uint16_t port);
|
||||||
|
|
||||||
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time);
|
|
||||||
|
|
||||||
/// Set to true if the next startup will enter safe mode
|
|
||||||
void set_safe_mode_pending(const bool &pending);
|
|
||||||
bool get_safe_mode_pending();
|
|
||||||
|
|
||||||
// ========== INTERNAL METHODS ==========
|
|
||||||
// (In most use cases you won't need these)
|
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override;
|
float get_setup_priority() const override;
|
||||||
|
@ -33,14 +25,7 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||||
|
|
||||||
uint16_t get_port() const;
|
uint16_t get_port() const;
|
||||||
|
|
||||||
void clean_rtc();
|
|
||||||
|
|
||||||
void on_safe_shutdown() override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void write_rtc_(uint32_t val);
|
|
||||||
uint32_t read_rtc_();
|
|
||||||
|
|
||||||
void handle_();
|
void handle_();
|
||||||
bool readall_(uint8_t *buf, size_t len);
|
bool readall_(uint8_t *buf, size_t len);
|
||||||
bool writeall_(const uint8_t *buf, size_t len);
|
bool writeall_(const uint8_t *buf, size_t len);
|
||||||
|
@ -53,16 +38,6 @@ class ESPHomeOTAComponent : public ota::OTAComponent {
|
||||||
|
|
||||||
std::unique_ptr<socket::Socket> server_;
|
std::unique_ptr<socket::Socket> server_;
|
||||||
std::unique_ptr<socket::Socket> client_;
|
std::unique_ptr<socket::Socket> client_;
|
||||||
|
|
||||||
bool has_safe_mode_{false}; ///< stores whether safe mode can be enabled
|
|
||||||
uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled
|
|
||||||
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should be on for
|
|
||||||
uint32_t safe_mode_rtc_value_;
|
|
||||||
uint8_t safe_mode_num_attempts_;
|
|
||||||
ESPPreferenceObject rtc_;
|
|
||||||
|
|
||||||
static const uint32_t ENTER_SAFE_MODE_MAGIC =
|
|
||||||
0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
|
@ -98,11 +98,15 @@ void EthernetComponent::setup() {
|
||||||
.post_cb = nullptr,
|
.post_cb = nullptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if USE_ESP_IDF && (ESP_IDF_VERSION_MAJOR >= 5)
|
||||||
|
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg);
|
||||||
|
#else
|
||||||
spi_device_handle_t spi_handle = nullptr;
|
spi_device_handle_t spi_handle = nullptr;
|
||||||
err = spi_bus_add_device(host, &devcfg, &spi_handle);
|
err = spi_bus_add_device(host, &devcfg, &spi_handle);
|
||||||
ESPHL_ERROR_CHECK(err, "SPI bus add device error");
|
ESPHL_ERROR_CHECK(err, "SPI bus add device error");
|
||||||
|
|
||||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
|
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(spi_handle);
|
||||||
|
#endif
|
||||||
w5500_config.int_gpio_num = this->interrupt_pin_;
|
w5500_config.int_gpio_num = this->interrupt_pin_;
|
||||||
phy_config.phy_addr = this->phy_addr_spi_;
|
phy_config.phy_addr = this->phy_addr_spi_;
|
||||||
phy_config.reset_gpio_num = this->reset_pin_;
|
phy_config.reset_gpio_num = this->reset_pin_;
|
||||||
|
@ -614,14 +618,14 @@ void EthernetComponent::rtl8201_set_rmii_mode_(esp_eth_mac_t *mac) {
|
||||||
|
|
||||||
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
|
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
|
||||||
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
|
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
|
||||||
ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04X", phy_rmii_mode);
|
ESP_LOGV(TAG, "Hardware default RTL8201 RMII Mode Register is: 0x%04" PRIX32, phy_rmii_mode);
|
||||||
|
|
||||||
err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA);
|
err = mac->write_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, 0x1FFA);
|
||||||
ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed");
|
ESPHL_ERROR_CHECK(err, "Setting Register 16 RMII Mode Setting failed");
|
||||||
|
|
||||||
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
|
err = mac->read_phy_reg(mac, this->phy_addr_, RTL8201_RMSR_REG_ADDR, &(phy_rmii_mode));
|
||||||
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
|
ESPHL_ERROR_CHECK(err, "Read PHY RMSR Register failed");
|
||||||
ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04X", phy_rmii_mode);
|
ESP_LOGV(TAG, "Setting RTL8201 RMII Mode Register to: 0x%04" PRIX32, phy_rmii_mode);
|
||||||
|
|
||||||
err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0);
|
err = mac->write_phy_reg(mac, this->phy_addr_, 0x1f, 0x0);
|
||||||
ESPHL_ERROR_CHECK(err, "Setting Page 0 failed");
|
ESPHL_ERROR_CHECK(err, "Setting Page 0 failed");
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "esp_eth.h"
|
#include "esp_eth.h"
|
||||||
#include "esp_eth_mac.h"
|
#include "esp_eth_mac.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
|
#include "esp_mac.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace ethernet {
|
namespace ethernet {
|
||||||
|
|
|
@ -377,7 +377,7 @@ uint8_t FingerprintGrowComponent::transfer_(std::vector<uint8_t> *p_data_buffer)
|
||||||
this->write((uint8_t) (wire_length >> 8));
|
this->write((uint8_t) (wire_length >> 8));
|
||||||
this->write((uint8_t) (wire_length & 0xFF));
|
this->write((uint8_t) (wire_length & 0xFF));
|
||||||
|
|
||||||
uint16_t sum = ((wire_length) >> 8) + ((wire_length) &0xFF) + COMMAND;
|
uint16_t sum = (wire_length >> 8) + (wire_length & 0xFF) + COMMAND;
|
||||||
for (auto data : *p_data_buffer) {
|
for (auto data : *p_data_buffer) {
|
||||||
this->write(data);
|
this->write(data);
|
||||||
sum += data;
|
sum += data;
|
||||||
|
@ -541,34 +541,34 @@ void FingerprintGrowComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s",
|
ESP_LOGCONFIG(TAG, " Sensor Power Pin: %s",
|
||||||
this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
|
this->has_power_pin_ ? this->sensor_power_pin_->dump_summary().c_str() : "None");
|
||||||
if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
|
if (this->idle_period_to_sleep_ms_ < UINT32_MAX) {
|
||||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %u ms", this->idle_period_to_sleep_ms_);
|
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: %" PRIu32 " ms", this->idle_period_to_sleep_ms_);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never");
|
ESP_LOGCONFIG(TAG, " Idle Period to Sleep: Never");
|
||||||
}
|
}
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
if (this->fingerprint_count_sensor_) {
|
if (this->fingerprint_count_sensor_) {
|
||||||
LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
|
LOG_SENSOR(" ", "Fingerprint Count", this->fingerprint_count_sensor_);
|
||||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->fingerprint_count_sensor_->get_state());
|
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->fingerprint_count_sensor_->get_state());
|
||||||
}
|
}
|
||||||
if (this->status_sensor_) {
|
if (this->status_sensor_) {
|
||||||
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
LOG_SENSOR(" ", "Status", this->status_sensor_);
|
||||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->status_sensor_->get_state());
|
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->status_sensor_->get_state());
|
||||||
}
|
}
|
||||||
if (this->capacity_sensor_) {
|
if (this->capacity_sensor_) {
|
||||||
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
|
LOG_SENSOR(" ", "Capacity", this->capacity_sensor_);
|
||||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint16_t) this->capacity_sensor_->get_state());
|
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint16_t) this->capacity_sensor_->get_state());
|
||||||
}
|
}
|
||||||
if (this->security_level_sensor_) {
|
if (this->security_level_sensor_) {
|
||||||
LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
|
LOG_SENSOR(" ", "Security Level", this->security_level_sensor_);
|
||||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint8_t) this->security_level_sensor_->get_state());
|
ESP_LOGCONFIG(TAG, " Current Value: %u", (uint8_t) this->security_level_sensor_->get_state());
|
||||||
}
|
}
|
||||||
if (this->last_finger_id_sensor_) {
|
if (this->last_finger_id_sensor_) {
|
||||||
LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
|
LOG_SENSOR(" ", "Last Finger ID", this->last_finger_id_sensor_);
|
||||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_finger_id_sensor_->get_state());
|
ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_finger_id_sensor_->get_state());
|
||||||
}
|
}
|
||||||
if (this->last_confidence_sensor_) {
|
if (this->last_confidence_sensor_) {
|
||||||
LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
|
LOG_SENSOR(" ", "Last Confidence", this->last_confidence_sensor_);
|
||||||
ESP_LOGCONFIG(TAG, " Current Value: %d", (uint32_t) this->last_confidence_sensor_->get_state());
|
ESP_LOGCONFIG(TAG, " Current Value: %" PRIu32, (uint32_t) this->last_confidence_sensor_->get_state());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ template<typename... Ts> class BeeperOffAction : public Action<Ts...> {
|
||||||
template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
|
template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
VerticalAirflowAction(HonClimate *parent) : parent_(parent) {}
|
VerticalAirflowAction(HonClimate *parent) : parent_(parent) {}
|
||||||
TEMPLATABLE_VALUE(AirflowVerticalDirection, direction)
|
TEMPLATABLE_VALUE(hon_protocol::VerticalSwingMode, direction)
|
||||||
void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); }
|
void play(Ts... x) { this->parent_->set_vertical_airflow(this->direction_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -56,7 +56,7 @@ template<typename... Ts> class VerticalAirflowAction : public Action<Ts...> {
|
||||||
template<typename... Ts> class HorizontalAirflowAction : public Action<Ts...> {
|
template<typename... Ts> class HorizontalAirflowAction : public Action<Ts...> {
|
||||||
public:
|
public:
|
||||||
HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {}
|
HorizontalAirflowAction(HonClimate *parent) : parent_(parent) {}
|
||||||
TEMPLATABLE_VALUE(AirflowHorizontalDirection, direction)
|
TEMPLATABLE_VALUE(hon_protocol::HorizontalSwingMode, direction)
|
||||||
void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); }
|
void play(Ts... x) { this->parent_->set_horizontal_airflow(this->direction_.value(x...)); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -11,6 +11,7 @@ from ..climate import (
|
||||||
HonClimate,
|
HonClimate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@paveldn"]
|
||||||
BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True)
|
BinarySensorTypeEnum = HonClimate.enum("SubBinarySensorType", True)
|
||||||
|
|
||||||
# Haier sensors
|
# Haier sensors
|
||||||
|
|
41
esphome/components/haier/button/__init__.py
Normal file
41
esphome/components/haier/button/__init__.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import button
|
||||||
|
from ..climate import (
|
||||||
|
CONF_HAIER_ID,
|
||||||
|
HonClimate,
|
||||||
|
haier_ns,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@paveldn"]
|
||||||
|
SelfCleaningButton = haier_ns.class_("SelfCleaningButton", button.Button)
|
||||||
|
SteriCleaningButton = haier_ns.class_("SteriCleaningButton", button.Button)
|
||||||
|
|
||||||
|
|
||||||
|
# Haier buttons
|
||||||
|
CONF_SELF_CLEANING = "self_cleaning"
|
||||||
|
CONF_STERI_CLEANING = "steri_cleaning"
|
||||||
|
|
||||||
|
# Additional icons
|
||||||
|
ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||||
|
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
||||||
|
SelfCleaningButton,
|
||||||
|
icon=ICON_SPRAY_BOTTLE,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_STERI_CLEANING): button.button_schema(
|
||||||
|
SteriCleaningButton,
|
||||||
|
icon=ICON_SPRAY_BOTTLE,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
for button_type in [CONF_SELF_CLEANING, CONF_STERI_CLEANING]:
|
||||||
|
if conf := config.get(button_type):
|
||||||
|
btn = await button.new_button(conf)
|
||||||
|
await cg.register_parented(btn, config[CONF_HAIER_ID])
|
9
esphome/components/haier/button/self_cleaning.cpp
Normal file
9
esphome/components/haier/button/self_cleaning.cpp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#include "self_cleaning.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
void SelfCleaningButton::press_action() { this->parent_->start_self_cleaning(); }
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
18
esphome/components/haier/button/self_cleaning.h
Normal file
18
esphome/components/haier/button/self_cleaning.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/button/button.h"
|
||||||
|
#include "../hon_climate.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
class SelfCleaningButton : public button::Button, public Parented<HonClimate> {
|
||||||
|
public:
|
||||||
|
SelfCleaningButton() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void press_action() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
9
esphome/components/haier/button/steri_cleaning.cpp
Normal file
9
esphome/components/haier/button/steri_cleaning.cpp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#include "steri_cleaning.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
void SteriCleaningButton::press_action() { this->parent_->start_steri_cleaning(); }
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
18
esphome/components/haier/button/steri_cleaning.h
Normal file
18
esphome/components/haier/button/steri_cleaning.h
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/button/button.h"
|
||||||
|
#include "../hon_climate.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace haier {
|
||||||
|
|
||||||
|
class SteriCleaningButton : public button::Button, public Parented<HonClimate> {
|
||||||
|
public:
|
||||||
|
SteriCleaningButton() = default;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void press_action() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace haier
|
||||||
|
} // namespace esphome
|
|
@ -55,6 +55,7 @@ PROTOCOL_HON = "HON"
|
||||||
PROTOCOL_SMARTAIR2 = "SMARTAIR2"
|
PROTOCOL_SMARTAIR2 = "SMARTAIR2"
|
||||||
|
|
||||||
haier_ns = cg.esphome_ns.namespace("haier")
|
haier_ns = cg.esphome_ns.namespace("haier")
|
||||||
|
hon_protocol_ns = haier_ns.namespace("hon_protocol")
|
||||||
HaierClimateBase = haier_ns.class_(
|
HaierClimateBase = haier_ns.class_(
|
||||||
"HaierClimateBase", uart.UARTDevice, climate.Climate, cg.Component
|
"HaierClimateBase", uart.UARTDevice, climate.Climate, cg.Component
|
||||||
)
|
)
|
||||||
|
@ -63,7 +64,7 @@ Smartair2Climate = haier_ns.class_("Smartair2Climate", HaierClimateBase)
|
||||||
|
|
||||||
CONF_HAIER_ID = "haier_id"
|
CONF_HAIER_ID = "haier_id"
|
||||||
|
|
||||||
AirflowVerticalDirection = haier_ns.enum("AirflowVerticalDirection", True)
|
AirflowVerticalDirection = hon_protocol_ns.enum("VerticalSwingMode", True)
|
||||||
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
|
AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
|
||||||
"HEALTH_UP": AirflowVerticalDirection.HEALTH_UP,
|
"HEALTH_UP": AirflowVerticalDirection.HEALTH_UP,
|
||||||
"MAX_UP": AirflowVerticalDirection.MAX_UP,
|
"MAX_UP": AirflowVerticalDirection.MAX_UP,
|
||||||
|
@ -73,7 +74,7 @@ AIRFLOW_VERTICAL_DIRECTION_OPTIONS = {
|
||||||
"HEALTH_DOWN": AirflowVerticalDirection.HEALTH_DOWN,
|
"HEALTH_DOWN": AirflowVerticalDirection.HEALTH_DOWN,
|
||||||
}
|
}
|
||||||
|
|
||||||
AirflowHorizontalDirection = haier_ns.enum("AirflowHorizontalDirection", True)
|
AirflowHorizontalDirection = hon_protocol_ns.enum("HorizontalSwingMode", True)
|
||||||
AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = {
|
AIRFLOW_HORIZONTAL_DIRECTION_OPTIONS = {
|
||||||
"MAX_LEFT": AirflowHorizontalDirection.MAX_LEFT,
|
"MAX_LEFT": AirflowHorizontalDirection.MAX_LEFT,
|
||||||
"LEFT": AirflowHorizontalDirection.LEFT,
|
"LEFT": AirflowHorizontalDirection.LEFT,
|
||||||
|
@ -483,4 +484,4 @@ async def to_code(config):
|
||||||
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
|
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
|
||||||
)
|
)
|
||||||
# https://github.com/paveldn/HaierProtocol
|
# https://github.com/paveldn/HaierProtocol
|
||||||
cg.add_library("pavlodn/HaierProtocol", "0.9.25")
|
cg.add_library("pavlodn/HaierProtocol", "0.9.28")
|
||||||
|
|
|
@ -234,6 +234,7 @@ void HaierClimateBase::setup() {
|
||||||
this->haier_protocol_.set_default_timeout_handler(
|
this->haier_protocol_.set_default_timeout_handler(
|
||||||
std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1));
|
std::bind(&esphome::haier::HaierClimateBase::timeout_default_handler_, this, std::placeholders::_1));
|
||||||
this->set_handlers();
|
this->set_handlers();
|
||||||
|
this->initialization();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HaierClimateBase::dump_config() {
|
void HaierClimateBase::dump_config() {
|
||||||
|
@ -326,7 +327,7 @@ ClimateTraits HaierClimateBase::traits() { return traits_; }
|
||||||
|
|
||||||
void HaierClimateBase::control(const ClimateCall &call) {
|
void HaierClimateBase::control(const ClimateCall &call) {
|
||||||
ESP_LOGD("Control", "Control call");
|
ESP_LOGD("Control", "Control call");
|
||||||
if (this->protocol_phase_ < ProtocolPhases::IDLE) {
|
if (!this->valid_connection()) {
|
||||||
ESP_LOGW(TAG, "Can't send control packet, first poll answer not received");
|
ESP_LOGW(TAG, "Can't send control packet, first poll answer not received");
|
||||||
return; // cancel the control, we cant do it without a poll answer.
|
return; // cancel the control, we cant do it without a poll answer.
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class HaierClimateBase : public esphome::Component,
|
||||||
void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes);
|
void set_supported_modes(const std::set<esphome::climate::ClimateMode> &modes);
|
||||||
void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes);
|
void set_supported_swing_modes(const std::set<esphome::climate::ClimateSwingMode> &modes);
|
||||||
void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets);
|
void set_supported_presets(const std::set<esphome::climate::ClimatePreset> &presets);
|
||||||
bool valid_connection() { return this->protocol_phase_ >= ProtocolPhases::IDLE; };
|
bool valid_connection() const { return this->protocol_phase_ >= ProtocolPhases::IDLE; };
|
||||||
size_t available() noexcept override { return esphome::uart::UARTDevice::available(); };
|
size_t available() noexcept override { return esphome::uart::UARTDevice::available(); };
|
||||||
size_t read_array(uint8_t *data, size_t len) noexcept override {
|
size_t read_array(uint8_t *data, size_t len) noexcept override {
|
||||||
return esphome::uart::UARTDevice::read_array(data, len) ? len : 0;
|
return esphome::uart::UARTDevice::read_array(data, len) ? len : 0;
|
||||||
|
@ -80,6 +80,7 @@ class HaierClimateBase : public esphome::Component,
|
||||||
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
|
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
|
||||||
virtual haier_protocol::HaierMessage get_control_message() = 0;
|
virtual haier_protocol::HaierMessage get_control_message() = 0;
|
||||||
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;
|
virtual haier_protocol::HaierMessage get_power_message(bool state) = 0;
|
||||||
|
virtual void initialization(){};
|
||||||
virtual bool prepare_pending_action();
|
virtual bool prepare_pending_action();
|
||||||
virtual void process_protocol_reset();
|
virtual void process_protocol_reset();
|
||||||
esphome::climate::ClimateTraits traits() override;
|
esphome::climate::ClimateTraits traits() override;
|
||||||
|
|
|
@ -19,38 +19,6 @@ constexpr uint8_t CONTROL_MESSAGE_RETRIES = 5;
|
||||||
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
|
constexpr std::chrono::milliseconds CONTROL_MESSAGE_RETRIES_INTERVAL = std::chrono::milliseconds(500);
|
||||||
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
|
constexpr size_t ALARM_STATUS_REQUEST_INTERVAL_MS = 600000;
|
||||||
|
|
||||||
hon_protocol::VerticalSwingMode get_vertical_swing_mode(AirflowVerticalDirection direction) {
|
|
||||||
switch (direction) {
|
|
||||||
case AirflowVerticalDirection::HEALTH_UP:
|
|
||||||
return hon_protocol::VerticalSwingMode::HEALTH_UP;
|
|
||||||
case AirflowVerticalDirection::MAX_UP:
|
|
||||||
return hon_protocol::VerticalSwingMode::MAX_UP;
|
|
||||||
case AirflowVerticalDirection::UP:
|
|
||||||
return hon_protocol::VerticalSwingMode::UP;
|
|
||||||
case AirflowVerticalDirection::DOWN:
|
|
||||||
return hon_protocol::VerticalSwingMode::DOWN;
|
|
||||||
case AirflowVerticalDirection::HEALTH_DOWN:
|
|
||||||
return hon_protocol::VerticalSwingMode::HEALTH_DOWN;
|
|
||||||
default:
|
|
||||||
return hon_protocol::VerticalSwingMode::CENTER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hon_protocol::HorizontalSwingMode get_horizontal_swing_mode(AirflowHorizontalDirection direction) {
|
|
||||||
switch (direction) {
|
|
||||||
case AirflowHorizontalDirection::MAX_LEFT:
|
|
||||||
return hon_protocol::HorizontalSwingMode::MAX_LEFT;
|
|
||||||
case AirflowHorizontalDirection::LEFT:
|
|
||||||
return hon_protocol::HorizontalSwingMode::LEFT;
|
|
||||||
case AirflowHorizontalDirection::RIGHT:
|
|
||||||
return hon_protocol::HorizontalSwingMode::RIGHT;
|
|
||||||
case AirflowHorizontalDirection::MAX_RIGHT:
|
|
||||||
return hon_protocol::HorizontalSwingMode::MAX_RIGHT;
|
|
||||||
default:
|
|
||||||
return hon_protocol::HorizontalSwingMode::CENTER;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HonClimate::HonClimate()
|
HonClimate::HonClimate()
|
||||||
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
: cleaning_status_(CleaningState::NO_CLEANING), got_valid_outdoor_temp_(false), active_alarms_{0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00,
|
||||||
|
@ -66,17 +34,21 @@ void HonClimate::set_beeper_state(bool state) { this->beeper_status_ = state; }
|
||||||
|
|
||||||
bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
|
bool HonClimate::get_beeper_state() const { return this->beeper_status_; }
|
||||||
|
|
||||||
AirflowVerticalDirection HonClimate::get_vertical_airflow() const { return this->vertical_direction_; };
|
esphome::optional<hon_protocol::VerticalSwingMode> HonClimate::get_vertical_airflow() const {
|
||||||
|
return this->current_vertical_swing_;
|
||||||
|
};
|
||||||
|
|
||||||
void HonClimate::set_vertical_airflow(AirflowVerticalDirection direction) {
|
void HonClimate::set_vertical_airflow(hon_protocol::VerticalSwingMode direction) {
|
||||||
this->vertical_direction_ = direction;
|
this->pending_vertical_direction_ = direction;
|
||||||
this->force_send_control_ = true;
|
this->force_send_control_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
AirflowHorizontalDirection HonClimate::get_horizontal_airflow() const { return this->horizontal_direction_; }
|
esphome::optional<hon_protocol::HorizontalSwingMode> HonClimate::get_horizontal_airflow() const {
|
||||||
|
return this->current_horizontal_swing_;
|
||||||
|
}
|
||||||
|
|
||||||
void HonClimate::set_horizontal_airflow(AirflowHorizontalDirection direction) {
|
void HonClimate::set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction) {
|
||||||
this->horizontal_direction_ = direction;
|
this->pending_horizontal_direction_ = direction;
|
||||||
this->force_send_control_ = true;
|
this->force_send_control_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +120,11 @@ haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haie
|
||||||
this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
|
this->hvac_hardware_info_.value().hardware_version_ = std::string(tmp);
|
||||||
strncpy(tmp, answr->device_name, 8);
|
strncpy(tmp, answr->device_name, 8);
|
||||||
this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
|
this->hvac_hardware_info_.value().device_name_ = std::string(tmp);
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
this->update_sub_text_sensor_(SubTextSensorType::APPLIANCE_NAME, this->hvac_hardware_info_.value().device_name_);
|
||||||
|
this->update_sub_text_sensor_(SubTextSensorType::PROTOCOL_VERSION,
|
||||||
|
this->hvac_hardware_info_.value().protocol_version_);
|
||||||
|
#endif
|
||||||
this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
|
this->hvac_hardware_info_.value().functions_[0] = (answr->functions[1] & 0x01) != 0; // interactive mode support
|
||||||
this->hvac_hardware_info_.value().functions_[1] =
|
this->hvac_hardware_info_.value().functions_[1] =
|
||||||
(answr->functions[1] & 0x02) != 0; // controller-device mode support
|
(answr->functions[1] & 0x02) != 0; // controller-device mode support
|
||||||
|
@ -488,6 +465,19 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HonClimate::initialization() {
|
||||||
|
constexpr uint32_t restore_settings_version = 0xE834D8DCUL;
|
||||||
|
this->rtc_ = global_preferences->make_preference<HonSettings>(this->get_object_id_hash() ^ restore_settings_version);
|
||||||
|
HonSettings recovered;
|
||||||
|
if (this->rtc_.load(&recovered)) {
|
||||||
|
this->settings_ = recovered;
|
||||||
|
} else {
|
||||||
|
this->settings_ = {hon_protocol::VerticalSwingMode::CENTER, hon_protocol::HorizontalSwingMode::CENTER};
|
||||||
|
}
|
||||||
|
this->current_vertical_swing_ = this->settings_.last_vertiacal_swing;
|
||||||
|
this->current_horizontal_swing_ = this->settings_.last_horizontal_swing;
|
||||||
|
}
|
||||||
|
|
||||||
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||||
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
|
uint8_t control_out_buffer[sizeof(hon_protocol::HaierPacketControl)];
|
||||||
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
|
memcpy(control_out_buffer, this->last_status_message_.get(), sizeof(hon_protocol::HaierPacketControl));
|
||||||
|
@ -560,16 +550,16 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||||
if (climate_control.swing_mode.has_value()) {
|
if (climate_control.swing_mode.has_value()) {
|
||||||
switch (climate_control.swing_mode.value()) {
|
switch (climate_control.swing_mode.value()) {
|
||||||
case CLIMATE_SWING_OFF:
|
case CLIMATE_SWING_OFF:
|
||||||
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_);
|
out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
|
||||||
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_);
|
out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_SWING_VERTICAL:
|
case CLIMATE_SWING_VERTICAL:
|
||||||
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_);
|
out_data->horizontal_swing_mode = (uint8_t) this->settings_.last_horizontal_swing;
|
||||||
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
|
out_data->vertical_swing_mode = (uint8_t) hon_protocol::VerticalSwingMode::AUTO;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_SWING_HORIZONTAL:
|
case CLIMATE_SWING_HORIZONTAL:
|
||||||
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
|
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
|
||||||
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_);
|
out_data->vertical_swing_mode = (uint8_t) this->settings_.last_vertiacal_swing;
|
||||||
break;
|
break;
|
||||||
case CLIMATE_SWING_BOTH:
|
case CLIMATE_SWING_BOTH:
|
||||||
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
|
out_data->horizontal_swing_mode = (uint8_t) hon_protocol::HorizontalSwingMode::AUTO;
|
||||||
|
@ -631,11 +621,14 @@ haier_protocol::HaierMessage HonClimate::get_control_message() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if (out_data->vertical_swing_mode != (uint8_t) hon_protocol::VerticalSwingMode::AUTO)
|
if (this->pending_vertical_direction_.has_value()) {
|
||||||
out_data->vertical_swing_mode = (uint8_t) get_vertical_swing_mode(this->vertical_direction_);
|
out_data->vertical_swing_mode = (uint8_t) this->pending_vertical_direction_.value();
|
||||||
if (out_data->horizontal_swing_mode != (uint8_t) hon_protocol::HorizontalSwingMode::AUTO)
|
this->pending_vertical_direction_.reset();
|
||||||
out_data->horizontal_swing_mode = (uint8_t) get_horizontal_swing_mode(this->horizontal_direction_);
|
}
|
||||||
|
if (this->pending_horizontal_direction_.has_value()) {
|
||||||
|
out_data->horizontal_swing_mode = (uint8_t) this->pending_horizontal_direction_.value();
|
||||||
|
this->pending_horizontal_direction_.reset();
|
||||||
}
|
}
|
||||||
out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0;
|
out_data->beeper_status = ((!this->beeper_status_) || (!has_hvac_settings)) ? 1 : 0;
|
||||||
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
control_out_buffer[4] = 0; // This byte should be cleared before setting values
|
||||||
|
@ -737,6 +730,33 @@ void HonClimate::update_sub_binary_sensor_(SubBinarySensorType type, uint8_t val
|
||||||
}
|
}
|
||||||
#endif // USE_BINARY_SENSOR
|
#endif // USE_BINARY_SENSOR
|
||||||
|
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
void HonClimate::set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens) {
|
||||||
|
this->sub_text_sensors_[(size_t) type] = sens;
|
||||||
|
switch (type) {
|
||||||
|
case SubTextSensorType::APPLIANCE_NAME:
|
||||||
|
if (this->hvac_hardware_info_.has_value())
|
||||||
|
sens->publish_state(this->hvac_hardware_info_.value().device_name_);
|
||||||
|
break;
|
||||||
|
case SubTextSensorType::PROTOCOL_VERSION:
|
||||||
|
if (this->hvac_hardware_info_.has_value())
|
||||||
|
sens->publish_state(this->hvac_hardware_info_.value().protocol_version_);
|
||||||
|
break;
|
||||||
|
case SubTextSensorType::CLEANING_STATUS:
|
||||||
|
sens->publish_state(this->get_cleaning_status_text());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HonClimate::update_sub_text_sensor_(SubTextSensorType type, const std::string &value) {
|
||||||
|
size_t index = (size_t) type;
|
||||||
|
if (this->sub_text_sensors_[index] != nullptr)
|
||||||
|
this->sub_text_sensors_[index]->publish_state(value);
|
||||||
|
}
|
||||||
|
#endif // USE_TEXT_SENSOR
|
||||||
|
|
||||||
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *packet_buffer, uint8_t size) {
|
||||||
size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) +
|
size_t expected_size = 2 + sizeof(hon_protocol::HaierPacketControl) + sizeof(hon_protocol::HaierPacketSensors) +
|
||||||
this->extra_control_packet_bytes_;
|
this->extra_control_packet_bytes_;
|
||||||
|
@ -896,6 +916,9 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||||
PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
|
PendingAction({ActionRequest::TURN_POWER_OFF, esphome::optional<haier_protocol::HaierMessage>()});
|
||||||
}
|
}
|
||||||
this->cleaning_status_ = new_cleaning;
|
this->cleaning_status_ = new_cleaning;
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
this->update_sub_text_sensor_(SubTextSensorType::CLEANING_STATUS, this->get_cleaning_status_text());
|
||||||
|
#endif // USE_TEXT_SENSOR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
@ -941,6 +964,19 @@ haier_protocol::HandlerError HonClimate::process_status_message_(const uint8_t *
|
||||||
this->swing_mode = CLIMATE_SWING_OFF;
|
this->swing_mode = CLIMATE_SWING_OFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Saving last known non auto mode for vertical and horizontal swing
|
||||||
|
this->current_vertical_swing_ = (hon_protocol::VerticalSwingMode) packet.control.vertical_swing_mode;
|
||||||
|
this->current_horizontal_swing_ = (hon_protocol::HorizontalSwingMode) packet.control.horizontal_swing_mode;
|
||||||
|
bool save_settings = ((this->current_vertical_swing_.value() != hon_protocol::VerticalSwingMode::AUTO) &&
|
||||||
|
(this->current_vertical_swing_.value() != hon_protocol::VerticalSwingMode::AUTO_SPECIAL) &&
|
||||||
|
(this->current_vertical_swing_.value() != this->settings_.last_vertiacal_swing)) ||
|
||||||
|
((this->current_horizontal_swing_.value() != hon_protocol::HorizontalSwingMode::AUTO) &&
|
||||||
|
(this->current_horizontal_swing_.value() != this->settings_.last_horizontal_swing));
|
||||||
|
if (save_settings) {
|
||||||
|
this->settings_.last_vertiacal_swing = this->current_vertical_swing_.value();
|
||||||
|
this->settings_.last_horizontal_swing = this->current_horizontal_swing_.value();
|
||||||
|
this->rtc_.save(&this->settings_);
|
||||||
|
}
|
||||||
should_publish = should_publish || (old_swing_mode != this->swing_mode);
|
should_publish = should_publish || (old_swing_mode != this->swing_mode);
|
||||||
}
|
}
|
||||||
this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
|
this->last_valid_status_timestamp_ = std::chrono::steady_clock::now();
|
||||||
|
|
|
@ -7,29 +7,16 @@
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
#include "esphome/components/text_sensor/text_sensor.h"
|
||||||
|
#endif
|
||||||
#include "esphome/core/automation.h"
|
#include "esphome/core/automation.h"
|
||||||
#include "haier_base.h"
|
#include "haier_base.h"
|
||||||
|
#include "hon_packet.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace haier {
|
namespace haier {
|
||||||
|
|
||||||
enum class AirflowVerticalDirection : uint8_t {
|
|
||||||
HEALTH_UP = 0,
|
|
||||||
MAX_UP = 1,
|
|
||||||
UP = 2,
|
|
||||||
CENTER = 3,
|
|
||||||
DOWN = 4,
|
|
||||||
HEALTH_DOWN = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class AirflowHorizontalDirection : uint8_t {
|
|
||||||
MAX_LEFT = 0,
|
|
||||||
LEFT = 1,
|
|
||||||
CENTER = 2,
|
|
||||||
RIGHT = 3,
|
|
||||||
MAX_RIGHT = 4,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class CleaningState : uint8_t {
|
enum class CleaningState : uint8_t {
|
||||||
NO_CLEANING = 0,
|
NO_CLEANING = 0,
|
||||||
SELF_CLEAN = 1,
|
SELF_CLEAN = 1,
|
||||||
|
@ -38,6 +25,11 @@ enum class CleaningState : uint8_t {
|
||||||
|
|
||||||
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
|
enum class HonControlMethod { MONITOR_ONLY = 0, SET_GROUP_PARAMETERS, SET_SINGLE_PARAMETER };
|
||||||
|
|
||||||
|
struct HonSettings {
|
||||||
|
hon_protocol::VerticalSwingMode last_vertiacal_swing;
|
||||||
|
hon_protocol::HorizontalSwingMode last_horizontal_swing;
|
||||||
|
};
|
||||||
|
|
||||||
class HonClimate : public HaierClimateBase {
|
class HonClimate : public HaierClimateBase {
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
public:
|
public:
|
||||||
|
@ -80,6 +72,20 @@ class HonClimate : public HaierClimateBase {
|
||||||
protected:
|
protected:
|
||||||
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value);
|
void update_sub_binary_sensor_(SubBinarySensorType type, uint8_t value);
|
||||||
binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr};
|
binary_sensor::BinarySensor *sub_binary_sensors_[(size_t) SubBinarySensorType::SUB_BINARY_SENSOR_TYPE_COUNT]{nullptr};
|
||||||
|
#endif
|
||||||
|
#ifdef USE_TEXT_SENSOR
|
||||||
|
public:
|
||||||
|
enum class SubTextSensorType {
|
||||||
|
CLEANING_STATUS = 0,
|
||||||
|
PROTOCOL_VERSION,
|
||||||
|
APPLIANCE_NAME,
|
||||||
|
SUB_TEXT_SENSOR_TYPE_COUNT,
|
||||||
|
};
|
||||||
|
void set_sub_text_sensor(SubTextSensorType type, text_sensor::TextSensor *sens);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void update_sub_text_sensor_(SubTextSensorType type, const std::string &value);
|
||||||
|
text_sensor::TextSensor *sub_text_sensors_[(size_t) SubTextSensorType::SUB_TEXT_SENSOR_TYPE_COUNT]{nullptr};
|
||||||
#endif
|
#endif
|
||||||
public:
|
public:
|
||||||
HonClimate();
|
HonClimate();
|
||||||
|
@ -89,10 +95,10 @@ class HonClimate : public HaierClimateBase {
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void set_beeper_state(bool state);
|
void set_beeper_state(bool state);
|
||||||
bool get_beeper_state() const;
|
bool get_beeper_state() const;
|
||||||
AirflowVerticalDirection get_vertical_airflow() const;
|
esphome::optional<hon_protocol::VerticalSwingMode> get_vertical_airflow() const;
|
||||||
void set_vertical_airflow(AirflowVerticalDirection direction);
|
void set_vertical_airflow(hon_protocol::VerticalSwingMode direction);
|
||||||
AirflowHorizontalDirection get_horizontal_airflow() const;
|
esphome::optional<hon_protocol::HorizontalSwingMode> get_horizontal_airflow() const;
|
||||||
void set_horizontal_airflow(AirflowHorizontalDirection direction);
|
void set_horizontal_airflow(hon_protocol::HorizontalSwingMode direction);
|
||||||
std::string get_cleaning_status_text() const;
|
std::string get_cleaning_status_text() const;
|
||||||
CleaningState get_cleaning_status() const;
|
CleaningState get_cleaning_status() const;
|
||||||
void start_self_cleaning();
|
void start_self_cleaning();
|
||||||
|
@ -108,6 +114,7 @@ class HonClimate : public HaierClimateBase {
|
||||||
void process_phase(std::chrono::steady_clock::time_point now) override;
|
void process_phase(std::chrono::steady_clock::time_point now) override;
|
||||||
haier_protocol::HaierMessage get_control_message() override;
|
haier_protocol::HaierMessage get_control_message() override;
|
||||||
haier_protocol::HaierMessage get_power_message(bool state) override;
|
haier_protocol::HaierMessage get_power_message(bool state) override;
|
||||||
|
void initialization() override;
|
||||||
bool prepare_pending_action() override;
|
bool prepare_pending_action() override;
|
||||||
void process_protocol_reset() override;
|
void process_protocol_reset() override;
|
||||||
bool should_get_big_data_();
|
bool should_get_big_data_();
|
||||||
|
@ -147,9 +154,9 @@ class HonClimate : public HaierClimateBase {
|
||||||
bool beeper_status_;
|
bool beeper_status_;
|
||||||
CleaningState cleaning_status_;
|
CleaningState cleaning_status_;
|
||||||
bool got_valid_outdoor_temp_;
|
bool got_valid_outdoor_temp_;
|
||||||
AirflowVerticalDirection vertical_direction_;
|
esphome::optional<hon_protocol::VerticalSwingMode> pending_vertical_direction_{};
|
||||||
AirflowHorizontalDirection horizontal_direction_;
|
esphome::optional<hon_protocol::HorizontalSwingMode> pending_horizontal_direction_{};
|
||||||
esphome::optional<HardwareInfo> hvac_hardware_info_;
|
esphome::optional<HardwareInfo> hvac_hardware_info_{};
|
||||||
uint8_t active_alarms_[8];
|
uint8_t active_alarms_[8];
|
||||||
int extra_control_packet_bytes_;
|
int extra_control_packet_bytes_;
|
||||||
HonControlMethod control_method_;
|
HonControlMethod control_method_;
|
||||||
|
@ -159,6 +166,10 @@ class HonClimate : public HaierClimateBase {
|
||||||
float active_alarm_count_{NAN};
|
float active_alarm_count_{NAN};
|
||||||
std::chrono::steady_clock::time_point last_alarm_request_;
|
std::chrono::steady_clock::time_point last_alarm_request_;
|
||||||
int big_data_sensors_{0};
|
int big_data_sensors_{0};
|
||||||
|
esphome::optional<hon_protocol::VerticalSwingMode> current_vertical_swing_{};
|
||||||
|
esphome::optional<hon_protocol::HorizontalSwingMode> current_horizontal_swing_{};
|
||||||
|
HonSettings settings_;
|
||||||
|
ESPPreferenceObject rtc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
|
||||||
|
|
|
@ -13,7 +13,10 @@ enum class VerticalSwingMode : uint8_t {
|
||||||
UP = 0x04,
|
UP = 0x04,
|
||||||
CENTER = 0x06,
|
CENTER = 0x06,
|
||||||
DOWN = 0x08,
|
DOWN = 0x08,
|
||||||
AUTO = 0x0C
|
MAX_DOWN = 0x0A,
|
||||||
|
AUTO = 0x0C,
|
||||||
|
// Auto for special modes
|
||||||
|
AUTO_SPECIAL = 0x0E
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class HorizontalSwingMode : uint8_t {
|
enum class HorizontalSwingMode : uint8_t {
|
||||||
|
|
|
@ -31,6 +31,7 @@ from ..climate import (
|
||||||
HonClimate,
|
HonClimate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@paveldn"]
|
||||||
SensorTypeEnum = HonClimate.enum("SubSensorType", True)
|
SensorTypeEnum = HonClimate.enum("SubSensorType", True)
|
||||||
|
|
||||||
# Haier sensors
|
# Haier sensors
|
||||||
|
|
54
esphome/components/haier/text_sensor/__init__.py
Normal file
54
esphome/components/haier/text_sensor/__init__.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import text_sensor
|
||||||
|
from esphome.const import (
|
||||||
|
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
ENTITY_CATEGORY_NONE,
|
||||||
|
)
|
||||||
|
from ..climate import (
|
||||||
|
CONF_HAIER_ID,
|
||||||
|
HonClimate,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@paveldn"]
|
||||||
|
TextSensorTypeEnum = HonClimate.enum("SubTextSensorType", True)
|
||||||
|
|
||||||
|
# Haier text sensors
|
||||||
|
CONF_CLEANING_STATUS = "cleaning_status"
|
||||||
|
CONF_PROTOCOL_VERSION = "protocol_version"
|
||||||
|
CONF_APPLIANCE_NAME = "appliance_name"
|
||||||
|
|
||||||
|
# Additional icons
|
||||||
|
ICON_SPRAY_BOTTLE = "mdi:spray-bottle"
|
||||||
|
ICON_TEXT_BOX = "mdi:text-box-outline"
|
||||||
|
|
||||||
|
TEXT_SENSOR_TYPES = {
|
||||||
|
CONF_CLEANING_STATUS: text_sensor.text_sensor_schema(
|
||||||
|
icon=ICON_SPRAY_BOTTLE,
|
||||||
|
entity_category=ENTITY_CATEGORY_NONE,
|
||||||
|
),
|
||||||
|
CONF_PROTOCOL_VERSION: text_sensor.text_sensor_schema(
|
||||||
|
icon=ICON_TEXT_BOX,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
CONF_APPLIANCE_NAME: text_sensor.text_sensor_schema(
|
||||||
|
icon=ICON_TEXT_BOX,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||||
|
}
|
||||||
|
).extend({cv.Optional(type): schema for type, schema in TEXT_SENSOR_TYPES.items()})
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
paren = await cg.get_variable(config[CONF_HAIER_ID])
|
||||||
|
|
||||||
|
for type, _ in TEXT_SENSOR_TYPES.items():
|
||||||
|
if conf := config.get(type):
|
||||||
|
sens = await text_sensor.new_text_sensor(conf)
|
||||||
|
text_sensor_type = getattr(TextSensorTypeEnum, type.upper())
|
||||||
|
cg.add(paren.set_sub_text_sensor(text_sensor_type, sens))
|
|
@ -2,6 +2,8 @@
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace he60r {
|
namespace he60r {
|
||||||
|
|
||||||
|
@ -124,10 +126,10 @@ void HE60rCover::process_rx_(uint8_t data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HE60rCover::update_() {
|
void HE60rCover::update_() {
|
||||||
if (toggles_needed_ != 0) {
|
if (this->toggles_needed_ != 0) {
|
||||||
if ((this->counter_++ & 0x3) == 0) {
|
if ((this->counter_++ & 0x3) == 0) {
|
||||||
toggles_needed_--;
|
this->toggles_needed_--;
|
||||||
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%d", toggles_needed_);
|
ESP_LOGD(TAG, "Writing byte 0x30, still needed=%" PRIu32, this->toggles_needed_);
|
||||||
this->write_byte(TOGGLE_BYTE);
|
this->write_byte(TOGGLE_BYTE);
|
||||||
} else {
|
} else {
|
||||||
this->write_byte(QUERY_BYTE);
|
this->write_byte(QUERY_BYTE);
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace htu31d {
|
namespace htu31d {
|
||||||
|
|
||||||
|
@ -204,7 +206,7 @@ uint32_t HTU31DComponent::read_serial_num_() {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Found serial: 0x%X", serial);
|
ESP_LOGD(TAG, "Found serial: 0x%" PRIX32, serial);
|
||||||
|
|
||||||
return serial;
|
return serial;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,10 @@ namespace i2s_audio {
|
||||||
|
|
||||||
static const char *const TAG = "i2s_audio";
|
static const char *const TAG = "i2s_audio";
|
||||||
|
|
||||||
|
#if defined(USE_ESP_IDF) && (ESP_IDF_VERSION_MAJOR >= 5)
|
||||||
|
static const uint8_t I2S_NUM_MAX = SOC_I2S_NUM; // because IDF 5+ took this away :(
|
||||||
|
#endif
|
||||||
|
|
||||||
void I2SAudioComponent::setup() {
|
void I2SAudioComponent::setup() {
|
||||||
static i2s_port_t next_port_num = I2S_NUM_0;
|
static i2s_port_t next_port_num = I2S_NUM_0;
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ void I2SAudioMicrophone::start_() {
|
||||||
.use_apll = this->use_apll_,
|
.use_apll = this->use_apll_,
|
||||||
.tx_desc_auto_clear = false,
|
.tx_desc_auto_clear = false,
|
||||||
.fixed_mclk = 0,
|
.fixed_mclk = 0,
|
||||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ void I2SAudioSpeaker::player_task(void *params) {
|
||||||
.use_apll = false,
|
.use_apll = false,
|
||||||
.tx_desc_auto_clear = true,
|
.tx_desc_auto_clear = true,
|
||||||
.fixed_mclk = I2S_PIN_NO_CHANGE,
|
.fixed_mclk = I2S_PIN_NO_CHANGE,
|
||||||
.mclk_multiple = I2S_MCLK_MULTIPLE_DEFAULT,
|
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
|
||||||
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
.bits_per_chan = I2S_BITS_PER_CHAN_DEFAULT,
|
||||||
};
|
};
|
||||||
#if SOC_I2S_SUPPORTS_DAC
|
#if SOC_I2S_SUPPORTS_DAC
|
||||||
|
|
|
@ -483,7 +483,7 @@ bool INA2XX::read_power_w_(float &power_out) {
|
||||||
uint64_t power_reading{0};
|
uint64_t power_reading{0};
|
||||||
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_POWER, 3, power_reading);
|
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_POWER, 3, power_reading);
|
||||||
|
|
||||||
ESP_LOGV(TAG, "read_power_w_ ret=%s, reading_lsb=%d", OKFAILED(ret), (uint32_t) power_reading);
|
ESP_LOGV(TAG, "read_power_w_ ret=%s, reading_lsb=%" PRIu32, OKFAILED(ret), (uint32_t) power_reading);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
power_out = this->cfg_.power_coeff * this->current_lsb_ * (float) power_reading;
|
power_out = this->cfg_.power_coeff * this->current_lsb_ * (float) power_reading;
|
||||||
}
|
}
|
||||||
|
@ -503,8 +503,8 @@ bool INA2XX::read_energy_(double &joules_out, double &watt_hours_out) {
|
||||||
uint64_t previous_energy = this->energy_overflows_count_ * (((uint64_t) 1) << 40);
|
uint64_t previous_energy = this->energy_overflows_count_ * (((uint64_t) 1) << 40);
|
||||||
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_ENERGY, 5, joules_reading);
|
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_ENERGY, 5, joules_reading);
|
||||||
|
|
||||||
ESP_LOGV(TAG, "read_energy_j_ ret=%s, reading_lsb=0x%" PRIX64 ", current_lsb=%f, overflow_cnt=%d", OKFAILED(ret),
|
ESP_LOGV(TAG, "read_energy_j_ ret=%s, reading_lsb=0x%" PRIX64 ", current_lsb=%f, overflow_cnt=%" PRIu32,
|
||||||
joules_reading, this->current_lsb_, this->energy_overflows_count_);
|
OKFAILED(ret), joules_reading, this->current_lsb_, this->energy_overflows_count_);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
joules_out = this->cfg_.energy_coeff * this->current_lsb_ * (double) joules_reading + (double) previous_energy;
|
joules_out = this->cfg_.energy_coeff * this->current_lsb_ * (double) joules_reading + (double) previous_energy;
|
||||||
watt_hours_out = joules_out / 3600.0;
|
watt_hours_out = joules_out / 3600.0;
|
||||||
|
@ -528,7 +528,7 @@ bool INA2XX::read_charge_(double &coulombs_out, double &_hours_out) {
|
||||||
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_CHARGE, 5, raw);
|
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_CHARGE, 5, raw);
|
||||||
coulombs_reading = this->two_complement_(raw, 40);
|
coulombs_reading = this->two_complement_(raw, 40);
|
||||||
|
|
||||||
ESP_LOGV(TAG, "read_charge_c_ ret=%d, curr_charge=%f + 39-bit overflow_cnt=%d", ret, coulombs_reading,
|
ESP_LOGV(TAG, "read_charge_c_ ret=%d, curr_charge=%f + 39-bit overflow_cnt=%" PRIu32, ret, coulombs_reading,
|
||||||
this->charge_overflows_count_);
|
this->charge_overflows_count_);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
coulombs_out = this->current_lsb_ * (double) coulombs_reading + (double) previous_charge;
|
coulombs_out = this->current_lsb_ * (double) coulombs_reading + (double) previous_charge;
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
#include <cinttypes>
|
|
||||||
|
|
||||||
// Very basic support for JSN_SR04T V3.0 distance sensor in mode 2
|
// Very basic support for JSN_SR04T V3.0 distance sensor in mode 2
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -38,7 +36,7 @@ void Jsnsr04tComponent::check_buffer_() {
|
||||||
uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]);
|
uint16_t distance = encode_uint16(this->buffer_[1], this->buffer_[2]);
|
||||||
if (distance > 250) {
|
if (distance > 250) {
|
||||||
float meters = distance / 1000.0f;
|
float meters = distance / 1000.0f;
|
||||||
ESP_LOGV(TAG, "Distance from sensor: %" PRIu32 "mm, %.3fm", distance, meters);
|
ESP_LOGV(TAG, "Distance from sensor: %umm, %.3fm", distance, meters);
|
||||||
this->publish_state(meters);
|
this->publish_state(meters);
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
|
ESP_LOGW(TAG, "Invalid data read from sensor: %s", format_hex_pretty(this->buffer_).c_str());
|
||||||
|
|
|
@ -52,12 +52,12 @@ float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
|
||||||
}
|
}
|
||||||
|
|
||||||
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||||
ESP_LOGD(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
|
ESP_LOGV(TAG, "Calculating resolution bit-depth for frequency %f", frequency);
|
||||||
for (int i = MAX_RES_BITS; i >= 1; i--) {
|
for (int i = MAX_RES_BITS; i >= 1; i--) {
|
||||||
const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
|
const float min_frequency = ledc_min_frequency_for_bit_depth(i, (frequency < 100));
|
||||||
const float max_frequency = ledc_max_frequency_for_bit_depth(i);
|
const float max_frequency = ledc_max_frequency_for_bit_depth(i);
|
||||||
if (min_frequency <= frequency && frequency <= max_frequency) {
|
if (min_frequency <= frequency && frequency <= max_frequency) {
|
||||||
ESP_LOGD(TAG, "Resolution calculated as %d", i);
|
ESP_LOGV(TAG, "Resolution calculated as %d", i);
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "mhz19.h"
|
#include "mhz19.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace mhz19 {
|
namespace mhz19 {
|
||||||
|
|
||||||
|
@ -32,7 +34,7 @@ void MHZ19Component::update() {
|
||||||
uint32_t now_ms = millis();
|
uint32_t now_ms = millis();
|
||||||
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
|
uint32_t warmup_ms = this->warmup_seconds_ * 1000;
|
||||||
if (now_ms < warmup_ms) {
|
if (now_ms < warmup_ms) {
|
||||||
ESP_LOGW(TAG, "MHZ19 warming up, %ds left", (warmup_ms - now_ms) / 1000);
|
ESP_LOGW(TAG, "MHZ19 warming up, %" PRIu32 " s left", (warmup_ms - now_ms) / 1000);
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +112,7 @@ void MHZ19Component::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot");
|
ESP_LOGCONFIG(TAG, " Automatic baseline calibration disabled on boot");
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, " Warmup seconds: %ds", this->warmup_seconds_);
|
ESP_LOGCONFIG(TAG, " Warmup time: %" PRIu32 " s", this->warmup_seconds_);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mhz19
|
} // namespace mhz19
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include <tensorflow/lite/micro/micro_interpreter.h>
|
#include <tensorflow/lite/micro/micro_interpreter.h>
|
||||||
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -316,7 +317,7 @@ float MicroWakeWord::perform_streaming_inference_() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Streaming Inference Latency=%u ms", (millis() - prior_invoke));
|
ESP_LOGV(TAG, "Streaming Inference Latency=%" PRIu32 " ms", (millis() - prior_invoke));
|
||||||
|
|
||||||
TfLiteTensor *output = this->streaming_interpreter_->output(0);
|
TfLiteTensor *output = this->streaming_interpreter_->output(0);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ namespace mitsubishi {
|
||||||
|
|
||||||
static const char *const TAG = "mitsubishi.climate";
|
static const char *const TAG = "mitsubishi.climate";
|
||||||
|
|
||||||
const uint32_t MITSUBISHI_OFF = 0x00;
|
const uint8_t MITSUBISHI_OFF = 0x00;
|
||||||
|
|
||||||
const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
|
const uint8_t MITSUBISHI_MODE_AUTO = 0x20;
|
||||||
const uint8_t MITSUBISHI_MODE_COOL = 0x18;
|
const uint8_t MITSUBISHI_MODE_COOL = 0x18;
|
||||||
|
@ -109,7 +109,7 @@ void MitsubishiClimate::transmit_state() {
|
||||||
// Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
|
// Byte 15: HVAC specfic, i.e. POWERFUL, SMART SET, PLASMA, always 0x00
|
||||||
// Byte 16: Constant 0x00
|
// Byte 16: Constant 0x00
|
||||||
// Byte 17: Checksum: SUM[Byte0...Byte16]
|
// Byte 17: Checksum: SUM[Byte0...Byte16]
|
||||||
uint32_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00,
|
uint8_t remote_state[18] = {0x23, 0xCB, 0x26, 0x01, 0x00, 0x20, 0x08, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
switch (this->mode) {
|
switch (this->mode) {
|
||||||
|
@ -249,7 +249,7 @@ void MitsubishiClimate::transmit_state() {
|
||||||
|
|
||||||
data->set_carrier_frequency(38000);
|
data->set_carrier_frequency(38000);
|
||||||
// repeat twice
|
// repeat twice
|
||||||
for (uint16_t r = 0; r < 2; r++) {
|
for (uint8_t r = 0; r < 2; r++) {
|
||||||
// Header
|
// Header
|
||||||
data->mark(MITSUBISHI_HEADER_MARK);
|
data->mark(MITSUBISHI_HEADER_MARK);
|
||||||
data->space(MITSUBISHI_HEADER_SPACE);
|
data->space(MITSUBISHI_HEADER_SPACE);
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
import esphome.final_validate as fv
|
||||||
from esphome.cpp_helpers import gpio_pin_expression
|
from esphome.cpp_helpers import gpio_pin_expression
|
||||||
from esphome.components import uart
|
from esphome.components import uart
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
|
@ -17,13 +21,21 @@ Modbus = modbus_ns.class_("Modbus", cg.Component, uart.UARTDevice)
|
||||||
ModbusDevice = modbus_ns.class_("ModbusDevice")
|
ModbusDevice = modbus_ns.class_("ModbusDevice")
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
|
CONF_ROLE = "role"
|
||||||
CONF_MODBUS_ID = "modbus_id"
|
CONF_MODBUS_ID = "modbus_id"
|
||||||
CONF_SEND_WAIT_TIME = "send_wait_time"
|
CONF_SEND_WAIT_TIME = "send_wait_time"
|
||||||
|
|
||||||
|
ModbusRole = modbus_ns.enum("ModbusRole")
|
||||||
|
MODBUS_ROLES = {
|
||||||
|
"client": ModbusRole.CLIENT,
|
||||||
|
"server": ModbusRole.SERVER,
|
||||||
|
}
|
||||||
|
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(Modbus),
|
cv.GenerateID(): cv.declare_id(Modbus),
|
||||||
|
cv.Optional(CONF_ROLE, default="client"): cv.enum(MODBUS_ROLES),
|
||||||
cv.Optional(CONF_FLOW_CONTROL_PIN): pins.gpio_output_pin_schema,
|
cv.Optional(CONF_FLOW_CONTROL_PIN): pins.gpio_output_pin_schema,
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_SEND_WAIT_TIME, default="250ms"
|
CONF_SEND_WAIT_TIME, default="250ms"
|
||||||
|
@ -43,6 +55,7 @@ async def to_code(config):
|
||||||
|
|
||||||
await uart.register_uart_device(var, config)
|
await uart.register_uart_device(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_role(config[CONF_ROLE]))
|
||||||
if CONF_FLOW_CONTROL_PIN in config:
|
if CONF_FLOW_CONTROL_PIN in config:
|
||||||
pin = await gpio_pin_expression(config[CONF_FLOW_CONTROL_PIN])
|
pin = await gpio_pin_expression(config[CONF_FLOW_CONTROL_PIN])
|
||||||
cg.add(var.set_flow_control_pin(pin))
|
cg.add(var.set_flow_control_pin(pin))
|
||||||
|
@ -62,6 +75,28 @@ def modbus_device_schema(default_address):
|
||||||
return cv.Schema(schema)
|
return cv.Schema(schema)
|
||||||
|
|
||||||
|
|
||||||
|
def final_validate_modbus_device(
|
||||||
|
name: str, *, role: Literal["server", "client"] | None = None
|
||||||
|
):
|
||||||
|
def validate_role(value):
|
||||||
|
assert role in MODBUS_ROLES
|
||||||
|
if value != role:
|
||||||
|
raise cv.Invalid(f"Component {name} requires role to be {role}")
|
||||||
|
return value
|
||||||
|
|
||||||
|
def validate_hub(hub_config):
|
||||||
|
hub_schema = {}
|
||||||
|
if role is not None:
|
||||||
|
hub_schema[cv.Required(CONF_ROLE)] = validate_role
|
||||||
|
|
||||||
|
return cv.Schema(hub_schema, extra=cv.ALLOW_EXTRA)(hub_config)
|
||||||
|
|
||||||
|
return cv.Schema(
|
||||||
|
{cv.Required(CONF_MODBUS_ID): fv.id_declaration_match_schema(validate_hub)},
|
||||||
|
extra=cv.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def register_modbus_device(var, config):
|
async def register_modbus_device(var, config):
|
||||||
parent = await cg.get_variable(config[CONF_MODBUS_ID])
|
parent = await cg.get_variable(config[CONF_MODBUS_ID])
|
||||||
cg.add(var.set_parent(parent))
|
cg.add(var.set_parent(parent))
|
||||||
|
|
|
@ -77,7 +77,13 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
||||||
ESP_LOGD(TAG, "Modbus user-defined function %02X found", function_code);
|
ESP_LOGD(TAG, "Modbus user-defined function %02X found", function_code);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// the response for write command mirrors the requests and data startes at offset 2 instead of 3 for read commands
|
// data starts at 2 and length is 4 for read registers commands
|
||||||
|
if (this->role == ModbusRole::SERVER && (function_code == 0x3 || function_code == 0x4)) {
|
||||||
|
data_offset = 2;
|
||||||
|
data_len = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the response for write command mirrors the requests and data starts at offset 2 instead of 3 for read commands
|
||||||
if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) {
|
if (function_code == 0x5 || function_code == 0x06 || function_code == 0xF || function_code == 0x10) {
|
||||||
data_offset = 2;
|
data_offset = 2;
|
||||||
data_len = 4;
|
data_len = 4;
|
||||||
|
@ -123,6 +129,9 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
||||||
// Ignore modbus exception not related to a pending command
|
// Ignore modbus exception not related to a pending command
|
||||||
ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
|
ESP_LOGD(TAG, "Ignoring Modbus error - not expecting a response");
|
||||||
}
|
}
|
||||||
|
} else if (this->role == ModbusRole::SERVER && (function_code == 0x3 || function_code == 0x4)) {
|
||||||
|
device->on_modbus_read_registers(function_code, uint16_t(data[1]) | (uint16_t(data[0]) << 8),
|
||||||
|
uint16_t(data[3]) | (uint16_t(data[2]) << 8));
|
||||||
} else {
|
} else {
|
||||||
device->on_modbus_data(data);
|
device->on_modbus_data(data);
|
||||||
}
|
}
|
||||||
|
@ -164,15 +173,17 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address
|
||||||
std::vector<uint8_t> data;
|
std::vector<uint8_t> data;
|
||||||
data.push_back(address);
|
data.push_back(address);
|
||||||
data.push_back(function_code);
|
data.push_back(function_code);
|
||||||
|
if (this->role == ModbusRole::CLIENT) {
|
||||||
data.push_back(start_address >> 8);
|
data.push_back(start_address >> 8);
|
||||||
data.push_back(start_address >> 0);
|
data.push_back(start_address >> 0);
|
||||||
if (function_code != 0x5 && function_code != 0x6) {
|
if (function_code != 0x5 && function_code != 0x6) {
|
||||||
data.push_back(number_of_entities >> 8);
|
data.push_back(number_of_entities >> 8);
|
||||||
data.push_back(number_of_entities >> 0);
|
data.push_back(number_of_entities >> 0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (payload != nullptr) {
|
if (payload != nullptr) {
|
||||||
if (function_code == 0xF || function_code == 0x10) { // Write multiple
|
if (this->role == ModbusRole::SERVER || function_code == 0xF || function_code == 0x10) { // Write multiple
|
||||||
data.push_back(payload_len); // Byte count is required for write
|
data.push_back(payload_len); // Byte count is required for write
|
||||||
} else {
|
} else {
|
||||||
payload_len = 2; // Write single register or coil
|
payload_len = 2; // Write single register or coil
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace modbus {
|
namespace modbus {
|
||||||
|
|
||||||
|
enum ModbusRole {
|
||||||
|
CLIENT,
|
||||||
|
SERVER,
|
||||||
|
};
|
||||||
|
|
||||||
class ModbusDevice;
|
class ModbusDevice;
|
||||||
|
|
||||||
class Modbus : public uart::UARTDevice, public Component {
|
class Modbus : public uart::UARTDevice, public Component {
|
||||||
|
@ -27,11 +32,14 @@ class Modbus : public uart::UARTDevice, public Component {
|
||||||
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities,
|
void send(uint8_t address, uint8_t function_code, uint16_t start_address, uint16_t number_of_entities,
|
||||||
uint8_t payload_len = 0, const uint8_t *payload = nullptr);
|
uint8_t payload_len = 0, const uint8_t *payload = nullptr);
|
||||||
void send_raw(const std::vector<uint8_t> &payload);
|
void send_raw(const std::vector<uint8_t> &payload);
|
||||||
|
void set_role(ModbusRole role) { this->role = role; }
|
||||||
void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; }
|
void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; }
|
||||||
uint8_t waiting_for_response{0};
|
uint8_t waiting_for_response{0};
|
||||||
void set_send_wait_time(uint16_t time_in_ms) { send_wait_time_ = time_in_ms; }
|
void set_send_wait_time(uint16_t time_in_ms) { send_wait_time_ = time_in_ms; }
|
||||||
void set_disable_crc(bool disable_crc) { disable_crc_ = disable_crc; }
|
void set_disable_crc(bool disable_crc) { disable_crc_ = disable_crc; }
|
||||||
|
|
||||||
|
ModbusRole role;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
GPIOPin *flow_control_pin_{nullptr};
|
GPIOPin *flow_control_pin_{nullptr};
|
||||||
|
|
||||||
|
@ -50,6 +58,7 @@ class ModbusDevice {
|
||||||
void set_address(uint8_t address) { address_ = address; }
|
void set_address(uint8_t address) { address_ = address; }
|
||||||
virtual void on_modbus_data(const std::vector<uint8_t> &data) = 0;
|
virtual void on_modbus_data(const std::vector<uint8_t> &data) = 0;
|
||||||
virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {}
|
virtual void on_modbus_error(uint8_t function_code, uint8_t exception_code) {}
|
||||||
|
virtual void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers){};
|
||||||
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0,
|
void send(uint8_t function, uint16_t start_address, uint16_t number_of_entities, uint8_t payload_len = 0,
|
||||||
const uint8_t *payload = nullptr) {
|
const uint8_t *payload = nullptr) {
|
||||||
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload);
|
||||||
|
|
|
@ -23,6 +23,8 @@ CODEOWNERS = ["@martgras"]
|
||||||
|
|
||||||
AUTO_LOAD = ["modbus"]
|
AUTO_LOAD = ["modbus"]
|
||||||
|
|
||||||
|
CONF_READ_LAMBDA = "read_lambda"
|
||||||
|
CONF_SERVER_REGISTERS = "server_registers"
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
|
|
||||||
modbus_controller_ns = cg.esphome_ns.namespace("modbus_controller")
|
modbus_controller_ns = cg.esphome_ns.namespace("modbus_controller")
|
||||||
|
@ -31,6 +33,7 @@ ModbusController = modbus_controller_ns.class_(
|
||||||
)
|
)
|
||||||
|
|
||||||
SensorItem = modbus_controller_ns.struct("SensorItem")
|
SensorItem = modbus_controller_ns.struct("SensorItem")
|
||||||
|
ServerRegister = modbus_controller_ns.struct("ServerRegister")
|
||||||
|
|
||||||
ModbusFunctionCode_ns = modbus_controller_ns.namespace("ModbusFunctionCode")
|
ModbusFunctionCode_ns = modbus_controller_ns.namespace("ModbusFunctionCode")
|
||||||
ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode")
|
ModbusFunctionCode = ModbusFunctionCode_ns.enum("ModbusFunctionCode")
|
||||||
|
@ -94,10 +97,18 @@ TYPE_REGISTER_MAP = {
|
||||||
"FP32_R": 2,
|
"FP32_R": 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
MULTI_CONF = True
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
ModbusServerRegisterSchema = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(ServerRegister),
|
||||||
|
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||||
|
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||||
|
cv.Required(CONF_READ_LAMBDA): cv.returning_lambda,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
|
@ -106,6 +117,9 @@ CONFIG_SCHEMA = cv.All(
|
||||||
CONF_COMMAND_THROTTLE, default="0ms"
|
CONF_COMMAND_THROTTLE, default="0ms"
|
||||||
): cv.positive_time_period_milliseconds,
|
): cv.positive_time_period_milliseconds,
|
||||||
cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int,
|
cv.Optional(CONF_OFFLINE_SKIP_UPDATES, default=0): cv.positive_int,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_SERVER_REGISTERS,
|
||||||
|
): cv.ensure_list(ModbusServerRegisterSchema),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.polling_component_schema("60s"))
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
@ -154,6 +168,17 @@ def validate_modbus_register(config):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def _final_validate(config):
|
||||||
|
if CONF_SERVER_REGISTERS in config:
|
||||||
|
return modbus.final_validate_modbus_device("modbus_controller", role="server")(
|
||||||
|
config
|
||||||
|
)
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||||
|
|
||||||
|
|
||||||
def modbus_calc_properties(config):
|
def modbus_calc_properties(config):
|
||||||
byte_offset = 0
|
byte_offset = 0
|
||||||
reg_count = 0
|
reg_count = 0
|
||||||
|
@ -183,7 +208,7 @@ def modbus_calc_properties(config):
|
||||||
|
|
||||||
|
|
||||||
async def add_modbus_base_properties(
|
async def add_modbus_base_properties(
|
||||||
var, config, sensor_type, lamdba_param_type=cg.float_, lamdba_return_type=float
|
var, config, sensor_type, lambda_param_type=cg.float_, lambda_return_type=float
|
||||||
):
|
):
|
||||||
if CONF_CUSTOM_COMMAND in config:
|
if CONF_CUSTOM_COMMAND in config:
|
||||||
cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND]))
|
cg.add(var.set_custom_data(config[CONF_CUSTOM_COMMAND]))
|
||||||
|
@ -196,13 +221,13 @@ async def add_modbus_base_properties(
|
||||||
config[CONF_LAMBDA],
|
config[CONF_LAMBDA],
|
||||||
[
|
[
|
||||||
(sensor_type.operator("ptr"), "item"),
|
(sensor_type.operator("ptr"), "item"),
|
||||||
(lamdba_param_type, "x"),
|
(lambda_param_type, "x"),
|
||||||
(
|
(
|
||||||
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
||||||
"data",
|
"data",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
return_type=cg.optional.template(lamdba_return_type),
|
return_type=cg.optional.template(lambda_return_type),
|
||||||
)
|
)
|
||||||
cg.add(var.set_template(template_))
|
cg.add(var.set_template(template_))
|
||||||
|
|
||||||
|
@ -211,6 +236,23 @@ async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
|
cg.add(var.set_command_throttle(config[CONF_COMMAND_THROTTLE]))
|
||||||
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES]))
|
||||||
|
if CONF_SERVER_REGISTERS in config:
|
||||||
|
for server_register in config[CONF_SERVER_REGISTERS]:
|
||||||
|
cg.add(
|
||||||
|
var.add_server_register(
|
||||||
|
cg.new_Pvariable(
|
||||||
|
server_register[CONF_ID],
|
||||||
|
server_register[CONF_ADDRESS],
|
||||||
|
server_register[CONF_VALUE_TYPE],
|
||||||
|
TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]],
|
||||||
|
await cg.process_lambda(
|
||||||
|
server_register[CONF_READ_LAMBDA],
|
||||||
|
[],
|
||||||
|
return_type=cg.float_,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
await register_modbus_device(var, config)
|
await register_modbus_device(var, config)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,7 @@ namespace modbus_controller {
|
||||||
|
|
||||||
static const char *const TAG = "modbus_controller";
|
static const char *const TAG = "modbus_controller";
|
||||||
|
|
||||||
void ModbusController::setup() {
|
void ModbusController::setup() { this->create_register_ranges_(); }
|
||||||
// Modbus::setup();
|
|
||||||
this->create_register_ranges_();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
To work with the existing modbus class and avoid polling for responses a command queue is used.
|
To work with the existing modbus class and avoid polling for responses a command queue is used.
|
||||||
|
@ -102,6 +99,51 @@ void ModbusController::on_modbus_error(uint8_t function_code, uint8_t exception_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t start_address,
|
||||||
|
uint16_t number_of_registers) {
|
||||||
|
ESP_LOGD(TAG,
|
||||||
|
"Received read holding/input registers for device 0x%X. FC: 0x%X. Start address: 0x%X. Number of registers: "
|
||||||
|
"0x%X.",
|
||||||
|
this->address_, function_code, start_address, number_of_registers);
|
||||||
|
|
||||||
|
std::vector<uint16_t> sixteen_bit_response;
|
||||||
|
for (uint16_t current_address = start_address; current_address < start_address + number_of_registers;) {
|
||||||
|
bool found = false;
|
||||||
|
for (auto *server_register : this->server_registers_) {
|
||||||
|
if (server_register->address == current_address) {
|
||||||
|
float value = server_register->read_lambda();
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.",
|
||||||
|
server_register->address, static_cast<uint8_t>(server_register->value_type),
|
||||||
|
server_register->register_count, value);
|
||||||
|
number_to_payload(sixteen_bit_response, value, server_register->value_type);
|
||||||
|
current_address += server_register->register_count;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address);
|
||||||
|
std::vector<uint8_t> error_response;
|
||||||
|
error_response.push_back(this->address_);
|
||||||
|
error_response.push_back(0x81);
|
||||||
|
error_response.push_back(0x02);
|
||||||
|
this->send_raw(error_response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> response;
|
||||||
|
for (auto v : sixteen_bit_response) {
|
||||||
|
auto decoded_value = decode_value(v);
|
||||||
|
response.push_back(decoded_value[0]);
|
||||||
|
response.push_back(decoded_value[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->send(function_code, start_address, number_of_registers, response.size(), response.data());
|
||||||
|
}
|
||||||
|
|
||||||
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
|
SensorSet ModbusController::find_sensors_(ModbusRegisterType register_type, uint16_t start_address) const {
|
||||||
auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
|
auto reg_it = find_if(begin(register_ranges_), end(register_ranges_), [=](RegisterRange const &r) {
|
||||||
return (r.start_address == start_address && r.register_type == register_type);
|
return (r.start_address == start_address && r.register_type == register_type);
|
||||||
|
@ -190,7 +232,7 @@ void ModbusController::update() {
|
||||||
// walk through the sensors and determine the register ranges to read
|
// walk through the sensors and determine the register ranges to read
|
||||||
size_t ModbusController::create_register_ranges_() {
|
size_t ModbusController::create_register_ranges_() {
|
||||||
register_ranges_.clear();
|
register_ranges_.clear();
|
||||||
if (sensorset_.empty()) {
|
if (this->parent_->role == modbus::ModbusRole::CLIENT && sensorset_.empty()) {
|
||||||
ESP_LOGW(TAG, "No sensors registered");
|
ESP_LOGW(TAG, "No sensors registered");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -309,6 +351,11 @@ void ModbusController::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
|
ESP_LOGCONFIG(TAG, " Range type=%zu start=0x%X count=%d skip_updates=%d", static_cast<uint8_t>(it.register_type),
|
||||||
it.start_address, it.register_count, it.skip_updates);
|
it.start_address, it.register_count, it.skip_updates);
|
||||||
}
|
}
|
||||||
|
ESP_LOGCONFIG(TAG, "server registers");
|
||||||
|
for (auto &r : server_registers_) {
|
||||||
|
ESP_LOGCONFIG(TAG, " Address=0x%02X value_type=%zu register_count=%u", r->address,
|
||||||
|
static_cast<uint8_t>(r->value_type), r->register_count);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -251,6 +252,21 @@ class SensorItem {
|
||||||
bool force_new_range{false};
|
bool force_new_range{false};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ServerRegister {
|
||||||
|
public:
|
||||||
|
ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count,
|
||||||
|
std::function<float()> read_lambda) {
|
||||||
|
this->address = address;
|
||||||
|
this->value_type = value_type;
|
||||||
|
this->register_count = register_count;
|
||||||
|
this->read_lambda = std::move(read_lambda);
|
||||||
|
}
|
||||||
|
uint16_t address;
|
||||||
|
SensorValueType value_type;
|
||||||
|
uint8_t register_count;
|
||||||
|
std::function<float()> read_lambda;
|
||||||
|
};
|
||||||
|
|
||||||
// ModbusController::create_register_ranges_ tries to optimize register range
|
// ModbusController::create_register_ranges_ tries to optimize register range
|
||||||
// for this the sensors must be ordered by register_type, start_address and bitmask
|
// for this the sensors must be ordered by register_type, start_address and bitmask
|
||||||
class SensorItemsComparator {
|
class SensorItemsComparator {
|
||||||
|
@ -418,10 +434,14 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
||||||
void queue_command(const ModbusCommandItem &command);
|
void queue_command(const ModbusCommandItem &command);
|
||||||
/// Registers a sensor with the controller. Called by esphomes code generator
|
/// Registers a sensor with the controller. Called by esphomes code generator
|
||||||
void add_sensor_item(SensorItem *item) { sensorset_.insert(item); }
|
void add_sensor_item(SensorItem *item) { sensorset_.insert(item); }
|
||||||
|
/// Registers a server register with the controller. Called by esphomes code generator
|
||||||
|
void add_server_register(ServerRegister *server_register) { server_registers_.push_back(server_register); }
|
||||||
/// called when a modbus response was parsed without errors
|
/// called when a modbus response was parsed without errors
|
||||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||||
/// called when a modbus error response was received
|
/// called when a modbus error response was received
|
||||||
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
|
void on_modbus_error(uint8_t function_code, uint8_t exception_code) override;
|
||||||
|
/// called when a modbus request (function code 3 or 4) was parsed without errors
|
||||||
|
void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final;
|
||||||
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
/// default delegate called by process_modbus_data when a response has retrieved from the incoming queue
|
||||||
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data);
|
||||||
/// default delegate called by process_modbus_data when a response for a write response has retrieved from the
|
/// default delegate called by process_modbus_data when a response for a write response has retrieved from the
|
||||||
|
@ -452,6 +472,8 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
||||||
void dump_sensors_();
|
void dump_sensors_();
|
||||||
/// Collection of all sensors for this component
|
/// Collection of all sensors for this component
|
||||||
SensorSet sensorset_;
|
SensorSet sensorset_;
|
||||||
|
/// Collection of all server registers for this component
|
||||||
|
std::vector<ServerRegister *> server_registers_;
|
||||||
/// Continuous range of modbus registers
|
/// Continuous range of modbus registers
|
||||||
std::vector<RegisterRange> register_ranges_;
|
std::vector<RegisterRange> register_ranges_;
|
||||||
/// Hold the pending requests to be sent
|
/// Hold the pending requests to be sent
|
||||||
|
|
|
@ -1,19 +1,32 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
|
import esphome.final_validate as fv
|
||||||
|
from esphome import pins
|
||||||
from esphome.components import i2c
|
from esphome.components import i2c
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import (
|
||||||
|
CONF_BINARY_SENSOR,
|
||||||
|
CONF_CHANNEL,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_INPUT,
|
||||||
|
CONF_INVERTED,
|
||||||
|
CONF_MODE,
|
||||||
|
CONF_NUMBER,
|
||||||
|
CONF_OUTPUT,
|
||||||
|
)
|
||||||
|
|
||||||
CONF_TOUCH_THRESHOLD = "touch_threshold"
|
CONF_TOUCH_THRESHOLD = "touch_threshold"
|
||||||
CONF_RELEASE_THRESHOLD = "release_threshold"
|
CONF_RELEASE_THRESHOLD = "release_threshold"
|
||||||
CONF_TOUCH_DEBOUNCE = "touch_debounce"
|
CONF_TOUCH_DEBOUNCE = "touch_debounce"
|
||||||
CONF_RELEASE_DEBOUNCE = "release_debounce"
|
CONF_RELEASE_DEBOUNCE = "release_debounce"
|
||||||
|
CONF_MAX_TOUCH_CHANNEL = "max_touch_channel"
|
||||||
|
CONF_MPR121 = "mpr121"
|
||||||
|
CONF_MPR121_ID = "mpr121_id"
|
||||||
|
|
||||||
DEPENDENCIES = ["i2c"]
|
DEPENDENCIES = ["i2c"]
|
||||||
AUTO_LOAD = ["binary_sensor"]
|
|
||||||
|
|
||||||
mpr121_ns = cg.esphome_ns.namespace("mpr121")
|
mpr121_ns = cg.esphome_ns.namespace("mpr121")
|
||||||
CONF_MPR121_ID = "mpr121_id"
|
|
||||||
MPR121Component = mpr121_ns.class_("MPR121Component", cg.Component, i2c.I2CDevice)
|
MPR121Component = mpr121_ns.class_("MPR121Component", cg.Component, i2c.I2CDevice)
|
||||||
|
MPR121GPIOPin = mpr121_ns.class_("MPR121GPIOPin", cg.GPIOPin)
|
||||||
|
|
||||||
MULTI_CONF = True
|
MULTI_CONF = True
|
||||||
CONFIG_SCHEMA = (
|
CONFIG_SCHEMA = (
|
||||||
|
@ -28,6 +41,7 @@ CONFIG_SCHEMA = (
|
||||||
cv.Optional(CONF_RELEASE_THRESHOLD, default=0x06): cv.int_range(
|
cv.Optional(CONF_RELEASE_THRESHOLD, default=0x06): cv.int_range(
|
||||||
min=0x05, max=0x30
|
min=0x05, max=0x30
|
||||||
),
|
),
|
||||||
|
cv.Optional(CONF_MAX_TOUCH_CHANNEL): cv.int_range(min=3, max=11),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
|
@ -35,11 +49,79 @@ CONFIG_SCHEMA = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _final_validate(config):
|
||||||
|
fconf = fv.full_config.get()
|
||||||
|
max_touch_channel = 3
|
||||||
|
if (binary_sensors := fconf.get(CONF_BINARY_SENSOR)) is not None:
|
||||||
|
for binary_sensor in binary_sensors:
|
||||||
|
if binary_sensor.get(CONF_MPR121_ID) == config[CONF_ID]:
|
||||||
|
max_touch_channel = max(max_touch_channel, binary_sensor[CONF_CHANNEL])
|
||||||
|
if max_touch_channel_in_config := config.get(CONF_MAX_TOUCH_CHANNEL):
|
||||||
|
if max_touch_channel != max_touch_channel_in_config:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Max touch channel must equal the highest binary sensor channel or be removed for auto calculation",
|
||||||
|
path=[CONF_MAX_TOUCH_CHANNEL],
|
||||||
|
)
|
||||||
|
path = fconf.get_path_for_id(config[CONF_ID])[:-1]
|
||||||
|
this_config = fconf.get_config_for_path(path)
|
||||||
|
this_config[CONF_MAX_TOUCH_CHANNEL] = max_touch_channel
|
||||||
|
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
cg.add(var.set_touch_debounce(config[CONF_TOUCH_DEBOUNCE]))
|
cg.add(var.set_touch_debounce(config[CONF_TOUCH_DEBOUNCE]))
|
||||||
cg.add(var.set_release_debounce(config[CONF_RELEASE_DEBOUNCE]))
|
cg.add(var.set_release_debounce(config[CONF_RELEASE_DEBOUNCE]))
|
||||||
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
|
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
|
||||||
cg.add(var.set_release_threshold(config[CONF_RELEASE_THRESHOLD]))
|
cg.add(var.set_release_threshold(config[CONF_RELEASE_THRESHOLD]))
|
||||||
|
cg.add(var.set_max_touch_channel(config[CONF_MAX_TOUCH_CHANNEL]))
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
await i2c.register_i2c_device(var, config)
|
await i2c.register_i2c_device(var, config)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_mode(value):
|
||||||
|
if bool(value[CONF_INPUT]) == bool(value[CONF_OUTPUT]):
|
||||||
|
raise cv.Invalid("Mode must be either input or output")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# https://www.nxp.com/docs/en/data-sheet/MPR121.pdf, page 4
|
||||||
|
#
|
||||||
|
# Among the 12 electrode inputs, 8 inputs are designed as multifunctional pins. When these pins are
|
||||||
|
# not configured as electrodes, they may be used to drive LEDs or used for general purpose input or
|
||||||
|
# output.
|
||||||
|
MPR121_GPIO_PIN_SCHEMA = pins.gpio_base_schema(
|
||||||
|
MPR121GPIOPin,
|
||||||
|
cv.int_range(min=4, max=11),
|
||||||
|
modes=[CONF_INPUT, CONF_OUTPUT],
|
||||||
|
mode_validator=validate_mode,
|
||||||
|
).extend(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_MPR121): cv.use_id(MPR121Component),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def mpr121_pin_final_validate(pin_config, parent_config):
|
||||||
|
if pin_config[CONF_NUMBER] <= parent_config[CONF_MAX_TOUCH_CHANNEL]:
|
||||||
|
raise cv.Invalid(
|
||||||
|
"Pin number must be higher than the max touch channel of the MPR121 component",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pins.PIN_SCHEMA_REGISTRY.register(
|
||||||
|
CONF_MPR121, MPR121_GPIO_PIN_SCHEMA, mpr121_pin_final_validate
|
||||||
|
)
|
||||||
|
async def mpr121_gpio_pin_to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
parent = await cg.get_variable(config[CONF_MPR121])
|
||||||
|
|
||||||
|
cg.add(var.set_parent(parent))
|
||||||
|
|
||||||
|
num = config[CONF_NUMBER]
|
||||||
|
cg.add(var.set_pin(num))
|
||||||
|
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||||
|
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||||
|
return var
|
||||||
|
|
|
@ -2,7 +2,7 @@ import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import binary_sensor
|
from esphome.components import binary_sensor
|
||||||
from esphome.const import CONF_CHANNEL
|
from esphome.const import CONF_CHANNEL
|
||||||
from . import (
|
from .. import (
|
||||||
mpr121_ns,
|
mpr121_ns,
|
||||||
MPR121Component,
|
MPR121Component,
|
||||||
CONF_MPR121_ID,
|
CONF_MPR121_ID,
|
||||||
|
@ -11,9 +11,9 @@ from . import (
|
||||||
)
|
)
|
||||||
|
|
||||||
DEPENDENCIES = ["mpr121"]
|
DEPENDENCIES = ["mpr121"]
|
||||||
MPR121Channel = mpr121_ns.class_("MPR121Channel", binary_sensor.BinarySensor)
|
MPR121BinarySensor = mpr121_ns.class_("MPR121BinarySensor", binary_sensor.BinarySensor)
|
||||||
|
|
||||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(MPR121Channel).extend(
|
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(MPR121BinarySensor).extend(
|
||||||
{
|
{
|
||||||
cv.GenerateID(CONF_MPR121_ID): cv.use_id(MPR121Component),
|
cv.GenerateID(CONF_MPR121_ID): cv.use_id(MPR121Component),
|
||||||
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=11),
|
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=11),
|
||||||
|
@ -27,6 +27,7 @@ async def to_code(config):
|
||||||
var = await binary_sensor.new_binary_sensor(config)
|
var = await binary_sensor.new_binary_sensor(config)
|
||||||
hub = await cg.get_variable(config[CONF_MPR121_ID])
|
hub = await cg.get_variable(config[CONF_MPR121_ID])
|
||||||
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
cg.add(var.set_channel(config[CONF_CHANNEL]))
|
||||||
|
cg.register_parented(var, hub)
|
||||||
|
|
||||||
if CONF_TOUCH_THRESHOLD in config:
|
if CONF_TOUCH_THRESHOLD in config:
|
||||||
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
|
cg.add(var.set_touch_threshold(config[CONF_TOUCH_THRESHOLD]))
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include "mpr121_binary_sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mpr121 {
|
||||||
|
|
||||||
|
void MPR121BinarySensor::setup() {
|
||||||
|
uint8_t touch_threshold = this->touch_threshold_.value_or(this->parent_->get_touch_threshold());
|
||||||
|
this->parent_->write_byte(MPR121_TOUCHTH_0 + 2 * this->channel_, touch_threshold);
|
||||||
|
|
||||||
|
uint8_t release_threshold = this->release_threshold_.value_or(this->parent_->get_release_threshold());
|
||||||
|
this->parent_->write_byte(MPR121_RELEASETH_0 + 2 * this->channel_, release_threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPR121BinarySensor::process(uint16_t data) {
|
||||||
|
bool new_state = data & (1 << this->channel_);
|
||||||
|
this->publish_state(new_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mpr121
|
||||||
|
} // namespace esphome
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
|
||||||
|
#include "../mpr121.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace mpr121 {
|
||||||
|
|
||||||
|
class MPR121BinarySensor : public binary_sensor::BinarySensor, public MPR121Channel, public Parented<MPR121Component> {
|
||||||
|
public:
|
||||||
|
void set_channel(uint8_t channel) { this->channel_ = channel; }
|
||||||
|
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
|
||||||
|
void set_release_threshold(uint8_t release_threshold) { this->release_threshold_ = release_threshold; };
|
||||||
|
|
||||||
|
void setup() override;
|
||||||
|
void process(uint16_t data) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t channel_{0};
|
||||||
|
optional<uint8_t> touch_threshold_{};
|
||||||
|
optional<uint8_t> release_threshold_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mpr121
|
||||||
|
} // namespace esphome
|
|
@ -1,6 +1,9 @@
|
||||||
#include "mpr121.h"
|
#include "mpr121.h"
|
||||||
#include "esphome/core/log.h"
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace mpr121 {
|
namespace mpr121 {
|
||||||
|
@ -20,10 +23,7 @@ void MPR121Component::setup() {
|
||||||
|
|
||||||
// set touch sensitivity for all 12 channels
|
// set touch sensitivity for all 12 channels
|
||||||
for (auto *channel : this->channels_) {
|
for (auto *channel : this->channels_) {
|
||||||
this->write_byte(MPR121_TOUCHTH_0 + 2 * channel->channel_,
|
channel->setup();
|
||||||
channel->touch_threshold_.value_or(this->touch_threshold_));
|
|
||||||
this->write_byte(MPR121_RELEASETH_0 + 2 * channel->channel_,
|
|
||||||
channel->release_threshold_.value_or(this->release_threshold_));
|
|
||||||
}
|
}
|
||||||
this->write_byte(MPR121_MHDR, 0x01);
|
this->write_byte(MPR121_MHDR, 0x01);
|
||||||
this->write_byte(MPR121_NHDR, 0x01);
|
this->write_byte(MPR121_NHDR, 0x01);
|
||||||
|
@ -44,8 +44,15 @@ void MPR121Component::setup() {
|
||||||
this->write_byte(MPR121_CONFIG1, 0x10);
|
this->write_byte(MPR121_CONFIG1, 0x10);
|
||||||
// 0.5uS encoding, 1ms period
|
// 0.5uS encoding, 1ms period
|
||||||
this->write_byte(MPR121_CONFIG2, 0x20);
|
this->write_byte(MPR121_CONFIG2, 0x20);
|
||||||
// start with first 5 bits of baseline tracking
|
|
||||||
this->write_byte(MPR121_ECR, 0x8F);
|
// Write the Electrode Configuration Register
|
||||||
|
// * Highest 2 bits is "Calibration Lock", which we set to a value corresponding to 5 bits.
|
||||||
|
// * The 2 bits below is "Proximity Enable" and are left at 0.
|
||||||
|
// * The 4 least significant bits control how many electrodes are enabled. Electrodes are enabled
|
||||||
|
// as a range, starting at 0 up to the highest channel index used.
|
||||||
|
this->write_byte(MPR121_ECR, 0x80 | (this->max_touch_channel_ + 1));
|
||||||
|
|
||||||
|
this->flush_gpio_();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MPR121Component::set_touch_debounce(uint8_t debounce) {
|
void MPR121Component::set_touch_debounce(uint8_t debounce) {
|
||||||
|
@ -86,6 +93,72 @@ void MPR121Component::loop() {
|
||||||
|
|
||||||
for (auto *channel : this->channels_)
|
for (auto *channel : this->channels_)
|
||||||
channel->process(val);
|
channel->process(val);
|
||||||
|
|
||||||
|
this->read_byte(MPR121_GPIODATA, &this->gpio_input_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MPR121Component::digital_read(uint8_t ionum) { return (this->gpio_input_ & (1 << ionum)) != 0; }
|
||||||
|
|
||||||
|
void MPR121Component::digital_write(uint8_t ionum, bool value) {
|
||||||
|
if (value) {
|
||||||
|
this->gpio_output_ |= (1 << ionum);
|
||||||
|
} else {
|
||||||
|
this->gpio_output_ &= ~(1 << ionum);
|
||||||
|
}
|
||||||
|
this->flush_gpio_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPR121Component::pin_mode(uint8_t ionum, gpio::Flags flags) {
|
||||||
|
this->gpio_enable_ |= (1 << ionum);
|
||||||
|
if (flags & gpio::FLAG_INPUT) {
|
||||||
|
this->gpio_direction_ &= ~(1 << ionum);
|
||||||
|
} else if (flags & gpio::FLAG_OUTPUT) {
|
||||||
|
this->gpio_direction_ |= 1 << ionum;
|
||||||
|
}
|
||||||
|
this->flush_gpio_();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MPR121Component::flush_gpio_() {
|
||||||
|
if (this->is_failed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: The CTL registers can configure internal pullup/pulldown resistors.
|
||||||
|
this->write_byte(MPR121_GPIOCTL0, 0x00);
|
||||||
|
this->write_byte(MPR121_GPIOCTL1, 0x00);
|
||||||
|
this->write_byte(MPR121_GPIOEN, this->gpio_enable_);
|
||||||
|
this->write_byte(MPR121_GPIODIR, this->gpio_direction_);
|
||||||
|
|
||||||
|
if (!this->write_byte(MPR121_GPIODATA, this->gpio_output_)) {
|
||||||
|
this->status_set_warning();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->status_clear_warning();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPR121GPIOPin::setup() { this->pin_mode(this->flags_); }
|
||||||
|
|
||||||
|
void MPR121GPIOPin::pin_mode(gpio::Flags flags) {
|
||||||
|
assert(this->pin_ >= 4);
|
||||||
|
this->parent_->pin_mode(this->pin_ - 4, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MPR121GPIOPin::digital_read() {
|
||||||
|
assert(this->pin_ >= 4);
|
||||||
|
return this->parent_->digital_read(this->pin_ - 4) != this->inverted_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MPR121GPIOPin::digital_write(bool value) {
|
||||||
|
assert(this->pin_ >= 4);
|
||||||
|
this->parent_->digital_write(this->pin_ - 4, value != this->inverted_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MPR121GPIOPin::dump_summary() const {
|
||||||
|
char buffer[32];
|
||||||
|
snprintf(buffer, sizeof(buffer), "ELE%u on MPR121", this->pin_);
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mpr121
|
} // namespace mpr121
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -39,6 +41,9 @@ enum {
|
||||||
MPR121_UPLIMIT = 0x7D,
|
MPR121_UPLIMIT = 0x7D,
|
||||||
MPR121_LOWLIMIT = 0x7E,
|
MPR121_LOWLIMIT = 0x7E,
|
||||||
MPR121_TARGETLIMIT = 0x7F,
|
MPR121_TARGETLIMIT = 0x7F,
|
||||||
|
MPR121_GPIOCTL0 = 0x73,
|
||||||
|
MPR121_GPIOCTL1 = 0x74,
|
||||||
|
MPR121_GPIODATA = 0x75,
|
||||||
MPR121_GPIODIR = 0x76,
|
MPR121_GPIODIR = 0x76,
|
||||||
MPR121_GPIOEN = 0x77,
|
MPR121_GPIOEN = 0x77,
|
||||||
MPR121_GPIOSET = 0x78,
|
MPR121_GPIOSET = 0x78,
|
||||||
|
@ -47,19 +52,10 @@ enum {
|
||||||
MPR121_SOFTRESET = 0x80,
|
MPR121_SOFTRESET = 0x80,
|
||||||
};
|
};
|
||||||
|
|
||||||
class MPR121Channel : public binary_sensor::BinarySensor {
|
class MPR121Channel {
|
||||||
friend class MPR121Component;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void set_channel(uint8_t channel) { channel_ = channel; }
|
virtual void setup() = 0;
|
||||||
void process(uint16_t data) { this->publish_state(static_cast<bool>(data & (1 << this->channel_))); }
|
virtual void process(uint16_t data) = 0;
|
||||||
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
|
|
||||||
void set_release_threshold(uint8_t release_threshold) { this->release_threshold_ = release_threshold; };
|
|
||||||
|
|
||||||
protected:
|
|
||||||
uint8_t channel_{0};
|
|
||||||
optional<uint8_t> touch_threshold_{};
|
|
||||||
optional<uint8_t> release_threshold_{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class MPR121Component : public Component, public i2c::I2CDevice {
|
class MPR121Component : public Component, public i2c::I2CDevice {
|
||||||
|
@ -69,23 +65,63 @@ class MPR121Component : public Component, public i2c::I2CDevice {
|
||||||
void set_release_debounce(uint8_t debounce);
|
void set_release_debounce(uint8_t debounce);
|
||||||
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
|
void set_touch_threshold(uint8_t touch_threshold) { this->touch_threshold_ = touch_threshold; };
|
||||||
void set_release_threshold(uint8_t release_threshold) { this->release_threshold_ = release_threshold; };
|
void set_release_threshold(uint8_t release_threshold) { this->release_threshold_ = release_threshold; };
|
||||||
uint8_t get_touch_threshold() { return this->touch_threshold_; };
|
uint8_t get_touch_threshold() const { return this->touch_threshold_; };
|
||||||
uint8_t get_release_threshold() { return this->release_threshold_; };
|
uint8_t get_release_threshold() const { return this->release_threshold_; };
|
||||||
void setup() override;
|
void setup() override;
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
float get_setup_priority() const override { return setup_priority::IO; }
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|
||||||
|
void set_max_touch_channel(uint8_t max_touch_channel) { this->max_touch_channel_ = max_touch_channel; }
|
||||||
|
|
||||||
|
// GPIO helper functions.
|
||||||
|
bool digital_read(uint8_t ionum);
|
||||||
|
void digital_write(uint8_t ionum, bool value);
|
||||||
|
void pin_mode(uint8_t ionum, gpio::Flags flags);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::vector<MPR121Channel *> channels_{};
|
std::vector<MPR121Channel *> channels_{};
|
||||||
uint8_t debounce_{0};
|
uint8_t debounce_{0};
|
||||||
uint8_t touch_threshold_{};
|
uint8_t touch_threshold_{};
|
||||||
uint8_t release_threshold_{};
|
uint8_t release_threshold_{};
|
||||||
|
uint8_t max_touch_channel_{3};
|
||||||
enum ErrorCode {
|
enum ErrorCode {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
COMMUNICATION_FAILED,
|
COMMUNICATION_FAILED,
|
||||||
WRONG_CHIP_STATE,
|
WRONG_CHIP_STATE,
|
||||||
} error_code_{NONE};
|
} error_code_{NONE};
|
||||||
|
|
||||||
|
bool flush_gpio_();
|
||||||
|
|
||||||
|
/// The enable mask - zero means high Z, 1 means GPIO usage
|
||||||
|
uint8_t gpio_enable_{0x00};
|
||||||
|
/// Mask for the pin mode - 1 means output, 0 means input
|
||||||
|
uint8_t gpio_direction_{0x00};
|
||||||
|
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
||||||
|
uint8_t gpio_output_{0x00};
|
||||||
|
/// The mask to read as input state - 1 means HIGH, 0 means LOW
|
||||||
|
uint8_t gpio_input_{0x00};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Helper class to expose a MPR121 pin as an internal input GPIO pin.
|
||||||
|
class MPR121GPIOPin : public GPIOPin {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void pin_mode(gpio::Flags flags) override;
|
||||||
|
bool digital_read() override;
|
||||||
|
void digital_write(bool value) override;
|
||||||
|
std::string dump_summary() const override;
|
||||||
|
|
||||||
|
void set_parent(MPR121Component *parent) { this->parent_ = parent; }
|
||||||
|
void set_pin(uint8_t pin) { this->pin_ = pin; }
|
||||||
|
void set_inverted(bool inverted) { this->inverted_ = inverted; }
|
||||||
|
void set_flags(gpio::Flags flags) { this->flags_ = flags; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
MPR121Component *parent_;
|
||||||
|
uint8_t pin_;
|
||||||
|
bool inverted_;
|
||||||
|
gpio::Flags flags_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mpr121
|
} // namespace mpr121
|
||||||
|
|
|
@ -952,6 +952,73 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
||||||
*/
|
*/
|
||||||
bool set_protocol_reparse_mode(bool active_mode);
|
bool set_protocol_reparse_mode(bool active_mode);
|
||||||
|
|
||||||
|
// ======== Nextion Intelligent Series ========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the video id of a component.
|
||||||
|
* @param component The component name.
|
||||||
|
* @param vid_id The video ID.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_component_vid("textview", 1);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This will change the video id of the component `textview`.
|
||||||
|
*
|
||||||
|
* Note: Requires Nextion Intelligent series display.
|
||||||
|
*/
|
||||||
|
void set_component_vid(const char *component, uint8_t vid_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the drag availability of a component.
|
||||||
|
* @param component The component name.
|
||||||
|
* @param drag False: Drag not available, True: Drag available.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_component_drag("textview", true);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This will enable drag to the component `textview`.
|
||||||
|
*
|
||||||
|
* Note: Requires Nextion Intelligent series display.
|
||||||
|
*/
|
||||||
|
void set_component_drag(const char *component, bool drag);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the opaqueness (fading) of a component.
|
||||||
|
* @param component The component name.
|
||||||
|
* @param aph An integer between 0 and 127 related to the opaqueness/fading level.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_component_aph("textview", 64);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This will set the opaqueness level of the component `textview` to 64.
|
||||||
|
*
|
||||||
|
* Note: Requires Nextion Intelligent series display.
|
||||||
|
*/
|
||||||
|
void set_component_aph(const char *component, uint8_t aph);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the position of a component.
|
||||||
|
* @param component The component name.
|
||||||
|
* @param x The new X (horizontal) coordinate for the component.
|
||||||
|
* @param y The new Y (vertical) coordinate for the component.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_component_aph("textview", 64, 35);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This will move the component `textview` to the column 64 of row 35 of the display.
|
||||||
|
*
|
||||||
|
* Note: Requires Nextion Intelligent series display.
|
||||||
|
*/
|
||||||
|
void set_component_position(const char *component, uint32_t x, uint32_t y);
|
||||||
|
|
||||||
// ========== INTERNAL METHODS ==========
|
// ========== INTERNAL METHODS ==========
|
||||||
// (In most use cases you won't need these)
|
// (In most use cases you won't need these)
|
||||||
void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); }
|
void register_touch_component(NextionComponentBase *obj) { this->touch_.push_back(obj); }
|
||||||
|
|
|
@ -148,7 +148,25 @@ void Nextion::set_component_pic(const char *component, uint8_t pic_id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nextion::set_component_picc(const char *component, uint8_t pic_id) {
|
void Nextion::set_component_picc(const char *component, uint8_t pic_id) {
|
||||||
this->add_no_result_to_queue_with_printf_("set_component_pic", "%s.picc=%" PRIu8, component, pic_id);
|
this->add_no_result_to_queue_with_printf_("set_component_picc", "%s.picc=%" PRIu8, component, pic_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set video
|
||||||
|
void Nextion::set_component_vid(const char *component, uint8_t vid_id) {
|
||||||
|
this->add_no_result_to_queue_with_printf_("set_component_vid", "%s.vid=%" PRIu8, component, vid_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nextion::set_component_drag(const char *component, bool drag) {
|
||||||
|
this->add_no_result_to_queue_with_printf_("set_component_drag", "%s.drag=%i", component, drag ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nextion::set_component_aph(const char *component, uint8_t aph) {
|
||||||
|
this->add_no_result_to_queue_with_printf_("set_component_aph", "%s.aph=%" PRIu8, component, aph);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nextion::set_component_position(const char *component, uint32_t x, uint32_t y) {
|
||||||
|
this->add_no_result_to_queue_with_printf_("set_component_position_x", "%s.x=%" PRIu32, component, x);
|
||||||
|
this->add_no_result_to_queue_with_printf_("set_component_position_y", "%s.y=%" PRIu32, component, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nextion::set_component_text_printf(const char *component, const char *format, ...) {
|
void Nextion::set_component_text_printf(const char *component, const char *format, ...) {
|
||||||
|
|
|
@ -6,7 +6,7 @@ from esphome.core import CORE, coroutine_with_priority
|
||||||
from esphome.const import CONF_ESPHOME, CONF_OTA, CONF_PLATFORM, CONF_TRIGGER_ID
|
from esphome.const import CONF_ESPHOME, CONF_OTA, CONF_PLATFORM, CONF_TRIGGER_ID
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
AUTO_LOAD = ["md5"]
|
AUTO_LOAD = ["md5", "safe_mode"]
|
||||||
|
|
||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ BASE_OTA_SCHEMA = cv.Schema(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@coroutine_with_priority(51.0)
|
@coroutine_with_priority(54.0)
|
||||||
async def to_code(config):
|
async def to_code(config):
|
||||||
cg.add_define("USE_OTA")
|
cg.add_define("USE_OTA")
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "remote_base.h"
|
#include "remote_base.h"
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cinttypes>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
@ -144,7 +145,8 @@ class ABBWelcomeData {
|
||||||
std::string to_string(uint8_t max_print_bytes = 255) const {
|
std::string to_string(uint8_t max_print_bytes = 255) const {
|
||||||
std::string info;
|
std::string info;
|
||||||
if (this->is_valid()) {
|
if (this->is_valid()) {
|
||||||
info = str_sprintf(this->get_three_byte_address() ? "[%06X %s %06X] Type: %02X" : "[%04X %s %04X] Type: %02X",
|
info = str_sprintf(this->get_three_byte_address() ? "[%06" PRIX32 " %s %06" PRIX32 "] Type: %02X"
|
||||||
|
: "[%04" PRIX32 " %s %04" PRIX32 "] Type: %02X",
|
||||||
this->get_source_address(), this->get_retransmission() ? "»" : ">",
|
this->get_source_address(), this->get_retransmission() ? "»" : ">",
|
||||||
this->get_destination_address(), this->get_message_type());
|
this->get_destination_address(), this->get_message_type());
|
||||||
if (this->get_data_size())
|
if (this->get_data_size())
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "byronsx_protocol.h"
|
#include "byronsx_protocol.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace remote_base {
|
namespace remote_base {
|
||||||
|
|
||||||
|
@ -57,7 +59,7 @@ void ByronSXProtocol::encode(RemoteTransmitData *dst, const ByronSXData &data) {
|
||||||
out_data <<= NBITS_COMMAND;
|
out_data <<= NBITS_COMMAND;
|
||||||
out_data |= data.command;
|
out_data |= data.command;
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Send ByronSX: out_data %03x", out_data);
|
ESP_LOGV(TAG, "Send ByronSX: out_data %03" PRIx32, out_data);
|
||||||
|
|
||||||
// Initial Mark start bit
|
// Initial Mark start bit
|
||||||
dst->mark(1 * BIT_TIME_US);
|
dst->mark(1 * BIT_TIME_US);
|
||||||
|
@ -90,13 +92,16 @@ optional<ByronSXData> ByronSXProtocol::decode(RemoteReceiveData src) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "%3d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0),
|
ESP_LOGVV(TAG,
|
||||||
src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8),
|
"%3" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||||
src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15),
|
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||||
src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
" %" PRId32 " %" PRId32 " %" PRId32,
|
||||||
|
src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6),
|
||||||
|
src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
|
||||||
|
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
||||||
|
|
||||||
ESP_LOGVV(TAG, " %d %d %d %d %d %d", src.peek(20), src.peek(21), src.peek(22), src.peek(23), src.peek(24),
|
ESP_LOGVV(TAG, " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32, src.peek(20),
|
||||||
src.peek(25));
|
src.peek(21), src.peek(22), src.peek(23), src.peek(24), src.peek(25));
|
||||||
|
|
||||||
// Read data bits
|
// Read data bits
|
||||||
uint32_t out_data = 0;
|
uint32_t out_data = 0;
|
||||||
|
@ -107,10 +112,10 @@ optional<ByronSXData> ByronSXProtocol::decode(RemoteReceiveData src) {
|
||||||
} else if (src.expect_space(BIT_TIME_US) && src.expect_mark(2 * BIT_TIME_US)) {
|
} else if (src.expect_space(BIT_TIME_US) && src.expect_mark(2 * BIT_TIME_US)) {
|
||||||
out_data |= 0 << bit;
|
out_data |= 0 << bit;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08x", bit, out_data);
|
ESP_LOGV(TAG, "Decode ByronSX: Fail 2, %2d %08" PRIx32, bit, out_data);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08x", bit, out_data);
|
ESP_LOGVV(TAG, "Decode ByronSX: Data, %2d %08" PRIx32, bit, out_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last bit followed by a long space
|
// last bit followed by a long space
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "drayton_protocol.h"
|
#include "drayton_protocol.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace remote_base {
|
namespace remote_base {
|
||||||
|
|
||||||
|
@ -151,12 +153,12 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||||
|
|
||||||
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
||||||
while (src.size() - src.get_index() >= MIN_RX_SRC) {
|
while (src.size() - src.get_index() >= MIN_RX_SRC) {
|
||||||
ESP_LOGVV(TAG, "Decode Drayton: sync search %d, %" PRId32 " %" PRId32, src.size() - src.get_index(), src.peek(),
|
ESP_LOGVV(TAG, "Decode Drayton: sync search %" PRIu32 ", %" PRId32 " %" PRId32, src.size() - src.get_index(),
|
||||||
src.peek(1));
|
src.peek(), src.peek(1));
|
||||||
if (src.peek_mark(2 * BIT_TIME_US) &&
|
if (src.peek_mark(2 * BIT_TIME_US) &&
|
||||||
(src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) {
|
(src.peek_space(2 * BIT_TIME_US, 1) || src.peek_space(3 * BIT_TIME_US, 1))) {
|
||||||
src.advance(1);
|
src.advance(1);
|
||||||
ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %d", src.get_index());
|
ESP_LOGVV(TAG, "Decode Drayton: Found SYNC, - %" PRIu32, src.get_index());
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
src.advance(2);
|
src.advance(2);
|
||||||
|
@ -174,14 +176,16 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||||
// Checks next bit to leave index pointing correctly
|
// Checks next bit to leave index pointing correctly
|
||||||
uint32_t out_data = 0;
|
uint32_t out_data = 0;
|
||||||
uint8_t bit = NDATABITS - 1;
|
uint8_t bit = NDATABITS - 1;
|
||||||
ESP_LOGVV(TAG, "Decode Drayton: first bit %d %" PRId32 ", %" PRId32, src.peek(0), src.peek(1), src.peek(2));
|
ESP_LOGVV(TAG, "Decode Drayton: first bit %" PRId32 " %" PRId32 ", %" PRId32, src.peek(0), src.peek(1),
|
||||||
|
src.peek(2));
|
||||||
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
||||||
out_data |= 0 << bit;
|
out_data |= 0 << bit;
|
||||||
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
|
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
|
||||||
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
||||||
out_data |= 1 << bit;
|
out_data |= 1 << bit;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %d %d %d", src.peek(-1), src.peek(0), src.peek(1));
|
ESP_LOGV(TAG, "Decode Drayton: Fail 2, - %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0),
|
||||||
|
src.peek(1));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +206,8 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bit > 0) {
|
if (bit > 0) {
|
||||||
ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %d %" PRId32 " %" PRId32, src.peek(-1), src.peek(0), src.peek(1));
|
ESP_LOGVV(TAG, "Decode Drayton: Fail 3, %" PRId32 " %" PRId32 " %" PRId32, src.peek(-1), src.peek(0),
|
||||||
|
src.peek(1));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +219,7 @@ optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08" PRIx32, bit, out_data);
|
||||||
|
|
||||||
out.channel = (uint8_t) (out_data & 0x1F);
|
out.channel = (uint8_t) (out_data & 0x1F);
|
||||||
out_data >>= NBITS_CHANNEL;
|
out_data >>= NBITS_CHANNEL;
|
||||||
|
|
|
@ -52,7 +52,7 @@ void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) {
|
||||||
// Encrypted field
|
// Encrypted field
|
||||||
out_data = data.encrypted;
|
out_data = data.encrypted;
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04x", out_data);
|
ESP_LOGV(TAG, "Send Keeloq: Encrypted data %04" PRIx32, out_data);
|
||||||
|
|
||||||
for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) {
|
for (uint32_t mask = 1, cnt = 0; cnt < NBITS_ENCRYPTED_DATA; cnt++, mask <<= 1) {
|
||||||
if (out_data & mask) {
|
if (out_data & mask) {
|
||||||
|
@ -68,7 +68,7 @@ void KeeloqProtocol::encode(RemoteTransmitData *dst, const KeeloqData &data) {
|
||||||
out_data = (data.command & 0x0f);
|
out_data = (data.command & 0x0f);
|
||||||
out_data <<= NBITS_SERIAL;
|
out_data <<= NBITS_SERIAL;
|
||||||
out_data |= data.address;
|
out_data |= data.address;
|
||||||
ESP_LOGV(TAG, "Send Keeloq: Fixed data %04x", out_data);
|
ESP_LOGV(TAG, "Send Keeloq: Fixed data %04" PRIx32, out_data);
|
||||||
|
|
||||||
for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) {
|
for (uint32_t mask = 1, cnt = 0; cnt < (NBITS_FIXED_DATA - 2); cnt++, mask <<= 1) {
|
||||||
if (out_data & mask) {
|
if (out_data & mask) {
|
||||||
|
@ -111,21 +111,24 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGVV(TAG, "%2d: %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(), src.peek(0),
|
ESP_LOGVV(TAG,
|
||||||
src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7), src.peek(8),
|
"%2" PRId32 ": %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||||
src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14), src.peek(15),
|
" %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32 " %" PRId32
|
||||||
src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
" %" PRId32 " %" PRId32 " %" PRId32,
|
||||||
|
src.size(), src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6),
|
||||||
|
src.peek(7), src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
|
||||||
|
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
||||||
|
|
||||||
// Check preamble bits
|
// Check preamble bits
|
||||||
int8_t bit = NBITS_PREAMBLE - 1;
|
int8_t bit = NBITS_PREAMBLE - 1;
|
||||||
while (--bit >= 0) {
|
while (--bit >= 0) {
|
||||||
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) {
|
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(BIT_TIME_US)) {
|
||||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek());
|
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) {
|
if (!src.expect_mark(BIT_TIME_US) || !src.expect_space(10 * BIT_TIME_US)) {
|
||||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %d", bit + 1, src.peek());
|
ESP_LOGV(TAG, "Decode KeeLoq: Fail 1, %d %" PRId32, bit + 1, src.peek());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,11 +140,11 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
|
||||||
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
||||||
out_data |= 1 << bit;
|
out_data |= 1 << bit;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %d %d", src.get_index(), src.peek());
|
ESP_LOGV(TAG, "Decode KeeLoq: Fail 2, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08x", bit, out_data);
|
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %d %08" PRIx32, bit, out_data);
|
||||||
out.encrypted = out_data;
|
out.encrypted = out_data;
|
||||||
|
|
||||||
// Read Serial Number and Button Status
|
// Read Serial Number and Button Status
|
||||||
|
@ -152,11 +155,11 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
|
||||||
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
||||||
out_data |= 1 << bit;
|
out_data |= 1 << bit;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %d %d", src.get_index(), src.peek());
|
ESP_LOGV(TAG, "Decode KeeLoq: Fail 3, %" PRIu32 " %" PRId32, src.get_index(), src.peek());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08x", bit, out_data);
|
ESP_LOGVV(TAG, "Decode KeeLoq: Data, %2d %08" PRIx32, bit, out_data);
|
||||||
out.command = (out_data >> 28) & 0xf;
|
out.command = (out_data >> 28) & 0xf;
|
||||||
out.address = out_data & 0xfffffff;
|
out.address = out_data & 0xfffffff;
|
||||||
|
|
||||||
|
@ -166,7 +169,7 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
|
||||||
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
} else if (src.expect_mark(BIT_TIME_US) && src.expect_space(2 * BIT_TIME_US)) {
|
||||||
out.vlow = true;
|
out.vlow = true;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %08x", src.peek());
|
ESP_LOGV(TAG, "Decode KeeLoq: Fail 4, %" PRId32, src.peek());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +179,7 @@ optional<KeeloqData> KeeloqProtocol::decode(RemoteReceiveData src) {
|
||||||
} else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) {
|
} else if (src.expect_mark(BIT_TIME_US) && src.peek_space_at_least(2 * BIT_TIME_US)) {
|
||||||
out.repeat = true;
|
out.repeat = true;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %08x", src.peek());
|
ESP_LOGV(TAG, "Decode KeeLoq: Fail 5, %" PRId32, src.peek());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,70 @@
|
||||||
|
from esphome.cpp_generator import RawExpression
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_DISABLED,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_NUM_ATTEMPTS,
|
||||||
|
CONF_REBOOT_TIMEOUT,
|
||||||
|
CONF_SAFE_MODE,
|
||||||
|
CONF_TRIGGER_ID,
|
||||||
|
KEY_PAST_SAFE_MODE,
|
||||||
|
)
|
||||||
|
from esphome.core import CORE, coroutine_with_priority
|
||||||
|
from esphome import automation
|
||||||
|
|
||||||
CODEOWNERS = ["@paulmonigatti", "@jsuanet"]
|
|
||||||
|
CODEOWNERS = ["@paulmonigatti", "@jsuanet", "@kbx81"]
|
||||||
|
|
||||||
|
CONF_ON_SAFE_MODE = "on_safe_mode"
|
||||||
|
|
||||||
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
|
safe_mode_ns = cg.esphome_ns.namespace("safe_mode")
|
||||||
|
SafeModeComponent = safe_mode_ns.class_("SafeModeComponent", cg.Component)
|
||||||
|
SafeModeTrigger = safe_mode_ns.class_("SafeModeTrigger", automation.Trigger.template())
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_id_if_disabled(value):
|
||||||
|
value = value.copy()
|
||||||
|
if value[CONF_DISABLED]:
|
||||||
|
value.pop(CONF_ID)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(SafeModeComponent),
|
||||||
|
cv.Optional(CONF_DISABLED, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_NUM_ATTEMPTS, default="10"): cv.positive_not_null_int,
|
||||||
|
cv.Optional(
|
||||||
|
CONF_REBOOT_TIMEOUT, default="5min"
|
||||||
|
): cv.positive_time_period_milliseconds,
|
||||||
|
cv.Optional(CONF_ON_SAFE_MODE): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SafeModeTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
|
_remove_id_if_disabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@coroutine_with_priority(50.0)
|
||||||
|
async def to_code(config):
|
||||||
|
if config[CONF_DISABLED]:
|
||||||
|
return
|
||||||
|
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
for conf in config.get(CONF_ON_SAFE_MODE, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
|
condition = var.should_enter_safe_mode(
|
||||||
|
config[CONF_NUM_ATTEMPTS], config[CONF_REBOOT_TIMEOUT]
|
||||||
|
)
|
||||||
|
cg.add(RawExpression(f"if ({condition}) return"))
|
||||||
|
CORE.data[CONF_SAFE_MODE] = {}
|
||||||
|
CORE.data[CONF_SAFE_MODE][KEY_PAST_SAFE_MODE] = True
|
||||||
|
|
17
esphome/components/safe_mode/automation.h
Normal file
17
esphome/components/safe_mode/automation.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
#include "safe_mode.h"
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace safe_mode {
|
||||||
|
|
||||||
|
class SafeModeTrigger : public Trigger<> {
|
||||||
|
public:
|
||||||
|
explicit SafeModeTrigger(SafeModeComponent *parent) {
|
||||||
|
parent->add_on_safe_mode_callback([this, parent]() { trigger(); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace safe_mode
|
||||||
|
} // namespace esphome
|
|
@ -1,16 +1,15 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import button
|
from esphome.components import button
|
||||||
from esphome.components.esphome.ota import ESPHomeOTAComponent
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ESPHOME,
|
CONF_SAFE_MODE,
|
||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
ENTITY_CATEGORY_CONFIG,
|
ENTITY_CATEGORY_CONFIG,
|
||||||
ICON_RESTART_ALERT,
|
ICON_RESTART_ALERT,
|
||||||
)
|
)
|
||||||
from .. import safe_mode_ns
|
from .. import safe_mode_ns, SafeModeComponent
|
||||||
|
|
||||||
DEPENDENCIES = ["ota.esphome"]
|
DEPENDENCIES = ["safe_mode"]
|
||||||
|
|
||||||
SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component)
|
SafeModeButton = safe_mode_ns.class_("SafeModeButton", button.Button, cg.Component)
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ CONFIG_SCHEMA = (
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_RESTART_ALERT,
|
icon=ICON_RESTART_ALERT,
|
||||||
)
|
)
|
||||||
.extend({cv.GenerateID(CONF_ESPHOME): cv.use_id(ESPHomeOTAComponent)})
|
.extend({cv.GenerateID(CONF_SAFE_MODE): cv.use_id(SafeModeComponent)})
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,5 +29,5 @@ async def to_code(config):
|
||||||
var = await button.new_button(config)
|
var = await button.new_button(config)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
ota = await cg.get_variable(config[CONF_ESPHOME])
|
safe_mode_component = await cg.get_variable(config[CONF_SAFE_MODE])
|
||||||
cg.add(var.set_ota(ota))
|
cg.add(var.set_safe_mode(safe_mode_component))
|
||||||
|
|
|
@ -8,11 +8,13 @@ namespace safe_mode {
|
||||||
|
|
||||||
static const char *const TAG = "safe_mode.button";
|
static const char *const TAG = "safe_mode.button";
|
||||||
|
|
||||||
void SafeModeButton::set_ota(esphome::ESPHomeOTAComponent *ota) { this->ota_ = ota; }
|
void SafeModeButton::set_safe_mode(SafeModeComponent *safe_mode_component) {
|
||||||
|
this->safe_mode_component_ = safe_mode_component;
|
||||||
|
}
|
||||||
|
|
||||||
void SafeModeButton::press_action() {
|
void SafeModeButton::press_action() {
|
||||||
ESP_LOGI(TAG, "Restarting device in safe mode...");
|
ESP_LOGI(TAG, "Restarting device in safe mode...");
|
||||||
this->ota_->set_safe_mode_pending(true);
|
this->safe_mode_component_->set_safe_mode_pending(true);
|
||||||
|
|
||||||
// Let MQTT settle a bit
|
// Let MQTT settle a bit
|
||||||
delay(100); // NOLINT
|
delay(100); // NOLINT
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/components/button/button.h"
|
#include "esphome/components/button/button.h"
|
||||||
#include "esphome/components/esphome/ota/ota_esphome.h"
|
#include "esphome/components/safe_mode/safe_mode.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -10,10 +10,10 @@ namespace safe_mode {
|
||||||
class SafeModeButton : public button::Button, public Component {
|
class SafeModeButton : public button::Button, public Component {
|
||||||
public:
|
public:
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void set_ota(esphome::ESPHomeOTAComponent *ota);
|
void set_safe_mode(SafeModeComponent *safe_mode_component);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
esphome::ESPHomeOTAComponent *ota_;
|
SafeModeComponent *safe_mode_component_;
|
||||||
void press_action() override;
|
void press_action() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
127
esphome/components/safe_mode/safe_mode.cpp
Normal file
127
esphome/components/safe_mode/safe_mode.cpp
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
#include "safe_mode.h"
|
||||||
|
|
||||||
|
#include "esphome/core/application.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/util.h"
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace safe_mode {
|
||||||
|
|
||||||
|
static const char *const TAG = "safe_mode";
|
||||||
|
|
||||||
|
void SafeModeComponent::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Safe Mode:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Invoke after %u boot attempts", this->safe_mode_num_attempts_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Remain in safe mode for %" PRIu32 " seconds",
|
||||||
|
this->safe_mode_enable_time_ / 1000); // because milliseconds
|
||||||
|
|
||||||
|
if (this->safe_mode_rtc_value_ > 1 && this->safe_mode_rtc_value_ != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||||
|
auto remaining_restarts = this->safe_mode_num_attempts_ - this->safe_mode_rtc_value_;
|
||||||
|
if (remaining_restarts) {
|
||||||
|
ESP_LOGW(TAG, "Last reset occurred too quickly; safe mode will be invoked in %" PRIu32 " restarts",
|
||||||
|
remaining_restarts);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float SafeModeComponent::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
|
void SafeModeComponent::loop() {
|
||||||
|
if (!this->boot_successful_ && (millis() - this->safe_mode_start_time_) > this->safe_mode_enable_time_) {
|
||||||
|
// successful boot, reset counter
|
||||||
|
ESP_LOGI(TAG, "Boot seems successful; resetting boot loop counter");
|
||||||
|
this->clean_rtc();
|
||||||
|
this->boot_successful_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SafeModeComponent::set_safe_mode_pending(const bool &pending) {
|
||||||
|
uint32_t current_rtc = this->read_rtc_();
|
||||||
|
|
||||||
|
if (pending && current_rtc != SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||||
|
ESP_LOGI(TAG, "Device will enter safe mode on next boot");
|
||||||
|
this->write_rtc_(SafeModeComponent::ENTER_SAFE_MODE_MAGIC);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pending && current_rtc == SafeModeComponent::ENTER_SAFE_MODE_MAGIC) {
|
||||||
|
ESP_LOGI(TAG, "Safe mode pending has been cleared");
|
||||||
|
this->clean_rtc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SafeModeComponent::get_safe_mode_pending() {
|
||||||
|
return this->read_rtc_() == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SafeModeComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time) {
|
||||||
|
this->safe_mode_start_time_ = millis();
|
||||||
|
this->safe_mode_enable_time_ = enable_time;
|
||||||
|
this->safe_mode_num_attempts_ = num_attempts;
|
||||||
|
this->rtc_ = global_preferences->make_preference<uint32_t>(233825507UL, false);
|
||||||
|
this->safe_mode_rtc_value_ = this->read_rtc_();
|
||||||
|
|
||||||
|
bool is_manual_safe_mode = this->safe_mode_rtc_value_ == SafeModeComponent::ENTER_SAFE_MODE_MAGIC;
|
||||||
|
|
||||||
|
if (is_manual_safe_mode) {
|
||||||
|
ESP_LOGI(TAG, "Safe mode invoked manually");
|
||||||
|
} else {
|
||||||
|
ESP_LOGCONFIG(TAG, "There have been %" PRIu32 " suspected unsuccessful boot attempts", this->safe_mode_rtc_value_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->safe_mode_rtc_value_ >= num_attempts || is_manual_safe_mode) {
|
||||||
|
this->clean_rtc();
|
||||||
|
|
||||||
|
if (!is_manual_safe_mode) {
|
||||||
|
ESP_LOGE(TAG, "Boot loop detected. Proceeding to safe mode");
|
||||||
|
}
|
||||||
|
|
||||||
|
this->status_set_error();
|
||||||
|
this->set_timeout(enable_time, []() {
|
||||||
|
ESP_LOGW(TAG, "Safe mode enable time has elapsed -- restarting");
|
||||||
|
App.reboot();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delay here to allow power to stabilize before Wi-Fi/Ethernet is initialised
|
||||||
|
delay(300); // NOLINT
|
||||||
|
App.setup();
|
||||||
|
|
||||||
|
ESP_LOGW(TAG, "SAFE MODE IS ACTIVE");
|
||||||
|
|
||||||
|
this->safe_mode_callback_.call();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// increment counter
|
||||||
|
this->write_rtc_(this->safe_mode_rtc_value_ + 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SafeModeComponent::write_rtc_(uint32_t val) {
|
||||||
|
this->rtc_.save(&val);
|
||||||
|
global_preferences->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SafeModeComponent::read_rtc_() {
|
||||||
|
uint32_t val;
|
||||||
|
if (!this->rtc_.load(&val))
|
||||||
|
return 0;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SafeModeComponent::clean_rtc() { this->write_rtc_(0); }
|
||||||
|
|
||||||
|
void SafeModeComponent::on_safe_shutdown() {
|
||||||
|
if (this->read_rtc_() != SafeModeComponent::ENTER_SAFE_MODE_MAGIC)
|
||||||
|
this->clean_rtc();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace safe_mode
|
||||||
|
} // namespace esphome
|
49
esphome/components/safe_mode/safe_mode.h
Normal file
49
esphome/components/safe_mode/safe_mode.h
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/defines.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/preferences.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace safe_mode {
|
||||||
|
|
||||||
|
/// SafeModeComponent provides a safe way to recover from repeated boot failures
|
||||||
|
class SafeModeComponent : public Component {
|
||||||
|
public:
|
||||||
|
bool should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_time);
|
||||||
|
|
||||||
|
/// Set to true if the next startup will enter safe mode
|
||||||
|
void set_safe_mode_pending(const bool &pending);
|
||||||
|
bool get_safe_mode_pending();
|
||||||
|
|
||||||
|
void dump_config() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
void clean_rtc();
|
||||||
|
|
||||||
|
void on_safe_shutdown() override;
|
||||||
|
|
||||||
|
void add_on_safe_mode_callback(std::function<void()> &&callback) {
|
||||||
|
this->safe_mode_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void write_rtc_(uint32_t val);
|
||||||
|
uint32_t read_rtc_();
|
||||||
|
|
||||||
|
bool boot_successful_{false}; ///< set to true after boot is considered successful
|
||||||
|
uint32_t safe_mode_start_time_; ///< stores when safe mode was enabled
|
||||||
|
uint32_t safe_mode_enable_time_{60000}; ///< The time safe mode should remain active for
|
||||||
|
uint32_t safe_mode_rtc_value_;
|
||||||
|
uint8_t safe_mode_num_attempts_;
|
||||||
|
ESPPreferenceObject rtc_;
|
||||||
|
CallbackManager<void()> safe_mode_callback_{};
|
||||||
|
|
||||||
|
static const uint32_t ENTER_SAFE_MODE_MAGIC =
|
||||||
|
0x5afe5afe; ///< a magic number to indicate that safe mode should be entered on next boot
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace safe_mode
|
||||||
|
} // namespace esphome
|
|
@ -1,15 +1,14 @@
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.components import switch
|
from esphome.components import switch
|
||||||
from esphome.components.esphome.ota import ESPHomeOTAComponent
|
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ESPHOME,
|
CONF_SAFE_MODE,
|
||||||
ENTITY_CATEGORY_CONFIG,
|
ENTITY_CATEGORY_CONFIG,
|
||||||
ICON_RESTART_ALERT,
|
ICON_RESTART_ALERT,
|
||||||
)
|
)
|
||||||
from .. import safe_mode_ns
|
from .. import safe_mode_ns, SafeModeComponent
|
||||||
|
|
||||||
DEPENDENCIES = ["ota.esphome"]
|
DEPENDENCIES = ["safe_mode"]
|
||||||
|
|
||||||
SafeModeSwitch = safe_mode_ns.class_("SafeModeSwitch", switch.Switch, cg.Component)
|
SafeModeSwitch = safe_mode_ns.class_("SafeModeSwitch", switch.Switch, cg.Component)
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ CONFIG_SCHEMA = (
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_RESTART_ALERT,
|
icon=ICON_RESTART_ALERT,
|
||||||
)
|
)
|
||||||
.extend({cv.GenerateID(CONF_ESPHOME): cv.use_id(ESPHomeOTAComponent)})
|
.extend({cv.GenerateID(CONF_SAFE_MODE): cv.use_id(SafeModeComponent)})
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,5 +28,5 @@ async def to_code(config):
|
||||||
var = await switch.new_switch(config)
|
var = await switch.new_switch(config)
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
ota = await cg.get_variable(config[CONF_ESPHOME])
|
safe_mode_component = await cg.get_variable(config[CONF_SAFE_MODE])
|
||||||
cg.add(var.set_ota(ota))
|
cg.add(var.set_safe_mode(safe_mode_component))
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace safe_mode {
|
namespace safe_mode {
|
||||||
|
|
||||||
static const char *const TAG = "safe_mode_switch";
|
static const char *const TAG = "safe_mode.switch";
|
||||||
|
|
||||||
void SafeModeSwitch::set_ota(esphome::ESPHomeOTAComponent *ota) { this->ota_ = ota; }
|
void SafeModeSwitch::set_safe_mode(SafeModeComponent *safe_mode_component) {
|
||||||
|
this->safe_mode_component_ = safe_mode_component;
|
||||||
|
}
|
||||||
|
|
||||||
void SafeModeSwitch::write_state(bool state) {
|
void SafeModeSwitch::write_state(bool state) {
|
||||||
// Acknowledge
|
// Acknowledge
|
||||||
|
@ -16,13 +18,14 @@ void SafeModeSwitch::write_state(bool state) {
|
||||||
|
|
||||||
if (state) {
|
if (state) {
|
||||||
ESP_LOGI(TAG, "Restarting device in safe mode...");
|
ESP_LOGI(TAG, "Restarting device in safe mode...");
|
||||||
this->ota_->set_safe_mode_pending(true);
|
this->safe_mode_component_->set_safe_mode_pending(true);
|
||||||
|
|
||||||
// Let MQTT settle a bit
|
// Let MQTT settle a bit
|
||||||
delay(100); // NOLINT
|
delay(100); // NOLINT
|
||||||
App.safe_reboot();
|
App.safe_reboot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SafeModeSwitch::dump_config() { LOG_SWITCH("", "Safe Mode Switch", this); }
|
void SafeModeSwitch::dump_config() { LOG_SWITCH("", "Safe Mode Switch", this); }
|
||||||
|
|
||||||
} // namespace safe_mode
|
} // namespace safe_mode
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "esphome/components/esphome/ota/ota_esphome.h"
|
#include "esphome/components/safe_mode/safe_mode.h"
|
||||||
#include "esphome/components/switch/switch.h"
|
#include "esphome/components/switch/switch.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@ namespace safe_mode {
|
||||||
class SafeModeSwitch : public switch_::Switch, public Component {
|
class SafeModeSwitch : public switch_::Switch, public Component {
|
||||||
public:
|
public:
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
void set_ota(esphome::ESPHomeOTAComponent *ota);
|
void set_safe_mode(SafeModeComponent *safe_mode_component);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
esphome::ESPHomeOTAComponent *ota_;
|
SafeModeComponent *safe_mode_component_;
|
||||||
void write_state(bool state) override;
|
void write_state(bool state) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -469,7 +469,8 @@ class LWIPRawImpl : public Socket {
|
||||||
}
|
}
|
||||||
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override {
|
ssize_t sendto(const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen) override {
|
||||||
// return ::sendto(fd_, buf, len, flags, to, tolen);
|
// return ::sendto(fd_, buf, len, flags, to, tolen);
|
||||||
return 0;
|
errno = ENOSYS;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
int setblocking(bool blocking) override {
|
int setblocking(bool blocking) override {
|
||||||
if (pcb_ == nullptr) {
|
if (pcb_ == nullptr) {
|
||||||
|
|
|
@ -128,7 +128,8 @@ bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) {
|
||||||
// Expected acknowledgement from rf chip
|
// Expected acknowledgement from rf chip
|
||||||
uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00};
|
uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00};
|
||||||
uint8_t buffer[sizeof(ref_buffer)] = {0};
|
uint8_t buffer[sizeof(ref_buffer)] = {0};
|
||||||
uint32_t pos = 0, buf_len = sizeof(ref_buffer);
|
uint32_t pos = 0;
|
||||||
|
size_t buf_len = sizeof(ref_buffer);
|
||||||
|
|
||||||
// Update the reference checksum
|
// Update the reference checksum
|
||||||
this->populate_checksum_(ref_buffer, sizeof(ref_buffer));
|
this->populate_checksum_(ref_buffer, sizeof(ref_buffer));
|
||||||
|
|
|
@ -11,6 +11,7 @@ from esphome.const import (
|
||||||
CONF_OUTPUT,
|
CONF_OUTPUT,
|
||||||
CONF_PULLDOWN,
|
CONF_PULLDOWN,
|
||||||
CONF_PULLUP,
|
CONF_PULLUP,
|
||||||
|
CONF_OPEN_DRAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
CONF_KEYPAD = "keypad"
|
CONF_KEYPAD = "keypad"
|
||||||
|
@ -79,6 +80,8 @@ def validate_mode(value):
|
||||||
raise cv.Invalid("Pulldown only available with input")
|
raise cv.Invalid("Pulldown only available with input")
|
||||||
if value[CONF_PULLUP] and value[CONF_PULLDOWN]:
|
if value[CONF_PULLUP] and value[CONF_PULLDOWN]:
|
||||||
raise cv.Invalid("Can only have one of pullup or pulldown")
|
raise cv.Invalid("Can only have one of pullup or pulldown")
|
||||||
|
if value[CONF_OPEN_DRAIN] and not value[CONF_OUTPUT]:
|
||||||
|
raise cv.Invalid("Open drain available only with output")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@ -94,6 +97,7 @@ SX1509_PIN_SCHEMA = cv.All(
|
||||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
||||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||||
|
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
|
||||||
},
|
},
|
||||||
validate_mode,
|
validate_mode,
|
||||||
),
|
),
|
||||||
|
|
|
@ -86,33 +86,63 @@ void SX1509Component::digital_write(uint8_t pin, bool bit_value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) {
|
void SX1509Component::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||||
|
ESP_LOGI(TAG, "Configuring pin %u with flags %x", pin, flags);
|
||||||
|
|
||||||
|
uint16_t temp_word = 0;
|
||||||
|
|
||||||
this->read_byte_16(REG_DIR_B, &this->ddr_mask_);
|
this->read_byte_16(REG_DIR_B, &this->ddr_mask_);
|
||||||
if (flags == gpio::FLAG_OUTPUT) {
|
if (flags & gpio::FLAG_OUTPUT) {
|
||||||
|
// Always disable input buffer
|
||||||
|
this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
|
||||||
|
temp_word |= (1 << pin);
|
||||||
|
this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
|
||||||
|
|
||||||
|
if (flags & gpio::FLAG_OPEN_DRAIN) {
|
||||||
|
// Pullup must be disabled for open drain mode
|
||||||
|
this->read_byte_16(REG_PULL_UP_B, &temp_word);
|
||||||
|
temp_word &= ~(1 << pin);
|
||||||
|
this->write_byte_16(REG_PULL_UP_B, temp_word);
|
||||||
|
this->read_byte_16(REG_OPEN_DRAIN_B, &temp_word);
|
||||||
|
temp_word |= (1 << pin);
|
||||||
|
this->write_byte_16(REG_OPEN_DRAIN_B, temp_word);
|
||||||
|
ESP_LOGD(TAG, "Open drain output mode set for %u", pin);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "Output Mode for %u", pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set direction to output
|
||||||
this->ddr_mask_ &= ~(1 << pin);
|
this->ddr_mask_ &= ~(1 << pin);
|
||||||
} else {
|
|
||||||
this->ddr_mask_ |= (1 << pin);
|
|
||||||
|
|
||||||
uint16_t temp_pullup;
|
|
||||||
this->read_byte_16(REG_PULL_UP_B, &temp_pullup);
|
|
||||||
uint16_t temp_pulldown;
|
|
||||||
this->read_byte_16(REG_PULL_DOWN_B, &temp_pulldown);
|
|
||||||
|
|
||||||
if (flags & gpio::FLAG_PULLUP) {
|
|
||||||
temp_pullup |= (1 << pin);
|
|
||||||
} else {
|
|
||||||
temp_pullup &= ~(1 << pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & gpio::FLAG_PULLDOWN) {
|
|
||||||
temp_pulldown |= (1 << pin);
|
|
||||||
} else {
|
|
||||||
temp_pulldown &= ~(1 << pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->write_byte_16(REG_PULL_UP_B, temp_pullup);
|
|
||||||
this->write_byte_16(REG_PULL_DOWN_B, temp_pulldown);
|
|
||||||
}
|
|
||||||
this->write_byte_16(REG_DIR_B, this->ddr_mask_);
|
this->write_byte_16(REG_DIR_B, this->ddr_mask_);
|
||||||
|
} else {
|
||||||
|
ESP_LOGD(TAG, "Input Mode for %u", pin);
|
||||||
|
|
||||||
|
// Always enable input buffer
|
||||||
|
this->read_byte_16(REG_INPUT_DISABLE_B, &temp_word);
|
||||||
|
temp_word &= ~(1 << pin);
|
||||||
|
this->write_byte_16(REG_INPUT_DISABLE_B, temp_word);
|
||||||
|
|
||||||
|
// Pullup
|
||||||
|
this->read_byte_16(REG_PULL_UP_B, &temp_word);
|
||||||
|
if (flags & gpio::FLAG_PULLUP) {
|
||||||
|
temp_word |= (1 << pin);
|
||||||
|
} else {
|
||||||
|
temp_word &= ~(1 << pin);
|
||||||
|
}
|
||||||
|
this->write_byte_16(REG_PULL_UP_B, temp_word);
|
||||||
|
|
||||||
|
// Pulldown
|
||||||
|
this->read_byte_16(REG_PULL_DOWN_B, &temp_word);
|
||||||
|
if (flags & gpio::FLAG_PULLDOWN) {
|
||||||
|
temp_word |= (1 << pin);
|
||||||
|
} else {
|
||||||
|
temp_word &= ~(1 << pin);
|
||||||
|
}
|
||||||
|
this->write_byte_16(REG_PULL_DOWN_B, temp_word);
|
||||||
|
|
||||||
|
// Set direction to input
|
||||||
|
this->ddr_mask_ |= (1 << pin);
|
||||||
|
this->write_byte_16(REG_DIR_B, this->ddr_mask_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SX1509Component::setup_led_driver(uint8_t pin) {
|
void SX1509Component::setup_led_driver(uint8_t pin) {
|
||||||
|
|
|
@ -269,6 +269,30 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||||
ESP_LOGV(TAG, "Network status requested, reported as %i", wifi_status);
|
ESP_LOGV(TAG, "Network status requested, reported as %i", wifi_status);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TuyaCommandType::EXTENDED_SERVICES: {
|
||||||
|
uint8_t subcommand = buffer[0];
|
||||||
|
switch ((TuyaExtendedServicesCommandType) subcommand) {
|
||||||
|
case TuyaExtendedServicesCommandType::RESET_NOTIFICATION: {
|
||||||
|
this->send_command_(
|
||||||
|
TuyaCommand{.cmd = TuyaCommandType::EXTENDED_SERVICES,
|
||||||
|
.payload = std::vector<uint8_t>{
|
||||||
|
static_cast<uint8_t>(TuyaExtendedServicesCommandType::RESET_NOTIFICATION), 0x00}});
|
||||||
|
ESP_LOGV(TAG, "Reset status notification enabled");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TuyaExtendedServicesCommandType::MODULE_RESET: {
|
||||||
|
ESP_LOGE(TAG, "EXTENDED_SERVICES::MODULE_RESET is not handled");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TuyaExtendedServicesCommandType::UPDATE_IN_PROGRESS: {
|
||||||
|
ESP_LOGE(TAG, "EXTENDED_SERVICES::UPDATE_IN_PROGRESS is not handled");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ESP_LOGE(TAG, "Invalid extended services subcommand (0x%02X) received", subcommand);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
ESP_LOGE(TAG, "Invalid command (0x%02X) received", command);
|
ESP_LOGE(TAG, "Invalid command (0x%02X) received", command);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,13 @@ enum class TuyaCommandType : uint8_t {
|
||||||
WIFI_RSSI = 0x24,
|
WIFI_RSSI = 0x24,
|
||||||
VACUUM_MAP_UPLOAD = 0x28,
|
VACUUM_MAP_UPLOAD = 0x28,
|
||||||
GET_NETWORK_STATUS = 0x2B,
|
GET_NETWORK_STATUS = 0x2B,
|
||||||
|
EXTENDED_SERVICES = 0x34,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class TuyaExtendedServicesCommandType : uint8_t {
|
||||||
|
RESET_NOTIFICATION = 0x04,
|
||||||
|
MODULE_RESET = 0x05,
|
||||||
|
UPDATE_IN_PROGRESS = 0x0A,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class TuyaInitState : uint8_t {
|
enum class TuyaInitState : uint8_t {
|
||||||
|
|
|
@ -69,7 +69,7 @@ void IDFUARTComponent::setup() {
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->uart_num_ = next_uart_num++;
|
this->uart_num_ = static_cast<uart_port_t>(next_uart_num++);
|
||||||
ESP_LOGCONFIG(TAG, "Setting up UART %u...", this->uart_num_);
|
ESP_LOGCONFIG(TAG, "Setting up UART %u...", this->uart_num_);
|
||||||
|
|
||||||
this->lock_ = xSemaphoreCreateMutex();
|
this->lock_ = xSemaphoreCreateMutex();
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
|
@ -71,6 +72,12 @@ void VoiceAssistant::setup() {
|
||||||
ESP_LOGCONFIG(TAG, "Setting up Voice Assistant...");
|
ESP_LOGCONFIG(TAG, "Setting up Voice Assistant...");
|
||||||
|
|
||||||
global_voice_assistant = this;
|
global_voice_assistant = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VoiceAssistant::allocate_buffers_() {
|
||||||
|
if (this->send_buffer_ != nullptr) {
|
||||||
|
return true; // Already allocated
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef USE_SPEAKER
|
#ifdef USE_SPEAKER
|
||||||
if (this->speaker_ != nullptr) {
|
if (this->speaker_ != nullptr) {
|
||||||
|
@ -78,8 +85,7 @@ void VoiceAssistant::setup() {
|
||||||
this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE);
|
this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE);
|
||||||
if (this->speaker_buffer_ == nullptr) {
|
if (this->speaker_buffer_ == nullptr) {
|
||||||
ESP_LOGW(TAG, "Could not allocate speaker buffer");
|
ESP_LOGW(TAG, "Could not allocate speaker buffer");
|
||||||
this->mark_failed();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -88,8 +94,7 @@ void VoiceAssistant::setup() {
|
||||||
this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE);
|
this->input_buffer_ = allocator.allocate(INPUT_BUFFER_SIZE);
|
||||||
if (this->input_buffer_ == nullptr) {
|
if (this->input_buffer_ == nullptr) {
|
||||||
ESP_LOGW(TAG, "Could not allocate input buffer");
|
ESP_LOGW(TAG, "Could not allocate input buffer");
|
||||||
this->mark_failed();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_ESP_ADF
|
#ifdef USE_ESP_ADF
|
||||||
|
@ -99,17 +104,71 @@ void VoiceAssistant::setup() {
|
||||||
this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
|
this->ring_buffer_ = RingBuffer::create(BUFFER_SIZE * sizeof(int16_t));
|
||||||
if (this->ring_buffer_ == nullptr) {
|
if (this->ring_buffer_ == nullptr) {
|
||||||
ESP_LOGW(TAG, "Could not allocate ring buffer");
|
ESP_LOGW(TAG, "Could not allocate ring buffer");
|
||||||
this->mark_failed();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ExternalRAMAllocator<uint8_t> send_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
ExternalRAMAllocator<uint8_t> send_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE);
|
this->send_buffer_ = send_allocator.allocate(SEND_BUFFER_SIZE);
|
||||||
if (send_buffer_ == nullptr) {
|
if (send_buffer_ == nullptr) {
|
||||||
ESP_LOGW(TAG, "Could not allocate send buffer");
|
ESP_LOGW(TAG, "Could not allocate send buffer");
|
||||||
this->mark_failed();
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceAssistant::clear_buffers_() {
|
||||||
|
if (this->send_buffer_ != nullptr) {
|
||||||
|
memset(this->send_buffer_, 0, SEND_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->input_buffer_ != nullptr) {
|
||||||
|
memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->ring_buffer_ != nullptr) {
|
||||||
|
this->ring_buffer_->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_buffer_ != nullptr) {
|
||||||
|
memset(this->speaker_buffer_, 0, SPEAKER_BUFFER_SIZE);
|
||||||
|
|
||||||
|
this->speaker_buffer_size_ = 0;
|
||||||
|
this->speaker_buffer_index_ = 0;
|
||||||
|
this->speaker_bytes_received_ = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void VoiceAssistant::deallocate_buffers_() {
|
||||||
|
ExternalRAMAllocator<uint8_t> send_deallocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
|
send_deallocator.deallocate(this->send_buffer_, SEND_BUFFER_SIZE);
|
||||||
|
this->send_buffer_ = nullptr;
|
||||||
|
|
||||||
|
if (this->ring_buffer_ != nullptr) {
|
||||||
|
this->ring_buffer_.reset();
|
||||||
|
this->ring_buffer_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_ESP_ADF
|
||||||
|
if (this->vad_instance_ != nullptr) {
|
||||||
|
vad_destroy(this->vad_instance_);
|
||||||
|
this->vad_instance_ = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ExternalRAMAllocator<int16_t> input_deallocator(ExternalRAMAllocator<int16_t>::ALLOW_FAILURE);
|
||||||
|
input_deallocator.deallocate(this->input_buffer_, INPUT_BUFFER_SIZE);
|
||||||
|
this->input_buffer_ = nullptr;
|
||||||
|
|
||||||
|
#ifdef USE_SPEAKER
|
||||||
|
if (this->speaker_buffer_ != nullptr) {
|
||||||
|
ExternalRAMAllocator<uint8_t> speaker_deallocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||||
|
speaker_deallocator.deallocate(this->speaker_buffer_, SPEAKER_BUFFER_SIZE);
|
||||||
|
this->speaker_buffer_ = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
int VoiceAssistant::read_microphone_() {
|
int VoiceAssistant::read_microphone_() {
|
||||||
|
@ -138,14 +197,13 @@ void VoiceAssistant::loop() {
|
||||||
}
|
}
|
||||||
this->continuous_ = false;
|
this->continuous_ = false;
|
||||||
this->signal_stop_();
|
this->signal_stop_();
|
||||||
|
this->clear_buffers_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
case State::IDLE: {
|
case State::IDLE: {
|
||||||
if (this->continuous_ && this->desired_state_ == State::IDLE) {
|
if (this->continuous_ && this->desired_state_ == State::IDLE) {
|
||||||
this->idle_trigger_->trigger();
|
this->idle_trigger_->trigger();
|
||||||
|
|
||||||
this->ring_buffer_->reset();
|
|
||||||
#ifdef USE_ESP_ADF
|
#ifdef USE_ESP_ADF
|
||||||
if (this->use_wake_word_) {
|
if (this->use_wake_word_) {
|
||||||
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
|
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
|
||||||
|
@ -161,8 +219,15 @@ void VoiceAssistant::loop() {
|
||||||
}
|
}
|
||||||
case State::START_MICROPHONE: {
|
case State::START_MICROPHONE: {
|
||||||
ESP_LOGD(TAG, "Starting Microphone");
|
ESP_LOGD(TAG, "Starting Microphone");
|
||||||
memset(this->send_buffer_, 0, SEND_BUFFER_SIZE);
|
if (!this->allocate_buffers_()) {
|
||||||
memset(this->input_buffer_, 0, INPUT_BUFFER_SIZE * sizeof(int16_t));
|
this->status_set_error("Failed to allocate buffers");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this->status_has_error()) {
|
||||||
|
this->status_clear_error();
|
||||||
|
}
|
||||||
|
this->clear_buffers_();
|
||||||
|
|
||||||
this->mic_->start();
|
this->mic_->start();
|
||||||
this->high_freq_.start();
|
this->high_freq_.start();
|
||||||
this->set_state_(State::STARTING_MICROPHONE);
|
this->set_state_(State::STARTING_MICROPHONE);
|
||||||
|
@ -343,10 +408,9 @@ void VoiceAssistant::loop() {
|
||||||
this->speaker_->stop();
|
this->speaker_->stop();
|
||||||
this->cancel_timeout("speaker-timeout");
|
this->cancel_timeout("speaker-timeout");
|
||||||
this->cancel_timeout("playing");
|
this->cancel_timeout("playing");
|
||||||
this->speaker_buffer_size_ = 0;
|
|
||||||
this->speaker_buffer_index_ = 0;
|
this->clear_buffers_();
|
||||||
this->speaker_bytes_received_ = 0;
|
|
||||||
memset(this->speaker_buffer_, 0, SPEAKER_BUFFER_SIZE);
|
|
||||||
this->wait_for_stream_end_ = false;
|
this->wait_for_stream_end_ = false;
|
||||||
this->stream_ended_ = false;
|
this->stream_ended_ = false;
|
||||||
|
|
||||||
|
@ -507,7 +571,6 @@ void VoiceAssistant::request_start(bool continuous, bool silence_detection) {
|
||||||
if (this->state_ == State::IDLE) {
|
if (this->state_ == State::IDLE) {
|
||||||
this->continuous_ = continuous;
|
this->continuous_ = continuous;
|
||||||
this->silence_detection_ = silence_detection;
|
this->silence_detection_ = silence_detection;
|
||||||
this->ring_buffer_->reset();
|
|
||||||
#ifdef USE_ESP_ADF
|
#ifdef USE_ESP_ADF
|
||||||
if (this->use_wake_word_) {
|
if (this->use_wake_word_) {
|
||||||
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
|
this->set_state_(State::START_MICROPHONE, State::WAIT_FOR_VAD);
|
||||||
|
@ -560,7 +623,7 @@ void VoiceAssistant::signal_stop_() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
||||||
ESP_LOGD(TAG, "Event Type: %d", msg.event_type);
|
ESP_LOGD(TAG, "Event Type: %" PRId32, msg.event_type);
|
||||||
switch (msg.event_type) {
|
switch (msg.event_type) {
|
||||||
case api::enums::VOICE_ASSISTANT_RUN_START:
|
case api::enums::VOICE_ASSISTANT_RUN_START:
|
||||||
ESP_LOGD(TAG, "Assist Pipeline running");
|
ESP_LOGD(TAG, "Assist Pipeline running");
|
||||||
|
@ -723,7 +786,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
|
||||||
this->defer([this]() { this->stt_vad_end_trigger_->trigger(); });
|
this->defer([this]() { this->stt_vad_end_trigger_->trigger(); });
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
ESP_LOGD(TAG, "Unhandled event type: %d", msg.event_type);
|
ESP_LOGD(TAG, "Unhandled event type: %" PRId32, msg.event_type);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,6 +151,10 @@ class VoiceAssistant : public Component {
|
||||||
void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
|
void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
bool allocate_buffers_();
|
||||||
|
void clear_buffers_();
|
||||||
|
void deallocate_buffers_();
|
||||||
|
|
||||||
int read_microphone_();
|
int read_microphone_();
|
||||||
void set_state_(State state);
|
void set_state_(State state);
|
||||||
void set_state_(State state, State desired_state);
|
void set_state_(State state, State desired_state);
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
CODEOWNERS = ["@willwill2will54"]
|
CODEOWNERS = ["@willwill2will54", "@clydebarrow"]
|
||||||
|
|
|
@ -2,6 +2,16 @@ import esphome.codegen as cg
|
||||||
from esphome.components import button
|
from esphome.components import button
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
|
DEPENDENCIES = ["network"]
|
||||||
|
|
||||||
|
|
||||||
|
def AUTO_LOAD():
|
||||||
|
if CORE.is_esp8266 or CORE.is_rp2040:
|
||||||
|
return []
|
||||||
|
return ["socket"]
|
||||||
|
|
||||||
|
|
||||||
CONF_TARGET_MAC_ADDRESS = "target_mac_address"
|
CONF_TARGET_MAC_ADDRESS = "target_mac_address"
|
||||||
|
|
||||||
|
@ -9,25 +19,19 @@ wake_on_lan_ns = cg.esphome_ns.namespace("wake_on_lan")
|
||||||
|
|
||||||
WakeOnLanButton = wake_on_lan_ns.class_("WakeOnLanButton", button.Button, cg.Component)
|
WakeOnLanButton = wake_on_lan_ns.class_("WakeOnLanButton", button.Button, cg.Component)
|
||||||
|
|
||||||
DEPENDENCIES = ["network"]
|
CONFIG_SCHEMA = (
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
|
||||||
button.button_schema(WakeOnLanButton)
|
button.button_schema(WakeOnLanButton)
|
||||||
.extend(cv.COMPONENT_SCHEMA)
|
.extend(cv.COMPONENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
cv.Schema(
|
|
||||||
{
|
{
|
||||||
cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address,
|
cv.Required(CONF_TARGET_MAC_ADDRESS): cv.mac_address,
|
||||||
}
|
}
|
||||||
),
|
)
|
||||||
),
|
|
||||||
cv.only_with_arduino,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def to_code(config):
|
async def to_code(config):
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts))
|
||||||
yield cg.add(var.set_macaddr(*config[CONF_TARGET_MAC_ADDRESS].parts))
|
await cg.register_component(var, config)
|
||||||
yield cg.register_component(var, config)
|
await button.register_button(var, config)
|
||||||
yield button.register_button(var, config)
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include "wake_on_lan.h"
|
#include "wake_on_lan.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/components/network/ip_address.h"
|
#include "esphome/components/network/ip_address.h"
|
||||||
|
@ -22,40 +20,68 @@ void WakeOnLanButton::set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, ui
|
||||||
|
|
||||||
void WakeOnLanButton::dump_config() {
|
void WakeOnLanButton::dump_config() {
|
||||||
LOG_BUTTON("", "Wake-on-LAN Button", this);
|
LOG_BUTTON("", "Wake-on-LAN Button", this);
|
||||||
ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", macaddr_[0], macaddr_[1], macaddr_[2],
|
ESP_LOGCONFIG(TAG, " Target MAC address: %02X:%02X:%02X:%02X:%02X:%02X", this->macaddr_[0], this->macaddr_[1],
|
||||||
macaddr_[3], macaddr_[4], macaddr_[5]);
|
this->macaddr_[2], this->macaddr_[3], this->macaddr_[4], this->macaddr_[5]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WakeOnLanButton::press_action() {
|
void WakeOnLanButton::press_action() {
|
||||||
|
if (!network::is_connected()) {
|
||||||
|
ESP_LOGW(TAG, "Network not connected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
ESP_LOGI(TAG, "Sending Wake-on-LAN Packet...");
|
ESP_LOGI(TAG, "Sending Wake-on-LAN Packet...");
|
||||||
bool begin_status = false;
|
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||||
bool end_status = false;
|
struct sockaddr_storage saddr {};
|
||||||
|
auto addr_len =
|
||||||
|
socket::set_sockaddr(reinterpret_cast<sockaddr *>(&saddr), sizeof(saddr), "255.255.255.255", this->port_);
|
||||||
|
uint8_t buffer[6 + sizeof this->macaddr_ * 16];
|
||||||
|
memcpy(buffer, PREFIX, sizeof(PREFIX));
|
||||||
|
for (size_t i = 0; i != 16; i++) {
|
||||||
|
memcpy(buffer + i * sizeof(this->macaddr_) + sizeof(PREFIX), this->macaddr_, sizeof(this->macaddr_));
|
||||||
|
}
|
||||||
|
if (this->broadcast_socket_->sendto(buffer, sizeof(buffer), 0, reinterpret_cast<const sockaddr *>(&saddr),
|
||||||
|
addr_len) <= 0)
|
||||||
|
ESP_LOGW(TAG, "sendto() error %d", errno);
|
||||||
|
#else
|
||||||
IPAddress broadcast = IPAddress(255, 255, 255, 255);
|
IPAddress broadcast = IPAddress(255, 255, 255, 255);
|
||||||
#ifdef USE_ESP8266
|
|
||||||
for (auto ip : esphome::network::get_ip_addresses()) {
|
for (auto ip : esphome::network::get_ip_addresses()) {
|
||||||
if (ip.is_ip4()) {
|
if (ip.is_ip4()) {
|
||||||
begin_status = this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128);
|
if (this->udp_client_.beginPacketMulticast(broadcast, 9, ip, 128) != 0) {
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#ifdef USE_ESP32
|
|
||||||
begin_status = this->udp_client_.beginPacket(broadcast, 9);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (begin_status) {
|
|
||||||
this->udp_client_.write(PREFIX, 6);
|
this->udp_client_.write(PREFIX, 6);
|
||||||
for (size_t i = 0; i < 16; i++) {
|
for (size_t i = 0; i < 16; i++) {
|
||||||
this->udp_client_.write(macaddr_, 6);
|
this->udp_client_.write(macaddr_, 6);
|
||||||
}
|
}
|
||||||
end_status = this->udp_client_.endPacket();
|
if (this->udp_client_.endPacket() != 0)
|
||||||
|
return;
|
||||||
|
ESP_LOGW(TAG, "WOL broadcast failed");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!begin_status || end_status) {
|
|
||||||
ESP_LOGE(TAG, "Sending Wake-on-LAN Packet Failed!");
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ESP_LOGW(TAG, "No ip4 addresses to broadcast to");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void WakeOnLanButton::setup() {
|
||||||
|
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||||
|
this->broadcast_socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||||
|
if (this->broadcast_socket_ == nullptr) {
|
||||||
|
this->mark_failed();
|
||||||
|
this->status_set_error("Could not create socket");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int enable = 1;
|
||||||
|
auto err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
|
||||||
|
if (err != 0) {
|
||||||
|
this->status_set_warning("Socket unable to set reuseaddr");
|
||||||
|
// we can still continue
|
||||||
|
}
|
||||||
|
err = this->broadcast_socket_->setsockopt(SOL_SOCKET, SO_BROADCAST, &enable, sizeof(int));
|
||||||
|
if (err != 0) {
|
||||||
|
this->status_set_warning("Socket unable to set broadcast");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace wake_on_lan
|
} // namespace wake_on_lan
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#ifdef USE_ARDUINO
|
|
||||||
|
|
||||||
#include "esphome/components/button/button.h"
|
#include "esphome/components/button/button.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
|
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||||
|
#include "esphome/components/socket/socket.h"
|
||||||
|
#else
|
||||||
#include "WiFiUdp.h"
|
#include "WiFiUdp.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace wake_on_lan {
|
namespace wake_on_lan {
|
||||||
|
@ -14,14 +16,19 @@ class WakeOnLanButton : public button::Button, public Component {
|
||||||
void set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f);
|
void set_macaddr(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t e, uint8_t f);
|
||||||
|
|
||||||
void dump_config() override;
|
void dump_config() override;
|
||||||
|
void setup() override;
|
||||||
|
float get_setup_priority() const override { return setup_priority::AFTER_WIFI; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
#if defined(USE_SOCKET_IMPL_BSD_SOCKETS) || defined(USE_SOCKET_IMPL_LWIP_SOCKETS)
|
||||||
|
std::unique_ptr<socket::Socket> broadcast_socket_{};
|
||||||
|
#else
|
||||||
WiFiUDP udp_client_{};
|
WiFiUDP udp_client_{};
|
||||||
|
#endif
|
||||||
void press_action() override;
|
void press_action() override;
|
||||||
|
uint16_t port_{9};
|
||||||
uint8_t macaddr_[6];
|
uint8_t macaddr_[6];
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace wake_on_lan
|
} // namespace wake_on_lan
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ WaveshareEPaper2P9InBV3 = waveshare_epaper_ns.class_(
|
||||||
WaveshareEPaper2P9InV2R2 = waveshare_epaper_ns.class_(
|
WaveshareEPaper2P9InV2R2 = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper2P9InV2R2", WaveshareEPaper
|
"WaveshareEPaper2P9InV2R2", WaveshareEPaper
|
||||||
)
|
)
|
||||||
GDEY029T94 = waveshare_epaper_ns.class_("GDEY029T94", WaveshareEPaper)
|
GDEW029T5 = waveshare_epaper_ns.class_("GDEW029T5", WaveshareEPaper)
|
||||||
WaveshareEPaper2P9InDKE = waveshare_epaper_ns.class_(
|
WaveshareEPaper2P9InDKE = waveshare_epaper_ns.class_(
|
||||||
"WaveshareEPaper2P9InDKE", WaveshareEPaper
|
"WaveshareEPaper2P9InDKE", WaveshareEPaper
|
||||||
)
|
)
|
||||||
|
@ -110,7 +110,7 @@ MODELS = {
|
||||||
"2.13in-ttgo-b74": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B74),
|
"2.13in-ttgo-b74": ("a", WaveshareEPaperTypeAModel.TTGO_EPAPER_2_13_IN_B74),
|
||||||
"2.90in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN),
|
"2.90in": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN),
|
||||||
"2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2),
|
"2.90inv2": ("a", WaveshareEPaperTypeAModel.WAVESHARE_EPAPER_2_9_IN_V2),
|
||||||
"gdey029t94": ("c", GDEY029T94),
|
"gdew029t5": ("c", GDEW029T5),
|
||||||
"2.70in": ("b", WaveshareEPaper2P7In),
|
"2.70in": ("b", WaveshareEPaper2P7In),
|
||||||
"2.70in-b": ("b", WaveshareEPaper2P7InB),
|
"2.70in-b": ("b", WaveshareEPaper2P7InB),
|
||||||
"2.70in-bv2": ("b", WaveshareEPaper2P7InBV2),
|
"2.70in-bv2": ("b", WaveshareEPaper2P7InBV2),
|
||||||
|
|
|
@ -1514,7 +1514,7 @@ void WaveshareEPaper2P9InV2R2::set_full_update_every(uint32_t full_update_every)
|
||||||
// - https://github.com/adafruit/Adafruit_EPD/blob/master/src/panels/ThinkInk_290_Grayscale4_T5.h
|
// - https://github.com/adafruit/Adafruit_EPD/blob/master/src/panels/ThinkInk_290_Grayscale4_T5.h
|
||||||
// ========================================================
|
// ========================================================
|
||||||
|
|
||||||
void GDEY029T94::initialize() {
|
void GDEW029T5::initialize() {
|
||||||
// from https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf, page 37
|
// from https://www.waveshare.com/w/upload/b/bb/2.9inch-e-paper-b-specification.pdf, page 37
|
||||||
// EPD hardware init start
|
// EPD hardware init start
|
||||||
this->reset_();
|
this->reset_();
|
||||||
|
@ -1560,7 +1560,7 @@ void GDEY029T94::initialize() {
|
||||||
|
|
||||||
// EPD hardware init end
|
// EPD hardware init end
|
||||||
}
|
}
|
||||||
void HOT GDEY029T94::display() {
|
void HOT GDEW029T5::display() {
|
||||||
// COMMAND DATA START TRANSMISSION 2 (B/W only)
|
// COMMAND DATA START TRANSMISSION 2 (B/W only)
|
||||||
this->command(0x13);
|
this->command(0x13);
|
||||||
delay(2);
|
delay(2);
|
||||||
|
@ -1580,11 +1580,11 @@ void HOT GDEY029T94::display() {
|
||||||
// NOTE: power off < deep sleep
|
// NOTE: power off < deep sleep
|
||||||
this->command(0x02);
|
this->command(0x02);
|
||||||
}
|
}
|
||||||
int GDEY029T94::get_width_internal() { return 128; }
|
int GDEW029T5::get_width_internal() { return 128; }
|
||||||
int GDEY029T94::get_height_internal() { return 296; }
|
int GDEW029T5::get_height_internal() { return 296; }
|
||||||
void GDEY029T94::dump_config() {
|
void GDEW029T5::dump_config() {
|
||||||
LOG_DISPLAY("", "Waveshare E-Paper (Good Display)", this);
|
LOG_DISPLAY("", "Waveshare E-Paper (Good Display)", this);
|
||||||
ESP_LOGCONFIG(TAG, " Model: 2.9in Greyscale GDEY029T94");
|
ESP_LOGCONFIG(TAG, " Model: 2.9in Greyscale GDEW029T5");
|
||||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||||
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
LOG_PIN(" DC Pin: ", this->dc_pin_);
|
||||||
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
LOG_PIN(" Busy Pin: ", this->busy_pin_);
|
||||||
|
|
|
@ -227,7 +227,7 @@ class WaveshareEPaper2P7InBV2 : public WaveshareEPaperBWR {
|
||||||
int get_height_internal() override;
|
int get_height_internal() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GDEY029T94 : public WaveshareEPaper {
|
class GDEW029T5 : public WaveshareEPaper {
|
||||||
public:
|
public:
|
||||||
void initialize() override;
|
void initialize() override;
|
||||||
|
|
||||||
|
|
|
@ -37,4 +37,4 @@ async def to_code(config):
|
||||||
cg.add_library("FS", None)
|
cg.add_library("FS", None)
|
||||||
cg.add_library("Update", None)
|
cg.add_library("Update", None)
|
||||||
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
|
# https://github.com/esphome/ESPAsyncWebServer/blob/master/library.json
|
||||||
cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.0")
|
cg.add_library("esphome/ESPAsyncWebServer-esphome", "3.2.2")
|
||||||
|
|
|
@ -375,8 +375,8 @@ void WeikaiChannel::set_baudrate_() {
|
||||||
this->parent_->page1_ = false; // switch back to page 0
|
this->parent_->page1_ = false; // switch back to page 0
|
||||||
this->reg(WKREG_SPAGE) = 0;
|
this->reg(WKREG_SPAGE) = 0;
|
||||||
|
|
||||||
ESP_LOGV(TAG, " Crystal=%d baudrate=%d => registers [%d %d %d]", this->parent_->crystal_, this->baud_rate_,
|
ESP_LOGV(TAG, " Crystal=%" PRId32 " baudrate=%" PRId32 " => registers [%d %d %d]", this->parent_->crystal_,
|
||||||
baud_high, baud_low, baud_dec);
|
this->baud_rate_, baud_high, baud_low, baud_dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool WeikaiChannel::tx_fifo_is_not_empty_() { return this->reg(WKREG_FSR) & FSR_TFDAT; }
|
inline bool WeikaiChannel::tx_fifo_is_not_empty_() { return this->reg(WKREG_FSR) & FSR_TFDAT; }
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "wl_134.h"
|
#include "wl_134.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace wl_134 {
|
namespace wl_134 {
|
||||||
|
|
||||||
|
@ -71,7 +73,7 @@ Wl134Component::Rfid134Error Wl134Component::read_packet_() {
|
||||||
ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false");
|
ESP_LOGV(TAG, "isData: %s", reading.isData ? "true" : "false");
|
||||||
ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false");
|
ESP_LOGV(TAG, "isAnimal: %s", reading.isAnimal ? "true" : "false");
|
||||||
ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0);
|
ESP_LOGV(TAG, "Reserved0: %d", reading.reserved0);
|
||||||
ESP_LOGV(TAG, "Reserved1: %d", reading.reserved1);
|
ESP_LOGV(TAG, "Reserved1: %" PRId32, reading.reserved1);
|
||||||
|
|
||||||
char buf[20];
|
char buf[20];
|
||||||
sprintf(buf, "%03d%012lld", reading.country, reading.id);
|
sprintf(buf, "%03d%012lld", reading.country, reading.id);
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/components/i2c/i2c.h"
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
#include <cinttypes>
|
||||||
|
|
||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace xgzp68xx {
|
namespace xgzp68xx {
|
||||||
|
|
||||||
|
@ -37,8 +39,8 @@ void XGZP68XXComponent::update() {
|
||||||
temperature_raw = encode_uint16(data[3], data[4]);
|
temperature_raw = encode_uint16(data[3], data[4]);
|
||||||
|
|
||||||
// Convert the pressure data to hPa
|
// Convert the pressure data to hPa
|
||||||
ESP_LOGV(TAG, "Got raw pressure=%d, raw temperature=%d ", pressure_raw, temperature_raw);
|
ESP_LOGV(TAG, "Got raw pressure=%" PRIu32 ", raw temperature=%u", pressure_raw, temperature_raw);
|
||||||
ESP_LOGV(TAG, "K value is %d ", this->k_value_);
|
ESP_LOGV(TAG, "K value is %u", this->k_value_);
|
||||||
|
|
||||||
// The most significant bit of both pressure and temperature will be 1 to indicate a negative value.
|
// The most significant bit of both pressure and temperature will be 1 to indicate a negative value.
|
||||||
// This is directly from the datasheet, and the calculations below will handle this.
|
// This is directly from the datasheet, and the calculations below will handle this.
|
||||||
|
|
|
@ -433,6 +433,10 @@ int8_t step_to_accuracy_decimals(float step) {
|
||||||
return str.length() - dot_pos - 1;
|
return str.length() - dot_pos - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"0123456789+/";
|
||||||
|
|
||||||
static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); }
|
static inline bool is_base64(char c) { return (isalnum(c) || (c == '+') || (c == '/')); }
|
||||||
|
|
||||||
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
std::string base64_encode(const std::vector<uint8_t> &buf) { return base64_encode(buf.data(), buf.size()); }
|
||||||
|
|
|
@ -435,10 +435,6 @@ std::string value_accuracy_to_string(float value, int8_t accuracy_decimals);
|
||||||
/// Derive accuracy in decimals from an increment step.
|
/// Derive accuracy in decimals from an increment step.
|
||||||
int8_t step_to_accuracy_decimals(float step);
|
int8_t step_to_accuracy_decimals(float step);
|
||||||
|
|
||||||
static const std::string BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz"
|
|
||||||
"0123456789+/";
|
|
||||||
|
|
||||||
std::string base64_encode(const uint8_t *buf, size_t buf_len);
|
std::string base64_encode(const uint8_t *buf, size_t buf_len);
|
||||||
std::string base64_encode(const std::vector<uint8_t> &buf);
|
std::string base64_encode(const std::vector<uint8_t> &buf);
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,9 @@ import logging
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DISABLED_BY_DEFAULT,
|
CONF_DISABLED_BY_DEFAULT,
|
||||||
CONF_ENTITY_CATEGORY,
|
CONF_ENTITY_CATEGORY,
|
||||||
CONF_ESPHOME,
|
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_INTERNAL,
|
CONF_INTERNAL,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_OTA,
|
|
||||||
CONF_PLATFORM,
|
|
||||||
CONF_SAFE_MODE,
|
CONF_SAFE_MODE,
|
||||||
CONF_SETUP_PRIORITY,
|
CONF_SETUP_PRIORITY,
|
||||||
CONF_TYPE_ID,
|
CONF_TYPE_ID,
|
||||||
|
@ -141,22 +138,12 @@ async def build_registry_list(registry, config):
|
||||||
|
|
||||||
|
|
||||||
async def past_safe_mode():
|
async def past_safe_mode():
|
||||||
ota_conf = {}
|
if CONF_SAFE_MODE not in CORE.config:
|
||||||
for ota_item in CORE.config.get(CONF_OTA, []):
|
|
||||||
if ota_item[CONF_PLATFORM] == CONF_ESPHOME:
|
|
||||||
ota_conf = ota_item
|
|
||||||
break
|
|
||||||
|
|
||||||
if not ota_conf:
|
|
||||||
return
|
|
||||||
|
|
||||||
safe_mode_enabled = ota_conf[CONF_SAFE_MODE]
|
|
||||||
if not safe_mode_enabled:
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _safe_mode_generator():
|
def _safe_mode_generator():
|
||||||
while True:
|
while True:
|
||||||
if CORE.data.get(CONF_OTA, {}).get(KEY_PAST_SAFE_MODE, False):
|
if CORE.data.get(CONF_SAFE_MODE, {}).get(KEY_PAST_SAFE_MODE, False):
|
||||||
return
|
return
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ lib_deps =
|
||||||
bblanchon/ArduinoJson@6.18.5 ; json
|
bblanchon/ArduinoJson@6.18.5 ; json
|
||||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||||
pavlodn/HaierProtocol@0.9.25 ; haier
|
pavlodn/HaierProtocol@0.9.28 ; haier
|
||||||
; This is using the repository until a new release is published to PlatformIO
|
; This is using the repository until a new release is published to PlatformIO
|
||||||
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
|
https://github.com/Sensirion/arduino-gas-index-algorithm.git#3.2.1 ; Sensirion Gas Index Algorithm Arduino Library
|
||||||
build_flags =
|
build_flags =
|
||||||
|
@ -58,7 +58,7 @@ lib_deps =
|
||||||
SPI ; spi (Arduino built-in)
|
SPI ; spi (Arduino built-in)
|
||||||
Wire ; i2c (Arduino built-int)
|
Wire ; i2c (Arduino built-int)
|
||||||
heman/AsyncMqttClient-esphome@1.0.0 ; mqtt
|
heman/AsyncMqttClient-esphome@1.0.0 ; mqtt
|
||||||
esphome/ESPAsyncWebServer-esphome@3.2.0 ; web_server_base
|
esphome/ESPAsyncWebServer-esphome@3.2.2 ; web_server_base
|
||||||
fastled/FastLED@3.3.2 ; fastled_base
|
fastled/FastLED@3.3.2 ; fastled_base
|
||||||
mikalhart/TinyGPSPlus@1.0.2 ; gps
|
mikalhart/TinyGPSPlus@1.0.2 ; gps
|
||||||
freekode/TM1651@1.0.1 ; tm1651
|
freekode/TM1651@1.0.1 ; tm1651
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
pylint==3.1.0
|
pylint==3.1.0
|
||||||
flake8==7.0.0 # also change in .pre-commit-config.yaml when updating
|
flake8==7.0.0 # also change in .pre-commit-config.yaml when updating
|
||||||
black==24.4.0 # also change in .pre-commit-config.yaml when updating
|
black==24.4.2 # also change in .pre-commit-config.yaml when updating
|
||||||
pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating
|
pyupgrade==3.15.2 # also change in .pre-commit-config.yaml when updating
|
||||||
pre-commit
|
pre-commit
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
pytest==8.2.0
|
pytest==8.2.0
|
||||||
pytest-cov==4.1.0
|
pytest-cov==5.0.0
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.14.0
|
||||||
pytest-asyncio==0.23.6
|
pytest-asyncio==0.23.6
|
||||||
asyncmock==0.4.2
|
asyncmock==0.4.2
|
||||||
|
|
|
@ -93,3 +93,21 @@ binary_sensor:
|
||||||
name: Haier Indoor Fan Status
|
name: Haier Indoor Fan Status
|
||||||
outdoor_fan_status:
|
outdoor_fan_status:
|
||||||
name: Haier Outdoor Fan Status
|
name: Haier Outdoor Fan Status
|
||||||
|
|
||||||
|
button:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
self_cleaning:
|
||||||
|
name: Haier start self cleaning
|
||||||
|
steri_cleaning:
|
||||||
|
name: Haier start 56°C steri-cleaning
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
appliance_name:
|
||||||
|
name: Haier appliance name
|
||||||
|
cleaning_status:
|
||||||
|
name: Haier cleaning status
|
||||||
|
protocol_version:
|
||||||
|
name: Haier protocol version
|
||||||
|
|
|
@ -93,3 +93,21 @@ binary_sensor:
|
||||||
name: Haier Indoor Fan Status
|
name: Haier Indoor Fan Status
|
||||||
outdoor_fan_status:
|
outdoor_fan_status:
|
||||||
name: Haier Outdoor Fan Status
|
name: Haier Outdoor Fan Status
|
||||||
|
|
||||||
|
button:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
self_cleaning:
|
||||||
|
name: Haier start self cleaning
|
||||||
|
steri_cleaning:
|
||||||
|
name: Haier start 56°C steri-cleaning
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
appliance_name:
|
||||||
|
name: Haier appliance name
|
||||||
|
cleaning_status:
|
||||||
|
name: Haier cleaning status
|
||||||
|
protocol_version:
|
||||||
|
name: Haier protocol version
|
||||||
|
|
|
@ -93,3 +93,21 @@ binary_sensor:
|
||||||
name: Haier Indoor Fan Status
|
name: Haier Indoor Fan Status
|
||||||
outdoor_fan_status:
|
outdoor_fan_status:
|
||||||
name: Haier Outdoor Fan Status
|
name: Haier Outdoor Fan Status
|
||||||
|
|
||||||
|
button:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
self_cleaning:
|
||||||
|
name: Haier start self cleaning
|
||||||
|
steri_cleaning:
|
||||||
|
name: Haier start 56°C steri-cleaning
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
appliance_name:
|
||||||
|
name: Haier appliance name
|
||||||
|
cleaning_status:
|
||||||
|
name: Haier cleaning status
|
||||||
|
protocol_version:
|
||||||
|
name: Haier protocol version
|
||||||
|
|
|
@ -93,3 +93,21 @@ binary_sensor:
|
||||||
name: Haier Indoor Fan Status
|
name: Haier Indoor Fan Status
|
||||||
outdoor_fan_status:
|
outdoor_fan_status:
|
||||||
name: Haier Outdoor Fan Status
|
name: Haier Outdoor Fan Status
|
||||||
|
|
||||||
|
button:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
self_cleaning:
|
||||||
|
name: Haier start self cleaning
|
||||||
|
steri_cleaning:
|
||||||
|
name: Haier start 56°C steri-cleaning
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
appliance_name:
|
||||||
|
name: Haier appliance name
|
||||||
|
cleaning_status:
|
||||||
|
name: Haier cleaning status
|
||||||
|
protocol_version:
|
||||||
|
name: Haier protocol version
|
||||||
|
|
|
@ -93,3 +93,21 @@ binary_sensor:
|
||||||
name: Haier Indoor Fan Status
|
name: Haier Indoor Fan Status
|
||||||
outdoor_fan_status:
|
outdoor_fan_status:
|
||||||
name: Haier Outdoor Fan Status
|
name: Haier Outdoor Fan Status
|
||||||
|
|
||||||
|
button:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
self_cleaning:
|
||||||
|
name: Haier start self cleaning
|
||||||
|
steri_cleaning:
|
||||||
|
name: Haier start 56°C steri-cleaning
|
||||||
|
|
||||||
|
text_sensor:
|
||||||
|
- platform: haier
|
||||||
|
haier_id: haier_ac
|
||||||
|
appliance_name:
|
||||||
|
name: Haier appliance name
|
||||||
|
cleaning_status:
|
||||||
|
name: Haier cleaning status
|
||||||
|
protocol_version:
|
||||||
|
name: Haier protocol version
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue