diff --git a/.cproject b/.cproject
new file mode 100644
index 0000000..7cedfd6
--- /dev/null
+++ b/.cproject
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/.gitignore
index 0db0561..7911b6b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,12 +1,6 @@
-.metadata
.DS_Store
-.ino.cpp
-.project
-.cproject
-.settings
-Release
-Debug
-libraries
-core
*.bak
*-bak
+*.old
+build
+sdkconfig
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..40fab32
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "components/u8g2"]
+ path = components/u8g2
+ url = git@github.com:liebman/u8g2
diff --git a/.project b/.project
new file mode 100644
index 0000000..6f9dbee
--- /dev/null
+++ b/.project
@@ -0,0 +1,27 @@
+
+
+ esp_gps_ntp
+
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.genmakebuilder
+ clean,full,incremental,
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
+ full,incremental,
+
+
+
+
+
+ org.eclipse.cdt.core.cnature
+ org.eclipse.cdt.core.ccnature
+ org.eclipse.cdt.managedbuilder.core.managedBuildNature
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
+
+
diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml
new file mode 100644
index 0000000..8a0f91f
--- /dev/null
+++ b/.settings/language.settings.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs
new file mode 100644
index 0000000..530eb67
--- /dev/null
+++ b/.settings/org.eclipse.cdt.core.prefs
@@ -0,0 +1,12 @@
+eclipse.preferences.version=1
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/BATCH_BUILD/delimiter=\:
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/BATCH_BUILD/operation=append
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/BATCH_BUILD/value=1
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/IDF_PATH/delimiter=\:
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/IDF_PATH/operation=append
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/IDF_PATH/value=${HOME}/esp/esp-idf
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/PATH/delimiter=\:
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/PATH/operation=replace
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/PATH/value=/bin\:/usr/bin\:/usr/sbin\:/sbin\:${HOME}/esp/xtensa-esp32-elf/bin
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/append=true
+environment/project/cdt.managedbuild.toolchain.gnu.cross.base.285975342/appendContributed=true
diff --git a/ESPNTPServer.cpp b/ESPNTPServer.cpp
deleted file mode 100644
index 6587d52..0000000
--- a/ESPNTPServer.cpp
+++ /dev/null
@@ -1,552 +0,0 @@
-/*
- * 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"
-#include // htonl() & ntohl()
-#define DEBUG
-//define NTP_PACKET_DEBUG
-#include "Logger.h"
-
-Ticker validityTimer;
-bool valid;
-bool gps_valid;
-uint32_t pps_valid_count;
-volatile uint32_t valid_count; // how many times we have gone from invalid to valid
-bool sentence_unknown;
-uint32_t bad_checksum_count;
-uint32_t req_count;
-uint32_t rsp_count;
-int8_t precision;
-volatile uint32_t dispersion;
-
-volatile time_t seconds;
-
-volatile uint32_t last_micros;
-volatile uint32_t min_micros;
-volatile uint32_t max_micros;
-
-#if defined(MICROS_HISTORY_SIZE)
-volatile uint32_t micros_history[MICROS_HISTORY_SIZE];
-volatile uint16_t micros_history_count;
-volatile uint16_t micros_history_index;
-#endif
-
-#if defined(USE_ASYNC_UDP)
-AsyncUDP udp;
-#else
-WiFiUDP udp;
-#endif
-
-SoftwareSerial gps(GPS_RX_PIN, GPS_TX_PIN, false, SERIAL_BUFFER_SIZE);
-char nmeaBuffer[NMEA_BUFFER_SIZE];
-MicroNMEA nmea(nmeaBuffer, NMEA_BUFFER_SIZE);
-
-SSD1306Wire display(0x3c, SDA, SCL);
-
-#ifdef NTP_PACKET_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 invalidate()
-{
- valid = false;
- gps_valid = false;
- last_micros = 0;
-}
-
-void oneSecondInterrupt()
-{
- uint32_t cur_micros = micros();
-
- //
- // don't trust PPS if GPS is not valid.
- //
- if (!gps_valid)
- {
- return;
- }
-
- //
- // if we are still counting down then keep waiting
- //
- if (pps_valid_count)
- {
- --pps_valid_count;
- if (pps_valid_count == 0)
- {
- // clear stats and mark us valid
- min_micros = 0;
- max_micros = 0;
- valid = true;
- ++valid_count;
- }
- }
-
- //
- // restart the validity timer, if it runs out we invalidate our data.
- //
- validityTimer.attach_ms(VALIDITY_CHECK_MS, &invalidate);
-
- //
- // increment seconds
- //
- seconds += 1;
-
- //
- // the first time around we just initialize the last value
- //
- if (last_micros == 0)
- {
- last_micros = cur_micros;
- return;
- }
-
- uint32_t micros_count = cur_micros - last_micros;
- last_micros = cur_micros;
-
- if (min_micros == 0 || micros_count < min_micros)
- {
- min_micros = micros_count;
- }
-
- if (micros_count > max_micros)
- {
- max_micros = micros_count;
- }
-
-#if defined(MICROS_HISTORY_SIZE)
- micros_history[micros_history_index++] = micros_count;
- if (micros_history_index >= MICROS_HISTORY_SIZE)
- {
- micros_history_index = 0;
- }
- if (micros_history_count < MICROS_HISTORY_SIZE)
- {
- micros_history_count++;
- }
-#endif
-
-#if defined(DEBUG) && defined(LED_PIN)
- digitalWrite(LED_PIN, digitalRead(LED_PIN) ? LOW : HIGH);
-#endif
-}
-
-void getNTPTime(NTPTime *time)
-{
- time->seconds = toNTP(seconds);
- uint32_t cur_micros = micros();
- uint32_t micros_delta = cur_micros - last_micros;
-
- //
- // if micros_delta is at or bigger than one second then
- // use the max fraction.
- //
- if (micros_delta >= 1000000)
- {
- time->fraction = 0xffffffff;
- return;
- }
-
- double percent = us2s(micros_delta);
- time->fraction = (uint32_t)(percent * (double)4294967296L);
-}
-
-int8_t computePrecision()
-{
- NTPTime t;
- unsigned long start = micros();
- for (int i = 0; i < PRECISION_COUNT; ++i)
- {
- getNTPTime(&t);
- }
- unsigned long end = micros();
- double total = (double)(end - start) / 1000000.0;
- double time = total / PRECISION_COUNT;
- double prec = log2(time);
- dbprintf("computePrecision: total:%f time:%f prec:%f\n", total, time, prec);
- return (int8_t)prec;
-}
-
-#if defined(USE_ASYNC_UDP)
-void recievePacket(AsyncUDPPacket aup)
-#else
-void recievePacket()
-#endif
-{
- ++req_count;
- NTPPacket ntp;
- NTPTime recv_time;
- getNTPTime(&recv_time);
-#if defined(USE_ASYNC_UDP)
- if (aup.length() != sizeof(NTPPacket))
- {
- dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", aup.length(), sizeof(NTPPacket));
- return;
- }
-#else
- if (udp.available() != sizeof(NTPPacket))
- {
- dbprintf("recievePacket: ignoring packet with bad length: %d < %d\n", udp.available(), sizeof(NTPPacket));
- return;
- }
-#endif
-
- if (!valid)
- {
- dbprintln("recievePacket: GPS data not valid!");
- return;
- }
-
-#if defined(USE_ASYNC_UDP)
- memcpy(&ntp, aup.data(), sizeof(ntp));
-#else
- udp.read((unsigned char*)&ntp, sizeof(ntp));
-
-#endif
- ntp.delay = ntohl(ntp.delay);
- ntp.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 = precision;
- // TODO: compute actual root delay, and root dispersion
- ntp.delay = (uint32)(0.000001 * 65536);
- ntp.dispersion = 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);
-#if defined(USE_ASYNC_UDP)
- aup.write((uint8_t*)&ntp, sizeof(ntp));
- ++rsp_count;
-#else
- IPAddress address = udp.remoteIP();
- uint16_t port = udp.remotePort();
- udp.beginPacket(address, port);
- udp.write((uint8_t*)&ntp, sizeof(ntp));
- udp.flush();
- udp.endPacket();
-#endif
-}
-
-void badChecksum(MicroNMEA& mn)
-{
- ++bad_checksum_count;
- dbprintf("badChecksum: '%s'\n", mn.getSentence());
-}
-
-void unknownSentence(MicroNMEA& mn)
-{
- const char* sentence = mn.getSentence();
- dbprintf("unknownSentence: %s\n", sentence);
-
- if (!strncmp("$PMTK", sentence, 5))
- {
- return;
- }
-
- sentence_unknown = true;
-}
-
-void resetGPS()
-{
- dbprintln("resetGPS: starting!");
- // Empty input buffer
- while (gps.available())
- {
- gps.read();
- }
-
- digitalWrite(GPS_EN_PIN, LOW);
- delay(100);
- digitalWrite(GPS_EN_PIN, HIGH);
-
-#if 0
- dbprintln("resetGPS: waiting on first sentence");
- dbflush();
-
- // Reset is complete when the first valid message is received
- while (1)
- {
- delay(1);
- while (gps.available())
- {
- char c = gps.read();
- if (nmea.process(c))
- {
- const char* sentence = nmea.getSentence();
- dbprintf("resetGPS: done, sentence: '%s'\n", sentence);
- dbflush();
- return;
- }
- }
- }
-#endif
-}
-
-void processGPS()
-{
- static boolean last_valid = false;
- static boolean last_gps_valid = false;
-
- //
- // Print valid or invalid if stats has changed.
- //
- if ((last_valid && !valid) || (last_gps_valid && !gps_valid))
- {
- dbprintln("INVALID!");
- }
- else if (!last_valid && valid)
- {
- dbprintln("VALID!");
- }
- last_valid = valid;
- last_gps_valid = gps_valid;
-
- while (gps.available() > 0)
- {
- if (nmea.process(gps.read()))
- {
- //
- // if it was a GGA and its valid then check and maybe update the time
- //
- const char * id = nmea.getMessageID();
- if (nmea.isValid() && nmea.getYear() > 2000 && strcmp("GGA", id) == 0)
- {
- static struct tm tm;
- tm.tm_year = nmea.getYear() - 1900;
- tm.tm_mon = nmea.getMonth() - 1;
- tm.tm_mday = nmea.getDay();
- tm.tm_hour = nmea.getHour();
- tm.tm_min = nmea.getMinute();
- tm.tm_sec = nmea.getSecond();
- time_t new_seconds = mktime(&tm);
-
- time_t old_seconds = seconds;
- if (old_seconds != new_seconds)
- {
- seconds = new_seconds;
- dbprintf("%010lu: %s adjusting seconds from %lu to %lu\n", millis(), nmea.getMessageID(), old_seconds, new_seconds);
- }
-
- //
- // if gps was not valid, it is now
- //
- if (!gps_valid)
- {
- gps_valid = true;
- pps_valid_count = PPS_VALID_COUNT;
- dbprintln("gps valid!");
- }
- }
- }
- }
-}
-
-void sendSentence(const char* sentence)
-{
- static char cksum[3];
- MicroNMEA::generateChecksum(sentence, cksum);
- cksum[2] = '\0';
- dbprintf("sendSentence: '%s*%s'\n", sentence, cksum);
- gps.printf("%s*%s\r\n", sentence, cksum);
- gps.flush();
-}
-
-void setup()
-{
- delay(5000); // delay for IDE to re-open serial
- dbbegin(115200);
- dbprintln("\n\nStartup!");
-
- pinMode(SYNC_PIN, INPUT);
-#if defined(LED_PIN)
- pinMode(LED_PIN, OUTPUT);
-#endif
-
- valid = false;
- valid_count = 0;
- seconds = 0;
- max_micros = 0;
- min_micros = 0;
- last_micros = 0;
-#if defined(MICROS_HISTORY_SIZE)
- micros_history_count = 0;
- micros_history_index = 0;
-#endif
-
-#if !defined(USE_NO_WIFI)
- WiFiManager wifi;
- wifi.setDebugOutput(false);
- String ssid = "SynchroClock" + String(ESP.getChipId());
- wifi.autoConnect(ssid.c_str(), NULL);
-#endif
-
- if (!display.init())
- {
- dbprintln("display.init() failed!");
- }
- display.flipScreenVertically();
- display.setFont(ArialMT_Plain_10);
-
- gps.begin(9600);
- nmea.setBadChecksumHandler(&badChecksum);
- nmea.setUnknownSentenceHandler(&unknownSentence);
- resetGPS();
-
- precision = computePrecision();
-
- //
- // initialize UDP handler
- //
-#if defined(USE_ASYNC_UDP)
- while (!udp.listen(NTP_PORT))
- {
-#else
- while(!udp.begin(NTP_PORT))
- {
-#endif
- dbprintf("setup: failed to listen on port %d! Will retry in a bit...\n", NTP_PORT);
- delay(1000);
- dbprintf("setup: retrying!\n");
- }
-
- attachInterrupt(SYNC_PIN, &oneSecondInterrupt, FALLING);
-
-#if defined(USE_ASYNC_UDP)
- udp.onPacket(recievePacket);
-#endif
-}
-
-void loop()
-{
-#if !defined(USE_ASYNC_UDP)
- if (udp.parsePacket())
- {
- recievePacket();
- }
-#endif
-
- processGPS();
- if (sentence_unknown)
- {
- sentence_unknown = false;
- // Send only RMC and GGA messages.
- sendSentence("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0");
- }
-
- static time_t last_seconds;
- if (seconds != last_seconds)
- {
- if (seconds != last_seconds && ((seconds % 60) == 0 || pps_valid_count))
- {
-
-#if defined(MICROS_HISTORY_SIZE)
- double mean = 0.0;
- for (int i = 0; i < micros_history_count; ++i)
- {
- mean += us2s(micros_history[i]);
- }
- mean = mean / micros_history_count;
-
- double stdev = 0.0;
- for (int i = 0; i < micros_history_count; ++i)
- {
- stdev += pow(us2s(micros_history[i]) - mean, 2);
- }
- stdev = sqrt(stdev / micros_history_count);
- dbprintf("mean:%f stdev:%f ", mean, stdev);
-#endif
- double disp = us2s(MAX(abs(MICROS_PER_SEC-max_micros), abs(MICROS_PER_SEC-min_micros)));
- dispersion = (uint32_t)(disp * 65536.0);
- dbprintf("min:%lu max:%lu jitter:%lu valid_count:%lu valid:%s numsat:%d heap:%ld pps_valid_count:%d badcs: %lu\n", min_micros, max_micros,
- max_micros - min_micros, valid_count, valid ? "true" : "false", nmea.getNumSatellites(), ESP.getFreeHeap(), pps_valid_count,
- bad_checksum_count);
- }
-
- if (seconds < last_seconds)
- {
- dbprintf("OOPS: time went backwards: last:%lu now:%lu\n", last_seconds, seconds);
- }
-
- //
- // Update the display
- //
- display.clear();
- display.setTextAlignment(TEXT_ALIGN_LEFT);
- display.setFont(ArialMT_Plain_10);
- const char* current_time = ctime(&last_seconds);
- display.drawString(0, 0, current_time);
- display.drawString(0, 10, "Address: "+WiFi.localIP().toString());
- display.drawString(0, 20, "Sat Count: " + String(nmea.getNumSatellites()));
- display.drawString(0, 30, "Requests: " + String(req_count));
- display.drawString(0, 40, "Responses: " + String(rsp_count));
- // write the buffer to the display
- display.display();
- }
-
- last_seconds = seconds;
- delay(1);
-}
diff --git a/ESPNTPServer.h b/ESPNTPServer.h
deleted file mode 100644
index b63a421..0000000
--- a/ESPNTPServer.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * 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
- */
-
-#define USE_ASYNC_UDP
-//#define USE_NO_WIFI
-
-#ifndef _ESPNTPServer_H_
-#define _ESPNTPServer_H_
-#include "Arduino.h"
-#include "Ticker.h"
-#if !defined(USE_NO_WIFI)
-#include "WiFiManager.h"
-#endif
-#include "SoftwareSerial.h"
-#include "MicroNMEA.h"
-#include
-
-#include "SSD1306Wire.h"
-
-#if defined(USE_ASYNC_UDP)
-#include "ESPAsyncUDP.h"
-#else
-#include
-#endif
-
-// pin definitions
-#define LED_PIN BUILTIN_LED // LED on pin, active low
-#define SYNC_PIN D5 // (GPIO14) pin tied to 1hz square wave from GPS
-#define GPS_RX_PIN D6 // (GPIO12)
-#define GPS_TX_PIN D7 // (GPIO13)
-#define GPS_EN_PIN D4 // (GPIO2)
-
-#define NTP_PORT 123
-
-#define SYNC_EDGE_RISING 1
-#define SYNC_EDGE_FALLING 0
-
-#define WARMUP_SECONDS 2
-#define PRECISION_COUNT 10000
-#define MICROS_PER_SEC 1000000
-#define SERIAL_BUFFER_SIZE 128
-#define NMEA_BUFFER_SIZE 128
-#define VALIDITY_CHECK_MS 1100
-#define PPS_VALID_COUNT 60 // must have at least this many "good" PPS interrupts to be valid
-//#define MICROS_HISTORY_SIZE 1000
-
-#define us2s(x) (((double)x)/(double)MICROS_PER_SEC) // microseconds to seconds
-
-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)
-
-// simple versions - we don't worry about side effects
-#define MAX(a, b) ((a) < (b) ? (b) : (a))
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-
-#endif /* _ESPNTPServer_H_ */
diff --git a/Logger.cpp b/Logger.cpp
deleted file mode 100644
index 32dabfc..0000000
--- a/Logger.cpp
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * 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();
-#if defined(USE_NETWORK)
- send(_buffer);
-#endif
-}
-
-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();
-#if defined(USE_NETWORK)
- send(_buffer);
-#endif
-}
-
-void Logger::flush()
-{
- Serial.flush();
-#if defined(USE_NETWORK) && defined(USE_TCP)
- if (_client.connected())
- {
- _client.flush();
- }
-#endif
-}
-
-#if defined(USE_NETWORK)
-void Logger::send(const char* message)
-{
- // 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;
diff --git a/Logger.h b/Logger.h
deleted file mode 100644
index 708ecf0..0000000
--- a/Logger.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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
-#include
-
-#define USE_TCP
-//#define USE_NETWORK
-
-#ifdef USE_NETWORK
-#ifdef USE_TCP
-#include
-#else
-#include
-#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];
-#ifdef USE_NETWORK
- void send(const char* message);
-#endif
-};
-
-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_ */
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..30c4733
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := esp-gps-ntp
+
+include $(IDF_PATH)/make/project.mk
+
diff --git a/WireUtils.cpp b/WireUtils.cpp
deleted file mode 100644
index 295c2fc..0000000
--- a/WireUtils.cpp
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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();
-
diff --git a/WireUtils.h b/WireUtils.h
deleted file mode 100644
index f20dd54..0000000
--- a/WireUtils.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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
-
-class WireUtilsC {
-public:
- WireUtilsC();
- int clearBus();
-};
-
-extern WireUtilsC WireUtils;
-
-#endif /* WIREUTILS_H_ */
diff --git a/components/u8g2 b/components/u8g2
new file mode 160000
index 0000000..6bcf57f
--- /dev/null
+++ b/components/u8g2
@@ -0,0 +1 @@
+Subproject commit 6bcf57f2ae2d604e4954ea776d1eef1c0f3aed5e
diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild
new file mode 100644
index 0000000..6efe7cf
--- /dev/null
+++ b/main/Kconfig.projbuild
@@ -0,0 +1,17 @@
+menu "ESP GPS NTP Server Configuration"
+
+choice WPS_TYPE
+ prompt "WPS mode"
+ default WPS_TYPE_PBC
+ help
+ WPS type for the esp32 to use.
+
+config WPS_TYPE_PBC
+ bool "PBC"
+config WPS_TYPE_PIN
+ bool "PIN"
+config WPS_TYPE_DISABLE
+ bool "disable"
+endchoice
+
+endmenu
diff --git a/main/component.mk b/main/component.mk
new file mode 100644
index 0000000..b4d9655
--- /dev/null
+++ b/main/component.mk
@@ -0,0 +1,8 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
+COMPONENT_ADD_INCLUDEDIRS := . minmea u8g2
+
+COMPONENT_SRCDIRS := $(COMPONENT_ADD_INCLUDEDIRS)
diff --git a/main/display_task.c b/main/display_task.c
new file mode 100644
index 0000000..a821dc1
--- /dev/null
+++ b/main/display_task.c
@@ -0,0 +1,133 @@
+/*
+ * display_task.c
+ *
+ * Created on: Jan 21, 2018
+ * Author: chris.l
+ */
+
+#include "esp_gps_ntp.h"
+#include "freertos/queue.h"
+#include "u8g2.h"
+#include "u8g2_esp32_hal.h"
+#include
+#include
+#include "tcpip_adapter.h"
+
+static const char *TAG = "DSP";
+static xQueueHandle dsp_queue = NULL;
+static u8g2_t dsp;
+
+void IRAM_ATTR triggerDisplay()
+{
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ bool value = true;
+
+ if (dsp_queue != NULL)
+ {
+ xQueueSendFromISR(dsp_queue, &value, &xHigherPriorityTaskWoken );
+ }
+}
+
+static void dsp_task()
+{
+ GPSTaskStatus gps;
+ NTPTaskStatus ntp;
+ static long unsigned int duration;
+ static bool blink;
+ const int buf_size = 64;
+ char buf[buf_size];
+ struct tm t;
+ tcpip_adapter_ip_info_t ip;
+ memset(&ip, 0, sizeof(tcpip_adapter_ip_info_t));
+ bool value;
+ ESP_LOGI(TAG, "starting!");
+
+ dsp_queue = xQueueCreate(10, sizeof(value));
+
+ u8g2_esp32_hal_t u8g2_esp32_hal = U8G2_ESP32_HAL_DEFAULT;
+ u8g2_esp32_hal.sda = SDA_PIN;
+ u8g2_esp32_hal.scl = SCL_PIN;
+ u8g2_esp32_hal_init(u8g2_esp32_hal);
+
+ u8g2_Setup_ssd1306_i2c_128x64_noname_f(&dsp, U8G2_R0, u8g2_esp32_i2c_byte_cb, u8g2_esp32_gpio_and_delay_cb); // init u8g2 structure
+ u8g2_SetI2CAddress(&dsp, 0x3c<<1);
+ ESP_LOGD(TAG, "initializing display");
+ u8g2_InitDisplay(&dsp); // send init sequence to the display, display is in sleep mode after this,
+ ESP_LOGD(TAG, "waking display");
+ u8g2_ClearDisplay(&dsp);
+ u8g2_SetPowerSave(&dsp, 0); // wake up display
+ u8g2_SetFont(&dsp, u8g2_font_timR08_tr);
+ u8g2_SetFontMode(&dsp, 0);
+
+ while(1)
+ {
+ if (xQueueReceive(dsp_queue, &value, pdMS_TO_TICKS(10000)))
+ {
+ micros_t start = micros();
+ getGPSTaskStatus(&gps);
+ getNTPTaskStatus(&ntp);
+ u8g2_ClearBuffer(&dsp);
+ gmtime_r(&gps.seconds, &t);
+ snprintf(buf, buf_size-1, "UTC: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
+ u8g2_DrawStr(&dsp, 0, 8, buf);
+ snprintf(buf, buf_size-1, "Req: %u", ntp.req_count);
+ u8g2_DrawStr(&dsp, 0, 16, buf);
+ snprintf(buf, buf_size-1, "Rsp: %u", ntp.rsp_count);
+ u8g2_DrawStr(&dsp, 64, 16, buf);
+ snprintf(buf, buf_size-1, "Sat: %d", gps.sat_count);
+ u8g2_DrawStr(&dsp, 0, 24, buf);
+ snprintf(buf, buf_size-1, "Fix: %d", gps.fix_quality);
+ u8g2_DrawStr(&dsp, 64, 24, buf);
+ snprintf(buf, buf_size, "Valid: %d", gps.valid_count);
+ u8g2_DrawStr(&dsp, 0, 32, buf);
+ snprintf(buf, buf_size, "Timeout: %d", gps.timeouts);
+ u8g2_DrawStr(&dsp, 64, 32, buf);
+
+ if (gps.valid_in)
+ {
+ snprintf(buf, buf_size, "Valid In: %d", gps.valid_in);
+ u8g2_DrawStr(&dsp, 0, 40, buf);
+ }
+
+ gmtime_r(&gps.valid_since, &t);
+ if (gps.valid)
+ {
+ snprintf(buf, buf_size-1, "Valid: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
+ u8g2_DrawStr(&dsp, 0, 48, buf);
+ }
+ else
+ {
+ if (blink)
+ {
+ snprintf(buf, buf_size-1, "Invld: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
+ u8g2_DrawStr(&dsp, 0, 48, buf);
+ }
+ }
+ gmtime_r(&gps.initial_valid, &t);
+ snprintf(buf, buf_size-1, "Start: %04d/%02d/%02d %02d:%02d:%02d", t.tm_year+1900, t.tm_mon+1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
+ u8g2_DrawStr(&dsp, 0, 56, buf);
+ if (tcpip_adapter_get_ip_info(ESP_IF_WIFI_STA, &ip) == 0) {
+ snprintf(buf, buf_size-1, "IP: "IPSTR, IP2STR(&ip.ip));
+ u8g2_DrawStr(&dsp, 0, 64, buf);
+ }
+ u8g2_SendBuffer(&dsp);
+ micros_t end = micros();
+ duration = end - start;
+ blink = !blink;
+ }
+ else
+ {
+ ESP_LOGD(TAG, "timeout waiting for display queue!");
+ }
+ }
+}
+
+
+void start_display()
+{
+ esp_log_level_set(TAG, ESP_LOG_INFO);
+ esp_log_level_set("u8g2_hal", ESP_LOG_INFO);
+
+ xTaskCreatePinnedToCore(dsp_task, "dsp_task", 1024*4, NULL, DSP_TASK_PRI, NULL, 1);
+
+}
diff --git a/main/esp_gps_ntp.h b/main/esp_gps_ntp.h
new file mode 100644
index 0000000..c0729d8
--- /dev/null
+++ b/main/esp_gps_ntp.h
@@ -0,0 +1,60 @@
+/*
+ * esp_gps_ntp.h
+ *
+ * Created on: Jan 21, 2018
+ * Author: chris.l
+ */
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_log.h"
+
+#ifndef MAIN_ESP_GPS_NTP_H_
+#define MAIN_ESP_GPS_NTP_H_
+
+#define MICROS_PER_SEC 1000000
+#define us2s(x) (((double)x)/(double)MICROS_PER_SEC) // microseconds to seconds
+
+#define SDA_PIN (GPIO_NUM_21)
+#define SCL_PIN (GPIO_NUM_22)
+
+typedef uint64_t micros_t;
+typedef struct gps_task_status
+{
+ uint32_t timeouts;
+ time_t seconds;
+ micros_t min_micros;
+ micros_t max_micros;
+ bool gps_valid;
+ bool valid;
+ uint32_t valid_in;
+ time_t valid_since;
+ uint32_t valid_count;
+ time_t initial_valid;
+ int sat_count;
+ int fix_quality;
+} GPSTaskStatus;
+
+typedef struct ntp_task_status
+{
+ uint32_t req_count;
+ uint32_t rsp_count;
+} NTPTaskStatus;
+
+#define GPS_TASK_PRI configMAX_PRIORITIES
+#define NTP_TASK_PRI (configMAX_PRIORITIES-1)
+#define DSP_TASK_PRI (configMAX_PRIORITIES-2)
+
+extern void start_gps();
+extern void start_ntp();
+extern void start_display();
+
+extern void triggerDisplay();
+extern void getTime(time_t *secs, time_t *usecs);
+extern void getGPSTaskStatus(GPSTaskStatus* status);
+extern void getNTPTaskStatus(NTPTaskStatus* status);
+extern bool isTimeValid();
+extern micros_t micros();
+extern uint32_t getDispersion();
+
+#endif /* MAIN_ESP_GPS_NTP_H_ */
diff --git a/main/esp_gps_ntp_main.c b/main/esp_gps_ntp_main.c
new file mode 100644
index 0000000..ee2de1e
--- /dev/null
+++ b/main/esp_gps_ntp_main.c
@@ -0,0 +1,152 @@
+/* Hello World Example
+
+ This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+ Unless required by applicable law or agreed to in writing, this
+ software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_system.h"
+
+#include "esp_wifi.h"
+#include "esp_log.h"
+#include "esp_wps.h"
+#include "esp_event_loop.h"
+#include "nvs_flash.h"
+
+#include "esp_gps_ntp.h"
+
+/*set wps mode via "make menuconfig"*/
+#if CONFIG_WPS_TYPE_PBC
+#define WPS_TEST_MODE WPS_TYPE_PBC
+#elif CONFIG_WPS_TYPE_PIN
+#define WPS_TEST_MODE WPS_TYPE_PIN
+#else
+#define WPS_TEST_MODE WPS_TYPE_DISABLE
+#endif /*CONFIG_WPS_TYPE_PBC*/
+
+
+#ifndef PIN2STR
+#define PIN2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5], (a)[6], (a)[7]
+#define PINSTR "%c%c%c%c%c%c%c%c"
+#endif
+
+
+static const char *TAG = "ESP_GPS_NTP";
+
+static esp_wps_config_t config = WPS_CONFIG_INIT_DEFAULT(WPS_TEST_MODE);
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+ switch(event->event_id)
+ {
+ case SYSTEM_EVENT_STA_START:
+ ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
+ break;
+
+ case SYSTEM_EVENT_STA_GOT_IP:
+ ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
+ ESP_LOGI(TAG, "got ip:%s\n", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));
+ start_ntp();
+ break;
+
+ case SYSTEM_EVENT_STA_DISCONNECTED:
+ ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ break;
+
+ case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
+ /*point: the function esp_wifi_wps_start() only get ssid & password
+ * so call the function esp_wifi_connect() here
+ * */
+ ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_SUCCESS");
+ ESP_ERROR_CHECK(esp_wifi_wps_disable());
+ ESP_ERROR_CHECK(esp_wifi_connect());
+ break;
+
+ case SYSTEM_EVENT_STA_WPS_ER_FAILED:
+ ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_FAILED");
+ ESP_ERROR_CHECK(esp_wifi_wps_disable());
+ ESP_ERROR_CHECK(esp_wifi_wps_enable(&config));
+ ESP_ERROR_CHECK(esp_wifi_wps_start(0));
+ break;
+
+ case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
+ ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_TIMEOUT");
+ ESP_ERROR_CHECK(esp_wifi_wps_disable());
+ ESP_ERROR_CHECK(esp_wifi_wps_enable(&config));
+ ESP_ERROR_CHECK(esp_wifi_wps_start(0));
+ break;
+
+ case SYSTEM_EVENT_STA_WPS_ER_PIN:
+ ESP_LOGI(TAG, "SYSTEM_EVENT_STA_WPS_ER_PIN");
+ /*show the PIN code here*/
+ ESP_LOGI(TAG, "WPS_PIN = "PINSTR, PIN2STR(event->event_info.sta_er_pin.pin_code));
+ break;
+
+ default:
+ break;
+ }
+ return ESP_OK;
+}
+
+/*init wifi as sta and start wps*/
+static void start_wifi(void)
+{
+ tcpip_adapter_init();
+ ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
+
+ wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+
+ ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
+ ESP_ERROR_CHECK(esp_wifi_start());
+
+ if (esp_wifi_connect())
+ {
+ ESP_LOGI(TAG, "wifi not configured, starting wps...");
+
+
+ ESP_ERROR_CHECK(esp_wifi_wps_enable(&config));
+ ESP_ERROR_CHECK(esp_wifi_wps_start(0));
+ }
+}
+
+micros_t IRAM_ATTR micros()
+{
+ return esp_timer_get_time();
+}
+
+void app_main()
+{
+ printf("ESP32 GPS NTP Server\n");
+
+ /* Print chip information */
+ esp_chip_info_t chip_info;
+ esp_chip_info(&chip_info);
+ printf("This is ESP32 chip with %d CPU cores, WiFi%s%s, ",
+ chip_info.cores,
+ (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
+ (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "");
+
+ printf("silicon revision %d, ", chip_info.revision);
+
+ printf("\n*****\nRUNNING on core %d\n*****\n", xPortGetCoreID());
+ fflush(stdout);
+
+ /* Initialize NVS — it is used to store PHY calibration data */
+ esp_err_t ret = nvs_flash_init();
+ if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
+ ESP_ERROR_CHECK(nvs_flash_erase());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK( ret );
+
+ start_wifi();
+ start_gps();
+ start_display();
+}
diff --git a/main/gps_task.c b/main/gps_task.c
new file mode 100644
index 0000000..3651520
--- /dev/null
+++ b/main/gps_task.c
@@ -0,0 +1,480 @@
+/*
+ * gps_task.c
+ *
+ * Created on: Jan 20, 2018
+ * Author: chris.l
+ */
+
+#include "esp_gps_ntp.h"
+
+#include "driver/uart.h"
+#include "freertos/timers.h"
+#include "sys/time.h"
+#include "minmea.h"
+
+static const char *TAG = "GPS";
+
+#define RX_BUF_SIZE 255
+
+#define ESP_INTR_FLAG_DEFAULT 0
+
+// simple versions - we don't worry about side effects
+#define MAX(a, b) ((a) < (b) ? (b) : (a))
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+#define VALIDITY_TIMER_MS 1010
+#define PPS_VALID_COUNT 10 // must have at least this many "good" PPS interrupts to be valid
+
+#define PPS_PIN (GPIO_NUM_19)
+#define TXD_PIN (GPIO_NUM_17)
+#define RXD_PIN (GPIO_NUM_16)
+#define LED_PIN (GPIO_NUM_15)
+
+#define GPS_UART (UART_NUM_2)
+
+static TimerHandle_t timer;
+static volatile uint32_t timeouts;
+static volatile time_t seconds;
+static volatile micros_t last_micros;
+static volatile micros_t min_micros;
+static volatile micros_t max_micros;
+static volatile bool gps_valid;
+static volatile bool valid;
+static volatile uint32_t valid_in;
+static volatile time_t valid_since;
+static volatile uint32_t valid_count;
+static volatile time_t initial_valid;
+static uint32_t dispersion;
+static volatile char reason[128];
+
+typedef struct gps_status
+{
+ bool valid;
+ int sat_count;
+ int fix_quality;
+} GPSStatus;
+
+static GPSStatus gps_status;
+
+
+
+#ifdef LED_PIN
+#define DEBUG_LED_ON() gpio_set_level(LED_PIN, 1)
+#define DEBUG_LED_OFF() gpio_set_level(LED_PIN, 0)
+#define DEBUG_LED_INIT() init_debug_led()
+static void init_debug_led()
+{
+ //
+ // set up the LED pin
+ //
+
+ gpio_config_t io_conf;
+ //interrupt disabled
+ io_conf.intr_type = GPIO_PIN_INTR_DISABLE;
+ //set as input mode
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ //bit mask of the pins that you want to set,e.g.GPIO18/19
+ io_conf.pin_bit_mask = (1ULL<timeouts = timeouts;
+ status->seconds = seconds;
+ status->min_micros = min_micros;
+ status->max_micros = max_micros;
+ status->gps_valid = gps_valid;
+ status->valid = valid;
+ status->valid_in = valid_in;
+ status->valid_since = valid_since;
+ status->valid_count = valid_count;
+ status->initial_valid = initial_valid;
+ status->sat_count = gps_status.sat_count;
+ status->fix_quality = gps_status.fix_quality;
+}
+
+static void IRAM_ATTR pps_isr_handler(void* arg)
+{
+ DEBUG_LED_ON();
+ micros_t cur_micros = micros();
+ // restart the validity timer
+ xTimerReset(timer, 0);
+
+ //
+ // trigger a display update each second
+ //
+ triggerDisplay();
+
+ //
+ // don't trust PPS if GPS is not valid.
+ //
+ if (!gps_valid)
+ {
+ DEBUG_LED_OFF();
+ return;
+ }
+
+ //
+ // if we are still counting down then keep waiting
+ //
+ if (valid_in)
+ {
+ --valid_in;
+ if (valid_in == 0)
+ {
+ // clear stats and mark us valid
+ min_micros = 0;
+ max_micros = 0;
+ valid = true;
+ valid_since = seconds;
+ ++valid_count;
+ reason[0] = '\0';
+ if (initial_valid == 0)
+ {
+ initial_valid = seconds;
+ }
+ }
+ }
+
+ seconds++;
+ //
+ // the first time around we just initialize the last value
+ //
+ if (last_micros == 0)
+ {
+ last_micros = cur_micros;
+ DEBUG_LED_OFF();
+ return;
+ }
+
+ uint32_t micros_count = cur_micros - last_micros;
+ last_micros = cur_micros;
+
+ if (min_micros == 0 || micros_count < min_micros)
+ {
+ min_micros = micros_count;
+ }
+
+ if (micros_count > max_micros)
+ {
+ max_micros = micros_count;
+ }
+
+ DEBUG_LED_OFF();
+}
+
+//
+// read a single NMEA record
+//
+static char *readNMEA()
+{
+ static uint8_t line[RX_BUF_SIZE+1];
+ int size;
+ uint8_t *p = line;
+ *p = '\0';
+ while(1)
+ {
+ if (p >= line+RX_BUF_SIZE)
+ {
+ ESP_LOGI(TAG, "Read buffer overflow! size %d bytes: '%s'", p-line, line);
+ }
+ size = uart_read_bytes(GPS_UART, (unsigned char *)p, 1, portMAX_DELAY);
+ if (size == 1)
+ {
+ if (*p == '$')
+ {
+ //
+ // '$' starts a record so reset pointer to start
+ //
+ p = line;
+ *p = '$';
+ }
+ else if (*p == '\r' || line[0] != '$')
+ {
+ //
+ // skip newlin and any chars if buffer does not start with '$'
+ continue;
+ }
+ else if (*p == '\n')
+ {
+ //
+ // got a record!
+ //
+ *p = 0;
+ break;
+ }
+ p++;
+ }
+ }
+
+ return (char*)line;
+}
+
+
+
+static void IRAM_ATTR invalidate(const char* fmt, ...)
+{
+ va_list argp;
+ va_start(argp, fmt);
+ if (reason[0] == '\0')
+ {
+ vsnprintf((char*)reason, sizeof(reason)-1, fmt, argp);
+ }
+ va_end(argp);
+
+ if (valid)
+ {
+ valid_since = seconds;
+ }
+
+ valid = false;
+ gps_valid = false;
+ last_micros = 0;
+ valid_in = 0;
+}
+
+static void init_pps()
+{
+ //
+ // set up the PPS interrupt
+ //
+
+ gpio_config_t io_conf;
+ //interrupt of falling edge
+ io_conf.intr_type = GPIO_PIN_INTR_POSEDGE;
+ //set as input mode
+ io_conf.mode = GPIO_MODE_INPUT;
+ //bit mask of the pins that you want to set,e.g.GPIO18/19
+ io_conf.pin_bit_mask = (1ULL< ts.tv_sec)
+ {
+ timewarps += 1;
+ ESP_LOGD(TAG, "$RMC: ignoring timewarp back! delayed serial? %lu -> %lu", seconds, ts.tv_sec);
+ if (timewarps > 1)
+ {
+ invalidate("time warped backwards too many (%d) times!", timewarps);
+ timewarps = 0;
+ }
+ }
+ else if (seconds != ts.tv_sec)
+ {
+ ESP_LOGW(TAG, "$RMC: adjusting seconds %lu -> %lu", seconds, ts.tv_sec);
+ seconds = ts.tv_sec;
+ }
+ else
+ {
+ timewarps = 0;
+ }
+
+ //
+ // if gps was not valid, it is now
+ //
+ if (!gps_valid)
+ {
+ gps_valid = true;
+ valid_in = PPS_VALID_COUNT;
+ ESP_LOGI(TAG, "gps valid!");
+ }
+ }
+ else
+ {
+ if (valid)
+ {
+ invalidate("GPS $RMC");
+ }
+ }
+ }
+ else
+ {
+ ESP_LOGE(TAG, "$RMC: failed to parse line: %s", line);
+ }
+ }
+ break;
+
+ case MINMEA_SENTENCE_GGA:
+ {
+ struct minmea_sentence_gga frame;
+ if (minmea_parse_gga(&frame, line)) {
+ ESP_LOGD(TAG, "$GGA: seconds: %lu timeouts: %u fix: %d sats: %d", seconds, timeouts, frame.fix_quality, frame.satellites_tracked);
+ gps_status.sat_count = frame.satellites_tracked;
+ gps_status.fix_quality = frame.fix_quality;
+ }
+ else
+ {
+ ESP_LOGE(TAG, "$GGA: failed to parse line: %s", line);
+ }
+ }
+ break;
+
+ case MINMEA_SENTENCE_GSV:
+ case MINMEA_SENTENCE_GSA:
+ case MINMEA_SENTENCE_GLL:
+ case MINMEA_SENTENCE_GST:
+ case MINMEA_SENTENCE_VTG:
+ case MINMEA_SENTENCE_ZDA:
+ ESP_LOGV(TAG, "IGNORING: %s", line);
+ break;
+
+ case MINMEA_INVALID:
+ ESP_LOGE(TAG, "INVALID: '%s'", line);
+ break;
+
+ case MINMEA_UNKNOWN:
+ ESP_LOGW(TAG, "UNKNOWN: '%s'", line);
+ break;
+ }
+
+ //
+ // Recompute dispersion periodically
+ //
+ static time_t last_seconds;
+ if (seconds != last_seconds)
+ {
+ double disp = us2s(MAX(abs(MICROS_PER_SEC-max_micros), abs(MICROS_PER_SEC-min_micros)));
+ dispersion = (uint32_t)(disp * 65536.0);
+#if 0
+ ESP_LOGI(TAG, "min: %llu max: %llu jitter: %llu sats: %d fix: %d valid_count: %u valid_in: %u valid: %s",
+ min_micros, max_micros, max_micros - min_micros, gps_status.sat_count, gps_status.fix_quality, valid_count, valid_in, valid ? "true" : "false");
+#endif
+ }
+ last_seconds = seconds;
+
+
+ }
+}
+
+void start_gps()
+{
+ esp_log_level_set(TAG, ESP_LOG_INFO);
+
+ xTaskCreatePinnedToCore(gps_task, "gps_task", 1024*2, NULL, GPS_TASK_PRI, NULL, 1);
+
+}
diff --git a/main/minmea/COPYING b/main/minmea/COPYING
new file mode 100644
index 0000000..c6c7def
--- /dev/null
+++ b/main/minmea/COPYING
@@ -0,0 +1,13 @@
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ Version 2, December 2004
+
+ Copyright (C) 2004 Sam Hocevar
+
+ Everyone is permitted to copy and distribute verbatim or modified
+ copies of this license document, and changing it is allowed as long
+ as the name is changed.
+
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
diff --git a/main/minmea/minmea.c b/main/minmea/minmea.c
new file mode 100644
index 0000000..e45b019
--- /dev/null
+++ b/main/minmea/minmea.c
@@ -0,0 +1,645 @@
+/*
+ * Copyright © 2014 Kosma Moczek
+ * This program is free software. It comes without any warranty, to the extent
+ * permitted by applicable law. You can redistribute it and/or modify it under
+ * the terms of the Do What The Fuck You Want To Public License, Version 2, as
+ * published by Sam Hocevar. See the COPYING file for more details.
+ */
+
+#include "minmea.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#define boolstr(s) ((s) ? "true" : "false")
+
+static int hex2int(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return -1;
+}
+
+uint8_t minmea_checksum(const char *sentence)
+{
+ // Support senteces with or without the starting dollar sign.
+ if (*sentence == '$')
+ sentence++;
+
+ uint8_t checksum = 0x00;
+
+ // The optional checksum is an XOR of all bytes between "$" and "*".
+ while (*sentence && *sentence != '*')
+ checksum ^= *sentence++;
+
+ return checksum;
+}
+
+bool minmea_check(const char *sentence, bool strict)
+{
+ uint8_t checksum = 0x00;
+
+ // Sequence length is limited.
+ if (strlen(sentence) > MINMEA_MAX_LENGTH + 3)
+ return false;
+
+ // A valid sentence starts with "$".
+ if (*sentence++ != '$')
+ return false;
+
+ // The optional checksum is an XOR of all bytes between "$" and "*".
+ while (*sentence && *sentence != '*' && isprint((unsigned char) *sentence))
+ checksum ^= *sentence++;
+
+ // If checksum is present...
+ if (*sentence == '*') {
+ // Extract checksum.
+ sentence++;
+ int upper = hex2int(*sentence++);
+ if (upper == -1)
+ return false;
+ int lower = hex2int(*sentence++);
+ if (lower == -1)
+ return false;
+ int expected = upper << 4 | lower;
+
+ // Check for checksum mismatch.
+ if (checksum != expected)
+ return false;
+ } else if (strict) {
+ // Discard non-checksummed frames in strict mode.
+ return false;
+ }
+
+ // The only stuff allowed at this point is a newline.
+ if (*sentence && strcmp(sentence, "\n") && strcmp(sentence, "\r\n"))
+ return false;
+
+ return true;
+}
+
+static inline bool minmea_isfield(char c) {
+ return isprint((unsigned char) c) && c != ',' && c != '*';
+}
+
+bool minmea_scan(const char *sentence, const char *format, ...)
+{
+ bool result = false;
+ bool optional = false;
+ va_list ap;
+ va_start(ap, format);
+
+ const char *field = sentence;
+#define next_field() \
+ do { \
+ /* Progress to the next field. */ \
+ while (minmea_isfield(*sentence)) \
+ sentence++; \
+ /* Make sure there is a field there. */ \
+ if (*sentence == ',') { \
+ sentence++; \
+ field = sentence; \
+ } else { \
+ field = NULL; \
+ } \
+ } while (0)
+
+ while (*format) {
+ char type = *format++;
+
+ if (type == ';') {
+ // All further fields are optional.
+ optional = true;
+ continue;
+ }
+
+ if (!field && !optional) {
+ // Field requested but we ran out if input. Bail out.
+ goto parse_error;
+ }
+
+ switch (type) {
+ case 'c': { // Single character field (char).
+ char value = '\0';
+
+ if (field && minmea_isfield(*field))
+ value = *field;
+
+ *va_arg(ap, char *) = value;
+ } break;
+
+ case 'd': { // Single character direction field (int).
+ int value = 0;
+
+ if (field && minmea_isfield(*field)) {
+ switch (*field) {
+ case 'N':
+ case 'E':
+ value = 1;
+ break;
+ case 'S':
+ case 'W':
+ value = -1;
+ break;
+ default:
+ goto parse_error;
+ }
+ }
+
+ *va_arg(ap, int *) = value;
+ } break;
+
+ case 'f': { // Fractional value with scale (struct minmea_float).
+ int sign = 0;
+ int_least32_t value = -1;
+ int_least32_t scale = 0;
+
+ if (field) {
+ while (minmea_isfield(*field)) {
+ if (*field == '+' && !sign && value == -1) {
+ sign = 1;
+ } else if (*field == '-' && !sign && value == -1) {
+ sign = -1;
+ } else if (isdigit((unsigned char) *field)) {
+ int digit = *field - '0';
+ if (value == -1)
+ value = 0;
+ if (value > (INT_LEAST32_MAX-digit) / 10) {
+ /* we ran out of bits, what do we do? */
+ if (scale) {
+ /* truncate extra precision */
+ break;
+ } else {
+ /* integer overflow. bail out. */
+ goto parse_error;
+ }
+ }
+ value = (10 * value) + digit;
+ if (scale)
+ scale *= 10;
+ } else if (*field == '.' && scale == 0) {
+ scale = 1;
+ } else if (*field == ' ') {
+ /* Allow spaces at the start of the field. Not NMEA
+ * conformant, but some modules do this. */
+ if (sign != 0 || value != -1 || scale != 0)
+ goto parse_error;
+ } else {
+ goto parse_error;
+ }
+ field++;
+ }
+ }
+
+ if ((sign || scale) && value == -1)
+ goto parse_error;
+
+ if (value == -1) {
+ /* No digits were scanned. */
+ value = 0;
+ scale = 0;
+ } else if (scale == 0) {
+ /* No decimal point. */
+ scale = 1;
+ }
+ if (sign)
+ value *= sign;
+
+ *va_arg(ap, struct minmea_float *) = (struct minmea_float) {value, scale};
+ } break;
+
+ case 'i': { // Integer value, default 0 (int).
+ int value = 0;
+
+ if (field) {
+ char *endptr;
+ value = strtol(field, &endptr, 10);
+ if (minmea_isfield(*endptr))
+ goto parse_error;
+ }
+
+ *va_arg(ap, int *) = value;
+ } break;
+
+ case 's': { // String value (char *).
+ char *buf = va_arg(ap, char *);
+
+ if (field) {
+ while (minmea_isfield(*field))
+ *buf++ = *field++;
+ }
+
+ *buf = '\0';
+ } break;
+
+ case 't': { // NMEA talker+sentence identifier (char *).
+ // This field is always mandatory.
+ if (!field)
+ goto parse_error;
+
+ if (field[0] != '$')
+ goto parse_error;
+ for (int f=0; f<5; f++)
+ if (!minmea_isfield(field[1+f]))
+ goto parse_error;
+
+ char *buf = va_arg(ap, char *);
+ memcpy(buf, field+1, 5);
+ buf[5] = '\0';
+ } break;
+
+ case 'D': { // Date (int, int, int), -1 if empty.
+ struct minmea_date *date = va_arg(ap, struct minmea_date *);
+
+ int d = -1, m = -1, y = -1;
+
+ if (field && minmea_isfield(*field)) {
+ // Always six digits.
+ for (int f=0; f<6; f++)
+ if (!isdigit((unsigned char) field[f]))
+ goto parse_error;
+
+ char dArr[] = {field[0], field[1], '\0'};
+ char mArr[] = {field[2], field[3], '\0'};
+ char yArr[] = {field[4], field[5], '\0'};
+ d = strtol(dArr, NULL, 10);
+ m = strtol(mArr, NULL, 10);
+ y = strtol(yArr, NULL, 10);
+ }
+
+ date->day = d;
+ date->month = m;
+ date->year = y;
+ } break;
+
+ case 'T': { // Time (int, int, int, int), -1 if empty.
+ struct minmea_time *time_ = va_arg(ap, struct minmea_time *);
+
+ int h = -1, i = -1, s = -1, u = -1;
+
+ if (field && minmea_isfield(*field)) {
+ // Minimum required: integer time.
+ for (int f=0; f<6; f++)
+ if (!isdigit((unsigned char) field[f]))
+ goto parse_error;
+
+ char hArr[] = {field[0], field[1], '\0'};
+ char iArr[] = {field[2], field[3], '\0'};
+ char sArr[] = {field[4], field[5], '\0'};
+ h = strtol(hArr, NULL, 10);
+ i = strtol(iArr, NULL, 10);
+ s = strtol(sArr, NULL, 10);
+ field += 6;
+
+ // Extra: fractional time. Saved as microseconds.
+ if (*field++ == '.') {
+ uint32_t value = 0;
+ uint32_t scale = 1000000LU;
+ while (isdigit((unsigned char) *field) && scale > 1) {
+ value = (value * 10) + (*field++ - '0');
+ scale /= 10;
+ }
+ u = value * scale;
+ } else {
+ u = 0;
+ }
+ }
+
+ time_->hours = h;
+ time_->minutes = i;
+ time_->seconds = s;
+ time_->microseconds = u;
+ } break;
+
+ case '_': { // Ignore the field.
+ } break;
+
+ default: { // Unknown.
+ goto parse_error;
+ } break;
+ }
+
+ next_field();
+ }
+
+ result = true;
+
+parse_error:
+ va_end(ap);
+ return result;
+}
+
+bool minmea_talker_id(char talker[3], const char *sentence)
+{
+ char type[6];
+ if (!minmea_scan(sentence, "t", type))
+ return false;
+
+ talker[0] = type[0];
+ talker[1] = type[1];
+ talker[2] = '\0';
+
+ return true;
+}
+
+enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict)
+{
+ if (!minmea_check(sentence, strict))
+ return MINMEA_INVALID;
+
+ char type[6];
+ if (!minmea_scan(sentence, "t", type))
+ return MINMEA_INVALID;
+
+ if (!strcmp(type+2, "RMC"))
+ return MINMEA_SENTENCE_RMC;
+ if (!strcmp(type+2, "GGA"))
+ return MINMEA_SENTENCE_GGA;
+ if (!strcmp(type+2, "GSA"))
+ return MINMEA_SENTENCE_GSA;
+ if (!strcmp(type+2, "GLL"))
+ return MINMEA_SENTENCE_GLL;
+ if (!strcmp(type+2, "GST"))
+ return MINMEA_SENTENCE_GST;
+ if (!strcmp(type+2, "GSV"))
+ return MINMEA_SENTENCE_GSV;
+ if (!strcmp(type+2, "VTG"))
+ return MINMEA_SENTENCE_VTG;
+ if (!strcmp(type+2, "ZDA"))
+ return MINMEA_SENTENCE_ZDA;
+
+ return MINMEA_UNKNOWN;
+}
+
+bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence)
+{
+ // $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
+ char type[6];
+ char validity;
+ int latitude_direction;
+ int longitude_direction;
+ int variation_direction;
+ if (!minmea_scan(sentence, "tTcfdfdffDfd",
+ type,
+ &frame->time,
+ &validity,
+ &frame->latitude, &latitude_direction,
+ &frame->longitude, &longitude_direction,
+ &frame->speed,
+ &frame->course,
+ &frame->date,
+ &frame->variation, &variation_direction))
+ return false;
+ if (strcmp(type+2, "RMC"))
+ return false;
+
+ frame->valid = (validity == 'A');
+ frame->latitude.value *= latitude_direction;
+ frame->longitude.value *= longitude_direction;
+ frame->variation.value *= variation_direction;
+
+ return true;
+}
+
+bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence)
+{
+ // $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
+ char type[6];
+ int latitude_direction;
+ int longitude_direction;
+
+ if (!minmea_scan(sentence, "tTfdfdiiffcfci_",
+ type,
+ &frame->time,
+ &frame->latitude, &latitude_direction,
+ &frame->longitude, &longitude_direction,
+ &frame->fix_quality,
+ &frame->satellites_tracked,
+ &frame->hdop,
+ &frame->altitude, &frame->altitude_units,
+ &frame->height, &frame->height_units,
+ &frame->dgps_age))
+ return false;
+ if (strcmp(type+2, "GGA"))
+ return false;
+
+ frame->latitude.value *= latitude_direction;
+ frame->longitude.value *= longitude_direction;
+
+ return true;
+}
+
+bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence)
+{
+ // $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
+ char type[6];
+
+ if (!minmea_scan(sentence, "tciiiiiiiiiiiiifff",
+ type,
+ &frame->mode,
+ &frame->fix_type,
+ &frame->sats[0],
+ &frame->sats[1],
+ &frame->sats[2],
+ &frame->sats[3],
+ &frame->sats[4],
+ &frame->sats[5],
+ &frame->sats[6],
+ &frame->sats[7],
+ &frame->sats[8],
+ &frame->sats[9],
+ &frame->sats[10],
+ &frame->sats[11],
+ &frame->pdop,
+ &frame->hdop,
+ &frame->vdop))
+ return false;
+ if (strcmp(type+2, "GSA"))
+ return false;
+
+ return true;
+}
+
+bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence)
+{
+ // $GPGLL,3723.2475,N,12158.3416,W,161229.487,A,A*41$;
+ char type[6];
+ int latitude_direction;
+ int longitude_direction;
+
+ if (!minmea_scan(sentence, "tfdfdTc;c",
+ type,
+ &frame->latitude, &latitude_direction,
+ &frame->longitude, &longitude_direction,
+ &frame->time,
+ &frame->status,
+ &frame->mode))
+ return false;
+ if (strcmp(type+2, "GLL"))
+ return false;
+
+ frame->latitude.value *= latitude_direction;
+ frame->longitude.value *= longitude_direction;
+
+ return true;
+}
+
+bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence)
+{
+ // $GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58
+ char type[6];
+
+ if (!minmea_scan(sentence, "tTfffffff",
+ type,
+ &frame->time,
+ &frame->rms_deviation,
+ &frame->semi_major_deviation,
+ &frame->semi_minor_deviation,
+ &frame->semi_major_orientation,
+ &frame->latitude_error_deviation,
+ &frame->longitude_error_deviation,
+ &frame->altitude_error_deviation))
+ return false;
+ if (strcmp(type+2, "GST"))
+ return false;
+
+ return true;
+}
+
+bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence)
+{
+ // $GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74
+ // $GPGSV,3,3,11,22,42,067,42,24,14,311,43,27,05,244,00,,,,*4D
+ // $GPGSV,4,2,11,08,51,203,30,09,45,215,28*75
+ // $GPGSV,4,4,13,39,31,170,27*40
+ // $GPGSV,4,4,13*7B
+ char type[6];
+
+ if (!minmea_scan(sentence, "tiii;iiiiiiiiiiiiiiii",
+ type,
+ &frame->total_msgs,
+ &frame->msg_nr,
+ &frame->total_sats,
+ &frame->sats[0].nr,
+ &frame->sats[0].elevation,
+ &frame->sats[0].azimuth,
+ &frame->sats[0].snr,
+ &frame->sats[1].nr,
+ &frame->sats[1].elevation,
+ &frame->sats[1].azimuth,
+ &frame->sats[1].snr,
+ &frame->sats[2].nr,
+ &frame->sats[2].elevation,
+ &frame->sats[2].azimuth,
+ &frame->sats[2].snr,
+ &frame->sats[3].nr,
+ &frame->sats[3].elevation,
+ &frame->sats[3].azimuth,
+ &frame->sats[3].snr
+ )) {
+ return false;
+ }
+ if (strcmp(type+2, "GSV"))
+ return false;
+
+ return true;
+}
+
+bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence)
+{
+ // $GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48
+ // $GPVTG,156.1,T,140.9,M,0.0,N,0.0,K*41
+ // $GPVTG,096.5,T,083.5,M,0.0,N,0.0,K,D*22
+ // $GPVTG,188.36,T,,M,0.820,N,1.519,K,A*3F
+ char type[6];
+ char c_true, c_magnetic, c_knots, c_kph, c_faa_mode;
+
+ if (!minmea_scan(sentence, "tfcfcfcfc;c",
+ type,
+ &frame->true_track_degrees,
+ &c_true,
+ &frame->magnetic_track_degrees,
+ &c_magnetic,
+ &frame->speed_knots,
+ &c_knots,
+ &frame->speed_kph,
+ &c_kph,
+ &c_faa_mode))
+ return false;
+ if (strcmp(type+2, "VTG"))
+ return false;
+ // check chars
+ if (c_true != 'T' ||
+ c_magnetic != 'M' ||
+ c_knots != 'N' ||
+ c_kph != 'K')
+ return false;
+ frame->faa_mode = c_faa_mode;
+
+ return true;
+}
+
+bool minmea_parse_zda(struct minmea_sentence_zda *frame, const char *sentence)
+{
+ // $GPZDA,201530.00,04,07,2002,00,00*60
+ char type[6];
+
+ if(!minmea_scan(sentence, "tTiiiii",
+ type,
+ &frame->time,
+ &frame->date.day,
+ &frame->date.month,
+ &frame->date.year,
+ &frame->hour_offset,
+ &frame->minute_offset))
+ return false;
+ if (strcmp(type+2, "ZDA"))
+ return false;
+
+ // check offsets
+ if (abs(frame->hour_offset) > 13 ||
+ frame->minute_offset > 59 ||
+ frame->minute_offset < 0)
+ return false;
+
+ return true;
+}
+
+int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_)
+{
+ if (date->year == -1 || time_->hours == -1)
+ return -1;
+
+ struct tm tm;
+ memset(&tm, 0, sizeof(tm));
+ if (date->year < 80) {
+ tm.tm_year = 2000 + date->year - 1900; // 2000-2079
+ } else if (date->year >= 1900) {
+ tm.tm_year = date->year - 1900; // 4 digit year, use directly
+ } else {
+ tm.tm_year = date->year; // 1980-1999
+ }
+ tm.tm_mon = date->month - 1;
+ tm.tm_mday = date->day;
+ tm.tm_hour = time_->hours;
+ tm.tm_min = time_->minutes;
+ tm.tm_sec = time_->seconds;
+
+ time_t timestamp = mktime(&tm);
+ if (timestamp != -1) {
+ ts->tv_sec = timestamp;
+ ts->tv_nsec = time_->microseconds * 1000;
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/* vim: set ts=4 sw=4 et: */
diff --git a/main/minmea/minmea.h b/main/minmea/minmea.h
new file mode 100644
index 0000000..b80124c
--- /dev/null
+++ b/main/minmea/minmea.h
@@ -0,0 +1,261 @@
+/*
+ * Copyright © 2014 Kosma Moczek
+ * This program is free software. It comes without any warranty, to the extent
+ * permitted by applicable law. You can redistribute it and/or modify it under
+ * the terms of the Do What The Fuck You Want To Public License, Version 2, as
+ * published by Sam Hocevar. See the COPYING file for more details.
+ */
+
+#ifndef MINMEA_H
+#define MINMEA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#ifdef MINMEA_INCLUDE_COMPAT
+#include
+#endif
+
+#define MINMEA_MAX_LENGTH 80
+
+enum minmea_sentence_id {
+ MINMEA_INVALID = -1,
+ MINMEA_UNKNOWN = 0,
+ MINMEA_SENTENCE_RMC,
+ MINMEA_SENTENCE_GGA,
+ MINMEA_SENTENCE_GSA,
+ MINMEA_SENTENCE_GLL,
+ MINMEA_SENTENCE_GST,
+ MINMEA_SENTENCE_GSV,
+ MINMEA_SENTENCE_VTG,
+ MINMEA_SENTENCE_ZDA,
+};
+
+struct minmea_float {
+ int_least32_t value;
+ int_least32_t scale;
+};
+
+struct minmea_date {
+ int day;
+ int month;
+ int year;
+};
+
+struct minmea_time {
+ int hours;
+ int minutes;
+ int seconds;
+ int microseconds;
+};
+
+struct minmea_sentence_rmc {
+ struct minmea_time time;
+ bool valid;
+ struct minmea_float latitude;
+ struct minmea_float longitude;
+ struct minmea_float speed;
+ struct minmea_float course;
+ struct minmea_date date;
+ struct minmea_float variation;
+};
+
+struct minmea_sentence_gga {
+ struct minmea_time time;
+ struct minmea_float latitude;
+ struct minmea_float longitude;
+ int fix_quality;
+ int satellites_tracked;
+ struct minmea_float hdop;
+ struct minmea_float altitude; char altitude_units;
+ struct minmea_float height; char height_units;
+ int dgps_age;
+};
+
+enum minmea_gll_status {
+ MINMEA_GLL_STATUS_DATA_VALID = 'A',
+ MINMEA_GLL_STATUS_DATA_NOT_VALID = 'V',
+};
+
+// FAA mode added to some fields in NMEA 2.3.
+enum minmea_faa_mode {
+ MINMEA_FAA_MODE_AUTONOMOUS = 'A',
+ MINMEA_FAA_MODE_DIFFERENTIAL = 'D',
+ MINMEA_FAA_MODE_ESTIMATED = 'E',
+ MINMEA_FAA_MODE_MANUAL = 'M',
+ MINMEA_FAA_MODE_SIMULATED = 'S',
+ MINMEA_FAA_MODE_NOT_VALID = 'N',
+ MINMEA_FAA_MODE_PRECISE = 'P',
+};
+
+struct minmea_sentence_gll {
+ struct minmea_float latitude;
+ struct minmea_float longitude;
+ struct minmea_time time;
+ char status;
+ char mode;
+};
+
+struct minmea_sentence_gst {
+ struct minmea_time time;
+ struct minmea_float rms_deviation;
+ struct minmea_float semi_major_deviation;
+ struct minmea_float semi_minor_deviation;
+ struct minmea_float semi_major_orientation;
+ struct minmea_float latitude_error_deviation;
+ struct minmea_float longitude_error_deviation;
+ struct minmea_float altitude_error_deviation;
+};
+
+enum minmea_gsa_mode {
+ MINMEA_GPGSA_MODE_AUTO = 'A',
+ MINMEA_GPGSA_MODE_FORCED = 'M',
+};
+
+enum minmea_gsa_fix_type {
+ MINMEA_GPGSA_FIX_NONE = 1,
+ MINMEA_GPGSA_FIX_2D = 2,
+ MINMEA_GPGSA_FIX_3D = 3,
+};
+
+struct minmea_sentence_gsa {
+ char mode;
+ int fix_type;
+ int sats[12];
+ struct minmea_float pdop;
+ struct minmea_float hdop;
+ struct minmea_float vdop;
+};
+
+struct minmea_sat_info {
+ int nr;
+ int elevation;
+ int azimuth;
+ int snr;
+};
+
+struct minmea_sentence_gsv {
+ int total_msgs;
+ int msg_nr;
+ int total_sats;
+ struct minmea_sat_info sats[4];
+};
+
+struct minmea_sentence_vtg {
+ struct minmea_float true_track_degrees;
+ struct minmea_float magnetic_track_degrees;
+ struct minmea_float speed_knots;
+ struct minmea_float speed_kph;
+ enum minmea_faa_mode faa_mode;
+};
+
+struct minmea_sentence_zda {
+ struct minmea_time time;
+ struct minmea_date date;
+ int hour_offset;
+ int minute_offset;
+};
+
+/**
+ * Calculate raw sentence checksum. Does not check sentence integrity.
+ */
+uint8_t minmea_checksum(const char *sentence);
+
+/**
+ * Check sentence validity and checksum. Returns true for valid sentences.
+ */
+bool minmea_check(const char *sentence, bool strict);
+
+/**
+ * Determine talker identifier.
+ */
+bool minmea_talker_id(char talker[3], const char *sentence);
+
+/**
+ * Determine sentence identifier.
+ */
+enum minmea_sentence_id minmea_sentence_id(const char *sentence, bool strict);
+
+/**
+ * Scanf-like processor for NMEA sentences. Supports the following formats:
+ * c - single character (char *)
+ * d - direction, returned as 1/-1, default 0 (int *)
+ * f - fractional, returned as value + scale (int *, int *)
+ * i - decimal, default zero (int *)
+ * s - string (char *)
+ * t - talker identifier and type (char *)
+ * T - date/time stamp (int *, int *, int *)
+ * Returns true on success. See library source code for details.
+ */
+bool minmea_scan(const char *sentence, const char *format, ...);
+
+/*
+ * Parse a specific type of sentence. Return true on success.
+ */
+bool minmea_parse_rmc(struct minmea_sentence_rmc *frame, const char *sentence);
+bool minmea_parse_gga(struct minmea_sentence_gga *frame, const char *sentence);
+bool minmea_parse_gsa(struct minmea_sentence_gsa *frame, const char *sentence);
+bool minmea_parse_gll(struct minmea_sentence_gll *frame, const char *sentence);
+bool minmea_parse_gst(struct minmea_sentence_gst *frame, const char *sentence);
+bool minmea_parse_gsv(struct minmea_sentence_gsv *frame, const char *sentence);
+bool minmea_parse_vtg(struct minmea_sentence_vtg *frame, const char *sentence);
+bool minmea_parse_zda(struct minmea_sentence_zda *frame, const char *sentence);
+
+/**
+ * Convert GPS UTC date/time representation to a UNIX timestamp.
+ */
+int minmea_gettime(struct timespec *ts, const struct minmea_date *date, const struct minmea_time *time_);
+
+/**
+ * Rescale a fixed-point value to a different scale. Rounds towards zero.
+ */
+static inline int_least32_t minmea_rescale(struct minmea_float *f, int_least32_t new_scale)
+{
+ if (f->scale == 0)
+ return 0;
+ if (f->scale == new_scale)
+ return f->value;
+ if (f->scale > new_scale)
+ return (f->value + ((f->value > 0) - (f->value < 0)) * f->scale/new_scale/2) / (f->scale/new_scale);
+ else
+ return f->value * (new_scale/f->scale);
+}
+
+/**
+ * Convert a fixed-point value to a floating-point value.
+ * Returns NaN for "unknown" values.
+ */
+static inline float minmea_tofloat(struct minmea_float *f)
+{
+ if (f->scale == 0)
+ return NAN;
+ return (float) f->value / (float) f->scale;
+}
+
+/**
+ * Convert a raw coordinate to a floating point DD.DDD... value.
+ * Returns NaN for "unknown" values.
+ */
+static inline float minmea_tocoord(struct minmea_float *f)
+{
+ if (f->scale == 0)
+ return NAN;
+ int_least32_t degrees = f->value / (f->scale * 100);
+ int_least32_t minutes = f->value % (f->scale * 100);
+ return (float) degrees + (float) minutes / (60 * f->scale);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MINMEA_H */
+
+/* vim: set ts=4 sw=4 et: */
diff --git a/main/ntp_task.c b/main/ntp_task.c
new file mode 100644
index 0000000..5860bd1
--- /dev/null
+++ b/main/ntp_task.c
@@ -0,0 +1,255 @@
+/*
+ * ntp_task.c
+ *
+ * Created on: Jan 21, 2018
+ * Author: chris.l
+ */
+
+#include "esp_gps_ntp.h"
+
+#include
+#include
+#include
+
+
+static const char *TAG = "NTP";
+
+#define NTP_PORT 123
+
+
+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;
+
+#ifdef NTP_PACKET_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
+
+#define PRECISION_COUNT 10000
+
+#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)
+
+static int8_t precision;
+static uint32_t req_count;
+static uint32_t rsp_count;
+
+void getNTPTaskStatus(NTPTaskStatus* status)
+{
+ status->req_count = req_count;
+ status->rsp_count = rsp_count;
+}
+
+static void getNTPTime(NTPTime *time)
+{
+ time_t seconds;
+ time_t usecs;
+ getTime(&seconds, &usecs);
+ time->seconds = toNTP(seconds);
+
+ //
+ // if micros_delta is at or bigger than one second then
+ // use the max fraction.
+ //
+ if (usecs >= 1000000)
+ {
+ time->fraction = 0xffffffff;
+ return;
+ }
+
+ time->fraction = (uint32_t)(us2s(usecs) * (double)4294967296L);
+}
+
+int8_t computePrecision()
+{
+ NTPTime t;
+ unsigned long start = micros();
+ for (int i = 0; i < PRECISION_COUNT; ++i)
+ {
+ getNTPTime(&t);
+ }
+ unsigned long end = micros();
+ double total = (double)(end - start) / 1000000.0;
+ double time = total / PRECISION_COUNT;
+ double prec = log2(time);
+ ESP_LOGI(TAG, "computePrecision: total:%f time:%f prec:%f\n", total, time, prec);
+ return (int8_t)prec;
+}
+
+static void ntp_task()
+{
+ static NTPPacket ntp;
+ static int udp_server = -1;
+
+ ESP_LOGI(TAG, "starting!");
+
+ precision = computePrecision();
+ ESP_LOGI(TAG, "precision: %d", precision);
+ if ((udp_server=socket(AF_INET, SOCK_DGRAM, 0)) == -1)
+ {
+ ESP_LOGI(TAG, "could not create socket: %d", errno);
+ return;
+ }
+
+ int yes = 1;
+ if (setsockopt(udp_server,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)) < 0)
+ {
+ ESP_LOGI(TAG, "could not set socket option: %d", errno);
+ return;
+ }
+
+ struct sockaddr_in addr;
+ memset((char *) &addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(NTP_PORT);
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ if(bind(udp_server , (struct sockaddr*)&addr, sizeof(addr)) == -1)
+ {
+ ESP_LOGI(TAG, "could not bind socket: %d", errno);
+ return;
+ }
+
+ while(1)
+ {
+ int len;
+ int slen = sizeof(addr);
+ if ((len = recvfrom(udp_server, &ntp, sizeof(ntp), 0, (struct sockaddr *) &addr, (socklen_t *)&slen)) == -1)
+ {
+ ESP_LOGI(TAG, "could not receive data: %d", errno);
+ continue;
+ }
+
+ ++req_count;
+
+ NTPTime recv_time;
+ getNTPTime(&recv_time);
+
+ if (len != sizeof(NTPPacket))
+ {
+ ESP_LOGI(TAG, "ignoring packet with bad length: %d < %d\n", len, sizeof(ntp));
+ continue;
+ }
+
+ if (!isTimeValid())
+ {
+ ESP_LOGI(TAG, "GPS time data not valid!");
+ continue;
+ }
+
+ 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 = precision;
+ // TODO: compute actual root delay, and root dispersion
+ ntp.delay = (uint32_t)(0.000001 * 65536);
+ ntp.dispersion = getDispersion();
+ 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);
+
+ int sent = sendto(udp_server, &ntp, sizeof(ntp), 0, (struct sockaddr*) &addr, sizeof(addr));
+ if(sent < 0)
+ {
+ ESP_LOGI(TAG, "could not send data: %d", errno);
+ continue;
+ }
+ ++rsp_count;
+ }
+}
+
+void start_ntp()
+{
+ esp_log_level_set(TAG, ESP_LOG_INFO);
+
+ xTaskCreatePinnedToCore(ntp_task, "ntp_task", 1024*2, NULL, NTP_TASK_PRI, NULL, 1);
+}
diff --git a/main/u8g2/u8g2_esp32_hal.c b/main/u8g2/u8g2_esp32_hal.c
new file mode 100644
index 0000000..67a0695
--- /dev/null
+++ b/main/u8g2/u8g2_esp32_hal.c
@@ -0,0 +1,234 @@
+#include
+#include
+
+#include "sdkconfig.h"
+#include "esp_log.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#include "u8g2_esp32_hal.h"
+
+static const char *TAG = "u8g2_hal";
+static const unsigned int I2C_TIMEOUT_MS = 1000;
+
+static spi_device_handle_t handle_spi; // SPI handle.
+static i2c_cmd_handle_t handle_i2c; // I2C handle.
+static u8g2_esp32_hal_t u8g2_esp32_hal; // HAL state data.
+
+#undef ESP_ERROR_CHECK
+#define ESP_ERROR_CHECK(x) do { esp_err_t rc = (x); if (rc != ESP_OK) { ESP_LOGE("err", "esp_err_t = %d", rc); assert(0 && #x);} } while(0);
+
+/*
+ * Initialze the ESP32 HAL.
+ */
+void u8g2_esp32_hal_init(u8g2_esp32_hal_t u8g2_esp32_hal_param) {
+ u8g2_esp32_hal = u8g2_esp32_hal_param;
+} // u8g2_esp32_hal_init
+
+/*
+ * HAL callback function as prescribed by the U8G2 library. This callback is invoked
+ * to handle SPI communications.
+ */
+uint8_t u8g2_esp32_spi_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
+ ESP_LOGD(TAG, "spi_byte_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr);
+ switch(msg) {
+ case U8X8_MSG_BYTE_SET_DC:
+ if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) {
+ gpio_set_level(u8g2_esp32_hal.dc, arg_int);
+ }
+ break;
+
+ case U8X8_MSG_BYTE_INIT: {
+ if (u8g2_esp32_hal.clk == U8G2_ESP32_HAL_UNDEFINED ||
+ u8g2_esp32_hal.mosi == U8G2_ESP32_HAL_UNDEFINED ||
+ u8g2_esp32_hal.cs == U8G2_ESP32_HAL_UNDEFINED) {
+ break;
+ }
+
+ spi_bus_config_t bus_config;
+ bus_config.sclk_io_num = u8g2_esp32_hal.clk; // CLK
+ bus_config.mosi_io_num = u8g2_esp32_hal.mosi; // MOSI
+ bus_config.miso_io_num = -1; // MISO
+ bus_config.quadwp_io_num = -1; // Not used
+ bus_config.quadhd_io_num = -1; // Not used
+ //ESP_LOGI(TAG, "... Initializing bus.");
+ ESP_ERROR_CHECK(spi_bus_initialize(HSPI_HOST, &bus_config, 1));
+
+ spi_device_interface_config_t dev_config;
+ dev_config.address_bits = 0;
+ dev_config.command_bits = 0;
+ dev_config.dummy_bits = 0;
+ dev_config.mode = 0;
+ dev_config.duty_cycle_pos = 0;
+ dev_config.cs_ena_posttrans = 0;
+ dev_config.cs_ena_pretrans = 0;
+ dev_config.clock_speed_hz = 10000;
+ dev_config.spics_io_num = u8g2_esp32_hal.cs;
+ dev_config.flags = 0;
+ dev_config.queue_size = 200;
+ dev_config.pre_cb = NULL;
+ dev_config.post_cb = NULL;
+ //ESP_LOGI(TAG, "... Adding device bus.");
+ ESP_ERROR_CHECK(spi_bus_add_device(HSPI_HOST, &dev_config, &handle_spi));
+
+ break;
+ }
+
+ case U8X8_MSG_BYTE_SEND: {
+ spi_transaction_t trans_desc;
+ trans_desc.addr = 0;
+ trans_desc.cmd = 0;
+ trans_desc.flags = 0;
+ trans_desc.length = 8 * arg_int; // Number of bits NOT number of bytes.
+ trans_desc.rxlength = 0;
+ trans_desc.tx_buffer = arg_ptr;
+ trans_desc.rx_buffer = NULL;
+
+ //ESP_LOGI(TAG, "... Transmitting %d bytes.", arg_int);
+ ESP_ERROR_CHECK(spi_device_transmit(handle_spi, &trans_desc));
+ break;
+ }
+ }
+ return 0;
+} // u8g2_esp32_spi_byte_cb
+
+/*
+ * HAL callback function as prescribed by the U8G2 library. This callback is invoked
+ * to handle I2C communications.
+ */
+uint8_t u8g2_esp32_i2c_byte_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
+ ESP_LOGD(TAG, "i2c_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr);
+
+ switch(msg) {
+ case U8X8_MSG_BYTE_SET_DC: {
+ if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) {
+ gpio_set_level(u8g2_esp32_hal.dc, arg_int);
+ }
+ break;
+ }
+
+ case U8X8_MSG_BYTE_INIT: {
+ if (u8g2_esp32_hal.sda == U8G2_ESP32_HAL_UNDEFINED ||
+ u8g2_esp32_hal.scl == U8G2_ESP32_HAL_UNDEFINED) {
+ break;
+ }
+
+ i2c_config_t conf;
+ conf.mode = I2C_MODE_MASTER;
+ ESP_LOGI(TAG, "sda_io_num %d", u8g2_esp32_hal.sda);
+ conf.sda_io_num = u8g2_esp32_hal.sda;
+ conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
+ ESP_LOGI(TAG, "scl_io_num %d", u8g2_esp32_hal.scl);
+ conf.scl_io_num = u8g2_esp32_hal.scl;
+ conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
+ ESP_LOGI(TAG, "clk_speed %d", I2C_MASTER_FREQ_HZ);
+ conf.master.clk_speed = I2C_MASTER_FREQ_HZ;
+ ESP_LOGI(TAG, "i2c_param_config %d", conf.mode);
+ ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
+ ESP_LOGI(TAG, "i2c_driver_install %d", I2C_MASTER_NUM);
+ ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0));
+ break;
+ }
+
+ case U8X8_MSG_BYTE_SEND: {
+ uint8_t* data_ptr = (uint8_t*)arg_ptr;
+ ESP_LOG_BUFFER_HEXDUMP(TAG, data_ptr, arg_int, ESP_LOG_VERBOSE);
+
+ while( arg_int > 0 ) {
+ ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, *data_ptr, ACK_CHECK_EN));
+ data_ptr++;
+ arg_int--;
+ }
+ break;
+ }
+
+ case U8X8_MSG_BYTE_START_TRANSFER: {
+ uint8_t i2c_address = u8x8_GetI2CAddress(u8x8);
+ handle_i2c = i2c_cmd_link_create();
+ ESP_LOGD(TAG, "Start I2C transfer to %02X.", i2c_address);
+ ESP_ERROR_CHECK(i2c_master_start(handle_i2c));
+ ESP_ERROR_CHECK(i2c_master_write_byte(handle_i2c, i2c_address | I2C_MASTER_WRITE, ACK_CHECK_EN));
+ break;
+ }
+
+ case U8X8_MSG_BYTE_END_TRANSFER: {
+ ESP_LOGD(TAG, "End I2C transfer.");
+ ESP_ERROR_CHECK(i2c_master_stop(handle_i2c));
+ ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_MASTER_NUM, handle_i2c, I2C_TIMEOUT_MS / portTICK_RATE_MS));
+ i2c_cmd_link_delete(handle_i2c);
+ break;
+ }
+ }
+ return 0;
+} // u8g2_esp32_i2c_byte_cb
+
+/*
+ * HAL callback function as prescribed by the U8G2 library. This callback is invoked
+ * to handle callbacks for GPIO and delay functions.
+ */
+uint8_t u8g2_esp32_gpio_and_delay_cb(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
+ ESP_LOGD(TAG, "gpio_and_delay_cb: Received a msg: %d, arg_int: %d, arg_ptr: %p", msg, arg_int, arg_ptr);
+
+ switch(msg) {
+ // Initialize the GPIO and DELAY HAL functions. If the pins for DC and RESET have been
+ // specified then we define those pins as GPIO outputs.
+ case U8X8_MSG_GPIO_AND_DELAY_INIT: {
+ uint64_t bitmask = 0;
+ if (u8g2_esp32_hal.dc != U8G2_ESP32_HAL_UNDEFINED) {
+ bitmask = bitmask | (1<