diff --git a/.cproject b/.cproject
deleted file mode 100644
index 7cedfd6..0000000
--- a/.cproject
+++ /dev/null
@@ -1,57 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.gitignore b/.gitignore
index 7911b6b..0db0561 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,12 @@
+.metadata
.DS_Store
+.ino.cpp
+.project
+.cproject
+.settings
+Release
+Debug
+libraries
+core
*.bak
*-bak
-*.old
-build
-sdkconfig
diff --git a/.gitmodules b/.gitmodules
index 6879e83..e69de29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +0,0 @@
-[submodule "components/u8g2"]
- path = components/u8g2
- url = https://github.com/olikraus/u8g2.git
-[submodule "components/minmea/minmea"]
- path = components/minmea/minmea
- url = https://github.com/kosma/minmea.git
-[submodule "components/esp32-snippets/esp32-snippets"]
- path = components/esp32-snippets/esp32-snippets
- url = https://github.com/nkolban/esp32-snippets.git
diff --git a/.project b/.project
deleted file mode 100644
index 6f9dbee..0000000
--- a/.project
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
- 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
deleted file mode 100644
index 8a0f91f..0000000
--- a/.settings/language.settings.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs
deleted file mode 100644
index 530eb67..0000000
--- a/.settings/org.eclipse.cdt.core.prefs
+++ /dev/null
@@ -1,12 +0,0 @@
-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
new file mode 100644
index 0000000..d42e271
--- /dev/null
+++ b/ESPNTPServer.cpp
@@ -0,0 +1,170 @@
+/*
+ * 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()
+#include "GPS.h"
+#include "NTP.h"
+
+//#include "Logger.h"
+
+GPS gps(Serial, SYNC_PIN);
+NTP ntp(gps);
+
+#if defined(USE_OLED_DISPLAY)
+SSD1306Wire display(0x3c, SDA_PIN, SCL_PIN);
+#endif
+
+#include "Log.h"
+
+const char* SETUP_TAG = "setup";
+const char* LOOP_TAG = "loop";
+
+void logTimeFirst(Print* print)
+{
+ static struct timeval tv;
+ gps.getTime(&tv);
+ static struct tm tm;
+ gmtime_r(&tv.tv_sec, &tm);
+
+ print->printf("%04d/%02d/%02d %02d:%02d:%02d.%06ld ",
+ tm.tm_year+1900,
+ tm.tm_mon+1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ tv.tv_usec);
+}
+
+void setup()
+{
+ delay(5000); // delay for IDE to re-open serial
+ Serial1.begin(115200);
+ logger.setSize(256);
+ logger.setPrint(&Serial1);
+ logger.setPreFunc(&logTimeFirst);
+ logger.info(SETUP_TAG, "\nStartup!");
+
+
+#if !defined(USE_NO_WIFI)
+ logger.info(SETUP_TAG, "initializing wifi");
+ WiFiManager wifi;
+ wifi.setDebugStream(Serial1);
+ //wifi.setDebugOutput(false);
+ String ssid = "SynchroClock" + String(ESP.getChipId());
+ wifi.autoConnect(ssid.c_str(), NULL);
+#endif
+
+#if defined(USE_OLED_DISPLAY)
+ logger.info(SETUP_TAG, "initializing display");
+ if (!display.init())
+ {
+ logger.info(SETUP_TAG, "display.init() failed!");
+ }
+ display.flipScreenVertically();
+ display.setFont(ArialMT_Plain_10);
+#endif
+
+ logger.info(SETUP_TAG, "initializing serial for GPS");
+ Serial.begin(9600);
+ Serial.swap();
+
+ logger.info(SETUP_TAG, "initializing GPS");
+ gps.begin();
+
+ logger.info(SETUP_TAG, "initializing NTP");
+ ntp.begin();
+}
+
+void loop()
+{
+ static uint32_t min_loop;
+ static uint32_t max_loop;
+ static uint32_t last_loop;
+ uint32_t start_loop = millis();
+
+ gps.process();
+
+ static time_t last_seconds;
+ struct timeval tv;
+ gps.getTime(&tv);
+ if (tv.tv_sec != last_seconds)
+ {
+ struct tm tm;
+ gmtime_r(&tv.tv_sec, &tm);
+ char ts[64];
+ snprintf(ts, 63, "%04d/%02d/%02d %02d:%02d:%02d.%06ld",
+ tm.tm_year+1900,
+ tm.tm_mon+1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ tv.tv_usec);
+ if (tv.tv_sec != last_seconds && ((tv.tv_sec % 60) == 0 || gps.getValidDelay()))
+ {
+ logger.info("loop", "jitter:%lu valid_count:%lu valid:%s gpsvalid:%s numsat:%d heap:%ld valid_delay:%d",
+ gps.getJitter(),
+ gps.getValidCount(),
+ gps.isValid() ? "true" : "false",
+ gps.isGPSValid() ? "true" : "false",
+ gps.getSatelliteCount(),
+ ESP.getFreeHeap(),
+ gps.getValidDelay());
+ }
+
+ if (tv.tv_sec < last_seconds)
+ {
+ logger.warning(LOOP_TAG, "%s: OOPS: time went backwards: last:%lu now:%lu\n", ts, last_seconds, tv.tv_sec);
+ }
+
+#if defined(USE_OLED_DISPLAY)
+ //
+ // Update the display
+ //
+ display.clear();
+ display.setTextAlignment(TEXT_ALIGN_LEFT);
+ display.setFont(ArialMT_Plain_10);
+ display.drawString(0, 0, ts);
+ display.drawString(0, 10, "Address: "+WiFi.localIP().toString());
+ display.drawString(0, 20, "Sat Count: " + String(gps.getSatelliteCount()));
+ display.drawString(0, 30, "Requests: " + String(ntp.getReqCount()));
+ display.drawString(0, 40, "Responses: " + String(ntp.getRspCount()));
+ snprintf(ts, 63, "loop: %d / %d / %d", last_loop, min_loop, max_loop);
+ display.drawString(0, 50, String(ts));
+ // write the buffer to the display
+ display.display();
+#endif
+ }
+
+ last_seconds = tv.tv_sec;
+ last_loop = millis() - start_loop;
+ if (min_loop == 0 || last_loop < min_loop)
+ {
+ min_loop = last_loop;
+ }
+ if (max_loop == 0 || last_loop > max_loop)
+ {
+ max_loop = last_loop;
+ }
+}
diff --git a/ESPNTPServer.h b/ESPNTPServer.h
new file mode 100644
index 0000000..d566ca1
--- /dev/null
+++ b/ESPNTPServer.h
@@ -0,0 +1,43 @@
+/*
+ * 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_OLED_DISPLAY
+//#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
+
+#include "SSD1306Wire.h"
+
+// pin definitions
+#define SYNC_PIN 14 // (GPIO14) pin tied to 1hz square wave from GPS
+#define SDA_PIN 4
+#define SCL_PIN 5
+
+
+#endif /* _ESPNTPServer_H_ */
diff --git a/GPS.cpp b/GPS.cpp
new file mode 100644
index 0000000..48c7840
--- /dev/null
+++ b/GPS.cpp
@@ -0,0 +1,289 @@
+/*
+ * GPS.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: Feb 26, 2018
+ * Author: chris.l
+ */
+
+#include
+#include "GPS.h"
+#include
+#include
+#include
+
+#include "Log.h"
+static const char* TAG = "GPS";
+
+static std::function _pps;
+
+static ICACHE_RAM_ATTR void _pps_isr()
+{
+ if (_pps)
+ {
+ _pps();
+ }
+}
+
+static ICACHE_RAM_ATTR void _timer_handler(std::function *func)
+{
+ (*func)();
+}
+
+GPS::GPS(Stream& gps_stream, int pps_pin) :
+ _stream(gps_stream),
+ _nmea(_buffer, NMEA_BUFFER_SIZE),
+ _pps_timer(),
+ _seconds(0),
+ _valid_delay(0),
+ _valid_count(0),
+ _min_micros(0),
+ _max_micros(0),
+ _last_micros(0),
+ _timeouts(0),
+ _pps_pin(pps_pin),
+ _gps_valid(false),
+ _valid(false),
+ _nmea_late(true)
+{
+ _reason[0] = '\0';
+}
+
+GPS::~GPS()
+{
+ end();
+}
+
+void GPS::begin()
+{
+ PPS_TIMIMG_PIN_INIT();
+ _pps = std::bind( &GPS::pps, this);
+ _invalidate = std::bind( &GPS::timeout, this);
+ _nmea_timeout = std::bind( &GPS::nmeaTimeout, this);
+ _pps_timer.attach_ms(VALID_TIMER_MS, _timer_handler, &_invalidate);
+ pinMode(_pps_pin, INPUT);
+ attachInterrupt(_pps_pin, _pps_isr, RISING);
+}
+
+void GPS::end()
+{
+ _pps_timer.detach();
+ detachInterrupt(_pps_pin);
+ _pps = nullptr;
+}
+
+void GPS::getTime(struct timeval* tv)
+{
+ uint32_t cur_micros = micros();
+ tv->tv_sec = _seconds;
+ tv->tv_usec = (uint32_t)(cur_micros - _last_micros);
+
+ //
+ // if micros_delta is at or bigger than one second then
+ // use the max just under 1 second.
+ //
+ if (tv->tv_usec >= 1000000 || tv->tv_usec < 0)
+ {
+ tv->tv_usec = 999999;
+ }
+}
+
+double GPS::getDispersion()
+{
+ return us2s(MAX(abs(MICROS_PER_SEC-_max_micros), abs(MICROS_PER_SEC-_min_micros)));
+}
+
+void GPS::process()
+{
+ if (_reason[0] != '\0')
+ {
+ logger.warning(TAG, "REASON: %s", _reason);
+ _reason[0] = '\0';
+ }
+
+ while (_stream.available() > 0)
+ {
+ if (_nmea.process(_stream.read()))
+ {
+ struct timeval tv;
+ getTime(&tv);
+ logger.debug(TAG, "'%s'", _nmea.getSentence());
+
+ if (_nmea.isValid() && _nmea.getNumSatellites() >= 4)
+ {
+ //
+ // if it was a GGA and its valid then check and maybe update the time
+ //
+ const char * id = _nmea.getMessageID();
+ if (_nmea.getYear() > 2000 && strcmp("RMC", id) == 0)
+ {
+ 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);
+
+ //
+ // we only update seconds if the message arrived in the last half of a second,
+ // if its in the first half then its most likely delayed from the previous second.
+ time_t old_seconds = _seconds;
+ if (old_seconds != new_seconds)
+ {
+ if (!_nmea_late)
+ {
+ _seconds = new_seconds;
+ logger.info(TAG, "adjusting seconds from %lu to %lu from:'%s'", old_seconds, new_seconds, _nmea.getSentence());
+ }
+ else
+ {
+ logger.warning(TAG, "ignoring late NMEA time: '%s'",_nmea.getSentence());
+ }
+ }
+
+ _nmea_late = false;
+ _nmea_timer.attach_ms(NMEA_TIMER_MS, _timer_handler, &_nmea_timeout);
+
+ //
+ // if gps was not valid, it is now
+ //
+ if (!_gps_valid)
+ {
+ _valid_delay = VALID_DELAY;
+ _gps_valid = true;
+ logger.warning(TAG, "GPS valid!");
+ }
+ }
+ }
+ else /* nmea not valid or sat count < 4 */
+ {
+ if (_gps_valid)
+ {
+ invalidate("NMEA:%s SATS:%d from: '%s'",
+ _nmea.isValid() ? "valid" : "invalid",
+ _nmea.getNumSatellites(), _nmea.getSentence());
+ }
+ }
+ }
+ }
+}
+
+void GPS::nmeaTimeout()
+{
+ _nmea_late = true;
+}
+
+/*
+ * Mark as not valid
+ */
+void ICACHE_RAM_ATTR GPS::timeout()
+{
+ if (_valid)
+ {
+ invalidate("timeout!");
+ }
+ _pps_timer.attach_ms(VALID_TIMER_MS, _timer_handler, &_invalidate);
+}
+
+/*
+ * Mark as not valid
+ */
+void ICACHE_RAM_ATTR GPS::invalidate(const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(_reason, REASON_SIZE-1, fmt, ap);
+ _reason[REASON_SIZE-1] = '\0';
+ va_end(ap);
+
+ _valid = false;
+ _gps_valid = false;
+ _last_micros = 0;
+}
+
+/*
+ * Interrupt handler for a PPS (Pulse Per Second) signal from GPS module.
+ */
+void ICACHE_RAM_ATTR GPS::pps()
+{
+ PPS_TIMING_PIN_ON();
+
+ uint32_t cur_micros = micros();
+ (void)cur_micros;
+
+ //
+ // don't trust PPS if GPS is not valid.
+ //
+ if (!_gps_valid)
+ {
+ PPS_TIMING_PIN_OFF();
+ return;
+ }
+
+ //
+ // if we are still counting down then keep waiting
+ //
+ if (_valid_delay)
+ {
+ --_valid_delay;
+ if (_valid_delay == 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.
+ //
+ _pps_timer.attach_ms(VALID_TIMER_MS, &_timer_handler, &_invalidate);
+
+ //
+ // increment seconds
+ //
+ _seconds += 1;
+
+ //
+ // the first time around we just initialize the last value
+ //
+ if (_last_micros == 0)
+ {
+ _last_micros = cur_micros;
+ PPS_TIMING_PIN_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;
+ }
+
+ PPS_TIMING_PIN_OFF();
+}
+
diff --git a/GPS.h b/GPS.h
new file mode 100644
index 0000000..a9c3778
--- /dev/null
+++ b/GPS.h
@@ -0,0 +1,104 @@
+/*
+ * GPS.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: Feb 26, 2018
+ * Author: chris.l
+ */
+
+#ifndef GPS_H_
+#define GPS_H_
+#include "Arduino.h"
+#include "MicroNMEA.h"
+#include "Ticker.h"
+
+#define REASON_SIZE 128
+#define NMEA_BUFFER_SIZE 128
+#define PPS_TIMING_PIN 12 // (GPIO12) if defined PPS interrupt will make high during processing
+#define VALID_DELAY 120 // delay (seconds) from gps valid to valid
+#define VALID_TIMER_MS 1001 // if this timer expires we invalidate!
+#define NMEA_TIMER_MS 1100 // if this timer expires we mark NMEA time late
+
+#define MICROS_PER_SEC 1000000
+
+#define us2s(x) (((double)x)/(double)MICROS_PER_SEC) // microseconds to seconds
+
+// 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))
+
+#if defined(PPS_TIMING_PIN)
+#define PPS_TIMIMG_PIN_INIT() {digitalWrite(PPS_TIMING_PIN, LOW); pinMode(PPS_TIMING_PIN, OUTPUT);}
+#define PPS_TIMING_PIN_ON() digitalWrite(PPS_TIMING_PIN, HIGH)
+#define PPS_TIMING_PIN_OFF() digitalWrite(PPS_TIMING_PIN, LOW)
+#else
+#define PPS_TIMIMG_PIN_INIT()
+#define PPS_TIMING_PIN_ON()
+#define PPS_TIMING_PIN_OFF()
+#endif
+
+class GPS
+{
+public:
+ GPS(Stream& gps_serial, int pps_pin);
+ virtual ~GPS();
+
+ void begin();
+ void process();
+ void end();
+
+ bool isValid() { return _valid; }
+ bool isGPSValid() { return _gps_valid; }
+ uint32_t getJitter() { return _max_micros - _min_micros; }
+ uint32_t getValidCount() { return _valid_count; }
+ uint32_t getValidDelay() { return _valid_delay; }
+ uint8_t getSatelliteCount() { return _nmea.getNumSatellites(); }
+ time_t getSeconds() { return _seconds; }
+ void getTime(struct timeval* tv);
+ double getDispersion();
+
+ // we don't allow copying this guy!
+ GPS(const GPS&) = delete;
+ GPS& operator=(const GPS&) = delete;
+
+private:
+ Stream& _stream;
+ char _buffer[NMEA_BUFFER_SIZE];
+ MicroNMEA _nmea;
+ Ticker _pps_timer;
+ Ticker _nmea_timer;
+ std::function _invalidate;
+ std::function _nmea_timeout;
+ volatile time_t _seconds;
+ volatile uint32_t _valid_delay; // delay (seconds) from gps_valid until we thing we are valid
+ volatile uint32_t _valid_count; // number of times we have gone valid
+ volatile uint32_t _min_micros;
+ volatile uint32_t _max_micros;
+ volatile uint32_t _last_micros;
+ volatile uint32_t _timeouts;
+
+ uint8_t _pps_pin;
+ bool _gps_valid;
+ volatile bool _valid;
+ volatile bool _nmea_late;
+ char _reason[REASON_SIZE];
+ void pps(); // interrupt handler
+ void timeout();
+ void invalidate(const char* fmt, ...);
+ void nmeaTimeout();
+};
+
+#endif /* GPS_H_ */
diff --git a/Log.cpp b/Log.cpp
new file mode 100644
index 0000000..d756120
--- /dev/null
+++ b/Log.cpp
@@ -0,0 +1,160 @@
+/*
+ * Log.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: Mar 2, 2018
+ * Author: chris.l
+ */
+#include "Arduino.h"
+#include "Log.h"
+
+static const char* level_names[LOG_LEVEL_MAX] = {
+ "NONE",
+ "ERROR",
+ "WARN",
+ "INFO",
+ "DEBUG",
+ "TRACE"
+};
+
+Log& Log::getLog()
+{
+ static Log log;
+ return log;
+}
+
+Log::Log() :
+ _level(LOG_LEVEL_INFO),
+ _size(LOG_MAX_SIZE),
+ _buffer(nullptr),
+ _print(nullptr)
+{
+}
+
+Log::~Log()
+{
+ if (_buffer != nullptr)
+ {
+ delete[] _buffer;
+ }
+}
+
+void Log::setSize(size_t size)
+{
+ _size = size;
+ if (_buffer)
+ {
+ delete[] _buffer;
+ _buffer = nullptr;
+ }
+}
+
+void Log::setLevel(LogLevel level)
+{
+ _level = level;
+}
+
+void Log::setLevel(const char* name, LogLevel level)
+{
+ _levels[name] = level;
+}
+
+void Log::setPrint(Print *print)
+{
+ _print = print;
+}
+void Log::setPreFunc(std::function func)
+{
+ pre_func = func;
+}
+
+void Log::error(const char* name, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ print(name, LOG_LEVEL_ERROR, fmt, ap);
+ va_end(ap);
+}
+
+void Log::warning(const char* name, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ print(name, LOG_LEVEL_WARNING, fmt, ap);
+ va_end(ap);
+}
+
+void Log::info(const char* name, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ print(name, LOG_LEVEL_INFO, fmt, ap);
+ va_end(ap);
+}
+
+void Log::debug(const char* name, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ print(name, LOG_LEVEL_DEBUG, fmt, ap);
+ va_end(ap);
+}
+
+void Log::trace(const char* name, const char* fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ print(name, LOG_LEVEL_TRACE, fmt, ap);
+ va_end(ap);
+}
+
+void Log::print(const char* name, LogLevel level, const char* fmt, va_list ap)
+{
+ LogLevel limit = _level;
+ if (_levels.find(name) != _levels.end())
+ {
+ limit = _levels[name];
+ }
+
+ if (level > limit)
+ {
+ return;
+ }
+
+ //
+ // allocate the buffer on first use
+ //
+ if (_buffer == nullptr)
+ {
+ _buffer = new char[_size];
+ }
+
+ if (_print == nullptr)
+ {
+ _print = &Serial;
+ }
+
+ vsnprintf(_buffer, _size-1, fmt, ap);
+
+ if (pre_func)
+ {
+ pre_func(_print);
+ }
+
+ _print->printf("%s %5s %s\n", name, level_names[level], _buffer);
+}
+
+Log& logger = Log::getLog();
diff --git a/Log.h b/Log.h
new file mode 100644
index 0000000..3fc5ce4
--- /dev/null
+++ b/Log.h
@@ -0,0 +1,82 @@
+/*
+ * Log.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: Mar 2, 2018
+ * Author: chris.l
+ */
+
+#ifndef LOG_H_
+#define LOG_H_
+
+#include "Print.h"
+#include
+#include