support_rtc.ino 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. /*
  2. support_rtc.ino - Real Time Clock support for Sonoff-Tasmota
  3. Copyright (C) 2018 Theo Arends
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. /*********************************************************************************************\
  16. * Sources: Time by Michael Margolis and Paul Stoffregen (https://github.com/PaulStoffregen/Time)
  17. * Timezone by Jack Christensen (https://github.com/JChristensen/Timezone)
  18. \*********************************************************************************************/
  19. #define SECS_PER_MIN ((uint32_t)(60UL))
  20. #define SECS_PER_HOUR ((uint32_t)(3600UL))
  21. #define SECS_PER_DAY ((uint32_t)(SECS_PER_HOUR * 24UL))
  22. #define MINS_PER_HOUR ((uint32_t)(60UL))
  23. #define LEAP_YEAR(Y) (((1970+Y)>0) && !((1970+Y)%4) && (((1970+Y)%100) || !((1970+Y)%400)))
  24. extern "C" {
  25. #include "sntp.h"
  26. }
  27. #include <Ticker.h>
  28. Ticker TickerRtc;
  29. static const uint8_t kDaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // API starts months from 1, this array starts from 0
  30. static const char kMonthNamesEnglish[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
  31. uint32_t utc_time = 0;
  32. uint32_t local_time = 0;
  33. uint32_t daylight_saving_time = 0;
  34. uint32_t standard_time = 0;
  35. uint32_t ntp_time = 0;
  36. uint32_t midnight = 1451602800;
  37. uint32_t restart_time = 0;
  38. int32_t time_timezone = 0;
  39. uint8_t midnight_now = 0;
  40. uint8_t ntp_sync_minute = 0;
  41. String GetBuildDateAndTime(void)
  42. {
  43. // "2017-03-07T11:08:02" - ISO8601:2004
  44. char bdt[21];
  45. char *p;
  46. char mdate[] = __DATE__; // "Mar 7 2017"
  47. char *smonth = mdate;
  48. int day = 0;
  49. int year = 0;
  50. // sscanf(mdate, "%s %d %d", bdt, &day, &year); // Not implemented in 2.3.0 and probably too much code
  51. byte i = 0;
  52. for (char *str = strtok_r(mdate, " ", &p); str && i < 3; str = strtok_r(NULL, " ", &p)) {
  53. switch (i++) {
  54. case 0: // Month
  55. smonth = str;
  56. break;
  57. case 1: // Day
  58. day = atoi(str);
  59. break;
  60. case 2: // Year
  61. year = atoi(str);
  62. }
  63. }
  64. int month = (strstr(kMonthNamesEnglish, smonth) -kMonthNamesEnglish) /3 +1;
  65. snprintf_P(bdt, sizeof(bdt), PSTR("%d" D_YEAR_MONTH_SEPARATOR "%02d" D_MONTH_DAY_SEPARATOR "%02d" D_DATE_TIME_SEPARATOR "%s"), year, month, day, __TIME__);
  66. return String(bdt); // 2017-03-07T11:08:02
  67. }
  68. String GetTimeZone(void)
  69. {
  70. char tz[7];
  71. snprintf_P(tz, sizeof(tz), PSTR("%+03d:%02d"), time_timezone / 60, abs(time_timezone % 60));
  72. return String(tz); // -03:45
  73. }
  74. String GetDT(uint32_t time)
  75. {
  76. // "2017-03-07T11:08:02" - ISO8601:2004
  77. char dt[20];
  78. TIME_T tmpTime;
  79. BreakTime(time, tmpTime);
  80. snprintf_P(dt, sizeof(dt), PSTR("%04d-%02d-%02dT%02d:%02d:%02d"),
  81. tmpTime.year +1970, tmpTime.month, tmpTime.day_of_month, tmpTime.hour, tmpTime.minute, tmpTime.second);
  82. return String(dt); // 2017-03-07T11:08:02
  83. }
  84. /*
  85. * timestamps in https://en.wikipedia.org/wiki/ISO_8601 format
  86. *
  87. * DT_UTC - current data and time in Greenwich, England (aka GMT)
  88. * DT_LOCAL - current date and time taking timezone into account
  89. * DT_RESTART - the date and time this device last started, in local timezone
  90. *
  91. * Format:
  92. * "2017-03-07T11:08:02-07:00" - if DT_LOCAL and SetOption52 = 1
  93. * "2017-03-07T11:08:02" - otherwise
  94. */
  95. String GetDateAndTime(byte time_type)
  96. {
  97. // "2017-03-07T11:08:02-07:00" - ISO8601:2004
  98. uint32_t time = local_time;
  99. switch (time_type) {
  100. case DT_ENERGY:
  101. time = Settings.energy_kWhtotal_time;
  102. break;
  103. case DT_UTC:
  104. time = utc_time;
  105. break;
  106. case DT_RESTART:
  107. if (restart_time == 0) {
  108. return "";
  109. }
  110. time = restart_time;
  111. break;
  112. }
  113. String dt = GetDT(time); // 2017-03-07T11:08:02
  114. if (Settings.flag3.time_append_timezone && (DT_LOCAL == time_type)) {
  115. dt += GetTimeZone(); // 2017-03-07T11:08:02-07:00
  116. }
  117. return dt; // 2017-03-07T11:08:02-07:00
  118. }
  119. String GetTime(int type)
  120. {
  121. /* type 1 - Local time
  122. * type 2 - Daylight Savings time
  123. * type 3 - Standard time
  124. */
  125. char stime[25]; // Skip newline
  126. uint32_t time = utc_time;
  127. if (1 == type) time = local_time;
  128. if (2 == type) time = daylight_saving_time;
  129. if (3 == type) time = standard_time;
  130. snprintf_P(stime, sizeof(stime), sntp_get_real_time(time));
  131. return String(stime); // Thu Nov 01 11:41:02 2018
  132. }
  133. String GetUptime(void)
  134. {
  135. char dt[16];
  136. TIME_T ut;
  137. if (restart_time) {
  138. BreakTime(utc_time - restart_time, ut);
  139. } else {
  140. BreakTime(uptime, ut);
  141. }
  142. // "P128DT14H35M44S" - ISO8601:2004 - https://en.wikipedia.org/wiki/ISO_8601 Durations
  143. // snprintf_P(dt, sizeof(dt), PSTR("P%dDT%02dH%02dM%02dS"), ut.days, ut.hour, ut.minute, ut.second);
  144. // "128 14:35:44" - OpenVMS
  145. // "128T14:35:44" - Tasmota
  146. snprintf_P(dt, sizeof(dt), PSTR("%dT%02d:%02d:%02d"), ut.days, ut.hour, ut.minute, ut.second);
  147. return String(dt); // 128T14:35:44
  148. }
  149. uint32_t GetMinutesUptime(void)
  150. {
  151. TIME_T ut;
  152. if (restart_time) {
  153. BreakTime(utc_time - restart_time, ut);
  154. } else {
  155. BreakTime(uptime, ut);
  156. }
  157. return (ut.days *1440) + (ut.hour *60) + ut.minute;
  158. }
  159. uint32_t GetMinutesPastMidnight(void)
  160. {
  161. uint32_t minutes = 0;
  162. if (RtcTime.valid) {
  163. minutes = (RtcTime.hour *60) + RtcTime.minute;
  164. }
  165. return minutes;
  166. }
  167. void BreakTime(uint32_t time_input, TIME_T &tm)
  168. {
  169. // break the given time_input into time components
  170. // this is a more compact version of the C library localtime function
  171. // note that year is offset from 1970 !!!
  172. uint8_t year;
  173. uint8_t month;
  174. uint8_t month_length;
  175. uint32_t time;
  176. unsigned long days;
  177. time = time_input;
  178. tm.second = time % 60;
  179. time /= 60; // now it is minutes
  180. tm.minute = time % 60;
  181. time /= 60; // now it is hours
  182. tm.hour = time % 24;
  183. time /= 24; // now it is days
  184. tm.days = time;
  185. tm.day_of_week = ((time + 4) % 7) + 1; // Sunday is day 1
  186. year = 0;
  187. days = 0;
  188. while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
  189. year++;
  190. }
  191. tm.year = year; // year is offset from 1970
  192. days -= LEAP_YEAR(year) ? 366 : 365;
  193. time -= days; // now it is days in this year, starting at 0
  194. tm.day_of_year = time;
  195. days = 0;
  196. month = 0;
  197. month_length = 0;
  198. for (month = 0; month < 12; month++) {
  199. if (1 == month) { // february
  200. if (LEAP_YEAR(year)) {
  201. month_length = 29;
  202. } else {
  203. month_length = 28;
  204. }
  205. } else {
  206. month_length = kDaysInMonth[month];
  207. }
  208. if (time >= month_length) {
  209. time -= month_length;
  210. } else {
  211. break;
  212. }
  213. }
  214. strlcpy(tm.name_of_month, kMonthNames + (month *3), 4);
  215. tm.month = month + 1; // jan is month 1
  216. tm.day_of_month = time + 1; // day of month
  217. tm.valid = (time_input > 1451602800); // 2016-01-01
  218. }
  219. uint32_t MakeTime(TIME_T &tm)
  220. {
  221. // assemble time elements into time_t
  222. // note year argument is offset from 1970
  223. int i;
  224. uint32_t seconds;
  225. // seconds from 1970 till 1 jan 00:00:00 of the given year
  226. seconds = tm.year * (SECS_PER_DAY * 365);
  227. for (i = 0; i < tm.year; i++) {
  228. if (LEAP_YEAR(i)) {
  229. seconds += SECS_PER_DAY; // add extra days for leap years
  230. }
  231. }
  232. // add days for this year, months start from 1
  233. for (i = 1; i < tm.month; i++) {
  234. if ((2 == i) && LEAP_YEAR(tm.year)) {
  235. seconds += SECS_PER_DAY * 29;
  236. } else {
  237. seconds += SECS_PER_DAY * kDaysInMonth[i-1]; // monthDay array starts from 0
  238. }
  239. }
  240. seconds+= (tm.day_of_month - 1) * SECS_PER_DAY;
  241. seconds+= tm.hour * SECS_PER_HOUR;
  242. seconds+= tm.minute * SECS_PER_MIN;
  243. seconds+= tm.second;
  244. return seconds;
  245. }
  246. uint32_t RuleToTime(TimeRule r, int yr)
  247. {
  248. TIME_T tm;
  249. uint32_t t;
  250. uint8_t m;
  251. uint8_t w; // temp copies of r.month and r.week
  252. m = r.month;
  253. w = r.week;
  254. if (0 == w) { // Last week = 0
  255. if (++m > 12) { // for "Last", go to the next month
  256. m = 1;
  257. yr++;
  258. }
  259. w = 1; // and treat as first week of next month, subtract 7 days later
  260. }
  261. tm.hour = r.hour;
  262. tm.minute = 0;
  263. tm.second = 0;
  264. tm.day_of_month = 1;
  265. tm.month = m;
  266. tm.year = yr - 1970;
  267. t = MakeTime(tm); // First day of the month, or first day of next month for "Last" rules
  268. BreakTime(t, tm);
  269. t += (7 * (w - 1) + (r.dow - tm.day_of_week + 7) % 7) * SECS_PER_DAY;
  270. if (0 == r.week) {
  271. t -= 7 * SECS_PER_DAY; // back up a week if this is a "Last" rule
  272. }
  273. return t;
  274. }
  275. uint32_t LocalTime(void)
  276. {
  277. return local_time;
  278. }
  279. uint32_t Midnight(void)
  280. {
  281. return midnight;
  282. }
  283. boolean MidnightNow(void)
  284. {
  285. boolean mnflg = midnight_now;
  286. if (mnflg) midnight_now = 0;
  287. return mnflg;
  288. }
  289. void RtcSecond(void)
  290. {
  291. TIME_T tmpTime;
  292. if ((ntp_sync_minute > 59) && (RtcTime.minute > 2)) ntp_sync_minute = 1; // If sync prepare for a new cycle
  293. uint8_t offset = (uptime < 30) ? RtcTime.second : (((ESP.getChipId() & 0xF) * 3) + 3) ; // First try ASAP to sync. If fails try once every 60 seconds based on chip id
  294. if (!global_state.wifi_down && (offset == RtcTime.second) && ((RtcTime.year < 2016) || (ntp_sync_minute == RtcTime.minute) || ntp_force_sync)) {
  295. ntp_time = sntp_get_current_timestamp();
  296. if (ntp_time > 1451602800) { // Fix NTP bug in core 2.4.1/SDK 2.2.1 (returns Thu Jan 01 08:00:10 1970 after power on)
  297. ntp_force_sync = 0;
  298. utc_time = ntp_time;
  299. ntp_sync_minute = 60; // Sync so block further requests
  300. if (restart_time == 0) {
  301. restart_time = utc_time - uptime; // save first ntp time as restart time
  302. }
  303. BreakTime(utc_time, tmpTime);
  304. RtcTime.year = tmpTime.year + 1970;
  305. daylight_saving_time = RuleToTime(Settings.tflag[1], RtcTime.year);
  306. standard_time = RuleToTime(Settings.tflag[0], RtcTime.year);
  307. snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION "(" D_UTC_TIME ") %s, (" D_DST_TIME ") %s, (" D_STD_TIME ") %s"),
  308. GetTime(0).c_str(), GetTime(2).c_str(), GetTime(3).c_str());
  309. AddLog(LOG_LEVEL_DEBUG);
  310. if (local_time < 1451602800) { // 2016-01-01
  311. rules_flag.time_init = 1;
  312. } else {
  313. rules_flag.time_set = 1;
  314. }
  315. } else {
  316. ntp_sync_minute++; // Try again in next minute
  317. }
  318. }
  319. utc_time++;
  320. local_time = utc_time;
  321. if (local_time > 1451602800) { // 2016-01-01
  322. int16_t timezone_minutes = Settings.timezone_minutes;
  323. if (Settings.timezone < 0) { timezone_minutes *= -1; }
  324. time_timezone = (Settings.timezone * SECS_PER_HOUR) + (timezone_minutes * SECS_PER_MIN);
  325. if (99 == Settings.timezone) {
  326. int32_t dstoffset = Settings.toffset[1] * SECS_PER_MIN;
  327. int32_t stdoffset = Settings.toffset[0] * SECS_PER_MIN;
  328. if (Settings.tflag[1].hemis) {
  329. // Southern hemisphere
  330. if ((utc_time >= (standard_time - dstoffset)) && (utc_time < (daylight_saving_time - stdoffset))) {
  331. time_timezone = stdoffset; // Standard Time
  332. } else {
  333. time_timezone = dstoffset; // Daylight Saving Time
  334. }
  335. } else {
  336. // Northern hemisphere
  337. if ((utc_time >= (daylight_saving_time - stdoffset)) && (utc_time < (standard_time - dstoffset))) {
  338. time_timezone = dstoffset; // Daylight Saving Time
  339. } else {
  340. time_timezone = stdoffset; // Standard Time
  341. }
  342. }
  343. }
  344. local_time += time_timezone;
  345. time_timezone /= 60;
  346. if (!Settings.energy_kWhtotal_time) { Settings.energy_kWhtotal_time = local_time; }
  347. }
  348. BreakTime(local_time, RtcTime);
  349. if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second && RtcTime.valid) {
  350. midnight = local_time;
  351. midnight_now = 1;
  352. }
  353. RtcTime.year += 1970;
  354. }
  355. void RtcInit(void)
  356. {
  357. sntp_setservername(0, Settings.ntp_server[0]);
  358. sntp_setservername(1, Settings.ntp_server[1]);
  359. sntp_setservername(2, Settings.ntp_server[2]);
  360. sntp_stop();
  361. sntp_set_timezone(0); // UTC time
  362. sntp_init();
  363. utc_time = 0;
  364. BreakTime(utc_time, RtcTime);
  365. TickerRtc.attach(1, RtcSecond);
  366. }