SingingCat 0
application
sc_time.c
1#include "main-header.h"
2#include "sc_time.h"
3#include "mculibrary.h"
4#include "config.h"
5
6#define TIME_REQUEST_SYNC_INTERVAL 300
7// we can go for some time without sync before things break. after this time we refuse to give out time because chances are, it is just "too wrong"
8#define TIME_MAX_AGE_NO_SYNC (60 * 60 * 24 * 2)
9
10static long appstarted;
11static uint8_t daytime;
12static uint32_t last_time_stamp; // timestamp as received (4 bytes: HHMMSSWD WD=WEEKDAY)
13static uint32_t last_date_stamp; // date as received (4 bytes: YYYYMMDD)
14static uint32_t last_time_sync_req = 0; // timestamp sync was last requested
15static uint32_t last_time_sync = 0; // timestamp when time was last synced
16
17static uint32_t old_last_time_sync = 0; // timestamp when time was last synced, but not updated as often
18static uint32_t old_last_time_stamp; // timestamp as received (4 bytes: HHMMSSWD WD=WEEKDAY)
19static uint32_t old_last_date_stamp; // date as received (4 bytes: YYYYMMDD)
20static int32_t old_last_drift;
21static struct sctime old_time;
22static uint32_t last_published_time = 0;
23static int adj_time = 0; // time "per second" adjusted, multiplied by 100
24static uint32_t ltd = 0; // time diff since last update (to be used in conjunction with adj_time)
25
26
27static int get_time_as_struct(struct sctime *time, uint32_t l_time_sync, uint32_t l_time_stamp, uint32_t l_date_stamp);
28// set a new time
29static void time_set_new_from_sync(uint32_t received, uint32_t bcdtime, uint32_t bcddate, uint32_t temp_override);
30static void add_seconds_to_timestruct(struct sctime *nt, uint32_t add_secs);
31
32
33
34void check_time_on_loop() {
35 uint32_t now = mculib_get_seconds_since_boot();
36
37 if (last_time_sync == 0) {
38 if (get_seconds_since_start() < 60) {
39 // wait a bit after starting before getting time..
40 return;
41 }
42 } else {
43 if ((now - last_time_sync < TIME_REQUEST_SYNC_INTERVAL) && (now >= last_time_sync)) {
44 return;
45 }
46 }
47 // only ask for sync at most every 5 seconds
48 if ((now - last_time_sync_req <= 5) && (now >= last_time_sync_req)) {
49 return;
50 }
51 send_clock_sync_request();
52}
53
54// returns true (1) if disabled
55static int sync_disabled() {
56 if ((last_time_sync != 0) && (config_get_flag(CONFIG_FLAGS_CLOCKSYNC_DISABLED))) {
57 return 1;
58 }
59 return 0;
60}
61void send_clock_sync_request() {
62 if (sync_disabled()) {
63 return;
64 }
65 uint32_t now = mculib_get_seconds_since_boot();
66
67 printf("[time] sending sync request...\r\n");
68 struct command *com = alloc_command();
69
70 if (com == NULL) {
71 return;
72 }
73 com->target = CLOUD_SERVER;
74 com->com = 59; // clock-sync
75 last_time_sync_req = now;
76 command_add_arg(com, "ls=%i", last_time_sync);
77 command_add_arg(com, "sss=%i", get_seconds_since_start());
78 command_add_arg(com, "ssb=%i", now);
79 command_add_arg(com, "tz=%i", config_get_timezone());
80 time_add_debug_to_command(com);
81 int n = send_command(com);
82
84 if (n != 0) {
85 printf("[time] failed to send sync request: %i\r\n", n);
86 return;
87 }
88}
89
90// if temp_override is set, then do it even if disabled (server overrides)
91static void time_set_new_from_sync(uint32_t received, uint32_t bcdtime, uint32_t bcddate, uint32_t temp_override) {
92 struct sctime nt1, nt2;
93 int n = get_localtime_as_struct(&nt1);
94 uint32_t lt1 = last_time_sync;
95
96 if ((temp_override) || (!sync_disabled())) {
97 last_time_sync = received;
98 last_time_stamp = bcdtime;
99 last_date_stamp = bcddate;
100 }
101
102 // (re)set the "old" timestamp
103 if ((old_last_time_sync == 0) || ((received - old_last_time_sync) > 60 * 60 * 4)) {
104 old_last_time_sync = received;
105 old_last_time_stamp = bcdtime;
106 old_last_date_stamp = bcddate;
107 }
108 uint32_t lt2 = received;
109
110 if (n != 0) {
111 printf("[time] not calculating drift because cannot calc time on old time\r\n");
112 return;
113 }
114 // nt2 is now the _new_time
115 n = get_time_as_struct(&nt2, received, bcdtime, bcddate);
116 if (n != 0) {
117 printf("[time] not calculating drift because cannot calc time on new time\r\n");
118 return;
119 }
120 if ((nt1.month != nt2.month) || (nt1.month != nt2.month)) {
121 // don't even try to compare across days.
122 // next sync, there are aligned again, so it's not such a big deal.
123 printf("[time] not calculating drift across two different dates\r\n");
124 return;
125 }
126 // got valid timestructs nt1 and nt2, *before* and *after* setting the time
127 uint32_t ne1 = nt1.second + (nt1.minute * 60) + (nt1.hour * 60 * 60);
128 uint32_t ne2 = nt2.second + (nt2.minute * 60) + (nt2.hour * 60 * 60);
129
130 adj_time = ne2 - ne1;
131 ltd = lt2 - lt1;
132
133 // calculate *OLD* drift
134 if (old_last_time_sync < received) {
135 convert_timestamps_to_time(old_last_time_stamp, old_last_date_stamp, &old_time);
136 add_seconds_to_timestruct(&old_time, received - old_last_time_sync);
137 if ((nt2.month == old_time.month) && (nt2.month == old_time.month)) {
138 old_last_drift = old_time.second + (old_time.minute * 60) + (old_time.hour * 60 * 60) - ne2;
139 }
140 }
141
142
143 printf("[time] set time diff: %i@%i, %i@%i (%i/%i)\r\n", ne1, lt1, ne2, lt2, adj_time, ltd);
144}
145
146/*
147 * return: 1=ACK, 2=NACK, 0=nothing
148 * the order of parsing (and exiting function) is relevant.
149 * for example: the server might send mul/div without sync'ing time
150 */
151int time_command_received(struct command *com) {
152 int res = 0;
153 int n;
154 uint32_t now = mculib_get_seconds_since_boot();
155 uint32_t p1, p2;
156 uint32_t dm;
157
158 // ackreq?
159 n = namedarg_uint32(com, "ar", &dm);
160 if (n == 0) {
161 res = 1;
162 }
163
164 // set multiplier, if exists
165 n = namedarg_uint32(com, "mul", &dm);
166 if (n == 0) {
167 config_set_clock_mul(dm);
168 }
169
170 // set divisor, if exists
171 n = namedarg_uint32(com, "div", &dm);
172 if (n == 0) {
173 config_set_clock_div(dm);
174 }
175
176 // parse time
177 n = namedarg_uint32(com, "bcdtime", &p1);
178 if (n != 0) {
179 printf("[time] clock-info did not include bcdtime - ignored\r\n");
180 return res;
181 }
182 // parse date
183 n = namedarg_uint32(com, "bcddate", &p2);
184 if (n != 0) {
185 printf("[time] clock-info did not include bcddate - ignored\r\n");
186 return res;
187 }
188
189 // is it a set-timezone request?
190 uint32_t tz;
191
192 n = namedarg_uint32(com, "settz", &tz);
193 if (n == 0) {
194 config_set_timezone(tz);
195 printf("[time] set new timezone %i\r\n");
196 }
197
198 // parse timezone and ignore if it does not match our timezone
199
200 n = namedarg_uint32(com, "tz", &tz);
201 if (n != 0) {
202 // no timezone? -- ignore
203 printf("[time] clock-info did not include timezone - ignored\r\n");
204 return res;
205 }
206 if (tz != config_get_timezone()) {
207 printf("[time] received for time %i, but we are in %i - ignored\r\n", tz, config_get_timezone());
208 return res;
209 }
210
211
212
213 time_set_new_from_sync(now, p1, p2, res);
214
215 // if time_sync disabled, do not forward
216 if (sync_disabled()) {
217 return res;
218 }
219
220 uint32_t p;
221
222 // parse daytime
223 n = namedarg_uint32(com, "dt", &p);
224 if (n == 0) {
225 daytime = p;
226 }
227
228 n = namedarg_uint32(com, "fw", &p);
229 if (n == 0) {
230 // it was already forwarded.
231 return 0;
232 }
233
234 // forward it
235 struct command *ocom = alloc_command();
236
237 if (ocom == NULL) {
238 return 0;
239 }
240 ocom->target = BROADCAST;
241 ocom->com = 59; // clock-sync
242 command_add_arg(ocom, "fw=1");
243 command_add_arg(ocom, "bcdtime=%D", last_time_stamp);
244 command_add_arg(ocom, "bcddate=%D", last_date_stamp);
245 command_add_arg(ocom, "dt=%i", (int)daytime);
246 command_add_arg(ocom, "tz=%i", (int)tz);
247 n = send_command(ocom);
248 free_command(ocom);
249 if (n != 0) {
250 printf("[time] failed to forward sync request: %i\r\n", n);
251 return res;
252 }
253 return res;
254}
255
256// given a month [1..12] - will return max days (ignoring leap years)
257static uint32_t max_date_for_month(uint32_t month) {
258 if ((month == 1) || (month == 3) || (month == 5) || (month == 7) || (month == 8) || (month == 10) || (month == 12)) {
259 return 31;
260 }
261 if (month == 2) {
262 return 28;
263 }
264 return 30;
265}
266
267// add some seconds to nt
268// handles days/months/hours and weekdays.
269// does not handle leap years.
270// does not handle daylight saving
271static void add_seconds_to_timestruct(struct sctime *nt, uint32_t add_secs) {
272 uint32_t add_wdays = 0;
273 uint32_t days = add_secs / (60 * 60 * 24);
274
275 nt->day = nt->day + days;
276 add_wdays = add_wdays + days;
277 add_secs = add_secs - (days * 60 * 60 * 24);
278
279 uint32_t hours = add_secs / (60 * 60);
280
281 nt->hour = nt->hour + hours;
282 add_secs = add_secs - (hours * 60 * 60);
283
284 uint32_t secs = add_secs % 60;
285
286 nt->second = nt->second + secs;
287 if (nt->second >= 60) {
288 nt->minute++;
289 nt->second = nt->second - 60;
290 }
291 uint32_t mins = ((add_secs - (secs)) / 60) % 3600;
292
293 nt->minute = nt->minute + mins;
294 if (nt->minute >= 60) {
295 nt->hour++;
296 nt->minute = nt->minute - 60;
297 }
298 if (nt->hour >= 24) {
299 nt->day++;
300 add_wdays++;
301 nt->hour = nt->hour - 24;
302 }
303 uint32_t mdm = max_date_for_month(nt->month);
304
305 if (nt->day > mdm) {
306 nt->month++;
307 nt->day = nt->day - mdm;
308 }
309 nt->weekday = nt->weekday + (add_wdays % 7);
310 if (nt->weekday > 7) {
311 nt->weekday = nt->weekday - 7;
312 }
313}
314
315uint32_t convert_time_to_timestamp(struct sctime *time) {
316 uint32_t res = 0;
317
318 res |= time->hour << 24;
319 res |= time->minute << 16;
320 res |= time->second << 8;
321 res |= time->weekday;
322 return res;
323}
324/*
325 * returns:
326 * 0 if time1 represents the same moment in time as time2
327 * 1 if time1 is AFTER time2
328 * 2 if time2 is BEFORE time2
329 */
330static uint8_t compare_time(struct sctime *time1, struct sctime *time2) {
331 if (time1->year > time2->year) {
332 return 1;
333 }
334 if (time1->year < time2->year) {
335 return 2;
336 }
337 if (time1->month > time2->month) {
338 return 1;
339 }
340 if (time1->month < time2->month) {
341 return 2;
342 }
343 if (time1->day > time2->day) {
344 return 1;
345 }
346 if (time1->day < time2->day) {
347 return 2;
348 }
349 if (time1->hour > time2->hour) {
350 return 1;
351 }
352 if (time1->hour < time2->hour) {
353 return 2;
354 }
355 if (time1->minute > time2->minute) {
356 return 1;
357 }
358 if (time1->minute < time2->minute) {
359 return 2;
360 }
361 if (time1->second > time2->second) {
362 return 1;
363 }
364 if (time1->second < time2->second) {
365 return 2;
366 }
367 return 0;
368}
369int diff_time(struct sctime *time1, struct sctime *time2) {
370 uint8_t reverse_sign = 0;
371 int diff = 0;
372 int m_days = 0;
373 struct sctime *larger;
374 struct sctime *smaller;
375
376 larger = time1;
377 smaller = time2;
378 int r = compare_time(larger, smaller);
379
380 if (r == 0) {
381 // equal? no secs difference
382 return 0;
383 } else if (r == 1) {
384 larger = time2;
385 smaller = time1;
386 reverse_sign = 1;
387 }
388
389 if (larger->month != smaller->month) {
390 m_days = max_date_for_month(larger->month);
391 }
392 diff = diff + (m_days + smaller->day - larger->day) * 60 * 60 * 24;
393 diff = diff + (smaller->hour - larger->hour) * 60 * 60;
394 diff = diff + (smaller->minute - larger->minute) * 60;
395 diff = diff + (smaller->second - larger->second);
396
397 if (reverse_sign) {
398 diff = 0 - diff;
399 }
400 return diff;
401}
402void convert_timestamps_to_time(uint32_t bcdtime, uint32_t bcddate, struct sctime *time) {
403 time->hour = ((bcdtime >> 24) & 0xFF);
404 time->minute = ((bcdtime >> 16) & 0xFF);
405 time->second = ((bcdtime >> 8) & 0xFF);
406 time->weekday = ((bcdtime) & 0xFF);
407 time->year = ((bcddate >> 16) & 0xFFFF);
408 time->month = ((bcddate >> 8) & 0xFF);
409 time->day = ((bcddate) & 0xFF);
410 time->timestamp = bcdtime;
411 uint32_t now = mculib_get_seconds_since_boot();
412 uint32_t since = now - last_time_sync;
413
414 time->secs_since_last_sync = since;
415 time->daytime = daytime;
416}
417static int get_time_as_struct(struct sctime *time, uint32_t l_time_sync, uint32_t l_time_stamp, uint32_t l_date_stamp) {
418 if (l_time_sync == 0) { // never synced?
419 return 2;
420 }
421
422 uint32_t now = mculib_get_seconds_since_boot();
423
424 if ((now < last_published_time) && (last_published_time - now < 50)) {
425 now = last_published_time;
426 }
427 last_published_time = now;
428
429 uint32_t since = now - l_time_sync;
430
431 if (since > TIME_MAX_AGE_NO_SYNC) { // last sync too long ago?
432 return 1;
433 }
434 if (l_time_sync > now) {
435 since = 0;
436 }
437 convert_timestamps_to_time(l_time_stamp, l_date_stamp, time);
438 // the internal clock is often very inaccurate. External crystals (and their placement) cost money,
439 // instead we keep track of how "off" the clock is when we synchronise with the server and adjust
440 // our calculation accordingly to compensate for the inaccurate clock
441 if (config_get_clock_mul() != 0) {
442 since = since * config_get_clock_mul();
443 }
444 if (config_get_clock_div() != 0) {
445 since = since / config_get_clock_div();
446 }
447 add_seconds_to_timestruct(time, since);
448 return 0;
449}
450// return 0==ok, otherwise error
451int get_localtime_as_struct(struct sctime *time) {
452 return get_time_as_struct(time, last_time_sync, last_time_stamp, last_date_stamp);
453}
454
455void clocksync_enable(uint8_t e) {
456 if (e) {
457 printf("clocksync enabled\r\n");
458 config_set_flag(CONFIG_FLAGS_CLOCKSYNC_DISABLED, 0);
459 } else {
460 printf("clocksync disabled\r\n");
461 config_set_flag(CONFIG_FLAGS_CLOCKSYNC_DISABLED, 1);
462 }
463}
464
465void time_add_debug_to_command(struct command *com) {
466 struct sctime lt;
467 int n = get_localtime_as_struct(&lt);
468
469 if (n == 0) {
470 command_add_arg(com, "time=%i:%i:%i", lt.hour, lt.minute, lt.second);
471 command_add_arg(com, "timestamp=%i", lt.timestamp);
472 command_add_arg(com, "syncage=%i", lt.secs_since_last_sync);
473 command_add_arg(com, "date=%i-%i-%i", lt.year, lt.month, lt.day);
474 command_add_arg(com, "now=%i", mculib_get_seconds_since_boot());
475 }
476 uint32_t *CNWRTC_BASE_TR = (uint32_t *)0x40002800;
477 // Capture seconds and minutes 0-9, in BCD format
478 uint32_t stupidcal = (*CNWRTC_BASE_TR);
479
480 command_add_arg(com, "rtc=%i", stupidcal);
481 CNWRTC_BASE_TR = (uint32_t *)0x40002804;
482 // Capture seconds and minutes 0-9, in BCD format
483 stupidcal = (*CNWRTC_BASE_TR);
484 command_add_arg(com, "rtd=%i", stupidcal);
485 command_add_arg(com, "dr=%i", adj_time);
486 command_add_arg(com, "ltd=%i", ltd);
487 command_add_arg(com, "f=%i/%i", config_get_clock_mul(), config_get_clock_div());
488 command_add_arg(com, "ols=%i", old_last_time_sync);
489 command_add_arg(com, "oldr=%i", old_last_drift);
490}
491
492void sctime_init() {
493 appstarted = mculib_get_seconds_since_boot();
494}
495uint32_t get_seconds_since_start() {
496 return mculib_get_seconds_since_boot() - appstarted;
497}
498void clocksync_help() {
499 uint32_t now = mculib_get_seconds_since_boot();
500 struct sctime nt;
501 int n = get_localtime_as_struct(&nt);
502
503 printf("Clocksync help:\r\n");
504 printf("Now (boot): %i\r\n", now);
505 printf("Now (strt): %i\r\n", (now - appstarted));
506 printf("Multiplier: %i\r\n", config_get_clock_mul());
507 printf("Divisor : %i\r\n", config_get_clock_div());
508 printf("ltd : %i\r\n", ltd);
509 printf("adj_time : %i\r\n", adj_time);
510 printf("last sync : %i (%i)\r\n", last_time_sync, (now - last_time_sync));
511 if (n == 0) {
512 printf("Time : %i:%i:%i (%s)\r\n", nt.hour, nt.minute, nt.second, (nt.daytime ? "day":"night"));
513 } else {
514 printf("Time : unavailable (%i)\r\n", n);
515 }
516 printf("Timezone : %i\r\n", config_get_timezone());
517 printf("Enabled : %s\r\n", (sync_disabled() ? "No" : "Yes"));
518}
int send_command(struct command *com)
send a command to another module (or broadcast)
Definition: queue.c:374
void free_command(struct command *com)
free a command
Definition: queue.c:200
void command_add_arg(struct command *com, const char *format,...)
adds an arg to a partially initialised command structure
int namedarg_uint32(struct command *com, const char *name, uint32_t *value)
get a named arg (key-value pair), parsed as integer. result in "value". if return value == 0 ,...
struct command * alloc_command()
allocate a free command
Definition: queue.c:173
int com
Definition: command.h:22
long target
Definition: command.h:16
Definition: sctime.h:3