diff --git a/BUILDS.md b/BUILDS.md index 3b311a6eb..5c897746a 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -119,6 +119,9 @@ | USE_MP3_PLAYER | - | - | - | - | x | - | - | | USE_AZ7798 | - | - | - | - | - | - | - | | USE_PN532_HSU | - | - | - | - | x | - | - | +| USE_RDM6300 | - | - | - | - | x | - | - | +| USE_IBEACON | - | - | - | - | x | - | - | +| USE_GPS | - | - | - | - | - | - | - | | USE_ZIGBEE | - | - | - | - | - | - | - | | | | | | | | | | | USE_IR_REMOTE | - | - | x | x | x | x | x | diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3e02a4219..b64e01748 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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 diff --git a/lib/ArduinoNTPd/NTPPacket.cpp b/lib/ArduinoNTPd/NTPPacket.cpp new file mode 100644 index 000000000..e3cdf6b32 --- /dev/null +++ b/lib/ArduinoNTPd/NTPPacket.cpp @@ -0,0 +1,39 @@ +/* + * File: NTPPacket.cpp + * Description: + * NTP packet representation. + * Author: Mooneer Salem + * License: New BSD License + */ + +#include "NTPPacket.h" + +void NtpPacket::swapEndian() +{ + reverseBytes_(&rootDelay); + reverseBytes_(&rootDispersion); + reverseBytes_(&referenceTimestampSeconds); + reverseBytes_(&referenceTimestampFraction); + reverseBytes_(&originTimestampSeconds); + reverseBytes_(&originTimestampFraction); + reverseBytes_(&receiveTimestampSeconds); + reverseBytes_(&receiveTimestampFraction); + reverseBytes_(&transmitTimestampSeconds); + reverseBytes_(&transmitTimestampFraction); +} + +void NtpPacket::reverseBytes_(uint32_t *number) +{ + char buf[4]; + char *numberAsChar = (char*)number; + + buf[0] = numberAsChar[3]; + buf[1] = numberAsChar[2]; + buf[2] = numberAsChar[1]; + buf[3] = numberAsChar[0]; + + numberAsChar[0] = buf[0]; + numberAsChar[1] = buf[1]; + numberAsChar[2] = buf[2]; + numberAsChar[3] = buf[3]; +} diff --git a/lib/ArduinoNTPd/NTPPacket.h b/lib/ArduinoNTPd/NTPPacket.h new file mode 100644 index 000000000..80a1366f5 --- /dev/null +++ b/lib/ArduinoNTPd/NTPPacket.h @@ -0,0 +1,75 @@ +/* + * File: NTPPacket.h + * Description: + * NTP packet representation. + * Author: Mooneer Salem + * License: New BSD License + */ + +#ifndef NTP_PACKET_H +#define NTP_PACKET_H + +#include "Arduino.h" + + +/* + * Contains the data in a typical NTP packet. + */ +struct NtpPacket +{ + static const int PACKET_SIZE = 48; + + unsigned char leapVersionMode; + + unsigned int leapIndicator() const { return leapVersionMode >> 6; } + void leapIndicator(unsigned int newValue) { leapVersionMode = (0x3F & leapVersionMode) | ((newValue & 0x03) << 6); } + + unsigned int versionNumber() const { return (leapVersionMode >> 3) & 0x07; } + void versionNumber(unsigned int newValue) { leapVersionMode = (0xC7 & leapVersionMode) | ((newValue & 0x07) << 3); } + + unsigned int mode() const { return (leapVersionMode & 0x07); } + void mode(unsigned int newValue) { leapVersionMode = (leapVersionMode & 0xF8) | (newValue & 0x07); } + + char stratum; + char poll; + char precision; + uint32_t rootDelay; + uint32_t rootDispersion; + char referenceId[4]; + uint32_t referenceTimestampSeconds; + uint32_t referenceTimestampFraction; + uint32_t originTimestampSeconds; + uint32_t originTimestampFraction; + uint32_t receiveTimestampSeconds; + uint32_t receiveTimestampFraction; + uint32_t transmitTimestampSeconds; + uint32_t transmitTimestampFraction; + + /* + * Rearranges bytes in 32 bit values from big-endian (NTP protocol) + * to little-endian (Arduino/PC), or vice versa. Must be called before + * modifying the structure or sending the packet. + */ + void swapEndian(); + + /* + * Returns packet as a char array for transmission via network. + * WARNING: modifying the return value is unsafe. + */ + const char *packet() { return (const char*)this; } + + /* + * Copies packet buffer to packet object. + */ + void populatePacket(const char *buffer) + { + memcpy(this, buffer, PACKET_SIZE); + } +private: + /* + * Reverses bytes in a number. + */ + void reverseBytes_(uint32_t *number); +}; + +#endif // NTP_PACKET_H diff --git a/lib/ArduinoNTPd/NTPServer.cpp b/lib/ArduinoNTPd/NTPServer.cpp new file mode 100644 index 000000000..6ee82bd6c --- /dev/null +++ b/lib/ArduinoNTPd/NTPServer.cpp @@ -0,0 +1,79 @@ +/* + * File: NTPServer.cpp + * Description: + * NTP server implementation. + * Author: Mooneer Salem + * License: New BSD License + */ + + +#include + +#include "NTPPacket.h" +#include "NTPServer.h" + +#define NTP_PORT 123 +#define NTP_TIMESTAMP_DIFF (2208988800) // 1900 to 1970 in seconds + +bool NtpServer::beginListening() +{ + if (timeServerPort_.begin(NTP_PORT)){ + return true; + } + return false; +} + +bool NtpServer::processOneRequest(uint32_t utc, uint32_t millisecs) +{ + // We need the time we've received the packet in our response. + uint32_t recvSecs = utc + NTP_TIMESTAMP_DIFF; + double recvFractDouble = (double)millisecs/0.00023283064365386963; // millisec/((10^6)/(2^32)) + uint32_t recvFract = (double)recvFractDouble; //TODO: really handle this!!! + bool processed = false; + + int packetDataSize = timeServerPort_.parsePacket(); + if (packetDataSize && packetDataSize >= NtpPacket::PACKET_SIZE) + { + // Received what is probably an NTP packet. Read it in and verify + // that it's legit. + NtpPacket packet; + timeServerPort_.read((char*)&packet, NtpPacket::PACKET_SIZE); + // TODO: verify packet. + + // Populate response. + packet.swapEndian(); + packet.leapIndicator(0); + packet.versionNumber(4); + packet.mode(4); + packet.stratum = 2; // I guess stratum 1 is too optimistic + packet.poll = 10; // 6-10 per RFC 5905. + packet.precision = -21; // ~0.5 microsecond precision. + packet.rootDelay = 0; //60 * (0xFFFF / 1000); // ~60 milliseconds, TBD + packet.rootDispersion = 0; //10 * (0xFFFF / 1000); // ~10 millisecond dispersion, TBD + packet.referenceId[0] = 'G'; + packet.referenceId[1] = 'P'; + packet.referenceId[2] = 'S'; + packet.referenceId[3] = 0; + packet.referenceTimestampSeconds = utc; + packet.referenceTimestampFraction = recvFract; + packet.originTimestampSeconds = packet.transmitTimestampSeconds; + packet.originTimestampFraction = packet.transmitTimestampFraction; + packet.receiveTimestampSeconds = recvSecs; + packet.receiveTimestampFraction = recvFract; + + // ...and the transmit time. + // timeSource_.now(&packet.transmitTimestampSeconds, &packet.transmitTimestampFraction); + + // Now transmit the response to the client. + packet.swapEndian(); + timeServerPort_.beginPacket(timeServerPort_.remoteIP(), timeServerPort_.remotePort()); + for (int count = 0; count < NtpPacket::PACKET_SIZE; count++) + { + timeServerPort_.write(packet.packet()[count]); + } + timeServerPort_.endPacket(); + processed = true; + } + + return processed; +} \ No newline at end of file diff --git a/lib/ArduinoNTPd/NTPServer.h b/lib/ArduinoNTPd/NTPServer.h new file mode 100644 index 000000000..121d905ce --- /dev/null +++ b/lib/ArduinoNTPd/NTPServer.h @@ -0,0 +1,35 @@ +/* + * File: NTPServer.h + * Description: + * NTP server implementation. + * Author: Mooneer Salem + * License: New BSD License + */ + +#ifndef NTP_SERVER_H +#define NTP_SERVER_H + +class NtpServer +{ +public: + NtpServer(WiFiUDP Port) + { + timeServerPort_=Port; + } + + /* + * Begins listening for NTP requests. + */ + bool beginListening(void); + + + /* + * Processes a single NTP request. + */ + bool processOneRequest(uint32_t utc, uint32_t millisecs); + +private: + WiFiUDP timeServerPort_; +}; + +#endif // NTP_SERVER_H diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 097919ad0..7483129ff 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -6,12 +6,17 @@ - Release +### 8.0.0.2 20191223 + +- Changed Settings variable namings + ### 8.0.0.1 20191221 - Change Settings text handling allowing variable length text within a total text pool of 699 characters - 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 diff --git a/tasmota/language/bg-BG.h b/tasmota/language/bg-BG.h index 251a840e8..12937ca6c 100644 --- a/tasmota/language/bg-BG.h +++ b/tasmota/language/bg-BG.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/cs-CZ.h b/tasmota/language/cs-CZ.h index 79ba068ab..a297d7063 100644 --- a/tasmota/language/cs-CZ.h +++ b/tasmota/language/cs-CZ.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/de-DE.h b/tasmota/language/de-DE.h index c61da0932..663628cd1 100644 --- a/tasmota/language/de-DE.h +++ b/tasmota/language/de-DE.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/el-GR.h b/tasmota/language/el-GR.h index 3bea69d8f..d491d31a2 100644 --- a/tasmota/language/el-GR.h +++ b/tasmota/language/el-GR.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/en-GB.h b/tasmota/language/en-GB.h index d64632603..8ec56b16c 100644 --- a/tasmota/language/en-GB.h +++ b/tasmota/language/en-GB.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/es-ES.h b/tasmota/language/es-ES.h index b0c9477de..42f0991a6 100644 --- a/tasmota/language/es-ES.h +++ b/tasmota/language/es-ES.h @@ -627,9 +627,11 @@ #define D_SENSOR_SM2135_DAT "SM2135 Dat" #define D_SENSOR_DEEPSLEEP "DeepSleep" #define D_SENSOR_EXS_ENABLE "EXS Enable" -#define D_SENSOR_SLAVE_TX "Slave TX" -#define D_SENSOR_SLAVE_RX "Slave RX" -#define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_SLAVE_TX "Slave TX" +#define D_SENSOR_SLAVE_RX "Slave RX" +#define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/fr-FR.h b/tasmota/language/fr-FR.h index 48576002e..a0e34da3e 100644 --- a/tasmota/language/fr-FR.h +++ b/tasmota/language/fr-FR.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/he-HE.h b/tasmota/language/he-HE.h index 1d4a1b549..d31d5380c 100644 --- a/tasmota/language/he-HE.h +++ b/tasmota/language/he-HE.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/hu-HU.h b/tasmota/language/hu-HU.h index 111558516..7cde76cbb 100644 --- a/tasmota/language/hu-HU.h +++ b/tasmota/language/hu-HU.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/it-IT.h b/tasmota/language/it-IT.h index edf9b92c2..7b6d7eed0 100644 --- a/tasmota/language/it-IT.h +++ b/tasmota/language/it-IT.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ko-KO.h b/tasmota/language/ko-KO.h index 4e20860f5..5d15554de 100644 --- a/tasmota/language/ko-KO.h +++ b/tasmota/language/ko-KO.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/nl-NL.h b/tasmota/language/nl-NL.h index 40222e65f..ec352f3e6 100644 --- a/tasmota/language/nl-NL.h +++ b/tasmota/language/nl-NL.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pl-PL.h b/tasmota/language/pl-PL.h index a2aada42b..9c547e29b 100644 --- a/tasmota/language/pl-PL.h +++ b/tasmota/language/pl-PL.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt-BR.h b/tasmota/language/pt-BR.h index ae808ef9e..7a8db0dca 100644 --- a/tasmota/language/pt-BR.h +++ b/tasmota/language/pt-BR.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/pt-PT.h b/tasmota/language/pt-PT.h index e6684232c..8b9868bd8 100644 --- a/tasmota/language/pt-PT.h +++ b/tasmota/language/pt-PT.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/ru-RU.h b/tasmota/language/ru-RU.h index cd5c072aa..610af6f3f 100644 --- a/tasmota/language/ru-RU.h +++ b/tasmota/language/ru-RU.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/sk-SK.h b/tasmota/language/sk-SK.h index ef16212ca..9aa428b69 100644 --- a/tasmota/language/sk-SK.h +++ b/tasmota/language/sk-SK.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/sv-SE.h b/tasmota/language/sv-SE.h index 971ee42b1..1a70704ee 100644 --- a/tasmota/language/sv-SE.h +++ b/tasmota/language/sv-SE.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/tr-TR.h b/tasmota/language/tr-TR.h index 67400d1cc..6e9d9b9bf 100644 --- a/tasmota/language/tr-TR.h +++ b/tasmota/language/tr-TR.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "A" diff --git a/tasmota/language/uk-UK.h b/tasmota/language/uk-UK.h index b81f30012..d4862c3c8 100644 --- a/tasmota/language/uk-UK.h +++ b/tasmota/language/uk-UK.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "А" diff --git a/tasmota/language/zh-CN.h b/tasmota/language/zh-CN.h index 665f39fc3..886a7c918 100644 --- a/tasmota/language/zh-CN.h +++ b/tasmota/language/zh-CN.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "安" diff --git a/tasmota/language/zh-TW.h b/tasmota/language/zh-TW.h index 6f56e03df..61f82fb4c 100644 --- a/tasmota/language/zh-TW.h +++ b/tasmota/language/zh-TW.h @@ -630,6 +630,8 @@ #define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RESET "Slave RST" +#define D_SENSOR_GPS_RX "GPS RX" +#define D_SENSOR_GPS_TX "GPS TX" // Units #define D_UNIT_AMPERE "安" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 89640ffcf..13e7c6c64 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -471,6 +471,8 @@ // #define USE_PN532_DATA_RAW // Allow DATA block to be used by non-alpha-numberic data (+ 80 bytes code, 48 bytes ram) //#define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8) //#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) +//#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) +// #define USE_FLOG // Add support for GPS logging in OTA's Flash (Experimental) (+ 2.9kb flash, +8 bytes RAM) // -- Power monitoring sensors -------------------- #define USE_ENERGY_MARGIN_DETECTION // Add support for Energy Margin detection (+1k6 code) diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index 31428d03b..dcc24de0b 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -493,7 +493,9 @@ void GetFeatures(void) feature5 |= 0x00100000; #endif // feature5 |= 0x00200000; -// feature5 |= 0x00400000; +#ifdef USE_GPS + feature5 |= 0x00400000; +#endif // feature5 |= 0x00800000; // feature5 |= 0x01000000; diff --git a/tasmota/support_flash_log.ino b/tasmota/support_flash_log.ino new file mode 100644 index 000000000..8d02ec2e8 --- /dev/null +++ b/tasmota/support_flash_log.ino @@ -0,0 +1,432 @@ +/* + support_flash_log.ino - log to flash support for Sonoff-Tasmota + + Copyright (C) 2019 Theo Arends & Christian Baars + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + -------------------------------------------------------------------------------------------- + Version Date Action Description + -------------------------------------------------------------------------------------------- + + + --- + 1.0.0.0 20190923 started - further development by Christian Baars - https://github.com/Staars/Sonoff-Tasmota + forked - from arendst/tasmota - https://github.com/arendst/Sonoff-Tasmota + base - code base from arendst and - written from scratch + +*/ + +/********************************************************************************************\ +| * Generic helper class to log arbitrary data to the OTA-partition +| * Working principle: Add preferrable small chunks of data to the sector buffer, which will +| * be written to FLASH when full automatically. The next sector will be +| * erased and is the anchor point for downloading and state configuration +| * after reboot. +\*********************************************************************************************/ + +#ifdef USE_FLOG + +class FLOG + +#define MAGIC_WORD_FL 0x464c //F, L + +{ + +struct header_t{ + uint16_t magic_word; // FL + uint16_t padding; // leave something for the future + uint32_t physical_start_sector:10; //first used sector of the current FLOG + uint32_t number:10; // number of this sector, starting with 0 for the first sector + uint32_t buf_pointer:12; //internal pointer to the next free position in the buffer = first empty byte when reading + }; // should be 4-byte-aligned + +private: +void _readSector(uint8_t one_sector); +void _eraseSector(uint8_t one_sector); +void _writeSector(uint8_t one_sector); +void _clearBuffer(void); +void _searchSaves(void); +void _findFirstErasedSector(void); +void _showBuffer(void); +void _initBuffer(void); +void _saveBufferToSector(void); +header_t _saved_header; + +public: + uint32_t size; // size of OTA-partition + uint32_t start; // start position of OTA-partition in bytes + uint32_t end; // end position of OTA-partition in bytes + uint16_t num_sectors; // calculated number of sectors with a size of 4096 bytes + + uint16_t first_erased_sector; // this will be our new start + uint16_t current_sector; // always point to next sector, where data from the buffer will be written to + + uint16_t bytes_left; // byte per buffer (of sector size 4096 bytes - 8 byte header size) + uint16_t sectors_left; // number of saved sectors for download + + uint8_t mode = 0; // 0 - write once on all sectors, then stop, 1 - write infinitely through the sectors + bool found_saved_data = false; // possible saved data has been found + bool ready = false; // the FLOG is initialized + bool running_download = false; // a download operation is running + bool recording = false; // ready for recording + + union sector_t{ + uint32_t dword_buffer[FLASH_SECTOR_SIZE/4]; + uint8_t byte_buffer[FLASH_SECTOR_SIZE]; + header_t header; // should be 4-byte-aligned + } sector; // the global buffer of 4096 bytes, used for reading and writing + + void init(void); + void addToBuffer(uint8_t src[], uint32_t size); + void startRecording(bool append); + void stopRecording(void); + + typedef void (*CallbackNoArgs) (); // simple typedef for a callback + typedef void (*CallbackWithArgs) (uint8_t *_record); // typedef for a callback with one argument + + void startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter); +}; + +extern "C" uint32_t _SPIFFS_start; // we make shure later, that only one of the two is really used ... +extern "C" uint32_t _FS_start; // ... depending on core-sdk-version + +/** + * @brief Will examine the start and end of the OTA-partition. Then the sector size will be computed, saved data should be found and the initial state will be configured. + */ +void FLOG::init(void) +{ +DEBUG_SENSOR_LOG(PSTR("FLOG: init ...")); +size = ESP.getSketchSize(); +// round one sector up +start = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1)); +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2) +end = (uint32_t)&_SPIFFS_start - 0x40200000; +#else // Core > 2.5.2 and STAGE +end = (uint32_t)&_FS_start - 0x40200000; +#endif +num_sectors = (end - start)/FLASH_SECTOR_SIZE; +DEBUG_SENSOR_LOG(PSTR("FLOG: size: 0x%lx, start: 0x%lx, end: 0x%lx, num_sectors(dec): %lu"), size, start, end, num_sectors ); +_findFirstErasedSector(); +if(first_erased_sector == 0xffff){ + _eraseSector(0); + first_erased_sector = 0; // start with sector 0, could be first run or after crash +} +_searchSaves(); +_initBuffer(); +ready = true; +} + +/********************************************************************************************\ +| * +| * private helper functions +| * +\*********************************************************************************************/ + +/** + * @brief Read a sector into the global buffer + * + * @param one_sector as an uint8_t + */ +void FLOG::_readSector(uint8_t one_sector){ + DEBUG_SENSOR_LOG(PSTR("FLOG: read sector number: %u" ), one_sector); + ESP.flashRead(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)§or.dword_buffer, FLASH_SECTOR_SIZE); +} +/** + * @brief Erase the given sector og the OTA-partition + * + * @param one_sector as an uint8_t + */ +void FLOG::_eraseSector(uint8_t one_sector){ // Erase sector of FLOG/OTA + DEBUG_SENSOR_LOG(PSTR("FLOG: erasing sector number: %u" ), one_sector); + ESP.flashEraseSector((start/FLASH_SECTOR_SIZE)+one_sector); +} +/** + * @brief Write the global buffer to the given sector + * + * @param one_sector as an uint8_t + */ +void FLOG::_writeSector(uint8_t one_sector){ // Write sector of FLOG/OTA + DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to sector number: %u" ), one_sector); + ESP.flashWrite(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)§or.dword_buffer, FLASH_SECTOR_SIZE); +} +/** + * @brief Clear the global buffer, but leave the header intact + * + */ +void FLOG::_clearBuffer(){ //not the header + for (uint32_t i = sizeof(sector.header)/4; i<(sizeof(sector.dword_buffer)/4); i++){ + sector.dword_buffer[i] = 0; + } + sector.header.buf_pointer = sizeof(sector.header); + // _showBuffer(); +} +/** + * @brief Write global buffer to FLASH and set the current sector to the next valid position, maybe to 0 + * + */ +void FLOG::_saveBufferToSector(){ // save buffer to already erased(!) sector, erase next sector, clear buffer, increment number + DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to current sector: %u" ),current_sector); + _writeSector(current_sector); + if(current_sector == num_sectors){ // 1 MB means ~110 sectors in OTA-partition, if we reach this, start a again + current_sector = 0; + } + else{ + current_sector++; + } + _eraseSector(current_sector); // we always erase the next sector, to find out were we are after restart + _clearBuffer(); + sector.header.number++; + DEBUG_SENSOR_LOG(PSTR("FLOG: new sector header number: %u" ),sector.header.number); +} + +/** + * @brief Typically after restart find the first erased sector as a starting point for further operations + * + */ +void FLOG::_findFirstErasedSector(){ + for (uint32_t i = 0; i3){ + break; + } + } +} + +/** + * @brief pass a data entry/record as uint8_t array with its size + * + * @param src uint8_t array + * @param size uint32_t size of the array + */ +void FLOG::addToBuffer(uint8_t src[], uint32_t size){ + if(mode == 0){ + if(sector.header.number == num_sectors && !ready){ + return; // we ignore additional calls and are done, TODO: maybe use meaningful return values + } + } + if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){ + // DEBUG_SENSOR_LOG(PSTR("FLOG: enough space left in buffer: %u"), FLASH_SECTOR_SIZE - sector.header.buf_pointer - sizeof(sector.header)); + // DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u, size of added: %u"), sector.header.buf_pointer, size); + + memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size); + sector.header.buf_pointer+=size; // this is the next free spot + // DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u"), sector.header.buf_pointer); + } + else{ + DEBUG_SENSOR_LOG(PSTR("FLOG: save buffer to flash sector: %u"), current_sector); + DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u"), sector.header.buf_pointer); + _saveBufferToSector(); + sectors_left++; + // but now save the data to the fresh buffer + if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){ + memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size); + sector.header.buf_pointer+=size; // this is the next free spot + } + } +} + +/** + * @brief shows that it is ready to accept recording + * + * @param append - if true append to current log, else start a new log + */ +void FLOG::startRecording(bool append){ + if(recording){ + DEBUG_SENSOR_LOG(PSTR("FLOG: already recording")); + return; + } + recording = true; + DEBUG_SENSOR_LOG(PSTR("FLOG: start recording")); + _initBuffer(); + if(!found_saved_data) { + append = false; // nothing to append to, we silently start a new log + } + if(append){ + sector.header.number = _saved_header.number+1; // continue with the next number + sector.header.physical_start_sector = _saved_header.physical_start_sector; // keep the old start sector + } + else{ //new log, old data is lost + sector.header.physical_start_sector = (uint16_t)first_erased_sector; + found_saved_data = false; + sectors_left = 0; + } + } + +/** + * @brief stop recording including saving current buffer to FLASH + * + */ +void FLOG::stopRecording(void){ + _saveBufferToSector(); + _findFirstErasedSector(); + _searchSaves(); + _initBuffer(); + recording = false; + found_saved_data = true; + } + +/** + * @brief Will start a downloads, needs the correct implementation of 3 callback functions + * + * @param size: size of the data entry/record in bytes, i.e. sizeof(myStruct) + * @param sendHeader: should implement at least something like: + * @example WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN); // This is very likely unknown!! + * WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=myfile.txt")); + * @param sendRecord: will receive the memory address as "uint8_t* addr" and should consume the current entry/record + * @example myStruct_t *entry = (myStruct_t*)addr; + * Then make useful Strings and send it, i.e.: WebServer->sendContent_P(myString); + * @param sendFooter: finish the download, should implement at least: + * @example WebServer->sendContent(""); + */ + void FLOG::startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter){ + + _readSector(sector.header.physical_start_sector); + uint32_t next_sector = sector.header.physical_start_sector; + bytes_left = sector.header.buf_pointer - sizeof(sector.header); + DEBUG_SENSOR_LOG(PSTR("FLOG: create file for download, will process %u bytes"), bytes_left); + running_download = true; + // Callback 1: Create the header incl. file name, content length (probably unknown!!) and additional header stuff + sendHeader(); + + while(sectors_left){ + DEBUG_SENSOR_LOG(PSTR("FLOG: next sector: %u"), next_sector); + //initially we have the first sector already loaded, so we do it at the bottom + uint32_t k = sizeof(sector.header); + while(bytes_left){ + // DEBUG_SENSOR_LOG(PSTR("FLOG: DL %u %u"), Flog->sector.byte_buffer[k],Flog->sector.byte_buffer[k+1]); + uint8_t *_record_start = (uint8_t*)§or.byte_buffer[k]; // this is basically the start address of the current record/entry of the Log + // Callback 2: send the pointer for consuming the next record/entry and doing something useful to create a file + sendRecord(_record_start); + if(k%128 == 0){ // give control to the system every x iteration, TODO: This will fail, when record/entry-size is not 8 + // DEBUG_SENSOR_LOG(PSTR("FLOG: now loop(), %u bytes left"), Flog->bytes_left); + OsWatchLoop(); + delay(sleep); + } + k+=size; + if(bytes_left>7){ + bytes_left-=size; + } + else{ + bytes_left = 0; + DEBUG_SENSOR_LOG(PSTR("FLOG: Flog->bytes_left not dividable by 8 ??????")); + } + } + next_sector++; + if(next_sector>num_sectors){ + next_sector = 0; + } + sectors_left--; + _readSector(next_sector); + bytes_left = sector.header.buf_pointer - sizeof(sector.header); + OsWatchLoop(); + delay(sleep); + } + running_download = false; + // Callback 3: create a footer or simply finish the download with an empty payload + sendFooter(); + // refresh settings for another download + _searchSaves(); + _initBuffer(); + } + + #endif // USE_FLOG \ No newline at end of file diff --git a/tasmota/tasmota_post.h b/tasmota/tasmota_post.h index c54adfe82..c9f8396fe 100644 --- a/tasmota/tasmota_post.h +++ b/tasmota/tasmota_post.h @@ -180,6 +180,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #define USE_PN532_HSU // Add support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8) #define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) +//#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) @@ -374,6 +375,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) +#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) //#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor #undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI @@ -462,6 +464,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) +#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) //#undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_PZEM004T // Disable PZEM004T energy sensor @@ -571,6 +574,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) +#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM) #undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_PZEM004T // Disable PZEM004T energy sensor diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h index 5556629f9..3985884ef 100644 --- a/tasmota/tasmota_template.h +++ b/tasmota/tasmota_template.h @@ -214,6 +214,8 @@ enum UserSelectablePins { GPIO_TASMOTASLAVE_RST_INV, // Slave Reset Inverted GPIO_HPMA_RX, // Honeywell HPMA115S0 Serial interface GPIO_HPMA_TX, // Honeywell HPMA115S0 Serial interface + GPIO_GPS_RX, // GPS serial interface + GPIO_GPS_TX, // GPS serial interface GPIO_SENSOR_END }; // Programmer selectable GPIO functionality @@ -294,6 +296,7 @@ const char kSensorNames[] PROGMEM = 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 ; const char kSensorNamesFixed[] PROGMEM = @@ -671,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 @@ -725,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, @@ -754,8 +763,9 @@ const uint8_t kGpioNiceList[] PROGMEM = { GPIO_A4988_MS3, // A4988 microstep pin3 #endif #ifdef USE_DEEPSLEEP - GPIO_DEEPSLEEP + GPIO_DEEPSLEEP, #endif + }; const uint8_t kModuleNiceList[] PROGMEM = { diff --git a/tasmota/xsns_60_GPS.ino b/tasmota/xsns_60_GPS.ino new file mode 100644 index 000000000..519caac20 --- /dev/null +++ b/tasmota/xsns_60_GPS.ino @@ -0,0 +1,860 @@ +/* + xsns_60_GPS.ino - GPS UBLOX support for Tasmota + + Copyright (C) 2019 Theo Arends, Christian Baars and Adrian Scillato + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_GPS +/*********************************************************************************************\ + -------------------------------------------------------------------------------------------- + Version Date Action Description + -------------------------------------------------------------------------------------------- + + 0.9.1.0 20191216 integrate - Added pin specifications from Tasmota WEB UI. Minor tweaks. + --- + 0.9.0.0 20190817 started - further development by Christian Baars - https://github.com/Staars/Sonoff-Tasmota + forked - from arendst/tasmota - https://github.com/arendst/Sonoff-Tasmota + base - code base from arendst and - https://www.youtube.com/watch?v=TwhCX0c8Xe0 + +## GPS-driver for the Ublox-series 6-8 +Driver is tested on a NEO-6m and a Beitian-220. Series 7 should work too. This adds only about 6kb to the program size, because the efficient UBX-protocol is used. These modules are quite cheap, starting at about 3.50€ for the NEO-6m. + +## Features: +- get position and time data +- sets system time automatically and Settings.latitude and Settings.longitude via command +- can log postion data with timestamp to flash with a small memory footprint of only 12 Bytes per record +- constructs a GPX-file for download of this data +- Web-UI +- simplified NTP-server +- command interface + +## Usage: +The serial pins are GPX_RX and GPS_TX, no further installation steps needed. To get more debug information compile it with option "DEBUG_TASMOTA_SENSOR". + + +## Commands: + ++ sensor60 0 + write to all available sectors, then restart and overwrite the older ones + ++ sensor60 1 + write to all available sectors, then restart and overwrite the older ones + ++ sensor60 2 + filter out horizontal drift noise + ++ sensor60 3 + turn off noise filter + ++ sensor60 4 + start recording, new data will be appended + ++ sensor60 5 + start new recording, old data will lost + ++ sensor60 6 + stop recording, download link will be visible in Web-UI + ++ sensor60 7 + send mqtt on new postion + TELE -> consider to set TELE to a very high value + ++ sensor60 8 + only TELE message + ++ sensor60 9 + start NTP-server + ++ sensor60 10 + deactivate NTP-server + ++ sensor60 11 + force update of Tasmota-system-UTC with every new GPS-time-message + ++ sensor60 12 + do not update of Tasmota-system-UTC with every new GPS-time-message + ++ sensor60 13 + set latitude and longitude in settings + + + +## Rules examples for SSD1306 32x128 + + +rule1 on tele-GPS#lat do DisplayText [s1p21c1l01f1]LAT: %value% endon on tele-GPS#lon do DisplayText [s1p21c1l2]LON: %value% endon on switch1#state==3 do sensor60 4 endon on switch1#state==2 do sensor60 6 endon + +rule2 on tele-GPS#int>9 do DisplayText [f0c9l4]I%value% endon on tele-GPS#int<10 do DisplayText [f0c9l4]I0%value% endon on tele-GPS#fil==1 do DisplayText [f0c18l4]F endon on tele-GPS#fil==0 do DisplayText [f0c18l4]N endon + +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 + +\*********************************************************************************************/ + +#define XSNS_60 60 + +#include "NTPServer.h" +#include "NTPPacket.h" + +/*********************************************************************************************\ + * constants +\*********************************************************************************************/ + +#define D_CMND_UBX "UBX" + +const char S_JSON_UBX_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_UBX "%s\":%d}"; + +const char kUBXTypes[] PROGMEM = "UBX"; + +#define UBX_LAT_LON_THRESHOLD 1000 // filter out some noise of local drift + +/********************************************************************************************\ +| *globals +\*********************************************************************************************/ + +const char UBLOX_INIT[] PROGMEM = { + // Disable NMEA + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x24, // GxGGA off + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x2B, // GxGLL off + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x02,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x32, // GxGSA off + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x39, // GxGSV off + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x04,0x00,0x00,0x00,0x00,0x00,0x01,0x04,0x40, // GxRMC off + 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x05,0x00,0x00,0x00,0x00,0x00,0x01,0x05,0x47, // GxVTG off + + // Disable UBX + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xDC, //NAV-PVT off + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0xB9, //NAV-POSLLH off + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xC0, //NAV-STATUS off + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0x92, //NAV-TIMEUTC off + + // Enable UBX + // 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x01,0x00,0x00,0x00,0x00,0x18,0xE1, //NAV-PVT on + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x13,0xBE, //NAV-POSLLH on + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x14,0xC5, //NAV-STATUS on + 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x01,0x00,0x00,0x00,0x00,0x32,0x97, //NAV-TIMEUTC on + + // Rate - we will not reset it for the moment after restart + // 0xB5,0x62,0x06,0x08,0x06,0x00,0x64,0x00,0x01,0x00,0x01,0x00,0x7A,0x12, //(10Hz) + // 0xB5,0x62,0x06,0x08,0x06,0x00,0xC8,0x00,0x01,0x00,0x01,0x00,0xDE,0x6A, //(5Hz) + // 0xB5,0x62,0x06,0x08,0x06,0x00,0xE8,0x03,0x01,0x00,0x01,0x00,0x01,0x39 //(1Hz) + // 0xB5,0x62,0x06,0x08,0x06,0x00,0xD0,0x07,0x01,0x00,0x01,0x00,0xED,0xBD //(0.5Hz) +}; + +char UBX_name[4]; + +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 { + int32_t lat; //raw sensor value + int32_t lon; //raw sensor value + uint32_t time; //local time from system (maybe provided by the sensor) + }; + + union { + entry_t values; + uint8_t bytes[sizeof(entry_t)]; + } rec_buffer; + + struct POLL_MSG { + uint8_t cls; + uint8_t id; + uint16_t zero; + }; + + struct NAV_POSLLH { + uint8_t cls; + uint8_t id; + uint16_t len; + uint32_t iTOW; + int32_t lon; + int32_t lat; + int32_t height; + int32_t hMSL; + uint32_t hAcc; + uint32_t vAcc; + }; + + struct NAV_STATUS { + uint8_t cls; + uint8_t id; + uint16_t len; + uint32_t iTOW; + uint8_t gpsFix; + uint8_t flags; //bit 0 - gpsfix valid + uint8_t fixStat; + uint8_t flags2; + uint32_t ttff; + uint32_t msss; + }; + + struct NAV_TIME_UTC { + uint8_t cls; + uint8_t id; + uint16_t len; + uint32_t iTOW; + uint32_t tAcc; + int32_t nano; // Nanoseconds of second, range -1e9 .. 1e9 (UTC) + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + struct { + uint8_t UTC:1; + uint8_t WKN:1; // week number + uint8_t TOW:1; // time of week + uint8_t padding:5; + } valid; + }; + + struct CFG_RATE { + uint8_t cls; //0x06 + uint8_t id; //0x08 + uint16_t len; // 6 bytes + uint16_t measRate; // in every ms -> 1 Hz = 1000 ms; 10 Hz = 100 ms -> x = 1000 ms / Hz + uint16_t navRate; // x measurements for 1 navigation event + uint16_t timeRef; // align to time system: 0= UTC, 1 = GPS, 2 = GLONASS, ... + char CK[2]; // checksum + }; + + struct { + uint32_t last_iTOW; + int32_t last_lat; + int32_t last_lon; + int32_t last_height; + uint32_t last_hAcc; + uint32_t last_vAcc; + uint8_t gpsFix; + uint8_t non_empty_loops; // in case of an unintended reset of the GPS, the serial interface will get flooded with NMEA + uint16_t log_interval; // in tenth of seconds + } state; + + struct { + uint32_t filter_noise:1; + uint32_t send_when_new:1; // no teleinterval + uint32_t send_UI_only:1; + uint32_t runningNTP:1; + uint32_t forceUTCupdate:1; + // TODO: more to come + } mode; + + union { + NAV_POSLLH navPosllh; + NAV_STATUS navStatus; + NAV_TIME_UTC navTime; + POLL_MSG pollMsg; + CFG_RATE cfgRate; + } Message; + +} UBX; + +enum UBXMsgType { + MT_NONE, + MT_NAV_POSLLH, + MT_NAV_STATUS, + MT_NAV_TIME, + MT_POLL +}; + +#ifdef USE_FLOG +FLOG *Flog = nullptr; +#endif //USE_FLOG +TasmotaSerial *UBXSerial; + +NtpServer timeServer(PortUdp); + +/*********************************************************************************************\ + * helper function +\*********************************************************************************************/ + +void UBXcalcChecksum(char* CK, size_t msgSize) +{ + memset(CK, 0, 2); + for (int i = 0; i < msgSize; i++) { + CK[0] += ((char*)(&UBX.Message))[i]; + CK[1] += CK[0]; + } +} + +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++) { + UBXSerial->write( pgm_read_byte(UBLOX_INIT+i) ); + } + DEBUG_SENSOR_LOG(PSTR("UBX: turn off NMEA")); +} + +void UBXTriggerTele(void) +{ + mqtt_data[0] = '\0'; + if (MqttShowSensor()) { + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); +#ifdef USE_RULES + RulesTeleperiod(); // Allow rule based HA messages +#endif // USE_RULES + } +} + +/********************************************************************************************/ + +void UBXDetect(void) +{ + if ((pin[GPIO_GPS_RX] < 99) && (pin[GPIO_GPS_TX] < 99)) { + UBXSerial = new TasmotaSerial(pin[GPIO_GPS_RX], pin[GPIO_GPS_TX], 1, 0, 96); // 64 byte buffer is NOT enough + if (UBXSerial->begin(9600)) { + DEBUG_SENSOR_LOG(PSTR("UBX: started serial")); + if (UBXSerial->hardwareSerial()) { + ClaimSerial(); + DEBUG_SENSOR_LOG(PSTR("UBX: claim HW")); + } + } + } + + UBXinitCFG(); // turn of NMEA, only use "our" UBX-messages +#ifdef USE_FLOG + if (!Flog) { + Flog = new FLOG; // init Flash Log + Flog->init(); + } +#endif // USE_FLOG + + UBX.state.log_interval = 10; // 1 second + UBX.mode.send_UI_only = true; // send UI data ... + UBXTriggerTele(); // ... once at after start +} + +uint32_t UBXprocessGPS() +{ + static uint32_t fpos = 0; + static char checksum[2]; + static uint8_t currentMsgType = MT_NONE; + static size_t payloadSize = sizeof(UBX.Message); + + // DEBUG_SENSOR_LOG(PSTR("UBX: check for serial data")); + uint32_t data_bytes = 0; + while ( UBXSerial->available() ) { + data_bytes++; + 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] ) { + fpos++; + } else { + fpos = 0; // Reset to beginning state. + } + } 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 ) { + ((char*)(&UBX.Message))[fpos-2] = c; + } + fpos++; + + if ( fpos == 4 ) { + // We have just received the second byte of the message type header, + // so now we can check to see what kind of message it is. + if ( UBXcompareMsgHeader(UBX.NAV_POSLLH_HEADER) ) { + currentMsgType = MT_NAV_POSLLH; + payloadSize = sizeof(UBX_t::NAV_POSLLH); + DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_POSLLH")); + } + else if ( UBXcompareMsgHeader(UBX.NAV_STATUS_HEADER) ) { + currentMsgType = MT_NAV_STATUS; + payloadSize = sizeof(UBX_t::NAV_STATUS); + DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_STATUS")); + } + else if ( UBXcompareMsgHeader(UBX.NAV_TIME_HEADER) ) { + currentMsgType = MT_NAV_TIME; + payloadSize = sizeof(UBX_t::NAV_TIME_UTC); + DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_TIME_UTC")); + } + else { + // unknown message type, bail + fpos = 0; + continue; + } + } + + if ( fpos == (payloadSize+2) ) { + // All payload bytes have now been received, so we can calculate the + // expected checksum value to compare with the next two incoming bytes. + UBXcalcChecksum(checksum, payloadSize); + } + else if ( fpos == (payloadSize+3) ) { + // First byte after the payload, ie. first byte of the checksum. + // Does it match the first byte of the checksum we calculated? + if ( c != checksum[0] ) { + // Checksum doesn't match, reset to beginning state and try again. + fpos = 0; + } + } + else if ( fpos == (payloadSize+4) ) { + // Second byte after the payload, ie. second byte of the checksum. + // Does it match the second byte of the checksum we calculated? + fpos = 0; // We will reset the state regardless of whether the checksum matches. + if ( c == checksum[1] ) { + // Checksum matches, we have a valid message. + return currentMsgType; + } + } + else if ( fpos > (payloadSize+4) ) { + // We have now read more bytes than both the expected payload and checksum + // together, so something went wrong. Reset to beginning state and try again. + fpos = 0; + } + } + } + // DEBUG_SENSOR_LOG(PSTR("UBX: got none or unknown Message")); + 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 { + UBX.state.non_empty_loops = 0; // now a hidden GPS-device reset is unlikely + } + return MT_NONE; +} + +/********************************************************************************************\ +| * callback functions for the download +\*********************************************************************************************/ + +#ifdef USE_FLOG +void UBXsendHeader(void) +{ + WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN); + WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=TASMOTA.gpx")); + WSSend(200, CT_STREAM, F( + "\r\n" + "\r\n" + "\r\n\r\n")); +} + +void UBXsendRecord(uint8_t *buf) +{ + char record[100]; + char stime[32]; + UBX_t::entry_t *entry = (UBX_t::entry_t*)buf; + snprintf_P(stime, sizeof(stime), GetDT(entry->time).c_str()); + char lat[12]; + char lon[12]; + dtostrfd((double)entry->lat/10000000.0f,7,lat); + dtostrfd((double)entry->lon/10000000.0f,7,lon); + snprintf_P(record, sizeof(record),PSTR("\n\t\n\n"),lat ,lon, stime); + // DEBUG_SENSOR_LOG(PSTR("FLOG: DL %u %u"), Flog->sector.dword_buffer[k+j],Flog->sector.dword_buffer[k+j+1]); + WebServer->sendContent_P(record); +} + +void UBXsendFooter(void) +{ + WebServer->sendContent(F("\n\n")); + WebServer->sendContent(""); + Rtc.user_time_entry = false; // we have blocked the main loop and want a new valid time +} + +/********************************************************************************************/ + +void UBXsendFile(void) +{ + if (!HttpCheckPriviledgedAccess()) { return; } + Flog->startDownload(sizeof(UBX.rec_buffer),UBXsendHeader,UBXsendRecord,UBXsendFooter); +} +#endif //USE_FLOG + +/********************************************************************************************/ + +void UBXSetRate(uint16_t interval) +{ + UBX.Message.cfgRate.cls = 0x06; + UBX.Message.cfgRate.id = 0x08; + UBX.Message.cfgRate.len = 6; + uint32_t measRate = (1000*(uint32_t)interval); //seconds to milliseconds + if (measRate > 0xffff) { + measRate = 0xffff; // max. 65535 ms interval + } + UBX.Message.cfgRate.measRate = (uint16_t)measRate; + UBX.Message.cfgRate.navRate = 1; + UBX.Message.cfgRate.timeRef = 1; + UBXcalcChecksum(UBX.Message.cfgRate.CK, sizeof(UBX.Message.cfgRate)-sizeof(UBX.Message.cfgRate.CK)); + 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; iwrite(((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) +{ + DEBUG_SENSOR_LOG(PSTR("UBX: set mode to %u"),mode); + switch(mode){ +#ifdef USE_FLOG + case 0: + Flog->mode = 0; // write once to all available sectors, then stop + break; + case 1: + Flog->mode = 1; // write to all available sectors, then restart and overwrite the older ones + break; + case 2: + UBX.mode.filter_noise = true; // filter out horizontal drift noise, TODO: find useful values + break; + case 3: + UBX.mode.filter_noise = false; + break; + case 4: + Flog->startRecording(true); + AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - appending")); + break; + case 5: + Flog->startRecording(false); + AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - new log")); + break; + case 6: + if(Flog->recording == true){ + Flog->stopRecording(); + } + AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: stop recording")); + break; +#endif //USE_FLOG + case 7: + UBX.mode.send_when_new = 1; // send mqtt on new postion + TELE -> consider to set TELE to a very high value + break; + case 8: + UBX.mode.send_when_new = 0; // only TELE + break; + case 9: + if (timeServer.beginListening()) { + UBX.mode.runningNTP = true; + } + break; + case 10: + UBX.mode.runningNTP = false; + break; + case 11: + UBX.mode.forceUTCupdate = true; + break; + case 12: + UBX.mode.forceUTCupdate = false; + break; + case 13: + Settings.latitude = UBX.state.last_lat; + Settings.longitude = UBX.state.last_lon; + break; + default: + 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 + } + break; + } + UBX.mode.send_UI_only = true; + UBXTriggerTele(); +} + +/********************************************************************************************/ + +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.lat6) { // 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(); + UBX.state.non_empty_loops = 0; + } +} + +/********************************************************************************************/ + +void UBXTimeServer() +{ + if(UBX.mode.runningNTP){ + timeServer.processOneRequest(Rtc.utc_time, UBX.state.last_iTOW%1000); + } +} + +void UBXLoop(void) +{ + static uint16_t counter; //count up every 100 msec + static bool new_position; + + uint32_t msgType = UBXprocessGPS(); + + switch(msgType){ + case MT_NAV_POSLLH: + new_position = UBXHandlePOSLLH(); + break; + case MT_NAV_STATUS: + UBXHandleSTATUS(); + break; + case MT_NAV_TIME: + UBXHandleTIME(); + break; + default: + UBXHandleOther(); + break; + } + +#ifdef USE_FLOG + 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; + } + } +#endif // USE_FLOG + + counter++; +} + +/********************************************************************************************/ +// normaly in i18n.h + +#ifdef USE_WEBSERVER + // {s} = , {m} = , {e} = + +#ifdef USE_FLOG +#ifdef DEBUG_TASMOTA_SENSOR + const char HTTP_SNS_FLOGVER[] PROGMEM = "{s}
{m}
{e}{s} FLOG with %u sectors: {m}%u bytes{e}" + "{s} FLOG next sector for REC: {m} %u {e}" + "{s} %u sector(s) with data at sector: {m} %u {e}"; + const char HTTP_SNS_FLOGREC[] PROGMEM = "{s} RECORDING (bytes in buffer) {m}%u{e}"; +#endif // DEBUG_TASMOTA_SENSOR + + const char HTTP_SNS_FLOG[] PROGMEM = "{s}
{m}
{e}{s} Flash-Log {m} %s{e}"; + const char kFLOG_STATE0[] PROGMEM = "ready"; + const char kFLOG_STATE1[] PROGMEM = "recording"; + const char * kFLOG_STATE[] ={kFLOG_STATE0, kFLOG_STATE1}; + + const char HTTP_BTN_FLOG_DL[] PROGMEM = ""; + +#endif //USE_FLOG + const char HTTP_SNS_NTPSERVER[] PROGMEM = "{s} NTP server {m}active{e}"; + + const char HTTP_SNS_GPS[] PROGMEM = "{s} GPS latitude {m}%s{e}" + "{s} GPS longitude {m}%s{e}" + "{s} GPS height {m}%s m{e}" + "{s} GPS hor. Accuracy {m}%s m{e}" + "{s} GPS vert. Accuracy {m}%s m{e}" + "{s} GPS sat-fix status {m}%s{e}"; + + const char kGPSFix0[] PROGMEM = "no fix"; + const char kGPSFix1[] PROGMEM = "dead reckoning only"; + const char kGPSFix2[] PROGMEM = "2D-fix"; + const char kGPSFix3[] PROGMEM = "3D-fix"; + const char kGPSFix4[] PROGMEM = "GPS + dead reckoning combined"; + const char kGPSFix5[] PROGMEM = "Time only fix"; + const char * kGPSFix[] PROGMEM ={kGPSFix0, kGPSFix1, kGPSFix2, kGPSFix3, kGPSFix4, kGPSFix5}; + +// const char UBX_GOOGLE_MAPS[] =""; + + +#endif // USE_WEBSERVER + +/********************************************************************************************/ + +void UBXShow(bool json) +{ + char lat[12]; + char lon[12]; + char height[12]; + char hAcc[12]; + char vAcc[12]; + dtostrfd((double)UBX.rec_buffer.values.lat/10000000.0f,7,lat); + dtostrfd((double)UBX.rec_buffer.values.lon/10000000.0f,7,lon); + dtostrfd((double)UBX.state.last_height/1000.0f,3,height); + dtostrfd((double)UBX.state.last_vAcc/1000.0f,3,hAcc); + dtostrfd((double)UBX.state.last_hAcc/1000.0f,3,vAcc); + + if (json) { + ResponseAppend_P(PSTR(",\"GPS\":{")); + 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); + } else { + ResponseAppend_P(PSTR("\"lat\":%s,\"lon\":%s,\"height\":%s,\"hAcc\":%s,\"vAcc\":%s}"), lat, lon, height, hAcc, vAcc); + } +#ifdef USE_FLOG + ResponseAppend_P(PSTR(",\"FLOG\":{\"rec\":%u,\"mode\":%u,\"sec\":%u}"), Flog->recording, Flog->mode, Flog->sectors_left); +#endif //USE_FLOG + UBX.mode.send_UI_only = false; +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_GPS, lat, lon, height, hAcc, vAcc, kGPSFix[UBX.state.gpsFix]); + //WSContentSend_P(UBX_GOOGLE_MAPS, lat, lon); +#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) { + WSContentSend_PD(HTTP_SNS_FLOGREC, Flog->sector.header.buf_pointer - 8); + } +#endif //USE_FLOG +#endif // DEBUG_TASMOTA_SENSOR +#ifdef USE_FLOG + if (Flog->ready) { + WSContentSend_P(HTTP_SNS_FLOG,kFLOG_STATE[Flog->recording]); + } + if (!Flog->recording && Flog->found_saved_data) { + WSContentSend_P(HTTP_BTN_FLOG_DL); + } +#endif //USE_FLOG + if (UBX.mode.runningNTP) { + WSContentSend_P(HTTP_SNS_NTPSERVER); + } +#endif // USE_WEBSERVER + } +} + +/*********************************************************************************************\ + * check the UBX commands +\*********************************************************************************************/ + +bool UBXCmd(void) +{ + bool serviced = true; + if (XdrvMailbox.data_len > 0) { + UBXSelectMode(XdrvMailbox.payload); + Response_P(S_JSON_UBX_COMMAND_NVALUE, XdrvMailbox.command, XdrvMailbox.payload); + } + return serviced; +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns60(uint8_t function) +{ + bool result = false; + + if (true) { + switch (function) { + case FUNC_INIT: + UBXDetect(); + break; + case FUNC_COMMAND_SENSOR: + if (XSNS_60 == XdrvMailbox.index) { + result = UBXCmd(); + } + break; + case FUNC_EVERY_50_MSECOND: + UBXTimeServer(); + break; + case FUNC_EVERY_100_MSECOND: +#ifdef USE_FLOG + if (!Flog->running_download) +#endif //USE_FLOG + { + UBXLoop(); + } + break; +#ifdef USE_FLOG + case FUNC_WEB_ADD_HANDLER: + WebServer->on("/UBX", UBXsendFile); + break; +#endif //USE_FLOG + case FUNC_JSON_APPEND: + UBXShow(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: +#ifdef USE_FLOG + if (!Flog->running_download) +#endif //USE_FLOG + { + UBXShow(0); + } + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_GPS diff --git a/tools/decode-status.py b/tools/decode-status.py index e6751ae92..492c300fa 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -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","", "","","","", "","","","" ]]