mirror of
https://github.com/esphome/esphome.git
synced 2024-11-29 18:24:13 +01:00
commit
cc115e7cc9
16 changed files with 109 additions and 52 deletions
|
@ -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"
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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)>;
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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_{};
|
||||||
|
|
|
@ -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],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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_());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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_{};
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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 = (
|
||||||
|
|
|
@ -8,3 +8,5 @@ MAX_EXECUTOR_WORKERS = 48
|
||||||
|
|
||||||
|
|
||||||
SENTINEL = object()
|
SENTINEL = object()
|
||||||
|
|
||||||
|
DASHBOARD_COMMAND = ["esphome", "--dashboard"]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue