switch to adafruit ultimate GPS, drop all the DS3231 rtc junk
This commit is contained in:
parent
81456c7c5f
commit
72434b0151
231
DS3231.cpp
231
DS3231.cpp
|
|
@ -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);
|
|
||||||
}
|
|
||||||
101
DS3231.h
101
DS3231.h
|
|
@ -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 <Arduino.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#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_ */
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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 <Arduino.h>
|
|
||||||
#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_ */
|
|
||||||
296
ESPNTPServer.cpp
296
ESPNTPServer.cpp
|
|
@ -23,14 +23,17 @@
|
||||||
#include "ESPNTPServer.h"
|
#include "ESPNTPServer.h"
|
||||||
#include <lwip/def.h> // htonl() & ntohl()
|
#include <lwip/def.h> // htonl() & ntohl()
|
||||||
#define DEBUG
|
#define DEBUG
|
||||||
|
//define NTP_PACKET_DEBUG
|
||||||
#include "Logger.h"
|
#include "Logger.h"
|
||||||
|
|
||||||
//define NTP_PACKET_DEBUG
|
Ticker validityTimer;
|
||||||
|
uint32_t valid_count;
|
||||||
|
bool valid;
|
||||||
|
bool sentence_unknown;
|
||||||
int8_t precision;
|
int8_t precision;
|
||||||
volatile uint32_t dispersion;
|
volatile uint32_t dispersion;
|
||||||
|
|
||||||
volatile uint32_t seconds;
|
volatile time_t seconds;
|
||||||
|
|
||||||
volatile uint32_t last_micros;
|
volatile uint32_t last_micros;
|
||||||
volatile uint32_t micros_wraps;
|
volatile uint32_t micros_wraps;
|
||||||
|
|
@ -43,9 +46,15 @@ volatile uint16_t micros_history_count;
|
||||||
volatile uint16_t micros_history_index;
|
volatile uint16_t micros_history_index;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(USE_ASYNC_UDP)
|
||||||
AsyncUDP 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
|
#ifdef NTP_PACKET_DEBUG
|
||||||
void dumpNTPPacket(NTPPacket* ntp)
|
void dumpNTPPacket(NTPPacket* ntp)
|
||||||
|
|
@ -70,9 +79,25 @@ void dumpNTPPacket(NTPPacket* ntp)
|
||||||
#define dumpNTPPacket(x)
|
#define dumpNTPPacket(x)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void validityCheck()
|
||||||
|
{
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
void oneSecondInterrupt()
|
void oneSecondInterrupt()
|
||||||
{
|
{
|
||||||
uint32_t cur_micros = micros();
|
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
|
// the first time around we just initialize the last value
|
||||||
//
|
//
|
||||||
|
|
@ -112,12 +137,7 @@ void oneSecondInterrupt()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//
|
#if defined(DEBUG) && defined(LED_PIN)
|
||||||
// increment seconds
|
|
||||||
//
|
|
||||||
seconds += 1;
|
|
||||||
|
|
||||||
#if defined(DEBUG)
|
|
||||||
digitalWrite(LED_PIN, digitalRead(LED_PIN) ? LOW : HIGH);
|
digitalWrite(LED_PIN, digitalRead(LED_PIN) ? LOW : HIGH);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
@ -171,39 +191,41 @@ int8_t computePrecision()
|
||||||
return (int8_t)prec;
|
return (int8_t)prec;
|
||||||
}
|
}
|
||||||
|
|
||||||
int updateSeconds()
|
#if defined(USE_ASYNC_UDP)
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void recievePacket(AsyncUDPPacket aup)
|
void recievePacket(AsyncUDPPacket aup)
|
||||||
|
#else
|
||||||
|
void recievePacket()
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
NTPTime recv_time;
|
NTPTime recv_time;
|
||||||
getNTPTime(&recv_time);
|
getNTPTime(&recv_time);
|
||||||
|
#if defined(USE_ASYNC_UDP)
|
||||||
if (aup.length() != sizeof(NTPPacket))
|
if (aup.length() != sizeof(NTPPacket))
|
||||||
{
|
{
|
||||||
dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", aup.length(), sizeof(NTPPacket));
|
dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", aup.length(), sizeof(NTPPacket));
|
||||||
return;
|
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;
|
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.delay = ntohl(ntp.delay);
|
||||||
ntp.dispersion = ntohl(ntp.dispersion);
|
ntp.dispersion = ntohl(ntp.dispersion);
|
||||||
ntp.orig_time.seconds = ntohl(ntp.orig_time.seconds);
|
ntp.orig_time.seconds = ntohl(ntp.orig_time.seconds);
|
||||||
|
|
@ -241,7 +263,131 @@ void recievePacket(AsyncUDPPacket aup)
|
||||||
getNTPTime(&(ntp.xmit_time));
|
getNTPTime(&(ntp.xmit_time));
|
||||||
ntp.xmit_time.seconds = htonl(ntp.xmit_time.seconds);
|
ntp.xmit_time.seconds = htonl(ntp.xmit_time.seconds);
|
||||||
ntp.xmit_time.fraction = htonl(ntp.xmit_time.fraction);
|
ntp.xmit_time.fraction = htonl(ntp.xmit_time.fraction);
|
||||||
|
#if defined(USE_ASYNC_UDP)
|
||||||
aup.write((uint8_t*)&ntp, sizeof(ntp));
|
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()
|
void setup()
|
||||||
|
|
@ -250,8 +396,12 @@ void setup()
|
||||||
dbprintln("\n\nStartup!");
|
dbprintln("\n\nStartup!");
|
||||||
|
|
||||||
pinMode(SYNC_PIN, INPUT);
|
pinMode(SYNC_PIN, INPUT);
|
||||||
|
#if defined(LED_PIN)
|
||||||
pinMode(LED_PIN, OUTPUT);
|
pinMode(LED_PIN, OUTPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
valid = false;
|
||||||
|
valid_count = 0;
|
||||||
seconds = 0;
|
seconds = 0;
|
||||||
max_micros = 0;
|
max_micros = 0;
|
||||||
min_micros = 0;
|
min_micros = 0;
|
||||||
|
|
@ -268,72 +418,55 @@ void setup()
|
||||||
|
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
Wire.setClockStretchLimit(1500);
|
Wire.setClockStretchLimit(1500);
|
||||||
while (rtc.begin())
|
|
||||||
{
|
|
||||||
dbprintln("RTC begin failed! Attempting recovery...");
|
|
||||||
|
|
||||||
while (WireUtils.clearBus())
|
gps.begin(9600);
|
||||||
{
|
nmea.setBadChecksumHandler(&badChecksum);
|
||||||
delay(10000);
|
nmea.setUnknownSentenceHandler(&unknownSentence);
|
||||||
dbprintln("lets try that again...");
|
resetGPS();
|
||||||
}
|
|
||||||
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);
|
|
||||||
|
|
||||||
precision = computePrecision();
|
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
|
// initialize UDP handler
|
||||||
//
|
//
|
||||||
|
#if defined(USE_ASYNC_UDP)
|
||||||
while(!udp.listen(NTP_PORT)) {
|
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);
|
dbprintf("setup: failed to listen on port %d! Will retry in a bit...\n", NTP_PORT);
|
||||||
delay(1000);
|
delay(1000);
|
||||||
dbprintf("setup: retrying!\n");
|
dbprintf("setup: retrying!\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
udp.onPacket(recievePacket);
|
attachInterrupt(SYNC_PIN, &oneSecondInterrupt, FALLING);
|
||||||
}
|
|
||||||
|
|
||||||
|
#if defined(USE_ASYNC_UDP)
|
||||||
|
udp.onPacket(recievePacket);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
static int last_sync_level;
|
#if !defined(USE_ASYNC_UDP)
|
||||||
|
if (udp.parsePacket())
|
||||||
//
|
|
||||||
// insure seconds is correct after each falling edge
|
|
||||||
//
|
|
||||||
int sync_level = digitalRead(SYNC_PIN);
|
|
||||||
if (sync_level == 0 && sync_level != last_sync_level)
|
|
||||||
{
|
{
|
||||||
delay(2); // for some reason I get errors if we read too soon after the falling edge.
|
recievePacket();
|
||||||
updateSeconds();
|
|
||||||
}
|
}
|
||||||
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 (seconds != last_seconds && (seconds % 60) == 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
#if defined(MICROS_HISTORY_SIZE)
|
#if defined(MICROS_HISTORY_SIZE)
|
||||||
double mean = 0.0;
|
double mean = 0.0;
|
||||||
for (int i = 0; i < micros_history_count; ++i)
|
for (int i = 0; i < micros_history_count; ++i)
|
||||||
|
|
@ -350,9 +483,12 @@ void loop()
|
||||||
stdev = sqrt(stdev / micros_history_count);
|
stdev = sqrt(stdev / micros_history_count);
|
||||||
dbprintf("mean:%f stdev:%f ", mean, stdev);
|
dbprintf("mean:%f stdev:%f ", mean, stdev);
|
||||||
#endif
|
#endif
|
||||||
double disp = us2s(max(abs(1000000-max_micros), abs(1000000-min_micros)));
|
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\n", us2s(min_micros), us2s(max_micros), us2s(max_micros-min_micros), disp);
|
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);
|
dispersion = (uint32_t)(disp * 65536.0);
|
||||||
}
|
}
|
||||||
last_seconds = seconds;
|
last_seconds = seconds;
|
||||||
|
//delay(1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,19 +20,30 @@
|
||||||
* Author: liebman
|
* Author: liebman
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
//#define USE_ASYNC_UDP
|
||||||
|
|
||||||
#ifndef _ESPNTPServer_H_
|
#ifndef _ESPNTPServer_H_
|
||||||
#define _ESPNTPServer_H_
|
#define _ESPNTPServer_H_
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
|
#include "Ticker.h"
|
||||||
#include "Wire.h"
|
#include "Wire.h"
|
||||||
#include "DS3231.h"
|
|
||||||
#include "ESPAsyncUDP.h"
|
|
||||||
#include "WiFiManager.h"
|
#include "WiFiManager.h"
|
||||||
|
#include "SoftwareSerial.h"
|
||||||
|
#include "MicroNMEA.h"
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#if defined(USE_ASYNC_UDP)
|
||||||
|
#include "ESPAsyncUDP.h"
|
||||||
|
#else
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// pin definitions
|
// pin definitions
|
||||||
#define LED_PIN D7 // (GPIO13) LED on pin, active low
|
#define LED_PIN BUILTIN_LED // LED on pin, active low
|
||||||
#define SYNC_PIN D5 // (GPIO14) pin tied to 1hz square wave from RTC
|
#define SYNC_PIN D5 // (GPIO14) pin tied to 1hz square wave from GPS
|
||||||
#define CONFIG_PIN D6 // (GPIO12) button tied to pin
|
#define GPS_RX_PIN D6 // (GPIO12)
|
||||||
|
#define GPS_TX_PIN D7 // (GPIO13)
|
||||||
|
#define GPS_EN_PIN D4 // (GPIO2)
|
||||||
|
|
||||||
#define NTP_PORT 123
|
#define NTP_PORT 123
|
||||||
|
|
||||||
|
|
@ -40,10 +51,14 @@
|
||||||
#define SYNC_EDGE_FALLING 0
|
#define SYNC_EDGE_FALLING 0
|
||||||
|
|
||||||
#define WARMUP_SECONDS 2
|
#define WARMUP_SECONDS 2
|
||||||
#define MICROS_HISTORY_SIZE 1000
|
|
||||||
#define PRECISION_COUNT 10000
|
#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
|
typedef struct ntp_time
|
||||||
{
|
{
|
||||||
|
|
|
||||||
491
TimeUtils.cpp
491
TimeUtils.cpp
|
|
@ -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;
|
|
||||||
}
|
|
||||||
55
TimeUtils.h
55
TimeUtils.h
|
|
@ -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 <Arduino.h>
|
|
||||||
#include <time.h>
|
|
||||||
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_ */
|
|
||||||
Loading…
Reference in New Issue
Block a user