Simple time management w/o the Network interface
Diff: TimeInterface.cpp
- Revision:
- 0:e8e62dfee5eb
- Child:
- 1:b78c91e34eda
diff -r 000000000000 -r e8e62dfee5eb TimeInterface.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TimeInterface.cpp Fri Oct 11 20:53:30 2019 +0000 @@ -0,0 +1,763 @@ + +#include "TimeInterface.h" + +#include "rtc_api.h" + +//#define DEBUG "Time" +#include <cstdio> +#if (defined(DEBUG) && !defined(TARGET_LPC11U24)) +#define DBG(x, ...) std::printf("[DBG %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define ERR(x, ...) std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); +#else +#define DBG(x, ...) +#define WARN(x, ...) +#define ERR(x, ...) +#define INFO(x, ...) +#endif + +#ifdef WIN32 +// Fake it out for Win32 development and testing +struct LPC { + unsigned long CCR; // Clock Control register + unsigned long GPREG0; // General Purpose Register #0 - 32-bit Battery backed + unsigned long GPREG1; // General Purpose Register #1 - 32-bit Battery backed + unsigned long CALIBRATION; // Calibration Register +}; +struct LPC X; +struct LPC * LPC_RTC = &X; +#define set_time(x) (void)x +#endif + + +TimeInterface::TimeInterface(void *net) +{ + m_net = net; + dst = false; + memset(&dst_pair, 0, sizeof(dst_pair)); // that's enough to keep it from running +} + +TimeInterface::~TimeInterface() +{ +} + +#if 0 +NTPResult TimeInterface::setTime(const char* host, uint16_t port, uint32_t timeout) +{ + NTPResult res; + + if (m_net) { + NTPClient ntp(m_net); + // int16_t tzomin = get_tzo_min(); + INFO("setTime(%s, %d, %d)\r\n", host, port, timeout); + res = ntp.setTime(host, port, timeout); + INFO(" ret: %d\r\n", res); + if (res == NTP_OK) { + // if the time was fetched successfully, then + // let's save the time last set with the local tzo applied + // and this saves the last time set for later precision + // tuning. + set_time(std::time(NULL)); + } + } else { + ERR("No connection"); + res = NTP_CONN; + } + return res; +} +#endif + +bool TimeInterface::parseDSTstring(TimeInterface::dst_event_t * result, const char * dstr) +{ + int x; + dst_event_t test_dst; + + x = atoi(dstr); + if (x >= 1 && x <= 12) { + test_dst.MM = x; + dstr = strchr(dstr, '/'); + if (dstr++) { + x = atoi(dstr); + if (x >= 1 && x <= 31) { + test_dst.DD = x; + dstr = strchr(dstr, ','); + if (dstr++) { + x = atoi(dstr); + if (x >= 0 && x <= 23) { + test_dst.hh = x; + dstr = strchr(dstr, ':'); + if (dstr++) { + x = atoi(dstr); + if (x >= 0 && x <= 59) { + test_dst.mm = x; + memcpy(result, &test_dst, sizeof(dst_event_t)); + INFO("parsed: %d/%d %d:%02d", test_dst.MM, test_dst.DD, test_dst.hh, test_dst.mm); + return true; + } + } + } + } + } + } + } + return false; +} + +// parse MM/DD,hh:mm +bool TimeInterface::set_dst(const char * dstStart, const char * dstStop) +{ + dst_event_pair_t test_pair; + + if (parseDSTstring(&test_pair.dst_start, dstStart) + && parseDSTstring(&test_pair.dst_stop, dstStop)) { + memcpy(&dst_pair, &test_pair, sizeof(dst_event_pair_t)); + INFO("set_dst from (%s,%s)", dstStart, dstStop); + return true; + } + WARN("failed to set_dst from (%s,%s)", dstStart, dstStop); + return false; +} + +bool TimeInterface::set_dst(bool isdst) +{ + dst = isdst; + return true; +} + +bool TimeInterface::get_dst(void) +{ + return dst; +} + +clock_t TimeInterface::clock(void) +{ + return std::clock(); +} + +time_t TimeInterface::time(time_t * timer) +{ + return std::time(timer); +} + +uint32_t TimeInterface::minutesSinceJan(int mon, int day, int hr, int min) +{ + return (mon * 60 * 24 * 31) + (day * 60 * 24) + (hr * 60) + min; +} + +time_t TimeInterface::timelocal(time_t * timer) +{ + time_t privTime; + struct tm * tminfo; + + if (dst_pair.dst_start.MM) { // may have to change the dst + std::time(&privTime); + tminfo = std::localtime(&privTime); + + uint32_t min_since_jan = minutesSinceJan(tminfo->tm_mon + 1, tminfo->tm_mday, tminfo->tm_hour, tminfo->tm_min); + uint32_t min_dst_start = minutesSinceJan(dst_pair.dst_start.MM, dst_pair.dst_start.DD, dst_pair.dst_start.hh, dst_pair.dst_start.mm) + get_tzo_min(); + uint32_t min_dst_stop = minutesSinceJan(dst_pair.dst_stop.MM, dst_pair.dst_stop.DD, dst_pair.dst_stop.hh, dst_pair.dst_stop.mm) + get_tzo_min(); + + if (min_since_jan >= min_dst_start && min_since_jan < min_dst_stop) { + dst = 1; + //INFO(" is dst: %u - %u - %u", min_since_jan, min_dst_start, min_dst_stop); + } else { + dst = 0; + //INFO("not dst: %u - %u - %u", min_since_jan, min_dst_start, min_dst_stop); + } + } + INFO(" timelocal: %u, %d, %d", std::time(timer), get_tzo_min(), dst); + return std::time(timer) + get_tzo_min() * 60 + dst * 3600; +} + +char * TimeInterface::ctime(const time_t * timer) +{ + char * p = std::ctime(timer); + + if (strlen(p) < sizeof(result)) { + strcpy(result, p); + p = strchr(result, '\n'); + if (p) + *p = '\0'; + } else { + result[0] = '\0'; + } + return result; +} + +char * TimeInterface::asctime(const struct tm_ex * timeptr) +{ + static const char wday_name[][4] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char mon_name[][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + struct tm_ex tmp = *timeptr; + + tmp.tm_min += tmp.tm_tzo_min; + if (tmp.tm_isdst) + tmp.tm_min += 60; + while (tmp.tm_min >= 60) { + tmp.tm_min -= 60; + tmp.tm_hour++; + } + while (tmp.tm_min < 0) { + tmp.tm_min += 60; + tmp.tm_hour--; + } + while (tmp.tm_hour >= 24) { + tmp.tm_wday = (tmp.tm_wday + 1) % 7; + tmp.tm_mday++; + tmp.tm_hour -= 24; + } + while (tmp.tm_hour < 0) { + tmp.tm_wday = (tmp.tm_wday + 6) % 7; + tmp.tm_mday--; + tmp.tm_hour += 24; + } + sprintf(result, "%.3s %.3s%3d %.2d:%.2d:%.2d %d", + wday_name[tmp.tm_wday % 7], + mon_name[tmp.tm_mon % 12], + tmp.tm_mday, tmp.tm_hour, + tmp.tm_min, tmp.tm_sec, + 1900 + tmp.tm_year); + return result; +} + +struct tm_ex * TimeInterface::gmtime(const time_t * timer) +{ + time_t priv = *timer + get_tzo_min() * 60 + dst * 3600; + struct tm * tmp = std::localtime(&priv); + + tm_ext.tm_sec = tmp->tm_sec; + tm_ext.tm_min = tmp->tm_min; + tm_ext.tm_hour = tmp->tm_hour; + tm_ext.tm_mday = tmp->tm_mday; + tm_ext.tm_mon = tmp->tm_mon; + tm_ext.tm_year = tmp->tm_year; + tm_ext.tm_wday = tmp->tm_wday; + tm_ext.tm_yday = tmp->tm_yday; + tm_ext.tm_isdst = tmp->tm_isdst; + tm_ext.tm_tzo_min = get_tzo_min(); + return &tm_ext; +} + +struct tm_ex * TimeInterface::localtime(const time_t * timer) +{ + struct tm * tmp = std::localtime(timer); + + tm_ext.tm_sec = tmp->tm_sec; + tm_ext.tm_min = tmp->tm_min; + tm_ext.tm_hour = tmp->tm_hour; + tm_ext.tm_mday = tmp->tm_mday; + tm_ext.tm_mon = tmp->tm_mon; + tm_ext.tm_year = tmp->tm_year; + tm_ext.tm_wday = tmp->tm_wday; + tm_ext.tm_yday = tmp->tm_yday; + tm_ext.tm_isdst = tmp->tm_isdst; + tm_ext.tm_tzo_min = get_tzo_min(); + return &tm_ext; +} + +time_t TimeInterface::mktime(struct tm_ex * timeptr) +{ + timeptr->tm_tzo_min = get_tzo_min(); + return std::mktime((struct tm *)timeptr); +} + +size_t TimeInterface::strftime(char * ptr, size_t maxsize, const char * format, const struct tm_ex * timeptr) +{ + return std::strftime(ptr, maxsize, format, (struct tm *)timeptr); +} + +double TimeInterface::difftime(time_t end, time_t beginning) +{ + return std::difftime(end, beginning); +} + + + +// time zone functions + +void TimeInterface::set_time(time_t t, int16_t tzo_min) +{ + time_t tval = t - (tzo_min * 60); + rtc_init(); + rtc_write(tval); + LPC_RTC->GPREG1 = tval; + INFO("set_time(%s)", ctime(&tval)); +} + +void TimeInterface::set_tzo_min(int16_t tzo_min) +{ + uint16_t th; + uint32_t treg; + + if (tzo_min >= -720 && tzo_min <= 720) { + th = (uint16_t)(-tzo_min); + treg = (th << 16) | (uint16_t)tzo_min; + LPC_RTC->GPREG0 = treg; + //printf("set_tzo(%d) %d is %08X\r\n", tzo, th, LPC_RTC->GPREG0); + } +} + +int16_t TimeInterface::get_tzo_min(void) +{ + uint16_t th, tl; + + th = LPC_RTC->GPREG0 >> 16; + tl = LPC_RTC->GPREG0; + //printf("get_tzo() is %04X %04X\r\n", th, tl); + if ((uint16_t)(th + tl) == 0) { + return tl; + } else { + return 0; + } +} + +time_t TimeInterface::get_timelastset(void) +{ + return LPC_RTC->GPREG1; +} + +int32_t TimeInterface::get_cal() +{ + int32_t calvalue = LPC_RTC->CALIBRATION & 0x3FFFF; + + if (calvalue & 0x20000) { + calvalue = -(calvalue & 0x1FFFF); + } + return calvalue; +} + +void TimeInterface::set_cal(int32_t calibration) +{ + if (calibration) { + if (calibration < 0) { + calibration = (-calibration & 0x1FFFF) | 0x20000; + } + LPC_RTC->CCR = 0x000001; //(LPC_RTC->CCR & 0x0003); // Clear CCALEN to enable it + } else { + LPC_RTC->CCR = 0x000011; //(LPC_RTC->CCR & 0x0003) | 0x0010; // Set CCALEN to disable it + } + LPC_RTC->CALIBRATION = calibration; +} + +bool TimeInterface::adjust_sec(int32_t adjustSeconds) +{ + time_t lastSet = get_timelastset(); + + if (lastSet != 0) { + time_t seconds = time(NULL); // get "now" according to the rtc + int32_t delta = seconds - lastSet; + //int32_t curCal = get_cal(); // calibration might want to leverage the current cal factor. + int32_t calMAX = 131071; + int32_t secPerDay = 86400; + float errSecPerDay; + + // Convert the current calibration and the adjustment into + // the new calibration value + // assume it is +10sec and it has been 2days, then the adjustment + // needs to be +5 sec per day, or one adjustment every 1/5th + // of a day, or 1 adjustment every 86400/5 counts. + // delta = now - then (number of elapsed seconds) + if (adjustSeconds != 0 && delta != 0) { + int32_t calFactor; + + // Make the clock correct + seconds = seconds + adjustSeconds; + set_time(seconds); + // Compute the calibration factor + errSecPerDay = (float)adjustSeconds / ((float)(delta)/secPerDay); + calFactor = (int32_t)((float)secPerDay/errSecPerDay); + if (abs(calFactor) < calMAX) + set_cal(calFactor); + } + return true; + } else { + return false; + } +} + + +// ############################################################################# +/* + * Enhancement to use a custom tm_ex struct and the time zone by D. Smart + * %Z + * + * Copyright (c) 1994 Powerdog Industries. All rights reserved. + * + * Redistribution and use in source and binary forms, without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgement: + * This product includes software developed by Powerdog Industries. + * 4. The name of Powerdog Industries may not be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define asizeof(a) (sizeof (a) / sizeof ((a)[0])) + +struct dtconv { + char *abbrev_month_names[12]; + char *month_names[12]; + char *abbrev_weekday_names[7]; + char *weekday_names[7]; + char *time_format; + char *sdate_format; + char *dtime_format; + char *am_string; + char *pm_string; + char *ldate_format; + char *zone_names[10]; + int8_t zone_offsets[10]; +}; + +static const struct dtconv En_US = { + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }, + { + "January", "February", "March", "April", + "May", "June", "July", "August", + "September", "October", "November", "December" + }, + { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }, + { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }, + "%H:%M:%S", + "%m/%d/%y", + "%a %b %e %T %Z %Y", + "AM", + "PM", + "%A, %B, %e, %Y", + { "UTC", "EST", "CST", "MST", "PST", "YST", "CAT", "HST", "CET", "EET", }, + { 0, -5, -6, -7, -8, -9, -10, -10, +1, +2, }, +}; + + +#ifndef isprint +#define in_range(c, lo, up) ((uint8_t)c >= lo && (uint8_t)c <= up) +#define isprint(c) in_range(c, 0x20, 0x7f) +#define isdigit(c) in_range(c, '0', '9') +#define isxdigit(c) (isdigit(c) || in_range(c, 'a', 'f') || in_range(c, 'A', 'F')) +#define islower(c) in_range(c, 'a', 'z') +#define isspace(c) (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v') +#endif + + +const char * TimeInterface::strptime(const char *buf, char *fmt, struct tm_ex *tm) +{ + char c, *ptr; + int i, len; + bool fSet_wday = false; // so we can notice if the wday was set + + ptr = fmt; + while (*ptr != 0) { + if (*buf == 0) + break; + + c = *ptr++; + + if (c != '%') { + if (isspace(c)) + while (*buf != 0 && isspace(*buf)) + buf++; + else if (c != *buf++) + return 0; + continue; + } + + c = *ptr++; + switch (c) { + case 0: + case '%': + if (*buf++ != '%') + return 0; + break; + + case 'C': + buf = strptime(buf, En_US.ldate_format, tm); + if (buf == 0) + return 0; + break; + + case 'c': + buf = strptime(buf, "%x %X", tm); + if (buf == 0) + return 0; + break; + + case 'D': + buf = strptime(buf, "%m/%d/%y", tm); + if (buf == 0) + return 0; + break; + + case 'R': + buf = strptime(buf, "%H:%M", tm); + if (buf == 0) + return 0; + break; + + case 'r': + buf = strptime(buf, "%I:%M:%S %p", tm); + if (buf == 0) + return 0; + break; + + case 'T': + buf = strptime(buf, "%H:%M:%S", tm); + if (buf == 0) + return 0; + break; + + case 'X': + buf = strptime(buf, En_US.time_format, tm); + if (buf == 0) + return 0; + break; + + case 'x': + buf = strptime(buf, En_US.sdate_format, tm); + if (buf == 0) + return 0; + break; + + case 'j': + if (!isdigit(*buf)) + return 0; + + for (i = 0; *buf != 0 && isdigit(*buf); buf++) { + i *= 10; + i += *buf - '0'; + } + if (i > 365) + return 0; + + tm->tm_yday = i; + break; + + case 'M': + case 'S': + if (*buf == 0 || isspace(*buf)) + break; + + if (!isdigit(*buf)) + return 0; + + for (i = 0; *buf != 0 && isdigit(*buf); buf++) { + i *= 10; + i += *buf - '0'; + } + if (i > 59) + return 0; + + if (c == 'M') + tm->tm_min = i; + else + tm->tm_sec = i; + + if (*buf != 0 && isspace(*buf)) + while (*ptr != 0 && !isspace(*ptr)) + ptr++; + break; + + case 'H': + case 'I': + case 'k': + case 'l': + if (!isdigit(*buf)) + return 0; + + for (i = 0; *buf != 0 && isdigit(*buf); buf++) { + i *= 10; + i += *buf - '0'; + } + if (c == 'H' || c == 'k') { + if (i > 23) + return 0; + } else if (i > 11) + return 0; + + tm->tm_hour = i; + + if (*buf != 0 && isspace(*buf)) + while (*ptr != 0 && !isspace(*ptr)) + ptr++; + break; + + case 'p': + len = strlen(En_US.am_string); + if (strncasecmp(buf, En_US.am_string, len) == 0) { + if (tm->tm_hour > 12) + return 0; + if (tm->tm_hour == 12) + tm->tm_hour = 0; + buf += len; + break; + } + + len = strlen(En_US.pm_string); + if (strncasecmp(buf, En_US.pm_string, len) == 0) { + if (tm->tm_hour > 12) + return 0; + if (tm->tm_hour != 12) + tm->tm_hour += 12; + buf += len; + break; + } + + return 0; + + case 'A': + case 'a': + for (i = 0; i < asizeof(En_US.weekday_names); i++) { + len = strlen(En_US.weekday_names[i]); + if (strncasecmp(buf, + En_US.weekday_names[i], + len) == 0) + break; + + len = strlen(En_US.abbrev_weekday_names[i]); + if (strncasecmp(buf, + En_US.abbrev_weekday_names[i], + len) == 0) + break; + } + if (i == asizeof(En_US.weekday_names)) + return 0; + fSet_wday = true; + tm->tm_wday = i; + buf += len; + break; + + case 'd': + case 'e': + if (!isdigit(*buf)) + return 0; + + for (i = 0; *buf != 0 && isdigit(*buf); buf++) { + i *= 10; + i += *buf - '0'; + } + if (i > 31) + return 0; + + tm->tm_mday = i; + + if (*buf != 0 && isspace(*buf)) + while (*ptr != 0 && !isspace(*ptr)) + ptr++; + break; + + case 'B': + case 'b': + case 'h': + for (i = 0; i < asizeof(En_US.month_names); i++) { + len = strlen(En_US.month_names[i]); + if (strncasecmp(buf, + En_US.month_names[i], + len) == 0) + break; + + len = strlen(En_US.abbrev_month_names[i]); + if (strncasecmp(buf, + En_US.abbrev_month_names[i], + len) == 0) + break; + } + if (i == asizeof(En_US.month_names)) + return 0; + + tm->tm_mon = i; + buf += len; + break; + + case 'm': + if (!isdigit(*buf)) + return 0; + + for (i = 0; *buf != 0 && isdigit(*buf); buf++) { + i *= 10; + i += *buf - '0'; + } + if (i < 1 || i > 12) + return 0; + + tm->tm_mon = i - 1; + + if (*buf != 0 && isspace(*buf)) + while (*ptr != 0 && !isspace(*ptr)) + ptr++; + break; + + case 'Y': + case 'y': + if (*buf == 0 || isspace(*buf)) + break; + + if (!isdigit(*buf)) + return 0; + + for (i = 0; *buf != 0 && isdigit(*buf); buf++) { + i *= 10; + i += *buf - '0'; + } + if (c == 'Y') + i -= 1900; + if (i < 0) + return 0; + + tm->tm_year = i; + + if (*buf != 0 && isspace(*buf)) + while (*ptr != 0 && !isspace(*ptr)) + ptr++; + break; + case 'Z': + for (i = 0; i < asizeof(En_US.zone_names); i++) { + len = strlen(En_US.zone_names[i]); + if (strncasecmp(buf, + En_US.zone_names[i], + len) == 0) + break; + } + if (i == asizeof(En_US.zone_names)) + return 0; + tm->tm_tzo_min = En_US.zone_offsets[i] * 60; + buf += len; + break; + } + } + if (!fSet_wday) { + if (mktime(tm) == (time_t)-1) + tm->tm_wday = 7; + } + return buf; +}