Merge pull request #6284 from esphome/bump-2024.2.1

2024.2.1
This commit is contained in:
Jesse Hills 2024-02-26 09:04:36 +13:00 committed by GitHub
commit cc115e7cc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 109 additions and 52 deletions

View file

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

View file

@ -252,7 +252,9 @@ ThrottleAverageFilter::ThrottleAverageFilter(uint32_t time_period) : time_period
optional<float> ThrottleAverageFilter::new_value(float value) { optional<float> ThrottleAverageFilter::new_value(float value) {
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::new_value(value=%f)", this, 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->sum_ += value;
this->n_++; this->n_++;
} }
@ -262,12 +264,14 @@ void ThrottleAverageFilter::setup() {
this->set_interval("throttle_average", this->time_period_, [this]() { this->set_interval("throttle_average", this->time_period_, [this]() {
ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_); ESP_LOGVV(TAG, "ThrottleAverageFilter(%p)::interval(sum=%f, n=%i)", this, this->sum_, this->n_);
if (this->n_ == 0) { if (this->n_ == 0) {
if (this->have_nan_)
this->output(NAN); this->output(NAN);
} else { } else {
this->output(this->sum_ / this->n_); this->output(this->sum_ / this->n_);
this->sum_ = 0.0f; this->sum_ = 0.0f;
this->n_ = 0; this->n_ = 0;
} }
this->have_nan_ = false;
}); });
} }
float ThrottleAverageFilter::get_setup_priority() const { return setup_priority::HARDWARE; } 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_; uint32_t time_period_;
float sum_{0.0f}; float sum_{0.0f};
unsigned int n_{0}; unsigned int n_{0};
bool have_nan_{false};
}; };
using lambda_filter_t = std::function<optional<float>(float)>; 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( CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
{ {
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan), 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_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput), cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_SPEED): cv.invalid( cv.Optional(CONF_SPEED): cv.invalid(
@ -32,11 +32,14 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
async def to_code(config): async def to_code(config):
output_ = await cg.get_variable(config[CONF_OUTPUT]) var = cg.new_Pvariable(config[CONF_OUTPUT_ID], config[CONF_SPEED_COUNT])
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT])
await cg.register_component(var, config) await cg.register_component(var, config)
await fan.register_fan(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: if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
cg.add(var.set_oscillating(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_() { void SpeedFan::write_state_() {
if (this->output_ != nullptr) {
float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f; float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f;
this->output_->set_level(speed); this->output_->set_level(speed);
}
if (this->oscillating_ != nullptr) if (this->oscillating_ != nullptr)
this->oscillating_->set_state(this->oscillating); this->oscillating_->set_state(this->oscillating);
if (this->direction_ != nullptr) if (this->direction_ != nullptr)

View file

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

View file

@ -74,7 +74,8 @@ CONF_FORCE_SW = "force_sw"
CONF_INTERFACE = "interface" CONF_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index" 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 = [ RP_SPI_PINSETS = [
{ {
@ -85,7 +86,7 @@ RP_SPI_PINSETS = [
{ {
CONF_MISO_PIN: [8, 12, 24, 28, -1], CONF_MISO_PIN: [8, 12, 24, 28, -1],
CONF_CLK_PIN: [10, 14, 26], 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 "thermostat_climate.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes>
namespace esphome { namespace esphome {
namespace thermostat { namespace thermostat {
@ -63,6 +62,15 @@ void ThermostatClimate::setup() {
this->publish_state(); 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_deadband() { return this->cooling_deadband_; }
float ThermostatClimate::cool_overrun() { return this->cooling_overrun_; } float ThermostatClimate::cool_overrun() { return this->cooling_overrun_; }
float ThermostatClimate::heat_deadband() { return this->heating_deadband_; } 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); this->start_timer_(thermostat::TIMER_FANNING_ON);
trig_fan = this->fan_only_action_trigger_; trig_fan = this->fan_only_action_trigger_;
} }
this->cooling_max_runtime_exceeded_ = false;
trig = this->cool_action_trigger_; trig = this->cool_action_trigger_;
ESP_LOGVV(TAG, "Switching to COOLING action"); ESP_LOGVV(TAG, "Switching to COOLING action");
action_ready = true; action_ready = true;
@ -452,6 +461,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
this->start_timer_(thermostat::TIMER_FANNING_ON); this->start_timer_(thermostat::TIMER_FANNING_ON);
trig_fan = this->fan_only_action_trigger_; trig_fan = this->fan_only_action_trigger_;
} }
this->heating_max_runtime_exceeded_ = false;
trig = this->heat_action_trigger_; trig = this->heat_action_trigger_;
ESP_LOGVV(TAG, "Switching to HEATING action"); ESP_LOGVV(TAG, "Switching to HEATING action");
action_ready = true; action_ready = true;
@ -752,15 +762,15 @@ bool ThermostatClimate::heating_action_ready_() {
void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) { void ThermostatClimate::start_timer_(const ThermostatClimateTimerIndex timer_index) {
if (this->timer_duration_(timer_index) > 0) { if (this->timer_duration_(timer_index) > 0) {
this->set_timeout(this->timer_[timer_index].name, this->timer_duration_(timer_index), this->timer_[timer_index].started = millis();
this->timer_cbf_(timer_index));
this->timer_[timer_index].active = true; this->timer_[timer_index].active = true;
} }
} }
bool ThermostatClimate::cancel_timer_(ThermostatClimateTimerIndex timer_index) { bool ThermostatClimate::cancel_timer_(ThermostatClimateTimerIndex timer_index) {
auto ret = this->timer_[timer_index].active;
this->timer_[timer_index].active = false; this->timer_[timer_index].active = false;
return this->cancel_timeout(this->timer_[timer_index].name); return ret;
} }
bool ThermostatClimate::timer_active_(ThermostatClimateTimerIndex timer_index) { 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_() { void ThermostatClimate::cooling_max_run_time_timer_callback_() {
ESP_LOGVV(TAG, "cooling_max_run_time timer expired"); 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->cooling_max_runtime_exceeded_ = true;
this->trigger_supplemental_action_(); this->trigger_supplemental_action_();
this->switch_to_supplemental_action_(this->compute_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_() { void ThermostatClimate::cooling_off_timer_callback_() {
ESP_LOGVV(TAG, "cooling_off timer expired"); 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_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_());
} }
void ThermostatClimate::cooling_on_timer_callback_() { void ThermostatClimate::cooling_on_timer_callback_() {
ESP_LOGVV(TAG, "cooling_on timer expired"); 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_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_());
} }
void ThermostatClimate::fan_mode_timer_callback_() { void ThermostatClimate::fan_mode_timer_callback_() {
ESP_LOGVV(TAG, "fan_mode timer expired"); 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)); this->switch_to_fan_mode_(this->fan_mode.value_or(climate::CLIMATE_FAN_ON));
if (this->supports_fan_only_action_uses_fan_mode_timer_) if (this->supports_fan_only_action_uses_fan_mode_timer_)
this->switch_to_action_(this->compute_action_()); this->switch_to_action_(this->compute_action_());
@ -807,19 +813,16 @@ void ThermostatClimate::fan_mode_timer_callback_() {
void ThermostatClimate::fanning_off_timer_callback_() { void ThermostatClimate::fanning_off_timer_callback_() {
ESP_LOGVV(TAG, "fanning_off timer expired"); ESP_LOGVV(TAG, "fanning_off timer expired");
this->timer_[thermostat::TIMER_FANNING_OFF].active = false;
this->switch_to_action_(this->compute_action_()); this->switch_to_action_(this->compute_action_());
} }
void ThermostatClimate::fanning_on_timer_callback_() { void ThermostatClimate::fanning_on_timer_callback_() {
ESP_LOGVV(TAG, "fanning_on timer expired"); ESP_LOGVV(TAG, "fanning_on timer expired");
this->timer_[thermostat::TIMER_FANNING_ON].active = false;
this->switch_to_action_(this->compute_action_()); this->switch_to_action_(this->compute_action_());
} }
void ThermostatClimate::heating_max_run_time_timer_callback_() { void ThermostatClimate::heating_max_run_time_timer_callback_() {
ESP_LOGVV(TAG, "heating_max_run_time timer expired"); 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->heating_max_runtime_exceeded_ = true;
this->trigger_supplemental_action_(); this->trigger_supplemental_action_();
this->switch_to_supplemental_action_(this->compute_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_() { void ThermostatClimate::heating_off_timer_callback_() {
ESP_LOGVV(TAG, "heating_off timer expired"); 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_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_());
} }
void ThermostatClimate::heating_on_timer_callback_() { void ThermostatClimate::heating_on_timer_callback_() {
ESP_LOGVV(TAG, "heating_on timer expired"); 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_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_());
} }
void ThermostatClimate::idle_on_timer_callback_() { void ThermostatClimate::idle_on_timer_callback_() {
ESP_LOGVV(TAG, "idle_on timer expired"); 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_action_(this->compute_action_());
this->switch_to_supplemental_action_(this->compute_supplemental_action_()); this->switch_to_supplemental_action_(this->compute_supplemental_action_());
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -10,12 +10,14 @@ from esphome import const, util
from esphome.storage_json import StorageJSON, ext_storage_path from esphome.storage_json import StorageJSON, ext_storage_path
from .const import ( from .const import (
DASHBOARD_COMMAND,
EVENT_ENTRY_ADDED, EVENT_ENTRY_ADDED,
EVENT_ENTRY_REMOVED, EVENT_ENTRY_REMOVED,
EVENT_ENTRY_STATE_CHANGED, EVENT_ENTRY_STATE_CHANGED,
EVENT_ENTRY_UPDATED, EVENT_ENTRY_UPDATED,
) )
from .enum import StrEnum from .enum import StrEnum
from .util.subprocess import async_run_system_command
if TYPE_CHECKING: if TYPE_CHECKING:
from .core import ESPHomeDashboard from .core import ESPHomeDashboard
@ -235,6 +237,14 @@ class DashboardEntries:
) )
return path_to_cache_key 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: class DashboardEntry:
"""Represents a single dashboard entry. """Represents a single dashboard entry.

View file

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

View file

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