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
|
.DS_Store
|
||||||
|
.ino.cpp
|
||||||
|
.project
|
||||||
|
.cproject
|
||||||
|
.settings
|
||||||
|
Release
|
||||||
|
Debug
|
||||||
|
libraries
|
||||||
|
core
|
||||||
*.bak
|
*.bak
|
||||||
*-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
|
# ESPNTPServer
|
||||||
GPS fueled ESP32 based NTP Server
|
GPS fueled ESP8266 based NTP Server
|
||||||
|
|
||||||
GPS: Adafruit Untimate GPS
|
GPS: any with standard NMEA sentences & a pulse per second signal..
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
Work in progress....
|
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