1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-26 23:22:21 +01:00

Add files via upload

This commit is contained in:
Oliver Kleinecke
2025-02-17 13:24:20 +01:00
committed by GitHub
parent 81d463c909
commit dc15aec7f2
3 changed files with 343 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
'''
tbd
'''
from esphome.const import (
CONF_ID,
CONF_ON_TIME,
CONF_THEN,
CONF_HOUR,
CONF_MINUTE,
)
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome import automation
from esphome.components.number import Number
from esphome.components.switch import Switch
from esphome.components.time import RealTimeClock
MULTI_CONF = True
DEPENDENCIES = ["time", "number", "switch"]
CODEOWNERS = ["@hostcc"]
CONF_RTC = 'rtc'
CONF_MON = 'mon'
CONF_TUE = 'tue'
CONF_WED = 'wed'
CONF_THU = 'thu'
CONF_FRI = 'fri'
CONF_SAT = 'sat'
CONF_SUN = 'sun'
CONF_DISABLED = 'disabled'
dynamic_on_time_ns = cg.esphome_ns.namespace("dynamic_on_time")
DynamicOnTimeComponent = dynamic_on_time_ns.class_(
"DynamicOnTime", cg.Component
)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(DynamicOnTimeComponent),
cv.Required(CONF_RTC): cv.use_id(RealTimeClock),
cv.Required(CONF_HOUR): cv.use_id(Number),
cv.Required(CONF_MINUTE): cv.use_id(Number),
cv.Required(CONF_MON): cv.use_id(Switch),
cv.Required(CONF_TUE): cv.use_id(Switch),
cv.Required(CONF_WED): cv.use_id(Switch),
cv.Required(CONF_THU): cv.use_id(Switch),
cv.Required(CONF_FRI): cv.use_id(Switch),
cv.Required(CONF_SAT): cv.use_id(Switch),
cv.Required(CONF_SUN): cv.use_id(Switch),
cv.Required(CONF_DISABLED): cv.use_id(Switch),
cv.Required(CONF_ON_TIME): automation.validate_automation({}),
}).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
'''
tbd
'''
actions = []
for conf in config[CONF_ON_TIME]:
actions.extend(await automation.build_action_list(
conf[CONF_THEN], cg.TemplateArguments(), [])
)
var = cg.new_Pvariable(
config[CONF_ID],
await cg.get_variable(config[CONF_RTC]),
await cg.get_variable(config[CONF_HOUR]),
await cg.get_variable(config[CONF_MINUTE]),
await cg.get_variable(config[CONF_MON]),
await cg.get_variable(config[CONF_TUE]),
await cg.get_variable(config[CONF_WED]),
await cg.get_variable(config[CONF_THU]),
await cg.get_variable(config[CONF_FRI]),
await cg.get_variable(config[CONF_SAT]),
await cg.get_variable(config[CONF_SUN]),
await cg.get_variable(config[CONF_DISABLED]),
actions,
)
await cg.register_component(var, config)

View File

@@ -0,0 +1,212 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023 Ilia Sotnikov
#include "dynamic_on_time.h" // NOLINT(build/include_subdir)
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace dynamic_on_time {
static const char *tag = "dynamic_on_time";
DynamicOnTime::DynamicOnTime(
time::RealTimeClock *rtc,
number::Number *hour,
number::Number *minute,
switch_::Switch *mon,
switch_::Switch *tue,
switch_::Switch *wed,
switch_::Switch *thu,
switch_::Switch *fri,
switch_::Switch *sat,
switch_::Switch *sun,
switch_::Switch *disabled,
std::vector<esphome::Action<> *> actions):
rtc_(rtc),
hour_(hour), minute_(minute),
mon_(mon), tue_(tue), wed_(wed), thu_(thu), fri_(fri), sat_(sat),
sun_(sun), disabled_(disabled), actions_(actions) {}
std::vector<uint8_t> DynamicOnTime::flags_to_days_of_week_(
bool mon, bool tue, bool wed, bool thu, bool fri, bool sat, bool sun
) {
// Numeric representation for days of week (starts from Sun internally)
std::vector<uint8_t> days_of_week = { 1, 2, 3, 4, 5, 6, 7 };
std::vector<bool> flags = { sun, mon, tue, wed, thu, fri, sat };
// Translate set of bool flags into vector of corresponding numeric
// representation. This uses 'erase-remove' approach (
// https://en.cppreference.com/w/cpp/algorithm/remove,
// https://en.wikipedia.org/wiki/Erase%E2%80%93remove_idiom)
days_of_week.erase(
std::remove_if(
std::begin(days_of_week), std::end(days_of_week),
[&](uint8_t& arg) { return !flags[&arg - days_of_week.data()]; }),
days_of_week.end());
return days_of_week;
}
void DynamicOnTime::setup() {
// Update the configuration initially, ensuring all entities are created
// before a callback would be delivered to them
this->update_schedule_();
// Register the cron trigger component
App.register_component(this->trigger_);
// The `Number` and `Switch` has no common base type with
// `add_on_state_callback`, and solutions to properly cast to derived
// class in single loop over vector of base class instances seemingly imply
// more code than just two loops
for (number::Number *comp : {this->hour_, this->minute_}) {
comp->add_on_state_callback([this](float value) {
this->update_schedule_();
});
}
for (switch_::Switch *comp : {
this->mon_, this->tue_, this->wed_, this->thu_, this->fri_, this->sat_,
this->sun_, this->disabled_
}) {
comp->add_on_state_callback([this](bool value) {
this->update_schedule_();
});
}
}
void DynamicOnTime::update_schedule_() {
// CronTrigger doesn't allow its configuration to be reset programmatically,
// so its instance is either created initially, or reinitialized in place if
// allocated already
if (this->trigger_ != nullptr) {
// Use 'placement new' (https://en.cppreference.com/w/cpp/language/new) to
// reinitialize existing CronTrigger instance in place
this->trigger_->~CronTrigger();
new (this->trigger_) time::CronTrigger(this->rtc_);
} else {
this->trigger_ = new time::CronTrigger(this->rtc_);
}
// (Re)create the automation instance but only if scheduled actions aren't
// disabled
if (this->automation_ != nullptr) {
delete this->automation_;
this->automation_ = nullptr;
}
if (!this->disabled_->state) {
this->automation_ = new Automation<>(this->trigger_);
// Add requested actions to it
this->automation_->add_actions(this->actions_);
}
// All remaining logic is active regardless of scheduled actions are
// disabled, since callbacks from Switch/Number components being active still
// need to be processed otherwise inputs will be lost
//
// Set trigger to fire on zeroth second of configured time
this->trigger_->add_second(0);
// Enable all days of months for the schedule
for (uint8_t i = 1; i <= 31; i++)
this->trigger_->add_day_of_month(i);
// Same but for months
for (uint8_t i = 1; i <= 12; i++)
this->trigger_->add_month(i);
// Configure hour/minute of the schedule from corresponding components' state
this->trigger_->add_hour(static_cast<uint8_t>(this->hour_->state));
this->trigger_->add_minute(static_cast<uint8_t>(this->minute_->state));
// Similarly but for days of week translating set of components' state to
// vector of numeric representation as `CrontTrigger::add_days_of_week()`
// requires
this->days_of_week_ = this->flags_to_days_of_week_(
this->mon_->state, this->tue_->state, this->wed_->state,
this->thu_->state, this->fri_->state, this->sat_->state,
this->sun_->state);
this->trigger_->add_days_of_week(this->days_of_week_);
// Initiate updating the cached value for the next schedule
this->next_schedule_.reset();
// Log the configuration
this->dump_config();
}
optional<ESPTime> DynamicOnTime::get_next_schedule() {
if (this->disabled_->state || this->days_of_week_.empty())
return {};
ESPTime now = this->rtc_->now();
if (now < this->next_schedule_.value_or(now))
return this->next_schedule_;
ESP_LOGVV(tag, "Non-cached calculation of next schedule");
// Calculate timestamp for the start of the week with time being hour/time of
// the schedule
time_t start_of_week = now.timestamp
- (now.second + now.hour * 3600 + now.minute * 60 + now.day_of_week * 86400)
+ (3600 * static_cast<int>(this->hour_->state)
+ 60 * static_cast<int>(this->minute_->state));
time_t next = 0, first = 0;
for (auto next_day : this->days_of_week_) {
// Calculate the timestamp for next day in schedule
next = start_of_week + 86400 * next_day;
// Capture timestamp for the first scheduled day
if (!first)
first = next;
// Exit if timestamp corresponds of later date in the schedule found
if (next > now.timestamp)
break;
}
// No later date has been found, use the earlier of scheduled ones plus one
// week
if (next < now.timestamp)
next = first + 7 * 86400;
return this->next_schedule_ = ESPTime::from_epoch_local(next);
}
void DynamicOnTime::dump_config() {
ESP_LOGCONFIG(tag, "Cron trigger details:");
ESP_LOGCONFIG(tag, "Disabled: %s", ONOFF(this->disabled_->state));
ESP_LOGCONFIG(
tag, "Hour (source: '%s'): %.0f",
this->hour_->get_name().c_str(), this->hour_->state);
ESP_LOGCONFIG(
tag, "Minute (source: '%s'): %.0f",
this->minute_->get_name().c_str(), this->minute_->state);
ESP_LOGCONFIG(
tag, "Mon (source: '%s'): %s",
this->mon_->get_name().c_str(), ONOFF(this->mon_->state));
ESP_LOGCONFIG(
tag, "Tue (source: '%s'): %s",
this->tue_->get_name().c_str(), ONOFF(this->tue_->state));
ESP_LOGCONFIG(
tag, "Wed (source: '%s'): %s",
this->wed_->get_name().c_str(), ONOFF(this->wed_->state));
ESP_LOGCONFIG(
tag, "Thu (source: '%s'): %s",
this->thu_->get_name().c_str(), ONOFF(this->thu_->state));
ESP_LOGCONFIG(
tag, "Fri (source: '%s'): %s",
this->fri_->get_name().c_str(), ONOFF(this->fri_->state));
ESP_LOGCONFIG(
tag, "Sat (source: '%s'): %s",
this->sat_->get_name().c_str(), ONOFF(this->sat_->state));
ESP_LOGCONFIG(
tag, "Sun (source: '%s'): %s",
this->sun_->get_name().c_str(), ONOFF(this->sun_->state));
auto schedule = this->get_next_schedule();
if (schedule.has_value())
ESP_LOGCONFIG(
tag, "Next schedule: %s",
schedule.value().strftime("%a %H:%M:%S %m/%d/%Y").c_str());
}
} // namespace dynamic_on_time
} // namespace esphome

View File

@@ -0,0 +1,52 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023 Ilia Sotnikov
#pragma once
#include <vector>
#include "esphome/components/time/real_time_clock.h"
#include "esphome/components/time/automation.h"
#include "esphome/components/number/number.h"
#include "esphome/components/switch/switch.h"
namespace esphome {
namespace dynamic_on_time {
class DynamicOnTime : public Component {
public:
explicit DynamicOnTime(
time::RealTimeClock *, number::Number *, number::Number *,
switch_::Switch *, switch_::Switch *, switch_::Switch *, switch_::Switch *,
switch_::Switch *, switch_::Switch *, switch_::Switch *, switch_::Switch *,
std::vector<esphome::Action<> *>);
void setup() override;
void dump_config() override;
optional<ESPTime> get_next_schedule();
protected:
time::RealTimeClock *rtc_;
number::Number *hour_;
number::Number *minute_;
switch_::Switch *mon_;
switch_::Switch *tue_;
switch_::Switch *wed_;
switch_::Switch *thu_;
switch_::Switch *fri_;
switch_::Switch *sat_;
switch_::Switch *sun_;
switch_::Switch *disabled_;
std::vector<esphome::Action<> *> actions_;
time::CronTrigger *trigger_{nullptr};
Automation<> *automation_{nullptr};
std::vector<uint8_t> days_of_week_{};
std::vector<uint8_t> flags_to_days_of_week_(
bool, bool, bool, bool, bool, bool, bool);
void update_schedule_();
optional<ESPTime> next_schedule_{};
};
} // namespace dynamic_on_time
} // namespace esphome