1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-17 15:26:01 +00:00

Compare commits

..

21 Commits

Author SHA1 Message Date
Jesse Hills
cc115e7cc9 Merge pull request #6284 from esphome/bump-2024.2.1
2024.2.1
2024-02-26 09:04:36 +13:00
Jesse Hills
badac933ae Bump version to 2024.2.1 2024-02-26 07:52:25 +13:00
Keith Burzinski
b1b8217713 Fix thermostat supplemental actions (#6282) 2024-02-26 07:52:25 +13:00
Samuel Sieb
f3174c58bc fix throttle average nan handling (#6275)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-02-26 07:52:25 +13:00
Samuel Sieb
e66e135a63 make output optional for speed fan (#6274)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-02-26 07:52:25 +13:00
Jesse Hills
d814ed1d4a Merge pull request from GHSA-8p25-3q46-8q2p 2024-02-26 07:52:08 +13:00
J. Nick Koston
84c6e52be2 dashboard: move storage json update to a background task in edit save (#6280)
* dashboard: move storage json update to a background task in edit save

* dashboard: move storage json update to a background task in edit save

* fix typing

* docs
2024-02-26 07:50:28 +13:00
Keith Burzinski
2cf6393161 Fix RP2040 SPI pin validation (#6277) 2024-02-26 07:47:24 +13:00
Samuel Sieb
5a7759f1c4 allow multiple emc2101 (#6272) 2024-02-26 07:47:24 +13:00
Daniel Baulig
db5205931b web_server: Add a position property for cover entities that have the supports position trait (#6269) 2024-02-26 07:47:24 +13:00
Jesse Hills
62d59cffcc Bump zeroconf timeout to 3000 (#6270) 2024-02-26 07:47:24 +13:00
Jesse Hills
2e7129e816 Add missing timeout to "async_request" (#6267) 2024-02-26 07:47:24 +13:00
Jesse Hills
2d22a2d1c2 Merge pull request #6252 from esphome/bump-2024.2.0
2024.2.0
2024-02-21 13:48:58 +13:00
Jesse Hills
c92968da8a Bump version to 2024.2.0 2024-02-21 12:51:13 +13:00
Jesse Hills
86580d07cb Merge pull request #6249 from esphome/bump-2024.2.0b3
2024.2.0b3
2024-02-21 11:38:08 +13:00
Jesse Hills
03ea71034f Bump version to 2024.2.0b3 2024-02-21 10:57:43 +13:00
sibowler
7bf676abfa Tuya Fan component fix to handle enum datapoint type (#6135) 2024-02-21 10:57:43 +13:00
Michael Hansen
fb16e6b027 Voice Assistant: add on_idle trigger and fix nevermind (#6141) 2024-02-21 10:57:43 +13:00
SmartShackMaster
4eb04afa62 Clear UART read buffer before sending next command (#6200) 2024-02-21 10:57:43 +13:00
Keith Burzinski
841a831c63 Fix tm1651 enum (#6248) 2024-02-21 10:57:43 +13:00
Samuel Sieb
ae4af2966a hold interrupt disable for dallas one-wire (#6244)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
2024-02-21 10:57:43 +13:00
26 changed files with 155 additions and 67 deletions

View File

@@ -168,10 +168,6 @@ bool IRAM_ATTR DallasTemperatureSensor::read_scratch_pad() {
if (!wire->reset()) {
return false;
}
}
{
InterruptLock lock;
wire->select(this->address_);
wire->write8(DALLAS_COMMAND_READ_SCRATCH_PAD);

View File

@@ -7,6 +7,8 @@ CODEOWNERS = ["@ellull"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
CONF_PWM = "pwm"
CONF_DIVIDER = "divider"
CONF_DAC = "dac"

View File

@@ -336,6 +336,8 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui
}
uint8_t FingerprintGrowComponent::send_command_() {
while (this->available())
this->read();
this->write((uint8_t) (START_CODE >> 8));
this->write((uint8_t) (START_CODE & 0xFF));
this->write(this->address_[0]);

View File

@@ -252,7 +252,9 @@ ThrottleAverageFilter::ThrottleAverageFilter(uint32_t time_period) : time_period
optional<float> ThrottleAverageFilter::new_value(float value) {
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::new_value(value=%f)", this, value);
if (!std::isnan(value)) {
if (std::isnan(value)) {
this->have_nan_ = true;
} else {
this->sum_ += value;
this->n_++;
}
@@ -262,12 +264,14 @@ void ThrottleAverageFilter::setup() {
this->set_interval("throttle_average", this->time_period_, [this]() {
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_);
if (this->n_ == 0) {
this->output(NAN);
if (this->have_nan_)
this->output(NAN);
} else {
this->output(this->sum_ / this->n_);
this->sum_ = 0.0f;
this->n_ = 0;
}
this->have_nan_ = false;
});
}
float ThrottleAverageFilter::get_setup_priority() const { return setup_priority::HARDWARE; }

View File

@@ -245,6 +245,7 @@ class ThrottleAverageFilter : public Filter, public Component {
uint32_t time_period_;
float sum_{0.0f};
unsigned int n_{0};
bool have_nan_{false};
};
using lambda_filter_t = std::function<optional<float>(float)>;

View File

@@ -19,7 +19,7 @@ SpeedFan = speed_ns.class_("SpeedFan", cg.Component, fan.Fan)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
{
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan),
cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput),
cv.Optional(CONF_OUTPUT): cv.use_id(output.FloatOutput),
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_SPEED): cv.invalid(
@@ -32,11 +32,14 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
async def to_code(config):
output_ = await cg.get_variable(config[CONF_OUTPUT])
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT])
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_SPEED_COUNT])
await cg.register_component(var, config)
await fan.register_fan(var, config)
if CONF_OUTPUT in config:
output_ = await cg.get_variable(config[CONF_OUTPUT])
cg.add(var.set_output(output_))
if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
cg.add(var.set_oscillating(oscillation_output))

View File

@@ -36,9 +36,10 @@ void SpeedFan::control(const fan::FanCall &call) {
}
void SpeedFan::write_state_() {
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
this->output_->set_level(speed);
if (this->output_ != nullptr) {
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
this->output_->set_level(speed);
}
if (this->oscillating_ != nullptr)
this->oscillating_->set_state(this->oscillating);
if (this->direction_ != nullptr)

View File

@@ -12,9 +12,10 @@ namespace speed {
class SpeedFan : public Component, public fan::Fan {
public:
SpeedFan(output::FloatOutput *output, int speed_count) : output_(output), speed_count_(speed_count) {}
SpeedFan(int speed_count) : speed_count_(speed_count) {}
void setup() override;
void dump_config() override;
void set_output(output::FloatOutput *output) { this->output_ = output; }
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
void set_preset_modes(const std::set<std::string> &presets) { this->preset_modes_ = presets; }
@@ -24,7 +25,7 @@ class SpeedFan : public Component, public fan::Fan {
void control(const fan::FanCall &call) override;
void write_state_();
output::FloatOutput *output_;
output::FloatOutput *output_{nullptr};
output::BinaryOutput *oscillating_{nullptr};
output::BinaryOutput *direction_{nullptr};
int speed_count_{};

View File

@@ -74,7 +74,8 @@ CONF_FORCE_SW = "force_sw"
CONF_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index"
# RP2040 SPI pin assignments are complicated. Refer to https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
# RP2040 SPI pin assignments are complicated;
# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
RP_SPI_PINSETS = [
{
@@ -85,7 +86,7 @@ RP_SPI_PINSETS = [
{
CONF_MISO_PIN: [8, 12, 24, 28, -1],
CONF_CLK_PIN: [10, 14, 26],
CONF_MOSI_PIN: [11, 23, 27, -1],
CONF_MOSI_PIN: [11, 15, 27, -1],
},
]

View File

@@ -1,6 +1,5 @@
#include "thermostat_climate.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace thermostat {
@@ -63,6 +62,15 @@ void ThermostatClimate::setup() {
this->publish_state();
}
void ThermostatClimate::loop() {
for (auto &timer : this->timer_) {
if (timer.active && (timer.started + timer.time < millis())) {
timer.active = false;
timer.func();
}
}
}
float ThermostatClimate::cool_deadband() { return this->cooling_deadband_; }
float ThermostatClimate::cool_overrun() { return this->cooling_overrun_; }
float ThermostatClimate::heat_deadband() { return this->heating_deadband_; }
@@ -439,6 +447,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
this->start_timer_(thermostat::TIMER_FANNING_ON);
trig_fan = this->fan_only_action_trigger_;
}
this->cooling_max_runtime_exceeded_ = false;
trig = this->cool_action_trigger_;
ESP_LOGVV(TAG, "Switching to COOLING action");
action_ready = true;
@@ -452,6 +461,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
this->start_timer_(thermostat::TIMER_FANNING_ON);
trig_fan = this->fan_only_action_trigger_;
}
this->heating_max_runtime_exceeded_ = false;
trig = this->heat_action_trigger_;
ESP_LOGVV(TAG, "Switching to HEATING action");
action_ready = true;
@@ -752,15 +762,15 @@ bool ThermostatClimate::heating_action_ready_() {
void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) {
if (this->timer_duration_(timer_index) > 0) {
this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index),
this->timer_cbf_(timer_index));
this->timer_[timer_index].started = millis();
this->timer_[timer_index].active = true;
}
}
bool ThermostatClimate::cancel_timer_(ThermostatClimateTimerIndex timer_index) {
auto ret = this->timer_[timer_index].active;
this->timer_[timer_index].active = false;
return this->cancel_timeout(this->timer_[timer_index].name);
return ret;
}
bool ThermostatClimate::timer_active_(ThermostatClimateTimerIndex timer_index) {
@@ -777,7 +787,6 @@ std::function<void()> ThermostatClimate::timer_cbf_(ThermostatClimateTimerIndex
void ThermostatClimate::cooling_max_run_time_timer_callback_() {
ESP_LOGVV(TAG, "cooling_max_run_time timer expired");
this->timer_[thermostat::TIMER_COOLING_MAX_RUN_TIME].active = false;
this->cooling_max_runtime_exceeded_ = true;
this->trigger_supplemental_action_();
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
@@ -785,21 +794,18 @@ void ThermostatClimate::cooling_max_run_time_timer_callback_() {
void ThermostatClimate::cooling_off_timer_callback_() {
ESP_LOGVV(TAG, "cooling_off timer expired");
this->timer_[thermostat::TIMER_COOLING_OFF].active = false;
this->switch_to_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
}
void ThermostatClimate::cooling_on_timer_callback_() {
ESP_LOGVV(TAG, "cooling_on timer expired");
this->timer_[thermostat::TIMER_COOLING_ON].active = false;
this->switch_to_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
}
void ThermostatClimate::fan_mode_timer_callback_() {
ESP_LOGVV(TAG, "fan_mode timer expired");
this->timer_[thermostat::TIMER_FAN_MODE].active = false;
this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON));
if (this->supports_fan_only_action_uses_fan_mode_timer_)
this->switch_to_action_(this->compute_action_());
@@ -807,19 +813,16 @@ void ThermostatClimate::fan_mode_timer_callback_() {
void ThermostatClimate::fanning_off_timer_callback_() {
ESP_LOGVV(TAG, "fanning_off timer expired");
this->timer_[thermostat::TIMER_FANNING_OFF].active = false;
this->switch_to_action_(this->compute_action_());
}
void ThermostatClimate::fanning_on_timer_callback_() {
ESP_LOGVV(TAG, "fanning_on timer expired");
this->timer_[thermostat::TIMER_FANNING_ON].active = false;
this->switch_to_action_(this->compute_action_());
}
void ThermostatClimate::heating_max_run_time_timer_callback_() {
ESP_LOGVV(TAG, "heating_max_run_time timer expired");
this->timer_[thermostat::TIMER_HEATING_MAX_RUN_TIME].active = false;
this->heating_max_runtime_exceeded_ = true;
this->trigger_supplemental_action_();
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
@@ -827,21 +830,18 @@ void ThermostatClimate::heating_max_run_time_timer_callback_() {
void ThermostatClimate::heating_off_timer_callback_() {
ESP_LOGVV(TAG, "heating_off timer expired");
this->timer_[thermostat::TIMER_HEATING_OFF].active = false;
this->switch_to_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
}
void ThermostatClimate::heating_on_timer_callback_() {
ESP_LOGVV(TAG, "heating_on timer expired");
this->timer_[thermostat::TIMER_HEATING_ON].active = false;
this->switch_to_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
}
void ThermostatClimate::idle_on_timer_callback_() {
ESP_LOGVV(TAG, "idle_on timer expired");
this->timer_[thermostat::TIMER_IDLE_ON].active = false;
this->switch_to_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_());
}

View File

@@ -1,10 +1,12 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/climate/climate.h"
#include "esphome/components/sensor/sensor.h"
#include <cinttypes>
#include <map>
#include <vector>
@@ -26,9 +28,9 @@ enum ThermostatClimateTimerIndex : size_t {
enum OnBootRestoreFrom : size_t { MEMORY = 0, DEFAULT_PRESET = 1 };
struct ThermostatClimateTimer {
const std::string name;
bool active;
uint32_t time;
uint32_t started;
std::function<void()> func;
};
@@ -59,6 +61,7 @@ class ThermostatClimate : public climate::Climate, public Component {
ThermostatClimate();
void setup() override;
void dump_config() override;
void loop() override;
void set_default_preset(const std::string &custom_preset);
void set_default_preset(climate::ClimatePreset preset);
@@ -441,16 +444,17 @@ class ThermostatClimate : public climate::Climate, public Component {
/// Climate action timers
std::vector<ThermostatClimateTimer> timer_{
{"cool_run", false, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)},
{"cool_off", false, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)},
{"cool_on", false, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)},
{"fan_mode", false, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)},
{"fan_off", false, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)},
{"fan_on", false, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)},
{"heat_run", false, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)},
{"heat_off", false, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)},
{"heat_on", false, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)},
{"idle_on", false, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)}};
{false, 0, 0, std::bind(&ThermostatClimate::cooling_max_run_time_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::cooling_off_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::cooling_on_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::fan_mode_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::fanning_off_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::fanning_on_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::heating_max_run_time_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::heating_off_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::heating_on_timer_callback_, this)},
{false, 0, 0, std::bind(&ThermostatClimate::idle_on_timer_callback_, this)},
};
/// The set of standard preset configurations this thermostat supports (Eg. AWAY, ECO, etc)
std::map<climate::ClimatePreset, ThermostatClimateTargetTempConfig> preset_config_{};

View File

@@ -13,6 +13,7 @@ from esphome.const import (
CODEOWNERS = ["@freekode"]
tm1651_ns = cg.esphome_ns.namespace("tm1651")
TM1651Brightness = tm1651_ns.enum("TM1651Brightness")
TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component)
SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
@@ -24,9 +25,9 @@ TurnOffAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
CONF_LEVEL_PERCENT = "level_percent"
TM1651_BRIGHTNESS_OPTIONS = {
1: TM1651Display.TM1651_BRIGHTNESS_LOW,
2: TM1651Display.TM1651_BRIGHTNESS_MEDIUM,
3: TM1651Display.TM1651_BRIGHTNESS_HIGH,
1: TM1651Brightness.TM1651_BRIGHTNESS_LOW,
2: TM1651Brightness.TM1651_BRIGHTNESS_MEDIUM,
3: TM1651Brightness.TM1651_BRIGHTNESS_HIGH,
}
CONFIG_SCHEMA = cv.All(

View File

@@ -12,9 +12,9 @@ static const char *const TAG = "tm1651.display";
static const uint8_t MAX_INPUT_LEVEL_PERCENT = 100;
static const uint8_t TM1651_MAX_LEVEL = 7;
static const uint8_t TM1651_BRIGHTNESS_LOW = 0;
static const uint8_t TM1651_BRIGHTNESS_MEDIUM = 2;
static const uint8_t TM1651_BRIGHTNESS_HIGH = 7;
static const uint8_t TM1651_BRIGHTNESS_LOW_HW = 0;
static const uint8_t TM1651_BRIGHTNESS_MEDIUM_HW = 2;
static const uint8_t TM1651_BRIGHTNESS_HIGH_HW = 7;
void TM1651Display::setup() {
ESP_LOGCONFIG(TAG, "Setting up TM1651...");
@@ -78,14 +78,14 @@ uint8_t TM1651Display::calculate_level_(uint8_t new_level) {
uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) {
if (new_brightness <= 1) {
return TM1651_BRIGHTNESS_LOW;
return TM1651_BRIGHTNESS_LOW_HW;
} else if (new_brightness == 2) {
return TM1651_BRIGHTNESS_MEDIUM;
return TM1651_BRIGHTNESS_MEDIUM_HW;
} else if (new_brightness >= 3) {
return TM1651_BRIGHTNESS_HIGH;
return TM1651_BRIGHTNESS_HIGH_HW;
}
return TM1651_BRIGHTNESS_LOW;
return TM1651_BRIGHTNESS_LOW_HW;
}
} // namespace tm1651

View File

@@ -13,6 +13,12 @@
namespace esphome {
namespace tm1651 {
enum TM1651Brightness : uint8_t {
TM1651_BRIGHTNESS_LOW = 1,
TM1651_BRIGHTNESS_MEDIUM = 2,
TM1651_BRIGHTNESS_HIGH = 3,
};
class TM1651Display : public Component {
public:
void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; }
@@ -24,6 +30,7 @@ class TM1651Display : public Component {
void set_level_percent(uint8_t new_level);
void set_level(uint8_t new_level);
void set_brightness(uint8_t new_brightness);
void set_brightness(TM1651Brightness new_brightness) { this->set_brightness(static_cast<uint8_t>(new_brightness)); }
void turn_on();
void turn_off();

View File

@@ -34,9 +34,13 @@ void TuyaFan::setup() {
}
if (this->oscillation_id_.has_value()) {
this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) {
// Whether data type is BOOL or ENUM, it will still be a 1 or a 0, so the functions below are valid in both
// scenarios
ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool));
this->oscillating = datapoint.value_bool;
this->publish_state();
this->oscillation_type_ = datapoint.type;
});
}
if (this->direction_id_.has_value()) {
@@ -80,7 +84,11 @@ void TuyaFan::control(const fan::FanCall &call) {
this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state());
}
if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) {
this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
if (this->oscillation_type_ == TuyaDatapointType::ENUM) {
this->parent_->set_enum_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
} else if (this->speed_type_ == TuyaDatapointType::BOOLEAN) {
this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
}
}
if (this->direction_id_.has_value() && call.get_direction().has_value()) {
bool enable = *call.get_direction() == fan::FanDirection::REVERSE;

View File

@@ -29,6 +29,7 @@ class TuyaFan : public Component, public fan::Fan {
optional<uint8_t> direction_id_{};
int speed_count_{};
TuyaDatapointType speed_type_{};
TuyaDatapointType oscillation_type_{};
};
} // namespace tuya

View File

@@ -32,6 +32,7 @@ CONF_ON_TTS_START = "on_tts_start"
CONF_ON_TTS_STREAM_START = "on_tts_stream_start"
CONF_ON_TTS_STREAM_END = "on_tts_stream_end"
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
CONF_ON_IDLE = "on_idle"
CONF_SILENCE_DETECTION = "silence_detection"
CONF_USE_WAKE_WORD = "use_wake_word"
@@ -127,6 +128,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ON_TTS_STREAM_END): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True),
}
).extend(cv.COMPONENT_SCHEMA),
tts_stream_validate,
@@ -259,6 +261,13 @@ async def to_code(config):
config[CONF_ON_TTS_STREAM_END],
)
if CONF_ON_IDLE in config:
await automation.build_automation(
var.get_idle_trigger(),
[],
config[CONF_ON_IDLE],
)
cg.add_define("USE_VOICE_ASSISTANT")

View File

@@ -135,6 +135,8 @@ void VoiceAssistant::loop() {
switch (this->state_) {
case State::IDLE: {
if (this->continuous_ && this->desired_state_ == State::IDLE) {
this->idle_trigger_->trigger();
this->ring_buffer_->reset();
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
@@ -618,6 +620,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
{
this->set_state_(State::IDLE, State::IDLE);
}
} else if (this->state_ == State::AWAITING_RESPONSE) {
// No TTS start event ("nevermind")
this->set_state_(State::IDLE, State::IDLE);
}
this->defer([this]() { this->end_trigger_->trigger(); });
break;

View File

@@ -116,6 +116,7 @@ class VoiceAssistant : public Component {
Trigger<std::string> *get_tts_end_trigger() const { return this->tts_end_trigger_; }
Trigger<std::string> *get_tts_start_trigger() const { return this->tts_start_trigger_; }
Trigger<std::string, std::string> *get_error_trigger() const { return this->error_trigger_; }
Trigger<> *get_idle_trigger() const { return this->idle_trigger_; }
Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; }
@@ -148,6 +149,7 @@ class VoiceAssistant : public Component {
Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>();
Trigger<std::string> *tts_start_trigger_ = new Trigger<std::string>();
Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
Trigger<> *idle_trigger_ = new Trigger<>();
Trigger<> *client_connected_trigger_ = new Trigger<>();
Trigger<> *client_disconnected_trigger_ = new Trigger<>();

View File

@@ -785,6 +785,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
obj->position, start_config);
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
if (obj->get_traits().get_supports_position())
root["position"] = obj->position;
if (obj->get_traits().get_supports_tilt())
root["tilt"] = obj->tilt;
});

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2024.2.0b2"
__version__ = "2024.2.1"
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
VALID_SUBSTITUTIONS_CHARACTERS = (

View File

@@ -8,3 +8,5 @@ MAX_EXECUTOR_WORKERS = 48
SENTINEL = object()
DASHBOARD_COMMAND = ["esphome", "--dashboard"]

View File

@@ -1,11 +1,13 @@
from __future__ import annotations
import asyncio
import contextlib
import logging
import threading
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any, Callable
from collections.abc import Coroutine
from ..zeroconf import DiscoveredImport
from .dns import DNSCache
@@ -71,6 +73,7 @@ class ESPHomeDashboard:
"mdns_status",
"settings",
"dns_cache",
"_background_tasks",
)
def __init__(self) -> None:
@@ -85,6 +88,7 @@ class ESPHomeDashboard:
self.mdns_status: MDNSStatus | None = None
self.settings = DashboardSettings()
self.dns_cache = DNSCache()
self._background_tasks: set[asyncio.Task] = set()
async def async_setup(self) -> None:
"""Setup the dashboard."""
@@ -132,7 +136,19 @@ class ESPHomeDashboard:
if settings.status_use_mqtt:
status_thread_mqtt.join()
self.mqtt_ping_request.set()
for task in self._background_tasks:
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
await asyncio.sleep(0)
def async_create_background_task(
self, coro: Coroutine[Any, Any, Any]
) -> asyncio.Task:
"""Create a background task."""
task = self.loop.create_task(coro)
task.add_done_callback(self._background_tasks.discard)
return task
DASHBOARD = ESPHomeDashboard()

View File

@@ -10,12 +10,14 @@ from esphome import const, util
from esphome.storage_json import StorageJSON, ext_storage_path
from .const import (
DASHBOARD_COMMAND,
EVENT_ENTRY_ADDED,
EVENT_ENTRY_REMOVED,
EVENT_ENTRY_STATE_CHANGED,
EVENT_ENTRY_UPDATED,
)
from .enum import StrEnum
from .util.subprocess import async_run_system_command
if TYPE_CHECKING:
from .core import ESPHomeDashboard
@@ -235,6 +237,14 @@ class DashboardEntries:
)
return path_to_cache_key
def async_schedule_storage_json_update(self, filename: str) -> None:
"""Schedule a task to update the storage JSON file."""
self._dashboard.async_create_background_task(
async_run_system_command(
[*DASHBOARD_COMMAND, "compile", "--only-generate", filename]
)
)
class DashboardEntry:
"""Represents a single dashboard entry.

View File

@@ -9,11 +9,11 @@ import hashlib
import json
import logging
import os
import time
import secrets
import shutil
import subprocess
import threading
import time
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, TypeVar
@@ -40,6 +40,7 @@ from esphome.storage_json import StorageJSON, ext_storage_path, trash_storage_pa
from esphome.util import get_serial_ports, shlex_quote
from esphome.yaml_util import FastestAvailableSafeLoader
from .const import DASHBOARD_COMMAND
from .core import DASHBOARD
from .entries import EntryState, entry_state_to_bool
from .util.file import write_file
@@ -286,9 +287,6 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
raise NotImplementedError
DASHBOARD_COMMAND = ["esphome", "--dashboard"]
class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
"""Base class for commands that require a port."""
@@ -808,8 +806,16 @@ class EditRequestHandler(BaseHandler):
@bind_config
async def get(self, configuration: str | None = None) -> None:
"""Get the content of a file."""
loop = asyncio.get_running_loop()
if not configuration.endswith((".yaml", ".yml")):
self.send_error(404)
return
filename = settings.rel_path(configuration)
if Path(filename).resolve().parent != settings.absolute_config_dir:
self.send_error(404)
return
loop = asyncio.get_running_loop()
content = await loop.run_in_executor(
None, self._read_file, filename, configuration
)
@@ -835,15 +841,19 @@ class EditRequestHandler(BaseHandler):
@bind_config
async def post(self, configuration: str | None = None) -> None:
"""Write the content of a file."""
if not configuration.endswith((".yaml", ".yml")):
self.send_error(404)
return
filename = settings.rel_path(configuration)
if Path(filename).resolve().parent != settings.absolute_config_dir:
self.send_error(404)
return
loop = asyncio.get_running_loop()
config_file = settings.rel_path(configuration)
await loop.run_in_executor(
None, self._write_file, config_file, self.request.body
)
await loop.run_in_executor(None, self._write_file, filename, self.request.body)
# Ensure the StorageJSON is updated as well
await async_run_system_command(
[*DASHBOARD_COMMAND, "compile", "--only-generate", config_file]
)
DASHBOARD.entries.async_schedule_storage_json_update(filename)
self.set_status(200)

View File

@@ -110,7 +110,7 @@ class DashboardImportDiscovery:
self, zeroconf: Zeroconf, info: AsyncServiceInfo, service_type: str, name: str
) -> None:
"""Process a service info."""
if await info.async_request(zeroconf):
if await info.async_request(zeroconf, timeout=3000):
self._process_service_info(name, info)
def _process_service_info(self, name: str, info: ServiceInfo) -> None: