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*)§or.byte_buffer[k]; // this is basically the start address of the current record/entry of the Log
+ // Callback 2: send the pointer for consuming the next record/entry and doing something useful to create a file
+ sendRecord(_record_start);
+ if(k%128 == 0){ // give control to the system every x iteration, TODO: This will fail, when record/entry-size is not 8
+ // DEBUG_SENSOR_LOG(PSTR("FLOG: now loop(), %u bytes left"), Flog->bytes_left);
+ OsWatchLoop();
+ delay(sleep);
+ }
+ k+=size;
+ if(bytes_left>7){
+ bytes_left-=size;
+ }
+ else{
+ bytes_left = 0;
+ DEBUG_SENSOR_LOG(PSTR("FLOG: Flog->bytes_left not dividable by 8 ??????"));
+ }
+ }
+ next_sector++;
+ if(next_sector>num_sectors){
+ next_sector = 0;
+ }
+ sectors_left--;
+ _readSector(next_sector);
+ bytes_left = sector.header.buf_pointer - sizeof(sector.header);
+ OsWatchLoop();
+ delay(sleep);
+ }
+ running_download = false;
+ // Callback 3: create a footer or simply finish the download with an empty payload
+ sendFooter();
+ // refresh settings for another download
+ _searchSaves();
+ _initBuffer();
+ }
+
+ #endif // USE_FLOG
\ No newline at end of file
diff --git a/tasmota/support_rtc.ino b/tasmota/support_rtc.ino
index ffaf171a3..823d0d650 100644
--- a/tasmota/support_rtc.ino
+++ b/tasmota/support_rtc.ino
@@ -460,7 +460,9 @@ void RtcSetTime(uint32_t epoch)
if (epoch < START_VALID_TIME) { // 2016-01-01
Rtc.user_time_entry = false;
ntp_force_sync = true;
+ sntp_init();
} else {
+ sntp_stop();
Rtc.user_time_entry = true;
Rtc.utc_time = epoch -1; // Will be corrected by RtcSecond
}
diff --git a/tasmota/support_statistics.ino b/tasmota/support_statistics.ino
index b8f65bd20..1907b1edc 100644
--- a/tasmota/support_statistics.ino
+++ b/tasmota/support_statistics.ino
@@ -27,42 +27,7 @@
String GetStatistics(void)
{
char data[40];
-
- if (Settings.version < 0x08000000) {
- uint32_t str_len = 0;
- for (uint32_t i = 0; i < 2; i++) {
- str_len += strlen(Settings.sta_ssid[i]);
- str_len += strlen(Settings.sta_pwd[i]);
- }
- for (uint32_t i = 0; i < 3; i++) {
- str_len += strlen(Settings.mqtt_prefix[i]);
- str_len += strlen(Settings.ntp_server[i]);
- }
- for (uint32_t i = 0; i < 4; i++) {
- str_len += strlen(Settings.state_text[i]);
- str_len += strlen(Settings.friendlyname[i]);
- }
- for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
- str_len += strlen(Settings.mems[i]);
- }
- str_len += strlen(Settings.ota_url);
- str_len += strlen(Settings.hostname);
- str_len += strlen(Settings.syslog_host);
- str_len += strlen(Settings.mqtt_host);
- str_len += strlen(Settings.mqtt_client);
- str_len += strlen(Settings.mqtt_user);
- str_len += strlen(Settings.mqtt_pwd);
- str_len += strlen(Settings.mqtt_topic);
- str_len += strlen(Settings.button_topic);
- str_len += strlen(Settings.switch_topic);
- str_len += strlen(Settings.mqtt_grptopic);
- str_len += strlen(Settings.web_password);
- str_len += strlen(Settings.mqtt_fulltopic);
- str_len += strlen(Settings.cors_domain);
- snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/1151\""), 37 + str_len); // Char Usage Ratio
- } else {
- snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size); // Char Usage Ratio
- }
+ snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size); // Char Usage Ratio
return String(data);
}
diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino
index 900ba3dd4..c649d9379 100644
--- a/tasmota/support_tasmota.ino
+++ b/tasmota/support_tasmota.ino
@@ -546,7 +546,7 @@ void MqttShowPWMState(void)
void MqttShowState(void)
{
- char stemp1[33];
+ char stemp1[TOPSZ];
ResponseAppendTime();
ResponseAppend_P(PSTR(",\"" D_JSON_UPTIME "\":\"%s\",\"UptimeSec\":%u"), GetUptime().c_str(), UpTime());
diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h
index c93c3ea9e..c0231d3c9 100644
--- a/tasmota/tasmota.h
+++ b/tasmota/tasmota.h
@@ -58,7 +58,6 @@ const uint8_t MAX_PWMS = 5; // Max number of PWM channels
const uint8_t MAX_COUNTERS = 4; // Max number of counter sensors
const uint8_t MAX_TIMERS = 16; // Max number of Timers
const uint8_t MAX_PULSETIMERS = 8; // Max number of supported pulse timers
-const uint8_t MAX_FRIENDLYNAMES = 4; // Max number of Friendly names
const uint8_t MAX_DOMOTICZ_IDX = 4; // Max number of Domoticz device, key and switch indices
const uint8_t MAX_DOMOTICZ_SNS_IDX = 12; // Max number of Domoticz sensors indices
const uint8_t MAX_KNX_GA = 10; // Max number of KNX Group Addresses to read that can be set
@@ -70,10 +69,14 @@ const uint8_t MAX_XSNS_DRIVERS = 96; // Max number of allowed sensor driv
const uint8_t MAX_I2C_DRIVERS = 96; // Max number of allowed i2c drivers
const uint8_t MAX_SHUTTERS = 4; // Max number of shutters
const uint8_t MAX_PCF8574 = 8; // Max number of PCF8574 devices
-const uint8_t MAX_RULE_MEMS = 5; // Max number of saved vars
const uint8_t MAX_RULE_SETS = 3; // Max number of rule sets of size 512 characters
const uint16_t MAX_RULE_SIZE = 512; // Max number of characters in rules
+// Changes to the following MAX_ defines need to be in line with enum SettingsTextIndex
+const uint8_t MAX_RULE_MEMS = 16; // Max number of saved vars
+const uint8_t MAX_FRIENDLYNAMES = 8; // Max number of Friendly names
+const uint8_t MAX_BUTTON_TEXT = 16; // Max number of GUI button labels
+
const uint8_t MAX_HUE_DEVICES = 15; // Max number of Philips Hue device per emulation
const char MQTT_TOKEN_PREFIX[] PROGMEM = "%prefix%"; // To be substituted by mqtt_prefix[x]
@@ -118,7 +121,7 @@ const uint8_t OTA_ATTEMPTS = 5; // Number of times to try fetching t
const uint16_t INPUT_BUFFER_SIZE = 520; // Max number of characters in (serial and http) command buffer
const uint16_t FLOATSZ = 16; // Max number of characters in float result from dtostrfd (max 32)
const uint16_t CMDSZ = 24; // Max number of characters in command
-const uint16_t TOPSZ = 100; // Max number of characters in topic string
+const uint16_t TOPSZ = 151; // Max number of characters in topic string
const uint16_t LOGSZ = 700; // Max number of characters in log
const uint16_t MIN_MESSZ = 893; // Min number of characters in MQTT message
@@ -143,7 +146,7 @@ const uint32_t LOOP_SLEEP_DELAY = 50; // Lowest number of milliseconds to
\*********************************************************************************************/
#define MAX_RULE_TIMERS 8 // Max number of rule timers (4 bytes / timer)
-#define MAX_RULE_VARS 5 // Max number of rule variables (10 bytes / variable)
+#define MAX_RULE_VARS 16 // Max number of rule variables (33 bytes / variable)
/*
// Removed from esp8266 core since 20171105
@@ -287,13 +290,9 @@ enum SettingsTextIndex { SET_OTAURL,
SET_MEM1, SET_MEM2, SET_MEM3, SET_MEM4, SET_MEM5, SET_MEM6, SET_MEM7, SET_MEM8,
SET_MEM9, SET_MEM10, SET_MEM11, SET_MEM12, SET_MEM13, SET_MEM14, SET_MEM15, SET_MEM16,
SET_FRIENDLYNAME1, SET_FRIENDLYNAME2, SET_FRIENDLYNAME3, SET_FRIENDLYNAME4,
-
-// SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8, // Future extension
-// SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, // Future extension
-// SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8, // Future extension
-// SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, // Future extension
-// SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16, // Future extension
-
+ SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8,
+ SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8,
+ SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16,
SET_MAX };
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino
index 2f7ffcd3b..e80331366 100644
--- a/tasmota/tasmota.ino
+++ b/tasmota/tasmota.ino
@@ -161,8 +161,8 @@ StateBitfield global_state; // Global states (currently Wifi and
char my_version[33]; // Composed version string
char my_image[33]; // Code image and/or commit
char my_hostname[33]; // Composed Wifi hostname
-char mqtt_client[33]; // Composed MQTT Clientname
-char mqtt_topic[33]; // Composed MQTT topic
+char mqtt_client[TOPSZ]; // Composed MQTT Clientname
+char mqtt_topic[TOPSZ]; // Composed MQTT topic
char serial_in_buffer[INPUT_BUFFER_SIZE]; // Receive buffer
char mqtt_data[MESSZ]; // MQTT publish buffer and web page ajax buffer
char log_data[LOGSZ]; // Logging
diff --git a/tasmota/tasmota_post.h b/tasmota/tasmota_post.h
index c54adfe82..c9f8396fe 100644
--- a/tasmota/tasmota_post.h
+++ b/tasmota/tasmota_post.h
@@ -180,6 +180,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#define USE_PN532_HSU // Add support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#define USE_RDM6300 // Add support for RDM6300 125kHz RFID Reader (+0k8)
#define USE_IBEACON // Add support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
+//#define USE_GPS // Add support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#define USE_ENERGY_SENSOR // Add energy sensors (-14k code)
#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code)
@@ -374,6 +375,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
+#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
//#define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor
#undef USE_MAX31855 // Disable MAX31855 K-Type thermocouple sensor using softSPI
@@ -462,6 +464,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
+#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
//#undef USE_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor
@@ -571,6 +574,7 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
#undef USE_PN532_HSU // Disable support for PN532 using HSU (Serial) interface (+1k8 code, 140 bytes mem)
#undef USE_RDM6300 // Disable support for RDM6300 125kHz RFID Reader (+0k8)
#undef USE_IBEACON // Disable support for bluetooth LE passive scan of ibeacon devices (uses HM17 module)
+#undef USE_GPS // Disable support for GPS and NTP Server for becoming Stratus 1 Time Source (+ 3.1kb flash, +132 bytes RAM)
#undef USE_ENERGY_SENSOR // Disable energy sensors
#undef USE_PZEM004T // Disable PZEM004T energy sensor
diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h
index 5556629f9..685546b91 100644
--- a/tasmota/tasmota_template.h
+++ b/tasmota/tasmota_template.h
@@ -214,6 +214,8 @@ enum UserSelectablePins {
GPIO_TASMOTASLAVE_RST_INV, // Slave Reset Inverted
GPIO_HPMA_RX, // Honeywell HPMA115S0 Serial interface
GPIO_HPMA_TX, // Honeywell HPMA115S0 Serial interface
+ GPIO_GPS_RX, // GPS serial interface
+ GPIO_GPS_TX, // GPS serial interface
GPIO_SENSOR_END };
// Programmer selectable GPIO functionality
@@ -294,6 +296,7 @@ const char kSensorNames[] PROGMEM =
D_SENSOR_DEEPSLEEP "|" D_SENSOR_EXS_ENABLE "|"
D_SENSOR_SLAVE_TX "|" D_SENSOR_SLAVE_RX "|" D_SENSOR_SLAVE_RESET "|" D_SENSOR_SLAVE_RESET "i|"
D_SENSOR_HPMA_RX "|" D_SENSOR_HPMA_TX "|"
+ D_SENSOR_GPS_RX "|" D_SENSOR_GPS_TX
;
const char kSensorNamesFixed[] PROGMEM =
@@ -308,6 +311,7 @@ enum UserSelectableAdc0 {
ADC0_LIGHT, // Light sensor
ADC0_BUTTON, // Button
ADC0_BUTTON_INV,
+ ADC0_MOIST, // Moisture
// ADC0_SWITCH, // Switch
// ADC0_SWITCH_INV,
ADC0_END };
@@ -323,6 +327,7 @@ const char kAdc0Names[] PROGMEM =
D_SENSOR_NONE "|" D_ANALOG_INPUT "|"
D_TEMPERATURE "|" D_LIGHT "|"
D_SENSOR_BUTTON "|" D_SENSOR_BUTTON "i|"
+ D_MOISTURE "|"
// D_SENSOR_SWITCH "|" D_SENSOR_SWITCH "i|"
;
@@ -671,6 +676,7 @@ const uint8_t kGpioNiceList[] PROGMEM = {
#endif // USE_SOLAX_X1
#endif // USE_ENERGY_SENSOR
+// Serial
#ifdef USE_SERIAL_BRIDGE
GPIO_SBR_TX, // Serial Bridge Serial interface
GPIO_SBR_RX, // Serial Bridge Serial interface
@@ -725,6 +731,11 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_IBEACON_RX,
GPIO_IBEACON_TX,
#endif
+#ifdef USE_GPS
+ GPIO_GPS_RX, // GPS serial interface
+ GPIO_GPS_TX, // GPS serial interface
+#endif
+
#ifdef USE_MGC3130
GPIO_MGC3130_XFER,
GPIO_MGC3130_RESET,
@@ -754,8 +765,9 @@ const uint8_t kGpioNiceList[] PROGMEM = {
GPIO_A4988_MS3, // A4988 microstep pin3
#endif
#ifdef USE_DEEPSLEEP
- GPIO_DEEPSLEEP
+ GPIO_DEEPSLEEP,
#endif
+
};
const uint8_t kModuleNiceList[] PROGMEM = {
diff --git a/tasmota/tasmota_version.h b/tasmota/tasmota_version.h
index f210112b1..70747a9a6 100644
--- a/tasmota/tasmota_version.h
+++ b/tasmota/tasmota_version.h
@@ -20,9 +20,9 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
-const uint32_t VERSION = 0x07020000;
+const uint32_t VERSION = 0x08010000;
// Lowest compatible version
-const uint32_t VERSION_COMPATIBLE = 0x00000000;
+const uint32_t VERSION_COMPATIBLE = 0x07010006;
#endif // _TASMOTA_VERSION_H_
diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino
index 8aece2be8..d86562bb3 100644
--- a/tasmota/xdrv_01_webserver.ino
+++ b/tasmota/xdrv_01_webserver.ino
@@ -1014,7 +1014,7 @@ void HandleRoot(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_MAIN_MENU);
- char stemp[10];
+ char stemp[33];
WSContentStart_P(S_MAIN_MENU);
#ifdef USE_SCRIPT_WEB_DISPLAY
@@ -1106,14 +1106,19 @@ void HandleRoot(void)
WSContentSend_P(PSTR(""));
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan()) {
- WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, D_BUTTON_TOGGLE, "");
+ WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1,
+ (strlen(SettingsText(SET_BUTTON1))) ? SettingsText(SET_BUTTON1) : D_BUTTON_TOGGLE,
+ "");
for (uint32_t i = 0; i < MaxFanspeed(); i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%d"), i);
- WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2, stemp, "");
+ WSContentSend_P(HTTP_DEVICE_CONTROL, 16, i +2,
+ (strlen(SettingsText(SET_BUTTON2 + i))) ? SettingsText(SET_BUTTON2 + i) : stemp,
+ "");
}
} else {
#endif // USE_SONOFF_IFAN
for (uint32_t idx = 1; idx <= devices_present; idx++) {
+ bool set_button = ((idx <= MAX_BUTTON_TEXT) && strlen(SettingsText(SET_BUTTON1 + idx -1)));
#ifdef USE_SHUTTER
if (Settings.flag3.shutter_mode) { // SetOption80 - Enable shutter support
bool shutter_used = false;
@@ -1124,13 +1129,17 @@ void HandleRoot(void)
}
}
if (shutter_used) {
- WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (idx % 2) ? "▲" : "▼" , "");
+ WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx,
+ (set_button) ? SettingsText(SET_BUTTON1 + idx -1) : (idx % 2) ? "▲" : "▼",
+ "");
continue;
}
}
#endif // USE_SHUTTER
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx);
- WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (devices_present < 5) ? D_BUTTON_TOGGLE : "", (devices_present > 1) ? stemp : "");
+ WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx,
+ (set_button) ? SettingsText(SET_BUTTON1 + idx -1) : (devices_present < 5) ? D_BUTTON_TOGGLE : "",
+ (set_button) ? "" : (devices_present > 1) ? stemp : "");
}
#ifdef USE_SONOFF_IFAN
}
@@ -1146,7 +1155,9 @@ void HandleRoot(void)
if (idx > 0) { WSContentSend_P(PSTR("
")); }
for (uint32_t j = 0; j < 4; j++) {
idx++;
- WSContentSend_P(PSTR(" | "), idx, idx); // &k is related to WebGetArg("k", tmp, sizeof(tmp));
+ snprintf_P(stemp, sizeof(stemp), PSTR("%d"), idx);
+ WSContentSend_P(PSTR(" | "), idx, // &k is related to WebGetArg("k", tmp, sizeof(tmp));
+ (strlen(SettingsText(SET_BUTTON1 + idx -1))) ? SettingsText(SET_BUTTON1 + idx -1) : stemp);
}
}
WSContentSend_P(PSTR("
"));
@@ -1694,7 +1705,7 @@ void HandleWifiConfiguration(void)
void WifiSaveSettings(void)
{
- char tmp[100]; // Max length is currently 65
+ char tmp[TOPSZ]; // Max length is currently 150
WebGetArg("h", tmp, sizeof(tmp));
SettingsUpdateText(SET_HOSTNAME, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp);
@@ -1757,7 +1768,7 @@ void HandleLoggingConfiguration(void)
void LoggingSaveSettings(void)
{
- char tmp[100]; // Max length is currently 33
+ char tmp[TOPSZ]; // Max length is currently 33
WebGetArg("l0", tmp, sizeof(tmp));
SetSeriallog((!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp));
@@ -1843,9 +1854,10 @@ void HandleOtherConfiguration(void)
void OtherSaveSettings(void)
{
- char tmp[128];
+ char tmp[TOPSZ];
char webindex[5];
char friendlyname[TOPSZ];
+ char message[LOGSZ];
WebGetArg("wp", tmp, sizeof(tmp));
SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp);
@@ -1854,15 +1866,17 @@ void OtherSaveSettings(void)
WebGetArg("b2", tmp, sizeof(tmp));
Settings.flag2.emulation = (!strlen(tmp)) ? 0 : atoi(tmp);
#endif // USE_EMULATION
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation);
+
+ snprintf_P(message, sizeof(message), PSTR(D_LOG_OTHER D_MQTT_ENABLE " %s, " D_CMND_EMULATION " %d, " D_CMND_FRIENDLYNAME), GetStateText(Settings.flag.mqtt_enabled), Settings.flag2.emulation);
for (uint32_t i = 0; i < MAX_FRIENDLYNAMES; i++) {
snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i);
WebGetArg(webindex, tmp, sizeof(tmp));
snprintf_P(friendlyname, sizeof(friendlyname), PSTR(FRIENDLY_NAME"%d"), i +1);
SettingsUpdateText(SET_FRIENDLYNAME1 +i, (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp);
- snprintf_P(log_data, sizeof(log_data), PSTR("%s%s %s"), log_data, (i) ? "," : "", SettingsText(SET_FRIENDLYNAME1 +i));
+ snprintf_P(message, sizeof(message), PSTR("%s%s %s"), message, (i) ? "," : "", SettingsText(SET_FRIENDLYNAME1 +i));
}
- AddLog(LOG_LEVEL_INFO);
+ AddLog_P(LOG_LEVEL_INFO, message);
+
WebGetArg("t1", tmp, sizeof(tmp));
if (strlen(tmp)) { // {"NAME":"12345678901234","GPIO":[255,255,255,255,255,255,255,255,255,255,255,255,255],"FLAG":255,"BASE":255}
char svalue[128];
@@ -1890,7 +1904,7 @@ void HandleBackupConfiguration(void)
WiFiClient myClient = WebServer->client();
WebServer->setContentLength(sizeof(Settings));
- char attachment[100];
+ char attachment[TOPSZ];
// char friendlyname[TOPSZ];
// snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(friendlyname, SettingsText(SET_FRIENDLYNAME1)), my_version);
@@ -2095,12 +2109,12 @@ void HandleUpgradeFirmwareStart(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
- char command[128]; // OtaUrl
+ char command[TOPSZ + 10]; // OtaUrl
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
WifiConfigCounter();
- char otaurl[101];
+ char otaurl[TOPSZ];
WebGetArg("o", otaurl, sizeof(otaurl));
if (strlen(otaurl)) {
snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl);
@@ -2637,7 +2651,7 @@ int WebSend(char *buffer)
if (user) {
user = strtok_r(user, ":", &password); // user = |admin|, password = |joker|
if (user && password) {
- char userpass[128];
+ char userpass[200];
snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password);
url += userpass; // url = |http://192.168.178.86/cm?user=admin&password=joker&|
}
@@ -2730,7 +2744,8 @@ const char kWebCommands[] PROGMEM = "|" // No prefix
#ifdef USE_SENDMAIL
D_CMND_SENDMAIL "|"
#endif
- D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|" D_CMND_WEBSENSOR "|" D_CMND_CORS;
+ D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|"
+ D_CMND_WEBSENSOR "|" D_CMND_WEBBUTTON "|" D_CMND_CORS;
void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION
@@ -2739,7 +2754,8 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_SENDMAIL
&CmndSendmail,
#endif
- &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, &CmndWebSensor, &CmndCors };
+ &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor,
+ &CmndWebSensor, &CmndWebButton, &CmndCors };
/*********************************************************************************************\
* Commands
@@ -2860,6 +2876,24 @@ void CmndWebSensor(void)
ResponseJsonEnd();
}
+void CmndWebButton(void)
+{
+ if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_BUTTON_TEXT)) {
+ if (!XdrvMailbox.usridx) {
+ mqtt_data[0] = '\0';
+ for (uint32_t i = 0; i < MAX_BUTTON_TEXT; i++) {
+ ResponseAppend_P(PSTR("%c\"WebButton%d\":\"%s\""), (i) ? ',' : '{', i +1, SettingsText(SET_BUTTON1 +i));
+ }
+ ResponseJsonEnd();
+ } else {
+ if (XdrvMailbox.data_len > 0) {
+ SettingsUpdateText(SET_BUTTON1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
+ }
+ ResponseCmndIdxChar(SettingsText(SET_BUTTON1 + XdrvMailbox.index -1));
+ }
+ }
+}
+
void CmndCors(void)
{
if (XdrvMailbox.data_len > 0) {
diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino
index 47838e223..910a2ea6f 100644
--- a/tasmota/xdrv_02_mqtt.ino
+++ b/tasmota/xdrv_02_mqtt.ino
@@ -32,9 +32,7 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
D_CMND_MQTTFINGERPRINT "|"
#endif
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|"
-#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
D_CMND_TLSKEY "|"
#endif
@@ -46,9 +44,7 @@ void (* const MqttCommand[])(void) PROGMEM = {
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
&CmndMqttFingerprint,
#endif
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
&CmndMqttUser, &CmndMqttPassword,
-#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
&CmndTlsKey,
#endif
@@ -640,7 +636,6 @@ void MqttReconnect(void)
#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), SettingsText(SET_MQTT_HOST));
- //if (MqttClient.connect(mqtt_client, nullptr, nullptr, nullptr, 0, false, nullptr)) {
if (MqttClient.connect(mqtt_client, nullptr, nullptr, stopic, 1, false, mqtt_data, MQTT_CLEAN_SESSION)) {
#else
if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data, MQTT_CLEAN_SESSION)) {
@@ -737,7 +732,6 @@ void CmndMqttFingerprint(void)
}
#endif
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
void CmndMqttUser(void)
{
if (XdrvMailbox.data_len > 0) {
@@ -757,7 +751,6 @@ void CmndMqttPassword(void)
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
}
-#endif // USE_MQTT_AWS_IOT
void CmndMqttlog(void)
{
@@ -1171,10 +1164,8 @@ const char HTTP_FORM_MQTT1[] PROGMEM =
"" D_PORT " (" STR(MQTT_PORT) ")
"
"" D_CLIENT " (%s)
";
const char HTTP_FORM_MQTT2[] PROGMEM =
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password disabled with AWS IoT
"" D_USER " (" MQTT_USER ")
"
"" D_PASSWORD "
"
-#endif // USE_MQTT_AWS_IOT
"" D_TOPIC " = %%topic%% (%s)
"
"" D_FULL_TOPIC " (%s)
";
@@ -1190,7 +1181,7 @@ void HandleMqttConfiguration(void)
return;
}
- char str[33];
+ char str[TOPSZ];
WSContentStart_P(S_CONFIGURE_MQTT);
WSContentSendStyle();
@@ -1209,7 +1200,7 @@ void HandleMqttConfiguration(void)
void MqttSaveSettings(void)
{
- char tmp[100];
+ char tmp[TOPSZ];
char stemp[TOPSZ];
char stemp2[TOPSZ];
diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino
index 6148a9aad..d6e71ab08 100644
--- a/tasmota/xdrv_04_light.ino
+++ b/tasmota/xdrv_04_light.ino
@@ -262,8 +262,8 @@ struct LIGHT {
uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0};
uint16_t fade_cur_10[LST_MAX];
uint16_t fade_end_10[LST_MAX]; // 10 bits resolution target channel values
- uint16_t fade_counter = 0; // fade timer in ticks (50ms)
- uint16_t fade_duration = 0; // duration of fade in ticks (50ms)
+ uint16_t fade_duration = 0; // duration of fade in milliseconds
+ uint32_t fade_start = 0; // fade start time in milliseconds, compared to millis()
} Light;
power_t LightPower(void)
@@ -1574,20 +1574,25 @@ void LightAnimate(void)
bool power_off = false;
Light.strip_timer_counter++;
- if (!Light.power) { // All channels powered off
- Light.strip_timer_counter = 0;
- if (!Light.fade_running) {
- sleep = Settings.sleep;
- }
- if (Settings.light_scheme >= LS_MAX) {
- power_off = true;
- }
- } else {
+
+ // set sleep parameter: either settings,
+ // or set a maximum of PWM_MAX_SLEEP if light is on or Fade is running
+ if (Light.power || Light.fade_running) {
if (Settings.sleep > PWM_MAX_SLEEP) {
sleep = PWM_MAX_SLEEP; // set a maxumum value of 50 milliseconds to ensure that animations are smooth
} else {
sleep = Settings.sleep; // or keep the current sleep if it's lower than 50
}
+ } else {
+ sleep = Settings.sleep;
+ }
+
+ if (!Light.power) { // All channels powered off
+ Light.strip_timer_counter = 0;
+ if (Settings.light_scheme >= LS_MAX) {
+ power_off = true;
+ }
+ } else {
switch (Settings.light_scheme) {
case LS_POWER:
light_controller.calcLevels(Light.new_color);
@@ -1733,27 +1738,35 @@ void LightAnimate(void)
memcpy(Light.fade_end_8, cur_col, sizeof(Light.fade_start_8));
memcpy(Light.fade_end_10, cur_col_10bits, sizeof(Light.fade_start_10));
Light.fade_running = true;
- Light.fade_counter = 0;
Light.fade_duration = 0; // set the value to zero to force a recompute
+ Light.fade_start = 0;
// Fade will applied immediately below
}
}
if (Light.fade_running) {
- LightApplyFade();
- // AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d - %d %d %d %d %d"),
- // Light.fade_cur_8[0], Light.fade_cur_8[1], Light.fade_cur_8[2], Light.fade_cur_8[3], Light.fade_cur_8[4],
- // Light.fade_cur_10[0], Light.fade_cur_10[1], Light.fade_cur_10[2], Light.fade_cur_10[3], Light.fade_cur_10[4]);
+ if (LightApplyFade()) {
+ // AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d - %d %d %d %d %d"),
+ // Light.fade_cur_8[0], Light.fade_cur_8[1], Light.fade_cur_8[2], Light.fade_cur_8[3], Light.fade_cur_8[4],
+ // Light.fade_cur_10[0], Light.fade_cur_10[1], Light.fade_cur_10[2], Light.fade_cur_10[3], Light.fade_cur_10[4]);
- LightSetOutputs(Light.fade_cur_8, Light.fade_cur_10);
+ LightSetOutputs(Light.fade_cur_8, Light.fade_cur_10);
+ }
}
}
}
-void LightApplyFade(void) {
+bool LightApplyFade(void) { // did the value chanegd and needs to be applied
+ static uint32_t last_millis = 0;
+ uint32_t now = millis();
+
+ if ((now - last_millis) <= 5) {
+ return false; // the value was not changed in the last 5 milliseconds, ignore
+ }
+ last_millis = now;
// Check if we need to calculate the duration
if (0 == Light.fade_duration) {
- Light.fade_counter = 0;
+ Light.fade_start = now;
// compute the distance between start and and color (max of distance for each channel)
uint32_t distance = 0;
for (uint32_t i = 0; i < Light.subtype; i++) {
@@ -1764,11 +1777,11 @@ void LightApplyFade(void) {
if (distance > 0) {
// compute the duration of the animation
// Note: Settings.light_speed is the number of half-seconds for a 100% fade,
- // i.e. light_speed=1 means 1024 steps in 10 ticks (500ms)
- Light.fade_duration = (distance * Settings.light_speed * 10) / 1024;
+ // i.e. light_speed=1 means 1024 steps in 500ms
+ Light.fade_duration = (distance * Settings.light_speed * 500) / 1023;
if (Settings.save_data) {
// Also postpone the save_data for the duration of the Fade (in seconds)
- uint32_t delay_seconds = 1 + (Light.fade_duration + 19) / 20; // add one more second
+ uint32_t delay_seconds = 1 + (Light.fade_duration + 999) / 1000; // add one more second
// AddLog_P2(LOG_LEVEL_INFO, PSTR("delay_seconds %d, save_data_counter %d"), delay_seconds, save_data_counter);
if (save_data_counter < delay_seconds) {
save_data_counter = delay_seconds; // pospone
@@ -1776,16 +1789,18 @@ void LightApplyFade(void) {
}
} else {
// no fade needed, we keep the duration at zero, it will fallback directly to end of fade
+ Light.fade_running = false;
}
}
- Light.fade_counter++;
- if (Light.fade_counter <= Light.fade_duration) { // fade not finished
+ uint16_t fade_current = now - Light.fade_start; // number of milliseconds since start of fade
+ if (fade_current <= Light.fade_duration) { // fade not finished
+ //Serial.printf("Fade: %d / %d - ", fade_current, Light.fade_duration);
for (uint32_t i = 0; i < Light.subtype; i++) {
- Light.fade_cur_8[i] = changeUIntScale(Light.fade_counter,
+ Light.fade_cur_8[i] = changeUIntScale(fade_current,
0, Light.fade_duration,
Light.fade_start_8[i], Light.fade_end_8[i]);
- Light.fade_cur_10[i] = changeUIntScale(Light.fade_counter,
+ Light.fade_cur_10[i] = changeUIntScale(fade_current,
0, Light.fade_duration,
Light.fade_start_10[i], Light.fade_end_10[i]);
}
@@ -1793,7 +1808,7 @@ void LightApplyFade(void) {
// stop fade
//AddLop_P2(LOG_LEVEL_DEBUG, PSTR("Stop fade"));
Light.fade_running = false;
- Light.fade_counter = 0;
+ Light.fade_start = 0;
Light.fade_duration = 0;
// set light to target value
memcpy(Light.fade_cur_8, Light.fade_end_8, sizeof(Light.fade_end_8));
@@ -1802,7 +1817,7 @@ void LightApplyFade(void) {
memcpy(Light.fade_start_8, Light.fade_end_8, sizeof(Light.fade_start_8));
memcpy(Light.fade_start_10, Light.fade_end_10, sizeof(Light.fade_start_10));
}
-
+ return true;
}
// On entry we take the 5 channels 8 bits entry, and we apply Power modifiers
@@ -2428,6 +2443,13 @@ bool Xdrv04(uint8_t function)
case FUNC_SERIAL:
result = XlgtCall(FUNC_SERIAL);
break;
+ case FUNC_LOOP:
+ if (Light.fade_running) {
+ if (LightApplyFade()) {
+ LightSetOutputs(Light.fade_cur_8, Light.fade_cur_10);
+ }
+ }
+ break;
case FUNC_EVERY_50_MSECOND:
LightAnimate();
break;
diff --git a/tasmota/xdrv_09_timers.ino b/tasmota/xdrv_09_timers.ino
index db1cde97e..a6c7b23a9 100644
--- a/tasmota/xdrv_09_timers.ino
+++ b/tasmota/xdrv_09_timers.ino
@@ -733,12 +733,13 @@ void HandleTimerConfiguration(void)
void TimerSaveSettings(void)
{
char tmp[MAX_TIMERS *12]; // Need space for MAX_TIMERS x 10 digit numbers separated by a comma
+ char message[LOGSZ];
Timer timer;
Settings.flag3.timers_enable = WebServer->hasArg("e0"); // CMND_TIMERS
WebGetArg("t0", tmp, sizeof(tmp));
char *p = tmp;
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable); // CMND_TIMERS
+ snprintf_P(message, sizeof(message), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable); // CMND_TIMERS
for (uint32_t i = 0; i < MAX_TIMERS; i++) {
timer.data = strtol(p, &p, 10);
p++; // Skip comma
@@ -747,9 +748,9 @@ void TimerSaveSettings(void)
Settings.timer[i].data = timer.data;
if (flag) TimerSetRandomWindow(i);
}
- snprintf_P(log_data, sizeof(log_data), PSTR("%s,0x%08X"), log_data, Settings.timer[i].data);
+ snprintf_P(message, sizeof(message), PSTR("%s,0x%08X"), message, Settings.timer[i].data);
}
- AddLog(LOG_LEVEL_DEBUG);
+ AddLog_P(LOG_LEVEL_DEBUG, message);
}
#endif // USE_TIMERS_WEB
#endif // USE_WEBSERVER
diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino
index 6e1636518..21c9de283 100644
--- a/tasmota/xdrv_10_rules.ino
+++ b/tasmota/xdrv_10_rules.ino
@@ -174,8 +174,8 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};
#if (MAX_RULE_VARS>16)
#error MAX_RULE_VARS is bigger than 16
#endif
-#if (MAX_RULE_MEMS>5)
-#error MAX_RULE_MEMS is bigger than 5
+#if (MAX_RULE_MEMS>16)
+#error MAX_RULE_MEMS is bigger than 16
#endif
/*******************************************************************************************/
@@ -1400,8 +1400,7 @@ bool evaluateLogicalExpression(const char * expression, int len)
memcpy(expbuff, expression, len);
expbuff[len] = '\0';
- //snprintf_P(log_data, sizeof(log_data), PSTR("EvalLogic: |%s|"), expbuff);
- //AddLog(LOG_LEVEL_DEBUG);
+ //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EvalLogic: |%s|"), expbuff);
char * pointer = expbuff;
LinkedList values;
LinkedList logicOperators;
@@ -1527,8 +1526,7 @@ void ExecuteCommandBlock(const char * commands, int len)
memcpy(cmdbuff, commands, len);
cmdbuff[len] = '\0';
- //snprintf_P(log_data, sizeof(log_data), PSTR("ExecCmd: |%s|"), cmdbuff);
- //AddLog(LOG_LEVEL_DEBUG);
+ //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ExecCmd: |%s|"), cmdbuff);
char oneCommand[len + 1]; //To put one command
int insertPosition = 0; //When insert into backlog, we should do it by 0, 1, 2 ...
char * pos = cmdbuff;
diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino
index 68df13984..6b97fa68a 100755
--- a/tasmota/xdrv_10_scripter.ino
+++ b/tasmota/xdrv_10_scripter.ino
@@ -56,7 +56,8 @@ keywords if then else endif, or, and are better readable for beginners (others m
#define SCRIPT_MAXSSIZE 48
#define SCRIPT_EOL '\n'
#define SCRIPT_FLOAT_PRECISION 2
-#define SCRIPT_MAXPERM (MAX_RULE_MEMS*10)-4/sizeof(float)
+#define PMEM_SIZE sizeof(Settings.script_pram)
+#define SCRIPT_MAXPERM (PMEM_SIZE)-4/sizeof(float)
#define MAX_SCRIPT_SIZE MAX_RULE_SIZE*MAX_RULE_SETS
// offsets epoch readings by 1.1.2019 00:00:00 to fit into float with second resolution
@@ -1041,7 +1042,7 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso
if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE);
return lp+len;
}
-
+
} else {
if (fp) {
if (!strncmp(vn.c_str(),"Epoch",5)) {
@@ -1575,7 +1576,7 @@ chknext:
case 'r':
if (!strncmp(vname,"ram",3)) {
- fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(MAX_RULE_MEMS*10);
+ fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(PMEM_SIZE);
goto exit;
}
break;
@@ -2203,8 +2204,7 @@ void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) {
void toLog(const char *str) {
if (!str) return;
- snprintf_P(log_data, sizeof(log_data), PSTR("%s"),str);
- AddLog(LOG_LEVEL_INFO);
+ AddLog_P(LOG_LEVEL_INFO, str);
}
@@ -2681,8 +2681,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
}
cmd[count]=*lp++;
}
- //snprintf_P(log_data, sizeof(log_data), tmp);
- //AddLog(LOG_LEVEL_INFO);
+ //AddLog_P(LOG_LEVEL_INFO, tmp);
// replace vars in cmd
char *tmp=cmdmem+SCRIPT_CMDMEM/2;
Replace_Cmd_Vars(cmd,tmp,SCRIPT_CMDMEM/2);
@@ -2694,8 +2693,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
} else {
if (!sflag) {
tasm_cmd_activ=1;
- snprintf_P(log_data, sizeof(log_data), PSTR("Script: performs \"%s\""), tmp);
- AddLog(glob_script_mem.script_loglevel&0x7f);
+ AddLog_P2(glob_script_mem.script_loglevel&0x7f, PSTR("Script: performs \"%s\""), tmp);
} else if (sflag==2) {
// allow recursive call
} else {
@@ -2995,7 +2993,7 @@ void ScripterEvery100ms(void) {
if (fast_script==99) Run_Scripter(">F",2,0);
}
-//mems[MAX_RULE_MEMS] is 50 bytes in 6.5
+//mems[5] is 50 bytes in 6.5
// can hold 11 floats or floats + strings
// should report overflow later
void Scripter_save_pvars(void) {
@@ -3007,7 +3005,7 @@ void Scripter_save_pvars(void) {
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
mlen+=sizeof(float);
- if (mlen>MAX_RULE_MEMS*10) {
+ if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0;
return;
}
@@ -3021,7 +3019,7 @@ void Scripter_save_pvars(void) {
char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
uint8_t slen=strlen(sp);
mlen+=slen+1;
- if (mlen>MAX_RULE_MEMS*10) {
+ if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0;
return;
}
@@ -3544,8 +3542,7 @@ void ScriptSaveSettings(void) {
if (bitRead(Settings.rule_enabled, 0)) {
int16_t res=Init_Scripter();
if (res) {
- snprintf_P(log_data, sizeof(log_data), PSTR("script init error: %d"),res);
- AddLog(LOG_LEVEL_INFO);
+ AddLog_P2(LOG_LEVEL_INFO, PSTR("script init error: %d"), res);
return;
}
Run_Scripter(">B",2,0);
@@ -4754,8 +4751,8 @@ bool Xdrv10(uint8_t function)
glob_script_mem.script_ram=Settings.rules[0];
glob_script_mem.script_size=MAX_SCRIPT_SIZE;
glob_script_mem.flags=0;
- glob_script_mem.script_pram=(uint8_t*)Settings.mems[0];
- glob_script_mem.script_pram_size=MAX_RULE_MEMS*10;
+ glob_script_mem.script_pram=(uint8_t*)Settings.script_pram[0];
+ glob_script_mem.script_pram_size=PMEM_SIZE;
#ifdef USE_BUTTON_EVENT
for (uint32_t cnt=0;cnt {{ value_json['SI7021-14'].Humidity }}
"\"dev_cla\":\"humidity\""; // humidity
+const char HASS_DISCOVER_SENSOR_MOIST[] PROGMEM =
+ ",\"unit_of_meas\":\"%%\"," // %
+ "\"val_tpl\":\"{{value_json['%s'].Moisture}}\"," // "ANALOG":{"Moisture":78} -> {{ value_json['ANALOG'].Moisture }}
+ "\"dev_cla\":\"humidity\"," // humidity
+ "\"ic\":\"mdi:cup-water\""; // cup-water icon
+
const char HASS_DISCOVER_SENSOR_PRESS[] PROGMEM =
",\"unit_of_meas\":\"%s\"," // PressureUnit() setting
"\"val_tpl\":\"{{value_json['%s'].%s}}\"," // "BME280":{"Temperature":19.7,"Humidity":27.8,"Pressure":990.1} -> {{ value_json['BME280'].Pressure }}
@@ -473,6 +479,8 @@ void HAssAnnounceSensor(const char* sensorname, const char* subsensortype)
TryResponseAppend_P(HASS_DISCOVER_SENSOR_AMPERE, sensorname, subsensortype);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_ILLUMINANCE))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_ILLUMINANCE, sensorname, subsensortype);
+ } else if (!strcmp_P(subsensortype, PSTR(D_JSON_MOISTURE))){
+ TryResponseAppend_P(HASS_DISCOVER_SENSOR_MOIST, sensorname, subsensortype);
} else {
if (is_sensor){
TryResponseAppend_P(PSTR(",\"unit_of_meas\":\" \"")); // " " As unit of measurement to get a value graph (not available for binary sensors)
diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino
index 1d13ee263..80e85d821 100644
--- a/tasmota/xdrv_20_hue.ino
+++ b/tasmota/xdrv_20_hue.ino
@@ -365,7 +365,6 @@ void HueLightStatus1(uint8_t device, String *response)
// Any device whose friendly name start with "$" is considered hidden
bool HueActive(uint8_t device) {
if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
-// return '$' != Settings.friendlyname[device-1][0];
return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
}
diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino
index 0c74c9d8a..f83acf21b 100644
--- a/tasmota/xdrv_23_zigbee_0_constants.ino
+++ b/tasmota/xdrv_23_zigbee_0_constants.ino
@@ -19,6 +19,8 @@
#ifdef USE_ZIGBEE
+#define OCCUPANCY "Occupancy" // global define for Aqara
+
typedef uint64_t Z_IEEEAddress;
typedef uint16_t Z_ShortAddress;
diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino
index b017d8e2e..8bfc59b70 100644
--- a/tasmota/xdrv_23_zigbee_1_headers.ino
+++ b/tasmota/xdrv_23_zigbee_1_headers.ino
@@ -23,4 +23,24 @@
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1);
+
+// Get an JSON attribute, with case insensitive key search
+JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) {
+ // key can be in PROGMEM
+ if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
+ return *(JsonVariant*)nullptr;
+ }
+
+ for (auto kv : json) {
+ const char *key = kv.key;
+ JsonVariant &value = kv.value;
+
+ if (0 == strcasecmp_P(key, needle)) {
+ return value;
+ }
+ }
+ // if not found
+ return *(JsonVariant*)nullptr;
+}
+
#endif // USE_ZIGBEE
diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino
index de13990c2..1ec18d3e1 100644
--- a/tasmota/xdrv_23_zigbee_3_devices.ino
+++ b/tasmota/xdrv_23_zigbee_3_devices.ino
@@ -42,6 +42,9 @@ typedef struct Z_Device {
uint16_t endpoint; // endpoint to use for timer
uint32_t value; // any raw value to use for the timer
Z_DeviceTimer func; // function to call when timer occurs
+ // json buffer used for attribute reporting
+ DynamicJsonBuffer *json_buffer;
+ JsonObject *json;
} Z_Device;
// All devices are stored in a Vector
@@ -84,6 +87,13 @@ public:
void setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func);
void runTimer(void);
+ // Append or clear attributes Json structure
+ void jsonClear(uint16_t shortaddr);
+ void jsonAppend(uint16_t shortaddr, JsonObject &values);
+ const JsonObject *jsonGet(uint16_t shortaddr);
+ const void jsonPublish(uint16_t shortaddr); // publish the json message and clear buffer
+ bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values);
+
private:
std::vector _devices = {};
@@ -173,7 +183,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
std::vector(),
std::vector(),
0,0,0,0,
- nullptr };
+ nullptr,
+ nullptr, nullptr };
+ device.json_buffer = new DynamicJsonBuffer();
_devices.push_back(device);
return _devices.back();
}
@@ -394,14 +406,112 @@ void Z_Devices::runTimer(void) {
uint32_t timer = device.timer;
if ((timer) && (timer <= now)) {
+ device.timer = 0; // cancel the timer before calling, so the callback can set another timer
// trigger the timer
(*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value);
-
- device.timer = 0; // cancel the timer
}
}
}
+void Z_Devices::jsonClear(uint16_t shortaddr) {
+ Z_Device & device = getShortAddr(shortaddr);
+ if (&device == nullptr) { return; } // don't crash if not found
+
+ device.json = nullptr;
+ device.json_buffer->clear();
+}
+
+void CopyJsonVariant(JsonObject &to, const String &key, const JsonVariant &val) {
+ to.remove(key); // force remove to have metadata like LinkQuality at the end
+
+ if (val.is()) {
+ String sval = val.as(); // force a copy of the String value
+ to.set(key, sval);
+ } else if (val.is()) {
+ JsonArray &nested_arr = to.createNestedArray(key);
+ CopyJsonArray(nested_arr, val.as());
+ } else if (val.is()) {
+ JsonObject &nested_obj = to.createNestedObject(key);
+ CopyJsonObject(nested_obj, val.as());
+ } else {
+ to.set(key, val);
+ }
+}
+
+void CopyJsonArray(JsonArray &to, const JsonArray &arr) {
+ for (auto v : arr) {
+ if (v.is()) {
+ String sval = v.as(); // force a copy of the String value
+ to.add(sval);
+ } else if (v.is()) {
+ } else if (v.is()) {
+ } else {
+ to.add(v);
+ }
+ }
+}
+
+void CopyJsonObject(JsonObject &to, const JsonObject &from) {
+ for (auto kv : from) {
+ String key_string = kv.key;
+ JsonVariant &val = kv.value;
+
+ CopyJsonVariant(to, key_string, val);
+ }
+}
+
+// does the new payload conflicts with the existing payload, i.e. values would be overwritten
+bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) {
+ Z_Device & device = getShortAddr(shortaddr);
+ if (&device == nullptr) { return false; } // don't crash if not found
+ if (&values == nullptr) { return false; }
+
+ if (nullptr == device.json) {
+ return false; // if no previous value, no conflict
+ }
+
+ for (auto kv : values) {
+ String key_string = kv.key;
+
+ if (strcasecmp_P(kv.key, PSTR(D_CMND_ZIGBEE_LINKQUALITY))) { // exception = ignore duplicates for LinkQuality
+ if (device.json->containsKey(kv.key)) {
+ return true; // conflict!
+ }
+ }
+ }
+ return false;
+}
+
+void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) {
+ Z_Device & device = getShortAddr(shortaddr);
+ if (&device == nullptr) { return; } // don't crash if not found
+ if (&values == nullptr) { return; }
+
+ if (nullptr == device.json) {
+ device.json = &(device.json_buffer->createObject());
+ }
+ // copy all values from 'values' to 'json'
+ CopyJsonObject(*device.json, values);
+}
+
+const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) {
+ Z_Device & device = getShortAddr(shortaddr);
+ if (&device == nullptr) { return nullptr; } // don't crash if not found
+ return device.json;
+}
+
+const void Z_Devices::jsonPublish(uint16_t shortaddr) {
+ const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
+ if (json == nullptr) { return; } // don't crash if not found
+
+ String msg = "";
+ json->printTo(msg);
+ zigbee_devices.jsonClear(shortaddr);
+ Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
+ XdrvRulesProcess();
+}
+
// Dump the internal memory of Zigbee devices
// Mode = 1: simple dump of devices addresses and names
diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino
index 687ac2483..1423c53f1 100644
--- a/tasmota/xdrv_23_zigbee_5_converters.ino
+++ b/tasmota/xdrv_23_zigbee_5_converters.ino
@@ -100,6 +100,7 @@ public:
return _frame_control.b.frame_type & 1;
}
+ static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len);
void parseRawAttributes(JsonObject& json, uint8_t offset = 0);
void parseReadAttributes(JsonObject& json, uint8_t offset = 0);
void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0);
@@ -290,17 +291,20 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
bool parse_as_string = true;
uint32_t len = (attrtype <= 0x42) ? buf.get8(i) : buf.get16(i); // len is 8 or 16 bits
i += (attrtype <= 0x42) ? 1 : 2; // increment pointer
+ if (i + len > buf.len()) { // make sure we don't get past the buffer
+ len = buf.len() - i;
+ }
// check if we can safely use a string
if ((0x41 == attrtype) || (0x43 == attrtype)) { parse_as_string = false; }
- else {
- for (uint32_t j = 0; j < len; j++) {
- if (0x00 == buf.get8(i+j)) {
- parse_as_string = false;
- break;
- }
- }
- }
+ // else {
+ // for (uint32_t j = 0; j < len; j++) {
+ // if (0x00 == buf.get8(i+j)) {
+ // parse_as_string = false;
+ // break;
+ // }
+ // }
+ // }
if (parse_as_string) {
char str[len+1];
@@ -409,19 +413,28 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer
return i - offset; // how much have we increased the index
}
+// Generate an attribute name based on cluster number, attribute, and suffix if duplicates
+void ZCLFrame::generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len) {
+ uint32_t suffix = 1;
+
+ snprintf_P(key, key_len, PSTR("%04X/%04X"), cluster, attr);
+ while (json.containsKey(key)) {
+ suffix++;
+ snprintf_P(key, key_len, PSTR("%04X/%04X+%d"), cluster, attr, suffix); // add "0008/0001+2" suffix if duplicate
+ }
+}
// First pass, parse all attributes in their native format
void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) {
uint32_t i = offset;
uint32_t len = _payload.len();
- while (len - i >= 3) {
+ while (len >= i + 3) {
uint16_t attrid = _payload.get16(i);
i += 2;
char key[16];
- snprintf_P(key, sizeof(key), PSTR("%04X/%04X"),
- _cluster_id, attrid);
+ generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
// exception for Xiaomi lumi.weather - specific field to be treated as octet and not char
if ((0x0000 == _cluster_id) && (0xFF01 == attrid)) {
@@ -445,8 +458,7 @@ void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) {
if (0 == status) {
char key[16];
- snprintf_P(key, sizeof(key), PSTR("%04X/%04X"),
- _cluster_id, attrid);
+ generateAttributeName(json, _cluster_id, attrid, key, sizeof(key));
i += parseSingleAttribute(json, key, _payload, i, len);
}
@@ -472,7 +484,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
// return value:
// 0 = keep initial value
// 1 = remove initial value
-typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper* new_name, uint16_t cluster, uint16_t attr);
+typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr);
typedef struct Z_AttributeConverter {
uint16_t cluster;
uint16_t attribute;
@@ -480,8 +492,6 @@ typedef struct Z_AttributeConverter {
Z_AttrConverter func;
} Z_AttributeConverter;
-#define OCCUPANCY "Occupancy" // global define for Aqara
-
// list of post-processing directives
const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0000, 0x0000, "ZCLVersion", &Z_Copy },
@@ -511,6 +521,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// On/off cluster
{ 0x0006, 0x0000, "Power", &Z_Copy },
+ { 0x0006, 0x8000, "Power", &Z_Copy }, // See 7280
// On/Off Switch Configuration cluster
{ 0x0007, 0x0000, "SwitchType", &Z_Copy },
@@ -750,7 +761,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Occupancy Sensing cluster
- { 0x0406, 0x0000, OCCUPANCY, &Z_AqaraOccupancy }, // Occupancy (map8)
+ { 0x0406, 0x0000, OCCUPANCY, &Z_Copy }, // Occupancy (map8)
{ 0x0406, 0x0001, "OccupancySensorType", &Z_Copy }, // OccupancySensorType
{ 0x0406, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
@@ -776,13 +787,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// ======================================================================
// Record Manuf
-int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = value;
zigbee_devices.setManufId(shortaddr, value.as());
return 1;
}
//
-int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = value;
zigbee_devices.setModelId(shortaddr, value.as());
return 1;
@@ -790,38 +801,34 @@ int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& j
// ======================================================================
// Remove attribute
-int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
return 1; // remove original key
}
// Copy value as-is
-int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = value;
return 1; // remove original key
}
// Add pressure unit
-int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = F(D_UNIT_PRESSURE);
return 0; // keep original key
}
// Convert int to float and divide by 100
-int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = ((float)value) / 100.0f;
return 1; // remove original key
}
// Convert int to float and divide by 10
-int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = ((float)value) / 10.0f;
return 1; // remove original key
}
-
-// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
-// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
-const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
-
+// Publish a message for `"Occupancy":0` when the timer expired
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
// send Occupancy:false message
Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":{\"" OCCUPANCY "\":0}}}"), shortaddr);
@@ -829,20 +836,8 @@ int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpo
XdrvRulesProcess();
}
-int32_t Z_AqaraOccupancy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
- json[new_name] = value;
- uint32_t occupancy = value;
-
- if (occupancy) {
- zigbee_devices.setTimer(shortaddr, OCCUPANCY_TIMEOUT, cluster, zcl->getSrcEndpoint(), 0, &Z_OccupancyCallback);
- } else {
- zigbee_devices.resetTimer(shortaddr);
- }
- return 1; // remove original key
-}
-
// Aqara Vibration Sensor - special proprietary attributes
-int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
//json[new_name] = value;
switch (attr) {
case 0x0055:
@@ -896,7 +891,7 @@ int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje
return 1; // remove original key
}
-int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
+int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) {
String hex = value;
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint32_t i = 0;
@@ -942,11 +937,19 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
String key_string = kv.key;
const char * key = key_string.c_str();
JsonVariant& value = kv.value;
- // Check that format looks like "CCCC/AAAA"
+ // Check that format looks like "CCCC/AAAA" or "CCCC/AAAA+d"
char * delimiter = strchr(key, '/');
+ char * delimiter2 = strchr(key, '+');
if (delimiter) {
+ uint16_t attribute;
+ uint16_t suffix = 1;
uint16_t cluster = strtoul(key, &delimiter, 16);
- uint16_t attribute = strtoul(delimiter+1, nullptr, 16);
+ if (!delimiter2) {
+ attribute = strtoul(delimiter+1, nullptr, 16);
+ } else {
+ attribute = strtoul(delimiter+1, &delimiter2, 16);
+ suffix = strtoul(delimiter2+1, nullptr, 10);
+ }
// Iterate on filter
for (uint32_t i = 0; i < sizeof(Z_PostProcess) / sizeof(Z_PostProcess[0]); i++) {
@@ -956,7 +959,9 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
if ((conv_cluster == cluster) &&
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
- int32_t drop = (*converter->func)(this, shortaddr, json, key, value, (const __FlashStringHelper*) converter->name, conv_cluster, conv_attribute);
+ String new_name_str = converter->name;
+ if (suffix > 1) { new_name_str += suffix; } // append suffix number
+ int32_t drop = (*converter->func)(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute);
if (drop) {
json.remove(key);
}
diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino
index d4f3e459f..261a4ac6a 100644
--- a/tasmota/xdrv_23_zigbee_7_statemachine.ino
+++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino
@@ -32,6 +32,7 @@ const uint8_t ZIGBEE_STATUS_DEVICE_ANNOUNCE = 30; // Device announces its
const uint8_t ZIGBEE_STATUS_NODE_DESC = 31; // Node descriptor
const uint8_t ZIGBEE_STATUS_ACTIVE_EP = 32; // Endpoints descriptor
const uint8_t ZIGBEE_STATUS_SIMPLE_DESC = 33; // Simple Descriptor (clusters)
+const uint8_t ZIGBEE_STATUS_DEVICE_INDICATION = 34; // Device announces its address
const uint8_t ZIGBEE_STATUS_CC_VERSION = 50; // Status: CC2530 ZNP Version
const uint8_t ZIGBEE_STATUS_CC_INFO = 51; // Status: CC2530 Device Configuration
const uint8_t ZIGBEE_STATUS_UNSUPPORTED_VERSION = 98; // Unsupported ZNP version
diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino
index 2f30080fa..fcfeb06a7 100644
--- a/tasmota/xdrv_23_zigbee_8_parsers.ino
+++ b/tasmota/xdrv_23_zigbee_8_parsers.ino
@@ -357,6 +357,56 @@ int32_t Z_ReceiveEndDeviceAnnonce(int32_t res, const class SBuffer &buf) {
return -1;
}
+// 45CA
+int32_t Z_ReceiveTCDevInd(int32_t res, const class SBuffer &buf) {
+ Z_ShortAddress srcAddr = buf.get16(2);
+ Z_IEEEAddress ieeeAddr = buf.get64(4);
+ Z_ShortAddress parentNw = buf.get16(12);
+
+ zigbee_devices.updateDevice(srcAddr, ieeeAddr);
+
+ char hex[20];
+ Uint64toHex(ieeeAddr, hex, 64);
+ Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{"
+ "\"Status\":%d,\"IEEEAddr\":\"%s\",\"ShortAddr\":\"0x%04X\""
+ ",\"ParentNetwork\":\"0x%04X\"}}"),
+ ZIGBEE_STATUS_DEVICE_INDICATION, hex, srcAddr, parentNw
+ );
+
+ MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
+ XdrvRulesProcess();
+ //Z_SendActiveEpReq(srcAddr);
+ return -1;
+}
+
+// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
+// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
+const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
+
+void Z_AqaraOccupancy(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, const JsonObject *json) {
+ // Read OCCUPANCY value if any
+ const JsonVariant &val_endpoint = getCaseInsensitive(*json, PSTR(OCCUPANCY));
+ if (nullptr != &val_endpoint) {
+ uint32_t occupancy = strToUInt(val_endpoint);
+
+ if (occupancy) {
+ zigbee_devices.setTimer(shortaddr, OCCUPANCY_TIMEOUT, cluster, endpoint, 0, &Z_OccupancyCallback);
+ }
+ }
+}
+
+
+// Publish the received values once they have been coalesced
+int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
+ const JsonObject *json = zigbee_devices.jsonGet(shortaddr);
+ if (json == nullptr) { return 0; } // don't crash if not found
+ // Post-provess for Aqara Presence Senson
+ Z_AqaraOccupancy(shortaddr, cluster, endpoint, json);
+
+ zigbee_devices.jsonPublish(shortaddr);
+ return 1;
+}
+
int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint16_t groupid = buf.get16(2);
uint16_t clusterid = buf.get16(4);
@@ -369,6 +419,8 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint32_t timestamp = buf.get32(13);
uint8_t seqnumber = buf.get8(17);
+ bool defer_attributes = false; // do we defer attributes reporting to coalesce
+
zigbee_devices.updateLastSeen(srcaddr);
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid,
srcaddr,
@@ -384,13 +436,13 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
JsonObject& json1 = json_root.createNestedObject(F(D_CMND_ZIGBEE_RECEIVED));
JsonObject& json = json1.createNestedObject(shortaddr);
- // TODO add name field if it is known
if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) {
- zcl_received.parseRawAttributes(json);
+ zcl_received.parseRawAttributes(json);
+ if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages
} else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) {
zcl_received.parseReadAttributes(json);
} else if (zcl_received.isClusterSpecificCommand()) {
- zcl_received.parseClusterSpecificCommand(json);
+ zcl_received.parseClusterSpecificCommand(json);
}
String msg("");
msg.reserve(100);
@@ -401,11 +453,23 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
// Add linkquality
json[F(D_CMND_ZIGBEE_LINKQUALITY)] = linkquality;
- msg = "";
- json_root.printTo(msg);
- Response_P(PSTR("%s"), msg.c_str());
- MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
- XdrvRulesProcess();
+ if (defer_attributes) {
+ // Prepare for publish
+ if (zigbee_devices.jsonIsConflict(srcaddr, json)) {
+ // there is conflicting values, force a publish of the previous message now and don't coalesce
+ zigbee_devices.jsonPublish(srcaddr);
+ } else {
+ zigbee_devices.jsonAppend(srcaddr, json);
+ zigbee_devices.setTimer(srcaddr, USE_ZIGBEE_COALESCE_ATTR_TIMER, clusterid, srcendpoint, 0, &Z_PublishAttributes);
+ }
+ } else {
+ // Publish immediately
+ msg = "";
+ json_root.printTo(msg);
+ Response_P(PSTR("%s"), msg.c_str());
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
+ XdrvRulesProcess();
+ }
return -1;
}
@@ -417,6 +481,7 @@ typedef struct Z_Dispatcher {
// Filters for ZCL frames
ZBM(AREQ_AF_INCOMING_MESSAGE, Z_AREQ | Z_AF, AF_INCOMING_MSG) // 4481
ZBM(AREQ_END_DEVICE_ANNCE_IND, Z_AREQ | Z_ZDO, ZDO_END_DEVICE_ANNCE_IND) // 45C1
+ZBM(AREQ_END_DEVICE_TC_DEV_IND, Z_AREQ | Z_ZDO, ZDO_TC_DEV_IND) // 45CA
ZBM(AREQ_PERMITJOIN_OPEN_XX, Z_AREQ | Z_ZDO, ZDO_PERMIT_JOIN_IND ) // 45CB
ZBM(AREQ_ZDO_ACTIVEEPRSP, Z_AREQ | Z_ZDO, ZDO_ACTIVE_EP_RSP) // 4585
ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
@@ -424,6 +489,7 @@ ZBM(AREQ_ZDO_SIMPLEDESCRSP, Z_AREQ | Z_ZDO, ZDO_SIMPLE_DESC_RSP) // 4584
const Z_Dispatcher Z_DispatchTable[] PROGMEM = {
{ AREQ_AF_INCOMING_MESSAGE, &Z_ReceiveAfIncomingMessage },
{ AREQ_END_DEVICE_ANNCE_IND, &Z_ReceiveEndDeviceAnnonce },
+ { AREQ_END_DEVICE_TC_DEV_IND, &Z_ReceiveTCDevInd },
{ AREQ_PERMITJOIN_OPEN_XX, &Z_ReceivePermitJoinStatus },
{ AREQ_ZDO_NODEDESCRSP, &Z_ReceiveNodeDesc },
{ AREQ_ZDO_ACTIVEEPRSP, &Z_ReceiveActiveEp },
diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino
index 461b3af32..59cf382b0 100644
--- a/tasmota/xdrv_23_zigbee_9_impl.ino
+++ b/tasmota/xdrv_23_zigbee_9_impl.ino
@@ -31,12 +31,14 @@ TasmotaSerial *ZigbeeSerial = nullptr;
const char kZigbeeCommands[] PROGMEM = "|"
D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|"
D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|"
- D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ ;
+ D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE
+ ;
void (* const ZigbeeCommand[])(void) PROGMEM = {
&CmndZigbeeZNPSend, &CmndZigbeePermitJoin,
&CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend,
- &CmndZigbeeProbe, &CmndZigbeeRead };
+ &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive
+ };
int32_t ZigbeeProcessInput(class SBuffer &buf) {
if (!zigbee.state_machine) { return -1; } // if state machine is stopped, send 'ignore' message
@@ -268,7 +270,7 @@ void CmndZigbeeStatus(void) {
}
}
-void CmndZigbeeZNPSend(void)
+void CmndZigbeeZNPSendOrReceive(bool send)
{
if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) {
uint8_t code;
@@ -286,11 +288,26 @@ void CmndZigbeeZNPSend(void)
size -= 2;
codes += 2;
}
- ZigbeeZNPSend(buf.getBuffer(), buf.len());
+ if (send) {
+ ZigbeeZNPSend(buf.getBuffer(), buf.len());
+ } else {
+ ZigbeeProcessInput(buf);
+ }
}
ResponseCmndDone();
}
+// For debug purposes only, simulates a message received
+void CmndZigbeeZNPReceive(void)
+{
+ CmndZigbeeZNPSendOrReceive(false);
+}
+
+void CmndZigbeeZNPSend(void)
+{
+ CmndZigbeeZNPSendOrReceive(true);
+}
+
void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
if ((len < 2) || (len > 252)) {
// abort, message cannot be less than 2 bytes for CMD1 and CMD2
@@ -423,25 +440,6 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
ResponseCmndDone();
}
-// Get an JSON attribute, with case insensitive key search
-JsonVariant &getCaseInsensitive(const JsonObject &json, const char *needle) {
- // key can be in PROGMEM
- if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) {
- return *(JsonVariant*)nullptr;
- }
-
- for (auto kv : json) {
- const char *key = kv.key;
- JsonVariant &value = kv.value;
-
- if (0 == strcasecmp_P(key, needle)) {
- return value;
- }
- }
- // if not found
- return *(JsonVariant*)nullptr;
-}
-
void CmndZigbeeSend(void) {
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} }
// ZigbeeSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} }
diff --git a/tasmota/xdrv_29_deepsleep.ino b/tasmota/xdrv_29_deepsleep.ino
index f0a8de685..69eed78f1 100644
--- a/tasmota/xdrv_29_deepsleep.ino
+++ b/tasmota/xdrv_29_deepsleep.ino
@@ -192,7 +192,7 @@ bool Xdrv29(uint8_t function)
DeepSleepEverySecond();
break;
case FUNC_AFTER_TELEPERIOD:
- if (DeepSleepEnabled() && !deepsleep_flag) {
+ if (DeepSleepEnabled() && !deepsleep_flag && (Settings.tele_period == 10 || Settings.tele_period == 300 || UpTime() > Settings.tele_period)) {
deepsleep_flag = DEEPSLEEP_START_COUNTDOWN; // Start deepsleep in 4 seconds
}
break;
diff --git a/tasmota/xdsp_02_ssd1306.ino b/tasmota/xdsp_02_ssd1306.ino
index e263901bc..0bee9d10d 100644
--- a/tasmota/xdsp_02_ssd1306.ino
+++ b/tasmota/xdsp_02_ssd1306.ino
@@ -124,8 +124,7 @@ void Ssd1306PrintLog(void)
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
DisplayFillScreen(last_row);
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
- AddLog(LOG_LEVEL_DEBUG);
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
renderer->println(disp_screen_buffer[last_row]);
renderer->Updateframe();
diff --git a/tasmota/xdsp_07_sh1106.ino b/tasmota/xdsp_07_sh1106.ino
index eba56a442..00babbdf9 100644
--- a/tasmota/xdsp_07_sh1106.ino
+++ b/tasmota/xdsp_07_sh1106.ino
@@ -119,8 +119,7 @@ void SH1106PrintLog(void)
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
DisplayFillScreen(last_row);
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
- AddLog(LOG_LEVEL_DEBUG);
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
renderer->println(disp_screen_buffer[last_row]);
renderer->Updateframe();
diff --git a/tasmota/xdsp_09_SSD1351.ino b/tasmota/xdsp_09_SSD1351.ino
index ad7e86f99..0391bf8f4 100644
--- a/tasmota/xdsp_09_SSD1351.ino
+++ b/tasmota/xdsp_09_SSD1351.ino
@@ -112,8 +112,7 @@ void SSD1351PrintLog(void)
strlcpy(disp_screen_buffer[last_row], txt, disp_screen_buffer_cols);
DisplayFillScreen(last_row);
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
- AddLog(LOG_LEVEL_DEBUG);
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "[%s]"), disp_screen_buffer[last_row]);
renderer->println(disp_screen_buffer[last_row]);
renderer->Updateframe();
diff --git a/tasmota/xsns_02_analog.ino b/tasmota/xsns_02_analog.ino
index 1a4bc090b..3dd974918 100644
--- a/tasmota/xsns_02_analog.ino
+++ b/tasmota/xsns_02_analog.ino
@@ -66,6 +66,12 @@ void AdcInit(void)
Settings.adc_param2 = ANALOG_LDR_LUX_CALC_SCALAR;
Settings.adc_param3 = ANALOG_LDR_LUX_CALC_EXPONENT * 10000;
}
+ else if (ADC0_MOIST == my_adc0) {
+ Settings.adc_param_type = ADC0_MOIST;
+ Settings.adc_param1 = 0;
+ Settings.adc_param2 = 1023;
+ Settings.adc_param3 = 0;
+ }
}
}
@@ -113,6 +119,20 @@ uint16_t AdcGetLux(void)
return (uint16_t)ldrLux;
}
+uint16_t AdcGetMoist(void)
+// formula for calibration: value, fromLow, fromHigh, toHigh, toLow
+// Example: 632, 0, 1023, 100, 0
+// int( ( ( ( - ) / ( - ) ) * ( - ) ) + )
+// double amoist = ((Settings.adc_param2 - (double)adc) / (Settings.adc_param2 - Settings.adc_param1) * 100;
+// int((((1023 - ) / ( 1023 - 0 )) * ( 100 - 0 )) + 0 )
+
+{
+ int adc = AdcRead(2);
+ double amoist = ((double)Settings.adc_param2 - (double)adc) / ((double)Settings.adc_param2 - (double)Settings.adc_param1) * 100;
+ //double amoist = ((1023 - (double)adc) / 1023) * 100;
+ return (uint16_t)amoist;
+}
+
void AdcEverySecond(void)
{
if (ADC0_TEMP == my_adc0) {
@@ -173,6 +193,17 @@ void AdcShow(bool json)
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_SNS_ILLUMINANCE, "", adc_light);
+#endif // USE_WEBSERVER
+ }
+ }
+ else if (ADC0_MOIST == my_adc0) {
+ uint16_t adc_moist = AdcGetMoist();
+
+ if (json) {
+ ResponseAppend_P(JSON_SNS_MOISTURE, "ANALOG", adc_moist);
+#ifdef USE_WEBSERVER
+ } else {
+ WSContentSend_PD(HTTP_SNS_MOISTURE, "", adc_moist);
#endif // USE_WEBSERVER
}
}
@@ -182,7 +213,6 @@ void AdcShow(bool json)
* Commands
\*********************************************************************************************/
-#define D_CMND_ADCPARAM "AdcParam"
const char kAdcCommands[] PROGMEM = "|" // No prefix
D_CMND_ADC "|" D_CMND_ADCS "|" D_CMND_ADCPARAM;
@@ -217,7 +247,7 @@ void CmndAdcs(void)
void CmndAdcParam(void)
{
if (XdrvMailbox.data_len) {
- if ((ADC0_TEMP == XdrvMailbox.payload) || (ADC0_LIGHT == XdrvMailbox.payload)) {
+ if ((ADC0_TEMP == XdrvMailbox.payload) || (ADC0_LIGHT == XdrvMailbox.payload) || (ADC0_MOIST == XdrvMailbox.payload)) {
// if ((XdrvMailbox.payload == my_adc0) && ((ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0))) {
if (strstr(XdrvMailbox.data, ",") != nullptr) { // Process parameter entry
char sub_string[XdrvMailbox.data_len +1];
@@ -227,10 +257,13 @@ void CmndAdcParam(void)
// Settings.adc_param_type = my_adc0;
Settings.adc_param1 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
Settings.adc_param2 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 3), nullptr, 10);
- Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000);
+ if (!ADC0_MOIST == XdrvMailbox.payload) {
+ Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000);
+ }
} else { // Set default values based on current adc type
// AdcParam 2
// AdcParam 3
+ // AdcParam 6
Settings.adc_param_type = 0;
AdcInit();
}
@@ -246,8 +279,14 @@ void CmndAdcParam(void)
}
char param3[33];
dtostrfd(((double)Settings.adc_param3)/10000, precision, param3);
- Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d,%s]}"),
- Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2, param3);
+ if ((ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0)) {
+ Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d,%s]}"),
+ Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2, param3);
+ }
+ else if (ADC0_MOIST == my_adc0) {
+ Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d]}"),
+ Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2);
+ }
}
/*********************************************************************************************\
@@ -263,7 +302,7 @@ bool Xsns02(uint8_t function)
result = DecodeCommand(kAdcCommands, AdcCommand);
break;
default:
- if ((ADC0_INPUT == my_adc0) || (ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0)) {
+ if ((ADC0_INPUT == my_adc0) || (ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0) || (ADC0_MOIST == my_adc0)) {
switch (function) {
#ifdef USE_RULES
case FUNC_EVERY_250_MSECOND:
diff --git a/tasmota/xsns_41_max44009.ino b/tasmota/xsns_41_max44009.ino
index 2cdf6b78d..e1ef99ee1 100644
--- a/tasmota/xsns_41_max44009.ino
+++ b/tasmota/xsns_41_max44009.ino
@@ -75,8 +75,7 @@ void Max4409Detect(void)
if ((I2cValidRead8(&buffer1, max44009_address, REG_LOWER_THRESHOLD)) &&
(I2cValidRead8(&buffer2, max44009_address, REG_THRESHOLD_TIMER))) {
- //snprintf(log_data, sizeof(log_data), "MAX44009 %x: %x, %x", max44009_address, (int)buffer1, (int)buffer2);
- //AddLog(LOG_LEVEL_DEBUG_MORE);
+ //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("MAX44009 %x: %x, %x"), max44009_address, (int)buffer1, (int)buffer2);
if ((0x00 == buffer1) &&
(0xFF == buffer2)) {
diff --git a/tasmota/xsns_42_scd30.ino b/tasmota/xsns_42_scd30.ino
index d3bb5cbef..12ac95cd2 100644
--- a/tasmota/xsns_42_scd30.ino
+++ b/tasmota/xsns_42_scd30.ino
@@ -23,6 +23,8 @@
#define XSNS_42 42
#define XI2C_29 29 // See I2CDEVICES.md
+//#define SCD30_DEBUG
+
#define SCD30_ADDRESS 0x61
#define SCD30_MAX_MISSED_READS 3
@@ -118,24 +120,23 @@ void Scd30Update(void)
scd30ErrorState = SCD30_STATE_ERROR_DATA_CRC;
scd30CrcError_count++;
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld", scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"),
+ scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
#endif
break;
case ERROR_SCD30_CO2_ZERO:
scd30Co2Zero_count++;
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld", scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld"),
+ scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
#endif
break;
default: {
scd30ErrorState = SCD30_STATE_ERROR_READ_MEAS;
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld", error, scd30Loop_count);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld"), error, scd30Loop_count);
#endif
return;
}
@@ -147,10 +148,9 @@ void Scd30Update(void)
case SCD30_STATE_ERROR_DATA_CRC: {
//scd30IsDataValid = false;
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
- AddLog(LOG_LEVEL_ERROR);
- snprintf_P(log_data, sizeof(log_data), "SCD30: got CRC error, try again, counter: %ld", scd30Loop_count);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
+ scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: got CRC error, try again, counter: %ld"), scd30Loop_count);
#endif
scd30ErrorState = ERROR_SCD30_NO_ERROR;
}
@@ -159,17 +159,15 @@ void Scd30Update(void)
case SCD30_STATE_ERROR_READ_MEAS: {
//scd30IsDataValid = false;
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
- AddLog(LOG_LEVEL_ERROR);
- snprintf_P(log_data, sizeof(log_data), "SCD30: not answering, sending soft reset, counter: %ld", scd30Loop_count);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
+ scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: not answering, sending soft reset, counter: %ld"), scd30Loop_count);
#endif
scd30Reset_count++;
error = scd30.softReset();
if (error) {
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: resetting got error: 0x%lX", error);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: resetting got error: 0x%lX"), error);
#endif
error >>= 8;
if (error == 4) {
@@ -186,18 +184,16 @@ void Scd30Update(void)
case SCD30_STATE_ERROR_SOFT_RESET: {
//scd30IsDataValid = false;
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
- AddLog(LOG_LEVEL_ERROR);
- snprintf_P(log_data, sizeof(log_data), "SCD30: clearing i2c bus");
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld"),
+ scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog_P(LOG_LEVEL_ERROR, PSTR("SCD30: clearing i2c bus"));
#endif
i2cReset_count++;
error = scd30.clearI2CBus();
if (error) {
scd30ErrorState = SCD30_STATE_ERROR_I2C_RESET;
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: error clearing i2c bus: 0x%lX", error);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error clearing i2c bus: 0x%lX"), error);
#endif
} else {
scd30ErrorState = ERROR_SCD30_NO_ERROR;
@@ -342,8 +338,7 @@ bool Scd30CommandSensor()
if (error)
{
#ifdef SCD30_DEBUG
- snprintf_P(log_data, sizeof(log_data), "SCD30: error getting FW version: 0x%lX", error);
- AddLog(LOG_LEVEL_ERROR);
+ AddLog_P2(LOG_LEVEL_ERROR, PSTR("SCD30: error getting FW version: 0x%lX"), error);
#endif
serviced = false;
}
diff --git a/tasmota/xsns_48_chirp.ino b/tasmota/xsns_48_chirp.ino
index 84fcd4f66..f761e6afc 100644
--- a/tasmota/xsns_48_chirp.ino
+++ b/tasmota/xsns_48_chirp.ino
@@ -397,22 +397,17 @@ void ChirpEvery100MSecond(void)
}
/********************************************************************************************/
+
// normaly in i18n.h
-
-#define D_JSON_MOISTURE "Moisture"
-#define D_JSON_DARKNESS "Darkness"
-
#ifdef USE_WEBSERVER
// {s} = , {m} = | , {e} = |
- const char HTTP_SNS_MOISTURE[] PROGMEM = "{s} " D_JSON_MOISTURE "{m}%s %{e}";
- const char HTTP_SNS_DARKNESS[] PROGMEM = "{s} " D_JSON_DARKNESS "{m}%s %{e}";
- const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}"
- "{s} FW-version{m}%s {e}"; ;
- const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}";
+const char HTTP_SNS_DARKNESS[] PROGMEM = "{s} " D_JSON_DARKNESS "{m}%s %%{e}";
+const char HTTP_SNS_CHIRPVER[] PROGMEM = "{s} CHIRP-sensor %u at address{m}0x%x{e}"
+ "{s} FW-version{m}%s {e}"; ;
+const char HTTP_SNS_CHIRPSLEEP[] PROGMEM = "{s} {m} is sleeping ...{e}";
#endif // USE_WEBSERVER
-
/********************************************************************************************/
void ChirpShow(bool json)
@@ -420,8 +415,6 @@ void ChirpShow(bool json)
for (uint32_t i = 0; i < chirp_found_sensors; i++) {
if (chirp_sensor[i].version) {
// convert double values to string
- char str_moisture[33];
- dtostrfd(chirp_sensor[i].moisture, 0, str_moisture);
char str_temperature[33];
double t_temperature = ((double) chirp_sensor[i].temperature )/10.0;
dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature);
@@ -434,9 +427,10 @@ void ChirpShow(bool json)
else{
sprintf(str_version, "%x", chirp_sensor[i].version);
}
+
if (json) {
if(!chirp_sensor[i].explicitSleep) {
- ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%s"),chirp_name, i, str_moisture);
+ ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%d"), chirp_name, i, chirp_sensor[i].moisture);
if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature
ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature);
}
@@ -447,9 +441,11 @@ void ChirpShow(bool json)
}
#ifdef USE_DOMOTICZ
if (0 == tele_period) {
- DomoticzTempHumSensor(str_temperature, str_moisture);
- DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); // this is not LUX!!
- }
+ char str_moisture[33];
+ dtostrfd(chirp_sensor[i].moisture, 0, str_moisture);
+ DomoticzTempHumSensor(str_temperature, str_moisture);
+ DomoticzSensor(DZ_ILLUMINANCE,chirp_sensor[i].light); // this is not LUX!!
+ }
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
@@ -458,10 +454,10 @@ void ChirpShow(bool json)
WSContentSend_PD(HTTP_SNS_CHIRPSLEEP);
}
else {
- WSContentSend_PD(HTTP_SNS_MOISTURE, str_moisture);
+ WSContentSend_PD(HTTP_SNS_MOISTURE, "", chirp_sensor[i].moisture);
WSContentSend_PD(HTTP_SNS_DARKNESS, str_light);
- if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature
- WSContentSend_PD(HTTP_SNS_TEMP, " ",str_temperature, TempUnit());
+ if (chirp_sensor[i].temperature!=-1) { // this is the error code -> no temperature
+ WSContentSend_PD(HTTP_SNS_TEMP, "", str_temperature, TempUnit());
}
}
diff --git a/tasmota/xsns_53_sml.ino b/tasmota/xsns_53_sml.ino
index 7aa0fd22d..672f7f826 100755
--- a/tasmota/xsns_53_sml.ino
+++ b/tasmota/xsns_53_sml.ino
@@ -493,19 +493,19 @@ const uint8_t meter[]=
//=====================================================
// median filter eliminates outliers, but uses much RAM and CPU cycles
-// 672 bytes extra RAM with MAX_VARS = 16
+// 672 bytes extra RAM with SML_MAX_VARS = 16
// default compile on, but must be enabled by descriptor flag 16
// may be undefined if RAM must be saved
#define USE_SML_MEDIAN_FILTER
// max number of vars , may be adjusted
-#ifndef MAX_VARS
-#define MAX_VARS 20
+#ifndef SML_MAX_VARS
+#define SML_MAX_VARS 20
#endif
// max number of meters , may be adjusted
#define MAX_METERS 5
-double meter_vars[MAX_VARS];
+double meter_vars[SML_MAX_VARS];
// calulate deltas
#define MAX_DVARS MAX_METERS*2
double dvalues[MAX_DVARS];
@@ -540,7 +540,7 @@ uint8_t sml_desc_cnt;
struct SML_MEDIAN_FILTER {
double buffer[MEDIAN_SIZE];
int8_t index;
-} sml_mf[MAX_VARS];
+} sml_mf[SML_MAX_VARS];
#ifndef FLT_MAX
#define FLT_MAX 99999999
@@ -1326,7 +1326,7 @@ void SML_Decode(uint8_t index) {
uint32_t ind;
ind=atoi(mp);
while (*mp>='0' && *mp<='9') mp++;
- if (ind<1 || ind>MAX_VARS) ind=1;
+ if (ind<1 || ind>SML_MAX_VARS) ind=1;
dvar=meter_vars[ind-1];
for (uint8_t p=0;p<5;p++) {
if (*mp=='@') {
@@ -1345,7 +1345,7 @@ void SML_Decode(uint8_t index) {
}
ind=atoi(mp);
while (*mp>='0' && *mp<='9') mp++;
- if (ind<1 || ind>MAX_VARS) ind=1;
+ if (ind<1 || ind>SML_MAX_VARS) ind=1;
switch (opr) {
case '+':
if (iflg) dvar+=ind;
@@ -1381,7 +1381,7 @@ void SML_Decode(uint8_t index) {
while (*mp==' ') mp++;
uint8_t ind=atoi(mp);
while (*mp>='0' && *mp<='9') mp++;
- if (ind<1 || ind>MAX_VARS) ind=1;
+ if (ind<1 || ind>SML_MAX_VARS) ind=1;
uint32_t delay=atoi(mp)*1000;
uint32_t dtime=millis()-dtimes[dindex];
if (dtime>delay) {
@@ -1604,7 +1604,7 @@ void SML_Decode(uint8_t index) {
}
nextsect:
// next section
- if (vindex.
+*/
+
+#ifdef USE_GPS
+/*********************************************************************************************\
+ --------------------------------------------------------------------------------------------
+ Version Date Action Description
+ --------------------------------------------------------------------------------------------
+
+ 0.9.1.0 20191216 integrate - Added pin specifications from Tasmota WEB UI. Minor tweaks.
+ ---
+ 0.9.0.0 20190817 started - further development by Christian Baars - https://github.com/Staars/Sonoff-Tasmota
+ forked - from arendst/tasmota - https://github.com/arendst/Sonoff-Tasmota
+ base - code base from arendst and - https://www.youtube.com/watch?v=TwhCX0c8Xe0
+
+## GPS-driver for the Ublox-series 6-8
+Driver is tested on a NEO-6m and a Beitian-220. Series 7 should work too. This adds only about 6kb to the program size, because the efficient UBX-protocol is used. These modules are quite cheap, starting at about 3.50€ for the NEO-6m.
+
+## Features:
+- get position and time data
+- sets system time automatically and Settings.latitude and Settings.longitude via command
+- can log postion data with timestamp to flash with a small memory footprint of only 12 Bytes per record
+- constructs a GPX-file for download of this data
+- Web-UI
+- simplified NTP-server
+- command interface
+
+## Usage:
+The serial pins are GPX_RX and GPS_TX, no further installation steps needed. To get more debug information compile it with option "DEBUG_TASMOTA_SENSOR".
+
+
+## Commands:
+
++ sensor60 0
+ write to all available sectors, then restart and overwrite the older ones
+
++ sensor60 1
+ write to all available sectors, then restart and overwrite the older ones
+
++ sensor60 2
+ filter out horizontal drift noise
+
++ sensor60 3
+ turn off noise filter
+
++ sensor60 4
+ start recording, new data will be appended
+
++ sensor60 5
+ start new recording, old data will lost
+
++ sensor60 6
+ stop recording, download link will be visible in Web-UI
+
++ sensor60 7
+ send mqtt on new postion + TELE -> consider to set TELE to a very high value
+
++ sensor60 8
+ only TELE message
+
++ sensor60 9
+ start NTP-server
+
++ sensor60 10
+ deactivate NTP-server
+
++ sensor60 11
+ force update of Tasmota-system-UTC with every new GPS-time-message
+
++ sensor60 12
+ do not update of Tasmota-system-UTC with every new GPS-time-message
+
++ sensor60 13
+ set latitude and longitude in settings
+
+
+
+## Rules examples for SSD1306 32x128
+
+
+rule1 on tele-GPS#lat do DisplayText [s1p21c1l01f1]LAT: %value% endon on tele-GPS#lon do DisplayText [s1p21c1l2]LON: %value% endon on switch1#state==3 do sensor60 4 endon on switch1#state==2 do sensor60 6 endon
+
+rule2 on tele-GPS#int>9 do DisplayText [f0c9l4]I%value% endon on tele-GPS#int<10 do DisplayText [f0c9l4]I0%value% endon on tele-GPS#fil==1 do DisplayText [f0c18l4]F endon on tele-GPS#fil==0 do DisplayText [f0c18l4]N endon
+
+rule3 on tele-FLOG#sec do DisplayText [f0c1l4]SAV:%value% endon on tele-FLOG#rec==1 do DisplayText [f0c1l4]REC: endon on tele-FLOG#mode do DisplayText [f0c14l4]M%value% endon
+
+\*********************************************************************************************/
+
+#define XSNS_60 60
+
+#include "NTPServer.h"
+#include "NTPPacket.h"
+
+/*********************************************************************************************\
+ * constants
+\*********************************************************************************************/
+
+#define D_CMND_UBX "UBX"
+
+const char S_JSON_UBX_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_UBX "%s\":%d}";
+
+const char kUBXTypes[] PROGMEM = "UBX";
+
+#define UBX_LAT_LON_THRESHOLD 1000 // filter out some noise of local drift
+
+/********************************************************************************************\
+| *globals
+\*********************************************************************************************/
+
+const char UBLOX_INIT[] PROGMEM = {
+ // Disable NMEA
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x24, // GxGGA off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x2B, // GxGLL off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x02,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x32, // GxGSA off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x03,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x39, // GxGSV off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x04,0x00,0x00,0x00,0x00,0x00,0x01,0x04,0x40, // GxRMC off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0xF0,0x05,0x00,0x00,0x00,0x00,0x00,0x01,0x05,0x47, // GxVTG off
+
+ // Disable UBX
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0xDC, //NAV-PVT off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0xB9, //NAV-POSLLH off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xC0, //NAV-STATUS off
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x31,0x92, //NAV-TIMEUTC off
+
+ // Enable UBX
+ // 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x07,0x00,0x01,0x00,0x00,0x00,0x00,0x18,0xE1, //NAV-PVT on
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x13,0xBE, //NAV-POSLLH on
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x14,0xC5, //NAV-STATUS on
+ 0xB5,0x62,0x06,0x01,0x08,0x00,0x01,0x21,0x00,0x01,0x00,0x00,0x00,0x00,0x32,0x97, //NAV-TIMEUTC on
+
+ // Rate - we will not reset it for the moment after restart
+ // 0xB5,0x62,0x06,0x08,0x06,0x00,0x64,0x00,0x01,0x00,0x01,0x00,0x7A,0x12, //(10Hz)
+ // 0xB5,0x62,0x06,0x08,0x06,0x00,0xC8,0x00,0x01,0x00,0x01,0x00,0xDE,0x6A, //(5Hz)
+ // 0xB5,0x62,0x06,0x08,0x06,0x00,0xE8,0x03,0x01,0x00,0x01,0x00,0x01,0x39 //(1Hz)
+ // 0xB5,0x62,0x06,0x08,0x06,0x00,0xD0,0x07,0x01,0x00,0x01,0x00,0xED,0xBD //(0.5Hz)
+};
+
+char UBX_name[4];
+
+struct UBX_t {
+ const char UBX_HEADER[2] = { 0xB5, 0x62 }; // TODO: Check if we really save space here inside the struct
+ const char NAV_POSLLH_HEADER[2] = { 0x01, 0x02 };
+ const char NAV_STATUS_HEADER[2] = { 0x01, 0x03 };
+ const char NAV_TIME_HEADER[2] = { 0x01, 0x21 };
+
+ struct entry_t {
+ int32_t lat; //raw sensor value
+ int32_t lon; //raw sensor value
+ uint32_t time; //local time from system (maybe provided by the sensor)
+ };
+
+ union {
+ entry_t values;
+ uint8_t bytes[sizeof(entry_t)];
+ } rec_buffer;
+
+ struct POLL_MSG {
+ uint8_t cls;
+ uint8_t id;
+ uint16_t zero;
+ };
+
+ struct NAV_POSLLH {
+ uint8_t cls;
+ uint8_t id;
+ uint16_t len;
+ uint32_t iTOW;
+ int32_t lon;
+ int32_t lat;
+ int32_t height;
+ int32_t hMSL;
+ uint32_t hAcc;
+ uint32_t vAcc;
+ };
+
+ struct NAV_STATUS {
+ uint8_t cls;
+ uint8_t id;
+ uint16_t len;
+ uint32_t iTOW;
+ uint8_t gpsFix;
+ uint8_t flags; //bit 0 - gpsfix valid
+ uint8_t fixStat;
+ uint8_t flags2;
+ uint32_t ttff;
+ uint32_t msss;
+ };
+
+ struct NAV_TIME_UTC {
+ uint8_t cls;
+ uint8_t id;
+ uint16_t len;
+ uint32_t iTOW;
+ uint32_t tAcc;
+ int32_t nano; // Nanoseconds of second, range -1e9 .. 1e9 (UTC)
+ uint16_t year;
+ uint8_t month;
+ uint8_t day;
+ uint8_t hour;
+ uint8_t min;
+ uint8_t sec;
+ struct {
+ uint8_t UTC:1;
+ uint8_t WKN:1; // week number
+ uint8_t TOW:1; // time of week
+ uint8_t padding:5;
+ } valid;
+ };
+
+ struct CFG_RATE {
+ uint8_t cls; //0x06
+ uint8_t id; //0x08
+ uint16_t len; // 6 bytes
+ uint16_t measRate; // in every ms -> 1 Hz = 1000 ms; 10 Hz = 100 ms -> x = 1000 ms / Hz
+ uint16_t navRate; // x measurements for 1 navigation event
+ uint16_t timeRef; // align to time system: 0= UTC, 1 = GPS, 2 = GLONASS, ...
+ char CK[2]; // checksum
+ };
+
+ struct {
+ uint32_t last_iTOW;
+ int32_t last_lat;
+ int32_t last_lon;
+ int32_t last_height;
+ uint32_t last_hAcc;
+ uint32_t last_vAcc;
+ uint8_t gpsFix;
+ uint8_t non_empty_loops; // in case of an unintended reset of the GPS, the serial interface will get flooded with NMEA
+ uint16_t log_interval; // in tenth of seconds
+ } state;
+
+ struct {
+ uint32_t filter_noise:1;
+ uint32_t send_when_new:1; // no teleinterval
+ uint32_t send_UI_only:1;
+ uint32_t runningNTP:1;
+ uint32_t forceUTCupdate:1;
+ // TODO: more to come
+ } mode;
+
+ union {
+ NAV_POSLLH navPosllh;
+ NAV_STATUS navStatus;
+ NAV_TIME_UTC navTime;
+ POLL_MSG pollMsg;
+ CFG_RATE cfgRate;
+ } Message;
+
+} UBX;
+
+enum UBXMsgType {
+ MT_NONE,
+ MT_NAV_POSLLH,
+ MT_NAV_STATUS,
+ MT_NAV_TIME,
+ MT_POLL
+};
+
+#ifdef USE_FLOG
+FLOG *Flog = nullptr;
+#endif //USE_FLOG
+TasmotaSerial *UBXSerial;
+
+NtpServer timeServer(PortUdp);
+
+/*********************************************************************************************\
+ * helper function
+\*********************************************************************************************/
+
+void UBXcalcChecksum(char* CK, size_t msgSize)
+{
+ memset(CK, 0, 2);
+ for (int i = 0; i < msgSize; i++) {
+ CK[0] += ((char*)(&UBX.Message))[i];
+ CK[1] += CK[0];
+ }
+}
+
+bool UBXcompareMsgHeader(const char* msgHeader)
+{
+ char* ptr = (char*)(&UBX.Message);
+ return ptr[0] == msgHeader[0] && ptr[1] == msgHeader[1];
+}
+
+void UBXinitCFG(void)
+{
+ for (uint32_t i = 0; i < sizeof(UBLOX_INIT); i++) {
+ UBXSerial->write( pgm_read_byte(UBLOX_INIT+i) );
+ }
+ DEBUG_SENSOR_LOG(PSTR("UBX: turn off NMEA"));
+}
+
+void UBXTriggerTele(void)
+{
+ mqtt_data[0] = '\0';
+ if (MqttShowSensor()) {
+ MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain);
+#ifdef USE_RULES
+ RulesTeleperiod(); // Allow rule based HA messages
+#endif // USE_RULES
+ }
+}
+
+/********************************************************************************************/
+
+void UBXDetect(void)
+{
+ if ((pin[GPIO_GPS_RX] < 99) && (pin[GPIO_GPS_TX] < 99)) {
+ UBXSerial = new TasmotaSerial(pin[GPIO_GPS_RX], pin[GPIO_GPS_TX], 1, 0, 96); // 64 byte buffer is NOT enough
+ if (UBXSerial->begin(9600)) {
+ DEBUG_SENSOR_LOG(PSTR("UBX: started serial"));
+ if (UBXSerial->hardwareSerial()) {
+ ClaimSerial();
+ DEBUG_SENSOR_LOG(PSTR("UBX: claim HW"));
+ }
+ }
+ }
+
+ UBXinitCFG(); // turn of NMEA, only use "our" UBX-messages
+#ifdef USE_FLOG
+ if (!Flog) {
+ Flog = new FLOG; // init Flash Log
+ Flog->init();
+ }
+#endif // USE_FLOG
+
+ UBX.state.log_interval = 10; // 1 second
+ UBX.mode.send_UI_only = true; // send UI data ...
+ UBXTriggerTele(); // ... once at after start
+}
+
+uint32_t UBXprocessGPS()
+{
+ static uint32_t fpos = 0;
+ static char checksum[2];
+ static uint8_t currentMsgType = MT_NONE;
+ static size_t payloadSize = sizeof(UBX.Message);
+
+ // DEBUG_SENSOR_LOG(PSTR("UBX: check for serial data"));
+ uint32_t data_bytes = 0;
+ while ( UBXSerial->available() ) {
+ data_bytes++;
+ byte c = UBXSerial->read();
+ if ( fpos < 2 ) {
+ // For the first two bytes we are simply looking for a match with the UBX header bytes (0xB5,0x62)
+ if ( c == UBX.UBX_HEADER[fpos] ) {
+ fpos++;
+ } else {
+ fpos = 0; // Reset to beginning state.
+ }
+ } else {
+ // If we come here then fpos >= 2, which means we have found a match with the UBX_HEADER
+ // and we are now reading in the bytes that make up the payload.
+
+ // Place the incoming byte into the ubxMessage struct. The position is fpos-2 because
+ // the struct does not include the initial two-byte header (UBX_HEADER).
+ if ( (fpos-2) < payloadSize ) {
+ ((char*)(&UBX.Message))[fpos-2] = c;
+ }
+ fpos++;
+
+ if ( fpos == 4 ) {
+ // We have just received the second byte of the message type header,
+ // so now we can check to see what kind of message it is.
+ if ( UBXcompareMsgHeader(UBX.NAV_POSLLH_HEADER) ) {
+ currentMsgType = MT_NAV_POSLLH;
+ payloadSize = sizeof(UBX_t::NAV_POSLLH);
+ DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_POSLLH"));
+ }
+ else if ( UBXcompareMsgHeader(UBX.NAV_STATUS_HEADER) ) {
+ currentMsgType = MT_NAV_STATUS;
+ payloadSize = sizeof(UBX_t::NAV_STATUS);
+ DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_STATUS"));
+ }
+ else if ( UBXcompareMsgHeader(UBX.NAV_TIME_HEADER) ) {
+ currentMsgType = MT_NAV_TIME;
+ payloadSize = sizeof(UBX_t::NAV_TIME_UTC);
+ DEBUG_SENSOR_LOG(PSTR("UBX: got NAV_TIME_UTC"));
+ }
+ else {
+ // unknown message type, bail
+ fpos = 0;
+ continue;
+ }
+ }
+
+ if ( fpos == (payloadSize+2) ) {
+ // All payload bytes have now been received, so we can calculate the
+ // expected checksum value to compare with the next two incoming bytes.
+ UBXcalcChecksum(checksum, payloadSize);
+ }
+ else if ( fpos == (payloadSize+3) ) {
+ // First byte after the payload, ie. first byte of the checksum.
+ // Does it match the first byte of the checksum we calculated?
+ if ( c != checksum[0] ) {
+ // Checksum doesn't match, reset to beginning state and try again.
+ fpos = 0;
+ }
+ }
+ else if ( fpos == (payloadSize+4) ) {
+ // Second byte after the payload, ie. second byte of the checksum.
+ // Does it match the second byte of the checksum we calculated?
+ fpos = 0; // We will reset the state regardless of whether the checksum matches.
+ if ( c == checksum[1] ) {
+ // Checksum matches, we have a valid message.
+ return currentMsgType;
+ }
+ }
+ else if ( fpos > (payloadSize+4) ) {
+ // We have now read more bytes than both the expected payload and checksum
+ // together, so something went wrong. Reset to beginning state and try again.
+ fpos = 0;
+ }
+ }
+ }
+ // DEBUG_SENSOR_LOG(PSTR("UBX: got none or unknown Message"));
+ if (data_bytes!=0) {
+ UBX.state.non_empty_loops++;
+ DEBUG_SENSOR_LOG(PSTR("UBX: got %u bytes, non-empty-loop: %u"), data_bytes, UBX.state.non_empty_loops);
+ } else {
+ UBX.state.non_empty_loops = 0; // now a hidden GPS-device reset is unlikely
+ }
+ return MT_NONE;
+}
+
+/********************************************************************************************\
+| * callback functions for the download
+\*********************************************************************************************/
+
+#ifdef USE_FLOG
+void UBXsendHeader(void)
+{
+ WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN);
+ WebServer->sendHeader(F("Content-Disposition"), F("attachment; filename=TASMOTA.gpx"));
+ WSSend(200, CT_STREAM, F(
+ "\r\n"
+ "\r\n"
+ "\r\n\r\n"));
+}
+
+void UBXsendRecord(uint8_t *buf)
+{
+ char record[100];
+ char stime[32];
+ UBX_t::entry_t *entry = (UBX_t::entry_t*)buf;
+ snprintf_P(stime, sizeof(stime), GetDT(entry->time).c_str());
+ char lat[12];
+ char lon[12];
+ dtostrfd((double)entry->lat/10000000.0f,7,lat);
+ dtostrfd((double)entry->lon/10000000.0f,7,lon);
+ snprintf_P(record, sizeof(record),PSTR("\n\t\n\n"),lat ,lon, stime);
+ // DEBUG_SENSOR_LOG(PSTR("FLOG: DL %u %u"), Flog->sector.dword_buffer[k+j],Flog->sector.dword_buffer[k+j+1]);
+ WebServer->sendContent_P(record);
+}
+
+void UBXsendFooter(void)
+{
+ WebServer->sendContent(F("\n\n"));
+ WebServer->sendContent("");
+ Rtc.user_time_entry = false; // we have blocked the main loop and want a new valid time
+}
+
+/********************************************************************************************/
+
+void UBXsendFile(void)
+{
+ if (!HttpCheckPriviledgedAccess()) { return; }
+ Flog->startDownload(sizeof(UBX.rec_buffer),UBXsendHeader,UBXsendRecord,UBXsendFooter);
+}
+#endif //USE_FLOG
+
+/********************************************************************************************/
+
+void UBXSetRate(uint16_t interval)
+{
+ UBX.Message.cfgRate.cls = 0x06;
+ UBX.Message.cfgRate.id = 0x08;
+ UBX.Message.cfgRate.len = 6;
+ uint32_t measRate = (1000*(uint32_t)interval); //seconds to milliseconds
+ if (measRate > 0xffff) {
+ measRate = 0xffff; // max. 65535 ms interval
+ }
+ UBX.Message.cfgRate.measRate = (uint16_t)measRate;
+ UBX.Message.cfgRate.navRate = 1;
+ UBX.Message.cfgRate.timeRef = 1;
+ UBXcalcChecksum(UBX.Message.cfgRate.CK, sizeof(UBX.Message.cfgRate)-sizeof(UBX.Message.cfgRate.CK));
+ DEBUG_SENSOR_LOG(PSTR("UBX: requested interval: %u seconds measRate: %u ms"), interval, UBX.Message.cfgRate.measRate);
+ UBXSerial->write(UBX.UBX_HEADER[0]);
+ UBXSerial->write(UBX.UBX_HEADER[1]);
+ for (uint32_t i =0; iwrite(((uint8_t*)(&UBX.Message.cfgRate))[i]);
+ DEBUG_SENSOR_LOG(PSTR("UBX: cfgRate byte %u: %x"), i, ((uint8_t*)(&UBX.Message.cfgRate))[i]);
+ }
+ UBX.state.log_interval = 10*interval;
+}
+
+void UBXSelectMode(uint16_t mode)
+{
+ DEBUG_SENSOR_LOG(PSTR("UBX: set mode to %u"),mode);
+ switch(mode){
+#ifdef USE_FLOG
+ case 0:
+ Flog->mode = 0; // write once to all available sectors, then stop
+ break;
+ case 1:
+ Flog->mode = 1; // write to all available sectors, then restart and overwrite the older ones
+ break;
+ case 2:
+ UBX.mode.filter_noise = true; // filter out horizontal drift noise, TODO: find useful values
+ break;
+ case 3:
+ UBX.mode.filter_noise = false;
+ break;
+ case 4:
+ Flog->startRecording(true);
+ AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - appending"));
+ break;
+ case 5:
+ Flog->startRecording(false);
+ AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: start recording - new log"));
+ break;
+ case 6:
+ if(Flog->recording == true){
+ Flog->stopRecording();
+ }
+ AddLog_P(LOG_LEVEL_INFO, PSTR("UBX: stop recording"));
+ break;
+#endif //USE_FLOG
+ case 7:
+ UBX.mode.send_when_new = 1; // send mqtt on new postion + TELE -> consider to set TELE to a very high value
+ break;
+ case 8:
+ UBX.mode.send_when_new = 0; // only TELE
+ break;
+ case 9:
+ if (timeServer.beginListening()) {
+ UBX.mode.runningNTP = true;
+ }
+ break;
+ case 10:
+ UBX.mode.runningNTP = false;
+ break;
+ case 11:
+ UBX.mode.forceUTCupdate = true;
+ break;
+ case 12:
+ UBX.mode.forceUTCupdate = false;
+ break;
+ case 13:
+ Settings.latitude = UBX.state.last_lat;
+ Settings.longitude = UBX.state.last_lon;
+ break;
+ default:
+ if (mode>1000 && mode <1066) {
+ // UBXSetRate(mode-1000); // min. 1001 = 0.001 Hz, but will be converted to 1/65535 anyway ~0.015 Hz, max. 2000 = 1.000 Hz
+ UBXSetRate(mode-1000); // set interval between measurements in seconds from 1 to 65
+ }
+ break;
+ }
+ UBX.mode.send_UI_only = true;
+ UBXTriggerTele();
+}
+
+/********************************************************************************************/
+
+bool UBXHandlePOSLLH()
+{
+ DEBUG_SENSOR_LOG(PSTR("UBX: iTOW: %u"),UBX.Message.navPosllh.iTOW);
+ if (UBX.state.gpsFix>1) {
+ if (UBX.mode.filter_noise) {
+ if ((UBX.Message.navPosllh.lat-UBX.rec_buffer.values.lat6) { // we expect only 4-5 non-empty loops in a row, could change with other sensor speed (Hz)
+ UBXinitCFG(); // this should only happen with lots of NMEA-messages, but it is only a guess!!
+ AddLog_P(LOG_LEVEL_ERROR, PSTR("UBX: possible device-reset, will re-init"));
+ UBXSerial->flush();
+ UBX.state.non_empty_loops = 0;
+ }
+}
+
+/********************************************************************************************/
+
+void UBXTimeServer()
+{
+ if(UBX.mode.runningNTP){
+ timeServer.processOneRequest(Rtc.utc_time, UBX.state.last_iTOW%1000);
+ }
+}
+
+void UBXLoop(void)
+{
+ static uint16_t counter; //count up every 100 msec
+ static bool new_position;
+
+ uint32_t msgType = UBXprocessGPS();
+
+ switch(msgType){
+ case MT_NAV_POSLLH:
+ new_position = UBXHandlePOSLLH();
+ break;
+ case MT_NAV_STATUS:
+ UBXHandleSTATUS();
+ break;
+ case MT_NAV_TIME:
+ UBXHandleTIME();
+ break;
+ default:
+ UBXHandleOther();
+ break;
+ }
+
+#ifdef USE_FLOG
+ if (counter>UBX.state.log_interval) {
+ if (Flog->recording && new_position) {
+ UBX.rec_buffer.values.time = Rtc.local_time;
+ Flog->addToBuffer(UBX.rec_buffer.bytes, sizeof(UBX.rec_buffer.bytes));
+ counter = 0;
+ }
+ }
+#endif // USE_FLOG
+
+ counter++;
+}
+
+/********************************************************************************************/
+// normaly in i18n.h
+
+#ifdef USE_WEBSERVER
+ // {s} = , {m} = | , {e} = |
+
+#ifdef USE_FLOG
+#ifdef DEBUG_TASMOTA_SENSOR
+ const char HTTP_SNS_FLOGVER[] PROGMEM = "{s}
{m}
{e}{s} FLOG with %u sectors: {m}%u bytes{e}"
+ "{s} FLOG next sector for REC: {m} %u {e}"
+ "{s} %u sector(s) with data at sector: {m} %u {e}";
+ const char HTTP_SNS_FLOGREC[] PROGMEM = "{s} RECORDING (bytes in buffer) {m}%u{e}";
+#endif // DEBUG_TASMOTA_SENSOR
+
+ const char HTTP_SNS_FLOG[] PROGMEM = "{s}
{m}
{e}{s} Flash-Log {m} %s{e}";
+ const char kFLOG_STATE0[] PROGMEM = "ready";
+ const char kFLOG_STATE1[] PROGMEM = "recording";
+ const char * kFLOG_STATE[] ={kFLOG_STATE0, kFLOG_STATE1};
+
+ const char HTTP_BTN_FLOG_DL[] PROGMEM = "";
+
+#endif //USE_FLOG
+ const char HTTP_SNS_NTPSERVER[] PROGMEM = "{s} NTP server {m}active{e}";
+
+ const char HTTP_SNS_GPS[] PROGMEM = "{s} GPS latitude {m}%s{e}"
+ "{s} GPS longitude {m}%s{e}"
+ "{s} GPS height {m}%s m{e}"
+ "{s} GPS hor. Accuracy {m}%s m{e}"
+ "{s} GPS vert. Accuracy {m}%s m{e}"
+ "{s} GPS sat-fix status {m}%s{e}";
+
+ const char kGPSFix0[] PROGMEM = "no fix";
+ const char kGPSFix1[] PROGMEM = "dead reckoning only";
+ const char kGPSFix2[] PROGMEM = "2D-fix";
+ const char kGPSFix3[] PROGMEM = "3D-fix";
+ const char kGPSFix4[] PROGMEM = "GPS + dead reckoning combined";
+ const char kGPSFix5[] PROGMEM = "Time only fix";
+ const char * kGPSFix[] PROGMEM ={kGPSFix0, kGPSFix1, kGPSFix2, kGPSFix3, kGPSFix4, kGPSFix5};
+
+// const char UBX_GOOGLE_MAPS[] ="";
+
+
+#endif // USE_WEBSERVER
+
+/********************************************************************************************/
+
+void UBXShow(bool json)
+{
+ char lat[12];
+ char lon[12];
+ char height[12];
+ char hAcc[12];
+ char vAcc[12];
+ dtostrfd((double)UBX.rec_buffer.values.lat/10000000.0f,7,lat);
+ dtostrfd((double)UBX.rec_buffer.values.lon/10000000.0f,7,lon);
+ dtostrfd((double)UBX.state.last_height/1000.0f,3,height);
+ dtostrfd((double)UBX.state.last_vAcc/1000.0f,3,hAcc);
+ dtostrfd((double)UBX.state.last_hAcc/1000.0f,3,vAcc);
+
+ if (json) {
+ ResponseAppend_P(PSTR(",\"GPS\":{"));
+ if (UBX.mode.send_UI_only) {
+ uint32_t i = UBX.state.log_interval / 10;
+ ResponseAppend_P(PSTR("\"fil\":%u,\"int\":%u}"), UBX.mode.filter_noise, i);
+ } else {
+ ResponseAppend_P(PSTR("\"lat\":%s,\"lon\":%s,\"height\":%s,\"hAcc\":%s,\"vAcc\":%s}"), lat, lon, height, hAcc, vAcc);
+ }
+#ifdef USE_FLOG
+ ResponseAppend_P(PSTR(",\"FLOG\":{\"rec\":%u,\"mode\":%u,\"sec\":%u}"), Flog->recording, Flog->mode, Flog->sectors_left);
+#endif //USE_FLOG
+ UBX.mode.send_UI_only = false;
+#ifdef USE_WEBSERVER
+ } else {
+ WSContentSend_PD(HTTP_SNS_GPS, lat, lon, height, hAcc, vAcc, kGPSFix[UBX.state.gpsFix]);
+ //WSContentSend_P(UBX_GOOGLE_MAPS, lat, lon);
+#ifdef DEBUG_TASMOTA_SENSOR
+#ifdef USE_FLOG
+ WSContentSend_PD(HTTP_SNS_FLOGVER, Flog->num_sectors, Flog->size, Flog->current_sector, Flog->sectors_left, Flog->sector.header.physical_start_sector);
+ if (Flog->recording) {
+ WSContentSend_PD(HTTP_SNS_FLOGREC, Flog->sector.header.buf_pointer - 8);
+ }
+#endif //USE_FLOG
+#endif // DEBUG_TASMOTA_SENSOR
+#ifdef USE_FLOG
+ if (Flog->ready) {
+ WSContentSend_P(HTTP_SNS_FLOG,kFLOG_STATE[Flog->recording]);
+ }
+ if (!Flog->recording && Flog->found_saved_data) {
+ WSContentSend_P(HTTP_BTN_FLOG_DL);
+ }
+#endif //USE_FLOG
+ if (UBX.mode.runningNTP) {
+ WSContentSend_P(HTTP_SNS_NTPSERVER);
+ }
+#endif // USE_WEBSERVER
+ }
+}
+
+/*********************************************************************************************\
+ * check the UBX commands
+\*********************************************************************************************/
+
+bool UBXCmd(void)
+{
+ bool serviced = true;
+ if (XdrvMailbox.data_len > 0) {
+ UBXSelectMode(XdrvMailbox.payload);
+ Response_P(S_JSON_UBX_COMMAND_NVALUE, XdrvMailbox.command, XdrvMailbox.payload);
+ }
+ return serviced;
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xsns60(uint8_t function)
+{
+ bool result = false;
+
+ if (true) {
+ switch (function) {
+ case FUNC_INIT:
+ UBXDetect();
+ break;
+ case FUNC_COMMAND_SENSOR:
+ if (XSNS_60 == XdrvMailbox.index) {
+ result = UBXCmd();
+ }
+ break;
+ case FUNC_EVERY_50_MSECOND:
+ UBXTimeServer();
+ break;
+ case FUNC_EVERY_100_MSECOND:
+#ifdef USE_FLOG
+ if (!Flog->running_download)
+#endif //USE_FLOG
+ {
+ UBXLoop();
+ }
+ break;
+#ifdef USE_FLOG
+ case FUNC_WEB_ADD_HANDLER:
+ WebServer->on("/UBX", UBXsendFile);
+ break;
+#endif //USE_FLOG
+ case FUNC_JSON_APPEND:
+ UBXShow(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_SENSOR:
+#ifdef USE_FLOG
+ if (!Flog->running_download)
+#endif //USE_FLOG
+ {
+ UBXShow(0);
+ }
+ break;
+#endif // USE_WEBSERVER
+ }
+ }
+ return result;
+}
+
+#endif // USE_GPS
diff --git a/tools/decode-status.py b/tools/decode-status.py
index e6751ae92..492c300fa 100755
--- a/tools/decode-status.py
+++ b/tools/decode-status.py
@@ -131,7 +131,8 @@ a_setoption = [[
"GroupTopic replaces %topic% (0) or fixed topic cmnd/grouptopic (1)",
"Enable incrementing bootcount when deepsleep is enabled",
"Do not power off if slider moved to far left",
- "","",
+ "Bypass Compatibility check",
+ "",
"Enable shutter support",
"Invert PCF8574 ports"
],[
@@ -187,7 +188,7 @@ a_features = [[
"USE_SHUTTER","USE_PCF8574","USE_DDSU666","USE_DEEPSLEEP",
"USE_SONOFF_SC","USE_SONOFF_RF","USE_SONOFF_L1","USE_EXS_DIMMER",
"USE_ARDUINO_SLAVE","USE_HIH6","USE_HPMA","USE_TSL2591",
- "USE_DHT12","","","",
+ "USE_DHT12","","USE_GPS","",
"","","","",
"","","",""
]]