/* * 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 // 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 }