Initial checkin, hacky code, using DS3231 instead of GPS unit.
This commit is contained in:
parent
822d59009a
commit
9c65ef10e9
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
.metadata
|
||||||
|
.DS_Store
|
||||||
|
.ino.cpp
|
||||||
|
.project
|
||||||
|
.cproject
|
||||||
|
.settings
|
||||||
|
Release
|
||||||
|
Debug
|
||||||
|
libraries
|
||||||
|
core
|
||||||
|
*.bak
|
||||||
|
*-bak
|
||||||
231
DS3231.cpp
Normal file
231
DS3231.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
||||||
|
/*
|
||||||
|
* 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
Normal file
101
DS3231.h
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* 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_ */
|
||||||
214
DS3231DateTime.cpp
Normal file
214
DS3231DateTime.cpp
Normal file
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
60
DS3231DateTime.h
Normal file
60
DS3231DateTime.h
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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_ */
|
||||||
279
ESPNTPServer.cpp
Normal file
279
ESPNTPServer.cpp
Normal file
|
|
@ -0,0 +1,279 @@
|
||||||
|
/*
|
||||||
|
* ESPNTPServer.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: Oct 29, 2017
|
||||||
|
* Author: liebman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ESPNTPServer.h"
|
||||||
|
#define DEBUG
|
||||||
|
#include "Logger.h"
|
||||||
|
#include <lwip/def.h> // htonl() & ntohl()
|
||||||
|
|
||||||
|
volatile uint32_t seconds;
|
||||||
|
volatile uint32_t cycles;
|
||||||
|
volatile uint32_t last_cpu_cycles;
|
||||||
|
volatile uint32_t min_cycles;
|
||||||
|
volatile uint32_t max_cycles;
|
||||||
|
|
||||||
|
AsyncUDP udp;
|
||||||
|
DS3231 rtc; // real time clock on i2c interface
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
void dumpNTPPacket(NTPPacket* ntp)
|
||||||
|
{
|
||||||
|
dbprintf("size: %u\n", sizeof(*ntp));
|
||||||
|
dbprintf("firstbyte: 0x%02x\n", *(uint8_t*)ntp);
|
||||||
|
dbprintf("li: %u\n", getLI(ntp->flags));
|
||||||
|
dbprintf("version: %u\n", getVERS(ntp->flags));
|
||||||
|
dbprintf("mode: %u\n", getMODE(ntp->flags));
|
||||||
|
dbprintf("stratum: %u\n", ntp->stratum);
|
||||||
|
dbprintf("poll: %u\n", ntp->poll);
|
||||||
|
dbprintf("precision: %d\n", ntp->precision);
|
||||||
|
dbprintf("delay: %u\n", ntp->delay);
|
||||||
|
dbprintf("dispersion: %u\n", ntp->dispersion);
|
||||||
|
dbprintf("ref_id: %02x:%02x:%02x:%02x\n", ntp->ref_id[0], ntp->ref_id[1], ntp->ref_id[2], ntp->ref_id[3]);
|
||||||
|
dbprintf("ref_time: %08x:%08x\n", ntp->ref_time.seconds, ntp->ref_time.fraction);
|
||||||
|
dbprintf("orig_time: %08x:%08x\n", ntp->orig_time.seconds, ntp->orig_time.fraction);
|
||||||
|
dbprintf("recv_time: %08x:%08x\n", ntp->recv_time.seconds, ntp->recv_time.fraction);
|
||||||
|
dbprintf("xmit_time: %08x:%08x\n", ntp->xmit_time.seconds, ntp->xmit_time.fraction);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#define dumpNTPPacket(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void oneSecondInterrupt()
|
||||||
|
{
|
||||||
|
uint32_t cpu_cycles = ESP.getCycleCount();
|
||||||
|
//
|
||||||
|
// the first time around we just initialize the last value
|
||||||
|
//
|
||||||
|
if (last_cpu_cycles == 0)
|
||||||
|
{
|
||||||
|
last_cpu_cycles = cpu_cycles;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cycles = cpu_cycles - last_cpu_cycles;
|
||||||
|
last_cpu_cycles = cpu_cycles;
|
||||||
|
seconds += 1;
|
||||||
|
|
||||||
|
if (min_cycles == 0 || cycles < min_cycles)
|
||||||
|
{
|
||||||
|
min_cycles = cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cycles > max_cycles)
|
||||||
|
{
|
||||||
|
max_cycles = cycles;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(DEBUG)
|
||||||
|
digitalWrite(LED_PIN, digitalRead(LED_PIN) ? LOW : HIGH);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForEdge(int edge)
|
||||||
|
{
|
||||||
|
while (digitalRead(SYNC_PIN) == edge)
|
||||||
|
{
|
||||||
|
delay(0);
|
||||||
|
}
|
||||||
|
while (digitalRead(SYNC_PIN) != edge)
|
||||||
|
{
|
||||||
|
delay(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getNTPTime(NTPTime *time)
|
||||||
|
{
|
||||||
|
time->seconds = toNTP(seconds);
|
||||||
|
uint32_t cpu_cycles = ESP.getCycleCount();
|
||||||
|
uint32_t cycles_delta = cpu_cycles - last_cpu_cycles;
|
||||||
|
|
||||||
|
//
|
||||||
|
// if cycles_delta is at or bigger than cycles then
|
||||||
|
// use the max fraction.
|
||||||
|
//
|
||||||
|
if (cycles_delta >= cycles)
|
||||||
|
{
|
||||||
|
time->fraction = 0xffffffff;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double percent = (double)cycles_delta / (double)cycles;
|
||||||
|
//dbprintf("cycles_delta: %lu cycles: %lu percent: %lf\n", cycles_delta, cycles, percent);
|
||||||
|
time->fraction = (uint32_t)(percent * (double)4294967296L);
|
||||||
|
}
|
||||||
|
|
||||||
|
void recievePacket(AsyncUDPPacket aup)
|
||||||
|
{
|
||||||
|
NTPTime recv_time;
|
||||||
|
getNTPTime(&recv_time);
|
||||||
|
|
||||||
|
if (aup.length() != sizeof(NTPPacket))
|
||||||
|
{
|
||||||
|
dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", aup.length(), sizeof(NTPPacket));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NTPPacket ntp;
|
||||||
|
memcpy(&ntp, aup.data(), sizeof(NTPPacket));
|
||||||
|
ntp.delay = ntohl(ntp.delay);
|
||||||
|
ntp.dispersion = ntohl(ntp.dispersion);
|
||||||
|
ntp.orig_time.seconds = ntohl(ntp.orig_time.seconds);
|
||||||
|
ntp.orig_time.fraction = ntohl(ntp.orig_time.fraction);
|
||||||
|
ntp.ref_time.seconds = ntohl(ntp.ref_time.seconds);
|
||||||
|
ntp.ref_time.fraction = ntohl(ntp.ref_time.fraction);
|
||||||
|
ntp.recv_time.seconds = ntohl(ntp.recv_time.seconds);
|
||||||
|
ntp.recv_time.fraction = ntohl(ntp.recv_time.fraction);
|
||||||
|
ntp.xmit_time.seconds = ntohl(ntp.xmit_time.seconds);
|
||||||
|
ntp.xmit_time.fraction = ntohl(ntp.xmit_time.fraction);
|
||||||
|
dumpNTPPacket(&ntp);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Build the response
|
||||||
|
//
|
||||||
|
ntp.flags = setLI(LI_NONE) | setVERS(NTP_VERSION) | setMODE(MODE_SERVER);
|
||||||
|
ntp.stratum = 1;
|
||||||
|
ntp.precision = -18;
|
||||||
|
// TODO: root delay, and root dispersion
|
||||||
|
strncpy((char*)ntp.ref_id, REF_ID, sizeof(ntp.ref_id));
|
||||||
|
ntp.orig_time = ntp.xmit_time;
|
||||||
|
ntp.recv_time = recv_time;
|
||||||
|
getNTPTime(&(ntp.ref_time));
|
||||||
|
dumpNTPPacket(&ntp);
|
||||||
|
ntp.delay = htonl(ntp.delay);
|
||||||
|
ntp.dispersion = htonl(ntp.dispersion);
|
||||||
|
ntp.orig_time.seconds = htonl(ntp.orig_time.seconds);
|
||||||
|
ntp.orig_time.fraction = htonl(ntp.orig_time.fraction);
|
||||||
|
ntp.ref_time.seconds = htonl(ntp.ref_time.seconds);
|
||||||
|
ntp.ref_time.fraction = htonl(ntp.ref_time.fraction);
|
||||||
|
ntp.recv_time.seconds = htonl(ntp.recv_time.seconds);
|
||||||
|
ntp.recv_time.fraction = htonl(ntp.recv_time.fraction);
|
||||||
|
getNTPTime(&(ntp.xmit_time));
|
||||||
|
ntp.xmit_time.seconds = htonl(ntp.xmit_time.seconds);
|
||||||
|
ntp.xmit_time.fraction = htonl(ntp.xmit_time.fraction);
|
||||||
|
aup.write((uint8_t*)&ntp, sizeof(ntp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
dbbegin(115200);
|
||||||
|
dbprintln("");
|
||||||
|
dbprintln("Startup!");
|
||||||
|
|
||||||
|
pinMode(SYNC_PIN, INPUT);
|
||||||
|
pinMode(LED_PIN, OUTPUT);
|
||||||
|
|
||||||
|
seconds = 0;
|
||||||
|
cycles = 0;
|
||||||
|
max_cycles = 0;
|
||||||
|
min_cycles = 0;
|
||||||
|
last_cpu_cycles = 0;
|
||||||
|
|
||||||
|
attachInterrupt(SYNC_PIN, &oneSecondInterrupt, FALLING);
|
||||||
|
dbprintf("delay 2 seconds to make sure we have a clean cycle count\n");
|
||||||
|
delay(2000);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
WiFiManager wifi;
|
||||||
|
//wifi.setDebugOutput(false);
|
||||||
|
String ssid = "SynchroClock" + String(ESP.getChipId());
|
||||||
|
wifi.autoConnect(ssid.c_str(), NULL);
|
||||||
|
|
||||||
|
//
|
||||||
|
// initialize UDP handler
|
||||||
|
//
|
||||||
|
while(!udp.listen(NTP_PORT)) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
DS3231DateTime dt;
|
||||||
|
delay(2); // for some reason we get errors if we read too soon after the falling edge.
|
||||||
|
if (rtc.readTime(dt))
|
||||||
|
{
|
||||||
|
dbprintln("loop: FAILED to read RTC, clearing bus & not checking seconds!");
|
||||||
|
WireUtils.clearBus();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint32_t old_seconds = seconds;
|
||||||
|
uint32_t now = dt.getUnixTime();
|
||||||
|
if (now != seconds)
|
||||||
|
{
|
||||||
|
seconds = now;
|
||||||
|
dbprintf("loop: updated seconds from %lu to %lu\n", old_seconds, now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_sync_level = sync_level;
|
||||||
|
|
||||||
|
#if 1
|
||||||
|
static uint32_t last_seconds;
|
||||||
|
if (seconds != last_seconds && (seconds % 300) == 0)
|
||||||
|
{
|
||||||
|
dbprintf("min_cycles:%lu max_cycles:%lu cycles:%lu\n", min_cycles, max_cycles, cycles);
|
||||||
|
}
|
||||||
|
last_seconds = seconds;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
93
ESPNTPServer.h
Normal file
93
ESPNTPServer.h
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* ESPNTPServer.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: Oct 29, 2017
|
||||||
|
* Author: liebman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _ESPNTPServer_H_
|
||||||
|
#define _ESPNTPServer_H_
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Wire.h"
|
||||||
|
#include "DS3231.h"
|
||||||
|
#include "ESPAsyncUDP.h"
|
||||||
|
#include "WiFiManager.h"
|
||||||
|
|
||||||
|
|
||||||
|
// 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 NTP_PORT 123
|
||||||
|
|
||||||
|
#define SYNC_EDGE_RISING 1
|
||||||
|
#define SYNC_EDGE_FALLING 0
|
||||||
|
|
||||||
|
typedef struct ntp_time
|
||||||
|
{
|
||||||
|
uint32_t seconds;
|
||||||
|
uint32_t fraction;
|
||||||
|
} NTPTime;
|
||||||
|
|
||||||
|
typedef struct ntp_packet
|
||||||
|
{
|
||||||
|
uint8_t flags;
|
||||||
|
uint8_t stratum;
|
||||||
|
uint8_t poll;
|
||||||
|
int8_t precision;
|
||||||
|
uint32_t delay;
|
||||||
|
uint32_t dispersion;
|
||||||
|
uint8_t ref_id[4];
|
||||||
|
NTPTime ref_time;
|
||||||
|
NTPTime orig_time;
|
||||||
|
NTPTime recv_time;
|
||||||
|
NTPTime xmit_time;
|
||||||
|
} NTPPacket;
|
||||||
|
|
||||||
|
#define LI_NONE 0
|
||||||
|
#define LI_SIXTY_ONE 1
|
||||||
|
#define LI_FIFTY_NINE 2
|
||||||
|
#define LI_NOSYNC 3
|
||||||
|
|
||||||
|
#define MODE_RESERVED 0
|
||||||
|
#define MODE_ACTIVE 1
|
||||||
|
#define MODE_PASSIVE 2
|
||||||
|
#define MODE_CLIENT 3
|
||||||
|
#define MODE_SERVER 4
|
||||||
|
#define MODE_BROADCAST 5
|
||||||
|
#define MODE_CONTROL 6
|
||||||
|
#define MODE_PRIVATE 7
|
||||||
|
|
||||||
|
#define NTP_VERSION 4
|
||||||
|
|
||||||
|
#define REF_ID "PPS" // "GPS" when we have one!
|
||||||
|
|
||||||
|
#define setLI(value) ((value&0x03)<<6)
|
||||||
|
#define setVERS(value) ((value&0x07)<<3)
|
||||||
|
#define setMODE(value) ((value&0x07))
|
||||||
|
|
||||||
|
#define getLI(value) ((value>>6)&0x03)
|
||||||
|
#define getVERS(value) ((value>>3)&0x07)
|
||||||
|
#define getMODE(value) (value&0x07)
|
||||||
|
|
||||||
|
#define SEVENTY_YEARS 2208988800L
|
||||||
|
#define toEPOCH(t) ((uint32_t)t-SEVENTY_YEARS)
|
||||||
|
#define toNTP(t) ((uint32_t)t+SEVENTY_YEARS)
|
||||||
|
|
||||||
|
#endif /* _ESPNTPServer_H_ */
|
||||||
159
Logger.cpp
Normal file
159
Logger.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Logger.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 31, 2017
|
||||||
|
* Author: liebman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
|
||||||
|
#undef dbprintf
|
||||||
|
#undef dbprint
|
||||||
|
#undef dbprintln
|
||||||
|
#undef dbflush
|
||||||
|
|
||||||
|
#define DBP_BUF_SIZE 256
|
||||||
|
extern unsigned int snprintf(char*, unsigned int, ...);
|
||||||
|
#define dbprintf(...) {char dbp_buf[DBP_BUF_SIZE]; snprintf(dbp_buf, DBP_BUF_SIZE-1, __VA_ARGS__); Serial.print(dbp_buf);}
|
||||||
|
#define dbprintln(x) Serial.println(x)
|
||||||
|
#define dbflush() Serial.flush()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Logger::Logger()
|
||||||
|
{
|
||||||
|
_host = NULL;
|
||||||
|
_port = 0;
|
||||||
|
_failed = 0;
|
||||||
|
#if defined(USE_NETWORK) && defined(USE_TCP)
|
||||||
|
_client.setTimeout(1000); // 1 second connect timeout
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::begin()
|
||||||
|
{
|
||||||
|
begin(LOGGER_DEFAULT_BAUD);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::begin(long int baud)
|
||||||
|
{
|
||||||
|
Serial.begin(baud);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::end()
|
||||||
|
{
|
||||||
|
#if defined(USE_NETWORK) && defined(USE_TCP)
|
||||||
|
_client.stop();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::setNetworkLogger(const char* host, uint16_t port)
|
||||||
|
{
|
||||||
|
_host = host;
|
||||||
|
_port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::println(const char* message)
|
||||||
|
{
|
||||||
|
snprintf(_buffer, LOGGER_BUFFER_SIZE-1, "%s\n", message);
|
||||||
|
Serial.print(_buffer);
|
||||||
|
Serial.flush();
|
||||||
|
send(_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int vsnprintf(char* buffer, size_t size, const char* fmt, va_list args);
|
||||||
|
|
||||||
|
void Logger::printf(const char* fmt, ...)
|
||||||
|
{
|
||||||
|
va_list argp;
|
||||||
|
va_start(argp, fmt);
|
||||||
|
|
||||||
|
vsnprintf(_buffer, LOGGER_BUFFER_SIZE-1, fmt, argp);
|
||||||
|
va_end(argp);
|
||||||
|
|
||||||
|
Serial.print(_buffer);
|
||||||
|
Serial.flush();
|
||||||
|
send(_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::flush()
|
||||||
|
{
|
||||||
|
Serial.flush();
|
||||||
|
#if defined(USE_NETWORK) && defined(USE_TCP)
|
||||||
|
if (_client.connected())
|
||||||
|
{
|
||||||
|
_client.flush();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Logger::send(const char* message)
|
||||||
|
{
|
||||||
|
#if defined(USE_NETWORK)
|
||||||
|
// if we are not configured for TCP then just return
|
||||||
|
if (_host == NULL)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress ip;
|
||||||
|
|
||||||
|
if (!WiFi.isConnected())
|
||||||
|
{
|
||||||
|
dbprintln("Logger::log: not connected!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WiFi.hostByName(_host, ip))
|
||||||
|
{
|
||||||
|
dbprintf("Logger::log failed to resolve address for:%s\n", _host);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(USE_TCP)
|
||||||
|
if (!_client.connected() && _failed < 3) // try 3 times at most to connect then give up.
|
||||||
|
{
|
||||||
|
if(!_client.connect(ip, _port) && _failed < 3)
|
||||||
|
{
|
||||||
|
_failed++;
|
||||||
|
dbprintf("Logger::send failed to connect!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_client.write(message) != strlen(message))
|
||||||
|
{
|
||||||
|
dbprintf("Logger::write failed to write message: %s\n", message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_client.flush();
|
||||||
|
#else
|
||||||
|
_udp.beginPacket(ip, _port);
|
||||||
|
_udp.write(message);
|
||||||
|
if (!_udp.endPacket())
|
||||||
|
{
|
||||||
|
dbprintln("Logger::log failed to send packet!");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger logger;
|
||||||
91
Logger.h
Normal file
91
Logger.h
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Logger.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 31, 2017
|
||||||
|
* Author: liebman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LOGGER_H_
|
||||||
|
#define LOGGER_H_
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
|
||||||
|
#define USE_TCP
|
||||||
|
#define USE_NETWORK
|
||||||
|
|
||||||
|
#ifdef USE_NETWORK
|
||||||
|
#ifdef USE_TCP
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#else
|
||||||
|
#include <WiFiUDP.h>
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LOGGER_DEFAULT_BAUD 115200L
|
||||||
|
#define LOGGER_BUFFER_SIZE 256
|
||||||
|
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
Logger();
|
||||||
|
void begin();
|
||||||
|
void begin(long int baud);
|
||||||
|
void end();
|
||||||
|
void setNetworkLogger(const char* host, uint16_t port);
|
||||||
|
void println(const char*message);
|
||||||
|
void printf(const char*message, ...);
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef USE_NETWORK
|
||||||
|
#ifdef USE_TCP
|
||||||
|
WiFiClient _client;
|
||||||
|
#else
|
||||||
|
WiFiUDP _udp;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
const char* _host;
|
||||||
|
uint16_t _port;
|
||||||
|
uint16_t _failed;
|
||||||
|
char _buffer[LOGGER_BUFFER_SIZE];
|
||||||
|
|
||||||
|
void send(const char* message);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Logger logger;
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
#define dbbegin(x) logger.begin(x)
|
||||||
|
#define dbnetlog(h, p) {if (strlen(h) && p) logger.setNetworkLogger(h, p);}
|
||||||
|
#define dbend() logger.end()
|
||||||
|
#define dbprintf(...) logger.printf(__VA_ARGS__)
|
||||||
|
#define dbprintln(x) logger.println(x)
|
||||||
|
#define dbprint64(l,v) logger.printf("%s %08x:%08x (%Lf)\n", l, (uint32_t)(v>>32), (uint32_t)(v & 0xffffffff), ((long double)v / 4294967296.))
|
||||||
|
#define dbprint64s(l,v) logger.printf("%s %08x:%08x (%Lf)\n", l, (int32_t)(v>>32), (uint32_t)(v & 0xffffffff), ((long double)v / 4294967296.))
|
||||||
|
#define dbflush() logger.flush()
|
||||||
|
#else
|
||||||
|
#define dbbegin(x)
|
||||||
|
#define dbnetlog(h, p)
|
||||||
|
#define dbend()
|
||||||
|
#define dbprintf(...)
|
||||||
|
#define dbprintln(x)
|
||||||
|
#define dbprint64(l,v)
|
||||||
|
#define dbprint64s(l,v)
|
||||||
|
#define dbflush()
|
||||||
|
#endif
|
||||||
|
#endif /* LOGGER_H_ */
|
||||||
|
|
@ -1,2 +1,6 @@
|
||||||
# ESPNTPServer
|
# ESPNTPServer
|
||||||
GPS fueled ESP8266 based NTP Server
|
GPS fueled ESP8266 based NTP Server
|
||||||
|
|
||||||
|
Don't have a GPS unit yet so I'm using a DS3231 as a PPS source to workout
|
||||||
|
some of the software. I'm actually using one of my [AnalogClock](https://github.com/liebman/AnalogClock) boards as it has the DS3231 1hz routed to an ESP8266 GPIO pin.
|
||||||
|
|
||||||
|
|
|
||||||
491
TimeUtils.cpp
Normal file
491
TimeUtils.cpp
Normal file
|
|
@ -0,0 +1,491 @@
|
||||||
|
/*
|
||||||
|
* 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
Normal file
55
TimeUtils.h
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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_ */
|
||||||
112
WireUtils.cpp
Normal file
112
WireUtils.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
/*
|
||||||
|
* WireUtils.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 26, 2017
|
||||||
|
* Author: liebman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "WireUtils.h"
|
||||||
|
|
||||||
|
#define DEBUG
|
||||||
|
#include "Logger.h"
|
||||||
|
|
||||||
|
WireUtilsC::WireUtilsC()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This routine turns off the I2C bus and clears it
|
||||||
|
* on return SCA and SCL pins are tri-state inputs.
|
||||||
|
* You need to call Wire.begin() after this to re-enable I2C
|
||||||
|
* This routine does NOT use the Wire library at all.
|
||||||
|
*
|
||||||
|
* returns 0 if bus cleared
|
||||||
|
* 1 if SCL held low.
|
||||||
|
* 2 if SDA held low by slave clock stretch for > 2sec
|
||||||
|
* 3 if SDA held low after 20 clocks.
|
||||||
|
*/
|
||||||
|
int WireUtilsC::clearBus()
|
||||||
|
{
|
||||||
|
dbprintln("WireUtils::ClearBus: attempting to clean i2c bus");
|
||||||
|
#if defined(TWCR) && defined(TWEN)
|
||||||
|
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
|
||||||
|
#endif
|
||||||
|
pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
|
||||||
|
pinMode(SCL, INPUT_PULLUP);
|
||||||
|
|
||||||
|
boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
|
||||||
|
if (SCL_LOW)
|
||||||
|
{ //If it is held low Arduno cannot become the I2C master.
|
||||||
|
dbprintln("WireUtils::ClearBus: Failed! SCL held low!");
|
||||||
|
return 1; //I2C bus error. Could not clear SCL clock line held low
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
|
||||||
|
int clockCount = 20; // > 2x9 clock
|
||||||
|
|
||||||
|
while (SDA_LOW && (clockCount > 0))
|
||||||
|
{ // vii. If SDA is Low,
|
||||||
|
clockCount--;
|
||||||
|
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
|
||||||
|
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
|
||||||
|
pinMode(SCL, OUTPUT); // then clock SCL Low
|
||||||
|
delayMicroseconds(10); // for >5uS
|
||||||
|
pinMode(SCL, INPUT); // release SCL LOW
|
||||||
|
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
|
||||||
|
// do not force high as slave may be holding it low for clock stretching.
|
||||||
|
delayMicroseconds(10); // for >5uS
|
||||||
|
// The >5uS is so that even the slowest I2C devices are handled.
|
||||||
|
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
|
||||||
|
int counter = 20;
|
||||||
|
while (SCL_LOW && (counter > 0))
|
||||||
|
{ // loop waiting for SCL to become High only wait 2sec.
|
||||||
|
counter--;
|
||||||
|
delay(100);
|
||||||
|
SCL_LOW = (digitalRead(SCL) == LOW);
|
||||||
|
}
|
||||||
|
if (SCL_LOW)
|
||||||
|
{ // still low after 2 sec error
|
||||||
|
dbprintln("WireUtils::ClearBus: Failed! SCL clock line held low by slave clock stretch for >2sec");
|
||||||
|
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
|
||||||
|
}
|
||||||
|
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
|
||||||
|
}
|
||||||
|
if (SDA_LOW)
|
||||||
|
{ // still low
|
||||||
|
dbprintln("WireUtils::ClearBus: Failed! SDA data line still held low");
|
||||||
|
return 3; // I2C bus error. Could not clear. SDA data line held low
|
||||||
|
}
|
||||||
|
|
||||||
|
// else pull SDA line low for Start or Repeated Start
|
||||||
|
pinMode(SDA, INPUT); // remove pullup.
|
||||||
|
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
|
||||||
|
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
|
||||||
|
/// A Repeat Start is a Start occurring after a Start with no intervening Stop.
|
||||||
|
delayMicroseconds(10); // wait >5uS
|
||||||
|
pinMode(SDA, INPUT); // remove output low
|
||||||
|
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
|
||||||
|
delayMicroseconds(10); // x. wait >5uS
|
||||||
|
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
|
||||||
|
pinMode(SCL, INPUT);
|
||||||
|
dbprintln("WireUtils::ClearBus: Success!");
|
||||||
|
return 0; // all ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// act like wire library (even though I don't like it)
|
||||||
|
WireUtilsC WireUtils = WireUtilsC();
|
||||||
|
|
||||||
35
WireUtils.h
Normal file
35
WireUtils.h
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* WireUtils.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 26, 2017
|
||||||
|
* Author: liebman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WIREUTILS_H_
|
||||||
|
#define WIREUTILS_H_
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
class WireUtilsC {
|
||||||
|
public:
|
||||||
|
WireUtilsC();
|
||||||
|
int clearBus();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern WireUtilsC WireUtils;
|
||||||
|
|
||||||
|
#endif /* WIREUTILS_H_ */
|
||||||
Loading…
Reference in New Issue
Block a user