diff --git a/BUILDS.md b/BUILDS.md index 5cac6108f..aa1834061 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -128,6 +128,7 @@ | | | | | | | | | | USE_IR_REMOTE | - | - | x | x | x | x | x | | USE_IR_RECEIVE | - | - | x | x | x | x | x | +| USE_IR_REMOTE_FULL | - | - | - | - | - | x | - | Enable ALL protocols | | | | | | | | | | USE_SR04 | - | - | x | x | x | - | x | | USE_TM1638 | - | - | - | - | x | - | - | diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ce6c4963f..10e9a8139 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -52,11 +52,13 @@ The following binary downloads have been compiled with ESP8266/Arduino library c ## Changelog -### Version 8.1.0.4 +### Version 8.1.0.5 - Change Lights: simplified gamma correction and 10 bits internal computation - Change commands ``Prefix``, ``Ssid``, ``StateText``, ``NTPServer``, and ``FriendlyName`` displaying all items - Change IRremoteESP8266 library updated to v2.7.2 +- Change Zigbee command prefix from ``Zigbee*`` to ``Zb*`` +- Change wifi connectivity stability (#7602) - Fix Sonoff Bridge, Sc, L1, iFan03 and CSE7766 serial interface to forced speed, config and disable logging - Fix commands ``Display`` and ``Counter`` from overruling command processing (#7322) - Fix ``White`` added to light status (#7142) @@ -66,6 +68,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Fix ``WakeUp `` ignores provided value (#7473) - Fix exception 9 restart on log message in Ticker interrupt service routines NTP, Wemos and Hue emulation (#7496) - Fix ``PowerDelta`` zero power detection (#7515) +- Fix ``RGBWWTable`` ignored (#7572) - Add command ``SetOption79 0/1`` to enable reset of counters at teleperiod time by Andre Thomas (#7355) - Add command ``SetOption82 0/1`` to limit the CT range for Alexa to 200..380 - Add command ``ShutterButton `` to control shutter(s) by to-scho (#7403) @@ -80,3 +83,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support to BMP driver to enter reset state (sleep enable) when deep sleep is used in Tasmota - Add support for gzipped binaries - Add web page sliders when ``SetOption37 128`` is active allowing control of white(s) +- Add most SetOptions as defines to my_user_config.h +- Add SoftwareSerial to CSE7766 driver allowing different GPIOs (#7563) +- Add optional parameter to command ``Scheme , `` to control initial start color +- Add rule trigger on one level deeper using syntax with two ``#`` like ``on zigbeereceived#vibration_sensor#aqaracubeside=0 do ...`` diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index e1d562772..b5036467c 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -1,10 +1,22 @@ ## Unreleased (development) +### 8.1.0.5 20200126 + +- Change wifi connectivity stability (#7602) +- Add ``SetOption84 1`` sends AWS IoT device shadow updates (alternative to retained) + ### 8.1.0.4 20200116 +- Change Zigbee command prefix from ``Zigbee*`` to ``Zb*`` - Fix ``PowerDelta`` zero power detection (#7515) - Fix OTA minimal gzipped detection regression from 8.1.0.3 +- Fix ``RGBWWTable`` ignored (#7572) - Add web page sliders when ``SetOption37 128`` is active allowing control of white(s) +- Add Zigbee persistence and friendly names +- Add most SetOptions as defines to my_user_config.h +- Add SoftwareSerial to CSE7766 driver allowing different GPIOs (#7563) +- Add optional parameter to command ``Scheme , `` to control initial start color +- Add rule trigger on one level deeper using syntax with two ``#`` like ``on zigbeereceived#vibration_sensor#aqaracubeside=0 do ...`` ### 8.1.0.3 20200106 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 0dece041a..fdf07acd6 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -114,6 +114,7 @@ #define D_JSON_ACTIVE_POWERUSAGE "ActivePower" #define D_JSON_APPARENT_POWERUSAGE "ApparentPower" #define D_JSON_REACTIVE_POWERUSAGE "ReactivePower" +#define D_JSON_RANGE "Range" #define D_JSON_PRESSURE "Pressure" #define D_JSON_PRESSUREATSEALEVEL "SeaPressure" #define D_JSON_PRESSURE_UNIT "PressureUnit" @@ -466,26 +467,32 @@ #define D_JSON_TUYA_MCU_RECEIVED "TuyaReceived" // Commands xdrv_23_zigbee.ino +#define D_PRFX_ZB "Zb" +#define D_PRFX_ZIGBEE "Zigbee" #define D_ZIGBEE_NOT_STARTED "Zigbee not started (yet)" -#define D_CMND_ZIGBEE_PERMITJOIN "ZigbeePermitJoin" -#define D_CMND_ZIGBEE_STATUS "ZigbeeStatus" -#define D_CMND_ZIGBEE_RESET "ZigbeeReset" +#define D_CMND_ZIGBEE_PERMITJOIN "PermitJoin" +#define D_CMND_ZIGBEE_STATUS "Status" +#define D_CMND_ZIGBEE_RESET "Reset" #define D_JSON_ZIGBEE_CC2530 "CC2530" -#define D_CMND_ZIGBEEZNPRECEIVE "ZigbeeZNPReceive" // only for debug -#define D_CMND_ZIGBEEZNPSEND "ZigbeeZNPSend" - #define D_JSON_ZIGBEE_STATE "ZigbeeState" - #define D_JSON_ZIGBEEZNPRECEIVED "ZigbeeZNPReceived" - #define D_JSON_ZIGBEEZNPSENT "ZigbeeZNPSent" - #define D_JSON_ZIGBEEZCL_RECEIVED "ZigbeeZCLReceived" - #define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZigbeeZCLRawReceived" +#define D_CMND_ZIGBEEZNPRECEIVE "ZNPReceive" // only for debug +#define D_CMND_ZIGBEEZNPSEND "ZNPSend" + #define D_JSON_ZIGBEE_STATE "ZbState" + #define D_JSON_ZIGBEEZNPRECEIVED "ZbZNPReceived" + #define D_JSON_ZIGBEEZNPSENT "ZbZNPSent" + #define D_JSON_ZIGBEEZCL_RECEIVED "ZbZCLReceived" + #define D_JSON_ZIGBEEZCL_RAW_RECEIVED "ZbZCLRawReceived" #define D_JSON_ZIGBEE_DEVICE "Device" #define D_JSON_ZIGBEE_NAME "Name" -#define D_CMND_ZIGBEE_PROBE "ZigbeeProbe" -#define D_CMND_ZIGBEE_RECEIVED "ZigbeeReceived" +#define D_CMND_ZIGBEE_NAME "Name" +#define D_CMND_ZIGBEE_PROBE "Probe" +#define D_CMND_ZIGBEE_FORGET "Forget" +#define D_CMND_ZIGBEE_SAVE "Save" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" -#define D_CMND_ZIGBEE_READ "ZigbeeRead" -#define D_CMND_ZIGBEE_SEND "ZigbeeSend" - #define D_JSON_ZIGBEE_ZCL_SENT "ZigbeeZCLSent" +#define D_CMND_ZIGBEE_READ "Read" +#define D_CMND_ZIGBEE_SEND "Send" + #define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent" +#define D_JSON_ZIGBEE_RECEIVED "ZbReceived" +#define D_JSON_ZIGBEE_RECEIVED_LEGACY "ZigbeeReceived" // Commands xdrv_25_A4988_Stepper.ino #define D_CMND_MOTOR "MOTOR" @@ -602,6 +609,7 @@ const char JSON_SNS_TEMPHUM[] PROGMEM = ",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s, const char JSON_SNS_ILLUMINANCE[] PROGMEM = ",\"%s\":{\"" D_JSON_ILLUMINANCE "\":%d}"; const char JSON_SNS_MOISTURE[] PROGMEM = ",\"%s\":{\"" D_JSON_MOISTURE "\":%d}"; +const char JSON_SNS_RANGE[] PROGMEM = ",\"%s\":{\"" D_JSON_RANGE "\":%d}"; const char JSON_SNS_GNGPM[] PROGMEM = ",\"%s\":{\"" D_JSON_TOTAL_USAGE "\":%s,\"" D_JSON_FLOWRATE "\":%s}"; @@ -633,6 +641,7 @@ const char HTTP_SNS_CO2EAVG[] PROGMEM = "{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PE const char HTTP_SNS_GALLONS[] PROGMEM = "{s}%s " D_TOTAL_USAGE "{m}%s " D_UNIT_GALLONS " {e}"; const char HTTP_SNS_GPM[] PROGMEM = "{s}%s " D_FLOW_RATE "{m}%s " D_UNIT_GALLONS_PER_MIN" {e}"; const char HTTP_SNS_MOISTURE[] PROGMEM = "{s}%s " D_MOISTURE "{m}%d %%{e}"; +const char HTTP_SNS_RANGE[] PROGMEM = "{s}%s " D_RANGE "{m}%d{e}"; const char HTTP_SNS_VOLTAGE[] PROGMEM = "{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"; const char HTTP_SNS_CURRENT[] PROGMEM = "{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"; diff --git a/tasmota/language/bg-BG.h b/tasmota/language/bg-BG.h index 8f1c82bc3..e89903cee 100644 --- a/tasmota/language/bg-BG.h +++ b/tasmota/language/bg-BG.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Размер на програмата" #define D_PROJECT "Проект" #define D_RAIN "Дъжд" +#define D_RANGE "Range" #define D_RECEIVED "Получено" #define D_RESTART "Рестарт" #define D_RESTARTING "Рестартиране" diff --git a/tasmota/language/cs-CZ.h b/tasmota/language/cs-CZ.h index 3b11a7ebc..d7e8e188e 100644 --- a/tasmota/language/cs-CZ.h +++ b/tasmota/language/cs-CZ.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Velikost programu" #define D_PROJECT "Projekt" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "Přijatý" #define D_RESTART "Restart" #define D_RESTARTING "Restartování" diff --git a/tasmota/language/de-DE.h b/tasmota/language/de-DE.h index e09278526..b710e2975 100644 --- a/tasmota/language/de-DE.h +++ b/tasmota/language/de-DE.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Ben. Flash Speicher" #define D_PROJECT "Projekt" #define D_RAIN "Regen" +#define D_RANGE "Range" #define D_RECEIVED "erhalten" #define D_RESTART "Neustart" #define D_RESTARTING "starte neu" diff --git a/tasmota/language/el-GR.h b/tasmota/language/el-GR.h index 070267215..1c76125fa 100644 --- a/tasmota/language/el-GR.h +++ b/tasmota/language/el-GR.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Μέγεθος προγράμματος" #define D_PROJECT "Έργο" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "Ελήφθη" #define D_RESTART "Επανεκκίνηση" #define D_RESTARTING "Επανεκκινεί" diff --git a/tasmota/language/en-GB.h b/tasmota/language/en-GB.h index 5121a7471..b994b0409 100644 --- a/tasmota/language/en-GB.h +++ b/tasmota/language/en-GB.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Program Size" #define D_PROJECT "Project" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "Received" #define D_RESTART "Restart" #define D_RESTARTING "Restarting" diff --git a/tasmota/language/es-ES.h b/tasmota/language/es-ES.h index 8ff2b415f..6b8d60d01 100644 --- a/tasmota/language/es-ES.h +++ b/tasmota/language/es-ES.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Tamaño Programa" #define D_PROJECT "Proyecto" #define D_RAIN "Lluvia" +#define D_RANGE "Range" #define D_RECEIVED "Recibido" #define D_RESTART "Reiniciar" #define D_RESTARTING "Reiniciando" diff --git a/tasmota/language/fr-FR.h b/tasmota/language/fr-FR.h index 3829a8681..4a861bd82 100644 --- a/tasmota/language/fr-FR.h +++ b/tasmota/language/fr-FR.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Taille programme" #define D_PROJECT "Projet" #define D_RAIN "Pluie" +#define D_RANGE "Range" #define D_RECEIVED "Reçu" #define D_RESTART "Redémarrage" #define D_RESTARTING "Redémarre" diff --git a/tasmota/language/he-HE.h b/tasmota/language/he-HE.h index d0c5e364b..166c5c735 100644 --- a/tasmota/language/he-HE.h +++ b/tasmota/language/he-HE.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "גודל תוכנית" #define D_PROJECT "פרויקט" #define D_RAIN "גשם" +#define D_RANGE "Range" #define D_RECEIVED "התקבל" #define D_RESTART "איתחול" #define D_RESTARTING "הפעלה מחדש" diff --git a/tasmota/language/hu-HU.h b/tasmota/language/hu-HU.h index 70f219c98..e1cec813a 100644 --- a/tasmota/language/hu-HU.h +++ b/tasmota/language/hu-HU.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Program méret" #define D_PROJECT "Projekt" #define D_RAIN "Eső" +#define D_RANGE "Range" #define D_RECEIVED "Érkezett" #define D_RESTART "Újraindítás" #define D_RESTARTING "Újraindítás" diff --git a/tasmota/language/it-IT.h b/tasmota/language/it-IT.h index 73830deca..d7ec1e0e5 100644 --- a/tasmota/language/it-IT.h +++ b/tasmota/language/it-IT.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Dimensione Programma" #define D_PROJECT "Progetto" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "Ricevuto" #define D_RESTART "Riavvio" #define D_RESTARTING "Riavviando" diff --git a/tasmota/language/ko-KO.h b/tasmota/language/ko-KO.h index 328acc568..55b8dba97 100644 --- a/tasmota/language/ko-KO.h +++ b/tasmota/language/ko-KO.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "프로그램 용량" #define D_PROJECT "프로젝트" #define D_RAIN "비" +#define D_RANGE "Range" #define D_RECEIVED "받음" #define D_RESTART "재시작" #define D_RESTARTING "재시작 중.." diff --git a/tasmota/language/nl-NL.h b/tasmota/language/nl-NL.h index 3d5ec8b7d..c0b5a9862 100644 --- a/tasmota/language/nl-NL.h +++ b/tasmota/language/nl-NL.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Programma Grootte" #define D_PROJECT "Project" #define D_RAIN "Regen" +#define D_RANGE "Range" #define D_RECEIVED "Ontvangen" #define D_RESTART "Herstart" #define D_RESTARTING "Herstarten" diff --git a/tasmota/language/pl-PL.h b/tasmota/language/pl-PL.h index 63fe6fcad..237bc66f1 100644 --- a/tasmota/language/pl-PL.h +++ b/tasmota/language/pl-PL.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Rozmiar programu" #define D_PROJECT "Projekt" #define D_RAIN "Deszcz" +#define D_RANGE "Range" #define D_RECEIVED "Otrzymany" #define D_RESTART "Restart" #define D_RESTARTING "Restartowanie" diff --git a/tasmota/language/pt-BR.h b/tasmota/language/pt-BR.h index da6e8ca1b..c56262b11 100644 --- a/tasmota/language/pt-BR.h +++ b/tasmota/language/pt-BR.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Tamanho do programa" #define D_PROJECT "Projeto" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "Recebido" #define D_RESTART "Reiniciar" #define D_RESTARTING "Reiniciando" diff --git a/tasmota/language/pt-PT.h b/tasmota/language/pt-PT.h index 47137b90b..be9c6d493 100644 --- a/tasmota/language/pt-PT.h +++ b/tasmota/language/pt-PT.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Tamanho do Programa" #define D_PROJECT "Projeto" #define D_RAIN "Chuva" +#define D_RANGE "Range" #define D_RECEIVED "Recebido" #define D_RESTART "Reiniciar" #define D_RESTARTING "A reiniciar" diff --git a/tasmota/language/ru-RU.h b/tasmota/language/ru-RU.h index 31c295059..57dfa40e1 100644 --- a/tasmota/language/ru-RU.h +++ b/tasmota/language/ru-RU.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Размер программы " #define D_PROJECT "Проект" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "Получено" #define D_RESTART "Перезапуск" #define D_RESTARTING "Перезапуск" diff --git a/tasmota/language/sk-SK.h b/tasmota/language/sk-SK.h index 89f3d7353..ee7afb49f 100644 --- a/tasmota/language/sk-SK.h +++ b/tasmota/language/sk-SK.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Veľkosť programu" #define D_PROJECT "Projekt" #define D_RAIN "Dážď" +#define D_RANGE "Range" #define D_RECEIVED "Prijatý" #define D_RESTART "Reštart" #define D_RESTARTING "Reštartuje sa" diff --git a/tasmota/language/sv-SE.h b/tasmota/language/sv-SE.h index ddc2307b4..351bf6dbb 100644 --- a/tasmota/language/sv-SE.h +++ b/tasmota/language/sv-SE.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Programstorlek" #define D_PROJECT "Projekt" #define D_RAIN "Regn" +#define D_RANGE "Range" #define D_RECEIVED "Mottagen" #define D_RESTART "Omstart" #define D_RESTARTING "Startar om" diff --git a/tasmota/language/tr-TR.h b/tasmota/language/tr-TR.h index 38f125d6c..fced504d3 100644 --- a/tasmota/language/tr-TR.h +++ b/tasmota/language/tr-TR.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Yazılım Boyutu" #define D_PROJECT "Proje" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "Alınan" #define D_RESTART "Yeniden Başlat" #define D_RESTARTING "Yeniden Başlatılıyor" diff --git a/tasmota/language/uk-UA.h b/tasmota/language/uk-UA.h index b634935cf..81d02a8b3 100644 --- a/tasmota/language/uk-UA.h +++ b/tasmota/language/uk-UA.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "Розмір програми" #define D_PROJECT "Проект" #define D_RAIN "Дощ" +#define D_RANGE "Range" #define D_RECEIVED "Отримано" #define D_RESTART "Перезавантаження" #define D_RESTARTING "Перезавантаження" diff --git a/tasmota/language/zh-CN.h b/tasmota/language/zh-CN.h index 97b41a29a..12c368cd3 100644 --- a/tasmota/language/zh-CN.h +++ b/tasmota/language/zh-CN.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "固件大小" #define D_PROJECT "项目:" #define D_RAIN "降水量" +#define D_RANGE "Range" #define D_RECEIVED "已接收" #define D_RESTART "重启" #define D_RESTARTING "正在重启" diff --git a/tasmota/language/zh-TW.h b/tasmota/language/zh-TW.h index 1fd30e6e4..7c1b2e3fb 100644 --- a/tasmota/language/zh-TW.h +++ b/tasmota/language/zh-TW.h @@ -137,6 +137,7 @@ #define D_PROGRAM_SIZE "韌體大小" #define D_PROJECT "項目:" #define D_RAIN "Rain" +#define D_RANGE "Range" #define D_RECEIVED "已接收" #define D_RESTART "重啟" #define D_RESTARTING "正在重啟" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index f22023f6a..8b52aa7a7 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -53,7 +53,7 @@ //#define MODULE SONOFF_BASIC // [Module] Select default model from tasmota_template.h #define SAVE_DATA 1 // [SaveData] Save changed parameters to Flash (0 = disable, 1 - 3600 seconds) -#define SAVE_STATE 1 // [SetOption0] Save changed power state to Flash (0 = disable, 1 = enable) +#define SAVE_STATE true // [SetOption0] Save changed power state to Flash (false = disable, true = enable) #define BOOT_LOOP_OFFSET 1 // [SetOption36] Number of boot loops before starting restoring defaults (0 = disable, 1..200 = boot loops offset) // -- Wifi ---------------------------------------- @@ -69,6 +69,8 @@ #define WIFI_CONFIG_TOOL WIFI_RETRY // [WifiConfig] Default tool if wifi fails to connect (default option: 4 - WIFI_RETRY) // (WIFI_RESTART, WIFI_MANAGER, WIFI_RETRY, WIFI_WAIT, WIFI_SERIAL, WIFI_MANAGER_RESET_ONLY) // The configuration can be changed after first setup using WifiConfig 0, 2, 4, 5, 6 and 7. +#define WIFI_SCAN_AT_RESTART false // [SetOption56] Scan wifi network at restart for configured AP's +#define WIFI_SCAN_REGULARLY false // [SetOption57] Scan wifi network every 44 minutes for configured AP's // -- Syslog -------------------------------------- #define SYS_LOG_HOST "" // [LogHost] (Linux) syslog host @@ -80,9 +82,10 @@ // -- Ota ----------------------------------------- #define OTA_URL "http://thehackbox.org/tasmota/release/tasmota.bin" // [OtaUrl] +#define OTA_COMPATIBILITY false // [SetOption78] Disable OTA compatibility check // -- MQTT ---------------------------------------- -#define MQTT_USE 1 // [SetOption3] Select default MQTT use (0 = Off, 1 = On) +#define MQTT_USE true // [SetOption3] Select default MQTT use (false = Off, true = On) #define MQTT_HOST "" // [MqttHost] #define MQTT_FINGERPRINT1 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00" // [MqttFingerprint1] @@ -91,10 +94,11 @@ #define MQTT_USER "DVES_USER" // [MqttUser] MQTT user #define MQTT_PASS "DVES_PASS" // [MqttPassword] MQTT password -#define MQTT_BUTTON_RETAIN 0 // [ButtonRetain] Button may send retain flag (0 = off, 1 = on) -#define MQTT_POWER_RETAIN 0 // [PowerRetain] Power status message may send retain flag (0 = off, 1 = on) -#define MQTT_SWITCH_RETAIN 0 // [SwitchRetain] Switch may send retain flag (0 = off, 1 = on) -#define MQTT_BUTTON_SWITCH_FORCE_LOCAL 0 // [SetOption61] Force local operation when button/switch topic is set (0 = off, 1 = on) +#define MQTT_BUTTON_RETAIN false // [ButtonRetain] Button may send retain flag (false = off, true = on) +#define MQTT_POWER_RETAIN false // [PowerRetain] Power status message may send retain flag (false = off, true = on) +#define MQTT_SWITCH_RETAIN false // [SwitchRetain] Switch may send retain flag (false = off, true = on) +#define MQTT_SENSOR_RETAIN false // [SensorRetain] Sensor may send retain flag (false = off, true = on) +#define MQTT_BUTTON_SWITCH_FORCE_LOCAL false // [SetOption61] Force local operation when button/switch topic is set (false = off, true = on) #define MQTT_STATUS_OFF "OFF" // [StateText1] Command or Status result when turned off (needs to be a string like "0" or "Off") #define MQTT_STATUS_ON "ON" // [StateText2] Command or Status result when turned on (needs to be a string like "1" or "On") @@ -113,19 +117,30 @@ // %topic% token options (also ButtonTopic and SwitchTopic) #define MQTT_TOPIC PROJECT // [Topic] (unique) MQTT device topic, set to 'PROJECT "_%06X"' for unique topic including device MAC address #define MQTT_GRPTOPIC "tasmotas" // [GroupTopic] MQTT Group topic +#define MQTT_GROUPTOPIC_FORMAT false // [SetOption75] GroupTopic replaces %topic% (false) or fixed topic cmnd/grouptopic (true) #define MQTT_BUTTON_TOPIC "0" // [ButtonTopic] MQTT button topic, "0" = same as MQTT_TOPIC, set to 'PROJECT "_BTN_%06X"' for unique topic including device MAC address #define MQTT_SWITCH_TOPIC "0" // [SwitchTopic] MQTT button topic, "0" = same as MQTT_TOPIC, set to 'PROJECT "_SW_%06X"' for unique topic including device MAC address #define MQTT_CLIENT_ID "DVES_%06X" // [MqttClient] Also fall back topic using Chip Id = last 6 characters of MAC address // -- MQTT - Telemetry ---------------------------- #define TELE_PERIOD 300 // [TelePeriod] Telemetry (0 = disable, 10 - 3600 seconds) -#define TELE_ON_POWER 0 // [SetOption59] send tele/STATE together with stat/RESULT (0 = Disable, 1 = Enable) +#define TELE_ON_POWER false // [SetOption59] send tele/STATE together with stat/RESULT (false = Disable, true = Enable) // -- MQTT - Domoticz ----------------------------- #define DOMOTICZ_UPDATE_TIMER 0 // [DomoticzUpdateTimer] Send relay status (0 = disable, 1 - 3600 seconds) // -- MQTT - Home Assistant Discovery ------------- -#define HOME_ASSISTANT_DISCOVERY_ENABLE 0 // [SetOption19] Home Assistant Discovery (0 = Disable, 1 = Enable) +#define HOME_ASSISTANT_DISCOVERY_ENABLE false // [SetOption19] Home Assistant Discovery (false = Disable, true = Enable) +#define HASS_AS_LIGHT false // [SetOption30] Enforce HAss autodiscovery as light + +// -- MQTT - Options ------------------------------ +#define MQTT_RESULT_COMMAND false // [SetOption4] Switch between MQTT RESULT or COMMAND +#define MQTT_LWT_MESSAGE false // [SetOption10] Switch between MQTT LWT OFFLINE or empty message +#define MQTT_POWER_FORMAT false // [SetOption26] Switch between POWER or POWER1 for single power devices +#define MQTT_APPEND_TIMEZONE false // [SetOption52] Append timezone to JSON time +#define MQTT_NO_HOLD_RETAIN false // [SetOption62] Disable retain flag on HOLD messages +#define MQTT_INDEX_SEPARATOR false // [SetOption64] Enable "_" instead of "-" as sensor index separator +#define MQTT_TUYA_RECEIVED false // [SetOption66] Enable TuyaMcuReceived messages over Mqtt // -- HTTP ---------------------------------------- #define WEB_SERVER 2 // [WebServer] Web server (0 = Off, 1 = Start as User, 2 = Start as Admin) @@ -134,6 +149,9 @@ #define EMULATION EMUL_NONE // [Emulation] Select Belkin WeMo (single relay/light) or Hue Bridge emulation (multi relay/light) (EMUL_NONE, EMUL_WEMO or EMUL_HUE) #define CORS_DOMAIN "" // [Cors] CORS Domain for preflight requests +// -- HTTP Options -------------------------------- +#define GUI_SHOW_HOSTNAME false // [SetOption53] Show hostname and IP address in GUI main menu + // -- HTTP GUI Colors ----------------------------- // HTML hex color codes. Only 3 and 6 digit hex string values are supported!! See https://www.w3schools.com/colors/colors_hex.asp // Light theme - pre v7 @@ -181,8 +199,12 @@ #define COLOR_TIMER_TAB_BACKGROUND "#999" // [WebColor18] Config timer tab background color - Dark gray #define COLOR_TITLE_TEXT "#eaeaea" // [WebColor19] Title text color - Very light gray +// -- KNX ----------------------------------------- +#define KNX_ENABLED false // [Knx_Enabled] Enable KNX protocol +#define KNX_ENHANCED false // [Knx_Enhanced] Enable KNX Enhanced Mode + // -- mDNS ---------------------------------------- -#define MDNS_ENABLED 0 // [SetOption55] Use mDNS (0 = Disable, 1 = Enable) +#define MDNS_ENABLED false // [SetOption55] Use mDNS (false = Disable, true = Enable) // -- Time - Up to three NTP servers in your region #define NTP_SERVER1 "pool.ntp.org" // [NtpServer1] Select first NTP server by name or IP address (129.250.35.250) @@ -214,28 +236,69 @@ #define APP_LEDSTATE LED_POWER // [LedState] Function of led // (LED_OFF, LED_POWER, LED_MQTTSUB, LED_POWER_MQTTSUB, LED_MQTTPUB, LED_POWER_MQTTPUB, LED_MQTT, LED_POWER_MQTT) #define APP_LEDMASK 0xFFFF // [LedMask] Assign Relay to Power led (0xFFFF is default) +#define APP_ENABLE_LEDLINK false // [SetOption31] Enable link led blinking + #define APP_PULSETIME 0 // [PulseTime] Time in 0.1 Sec to turn off power for relay 1 (0 = disabled) #define APP_POWERON_STATE POWER_ALL_SAVED // [PowerOnState] Power On Relay state // (POWER_ALL_OFF, POWER_ALL_ON, POWER_ALL_SAVED_TOGGLE, POWER_ALL_SAVED, POWER_ALL_ALWAYS_ON, POWER_ALL_OFF_PULSETIME_ON) #define APP_BLINKTIME 10 // [BlinkTime] Time in 0.1 Sec to blink/toggle power for relay 1 #define APP_BLINKCOUNT 10 // [BlinkCount] Number of blinks (0 = 32000) + +#define APP_NORMAL_SLEEP false // [SetOption60] Enable normal sleep instead of dynamic sleep #define APP_SLEEP 0 // [Sleep] Sleep time to lower energy consumption (0 = Off, 1 - 250 mSec), #define PWM_MAX_SLEEP 10 // Sleep will be lowered to this value when light is on, to avoid flickering #define KEY_DEBOUNCE_TIME 50 // [ButtonDebounce] Number of mSeconds button press debounce time #define KEY_HOLD_TIME 40 // [SetOption32] Number of 0.1 seconds to hold Button or external Pushbutton before sending HOLD message +#define KEY_DISABLE_MULTIPRESS false // [SetOption1] Disable button multipress +#define KEY_SWAP_DOUBLE_PRESS false // [SetOption11] Swap button single and double press functionality +#define KEY_ONLY_SINGLE_PRESS false // [SetOption13] Enable only single press to speed up button press recognition + #define SWITCH_DEBOUNCE_TIME 50 // [SwitchDebounce] Number of mSeconds switch press debounce time #define SWITCH_MODE TOGGLE // [SwitchMode] TOGGLE, FOLLOW, FOLLOW_INV, PUSHBUTTON, PUSHBUTTON_INV, PUSHBUTTONHOLD, PUSHBUTTONHOLD_INV, PUSHBUTTON_TOGGLE, TOGGLEMULTI, FOLLOWMULTI, FOLLOWMULTI_INV (the wall switch state) -#define WS2812_LEDS 30 // [Pixels] Number of WS2812 LEDs to start with (max is 512) -#define TEMP_CONVERSION 0 // [SetOption8] Return temperature in (0 = Celsius or 1 = Fahrenheit) -#define PRESSURE_CONVERSION 0 // [SetOption24] Return pressure in (0 = hPa or 1 = mmHg) +#define TEMP_CONVERSION false // [SetOption8] Return temperature in (false = Celsius or true = Fahrenheit) +#define PRESSURE_CONVERSION false // [SetOption24] Return pressure in (false = hPa or true = mmHg) #define TEMP_RESOLUTION 1 // [TempRes] Maximum number of decimals (0 - 3) showing sensor Temperature #define HUMIDITY_RESOLUTION 1 // [HumRes] Maximum number of decimals (0 - 3) showing sensor Humidity #define PRESSURE_RESOLUTION 1 // [PressRes] Maximum number of decimals (0 - 3) showing sensor Pressure #define ENERGY_RESOLUTION 3 // [EnergyRes] Maximum number of decimals (0 - 5) showing energy usage in kWh #define CALC_RESOLUTION 3 // [CalcRes] Maximum number of decimals (0 - 7) used in commands ADD, SUB, MULT and SCALE +#define APP_FLASH_CYCLE false // [SetOption12] Switch between dynamic or fixed slot flash save location +#define APP_NO_RELAY_SCAN false // [SetOption63] Don't scan relay power state at restart +#define APP_DISABLE_POWERCYCLE false // [SetOption65] Disable fast power cycle detection for device reset +#define DEEPSLEEP_BOOTCOUNT false // [SetOption76] Enable incrementing bootcount when deepsleep is enabled + +// -- Lights -------------------------------------- +#define WS2812_LEDS 30 // [Pixels] Number of WS2812 LEDs to start with (max is 512) +#define LIGHT_MODE true // [SetOption15] Switch between commands PWM or COLOR/DIMMER/CT/CHANNEL +#define LIGHT_CLOCK_DIRECTION false // [SetOption16] Switch WS2812 clock between clockwise or counter-clockwise +#define LIGHT_COLOR_RADIX false // [SetOption17] Switch between decimal or hexadecimal color output (false = hexadecimal, true = decimal) +#define LIGHT_PAIRS_CO2 false // [SetOption18] Enable Pair light signal with CO2 sensor +#define LIGHT_POWER_CONTROL false // [SetOption20] Enable power control in relation to Dimmer/Color/Ct changes +#define LIGHT_CHANNEL_MODE false // [SetOption68] Enable multi-channels PWM instead of Color PWM +#define LIGHT_SLIDER_POWER false // [SetOption77] Do not power off if slider moved to far left +#define LIGHT_ALEXA_CT_RANGE false // [SetOption82] Reduced CT range for Alexa + +// -- Energy -------------------------------------- +#define ENERGY_VOLTAGE_ALWAYS false // [SetOption21] Enable show voltage even if powered off +#define ENERGY_DDS2382_MODE false // [SetOption71] Enable DDS2382 different Modbus registers for Active Energy (#6531) +#define ENERGY_HARDWARE_TOTALS false // [SetOption72] Enable hardware energy total counter as reference (#6561) + +// -- Other Options ------------------------------- +#define TIMERS_ENABLED false // [Timers] Enable Timers +#define RF_DATA_RADIX false // [SetOption28] RF receive data format (false = hexadecimal, true = decimal) +#define IR_DATA_RADIX false // [SetOption29] IR receive data format (false = hexadecimal, true = decimal) +#define TUYA_SETOPTION_20 false // [SetOption54] Apply SetOption20 settings to Tuya device +#define IR_ADD_RAW_DATA false // [SetOption58] Add IR Raw data to JSON message +#define BUZZER_ENABLE false // [SetOption67] Enable buzzer when available +#define DS18X20_PULL_UP false // [SetOption74] Enable internal pullup for single DS18x20 sensor +#define COUNTER_RESET false // [SetOption79] Enable resetting of counters after telemetry was sent +#define SHUTTER_SUPPORT false // [SetOption80] Enable shutter support +#define PCF8574_INVERT_PORTS false // [SetOption81] Invert all ports on PCF8574 devices +#define ZIGBEE_FRIENDLY_NAMES false // [SetOption83] Enable Zigbee FriendlyNames instead of ShortAddresses when possible + /*********************************************************************************************\ * END OF SECTION 1 * @@ -531,7 +594,7 @@ #define IR_RCV_MIN_UNKNOWN_SIZE 6 // Set the smallest sized "UNKNOWN" message packets we actually care about (default 6, max 255) // -- Zigbee interface ---------------------------- -//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP +//#define USE_ZIGBEE // Enable serial communication with Zigbee CC2530 flashed with ZNP (+35k code, +3.2k mem) #define USE_ZIGBEE_PANID 0x1A63 // arbitrary PAN ID for Zigbee network, must be unique in the home #define USE_ZIGBEE_EXTPANID 0xCCCCCCCCCCCCCCCCL // arbitrary extended PAN ID #define USE_ZIGBEE_CHANNEL 11 // Zigbee Channel (11-26) diff --git a/tasmota/settings.h b/tasmota/settings.h index 684ab18af..cef401a94 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -101,9 +101,9 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu typedef union { // Restricted by MISRA-C Rule 18.4 but so useful... uint32_t data; // Allow bit manipulation using SetOption struct { // SetOption82 .. SetOption113 - uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa - uint32_t spare01 : 1; - uint32_t spare02 : 1; + uint32_t alexa_ct_range : 1; // bit 0 (v8.1.0.2) - SetOption82 - Reduced CT range for Alexa + uint32_t zigbee_use_names : 1; // bit 1 (v8.1.0.4) - SetOption83 - Use FriendlyNames instead of ShortAddresses when possible + uint32_t awsiot_shadow : 1; // bit 2 (v8.1.0.5) - SetOption84 - (AWS IoT) publish MQTT state to a device shadow uint32_t spare03 : 1; uint32_t spare04 : 1; uint32_t spare05 : 1; @@ -467,8 +467,9 @@ struct SYSCFG { uint8_t hotplug_scan; // F03 uint8_t reserved1; // F04 - reserved for s-hadinger - uint8_t free_f05[215]; // F05 + uint8_t free_f05[211]; // F05 + int adc_param4; // FD8 uint32_t shutter_button[MAX_KEYS]; // FDC uint32_t i2c_drivers[3]; // FEC I2cDriver uint32_t cfg_timestamp; // FF8 diff --git a/tasmota/settings.ino b/tasmota/settings.ino index a059b71e6..33da93562 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -700,7 +700,11 @@ void EspErase(uint32_t start_sector, uint32_t end_sector) // bool result = EsptoolEraseSector(sector); // Esptool - erases flash completely (slow) if (serial_output) { +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + Serial.printf(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n", sector, (result) ? D_OK : D_ERROR); +#else Serial.printf_P(PSTR(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n"), sector, (result) ? D_OK : D_ERROR); +#endif delay(10); } else { yield(); @@ -782,8 +786,13 @@ void SettingsDefaultSet2(void) { memset((char*)&Settings +16, 0x00, sizeof(SYSCFG) -16); -// Settings.flag.value_units = 0; -// Settings.flag.stop_flash_rotate = 0; + Settings.flag.stop_flash_rotate = APP_FLASH_CYCLE; + Settings.flag.global_state = APP_ENABLE_LEDLINK; + Settings.flag3.sleep_normal = APP_NORMAL_SLEEP; + Settings.flag3.no_power_feedback = APP_NO_RELAY_SCAN; + Settings.flag3.fast_power_cycle_disable = APP_DISABLE_POWERCYCLE; + Settings.flag3.bootcount_update = DEEPSLEEP_BOOTCOUNT; + Settings.flag3.compatibility_check = OTA_COMPATIBILITY; Settings.save_data = SAVE_DATA; Settings.param[P_BACKLOG_DELAY] = MIN_BACKLOG_DELAY; Settings.param[P_BOOT_LOOP_OFFSET] = BOOT_LOOP_OFFSET; // SetOption36 @@ -824,6 +833,8 @@ void SettingsDefaultSet2(void) Settings.seriallog_level = SERIAL_LOG_LEVEL; // Wifi + Settings.flag3.use_wifi_scan = WIFI_SCAN_AT_RESTART; + Settings.flag3.use_wifi_rescan = WIFI_SCAN_REGULARLY; Settings.wifi_output_power = 170; ParseIp(&Settings.ip_address[0], WIFI_IP_ADDRESS); ParseIp(&Settings.ip_address[1], WIFI_GATEWAY); @@ -844,16 +855,17 @@ void SettingsDefaultSet2(void) // Webserver Settings.flag2.emulation = EMULATION; + Settings.flag3.gui_hostname_ip = GUI_SHOW_HOSTNAME; + Settings.flag3.mdns_enabled = MDNS_ENABLED; Settings.webserver = WEB_SERVER; Settings.weblog_level = WEB_LOG_LEVEL; SettingsUpdateText(SET_WEBPWD, WEB_PASSWORD); - Settings.flag3.mdns_enabled = MDNS_ENABLED; SettingsUpdateText(SET_CORS, CORS_DOMAIN); // Button -// Settings.flag.button_restrict = 0; -// Settings.flag.button_swap = 0; -// Settings.flag.button_single = 0; + Settings.flag.button_restrict = KEY_DISABLE_MULTIPRESS; + Settings.flag.button_swap = KEY_SWAP_DOUBLE_PRESS; + Settings.flag.button_single = KEY_ONLY_SINGLE_PRESS; Settings.param[P_HOLD_TIME] = KEY_HOLD_TIME; // Default 4 seconds hold time // Switch @@ -861,16 +873,19 @@ void SettingsDefaultSet2(void) // MQTT Settings.flag.mqtt_enabled = MQTT_USE; -// Settings.flag.mqtt_response = 0; + Settings.flag.mqtt_response = MQTT_RESULT_COMMAND; + Settings.flag.mqtt_offline = MQTT_LWT_MESSAGE; Settings.flag.mqtt_power_retain = MQTT_POWER_RETAIN; Settings.flag.mqtt_button_retain = MQTT_BUTTON_RETAIN; Settings.flag.mqtt_switch_retain = MQTT_SWITCH_RETAIN; - Settings.flag3.button_switch_force_local = MQTT_BUTTON_SWITCH_FORCE_LOCAL; - Settings.flag3.hass_tele_on_power = TELE_ON_POWER; -// Settings.flag.mqtt_sensor_retain = 0; -// Settings.flag.mqtt_offline = 0; + Settings.flag.mqtt_sensor_retain = MQTT_SENSOR_RETAIN; // Settings.flag.mqtt_serial = 0; -// Settings.flag.device_index_enable = 0; + Settings.flag.device_index_enable = MQTT_POWER_FORMAT; + Settings.flag3.time_append_timezone = MQTT_APPEND_TIMEZONE; + Settings.flag3.button_switch_force_local = MQTT_BUTTON_SWITCH_FORCE_LOCAL; + Settings.flag3.no_hold_retain = MQTT_NO_HOLD_RETAIN; + Settings.flag3.use_underscore = MQTT_INDEX_SEPARATOR; + Settings.flag3.grouptopic_mode = MQTT_GROUPTOPIC_FORMAT; SettingsUpdateText(SET_MQTT_HOST, MQTT_HOST); Settings.mqtt_port = MQTT_PORT; SettingsUpdateText(SET_MQTT_CLIENT, MQTT_CLIENT_ID); @@ -904,10 +919,13 @@ void SettingsDefaultSet2(void) Settings.mqttlog_level = MQTT_LOG_LEVEL; // Energy + Settings.flag.no_power_on_check = ENERGY_VOLTAGE_ALWAYS; Settings.flag2.current_resolution = 3; // Settings.flag2.voltage_resolution = 0; // Settings.flag2.wattage_resolution = 0; Settings.flag2.energy_resolution = ENERGY_RESOLUTION; + Settings.flag3.dds2382_model = ENERGY_DDS2382_MODE; + Settings.flag3.hardware_energy_total = ENERGY_HARDWARE_TOTALS; Settings.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY; // Settings.energy_power_delta = 0; Settings.energy_power_calibration = HLW_PREF_PULSE; @@ -937,9 +955,12 @@ void SettingsDefaultSet2(void) Settings.param[P_OVER_TEMP] = ENERGY_OVERTEMP; // IRRemote + Settings.flag.ir_receive_decimal = IR_DATA_RADIX; + Settings.flag3.receive_raw = IR_ADD_RAW_DATA; Settings.param[P_IR_UNKNOW_THRESHOLD] = IR_RCV_MIN_UNKNOWN_SIZE; // RF Bridge + Settings.flag.rf_receive_decimal = RF_DATA_RADIX; // for (uint32_t i = 0; i < 17; i++) { Settings.rf_code[i][0] = 0; } memcpy_P(Settings.rf_code[0], kDefaultRfCode, 9); @@ -960,6 +981,8 @@ void SettingsDefaultSet2(void) Settings.flag2.pressure_resolution = PRESSURE_RESOLUTION; Settings.flag2.humidity_resolution = HUMIDITY_RESOLUTION; Settings.flag2.temperature_resolution = TEMP_RESOLUTION; + Settings.flag3.ds18x20_internal_pullup = DS18X20_PULL_UP; + Settings.flag3.counter_reset_on_tele = COUNTER_RESET; // Settings.altitude = 0; // Rules @@ -968,19 +991,28 @@ void SettingsDefaultSet2(void) // for (uint32_t i = 1; i < MAX_RULE_SETS; i++) { Settings.rules[i][0] = '\0'; } Settings.flag2.calc_resolution = CALC_RESOLUTION; + // Timer + Settings.flag3.timers_enable = TIMERS_ENABLED; + // Home Assistant + Settings.flag.hass_light = HASS_AS_LIGHT; Settings.flag.hass_discovery = HOME_ASSISTANT_DISCOVERY_ENABLE; + Settings.flag3.hass_tele_on_power = TELE_ON_POWER; // Knx -// Settings.flag.knx_enabled = 0; -// Settings.flag.knx_enable_enhancement = 0; + Settings.flag.knx_enabled = KNX_ENABLED; + Settings.flag.knx_enable_enhancement = KNX_ENHANCED; // Light - Settings.flag.pwm_control = 1; - //Settings.flag.ws_clock_reverse = 0; - //Settings.flag.light_signal = 0; - //Settings.flag.not_power_linked = 0; - //Settings.flag.decimal_text = 0; + Settings.flag.pwm_control = LIGHT_MODE; + Settings.flag.ws_clock_reverse = LIGHT_CLOCK_DIRECTION; + Settings.flag.light_signal = LIGHT_PAIRS_CO2; + Settings.flag.not_power_linked = LIGHT_POWER_CONTROL; + Settings.flag.decimal_text = LIGHT_COLOR_RADIX; + Settings.flag3.pwm_multi_channels = LIGHT_CHANNEL_MODE; + Settings.flag3.slider_dimmer_stay_on = LIGHT_SLIDER_POWER; + Settings.flag4.alexa_ct_range = LIGHT_ALEXA_CT_RANGE; + Settings.pwm_frequency = PWM_FREQ; Settings.pwm_range = PWM_RANGE; for (uint32_t i = 0; i < MAX_PWMS; i++) { @@ -1064,6 +1096,15 @@ void SettingsDefaultSet2(void) memset(&Settings.monitors, 0xFF, 20); // Enable all possible monitors, displays and sensors SettingsEnableAllI2cDrivers(); + + // Tuya + Settings.flag3.tuya_apply_o20 = TUYA_SETOPTION_20; + Settings.flag3.tuya_serial_mqtt_publish = MQTT_TUYA_RECEIVED; + + Settings.flag3.buzzer_enable = BUZZER_ENABLE; + Settings.flag3.shutter_mode = SHUTTER_SUPPORT; + Settings.flag3.pcf8574_ports_inverted = PCF8574_INVERT_PORTS; + Settings.flag4.zigbee_use_names = ZIGBEE_FRIENDLY_NAMES; } /********************************************************************************************/ diff --git a/tasmota/support.ino b/tasmota/support.ino index a9a631fd1..0a281bc68 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -493,6 +493,17 @@ bool ParseIp(uint32_t* addr, const char* str) return (3 == i); } +uint32_t ParseParameters(uint32_t count, uint32_t *params) +{ + char *p; + uint32_t i = 0; + for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < count; str = strtok_r(nullptr, ", ", &p)) { + params[i] = strtoul(str, nullptr, 0); + i++; + } + return i; +} + // Function to parse & check if version_str is newer than our currently installed version. bool NewerVersion(char* version_str) { @@ -1521,11 +1532,7 @@ bool I2cSetDevice(uint32_t addr) return false; // If already active report as not present; } Wire.beginTransmission((uint8_t)addr); - bool result = (0 == Wire.endTransmission()); - if (result) { - I2cSetActive(addr, 1); - } - return result; + return (0 == Wire.endTransmission()); } #endif // USE_I2C @@ -1631,8 +1638,12 @@ void AddLog(uint32_t loglevel) if (!web_log_index) web_log_index++; // Index 0 is not allowed as it is the end of char string } #endif // USE_WEBSERVER - if (!global_state.mqtt_down && (loglevel <= Settings.mqttlog_level)) { MqttPublishLogging(mxtime); } - if (!global_state.wifi_down && (loglevel <= syslog_level)) { Syslog(); } + if (Settings.flag.mqtt_enabled && // SetOption3 - Enable MQTT + !global_state.mqtt_down && + (loglevel <= Settings.mqttlog_level)) { MqttPublishLogging(mxtime); } + + if (!global_state.wifi_down && + (loglevel <= syslog_level)) { Syslog(); } } void AddLog_P(uint32_t loglevel, const char *formatP) diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 6f70e7359..24ef2051c 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -338,7 +338,8 @@ void CmndStatus(void) char stemp2[TOPSZ]; // Workaround MQTT - TCP/IP stack queueing when SUB_PREFIX = PUB_PREFIX - if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2)) && (!payload)) { option++; } // TELE + // Commented on 20200118 as it seems to be no longer needed +// if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2)) && (!payload)) { option++; } // TELE if ((!Settings.flag.mqtt_enabled) && (6 == payload)) { payload = 99; } // SetOption3 - Enable MQTT if (!energy_flg && (9 == payload)) { payload = 99; } diff --git a/tasmota/support_static_buffer.ino b/tasmota/support_static_buffer.ino index 512ec0db1..bec831cc9 100644 --- a/tasmota/support_static_buffer.ino +++ b/tasmota/support_static_buffer.ino @@ -80,7 +80,7 @@ public: return _buf->len; } size_t add32(const uint32_t data) { // append 32 bits value - if (_buf->len < _buf->size - 3) { // do we have room for 2 bytes + if (_buf->len < _buf->size - 3) { // do we have room for 4 bytes _buf->buf[_buf->len++] = data; _buf->buf[_buf->len++] = data >> 8; _buf->buf[_buf->len++] = data >> 16; @@ -88,6 +88,19 @@ public: } return _buf->len; } + size_t add64(const uint64_t data) { // append 64 bits value + if (_buf->len < _buf->size - 7) { // do we have room for 8 bytes + _buf->buf[_buf->len++] = data; + _buf->buf[_buf->len++] = data >> 8; + _buf->buf[_buf->len++] = data >> 16; + _buf->buf[_buf->len++] = data >> 24; + _buf->buf[_buf->len++] = data >> 32; + _buf->buf[_buf->len++] = data >> 40; + _buf->buf[_buf->len++] = data >> 48; + _buf->buf[_buf->len++] = data >> 56; + } + return _buf->len; + } size_t addBuffer(const SBuffer &buf2) { if (len() + buf2.len() <= size()) { @@ -152,6 +165,20 @@ public: return 0; } + // if no NULL is found, returns length until the end of the buffer + inline size_t strlen(const size_t offset) const { + return strnlen((const char*) &_buf->buf[offset], len() - offset); + } + + size_t strlen_s(const size_t offset) const { + size_t slen = this->strlen(offset); + if (slen == len() - offset) { + return 0; // we didn't find a NULL char + } else { + return slen; + } + } + SBuffer subBuffer(const size_t start, size_t len) const { if (start >= _buf->len) { len = 0; diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 1cb3aae8b..9095a09bf 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -438,9 +438,9 @@ bool SendKey(uint32_t key, uint32_t device, uint32_t state) #ifdef USE_DOMOTICZ if (!(DomoticzSendKey(key, device, state, strlen(mqtt_data)))) { #endif // USE_DOMOTICZ - MqttPublishDirect(stopic, ((key) ? Settings.flag.mqtt_switch_retain // CMND_SWITCHRETAIN - : Settings.flag.mqtt_button_retain) && // CMND_BUTTONRETAIN - (state != POWER_HOLD || !Settings.flag3.no_hold_retain)); // SetOption62 - Don't use retain flag on HOLD messages + MqttPublish(stopic, ((key) ? Settings.flag.mqtt_switch_retain // CMND_SWITCHRETAIN + : Settings.flag.mqtt_button_retain) && // CMND_BUTTONRETAIN + (state != POWER_HOLD || !Settings.flag3.no_hold_retain)); // SetOption62 - Don't use retain flag on HOLD messages #ifdef USE_DOMOTICZ } #endif // USE_DOMOTICZ @@ -692,7 +692,12 @@ void MqttPublishSensor(void) } } -/********************************************************************************************/ +/*********************************************************************************************\ + * State loops +\*********************************************************************************************/ +/*-------------------------------------------------------------------------------------------*\ + * Every second +\*-------------------------------------------------------------------------------------------*/ void PerformEverySecond(void) { @@ -715,6 +720,13 @@ void PerformEverySecond(void) #endif } + if (mqtt_cmnd_blocked_reset) { + mqtt_cmnd_blocked_reset--; + if (!mqtt_cmnd_blocked_reset) { + mqtt_cmnd_blocked = 0; // Clean up MQTT cmnd loop block + } + } + if (seriallog_timer) { seriallog_timer--; if (!seriallog_timer) { @@ -763,9 +775,6 @@ void PerformEverySecond(void) } } -/*********************************************************************************************\ - * State loops -\*********************************************************************************************/ /*-------------------------------------------------------------------------------------------*\ * Every 0.1 second \*-------------------------------------------------------------------------------------------*/ @@ -822,8 +831,6 @@ void Every250mSeconds(void) state_250mS++; state_250mS &= 0x3; - if (mqtt_cmnd_publish) mqtt_cmnd_publish--; // Clean up - if (!Settings.flag.global_state) { // Problem blinkyblinky enabled - SetOption31 - Control link led blinking if (global_state.data) { // Any problem if (global_state.mqtt_down) { blinkinterval = 7; } // MQTT problem so blink every 2 seconds (slowest) diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index 954e2fb3c..2a3951147 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -22,14 +22,17 @@ \*********************************************************************************************/ #ifndef WIFI_RSSI_THRESHOLD -#define WIFI_RSSI_THRESHOLD 10 // Difference in dB between current network and scanned network +// Decrease the roam threshold from 10 to 5 to address devices connecting at very low RSSI and being close to inoperative +#define WIFI_RSSI_THRESHOLD 5 // Difference in dB between current network and scanned network #endif #ifndef WIFI_RESCAN_MINUTES -#define WIFI_RESCAN_MINUTES 44 // Number of minutes between wifi network rescan +// Increase rescan interval from 44 to 5 minutes to improve ability for devices to reach network harmony +#define WIFI_RESCAN_MINUTES 5 // Number of minutes between wifi network rescan #endif const uint8_t WIFI_CONFIG_SEC = 180; // seconds before restart -const uint8_t WIFI_CHECK_SEC = 20; // seconds +// Drop from 20 seconds to 5 seconds since we control the reconnections, not the Arduino SDK +const uint8_t WIFI_CHECK_SEC = 5; // seconds const uint8_t WIFI_RETRY_OFFSET_SEC = 20; // seconds #include // Wifi, MQTT, Ota, WifiManager @@ -49,7 +52,9 @@ struct WIFI { uint8_t config_counter = 0; uint8_t mdns_begun = 0; // mDNS active uint8_t scan_state; - uint8_t bssid[6]; + uint8_t bssid[6] = {0}; + uint8_t bssid_last[6] = {0}; // store the last connect bssid + int8_t best_network_db; } Wifi; int WifiGetRssiAsQuality(int rssi) @@ -182,7 +187,9 @@ void WifiBegin(uint8_t flag, uint8_t channel) // if (WiFi.getPhyMode() != WIFI_PHY_MODE_11N) { WiFi.setPhyMode(WIFI_PHY_MODE_11N); } // B/G/N // if (WiFi.getPhyMode() != WIFI_PHY_MODE_11G) { WiFi.setPhyMode(WIFI_PHY_MODE_11G); } // B/G if (!WiFi.getAutoConnect()) { WiFi.setAutoConnect(true); } -// WiFi.setAutoReconnect(true); + // Handle the reconnection in WifiCheckIp() since the autoreconnect keeps sending deauthentication messages which causes the AP to block traffic as it looks like an DoS attack + // This needs to be explicitly called as "false" otherwise the default is enabled + WiFi.setAutoReconnect(false); switch (flag) { case 0: // AP1 case 1: // AP2 @@ -198,13 +205,18 @@ void WifiBegin(uint8_t flag, uint8_t channel) WiFi.config(Settings.ip_address[0], Settings.ip_address[1], Settings.ip_address[2], Settings.ip_address[3]); // Set static IP } WiFi.hostname(my_hostname); + + char stemp[40] = { 0 }; if (channel) { WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active), channel, Wifi.bssid); + // Add connected BSSID and channel for multi-AP installations + char hex_char[18]; + snprintf_P(stemp, sizeof(stemp), PSTR(" Channel %d BSSId %s"), channel, ToHex_P((unsigned char*)Wifi.bssid, 6, hex_char, sizeof(hex_char), ':')); } else { WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active)); } - AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CONNECTING_TO_AP "%d %s " D_IN_MODE " 11%c " D_AS " %s..."), - Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CONNECTING_TO_AP "%d %s%s " D_IN_MODE " 11%c " D_AS " %s..."), + Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), stemp, kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname); #if LWIP_IPV6 for (bool configured = false; !configured;) { @@ -223,22 +235,22 @@ void WifiBegin(uint8_t flag, uint8_t channel) void WifiBeginAfterScan(void) { - static int8_t best_network_db; - // Not active if (0 == Wifi.scan_state) { return; } // Init scan when not connected if (1 == Wifi.scan_state) { memset((void*) &Wifi.bssid, 0, sizeof(Wifi.bssid)); - best_network_db = -127; + Wifi.best_network_db = -127; Wifi.scan_state = 3; } // Init scan when connected if (2 == Wifi.scan_state) { uint8_t* bssid = WiFi.BSSID(); // Get current bssid memcpy((void*) &Wifi.bssid, (void*) bssid, sizeof(Wifi.bssid)); - best_network_db = WiFi.RSSI(); // Get current rssi and add threshold - if (best_network_db < -WIFI_RSSI_THRESHOLD) { best_network_db += WIFI_RSSI_THRESHOLD; } + Wifi.best_network_db = WiFi.RSSI(); // Get current rssi and add threshold + if (Wifi.best_network_db < -WIFI_RSSI_THRESHOLD) { + Wifi.best_network_db += WIFI_RSSI_THRESHOLD; + } Wifi.scan_state = 3; } // Init scan @@ -259,6 +271,12 @@ void WifiBeginAfterScan(void) } // Scan done if (5 == Wifi.scan_state) { + uint32_t number_known = 0; // count the number of known AP's so we can clear the Wifi.bssid_last if there is only one + int32_t channel_max = 0; // No scan result + int8_t ap_max = 3; // AP default if not found + uint8_t bssid_max[6]; // Save last bssid + memcpy((void*) &bssid_max, (void*) &Wifi.bssid, sizeof(bssid_max)); // store the strongest bssid + int32_t channel = 0; // No scan result int8_t ap = 3; // AP default if not found uint8_t last_bssid[6]; // Save last bssid @@ -282,12 +300,25 @@ void WifiBeginAfterScan(void) for (j = 0; j < MAX_SSIDS; j++) { if (ssid_scan == SettingsText(SET_STASSID1 + j)) { // SSID match known = true; - if (rssi_scan > best_network_db) { // Best network + number_known++; + if (rssi_scan > Wifi.best_network_db) { // Best network if (sec_scan == ENC_TYPE_NONE || SettingsText(SET_STAPWD1 + j)) { // Check for passphrase if not open wlan - best_network_db = (int8_t)rssi_scan; - channel = chan_scan; - ap = j; // AP1 or AP2 - memcpy((void*) &Wifi.bssid, (void*) bssid_scan, sizeof(Wifi.bssid)); + // store the max values in case there is only one AP and we need to try to reconnect + memcpy((void*) &bssid_max, (void*) bssid_scan, sizeof(bssid_max)); + channel_max = chan_scan; + ap_max = j; + // if the bssid is not the same as the last failed attempt, force picking the next strongest AP to prevent getting stuck on a strong RSSI, but poor channel health + for (uint32_t i = 0; i < sizeof(Wifi.bssid_last); i++) { + if (bssid_scan[i] != Wifi.bssid_last[i]) { + Wifi.best_network_db = (int8_t)rssi_scan; + channel = chan_scan; + ap = j; // AP1 or AP2 + memcpy((void*) &Wifi.bssid, (void*) bssid_scan, sizeof(Wifi.bssid)); + // save the last bssid used + memcpy((void*) &Wifi.bssid_last, (void*) bssid_scan, sizeof(Wifi.bssid_last)); + break; + } + } } } break; @@ -307,6 +338,16 @@ void WifiBeginAfterScan(void) WiFi.scanDelete(); // Clean up Ram delay(0); } + + // reset the last bssid if there is only one AP to allow the reconnect of the same AP on the next cycle + if (number_known == 1) { + // clear the last value + memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last)); + memcpy((void*) &Wifi.bssid, (void*) bssid_max, sizeof(Wifi.bssid)); + channel = channel_max; + ap = ap_max; + } + Wifi.scan_state = 0; // If bssid changed then (re)connect wifi for (uint32_t i = 0; i < sizeof(Wifi.bssid); i++) { @@ -381,17 +422,11 @@ void WifiCheckIp(void) #else if ((WL_CONNECTED == WiFi.status()) && (static_cast(WiFi.localIP()) != 0)) { #endif // LWIP_IPV6=1 + // initialize the last connect bssid since we had a successful connection + memset((void*) &Wifi.bssid_last, 0, sizeof(Wifi.bssid_last)); WifiSetState(1); Wifi.counter = WIFI_CHECK_SEC; Wifi.retry = Wifi.retry_init; - AddLog_P((Wifi.status != WL_CONNECTED) ? LOG_LEVEL_INFO : LOG_LEVEL_DEBUG_MORE, S_LOG_WIFI, PSTR(D_CONNECTED)); - if (Wifi.status != WL_CONNECTED) { -// AddLog_P(LOG_LEVEL_INFO, PSTR("Wifi: Set IP addresses")); - Settings.ip_address[1] = (uint32_t)WiFi.gatewayIP(); - Settings.ip_address[2] = (uint32_t)WiFi.subnetMask(); - Settings.ip_address[3] = (uint32_t)WiFi.dnsIP(); - } - Wifi.status = WL_CONNECTED; #ifdef USE_DISCOVERY #ifdef WEBSERVER_ADVERTISE if (2 == Wifi.mdns_begun) { @@ -407,32 +442,18 @@ void WifiCheckIp(void) switch (Wifi.status) { case WL_CONNECTED: AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_NO_IP_ADDRESS)); - Wifi.status = 0; - Wifi.retry = Wifi.retry_init; + // if poor channel health prevents DHCP broadcast from succeeding, restart the request + // The code will eventually do a recoonect when the 1/2 interval is hit to try another access point if this remains unsuccessful + wifi_station_dhcpc_start(); break; case WL_NO_SSID_AVAIL: AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_NOT_REACHED)); - if (WIFI_WAIT == Settings.sta_config) { - Wifi.retry = Wifi.retry_init; - } else { - if (Wifi.retry > (Wifi.retry_init / 2)) { - Wifi.retry = Wifi.retry_init / 2; - } - else if (Wifi.retry) { - Wifi.retry = 0; - } - } break; case WL_CONNECT_FAILED: AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_WRONG_PASSWORD)); - if (Wifi.retry > (Wifi.retry_init / 2)) { - Wifi.retry = Wifi.retry_init / 2; - } - else if (Wifi.retry) { - Wifi.retry = 0; - } break; default: // WL_IDLE_STATUS and WL_DISCONNECTED + // log on the 1/2 or full interval if (!Wifi.retry || ((Wifi.retry_init / 2) == Wifi.retry)) { AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_TIMEOUT)); } else { @@ -444,9 +465,11 @@ void WifiCheckIp(void) } } } + if (Wifi.retry) { if (Settings.flag3.use_wifi_scan) { // SetOption56 - Scan wifi network at restart for configured AP's - if (Wifi.retry_init == Wifi.retry) { + // check the 1/2 interval as well when rescanning - scan state machine takes 4 seconds + if ((Wifi.retry_init == Wifi.retry) || ((Wifi.retry_init / 2) == Wifi.retry)){ Wifi.scan_state = 1; // Select scanned SSID } } else { @@ -593,7 +616,6 @@ String WifiGetOutputPower(void) dtostrfd((float)(Settings.wifi_output_power) / 10, 1, stemp1); return String(stemp1); } - void WifiSetOutputPower(void) { WiFi.setOutputPower((float)(Settings.wifi_output_power) / 10); @@ -605,7 +627,9 @@ void WifiConnect(void) WifiSetOutputPower(); WiFi.persistent(false); // Solve possible wifi init errors Wifi.status = 0; - Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2); + // lower the rety times now Tasmota control the reconnections, not the Arduino SDK + // Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + ((ESP.getChipId() & 0xF) * 2); + Wifi.retry_init = WIFI_RETRY_OFFSET_SEC + (ESP.getChipId() & 0xF); Wifi.retry = Wifi.retry_init; Wifi.counter = 1; } diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index b4b944d80..4cb435a55 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -31,6 +31,7 @@ \*********************************************************************************************/ #define CODE_IMAGE 0 +#define CODE_IMAGE_STR "tasmota" #define USE_LIGHT // Enable light control #define USE_ENERGY_SENSOR // Use energy sensors (+14k code) diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 721c2f4ac..92ee55fdf 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -68,8 +68,6 @@ // Structs #include "settings.h" -const char kCodeImage[] PROGMEM = "tasmota|minimal|sensors|knx|lite|display|ir"; - /*********************************************************************************************\ * Global variables \*********************************************************************************************/ @@ -110,12 +108,13 @@ float global_temperature = 9999; // Provide a global temperature to b float global_humidity = 0; // Provide a global humidity to be used by some sensors float global_pressure = 0; // Provide a global pressure to be used by some sensors uint16_t tele_period = 9999; // Tele period timer -uint16_t mqtt_cmnd_publish = 0; // ignore flag for publish command uint16_t blink_counter = 0; // Number of blink cycles uint16_t seriallog_timer = 0; // Timer to disable Seriallog uint16_t syslog_timer = 0; // Timer to re-enable syslog_level int16_t save_data_counter; // Counter and flag for config save to Flash RulesBitfield rules_flag; // Rule state flags (16 bits) +uint8_t mqtt_cmnd_blocked = 0; // Ignore flag for publish command +uint8_t mqtt_cmnd_blocked_reset = 0; // Count down to reset if needed uint8_t state_250mS = 0; // State 250msecond per second flag uint8_t latching_relay_pulse = 0; // Latching relay pulse timer uint8_t sleep; // Current copy of Settings.sleep @@ -199,8 +198,8 @@ void setup(void) if (VERSION & 0xff) { // Development or patched version 6.3.0.10 snprintf_P(my_version, sizeof(my_version), PSTR("%s.%d"), my_version, VERSION & 0xff); } - char code_image[20]; - snprintf_P(my_image, sizeof(my_image), PSTR("(%s)"), GetTextIndexed(code_image, sizeof(code_image), CODE_IMAGE, kCodeImage)); + // Thehackbox inserts "release" or "commit number" before compiling using sed -i -e 's/PSTR("(%s)")/PSTR("(85cff52-%s)")/g' tasmota.ino + snprintf_P(my_image, sizeof(my_image), PSTR("(%s)"), CODE_IMAGE_STR); // Results in (85cff52-tasmota) or (release-tasmota) SettingsLoad(); SettingsDelta(); diff --git a/tasmota/tasmota_post.h b/tasmota/tasmota_post.h index af2fe00f7..bef9c567d 100644 --- a/tasmota/tasmota_post.h +++ b/tasmota/tasmota_post.h @@ -79,6 +79,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef CODE_IMAGE #define CODE_IMAGE 2 +#undef CODE_IMAGE_STR +#define CODE_IMAGE_STR "sensors" #undef USE_DISCOVERY // Disable mDNS (+8k code or +23.5k code with core 2_5_x, +0.3k mem) @@ -228,6 +230,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef CODE_IMAGE #define CODE_IMAGE 3 +#undef CODE_IMAGE_STR +#define CODE_IMAGE_STR "knx" #ifndef USE_KNX #define USE_KNX // Enable KNX IP Protocol Support (+23k code, +3k3 mem) @@ -248,6 +252,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef CODE_IMAGE #define CODE_IMAGE 5 +#undef CODE_IMAGE_STR +#define CODE_IMAGE_STR "display" #undef USE_EMULATION // Disable Belkin WeMo and Hue Bridge emulation for Alexa (-16k code, -2k mem) #undef USE_EMULATION_HUE // Disable Hue Bridge emulation for Alexa (+14k code, +2k mem common) @@ -313,6 +319,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef CODE_IMAGE #define CODE_IMAGE 6 +#undef CODE_IMAGE_STR +#define CODE_IMAGE_STR "ir" #undef USE_EMULATION #undef USE_EMULATION_HUE // Disable Hue emulation - only for lights and relays @@ -406,6 +414,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef CODE_IMAGE #define CODE_IMAGE 4 +#undef CODE_IMAGE_STR +#define CODE_IMAGE_STR "lite" #undef APP_SLEEP #define APP_SLEEP 1 // Default to sleep = 1 for FIRMWARE_LITE @@ -511,6 +521,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack #undef CODE_IMAGE #define CODE_IMAGE 1 +#undef CODE_IMAGE_STR +#define CODE_IMAGE_STR "minimal" #undef FIRMWARE_LITE // Disable tasmota-lite with no sensors #undef FIRMWARE_SENSORS // Disable tasmota-sensors with useful sensors enabled diff --git a/tasmota/tasmota_template.h b/tasmota/tasmota_template.h index 1793902eb..f041620c6 100644 --- a/tasmota/tasmota_template.h +++ b/tasmota/tasmota_template.h @@ -169,7 +169,7 @@ enum UserSelectablePins { GPIO_SM16716_SEL, // SM16716 SELECT GPIO_DI, // my92x1 PWM input GPIO_DCKI, // my92x1 CLK input - GPIO_CSE7766_TX, // CSE7766 Serial interface (S31 and Pow R2) + GPIO_CSE7766_TX, // CSE7766 Serial interface (S31 and Pow R2) - Not used anymore 20200121 GPIO_CSE7766_RX, // CSE7766 Serial interface (S31 and Pow R2) GPIO_ARIRFRCV, // AriLux RF Receive input GPIO_TXD, // Serial interface @@ -311,7 +311,7 @@ enum UserSelectableAdc0 { ADC0_LIGHT, // Light sensor ADC0_BUTTON, // Button ADC0_BUTTON_INV, - ADC0_MOIST, // Moisture + ADC0_RANGE, // Range ADC0_CT_POWER, // Current // ADC0_SWITCH, // Switch // ADC0_SWITCH_INV, @@ -328,7 +328,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_RANGE "|" D_CT_POWER "|" // D_SENSOR_SWITCH "|" D_SENSOR_SWITCH "i|" ; diff --git a/tasmota/tasmota_version.h b/tasmota/tasmota_version.h index 1539def62..e38f4cad0 100644 --- a/tasmota/tasmota_version.h +++ b/tasmota/tasmota_version.h @@ -20,7 +20,7 @@ #ifndef _TASMOTA_VERSION_H_ #define _TASMOTA_VERSION_H_ -const uint32_t VERSION = 0x08010004; +const uint32_t VERSION = 0x08010005; // Lowest compatible version const uint32_t VERSION_COMPATIBLE = 0x07010006; diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino index 6096103c4..4d574a9f1 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt.ino @@ -221,6 +221,15 @@ void MqttUnsubscribeLib(const char *topic) bool MqttPublishLib(const char* topic, bool retained) { + // If Prefix1 equals Prefix2 disable next MQTT subscription to prevent loop + if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) { + char *str = strstr(topic, SettingsText(SET_MQTTPREFIX1)); + if (str == topic) { + mqtt_cmnd_blocked_reset = 4; // Allow up to four seconds before resetting residual cmnd blocks + mqtt_cmnd_blocked++; + } + } + bool result = MqttClient.publish(topic, mqtt_data, retained); yield(); // #3313 return result; @@ -238,12 +247,8 @@ void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len // Do not execute multiple times if Prefix1 equals Prefix2 if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) { char *str = strstr(mqtt_topic, SettingsText(SET_MQTTPREFIX1)); - if ((str == mqtt_topic) && mqtt_cmnd_publish) { - if (mqtt_cmnd_publish > 3) { - mqtt_cmnd_publish -= 3; - } else { - mqtt_cmnd_publish = 0; - } + if ((str == mqtt_topic) && mqtt_cmnd_blocked) { + mqtt_cmnd_blocked--; return; } } @@ -291,52 +296,41 @@ void MqttUnsubscribe(const char *topic) void MqttPublishLogging(const char *mxtime) { - if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT - if (MqttIsConnected()) { + char saved_mqtt_data[strlen(mqtt_data) +1]; + memcpy(saved_mqtt_data, mqtt_data, sizeof(saved_mqtt_data)); - char saved_mqtt_data[MESSZ]; - memcpy(saved_mqtt_data, mqtt_data, sizeof(saved_mqtt_data)); -// ResponseTime_P(PSTR(",\"Log\":{\"%s\"}}"), log_data); // Will fail as some messages contain JSON - Response_P(PSTR("%s%s"), mxtime, log_data); // No JSON and ugly!! +// ResponseTime_P(PSTR(",\"Log\":{\"%s\"}}"), log_data); // Will fail as some messages contain JSON + Response_P(PSTR("%s%s"), mxtime, log_data); // No JSON and ugly!! + char stopic[TOPSZ]; + GetTopic_P(stopic, STAT, mqtt_topic, PSTR("LOGGING")); + MqttPublishLib(stopic, false); - char romram[33]; - char stopic[TOPSZ]; - snprintf_P(romram, sizeof(romram), PSTR("LOGGING")); - GetTopic_P(stopic, STAT, mqtt_topic, romram); - - char *me; - if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) { - me = strstr(stopic, SettingsText(SET_MQTTPREFIX1)); - if (me == stopic) { - mqtt_cmnd_publish += 3; - } - } - MqttPublishLib(stopic, false); - - memcpy(mqtt_data, saved_mqtt_data, sizeof(saved_mqtt_data)); - } - } + memcpy(mqtt_data, saved_mqtt_data, sizeof(saved_mqtt_data)); } -void MqttPublishDirect(const char* topic, bool retained) +void MqttPublish(const char* topic, bool retained) { - char sretained[CMDSZ]; - char slog_type[20]; - #ifdef USE_DEBUG_DRIVER - ShowFreeMem(PSTR("MqttPublishDirect")); + ShowFreeMem(PSTR("MqttPublish")); #endif +#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) +// if (retained) { +// AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("Retained are not supported by AWS IoT, using retained = false.")); +// } + retained = false; // AWS IoT does not support retained, it will disconnect if received +#endif + + char sretained[CMDSZ]; sretained[0] = '\0'; + char slog_type[20]; snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_RESULT)); if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT - if (MqttIsConnected()) { - if (MqttPublishLib(topic, retained)) { - snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_MQTT)); - if (retained) { - snprintf_P(sretained, sizeof(sretained), PSTR(" (" D_RETAINED ")")); - } + if (MqttPublishLib(topic, retained)) { + snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_MQTT)); + if (retained) { + snprintf_P(sretained, sizeof(sretained), PSTR(" (" D_RETAINED ")")); } } } @@ -354,25 +348,6 @@ void MqttPublishDirect(const char* topic, bool retained) } } -void MqttPublish(const char* topic, bool retained) -{ - char *me; -#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) - if (retained) { - AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("Retained are not supported by AWS IoT, using retained = false.")); - } - retained = false; // AWS IoT does not support retained, it will disconnect if received -#endif - - if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) { - me = strstr(topic, SettingsText(SET_MQTTPREFIX1)); - if (me == topic) { - mqtt_cmnd_publish += 3; - } - } - MqttPublishDirect(topic, retained); -} - void MqttPublish(const char* topic) { MqttPublish(topic, false); @@ -387,7 +362,7 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain * prefix 5 = stat using subtopic or RESULT * prefix 6 = tele using subtopic or RESULT */ - char romram[33]; + char romram[64]; char stopic[TOPSZ]; snprintf_P(romram, sizeof(romram), ((prefix > 3) && !Settings.flag.mqtt_response) ? S_RSLT_RESULT : subtopic); // SetOption4 - Switch between MQTT RESULT or COMMAND @@ -397,6 +372,36 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain prefix &= 3; GetTopic_P(stopic, prefix, mqtt_topic, romram); MqttPublish(stopic, retained); + +#ifdef USE_MQTT_AWS_IOT + if ((prefix > 0) && (Settings.flag4.awsiot_shadow)) { // placeholder for SetOptionXX + // compute the target topic + char *topic = SettingsText(SET_MQTT_TOPIC); + char topic2[strlen(topic)+1]; // save buffer onto stack + strcpy(topic2, topic); + // replace any '/' with '_' + char *s = topic2; + while (*s) { + if ('/' == *s) { + *s = '_'; + } + s++; + } + // update topic is "$aws/things//shadow/update" + snprintf_P(romram, sizeof(romram), PSTR("$aws/things/%s/shadow/update"), topic2); + + // copy buffer + char *mqtt_save = (char*) malloc(strlen(mqtt_data)+1); + if (!mqtt_save) { return; } // abort + strcpy(mqtt_save, mqtt_data); + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"state\":{\"reported\":%s}}"), mqtt_save); + free(mqtt_save); + + bool result = MqttClient.publish(romram, mqtt_data, false); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram); + yield(); // #3313 + } +#endif // USE_MQTT_AWS_IOT } void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic) @@ -692,11 +697,6 @@ void MqttCheck(void) if (!MqttIsConnected()) { global_state.mqtt_down = 1; if (!Mqtt.retry_counter) { -#ifdef USE_DISCOVERY -#ifdef MQTT_HOST_DISCOVERY - if (!strlen(SettingsText(SET_MQTT_HOST)) && !Wifi.mdns_begun) { return; } -#endif // MQTT_HOST_DISCOVERY -#endif // USE_DISCOVERY MqttReconnect(); } else { Mqtt.retry_counter--; @@ -706,7 +706,9 @@ void MqttCheck(void) } } else { global_state.mqtt_down = 0; - if (Mqtt.initial_connection_state) MqttReconnect(); + if (Mqtt.initial_connection_state) { + MqttReconnect(); + } } } @@ -850,17 +852,17 @@ void CmndPrefix(void) void CmndPublish(void) { if (XdrvMailbox.data_len > 0) { - char *mqtt_part = strtok(XdrvMailbox.data, " "); + char *payload_part; + char *mqtt_part = strtok_r(XdrvMailbox.data, " ", &payload_part); if (mqtt_part) { char stemp1[TOPSZ]; strlcpy(stemp1, mqtt_part, sizeof(stemp1)); - mqtt_part = strtok(nullptr, " "); - if (mqtt_part) { - strlcpy(mqtt_data, mqtt_part, sizeof(mqtt_data)); + if ((payload_part != nullptr) && strlen(payload_part)) { + strlcpy(mqtt_data, payload_part, sizeof(mqtt_data)); } else { mqtt_data[0] = '\0'; } - MqttPublishDirect(stemp1, (XdrvMailbox.index == 2)); + MqttPublish(stemp1, (XdrvMailbox.index == 2)); // ResponseCmndDone(); mqtt_data[0] = '\0'; } @@ -986,8 +988,10 @@ void CmndSensorRetain(void) \*********************************************************************************************/ #if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) -const static uint16_t tls_spi_start_sector = SPIFFS_END + 4; // 0xXXFF -const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000); // 0x40XFF000 +// const static uint16_t tls_spi_start_sector = SPIFFS_END + 4; // 0xXXFF +// const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000); // 0x40XFF000 +const static uint16_t tls_spi_start_sector = 0xFF; // Force last bank of first MB +const static uint8_t* tls_spi_start = (uint8_t*) 0x402FF000; // 0x402FF000 const static size_t tls_spi_len = 0x1000; // 4kb blocs const static size_t tls_block_offset = 0x0400; const static size_t tls_block_len = 0x0400; // 1kb @@ -1144,8 +1148,12 @@ void CmndTlsDump(void) { uint32_t start = (uint32_t)tls_spi_start + tls_block_offset; uint32_t end = start + tls_block_len -1; for (uint32_t pos = start; pos < end; pos += 0x10) { - uint32_t* values = (uint32_t*)(pos); - Serial.printf_P(PSTR("%08x: %08x %08x %08x %08x\n"), pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3])); + uint32_t* values = (uint32_t*)(pos); +#ifdef ARDUINO_ESP8266_RELEASE_2_3_0 + Serial.printf("%08x: %08x %08x %08x %08x\n", pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3])); +#else + Serial.printf_P(PSTR("%08x: %08x %08x %08x %08x\n"), pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3])); +#endif } } #endif // DEBUG_DUMP_TLS diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino index 166fd7aa8..4573d2406 100644 --- a/tasmota/xdrv_03_energy.ino +++ b/tasmota/xdrv_03_energy.ino @@ -522,26 +522,19 @@ void CmndEnergyReset(void) } } else if ((XdrvMailbox.index > 3) && (XdrvMailbox.index <= 5)) { - char *p; - char *str = strtok_r(XdrvMailbox.data, ", ", &p); - int32_t position = -1; - uint32_t values[2]; - - while ((str != nullptr) && (position < 1)) { - uint32_t value = strtoul(str, nullptr, 10); - position++; - values[position] = value *100; - str = strtok_r(nullptr, ", ", &p); - } + uint32_t values[2] = { 0 }; + uint32_t position = ParseParameters(2, values); + values[0] *= 100; + values[1] *= 100; switch (XdrvMailbox.index) { case 4: // Reset energy_usage.usage totals - if (position > -1) { + if (position > 0) { RtcSettings.energy_usage.usage1_kWhtotal = values[0]; } - if (position > 0) { + if (position > 1) { RtcSettings.energy_usage.usage2_kWhtotal = values[1]; } Settings.energy_usage.usage1_kWhtotal = RtcSettings.energy_usage.usage1_kWhtotal; @@ -549,10 +542,10 @@ void CmndEnergyReset(void) break; case 5: // Reset energy_usage.return totals - if (position > -1) { + if (position > 0) { RtcSettings.energy_usage.return1_kWhtotal = values[0]; } - if (position > 0) { + if (position > 1) { RtcSettings.energy_usage.return2_kWhtotal = values[1]; } Settings.energy_usage.return1_kWhtotal = RtcSettings.energy_usage.return1_kWhtotal; diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index d217b75c7..4ef8b409e 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -1723,8 +1723,16 @@ void LightAnimate(void) Light.new_color[i] = Light.current_color[i]; } } else { +/* Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\"}")); MqttPublishPrefixTopic_P(TELE, PSTR(D_CMND_WAKEUP)); +*/ + Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\"")); + LightState(1); + ResponseJsonEnd(); + MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WAKEUP)); + XdrvRulesProcess(); + Light.wakeup_active = 0; Settings.light_scheme = LS_POWER; } @@ -1799,6 +1807,14 @@ void LightAnimate(void) } } + // Apply RGBWWTable only if Settings.rgbwwTable[4] != 0 + if (0 != Settings.rgbwwTable[4]) { + for (uint32_t i = 0; i LST_SINGLE) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) { CmndSupportColor(); } @@ -2192,6 +2215,8 @@ void CmndColor(void) void CmndWhite(void) { + // White - Show current White (=Dimmer2) state + // White 0..100 - Set White colors dimmer state if (Light.pwm_multi_channels) { return; } if ( ((Light.subtype >= LST_RGBW) || (LST_COLDWARM == Light.subtype)) && (XdrvMailbox.index == 1)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { @@ -2205,6 +2230,10 @@ void CmndWhite(void) void CmndChannel(void) { + // Channel - Show current Channel state + // Channel 0..100 - Set Channel dimmer state + // Channel + - Incerement Channel in steps of 10 + // Channel - - Decrement Channel in steps of 10 if ((XdrvMailbox.index >= Light.device) && (XdrvMailbox.index < Light.device + Light.subtype )) { uint32_t light_index = XdrvMailbox.index - Light.device; power_t coldim = 0; // bit flag to update @@ -2249,48 +2278,35 @@ void CmndChannel(void) void CmndHsbColor(void) { + // HsbColor - Show current HSB + // HsbColor 360,100,100 - Set Hue, Saturation and Brighthness + // HsbColor 360,100 - Set Hue and Saturation + // HsbColor 360 - Set Hue + // HsbColor1 360 - Set Hue + // HsbColor2 100 - Set Saturation + // HsbColor3 100 - Set Brightness if (Light.subtype >= LST_RGB) { - bool validHSB = (XdrvMailbox.data_len > 0); - if (validHSB) { - uint16_t HSB[3]; + if (XdrvMailbox.data_len > 0) { uint16_t c_hue; uint8_t c_sat; - light_state.getHSB(&c_hue, &c_sat, nullptr); + uint32_t HSB[3]; HSB[0] = c_hue; HSB[1] = c_sat; HSB[2] = light_state.getBriRGB(); - - char *substr = strstr(XdrvMailbox.data, ","); - if (substr != nullptr) { // Command with comma separated parameters, Hue (0 100) { XdrvMailbox.payload = 100; } + HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 255); + } else { + uint32_t paramcount = ParseParameters(3, HSB); + if (HSB[0] > 360) { HSB[0] = 360; } + for (uint32_t i = 1; i < paramcount; i++) { + if (HSB[i] > 100) { HSB[i] == 100; } HSB[i] = changeUIntScale(HSB[i], 0, 100, 0, 255); // change sat and bri to 0..255 - substr = strstr(substr, ","); - if (substr == nullptr) { - break; - } - } - if (substr != nullptr) { - validHSB = false; - } - } else { // Command with only 1 parameter, Hue (0 1) && (XdrvMailbox.index < 4)) { - HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload,0,100,0,255); - } else { - validHSB = false; } } - if (validHSB) { - light_controller.changeHSB(HSB[0], HSB[1], HSB[2]); - LightPreparePower(1); - } + light_controller.changeHSB(HSB[0], HSB[1], HSB[2]); + LightPreparePower(1); } else { LightState(0); } @@ -2299,6 +2315,11 @@ void CmndHsbColor(void) void CmndScheme(void) { + // Scheme 0..12 - Select one of schemes 0 to 12 + // Scheme 2 - Select scheme 2 + // Scheme 2,0 - Select scheme 2 with color wheel set to 0 (HSB Red) + // Scheme + - Select next scheme + // Scheme - - Select previous scheme if (Light.subtype >= LST_RGB) { uint32_t max_scheme = Light.max_scheme; @@ -2311,6 +2332,10 @@ void CmndScheme(void) } } if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= max_scheme)) { + uint32_t parm[2]; + if (ParseParameters(2, parm) > 1) { + Light.wheel = parm[1]; + } Settings.light_scheme = XdrvMailbox.payload; if (LS_WAKEUP == Settings.light_scheme) { Light.wakeup_active = 3; @@ -2328,6 +2353,8 @@ void CmndScheme(void) void CmndWakeup(void) { + // Wakeup - Start wakeup light + // Wakeup 0..100 - Start wakeup light to dimmer value 0..100 if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) { light_controller.changeDimmer(XdrvMailbox.payload); } @@ -2339,6 +2366,10 @@ void CmndWakeup(void) void CmndColorTemperature(void) { + // CT - Show current color temperature + // CT 153..500 - Set color temperature + // CT + - Incerement color temperature in steps of 34 + // CT - - Decrement color temperature in steps of 34 if (Light.pwm_multi_channels) { return; } if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { // ColorTemp uint32_t ct = light_state.getCT(); @@ -2361,6 +2392,12 @@ void CmndColorTemperature(void) void CmndDimmer(void) { + // Dimmer - Show current Dimmer state + // Dimmer0 0..100 - Change both RGB and W(W) Dimmers + // Dimmer1 0..100 - Change RGB Dimmer + // Dimmer2 0..100 - Change W(W) Dimmer + // Dimmer + - Incerement Dimmer in steps of 10 + // Dimmer - - Decrement Dimmer in steps of 10 uint32_t dimmer; if (XdrvMailbox.index > 2) { XdrvMailbox.index = 1; } @@ -2402,17 +2439,13 @@ void CmndDimmer(void) void CmndDimmerRange(void) { + // DimmerRange - Show current dimmer range as used by Tuya and PS16DZ Dimmers + // DimmerRange 0,100 - Set dimmer hardware range from 0 to 100 and restart if (XdrvMailbox.data_len > 0) { - char *p; - uint8_t i = 0; - uint16_t parm[2]; + uint32_t parm[2]; parm[0] = Settings.dimmer_hw_min; parm[1] = Settings.dimmer_hw_max; - for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) { - parm[i] = strtoul(str, nullptr, 0); - i++; - } - + ParseParameters(2, parm); if (parm[0] < parm[1]) { Settings.dimmer_hw_min = parm[0]; Settings.dimmer_hw_max = parm[1]; @@ -2427,6 +2460,10 @@ void CmndDimmerRange(void) void CmndLedTable(void) { + // LedTable - Show current LedTable state + // LedTable 0 - Turn LedTable Off + // LedTable On - Turn LedTable On + // LedTable Toggle - Toggle LedTable state if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { switch (XdrvMailbox.payload) { case 0: // Off @@ -2444,20 +2481,13 @@ void CmndLedTable(void) void CmndRgbwwTable(void) { + // RgbWwTable - Show current RGBWW State + // RgbWwTable 255,255,255,255,255 - Set RGBWW state to maximum if ((XdrvMailbox.data_len > 0)) { - if (strstr(XdrvMailbox.data, ",") != nullptr) { // Command with up to 5 comma separated parameters - for (uint32_t i = 0; i < LST_RGBCW; i++) { - char *substr; - - if (0 == i) { - substr = strtok(XdrvMailbox.data, ","); - } else { - substr = strtok(nullptr, ","); - } - if (substr != nullptr) { - Settings.rgbwwTable[i] = atoi(substr); - } - } + uint32_t parm[LST_RGBCW -1]; + uint32_t parmcount = ParseParameters(LST_RGBCW, parm); + for (uint32_t i = 0; i < parmcount; i++) { + Settings.rgbwwTable[i] = parm[i]; } Light.update = true; } @@ -2466,11 +2496,15 @@ void CmndRgbwwTable(void) for (uint32_t i = 0; i < LST_RGBCW; i++) { snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]); } - ResponseCmndIdxChar(scolor); + ResponseCmndChar(scolor); } void CmndFade(void) { + // Fade - Show current Fade state + // Fade 0 - Turn Fade Off + // Fade On - Turn Fade On + // Fade Toggle - Toggle Fade state switch (XdrvMailbox.payload) { case 0: // Off case 1: // On @@ -2485,7 +2519,11 @@ void CmndFade(void) } void CmndSpeed(void) -{ // 1 - fast, 40 - very slow +{ + // Speed 1 - Fast + // Speed 40 - Very slow + // Speed + - Increment Speed + // Speed - - Decrement Speed if (1 == XdrvMailbox.data_len) { if (('+' == XdrvMailbox.data[0]) && (Settings.light_speed > 1)) { XdrvMailbox.payload = Settings.light_speed - 1; @@ -2502,6 +2540,8 @@ void CmndSpeed(void) void CmndWakeupDuration(void) { + // WakeUpDuration - Show current Wake Up duration in seconds + // WakeUpDuration 60 - Set Wake Up duration to 60 seconds if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3001)) { Settings.light_wakeup = XdrvMailbox.payload; Light.wakeup_active = 0; @@ -2510,7 +2550,8 @@ void CmndWakeupDuration(void) } void CmndUndocA(void) -{ // Theos legacy status +{ + // Theos legacy status char scolor[LIGHT_COLOR_SIZE]; LightGetColor(scolor, true); // force hex whatever Option 17 scolor[6] = '\0'; // RGB only diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index 13287d009..89fbfebe9 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -255,27 +255,42 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule) } // Step2: Search rule_task and rule_name - StaticJsonBuffer<1024> jsonBuf; - JsonObject &root = jsonBuf.parseObject(event); - if (!root.success()) { return false; } // No valid JSON data - - const char* str_value; - if ((pos = rule_name.indexOf("[")) > 0) { // "CURRENT[1]" - int rule_name_idx = rule_name.substring(pos +1).toInt(); + int rule_name_idx = 0; + if ((pos = rule_name.indexOf("[")) > 0) { // "SUBTYPE1#CURRENT[1]" + rule_name_idx = rule_name.substring(pos +1).toInt(); if ((rule_name_idx < 1) || (rule_name_idx > 6)) { // Allow indexes 1 to 6 rule_name_idx = 1; } - rule_name = rule_name.substring(0, pos); // "CURRENT" - str_value = root[rule_task][rule_name][rule_name_idx -1]; // "ENERGY" and "CURRENT" and 0 - } else { - str_value = root[rule_task][rule_name]; // "INA219" and "CURRENT" + rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT" } -//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"), -// rule_task.c_str(), rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none"); + StaticJsonBuffer<1024> jsonBuf; + JsonObject &root = jsonBuf.parseObject(event); + if (!root.success()) { return false; } // No valid JSON data + if (!root[rule_task].success()) { return false; } // No rule_task in JSON data - if (!root[rule_task][rule_name].success()) { return false; } - // No value but rule_name is ok + JsonObject &obj1 = root[rule_task]; + JsonObject *obj = &obj1; + String subtype; + uint32_t i = 0; + while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT" + subtype = rule_name.substring(0, pos); + if (!(*obj)[subtype].success()) { return false; } // No subtype in JSON data + JsonObject &obj2 = (*obj)[subtype]; + obj = &obj2; + rule_name = rule_name.substring(pos +1); + if (i++ > 10) { return false; } // Abandon possible loop + } + if (!(*obj)[rule_name].success()) { return false; } // No name in JSON data + const char* str_value; + if (rule_name_idx) { + str_value = (*obj)[rule_name][rule_name_idx -1]; // "CURRENT[1]" + } else { + str_value = (*obj)[rule_name]; // "CURRENT" + } + +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"), +// rule_task.c_str(), rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none"); Rules.event_value = str_value; // Prepare %value% diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 17ef36177..09406e810 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -4775,6 +4775,7 @@ bool Xdrv10(uint8_t function) glob_script_mem.script_pram_size=MAX_SCRIPT_SIZE; glob_script_mem.flags=1; + I2cSetActiveFound(EEPROM_ADDRESS, "EEPROM"); } } #endif diff --git a/tasmota/xdrv_23_zigbee_3_devices.ino b/tasmota/xdrv_23_zigbee_3_devices.ino index 1d793f730..5db3986e4 100644 --- a/tasmota/xdrv_23_zigbee_3_devices.ino +++ b/tasmota/xdrv_23_zigbee_3_devices.ino @@ -22,6 +22,10 @@ #include #include +#ifndef ZIGBEE_SAVE_DELAY_SECONDS +#define ZIGBEE_SAVE_DELAY_SECONDS 10; // wait for 10s before saving Zigbee info +#endif +const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value); @@ -57,6 +61,16 @@ class Z_Devices { public: Z_Devices() {}; + // Probe the existence of device keys + // Results: + // - 0x0000 = not found + // - 0xFFFF = bad parameter + // - 0x = the device's short address + uint16_t isKnownShortAddr(uint16_t shortaddr) const; + uint16_t isKnownLongAddr(uint64_t longaddr) const; + uint16_t isKnownIndex(uint32_t index) const; + uint16_t isKnownFriendlyName(const char * name) const; + // Add new device, provide ShortAddr and optional longAddr // If it is already registered, update information, otherwise create the entry void updateDevice(uint16_t shortaddr, uint64_t longaddr = 0); @@ -74,13 +88,14 @@ public: void setManufId(uint16_t shortaddr, const char * str); void setModelId(uint16_t shortaddr, const char * str); - void setFriendlyNameId(uint16_t shortaddr, const char * str); + void setFriendlyName(uint16_t shortaddr, const char * str); + const String * getFriendlyName(uint16_t) const; // device just seen on the network, update the lastSeen field void updateLastSeen(uint16_t shortaddr); // Dump json - String dump(uint32_t dump_mode, int32_t device_num = 0) const; + String dump(uint32_t dump_mode, uint16_t status_shortaddr = 0) const; // Timers void resetTimer(uint32_t shortaddr); @@ -89,13 +104,33 @@ public: // Append or clear attributes Json structure void jsonClear(uint16_t shortaddr); - void jsonAppend(uint16_t shortaddr, JsonObject &values); + void jsonAppend(uint16_t shortaddr, const JsonObject &values); const JsonObject *jsonGet(uint16_t shortaddr); - const void jsonPublish(uint16_t shortaddr); // publish the json message and clear buffer + void jsonPublishFlush(uint16_t shortaddr); // publish the json message and clear buffer bool jsonIsConflict(uint16_t shortaddr, const JsonObject &values); + void jsonPublishNow(uint16_t shortaddr, JsonObject &values); + + // Iterator + size_t devicesSize(void) const { + return _devices.size(); + } + const Z_Device &devicesAt(size_t i) const { + return _devices.at(i); + } + + // Remove device from list + bool removeDevice(uint16_t shortaddr); + + // Mark data as 'dirty' and requiring to save in Flash + void dirty(void); + void clean(void); // avoid writing to flash the last changes + + // Find device by name, can be short_addr, long_addr, number_in_array or name + uint16_t parseDeviceParam(const char * param, bool short_must_be_known = false) const; private: std::vector _devices = {}; + uint32_t _saveTimer = 0; template < typename T> static bool findInVector(const std::vector & vecOfElements, const T & element); @@ -109,8 +144,9 @@ private: Z_Device & getShortAddr(uint16_t shortaddr); // find Device from shortAddr, creates it if does not exist Z_Device & getLongAddr(uint64_t longaddr); // find Device from shortAddr, creates it if does not exist - int32_t findShortAddr(uint16_t shortaddr); - int32_t findLongAddr(uint64_t longaddr); + int32_t findShortAddr(uint16_t shortaddr) const; + int32_t findLongAddr(uint64_t longaddr) const; + int32_t findFriendlyName(const char * name) const; void _updateLastSeen(Z_Device &device) { if (&device != nullptr) { @@ -187,6 +223,7 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { nullptr, nullptr }; device.json_buffer = new DynamicJsonBuffer(); _devices.push_back(device); + dirty(); return _devices.back(); } @@ -198,7 +235,7 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { // Out: // index in _devices of entry, -1 if not found // -int32_t Z_Devices::findShortAddr(uint16_t shortaddr) { +int32_t Z_Devices::findShortAddr(uint16_t shortaddr) const { if (!shortaddr) { return -1; } // does not make sense to look for 0x0000 shortaddr (localhost) int32_t found = 0; if (shortaddr) { @@ -217,7 +254,7 @@ int32_t Z_Devices::findShortAddr(uint16_t shortaddr) { // Out: // index in _devices of entry, -1 if not found // -int32_t Z_Devices::findLongAddr(uint64_t longaddr) { +int32_t Z_Devices::findLongAddr(uint64_t longaddr) const { if (!longaddr) { return -1; } int32_t found = 0; if (longaddr) { @@ -228,6 +265,66 @@ int32_t Z_Devices::findLongAddr(uint64_t longaddr) { } return -1; } +// +// Scan all devices to find a corresponding friendlyNme +// Looks info device.friendlyName entry +// In: +// friendlyName (null terminated, should not be empty) +// Out: +// index in _devices of entry, -1 if not found +// +int32_t Z_Devices::findFriendlyName(const char * name) const { + if (!name) { return -1; } // if pointer is null + size_t name_len = strlen(name); + int32_t found = 0; + if (name_len) { + for (auto &elem : _devices) { + if (elem.friendlyName == name) { return found; } + found++; + } + } + return -1; +} + +// Probe if device is already known but don't create any entry +uint16_t Z_Devices::isKnownShortAddr(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + return shortaddr; + } else { + return 0; // unknown + } +} + +uint16_t Z_Devices::isKnownLongAddr(uint64_t longaddr) const { + int32_t found = findLongAddr(longaddr); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + return device.shortaddr; // can be zero, if not yet registered + } else { + return 0; + } +} + +uint16_t Z_Devices::isKnownIndex(uint32_t index) const { + if (index < devicesSize()) { + const Z_Device & device = devicesAt(index); + return device.shortaddr; + } else { + return 0; + } +} + +uint16_t Z_Devices::isKnownFriendlyName(const char * name) const { + if ((!name) || (0 == strlen(name))) { return 0xFFFF; } // Error + int32_t found = findFriendlyName(name); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + return device.shortaddr; // can be zero, if not yet registered + } else { + return 0; + } +} // // We have a seen a shortaddr on the network, get the corresponding @@ -252,6 +349,17 @@ Z_Device & Z_Devices::getLongAddr(uint64_t longaddr) { return createDeviceEntry(0, longaddr); } +// Remove device from list, return true if it was known, false if it was not recorded +bool Z_Devices::removeDevice(uint16_t shortaddr) { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + _devices.erase(_devices.begin() + found); + dirty(); + return true; + } + return false; +} + // // We have just seen a device on the network, update the info based on short/long addr // In: @@ -270,15 +378,18 @@ void Z_Devices::updateDevice(uint16_t shortaddr, uint64_t longaddr) { // erase the previous shortaddr _devices.erase(_devices.begin() + s_found); updateLastSeen(shortaddr); + dirty(); } } else if (s_found >= 0) { // shortaddr already exists but longaddr not // add the longaddr to the entry _devices[s_found].longaddr = longaddr; updateLastSeen(shortaddr); + dirty(); } else if (l_found >= 0) { // longaddr entry exists, update shortaddr _devices[l_found].shortaddr = shortaddr; + dirty(); } else { // neither short/lonf addr are found. if (shortaddr || longaddr) { @@ -298,6 +409,7 @@ void Z_Devices::addEndoint(uint16_t shortaddr, uint8_t endpoint) { _updateLastSeen(device); if (findEndpointInVector(device.endpoints, ep_profile) < 0) { device.endpoints.push_back(ep_profile); + dirty(); } } @@ -310,8 +422,12 @@ void Z_Devices::addEndointProfile(uint16_t shortaddr, uint8_t endpoint, uint16_t int32_t found = findEndpointInVector(device.endpoints, ep_profile); if (found < 0) { device.endpoints.push_back(ep_profile); + dirty(); } else { - device.endpoints[found] = ep_profile; + if (device.endpoints[found] != ep_profile) { + device.endpoints[found] = ep_profile; + dirty(); + } } } @@ -324,10 +440,12 @@ void Z_Devices::addCluster(uint16_t shortaddr, uint8_t endpoint, uint16_t cluste if (!out) { if (!findInVector(device.clusters_in, ep_cluster)) { device.clusters_in.push_back(ep_cluster); + dirty(); } } else { // out if (!findInVector(device.clusters_out, ep_cluster)) { device.clusters_out.push_back(ep_cluster); + dirty(); } } } @@ -352,21 +470,41 @@ void Z_Devices::setManufId(uint16_t shortaddr, const char * str) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found _updateLastSeen(device); + if (!device.manufacturerId.equals(str)) { + dirty(); + } device.manufacturerId = str; } void Z_Devices::setModelId(uint16_t shortaddr, const char * str) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found _updateLastSeen(device); + if (!device.modelId.equals(str)) { + dirty(); + } device.modelId = str; } -void Z_Devices::setFriendlyNameId(uint16_t shortaddr, const char * str) { +void Z_Devices::setFriendlyName(uint16_t shortaddr, const char * str) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found _updateLastSeen(device); + if (!device.friendlyName.equals(str)) { + dirty(); + } device.friendlyName = str; } +const String * Z_Devices::getFriendlyName(uint16_t shortaddr) const { + int32_t found = findShortAddr(shortaddr); + if (found >= 0) { + const Z_Device & device = devicesAt(found); + if (device.friendlyName.length() > 0) { + return &device.friendlyName; + } + } + return nullptr; +} + // device just seen on the network, update the lastSeen field void Z_Devices::updateLastSeen(uint16_t shortaddr) { Z_Device & device = getShortAddr(shortaddr); @@ -398,19 +536,22 @@ void Z_Devices::setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, // Run timer at each tick void Z_Devices::runTimer(void) { - uint32_t now = millis(); - for (std::vector::iterator it = _devices.begin(); it != _devices.end(); ++it) { Z_Device &device = *it; uint16_t shortaddr = device.shortaddr; uint32_t timer = device.timer; - if ((timer) && (timer <= now)) { + if ((timer) && TimeReached(timer)) { 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); } } + // save timer + if ((_saveTimer) && TimeReached(_saveTimer)) { + saveZigbeeDevices(); + _saveTimer = 0; + } } void Z_Devices::jsonClear(uint16_t shortaddr) { @@ -482,7 +623,7 @@ bool Z_Devices::jsonIsConflict(uint16_t shortaddr, const JsonObject &values) { return false; } -void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) { +void Z_Devices::jsonAppend(uint16_t shortaddr, const JsonObject &values) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found if (&values == nullptr) { return; } @@ -490,6 +631,16 @@ void Z_Devices::jsonAppend(uint16_t shortaddr, JsonObject &values) { if (nullptr == device.json) { device.json = &(device.json_buffer->createObject()); } + // Prepend Device, will be removed later if redundant + char sa[8]; + snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr); + device.json->set(F(D_JSON_ZIGBEE_DEVICE), sa); + // Prepend Friendly Name if it has one + const String * fname = zigbee_devices.getFriendlyName(shortaddr); + if (fname) { + device.json->set(F(D_JSON_ZIGBEE_NAME), (char*)fname->c_str()); // (char*) forces ArduinoJson to make a copy of the cstring + } + // copy all values from 'values' to 'json' CopyJsonObject(*device.json, values); } @@ -500,40 +651,125 @@ const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { 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 +void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { + Z_Device & device = getShortAddr(shortaddr); + if (&device == nullptr) { return; } // don't crash if not found + JsonObject * json = device.json; + if (json == nullptr) { return; } // abort if nothing in buffer + + const String * fname = zigbee_devices.getFriendlyName(shortaddr); + bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? + + // if (use_fname) { + // // we need to add the Device short_addr inside the JSON + // char sa[8]; + // snprintf_P(sa, sizeof(sa), PSTR("0x%04X"), shortaddr); + // json->set(F(D_JSON_ZIGBEE_DEVICE), sa); + // } else if (fname) { + // json->set(F(D_JSON_NAME), (char*) fname); + // } + + // Remove redundant "Name" or "Device" + if (use_fname) { + json->remove(F(D_JSON_ZIGBEE_NAME)); + } else { + json->remove(F(D_JSON_ZIGBEE_DEVICE)); + } 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(); + + if (use_fname) { + Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + // DEPRECATED TODO + Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"%s\":%s}}"), fname->c_str(), msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + } else { + Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + // DEPRECATED TODO + Response_P(PSTR("{\"" D_JSON_ZIGBEE_RECEIVED_LEGACY "\":{\"0x%04X\":%s}}"), shortaddr, msg.c_str()); + MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + XdrvRulesProcess(); + } + // MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); + // XdrvRulesProcess(); } +void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) { + jsonPublishFlush(shortaddr); // flush any previous buffer + jsonAppend(shortaddr, values); + jsonPublishFlush(shortaddr); // publish now +} + +void Z_Devices::dirty(void) { + _saveTimer = kZigbeeSaveDelaySeconds * 1000 + millis(); +} +void Z_Devices::clean(void) { + _saveTimer = 0; +} + +// Parse the command parameters for either: +// - a short address starting with "0x", example: 0x1234 +// - a long address starting with "0x", example: 0x7CB03EBB0A0292DD +// - a number 0..99, the index number in ZigbeeStatus +// - a friendly name, between quotes, example: "Room_Temp" +uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_known) const { + if (nullptr == param) { return 0; } + size_t param_len = strlen(param); + char dataBuf[param_len + 1]; + strcpy(dataBuf, param); + RemoveSpace(dataBuf); + uint16_t shortaddr = 0; + + if (strlen(dataBuf) < 4) { + // simple number 0..99 + if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) { + shortaddr = zigbee_devices.isKnownIndex(XdrvMailbox.payload - 1); + } + } else if ((dataBuf[0] == '0') && (dataBuf[1] == 'x')) { + // starts with 0x + if (strlen(dataBuf) < 18) { + // expect a short address + shortaddr = strtoull(dataBuf, nullptr, 0); + if (short_must_be_known) { + shortaddr = zigbee_devices.isKnownShortAddr(shortaddr); + } + // else we don't check if it's already registered to force unregistered devices + } else { + // expect a long address + uint64_t longaddr = strtoull(dataBuf, nullptr, 0); + shortaddr = zigbee_devices.isKnownLongAddr(longaddr); + } + } else { + // expect a Friendly Name + shortaddr = zigbee_devices.isKnownFriendlyName(dataBuf); + } + + return shortaddr; +} // Dump the internal memory of Zigbee devices -// Mode = 1: simple dump of devices addresses and names -// Mode = 2: Mode 1 + also dump the endpoints, profiles and clusters -String Z_Devices::dump(uint32_t dump_mode, int32_t device_num) const { +// Mode = 1: simple dump of devices addresses +// Mode = 2: simple dump of devices addresses and names +// Mode = 3: Mode 2 + also dump the endpoints, profiles and clusters +String Z_Devices::dump(uint32_t dump_mode, uint16_t status_shortaddr) const { DynamicJsonBuffer jsonBuffer; JsonArray& json = jsonBuffer.createArray(); JsonArray& devices = json; - //JsonArray& devices = json.createNestedArray(F("ZigbeeDevices")); - - // if device_num == 0, then we show all devices. - // When no payload, the default is -99. In this case change it to 0. - if (device_num < 0) { device_num = 0; } - - uint32_t device_current = 1; - for (std::vector::const_iterator it = _devices.begin(); it != _devices.end(); ++it, ++device_current) { - // ignore non-current device, if specified device is non-zero - if ((device_num > 0) && (device_num != device_current)) { continue; } + for (std::vector::const_iterator it = _devices.begin(); it != _devices.end(); ++it) { const Z_Device& device = *it; uint16_t shortaddr = device.shortaddr; - char hex[20]; + char hex[22]; + + // ignore non-current device, if specified device is non-zero + if ((status_shortaddr) && (status_shortaddr != shortaddr)) { continue; } JsonObject& dev = devices.createNestedObject(); @@ -545,7 +781,9 @@ String Z_Devices::dump(uint32_t dump_mode, int32_t device_num) const { } if (2 <= dump_mode) { - Uint64toHex(device.longaddr, hex, 64); + hex[0] = '0'; // prefix with '0x' + hex[1] = 'x'; + Uint64toHex(device.longaddr, &hex[2], 64); dev[F("IEEEAddr")] = hex; if (device.modelId.length() > 0) { dev[F(D_JSON_MODEL D_JSON_ID)] = device.modelId; diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino new file mode 100644 index 000000000..fcf1dcdc3 --- /dev/null +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -0,0 +1,334 @@ +/* + xdrv_23_zigbee.ino - zigbee support for Tasmota + + Copyright (C) 2020 Theo Arends and Stephan Hadinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_ZIGBEE + +// Ensure persistence of devices into Flash +// +// Structure: +// (from file info): +// uint16 - start address in Flash (offset) +// uint16 - length in bytes (makes sure parsing stops) +// +// File structure: +// uint8 - number of devices, 0=none, 0xFF=invalid entry (probably Flash was erased) +// +// [Array of devices] +// [Offset = 2] +// uint8 - length of revice record +// uint16 - short address +// uint64 - long IEEE address +// uint8 - number of endpoints +// [Array of endpoints] +// uint8 - endpoint number +// uint16 - profileID of the endpoint +// Array of uint8 - clusters In codes, 0xFF end marker +// Array of uint8 - clusters Out codes, 0xFF end marker +// +// str - ModelID (null terminated C string, 32 chars max) +// str - Manuf (null terminated C string, 32 chars max) +// reserved for extensions + +// Memory footprint +const static uint16_t z_spi_start_sector = 0xFF; // Force last bank of first MB +const static uint8_t* z_spi_start = (uint8_t*) 0x402FF000; // 0x402FF000 +const static uint8_t* z_dev_start = z_spi_start + 0x0800; // 0x402FF800 - 2KB +const static size_t z_spi_len = 0x1000; // 4kb blocs +const static size_t z_block_offset = 0x0800; +const static size_t z_block_len = 0x0800; // 2kb + +class z_flashdata_t { +public: + uint32_t name; // simple 4 letters name. Currently 'skey', 'crt ', 'crt1', 'crt2' + uint16_t len; // len of object + uint16_t reserved; // align on 4 bytes boundary +}; + +const static uint32_t ZIGB_NAME = 0x3167697A; // 'zig1' little endian +const static size_t Z_MAX_FLASH = z_block_len - sizeof(z_flashdata_t); // 2040 + +// encoding for the most commonly 32 clusters, used for binary encoding +const uint16_t Z_ClusterNumber[] PROGMEM = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, + 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0100, 0x0101, 0x0102, + 0x0201, 0x0202, 0x0203, 0x0204, + 0x0300, 0x0301, + 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, + 0x0500, 0x0501, 0x0502, + 0x0700, 0x0701, 0x0702, + 0x0B00, 0x0B01, 0x0B02, 0x0B03, 0x0B04, 0x0B05, + 0x1000, + 0xFC0F, +}; + +// convert a 1 byte cluster code to the actual cluster number +uint16_t fromClusterCode(uint8_t c) { + if (c >= sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0])) { + return 0xFFFF; // invalid + } + return pgm_read_word(&Z_ClusterNumber[c]); +} + +// convert a cluster number to 1 byte, or 0xFF if not in table +uint8_t toClusterCode(uint16_t c) { + for (uint32_t i = 0; i < sizeof(Z_ClusterNumber)/sizeof(Z_ClusterNumber[0]); i++) { + if (c == pgm_read_word(&Z_ClusterNumber[i])) { + return i; + } + } + return 0xFF; // not found +} + +class SBuffer hibernateDevice(const struct Z_Device &device) { + SBuffer buf(128); + + buf.add8(0x00); // overall length, will be updated later + buf.add16(device.shortaddr); + buf.add64(device.longaddr); + uint32_t endpoints = device.endpoints.size(); + if (endpoints > 254) { endpoints = 254; } + buf.add8(endpoints); + // iterate on endpoints + for (std::vector::const_iterator ite = device.endpoints.begin() ; ite != device.endpoints.end(); ++ite) { + uint32_t ep_profile = *ite; + uint8_t endpoint = (ep_profile >> 16) & 0xFF; + uint16_t profileId = ep_profile & 0xFFFF; + + buf.add8(endpoint); + buf.add16(profileId); + for (std::vector::const_iterator itc = device.clusters_in.begin() ; itc != device.clusters_in.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t c_endpoint = (*itc >> 16) & 0xFF; + + if (endpoint == c_endpoint) { + uint8_t clusterCode = toClusterCode(cluster); + if (0xFF != clusterCode) { buf.add8(clusterCode); } + } + } + buf.add8(0xFF); // end of endpoint marker + + for (std::vector::const_iterator itc = device.clusters_out.begin() ; itc != device.clusters_out.end(); ++itc) { + uint16_t cluster = *itc & 0xFFFF; + uint8_t c_endpoint = (*itc >> 16) & 0xFF; + + if (endpoint == c_endpoint) { + uint8_t clusterCode = toClusterCode(cluster); + if (0xFF != clusterCode) { buf.add8(clusterCode); } + } + } + buf.add8(0xFF); // end of endpoint marker + } + + // ModelID + size_t model_len = device.modelId.length(); + if (model_len > 32) { model_len = 32; } // max 32 chars + buf.addBuffer(device.modelId.c_str(), model_len); + buf.add8(0x00); // end of string marker + + // ManufID + size_t manuf_len = device.manufacturerId.length(); + if (manuf_len > 32) {manuf_len = 32; } // max 32 chars + buf.addBuffer(device.manufacturerId.c_str(), manuf_len); + buf.add8(0x00); // end of string marker + + // FriendlyName + size_t frname_len = device.friendlyName.length(); + if (frname_len > 32) {frname_len = 32; } // max 32 chars + buf.addBuffer(device.friendlyName.c_str(), frname_len); + buf.add8(0x00); // end of string marker + + // update overall length + buf.set8(0, buf.len()); + + return buf; +} + +class SBuffer hibernateDevices(void) { + SBuffer buf(2048); + + size_t devices_size = zigbee_devices.devicesSize(); + if (devices_size > 32) { devices_size = 32; } // arbitrarily limit to 32 devices, for now + buf.add8(devices_size); // number of devices + + for (uint32_t i = 0; i < devices_size; i++) { + const Z_Device & device = zigbee_devices.devicesAt(i); + const SBuffer buf_device = hibernateDevice(device); + buf.addBuffer(buf_device); + } + + size_t buf_len = buf.len(); + if (buf_len > 2040) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Devices list too big to fit in Flash (%d)"), buf_len); + } + + // Log + char *hex_char = (char*) malloc((buf_len * 2) + 2); + if (hex_char) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbFlashStore %s"), + ToHex_P(buf.getBuffer(), buf_len, hex_char, (buf_len * 2) + 2)); + free(hex_char); + } + + return buf; +} + +void hidrateDevices(const SBuffer &buf) { + uint32_t buf_len = buf.len(); + if (buf_len <= 10) { return; } + + uint32_t k = 0; + uint32_t num_devices = buf.get8(k++); + + for (uint32_t i = 0; (i < num_devices) && (k < buf_len); i++) { + uint32_t dev_record_len = buf.get8(k); + + SBuffer buf_d = buf.subBuffer(k, dev_record_len); + + uint32_t d = 1; // index in device buffer + uint16_t shortaddr = buf_d.get16(d); d += 2; + uint64_t longaddr = buf_d.get64(d); d += 8; + zigbee_devices.updateDevice(shortaddr, longaddr); // update device's addresses + + uint32_t endpoints = buf_d.get8(d++); + for (uint32_t j = 0; j < endpoints; j++) { + uint8_t ep = buf_d.get8(d++); + uint16_t ep_profile = buf_d.get16(d); d += 2; + zigbee_devices.addEndointProfile(shortaddr, ep, ep_profile); + + // in clusters + while (d < dev_record_len) { // safe guard against overflow + uint8_t ep_cluster = buf_d.get8(d++); + if (0xFF == ep_cluster) { break; } // end of block + zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), false); + } + // out clusters + while (d < dev_record_len) { // safe guard against overflow + uint8_t ep_cluster = buf_d.get8(d++); + if (0xFF == ep_cluster) { break; } // end of block + zigbee_devices.addCluster(shortaddr, ep, fromClusterCode(ep_cluster), true); + } + } + + // parse 3 strings + char empty[] = ""; + + // ManufID + uint32_t s_len = buf_d.strlen_s(d); + char *ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setModelId(shortaddr, ptr); + d += s_len + 1; + + // ManufID + s_len = buf_d.strlen_s(d); + ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setManufId(shortaddr, ptr); + d += s_len + 1; + + // FriendlyName + s_len = buf_d.strlen_s(d); + ptr = s_len ? buf_d.charptr(d) : empty; + zigbee_devices.setFriendlyName(shortaddr, ptr); + d += s_len + 1; + + // next iteration + k += dev_record_len; + } +} + +void loadZigbeeDevices(void) { + z_flashdata_t flashdata; + memcpy_P(&flashdata, z_dev_start, sizeof(z_flashdata_t)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "Zigbee signature in Flash: %08X - %d"), flashdata.name, flashdata.len); + + // Check the signature + if ((flashdata.name == ZIGB_NAME) && (flashdata.len > 0)) { + uint16_t buf_len = flashdata.len; + // parse what seems to be a valid entry + SBuffer buf(buf_len); + buf.addBuffer(z_dev_start + sizeof(z_flashdata_t), buf_len); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee devices data in Flash (%d bytes)"), buf_len); + hidrateDevices(buf); + zigbee_devices.clean(); // don't write back to Flash what we just loaded + } else { + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "No zigbee devices data in Flash")); + } +} + +void saveZigbeeDevices(void) { + SBuffer buf = hibernateDevices(); + size_t buf_len = buf.len(); + if (buf_len > Z_MAX_FLASH) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Buffer too big to fit in Flash (%d bytes)"), buf_len); + return; + } + + // first copy SPI buffer into ram + uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len); + if (!spi_buffer) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer")); + return; + } + // copy the flash into RAM to make local change, and write back the whole buffer + ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + + z_flashdata_t *flashdata = (z_flashdata_t*)(spi_buffer + z_block_offset); + flashdata->name = ZIGB_NAME; + flashdata->len = buf_len; + flashdata->reserved = 0; + + memcpy(spi_buffer + z_block_offset + sizeof(z_flashdata_t), buf.getBuffer(), buf_len); + + // buffer is now ready, write it back + if (ESP.flashEraseSector(z_spi_start_sector)) { + ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + } + + free(spi_buffer); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data store in Flash (0x%08X - %d bytes)"), z_dev_start, buf_len); +} + +// Erase the flash area containing the ZigbeeData +void eraseZigbeeDevices(void) { + zigbee_devices.clean(); // avoid writing data to flash after erase + // first copy SPI buffer into ram + uint8_t *spi_buffer = (uint8_t*) malloc(z_spi_len); + if (!spi_buffer) { + AddLog_P2(LOG_LEVEL_ERROR, PSTR(D_LOG_ZIGBEE "Cannot allocate 4KB buffer")); + return; + } + // copy the flash into RAM to make local change, and write back the whole buffer + ESP.flashRead(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + + // Fill the Zigbee area with 0xFF + memset(spi_buffer + z_block_offset, 0xFF, z_block_len); + + // buffer is now ready, write it back + if (ESP.flashEraseSector(z_spi_start_sector)) { + ESP.flashWrite(z_spi_start_sector * SPI_FLASH_SEC_SIZE, (uint32_t*) spi_buffer, SPI_FLASH_SEC_SIZE); + } + + free(spi_buffer); + AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Zigbee Devices Data erased (0x%08X - %d bytes)"), z_dev_start, z_block_len); +} + +#endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 58b055d2b..0518ec6be 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -356,7 +356,12 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer // TODO case 0x39: // float - i += 4; + { + uint32_t uint32_val = buf.get32(i); + float * float_val = (float*) &uint32_val; + i += 4; + json[attrid_str] = *float_val; + } break; case 0xE0: // ToD @@ -401,7 +406,12 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer i += 2; break; case 0x3A: // double precision - i += 8; + { + uint64_t uint64_val = buf.get64(i); + double * double_val = (double*) &uint64_val; + i += 8; + json[attrid_str] = *double_val; + } break; } @@ -473,7 +483,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { uint32_t len = _payload.len(); char attrid_str[12]; - snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cmd_id, _cluster_id); + snprintf_P(attrid_str, sizeof(attrid_str), PSTR("%04X!%02X"), _cluster_id, _cmd_id); char hex_char[_payload.len()*2+2]; ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char)); @@ -561,7 +571,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { 0x000C, 0x0041, "MaxPresentValue", &Z_Copy }, { 0x000C, 0x0045, "MinPresentValue", &Z_Copy }, { 0x000C, 0x0051, "OutOfService", &Z_Copy }, - { 0x000C, 0x0055, "PresentValue", &Z_Copy }, + { 0x000C, 0x0055, "AqaraRotate", &Z_Copy }, { 0x000C, 0x0057, "PriorityArray", &Z_Copy }, { 0x000C, 0x0067, "Reliability", &Z_Copy }, { 0x000C, 0x0068, "RelinquishDefault", &Z_Copy }, @@ -569,6 +579,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = { { 0x000C, 0x006F, "StatusFlags", &Z_Copy }, { 0x000C, 0x0075, "EngineeringUnits", &Z_Copy }, { 0x000C, 0x0100, "ApplicationType", &Z_Copy }, + { 0x000C, 0xFF05, "Aqara_FF05", &Z_Copy }, // Binary Output cluster { 0x0010, 0x0004, "ActiveText", &Z_Copy }, { 0x0010, 0x001C, "Description", &Z_Copy }, @@ -830,10 +841,10 @@ int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& // 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); - MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR)); - XdrvRulesProcess(); + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + json[F(OCCUPANCY)] = 0; + zigbee_devices.jsonPublishNow(shortaddr, json); } // Aqara Cube diff --git a/tasmota/xdrv_23_zigbee_6_commands.ino b/tasmota/xdrv_23_zigbee_6_commands.ino index 4c9b0672f..073ab8e4c 100644 --- a/tasmota/xdrv_23_zigbee_6_commands.ino +++ b/tasmota/xdrv_23_zigbee_6_commands.ino @@ -40,11 +40,11 @@ const Z_CommandConverter Z_Commands[] = { { "Color", "0300!07/xxxxyyyy0A00" }, // x, y (uint16) { "CT", "0300!0A/xxxx0A00" }, // Color Temperature Mireds (uint16) { "Shutter", "0102!xx" }, - { "ShutterOpen", "0102!00"}, - { "ShutterClose", "0102!01"}, - { "ShutterStop", "0102!02"}, - { "ShutterLift", "0102!05xx"}, // Lift percentage, 0%=open, 100%=closed - { "ShutterTilt", "0102!08xx"}, // Tilt percentage + { "ShutterOpen", "0102!00" }, + { "ShutterClose", "0102!01" }, + { "ShutterStop", "0102!02" }, + { "ShutterLift", "0102!05xx" }, // Lift percentage, 0%=open, 100%=closed + { "ShutterTilt", "0102!08xx" }, // Tilt percentage }; #define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 520dbd232..5a3d78e85 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -32,7 +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_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 @@ -354,7 +354,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { //ZI_LOG(LOG_LEVEL_INFO, D_LOG_ZIGBEE "starting zigbee coordinator") ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator ZI_WAIT_RECV(2000, ZBR_STARTUPFROMAPP) // wait for sync ack of command - ZI_WAIT_UNTIL(5000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started + ZI_WAIT_UNTIL(10000, AREQ_STARTUPFROMAPP) // wait for async message that coordinator started ZI_SEND(ZBS_GETDEVICEINFO) // GetDeviceInfo ZI_WAIT_RECV_FUNC(2000, ZBR_GETDEVICEINFO, &Z_ReceiveDeviceInfo) //ZI_WAIT_RECV(2000, ZBR_GETDEVICEINFO) // memorize info @@ -386,6 +386,7 @@ ZI_SEND(ZBS_STARTUPFROMAPP) // start coordinator ZI_MQTT_STATE(ZIGBEE_STATUS_OK, "Started") ZI_LOG(LOG_LEVEL_INFO, D_LOG_ZIGBEE "Zigbee started") ZI_CALL(&Z_State_Ready, 1) // Now accept incoming messages + ZI_CALL(&Z_Load_Devices, 0) ZI_LABEL(ZIGBEE_LABEL_MAIN_LOOP) ZI_WAIT_FOREVER() ZI_GOTO(ZIGBEE_LABEL_READY) @@ -544,7 +545,7 @@ void ZigbeeStateMachine_Run(void) { } // load current instruction details - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Executing instruction pc=%d"), zigbee.pc); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Executing instruction pc=%d"), zigbee.pc); const Zigbee_Instruction *cur_instr_line = &zb_prog[zigbee.pc]; cur_instr = pgm_read_byte(&cur_instr_line->i.i); cur_d8 = pgm_read_byte(&cur_instr_line->i.d8); diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index d396b3fc4..74a900f00 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -403,7 +403,7 @@ int32_t Z_PublishAttributes(uint16_t shortaddr, uint16_t cluster, uint16_t endpo // Post-provess for Aqara Presence Senson Z_AqaraOccupancy(shortaddr, cluster, endpoint, json); - zigbee_devices.jsonPublish(shortaddr); + zigbee_devices.jsonPublishFlush(shortaddr); return 1; } @@ -432,9 +432,7 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr); DynamicJsonBuffer jsonBuffer; - JsonObject& json_root = jsonBuffer.createObject(); - JsonObject& json1 = json_root.createNestedObject(F(D_CMND_ZIGBEE_RECEIVED)); - JsonObject& json = json1.createNestedObject(shortaddr); + JsonObject& json = jsonBuffer.createObject(); if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { zcl_received.parseRawAttributes(json); @@ -446,8 +444,8 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { } String msg(""); msg.reserve(100); - json_root.printTo(msg); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLRawReceived: %s"), msg.c_str()); + json.printTo(msg); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str()); zcl_received.postProcessAttributes(srcaddr, json); // Add linkquality @@ -457,18 +455,14 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { // 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); + zigbee_devices.jsonPublishFlush(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(); + zigbee_devices.jsonPublishNow(srcaddr, json); } return -1; } @@ -511,6 +505,12 @@ int32_t Z_Recv_Default(int32_t res, const class SBuffer &buf) { } } +int32_t Z_Load_Devices(uint8_t value) { + // try to hidrate from known devices + loadZigbeeDevices(); + return 0; // continue +} + int32_t Z_State_Ready(uint8_t value) { zigbee.init_phase = false; // initialization phase complete return 0; // continue diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 408410f9b..2b529c8d6 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -28,16 +28,24 @@ const uint8_t ZIGBEE_SOF_ALT = 0xFF; #include TasmotaSerial *ZigbeeSerial = nullptr; -const char kZigbeeCommands[] PROGMEM = "|" + +const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix 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_ZIGBEEZNPRECEIVE - ; + D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME ; + +const char kZigbeeCommands[] PROGMEM = D_PRFX_ZIGBEE "|" // legacy prefix -- deprecated + 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_ZIGBEEZNPRECEIVE "|" + D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME ; void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZigbeeZNPSend, &CmndZigbeePermitJoin, &CmndZigbeeStatus, &CmndZigbeeReset, &CmndZigbeeSend, - &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive + &CmndZigbeeProbe, &CmndZigbeeRead, &CmndZigbeeZNPReceive, + &CmndZigbeeForget, &CmndZigbeeSave, &CmndZigbeeName }; int32_t ZigbeeProcessInput(class SBuffer &buf) { @@ -62,7 +70,7 @@ int32_t ZigbeeProcessInput(class SBuffer &buf) { } } - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZigbeeProcessInput: recv_prefix_match = %d, recv_filter_match = %d"), recv_prefix_match, recv_filter_match); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: recv_prefix_match = %d, recv_filter_match = %d"), recv_prefix_match, recv_filter_match); } // if there is a recv_callback, call it now @@ -101,7 +109,7 @@ int32_t ZigbeeProcessInput(class SBuffer &buf) { res = (*zigbee.recv_unexpected)(res, buf); } } - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZigbeeProcessInput: res = %d"), res); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "ZbProcessInput: res = %d"), res); // change state accordingly if (0 == res) { @@ -134,7 +142,7 @@ void ZigbeeInput(void) while (ZigbeeSerial->available()) { yield(); uint8_t zigbee_in_byte = ZigbeeSerial->read(); - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZigbeeInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len()); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZbInput byte=%d len=%d"), zigbee_in_byte, zigbee_buffer->len()); if (0 == zigbee_buffer->len()) { // make sure all variables are correctly initialized zigbee_frame_len = 5; @@ -143,14 +151,14 @@ void ZigbeeInput(void) // in this case the first bit (lsb) is missed and Tasmota receives 0xFF instead of 0xFE // We forgive this mistake, and next bytes are automatically resynchronized if (ZIGBEE_SOF_ALT == zigbee_in_byte) { - AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte); + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput forgiven first byte %02X (only for statistics)"), zigbee_in_byte); zigbee_in_byte = ZIGBEE_SOF; } } if ((0 == zigbee_buffer->len()) && (ZIGBEE_SOF != zigbee_in_byte)) { // waiting for SOF (Start Of Frame) byte, discard anything else - AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeInput discarding byte %02X"), zigbee_in_byte); + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbInput discarding byte %02X"), zigbee_in_byte); continue; // discard } @@ -210,7 +218,7 @@ void ZigbeeInit(void) { zigbee.active = false; if ((pin[GPIO_ZIGBEE_RX] < 99) && (pin[GPIO_ZIGBEE_TX] < 99)) { - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("Zigbee: GPIOs Rx:%d Tx:%d"), pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "GPIOs Rx:%d Tx:%d"), pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX]); // if seriallog_level is 0, we allow GPIO 13/15 to switch to Hardware Serial ZigbeeSerial = new TasmotaSerial(pin[GPIO_ZIGBEE_RX], pin[GPIO_ZIGBEE_TX], seriallog_level ? 1 : 2, 0, 256); // set a receive buffer of 256 bytes ZigbeeSerial->begin(115200); @@ -254,6 +262,7 @@ void CmndZigbeeReset(void) { switch (XdrvMailbox.payload) { case 1: ZigbeeZNPSend(ZIGBEE_FACTORY_RESET, sizeof(ZIGBEE_FACTORY_RESET)); + eraseZigbeeDevices(); restart_flag = 2; ResponseCmndChar(D_JSON_ZIGBEE_CC2530 " " D_JSON_RESET_AND_RESTARTING); break; @@ -263,13 +272,6 @@ void CmndZigbeeReset(void) { } } -void CmndZigbeeStatus(void) { - if (ZigbeeSerial) { - String dump = zigbee_devices.dump(XdrvMailbox.index, XdrvMailbox.payload); - Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.index, dump.c_str()); - } -} - void CmndZigbeeZNPSendOrReceive(bool send) { if (ZigbeeSerial && (XdrvMailbox.data_len > 0)) { @@ -320,17 +322,17 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) { uint8_t fcs = data_len; ZigbeeSerial->write(ZIGBEE_SOF); // 0xFE - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend SOF %02X"), ZIGBEE_SOF); ZigbeeSerial->write(data_len); - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend LEN %02X"), data_len); for (uint32_t i = 0; i < len; i++) { uint8_t b = pgm_read_byte(msg + i); ZigbeeSerial->write(b); fcs ^= b; - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend byt %02X"), b); } ZigbeeSerial->write(fcs); // finally send fcs checksum byte - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs); + //AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs); } // Now send a MQTT message to report the sent message char hex_char[(len * 2) + 2]; @@ -421,13 +423,13 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) { if (0 == endpoint) { // endpoint is not specified, let's try to find it from shortAddr endpoint = zigbee_devices.findClusterEndpointIn(dstAddr, cluster); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: guessing endpoint 0x%02X"), endpoint); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"), + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: dstAddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"), dstAddr, cluster, endpoint, cmd, data); if (0 == endpoint) { - AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeSend: unspecified endpoint")); + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); return; } @@ -467,7 +469,7 @@ void CmndZigbeeSend(void) { if (nullptr != &val_device) { device = strToUInt(val_device); } const JsonVariant &val_endpoint = getCaseInsensitive(json, PSTR("endpoint")); if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } - const JsonVariant val_cmd = getCaseInsensitive(json, PSTR("Send")); + const JsonVariant &val_cmd = getCaseInsensitive(json, PSTR("Send")); if (nullptr != &val_cmd) { // probe the type of the argument // If JSON object, it's high level commands @@ -522,9 +524,9 @@ void CmndZigbeeSend(void) { } } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: command_template = %s"), cmd_str.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str()); cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeSend: command_final = %s"), cmd_str.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str()); } else { // we have zero command, pass through until last error for missing command } @@ -535,7 +537,7 @@ void CmndZigbeeSend(void) { // we have an unsupported command type, just ignore it and fallback to missing command } - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"), + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"), device, endpoint, cmd_str.c_str()); zigbeeZCLSendStr(device, endpoint, cmd_str.c_str()); } else { @@ -548,20 +550,68 @@ void CmndZigbeeSend(void) { // Probe a specific device to get its endpoints and supported clusters void CmndZigbeeProbe(void) { if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } - char dataBufUc[XdrvMailbox.data_len + 1]; - UpperCase(dataBufUc, XdrvMailbox.data); - RemoveSpace(dataBufUc); - if (strlen(dataBufUc) < 3) { ResponseCmndChar("Invalid destination"); return; } - - // TODO, for now ignore friendly names - uint16_t shortaddr = strtoull(dataBufUc, nullptr, 0); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CmndZigbeeScan: short addr 0x%04X"), shortaddr); + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } // everything is good, we can send the command Z_SendActiveEpReq(shortaddr); ResponseCmndDone(); } +// Specify, read or erase a Friendly Name +void CmndZigbeeName(void) { + // Syntax is: + // ZigbeeName , - assign a friendly name + // ZigbeeName - display the current friendly name + // ZigbeeName , - remove friendly name + // + // Where can be: short_addr, long_addr, device_index, friendly_name + + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + + // check if parameters contain a comma ',' + char *p; + char *str = strtok_r(XdrvMailbox.data, ", ", &p); + + // parse first part, + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data, true); // in case of short_addr, it must be already registered + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + + if (p == nullptr) { + const String * friendlyName = zigbee_devices.getFriendlyName(shortaddr); + Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), shortaddr, friendlyName ? friendlyName->c_str() : ""); + } else { + zigbee_devices.setFriendlyName(shortaddr, p); + Response_P(PSTR("{\"0x%04X\":{\"" D_JSON_ZIGBEE_NAME "\":\"%s\"}}"), shortaddr, p); + } +} + +// Remove an old Zigbee device from the list of known devices, use ZigbeeStatus to know all registered devices +void CmndZigbeeForget(void) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + + // everything is good, we can send the command + if (zigbee_devices.removeDevice(shortaddr)) { + ResponseCmndDone(); + } else { + ResponseCmndChar("Unknown device"); + } +} + +// Save Zigbee information to flash +void CmndZigbeeSave(void) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + + saveZigbeeDevices(); + + ResponseCmndDone(); +} + // Send an attribute read command to a device, specifying cluster and list of attributes void CmndZigbeeRead(void) { // ZigbeeRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5} @@ -633,6 +683,20 @@ void CmndZigbeePermitJoin(void) ResponseCmndDone(); } +void CmndZigbeeStatus(void) { + if (ZigbeeSerial) { + if (zigbee.init_phase) { ResponseCmndChar(D_ZIGBEE_NOT_STARTED); return; } + uint16_t shortaddr = zigbee_devices.parseDeviceParam(XdrvMailbox.data); + if (0xFFFF == shortaddr) { ResponseCmndChar("Invalid parameter"); return; } + if (XdrvMailbox.payload > 0) { + if (0x0000 == shortaddr) { ResponseCmndChar("Unknown device"); return; } + } + + String dump = zigbee_devices.dump(XdrvMailbox.index, shortaddr); + Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.index, dump.c_str()); + } +} + /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -659,7 +723,8 @@ bool Xdrv23(uint8_t function) ZigbeeInit(); break; case FUNC_COMMAND: - result = DecodeCommand(kZigbeeCommands, ZigbeeCommand); + result = DecodeCommand(kZbCommands, ZigbeeCommand); + result = result || DecodeCommand(kZigbeeCommands, ZigbeeCommand); // deprecated break; } } diff --git a/tasmota/xdrv_24_buzzer.ino b/tasmota/xdrv_24_buzzer.ino index 9cfa7e111..78246f0f6 100644 --- a/tasmota/xdrv_24_buzzer.ino +++ b/tasmota/xdrv_24_buzzer.ino @@ -155,13 +155,8 @@ void CmndBuzzer(void) if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.payload > 0) { - char *p; - uint32_t i = 0; uint32_t parm[4] = { 0 }; - for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 4; str = strtok_r(nullptr, ", ", &p)) { - parm[i] = strtoul(str, nullptr, 0); - i++; - } + ParseParameters(4, parm); for (uint32_t i = 0; i < 3; i++) { if (parm[i] < 1) { parm[i] = 1; } // Default Count, On time, Off time } diff --git a/tasmota/xdrv_27_shutter.ino b/tasmota/xdrv_27_shutter.ino index f7a574714..0a226c2ea 100644 --- a/tasmota/xdrv_27_shutter.ino +++ b/tasmota/xdrv_27_shutter.ino @@ -34,7 +34,7 @@ uint16_t messwerte[5] = {30,50,70,90,100}; uint16_t last_execute_step; enum ShutterModes { SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE, SHT_OFF_ON__OPEN_CLOSE_STEPPER,}; -enum ShutterButtonStates { SHT_NOT_PRESSED, SHT_PRESSED_MULTI, SHT_PRESSED_HOLD, SHT_PRESSED_IMMEDIATE, SHT_SHT_PRESSED_MULTI_SIMULTANEOUS, SHT_PRESSED_EXT_HOLD_SIMULTANEOUS,}; +enum ShutterButtonStates { SHT_NOT_PRESSED, SHT_PRESSED_MULTI, SHT_PRESSED_HOLD, SHT_PRESSED_IMMEDIATE, SHT_PRESSED_MULTI_SIMULTANEOUS, SHT_PRESSED_HOLD_SIMULTANEOUS, SHT_PRESSED_EXT_HOLD_SIMULTANEOUS,}; const char kShutterCommands[] PROGMEM = D_PRFX_SHUTTER "|" D_CMND_SHUTTER_OPEN "|" D_CMND_SHUTTER_CLOSE "|" D_CMND_SHUTTER_STOP "|" D_CMND_SHUTTER_POSITION "|" @@ -48,7 +48,8 @@ void (* const ShutterCommand[])(void) PROGMEM = { &CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay, &CmndShutterFrequency, &CmndShutterButton, &CmndShutterLock, &CmndShutterEnableEndStopTime}; -const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d}"; + const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"Direction\":%d}"; + const char JSON_SHUTTER_BUTTON[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Button%d\":%d}"; #include @@ -101,6 +102,8 @@ void ShutterRtc50mS(void) int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index) { + if (0 == percent) return 0; + if (100 == percent) return Shutter.open_max[index]; if (Settings.shutter_set50percent[index] != 50) { return (percent <= 5) ? Settings.shuttercoeff[2][index] * percent : Settings.shuttercoeff[1][index] * percent + Settings.shuttercoeff[0][index]; } else { @@ -135,6 +138,8 @@ int32_t ShutterPercentToRealPosition(uint32_t percent, uint32_t index) uint8_t ShutterRealToPercentPosition(int32_t realpos, uint32_t index) { + if (0 >= realpos) return 0; + if (Shutter.open_max[index] <= realpos) return 100; if (Settings.shutter_set50percent[index] != 50) { return (Settings.shuttercoeff[2][index] * 5 > realpos) ? SHT_DIV_ROUND(realpos, Settings.shuttercoeff[2][index]) : SHT_DIV_ROUND(realpos-Settings.shuttercoeff[0][index], Settings.shuttercoeff[1][index]); } else { @@ -228,7 +233,7 @@ void ShutterInit(void) Shutter.real_position[i] = ShutterPercentToRealPosition(Settings.shutter_position[i], i); //Shutter.real_position[i] = Settings.shutter_position[i] <= 5 ? Settings.shuttercoeff[2][i] * Settings.shutter_position[i] : Settings.shuttercoeff[1][i] * Settings.shutter_position[i] + Settings.shuttercoeff[0,i]; - Shutter.start_position[i] = Shutter.real_position[i]; + Shutter.start_position[i] = Shutter.target_position[i] = Shutter.real_position[i]; Shutter.motordelay[i] = Settings.shutter_motordelay[i]; char shutter_open_chr[10]; @@ -244,7 +249,7 @@ void ShutterInit(void) // terminate loop at first INVALID shutter. break; } - + ShutterLimitRealAndTargetPositions(i); Settings.shutter_accuracy = 1; } } @@ -556,28 +561,27 @@ void ShutterButtonHandler(void) } } if ((Button.press_counter[button_index]<99) && (Button.hold_timer[button_index] == loops_per_second * Settings.param[P_HOLD_TIME] / 10)) { // press still valid && SetOption32 (40) - Button hold - if (!Settings.flag.button_restrict) { // no SetOption1 (0) - // check for simultaneous shutter button hold - if (ShutterButtonIsSimultaneousHold(button_index)) { - // simultaneous shutter button hold detected - for (uint32_t i = 0; i < MAX_KEYS; i++) - if (Settings.shutter_button[i] & (1<<31)) - Button.press_counter[i] = 99; // Remember to discard further action for press & hold within button timings - } + // check for simultaneous shutter button hold + if (ShutterButtonIsSimultaneousHold(button_index)) { + // simultaneous shutter button hold detected + for (uint32_t i = 0; i < MAX_KEYS; i++) + if (Settings.shutter_button[i] & (1<<31)) + Button.press_counter[i] = 99; // Remember to discard further action for press & hold within button timings + press_index = 0; + buttonState = SHT_PRESSED_HOLD_SIMULTANEOUS; } - if (Button.press_counter[button_index]<99) + if (Button.press_counter[button_index]<99) { + press_index = 0; buttonState = SHT_PRESSED_HOLD; + } Button.press_counter[button_index] = 0; } - if ((!Settings.flag.button_restrict) && (Button.press_counter[button_index]==0) && (Button.hold_timer[button_index] == loops_per_second * IMMINENT_RESET_FACTOR * Settings.param[P_HOLD_TIME] / 10)) { // no SetOption1 (0) && SetOption32 (40) - Button held for factor times longer + if ((Button.press_counter[button_index]==0) && (Button.hold_timer[button_index] == loops_per_second * IMMINENT_RESET_FACTOR * Settings.param[P_HOLD_TIME] / 10)) { // SetOption32 (40) - Button held for factor times longer // check for simultaneous shutter button extend hold if (ShutterButtonIsSimultaneousHold(button_index)) { // simultaneous shutter button extend hold detected - char scmnd[20]; + press_index = 0; buttonState = SHT_PRESSED_EXT_HOLD_SIMULTANEOUS; - snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); - ExecuteCommand(scmnd, SRC_BUTTON); - return; } } } @@ -589,29 +593,23 @@ void ShutterButtonHandler(void) } else { if (!restart_flag && !Button.hold_timer[button_index] && (Button.press_counter[button_index] > 0)) { if (Button.press_counter[button_index]<99) { - if ((!Settings.flag.button_restrict) && (Button.press_counter[button_index]>=5)) { // no SetOption1 (0) && 5x or more presses - // check for simultaneous shutter button press >3 - uint32 min_shutterbutton_press_counter = -1; - for (uint32_t i = 0; i < MAX_KEYS; i++) { - if ((Settings.shutter_button[i] & (1<<31)) && (Button.press_counter[i] < min_shutterbutton_press_counter)) - min_shutterbutton_press_counter = Button.press_counter[i]; - } - if (min_shutterbutton_press_counter >= Button.press_counter[button_index]-2) { - char scmnd[20]; - // simultaneous shutter button press >3 detected - press_index = Button.press_counter[button_index]; - for (uint32_t i = 0; i < MAX_KEYS; i++) - if (Settings.shutter_button[i] & (1<<31)) - Button.press_counter[i] = 99; // Remember to discard further action for press & hold within button timings - buttonState = SHT_SHT_PRESSED_MULTI_SIMULTANEOUS; - GetTextIndexed(scmnd, sizeof(scmnd), press_index -3, kCommands); - ExecuteCommand(scmnd, SRC_BUTTON); - return; - } + // check for simultaneous shutter button press + uint32 min_shutterbutton_press_counter = -1; + for (uint32_t i = 0; i < MAX_KEYS; i++) { + if ((Settings.shutter_button[i] & (1<<31)) && (Button.press_counter[i] < min_shutterbutton_press_counter)) + min_shutterbutton_press_counter = Button.press_counter[i]; } - press_index = Button.press_counter[button_index]; - if ((buttonState == SHT_NOT_PRESSED) && (Button.press_counter[button_index]<99)) { + if (min_shutterbutton_press_counter == Button.press_counter[button_index]) { + // simultaneous shutter button press detected + press_index = Button.press_counter[button_index]; + for (uint32_t i = 0; i < MAX_KEYS; i++) + if (Settings.shutter_button[i] & (1<<31)) + Button.press_counter[i] = 99; // Remember to discard further action for press & hold within button timings + buttonState = SHT_PRESSED_MULTI_SIMULTANEOUS; + } + if ((buttonState != SHT_PRESSED_MULTI_SIMULTANEOUS) && (Button.press_counter[button_index]<99)) { // no simultaneous shutter button press >3 detected + press_index = Button.press_counter[button_index]; buttonState = SHT_PRESSED_MULTI; } } @@ -620,40 +618,58 @@ void ShutterButtonHandler(void) } } - if ((buttonState != SHT_NOT_PRESSED) && (buttonState != SHT_SHT_PRESSED_MULTI_SIMULTANEOUS) && (buttonState != SHT_PRESSED_EXT_HOLD_SIMULTANEOUS)) { - if (Settings.shutter_startrelay[shutter_index] && Settings.shutter_startrelay[shutter_index] <9) { - if (press_index>3) press_index=3; - press_index = (buttonState == SHT_PRESSED_HOLD) ? 3 : (press_index-1); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: shutter %d, button %d = %d (single=1, double=2, tripple=3, hold=4)"), shutter_index+1, button_index+1, press_index+1); - XdrvMailbox.index = shutter_index +1; - last_source = SRC_BUTTON; - XdrvMailbox.data_len = 0; - char databuf[1] = ""; - XdrvMailbox.data = databuf; - XdrvMailbox.command = NULL; - if (buttonState == SHT_PRESSED_IMMEDIATE) { - XdrvMailbox.payload = XdrvMailbox.index; - CmndShutterStop(); - } - else { - uint8_t position = (Settings.shutter_button[button_index]>>(6*press_index + 2)) & 0x03f; - if (position) { - if (Shutter.direction[shutter_index]) { - XdrvMailbox.payload = XdrvMailbox.index; - CmndShutterStop(); - } else { - XdrvMailbox.payload = position = (position-1)<<1; - CmndShutterPosition(); - if (Settings.shutter_button[button_index] & ((0x01<<26)<=5) && (press_index<=7) && (!Settings.flag.button_restrict)) { // 5x..7x && no SetOption1 (0) + // simultaneous shutter button press 5x, 6x, 7x detected + char scmnd[20]; + GetTextIndexed(scmnd, sizeof(scmnd), press_index -3, kCommands); + ExecuteCommand(scmnd, SRC_BUTTON); + return; + } + } else if (buttonState == SHT_PRESSED_EXT_HOLD_SIMULTANEOUS) { + // simultaneous shutter button extend hold detected + if (!Settings.flag.button_restrict) { // no SetOption1 (0) + char scmnd[20]; + snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); + ExecuteCommand(scmnd, SRC_BUTTON); + return; + } + } else if (buttonState <= SHT_PRESSED_IMMEDIATE) { + if (Settings.shutter_startrelay[shutter_index] && Settings.shutter_startrelay[shutter_index] <9) { + uint8_t pos_press_index = (buttonState == SHT_PRESSED_HOLD) ? 3 : (press_index-1); + if (pos_press_index>3) pos_press_index=3; + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: shutter %d, button %d = %d (single=1, double=2, tripple=3, hold=4)"), shutter_index+1, button_index+1, pos_press_index+1); + XdrvMailbox.index = shutter_index +1; + last_source = SRC_BUTTON; + XdrvMailbox.data_len = 0; + char databuf[1] = ""; + XdrvMailbox.data = databuf; + XdrvMailbox.command = NULL; + if (buttonState == SHT_PRESSED_IMMEDIATE) { + XdrvMailbox.payload = XdrvMailbox.index; + CmndShutterStop(); + } + else { + uint8_t position = (Settings.shutter_button[button_index]>>(6*pos_press_index + 2)) & 0x03f; + if (position) { + if (Shutter.direction[shutter_index]) { + XdrvMailbox.payload = XdrvMailbox.index; + CmndShutterStop(); + } else { + XdrvMailbox.payload = position = (position-1)<<1; + CmndShutterPosition(); + if (Settings.shutter_button[button_index] & ((0x01<<26)< 0) ? Shutter.motordelay[index] : 1); //Shutter.target_position[index] = XdrvMailbox.payload < 5 ? Settings.shuttercoeff[2][index] * XdrvMailbox.payload : Settings.shuttercoeff[1][index] * XdrvMailbox.payload + Settings.shuttercoeff[0,index]; AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: lastsource %d:, real %d, target %d, payload %d"), last_source, Shutter.real_position[index] ,Shutter.target_position[index],target_pos_percent); } if ( (target_pos_percent >= 0) && (target_pos_percent <= 100) && abs(Shutter.target_position[index] - Shutter.real_position[index] ) / Shutter.close_velocity[index] > 2) { + if (Settings.shutter_options[index] & 4) { + if (0 == target_pos_percent) Shutter.target_position[index] -= 1 * 2000; + if (100 == target_pos_percent) Shutter.target_position[index] += 1 * 2000; + } int8_t new_shutterdirection = Shutter.real_position[index] < Shutter.target_position[index] ? 1 : -1; if (Shutter.direction[index] == -new_shutterdirection) { // direction need to be changed. on momentary switches first stop the Shutter @@ -789,26 +808,32 @@ void CmndShutterPosition(void) ShutterWaitForMotorStop(index); ExecuteCommandPower(Settings.shutter_startrelay[index], 0, SRC_SHUTTER); ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]); - // Code for shutters with circuit safe configuration, switch the direction Relay - ExecuteCommandPower(Settings.shutter_startrelay[index] +1, new_shutterdirection == 1 ? 0 : 1, SRC_SHUTTER); - // power on - ExecuteCommandPower(Settings.shutter_startrelay[index], 1, SRC_SHUTTER); + if (Shutter.skip_relay_change == 0) { + // Code for shutters with circuit safe configuration, switch the direction Relay + ExecuteCommandPower(Settings.shutter_startrelay[index] +1, new_shutterdirection == 1 ? 0 : 1, SRC_SHUTTER); + // power on + ExecuteCommandPower(Settings.shutter_startrelay[index], 1, SRC_SHUTTER); + } } else { // now start the motor for the right direction, work for momentary and normal shutters. AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start in dir %d"), Shutter.direction[index]); ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]); - ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER); + if (Shutter.skip_relay_change == 0) { + ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER); + } //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Delay6 5s, xdrv %d"), XdrvMailbox.payload); } Shutter.switched_relay = 0; } } else { target_pos_percent = ShutterRealToPercentPosition(Shutter.real_position[index], index); + ShutterReportPosition(true); } XdrvMailbox.index = index +1; // Fix random index for ShutterClose if (XdrvMailbox.command) ResponseCmndIdxNumber((Settings.shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent); } else { + ShutterReportPosition(true); if (XdrvMailbox.command) ResponseCmndIdxChar("Locked"); } diff --git a/tasmota/xdsp_08_ILI9488.ino b/tasmota/xdsp_08_ILI9488.ino index 8b7438c5a..84a493fc6 100644 --- a/tasmota/xdsp_08_ILI9488.ino +++ b/tasmota/xdsp_08_ILI9488.ino @@ -116,6 +116,7 @@ void ILI9488_InitDriver() if (I2cEnabled(XI2C_38) && I2cSetDevice(FT6236_address)) { FT6236begin(FT6236_address); FT6236_found=1; + I2cSetActiveFound(FT6236_address, "FT6236"); } else { FT6236_found=0; } diff --git a/tasmota/xdsp_10_RA8876.ino b/tasmota/xdsp_10_RA8876.ino index 139a654ed..88f3ebad2 100644 --- a/tasmota/xdsp_10_RA8876.ino +++ b/tasmota/xdsp_10_RA8876.ino @@ -101,6 +101,7 @@ void RA8876_InitDriver() if (I2cEnabled(XI2C_39) && I2cSetDevice(FT5316_address)) { FT6236begin(FT5316_address); FT5316_found=1; + I2cSetActiveFound(FT5316_address, "FT5316"); } else { FT5316_found=0; } diff --git a/tasmota/xnrg_02_cse7766.ino b/tasmota/xnrg_02_cse7766.ino index 45001b55e..d7bb93665 100644 --- a/tasmota/xnrg_02_cse7766.ino +++ b/tasmota/xnrg_02_cse7766.ino @@ -20,7 +20,7 @@ #ifdef USE_ENERGY_SENSOR #ifdef USE_CSE7766 /*********************************************************************************************\ - * CSE7766 - Energy (Sonoff S31 and Sonoff Pow R2) + * CSE7759 and CSE7766 - Energy (Sonoff S31 and Sonoff Pow R2) * HLW8032 - Energy (Blitzwolf SHP5) * * Based on datasheet from http://www.chipsea.com/UploadFiles/2017/08/11144342F01B5662.pdf @@ -37,6 +37,12 @@ #define CSE_PREF 1000 #define CSE_UREF 100 +#define CSE_BUFFER_SIZE 25 + +#include + +TasmotaSerial *CseSerial = nullptr; + struct CSE { long voltage_cycle = 0; long current_cycle = 0; @@ -45,6 +51,8 @@ struct CSE { long cf_pulses = 0; long cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; + int byte_counter = 0; + uint8_t *rx_buffer = nullptr; uint8_t power_invalid = 0; bool received = false; } Cse; @@ -57,7 +65,7 @@ void CseReceived(void) // 55 5A 02 F7 60 00 03 AB 00 40 10 02 60 5D 51 A6 58 03 E9 EF 71 0B 7A 36 - 55 = Ok, 71 = Ok // Hd Id VCal---- Voltage- ICal---- Current- PCal---- Power--- Ad CF--- Ck - uint8_t header = serial_in_buffer[0]; + uint8_t header = Cse.rx_buffer[0]; if ((header & 0xFC) == 0xFC) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware")); return; @@ -67,30 +75,30 @@ void CseReceived(void) if (HLW_UREF_PULSE == Settings.energy_voltage_calibration) { long voltage_coefficient = 191200; // uSec if (CSE_NOT_CALIBRATED != header) { - voltage_coefficient = serial_in_buffer[2] << 16 | serial_in_buffer[3] << 8 | serial_in_buffer[4]; + voltage_coefficient = Cse.rx_buffer[2] << 16 | Cse.rx_buffer[3] << 8 | Cse.rx_buffer[4]; } Settings.energy_voltage_calibration = voltage_coefficient / CSE_UREF; } if (HLW_IREF_PULSE == Settings.energy_current_calibration) { long current_coefficient = 16140; // uSec if (CSE_NOT_CALIBRATED != header) { - current_coefficient = serial_in_buffer[8] << 16 | serial_in_buffer[9] << 8 | serial_in_buffer[10]; + current_coefficient = Cse.rx_buffer[8] << 16 | Cse.rx_buffer[9] << 8 | Cse.rx_buffer[10]; } Settings.energy_current_calibration = current_coefficient; } if (HLW_PREF_PULSE == Settings.energy_power_calibration) { long power_coefficient = 5364000; // uSec if (CSE_NOT_CALIBRATED != header) { - power_coefficient = serial_in_buffer[14] << 16 | serial_in_buffer[15] << 8 | serial_in_buffer[16]; + power_coefficient = Cse.rx_buffer[14] << 16 | Cse.rx_buffer[15] << 8 | Cse.rx_buffer[16]; } Settings.energy_power_calibration = power_coefficient / CSE_PREF; } - uint8_t adjustement = serial_in_buffer[20]; - Cse.voltage_cycle = serial_in_buffer[5] << 16 | serial_in_buffer[6] << 8 | serial_in_buffer[7]; - Cse.current_cycle = serial_in_buffer[11] << 16 | serial_in_buffer[12] << 8 | serial_in_buffer[13]; - Cse.power_cycle = serial_in_buffer[17] << 16 | serial_in_buffer[18] << 8 | serial_in_buffer[19]; - Cse.cf_pulses = serial_in_buffer[21] << 8 | serial_in_buffer[22]; + uint8_t adjustement = Cse.rx_buffer[20]; + Cse.voltage_cycle = Cse.rx_buffer[5] << 16 | Cse.rx_buffer[6] << 8 | Cse.rx_buffer[7]; + Cse.current_cycle = Cse.rx_buffer[11] << 16 | Cse.rx_buffer[12] << 8 | Cse.rx_buffer[13]; + Cse.power_cycle = Cse.rx_buffer[17] << 16 | Cse.rx_buffer[18] << 8 | Cse.rx_buffer[19]; + Cse.cf_pulses = Cse.rx_buffer[21] << 8 | Cse.rx_buffer[22]; if (Energy.power_on) { // Powered on if (adjustement & 0x40) { // Voltage valid @@ -134,41 +142,44 @@ void CseReceived(void) bool CseSerialInput(void) { - if (Cse.received) { - serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; - if (24 == serial_in_byte_counter) { + while (CseSerial->available()) { + yield(); + uint8_t serial_in_byte = CseSerial->read(); - AddLogSerial(LOG_LEVEL_DEBUG_MORE); + if (Cse.received) { + Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte; + if (24 == Cse.byte_counter) { - uint8_t checksum = 0; - for (uint32_t i = 2; i < 23; i++) { checksum += serial_in_buffer[i]; } - if (checksum == serial_in_buffer[23]) { - Energy.data_valid[0] = 0; - CseReceived(); - Cse.received = false; - return true; - } else { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); - do { // Sync buffer with data (issue #1907 and #3425) - memmove(serial_in_buffer, serial_in_buffer +1, 24); - serial_in_byte_counter--; - } while ((serial_in_byte_counter > 2) && (0x5A != serial_in_buffer[1])); - if (0x5A != serial_in_buffer[1]) { + AddLogBuffer(LOG_LEVEL_DEBUG_MORE, Cse.rx_buffer, 24); + + uint8_t checksum = 0; + for (uint32_t i = 2; i < 23; i++) { checksum += Cse.rx_buffer[i]; } + if (checksum == Cse.rx_buffer[23]) { + Energy.data_valid[0] = 0; + CseReceived(); Cse.received = false; - serial_in_byte_counter = 0; + return true; + } else { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); + do { // Sync buffer with data (issue #1907 and #3425) + memmove(Cse.rx_buffer, Cse.rx_buffer +1, 24); + Cse.byte_counter--; + } while ((Cse.byte_counter > 2) && (0x5A != Cse.rx_buffer[1])); + if (0x5A != Cse.rx_buffer[1]) { + Cse.received = false; + Cse.byte_counter = 0; + } } } - } - } else { - if ((0x5A == serial_in_byte) && (1 == serial_in_byte_counter)) { // 0x5A - Packet header 2 - Cse.received = true; } else { - serial_in_byte_counter = 0; + if ((0x5A == serial_in_byte) && (1 == Cse.byte_counter)) { // 0x5A - Packet header 2 + Cse.received = true; + } else { + Cse.byte_counter = 0; + } + Cse.rx_buffer[Cse.byte_counter++] = serial_in_byte; } - serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; } - serial_in_byte = 0; // Discard - return false; } /********************************************************************************************/ @@ -209,15 +220,33 @@ void CseEverySecond(void) } } -void CseDrvInit(void) +void CseSnsInit(void) { - if ((3 == pin[GPIO_CSE7766_RX]) && (1 == pin[GPIO_CSE7766_TX])) { // As it uses 8E1 currently only hardware serial is supported - SetSerial(4800, TS_SERIAL_8E1); + // Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions +// CseSerial = new TasmotaSerial(pin[GPIO_CSE7766_RX], pin[GPIO_CSE7766_TX], 1); + CseSerial = new TasmotaSerial(pin[GPIO_CSE7766_RX], -1, 1); + if (CseSerial->begin(4800, 2)) { // Fake Software Serial 8E1 by using two stop bits + if (CseSerial->hardwareSerial()) { + SetSerial(4800, TS_SERIAL_8E1); + ClaimSerial(); + } if (0 == Settings.param[P_CSE7766_INVALID_POWER]) { Settings.param[P_CSE7766_INVALID_POWER] = CSE_MAX_INVALID_POWER; // SetOption39 1..255 } Cse.power_invalid = Settings.param[P_CSE7766_INVALID_POWER]; - energy_flg = XNRG_02; + } else { + energy_flg = ENERGY_NONE; + } +} + +void CseDrvInit(void) +{ + Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE)); + if (Cse.rx_buffer != nullptr) { +// if ((pin[GPIO_CSE7766_RX] < 99) && (pin[GPIO_CSE7766_TX] < 99)) { + if (pin[GPIO_CSE7766_RX] < 99) { + energy_flg = XNRG_02; + } } } @@ -254,8 +283,8 @@ bool Xnrg02(uint8_t function) bool result = false; switch (function) { - case FUNC_SERIAL: - result = CseSerialInput(); + case FUNC_LOOP: + if (CseSerial) { CseSerialInput(); } break; case FUNC_ENERGY_EVERY_SECOND: CseEverySecond(); @@ -263,6 +292,9 @@ bool Xnrg02(uint8_t function) case FUNC_COMMAND: result = CseCommand(); break; + case FUNC_INIT: + CseSnsInit(); + break; case FUNC_PRE_INIT: CseDrvInit(); break; diff --git a/tasmota/xsns_02_analog.ino b/tasmota/xsns_02_analog.ino index f4de98d9a..038da9fa5 100644 --- a/tasmota/xsns_02_analog.ino +++ b/tasmota/xsns_02_analog.ino @@ -71,7 +71,7 @@ struct { void AdcInit(void) { - if ((Settings.adc_param_type != my_adc0) || (Settings.adc_param1 > 1000000) || (Settings.adc_param1 < 100)) { + if ((Settings.adc_param_type != my_adc0) || (Settings.adc_param1 > 1000000)) { if (ADC0_TEMP == my_adc0) { // Default Shelly 2.5 and 1PM parameters Settings.adc_param_type = ADC0_TEMP; @@ -85,11 +85,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; + else if (ADC0_RANGE == my_adc0) { + Settings.adc_param_type = ADC0_RANGE; Settings.adc_param1 = 0; Settings.adc_param2 = 1023; Settings.adc_param3 = 0; + Settings.adc_param4 = 100; } else if (ADC0_CT_POWER == my_adc0) { Settings.adc_param_type = ADC0_CT_POWER; @@ -144,17 +145,14 @@ uint16_t AdcGetLux(void) return (uint16_t)ldrLux; } -uint16_t AdcGetMoist(void) +uint16_t AdcGetRange(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 ) + // formula for calibration: value, fromLow, fromHigh, toLow, toHigh + // Example: 514, 632, 236, 0, 100 + // int( (( - ) / ( - ) ) * ( - ) ) + ) 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; + double adcrange = ( ((double)Settings.adc_param2 - (double)adc) / ( ((double)Settings.adc_param2 - (double)Settings.adc_param1)) * ((double)Settings.adc_param3 - (double)Settings.adc_param4) + (double)Settings.adc_param4 ); + return (uint16_t)adcrange; } void AdcGetCurrentPower(uint8_t factor) @@ -255,14 +253,14 @@ void AdcShow(bool json) } } - else if (ADC0_MOIST == my_adc0) { - uint16_t adc_moist = AdcGetMoist(); + else if (ADC0_RANGE == my_adc0) { + uint16_t adc_range = AdcGetRange(); if (json) { - ResponseAppend_P(JSON_SNS_MOISTURE, "ANALOG", adc_moist); + ResponseAppend_P(JSON_SNS_RANGE, "ANALOG", adc_range); #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_MOISTURE, "", adc_moist); + WSContentSend_PD(HTTP_SNS_RANGE, "", adc_range); #endif // USE_WEBSERVER } } @@ -342,16 +340,20 @@ void CmndAdcParam(void) if (XdrvMailbox.data_len) { if ((ADC0_TEMP == XdrvMailbox.payload) || (ADC0_LIGHT == XdrvMailbox.payload) || - (ADC0_MOIST == XdrvMailbox.payload) || + (ADC0_RANGE == XdrvMailbox.payload) || (ADC0_CT_POWER == XdrvMailbox.payload)) { if (strstr(XdrvMailbox.data, ",") != nullptr) { // Process parameter entry char sub_string[XdrvMailbox.data_len +1]; // AdcParam 2, 32000, 10000, 3350 // AdcParam 3, 10000, 12518931, -1.405 + // AdcParam 6, 0, 1023, 0, 100 Settings.adc_param_type = XdrvMailbox.payload; Settings.adc_param1 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10); Settings.adc_param2 = strtol(subStr(sub_string, XdrvMailbox.data, ",", 3), nullptr, 10); - if (!ADC0_MOIST == XdrvMailbox.payload) { + if (ADC0_RANGE == XdrvMailbox.payload) { + Settings.adc_param3 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 4), nullptr, 10)); + Settings.adc_param4 = abs(strtol(subStr(sub_string, XdrvMailbox.data, ",", 5), nullptr, 10)); + } else { Settings.adc_param3 = (int)(CharToFloat(subStr(sub_string, XdrvMailbox.data, ",", 4)) * 10000); } if (ADC0_CT_POWER == XdrvMailbox.payload) { @@ -373,7 +375,9 @@ void CmndAdcParam(void) // AdcParam Response_P(PSTR("{\"" D_CMND_ADCPARAM "\":[%d,%d,%d"), Settings.adc_param_type, Settings.adc_param1, Settings.adc_param2); - if (ADC0_MOIST != my_adc0) { + if (ADC0_RANGE == my_adc0) { + ResponseAppend_P(PSTR(",%d,%d"), Settings.adc_param3, Settings.adc_param4); + } else { int value = Settings.adc_param3; uint8_t precision; for (precision = 4; precision > 0; precision--) { @@ -403,7 +407,7 @@ bool Xsns02(uint8_t function) if ((ADC0_INPUT == my_adc0) || (ADC0_TEMP == my_adc0) || (ADC0_LIGHT == my_adc0) || - (ADC0_MOIST == my_adc0) || + (ADC0_RANGE == my_adc0) || (ADC0_CT_POWER == my_adc0)) { switch (function) { #ifdef USE_RULES diff --git a/tasmota/xsns_59_ds1624.ino b/tasmota/xsns_59_ds1624.ino index 9fe34b7d8..be0bcf9e5 100644 --- a/tasmota/xsns_59_ds1624.ino +++ b/tasmota/xsns_59_ds1624.ino @@ -38,6 +38,9 @@ #define DS1621_COUNTER_REGISTER 0xA8 //exists on 1621 and 1624(undocumented) #define DS1621_SLOPE_REGISTER 0xA9 //exists on 1624 and 1624(undocumented) +#define DS1621_CFG_1SHOT (1<<0) +#define DS1621_CFG_DONE (1<<7) + enum { DS1624_TYPE_DS1624, DS1624_TYPE_DS1621 @@ -50,6 +53,8 @@ bool ds1624_init = false; struct { float value; uint8_t type; + int errcnt; + int misscnt; bool valid; char name[9]; } ds1624_sns[DS1624_MAX_SENSORS]; @@ -58,6 +63,17 @@ uint32_t DS1624_Idx2Addr(uint32_t idx) { return 0x48 + idx; } +int DS1624_Restart(uint8_t config, uint32_t idx) { + uint32_t addr = DS1624_Idx2Addr(idx); + if ((config & 1) == 1) { + config &= ~(DS1621_CFG_DONE|DS1621_CFG_1SHOT); + I2cWrite8(addr, DS1624_CONF_REGISTER, config); // 1shot off + delay(10); // by spec after writing + AddLog_P2(LOG_LEVEL_ERROR, "%s addr %x is reset, reconfig: %x", ds1624_sns[idx].name, addr, config); + } + I2cValidRead(addr, DS1624_START_REGISTER, 1); +} + void DS1624_HotPlugUp(uint32_t idx) { uint32_t addr = DS1624_Idx2Addr(idx); @@ -75,12 +91,9 @@ void DS1624_HotPlugUp(uint32_t idx) I2cSetActiveFound(addr, ds1624_sns[idx].name); ds1624_sns[idx].valid = true; - if ((config & 1) == 1) { - config &= 0xfe; - I2cWrite8(addr, DS1624_CONF_REGISTER, config); // 1show off - delay(10); // by spec after writing - } - I2cValidRead(addr, DS1624_START_REGISTER, 1); // FIXME 0 must read, but 0 isn't work for tasmota + ds1624_sns[idx].errcnt = 0; + ds1624_sns[idx].misscnt = 0; + DS1624_Restart(config,idx); AddLog_P2(LOG_LEVEL_INFO, "Hot Plug %s addr %x config: %x", ds1624_sns[idx].name, addr, config); } } @@ -98,16 +111,37 @@ bool DS1624GetTemp(float *value, int idx) { uint32_t addr = DS1624_Idx2Addr(idx); + uint8_t config; + if (!I2cValidRead8(&config, addr, DS1624_CONF_REGISTER)) { + ds1624_sns[idx].misscnt++; + AddLog_P2(LOG_LEVEL_INFO, "%s device missing (errors: %i)", ds1624_sns[idx].name, ds1624_sns[idx].misscnt); + return false; + } + ds1624_sns[idx].misscnt=0; + if (config & (DS1621_CFG_1SHOT|DS1621_CFG_DONE)) { + ds1624_sns[idx].errcnt++; + AddLog_P2(LOG_LEVEL_INFO, "%s config error, restart... (errors: %i)", ds1624_sns[idx].name, ds1624_sns[idx].errcnt); + DS1624_Restart(config, idx); + return false; + } + uint16_t t; if (!I2cValidRead16(&t, DS1624_Idx2Addr(idx), DS1624_TEMP_REGISTER)) { return false; } - *value = ((float)(int8_t)(t>>8)) + ((t>>4)&0xf)*0.0625; - if (ds1624_sns[idx].type == DS1624_TYPE_DS1621) { // Higher resolution + if (ds1624_sns[idx].type == DS1624_TYPE_DS1624) { + *value = ((float)(int8_t)(t>>8)) + ((t>>4)&0xf)*0.0625; + } else { //type == DS1624_TYPE_DS1621 + // Datasheet for ds1621 is wrong for high resolution, real is: + *value = ((float)(int8_t)(t>>8)); uint8_t remain; if (!I2cValidRead8(&remain, addr, DS1621_COUNTER_REGISTER)) { return true; } uint8_t perc; if (!I2cValidRead8(&perc, addr, DS1621_SLOPE_REGISTER)) { return true; } - *value += ((float)perc - (float)remain)/((float)perc) - 0.25; + float fix=(float)(perc - remain)/(float)perc; + *value+=fix; } + ds1624_sns[idx].errcnt=0; + config &= ~(DS1621_CFG_DONE); + I2cWrite8(addr, DS1624_CONF_REGISTER, config); return true; } @@ -120,9 +154,11 @@ void DS1624HotPlugScan(void) if (I2cActive(addr) && !ds1624_sns[idx].valid) { continue; // is busy by another driver } - if (!I2cValidRead16(&t, DS1624_Idx2Addr(idx), DS1624_TEMP_REGISTER)) { - DS1624_HotPlugDown(idx); - continue; + if (ds1624_sns[idx].valid) { + if ((ds1624_sns[idx].misscnt>2)||(ds1624_sns[idx].errcnt>2)) { + DS1624_HotPlugDown(idx); + continue; + } } DS1624_HotPlugUp(idx); }