mirror of
https://github.com/esphome/esphome.git
synced 2025-03-25 03:58:23 +00:00
253 lines
7.6 KiB
C++
253 lines
7.6 KiB
C++
#include "datetime_entity.h"
|
|
|
|
#ifdef USE_DATETIME_DATETIME
|
|
|
|
#include "esphome/core/log.h"
|
|
|
|
namespace esphome {
|
|
namespace datetime {
|
|
|
|
static const char *const TAG = "datetime.datetime_entity";
|
|
|
|
void DateTimeEntity::publish_state() {
|
|
if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
|
|
this->has_state_ = false;
|
|
return;
|
|
}
|
|
if (this->year_ < 1970 || this->year_ > 3000) {
|
|
this->has_state_ = false;
|
|
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
|
return;
|
|
}
|
|
if (this->month_ < 1 || this->month_ > 12) {
|
|
this->has_state_ = false;
|
|
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
|
return;
|
|
}
|
|
if (this->day_ > days_in_month(this->month_, this->year_)) {
|
|
this->has_state_ = false;
|
|
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
|
|
return;
|
|
}
|
|
if (this->hour_ > 23) {
|
|
this->has_state_ = false;
|
|
ESP_LOGE(TAG, "Hour must be between 0 and 23");
|
|
return;
|
|
}
|
|
if (this->minute_ > 59) {
|
|
this->has_state_ = false;
|
|
ESP_LOGE(TAG, "Minute must be between 0 and 59");
|
|
return;
|
|
}
|
|
if (this->second_ > 59) {
|
|
this->has_state_ = false;
|
|
ESP_LOGE(TAG, "Second must be between 0 and 59");
|
|
return;
|
|
}
|
|
this->has_state_ = true;
|
|
ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
|
|
this->month_, this->day_, this->hour_, this->minute_, this->second_);
|
|
this->state_callback_.call();
|
|
}
|
|
|
|
DateTimeCall DateTimeEntity::make_call() { return DateTimeCall(this); }
|
|
|
|
ESPTime DateTimeEntity::state_as_esptime() const {
|
|
ESPTime obj;
|
|
obj.year = this->year_;
|
|
obj.month = this->month_;
|
|
obj.day_of_month = this->day_;
|
|
obj.hour = this->hour_;
|
|
obj.minute = this->minute_;
|
|
obj.second = this->second_;
|
|
obj.recalc_timestamp_local();
|
|
return obj;
|
|
}
|
|
|
|
void DateTimeCall::validate_() {
|
|
if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
|
|
ESP_LOGE(TAG, "Year must be between 1970 and 3000");
|
|
this->year_.reset();
|
|
this->month_.reset();
|
|
this->day_.reset();
|
|
}
|
|
if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
|
|
ESP_LOGE(TAG, "Month must be between 1 and 12");
|
|
this->month_.reset();
|
|
this->day_.reset();
|
|
}
|
|
if (this->day_.has_value()) {
|
|
uint16_t year = 0;
|
|
uint8_t month = 0;
|
|
if (this->month_.has_value()) {
|
|
month = *this->month_;
|
|
} else {
|
|
if (this->parent_->month != 0) {
|
|
month = this->parent_->month;
|
|
} else {
|
|
ESP_LOGE(TAG, "Month must be set to validate day");
|
|
this->day_.reset();
|
|
}
|
|
}
|
|
if (this->year_.has_value()) {
|
|
year = *this->year_;
|
|
} else {
|
|
if (this->parent_->year != 0) {
|
|
year = this->parent_->year;
|
|
} else {
|
|
ESP_LOGE(TAG, "Year must be set to validate day");
|
|
this->day_.reset();
|
|
}
|
|
}
|
|
if (this->day_.has_value() && *this->day_ > days_in_month(month, year)) {
|
|
ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(month, year), month);
|
|
this->day_.reset();
|
|
}
|
|
}
|
|
|
|
if (this->hour_.has_value() && this->hour_ > 23) {
|
|
ESP_LOGE(TAG, "Hour must be between 0 and 23");
|
|
this->hour_.reset();
|
|
}
|
|
if (this->minute_.has_value() && this->minute_ > 59) {
|
|
ESP_LOGE(TAG, "Minute must be between 0 and 59");
|
|
this->minute_.reset();
|
|
}
|
|
if (this->second_.has_value() && this->second_ > 59) {
|
|
ESP_LOGE(TAG, "Second must be between 0 and 59");
|
|
this->second_.reset();
|
|
}
|
|
}
|
|
|
|
void DateTimeCall::perform() {
|
|
this->validate_();
|
|
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
|
|
|
if (this->year_.has_value()) {
|
|
ESP_LOGD(TAG, " Year: %d", *this->year_);
|
|
}
|
|
if (this->month_.has_value()) {
|
|
ESP_LOGD(TAG, " Month: %d", *this->month_);
|
|
}
|
|
if (this->day_.has_value()) {
|
|
ESP_LOGD(TAG, " Day: %d", *this->day_);
|
|
}
|
|
if (this->hour_.has_value()) {
|
|
ESP_LOGD(TAG, " Hour: %d", *this->hour_);
|
|
}
|
|
if (this->minute_.has_value()) {
|
|
ESP_LOGD(TAG, " Minute: %d", *this->minute_);
|
|
}
|
|
if (this->second_.has_value()) {
|
|
ESP_LOGD(TAG, " Second: %d", *this->second_);
|
|
}
|
|
this->parent_->control(*this);
|
|
}
|
|
|
|
DateTimeCall &DateTimeCall::set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
|
|
uint8_t second) {
|
|
this->year_ = year;
|
|
this->month_ = month;
|
|
this->day_ = day;
|
|
this->hour_ = hour;
|
|
this->minute_ = minute;
|
|
this->second_ = second;
|
|
return *this;
|
|
};
|
|
|
|
DateTimeCall &DateTimeCall::set_datetime(ESPTime datetime) {
|
|
return this->set_datetime(datetime.year, datetime.month, datetime.day_of_month, datetime.hour, datetime.minute,
|
|
datetime.second);
|
|
};
|
|
|
|
DateTimeCall &DateTimeCall::set_datetime(const std::string &datetime) {
|
|
ESPTime val{};
|
|
if (!ESPTime::strptime(datetime, val)) {
|
|
ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
|
|
return *this;
|
|
}
|
|
return this->set_datetime(val);
|
|
}
|
|
|
|
DateTimeCall &DateTimeCall::set_datetime(time_t epoch_seconds) {
|
|
ESPTime val = ESPTime::from_epoch_local(epoch_seconds);
|
|
return this->set_datetime(val);
|
|
}
|
|
|
|
DateTimeCall DateTimeEntityRestoreState::to_call(DateTimeEntity *datetime) {
|
|
DateTimeCall call = datetime->make_call();
|
|
call.set_datetime(this->year, this->month, this->day, this->hour, this->minute, this->second);
|
|
return call;
|
|
}
|
|
|
|
void DateTimeEntityRestoreState::apply(DateTimeEntity *time) {
|
|
time->year_ = this->year;
|
|
time->month_ = this->month;
|
|
time->day_ = this->day;
|
|
time->hour_ = this->hour;
|
|
time->minute_ = this->minute;
|
|
time->second_ = this->second;
|
|
time->publish_state();
|
|
}
|
|
|
|
#ifdef USE_TIME
|
|
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
|
|
// there has been a drastic time synchronization
|
|
|
|
void OnDateTimeTrigger::loop() {
|
|
if (!this->parent_->has_state()) {
|
|
return;
|
|
}
|
|
ESPTime time = this->parent_->rtc_->now();
|
|
if (!time.is_valid()) {
|
|
return;
|
|
}
|
|
if (this->last_check_.has_value()) {
|
|
if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > MAX_TIMESTAMP_DRIFT) {
|
|
// We went back in time (a lot), probably caused by time synchronization
|
|
ESP_LOGW(TAG, "Time has jumped back!");
|
|
} else if (*this->last_check_ >= time) {
|
|
// already handled this one
|
|
return;
|
|
} else if (time > *this->last_check_ && time.timestamp - this->last_check_->timestamp > MAX_TIMESTAMP_DRIFT) {
|
|
// We went ahead in time (a lot), probably caused by time synchronization
|
|
ESP_LOGW(TAG, "Time has jumped ahead!");
|
|
this->last_check_ = time;
|
|
return;
|
|
}
|
|
|
|
while (true) {
|
|
this->last_check_->increment_second();
|
|
if (*this->last_check_ >= time)
|
|
break;
|
|
|
|
if (this->matches_(*this->last_check_)) {
|
|
this->trigger();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
this->last_check_ = time;
|
|
if (!time.fields_in_range()) {
|
|
ESP_LOGW(TAG, "Time is out of range!");
|
|
ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u Day=%02u Month=%02u Year=%04u", time.second, time.minute,
|
|
time.hour, time.day_of_month, time.month, time.year);
|
|
}
|
|
|
|
if (this->matches_(time))
|
|
this->trigger();
|
|
}
|
|
|
|
bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
|
|
return time.is_valid() && time.year == this->parent_->year && time.month == this->parent_->month &&
|
|
time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
|
|
time.minute == this->parent_->minute && time.second == this->parent_->second;
|
|
}
|
|
#endif
|
|
|
|
} // namespace datetime
|
|
} // namespace esphome
|
|
|
|
#endif // USE_DATETIME_TIME
|