Merge branch 'development' into release

This commit is contained in:
Theo Arends 2019-12-17 14:24:30 +01:00
commit 1b315134da
68 changed files with 1893 additions and 4615 deletions

View File

@ -47,10 +47,28 @@ The following binary downloads have been compiled with ESP8266/Arduino library c
## Changelog
### Version 7.1.2 Betty
### Version 7.2.0 Constance
- Fix lost functionality of GPIO9 and GPIO10 on some devices (#7080)
- Fix Zigbee uses Hardware Serial if GPIO 1/3 or GPIO 13/15 and SerialLog 0 (#7071)
- Fix WS2812 power control (#7090)
- Change light color schemes 2, 3 and 4 from color wheel to Hue driven with user Saturation control
- Change log buffer size from 520 to 700 characters accomodating full rule text (#7110)
- Change Exception reporting removing exception details from ``Status 1`` and consolidated in ``Status 12`` if available
- Change HTTP CORS from command ``SetOption73 0/1`` to ``Cors <cors_domain>`` allowing user control of specific CORS domain by Shantur Rathore (#7066)
- Change GUI Shutter button text to Up and Down Arrows based on PR by Xavier Muller (#7166)
- Change amount of supported DHT sensors from 3 to 4 by Xavier Muller (#7167)
- Change some Settings locations freeing up space for future single char allowing variable length text
- Fix flashing H801 led at boot by Stefan Hadinger (#7165, #649)
- Fix duplicated ``Backlog`` when using Event inside a Backlog by Adrian Scillato (#7178, #7147)
- Fix Gui Timer when using a negative zero offset of -00:00 by Peter Ooms (#7174)
- Add command ``SerialConfig 0..23`` or ``SerialConfig 8N1`` to select Serial Config based in PR by Luis Teixeira (#7108)
- Add command ``Sensor34 9 <weight code>`` to set minimum delta to trigger JSON message by @tobox (#7188)
- Add rule var ``%topic%`` by Adrian Scillato (#5522)
- Add rule triggers ``tele-wifi1#xxx`` by Adrian Scillato (#7093)
- Add SML bus decoder syntax support for byte order by Gerhard Mutz (#7112)
- Add experimental support for stepper motor shutter control by Stefan Bode
- Add optional USE_MQTT_TLS to tasmota-minimal.bin by Bohdan Kmit (#7115)
- Add save call stack in RTC memory in case of crash, command ``Status 12`` to dump the stack by Stefan Hadinger
- Add Home Assistant force update by Frederico Leoni (#7140, #7074)
- Add Wifi Signal Strength in dBm in addition to RSSI Wifi Experience by Andreas Schultz (#7145)
- Add Yaw, Pitch and Roll support for MPU6050 by Philip Barclay (#7058)
- Add reporting of raw weight to JSON from HX711 to overcome auto-tare functionality by @tobox (#7171)
- Add Zigbee support for Xiaomi Aqara Vibration Sensor and Presence Sensor by Stefan Hadinger
- Add Shutter functions ramp up/down and MQTT reporting by Stefan Bode
- Add fallback functionality from version 8.x

View File

@ -32,8 +32,18 @@
#define DISABLE_RESTORE_BUTTON 1 // [Default 0] Set to 1 to disable the "restore defaults" button in the web ui.
// These values normally don't need adjustment
#define MULTICAST_PORT 3671 // [Default 3671]
#ifndef MULTICAST_IP
#define MULTICAST_IP IPAddress(224, 0, 23, 12) // [Default IPAddress(224, 0, 23, 12)]
#else
#warning USING CUSTOM MULTICAST_IP
#endif
#ifndef MULTICAST_PORT
#define MULTICAST_PORT 3671 // [Default 3671]
#else
#warning USING CUSTOM MULTICAST_PORT
#endif
#define SEND_CHECKSUM 0
// Uncomment to enable printing out debug messages.

View File

@ -2,6 +2,56 @@
## Released
### 7.2.0 20191221
- Release
### 7.1.2.6 20191214
- Change some more Settings locations freeing up space for future single char allowing variable length text
- Add Zigbee send automatic ZigbeeRead after sending a command
- Add Zigbee improving Occupancy:false detection for Aqara sensor
- Add fallback functionality from version 8.x
### 7.1.2.5 20191213
- Change some Settings locations freeing up space for future single char allowing variable length text
- Add Zigbee support for Xiaomi Aqara Vibration Sensor and Presence Sensor by Stefan Hadinger
- Add Shutter functions ramp up/down and MQTT reporting by Stefan Bode
### 7.1.2.4 20191209
- Change HTTP CORS from command ``SetOption73 0/1`` to ``Cors <cors_domain>`` allowing user control of specific CORS domain by Shantur Rathore (#7066)
- Change GUI Shutter button text to Up and Down Arrows based on PR by Xavier Muller (#7166)
- Change amount of supported DHT sensors from 3 to 4 by Xavier Muller (#7167)
- Revert removal of exception details from MQTT info on restart
- Add Wifi Signal Strength in dBm in addition to RSSI Wifi Experience by Andreas Schultz (#7145)
- Add Yaw, Pitch and Roll support for MPU6050 by Philip Barclay (#7058)
- Add reporting of raw weight to JSON from HX711 to overcome auto-tare functionality by @tobox (#7171)
- Add command ``Sensor34 9 <weight code>`` to set minimum delta to trigger JSON message by @tobox (#7188)
- Fix flashing H801 led at boot by Stefan Hadinger (#7165, #649)
- Fix duplicated ``Backlog`` when using Event inside a Backlog by Adrian Scillato (#7178, #7147)
- Fix Gui Timer when using a negative zero offset of -00:00 by Peter Ooms (#7174)
### 7.1.2.3 20191208
- Change Exception reporting removing exception details from both MQTT info and ``Status 1``. Now consolidated in ``Status 12`` if available.
### 7.1.2.2 20191206
- Remove rule trigger ``tele_power1#state`` due to compatibility
- Add command ``SerialConfig 0..23`` or ``SerialConfig 8N1`` to select Serial Config based in PR by Luis Teixeira (#7108)
- Add save call stack in RTC memory in case of crash, command ``Status 12`` to dump the stack by Stefan Hadinger
- Add Home Assistant force update by Frederico Leoni (#7140, #7074)
### 7.1.2.1 20191206
- Add SML bus decoder syntax support for byte order by Gerhard Mutz (#7112)
- Add rule var ``%topic%`` by Adrian Scillato (#5522)
- Add rule triggers ``tele_power1#state`` and multiple ``tele-wifi1#xxx`` by Adrian Scillato (#7093)
- Add experimental support for stepper motor shutter control by Stefan Bode
- Add optional USE_MQTT_TLS to tasmota-minimal.bin by Bohdan Kmit (#7115)
### 7.1.2 20191206
- Maintenance Release

View File

@ -134,6 +134,7 @@
#define D_JSON_SELECTED "selected"
#define D_JSON_SERIALRECEIVED "SerialReceived"
#define D_JSON_SET "Set"
#define D_JSON_SIGNAL "Signal"
#define D_JSON_SSID "SSId"
#define D_JSON_STARTDST "StartDST" // Start Daylight Savings Time
#define D_JSON_STARTED "Started"
@ -208,6 +209,7 @@
#define D_STATUS9_MARGIN "PTH"
#define D_STATUS10_SENSOR "SNS"
#define D_STATUS11_STATUS "STS"
#define D_STATUS12_STATUS "STK"
#define D_CMND_STATE "State"
#define D_CMND_POWER "Power"
#define D_CMND_FANSPEED "FanSpeed"
@ -285,6 +287,7 @@
#define D_CMND_SERIALSEND "SerialSend"
#define D_CMND_SERIALDELIMITER "SerialDelimiter"
#define D_CMND_BAUDRATE "Baudrate"
#define D_CMND_SERIALCONFIG "SerialConfig"
#define D_CMND_TEMPLATE "Template"
#define D_JSON_NAME "NAME"
#define D_JSON_GPIO "GPIO"
@ -332,6 +335,7 @@
#define D_CMND_WEBSENSOR "WebSensor"
#define D_CMND_EMULATION "Emulation"
#define D_CMND_SENDMAIL "Sendmail"
#define D_CMND_CORS "CORS"
// Commands xdrv_03_energy.ino
#define D_CMND_POWERLOW "PowerLow"
@ -490,6 +494,24 @@
#define D_JSON_MOTOR_MIS "setMIS"
#endif
// Commands xdrv_27_Shutter.ino
#ifdef USE_SHUTTER
#define D_PRFX_SHUTTER "Shutter"
#define D_CMND_SHUTTER_OPEN "Open"
#define D_CMND_SHUTTER_CLOSE "Close"
#define D_CMND_SHUTTER_STOP "Stop"
#define D_CMND_SHUTTER_POSITION "Position"
#define D_CMND_SHUTTER_OPENTIME "OpenDuration"
#define D_CMND_SHUTTER_CLOSETIME "CloseDuration"
#define D_CMND_SHUTTER_RELAY "Relay"
#define D_CMND_SHUTTER_SETHALFWAY "SetHalfway"
#define D_CMND_SHUTTER_SETCLOSE "SetClose"
#define D_CMND_SHUTTER_INVERT "Invert"
#define D_CMND_SHUTTER_CLIBRATION "Calibration"
#define D_CMND_SHUTTER_MOTORDELAY "MotorDelay"
#define D_CMND_SHUTTER_FREQUENCY "Frequency"
#endif
/********************************************************************************************/
// Log message prefix

View File

@ -28,7 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v6.5.0.8
* Updated until v7.1.2.4
\*********************************************************************/
//#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -71,6 +71,7 @@
#define D_COLDLIGHT "Хладна"
#define D_COMMAND "Команда"
#define D_CONNECTED "Свързан"
#define D_CORS_DOMAIN "CORS домейн"
#define D_COUNT "Брой"
#define D_COUNTER "Брояч"
#define D_CURRENT "Ток" // As in Voltage and Current
@ -113,7 +114,7 @@
#define D_LWT "LWT"
#define D_MODULE "Модул"
#define D_MQTT "MQTT"
#define D_MULTI_PRESS "множествено натискане"
#define D_MULTI_PRESS "неколкократно натискане"
#define D_NOISE "Шум"
#define D_NONE "Няма"
#define D_OFF "Изкл."
@ -284,10 +285,10 @@
#define D_LOGGING_PARAMETERS "Параметри на лога"
#define D_SERIAL_LOG_LEVEL "Степен на серийния лог"
#define D_MQTT_LOG_LEVEL "Mqtt log level"
#define D_MQTT_LOG_LEVEL "Степен на MQTT лога"
#define D_WEB_LOG_LEVEL "Степен на уеб лога"
#define D_SYS_LOG_LEVEL "Степен на системния лог"
#define D_MORE_DEBUG "Още дебъгване"
#define D_MORE_DEBUG "Допълнителна debug информация"
#define D_SYSLOG_HOST "Хост на системния лог"
#define D_SYSLOG_PORT "Порт на системния лог"
#define D_TELEMETRY_PERIOD "Период на телеметрия"
@ -381,7 +382,7 @@
#define D_HUE "Hue"
#define D_HUE_BRIDGE_SETUP "Настройка на Hue bridge"
#define D_HUE_API_NOT_IMPLEMENTED "Hue API не е внедрено"
#define D_HUE_API_NOT_IMPLEMENTED "Hue API не е внедрен"
#define D_HUE_API "Hue API"
#define D_HUE_POST_ARGS "Hue POST аргументи"
#define D_3_RESPONSE_PACKETS_SENT "Изпратени са 3 пакета за отговор"
@ -443,17 +444,17 @@
#define D_ENERGY_TOTAL "Използвана енергия общо"
// xdrv_27_shutter.ino
#define D_OPEN "Open"
#define D_CLOSE "Close"
#define D_DOMOTICZ_SHUTTER "Shutter"
#define D_OPEN "Отворена"
#define D_CLOSE "Затворена"
#define D_DOMOTICZ_SHUTTER "Щора"
// xdrv_28_pcf8574.ino
#define D_CONFIGURE_PCF8574 "Configure PCF8574"
#define D_PCF8574_PARAMETERS "PCF8574 parameters"
#define D_INVERT_PORTS "Invert Ports"
#define D_DEVICE "Device"
#define D_DEVICE_INPUT "Input"
#define D_DEVICE_OUTPUT "Output"
#define D_CONFIGURE_PCF8574 "Конфигуриране на PCF8574"
#define D_PCF8574_PARAMETERS "PCF8574 параметри"
#define D_INVERT_PORTS "Обърни портовете"
#define D_DEVICE "Устройство"
#define D_DEVICE_INPUT "Вход"
#define D_DEVICE_OUTPUT "Изход"
// xsns_05_ds18b20.ino
#define D_SENSOR_BUSY "Датчикът DS18x20 е зает"
@ -674,27 +675,27 @@
#define D_UNIT_ANGLE "°"
//SOLAXX1
#define D_PV1_VOLTAGE "PV1 Voltage"
#define D_PV1_CURRENT "PV1 Current"
#define D_PV1_POWER "PV1 Power"
#define D_PV2_VOLTAGE "PV2 Voltage"
#define D_PV2_CURRENT "PV2 Current"
#define D_PV2_POWER "PV2 Power"
#define D_SOLAR_POWER "Solar Power"
#define D_INVERTER_POWER "Inverter Power"
#define D_STATUS "Status"
#define D_WAITING "Waiting"
#define D_CHECKING "Checking"
#define D_WORKING "Working"
#define D_FAILURE "Failure"
#define D_SOLAX_ERROR_0 "No Error Code"
#define D_SOLAX_ERROR_1 "Grid Lost Fault"
#define D_SOLAX_ERROR_2 "Grid Voltage Fault"
#define D_SOLAX_ERROR_3 "Grid Frequency Fault"
#define D_SOLAX_ERROR_4 "Pv Voltage Fault"
#define D_SOLAX_ERROR_5 "Isolation Fault"
#define D_SOLAX_ERROR_6 "Over Temperature Fault"
#define D_SOLAX_ERROR_7 "Fan Fault"
#define D_SOLAX_ERROR_8 "Other Device Fault"
#define D_PV1_VOLTAGE "Напрежение на PV1"
#define D_PV1_CURRENT "Ток на PV1"
#define D_PV1_POWER "Мощност на PV1"
#define D_PV2_VOLTAGE "Напрежение на PV2"
#define D_PV2_CURRENT "Ток на PV2"
#define D_PV2_POWER "Мощност на PV2"
#define D_SOLAR_POWER "Слънчева мощност"
#define D_INVERTER_POWER "Мощност на инвертера"
#define D_STATUS "Състояние"
#define D_WAITING "Очакване"
#define D_CHECKING "Проверка"
#define D_WORKING "Работи"
#define D_FAILURE "Грешка"
#define D_SOLAX_ERROR_0 "Грешка - няма код"
#define D_SOLAX_ERROR_1 "Грешка - загуба на мрежата"
#define D_SOLAX_ERROR_2 "Грешка - мрежово напрежение"
#define D_SOLAX_ERROR_3 "Грешка - мрежова честота"
#define D_SOLAX_ERROR_4 "Грешка - напрежение на Pv"
#define D_SOLAX_ERROR_5 "Грешка - проблем с изолацията"
#define D_SOLAX_ERROR_6 "Грешка - прегряване"
#define D_SOLAX_ERROR_7 "Грешка - вентилатор"
#define D_SOLAX_ERROR_8 "Грешка - друго оборудване"
#endif // _LANGUAGE_BG_BG_H_

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Studené světlo"
#define D_COMMAND "Příkaz"
#define D_CONNECTED "...připojeno"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Počítej"
#define D_COUNTER "Počítadlo"
#define D_CURRENT "Proud" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "kalt"
#define D_COMMAND "Befehl"
#define D_CONNECTED "verbunden"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "zählen"
#define D_COUNTER "Zähler"
#define D_CURRENT "Strom" // As in Voltage and Current

View File

@ -72,6 +72,7 @@
#define D_COMMAND "Εντολή"
#define D_CONNECTED "Συνδεδεμένο"
#define D_COUNT "Μέτρηση"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNTER "Μετρητής"
#define D_CURRENT "Ένταση" // As in Voltage and Current
#define D_DATA "Δεδομένα"

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Cold"
#define D_COMMAND "Command"
#define D_CONNECTED "Connected"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Count"
#define D_COUNTER "Counter"
#define D_CURRENT "Current" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Fría"
#define D_COMMAND "Comando"
#define D_CONNECTED "Conectado"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Conteo"
#define D_COUNTER "Contador"
#define D_CURRENT "Corriente" // As in Voltage and Current

View File

@ -28,7 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v6.6.0.15
* Updated until v7.1.2.4
\*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -71,6 +71,7 @@
#define D_COLDLIGHT "Froid"
#define D_COMMAND "Commande"
#define D_CONNECTED "Connecté"
#define D_CORS_DOMAIN "Domaine CORS"
#define D_COUNT "Compte"
#define D_COUNTER "Compteur"
#define D_CURRENT "Courant" // As in Voltage and Current
@ -280,7 +281,7 @@
#define D_MQTT_PARAMETERS "Paramètres MQTT"
#define D_CLIENT "Client"
#define D_FULL_TOPIC "topic complet"
#define D_FULL_TOPIC "Topic complet"
#define D_LOGGING_PARAMETERS "Paramètres du journal"
#define D_SERIAL_LOG_LEVEL "Niveau de journalisation série"
@ -687,7 +688,7 @@
#define D_CHECKING "En test"
#define D_WORKING "En marche"
#define D_FAILURE "Défault"
#define D_SOLAX_ERROR_0 "Aucun Code d'erreur"
#define D_SOLAX_ERROR_0 "Aucun code d'erreur"
#define D_SOLAX_ERROR_1 "Défaut Perte de réseau"
#define D_SOLAX_ERROR_2 "Défaut Tension réseau"
#define D_SOLAX_ERROR_3 "Défaut Fréquence réseau"

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "אור קר"
#define D_COMMAND "פקודה"
#define D_CONNECTED "מחובר"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "סופר"
#define D_COUNTER "מונה"
#define D_CURRENT "נוכחי" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Hideg fény"
#define D_COMMAND "Parancs"
#define D_CONNECTED "Csatlakoztatva"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Szám"
#define D_COUNTER "Számláló"
#define D_CURRENT "Áramerősség" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Fredda"
#define D_COMMAND "Comando"
#define D_CONNECTED "Connesso"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Conteggio"
#define D_COUNTER "Contatore"
#define D_CURRENT "Corrente" // As in Voltage and Current

View File

@ -72,6 +72,7 @@
#define D_COMMAND "커맨드"
#define D_CONNECTED "연결됨"
#define D_COUNT "횟수"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNTER "Counter"
#define D_CURRENT "전류" // As in Voltage and Current
#define D_DATA "Data"

View File

@ -72,6 +72,7 @@
#define D_COMMAND "Opdracht"
#define D_CONNECTED "Verbonden"
#define D_COUNT "Aantal"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNTER "Teller"
#define D_CURRENT "Stroom" // As in Voltage and Current
#define D_DATA "Data"

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Zimny"
#define D_COMMAND "Komenda"
#define D_CONNECTED "Połączony"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Licz"
#define D_COUNTER "Licznik"
#define D_CURRENT "Prąd" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Luz fria"
#define D_COMMAND "Comando"
#define D_CONNECTED "Ligado"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Contagem"
#define D_COUNTER "Contador"
#define D_CURRENT "Corrente" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Luz Fria"
#define D_COMMAND "Comando"
#define D_CONNECTED "Ligado"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Contagem"
#define D_COUNTER "Contador"
#define D_CURRENT "Corrente" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Холодный"
#define D_COMMAND "Команда"
#define D_CONNECTED "Соединен"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Подсчет"
#define D_COUNTER "Счетчик"
#define D_CURRENT "Ток" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Studené svetlo"
#define D_COMMAND "Príkaz"
#define D_CONNECTED "...pripojené"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Počítaj"
#define D_COUNTER "Počítadlo"
#define D_CURRENT "Prúd" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Kallt"
#define D_COMMAND "Kommando"
#define D_CONNECTED "Ansluten"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Räkna"
#define D_COUNTER "Räknare"
#define D_CURRENT "Ström" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Soğuk"
#define D_COMMAND "Komut"
#define D_CONNECTED "Bağlandı"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Sayı"
#define D_COUNTER "Sayaç"
#define D_CURRENT "Current" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "Холодний"
#define D_COMMAND "Команда"
#define D_CONNECTED "Під'єднано"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "Розмір"
#define D_COUNTER "Лічильник"
#define D_CURRENT "Струм" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "冷"
#define D_COMMAND "命令:"
#define D_CONNECTED "已连接"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "数量:"
#define D_COUNTER "计数器"
#define D_CURRENT "电流" // As in Voltage and Current

View File

@ -71,6 +71,7 @@
#define D_COLDLIGHT "冷"
#define D_COMMAND "命令:"
#define D_CONNECTED "已連接"
#define D_CORS_DOMAIN "CORS Domain"
#define D_COUNT "數量:"
#define D_COUNTER "Counter"
#define D_CURRENT "電流" // As in Voltage and Current

View File

@ -132,6 +132,7 @@
#define WEB_PASSWORD "" // [WebPassword] Web server Admin mode Password for WEB_USERNAME (empty string = Disable)
#define FRIENDLY_NAME "Tasmota" // [FriendlyName] Friendlyname up to 32 characters used by webpages and Alexa
#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 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
@ -219,7 +220,7 @@
#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_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 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
@ -284,14 +285,14 @@
#define DOMOTICZ_OUT_TOPIC "domoticz/out" // Domoticz Output Topic
// -- MQTT - Home Assistant Discovery -------------
#define USE_HOME_ASSISTANT // Enable Home Assistant Discovery Support (+7k code)
#define USE_HOME_ASSISTANT // Enable Home Assistant Discovery Support (+4.1k code, +6 bytes mem)
#define HOME_ASSISTANT_DISCOVERY_PREFIX "homeassistant" // Home Assistant discovery prefix
// -- MQTT - TLS - AWS IoT ------------------------
// Using TLS starting with version v6.5.0.16 compilation will only work using Core 2.4.2 and 2.5.2. No longer supported: 2.3.0
//#define USE_MQTT_TLS // Use TLS for MQTT connection (+34.5k code, +7.0k mem and +4.8k additional during connection handshake)
// #define USE_MQTT_TLS_CA_CERT // Force full CA validation instead of fingerprints, slower, but simpler to use. (+2.2k code, +1.9k mem during connection handshake)
// This includes the LetsEncrypt CA in tasmota_ca.ino for verifying server certificates
// This includes the LetsEncrypt CA in tasmota_ca.ino for verifying server certificates
// #define USE_MQTT_TLS_FORCE_EC_CIPHER // Force Elliptic Curve cipher (higher security) required by some servers (automatically enabled with USE_MQTT_AWS_IOT) (+11.4k code, +0.4k mem)
// #define USE_MQTT_AWS_IOT // Enable MQTT for AWS IoT - requires a private key (+11.9k code, +0.4k mem)
// Note: you need to generate a private key + certificate per device and update 'tasmota/tasmota_aws_iot.cpp'

View File

@ -86,7 +86,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu
uint32_t energy_weekend : 1; // bit 20 (v6.6.0.8) - CMND_TARIFF
uint32_t dds2382_model : 1; // bit 21 (v6.6.0.14) - SetOption71 - Select different Modbus registers for Active Energy (#6531)
uint32_t hardware_energy_total : 1; // bit 22 (v6.6.0.15) - SetOption72 - Enable hardware energy total counter as reference (#6561)
uint32_t cors_enabled : 1; // bit 23 (v7.0.0.1) - SetOption73 - Enable HTTP CORS
uint32_t ex_cors_enabled : 1; // bit 23 (v7.0.0.1) - SetOption73 - Enable HTTP CORS
uint32_t ds18x20_internal_pullup : 1; // bit 24 (v7.0.0.1) - SetOption74 - Enable internal pullup for single DS18x20 sensor
uint32_t grouptopic_mode : 1; // bit 25 (v7.0.0.1) - SetOption75 - GroupTopic replaces %topic% (0) or fixed topic cmnd/grouptopic (1)
uint32_t bootcount_update : 1; // bit 26 (v7.0.0.4) - SetOption76 - Enable incrementing bootcount when deepsleep is enabled
@ -248,42 +248,54 @@ struct SYSCFG {
SysBitfield flag; // 010
int16_t save_data; // 014
int8_t timezone; // 016
// Start of single char array Settings.text
char ota_url[101]; // 017
char mqtt_prefix[3][11]; // 07C
uint8_t ex_baudrate; // 09D - Free since 6.6.0.9
uint8_t seriallog_level; // 09E
uint8_t sta_config; // 09F
uint8_t sta_active; // 0A0
uint8_t ex_seriallog_level; // 09E
uint8_t ex_sta_config; // 09F
uint8_t ex_sta_active; // 0A0
char sta_ssid[2][33]; // 0A1 - Keep together with sta_pwd as being copied as one chunck with reset 5
char sta_pwd[2][65]; // 0E3 - Keep together with sta_ssid as being copied as one chunck with reset 5
char hostname[33]; // 165
char syslog_host[33]; // 186
uint8_t rule_stop; // 1A7
uint16_t syslog_port; // 1A8
uint8_t syslog_level; // 1AA
uint8_t webserver; // 1AB
uint8_t weblog_level; // 1AC
uint8_t mqtt_fingerprint[2][20]; // 1AD
uint8_t adc_param_type; // 1D5
uint8_t free_1d6[10]; // 1D6
uint8_t ex_rule_stop; // 1A7
uint16_t ex_syslog_port; // 1A8
uint8_t ex_syslog_level; // 1AA
uint8_t ex_webserver; // 1AB
uint8_t ex_weblog_level; // 1AC
uint8_t ex_mqtt_fingerprint[2][20]; // 1AD
uint8_t ex_adc_param_type; // 1D5
SysBitfield4 flag4; // 1E0
uint8_t ex_free_1d6[10]; // 1D6
uint8_t free_1e4; // 1E4
// End of single char array of 456 chars max (phase 3)
SysBitfield4 ex_flag4; // 1E0
uint8_t ex_serial_config; // 1E4
uint8_t ex_wifi_output_power; // 1E5
uint8_t ex_shutter_accuracy; // 1E6
uint8_t ex_mqttlog_level; // 1E7
uint8_t ex_sps30_inuse_hours; // 1E8
uint8_t wifi_output_power; // 1E5
uint8_t shutter_accuracy; // 1E6
uint8_t mqttlog_level; // 1E7
uint8_t sps30_inuse_hours; // 1E8
char mqtt_host[33]; // 1E9 - Keep together with below as being copied as one chunck with reset 6
uint16_t mqtt_port; // 20A - Keep together
uint16_t ex_mqtt_port; // 20A - Keep together
char mqtt_client[33]; // 20C - Keep together
char mqtt_user[33]; // 22D - Keep together
char mqtt_pwd[33]; // 24E - Keep together
char mqtt_topic[33]; // 26F - Keep together with above items as being copied as one chunck with reset 6
char button_topic[33]; // 290
char mqtt_grptopic[33]; // 2B1
// Optional end of single char array of 698 chars max (phase 5)
uint8_t display_model; // 2D2
uint8_t display_mode; // 2D3
uint8_t display_refresh; // 2D4
@ -304,7 +316,9 @@ struct SYSCFG {
int16_t toffset[2]; // 30E
uint8_t display_font; // 312
char state_text[4][11]; // 313
uint8_t ex_energy_power_delta; // 33F - Free since 6.6.0.20
uint16_t domoticz_update_timer; // 340
uint16_t pwm_range; // 342
unsigned long domoticz_relay_idx[MAX_DOMOTICZ_IDX]; // 344
@ -340,7 +354,7 @@ struct SYSCFG {
char friendlyname[MAX_FRIENDLYNAMES][33]; // 3AC
char switch_topic[33]; // 430
char serial_delimiter; // 451
uint8_t ex_sbaudrate; // 452 - Free since 6.6.0.9
uint8_t seriallog_level; // 452
uint8_t sleep; // 453
uint16_t domoticz_switch_idx[MAX_DOMOTICZ_IDX]; // 454
uint16_t domoticz_sensor_idx[MAX_DOMOTICZ_SNS_IDX]; // 45C
@ -400,7 +414,6 @@ struct SYSCFG {
uint16_t baudrate; // 778
uint16_t sbaudrate; // 77A
EnergyUsage energy_usage; // 77C
// uint32_t drivers[3]; // 794 - 6.5.0.12 replaced by below three entries
uint32_t adc_param1; // 794
uint32_t adc_param2; // 798
int adc_param3; // 79C
@ -416,7 +429,9 @@ struct SYSCFG {
unsigned long energy_frequency_calibration; // 7C8 also used by HX711 to save last weight
uint16_t web_refresh; // 7CC
char mems[MAX_RULE_MEMS][10]; // 7CE
char rules[MAX_RULE_SETS][MAX_RULE_SIZE]; // 800 uses 512 bytes in v5.12.0m, 3 x 512 bytes in v5.14.0b
TuyaFnidDpidMap tuya_fnid_map[MAX_TUYA_FUNCTIONS]; // E00 32 bytes
uint16_t ina226_r_shunt[4]; // E20
uint16_t ina226_i_fs[4]; // E28
@ -435,12 +450,27 @@ struct SYSCFG {
uint16_t energy_power_delta; // E98
uint8_t shutter_motordelay[MAX_SHUTTERS]; // E9A
int8_t temp_comp; // E9E
uint8_t free_e9f[1]; // E9F
uint8_t weight_change; // E9F
uint8_t web_color2[2][3]; // EA0 - Needs to be on integer / 3 distance from web_color
char cors_domain[33]; // EA6
uint8_t sta_config; // EC7
uint8_t sta_active; // EC8
uint8_t rule_stop; // EC9
uint16_t syslog_port; // ECA
uint8_t syslog_level; // ECC
uint8_t webserver; // ECD
uint8_t weblog_level; // ECE
uint8_t mqtt_fingerprint[2][20]; // ECF
uint8_t adc_param_type; // EF7
SysBitfield4 flag4; // EF8
uint16_t mqtt_port; // EFC
uint8_t serial_config; // EFE
uint8_t wifi_output_power; // EFF
uint8_t shutter_accuracy; // F00
uint8_t mqttlog_level; // F01
uint8_t sps30_inuse_hours; // F02
uint8_t free_ea4[326]; // EA6
uint8_t free_f03[233]; // F03
uint32_t i2c_drivers[3]; // FEC I2cDriver
uint32_t cfg_timestamp; // FF8

View File

@ -140,6 +140,10 @@
#ifndef DEFAULT_LIGHT_COMPONENT
#define DEFAULT_LIGHT_COMPONENT 255
#endif
#ifndef CORS_ENABLED_ALL
#define CORS_ENABLED_ALL "*"
#endif
enum WebColors {
COL_TEXT, COL_BACKGROUND, COL_FORM,
@ -156,6 +160,23 @@ const char kWebColors[] PROGMEM =
COLOR_BUTTON_TEXT "|" COLOR_BUTTON "|" COLOR_BUTTON_HOVER "|" COLOR_BUTTON_RESET "|" COLOR_BUTTON_RESET_HOVER "|" COLOR_BUTTON_SAVE "|" COLOR_BUTTON_SAVE_HOVER "|"
COLOR_TIMER_TAB_TEXT "|" COLOR_TIMER_TAB_BACKGROUND "|" COLOR_TITLE_TEXT;
enum TasmotaSerialConfig {
TS_SERIAL_5N1, TS_SERIAL_6N1, TS_SERIAL_7N1, TS_SERIAL_8N1,
TS_SERIAL_5N2, TS_SERIAL_6N2, TS_SERIAL_7N2, TS_SERIAL_8N2,
TS_SERIAL_5E1, TS_SERIAL_6E1, TS_SERIAL_7E1, TS_SERIAL_8E1,
TS_SERIAL_5E2, TS_SERIAL_6E2, TS_SERIAL_7E2, TS_SERIAL_8E2,
TS_SERIAL_5O1, TS_SERIAL_6O1, TS_SERIAL_7O1, TS_SERIAL_8O1,
TS_SERIAL_5O2, TS_SERIAL_6O2, TS_SERIAL_7O2, TS_SERIAL_8O2 };
const uint8_t kTasmotaSerialConfig[] PROGMEM = {
SERIAL_5N1, SERIAL_6N1, SERIAL_7N1, SERIAL_8N1,
SERIAL_5N2, SERIAL_6N2, SERIAL_7N2, SERIAL_8N2,
SERIAL_5E1, SERIAL_6E1, SERIAL_7E1, SERIAL_8E1,
SERIAL_5E2, SERIAL_6E2, SERIAL_7E2, SERIAL_8E2,
SERIAL_5O1, SERIAL_6O1, SERIAL_7O1, SERIAL_8O1,
SERIAL_5O2, SERIAL_6O2, SERIAL_7O2, SERIAL_8O2
};
/*********************************************************************************************\
* RTC memory
\*********************************************************************************************/
@ -421,6 +442,197 @@ void UpdateQuickPowerCycle(bool update)
}
}
/*********************************************************************************************\
* Config Settings.text char array support
\*********************************************************************************************/
char aws_mqtt_host[66];
char aws_mqtt_user[1] { 0 };
const uint32_t settings_text_size = 457; // Settings.flag4 (1E0) - Settings.ota_url (017)
uint32_t GetSettingsTextLen(void)
{
char* position = Settings.ota_url;
for (uint32_t size = 0; size < SET_MAX; size++) {
while (*position++ != '\0') { }
}
return position - Settings.ota_url;
}
bool SettingsUpdateText(uint32_t index, const char* replace_me)
{
if (index >= SET_MAX) {
return false; // Setting not supported - internal error
}
// Make a copy first in case we use source from Settings.text
uint32_t replace_len = strlen(replace_me);
char replace[replace_len +1];
memcpy(replace, replace_me, sizeof(replace));
if (Settings.version < 0x08000000) {
uint32_t idx = 0;
switch (index) {
case SET_OTAURL: strlcpy(Settings.ota_url, replace, sizeof(Settings.ota_url)); break;
case SET_MQTTPREFIX3: idx++;
case SET_MQTTPREFIX2: idx++;
case SET_MQTTPREFIX1: strlcpy(Settings.mqtt_prefix[idx], replace, sizeof(Settings.mqtt_prefix[idx])); break;
case SET_STASSID2: idx++;
case SET_STASSID1: strlcpy(Settings.sta_ssid[idx], replace, sizeof(Settings.sta_ssid[idx])); break;
case SET_STAPWD2: idx++;
case SET_STAPWD1: strlcpy(Settings.sta_pwd[idx], replace, sizeof(Settings.sta_pwd[idx])); break;
case SET_HOSTNAME: strlcpy(Settings.hostname, replace, sizeof(Settings.hostname)); break;
case SET_SYSLOG_HOST: strlcpy(Settings.syslog_host, replace, sizeof(Settings.syslog_host)); break;
case SET_WEBPWD: strlcpy(Settings.web_password, replace, sizeof(Settings.web_password)); break;
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
case SET_MQTT_HOST:
if (strlen(replace) <= sizeof(Settings.mqtt_host)) {
strlcpy(Settings.mqtt_host, replace, sizeof(Settings.mqtt_host));
Settings.mqtt_user[0] = 0;
} else {
// need to split in mqtt_user first then mqtt_host
strlcpy(Settings.mqtt_user, replace, sizeof(Settings.mqtt_user));
strlcpy(Settings.mqtt_host, &replace[sizeof(Settings.mqtt_user)-1], sizeof(Settings.mqtt_host));
}
break;
case SET_MQTT_USER: break;
#else
case SET_MQTT_HOST: strlcpy(Settings.mqtt_host, replace, sizeof(Settings.mqtt_host)); break;
case SET_MQTT_USER: strlcpy(Settings.mqtt_user, replace, sizeof(Settings.mqtt_user)); break;
#endif
case SET_MQTT_CLIENT: strlcpy(Settings.mqtt_client, replace, sizeof(Settings.mqtt_client)); break;
case SET_MQTT_PWD: strlcpy(Settings.mqtt_pwd, replace, sizeof(Settings.mqtt_pwd)); break;
case SET_MQTT_FULLTOPIC: strlcpy(Settings.mqtt_fulltopic, replace, sizeof(Settings.mqtt_fulltopic)); break;
case SET_MQTT_TOPIC: strlcpy(Settings.mqtt_topic, replace, sizeof(Settings.mqtt_topic)); break;
case SET_MQTT_BUTTON_TOPIC: strlcpy(Settings.button_topic, replace, sizeof(Settings.button_topic)); break;
case SET_MQTT_SWITCH_TOPIC: strlcpy(Settings.switch_topic, replace, sizeof(Settings.switch_topic)); break;
case SET_MQTT_GRP_TOPIC: strlcpy(Settings.mqtt_grptopic, replace, sizeof(Settings.mqtt_grptopic)); break;
case SET_STATE_TXT4: idx++;
case SET_STATE_TXT3: idx++;
case SET_STATE_TXT2: idx++;
case SET_STATE_TXT1: strlcpy(Settings.state_text[idx], replace, sizeof(Settings.state_text[idx])); break;
case SET_NTPSERVER3: idx++;
case SET_NTPSERVER2: idx++;
case SET_NTPSERVER1: strlcpy(Settings.ntp_server[idx], replace, sizeof(Settings.ntp_server[idx])); break;
case SET_MEM5: idx++;
case SET_MEM4: idx++;
case SET_MEM3: idx++;
case SET_MEM2: idx++;
case SET_MEM1: strlcpy(Settings.mems[idx], replace, sizeof(Settings.mems[idx])); break;
case SET_CORS: strlcpy(Settings.cors_domain, replace, sizeof(Settings.cors_domain)); break;
case SET_FRIENDLYNAME4: idx++;
case SET_FRIENDLYNAME3: idx++;
case SET_FRIENDLYNAME2: idx++;
case SET_FRIENDLYNAME1: strlcpy(Settings.friendlyname[idx], replace, sizeof(Settings.friendlyname[idx])); break;
}
} else {
uint32_t start_pos = 0;
uint32_t end_pos = 0;
char* position = Settings.ota_url;
for (uint32_t size = 0; size < SET_MAX; size++) {
while (*position++ != '\0') { }
if (1 == index) {
start_pos = position - Settings.ota_url;
}
else if (0 == index) {
end_pos = position - Settings.ota_url -1;
}
index--;
}
uint32_t char_len = position - Settings.ota_url;
uint32_t current_len = end_pos - start_pos;
int diff = replace_len - current_len;
// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TST: start %d, end %d, len %d, current %d, replace %d, diff %d"),
// start_pos, end_pos, char_len, current_len, replace_len, diff);
int too_long = (char_len + diff) - settings_text_size;
if (too_long > 0) {
// AddLog_P2(LOG_LEVEL_INFO, PSTR("CFG: Text too long by %d char(s)"), too_long);
return false; // Replace text too long
}
if (diff != 0) {
// Shift Settings.text up or down
memmove_P(Settings.ota_url + start_pos + replace_len, Settings.ota_url + end_pos, char_len - end_pos);
}
// Replace text
memmove_P(Settings.ota_url + start_pos, replace, replace_len);
// Fill for future use
memset(Settings.ota_url + char_len + diff, 0x00, settings_text_size - char_len - diff);
}
return true;
}
char* SettingsText(uint32_t index)
{
if (index >= SET_MAX) {
return nullptr; // Setting not supported - internal error
}
char* position = Settings.ota_url;
if (Settings.version < 0x08000000) {
uint32_t idx = 0;
switch (index) {
case SET_MQTTPREFIX3: idx++;
case SET_MQTTPREFIX2: idx++;
case SET_MQTTPREFIX1: position = Settings.mqtt_prefix[idx]; break;
case SET_STASSID2: idx++;
case SET_STASSID1: position = Settings.sta_ssid[idx]; break;
case SET_STAPWD2: idx++;
case SET_STAPWD1: position = Settings.sta_pwd[idx]; break;
case SET_HOSTNAME: position = Settings.hostname; break;
case SET_SYSLOG_HOST: position = Settings.syslog_host; break;
case SET_WEBPWD: position = Settings.web_password; break;
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
case SET_MQTT_HOST:
strlcpy(aws_mqtt_host, Settings.mqtt_user, strlen(Settings.mqtt_user));
strlcpy(&aws_mqtt_host[strlen(Settings.mqtt_user)], Settings.mqtt_host, sizeof(Settings.mqtt_host));
position = aws_mqtt_host; break;
case SET_MQTT_USER: position = aws_mqtt_user; break;
#else
case SET_MQTT_HOST: position = Settings.mqtt_host; break;
case SET_MQTT_USER: position = Settings.mqtt_user; break;
#endif
case SET_MQTT_CLIENT: position = Settings.mqtt_client; break;
case SET_MQTT_PWD: position = Settings.mqtt_pwd; break;
case SET_MQTT_FULLTOPIC: position = Settings.mqtt_fulltopic; break;
case SET_MQTT_TOPIC: position = Settings.mqtt_topic; break;
case SET_MQTT_BUTTON_TOPIC: position = Settings.button_topic; break;
case SET_MQTT_SWITCH_TOPIC: position = Settings.switch_topic; break;
case SET_MQTT_GRP_TOPIC: position = Settings.mqtt_grptopic; break;
case SET_STATE_TXT4: idx++;
case SET_STATE_TXT3: idx++;
case SET_STATE_TXT2: idx++;
case SET_STATE_TXT1: position = Settings.state_text[idx]; break;
case SET_NTPSERVER3: idx++;
case SET_NTPSERVER2: idx++;
case SET_NTPSERVER1: position = Settings.ntp_server[idx]; break;
case SET_MEM5: idx++;
case SET_MEM4: idx++;
case SET_MEM3: idx++;
case SET_MEM2: idx++;
case SET_MEM1: position = Settings.mems[idx]; break;
case SET_CORS: position = Settings.cors_domain; break;
case SET_FRIENDLYNAME4: idx++;
case SET_FRIENDLYNAME3: idx++;
case SET_FRIENDLYNAME2: idx++;
case SET_FRIENDLYNAME1: position = Settings.friendlyname[idx]; break;
}
} else {
for (;index > 0; index--) {
while (*position++ != '\0') { }
}
}
return position;
}
/*********************************************************************************************\
* Config Save - Save parameters to Flash ONLY if any parameter has changed
\*********************************************************************************************/
@ -652,11 +864,11 @@ void SettingsDefaultSet2(void)
Settings.module = MODULE;
ModuleDefault(WEMOS);
// for (uint32_t i = 0; i < sizeof(Settings.my_gp); i++) { Settings.my_gp.io[i] = GPIO_NONE; }
strlcpy(Settings.friendlyname[0], FRIENDLY_NAME, sizeof(Settings.friendlyname[0]));
strlcpy(Settings.friendlyname[1], FRIENDLY_NAME"2", sizeof(Settings.friendlyname[1]));
strlcpy(Settings.friendlyname[2], FRIENDLY_NAME"3", sizeof(Settings.friendlyname[2]));
strlcpy(Settings.friendlyname[3], FRIENDLY_NAME"4", sizeof(Settings.friendlyname[3]));
strlcpy(Settings.ota_url, OTA_URL, sizeof(Settings.ota_url));
SettingsUpdateText(SET_FRIENDLYNAME1, FRIENDLY_NAME);
SettingsUpdateText(SET_FRIENDLYNAME2, FRIENDLY_NAME"2");
SettingsUpdateText(SET_FRIENDLYNAME3, FRIENDLY_NAME"3");
SettingsUpdateText(SET_FRIENDLYNAME4, FRIENDLY_NAME"4");
SettingsUpdateText(SET_OTAURL, OTA_URL);
// Power
Settings.flag.save_state = SAVE_STATE;
@ -670,6 +882,7 @@ void SettingsDefaultSet2(void)
// for (uint32_t i = 1; i < MAX_PULSETIMERS; i++) { Settings.pulse_timer[i] = 0; }
// Serial
Settings.serial_config = TS_SERIAL_8N1;
Settings.baudrate = APP_BAUDRATE / 300;
Settings.sbaudrate = SOFT_BAUDRATE / 300;
Settings.serial_delimiter = 0xff;
@ -683,14 +896,14 @@ void SettingsDefaultSet2(void)
ParseIp(&Settings.ip_address[3], WIFI_DNS);
Settings.sta_config = WIFI_CONFIG_TOOL;
// Settings.sta_active = 0;
strlcpy(Settings.sta_ssid[0], STA_SSID1, sizeof(Settings.sta_ssid[0]));
strlcpy(Settings.sta_pwd[0], STA_PASS1, sizeof(Settings.sta_pwd[0]));
strlcpy(Settings.sta_ssid[1], STA_SSID2, sizeof(Settings.sta_ssid[1]));
strlcpy(Settings.sta_pwd[1], STA_PASS2, sizeof(Settings.sta_pwd[1]));
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
SettingsUpdateText(SET_STASSID1, STA_SSID1);
SettingsUpdateText(SET_STASSID2, STA_SSID2);
SettingsUpdateText(SET_STAPWD1, STA_PASS1);
SettingsUpdateText(SET_STAPWD2, STA_PASS2);
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
// Syslog
strlcpy(Settings.syslog_host, SYS_LOG_HOST, sizeof(Settings.syslog_host));
SettingsUpdateText(SET_SYSLOG_HOST, SYS_LOG_HOST);
Settings.syslog_port = SYS_LOG_PORT;
Settings.syslog_level = SYS_LOG_LEVEL;
@ -698,8 +911,9 @@ void SettingsDefaultSet2(void)
Settings.flag2.emulation = EMULATION;
Settings.webserver = WEB_SERVER;
Settings.weblog_level = WEB_LOG_LEVEL;
strlcpy(Settings.web_password, WEB_PASSWORD, sizeof(Settings.web_password));
SettingsUpdateText(SET_WEBPWD, WEB_PASSWORD);
Settings.flag3.mdns_enabled = MDNS_ENABLED;
SettingsUpdateText(SET_CORS, CORS_DOMAIN);
// Button
// Settings.flag.button_restrict = 0;
@ -722,24 +936,24 @@ void SettingsDefaultSet2(void)
// Settings.flag.mqtt_offline = 0;
// Settings.flag.mqtt_serial = 0;
// Settings.flag.device_index_enable = 0;
strlcpy(Settings.mqtt_host, MQTT_HOST, sizeof(Settings.mqtt_host));
SettingsUpdateText(SET_MQTT_HOST, MQTT_HOST);
Settings.mqtt_port = MQTT_PORT;
strlcpy(Settings.mqtt_client, MQTT_CLIENT_ID, sizeof(Settings.mqtt_client));
strlcpy(Settings.mqtt_user, MQTT_USER, sizeof(Settings.mqtt_user));
strlcpy(Settings.mqtt_pwd, MQTT_PASS, sizeof(Settings.mqtt_pwd));
strlcpy(Settings.mqtt_topic, MQTT_TOPIC, sizeof(Settings.mqtt_topic));
strlcpy(Settings.button_topic, MQTT_BUTTON_TOPIC, sizeof(Settings.button_topic));
strlcpy(Settings.switch_topic, MQTT_SWITCH_TOPIC, sizeof(Settings.switch_topic));
strlcpy(Settings.mqtt_grptopic, MQTT_GRPTOPIC, sizeof(Settings.mqtt_grptopic));
strlcpy(Settings.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(Settings.mqtt_fulltopic));
SettingsUpdateText(SET_MQTT_CLIENT, MQTT_CLIENT_ID);
SettingsUpdateText(SET_MQTT_USER, MQTT_USER);
SettingsUpdateText(SET_MQTT_PWD, MQTT_PASS);
SettingsUpdateText(SET_MQTT_TOPIC, MQTT_TOPIC);
SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC);
SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC);
SettingsUpdateText(SET_MQTT_GRP_TOPIC, MQTT_GRPTOPIC);
SettingsUpdateText(SET_MQTT_FULLTOPIC, MQTT_FULLTOPIC);
Settings.mqtt_retry = MQTT_RETRY_SECS;
strlcpy(Settings.mqtt_prefix[0], SUB_PREFIX, sizeof(Settings.mqtt_prefix[0]));
strlcpy(Settings.mqtt_prefix[1], PUB_PREFIX, sizeof(Settings.mqtt_prefix[1]));
strlcpy(Settings.mqtt_prefix[2], PUB_PREFIX2, sizeof(Settings.mqtt_prefix[2]));
strlcpy(Settings.state_text[0], MQTT_STATUS_OFF, sizeof(Settings.state_text[0]));
strlcpy(Settings.state_text[1], MQTT_STATUS_ON, sizeof(Settings.state_text[1]));
strlcpy(Settings.state_text[2], MQTT_CMND_TOGGLE, sizeof(Settings.state_text[2]));
strlcpy(Settings.state_text[3], MQTT_CMND_HOLD, sizeof(Settings.state_text[3]));
SettingsUpdateText(SET_MQTTPREFIX1, SUB_PREFIX);
SettingsUpdateText(SET_MQTTPREFIX2, PUB_PREFIX);
SettingsUpdateText(SET_MQTTPREFIX3, PUB_PREFIX2);
SettingsUpdateText(SET_STATE_TXT1, MQTT_STATUS_OFF);
SettingsUpdateText(SET_STATE_TXT2, MQTT_STATUS_ON);
SettingsUpdateText(SET_STATE_TXT3, MQTT_CMND_TOGGLE);
SettingsUpdateText(SET_STATE_TXT4, MQTT_CMND_HOLD);
char fingerprint[60];
strlcpy(fingerprint, MQTT_FINGERPRINT1, sizeof(fingerprint));
char *p = fingerprint;
@ -891,15 +1105,11 @@ void SettingsDefaultSet2(void)
Settings.timezone = APP_TIMEZONE / 60;
Settings.timezone_minutes = abs(APP_TIMEZONE % 60);
}
strlcpy(Settings.ntp_server[0], NTP_SERVER1, sizeof(Settings.ntp_server[0]));
strlcpy(Settings.ntp_server[1], NTP_SERVER2, sizeof(Settings.ntp_server[1]));
strlcpy(Settings.ntp_server[2], NTP_SERVER3, sizeof(Settings.ntp_server[2]));
for (uint32_t j = 0; j < 3; j++) {
for (uint32_t i = 0; i < strlen(Settings.ntp_server[j]); i++) {
if (Settings.ntp_server[j][i] == ',') {
Settings.ntp_server[j][i] = '.';
}
}
SettingsUpdateText(SET_NTPSERVER1, NTP_SERVER1);
SettingsUpdateText(SET_NTPSERVER2, NTP_SERVER2);
SettingsUpdateText(SET_NTPSERVER3, NTP_SERVER3);
for (uint32_t i = 0; i < 3; i++) {
SettingsUpdateText(SET_NTPSERVER1 +i, ReplaceCommaWithDot(SettingsText(SET_NTPSERVER1 +i)));
}
Settings.latitude = (int)((double)LATITUDE * 1000000);
Settings.longitude = (int)((double)LONGITUDE * 1000000);
@ -1059,8 +1269,8 @@ void SettingsDelta(void)
}
}
if (Settings.version < 0x06060009) {
Settings.baudrate = Settings.ex_baudrate * 4;
Settings.sbaudrate = Settings.ex_sbaudrate * 4;
Settings.baudrate = APP_BAUDRATE / 300;
Settings.sbaudrate = SOFT_BAUDRATE / 300;
}
if (Settings.version < 0x0606000A) {
uint8_t tuyaindex = 0;
@ -1155,6 +1365,54 @@ void SettingsDelta(void)
if (Settings.version < 0x07000004) {
Settings.wifi_output_power = 170;
}
if (Settings.version < 0x07010202) {
Settings.serial_config = TS_SERIAL_8N1;
}
if (Settings.version < 0x07010204) {
if (Settings.flag3.ex_cors_enabled == 1) {
strlcpy(Settings.cors_domain, CORS_ENABLED_ALL, sizeof(Settings.cors_domain));
} else {
Settings.cors_domain[0] = 0;
}
}
if (Settings.version < 0x07010205) {
Settings.seriallog_level = Settings.ex_seriallog_level; // 09E -> 452
Settings.sta_config = Settings.ex_sta_config; // 09F -> EC7
Settings.sta_active = Settings.ex_sta_active; // 0A0 -> EC8
memcpy((char*)&Settings.rule_stop, (char*)&Settings.ex_rule_stop, 47); // 1A7 -> EC9
}
if (Settings.version < 0x07010206) {
Settings.flag4 = Settings.ex_flag4; // 1E0 -> EF8
Settings.mqtt_port = Settings.ex_mqtt_port; // 20A -> EFC
memcpy((char*)&Settings.serial_config, (char*)&Settings.ex_serial_config, 5); // 1E4 -> EFE
}
if ((VERSION < 0x08000000) && (Settings.version > VERSION)) {
char temp[strlen(SettingsText(SET_OTAURL)) +1]; strncpy(temp, SettingsText(SET_OTAURL), sizeof(temp));
char temp21[strlen(SettingsText(SET_MQTTPREFIX1)) +1]; strncpy(temp21, SettingsText(SET_MQTTPREFIX1), sizeof(temp21));
char temp22[strlen(SettingsText(SET_MQTTPREFIX2)) +1]; strncpy(temp22, SettingsText(SET_MQTTPREFIX2), sizeof(temp22));
char temp23[strlen(SettingsText(SET_MQTTPREFIX3)) +1]; strncpy(temp23, SettingsText(SET_MQTTPREFIX3), sizeof(temp23));
char temp31[strlen(SettingsText(SET_STASSID1)) +1]; strncpy(temp31, SettingsText(SET_STASSID1), sizeof(temp31));
char temp32[strlen(SettingsText(SET_STASSID2)) +1]; strncpy(temp32, SettingsText(SET_STASSID2), sizeof(temp32));
char temp41[strlen(SettingsText(SET_STAPWD1)) +1]; strncpy(temp41, SettingsText(SET_STAPWD1), sizeof(temp41));
char temp42[strlen(SettingsText(SET_STAPWD2)) +1]; strncpy(temp42, SettingsText(SET_STAPWD2), sizeof(temp42));
char temp5[strlen(SettingsText(SET_HOSTNAME)) +1]; strncpy(temp5, SettingsText(SET_HOSTNAME), sizeof(temp5));
char temp6[strlen(SettingsText(SET_SYSLOG_HOST)) +1]; strncpy(temp6, SettingsText(SET_SYSLOG_HOST), sizeof(temp5));
uint32_t version = Settings.version;
Settings.version = VERSION;
SettingsUpdateText(SET_OTAURL, temp);
SettingsUpdateText(SET_MQTTPREFIX1, temp21);
SettingsUpdateText(SET_MQTTPREFIX2, temp22);
SettingsUpdateText(SET_MQTTPREFIX3, temp23);
SettingsUpdateText(SET_STASSID1, temp31);
SettingsUpdateText(SET_STASSID2, temp32);
SettingsUpdateText(SET_STAPWD1, temp41);
SettingsUpdateText(SET_STAPWD2, temp42);
SettingsUpdateText(SET_HOSTNAME, temp5);
SettingsUpdateText(SET_SYSLOG_HOST, temp6);
Settings.version = version;
}
Settings.version = VERSION;
SettingsSave(1);

View File

@ -51,7 +51,7 @@ void OsWatchTicker(void)
uint32_t last_run = abs(t - oswatch_last_loop_time);
#ifdef DEBUG_THEO
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d, last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(WiFi.RSSI()), last_run);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_OSWATCH " FreeRam %d, rssi %d %% (%d dBm), last_run %d"), ESP.getFreeHeap(), WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI(), last_run);
#endif // DEBUG_THEO
if (last_run >= (OSWATCH_RESET_TIME * 1000)) {
// AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_OSWATCH " " D_BLOCKED_LOOP ". " D_RESTARTING)); // Save iram space
@ -107,12 +107,6 @@ String GetResetReason(void)
}
}
String GetResetReasonInfo(void)
{
// "Fatal exception:0 flag:2 (EXCEPTION) epc1:0x704022a7 epc2:0x00000000 epc3:0x00000000 excvaddr:0x00000000 depc:0x00000000"
return (ResetReason() == REASON_EXCEPTION_RST) ? ESP.getResetInfo() : GetResetReason();
}
/*********************************************************************************************\
* Miscellaneous
\*********************************************************************************************/
@ -334,6 +328,22 @@ char* RemoveSpace(char* p)
return p;
}
char* ReplaceCommaWithDot(char* p)
{
char* write = (char*)p;
char* read = (char*)p;
char ch = '.';
while (ch != '\0') {
ch = *read++;
if (ch == ',') {
ch = '.';
}
*write++ = ch;
}
return p;
}
char* LowerCase(char* dest, const char* source)
{
char* write = dest;
@ -383,6 +393,23 @@ char* Trim(char* p)
return p;
}
char* RemoveAllSpaces(char* p)
{
// remove any white space from the base64
char *cursor = p;
uint32_t offset = 0;
while (1) {
*cursor = *(cursor + offset);
if ((' ' == *cursor) || ('\t' == *cursor) || ('\n' == *cursor)) { // if space found, remove this char until end of string
offset++;
} else {
if (0 == *cursor) { break; }
cursor++;
}
}
return p;
}
char* NoAlNumToUnderscore(char* dest, const char* source)
{
char* write = dest;
@ -737,19 +764,50 @@ int GetStateNumber(char *state_text)
return state_number;
}
String GetSerialConfig(void)
{
// Settings.serial_config layout
// b000000xx - 5, 6, 7 or 8 data bits
// b00000x00 - 1 or 2 stop bits
// b000xx000 - None, Even or Odd parity
const char kParity[] = "NEOI";
char config[4];
config[0] = '5' + (Settings.serial_config & 0x3);
config[1] = kParity[(Settings.serial_config >> 3) & 0x3];
config[2] = '1' + ((Settings.serial_config >> 2) & 0x1);
config[3] = '\0';
return String(config);
}
void SetSerialBegin(uint32_t baudrate)
{
if (seriallog_level) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION "Set Serial to %s %d bit/s"), GetSerialConfig().c_str(), baudrate);
delay(100);
}
Serial.flush();
Serial.begin(baudrate, (SerialConfig)pgm_read_byte(kTasmotaSerialConfig + Settings.serial_config));
delay(10);
Serial.println();
}
void SetSerialConfig(uint32_t serial_config)
{
if (serial_config == Settings.serial_config) { return; }
if (serial_config > TS_SERIAL_8O2) { return; }
Settings.serial_config = serial_config;
SetSerialBegin(Serial.baudRate());
}
void SetSerialBaudrate(int baudrate)
{
Settings.baudrate = baudrate / 300;
if (Serial.baudRate() != baudrate) {
if (seriallog_level) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SET_BAUDRATE_TO " %d"), baudrate);
}
delay(100);
Serial.flush();
Serial.begin(baudrate, serial_config);
delay(10);
Serial.println();
}
if (Serial.baudRate() == baudrate) { return; }
SetSerialBegin(baudrate);
}
void ClaimSerial(void)
@ -1529,10 +1587,10 @@ void Syslog(void)
{
// Destroys log_data
uint32_t current_hash = GetHash(Settings.syslog_host, strlen(Settings.syslog_host));
uint32_t current_hash = GetHash(SettingsText(SET_SYSLOG_HOST), strlen(SettingsText(SET_SYSLOG_HOST)));
if (syslog_host_hash != current_hash) {
syslog_host_hash = current_hash;
WiFi.hostByName(Settings.syslog_host, syslog_host_addr); // If sleep enabled this might result in exception so try to do it once using hash
WiFi.hostByName(SettingsText(SET_SYSLOG_HOST), syslog_host_addr); // If sleep enabled this might result in exception so try to do it once using hash
}
if (PortUdp.beginPacket(syslog_host_addr, Settings.syslog_port)) {
char syslog_preamble[64]; // Hostname + Id

View File

@ -23,7 +23,7 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix
D_CMND_SETOPTION "|" D_CMND_TEMPERATURE_RESOLUTION "|" D_CMND_HUMIDITY_RESOLUTION "|" D_CMND_PRESSURE_RESOLUTION "|" D_CMND_POWER_RESOLUTION "|"
D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_FREQUENCY_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_WEIGHT_RESOLUTION "|"
D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_TEMPLATE "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|"
D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|"
D_CMND_BUTTONDEBOUNCE "|" D_CMND_SWITCHDEBOUNCE "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_SERIALSEND "|" D_CMND_BAUDRATE "|" D_CMND_SERIALCONFIG "|"
D_CMND_SERIALDELIMITER "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|"
D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_INTERLOCK "|" D_CMND_TELEPERIOD "|" D_CMND_RESET "|" D_CMND_TIME "|" D_CMND_TIMEZONE "|" D_CMND_TIMESTD "|"
D_CMND_TIMEDST "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_LEDMASK "|" D_CMND_WIFIPOWER "|" D_CMND_TEMPOFFSET "|"
@ -38,7 +38,7 @@ void (* const TasmotaCommand[])(void) PROGMEM = {
&CmndSetoption, &CmndTemperatureResolution, &CmndHumidityResolution, &CmndPressureResolution, &CmndPowerResolution,
&CmndVoltageResolution, &CmndFrequencyResolution, &CmndCurrentResolution, &CmndEnergyResolution, &CmndWeightResolution,
&CmndModule, &CmndModules, &CmndGpio, &CmndGpios, &CmndTemplate, &CmndPwm, &CmndPwmfrequency, &CmndPwmrange,
&CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate,
&CmndButtonDebounce, &CmndSwitchDebounce, &CmndSyslog, &CmndLoghost, &CmndLogport, &CmndSerialSend, &CmndBaudrate, &CmndSerialConfig,
&CmndSerialDelimiter, &CmndIpAddress, &CmndNtpServer, &CmndAp, &CmndSsid, &CmndPassword, &CmndHostname, &CmndWifiConfig,
&CmndFriendlyname, &CmndSwitchMode, &CmndInterlock, &CmndTeleperiod, &CmndReset, &CmndTime, &CmndTimezone, &CmndTimeStd,
&CmndTimeDst, &CmndAltitude, &CmndLedPower, &CmndLedState, &CmndLedMask, &CmndWifiPower, &CmndTempOffset,
@ -147,7 +147,7 @@ void CommandHandler(char* topicBuf, char* dataBuf, uint32_t data_len)
data_len--;
}
bool grpflg = (strstr(topicBuf, Settings.mqtt_grptopic) != nullptr);
bool grpflg = (strstr(topicBuf, SettingsText(SET_MQTT_GRP_TOPIC)) != nullptr);
char stemp1[TOPSZ];
GetFallbackTopic_P(stemp1, ""); // Full Fallback topic = cmnd/DVES_xxxxxxxx_fb/
@ -325,15 +325,18 @@ void CmndStatus(void)
uint32_t payload = ((XdrvMailbox.payload < 0) || (XdrvMailbox.payload > MAX_STATUS)) ? 99 : XdrvMailbox.payload;
uint32_t option = STAT;
char stemp[MAX_FRIENDLYNAMES * (sizeof(Settings.friendlyname[0]) +MAX_FRIENDLYNAMES)];
char stemp[200];
char stemp2[100];
// Workaround MQTT - TCP/IP stack queueing when SUB_PREFIX = PUB_PREFIX
if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1]) && (!payload)) { option++; } // TELE
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; }
bool exception_flg = (ResetReason() == REASON_EXCEPTION_RST);
if (!exception_flg && (12 == payload)) { payload = 99; }
if ((0 == payload) || (99 == payload)) {
uint32_t maxfn = (devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!devices_present) ? 1 : devices_present;
#ifdef USE_SONOFF_IFAN
@ -341,7 +344,7 @@ void CmndStatus(void)
#endif // USE_SONOFF_IFAN
stemp[0] = '\0';
for (uint32_t i = 0; i < maxfn; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%s%s\"%s\"" ), stemp, (i > 0 ? "," : ""), Settings.friendlyname[i]);
snprintf_P(stemp, sizeof(stemp), PSTR("%s%s\"%s\"" ), stemp, (i > 0 ? "," : ""), SettingsText(SET_FRIENDLYNAME1 +i));
}
stemp2[0] = '\0';
for (uint32_t i = 0; i < MAX_SWITCHES; i++) {
@ -352,10 +355,10 @@ void CmndStatus(void)
D_CMND_LEDMASK "\":\"%04X\",\"" D_CMND_SAVEDATA "\":%d,\"" D_JSON_SAVESTATE "\":%d,\"" D_CMND_SWITCHTOPIC "\":\"%s\",\""
D_CMND_SWITCHMODE "\":[%s],\"" D_CMND_BUTTONRETAIN "\":%d,\"" D_CMND_SWITCHRETAIN "\":%d,\"" D_CMND_SENSORRETAIN "\":%d,\"" D_CMND_POWERRETAIN "\":%d}}"),
ModuleNr(), stemp, mqtt_topic,
Settings.button_topic, power, Settings.poweronstate, Settings.ledstate,
SettingsText(SET_MQTT_BUTTON_TOPIC), power, Settings.poweronstate, Settings.ledstate,
Settings.ledmask, Settings.save_data,
Settings.flag.save_state, // SetOption0 - Save power state and use after restart
Settings.switch_topic,
SettingsText(SET_MQTT_SWITCH_TOPIC),
stemp2,
Settings.flag.mqtt_button_retain, // CMND_BUTTONRETAIN
Settings.flag.mqtt_switch_retain, // CMND_SWITCHRETAIN
@ -368,8 +371,8 @@ void CmndStatus(void)
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS1_PARAMETER "\":{\"" D_JSON_BAUDRATE "\":%d,\"" D_CMND_GROUPTOPIC "\":\"%s\",\"" D_CMND_OTAURL "\":\"%s\",\""
D_JSON_RESTARTREASON "\":\"%s\",\"" D_JSON_UPTIME "\":\"%s\",\"" D_JSON_STARTUPUTC "\":\"%s\",\"" D_CMND_SLEEP "\":%d,\""
D_JSON_CONFIG_HOLDER "\":%d,\"" D_JSON_BOOTCOUNT "\":%d,\"" D_JSON_SAVECOUNT "\":%d,\"" D_JSON_SAVEADDRESS "\":\"%X\"}}"),
baudrate, Settings.mqtt_grptopic, Settings.ota_url,
GetResetReasonInfo().c_str(), GetUptime().c_str(), GetDateAndTime(DT_RESTART).c_str(), Settings.sleep,
baudrate, SettingsText(SET_MQTT_GRP_TOPIC), SettingsText(SET_OTAURL),
GetResetReason().c_str(), GetUptime().c_str(), GetDateAndTime(DT_RESTART).c_str(), Settings.sleep,
Settings.cfg_holder, Settings.bootcount, Settings.save_flag, GetSettingsAddress());
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "1"));
}
@ -377,9 +380,12 @@ void CmndStatus(void)
if ((0 == payload) || (2 == payload)) {
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS2_FIRMWARE "\":{\"" D_JSON_VERSION "\":\"%s%s\",\"" D_JSON_BUILDDATETIME "\":\"%s\",\""
D_JSON_BOOTVERSION "\":%d,\"" D_JSON_COREVERSION "\":\"" ARDUINO_ESP8266_RELEASE "\",\"" D_JSON_SDKVERSION "\":\"%s\","
"\"Hardware\":\"%s\"}}"),
"\"Hardware\":\"%s\""
"%s}}"),
my_version, my_image, GetBuildDateAndTime().c_str(),
ESP.getBootVersion(), ESP.getSdkVersion(), GetDeviceHardware().c_str());
ESP.getBootVersion(), ESP.getSdkVersion(),
GetDeviceHardware().c_str(),
GetStatistics().c_str());
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "2"));
}
@ -388,7 +394,7 @@ void CmndStatus(void)
D_CMND_LOGHOST "\":\"%s\",\"" D_CMND_LOGPORT "\":%d,\"" D_CMND_SSID "\":[\"%s\",\"%s\"],\"" D_CMND_TELEPERIOD "\":%d,\""
D_JSON_RESOLUTION "\":\"%08X\",\"" D_CMND_SETOPTION "\":[\"%08X\",\"%s\",\"%08X\",\"%08X\"]}}"),
Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level,
Settings.syslog_host, Settings.syslog_port, Settings.sta_ssid[0], Settings.sta_ssid[1], Settings.tele_period,
SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), Settings.tele_period,
Settings.flag2.data, Settings.flag.data, ToHex_P((unsigned char*)Settings.param, PARAM8_SIZE, stemp2, sizeof(stemp2)),
Settings.flag3.data, Settings.flag4.data);
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "3"));
@ -419,17 +425,10 @@ void CmndStatus(void)
}
if (((0 == payload) || (6 == payload)) && Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
#ifdef USE_MQTT_AWS_IOT
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS6_MQTT "\":{\"" D_CMND_MQTTHOST "\":\"%s%s\",\"" D_CMND_MQTTPORT "\":%d,\"" D_CMND_MQTTCLIENT D_JSON_MASK "\":\"%s\",\""
D_CMND_MQTTCLIENT "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d,\"MAX_PACKET_SIZE\":%d,\"KEEPALIVE\":%d}}"),
Settings.mqtt_user, Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client,
mqtt_client, MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE);
#else
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS6_MQTT "\":{\"" D_CMND_MQTTHOST "\":\"%s\",\"" D_CMND_MQTTPORT "\":%d,\"" D_CMND_MQTTCLIENT D_JSON_MASK "\":\"%s\",\""
D_CMND_MQTTCLIENT "\":\"%s\",\"" D_CMND_MQTTUSER "\":\"%s\",\"" D_JSON_MQTT_COUNT "\":%d,\"MAX_PACKET_SIZE\":%d,\"KEEPALIVE\":%d}}"),
Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client,
mqtt_client, Settings.mqtt_user, MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE);
#endif
SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT),
mqtt_client, SettingsText(SET_MQTT_USER), MqttConnectCount(), MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE);
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "6"));
}
@ -483,6 +482,15 @@ void CmndStatus(void)
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "11"));
}
if (exception_flg) {
if ((0 == payload) || (12 == payload)) {
Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS12_STATUS "\":"));
CrashDump();
ResponseJsonEnd();
MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "12"));
}
}
#ifdef USE_SCRIPT_STATUS
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">U",2,mqtt_data);
#endif
@ -542,10 +550,10 @@ void CmndUpgrade(void)
void CmndOtaUrl(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.ota_url))) {
strlcpy(Settings.ota_url, (SC_DEFAULT == Shortcut()) ? OTA_URL : XdrvMailbox.data, sizeof(Settings.ota_url));
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_OTAURL, (SC_DEFAULT == Shortcut()) ? OTA_URL : XdrvMailbox.data);
}
ResponseCmndChar(Settings.ota_url);
ResponseCmndChar(SettingsText(SET_OTAURL));
}
void CmndSeriallog(void)
@ -564,6 +572,9 @@ void CmndRestart(void)
restart_flag = 2;
ResponseCmndChar(D_JSON_RESTARTING);
break;
case -1:
CmndCrash(); // force a crash
break;
case 99:
AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING));
EspRestart();
@ -1082,6 +1093,48 @@ void CmndBaudrate(void)
ResponseCmndNumber(Settings.baudrate * 300);
}
void CmndSerialConfig(void)
{
// See TasmotaSerialConfig for possible options
// SerialConfig 0..23 where 3 equals 8N1
// SerialConfig 8N1
if (XdrvMailbox.data_len > 0) {
if (XdrvMailbox.data_len < 3) { // Use 0..23 as serial config option
if ((XdrvMailbox.payload >= TS_SERIAL_5N1) && (XdrvMailbox.payload <= TS_SERIAL_8O2)) {
SetSerialConfig(XdrvMailbox.payload);
}
}
else if ((XdrvMailbox.payload >= 5) && (XdrvMailbox.payload <= 8)) {
uint8_t serial_config = XdrvMailbox.payload -5; // Data bits 5, 6, 7 or 8, No parity and 1 stop bit
bool valid = true;
char parity = (XdrvMailbox.data[1] & 0xdf);
if ('E' == parity) {
serial_config += 0x08; // Even parity
}
else if ('O' == parity) {
serial_config += 0x10; // Odd parity
}
else if ('N' != parity) {
valid = false;
}
if ('2' == XdrvMailbox.data[2]) {
serial_config += 0x04; // Stop bits 2
}
else if ('1' != XdrvMailbox.data[2]) {
valid = false;
}
if (valid) {
SetSerialConfig(serial_config);
}
}
}
ResponseCmndChar(GetSerialConfig().c_str());
}
void CmndSerialSend(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 5)) {
@ -1133,10 +1186,10 @@ void CmndSyslog(void)
void CmndLoghost(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.syslog_host))) {
strlcpy(Settings.syslog_host, (SC_DEFAULT == Shortcut()) ? SYS_LOG_HOST : XdrvMailbox.data, sizeof(Settings.syslog_host));
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_SYSLOG_HOST, (SC_DEFAULT == Shortcut()) ? SYS_LOG_HOST : XdrvMailbox.data);
}
ResponseCmndChar(Settings.syslog_host);
ResponseCmndChar(SettingsText(SET_SYSLOG_HOST));
}
void CmndLogport(void)
@ -1164,17 +1217,15 @@ void CmndIpAddress(void)
void CmndNtpServer(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.ntp_server[0]))) {
strlcpy(Settings.ntp_server[XdrvMailbox.index -1],
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index)?NTP_SERVER1:(2==XdrvMailbox.index)?NTP_SERVER2:NTP_SERVER3 : XdrvMailbox.data,
sizeof(Settings.ntp_server[0]));
for (uint32_t i = 0; i < strlen(Settings.ntp_server[XdrvMailbox.index -1]); i++) {
if (Settings.ntp_server[XdrvMailbox.index -1][i] == ',') Settings.ntp_server[XdrvMailbox.index -1][i] = '.';
}
uint32_t ntp_server = SET_NTPSERVER1 + XdrvMailbox.index -1;
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(ntp_server,
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? NTP_SERVER1 : (2 == XdrvMailbox.index) ? NTP_SERVER2 : NTP_SERVER3 : XdrvMailbox.data);
SettingsUpdateText(ntp_server, ReplaceCommaWithDot(SettingsText(ntp_server)));
// restart_flag = 2; // Issue #3890
ntp_force_sync = true;
}
ResponseCmndIdxChar(Settings.ntp_server[XdrvMailbox.index -1]);
ResponseCmndIdxChar(SettingsText(ntp_server));
}
}
@ -1191,33 +1242,31 @@ void CmndAp(void)
}
restart_flag = 2;
}
Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active]);
Response_P(S_JSON_COMMAND_NVALUE_SVALUE, XdrvMailbox.command, Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active));
}
void CmndSsid(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.sta_ssid[0]))) {
strlcpy(Settings.sta_ssid[XdrvMailbox.index -1],
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_SSID1 : STA_SSID2 : XdrvMailbox.data,
sizeof(Settings.sta_ssid[0]));
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_STASSID1 + XdrvMailbox.index -1,
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_SSID1 : STA_SSID2 : XdrvMailbox.data);
Settings.sta_active = XdrvMailbox.index -1;
restart_flag = 2;
}
ResponseCmndIdxChar(Settings.sta_ssid[XdrvMailbox.index -1]);
ResponseCmndIdxChar(SettingsText(SET_STASSID1 + XdrvMailbox.index -1));
}
}
void CmndPassword(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 2)) {
if ((XdrvMailbox.data_len > 4 || SC_CLEAR == Shortcut() || SC_DEFAULT == Shortcut()) && (XdrvMailbox.data_len < sizeof(Settings.sta_pwd[0]))) {
strlcpy(Settings.sta_pwd[XdrvMailbox.index -1],
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_PASS1 : STA_PASS2 : XdrvMailbox.data,
sizeof(Settings.sta_pwd[0]));
if ((XdrvMailbox.data_len > 4) || (SC_CLEAR == Shortcut()) || (SC_DEFAULT == Shortcut())) {
SettingsUpdateText(SET_STAPWD1 + XdrvMailbox.index -1,
(SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? (1 == XdrvMailbox.index) ? STA_PASS1 : STA_PASS2 : XdrvMailbox.data);
Settings.sta_active = XdrvMailbox.index -1;
restart_flag = 2;
ResponseCmndIdxChar(Settings.sta_pwd[XdrvMailbox.index -1]);
ResponseCmndIdxChar(SettingsText(SET_STAPWD1 + XdrvMailbox.index -1));
} else {
Response_P(S_JSON_COMMAND_INDEX_ASTERISK, XdrvMailbox.command, XdrvMailbox.index);
}
@ -1226,14 +1275,14 @@ void CmndPassword(void)
void CmndHostname(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.hostname))) {
strlcpy(Settings.hostname, (SC_DEFAULT == Shortcut()) ? WIFI_HOSTNAME : XdrvMailbox.data, sizeof(Settings.hostname));
if (strstr(Settings.hostname, "%") != nullptr) {
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
SettingsUpdateText(SET_HOSTNAME, (SC_DEFAULT == Shortcut()) ? WIFI_HOSTNAME : XdrvMailbox.data);
if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) {
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
}
restart_flag = 2;
}
ResponseCmndChar(Settings.hostname);
ResponseCmndChar(SettingsText(SET_HOSTNAME));
}
void CmndWifiConfig(void)
@ -1255,16 +1304,16 @@ void CmndWifiConfig(void)
void CmndFriendlyname(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_FRIENDLYNAMES)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.friendlyname[0]))) {
if (XdrvMailbox.data_len > 0) {
char stemp1[TOPSZ];
if (1 == XdrvMailbox.index) {
snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME));
} else {
snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), XdrvMailbox.index);
}
strlcpy(Settings.friendlyname[XdrvMailbox.index -1], (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data, sizeof(Settings.friendlyname[XdrvMailbox.index -1]));
SettingsUpdateText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1, (SC_DEFAULT == Shortcut()) ? stemp1 : XdrvMailbox.data);
}
ResponseCmndIdxChar(Settings.friendlyname[XdrvMailbox.index -1]);
ResponseCmndIdxChar(SettingsText(SET_FRIENDLYNAME1 + XdrvMailbox.index -1));
}
}

View File

@ -0,0 +1,89 @@
/*
support_crash_recorder.ino - record the call stack in RTC in case of crash
Copyright (C) 2019 Stephan Hadinger, Theo Arends,
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
const uint32_t crash_magic = 0x53415400; // Stack trace magic number (TASx)
const uint32_t crash_rtc_offset = 32; // Offset in RTC memory skipping OTA used block
const uint32_t crash_dump_max_len = 31; // Dump only 31 call addresses to satisfy max JSON length of about 600 characters
/**
* Save crash information in RTC memory
* This function is called automatically if ESP8266 suffers an exception
* It should be kept quick / consise to be able to execute before hardware wdt may kick in
*/
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack, uint32_t stack_end )
{
uint32_t addr_written = 0; // how many addresses have we already written in RTC
uint32_t value; // 4 bytes buffer to write to RTC
for (uint32_t i = stack; i < stack_end; i += 4) {
value = *((uint32_t*) i); // load value from stack
if ((value >= 0x40000000) && (value < 0x40300000)) { // keep only addresses in code area
ESP.rtcUserMemoryWrite(crash_rtc_offset + addr_written, (uint32_t*)&value, sizeof(value));
addr_written++;
if (addr_written >= crash_dump_max_len) { break; } // we store only 31 addresses
}
}
value = crash_magic + addr_written;
ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value));
}
// Generate a crash to test the crash recorder
void CmndCrash(void)
{
volatile uint32_t dummy;
dummy = *((uint32_t*) 0x00000000);
}
// Clear the RTC dump counter when we do a normal reboot, this avoids garbage data to stay in RTC
void CrashDumpClear(void)
{
uint32_t value = 0;
ESP.rtcUserMemoryWrite(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value));
}
/*********************************************************************************************\
* CmndCrashDump - dump the crash history - called by `Status 12`
\*********************************************************************************************/
void CrashDump(void)
{
ResponseAppend_P(PSTR("{\"Exception\":%d,\"Reason\":\"%s\",\"EPC\":[\"%08x\",\"%08x\",\"%08x\"],\"EXCVADDR\":\"%08x\",\"DEPC\":\"%08x\""),
resetInfo.exccause, // Exception Cause
GetResetReason().c_str(), // Reset Reason
resetInfo.epc1, // Exception Progam Counter
resetInfo.epc2, // Exception Progam Counter - High-Priority Interrupt 1
resetInfo.epc3, // Exception Progam Counter - High-Priority Interrupt 2
resetInfo.excvaddr, // Exception Virtual Address Register - Virtual address that caused last fetch, load, or store exception
resetInfo.depc); // Double Exception Program Counter
uint32_t value;
ESP.rtcUserMemoryRead(crash_rtc_offset + crash_dump_max_len, (uint32_t*)&value, sizeof(value));
if (crash_magic == (value & 0xFFFFFF00)) {
ResponseAppend_P(PSTR(",\"CallChain\":["));
uint32_t count = value & 0x3F;
for (uint32_t i = 0; i < count; i++) {
ESP.rtcUserMemoryRead(crash_rtc_offset +i, (uint32_t*)&value, sizeof(value));
if (i > 0) { ResponseAppend_P(PSTR(",")); }
ResponseAppend_P(PSTR("\"%08x\""), value);
}
ResponseAppend_P(PSTR("]"));
}
ResponseJsonEnd();
}

View File

@ -172,6 +172,7 @@ double const f_sixthpi = f_pi / 6.0; // f_pi/6.0, used in
double const f_tansixthpi = tan(f_sixthpi); // tan(f_pi/6), used in atan routines
double const f_twelfthpi = f_pi / 12.0; // f_pi/12.0, used in atan routines
double const f_tantwelfthpi = tan(f_twelfthpi); // tan(f_pi/12), used in atan routines
float const f_180pi = 180 / f_pi; // 180 / pi for angles in degrees
// *******************************************************************
// ***

View File

@ -379,7 +379,9 @@ void RtcSecond(void)
Rtc.ntp_sync_minute = 1; // If sync prepare for a new cycle
}
uint8_t offset = (uptime < 30) ? RtcTime.second : (((ESP.getChipId() & 0xF) * 3) + 3) ; // First try ASAP to sync. If fails try once every 60 seconds based on chip id
if ( (((offset == RtcTime.second) && ( (RtcTime.year < 2016) || (Rtc.ntp_sync_minute == uptime_minute))) || ntp_force_sync ) ) {
if ( (((offset == RtcTime.second) && ( (RtcTime.year < 2016) || // Never synced
(Rtc.ntp_sync_minute == uptime_minute))) || // Re-sync every hour
ntp_force_sync ) ) { // Forced sync
Rtc.ntp_time = sntp_get_current_timestamp();
if (Rtc.ntp_time > START_VALID_TIME) { // Fix NTP bug in core 2.4.1/SDK 2.2.1 (returns Thu Jan 01 08:00:10 1970 after power on)
ntp_force_sync = false;
@ -467,9 +469,9 @@ void RtcSetTime(uint32_t epoch)
void RtcInit(void)
{
sntp_setservername(0, Settings.ntp_server[0]);
sntp_setservername(1, Settings.ntp_server[1]);
sntp_setservername(2, Settings.ntp_server[2]);
sntp_setservername(0, SettingsText(SET_NTPSERVER1));
sntp_setservername(1, SettingsText(SET_NTPSERVER2));
sntp_setservername(2, SettingsText(SET_NTPSERVER3));
sntp_stop();
sntp_set_timezone(0); // UTC time
sntp_init();

View File

@ -0,0 +1,76 @@
/*
support_statistics.ino - gather statistics for Tasmota
Copyright (C) 2019 Theo Arends
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define USE_STATS_CODE
#ifdef USE_STATS_CODE
/*********************************************************************************************\
* Gather statistics
\*********************************************************************************************/
String GetStatistics(void)
{
char data[40];
if (Settings.version < 0x08000000) {
uint32_t str_len = 0;
for (uint32_t i = 0; i < 2; i++) {
str_len += strlen(Settings.sta_ssid[i]);
str_len += strlen(Settings.sta_pwd[i]);
}
for (uint32_t i = 0; i < 3; i++) {
str_len += strlen(Settings.mqtt_prefix[i]);
str_len += strlen(Settings.ntp_server[i]);
}
for (uint32_t i = 0; i < 4; i++) {
str_len += strlen(Settings.state_text[i]);
str_len += strlen(Settings.friendlyname[i]);
}
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
str_len += strlen(Settings.mems[i]);
}
str_len += strlen(Settings.ota_url);
str_len += strlen(Settings.hostname);
str_len += strlen(Settings.syslog_host);
str_len += strlen(Settings.mqtt_host);
str_len += strlen(Settings.mqtt_client);
str_len += strlen(Settings.mqtt_user);
str_len += strlen(Settings.mqtt_pwd);
str_len += strlen(Settings.mqtt_topic);
str_len += strlen(Settings.button_topic);
str_len += strlen(Settings.switch_topic);
str_len += strlen(Settings.mqtt_grptopic);
str_len += strlen(Settings.web_password);
str_len += strlen(Settings.mqtt_fulltopic);
str_len += strlen(Settings.cors_domain);
snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/1151\""), 37 + str_len); // Char Usage Ratio
} else {
snprintf_P(data, sizeof(data), PSTR(",\"CR\":\"%d/%d\""), GetSettingsTextLen(), settings_text_size); // Char Usage Ratio
}
return String(data);
}
#else
String GetStatistics(void)
{
return String("");
}
#endif // USE_STATS_CODE

View File

@ -60,15 +60,16 @@ char* Format(char* output, const char* input, int size)
char* GetOtaUrl(char *otaurl, size_t otaurl_size)
{
if (strstr(Settings.ota_url, "%04d") != nullptr) { // OTA url contains placeholder for chip ID
snprintf(otaurl, otaurl_size, Settings.ota_url, ESP.getChipId() & 0x1fff);
if (strstr(SettingsText(SET_OTAURL), "%04d") != nullptr) { // OTA url contains placeholder for chip ID
snprintf(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId() & 0x1fff);
}
else if (strstr(Settings.ota_url, "%d") != nullptr) { // OTA url contains placeholder for chip ID
snprintf_P(otaurl, otaurl_size, Settings.ota_url, ESP.getChipId());
else if (strstr(SettingsText(SET_OTAURL), "%d") != nullptr) { // OTA url contains placeholder for chip ID
snprintf_P(otaurl, otaurl_size, SettingsText(SET_OTAURL), ESP.getChipId());
}
else {
strlcpy(otaurl, Settings.ota_url, otaurl_size);
strlcpy(otaurl, SettingsText(SET_OTAURL), otaurl_size);
}
return otaurl;
}
@ -101,17 +102,19 @@ char* GetTopic_P(char *stopic, uint32_t prefix, char *topic, const char* subtopi
fulltopic += topic; // cmnd/<grouptopic>
}
} else {
fulltopic = Settings.mqtt_fulltopic;
fulltopic = SettingsText(SET_MQTT_FULLTOPIC);
if ((0 == prefix) && (-1 == fulltopic.indexOf(FPSTR(MQTT_TOKEN_PREFIX)))) {
fulltopic += F("/");
fulltopic += FPSTR(MQTT_TOKEN_PREFIX); // Need prefix for commands to handle mqtt topic loops
}
for (uint32_t i = 0; i < 3; i++) {
if ('\0' == Settings.mqtt_prefix[i][0]) {
GetTextIndexed(Settings.mqtt_prefix[i], sizeof(Settings.mqtt_prefix[i]), i, kPrefixes);
if ('\0' == SettingsText(SET_MQTTPREFIX1 + i)) {
char temp[TOPSZ];
SettingsUpdateText(SET_MQTTPREFIX1 + i, GetTextIndexed(temp, sizeof(temp), i, kPrefixes));
}
}
fulltopic.replace(FPSTR(MQTT_TOKEN_PREFIX), Settings.mqtt_prefix[prefix]);
fulltopic.replace(FPSTR(MQTT_TOKEN_PREFIX), SettingsText(SET_MQTTPREFIX1 + prefix));
fulltopic.replace(FPSTR(MQTT_TOKEN_TOPIC), topic);
fulltopic.replace(F("%hostname%"), my_hostname);
String token_id = WiFi.macAddress();
@ -131,7 +134,7 @@ char* GetGroupTopic_P(char *stopic, const char* subtopic)
{
// SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing/<grouptopic>/#
// SetOption75 1: cmnd/<grouptopic>
return GetTopic_P(stopic, (Settings.flag3.grouptopic_mode) ? CMND +8 : CMND, Settings.mqtt_grptopic, subtopic); // SetOption75 - GroupTopic replaces %topic% (0) or fixed topic cmnd/grouptopic (1)
return GetTopic_P(stopic, (Settings.flag3.grouptopic_mode) ? CMND +8 : CMND, SettingsText(SET_MQTT_GRP_TOPIC), subtopic); // SetOption75 - GroupTopic replaces %topic% (0) or fixed topic cmnd/grouptopic (1)
}
char* GetFallbackTopic_P(char *stopic, const char* subtopic)
@ -144,7 +147,7 @@ char* GetStateText(uint32_t state)
if (state > 3) {
state = 1;
}
return Settings.state_text[state];
return SettingsText(SET_STATE_TXT1 + state);
}
/********************************************************************************************/
@ -358,10 +361,10 @@ bool SendKey(uint32_t key, uint32_t device, uint32_t state)
char stopic[TOPSZ];
char scommand[CMDSZ];
char key_topic[sizeof(Settings.button_topic)];
char key_topic[TOPSZ];
bool result = false;
char *tmp = (key) ? Settings.switch_topic : Settings.button_topic;
char *tmp = (key) ? SettingsText(SET_MQTT_SWITCH_TOPIC) : SettingsText(SET_MQTT_BUTTON_TOPIC);
Format(key_topic, tmp, sizeof(key_topic));
if (Settings.flag.mqtt_enabled && MqttIsConnected() && (strlen(key_topic) != 0) && strcmp(key_topic, "0")) { // SetOption3 - Enable MQTT
if (!key && (device > devices_present)) {
@ -374,7 +377,7 @@ bool SendKey(uint32_t key, uint32_t device, uint32_t state)
} else {
if ((Settings.flag3.button_switch_force_local || // SetOption61 - Force local operation when button/switch topic is set
!strcmp(mqtt_topic, key_topic) ||
!strcmp(Settings.mqtt_grptopic, key_topic)) &&
!strcmp(SettingsText(SET_MQTT_GRP_TOPIC), key_topic)) &&
(POWER_TOGGLE == state)) {
state = ~(power >> (device -1)) &1; // POWER_OFF or POWER_ON
}
@ -583,8 +586,9 @@ void MqttShowState(void)
MqttShowPWMState();
}
ResponseAppend_P(PSTR(",\"" D_JSON_WIFI "\":{\"" D_JSON_AP "\":%d,\"" D_JSON_SSID "\":\"%s\",\"" D_JSON_BSSID "\":\"%s\",\"" D_JSON_CHANNEL "\":%d,\"" D_JSON_RSSI "\":%d,\"" D_JSON_LINK_COUNT "\":%d,\"" D_JSON_DOWNTIME "\":\"%s\"}}"),
Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active], WiFi.BSSIDstr().c_str(), WiFi.channel(), WifiGetRssiAsQuality(WiFi.RSSI()), WifiLinkCount(), WifiDowntime().c_str());
ResponseAppend_P(PSTR(",\"" D_JSON_WIFI "\":{\"" D_JSON_AP "\":%d,\"" D_JSON_SSID "\":\"%s\",\"" D_JSON_BSSID "\":\"%s\",\"" D_JSON_CHANNEL "\":%d,\"" D_JSON_RSSI "\":%d,\"" D_JSON_SIGNAL "\":%d,\"" D_JSON_LINK_COUNT "\":%d,\"" D_JSON_DOWNTIME "\":\"%s\"}}"),
Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), WiFi.BSSIDstr().c_str(), WiFi.channel(),
WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI(), WifiLinkCount(), WifiDowntime().c_str());
}
void MqttPublishTeleState(void)
@ -592,7 +596,7 @@ void MqttPublishTeleState(void)
mqtt_data[0] = '\0';
MqttShowState();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_STATE), MQTT_TELE_RETAIN);
#ifdef USE_SCRIPT
#if defined(USE_RULES) || defined(USE_SCRIPT)
RulesTeleperiod(); // Allow rule based HA messages
#endif // USE_SCRIPT
}
@ -812,7 +816,6 @@ void Every250mSeconds(void)
if (ota_state_flag && BACKLOG_EMPTY) {
ota_state_flag--;
if (2 == ota_state_flag) {
ota_url = Settings.ota_url;
RtcSettings.ota_loader = 0; // Try requested image first
ota_retry_counter = OTA_ATTEMPTS;
ESPhttpUpdate.rebootOnUpdate(false);
@ -838,7 +841,7 @@ void Every250mSeconds(void)
if (!pch) { pch = ech; }
if (pch) {
mqtt_data[pch - mqtt_data] = '\0';
char *ech = strrchr(Settings.ota_url, '.'); // Change from filename.bin into filename-minimal.bin
char *ech = strrchr(SettingsText(SET_OTAURL), '.'); // Change from filename.bin into filename-minimal.bin
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s-" D_JSON_MINIMAL "%s"), mqtt_data, ech); // Minimal filename must be filename-minimal
}
}
@ -902,26 +905,45 @@ void Every250mSeconds(void)
}
if (restart_flag && BACKLOG_EMPTY) {
if ((214 == restart_flag) || (215 == restart_flag) || (216 == restart_flag)) {
char storage_wifi[sizeof(Settings.sta_ssid) +
sizeof(Settings.sta_pwd)];
char storage_mqtt[sizeof(Settings.mqtt_host) +
sizeof(Settings.mqtt_port) +
sizeof(Settings.mqtt_client) +
sizeof(Settings.mqtt_user) +
sizeof(Settings.mqtt_pwd) +
sizeof(Settings.mqtt_topic)];
memcpy(storage_wifi, Settings.sta_ssid, sizeof(storage_wifi)); // Backup current SSIDs and Passwords
if (216 == restart_flag) {
memcpy(storage_mqtt, Settings.mqtt_host, sizeof(storage_mqtt)); // Backup mqtt host, port, client, username and password
}
// Backup current SSIDs and Passwords
char storage_ssid1[strlen(SettingsText(SET_STASSID1)) +1];
strncpy(storage_ssid1, SettingsText(SET_STASSID1), sizeof(storage_ssid1));
char storage_ssid2[strlen(SettingsText(SET_STASSID2)) +1];
strncpy(storage_ssid2, SettingsText(SET_STASSID2), sizeof(storage_ssid2));
char storage_pass1[strlen(SettingsText(SET_STAPWD1)) +1];
strncpy(storage_pass1, SettingsText(SET_STAPWD1), sizeof(storage_pass1));
char storage_pass2[strlen(SettingsText(SET_STAPWD2)) +1];
strncpy(storage_pass2, SettingsText(SET_STAPWD2), sizeof(storage_pass2));
char storage_mqtthost[strlen(SettingsText(SET_MQTT_HOST)) +1];
strncpy(storage_mqtthost, SettingsText(SET_MQTT_HOST), sizeof(storage_mqtthost));
char storage_mqttuser[strlen(SettingsText(SET_MQTT_USER)) +1];
strncpy(storage_mqttuser, SettingsText(SET_MQTT_USER), sizeof(storage_mqttuser));
char storage_mqttpwd[strlen(SettingsText(SET_MQTT_PWD)) +1];
strncpy(storage_mqttpwd, SettingsText(SET_MQTT_PWD), sizeof(storage_mqttpwd));
char storage_mqtttopic[strlen(SettingsText(SET_MQTT_TOPIC)) +1];
strncpy(storage_mqtttopic, SettingsText(SET_MQTT_TOPIC), sizeof(storage_mqtttopic));
uint16_t mqtt_port = Settings.mqtt_port;
// if (216 == restart_flag) {
// Backup mqtt host, port, client, username and password
// }
if ((215 == restart_flag) || (216 == restart_flag)) {
SettingsErase(0); // Erase all flash from program end to end of physical flash
}
SettingsDefault();
memcpy(Settings.sta_ssid, storage_wifi, sizeof(storage_wifi)); // Restore current SSIDs and Passwords
// Restore current SSIDs and Passwords
SettingsUpdateText(SET_STASSID1, storage_ssid1);
SettingsUpdateText(SET_STASSID2, storage_ssid2);
SettingsUpdateText(SET_STAPWD1, storage_pass1);
SettingsUpdateText(SET_STAPWD2, storage_pass2);
if (216 == restart_flag) {
memcpy(Settings.mqtt_host, storage_mqtt, sizeof(storage_mqtt)); // Restore the mqtt host, port, client, username and password
strlcpy(Settings.mqtt_client, MQTT_CLIENT_ID, sizeof(Settings.mqtt_client)); // Set client to default
// Restore the mqtt host, port, client, username and password
SettingsUpdateText(SET_MQTT_HOST, storage_mqtthost);
SettingsUpdateText(SET_MQTT_USER, storage_mqttuser);
SettingsUpdateText(SET_MQTT_PWD, storage_mqttpwd);
SettingsUpdateText(SET_MQTT_TOPIC, storage_mqtttopic);
Settings.mqtt_port = mqtt_port;
}
restart_flag = 2;
}
@ -972,7 +994,7 @@ void ArduinoOTAInit(void)
{
ArduinoOTA.setPort(8266);
ArduinoOTA.setHostname(my_hostname);
if (Settings.web_password[0] !=0) { ArduinoOTA.setPassword(Settings.web_password); }
if (strlen(SettingsText(SET_WEBPWD))) { ArduinoOTA.setPassword(SettingsText(SET_WEBPWD)); }
ArduinoOTA.onStart([]()
{
@ -1285,15 +1307,19 @@ void GpioInit(void)
}
#endif // USE_SONOFF_SC
if (!light_type) {
for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only
if (pin[GPIO_PWM1 +i] < 99) {
for (uint32_t i = 0; i < MAX_PWMS; i++) { // Basic PWM control only
if (pin[GPIO_PWM1 +i] < 99) {
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
if (light_type) {
// force PWM GPIOs to low or high mode, see #7165
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range : 0);
} else {
pwm_present = true;
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]);
}
}
}
for (uint32_t i = 0; i < MAX_RELAYS; i++) {
if (pin[GPIO_REL1 +i] < 99) {
pinMode(pin[GPIO_REL1 +i], OUTPUT);

View File

@ -191,18 +191,20 @@ void WifiBegin(uint8_t flag, uint8_t channel)
case 2: // Toggle
Settings.sta_active ^= 1;
} // 3: Current AP
if ('\0' == Settings.sta_ssid[Settings.sta_active][0]) { Settings.sta_active ^= 1; } // Skip empty SSID
if ('\0' == SettingsText(SET_STASSID1 + Settings.sta_active)) {
Settings.sta_active ^= 1; // Skip empty SSID
}
if (Settings.ip_address[0]) {
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);
if (channel) {
WiFi.begin(Settings.sta_ssid[Settings.sta_active], Settings.sta_pwd[Settings.sta_active], channel, Wifi.bssid);
WiFi.begin(SettingsText(SET_STASSID1 + Settings.sta_active), SettingsText(SET_STAPWD1 + Settings.sta_active), channel, Wifi.bssid);
} else {
WiFi.begin(Settings.sta_ssid[Settings.sta_active], Settings.sta_pwd[Settings.sta_active]);
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, Settings.sta_ssid[Settings.sta_active], kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname);
Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), kWifiPhyMode[WiFi.getPhyMode() & 0x3], my_hostname);
#if LWIP_IPV6
for (bool configured = false; !configured;) {
@ -278,10 +280,10 @@ void WifiBeginAfterScan(void)
bool known = false;
uint32_t j;
for (j = 0; j < 2; j++) {
if (ssid_scan == Settings.sta_ssid[j]) { // SSID match
if (ssid_scan == SettingsText(SET_STASSID1 + j)) { // SSID match
known = true;
if (rssi_scan > best_network_db) { // Best network
if (sec_scan == ENC_TYPE_NONE || Settings.sta_pwd[j]) { // Check for passphrase if not open wlan
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
@ -352,6 +354,14 @@ bool WifiCheckIPv6(void)
return ipv6_global;
}
String WifiGetIPv6(void)
{
for (auto a : addrList) {
if(!a.isLocal() && a.isV6()) return a.toString();
}
return "";
}
bool WifiCheckIPAddrStatus(void) // Return false for 169.254.x.x or fe80::/64
{
bool ip_global=false;
@ -426,7 +436,7 @@ void WifiCheckIp(void)
if (!Wifi.retry || ((Wifi.retry_init / 2) == Wifi.retry)) {
AddLog_P(LOG_LEVEL_INFO, S_LOG_WIFI, PSTR(D_CONNECT_FAILED_AP_TIMEOUT));
} else {
if (('\0' == Settings.sta_ssid[0][0]) && ('\0' == Settings.sta_ssid[1][0])) {
if (('\0' == SettingsText(SET_STASSID1)) && ('\0' == SettingsText(SET_STASSID2))) {
wifi_config_tool = WIFI_MANAGER; // Skip empty SSIDs and start Wifi config tool
Wifi.retry = 0;
} else {
@ -472,13 +482,13 @@ void WifiCheck(uint8_t param)
if (Wifi.config_counter) {
if (!Wifi.config_counter) {
if (strlen(WiFi.SSID().c_str())) {
strlcpy(Settings.sta_ssid[0], WiFi.SSID().c_str(), sizeof(Settings.sta_ssid[0]));
SettingsUpdateText(SET_STASSID1, WiFi.SSID().c_str());
}
if (strlen(WiFi.psk().c_str())) {
strlcpy(Settings.sta_pwd[0], WiFi.psk().c_str(), sizeof(Settings.sta_pwd[0]));
SettingsUpdateText(SET_STAPWD1, WiFi.psk().c_str());
}
Settings.sta_active = 0;
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_WCFG_2_WIFIMANAGER D_CMND_SSID "1 %s"), Settings.sta_ssid[0]);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_WCFG_2_WIFIMANAGER D_CMND_SSID "1 %s"), SettingsText(SET_STASSID1));
}
}
if (!Wifi.config_counter) {
@ -617,6 +627,7 @@ void WifiShutdown(void)
void EspRestart(void)
{
WifiShutdown();
CrashDumpClear(); // Clear the stack dump in RTC
// ESP.restart(); // This results in exception 3 on restarts on core 2.3.0
ESP.reset();
}

View File

@ -131,7 +131,7 @@ const uint32_t SOFT_BAUDRATE = 9600; // Default software serial baudrate
const uint32_t APP_BAUDRATE = 115200; // Default serial baudrate
const uint32_t SERIAL_POLLING = 100; // Serial receive polling in ms
const uint32_t ZIGBEE_POLLING = 100; // Serial receive polling in ms
const uint8_t MAX_STATUS = 11; // Max number of status lines
const uint8_t MAX_STATUS = 12; // Max number of status lines
const uint32_t START_VALID_TIME = 1451602800; // Time is synced and after 2016-01-01
@ -272,6 +272,30 @@ enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_PIN_STATE, FUNC_MODULE_INIT, FU
enum AddressConfigSteps { ADDR_IDLE, ADDR_RECEIVE, ADDR_SEND };
enum SettingsTextIndex { SET_OTAURL,
SET_MQTTPREFIX1, SET_MQTTPREFIX2, SET_MQTTPREFIX3,
SET_STASSID1, SET_STASSID2,
SET_STAPWD1, SET_STAPWD2,
SET_HOSTNAME, SET_SYSLOG_HOST,
SET_WEBPWD,
SET_MQTT_HOST, SET_MQTT_CLIENT,
SET_MQTT_USER, SET_MQTT_PWD,
SET_MQTT_FULLTOPIC, SET_MQTT_TOPIC,
SET_MQTT_BUTTON_TOPIC, SET_MQTT_SWITCH_TOPIC, SET_MQTT_GRP_TOPIC,
SET_STATE_TXT1, SET_STATE_TXT2, SET_STATE_TXT3, SET_STATE_TXT4,
SET_NTPSERVER1, SET_NTPSERVER2, SET_NTPSERVER3,
SET_MEM1, SET_MEM2, SET_MEM3, SET_MEM4, SET_MEM5,
SET_CORS,
SET_FRIENDLYNAME1, SET_FRIENDLYNAME2, SET_FRIENDLYNAME3, SET_FRIENDLYNAME4,
// SET_FRIENDLYNAME5, SET_FRIENDLYNAME6, SET_FRIENDLYNAME7, SET_FRIENDLYNAME8, // Future extension
// SET_BUTTON1, SET_BUTTON2, SET_BUTTON3, SET_BUTTON4, // Future extension
// SET_BUTTON5, SET_BUTTON6, SET_BUTTON7, SET_BUTTON8, // Future extension
// SET_BUTTON9, SET_BUTTON10, SET_BUTTON11, SET_BUTTON12, // Future extension
// SET_BUTTON13, SET_BUTTON14, SET_BUTTON15, SET_BUTTON16, // Future extension
SET_MAX };
enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER,
SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER,
SRC_MAX };

View File

@ -74,8 +74,6 @@ const char kCodeImage[] PROGMEM = "tasmota|minimal|sensors|knx|basic|display|ir"
* Global variables
\*********************************************************************************************/
SerialConfig serial_config = SERIAL_8N1; // Serial interface configuration 8 data bits, No parity, 1 stop bit
WiFiUDP PortUdp; // UDP Syslog and Alexa
unsigned long feature_drv1; // Compiled driver feature map
@ -112,7 +110,6 @@ uint32_t web_log_index = 1; // Index in Web log buffer (should n
float global_temperature = 9999; // Provide a global temperature to be used by some sensors
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
char *ota_url; // OTA url string pointer
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
@ -267,13 +264,13 @@ void setup(void)
}
}
Format(mqtt_client, Settings.mqtt_client, sizeof(mqtt_client));
Format(mqtt_topic, Settings.mqtt_topic, sizeof(mqtt_topic));
if (strstr(Settings.hostname, "%") != nullptr) {
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
snprintf_P(my_hostname, sizeof(my_hostname)-1, Settings.hostname, mqtt_topic, ESP.getChipId() & 0x1FFF);
Format(mqtt_client, SettingsText(SET_MQTT_CLIENT), sizeof(mqtt_client));
Format(mqtt_topic, SettingsText(SET_MQTT_TOPIC), sizeof(mqtt_topic));
if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) {
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME), mqtt_topic, ESP.getChipId() & 0x1FFF);
} else {
snprintf_P(my_hostname, sizeof(my_hostname)-1, Settings.hostname);
snprintf_P(my_hostname, sizeof(my_hostname)-1, SettingsText(SET_HOSTNAME));
}
GetEspHardwareType();
@ -332,7 +329,7 @@ void setup(void)
}
blink_powersave = power;
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_ESP8266_RELEASE), PROJECT, Settings.friendlyname[0], my_version, my_image);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_PROJECT " %s %s " D_VERSION " %s%s-" ARDUINO_ESP8266_RELEASE), PROJECT, SettingsText(SET_FRIENDLYNAME1), my_version, my_image);
#ifdef FIRMWARE_MINIMAL
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_WARNING_MINIMAL_VERSION));
#endif // FIRMWARE_MINIMAL
@ -351,15 +348,18 @@ void BacklogLoop(void)
{
if (TimeReached(backlog_delay)) {
if (!BACKLOG_EMPTY && !backlog_mutex) {
backlog_mutex = true;
#ifdef SUPPORT_IF_STATEMENT
ExecuteCommand((char*)backlog.shift().c_str(), SRC_BACKLOG);
backlog_mutex = true;
String cmd = backlog.shift();
backlog_mutex = false;
ExecuteCommand((char*)cmd.c_str(), SRC_BACKLOG);
#else
backlog_mutex = true;
ExecuteCommand((char*)backlog[backlog_pointer].c_str(), SRC_BACKLOG);
backlog_pointer++;
if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; }
#endif
backlog_mutex = false;
#endif
}
}
}

View File

@ -512,7 +512,7 @@ char* ToHex_P(const unsigned char * in, size_t insz, char * out, size_t outsz, c
#undef USE_ARDUINO_OTA // Disable support for Arduino OTA
#undef USE_DOMOTICZ // Disable Domoticz
#undef USE_HOME_ASSISTANT // Disable Home Assistant
#undef USE_MQTT_TLS // Disable TLS support won't work as the MQTTHost is not set
//#undef USE_MQTT_TLS // Disable TLS support won't work as the MQTTHost is not set
#undef USE_KNX // Disable KNX IP Protocol Support
//#undef USE_WEBSERVER // Disable Webserver
#undef USE_WEBSEND_RESPONSE // Disable command WebSend response message (+1k code)

View File

@ -20,6 +20,6 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x07010200;
const uint32_t VERSION = 0x07020000;
#endif // _TASMOTA_VERSION_H_

View File

@ -414,7 +414,8 @@ const char HTTP_FORM_WIFI[] PROGMEM =
"<p><b>" D_AP1_PASSWORD "</b><input type='checkbox' onclick='sp(\"p1\")'><br><input id='p1' type='password' placeholder='" D_AP1_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_AP2_SSID "</b> (" STA_SSID2 ")<br><input id='s2' placeholder='" STA_SSID2 "' value='%s'></p>"
"<p><b>" D_AP2_PASSWORD "</b><input type='checkbox' onclick='sp(\"p2\")'><br><input id='p2' type='password' placeholder='" D_AP2_PASSWORD "' value='" D_ASTERISK_PWD "'></p>"
"<p><b>" D_HOSTNAME "</b> (%s)<br><input id='h' placeholder='%s' value='%s'></p>";
"<p><b>" D_HOSTNAME "</b> (%s)<br><input id='h' placeholder='%s' value='%s'></p>"
"<p><b>" D_CORS_DOMAIN "</b><input id='c' placeholder='" CORS_DOMAIN "' value='%s'></p>";
const char HTTP_FORM_LOG1[] PROGMEM =
"<fieldset><legend><b>&nbsp;" D_LOGGING_PARAMETERS "&nbsp;</b>"
@ -597,7 +598,13 @@ void StartWebserver(int type, IPAddress ipweb)
WebServer->begin(); // Web server start
}
if (Web.state != type) {
#if LWIP_IPV6
String ipv6_addr = WifiGetIPv6();
if(ipv6_addr!="") AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s and IPv6 global address %s "), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str(),ipv6_addr.c_str());
else AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str());
#else
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "", ipweb.toString().c_str());
#endif // LWIP_IPV6 = 1
rules_flag.http_init = 1;
}
if (type) { Web.state = type; }
@ -651,8 +658,8 @@ void PollDnsWebserver(void)
bool WebAuthenticate(void)
{
if (Settings.web_password[0] != 0 && HTTP_MANAGER_RESET_ONLY != Web.state) {
return WebServer->authenticate(WEB_USERNAME, Settings.web_password);
if (strlen(SettingsText(SET_WEBPWD)) && (HTTP_MANAGER_RESET_ONLY != Web.state)) {
return WebServer->authenticate(WEB_USERNAME, SettingsText(SET_WEBPWD));
} else {
return true;
}
@ -673,8 +680,8 @@ bool HttpCheckPriviledgedAccess(bool autorequestauth = true)
void HttpHeaderCors(void)
{
if (Settings.flag3.cors_enabled) { // SetOption73 - Enable HTTP CORS
WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
if (strlen(SettingsText(SET_CORS))) {
WebServer->sendHeader(F("Access-Control-Allow-Origin"), SettingsText(SET_CORS));
}
}
@ -809,7 +816,7 @@ void WSContentSend_PD(const char* formatP, ...) // Content send snprintf_P ch
void WSContentStart_P(const char* title, bool auth)
{
if (auth && (Settings.web_password[0] != 0) && !WebServer->authenticate(WEB_USERNAME, Settings.web_password)) {
if (auth && strlen(SettingsText(SET_WEBPWD)) && !WebServer->authenticate(WEB_USERNAME, SettingsText(SET_WEBPWD))) {
return WebServer->requestAuthentication();
}
@ -818,7 +825,7 @@ void WSContentStart_P(const char* title, bool auth)
if (title != nullptr) {
char ctitle[strlen_P(title) +1];
strcpy_P(ctitle, title); // Get title from flash to RAM
WSContentSend_P(HTTP_HEADER, Settings.friendlyname[0], ctitle);
WSContentSend_P(HTTP_HEADER, SettingsText(SET_FRIENDLYNAME1), ctitle);
}
}
@ -862,7 +869,7 @@ void WSContentSendStyle_P(const char* formatP, ...)
WebColor(COL_TEXT_WARNING),
#endif
WebColor(COL_TITLE),
ModuleName().c_str(), Settings.friendlyname[0]);
ModuleName().c_str(), SettingsText(SET_FRIENDLYNAME1));
if (Settings.flag3.gui_hostname_ip) { // SetOption53 - Show hostanme and IP address in GUI main menu
bool lip = (static_cast<uint32_t>(WiFi.localIP()) != 0);
bool sip = (static_cast<uint32_t>(WiFi.softAPIP()) != 0);
@ -986,10 +993,10 @@ void HandleRoot(void)
if (WifiIsInManagerMode()) {
#ifndef FIRMWARE_MINIMAL
if ((Settings.web_password[0] != 0) && !(WebServer->hasArg("USER1")) && !(WebServer->hasArg("PASS1")) && HTTP_MANAGER_RESET_ONLY != Web.state) {
if (strlen(SettingsText(SET_WEBPWD)) && !(WebServer->hasArg("USER1")) && !(WebServer->hasArg("PASS1")) && HTTP_MANAGER_RESET_ONLY != Web.state) {
HandleWifiLogin();
} else {
if (!(Settings.web_password[0] != 0) || (((WebServer->arg("USER1") == WEB_USERNAME ) && (WebServer->arg("PASS1") == Settings.web_password )) || HTTP_MANAGER_RESET_ONLY == Web.state)) {
if (!strlen(SettingsText(SET_WEBPWD)) || (((WebServer->arg("USER1") == WEB_USERNAME ) && (WebServer->arg("PASS1") == SettingsText(SET_WEBPWD) )) || HTTP_MANAGER_RESET_ONLY == Web.state)) {
HandleWifiConfiguration();
} else {
// wrong user and pass
@ -1106,6 +1113,21 @@ void HandleRoot(void)
} else {
#endif // USE_SONOFF_IFAN
for (uint32_t idx = 1; idx <= devices_present; idx++) {
#ifdef USE_SHUTTER
if (Settings.flag3.shutter_mode) { // SetOption80 - Enable shutter support
bool shutter_used = false;
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
if (Settings.shutter_startrelay[i] == (((idx -1) & 0xFFFFFFFE) +1)) {
shutter_used = true;
break;
}
}
if (shutter_used) {
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (idx % 2) ? "" : "" , "");
continue;
}
}
#endif // USE_SHUTTER
snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx);
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (devices_present < 5) ? D_BUTTON_TOGGLE : "", (devices_present > 1) ? stemp : "");
}
@ -1638,11 +1660,11 @@ void HandleWifiConfiguration(void)
int quality = WifiGetRssiAsQuality(WiFi.RSSI(indices[i]));
int auth = WiFi.encryptionType(indices[i]);
char encryption[20];
WSContentSend_P(PSTR("<div><a href='#p' onclick='c(this)'>%s</a>&nbsp;(%d)&nbsp<span class='q'>%s %d%%</span></div>"),
WSContentSend_P(PSTR("<div><a href='#p' onclick='c(this)'>%s</a>&nbsp;(%d)&nbsp<span class='q'>%s %d%% (%d dBm)</span></div>"),
HtmlEscape(WiFi.SSID(indices[i])).c_str(),
WiFi.channel(indices[i]),
GetTextIndexed(encryption, sizeof(encryption), auth +1, kEncryptionType),
quality
quality, WiFi.RSSI()
);
delay(0);
@ -1654,7 +1676,7 @@ void HandleWifiConfiguration(void)
}
// As WIFI_HOSTNAME may contain %s-%04d it cannot be part of HTTP_FORM_WIFI where it will exception
WSContentSend_P(HTTP_FORM_WIFI, Settings.sta_ssid[0], Settings.sta_ssid[1], WIFI_HOSTNAME, WIFI_HOSTNAME, Settings.hostname);
WSContentSend_P(HTTP_FORM_WIFI, SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), WIFI_HOSTNAME, WIFI_HOSTNAME, SettingsText(SET_HOSTNAME), SettingsText(SET_CORS));
WSContentSend_P(HTTP_FORM_END);
}
@ -1671,22 +1693,25 @@ void HandleWifiConfiguration(void)
void WifiSaveSettings(void)
{
char tmp[sizeof(Settings.sta_pwd[0])]; // Max length is currently 65
char tmp[100]; // Max length is currently 65
WebGetArg("h", tmp, sizeof(tmp));
strlcpy(Settings.hostname, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp, sizeof(Settings.hostname));
if (strstr(Settings.hostname, "%") != nullptr) {
strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname));
SettingsUpdateText(SET_HOSTNAME, (!strlen(tmp)) ? WIFI_HOSTNAME : tmp);
if (strstr(SettingsText(SET_HOSTNAME), "%") != nullptr) {
SettingsUpdateText(SET_HOSTNAME, WIFI_HOSTNAME);
}
WebGetArg("c", tmp, sizeof(tmp));
SettingsUpdateText(SET_CORS, (!strlen(tmp)) ? CORS_DOMAIN : tmp);
WebGetArg("s1", tmp, sizeof(tmp));
strlcpy(Settings.sta_ssid[0], (!strlen(tmp)) ? STA_SSID1 : tmp, sizeof(Settings.sta_ssid[0]));
SettingsUpdateText(SET_STASSID1, (!strlen(tmp)) ? STA_SSID1 : tmp);
WebGetArg("s2", tmp, sizeof(tmp));
strlcpy(Settings.sta_ssid[1], (!strlen(tmp)) ? STA_SSID2 : tmp, sizeof(Settings.sta_ssid[1]));
SettingsUpdateText(SET_STASSID2, (!strlen(tmp)) ? STA_SSID2 : tmp);
WebGetArg("p1", tmp, sizeof(tmp));
strlcpy(Settings.sta_pwd[0], (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? Settings.sta_pwd[0] : tmp, sizeof(Settings.sta_pwd[0]));
SettingsUpdateText(SET_STAPWD1, (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? SettingsText(SET_STAPWD1) : tmp);
WebGetArg("p2", tmp, sizeof(tmp));
strlcpy(Settings.sta_pwd[1], (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? Settings.sta_pwd[1] : tmp, sizeof(Settings.sta_pwd[1]));
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CMND_HOSTNAME " %s, " D_CMND_SSID "1 %s, " D_CMND_SSID "2 %s"), Settings.hostname, Settings.sta_ssid[0], Settings.sta_ssid[1]);
SettingsUpdateText(SET_STAPWD2, (!strlen(tmp)) ? "" : (strlen(tmp) < 5) ? SettingsText(SET_STAPWD2) : tmp);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_WIFI D_CMND_HOSTNAME " %s, " D_CMND_SSID "1 %s, " D_CMND_SSID "2 %s, " D_CMND_CORS " %s"),
SettingsText(SET_HOSTNAME), SettingsText(SET_STASSID1), SettingsText(SET_STASSID2), SettingsText(SET_CORS));
}
/*-------------------------------------------------------------------------------------------*/
@ -1723,7 +1748,7 @@ void HandleLoggingConfiguration(void)
}
WSContentSend_P(PSTR("</select></p>"));
}
WSContentSend_P(HTTP_FORM_LOG2, Settings.syslog_host, Settings.syslog_port, Settings.tele_period);
WSContentSend_P(HTTP_FORM_LOG2, SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, Settings.tele_period);
WSContentSend_P(HTTP_FORM_END);
WSContentSpaceButton(BUTTON_CONFIGURATION);
WSContentStop();
@ -1731,7 +1756,7 @@ void HandleLoggingConfiguration(void)
void LoggingSaveSettings(void)
{
char tmp[sizeof(Settings.syslog_host)]; // Max length is currently 33
char tmp[100]; // Max length is currently 33
WebGetArg("l0", tmp, sizeof(tmp));
SetSeriallog((!strlen(tmp)) ? SERIAL_LOG_LEVEL : atoi(tmp));
@ -1742,7 +1767,7 @@ void LoggingSaveSettings(void)
WebGetArg("l3", tmp, sizeof(tmp));
SetSyslog((!strlen(tmp)) ? SYS_LOG_LEVEL : atoi(tmp));
WebGetArg("lh", tmp, sizeof(tmp));
strlcpy(Settings.syslog_host, (!strlen(tmp)) ? SYS_LOG_HOST : tmp, sizeof(Settings.syslog_host));
SettingsUpdateText(SET_SYSLOG_HOST, (!strlen(tmp)) ? SYS_LOG_HOST : tmp);
WebGetArg("lp", tmp, sizeof(tmp));
Settings.syslog_port = (!strlen(tmp)) ? SYS_LOG_PORT : atoi(tmp);
WebGetArg("lt", tmp, sizeof(tmp));
@ -1751,7 +1776,7 @@ void LoggingSaveSettings(void)
Settings.tele_period = 10; // Do not allow periods < 10 seconds
}
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_LOG D_CMND_SERIALLOG " %d, " D_CMND_WEBLOG " %d, " D_CMND_MQTTLOG " %d, " D_CMND_SYSLOG " %d, " D_CMND_LOGHOST " %s, " D_CMND_LOGPORT " %d, " D_CMND_TELEPERIOD " %d"),
Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level, Settings.syslog_host, Settings.syslog_port, Settings.tele_period);
Settings.seriallog_level, Settings.weblog_level, Settings.mqttlog_level, Settings.syslog_level, SettingsText(SET_SYSLOG_HOST), Settings.syslog_port, Settings.tele_period);
}
/*-------------------------------------------------------------------------------------------*/
@ -1787,7 +1812,7 @@ void HandleOtherConfiguration(void)
(i) ? stemp : "",
i,
(i) ? stemp : "",
Settings.friendlyname[i]);
SettingsText(SET_FRIENDLYNAME1 + i));
}
#ifdef USE_EMULATION
@ -1819,10 +1844,10 @@ void OtherSaveSettings(void)
{
char tmp[128];
char webindex[5];
char friendlyname[sizeof(Settings.friendlyname[0])];
char friendlyname[TOPSZ];
WebGetArg("wp", tmp, sizeof(tmp));
strlcpy(Settings.web_password, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? Settings.web_password : tmp, sizeof(Settings.web_password));
SettingsUpdateText(SET_WEBPWD, (!strlen(tmp)) ? "" : (strchr(tmp,'*')) ? SettingsText(SET_WEBPWD) : tmp);
Settings.flag.mqtt_enabled = WebServer->hasArg("b1"); // SetOption3 - Enable MQTT
#ifdef USE_EMULATION
WebGetArg("b2", tmp, sizeof(tmp));
@ -1833,8 +1858,8 @@ void OtherSaveSettings(void)
snprintf_P(webindex, sizeof(webindex), PSTR("a%d"), i);
WebGetArg(webindex, tmp, sizeof(tmp));
snprintf_P(friendlyname, sizeof(friendlyname), PSTR(FRIENDLY_NAME"%d"), i +1);
strlcpy(Settings.friendlyname[i], (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp, sizeof(Settings.friendlyname[i]));
snprintf_P(log_data, sizeof(log_data), PSTR("%s%s %s"), log_data, (i) ? "," : "", Settings.friendlyname[i]);
SettingsUpdateText(SET_FRIENDLYNAME1 +i, (!strlen(tmp)) ? (i) ? friendlyname : FRIENDLY_NAME : tmp);
snprintf_P(log_data, sizeof(log_data), PSTR("%s%s %s"), log_data, (i) ? "," : "", SettingsText(SET_FRIENDLYNAME1 +i));
}
AddLog(LOG_LEVEL_INFO);
WebGetArg("t1", tmp, sizeof(tmp));
@ -1866,8 +1891,8 @@ void HandleBackupConfiguration(void)
char attachment[100];
// char friendlyname[sizeof(Settings.friendlyname[0])];
// snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(friendlyname, Settings.friendlyname[0]), my_version);
// char friendlyname[TOPSZ];
// snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(friendlyname, SettingsText(SET_FRIENDLYNAME1)), my_version);
char hostname[sizeof(my_hostname)];
snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), NoAlNumToUnderscore(hostname, my_hostname), my_version);
@ -1967,11 +1992,17 @@ void HandleInformation(void)
if (IsModuleIfan()) { maxfn = 1; }
#endif // USE_SONOFF_IFAN
for (uint32_t i = 0; i < maxfn; i++) {
WSContentSend_P(PSTR("}1" D_FRIENDLY_NAME " %d}2%s"), i +1, Settings.friendlyname[i]);
WSContentSend_P(PSTR("}1" D_FRIENDLY_NAME " %d}2%s"), i +1, SettingsText(SET_FRIENDLYNAME1 +i));
}
WSContentSend_P(PSTR("}1}2&nbsp;")); // Empty line
WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%)"), Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active], WifiGetRssiAsQuality(WiFi.RSSI()));
WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%, %d dBm)"), Settings.sta_active +1, SettingsText(SET_STASSID1 + Settings.sta_active), WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.RSSI());
WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), my_hostname, (Wifi.mdns_begun) ? ".local" : "");
#if LWIP_IPV6
String ipv6_addr = WifiGetIPv6();
if(ipv6_addr != ""){
WSContentSend_P(PSTR("}1 IPv6 Address }2%s"), ipv6_addr.c_str());
}
#endif
if (static_cast<uint32_t>(WiFi.localIP()) != 0) {
WSContentSend_P(PSTR("}1" D_IP_ADDRESS "}2%s"), WiFi.localIP().toString().c_str());
WSContentSend_P(PSTR("}1" D_GATEWAY "}2%s"), IPAddress(Settings.ip_address[1]).toString().c_str());
@ -1986,17 +2017,12 @@ void HandleInformation(void)
}
WSContentSend_P(PSTR("}1}2&nbsp;")); // Empty line
if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
#ifdef USE_MQTT_AWS_IOT
WSContentSend_P(PSTR("}1" D_MQTT_HOST "}2%s%s"), Settings.mqtt_user, Settings.mqtt_host);
WSContentSend_P(PSTR("}1" D_MQTT_HOST "}2%s"), SettingsText(SET_MQTT_HOST));
WSContentSend_P(PSTR("}1" D_MQTT_PORT "}2%d"), Settings.mqtt_port);
#else
WSContentSend_P(PSTR("}1" D_MQTT_HOST "}2%s"), Settings.mqtt_host);
WSContentSend_P(PSTR("}1" D_MQTT_PORT "}2%d"), Settings.mqtt_port);
WSContentSend_P(PSTR("}1" D_MQTT_USER "}2%s"), Settings.mqtt_user);
#endif
WSContentSend_P(PSTR("}1" D_MQTT_USER "}2%s"), SettingsText(SET_MQTT_USER));
WSContentSend_P(PSTR("}1" D_MQTT_CLIENT "}2%s"), mqtt_client);
WSContentSend_P(PSTR("}1" D_MQTT_TOPIC "}2%s"), Settings.mqtt_topic);
// WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC "}2%s"), Settings.mqtt_grptopic);
WSContentSend_P(PSTR("}1" D_MQTT_TOPIC "}2%s"), SettingsText(SET_MQTT_TOPIC));
// WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC "}2%s"), SettingsText(SET_MQTT_GRP_TOPIC));
WSContentSend_P(PSTR("}1" D_MQTT_GROUP_TOPIC "}2%s"), GetGroupTopic_P(stopic, ""));
WSContentSend_P(PSTR("}1" D_MQTT_FULL_TOPIC "}2%s"), GetTopic_P(stopic, CMND, mqtt_topic, ""));
WSContentSend_P(PSTR("}1" D_MQTT " " D_FALLBACK_TOPIC "}2%s"), GetFallbackTopic_P(stopic, ""));
@ -2055,7 +2081,7 @@ void HandleUpgradeFirmware(void)
WSContentStart_P(S_FIRMWARE_UPGRADE);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_UPG, Settings.ota_url);
WSContentSend_P(HTTP_FORM_UPG, SettingsText(SET_OTAURL));
WSContentSend_P(HTTP_FORM_RST_UPG, D_UPGRADE);
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
@ -2068,12 +2094,12 @@ void HandleUpgradeFirmwareStart(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
char command[sizeof(Settings.ota_url) + 10]; // OtaUrl
char command[128]; // OtaUrl
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_UPGRADE_STARTED));
WifiConfigCounter();
char otaurl[sizeof(Settings.ota_url)];
char otaurl[101];
WebGetArg("o", otaurl, sizeof(otaurl));
if (strlen(otaurl)) {
snprintf_P(command, sizeof(command), PSTR(D_CMND_OTAURL " %s"), otaurl);
@ -2382,12 +2408,12 @@ void HandleHttpCommand(void)
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND));
bool valid = true;
if (Settings.web_password[0] != 0) {
char tmp1[sizeof(Settings.web_password)];
if (strlen(SettingsText(SET_WEBPWD))) {
char tmp1[33];
WebGetArg("user", tmp1, sizeof(tmp1));
char tmp2[sizeof(Settings.web_password)];
char tmp2[strlen(SettingsText(SET_WEBPWD)) +1];
WebGetArg("password", tmp2, sizeof(tmp2));
if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, Settings.web_password))) { valid = false; }
if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, SettingsText(SET_WEBPWD)))) { valid = false; }
}
WSContentBegin(200, CT_JSON);
@ -2698,7 +2724,7 @@ const char kWebCommands[] PROGMEM = "|" // No prefix
#ifdef USE_SENDMAIL
D_CMND_SENDMAIL "|"
#endif
D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|" D_CMND_WEBSENSOR;
D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|" D_CMND_WEBSENSOR "|" D_CMND_CORS;
void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION
@ -2707,7 +2733,7 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_SENDMAIL
&CmndSendmail,
#endif
&CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, &CmndWebSensor };
&CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, &CmndWebSensor, &CmndCors };
/*********************************************************************************************\
* Commands
@ -2760,9 +2786,9 @@ void CmndWebServer(void)
void CmndWebPassword(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.web_password))) {
strlcpy(Settings.web_password, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data, sizeof(Settings.web_password));
ResponseCmndChar(Settings.web_password);
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_WEBPWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
ResponseCmndChar(SettingsText(SET_WEBPWD));
} else {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
@ -2828,6 +2854,14 @@ void CmndWebSensor(void)
ResponseJsonEnd();
}
void CmndCors(void)
{
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_CORS, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
}
ResponseCmndChar(SettingsText(SET_CORS));
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/

View File

@ -91,10 +91,6 @@ tls_dir_t tls_dir; // memory copy of tls_dir from flash
#endif // USE_MQTT_AWS_IOT
// A typical AWS IoT endpoint is 50 characters long, it does not fit
// in MqttHost field (32 chars). We need to concatenate both MqttUser and MqttHost
char AWS_endpoint[65]; // aWS IOT endpoint, concatenation of user and host
// check whether the fingerprint is filled with a single value
// Filled with 0x00 = accept any fingerprint and learn it for next time
// Filled with 0xFF = accept any fingerpring forever
@ -106,21 +102,6 @@ bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value) {
}
return true;
}
#ifdef USE_MQTT_AWS_IOT
void setLongMqttHost(const char *mqtt_host) {
if (strlen(mqtt_host) <= sizeof(Settings.mqtt_host)) {
strlcpy(Settings.mqtt_host, mqtt_host, sizeof(Settings.mqtt_host));
Settings.mqtt_user[0] = 0;
} else {
// need to split in mqtt_user first then mqtt_host
strlcpy(Settings.mqtt_user, mqtt_host, sizeof(Settings.mqtt_user));
strlcpy(Settings.mqtt_host, &mqtt_host[sizeof(Settings.mqtt_user)-1], sizeof(Settings.mqtt_host));
}
strlcpy(AWS_endpoint, mqtt_host, sizeof(AWS_endpoint));
}
#endif // USE_MQTT_AWS_IOT
#endif // USE_MQTT_TLS
void MakeValidMqtt(uint32_t option, char* str)
@ -165,10 +146,10 @@ void MqttDiscoverServer(void)
}
}
#endif // MDNS_HOSTNAME
snprintf_P(Settings.mqtt_host, sizeof(Settings.mqtt_host), MDNS.IP(i).toString().c_str());
SettingsUpdateText(SET_MQTT_HOST, MDNS.IP(i).toString().c_str());
Settings.mqtt_port = MDNS.port(i);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_MQTT_SERVICE_FOUND " %s, " D_IP_ADDRESS " %s, " D_PORT " %d"), MDNS.hostname(i).c_str(), Settings.mqtt_host, Settings.mqtt_port);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_MQTT_SERVICE_FOUND " %s, " D_IP_ADDRESS " %s, " D_PORT " %d"), MDNS.hostname(i).c_str(), SettingsText(SET_MQTT_HOST), Settings.mqtt_port);
}
}
#endif // MQTT_HOST_DISCOVERY
@ -202,8 +183,6 @@ void MqttInit(void)
tlsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
#ifdef USE_MQTT_AWS_IOT
snprintf_P(AWS_endpoint, sizeof(AWS_endpoint), PSTR("%s%s"), Settings.mqtt_user, Settings.mqtt_host);
loadTlsDir(); // load key and certificate data from Flash
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
AWS_IoT_Private_Key,
@ -261,8 +240,8 @@ void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len
if (data_len >= MQTT_MAX_PACKET_SIZE) { return; }
// Do not execute multiple times if Prefix1 equals Prefix2
if (!strcmp(Settings.mqtt_prefix[0], Settings.mqtt_prefix[1])) {
char *str = strstr(mqtt_topic, Settings.mqtt_prefix[0]);
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;
@ -330,8 +309,8 @@ void MqttPublishLogging(const char *mxtime)
GetTopic_P(stopic, STAT, mqtt_topic, romram);
char *me;
if (!strcmp(Settings.mqtt_prefix[0], Settings.mqtt_prefix[1])) {
me = strstr(stopic, Settings.mqtt_prefix[0]);
if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
me = strstr(stopic, SettingsText(SET_MQTTPREFIX1));
if (me == stopic) {
mqtt_cmnd_publish += 3;
}
@ -389,8 +368,8 @@ void MqttPublish(const char* topic, bool retained)
retained = false; // AWS IoT does not support retained, it will disconnect if received
#endif
if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1])) {
me = strstr(topic,Settings.mqtt_prefix[0]);
if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
me = strstr(topic, SettingsText(SET_MQTTPREFIX1));
if (me == topic) {
mqtt_cmnd_publish += 3;
}
@ -505,11 +484,7 @@ void MqttDisconnected(int state)
MqttClient.disconnect();
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), AWS_endpoint, Settings.mqtt_port, state, Mqtt.retry_counter);
#else
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), Settings.mqtt_host, Settings.mqtt_port, state, Mqtt.retry_counter);
#endif
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), SettingsText(SET_MQTT_HOST), Settings.mqtt_port, state, Mqtt.retry_counter);
rules_flag.mqtt_disconnected = 1;
}
@ -533,7 +508,7 @@ void MqttConnected(void)
GetTopic_P(stopic, CMND, mqtt_topic, PSTR("#"));
MqttSubscribe(stopic);
if (strstr_P(Settings.mqtt_fulltopic, MQTT_TOKEN_TOPIC) != nullptr) {
if (strstr_P(SettingsText(SET_MQTT_FULLTOPIC), MQTT_TOKEN_TOPIC) != nullptr) {
GetGroupTopic_P(stopic, PSTR("#")); // SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing/<grouptopic>/# or SetOption75 1: cmnd/<grouptopic>
MqttSubscribe(stopic);
GetFallbackTopic_P(stopic, PSTR("#"));
@ -550,12 +525,23 @@ void MqttConnected(void)
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1"));
#ifdef USE_WEBSERVER
if (Settings.webserver) {
#if LWIP_IPV6
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"IPv6Address\":\"%s\"}"),
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str(),WifiGetIPv6().c_str());
#else
Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}"),
(2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str());
#endif // LWIP_IPV6 = 1
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2"));
}
#endif // USE_WEBSERVER
Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":\"%s\"}"), GetResetReasonInfo().c_str());
Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":"));
if (ResetReason() == REASON_EXCEPTION_RST) {
CrashDump();
} else {
ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str());
}
ResponseJsonEnd();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3"));
MqttPublishAllPowerState();
if (Settings.tele_period) {
@ -583,7 +569,7 @@ void MqttReconnect(void)
MqttDiscoverServer();
#endif // MQTT_HOST_DISCOVERY
#endif // USE_DISCOVERY
if (!strlen(Settings.mqtt_host) || !Settings.mqtt_port) {
if (!strlen(SettingsText(SET_MQTT_HOST)) || !Settings.mqtt_port) {
Mqtt.allowed = false;
}
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
@ -610,8 +596,12 @@ void MqttReconnect(void)
char *mqtt_user = nullptr;
char *mqtt_pwd = nullptr;
if (strlen(Settings.mqtt_user) > 0) mqtt_user = Settings.mqtt_user;
if (strlen(Settings.mqtt_pwd) > 0) mqtt_pwd = Settings.mqtt_pwd;
if (strlen(SettingsText(SET_MQTT_USER))) {
mqtt_user = SettingsText(SET_MQTT_USER);
}
if (strlen(SettingsText(SET_MQTT_PWD))) {
mqtt_pwd = SettingsText(SET_MQTT_PWD);
}
GetTopic_P(stopic, TELE, mqtt_topic, S_LWT);
Response_P(S_OFFLINE);
@ -634,10 +624,8 @@ void MqttReconnect(void)
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
AWS_IoT_Private_Key,
0xFFFF /* all usages, don't care */, 0);
MqttClient.setServer(AWS_endpoint, Settings.mqtt_port);
#else
MqttClient.setServer(Settings.mqtt_host, Settings.mqtt_port);
#endif
MqttClient.setServer(SettingsText(SET_MQTT_HOST), Settings.mqtt_port);
uint32_t mqtt_connect_time = millis();
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
@ -651,7 +639,7 @@ void MqttReconnect(void)
tlsClient->setPubKeyFingerprint(Settings.mqtt_fingerprint[0], Settings.mqtt_fingerprint[1], allow_all_fingerprints);
#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), AWS_endpoint);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), SettingsText(SET_MQTT_HOST));
//if (MqttClient.connect(mqtt_client, nullptr, nullptr, nullptr, 0, false, nullptr)) {
if (MqttClient.connect(mqtt_client, nullptr, nullptr, stopic, 1, false, mqtt_data, MQTT_CLEAN_SESSION)) {
#else
@ -711,7 +699,7 @@ void MqttCheck(void)
if (!Mqtt.retry_counter) {
#ifdef USE_DISCOVERY
#ifdef MQTT_HOST_DISCOVERY
if (!strlen(Settings.mqtt_host) && !Wifi.mdns_begun) { return; }
if (!strlen(SettingsText(SET_MQTT_HOST)) && !Wifi.mdns_begun) { return; }
#endif // MQTT_HOST_DISCOVERY
#endif // USE_DISCOVERY
MqttReconnect();
@ -752,18 +740,18 @@ void CmndMqttFingerprint(void)
#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
void CmndMqttUser(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_user))) {
strlcpy(Settings.mqtt_user, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_USER : XdrvMailbox.data, sizeof(Settings.mqtt_user));
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_MQTT_USER, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_USER : XdrvMailbox.data);
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_user);
ResponseCmndChar(SettingsText(SET_MQTT_USER));
}
void CmndMqttPassword(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_pwd))) {
strlcpy(Settings.mqtt_pwd, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_PASS : XdrvMailbox.data, sizeof(Settings.mqtt_pwd));
ResponseCmndChar(Settings.mqtt_pwd);
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_MQTT_PWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_PASS : XdrvMailbox.data);
ResponseCmndChar(SettingsText(SET_MQTT_PWD));
restart_flag = 2;
} else {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
@ -781,19 +769,11 @@ void CmndMqttlog(void)
void CmndMqttHost(void)
{
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len <= sizeof(Settings.mqtt_host) + sizeof(Settings.mqtt_user) - 2)) {
setLongMqttHost((SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data);
if (XdrvMailbox.data_len > 0) {
SettingsUpdateText(SET_MQTT_HOST, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data);
restart_flag = 2;
}
ResponseCmndChar(AWS_endpoint);
#else
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_host))) {
strlcpy(Settings.mqtt_host, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data, sizeof(Settings.mqtt_host));
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_host);
#endif
ResponseCmndChar(SettingsText(SET_MQTT_HOST));
}
void CmndMqttPort(void)
@ -817,11 +797,11 @@ void CmndMqttRetry(void)
void CmndStateText(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.state_text[0]))) {
if (XdrvMailbox.data_len > 0) {
for (uint32_t i = 0; i <= XdrvMailbox.data_len; i++) {
if (XdrvMailbox.data[i] == ' ') XdrvMailbox.data[i] = '_';
}
strlcpy(Settings.state_text[XdrvMailbox.index -1], XdrvMailbox.data, sizeof(Settings.state_text[0]));
SettingsUpdateText(SET_STATE_TXT1 + XdrvMailbox.index -1, XdrvMailbox.data);
}
ResponseCmndIdxChar(GetStateText(XdrvMailbox.index -1));
}
@ -829,40 +809,41 @@ void CmndStateText(void)
void CmndMqttClient(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_client))) {
strlcpy(Settings.mqtt_client, (SC_DEFAULT == Shortcut()) ? MQTT_CLIENT_ID : XdrvMailbox.data, sizeof(Settings.mqtt_client));
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
SettingsUpdateText(SET_MQTT_CLIENT, (SC_DEFAULT == Shortcut()) ? MQTT_CLIENT_ID : XdrvMailbox.data);
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_client);
ResponseCmndChar(SettingsText(SET_MQTT_CLIENT));
}
void CmndFullTopic(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_fulltopic))) {
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(1, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_FULLTOPIC : XdrvMailbox.data, sizeof(stemp1));
if (strcmp(stemp1, Settings.mqtt_fulltopic)) {
if (strcmp(stemp1, SettingsText(SET_MQTT_FULLTOPIC))) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); // Offline or remove previous retained topic
strlcpy(Settings.mqtt_fulltopic, stemp1, sizeof(Settings.mqtt_fulltopic));
SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp1);
restart_flag = 2;
}
}
ResponseCmndChar(Settings.mqtt_fulltopic);
ResponseCmndChar(SettingsText(SET_MQTT_FULLTOPIC));
}
void CmndPrefix(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_prefix[0]))) {
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
strlcpy(Settings.mqtt_prefix[XdrvMailbox.index -1], (SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index)?SUB_PREFIX:(2==XdrvMailbox.index)?PUB_PREFIX:PUB_PREFIX2 : XdrvMailbox.data, sizeof(Settings.mqtt_prefix[0]));
// if (Settings.mqtt_prefix[XdrvMailbox.index -1][strlen(Settings.mqtt_prefix[XdrvMailbox.index -1])] == '/') Settings.mqtt_prefix[XdrvMailbox.index -1][strlen(Settings.mqtt_prefix[XdrvMailbox.index -1])] = 0;
SettingsUpdateText(SET_MQTTPREFIX1 + XdrvMailbox.index -1,
(SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index) ? SUB_PREFIX : (2==XdrvMailbox.index) ? PUB_PREFIX : PUB_PREFIX2 : XdrvMailbox.data);
restart_flag = 2;
}
ResponseCmndIdxChar(Settings.mqtt_prefix[XdrvMailbox.index -1]);
ResponseCmndIdxChar(SettingsText(SET_MQTTPREFIX1 + XdrvMailbox.index -1));
}
}
@ -888,60 +869,60 @@ void CmndPublish(void)
void CmndGroupTopic(void)
{
if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_grptopic))) {
if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
strlcpy(Settings.mqtt_grptopic, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data, sizeof(Settings.mqtt_grptopic));
SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
restart_flag = 2;
}
ResponseCmndChar(Settings.mqtt_grptopic);
ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));
}
void CmndTopic(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_topic))) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_TOPIC : XdrvMailbox.data, sizeof(stemp1));
if (strcmp(stemp1, Settings.mqtt_topic)) {
if (strcmp(stemp1, SettingsText(SET_MQTT_TOPIC))) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); // Offline or remove previous retained topic
strlcpy(Settings.mqtt_topic, stemp1, sizeof(Settings.mqtt_topic));
SettingsUpdateText(SET_MQTT_TOPIC, stemp1);
restart_flag = 2;
}
}
ResponseCmndChar(Settings.mqtt_topic);
ResponseCmndChar(SettingsText(SET_MQTT_TOPIC));
}
void CmndButtonTopic(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.button_topic))) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
case SC_CLEAR: strlcpy(Settings.button_topic, "", sizeof(Settings.button_topic)); break;
case SC_DEFAULT: strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic)); break;
case SC_USER: strlcpy(Settings.button_topic, MQTT_BUTTON_TOPIC, sizeof(Settings.button_topic)); break;
default: strlcpy(Settings.button_topic, XdrvMailbox.data, sizeof(Settings.button_topic));
case SC_CLEAR: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, ""); break;
case SC_DEFAULT: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, mqtt_topic); break;
case SC_USER: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC); break;
default: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, XdrvMailbox.data);
}
}
ResponseCmndChar(Settings.button_topic);
ResponseCmndChar(SettingsText(SET_MQTT_BUTTON_TOPIC));
}
void CmndSwitchTopic(void)
{
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.switch_topic))) {
if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
case SC_CLEAR: strlcpy(Settings.switch_topic, "", sizeof(Settings.switch_topic)); break;
case SC_DEFAULT: strlcpy(Settings.switch_topic, mqtt_topic, sizeof(Settings.switch_topic)); break;
case SC_USER: strlcpy(Settings.switch_topic, MQTT_SWITCH_TOPIC, sizeof(Settings.switch_topic)); break;
default: strlcpy(Settings.switch_topic, XdrvMailbox.data, sizeof(Settings.switch_topic));
case SC_CLEAR: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, ""); break;
case SC_DEFAULT: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, mqtt_topic); break;
case SC_USER: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC); break;
default: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, XdrvMailbox.data);
}
}
ResponseCmndChar(Settings.switch_topic);
ResponseCmndChar(SettingsText(SET_MQTT_SWITCH_TOPIC));
}
void CmndButtonRetain(void)
@ -1071,6 +1052,9 @@ void CmndTlsKey(void) {
}
memcpy_P(spi_buffer, tls_spi_start, tls_spi_len);
// remove any white space from the base64
RemoveAllSpaces(XdrvMailbox.data);
// allocate buffer for decoded base64
uint32_t bin_len = decode_base64_length((unsigned char*)XdrvMailbox.data);
uint8_t *bin_buf = nullptr;
@ -1206,22 +1190,18 @@ void HandleMqttConfiguration(void)
return;
}
char str[sizeof(Settings.mqtt_client)];
char str[33];
WSContentStart_P(S_CONFIGURE_MQTT);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_MQTT1,
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AWS_endpoint,
#else
Settings.mqtt_host,
#endif
SettingsText(SET_MQTT_HOST),
Settings.mqtt_port,
Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, Settings.mqtt_client);
Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, SettingsText(SET_MQTT_CLIENT));
WSContentSend_P(HTTP_FORM_MQTT2,
(Settings.mqtt_user[0] == '\0') ? "0" : Settings.mqtt_user,
Format(str, MQTT_TOPIC, sizeof(str)), MQTT_TOPIC, Settings.mqtt_topic,
MQTT_FULLTOPIC, MQTT_FULLTOPIC, Settings.mqtt_fulltopic);
(!strlen(SettingsText(SET_MQTT_USER))) ? "0" : SettingsText(SET_MQTT_USER),
Format(str, MQTT_TOPIC, sizeof(str)), MQTT_TOPIC, SettingsText(SET_MQTT_TOPIC),
MQTT_FULLTOPIC, MQTT_FULLTOPIC, SettingsText(SET_MQTT_FULLTOPIC));
WSContentSend_P(HTTP_FORM_END);
WSContentSpaceButton(BUTTON_CONFIGURATION);
WSContentStop();
@ -1239,32 +1219,28 @@ void MqttSaveSettings(void)
WebGetArg("mf", tmp, sizeof(tmp));
strlcpy(stemp2, (!strlen(tmp)) ? MQTT_FULLTOPIC : tmp, sizeof(stemp2));
MakeValidMqtt(1, stemp2);
if ((strcmp(stemp, Settings.mqtt_topic)) || (strcmp(stemp2, Settings.mqtt_fulltopic))) {
if ((strcmp(stemp, SettingsText(SET_MQTT_TOPIC))) || (strcmp(stemp2, SettingsText(SET_MQTT_FULLTOPIC)))) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, S_LWT, true); // Offline or remove previous retained topic
}
strlcpy(Settings.mqtt_topic, stemp, sizeof(Settings.mqtt_topic));
strlcpy(Settings.mqtt_fulltopic, stemp2, sizeof(Settings.mqtt_fulltopic));
SettingsUpdateText(SET_MQTT_TOPIC, stemp);
SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp2);
WebGetArg("mh", tmp, sizeof(tmp));
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
setLongMqttHost((!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp);
#else
strlcpy(Settings.mqtt_host, (!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp, sizeof(Settings.mqtt_host));
#endif
SettingsUpdateText(SET_MQTT_HOST, (!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp);
WebGetArg("ml", tmp, sizeof(tmp));
Settings.mqtt_port = (!strlen(tmp)) ? MQTT_PORT : atoi(tmp);
WebGetArg("mc", tmp, sizeof(tmp));
strlcpy(Settings.mqtt_client, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp, sizeof(Settings.mqtt_client));
SettingsUpdateText(SET_MQTT_CLIENT, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp);
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
AWS_endpoint, Settings.mqtt_port, Settings.mqtt_client, Settings.mqtt_topic, Settings.mqtt_fulltopic);
SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC));
#else // USE_MQTT_AWS_IOT
WebGetArg("mu", tmp, sizeof(tmp));
strlcpy(Settings.mqtt_user, (!strlen(tmp)) ? MQTT_USER : (!strcmp(tmp,"0")) ? "" : tmp, sizeof(Settings.mqtt_user));
SettingsUpdateText(SET_MQTT_USER, (!strlen(tmp)) ? MQTT_USER : (!strcmp(tmp,"0")) ? "" : tmp);
WebGetArg("mp", tmp, sizeof(tmp));
strlcpy(Settings.mqtt_pwd, (!strlen(tmp)) ? "" : (!strcmp(tmp, D_ASTERISK_PWD)) ? Settings.mqtt_pwd : tmp, sizeof(Settings.mqtt_pwd));
SettingsUpdateText(SET_MQTT_PWD, (!strlen(tmp)) ? "" : (!strcmp(tmp, D_ASTERISK_PWD)) ? SettingsText(SET_MQTT_PWD) : tmp);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_MQTTUSER " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client, Settings.mqtt_user, Settings.mqtt_topic, Settings.mqtt_fulltopic);
SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_USER), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC));
#endif
}
#endif // USE_WEBSERVER

View File

@ -554,10 +554,10 @@ const char HTTP_TIMER_SCRIPT2[] PROGMEM =
"o=qs('#ho');"
"e=o.childElementCount;"
"if(b==1){"
"qs('#dr').disabled='';"
"qs('#dr').style.visibility='';"
"if(e>12){for(i=12;i<=23;i++){o.removeChild(o.lastElementChild);}}" // Create offset hours select options
"}else{"
"qs('#dr').disabled='disabled';"
"qs('#dr').style.visibility='hidden';"
"if(e<23){for(i=12;i<=23;i++){ce(i,o);}}" // Create hours select options
"}"
"}";
@ -583,7 +583,7 @@ const char HTTP_TIMER_SCRIPT3[] PROGMEM =
"if(m==0){s|=l;}" // Get time
#ifdef USE_SUNRISE
"if((m==1)||(m==2)){"
"if(qs('#dr').selectedIndex>0){l+=720;}" // If negative offset, add 12h to given offset time
"if(qs('#dr').selectedIndex>0){if(l>0){l+=720;}}" // If negative offset and delta-time > 0, add 12h to given offset time
"s|=l&0x7FF;" // Save offset instead of time
"}"
#endif

View File

@ -217,7 +217,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1);
if (rule_param.startsWith(stemp)) {
rule_param = Settings.mems[i];
rule_param = SettingsText(SET_MEM1 + i);
break;
}
}
@ -438,7 +438,7 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
String ucommand = commands;
ucommand.toUpperCase();
// if (!ucommand.startsWith("BACKLOG")) { commands = "backlog " + commands; } // Always use Backlog to prevent power race exception
if (ucommand.indexOf("EVENT ") != -1) { commands = "backlog " + commands; } // Always use Backlog with event to prevent rule event loop exception
if ((ucommand.indexOf("EVENT ") != -1) && (ucommand.indexOf("BACKLOG ") == -1)) { commands = "backlog " + commands; } // Always use Backlog with event to prevent rule event loop exception
RulesVarReplace(commands, F("%VALUE%"), Rules.event_value);
for (uint32_t i = 0; i < MAX_RULE_VARS; i++) {
@ -447,11 +447,12 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
}
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1);
RulesVarReplace(commands, stemp, Settings.mems[i]);
RulesVarReplace(commands, stemp, SettingsText(SET_MEM1 +i));
}
RulesVarReplace(commands, F("%TIME%"), String(MinutesPastMidnight()));
RulesVarReplace(commands, F("%UPTIME%"), String(MinutesUptime()));
RulesVarReplace(commands, F("%TIMESTAMP%"), GetDateAndTime(DT_LOCAL));
RulesVarReplace(commands, F("%TOPIC%"), SettingsText(SET_MQTT_TOPIC));
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
RulesVarReplace(commands, F("%SUNRISE%"), String(SunMinutes(0)));
RulesVarReplace(commands, F("%SUNSET%"), String(SunMinutes(1)));
@ -607,7 +608,7 @@ void RulesEvery50ms(void)
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
if (bitRead(Rules.mems_event, i)) {
bitClear(Rules.mems_event, i);
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Mem%d\":{\"State\":%s}}"), i+1, Settings.mems[i]);
snprintf_P(json_event, sizeof(json_event), PSTR("{\"Mem%d\":{\"State\":%s}}"), i+1, SettingsText(SET_MEM1 +i));
RulesProcessEvent(json_event);
break;
}
@ -1001,7 +1002,7 @@ bool findNextVariableValue(char * &pVarname, float &value)
} else if (sVarName.startsWith(F("MEM"))) {
int index = sVarName.substring(3).toInt();
if (index > 0 && index <= MAX_RULE_MEMS) {
value = CharToFloat(Settings.mems[index -1]);
value = CharToFloat(SettingsText(SET_MEM1 + index -1));
}
} else if (sVarName.equals(F("TIME"))) {
value = MinutesPastMidnight();
@ -1809,23 +1810,23 @@ void CmndMemory(void)
if (!XdrvMailbox.usridx) {
mqtt_data[0] = '\0';
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
ResponseAppend_P(PSTR("%c\"Mem%d\":\"%s\""), (i) ? ',' : '{', i +1, Settings.mems[i]);
ResponseAppend_P(PSTR("%c\"Mem%d\":\"%s\""), (i) ? ',' : '{', i +1, SettingsText(SET_MEM1 +i));
}
ResponseJsonEnd();
} else {
if (XdrvMailbox.data_len > 0) {
#ifdef USE_EXPRESSION
if (XdrvMailbox.data[0] == '=') { // Spaces already been skipped in data
dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, Settings.mems[XdrvMailbox.index -1]);
dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, SettingsText(SET_MEM1 + XdrvMailbox.index -1));
} else {
strlcpy(Settings.mems[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[XdrvMailbox.index -1]));
SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
}
#else
strlcpy(Settings.mems[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[XdrvMailbox.index -1]));
SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
#endif // USE_EXPRESSION
bitSet(Rules.mems_event, XdrvMailbox.index -1);
}
ResponseCmndIdxChar(Settings.mems[XdrvMailbox.index -1]);
ResponseCmndIdxChar(SettingsText(SET_MEM1 + XdrvMailbox.index -1));
}
}
}

View File

@ -1030,8 +1030,10 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso
if ((*jo).is<char*>(vn)) {
if (!strncmp(str_value,"ON",2)) {
if (fp) *fp=1;
goto nexit;
} else if (!strncmp(str_value,"OFF",3)) {
if (fp) *fp=0;
goto nexit;
} else {
*vtype=STR_RES;
tind->bits.constant=1;
@ -1039,6 +1041,7 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso
if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE);
return lp+len;
}
} else {
if (fp) {
if (!strncmp(vn.c_str(),"Epoch",5)) {
@ -1047,6 +1050,7 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso
*fp=CharToFloat((char*)str_value);
}
}
nexit:
*vtype=NUM_RES;
tind->bits.constant=1;
tind->bits.is_string=0;
@ -1366,7 +1370,7 @@ chknext:
goto exit;
}
if (!strncmp(vname,"gtopic",6)) {
if (sp) strlcpy(sp,Settings.mqtt_grptopic,glob_script_mem.max_ssize);
if (sp) strlcpy(sp,SettingsText(SET_MQTT_GRP_TOPIC),glob_script_mem.max_ssize);
goto strexit;
}
break;
@ -1523,15 +1527,15 @@ chknext:
goto exit;
}
if (!strncmp(vname,"prefix1",7)) {
if (sp) strlcpy(sp,Settings.mqtt_prefix[0],glob_script_mem.max_ssize);
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX1),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix2",7)) {
if (sp) strlcpy(sp,Settings.mqtt_prefix[1],glob_script_mem.max_ssize);
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX2),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix3",7)) {
if (sp) strlcpy(sp,Settings.mqtt_prefix[2],glob_script_mem.max_ssize);
if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX3),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"pow(",4)) {
@ -1740,7 +1744,7 @@ chknext:
goto strexit;
}
if (!strncmp(vname,"topic",5)) {
if (sp) strlcpy(sp,Settings.mqtt_topic,glob_script_mem.max_ssize);
if (sp) strlcpy(sp,SettingsText(SET_MQTT_TOPIC),glob_script_mem.max_ssize);
goto strexit;
}
#ifdef USE_DISPLAY
@ -4843,6 +4847,12 @@ bool Xdrv10(uint8_t function)
result = ScriptCommand();
break;
case FUNC_SET_POWER:
#ifdef SCRIPT_POWER_SECTION
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">P",2,0);
#else
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">E",2,0);
#endif
break;
case FUNC_RULES_PROCESS:
if (bitRead(Settings.rule_enabled, 0)) Run_Scripter(">E",2,mqtt_data);
break;

View File

@ -499,6 +499,7 @@ void KNX_INIT(void)
if (GetUsedInModule(GPIO_DHT22, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; }
if (GetUsedInModule(GPIO_SI7021, my_module.io)) { device_param[KNX_HUMIDITY-1].show = true; }
#if defined(USE_ENERGY_SENSOR)
// Any device with a Power Monitoring
if ( energy_flg != ENERGY_NONE ) {
device_param[KNX_ENERGY_POWER-1].show = true;
@ -509,6 +510,7 @@ void KNX_INIT(void)
device_param[KNX_ENERGY_CURRENT-1].show = true;
device_param[KNX_ENERGY_POWERFACTOR-1].show = true;
}
#endif
#ifdef USE_RULES
device_param[KNX_SLOT1-1].show = true;

View File

@ -46,7 +46,7 @@ const char HASS_DISCOVER_BUTTON_TOGGLE[] PROGMEM =
const char HASS_DISCOVER_SWITCH_TOGGLE[] PROGMEM =
",\"value_template\":\"{%%if is_state(entity_id,\\\"on\\\")-%%}OFF{%%-else-%%}ON{%%-endif%%}\""; // A switch must maintain his state until the next update
const char HASS_DISCOVER_BUTTON_SWITCH_ONOFF[] PROGMEM =
",\"value_template\":\"{{value_json.%s}}\"," // STATE
"\"frc_upd\":true," // In ON/OFF case, enable force_update to make automations work
@ -86,6 +86,7 @@ const char HASS_DISCOVER_SENSOR[] PROGMEM =
"{\"name\":\"%s\"," // dualr2 1 BTN
"\"stat_t\":\"%s\"," // cmnd/dualr2/POWER (implies "\"optimistic\":\"false\",")
"\"avty_t\":\"%s\"," // tele/dualr2/LWT
"\"frc_upd\":true," // force update for better graph representation
"\"pl_avail\":\"" D_ONLINE "\"," // Online
"\"pl_not_avail\":\"" D_OFFLINE "\""; // Offline
@ -238,9 +239,9 @@ void HAssAnnounceRelayLight(void)
char *availability_topic = stemp3;
if (i > MAX_FRIENDLYNAMES) {
snprintf_P(name, sizeof(name), PSTR("%s %d"), Settings.friendlyname[0], i);
snprintf_P(name, sizeof(name), PSTR("%s %d"), SettingsText(SET_FRIENDLYNAME1), i);
} else {
snprintf_P(name, sizeof(name), Settings.friendlyname[i -1]);
snprintf_P(name, sizeof(name), SettingsText(SET_FRIENDLYNAME1 +i -1));
}
GetPowerDevice(value_template, i, sizeof(value_template), Settings.flag.device_index_enable); // SetOption26 - Switch between POWER or POWER1
GetTopic_P(command_topic, CMND, mqtt_topic, value_template);
@ -252,7 +253,7 @@ void HAssAnnounceRelayLight(void)
Shorten(&state_topic, prefix);
Shorten(&availability_topic, prefix);
Response_P(HASS_DISCOVER_RELAY, name, command_topic, state_topic, value_template, Settings.state_text[0], Settings.state_text[1], availability_topic);
Response_P(HASS_DISCOVER_RELAY, name, command_topic, state_topic, value_template, SettingsText(SET_STATE_TXT1), SettingsText(SET_STATE_TXT2), availability_topic);
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
TryResponseAppend_P(HASS_DISCOVER_TOPIC_PREFIX, prefix);
@ -323,7 +324,7 @@ void HAssAnnounceButtonSwitch(uint8_t device, char* topic, uint8_t present, uint
char *availability_topic = stemp2;
char jsoname[8];
snprintf_P(name, sizeof(name), PSTR("%s %s%d"), Settings.friendlyname[0], key?"Switch":"Button", device+1);
snprintf_P(name, sizeof(name), PSTR("%s %s%d"), SettingsText(SET_FRIENDLYNAME1), key?"Switch":"Button", device+1);
snprintf_P(jsoname, sizeof(jsoname), PSTR("%s%d"), key?"SWITCH":"BUTTON", device+1);
GetPowerDevice(value_template, device+1, sizeof(value_template),
key + Settings.flag.device_index_enable); // Force index for Switch 1, Index on Button1 is controlled by SetOption26 - Switch between POWER or POWER1
@ -338,11 +339,11 @@ void HAssAnnounceButtonSwitch(uint8_t device, char* topic, uint8_t present, uint
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO_SHORT, unique_id, ESP.getChipId(), WiFi.macAddress().c_str());
if (strlen(prefix) > 0 ) TryResponseAppend_P(HASS_DISCOVER_TOPIC_PREFIX, prefix);
if (toggle) {
if (!key) {
TryResponseAppend_P(HASS_DISCOVER_BUTTON_TOGGLE, PSTR(D_RSLT_STATE), Settings.state_text[toggle?2:1]);
if (!key) {
TryResponseAppend_P(HASS_DISCOVER_BUTTON_TOGGLE, PSTR(D_RSLT_STATE), SettingsText(SET_STATE_TXT1 + toggle?2:1));
} else {TryResponseAppend_P(HASS_DISCOVER_SWITCH_TOGGLE);}
}
else TryResponseAppend_P(HASS_DISCOVER_BUTTON_SWITCH_ONOFF, PSTR(D_RSLT_STATE), Settings.state_text[toggle?2:1], Settings.state_text[0]);
else TryResponseAppend_P(HASS_DISCOVER_BUTTON_SWITCH_ONOFF, PSTR(D_RSLT_STATE), SettingsText(SET_STATE_TXT1 + toggle?2:1), SettingsText(SET_STATE_TXT1));
TryResponseAppend_P(PSTR("}"));
}
@ -351,10 +352,10 @@ void HAssAnnounceButtonSwitch(uint8_t device, char* topic, uint8_t present, uint
void HAssAnnounceSwitches(void)
{
char sw_topic[sizeof(Settings.switch_topic)];
char sw_topic[TOPSZ];
// Send info about buttons
char *tmp = Settings.switch_topic;
char *tmp = SettingsText(SET_MQTT_SWITCH_TOPIC);
Format(sw_topic, tmp, sizeof(sw_topic));
if (!strcmp_P(sw_topic, "0") || strlen(sw_topic) == 0 ) {
for (uint32_t switch_index = 0; switch_index < MAX_SWITCHES; switch_index++) {
@ -368,7 +369,7 @@ void HAssAnnounceSwitches(void)
// Check if MQTT message will be ON/OFF or TOGGLE
if (Settings.switchmode[switch_index] == FOLLOW || Settings.switchmode[switch_index] == FOLLOW_INV ||
Settings.flag3.button_switch_force_local || // SetOption61 - Force local operation when button/switch topic is set
!strcmp(mqtt_topic, sw_topic) || !strcmp(Settings.mqtt_grptopic, sw_topic))
!strcmp(mqtt_topic, sw_topic) || !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), sw_topic))
{
toggle = 0; // MQTT message will be ON/OFF
}
@ -380,10 +381,10 @@ void HAssAnnounceSwitches(void)
void HAssAnnounceButtons(void)
{
char key_topic[sizeof(Settings.button_topic)];
char key_topic[TOPSZ];
// Send info about buttons
char *tmp = Settings.button_topic;
char *tmp = SettingsText(SET_MQTT_BUTTON_TOPIC);
Format(key_topic, tmp, sizeof(key_topic));
if (!strcmp_P(key_topic, "0") || strlen(key_topic) == 0 ) {
for (uint32_t button_index = 0; button_index < MAX_KEYS; button_index++) {
@ -400,7 +401,7 @@ void HAssAnnounceButtons(void)
// Check if MQTT message will be ON/OFF or TOGGLE
if (Settings.flag3.button_switch_force_local || // SetOption61 - Force local operation when button/switch topic is set
!strcmp(mqtt_topic, key_topic) || !strcmp(Settings.mqtt_grptopic, key_topic))
!strcmp(mqtt_topic, key_topic) || !strcmp(SettingsText(SET_MQTT_GRP_TOPIC), key_topic))
{
toggle = 0; // MQTT message will be ON/OFF
}
@ -444,7 +445,7 @@ void HAssAnnounceSensor(const char* sensorname, const char* subsensortype)
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_SENSOR));
}
snprintf_P(name, sizeof(name), PSTR("%s %s %s"), Settings.friendlyname[0], sensorname, subsensortype);
snprintf_P(name, sizeof(name), PSTR("%s %s %s"), SettingsText(SET_FRIENDLYNAME1), sensorname, subsensortype);
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
FindPrefix(state_topic, availability_topic, prefix);
Shorten(&state_topic, prefix);
@ -457,7 +458,7 @@ void HAssAnnounceSensor(const char* sensorname, const char* subsensortype)
TryResponseAppend_P(HASS_DISCOVER_SENSOR_TEMP, TempUnit(), sensorname);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_HUMIDITY))) {
TryResponseAppend_P(HASS_DISCOVER_SENSOR_HUM, sensorname);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_PRESSURE))
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_PRESSURE))
|| !strcmp_P(subsensortype, PSTR(D_JSON_PRESSUREATSEALEVEL))){
TryResponseAppend_P(HASS_DISCOVER_SENSOR_PRESS, PressureUnit().c_str(), sensorname, subsensortype);
} else if (!strcmp_P(subsensortype, PSTR(D_JSON_TOTAL))
@ -547,7 +548,7 @@ void HAssAnnounceStatusSensor(void)
char *state_topic = stemp1;
char *availability_topic = stemp2;
snprintf_P(name, sizeof(name), PSTR("%s status"), Settings.friendlyname[0]);
snprintf_P(name, sizeof(name), PSTR("%s status"), SettingsText(SET_FRIENDLYNAME1));
GetTopic_P(state_topic, TELE, mqtt_topic, PSTR(D_RSLT_HASS_STATE));
GetTopic_P(availability_topic, TELE, mqtt_topic, S_LWT);
FindPrefix(state_topic, availability_topic, prefix);
@ -557,7 +558,7 @@ void HAssAnnounceStatusSensor(void)
Response_P(HASS_DISCOVER_SENSOR, name, state_topic, availability_topic);
TryResponseAppend_P(HASS_DISCOVER_SENSOR_HASS_STATUS, state_topic);
TryResponseAppend_P(HASS_DISCOVER_DEVICE_INFO, unique_id, ESP.getChipId(), WiFi.macAddress().c_str(),
Settings.friendlyname[0], ModuleName().c_str(), my_version, my_image);
SettingsText(SET_FRIENDLYNAME1), ModuleName().c_str(), my_version, my_image);
TryResponseAppend_P(HASS_DISCOVER_TOPIC_PREFIX, prefix);
TryResponseAppend_P(PSTR("}"));
}
@ -587,8 +588,8 @@ void HAssDiscovery(void)
Settings.flag.decimal_text = 1; // SetOption17 - Switch between decimal or hexadecimal output - Respond with decimal color values
Settings.flag3.hass_tele_on_power = 1; // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT - send tele/STATE message as stat/RESULT
// Settings.light_scheme = 0; // To just control color it needs to be Scheme 0
if (strcmp_P(Settings.mqtt_fulltopic, PSTR("%topic%/%prefix%/"))) {
strncpy_P(Settings.mqtt_fulltopic, PSTR("%topic%/%prefix%/"), sizeof(Settings.mqtt_fulltopic));
if (strcmp_P(SettingsText(SET_MQTT_FULLTOPIC), PSTR("%topic%/%prefix%/"))) {
SettingsUpdateText(SET_MQTT_FULLTOPIC, "%topic%/%prefix%/");
restart_flag = 2;
return; // As full topic has changed do restart first before sending discovery data
}

View File

@ -1008,7 +1008,7 @@ void DisplayLogBufferInit(void)
snprintf_P(buffer, sizeof(buffer), PSTR(D_CMND_HOSTNAME " %s"), my_hostname);
DisplayLogBufferAdd(buffer);
snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_SSID " %s"), Settings.sta_ssid[Settings.sta_active]);
snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_SSID " %s"), SettingsText(SET_STASSID1 + Settings.sta_active));
DisplayLogBufferAdd(buffer);
snprintf_P(buffer, sizeof(buffer), PSTR(D_JSON_MAC " %s"), WiFi.macAddress().c_str());
DisplayLogBufferAdd(buffer);
@ -1196,7 +1196,7 @@ void DisplayMqttSubscribe(void)
char ntopic[TOPSZ];
ntopic[0] = '\0';
strlcpy(stopic, Settings.mqtt_fulltopic, sizeof(stopic));
strlcpy(stopic, SettingsText(SET_MQTT_FULLTOPIC), sizeof(stopic));
char *tp = strtok(stopic, "/");
while (tp != nullptr) {
if (!strcmp_P(tp, MQTT_TOKEN_PREFIX)) {
@ -1205,7 +1205,7 @@ void DisplayMqttSubscribe(void)
strncat_P(ntopic, PSTR("+/"), sizeof(ntopic) - strlen(ntopic) -1); // Add single-level wildcards
tp = strtok(nullptr, "/");
}
strncat(ntopic, Settings.mqtt_prefix[2], sizeof(ntopic) - strlen(ntopic) -1); // Subscribe to tele messages
strncat(ntopic, SettingsText(SET_MQTTPREFIX3), sizeof(ntopic) - strlen(ntopic) -1); // Subscribe to tele messages
strncat_P(ntopic, PSTR("/#"), sizeof(ntopic) - strlen(ntopic) -1); // Add multi-level wildcard
MqttSubscribe(ntopic);
disp_subscribed = true;
@ -1219,7 +1219,7 @@ bool DisplayMqttData(void)
if (disp_subscribed) {
char stopic[TOPSZ];
snprintf_P(stopic, sizeof(stopic) , PSTR("%s/"), Settings.mqtt_prefix[2]); // tele/
snprintf_P(stopic, sizeof(stopic) , PSTR("%s/"), SettingsText(SET_MQTTPREFIX3)); // tele/
char *tp = strstr(XdrvMailbox.topic, stopic);
if (tp) { // tele/tasmota/SENSOR
if (Settings.display_mode &0x04) {

View File

@ -85,6 +85,7 @@ void RfInit(void)
mySwitch.enableTransmit(pin[GPIO_RFSEND]);
}
if (pin[GPIO_RFRECV] < 99) {
pinMode( pin[GPIO_RFRECV], INPUT);
mySwitch.enableReceive(pin[GPIO_RFRECV]);
}
}

View File

@ -365,17 +365,18 @@ void HueLightStatus1(uint8_t device, String *response)
// Any device whose friendly name start with "$" is considered hidden
bool HueActive(uint8_t device) {
if (device > MAX_FRIENDLYNAMES) { device = MAX_FRIENDLYNAMES; }
return '$' != Settings.friendlyname[device-1][0];
// return '$' != Settings.friendlyname[device-1][0];
return '$' != *SettingsText(SET_FRIENDLYNAME1 +device -1);
}
void HueLightStatus2(uint8_t device, String *response)
{
*response += FPSTR(HUE_LIGHTS_STATUS_JSON2);
if (device <= MAX_FRIENDLYNAMES) {
response->replace("{j1", Settings.friendlyname[device-1]);
response->replace("{j1", SettingsText(SET_FRIENDLYNAME1 +device -1));
} else {
char fname[33];
strcpy(fname, Settings.friendlyname[MAX_FRIENDLYNAMES-1]);
strcpy(fname, SettingsText(SET_FRIENDLYNAME1 + MAX_FRIENDLYNAMES -1));
uint32_t fname_len = strlen(fname);
if (fname_len > 30) { fname_len = 30; }
fname[fname_len++] = '-';

View File

@ -241,7 +241,7 @@ void HandleUpnpSetupWemo(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_SETUP));
String setup_xml = FPSTR(WEMO_SETUP_XML);
setup_xml.replace("{x1", Settings.friendlyname[0]);
setup_xml.replace("{x1", SettingsText(SET_FRIENDLYNAME1));
setup_xml.replace("{x2", WemoUuid());
setup_xml.replace("{x3", WemoSerialnumber());
WSSend(200, CT_XML, setup_xml);

View File

@ -19,8 +19,6 @@
#ifdef USE_ZIGBEE
#define ZIGBEE_VERBOSE // output versbose MQTT Zigbee logs. Will remain active for now
typedef uint64_t Z_IEEEAddress;
typedef uint16_t Z_ShortAddress;

View File

@ -0,0 +1,26 @@
/*
xdrv_23_zigbee_1_headers.ino - zigbee support for Tasmota
Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
*/
#ifdef USE_ZIGBEE
// contains some definitions for functions used before their declarations
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1);
#endif // USE_ZIGBEE

View File

@ -22,6 +22,9 @@
#include <vector>
#include <map>
typedef int32_t (*Z_DeviceTimer)(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value);
typedef struct Z_Device {
uint16_t shortaddr; // unique key if not null, or unspecified if null
uint64_t longaddr; // 0x00 means unspecified
@ -33,6 +36,12 @@ typedef struct Z_Device {
std::vector<uint32_t> endpoints; // encoded as high 16 bits is endpoint, low 16 bits is ProfileId
std::vector<uint32_t> clusters_in; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
std::vector<uint32_t> clusters_out; // encoded as high 16 bits is endpoint, low 16 bits is cluster number
// below are per device timers, used for example to query the new state of the device
uint32_t timer; // millis() when to fire the timer, 0 if no timer
uint16_t cluster; // cluster to use for the timer
uint16_t endpoint; // endpoint to use for timer
uint32_t value; // any raw value to use for the timer
Z_DeviceTimer func; // function to call when timer occurs
} Z_Device;
// All devices are stored in a Vector
@ -70,6 +79,11 @@ public:
// Dump json
String dump(uint32_t dump_mode, int32_t device_num = 0) const;
// Timers
void resetTimer(uint32_t shortaddr);
void setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func);
void runTimer(void);
private:
std::vector<Z_Device> _devices = {};
@ -157,7 +171,9 @@ Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) {
String(), // FriendlyName
std::vector<uint32_t>(),
std::vector<uint32_t>(),
std::vector<uint32_t>() };
std::vector<uint32_t>(),
0,0,0,0,
nullptr };
_devices.push_back(device);
return _devices.back();
}
@ -346,6 +362,47 @@ void Z_Devices::updateLastSeen(uint16_t shortaddr) {
_updateLastSeen(device);
}
// Per device timers
//
// Reset the timer for a specific device
void Z_Devices::resetTimer(uint32_t shortaddr) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
device.timer = 0;
device.func = nullptr;
}
// Set timer for a specific device
void Z_Devices::setTimer(uint32_t shortaddr, uint32_t wait_ms, uint16_t cluster, uint16_t endpoint, uint32_t value, Z_DeviceTimer func) {
Z_Device & device = getShortAddr(shortaddr);
if (&device == nullptr) { return; } // don't crash if not found
device.cluster = cluster;
device.endpoint = endpoint;
device.value = value;
device.func = func;
device.timer = wait_ms + millis();
}
// Run timer at each tick
void Z_Devices::runTimer(void) {
uint32_t now = millis();
for (std::vector<Z_Device>::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)) {
// trigger the timer
(*device.func)(device.shortaddr, device.cluster, device.endpoint, device.value);
device.timer = 0; // cancel the timer
}
}
}
// 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

View File

@ -39,45 +39,44 @@ class ZCLFrame {
public:
ZCLFrame(uint8_t frame_control, uint16_t manuf_code, uint8_t transact_seq, uint8_t cmd_id,
const char *buf, size_t buf_len, uint16_t clusterid = 0, uint16_t groupid = 0):
const char *buf, size_t buf_len, uint16_t clusterid = 0, uint16_t groupid = 0,
uint16_t srcaddr = 0, uint8_t srcendpoint = 0, uint8_t dstendpoint = 0, uint8_t wasbroadcast = 0,
uint8_t linkquality = 0, uint8_t securityuse = 0, uint8_t seqnumber = 0,
uint32_t timestamp = 0):
_cmd_id(cmd_id), _manuf_code(manuf_code), _transact_seq(transact_seq),
_payload(buf_len ? buf_len : 250), // allocate the data frame from source or preallocate big enough
_cluster_id(clusterid), _group_id(groupid)
_cluster_id(clusterid), _group_id(groupid),
_srcaddr(srcaddr), _srcendpoint(srcendpoint), _dstendpoint(dstendpoint), _wasbroadcast(wasbroadcast),
_linkquality(linkquality), _securityuse(securityuse), _seqnumber(seqnumber),
_timestamp(timestamp)
{
_frame_control.d8 = frame_control;
_payload.addBuffer(buf, buf_len);
};
void publishMQTTReceived(uint16_t groupid, uint16_t clusterid, Z_ShortAddress srcaddr,
uint8_t srcendpoint, uint8_t dstendpoint, uint8_t wasbroadcast,
uint8_t linkquality, uint8_t securityuse, uint8_t seqnumber,
uint32_t timestamp) {
#ifdef ZIGBEE_VERBOSE
void log(void) {
char hex_char[_payload.len()*2+2];
ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char));
Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{"
"\"groupid\":%d," "\"clusterid\":%d," "\"srcaddr\":\"0x%04X\","
"\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d,"
"\"" D_CMND_ZIGBEE_LINKQUALITY "\":%d," "\"securityuse\":%d," "\"seqnumber\":%d,"
"\"timestamp\":%d,"
"\"fc\":\"0x%02X\",\"manuf\":\"0x%04X\",\"transact\":%d,"
"\"cmdid\":\"0x%02X\",\"payload\":\"%s\""),
groupid, clusterid, srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp,
"\"cmdid\":\"0x%02X\",\"payload\":\"%s\"}}"),
_group_id, _cluster_id, _srcaddr,
_srcendpoint, _dstendpoint, _wasbroadcast,
_linkquality, _securityuse, _seqnumber,
_timestamp,
_frame_control, _manuf_code, _transact_seq, _cmd_id,
hex_char);
ResponseJsonEnd(); // append '}'
ResponseJsonEnd(); // append '}'
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZCL_RECEIVED));
XdrvRulesProcess();
#endif
}
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid) { // parse a raw frame and build the ZCL frame object
static ZCLFrame parseRawFrame(const SBuffer &buf, uint8_t offset, uint8_t len, uint16_t clusterid, uint16_t groupid,
uint16_t srcaddr = 0, uint8_t srcendpoint = 0, uint8_t dstendpoint = 0, uint8_t wasbroadcast = 0,
uint8_t linkquality = 0, uint8_t securityuse = 0, uint8_t seqnumber = 0,
uint32_t timestamp = 0) { // parse a raw frame and build the ZCL frame object
uint32_t i = offset;
ZCLHeaderFrameControl_t frame_control;
uint16_t manuf_code = 0;
@ -122,10 +121,18 @@ public:
return _cluster_id;
}
inline uint16_t getSrcEndpoint(void) const {
return _srcendpoint;
}
const SBuffer &getPayload(void) const {
return _payload;
}
uint16_t getManufCode(void) const {
return _manuf_code;
}
private:
ZCLHeaderFrameControl_t _frame_control = { .d8 = 0 };
uint16_t _manuf_code = 0; // optional
@ -134,6 +141,15 @@ private:
uint16_t _cluster_id = 0;
uint16_t _group_id = 0;
SBuffer _payload;
// information from decoded ZCL frame
uint16_t _srcaddr;
uint8_t _srcendpoint;
uint8_t _dstendpoint;
uint8_t _wasbroadcast;
uint8_t _linkquality;
uint8_t _securityuse;
uint8_t _seqnumber;
uint32_t _timestamp;
};
// Zigbee ZCL converters
@ -456,7 +472,7 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) {
// return value:
// 0 = keep initial value
// 1 = remove initial value
typedef int32_t (*Z_AttrConverter)(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper* new_name);
typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper* new_name, uint16_t cluster, uint16_t attr);
typedef struct Z_AttributeConverter {
uint16_t cluster;
uint16_t attribute;
@ -464,6 +480,8 @@ typedef struct Z_AttributeConverter {
Z_AttrConverter func;
} Z_AttributeConverter;
#define OCCUPANCY "Occupancy" // global define for Aqara
// list of post-processing directives
const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0000, 0x0000, "ZCLVersion", &Z_Copy },
@ -498,7 +516,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0007, 0x0000, "SwitchType", &Z_Copy },
// Level Control cluster
{ 0x0008, 0x0000, "CurrentLevel", &Z_Copy },
{ 0x0008, 0x0000, "Dimmer", &Z_Copy },
// { 0x0008, 0x0001, "RemainingTime", &Z_Copy },
// { 0x0008, 0x0010, "OnOffTransitionTime", &Z_Copy },
// { 0x0008, 0x0011, "OnLevel", &Z_Copy },
@ -625,6 +643,11 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0101, 0x0004, "DoorOpenEvents", &Z_Copy },
{ 0x0101, 0x0005, "DoorClosedEvents", &Z_Copy },
{ 0x0101, 0x0006, "OpenPeriod", &Z_Copy },
// Aqara Lumi Vibration Sensor
{ 0x0101, 0x0055, "AqaraVibrationMode", &Z_AqaraVibration },
{ 0x0101, 0x0503, "AqaraVibrationsOrAngle", &Z_Copy },
{ 0x0101, 0x0505, "AqaraVibration505", &Z_Copy },
{ 0x0101, 0x0508, "AqaraAccelerometer", &Z_AqaraVibration },
// Window Covering cluster
{ 0x0102, 0x0000, "WindowCoveringType", &Z_Copy },
{ 0x0102, 0x0001, "PhysicalClosedLimitLift",&Z_Copy },
@ -648,14 +671,14 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0102, 0x0019, "IntermediateSetpointsTilt",&Z_Copy },
// Color Control cluster
{ 0x0300, 0x0000, "CurrentHue", &Z_Copy },
{ 0x0300, 0x0001, "CurrentSaturation", &Z_Copy },
{ 0x0300, 0x0000, "Hue", &Z_Copy },
{ 0x0300, 0x0001, "Sat", &Z_Copy },
{ 0x0300, 0x0002, "RemainingTime", &Z_Copy },
{ 0x0300, 0x0003, "CurrentX", &Z_Copy },
{ 0x0300, 0x0004, "CurrentY", &Z_Copy },
{ 0x0300, 0x0003, "X", &Z_Copy },
{ 0x0300, 0x0004, "Y", &Z_Copy },
{ 0x0300, 0x0005, "DriftCompensation", &Z_Copy },
{ 0x0300, 0x0006, "CompensationText", &Z_Copy },
{ 0x0300, 0x0007, "ColorTemperatureMireds",&Z_Copy },
{ 0x0300, 0x0007, "CT", &Z_Copy },
{ 0x0300, 0x0008, "ColorMode", &Z_Copy },
{ 0x0300, 0x0010, "NumberOfPrimaries", &Z_Copy },
{ 0x0300, 0x0011, "Primary1X", &Z_Copy },
@ -727,7 +750,7 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
{ 0x0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
// Occupancy Sensing cluster
{ 0x0406, 0x0000, "Occupancy", &Z_Copy }, // Occupancy (map8)
{ 0x0406, 0x0000, OCCUPANCY, &Z_AqaraOccupancy }, // Occupancy (map8)
{ 0x0406, 0x0001, "OccupancySensorType", &Z_Copy }, // OccupancySensorType
{ 0x0406, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values
@ -753,13 +776,13 @@ const Z_AttributeConverter Z_PostProcess[] PROGMEM = {
// ======================================================================
// Record Manuf
int32_t Z_ManufKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = value;
zigbee_devices.setManufId(shortaddr, value.as<const char*>());
return 1;
}
//
int32_t Z_ModelKeep(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = value;
zigbee_devices.setModelId(shortaddr, value.as<const char*>());
return 1;
@ -767,34 +790,113 @@ int32_t Z_ModelKeep(uint16_t shortaddr, JsonObject& json, const char *name, Json
// ======================================================================
// Remove attribute
int32_t Z_Remove(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
return 1; // remove original key
}
// Copy value as-is
int32_t Z_Copy(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = value;
return 1; // remove original key
}
// Add pressure unit
int32_t Z_AddPressureUnit(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = F(D_UNIT_PRESSURE);
return 0; // keep original key
}
// Convert int to float and divide by 100
int32_t Z_FloatDiv100(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = ((float)value) / 100.0f;
return 1; // remove original key
}
// Convert int to float and divide by 10
int32_t Z_FloatDiv10(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = ((float)value) / 10.0f;
return 1; // remove original key
}
int32_t Z_AqaraSensor(uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name) {
// Aqara Occupancy behavior: the Aqara device only sends Occupancy: true events every 60 seconds.
// Here we add a timer so if we don't receive a Occupancy event for 90 seconds, we send Occupancy:false
const uint32_t OCCUPANCY_TIMEOUT = 90 * 1000; // 90 s
int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
// send Occupancy:false message
Response_P(PSTR("{\"" D_CMND_ZIGBEE_RECEIVED "\":{\"0x%04X\":{\"" OCCUPANCY "\":0}}}"), shortaddr);
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR));
XdrvRulesProcess();
}
int32_t Z_AqaraOccupancy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
json[new_name] = value;
uint32_t occupancy = value;
if (occupancy) {
zigbee_devices.setTimer(shortaddr, OCCUPANCY_TIMEOUT, cluster, zcl->getSrcEndpoint(), 0, &Z_OccupancyCallback);
} else {
zigbee_devices.resetTimer(shortaddr);
}
return 1; // remove original key
}
// Aqara Vibration Sensor - special proprietary attributes
int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
//json[new_name] = value;
switch (attr) {
case 0x0055:
{
int32_t ivalue = value;
const __FlashStringHelper * svalue;
switch (ivalue) {
case 1: svalue = F("vibrate"); break;
case 2: svalue = F("tilt"); break;
case 3: svalue = F("drop"); break;
default: svalue = F("unknown"); break;
}
json[new_name] = svalue;
}
break;
// case 0x0503:
// break;
// case 0x0505:
// break;
case 0x0508:
{
// see https://github.com/Koenkk/zigbee2mqtt/issues/295 and http://faire-ca-soi-meme.fr/domotique/2018/09/03/test-xiaomi-aqara-vibration-sensor/
// report accelerometer measures
String hex = value;
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
int16_t x, y, z;
z = buf2.get16(0);
y = buf2.get16(2);
x = buf2.get16(4);
JsonArray& xyz = json.createNestedArray(new_name);
xyz.add(x);
xyz.add(y);
xyz.add(z);
// calculate angles
float X = x;
float Y = y;
float Z = z;
int32_t Angle_X = 0.5f + atanf(X/sqrtf(z*z+y*y)) * f_180pi;
int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(x*x+z*z)) * f_180pi;
int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(x*x+y*y)) * f_180pi;
// int32_t Angle_X = 0.5f + atanf(X/sqrtf(Z*Z+Y*Y)) * f_180pi;
// int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(X*X+Z*Z)) * f_180pi;
// int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(X*X+Y*Y)) * f_180pi;
JsonArray& angles = json.createNestedArray(F("AqaraAngles"));
angles.add(Angle_X);
angles.add(Angle_Y);
angles.add(Angle_Z);
}
break;
}
return 1; // remove original key
}
int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const __FlashStringHelper *new_name, uint16_t cluster, uint16_t attr) {
String hex = value;
SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
uint32_t i = 0;
@ -809,36 +911,31 @@ int32_t Z_AqaraSensor(uint16_t shortaddr, JsonObject& json, const char *name, Js
i += parseSingleAttribute(json, tmp, buf2, i, len);
float val = json[tmp];
json.remove(tmp);
if (0x64 == attrid) {
json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
} else if (0x65 == attrid) {
json[F(D_JSON_HUMIDITY)] = val / 100.0f;
} else if (0x66 == attrid) {
json[F(D_JSON_PRESSURE)] = val / 100.0f;
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
} else if (0x01 == attrid) {
if (0x01 == attrid) {
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
json[F("Battery")] = toPercentageCR2032(val);
} else if (0 == zcl->getManufCode()) {
// onla Aqara Temp/Humidity has manuf_code of zero. If non-zero we skip the parameters
if (0x64 == attrid) {
json[F(D_JSON_TEMPERATURE)] = val / 100.0f;
} else if (0x65 == attrid) {
json[F(D_JSON_HUMIDITY)] = val / 100.0f;
} else if (0x66 == attrid) {
json[F(D_JSON_PRESSURE)] = val / 100.0f;
json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
} else if (0x01 == attrid) {
json[F(D_JSON_VOLTAGE)] = val / 1000.0f;
json[F("Battery")] = toPercentageCR2032(val);
}
} else if (0x115F == zcl->getManufCode()) {
// Aqara Motion Sensor, still unknown field
json[F("AqaraUnknown")] = val;
}
}
return 1; // remove original key
}
// ======================================================================
// Cluster Specific commands
// #define ZCL_OO_OFF "s_0006_00" // Cluster 0x0006, cmd 0x00 - On/Off - Off
// #define ZCL_OO_ON "s_0006_01" // Cluster 0x0006, cmd 0x01 - On/Off - On
// #define ZCL_COLORTEMP_MOVE "s_0300_0A" // Cluster 0x0300, cmd 0x0A, Move to Color Temp
// #define ZCL_LC_MOVE "s_0008_00" // Cluster 0x0008, cmd 0x00, Level Control Move to Level
// #define ZCL_LC_MOVE_1 "s_0008_01" // Cluster 0x0008, cmd 0x01, Level Control Move
// #define ZCL_LC_STEP "s_0008_02" // Cluster 0x0008, cmd 0x02, Level Control Step
// #define ZCL_LC_STOP "s_0008_03" // Cluster 0x0008, cmd 0x03, Level Control Stop
// #define ZCL_LC_MOVE_WOO "s_0008_04" // Cluster 0x0008, cmd 0x04, Level Control Move to Level, with On/Off
// #define ZCL_LC_MOVE_1_WOO "s_0008_05" // Cluster 0x0008, cmd 0x05, Level Control Move, with On/Off
// #define ZCL_LC_STEP_WOO "s_0008_06" // Cluster 0x0008, cmd 0x05, Level Control Step, with On/Off
// #define ZCL_LC_STOP_WOO "s_0008_07" // Cluster 0x0008, cmd 0x07, Level Control Stop
void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
// iterate on json elements
for (auto kv : json) {
@ -859,7 +956,7 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
if ((conv_cluster == cluster) &&
((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) {
int32_t drop = (*converter->func)(shortaddr, json, key, value, (const __FlashStringHelper*) converter->name);
int32_t drop = (*converter->func)(this, shortaddr, json, key, value, (const __FlashStringHelper*) converter->name, conv_cluster, conv_attribute);
if (drop) {
json.remove(key);
}
@ -870,157 +967,4 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) {
}
}
//void ZCLFrame::postProcessAttributes2(JsonObject& json) {
// void postProcessAttributes2(JsonObject& json) {
// const __FlashStringHelper *key;
//
// // Osram Mini Switch
// key = F(ZCL_OO_OFF);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F(D_CMND_POWER)] = F("Off");
// }
// key = F(ZCL_OO_ON);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F(D_CMND_POWER)] = F("On");
// }
// key = F(ZCL_COLORTEMP_MOVE);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint16_t color_temp = buf2.get16(0);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("ColorTemp")] = color_temp;
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_MOVE_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t level = buf2.get8(0);
// uint16_t transition_time = buf2.get16(1);
// json.remove(key);
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
// json[F("TransitionTime")] = transition_time / 10.0f;
// if (0 == level) {
// json[F(D_CMND_POWER)] = F("Off");
// } else {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_MOVE);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t level = buf2.get8(0);
// uint16_t transition_time = buf2.get16(1);
// json.remove(key);
// json[F("Dimmer")] = changeUIntScale(level, 0, 255, 0, 100); // change to percentage
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_MOVE_1);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t move_mode = buf2.get8(0);
// uint8_t move_rate = buf2.get8(1);
// json.remove(key);
// json[F("Move")] = move_mode ? F("Down") : F("Up");
// json[F("Rate")] = move_rate;
// }
// key = F(ZCL_LC_MOVE_1_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t move_mode = buf2.get8(0);
// uint8_t move_rate = buf2.get8(1);
// json.remove(key);
// json[F("Move")] = move_mode ? F("Down") : F("Up");
// json[F("Rate")] = move_rate;
// if (0 == move_mode) {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_STEP);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t step_mode = buf2.get8(0);
// uint8_t step_size = buf2.get8(1);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("Step")] = step_mode ? F("Down") : F("Up");
// json[F("StepSize")] = step_size;
// json[F("TransitionTime")] = transition_time / 10.0f;
// }
// key = F(ZCL_LC_STEP_WOO);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// uint8_t step_mode = buf2.get8(0);
// uint8_t step_size = buf2.get8(1);
// uint16_t transition_time = buf2.get16(2);
// json.remove(key);
// json[F("Step")] = step_mode ? F("Down") : F("Up");
// json[F("StepSize")] = step_size;
// json[F("TransitionTime")] = transition_time / 10.0f;
// if (0 == step_mode) {
// json[F(D_CMND_POWER)] = F("On");
// }
// }
// key = F(ZCL_LC_STOP);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F("Stop")] = 1;
// }
// key = F(ZCL_LC_STOP_WOO);
// if (json.containsKey(key)) {
// json.remove(key);
// json[F("Stop")] = 1;
// }
//
// // Lumi.weather proprietary field
// key = F(ZCL_LUMI_WEATHER);
// if (json.containsKey(key)) {
// String hex = json[key];
// SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length());
// DynamicJsonBuffer jsonBuffer;
// JsonObject& json_lumi = jsonBuffer.createObject();
// uint32_t i = 0;
// uint32_t len = buf2.len();
// char shortaddr[8];
//
// while (len - i >= 2) {
// uint8_t attrid = buf2.get8(i++);
//
// snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%02X"), attrid);
//
// //json[shortaddr] = parseSingleAttribute(json_lumi, buf2, i, len, nullptr, 0);
// }
// // parse output
// if (json_lumi.containsKey("0x64")) { // Temperature
// int32_t temperature = json_lumi["0x64"];
// json[F(D_JSON_TEMPERATURE)] = temperature / 100.0f;
// }
// if (json_lumi.containsKey("0x65")) { // Humidity
// uint32_t humidity = json_lumi["0x65"];
// json[F(D_JSON_HUMIDITY)] = humidity / 100.0f;
// }
// if (json_lumi.containsKey("0x66")) { // Pressure
// int32_t pressure = json_lumi["0x66"];
// json[F(D_JSON_PRESSURE)] = pressure / 100.0f;
// json[F(D_JSON_PRESSURE_UNIT)] = F(D_UNIT_PRESSURE); // hPa
// }
// if (json_lumi.containsKey("0x01")) { // Battery Voltage
// uint32_t voltage = json_lumi["0x01"];
// json[F(D_JSON_VOLTAGE)] = voltage / 1000.0f;
// json[F("Battery")] = toPercentageCR2032(voltage);
// }
// json.remove(key);
// }
//
// }
#endif // USE_ZIGBEE

View File

@ -47,6 +47,64 @@ const Z_CommandConverter Z_Commands[] = {
{ "ShutterTilt", "0102!08xx"}, // Tilt percentage
};
#define ZLE(x) ((x) & 0xFF), ((x) >> 8) // Little Endian
// Below are the attributes we wand to read from each cluster
const uint8_t CLUSTER_0006[] = { ZLE(0x0000) }; // Power
const uint8_t CLUSTER_0008[] = { ZLE(0x0000) }; // CurrentLevel
const uint8_t CLUSTER_0009[] = { ZLE(0x0000) }; // AlarmCount
const uint8_t CLUSTER_0300[] = { ZLE(0x0000), ZLE(0x0001), ZLE(0x0003), ZLE(0x0004), ZLE(0x0007) }; // Hue, Sat, X, Y, CT
int32_t Z_ReadAttrCallback(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint, uint32_t value) {
size_t attrs_len = 0;
const uint8_t* attrs = nullptr;
switch (cluster) {
case 0x0006: // for On/Off
attrs = CLUSTER_0006;
attrs_len = sizeof(CLUSTER_0006);
break;
case 0x0008: // for Dimmer
attrs = CLUSTER_0008;
attrs_len = sizeof(CLUSTER_0008);
break;
case 0x0009: // for Alarms
attrs = CLUSTER_0009;
attrs_len = sizeof(CLUSTER_0009);
break;
case 0x0300: // for Lights
attrs = CLUSTER_0300;
attrs_len = sizeof(CLUSTER_0300);
break;
}
if (attrs) {
ZigbeeZCLSend(shortaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
}
}
// set a timer to read back the value in the future
void zigbeeSetCommandTimer(uint16_t shortaddr, uint16_t cluster, uint16_t endpoint) {
uint32_t wait_ms = 0;
switch (cluster) {
case 0x0006: // for On/Off
case 0x0009: // for Alamrs
wait_ms = 200; // wait 0.2 s
break;
case 0x0008: // for Dimmer
case 0x0300: // for Color
wait_ms = 1050; // wait 1.0 s
break;
case 0x0102: // for Shutters
wait_ms = 10000; // wait 10.0 s
break;
}
if (wait_ms) {
zigbee_devices.setTimer(shortaddr, wait_ms, cluster, endpoint, 0 /* value */, &Z_ReadAttrCallback);
}
}
const __FlashStringHelper* zigbeeFindCommand(const char *command) {
char parm_uc[16]; // used to convert JSON keys to uppercase
for (uint32_t i = 0; i < sizeof(Z_Commands) / sizeof(Z_Commands[0]); i++) {
@ -114,6 +172,7 @@ const uint8_t kZ_Numbers[] PROGMEM = { 0,0,0,0,0,
2,2,
255 };
// Convert an alias like "On" to the corresponding number
uint32_t ZigbeeAliasOrNumber(const char *state_text) {
char command[16];
int state_number = GetCommandCode(command, sizeof(command), state_text, kZ_Alias);

View File

@ -370,15 +370,12 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
uint8_t seqnumber = buf.get8(17);
zigbee_devices.updateLastSeen(srcaddr);
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid);
#ifdef ZIGBEE_VERBOSE
zcl_received.publishMQTTReceived(groupid, clusterid, srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp);
#endif
ZCLFrame zcl_received = ZCLFrame::parseRawFrame(buf, 19, buf.get8(18), clusterid, groupid,
srcaddr,
srcendpoint, dstendpoint, wasbroadcast,
linkquality, securityuse, seqnumber,
timestamp);
zcl_received.log();
char shortaddr[8];
snprintf_P(shortaddr, sizeof(shortaddr), PSTR("0x%04X"), srcaddr);
@ -398,7 +395,7 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) {
String msg("");
msg.reserve(100);
json_root.printTo(msg);
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeZCLRawReceived: %s"), msg.c_str());
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLRawReceived: %s"), msg.c_str());
zcl_received.postProcessAttributes(srcaddr, json);
// Add linkquality

View File

@ -191,14 +191,9 @@ void ZigbeeInput(void)
SBuffer znp_buffer = zigbee_buffer->subBuffer(2, zigbee_frame_len - 3); // remove SOF, LEN and FCS
#ifdef ZIGBEE_VERBOSE
ToHex_P((unsigned char*)znp_buffer.getBuffer(), znp_buffer.len(), hex_char, sizeof(hex_char));
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPRECEIVED " %s"),
hex_char);
// Response_P(PSTR("{\"" D_JSON_ZIGBEEZNPRECEIVED "\":\"%s\"}"), hex_char);
// MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEEZNPRECEIVED));
// XdrvRulesProcess();
#endif
// now process the message
ZigbeeProcessInput(znp_buffer);
@ -235,13 +230,14 @@ void ZigbeeInit(void)
* Commands
\*********************************************************************************************/
uint32_t strToUInt(const JsonVariant val) {
uint32_t strToUInt(const JsonVariant &val) {
// if the string starts with 0x, it is considered Hex, otherwise it is an int
if (val.is<unsigned int>()) {
return val.as<unsigned int>();
} else {
if (val.is<char*>()) {
return strtoull(val.as<char*>(), nullptr, 0);
if (val.is<const char*>()) {
String sval = val.as<String>();
return strtoull(sval.c_str(), nullptr, 0);
}
}
return 0; // couldn't parse anything
@ -268,7 +264,7 @@ 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.payload, dump.c_str());
Response_P(PSTR("{\"%s%d\":%s}"), XdrvMailbox.command, XdrvMailbox.index, dump.c_str());
}
}
@ -319,15 +315,13 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) {
ZigbeeSerial->write(fcs); // finally send fcs checksum byte
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs);
}
#ifdef ZIGBEE_VERBOSE
// Now send a MQTT message to report the sent message
char hex_char[(len * 2) + 2];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"),
ToHex_P(msg, len, hex_char, sizeof(hex_char)));
#endif
}
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp = true, uint8_t transacId = 1) {
void ZigbeeZCLSend(uint16_t dtsAddr, uint16_t clusterId, uint8_t endpoint, uint8_t cmdId, bool clusterSpecific, const uint8_t *msg, size_t len, bool disableDefResp, uint8_t transacId) {
SBuffer buf(25+len);
buf.add8(Z_SREQ | Z_AF); // 24
buf.add8(AF_DATA_REQUEST); // 01
@ -422,6 +416,10 @@ void zigbeeZCLSendStr(uint16_t dstAddr, uint8_t endpoint, const char *data) {
// everything is good, we can send the command
ZigbeeZCLSend(dstAddr, cluster, endpoint, cmd, clusterSpecific, buf.getBuffer(), buf.len());
// now set the timer, if any, to read back the state later
if (clusterSpecific) {
zigbeeSetCommandTimer(dstAddr, cluster, endpoint);
}
ResponseCmndDone();
}
@ -539,7 +537,7 @@ void CmndZigbeeSend(void) {
// we have an unsupported command type, just ignore it and fallback to missing command
}
AddLog_P2(LOG_LEVEL_INFO, PSTR("ZigbeeCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"),
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeCmd_actual: ZigbeeZCLSend {\"device\":\"0x%04X\",\"endpoint\":%d,\"send\":\"%s\"}"),
device, endpoint, cmd_str.c_str());
zigbeeZCLSendStr(device, endpoint, cmd_str.c_str());
} else {
@ -593,6 +591,7 @@ void CmndZigbeeRead(void) {
const JsonVariant &val_attr = getCaseInsensitive(json, PSTR("Read"));
if (nullptr != &val_attr) {
uint16_t val = strToUInt(val_attr);
if (val_attr.is<JsonArray>()) {
JsonArray& attr_arr = val_attr;
attrs_len = attr_arr.size() * 2;
@ -604,19 +603,18 @@ void CmndZigbeeRead(void) {
attrs[i++] = val & 0xFF;
attrs[i++] = val >> 8;
}
} else {
attrs_len = 2;
attrs = new uint8_t[attrs_len];
uint16_t val = strToUInt(val_attr);
attrs[0] = val & 0xFF; // little endian
attrs[1] = val >> 8;
}
}
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
ZigbeeZCLSend(device, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, attrs, attrs_len, false /* we do want a response */);
if (attrs) { delete[] attrs; }
if (attrs) { delete[] attrs; }
ResponseCmndDone();
}
// Allow or Deny pairing of new Zigbee devices
@ -647,6 +645,11 @@ bool Xdrv23(uint8_t function)
if (zigbee.active) {
switch (function) {
case FUNC_EVERY_50_MSECOND:
if (!zigbee.init_phase) {
zigbee_devices.runTimer();
}
break;
case FUNC_LOOP:
if (ZigbeeSerial) { ZigbeeInput(); }
if (zigbee.state_machine) {

View File

@ -24,23 +24,9 @@
#define XDRV_27 27
#define D_PRFX_SHUTTER "Shutter"
#define D_CMND_SHUTTER_OPEN "Open"
#define D_CMND_SHUTTER_CLOSE "Close"
#define D_CMND_SHUTTER_STOP "Stop"
#define D_CMND_SHUTTER_POSITION "Position"
#define D_CMND_SHUTTER_OPENTIME "OpenDuration"
#define D_CMND_SHUTTER_CLOSETIME "CloseDuration"
#define D_CMND_SHUTTER_RELAY "Relay"
#define D_CMND_SHUTTER_SETHALFWAY "SetHalfway"
#define D_CMND_SHUTTER_SETCLOSE "SetClose"
#define D_CMND_SHUTTER_INVERT "Invert"
#define D_CMND_SHUTTER_CLIBRATION "Calibration"
#define D_CMND_SHUTTER_MOTORDELAY "MotorDelay"
#define D_SHUTTER "SHUTTER"
const uint16_t MOTOR_STOP_TIME = 500; // in mS
const uint16_t MOTOR_STOP_TIME = 500; // in mS
uint8_t calibrate_pos[6] = {0,30,50,70,90,100};
uint16_t messwerte[5] = {30,50,70,90,100};
@ -51,14 +37,16 @@ const char kShutterCommands[] PROGMEM = D_PRFX_SHUTTER "|"
D_CMND_SHUTTER_OPEN "|" D_CMND_SHUTTER_CLOSE "|" D_CMND_SHUTTER_STOP "|" D_CMND_SHUTTER_POSITION "|"
D_CMND_SHUTTER_OPENTIME "|" D_CMND_SHUTTER_CLOSETIME "|" D_CMND_SHUTTER_RELAY "|"
D_CMND_SHUTTER_SETHALFWAY "|" D_CMND_SHUTTER_SETCLOSE "|" D_CMND_SHUTTER_INVERT "|" D_CMND_SHUTTER_CLIBRATION "|"
D_CMND_SHUTTER_MOTORDELAY;
D_CMND_SHUTTER_MOTORDELAY "|" D_CMND_SHUTTER_FREQUENCY;
void (* const ShutterCommand[])(void) PROGMEM = {
&CmndShutterOpen, &CmndShutterClose, &CmndShutterStop, &CmndShutterPosition,
&CmndShutterOpenTime, &CmndShutterCloseTime, &CmndShutterRelay,
&CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay};
&CmndShutterSetHalfway, &CmndShutterSetClose, &CmndShutterInvert, &CmndShutterCalibration , &CmndShutterMotorDelay,
&CmndShutterFrequency};
const char JSON_SHUTTER_POS[] PROGMEM = "\"" D_PRFX_SHUTTER "%d\":{\"Position\":%d,\"direction\":%d}";
const char MSG_SHUTTER_POS[] PROGMEM = "SHT: " D_PRFX_SHUTTER " %d: Real. %d, Start: %d, Stop: %d, dir %d, motordelay %d, rtc: %s [s], freq %d";
#include <Ticker.h>
@ -68,23 +56,25 @@ struct SHUTTER {
power_t mask = 0; // bit mask with 11 at the position of relays that belong to at least ONE shutter
power_t old_power = 0; // preserve old bitmask for power to extract the relay that changes.
power_t switched_relay = 0; // bitmatrix that contain the relays that was lastly changed.
uint32_t time[MAX_SHUTTERS];
uint32_t time[MAX_SHUTTERS]; // operating time of the shutter in 0.05sec
int32_t open_max[MAX_SHUTTERS]; // max value on maximum open calculated
int32_t target_position[MAX_SHUTTERS]; // position to go to
int32_t start_position[MAX_SHUTTERS];
int32_t start_position[MAX_SHUTTERS]; // position before a movement is started. init at start
int32_t real_position[MAX_SHUTTERS]; // value between 0 and Shutter.open_max
uint16_t open_time[MAX_SHUTTERS]; // duration to open the shutter
uint16_t close_time[MAX_SHUTTERS]; // duration to close the shutter
uint16_t open_time[MAX_SHUTTERS]; // duration to open the shutter. 112 = 11.2sec
uint16_t close_time[MAX_SHUTTERS]; // duration to close the shutter. 112 = 11.2sec
uint16_t close_velocity[MAX_SHUTTERS]; // in relation to open velocity. higher value = faster
uint16_t operations[MAX_SHUTTERS];
int8_t direction[MAX_SHUTTERS]; // 1 == UP , 0 == stop; -1 == down
uint8_t mode = 0; // operation mode definition. see enum type above SHT_OFF_OPEN__OFF_CLOSE, SHT_OFF_ON__OPEN_CLOSE, SHT_PULSE_OPEN__PULSE_CLOSE
uint8_t motordelay[MAX_SHUTTERS]; // initial motorstarttime in 0.05sec.
int16_t motordelay[MAX_SHUTTERS]; // initial motorstarttime in 0.05sec.
int16_t pwm_frequency; // frequency of PWN for stepper motors
uint16_t max_pwm_frequency = 1000; // maximum of PWM frequency that can be used. depend on the motor and drivers
uint8_t skip_relay_change; // avoid overrun at endstops
} Shutter;
void ShutterRtc50mS(void)
{
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
for (uint32_t i = 0; i < shutters_present; i++) {
Shutter.time[i]++;
}
}
@ -94,7 +84,7 @@ int32_t ShutterPercentToRealPosition(uint8_t percent,uint8_t 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 {
uint32_t realpos;
int32_t realpos;
// check against DIV 0
for (uint8_t j=0 ; j < 5 ; j++) {
if (Settings.shuttercoeff[j][index] == 0) {
@ -128,7 +118,7 @@ uint8_t ShutterRealToPercentPosition(int32_t realpos, uint8_t index)
if (Settings.shutter_set50percent[index] != 50) {
return Settings.shuttercoeff[2][index] * 5 > realpos ? realpos / Settings.shuttercoeff[2][index] : (realpos-Settings.shuttercoeff[0][index]) / Settings.shuttercoeff[1][index];
} else {
uint16_t realpercent;
int16_t realpercent;
for (uint8_t i=0 ; i < 5 ; i++) {
if (realpos > Shutter.open_max[index] * calibrate_pos[i+1] / 100) {
@ -146,7 +136,7 @@ uint8_t ShutterRealToPercentPosition(int32_t realpos, uint8_t index)
break;
}
}
return realpercent;
return (realpercent < 0 ? 0 : (realpercent > 100 ? 0 : realpercent));
}
}
@ -158,8 +148,6 @@ void ShutterInit(void)
Shutter.old_power = power;
bool relay_in_interlock = false;
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Accuracy digits: %d"), Settings.shutter_accuracy);
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
// upgrade to 0.1sec calculation base.
if ( Settings.shutter_accuracy == 0) {
@ -189,6 +177,11 @@ void ShutterInit(void)
}
} else {
Shutter.mode = SHT_OFF_ON__OPEN_CLOSE;
if (pin[GPIO_PWM1+i] < 99) {
Shutter.pwm_frequency = 0;
analogWriteFreq(Shutter.pwm_frequency);
analogWrite(pin[GPIO_PWM1+i], 50);
}
}
TickerShutter.attach_ms(50, ShutterRtc50mS );
@ -220,8 +213,8 @@ void ShutterInit(void)
dtostrfd((float)Shutter.open_time[i] / 10 , 1, shutter_open_chr);
char shutter_close_chr[10];
dtostrfd((float)Shutter.close_time[i] / 10, 1, shutter_close_chr);
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100 Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoedffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, shuttermode %d,motordelay %d"),
i, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr,
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d (Relay:%d): Init. Pos: %d [%d %%], Open Vel.: 100 Close Vel.: %d , Max Way: %d, Opentime %s [s], Closetime %s [s], CoeffCalc: c0: %d, c1 %d, c2: %d, c3: %d, c4: %d, binmask %d, is inverted %d, shuttermode %d,motordelay %d"),
i+1, Settings.shutter_startrelay[i], Shutter.real_position[i], Settings.shutter_position[i], Shutter.close_velocity[i], Shutter.open_max[i], shutter_open_chr, shutter_close_chr,
Settings.shuttercoeff[0][i], Settings.shuttercoeff[1][i], Settings.shuttercoeff[2][i], Settings.shuttercoeff[3][i], Settings.shuttercoeff[4][i],
Shutter.mask, Settings.shutter_invert[i], Shutter.mode, Shutter.motordelay[i]);
@ -229,6 +222,9 @@ void ShutterInit(void)
// terminate loop at first INVALID shutter.
break;
}
if (shutters_present < 4) {
Shutter.max_pwm_frequency = Settings.shuttercoeff[4][4] > 0 ? Settings.shuttercoeff[4][4] : Shutter.max_pwm_frequency;
}
Settings.shutter_accuracy = 1;
}
}
@ -237,45 +233,32 @@ void ShutterUpdatePosition(void)
{
char scommand[CMDSZ];
char stopic[TOPSZ];
char stemp2[10];
for (uint32_t i = 0; i < shutters_present; i++) {
if (Shutter.direction[i] != 0) {
//char stemp1[20];
Shutter.real_position[i] = Shutter.start_position[i] + ( (Shutter.time[i] - Shutter.motordelay[i]) * (Shutter.direction[i] > 0 ? 100 : -Shutter.close_velocity[i]));
// avoid real position leaving the boundaries.
Shutter.real_position[i] = Shutter.real_position[i] < 0 ? 0 : (Shutter.real_position[i] > Shutter.open_max[i] ? Shutter.open_max[i] : Shutter.real_position[i]) ;
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE && pin[GPIO_PWM1+i] < 99 && pin[GPIO_CNTR1+i] < 99 ) {
// Calculate position with counter. Much more accurate and no need for motordelay workaround
// adding some steps to stop early
Shutter.real_position[i] = Shutter.direction[i] * 20 + ShutterCounterBasedPosition(i);;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: real %d, start %d, counter %d, max_freq %d, dir %d, freq %d"),Shutter.real_position[i], Shutter.start_position[i] ,RtcSettings.pulse_counter[i],Shutter.max_pwm_frequency , Shutter.direction[i] ,Shutter.max_pwm_frequency );
} else {
Shutter.real_position[i] = Shutter.start_position[i] + ( (Shutter.time[i] - Shutter.motordelay[i]) * (Shutter.direction[i] > 0 ? 100 : -Shutter.close_velocity[i]));
}
// Add additional runtime, if shutter did not reach the endstop for some time.
if (Shutter.target_position[i] == Shutter.real_position[i] && Shutter.target_position[i] == 0) {
// for every operation add 5x50ms = 250ms to stop position
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Adding additional runtime"));
Shutter.real_position[i] += 500 * Shutter.operations[i] ;
Shutter.operations[i] = 0;
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE && pin[GPIO_PWM1+i] < 99) {
uint16_t freq_change = Shutter.max_pwm_frequency/(Shutter.motordelay[i]+1);
// ramp up phase. calculate frequency
Shutter.pwm_frequency = tmin(freq_change * Shutter.time[i],Shutter.max_pwm_frequency);
// ramp down at the end of the movement time will not be exactly motordelay
Shutter.pwm_frequency = tmax(tmin(freq_change * (Shutter.target_position[i]-Shutter.real_position[i])*Shutter.direction[i]/30, Shutter.pwm_frequency),10);
analogWriteFreq(Shutter.pwm_frequency);
analogWrite(pin[GPIO_PWM1+i], 50);
}
if (Shutter.real_position[i] * Shutter.direction[i] >= Shutter.target_position[i] * Shutter.direction[i] ) {
// calculate relay number responsible for current movement.
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Stop Condition detected: real: %d, Target: %d, direction: %d"),Shutter.real_position[i], Shutter.target_position[i],Shutter.direction[i]);
uint8_t cur_relay = Settings.shutter_startrelay[i] + (Shutter.direction[i] == 1 ? 0 : 1) ;
char stemp2[10];
Settings.shutter_position[i] = ShutterRealToPercentPosition(Shutter.real_position[i], i);
//Settings.shutter_position[i] = Settings.shuttercoeff[2][i] * 5 > Shutter.real_position[i] ? (Shutter.real_position[i] * 10 / Settings.shuttercoeff[2][i] + 4)/10 : ((Shutter.real_position[i]-Settings.shuttercoeff[0,i]) *10 / Settings.shuttercoeff[1][i] +4) / 10;
if (0 < Settings.shutter_position[i] && Settings.shutter_position[i] < 100) {
Shutter.operations[i]++;
} else {
Shutter.operations[i] = 0;
}
dtostrfd((float)Shutter.time[i] / 20, 1, stemp2);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Real Pos. %d, Stoppos: %ld, relay: %d, direction %d, pulsetimer: %d, rtcshutter: %s [s], operationtime %d"), i, Shutter.real_position[i], Settings.shutter_position[i], cur_relay -1, Shutter.direction[i], Settings.pulse_timer[cur_relay -1], stemp2, Shutter.operations[i]);
Shutter.start_position[i] = Shutter.real_position[i];
// sending MQTT result to broker
snprintf_P(scommand, sizeof(scommand),PSTR(D_SHUTTER "%d"), i+1);
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
Response_P("%d", Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]);
MqttPublish(stopic, Settings.flag.mqtt_power_retain); // CMND_POWERRETAIN
switch (Shutter.mode) {
case SHT_PULSE_OPEN__PULSE_CLOSE:
@ -288,8 +271,25 @@ void ShutterUpdatePosition(void)
break;
case SHT_OFF_ON__OPEN_CLOSE:
// This is a failsafe configuration. Relay1 ON/OFF Relay2 -1/1 direction
// Only allow PWM microstepping if PWM and COUNTER are defined.
// see wiki to connect PWM and COUNTER
if (pin[GPIO_PWM1+i] < 99 && pin[GPIO_CNTR1+i] < 99 ) {
int16_t missing_steps = ((Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) - RtcSettings.pulse_counter[i];
//prepare for stop PWM
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Remain steps %d, counter %d, freq %d"), missing_steps, RtcSettings.pulse_counter[i] ,Shutter.pwm_frequency);
Shutter.pwm_frequency = 0;
analogWriteFreq(Shutter.pwm_frequency);
while (RtcSettings.pulse_counter[i] < (uint32_t)(Shutter.target_position[i]-Shutter.start_position[i])*Shutter.direction[i]*Shutter.max_pwm_frequency/2000) {
delay(1);
}
analogWrite(pin[GPIO_PWM1+i], 0);
Shutter.real_position[i] = ShutterCounterBasedPosition(i);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT:Real %d, pulsecount %d, start %d"), Shutter.real_position[i],RtcSettings.pulse_counter[i], Shutter.start_position[i]);
}
if ((1 << (Settings.shutter_startrelay[i]-1)) & power) {
ExecuteCommandPower(Settings.shutter_startrelay[i], 0, SRC_SHUTTER);
ExecuteCommandPower(Settings.shutter_startrelay[i]+1, 0, SRC_SHUTTER);
}
break;
case SHT_OFF_OPEN__OFF_CLOSE:
@ -300,6 +300,18 @@ void ShutterUpdatePosition(void)
}
break;
}
Settings.shutter_position[i] = ShutterRealToPercentPosition(Shutter.real_position[i], i);
dtostrfd((float)Shutter.time[i] / 20, 1, stemp2);
AddLog_P2(LOG_LEVEL_INFO, MSG_SHUTTER_POS, i+1, Shutter.real_position[i], Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i], Shutter.motordelay[i],stemp2,Shutter.pwm_frequency);
Shutter.start_position[i] = Shutter.real_position[i];
// sending MQTT result to broker
snprintf_P(scommand, sizeof(scommand),PSTR(D_SHUTTER "%d"), i+1);
GetTopic_P(stopic, STAT, mqtt_topic, scommand);
Response_P("%d", Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i]);
MqttPublish(stopic, Settings.flag.mqtt_power_retain); // CMND_POWERRETAIN
Shutter.direction[i] = 0;
uint8_t position = Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i];
Response_P(PSTR("{"));
@ -320,32 +332,73 @@ bool ShutterState(uint8_t device)
(Shutter.mask & (1 << (Settings.shutter_startrelay[device]-1))) );
}
void ShutterStartInit(uint8_t index, uint8_t direction, int32_t target_pos)
void ShutterStartInit(uint8_t index, int8_t direction, int32_t target_pos)
{
Shutter.direction[index] = direction;
Shutter.target_position[index] = target_pos;
Shutter.start_position[index] = Shutter.real_position[index];
Shutter.time[index] = 0;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: dir %d, delta1 %d, delta2 %d, grant %d"),direction, (Shutter.open_max[index] - Shutter.real_position[index]) / Shutter.close_velocity[index], Shutter.real_position[index] / Shutter.close_velocity[index], 2+Shutter.motordelay[index]);
if ( ( direction == 1 && (Shutter.open_max[index] - Shutter.real_position[index]) / 100 <= 2 )
|| ( direction == -1 && Shutter.real_position[index] / Shutter.close_velocity[index] <= 2)) {
Shutter.skip_relay_change = 1 ;
} else {
if (pin[GPIO_PWM1+index] < 99) {
Shutter.pwm_frequency = 0;
analogWriteFreq(Shutter.pwm_frequency);
analogWrite(pin[GPIO_PWM1+index], 0);
// can be operated without counter, but then not that acurate.
if (pin[GPIO_CNTR1+index] < 99) {
RtcSettings.pulse_counter[index] = 0;
}
}
Shutter.target_position[index] = target_pos;
Shutter.start_position[index] = Shutter.real_position[index];
Shutter.time[index] = 0;
Shutter.skip_relay_change = 0;
Shutter.direction[index] = direction;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: real %d, start %d, counter %d, max_freq %d, dir %d, freq %d"),Shutter.real_position[index], Shutter.start_position[index] ,RtcSettings.pulse_counter[index],Shutter.max_pwm_frequency , Shutter.direction[index] ,Shutter.max_pwm_frequency );
}
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start shutter: %d from %d to %d in directin %d"), index, Shutter.start_position[index], Shutter.target_position[index], Shutter.direction[index]);
}
void ShutterDelayForMotorStop(void)
void ShutterWaitForMotorStop(uint8_t index)
{
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Wait for Motorstop %d"), MOTOR_STOP_TIME);
delay(MOTOR_STOP_TIME);
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Wait for Motorstop.."));
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE) {
if (pin[GPIO_PWM1+index] < 99 && pin[GPIO_CNTR1+index] < 99 ) {
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Frequency change %d"), Shutter.pwm_frequency);
while (Shutter.pwm_frequency > 100) {
Shutter.pwm_frequency = tmax(Shutter.pwm_frequency-(Shutter.max_pwm_frequency/(Shutter.motordelay[index]+1)) , 0);
analogWriteFreq(Shutter.pwm_frequency);
analogWrite(pin[GPIO_PWM1+index], 50);
delay(50);
}
Shutter.pwm_frequency = 0;
analogWriteFreq(Shutter.pwm_frequency);
analogWrite(pin[GPIO_PWM1+index], 0);
Shutter.real_position[index] = ShutterCounterBasedPosition(index);
} else {
ExecuteCommandPower(Settings.shutter_startrelay[index], 0, SRC_SHUTTER);
delay(MOTOR_STOP_TIME);
}
} else {
delay(MOTOR_STOP_TIME);
}
}
void ShutterReportPosition(void)
{
uint16_t shutter_moving = 0;
for (uint32_t i = 0; i < shutters_present; i++) {
for (uint8_t i = 0; i < shutters_present; i++) {
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d: Real Pos: %d"), i+1,Shutter.real_position[i]);
if (Shutter.direction[i] != 0) {
char stemp1[20];
char stemp2[10];
dtostrfd((float)Shutter.time[i] / 20, 1, stemp2);
uint8_t position = ShutterRealToPercentPosition(Shutter.real_position[i], i);
dtostrfd((float)Shutter.time[i] / 20, 2, stemp2);
shutter_moving = 1;
//Settings.shutter_position[i] = Settings.shuttercoeff[2][i] * 5 > Shutter.real_position[i] ? Shutter.real_position[i] / Settings.shuttercoeff[2][i] : (Shutter.real_position[i]-Settings.shuttercoeff[0,i]) / Settings.shuttercoeff[1][i];
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Shutter %d: Real Pos: %d, Target %d, source: %s, start-pos: %d %%, direction: %d, rtcshutter: %s [s]"), i,Shutter.real_position[i], Shutter.target_position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), Settings.shutter_position[i], Shutter.direction[i], stemp2 );
AddLog_P2(LOG_LEVEL_INFO, MSG_SHUTTER_POS, i+1, Shutter.real_position[i], Shutter.start_position[i], Shutter.target_position[i], Shutter.direction[i], Shutter.motordelay[i],stemp2,Shutter.pwm_frequency);
Response_P(PSTR("{"));
ResponseAppend_P(JSON_SHUTTER_POS, i+1, Settings.shutter_invert[i] ? 100-position : position, Shutter.direction[i]);
ResponseJsonEnd();
MqttPublishPrefixTopic_P(RESULT_OR_TELE, mqtt_data);
}
}
if (rules_flag.shutter_moving > shutter_moving) {
@ -357,6 +410,11 @@ void ShutterReportPosition(void)
//AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: rules_flag.shutter_moving: %d, moved %d"), rules_flag.shutter_moving, rules_flag.shutter_moved);
}
int32_t ShutterCounterBasedPosition(uint8 i)
{
return ((int32_t)RtcSettings.pulse_counter[i]*Shutter.direction[i]*2000 / Shutter.max_pwm_frequency)+Shutter.start_position[i];
}
void ShutterRelayChanged(void)
{
@ -369,49 +427,44 @@ void ShutterRelayChanged(void)
power_t powerstate_local = (power >> (Settings.shutter_startrelay[i] -1)) & 3;
//uint8 manual_relays_changed = ((Shutter.switched_relay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_IGNORE != last_source && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ;
uint8 manual_relays_changed = ((Shutter.switched_relay >> (Settings.shutter_startrelay[i] -1)) & 3) && SRC_SHUTTER != last_source && SRC_PULSETIMER != last_source ;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: source: %s, powerstate_local %ld, Shutter.switched_relay %d, manual change %d"), i+1, GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), powerstate_local,Shutter.switched_relay,manual_relays_changed);
if (manual_relays_changed) {
//Shutter.skip_relay_change = true;
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE) {
ShutterWaitForMotorStop(i);
switch (powerstate_local) {
case 1:
ShutterDelayForMotorStop();
ShutterStartInit(i, 1, Shutter.open_max[i]);
ShutterStartInit(i, 1, Shutter.open_max[i]);
break;
case 3:
ShutterDelayForMotorStop();
ShutterStartInit(i, -1, 0);
break;
default:
Shutter.direction[i] = 0;
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Switch OFF motor."),i);
Shutter.target_position[i] = Shutter.real_position[i];
}
} else {
if (Shutter.direction[i] != 0 && (!powerstate_local || (powerstate_local && Shutter.mode == SHT_PULSE_OPEN__PULSE_CLOSE))) {
Shutter.target_position[i] = Shutter.real_position[i];
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Switch OFF motor. Target: %ld, source: %s, powerstate_local %ld, Shutter.switched_relay %d, manual change %d"), i, Shutter.target_position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), powerstate_local,Shutter.switched_relay,manual_relays_changed);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Switch OFF motor. Target: %ld, source: %s, powerstate_local %ld, Shutter.switched_relay %d, manual change %d"), i+1, Shutter.target_position[i], GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource), powerstate_local,Shutter.switched_relay,manual_relays_changed);
} else {
last_source = SRC_SHUTTER; // avoid switch off in the next loop
if (powerstate_local == 2) { // testing on CLOSE relay, if ON
// close with relay two
ShutterDelayForMotorStop();
ShutterWaitForMotorStop(i);
ShutterStartInit(i, -1, 0);
} else {
// opens with relay one
ShutterDelayForMotorStop();
ShutterWaitForMotorStop(i);
ShutterStartInit(i, 1, Shutter.open_max[i]);
}
}
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Target: %ld, powerstatelocal %d"), i, Shutter.target_position[i], powerstate_local);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Shutter %d: Target: %ld, powerstatelocal %d"), i+1, Shutter.target_position[i], powerstate_local);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////
// Shutter specific functions
// TODO: move to shutter driver and make them accessible in a generic way
// device: 1..<numberOfShutters>
// position: 0-100
void ShutterSetPosition(uint8_t device, uint8_t position)
{
char svalue[32]; // Command and number parameter
@ -425,16 +478,23 @@ void ShutterSetPosition(uint8_t device, uint8_t position)
void CmndShutterOpen(void)
{
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Payload close: %d, index %d"), XdrvMailbox.payload, XdrvMailbox.index);
if ( XdrvMailbox.index == 1 && XdrvMailbox.payload != -99) {
XdrvMailbox.index = XdrvMailbox.payload;
}
XdrvMailbox.payload = 100;
XdrvMailbox.data_len = 3;
last_source = SRC_WEBGUI;
CmndShutterPosition();
}
void CmndShutterClose(void)
{
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Payload open: %d, index %d"), XdrvMailbox.payload, XdrvMailbox.index);
if ( XdrvMailbox.index == 1 && XdrvMailbox.payload != -99) {
XdrvMailbox.index = XdrvMailbox.payload;
}
XdrvMailbox.payload = 0;
XdrvMailbox.data_len = 1;
XdrvMailbox.data_len = 0;
last_source = SRC_WEBGUI;
CmndShutterPosition();
}
@ -442,11 +502,14 @@ void CmndShutterClose(void)
void CmndShutterStop(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
if ( XdrvMailbox.index == 1 && XdrvMailbox.payload != -99) {
XdrvMailbox.index = XdrvMailbox.payload;
}
uint32_t index = XdrvMailbox.index -1;
if (Shutter.direction[index] != 0) {
//AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Stop moving shutter %d: direction: %d"), XdrvMailbox.index, Shutter.direction[index]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Stop moving %d: dir: %d"), XdrvMailbox.index, Shutter.direction[index]);
// set stop position 10 steps ahead (0.5sec to allow normal stop)
int32_t temp_realpos = Shutter.start_position[index] + ( (Shutter.time[index]+10) * (Shutter.direction[index] > 0 ? 100 : -Shutter.close_velocity[index]));
XdrvMailbox.payload = ShutterRealToPercentPosition(temp_realpos, index);
//XdrvMailbox.payload = Settings.shuttercoeff[2][index] * 5 > temp_realpos ? temp_realpos / Settings.shuttercoeff[2][index] : (temp_realpos-Settings.shuttercoeff[0,index]) / Settings.shuttercoeff[1][index];
@ -463,7 +526,7 @@ void CmndShutterPosition(void)
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
uint32_t index = XdrvMailbox.index -1;
//limit the payload
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Position in: payload %s (%d), payload %d, index %d, source %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, last_source );
AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Pos. in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, last_source );
if (XdrvMailbox.data_len > 1 && XdrvMailbox.payload <=0) {
UpperCase(XdrvMailbox.data, XdrvMailbox.data);
@ -472,7 +535,7 @@ void CmndShutterPosition(void)
if (!strcmp(XdrvMailbox.data,"STOP")) { CmndShutterStop(); }
return;
}
int8_t target_pos_percent = XdrvMailbox.payload < 0 ? 0 : (XdrvMailbox.payload > 100 ? 100 : XdrvMailbox.payload);
// webgui still send also on inverted shutter the native position.
target_pos_percent = Settings.shutter_invert[index] && SRC_WEBGUI != last_source ? 100 - target_pos_percent : target_pos_percent;
@ -480,7 +543,7 @@ void CmndShutterPosition(void)
//target_pos_percent = Settings.shutter_invert[index] ? 100 - target_pos_percent : target_pos_percent;
Shutter.target_position[index] = ShutterPercentToRealPosition(target_pos_percent, index);
//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:, realpos %d, target %d, payload %d"), last_source, Shutter.real_position[index] ,Shutter.target_position[index],target_pos_percent);
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) {
int8_t new_shutterdirection = Shutter.real_position[index] < Shutter.target_position[index] ? 1 : -1;
@ -491,24 +554,26 @@ void CmndShutterPosition(void)
ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 0 : 1), 1, SRC_SHUTTER);
delay(100);
} else {
ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 1 : 0), 0, SRC_SHUTTER);
ShutterDelayForMotorStop();
if (Shutter.mode == SHT_OFF_OPEN__OFF_CLOSE) {
ExecuteCommandPower(Settings.shutter_startrelay[index] + (new_shutterdirection == 1 ? 1 : 0), 0, SRC_SHUTTER);
ShutterWaitForMotorStop(index);
}
}
}
if (Shutter.direction[index] != new_shutterdirection ) {
ShutterStartInit(index, new_shutterdirection, Shutter.target_position[index]);
Shutter.operations[index]++;
if (Shutter.mode == SHT_OFF_ON__OPEN_CLOSE) {
ExecuteCommandPower(Settings.shutter_startrelay[index], 0, SRC_SHUTTER);
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Delay5 5s, xdrv %d"), XdrvMailbox.payload);
ShutterDelayForMotorStop();
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);
} else {
// now start the motor for the right direction, work for momentary and normal shutters.
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Start shutter in direction %d"), Shutter.direction[index]);
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);
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("SHT: Delay6 5s, xdrv %d"), XdrvMailbox.payload);
}
@ -556,7 +621,7 @@ void CmndShutterMotorDelay(void)
ShutterInit();
}
char time_chr[10];
dtostrfd((float)(Settings.shutter_motordelay[XdrvMailbox.index -1]) / 20, 1, time_chr);
dtostrfd((float)(Settings.shutter_motordelay[XdrvMailbox.index -1]) / 20, 2, time_chr);
ResponseCmndIdxChar(time_chr);
}
}
@ -571,7 +636,6 @@ void CmndShutterRelay(void)
} else {
Shutter.mask ^= 3 << (Settings.shutter_startrelay[XdrvMailbox.index -1] - 1);
}
AddLog_P2(LOG_LEVEL_INFO, PSTR("SHT: Relay %d is %d"), XdrvMailbox.index, XdrvMailbox.payload);
Settings.shutter_startrelay[XdrvMailbox.index -1] = XdrvMailbox.payload;
ShutterInit();
// if payload is 0 to disable the relay there must be a reboot. Otherwhise does not work
@ -586,10 +650,21 @@ void CmndShutterSetHalfway(void)
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
Settings.shutter_set50percent[XdrvMailbox.index -1] = Settings.shutter_invert[XdrvMailbox.index -1] ? 100 - XdrvMailbox.payload : XdrvMailbox.payload;
ShutterInit();
ResponseCmndIdxNumber(XdrvMailbox.payload); // ????
} else {
ResponseCmndIdxNumber(Settings.shutter_set50percent[XdrvMailbox.index -1]);
}
ResponseCmndIdxNumber(Settings.shutter_invert[XdrvMailbox.index -1] ? 100 - Settings.shutter_set50percent[XdrvMailbox.index -1] : Settings.shutter_set50percent[XdrvMailbox.index -1]);
}
}
void CmndShutterFrequency(void)
{
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 20000)) {
Shutter.max_pwm_frequency = XdrvMailbox.payload;
if (shutters_present < 4) {
Settings.shuttercoeff[4][4] = Shutter.max_pwm_frequency;
}
ResponseCmndNumber(XdrvMailbox.payload); // ????
} else {
ResponseCmndNumber(Shutter.max_pwm_frequency);
}
}
@ -599,7 +674,7 @@ void CmndShutterSetClose(void)
Shutter.real_position[XdrvMailbox.index -1] = 0;
ShutterStartInit(XdrvMailbox.index -1, 0, 0);
Settings.shutter_position[XdrvMailbox.index -1] = 0;
ResponseCmndChar(D_CONFIGURATION_RESET);
ResponseCmndIdxChar(D_CONFIGURATION_RESET);
}
}
@ -615,7 +690,7 @@ void CmndShutterInvert(void)
void CmndShutterCalibration(void) // ????
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_SHUTTERS)) {
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= shutters_present)) {
if (XdrvMailbox.data_len > 0) {
uint32_t i = 0;
char *str_ptr;
@ -658,13 +733,15 @@ bool Xdrv27(uint8_t function)
ShutterUpdatePosition();
break;
case FUNC_EVERY_SECOND:
//case FUNC_EVERY_250_MSECOND:
ShutterReportPosition();
break;
case FUNC_COMMAND:
result = DecodeCommand(kShutterCommands, ShutterCommand);
break;
case FUNC_JSON_APPEND:
for (uint32_t i = 0; i < shutters_present; i++) {
for (uint8_t i = 0; i < shutters_present; i++) {
uint8_t position = Settings.shutter_invert[i] ? 100 - Settings.shutter_position[i]: Settings.shutter_position[i];
ResponseAppend_P(",");
ResponseAppend_P(JSON_SHUTTER_POS, i+1, position, Shutter.direction[i]);
@ -678,10 +755,26 @@ bool Xdrv27(uint8_t function)
case FUNC_SET_POWER:
char stemp1[10];
// extract the number of the relay that was switched and save for later in Update Position.
Shutter.switched_relay = power ^ Shutter.old_power;
Shutter.old_power = power;
Shutter.switched_relay = XdrvMailbox.index ^ Shutter.old_power;
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Switched relay: %d by %s"), Shutter.switched_relay,GetTextIndexed(stemp1, sizeof(stemp1), last_source, kCommandSource));
ShutterRelayChanged();
Shutter.old_power = XdrvMailbox.index;
break;
case FUNC_SET_DEVICE_POWER:
if (Shutter.skip_relay_change ) {
uint8_t i;
for (i = 0; i < devices_present; i++) {
if (Shutter.switched_relay &1) {
break;
}
Shutter.switched_relay >>= 1;
}
//AddLog_P2(LOG_LEVEL_ERROR, PSTR("SHT: skip relay change: %d"),i+1);
result = true;
Shutter.skip_relay_change = 0;
AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Skipping switch off relay %d"),i);
ExecuteCommandPower(i+1, 0, SRC_SHUTTER);
}
break;
}
}

View File

@ -25,8 +25,6 @@
#endif // USE_DEBUG_DRIVER
#endif // DEBUG_THEO
//#define USE_DEBUG_SETTING_NAMES
#ifdef USE_DEBUG_DRIVER
/*********************************************************************************************\
* Virtual debugging support - Part1
@ -64,9 +62,6 @@
const char kDebugCommands[] PROGMEM = "|" // No prefix
D_CMND_CFGDUMP "|" D_CMND_CFGPEEK "|" D_CMND_CFGPOKE "|"
#ifdef USE_DEBUG_SETTING_NAMES
D_CMND_CFGSHOW "|"
#endif
#ifdef USE_WEBSERVER
D_CMND_CFGXOR "|"
#endif
@ -82,9 +77,6 @@ const char kDebugCommands[] PROGMEM = "|" // No prefix
void (* const DebugCommand[])(void) PROGMEM = {
&CmndCfgDump, &CmndCfgPeek, &CmndCfgPoke,
#ifdef USE_DEBUG_SETTING_NAMES
&CmndCfgShow,
#endif
#ifdef USE_WEBSERVER
&CmndCfgXor,
#endif
@ -391,36 +383,6 @@ void DebugCfgPoke(char* parms)
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: 0x%0LX (%lu) poked to 0x%0LX (%lu)"), address, data32, data32, ndata32, ndata32);
}
#ifdef USE_DEBUG_SETTING_NAMES
void DebugCfgShow(uint8_t more)
{
uint8_t *SetAddr;
SetAddr = (uint8_t *)&Settings;
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Hostname (%d) [%s]"), (uint8_t *)&Settings.hostname - SetAddr, sizeof(Settings.hostname)-1, Settings.hostname);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: SSids (%d) [%s], [%s]"), (uint8_t *)&Settings.sta_ssid - SetAddr, sizeof(Settings.sta_ssid[0])-1, Settings.sta_ssid[0], Settings.sta_ssid[1]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Friendlynames (%d) [%s], [%s], [%s], [%s]"), (uint8_t *)&Settings.friendlyname - SetAddr, sizeof(Settings.friendlyname[0])-1, Settings.friendlyname[0], Settings.friendlyname[1], Settings.friendlyname[2], Settings.friendlyname[3]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: OTA Url (%d) [%s]"), (uint8_t *)&Settings.ota_url - SetAddr, sizeof(Settings.ota_url)-1, Settings.ota_url);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: StateText (%d) [%s], [%s], [%s], [%s]"), (uint8_t *)&Settings.state_text - SetAddr, sizeof(Settings.state_text[0])-1, Settings.state_text[0], Settings.state_text[1], Settings.state_text[2], Settings.state_text[3]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Syslog Host (%d) [%s]"), (uint8_t *)&Settings.syslog_host - SetAddr, sizeof(Settings.syslog_host)-1, Settings.syslog_host);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: NTP Servers (%d) [%s], [%s], [%s]"), (uint8_t *)&Settings.ntp_server - SetAddr, sizeof(Settings.ntp_server[0])-1, Settings.ntp_server[0], Settings.ntp_server[1], Settings.ntp_server[2]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Host (%d) [%s]"), (uint8_t *)&Settings.mqtt_host - SetAddr, sizeof(Settings.mqtt_host)-1, Settings.mqtt_host);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Client (%d) [%s]"), (uint8_t *)&Settings.mqtt_client - SetAddr, sizeof(Settings.mqtt_client)-1, Settings.mqtt_client);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT User (%d) [%s]"), (uint8_t *)&Settings.mqtt_user - SetAddr, sizeof(Settings.mqtt_user)-1, Settings.mqtt_user);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT FullTopic (%d) [%s]"), (uint8_t *)&Settings.mqtt_fulltopic - SetAddr, sizeof(Settings.mqtt_fulltopic)-1, Settings.mqtt_fulltopic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Topic (%d) [%s]"), (uint8_t *)&Settings.mqtt_topic - SetAddr, sizeof(Settings.mqtt_topic)-1, Settings.mqtt_topic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT GroupTopic (%d) [%s]"), (uint8_t *)&Settings.mqtt_grptopic - SetAddr, sizeof(Settings.mqtt_grptopic)-1, Settings.mqtt_grptopic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT ButtonTopic (%d) [%s]"), (uint8_t *)&Settings.button_topic - SetAddr, sizeof(Settings.button_topic)-1, Settings.button_topic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT SwitchTopic (%d) [%s]"), (uint8_t *)&Settings.switch_topic - SetAddr, sizeof(Settings.switch_topic)-1, Settings.switch_topic);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Prefixes (%d) [%s], [%s], [%s]"), (uint8_t *)&Settings.mqtt_prefix - SetAddr, sizeof(Settings.mqtt_prefix[0])-1, Settings.mqtt_prefix[0], Settings.mqtt_prefix[1], Settings.mqtt_prefix[2]);
if (17 == more) {
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: AP Passwords (%d) [%s], [%s]"), (uint8_t *)&Settings.sta_pwd - SetAddr, sizeof(Settings.sta_pwd[0])-1, Settings.sta_pwd[0], Settings.sta_pwd[1]);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: MQTT Password (%d) [%s]"), (uint8_t *)&Settings.mqtt_pwd - SetAddr, sizeof(Settings.mqtt_pwd)-1, Settings.mqtt_pwd);
AddLog_P2(LOG_LEVEL_INFO, PSTR("%03X: Web Password (%d) [%s]"), (uint8_t *)&Settings.web_password - SetAddr, sizeof(Settings.web_password)-1, Settings.web_password);
}
}
#endif // USE_DEBUG_SETTING_NAMES
void SetFlashMode(uint8_t mode)
{
uint8_t *_buffer;
@ -474,14 +436,6 @@ void CmndCfgPoke(void)
ResponseCmndDone();
}
#ifdef USE_DEBUG_SETTING_NAMES
void CmndCfgShow(void)
{
DebugCfgShow(XdrvMailbox.payload);
ResponseCmndDone();
}
#endif // USE_DEBUG_SETTING_NAMES
#ifdef USE_WEBSERVER
void CmndCfgXor(void)
{

View File

@ -211,7 +211,7 @@ void CseDrvInit(void)
{
if ((3 == pin[GPIO_CSE7766_RX]) && (1 == pin[GPIO_CSE7766_TX])) { // As it uses 8E1 currently only hardware serial is supported
baudrate = 4800;
serial_config = SERIAL_8E1;
Settings.serial_config = TS_SERIAL_8E1;
if (0 == Settings.param[P_CSE7766_INVALID_POWER]) {
Settings.param[P_CSE7766_INVALID_POWER] = CSE_MAX_INVALID_POWER; // SetOption39 1..255
}

View File

@ -28,7 +28,7 @@
#define XSNS_06 6
#define DHT_MAX_SENSORS 3
#define DHT_MAX_SENSORS 4
#define DHT_MAX_RETRY 8
uint32_t dht_max_cycles;

View File

@ -57,6 +57,7 @@ int16_t MPU_6050_temperature = 0;
VectorInt16 aaReal; // [x, y, z] gravity-free accel sensor measurements
VectorFloat gravity; // [x, y, z] gravity vector
float euler[3]; // [psi, theta, phi] Euler angle container
float yawPitchRoll[3]; // [yaw, pitch roll] Yaw-pitch-roll container
} MPU6050_DMP;
MPU6050_DMP MPU6050_dmp;
@ -68,7 +69,7 @@ MPU6050 mpu6050;
void MPU_6050PerformReading(void)
{
#ifdef USE_MPU6050_DMP
mpu6050.resetFIFO(); // with a default dampling rate of 200Hz, we create a delay of approx. 5ms with a complete read cycle
mpu6050.resetFIFO(); // with a default sampling rate of 200Hz, we create a delay of approx. 5ms with a complete read cycle
MPU6050_dmp.fifoCount = mpu6050.getFIFOCount();
while (MPU6050_dmp.fifoCount < MPU6050_dmp.packetSize) MPU6050_dmp.fifoCount = mpu6050.getFIFOCount();
mpu6050.getFIFOBytes(MPU6050_dmp.fifoBuffer, MPU6050_dmp.packetSize);
@ -79,6 +80,7 @@ void MPU_6050PerformReading(void)
mpu6050.dmpGetAccel(&MPU6050_dmp.aa, MPU6050_dmp.fifoBuffer);
mpu6050.dmpGetGravity(&MPU6050_dmp.gravity, &MPU6050_dmp.q);
mpu6050.dmpGetLinearAccel(&MPU6050_dmp.aaReal, &MPU6050_dmp.aa, &MPU6050_dmp.gravity);
mpu6050.dmpGetYawPitchRoll(MPU6050_dmp.yawPitchRoll, &MPU6050_dmp.q, &MPU6050_dmp.gravity);
MPU_6050_gx = MPU6050_dmp.euler[0] * 180/M_PI;
MPU_6050_gy = MPU6050_dmp.euler[1] * 180/M_PI;
MPU_6050_gz = MPU6050_dmp.euler[2] * 180/M_PI;
@ -145,6 +147,10 @@ void MPU_6050Detect(void)
}
}
#define D_YAW "Yaw"
#define D_PITCH "Pitch"
#define D_ROLL "Roll"
#ifdef USE_WEBSERVER
const char HTTP_SNS_AXIS[] PROGMEM =
"{s}" D_SENSOR_MPU6050 " " D_AX_AXIS "{m}%s{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
@ -153,6 +159,12 @@ const char HTTP_SNS_AXIS[] PROGMEM =
"{s}" D_SENSOR_MPU6050 " " D_GX_AXIS "{m}%s{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{s}" D_SENSOR_MPU6050 " " D_GY_AXIS "{m}%s{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{s}" D_SENSOR_MPU6050 " " D_GZ_AXIS "{m}%s{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
#ifdef USE_MPU6050_DMP
const char HTTP_SNS_YPR[] PROGMEM =
"{s}" D_SENSOR_MPU6050 " " D_YAW "{m}%s{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{s}" D_SENSOR_MPU6050 " " D_PITCH "{m}%s{e}" // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
"{s}" D_SENSOR_MPU6050 " " D_ROLL "{m}%s{e}"; // {s} = <tr><th>, {m} = </th><td>, {e} = </td></tr>
#endif // USE_MPU6050_DMP
#endif // USE_WEBSERVER
#define D_JSON_AXIS_AX "AccelXAxis"
@ -161,6 +173,9 @@ const char HTTP_SNS_AXIS[] PROGMEM =
#define D_JSON_AXIS_GX "GyroXAxis"
#define D_JSON_AXIS_GY "GyroYAxis"
#define D_JSON_AXIS_GZ "GyroZAxis"
#define D_JSON_YAW "Yaw"
#define D_JSON_PITCH "Pitch"
#define D_JSON_ROLL "Roll"
void MPU_6050Show(bool json)
{
@ -181,6 +196,14 @@ void MPU_6050Show(bool json)
dtostrfd(MPU_6050_gy, Settings.flag2.axis_resolution, axis_gy);
char axis_gz[33];
dtostrfd(MPU_6050_gz, Settings.flag2.axis_resolution, axis_gz);
#ifdef USE_MPU6050_DMP
char axis_yaw[33];
dtostrfd(MPU6050_dmp.yawPitchRoll[0] / PI * 180.0, Settings.flag2.axis_resolution, axis_yaw);
char axis_pitch[33];
dtostrfd(MPU6050_dmp.yawPitchRoll[1] / PI * 180.0, Settings.flag2.axis_resolution, axis_pitch);
char axis_roll[33];
dtostrfd(MPU6050_dmp.yawPitchRoll[2] / PI * 180.0, Settings.flag2.axis_resolution, axis_roll);
#endif // USE_MPU6050_DMP
if (json) {
char json_axis_ax[25];
@ -195,8 +218,20 @@ void MPU_6050Show(bool json)
snprintf_P(json_axis_gy, sizeof(json_axis_gy), PSTR(",\"" D_JSON_AXIS_GY "\":%s"), axis_gy);
char json_axis_gz[25];
snprintf_P(json_axis_gz, sizeof(json_axis_gz), PSTR(",\"" D_JSON_AXIS_GZ "\":%s"), axis_gz);
#ifdef USE_MPU6050_DMP
char json_ypr_y[25];
snprintf_P(json_ypr_y, sizeof(json_ypr_y), PSTR(",\"" D_JSON_YAW "\":%s"), axis_yaw);
char json_ypr_p[25];
snprintf_P(json_ypr_p, sizeof(json_ypr_p), PSTR(",\"" D_JSON_PITCH "\":%s"), axis_pitch);
char json_ypr_r[25];
snprintf_P(json_ypr_r, sizeof(json_ypr_r), PSTR(",\"" D_JSON_ROLL "\":%s"), axis_roll);
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s%s%s%s}"),
D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz,
json_ypr_y, json_ypr_p, json_ypr_r);
#else
ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s}"),
D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz);
#endif // USE_MPU6050_DMP
#ifdef USE_DOMOTICZ
DomoticzSensor(DZ_TEMP, temperature);
#endif // USE_DOMOTICZ
@ -204,6 +239,9 @@ void MPU_6050Show(bool json)
} else {
WSContentSend_PD(HTTP_SNS_TEMP, D_SENSOR_MPU6050, temperature, TempUnit());
WSContentSend_PD(HTTP_SNS_AXIS, axis_ax, axis_ay, axis_az, axis_gx, axis_gy, axis_gz);
#ifdef USE_MPU6050_DMP
WSContentSend_PD(HTTP_SNS_YPR, axis_yaw, axis_pitch, axis_roll);
#endif // USE_MPU6050_DMP
#endif // USE_WEBSERVER
}
}

View File

@ -57,6 +57,8 @@
#define D_JSON_WEIGHT_MAX "WeightMax"
#define D_JSON_WEIGHT_ITEM "WeightItem"
#define D_JSON_WEIGHT_CHANGE "WeightChange"
#define D_JSON_WEIGHT_RAW "WeightRaw"
#define D_JSON_WEIGHT_DELTA "WeightDelta"
enum HxCalibrationSteps { HX_CAL_END, HX_CAL_LIMBO, HX_CAL_FINISH, HX_CAL_FAIL, HX_CAL_DONE, HX_CAL_FIRST, HX_CAL_RESET, HX_CAL_START };
@ -64,8 +66,10 @@ const char kHxCalibrationStates[] PROGMEM = D_HX_CAL_FAIL "|" D_HX_CAL_DONE "|"
struct HX {
long weight = 0;
long raw = 0;
long last_weight = 0;
long sum_weight = 0;
long sum_raw = 0;
long offset = 0;
long scale = 1;
long weight_diff = 0;
@ -78,6 +82,7 @@ struct HX {
uint8_t pin_dout;
bool tare_flg = false;
bool weight_changed = false;
uint16_t weight_delta = 4;
} Hx;
/*********************************************************************************************/
@ -146,6 +151,24 @@ void HxCalibrationStateTextJson(uint8_t msg_id)
if (msg_id < 3) { MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR("Sensor34")); }
}
void SetWeightDelta()
{
// backwards compatible: restore old default value of 4 grams
if (Settings.weight_change == 0) {
Hx.weight_delta = 4;
return;
}
// map upper values 101-255 to
if (Settings.weight_change > 100) {
Hx.weight_delta = (Settings.weight_change - 100) * 10 + 100;
return;
}
// map 1..100 to 0..99 grams
Hx.weight_delta = Settings.weight_change - 1;
}
/*********************************************************************************************\
* Supported commands for Sensor34:
*
@ -163,6 +186,7 @@ void HxCalibrationStateTextJson(uint8_t msg_id)
* Sensor34 7 - Save current weight to be used as start weight on restart
* Sensor34 8 0 - Disable JSON weight change message
* Sensor34 8 1 - Enable JSON weight change message
* Sensor34 9 <weight code> - Set minimum delta to trigger JSON message
\*********************************************************************************************/
bool HxCommand(void)
@ -225,6 +249,13 @@ bool HxCommand(void)
}
show_parms = true;
break;
case 9: // WeightDelta
if (strstr(XdrvMailbox.data, ",") != nullptr) {
Settings.weight_change = strtol(subStr(sub_string, XdrvMailbox.data, ",", 2), nullptr, 10);
SetWeightDelta();
}
show_parms = true;
break;
default:
show_parms = true;
}
@ -232,8 +263,10 @@ bool HxCommand(void)
if (show_parms) {
char item[33];
dtostrfd((float)Settings.weight_item / 10, 1, item);
Response_P(PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\"" D_JSON_WEIGHT_ITEM "\":%s,\"" D_JSON_WEIGHT_CHANGE "\":\"%s\"}}"),
Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000, item, GetStateText(Settings.SensorBits1.hx711_json_weight_change));
Response_P(PSTR("{\"Sensor34\":{\"" D_JSON_WEIGHT_REF "\":%d,\"" D_JSON_WEIGHT_CAL "\":%d,\"" D_JSON_WEIGHT_MAX "\":%d,\""
D_JSON_WEIGHT_ITEM "\":%s,\"" D_JSON_WEIGHT_CHANGE "\":%s,\"" D_JSON_WEIGHT_DELTA "\":%d}}"),
Settings.weight_reference, Settings.weight_calibration, Settings.weight_max * 1000,
item, GetStateText(Settings.SensorBits1.hx711_json_weight_change), Settings.weight_change);
}
return serviced;
@ -258,6 +291,8 @@ void HxInit(void)
digitalWrite(Hx.pin_sck, LOW);
SetWeightDelta();
if (HxIsReady(8 * HX_TIMEOUT)) { // Can take 600 milliseconds after power on
if (!Settings.weight_max) { Settings.weight_max = HX_MAX_WEIGHT / 1000; }
if (!Settings.weight_calibration) { Settings.weight_calibration = HX_SCALE; }
@ -272,13 +307,17 @@ void HxInit(void)
void HxEvery100mSecond(void)
{
Hx.sum_weight += HxRead();
long raw = HxRead();
Hx.sum_raw += raw;
Hx.sum_weight += raw;
Hx.sample_count++;
if (HX_SAMPLES == Hx.sample_count) {
long average = Hx.sum_weight / Hx.sample_count; // grams
long raw_average = Hx.sum_raw / Hx.sample_count; // grams
long value = average - Hx.offset; // grams
Hx.weight = value / Hx.scale; // grams
Hx.raw = raw_average / Hx.scale;
if (Hx.weight < 0) {
if (Settings.energy_frequency_calibration) {
long difference = Settings.energy_frequency_calibration + Hx.weight;
@ -351,7 +390,7 @@ void HxEvery100mSecond(void)
Hx.weight += Hx.last_weight; // grams
if (Settings.SensorBits1.hx711_json_weight_change) {
if (abs(Hx.weight - Hx.weight_diff) > 4) { // Use 4 gram threshold to decrease "ghost" weights
if (abs(Hx.weight - Hx.weight_diff) > Hx.weight_delta) { // Use weight_delta threshold to decrease "ghost" weights
Hx.weight_diff = Hx.weight;
Hx.weight_changed = true;
}
@ -367,6 +406,7 @@ void HxEvery100mSecond(void)
}
Hx.sum_weight = 0;
Hx.sum_raw = 0;
Hx.sample_count = 0;
}
}
@ -405,7 +445,7 @@ void HxShow(bool json)
dtostrfd(weight, Settings.flag2.weight_resolution, weight_chr);
if (json) {
ResponseAppend_P(PSTR(",\"HX711\":{\"" D_JSON_WEIGHT "\":%s%s}"), weight_chr, scount);
ResponseAppend_P(PSTR(",\"HX711\":{\"" D_JSON_WEIGHT "\":%s%s, \"" D_JSON_WEIGHT_RAW "\":%d}"), weight_chr, scount, Hx.raw);
#ifdef USE_WEBSERVER
} else {
WSContentSend_PD(HTTP_HX711_WEIGHT, weight_chr);
@ -565,4 +605,4 @@ bool Xsns34(uint8_t function)
return result;
}
#endif // USE_HX711
#endif // USE_HX711

View File

@ -499,7 +499,10 @@ const uint8_t meter[]=
#define USE_SML_MEDIAN_FILTER
// max number of vars , may be adjusted
#ifndef MAX_VARS
#define MAX_VARS 20
#endif
// max number of meters , may be adjusted
#define MAX_METERS 5
double meter_vars[MAX_VARS];
@ -1427,14 +1430,25 @@ void SML_Decode(uint8_t index) {
//ignore
mp+=2;
cp++;
} else if (!strncmp(mp,"uuuuuuuu",8)) {
} else if (!strncmp(mp,"UUuuUUuu",8)) {
uint32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0);
ebus_dval=val;
mbus_dval=val;
mp+=8;
cp+=4;
}
else if (*mp=='u' && *(mp+1)=='u' && *(mp+2)=='u' && *(mp+3)=='u'){
} else if (*mp=='U' && *(mp+1)=='U' && *(mp+2)=='u' && *(mp+3)=='u'){
uint16_t val = cp[1]|(cp[0]<<8);
mbus_dval=val;
ebus_dval=val;
mp+=4;
cp+=2;
} else if (!strncmp(mp,"SSssSSss",8)) {
int32_t val= (cp[0]<<24)|(cp[1]<<16)|(cp[2]<<8)|(cp[3]<<0);
ebus_dval=val;
mbus_dval=val;
mp+=8;
cp+=4;
} else if (*mp=='u' && *(mp+1)=='u' && *(mp+2)=='U' && *(mp+3)=='U'){
uint16_t val = cp[0]|(cp[1]<<8);
mbus_dval=val;
ebus_dval=val;
@ -1442,17 +1456,25 @@ void SML_Decode(uint8_t index) {
cp+=2;
} else if (*mp=='u' && *(mp+1)=='u') {
uint8_t val = *cp++;
mbus_dval=val;
ebus_dval=val;
mp+=2;
}
else if (*mp=='s' && *(mp+1)=='s' && *(mp+2)=='s' && *(mp+3)=='s') {
} else if (*mp=='s' && *(mp+1)=='s' && *(mp+2)=='S' && *(mp+3)=='S') {
int16_t val = *cp|(*(cp+1)<<8);
mbus_dval=val;
ebus_dval=val;
mp+=4;
cp+=2;
} else if (*mp=='S' && *(mp+1)=='S' && *(mp+2)=='s' && *(mp+3)=='s') {
int16_t val = cp[1]|(cp[0]<<8);
mbus_dval=val;
ebus_dval=val;
mp+=4;
cp+=2;
}
else if (*mp=='s' && *(mp+1)=='s') {
int8_t val = *cp++;
mbus_dval=val;
ebus_dval=val;
mp+=2;
}

View File

@ -1,407 +1,3 @@
# decode-config.py
_decode-config.py_ is able to backup and restore Tasmota configuration.
A tool to backup and restore the configuration of [Tasmota](http://tasmota.com/)-devices.
In comparison with the Tasmota build-in "Backup/Restore Configuration" function _decode-config.py_
* uses human readable and editable [JSON](http://www.json.org/)-format for backup/restore,
* can restore previously backup and changed [JSON](http://www.json.org/)-format files,
* is able to create Tasmota compatible command list with related config parameter
Comparing backup files created by *decode-config.py* and *.dmp files created by Tasmota "Backup/Restore Configuration":
| &nbsp; | decode-config.py<br />*.json file | Tasmota<br />*.dmp file |
|-------------------------|:-------------------------------:|:-----------------------------------:|
| Encrypted | No | Yes |
| Readable | Yes | No |
| Simply editable | Yes | No |
| Simply batch processing | Yes | No |
_decode-config.py_ is compatible with Tasmota version from v5.10.0 up to now.
# Content
* [Prerequisite](decode-config.md#prerequisite)
* [File Types](decode-config.md#file-types)
* [.dmp File Format](decode-config.md#-dmp-format)
* [.json File Format](decode-config.md#-json-format)
* [.bin File Format](decode-config.md#-bin-format)
* [File extensions](decode-config.md#file-extensions)
* [Usage](decode-config.md#usage)
* [Basics](decode-config.md#basics)
* [Save backup file](decode-config.md#save-backup-file)
* [Restore backup file](decode-config.md#restore-backup-file)
* [Output to screen](decode-config.md#output-to-screen)
* [JSON output](decode-config.md#json-output)
* [Tasmota command output](decode-config.md#tasmota-command-output)
* [Filter data](decode-config.md#filter-data)
* [Configuration file](decode-config.md#configuration-file)
* [More program arguments](decode-config.md#more-program-arguments)
* [Examples](decode-config.md#examples)
* [Config file](decode-config.md#config-file)
* [Using Tasmota binary configuration files](decode-config.md#using-tasmota-binary-configuration-files)
* [Use batch processing](decode-config.md#use-batch-processing)
* [Notes](decode-config.md#notes)
## Prerequisite
* This program is written in [Python](https://en.wikipedia.org/wiki/Python_(programming_language)) so you need to install a working python environment for your operating system.
### Linux
```
sudo apt-get install python python-pip libcurl4-openssl-dev libssl-dev
```
```
pip install pycurl configargparse
```
### Windows 10
Install [Python 2.7](https://www.python.org/download/releases/2.7/) then install dependencies. For PyCurl you need to [download pycurl7.43.0.3cp27cp27mwin_amd64.whl](https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycurl) for Windows 10 64bit.
```
pip install pycurl-7.43.0.3-cp27-cp27m-win_amd64.whl
// run the command from the folder where you downloaded the file
pip install configargparse
```
* [Tasmota](https://github.com/arendst/Tasmota) [Firmware](https://github.com/arendst/Tasmota/releases) with Web-Server enabled:
* To backup or restore configurations from or to a Tasmota device you need a firmare with enabled web-server in admin mode (command [WebServer 2](https://tasmota.github.io/docs/#/Commands#wifi)). This is the Tasmota default.
* If using your own compiled firmware be aware to enable the web-server (`#define USE_WEBSERVER` and `#define WEB_SERVER 2`).
## File Types
_decode-config.py_ can handle the following backup file types:
### .dmp Format
Configuration data as used by Tasmota "Backup/Restore Configuration" web interface.
This format is binary and encrypted.
### .json Format
Configuration data in [JSON](http://www.json.org/)-format.
This format is decrypted, human readable and editable and can also be used for the `--restore-file` parameter.
This file will be created by _decode-config.py_ using the `--backup-file` with `--backup-type json` parameter, this is the default.
### .bin Format
Configuration data in binary format.
This format is binary decryptet, editable (e.g. using a hex editor) and can also be used for `--restore-file` command.
It will be created by _decode-config.py_ using `--backup-file` with `--backup-type bin`.
Note:
The .bin file contains the same information as the original .dmp file from Tasmota "Backup/Restore Configuration" but it is decrpted and 4 byte longer than an original (it is a prefix header at the beginning). .bin file data starting at address 4 contains the same as the **struct SYSCFG** from Tasmota [settings.h](https://github.com/arendst/Tasmota/blob/master/tasmota/settings.h) in decrypted format.
#### File extensions
You don't need to append exensions for your file name as _decode-config.py_ uses auto extension as default. The extension will be choose based on file contents and `--backup-type` parameter.
If you do not want using auto extensions use the `--no-extension` parameter.
## Usage
After download don't forget to set the executable flag under linux with `chmod +x decode-config.py` or call the program using `python decode-config.py...`.
### Basics
At least pass a source where you want to read the configuration data from using `-f <filename>` or `-d <host>`:
The source can be either
* a Tasmota device hostname or IP using the `-d <host>` parameter
* a Tasmota `*.dmp` configuration file using `-f <filename>` parameter
Example:
decode-config.py -d tasmota-4281
will output a human readable configuration in [JSON](http://www.json.org/)-format:
{
"altitude": 112,
"baudrate": 115200,
"blinkcount": 10,
"blinktime": 10,
...
"ws_width": [
1,
3,
5
]
}
### Save backup file
To save the output as backup file use `--backup-file <filename>`, you can use placeholder for Version, Friendlyname and Hostname:
decode-config.py -d tasmota-4281 --backup-file Config_@f_@v
If you have setup a WebPassword within Tasmota, use
decode-config.py -d tasmota-4281 -p <yourpassword> --backup-file Config_@f_@v
will create a file like `Config_Tasmota_6.4.0.json` (the part `Tasmota` and `6.4.0` will choosen related to your device configuration). Because the default backup file format is JSON, you can read and change it with any raw text editor.
### Restore backup file
Reading back a saved (and possible changed) backup file use the `--restore-file <filename>` parameter. This will read the (changed) configuration data from this file and send it back to the source device or filename.
To restore the previously save backup file `Config_Tasmota_6.2.1.json` to device `tasmota-4281` use:
decode-config.py -d tasmota-4281 --restore-file Config_Tasmota_6.2.1.json
with password set by WebPassword:
decode-config.py -d tasmota-4281 -p <yourpassword> --restore-file Config_Tasmota_6.2.1.json
### Output to screen
To force screen output use the `--output` parameter.
Output to screen is default enabled when calling the program with a source parameter (-f or -d) but without any backup or restore parameter.
#### JSON output
The default output format is [JSON](decode-config.md#-json-format). You can force JSON output using the `--output-format json` parameter.
Example:
decode-config.py -d tasmota-4281 -c my.conf -x Wifi --output-format json
{
...
"hostname": "%s-%04d",
"ip_address": [
"0.0.0.0",
"192.168.12.1",
"255.255.255.0",
"192.168.12.1"
],
"ntp_server": [
"ntp.localnet.home",
"ntp2.localnet.home",
"192.168.12.1"
],
"sta_active": 0,
"sta_config": 5,
"sta_pwd": [
"myWlAnPaszxwo!z",
"myWlAnPaszxwo!z2"
],
"sta_ssid": [
"wlan.1",
"my-wlan"
],
"web_password": "myPaszxwo!z",
"webserver": 2
...
}
Note: JSON output always contains all configuration data like the backup file except you are using `--group` arg.
#### Tasmota command output
_decode-config.py_ is able to translate the configuration data to (most all) Tasmota commands. To output your configuration as Tasmota commands use `--output-format cmnd` or `--output-format command`.
Example:
decode-config.py -d tasmota-4281 -c my.conf -g Wifi --output-format cmnd
# Wifi:
AP 0
Hostname %s-%04d
IPAddress1 0.0.0.0
IPAddress2 192.168.12.1
IPAddress3 255.255.255.0
IPAddress4 192.168.12.1
NtpServer1 ntp.localnet.home
NtpServer2 ntp2.localnet.home
NtpServer3 192.168.12.1
Password1 myWlAnPaszxwo!z
Password2 myWlAnPaszxwo!z2
SSId1 wlan.1
SSId2 wlan.1
WebPassword myPaszxwo!z
WebServer 2
WifiConfig 5
Note: A few very specific module commands like MPC230xx, KNX and some Display commands are not supported. These are still available by JSON output.
### Filter data
The huge number of Tasmota configuration data can be overstrained and confusing, so the most of the configuration data are grouped into categories.
With _decode-config.py_ the following categories are available: `Display`, `Domoticz`, `Internal`, `KNX`, `Led`, `Logging`, `MCP230xx`, `MQTT`, `Main`, `Management`, `Pow`, `Sensor`, `Serial`, `SetOption`, `RF`, `System`, `Timers`, `Wifi`
These are similary to the categories on [https://tasmota.github.io/docs/#/Commands](Tasmota Command Wiki).
To filter outputs to a subset of groups use the `-g` or `--group` arg concatenating the grooup you want, e. g.
decode-config.py -d tasmota-4281 -c my.conf --output-format cmnd --group Main MQTT Management Wifi
### Configuration file
Each argument that start with `--` (eg. `--file`) can also be set in a config file (specified via -c). Config file syntax allows: key=value, flag=true, stuff=[a,b,c] (for details, see syntax at [https://pypi.org/project/ConfigArgParse](https://pypi.org/project/ConfigArgParse/)).
If an argument is specified in more than one place, then commandline values override config file values which override defaults. This is usefull if you always use the same argument or a basic set of arguments.
The http authentication credentials `--username` and `--password` is predestinated to store it in a file instead using it on your command line as argument:
e.g. my.conf:
[source]
username = admin
password = myPaszxwo!z
To make a backup file from example above you can now pass the config file instead using the password on command line:
decode-config.py -d tasmota-4281 -c my.conf --backup-file Config_@f_@v
### More program arguments
For better reading each short written arg (minus sign `-`) has a corresponding long version (two minus signs `--`), eg. `--device` for `-d` or `--file` for `-f` (note: not even all `--` arg has a corresponding `-` one).
A short list of possible program args is displayed using `-h` or `--help`.
For advanced help use `-H` or `--full-help`:
usage: decode-config.py [-f <filename>] [-d <host>] [-P <port>]
[-u <username>] [-p <password>] [-i <filename>]
[-o <filename>] [-t json|bin|dmp] [-E] [-e] [-F]
[--json-indent <indent>] [--json-compact]
[--json-hide-pw] [--json-show-pw]
[--cmnd-indent <indent>] [--cmnd-groups]
[--cmnd-nogroups] [--cmnd-sort] [--cmnd-unsort]
[-c <filename>] [-S] [-T json|cmnd|command]
[-g {Control,Devices,Display,Domoticz,Internal,Knx,Light,Management,Mqtt,Power,Rf,Rules,Sensor,Serial,Setoption,Shutter,System,Timer,Wifi} [{Control,Devices,Display,Domoticz,Internal,Knx,Light,Management,Mqtt,Power,Rf,Rules,Sensor,Serial,Setoption,Shutter,System,Timer,Wifi} ...]]
[--ignore-warnings] [-h] [-H] [-v] [-V]
Backup/Restore Tasmota configuration data. Args that start with '--'
(eg. -f) can also be set in a config file (specified via -c). Config file
syntax allows: key=value, flag=true, stuff=[a,b,c] (for details, see syntax at
https://goo.gl/R74nmi). If an arg is specified in more than one place, then
commandline values override config file values which override defaults.
Source:
Read/Write Tasmota configuration from/to
-f, --file, --tasmota-file <filename>
file to retrieve/write Tasmota configuration from/to
(default: None)'
-d, --device, --host <host>
hostname or IP address to retrieve/send Tasmota
configuration from/to (default: None)
-P, --port <port> TCP/IP port number to use for the host connection
(default: 80)
-u, --username <username>
host HTTP access username (default: admin)
-p, --password <password>
host HTTP access password (default: None)
Backup/Restore:
Backup & restore specification
-i, --restore-file <filename>
file to restore configuration from (default: None).
Replacements: @v=firmware version from config,
@f=device friendly name from config, @h=device
hostname from config, @H=device hostname from device
(-d arg only)
-o, --backup-file <filename>
file to backup configuration to (default: None).
Replacements: @v=firmware version from config,
@f=device friendly name from config, @h=device
hostname from config, @H=device hostname from device
(-d arg only)
-t, --backup-type json|bin|dmp
backup filetype (default: 'json')
-E, --extension append filetype extension for -i and -o filename
(default)
-e, --no-extension do not append filetype extension, use -i and -o
filename as passed
-F, --force-restore force restore even configuration is identical
JSON output:
JSON format specification
--json-indent <indent>
pretty-printed JSON output using indent level
(default: 'None'). -1 disables indent.
--json-compact compact JSON output by eliminate whitespace
--json-hide-pw hide passwords
--json-show-pw, --json-unhide-pw
unhide passwords (default)
Tasmota command output:
Tasmota command output format specification
--cmnd-indent <indent>
Tasmota command grouping indent level (default: '2').
0 disables indent
--cmnd-groups group Tasmota commands (default)
--cmnd-nogroups leave Tasmota commands ungrouped
--cmnd-sort sort Tasmota commands (default)
--cmnd-unsort leave Tasmota commands unsorted
Common:
Optional arguments
-c, --config <filename>
program config file - can be used to set default
command args (default: None)
-S, --output display output regardsless of backup/restore usage
(default do not output on backup or restore usage)
-T, --output-format json|cmnd|command
display output format (default: 'json')
-g, --group {Control,Devices,Display,Domoticz,Internal,Knx,Light,Management,Mqtt,Power,Rf,Rules,Sensor,Serial,Setoption,Shutter,System,Timer,Wifi}
limit data processing to command groups (default no
filter)
--ignore-warnings do not exit on warnings. Not recommended, used by your
own responsibility!
Info:
Extra information
-h, --help show usage help message and exit
-H, --full-help show full help message and exit
-v, --verbose produce more output about what the program does
-V, --version show program's version number and exit
Either argument -d <host> or -f <filename> must be given.
### Program parameter notes
_decode-config.py_
### Examples
The most of the examples are for linux command line. Under Windows call the program using `python decode-config.py ...`.
#### Config file
Note: The example contains .ini style sections `[...]`. Sections are always treated as comment and serves as clarity only.
For further details of config file syntax see [https://pypi.org/project/ConfigArgParse](https://pypi.org/project/ConfigArgParse/).
*my.conf*
[Source]
username = admin
password = myPaszxwo!z
[JSON]
json-indent 2
#### Using Tasmota binary configuration files
1. Restore a Tasmota configuration file
`decode-config.py -c my.conf -d tasmota --restore-file Config_Tasmota_6.2.1.dmp`
2. Backup device using Tasmota configuration compatible format
a) use file extension to choice the file format
`decode-config.py -c my.conf -d tasmota --backup-file Config_@f_@v.dmp`
b) use args to choice the file format
`decode-config.py -c my.conf -d tasmota --backup-type dmp --backup-file Config_@f_@v`
#### Use batch processing
for device in tasmota1 tasmota2 tasmota3; do ./decode-config.py -c my.conf -d $device -o Config_@f_@v
or under windows
for device in (tasmota1 tasmota2 tasmota3) do python decode-config.py -c my.conf -d %device -o Config_@f_@v
will produce JSON configuration files for host tasmota1, tasmota2 and tasmota3 using friendly name and Tasmota firmware version for backup filenames.
## Notes
Some general notes:
* Filename replacement macros **@h** and **@H**:
* **@h**
The **@h** replacement macro uses the hostname configured with the Tasomta Wifi `Hostname <host>` command (defaults to `%s-%04d`). It will not use the network hostname of your device because this is not available when working with files only (e.g. `--file <filename>` as source).
To prevent having a useless % in your filename, **@h** will not replaced by configuration data hostname if this contains '%' characters.
* **@H**
If you want to use the network hostname within your filename, use the **@H** replacement macro instead - but be aware this will only replaced if you are using a network device as source (`-d`, `--device`, `--host`); it will not work when using a file as source (`-f`, `--file`)
## decode-config has moved to [https://github.com/tasmota/decode-config](https://github.com/tasmota/decode-config)

File diff suppressed because it is too large Load Diff