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/README.md b/README.md index 9a9ff0e1c..35abdb7e6 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ In addition to the [release webpage](https://github.com/arendst/Tasmota/releases ## Development -[![Dev Version](https://img.shields.io/badge/development%20version-v7.2.x.x-blue.svg)](https://github.com/arendst/Tasmota) +[![Dev Version](https://img.shields.io/badge/development%20version-v8.1.x.x-blue.svg)](https://github.com/arendst/Tasmota) [![Download Dev](https://img.shields.io/badge/download-development-yellow.svg)](http://thehackbox.org/tasmota/) [![Build Status](https://img.shields.io/travis/arendst/Tasmota.svg)](https://travis-ci.org/arendst/Tasmota) @@ -68,6 +68,11 @@ See [wiki migration path](https://tasmota.github.io/docs/#/Upgrading?id=migratio 4. Migrate to **Sonoff-Tasmota 6.x** 5. Migrate to **Tasmota 7.x** +--- Major change in parameter storage layout --- + +6. Migrate to **Tasmota 8.1** +7. Migrate to **Tasmota 8.x** + ## Support Information diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 8b952e5da..578d145b9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,8 +2,6 @@ # RELEASE NOTES -### Sonoff-Tasmota is now Tasmota - ## Migration Information See [migration path](https://tasmota.github.io/docs/#/Upgrading?id=migration-path) for instructions how to migrate to a major version. Pay attention to the following version breaks due to dynamic settings updates: @@ -14,7 +12,12 @@ See [migration path](https://tasmota.github.io/docs/#/Upgrading?id=migration-pat 4. Migrate to **Sonoff-Tasmota 6.x** 5. Migrate to **Tasmota 7.x** -Only this version will support fallback from version 8.x. +--- Major change in parameter storage layout --- + +6. Migrate to **Tasmota 8.1** +7. Migrate to **Tasmota 8.x** + +While fallback or downgrading is common practice it was never supported due to Settings additions or changes in newer releases. Starting with release **v8.1.0 Doris** the Settings are re-allocated in such a way that fallback is only allowed and possible to release **v7.2.0 Constance**. Once at v7.2.0 you're on your own when downgrading even further. ## Supported Core versions @@ -49,38 +52,16 @@ The following binary downloads have been compiled with ESP8266/Arduino library c ## Changelog -### Version 7.2.0 Constance +### Version 8.1.0 Doris -- Change Exception reporting removing exception details from ``Status 1`` and consolidated in ``Status 12`` if available -- Change HTTP CORS from command ``SetOption73 0/1`` to ``Cors `` allowing user control of specific CORS domain by Shantur Rathore (#7066) -- Change GUI Shutter button text to Up and Down Arrows based on PR by Xavier Muller (#7166) -- Change amount of supported DHT sensors from 3 to 4 by Xavier Muller (#7167) -- Change some Settings locations freeing up space for future single char allowing variable length text -- Change tasmota-basic.bin and FIRMWARE_BASIC to tasmota-lite.bin and FIRMWARE_LITE -- Change basic version string to lite (#7291) -- Fix flashing H801 led at boot by Stefan Hadinger (#7165, #649) -- Fix duplicated ``Backlog`` when using Event inside a Backlog by Adrian Scillato (#7178, #7147) -- Fix Gui Timer when using a negative zero offset of -00:00 by Peter Ooms (#7174) -- Fix DeepSleep in case there is no wifi by Stefan Bode (#7213) -- Fix Fade would ignore ``savedata 0`` and store to flash anyways (#7262) -- Fix Arduino IDE compile error (#7277) -- Fix restore ShutterAccuracy, MqttLog, WifiConfig, WifiPower and SerialConfig (#7281) -- Fix no AP on initial install (#7282) -- Fix failing downgrade (#7285) -- Add command ``SerialConfig 0..23`` or ``SerialConfig 8N1`` to select Serial Config based in PR by Luis Teixeira (#7108) -- Add command ``Sensor34 9 `` to set minimum delta to trigger JSON message by @tobox (#7188) -- Add rule var ``%topic%`` by Adrian Scillato (#5522) -- Add rule triggers ``tele-wifi1#xxx`` by Adrian Scillato (#7093) -- Add SML bus decoder syntax support for byte order by Gerhard Mutz (#7112) -- Add experimental support for stepper motor shutter control by Stefan Bode -- Add optional USE_MQTT_TLS to tasmota-minimal.bin by Bohdan Kmit (#7115) -- Add save call stack in RTC memory in case of crash, command ``Status 12`` to dump the stack by Stefan Hadinger -- Add Home Assistant force update by Frederico Leoni (#7140, #7074) -- Add Wifi Signal Strength in dBm in addition to RSSI Wifi Experience by Andreas Schultz (#7145) -- Add Yaw, Pitch and Roll support for MPU6050 by Philip Barclay (#7058) -- Add reporting of raw weight to JSON from HX711 to overcome auto-tare functionality by @tobox (#7171) -- Add Zigbee support for Xiaomi Aqara Vibration Sensor and Presence Sensor by Stefan Hadinger -- Add Shutter functions ramp up/down and MQTT reporting by Stefan Bode -- Add fallback support from version 8.x -- Add restriction if fallback firmware is incompatible with settings resulting in unreachable device -- Add support for DHT12 Temperature and Humidity sensor by Stefan Oskamp +- 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) +- Change number of ``FriendlyName``s from 4 to 8 +- Add commands ``WebButton1`` until ``WebButton16`` to support user defined GUI button text (#7166) +- 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 support for ``AdcParam`` parameters to control ADC0 Moisture formula by Federico Leoni (#7309) +- Add Zigbee coalesce sensor attributes into a single message +- Add Zigbee better support for Xiaomi Double Switch and Xiaomi Vibration sensor +- 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 5df86032f..92d7a0240 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -2,14 +2,40 @@ ## Released +### 8.1.0 20191225 + +- Release + +### 8.0.0.3 20191224 + +- Version bump due to internal Settings change + +### 8.0.0.2 20191223 + +- Changed Settings variable namings +- Change number of ``FriendlyName``s from 4 to 8 +- Add Zigbee better support for Xiaomi Double Switch and Xiaomi Vibration sensor +- Add support for ``AdcParam`` parameters to control ADC0 Moisture formula by Federico Leoni (#7309) +- Add commands ``WebButton1`` until ``WebButton16`` to support user defined GUI button text (#7166) + +### 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 + ### 7.2.0 20191221 - Release +- Change basic version string to lite (#7291) - Fix Arduino IDE compile error (#7277) - Fix restore ShutterAccuracy, MqttLog, WifiConfig, WifiPower and SerialConfig (#7281) - Fix no AP on initial install (#7282) - Fix failing downgrade (#7285) -- Change basic version string to lite (#7291) ### 7.1.2.6 20191214 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 1844dd44b..e726fdf5d 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -50,6 +50,7 @@ #define D_JSON_COUNT "Count" #define D_JSON_COUNTER "Counter" #define D_JSON_CURRENT "Current" // As in Voltage and Current +#define D_JSON_DARKNESS "Darkness" #define D_JSON_DATA "Data" #define D_JSON_DISTANCE "Distance" #define D_JSON_DNSSERVER "DNSServer" @@ -99,6 +100,7 @@ #define D_JSON_MEMORY_ERROR "Memory error" #define D_JSON_MINIMAL "minimal" #define D_JSON_MODEL "Model" +#define D_JSON_MOISTURE "Moisture" #define D_JSON_MQTT_COUNT "MqttCount" #define D_JSON_NO "No" #define D_JSON_NOISE "Noise" @@ -332,6 +334,7 @@ #define D_CMND_WEBREFRESH "WebRefresh" #define D_CMND_WEBSEND "WebSend" #define D_CMND_WEBCOLOR "WebColor" +#define D_CMND_WEBBUTTON "WebButton" #define D_CMND_WEBSENSOR "WebSensor" #define D_CMND_EMULATION "Emulation" #define D_CMND_SENDMAIL "Sendmail" @@ -457,7 +460,6 @@ #define D_CMND_LONGITUDE "Longitude" // Commands xdrv_16_tuyadimmer.ino - #define D_CMND_TUYA_MCU "TuyaMCU" #define D_CMND_TUYA_MCU_SEND_STATE "TuyaSend" #define D_JSON_TUYA_MCU_RECEIVED "TuyaReceived" @@ -468,6 +470,7 @@ #define D_CMND_ZIGBEE_STATUS "ZigbeeStatus" #define D_CMND_ZIGBEE_RESET "ZigbeeReset" #define D_JSON_ZIGBEE_CC2530 "CC2530" +#define D_CMND_ZIGBEEZNPRECEIVE "ZigbeeZNPReceive" // only for debug #define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend" #define D_JSON_ZIGBEE_STATE "ZigbeeState" #define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" @@ -483,34 +486,33 @@ #define D_CMND_ZIGBEE_SEND "ZigbeeSend" #define D_JSON_ZIGBEE_ZCL_SENT "ZigbeeZCLSent" - // Commands xdrv_25_A4988_Stepper.ino - #ifdef USE_A4988_STEPPER - #define D_CMND_MOTOR "MOTOR" - #define D_JSON_MOTOR_MOVE "doMove" - #define D_JSON_MOTOR_ROTATE "doRotate" - #define D_JSON_MOTOR_TURN "doTurn" - #define D_JSON_MOTOR_SPR "setSPR" - #define D_JSON_MOTOR_RPM "setRPM" - #define D_JSON_MOTOR_MIS "setMIS" - #endif +// Commands xdrv_25_A4988_Stepper.ino +#define D_CMND_MOTOR "MOTOR" +#define D_JSON_MOTOR_MOVE "doMove" +#define D_JSON_MOTOR_ROTATE "doRotate" +#define D_JSON_MOTOR_TURN "doTurn" +#define D_JSON_MOTOR_SPR "setSPR" +#define D_JSON_MOTOR_RPM "setRPM" +#define D_JSON_MOTOR_MIS "setMIS" - // Commands xdrv_27_Shutter.ino - #ifdef USE_SHUTTER - #define D_PRFX_SHUTTER "Shutter" - #define D_CMND_SHUTTER_OPEN "Open" - #define D_CMND_SHUTTER_CLOSE "Close" - #define D_CMND_SHUTTER_STOP "Stop" - #define D_CMND_SHUTTER_POSITION "Position" - #define D_CMND_SHUTTER_OPENTIME "OpenDuration" - #define D_CMND_SHUTTER_CLOSETIME "CloseDuration" - #define D_CMND_SHUTTER_RELAY "Relay" - #define D_CMND_SHUTTER_SETHALFWAY "SetHalfway" - #define D_CMND_SHUTTER_SETCLOSE "SetClose" - #define D_CMND_SHUTTER_INVERT "Invert" - #define D_CMND_SHUTTER_CLIBRATION "Calibration" - #define D_CMND_SHUTTER_MOTORDELAY "MotorDelay" - #define D_CMND_SHUTTER_FREQUENCY "Frequency" - #endif +// Commands xdrv_27_Shutter.ino +#define D_PRFX_SHUTTER "Shutter" +#define D_CMND_SHUTTER_OPEN "Open" +#define D_CMND_SHUTTER_CLOSE "Close" +#define D_CMND_SHUTTER_STOP "Stop" +#define D_CMND_SHUTTER_POSITION "Position" +#define D_CMND_SHUTTER_OPENTIME "OpenDuration" +#define D_CMND_SHUTTER_CLOSETIME "CloseDuration" +#define D_CMND_SHUTTER_RELAY "Relay" +#define D_CMND_SHUTTER_SETHALFWAY "SetHalfway" +#define D_CMND_SHUTTER_SETCLOSE "SetClose" +#define D_CMND_SHUTTER_INVERT "Invert" +#define D_CMND_SHUTTER_CLIBRATION "Calibration" +#define D_CMND_SHUTTER_MOTORDELAY "MotorDelay" +#define D_CMND_SHUTTER_FREQUENCY "Frequency" + +// Commands xsns_02_analog.ino +#define D_CMND_ADCPARAM "AdcParam" /********************************************************************************************/ @@ -588,6 +590,7 @@ const char JSON_SNS_TEMP[] PROGMEM = ",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"; const char JSON_SNS_TEMPHUM[] PROGMEM = ",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}"; const char JSON_SNS_ILLUMINANCE[] PROGMEM = ",\"%s\":{\"" D_JSON_ILLUMINANCE "\":%d}"; +const char JSON_SNS_MOISTURE[] PROGMEM = ",\"%s\":{\"" D_JSON_MOISTURE "\":%d}"; const char JSON_SNS_GNGPM[] PROGMEM = ",\"%s\":{\"" D_JSON_TOTAL_USAGE "\":%s,\"" D_JSON_FLOWRATE "\":%s}"; @@ -617,6 +620,8 @@ const char HTTP_SNS_CO2[] PROGMEM = "{s}%s " D_CO2 "{m}%d " D_UNIT_PARTS_PER_MIL const char HTTP_SNS_CO2EAVG[] PROGMEM = "{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; // {s} = , {m} = , {e} = const char HTTP_SNS_GALLONS[] PROGMEM = "{s}%s " D_TOTAL_USAGE "{m}%s " D_UNIT_GALLONS " {e}"; // {s} = , {m} = , {e} = const char HTTP_SNS_GPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_GALLONS_PER_MIN" {e}"; // {s} = , {m} = , {e} = +const char HTTP_SNS_MOISTURE[] PROGMEM = "{s}%s " D_MOISTURE "{m}%d %%{e}"; // {s} = , {m} = , {e} = + const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU; const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION; diff --git a/tasmota/language/bg-BG.h b/tasmota/language/bg-BG.h index 251a840e8..f2ac56402 100644 --- a/tasmota/language/bg-BG.h +++ b/tasmota/language/bg-BG.h @@ -113,6 +113,7 @@ #define D_LIGHT "Светлина" #define D_LWT "LWT" #define D_MODULE "Модул" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "неколкократно натискане" #define D_NOISE "Шум" @@ -630,6 +631,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..272e2c5ca 100644 --- a/tasmota/language/cs-CZ.h +++ b/tasmota/language/cs-CZ.h @@ -113,6 +113,7 @@ #define D_LIGHT "Světlo" #define D_LWT "LWT" #define D_MODULE "Modul" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "několikeré-stisknutí" #define D_NOISE "Hluk" @@ -630,6 +631,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..89d36103d 100644 --- a/tasmota/language/de-DE.h +++ b/tasmota/language/de-DE.h @@ -113,6 +113,7 @@ #define D_LIGHT "Licht" #define D_LWT "LWT" #define D_MODULE "Modul" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "Mehrfachdruck" #define D_NOISE "Lautstärke" @@ -630,6 +631,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..2b1e4748c 100644 --- a/tasmota/language/el-GR.h +++ b/tasmota/language/el-GR.h @@ -113,6 +113,7 @@ #define D_LIGHT "Φως" #define D_LWT "LWT" #define D_MODULE "Μονάδα" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "ανίχνευση για πολλαπλά πατήματα" #define D_NOISE "Θόρυβος" @@ -630,6 +631,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..ab7f90cf8 100644 --- a/tasmota/language/en-GB.h +++ b/tasmota/language/en-GB.h @@ -113,6 +113,7 @@ #define D_LIGHT "Light" #define D_LWT "LWT" #define D_MODULE "Module" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-press" #define D_NOISE "Noise" @@ -630,6 +631,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..e41b4cb32 100644 --- a/tasmota/language/es-ES.h +++ b/tasmota/language/es-ES.h @@ -113,6 +113,7 @@ #define D_LIGHT "Luz" #define D_LWT "LWT" #define D_MODULE "Módulo" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-press" #define D_NOISE "Ruido" @@ -627,9 +628,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..840421ff3 100644 --- a/tasmota/language/fr-FR.h +++ b/tasmota/language/fr-FR.h @@ -113,6 +113,7 @@ #define D_LIGHT "Lumière" #define D_LWT "LWT" #define D_MODULE "Module" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-pression" #define D_NOISE "Bruit" @@ -630,6 +631,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..9e26a6c5e 100644 --- a/tasmota/language/he-HE.h +++ b/tasmota/language/he-HE.h @@ -113,6 +113,7 @@ #define D_LIGHT "אור" #define D_LWT "LWT" #define D_MODULE "מודול" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "לחיצה מרובה" #define D_NOISE "רעש" @@ -630,6 +631,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..b7f33a549 100644 --- a/tasmota/language/hu-HU.h +++ b/tasmota/language/hu-HU.h @@ -113,6 +113,7 @@ #define D_LIGHT "Fény" #define D_LWT "LWT" #define D_MODULE "Modul" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "több lenyomás" #define D_NOISE "Zaj" @@ -630,6 +631,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..d3c55579a 100644 --- a/tasmota/language/it-IT.h +++ b/tasmota/language/it-IT.h @@ -113,6 +113,7 @@ #define D_LIGHT "Luce" #define D_LWT "LWT" #define D_MODULE "Modulo" +#define D_MOISTURE "Umidità" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-pressione" #define D_NOISE "Rumore" @@ -630,6 +631,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..a6c2088a1 100644 --- a/tasmota/language/ko-KO.h +++ b/tasmota/language/ko-KO.h @@ -113,6 +113,7 @@ #define D_LIGHT "밝게" #define D_LWT "LWT" #define D_MODULE "모듈" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-press" #define D_NOISE "소음" @@ -630,6 +631,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..8fb492399 100644 --- a/tasmota/language/nl-NL.h +++ b/tasmota/language/nl-NL.h @@ -113,6 +113,7 @@ #define D_LIGHT "Licht" #define D_LWT "LWT" #define D_MODULE "Module" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "meervoudig" #define D_NOISE "Lawaai" @@ -630,6 +631,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..58168516d 100644 --- a/tasmota/language/pl-PL.h +++ b/tasmota/language/pl-PL.h @@ -113,6 +113,7 @@ #define D_LIGHT "Światło" #define D_LWT "LWT" #define D_MODULE "Moduł" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "Wielokrotne naciśnięcie" #define D_NOISE "Szum" @@ -630,6 +631,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..0c9891323 100644 --- a/tasmota/language/pt-BR.h +++ b/tasmota/language/pt-BR.h @@ -113,6 +113,7 @@ #define D_LIGHT "Luz" #define D_LWT "LWT" #define D_MODULE "Módulo" +#define D_MOISTURE "Umidade" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-pressão" #define D_NOISE "Ruído" @@ -630,6 +631,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..210ef8e29 100644 --- a/tasmota/language/pt-PT.h +++ b/tasmota/language/pt-PT.h @@ -113,6 +113,7 @@ #define D_LIGHT "Luz" #define D_LWT "LWT" #define D_MODULE "Módulo" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-pressão" #define D_NOISE "Ruído" @@ -630,6 +631,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..6696e0e48 100644 --- a/tasmota/language/ru-RU.h +++ b/tasmota/language/ru-RU.h @@ -113,6 +113,7 @@ #define D_LIGHT "Свет" #define D_LWT "LWT" #define D_MODULE "Модуль" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "многократное нажатие" #define D_NOISE "Шум" @@ -630,6 +631,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..21120179a 100644 --- a/tasmota/language/sk-SK.h +++ b/tasmota/language/sk-SK.h @@ -113,6 +113,7 @@ #define D_LIGHT "Svetlo" #define D_LWT "LWT" #define D_MODULE "Modul" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-stlačenie" #define D_NOISE "Hluk" @@ -630,6 +631,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..117fb40ed 100644 --- a/tasmota/language/sv-SE.h +++ b/tasmota/language/sv-SE.h @@ -113,6 +113,7 @@ #define D_LIGHT "Ljus" #define D_LWT "LWT" #define D_MODULE "Modul" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "fler tryck" #define D_NOISE "Oväsen" @@ -630,6 +631,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..9874688da 100644 --- a/tasmota/language/tr-TR.h +++ b/tasmota/language/tr-TR.h @@ -113,6 +113,7 @@ #define D_LIGHT "Işık" #define D_LWT "LWT" #define D_MODULE "Modül" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "multi-press" #define D_NOISE "Noise" @@ -630,6 +631,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..dbf58fe35 100644 --- a/tasmota/language/uk-UK.h +++ b/tasmota/language/uk-UK.h @@ -113,6 +113,7 @@ #define D_LIGHT "Світло" #define D_LWT "LWT" #define D_MODULE "Модуль" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "Багаторазове натискання" #define D_NOISE "Шум" @@ -630,6 +631,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..7700e7e78 100644 --- a/tasmota/language/zh-CN.h +++ b/tasmota/language/zh-CN.h @@ -113,6 +113,7 @@ #define D_LIGHT "灯" #define D_LWT "LWT" #define D_MODULE "模块" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "多次按键" #define D_NOISE "嘈杂" @@ -630,6 +631,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..34bb68e3d 100644 --- a/tasmota/language/zh-TW.h +++ b/tasmota/language/zh-TW.h @@ -113,6 +113,7 @@ #define D_LIGHT "燈" #define D_LWT "LWT" #define D_MODULE "模組" +#define D_MOISTURE "Moisture" #define D_MQTT "MQTT" #define D_MULTI_PRESS "多次按鍵" #define D_NOISE "雜訊" @@ -630,6 +631,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 73a965d30..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) @@ -530,6 +532,7 @@ #define USE_ZIGBEE_PRECFGKEY_L 0x0F0D0B0907050301L // note: changing requires to re-pair all devices #define USE_ZIGBEE_PRECFGKEY_H 0x0D0C0A0806040200L // note: changing requires to re-pair all devices #define USE_ZIGBEE_PERMIT_JOIN false // don't allow joining by default + #define USE_ZIGBEE_COALESCE_ATTR_TIMER 350 // timer to coalesce attribute values (in ms) // -- Other sensors/drivers ----------------------- diff --git a/tasmota/settings.h b/tasmota/settings.h index 8b7fe2822..eddc4f04b 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -229,15 +229,9 @@ typedef struct { uint8_t dpid = 0; } TuyaFnidDpidMap; +const uint32_t settings_text_size = 699; // Settings.text_pool[size] = Settings.display_model (2D2) - Settings.text_pool (017) const uint8_t MAX_TUYA_FUNCTIONS = 16; -/* -struct SYSCFG { - unsigned long cfg_holder; // 000 Pre v6 header - unsigned long save_flag; // 004 - unsigned long version; // 008 - unsigned long bootcount; // 00C -*/ struct SYSCFG { uint16_t cfg_holder; // 000 v6 header uint16_t cfg_size; // 002 @@ -249,21 +243,19 @@ struct SYSCFG { int16_t save_data; // 014 int8_t timezone; // 016 - // Start of single char array Settings.text + // Start of char array storing all parameter strings - char ota_url[101]; // 017 - char mqtt_prefix[3][11]; // 07C + char text_pool[101]; // 017 - was ota_url[101] - size is settings_text_size - uint8_t ex_baudrate; // 09D - Free since 6.6.0.9 + char ex_mqtt_prefix[3][11]; // 07C + uint8_t ex_baudrate; // 09D uint8_t ex_seriallog_level; // 09E uint8_t ex_sta_config; // 09F uint8_t ex_sta_active; // 0A0 - - char sta_ssid[2][33]; // 0A1 - Keep together with sta_pwd as being copied as one chunck with reset 5 - char sta_pwd[2][65]; // 0E3 - Keep together with sta_ssid as being copied as one chunck with reset 5 - char hostname[33]; // 165 - char syslog_host[33]; // 186 - + char ex_sta_ssid[2][33]; // 0A1 + char ex_sta_pwd[2][65]; // 0E3 + char ex_hostname[33]; // 165 + char ex_syslog_host[33]; // 186 uint8_t ex_rule_stop; // 1A7 uint16_t ex_syslog_port; // 1A8 uint8_t ex_syslog_level; // 1AA @@ -271,30 +263,23 @@ struct SYSCFG { uint8_t ex_weblog_level; // 1AC uint8_t ex_mqtt_fingerprint[2][20]; // 1AD uint8_t ex_adc_param_type; // 1D5 - uint8_t ex_free_1d6[10]; // 1D6 - - // End of single char array of 456 chars max (phase 3) - SysBitfield4 ex_flag4; // 1E0 uint8_t ex_serial_config; // 1E4 uint8_t ex_wifi_output_power; // 1E5 uint8_t ex_shutter_accuracy; // 1E6 uint8_t ex_mqttlog_level; // 1E7 uint8_t ex_sps30_inuse_hours; // 1E8 + char ex_mqtt_host[33]; // 1E9 + uint16_t ex_mqtt_port; // 20A + char ex_mqtt_client[33]; // 20C + char ex_mqtt_user[33]; // 22D + char ex_mqtt_pwd[33]; // 24E + char ex_mqtt_topic[33]; // 26F + char ex_button_topic[33]; // 290 + char ex_mqtt_grptopic[33]; // 2B1 - char mqtt_host[33]; // 1E9 - Keep together with below as being copied as one chunck with reset 6 - - uint16_t ex_mqtt_port; // 20A - Keep together - - char mqtt_client[33]; // 20C - Keep together - char mqtt_user[33]; // 22D - Keep together - char mqtt_pwd[33]; // 24E - Keep together - char mqtt_topic[33]; // 26F - Keep together with above items as being copied as one chunck with reset 6 - char button_topic[33]; // 290 - char mqtt_grptopic[33]; // 2B1 - - // Optional end of single char array of 698 chars max (phase 5) + // End of single char array of 698 chars max uint8_t display_model; // 2D2 uint8_t display_mode; // 2D3 @@ -315,8 +300,8 @@ struct SYSCFG { uint8_t param[PARAM8_SIZE]; // 2FC SetOption32 .. SetOption49 int16_t toffset[2]; // 30E uint8_t display_font; // 312 - char state_text[4][11]; // 313 + char ex_state_text[4][11]; // 313 uint8_t ex_energy_power_delta; // 33F - Free since 6.6.0.20 uint16_t domoticz_update_timer; // 340 @@ -351,8 +336,10 @@ struct SYSCFG { uint16_t light_rotation; // 39E SysBitfield3 flag3; // 3A0 uint8_t switchmode[MAX_SWITCHES]; // 3A4 (6.0.0b - moved from 0x4CA) - char friendlyname[MAX_FRIENDLYNAMES][33]; // 3AC - char switch_topic[33]; // 430 + + char ex_friendlyname[4][33]; // 3AC + char ex_switch_topic[33]; // 430 + char serial_delimiter; // 451 uint8_t seriallog_level; // 452 uint8_t sleep; // 453 @@ -376,15 +363,21 @@ struct SYSCFG { uint8_t knx_GA_registered; // 4A5 Number of Group Address to read uint16_t light_wakeup; // 4A6 uint8_t knx_CB_registered; // 4A8 Number of Group Address to write - char web_password[33]; // 4A9 + + char ex_web_password[33]; // 4A9 + uint8_t interlock[MAX_INTERLOCKS]; // 4CA - char ntp_server[3][33]; // 4CE + + char ex_ntp_server[3][33]; // 4CE + uint8_t ina219_mode; // 531 uint16_t pulse_timer[MAX_PULSETIMERS]; // 532 uint16_t button_debounce; // 542 uint32_t ip_address[4]; // 544 unsigned long energy_kWhtotal; // 554 - char mqtt_fulltopic[100]; // 558 + + char ex_mqtt_fulltopic[100]; // 558 + SysBitfield2 flag2; // 5BC unsigned long pulse_counter[MAX_COUNTERS]; // 5C0 uint16_t pulse_counter_type; // 5D0 @@ -428,7 +421,7 @@ struct SYSCFG { unsigned long weight_calibration; // 7C4 unsigned long energy_frequency_calibration; // 7C8 also used by HX711 to save last weight uint16_t web_refresh; // 7CC - char mems[MAX_RULE_MEMS][10]; // 7CE - Used by scripter as script_pram + char script_pram[5][10]; // 7CE char rules[MAX_RULE_SETS][MAX_RULE_SIZE]; // 800 uses 512 bytes in v5.12.0m, 3 x 512 bytes in v5.14.0b @@ -452,7 +445,9 @@ struct SYSCFG { int8_t temp_comp; // E9E uint8_t weight_change; // E9F uint8_t web_color2[2][3]; // EA0 - Needs to be on integer / 3 distance from web_color - char cors_domain[33]; // EA6 + + char ex_cors_domain[33]; // EA6 + uint8_t sta_config; // EC7 uint8_t sta_active; // EC8 uint8_t rule_stop; // EC9 diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 95edacfb0..6412d3fc9 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -488,18 +488,13 @@ void UpdateQuickPowerCycle(bool update) * Config Settings.text char array support \*********************************************************************************************/ -char aws_mqtt_host[66]; -char aws_mqtt_user[1] { 0 }; - -const uint32_t settings_text_size = 699; // Settings.display_model (2D2) - Settings.ota_url (017) - uint32_t GetSettingsTextLen(void) { - char* position = Settings.ota_url; + char* position = Settings.text_pool; for (uint32_t size = 0; size < SET_MAX; size++) { while (*position++ != '\0') { } } - return position - Settings.ota_url; + return position - Settings.text_pool; } bool SettingsUpdateText(uint32_t index, const char* replace_me) @@ -513,126 +508,58 @@ bool SettingsUpdateText(uint32_t index, const char* replace_me) char replace[replace_len +1]; memcpy(replace, replace_me, sizeof(replace)); - uint32_t idx = 0; - switch (index) { - case SET_OTAURL: strlcpy(Settings.ota_url, replace, sizeof(Settings.ota_url)); break; - case SET_MQTTPREFIX3: idx++; - case SET_MQTTPREFIX2: idx++; - case SET_MQTTPREFIX1: strlcpy(Settings.mqtt_prefix[idx], replace, sizeof(Settings.mqtt_prefix[idx])); break; - case SET_STASSID2: idx++; - case SET_STASSID1: strlcpy(Settings.sta_ssid[idx], replace, sizeof(Settings.sta_ssid[idx])); break; - case SET_STAPWD2: idx++; - case SET_STAPWD1: strlcpy(Settings.sta_pwd[idx], replace, sizeof(Settings.sta_pwd[idx])); break; - case SET_HOSTNAME: strlcpy(Settings.hostname, replace, sizeof(Settings.hostname)); break; - case SET_SYSLOG_HOST: strlcpy(Settings.syslog_host, replace, sizeof(Settings.syslog_host)); break; - case SET_WEBPWD: strlcpy(Settings.web_password, replace, sizeof(Settings.web_password)); break; -#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) - case SET_MQTT_HOST: - if (strlen(replace) <= sizeof(Settings.mqtt_host)) { - strlcpy(Settings.mqtt_host, replace, sizeof(Settings.mqtt_host)); - Settings.mqtt_user[0] = 0; - } else { - // need to split in mqtt_user first then mqtt_host - strlcpy(Settings.mqtt_user, replace, sizeof(Settings.mqtt_user)); - strlcpy(Settings.mqtt_host, &replace[sizeof(Settings.mqtt_user)-1], sizeof(Settings.mqtt_host)); - } - break; - case SET_MQTT_USER: break; -#else - case SET_MQTT_HOST: strlcpy(Settings.mqtt_host, replace, sizeof(Settings.mqtt_host)); break; - case SET_MQTT_USER: strlcpy(Settings.mqtt_user, replace, sizeof(Settings.mqtt_user)); break; -#endif - case SET_MQTT_CLIENT: strlcpy(Settings.mqtt_client, replace, sizeof(Settings.mqtt_client)); break; - case SET_MQTT_PWD: strlcpy(Settings.mqtt_pwd, replace, sizeof(Settings.mqtt_pwd)); break; - case SET_MQTT_FULLTOPIC: strlcpy(Settings.mqtt_fulltopic, replace, sizeof(Settings.mqtt_fulltopic)); break; - case SET_MQTT_TOPIC: strlcpy(Settings.mqtt_topic, replace, sizeof(Settings.mqtt_topic)); break; - case SET_MQTT_BUTTON_TOPIC: strlcpy(Settings.button_topic, replace, sizeof(Settings.button_topic)); break; - case SET_MQTT_SWITCH_TOPIC: strlcpy(Settings.switch_topic, replace, sizeof(Settings.switch_topic)); break; - case SET_MQTT_GRP_TOPIC: strlcpy(Settings.mqtt_grptopic, replace, sizeof(Settings.mqtt_grptopic)); break; - case SET_STATE_TXT4: idx++; - case SET_STATE_TXT3: idx++; - case SET_STATE_TXT2: idx++; - case SET_STATE_TXT1: strlcpy(Settings.state_text[idx], replace, sizeof(Settings.state_text[idx])); break; - case SET_NTPSERVER3: idx++; - case SET_NTPSERVER2: idx++; - case SET_NTPSERVER1: strlcpy(Settings.ntp_server[idx], replace, sizeof(Settings.ntp_server[idx])); break; - case SET_MEM5: idx++; - case SET_MEM4: idx++; - case SET_MEM3: idx++; - case SET_MEM2: idx++; - case SET_MEM1: strlcpy(Settings.mems[idx], replace, sizeof(Settings.mems[idx])); break; - case SET_CORS: strlcpy(Settings.cors_domain, replace, sizeof(Settings.cors_domain)); break; - case SET_FRIENDLYNAME4: idx++; - case SET_FRIENDLYNAME3: idx++; - case SET_FRIENDLYNAME2: idx++; - case SET_FRIENDLYNAME1: strlcpy(Settings.friendlyname[idx], replace, sizeof(Settings.friendlyname[idx])); break; + uint32_t start_pos = 0; + uint32_t end_pos = 0; + char* position = Settings.text_pool; + for (uint32_t size = 0; size < SET_MAX; size++) { + while (*position++ != '\0') { } + if (1 == index) { + start_pos = position - Settings.text_pool; + } + else if (0 == index) { + end_pos = position - Settings.text_pool -1; + } + index--; } + uint32_t char_len = position - Settings.text_pool; + + uint32_t current_len = end_pos - start_pos; + int diff = replace_len - current_len; + +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TST: start %d, end %d, len %d, current %d, replace %d, diff %d"), +// start_pos, end_pos, char_len, current_len, replace_len, diff); + + int too_long = (char_len + diff) - settings_text_size; + if (too_long > 0) { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_CONFIG "Text overflow by %d char(s)"), too_long); + return false; // Replace text too long + } + + if (diff != 0) { + // Shift Settings.text up or down + memmove_P(Settings.text_pool + start_pos + replace_len, Settings.text_pool + end_pos, char_len - end_pos); + } + // Replace text + memmove_P(Settings.text_pool + start_pos, replace, replace_len); + // Fill for future use + memset(Settings.text_pool + char_len + diff, 0x00, settings_text_size - char_len - diff); + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_CONFIG "CR %d/%d"), GetSettingsTextLen(), settings_text_size); return true; } char* SettingsText(uint32_t index) { + char* position = Settings.text_pool; + if (index >= SET_MAX) { - return nullptr; // Setting not supported - internal error - } - - char* position = Settings.ota_url; - - if (Settings.version < 0x08000000) { - uint32_t idx = 0; - switch (index) { - case SET_MQTTPREFIX3: idx++; - case SET_MQTTPREFIX2: idx++; - case SET_MQTTPREFIX1: position = Settings.mqtt_prefix[idx]; break; - case SET_STASSID2: idx++; - case SET_STASSID1: position = Settings.sta_ssid[idx]; break; - case SET_STAPWD2: idx++; - case SET_STAPWD1: position = Settings.sta_pwd[idx]; break; - case SET_HOSTNAME: position = Settings.hostname; break; - case SET_SYSLOG_HOST: position = Settings.syslog_host; break; - case SET_WEBPWD: position = Settings.web_password; break; -#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) - case SET_MQTT_HOST: - snprintf_P(aws_mqtt_host, sizeof(aws_mqtt_host), PSTR("%s%s"), Settings.mqtt_user, Settings.mqtt_host); - position = aws_mqtt_host; break; - case SET_MQTT_USER: position = aws_mqtt_user; break; -#else - case SET_MQTT_HOST: position = Settings.mqtt_host; break; - case SET_MQTT_USER: position = Settings.mqtt_user; break; -#endif - case SET_MQTT_CLIENT: position = Settings.mqtt_client; break; - case SET_MQTT_PWD: position = Settings.mqtt_pwd; break; - case SET_MQTT_FULLTOPIC: position = Settings.mqtt_fulltopic; break; - case SET_MQTT_TOPIC: position = Settings.mqtt_topic; break; - case SET_MQTT_BUTTON_TOPIC: position = Settings.button_topic; break; - case SET_MQTT_SWITCH_TOPIC: position = Settings.switch_topic; break; - case SET_MQTT_GRP_TOPIC: position = Settings.mqtt_grptopic; break; - case SET_STATE_TXT4: idx++; - case SET_STATE_TXT3: idx++; - case SET_STATE_TXT2: idx++; - case SET_STATE_TXT1: position = Settings.state_text[idx]; break; - case SET_NTPSERVER3: idx++; - case SET_NTPSERVER2: idx++; - case SET_NTPSERVER1: position = Settings.ntp_server[idx]; break; - case SET_MEM5: idx++; - case SET_MEM4: idx++; - case SET_MEM3: idx++; - case SET_MEM2: idx++; - case SET_MEM1: position = Settings.mems[idx]; break; - case SET_CORS: position = Settings.cors_domain; break; - case SET_FRIENDLYNAME4: idx++; - case SET_FRIENDLYNAME3: idx++; - case SET_FRIENDLYNAME2: idx++; - case SET_FRIENDLYNAME1: position = Settings.friendlyname[idx]; break; - } - + position += settings_text_size -1; // Setting not supported - internal error - return empty string } else { for (;index > 0; index--) { while (*position++ != '\0') { } } } - return position; } @@ -1132,10 +1059,6 @@ void SettingsDefaultSet2(void) memset(&Settings.monitors, 0xFF, 20); // Enable all possible monitors, displays and sensors SettingsEnableAllI2cDrivers(); - - if (VERSION < 0x08000000) { - SettingsBackwardCompat(); - } } /********************************************************************************************/ @@ -1175,17 +1098,6 @@ void SettingsEnableAllI2cDrivers(void) Settings.i2c_drivers[2] = 0xFFFFFFFF; } -void SettingsBackwardCompat(void) -{ - Settings.ex_seriallog_level = Settings.seriallog_level; // 09E <- 452 - Settings.ex_sta_config = Settings.sta_config; // 09F <- EC7 - Settings.ex_sta_active = Settings.sta_active; // 0A0 <- EC8 - memcpy((char*)&Settings.ex_rule_stop, (char*)&Settings.rule_stop, 47); // 1A7 <- EC9 - Settings.ex_flag4 = Settings.flag4; // 1E0 <- EF8 - Settings.ex_mqtt_port = Settings.mqtt_port; // 20A <- EFC - memcpy((char*)&Settings.ex_serial_config, (char*)&Settings.serial_config, 5); // 1E4 <- EFE -} - /********************************************************************************************/ void SettingsDelta(void) @@ -1388,9 +1300,9 @@ void SettingsDelta(void) } if (Settings.version < 0x07010204) { if (Settings.flag3.ex_cors_enabled == 1) { - strlcpy(Settings.cors_domain, CORS_ENABLED_ALL, sizeof(Settings.cors_domain)); + strlcpy(Settings.ex_cors_domain, CORS_ENABLED_ALL, sizeof(Settings.ex_cors_domain)); } else { - Settings.cors_domain[0] = 0; + Settings.ex_cors_domain[0] = 0; } } if (Settings.version < 0x07010205) { @@ -1405,46 +1317,26 @@ void SettingsDelta(void) memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5); // 1E4 -> EFE } - if ((VERSION < 0x08000000) && (Settings.version >= 0x08000000)) { - SettingsUpdateText(SET_WEBPWD, SettingsText(SET_WEBPWD)); - SettingsUpdateText(SET_CORS, SettingsText(SET_CORS)); - SettingsUpdateText(SET_MQTT_FULLTOPIC, SettingsText(SET_MQTT_FULLTOPIC)); - SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, SettingsText(SET_MQTT_SWITCH_TOPIC)); - SettingsUpdateText(SET_STATE_TXT1, SettingsText(SET_STATE_TXT1)); - SettingsUpdateText(SET_STATE_TXT2, SettingsText(SET_STATE_TXT2)); - SettingsUpdateText(SET_STATE_TXT3, SettingsText(SET_STATE_TXT3)); - SettingsUpdateText(SET_STATE_TXT4, SettingsText(SET_STATE_TXT4)); - SettingsUpdateText(SET_NTPSERVER1, SettingsText(SET_NTPSERVER1)); - SettingsUpdateText(SET_NTPSERVER2, SettingsText(SET_NTPSERVER2)); - SettingsUpdateText(SET_NTPSERVER3, SettingsText(SET_NTPSERVER3)); - SettingsUpdateText(SET_MEM1, SettingsText(SET_MEM1)); - SettingsUpdateText(SET_MEM2, SettingsText(SET_MEM2)); - SettingsUpdateText(SET_MEM3, SettingsText(SET_MEM3)); - SettingsUpdateText(SET_MEM4, SettingsText(SET_MEM4)); - SettingsUpdateText(SET_MEM5, SettingsText(SET_MEM5)); - SettingsUpdateText(SET_FRIENDLYNAME1, SettingsText(SET_FRIENDLYNAME1)); - SettingsUpdateText(SET_FRIENDLYNAME2, SettingsText(SET_FRIENDLYNAME2)); - SettingsUpdateText(SET_FRIENDLYNAME3, SettingsText(SET_FRIENDLYNAME3)); - SettingsUpdateText(SET_FRIENDLYNAME4, SettingsText(SET_FRIENDLYNAME4)); - - char temp[strlen(SettingsText(SET_OTAURL)) +1]; strncpy(temp, SettingsText(SET_OTAURL), sizeof(temp)); - char temp21[strlen(SettingsText(SET_MQTTPREFIX1)) +1]; strncpy(temp21, SettingsText(SET_MQTTPREFIX1), sizeof(temp21)); - char temp22[strlen(SettingsText(SET_MQTTPREFIX2)) +1]; strncpy(temp22, SettingsText(SET_MQTTPREFIX2), sizeof(temp22)); - char temp23[strlen(SettingsText(SET_MQTTPREFIX3)) +1]; strncpy(temp23, SettingsText(SET_MQTTPREFIX3), sizeof(temp23)); - char temp31[strlen(SettingsText(SET_STASSID1)) +1]; strncpy(temp31, SettingsText(SET_STASSID1), sizeof(temp31)); - char temp32[strlen(SettingsText(SET_STASSID2)) +1]; strncpy(temp32, SettingsText(SET_STASSID2), sizeof(temp32)); - char temp41[strlen(SettingsText(SET_STAPWD1)) +1]; strncpy(temp41, SettingsText(SET_STAPWD1), sizeof(temp41)); - char temp42[strlen(SettingsText(SET_STAPWD2)) +1]; strncpy(temp42, SettingsText(SET_STAPWD2), sizeof(temp42)); - char temp5[strlen(SettingsText(SET_HOSTNAME)) +1]; strncpy(temp5, SettingsText(SET_HOSTNAME), sizeof(temp5)); - char temp6[strlen(SettingsText(SET_SYSLOG_HOST)) +1]; strncpy(temp6, SettingsText(SET_SYSLOG_HOST), sizeof(temp6)); - char temp7[strlen(SettingsText(SET_MQTT_HOST)) +1]; strncpy(temp7, SettingsText(SET_MQTT_HOST), sizeof(temp7)); - char temp8[strlen(SettingsText(SET_MQTT_CLIENT)) +1]; strncpy(temp8, SettingsText(SET_MQTT_CLIENT), sizeof(temp8)); - char temp9[strlen(SettingsText(SET_MQTT_USER)) +1]; strncpy(temp9, SettingsText(SET_MQTT_USER), sizeof(temp9)); - char temp10[strlen(SettingsText(SET_MQTT_PWD)) +1]; strncpy(temp10, SettingsText(SET_MQTT_PWD), sizeof(temp10)); - char temp11[strlen(SettingsText(SET_MQTT_TOPIC)) +1]; strncpy(temp11, SettingsText(SET_MQTT_TOPIC), sizeof(temp11)); - char temp12[strlen(SettingsText(SET_MQTT_BUTTON_TOPIC)) +1]; strncpy(temp12, SettingsText(SET_MQTT_BUTTON_TOPIC), sizeof(temp12)); - char temp13[strlen(SettingsText(SET_MQTT_GRP_TOPIC)) +1]; strncpy(temp13, SettingsText(SET_MQTT_GRP_TOPIC), sizeof(temp13)); + if (Settings.version < 0x08000000) { + char temp[strlen(Settings.text_pool) +1]; strncpy(temp, Settings.text_pool, sizeof(temp)); // Was ota_url + char temp21[strlen(Settings.ex_mqtt_prefix[0]) +1]; strncpy(temp21, Settings.ex_mqtt_prefix[0], sizeof(temp21)); + char temp22[strlen(Settings.ex_mqtt_prefix[1]) +1]; strncpy(temp22, Settings.ex_mqtt_prefix[1], sizeof(temp22)); + char temp23[strlen(Settings.ex_mqtt_prefix[2]) +1]; strncpy(temp23, Settings.ex_mqtt_prefix[2], sizeof(temp23)); + char temp31[strlen(Settings.ex_sta_ssid[0]) +1]; strncpy(temp31, Settings.ex_sta_ssid[0], sizeof(temp31)); + char temp32[strlen(Settings.ex_sta_ssid[1]) +1]; strncpy(temp32, Settings.ex_sta_ssid[1], sizeof(temp32)); + char temp41[strlen(Settings.ex_sta_pwd[0]) +1]; strncpy(temp41, Settings.ex_sta_pwd[0], sizeof(temp41)); + char temp42[strlen(Settings.ex_sta_pwd[1]) +1]; strncpy(temp42, Settings.ex_sta_pwd[1], sizeof(temp42)); + char temp5[strlen(Settings.ex_hostname) +1]; strncpy(temp5, Settings.ex_hostname, sizeof(temp5)); + char temp6[strlen(Settings.ex_syslog_host) +1]; strncpy(temp6, Settings.ex_syslog_host, sizeof(temp6)); + char temp7[strlen(Settings.ex_mqtt_host) +1]; strncpy(temp7, Settings.ex_mqtt_host, sizeof(temp7)); + char temp8[strlen(Settings.ex_mqtt_client) +1]; strncpy(temp8, Settings.ex_mqtt_client, sizeof(temp8)); + char temp9[strlen(Settings.ex_mqtt_user) +1]; strncpy(temp9, Settings.ex_mqtt_user, sizeof(temp9)); + char temp10[strlen(Settings.ex_mqtt_pwd) +1]; strncpy(temp10, Settings.ex_mqtt_pwd, sizeof(temp10)); + char temp11[strlen(Settings.ex_mqtt_topic) +1]; strncpy(temp11, Settings.ex_mqtt_topic, sizeof(temp11)); + char temp12[strlen(Settings.ex_button_topic) +1]; strncpy(temp12, Settings.ex_button_topic, sizeof(temp12)); + char temp13[strlen(Settings.ex_mqtt_grptopic) +1]; strncpy(temp13, Settings.ex_mqtt_grptopic, sizeof(temp13)); + memset(Settings.text_pool, 0x00, settings_text_size); SettingsUpdateText(SET_OTAURL, temp); SettingsUpdateText(SET_MQTTPREFIX1, temp21); SettingsUpdateText(SET_MQTTPREFIX2, temp22); @@ -1455,15 +1347,46 @@ void SettingsDelta(void) SettingsUpdateText(SET_STAPWD2, temp42); SettingsUpdateText(SET_HOSTNAME, temp5); SettingsUpdateText(SET_SYSLOG_HOST, temp6); +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) + if (!strlen(Settings.ex_mqtt_user)) { + SettingsUpdateText(SET_MQTT_HOST, temp7); + SettingsUpdateText(SET_MQTT_USER, temp9); + } else { + char aws_mqtt_host[66]; + snprintf_P(aws_mqtt_host, sizeof(aws_mqtt_host), PSTR("%s%s"), temp9, temp7); + SettingsUpdateText(SET_MQTT_HOST, aws_mqtt_host); + SettingsUpdateText(SET_MQTT_USER, ""); + } +#else SettingsUpdateText(SET_MQTT_HOST, temp7); - SettingsUpdateText(SET_MQTT_CLIENT, temp8); SettingsUpdateText(SET_MQTT_USER, temp9); +#endif + SettingsUpdateText(SET_MQTT_CLIENT, temp8); SettingsUpdateText(SET_MQTT_PWD, temp10); SettingsUpdateText(SET_MQTT_TOPIC, temp11); SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, temp12); SettingsUpdateText(SET_MQTT_GRP_TOPIC, temp13); - SettingsBackwardCompat(); + SettingsUpdateText(SET_WEBPWD, Settings.ex_web_password); + SettingsUpdateText(SET_CORS, Settings.ex_cors_domain); + SettingsUpdateText(SET_MQTT_FULLTOPIC, Settings.ex_mqtt_fulltopic); + SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, Settings.ex_switch_topic); + SettingsUpdateText(SET_STATE_TXT1, Settings.ex_state_text[0]); + SettingsUpdateText(SET_STATE_TXT2, Settings.ex_state_text[1]); + SettingsUpdateText(SET_STATE_TXT3, Settings.ex_state_text[2]); + SettingsUpdateText(SET_STATE_TXT4, Settings.ex_state_text[3]); + SettingsUpdateText(SET_NTPSERVER1, Settings.ex_ntp_server[0]); + SettingsUpdateText(SET_NTPSERVER2, Settings.ex_ntp_server[1]); + SettingsUpdateText(SET_NTPSERVER3, Settings.ex_ntp_server[2]); + SettingsUpdateText(SET_MEM1, Settings.script_pram[0]); + SettingsUpdateText(SET_MEM2, Settings.script_pram[1]); + SettingsUpdateText(SET_MEM3, Settings.script_pram[2]); + SettingsUpdateText(SET_MEM4, Settings.script_pram[3]); + SettingsUpdateText(SET_MEM5, Settings.script_pram[4]); + SettingsUpdateText(SET_FRIENDLYNAME1, Settings.ex_friendlyname[0]); + SettingsUpdateText(SET_FRIENDLYNAME2, Settings.ex_friendlyname[1]); + SettingsUpdateText(SET_FRIENDLYNAME3, Settings.ex_friendlyname[2]); + SettingsUpdateText(SET_FRIENDLYNAME4, Settings.ex_friendlyname[3]); } Settings.version = VERSION; diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index b8fbd4a7a..f6413dd57 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -326,7 +326,7 @@ void CmndStatus(void) uint32_t option = STAT; char stemp[200]; - char stemp2[100]; + char stemp2[TOPSZ]; // Workaround MQTT - TCP/IP stack queueing when SUB_PREFIX = PUB_PREFIX if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2)) && (!payload)) { option++; } // TELE @@ -1311,7 +1311,7 @@ void CmndFriendlyname(void) } else { snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), XdrvMailbox.index); } - SettingsUpdateText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1, (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data); + SettingsUpdateText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data); } ResponseCmndIdxChar(SettingsText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1)); } 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/support_rtc.ino b/tasmota/support_rtc.ino index ffaf171a3..823d0d650 100644 --- a/tasmota/support_rtc.ino +++ b/tasmota/support_rtc.ino @@ -460,7 +460,9 @@ void RtcSetTime(uint32_t epoch) if (epoch < START_VALID_TIME) { // 2016-01-01 Rtc.user_time_entry = false; ntp_force_sync = true; + sntp_init(); } else { + sntp_stop(); Rtc.user_time_entry = true; Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond } diff --git a/tasmota/support_statistics.ino b/tasmota/support_statistics.ino index b8f65bd20..1907b1edc 100644 --- a/tasmota/support_statistics.ino +++ b/tasmota/support_statistics.ino @@ -27,42 +27,7 @@ String GetStatistics(void) { char data[40]; - - if (Settings.version < 0x08000000) { - uint32_t str_len = 0; - for (uint32_t i = 0; i < 2; i++) { - str_len += strlen(Settings.sta_ssid[i]); - str_len += strlen(Settings.sta_pwd[i]); - } - for (uint32_t i = 0; i < 3; i++) { - str_len += strlen(Settings.mqtt_prefix[i]); - str_len += strlen(Settings.ntp_server[i]); - } - for (uint32_t i = 0; i < 4; i++) { - str_len += strlen(Settings.state_text[i]); - str_len += strlen(Settings.friendlyname[i]); - } - for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) { - str_len += strlen(Settings.mems[i]); - } - str_len += strlen(Settings.ota_url); - str_len += strlen(Settings.hostname); - str_len += strlen(Settings.syslog_host); - str_len += strlen(Settings.mqtt_host); - str_len += strlen(Settings.mqtt_client); - str_len += strlen(Settings.mqtt_user); - str_len += strlen(Settings.mqtt_pwd); - str_len += strlen(Settings.mqtt_topic); - str_len += strlen(Settings.button_topic); - str_len += strlen(Settings.switch_topic); - str_len += strlen(Settings.mqtt_grptopic); - str_len += strlen(Settings.web_password); - str_len += strlen(Settings.mqtt_fulltopic); - str_len += strlen(Settings.cors_domain); - snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/1151\""), 37 + str_len); // Char Usage Ratio - } else { - snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size); // Char Usage Ratio - } + snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size); // Char Usage Ratio return String(data); } diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 900ba3dd4..c649d9379 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -546,7 +546,7 @@ void MqttShowPWMState(void) void MqttShowState(void) { - char stemp1[33]; + char stemp1[TOPSZ]; ResponseAppendTime(); ResponseAppend_P(PSTR(",\"" D_JSON_UPTIME "\":\"%s\",\"UptimeSec\":%u"), GetUptime().c_str(), UpTime()); diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index c93c3ea9e..c0231d3c9 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -58,7 +58,6 @@ const uint8_t MAX_PWMS = 5; // Max number of PWM channels const uint8_t MAX_COUNTERS = 4; // Max number of counter sensors const uint8_t MAX_TIMERS = 16; // Max number of Timers const uint8_t MAX_PULSETIMERS = 8; // Max number of supported pulse timers -const uint8_t MAX_FRIENDLYNAMES = 4; // Max number of Friendly names const uint8_t MAX_DOMOTICZ_IDX = 4; // Max number of Domoticz device, key and switch indices const uint8_t MAX_DOMOTICZ_SNS_IDX = 12; // Max number of Domoticz sensors indices const uint8_t MAX_KNX_GA = 10; // Max number of KNX Group Addresses to read that can be set @@ -70,10 +69,14 @@ const uint8_t MAX_XSNS_DRIVERS = 96; // Max number of allowed sensor driv const uint8_t MAX_I2C_DRIVERS = 96; // Max number of allowed i2c drivers const uint8_t MAX_SHUTTERS = 4; // Max number of shutters const uint8_t MAX_PCF8574 = 8; // Max number of PCF8574 devices -const uint8_t MAX_RULE_MEMS = 5; // Max number of saved vars const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules +// Changes to the following MAX_ defines need to be in line with enum SettingsTextIndex +const uint8_t MAX_RULE_MEMS = 16; // Max number of saved vars +const uint8_t MAX_FRIENDLYNAMES = 8; // Max number of Friendly names +const uint8_t MAX_BUTTON_TEXT = 16; // Max number of GUI button labels + const uint8_t MAX_HUE_DEVICES = 15; // Max number of Philips Hue device per emulation const char MQTT_TOKEN_PREFIX[] PROGMEM = "%prefix%"; // To be substituted by mqtt_prefix[x] @@ -118,7 +121,7 @@ const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching t const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in (serial and http) command buffer const uint16_t FLOATSZ = 16; // Max number of characters in float result from dtostrfd (max 32) const uint16_t CMDSZ = 24; // Max number of characters in command -const uint16_t TOPSZ = 100; // Max number of characters in topic string +const uint16_t TOPSZ = 151; // Max number of characters in topic string const uint16_t LOGSZ = 700; // Max number of characters in log const uint16_t MIN_MESSZ = 893; // Min number of characters in MQTT message @@ -143,7 +146,7 @@ const uint32_t LOOP_SLEEP_DELAY = 50; // Lowest number of milliseconds to \*********************************************************************************************/ #define MAX_RULE_TIMERS 8 // Max number of rule timers (4 bytes / timer) -#define MAX_RULE_VARS 5 // Max number of rule variables (10 bytes / variable) +#define MAX_RULE_VARS 16 // Max number of rule variables (33 bytes / variable) /* // Removed from esp8266 core since 20171105 @@ -287,13 +290,9 @@ enum SettingsTextIndex { SET_OTAURL, SET_MEM1, SET_MEM2, SET_MEM3, SET_MEM4, SET_MEM5, SET_MEM6, SET_MEM7, SET_MEM8, SET_MEM9, SET_MEM10, SET_MEM11, SET_MEM12, SET_MEM13, SET_MEM14, SET_MEM15, SET_MEM16, SET_FRIENDLYNAME1, SET_FRIENDLYNAME2, SET_FRIENDLYNAME3, SET_FRIENDLYNAME4, - -// SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8, // Future extension -// SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, // Future extension -// SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8, // Future extension -// SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, // Future extension -// SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16, // Future extension - + SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8, + SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8, + SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16, SET_MAX }; enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER, diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 2f7ffcd3b..e80331366 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -161,8 +161,8 @@ StateBitfield global_state; // Global states (currently Wifi and char my_version[33]; // Composed version string char my_image[33]; // Code image and/or commit char my_hostname[33]; // Composed Wifi hostname -char mqtt_client[33]; // Composed MQTT Clientname -char mqtt_topic[33]; // Composed MQTT topic +char mqtt_client[TOPSZ]; // Composed MQTT Clientname +char mqtt_topic[TOPSZ]; // Composed MQTT topic char serial_in_buffer[INPUT_BUFFER_SIZE]; // Receive buffer char mqtt_data[MESSZ]; // MQTT publish buffer and web page ajax buffer char log_data[LOGSZ]; // Logging 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..685546b91 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 = @@ -308,6 +311,7 @@ enum UserSelectableAdc0 { ADC0_LIGHT, // Light sensor ADC0_BUTTON, // Button ADC0_BUTTON_INV, + ADC0_MOIST, // Moisture // ADC0_SWITCH, // Switch // ADC0_SWITCH_INV, ADC0_END }; @@ -323,6 +327,7 @@ const char kAdc0Names[] PROGMEM = D_SENSOR_NONE "|" D_ANALOG_INPUT "|" D_TEMPERATURE "|" D_LIGHT "|" D_SENSOR_BUTTON "|" D_SENSOR_BUTTON "i|" + D_MOISTURE "|" // D_SENSOR_SWITCH "|" D_SENSOR_SWITCH "i|" ; @@ -671,6 +676,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 +731,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 +765,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/tasmota_version.h b/tasmota/tasmota_version.h index f210112b1..70747a9a6 100644 --- a/tasmota/tasmota_version.h +++ b/tasmota/tasmota_version.h @@ -20,9 +20,9 @@ #ifndef _TASMOTA_VERSION_H_ #define _TASMOTA_VERSION_H_ -const uint32_t VERSION = 0x07020000; +const uint32_t VERSION = 0x08010000; // Lowest compatible version -const uint32_t VERSION_COMPATIBLE = 0x00000000; +const uint32_t VERSION_COMPATIBLE = 0x07010006; #endif // _TASMOTA_VERSION_H_ diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 8aece2be8..d86562bb3 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -1014,7 +1014,7 @@ void HandleRoot(void) AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU); - char stemp[10]; + char stemp[33]; WSContentStart_P(S_MAIN_MENU); #ifdef USE_SCRIPT_WEB_DISPLAY @@ -1106,14 +1106,19 @@ void HandleRoot(void) WSContentSend_P(PSTR("")); #ifdef USE_SONOFF_IFAN if (IsModuleIfan()) { - WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, D_BUTTON_TOGGLE, ""); + WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, + (strlen(SettingsText(SET_BUTTON1))) ? SettingsText(SET_BUTTON1) : D_BUTTON_TOGGLE, + ""); for (uint32_t i = 0; i < MaxFanspeed(); i++) { snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i); - WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2, stemp, ""); + WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2, + (strlen(SettingsText(SET_BUTTON2 + i))) ? SettingsText(SET_BUTTON2 + i) : stemp, + ""); } } else { #endif // USE_SONOFF_IFAN for (uint32_t idx = 1; idx <= devices_present; idx++) { + bool set_button = ((idx <= MAX_BUTTON_TEXT) && strlen(SettingsText(SET_BUTTON1 + idx -1))); #ifdef USE_SHUTTER if (Settings.flag3.shutter_mode) { // SetOption80 - Enable shutter support bool shutter_used = false; @@ -1124,13 +1129,17 @@ void HandleRoot(void) } } if (shutter_used) { - WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (idx % 2) ? "▲" : "▼" , ""); + WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, + (set_button) ? SettingsText(SET_BUTTON1 + idx -1) : (idx % 2) ? "▲" : "▼", + ""); continue; } } #endif // USE_SHUTTER snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx); - WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (devices_present < 5) ? D_BUTTON_TOGGLE : "", (devices_present > 1) ? stemp : ""); + WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, + (set_button) ? SettingsText(SET_BUTTON1 + idx -1) : (devices_present < 5) ? D_BUTTON_TOGGLE : "", + (set_button) ? "" : (devices_present > 1) ? stemp : ""); } #ifdef USE_SONOFF_IFAN } @@ -1146,7 +1155,9 @@ void HandleRoot(void) if (idx > 0) { WSContentSend_P(PSTR("")); } for (uint32_t j = 0; j < 4; j++) { idx++; - WSContentSend_P(PSTR(""), idx, idx); // &k is related to WebGetArg("k", tmp, sizeof(tmp)); + snprintf_P(stemp, sizeof(stemp), PSTR("%d"), idx); + WSContentSend_P(PSTR(""), idx, // &k is related to WebGetArg("k", tmp, sizeof(tmp)); + (strlen(SettingsText(SET_BUTTON1 + idx -1))) ? SettingsText(SET_BUTTON1 + idx -1) : stemp); } } WSContentSend_P(PSTR("")); @@ -1694,7 +1705,7 @@ void HandleWifiConfiguration(void) void WifiSaveSettings(void) { - char tmp[100]; // Max length is currently 65 + char tmp[TOPSZ]; // Max length is currently 150 WebGetArg("h", tmp, sizeof(tmp)); SettingsUpdateText(SET_HOSTNAME, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp); @@ -1757,7 +1768,7 @@ void HandleLoggingConfiguration(void) void LoggingSaveSettings(void) { - char tmp[100]; // Max length is currently 33 + char tmp[TOPSZ]; // Max length is currently 33 WebGetArg("l0", tmp, sizeof(tmp)); SetSeriallog((!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp)); @@ -1843,9 +1854,10 @@ void HandleOtherConfiguration(void) void OtherSaveSettings(void) { - char tmp[128]; + char tmp[TOPSZ]; char webindex[5]; char friendlyname[TOPSZ]; + char message[LOGSZ]; WebGetArg("wp", tmp, sizeof(tmp)); SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp); @@ -1854,15 +1866,17 @@ void OtherSaveSettings(void) WebGetArg("b2", tmp, sizeof(tmp)); Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp); #endif // USE_EMULATION - snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation); + + snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation); for (uint32_t i = 0; i < MAX_FRIENDLYNAMES; i++) { snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i); WebGetArg(webindex, tmp, sizeof(tmp)); snprintf_P(friendlyname, sizeof(friendlyname), PSTR(FRIENDLY_NAME"%d"), i +1); SettingsUpdateText(SET_FRIENDLYNAME1 +i, (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp); - snprintf_P(log_data, sizeof(log_data), PSTR("%s%s %s"), log_data, (i) ? "," : "", SettingsText(SET_FRIENDLYNAME1 +i)); + snprintf_P(message, sizeof(message), PSTR("%s%s %s"), message, (i) ? "," : "", SettingsText(SET_FRIENDLYNAME1 +i)); } - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, message); + WebGetArg("t1", tmp, sizeof(tmp)); if (strlen(tmp)) { // {"NAME":"12345678901234","GPIO":[255,255,255,255,255,255,255,255,255,255,255,255,255],"FLAG":255,"BASE":255} char svalue[128]; @@ -1890,7 +1904,7 @@ void HandleBackupConfiguration(void) WiFiClient myClient = WebServer->client(); WebServer->setContentLength(sizeof(Settings)); - char attachment[100]; + char attachment[TOPSZ]; // char friendlyname[TOPSZ]; // snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(friendlyname, SettingsText(SET_FRIENDLYNAME1)), my_version); @@ -2095,12 +2109,12 @@ void HandleUpgradeFirmwareStart(void) { if (!HttpCheckPriviledgedAccess()) { return; } - char command[128]; // OtaUrl + char command[TOPSZ + 10]; // OtaUrl AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED)); WifiConfigCounter(); - char otaurl[101]; + char otaurl[TOPSZ]; WebGetArg("o", otaurl, sizeof(otaurl)); if (strlen(otaurl)) { snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl); @@ -2637,7 +2651,7 @@ int WebSend(char *buffer) if (user) { user = strtok_r(user, ":", &password); // user = |admin|, password = |joker| if (user && password) { - char userpass[128]; + char userpass[200]; snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password); url += userpass; // url = |http://192.168.178.86/cm?user=admin&password=joker&| } @@ -2730,7 +2744,8 @@ const char kWebCommands[] PROGMEM = "|" // No prefix #ifdef USE_SENDMAIL D_CMND_SENDMAIL "|" #endif - D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|" D_CMND_WEBSENSOR "|" D_CMND_CORS; + D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|" + D_CMND_WEBSENSOR "|" D_CMND_WEBBUTTON "|" D_CMND_CORS; void (* const WebCommand[])(void) PROGMEM = { #ifdef USE_EMULATION @@ -2739,7 +2754,8 @@ void (* const WebCommand[])(void) PROGMEM = { #ifdef USE_SENDMAIL &CmndSendmail, #endif - &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, &CmndWebSensor, &CmndCors }; + &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, + &CmndWebSensor, &CmndWebButton, &CmndCors }; /*********************************************************************************************\ * Commands @@ -2860,6 +2876,24 @@ void CmndWebSensor(void) ResponseJsonEnd(); } +void CmndWebButton(void) +{ + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_BUTTON_TEXT)) { + if (!XdrvMailbox.usridx) { + mqtt_data[0] = '\0'; + for (uint32_t i = 0; i < MAX_BUTTON_TEXT; i++) { + ResponseAppend_P(PSTR("%c\"WebButton%d\":\"%s\""), (i) ? ',' : '{', i +1, SettingsText(SET_BUTTON1 +i)); + } + ResponseJsonEnd(); + } else { + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_BUTTON1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); + } + ResponseCmndIdxChar(SettingsText(SET_BUTTON1 + XdrvMailbox.index -1)); + } + } +} + void CmndCors(void) { if (XdrvMailbox.data_len > 0) { diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino index 47838e223..910a2ea6f 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt.ino @@ -32,9 +32,7 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix #if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) D_CMND_MQTTFINGERPRINT "|" #endif -#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" -#endif #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) D_CMND_TLSKEY "|" #endif @@ -46,9 +44,7 @@ void (* const MqttCommand[])(void) PROGMEM = { #if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) &CmndMqttFingerprint, #endif -#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT &CmndMqttUser, &CmndMqttPassword, -#endif #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) &CmndTlsKey, #endif @@ -640,7 +636,6 @@ void MqttReconnect(void) #endif #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), SettingsText(SET_MQTT_HOST)); - //if (MqttClient.connect(mqtt_client, nullptr, nullptr, nullptr, 0, false, nullptr)) { if (MqttClient.connect(mqtt_client, nullptr, nullptr, stopic, 1, false, mqtt_data, MQTT_CLEAN_SESSION)) { #else if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data, MQTT_CLEAN_SESSION)) { @@ -737,7 +732,6 @@ void CmndMqttFingerprint(void) } #endif -#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT void CmndMqttUser(void) { if (XdrvMailbox.data_len > 0) { @@ -757,7 +751,6 @@ void CmndMqttPassword(void) Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command); } } -#endif // USE_MQTT_AWS_IOT void CmndMqttlog(void) { @@ -1171,10 +1164,8 @@ const char HTTP_FORM_MQTT1[] PROGMEM = "

" D_PORT " (" STR(MQTT_PORT) ")

" "

" D_CLIENT " (%s)

"; const char HTTP_FORM_MQTT2[] PROGMEM = -#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password disabled with AWS IoT "

" D_USER " (" MQTT_USER ")

" "

" D_PASSWORD "

" -#endif // USE_MQTT_AWS_IOT "

" D_TOPIC " = %%topic%% (%s)

" "

" D_FULL_TOPIC " (%s)

"; @@ -1190,7 +1181,7 @@ void HandleMqttConfiguration(void) return; } - char str[33]; + char str[TOPSZ]; WSContentStart_P(S_CONFIGURE_MQTT); WSContentSendStyle(); @@ -1209,7 +1200,7 @@ void HandleMqttConfiguration(void) void MqttSaveSettings(void) { - char tmp[100]; + char tmp[TOPSZ]; char stemp[TOPSZ]; char stemp2[TOPSZ]; diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index 6148a9aad..d6e71ab08 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -262,8 +262,8 @@ struct LIGHT { uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0}; uint16_t fade_cur_10[LST_MAX]; uint16_t fade_end_10[LST_MAX]; // 10 bits resolution target channel values - uint16_t fade_counter = 0; // fade timer in ticks (50ms) - uint16_t fade_duration = 0; // duration of fade in ticks (50ms) + uint16_t fade_duration = 0; // duration of fade in milliseconds + uint32_t fade_start = 0; // fade start time in milliseconds, compared to millis() } Light; power_t LightPower(void) @@ -1574,20 +1574,25 @@ void LightAnimate(void) bool power_off = false; Light.strip_timer_counter++; - if (!Light.power) { // All channels powered off - Light.strip_timer_counter = 0; - if (!Light.fade_running) { - sleep = Settings.sleep; - } - if (Settings.light_scheme >= LS_MAX) { - power_off = true; - } - } else { + + // set sleep parameter: either settings, + // or set a maximum of PWM_MAX_SLEEP if light is on or Fade is running + if (Light.power || Light.fade_running) { if (Settings.sleep > PWM_MAX_SLEEP) { sleep = PWM_MAX_SLEEP; // set a maxumum value of 50 milliseconds to ensure that animations are smooth } else { sleep = Settings.sleep; // or keep the current sleep if it's lower than 50 } + } else { + sleep = Settings.sleep; + } + + if (!Light.power) { // All channels powered off + Light.strip_timer_counter = 0; + if (Settings.light_scheme >= LS_MAX) { + power_off = true; + } + } else { switch (Settings.light_scheme) { case LS_POWER: light_controller.calcLevels(Light.new_color); @@ -1733,27 +1738,35 @@ void LightAnimate(void) memcpy(Light.fade_end_8, cur_col, sizeof(Light.fade_start_8)); memcpy(Light.fade_end_10, cur_col_10bits, sizeof(Light.fade_start_10)); Light.fade_running = true; - Light.fade_counter = 0; Light.fade_duration = 0; // set the value to zero to force a recompute + Light.fade_start = 0; // Fade will applied immediately below } } if (Light.fade_running) { - LightApplyFade(); - // AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d - %d %d %d %d %d"), - // Light.fade_cur_8[0], Light.fade_cur_8[1], Light.fade_cur_8[2], Light.fade_cur_8[3], Light.fade_cur_8[4], - // Light.fade_cur_10[0], Light.fade_cur_10[1], Light.fade_cur_10[2], Light.fade_cur_10[3], Light.fade_cur_10[4]); + if (LightApplyFade()) { + // AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d - %d %d %d %d %d"), + // Light.fade_cur_8[0], Light.fade_cur_8[1], Light.fade_cur_8[2], Light.fade_cur_8[3], Light.fade_cur_8[4], + // Light.fade_cur_10[0], Light.fade_cur_10[1], Light.fade_cur_10[2], Light.fade_cur_10[3], Light.fade_cur_10[4]); - LightSetOutputs(Light.fade_cur_8, Light.fade_cur_10); + LightSetOutputs(Light.fade_cur_8, Light.fade_cur_10); + } } } } -void LightApplyFade(void) { +bool LightApplyFade(void) { // did the value chanegd and needs to be applied + static uint32_t last_millis = 0; + uint32_t now = millis(); + + if ((now - last_millis) <= 5) { + return false; // the value was not changed in the last 5 milliseconds, ignore + } + last_millis = now; // Check if we need to calculate the duration if (0 == Light.fade_duration) { - Light.fade_counter = 0; + Light.fade_start = now; // compute the distance between start and and color (max of distance for each channel) uint32_t distance = 0; for (uint32_t i = 0; i < Light.subtype; i++) { @@ -1764,11 +1777,11 @@ void LightApplyFade(void) { if (distance > 0) { // compute the duration of the animation // Note: Settings.light_speed is the number of half-seconds for a 100% fade, - // i.e. light_speed=1 means 1024 steps in 10 ticks (500ms) - Light.fade_duration = (distance * Settings.light_speed * 10) / 1024; + // i.e. light_speed=1 means 1024 steps in 500ms + Light.fade_duration = (distance * Settings.light_speed * 500) / 1023; if (Settings.save_data) { // Also postpone the save_data for the duration of the Fade (in seconds) - uint32_t delay_seconds = 1 + (Light.fade_duration + 19) / 20; // add one more second + uint32_t delay_seconds = 1 + (Light.fade_duration + 999) / 1000; // add one more second // AddLog_P2(LOG_LEVEL_INFO, PSTR("delay_seconds %d, save_data_counter %d"), delay_seconds, save_data_counter); if (save_data_counter < delay_seconds) { save_data_counter = delay_seconds; // pospone @@ -1776,16 +1789,18 @@ void LightApplyFade(void) { } } else { // no fade needed, we keep the duration at zero, it will fallback directly to end of fade + Light.fade_running = false; } } - Light.fade_counter++; - if (Light.fade_counter <= Light.fade_duration) { // fade not finished + uint16_t fade_current = now - Light.fade_start; // number of milliseconds since start of fade + if (fade_current <= Light.fade_duration) { // fade not finished + //Serial.printf("Fade: %d / %d - ", fade_current, Light.fade_duration); for (uint32_t i = 0; i < Light.subtype; i++) { - Light.fade_cur_8[i] = changeUIntScale(Light.fade_counter, + Light.fade_cur_8[i] = changeUIntScale(fade_current, 0, Light.fade_duration, Light.fade_start_8[i], Light.fade_end_8[i]); - Light.fade_cur_10[i] = changeUIntScale(Light.fade_counter, + Light.fade_cur_10[i] = changeUIntScale(fade_current, 0, Light.fade_duration, Light.fade_start_10[i], Light.fade_end_10[i]); } @@ -1793,7 +1808,7 @@ void LightApplyFade(void) { // stop fade //AddLop_P2(LOG_LEVEL_DEBUG, PSTR("Stop fade")); Light.fade_running = false; - Light.fade_counter = 0; + Light.fade_start = 0; Light.fade_duration = 0; // set light to target value memcpy(Light.fade_cur_8, Light.fade_end_8, sizeof(Light.fade_end_8)); @@ -1802,7 +1817,7 @@ void LightApplyFade(void) { memcpy(Light.fade_start_8, Light.fade_end_8, sizeof(Light.fade_start_8)); memcpy(Light.fade_start_10, Light.fade_end_10, sizeof(Light.fade_start_10)); } - + return true; } // On entry we take the 5 channels 8 bits entry, and we apply Power modifiers @@ -2428,6 +2443,13 @@ bool Xdrv04(uint8_t function) case FUNC_SERIAL: result = XlgtCall(FUNC_SERIAL); break; + case FUNC_LOOP: + if (Light.fade_running) { + if (LightApplyFade()) { + LightSetOutputs(Light.fade_cur_8, Light.fade_cur_10); + } + } + break; case FUNC_EVERY_50_MSECOND: LightAnimate(); break; diff --git a/tasmota/xdrv_09_timers.ino b/tasmota/xdrv_09_timers.ino index db1cde97e..a6c7b23a9 100644 --- a/tasmota/xdrv_09_timers.ino +++ b/tasmota/xdrv_09_timers.ino @@ -733,12 +733,13 @@ void HandleTimerConfiguration(void) void TimerSaveSettings(void) { char tmp[MAX_TIMERS *12]; // Need space for MAX_TIMERS x 10 digit numbers separated by a comma + char message[LOGSZ]; Timer timer; Settings.flag3.timers_enable = WebServer->hasArg("e0"); // CMND_TIMERS WebGetArg("t0", tmp, sizeof(tmp)); char *p = tmp; - snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable); // CMND_TIMERS + snprintf_P(message, sizeof(message), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable); // CMND_TIMERS for (uint32_t i = 0; i < MAX_TIMERS; i++) { timer.data = strtol(p, &p, 10); p++; // Skip comma @@ -747,9 +748,9 @@ void TimerSaveSettings(void) Settings.timer[i].data = timer.data; if (flag) TimerSetRandomWindow(i); } - snprintf_P(log_data, sizeof(log_data), PSTR("%s,0x%08X"), log_data, Settings.timer[i].data); + snprintf_P(message, sizeof(message), PSTR("%s,0x%08X"), message, Settings.timer[i].data); } - AddLog(LOG_LEVEL_DEBUG); + AddLog_P(LOG_LEVEL_DEBUG, message); } #endif // USE_TIMERS_WEB #endif // USE_WEBSERVER diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index 6e1636518..21c9de283 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -174,8 +174,8 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }}; #if (MAX_RULE_VARS>16) #error MAX_RULE_VARS is bigger than 16 #endif -#if (MAX_RULE_MEMS>5) -#error MAX_RULE_MEMS is bigger than 5 +#if (MAX_RULE_MEMS>16) +#error MAX_RULE_MEMS is bigger than 16 #endif /*******************************************************************************************/ @@ -1400,8 +1400,7 @@ bool evaluateLogicalExpression(const char * expression, int len) memcpy(expbuff, expression, len); expbuff[len] = '\0'; - //snprintf_P(log_data, sizeof(log_data), PSTR("EvalLogic: |%s|"), expbuff); - //AddLog(LOG_LEVEL_DEBUG); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EvalLogic: |%s|"), expbuff); char * pointer = expbuff; LinkedList values; LinkedList logicOperators; @@ -1527,8 +1526,7 @@ void ExecuteCommandBlock(const char * commands, int len) memcpy(cmdbuff, commands, len); cmdbuff[len] = '\0'; - //snprintf_P(log_data, sizeof(log_data), PSTR("ExecCmd: |%s|"), cmdbuff); - //AddLog(LOG_LEVEL_DEBUG); + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ExecCmd: |%s|"), cmdbuff); char oneCommand[len + 1]; //To put one command int insertPosition = 0; //When insert into backlog, we should do it by 0, 1, 2 ... char * pos = cmdbuff; diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 68df13984..6b97fa68a 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -56,7 +56,8 @@ keywords if then else endif, or, and are better readable for beginners (others m #define SCRIPT_MAXSSIZE 48 #define SCRIPT_EOL '\n' #define SCRIPT_FLOAT_PRECISION 2 -#define SCRIPT_MAXPERM (MAX_RULE_MEMS*10)-4/sizeof(float) +#define PMEM_SIZE sizeof(Settings.script_pram) +#define SCRIPT_MAXPERM (PMEM_SIZE)-4/sizeof(float) #define MAX_SCRIPT_SIZE MAX_RULE_SIZE*MAX_RULE_SETS // offsets epoch readings by 1.1.2019 00:00:00 to fit into float with second resolution @@ -1041,7 +1042,7 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE); return lp+len; } - + } else { if (fp) { if (!strncmp(vn.c_str(),"Epoch",5)) { @@ -1575,7 +1576,7 @@ chknext: case 'r': if (!strncmp(vname,"ram",3)) { - fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(MAX_RULE_MEMS*10); + fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(PMEM_SIZE); goto exit; } break; @@ -2203,8 +2204,7 @@ void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) { void toLog(const char *str) { if (!str) return; - snprintf_P(log_data, sizeof(log_data), PSTR("%s"),str); - AddLog(LOG_LEVEL_INFO); + AddLog_P(LOG_LEVEL_INFO, str); } @@ -2681,8 +2681,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { } cmd[count]=*lp++; } - //snprintf_P(log_data, sizeof(log_data), tmp); - //AddLog(LOG_LEVEL_INFO); + //AddLog_P(LOG_LEVEL_INFO, tmp); // replace vars in cmd char *tmp=cmdmem+SCRIPT_CMDMEM/2; Replace_Cmd_Vars(cmd,tmp,SCRIPT_CMDMEM/2); @@ -2694,8 +2693,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { } else { if (!sflag) { tasm_cmd_activ=1; - snprintf_P(log_data, sizeof(log_data), PSTR("Script: performs \"%s\""), tmp); - AddLog(glob_script_mem.script_loglevel&0x7f); + AddLog_P2(glob_script_mem.script_loglevel&0x7f, PSTR("Script: performs \"%s\""), tmp); } else if (sflag==2) { // allow recursive call } else { @@ -2995,7 +2993,7 @@ void ScripterEvery100ms(void) { if (fast_script==99) Run_Scripter(">F",2,0); } -//mems[MAX_RULE_MEMS] is 50 bytes in 6.5 +//mems[5] is 50 bytes in 6.5 // can hold 11 floats or floats + strings // should report overflow later void Scripter_save_pvars(void) { @@ -3007,7 +3005,7 @@ void Scripter_save_pvars(void) { if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) { uint8_t index=vtp[count].index; mlen+=sizeof(float); - if (mlen>MAX_RULE_MEMS*10) { + if (mlen>PMEM_SIZE) { vtp[count].bits.is_permanent=0; return; } @@ -3021,7 +3019,7 @@ void Scripter_save_pvars(void) { char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize); uint8_t slen=strlen(sp); mlen+=slen+1; - if (mlen>MAX_RULE_MEMS*10) { + if (mlen>PMEM_SIZE) { vtp[count].bits.is_permanent=0; return; } @@ -3544,8 +3542,7 @@ void ScriptSaveSettings(void) { if (bitRead(Settings.rule_enabled, 0)) { int16_t res=Init_Scripter(); if (res) { - snprintf_P(log_data, sizeof(log_data), PSTR("script init error: %d"),res); - AddLog(LOG_LEVEL_INFO); + AddLog_P2(LOG_LEVEL_INFO, PSTR("script init error: %d"), res); return; } Run_Scripter(">B",2,0); @@ -4754,8 +4751,8 @@ bool Xdrv10(uint8_t function) glob_script_mem.script_ram=Settings.rules[0]; glob_script_mem.script_size=MAX_SCRIPT_SIZE; glob_script_mem.flags=0; - glob_script_mem.script_pram=(uint8_t*)Settings.mems[0]; - glob_script_mem.script_pram_size=MAX_RULE_MEMS*10; + glob_script_mem.script_pram=(uint8_t*)Settings.script_pram[0]; + glob_script_mem.script_pram_size=PMEM_SIZE; #ifdef USE_BUTTON_EVENT for (uint32_t cnt=0;cnt {{ value_json['SI7021-14'].Humidity }} "\"dev_cla\":\"humidity\""; // humidity +const char HASS_DISCOVER_SENSOR_MOIST[] PROGMEM = + ",\"unit_of_meas\":\"%%\"," // % + "\"val_tpl\":\"{{value_json['%s'].Moisture}}\"," // "ANALOG":{"Moisture":78} -> {{ value_json['ANALOG'].Moisture }} + "\"dev_cla\":\"humidity\"," // humidity + "\"ic\":\"mdi:cup-water\""; // cup-water icon + const char HASS_DISCOVER_SENSOR_PRESS[] PROGMEM = ",\"unit_of_meas\":\"%s\"," // PressureUnit() setting "\"val_tpl\":\"{{value_json['%s'].%s}}\"," // "BME280":{"Temperature":19.7,"Humidity":27.8,"Pressure":990.1} -> {{ value_json['BME280'].Pressure }} @@ -473,6 +479,8 @@ void HAssAnnounceSensor(const char* sensorname, const char* subsensortype) TryResponseAppend_P(HASS_DISCOVER_SENSOR_AMPERE, sensorname, subsensortype); } else if (!strcmp_P(subsensortype, PSTR(D_JSON_ILLUMINANCE))){ TryResponseAppend_P(HASS_DISCOVER_SENSOR_ILLUMINANCE, sensorname, subsensortype); + } else if (!strcmp_P(subsensortype, PSTR(D_JSON_MOISTURE))){ + TryResponseAppend_P(HASS_DISCOVER_SENSOR_MOIST, sensorname, subsensortype); } else { if (is_sensor){ TryResponseAppend_P(PSTR(",\"unit_of_meas\":\" \"")); // " " As unit of measurement to get a value graph (not available for binary sensors) diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino index 1d13ee263..80e85d821 100644 --- a/tasmota/xdrv_20_hue.ino +++ b/tasmota/xdrv_20_hue.ino @@ -365,7 +365,6 @@ void HueLightStatus1(uint8_t device, String *response) // Any device whose friendly name start with "$" is considered hidden bool HueActive(uint8_t device) { if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; } -// return '$' != Settings.friendlyname[device-1][0]; return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1); } diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index 0c74c9d8a..f83acf21b 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -19,6 +19,8 @@ #ifdef USE_ZIGBEE +#define OCCUPANCY "Occupancy" // global define for Aqara + typedef uint64_t Z_IEEEAddress; typedef uint16_t Z_ShortAddress; diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index b017d8e2e..8bfc59b70 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -23,4 +23,24 @@ void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1); + +// Get an JSON attribute, with case insensitive key search +JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) { + // key can be in PROGMEM + if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) { + return *(JsonVariant*)nullptr; + } + + for (auto kv : json) { + const char *key = kv.key; + JsonVariant &value = kv.value; + + if (0 == strcasecmp_P(key, needle)) { + return value; + } + } + // if not found + return *(JsonVariant*)nullptr; +} + #endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index de13990c2..1ec18d3e1 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -42,6 +42,9 @@ typedef struct Z_Device { uint16_t endpoint; // endpoint to use for timer uint32_t value; // any raw value to use for the timer Z_DeviceTimer func; // function to call when timer occurs + // json buffer used for attribute reporting + DynamicJsonBuffer *json_buffer; + JsonObject *json; } Z_Device; // All devices are stored in a Vector @@ -84,6 +87,13 @@ public: void setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func); void runTimer(void); + // Append or clear attributes Json structure + void jsonClear(uint16_t shortaddr); + void jsonAppend(uint16_t shortaddr, JsonObject &values); + const JsonObject *jsonGet(uint16_t shortaddr); + const void jsonPublish(uint16_t shortaddr); // publish the json message and clear buffer + bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values); + private: std::vector _devices = {}; @@ -173,7 +183,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { std::vector(), std::vector(), 0,0,0,0, - nullptr }; + nullptr, + nullptr, nullptr }; + device.json_buffer = new DynamicJsonBuffer(); _devices.push_back(device); return _devices.back(); } @@ -394,14 +406,112 @@ void Z_Devices::runTimer(void) { uint32_t timer = device.timer; if ((timer) && (timer <= now)) { + device.timer = 0; // cancel the timer before calling, so the callback can set another timer // trigger the timer (*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value); - - device.timer = 0; // cancel the timer } } } +void Z_Devices::jsonClear(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + + device.json = nullptr; + device.json_buffer->clear(); +} + +void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) { + to.remove(key); // force remove to have metadata like LinkQuality at the end + + if (val.is()) { + String sval = val.as(); // force a copy of the String value + to.set(key, sval); + } else if (val.is()) { + JsonArray &nested_arr = to.createNestedArray(key); + CopyJsonArray(nested_arr, val.as()); + } else if (val.is()) { + JsonObject &nested_obj = to.createNestedObject(key); + CopyJsonObject(nested_obj, val.as()); + } else { + to.set(key, val); + } +} + +void CopyJsonArray(JsonArray &to, const JsonArray &arr) { + for (auto v : arr) { + if (v.is()) { + String sval = v.as(); // force a copy of the String value + to.add(sval); + } else if (v.is()) { + } else if (v.is()) { + } else { + to.add(v); + } + } +} + +void CopyJsonObject(JsonObject &to, const JsonObject &from) { + for (auto kv : from) { + String key_string = kv.key; + JsonVariant &val = kv.value; + + CopyJsonVariant(to, key_string, val); + } +} + +// does the new payload conflicts with the existing payload, i.e. values would be overwritten +bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return false; } // don't crash if not found + if (&values == nullptr) { return false; } + + if (nullptr == device.json) { + return false; // if no previous value, no conflict + } + + for (auto kv : values) { + String key_string = kv.key; + + if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality + if (device.json->containsKey(kv.key)) { + return true; // conflict! + } + } + } + return false; +} + +void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + if (&values == nullptr) { return; } + + if (nullptr == device.json) { + device.json = &(device.json_buffer->createObject()); + } + // copy all values from 'values' to 'json' + CopyJsonObject(*device.json, values); +} + +const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return nullptr; } // don't crash if not found + return device.json; +} + +const void Z_Devices::jsonPublish(uint16_t shortaddr) { + const JsonObject *json = zigbee_devices.jsonGet(shortaddr); + if (json == nullptr) { return; } // don't crash if not found + + String msg = ""; + json->printTo(msg); + zigbee_devices.jsonClear(shortaddr); + Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); +} + // Dump the internal memory of Zigbee devices // Mode = 1: simple dump of devices addresses and names diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 687ac2483..1423c53f1 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -100,6 +100,7 @@ public: return _frame_control.b.frame_type & 1; } + static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len); void parseRawAttributes(JsonObject& json, uint8_t offset = 0); void parseReadAttributes(JsonObject& json, uint8_t offset = 0); void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0); @@ -290,17 +291,20 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer bool parse_as_string = true; uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits i += (attrtype <= 0x42) ? 1 : 2; // increment pointer + if (i + len > buf.len()) { // make sure we don't get past the buffer + len = buf.len() - i; + } // check if we can safely use a string if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; } - else { - for (uint32_t j = 0; j < len; j++) { - if (0x00 == buf.get8(i+j)) { - parse_as_string = false; - break; - } - } - } + // else { + // for (uint32_t j = 0; j < len; j++) { + // if (0x00 == buf.get8(i+j)) { + // parse_as_string = false; + // break; + // } + // } + // } if (parse_as_string) { char str[len+1]; @@ -409,19 +413,28 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer return i - offset; // how much have we increased the index } +// Generate an attribute name based on cluster number, attribute, and suffix if duplicates +void ZCLFrame::generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len) { + uint32_t suffix = 1; + + snprintf_P(key, key_len, PSTR("%04X/%04X"), cluster, attr); + while (json.containsKey(key)) { + suffix++; + snprintf_P(key, key_len, PSTR("%04X/%04X+%d"), cluster, attr, suffix); // add "0008/0001+2" suffix if duplicate + } +} // First pass, parse all attributes in their native format void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { uint32_t i = offset; uint32_t len = _payload.len(); - while (len - i >= 3) { + while (len >= i + 3) { uint16_t attrid = _payload.get16(i); i += 2; char key[16]; - snprintf_P(key, sizeof(key), PSTR("%04X/%04X"), - _cluster_id, attrid); + generateAttributeName(json, _cluster_id, attrid, key, sizeof(key)); // exception for Xiaomi lumi.weather - specific field to be treated as octet and not char if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) { @@ -445,8 +458,7 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) { if (0 == status) { char key[16]; - snprintf_P(key, sizeof(key), PSTR("%04X/%04X"), - _cluster_id, attrid); + generateAttributeName(json, _cluster_id, attrid, key, sizeof(key)); i += parseSingleAttribute(json, key, _payload, i, len); } @@ -472,7 +484,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { // return value: // 0 = keep initial value // 1 = remove initial value -typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper* new_name, uint16_t cluster, uint16_t attr); +typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); typedef struct Z_AttributeConverter { uint16_t cluster; uint16_t attribute; @@ -480,8 +492,6 @@ typedef struct Z_AttributeConverter { Z_AttrConverter func; } Z_AttributeConverter; -#define OCCUPANCY "Occupancy" // global define for Aqara - // list of post-processing directives const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { 0x0000, 0x0000, "ZCLVersion", &Z_Copy }, @@ -511,6 +521,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // On/off cluster { 0x0006, 0x0000, "Power", &Z_Copy }, + { 0x0006, 0x8000, "Power", &Z_Copy }, // See 7280 // On/Off Switch Configuration cluster { 0x0007, 0x0000, "SwitchType", &Z_Copy }, @@ -750,7 +761,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { 0x0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values // Occupancy Sensing cluster - { 0x0406, 0x0000, OCCUPANCY, &Z_AqaraOccupancy }, // Occupancy (map8) + { 0x0406, 0x0000, OCCUPANCY, &Z_Copy }, // Occupancy (map8) { 0x0406, 0x0001, "OccupancySensorType", &Z_Copy }, // OccupancySensorType { 0x0406, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values @@ -776,13 +787,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { // ====================================================================== // Record Manuf -int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; zigbee_devices.setManufId(shortaddr, value.as()); return 1; } // -int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; zigbee_devices.setModelId(shortaddr, value.as()); return 1; @@ -790,38 +801,34 @@ int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& j // ====================================================================== // Remove attribute -int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { return 1; // remove original key } // Copy value as-is -int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; return 1; // remove original key } // Add pressure unit -int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = F(D_UNIT_PRESSURE); return 0; // keep original key } // Convert int to float and divide by 100 -int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = ((float)value) / 100.0f; return 1; // remove original key } // Convert int to float and divide by 10 -int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = ((float)value) / 10.0f; return 1; // remove original key } - -// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds. -// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false -const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s - +// Publish a message for `"Occupancy":0` when the timer expired int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { // send Occupancy:false message Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":{\"" OCCUPANCY "\":0}}}"), shortaddr); @@ -829,20 +836,8 @@ int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpo XdrvRulesProcess(); } -int32_t Z_AqaraOccupancy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { - json[new_name] = value; - uint32_t occupancy = value; - - if (occupancy) { - zigbee_devices.setTimer(shortaddr, OCCUPANCY_TIMEOUT, cluster, zcl->getSrcEndpoint(), 0, &Z_OccupancyCallback); - } else { - zigbee_devices.resetTimer(shortaddr); - } - return 1; // remove original key -} - // Aqara Vibration Sensor - special proprietary attributes -int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { //json[new_name] = value; switch (attr) { case 0x0055: @@ -896,7 +891,7 @@ int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje return 1; // remove original key } -int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { String hex = value; SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); uint32_t i = 0; @@ -942,11 +937,19 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { String key_string = kv.key; const char * key = key_string.c_str(); JsonVariant& value = kv.value; - // Check that format looks like "CCCC/AAAA" + // Check that format looks like "CCCC/AAAA" or "CCCC/AAAA+d" char * delimiter = strchr(key, '/'); + char * delimiter2 = strchr(key, '+'); if (delimiter) { + uint16_t attribute; + uint16_t suffix = 1; uint16_t cluster = strtoul(key, &delimiter, 16); - uint16_t attribute = strtoul(delimiter+1, nullptr, 16); + if (!delimiter2) { + attribute = strtoul(delimiter+1, nullptr, 16); + } else { + attribute = strtoul(delimiter+1, &delimiter2, 16); + suffix = strtoul(delimiter2+1, nullptr, 10); + } // Iterate on filter for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) { @@ -956,7 +959,9 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { if ((conv_cluster == cluster) && ((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) { - int32_t drop = (*converter->func)(this, shortaddr, json, key, value, (const __FlashStringHelper*) converter->name, conv_cluster, conv_attribute); + String new_name_str = converter->name; + if (suffix > 1) { new_name_str += suffix; } // append suffix number + int32_t drop = (*converter->func)(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute); if (drop) { json.remove(key); } diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index d4f3e459f..261a4ac6a 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -32,6 +32,7 @@ const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters) +const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 2f30080fa..fcfeb06a7 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -357,6 +357,56 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) { return -1; } +// 45CA +int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) { + Z_ShortAddress srcAddr = buf.get16(2); + Z_IEEEAddress ieeeAddr = buf.get64(4); + Z_ShortAddress parentNw = buf.get16(12); + + zigbee_devices.updateDevice(srcAddr, ieeeAddr); + + char hex[20]; + Uint64toHex(ieeeAddr, hex, 64); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" + "\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\"" + ",\"ParentNetwork\":\"0x%04X\"}}"), + ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw + ); + + MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED)); + XdrvRulesProcess(); + //Z_SendActiveEpReq(srcAddr); + return -1; +} + +// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds. +// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false +const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s + +void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, const JsonObject *json) { + // Read OCCUPANCY value if any + const JsonVariant &val_endpoint = getCaseInsensitive(*json, PSTR(OCCUPANCY)); + if (nullptr != &val_endpoint) { + uint32_t occupancy = strToUInt(val_endpoint); + + if (occupancy) { + zigbee_devices.setTimer(shortaddr, OCCUPANCY_TIMEOUT, cluster, endpoint, 0, &Z_OccupancyCallback); + } + } +} + + +// Publish the received values once they have been coalesced +int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { + const JsonObject *json = zigbee_devices.jsonGet(shortaddr); + if (json == nullptr) { return 0; } // don't crash if not found + // Post-provess for Aqara Presence Senson + Z_AqaraOccupancy(shortaddr, cluster, endpoint, json); + + zigbee_devices.jsonPublish(shortaddr); + return 1; +} + int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { uint16_t groupid = buf.get16(2); uint16_t clusterid = buf.get16(4); @@ -369,6 +419,8 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { uint32_t timestamp = buf.get32(13); uint8_t seqnumber = buf.get8(17); + bool defer_attributes = false; // do we defer attributes reporting to coalesce + zigbee_devices.updateLastSeen(srcaddr); ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid, srcaddr, @@ -384,13 +436,13 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { JsonObject& json1 = json_root.createNestedObject(F(D_CMND_ZIGBEE_RECEIVED)); JsonObject& json = json1.createNestedObject(shortaddr); - // TODO add name field if it is known if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { - zcl_received.parseRawAttributes(json); + zcl_received.parseRawAttributes(json); + if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) { zcl_received.parseReadAttributes(json); } else if (zcl_received.isClusterSpecificCommand()) { - zcl_received.parseClusterSpecificCommand(json); + zcl_received.parseClusterSpecificCommand(json); } String msg(""); msg.reserve(100); @@ -401,11 +453,23 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { // Add linkquality json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality; - msg = ""; - json_root.printTo(msg); - Response_P(PSTR("%s"), msg.c_str()); - MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); - XdrvRulesProcess(); + if (defer_attributes) { + // Prepare for publish + if (zigbee_devices.jsonIsConflict(srcaddr, json)) { + // there is conflicting values, force a publish of the previous message now and don't coalesce + zigbee_devices.jsonPublish(srcaddr); + } else { + zigbee_devices.jsonAppend(srcaddr, json); + zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes); + } + } else { + // Publish immediately + msg = ""; + json_root.printTo(msg); + Response_P(PSTR("%s"), msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + } return -1; } @@ -417,6 +481,7 @@ typedef struct Z_Dispatcher { // Filters for ZCL frames ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481 ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1 +ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585 ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584 @@ -424,6 +489,7 @@ ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584 const Z_Dispatcher Z_DispatchTable[] PROGMEM = { { AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage }, { AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce }, + { AREQ_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd }, { AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus }, { AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc }, { AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp }, diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 461b3af32..59cf382b0 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -31,12 +31,14 @@ TasmotaSerial *ZigbeeSerial = nullptr; const char kZigbeeCommands[] PROGMEM = "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" - D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ ; + D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE + ; void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin, &CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend, - &CmndZigbeeProbe, &CmndZigbeeRead }; + &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive + }; int32_t ZigbeeProcessInput(class SBuffer &buf) { if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message @@ -268,7 +270,7 @@ void CmndZigbeeStatus(void) { } } -void CmndZigbeeZNPSend(void) +void CmndZigbeeZNPSendOrReceive(bool send) { if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { uint8_t code; @@ -286,11 +288,26 @@ void CmndZigbeeZNPSend(void) size -= 2; codes += 2; } - ZigbeeZNPSend(buf.getBuffer(), buf.len()); + if (send) { + ZigbeeZNPSend(buf.getBuffer(), buf.len()); + } else { + ZigbeeProcessInput(buf); + } } ResponseCmndDone(); } +// For debug purposes only, simulates a message received +void CmndZigbeeZNPReceive(void) +{ + CmndZigbeeZNPSendOrReceive(false); +} + +void CmndZigbeeZNPSend(void) +{ + CmndZigbeeZNPSendOrReceive(true); +} + void ZigbeeZNPSend(const uint8_t *msg, size_t len) { if ((len < 2) || (len > 252)) { // abort, message cannot be less than 2 bytes for CMD1 and CMD2 @@ -423,25 +440,6 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) { ResponseCmndDone(); } -// Get an JSON attribute, with case insensitive key search -JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) { - // key can be in PROGMEM - if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) { - return *(JsonVariant*)nullptr; - } - - for (auto kv : json) { - const char *key = kv.key; - JsonVariant &value = kv.value; - - if (0 == strcasecmp_P(key, needle)) { - return value; - } - } - // if not found - return *(JsonVariant*)nullptr; -} - void CmndZigbeeSend(void) { // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} } // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} } diff --git a/tasmota/xdrv_29_deepsleep.ino b/tasmota/xdrv_29_deepsleep.ino index f0a8de685..69eed78f1 100644 --- a/tasmota/xdrv_29_deepsleep.ino +++ b/tasmota/xdrv_29_deepsleep.ino @@ -192,7 +192,7 @@ bool Xdrv29(uint8_t function) DeepSleepEverySecond(); break; case FUNC_AFTER_TELEPERIOD: - if (DeepSleepEnabled() && !deepsleep_flag) { + if (DeepSleepEnabled() && !deepsleep_flag && (Settings.tele_period == 10 || Settings.tele_period == 300 || UpTime() > Settings.tele_period)) { deepsleep_flag = DEEPSLEEP_START_COUNTDOWN; // Start deepsleep in 4 seconds } break; diff --git a/tasmota/xdsp_02_ssd1306.ino b/tasmota/xdsp_02_ssd1306.ino index e263901bc..0bee9d10d 100644 --- a/tasmota/xdsp_02_ssd1306.ino +++ b/tasmota/xdsp_02_ssd1306.ino @@ -124,8 +124,7 @@ void Ssd1306PrintLog(void) strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); - snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); - AddLog(LOG_LEVEL_DEBUG); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]); renderer->Updateframe(); diff --git a/tasmota/xdsp_07_sh1106.ino b/tasmota/xdsp_07_sh1106.ino index eba56a442..00babbdf9 100644 --- a/tasmota/xdsp_07_sh1106.ino +++ b/tasmota/xdsp_07_sh1106.ino @@ -119,8 +119,7 @@ void SH1106PrintLog(void) strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); - snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); - AddLog(LOG_LEVEL_DEBUG); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]); renderer->Updateframe(); diff --git a/tasmota/xdsp_09_SSD1351.ino b/tasmota/xdsp_09_SSD1351.ino index ad7e86f99..0391bf8f4 100644 --- a/tasmota/xdsp_09_SSD1351.ino +++ b/tasmota/xdsp_09_SSD1351.ino @@ -112,8 +112,7 @@ void SSD1351PrintLog(void) strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); DisplayFillScreen(last_row); - snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); - AddLog(LOG_LEVEL_DEBUG); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]); renderer->Updateframe(); diff --git a/tasmota/xsns_02_analog.ino b/tasmota/xsns_02_analog.ino index 1a4bc090b..3dd974918 100644 --- a/tasmota/xsns_02_analog.ino +++ b/tasmota/xsns_02_analog.ino @@ -66,6 +66,12 @@ void AdcInit(void) Settings.adc_param2 = ANALOG_LDR_LUX_CALC_SCALAR; Settings.adc_param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000; } + else if (ADC0_MOIST == my_adc0) { + Settings.adc_param_type = ADC0_MOIST; + Settings.adc_param1 = 0; + Settings.adc_param2 = 1023; + Settings.adc_param3 = 0; + } } } @@ -113,6 +119,20 @@ uint16_t AdcGetLux(void) return (uint16_t)ldrLux; } +uint16_t AdcGetMoist(void) +// formula for calibration: value, fromLow, fromHigh, toHigh, toLow +// Example: 632, 0, 1023, 100, 0 +// int( ( ( ( - ) / ( - ) ) * ( - ) ) + ) +// double amoist = ((Settings.adc_param2 - (double)adc) / (Settings.adc_param2 - Settings.adc_param1) * 100; +// int((((1023 - ) / ( 1023 - 0 )) * ( 100 - 0 )) + 0 ) + +{ + int adc = AdcRead(2); + double amoist = ((double)Settings.adc_param2 - (double)adc) / ((double)Settings.adc_param2 - (double)Settings.adc_param1) * 100; + //double amoist = ((1023 - (double)adc) / 1023) * 100; + return (uint16_t)amoist; +} + void AdcEverySecond(void) { if (ADC0_TEMP == my_adc0) { @@ -173,6 +193,17 @@ void AdcShow(bool json) #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_ILLUMINANCE, "", adc_light); +#endif // USE_WEBSERVER + } + } + else if (ADC0_MOIST == my_adc0) { + uint16_t adc_moist = AdcGetMoist(); + + if (json) { + ResponseAppend_P(JSON_SNS_MOISTURE, "ANALOG", adc_moist); +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_MOISTURE, "", adc_moist); #endif // USE_WEBSERVER } } @@ -182,7 +213,6 @@ void AdcShow(bool json) * Commands \*********************************************************************************************/ -#define D_CMND_ADCPARAM "AdcParam" const char kAdcCommands[] PROGMEM = "|" // No prefix D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_ADCPARAM; @@ -217,7 +247,7 @@ void CmndAdcs(void) void CmndAdcParam(void) { if (XdrvMailbox.data_len) { - if ((ADC0_TEMP == XdrvMailbox.payload) || (ADC0_LIGHT == XdrvMailbox.payload)) { + if ((ADC0_TEMP == XdrvMailbox.payload) || (ADC0_LIGHT == XdrvMailbox.payload) || (ADC0_MOIST == XdrvMailbox.payload)) { // if ((XdrvMailbox.payload == my_adc0) && ((ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0))) { if (strstr(XdrvMailbox.data, ",") != nullptr) { // Process parameter entry char sub_string[XdrvMailbox.data_len +1]; @@ -227,10 +257,13 @@ void CmndAdcParam(void) // Settings.adc_param_type = my_adc0; Settings.adc_param1 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); Settings.adc_param2 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 3), nullptr, 10); - Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000); + if (!ADC0_MOIST == XdrvMailbox.payload) { + Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000); + } } else { // Set default values based on current adc type // AdcParam 2 // AdcParam 3 + // AdcParam 6 Settings.adc_param_type = 0; AdcInit(); } @@ -246,8 +279,14 @@ void CmndAdcParam(void) } char param3[33]; dtostrfd(((double)Settings.adc_param3)/10000, precision, param3); - Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d,%s]}"), - Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2, param3); + if ((ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0)) { + Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d,%s]}"), + Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2, param3); + } + else if (ADC0_MOIST == my_adc0) { + Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d]}"), + Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2); + } } /*********************************************************************************************\ @@ -263,7 +302,7 @@ bool Xsns02(uint8_t function) result = DecodeCommand(kAdcCommands, AdcCommand); break; default: - if ((ADC0_INPUT == my_adc0) || (ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0)) { + if ((ADC0_INPUT == my_adc0) || (ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0) || (ADC0_MOIST == my_adc0)) { switch (function) { #ifdef USE_RULES case FUNC_EVERY_250_MSECOND: diff --git a/tasmota/xsns_41_max44009.ino b/tasmota/xsns_41_max44009.ino index 2cdf6b78d..e1ef99ee1 100644 --- a/tasmota/xsns_41_max44009.ino +++ b/tasmota/xsns_41_max44009.ino @@ -75,8 +75,7 @@ void Max4409Detect(void) if ((I2cValidRead8(&buffer1, max44009_address, REG_LOWER_THRESHOLD)) && (I2cValidRead8(&buffer2, max44009_address, REG_THRESHOLD_TIMER))) { - //snprintf(log_data, sizeof(log_data), "MAX44009 %x: %x, %x", max44009_address, (int)buffer1, (int)buffer2); - //AddLog(LOG_LEVEL_DEBUG_MORE); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("MAX44009 %x: %x, %x"), max44009_address, (int)buffer1, (int)buffer2); if ((0x00 == buffer1) && (0xFF == buffer2)) { diff --git a/tasmota/xsns_42_scd30.ino b/tasmota/xsns_42_scd30.ino index d3bb5cbef..12ac95cd2 100644 --- a/tasmota/xsns_42_scd30.ino +++ b/tasmota/xsns_42_scd30.ino @@ -23,6 +23,8 @@ #define XSNS_42 42 #define XI2C_29 29 // See I2CDEVICES.md +//#define SCD30_DEBUG + #define SCD30_ADDRESS 0x61 #define SCD30_MAX_MISSED_READS 3 @@ -118,24 +120,23 @@ void Scd30Update(void) scd30ErrorState = SCD30_STATE_ERROR_DATA_CRC; scd30CrcError_count++; #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld", scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"), + scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); #endif break; case ERROR_SCD30_CO2_ZERO: scd30Co2Zero_count++; #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld", scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"), + scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); #endif break; default: { scd30ErrorState = SCD30_STATE_ERROR_READ_MEAS; #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld", error, scd30Loop_count); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld"), error, scd30Loop_count); #endif return; } @@ -147,10 +148,9 @@ void Scd30Update(void) case SCD30_STATE_ERROR_DATA_CRC: { //scd30IsDataValid = false; #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); - AddLog(LOG_LEVEL_ERROR); - snprintf_P(log_data, sizeof(log_data), "SCD30: got CRC error, try again, counter: %ld", scd30Loop_count); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"), + scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: got CRC error, try again, counter: %ld"), scd30Loop_count); #endif scd30ErrorState = ERROR_SCD30_NO_ERROR; } @@ -159,17 +159,15 @@ void Scd30Update(void) case SCD30_STATE_ERROR_READ_MEAS: { //scd30IsDataValid = false; #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); - AddLog(LOG_LEVEL_ERROR); - snprintf_P(log_data, sizeof(log_data), "SCD30: not answering, sending soft reset, counter: %ld", scd30Loop_count); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"), + scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: not answering, sending soft reset, counter: %ld"), scd30Loop_count); #endif scd30Reset_count++; error = scd30.softReset(); if (error) { #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: resetting got error: 0x%lX", error); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: resetting got error: 0x%lX"), error); #endif error >>= 8; if (error == 4) { @@ -186,18 +184,16 @@ void Scd30Update(void) case SCD30_STATE_ERROR_SOFT_RESET: { //scd30IsDataValid = false; #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); - AddLog(LOG_LEVEL_ERROR); - snprintf_P(log_data, sizeof(log_data), "SCD30: clearing i2c bus"); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"), + scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count); + AddLog_P(LOG_LEVEL_ERROR, PSTR("SCD30: clearing i2c bus")); #endif i2cReset_count++; error = scd30.clearI2CBus(); if (error) { scd30ErrorState = SCD30_STATE_ERROR_I2C_RESET; #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: error clearing i2c bus: 0x%lX", error); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error clearing i2c bus: 0x%lX"), error); #endif } else { scd30ErrorState = ERROR_SCD30_NO_ERROR; @@ -342,8 +338,7 @@ bool Scd30CommandSensor() if (error) { #ifdef SCD30_DEBUG - snprintf_P(log_data, sizeof(log_data), "SCD30: error getting FW version: 0x%lX", error); - AddLog(LOG_LEVEL_ERROR); + AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error getting FW version: 0x%lX"), error); #endif serviced = false; } diff --git a/tasmota/xsns_48_chirp.ino b/tasmota/xsns_48_chirp.ino index 84fcd4f66..f761e6afc 100644 --- a/tasmota/xsns_48_chirp.ino +++ b/tasmota/xsns_48_chirp.ino @@ -397,22 +397,17 @@ void ChirpEvery100MSecond(void) } /********************************************************************************************/ + // normaly in i18n.h - -#define D_JSON_MOISTURE "Moisture" -#define D_JSON_DARKNESS "Darkness" - #ifdef USE_WEBSERVER // {s} = , {m} = , {e} = - const char HTTP_SNS_MOISTURE[] PROGMEM = "{s} " D_JSON_MOISTURE "{m}%s %{e}"; - const char HTTP_SNS_DARKNESS[] PROGMEM = "{s} " D_JSON_DARKNESS "{m}%s %{e}"; - const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}" - "{s} FW-version{m}%s {e}"; ; - const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}"; +const char HTTP_SNS_DARKNESS[] PROGMEM = "{s} " D_JSON_DARKNESS "{m}%s %%{e}"; +const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}" + "{s} FW-version{m}%s {e}"; ; +const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}"; #endif // USE_WEBSERVER - /********************************************************************************************/ void ChirpShow(bool json) @@ -420,8 +415,6 @@ void ChirpShow(bool json) for (uint32_t i = 0; i < chirp_found_sensors; i++) { if (chirp_sensor[i].version) { // convert double values to string - char str_moisture[33]; - dtostrfd(chirp_sensor[i].moisture, 0, str_moisture); char str_temperature[33]; double t_temperature = ((double) chirp_sensor[i].temperature )/10.0; dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature); @@ -434,9 +427,10 @@ void ChirpShow(bool json) else{ sprintf(str_version, "%x", chirp_sensor[i].version); } + if (json) { if(!chirp_sensor[i].explicitSleep) { - ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%s"),chirp_name, i, str_moisture); + ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%d"), chirp_name, i, chirp_sensor[i].moisture); if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature); } @@ -447,9 +441,11 @@ void ChirpShow(bool json) } #ifdef USE_DOMOTICZ if (0 == tele_period) { - DomoticzTempHumSensor(str_temperature, str_moisture); - DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); // this is not LUX!! - } + char str_moisture[33]; + dtostrfd(chirp_sensor[i].moisture, 0, str_moisture); + DomoticzTempHumSensor(str_temperature, str_moisture); + DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); // this is not LUX!! + } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { @@ -458,10 +454,10 @@ void ChirpShow(bool json) WSContentSend_PD(HTTP_SNS_CHIRPSLEEP); } else { - WSContentSend_PD(HTTP_SNS_MOISTURE, str_moisture); + WSContentSend_PD(HTTP_SNS_MOISTURE, "", chirp_sensor[i].moisture); WSContentSend_PD(HTTP_SNS_DARKNESS, str_light); - if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature - WSContentSend_PD(HTTP_SNS_TEMP, " ",str_temperature, TempUnit()); + if (chirp_sensor[i].temperature!=-1) { // this is the error code -> no temperature + WSContentSend_PD(HTTP_SNS_TEMP, "", str_temperature, TempUnit()); } } diff --git a/tasmota/xsns_53_sml.ino b/tasmota/xsns_53_sml.ino index 7aa0fd22d..672f7f826 100755 --- a/tasmota/xsns_53_sml.ino +++ b/tasmota/xsns_53_sml.ino @@ -493,19 +493,19 @@ const uint8_t meter[]= //===================================================== // median filter eliminates outliers, but uses much RAM and CPU cycles -// 672 bytes extra RAM with MAX_VARS = 16 +// 672 bytes extra RAM with SML_MAX_VARS = 16 // default compile on, but must be enabled by descriptor flag 16 // may be undefined if RAM must be saved #define USE_SML_MEDIAN_FILTER // max number of vars , may be adjusted -#ifndef MAX_VARS -#define MAX_VARS 20 +#ifndef SML_MAX_VARS +#define SML_MAX_VARS 20 #endif // max number of meters , may be adjusted #define MAX_METERS 5 -double meter_vars[MAX_VARS]; +double meter_vars[SML_MAX_VARS]; // calulate deltas #define MAX_DVARS MAX_METERS*2 double dvalues[MAX_DVARS]; @@ -540,7 +540,7 @@ uint8_t sml_desc_cnt; struct SML_MEDIAN_FILTER { double buffer[MEDIAN_SIZE]; int8_t index; -} sml_mf[MAX_VARS]; +} sml_mf[SML_MAX_VARS]; #ifndef FLT_MAX #define FLT_MAX 99999999 @@ -1326,7 +1326,7 @@ void SML_Decode(uint8_t index) { uint32_t ind; ind=atoi(mp); while (*mp>='0' && *mp<='9') mp++; - if (ind<1 || ind>MAX_VARS) ind=1; + if (ind<1 || ind>SML_MAX_VARS) ind=1; dvar=meter_vars[ind-1]; for (uint8_t p=0;p<5;p++) { if (*mp=='@') { @@ -1345,7 +1345,7 @@ void SML_Decode(uint8_t index) { } ind=atoi(mp); while (*mp>='0' && *mp<='9') mp++; - if (ind<1 || ind>MAX_VARS) ind=1; + if (ind<1 || ind>SML_MAX_VARS) ind=1; switch (opr) { case '+': if (iflg) dvar+=ind; @@ -1381,7 +1381,7 @@ void SML_Decode(uint8_t index) { while (*mp==' ') mp++; uint8_t ind=atoi(mp); while (*mp>='0' && *mp<='9') mp++; - if (ind<1 || ind>MAX_VARS) ind=1; + if (ind<1 || ind>SML_MAX_VARS) ind=1; uint32_t delay=atoi(mp)*1000; uint32_t dtime=millis()-dtimes[dindex]; if (dtime>delay) { @@ -1604,7 +1604,7 @@ void SML_Decode(uint8_t index) { } nextsect: // next section - if (vindex. +*/ + +#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","", "","","","", "","","","" ]]