-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New 'Duty Time' sensor component (#5069)
- Loading branch information
Showing
6 changed files
with
337 additions
and
0 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
CODEOWNERS = ["@dudanov"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
#include "duty_time_sensor.h" | ||
#include "esphome/core/hal.h" | ||
|
||
namespace esphome { | ||
namespace duty_time_sensor { | ||
|
||
static const char *const TAG = "duty_time_sensor"; | ||
|
||
void DutyTimeSensor::set_sensor(binary_sensor::BinarySensor *const sensor) { | ||
sensor->add_on_state_callback([this](bool state) { this->process_state_(state); }); | ||
} | ||
|
||
void DutyTimeSensor::start() { | ||
if (!this->last_state_) | ||
this->process_state_(true); | ||
} | ||
|
||
void DutyTimeSensor::stop() { | ||
if (this->last_state_) | ||
this->process_state_(false); | ||
} | ||
|
||
void DutyTimeSensor::update() { | ||
if (this->last_state_) | ||
this->process_state_(true); | ||
} | ||
|
||
void DutyTimeSensor::loop() { | ||
if (this->func_ == nullptr) | ||
return; | ||
|
||
const bool state = this->func_(); | ||
|
||
if (state != this->last_state_) | ||
this->process_state_(state); | ||
} | ||
|
||
void DutyTimeSensor::setup() { | ||
uint32_t seconds = 0; | ||
|
||
if (this->restore_) { | ||
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_object_id_hash()); | ||
this->pref_.load(&seconds); | ||
} | ||
|
||
this->set_value_(seconds); | ||
} | ||
|
||
void DutyTimeSensor::set_value_(const uint32_t sec) { | ||
this->last_time_ = 0; | ||
if (this->last_state_) | ||
this->last_time_ = millis(); // last time with 0 ms correction | ||
this->publish_and_save_(sec, 0); | ||
} | ||
|
||
void DutyTimeSensor::process_state_(const bool state) { | ||
const uint32_t now = millis(); | ||
|
||
if (this->last_state_) { | ||
// update or falling edge | ||
const uint32_t tm = now - this->last_time_; | ||
const uint32_t ms = tm % 1000; | ||
|
||
this->publish_and_save_(this->total_sec_ + tm / 1000, ms); | ||
this->last_time_ = now - ms; // store time with ms correction | ||
|
||
if (!state) { | ||
// falling edge | ||
this->last_time_ = ms; // temporary store ms correction only | ||
this->last_state_ = false; | ||
|
||
if (this->last_duty_time_sensor_ != nullptr) { | ||
const uint32_t turn_on_ms = now - this->edge_time_; | ||
this->last_duty_time_sensor_->publish_state(turn_on_ms * 1e-3f); | ||
} | ||
} | ||
|
||
} else if (state) { | ||
// rising edge | ||
this->last_time_ = now - this->last_time_; // store time with ms correction | ||
this->edge_time_ = now; // store turn-on start time | ||
this->last_state_ = true; | ||
} | ||
} | ||
|
||
void DutyTimeSensor::publish_and_save_(const uint32_t sec, const uint32_t ms) { | ||
this->total_sec_ = sec; | ||
this->publish_state(sec + ms * 1e-3f); | ||
|
||
if (this->restore_) | ||
this->pref_.save(&sec); | ||
} | ||
|
||
void DutyTimeSensor::dump_config() { | ||
ESP_LOGCONFIG(TAG, "Duty Time:"); | ||
ESP_LOGCONFIG(TAG, " Update Interval: %dms", this->get_update_interval()); | ||
ESP_LOGCONFIG(TAG, " Restore: %s", ONOFF(this->restore_)); | ||
LOG_SENSOR(" ", "Duty Time Sensor:", this); | ||
LOG_SENSOR(" ", "Last Duty Time Sensor:", this->last_duty_time_sensor_); | ||
} | ||
|
||
} // namespace duty_time_sensor | ||
} // namespace esphome |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
#pragma once | ||
|
||
#include "esphome/core/automation.h" | ||
#include "esphome/core/component.h" | ||
#include "esphome/core/preferences.h" | ||
#include "esphome/components/binary_sensor/binary_sensor.h" | ||
#include "esphome/components/sensor/sensor.h" | ||
|
||
namespace esphome { | ||
namespace duty_time_sensor { | ||
|
||
class DutyTimeSensor : public sensor::Sensor, public PollingComponent { | ||
public: | ||
void setup() override; | ||
void update() override; | ||
void loop() override; | ||
void dump_config() override; | ||
float get_setup_priority() const override { return setup_priority::DATA; } | ||
|
||
void start(); | ||
void stop(); | ||
bool is_running() const { return this->last_state_; } | ||
void reset() { this->set_value_(0); } | ||
|
||
void set_lambda(std::function<bool()> &&func) { this->func_ = func; } | ||
void set_sensor(binary_sensor::BinarySensor *sensor); | ||
void set_last_duty_time_sensor(sensor::Sensor *sensor) { this->last_duty_time_sensor_ = sensor; } | ||
void set_restore(bool restore) { this->restore_ = restore; } | ||
|
||
protected: | ||
void set_value_(uint32_t sec); | ||
void process_state_(bool state); | ||
void publish_and_save_(uint32_t sec, uint32_t ms); | ||
|
||
std::function<bool()> func_{nullptr}; | ||
sensor::Sensor *last_duty_time_sensor_{nullptr}; | ||
ESPPreferenceObject pref_; | ||
|
||
uint32_t total_sec_; | ||
uint32_t last_time_; | ||
uint32_t edge_time_; | ||
bool last_state_{false}; | ||
bool restore_; | ||
}; | ||
|
||
template<typename... Ts> class StartAction : public Action<Ts...> { | ||
public: | ||
explicit StartAction(DutyTimeSensor *parent) : parent_(parent) {} | ||
|
||
void play(Ts... x) override { this->parent_->start(); } | ||
|
||
protected: | ||
DutyTimeSensor *parent_; | ||
}; | ||
|
||
template<typename... Ts> class StopAction : public Action<Ts...> { | ||
public: | ||
explicit StopAction(DutyTimeSensor *parent) : parent_(parent) {} | ||
|
||
void play(Ts... x) override { this->parent_->stop(); } | ||
|
||
protected: | ||
DutyTimeSensor *parent_; | ||
}; | ||
|
||
template<typename... Ts> class ResetAction : public Action<Ts...> { | ||
public: | ||
explicit ResetAction(DutyTimeSensor *parent) : parent_(parent) {} | ||
|
||
void play(Ts... x) override { this->parent_->reset(); } | ||
|
||
protected: | ||
DutyTimeSensor *parent_; | ||
}; | ||
|
||
template<typename... Ts> class RunningCondition : public Condition<Ts...> { | ||
public: | ||
explicit RunningCondition(DutyTimeSensor *parent, bool state) : parent_(parent), state_(state) {} | ||
|
||
bool check(Ts... x) override { return this->parent_->is_running() == this->state_; } | ||
|
||
protected: | ||
DutyTimeSensor *parent_; | ||
bool state_; | ||
}; | ||
|
||
} // namespace duty_time_sensor | ||
} // namespace esphome |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import esphome.codegen as cg | ||
import esphome.config_validation as cv | ||
from esphome.automation import ( | ||
Action, | ||
Condition, | ||
maybe_simple_id, | ||
register_action, | ||
register_condition, | ||
) | ||
from esphome.components import binary_sensor, sensor | ||
from esphome.const import ( | ||
CONF_ID, | ||
CONF_SENSOR, | ||
CONF_RESTORE, | ||
CONF_LAMBDA, | ||
UNIT_SECOND, | ||
STATE_CLASS_TOTAL, | ||
STATE_CLASS_TOTAL_INCREASING, | ||
DEVICE_CLASS_DURATION, | ||
ENTITY_CATEGORY_DIAGNOSTIC, | ||
) | ||
|
||
CONF_LAST_TIME = "last_time" | ||
|
||
duty_time_sensor_ns = cg.esphome_ns.namespace("duty_time_sensor") | ||
DutyTimeSensor = duty_time_sensor_ns.class_( | ||
"DutyTimeSensor", sensor.Sensor, cg.PollingComponent | ||
) | ||
StartAction = duty_time_sensor_ns.class_("StartAction", Action) | ||
StopAction = duty_time_sensor_ns.class_("StopAction", Action) | ||
ResetAction = duty_time_sensor_ns.class_("ResetAction", Action) | ||
SetAction = duty_time_sensor_ns.class_("SetAction", Action) | ||
RunningCondition = duty_time_sensor_ns.class_("RunningCondition", Condition) | ||
|
||
|
||
CONFIG_SCHEMA = cv.All( | ||
sensor.sensor_schema( | ||
DutyTimeSensor, | ||
unit_of_measurement=UNIT_SECOND, | ||
icon="mdi:timer-play-outline", | ||
accuracy_decimals=3, | ||
state_class=STATE_CLASS_TOTAL_INCREASING, | ||
device_class=DEVICE_CLASS_DURATION, | ||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||
) | ||
.extend( | ||
{ | ||
cv.Optional(CONF_SENSOR): cv.use_id(binary_sensor.BinarySensor), | ||
cv.Optional(CONF_LAMBDA): cv.lambda_, | ||
cv.Optional(CONF_RESTORE, default=False): cv.boolean, | ||
cv.Optional(CONF_LAST_TIME): sensor.sensor_schema( | ||
unit_of_measurement=UNIT_SECOND, | ||
icon="mdi:timer-marker-outline", | ||
accuracy_decimals=3, | ||
state_class=STATE_CLASS_TOTAL, | ||
device_class=DEVICE_CLASS_DURATION, | ||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||
), | ||
} | ||
) | ||
.extend(cv.polling_component_schema("60s")), | ||
cv.has_at_most_one_key(CONF_SENSOR, CONF_LAMBDA), | ||
) | ||
|
||
|
||
async def to_code(config): | ||
var = await sensor.new_sensor(config) | ||
await cg.register_component(var, config) | ||
cg.add(var.set_restore(config[CONF_RESTORE])) | ||
if CONF_SENSOR in config: | ||
sens = await cg.get_variable(config[CONF_SENSOR]) | ||
cg.add(var.set_sensor(sens)) | ||
if CONF_LAMBDA in config: | ||
lambda_ = await cg.process_lambda(config[CONF_LAMBDA], [], return_type=cg.bool_) | ||
cg.add(var.set_lambda(lambda_)) | ||
if CONF_LAST_TIME in config: | ||
sens = await sensor.new_sensor(config[CONF_LAST_TIME]) | ||
cg.add(var.set_last_duty_time_sensor(sens)) | ||
|
||
|
||
# AUTOMATIONS | ||
|
||
DUTY_TIME_ID_SCHEMA = maybe_simple_id( | ||
{ | ||
cv.Required(CONF_ID): cv.use_id(DutyTimeSensor), | ||
} | ||
) | ||
|
||
|
||
@register_action("sensor.duty_time.start", StartAction, DUTY_TIME_ID_SCHEMA) | ||
async def sensor_runtime_start_to_code(config, action_id, template_arg, args): | ||
paren = await cg.get_variable(config[CONF_ID]) | ||
return cg.new_Pvariable(action_id, template_arg, paren) | ||
|
||
|
||
@register_action("sensor.duty_time.stop", StopAction, DUTY_TIME_ID_SCHEMA) | ||
async def sensor_runtime_stop_to_code(config, action_id, template_arg, args): | ||
paren = await cg.get_variable(config[CONF_ID]) | ||
return cg.new_Pvariable(action_id, template_arg, paren) | ||
|
||
|
||
@register_action("sensor.duty_time.reset", ResetAction, DUTY_TIME_ID_SCHEMA) | ||
async def sensor_runtime_reset_to_code(config, action_id, template_arg, args): | ||
paren = await cg.get_variable(config[CONF_ID]) | ||
return cg.new_Pvariable(action_id, template_arg, paren) | ||
|
||
|
||
@register_condition( | ||
"sensor.duty_time.is_running", RunningCondition, DUTY_TIME_ID_SCHEMA | ||
) | ||
async def duty_time_is_running_to_code(config, condition_id, template_arg, args): | ||
paren = await cg.get_variable(config[CONF_ID]) | ||
return cg.new_Pvariable(condition_id, template_arg, paren, True) | ||
|
||
|
||
@register_condition( | ||
"sensor.duty_time.is_not_running", RunningCondition, DUTY_TIME_ID_SCHEMA | ||
) | ||
async def duty_time_is_not_running_to_code(config, condition_id, template_arg, args): | ||
paren = await cg.get_variable(config[CONF_ID]) | ||
return cg.new_Pvariable(condition_id, template_arg, paren, False) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters