ESPHome  2024.12.2
datetime_entity.cpp
Go to the documentation of this file.
1 #include "datetime_entity.h"
2 
3 #ifdef USE_DATETIME_DATETIME
4 
5 #include "esphome/core/log.h"
6 
7 namespace esphome {
8 namespace datetime {
9 
10 static const char *const TAG = "datetime.datetime_entity";
11 
13  if (this->year_ == 0 || this->month_ == 0 || this->day_ == 0) {
14  this->has_state_ = false;
15  return;
16  }
17  if (this->year_ < 1970 || this->year_ > 3000) {
18  this->has_state_ = false;
19  ESP_LOGE(TAG, "Year must be between 1970 and 3000");
20  return;
21  }
22  if (this->month_ < 1 || this->month_ > 12) {
23  this->has_state_ = false;
24  ESP_LOGE(TAG, "Month must be between 1 and 12");
25  return;
26  }
27  if (this->day_ > days_in_month(this->month_, this->year_)) {
28  this->has_state_ = false;
29  ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(this->month_, this->year_), this->month_);
30  return;
31  }
32  if (this->hour_ > 23) {
33  this->has_state_ = false;
34  ESP_LOGE(TAG, "Hour must be between 0 and 23");
35  return;
36  }
37  if (this->minute_ > 59) {
38  this->has_state_ = false;
39  ESP_LOGE(TAG, "Minute must be between 0 and 59");
40  return;
41  }
42  if (this->second_ > 59) {
43  this->has_state_ = false;
44  ESP_LOGE(TAG, "Second must be between 0 and 59");
45  return;
46  }
47  this->has_state_ = true;
48  ESP_LOGD(TAG, "'%s': Sending datetime %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_,
49  this->month_, this->day_, this->hour_, this->minute_, this->second_);
50  this->state_callback_.call();
51 }
52 
54 
56  ESPTime obj;
57  obj.year = this->year_;
58  obj.month = this->month_;
59  obj.day_of_month = this->day_;
60  obj.hour = this->hour_;
61  obj.minute = this->minute_;
62  obj.second = this->second_;
64  return obj;
65 }
66 
68  if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
69  ESP_LOGE(TAG, "Year must be between 1970 and 3000");
70  this->year_.reset();
71  this->month_.reset();
72  this->day_.reset();
73  }
74  if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
75  ESP_LOGE(TAG, "Month must be between 1 and 12");
76  this->month_.reset();
77  this->day_.reset();
78  }
79  if (this->day_.has_value()) {
80  uint16_t year = 0;
81  uint8_t month = 0;
82  if (this->month_.has_value()) {
83  month = *this->month_;
84  } else {
85  if (this->parent_->month != 0) {
86  month = this->parent_->month;
87  } else {
88  ESP_LOGE(TAG, "Month must be set to validate day");
89  this->day_.reset();
90  }
91  }
92  if (this->year_.has_value()) {
93  year = *this->year_;
94  } else {
95  if (this->parent_->year != 0) {
96  year = this->parent_->year;
97  } else {
98  ESP_LOGE(TAG, "Year must be set to validate day");
99  this->day_.reset();
100  }
101  }
102  if (this->day_.has_value() && *this->day_ > days_in_month(month, year)) {
103  ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(month, year), month);
104  this->day_.reset();
105  }
106  }
107 
108  if (this->hour_.has_value() && this->hour_ > 23) {
109  ESP_LOGE(TAG, "Hour must be between 0 and 23");
110  this->hour_.reset();
111  }
112  if (this->minute_.has_value() && this->minute_ > 59) {
113  ESP_LOGE(TAG, "Minute must be between 0 and 59");
114  this->minute_.reset();
115  }
116  if (this->second_.has_value() && this->second_ > 59) {
117  ESP_LOGE(TAG, "Second must be between 0 and 59");
118  this->second_.reset();
119  }
120 }
121 
123  this->validate_();
124  ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
125 
126  if (this->year_.has_value()) {
127  ESP_LOGD(TAG, " Year: %d", *this->year_);
128  }
129  if (this->month_.has_value()) {
130  ESP_LOGD(TAG, " Month: %d", *this->month_);
131  }
132  if (this->day_.has_value()) {
133  ESP_LOGD(TAG, " Day: %d", *this->day_);
134  }
135  if (this->hour_.has_value()) {
136  ESP_LOGD(TAG, " Hour: %d", *this->hour_);
137  }
138  if (this->minute_.has_value()) {
139  ESP_LOGD(TAG, " Minute: %d", *this->minute_);
140  }
141  if (this->second_.has_value()) {
142  ESP_LOGD(TAG, " Second: %d", *this->second_);
143  }
144  this->parent_->control(*this);
145 }
146 
147 DateTimeCall &DateTimeCall::set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
148  uint8_t second) {
149  this->year_ = year;
150  this->month_ = month;
151  this->day_ = day;
152  this->hour_ = hour;
153  this->minute_ = minute;
154  this->second_ = second;
155  return *this;
156 };
157 
159  return this->set_datetime(datetime.year, datetime.month, datetime.day_of_month, datetime.hour, datetime.minute,
160  datetime.second);
161 };
162 
163 DateTimeCall &DateTimeCall::set_datetime(const std::string &datetime) {
164  ESPTime val{};
165  if (!ESPTime::strptime(datetime, val)) {
166  ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
167  return *this;
168  }
169  return this->set_datetime(val);
170 }
171 
172 DateTimeCall &DateTimeCall::set_datetime(time_t epoch_seconds) {
173  ESPTime val = ESPTime::from_epoch_local(epoch_seconds);
174  return this->set_datetime(val);
175 }
176 
178  DateTimeCall call = datetime->make_call();
179  call.set_datetime(this->year, this->month, this->day, this->hour, this->minute, this->second);
180  return call;
181 }
182 
184  time->year_ = this->year;
185  time->month_ = this->month;
186  time->day_ = this->day;
187  time->hour_ = this->hour;
188  time->minute_ = this->minute;
189  time->second_ = this->second;
190  time->publish_state();
191 }
192 
193 #ifdef USE_TIME
194 static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
195  // there has been a drastic time synchronization
196 
198  if (!this->parent_->has_state()) {
199  return;
200  }
201  ESPTime time = this->parent_->rtc_->now();
202  if (!time.is_valid()) {
203  return;
204  }
205  if (this->last_check_.has_value()) {
206  if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > MAX_TIMESTAMP_DRIFT) {
207  // We went back in time (a lot), probably caused by time synchronization
208  ESP_LOGW(TAG, "Time has jumped back!");
209  } else if (*this->last_check_ >= time) {
210  // already handled this one
211  return;
212  } else if (time > *this->last_check_ && time.timestamp - this->last_check_->timestamp > MAX_TIMESTAMP_DRIFT) {
213  // We went ahead in time (a lot), probably caused by time synchronization
214  ESP_LOGW(TAG, "Time has jumped ahead!");
215  this->last_check_ = time;
216  return;
217  }
218 
219  while (true) {
220  this->last_check_->increment_second();
221  if (*this->last_check_ >= time)
222  break;
223 
224  if (this->matches_(*this->last_check_)) {
225  this->trigger();
226  break;
227  }
228  }
229  }
230 
231  this->last_check_ = time;
232  if (!time.fields_in_range()) {
233  ESP_LOGW(TAG, "Time is out of range!");
234  ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u Day=%02u Month=%02u Year=%04u", time.second, time.minute,
235  time.hour, time.day_of_month, time.month, time.year);
236  }
237 
238  if (this->matches_(time))
239  this->trigger();
240 }
241 
242 bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
243  return time.is_valid() && time.year == this->parent_->year && time.month == this->parent_->month &&
244  time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
245  time.minute == this->parent_->minute && time.second == this->parent_->second;
246 }
247 #endif
248 
249 } // namespace datetime
250 } // namespace esphome
251 
252 #endif // USE_DATETIME_TIME
void recalc_timestamp_local()
Recalculate the timestamp field from the other fields of this ESPTime instance assuming local fields...
Definition: time.cpp:191
A more user-friendly version of struct tm from time.h.
Definition: time.h:15
mopeka_std_values val[4]
CallbackManager< void()> state_callback_
Definition: datetime_base.h:29
void increment_second()
Increment this clock instance by one second.
Definition: time.cpp:118
uint8_t days_in_month(uint8_t month, uint16_t year)
Definition: time.cpp:8
static ESPTime from_epoch_local(time_t epoch)
Convert an UTC epoch timestamp to a local time ESPTime instance.
Definition: time.h:83
uint8_t second
seconds after the minute [0-60]
Definition: time.h:19
time_t timestamp
unix epoch time (seconds since UTC Midnight January 1, 1970)
Definition: time.h:37
uint8_t minute
minutes after the hour [0-59]
Definition: time.h:21
bool matches_(const ESPTime &time) const
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018) ...
Definition: time.h:59
uint16_t year
year
Definition: time.h:33
ESPTime state_as_esptime() const override
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
DateTimeCall to_call(DateTimeEntity *datetime)
uint8_t month
month; january=1 [1-12]
Definition: time.h:31
uint8_t hour
hours since midnight [0-23]
Definition: time.h:23
DateTimeCall & set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)
uint8_t day_of_month
day of the month [1-31]
Definition: time.h:27
const StringRef & get_name() const
Definition: entity_base.cpp:10
bool fields_in_range() const
Check if all time fields of this ESPTime are in range.
Definition: time.h:62
static bool strptime(const std::string &time_to_parse, ESPTime &esp_time)
Convert a string to ESPTime struct as specified by the format argument.
Definition: time.cpp:67