Initial checkin, hacky code, using DS3231 instead of GPS unit.

This commit is contained in:
Chris Liebman 2017-11-03 19:03:05 -07:00
parent 822d59009a
commit 9c65ef10e9
14 changed files with 1937 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
.metadata
.DS_Store
.ino.cpp
.project
.cproject
.settings
Release
Debug
libraries
core
*.bak
*-bak

231
DS3231.cpp Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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_ */

View File

@ -1,2 +1,6 @@
# ESPNTPServer
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
View 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
View 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
View 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
View 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_ */