From 72434b015193f77e2a1060f48fbe22075148fc17 Mon Sep 17 00:00:00 2001 From: Chris Liebman Date: Fri, 10 Nov 2017 19:51:14 -0800 Subject: [PATCH] switch to adafruit ultimate GPS, drop all the DS3231 rtc junk --- DS3231.cpp | 231 --------------------- DS3231.h | 101 ---------- DS3231DateTime.cpp | 214 -------------------- DS3231DateTime.h | 60 ------ ESPNTPServer.cpp | 296 +++++++++++++++++++-------- ESPNTPServer.h | 29 ++- TimeUtils.cpp | 491 --------------------------------------------- TimeUtils.h | 55 ----- 8 files changed, 238 insertions(+), 1239 deletions(-) delete mode 100644 DS3231.cpp delete mode 100644 DS3231.h delete mode 100644 DS3231DateTime.cpp delete mode 100644 DS3231DateTime.h delete mode 100644 TimeUtils.cpp delete mode 100644 TimeUtils.h diff --git a/DS3231.cpp b/DS3231.cpp deleted file mode 100644 index 0f6448d..0000000 --- a/DS3231.cpp +++ /dev/null @@ -1,231 +0,0 @@ -/* - * DS3231.cpp - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 21, 2017 - * Author: chris.l - */ - -#include "DS3231.h" - -//#define DEBUG -#include "Logger.h" - -DS3231::DS3231() -{ -} - -int DS3231::begin() -{ - dbprintln("DS3231::begin()"); - - delay(2500); // DS3231 may need this on powerup. - - uint8_t ctrl = 0b00000000; // enable osc/no BBS/no CONV/1hz/SQWV on/no ALRM - int err = write(DS3231_CONTROL_REG, ctrl); //CONTROL Register Address - if (err) - { - dbprintf("DS3231(): write(DS3231_CONTROL_REG) failed: %d\n", err); - return -1; - } - - delay(100); - - // set the clock to 24hr format - uint8_t hr; - if (read(DS3231_HOUR_REG, &hr)) - { - dbprintf("DS3231::begin(): read(DS3231_HOUR_REG) failed!\n"); - return -1; - } - - // switch to 24hr mode if its not already - if (hr & _BV(DS3231_AMPM)) - { - hr &= ~_BV(DS3231_AMPM); - - if(write(DS3231_HOUR_REG, hr)) - { - dbprintf("DS3231::begin(): write(DS3231_HOUR_REG, 0x%02x) failed!\n", hr); - return -1; - } - } - - return 0; -} - -uint8_t DS3231::fromBCD(uint8_t val) -{ - return val - 6 * (val >> 4); -} - -uint8_t DS3231::toBCD(uint8_t val) -{ - return val + 6 * (val / 10); -} - -int DS3231::readTime(DS3231DateTime& dt) -{ - - uint8_t count = setupRead(DS3231_SEC_REG, 7); - if (count != 7) - { - dbprintf("DS3231::readTime: setupRead failed! count:%u = 7\n", count); - Wire.clearWriteError(); - Wire.flush(); - return -1; - } - - uint8_t seconds = Wire.read(); - uint8_t minutes = Wire.read(); - uint8_t hours = Wire.read(); - uint8_t day = Wire.read(); - uint8_t date = Wire.read(); - uint8_t month = Wire.read(); - uint8_t year = Wire.read(); - - dbprintf("DS3231::readTime raw month: 0x%02x\n", month); - - dt.seconds = fromBCD(seconds); - dt.minutes = fromBCD(minutes); - dt.hours = fromBCD(hours & DS3231_24HR_MASK); - dt.day = fromBCD(day); - dt.date = fromBCD(date); - dt.month = fromBCD(month & DS3231_MONTH_MASK); - dt.year = fromBCD(year); - dt.century = month&DS3231_CENTURY? 1 : 0; - - if (!dt.isValid()) - { - dbprintf("DS3231::readTime: result not valid!!!\n"); - Wire.clearWriteError(); - Wire.flush(); - return -1; - } - - dbprintf("DS3231::readTime %04u-%02u-%02u %02u:%02u:%02u day:%u century:%u\n", - dt.year, - dt.month, - dt.date, - dt.hours, - dt.minutes, - dt.seconds, - dt.day, - dt.century); - return 0; -} - -int DS3231::writeTime(DS3231DateTime &dt) -{ - dbprintf("DS3231::writeTime %04u-%02u-%02u %02u:%02u:%02u day:%u century:%u\n", - dt.year, - dt.month, - dt.date, - dt.hours, - dt.minutes, - dt.seconds, - dt.day, - dt.century); - - Wire.beginTransmission(DS3231_ADDRESS); - if (Wire.write(DS3231_SEC_REG) != 1) - { - Wire.endTransmission(); - dbprintln("DS3231::writeTime: Wire.write(reg=DS3231_SEC_REG) failed!"); - return -1; - } - - int count; - count = Wire.write(toBCD(dt.seconds)); - count += Wire.write(toBCD(dt.minutes)); - count += Wire.write(toBCD(dt.hours)); - count += Wire.write(toBCD(dt.day)); - count += Wire.write(toBCD(dt.date)); - count += Wire.write(toBCD(dt.month) | (dt.century ? _BV(DS3231_CENTURY) : 0)); - count += Wire.write(toBCD(dt.year)); - if (count != 7) - { - dbprintf("DS3231::writeTime: Wire.write() all fields, expected 7 got %u\n", count); - return -1; - } - int err = Wire.endTransmission(); - if (err) - { - dbprintf("DS3231::writeTime: Wire.endTransmission() returned: %d\n", err); - return -1; - } - return(0); -} - -int DS3231::setupRead(uint8_t reg, uint8_t size) -{ - Wire.beginTransmission(DS3231_ADDRESS); - if (Wire.write(reg) != 1) - { - Wire.endTransmission(); - dbprintln("DS3231::setupRead: Wire.write(DS3231_CONTROL_REG) failed!"); - return -1; - } - int err = Wire.endTransmission(); - - if (err) - { - dbprintf("DS3231::setupRead: Wire.endTransmission() returned: %d\n", err); - return -1; - } - - return Wire.requestFrom(DS3231_ADDRESS, (size_t)size); -} - -int DS3231::read(uint8_t reg, uint8_t *value) -{ - size_t count = setupRead(reg, 1); - count = Wire.requestFrom(DS3231_ADDRESS, (size_t)1); - *value = Wire.read(); - if (count != 1) - { - dbprintf("DS3231::read: Wire.requestFrom() returns %u, expected 1\n", count); - return -1; - } - return 0; -} - - -int DS3231::write(uint8_t reg, uint8_t value) -{ - Wire.beginTransmission(DS3231_ADDRESS); - if (Wire.write(reg) != 1) - { - Wire.endTransmission(); - dbprintf("DS3231::write: Wire.write(reg=%d, value=%d) failed!\n", reg, value); - return -1; - } - size_t count; - count = Wire.write(value); - if (count != 1) - { - dbprintf("DS3231::write: Wire.write(value=%u) returns %u\n", value, count); - return -1; - } - int err = Wire.endTransmission(); - if (err) - { - dbprintf("DS3231::write: Wire.endTransmission() returned: %d\n", err); - return -1; - } - return(0); -} diff --git a/DS3231.h b/DS3231.h deleted file mode 100644 index bd08c12..0000000 --- a/DS3231.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * DS3231.h - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 21, 2017 - * Author: chris.l - */ - -#ifndef DS3231_H_ -#define DS3231_H_ -#include -#include -#include "WireUtils.h" -#include "DS3231DateTime.h" - -// I2C address -const uint8_t DS3231_ADDRESS = 0x68; - -// Registers -const uint8_t DS3231_SEC_REG = 0x00; -const uint8_t DS3231_MIN_REG = 0x01; -const uint8_t DS3231_HOUR_REG = 0x02; -const uint8_t DS3231_WDAY_REG = 0x03; -const uint8_t DS3231_MDAY_REG = 0x04; -const uint8_t DS3231_MONTH_REG = 0x05; -const uint8_t DS3231_YEAR_REG = 0x06; - -const uint8_t DS3231_A1S_REG = 0x07; -const uint8_t DS3231_A1M_REG = 0x08; -const uint8_t DS3231_A1H_REG = 0x09; -const uint8_t DS3231_A1W_REG = 0x0A; - -const uint8_t DS3231_AL2M_REG = 0x0B; -const uint8_t DS3231_AL2H_REG = 0x0C; -const uint8_t DS3231_AL2W_REG = 0x0D; - -const uint8_t DS3231_CONTROL_REG = 0x0E; -const uint8_t DS3231_STATUS_REG = 0x0F; -const uint8_t DS3231_AGING_REG = 0x0F; -const uint8_t DS3231_TEMP_UP_REG = 0x11; -const uint8_t DS3231_TEMP_LOW_REG = 0x12; - -// DS3231 Control Register Bits -const uint8_t DS3231_CTL_A1IE = 0; -const uint8_t DS3231_CTL_A2IE = 1; -const uint8_t DS3231_CTL_INTCN = 2; -const uint8_t DS3231_CTL_RS1 = 3; -const uint8_t DS3231_CTL_RS2 = 4; -const uint8_t DS3231_CTL_CONV = 5; -const uint8_t DS3231_CTL_BBSQW = 6; -const uint8_t DS3231_CTL_EOSC = 7; - -// DS3231 Status Register Bits -const uint8_t DS3231_STS_A1F = 0; -const uint8_t DS3231_STS_A2F = 1; -const uint8_t DS3231_STS_BSY = 2; -const uint8_t DS3231_STS_EN32KHZ = 3; -const uint8_t DS3231_STS_OSF = 7; - -// DS3231 Hour register -const uint8_t DS3231_AMPM = 6; // AMPM bit -const uint8_t DS3231_24HR_MASK = 0x3f; // mask for 24 hour value - -// DS3231 Month register -const uint8_t DS3231_CENTURY = 7; -const uint8_t DS3231_MONTH_MASK = 0x1f; - -#define RTC_POSITION_ERROR 0xffff - - -class DS3231 -{ -public: - DS3231(); - int begin(); - int readTime(DS3231DateTime& dt); // return 0 if ok - int writeTime(DS3231DateTime& dt); // return 0 if ok - -private: - uint8_t fromBCD(uint8_t val); - uint8_t toBCD(uint8_t val); - int setupRead(uint8_t reg, uint8_t size); - int write(uint8_t reg, uint8_t value); - int read(uint8_t reg, uint8_t* value); -}; - -#endif /* DS3231_H_ */ diff --git a/DS3231DateTime.cpp b/DS3231DateTime.cpp deleted file mode 100644 index 82a95f7..0000000 --- a/DS3231DateTime.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * DS3231DateTime.cpp - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 22, 2017 - * Author: chris.l - */ - -#include "DS3231DateTime.h" - - -//#define DEBUG -#include "Logger.h" - -#if defined(DEBUG) -#define dbvalue(prefix) { \ - dbprintf("%s position:%u (%04u-%02u-%02u %02u:%02u:%02u) weekday:%u century:%d unix:%lu\n", \ - prefix, \ - getPosition(), \ - year+1900+100, \ - month, \ - date, \ - hours, \ - minutes, \ - seconds, \ - day, \ - century, \ - getUnixTime());} -#else -#define dbvalue(prefix) -#endif - - -DS3231DateTime::DS3231DateTime() -{ - seconds = 0; - minutes = 0; - hours = 0; - date = 0; - day = 0; - month = 0; - year = 0; - century = 0; -} - -boolean DS3231DateTime::isValid() -{ - dbvalue("DS3231DateTime::isValid"); - - if (seconds > 59) - { - dbprintf("invalid seconds %d\n", seconds); - return false; - } - - if (minutes > 59) - { - dbprintf("invalid minutes %d\n", minutes); - return false; - } - - if (hours > 23) - { - dbprintf("invalid hours %d\n", hours); - return false; - } - - if (date > 31) - { - dbprintf("invalid hours %d\n", hours); - return false; - } - - if ((month > 12) || (month < 1)) - { - dbprintf("invalid month %d\n", month); - return false; - } - - if (year > 99) - { - dbprintf("invalid year %d\n", year); - return false; - } - - return true; -} - -void DS3231DateTime::setUnixTime(unsigned long time) -{ - struct tm tm; - TimeUtils::gmtime_r((time_t*)&time, &tm); - - dbprintf("DS3231DateTime::setUnixTime month: %d\n", tm.tm_mon); - - seconds = tm.tm_sec; - minutes = tm.tm_min; - hours = tm.tm_hour; - day = tm.tm_wday; - date = tm.tm_mday; - month = tm.tm_mon + 1; - year = tm.tm_year - 100; - century = 0; - dbvalue("setUnixTime new value:"); -} - -unsigned long DS3231DateTime::getUnixTime() -{ - struct tm tm; - tm.tm_sec = seconds; - tm.tm_min = minutes; - tm.tm_hour = hours; - tm.tm_mday = date; - tm.tm_mon = month - 1; - tm.tm_year = year + 100; - tm.tm_isdst = 0; - tm.tm_wday = 0; - tm.tm_yday = 0; - unsigned long unix = TimeUtils::mktime(&tm); - dbprintf("returning unix time: %lu\n", unix); - return unix; -} - -void DS3231DateTime::applyOffset(int offset) -{ - unsigned long now = getUnixTime(); - now += offset; - setUnixTime(now); -} - -uint16_t DS3231DateTime::getPosition() -{ - int h = hours; - if (h > 12) - { - h -= 12; - } - return h*3600 + minutes*60 + seconds; -} - -uint16_t DS3231DateTime::getPosition(int offset) -{ - int signed_position = getPosition(); - dbprintf("position before offset: %d\n", signed_position); - signed_position += offset; - dbprintf("position after offset: %d\n", signed_position); - if (signed_position < 0) - { - signed_position += MAX_POSITION; - dbprintf("position corrected+: %d\n", signed_position); - } - else if (signed_position >= MAX_POSITION) - { - signed_position -= MAX_POSITION; - dbprintf("position corrected-: %d\n", signed_position); - } - uint16_t position = (uint16_t) signed_position; - return position; -} - -char value_buf[128]; - -const char* DS3231DateTime::string() -{ - snprintf(value_buf, 127, - "%04u-%02u-%02u %02u:%02u:%02u", - year+1900+100, - month, - date, - hours, - minutes, - seconds); - return value_buf; -} - -uint8_t DS3231DateTime::getDay() -{ - return day; -} - -uint8_t DS3231DateTime::getDate() -{ - return date; -} - -uint8_t DS3231DateTime::getHour() -{ - return hours; -} - -uint8_t DS3231DateTime::getMonth() -{ - return month; -} - -uint16_t DS3231DateTime::getYear() -{ - return year+2000; -} - diff --git a/DS3231DateTime.h b/DS3231DateTime.h deleted file mode 100644 index 28658c5..0000000 --- a/DS3231DateTime.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * DS3231DateTime.h - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: May 22, 2017 - * Author: chris.l - */ - -#ifndef DS3231DATETIME_H_ -#define DS3231DATETIME_H_ -#include -#include "TimeUtils.h" - -#define MAX_POSITION 43200 // seconds in 12 hours - -class DS3231DateTime -{ -public: - DS3231DateTime(); - boolean isValid(); - void setUnixTime(unsigned long time); - unsigned long getUnixTime(); - void applyOffset(int offset); - uint16_t getPosition(); - uint16_t getPosition(int offset); - const char* string(); - - uint8_t getDay(); - uint8_t getDate(); - uint8_t getHour(); - uint8_t getMonth(); - uint16_t getYear(); - - friend class DS3231; -protected: - uint8_t seconds; - uint8_t minutes; - uint8_t hours; - uint8_t date; - uint8_t day; - uint8_t month; - uint8_t year; - uint8_t century; -}; - -#endif /* DS3231DATETIME_H_ */ diff --git a/ESPNTPServer.cpp b/ESPNTPServer.cpp index d5b41a3..9709f41 100644 --- a/ESPNTPServer.cpp +++ b/ESPNTPServer.cpp @@ -23,14 +23,17 @@ #include "ESPNTPServer.h" #include // htonl() & ntohl() #define DEBUG +//define NTP_PACKET_DEBUG #include "Logger.h" -//define NTP_PACKET_DEBUG - +Ticker validityTimer; +uint32_t valid_count; +bool valid; +bool sentence_unknown; int8_t precision; volatile uint32_t dispersion; -volatile uint32_t seconds; +volatile time_t seconds; volatile uint32_t last_micros; volatile uint32_t micros_wraps; @@ -43,9 +46,15 @@ volatile uint16_t micros_history_count; volatile uint16_t micros_history_index; #endif - +#if defined(USE_ASYNC_UDP) AsyncUDP udp; -DS3231 rtc; // real time clock on i2c interface +#else +WiFiUDP udp; +#endif + +SoftwareSerial gps(GPS_RX_PIN, GPS_TX_PIN, false, SERIAL_BUFFER_SIZE); +char nmeaBuffer[NMEA_BUFFER_SIZE]; +MicroNMEA nmea(nmeaBuffer, NMEA_BUFFER_SIZE); #ifdef NTP_PACKET_DEBUG void dumpNTPPacket(NTPPacket* ntp) @@ -70,9 +79,25 @@ void dumpNTPPacket(NTPPacket* ntp) #define dumpNTPPacket(x) #endif +void validityCheck() +{ + valid = false; +} + void oneSecondInterrupt() { uint32_t cur_micros = micros(); + + // + // restart the validity timer, if it runs out we invalidate our data. + // + validityTimer.attach_ms(VALIDITY_CHECK_MS, &validityCheck); + + // + // increment seconds + // + seconds += 1; + // // the first time around we just initialize the last value // @@ -112,12 +137,7 @@ void oneSecondInterrupt() } #endif - // - // increment seconds - // - seconds += 1; - -#if defined(DEBUG) +#if defined(DEBUG) && defined(LED_PIN) digitalWrite(LED_PIN, digitalRead(LED_PIN) ? LOW : HIGH); #endif } @@ -171,39 +191,41 @@ int8_t computePrecision() return (int8_t)prec; } -int updateSeconds() -{ - DS3231DateTime dt; - if (rtc.readTime(dt)) - { - dbprintln("updateSeconds: FAILED to read RTC, clearing bus & not checking seconds!"); - WireUtils.clearBus(); - return -1; - } - - uint32_t old_seconds = seconds; - uint32_t now = dt.getUnixTime(); - if (now != seconds) - { - seconds = now; - dbprintf("updateSeconds: updated seconds from %lu to %lu\n", old_seconds, now); - } - - return 0; -} - +#if defined(USE_ASYNC_UDP) void recievePacket(AsyncUDPPacket aup) +#else +void recievePacket() +#endif { NTPTime recv_time; getNTPTime(&recv_time); - +#if defined(USE_ASYNC_UDP) if (aup.length() != sizeof(NTPPacket)) { dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", aup.length(), sizeof(NTPPacket)); return; } +#else + if (udp.available() != sizeof(NTPPacket)) + { + dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", udp.available(), sizeof(NTPPacket)); + return; + } +#endif + + if (!valid) + { + dbprintln("recievePacket: GPS data not valid!"); + return; + } + NTPPacket ntp; - memcpy(&ntp, aup.data(), sizeof(NTPPacket)); +#if defined(USE_ASYNC_UDP) + memcpy(&ntp, aup.data(), sizeof(ntp)); +#else + udp.read((unsigned char*)&ntp, sizeof(ntp)); + +#endif ntp.delay = ntohl(ntp.delay); ntp.dispersion = ntohl(ntp.dispersion); ntp.orig_time.seconds = ntohl(ntp.orig_time.seconds); @@ -241,7 +263,131 @@ void recievePacket(AsyncUDPPacket aup) getNTPTime(&(ntp.xmit_time)); ntp.xmit_time.seconds = htonl(ntp.xmit_time.seconds); ntp.xmit_time.fraction = htonl(ntp.xmit_time.fraction); +#if defined(USE_ASYNC_UDP) aup.write((uint8_t*)&ntp, sizeof(ntp)); +#else + IPAddress address = udp.remoteIP(); + uint16_t port = udp.remotePort(); + udp.beginPacket(address, port); + udp.write((uint8_t*)&ntp, sizeof(ntp)); + udp.flush(); + udp.endPacket(); +#endif +} + +void badChecksum(MicroNMEA& mn) +{ + const char* s = mn.getSentence(); + if (s == NULL || s[0] == '\0') + { + //dbprintf("badChecksum: NULL sentence!!!!"); + return; + } + dbprintf("badChecksum: (length:%d 1stbyte:%02x) '%s'\n", strlen(s), s[0], s); +} + +void unknownSentence(MicroNMEA& mn) +{ + const char* sentence = mn.getSentence(); + if (!strncmp("$PMTK", sentence, 5)) + { + dbprintf("unknownSentence: %s\n", sentence); + return; + } + + sentence_unknown = true; + dbprintf("unknownSentence: %s\n", mn.getSentence()); +} + +void resetGPS() +{ + dbprintln("resetGPS: starting!"); + // Empty input buffer + while (gps.available()) + gps.read(); + + digitalWrite(GPS_EN_PIN, LOW); + delay(100); + digitalWrite(GPS_EN_PIN, HIGH); + + dbprintln("resetGPS: waiting on first sentence"); + dbflush(); + + // Reset is complete when the first valid message is received + while (1) + { + delay(1); + while (gps.available()) + { + char c = gps.read(); + if (nmea.process(c)) + { + const char* sentence = nmea.getSentence(); + dbprintf("resetGPS: done, sentence: '%s'\n", sentence); + dbflush(); + return; + } + } + } +} + +void processGPS() +{ + static boolean last_valid; + if (last_valid && !valid) + { + dbprintln("INVALID!"); + } + last_valid = valid; + + while(gps.available() > 0) + { + if (nmea.process(gps.read())) + { + if (nmea.isValid() && nmea.getYear() > 2000) + { + struct tm tm; + tm.tm_year = nmea.getYear() - 1900; + tm.tm_mon = nmea.getMonth() - 1; + tm.tm_mday = nmea.getDay(); + tm.tm_hour = nmea.getHour(); + tm.tm_min = nmea.getMinute(); + tm.tm_sec = nmea.getSecond(); + time_t new_seconds = mktime(&tm); + + time_t old_seconds = seconds; + if (old_seconds != new_seconds) + { + seconds = new_seconds; + dbprintf("adjusting seconds from %lu to %lu\n", old_seconds, new_seconds); + } + + // + // if we were not valid, we are now + // + if (!valid) + { + // clear stats and mark us valid + last_micros = 0; + min_micros = 0; + max_micros = 0; + valid = true; + ++valid_count; + dbprintln("VALID!"); + } + } + } + } +} + +void sendSentence(const char* sentence) +{ + char cksum[3]; + MicroNMEA::generateChecksum(sentence, cksum); + cksum[2] = '\0'; + dbprintf("sendSentence: '%s*%s'\n", sentence, cksum); + gps.printf("%s*%s\r\n", sentence, cksum); + gps.flush(); } void setup() @@ -250,8 +396,12 @@ void setup() dbprintln("\n\nStartup!"); pinMode(SYNC_PIN, INPUT); +#if defined(LED_PIN) pinMode(LED_PIN, OUTPUT); +#endif + valid = false; + valid_count = 0; seconds = 0; max_micros = 0; min_micros = 0; @@ -268,72 +418,55 @@ void setup() Wire.begin(); Wire.setClockStretchLimit(1500); - while (rtc.begin()) - { - dbprintln("RTC begin failed! Attempting recovery..."); - while (WireUtils.clearBus()) - { - delay(10000); - dbprintln("lets try that again..."); - } - delay(1000); - } - - attachInterrupt(SYNC_PIN, &oneSecondInterrupt, FALLING); - dbprintf("delay %d seconds to make sure we have a clean last_micros value\n", WARMUP_SECONDS); - delay(WARMUP_SECONDS*1000); + gps.begin(9600); + nmea.setBadChecksumHandler(&badChecksum); + nmea.setUnknownSentenceHandler(&unknownSentence); + resetGPS(); precision = computePrecision(); -#if 0 - waitForEdge(SYNC_EDGE_FALLING); - delay(2); // for some reason we get errors if we read too soon after the falling edge. - DS3231DateTime dt; - while (rtc.readTime(dt)) - { - dbprintln("setup: FAILED to read RTC"); - while (WireUtils.clearBus()) - { - delay(10000); - dbprintln("lets try that again..."); - } - delay(1000); - } - seconds = dt.getUnixTime(); -#endif - // // initialize UDP handler // +#if defined(USE_ASYNC_UDP) while(!udp.listen(NTP_PORT)) { +#else + while(!udp.begin(NTP_PORT)) { +#endif dbprintf("setup: failed to listen on port %d! Will retry in a bit...\n", NTP_PORT); delay(1000); dbprintf("setup: retrying!\n"); } - udp.onPacket(recievePacket); -} + attachInterrupt(SYNC_PIN, &oneSecondInterrupt, FALLING); +#if defined(USE_ASYNC_UDP) + udp.onPacket(recievePacket); +#endif +} void loop() { - static int last_sync_level; - - // - // insure seconds is correct after each falling edge - // - int sync_level = digitalRead(SYNC_PIN); - if (sync_level == 0 && sync_level != last_sync_level) +#if !defined(USE_ASYNC_UDP) + if (udp.parsePacket()) { - delay(2); // for some reason I get errors if we read too soon after the falling edge. - updateSeconds(); + recievePacket(); } - last_sync_level = sync_level; +#endif - static uint32_t last_seconds; + processGPS(); + if (sentence_unknown) + { + sentence_unknown = false; + // Send only RMC and GGA messages. + sendSentence("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"); + } + + static time_t last_seconds; if (seconds != last_seconds && (seconds % 60) == 0) { + #if defined(MICROS_HISTORY_SIZE) double mean = 0.0; for (int i = 0; i < micros_history_count; ++i) @@ -350,9 +483,12 @@ void loop() stdev = sqrt(stdev / micros_history_count); dbprintf("mean:%f stdev:%f ", mean, stdev); #endif - double disp = us2s(max(abs(1000000-max_micros), abs(1000000-min_micros))); - dbprintf("min:%f max:%f jitter:%f dispersion:%f\n", us2s(min_micros), us2s(max_micros), us2s(max_micros-min_micros), disp); + double disp = us2s(max(abs(MICROS_PER_SEC-max_micros), abs(MICROS_PER_SEC-min_micros))); + dbprintf("min:%f max:%f jitter:%f dispersion:%f valid_count:%lu valid:%s\n", + us2s(min_micros), us2s(max_micros), us2s(max_micros-min_micros), disp, + valid_count, valid?"true":"false"); dispersion = (uint32_t)(disp * 65536.0); } last_seconds = seconds; + //delay(1); } diff --git a/ESPNTPServer.h b/ESPNTPServer.h index 5ba1bc0..a8e16b1 100644 --- a/ESPNTPServer.h +++ b/ESPNTPServer.h @@ -20,19 +20,30 @@ * Author: liebman */ +//#define USE_ASYNC_UDP + #ifndef _ESPNTPServer_H_ #define _ESPNTPServer_H_ #include "Arduino.h" +#include "Ticker.h" #include "Wire.h" -#include "DS3231.h" -#include "ESPAsyncUDP.h" #include "WiFiManager.h" +#include "SoftwareSerial.h" +#include "MicroNMEA.h" +#include +#if defined(USE_ASYNC_UDP) +#include "ESPAsyncUDP.h" +#else +#include +#endif // pin definitions -#define LED_PIN D7 // (GPIO13) LED on pin, active low -#define SYNC_PIN D5 // (GPIO14) pin tied to 1hz square wave from RTC -#define CONFIG_PIN D6 // (GPIO12) button tied to pin +#define LED_PIN BUILTIN_LED // LED on pin, active low +#define SYNC_PIN D5 // (GPIO14) pin tied to 1hz square wave from GPS +#define GPS_RX_PIN D6 // (GPIO12) +#define GPS_TX_PIN D7 // (GPIO13) +#define GPS_EN_PIN D4 // (GPIO2) #define NTP_PORT 123 @@ -40,10 +51,14 @@ #define SYNC_EDGE_FALLING 0 #define WARMUP_SECONDS 2 -#define MICROS_HISTORY_SIZE 1000 #define PRECISION_COUNT 10000 +#define MICROS_PER_SEC 1000000 +#define SERIAL_BUFFER_SIZE 256 +#define NMEA_BUFFER_SIZE 128 +#define VALIDITY_CHECK_MS 1100 +//#define MICROS_HISTORY_SIZE 1000 -#define us2s(x) (((double)x)/1000000.0) // microseconds to seconds +#define us2s(x) (((double)x)/(double)MICROS_PER_SEC) // microseconds to seconds typedef struct ntp_time { diff --git a/TimeUtils.cpp b/TimeUtils.cpp deleted file mode 100644 index d59fb5a..0000000 --- a/TimeUtils.cpp +++ /dev/null @@ -1,491 +0,0 @@ -/* - * TimeUtils.cpp - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: Jun 7, 2017 - * Author: liebman - */ - -#include "TimeUtils.h" - -//#define DEBUG -#include "Logger.h" - -uint8_t TimeUtils::parseSmallDuration(const char* value) -{ - int i = atoi(value); - if (i < 0 || i > 255) - { - dbprintf("TimeUtils::parseSmallDuration: invalid value %s: using 32 instead!\n", value); - i = 32; - } - return (uint8_t) i; -} - -uint8_t TimeUtils::parseOccurrence(const char* occurrence_string) -{ - int i = atoi(occurrence_string); - if (i < -5 || i == 0 || i > 5) - { - dbprintf("TimeUtils::parseOccurrence: invalid value %s: using 1 instead!\n", occurrence_string); - i = 1; - } - return (uint8_t) i; -} - -uint8_t TimeUtils::parseDayOfWeek(const char* dow_string) -{ - int i = atoi(dow_string); - if (i < 0 || i > 6) - { - dbprintf("TimeUtils::parseDayOfWeek: invalid value %s: using 0 (Sunday) instead!\n", dow_string); - i = 1; - } - return (uint8_t) i; -} - -uint8_t TimeUtils::parseMonth(const char* month_string) -{ - int i = atoi(month_string); - if (i < 0 || i > 12) - { - dbprintf("TimeUtils::parseMonth: invalid value '%s': using 3 (Mar) instead!\n", month_string); - i = 1; - } - return (uint8_t) i; -} - -uint8_t TimeUtils::parseHour(const char* hour_string) -{ - int i = atoi(hour_string); - if (i < 0 || i > 23) - { - dbprintf("TimeUtils::parseMonth: invalid value '%s': using 2 instead!\n", hour_string); - i = 1; - } - return (uint8_t) i; -} - -int TimeUtils::parseOffset(const char* offset_string) -{ - int result = 0; - char value[11]; - strncpy(value, offset_string, 10); - if (strchr(value, ':') != NULL) - { - int sign = 1; - char* s; - - if (value[0] == '-') - { - sign = -1; - s = strtok(&(value[1]), ":"); - } - else - { - s = strtok(value, ":"); - } - if (s != NULL) - { - int h = atoi(s); - while (h > 11) - { - h -= 12; - } - - result += h * 3600; // hours to seconds - s = strtok(NULL, ":"); - } - if (s != NULL) - { - result += atoi(s) * 60; // minutes to seconds - s = strtok(NULL, ":"); - } - if (s != NULL) - { - result += atoi(s); - } - // apply sign - result *= sign; - } - else - { - result = atoi(value); - if (result < -43199 || result > 43199) - { - result = 0; - } - } - return result; -} - -uint16_t TimeUtils::parsePosition(const char* position_string) -{ - int result = 0; - char value[10]; - strncpy(value, position_string, 9); - if (strchr(value, ':') != NULL) - { - char* s = strtok(value, ":"); - - if (s != NULL) - { - int h = atoi(s); - while (h > 11) - { - h -= 12; - } - - result += h * 3600; // hours to seconds - s = strtok(NULL, ":"); - } - if (s != NULL) - { - result += atoi(s) * 60; // minutes to seconds - s = strtok(NULL, ":"); - } - if (s != NULL) - { - result += atoi(s); - } - } - else - { - result = atoi(value); - if (result < 0 || result > 43199) - { - result = 0; - } - } - return result; -} - - -// -// Modified code from: http://www.jbox.dk/sanos/source/lib/time.c.html -// - -#define YEAR0 1900 -#define EPOCH_YR 1970 -#define SECS_DAY (24L * 60L * 60L) -#define LEAPYEAR(year) (!((year) % 4) && (((year) % 100) || !((year) % 400))) -#define YEARSIZE(year) (LEAPYEAR(year) ? 366 : 365) -#define TIME_MAX 2147483647L - -const int _ytab[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 } }; - -// esp version is broken :-( -time_t TimeUtils::mktime(struct tm *tmbuf) -{ - long day, year; - int tm_year; - int yday, month; - /*unsigned*/long seconds; - int overflow; - - tmbuf->tm_min += tmbuf->tm_sec / 60; - tmbuf->tm_sec %= 60; - if (tmbuf->tm_sec < 0) - { - tmbuf->tm_sec += 60; - tmbuf->tm_min--; - } - tmbuf->tm_hour += tmbuf->tm_min / 60; - tmbuf->tm_min = tmbuf->tm_min % 60; - if (tmbuf->tm_min < 0) - { - tmbuf->tm_min += 60; - tmbuf->tm_hour--; - } - day = tmbuf->tm_hour / 24; - tmbuf->tm_hour = tmbuf->tm_hour % 24; - if (tmbuf->tm_hour < 0) - { - tmbuf->tm_hour += 24; - day--; - } - tmbuf->tm_year += tmbuf->tm_mon / 12; - tmbuf->tm_mon %= 12; - if (tmbuf->tm_mon < 0) - { - tmbuf->tm_mon += 12; - tmbuf->tm_year--; - } - day += (tmbuf->tm_mday - 1); - while (day < 0) - { - if (--tmbuf->tm_mon < 0) - { - tmbuf->tm_year--; - tmbuf->tm_mon = 11; - } - day += _ytab[LEAPYEAR(YEAR0 + tmbuf->tm_year)][tmbuf->tm_mon]; - } - while (day >= _ytab[LEAPYEAR(YEAR0 + tmbuf->tm_year)][tmbuf->tm_mon]) - { - day -= _ytab[LEAPYEAR(YEAR0 + tmbuf->tm_year)][tmbuf->tm_mon]; - if (++(tmbuf->tm_mon) == 12) - { - tmbuf->tm_mon = 0; - tmbuf->tm_year++; - } - } - tmbuf->tm_mday = day + 1; - year = EPOCH_YR; - if (tmbuf->tm_year < year - YEAR0) - return (time_t) -1; - seconds = 0; - day = 0; // Means days since day 0 now - overflow = 0; - - // Assume that when day becomes negative, there will certainly - // be overflow on seconds. - // The check for overflow needs not to be done for leapyears - // divisible by 400. - // The code only works when year (1970) is not a leapyear. - tm_year = tmbuf->tm_year + YEAR0; - - if (TIME_MAX / 365 < tm_year - year) - overflow++; - day = (tm_year - year) * 365; - if (TIME_MAX - day < (tm_year - year) / 4 + 1) - overflow++; - day += (tm_year - year) / 4 + ((tm_year % 4) && tm_year % 4 < year % 4); - day -= (tm_year - year) / 100 - + ((tm_year % 100) && tm_year % 100 < year % 100); - day += (tm_year - year) / 400 - + ((tm_year % 400) && tm_year % 400 < year % 400); - - yday = month = 0; - while (month < tmbuf->tm_mon) - { - yday += _ytab[LEAPYEAR(tm_year)][month]; - month++; - } - yday += (tmbuf->tm_mday - 1); - if (day + yday < 0) - overflow++; - day += yday; - - tmbuf->tm_yday = yday; - tmbuf->tm_wday = (day + 4) % 7; // Day 0 was thursday (4) - - seconds = ((tmbuf->tm_hour * 60L) + tmbuf->tm_min) * 60L + tmbuf->tm_sec; - - if ((TIME_MAX - seconds) / SECS_DAY < day) - overflow++; - seconds += day * SECS_DAY; - - if (overflow) - return (time_t) -1; - - if ((time_t) seconds != seconds) - return (time_t) -1; - return (time_t) seconds; -} - -struct tm *TimeUtils::gmtime_r(const time_t *timer, struct tm *tmbuf) -{ - time_t time = *timer; - unsigned long dayclock, dayno; - int year = EPOCH_YR; - - dayclock = (unsigned long) time % SECS_DAY; - dayno = (unsigned long) time / SECS_DAY; - - tmbuf->tm_sec = dayclock % 60; - tmbuf->tm_min = (dayclock % 3600) / 60; - tmbuf->tm_hour = dayclock / 3600; - tmbuf->tm_wday = (dayno + 4) % 7; // Day 0 was a thursday - while (dayno >= (unsigned long) YEARSIZE(year)) { - dayno -= YEARSIZE(year); - year++; - } - tmbuf->tm_year = year - YEAR0; - tmbuf->tm_yday = dayno; - tmbuf->tm_mon = 0; - while (dayno >= (unsigned long) _ytab[LEAPYEAR(year)][tmbuf->tm_mon]) { - dayno -= _ytab[LEAPYEAR(year)][tmbuf->tm_mon]; - tmbuf->tm_mon++; - } - tmbuf->tm_mday = dayno + 1; - tmbuf->tm_isdst = 0; - return tmbuf; -} - -// -// The functions findDOW & findNthDate are from: -// -// http://hackaday.com/2012/07/16/automatic-daylight-savings-time-compensation-for-your-clock-projects -// - -/*-------------------------------------------------------------------------- - FUNC: 6/11/11 - Returns day of week for any given date - PARAMS: year, month, date - RETURNS: day of week (0-7 is Sun-Sat) - NOTES: Sakamoto's Algorithm - http://en.wikipedia.org/wiki/Calculating_the_day_of_the_week#Sakamoto.27s_methods - Altered to use char when possible to save microcontroller ram ---------------------------------------------------------------------------*/ -uint8_t TimeUtils::findDOW(uint16_t y, uint8_t m, uint8_t d) -{ - static char t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; - y -= m < 3; - return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7; -} - -/*-------------------------------------------------------------------------- - http://hackaday.com/2012/07/16/automatic-daylight-savings-time-compensation-for-your-clock-projects - FUNC: 6/11/11 - Returns the date for Nth day of month. For instance, - it will return the numeric date for the 2nd Sunday of April - PARAMS: year, month, day of week, Nth occurrence of that day in that month - RETURNS: date - NOTES: There is no error checking for invalid inputs. ---------------------------------------------------------------------------*/ -uint8_t TimeUtils::findNthDate(uint16_t year, uint8_t month, uint8_t dow, uint8_t nthWeek) -{ - dbprintf("findNthDate: year:%u month:%u, dow:%u nthWeek:%d\n", year, month, dow, nthWeek); - - uint8_t targetDate = 1; - uint8_t firstDOW = findDOW(year,month,targetDate); - while (firstDOW != dow) { - firstDOW = (firstDOW+1)%7; - targetDate++; - } - //Adjust for weeks - targetDate += (nthWeek-1)*7; - return targetDate; -} - -uint8_t TimeUtils::daysInMonth(uint16_t year, uint8_t month) -{ - uint8_t days = 31; - - switch(month) - { - case 2: - days = 28; - if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) - { - days = 29; - } - break; - case 4: - case 6: - case 9: - case 11: - days = 30; - break; - } - return days; -} - -uint8_t TimeUtils::findDateForWeek(uint16_t year, uint8_t month, uint8_t dow, int8_t week) -{ - dbprintf("findDateForWeek: year:%u month:%u, dow:%u week:%d\n", year, month, dow, week); - - uint8_t weeks[5]; - uint8_t max_day = daysInMonth(year, month); - int last = 0; - - if (week >= 0) - { - return findNthDate(year, month, dow, week); - } - - // - // find all times this weekday shows up in the month - // Note that 'last' will end up pointing 1 past the last - // valid occurrence. -1 will give the last one. - // - for(last = 0; last <= 5; ++last) - { - weeks[last] = findNthDate(year, month, dow, last+1); - - dbprintf("findDateForWeek: last:%d date:%u\n", last, weeks[last]); - - if (weeks[last] > max_day) - { - break; - } - } - - return weeks[last+week]; -} - -int TimeUtils::computeUTCOffset(time_t now, int tz_offset, TimeChange* tc, int tc_count) -{ - struct tm tm; - - // - // get the current year - // - gmtime_r(&now, &tm); - int year = tm.tm_year; - - // - // pre-set the offset to the last timechange of the year - // - int offset = tc[tc_count-1].tz_offset; - - // - // loop thru each time change entry, converting it to the time in seconds for the - // current year. If now is greater/equal to the time change the use the new offset. - // We return the last offset that is greater/equal now. - // - for(int i = 0; i < tc_count; ++i) - { - dbprintf("TimeUtils::computeUTCOffset: index:%d offset:%d month:%u dow:%u occurrence:%d hour:%u day_offset:%d\n", - i, - tc[i].tz_offset, - tc[i].month, - tc[i].day_of_week, - tc[i].occurrence, - tc[i].hour, - tc[i].day_offset); - - tm.tm_sec = 0; - tm.tm_min = 0; - tm.tm_hour = tc[i].hour; - tm.tm_mday = TimeUtils::findDateForWeek(year+1900, tc[i].month, tc[i].day_of_week, tc[i].occurrence); - tm.tm_mon = tc[i].month-1; - tm.tm_year = year; - - dbprintf("computeUTCOffset: tm: %04d/%02d/%02d %02d:%02d:%02d + %d days\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tc[i].day_offset); - - // convert to seconds - time_t tc_time = mktime(&tm); - // convert to UTC - tc_time -= tz_offset; - dbprintf("computeUTCOffset: tc_time: %ld (UTC)\n", tc_time); - // add in days offset - tc_time += tc[i].day_offset*86400; - - dbprintf("computeUTCOffset: now: %ld tc_time: %ld\n", now, tc_time); - - if (now >= tc_time) - { - offset = tc[i].tz_offset; - dbprintf("computeUTCOffset: now > tc_time, offset: %d\n", offset); - } - } - - return offset; -} diff --git a/TimeUtils.h b/TimeUtils.h deleted file mode 100644 index 4c97873..0000000 --- a/TimeUtils.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * TimeUtils.h - * - * Copyright 2017 Christopher B. Liebman - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Created on: Jun 7, 2017 - * Author: liebman - */ - -#ifndef TIMEUTILS_H_ -#define TIMEUTILS_H_ -#include -#include -typedef struct -{ - int tz_offset; // seconds offset from UTC - uint8_t month; // starting month that this takes effect. - int8_t occurrence; // 2 is second occurrence of the given day, -1 is last - uint8_t day_of_week; // 0 = Sunday - uint8_t hour; - int8_t day_offset; // +/- days (for Friday before last Sunday type) -} TimeChange; - -class TimeUtils -{ -public: - static uint8_t parseSmallDuration(const char* value); - static int parseOffset(const char* offset_string); - static uint16_t parsePosition(const char* position_string); - static uint8_t parseOccurrence(const char* occurrence_string); - static uint8_t parseDayOfWeek(const char* dow_string); - static uint8_t parseMonth(const char* month_string); - static uint8_t parseHour(const char* hour_string); - static time_t mktime(struct tm *tmbuf); - static struct tm* gmtime_r(const time_t *timer, struct tm *tmbuf); - static int computeUTCOffset(time_t now, int tz_offset, TimeChange* tc, int tc_count); - static uint8_t findDOW(uint16_t y, uint8_t m, uint8_t d); - static uint8_t findNthDate(uint16_t year, uint8_t month, uint8_t dow, uint8_t nthWeek); - static uint8_t daysInMonth(uint16_t year, uint8_t month); - static uint8_t findDateForWeek(uint16_t year, uint8_t month, uint8_t dow, int8_t nthWeek); -}; -#endif /* TIMEUTILS_H_ */