Merge branch 'development' into release

This commit is contained in:
Theo Arends 2019-12-23 16:01:41 +01:00
commit 5c9e090ce2
36 changed files with 1599 additions and 7 deletions

View File

@ -119,6 +119,9 @@
| USE_MP3_PLAYER | - | - | - | - | x | - | - | | USE_MP3_PLAYER | - | - | - | - | x | - | - |
| USE_AZ7798 | - | - | - | - | - | - | - | | USE_AZ7798 | - | - | - | - | - | - | - |
| USE_PN532_HSU | - | - | - | - | x | - | - | | USE_PN532_HSU | - | - | - | - | x | - | - |
| USE_RDM6300 | - | - | - | - | x | - | - |
| USE_IBEACON | - | - | - | - | x | - | - |
| USE_GPS | - | - | - | - | - | - | - |
| USE_ZIGBEE | - | - | - | - | - | - | - | | USE_ZIGBEE | - | - | - | - | - | - | - |
| | | | | | | | | | | | | | | | | |
| USE_IR_REMOTE | - | - | x | x | x | x | x | | USE_IR_REMOTE | - | - | x | x | x | x | x |

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 Smoother ``Fade`` using 100Hz instead of 20Hz animation (#7179)
- Change number of rule ``Var``s and ``Mem``s from 5 to 16 (#4933) - 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 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 Zigbee coalesce sensor attributes into a single message
- Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300 - Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300

View File

@ -0,0 +1,39 @@
/*
* File: NTPPacket.cpp
* Description:
* NTP packet representation.
* Author: Mooneer Salem <mooneer@gmail.com>
* 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];
}

View File

@ -0,0 +1,75 @@
/*
* File: NTPPacket.h
* Description:
* NTP packet representation.
* Author: Mooneer Salem <mooneer@gmail.com>
* 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

View File

@ -0,0 +1,79 @@
/*
* File: NTPServer.cpp
* Description:
* NTP server implementation.
* Author: Mooneer Salem <mooneer@gmail.com>
* License: New BSD License
*/
#include <WiFiUdp.h>
#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;
}

View File

@ -0,0 +1,35 @@
/*
* File: NTPServer.h
* Description:
* NTP server implementation.
* Author: Mooneer Salem <mooneer@gmail.com>
* 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

View File

@ -6,12 +6,17 @@
- Release - Release
### 8.0.0.2 20191223
- Changed Settings variable namings
### 8.0.0.1 20191221 ### 8.0.0.1 20191221
- Change Settings text handling allowing variable length text within a total text pool of 699 characters - 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 Smoother ``Fade`` using 100Hz instead of 20Hz animation (#7179)
- Change number of rule ``Var``s and ``Mem``s from 5 to 16 (#4933) - 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 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 Zigbee coalesce sensor attributes into a single message
- Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300 - Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -627,9 +627,11 @@
#define D_SENSOR_SM2135_DAT "SM2135 Dat" #define D_SENSOR_SM2135_DAT "SM2135 Dat"
#define D_SENSOR_DEEPSLEEP "DeepSleep" #define D_SENSOR_DEEPSLEEP "DeepSleep"
#define D_SENSOR_EXS_ENABLE "EXS Enable" #define D_SENSOR_EXS_ENABLE "EXS Enable"
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "А" #define D_UNIT_AMPERE "А"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "А" #define D_UNIT_AMPERE "А"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "安" #define D_UNIT_AMPERE "安"

View File

@ -630,6 +630,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "安" #define D_UNIT_AMPERE "安"

View File

@ -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_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_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_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 -------------------- // -- Power monitoring sensors --------------------
#define USE_ENERGY_MARGIN_DETECTION // Add support for Energy Margin detection (+1k6 code) #define USE_ENERGY_MARGIN_DETECTION // Add support for Energy Margin detection (+1k6 code)

View File

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

View File

@ -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 <http://www.gnu.org/licenses/>.
--------------------------------------------------------------------------------------------
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 *)&sector.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 *)&sector.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; i<num_sectors; i++){
bool success = true;
DEBUG_SENSOR_LOG(PSTR("FLOG: read sector: %u"), i);
_readSector(i);
for (uint32_t j = 0; j<(sizeof(sector.dword_buffer)/4); j++){
if(sector.dword_buffer[j]!=0xffffffff){
// DEBUG_SENSOR_LOG(PSTR("FLOG: buffer_dword: %u"), sector.dword_buffer[j]);
success = false;
}
}
if(success){
first_erased_sector = i; // save this for the whole next write operation
sector.header.physical_start_sector = i; // save to header for every sector
current_sector = i; // this is our actual sector to write to
DEBUG_SENSOR_LOG(PSTR("FLOG: first erased sector: %u, now init ..."), first_erased_sector);
return;
}
}
DEBUG_SENSOR_LOG(PSTR("FLOG: no erased sector found"));
first_erased_sector = 0xffff; // this will not happen unless we have 256 MByte FLASH
}
/**
* @brief Look at the sector before the first erased sector to check, if there could be saved data
*
*/
void FLOG::_searchSaves(void){
//check if old Data is present
found_saved_data = false;
uint32_t s;
if(first_erased_sector==0){
DEBUG_SENSOR_LOG(PSTR("FLOG: sector 0 was erased before, examine sector: %u"), num_sectors);
s = num_sectors; //count back to the highest possible sector
}
else{
s = first_erased_sector-1;
DEBUG_SENSOR_LOG(PSTR("FLOG: examine sector: %u"), s);
}
_readSector(s); //read the sector before the first erased sector
if(sector.header.magic_word!=MAGIC_WORD_FL){
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
return;
}
sectors_left = sector.header.number + 1; // this might be wrong, but this less important
_saved_header = sector.header; // back this up for appending mode
s = sector.header.physical_start_sector;
DEBUG_SENSOR_LOG(PSTR("FLOG: will check pysical start sector: %u"), s);
_readSector(s); //read the physical_start_sector
_showBuffer();
if(sector.header.magic_word!=MAGIC_WORD_FL){ //F, L
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
sectors_left = 0;
return;
}
if(sector.header.number==0){ //physical_start_sector should have number 0
DEBUG_SENSOR_LOG(PSTR("FLOG: possible saved data found"));
found_saved_data = true; // TODO: this is only a very rough check and should be completed later
}
else{
DEBUG_SENSOR_LOG(PSTR("FLOG: number: %u should be 0"), sector.header.number);
sectors_left = 0;
}
}
/**
* @brief Start with a new buffer to be able to start a write session
*
*/
void FLOG::_initBuffer(void){
if(!found_saved_data){ // we must re-init this, because the buffer is in an undefined state
sector.header.physical_start_sector = (uint16_t)first_erased_sector;
}
DEBUG_SENSOR_LOG(PSTR("FLOG: init header"));
sector.header.magic_word = MAGIC_WORD_FL; //F, L
sector.header.number = 0;
sector.header.buf_pointer = (uint16_t)sizeof(sector.header);
current_sector = first_erased_sector;
ready = true;
_clearBuffer();
}
/**
* @brief - a pure debug function
*
*/
void FLOG::_showBuffer(void){
DEBUG_SENSOR_LOG(PSTR("FLOG: Header: %c %c"), sector.byte_buffer[0],sector.byte_buffer[1]);
DEBUG_SENSOR_LOG(PSTR("FLOG: V_Start_sector: %u, sector number: %u, pointer: %u "), sector.header.physical_start_sector, sector.header.number, sector.header.buf_pointer);
uint32_t j = 0;
for (uint32_t i = sector.header.buf_pointer-16; i<(sizeof(sector.byte_buffer)); i+=8){
// DEBUG_SENSOR_LOG(PSTR("FLOG: buffer: %c %c %c %c %c %c %c %c "), sector.byte_buffer[i], sector.byte_buffer[i+1], sector.byte_buffer[i+2], sector.byte_buffer[i+3], sector.byte_buffer[i+4], sector.byte_buffer[i+5], sector.byte_buffer[i+6], sector.byte_buffer[i+7]);
DEBUG_SENSOR_LOG(PSTR("FLOG: buffer: %u %u %u %u %u %u %u %u "), sector.byte_buffer[i], sector.byte_buffer[i+1], sector.byte_buffer[i+2], sector.byte_buffer[i+3], sector.byte_buffer[i+4], sector.byte_buffer[i+5], sector.byte_buffer[i+6], sector.byte_buffer[i+7]);
j++;
if(j>3){
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*)&sector.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

View File

@ -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_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_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_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_ENERGY_SENSOR // Add energy sensors (-14k code)
#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k 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_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_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_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 //#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 #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_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_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_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_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor #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_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_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_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_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor #undef USE_PZEM004T // Disable PZEM004T energy sensor

View File

@ -214,6 +214,8 @@ enum UserSelectablePins {
GPIO_TASMOTASLAVE_RST_INV, // Slave Reset Inverted GPIO_TASMOTASLAVE_RST_INV, // Slave Reset Inverted
GPIO_HPMA_RX, // Honeywell HPMA115S0 Serial interface GPIO_HPMA_RX, // Honeywell HPMA115S0 Serial interface
GPIO_HPMA_TX, // 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 }; GPIO_SENSOR_END };
// Programmer selectable GPIO functionality // Programmer selectable GPIO functionality
@ -294,6 +296,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_DEEPSLEEP "|" D_SENSOR_EXS_ENABLE "|" 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_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_HPMA_RX "|" D_SENSOR_HPMA_TX "|"
D_SENSOR_GPS_RX "|" D_SENSOR_GPS_TX
; ;
const char kSensorNamesFixed[] PROGMEM = const char kSensorNamesFixed[] PROGMEM =
@ -671,6 +674,7 @@ const uint8_t kGpioNiceList[] PROGMEM = {
#endif // USE_SOLAX_X1 #endif // USE_SOLAX_X1
#endif // USE_ENERGY_SENSOR #endif // USE_ENERGY_SENSOR
// Serial
#ifdef USE_SERIAL_BRIDGE #ifdef USE_SERIAL_BRIDGE
GPIO_SBR_TX, // Serial Bridge Serial interface GPIO_SBR_TX, // Serial Bridge Serial interface
GPIO_SBR_RX, // 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_RX,
GPIO_IBEACON_TX, GPIO_IBEACON_TX,
#endif #endif
#ifdef USE_GPS
GPIO_GPS_RX, // GPS serial interface
GPIO_GPS_TX, // GPS serial interface
#endif
#ifdef USE_MGC3130 #ifdef USE_MGC3130
GPIO_MGC3130_XFER, GPIO_MGC3130_XFER,
GPIO_MGC3130_RESET, GPIO_MGC3130_RESET,
@ -754,8 +763,9 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_A4988_MS3, // A4988 microstep pin3 GPIO_A4988_MS3, // A4988 microstep pin3
#endif #endif
#ifdef USE_DEEPSLEEP #ifdef USE_DEEPSLEEP
GPIO_DEEPSLEEP GPIO_DEEPSLEEP,
#endif #endif
}; };
const uint8_t kModuleNiceList[] PROGMEM = { const uint8_t kModuleNiceList[] PROGMEM = {

860
tasmota/xsns_60_GPS.ino Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\r\n"
"<GPX version=\"1.1\" creator=\"TASMOTA\" xmlns=\"http://www.topografix.com/GPX/1/1\" \r\n"
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
"xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\r\n"
"<trk>\r\n<trkseg>\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("<trkpt\n\t lat=\"%s\" lon=\"%s\">\n\t<time>%s</time>\n</trkpt>\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("</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)
{
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; 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)
{
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.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);
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) {
UBXTriggerTele();
}
return true; // new position
} else {
DEBUG_SENSOR_LOG(PSTR("UBX: no valid position data"));
}
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) {
UBX.state.gpsFix = UBX.Message.navStatus.gpsFix; //only store fixed status if flag is valid
} 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) {
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time is valid"));
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;
gpsTime.month = UBX.Message.navTime.month;
gpsTime.day_of_month = UBX.Message.navTime.day;
gpsTime.hour = UBX.Message.navTime.hour;
gpsTime.minute = UBX.Message.navTime.min;
gpsTime.second = UBX.Message.navTime.sec;
Rtc.utc_time = MakeTime(gpsTime);
Rtc.user_time_entry = true;
}
}
}
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)
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} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
#ifdef USE_FLOG
#ifdef DEBUG_TASMOTA_SENSOR
const char HTTP_SNS_FLOGVER[] PROGMEM = "{s}<hr>{m}<hr>{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}<hr>{m}<hr>{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 = "<button><a href='/UBX'>Download GPX-File</a></button>";
#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[] ="<iframe width='100%%' src='https://maps.google.com/maps?width=&amp;height=&amp;hl=en&amp;q=%s %s+(Tasmota)&amp;ie=UTF8&amp;t=&amp;z=10&amp;iwloc=B&amp;output=embed' frameborder='0' scrolling='no' marginheight='0' marginwidth='0'></iframe>";
#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

View File

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