]> the.earth.li Git - esp8266-clock.git/blob - clock.c
Update NTP code to use DNS rather than hard coded IP
[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 "espmissingincludes.h"
48
49 #include "clock.h"
50
51 #define NTP_SERVER     "uk.pool.ntp.org"
52 #define NTP_TIMEOUT_MS 5000
53
54 static uint32_t sys_last_ticks;
55 static uint32_t sys_delta;
56 static os_timer_t ntp_timeout;
57
58 static ip_addr_t ntp_server_ip;
59
60 typedef struct {
61         uint8 options;
62         uint8 stratum;
63         uint8 poll;
64         uint8 precision;
65         uint32 root_delay;
66         uint32 root_disp;
67         uint32 ref_id;
68         uint8 ref_time[8];
69         uint8 orig_time[8];
70         uint8 recv_time[8];
71         uint8 trans_time[8];
72 } ntp_t;
73
74 void ICACHE_FLASH_ATTR set_time(uint32_t now)
75 {
76         sys_last_ticks = system_get_time();
77         sys_delta = now - (sys_last_ticks / 1000000);
78 }
79
80 uint32_t ICACHE_FLASH_ATTR get_time(void)
81 {
82         uint32_t sys_ticks;
83
84         sys_ticks = system_get_time();
85         if (sys_ticks < sys_last_ticks) {
86                 sys_delta += (1ULL << 32ULL) / 1000000;
87         }
88         sys_last_ticks = sys_ticks;
89
90         return sys_ticks / 1000000 + sys_delta;
91 }
92
93 void ICACHE_FLASH_ATTR breakdown_time(uint32_t time, struct tm *result)
94 {
95         result->tm_sec = time % 60;
96         time /= 60;
97         result->tm_min = time % 60;
98         time /= 60;
99         result->tm_hour = time % 24;
100         time /= 24;
101
102         result->tm_year = time / (365 * 4 + 1) * 4 + 70;
103         time %= 365 * 4 + 1;
104 }
105
106 static void ICACHE_FLASH_ATTR ntp_udp_timeout(void *arg)
107 {
108         struct espconn *pCon = (struct espconn *) arg;
109
110         os_timer_disarm(&ntp_timeout);
111         os_printf("NTP timeout.\n");
112
113         // clean up connection
114         if (pCon) {
115                 espconn_delete(pCon);
116                 os_free(pCon->proto.udp);
117                 os_free(pCon);
118                 pCon = 0;
119         }
120 }
121
122 static void ICACHE_FLASH_ATTR ntp_udp_recv(void *arg, char *pdata,
123         unsigned short len)
124 {
125         struct espconn *pCon = (struct espconn *) arg;
126         uint32_t timestamp;
127         ntp_t *ntp;
128         struct tm dt;
129
130         os_printf("Got NTP response.\n");
131
132         os_timer_disarm(&ntp_timeout);
133
134         // Extract NTP time
135         ntp = (ntp_t *) pdata;
136         timestamp = ntp->trans_time[0] << 24 | ntp->trans_time[1] << 16 |
137                 ntp->trans_time[2] << 8 | ntp->trans_time[3];
138         // Convert to Unix time ms
139         timestamp -= 2208988800ULL;
140
141         // Store the time
142         set_time(timestamp);
143
144         // Print it out
145         breakdown_time(timestamp, &dt);
146         os_printf("%04d %02d:%02d:%02d (%u)\r\n", dt.tm_year, dt.tm_hour,
147                 dt.tm_min, dt.tm_sec, timestamp);
148
149         // clean up connection
150         if (pCon) {
151                 espconn_delete(pCon);
152                 os_free(pCon->proto.udp);
153                 os_free(pCon);
154                 pCon = NULL;
155         }
156 }
157
158 void ICACHE_FLASH_ATTR ntp_got_dns(const char *name, ip_addr_t *ip, void *arg)
159 {
160         ntp_t ntp;
161         struct espconn *pCon = (struct espconn *) arg;
162
163         if (ip == NULL) {
164                 os_printf("NTP DNS request failed.\n");
165                 os_free(pCon);
166                 return;
167         }
168
169         os_printf("Sending NTP request.\n");
170
171         // Set up the UDP "connection"
172         pCon->type = ESPCONN_UDP;
173         pCon->state = ESPCONN_NONE;
174         pCon->proto.udp = (esp_udp *) os_zalloc(sizeof(esp_udp));
175         pCon->proto.udp->local_port = espconn_port();
176         pCon->proto.udp->remote_port = 123;
177         os_memcpy(pCon->proto.udp->remote_ip, &ip->addr, 4);
178
179         // Create a really simple NTP request packet
180         os_memset(&ntp, 0, sizeof(ntp_t));
181         ntp.options = 0b00100011; // leap = 0, version = 4, mode = 3 (client)
182
183         // Set timeout timer
184         os_timer_disarm(&ntp_timeout);
185         os_timer_setfn(&ntp_timeout, (os_timer_func_t*) ntp_udp_timeout, pCon);
186         os_timer_arm(&ntp_timeout, NTP_TIMEOUT_MS, 0);
187
188         // Send the NTP request
189         espconn_create(pCon);
190         espconn_regist_recvcb(pCon, ntp_udp_recv);
191         espconn_sent(pCon, (uint8_t *) &ntp, sizeof(ntp_t));
192 }
193
194 void ICACHE_FLASH_ATTR ntp_get_time(void)
195 {
196         struct espconn *pCon = NULL;
197
198         os_printf("Sending DNS request for NTP server.\n");
199         pCon = (struct espconn *) os_zalloc(sizeof(struct espconn));
200         espconn_gethostbyname(pCon, NTP_SERVER, &ntp_server_ip, ntp_got_dns);
201 }
202
203 void ICACHE_FLASH_ATTR rtc_init(void)
204 {
205         sys_last_ticks = system_get_time();
206 }