280 lines
8.0 KiB
C++
280 lines
8.0 KiB
C++
/*
|
|
* 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
|
|
}
|