Merge branch 'release'

This commit is contained in:
Theo Arends 2019-12-25 13:13:19 +01:00
commit 7138ddd80a
71 changed files with 2366 additions and 574 deletions

View File

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

View File

@ -20,7 +20,7 @@ In addition to the [release webpage](https://github.com/arendst/Tasmota/releases
## Development ## 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/) [![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) [![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** 4. Migrate to **Sonoff-Tasmota 6.x**
5. Migrate to **Tasmota 7.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 ## Support Information
<img src="https://user-images.githubusercontent.com/5904370/68332933-e6e5a600-00d7-11ea-885d-50395f7239a1.png" width=150 align="right" /> <img src="https://user-images.githubusercontent.com/5904370/68332933-e6e5a600-00d7-11ea-885d-50395f7239a1.png" width=150 align="right" />

View File

@ -2,8 +2,6 @@
# RELEASE NOTES # RELEASE NOTES
### Sonoff-Tasmota is now Tasmota
## Migration Information ## 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: 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** 4. Migrate to **Sonoff-Tasmota 6.x**
5. Migrate to **Tasmota 7.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 ## Supported Core versions
@ -49,38 +52,16 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
## Changelog ## 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 Settings text handling allowing variable length text within a total text pool of 699 characters
- Change HTTP CORS from command ``SetOption73 0/1`` to ``Cors <cors_domain>`` allowing user control of specific CORS domain by Shantur Rathore (#7066) - Change Smoother ``Fade`` using 100Hz instead of 20Hz animation (#7179)
- Change GUI Shutter button text to Up and Down Arrows based on PR by Xavier Muller (#7166) - Change number of rule ``Var``s and ``Mem``s from 5 to 16 (#4933)
- Change amount of supported DHT sensors from 3 to 4 by Xavier Muller (#7167) - Change number of ``FriendlyName``s from 4 to 8
- Change some Settings locations freeing up space for future single char allowing variable length text - Add commands ``WebButton1`` until ``WebButton16`` to support user defined GUI button text (#7166)
- Change tasmota-basic.bin and FIRMWARE_BASIC to tasmota-lite.bin and FIRMWARE_LITE - Add support for max 150 characters in most command parameter strings (#3686, #4754)
- Change basic version string to lite (#7291) - Add support for GPS as NTP server by Christian Baars and Adrian Scillato
- Fix flashing H801 led at boot by Stefan Hadinger (#7165, #649) - Add support for ``AdcParam`` parameters to control ADC0 Moisture formula by Federico Leoni (#7309)
- Fix duplicated ``Backlog`` when using Event inside a Backlog by Adrian Scillato (#7178, #7147) - Add Zigbee coalesce sensor attributes into a single message
- Fix Gui Timer when using a negative zero offset of -00:00 by Peter Ooms (#7174) - Add Zigbee better support for Xiaomi Double Switch and Xiaomi Vibration sensor
- Fix DeepSleep in case there is no wifi by Stefan Bode (#7213) - Add Deepsleep start delay based on Teleperiod if ``Teleperiod`` differs from 10 or 300
- 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 <weight code>`` 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

View File

@ -0,0 +1,39 @@
/*
* File: NTPPacket.cpp
* Description:
* NTP packet representation.
* Author: Mooneer Salem <mooneer@gmail.com>
* License: New BSD License
*/
#include "NTPPacket.h"
void NtpPacket::swapEndian()
{
reverseBytes_(&rootDelay);
reverseBytes_(&rootDispersion);
reverseBytes_(&referenceTimestampSeconds);
reverseBytes_(&referenceTimestampFraction);
reverseBytes_(&originTimestampSeconds);
reverseBytes_(&originTimestampFraction);
reverseBytes_(&receiveTimestampSeconds);
reverseBytes_(&receiveTimestampFraction);
reverseBytes_(&transmitTimestampSeconds);
reverseBytes_(&transmitTimestampFraction);
}
void NtpPacket::reverseBytes_(uint32_t *number)
{
char buf[4];
char *numberAsChar = (char*)number;
buf[0] = numberAsChar[3];
buf[1] = numberAsChar[2];
buf[2] = numberAsChar[1];
buf[3] = numberAsChar[0];
numberAsChar[0] = buf[0];
numberAsChar[1] = buf[1];
numberAsChar[2] = buf[2];
numberAsChar[3] = buf[3];
}

View File

@ -0,0 +1,75 @@
/*
* File: NTPPacket.h
* Description:
* NTP packet representation.
* Author: Mooneer Salem <mooneer@gmail.com>
* License: New BSD License
*/
#ifndef NTP_PACKET_H
#define NTP_PACKET_H
#include "Arduino.h"
/*
* Contains the data in a typical NTP packet.
*/
struct NtpPacket
{
static const int PACKET_SIZE = 48;
unsigned char leapVersionMode;
unsigned int leapIndicator() const { return leapVersionMode >> 6; }
void leapIndicator(unsigned int newValue) { leapVersionMode = (0x3F & leapVersionMode) | ((newValue & 0x03) << 6); }
unsigned int versionNumber() const { return (leapVersionMode >> 3) & 0x07; }
void versionNumber(unsigned int newValue) { leapVersionMode = (0xC7 & leapVersionMode) | ((newValue & 0x07) << 3); }
unsigned int mode() const { return (leapVersionMode & 0x07); }
void mode(unsigned int newValue) { leapVersionMode = (leapVersionMode & 0xF8) | (newValue & 0x07); }
char stratum;
char poll;
char precision;
uint32_t rootDelay;
uint32_t rootDispersion;
char referenceId[4];
uint32_t referenceTimestampSeconds;
uint32_t referenceTimestampFraction;
uint32_t originTimestampSeconds;
uint32_t originTimestampFraction;
uint32_t receiveTimestampSeconds;
uint32_t receiveTimestampFraction;
uint32_t transmitTimestampSeconds;
uint32_t transmitTimestampFraction;
/*
* Rearranges bytes in 32 bit values from big-endian (NTP protocol)
* to little-endian (Arduino/PC), or vice versa. Must be called before
* modifying the structure or sending the packet.
*/
void swapEndian();
/*
* Returns packet as a char array for transmission via network.
* WARNING: modifying the return value is unsafe.
*/
const char *packet() { return (const char*)this; }
/*
* Copies packet buffer to packet object.
*/
void populatePacket(const char *buffer)
{
memcpy(this, buffer, PACKET_SIZE);
}
private:
/*
* Reverses bytes in a number.
*/
void reverseBytes_(uint32_t *number);
};
#endif // NTP_PACKET_H

View File

@ -0,0 +1,79 @@
/*
* File: NTPServer.cpp
* Description:
* NTP server implementation.
* Author: Mooneer Salem <mooneer@gmail.com>
* License: New BSD License
*/
#include <WiFiUdp.h>
#include "NTPPacket.h"
#include "NTPServer.h"
#define NTP_PORT 123
#define NTP_TIMESTAMP_DIFF (2208988800) // 1900 to 1970 in seconds
bool NtpServer::beginListening()
{
if (timeServerPort_.begin(NTP_PORT)){
return true;
}
return false;
}
bool NtpServer::processOneRequest(uint32_t utc, uint32_t millisecs)
{
// We need the time we've received the packet in our response.
uint32_t recvSecs = utc + NTP_TIMESTAMP_DIFF;
double recvFractDouble = (double)millisecs/0.00023283064365386963; // millisec/((10^6)/(2^32))
uint32_t recvFract = (double)recvFractDouble; //TODO: really handle this!!!
bool processed = false;
int packetDataSize = timeServerPort_.parsePacket();
if (packetDataSize && packetDataSize >= NtpPacket::PACKET_SIZE)
{
// Received what is probably an NTP packet. Read it in and verify
// that it's legit.
NtpPacket packet;
timeServerPort_.read((char*)&packet, NtpPacket::PACKET_SIZE);
// TODO: verify packet.
// Populate response.
packet.swapEndian();
packet.leapIndicator(0);
packet.versionNumber(4);
packet.mode(4);
packet.stratum = 2; // I guess stratum 1 is too optimistic
packet.poll = 10; // 6-10 per RFC 5905.
packet.precision = -21; // ~0.5 microsecond precision.
packet.rootDelay = 0; //60 * (0xFFFF / 1000); // ~60 milliseconds, TBD
packet.rootDispersion = 0; //10 * (0xFFFF / 1000); // ~10 millisecond dispersion, TBD
packet.referenceId[0] = 'G';
packet.referenceId[1] = 'P';
packet.referenceId[2] = 'S';
packet.referenceId[3] = 0;
packet.referenceTimestampSeconds = utc;
packet.referenceTimestampFraction = recvFract;
packet.originTimestampSeconds = packet.transmitTimestampSeconds;
packet.originTimestampFraction = packet.transmitTimestampFraction;
packet.receiveTimestampSeconds = recvSecs;
packet.receiveTimestampFraction = recvFract;
// ...and the transmit time.
// timeSource_.now(&packet.transmitTimestampSeconds, &packet.transmitTimestampFraction);
// Now transmit the response to the client.
packet.swapEndian();
timeServerPort_.beginPacket(timeServerPort_.remoteIP(), timeServerPort_.remotePort());
for (int count = 0; count < NtpPacket::PACKET_SIZE; count++)
{
timeServerPort_.write(packet.packet()[count]);
}
timeServerPort_.endPacket();
processed = true;
}
return processed;
}

View File

@ -0,0 +1,35 @@
/*
* File: NTPServer.h
* Description:
* NTP server implementation.
* Author: Mooneer Salem <mooneer@gmail.com>
* License: New BSD License
*/
#ifndef NTP_SERVER_H
#define NTP_SERVER_H
class NtpServer
{
public:
NtpServer(WiFiUDP Port)
{
timeServerPort_=Port;
}
/*
* Begins listening for NTP requests.
*/
bool beginListening(void);
/*
* Processes a single NTP request.
*/
bool processOneRequest(uint32_t utc, uint32_t millisecs);
private:
WiFiUDP timeServerPort_;
};
#endif // NTP_SERVER_H

View File

@ -2,14 +2,40 @@
## Released ## 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 ### 7.2.0 20191221
- Release - Release
- Change basic version string to lite (#7291)
- Fix Arduino IDE compile error (#7277) - Fix Arduino IDE compile error (#7277)
- Fix restore ShutterAccuracy, MqttLog, WifiConfig, WifiPower and SerialConfig (#7281) - Fix restore ShutterAccuracy, MqttLog, WifiConfig, WifiPower and SerialConfig (#7281)
- Fix no AP on initial install (#7282) - Fix no AP on initial install (#7282)
- Fix failing downgrade (#7285) - Fix failing downgrade (#7285)
- Change basic version string to lite (#7291)
### 7.1.2.6 20191214 ### 7.1.2.6 20191214

View File

@ -50,6 +50,7 @@
#define D_JSON_COUNT "Count" #define D_JSON_COUNT "Count"
#define D_JSON_COUNTER "Counter" #define D_JSON_COUNTER "Counter"
#define D_JSON_CURRENT "Current" // As in Voltage and Current #define D_JSON_CURRENT "Current" // As in Voltage and Current
#define D_JSON_DARKNESS "Darkness"
#define D_JSON_DATA "Data" #define D_JSON_DATA "Data"
#define D_JSON_DISTANCE "Distance" #define D_JSON_DISTANCE "Distance"
#define D_JSON_DNSSERVER "DNSServer" #define D_JSON_DNSSERVER "DNSServer"
@ -99,6 +100,7 @@
#define D_JSON_MEMORY_ERROR "Memory error" #define D_JSON_MEMORY_ERROR "Memory error"
#define D_JSON_MINIMAL "minimal" #define D_JSON_MINIMAL "minimal"
#define D_JSON_MODEL "Model" #define D_JSON_MODEL "Model"
#define D_JSON_MOISTURE "Moisture"
#define D_JSON_MQTT_COUNT "MqttCount" #define D_JSON_MQTT_COUNT "MqttCount"
#define D_JSON_NO "No" #define D_JSON_NO "No"
#define D_JSON_NOISE "Noise" #define D_JSON_NOISE "Noise"
@ -332,6 +334,7 @@
#define D_CMND_WEBREFRESH "WebRefresh" #define D_CMND_WEBREFRESH "WebRefresh"
#define D_CMND_WEBSEND "WebSend" #define D_CMND_WEBSEND "WebSend"
#define D_CMND_WEBCOLOR "WebColor" #define D_CMND_WEBCOLOR "WebColor"
#define D_CMND_WEBBUTTON "WebButton"
#define D_CMND_WEBSENSOR "WebSensor" #define D_CMND_WEBSENSOR "WebSensor"
#define D_CMND_EMULATION "Emulation" #define D_CMND_EMULATION "Emulation"
#define D_CMND_SENDMAIL "Sendmail" #define D_CMND_SENDMAIL "Sendmail"
@ -457,7 +460,6 @@
#define D_CMND_LONGITUDE "Longitude" #define D_CMND_LONGITUDE "Longitude"
// Commands xdrv_16_tuyadimmer.ino // Commands xdrv_16_tuyadimmer.ino
#define D_CMND_TUYA_MCU "TuyaMCU" #define D_CMND_TUYA_MCU "TuyaMCU"
#define D_CMND_TUYA_MCU_SEND_STATE "TuyaSend" #define D_CMND_TUYA_MCU_SEND_STATE "TuyaSend"
#define D_JSON_TUYA_MCU_RECEIVED "TuyaReceived" #define D_JSON_TUYA_MCU_RECEIVED "TuyaReceived"
@ -468,6 +470,7 @@
#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus" #define D_CMND_ZIGBEE_STATUS "ZigbeeStatus"
#define D_CMND_ZIGBEE_RESET "ZigbeeReset" #define D_CMND_ZIGBEE_RESET "ZigbeeReset"
#define D_JSON_ZIGBEE_CC2530 "CC2530" #define D_JSON_ZIGBEE_CC2530 "CC2530"
#define D_CMND_ZIGBEEZNPRECEIVE "ZigbeeZNPReceive" // only for debug
#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend" #define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend"
#define D_JSON_ZIGBEE_STATE "ZigbeeState" #define D_JSON_ZIGBEE_STATE "ZigbeeState"
#define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" #define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived"
@ -483,34 +486,33 @@
#define D_CMND_ZIGBEE_SEND "ZigbeeSend" #define D_CMND_ZIGBEE_SEND "ZigbeeSend"
#define D_JSON_ZIGBEE_ZCL_SENT "ZigbeeZCLSent" #define D_JSON_ZIGBEE_ZCL_SENT "ZigbeeZCLSent"
// Commands xdrv_25_A4988_Stepper.ino // Commands xdrv_25_A4988_Stepper.ino
#ifdef USE_A4988_STEPPER #define D_CMND_MOTOR "MOTOR"
#define D_CMND_MOTOR "MOTOR" #define D_JSON_MOTOR_MOVE "doMove"
#define D_JSON_MOTOR_MOVE "doMove" #define D_JSON_MOTOR_ROTATE "doRotate"
#define D_JSON_MOTOR_ROTATE "doRotate" #define D_JSON_MOTOR_TURN "doTurn"
#define D_JSON_MOTOR_TURN "doTurn" #define D_JSON_MOTOR_SPR "setSPR"
#define D_JSON_MOTOR_SPR "setSPR" #define D_JSON_MOTOR_RPM "setRPM"
#define D_JSON_MOTOR_RPM "setRPM" #define D_JSON_MOTOR_MIS "setMIS"
#define D_JSON_MOTOR_MIS "setMIS"
#endif
// Commands xdrv_27_Shutter.ino // Commands xdrv_27_Shutter.ino
#ifdef USE_SHUTTER #define D_PRFX_SHUTTER "Shutter"
#define D_PRFX_SHUTTER "Shutter" #define D_CMND_SHUTTER_OPEN "Open"
#define D_CMND_SHUTTER_OPEN "Open" #define D_CMND_SHUTTER_CLOSE "Close"
#define D_CMND_SHUTTER_CLOSE "Close" #define D_CMND_SHUTTER_STOP "Stop"
#define D_CMND_SHUTTER_STOP "Stop" #define D_CMND_SHUTTER_POSITION "Position"
#define D_CMND_SHUTTER_POSITION "Position" #define D_CMND_SHUTTER_OPENTIME "OpenDuration"
#define D_CMND_SHUTTER_OPENTIME "OpenDuration" #define D_CMND_SHUTTER_CLOSETIME "CloseDuration"
#define D_CMND_SHUTTER_CLOSETIME "CloseDuration" #define D_CMND_SHUTTER_RELAY "Relay"
#define D_CMND_SHUTTER_RELAY "Relay" #define D_CMND_SHUTTER_SETHALFWAY "SetHalfway"
#define D_CMND_SHUTTER_SETHALFWAY "SetHalfway" #define D_CMND_SHUTTER_SETCLOSE "SetClose"
#define D_CMND_SHUTTER_SETCLOSE "SetClose" #define D_CMND_SHUTTER_INVERT "Invert"
#define D_CMND_SHUTTER_INVERT "Invert" #define D_CMND_SHUTTER_CLIBRATION "Calibration"
#define D_CMND_SHUTTER_CLIBRATION "Calibration" #define D_CMND_SHUTTER_MOTORDELAY "MotorDelay"
#define D_CMND_SHUTTER_MOTORDELAY "MotorDelay" #define D_CMND_SHUTTER_FREQUENCY "Frequency"
#define D_CMND_SHUTTER_FREQUENCY "Frequency"
#endif // 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_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_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}"; 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} = <tr><th>, {m} = </th><td>, {e} = </td></tr> const char HTTP_SNS_CO2EAVG[] PROGMEM = "{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
const char HTTP_SNS_GALLONS[] PROGMEM = "{s}%s " D_TOTAL_USAGE "{m}%s " D_UNIT_GALLONS " {e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr> const char HTTP_SNS_GALLONS[] PROGMEM = "{s}%s " D_TOTAL_USAGE "{m}%s " D_UNIT_GALLONS " {e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
const char HTTP_SNS_GPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_GALLONS_PER_MIN" {e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr> const char HTTP_SNS_GPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_GALLONS_PER_MIN" {e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
const char HTTP_SNS_MOISTURE[] PROGMEM = "{s}%s " D_MOISTURE "{m}%d %%{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU; const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU;
const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION; const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION;

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Светлина" #define D_LIGHT "Светлина"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Модул" #define D_MODULE "Модул"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "неколкократно натискане" #define D_MULTI_PRESS "неколкократно натискане"
#define D_NOISE "Шум" #define D_NOISE "Шум"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Světlo" #define D_LIGHT "Světlo"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Modul" #define D_MODULE "Modul"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "několikeré-stisknutí" #define D_MULTI_PRESS "několikeré-stisknutí"
#define D_NOISE "Hluk" #define D_NOISE "Hluk"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Licht" #define D_LIGHT "Licht"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Modul" #define D_MODULE "Modul"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "Mehrfachdruck" #define D_MULTI_PRESS "Mehrfachdruck"
#define D_NOISE "Lautstärke" #define D_NOISE "Lautstärke"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Φως" #define D_LIGHT "Φως"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Μονάδα" #define D_MODULE "Μονάδα"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "ανίχνευση για πολλαπλά πατήματα" #define D_MULTI_PRESS "ανίχνευση για πολλαπλά πατήματα"
#define D_NOISE "Θόρυβος" #define D_NOISE "Θόρυβος"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Light" #define D_LIGHT "Light"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Module" #define D_MODULE "Module"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-press" #define D_MULTI_PRESS "multi-press"
#define D_NOISE "Noise" #define D_NOISE "Noise"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Luz" #define D_LIGHT "Luz"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Módulo" #define D_MODULE "Módulo"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-press" #define D_MULTI_PRESS "multi-press"
#define D_NOISE "Ruido" #define D_NOISE "Ruido"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Lumière" #define D_LIGHT "Lumière"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Module" #define D_MODULE "Module"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-pression" #define D_MULTI_PRESS "multi-pression"
#define D_NOISE "Bruit" #define D_NOISE "Bruit"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "אור" #define D_LIGHT "אור"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "מודול" #define D_MODULE "מודול"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "לחיצה מרובה" #define D_MULTI_PRESS "לחיצה מרובה"
#define D_NOISE "רעש" #define D_NOISE "רעש"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Fény" #define D_LIGHT "Fény"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Modul" #define D_MODULE "Modul"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "több lenyomás" #define D_MULTI_PRESS "több lenyomás"
#define D_NOISE "Zaj" #define D_NOISE "Zaj"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Luce" #define D_LIGHT "Luce"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Modulo" #define D_MODULE "Modulo"
#define D_MOISTURE "Umidità"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-pressione" #define D_MULTI_PRESS "multi-pressione"
#define D_NOISE "Rumore" #define D_NOISE "Rumore"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "밝게" #define D_LIGHT "밝게"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "모듈" #define D_MODULE "모듈"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-press" #define D_MULTI_PRESS "multi-press"
#define D_NOISE "소음" #define D_NOISE "소음"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Licht" #define D_LIGHT "Licht"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Module" #define D_MODULE "Module"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "meervoudig" #define D_MULTI_PRESS "meervoudig"
#define D_NOISE "Lawaai" #define D_NOISE "Lawaai"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Światło" #define D_LIGHT "Światło"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Moduł" #define D_MODULE "Moduł"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "Wielokrotne naciśnięcie" #define D_MULTI_PRESS "Wielokrotne naciśnięcie"
#define D_NOISE "Szum" #define D_NOISE "Szum"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Luz" #define D_LIGHT "Luz"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Módulo" #define D_MODULE "Módulo"
#define D_MOISTURE "Umidade"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-pressão" #define D_MULTI_PRESS "multi-pressão"
#define D_NOISE "Ruído" #define D_NOISE "Ruído"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Luz" #define D_LIGHT "Luz"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Módulo" #define D_MODULE "Módulo"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-pressão" #define D_MULTI_PRESS "multi-pressão"
#define D_NOISE "Ruído" #define D_NOISE "Ruído"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Свет" #define D_LIGHT "Свет"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Модуль" #define D_MODULE "Модуль"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "многократное нажатие" #define D_MULTI_PRESS "многократное нажатие"
#define D_NOISE "Шум" #define D_NOISE "Шум"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "А" #define D_UNIT_AMPERE "А"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Svetlo" #define D_LIGHT "Svetlo"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Modul" #define D_MODULE "Modul"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-stlačenie" #define D_MULTI_PRESS "multi-stlačenie"
#define D_NOISE "Hluk" #define D_NOISE "Hluk"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Ljus" #define D_LIGHT "Ljus"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Modul" #define D_MODULE "Modul"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "fler tryck" #define D_MULTI_PRESS "fler tryck"
#define D_NOISE "Oväsen" #define D_NOISE "Oväsen"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Işık" #define D_LIGHT "Işık"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Modül" #define D_MODULE "Modül"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "multi-press" #define D_MULTI_PRESS "multi-press"
#define D_NOISE "Noise" #define D_NOISE "Noise"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "A" #define D_UNIT_AMPERE "A"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "Світло" #define D_LIGHT "Світло"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "Модуль" #define D_MODULE "Модуль"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "Багаторазове натискання" #define D_MULTI_PRESS "Багаторазове натискання"
#define D_NOISE "Шум" #define D_NOISE "Шум"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "А" #define D_UNIT_AMPERE "А"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "灯" #define D_LIGHT "灯"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "模块" #define D_MODULE "模块"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "多次按键" #define D_MULTI_PRESS "多次按键"
#define D_NOISE "嘈杂" #define D_NOISE "嘈杂"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "安" #define D_UNIT_AMPERE "安"

View File

@ -113,6 +113,7 @@
#define D_LIGHT "燈" #define D_LIGHT "燈"
#define D_LWT "LWT" #define D_LWT "LWT"
#define D_MODULE "模組" #define D_MODULE "模組"
#define D_MOISTURE "Moisture"
#define D_MQTT "MQTT" #define D_MQTT "MQTT"
#define D_MULTI_PRESS "多次按鍵" #define D_MULTI_PRESS "多次按鍵"
#define D_NOISE "雜訊" #define D_NOISE "雜訊"
@ -630,6 +631,8 @@
#define D_SENSOR_SLAVE_TX "Slave TX" #define D_SENSOR_SLAVE_TX "Slave TX"
#define D_SENSOR_SLAVE_RX "Slave RX" #define D_SENSOR_SLAVE_RX "Slave RX"
#define D_SENSOR_SLAVE_RESET "Slave RST" #define D_SENSOR_SLAVE_RESET "Slave RST"
#define D_SENSOR_GPS_RX "GPS RX"
#define D_SENSOR_GPS_TX "GPS TX"
// Units // Units
#define D_UNIT_AMPERE "安" #define D_UNIT_AMPERE "安"

View File

@ -471,6 +471,8 @@
// #define USE_PN532_DATA_RAW // Allow DATA block to be used by non-alpha-numberic data (+ 80 bytes code, 48 bytes ram) // #define USE_PN532_DATA_RAW // Allow DATA block to be used by non-alpha-numberic data (+ 80 bytes code, 48 bytes ram)
//#define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8) //#define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8)
//#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) //#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
//#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
// #define USE_FLOG // Add support for GPS logging in OTA's Flash (Experimental) (+ 2.9kb flash, +8 bytes RAM)
// -- Power monitoring sensors -------------------- // -- Power monitoring sensors --------------------
#define USE_ENERGY_MARGIN_DETECTION // Add support for Energy Margin detection (+1k6 code) #define USE_ENERGY_MARGIN_DETECTION // Add support for Energy Margin detection (+1k6 code)
@ -530,6 +532,7 @@
#define USE_ZIGBEE_PRECFGKEY_L 0x0F0D0B0907050301L // note: changing requires to re-pair all devices #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_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_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 ----------------------- // -- Other sensors/drivers -----------------------

View File

@ -229,15 +229,9 @@ typedef struct {
uint8_t dpid = 0; uint8_t dpid = 0;
} TuyaFnidDpidMap; } 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; 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 { struct SYSCFG {
uint16_t cfg_holder; // 000 v6 header uint16_t cfg_holder; // 000 v6 header
uint16_t cfg_size; // 002 uint16_t cfg_size; // 002
@ -249,21 +243,19 @@ struct SYSCFG {
int16_t save_data; // 014 int16_t save_data; // 014
int8_t timezone; // 016 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 text_pool[101]; // 017 - was ota_url[101] - size is settings_text_size
char mqtt_prefix[3][11]; // 07C
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_seriallog_level; // 09E
uint8_t ex_sta_config; // 09F uint8_t ex_sta_config; // 09F
uint8_t ex_sta_active; // 0A0 uint8_t ex_sta_active; // 0A0
char ex_sta_ssid[2][33]; // 0A1
char sta_ssid[2][33]; // 0A1 - Keep together with sta_pwd as being copied as one chunck with reset 5 char ex_sta_pwd[2][65]; // 0E3
char sta_pwd[2][65]; // 0E3 - Keep together with sta_ssid as being copied as one chunck with reset 5 char ex_hostname[33]; // 165
char hostname[33]; // 165 char ex_syslog_host[33]; // 186
char syslog_host[33]; // 186
uint8_t ex_rule_stop; // 1A7 uint8_t ex_rule_stop; // 1A7
uint16_t ex_syslog_port; // 1A8 uint16_t ex_syslog_port; // 1A8
uint8_t ex_syslog_level; // 1AA uint8_t ex_syslog_level; // 1AA
@ -271,30 +263,23 @@ struct SYSCFG {
uint8_t ex_weblog_level; // 1AC uint8_t ex_weblog_level; // 1AC
uint8_t ex_mqtt_fingerprint[2][20]; // 1AD uint8_t ex_mqtt_fingerprint[2][20]; // 1AD
uint8_t ex_adc_param_type; // 1D5 uint8_t ex_adc_param_type; // 1D5
uint8_t ex_free_1d6[10]; // 1D6 uint8_t ex_free_1d6[10]; // 1D6
// End of single char array of 456 chars max (phase 3)
SysBitfield4 ex_flag4; // 1E0 SysBitfield4 ex_flag4; // 1E0
uint8_t ex_serial_config; // 1E4 uint8_t ex_serial_config; // 1E4
uint8_t ex_wifi_output_power; // 1E5 uint8_t ex_wifi_output_power; // 1E5
uint8_t ex_shutter_accuracy; // 1E6 uint8_t ex_shutter_accuracy; // 1E6
uint8_t ex_mqttlog_level; // 1E7 uint8_t ex_mqttlog_level; // 1E7
uint8_t ex_sps30_inuse_hours; // 1E8 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 // End of single char array of 698 chars max
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)
uint8_t display_model; // 2D2 uint8_t display_model; // 2D2
uint8_t display_mode; // 2D3 uint8_t display_mode; // 2D3
@ -315,8 +300,8 @@ struct SYSCFG {
uint8_t param[PARAM8_SIZE]; // 2FC SetOption32 .. SetOption49 uint8_t param[PARAM8_SIZE]; // 2FC SetOption32 .. SetOption49
int16_t toffset[2]; // 30E int16_t toffset[2]; // 30E
uint8_t display_font; // 312 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 uint8_t ex_energy_power_delta; // 33F - Free since 6.6.0.20
uint16_t domoticz_update_timer; // 340 uint16_t domoticz_update_timer; // 340
@ -351,8 +336,10 @@ struct SYSCFG {
uint16_t light_rotation; // 39E uint16_t light_rotation; // 39E
SysBitfield3 flag3; // 3A0 SysBitfield3 flag3; // 3A0
uint8_t switchmode[MAX_SWITCHES]; // 3A4 (6.0.0b - moved from 0x4CA) 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 char serial_delimiter; // 451
uint8_t seriallog_level; // 452 uint8_t seriallog_level; // 452
uint8_t sleep; // 453 uint8_t sleep; // 453
@ -376,15 +363,21 @@ struct SYSCFG {
uint8_t knx_GA_registered; // 4A5 Number of Group Address to read uint8_t knx_GA_registered; // 4A5 Number of Group Address to read
uint16_t light_wakeup; // 4A6 uint16_t light_wakeup; // 4A6
uint8_t knx_CB_registered; // 4A8 Number of Group Address to write 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 uint8_t interlock[MAX_INTERLOCKS]; // 4CA
char ntp_server[3][33]; // 4CE
char ex_ntp_server[3][33]; // 4CE
uint8_t ina219_mode; // 531 uint8_t ina219_mode; // 531
uint16_t pulse_timer[MAX_PULSETIMERS]; // 532 uint16_t pulse_timer[MAX_PULSETIMERS]; // 532
uint16_t button_debounce; // 542 uint16_t button_debounce; // 542
uint32_t ip_address[4]; // 544 uint32_t ip_address[4]; // 544
unsigned long energy_kWhtotal; // 554 unsigned long energy_kWhtotal; // 554
char mqtt_fulltopic[100]; // 558
char ex_mqtt_fulltopic[100]; // 558
SysBitfield2 flag2; // 5BC SysBitfield2 flag2; // 5BC
unsigned long pulse_counter[MAX_COUNTERS]; // 5C0 unsigned long pulse_counter[MAX_COUNTERS]; // 5C0
uint16_t pulse_counter_type; // 5D0 uint16_t pulse_counter_type; // 5D0
@ -428,7 +421,7 @@ struct SYSCFG {
unsigned long weight_calibration; // 7C4 unsigned long weight_calibration; // 7C4
unsigned long energy_frequency_calibration; // 7C8 also used by HX711 to save last weight unsigned long energy_frequency_calibration; // 7C8 also used by HX711 to save last weight
uint16_t web_refresh; // 7CC 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 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 int8_t temp_comp; // E9E
uint8_t weight_change; // E9F uint8_t weight_change; // E9F
uint8_t web_color2[2][3]; // EA0 - Needs to be on integer / 3 distance from web_color 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_config; // EC7
uint8_t sta_active; // EC8 uint8_t sta_active; // EC8
uint8_t rule_stop; // EC9 uint8_t rule_stop; // EC9

View File

@ -488,18 +488,13 @@ void UpdateQuickPowerCycle(bool update)
* Config Settings.text char array support * 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) uint32_t GetSettingsTextLen(void)
{ {
char* position = Settings.ota_url; char* position = Settings.text_pool;
for (uint32_t size = 0; size < SET_MAX; size++) { for (uint32_t size = 0; size < SET_MAX; size++) {
while (*position++ != '\0') { } while (*position++ != '\0') { }
} }
return position - Settings.ota_url; return position - Settings.text_pool;
} }
bool SettingsUpdateText(uint32_t index, const char* replace_me) 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]; char replace[replace_len +1];
memcpy(replace, replace_me, sizeof(replace)); memcpy(replace, replace_me, sizeof(replace));
uint32_t idx = 0; uint32_t start_pos = 0;
switch (index) { uint32_t end_pos = 0;
case SET_OTAURL: strlcpy(Settings.ota_url, replace, sizeof(Settings.ota_url)); break; char* position = Settings.text_pool;
case SET_MQTTPREFIX3: idx++; for (uint32_t size = 0; size < SET_MAX; size++) {
case SET_MQTTPREFIX2: idx++; while (*position++ != '\0') { }
case SET_MQTTPREFIX1: strlcpy(Settings.mqtt_prefix[idx], replace, sizeof(Settings.mqtt_prefix[idx])); break; if (1 == index) {
case SET_STASSID2: idx++; start_pos = position - Settings.text_pool;
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; else if (0 == index) {
case SET_MQTT_USER: break; end_pos = position - Settings.text_pool -1;
#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;
} }
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; return true;
} }
char* SettingsText(uint32_t index) char* SettingsText(uint32_t index)
{ {
char* position = Settings.text_pool;
if (index >= SET_MAX) { if (index >= SET_MAX) {
return nullptr; // Setting not supported - internal error position += settings_text_size -1; // Setting not supported - internal error - return empty string
}
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;
}
} else { } else {
for (;index > 0; index--) { for (;index > 0; index--) {
while (*position++ != '\0') { } while (*position++ != '\0') { }
} }
} }
return position; return position;
} }
@ -1132,10 +1059,6 @@ void SettingsDefaultSet2(void)
memset(&Settings.monitors, 0xFF, 20); // Enable all possible monitors, displays and sensors memset(&Settings.monitors, 0xFF, 20); // Enable all possible monitors, displays and sensors
SettingsEnableAllI2cDrivers(); SettingsEnableAllI2cDrivers();
if (VERSION < 0x08000000) {
SettingsBackwardCompat();
}
} }
/********************************************************************************************/ /********************************************************************************************/
@ -1175,17 +1098,6 @@ void SettingsEnableAllI2cDrivers(void)
Settings.i2c_drivers[2] = 0xFFFFFFFF; 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) void SettingsDelta(void)
@ -1388,9 +1300,9 @@ void SettingsDelta(void)
} }
if (Settings.version < 0x07010204) { if (Settings.version < 0x07010204) {
if (Settings.flag3.ex_cors_enabled == 1) { 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 { } else {
Settings.cors_domain[0] = 0; Settings.ex_cors_domain[0] = 0;
} }
} }
if (Settings.version < 0x07010205) { if (Settings.version < 0x07010205) {
@ -1405,46 +1317,26 @@ void SettingsDelta(void)
memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5); // 1E4 -> EFE memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5); // 1E4 -> EFE
} }
if ((VERSION < 0x08000000) && (Settings.version >= 0x08000000)) { if (Settings.version < 0x08000000) {
SettingsUpdateText(SET_WEBPWD, SettingsText(SET_WEBPWD)); char temp[strlen(Settings.text_pool) +1]; strncpy(temp, Settings.text_pool, sizeof(temp)); // Was ota_url
SettingsUpdateText(SET_CORS, SettingsText(SET_CORS)); char temp21[strlen(Settings.ex_mqtt_prefix[0]) +1]; strncpy(temp21, Settings.ex_mqtt_prefix[0], sizeof(temp21));
SettingsUpdateText(SET_MQTT_FULLTOPIC, SettingsText(SET_MQTT_FULLTOPIC)); char temp22[strlen(Settings.ex_mqtt_prefix[1]) +1]; strncpy(temp22, Settings.ex_mqtt_prefix[1], sizeof(temp22));
SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, SettingsText(SET_MQTT_SWITCH_TOPIC)); char temp23[strlen(Settings.ex_mqtt_prefix[2]) +1]; strncpy(temp23, Settings.ex_mqtt_prefix[2], sizeof(temp23));
SettingsUpdateText(SET_STATE_TXT1, SettingsText(SET_STATE_TXT1)); char temp31[strlen(Settings.ex_sta_ssid[0]) +1]; strncpy(temp31, Settings.ex_sta_ssid[0], sizeof(temp31));
SettingsUpdateText(SET_STATE_TXT2, SettingsText(SET_STATE_TXT2)); char temp32[strlen(Settings.ex_sta_ssid[1]) +1]; strncpy(temp32, Settings.ex_sta_ssid[1], sizeof(temp32));
SettingsUpdateText(SET_STATE_TXT3, SettingsText(SET_STATE_TXT3)); char temp41[strlen(Settings.ex_sta_pwd[0]) +1]; strncpy(temp41, Settings.ex_sta_pwd[0], sizeof(temp41));
SettingsUpdateText(SET_STATE_TXT4, SettingsText(SET_STATE_TXT4)); char temp42[strlen(Settings.ex_sta_pwd[1]) +1]; strncpy(temp42, Settings.ex_sta_pwd[1], sizeof(temp42));
SettingsUpdateText(SET_NTPSERVER1, SettingsText(SET_NTPSERVER1)); char temp5[strlen(Settings.ex_hostname) +1]; strncpy(temp5, Settings.ex_hostname, sizeof(temp5));
SettingsUpdateText(SET_NTPSERVER2, SettingsText(SET_NTPSERVER2)); char temp6[strlen(Settings.ex_syslog_host) +1]; strncpy(temp6, Settings.ex_syslog_host, sizeof(temp6));
SettingsUpdateText(SET_NTPSERVER3, SettingsText(SET_NTPSERVER3)); char temp7[strlen(Settings.ex_mqtt_host) +1]; strncpy(temp7, Settings.ex_mqtt_host, sizeof(temp7));
SettingsUpdateText(SET_MEM1, SettingsText(SET_MEM1)); char temp8[strlen(Settings.ex_mqtt_client) +1]; strncpy(temp8, Settings.ex_mqtt_client, sizeof(temp8));
SettingsUpdateText(SET_MEM2, SettingsText(SET_MEM2)); char temp9[strlen(Settings.ex_mqtt_user) +1]; strncpy(temp9, Settings.ex_mqtt_user, sizeof(temp9));
SettingsUpdateText(SET_MEM3, SettingsText(SET_MEM3)); char temp10[strlen(Settings.ex_mqtt_pwd) +1]; strncpy(temp10, Settings.ex_mqtt_pwd, sizeof(temp10));
SettingsUpdateText(SET_MEM4, SettingsText(SET_MEM4)); char temp11[strlen(Settings.ex_mqtt_topic) +1]; strncpy(temp11, Settings.ex_mqtt_topic, sizeof(temp11));
SettingsUpdateText(SET_MEM5, SettingsText(SET_MEM5)); char temp12[strlen(Settings.ex_button_topic) +1]; strncpy(temp12, Settings.ex_button_topic, sizeof(temp12));
SettingsUpdateText(SET_FRIENDLYNAME1, SettingsText(SET_FRIENDLYNAME1)); char temp13[strlen(Settings.ex_mqtt_grptopic) +1]; strncpy(temp13, Settings.ex_mqtt_grptopic, sizeof(temp13));
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));
memset(Settings.text_pool, 0x00, settings_text_size);
SettingsUpdateText(SET_OTAURL, temp); SettingsUpdateText(SET_OTAURL, temp);
SettingsUpdateText(SET_MQTTPREFIX1, temp21); SettingsUpdateText(SET_MQTTPREFIX1, temp21);
SettingsUpdateText(SET_MQTTPREFIX2, temp22); SettingsUpdateText(SET_MQTTPREFIX2, temp22);
@ -1455,15 +1347,46 @@ void SettingsDelta(void)
SettingsUpdateText(SET_STAPWD2, temp42); SettingsUpdateText(SET_STAPWD2, temp42);
SettingsUpdateText(SET_HOSTNAME, temp5); SettingsUpdateText(SET_HOSTNAME, temp5);
SettingsUpdateText(SET_SYSLOG_HOST, temp6); 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_HOST, temp7);
SettingsUpdateText(SET_MQTT_CLIENT, temp8);
SettingsUpdateText(SET_MQTT_USER, temp9); 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_USER, temp9);
#endif
SettingsUpdateText(SET_MQTT_CLIENT, temp8);
SettingsUpdateText(SET_MQTT_PWD, temp10); SettingsUpdateText(SET_MQTT_PWD, temp10);
SettingsUpdateText(SET_MQTT_TOPIC, temp11); SettingsUpdateText(SET_MQTT_TOPIC, temp11);
SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, temp12); SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, temp12);
SettingsUpdateText(SET_MQTT_GRP_TOPIC, temp13); 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; Settings.version = VERSION;

View File

@ -326,7 +326,7 @@ void CmndStatus(void)
uint32_t option = STAT; uint32_t option = STAT;
char stemp[200]; char stemp[200];
char stemp2[100]; char stemp2[TOPSZ];
// Workaround MQTT - TCP/IP stack queueing when SUB_PREFIX = PUB_PREFIX // Workaround MQTT - TCP/IP stack queueing when SUB_PREFIX = PUB_PREFIX
if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2)) && (!payload)) { option++; } // TELE if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2)) && (!payload)) { option++; } // TELE
@ -1311,7 +1311,7 @@ void CmndFriendlyname(void)
} else { } else {
snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), XdrvMailbox.index); 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)); ResponseCmndIdxChar(SettingsText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1));
} }

View File

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

View File

@ -0,0 +1,432 @@
/*
support_flash_log.ino - log to flash support for Sonoff-Tasmota
Copyright (C) 2019 Theo Arends & Christian Baars
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
--------------------------------------------------------------------------------------------
Version Date Action Description
--------------------------------------------------------------------------------------------
---
1.0.0.0 20190923 started - further development by Christian Baars - https://github.com/Staars/Sonoff-Tasmota
forked - from arendst/tasmota - https://github.com/arendst/Sonoff-Tasmota
base - code base from arendst and - written from scratch
*/
/********************************************************************************************\
| * Generic helper class to log arbitrary data to the OTA-partition
| * Working principle: Add preferrable small chunks of data to the sector buffer, which will
| * be written to FLASH when full automatically. The next sector will be
| * erased and is the anchor point for downloading and state configuration
| * after reboot.
\*********************************************************************************************/
#ifdef USE_FLOG
class FLOG
#define MAGIC_WORD_FL 0x464c //F, L
{
struct header_t{
uint16_t magic_word; // FL
uint16_t padding; // leave something for the future
uint32_t physical_start_sector:10; //first used sector of the current FLOG
uint32_t number:10; // number of this sector, starting with 0 for the first sector
uint32_t buf_pointer:12; //internal pointer to the next free position in the buffer = first empty byte when reading
}; // should be 4-byte-aligned
private:
void _readSector(uint8_t one_sector);
void _eraseSector(uint8_t one_sector);
void _writeSector(uint8_t one_sector);
void _clearBuffer(void);
void _searchSaves(void);
void _findFirstErasedSector(void);
void _showBuffer(void);
void _initBuffer(void);
void _saveBufferToSector(void);
header_t _saved_header;
public:
uint32_t size; // size of OTA-partition
uint32_t start; // start position of OTA-partition in bytes
uint32_t end; // end position of OTA-partition in bytes
uint16_t num_sectors; // calculated number of sectors with a size of 4096 bytes
uint16_t first_erased_sector; // this will be our new start
uint16_t current_sector; // always point to next sector, where data from the buffer will be written to
uint16_t bytes_left; // byte per buffer (of sector size 4096 bytes - 8 byte header size)
uint16_t sectors_left; // number of saved sectors for download
uint8_t mode = 0; // 0 - write once on all sectors, then stop, 1 - write infinitely through the sectors
bool found_saved_data = false; // possible saved data has been found
bool ready = false; // the FLOG is initialized
bool running_download = false; // a download operation is running
bool recording = false; // ready for recording
union sector_t{
uint32_t dword_buffer[FLASH_SECTOR_SIZE/4];
uint8_t byte_buffer[FLASH_SECTOR_SIZE];
header_t header; // should be 4-byte-aligned
} sector; // the global buffer of 4096 bytes, used for reading and writing
void init(void);
void addToBuffer(uint8_t src[], uint32_t size);
void startRecording(bool append);
void stopRecording(void);
typedef void (*CallbackNoArgs) (); // simple typedef for a callback
typedef void (*CallbackWithArgs) (uint8_t *_record); // typedef for a callback with one argument
void startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter);
};
extern "C" uint32_t _SPIFFS_start; // we make shure later, that only one of the two is really used ...
extern "C" uint32_t _FS_start; // ... depending on core-sdk-version
/**
* @brief Will examine the start and end of the OTA-partition. Then the sector size will be computed, saved data should be found and the initial state will be configured.
*/
void FLOG::init(void)
{
DEBUG_SENSOR_LOG(PSTR("FLOG: init ..."));
size = ESP.getSketchSize();
// round one sector up
start = (size + FLASH_SECTOR_SIZE - 1) & (~(FLASH_SECTOR_SIZE - 1));
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_1) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_0) || defined(ARDUINO_ESP8266_RELEASE_2_5_1) || defined(ARDUINO_ESP8266_RELEASE_2_5_2)
end = (uint32_t)&_SPIFFS_start - 0x40200000;
#else // Core > 2.5.2 and STAGE
end = (uint32_t)&_FS_start - 0x40200000;
#endif
num_sectors = (end - start)/FLASH_SECTOR_SIZE;
DEBUG_SENSOR_LOG(PSTR("FLOG: size: 0x%lx, start: 0x%lx, end: 0x%lx, num_sectors(dec): %lu"), size, start, end, num_sectors );
_findFirstErasedSector();
if(first_erased_sector == 0xffff){
_eraseSector(0);
first_erased_sector = 0; // start with sector 0, could be first run or after crash
}
_searchSaves();
_initBuffer();
ready = true;
}
/********************************************************************************************\
| *
| * private helper functions
| *
\*********************************************************************************************/
/**
* @brief Read a sector into the global buffer
*
* @param one_sector as an uint8_t
*/
void FLOG::_readSector(uint8_t one_sector){
DEBUG_SENSOR_LOG(PSTR("FLOG: read sector number: %u" ), one_sector);
ESP.flashRead(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)&sector.dword_buffer, FLASH_SECTOR_SIZE);
}
/**
* @brief Erase the given sector og the OTA-partition
*
* @param one_sector as an uint8_t
*/
void FLOG::_eraseSector(uint8_t one_sector){ // Erase sector of FLOG/OTA
DEBUG_SENSOR_LOG(PSTR("FLOG: erasing sector number: %u" ), one_sector);
ESP.flashEraseSector((start/FLASH_SECTOR_SIZE)+one_sector);
}
/**
* @brief Write the global buffer to the given sector
*
* @param one_sector as an uint8_t
*/
void FLOG::_writeSector(uint8_t one_sector){ // Write sector of FLOG/OTA
DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to sector number: %u" ), one_sector);
ESP.flashWrite(start+(one_sector * FLASH_SECTOR_SIZE),(uint32_t *)&sector.dword_buffer, FLASH_SECTOR_SIZE);
}
/**
* @brief Clear the global buffer, but leave the header intact
*
*/
void FLOG::_clearBuffer(){ //not the header
for (uint32_t i = sizeof(sector.header)/4; i<(sizeof(sector.dword_buffer)/4); i++){
sector.dword_buffer[i] = 0;
}
sector.header.buf_pointer = sizeof(sector.header);
// _showBuffer();
}
/**
* @brief Write global buffer to FLASH and set the current sector to the next valid position, maybe to 0
*
*/
void FLOG::_saveBufferToSector(){ // save buffer to already erased(!) sector, erase next sector, clear buffer, increment number
DEBUG_SENSOR_LOG(PSTR("FLOG: write buffer to current sector: %u" ),current_sector);
_writeSector(current_sector);
if(current_sector == num_sectors){ // 1 MB means ~110 sectors in OTA-partition, if we reach this, start a again
current_sector = 0;
}
else{
current_sector++;
}
_eraseSector(current_sector); // we always erase the next sector, to find out were we are after restart
_clearBuffer();
sector.header.number++;
DEBUG_SENSOR_LOG(PSTR("FLOG: new sector header number: %u" ),sector.header.number);
}
/**
* @brief Typically after restart find the first erased sector as a starting point for further operations
*
*/
void FLOG::_findFirstErasedSector(){
for (uint32_t i = 0; i<num_sectors; i++){
bool success = true;
DEBUG_SENSOR_LOG(PSTR("FLOG: read sector: %u"), i);
_readSector(i);
for (uint32_t j = 0; j<(sizeof(sector.dword_buffer)/4); j++){
if(sector.dword_buffer[j]!=0xffffffff){
// DEBUG_SENSOR_LOG(PSTR("FLOG: buffer_dword: %u"), sector.dword_buffer[j]);
success = false;
}
}
if(success){
first_erased_sector = i; // save this for the whole next write operation
sector.header.physical_start_sector = i; // save to header for every sector
current_sector = i; // this is our actual sector to write to
DEBUG_SENSOR_LOG(PSTR("FLOG: first erased sector: %u, now init ..."), first_erased_sector);
return;
}
}
DEBUG_SENSOR_LOG(PSTR("FLOG: no erased sector found"));
first_erased_sector = 0xffff; // this will not happen unless we have 256 MByte FLASH
}
/**
* @brief Look at the sector before the first erased sector to check, if there could be saved data
*
*/
void FLOG::_searchSaves(void){
//check if old Data is present
found_saved_data = false;
uint32_t s;
if(first_erased_sector==0){
DEBUG_SENSOR_LOG(PSTR("FLOG: sector 0 was erased before, examine sector: %u"), num_sectors);
s = num_sectors; //count back to the highest possible sector
}
else{
s = first_erased_sector-1;
DEBUG_SENSOR_LOG(PSTR("FLOG: examine sector: %u"), s);
}
_readSector(s); //read the sector before the first erased sector
if(sector.header.magic_word!=MAGIC_WORD_FL){
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
return;
}
sectors_left = sector.header.number + 1; // this might be wrong, but this less important
_saved_header = sector.header; // back this up for appending mode
s = sector.header.physical_start_sector;
DEBUG_SENSOR_LOG(PSTR("FLOG: will check pysical start sector: %u"), s);
_readSector(s); //read the physical_start_sector
_showBuffer();
if(sector.header.magic_word!=MAGIC_WORD_FL){ //F, L
DEBUG_SENSOR_LOG(PSTR("FLOG: wrong magic number, no saved data found"));
sectors_left = 0;
return;
}
if(sector.header.number==0){ //physical_start_sector should have number 0
DEBUG_SENSOR_LOG(PSTR("FLOG: possible saved data found"));
found_saved_data = true; // TODO: this is only a very rough check and should be completed later
}
else{
DEBUG_SENSOR_LOG(PSTR("FLOG: number: %u should be 0"), sector.header.number);
sectors_left = 0;
}
}
/**
* @brief Start with a new buffer to be able to start a write session
*
*/
void FLOG::_initBuffer(void){
if(!found_saved_data){ // we must re-init this, because the buffer is in an undefined state
sector.header.physical_start_sector = (uint16_t)first_erased_sector;
}
DEBUG_SENSOR_LOG(PSTR("FLOG: init header"));
sector.header.magic_word = MAGIC_WORD_FL; //F, L
sector.header.number = 0;
sector.header.buf_pointer = (uint16_t)sizeof(sector.header);
current_sector = first_erased_sector;
ready = true;
_clearBuffer();
}
/**
* @brief - a pure debug function
*
*/
void FLOG::_showBuffer(void){
DEBUG_SENSOR_LOG(PSTR("FLOG: Header: %c %c"), sector.byte_buffer[0],sector.byte_buffer[1]);
DEBUG_SENSOR_LOG(PSTR("FLOG: V_Start_sector: %u, sector number: %u, pointer: %u "), sector.header.physical_start_sector, sector.header.number, sector.header.buf_pointer);
uint32_t j = 0;
for (uint32_t i = sector.header.buf_pointer-16; i<(sizeof(sector.byte_buffer)); i+=8){
// DEBUG_SENSOR_LOG(PSTR("FLOG: buffer: %c %c %c %c %c %c %c %c "), sector.byte_buffer[i], sector.byte_buffer[i+1], sector.byte_buffer[i+2], sector.byte_buffer[i+3], sector.byte_buffer[i+4], sector.byte_buffer[i+5], sector.byte_buffer[i+6], sector.byte_buffer[i+7]);
DEBUG_SENSOR_LOG(PSTR("FLOG: buffer: %u %u %u %u %u %u %u %u "), sector.byte_buffer[i], sector.byte_buffer[i+1], sector.byte_buffer[i+2], sector.byte_buffer[i+3], sector.byte_buffer[i+4], sector.byte_buffer[i+5], sector.byte_buffer[i+6], sector.byte_buffer[i+7]);
j++;
if(j>3){
break;
}
}
}
/**
* @brief pass a data entry/record as uint8_t array with its size
*
* @param src uint8_t array
* @param size uint32_t size of the array
*/
void FLOG::addToBuffer(uint8_t src[], uint32_t size){
if(mode == 0){
if(sector.header.number == num_sectors && !ready){
return; // we ignore additional calls and are done, TODO: maybe use meaningful return values
}
}
if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){
// DEBUG_SENSOR_LOG(PSTR("FLOG: enough space left in buffer: %u"), FLASH_SECTOR_SIZE - sector.header.buf_pointer - sizeof(sector.header));
// DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u, size of added: %u"), sector.header.buf_pointer, size);
memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size);
sector.header.buf_pointer+=size; // this is the next free spot
// DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u"), sector.header.buf_pointer);
}
else{
DEBUG_SENSOR_LOG(PSTR("FLOG: save buffer to flash sector: %u"), current_sector);
DEBUG_SENSOR_LOG(PSTR("FLOG: current buf_pointer: %u"), sector.header.buf_pointer);
_saveBufferToSector();
sectors_left++;
// but now save the data to the fresh buffer
if((FLASH_SECTOR_SIZE-sector.header.buf_pointer-sizeof(sector.header))>size){
memcpy(sector.byte_buffer + sector.header.buf_pointer, src, size);
sector.header.buf_pointer+=size; // this is the next free spot
}
}
}
/**
* @brief shows that it is ready to accept recording
*
* @param append - if true append to current log, else start a new log
*/
void FLOG::startRecording(bool append){
if(recording){
DEBUG_SENSOR_LOG(PSTR("FLOG: already recording"));
return;
}
recording = true;
DEBUG_SENSOR_LOG(PSTR("FLOG: start recording"));
_initBuffer();
if(!found_saved_data) {
append = false; // nothing to append to, we silently start a new log
}
if(append){
sector.header.number = _saved_header.number+1; // continue with the next number
sector.header.physical_start_sector = _saved_header.physical_start_sector; // keep the old start sector
}
else{ //new log, old data is lost
sector.header.physical_start_sector = (uint16_t)first_erased_sector;
found_saved_data = false;
sectors_left = 0;
}
}
/**
* @brief stop recording including saving current buffer to FLASH
*
*/
void FLOG::stopRecording(void){
_saveBufferToSector();
_findFirstErasedSector();
_searchSaves();
_initBuffer();
recording = false;
found_saved_data = true;
}
/**
* @brief Will start a downloads, needs the correct implementation of 3 callback functions
*
* @param size: size of the data entry/record in bytes, i.e. sizeof(myStruct)
* @param sendHeader: should implement at least something like:
* @example WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN); // This is very likely unknown!!
* WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=myfile.txt"));
* @param sendRecord: will receive the memory address as "uint8_t* addr" and should consume the current entry/record
* @example myStruct_t *entry = (myStruct_t*)addr;
* Then make useful Strings and send it, i.e.: WebServer->sendContent_P(myString);
* @param sendFooter: finish the download, should implement at least:
* @example WebServer->sendContent("");
*/
void FLOG::startDownload(size_t size, CallbackNoArgs sendHeader, CallbackWithArgs sendRecord, CallbackNoArgs sendFooter){
_readSector(sector.header.physical_start_sector);
uint32_t next_sector = sector.header.physical_start_sector;
bytes_left = sector.header.buf_pointer - sizeof(sector.header);
DEBUG_SENSOR_LOG(PSTR("FLOG: create file for download, will process %u bytes"), bytes_left);
running_download = true;
// Callback 1: Create the header incl. file name, content length (probably unknown!!) and additional header stuff
sendHeader();
while(sectors_left){
DEBUG_SENSOR_LOG(PSTR("FLOG: next sector: %u"), next_sector);
//initially we have the first sector already loaded, so we do it at the bottom
uint32_t k = sizeof(sector.header);
while(bytes_left){
// DEBUG_SENSOR_LOG(PSTR("FLOG: DL %u %u"), Flog->sector.byte_buffer[k],Flog->sector.byte_buffer[k+1]);
uint8_t *_record_start = (uint8_t*)&sector.byte_buffer[k]; // this is basically the start address of the current record/entry of the Log
// Callback 2: send the pointer for consuming the next record/entry and doing something useful to create a file
sendRecord(_record_start);
if(k%128 == 0){ // give control to the system every x iteration, TODO: This will fail, when record/entry-size is not 8
// DEBUG_SENSOR_LOG(PSTR("FLOG: now loop(), %u bytes left"), Flog->bytes_left);
OsWatchLoop();
delay(sleep);
}
k+=size;
if(bytes_left>7){
bytes_left-=size;
}
else{
bytes_left = 0;
DEBUG_SENSOR_LOG(PSTR("FLOG: Flog->bytes_left not dividable by 8 ??????"));
}
}
next_sector++;
if(next_sector>num_sectors){
next_sector = 0;
}
sectors_left--;
_readSector(next_sector);
bytes_left = sector.header.buf_pointer - sizeof(sector.header);
OsWatchLoop();
delay(sleep);
}
running_download = false;
// Callback 3: create a footer or simply finish the download with an empty payload
sendFooter();
// refresh settings for another download
_searchSaves();
_initBuffer();
}
#endif // USE_FLOG

View File

@ -460,7 +460,9 @@ void RtcSetTime(uint32_t epoch)
if (epoch < START_VALID_TIME) { // 2016-01-01 if (epoch < START_VALID_TIME) { // 2016-01-01
Rtc.user_time_entry = false; Rtc.user_time_entry = false;
ntp_force_sync = true; ntp_force_sync = true;
sntp_init();
} else { } else {
sntp_stop();
Rtc.user_time_entry = true; Rtc.user_time_entry = true;
Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond
} }

View File

@ -27,42 +27,7 @@
String GetStatistics(void) String GetStatistics(void)
{ {
char data[40]; 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); return String(data);
} }

View File

@ -546,7 +546,7 @@ void MqttShowPWMState(void)
void MqttShowState(void) void MqttShowState(void)
{ {
char stemp1[33]; char stemp1[TOPSZ];
ResponseAppendTime(); ResponseAppendTime();
ResponseAppend_P(PSTR(",\"" D_JSON_UPTIME "\":\"%s\",\"UptimeSec\":%u"), GetUptime().c_str(), UpTime()); ResponseAppend_P(PSTR(",\"" D_JSON_UPTIME "\":\"%s\",\"UptimeSec\":%u"), GetUptime().c_str(), UpTime());

View File

@ -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_COUNTERS = 4; // Max number of counter sensors
const uint8_t MAX_TIMERS = 16; // Max number of Timers 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_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_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_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 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_I2C_DRIVERS = 96; // Max number of allowed i2c drivers
const uint8_t MAX_SHUTTERS = 4; // Max number of shutters 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_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 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 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 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] 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 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 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 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 LOGSZ = 700; // Max number of characters in log
const uint16_t MIN_MESSZ = 893; // Min number of characters in MQTT message 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_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 // 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_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_MEM9, SET_MEM10, SET_MEM11, SET_MEM12, SET_MEM13, SET_MEM14, SET_MEM15, SET_MEM16,
SET_FRIENDLYNAME1, SET_FRIENDLYNAME2, SET_FRIENDLYNAME3, SET_FRIENDLYNAME4, SET_FRIENDLYNAME1, SET_FRIENDLYNAME2, SET_FRIENDLYNAME3, SET_FRIENDLYNAME4,
SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8,
// SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8, // Future extension SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8,
// SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, // Future extension SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16,
// 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_MAX }; 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, enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,

View File

@ -161,8 +161,8 @@ StateBitfield global_state; // Global states (currently Wifi and
char my_version[33]; // Composed version string char my_version[33]; // Composed version string
char my_image[33]; // Code image and/or commit char my_image[33]; // Code image and/or commit
char my_hostname[33]; // Composed Wifi hostname char my_hostname[33]; // Composed Wifi hostname
char mqtt_client[33]; // Composed MQTT Clientname char mqtt_client[TOPSZ]; // Composed MQTT Clientname
char mqtt_topic[33]; // Composed MQTT topic char mqtt_topic[TOPSZ]; // Composed MQTT topic
char serial_in_buffer[INPUT_BUFFER_SIZE]; // Receive buffer char serial_in_buffer[INPUT_BUFFER_SIZE]; // Receive buffer
char mqtt_data[MESSZ]; // MQTT publish buffer and web page ajax buffer char mqtt_data[MESSZ]; // MQTT publish buffer and web page ajax buffer
char log_data[LOGSZ]; // Logging char log_data[LOGSZ]; // Logging

View File

@ -180,6 +180,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#define USE_PN532_HSU // Add support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #define USE_PN532_HSU // Add support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8) #define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8)
#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
//#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code)
#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code)
@ -374,6 +375,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
//#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor //#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor
#undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI #undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI
@ -462,6 +464,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
//#undef USE_ENERGY_SENSOR // Disable energy sensors //#undef USE_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor #undef USE_PZEM004T // Disable PZEM004T energy sensor
@ -571,6 +574,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem) #undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8) #undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module) #undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#undef USE_ENERGY_SENSOR // Disable energy sensors #undef USE_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor #undef USE_PZEM004T // Disable PZEM004T energy sensor

View File

@ -214,6 +214,8 @@ enum UserSelectablePins {
GPIO_TASMOTASLAVE_RST_INV, // Slave Reset Inverted GPIO_TASMOTASLAVE_RST_INV, // Slave Reset Inverted
GPIO_HPMA_RX, // Honeywell HPMA115S0 Serial interface GPIO_HPMA_RX, // Honeywell HPMA115S0 Serial interface
GPIO_HPMA_TX, // Honeywell HPMA115S0 Serial interface GPIO_HPMA_TX, // Honeywell HPMA115S0 Serial interface
GPIO_GPS_RX, // GPS serial interface
GPIO_GPS_TX, // GPS serial interface
GPIO_SENSOR_END }; GPIO_SENSOR_END };
// Programmer selectable GPIO functionality // Programmer selectable GPIO functionality
@ -294,6 +296,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_DEEPSLEEP "|" D_SENSOR_EXS_ENABLE "|" D_SENSOR_DEEPSLEEP "|" D_SENSOR_EXS_ENABLE "|"
D_SENSOR_SLAVE_TX "|" D_SENSOR_SLAVE_RX "|" D_SENSOR_SLAVE_RESET "|" D_SENSOR_SLAVE_RESET "i|" D_SENSOR_SLAVE_TX "|" D_SENSOR_SLAVE_RX "|" D_SENSOR_SLAVE_RESET "|" D_SENSOR_SLAVE_RESET "i|"
D_SENSOR_HPMA_RX "|" D_SENSOR_HPMA_TX "|" D_SENSOR_HPMA_RX "|" D_SENSOR_HPMA_TX "|"
D_SENSOR_GPS_RX "|" D_SENSOR_GPS_TX
; ;
const char kSensorNamesFixed[] PROGMEM = const char kSensorNamesFixed[] PROGMEM =
@ -308,6 +311,7 @@ enum UserSelectableAdc0 {
ADC0_LIGHT, // Light sensor ADC0_LIGHT, // Light sensor
ADC0_BUTTON, // Button ADC0_BUTTON, // Button
ADC0_BUTTON_INV, ADC0_BUTTON_INV,
ADC0_MOIST, // Moisture
// ADC0_SWITCH, // Switch // ADC0_SWITCH, // Switch
// ADC0_SWITCH_INV, // ADC0_SWITCH_INV,
ADC0_END }; ADC0_END };
@ -323,6 +327,7 @@ const char kAdc0Names[] PROGMEM =
D_SENSOR_NONE "|" D_ANALOG_INPUT "|" D_SENSOR_NONE "|" D_ANALOG_INPUT "|"
D_TEMPERATURE "|" D_LIGHT "|" D_TEMPERATURE "|" D_LIGHT "|"
D_SENSOR_BUTTON "|" D_SENSOR_BUTTON "i|" D_SENSOR_BUTTON "|" D_SENSOR_BUTTON "i|"
D_MOISTURE "|"
// D_SENSOR_SWITCH "|" D_SENSOR_SWITCH "i|" // D_SENSOR_SWITCH "|" D_SENSOR_SWITCH "i|"
; ;
@ -671,6 +676,7 @@ const uint8_t kGpioNiceList[] PROGMEM = {
#endif // USE_SOLAX_X1 #endif // USE_SOLAX_X1
#endif // USE_ENERGY_SENSOR #endif // USE_ENERGY_SENSOR
// Serial
#ifdef USE_SERIAL_BRIDGE #ifdef USE_SERIAL_BRIDGE
GPIO_SBR_TX, // Serial Bridge Serial interface GPIO_SBR_TX, // Serial Bridge Serial interface
GPIO_SBR_RX, // Serial Bridge Serial interface GPIO_SBR_RX, // Serial Bridge Serial interface
@ -725,6 +731,11 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_IBEACON_RX, GPIO_IBEACON_RX,
GPIO_IBEACON_TX, GPIO_IBEACON_TX,
#endif #endif
#ifdef USE_GPS
GPIO_GPS_RX, // GPS serial interface
GPIO_GPS_TX, // GPS serial interface
#endif
#ifdef USE_MGC3130 #ifdef USE_MGC3130
GPIO_MGC3130_XFER, GPIO_MGC3130_XFER,
GPIO_MGC3130_RESET, GPIO_MGC3130_RESET,
@ -754,8 +765,9 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_A4988_MS3, // A4988 microstep pin3 GPIO_A4988_MS3, // A4988 microstep pin3
#endif #endif
#ifdef USE_DEEPSLEEP #ifdef USE_DEEPSLEEP
GPIO_DEEPSLEEP GPIO_DEEPSLEEP,
#endif #endif
}; };
const uint8_t kModuleNiceList[] PROGMEM = { const uint8_t kModuleNiceList[] PROGMEM = {

View File

@ -20,9 +20,9 @@
#ifndef _TASMOTA_VERSION_H_ #ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_ #define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x07020000; const uint32_t VERSION = 0x08010000;
// Lowest compatible version // Lowest compatible version
const uint32_t VERSION_COMPATIBLE = 0x00000000; const uint32_t VERSION_COMPATIBLE = 0x07010006;
#endif // _TASMOTA_VERSION_H_ #endif // _TASMOTA_VERSION_H_

View File

@ -1014,7 +1014,7 @@ void HandleRoot(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU); AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU);
char stemp[10]; char stemp[33];
WSContentStart_P(S_MAIN_MENU); WSContentStart_P(S_MAIN_MENU);
#ifdef USE_SCRIPT_WEB_DISPLAY #ifdef USE_SCRIPT_WEB_DISPLAY
@ -1106,14 +1106,19 @@ void HandleRoot(void)
WSContentSend_P(PSTR("<tr>")); WSContentSend_P(PSTR("<tr>"));
#ifdef USE_SONOFF_IFAN #ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) { 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++) { for (uint32_t i = 0; i < MaxFanspeed(); i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), 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 { } else {
#endif // USE_SONOFF_IFAN #endif // USE_SONOFF_IFAN
for (uint32_t idx = 1; idx <= devices_present; idx++) { 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 #ifdef USE_SHUTTER
if (Settings.flag3.shutter_mode) { // SetOption80 - Enable shutter support if (Settings.flag3.shutter_mode) { // SetOption80 - Enable shutter support
bool shutter_used = false; bool shutter_used = false;
@ -1124,13 +1129,17 @@ void HandleRoot(void)
} }
} }
if (shutter_used) { 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; continue;
} }
} }
#endif // USE_SHUTTER #endif // USE_SHUTTER
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx); 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 #ifdef USE_SONOFF_IFAN
} }
@ -1146,7 +1155,9 @@ void HandleRoot(void)
if (idx > 0) { WSContentSend_P(PSTR("</tr><tr>")); } if (idx > 0) { WSContentSend_P(PSTR("</tr><tr>")); }
for (uint32_t j = 0; j < 4; j++) { for (uint32_t j = 0; j < 4; j++) {
idx++; idx++;
WSContentSend_P(PSTR("<td style='width:25%%'><button onclick='la(\"&k=%d\");'>%d</button></td>"), idx, idx); // &k is related to WebGetArg("k", tmp, sizeof(tmp)); snprintf_P(stemp, sizeof(stemp), PSTR("%d"), idx);
WSContentSend_P(PSTR("<td style='width:25%%'><button onclick='la(\"&k=%d\");'>%s</button></td>"), 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("</tr></table>")); WSContentSend_P(PSTR("</tr></table>"));
@ -1694,7 +1705,7 @@ void HandleWifiConfiguration(void)
void WifiSaveSettings(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)); WebGetArg("h", tmp, sizeof(tmp));
SettingsUpdateText(SET_HOSTNAME, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp); SettingsUpdateText(SET_HOSTNAME, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp);
@ -1757,7 +1768,7 @@ void HandleLoggingConfiguration(void)
void LoggingSaveSettings(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)); WebGetArg("l0", tmp, sizeof(tmp));
SetSeriallog((!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp)); SetSeriallog((!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp));
@ -1843,9 +1854,10 @@ void HandleOtherConfiguration(void)
void OtherSaveSettings(void) void OtherSaveSettings(void)
{ {
char tmp[128]; char tmp[TOPSZ];
char webindex[5]; char webindex[5];
char friendlyname[TOPSZ]; char friendlyname[TOPSZ];
char message[LOGSZ];
WebGetArg("wp", tmp, sizeof(tmp)); WebGetArg("wp", tmp, sizeof(tmp));
SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp); SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp);
@ -1854,15 +1866,17 @@ void OtherSaveSettings(void)
WebGetArg("b2", tmp, sizeof(tmp)); WebGetArg("b2", tmp, sizeof(tmp));
Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp); Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp);
#endif // USE_EMULATION #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++) { for (uint32_t i = 0; i < MAX_FRIENDLYNAMES; i++) {
snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i); snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i);
WebGetArg(webindex, tmp, sizeof(tmp)); WebGetArg(webindex, tmp, sizeof(tmp));
snprintf_P(friendlyname, sizeof(friendlyname), PSTR(FRIENDLY_NAME"%d"), i +1); snprintf_P(friendlyname, sizeof(friendlyname), PSTR(FRIENDLY_NAME"%d"), i +1);
SettingsUpdateText(SET_FRIENDLYNAME1 +i, (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp); 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)); 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} 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]; char svalue[128];
@ -1890,7 +1904,7 @@ void HandleBackupConfiguration(void)
WiFiClient myClient = WebServer->client(); WiFiClient myClient = WebServer->client();
WebServer->setContentLength(sizeof(Settings)); WebServer->setContentLength(sizeof(Settings));
char attachment[100]; char attachment[TOPSZ];
// char friendlyname[TOPSZ]; // char friendlyname[TOPSZ];
// snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(friendlyname, SettingsText(SET_FRIENDLYNAME1)), my_version); // 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; } if (!HttpCheckPriviledgedAccess()) { return; }
char command[128]; // OtaUrl char command[TOPSZ + 10]; // OtaUrl
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED)); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
WifiConfigCounter(); WifiConfigCounter();
char otaurl[101]; char otaurl[TOPSZ];
WebGetArg("o", otaurl, sizeof(otaurl)); WebGetArg("o", otaurl, sizeof(otaurl));
if (strlen(otaurl)) { if (strlen(otaurl)) {
snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl); snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl);
@ -2637,7 +2651,7 @@ int WebSend(char *buffer)
if (user) { if (user) {
user = strtok_r(user, ":", &password); // user = |admin|, password = |joker| user = strtok_r(user, ":", &password); // user = |admin|, password = |joker|
if (user && password) { if (user && password) {
char userpass[128]; char userpass[200];
snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password); 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&| 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 #ifdef USE_SENDMAIL
D_CMND_SENDMAIL "|" D_CMND_SENDMAIL "|"
#endif #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 = { void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION #ifdef USE_EMULATION
@ -2739,7 +2754,8 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_SENDMAIL #ifdef USE_SENDMAIL
&CmndSendmail, &CmndSendmail,
#endif #endif
&CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, &CmndWebSensor, &CmndCors }; &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor,
&CmndWebSensor, &CmndWebButton, &CmndCors };
/*********************************************************************************************\ /*********************************************************************************************\
* Commands * Commands
@ -2860,6 +2876,24 @@ void CmndWebSensor(void)
ResponseJsonEnd(); 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) void CmndCors(void)
{ {
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {

View File

@ -32,9 +32,7 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) #if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
D_CMND_MQTTFINGERPRINT "|" D_CMND_MQTTFINGERPRINT "|"
#endif #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 "|" D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|"
#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
D_CMND_TLSKEY "|" D_CMND_TLSKEY "|"
#endif #endif
@ -46,9 +44,7 @@ void (* const MqttCommand[])(void) PROGMEM = {
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT) #if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
&CmndMqttFingerprint, &CmndMqttFingerprint,
#endif #endif
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
&CmndMqttUser, &CmndMqttPassword, &CmndMqttUser, &CmndMqttPassword,
#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
&CmndTlsKey, &CmndTlsKey,
#endif #endif
@ -640,7 +636,6 @@ void MqttReconnect(void)
#endif #endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) #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)); 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)) { if (MqttClient.connect(mqtt_client, nullptr, nullptr, stopic, 1, false, mqtt_data, MQTT_CLEAN_SESSION)) {
#else #else
if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data, MQTT_CLEAN_SESSION)) { 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 #endif
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
void CmndMqttUser(void) void CmndMqttUser(void)
{ {
if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.data_len > 0) {
@ -757,7 +751,6 @@ void CmndMqttPassword(void)
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command); Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
} }
} }
#endif // USE_MQTT_AWS_IOT
void CmndMqttlog(void) void CmndMqttlog(void)
{ {
@ -1171,10 +1164,8 @@ const char HTTP_FORM_MQTT1[] PROGMEM =
"<p><b>" D_PORT "</b> (" STR(MQTT_PORT) ")<br><input id='ml' placeholder='" STR(MQTT_PORT) "' value='%d'></p>" "<p><b>" D_PORT "</b> (" STR(MQTT_PORT) ")<br><input id='ml' placeholder='" STR(MQTT_PORT) "' value='%d'></p>"
"<p><b>" D_CLIENT "</b> (%s)<br><input id='mc' placeholder='%s' value='%s'></p>"; "<p><b>" D_CLIENT "</b> (%s)<br><input id='mc' placeholder='%s' value='%s'></p>";
const char HTTP_FORM_MQTT2[] PROGMEM = const char HTTP_FORM_MQTT2[] PROGMEM =
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password disabled with AWS IoT
"<p><b>" D_USER "</b> (" MQTT_USER ")<br><input id='mu' placeholder='" MQTT_USER "' value='%s'></p>" "<p><b>" D_USER "</b> (" MQTT_USER ")<br><input id='mu' placeholder='" MQTT_USER "' value='%s'></p>"
"<p><b>" D_PASSWORD "</b><input type='checkbox' onclick='sp(\"mp\")'><br><input id='mp' type='password' placeholder='" D_PASSWORD "' value='" D_ASTERISK_PWD "'></p>" "<p><b>" D_PASSWORD "</b><input type='checkbox' onclick='sp(\"mp\")'><br><input id='mp' type='password' placeholder='" D_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
#endif // USE_MQTT_AWS_IOT
"<p><b>" D_TOPIC "</b> = %%topic%% (%s)<br><input id='mt' placeholder='%s' value='%s'></p>" "<p><b>" D_TOPIC "</b> = %%topic%% (%s)<br><input id='mt' placeholder='%s' value='%s'></p>"
"<p><b>" D_FULL_TOPIC "</b> (%s)<br><input id='mf' placeholder='%s' value='%s'></p>"; "<p><b>" D_FULL_TOPIC "</b> (%s)<br><input id='mf' placeholder='%s' value='%s'></p>";
@ -1190,7 +1181,7 @@ void HandleMqttConfiguration(void)
return; return;
} }
char str[33]; char str[TOPSZ];
WSContentStart_P(S_CONFIGURE_MQTT); WSContentStart_P(S_CONFIGURE_MQTT);
WSContentSendStyle(); WSContentSendStyle();
@ -1209,7 +1200,7 @@ void HandleMqttConfiguration(void)
void MqttSaveSettings(void) void MqttSaveSettings(void)
{ {
char tmp[100]; char tmp[TOPSZ];
char stemp[TOPSZ]; char stemp[TOPSZ];
char stemp2[TOPSZ]; char stemp2[TOPSZ];

View File

@ -262,8 +262,8 @@ struct LIGHT {
uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0}; uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0};
uint16_t fade_cur_10[LST_MAX]; uint16_t fade_cur_10[LST_MAX];
uint16_t fade_end_10[LST_MAX]; // 10 bits resolution target channel values 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 milliseconds
uint16_t fade_duration = 0; // duration of fade in ticks (50ms) uint32_t fade_start = 0; // fade start time in milliseconds, compared to millis()
} Light; } Light;
power_t LightPower(void) power_t LightPower(void)
@ -1574,20 +1574,25 @@ void LightAnimate(void)
bool power_off = false; bool power_off = false;
Light.strip_timer_counter++; Light.strip_timer_counter++;
if (!Light.power) { // All channels powered off
Light.strip_timer_counter = 0; // set sleep parameter: either settings,
if (!Light.fade_running) { // or set a maximum of PWM_MAX_SLEEP if light is on or Fade is running
sleep = Settings.sleep; if (Light.power || Light.fade_running) {
}
if (Settings.light_scheme >= LS_MAX) {
power_off = true;
}
} else {
if (Settings.sleep > PWM_MAX_SLEEP) { if (Settings.sleep > PWM_MAX_SLEEP) {
sleep = PWM_MAX_SLEEP; // set a maxumum value of 50 milliseconds to ensure that animations are smooth sleep = PWM_MAX_SLEEP; // set a maxumum value of 50 milliseconds to ensure that animations are smooth
} else { } else {
sleep = Settings.sleep; // or keep the current sleep if it's lower than 50 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) { switch (Settings.light_scheme) {
case LS_POWER: case LS_POWER:
light_controller.calcLevels(Light.new_color); light_controller.calcLevels(Light.new_color);
@ -1733,13 +1738,13 @@ void LightAnimate(void)
memcpy(Light.fade_end_8, cur_col, sizeof(Light.fade_start_8)); 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)); memcpy(Light.fade_end_10, cur_col_10bits, sizeof(Light.fade_start_10));
Light.fade_running = true; Light.fade_running = true;
Light.fade_counter = 0;
Light.fade_duration = 0; // set the value to zero to force a recompute Light.fade_duration = 0; // set the value to zero to force a recompute
Light.fade_start = 0;
// Fade will applied immediately below // Fade will applied immediately below
} }
} }
if (Light.fade_running) { if (Light.fade_running) {
LightApplyFade(); if (LightApplyFade()) {
// AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d - %d %d %d %d %d"), // 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_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]); // 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]);
@ -1747,13 +1752,21 @@ void LightAnimate(void)
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 // Check if we need to calculate the duration
if (0 == Light.fade_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) // compute the distance between start and and color (max of distance for each channel)
uint32_t distance = 0; uint32_t distance = 0;
for (uint32_t i = 0; i < Light.subtype; i++) { for (uint32_t i = 0; i < Light.subtype; i++) {
@ -1764,11 +1777,11 @@ void LightApplyFade(void) {
if (distance > 0) { if (distance > 0) {
// compute the duration of the animation // compute the duration of the animation
// Note: Settings.light_speed is the number of half-seconds for a 100% fade, // 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) // i.e. light_speed=1 means 1024 steps in 500ms
Light.fade_duration = (distance * Settings.light_speed * 10) / 1024; Light.fade_duration = (distance * Settings.light_speed * 500) / 1023;
if (Settings.save_data) { if (Settings.save_data) {
// Also postpone the save_data for the duration of the Fade (in seconds) // 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); // AddLog_P2(LOG_LEVEL_INFO, PSTR("delay_seconds %d, save_data_counter %d"), delay_seconds, save_data_counter);
if (save_data_counter < delay_seconds) { if (save_data_counter < delay_seconds) {
save_data_counter = delay_seconds; // pospone save_data_counter = delay_seconds; // pospone
@ -1776,16 +1789,18 @@ void LightApplyFade(void) {
} }
} else { } else {
// no fade needed, we keep the duration at zero, it will fallback directly to end of fade // no fade needed, we keep the duration at zero, it will fallback directly to end of fade
Light.fade_running = false;
} }
} }
Light.fade_counter++; uint16_t fade_current = now - Light.fade_start; // number of milliseconds since start of fade
if (Light.fade_counter <= Light.fade_duration) { // fade not finished 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++) { 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, 0, Light.fade_duration,
Light.fade_start_8[i], Light.fade_end_8[i]); 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, 0, Light.fade_duration,
Light.fade_start_10[i], Light.fade_end_10[i]); Light.fade_start_10[i], Light.fade_end_10[i]);
} }
@ -1793,7 +1808,7 @@ void LightApplyFade(void) {
// stop fade // stop fade
//AddLop_P2(LOG_LEVEL_DEBUG, PSTR("Stop fade")); //AddLop_P2(LOG_LEVEL_DEBUG, PSTR("Stop fade"));
Light.fade_running = false; Light.fade_running = false;
Light.fade_counter = 0; Light.fade_start = 0;
Light.fade_duration = 0; Light.fade_duration = 0;
// set light to target value // set light to target value
memcpy(Light.fade_cur_8, Light.fade_end_8, sizeof(Light.fade_end_8)); 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_8, Light.fade_end_8, sizeof(Light.fade_start_8));
memcpy(Light.fade_start_10, Light.fade_end_10, sizeof(Light.fade_start_10)); 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 // 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: case FUNC_SERIAL:
result = XlgtCall(FUNC_SERIAL); result = XlgtCall(FUNC_SERIAL);
break; 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: case FUNC_EVERY_50_MSECOND:
LightAnimate(); LightAnimate();
break; break;

View File

@ -733,12 +733,13 @@ void HandleTimerConfiguration(void)
void TimerSaveSettings(void) void TimerSaveSettings(void)
{ {
char tmp[MAX_TIMERS *12]; // Need space for MAX_TIMERS x 10 digit numbers separated by a comma char tmp[MAX_TIMERS *12]; // Need space for MAX_TIMERS x 10 digit numbers separated by a comma
char message[LOGSZ];
Timer timer; Timer timer;
Settings.flag3.timers_enable = WebServer->hasArg("e0"); // CMND_TIMERS Settings.flag3.timers_enable = WebServer->hasArg("e0"); // CMND_TIMERS
WebGetArg("t0", tmp, sizeof(tmp)); WebGetArg("t0", tmp, sizeof(tmp));
char *p = 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++) { for (uint32_t i = 0; i < MAX_TIMERS; i++) {
timer.data = strtol(p, &p, 10); timer.data = strtol(p, &p, 10);
p++; // Skip comma p++; // Skip comma
@ -747,9 +748,9 @@ void TimerSaveSettings(void)
Settings.timer[i].data = timer.data; Settings.timer[i].data = timer.data;
if (flag) TimerSetRandomWindow(i); 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_TIMERS_WEB
#endif // USE_WEBSERVER #endif // USE_WEBSERVER

View File

@ -174,8 +174,8 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};
#if (MAX_RULE_VARS>16) #if (MAX_RULE_VARS>16)
#error MAX_RULE_VARS is bigger than 16 #error MAX_RULE_VARS is bigger than 16
#endif #endif
#if (MAX_RULE_MEMS>5) #if (MAX_RULE_MEMS>16)
#error MAX_RULE_MEMS is bigger than 5 #error MAX_RULE_MEMS is bigger than 16
#endif #endif
/*******************************************************************************************/ /*******************************************************************************************/
@ -1400,8 +1400,7 @@ bool evaluateLogicalExpression(const char * expression, int len)
memcpy(expbuff, expression, len); memcpy(expbuff, expression, len);
expbuff[len] = '\0'; expbuff[len] = '\0';
//snprintf_P(log_data, sizeof(log_data), PSTR("EvalLogic: |%s|"), expbuff); //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EvalLogic: |%s|"), expbuff);
//AddLog(LOG_LEVEL_DEBUG);
char * pointer = expbuff; char * pointer = expbuff;
LinkedList<bool> values; LinkedList<bool> values;
LinkedList<int8_t> logicOperators; LinkedList<int8_t> logicOperators;
@ -1527,8 +1526,7 @@ void ExecuteCommandBlock(const char * commands, int len)
memcpy(cmdbuff, commands, len); memcpy(cmdbuff, commands, len);
cmdbuff[len] = '\0'; cmdbuff[len] = '\0';
//snprintf_P(log_data, sizeof(log_data), PSTR("ExecCmd: |%s|"), cmdbuff); //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ExecCmd: |%s|"), cmdbuff);
//AddLog(LOG_LEVEL_DEBUG);
char oneCommand[len + 1]; //To put one command char oneCommand[len + 1]; //To put one command
int insertPosition = 0; //When insert into backlog, we should do it by 0, 1, 2 ... int insertPosition = 0; //When insert into backlog, we should do it by 0, 1, 2 ...
char * pos = cmdbuff; char * pos = cmdbuff;

View File

@ -56,7 +56,8 @@ keywords if then else endif, or, and are better readable for beginners (others m
#define SCRIPT_MAXSSIZE 48 #define SCRIPT_MAXSSIZE 48
#define SCRIPT_EOL '\n' #define SCRIPT_EOL '\n'
#define SCRIPT_FLOAT_PRECISION 2 #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 #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 // offsets epoch readings by 1.1.2019 00:00:00 to fit into float with second resolution
@ -1575,7 +1576,7 @@ chknext:
case 'r': case 'r':
if (!strncmp(vname,"ram",3)) { 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; goto exit;
} }
break; break;
@ -2203,8 +2204,7 @@ void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) {
void toLog(const char *str) { void toLog(const char *str) {
if (!str) return; if (!str) return;
snprintf_P(log_data, sizeof(log_data), PSTR("%s"),str); AddLog_P(LOG_LEVEL_INFO, str);
AddLog(LOG_LEVEL_INFO);
} }
@ -2681,8 +2681,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
} }
cmd[count]=*lp++; cmd[count]=*lp++;
} }
//snprintf_P(log_data, sizeof(log_data), tmp); //AddLog_P(LOG_LEVEL_INFO, tmp);
//AddLog(LOG_LEVEL_INFO);
// replace vars in cmd // replace vars in cmd
char *tmp=cmdmem+SCRIPT_CMDMEM/2; char *tmp=cmdmem+SCRIPT_CMDMEM/2;
Replace_Cmd_Vars(cmd,tmp,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 { } else {
if (!sflag) { if (!sflag) {
tasm_cmd_activ=1; tasm_cmd_activ=1;
snprintf_P(log_data, sizeof(log_data), PSTR("Script: performs \"%s\""), tmp); AddLog_P2(glob_script_mem.script_loglevel&0x7f, PSTR("Script: performs \"%s\""), tmp);
AddLog(glob_script_mem.script_loglevel&0x7f);
} else if (sflag==2) { } else if (sflag==2) {
// allow recursive call // allow recursive call
} else { } else {
@ -2995,7 +2993,7 @@ void ScripterEvery100ms(void) {
if (fast_script==99) Run_Scripter(">F",2,0); 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 // can hold 11 floats or floats + strings
// should report overflow later // should report overflow later
void Scripter_save_pvars(void) { 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) { if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index=vtp[count].index; uint8_t index=vtp[count].index;
mlen+=sizeof(float); mlen+=sizeof(float);
if (mlen>MAX_RULE_MEMS*10) { if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0; vtp[count].bits.is_permanent=0;
return; return;
} }
@ -3021,7 +3019,7 @@ void Scripter_save_pvars(void) {
char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize); char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
uint8_t slen=strlen(sp); uint8_t slen=strlen(sp);
mlen+=slen+1; mlen+=slen+1;
if (mlen>MAX_RULE_MEMS*10) { if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0; vtp[count].bits.is_permanent=0;
return; return;
} }
@ -3544,8 +3542,7 @@ void ScriptSaveSettings(void) {
if (bitRead(Settings.rule_enabled, 0)) { if (bitRead(Settings.rule_enabled, 0)) {
int16_t res=Init_Scripter(); int16_t res=Init_Scripter();
if (res) { if (res) {
snprintf_P(log_data, sizeof(log_data), PSTR("script init error: %d"),res); AddLog_P2(LOG_LEVEL_INFO, PSTR("script init error: %d"), res);
AddLog(LOG_LEVEL_INFO);
return; return;
} }
Run_Scripter(">B",2,0); 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_ram=Settings.rules[0];
glob_script_mem.script_size=MAX_SCRIPT_SIZE; glob_script_mem.script_size=MAX_SCRIPT_SIZE;
glob_script_mem.flags=0; glob_script_mem.flags=0;
glob_script_mem.script_pram=(uint8_t*)Settings.mems[0]; glob_script_mem.script_pram=(uint8_t*)Settings.script_pram[0];
glob_script_mem.script_pram_size=MAX_RULE_MEMS*10; glob_script_mem.script_pram_size=PMEM_SIZE;
#ifdef USE_BUTTON_EVENT #ifdef USE_BUTTON_EVENT
for (uint32_t cnt=0;cnt<MAX_KEYS;cnt++) { for (uint32_t cnt=0;cnt<MAX_KEYS;cnt++) {

View File

@ -100,6 +100,12 @@ const char HASS_DISCOVER_SENSOR_HUM[] PROGMEM =
"\"val_tpl\":\"{{value_json['%s'].Humidity}}\"," // "SI7021-14":{"Temperature":null,"Humidity":null} -> {{ value_json['SI7021-14'].Humidity }} "\"val_tpl\":\"{{value_json['%s'].Humidity}}\"," // "SI7021-14":{"Temperature":null,"Humidity":null} -> {{ value_json['SI7021-14'].Humidity }}
"\"dev_cla\":\"humidity\""; // 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 = const char HASS_DISCOVER_SENSOR_PRESS[] PROGMEM =
",\"unit_of_meas\":\"%s\"," // PressureUnit() setting ",\"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 }} "\"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); TryResponseAppend_P(HASS_DISCOVER_SENSOR_AMPERE, sensorname, subsensortype);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_ILLUMINANCE))){ } else if (!strcmp_P(subsensortype, PSTR(D_JSON_ILLUMINANCE))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_ILLUMINANCE, sensorname, subsensortype); 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 { } else {
if (is_sensor){ if (is_sensor){
TryResponseAppend_P(PSTR(",\"unit_of_meas\":\" \"")); // " " As unit of measurement to get a value graph (not available for binary sensors) TryResponseAppend_P(PSTR(",\"unit_of_meas\":\" \"")); // " " As unit of measurement to get a value graph (not available for binary sensors)

View File

@ -365,7 +365,6 @@ void HueLightStatus1(uint8_t device, String *response)
// Any device whose friendly name start with "$" is considered hidden // Any device whose friendly name start with "$" is considered hidden
bool HueActive(uint8_t device) { bool HueActive(uint8_t device) {
if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; } if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
// return '$' != Settings.friendlyname[device-1][0];
return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1); return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
} }

View File

@ -19,6 +19,8 @@
#ifdef USE_ZIGBEE #ifdef USE_ZIGBEE
#define OCCUPANCY "Occupancy" // global define for Aqara
typedef uint64_t Z_IEEEAddress; typedef uint64_t Z_IEEEAddress;
typedef uint16_t Z_ShortAddress; typedef uint16_t Z_ShortAddress;

View File

@ -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); 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 #endif // USE_ZIGBEE

View File

@ -42,6 +42,9 @@ typedef struct Z_Device {
uint16_t endpoint; // endpoint to use for timer uint16_t endpoint; // endpoint to use for timer
uint32_t value; // any raw value to use for the timer uint32_t value; // any raw value to use for the timer
Z_DeviceTimer func; // function to call when timer occurs Z_DeviceTimer func; // function to call when timer occurs
// json buffer used for attribute reporting
DynamicJsonBuffer *json_buffer;
JsonObject *json;
} Z_Device; } Z_Device;
// All devices are stored in a Vector // 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 setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func);
void runTimer(void); 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: private:
std::vector<Z_Device> _devices = {}; std::vector<Z_Device> _devices = {};
@ -173,7 +183,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
std::vector<uint32_t>(), std::vector<uint32_t>(),
std::vector<uint32_t>(), std::vector<uint32_t>(),
0,0,0,0, 0,0,0,0,
nullptr }; nullptr,
nullptr, nullptr };
device.json_buffer = new DynamicJsonBuffer();
_devices.push_back(device); _devices.push_back(device);
return _devices.back(); return _devices.back();
} }
@ -394,14 +406,112 @@ void Z_Devices::runTimer(void) {
uint32_t timer = device.timer; uint32_t timer = device.timer;
if ((timer) && (timer <= now)) { if ((timer) && (timer <= now)) {
device.timer = 0; // cancel the timer before calling, so the callback can set another timer
// trigger the timer // trigger the timer
(*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value); (*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<char*>()) {
String sval = val.as<String>(); // force a copy of the String value
to.set(key, sval);
} else if (val.is<JsonArray>()) {
JsonArray &nested_arr = to.createNestedArray(key);
CopyJsonArray(nested_arr, val.as<JsonArray>());
} else if (val.is<JsonObject>()) {
JsonObject &nested_obj = to.createNestedObject(key);
CopyJsonObject(nested_obj, val.as<JsonObject>());
} else {
to.set(key, val);
}
}
void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
for (auto v : arr) {
if (v.is<char*>()) {
String sval = v.as<String>(); // force a copy of the String value
to.add(sval);
} else if (v.is<JsonArray>()) {
} else if (v.is<JsonObject>()) {
} 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 // Dump the internal memory of Zigbee devices
// Mode = 1: simple dump of devices addresses and names // Mode = 1: simple dump of devices addresses and names

View File

@ -100,6 +100,7 @@ public:
return _frame_control.b.frame_type & 1; 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 parseRawAttributes(JsonObject& json, uint8_t offset = 0);
void parseReadAttributes(JsonObject& json, uint8_t offset = 0); void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
void parseClusterSpecificCommand(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; bool parse_as_string = true;
uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits
i += (attrtype <= 0x42) ? 1 : 2; // increment pointer 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 // check if we can safely use a string
if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; } if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; }
else { // else {
for (uint32_t j = 0; j < len; j++) { // for (uint32_t j = 0; j < len; j++) {
if (0x00 == buf.get8(i+j)) { // if (0x00 == buf.get8(i+j)) {
parse_as_string = false; // parse_as_string = false;
break; // break;
} // }
} // }
} // }
if (parse_as_string) { if (parse_as_string) {
char str[len+1]; 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 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 // First pass, parse all attributes in their native format
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
uint32_t i = offset; uint32_t i = offset;
uint32_t len = _payload.len(); uint32_t len = _payload.len();
while (len - i >= 3) { while (len >= i + 3) {
uint16_t attrid = _payload.get16(i); uint16_t attrid = _payload.get16(i);
i += 2; i += 2;
char key[16]; char key[16];
snprintf_P(key, sizeof(key), PSTR("%04X/%04X"), generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
_cluster_id, attrid);
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char // exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) { if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
@ -445,8 +458,7 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
if (0 == status) { if (0 == status) {
char key[16]; char key[16];
snprintf_P(key, sizeof(key), PSTR("%04X/%04X"), generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
_cluster_id, attrid);
i += parseSingleAttribute(json, key, _payload, i, len); i += parseSingleAttribute(json, key, _payload, i, len);
} }
@ -472,7 +484,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
// return value: // return value:
// 0 = keep initial value // 0 = keep initial value
// 1 = remove 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 { typedef struct Z_AttributeConverter {
uint16_t cluster; uint16_t cluster;
uint16_t attribute; uint16_t attribute;
@ -480,8 +492,6 @@ typedef struct Z_AttributeConverter {
Z_AttrConverter func; Z_AttrConverter func;
} Z_AttributeConverter; } Z_AttributeConverter;
#define OCCUPANCY "Occupancy" // global define for Aqara
// list of post-processing directives // list of post-processing directives
const Z_AttributeConverter Z_PostProcess[] PROGMEM = { const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0000, 0x0000, "ZCLVersion", &Z_Copy }, { 0x0000, 0x0000, "ZCLVersion", &Z_Copy },
@ -511,6 +521,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// On/off cluster // On/off cluster
{ 0x0006, 0x0000, "Power", &Z_Copy }, { 0x0006, 0x0000, "Power", &Z_Copy },
{ 0x0006, 0x8000, "Power", &Z_Copy }, // See 7280
// On/Off Switch Configuration cluster // On/Off Switch Configuration cluster
{ 0x0007, 0x0000, "SwitchType", &Z_Copy }, { 0x0007, 0x0000, "SwitchType", &Z_Copy },
@ -750,7 +761,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values { 0x0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Occupancy Sensing cluster // Occupancy Sensing cluster
{ 0x0406, 0x0000, OCCUPANCY, &Z_AqaraOccupancy }, // Occupancy (map8) { 0x0406, 0x0000, OCCUPANCY, &Z_Copy }, // Occupancy (map8)
{ 0x0406, 0x0001, "OccupancySensorType", &Z_Copy }, // OccupancySensorType { 0x0406, 0x0001, "OccupancySensorType", &Z_Copy }, // OccupancySensorType
{ 0x0406, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values { 0x0406, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
@ -776,13 +787,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// ====================================================================== // ======================================================================
// Record Manuf // 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; json[new_name] = value;
zigbee_devices.setManufId(shortaddr, value.as<const char*>()); zigbee_devices.setManufId(shortaddr, value.as<const char*>());
return 1; 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; json[new_name] = value;
zigbee_devices.setModelId(shortaddr, value.as<const char*>()); zigbee_devices.setModelId(shortaddr, value.as<const char*>());
return 1; return 1;
@ -790,38 +801,34 @@ int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& j
// ====================================================================== // ======================================================================
// Remove attribute // 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 return 1; // remove original key
} }
// Copy value as-is // 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; json[new_name] = value;
return 1; // remove original key return 1; // remove original key
} }
// Add pressure unit // 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); json[new_name] = F(D_UNIT_PRESSURE);
return 0; // keep original key return 0; // keep original key
} }
// Convert int to float and divide by 100 // 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; json[new_name] = ((float)value) / 100.0f;
return 1; // remove original key return 1; // remove original key
} }
// Convert int to float and divide by 10 // 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; json[new_name] = ((float)value) / 10.0f;
return 1; // remove original key return 1; // remove original key
} }
// Publish a message for `"Occupancy":0` when the timer expired
// 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
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) { int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
// send Occupancy:false message // send Occupancy:false message
Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":{\"" OCCUPANCY "\":0}}}"), shortaddr); 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(); 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 // 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; //json[new_name] = value;
switch (attr) { switch (attr) {
case 0x0055: case 0x0055:
@ -896,7 +891,7 @@ int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje
return 1; // remove original key 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; String hex = value;
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint32_t i = 0; uint32_t i = 0;
@ -942,11 +937,19 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
String key_string = kv.key; String key_string = kv.key;
const char * key = key_string.c_str(); const char * key = key_string.c_str();
JsonVariant& value = kv.value; 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 * delimiter = strchr(key, '/');
char * delimiter2 = strchr(key, '+');
if (delimiter) { if (delimiter) {
uint16_t attribute;
uint16_t suffix = 1;
uint16_t cluster = strtoul(key, &delimiter, 16); 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 // Iterate on filter
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) { 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) && if ((conv_cluster == cluster) &&
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) { ((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) { if (drop) {
json.remove(key); json.remove(key);
} }

View File

@ -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_NODE_DESC = 31; // Node descriptor
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints 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_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_VERSION = 50; // Status: CC2530 ZNP Version
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version

View File

@ -357,6 +357,56 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
return -1; 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) { int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint16_t groupid = buf.get16(2); uint16_t groupid = buf.get16(2);
uint16_t clusterid = buf.get16(4); 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); uint32_t timestamp = buf.get32(13);
uint8_t seqnumber = buf.get8(17); uint8_t seqnumber = buf.get8(17);
bool defer_attributes = false; // do we defer attributes reporting to coalesce
zigbee_devices.updateLastSeen(srcaddr); zigbee_devices.updateLastSeen(srcaddr);
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid, ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid,
srcaddr, srcaddr,
@ -384,9 +436,9 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
JsonObject& json1 = json_root.createNestedObject(F(D_CMND_ZIGBEE_RECEIVED)); JsonObject& json1 = json_root.createNestedObject(F(D_CMND_ZIGBEE_RECEIVED));
JsonObject& json = json1.createNestedObject(shortaddr); JsonObject& json = json1.createNestedObject(shortaddr);
// TODO add name field if it is known
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { 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())) { } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadAttributes(json); zcl_received.parseReadAttributes(json);
} else if (zcl_received.isClusterSpecificCommand()) { } else if (zcl_received.isClusterSpecificCommand()) {
@ -401,11 +453,23 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
// Add linkquality // Add linkquality
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality; json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
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 = ""; msg = "";
json_root.printTo(msg); json_root.printTo(msg);
Response_P(PSTR("%s"), msg.c_str()); Response_P(PSTR("%s"), msg.c_str());
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess(); XdrvRulesProcess();
}
return -1; return -1;
} }
@ -417,6 +481,7 @@ typedef struct Z_Dispatcher {
// Filters for ZCL frames // Filters for ZCL frames
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481 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_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_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_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584 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 = { const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage }, { AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
{ AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce }, { AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce },
{ AREQ_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd },
{ AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus }, { AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus },
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc }, { AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp }, { AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },

View File

@ -31,12 +31,14 @@ TasmotaSerial *ZigbeeSerial = nullptr;
const char kZigbeeCommands[] PROGMEM = "|" const char kZigbeeCommands[] PROGMEM = "|"
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" 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 = { void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZigbeeZNPSend, &CmndZigbeePermitJoin, &CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend, &CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend,
&CmndZigbeeProbe, &CmndZigbeeRead }; &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive
};
int32_t ZigbeeProcessInput(class SBuffer &buf) { int32_t ZigbeeProcessInput(class SBuffer &buf) {
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message 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)) { if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
uint8_t code; uint8_t code;
@ -286,11 +288,26 @@ void CmndZigbeeZNPSend(void)
size -= 2; size -= 2;
codes += 2; codes += 2;
} }
if (send) {
ZigbeeZNPSend(buf.getBuffer(), buf.len()); ZigbeeZNPSend(buf.getBuffer(), buf.len());
} else {
ZigbeeProcessInput(buf);
}
} }
ResponseCmndDone(); 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) { void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
if ((len < 2) || (len > 252)) { if ((len < 2) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2 // 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(); 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) { void CmndZigbeeSend(void) {
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} } // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} } // ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }

View File

@ -192,7 +192,7 @@ bool Xdrv29(uint8_t function)
DeepSleepEverySecond(); DeepSleepEverySecond();
break; break;
case FUNC_AFTER_TELEPERIOD: 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 deepsleep_flag = DEEPSLEEP_START_COUNTDOWN; // Start deepsleep in 4 seconds
} }
break; break;

View File

@ -124,8 +124,7 @@ void Ssd1306PrintLog(void)
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
DisplayFillScreen(last_row); DisplayFillScreen(last_row);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
AddLog(LOG_LEVEL_DEBUG);
renderer->println(disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]);
renderer->Updateframe(); renderer->Updateframe();

View File

@ -119,8 +119,7 @@ void SH1106PrintLog(void)
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
DisplayFillScreen(last_row); DisplayFillScreen(last_row);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
AddLog(LOG_LEVEL_DEBUG);
renderer->println(disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]);
renderer->Updateframe(); renderer->Updateframe();

View File

@ -112,8 +112,7 @@ void SSD1351PrintLog(void)
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols); strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
DisplayFillScreen(last_row); DisplayFillScreen(last_row);
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]); AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
AddLog(LOG_LEVEL_DEBUG);
renderer->println(disp_screen_buffer[last_row]); renderer->println(disp_screen_buffer[last_row]);
renderer->Updateframe(); renderer->Updateframe();

View File

@ -66,6 +66,12 @@ void AdcInit(void)
Settings.adc_param2 = ANALOG_LDR_LUX_CALC_SCALAR; Settings.adc_param2 = ANALOG_LDR_LUX_CALC_SCALAR;
Settings.adc_param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000; 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; return (uint16_t)ldrLux;
} }
uint16_t AdcGetMoist(void)
// formula for calibration: value, fromLow, fromHigh, toHigh, toLow
// Example: 632, 0, 1023, 100, 0
// int( ( ( (<param2> - <analogue-value>) / ( <param2> - <param1> ) ) * ( <param3> - <param4> ) ) + <param4> )
// double amoist = ((Settings.adc_param2 - (double)adc) / (Settings.adc_param2 - Settings.adc_param1) * 100;
// int((((1023 - <analog-reading>) / ( 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) void AdcEverySecond(void)
{ {
if (ADC0_TEMP == my_adc0) { if (ADC0_TEMP == my_adc0) {
@ -173,6 +193,17 @@ void AdcShow(bool json)
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
} else { } else {
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, "", adc_light); 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 #endif // USE_WEBSERVER
} }
} }
@ -182,7 +213,6 @@ void AdcShow(bool json)
* Commands * Commands
\*********************************************************************************************/ \*********************************************************************************************/
#define D_CMND_ADCPARAM "AdcParam"
const char kAdcCommands[] PROGMEM = "|" // No prefix const char kAdcCommands[] PROGMEM = "|" // No prefix
D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_ADCPARAM; D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_ADCPARAM;
@ -217,7 +247,7 @@ void CmndAdcs(void)
void CmndAdcParam(void) void CmndAdcParam(void)
{ {
if (XdrvMailbox.data_len) { 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 ((XdrvMailbox.payload == my_adc0) && ((ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0))) {
if (strstr(XdrvMailbox.data, ",") != nullptr) { // Process parameter entry if (strstr(XdrvMailbox.data, ",") != nullptr) { // Process parameter entry
char sub_string[XdrvMailbox.data_len +1]; char sub_string[XdrvMailbox.data_len +1];
@ -227,10 +257,13 @@ void CmndAdcParam(void)
// Settings.adc_param_type = my_adc0; // Settings.adc_param_type = my_adc0;
Settings.adc_param1 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); 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_param2 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 3), nullptr, 10);
if (!ADC0_MOIST == XdrvMailbox.payload) {
Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000); Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000);
}
} else { // Set default values based on current adc type } else { // Set default values based on current adc type
// AdcParam 2 // AdcParam 2
// AdcParam 3 // AdcParam 3
// AdcParam 6
Settings.adc_param_type = 0; Settings.adc_param_type = 0;
AdcInit(); AdcInit();
} }
@ -246,8 +279,14 @@ void CmndAdcParam(void)
} }
char param3[33]; char param3[33];
dtostrfd(((double)Settings.adc_param3)/10000, precision, param3); dtostrfd(((double)Settings.adc_param3)/10000, precision, param3);
if ((ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0)) {
Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d,%s]}"), Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d,%s]}"),
Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2, param3); 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); result = DecodeCommand(kAdcCommands, AdcCommand);
break; break;
default: 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) { switch (function) {
#ifdef USE_RULES #ifdef USE_RULES
case FUNC_EVERY_250_MSECOND: case FUNC_EVERY_250_MSECOND:

View File

@ -75,8 +75,7 @@ void Max4409Detect(void)
if ((I2cValidRead8(&buffer1, max44009_address, REG_LOWER_THRESHOLD)) && if ((I2cValidRead8(&buffer1, max44009_address, REG_LOWER_THRESHOLD)) &&
(I2cValidRead8(&buffer2, max44009_address, REG_THRESHOLD_TIMER))) { (I2cValidRead8(&buffer2, max44009_address, REG_THRESHOLD_TIMER))) {
//snprintf(log_data, sizeof(log_data), "MAX44009 %x: %x, %x", max44009_address, (int)buffer1, (int)buffer2); //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("MAX44009 %x: %x, %x"), max44009_address, (int)buffer1, (int)buffer2);
//AddLog(LOG_LEVEL_DEBUG_MORE);
if ((0x00 == buffer1) && if ((0x00 == buffer1) &&
(0xFF == buffer2)) { (0xFF == buffer2)) {

View File

@ -23,6 +23,8 @@
#define XSNS_42 42 #define XSNS_42 42
#define XI2C_29 29 // See I2CDEVICES.md #define XI2C_29 29 // See I2CDEVICES.md
//#define SCD30_DEBUG
#define SCD30_ADDRESS 0x61 #define SCD30_ADDRESS 0x61
#define SCD30_MAX_MISSED_READS 3 #define SCD30_MAX_MISSED_READS 3
@ -118,24 +120,23 @@ void Scd30Update(void)
scd30ErrorState = SCD30_STATE_ERROR_DATA_CRC; scd30ErrorState = SCD30_STATE_ERROR_DATA_CRC;
scd30CrcError_count++; scd30CrcError_count++;
#ifdef SCD30_DEBUG #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_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"),
AddLog(LOG_LEVEL_ERROR); scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
#endif #endif
break; break;
case ERROR_SCD30_CO2_ZERO: case ERROR_SCD30_CO2_ZERO:
scd30Co2Zero_count++; scd30Co2Zero_count++;
#ifdef SCD30_DEBUG #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_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"),
AddLog(LOG_LEVEL_ERROR); scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
#endif #endif
break; break;
default: { default: {
scd30ErrorState = SCD30_STATE_ERROR_READ_MEAS; scd30ErrorState = SCD30_STATE_ERROR_READ_MEAS;
#ifdef SCD30_DEBUG #ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld", error, scd30Loop_count); AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld"), error, scd30Loop_count);
AddLog(LOG_LEVEL_ERROR);
#endif #endif
return; return;
} }
@ -147,10 +148,9 @@ void Scd30Update(void)
case SCD30_STATE_ERROR_DATA_CRC: { case SCD30_STATE_ERROR_DATA_CRC: {
//scd30IsDataValid = false; //scd30IsDataValid = false;
#ifdef SCD30_DEBUG #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_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
AddLog(LOG_LEVEL_ERROR); scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
snprintf_P(log_data, sizeof(log_data), "SCD30: got CRC error, try again, counter: %ld", scd30Loop_count); AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: got CRC error, try again, counter: %ld"), scd30Loop_count);
AddLog(LOG_LEVEL_ERROR);
#endif #endif
scd30ErrorState = ERROR_SCD30_NO_ERROR; scd30ErrorState = ERROR_SCD30_NO_ERROR;
} }
@ -159,17 +159,15 @@ void Scd30Update(void)
case SCD30_STATE_ERROR_READ_MEAS: { case SCD30_STATE_ERROR_READ_MEAS: {
//scd30IsDataValid = false; //scd30IsDataValid = false;
#ifdef SCD30_DEBUG #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_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
AddLog(LOG_LEVEL_ERROR); scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
snprintf_P(log_data, sizeof(log_data), "SCD30: not answering, sending soft reset, counter: %ld", scd30Loop_count); AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: not answering, sending soft reset, counter: %ld"), scd30Loop_count);
AddLog(LOG_LEVEL_ERROR);
#endif #endif
scd30Reset_count++; scd30Reset_count++;
error = scd30.softReset(); error = scd30.softReset();
if (error) { if (error) {
#ifdef SCD30_DEBUG #ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: resetting got error: 0x%lX", error); AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: resetting got error: 0x%lX"), error);
AddLog(LOG_LEVEL_ERROR);
#endif #endif
error >>= 8; error >>= 8;
if (error == 4) { if (error == 4) {
@ -186,18 +184,16 @@ void Scd30Update(void)
case SCD30_STATE_ERROR_SOFT_RESET: { case SCD30_STATE_ERROR_SOFT_RESET: {
//scd30IsDataValid = false; //scd30IsDataValid = false;
#ifdef SCD30_DEBUG #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_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
AddLog(LOG_LEVEL_ERROR); scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
snprintf_P(log_data, sizeof(log_data), "SCD30: clearing i2c bus"); AddLog_P(LOG_LEVEL_ERROR, PSTR("SCD30: clearing i2c bus"));
AddLog(LOG_LEVEL_ERROR);
#endif #endif
i2cReset_count++; i2cReset_count++;
error = scd30.clearI2CBus(); error = scd30.clearI2CBus();
if (error) { if (error) {
scd30ErrorState = SCD30_STATE_ERROR_I2C_RESET; scd30ErrorState = SCD30_STATE_ERROR_I2C_RESET;
#ifdef SCD30_DEBUG #ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: error clearing i2c bus: 0x%lX", error); AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error clearing i2c bus: 0x%lX"), error);
AddLog(LOG_LEVEL_ERROR);
#endif #endif
} else { } else {
scd30ErrorState = ERROR_SCD30_NO_ERROR; scd30ErrorState = ERROR_SCD30_NO_ERROR;
@ -342,8 +338,7 @@ bool Scd30CommandSensor()
if (error) if (error)
{ {
#ifdef SCD30_DEBUG #ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: error getting FW version: 0x%lX", error); AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error getting FW version: 0x%lX"), error);
AddLog(LOG_LEVEL_ERROR);
#endif #endif
serviced = false; serviced = false;
} }

View File

@ -397,22 +397,17 @@ void ChirpEvery100MSecond(void)
} }
/********************************************************************************************/ /********************************************************************************************/
// normaly in i18n.h // normaly in i18n.h
#define D_JSON_MOISTURE "Moisture"
#define D_JSON_DARKNESS "Darkness"
#ifdef USE_WEBSERVER #ifdef USE_WEBSERVER
// {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr> // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
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_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}"
const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}"
"{s} FW-version{m}%s {e}"; ; "{s} FW-version{m}%s {e}"; ;
const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}"; const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}";
#endif // USE_WEBSERVER #endif // USE_WEBSERVER
/********************************************************************************************/ /********************************************************************************************/
void ChirpShow(bool json) void ChirpShow(bool json)
@ -420,8 +415,6 @@ void ChirpShow(bool json)
for (uint32_t i = 0; i < chirp_found_sensors; i++) { for (uint32_t i = 0; i < chirp_found_sensors; i++) {
if (chirp_sensor[i].version) { if (chirp_sensor[i].version) {
// convert double values to string // convert double values to string
char str_moisture[33];
dtostrfd(chirp_sensor[i].moisture, 0, str_moisture);
char str_temperature[33]; char str_temperature[33];
double t_temperature = ((double) chirp_sensor[i].temperature )/10.0; double t_temperature = ((double) chirp_sensor[i].temperature )/10.0;
dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature); dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature);
@ -434,9 +427,10 @@ void ChirpShow(bool json)
else{ else{
sprintf(str_version, "%x", chirp_sensor[i].version); sprintf(str_version, "%x", chirp_sensor[i].version);
} }
if (json) { if (json) {
if(!chirp_sensor[i].explicitSleep) { 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 if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature); ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature);
} }
@ -447,6 +441,8 @@ void ChirpShow(bool json)
} }
#ifdef USE_DOMOTICZ #ifdef USE_DOMOTICZ
if (0 == tele_period) { if (0 == tele_period) {
char str_moisture[33];
dtostrfd(chirp_sensor[i].moisture, 0, str_moisture);
DomoticzTempHumSensor(str_temperature, str_moisture); DomoticzTempHumSensor(str_temperature, str_moisture);
DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); // this is not LUX!! DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); // this is not LUX!!
} }
@ -458,10 +454,10 @@ void ChirpShow(bool json)
WSContentSend_PD(HTTP_SNS_CHIRPSLEEP); WSContentSend_PD(HTTP_SNS_CHIRPSLEEP);
} }
else { else {
WSContentSend_PD(HTTP_SNS_MOISTURE, str_moisture); WSContentSend_PD(HTTP_SNS_MOISTURE, "", chirp_sensor[i].moisture);
WSContentSend_PD(HTTP_SNS_DARKNESS, str_light); WSContentSend_PD(HTTP_SNS_DARKNESS, str_light);
if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature if (chirp_sensor[i].temperature!=-1) { // this is the error code -> no temperature
WSContentSend_PD(HTTP_SNS_TEMP, " ",str_temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_TEMP, "", str_temperature, TempUnit());
} }
} }

View File

@ -493,19 +493,19 @@ const uint8_t meter[]=
//===================================================== //=====================================================
// median filter eliminates outliers, but uses much RAM and CPU cycles // 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 // default compile on, but must be enabled by descriptor flag 16
// may be undefined if RAM must be saved // may be undefined if RAM must be saved
#define USE_SML_MEDIAN_FILTER #define USE_SML_MEDIAN_FILTER
// max number of vars , may be adjusted // max number of vars , may be adjusted
#ifndef MAX_VARS #ifndef SML_MAX_VARS
#define MAX_VARS 20 #define SML_MAX_VARS 20
#endif #endif
// max number of meters , may be adjusted // max number of meters , may be adjusted
#define MAX_METERS 5 #define MAX_METERS 5
double meter_vars[MAX_VARS]; double meter_vars[SML_MAX_VARS];
// calulate deltas // calulate deltas
#define MAX_DVARS MAX_METERS*2 #define MAX_DVARS MAX_METERS*2
double dvalues[MAX_DVARS]; double dvalues[MAX_DVARS];
@ -540,7 +540,7 @@ uint8_t sml_desc_cnt;
struct SML_MEDIAN_FILTER { struct SML_MEDIAN_FILTER {
double buffer[MEDIAN_SIZE]; double buffer[MEDIAN_SIZE];
int8_t index; int8_t index;
} sml_mf[MAX_VARS]; } sml_mf[SML_MAX_VARS];
#ifndef FLT_MAX #ifndef FLT_MAX
#define FLT_MAX 99999999 #define FLT_MAX 99999999
@ -1326,7 +1326,7 @@ void SML_Decode(uint8_t index) {
uint32_t ind; uint32_t ind;
ind=atoi(mp); ind=atoi(mp);
while (*mp>='0' && *mp<='9') 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]; dvar=meter_vars[ind-1];
for (uint8_t p=0;p<5;p++) { for (uint8_t p=0;p<5;p++) {
if (*mp=='@') { if (*mp=='@') {
@ -1345,7 +1345,7 @@ void SML_Decode(uint8_t index) {
} }
ind=atoi(mp); ind=atoi(mp);
while (*mp>='0' && *mp<='9') 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) { switch (opr) {
case '+': case '+':
if (iflg) dvar+=ind; if (iflg) dvar+=ind;
@ -1381,7 +1381,7 @@ void SML_Decode(uint8_t index) {
while (*mp==' ') mp++; while (*mp==' ') mp++;
uint8_t ind=atoi(mp); uint8_t ind=atoi(mp);
while (*mp>='0' && *mp<='9') 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 delay=atoi(mp)*1000;
uint32_t dtime=millis()-dtimes[dindex]; uint32_t dtime=millis()-dtimes[dindex];
if (dtime>delay) { if (dtime>delay) {
@ -1604,7 +1604,7 @@ void SML_Decode(uint8_t index) {
} }
nextsect: nextsect:
// next section // next section
if (vindex<MAX_VARS-1) { if (vindex<SML_MAX_VARS-1) {
vindex++; vindex++;
} }
mp = strchr(mp, '|'); mp = strchr(mp, '|');
@ -1770,7 +1770,7 @@ void SML_Show(boolean json) {
} }
} }
} }
if (index<MAX_VARS-1) { if (index<SML_MAX_VARS-1) {
index++; index++;
} }
// next section // next section
@ -1864,7 +1864,9 @@ struct METER_DESC script_meter_desc[MAX_METERS];
uint8_t *script_meter; uint8_t *script_meter;
#endif #endif
#define METER_DEF_SIZE 2000 #ifndef METER_DEF_SIZE
#define METER_DEF_SIZE 3000
#endif
bool Gpio_used(uint8_t gpiopin) { bool Gpio_used(uint8_t gpiopin) {
for (uint16_t i=0;i<GPIO_SENSOR_END;i++) { for (uint16_t i=0;i<GPIO_SENSOR_END;i++) {
@ -1882,7 +1884,7 @@ void SML_Init(void) {
sml_desc_cnt=0; sml_desc_cnt=0;
for (uint32_t cnt=0;cnt<MAX_VARS;cnt++) { for (uint32_t cnt=0;cnt<SML_MAX_VARS;cnt++) {
meter_vars[cnt]=0; meter_vars[cnt]=0;
} }

860
tasmota/xsns_60_GPS.ino Normal file
View File

@ -0,0 +1,860 @@
/*
xsns_60_GPS.ino - GPS UBLOX support for Tasmota
Copyright (C) 2019 Theo Arends, Christian Baars and Adrian Scillato
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef USE_GPS
/*********************************************************************************************\
--------------------------------------------------------------------------------------------
Version Date Action Description
--------------------------------------------------------------------------------------------
0.9.1.0 20191216 integrate - Added pin specifications from Tasmota WEB UI. Minor tweaks.
---
0.9.0.0 20190817 started - further development by Christian Baars - https://github.com/Staars/Sonoff-Tasmota
forked - from arendst/tasmota - https://github.com/arendst/Sonoff-Tasmota
base - code base from arendst and - https://www.youtube.com/watch?v=TwhCX0c8Xe0
## GPS-driver for the Ublox-series 6-8
Driver is tested on a NEO-6m and a Beitian-220. Series 7 should work too. This adds only about 6kb to the program size, because the efficient UBX-protocol is used. These modules are quite cheap, starting at about 3.50 for the NEO-6m.
## Features:
- get position and time data
- sets system time automatically and Settings.latitude and Settings.longitude via command
- can log postion data with timestamp to flash with a small memory footprint of only 12 Bytes per record
- constructs a GPX-file for download of this data
- Web-UI
- simplified NTP-server
- command interface
## Usage:
The serial pins are GPX_RX and GPS_TX, no further installation steps needed. To get more debug information compile it with option "DEBUG_TASMOTA_SENSOR".
## Commands:
+ sensor60 0
write to all available sectors, then restart and overwrite the older ones
+ sensor60 1
write to all available sectors, then restart and overwrite the older ones
+ sensor60 2
filter out horizontal drift noise
+ sensor60 3
turn off noise filter
+ sensor60 4
start recording, new data will be appended
+ sensor60 5
start new recording, old data will lost
+ sensor60 6
stop recording, download link will be visible in Web-UI
+ sensor60 7
send mqtt on new postion + TELE -> consider to set TELE to a very high value
+ sensor60 8
only TELE message
+ sensor60 9
start NTP-server
+ sensor60 10
deactivate NTP-server
+ sensor60 11
force update of Tasmota-system-UTC with every new GPS-time-message
+ sensor60 12
do not update of Tasmota-system-UTC with every new GPS-time-message
+ sensor60 13
set latitude and longitude in settings
## Rules examples for SSD1306 32x128
rule1 on tele-GPS#lat do DisplayText [s1p21c1l01f1]LAT: %value% endon on tele-GPS#lon do DisplayText [s1p21c1l2]LON: %value% endon on switch1#state==3 do sensor60 4 endon on switch1#state==2 do sensor60 6 endon
rule2 on tele-GPS#int>9 do DisplayText [f0c9l4]I%value% endon on tele-GPS#int<10 do DisplayText [f0c9l4]I0%value% endon on tele-GPS#fil==1 do DisplayText [f0c18l4]F endon on tele-GPS#fil==0 do DisplayText [f0c18l4]N endon
rule3 on tele-FLOG#sec do DisplayText [f0c1l4]SAV:%value% endon on tele-FLOG#rec==1 do DisplayText [f0c1l4]REC: endon on tele-FLOG#mode do DisplayText [f0c14l4]M%value% endon
\*********************************************************************************************/
#define XSNS_60 60
#include "NTPServer.h"
#include "NTPPacket.h"
/*********************************************************************************************\
* constants
\*********************************************************************************************/
#define D_CMND_UBX "UBX"
const char S_JSON_UBX_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_UBX "%s\":%d}";
const char kUBXTypes[] PROGMEM = "UBX";
#define UBX_LAT_LON_THRESHOLD 1000 // filter out some noise of local drift
/********************************************************************************************\
| *globals
\*********************************************************************************************/
const char UBLOX_INIT[] PROGMEM = {
// Disable NMEA
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x24, // GxGGA off
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x2B, // GxGLL off
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x02,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x32, // GxGSA off
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x39, // GxGSV off
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x04,0x00,0x00,0x00,0x00,0x00,0x01,0x04,0x40, // GxRMC off
0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x05,0x00,0x00,0x00,0x00,0x00,0x01,0x05,0x47, // GxVTG off
// Disable UBX
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xDC, //NAV-PVT off
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0xB9, //NAV-POSLLH off
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xC0, //NAV-STATUS off
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0x92, //NAV-TIMEUTC off
// Enable UBX
// 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x01,0x00,0x00,0x00,0x00,0x18,0xE1, //NAV-PVT on
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x13,0xBE, //NAV-POSLLH on
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x14,0xC5, //NAV-STATUS on
0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x01,0x00,0x00,0x00,0x00,0x32,0x97, //NAV-TIMEUTC on
// Rate - we will not reset it for the moment after restart
// 0xB5,0x62,0x06,0x08,0x06,0x00,0x64,0x00,0x01,0x00,0x01,0x00,0x7A,0x12, //(10Hz)
// 0xB5,0x62,0x06,0x08,0x06,0x00,0xC8,0x00,0x01,0x00,0x01,0x00,0xDE,0x6A, //(5Hz)
// 0xB5,0x62,0x06,0x08,0x06,0x00,0xE8,0x03,0x01,0x00,0x01,0x00,0x01,0x39 //(1Hz)
// 0xB5,0x62,0x06,0x08,0x06,0x00,0xD0,0x07,0x01,0x00,0x01,0x00,0xED,0xBD //(0.5Hz)
};
char UBX_name[4];
struct UBX_t {
const char UBX_HEADER[2] = { 0xB5, 0x62 }; // TODO: Check if we really save space here inside the struct
const char NAV_POSLLH_HEADER[2] = { 0x01, 0x02 };
const char NAV_STATUS_HEADER[2] = { 0x01, 0x03 };
const char NAV_TIME_HEADER[2] = { 0x01, 0x21 };
struct entry_t {
int32_t lat; //raw sensor value
int32_t lon; //raw sensor value
uint32_t time; //local time from system (maybe provided by the sensor)
};
union {
entry_t values;
uint8_t bytes[sizeof(entry_t)];
} rec_buffer;
struct POLL_MSG {
uint8_t cls;
uint8_t id;
uint16_t zero;
};
struct NAV_POSLLH {
uint8_t cls;
uint8_t id;
uint16_t len;
uint32_t iTOW;
int32_t lon;
int32_t lat;
int32_t height;
int32_t hMSL;
uint32_t hAcc;
uint32_t vAcc;
};
struct NAV_STATUS {
uint8_t cls;
uint8_t id;
uint16_t len;
uint32_t iTOW;
uint8_t gpsFix;
uint8_t flags; //bit 0 - gpsfix valid
uint8_t fixStat;
uint8_t flags2;
uint32_t ttff;
uint32_t msss;
};
struct NAV_TIME_UTC {
uint8_t cls;
uint8_t id;
uint16_t len;
uint32_t iTOW;
uint32_t tAcc;
int32_t nano; // Nanoseconds of second, range -1e9 .. 1e9 (UTC)
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t min;
uint8_t sec;
struct {
uint8_t UTC:1;
uint8_t WKN:1; // week number
uint8_t TOW:1; // time of week
uint8_t padding:5;
} valid;
};
struct CFG_RATE {
uint8_t cls; //0x06
uint8_t id; //0x08
uint16_t len; // 6 bytes
uint16_t measRate; // in every ms -> 1 Hz = 1000 ms; 10 Hz = 100 ms -> x = 1000 ms / Hz
uint16_t navRate; // x measurements for 1 navigation event
uint16_t timeRef; // align to time system: 0= UTC, 1 = GPS, 2 = GLONASS, ...
char CK[2]; // checksum
};
struct {
uint32_t last_iTOW;
int32_t last_lat;
int32_t last_lon;
int32_t last_height;
uint32_t last_hAcc;
uint32_t last_vAcc;
uint8_t gpsFix;
uint8_t non_empty_loops; // in case of an unintended reset of the GPS, the serial interface will get flooded with NMEA
uint16_t log_interval; // in tenth of seconds
} state;
struct {
uint32_t filter_noise:1;
uint32_t send_when_new:1; // no teleinterval
uint32_t send_UI_only:1;
uint32_t runningNTP:1;
uint32_t forceUTCupdate:1;
// TODO: more to come
} mode;
union {
NAV_POSLLH navPosllh;
NAV_STATUS navStatus;
NAV_TIME_UTC navTime;
POLL_MSG pollMsg;
CFG_RATE cfgRate;
} Message;
} UBX;
enum UBXMsgType {
MT_NONE,
MT_NAV_POSLLH,
MT_NAV_STATUS,
MT_NAV_TIME,
MT_POLL
};
#ifdef USE_FLOG
FLOG *Flog = nullptr;
#endif //USE_FLOG
TasmotaSerial *UBXSerial;
NtpServer timeServer(PortUdp);
/*********************************************************************************************\
* helper function
\*********************************************************************************************/
void UBXcalcChecksum(char* CK, size_t msgSize)
{
memset(CK, 0, 2);
for (int i = 0; i < msgSize; i++) {
CK[0] += ((char*)(&UBX.Message))[i];
CK[1] += CK[0];
}
}
bool UBXcompareMsgHeader(const char* msgHeader)
{
char* ptr = (char*)(&UBX.Message);
return ptr[0] == msgHeader[0] && ptr[1] == msgHeader[1];
}
void UBXinitCFG(void)
{
for (uint32_t i = 0; i < sizeof(UBLOX_INIT); i++) {
UBXSerial->write( pgm_read_byte(UBLOX_INIT+i) );
}
DEBUG_SENSOR_LOG(PSTR("UBX: turn off NMEA"));
}
void UBXTriggerTele(void)
{
mqtt_data[0] = '\0';
if (MqttShowSensor()) {
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
#ifdef USE_RULES
RulesTeleperiod(); // Allow rule based HA messages
#endif // USE_RULES
}
}
/********************************************************************************************/
void UBXDetect(void)
{
if ((pin[GPIO_GPS_RX] < 99) && (pin[GPIO_GPS_TX] < 99)) {
UBXSerial = new TasmotaSerial(pin[GPIO_GPS_RX], pin[GPIO_GPS_TX], 1, 0, 96); // 64 byte buffer is NOT enough
if (UBXSerial->begin(9600)) {
DEBUG_SENSOR_LOG(PSTR("UBX: started serial"));
if (UBXSerial->hardwareSerial()) {
ClaimSerial();
DEBUG_SENSOR_LOG(PSTR("UBX: claim HW"));
}
}
}
UBXinitCFG(); // turn of NMEA, only use "our" UBX-messages
#ifdef USE_FLOG
if (!Flog) {
Flog = new FLOG; // init Flash Log
Flog->init();
}
#endif // USE_FLOG
UBX.state.log_interval = 10; // 1 second
UBX.mode.send_UI_only = true; // send UI data ...
UBXTriggerTele(); // ... once at after start
}
uint32_t UBXprocessGPS()
{
static uint32_t fpos = 0;
static char checksum[2];
static uint8_t currentMsgType = MT_NONE;
static size_t payloadSize = sizeof(UBX.Message);
// DEBUG_SENSOR_LOG(PSTR("UBX: check for serial data"));
uint32_t data_bytes = 0;
while ( UBXSerial->available() ) {
data_bytes++;
byte c = UBXSerial->read();
if ( fpos < 2 ) {
// For the first two bytes we are simply looking for a match with the UBX header bytes (0xB5,0x62)
if ( c == UBX.UBX_HEADER[fpos] ) {
fpos++;
} else {
fpos = 0; // Reset to beginning state.
}
} else {
// If we come here then fpos >= 2, which means we have found a match with the UBX_HEADER
// and we are now reading in the bytes that make up the payload.
// Place the incoming byte into the ubxMessage struct. The position is fpos-2 because
// the struct does not include the initial two-byte header (UBX_HEADER).
if ( (fpos-2) < payloadSize ) {
((char*)(&UBX.Message))[fpos-2] = c;
}
fpos++;
if ( fpos == 4 ) {
// We have just received the second byte of the message type header,
// so now we can check to see what kind of message it is.
if ( UBXcompareMsgHeader(UBX.NAV_POSLLH_HEADER) ) {
currentMsgType = MT_NAV_POSLLH;
payloadSize = sizeof(UBX_t::NAV_POSLLH);
DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_POSLLH"));
}
else if ( UBXcompareMsgHeader(UBX.NAV_STATUS_HEADER) ) {
currentMsgType = MT_NAV_STATUS;
payloadSize = sizeof(UBX_t::NAV_STATUS);
DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_STATUS"));
}
else if ( UBXcompareMsgHeader(UBX.NAV_TIME_HEADER) ) {
currentMsgType = MT_NAV_TIME;
payloadSize = sizeof(UBX_t::NAV_TIME_UTC);
DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_TIME_UTC"));
}
else {
// unknown message type, bail
fpos = 0;
continue;
}
}
if ( fpos == (payloadSize+2) ) {
// All payload bytes have now been received, so we can calculate the
// expected checksum value to compare with the next two incoming bytes.
UBXcalcChecksum(checksum, payloadSize);
}
else if ( fpos == (payloadSize+3) ) {
// First byte after the payload, ie. first byte of the checksum.
// Does it match the first byte of the checksum we calculated?
if ( c != checksum[0] ) {
// Checksum doesn't match, reset to beginning state and try again.
fpos = 0;
}
}
else if ( fpos == (payloadSize+4) ) {
// Second byte after the payload, ie. second byte of the checksum.
// Does it match the second byte of the checksum we calculated?
fpos = 0; // We will reset the state regardless of whether the checksum matches.
if ( c == checksum[1] ) {
// Checksum matches, we have a valid message.
return currentMsgType;
}
}
else if ( fpos > (payloadSize+4) ) {
// We have now read more bytes than both the expected payload and checksum
// together, so something went wrong. Reset to beginning state and try again.
fpos = 0;
}
}
}
// DEBUG_SENSOR_LOG(PSTR("UBX: got none or unknown Message"));
if (data_bytes!=0) {
UBX.state.non_empty_loops++;
DEBUG_SENSOR_LOG(PSTR("UBX: got %u bytes, non-empty-loop: %u"), data_bytes, UBX.state.non_empty_loops);
} else {
UBX.state.non_empty_loops = 0; // now a hidden GPS-device reset is unlikely
}
return MT_NONE;
}
/********************************************************************************************\
| * callback functions for the download
\*********************************************************************************************/
#ifdef USE_FLOG
void UBXsendHeader(void)
{
WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN);
WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=TASMOTA.gpx"));
WSSend(200, CT_STREAM, F(
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\r\n"
"<GPX version=\"1.1\" creator=\"TASMOTA\" xmlns=\"http://www.topografix.com/GPX/1/1\" \r\n"
"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
"xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\r\n"
"<trk>\r\n<trkseg>\r\n"));
}
void UBXsendRecord(uint8_t *buf)
{
char record[100];
char stime[32];
UBX_t::entry_t *entry = (UBX_t::entry_t*)buf;
snprintf_P(stime, sizeof(stime), GetDT(entry->time).c_str());
char lat[12];
char lon[12];
dtostrfd((double)entry->lat/10000000.0f,7,lat);
dtostrfd((double)entry->lon/10000000.0f,7,lon);
snprintf_P(record, sizeof(record),PSTR("<trkpt\n\t lat=\"%s\" lon=\"%s\">\n\t<time>%s</time>\n</trkpt>\n"),lat ,lon, stime);
// DEBUG_SENSOR_LOG(PSTR("FLOG: DL %u %u"), Flog->sector.dword_buffer[k+j],Flog->sector.dword_buffer[k+j+1]);
WebServer->sendContent_P(record);
}
void UBXsendFooter(void)
{
WebServer->sendContent(F("</trkseg>\n</trk>\n</gpx>"));
WebServer->sendContent("");
Rtc.user_time_entry = false; // we have blocked the main loop and want a new valid time
}
/********************************************************************************************/
void UBXsendFile(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
Flog->startDownload(sizeof(UBX.rec_buffer),UBXsendHeader,UBXsendRecord,UBXsendFooter);
}
#endif //USE_FLOG
/********************************************************************************************/
void UBXSetRate(uint16_t interval)
{
UBX.Message.cfgRate.cls = 0x06;
UBX.Message.cfgRate.id = 0x08;
UBX.Message.cfgRate.len = 6;
uint32_t measRate = (1000*(uint32_t)interval); //seconds to milliseconds
if (measRate > 0xffff) {
measRate = 0xffff; // max. 65535 ms interval
}
UBX.Message.cfgRate.measRate = (uint16_t)measRate;
UBX.Message.cfgRate.navRate = 1;
UBX.Message.cfgRate.timeRef = 1;
UBXcalcChecksum(UBX.Message.cfgRate.CK, sizeof(UBX.Message.cfgRate)-sizeof(UBX.Message.cfgRate.CK));
DEBUG_SENSOR_LOG(PSTR("UBX: requested interval: %u seconds measRate: %u ms"), interval, UBX.Message.cfgRate.measRate);
UBXSerial->write(UBX.UBX_HEADER[0]);
UBXSerial->write(UBX.UBX_HEADER[1]);
for (uint32_t i =0; i<sizeof(UBX.Message.cfgRate); i++) {
UBXSerial->write(((uint8_t*)(&UBX.Message.cfgRate))[i]);
DEBUG_SENSOR_LOG(PSTR("UBX: cfgRate byte %u: %x"), i, ((uint8_t*)(&UBX.Message.cfgRate))[i]);
}
UBX.state.log_interval = 10*interval;
}
void UBXSelectMode(uint16_t mode)
{
DEBUG_SENSOR_LOG(PSTR("UBX: set mode to %u"),mode);
switch(mode){
#ifdef USE_FLOG
case 0:
Flog->mode = 0; // write once to all available sectors, then stop
break;
case 1:
Flog->mode = 1; // write to all available sectors, then restart and overwrite the older ones
break;
case 2:
UBX.mode.filter_noise = true; // filter out horizontal drift noise, TODO: find useful values
break;
case 3:
UBX.mode.filter_noise = false;
break;
case 4:
Flog->startRecording(true);
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - appending"));
break;
case 5:
Flog->startRecording(false);
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - new log"));
break;
case 6:
if(Flog->recording == true){
Flog->stopRecording();
}
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: stop recording"));
break;
#endif //USE_FLOG
case 7:
UBX.mode.send_when_new = 1; // send mqtt on new postion + TELE -> consider to set TELE to a very high value
break;
case 8:
UBX.mode.send_when_new = 0; // only TELE
break;
case 9:
if (timeServer.beginListening()) {
UBX.mode.runningNTP = true;
}
break;
case 10:
UBX.mode.runningNTP = false;
break;
case 11:
UBX.mode.forceUTCupdate = true;
break;
case 12:
UBX.mode.forceUTCupdate = false;
break;
case 13:
Settings.latitude = UBX.state.last_lat;
Settings.longitude = UBX.state.last_lon;
break;
default:
if (mode>1000 && mode <1066) {
// UBXSetRate(mode-1000); // min. 1001 = 0.001 Hz, but will be converted to 1/65535 anyway ~0.015 Hz, max. 2000 = 1.000 Hz
UBXSetRate(mode-1000); // set interval between measurements in seconds from 1 to 65
}
break;
}
UBX.mode.send_UI_only = true;
UBXTriggerTele();
}
/********************************************************************************************/
bool UBXHandlePOSLLH()
{
DEBUG_SENSOR_LOG(PSTR("UBX: iTOW: %u"),UBX.Message.navPosllh.iTOW);
if (UBX.state.gpsFix>1) {
if (UBX.mode.filter_noise) {
if ((UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat<abs(UBX_LAT_LON_THRESHOLD))||(UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon<abs(UBX_LAT_LON_THRESHOLD))) {
DEBUG_SENSOR_LOG(PSTR("UBX: Diff lat: %u lon: %u "),UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat, UBX.Message.navPosllh.lon-UBX.rec_buffer.values.lon);
return false; //no new position
}
}
UBX.rec_buffer.values.lat = UBX.Message.navPosllh.lat;
UBX.rec_buffer.values.lon = UBX.Message.navPosllh.lon;
DEBUG_SENSOR_LOG(PSTR("UBX: lat/lon: %i / %i"), UBX.rec_buffer.values.lat, UBX.rec_buffer.values.lon);
DEBUG_SENSOR_LOG(PSTR("UBX: hAcc: %d"), UBX.Message.navPosllh.hAcc);
UBX.state.last_iTOW = UBX.Message.navPosllh.iTOW;
UBX.state.last_height = UBX.Message.navPosllh.height;
UBX.state.last_vAcc = UBX.Message.navPosllh.vAcc;
UBX.state.last_hAcc = UBX.Message.navPosllh.hAcc;
if (UBX.mode.send_when_new) {
UBXTriggerTele();
}
return true; // new position
} else {
DEBUG_SENSOR_LOG(PSTR("UBX: no valid position data"));
}
return false; // no GPS-fix
}
void UBXHandleSTATUS()
{
DEBUG_SENSOR_LOG(PSTR("UBX: gpsFix: %u, valid: %u"), UBX.Message.navStatus.gpsFix, (UBX.Message.navStatus.flags)&1);
if ((UBX.Message.navStatus.flags)&1) {
UBX.state.gpsFix = UBX.Message.navStatus.gpsFix; //only store fixed status if flag is valid
} else {
UBX.state.gpsFix = 0; // without valid flag, everything is "no fix"
}
}
void UBXHandleTIME()
{
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time: %u-%u-%u %u:%u:%u"), UBX.Message.navTime.year, UBX.Message.navTime.month ,UBX.Message.navTime.day,UBX.Message.navTime.hour,UBX.Message.navTime.min,UBX.Message.navTime.sec);
if (UBX.Message.navTime.valid.UTC) {
DEBUG_SENSOR_LOG(PSTR("UBX: UTC-Time is valid"));
if (Rtc.user_time_entry == false || UBX.mode.forceUTCupdate) {
AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: UTC-Time is valid, set system time"));
TIME_T gpsTime;
gpsTime.year = UBX.Message.navTime.year - 1970;
gpsTime.month = UBX.Message.navTime.month;
gpsTime.day_of_month = UBX.Message.navTime.day;
gpsTime.hour = UBX.Message.navTime.hour;
gpsTime.minute = UBX.Message.navTime.min;
gpsTime.second = UBX.Message.navTime.sec;
Rtc.utc_time = MakeTime(gpsTime);
Rtc.user_time_entry = true;
}
}
}
void UBXHandleOther(void)
{
if (UBX.state.non_empty_loops>6) { // we expect only 4-5 non-empty loops in a row, could change with other sensor speed (Hz)
UBXinitCFG(); // this should only happen with lots of NMEA-messages, but it is only a guess!!
AddLog_P(LOG_LEVEL_ERROR, PSTR("UBX: possible device-reset, will re-init"));
UBXSerial->flush();
UBX.state.non_empty_loops = 0;
}
}
/********************************************************************************************/
void UBXTimeServer()
{
if(UBX.mode.runningNTP){
timeServer.processOneRequest(Rtc.utc_time, UBX.state.last_iTOW%1000);
}
}
void UBXLoop(void)
{
static uint16_t counter; //count up every 100 msec
static bool new_position;
uint32_t msgType = UBXprocessGPS();
switch(msgType){
case MT_NAV_POSLLH:
new_position = UBXHandlePOSLLH();
break;
case MT_NAV_STATUS:
UBXHandleSTATUS();
break;
case MT_NAV_TIME:
UBXHandleTIME();
break;
default:
UBXHandleOther();
break;
}
#ifdef USE_FLOG
if (counter>UBX.state.log_interval) {
if (Flog->recording && new_position) {
UBX.rec_buffer.values.time = Rtc.local_time;
Flog->addToBuffer(UBX.rec_buffer.bytes, sizeof(UBX.rec_buffer.bytes));
counter = 0;
}
}
#endif // USE_FLOG
counter++;
}
/********************************************************************************************/
// normaly in i18n.h
#ifdef USE_WEBSERVER
// {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
#ifdef USE_FLOG
#ifdef DEBUG_TASMOTA_SENSOR
const char HTTP_SNS_FLOGVER[] PROGMEM = "{s}<hr>{m}<hr>{e}{s} FLOG with %u sectors: {m}%u bytes{e}"
"{s} FLOG next sector for REC: {m} %u {e}"
"{s} %u sector(s) with data at sector: {m} %u {e}";
const char HTTP_SNS_FLOGREC[] PROGMEM = "{s} RECORDING (bytes in buffer) {m}%u{e}";
#endif // DEBUG_TASMOTA_SENSOR
const char HTTP_SNS_FLOG[] PROGMEM = "{s}<hr>{m}<hr>{e}{s} Flash-Log {m} %s{e}";
const char kFLOG_STATE0[] PROGMEM = "ready";
const char kFLOG_STATE1[] PROGMEM = "recording";
const char * kFLOG_STATE[] ={kFLOG_STATE0, kFLOG_STATE1};
const char HTTP_BTN_FLOG_DL[] PROGMEM = "<button><a href='/UBX'>Download GPX-File</a></button>";
#endif //USE_FLOG
const char HTTP_SNS_NTPSERVER[] PROGMEM = "{s} NTP server {m}active{e}";
const char HTTP_SNS_GPS[] PROGMEM = "{s} GPS latitude {m}%s{e}"
"{s} GPS longitude {m}%s{e}"
"{s} GPS height {m}%s m{e}"
"{s} GPS hor. Accuracy {m}%s m{e}"
"{s} GPS vert. Accuracy {m}%s m{e}"
"{s} GPS sat-fix status {m}%s{e}";
const char kGPSFix0[] PROGMEM = "no fix";
const char kGPSFix1[] PROGMEM = "dead reckoning only";
const char kGPSFix2[] PROGMEM = "2D-fix";
const char kGPSFix3[] PROGMEM = "3D-fix";
const char kGPSFix4[] PROGMEM = "GPS + dead reckoning combined";
const char kGPSFix5[] PROGMEM = "Time only fix";
const char * kGPSFix[] PROGMEM ={kGPSFix0, kGPSFix1, kGPSFix2, kGPSFix3, kGPSFix4, kGPSFix5};
// const char UBX_GOOGLE_MAPS[] ="<iframe width='100%%' src='https://maps.google.com/maps?width=&amp;height=&amp;hl=en&amp;q=%s %s+(Tasmota)&amp;ie=UTF8&amp;t=&amp;z=10&amp;iwloc=B&amp;output=embed' frameborder='0' scrolling='no' marginheight='0' marginwidth='0'></iframe>";
#endif // USE_WEBSERVER
/********************************************************************************************/
void UBXShow(bool json)
{
char lat[12];
char lon[12];
char height[12];
char hAcc[12];
char vAcc[12];
dtostrfd((double)UBX.rec_buffer.values.lat/10000000.0f,7,lat);
dtostrfd((double)UBX.rec_buffer.values.lon/10000000.0f,7,lon);
dtostrfd((double)UBX.state.last_height/1000.0f,3,height);
dtostrfd((double)UBX.state.last_vAcc/1000.0f,3,hAcc);
dtostrfd((double)UBX.state.last_hAcc/1000.0f,3,vAcc);
if (json) {
ResponseAppend_P(PSTR(",\"GPS\":{"));
if (UBX.mode.send_UI_only) {
uint32_t i = UBX.state.log_interval / 10;
ResponseAppend_P(PSTR("\"fil\":%u,\"int\":%u}"), UBX.mode.filter_noise, i);
} else {
ResponseAppend_P(PSTR("\"lat\":%s,\"lon\":%s,\"height\":%s,\"hAcc\":%s,\"vAcc\":%s}"), lat, lon, height, hAcc, vAcc);
}
#ifdef USE_FLOG
ResponseAppend_P(PSTR(",\"FLOG\":{\"rec\":%u,\"mode\":%u,\"sec\":%u}"), Flog->recording, Flog->mode, Flog->sectors_left);
#endif //USE_FLOG
UBX.mode.send_UI_only = false;
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_GPS, lat, lon, height, hAcc, vAcc, kGPSFix[UBX.state.gpsFix]);
//WSContentSend_P(UBX_GOOGLE_MAPS, lat, lon);
#ifdef DEBUG_TASMOTA_SENSOR
#ifdef USE_FLOG
WSContentSend_PD(HTTP_SNS_FLOGVER, Flog->num_sectors, Flog->size, Flog->current_sector, Flog->sectors_left, Flog->sector.header.physical_start_sector);
if (Flog->recording) {
WSContentSend_PD(HTTP_SNS_FLOGREC, Flog->sector.header.buf_pointer - 8);
}
#endif //USE_FLOG
#endif // DEBUG_TASMOTA_SENSOR
#ifdef USE_FLOG
if (Flog->ready) {
WSContentSend_P(HTTP_SNS_FLOG,kFLOG_STATE[Flog->recording]);
}
if (!Flog->recording && Flog->found_saved_data) {
WSContentSend_P(HTTP_BTN_FLOG_DL);
}
#endif //USE_FLOG
if (UBX.mode.runningNTP) {
WSContentSend_P(HTTP_SNS_NTPSERVER);
}
#endif // USE_WEBSERVER
}
}
/*********************************************************************************************\
* check the UBX commands
\*********************************************************************************************/
bool UBXCmd(void)
{
bool serviced = true;
if (XdrvMailbox.data_len > 0) {
UBXSelectMode(XdrvMailbox.payload);
Response_P(S_JSON_UBX_COMMAND_NVALUE, XdrvMailbox.command, XdrvMailbox.payload);
}
return serviced;
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xsns60(uint8_t function)
{
bool result = false;
if (true) {
switch (function) {
case FUNC_INIT:
UBXDetect();
break;
case FUNC_COMMAND_SENSOR:
if (XSNS_60 == XdrvMailbox.index) {
result = UBXCmd();
}
break;
case FUNC_EVERY_50_MSECOND:
UBXTimeServer();
break;
case FUNC_EVERY_100_MSECOND:
#ifdef USE_FLOG
if (!Flog->running_download)
#endif //USE_FLOG
{
UBXLoop();
}
break;
#ifdef USE_FLOG
case FUNC_WEB_ADD_HANDLER:
WebServer->on("/UBX", UBXsendFile);
break;
#endif //USE_FLOG
case FUNC_JSON_APPEND:
UBXShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_WEB_SENSOR:
#ifdef USE_FLOG
if (!Flog->running_download)
#endif //USE_FLOG
{
UBXShow(0);
}
break;
#endif // USE_WEBSERVER
}
}
return result;
}
#endif // USE_GPS

View File

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