switched back to esp8266 and use Serial1 for debug output and Serial for GPS
This commit is contained in:
parent
174888b6a1
commit
ac5eaf36fa
57
.cproject
57
.cproject
|
|
@ -1,57 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?fileVersion 4.0.0?><cproject storage_type_id="org.eclipse.cdt.core.XmlProjectDescriptionStorage">
|
||||
<storageModule moduleId="org.eclipse.cdt.core.settings">
|
||||
<cconfiguration id="cdt.managedbuild.toolchain.gnu.cross.base.285975342">
|
||||
<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.cross.base.285975342" moduleId="org.eclipse.cdt.core.settings" name="Default">
|
||||
<externalSettings/>
|
||||
<extensions>
|
||||
<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GmakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.CWDLocator" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/>
|
||||
</extensions>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<configuration buildProperties="" id="cdt.managedbuild.toolchain.gnu.cross.base.285975342" name="Default" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.285975342.940832650" name="/" resourcePath="">
|
||||
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.1246611223" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
|
||||
<option id="cdt.managedbuild.option.gnu.cross.prefix.351169990" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
|
||||
<option id="cdt.managedbuild.option.gnu.cross.path.109715206" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
|
||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1180611588" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
|
||||
<builder id="cdt.managedbuild.builder.gnu.cross.1292138490" managedBuildOn="false" name="Gnu Make Builder.Default" superClass="cdt.managedbuild.builder.gnu.cross"/>
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.914240970" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
|
||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1395483982" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||
</tool>
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.compiler.1686276063" name="Cross G++ Compiler" superClass="cdt.managedbuild.tool.gnu.cross.cpp.compiler">
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.compiler.input.427328797" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.input"/>
|
||||
</tool>
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.linker.1240604479" name="Cross GCC Linker" superClass="cdt.managedbuild.tool.gnu.cross.c.linker"/>
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.cpp.linker.827040114" name="Cross G++ Linker" superClass="cdt.managedbuild.tool.gnu.cross.cpp.linker">
|
||||
<inputType id="cdt.managedbuild.tool.gnu.cpp.linker.input.23406645" superClass="cdt.managedbuild.tool.gnu.cpp.linker.input">
|
||||
<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/>
|
||||
<additionalInput kind="additionalinput" paths="$(LIBS)"/>
|
||||
</inputType>
|
||||
</tool>
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.archiver.568903855" name="Cross GCC Archiver" superClass="cdt.managedbuild.tool.gnu.cross.archiver"/>
|
||||
<tool id="cdt.managedbuild.tool.gnu.cross.assembler.1777681043" name="Cross GCC Assembler" superClass="cdt.managedbuild.tool.gnu.cross.assembler">
|
||||
<inputType id="cdt.managedbuild.tool.gnu.assembler.input.835434270" superClass="cdt.managedbuild.tool.gnu.assembler.input"/>
|
||||
</tool>
|
||||
</toolChain>
|
||||
</folderInfo>
|
||||
</configuration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>
|
||||
</cconfiguration>
|
||||
</storageModule>
|
||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||
<project id="esp_gps_ntp.null.219935928" name="esp_gps_ntp"/>
|
||||
</storageModule>
|
||||
<storageModule moduleId="scannerConfiguration">
|
||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||
</storageModule>
|
||||
<storageModule moduleId="org.eclipse.cdt.core.LanguageSettingsProviders"/>
|
||||
<storageModule moduleId="refreshScope"/>
|
||||
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"/>
|
||||
</cproject>
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
|
|
@ -1,6 +1,12 @@
|
|||
.metadata
|
||||
.DS_Store
|
||||
.ino.cpp
|
||||
.project
|
||||
.cproject
|
||||
.settings
|
||||
Release
|
||||
Debug
|
||||
libraries
|
||||
core
|
||||
*.bak
|
||||
*-bak
|
||||
*.old
|
||||
build
|
||||
sdkconfig
|
||||
|
|
|
|||
9
.gitmodules
vendored
9
.gitmodules
vendored
|
|
@ -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
|
||||
27
.project
27
.project
|
|
@ -1,27 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>esp_gps_ntp</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name>
|
||||
<triggers>clean,full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name>
|
||||
<triggers>full,incremental,</triggers>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.cdt.core.cnature</nature>
|
||||
<nature>org.eclipse.cdt.core.ccnature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature>
|
||||
<nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project>
|
||||
<configuration id="cdt.managedbuild.toolchain.gnu.cross.base.285975342" name="Default">
|
||||
<extension point="org.eclipse.cdt.core.LanguageSettingsProvider">
|
||||
<provider copy-of="extension" id="org.eclipse.cdt.ui.UserLanguageSettingsProvider"/>
|
||||
<provider-reference id="org.eclipse.cdt.core.ReferencedProjectsLanguageSettingsProvider" ref="shared-provider"/>
|
||||
<provider class="org.eclipse.cdt.managedbuilder.language.settings.providers.GCCBuildCommandParser" id="org.eclipse.cdt.managedbuilder.core.GCCBuildCommandParser" keep-relative-paths="false" name="CDT GCC Build Output Parser" parameter="xtensa-esp32-elf-(g?cc)|([gc]\+\+)|(clang)" prefer-non-shared="true"/>
|
||||
<provider class="org.eclipse.cdt.internal.build.crossgcc.CrossGCCBuiltinSpecsDetector" console="false" env-hash="432386854730345216" id="org.eclipse.cdt.build.crossgcc.CrossGCCBuiltinSpecsDetector" keep-relative-paths="false" name="CDT Cross GCC Built-in Compiler Settings" parameter="xtensa-esp32-elf-gcc ${FLAGS} -E -P -v -dD "${INPUTS}"" prefer-non-shared="true">
|
||||
<language-scope id="org.eclipse.cdt.core.gcc"/>
|
||||
<language-scope id="org.eclipse.cdt.core.g++"/>
|
||||
</provider>
|
||||
<provider-reference id="org.eclipse.cdt.managedbuilder.core.MBSLanguageSettingsProvider" ref="shared-provider"/>
|
||||
</extension>
|
||||
</configuration>
|
||||
</project>
|
||||
|
|
@ -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
|
||||
170
ESPNTPServer.cpp
Normal file
170
ESPNTPServer.cpp
Normal file
|
|
@ -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 <lwip/def.h> // 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;
|
||||
}
|
||||
}
|
||||
43
ESPNTPServer.h
Normal file
43
ESPNTPServer.h
Normal file
|
|
@ -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 <time.h>
|
||||
|
||||
#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_ */
|
||||
289
GPS.cpp
Normal file
289
GPS.cpp
Normal file
|
|
@ -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 <functional>
|
||||
#include "GPS.h"
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "Log.h"
|
||||
static const char* TAG = "GPS";
|
||||
|
||||
static std::function<void()> _pps;
|
||||
|
||||
static ICACHE_RAM_ATTR void _pps_isr()
|
||||
{
|
||||
if (_pps)
|
||||
{
|
||||
_pps();
|
||||
}
|
||||
}
|
||||
|
||||
static ICACHE_RAM_ATTR void _timer_handler(std::function<void()> *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();
|
||||
}
|
||||
|
||||
104
GPS.h
Normal file
104
GPS.h
Normal file
|
|
@ -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<void()> _invalidate;
|
||||
std::function<void()> _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_ */
|
||||
160
Log.cpp
Normal file
160
Log.cpp
Normal file
|
|
@ -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<void(Print* print)> 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();
|
||||
82
Log.h
Normal file
82
Log.h
Normal file
|
|
@ -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 <string>
|
||||
#include <map>
|
||||
#include <stdarg.h>
|
||||
|
||||
typedef enum log_level
|
||||
{
|
||||
LOG_LEVEL_NONE = 0,
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARNING,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG,
|
||||
LOG_LEVEL_TRACE,
|
||||
LOG_LEVEL_MAX,
|
||||
} LogLevel;
|
||||
|
||||
#define LOG_MAX_SIZE 80
|
||||
#define LOG_LEVEL_DEFAULT LOG_LEVEL_INFO
|
||||
|
||||
class Log
|
||||
{
|
||||
public:
|
||||
static Log& getLog();
|
||||
|
||||
void setSize(size_t size);
|
||||
void setLevel(LogLevel level);
|
||||
void setLevel(const char* name, LogLevel level);
|
||||
void setPrint(Print *print);
|
||||
void setPreFunc(std::function<void(Print* print)> func);
|
||||
|
||||
void error(const char* name, const char* fmt, ...);
|
||||
void warning(const char* name, const char* fmt, ...);
|
||||
void info(const char* name, const char* fmt, ...);
|
||||
void debug(const char* name, const char* fmt, ...);
|
||||
void trace(const char* name, const char* fmt, ...);
|
||||
|
||||
// we don't allow copying this guy!
|
||||
Log(const Log&) = delete;
|
||||
Log& operator=(const Log&) = delete;
|
||||
|
||||
private:
|
||||
LogLevel _level; // default log level
|
||||
size_t _size;
|
||||
char* _buffer;
|
||||
std::map<std::string, LogLevel> _levels;
|
||||
Print* _print;
|
||||
|
||||
std::function<void(Print* print)> pre_func;
|
||||
|
||||
|
||||
Log();
|
||||
virtual ~Log();
|
||||
void print(const char* name, LogLevel level, const char*fmt, va_list ap);
|
||||
};
|
||||
|
||||
extern Log& logger;
|
||||
|
||||
#endif /* LOG_H_ */
|
||||
9
Makefile
9
Makefile
|
|
@ -1,9 +0,0 @@
|
|||
#
|
||||
# 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
|
||||
|
||||
221
NTP.cpp
Normal file
221
NTP.cpp
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* NTP.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 27, 2018
|
||||
* Author: chris.l
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
#include <lwip/def.h> // htonl() & ntohl()
|
||||
|
||||
#include "NTP.h"
|
||||
|
||||
#include "Log.h"
|
||||
static const char* TAG = "NTP";
|
||||
|
||||
//#define NTP_PACKET_DEBUG
|
||||
|
||||
#define NTP_PORT 123
|
||||
#define PRECISION_COUNT 10000
|
||||
|
||||
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)
|
||||
|
||||
#ifdef NTP_PACKET_DEBUG
|
||||
#include <time.h>
|
||||
char* timestr(long int t)
|
||||
{
|
||||
t = toEPOCH(t);
|
||||
return ctime(&t);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
NTP::NTP(GPS& gps) :
|
||||
_gps(gps),
|
||||
_udp(),
|
||||
_req_count(0),
|
||||
_rsp_count(0),
|
||||
_precision(0)
|
||||
{
|
||||
}
|
||||
|
||||
NTP::~NTP()
|
||||
{
|
||||
}
|
||||
|
||||
void NTP::begin()
|
||||
{
|
||||
_precision = computePrecision();
|
||||
while (!_udp.listen(NTP_PORT))
|
||||
{
|
||||
logger.error(TAG, "failed to listen on port %d! Will retry in a bit...", NTP_PORT);
|
||||
delay(1000);
|
||||
logger.warning(TAG, "setup: retrying!");
|
||||
}
|
||||
using namespace std::placeholders; // for _1, _2, _3...
|
||||
|
||||
_udp.onPacket(std::bind( &NTP::ntp, this, _1));
|
||||
}
|
||||
|
||||
int8_t NTP::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);
|
||||
logger.info(TAG, "computePrecision: total:%f time:%f prec:%f (%d)", total, time, prec, (int8_t)prec);
|
||||
return (int8_t)prec;
|
||||
}
|
||||
|
||||
void NTP::getNTPTime(NTPTime *time)
|
||||
{
|
||||
struct timeval tv;
|
||||
_gps.getTime(&tv);
|
||||
time->seconds = toNTP(tv.tv_sec);
|
||||
|
||||
double percent = us2s(tv.tv_usec);
|
||||
time->fraction = (uint32_t)(percent * (double)4294967296L);
|
||||
}
|
||||
|
||||
void NTP::ntp(AsyncUDPPacket& aup)
|
||||
{
|
||||
++_req_count;
|
||||
NTPPacket ntp;
|
||||
NTPTime recv_time;
|
||||
getNTPTime(&recv_time);
|
||||
if (aup.length() != sizeof(NTPPacket))
|
||||
{
|
||||
logger.warning(TAG, "recievePacket: ignoring packet with bad length: %d < %d", aup.length(), sizeof(NTPPacket));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_gps.isValid())
|
||||
{
|
||||
logger.warning(TAG, "recievePacket: GPS data not valid!");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&ntp, aup.data(), sizeof(ntp));
|
||||
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 = 1; //(uint32)(0.000001 * 65536.0);
|
||||
ntp.dispersion = 1; //(uint32_t)(_gps.getDispersion() * 65536.0); // TODO: pre-calculate this?
|
||||
strncpy((char*)ntp.ref_id, REF_ID, sizeof(ntp.ref_id));
|
||||
ntp.orig_time = ntp.xmit_time;
|
||||
ntp.recv_time = recv_time;
|
||||
getNTPTime(&(ntp.ref_time));
|
||||
dumpNTPPacket(&ntp);
|
||||
ntp.delay = htonl(ntp.delay);
|
||||
ntp.dispersion = htonl(ntp.dispersion);
|
||||
ntp.orig_time.seconds = htonl(ntp.orig_time.seconds);
|
||||
ntp.orig_time.fraction = htonl(ntp.orig_time.fraction);
|
||||
ntp.ref_time.seconds = htonl(ntp.ref_time.seconds);
|
||||
ntp.ref_time.fraction = htonl(ntp.ref_time.fraction);
|
||||
ntp.recv_time.seconds = htonl(ntp.recv_time.seconds);
|
||||
ntp.recv_time.fraction = htonl(ntp.recv_time.fraction);
|
||||
getNTPTime(&(ntp.xmit_time));
|
||||
ntp.xmit_time.seconds = htonl(ntp.xmit_time.seconds);
|
||||
ntp.xmit_time.fraction = htonl(ntp.xmit_time.fraction);
|
||||
aup.write((uint8_t*)&ntp, sizeof(ntp));
|
||||
++_rsp_count;
|
||||
}
|
||||
58
NTP.h
Normal file
58
NTP.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* NTP.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 27, 2018
|
||||
* Author: chris.l
|
||||
*/
|
||||
|
||||
#ifndef NTP_H_
|
||||
#define NTP_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "ESPAsyncUDP.h"
|
||||
#include "GPS.h"
|
||||
|
||||
typedef struct ntp_time
|
||||
{
|
||||
uint32_t seconds;
|
||||
uint32_t fraction;
|
||||
} NTPTime;
|
||||
|
||||
class NTP
|
||||
{
|
||||
public:
|
||||
NTP(GPS& gps);
|
||||
virtual ~NTP();
|
||||
|
||||
void begin();
|
||||
|
||||
uint32_t getReqCount() { return _req_count; }
|
||||
uint32_t getRspCount() { return _rsp_count; }
|
||||
|
||||
private:
|
||||
GPS& _gps;
|
||||
AsyncUDP _udp;
|
||||
uint32_t _req_count;
|
||||
uint32_t _rsp_count;
|
||||
uint8_t _precision;
|
||||
|
||||
void getNTPTime(NTPTime *time);
|
||||
int8_t computePrecision();
|
||||
void ntp(AsyncUDPPacket& aup);
|
||||
};
|
||||
|
||||
#endif /* NTP_H_ */
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
# ESPNTPServer
|
||||
GPS fueled ESP32 based NTP Server
|
||||
GPS fueled ESP8266 based NTP Server
|
||||
|
||||
GPS: Adafruit Untimate GPS
|
||||
|
||||
This project has git submodules so please use --recursive when you clone! If you forget then run: `git submodule update --init`! After a `git pull` you should run `git submodule update --init --recursive` to make sure submodules are up to date.
|
||||
GPS: any with standard NMEA sentences & a pulse per second signal..
|
||||
|
||||
Work in progress....
|
||||
|
|
|
|||
112
WireUtils.cpp
Normal file
112
WireUtils.cpp
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* WireUtils.cpp
|
||||
*
|
||||
* Copyright 2017 Christopher B. Liebman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
* Created on: May 26, 2017
|
||||
* Author: liebman
|
||||
*/
|
||||
|
||||
#include "WireUtils.h"
|
||||
|
||||
#define DEBUG
|
||||
#include "Logger.h"
|
||||
|
||||
WireUtilsC::WireUtilsC()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine turns off the I2C bus and clears it
|
||||
* on return SCA and SCL pins are tri-state inputs.
|
||||
* You need to call Wire.begin() after this to re-enable I2C
|
||||
* This routine does NOT use the Wire library at all.
|
||||
*
|
||||
* returns 0 if bus cleared
|
||||
* 1 if SCL held low.
|
||||
* 2 if SDA held low by slave clock stretch for > 2sec
|
||||
* 3 if SDA held low after 20 clocks.
|
||||
*/
|
||||
int WireUtilsC::clearBus()
|
||||
{
|
||||
dbprintln("WireUtils::ClearBus: attempting to clean i2c bus");
|
||||
#if defined(TWCR) && defined(TWEN)
|
||||
TWCR &= ~(_BV(TWEN)); //Disable the Atmel 2-Wire interface so we can control the SDA and SCL pins directly
|
||||
#endif
|
||||
pinMode(SDA, INPUT_PULLUP); // Make SDA (data) and SCL (clock) pins Inputs with pullup.
|
||||
pinMode(SCL, INPUT_PULLUP);
|
||||
|
||||
boolean SCL_LOW = (digitalRead(SCL) == LOW); // Check is SCL is Low.
|
||||
if (SCL_LOW)
|
||||
{ //If it is held low Arduno cannot become the I2C master.
|
||||
dbprintln("WireUtils::ClearBus: Failed! SCL held low!");
|
||||
return 1; //I2C bus error. Could not clear SCL clock line held low
|
||||
}
|
||||
|
||||
boolean SDA_LOW = (digitalRead(SDA) == LOW); // vi. Check SDA input.
|
||||
int clockCount = 20; // > 2x9 clock
|
||||
|
||||
while (SDA_LOW && (clockCount > 0))
|
||||
{ // vii. If SDA is Low,
|
||||
clockCount--;
|
||||
// Note: I2C bus is open collector so do NOT drive SCL or SDA high.
|
||||
pinMode(SCL, INPUT); // release SCL pullup so that when made output it will be LOW
|
||||
pinMode(SCL, OUTPUT); // then clock SCL Low
|
||||
delayMicroseconds(10); // for >5uS
|
||||
pinMode(SCL, INPUT); // release SCL LOW
|
||||
pinMode(SCL, INPUT_PULLUP); // turn on pullup resistors again
|
||||
// do not force high as slave may be holding it low for clock stretching.
|
||||
delayMicroseconds(10); // for >5uS
|
||||
// The >5uS is so that even the slowest I2C devices are handled.
|
||||
SCL_LOW = (digitalRead(SCL) == LOW); // Check if SCL is Low.
|
||||
int counter = 20;
|
||||
while (SCL_LOW && (counter > 0))
|
||||
{ // loop waiting for SCL to become High only wait 2sec.
|
||||
counter--;
|
||||
delay(100);
|
||||
SCL_LOW = (digitalRead(SCL) == LOW);
|
||||
}
|
||||
if (SCL_LOW)
|
||||
{ // still low after 2 sec error
|
||||
dbprintln("WireUtils::ClearBus: Failed! SCL clock line held low by slave clock stretch for >2sec");
|
||||
return 2; // I2C bus error. Could not clear. SCL clock line held low by slave clock stretch for >2sec
|
||||
}
|
||||
SDA_LOW = (digitalRead(SDA) == LOW); // and check SDA input again and loop
|
||||
}
|
||||
if (SDA_LOW)
|
||||
{ // still low
|
||||
dbprintln("WireUtils::ClearBus: Failed! SDA data line still held low");
|
||||
return 3; // I2C bus error. Could not clear. SDA data line held low
|
||||
}
|
||||
|
||||
// else pull SDA line low for Start or Repeated Start
|
||||
pinMode(SDA, INPUT); // remove pullup.
|
||||
pinMode(SDA, OUTPUT); // and then make it LOW i.e. send an I2C Start or Repeated start control.
|
||||
// When there is only one I2C master a Start or Repeat Start has the same function as a Stop and clears the bus.
|
||||
/// A Repeat Start is a Start occurring after a Start with no intervening Stop.
|
||||
delayMicroseconds(10); // wait >5uS
|
||||
pinMode(SDA, INPUT); // remove output low
|
||||
pinMode(SDA, INPUT_PULLUP); // and make SDA high i.e. send I2C STOP control.
|
||||
delayMicroseconds(10); // x. wait >5uS
|
||||
pinMode(SDA, INPUT); // and reset pins as tri-state inputs which is the default state on reset
|
||||
pinMode(SCL, INPUT);
|
||||
dbprintln("WireUtils::ClearBus: Success!");
|
||||
return 0; // all ok
|
||||
}
|
||||
|
||||
// act like wire library (even though I don't like it)
|
||||
WireUtilsC WireUtils = WireUtilsC();
|
||||
|
||||
35
WireUtils.h
Normal file
35
WireUtils.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* WireUtils.h
|
||||
*
|
||||
* Copyright 2017 Christopher B. Liebman
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*
|
||||
* Created on: May 26, 2017
|
||||
* Author: liebman
|
||||
*/
|
||||
|
||||
#ifndef WIREUTILS_H_
|
||||
#define WIREUTILS_H_
|
||||
#include <Arduino.h>
|
||||
|
||||
class WireUtilsC {
|
||||
public:
|
||||
WireUtilsC();
|
||||
int clearBus();
|
||||
};
|
||||
|
||||
extern WireUtilsC WireUtils;
|
||||
|
||||
#endif /* WIREUTILS_H_ */
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
COMPONENT_OBJS := esp32-snippets/hardware/displays/U8G2/u8g2_esp32_hal.o
|
||||
COMPONENT_SRCDIRS := esp32-snippets/hardware/displays/U8G2
|
||||
COMPONENT_ADD_INCLUDEDIRS := esp32-snippets/hardware/displays/U8G2
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit b1b56c7ef0b3cdfc6cbd1b109b2b24c48c8c52e2
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
COMPONENT_OBJS := minmea/minmea.o
|
||||
COMPONENT_SRCDIRS := minmea
|
||||
COMPONENT_ADD_INCLUDEDIRS := minmea
|
||||
CPPFLAGS += -Dtimegm=mktime
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit cd27e72970cd50b46e9c95c1c0076a49c1ceaf33
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit ea7dbc407cd68419322da57cdbdfc00521c86458
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
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
|
||||
|
||||
choice APP_LOG_LEVEL
|
||||
prompt "APP Log Level"
|
||||
default APP_LOG_LEVEL_INFO
|
||||
help
|
||||
Logging level for APP main
|
||||
|
||||
config APP_LOG_LEVEL_NONE
|
||||
bool "No output"
|
||||
config APP_LOG_LEVEL_ERROR
|
||||
bool "Error"
|
||||
config APP_LOG_LEVEL_WARN
|
||||
bool "Warning"
|
||||
config APP_LOG_LEVEL_INFO
|
||||
bool "Info"
|
||||
config APP_LOG_LEVEL_DEBUG
|
||||
bool "Debug"
|
||||
config APP_LOG_LEVEL_VERBOSE
|
||||
bool "Verbose"
|
||||
endchoice
|
||||
|
||||
config APP_LOG_LEVEL
|
||||
int
|
||||
default 0 if APP_LOG_LEVEL_NONE
|
||||
default 1 if APP_LOG_LEVEL_ERROR
|
||||
default 2 if APP_LOG_LEVEL_WARN
|
||||
default 3 if APP_LOG_LEVEL_INFO
|
||||
default 4 if APP_LOG_LEVEL_DEBUG
|
||||
default 5 if APP_LOG_LEVEL_VERBOSE
|
||||
|
||||
|
||||
choice GPS_LOG_LEVEL
|
||||
prompt "GPS Log Level"
|
||||
default GPS_LOG_LEVEL_INFO
|
||||
help
|
||||
Logging level for GPS task
|
||||
|
||||
config GPS_LOG_LEVEL_NONE
|
||||
bool "No output"
|
||||
config GPS_LOG_LEVEL_ERROR
|
||||
bool "Error"
|
||||
config GPS_LOG_LEVEL_WARN
|
||||
bool "Warning"
|
||||
config GPS_LOG_LEVEL_INFO
|
||||
bool "Info"
|
||||
config GPS_LOG_LEVEL_DEBUG
|
||||
bool "Debug"
|
||||
config GPS_LOG_LEVEL_VERBOSE
|
||||
bool "Verbose"
|
||||
endchoice
|
||||
|
||||
config GPS_LOG_LEVEL
|
||||
int
|
||||
default 0 if GPS_LOG_LEVEL_NONE
|
||||
default 1 if GPS_LOG_LEVEL_ERROR
|
||||
default 2 if GPS_LOG_LEVEL_WARN
|
||||
default 3 if GPS_LOG_LEVEL_INFO
|
||||
default 4 if GPS_LOG_LEVEL_DEBUG
|
||||
default 5 if GPS_LOG_LEVEL_VERBOSE
|
||||
|
||||
choice NTP_LOG_LEVEL
|
||||
prompt "NTP Log Level"
|
||||
default NTP_LOG_LEVEL_INFO
|
||||
help
|
||||
Logging level for NTP task
|
||||
|
||||
config NTP_LOG_LEVEL_NONE
|
||||
bool "No output"
|
||||
config NTP_LOG_LEVEL_ERROR
|
||||
bool "Error"
|
||||
config NTP_LOG_LEVEL_WARN
|
||||
bool "Warning"
|
||||
config NTP_LOG_LEVEL_INFO
|
||||
bool "Info"
|
||||
config NTP_LOG_LEVEL_DEBUG
|
||||
bool "Debug"
|
||||
config NTP_LOG_LEVEL_VERBOSE
|
||||
bool "Verbose"
|
||||
endchoice
|
||||
|
||||
config NTP_LOG_LEVEL
|
||||
int
|
||||
default 0 if NTP_LOG_LEVEL_NONE
|
||||
default 1 if NTP_LOG_LEVEL_ERROR
|
||||
default 2 if NTP_LOG_LEVEL_WARN
|
||||
default 3 if NTP_LOG_LEVEL_INFO
|
||||
default 4 if NTP_LOG_LEVEL_DEBUG
|
||||
default 5 if NTP_LOG_LEVEL_VERBOSE
|
||||
|
||||
choice DISPLAY_LOG_LEVEL
|
||||
prompt "DISPLAY Log Level"
|
||||
default DISPLAY_LOG_LEVEL_INFO
|
||||
help
|
||||
Logging level for DISPLAY task
|
||||
|
||||
config DISPLAY_LOG_LEVEL_NONE
|
||||
bool "No output"
|
||||
config DISPLAY_LOG_LEVEL_ERROR
|
||||
bool "Error"
|
||||
config DISPLAY_LOG_LEVEL_WARN
|
||||
bool "Warning"
|
||||
config DISPLAY_LOG_LEVEL_INFO
|
||||
bool "Info"
|
||||
config DISPLAY_LOG_LEVEL_DEBUG
|
||||
bool "Debug"
|
||||
config DISPLAY_LOG_LEVEL_VERBOSE
|
||||
bool "Verbose"
|
||||
endchoice
|
||||
|
||||
config DISPLAY_LOG_LEVEL
|
||||
int
|
||||
default 0 if DISPLAY_LOG_LEVEL_NONE
|
||||
default 1 if DISPLAY_LOG_LEVEL_ERROR
|
||||
default 2 if DISPLAY_LOG_LEVEL_WARN
|
||||
default 3 if DISPLAY_LOG_LEVEL_INFO
|
||||
default 4 if DISPLAY_LOG_LEVEL_DEBUG
|
||||
default 5 if DISPLAY_LOG_LEVEL_VERBOSE
|
||||
|
||||
choice U8G2_HAL_LOG_LEVEL
|
||||
prompt "U8G2_HAL Log Level"
|
||||
default U8G2_HAL_LOG_LEVEL_INFO
|
||||
help
|
||||
Logging level for U8G2_HAL implementation
|
||||
|
||||
config U8G2_HAL_LOG_LEVEL_NONE
|
||||
bool "No output"
|
||||
config U8G2_HAL_LOG_LEVEL_ERROR
|
||||
bool "Error"
|
||||
config U8G2_HAL_LOG_LEVEL_WARN
|
||||
bool "Warning"
|
||||
config U8G2_HAL_LOG_LEVEL_INFO
|
||||
bool "Info"
|
||||
config U8G2_HAL_LOG_LEVEL_DEBUG
|
||||
bool "Debug"
|
||||
config U8G2_HAL_LOG_LEVEL_VERBOSE
|
||||
bool "Verbose"
|
||||
endchoice
|
||||
|
||||
config U8G2_HAL_LOG_LEVEL
|
||||
int
|
||||
default 0 if U8G2_HAL_LOG_LEVEL_NONE
|
||||
default 1 if U8G2_HAL_LOG_LEVEL_ERROR
|
||||
default 2 if U8G2_HAL_LOG_LEVEL_WARN
|
||||
default 3 if U8G2_HAL_LOG_LEVEL_INFO
|
||||
default 4 if U8G2_HAL_LOG_LEVEL_DEBUG
|
||||
default 5 if U8G2_HAL_LOG_LEVEL_VERBOSE
|
||||
endmenu
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
|
||||
COMPONENT_SRCDIRS := $(COMPONENT_ADD_INCLUDEDIRS)
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* 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 <time.h>
|
||||
#include <string.h>
|
||||
#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, CONFIG_DISPLAY_LOG_LEVEL);
|
||||
esp_log_level_set("u8g2_hal", CONFIG_U8G2_HAL_LOG_LEVEL);
|
||||
|
||||
xTaskCreatePinnedToCore(dsp_task, "dsp_task", 1024*4, NULL, DSP_TASK_PRI, NULL, 1);
|
||||
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* esp_gps_ntp.h
|
||||
*
|
||||
* Created on: Jan 21, 2018
|
||||
* Author: chris.l
|
||||
*/
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE // we have separate config choices for all app parts
|
||||
#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_ */
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
/* 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 "esp_gps_ntp.h"
|
||||
#include <stdio.h>
|
||||
#include "esp_system.h"
|
||||
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_wps.h"
|
||||
#include "esp_event_loop.h"
|
||||
#include "nvs_flash.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()
|
||||
{
|
||||
esp_log_level_set(TAG, CONFIG_APP_LOG_LEVEL);
|
||||
ESP_LOGI(TAG, "ESP32 GPS NTP Server");
|
||||
|
||||
/* Print chip information */
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
ESP_LOGI(TAG, "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" : "");
|
||||
|
||||
ESP_LOGI(TAG, "silicon revision %d, ", chip_info.revision);
|
||||
|
||||
ESP_LOGI(TAG, "***** RUNNING on core %d *****", 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();
|
||||
}
|
||||
480
main/gps_task.c
480
main/gps_task.c
|
|
@ -1,480 +0,0 @@
|
|||
/*
|
||||
* 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<<LED_PIN);
|
||||
//disable pull-down mode
|
||||
io_conf.pull_down_en = 0;
|
||||
//disable pull-up mode
|
||||
io_conf.pull_up_en = 0;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
DEBUG_LED_OFF();
|
||||
}
|
||||
#else
|
||||
#define DEBUG_LED_ON()
|
||||
#define DEBUG_LED_OFF()
|
||||
#define DEBUG_LED_INIT()
|
||||
#endif
|
||||
|
||||
|
||||
uint32_t getDispersion()
|
||||
{
|
||||
return dispersion;
|
||||
}
|
||||
|
||||
bool isTimeValid()
|
||||
{
|
||||
return valid;
|
||||
}
|
||||
|
||||
void getTime(time_t *secs, time_t *usecs)
|
||||
{
|
||||
*secs = seconds;
|
||||
*usecs = micros() - last_micros;
|
||||
}
|
||||
|
||||
void getGPSTaskStatus(GPSTaskStatus* status)
|
||||
{
|
||||
status->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<<PPS_PIN);
|
||||
//disable pull-down mode
|
||||
io_conf.pull_down_en = 0;
|
||||
//enable pull-up mode
|
||||
io_conf.pull_up_en = 1;
|
||||
//configure GPIO with the given settings
|
||||
gpio_config(&io_conf);
|
||||
|
||||
//hook isr handler for specific gpio pin
|
||||
gpio_isr_handler_add(PPS_PIN, pps_isr_handler, (void*)0);
|
||||
}
|
||||
|
||||
static void init_uart()
|
||||
{
|
||||
//
|
||||
// initialize the uart attached to the GPS module
|
||||
//
|
||||
const uart_config_t uart_config = {
|
||||
.baud_rate = 9600,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
|
||||
};
|
||||
uart_param_config(GPS_UART, &uart_config);
|
||||
uart_set_pin(GPS_UART, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
// We won't use a buffer for sending data.
|
||||
uart_driver_install(GPS_UART, RX_BUF_SIZE * 2, 0, 0, NULL, 0);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR invalidateTime()
|
||||
{
|
||||
// only count timeouts after we have been valid once.
|
||||
if (initial_valid != 0)
|
||||
{
|
||||
++timeouts;
|
||||
}
|
||||
xTimerStart(timer, 0);
|
||||
invalidate("TIMEOUT");
|
||||
//
|
||||
// trigger a display update each timeout
|
||||
//
|
||||
triggerDisplay();
|
||||
}
|
||||
|
||||
static void init_timer()
|
||||
{
|
||||
timer = xTimerCreate("MyTimer", pdMS_TO_TICKS(VALIDITY_TIMER_MS), pdFALSE, NULL, &invalidateTime);
|
||||
xTimerStart(timer, 0);
|
||||
}
|
||||
|
||||
static void gps_task()
|
||||
{
|
||||
static bool last_valid = false;
|
||||
static bool last_gps_valid = false;
|
||||
static int timewarps = 0;
|
||||
|
||||
DEBUG_LED_INIT();
|
||||
init_uart();
|
||||
|
||||
//install gpio isr service
|
||||
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
|
||||
|
||||
init_pps();
|
||||
init_timer();
|
||||
|
||||
while (1) {
|
||||
|
||||
//
|
||||
// Print valid or invalid if status has changed.
|
||||
//
|
||||
if ((last_valid && !valid) || (last_gps_valid && !gps_valid))
|
||||
{
|
||||
ESP_LOGW(TAG, "INVALID: '%s'", reason);
|
||||
reason[0] = '\0';
|
||||
}
|
||||
else if (!last_valid && valid)
|
||||
{
|
||||
ESP_LOGI(TAG, "VALID!");
|
||||
}
|
||||
last_valid = valid;
|
||||
last_gps_valid = gps_valid;
|
||||
|
||||
char* line = readNMEA();
|
||||
|
||||
switch (minmea_sentence_id(line, true))
|
||||
{
|
||||
case MINMEA_SENTENCE_RMC:
|
||||
{
|
||||
struct minmea_sentence_rmc frame;
|
||||
if (minmea_parse_rmc(&frame, line))
|
||||
{
|
||||
ESP_LOGD(TAG, "$RMC: seconds: %lu timeouts: %u valid: %s", seconds, timeouts, frame.valid ? "true" : "false");
|
||||
gps_status.valid = frame.valid;
|
||||
|
||||
if (frame.valid)
|
||||
{
|
||||
struct timespec ts;
|
||||
minmea_gettime(&ts, &frame.date, &frame.time);
|
||||
if (valid && seconds > 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, CONFIG_GPS_LOG_LEVEL);
|
||||
|
||||
xTaskCreatePinnedToCore(gps_task, "gps_task", 1024*2, NULL, GPS_TASK_PRI, NULL, 1);
|
||||
|
||||
}
|
||||
255
main/ntp_task.c
255
main/ntp_task.c
|
|
@ -1,255 +0,0 @@
|
|||
/*
|
||||
* ntp_task.c
|
||||
*
|
||||
* Created on: Jan 21, 2018
|
||||
* Author: chris.l
|
||||
*/
|
||||
|
||||
#include "esp_gps_ntp.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include <math.h>
|
||||
|
||||
|
||||
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, CONFIG_NTP_LOG_LEVEL);
|
||||
|
||||
xTaskCreatePinnedToCore(ntp_task, "ntp_task", 1024*2, NULL, NTP_TASK_PRI, NULL, 1);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user