]> the.earth.li Git - esp8266-clock.git/blob - clock.c
Switch NTP to update hourly rather than only wifi connect
[esp8266-clock.git] / clock.c
1 /*
2  * Copyright 2017 Jonathan McDowell <noodles@earth.li>
3  *
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  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  * NTP code based on https://github.com/raburton/esp8266/tree/master/ntp
18  * Those portions MIT licensed:
19  *
20  * Copyright (c) 2015 Richard A Burton (richardaburton@gmail.com)
21  *
22  * Permission is hereby granted, free of charge, to any person obtaining a copy
23  * of this software and associated documentation files (the "Software"), to deal
24  * in the Software without restriction, including without limitation the rights
25  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
26  * copies of the Software, and to permit persons to whom the Software is
27  * furnished to do so, subject to the following conditions:
28  *
29  * The above copyright notice and this permission notice shall be included in
30  * all copies or substantial portions of the Software.
31  *
32  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
38  * THE SOFTWARE.
39  */
40 #include <stdint.h>
41
42 #include <user_interface.h>
43 #include <espconn.h>
44 #include <mem.h>
45 #include <osapi.h>
46
47 #include "clock.h"
48
49 #define NTP_SERVER     "uk.pool.ntp.org"
50 #define NTP_TIMEOUT_MS 5000
51
52 static uint32_t sys_last_ticks;
53 static uint32_t sys_delta;
54 static os_timer_t ntp_timeout;
55
56 static ip_addr_t ntp_server_ip;
57
58 /* See RFC5905 7.3 */
59 typedef struct {
60         uint8 options;
61         uint8 stratum;
62         uint8 poll;
63         uint8 precision;
64         uint32 root_delay;
65         uint32 root_disp;
66         uint32 ref_id;
67         uint8 ref_time[8];
68         uint8 orig_time[8];
69         uint8 recv_time[8];
70         uint8 trans_time[8];
71 } ntp_t;
72
73 void ICACHE_FLASH_ATTR set_time(uint32_t now)
74 {
75         sys_last_ticks = system_get_time();
76         sys_delta = now - (sys_last_ticks / 1000000);
77 }
78
79 uint32_t ICACHE_FLASH_ATTR get_time(void)
80 {
81         uint32_t sys_ticks;
82
83         sys_ticks = system_get_time();
84         if (sys_ticks < sys_last_ticks) {
85                 sys_delta += (1ULL << 32ULL) / 1000000;
86         }
87         sys_last_ticks = sys_ticks;
88
89         return sys_ticks / 1000000 + sys_delta;
90 }
91
92 bool ICACHE_FLASH_ATTR is_leap(uint32_t year)
93 {
94         return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);
95 }
96
97 bool ICACHE_FLASH_ATTR is_dst(struct tm *time)
98 {
99         int lastsun = time->tm_mday - time->tm_wday;
100
101         if (time->tm_mon < 2 || time->tm_mon > 9)
102                 return false;
103         if (time->tm_mon > 2 && time->tm_mon < 9)
104                 return true;
105
106         /*
107          * Starts last Sunday in March, ends last Sunday in October, which must
108          * be at least the 25th of the month. So must be past that in March, or
109          * before that in October, to be in DST.
110          */
111         if (time->tm_mon == 2)
112                 return (lastsun >= 25);
113         if (time->tm_mon == 9)
114                 return (lastsun < 25);
115
116         return false;
117 }
118
119 /*
120  * Takes time, a Unix time (seconds since 1st Jan 1970) and breaks it down to:
121  *
122  * Time:
123  *   tm_sec     0-59
124  *   tm_min     0-59
125  *   tm_hour    0-23
126  *
127  * Date:
128  *   tm_year
129  *   tm_mon     0-11
130  *   tm_mday    1-31
131  *
132  *   tm_yday
133  *   tm_wday    Sunday = 0, Saturday = 6
134  *
135  */
136 void ICACHE_FLASH_ATTR breakdown_time(uint32_t time, struct tm *result)
137 {
138         uint32_t era, doe, yoe, mp;
139
140         /* Do the time component */
141         result->tm_sec = time % 60;
142         time /= 60;
143         result->tm_min = time % 60;
144         time /= 60;
145         result->tm_hour = time % 24;
146         time /= 24;
147
148         /* Now time is the number of days since 1970-01-01 (a Thursday) */
149         result->tm_wday = (time + 4) % 7;
150
151         /* Below from http://howardhinnant.github.io/date_algorithms.html */
152         time += 719468;
153         era = time / 146097;
154         doe = time - era * 146097;
155         yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
156
157         result->tm_year = yoe + era * 400;
158         result->tm_yday = doe - (365 * yoe + yoe / 4 - yoe / 100);
159         mp = (5 * result->tm_yday + 2) / 153;
160         result->tm_mday = result->tm_yday - (153 * mp + 2) / 5 + 1;
161         result->tm_mon = mp + (mp < 10 ? 2 : -10);
162         if (result->tm_mon <= 2)
163                 result->tm_year++;
164
165         /* result->tm_yday is March 1st indexed at this point; fix up */
166         result->tm_yday += 28 + 31;
167         if (is_leap(result->tm_year))
168                 result->tm_yday++;
169
170         result->tm_isdst = is_dst(result);
171         if (result->tm_isdst)
172                 result->tm_hour++;
173         if (result->tm_hour > 23) {
174                 /*
175                  * We can ignore fixing up the date at the end of month etc.
176                  * because all we're actually displaying is the time.
177                  */
178                 result->tm_hour = 0;
179                 result->tm_wday++;
180                 result->tm_mday++;
181                 result->tm_yday++;
182         }
183 }
184
185 static void ICACHE_FLASH_ATTR ntp_udp_timeout(void *arg)
186 {
187         struct espconn *pCon = (struct espconn *) arg;
188
189         os_timer_disarm(&ntp_timeout);
190         os_printf("NTP timeout.\n");
191
192         // clean up connection
193         if (pCon) {
194                 espconn_delete(pCon);
195                 os_free(pCon->proto.udp);
196                 os_free(pCon);
197                 pCon = 0;
198         }
199 }
200
201 static void ICACHE_FLASH_ATTR ntp_udp_recv(void *arg, char *pdata,
202         unsigned short len)
203 {
204         struct espconn *pCon = (struct espconn *) arg;
205         uint32_t timestamp;
206         ntp_t *ntp;
207         struct tm dt;
208
209         os_printf("Got NTP response.\n");
210
211         os_timer_disarm(&ntp_timeout);
212
213         // Extract NTP time
214         ntp = (ntp_t *) pdata;
215         timestamp = ntp->trans_time[0] << 24 | ntp->trans_time[1] << 16 |
216                 ntp->trans_time[2] << 8 | ntp->trans_time[3];
217         // NTP 0 is 1st Jan 1900; convert to Unix time 0 of 1st Jan 1970
218         timestamp -= 2208988800ULL;
219
220         // Store the time
221         set_time(timestamp);
222
223         // Print it out
224         breakdown_time(timestamp, &dt);
225         os_printf("%04d-%02d-%02d %02d:%02d:%02d (%u)\r\n",
226                 dt.tm_year, dt.tm_mon + 1, dt.tm_mday,
227                 dt.tm_hour, dt.tm_min, dt.tm_sec, timestamp);
228
229         // clean up connection
230         if (pCon) {
231                 espconn_delete(pCon);
232                 os_free(pCon->proto.udp);
233                 os_free(pCon);
234                 pCon = NULL;
235         }
236 }
237
238 void ICACHE_FLASH_ATTR ntp_got_dns(const char *name, ip_addr_t *ip, void *arg)
239 {
240         ntp_t ntp;
241         struct espconn *pCon = (struct espconn *) arg;
242
243         if (ip == NULL) {
244                 os_printf("NTP DNS request failed.\n");
245                 os_free(pCon);
246                 return;
247         }
248
249         os_printf("Sending NTP request.\n");
250
251         // Set up the UDP "connection"
252         pCon->type = ESPCONN_UDP;
253         pCon->state = ESPCONN_NONE;
254         pCon->proto.udp = (esp_udp *) os_zalloc(sizeof(esp_udp));
255         pCon->proto.udp->local_port = espconn_port();
256         pCon->proto.udp->remote_port = 123;
257         os_memcpy(pCon->proto.udp->remote_ip, &ip->addr, 4);
258
259         // Create a really simple NTP request packet
260         os_memset(&ntp, 0, sizeof(ntp_t));
261         ntp.options = 0b00100011; // leap = 0, version = 4, mode = 3 (client)
262
263         // Set timeout timer
264         os_timer_disarm(&ntp_timeout);
265         os_timer_setfn(&ntp_timeout, (os_timer_func_t*) ntp_udp_timeout, pCon);
266         os_timer_arm(&ntp_timeout, NTP_TIMEOUT_MS, 0);
267
268         // Send the NTP request
269         espconn_create(pCon);
270         espconn_regist_recvcb(pCon, ntp_udp_recv);
271         espconn_sent(pCon, (uint8_t *) &ntp, sizeof(ntp_t));
272 }
273
274 void ICACHE_FLASH_ATTR ntp_get_time(void)
275 {
276         struct espconn *pCon = NULL;
277
278         os_printf("Sending DNS request for NTP server.\n");
279         pCon = (struct espconn *) os_zalloc(sizeof(struct espconn));
280         espconn_gethostbyname(pCon, NTP_SERVER, &ntp_server_ip, ntp_got_dns);
281 }
282
283 void ICACHE_FLASH_ATTR rtc_init(void)
284 {
285         sys_last_ticks = system_get_time();
286 }