mirror of
https://github.com/esphome/esphome.git
synced 2025-11-06 10:01:51 +00:00
Compare commits
29 Commits
2024.2.0b2
...
2024.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
833affc1bf | ||
|
|
e2b197dc2c | ||
|
|
f39dc49f49 | ||
|
|
b0a25401f7 | ||
|
|
37d2b3c797 | ||
|
|
63cce916e2 | ||
|
|
1aab87b41c | ||
|
|
a3fc1acdcb | ||
|
|
cc115e7cc9 | ||
|
|
badac933ae | ||
|
|
b1b8217713 | ||
|
|
f3174c58bc | ||
|
|
e66e135a63 | ||
|
|
d814ed1d4a | ||
|
|
84c6e52be2 | ||
|
|
2cf6393161 | ||
|
|
5a7759f1c4 | ||
|
|
db5205931b | ||
|
|
62d59cffcc | ||
|
|
2e7129e816 | ||
|
|
2d22a2d1c2 | ||
|
|
c92968da8a | ||
|
|
86580d07cb | ||
|
|
03ea71034f | ||
|
|
7bf676abfa | ||
|
|
fb16e6b027 | ||
|
|
4eb04afa62 | ||
|
|
841a831c63 | ||
|
|
ae4af2966a |
@@ -1449,6 +1449,7 @@ message VoiceAssistantRequest {
|
||||
string conversation_id = 2;
|
||||
uint32 flags = 3;
|
||||
VoiceAssistantAudioSettings audio_settings = 4;
|
||||
string wake_word_phrase = 5;
|
||||
}
|
||||
|
||||
message VoiceAssistantResponse {
|
||||
|
||||
@@ -6594,6 +6594,10 @@ bool VoiceAssistantRequest::decode_length(uint32_t field_id, ProtoLengthDelimite
|
||||
this->audio_settings = value.as_message<VoiceAssistantAudioSettings>();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->wake_word_phrase = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -6603,6 +6607,7 @@ void VoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(2, this->conversation_id);
|
||||
buffer.encode_uint32(3, this->flags);
|
||||
buffer.encode_message<VoiceAssistantAudioSettings>(4, this->audio_settings);
|
||||
buffer.encode_string(5, this->wake_word_phrase);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void VoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
@@ -6624,6 +6629,10 @@ void VoiceAssistantRequest::dump_to(std::string &out) const {
|
||||
out.append(" audio_settings: ");
|
||||
this->audio_settings.dump_to(out);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" wake_word_phrase: ");
|
||||
out.append("'").append(this->wake_word_phrase).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1701,6 +1701,7 @@ class VoiceAssistantRequest : public ProtoMessage {
|
||||
std::string conversation_id{};
|
||||
uint32_t flags{0};
|
||||
VoiceAssistantAudioSettings audio_settings{};
|
||||
std::string wake_word_phrase{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
@@ -118,7 +118,7 @@ void CSE7766Component::parse_data_() {
|
||||
uint32_t power_coeff = this->get_24_bit_uint_(14);
|
||||
uint32_t power_cycle = this->get_24_bit_uint_(17);
|
||||
uint8_t adj = this->raw_data_[20];
|
||||
uint32_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
|
||||
uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
|
||||
|
||||
bool have_power = adj & 0x10;
|
||||
bool have_current = adj & 0x20;
|
||||
@@ -132,8 +132,19 @@ void CSE7766Component::parse_data_() {
|
||||
}
|
||||
}
|
||||
|
||||
float energy = 0.0;
|
||||
if (this->energy_sensor_ != nullptr) {
|
||||
if (this->cf_pulses_last_ == 0 && !this->energy_sensor_->has_state()) {
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
}
|
||||
uint16_t cf_diff = cf_pulses - this->cf_pulses_last_;
|
||||
this->cf_pulses_total_ += cf_diff;
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f;
|
||||
this->energy_sensor_->publish_state(energy);
|
||||
}
|
||||
|
||||
float power = 0.0f;
|
||||
float energy = 0.0f;
|
||||
if (power_cycle_exceeds_range) {
|
||||
// Datasheet: power cycle exceeding range means active power is 0
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
@@ -144,27 +155,6 @@ void CSE7766Component::parse_data_() {
|
||||
if (this->power_sensor_ != nullptr) {
|
||||
this->power_sensor_->publish_state(power);
|
||||
}
|
||||
|
||||
// Add CF pulses to the total energy only if we have Power coefficient to multiply by
|
||||
|
||||
if (this->cf_pulses_last_ == 0) {
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
}
|
||||
|
||||
uint32_t cf_diff;
|
||||
if (cf_pulses < this->cf_pulses_last_) {
|
||||
cf_diff = cf_pulses + (0x10000 - this->cf_pulses_last_);
|
||||
} else {
|
||||
cf_diff = cf_pulses - this->cf_pulses_last_;
|
||||
}
|
||||
this->cf_pulses_last_ = cf_pulses;
|
||||
|
||||
energy = cf_diff * float(power_coeff) / 1000000.0f / 3600.0f;
|
||||
this->energy_total_ += energy;
|
||||
if (this->energy_sensor_ != nullptr)
|
||||
this->energy_sensor_->publish_state(this->energy_total_);
|
||||
} else if ((this->energy_sensor_ != nullptr) && !this->energy_sensor_->has_state()) {
|
||||
this->energy_sensor_->publish_state(0);
|
||||
}
|
||||
|
||||
float current = 0.0f;
|
||||
|
||||
@@ -30,8 +30,8 @@ class CSE7766Component : public Component, public uart::UARTDevice {
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
sensor::Sensor *energy_sensor_{nullptr};
|
||||
float energy_total_{0.0f};
|
||||
uint32_t cf_pulses_last_{0};
|
||||
uint32_t cf_pulses_total_{0};
|
||||
uint16_t cf_pulses_last_{0};
|
||||
};
|
||||
|
||||
} // namespace cse7766
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -7,6 +7,8 @@ CODEOWNERS = ["@ellull"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_PWM = "pwm"
|
||||
CONF_DIVIDER = "divider"
|
||||
CONF_DAC = "dac"
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -134,7 +134,7 @@ void MicroWakeWord::loop() {
|
||||
this->set_state_(State::IDLE);
|
||||
if (this->detected_) {
|
||||
this->detected_ = false;
|
||||
this->wake_word_detected_trigger_->trigger("");
|
||||
this->wake_word_detected_trigger_->trigger(this->wake_word_);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -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) {
|
||||
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; }
|
||||
|
||||
@@ -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)>;
|
||||
|
||||
@@ -14,12 +14,14 @@ from esphome.const import (
|
||||
|
||||
from .. import speed_ns
|
||||
|
||||
AUTO_LOAD = ["output"]
|
||||
|
||||
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 +34,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))
|
||||
|
||||
@@ -36,9 +36,10 @@ void SpeedFan::control(const fan::FanCall &call) {
|
||||
}
|
||||
|
||||
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;
|
||||
this->output_->set_level(speed);
|
||||
|
||||
}
|
||||
if (this->oscillating_ != nullptr)
|
||||
this->oscillating_->set_state(this->oscillating);
|
||||
if (this->direction_ != nullptr)
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -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_());
|
||||
}
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -28,7 +28,7 @@ void TMP102Component::dump_config() {
|
||||
}
|
||||
|
||||
void TMP102Component::update() {
|
||||
uint16_t raw_temperature;
|
||||
int16_t raw_temperature;
|
||||
if (this->write(&TMP102_REGISTER_TEMPERATURE, 1) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -39,7 +39,6 @@ void TMP102Component::update() {
|
||||
return;
|
||||
}
|
||||
raw_temperature = i2c::i2ctohs(raw_temperature);
|
||||
|
||||
raw_temperature = raw_temperature >> 4;
|
||||
float temperature = raw_temperature * TMP102_CONVERSION_FACTOR;
|
||||
ESP_LOGD(TAG, "Got Temperature=%.1f°C", temperature);
|
||||
|
||||
@@ -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,8 +84,12 @@ 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()) {
|
||||
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;
|
||||
this->parent_->set_enum_datapoint_value(*this->direction_id_, enable);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -41,6 +42,8 @@ CONF_AUTO_GAIN = "auto_gain"
|
||||
CONF_NOISE_SUPPRESSION_LEVEL = "noise_suppression_level"
|
||||
CONF_VOLUME_MULTIPLIER = "volume_multiplier"
|
||||
|
||||
CONF_WAKE_WORD = "wake_word"
|
||||
|
||||
|
||||
voice_assistant_ns = cg.esphome_ns.namespace("voice_assistant")
|
||||
VoiceAssistant = voice_assistant_ns.class_("VoiceAssistant", cg.Component)
|
||||
@@ -127,6 +130,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 +263,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")
|
||||
|
||||
|
||||
@@ -276,6 +287,7 @@ VOICE_ASSISTANT_ACTION_SCHEMA = cv.Schema({cv.GenerateID(): cv.use_id(VoiceAssis
|
||||
VOICE_ASSISTANT_ACTION_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_SILENCE_DETECTION, default=True): cv.boolean,
|
||||
cv.Optional(CONF_WAKE_WORD): cv.templatable(cv.string),
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -284,6 +296,9 @@ async def voice_assistant_listen_to_code(config, action_id, template_arg, args):
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
if CONF_SILENCE_DETECTION in config:
|
||||
cg.add(var.set_silence_detection(config[CONF_SILENCE_DETECTION]))
|
||||
if wake_word := config.get(CONF_WAKE_WORD):
|
||||
templ = await cg.templatable(wake_word, args, cg.std_string)
|
||||
cg.add(var.set_wake_word(templ))
|
||||
return var
|
||||
|
||||
|
||||
|
||||
@@ -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_) {
|
||||
@@ -213,6 +215,8 @@ void VoiceAssistant::loop() {
|
||||
msg.conversation_id = this->conversation_id_;
|
||||
msg.flags = flags;
|
||||
msg.audio_settings = audio_settings;
|
||||
msg.wake_word_phrase = this->wake_word_;
|
||||
this->wake_word_ = "";
|
||||
|
||||
if (this->api_client_ == nullptr || !this->api_client_->send_voice_assistant_request(msg)) {
|
||||
ESP_LOGW(TAG, "Could not request start");
|
||||
@@ -618,6 +622,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;
|
||||
|
||||
@@ -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_; }
|
||||
@@ -123,6 +124,8 @@ class VoiceAssistant : public Component {
|
||||
void client_subscription(api::APIConnection *client, bool subscribe);
|
||||
api::APIConnection *get_api_connection() const { return this->api_client_; }
|
||||
|
||||
void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
|
||||
|
||||
protected:
|
||||
int read_microphone_();
|
||||
void set_state_(State state);
|
||||
@@ -148,6 +151,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<>();
|
||||
@@ -173,6 +177,8 @@ class VoiceAssistant : public Component {
|
||||
|
||||
std::string conversation_id_{""};
|
||||
|
||||
std::string wake_word_{""};
|
||||
|
||||
HighFrequencyLoopRequester high_freq_;
|
||||
|
||||
#ifdef USE_ESP_ADF
|
||||
@@ -198,8 +204,13 @@ class VoiceAssistant : public Component {
|
||||
};
|
||||
|
||||
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<VoiceAssistant> {
|
||||
TEMPLATABLE_VALUE(std::string, wake_word);
|
||||
|
||||
public:
|
||||
void play(Ts... x) override { this->parent_->request_start(false, this->silence_detection_); }
|
||||
void play(Ts... x) override {
|
||||
this->parent_->set_wake_word(this->wake_word_.value(x...));
|
||||
this->parent_->request_start(false, this->silence_detection_);
|
||||
}
|
||||
|
||||
void set_silence_detection(bool silence_detection) { this->silence_detection_ = silence_detection; }
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2024.2.0b2"
|
||||
__version__ = "2024.2.2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
VALID_SUBSTITUTIONS_CHARACTERS = (
|
||||
|
||||
@@ -8,3 +8,5 @@ MAX_EXECUTOR_WORKERS = 48
|
||||
|
||||
|
||||
SENTINEL = object()
|
||||
|
||||
DASHBOARD_COMMAND = ["esphome", "--dashboard"]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,12 +806,21 @@ 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
|
||||
)
|
||||
if content is not None:
|
||||
self.set_header("Content-Type", "application/yaml")
|
||||
self.write(content)
|
||||
|
||||
def _read_file(self, filename: str, configuration: str) -> bytes | None:
|
||||
@@ -835,15 +842,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)
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user