ESPHome  2024.11.0
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_;
63  obj.day_of_week = 1; // Required to be valid for recalc_timestamp_local but not used.
64  obj.day_of_year = 1; // Required to be valid for recalc_timestamp_local but not used.
65  obj.recalc_timestamp_local(false);
66  return obj;
67 }
68 
70  if (this->year_.has_value() && (this->year_ < 1970 || this->year_ > 3000)) {
71  ESP_LOGE(TAG, "Year must be between 1970 and 3000");
72  this->year_.reset();
73  this->month_.reset();
74  this->day_.reset();
75  }
76  if (this->month_.has_value() && (this->month_ < 1 || this->month_ > 12)) {
77  ESP_LOGE(TAG, "Month must be between 1 and 12");
78  this->month_.reset();
79  this->day_.reset();
80  }
81  if (this->day_.has_value()) {
82  uint16_t year = 0;
83  uint8_t month = 0;
84  if (this->month_.has_value()) {
85  month = *this->month_;
86  } else {
87  if (this->parent_->month != 0) {
88  month = this->parent_->month;
89  } else {
90  ESP_LOGE(TAG, "Month must be set to validate day");
91  this->day_.reset();
92  }
93  }
94  if (this->year_.has_value()) {
95  year = *this->year_;
96  } else {
97  if (this->parent_->year != 0) {
98  year = this->parent_->year;
99  } else {
100  ESP_LOGE(TAG, "Year must be set to validate day");
101  this->day_.reset();
102  }
103  }
104  if (this->day_.has_value() && *this->day_ > days_in_month(month, year)) {
105  ESP_LOGE(TAG, "Day must be between 1 and %d for month %d", days_in_month(month, year), month);
106  this->day_.reset();
107  }
108  }
109 
110  if (this->hour_.has_value() && this->hour_ > 23) {
111  ESP_LOGE(TAG, "Hour must be between 0 and 23");
112  this->hour_.reset();
113  }
114  if (this->minute_.has_value() && this->minute_ > 59) {
115  ESP_LOGE(TAG, "Minute must be between 0 and 59");
116  this->minute_.reset();
117  }
118  if (this->second_.has_value() && this->second_ > 59) {
119  ESP_LOGE(TAG, "Second must be between 0 and 59");
120  this->second_.reset();
121  }
122 }
123 
125  this->validate_();
126  ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
127 
128  if (this->year_.has_value()) {
129  ESP_LOGD(TAG, " Year: %d", *this->year_);
130  }
131  if (this->month_.has_value()) {
132  ESP_LOGD(TAG, " Month: %d", *this->month_);
133  }
134  if (this->day_.has_value()) {
135  ESP_LOGD(TAG, " Day: %d", *this->day_);
136  }
137  if (this->hour_.has_value()) {
138  ESP_LOGD(TAG, " Hour: %d", *this->hour_);
139  }
140  if (this->minute_.has_value()) {
141  ESP_LOGD(TAG, " Minute: %d", *this->minute_);
142  }
143  if (this->second_.has_value()) {
144  ESP_LOGD(TAG, " Second: %d", *this->second_);
145  }
146  this->parent_->control(*this);
147 }
148 
149 DateTimeCall &DateTimeCall::set_datetime(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
150  uint8_t second) {
151  this->year_ = year;
152  this->month_ = month;
153  this->day_ = day;
154  this->hour_ = hour;
155  this->minute_ = minute;
156  this->second_ = second;
157  return *this;
158 };
159 
161  return this->set_datetime(datetime.year, datetime.month, datetime.day_of_month, datetime.hour, datetime.minute,
162  datetime.second);
163 };
164 
165 DateTimeCall &DateTimeCall::set_datetime(const std::string &datetime) {
166  ESPTime val{};
167  if (!ESPTime::strptime(datetime, val)) {
168  ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
169  return *this;
170  }
171  return this->set_datetime(val);
172 }
173 
174 DateTimeCall &DateTimeCall::set_datetime(time_t epoch_seconds) {
175  ESPTime val = ESPTime::from_epoch_local(epoch_seconds);
176  return this->set_datetime(val);
177 }
178 
180  DateTimeCall call = datetime->make_call();
181  call.set_datetime(this->year, this->month, this->day, this->hour, this->minute, this->second);
182  return call;
183 }
184 
186  time->year_ = this->year;
187  time->month_ = this->month;
188  time->day_ = this->day;
189  time->hour_ = this->hour;
190  time->minute_ = this->minute;
191  time->second_ = this->second;
192  time->publish_state();
193 }
194 
195 #ifdef USE_TIME
196 static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
197  // there has been a drastic time synchronization
198 
200  if (!this->parent_->has_state()) {
201  return;
202  }
203  ESPTime time = this->parent_->rtc_->now();
204  if (!time.is_valid()) {
205  return;
206  }
207  if (this->last_check_.has_value()) {
208  if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > MAX_TIMESTAMP_DRIFT) {
209  // We went back in time (a lot), probably caused by time synchronization
210  ESP_LOGW(TAG, "Time has jumped back!");
211  } else if (*this->last_check_ >= time) {
212  // already handled this one
213  return;
214  } else if (time > *this->last_check_ && time.timestamp - this->last_check_->timestamp > MAX_TIMESTAMP_DRIFT) {
215  // We went ahead in time (a lot), probably caused by time synchronization
216  ESP_LOGW(TAG, "Time has jumped ahead!");
217  this->last_check_ = time;
218  return;
219  }
220 
221  while (true) {
222  this->last_check_->increment_second();
223  if (*this->last_check_ >= time)
224  break;
225 
226  if (this->matches_(*this->last_check_)) {
227  this->trigger();
228  break;
229  }
230  }
231  }
232 
233  this->last_check_ = time;
234  if (!time.fields_in_range()) {
235  ESP_LOGW(TAG, "Time is out of range!");
236  ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u Day=%02u Month=%02u Year=%04u", time.second, time.minute,
237  time.hour, time.day_of_month, time.month, time.year);
238  }
239 
240  if (this->matches_(time))
241  this->trigger();
242 }
243 
244 bool OnDateTimeTrigger::matches_(const ESPTime &time) const {
245  return time.is_valid() && time.year == this->parent_->year && time.month == this->parent_->month &&
246  time.day_of_month == this->parent_->day && time.hour == this->parent_->hour &&
247  time.minute == this->parent_->minute && time.second == this->parent_->second;
248 }
249 #endif
250 
251 } // namespace datetime
252 } // namespace esphome
253 
254 #endif // USE_DATETIME_TIME
A more user-friendly version of struct tm from time.h.
Definition: time.h:17
mopeka_std_values val[4]
uint16_t day_of_year
day of the year [1-366]
Definition: time.h:31
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:10
static ESPTime from_epoch_local(time_t epoch)
Convert an UTC epoch timestamp to a local time ESPTime instance.
Definition: time.h:85
uint8_t second
seconds after the minute [0-60]
Definition: time.h:21
time_t timestamp
unix epoch time (seconds since UTC Midnight January 1, 1970)
Definition: time.h:39
uint8_t minute
minutes after the hour [0-59]
Definition: time.h:23
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:61
uint8_t day_of_week
day of the week; sunday=1 [1-7]
Definition: time.h:27
uint16_t year
year
Definition: time.h:35
ESPTime state_as_esptime() const override
void recalc_timestamp_local(bool use_day_of_year=true)
Recalculate the timestamp field from the other fields of this ESPTime instance assuming local fields...
Definition: time.cpp:191
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:33
uint8_t hour
hours since midnight [0-23]
Definition: time.h:25
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:29
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:64
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