Add support for GPS as NTP server

Add support for GPS as NTP server by Christian Baars and Adrian Scillato
This commit is contained in:
Theo Arends 2019-12-23 15:39:07 +01:00
parent d5dcf462d0
commit fb8bb1223a
6 changed files with 216 additions and 194 deletions

View File

@ -54,5 +54,6 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
- Change Smoother ``Fade`` using 100Hz instead of 20Hz animation (#7179)
- Change number of rule ``Var``s and ``Mem``s from 5 to 16 (#4933)
- Add support for max 150 characters in most command parameter strings (#3686, #4754)
- Add support for GPS as NTP server by Christian Baars and Adrian Scillato
- Add Zigbee coalesce sensor attributes into a single message
- Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300

View File

@ -6,6 +6,7 @@
- Change Smoother ``Fade`` using 100Hz instead of 20Hz animation (#7179)
- Change number of rule ``Var``s and ``Mem``s from 5 to 16 (#4933)
- Add support for max 150 characters in most command parameter strings (#3686, #4754)
- Add support for GPS as NTP server by Christian Baars and Adrian Scillato
- Add Zigbee coalesce sensor attributes into a single message
- Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300

View File

@ -493,7 +493,9 @@ void GetFeatures(void)
feature5 |= 0x00100000;
#endif
// feature5 |= 0x00200000;
// feature5 |= 0x00400000;
#ifdef USE_GPS
feature5 |= 0x00400000;
#endif
// feature5 |= 0x00800000;
// feature5 |= 0x01000000;

View File

@ -295,7 +295,8 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_SM2135_CLK "|" D_SENSOR_SM2135_DAT "|"
D_SENSOR_DEEPSLEEP "|" D_SENSOR_EXS_ENABLE "|"
D_SENSOR_SLAVE_TX "|" D_SENSOR_SLAVE_RX "|" D_SENSOR_SLAVE_RESET "|" D_SENSOR_SLAVE_RESET "i|"
D_SENSOR_HPMA_RX "|" D_SENSOR_HPMA_TX "|" D_SENSOR_GPS_RX "|" D_SENSOR_GPS_TX
D_SENSOR_HPMA_RX "|" D_SENSOR_HPMA_TX "|"
D_SENSOR_GPS_RX "|" D_SENSOR_GPS_TX
;
const char kSensorNamesFixed[] PROGMEM =
@ -673,6 +674,7 @@ const uint8_t kGpioNiceList[] PROGMEM = {
#endif // USE_SOLAX_X1
#endif // USE_ENERGY_SENSOR
// Serial
#ifdef USE_SERIAL_BRIDGE
GPIO_SBR_TX, // Serial Bridge Serial interface
GPIO_SBR_RX, // Serial Bridge Serial interface
@ -727,6 +729,11 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_IBEACON_RX,
GPIO_IBEACON_TX,
#endif
#ifdef USE_GPS
GPIO_GPS_RX, // GPS serial interface
GPIO_GPS_TX, // GPS serial interface
#endif
#ifdef USE_MGC3130
GPIO_MGC3130_XFER,
GPIO_MGC3130_RESET,
@ -756,11 +763,7 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_A4988_MS3, // A4988 microstep pin3
#endif
#ifdef USE_DEEPSLEEP
GPIO_DEEPSLEEP
#endif
#ifdef USE_GPS
GPIO_GPS_RX, // GPS serial interface
GPIO_GPS_TX // GPS serial interface
GPIO_DEEPSLEEP,
#endif
};

View File

@ -1,5 +1,5 @@
/*
xsns_60_GPS.ino - GPS UBLOX support for Sonoff-Tasmota
xsns_60_GPS.ino - GPS UBLOX support for Tasmota
Copyright (C) 2019 Theo Arends, Christian Baars and Adrian Scillato
@ -15,7 +15,10 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_GPS
/*********************************************************************************************\
--------------------------------------------------------------------------------------------
Version Date Action Description
--------------------------------------------------------------------------------------------
@ -97,14 +100,13 @@ rule2 on tele-GPS#int>9 do DisplayText [f0c9l4]I%value% endon on tele-GPS#int
rule3 on tele-FLOG#sec do DisplayText [f0c1l4]SAV:%value% endon on tele-FLOG#rec==1 do DisplayText [f0c1l4]REC: endon on tele-FLOG#mode do DisplayText [f0c14l4]M%value% endon
*/
\*********************************************************************************************/
#ifdef USE_GPS
#define XSNS_60 60
#include "NTPServer.h"
#include "NTPPacket.h"
/*********************************************************************************************\
* constants
\*********************************************************************************************/
@ -117,7 +119,6 @@ const char kUBXTypes[] PROGMEM = "UBX";
#define UBX_LAT_LON_THRESHOLD 1000 // filter out some noise of local drift
/********************************************************************************************\
| *globals
\*********************************************************************************************/
@ -152,13 +153,13 @@ const char UBLOX_INIT[] PROGMEM = {
char UBX_name[4];
struct UBX_t{
struct UBX_t {
const char UBX_HEADER[2] = { 0xB5, 0x62 }; // TODO: Check if we really save space here inside the struct
const char NAV_POSLLH_HEADER[2] = { 0x01, 0x02 };
const char NAV_STATUS_HEADER[2] = { 0x01, 0x03 };
const char NAV_TIME_HEADER[2] = { 0x01, 0x21 };
struct entry_t{
struct entry_t {
int32_t lat; //raw sensor value
int32_t lon; //raw sensor value
uint32_t time; //local time from system (maybe provided by the sensor)
@ -281,7 +282,9 @@ NtpServer timeServer(PortUdp);
/*********************************************************************************************\
* helper function
\*********************************************************************************************/
void UBXcalcChecksum(char* CK, size_t msgSize) {
void UBXcalcChecksum(char* CK, size_t msgSize)
{
memset(CK, 0, 2);
for (int i = 0; i < msgSize; i++) {
CK[0] += ((char*)(&UBX.Message))[i];
@ -289,19 +292,22 @@ void UBXcalcChecksum(char* CK, size_t msgSize) {
}
}
boolean UBXcompareMsgHeader(const char* msgHeader) {
bool UBXcompareMsgHeader(const char* msgHeader)
{
char* ptr = (char*)(&UBX.Message);
return ptr[0] == msgHeader[0] && ptr[1] == msgHeader[1];
}
void UBXinitCFG(void){
for(uint32_t i = 0; i < sizeof(UBLOX_INIT); i++) {
void UBXinitCFG(void)
{
for (uint32_t i = 0; i < sizeof(UBLOX_INIT); i++) {
UBXSerial->write( pgm_read_byte(UBLOX_INIT+i) );
}
DEBUG_SENSOR_LOG(PSTR("UBX: turn off NMEA"));
}
void UBXTriggerTele(void){
void UBXTriggerTele(void)
{
mqtt_data[0] = '\0';
if (MqttShowSensor()) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
@ -313,7 +319,6 @@ void UBXTriggerTele(void){
/********************************************************************************************/
void UBXDetect(void)
{
if ((pin[GPIO_GPS_RX] < 99) && (pin[GPIO_GPS_TX] < 99)) {
@ -329,7 +334,7 @@ void UBXDetect(void)
UBXinitCFG(); // turn of NMEA, only use "our" UBX-messages
#ifdef USE_FLOG
if(!Flog){
if (!Flog) {
Flog = new FLOG; // init Flash Log
Flog->init();
}
@ -340,7 +345,8 @@ void UBXDetect(void)
UBXTriggerTele(); // ... once at after start
}
uint32_t UBXprocessGPS() {
uint32_t UBXprocessGPS()
{
static uint32_t fpos = 0;
static char checksum[2];
static uint8_t currentMsgType = MT_NONE;
@ -353,20 +359,20 @@ uint32_t UBXprocessGPS() {
byte c = UBXSerial->read();
if ( fpos < 2 ) {
// For the first two bytes we are simply looking for a match with the UBX header bytes (0xB5,0x62)
if ( c == UBX.UBX_HEADER[fpos] )
if ( c == UBX.UBX_HEADER[fpos] ) {
fpos++;
else
} else {
fpos = 0; // Reset to beginning state.
}
else {
} else {
// If we come here then fpos >= 2, which means we have found a match with the UBX_HEADER
// and we are now reading in the bytes that make up the payload.
// Place the incoming byte into the ubxMessage struct. The position is fpos-2 because
// the struct does not include the initial two-byte header (UBX_HEADER).
if ( (fpos-2) < payloadSize )
if ( (fpos-2) < payloadSize ) {
((char*)(&UBX.Message))[fpos-2] = c;
}
fpos++;
if ( fpos == 4 ) {
@ -424,7 +430,7 @@ uint32_t UBXprocessGPS() {
}
}
// DEBUG_SENSOR_LOG(PSTR("UBX: got none or unknown Message"));
if(data_bytes!=0) {
if (data_bytes!=0) {
UBX.state.non_empty_loops++;
DEBUG_SENSOR_LOG(PSTR("UBX: got %u bytes, non-empty-loop: %u"), data_bytes, UBX.state.non_empty_loops);
} else {
@ -436,8 +442,10 @@ uint32_t UBXprocessGPS() {
/********************************************************************************************\
| * callback functions for the download
\*********************************************************************************************/
#ifdef USE_FLOG
void UBXsendHeader(void) {
void UBXsendHeader(void)
{
WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN);
WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=TASMOTA.gpx"));
WSSend(200, CT_STREAM, F(
@ -448,7 +456,8 @@ void UBXsendHeader(void) {
"<trk>\r\n<trkseg>\r\n"));
}
void UBXsendRecord(uint8_t *buf){
void UBXsendRecord(uint8_t *buf)
{
char record[100];
char stime[32];
UBX_t::entry_t *entry = (UBX_t::entry_t*)buf;
@ -462,21 +471,26 @@ void UBXsendRecord(uint8_t *buf){
WebServer->sendContent_P(record);
}
void UBXsendFooter(void){
void UBXsendFooter(void)
{
WebServer->sendContent(F("</trkseg>\n</trk>\n</gpx>"));
WebServer->sendContent("");
Rtc.user_time_entry = false; // we have blocked the main loop and want a new valid time
}
/********************************************************************************************/
void UBXsendFile(void){
void UBXsendFile(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
Flog->startDownload(sizeof(UBX.rec_buffer),UBXsendHeader,UBXsendRecord,UBXsendFooter);
}
#endif //USE_FLOG
/********************************************************************************************/
void UBXSetRate(uint16_t interval){
void UBXSetRate(uint16_t interval)
{
UBX.Message.cfgRate.cls = 0x06;
UBX.Message.cfgRate.id = 0x08;
UBX.Message.cfgRate.len = 6;
@ -491,15 +505,15 @@ void UBXSetRate(uint16_t interval){
DEBUG_SENSOR_LOG(PSTR("UBX: requested interval: %u seconds measRate: %u ms"), interval, UBX.Message.cfgRate.measRate);
UBXSerial->write(UBX.UBX_HEADER[0]);
UBXSerial->write(UBX.UBX_HEADER[1]);
for(uint32_t i =0; i<sizeof(UBX.Message.cfgRate); i++){
for (uint32_t i =0; i<sizeof(UBX.Message.cfgRate); i++) {
UBXSerial->write(((uint8_t*)(&UBX.Message.cfgRate))[i]);
DEBUG_SENSOR_LOG(PSTR("UBX: cfgRate byte %u: %x"), i, ((uint8_t*)(&UBX.Message.cfgRate))[i]);
}
UBX.state.log_interval = 10*interval;
}
void UBXSelectMode(uint16_t mode){
void UBXSelectMode(uint16_t mode)
{
DEBUG_SENSOR_LOG(PSTR("UBX: set mode to %u"),mode);
switch(mode){
#ifdef USE_FLOG
@ -537,7 +551,7 @@ void UBXSelectMode(uint16_t mode){
UBX.mode.send_when_new = 0; // only TELE
break;
case 9:
if (timeServer.beginListening()){
if (timeServer.beginListening()) {
UBX.mode.runningNTP = true;
}
break;
@ -555,7 +569,7 @@ void UBXSelectMode(uint16_t mode){
Settings.longitude = UBX.state.last_lon;
break;
default:
if(mode>1000 && mode <1066) {
if (mode>1000 && mode <1066) {
// UBXSetRate(mode-1000); // min. 1001 = 0.001 Hz, but will be converted to 1/65535 anyway ~0.015 Hz, max. 2000 = 1.000 Hz
UBXSetRate(mode-1000); // set interval between measurements in seconds from 1 to 65
}
@ -564,26 +578,28 @@ void UBXSelectMode(uint16_t mode){
UBX.mode.send_UI_only = true;
UBXTriggerTele();
}
/********************************************************************************************/
bool UBXHandlePOSLLH(){
bool UBXHandlePOSLLH()
{
DEBUG_SENSOR_LOG(PSTR("UBX: iTOW: %u"),UBX.Message.navPosllh.iTOW);
if(UBX.state.gpsFix>1){
if(UBX.mode.filter_noise){
if((UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat<abs(UBX_LAT_LON_THRESHOLD))||(UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon<abs(UBX_LAT_LON_THRESHOLD))){
if (UBX.state.gpsFix>1) {
if (UBX.mode.filter_noise) {
if ((UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat<abs(UBX_LAT_LON_THRESHOLD))||(UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon<abs(UBX_LAT_LON_THRESHOLD))) {
DEBUG_SENSOR_LOG(PSTR("UBX: Diff lat: %u lon: %u "),UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat, UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon);
return false; //no new position
}
}
UBX.rec_buffer.values.lat = UBX.Message.navPosllh.lat;
UBX.rec_buffer.values.lon = UBX.Message.navPosllh.lon;
DEBUG_SENSOR_LOG(PSTR("UBX: lat/lon: %i / %i"),UBX.rec_buffer.values.lat, UBX.rec_buffer.values.lon);
DEBUG_SENSOR_LOG(PSTR("UBX: hAcc: %d"),UBX.Message.navPosllh.hAcc);
DEBUG_SENSOR_LOG(PSTR("UBX: lat/lon: %i / %i"), UBX.rec_buffer.values.lat, UBX.rec_buffer.values.lon);
DEBUG_SENSOR_LOG(PSTR("UBX: hAcc: %d"), UBX.Message.navPosllh.hAcc);
UBX.state.last_iTOW = UBX.Message.navPosllh.iTOW;
UBX.state.last_height = UBX.Message.navPosllh.height;
UBX.state.last_vAcc = UBX.Message.navPosllh.vAcc;
UBX.state.last_hAcc = UBX.Message.navPosllh.hAcc;
if(UBX.mode.send_when_new){
if (UBX.mode.send_when_new) {
UBXTriggerTele();
}
return true; // new position
@ -593,21 +609,22 @@ bool UBXHandlePOSLLH(){
return false; // no GPS-fix
}
void UBXHandleSTATUS(){
DEBUG_SENSOR_LOG(PSTR("UBX: gpsFix: %u, valid: %u"),UBX.Message.navStatus.gpsFix, (UBX.Message.navStatus.flags)&1);
if((UBX.Message.navStatus.flags)&1){
void UBXHandleSTATUS()
{
DEBUG_SENSOR_LOG(PSTR("UBX: gpsFix: %u, valid: %u"), UBX.Message.navStatus.gpsFix, (UBX.Message.navStatus.flags)&1);
if ((UBX.Message.navStatus.flags)&1) {
UBX.state.gpsFix = UBX.Message.navStatus.gpsFix; //only store fixed status if flag is valid
}
else{
} else {
UBX.state.gpsFix = 0; // without valid flag, everything is "no fix"
}
}
void UBXHandleTIME(){
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time: %u-%u-%u %u:%u:%u"),UBX.Message.navTime.year, UBX.Message.navTime.month ,UBX.Message.navTime.day,UBX.Message.navTime.hour,UBX.Message.navTime.min,UBX.Message.navTime.sec);
if(UBX.Message.navTime.valid.UTC){
void UBXHandleTIME()
{
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time: %u-%u-%u %u:%u:%u"), UBX.Message.navTime.year, UBX.Message.navTime.month ,UBX.Message.navTime.day,UBX.Message.navTime.hour,UBX.Message.navTime.min,UBX.Message.navTime.sec);
if (UBX.Message.navTime.valid.UTC) {
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time is valid"));
if(Rtc.user_time_entry == false || UBX.mode.forceUTCupdate){
if (Rtc.user_time_entry == false || UBX.mode.forceUTCupdate) {
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: UTC-Time is valid, set system time"));
TIME_T gpsTime;
gpsTime.year = UBX.Message.navTime.year - 1970;
@ -624,7 +641,7 @@ void UBXHandleTIME(){
void UBXHandleOther(void)
{
if(UBX.state.non_empty_loops>6){ // we expect only 4-5 non-empty loops in a row, could change with other sensor speed (Hz)
if (UBX.state.non_empty_loops>6) { // we expect only 4-5 non-empty loops in a row, could change with other sensor speed (Hz)
UBXinitCFG(); // this should only happen with lots of NMEA-messages, but it is only a guess!!
AddLog_P(LOG_LEVEL_ERROR, PSTR("UBX: possible device-reset, will re-init"));
UBXSerial->flush();
@ -664,8 +681,8 @@ void UBXLoop(void)
}
#ifdef USE_FLOG
if(counter>UBX.state.log_interval){
if(Flog->recording && new_position){
if (counter>UBX.state.log_interval) {
if (Flog->recording && new_position) {
UBX.rec_buffer.values.time = Rtc.local_time;
Flog->addToBuffer(UBX.rec_buffer.bytes, sizeof(UBX.rec_buffer.bytes));
counter = 0;
@ -673,7 +690,7 @@ void UBXLoop(void)
}
#endif // USE_FLOG
counter++;
counter++;
}
/********************************************************************************************/
@ -735,12 +752,11 @@ void UBXShow(bool json)
dtostrfd((double)UBX.state.last_vAcc/1000.0f,3,hAcc);
dtostrfd((double)UBX.state.last_hAcc/1000.0f,3,vAcc);
if (json)
{
if (json) {
ResponseAppend_P(PSTR(",\"GPS\":{"));
if(UBX.mode.send_UI_only){
if (UBX.mode.send_UI_only) {
uint32_t i = UBX.state.log_interval / 10;
ResponseAppend_P(PSTR("\"fil\":%u,\"int\":%u}"),UBX.mode.filter_noise, i);
ResponseAppend_P(PSTR("\"fil\":%u,\"int\":%u}"), UBX.mode.filter_noise, i);
} else {
ResponseAppend_P(PSTR("\"lat\":%s,\"lon\":%s,\"height\":%s,\"hAcc\":%s,\"vAcc\":%s}"), lat, lon, height, hAcc, vAcc);
}
@ -755,32 +771,32 @@ void UBXShow(bool json)
#ifdef DEBUG_TASMOTA_SENSOR
#ifdef USE_FLOG
WSContentSend_PD(HTTP_SNS_FLOGVER, Flog->num_sectors, Flog->size, Flog->current_sector, Flog->sectors_left, Flog->sector.header.physical_start_sector);
if(Flog->recording){
if (Flog->recording) {
WSContentSend_PD(HTTP_SNS_FLOGREC, Flog->sector.header.buf_pointer - 8);
}
#endif //USE_FLOG
#endif // DEBUG_TASMOTA_SENSOR
#ifdef USE_FLOG
if(Flog->ready){
if (Flog->ready) {
WSContentSend_P(HTTP_SNS_FLOG,kFLOG_STATE[Flog->recording]);
}
if(!Flog->recording && Flog->found_saved_data){
if (!Flog->recording && Flog->found_saved_data) {
WSContentSend_P(HTTP_BTN_FLOG_DL);
}
#endif //USE_FLOG
if(UBX.mode.runningNTP){
if (UBX.mode.runningNTP) {
WSContentSend_P(HTTP_SNS_NTPSERVER);
}
#endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* check the UBX commands
\*********************************************************************************************/
bool UBXCmd(void) {
bool UBXCmd(void)
{
bool serviced = true;
if (XdrvMailbox.data_len > 0) {
UBXSelectMode(XdrvMailbox.payload);
@ -793,8 +809,6 @@ bool UBXCmd(void) {
* Interface
\*********************************************************************************************/
#define XSNS_60 60
bool Xsns60(uint8_t function)
{
bool result = false;
@ -805,7 +819,7 @@ bool Xsns60(uint8_t function)
UBXDetect();
break;
case FUNC_COMMAND_SENSOR:
if (XSNS_60 == XdrvMailbox.index){
if (XSNS_60 == XdrvMailbox.index) {
result = UBXCmd();
}
break;
@ -814,7 +828,7 @@ bool Xsns60(uint8_t function)
break;
case FUNC_EVERY_100_MSECOND:
#ifdef USE_FLOG
if(!Flog->running_download)
if (!Flog->running_download)
#endif //USE_FLOG
{
UBXLoop();
@ -831,7 +845,7 @@ bool Xsns60(uint8_t function)
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
#ifdef USE_FLOG
if(!Flog->running_download)
if (!Flog->running_download)
#endif //USE_FLOG
{
UBXShow(0);

View File

@ -131,7 +131,8 @@ a_setoption = [[
"GroupTopic replaces %topic% (0) or fixed topic cmnd/grouptopic (1)",
"Enable incrementing bootcount when deepsleep is enabled",
"Do not power off if slider moved to far left",
"","",
"Bypass Compatibility check",
"",
"Enable shutter support",
"Invert PCF8574 ports"
],[
@ -187,7 +188,7 @@ a_features = [[
"USE_SHUTTER","USE_PCF8574","USE_DDSU666","USE_DEEPSLEEP",
"USE_SONOFF_SC","USE_SONOFF_RF","USE_SONOFF_L1","USE_EXS_DIMMER",
"USE_ARDUINO_SLAVE","USE_HIH6","USE_HPMA","USE_TSL2591",
"USE_DHT12","","","",
"USE_DHT12","","USE_GPS","",
"","","","",
"","","",""
]]