diff --git a/cores/esp8266/coredecls.h b/cores/esp8266/coredecls.h new file mode 100644 index 000000000..809b1b2f6 --- /dev/null +++ b/cores/esp8266/coredecls.h @@ -0,0 +1,9 @@ + +#ifndef __COREDECLS_H +#define __COREDECLS_H + +extern bool s_bootTimeSet; + +// TODO: put declarations here, get rid of -Wno-implicit-function-declaration + +#endif // __COREDECLS_H diff --git a/cores/esp8266/sntp-lwip2.c b/cores/esp8266/sntp-lwip2.c new file mode 100644 index 000000000..0cb1a7165 --- /dev/null +++ b/cores/esp8266/sntp-lwip2.c @@ -0,0 +1,452 @@ +/* + * sntp-lwip2.c - ESP8266-specific functions for SNTP and lwIP-v2 + * Copyright (c) 2015 Espressif (license is tools/sdk/lwip/src/core/sntp.c's) + * Redistribution and use in source and binary forms, with or 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. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + * + * + * History: + * This code is extracted from lwip1.4-espressif's sntp.c + * which is a patched version of the original lwip1's sntp. + * (check the mix-up in tools/sdk/lwip/src/core/sntp.c) + * It is moved here as-is and cleaned for maintainability and + * because it does not belong to lwip. + * + * TODOs: + * settimeofday(): handle tv->tv_usec + * sntp_mktm_r(): review, fix DST handling (this one is currently untouched from lwip-1.4) + */ + +#include +#include +#include +#include +#include "coredecls.h" + +#if LWIP_VERSION_MAJOR == 1 + +#include + +static const char stod14[] PROGMEM = "settimeofday() can't set time!\n"; + +int settimeofday(const struct timeval* tv, const struct timezone* tz) +{ + if (tz) /*before*/ + { + sntp_set_timezone(tz->tz_minuteswest / 60); + // apparently tz->tz_dsttime is a bitfield and should not be further used (cf man) + sntp_set_daylight(0); + } + if (tv) /* after*/ + { + os_printf(stod14); + + // reset time subsystem + s_bootTimeSet = false; + + return -1; + } + return 0; +} + +#endif // lwip 1.4 only + +#if LWIP_VERSION_MAJOR == 2 + +#include + +static uint32 realtime_stamp = 0; +static uint16 dst = 0; +static sint8 time_zone = 8; // espressif HQ's default timezone +LOCAL os_timer_t sntp_timer; + +/*****************************************/ +#define SECSPERMIN 60L +#define MINSPERHOUR 60L +#define HOURSPERDAY 24L +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY (SECSPERHOUR * HOURSPERDAY) +#define DAYSPERWEEK 7 +#define MONSPERYEAR 12 + +#define YEAR_BASE 1900 +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY 4 +#define EPOCH_YEARS_SINCE_LEAP 2 +#define EPOCH_YEARS_SINCE_CENTURY 70 +#define EPOCH_YEARS_SINCE_LEAP_CENTURY 370 + +#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) + +int __tznorth; +int __tzyear; +char reult[100]; +static const int mon_lengths[2][12] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +} ; + +static const int year_lengths[2] = { + 365, + 366 +} ; +struct tm +{ + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; + +struct tm res_buf; +typedef struct __tzrule_struct +{ + char ch; + int m; + int n; + int d; + int s; + time_t change; + int offset; +} __tzrule_type; + +__tzrule_type sntp__tzrule[2]; +struct tm * +sntp_mktm_r(const time_t * tim_p ,struct tm *res ,int is_gmtime) +{ + long days, rem; + time_t lcltime; + int y; + int yleap; + const int *ip; + + /* base decision about std/dst time on current time */ + lcltime = *tim_p; + + days = ((long)lcltime) / SECSPERDAY; + rem = ((long)lcltime) % SECSPERDAY; + while (rem < 0) + { + rem += SECSPERDAY; + --days; + } + while (rem >= SECSPERDAY) + { + rem -= SECSPERDAY; + ++days; + } + + /* compute hour, min, and sec */ + res->tm_hour = (int) (rem / SECSPERHOUR); + rem %= SECSPERHOUR; + res->tm_min = (int) (rem / SECSPERMIN); + res->tm_sec = (int) (rem % SECSPERMIN); + + /* compute day of week */ + if ((res->tm_wday = ((EPOCH_WDAY + days) % DAYSPERWEEK)) < 0) + res->tm_wday += DAYSPERWEEK; + + /* compute year & day of year */ + y = EPOCH_YEAR; + if (days >= 0) + { + for (;;) + { + yleap = isleap(y); + if (days < year_lengths[yleap]) + break; + y++; + days -= year_lengths[yleap]; + } + } + else + { + do + { + --y; + yleap = isleap(y); + days += year_lengths[yleap]; + } while (days < 0); + } + + res->tm_year = y - YEAR_BASE; + res->tm_yday = days; + ip = mon_lengths[yleap]; + for (res->tm_mon = 0; days >= ip[res->tm_mon]; ++res->tm_mon) + days -= ip[res->tm_mon]; + res->tm_mday = days + 1; + + if (!is_gmtime) + { + int offset; + int hours, mins, secs; + +// TZ_LOCK; +// if (_daylight) +// { +// if (y == __tzyear || __tzcalc_limits (y)) +// res->tm_isdst = (__tznorth +// ? (*tim_p >= __tzrule[0].change && *tim_p < __tzrule[1].change) +// : (*tim_p >= __tzrule[0].change || *tim_p < __tzrule[1].change)); +// else +// res->tm_isdst = -1; +// } +// else + res->tm_isdst = -1; + + offset = (res->tm_isdst == 1 ? sntp__tzrule[1].offset : sntp__tzrule[0].offset); + + hours = offset / SECSPERHOUR; + offset = offset % SECSPERHOUR; + + mins = offset / SECSPERMIN; + secs = offset % SECSPERMIN; + + res->tm_sec -= secs; + res->tm_min -= mins; + res->tm_hour -= hours; + + if (res->tm_sec >= SECSPERMIN) + { + res->tm_min += 1; + res->tm_sec -= SECSPERMIN; + } + else if (res->tm_sec < 0) + { + res->tm_min -= 1; + res->tm_sec += SECSPERMIN; + } + if (res->tm_min >= MINSPERHOUR) + { + res->tm_hour += 1; + res->tm_min -= MINSPERHOUR; + } + else if (res->tm_min < 0) + { + res->tm_hour -= 1; + res->tm_min += MINSPERHOUR; + } + if (res->tm_hour >= HOURSPERDAY) + { + ++res->tm_yday; + ++res->tm_wday; + if (res->tm_wday > 6) + res->tm_wday = 0; + ++res->tm_mday; + res->tm_hour -= HOURSPERDAY; + if (res->tm_mday > ip[res->tm_mon]) + { + res->tm_mday -= ip[res->tm_mon]; + res->tm_mon += 1; + if (res->tm_mon == 12) + { + res->tm_mon = 0; + res->tm_year += 1; + res->tm_yday = 0; + } + } + } + else if (res->tm_hour < 0) + { + res->tm_yday -= 1; + res->tm_wday -= 1; + if (res->tm_wday < 0) + res->tm_wday = 6; + res->tm_mday -= 1; + res->tm_hour += 24; + if (res->tm_mday == 0) + { + res->tm_mon -= 1; + if (res->tm_mon < 0) + { + res->tm_mon = 11; + res->tm_year -= 1; + res->tm_yday = 365 + isleap(res->tm_year); + } + res->tm_mday = ip[res->tm_mon]; + } + } +// TZ_UNLOCK; + } + else + res->tm_isdst = 0; +// os_printf("res %d %d %d %d %d\n",res->tm_year,res->tm_mon,res->tm_mday,res->tm_yday,res->tm_hour); + return (res); +} +struct tm * +sntp_localtime_r(const time_t * tim_p , + struct tm *res) +{ + return sntp_mktm_r (tim_p, res, 0); +} + +struct tm * +sntp_localtime(const time_t * tim_p) +{ + return sntp_localtime_r (tim_p, &res_buf); +} + +int sntp__tzcalc_limits(int year) +{ + int days, year_days, years; + int i, j; + + if (year < EPOCH_YEAR) + return 0; + + __tzyear = year; + + years = (year - EPOCH_YEAR); + + year_days = years * 365 + + (years - 1 + EPOCH_YEARS_SINCE_LEAP) / 4 - (years - 1 + EPOCH_YEARS_SINCE_CENTURY) / 100 + + (years - 1 + EPOCH_YEARS_SINCE_LEAP_CENTURY) / 400; + + for (i = 0; i < 2; ++i) + { + if (sntp__tzrule[i].ch == 'J') + days = year_days + sntp__tzrule[i].d + (isleap(year) && sntp__tzrule[i].d >= 60); + else if (sntp__tzrule[i].ch == 'D') + days = year_days + sntp__tzrule[i].d; + else + { + int yleap = isleap(year); + int m_day, m_wday, wday_diff; + const int *ip = mon_lengths[yleap]; + + days = year_days; + + for (j = 1; j < sntp__tzrule[i].m; ++j) + days += ip[j-1]; + + m_wday = (EPOCH_WDAY + days) % DAYSPERWEEK; + + wday_diff = sntp__tzrule[i].d - m_wday; + if (wday_diff < 0) + wday_diff += DAYSPERWEEK; + m_day = (sntp__tzrule[i].n - 1) * DAYSPERWEEK + wday_diff; + + while (m_day >= ip[j-1]) + m_day -= DAYSPERWEEK; + + days += m_day; + } + + /* store the change-over time in GMT form by adding offset */ + sntp__tzrule[i].change = days * SECSPERDAY + sntp__tzrule[i].s + sntp__tzrule[i].offset; + } + + __tznorth = (sntp__tzrule[0].change < sntp__tzrule[1].change); + + return 1; +} + +char* sntp_asctime_r(struct tm *tim_p ,char *result) +{ + static const char day_name[7][4] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" + }; + static const char mon_name[12][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + os_sprintf (result, "%s %s %02d %02d:%02d:%02d %02d\n", + day_name[tim_p->tm_wday], + mon_name[tim_p->tm_mon], + tim_p->tm_mday, tim_p->tm_hour, tim_p->tm_min, + tim_p->tm_sec, 1900 + tim_p->tm_year); + return result; +} + +char* sntp_asctime(struct tm *tim_p) +{ + return sntp_asctime_r (tim_p, reult); +} + +uint32 ICACHE_RAM_ATTR sntp_get_current_timestamp(void) +{ + return realtime_stamp; +} + +char* sntp_get_real_time(time_t t) +{ + return sntp_asctime(sntp_localtime (&t)); +} + +sint8 sntp_get_timezone(void) +{ + return time_zone; +} + +bool sntp_set_timezone(sint8 timezone) +{ + if(timezone >= -11 || timezone <= 13) { + time_zone = timezone; + return true; + } else { + return false; + } +} + +void sntp_set_daylight(int daylight) +{ + dst = daylight; +} + +void ICACHE_RAM_ATTR sntp_time_inc (void) +{ + realtime_stamp++; +} + +static void sntp_set_system_time (uint32_t t) +{ + realtime_stamp = t + time_zone * 60 * 60 + dst; + os_timer_disarm(&sntp_timer); + os_timer_setfn(&sntp_timer, (os_timer_func_t *)sntp_time_inc, NULL); + os_timer_arm(&sntp_timer, 1000, 1); +} + +int settimeofday(const struct timeval* tv, const struct timezone* tz) +{ + if (tz) /*before*/ + { + sntp_set_timezone(tz->tz_minuteswest / 60); + // apparently tz->tz_dsttime is a bitfield and should not be further used (cf man) + sntp_set_daylight(0); + } + if (tv) /* after*/ + { + sntp_set_system_time(tv->tv_sec); + // XXX FIXME TODO: efficiently use provided tv->tv_sec + + // reset time subsystem + s_bootTimeSet = false; + } + return 0; +} + +#endif // lwip2 only diff --git a/cores/esp8266/time.c b/cores/esp8266/time.c index 27f6640ca..ab3fdfeca 100644 --- a/cores/esp8266/time.c +++ b/cores/esp8266/time.c @@ -17,9 +17,10 @@ */ #include +#include #include #include "sntp.h" - +#include "coredecls.h" #ifndef _TIMEVAL_DEFINED #define _TIMEVAL_DEFINED @@ -36,9 +37,7 @@ extern uint64_t micros64(); // time gap in seconds from 01.01.1900 (NTP time) to 01.01.1970 (UNIX time) #define DIFF1900TO1970 2208988800UL -static int s_daylightOffset_sec = 0; -static long s_timezone_sec = 0; -static bool s_bootTimeSet = false; +bool s_bootTimeSet = false; static uint64_t s_bootTime_us = 0; // calculate offset used in gettimeofday @@ -73,17 +72,17 @@ void configTime(int timezone, int daylightOffset_sec, const char* server1, const setServer(1, server2); setServer(2, server3); - s_timezone_sec = timezone; - s_daylightOffset_sec = daylightOffset_sec; sntp_set_timezone(timezone/3600); + sntp_set_daylight(daylightOffset_sec); sntp_init(); } int clock_gettime(clockid_t unused, struct timespec *tp) { (void) unused; - tp->tv_sec = millis() / 1000; - tp->tv_nsec = micros() * 1000; + uint64_t m = micros64(); + tp->tv_sec = m / 1000000; + tp->tv_nsec = (m % 1000000) * 1000; return 0; } diff --git a/libraries/esp8266/examples/NTP-TZ-DST/NTP-TZ-DST.ino b/libraries/esp8266/examples/NTP-TZ-DST/NTP-TZ-DST.ino new file mode 100644 index 000000000..254eb65fe --- /dev/null +++ b/libraries/esp8266/examples/NTP-TZ-DST/NTP-TZ-DST.ino @@ -0,0 +1,128 @@ +/* + NTP-TZ-DST + NetWork Time Protocol - Time Zone - Daylight Saving Time + + This example shows how to read and set time, + and how to use NTP (set NTP0_OR_LOCAL1 to 0 below) + or an external RTC (set NTP0_OR_LOCAL1 to 1 below) + + TZ and DST below have to be manually set + according to your local settings. + + This example code is in the public domain. +*/ + +#include // time() ctime() +#include // struct timeval +#include + +//////////////////////////////////////////////////////// + +#define SSID "open" +#define SSIDPWD "" +#define TZ 1 // (utc+) TZ in hours +#define DST_MN 60 // use 60mn for summer time in some countries + +#define NTP0_OR_LOCAL1 1 // 0:use NTP 1:fake external RTC +#define RTC_TEST 1510592825 // 1510592825 = Monday 13 November 2017 17:07:05 UTC + +//////////////////////////////////////////////////////// + +#define TZ_MN ((TZ)*60) +#define TZ_SEC ((TZ)*3600) +#define DST_SEC ((DST_MN)*60) + +void setup() { + Serial.begin(115200); + +#if NTP0_OR_LOCAL1 + // local + + ESP.eraseConfig(); + time_t rtc = RTC_TEST; + timeval tv = { rtc, 0 }; + timezone tz = { TZ_MN + DST_MN, 0 }; + settimeofday(&tv, &tz); + +#else // ntp + + configTime(TZ_SEC, DST_SEC, "pool.ntp.org"); + WiFi.mode(WIFI_STA); + WiFi.begin(SSID, SSIDPWD); + // don't wait, observe time changing when ntp timestamp is received + +#endif // ntp +} + +// for testing purpose: +extern "C" int clock_gettime(clockid_t unused, struct timespec *tp); + +#define PTM(w) \ + Serial.print(":" #w "="); \ + Serial.print(tm->tm_##w); + +void printTm (const char* what, const tm* tm) { + Serial.print(what); + PTM(isdst); PTM(yday); PTM(wday); + PTM(year); PTM(mon); PTM(mday); + PTM(hour); PTM(min); PTM(sec); +} + +timeval tv; +timespec tp; +time_t now; +uint32_t now_ms, now_us; + +void loop() { + + gettimeofday(&tv, nullptr); + clock_gettime(0, &tp); + now = time(nullptr); + now_ms = millis(); + now_us = micros(); + + // localtime / gmtime every second change + static time_t lastv = 0; + if (lastv != tv.tv_sec) { + lastv = tv.tv_sec; + Serial.println(); + printTm("localtime", localtime(&now)); + Serial.println(); + printTm("gmtime ", gmtime(&now)); + Serial.println(); + Serial.println(); + } + + // time from boot + Serial.print("clock:"); + Serial.print((uint32_t)tp.tv_sec); + Serial.print("/"); + Serial.print((uint32_t)tp.tv_nsec); + Serial.print("ns"); + + // time from boot + Serial.print(" millis:"); + Serial.print(now_ms); + Serial.print(" micros:"); + Serial.print(now_us); + + // EPOCH+tz+dst + Serial.print(" gtod:"); + Serial.print((uint32_t)tv.tv_sec); + Serial.print("/"); + Serial.print((uint32_t)tv.tv_usec); + Serial.print("us"); + + // EPOCH+tz+dst + Serial.print(" time:"); + Serial.print((uint32_t)now); + + // human readable + Serial.print(" ctime:(UTC+"); + Serial.print((uint32_t)(TZ * 60 + DST_MN)); + Serial.print("mn)"); + Serial.print(ctime(&now)); + + // simple drifting loop + delay(100); +} diff --git a/tools/sdk/lib/liblwip2.a b/tools/sdk/lib/liblwip2.a index 4c3ef868b..ad0064ef8 100644 Binary files a/tools/sdk/lib/liblwip2.a and b/tools/sdk/lib/liblwip2.a differ diff --git a/tools/sdk/lwip2/builder b/tools/sdk/lwip2/builder index 9b6966aea..693bfddbe 160000 --- a/tools/sdk/lwip2/builder +++ b/tools/sdk/lwip2/builder @@ -1 +1 @@ -Subproject commit 9b6966aea4c66ff5d1c6c077dee2641f848c4fdb +Subproject commit 693bfddbe274ade1b7aec731e8c688c073b976c5 diff --git a/tools/sdk/lwip2/include/arch/cc.h b/tools/sdk/lwip2/include/arch/cc.h index 98046e42c..5fd6f4d04 100644 --- a/tools/sdk/lwip2/include/arch/cc.h +++ b/tools/sdk/lwip2/include/arch/cc.h @@ -27,7 +27,7 @@ author: d. gauchard */ -// version for esp8266 sdk-2.0.0(656edbf) +// version for esp8266 sdk-2.0.0(656edbf) and later #ifndef LWIP2_ARCH_CC_H #define LWIP2_ARCH_CC_H @@ -48,12 +48,12 @@ extern "C" #include "ets_sys.h" #include "osapi.h" #include "esp-missing.h" -#ifdef __cplusplus -} -#endif void sntp_set_system_time (uint32_t t); +#ifdef __cplusplus +} +#endif #endif // defined(LWIP_BUILD) #include "mem.h" // useful for os_malloc used in esp-arduino's mDNS diff --git a/tools/sdk/lwip2/include/lwipopts.h b/tools/sdk/lwip2/include/lwipopts.h index a9c1c2a5d..55d25072d 100644 --- a/tools/sdk/lwip2/include/lwipopts.h +++ b/tools/sdk/lwip2/include/lwipopts.h @@ -2986,9 +2986,12 @@ -------------------------------------------------- */ #define SNTP_SERVER_DNS 1 // SNTP support DNS names through sntp_setservername / sntp_getservername -#define SNTP_SERVER_ADDRESS "pool.ntp.org" // default +// if SNTP_SERVER_ADDRESS is defined, it always overrides user's config +// so we do not define it. sntp server can come from dhcp server, or by +// user. +//#define SNTP_SERVER_ADDRESS "pool.ntp.org" // default #define SNTP_GET_SERVERS_FROM_DHCP 1 -#define SNTP_SET_SYSTEM_TIME(t) (sntp_set_system_time(t)) // implemented in lwip2-sntp.c +#define SNTP_SET_SYSTEM_TIME_US(t,us) do { struct timeval tv = { t, us }; settimeofday(&tv, NULL); } while (0) /* -------------------------------------------------- @@ -2998,5 +3001,6 @@ #include "lwip/debug.h" #include "arch/cc.h" #include "lwip-git-hash.h" +#include // settimeofday() + struct timeval #endif // MYLWIPOPTS_H