From c88080b2bdaa0f1f993cf9d1332ee90aaf46a595 Mon Sep 17 00:00:00 2001 From: Theo Arends Date: Tue, 8 Aug 2017 14:45:19 +0200 Subject: [PATCH] v5.5.1b 5.5.1b * Extent max number of WS2812 pixels from 256 to 512 (#667) * Add OTA handling if server responds with no update available (#695) * Removed undocumented command FlashMode (#696) * Fix compile time error message due to increased message buffer size (#703) --- README.md | 122 +- _releasenotes.ino | 900 ++++++++++++++ settings.ino | 722 ++++++++++++ sonoff.ino | 2871 +++++++++++++++++++++++++++++++++++++++++++++ webserver.ino | 1618 +++++++++++++++++++++++++ 5 files changed, 6172 insertions(+), 61 deletions(-) create mode 100644 _releasenotes.ino create mode 100644 settings.ino create mode 100644 sonoff.ino create mode 100644 webserver.ino diff --git a/README.md b/README.md index 77f5bcc13..ebc8cd216 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,61 @@ -## Sonoff-Tasmota -Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE. - -Current version is **5.5.1a** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. - -### ATTENTION All versions - -Only Flash Mode DOUT is supported. Do not use Flash Mode DIO / QIO / QOUT as it might seem to brick your device. - -See [Wiki](https://github.com/arendst/Sonoff-Tasmota/wiki/Theo's-Tasmota-Tips) for background information. - -### ATTENTION Version 5.x.x specific information - -This version uses a new linker script to free flash memory for future code additions. It moves the settings from Spiffs to Eeprom. If you compile your own firmware download the new linker to your IDE or Platformio base folder. See [Wiki > Prerequisite](https://github.com/arendst/Sonoff-Tasmota/wiki/Prerequisite). - -Best practice to implement is: -- Open the webpage to your device -- Perform option ``Backup Configuration`` -- Upgrade new firmware using ``Firmware upgrade`` -- If configuration conversion fails keep the webpage open and perform ``Restore Configuration`` - -You should now have a device with 32k more code memory to play with. - -### Version Information - -- This version provides all (Sonoff) modules in one file and starts up with Sonoff Basic. -- Once uploaded select module using the configuration webpage or the commands ```Modules``` and ```Module```. -- After reboot select config menu again or use commands ```GPIOs``` and ```GPIO``` to change GPIO with desired sensor. - - - -See [Wiki](https://github.com/arendst/Sonoff-Tasmota/wiki) for more information.
-See [Community](https://groups.google.com/d/forum/sonoffusers) for forum and more user experience. - -The following devices are supported: -- [iTead Sonoff Basic](http://sonoff.itead.cc/en/products/sonoff/sonoff-basic) -- [iTead Sonoff RF](http://sonoff.itead.cc/en/products/sonoff/sonoff-rf) -- [iTead Sonoff SV](https://www.itead.cc/sonoff-sv.html) -- [iTead Sonoff TH10/TH16 with temperature sensor](http://sonoff.itead.cc/en/products/sonoff/sonoff-th) -- [iTead Sonoff Dual](http://sonoff.itead.cc/en/products/sonoff/sonoff-dual) -- [iTead Sonoff Pow](http://sonoff.itead.cc/en/products/sonoff/sonoff-pow) -- [iTead Sonoff 4CH](http://sonoff.itead.cc/en/products/sonoff/sonoff-4ch) -- [iTead Sonoff 4CH Pro](http://sonoff.itead.cc/en/products/sonoff/sonoff-4ch-pro) -- [iTead S20 Smart Socket](http://sonoff.itead.cc/en/products/residential/s20-socket) -- [iTead Slampher](http://sonoff.itead.cc/en/products/residential/slampher-rf) -- [iTead Sonoff Touch](http://sonoff.itead.cc/en/products/residential/sonoff-touch) -- [iTead Sonoff SC](http://sonoff.itead.cc/en/products/residential/sonoff-sc) -- [iTead Sonoff Led](http://sonoff.itead.cc/en/products/appliances/sonoff-led) -- [iTead Sonoff BN-SZ01 Ceiling Led](http://sonoff.itead.cc/en/products/appliances/bn-sz01) -- [iTead Sonoff RF Bridge 433](http://sonoff.itead.cc/en/products/appliances/sonoff-rf-bridge-433) -- [iTead Sonoff Dev](https://www.itead.cc/sonoff-dev.html) -- [iTead 1 Channel Switch 5V / 12V](https://www.itead.cc/smart-home/inching-self-locking-wifi-wireless-switch.html) -- [iTead Motor Clockwise/Anticlockwise](https://www.itead.cc/smart-home/motor-reversing-wifi-wireless-switch.html) -- [Electrodragon IoT Relay Board](http://www.electrodragon.com/product/wifi-iot-relay-board-based-esp8266/) - -Planned support: -- [iTead Sonoff T1](http://sonoff.itead.cc/en/products/residential/sonoff-t1) -- [iTead Sonoff B1](http://sonoff.itead.cc/en/products/residential/sonoff-b1) - - - +## Sonoff-Tasmota +Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE. + +Current version is **5.5.1b** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/master/sonoff/_releasenotes.ino) for change information. + +### ATTENTION All versions + +Only Flash Mode DOUT is supported. Do not use Flash Mode DIO / QIO / QOUT as it might seem to brick your device. + +See [Wiki](https://github.com/arendst/Sonoff-Tasmota/wiki/Theo's-Tasmota-Tips) for background information. + +### ATTENTION Version 5.x.x specific information + +This version uses a new linker script to free flash memory for future code additions. It moves the settings from Spiffs to Eeprom. If you compile your own firmware download the new linker to your IDE or Platformio base folder. See [Wiki > Prerequisite](https://github.com/arendst/Sonoff-Tasmota/wiki/Prerequisite). + +Best practice to implement is: +- Open the webpage to your device +- Perform option ``Backup Configuration`` +- Upgrade new firmware using ``Firmware upgrade`` +- If configuration conversion fails keep the webpage open and perform ``Restore Configuration`` + +You should now have a device with 32k more code memory to play with. + +### Version Information + +- This version provides all (Sonoff) modules in one file and starts up with Sonoff Basic. +- Once uploaded select module using the configuration webpage or the commands ```Modules``` and ```Module```. +- After reboot select config menu again or use commands ```GPIOs``` and ```GPIO``` to change GPIO with desired sensor. + + + +See [Wiki](https://github.com/arendst/Sonoff-Tasmota/wiki) for more information.
+See [Community](https://groups.google.com/d/forum/sonoffusers) for forum and more user experience. + +The following devices are supported: +- [iTead Sonoff Basic](http://sonoff.itead.cc/en/products/sonoff/sonoff-basic) +- [iTead Sonoff RF](http://sonoff.itead.cc/en/products/sonoff/sonoff-rf) +- [iTead Sonoff SV](https://www.itead.cc/sonoff-sv.html) +- [iTead Sonoff TH10/TH16 with temperature sensor](http://sonoff.itead.cc/en/products/sonoff/sonoff-th) +- [iTead Sonoff Dual](http://sonoff.itead.cc/en/products/sonoff/sonoff-dual) +- [iTead Sonoff Pow](http://sonoff.itead.cc/en/products/sonoff/sonoff-pow) +- [iTead Sonoff 4CH](http://sonoff.itead.cc/en/products/sonoff/sonoff-4ch) +- [iTead Sonoff 4CH Pro](http://sonoff.itead.cc/en/products/sonoff/sonoff-4ch-pro) +- [iTead S20 Smart Socket](http://sonoff.itead.cc/en/products/residential/s20-socket) +- [iTead Slampher](http://sonoff.itead.cc/en/products/residential/slampher-rf) +- [iTead Sonoff Touch](http://sonoff.itead.cc/en/products/residential/sonoff-touch) +- [iTead Sonoff SC](http://sonoff.itead.cc/en/products/residential/sonoff-sc) +- [iTead Sonoff Led](http://sonoff.itead.cc/en/products/appliances/sonoff-led) +- [iTead Sonoff BN-SZ01 Ceiling Led](http://sonoff.itead.cc/en/products/appliances/bn-sz01) +- [iTead Sonoff RF Bridge 433](http://sonoff.itead.cc/en/products/appliances/sonoff-rf-bridge-433) +- [iTead Sonoff Dev](https://www.itead.cc/sonoff-dev.html) +- [iTead 1 Channel Switch 5V / 12V](https://www.itead.cc/smart-home/inching-self-locking-wifi-wireless-switch.html) +- [iTead Motor Clockwise/Anticlockwise](https://www.itead.cc/smart-home/motor-reversing-wifi-wireless-switch.html) +- [Electrodragon IoT Relay Board](http://www.electrodragon.com/product/wifi-iot-relay-board-based-esp8266/) + +Planned support: +- [iTead Sonoff T1](http://sonoff.itead.cc/en/products/residential/sonoff-t1) +- [iTead Sonoff B1](http://sonoff.itead.cc/en/products/residential/sonoff-b1) + + + diff --git a/_releasenotes.ino b/_releasenotes.ino new file mode 100644 index 000000000..db3d490a3 --- /dev/null +++ b/_releasenotes.ino @@ -0,0 +1,900 @@ +/* 5.5.1b + * Extent max number of WS2812 pixels from 256 to 512 (#667) + * Add OTA handling if server responds with no update available (#695) + * Removed undocumented command FlashMode (#696) + * Fix compile time error message due to increased message buffer size (#703) + * + * 5.5.1 20170805 + * Fix Sonoff Rf Bridge issues + * Add Sonoff RF Bridge MQTT messages on received and learned RF signal + * Add command VoltRes 0|1 to select voltage resolution to 0.1 V (#654) + * Add averaging to Analog input (#686) + * Add Energy tele data on Sonoff Pow Threshold change (#688) + * Fix inconsistent property names in Messages (#690) + * + * 5.5.0 20170730 + * Reduce code space by removing the following commands as they are replaced by SetOption alternatives: + * SaveState = SetOption0 + * ButtonRestrict = SetOption1 + * Units = SetOption2 + * MQTT = SetOption3 + * MQTTResponse = SetOption4 + * TempUnit = SetOption8 + * Smoothing WS2812 animation poll, invert fade speed and max allowed wakeup time down to 3000 seconds + * Fix initial button press detection + * Add support for Sonoff RF Bridge 433 using command RfKey + * Fix regression from 5.0.7 by increasing message buffer size from 360 to 368 to accomodate 4 x DS18x20 sensors (#637) + * Add GroupTopic to Topic test when using ButtonTopic/SwitchTopic to send either ON/OFF or TOGGLE (#642) + * Adjust HLW calibration limits to accomodate HuaFan device and add commands HlwPSet, HlwUSet and HlwISet (#654) + * + * 5.4.0 20170725 + * Fix command reset regression introduced in 5.2.0 + * Increase polling from 0.1 second to 0.05 second + * Add multipress to all buttons + * Fix button 1 double press behaviour on multi relay devices + * Add support for Hua Fan Smart Socket (#479) + * Add support for Sonoff 4ch Pro (#565) + * Add command SetOption13 1 to allow immediate action on single button press + * (disables multipress, hold and unrestricted commands) (#587) + * + * 5.3.0 20170715 + * Major Hue rewrite which might introduce Alexa problems. If so, initiate an issue + * Add support for Sonoff Led and BN-SZ01 Ceiling Led brightness control to Hue + * Fix Sonoff Led Power, Dimmer and Color MQTT response (#176) + * Add commands Delay and Backlog to allow multiple commands at once separated by ";" (#593) + * Use default flashmode DOUT to solve restart hangs on esp8285 chips (#453, #598) + * Change Web console column width from 99 to 300 (#599) + * + * 5.2.4 20170703 + * Removed flash mode update after selecting different module solving esp8285 related problems + * Add device type flag to sonoff_template.ino + * Change Sonoff Led Wakeup and add support for Sonoff BN-SZ01 Led (#567) + * + * 5.2.3 20170630 + * Change Sonoff Led color conversion code + * Fix SetOption12 handling + * Simplify auto configuration upgrade + * Add option Upgrade to only upgrade to any higher version (Old PR #213) + * Change FallbackTopic to cmnd// bypassing FullTopic and Prefix (#538) + * + * 5.2.2 20170625 + * Add configuration SaveAddress to Status 1 and Information Page + * Change Sonoff Led Color conversion from AtoH to strtol + * Fix possible wrong uploads due to configuration overwrites (#542) + * Fix payload negative numbers (#547) + * + * 5.2.1 20170622 + * Fix Restore Configuration in case of lower version + * Revert auto configuration upgrade allowing easy upgrade which was removed in version 5.2.0 + * Fix config auto upgrade from versions below version 4.1.1 (#530) + * + * 5.2.0 20170619 + * Add command SetOption12 1 to disable newly released configuration flash rotate to reduce flash wear + * Fix command CounterDebounce by removing test for active GPIO (#524) + * Add command SetOption33 1..250 to allow user configure POW Max_Power_Retry count (#525) + * + * 5.1.7 20170616 + * Prep removal of SetOptions alternatives + * Restore webpage upgrade error messages removed in 5.1.5 + * Add hold button functionality to buttons 2 to 4 + * Add command SetOption32 1..100 to set Key Hold Time from 0.1 seconds to 10 seconds (#200) + * Allow slashes in Topic, GroupTopic, ButtonTopic and SwitchTopic (#507) + * Changed webpage form actions from post to get and use relative path url (#434, #522) + * + * 5.1.6 20170606 + * Shrink code + * Removed online configuration of Domoticz In and Domoticz Out MQTT strings + * Removed commands DomoticzInTopic and DomoticzOutTopic + * Add define KEY_HOLD_TIME to configure button hold threshold before sending MQTT Hold message + * Add command StateText4 to configure button MQTT Hold text (= MQTT_CMND_HOLD) + * Add command SetOption11 0|1 to swap pushbutton single and double press functionality (#200) + * Add command SwitchMode 5 (PUSHBUTTONHOLD) and 6 (PUSHBUTTONHOLD_INV) (#489) + * + * 5.1.5 20170604 + * Shrink code in preparation to ESP8266-Arduino 2.4.0-rc1 + * Add effect parameter to HUE Device (#464) + * + * 5.1.4 20170601 + * Removed pre-compiled versions from repository as they are available within the release + * Changed HUE Device type to color supporting version (#464) + * Fix compile error when BE_MINIMAL is selected (#467, #476) + * Add multiple compiled versions to release using updated Travis script and platformio.ini (#467) + * + * 5.1.3 20170520 + * Add Domoticz Counter + * + * 5.1.2 20170519 + * Fix Counter/Timer JSON message and update Counter/Timer on webpage + * Fix WS2812 Domoticz related regression issues + * + * 5.1.1 20170517 + * Allow command FullTopic in group mode + * Prepare for more use of RTC memory + * Add independant WS2812 led string power control (#386, #390) + * Add command Counter to control up to four GPIO falling edge interrupt counters or timers (#459) + * Add command CounterType to select between pulse counting or pulse timing + * Add command CounterDebounce to select global counter debounce time in mSec + * + * 5.1.0 20170513 + * Fix Offline/Removal of retained topic when FullTopic is changed + * Add FullTopic to MQTT Configuration and Information web pages + * Add license model GPLv3 (#188) + * + * 5.0.7 20170511 + * Fix possible exception 28 on empty command + * Add command SetOption0 as replacement for SaveState + * Add command SetOption1 as replacement for ButtonRestrict + * Add command SetOption2 as replacement for Units + * Add command SetOption4 as replacement for MqttResponse + * Add command SetOption8 as replacement for TempUnit + * Add command SetOption10 On|Off to select between Offline or Removing previous retained topic (#417, #436) + * + * 5.0.6 20170510 + * Remove hyphen in case of a single DHT sensor connected (#427) + * Add command MqttRetry to change default MQTT reconnect retry timer from minimal 10 seconds (#429) + * + * 5.0.5 20170508 + * Add command FullTopic with tokens %topic% (replaced by command Topic value) and + * %prefix% (replaced by command Prefix values) for more flexible topic definitions (#244) + * See wiki > MQTT Features https://github.com/arendst/Sonoff-Tasmota/wiki/MQTT-Features for more information + * + * 5.0.4 20170505 + * Add Sonoff Pow Energy Total up to 40 MWh + * Add command EnergyReset 1|2|3 to reset Energy counters (#406) + * Fix Domoticz Energy logging (#411) + * Add command PowerOnState 4 to keep relay always on and disabling all power control (#418) + * + * 5.0.3 20170504 + * Add command SensorRetain on|off to enable retaining of mqtt message tele/sonoff/SENSOR (#74) + * Change WifiConfig timeout from 60 seconds to 180 seconds (#212) + * Change Sonoff Touch command Ledstate functionality by turning led on if power is off (#214) + * Add 4 seconds delay after power on before enabling button to workaround Wemos D1 mini RTS circuit (#380) + * + * 5.0.2 20170503 + * Reset SaveData, SaveState and MqttResponse to default values due to rearranging settings + * Moved some settings to flag area + * Add command TempUnit Celsius|Fahrenheit for selecting Celsius or Fahrenheit (#347) + * Add command TempRes 0..3 for selecting Temperature Resolution (#347) + * Add command HumRes 0..3 for selecting Humidity Resolution (#347) + * Add command PressRes 0..3 for selecting Pressure Resolution (#347) + * Add command EnergyRes 0..5 for selecting Energy Resolution (#347) + * Add "TempUnit":"C|F" to sensor JSON output (#347) + * Add support for up to three DHT type sensors each using a different GPIO (#339, #404) + * + * 5.0.1 20170429 + * Adjust Sonoff SC messages to prepare for display feature + * Move static data from RAM to Flash + * Fix PowerOnState for some devices not reporting "Power on" state (#284, #380, #383) + * + * 5.0.0 20170425 + * Memory status message update + * Fix setting migration to better preserve settings during move (#382) + * Best practice is first doing a Backup Configuration before installing version 5.0.0 + * Reset save count after setting move + * Start using new linker script without SPIFFS + * + * 4.2.0 20170424 + * Prepare for SPIFFS removal by moving settings to EEPROM area + * Fix compilation error when webserver is disabled (#378) + * + * 4.1.3 20170410 + * Add user configuarble GPIO to module S20 Socket and Slampher + * Add support for Sonoff SC (#112) + * Set PWM frequency from 1000Hz to 910Hz as used on iTead Sonoff Led firmware (#122) + * Set Sonoff Led unconfigured floating outputs to 0 to reduce exceptions due to power supply instabilities (#122) + * Add Access Point Mac Address to Status 11 and Telemetry (#329) + * Fix DS18B20 negative temperature readings (#334) + * + * 4.1.2 20170403 + * Rename Unrecognised command to Unknown command + * Remove all command lists + * Remove command SmartConfig (superseded by WifiConfig) + * Fix boot loop when selecting module Sonoff 4CH or Sonoff Touch on non ESP8285 hardware + * Add optional support for Toshiba and Mitsubishi HVAC IR control (needs updated IRremote8266 library) (#83, #257) + * Add all configured switches to Domoticz Configuration web page (#305) + * Fix compile error when selecting WS2812 DMA (#313) + * + * 4.1.1 20170329 + * Fix default Telemetry for command Prefix3 + * Fix webserver Module parameters for disabled select + * Fix sensor status for enabled switches + * Remove Light as alternative for Power (save code space) + * Remove migration option from pre V3 (code cleanup) + * Remove unofficial SPIFFS support (code cleanup) + * Remove command list when unknown command is entered (save code space) + * Rename Status11 json from StatusPWR to unique StatusSTS + * Rename command Gateway to IPAddres2, Subnetmask to IPAddress3 and DnsServer to IPAddress4 (save code space) + * Add Command MqttResponse to select either command or RESULT topic as response (#258) + * Add command StateText1 to StateText3 to assign MQTT_STATUS_OFF, MQTT_STATUS_ON and MQTT_CMND_TOGGLE respectively (#286) + * Remove restart after IPAddress changes (#292) + * Add support for MAX31850 in xsns_ds18x20.ino (#295) + * Fix possible uptime update misses (#302) + * + * 4.1.0 20170325 + * Change static IP addresses in user_config.h from list (using commas) to string (using dots) + * Unify display result of commands Modules, Module and Gpios + * Rewrite Module selection web page to bring size down from 18651 to 4319 bytes (!) (#234, #240) + * Add basic support for (Lixada) H801 RGBWW controller (#252) + * Add command Prefix1 to Prefix3 to assign SUB_PREFIX, PUB_PREFIX and PUB_PREFIX2 respectively (#255) + * Add static ip addresses to flash (#262) + * Add commands IpAddress, Gateway, Subnetmask and DnsServer to select static ip addresses (#273) + * + * 4.0.8 20170321 + * Fix entering non-numeric webpassword + * Force selection between TLS or Webserver due to memory restraint (#240) + * Allow entering empty string using "0" for selected commands (#242) + * Fix exception when posting commands to web console containing % (#250) + * + * 4.0.7 20170319 + * Increased Sonoff Led PWM frequency from 432 to 1000 + * Fix possible watch dog reboot after changing module type on web page + * Fix reporting of GPIO usage from web page + * Fix Sonoff Led blank during firmware upgrade + * Fix Sonoff Led flicker and possible flash corruption by using latest Arduino-esp8266 versions + * of pwm core files included in sonoff library (#211) + * Add PWM output control with commands PWM1 to PWM5 using user selectable GPIOs (#211) + * Fix exceptions due to low values of commands HlwPCal (10000), HlwUCal (1000) and HlwICal (2500) (#223) + * Add Switch state to sensor status (#227, #233) + * Add user configuarble GPIO to module Sonoff Touch (#228) + * Add define WEB_PORT to user_config.h to change default web server port from 80 (#232) + * Fix failed Ota Firmware upgrade started from Web page (#235) + * + * 4.0.6 20170316 + * Fix to better find device by Wifi hostname + * Fix compile error when some I2C devices are disabled + * Add (experimental) support for SHT1X emulating I2C (#97) + * Add ADC to ElectroDragon (#203) + * Add support for Sonoff Dev (#206) + * + * 4.0.5 20170314 + * Add command Status 11 to show power status with Vcc if define USE_ADC_VCC is enabled (default) + * Add ADC input to Sonoff SV and Wemos D1 mini - Needs recompile with define USE_ADC_VCC disabled (#137) + * Add MQTT host:port to timeout message (#199) + * + * 4.0.4 20170312 + * Add pulse timers for up to 4 relays (#106) + * Fix Sonoff Led power state when dimmer or color is 0 (#176) + * Add command NtpServer to configure up to three NTP servers (#177) + * Delete module User Test as module Wemos D1 mini has same/more user configurable GPIO (#178) + * Add more user configurable GPIO to module ElectroDragon (#183) + * + * 4.0.3 20170309 + * Renamed Module NodeMCU to WeMos D1 mini + * Add GPIO1 as user option to some modules + * Add Buttons, Relays and Leds to user configurable options (#159) + * Add description on Module parameters web page to some well known GPIOs (#107, #171) + * + * 4.0.2 20170308 + * Restore correct seriallog level after Serial logging was disabled + * Add simple dimmer slider to Sonoff Led web page + * Reduced root webpage size by 31% + * Expand Status 2 with Build date/time and core version + * Fix webserver redirection when not in WifiManager mode (#156) + * Add command ButtonRestrict On/Off to restrict access to button hold and button multi press options above 2 (#161) + * Fix DS18S20 negative temperature readings (#165) + * Fix crlf compilation error due to bad syntax (#144, #167) + * + * 4.0.1 20170305 + * Fix char default sizes and set MESSZ to 360 (#143) + * Fix SerialLog setting status + * Disable syslog when emulation is active + * Add DS18B20 web page display refresh + * + * 4.0.0 20170303 + * Add define to remove config migration code for versions below 3.0 (See Wiki-Upgrade-Migration path) + * Free memory by switching from String to char[] + * Raised Sonoff Led PWM frequency from 200Hz to 432Hz in search of stability (hardware watchdog timeouts) (#122) + * Increase message size and suggested minimum MQTT_MAX_PACKET_SIZE to 512 (#114, #124) + * Remove runtime warning message regarding MQTT_MAX_PACKET_SIZE too small as it is now moved to compile time (#124) + * Fix possible panics with web console and http commands while UDP syslog is active (#127) + * Add optional static IP address (#129) + * Add define ENERGY_RESOLUTION in user_config.h to allow user control over precision (#136) + * + * 3.9.22 20170228 + * Update web console + * Fix Status 4 JSON message + * Add Exception info during restart if available + * Add osWatch service to detect loop hangs that might happen during (OTA) upgrades + * Add WiOn support for relay and switch only (#82, #102) + * Allow for user specified relay count up to four in sonoff_template.h (#109) + * Add support for HTU21 compatible I2C sensors SI7013, SI7020 and SI7021 (#118) + * Add NodeMCU or Wemos configuration option (#119) + * + * 3.9.21 20170224 + * Add ajax to web root page and web console (#79) + * Add commands SwitchMode1..4 and enable user switches 2, 3 and 4 (#84, #88) + * Fix MQTT upgrade when webserver is active + * + * 3.9.20 20170221 + * Add minimal basic authentication to Web Admin mode (#87) + * Fix Hue and add HSB support (#89) + * + * 3.9.19 20170219 + * Sonoff Led: Made GPIO04, 05 and 15 available for user + * Sonoff Led: Add commands Fade, Speed, WakupDuration, Wakeup and LedTable + * + * 3.9.18 20170218 + * Fix ledstate 0 to turn off led + * Fix Sonoff Led dimmer range (#16) + * Change Sonoff Led command Dimmer to act on both cold and warm color + * Add Sonoff Led command Color CCWW where CCWW are hexadecimal values fro 00 - FF + * Reduce Sonoff Led flickering by disabling interrupts during flash save and disabling + * Led during OTA upgrade and Web upload (#16) + * + * 3.9.17 20170217 + * Fix possible ArduinoJSON related memory fragmentation + * Changed console logging using less memory + * Add GPIO04 as user selectable for Sonoff Dual (#75) + * + * 3.9.16 20170214 + * Update latching relay handler + * Add support for IR led using IRremoteESP8266 library (#59) + * Add Hue argument passing using ArduinoJSON library (#59) + * + * 3.9.15 20170213 + * Change JSON float values from string to number according to http://json.org (#56) + * Add support for exs latched relay module https://ex-store.de/ESP8266-WiFi-Relay-V31 (#58) + * Add support for inverted relays + * Changed MAX_LOG_LINES from 70 to 60 to preserve memory + * + * 3.9.14 20170211 + * Add False and True as alternatives for 0/Off and 1/On (#49) + * Fix Status10 JSON format (#52) + * Fix DS18x20 using OneWire library (#53) + * + * 3.9.13 20170210 + * Add FlashChipMode to Status 4 + * Removed redundant DHT2 option and code + * Add Sonoff SV GPIO pin 05 configuration (#40) + * Add configuration file backup and restore via web page + * Fix latency due to light_sleep mode even if sleep was set to zero (#50) + * + * 3.9.12 20170208 + * Fix compile error when webserver is disabled (#30) + * Fix possible ESP8285 flash problem by updating Flash Chip Mode to DOUT during OTA upload + * Fix hostname issues by not allowing user entry of string formatting and removing from user_config.h (#36) + * + * 3.9.11 20170204 + * Fix command I2Cscan + * Fix not allowed spaces in Topic, ButtonTopic and SwitchTopic + * Make all TELEMETRY, STATUS and COMMAND message topics unique (#4) + * Advertise command topic to be used by iobroker (#299) + * Fix butten (non)detection if no GPIO_KEY1 is defined (#13) + * Change WeMo serialnumber from 7 decimal chars to 8 hexadecimal chars (#18) + * Update web page with Build Date/Time, Emulation and mDNS Discovery and Advertise information (#21) + * + * 3.9.10 20170130 + * Add WS2812 Color Type selection (RGB or GRB) to user_config.h (#7) + * Hue api changes to support HUE App(s) (#8) + * + * 3.9.9 20170130 + * Add command status 10 showing sensor data + * Fix hlw status messages if hlw is disabled + * + * 3.9.8 20170130 + * Remove GPIO07 and GPIO08 from user selectable (#5) + * + * 3.9.7 20170129 + * Fix possible WS2812 exceptions when using emulation + * Add command Emulation to dynamic configure Belkin WeMo and Hue Bridge for Alexa + * + * 3.9.6 20170129 + * Add dynamic sleep for WS2812 animation (#1) + * + * 3.9.5 20170128 + * Fix error message in case of wrong Domoticz command + * + * 3.9.4 20170127 + * Fix Sonoff Dual Relay switching (#287) + * + * 3.9.3 20170127 + * Add confirmation before Restart via webpage + * Expand Domoticz Configuration webpage with Key, Switch and Sensor Index and + * add commands DomoticzSwitchIdx and DomoticzSensorIdx (#86) (#174) (#219) + * Fix default DHT11 sensor driver selection + * Fix LedPower status after button press (#279) + * Add command Sleep 0 - 250 mSec for optional light sleep mode to lower energy consumption (#272) + * (Expect overall button/key/switch misses and wrong values on Sonoff Pow) + * Add Hue brightness extension (#281) + * Fix Hue brightness and change to call by reference (#283) + * + * 3.9.2 20170124 + * Add confirmation before Reset Configuration via webpage (#244) + * Add WS2812 features (see Wiki commands) + * + * 3.9.1 20170124 + * Change PowerOnState function to only trigger when Power On (and not just restart) (#238) + * Move HLW interrupts back to RAM and make WS2812_DMA optional as it generates Exception on Pow (#264) + * Add charset=utf-8 to webpages (#266) + * Update Hue emulation (#268) + * Fix status module number + * Add support for domoticz Dimmer on Sonoff_Led and WS2812 + * Fix possible ESP8285 flash problem by updating Flash Chip Mode to DOUT during web upload + * + * 3.2.6a 20170120 + * Fix Sonoff Pow compile error (#255) + * Move HLW interrupts back to ROM (Needed for WS2812 DMA interrupts) + * Removed all IO config from user_config.h as this will be done by commands or webpage + * Removed MessageFormat and supports JSON only except POWER/LIGHT status + * Add command LedPower to control main led (#247) + * Add more FriendlyNames for Hue (#254) + * Add DMA support for WS2812 when using pin 3 while other pins work just as well in my case... + * Add HUE emulation for Alexa (#229) + * Add basic WS2812 support (#229) + * Fix Wemo when MQTT is disabled (#245) + * Revert ButtonTopic and change SwitchTopic1 - 4 to one SwitchTopic + * Rename MqttUnits to Units + * Add Mqtt command to enable/disable MQTT + * + * 3.2.2a 20170115 + * Add dynamic (Sonoff) Module, user GPIO and sensor selection (one size fits (almost) all) + * Add support for Sonoff LED + * Add Seriallog disable after 600 seconds for Sonoff Dual and 4 Channel + * Add ButtonTopic2 - 4, SwitchTopic1 - 4 and SwitchRetain + * + * 3.2.2 20170113 + * Fix PowerOnState 2 functionality after re-applying power (#230) + * + * 3.2.1 20170113 + * Fix some failed command decoding (#228) + * Removed passwords from status messages (#216) + * + * 3.2.0 20170111 + * Add I2C BH1750 sensor (#222) + * Sensor rewrite preparing for online selection + * + * 3.1.16 20170109 + * Fix Domoticz possible error condition + * Remove Wifi password from connection message (#216) + * Add Configure Other menu item to web page (#209) + * Add command FriendlyName, field Friendly Name and define FRIENDLY_NAME to be used by Alexa + * eliminating current use of MQTT_CLIENT_ID (#209) + * Add friendlyname to webpage replacing former hostname + * + * 3.1.15 20170108 + * Fix Domoticz send key regression with Toggle command + * + * 3.1.14 20170107 + * Add support for command TOGGLE (define MQTT_CMND_TOGGLE) when ButtonTopic is in use and not equal to Topic (#207) + * + * 3.1.13 20170107 + * Fix web console command input when SUB_PREFIX contains '/' (#152) + * Add command response to web command (#200) + * Add option to disable MQTT as define USE_MQTT in user_config.h (#200) + * + * 3.1.12 20170106 + * Add OTA retry to solve possible HTTP transient errors (#204) + * Fix MQTT host discovery + * + * 3.1.11 20170105 + * Add mDNS to advertise webserver as .local/ + * + * 3.1.10 20170105 + * Fix ButtonTopic when SUB_PREFIX = PUB_PREFIX + * Add workaround for possible MQTT queueing when SUB_PREFIX = PUB_PREFIX + * Add optional MQTT host discovery using define USE_DISCOVERY in user_config.h (#115) + * + * 3.1.9 20170104 + * Fix Power Blink start position (toggled) + * Change PulseTime increments: 1 .. 111 in 0.1 sec (max 11 seconds) and 112 .. 64900 in seconds (= 12 seconds until 18 hours) (#188) + * Add support for SUB_PREFIX = PUB_PREFIX (#190) + * + * 3.1.8 20170103 + * Add retain flag to LWT offline and only send "tele/sonoff/LWT Offline" (#179) + * Change retained LWT Online message to only send "tele/sonoff/LWT Online" + * + * 3.1.7 20161231 + * Add retained message LWT Online when sonoff makes MQTT connection (#179) + * + * 3.1.6 20161230 + * Add blinking using commands BlinkTime, BlinkCount and Power Blink|3|BlinkOff|4 (#165) + * + * 3.1.5 20161228 + * Fix serial space command exception (28) + * + * 3.1.4 20161227 + * Fix MQTT subscribe regression exception (3) (#162) + * Fix serial empty command exception (28) + * + * 3.1.3 20161225 + * Extent Domoticz configuration webpage with optional indices (#153) + * Fix multi relay legacy tele message from tele/sonoff/2/POWER to tele/sonoff/POWER2 + * Add support for iTead Motor Clockwise/Anticlockwise + * + * 3.1.2 20161224 + * Extent command PowerOnState with toggle at power on (option 2 is now option 3!) (#156) + * + * 3.1.1 20161223 + * Add support for Sonoff Touch and Sonoff 4CH (#40) + * Update DomoticzIdx and DomoticzKeyIdx with relay/key index (DomoticzIdx1/DomoticzKeyIdx1) + * Add command PowerOnState to control relay(s) at power on (#154) + * + * 3.1.0 20161221 + * Add Sonoff Pow measurement smoothing + * Fix serial command topic preamble error (#151) + * Fix 2.x to 3.x migration inconsistencies (#146) + * + * 3.0.9 20161218 + * Add Sonoff Pow voltage reading when relay is on but no load present (#123) + * + * 3.0.8 20161218 + * Add temperature conversion to Fahrenheit as option in user_config.h (TEMP_CONVERSION) (#145) + * + * 3.0.7 20161217 + * Add user_config_override.h to be used by user to override some defaults in user_config.h (#58) + * Fix Sonoff Pow low power (down to 4W) intermittent measurements (#123) + * + * 3.0.6 20161217 + * Fix MQTT_CLIENT_ID starting with % sign as in "%06X" (#142) + * Add auto power off after PulseTime * 0.1 Sec to relay 1 (#134) + * + * 3.0.5 20161215 + * Add more control over LED with command LedState options (#136, #143) + * LED_OFF (0), LED_POWER (1), LED_MQTTSUB (2), LED_POWER_MQTTSUB (3), LED_MQTTPUB (4), LED_POWER_MQTTPUB (5), LED_MQTT (6), LED_POWER_MQTT (7) + * Add option WIFI_RETRY (4) to command WifiConfig to allow connection retry to other AP without restart (#73) + * + * 3.0.4 20161211 + * Fix intermittent Domoticz update misses (#133) + * + * 3.0.3 20161210 + * Fix compiler warnings (#132) + * Remove redundant code + * Fix Domoticz pushbutton support + * + * 3.0.2 20161209 + * Add pushbutton to SwitchMode (#130) + * + * 3.0.1 20161209 + * Fix initial config + * + * 3.0.0 20161208 + * Migrate and clean-up flash layout + * Settings from version 2.x are saved but settings from version 3.x can not be used with version 2.x + * Change SEND_TELEMETRY_RSSI to SEND_TELEMETRY_WIFI and add AP and SSID to telemetry + * Split long JSON messages + * Fix inconsistent status messages + * Fix all status messages to return JSON if enabled + * Remove relay index in cmnd/sonoff//POWER now changed + * to cmnd/sonoff/POWER for single relay units + * and cmnd/sonoff/POWER for multi relay units like Sonoff dual + * Add retain option to Power/Light status controlled by command PowerRetain On|Off (#126) + * + * 2.1.2 20161204 + * Add support for second wifi AP (#73) + * Update command WifiConfig + * Fix possible WifiManager hang + * + * 2.1.1a 20161203 + * Fix scan for wifi networks if WeMo is enabled + * Fix syslog setting using web page + * + * 2.1.1 20161202 + * Add support for ElectroDragon second relay and button (only toggle with optional ButtonTopic) (#110) + * + * 2.1.0 20161202 + * Add optional EXPERIMENTAL TLS to MQTT (#49) + * Fix MQTT payload handling (#111) + * Optimzed WeMo code + * + * 2.0.21a 20161201 + * Fix WeMo PowerPlug emulation + * + * 2.0.21 20161130 + * Add Belkin WeMo PowerPlug emulation enabled with USE_WEMO_EMULATION in user_config.h (Heiko Krupp) (#105, #109) + * + * 2.0.20 20161130 + * Relax MQTTClient naming but only allows hexadecimal uppercase numbers (#107) + * Add I2C support with command I2CScan + * Add I2C sensor driver for HTU21 as alternate sensor using TH10/16 connectors (Heiko Krupp) (#105) + * Add I2C sensor driver for BMP085/BMP180/BMP280/BME280 as alternate sensor using TH10/16 connectors + * + * 2.0.19a 20161127 + * Add support for ButtonTopic and ButtonRetain to wall switch function + * Add pullup to SWITCH_PIN and command SwitchMode to syntax + * + * 2.0.18 20161126 + * Add SUB_PREFIX multi level support allowing 'cmnd' or 'cmnd/level2/level3' + * Add wall switch function to GPIO14 and command SwitchMode (Alex Scott) (#103) + * + * 2.0.17 20161123 + * Calibrate HLWPCAL from 12345 to 12530 + * Add alternative sensor driver DHT2 using Adafruit DHT library + * Add define MESSAGE_FORMAT to user_config.h + * Throttle console messages + * Shorten JSON messages + * Fix possible Panic + * Fix User mode webserver security + * + * 2.0.16 20161118 + * Add alternative sensor driver DS18x20 using OneWire library (#95) + * Change sensor MQTT message from tele/sonoff/TEMPERATURE to tele/sonoff/DHT/TEMPERATURE or + * tele/sonoff/DS18B20/TEMPERATURE or tele/sonoff/DS18x20/1/TEMPERATURE + * Add sensors to root webpage and auto refresh every 4 seconds (#92) + * Add optional JSON messageformat to all telemetry data + * Enforce minimum TelePeriod to be 10 seconds + * Fix Energy Yesterday reset after restart + * Add Energy Today restore after controlled restart + * + * 2.0.15 20161116 + * Change TODAY_POWER and PERIOD_POWER to TODAY_ENERGY and PERIOD_ENERGY + * Fix serial regression + * Fix syslog hangs when loghost is unavailable + * + * 2.0.14 20161115 + * Add HLW threshold delay + * Fix HLW intermittent current deviation + * Fix button functionality during wificonfig + * Add CRC check to DS18B20 sensor (#88) + * + * 2.0.13 20161113 + * Add additional upload error code descriptions + * Add PlatformIO support (#80) + * + * 2.0.12 20161113 + * Fix Serial and Web response regression when no MQTT connection available + * Fix Sonoff Dual power telemetric data for second relay + * Removed MQTT password from Information web page + * Hide MQTT password from Configure MQTT web page + * + * 2.0.11 20161111 + * Rewrite button and web toggle code + * Fix NTP sync + * Add HLW calibration commands HLWPCAL, HLWUCAL and HLWICAL (need define USE_POWERCALIBRATION) + * Fix power threshold tests + * + * 2.0.10 20161109 + * Add additional Domoticz define (#63) + * Add defines MQTT_STATUS_ON and MQTT_STATUS_OFF in user_config.h to select status On/Off string + * Fix status response differences (#65) + * Fix divide by zero exception (#70) + * Fix syslog loop exception + * + * 2.0.9 20161108 + * clarify MODULE in user_config.h + * Fix hlw false values + * + * 2.0.8 20161108 + * Add initial status after power on + * Seperate driver files + * Fix hlw code and calibrate Pow + * Move user config defines to user_config.h (#61) + * + * 2.0.7 20161030 + * Make Ticker mandatory + * Add Domoticz support (Increase MQTT_MAX_PACKET_SIZE to 400) (#54) + * Add command MessageFormat 0|1 to select either legacy or JSON output + * + * 2.0.6 20161024 + * Add Sonoff Pow power factor + * Initial support for up to four relays using iTEAD PSB (4Channel) + * - Currently only supports one button (All buttons behave the same) + * - Use command MODEL 4 to select four relay option + * (After first power on it will support 2 relays like Sonoff Dual) + * Fix ledstate + * Add command Status 9 to display Sonoff Pow thresholds + * Add commands PowerLow, PowerHigh, VoltageLow, VoltageHigh, CurrentLow and CurrentHigh for use + * with Sonoff Pow thresholds + * + * 2.0.5 20161018 + * Add updates to user_config.h - moved SEND_TELEMETRY_DS18B20 and SEND_TELEMETRY_DHT to module area. + * As Sonoff TH10/16 does not have the logic installed for GPIO04 You'll have to select ONE of both + * Add Sonoff Pow support (experimental until Pow tested) + * Add command Status 8 to display Sonoff Pow energy values + * Add command MqttUnits On|Off to add units to values + * Change web main page header character size + * Change On/Off to ON/OFF status messages to satisfy openHAB + * Change TEMP to TEMPERATURE and HUM to HUMIDITY + * + * 2.0.4 20161009 + * Add MQTT_BUTTON_RETAIN, SAVE_DATA and SAVE_STATE defines to user_config.h (#35) + * Update ButtonRetain to remove retained message(s) from broker when turned off + * Add Retain for second relay on Sonoff Dual + * Provide power status messages with device topic index if requested + * + * 2.0.3 20161008 + * Update wifi initialization + * Add command BUTTONRETAIN for optional MQTT retain on button press (#35) + * Add command SAVESTATE to disable power state save. May be used with MQTT retain + * + * 2.0.2 20161006 + * Fix wifi issue 2186 + * + * 2.0.1 20161002 + * Fix button press + * + * 2.0.0 20161002 + * Update Sonoff TH10/16 sensor pins (My TH10 only has GPIO14 connected) + * Add full support for Sonoff dual + * + * 1.0.35 20160929 + * Add more lines to console + * Add timeout and disable MQTT on web upload + * Add command SAVEDATA to control parameter save (for flash wear afficionados) (#30) + * + * 1.0.34 20160926 + * Fix button press six and seven + * Add more information to webserver + * + * 1.0.33 20160915 + * Better WPS error message + * Separate webserver code from support.ino into webserver.ino + * Fix webserver User by removing unwanted restart option + * + * 1.0.32 20160913 + * Add Wifi Protected Setup (WPS) as third option for initial config + * Add command WIFICONFIG replacing deprecated command SMARTCONFIG + * Add option WIFICONFIG 3 to start WPSconfig + * Add option WIFICONFIG 0 to start saved Wifi config tool (WPSconfig, Smartconfig or Wifimanager) + * Change button behaviour - See Wiki + * + * 1.0.31 20160907 + * Fix DS18B20 misread if teleperiod = 2 + * Tuned sensor code + * Updated prefered ElectroDragon connection to Relay 1 and Button 1 + * Moved SONOFF and ELECTRO_DRAGON port config to user_config.h + * + * 1.0.30 20160902 + * Fix command TELEPERIOD 0 + * Add ESP- tag to UDP log message for easy rsyslogd filtering + * Add ElectroDragon (Relay 2 only) functionality. Select with #define MODULE ELECTRO_DRAGON + * Add ? as null message alternative + * Add DHT temperature and humidity telemetry support. Enable with #define SEND_TELEMETRY_DHT + * Add DS18B20 temperature telemetry support. Enable with #define SEND_TELEMETRY_DS18B20 + * Restrict HOSTNAME, MQTTCLIENT, TOPIC and BUTTONTOPIC in topic mode only + * + * 1.0.29 20160831 + * Allow UPGRADE, OTAURL, RESTART, RESET, MQTTHOST, MQTTPORT, MQTTUSER, MQTTPASSWORD and WEBSERVER also in group mode + * + * 1.0.28 20160831 + * Add webserver state to status 5 + * Add optional PUB_PREFIX2 (tele) for telemetry usage + * Add command TELEPERIOD + * Fix syntax message + * Change memory status display + * + * 1.0.27 20160831 + * Add sketch flash size + * Add console to webserver + * Add command weblog + * Change WifiManager web pages to minimal + * Change display default hostname and MQTT client id in webserver + * Change HTTP command interface to http://sonoff-1234/cm?cmnd=light 2 + * Change HEARTBEAT to UPTIME + * + * 1.0.26 20160829 + * Add define USE_WEBSERVER to disable web server code in source + * Add file upload as alternative for ota upload to webserver + * Add information to webserver + * Add command hostname + * Add command logport + * Change HTTP command interface to http://sonoff-1234/cmd?cmnd=light 2 + * Change button behaviour with regards to Smartconfig and OTA upload. See README.md + * Enforce default hostname to either "%s-%04d" or user defined without any % + * Enforce default mqtt client id to either "DVES_%06X" or user defined without any % + * + * 1.0.25 20160822 + * Remove config system halts to keep ota available + * + * 1.0.24 20160821 + * Add test for MQTT_SUBTOPIC + * Change log range to LOG_LEVEL_ALL + * Change MQTT introduction messages + * Moved MQTT_MAX_PACKET_SIZE warning message to introduction messages + * + * 1.0.23 20160821 + * Add option USE_SPIFFS to move config from flash to spiffs + * Add webserver with options 0 (off), 1 (user) and 2 (admin) + * Add HTTP command interface (http://sonoff-1234/c?cmnd=light 2) + * Add wifimanager countdown counter + * Add command line webpage + * Add relay control to wifimanager + * Add restart option 99 to force restart + * Fix wifi hostname + * Fix NETBIOS hostname problem by reducing default hostname length + * Fix possible exception if WIFI_HOSTNAME is changed + * Fix upgrade messages + * Reduce memory use by redesigning config routines + * Split syntax message + * Rename define SERIAL_IO to USE_SERIAL + * + * 1.0.22 20160814 + * Add all MQTT parameters for configuration + * Add wifimanager to configure Wifi and MQTT via web server + * Change NTP time handling + * Fix Smartconfig parameter buffer overflow + * Fix PlatformIO warnings + * + * 1.0.21 20160808 + * Remove semaphore as subscription flooding (more than 15 subscriptions per second) is managed by SDK (LmacRxBlk:1) + * Add optional RTC interrupt (define USE_TICKER) to keep RTC synced during subscription flooding + * Remove heartbeatflag + * + * 1.0.20 20160805 + * Add semaphore to handle out of memory when too many subscriptions requested + * Use Daylight Saving (DST) parameters from user_config.h when timezone = 99 + * Add status 7 option displaying RTC information + * Add ledstate to status 0 + * + * 1.0.19 20160803 + * Fix possible MQTT_CLIENT_ID induced Exception(28) + * + * 1.0.18 20160803 + * Moved Cfg_Default + * Fix negative data handling + * Remove MQTT information from status 1 and add labels to status 1 + * Add mac address to status 5 + * Add MQTT ClientId, UserId and Password to status 6 + * + * 1.0.17 20160731 + * Better variable range checking + * Change ambiguous connection messages + * Add timestamp to serial message + * + * 1.0.16 20160729 + * Moved wifi, rtc, syslog and config to support.ino + * Fixed button action when buttontopic is used. Introduced with 1.0.15 + * Better buffer overflow checks (strlcpy) + * + * 1.0.15 20160728 + * Removed pubsubclient config changes from sonoff.ino as it doesn't work + * reapply MQTT_MAX_PACKET_SIZE 256 and MQTT_KEEPALIVE 120 to PubSubClient.h + * Add status 0 option displaying all status messages + * Change MQTT_MAX_PACKET_SIZE from 1024 to 256 + * Add buffer overflow checks (snprintf and strncpy) + * Implemented common string sizes + * + * 1.0.14 20160722 + * Seperate user config from sonoff.ino to user_config.h (pucebaboon) + * Change defaults from sidnas2 to domus1 + * Add MQTT status message as status 6 (pucebaboon) + * Add status type to message (pucebaboon) + * Add pubsubclient config changes to sonoff.ino (pucebaboon) + * + * 1.0.13 20160702 + * Add Ledstate 1 option to show power state on led + * + * 1.0.12 20160529 + * Allow disable of button topic using "0" + * + * 1.0.11 20160524 + * Provide button response if MQTT connection lost + * + * 1.0.10 20160520 + * Add optional button topic to assist external MQTT clients + * Change version notation + * Reset default values + * + * 1.0.9 20160503 + * Add more blinks + * Add reset 2 option erasing flash + * Add status 5 option displaying network info + * Add syslog check for Wifi connection + * Resize mqtt_publish log array + * Change Wifi smartconfig active from 100 to 60 seconds + * Update Wifi initialization + * + * 1.0.8 20160430 + * Remove use of Wifi config data from SDK + * Add status 3 (syslog info) and status 4 (flash info) + * Add restart option to button (5 quick presses) + * + * 1.0.7 20160420 + * Add UDP syslog support + * Change HOST command to MQTTHOST command + * Add commands SYSLOG, SERIALLOG and LOGHOST + * Change hostname to lower case to distinguise between open-sdk version + * Add support for ESP-12F used in my modified wkaku power socket switch + * Fix timezone command + * Add RTC month names for future use + * Modify button code + * Remove initialization errors by better use of MQTT loop + * + * 1.0.6 20160406 + * Removed Wifi AP mode (#1) + * Add test for Arduino IDE version >= 1.6.8 + * Fix RTC time sync code + * + * 1.0.5 20160310 + * Initial public release + * Show debug info by selecting option from IDE Tools Debug port: Serial + */ diff --git a/settings.ino b/settings.ino new file mode 100644 index 000000000..f48f05fe9 --- /dev/null +++ b/settings.ino @@ -0,0 +1,722 @@ +/* + settings.ino - user settings for Sonoff-Tasmota + + Copyright (C) 2017 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 . +*/ + +const uint8_t sfb_codeDefault[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 }; + +/*********************************************************************************************\ + * RTC memory +\*********************************************************************************************/ + +#define RTC_MEM_VALID 0xA55A + +uint32_t _rtcHash = 0; + +uint32_t getRtcHash() +{ + uint32_t hash = 0; + uint8_t *bytes = (uint8_t*)&rtcMem; + + for (uint16_t i = 0; i < sizeof(RTCMEM); i++) { + hash += bytes[i]*(i+1); + } + return hash; +} + +void RTC_Save() +{ + if (getRtcHash() != _rtcHash) { + rtcMem.valid = RTC_MEM_VALID; + ESP.rtcUserMemoryWrite(100, (uint32_t*)&rtcMem, sizeof(RTCMEM)); + _rtcHash = getRtcHash(); +#ifdef DEBUG_THEO + addLog_P(LOG_LEVEL_DEBUG, PSTR("Dump: Save")); + RTC_Dump(); +#endif // DEBUG_THEO + } +} + +void RTC_Load() +{ + ESP.rtcUserMemoryRead(100, (uint32_t*)&rtcMem, sizeof(RTCMEM)); +#ifdef DEBUG_THEO + addLog_P(LOG_LEVEL_DEBUG, PSTR("Dump: Load")); + RTC_Dump(); +#endif // DEBUG_THEO + if (rtcMem.valid != RTC_MEM_VALID) { + memset(&rtcMem, 0x00, sizeof(RTCMEM)); + rtcMem.valid = RTC_MEM_VALID; + rtcMem.power = sysCfg.power; + rtcMem.hlw_kWhtoday = sysCfg.hlw_kWhtoday; + rtcMem.hlw_kWhtotal = sysCfg.hlw_kWhtotal; + for (byte i = 0; i < 4; i++) { + rtcMem.pCounter[i] = sysCfg.pCounter[i]; + } + RTC_Save(); + } + _rtcHash = getRtcHash(); +} + +boolean RTC_Valid() +{ + return (RTC_MEM_VALID == rtcMem.valid); +} + +#ifdef DEBUG_THEO +void RTC_Dump() +{ + #define CFG_COLS 16 + + char log[LOGSZ]; + uint16_t idx; + uint16_t maxrow; + uint16_t row; + uint16_t col; + + uint8_t *buffer = (uint8_t *) &rtcMem; + maxrow = ((sizeof(RTCMEM)+CFG_COLS)/CFG_COLS); + + for (row = 0; row < maxrow; row++) { + idx = row * CFG_COLS; + snprintf_P(log, sizeof(log), PSTR("%04X:"), idx); + for (col = 0; col < CFG_COLS; col++) { + if (!(col%4)) { + snprintf_P(log, sizeof(log), PSTR("%s "), log); + } + snprintf_P(log, sizeof(log), PSTR("%s %02X"), log, buffer[idx + col]); + } + snprintf_P(log, sizeof(log), PSTR("%s |"), log); + for (col = 0; col < CFG_COLS; col++) { +// if (!(col%4)) { +// snprintf_P(log, sizeof(log), PSTR("%s "), log); +// } + snprintf_P(log, sizeof(log), PSTR("%s%c"), log, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' '); + } + snprintf_P(log, sizeof(log), PSTR("%s|"), log); + addLog(LOG_LEVEL_INFO, log); + } +} +#endif // DEBUG_THEO + +/*********************************************************************************************\ + * Config - Flash +\*********************************************************************************************/ + +extern "C" { +#include "spi_flash.h" +} +#include "eboot_command.h" + +extern "C" uint32_t _SPIFFS_end; + +#define SPIFFS_END ((uint32_t)&_SPIFFS_end - 0x40200000) / SPI_FLASH_SEC_SIZE + +// Version 3.x config +#define CFG_LOCATION_3 SPIFFS_END - 4 + +// Version 4.2 config = eeprom area +#define CFG_LOCATION SPIFFS_END // No need for SPIFFS as it uses EEPROM area +// Version 5.2 allow for more flash space +#define CFG_ROTATES 8 // Number of flash sectors used (handles uploads) + +uint32_t _cfgHash = 0; +uint32_t _cfgLocation = CFG_LOCATION; + +/********************************************************************************************/ +/* + * Based on cores/esp8266/Updater.cpp + */ +void setFlashModeDout() +{ + uint8_t *_buffer; + uint32_t address; + + eboot_command ebcmd; + eboot_command_read(&ebcmd); + address = ebcmd.args[0]; + _buffer = new uint8_t[FLASH_SECTOR_SIZE]; + if (SPI_FLASH_RESULT_OK == spi_flash_read(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE)) { + if (_buffer[2] != 3) { // DOUT + _buffer[2] = 3; + noInterrupts(); + if (SPI_FLASH_RESULT_OK == spi_flash_erase_sector(address / FLASH_SECTOR_SIZE)) { + spi_flash_write(address, (uint32_t*)_buffer, FLASH_SECTOR_SIZE); + } + interrupts(); + } + } + delete[] _buffer; +} + +uint32_t getHash() +{ + uint32_t hash = 0; + uint8_t *bytes = (uint8_t*)&sysCfg; + + for (uint16_t i = 0; i < sizeof(SYSCFG); i++) { + hash += bytes[i]*(i+1); + } + return hash; +} + +/*********************************************************************************************\ + * Config Save - Save parameters to Flash ONLY if any parameter has changed +\*********************************************************************************************/ + +uint32_t CFG_Address() +{ + return _cfgLocation * SPI_FLASH_SEC_SIZE; +} + +void CFG_Save(byte rotate) +{ +/* Save configuration in eeprom or one of 7 slots below + * + * rotate 0 = Save in next flash slot + * rotate 1 = Save only in eeprom flash slot until SetOption12 0 or restart + * rotate 2 = Save in eeprom flash slot, erase next flash slots and continue depending on stop_flash_rotate + * stop_flash_rotate 0 = Allow flash slot rotation (SetOption12 0) + * stop_flash_rotate 1 = Allow only eeprom flash slot use (SetOption12 1) + */ + char log[LOGSZ]; + +#ifndef BE_MINIMAL + if ((getHash() != _cfgHash) || rotate) { + if (1 == rotate) { // Use eeprom flash slot only and disable flash rotate from now on (upgrade) + stop_flash_rotate = 1; + } + if (2 == rotate) { // Use eeprom flash slot and erase next flash slots if stop_flash_rotate is off (default) + _cfgLocation = CFG_LOCATION +1; + } + if (stop_flash_rotate) { + _cfgLocation = CFG_LOCATION; + } else { + _cfgLocation--; + if (_cfgLocation <= (CFG_LOCATION - CFG_ROTATES)) { + _cfgLocation = CFG_LOCATION; + } + } + sysCfg.saveFlag++; + noInterrupts(); + spi_flash_erase_sector(_cfgLocation); + spi_flash_write(_cfgLocation * SPI_FLASH_SEC_SIZE, (uint32*)&sysCfg, sizeof(SYSCFG)); + interrupts(); + if (!stop_flash_rotate && rotate) { + for (byte i = 1; i < CFG_ROTATES; i++) { + noInterrupts(); + spi_flash_erase_sector(_cfgLocation -i); // Delete previous configurations by resetting to 0xFF + interrupts(); + delay(1); + } + } + snprintf_P(log, sizeof(log), PSTR("Cnfg: Save (%d bytes) to flash at %X and count %d"), sizeof(SYSCFG), _cfgLocation, sysCfg.saveFlag); + addLog(LOG_LEVEL_DEBUG, log); + _cfgHash = getHash(); + } +#endif // BE_MINIMAL + RTC_Save(); +} + +void CFG_Load() +{ +/* Load configuration from eeprom or one of 7 slots below if first load does not stop_flash_rotate + */ + char log[LOGSZ]; + + struct SYSCFGH { + unsigned long cfg_holder; + unsigned long saveFlag; + } _sysCfgH; + + _cfgLocation = CFG_LOCATION +1; + for (byte i = 0; i < CFG_ROTATES; i++) { + _cfgLocation--; + noInterrupts(); + spi_flash_read(_cfgLocation * SPI_FLASH_SEC_SIZE, (uint32*)&sysCfg, sizeof(SYSCFG)); + spi_flash_read((_cfgLocation -1) * SPI_FLASH_SEC_SIZE, (uint32*)&_sysCfgH, sizeof(SYSCFGH)); + interrupts(); + +// snprintf_P(log, sizeof(log), PSTR("Cnfg: Check at %X with count %d and holder %X"), _cfgLocation -1, _sysCfgH.saveFlag, _sysCfgH.cfg_holder); +// addLog(LOG_LEVEL_DEBUG, log); + + if (((sysCfg.version > 0x05000200) && sysCfg.flag.stop_flash_rotate) || (sysCfg.cfg_holder != _sysCfgH.cfg_holder) || (sysCfg.saveFlag > _sysCfgH.saveFlag)) { + break; + } + delay(1); + } + snprintf_P(log, sizeof(log), PSTR("Cnfg: Load from flash at %X and count %d"), _cfgLocation, sysCfg.saveFlag); + addLog(LOG_LEVEL_DEBUG, log); + if (sysCfg.cfg_holder != CFG_HOLDER) { + // Auto upgrade + noInterrupts(); + spi_flash_read((CFG_LOCATION_3) * SPI_FLASH_SEC_SIZE, (uint32*)&sysCfg, sizeof(SYSCFG)); + spi_flash_read((CFG_LOCATION_3 + 1) * SPI_FLASH_SEC_SIZE, (uint32*)&_sysCfgH, sizeof(SYSCFGH)); + if (sysCfg.saveFlag < _sysCfgH.saveFlag) + spi_flash_read((CFG_LOCATION_3 + 1) * SPI_FLASH_SEC_SIZE, (uint32*)&sysCfg, sizeof(SYSCFG)); + interrupts(); + if ((sysCfg.cfg_holder != CFG_HOLDER) || (sysCfg.version >= 0x04020000)) { + CFG_Default(); + } + } + + _cfgHash = getHash(); + + RTC_Load(); +} + +void CFG_Erase() +{ + char log[LOGSZ]; + SpiFlashOpResult result; + + uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1; + uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE; + boolean _serialoutput = (LOG_LEVEL_DEBUG_MORE <= seriallog_level); + + snprintf_P(log, sizeof(log), PSTR("Cnfg: Erase %d flash sectors"), _sectorEnd - _sectorStart); + addLog(LOG_LEVEL_DEBUG, log); + + for (uint32_t _sector = _sectorStart; _sector < _sectorEnd; _sector++) { + noInterrupts(); + result = spi_flash_erase_sector(_sector); + interrupts(); + if (_serialoutput) { + Serial.print(F("Flash: Erased sector ")); + Serial.print(_sector); + if (SPI_FLASH_RESULT_OK == result) { + Serial.println(F(" OK")); + } else { + Serial.println(F(" Error")); + } + delay(10); + } + } +} + +void CFG_Dump(char* parms) +{ + #define CFG_COLS 16 + + char log[LOGSZ]; + uint16_t idx; + uint16_t maxrow; + uint16_t row; + uint16_t col; + char *p; + + uint8_t *buffer = (uint8_t *) &sysCfg; + maxrow = ((sizeof(SYSCFG)+CFG_COLS)/CFG_COLS); + + uint16_t srow = strtol(parms, &p, 16) / CFG_COLS; + uint16_t mrow = strtol(p, &p, 10); + +// snprintf_P(log, sizeof(log), PSTR("Cnfg: Parms %s, Start row %d, rows %d"), parms, srow, mrow); +// addLog(LOG_LEVEL_DEBUG, log); + + if (0 == mrow) { // Default only 8 lines + mrow = 8; + } + if (srow > maxrow) { + srow = maxrow - mrow; + } + if (mrow < (maxrow - srow)) { + maxrow = srow + mrow; + } + + for (row = srow; row < maxrow; row++) { + idx = row * CFG_COLS; + snprintf_P(log, sizeof(log), PSTR("%04X:"), idx); + for (col = 0; col < CFG_COLS; col++) { + if (!(col%4)) { + snprintf_P(log, sizeof(log), PSTR("%s "), log); + } + snprintf_P(log, sizeof(log), PSTR("%s %02X"), log, buffer[idx + col]); + } + snprintf_P(log, sizeof(log), PSTR("%s |"), log); + for (col = 0; col < CFG_COLS; col++) { +// if (!(col%4)) { +// snprintf_P(log, sizeof(log), PSTR("%s "), log); +// } + snprintf_P(log, sizeof(log), PSTR("%s%c"), log, ((buffer[idx + col] > 0x20) && (buffer[idx + col] < 0x7F)) ? (char)buffer[idx + col] : ' '); + } + snprintf_P(log, sizeof(log), PSTR("%s|"), log); + addLog(LOG_LEVEL_INFO, log); + delay(1); + } +} + +/********************************************************************************************/ + +void CFG_Default() +{ + addLog_P(LOG_LEVEL_NONE, PSTR("Cnfg: Use defaults")); + CFG_DefaultSet1(); + CFG_DefaultSet2(); + CFG_Save(2); +} + +void CFG_DefaultSet1() +{ + memset(&sysCfg, 0x00, sizeof(SYSCFG)); + + sysCfg.cfg_holder = CFG_HOLDER; +// sysCfg.saveFlag = 0; + sysCfg.version = VERSION; +// sysCfg.bootcount = 0; +} + +void CFG_DefaultSet2() +{ + memset((char*)&sysCfg +16, 0x00, sizeof(SYSCFG) -16); + + sysCfg.flag.savestate = SAVE_STATE; + sysCfg.savedata = SAVE_DATA; + sysCfg.timezone = APP_TIMEZONE; + strlcpy(sysCfg.otaUrl, OTA_URL, sizeof(sysCfg.otaUrl)); + + sysCfg.seriallog_level = SERIAL_LOG_LEVEL; +// sysCfg.sta_active = 0; + strlcpy(sysCfg.sta_ssid[0], STA_SSID1, sizeof(sysCfg.sta_ssid[0])); + strlcpy(sysCfg.sta_pwd[0], STA_PASS1, sizeof(sysCfg.sta_pwd[0])); + strlcpy(sysCfg.sta_ssid[1], STA_SSID2, sizeof(sysCfg.sta_ssid[1])); + strlcpy(sysCfg.sta_pwd[1], STA_PASS2, sizeof(sysCfg.sta_pwd[1])); + strlcpy(sysCfg.hostname, WIFI_HOSTNAME, sizeof(sysCfg.hostname)); + sysCfg.sta_config = WIFI_CONFIG_TOOL; + strlcpy(sysCfg.syslog_host, SYS_LOG_HOST, sizeof(sysCfg.syslog_host)); + sysCfg.syslog_port = SYS_LOG_PORT; + sysCfg.syslog_level = SYS_LOG_LEVEL; + sysCfg.webserver = WEB_SERVER; + sysCfg.weblog_level = WEB_LOG_LEVEL; + + strlcpy(sysCfg.mqtt_fingerprint, MQTT_FINGERPRINT, sizeof(sysCfg.mqtt_fingerprint)); + strlcpy(sysCfg.mqtt_host, MQTT_HOST, sizeof(sysCfg.mqtt_host)); + sysCfg.mqtt_port = MQTT_PORT; + strlcpy(sysCfg.mqtt_client, MQTT_CLIENT_ID, sizeof(sysCfg.mqtt_client)); + strlcpy(sysCfg.mqtt_user, MQTT_USER, sizeof(sysCfg.mqtt_user)); + strlcpy(sysCfg.mqtt_pwd, MQTT_PASS, sizeof(sysCfg.mqtt_pwd)); + strlcpy(sysCfg.mqtt_topic, MQTT_TOPIC, sizeof(sysCfg.mqtt_topic)); + strlcpy(sysCfg.button_topic, "0", sizeof(sysCfg.button_topic)); + strlcpy(sysCfg.mqtt_grptopic, MQTT_GRPTOPIC, sizeof(sysCfg.mqtt_grptopic)); + sysCfg.flag.mqtt_button_retain = MQTT_BUTTON_RETAIN; + sysCfg.flag.mqtt_power_retain = MQTT_POWER_RETAIN; +// sysCfg.flag.value_units = 0; +// sysCfg.flag.button_restrict = 0; + sysCfg.tele_period = TELE_PERIOD; + + sysCfg.power = APP_POWER; + sysCfg.poweronstate = APP_POWERON_STATE; + sysCfg.ledstate = APP_LEDSTATE; + sysCfg.blinktime = APP_BLINKTIME; + sysCfg.blinkcount = APP_BLINKCOUNT; + sysCfg.sleep = APP_SLEEP; + + sysCfg.domoticz_update_timer = DOMOTICZ_UPDATE_TIMER; + for (byte i = 0; i < 4; i++) { + sysCfg.switchmode[i] = SWITCH_MODE; +// sysCfg.domoticz_relay_idx[i] = 0; +// sysCfg.domoticz_key_idx[i] = 0; +// sysCfg.domoticz_switch_idx[i] = 0; + } + + sysCfg.hlw_pcal = HLW_PREF_PULSE; + sysCfg.hlw_ucal = HLW_UREF_PULSE; + sysCfg.hlw_ical = HLW_IREF_PULSE; +// sysCfg.hlw_kWhtoday = 0; +// sysCfg.hlw_kWhyesterday = 0; +// sysCfg.hlw_kWhdoy = 0; +// sysCfg.hlw_pmin = 0; +// sysCfg.hlw_pmax = 0; +// sysCfg.hlw_umin = 0; +// sysCfg.hlw_umax = 0; +// sysCfg.hlw_imin = 0; +// sysCfg.hlw_imax = 0; +// sysCfg.hlw_mpl = 0; // MaxPowerLimit + sysCfg.hlw_mplh = MAX_POWER_HOLD; + sysCfg.hlw_mplw = MAX_POWER_WINDOW; +// sysCfg.hlw_mspl = 0; // MaxSafePowerLimit + sysCfg.hlw_msplh = SAFE_POWER_HOLD; + sysCfg.hlw_msplw = SAFE_POWER_WINDOW; +// sysCfg.hlw_mkwh = 0; // MaxEnergy +// sysCfg.hlw_mkwhs = 0; // MaxEnergyStart + + CFG_DefaultSet_3_2_4(); + + strlcpy(sysCfg.friendlyname[0], FRIENDLY_NAME, sizeof(sysCfg.friendlyname[0])); + strlcpy(sysCfg.friendlyname[1], FRIENDLY_NAME"2", sizeof(sysCfg.friendlyname[1])); + strlcpy(sysCfg.friendlyname[2], FRIENDLY_NAME"3", sizeof(sysCfg.friendlyname[2])); + strlcpy(sysCfg.friendlyname[3], FRIENDLY_NAME"4", sizeof(sysCfg.friendlyname[3])); + + CFG_DefaultSet_3_9_3(); + + strlcpy(sysCfg.switch_topic, "0", sizeof(sysCfg.switch_topic)); + sysCfg.flag.mqtt_switch_retain = MQTT_SWITCH_RETAIN; + sysCfg.flag.mqtt_enabled = MQTT_USE; + + sysCfg.flag.emulation = EMULATION; + + strlcpy(sysCfg.web_password, WEB_PASSWORD, sizeof(sysCfg.web_password)); + + CFG_DefaultSet_4_0_4(); + sysCfg.pulsetime[0] = APP_PULSETIME; + + // 4.0.7 +// for (byte i = 0; i < 5; i++) sysCfg.pwmvalue[i] = 0; + + // 4.0.9 + CFG_DefaultSet_4_0_9(); + + // 4.1.1 + 5.1.6 + CFG_DefaultSet_4_1_1(); + + // 5.0.2 + CFG_DefaultSet_5_0_2(); + + // 5.0.4 +// sysCfg.hlw_kWhtotal = 0; + rtcMem.hlw_kWhtotal = 0; + + // 5.0.5 + strlcpy(sysCfg.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(sysCfg.mqtt_fulltopic)); + + // 5.0.6 + sysCfg.mqtt_retry = MQTT_RETRY_SECS; + + // 5.1.7 + sysCfg.param[P_HOLD_TIME] = KEY_HOLD_TIME; // Default 4 seconds hold time + + // 5.2.0 + sysCfg.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY; + + // 5.4.1 + memcpy_P(sysCfg.sfb_code[0], sfb_codeDefault, 9); + +} + +/********************************************************************************************/ + +void CFG_DefaultSet_3_2_4() +{ + sysCfg.ws_pixels = WS2812_LEDS; + sysCfg.ws_red = 255; + sysCfg.ws_green = 0; + sysCfg.ws_blue = 0; + sysCfg.ws_ledtable = 0; + sysCfg.ws_dimmer = 8; + sysCfg.ws_fade = 0; + sysCfg.ws_speed = 1; + sysCfg.ws_scheme = 0; + sysCfg.ws_width = 1; + sysCfg.ws_wakeup = 0; +} + +void CFG_DefaultSet_3_9_3() +{ + for (byte i = 0; i < 4; i++) { + sysCfg.domoticz_switch_idx[i] = 0; + } + for (byte i = 0; i < 12; i++) { + sysCfg.domoticz_sensor_idx[i] = 0; + } + + sysCfg.module = MODULE; + for (byte i = 0; i < MAX_GPIO_PIN; i++){ + sysCfg.my_module.gp.io[i] = 0; + } + + sysCfg.led_pixels = 0; + for (byte i = 0; i < 5; i++) { + sysCfg.led_color[i] = 255; + } + sysCfg.led_table = 0; + for (byte i = 0; i < 3; i++){ + sysCfg.led_dimmer[i] = 10; + } + sysCfg.led_fade = 0; + sysCfg.led_speed = 0; + sysCfg.led_scheme = 0; + sysCfg.led_width = 0; + sysCfg.led_wakeup = 0; +} + +void CFG_DefaultSet_4_0_4() +{ + strlcpy(sysCfg.ntp_server[0], NTP_SERVER1, sizeof(sysCfg.ntp_server[0])); + strlcpy(sysCfg.ntp_server[1], NTP_SERVER2, sizeof(sysCfg.ntp_server[1])); + strlcpy(sysCfg.ntp_server[2], NTP_SERVER3, sizeof(sysCfg.ntp_server[2])); + for (byte j =0; j < 3; j++) { + for (byte i = 0; i < strlen(sysCfg.ntp_server[j]); i++) { + if (sysCfg.ntp_server[j][i] == ',') { + sysCfg.ntp_server[j][i] = '.'; + } + } + } + sysCfg.pulsetime[0] = APP_PULSETIME; + for (byte i = 1; i < MAX_PULSETIMERS; i++) { + sysCfg.pulsetime[i] = 0; + } +} + +void CFG_DefaultSet_4_0_9() +{ + strlcpy(sysCfg.mqtt_prefix[0], SUB_PREFIX, sizeof(sysCfg.mqtt_prefix[0])); + strlcpy(sysCfg.mqtt_prefix[1], PUB_PREFIX, sizeof(sysCfg.mqtt_prefix[1])); + strlcpy(sysCfg.mqtt_prefix[2], PUB_PREFIX2, sizeof(sysCfg.mqtt_prefix[2])); + parseIP(&sysCfg.ip_address[0], WIFI_IP_ADDRESS); + parseIP(&sysCfg.ip_address[1], WIFI_GATEWAY); + parseIP(&sysCfg.ip_address[2], WIFI_SUBNETMASK); + parseIP(&sysCfg.ip_address[3], WIFI_DNS); +} + +void CFG_DefaultSet_4_1_1() +{ + strlcpy(sysCfg.state_text[0], MQTT_STATUS_OFF, sizeof(sysCfg.state_text[0])); + strlcpy(sysCfg.state_text[1], MQTT_STATUS_ON, sizeof(sysCfg.state_text[1])); + strlcpy(sysCfg.state_text[2], MQTT_CMND_TOGGLE, sizeof(sysCfg.state_text[2])); + strlcpy(sysCfg.state_text[3], MQTT_CMND_HOLD, sizeof(sysCfg.state_text[3])); // v5.1.6 +} + +void CFG_DefaultSet_5_0_2() +{ + sysCfg.flag.temperature_conversion = TEMP_CONVERSION; + sysCfg.flag.temperature_resolution = TEMP_RESOLUTION; + sysCfg.flag.humidity_resolution = HUMIDITY_RESOLUTION; + sysCfg.flag.pressure_resolution = PRESSURE_RESOLUTION; + sysCfg.flag.energy_resolution = ENERGY_RESOLUTION; +} + +/********************************************************************************************/ + +void CFG_Delta() +{ + if (sysCfg.version != VERSION) { // Fix version dependent changes + if (sysCfg.version < 0x03010200) { // 3.1.2 - Add parameter + sysCfg.poweronstate = APP_POWERON_STATE; + } + if (sysCfg.version < 0x03010600) { // 3.1.6 - Add parameter + sysCfg.blinktime = APP_BLINKTIME; + sysCfg.blinkcount = APP_BLINKCOUNT; + } + if (sysCfg.version < 0x03020400) { // 3.2.4 - Add parameter + CFG_DefaultSet_3_2_4(); + } + if (sysCfg.version < 0x03020500) { // 3.2.5 - Add parameter + getClient(sysCfg.friendlyname[0], sysCfg.mqtt_client, sizeof(sysCfg.friendlyname[0])); + strlcpy(sysCfg.friendlyname[1], FRIENDLY_NAME"2", sizeof(sysCfg.friendlyname[1])); + strlcpy(sysCfg.friendlyname[2], FRIENDLY_NAME"3", sizeof(sysCfg.friendlyname[2])); + strlcpy(sysCfg.friendlyname[3], FRIENDLY_NAME"4", sizeof(sysCfg.friendlyname[3])); + } + if (sysCfg.version < 0x03020800) { // 3.2.8 - Add parameter + strlcpy(sysCfg.switch_topic, sysCfg.button_topic, sizeof(sysCfg.switch_topic)); + sysCfg.ex_mqtt_switch_retain = MQTT_SWITCH_RETAIN; + sysCfg.ex_mqtt_enabled = MQTT_USE; + } + if (sysCfg.version < 0x03020C00) { // 3.2.12 - Add parameter + sysCfg.sleep = APP_SLEEP; + } + if (sysCfg.version < 0x03090300) { // 3.9.2d - Add parameter + CFG_DefaultSet_3_9_3(); + } + if (sysCfg.version < 0x03090700) { // 3.9.7 - Add parameter + sysCfg.ex_emulation = EMULATION; + } + if (sysCfg.version < 0x03091400) { + strlcpy(sysCfg.web_password, WEB_PASSWORD, sizeof(sysCfg.web_password)); + } + if (sysCfg.version < 0x03091500) { + for (byte i = 0; i < 4; i++) sysCfg.switchmode[i] = SWITCH_MODE; + } + if (sysCfg.version < 0x04000200) { + sysCfg.ex_button_restrict = 0; + } + if (sysCfg.version < 0x04000400) { + CFG_DefaultSet_4_0_4(); + } + if (sysCfg.version < 0x04000500) { + memmove(sysCfg.my_module.gp.io, sysCfg.my_module.gp.io +1, MAX_GPIO_PIN -1); // move myio 1 byte to front + sysCfg.my_module.gp.io[MAX_GPIO_PIN -1] = 0; // Clear ADC0 + } + if (sysCfg.version < 0x04000700) { + for (byte i = 0; i < 5; i++) { + sysCfg.pwmvalue[i] = 0; + } + } + if (sysCfg.version < 0x04000804) { + CFG_DefaultSet_4_0_9(); + } + if (sysCfg.version < 0x04010100) { + CFG_DefaultSet_4_1_1(); + } + if (sysCfg.version < 0x05000105) { + sysCfg.flag = { 0 }; + sysCfg.flag.savestate = SAVE_STATE; + sysCfg.flag.button_restrict = sysCfg.ex_button_restrict; + sysCfg.flag.value_units = sysCfg.ex_value_units; + sysCfg.flag.mqtt_enabled = sysCfg.ex_mqtt_enabled; +// sysCfg.flag.mqtt_response = 0; + sysCfg.flag.mqtt_power_retain = sysCfg.ex_mqtt_power_retain; + sysCfg.flag.mqtt_button_retain = sysCfg.ex_mqtt_button_retain; + sysCfg.flag.mqtt_switch_retain = sysCfg.ex_mqtt_switch_retain; + sysCfg.flag.emulation = sysCfg.ex_emulation; + + CFG_DefaultSet_5_0_2(); + + sysCfg.savedata = SAVE_DATA; + } + if (sysCfg.version < 0x05000400) { + sysCfg.hlw_kWhtotal = 0; + rtcMem.hlw_kWhtotal = 0; + } + if (sysCfg.version < 0x05000500) { + strlcpy(sysCfg.mqtt_fulltopic, MQTT_FULLTOPIC, sizeof(sysCfg.mqtt_fulltopic)); + } + if (sysCfg.version < 0x05000600) { + sysCfg.mqtt_retry = MQTT_RETRY_SECS; + } + if (sysCfg.version < 0x05010100) { + sysCfg.pCounterType = 0; + sysCfg.pCounterDebounce = 0; + for (byte i = 0; i < MAX_COUNTERS; i++) { + sysCfg.pCounter[i] = 0; + rtcMem.pCounter[i] = 0; + } + } + if (sysCfg.version < 0x05010600) { + if (sysCfg.version > 0x04010100) { + memcpy(sysCfg.state_text, sysCfg.ex_state_text, 33); + } + strlcpy(sysCfg.state_text[3], MQTT_CMND_HOLD, sizeof(sysCfg.state_text[3])); + } + if (sysCfg.version < 0x05010700) { + sysCfg.param[P_HOLD_TIME] = KEY_HOLD_TIME; // Default 4 seconds hold time + } + if (sysCfg.version < 0x05020000) { + sysCfg.param[P_MAX_POWER_RETRY] = MAX_POWER_RETRY; + } + if (sysCfg.version < 0x05050000) { + for (byte i = 0; i < 17; i++) { + sysCfg.sfb_code[i][0] = 0; + } + memcpy_P(sysCfg.sfb_code[0], sfb_codeDefault, 9); + } + + sysCfg.version = VERSION; + CFG_Save(1); + } +} + + diff --git a/sonoff.ino b/sonoff.ino new file mode 100644 index 000000000..0bfbf591a --- /dev/null +++ b/sonoff.ino @@ -0,0 +1,2871 @@ +/* + sonoff.ino - Sonoff-Tasmota firmware for iTead Sonoff, Wemos and NodeMCU hardware + + Copyright (C) 2017 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 . +*/ +/*==================================================== + Prerequisites: + - Change libraries/PubSubClient/src/PubSubClient.h + #define MQTT_MAX_PACKET_SIZE 512 + + - Select IDE Tools - Flash Mode: "DOUT" + - Select IDE Tools - Flash Size: "1M (no SPIFFS)" + ====================================================*/ + +#define VERSION 0x05050102 // 5.5.1b + +enum log_t {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE, LOG_LEVEL_ALL}; +enum week_t {Last, First, Second, Third, Fourth}; +enum dow_t {Sun=1, Mon, Tue, Wed, Thu, Fri, Sat}; +enum month_t {Jan=1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec}; +enum wifi_t {WIFI_RESTART, WIFI_SMARTCONFIG, WIFI_MANAGER, WIFI_WPSCONFIG, WIFI_RETRY, MAX_WIFI_OPTION}; +enum swtch_t {TOGGLE, FOLLOW, FOLLOW_INV, PUSHBUTTON, PUSHBUTTON_INV, PUSHBUTTONHOLD, PUSHBUTTONHOLD_INV, MAX_SWITCH_OPTION}; +enum led_t {LED_OFF, LED_POWER, LED_MQTTSUB, LED_POWER_MQTTSUB, LED_MQTTPUB, LED_POWER_MQTTPUB, LED_MQTT, LED_POWER_MQTT, MAX_LED_OPTION}; +enum emul_t {EMUL_NONE, EMUL_WEMO, EMUL_HUE, EMUL_MAX}; + +#include "sonoff_template.h" + +#include "user_config.h" +#include "user_config_override.h" + +/*********************************************************************************************\ + * No user configurable items below +\*********************************************************************************************/ + +#define MODULE SONOFF_BASIC // [Module] Select default model + +#define USE_DHT // Default DHT11 sensor needs no external library +#ifndef USE_DS18x20 +#define USE_DS18B20 // Default DS18B20 sensor needs no external library +#endif +//#define DEBUG_THEO // Add debug code + +#ifdef BE_MINIMAL +#ifdef USE_MQTT_TLS +#undef USE_MQTT_TLS // Disable TLS support won't work as the MQTTHost is not set +#endif +#ifdef USE_DISCOVERY +#undef USE_DISCOVERY // Disable Discovery services for both MQTT and web server +#endif +#ifdef USE_DOMOTICZ +#undef USE_DOMOTICZ // Disable Domoticz +#endif +//#ifdef USE_WEBSERVER +//#undef USE_WEBSERVER // Disable Webserver +//#endif +#ifdef USE_EMULATION +#undef USE_EMULATION // Disable Wemo or Hue emulation +#endif +#ifdef USE_DS18x20 +#undef USE_DS18x20 // Disable DS18x20 sensor +#endif +#ifdef USE_I2C +#undef USE_I2C // Disable all I2C sensors +#endif +#ifdef USE_WS2812 +#undef USE_WS2812 // Disable WS2812 Led string +#endif +#ifdef USE_DS18B20 +#undef USE_DS18B20 // Disable internal DS18B20 sensor +#endif +#ifdef USE_DHT +#undef USE_DHT // Disable internal DHT sensor +#endif +#ifdef USE_IR_REMOTE +#undef USE_IR_REMOTE // Disable IR driver +#endif +#ifdef DEBUG_THEO +#undef DEBUG_THEO // Disable debug code +#endif +#endif // BE_MINIMAL + +#ifndef SWITCH_MODE +#define SWITCH_MODE TOGGLE // TOGGLE, FOLLOW or FOLLOW_INV (the wall switch state) +#endif + +#ifndef MQTT_FINGERPRINT +#define MQTT_FINGERPRINT "A5 02 FF 13 99 9F 8B 39 8E F1 83 4F 11 23 65 0B 32 36 FC 07" +#endif + +#ifndef WS2812_LEDS +#define WS2812_LEDS 30 // [Pixels] Number of LEDs +#endif + +#define MQTT_TOKEN_PREFIX "%prefix%" // To be substituted by mqtt_prefix[x] +#define MQTT_TOKEN_TOPIC "%topic%" // To be substituted by mqtt_topic, mqtt_grptopic, mqtt_buttontopic, mqtt_switchtopic + +#define WIFI_HOSTNAME "%s-%04d" // Expands to - +#define CONFIG_FILE_SIGN 0xA5 // Configuration file signature +#define CONFIG_FILE_XOR 0x5A // Configuration file xor (0 = No Xor) + +#define HLW_PREF_PULSE 12530 // was 4975us = 201Hz = 1000W +#define HLW_UREF_PULSE 1950 // was 1666us = 600Hz = 220V +#define HLW_IREF_PULSE 3500 // was 1666us = 600Hz = 4.545A + +#define MQTT_RETRY_SECS 10 // Minimum seconds to retry MQTT connection +#define APP_POWER 0 // Default saved power state Off +#define MAX_COUNTERS 4 // Max number of counter sensors +#define MAX_PULSETIMERS 4 // Max number of supported pulse timers +#define WS2812_MAX_LEDS 512 // Max number of LEDs + +#define PWM_RANGE 1023 // 255..1023 needs to be devisible by 256 +//#define PWM_FREQ 1000 // 100..1000 Hz led refresh +//#define PWM_FREQ 910 // 100..1000 Hz led refresh (iTead value) +#define PWM_FREQ 880 // 100..1000 Hz led refresh (BN-SZ01 value) + +#define MAX_POWER_HOLD 10 // Time in SECONDS to allow max agreed power (Pow) +#define MAX_POWER_WINDOW 30 // Time in SECONDS to disable allow max agreed power (Pow) +#define SAFE_POWER_HOLD 10 // Time in SECONDS to allow max unit safe power (Pow) +#define SAFE_POWER_WINDOW 30 // Time in MINUTES to disable allow max unit safe power (Pow) +#define MAX_POWER_RETRY 5 // Retry count allowing agreed power limit overflow (Pow) + +#define STATES 20 // State loops per second +#define SYSLOG_TIMER 600 // Seconds to restore syslog_level +#define SERIALLOG_TIMER 600 // Seconds to disable SerialLog +#define OTA_ATTEMPTS 10 // Number of times to try fetching the new firmware + +#define INPUT_BUFFER_SIZE 250 // Max number of characters in (serial) command buffer +#define CMDSZ 20 // Max number of characters in command +#define TOPSZ 100 // Max number of characters in topic string +#define LOGSZ 128 // Max number of characters in log string +#ifdef USE_MQTT_TLS + #define MAX_LOG_LINES 10 // Max number of lines in weblog +#else + #define MAX_LOG_LINES 20 // Max number of lines in weblog +#endif +#define MAX_BACKLOG 16 // Max number of commands in backlog (chk blogidx and blogptr code) +#define MIN_BACKLOG_DELAY 2 // Minimal backlog delay in 0.1 seconds + +#define APP_BAUDRATE 115200 // Default serial baudrate +#define MAX_STATUS 11 // Max number of status lines + +enum butt_t {PRESSED, NOT_PRESSED}; +enum opt_t {P_HOLD_TIME, P_MAX_POWER_RETRY, P_MAX_PARAM8}; // Index in sysCfg.param + +#include "support.h" // Global support + +#include // MQTT +#ifndef MESSZ + #define MESSZ 368 // Max number of characters in JSON message string (4 x DS18x20 sensors) +#endif +#if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MESSZ // If the max message size is too small, throw an error at compile time + // See pubsubclient.c line 359 + #error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 475" +#endif + +#include // RTC, HLW8012, OSWatch +#include // MQTT, Ota, WifiManager +#include // MQTT, Ota +#include // Ota +#include // Webserver, Updater +#include // WemoHue, IRremote, Domoticz +#ifdef USE_WEBSERVER + #include // WifiManager, Webserver + #include // WifiManager +#endif // USE_WEBSERVER +#ifdef USE_DISCOVERY + #include // MQTT, Webserver +#endif // USE_DISCOVERY +#ifdef USE_I2C + #include // I2C support library +#endif // USE_I2C +#ifdef USE_SPI + #include // SPI support, TFT +#endif // USE_SPI +#include "settings.h" + +#define MAX_BUTTON_COMMANDS 5 // Max number of button commands supported +const char commands[MAX_BUTTON_COMMANDS][14] PROGMEM = { + {"wificonfig 1"}, // Press button three times + {"wificonfig 2"}, // Press button four times + {"wificonfig 3"}, // Press button five times + {"restart 1"}, // Press button six times + {"upgrade 1"}}; // Press button seven times + +const char wificfg[5][12] PROGMEM = { "Restart", "Smartconfig", "Wifimanager", "WPSconfig", "Retry" }; +const char PREFIXES[3][5] PROGMEM = { "cmnd", "stat", "tele" }; + +struct TIME_T { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + char MonthName[4]; + uint16_t DayOfYear; + uint16_t Year; + unsigned long Valid; +} rtcTime; + +struct TimeChangeRule +{ + uint8_t week; // 1=First, 2=Second, 3=Third, 4=Fourth, or 0=Last week of the month + uint8_t dow; // day of week, 1=Sun, 2=Mon, ... 7=Sat + uint8_t month; // 1=Jan, 2=Feb, ... 12=Dec + uint8_t hour; // 0-23 + int offset; // offset from UTC in minutes +}; + +TimeChangeRule myDST = { TIME_DST }; // Daylight Saving Time +TimeChangeRule mySTD = { TIME_STD }; // Standard Time + +int Baudrate = APP_BAUDRATE; // Serial interface baud rate +byte SerialInByte; // Received byte +int SerialInByteCounter = 0; // Index in receive buffer +char serialInBuf[INPUT_BUFFER_SIZE + 2]; // Receive buffer +byte Hexcode = 0; // Sonoff dual input flag +uint16_t ButtonCode = 0; // Sonoff dual received code +int16_t savedatacounter; // Counter and flag for config save to Flash +char Version[16]; // Version string from VERSION define +char Hostname[33]; // Composed Wifi hostname +char MQTTClient[33]; // Composed MQTT Clientname +uint8_t mqttcounter = 0; // MQTT connection retry counter +uint8_t fallbacktopic = 0; // Use Topic or FallbackTopic +unsigned long timerxs = 0; // State loop timer +int state = 0; // State per second flag +int mqttflag = 2; // MQTT connection messages flag +int otaflag = 0; // OTA state flag +int otaok = 0; // OTA result +byte otaretry = OTA_ATTEMPTS; // OTA retry counter +int restartflag = 0; // Sonoff restart flag +int wificheckflag = WIFI_RESTART; // Wifi state flag +int uptime = 0; // Current uptime in hours +boolean uptime_flg = true; // Signal latest uptime +int tele_period = 0; // Tele period timer +String Log[MAX_LOG_LINES]; // Web log buffer +byte logidx = 0; // Index in Web log buffer +byte logajaxflg = 0; // Reset web console log +byte Maxdevice = 0; // Max number of devices supported +int status_update_timer = 0; // Refresh initial status +uint16_t pulse_timer[MAX_PULSETIMERS] = { 0 }; // Power off timer +uint16_t blink_timer = 0; // Power cycle timer +uint16_t blink_counter = 0; // Number of blink cycles +uint8_t blink_power; // Blink power state +uint8_t blink_mask = 0; // Blink relay active mask +uint8_t blink_powersave; // Blink start power save state +uint16_t mqtt_cmnd_publish = 0; // ignore flag for publish command +uint8_t latching_power = 0; // Power state at latching start +uint8_t latching_relay_pulse = 0; // Latching relay pulse timer +String Backlog[MAX_BACKLOG]; // Command backlog +uint8_t blogidx = 0; // Command backlog index +uint8_t blogptr = 0; // Command backlog pointer +uint8_t blogmutex = 0; // Command backlog pending +uint16_t blogdelay = 0; // Command backlog delay + +#ifdef USE_MQTT_TLS + WiFiClientSecure espClient; // Wifi Secure Client +#else + WiFiClient espClient; // Wifi Client +#endif +PubSubClient mqttClient(espClient); // MQTT Client +WiFiUDP portUDP; // UDP Syslog and Alexa + +uint8_t power; // Current copy of sysCfg.power +byte syslog_level; // Current copy of sysCfg.syslog_level +uint16_t syslog_timer = 0; // Timer to re-enable syslog_level +byte seriallog_level; // Current copy of sysCfg.seriallog_level +uint16_t seriallog_timer = 0; // Timer to disable Seriallog +uint8_t sleep; // Current copy of sysCfg.sleep +uint8_t stop_flash_rotate = 0; // Allow flash configuration rotation + +int blinks = 201; // Number of LED blinks +uint8_t blinkstate = 0; // LED state + +uint8_t lastbutton[4] = { NOT_PRESSED, NOT_PRESSED, NOT_PRESSED, NOT_PRESSED }; // Last button states +uint8_t holdbutton[4] = { 0 }; // Timer for button hold +uint8_t multiwindow[4] = { 0 }; // Max time between button presses to record press count +uint8_t multipress[4] = { 0 }; // Number of button presses within multiwindow +uint8_t lastwallswitch[4]; // Last wall switch states +uint8_t holdwallswitch[4] = { 0 }; // Timer for wallswitch push button hold +uint8_t blockgpio0 = 4; // Block GPIO0 for 4 seconds after poweron to workaround Wemos D1 RTS circuit + +mytmplt my_module; // Active copy of GPIOs +uint8_t pin[GPIO_MAX]; // Possible pin configurations +uint8_t rel_inverted[4] = { 0 }; // Relay inverted flag (1 = (0 = On, 1 = Off)) +uint8_t led_inverted[4] = { 0 }; // LED inverted flag (1 = (0 = On, 1 = Off)) +uint8_t swt_flg = 0; // Any external switch configured +uint8_t dht_flg = 0; // DHT configured +uint8_t hlw_flg = 0; // Power monitor configured +uint8_t i2c_flg = 0; // I2C configured +uint8_t spi_flg = 0; // SPI configured +uint8_t pwm_flg = 0; // PWM configured +uint8_t sfl_flg = 0; // Sonoff Led flag (0 = No led, 1 = BN-SZ01, 2 = Sonoff Led) +uint8_t pwm_idxoffset = 0; // Allowed PWM command offset (change for Sonoff Led) + +boolean mDNSbegun = false; + +/********************************************************************************************/ + +void getClient(char* output, const char* input, byte size) +{ + char *token; + uint8_t digits = 0; + + if (strstr(input, "%")) { + strlcpy(output, input, size); + token = strtok(output, "%"); + if (strstr(input, "%") == input) { + output[0] = '\0'; + } else { + token = strtok(NULL, ""); + } + if (token != NULL) { + digits = atoi(token); + if (digits) { + snprintf_P(output, size, PSTR("%s%c0%dX"), output, '%', digits); + snprintf_P(output, size, output, ESP.getChipId()); + } + } + } + if (!digits) { + strlcpy(output, input, size); + } +} + +void getTopic_P(char *stopic, byte prefix, char *topic, const char* subtopic) +{ + char romram[CMDSZ]; + String fulltopic; + + snprintf_P(romram, sizeof(romram), subtopic); + if (fallbacktopic) { + fulltopic = FPSTR(PREFIXES[prefix]); + fulltopic += F("/"); + fulltopic += MQTTClient; + } else { + fulltopic = sysCfg.mqtt_fulltopic; + if ((0 == prefix) && (-1 == fulltopic.indexOf(F(MQTT_TOKEN_PREFIX)))) { + fulltopic += F("/" MQTT_TOKEN_PREFIX); // Need prefix for commands to handle mqtt topic loops + } + for (byte i = 0; i < 3; i++) { + if ('\0' == sysCfg.mqtt_prefix[i][0]) { + snprintf_P(sysCfg.mqtt_prefix[i], sizeof(sysCfg.mqtt_prefix[i]), PREFIXES[i]); + } + } + fulltopic.replace(F(MQTT_TOKEN_PREFIX), sysCfg.mqtt_prefix[prefix]); + fulltopic.replace(F(MQTT_TOKEN_TOPIC), topic); + } + fulltopic.replace(F("#"), ""); + fulltopic.replace(F("//"), "/"); + if (!fulltopic.endsWith("/")) { + fulltopic += "/"; + } + snprintf_P(stopic, TOPSZ, PSTR("%s%s"), fulltopic.c_str(), romram); +} + +char* getStateText(byte state) +{ + if (state > 3) { + state = 1; + } + return sysCfg.state_text[state]; +} + +/********************************************************************************************/ + +void setLatchingRelay(uint8_t power, uint8_t state) +{ + power &= 1; + if (2 == state) { // Reset relay + state = 0; + latching_power = power; + latching_relay_pulse = 0; + } + else if (state && !latching_relay_pulse) { // Set port power to On + latching_power = power; + latching_relay_pulse = 2; // max 200mS (initiated by stateloop()) + } + if (pin[GPIO_REL1 +latching_power] < 99) { + digitalWrite(pin[GPIO_REL1 +latching_power], rel_inverted[latching_power] ? !state : state); + } +} + +void setRelay(uint8_t rpower) +{ + uint8_t state; + + if (4 == sysCfg.poweronstate) { // All on and stay on + power = (1 << Maxdevice) -1; + rpower = power; + } + if ((SONOFF_DUAL == sysCfg.module) || (CH4 == sysCfg.module)) { + Serial.write(0xA0); + Serial.write(0x04); + Serial.write(rpower); + Serial.write(0xA1); + Serial.write('\n'); + Serial.flush(); + } + else if (sfl_flg) { + sl_setPower(rpower &1); + } + else if (EXS_RELAY == sysCfg.module) { + setLatchingRelay(rpower, 1); + } + else { + for (byte i = 0; i < Maxdevice; i++) { + state = rpower &1; + if (pin[GPIO_REL1 +i] < 99) { + digitalWrite(pin[GPIO_REL1 +i], rel_inverted[i] ? !state : state); + } + rpower >>= 1; + } + } + hlw_setPowerSteadyCounter(2); +} + +void setLed(uint8_t state) +{ + if (state) { + state = 1; + } + digitalWrite(pin[GPIO_LED1], (led_inverted[0]) ? !state : state); +} + +/********************************************************************************************/ + +void mqtt_publish_sec(const char* topic, const char* data, boolean retained) +{ + char log[TOPSZ + MESSZ]; + + if (sysCfg.flag.mqtt_enabled) { + if (mqttClient.publish(topic, data, retained)) { + snprintf_P(log, sizeof(log), PSTR("MQTT: %s = %s%s"), topic, data, (retained) ? " (retained)" : ""); +// mqttClient.loop(); // Do not use here! Will block previous publishes + } else { + snprintf_P(log, sizeof(log), PSTR("RSLT: %s = %s"), topic, data); + } + } else { + snprintf_P(log, sizeof(log), PSTR("RSLT: %s = %s"), strrchr(topic,'/')+1, data); + } + + addLog(LOG_LEVEL_INFO, log); + if (sysCfg.ledstate &0x04) { + blinks++; + } +} + +void mqtt_publish(const char* topic, const char* data, boolean retained) +{ + char *me; + + if (!strcmp(sysCfg.mqtt_prefix[0],sysCfg.mqtt_prefix[1])) { + me = strstr(topic,sysCfg.mqtt_prefix[0]); + if (me == topic) { + mqtt_cmnd_publish += 8; + } + } + mqtt_publish_sec(topic, data, retained); +} + +void mqtt_publish(const char* topic, const char* data) +{ + mqtt_publish(topic, data, false); +} + +void mqtt_publish_topic_P(uint8_t prefix, const char* subtopic, const char* data, boolean retained) +{ +/* prefix 0 = cmnd using subtopic + * prefix 1 = stat using subtopic + * prefix 2 = tele using subtopic + * prefix 4 = cmnd using subtopic or RESULT + * prefix 5 = stat using subtopic or RESULT + * prefix 6 = tele using subtopic or RESULT + */ + char romram[16]; + char stopic[TOPSZ]; + + snprintf_P(romram, sizeof(romram), ((prefix > 3) && !sysCfg.flag.mqtt_response) ? PSTR("RESULT") : subtopic); + prefix &= 3; + getTopic_P(stopic, prefix, sysCfg.mqtt_topic, romram); + mqtt_publish(stopic, data, retained); +} + +void mqtt_publish_topic_P(uint8_t prefix, const char* subtopic, const char* data) +{ + mqtt_publish_topic_P(prefix, subtopic, data, false); +} + +void mqtt_publishPowerState(byte device) +{ + char stopic[TOPSZ]; + char sdevice[10]; + char scommand[10]; + char svalue[64]; // was MESSZ + + if ((device < 1) || (device > Maxdevice)) { + device = 1; + } + snprintf_P(sdevice, sizeof(sdevice), PSTR("%d"), device); + snprintf_P(scommand, sizeof(scommand), PSTR("POWER%s"), (Maxdevice > 1) ? sdevice : ""); + + getTopic_P(stopic, 1, sysCfg.mqtt_topic, (sysCfg.flag.mqtt_response)?"POWER":"RESULT"); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"%s\":\"%s\"}"), scommand, getStateText(bitRead(power, device -1))); + mqtt_publish(stopic, svalue); + + getTopic_P(stopic, 1, sysCfg.mqtt_topic, scommand); + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), getStateText(bitRead(power, device -1))); + mqtt_publish(stopic, svalue, sysCfg.flag.mqtt_power_retain); +} + +void mqtt_publishPowerBlinkState(byte device) +{ + char sdevice[10]; + char svalue[64]; // was MESSZ + + if ((device < 1) || (device > Maxdevice)) { + device = 1; + } + snprintf_P(sdevice, sizeof(sdevice), PSTR("%d"), device); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"POWER%s\":\"BLINK %s\"}"), + (Maxdevice > 1) ? sdevice : "", getStateText(bitRead(blink_mask, device -1))); + mqtt_publish_topic_P(5, PSTR("POWER"), svalue); +} + +void mqtt_connected() +{ + char stopic[TOPSZ]; + char svalue[128]; // was MESSZ + + if (sysCfg.flag.mqtt_enabled) { + + // Satisfy iobroker (#299) + svalue[0] = '\0'; + mqtt_publish_topic_P(0, PSTR("POWER"), svalue); + + getTopic_P(stopic, 0, sysCfg.mqtt_topic, PSTR("#")); + mqttClient.subscribe(stopic); + mqttClient.loop(); // Solve LmacRxBlk:1 messages + if (strstr(sysCfg.mqtt_fulltopic, MQTT_TOKEN_TOPIC) != NULL) { + getTopic_P(stopic, 0, sysCfg.mqtt_grptopic, PSTR("#")); + mqttClient.subscribe(stopic); + mqttClient.loop(); // Solve LmacRxBlk:1 messages + fallbacktopic = 1; + getTopic_P(stopic, 0, MQTTClient, PSTR("#")); + fallbacktopic = 0; + mqttClient.subscribe(stopic); + mqttClient.loop(); // Solve LmacRxBlk:1 messages + } +#ifdef USE_DOMOTICZ + domoticz_mqttSubscribe(); +#endif // USE_DOMOTICZ + } + + if (mqttflag) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Module\":\"%s\", \"Version\":\"%s\", \"FallbackTopic\":\"%s\", \"GroupTopic\":\"%s\"}"), + my_module.name, Version, MQTTClient, sysCfg.mqtt_grptopic); + mqtt_publish_topic_P(2, PSTR("INFO1"), svalue); +#ifdef USE_WEBSERVER + if (sysCfg.webserver) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"WebserverMode\":\"%s\", \"Hostname\":\"%s\", \"IPaddress\":\"%s\"}"), + (2 == sysCfg.webserver) ? "Admin" : "User", Hostname, WiFi.localIP().toString().c_str()); + mqtt_publish_topic_P(2, PSTR("INFO2"), svalue); + } +#endif // USE_WEBSERVER + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Started\":\"%s\"}"), + (getResetReason() == "Exception") ? ESP.getResetInfo().c_str() : getResetReason().c_str()); + mqtt_publish_topic_P(2, PSTR("INFO3"), svalue); + if (sysCfg.tele_period) { + tele_period = sysCfg.tele_period -9; + } + status_update_timer = 2; +#ifdef USE_DOMOTICZ + domoticz_setUpdateTimer(2); +#endif // USE_DOMOTICZ + } + mqttflag = 0; +} + +void mqtt_reconnect() +{ + char stopic[TOPSZ]; + char svalue[TOPSZ]; + char log[LOGSZ]; + + mqttcounter = sysCfg.mqtt_retry; + + if (!sysCfg.flag.mqtt_enabled) { + mqtt_connected(); + return; + } +#ifdef USE_EMULATION + UDP_Disconnect(); +#endif // USE_EMULATION + if (mqttflag > 1) { +#ifdef USE_MQTT_TLS + addLog_P(LOG_LEVEL_INFO, PSTR("MQTT: Verify TLS fingerprint...")); + if (!espClient.connect(sysCfg.mqtt_host, sysCfg.mqtt_port)) { + snprintf_P(log, sizeof(log), PSTR("MQTT: TLS Connect FAILED to %s:%d. Retry in %d seconds"), + sysCfg.mqtt_host, sysCfg.mqtt_port, mqttcounter); + addLog(LOG_LEVEL_DEBUG, log); + return; + } + if (espClient.verify(sysCfg.mqtt_fingerprint, sysCfg.mqtt_host)) { + addLog_P(LOG_LEVEL_INFO, PSTR("MQTT: Verified")); + } else { + addLog_P(LOG_LEVEL_DEBUG, PSTR("MQTT: Insecure connection due to invalid Fingerprint")); + } +#endif // USE_MQTT_TLS + mqttClient.setCallback(mqttDataCb); + mqttflag = 1; + mqttcounter = 1; + return; + } + + addLog_P(LOG_LEVEL_INFO, PSTR("MQTT: Attempting connection...")); +#ifndef USE_MQTT_TLS +#ifdef USE_DISCOVERY +#ifdef MQTT_HOST_DISCOVERY + mdns_discoverMQTTServer(); +#endif // MQTT_HOST_DISCOVERY +#endif // USE_DISCOVERY +#endif // USE_MQTT_TLS + mqttClient.setServer(sysCfg.mqtt_host, sysCfg.mqtt_port); + + getTopic_P(stopic, 2, sysCfg.mqtt_topic, PSTR("LWT")); + snprintf_P(svalue, sizeof(svalue), PSTR("Offline")); + if (mqttClient.connect(MQTTClient, sysCfg.mqtt_user, sysCfg.mqtt_pwd, stopic, 1, true, svalue)) { + addLog_P(LOG_LEVEL_INFO, PSTR("MQTT: Connected")); + mqttcounter = 0; + snprintf_P(svalue, sizeof(svalue), PSTR("Online")); + mqtt_publish(stopic, svalue, true); + mqtt_connected(); + } else { + snprintf_P(log, sizeof(log), PSTR("MQTT: Connect FAILED to %s:%d, rc %d. Retry in %d seconds"), + sysCfg.mqtt_host, sysCfg.mqtt_port, mqttClient.state(), mqttcounter); //status codes are documented here http://pubsubclient.knolleary.net/api.html#state + addLog(LOG_LEVEL_INFO, log); + } +} + +/********************************************************************************************/ + +boolean mqtt_command(boolean grpflg, char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload, char *svalue, uint16_t ssvalue) +{ + boolean serviced = true; + char stemp1[TOPSZ]; + char stemp2[10]; + char scommand[CMDSZ]; + uint16_t i; + + if (!strcmp_P(type,PSTR("MQTTHOST"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_host))) { + strlcpy(sysCfg.mqtt_host, (1 == payload) ? MQTT_HOST : dataBuf, sizeof(sysCfg.mqtt_host)); + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"MqttHost\",\"%s\"}"), sysCfg.mqtt_host); + } + else if (!strcmp_P(type,PSTR("MQTTPORT"))) { + if ((payload > 0) && (payload < 32766)) { + sysCfg.mqtt_port = (1 == payload) ? MQTT_PORT : payload; + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"MqttPort\":%d}"), sysCfg.mqtt_port); + } + else if (!strcmp_P(type,PSTR("MQTTRETRY"))) { + if ((payload >= MQTT_RETRY_SECS) && (payload < 32001)) { + sysCfg.mqtt_retry = payload; + mqttcounter = sysCfg.mqtt_retry; + } + snprintf_P(svalue, ssvalue, PSTR("{\"MqttRetry\":%d}"), sysCfg.mqtt_retry); + } + else if (!strcmp_P(type,PSTR("STATETEXT")) && (index > 0) && (index <= 4)) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.state_text[0]))) { + for(i = 0; i <= data_len; i++) { + if (dataBuf[i] == ' ') { + dataBuf[i] = '_'; + } + } + strlcpy(sysCfg.state_text[index -1], dataBuf, sizeof(sysCfg.state_text[0])); + } + snprintf_P(svalue, ssvalue, PSTR("{\"StateText%d\":\"%s\"}"), index, getStateText(index -1)); + } +#ifdef USE_MQTT_TLS + else if (!strcmp_P(type,PSTR("MQTTFINGERPRINT"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_fingerprint))) { + strlcpy(sysCfg.mqtt_fingerprint, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_FINGERPRINT : dataBuf, sizeof(sysCfg.mqtt_fingerprint)); + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"MqttFingerprint\":\"%s\"}"), sysCfg.mqtt_fingerprint); + } +#endif + else if (!grpflg && !strcmp_P(type,PSTR("MQTTCLIENT"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_client))) { + strlcpy(sysCfg.mqtt_client, (1 == payload) ? MQTT_CLIENT_ID : dataBuf, sizeof(sysCfg.mqtt_client)); + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"MqttClient\":\"%s\"}"), sysCfg.mqtt_client); + } + else if (!strcmp_P(type,PSTR("MQTTUSER"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_user))) { + strlcpy(sysCfg.mqtt_user, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_USER : dataBuf, sizeof(sysCfg.mqtt_user)); + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("[\"MqttUser\":\"%s\"}"), sysCfg.mqtt_user); + } + else if (!strcmp_P(type,PSTR("MQTTPASSWORD"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_pwd))) { + strlcpy(sysCfg.mqtt_pwd, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_PASS : dataBuf, sizeof(sysCfg.mqtt_pwd)); + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"MqttPassword\":\"%s\"}"), sysCfg.mqtt_pwd); + } + else if (!strcmp_P(type,PSTR("FULLTOPIC"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_fulltopic))) { + mqttfy(1, dataBuf); + if (!strcmp(dataBuf, MQTTClient)) { + payload = 1; + } + strlcpy(stemp1, (1 == payload) ? MQTT_FULLTOPIC : dataBuf, sizeof(stemp1)); + if (strcmp(stemp1, sysCfg.mqtt_fulltopic)) { + mqtt_publish_topic_P(2, PSTR("LWT"), (sysCfg.flag.mqtt_offline) ? "Offline" : "", true); // Offline or remove previous retained topic + strlcpy(sysCfg.mqtt_fulltopic, stemp1, sizeof(sysCfg.mqtt_fulltopic)); + restartflag = 2; + } + } + snprintf_P(svalue, ssvalue, PSTR("{\"FullTopic\":\"%s\"}"), sysCfg.mqtt_fulltopic); + } + else if (!strcmp_P(type,PSTR("PREFIX")) && (index > 0) && (index <= 3)) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_prefix[0]))) { + mqttfy(0, dataBuf); + strlcpy(sysCfg.mqtt_prefix[index -1], (1 == payload) ? (1==index)?SUB_PREFIX:(2==index)?PUB_PREFIX:PUB_PREFIX2 : dataBuf, sizeof(sysCfg.mqtt_prefix[0])); +// if (sysCfg.mqtt_prefix[index -1][strlen(sysCfg.mqtt_prefix[index -1])] == '/') sysCfg.mqtt_prefix[index -1][strlen(sysCfg.mqtt_prefix[index -1])] = 0; + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"Prefix%d\":\"%s\"}"), index, sysCfg.mqtt_prefix[index -1]); + } + else if (!strcmp_P(type,PSTR("GROUPTOPIC"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_grptopic))) { + mqttfy(0, dataBuf); + if (!strcmp(dataBuf, MQTTClient)) { + payload = 1; + } + strlcpy(sysCfg.mqtt_grptopic, (1 == payload) ? MQTT_GRPTOPIC : dataBuf, sizeof(sysCfg.mqtt_grptopic)); + restartflag = 2; + } + snprintf_P(svalue, ssvalue, PSTR("{\"GroupTopic\":\"%s\"}"), sysCfg.mqtt_grptopic); + } + else if (!grpflg && !strcmp_P(type,PSTR("TOPIC"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.mqtt_topic))) { + mqttfy(0, dataBuf); + if (!strcmp(dataBuf, MQTTClient)) { + payload = 1; + } + strlcpy(stemp1, (1 == payload) ? MQTT_TOPIC : dataBuf, sizeof(stemp1)); + if (strcmp(stemp1, sysCfg.mqtt_topic)) { + mqtt_publish_topic_P(2, PSTR("LWT"), (sysCfg.flag.mqtt_offline) ? "Offline" : "", true); // Offline or remove previous retained topic + strlcpy(sysCfg.mqtt_topic, stemp1, sizeof(sysCfg.mqtt_topic)); + restartflag = 2; + } + } + snprintf_P(svalue, ssvalue, PSTR("{\"Topic\":\"%s\"}"), sysCfg.mqtt_topic); + } + else if (!grpflg && !strcmp_P(type,PSTR("BUTTONTOPIC"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.button_topic))) { + mqttfy(0, dataBuf); + if (!strcmp(dataBuf, MQTTClient)) { + payload = 1; + } + strlcpy(sysCfg.button_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? sysCfg.mqtt_topic : dataBuf, sizeof(sysCfg.button_topic)); + } + snprintf_P(svalue, ssvalue, PSTR("{\"ButtonTopic\":\"%s\"}"), sysCfg.button_topic); + } + else if (!grpflg && !strcmp_P(type,PSTR("SWITCHTOPIC"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.switch_topic))) { + mqttfy(0, dataBuf); + if (!strcmp(dataBuf, MQTTClient)) { + payload = 1; + } + strlcpy(sysCfg.switch_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? sysCfg.mqtt_topic : dataBuf, sizeof(sysCfg.switch_topic)); + } + snprintf_P(svalue, ssvalue, PSTR("{\"SwitchTopic\":\"%s\"}"), sysCfg.switch_topic); + } + else if (!strcmp_P(type,PSTR("BUTTONRETAIN"))) { + if ((payload >= 0) && (payload <= 1)) { + strlcpy(sysCfg.button_topic, sysCfg.mqtt_topic, sizeof(sysCfg.button_topic)); + if (!payload) { + for(i = 1; i <= Maxdevice; i++) { + send_button_power(0, i, 9); // Clear MQTT retain in broker + } + } + sysCfg.flag.mqtt_button_retain = payload; + } + snprintf_P(svalue, ssvalue, PSTR("{\"ButtonRetain\":\"%s\"}"), getStateText(sysCfg.flag.mqtt_button_retain)); + } + else if (!strcmp_P(type,PSTR("SWITCHRETAIN"))) { + if ((payload >= 0) && (payload <= 1)) { + strlcpy(sysCfg.button_topic, sysCfg.mqtt_topic, sizeof(sysCfg.button_topic)); + if (!payload) { + for(i = 1; i <= 4; i++) { + send_button_power(1, i, 9); // Clear MQTT retain in broker + } + } + sysCfg.flag.mqtt_switch_retain = payload; + } + snprintf_P(svalue, ssvalue, PSTR("{\"SwitchRetain\":\"%s\"}"), getStateText(sysCfg.flag.mqtt_switch_retain)); + } + else if (!strcmp_P(type,PSTR("POWERRETAIN"))) { + if ((payload >= 0) && (payload <= 1)) { + if (!payload) { + for(i = 1; i <= Maxdevice; i++) { // Clear MQTT retain in broker + snprintf_P(stemp2, sizeof(stemp2), PSTR("%d"), i); + snprintf_P(scommand, sizeof(scommand), PSTR("POWER%s"), (Maxdevice > 1) ? stemp2 : ""); + getTopic_P(stemp1, 1, sysCfg.mqtt_topic, scommand); + mqtt_publish(stemp1, "", sysCfg.flag.mqtt_power_retain); + } + } + sysCfg.flag.mqtt_power_retain = payload; + } + snprintf_P(svalue, ssvalue, PSTR("{\"PowerRetain\":\"%s\"}"), getStateText(sysCfg.flag.mqtt_power_retain)); + } + else if (!strcmp_P(type,PSTR("SENSORRETAIN"))) { + if ((payload >= 0) && (payload <= 1)) { + if (!payload) { + svalue[0] = '\0'; + mqtt_publish_topic_P(2, PSTR("SENSOR"), svalue, sysCfg.flag.mqtt_sensor_retain); + } + sysCfg.flag.mqtt_sensor_retain = payload; + } + snprintf_P(svalue, ssvalue, PSTR("{\"SensorRetain\":\"%s\"}"), getStateText(sysCfg.flag.mqtt_sensor_retain)); + } + +#ifdef USE_DOMOTICZ + else if (domoticz_command(type, index, dataBuf, data_len, payload, svalue, ssvalue)) { + // Serviced + } +#endif // USE_DOMOTICZ + else { + serviced = false; + } + return serviced; +} + +/********************************************************************************************/ + +void mqttDataCb(char* topic, byte* data, unsigned int data_len) +{ + char *str; + + if (!strcmp(sysCfg.mqtt_prefix[0],sysCfg.mqtt_prefix[1])) { + str = strstr(topic,sysCfg.mqtt_prefix[0]); + if ((str == topic) && mqtt_cmnd_publish) { + if (mqtt_cmnd_publish > 8) { + mqtt_cmnd_publish -= 8; + } else { + mqtt_cmnd_publish = 0; + } + return; + } + } + + char topicBuf[TOPSZ]; + char dataBuf[data_len+1]; + char dataBufUc[128]; + char svalue[MESSZ]; + char stemp1[TOPSZ]; + char *p; + char *mtopic = NULL; + char *type = NULL; + byte otype = 0; + byte ptype = 0; + uint16_t i = 0; + uint16_t grpflg = 0; + uint16_t index; + uint32_t address; + + strncpy(topicBuf, topic, sizeof(topicBuf)); + memcpy(dataBuf, data, sizeof(dataBuf)); + dataBuf[sizeof(dataBuf)-1] = 0; + + snprintf_P(svalue, sizeof(svalue), PSTR("RSLT: Receive topic %s, data size %d, data %s"), topicBuf, data_len, dataBuf); + addLog(LOG_LEVEL_DEBUG_MORE, svalue); +// if (LOG_LEVEL_DEBUG_MORE <= seriallog_level) Serial.println(dataBuf); + +#ifdef USE_DOMOTICZ + if (sysCfg.flag.mqtt_enabled) { + if (domoticz_mqttData(topicBuf, sizeof(topicBuf), dataBuf, sizeof(dataBuf))) { + return; + } + } +#endif // USE_DOMOTICZ + + grpflg = (strstr(topicBuf, sysCfg.mqtt_grptopic) != NULL); + fallbacktopic = (strstr(topicBuf, MQTTClient) != NULL); + type = strrchr(topicBuf, '/') +1; // Last part of received topic is always the command (type) + + index = 1; + if (type != NULL) { + for (i = 0; i < strlen(type); i++) { + type[i] = toupper(type[i]); + } + while (isdigit(type[i-1])) { + i--; + } + if (i < strlen(type)) { + index = atoi(type +i); + } + type[i] = '\0'; + } + + for (i = 0; i <= sizeof(dataBufUc); i++) { + dataBufUc[i] = toupper(dataBuf[i]); + } + + snprintf_P(svalue, sizeof(svalue), PSTR("RSLT: DataCb Group %d, Index %d, Type %s, Data %s (%s)"), + grpflg, index, type, dataBuf, dataBufUc); + addLog(LOG_LEVEL_DEBUG, svalue); + + if (type != NULL) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Command\":\"Error\"}")); + if (sysCfg.ledstate &0x02) { + blinks++; + } + + if (!strcmp(dataBufUc,"?")) { + data_len = 0; + } + int16_t payload = -99; // No payload + uint16_t payload16 = 0; + long lnum = strtol(dataBuf, &p, 10); + if (p != dataBuf) { + payload = (int16_t) lnum; // -32766 - 32767 + payload16 = (uint16_t) lnum; // 0 - 65535 + } + blogdelay = MIN_BACKLOG_DELAY; // Reset backlog delay + + if (!strcmp_P(dataBufUc,PSTR("OFF")) || !strcmp_P(dataBufUc,PSTR("FALSE")) || !strcmp_P(dataBufUc,PSTR("STOP")) || !strcmp_P(dataBufUc,PSTR("CELSIUS"))) { + payload = 0; + } + if (!strcmp_P(dataBufUc,PSTR("ON")) || !strcmp_P(dataBufUc,PSTR("TRUE")) || !strcmp_P(dataBufUc,PSTR("START")) || !strcmp_P(dataBufUc,PSTR("FAHRENHEIT")) || !strcmp_P(dataBufUc,PSTR("USER"))) { + payload = 1; + } + if (!strcmp_P(dataBufUc,PSTR("TOGGLE")) || !strcmp_P(dataBufUc,PSTR("ADMIN"))) { + payload = 2; + } + if (!strcmp_P(dataBufUc,PSTR("BLINK"))) { + payload = 3; + } + if (!strcmp_P(dataBufUc,PSTR("BLINKOFF"))) { + payload = 4; + } + +// snprintf_P(svalue, sizeof(svalue), PSTR("RSLT: Payload %d, Payload16 %d"), payload, payload16); +// addLog(LOG_LEVEL_DEBUG, svalue); + + if (!strcmp_P(type,PSTR("BACKLOG"))) { + if (data_len) { + char *blcommand = strtok(dataBuf, ";"); + while (blcommand != NULL) { + Backlog[blogidx] = String(blcommand); + blogidx++; +/* + if (blogidx >= MAX_BACKLOG) { + blogidx = 0; + } +*/ + blogidx &= 0xF; + blcommand = strtok(NULL, ";"); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Backlog\":\"Appended\"}")); + } else { + uint8_t blflag = (blogptr == blogidx); + blogptr = blogidx; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Backlog\":\"%s\"}"), blflag ? "Empty" : "Aborted"); + } + } + else if (!strcmp_P(type,PSTR("DELAY"))) { + if ((payload >= MIN_BACKLOG_DELAY) && (payload <= 3600)) { + blogdelay = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Delay\":%d}"), blogdelay); + } + else if (!strcmp_P(type,PSTR("POWER")) && (index > 0) && (index <= Maxdevice)) { + if ((payload < 0) || (payload > 4)) { + payload = 9; + } + do_cmnd_power(index, payload); + fallbacktopic = 0; + return; + } + else if (!strcmp_P(type,PSTR("STATUS"))) { + if ((payload < 0) || (payload > MAX_STATUS)) { + payload = 99; + } + publish_status(payload); + fallbacktopic = 0; + return; + } + else if ((sysCfg.module != MOTOR) && !strcmp_P(type,PSTR("POWERONSTATE"))) { + if ((payload >= 0) && (payload <= 4)) { + sysCfg.poweronstate = payload; + if (4 == sysCfg.poweronstate) { + for (byte i = 1; i <= Maxdevice; i++) { + do_cmnd_power(i, 1); + } + } + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"PowerOnState\":%d}"), sysCfg.poweronstate); + } + else if (!strcmp_P(type,PSTR("PULSETIME")) && (index > 0) && (index <= MAX_PULSETIMERS)) { + if (data_len > 0) { + sysCfg.pulsetime[index -1] = payload16; // 0 - 65535 + pulse_timer[index -1] = 0; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"PulseTime%d\":%d}"), index, sysCfg.pulsetime[index -1]); + } + else if (!strcmp_P(type,PSTR("BLINKTIME"))) { + if ((payload > 2) && (payload <= 3600)) { + sysCfg.blinktime = payload; + if (blink_timer) { + blink_timer = sysCfg.blinktime; + } + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"BlinkTime\":%d}"), sysCfg.blinktime); + } + else if (!strcmp_P(type,PSTR("BLINKCOUNT"))) { + if (data_len > 0) { + sysCfg.blinkcount = payload16; // 0 - 65535 + if (blink_counter) { + blink_counter = sysCfg.blinkcount *2; + } + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"BlinkCount\":%d}"), sysCfg.blinkcount); + } + else if (sfl_flg && sl_command(type, index, dataBufUc, data_len, payload, svalue, sizeof(svalue))) { + // Serviced + } + else if (!strcmp_P(type,PSTR("SAVEDATA"))) { + if ((payload >= 0) && (payload <= 3600)) { + sysCfg.savedata = payload; + savedatacounter = sysCfg.savedata; + } + if (sysCfg.flag.savestate) { + sysCfg.power = power; + } + CFG_Save(0); + if (sysCfg.savedata > 1) { + snprintf_P(stemp1, sizeof(stemp1), PSTR("Every %d seconds"), sysCfg.savedata); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SaveData\":\"%s\"}"), (sysCfg.savedata > 1) ? stemp1 : getStateText(sysCfg.savedata)); + } + else if (!strcmp_P(type,PSTR("SETOPTION")) && ((index >= 0) && (index <= 13)) || ((index > 31) && (index <= P_MAX_PARAM8 +31))) { + if (index <= 31) { + ptype = 0; // SetOption0 .. 31 + } else { + ptype = 1; // SetOption32 .. + index = index -32; + } + if (payload >= 0) { + if (0 == ptype) { // SetOption0 .. 31 + if (payload <= 1) { + switch (index) { + case 3: // mqtt + restartflag = 2; + case 0: // savestate + case 1: // button_restrict + case 2: // value_units + case 4: // mqtt_response + case 8: // temperature_conversion + case 10: // mqtt_offline + case 11: // button_swap + case 12: // stop_flash_rotate + case 13: // button_single + bitWrite(sysCfg.flag.data, index, payload); + } + if (12 == index) { // stop_flash_rotate + stop_flash_rotate = payload; + CFG_Save(2); + } + } + } + else { // SetOption32 .. + switch (index) { + case P_HOLD_TIME: + if ((payload >= 1) && (payload <= 100)) { + sysCfg.param[P_HOLD_TIME] = payload; + } + break; + case P_MAX_POWER_RETRY: + if ((payload >= 1) && (payload <= 250)) { + sysCfg.param[P_MAX_POWER_RETRY] = payload; + } + break; + } + } + } + if (ptype) { + snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), sysCfg.param[index]); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SetOption%d\":\"%s\"}"), (ptype) ? index +32 : index, (ptype) ? stemp1 : getStateText(bitRead(sysCfg.flag.data, index))); + } + else if (!strcmp_P(type,PSTR("TEMPRES"))) { + if ((payload >= 0) && (payload <= 3)) { + sysCfg.flag.temperature_resolution = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"TempRes\":%d}"), sysCfg.flag.temperature_resolution); + } + else if (!strcmp_P(type,PSTR("HUMRES"))) { + if ((payload >= 0) && (payload <= 3)) { + sysCfg.flag.humidity_resolution = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"HumRes\":%d}"), sysCfg.flag.humidity_resolution); + } + else if (!strcmp_P(type,PSTR("PRESSRES"))) { + if ((payload >= 0) && (payload <= 3)) { + sysCfg.flag.pressure_resolution = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"PressRes\":%d}"), sysCfg.flag.pressure_resolution); + } + else if (!strcmp_P(type,PSTR("VOLTRES"))) { + if ((payload >= 0) && (payload <= 1)) { + sysCfg.flag.voltage_resolution = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"VoltRes\":%d}"), sysCfg.flag.voltage_resolution); + } + else if (!strcmp_P(type,PSTR("ENERGYRES"))) { + if ((payload >= 0) && (payload <= 5)) { + sysCfg.flag.energy_resolution = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"EnergyRes\":%d}"), sysCfg.flag.energy_resolution); + } + else if (!strcmp_P(type,PSTR("MODULE"))) { + if ((payload > 0) && (payload <= MAXMODULE)) { + payload--; + byte new_modflg = (sysCfg.module != payload); + sysCfg.module = payload; + if (new_modflg) { + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + sysCfg.my_module.gp.io[i] = 0; + } + } + restartflag = 2; + } + snprintf_P(stemp1, sizeof(stemp1), modules[sysCfg.module].name); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Module\":\"%d (%s)\"}"), sysCfg.module +1, stemp1); + } + else if (!strcmp_P(type,PSTR("MODULES"))) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Modules1\":\""), svalue); + byte jsflg = 0; + for (byte i = 0; i < MAXMODULE /2; i++) { + if (jsflg) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s, "), svalue); + } + jsflg = 1; + snprintf_P(stemp1, sizeof(stemp1), modules[i].name); + snprintf_P(svalue, sizeof(svalue), PSTR("%s%d (%s)"), svalue, i +1, stemp1); + } + snprintf_P(svalue, sizeof(svalue), PSTR("%s\"}"), svalue); + mqtt_publish_topic_P(5, type, svalue); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Modules2\":\""), svalue); + jsflg = 0; + for (byte i = MAXMODULE /2; i < MAXMODULE; i++) { + if (jsflg) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s, "), svalue); + } + jsflg = 1; + snprintf_P(stemp1, sizeof(stemp1), modules[i].name); + snprintf_P(svalue, sizeof(svalue), PSTR("%s%d (%s)"), svalue, i +1, stemp1); + } + snprintf_P(svalue, sizeof(svalue), PSTR("%s\"}"), svalue); + } + else if (!strcmp_P(type,PSTR("GPIO")) && (index < MAX_GPIO_PIN)) { + mytmplt cmodule; + memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule)); + if ((GPIO_USER == cmodule.gp.io[index]) && (payload >= 0) && (payload < GPIO_SENSOR_END)) { + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + if ((GPIO_USER == cmodule.gp.io[i]) && (sysCfg.my_module.gp.io[i] == payload)) { + sysCfg.my_module.gp.io[i] = 0; + } + } + sysCfg.my_module.gp.io[index] = payload; + restartflag = 2; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{"), svalue); + byte jsflg = 0; + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + if (GPIO_USER == cmodule.gp.io[i]) { + if (jsflg) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s, "), svalue); + } + jsflg = 1; + snprintf_P(stemp1, sizeof(stemp1), sensors[sysCfg.my_module.gp.io[i]]); + snprintf_P(svalue, sizeof(svalue), PSTR("%s\"GPIO%d\":%d (%s)"), svalue, i, sysCfg.my_module.gp.io[i], stemp1); + } + } + if (jsflg) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s}"), svalue); + } else { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"GPIO\":\"Not supported\"}")); + } + } + else if (!strcmp_P(type,PSTR("GPIOS"))) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"GPIOs1\":\""), svalue); + byte jsflg = 0; + for (byte i = 0; i < GPIO_SENSOR_END /2; i++) { + if (jsflg) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s, "), svalue); + } + jsflg = 1; + snprintf_P(stemp1, sizeof(stemp1), sensors[i]); + snprintf_P(svalue, sizeof(svalue), PSTR("%s%d (%s)"), svalue, i, stemp1); + } + snprintf_P(svalue, sizeof(svalue), PSTR("%s\"}"), svalue); + mqtt_publish_topic_P(5, type, svalue); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"GPIOs2\":\""), svalue); + jsflg = 0; + for (byte i = GPIO_SENSOR_END /2; i < GPIO_SENSOR_END; i++) { + if (jsflg) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s, "), svalue); + } + jsflg = 1; + snprintf_P(stemp1, sizeof(stemp1), sensors[i]); + snprintf_P(svalue, sizeof(svalue), PSTR("%s%d (%s)"), svalue, i, stemp1); + } + snprintf_P(svalue, sizeof(svalue), PSTR("%s\"}"), svalue); + } + else if (!strcmp_P(type,PSTR("PWM")) && (index > pwm_idxoffset) && (index <= 5)) { + if ((payload >= 0) && (payload <= PWM_RANGE) && (pin[GPIO_PWM1 + index -1] < 99)) { + sysCfg.pwmvalue[index -1] = payload; + analogWrite(pin[GPIO_PWM1 + index -1], payload); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"PWM\":{")); + bool first = true; + for (byte i = 0; i < 5; i++) { + if(pin[GPIO_PWM1 + i] < 99) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s%s\"PWM%d\":%d"), svalue, first ? "" : ", ", i+1, sysCfg.pwmvalue[i]); + first = false; + } + } + snprintf_P(svalue, sizeof(svalue), PSTR("%s}}"),svalue); + } + else if (!strcmp_P(type,PSTR("COUNTER")) && (index > 0) && (index <= MAX_COUNTERS)) { + if ((data_len > 0) && (pin[GPIO_CNTR1 + index -1] < 99)) { + rtcMem.pCounter[index -1] = payload16; + sysCfg.pCounter[index -1] = payload16; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Counter%d\":%d}"), index, rtcMem.pCounter[index -1]); + } + else if (!strcmp_P(type,PSTR("COUNTERTYPE")) && (index > 0) && (index <= MAX_COUNTERS)) { + if ((payload >= 0) && (payload <= 1) && (pin[GPIO_CNTR1 + index -1] < 99)) { + bitWrite(sysCfg.pCounterType, index -1, payload &1); + rtcMem.pCounter[index -1] = 0; + sysCfg.pCounter[index -1] = 0; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"CounterType%d\":%d}"), index, bitRead(sysCfg.pCounterType, index -1)); + } + else if (!strcmp_P(type,PSTR("COUNTERDEBOUNCE"))) { + if ((data_len > 0) && (payload16 < 32001)) { + sysCfg.pCounterDebounce = payload16; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"CounterDebounce\":%d}"), sysCfg.pCounterDebounce); + } + else if (!strcmp_P(type,PSTR("SLEEP"))) { + if ((payload >= 0) && (payload < 251)) { + if ((!sysCfg.sleep && payload) || (sysCfg.sleep && !payload)) { + restartflag = 2; + } + sysCfg.sleep = payload; + sleep = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Sleep\":\"%d%s (%d%s)\"}"), sleep, (sysCfg.flag.value_units) ? " mS" : "", sysCfg.sleep, (sysCfg.flag.value_units) ? " mS" : ""); + } + else if (!strcmp_P(type,PSTR("UPGRADE")) || !strcmp_P(type,PSTR("UPLOAD"))) { + // Check if the payload is numerically 1, and had no trailing chars. + // e.g. "1foo" or "1.2.3" could fool us. + // Check if the version we have been asked to upgrade to is higher than our current version. + // We also need at least 3 chars to make a valid version number string. + if (((1 == data_len) && (1 == payload)) || ((data_len >= 3) && newerVersion(dataBuf))) { + otaflag = 3; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Upgrade\":\"Version %s from %s\"}"), Version, sysCfg.otaUrl); + } else { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Upgrade\":\"Option 1 or >%s to upgrade\"}"), Version); + } + } + else if (!strcmp_P(type,PSTR("OTAURL"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.otaUrl))) + strlcpy(sysCfg.otaUrl, (1 == payload) ? OTA_URL : dataBuf, sizeof(sysCfg.otaUrl)); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"OtaUrl\":\"%s\"}"), sysCfg.otaUrl); + } + else if (!strcmp_P(type,PSTR("SERIALLOG"))) { + if ((payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) { + sysCfg.seriallog_level = payload; + seriallog_level = payload; + seriallog_timer = 0; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SerialLog\":\"%d (Active %d)\"}"), sysCfg.seriallog_level, seriallog_level); + } + else if (!strcmp_P(type,PSTR("SYSLOG"))) { + if ((payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) { + sysCfg.syslog_level = payload; + syslog_level = (sysCfg.flag.emulation) ? 0 : payload; + syslog_timer = 0; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SysLog\":\"%d (Active %d)\"}"), sysCfg.syslog_level, syslog_level); + } + else if (!strcmp_P(type,PSTR("LOGHOST"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.syslog_host))) { + strlcpy(sysCfg.syslog_host, (1 == payload) ? SYS_LOG_HOST : dataBuf, sizeof(sysCfg.syslog_host)); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"LogHost\":\"%s\"}"), sysCfg.syslog_host); + } + else if (!strcmp_P(type,PSTR("LOGPORT"))) { + if ((payload > 0) && (payload < 32766)) { + sysCfg.syslog_port = (1 == payload) ? SYS_LOG_PORT : payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"LogPort\":%d}"), sysCfg.syslog_port); + } + else if (!strcmp_P(type,PSTR("IPADDRESS")) && (index > 0) && (index <= 4)) { + if (parseIP(&address, dataBuf)) { + sysCfg.ip_address[index -1] = address; +// restartflag = 2; + } + snprintf_P(stemp1, sizeof(stemp1), PSTR(" (%s)"), WiFi.localIP().toString().c_str()); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"IPAddress%d\":\"%s%s\"}"), index, IPAddress(sysCfg.ip_address[index -1]).toString().c_str(), (1 == index) ? stemp1:""); + } + else if (!strcmp_P(type,PSTR("NTPSERVER")) && (index > 0) && (index <= 3)) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.ntp_server[0]))) { + strlcpy(sysCfg.ntp_server[index -1], (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? (1==index)?NTP_SERVER1:(2==index)?NTP_SERVER2:NTP_SERVER3 : dataBuf, sizeof(sysCfg.ntp_server[0])); + for (i = 0; i < strlen(sysCfg.ntp_server[index -1]); i++) { + if (sysCfg.ntp_server[index -1][i] == ',') { + sysCfg.ntp_server[index -1][i] = '.'; + } + } + restartflag = 2; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"NtpServer%d\":\"%s\"}"), index, sysCfg.ntp_server[index -1]); + } + else if (!strcmp_P(type,PSTR("AP"))) { + if ((payload >= 0) && (payload <= 2)) { + switch (payload) { + case 0: // Toggle + sysCfg.sta_active ^= 1; + break; + case 1: // AP1 + case 2: // AP2 + sysCfg.sta_active = payload -1; + } + restartflag = 2; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Ap\":\"%d (%s)\"}"), sysCfg.sta_active +1, sysCfg.sta_ssid[sysCfg.sta_active]); + } + else if (!strcmp_P(type,PSTR("SSID")) && (index > 0) && (index <= 2)) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.sta_ssid[0]))) { + strlcpy(sysCfg.sta_ssid[index -1], (1 == payload) ? (1 == index) ? STA_SSID1 : STA_SSID2 : dataBuf, sizeof(sysCfg.sta_ssid[0])); + sysCfg.sta_active = 0; + restartflag = 2; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SSid%d\":\"%s\"}"), index, sysCfg.sta_ssid[index -1]); + } + else if (!strcmp_P(type,PSTR("PASSWORD")) && (index > 0) && (index <= 2)) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.sta_pwd[0]))) { + strlcpy(sysCfg.sta_pwd[index -1], (1 == payload) ? (1 == index) ? STA_PASS1 : STA_PASS2 : dataBuf, sizeof(sysCfg.sta_pwd[0])); + sysCfg.sta_active = 0; + restartflag = 2; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Password%d\":\"%s\"}"), index, sysCfg.sta_pwd[index -1]); + } + else if (!grpflg && !strcmp_P(type,PSTR("HOSTNAME"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.hostname))) { + strlcpy(sysCfg.hostname, (1 == payload) ? WIFI_HOSTNAME : dataBuf, sizeof(sysCfg.hostname)); + if (strstr(sysCfg.hostname,"%")) { + strlcpy(sysCfg.hostname, WIFI_HOSTNAME, sizeof(sysCfg.hostname)); + } + restartflag = 2; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Hostname\":\"%s\"}"), sysCfg.hostname); + } + else if (!strcmp_P(type,PSTR("WIFICONFIG"))) { + if ((payload >= WIFI_RESTART) && (payload < MAX_WIFI_OPTION)) { + sysCfg.sta_config = payload; + wificheckflag = sysCfg.sta_config; + snprintf_P(stemp1, sizeof(stemp1), wificfg[sysCfg.sta_config]); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"WifiConfig\":\"%s selected\"}"), stemp1); + if (WIFI_State() != WIFI_RESTART) { +// snprintf_P(svalue, sizeof(svalue), PSTR("%s after restart"), svalue); + restartflag = 2; + } + } else { + snprintf_P(stemp1, sizeof(stemp1), wificfg[sysCfg.sta_config]); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"WifiConfig\":\"%d (%s)\"}"), sysCfg.sta_config, stemp1); + } + } + else if (!strcmp_P(type,PSTR("FRIENDLYNAME")) && (index > 0) && (index <= 4)) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.friendlyname[0]))) { + if (1 == index) { + snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME)); + } else { + snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), index); + } + strlcpy(sysCfg.friendlyname[index -1], (1 == payload) ? stemp1 : dataBuf, sizeof(sysCfg.friendlyname[index -1])); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"FriendlyName%d\":\"%s\"}"), index, sysCfg.friendlyname[index -1]); + } + else if (swt_flg && !strcmp_P(type,PSTR("SWITCHMODE")) && (index > 0) && (index <= 4)) { + if ((payload >= 0) && (payload < MAX_SWITCH_OPTION)) { + sysCfg.switchmode[index -1] = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"SwitchMode%d\":%d}"), index, sysCfg.switchmode[index-1]); + } +#ifdef USE_WEBSERVER + else if (!strcmp_P(type,PSTR("WEBSERVER"))) { + if ((payload >= 0) && (payload <= 2)) { + sysCfg.webserver = payload; + } + if (sysCfg.webserver) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Webserver\":\"Active for %s on %s with IP address %s\"}"), + (2 == sysCfg.webserver) ? "ADMIN" : "USER", Hostname, WiFi.localIP().toString().c_str()); + } else { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Webserver\":\"%s\"}"), getStateText(0)); + } + } + else if (!strcmp_P(type,PSTR("WEBPASSWORD"))) { + if ((data_len > 0) && (data_len < sizeof(sysCfg.web_password))) { + strlcpy(sysCfg.web_password, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? WEB_PASSWORD : dataBuf, sizeof(sysCfg.web_password)); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"WebPassword\":\"%s\"}"), sysCfg.web_password); + } + else if (!strcmp_P(type,PSTR("WEBLOG"))) { + if ((payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) { + sysCfg.weblog_level = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"WebLog\":%d}"), sysCfg.weblog_level); + } +#ifdef USE_EMULATION + else if (!strcmp_P(type,PSTR("EMULATION"))) { + if ((payload >= 0) && (payload <= 2)) { + sysCfg.flag.emulation = payload; + restartflag = 2; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Emulation\":%d}"), sysCfg.flag.emulation); + } +#endif // USE_EMULATION +#endif // USE_WEBSERVER + else if (!strcmp_P(type,PSTR("TELEPERIOD"))) { + if ((payload >= 0) && (payload < 3601)) { + sysCfg.tele_period = (1 == payload) ? TELE_PERIOD : payload; + if ((sysCfg.tele_period > 0) && (sysCfg.tele_period < 10)) { + sysCfg.tele_period = 10; // Do not allow periods < 10 seconds + } + tele_period = sysCfg.tele_period; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"TelePeriod\":\"%d%s\"}"), sysCfg.tele_period, (sysCfg.flag.value_units) ? " Sec" : ""); + } + else if (!strcmp_P(type,PSTR("RESTART"))) { + switch (payload) { + case 1: + restartflag = 2; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Restart\":\"Restarting\"}")); + break; + case 99: + addLog_P(LOG_LEVEL_INFO, PSTR("APP: Restarting")); + ESP.restart(); + break; + default: + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Restart\":\"1 to restart\"}")); + } + } + else if (!strcmp_P(type,PSTR("RESET"))) { + switch (payload) { + case 1: + restartflag = 211; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Reset\":\"Reset and Restarting\"}")); + break; + case 2: + restartflag = 212; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Reset\":\"Erase, Reset and Restarting\"}")); + break; + default: + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Reset\":\"1 to reset\"}")); + } + } + else if (!strcmp_P(type,PSTR("TIMEZONE"))) { + if ((data_len > 0) && (((payload >= -12) && (payload <= 12)) || (99 == payload))) { + sysCfg.timezone = payload; + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Timezone\":%d}"), sysCfg.timezone); + } + else if (!strcmp_P(type,PSTR("LEDPOWER"))) { + if ((payload >= 0) && (payload <= 2)) { + sysCfg.ledstate &= 8; + switch (payload) { + case 0: // Off + case 1: // On + sysCfg.ledstate = payload << 3; + break; + case 2: // Toggle + sysCfg.ledstate ^= 8; + break; + } + blinks = 0; + setLed(sysCfg.ledstate &8); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"LedPower\":\"%s\"}"), getStateText(bitRead(sysCfg.ledstate, 3))); + } + else if (!strcmp_P(type,PSTR("LEDSTATE"))) { + if ((payload >= 0) && (payload < MAX_LED_OPTION)) { + sysCfg.ledstate = payload; + if (!sysCfg.ledstate) { + setLed(0); + } + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"LedState\":%d}"), sysCfg.ledstate); + } + else if (!strcmp_P(type,PSTR("CFGDUMP"))) { + CFG_Dump(dataBuf); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"CfgDump\":\"Done\"}")); + } + else if (sysCfg.flag.mqtt_enabled && mqtt_command(grpflg, type, index, dataBuf, data_len, payload, svalue, sizeof(svalue))) { + // Serviced + } + else if (hlw_flg && hlw_command(type, index, dataBuf, data_len, payload, svalue, sizeof(svalue))) { + // Serviced + } + else if ((SONOFF_BRIDGE == sysCfg.module) && sb_command(type, index, dataBuf, data_len, payload, svalue, sizeof(svalue))) { + // Serviced + } +#ifdef USE_I2C + else if (i2c_flg && !strcmp_P(type,PSTR("I2CSCAN"))) { + i2c_scan(svalue, sizeof(svalue)); + } +#endif // USE_I2C +#ifdef USE_WS2812 + else if ((pin[GPIO_WS2812] < 99) && ws2812_command(type, index, dataBuf, data_len, payload, svalue, sizeof(svalue))) { + // Serviced + } +#endif // USE_WS2812 +#ifdef USE_IR_REMOTE + else if ((pin[GPIO_IRSEND] < 99) && ir_send_command(type, index, dataBufUc, data_len, payload, svalue, sizeof(svalue))) { + // Serviced + } +#endif // USE_IR_REMOTE +#ifdef DEBUG_THEO + else if (!strcmp_P(type,PSTR("EXCEPTION"))) { + if (data_len > 0) { + exception_tst(payload); + } + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Exception\":\"Triggered\"}")); + } +#endif // DEBUG_THEO + else { + type = NULL; + } + } + if (type == NULL) { + blinks = 201; + snprintf_P(topicBuf, sizeof(topicBuf), PSTR("COMMAND")); + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Command\":\"Unknown\"}")); + type = (char*)topicBuf; + } + if (svalue[0] != '\0') { + mqtt_publish_topic_P(5, type, svalue); + } + fallbacktopic = 0; +} + +/********************************************************************************************/ + +boolean send_button_power(byte key, byte device, byte state) +{ +// key 0 = button_topic +// key 1 = switch_topic +// state 0 = off +// state 1 = on +// state 2 = toggle +// state 3 = hold +// state 9 = clear retain flag + + char stopic[TOPSZ]; + char scommand[CMDSZ]; + char svalue[TOPSZ]; + char stemp1[10]; + boolean result = false; + + char *key_topic = (key) ? sysCfg.switch_topic : sysCfg.button_topic; + if (sysCfg.flag.mqtt_enabled && mqttClient.connected() && (strlen(key_topic) != 0) && strcmp(key_topic, "0")) { + if (!key && (device > Maxdevice)) { + device = 1; + } + snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), device); + snprintf_P(scommand, sizeof(scommand), PSTR("POWER%s"), (key || (Maxdevice > 1)) ? stemp1 : ""); + getTopic_P(stopic, 0, key_topic, scommand); + + if (9 == state) { + svalue[0] = '\0'; + } else { + if ((!strcmp(sysCfg.mqtt_topic, key_topic) || !strcmp(sysCfg.mqtt_grptopic, key_topic)) && (2 == state)) { + state = ~(power >> (device -1)) & 0x01; + } + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), getStateText(state)); + } +#ifdef USE_DOMOTICZ + if (!(domoticz_button(key, device, state, strlen(svalue)))) { + mqtt_publish_sec(stopic, svalue, (key) ? sysCfg.flag.mqtt_switch_retain : sysCfg.flag.mqtt_button_retain); + } +#else + mqtt_publish_sec(stopic, svalue, (key) ? sysCfg.flag.mqtt_switch_retain : sysCfg.flag.mqtt_button_retain); +#endif // USE_DOMOTICZ + result = true; + } + return result; +} + +void do_cmnd_power(byte device, byte state) +{ +// device = Relay number 1 and up +// state 0 = Relay Off +// state 1 = Relay On (turn off after sysCfg.pulsetime * 100 mSec if enabled) +// state 2 = Toggle relay +// state 3 = Blink relay +// state 4 = Stop blinking relay +// state 6 = Relay Off and no publishPowerState +// state 7 = Relay On and no publishPowerState +// state 9 = Show power state + + uint8_t publishPower = 1; + if ((6 == state) || (7 == state)) { + state &= 1; + publishPower = 0; + } + if ((device < 1) || (device > Maxdevice)) { + device = 1; + } + byte mask = 0x01 << (device -1); + pulse_timer[(device -1)&3] = 0; + if (state <= 2) { + if ((blink_mask & mask)) { + blink_mask &= (0xFF ^ mask); // Clear device mask + mqtt_publishPowerBlinkState(device); + } + switch (state) { + case 0: { // Off + power &= (0xFF ^ mask); + break; } + case 1: // On + power |= mask; + break; + case 2: // Toggle + power ^= mask; + } + setRelay(power); +#ifdef USE_DOMOTICZ + domoticz_updatePowerState(device); +#endif // USE_DOMOTICZ + pulse_timer[(device -1)&3] = (power & mask) ? sysCfg.pulsetime[(device -1)&3] : 0; + } + else if (3 == state) { // Blink + if (!(blink_mask & mask)) { + blink_powersave = (blink_powersave & (0xFF ^ mask)) | (power & mask); // Save state + blink_power = (power >> (device -1))&1; // Prep to Toggle + } + blink_timer = 1; + blink_counter = ((!sysCfg.blinkcount) ? 64000 : (sysCfg.blinkcount *2)) +1; + blink_mask |= mask; // Set device mask + mqtt_publishPowerBlinkState(device); + return; + } + else if (4 == state) { // No Blink + byte flag = (blink_mask & mask); + blink_mask &= (0xFF ^ mask); // Clear device mask + mqtt_publishPowerBlinkState(device); + if (flag) { + do_cmnd_power(device, (blink_powersave >> (device -1))&1); // Restore state + } + return; + } + if (publishPower) { + mqtt_publishPowerState(device); + } +} + +void stop_all_power_blink() +{ + byte mask; + + for (byte i = 1; i <= Maxdevice; i++) { + mask = 0x01 << (i -1); + if (blink_mask & mask) { + blink_mask &= (0xFF ^ mask); // Clear device mask + mqtt_publishPowerBlinkState(i); + do_cmnd_power(i, (blink_powersave >> (i -1))&1); // Restore state + } + } +} + +void do_cmnd(char *cmnd) +{ + char stopic[CMDSZ]; + char svalue[INPUT_BUFFER_SIZE]; + char *start; + char *token; + + token = strtok(cmnd, " "); + if (token != NULL) { + start = strrchr(token, '/'); // Skip possible cmnd/sonoff/ preamble + if (start) { + token = start +1; + } + } + snprintf_P(stopic, sizeof(stopic), PSTR("/%s"), (token == NULL) ? "" : token); + token = strtok(NULL, ""); + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), (token == NULL) ? "" : token); + mqttDataCb(stopic, (byte*)svalue, strlen(svalue)); +} + +void publish_status(uint8_t payload) +{ + char svalue[MESSZ]; + uint8_t option = 1; + + // Workaround MQTT - TCP/IP stack queueing when SUB_PREFIX = PUB_PREFIX + if (!strcmp(sysCfg.mqtt_prefix[0],sysCfg.mqtt_prefix[1]) && (!payload)) { + option++; + } + + if ((!sysCfg.flag.mqtt_enabled) && (6 == payload)) { + payload = 99; + } + if ((!hlw_flg) && ((8 == payload) || (9 == payload))) { + payload = 99; + } + + if ((0 == payload) || (99 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Status\":{\"Module\":%d, \"FriendlyName\":\"%s\", \"Topic\":\"%s\", \"ButtonTopic\":\"%s\", \"Power\":%d, \"PowerOnState\":%d, \"LedState\":%d, \"SaveData\":%d, \"SaveState\":%d, \"ButtonRetain\":%d, \"PowerRetain\":%d}}"), + sysCfg.module +1, sysCfg.friendlyname[0], sysCfg.mqtt_topic, sysCfg.button_topic, power, sysCfg.poweronstate, sysCfg.ledstate, sysCfg.savedata, sysCfg.flag.savestate, sysCfg.flag.mqtt_button_retain, sysCfg.flag.mqtt_power_retain); + mqtt_publish_topic_P(option, PSTR("STATUS"), svalue); + } + + if ((0 == payload) || (1 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusPRM\":{\"Baudrate\":%d, \"GroupTopic\":\"%s\", \"OtaUrl\":\"%s\", \"Uptime\":%d, \"Sleep\":%d, \"BootCount\":%d, \"SaveCount\":%d, \"SaveAddress\":\"%X\"}}"), + Baudrate, sysCfg.mqtt_grptopic, sysCfg.otaUrl, uptime, sysCfg.sleep, sysCfg.bootcount, sysCfg.saveFlag, CFG_Address()); + mqtt_publish_topic_P(option, PSTR("STATUS1"), svalue); + } + + if ((0 == payload) || (2 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusFWR\":{\"Program\":\"%s\", \"BuildDateTime\":\"%s\", \"Boot\":%d, \"Core\":\"%s\", \"SDK\":\"%s\"}}"), + Version, getBuildDateTime().c_str(), ESP.getBootVersion(), ESP.getCoreVersion().c_str(), ESP.getSdkVersion()); + mqtt_publish_topic_P(option, PSTR("STATUS2"), svalue); + } + + if ((0 == payload) || (3 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusLOG\":{\"Seriallog\":%d, \"Weblog\":%d, \"Syslog\":%d, \"LogHost\":\"%s\", \"SSId1\":\"%s\", \"SSId2\":\"%s\", \"TelePeriod\":%d, \"Option\":\"%08X\"}}"), + sysCfg.seriallog_level, sysCfg.weblog_level, sysCfg.syslog_level, sysCfg.syslog_host, sysCfg.sta_ssid[0], sysCfg.sta_ssid[1], sysCfg.tele_period, sysCfg.flag.data); + mqtt_publish_topic_P(option, PSTR("STATUS3"), svalue); + } + + if ((0 == payload) || (4 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusMEM\":{\"ProgramSize\":%d, \"Free\":%d, \"Heap\":%d, \"ProgramFlashSize\":%d, \"FlashSize\":%d, \"FlashMode\":%d}}"), + ESP.getSketchSize()/1024, ESP.getFreeSketchSpace()/1024, ESP.getFreeHeap()/1024, ESP.getFlashChipSize()/1024, ESP.getFlashChipRealSize()/1024, ESP.getFlashChipMode()); + mqtt_publish_topic_P(option, PSTR("STATUS4"), svalue); + } + + if ((0 == payload) || (5 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusNET\":{\"Hostname\":\"%s\", \"IPaddress\":\"%s\", \"Gateway\":\"%s\", \"Subnetmask\":\"%s\", \"DNSServer\":\"%s\", \"Mac\":\"%s\", \"Webserver\":%d, \"WifiConfig\":%d}}"), + Hostname, WiFi.localIP().toString().c_str(), IPAddress(sysCfg.ip_address[1]).toString().c_str(), IPAddress(sysCfg.ip_address[2]).toString().c_str(), IPAddress(sysCfg.ip_address[3]).toString().c_str(), + WiFi.macAddress().c_str(), sysCfg.webserver, sysCfg.sta_config); + mqtt_publish_topic_P(option, PSTR("STATUS5"), svalue); + } + + if (((0 == payload) || (6 == payload)) && sysCfg.flag.mqtt_enabled) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusMQT\":{\"Host\":\"%s\", \"Port\":%d, \"ClientMask\":\"%s\", \"Client\":\"%s\", \"User\":\"%s\", \"MAX_PACKET_SIZE\":%d, \"KEEPALIVE\":%d}}"), + sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.mqtt_client, MQTTClient, sysCfg.mqtt_user, MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE); + mqtt_publish_topic_P(option, PSTR("STATUS6"), svalue); + } + + if ((0 == payload) || (7 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusTIM\":{\"UTC\":\"%s\", \"Local\":\"%s\", \"StartDST\":\"%s\", \"EndDST\":\"%s\", \"Timezone\":%d}}"), + rtc_time(0).c_str(), rtc_time(1).c_str(), rtc_time(2).c_str(), rtc_time(3).c_str(), sysCfg.timezone); + mqtt_publish_topic_P(option, PSTR("STATUS7"), svalue); + } + + if (hlw_flg) { + if ((0 == payload) || (8 == payload)) { + hlw_mqttStatus(svalue, sizeof(svalue)); + mqtt_publish_topic_P(option, PSTR("STATUS8"), svalue); + } + + if ((0 == payload) || (9 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusPTH\":{\"PowerLow\":%d, \"PowerHigh\":%d, \"VoltageLow\":%d, \"VoltageHigh\":%d, \"CurrentLow\":%d, \"CurrentHigh\":%d}}"), + sysCfg.hlw_pmin, sysCfg.hlw_pmax, sysCfg.hlw_umin, sysCfg.hlw_umax, sysCfg.hlw_imin, sysCfg.hlw_imax); + mqtt_publish_topic_P(option, PSTR("STATUS9"), svalue); + } + } + + if ((0 == payload) || (10 == payload)) { + uint8_t djson = 0; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusSNS\":")); + sensors_mqttPresent(svalue, sizeof(svalue), &djson); + snprintf_P(svalue, sizeof(svalue), PSTR("%s}"), svalue); + mqtt_publish_topic_P(option, PSTR("STATUS10"), svalue); + } + + if ((0 == payload) || (11 == payload)) { + snprintf_P(svalue, sizeof(svalue), PSTR("{\"StatusSTS\":")); + state_mqttPresent(svalue, sizeof(svalue)); + snprintf_P(svalue, sizeof(svalue), PSTR("%s}"), svalue); + mqtt_publish_topic_P(option, PSTR("STATUS11"), svalue); + } + +} + +void state_mqttPresent(char* svalue, uint16_t ssvalue) +{ + char stemp1[8]; + + snprintf_P(svalue, ssvalue, PSTR("%s{\"Time\":\"%s\", \"Uptime\":%d"), svalue, getDateTime().c_str(), uptime); +#ifdef USE_ADC_VCC + dtostrf((double)ESP.getVcc()/1000, 1, 3, stemp1); + snprintf_P(svalue, ssvalue, PSTR("%s, \"Vcc\":%s"), svalue, stemp1); +#endif + for (byte i = 0; i < Maxdevice; i++) { + if (1 == Maxdevice) { // Legacy + snprintf_P(svalue, ssvalue, PSTR("%s, \"POWER\":"), svalue); + } else { + snprintf_P(svalue, ssvalue, PSTR("%s, \"POWER%d\":"), svalue, i +1); + } + snprintf_P(svalue, ssvalue, PSTR("%s\"%s\""), svalue, getStateText(bitRead(power, i))); + } + snprintf_P(svalue, ssvalue, PSTR("%s, \"Wifi\":{\"AP\":%d, \"SSID\":\"%s\", \"RSSI\":%d, \"APMac\":\"%s\"}}"), + svalue, sysCfg.sta_active +1, sysCfg.sta_ssid[sysCfg.sta_active], WIFI_getRSSIasQuality(WiFi.RSSI()), WiFi.BSSIDstr().c_str()); +} + +void sensors_mqttPresent(char* svalue, uint16_t ssvalue, uint8_t* djson) +{ + snprintf_P(svalue, ssvalue, PSTR("%s{\"Time\":\"%s\""), svalue, getDateTime().c_str()); + for (byte i = 0; i < 4; i++) { + if (pin[GPIO_SWT1 +i] < 99) { + boolean swm = ((FOLLOW_INV == sysCfg.switchmode[i]) || (PUSHBUTTON_INV == sysCfg.switchmode[i]) || (PUSHBUTTONHOLD_INV == sysCfg.switchmode[i])); + snprintf_P(svalue, ssvalue, PSTR("%s, \"Switch%d\":\"%s\""), svalue, i +1, getStateText(swm ^ lastwallswitch[i])); + *djson = 1; + } + } + counter_mqttPresent(svalue, ssvalue, djson); +#ifndef USE_ADC_VCC + if (pin[GPIO_ADC0] < 99) { + uint16_t alr = 0; + for (byte i = 0; i < 32; i++) { + alr += analogRead(A0); + delay(1); + } + snprintf_P(svalue, ssvalue, PSTR("%s, \"AnalogInput0\":%d"), svalue, alr >> 5); + *djson = 1; + } +#endif + if (SONOFF_SC == sysCfg.module) { + sc_mqttPresent(svalue, ssvalue, djson); + } + if (pin[GPIO_DSB] < 99) { +#ifdef USE_DS18B20 + dsb_mqttPresent(svalue, ssvalue, djson); +#endif // USE_DS18B20 +#ifdef USE_DS18x20 + ds18x20_mqttPresent(svalue, ssvalue, djson); +#endif // USE_DS18x20 + } +#ifdef USE_DHT + if (dht_flg) { + dht_mqttPresent(svalue, ssvalue, djson); + } +#endif // USE_DHT +#ifdef USE_I2C + if (i2c_flg) { +#ifdef USE_SHT + sht_mqttPresent(svalue, ssvalue, djson); +#endif // USE_SHT +#ifdef USE_HTU + htu_mqttPresent(svalue, ssvalue, djson); +#endif // USE_HTU +#ifdef USE_BMP + bmp_mqttPresent(svalue, ssvalue, djson); +#endif // USE_BMP +#ifdef USE_BH1750 + bh1750_mqttPresent(svalue, ssvalue, djson); +#endif // USE_BH1750 + } +#endif // USE_I2C + if (strstr_P(svalue, PSTR("Temperature"))) { + snprintf_P(svalue, ssvalue, PSTR("%s, \"TempUnit\":\"%c\""), svalue, tempUnit()); + } + snprintf_P(svalue, ssvalue, PSTR("%s}"), svalue); +} + +/********************************************************************************************/ + +void every_second() +{ + char svalue[MESSZ]; + + if (blockgpio0) { + blockgpio0--; + } + + for (byte i = 0; i < MAX_PULSETIMERS; i++) { + if (pulse_timer[i] > 111) { + pulse_timer[i]--; + } + } + + if (seriallog_timer) { + seriallog_timer--; + if (!seriallog_timer) { + if (seriallog_level) { + addLog_P(LOG_LEVEL_INFO, PSTR("APP: Serial logging disabled")); + } + seriallog_level = 0; + } + } + + if (syslog_timer) { // Restore syslog level + syslog_timer--; + if (!syslog_timer) { + syslog_level = (sysCfg.flag.emulation) ? 0 : sysCfg.syslog_level; + if (sysCfg.syslog_level) { + addLog_P(LOG_LEVEL_INFO, PSTR("SYSL: Syslog logging re-enabled")); // Might trigger disable again (on purpose) + } + } + } + +#ifdef USE_DOMOTICZ + domoticz_mqttUpdate(); +#endif // USE_DOMOTICZ + + if (status_update_timer) { + status_update_timer--; + if (!status_update_timer) { + for (byte i = 1; i <= Maxdevice; i++) { + mqtt_publishPowerState(i); + } + } + } + + if (sysCfg.tele_period) { + tele_period++; + if (tele_period == sysCfg.tele_period -1) { + if (pin[GPIO_DSB] < 99) { +#ifdef USE_DS18B20 + dsb_readTempPrep(); +#endif // USE_DS18B20 +#ifdef USE_DS18x20 + ds18x20_search(); // Check for changes in sensors number + ds18x20_convert(); // Start Conversion, takes up to one second +#endif // USE_DS18x20 + } +#ifdef USE_DHT + if (dht_flg) { + dht_readPrep(); + } +#endif // USE_DHT +#ifdef USE_I2C + if (i2c_flg) { +#ifdef USE_SHT + sht_detect(); +#endif // USE_SHT +#ifdef USE_HTU + htu_detect(); +#endif // USE_HTU +#ifdef USE_BMP + bmp_detect(); +#endif // USE_BMP +#ifdef USE_BH1750 + bh1750_detect(); +#endif // USE_BH1750 + } +#endif // USE_I2C + } + if (tele_period >= sysCfg.tele_period) { + tele_period = 0; + + svalue[0] = '\0'; + state_mqttPresent(svalue, sizeof(svalue)); + mqtt_publish_topic_P(2, PSTR("STATE"), svalue); + + uint8_t djson = 0; + svalue[0] = '\0'; + sensors_mqttPresent(svalue, sizeof(svalue), &djson); + if (djson) { + mqtt_publish_topic_P(2, PSTR("SENSOR"), svalue, sysCfg.flag.mqtt_sensor_retain); + } + + if (hlw_flg) { + hlw_mqttPresent(); + } + } + } + + if (hlw_flg) { + hlw_margin_chk(); + } + + if ((2 == rtcTime.Minute) && uptime_flg) { + uptime_flg = false; + uptime++; + snprintf_P(svalue, sizeof(svalue), PSTR("{\"Time\":\"%s\", \"Uptime\":%d}"), getDateTime().c_str(), uptime); + mqtt_publish_topic_P(2, PSTR("UPTIME"), svalue); + } + if ((3 == rtcTime.Minute) && !uptime_flg) { + uptime_flg = true; + } +} + +/*********************************************************************************************\ + * Button handler with single press only or multi-press and hold on all buttons +\*********************************************************************************************/ + +void button_handler() +{ + uint8_t button = NOT_PRESSED; + uint8_t butt_present = 0; + uint8_t flag = 0; + char scmnd[20]; + char log[LOGSZ]; + + for (byte i = 0; i < Maxdevice; i++) { + button = NOT_PRESSED; + butt_present = 0; + + if (!i && ((SONOFF_DUAL == sysCfg.module) || (CH4 == sysCfg.module))) { + butt_present = 1; + if (ButtonCode) { + snprintf_P(log, sizeof(log), PSTR("APP: Button code %04X"), ButtonCode); + addLog(LOG_LEVEL_DEBUG, log); + button = PRESSED; + if (0xF500 == ButtonCode) { // Button hold + holdbutton[i] = (sysCfg.param[P_HOLD_TIME] * (STATES / 10)) -1; + } + ButtonCode = 0; + } + } else { + if ((pin[GPIO_KEY1 +i] < 99) && !blockgpio0) { + butt_present = 1; + button = digitalRead(pin[GPIO_KEY1 +i]); + } + } + + if (butt_present) { + if (SONOFF_4CHPRO == sysCfg.module) { + if (holdbutton[i]) { + holdbutton[i]--; + } + flag = 0; + if ((PRESSED == button) && (NOT_PRESSED == lastbutton[i])) { + snprintf_P(log, sizeof(log), PSTR("APP: Button %d level 1-0"), i +1); + addLog(LOG_LEVEL_DEBUG, log); + holdbutton[i] = STATES; + flag = 1; + } + if ((NOT_PRESSED == button) && (PRESSED == lastbutton[i])) { + snprintf_P(log, sizeof(log), PSTR("APP: Button %d level 0-1"), i +1); + addLog(LOG_LEVEL_DEBUG, log); + if (!holdbutton[i]) { // Do not allow within 1 second + flag = 1; + } + } + if (flag) { + if (!send_button_power(0, i +1, 2)) { // Execute Toggle command via MQTT if ButtonTopic is set + do_cmnd_power(i +1, 2); // Execute Toggle command internally + } + } + } else { + if ((PRESSED == button) && (NOT_PRESSED == lastbutton[i])) { + if (sysCfg.flag.button_single) { // Allow only single button press for immediate action + snprintf_P(log, sizeof(log), PSTR("APP: Button %d immediate"), i +1); + if (!send_button_power(0, i +1, 2)) { // Execute Toggle command via MQTT if ButtonTopic is set + do_cmnd_power(i +1, 2); // Execute Toggle command internally + } + } else { + multipress[i] = (multiwindow[i]) ? multipress[i] +1 : 1; + snprintf_P(log, sizeof(log), PSTR("APP: Button %d multi-press %d"), i +1, multipress[i]); + multiwindow[i] = STATES /2; // 0.5 second multi press window + } + addLog(LOG_LEVEL_DEBUG, log); + blinks = 201; + } + + if (NOT_PRESSED == button) { + holdbutton[i] = 0; + } else { + holdbutton[i]++; + if (sysCfg.flag.button_single) { // Allow only single button press for immediate action + if (holdbutton[i] == sysCfg.param[P_HOLD_TIME] * (STATES / 10) * 4) { // Button hold for four times longer +// sysCfg.flag.button_single = 0; + snprintf_P(scmnd, sizeof(scmnd), PSTR("setoption13 0")); // Disable single press only + do_cmnd(scmnd); + } + } else { + if (holdbutton[i] == sysCfg.param[P_HOLD_TIME] * (STATES / 10)) { // Button hold + multipress[i] = 0; + if (!sysCfg.flag.button_restrict) { // No button restriction + snprintf_P(scmnd, sizeof(scmnd), PSTR("reset 1")); + do_cmnd(scmnd); + } else { + send_button_power(0, i +1, 3); // Execute Hold command via MQTT if ButtonTopic is set + } + } + } + } + + if (!sysCfg.flag.button_single) { // Allow multi-press + if (multiwindow[i]) { + multiwindow[i]--; + } else { + if (!restartflag && !holdbutton[i] && (multipress[i] > 0) && (multipress[i] < MAX_BUTTON_COMMANDS +3)) { + flag = 0; + if (multipress[i] < 3) { // Single or Double press + if ((SONOFF_DUAL == sysCfg.module) || (CH4 == sysCfg.module)) { + flag = 1; + } else { + flag = (sysCfg.flag.button_swap +1 == multipress[i]); + multipress[i] = 1; + } + } + if (flag && send_button_power(0, i + multipress[i], 2)) { // Execute Toggle command via MQTT if ButtonTopic is set + // Success + } else { + if (multipress[i] < 3) { // Single or Double press + if (WIFI_State()) { // WPSconfig, Smartconfig or Wifimanager active + restartflag = 1; + } else { + do_cmnd_power(i + multipress[i], 2); // Execute Toggle command internally + } + } else { // 3 - 7 press + if (!sysCfg.flag.button_restrict) { + snprintf_P(scmnd, sizeof(scmnd), commands[multipress[i] -3]); + do_cmnd(scmnd); + } + } + } + multipress[i] = 0; + } + } + } + } + } + lastbutton[i] = button; + } +} + +/*********************************************************************************************\ + * Switch handler +\*********************************************************************************************/ + +void switch_handler() +{ + uint8_t button = NOT_PRESSED; + uint8_t switchflag; + + for (byte i = 0; i < 4; i++) { + if (pin[GPIO_SWT1 +i] < 99) { + + if (holdwallswitch[i]) { + holdwallswitch[i]--; + if (0 == holdwallswitch[i]) { + send_button_power(1, i +1, 3); // Execute command via MQTT + } + } + + button = digitalRead(pin[GPIO_SWT1 +i]); + if (button != lastwallswitch[i]) { + switchflag = 3; + switch (sysCfg.switchmode[i]) { + case TOGGLE: + switchflag = 2; // Toggle + break; + case FOLLOW: + switchflag = button & 0x01; // Follow wall switch state + break; + case FOLLOW_INV: + switchflag = ~button & 0x01; // Follow inverted wall switch state + break; + case PUSHBUTTON: + if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { + switchflag = 2; // Toggle with pushbutton to Gnd + } + break; + case PUSHBUTTON_INV: + if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { + switchflag = 2; // Toggle with releasing pushbutton from Gnd + } + break; + case PUSHBUTTONHOLD: + if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { + holdwallswitch[i] = sysCfg.param[P_HOLD_TIME] * (STATES / 10); + } + if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { + holdwallswitch[i] = 0; + switchflag = 2; // Toggle with pushbutton to Gnd + } + break; + case PUSHBUTTONHOLD_INV: + if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { + holdwallswitch[i] = sysCfg.param[P_HOLD_TIME] * (STATES / 10); + } + if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { + holdwallswitch[i] = 0; + switchflag = 2; // Toggle with pushbutton to Gnd + } + break; + } + + if (switchflag < 3) { + if (!send_button_power(1, i +1, switchflag)) { // Execute command via MQTT + do_cmnd_power(i +1, switchflag); // Execute command internally (if i < Maxdevice) + } + } + + lastwallswitch[i] = button; + } + } + } +} + +/*********************************************************************************************\ + * State loop +\*********************************************************************************************/ + +void stateloop() +{ + uint8_t power_now; + char log[LOGSZ]; + char svalue[80]; // was MESSZ + + timerxs = millis() + (1000 / STATES); + state++; + +/*-------------------------------------------------------------------------------------------*\ + * Every second +\*-------------------------------------------------------------------------------------------*/ + + if (STATES == state) { + state = 0; + every_second(); + } + +/*-------------------------------------------------------------------------------------------*\ + * Every 0.1 second +\*-------------------------------------------------------------------------------------------*/ + + if (!(state % (STATES/10))) { + + if (mqtt_cmnd_publish) { + mqtt_cmnd_publish--; // Clean up + } + + if (latching_relay_pulse) { + latching_relay_pulse--; + if (!latching_relay_pulse) { + setLatchingRelay(0, 0); + } + } + + for (byte i = 0; i < MAX_PULSETIMERS; i++) { + if ((pulse_timer[i] > 0) && (pulse_timer[i] < 112)) { + pulse_timer[i]--; + if (!pulse_timer[i]) { + do_cmnd_power(i +1, 0); + } + } + } + + if (blink_mask) { + blink_timer--; + if (!blink_timer) { + blink_timer = sysCfg.blinktime; + blink_counter--; + if (!blink_counter) { + stop_all_power_blink(); + } else { + blink_power ^= 1; + power_now = (power & (0xFF ^ blink_mask)) | ((blink_power) ? blink_mask : 0); + setRelay(power_now); + } + } + } + + // Backlog + if (blogdelay) { + blogdelay--; + } + if ((blogptr != blogidx) && !blogdelay && !blogmutex) { + blogmutex = 1; + do_cmnd((char*)Backlog[blogptr].c_str()); + blogmutex = 0; + blogptr++; +/* + if (blogptr >= MAX_BACKLOG) { + blogptr = 0; + } +*/ + blogptr &= 0xF; + } + } + +/*-------------------------------------------------------------------------------------------*\ + * Every 0.05 second +\*-------------------------------------------------------------------------------------------*/ + + button_handler(); + switch_handler(); + + if (sfl_flg) { // Sonoff BN-SZ01 or Sonoff Led + sl_animate(); + } + +#ifdef USE_WS2812 + if (pin[GPIO_WS2812] < 99) { + ws2812_animate(); + } +#endif // USE_WS2812 + +/*-------------------------------------------------------------------------------------------*\ + * Every 0.2 second +\*-------------------------------------------------------------------------------------------*/ + + if (!(state % ((STATES/10)*2))) { + if (blinks || restartflag || otaflag) { + if (restartflag || otaflag) { + blinkstate = 1; // Stay lit + } else { + blinkstate ^= 1; // Blink + } + if ((!(sysCfg.ledstate &0x08)) && ((sysCfg.ledstate &0x06) || (blinks > 200) || (blinkstate))) { + setLed(blinkstate); + } + if (!blinkstate) { + blinks--; + if (200 == blinks) { + blinks = 0; + } + } + } else { + if (sysCfg.ledstate &0x01) { + setLed((SONOFF_TOUCH == sysCfg.module) ? (power ^1) : power); + } + } + } + +/*-------------------------------------------------------------------------------------------*\ + * Every second at 0.2 second interval +\*-------------------------------------------------------------------------------------------*/ + + switch (state) { + case (STATES/10)*2: + if (otaflag && (blogptr == blogidx)) { + otaflag--; + if (2 == otaflag) { + otaretry = OTA_ATTEMPTS; + ESPhttpUpdate.rebootOnUpdate(false); + CFG_Save(1); // Free flash for OTA update + } + if (otaflag <= 0) { +#ifdef USE_WEBSERVER + if (sysCfg.webserver) { + stopWebserver(); + } +#endif // USE_WEBSERVER + otaflag = 92; + otaok = 0; + otaretry--; + if (otaretry) { +// snprintf_P(log, sizeof(log), PSTR("OTA: Attempt %d"), OTA_ATTEMPTS - otaretry); +// addLog(LOG_LEVEL_INFO, log); + otaok = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(sysCfg.otaUrl)); + if (!otaok) { + otaflag = 2; + } + } + } + if (90 == otaflag) { // Allow MQTT to reconnect + otaflag = 0; + if (otaok) { + setFlashModeDout(); // Force DOUT for both ESP8266 and ESP8285 + snprintf_P(svalue, sizeof(svalue), PSTR("Successful. Restarting")); + } else { + snprintf_P(svalue, sizeof(svalue), PSTR("Failed %s"), ESPhttpUpdate.getLastErrorString().c_str()); + } + restartflag = 2; // Restart anyway to keep memory clean webserver + mqtt_publish_topic_P(1, PSTR("UPGRADE"), svalue); + } + } + break; + case (STATES/10)*4: + if (rtc_midnight_now()) { + counter_savestate(); + } + if (savedatacounter && (blogptr == blogidx)) { + savedatacounter--; + if (savedatacounter <= 0) { + if (sysCfg.flag.savestate) { + byte mask = 0xFF; + for (byte i = 0; i < MAX_PULSETIMERS; i++) { + if ((sysCfg.pulsetime[i] > 0) && (sysCfg.pulsetime[i] < 30)) { + mask &= ~(1 << i); + } + } + if (!((sysCfg.power &mask) == (power &mask))) { + sysCfg.power = power; + } + } + CFG_Save(0); + savedatacounter = sysCfg.savedata; + } + } + if (restartflag && (blogptr == blogidx)) { + if (211 == restartflag) { + CFG_Default(); + restartflag = 2; + } + if (212 == restartflag) { + CFG_Erase(); + CFG_Default(); + restartflag = 2; + } + if (sysCfg.flag.savestate) { + sysCfg.power = power; + } + if (hlw_flg) { + hlw_savestate(); + } + counter_savestate(); + CFG_Save(0); + restartflag--; + if (restartflag <= 0) { + addLog_P(LOG_LEVEL_INFO, PSTR("APP: Restarting")); + ESP.restart(); + } + } + break; + case (STATES/10)*6: + WIFI_Check(wificheckflag); + wificheckflag = WIFI_RESTART; + break; + case (STATES/10)*8: + if (WL_CONNECTED == WiFi.status()) { + if (sysCfg.flag.mqtt_enabled) { + if (!mqttClient.connected()) { + if (!mqttcounter) { + mqtt_reconnect(); + } else { + mqttcounter--; + } + } + } else { + if (!mqttcounter) { + mqtt_reconnect(); + } + } + } + break; + } +} + +/********************************************************************************************/ + +void serial() +{ + char log[LOGSZ]; + + while (Serial.available()) { + yield(); + SerialInByte = Serial.read(); + +/*-------------------------------------------------------------------------------------------*\ + * Sonoff dual 19200 baud serial interface +\*-------------------------------------------------------------------------------------------*/ + + if (Hexcode) { + Hexcode--; + if (Hexcode) { + ButtonCode = (ButtonCode << 8) | SerialInByte; + SerialInByte = 0; + } else { + if (SerialInByte != 0xA1) { + ButtonCode = 0; // 0xA1 - End of Sonoff dual button code + } + } + } + if (0xA0 == SerialInByte) { // 0xA0 - Start of Sonoff dual button code + SerialInByte = 0; + ButtonCode = 0; + Hexcode = 3; + } + +/*-------------------------------------------------------------------------------------------*\ + * Sonoff bridge 19200 baud serial interface +\*-------------------------------------------------------------------------------------------*/ + + if (sb_serial()) { + SerialInByteCounter = 0; + Serial.flush(); + return; + } + +/*-------------------------------------------------------------------------------------------*/ + + if (SerialInByte > 127) { // binary data... + SerialInByteCounter = 0; + Serial.flush(); + return; + } + if (isprint(SerialInByte)) { + if (SerialInByteCounter < INPUT_BUFFER_SIZE) { // add char to string if it still fits + serialInBuf[SerialInByteCounter++] = SerialInByte; + } else { + SerialInByteCounter = 0; + } + } + + if (SerialInByte == '\x1B') { // Sonoff SC status from ATMEGA328P + serialInBuf[SerialInByteCounter] = 0; // serial data completed + sc_rcvstat(serialInBuf); + SerialInByteCounter = 0; + Serial.flush(); + return; + } + else if (SerialInByte == '\n') { + serialInBuf[SerialInByteCounter] = 0; // serial data completed + seriallog_level = (sysCfg.seriallog_level < LOG_LEVEL_INFO) ? LOG_LEVEL_INFO : sysCfg.seriallog_level; + snprintf_P(log, sizeof(log), PSTR("CMND: %s"), serialInBuf); + addLog(LOG_LEVEL_INFO, log); + do_cmnd(serialInBuf); + SerialInByteCounter = 0; + Serial.flush(); + return; + } + } +} + +/********************************************************************************************/ + +void GPIO_init() +{ + char log[LOGSZ]; + uint8_t mpin; + mytmplt def_module; + + if (!sysCfg.module || (sysCfg.module >= MAXMODULE)) { + sysCfg.module = MODULE; + } + + memcpy_P(&def_module, &modules[sysCfg.module], sizeof(def_module)); +// sysCfg.my_module.flag = def_module.flag; + strlcpy(my_module.name, def_module.name, sizeof(my_module.name)); + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + if (sysCfg.my_module.gp.io[i] > GPIO_NONE) { + my_module.gp.io[i] = sysCfg.my_module.gp.io[i]; + } + if ((def_module.gp.io[i] > GPIO_NONE) && (def_module.gp.io[i] < GPIO_USER)) { + my_module.gp.io[i] = def_module.gp.io[i]; + } + } + + for (byte i = 0; i < GPIO_MAX; i++) { + pin[i] = 99; + } + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + mpin = my_module.gp.io[i]; + +// snprintf_P(log, sizeof(log), PSTR("DBG: gpio pin %d, mpin %d"), i, mpin); +// addLog(LOG_LEVEL_DEBUG, log); + + if (mpin) { + if ((mpin >= GPIO_REL1_INV) && (mpin <= GPIO_REL4_INV)) { + rel_inverted[mpin - GPIO_REL1_INV] = 1; + mpin -= 4; + } + else if ((mpin >= GPIO_LED1_INV) && (mpin <= GPIO_LED4_INV)) { + led_inverted[mpin - GPIO_LED1_INV] = 1; + mpin -= 4; + } +#ifdef USE_DHT + else if ((mpin >= GPIO_DHT11) && (mpin <= GPIO_DHT22)) { + if (dht_setup(i, mpin)) { + dht_flg = 1; + mpin = GPIO_DHT11; + } else { + mpin = 0; + } + } +#endif // USE_DHT + } + if (mpin) { + pin[mpin] = i; + } + } + + if (2 == pin[GPIO_TXD]) { + Serial.set_tx(2); + } + + analogWriteRange(PWM_RANGE); // Default is 1023 (Arduino.h) + analogWriteFreq(PWM_FREQ); // Default is 1000 (core_esp8266_wiring_pwm.c) + + Maxdevice = 1; + if (SONOFF_BRIDGE == sysCfg.module) { + Baudrate = 19200; + } + if (SONOFF_DUAL == sysCfg.module) { + Maxdevice = 2; + Baudrate = 19200; + } + else if (CH4 == sysCfg.module) { + Maxdevice = 4; + Baudrate = 19200; + } + else if (SONOFF_SC == sysCfg.module) { + Maxdevice = 0; + Baudrate = 19200; + } + else if (SONOFF_BN == sysCfg.module) { + sfl_flg = 1; + } + else if (SONOFF_LED == sysCfg.module) { + sfl_flg = 2; + } + else { + Maxdevice = 0; + for (byte i = 0; i < 4; i++) { + if (pin[GPIO_REL1 +i] < 99) { + pinMode(pin[GPIO_REL1 +i], OUTPUT); + Maxdevice++; + } +// if (pin[GPIO_KEY1 +i] < 99) { +// pinMode(pin[GPIO_KEY1 +i], INPUT_PULLUP); +// } + } + } + for (byte i = 0; i < 4; i++) { + if (pin[GPIO_KEY1 +i] < 99) { + pinMode(pin[GPIO_KEY1 +i], INPUT_PULLUP); + } + if (pin[GPIO_LED1 +i] < 99) { + pinMode(pin[GPIO_LED1 +i], OUTPUT); + digitalWrite(pin[GPIO_LED1 +i], led_inverted[i]); + } + if (pin[GPIO_SWT1 +i] < 99) { + swt_flg = 1; + pinMode(pin[GPIO_SWT1 +i], INPUT_PULLUP); + lastwallswitch[i] = digitalRead(pin[GPIO_SWT1 +i]); // set global now so doesn't change the saved power state on first switch check + } + } + + if (sfl_flg) { // Sonoff Led or BN-SZ01 + pwm_idxoffset = sfl_flg; // 1 for BN-SZ01, 2 for Sonoff Led + pin[GPIO_WS2812] = 99; // I do not allow both Sonoff Led AND WS2812 led + if (!my_module.gp.io[4]) { + pinMode(4, OUTPUT); // Stop floating outputs + digitalWrite(4, LOW); + } + if (!my_module.gp.io[5]) { + pinMode(5, OUTPUT); // Stop floating outputs + digitalWrite(5, LOW); + } + if (!my_module.gp.io[14]) { + pinMode(14, OUTPUT); // Stop floating outputs + digitalWrite(14, LOW); + } + sl_init(); + } + for (byte i = pwm_idxoffset; i < 5; i++) { + if (pin[GPIO_PWM1 +i] < 99) { + pwm_flg = 1; + pinMode(pin[GPIO_PWM1 +i], OUTPUT); + analogWrite(pin[GPIO_PWM1 +i], sysCfg.pwmvalue[i]); + } + } + + if (EXS_RELAY == sysCfg.module) { + setLatchingRelay(0,2); + setLatchingRelay(1,2); + } + setLed(sysCfg.ledstate &8); + +#ifdef USE_WS2812 + if (pin[GPIO_WS2812] < 99) { + Maxdevice++; + ws2812_init(Maxdevice); + } +#endif // USE_WS2812 + +#ifdef USE_IR_REMOTE + if (pin[GPIO_IRSEND] < 99) { + ir_send_init(); + } +#endif // USE_IR_REMOTE + + counter_init(); + + hlw_flg = ((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)); + if (hlw_flg) { + hlw_init(); + } + +#ifdef USE_DHT + if (dht_flg) { + dht_init(); + } +#endif // USE_DHT + +#ifdef USE_DS18x20 + if (pin[GPIO_DSB] < 99) { + ds18x20_init(); + } +#endif // USE_DS18x20 + +#ifdef USE_I2C + i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99)); + if (i2c_flg) { + Wire.begin(pin[GPIO_I2C_SDA], pin[GPIO_I2C_SCL]); + } +#endif // USE_I2C +} + +extern "C" { +extern struct rst_info resetInfo; +} + +void setup() +{ + char log[LOGSZ]; + byte idx; + + Serial.begin(Baudrate); + delay(10); + Serial.println(); + seriallog_level = LOG_LEVEL_INFO; // Allow specific serial messages until config loaded + + snprintf_P(Version, sizeof(Version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff); + if (VERSION & 0x1f) { + idx = strlen(Version); + Version[idx] = 96 + (VERSION & 0x1f); + Version[idx +1] = 0; + } + CFG_Load(); + CFG_Delta(); + + osw_init(); + + sysCfg.bootcount++; + snprintf_P(log, sizeof(log), PSTR("APP: Bootcount %d"), sysCfg.bootcount); + addLog(LOG_LEVEL_DEBUG, log); + stop_flash_rotate = sysCfg.flag.stop_flash_rotate; + savedatacounter = sysCfg.savedata; + seriallog_timer = SERIALLOG_TIMER; + seriallog_level = sysCfg.seriallog_level; +#ifndef USE_EMULATION + sysCfg.flag.emulation = 0; +#endif // USE_EMULATION + syslog_level = (sysCfg.flag.emulation) ? 0 : sysCfg.syslog_level; + sleep = sysCfg.sleep; + + GPIO_init(); + + if (Serial.baudRate() != Baudrate) { + if (seriallog_level) { + snprintf_P(log, sizeof(log), PSTR("APP: Set baudrate to %d"), Baudrate); + addLog(LOG_LEVEL_INFO, log); + } + delay(100); + Serial.flush(); + Serial.begin(Baudrate); + delay(10); + Serial.println(); + } + + if (strstr(sysCfg.hostname, "%")) { + strlcpy(sysCfg.hostname, WIFI_HOSTNAME, sizeof(sysCfg.hostname)); + snprintf_P(Hostname, sizeof(Hostname)-1, sysCfg.hostname, sysCfg.mqtt_topic, ESP.getChipId() & 0x1FFF); + } else { + snprintf_P(Hostname, sizeof(Hostname)-1, sysCfg.hostname); + } + WIFI_Connect(); + + getClient(MQTTClient, sysCfg.mqtt_client, sizeof(MQTTClient)); + + if (MOTOR == sysCfg.module) { + sysCfg.poweronstate = 1; // Needs always on else in limbo! + } + if (4 == sysCfg.poweronstate) { // Allways on + setRelay(power); + } else { + if ((resetInfo.reason == REASON_DEFAULT_RST) || (resetInfo.reason == REASON_EXT_SYS_RST)) { + switch (sysCfg.poweronstate) { + case 0: // All off + power = 0; + setRelay(power); + break; + case 1: // All on + power = (1 << Maxdevice) -1; + setRelay(power); + break; + case 2: // All saved state toggle + power = sysCfg.power & ((1 << Maxdevice) -1) ^ 0xFF; + if (sysCfg.flag.savestate) { + setRelay(power); + } + break; + case 3: // All saved state + power = sysCfg.power & ((1 << Maxdevice) -1); + if (sysCfg.flag.savestate) { + setRelay(power); + } + break; + } + } else { + power = sysCfg.power & ((1 << Maxdevice) -1); + if (sysCfg.flag.savestate) { + setRelay(power); + } + } + } + blink_powersave = power; + + if (SONOFF_SC == sysCfg.module) { + sc_init(); + } + + rtc_init(); + + snprintf_P(log, sizeof(log), PSTR("APP: Project %s %s (Topic %s, Fallback %s, GroupTopic %s) Version %s"), + PROJECT, sysCfg.friendlyname[0], sysCfg.mqtt_topic, MQTTClient, sysCfg.mqtt_grptopic, Version); + addLog(LOG_LEVEL_INFO, log); +} + +void loop() +{ + osw_loop(); + +#ifdef USE_WEBSERVER + pollDnsWeb(); +#endif // USE_WEBSERVER + +#ifdef USE_EMULATION + if (sysCfg.flag.emulation) { + pollUDP(); + } +#endif // USE_EMULATION + + if (millis() >= timerxs) { + stateloop(); + } + if (sysCfg.flag.mqtt_enabled) { + mqttClient.loop(); + } + if (Serial.available()){ + serial(); + } + +// yield(); // yield == delay(0), delay contains yield, auto yield in loop + delay(sleep); // https://github.com/esp8266/Arduino/issues/2021 +} diff --git a/webserver.ino b/webserver.ino new file mode 100644 index 000000000..dce438ca6 --- /dev/null +++ b/webserver.ino @@ -0,0 +1,1618 @@ +/* + webserver.ino - webserver for Sonoff-Tasmota + + Copyright (C) 2017 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 . +*/ + +#ifdef USE_WEBSERVER +/*********************************************************************************************\ + * Web server and WiFi Manager + * + * Enables configuration and reconfiguration of WiFi credentials using a Captive Portal + * Based on source by AlexT (https://github.com/tzapu) +\*********************************************************************************************/ + +#define STR_HELPER(x) #x +#define STR(x) STR_HELPER(x) + +const char HTTP_HEAD[] PROGMEM = + "" + "" + "" + "" + "{v}" + + "" + + "" + + "" + "" + "
" + "

{ha} Module

{h}

"; +const char HTTP_SCRIPT_CONSOL[] PROGMEM = + "var sn=0;" // Scroll position + "var id=99;" // Get most of weblog initially + "function l(p){" // Console log and command service + "var c,o,t;" + "clearTimeout(lt);" + "o='';" + "t=document.getElementById('t1');" + "if(p==1){" + "c=document.getElementById('c1');" + "o='&c1='+encodeURI(c.value);" + "c.value='';" + "t.scrollTop=sn;" + "}" + "if(t.scrollTop>=sn){" // User scrolled back so no updates + "if(x!=null){x.abort();}" // Abort if no response within 2 seconds (happens on restart 1) + "x=new XMLHttpRequest();" + "x.onreadystatechange=function(){" + "if(x.readyState==4&&x.status==200){" + "var z,d;" + "d=x.responseXML;" + "id=d.getElementsByTagName('i')[0].childNodes[0].nodeValue;" + "if(d.getElementsByTagName('j')[0].childNodes[0].nodeValue==0){t.value='';}" + "z=d.getElementsByTagName('l')[0].childNodes;" + "if(z.length>0){t.value+=z[0].nodeValue;}" + "t.scrollTop=99999;" + "sn=t.scrollTop;" + "}" + "};" + "x.open('GET','ax?c2='+id+o,true);" + "x.send();" + "}" + "lt=setTimeout(l,2345);" + "return false;" + "}" + ""; +const char HTTP_SCRIPT_MODULE[] PROGMEM = + "var os;" + "function sk(s,g){" + "var o=os.replace(\"value='\"+s+\"'\",\"selected value='\"+s+\"'\");" + "document.getElementById('g'+g).innerHTML=o;" + "}" + "function sl(){" + "var o0=\""; +const char HTTP_MSG_RSTRT[] PROGMEM = + "
Device will restart in a few seconds

"; +const char HTTP_BTN_MENU1[] PROGMEM = + "
" + "
" + "
" + "
"; +const char HTTP_BTN_RSTRT[] PROGMEM = + "
"; +const char HTTP_BTN_MENU2[] PROGMEM = + "
" + "
"; +const char HTTP_BTN_MENU3[] PROGMEM = + "
" +#ifdef USE_DOMOTICZ + "
" +#endif // USE_DOMOTICZ + ""; +const char HTTP_BTN_MENU4[] PROGMEM = + "
" + "
" + "
" + "
" + "
"; +const char HTTP_BTN_MAIN[] PROGMEM = + "

"; +const char HTTP_BTN_CONF[] PROGMEM = + "

"; +const char HTTP_FORM_MODULE[] PROGMEM = + "
 Module parameters 
" + "" + "
Module type ({mt})
" + "
AP1 SSId (" STA_SSID1 ")

" + "
AP1 Password

" + "
AP2 SSId (" STA_SSID2 ")

" + "
AP2 Password

" + "
Hostname (" WIFI_HOSTNAME ")

"; +const char HTTP_FORM_MQTT[] PROGMEM = + "
 MQTT parameters " + "" + "
Host (" MQTT_HOST ")

" + "
Port (" STR(MQTT_PORT) ")

" + "
Client Id ({m0})

" + "
User (" MQTT_USER ")

" + "
Password

" + "
Topic = %topic% (" MQTT_TOPIC ")

" + "
Full Topic (" MQTT_FULLTOPIC ")

"; +const char HTTP_FORM_LOG1[] PROGMEM = + "
 Logging parameters " + ""; +const char HTTP_FORM_LOG2[] PROGMEM = + "
{b0}log level ({b1})

"; +const char HTTP_FORM_LOG3[] PROGMEM = + "
Syslog host (" SYS_LOG_HOST ")

" + "
Syslog port (" STR(SYS_LOG_PORT) ")

" + "
Telemetric period (" STR(TELE_PERIOD) ")

"; +const char HTTP_FORM_OTHER[] PROGMEM = + "
 Other parameters " + "" + "
Web Admin Password

" + "
MQTT enable
"; +const char HTTP_FORM_OTHER2[] PROGMEM = + "
Friendly Name {1 ({2)

"; +#ifdef USE_EMULATION +const char HTTP_FORM_OTHER3a[] PROGMEM = + "
 Emulation "; +const char HTTP_FORM_OTHER3b[] PROGMEM = + "
{3{4"; +#endif // USE_EMULATION +const char HTTP_FORM_END[] PROGMEM = + "
"; +const char HTTP_FORM_RST[] PROGMEM = + "
" + "
 Restore configuration "; +const char HTTP_FORM_UPG[] PROGMEM = + "
" + "
 Upgrade by web server " + "
" + "
OTA Url

" + "
" + "


" + "
 Upgrade by file upload "; +const char HTTP_FORM_RST_UPG[] PROGMEM = + "
" + "

" + "
" + "
" + "
" + ""; +const char HTTP_FORM_CMND[] PROGMEM = + "


" + "
" + "
" +// "
" + "
"; +const char HTTP_TABLE100[] PROGMEM = + ""; +const char HTTP_COUNTER[] PROGMEM = + "
"; +const char HTTP_SNS_TEMP[] PROGMEM = + ""; +const char HTTP_SNS_HUM[] PROGMEM = + ""; +const char HTTP_SNS_PRESSURE[] PROGMEM = + ""; +const char HTTP_SNS_LIGHT[] PROGMEM = + ""; +const char HTTP_SNS_NOISE[] PROGMEM = + ""; +const char HTTP_SNS_DUST[] PROGMEM = + ""; +const char HTTP_END[] PROGMEM = + "" + "" + ""; + +const char HDR_CTYPE_PLAIN[] PROGMEM = "text/plain"; +const char HDR_CTYPE_HTML[] PROGMEM = "text/html"; +const char HDR_CTYPE_XML[] PROGMEM = "text/xml"; +const char HDR_CTYPE_JSON[] PROGMEM = "application/json"; +const char HDR_CTYPE_STREAM[] PROGMEM = "application/octet-stream"; + +#define DNS_PORT 53 +enum http_t {HTTP_OFF, HTTP_USER, HTTP_ADMIN, HTTP_MANAGER}; + +DNSServer *dnsServer; +ESP8266WebServer *webServer; + +boolean _removeDuplicateAPs = true; +int _minimumQuality = -1; +uint8_t _httpflag = HTTP_OFF; +uint8_t _uploaderror = 0; +uint8_t _uploadfiletype; +uint8_t _colcount; + +void startWebserver(int type, IPAddress ipweb) +{ + char log[LOGSZ]; + + if (!_httpflag) { + if (!webServer) { + webServer = new ESP8266WebServer((HTTP_MANAGER==type)?80:WEB_PORT); + webServer->on("/", handleRoot); + webServer->on("/cn", handleConfig); + webServer->on("/md", handleModule); + webServer->on("/w1", handleWifi1); + webServer->on("/w0", handleWifi0); + if (sysCfg.flag.mqtt_enabled) { + webServer->on("/mq", handleMqtt); +#ifdef USE_DOMOTICZ + webServer->on("/dm", handleDomoticz); +#endif // USE_DOMOTICZ + } + webServer->on("/lg", handleLog); + webServer->on("/co", handleOther); + webServer->on("/dl", handleDownload); + webServer->on("/sv", handleSave); + webServer->on("/rs", handleRestore); + webServer->on("/rt", handleReset); + webServer->on("/up", handleUpgrade); + webServer->on("/u1", handleUpgradeStart); // OTA + webServer->on("/u2", HTTP_POST, handleUploadDone, handleUploadLoop); + webServer->on("/cm", handleCmnd); + webServer->on("/cs", handleConsole); + webServer->on("/ax", handleAjax); + webServer->on("/ay", handleAjax2); + webServer->on("/in", handleInfo); + webServer->on("/rb", handleRestart); + webServer->on("/fwlink", handleRoot); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler. +#ifdef USE_EMULATION + if (EMUL_WEMO == sysCfg.flag.emulation) { + webServer->on("/upnp/control/basicevent1", HTTP_POST, handleUPnPevent); + webServer->on("/eventservice.xml", handleUPnPservice); + webServer->on("/setup.xml", handleUPnPsetupWemo); + } + if (EMUL_HUE == sysCfg.flag.emulation) { + webServer->on("/description.xml", handleUPnPsetupHue); + } +#endif // USE_EMULATION + webServer->onNotFound(handleNotFound); + } + logajaxflg = 0; + webServer->begin(); // Web server start + } + if (_httpflag != type) { + snprintf_P(log, sizeof(log), PSTR("HTTP: Webserver active on %s%s with IP address %s"), + Hostname, (mDNSbegun)?".local":"", ipweb.toString().c_str()); + addLog(LOG_LEVEL_INFO, log); + } + if (type) _httpflag = type; +} + +void stopWebserver() +{ + if (_httpflag) { + webServer->close(); + _httpflag = HTTP_OFF; + addLog_P(LOG_LEVEL_INFO, PSTR("HTTP: Webserver stopped")); + } +} + +void beginWifiManager() +{ + // setup AP + if ((WL_CONNECTED == WiFi.status()) && (static_cast(WiFi.localIP()) != 0)) { + WiFi.mode(WIFI_AP_STA); + addLog_P(LOG_LEVEL_DEBUG, PSTR("Wifimanager: Set AccessPoint and keep Station")); + } else { + WiFi.mode(WIFI_AP); + addLog_P(LOG_LEVEL_DEBUG, PSTR("Wifimanager: Set AccessPoint")); + } + + stopWebserver(); + + dnsServer = new DNSServer(); + WiFi.softAP(Hostname); + delay(500); // Without delay I've seen the IP address blank + /* Setup the DNS server redirecting all the domains to the apIP */ + dnsServer->setErrorReplyCode(DNSReplyCode::NoError); + dnsServer->start(DNS_PORT, "*", WiFi.softAPIP()); + + startWebserver(HTTP_MANAGER, WiFi.softAPIP()); +} + +void pollDnsWeb() +{ + if (dnsServer) { + dnsServer->processNextRequest(); + } + if (webServer) { + webServer->handleClient(); + } +} + +void setHeader() +{ + webServer->sendHeader(F("Cache-Control"), F("no-cache, no-store, must-revalidate")); + webServer->sendHeader(F("Pragma"), F("no-cache")); + webServer->sendHeader(F("Expires"), F("-1")); +} + +void showPage(String &page) +{ + if((HTTP_ADMIN == _httpflag) && (sysCfg.web_password[0] != 0) && !webServer->authenticate(WEB_USERNAME, sysCfg.web_password)) { + return webServer->requestAuthentication(); + } + page.replace(F("{ha}"), my_module.name); + page.replace(F("{h}"), sysCfg.friendlyname[0]); + if (HTTP_MANAGER == _httpflag) { + if (WIFI_configCounter()) { + page.replace(F(""), F("")); + page += FPSTR(HTTP_COUNTER); + } + } + page += FPSTR(HTTP_END); + setHeader(); + webServer->send(200, FPSTR(HDR_CTYPE_HTML), page); +} + +void handleRoot() +{ + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Root")); + + if (captivePortal()) { // If captive portal redirect instead of displaying the page. + return; + } + + if (HTTP_MANAGER == _httpflag) { + handleWifi0(); + } else { + char stemp[10], line[100]; + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Main menu")); + page.replace(F(""), F("")); + + page += F("
"); + if (Maxdevice) { + if (sfl_flg) { + snprintf_P(line, sizeof(line), PSTR(""), + sysCfg.led_dimmer[0]); + page += line; + } + page += FPSTR(HTTP_TABLE100); + page += F("
"); + for (byte idx = 1; idx <= Maxdevice; idx++) { + snprintf_P(stemp, sizeof(stemp), PSTR(" %d"), idx); + snprintf_P(line, sizeof(line), PSTR(""), + 100 / Maxdevice, idx, (Maxdevice > 1) ? stemp : ""); + page += line; + } + page += F("
%s Temperature%s°%c
%s Humidity%s%
%s Pressure%s hPa
%s Light%d%
%s Noise%d%
%s Air quality%d%
"); + } + if (SONOFF_BRIDGE == sysCfg.module) { + page += FPSTR(HTTP_TABLE100); + page += F(""); + byte idx = 0; + for (byte i = 0; i < 4; i++) { + if (idx > 0) { + page += F(""); + } + for (byte j = 0; j < 4; j++) { + idx++; + snprintf_P(line, sizeof(line), PSTR(""), + idx, idx); + page += line; + } + } + page += F(""); + } + + if (HTTP_ADMIN == _httpflag) { + page += FPSTR(HTTP_BTN_MENU1); + page += FPSTR(HTTP_BTN_RSTRT); + } + showPage(page); + } +} + +void handleAjax2() +{ + char svalue[16]; + + if (strlen(webServer->arg("o").c_str())) { + do_cmnd_power(atoi(webServer->arg("o").c_str()), 2); + } + if (strlen(webServer->arg("d").c_str())) { + snprintf_P(svalue, sizeof(svalue), PSTR("dimmer %s"), webServer->arg("d").c_str()); + do_cmnd(svalue); + } + if (strlen(webServer->arg("k").c_str())) { + snprintf_P(svalue, sizeof(svalue), PSTR("rfkey%s"), webServer->arg("k").c_str()); + do_cmnd(svalue); + } + + String tpage = ""; + tpage += counter_webPresent(); + if (hlw_flg) { + tpage += hlw_webPresent(); + } + if (SONOFF_SC == sysCfg.module) { + tpage += sc_webPresent(); + } +#ifdef USE_DS18B20 + if (pin[GPIO_DSB] < 99) { + tpage += dsb_webPresent(); + } +#endif // USE_DS18B20 +#ifdef USE_DS18x20 + if (pin[GPIO_DSB] < 99) { + tpage += ds18x20_webPresent(); + } +#endif // USE_DS18x20 +#ifdef USE_DHT + if (dht_flg) { + tpage += dht_webPresent(); + } +#endif // USE_DHT +#ifdef USE_I2C + if (i2c_flg) { +#ifdef USE_SHT + tpage += sht_webPresent(); +#endif +#ifdef USE_HTU + tpage += htu_webPresent(); +#endif +#ifdef USE_BMP + tpage += bmp_webPresent(); +#endif +#ifdef USE_BH1750 + tpage += bh1750_webPresent(); +#endif + } +#endif // USE_I2C + String page = ""; + if (tpage.length() > 0) { + page += FPSTR(HTTP_TABLE100); + page += tpage; + page += F(""); + } + char line[120]; + if (Maxdevice) { + page += FPSTR(HTTP_TABLE100); + page += F(""); + for (byte idx = 1; idx <= Maxdevice; idx++) { + snprintf_P(line, sizeof(line), PSTR("
%s
"), + 100 / Maxdevice, 70 - (Maxdevice * 8), getStateText(bitRead(power, idx -1))); + page += line; + } + page += F(""); + } +/* + * Will interrupt user action when selected + if (sfl_flg) { + snprintf_P(line, sizeof(line), PSTR(""), + sysCfg.led_dimmer[0]); + page += line; + } +*/ + webServer->send(200, FPSTR(HDR_CTYPE_HTML), page); +} + +boolean httpUser() +{ + boolean status = (HTTP_USER == _httpflag); + if (status) { + handleRoot(); + } + return status; +} + +void handleConfig() +{ + if (httpUser()) { + return; + } + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Config")); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Configuration")); + page += FPSTR(HTTP_BTN_MENU2); + if (sysCfg.flag.mqtt_enabled) { + page += FPSTR(HTTP_BTN_MENU3); + } + page += FPSTR(HTTP_BTN_MENU4); + page += FPSTR(HTTP_BTN_MAIN); + showPage(page); +} + +boolean inModule(byte val, uint8_t *arr) +{ + int offset = 0; + + if (!val) { + return false; // None + } +#ifndef USE_I2C + if (GPIO_I2C_SCL == val) { + return true; + } + if (GPIO_I2C_SDA == val) { + return true; + } +#endif +#ifndef USE_WS2812 + if (GPIO_WS2812 == val) { + return true; + } +#endif +#ifndef USE_IR_REMOTE + if (GPIO_IRSEND == val) { + return true; + } +#endif + if (((val >= GPIO_REL1) && (val <= GPIO_REL4)) || ((val >= GPIO_LED1) && (val <= GPIO_LED4))) { + offset = 4; + } + if (((val >= GPIO_REL1_INV) && (val <= GPIO_REL4_INV)) || ((val >= GPIO_LED1_INV) && (val <= GPIO_LED4_INV))) { + offset = -4; + } + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + if (arr[i] == val) { + return true; + } + if (arr[i] == val + offset) { + return true; + } + } + return false; +} + +void handleModule() +{ + if (httpUser()) { + return; + } + char stemp[20], line[128]; + + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Module config")); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Config module")); + page += FPSTR(HTTP_FORM_MODULE); + + snprintf_P(stemp, sizeof(stemp), modules[MODULE].name); + page.replace(F("{mt}"), stemp); + + for (byte i = 0; i < MAXMODULE; i++) { + snprintf_P(stemp, sizeof(stemp), modules[i].name); + snprintf_P(line, sizeof(line), PSTR("%02d %s"), + (i == sysCfg.module) ? " selected" : "", i, i +1, stemp); + page += line; + } + page += F("
"); + + mytmplt cmodule; + memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule)); + + String func = FPSTR(HTTP_SCRIPT_MODULE); + for (byte j = 0; j < GPIO_SENSOR_END; j++) { + if (!inModule(j, cmodule.gp.io)) { + snprintf_P(stemp, sizeof(stemp), sensors[j]); + snprintf_P(line, sizeof(line), PSTR("-1'%d'>%02d %s-2"), j, j, stemp); + func += line; + } + } + func += F("\";os=o0.replace(/-1/g,\"
"); +#endif // USE_EMULATION + page += FPSTR(HTTP_FORM_END); + page += FPSTR(HTTP_BTN_CONF); + showPage(page); +} + +void handleDownload() +{ + if (httpUser()) { + return; + } + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Download config")); + + uint8_t buffer[sizeof(sysCfg)]; + + WiFiClient myClient = webServer->client(); + webServer->setContentLength(4096); + + char attachment[100]; + snprintf_P(attachment, sizeof(attachment), PSTR("attachment; filename=Config_%s_%s.dmp"), + sysCfg.friendlyname[0], Version); + webServer->sendHeader(F("Content-Disposition"), attachment); + webServer->send(200, FPSTR(HDR_CTYPE_STREAM), ""); + memcpy(buffer, &sysCfg, sizeof(sysCfg)); + buffer[0] = CONFIG_FILE_SIGN; + buffer[1] = (!CONFIG_FILE_XOR)?0:1; + if (buffer[1]) { + for (uint16_t i = 2; i < sizeof(buffer); i++) { + buffer[i] ^= (CONFIG_FILE_XOR +i); + } + } + myClient.write((const char*)buffer, sizeof(buffer)); +} + +void handleSave() +{ + if (httpUser()) { + return; + } + + char log[LOGSZ +20]; + char stemp[TOPSZ]; + char stemp2[TOPSZ]; + byte what = 0; + byte restart; + String result = ""; + + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Parameter save")); + + if (strlen(webServer->arg("w").c_str())) { + what = atoi(webServer->arg("w").c_str()); + } + switch (what) { + case 1: + strlcpy(sysCfg.hostname, (!strlen(webServer->arg("h").c_str())) ? WIFI_HOSTNAME : webServer->arg("h").c_str(), sizeof(sysCfg.hostname)); + if (strstr(sysCfg.hostname,"%")) { + strlcpy(sysCfg.hostname, WIFI_HOSTNAME, sizeof(sysCfg.hostname)); + } + strlcpy(sysCfg.sta_ssid[0], (!strlen(webServer->arg("s1").c_str())) ? STA_SSID1 : webServer->arg("s1").c_str(), sizeof(sysCfg.sta_ssid[0])); + strlcpy(sysCfg.sta_pwd[0], (!strlen(webServer->arg("p1").c_str())) ? STA_PASS1 : webServer->arg("p1").c_str(), sizeof(sysCfg.sta_pwd[0])); + strlcpy(sysCfg.sta_ssid[1], (!strlen(webServer->arg("s2").c_str())) ? STA_SSID2 : webServer->arg("s2").c_str(), sizeof(sysCfg.sta_ssid[1])); + strlcpy(sysCfg.sta_pwd[1], (!strlen(webServer->arg("p2").c_str())) ? STA_PASS2 : webServer->arg("p2").c_str(), sizeof(sysCfg.sta_pwd[1])); + snprintf_P(log, sizeof(log), PSTR("HTTP: Wifi Hostname %s, SSID1 %s, Password1 %s, SSID2 %s, Password2 %s"), + sysCfg.hostname, sysCfg.sta_ssid[0], sysCfg.sta_pwd[0], sysCfg.sta_ssid[1], sysCfg.sta_pwd[1]); + addLog(LOG_LEVEL_INFO, log); + result += F("
Trying to connect device to network
If it fails reconnect to try again"); + break; + case 2: + strlcpy(stemp, (!strlen(webServer->arg("mt").c_str())) ? MQTT_TOPIC : webServer->arg("mt").c_str(), sizeof(stemp)); + mqttfy(0, stemp); + strlcpy(stemp2, (!strlen(webServer->arg("mf").c_str())) ? MQTT_FULLTOPIC : webServer->arg("mf").c_str(), sizeof(stemp2)); + mqttfy(1,stemp2); + if ((strcmp(stemp, sysCfg.mqtt_topic)) || (strcmp(stemp2, sysCfg.mqtt_fulltopic))) { + mqtt_publish_topic_P(2, PSTR("LWT"), (sysCfg.flag.mqtt_offline) ? "Offline" : "", true); // Offline or remove previous retained topic + } + strlcpy(sysCfg.mqtt_topic, stemp, sizeof(sysCfg.mqtt_topic)); + strlcpy(sysCfg.mqtt_fulltopic, stemp2, sizeof(sysCfg.mqtt_fulltopic)); + strlcpy(sysCfg.mqtt_host, (!strlen(webServer->arg("mh").c_str())) ? MQTT_HOST : webServer->arg("mh").c_str(), sizeof(sysCfg.mqtt_host)); + sysCfg.mqtt_port = (!strlen(webServer->arg("ml").c_str())) ? MQTT_PORT : atoi(webServer->arg("ml").c_str()); + strlcpy(sysCfg.mqtt_client, (!strlen(webServer->arg("mc").c_str())) ? MQTT_CLIENT_ID : webServer->arg("mc").c_str(), sizeof(sysCfg.mqtt_client)); + strlcpy(sysCfg.mqtt_user, (!strlen(webServer->arg("mu").c_str())) ? MQTT_USER : (!strcmp(webServer->arg("mu").c_str(),"0")) ? "" : webServer->arg("mu").c_str(), sizeof(sysCfg.mqtt_user)); + strlcpy(sysCfg.mqtt_pwd, (!strlen(webServer->arg("mp").c_str())) ? MQTT_PASS : (!strcmp(webServer->arg("mp").c_str(),"0")) ? "" : webServer->arg("mp").c_str(), sizeof(sysCfg.mqtt_pwd)); + snprintf_P(log, sizeof(log), PSTR("HTTP: MQTT Host %s, Port %d, Client %s, User %s, Password %s, Topic %s, FullTopic %s"), + sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.mqtt_client, sysCfg.mqtt_user, sysCfg.mqtt_pwd, sysCfg.mqtt_topic, sysCfg.mqtt_fulltopic); + addLog(LOG_LEVEL_INFO, log); + break; + case 3: + sysCfg.seriallog_level = (!strlen(webServer->arg("ls").c_str())) ? SERIAL_LOG_LEVEL : atoi(webServer->arg("ls").c_str()); + sysCfg.weblog_level = (!strlen(webServer->arg("lw").c_str())) ? WEB_LOG_LEVEL : atoi(webServer->arg("lw").c_str()); + sysCfg.syslog_level = (!strlen(webServer->arg("ll").c_str())) ? SYS_LOG_LEVEL : atoi(webServer->arg("ll").c_str()); + syslog_level = sysCfg.syslog_level; + syslog_timer = 0; + strlcpy(sysCfg.syslog_host, (!strlen(webServer->arg("lh").c_str())) ? SYS_LOG_HOST : webServer->arg("lh").c_str(), sizeof(sysCfg.syslog_host)); + sysCfg.syslog_port = (!strlen(webServer->arg("lp").c_str())) ? SYS_LOG_PORT : atoi(webServer->arg("lp").c_str()); + sysCfg.tele_period = (!strlen(webServer->arg("lt").c_str())) ? TELE_PERIOD : atoi(webServer->arg("lt").c_str()); + snprintf_P(log, sizeof(log), PSTR("HTTP: Logging Seriallog %d, Weblog %d, Syslog %d, Host %s, Port %d, TelePeriod %d"), + sysCfg.seriallog_level, sysCfg.weblog_level, sysCfg.syslog_level, sysCfg.syslog_host, sysCfg.syslog_port, sysCfg.tele_period); + addLog(LOG_LEVEL_INFO, log); + break; +#ifdef USE_DOMOTICZ + case 4: + domoticz_saveSettings(); + break; +#endif // USE_DOMOTICZ + case 5: + strlcpy(sysCfg.web_password, (!strlen(webServer->arg("p1").c_str())) ? WEB_PASSWORD : (!strcmp(webServer->arg("p1").c_str(),"0")) ? "" : webServer->arg("p1").c_str(), sizeof(sysCfg.web_password)); + sysCfg.flag.mqtt_enabled = webServer->hasArg("b1"); +#ifdef USE_EMULATION + sysCfg.flag.emulation = (!strlen(webServer->arg("b2").c_str())) ? 0 : atoi(webServer->arg("b2").c_str()); +#endif // USE_EMULATION + strlcpy(sysCfg.friendlyname[0], (!strlen(webServer->arg("a1").c_str())) ? FRIENDLY_NAME : webServer->arg("a1").c_str(), sizeof(sysCfg.friendlyname[0])); + strlcpy(sysCfg.friendlyname[1], (!strlen(webServer->arg("a2").c_str())) ? FRIENDLY_NAME"2" : webServer->arg("a2").c_str(), sizeof(sysCfg.friendlyname[1])); + strlcpy(sysCfg.friendlyname[2], (!strlen(webServer->arg("a3").c_str())) ? FRIENDLY_NAME"3" : webServer->arg("a3").c_str(), sizeof(sysCfg.friendlyname[2])); + strlcpy(sysCfg.friendlyname[3], (!strlen(webServer->arg("a4").c_str())) ? FRIENDLY_NAME"4" : webServer->arg("a4").c_str(), sizeof(sysCfg.friendlyname[3])); + snprintf_P(log, sizeof(log), PSTR("HTTP: Other MQTT Enable %s, Emulation %d, Friendly Names %s, %s, %s and %s"), + getStateText(sysCfg.flag.mqtt_enabled), sysCfg.flag.emulation, sysCfg.friendlyname[0], sysCfg.friendlyname[1], sysCfg.friendlyname[2], sysCfg.friendlyname[3]); + addLog(LOG_LEVEL_INFO, log); + break; + case 6: + byte new_module = (!strlen(webServer->arg("mt").c_str())) ? MODULE : atoi(webServer->arg("mt").c_str()); + byte new_modflg = (sysCfg.module != new_module); + sysCfg.module = new_module; + mytmplt cmodule; + memcpy_P(&cmodule, &modules[sysCfg.module], sizeof(cmodule)); + String gpios = ""; + for (byte i = 0; i < MAX_GPIO_PIN; i++) { + if (new_modflg) { + sysCfg.my_module.gp.io[i] = 0; + } + if (GPIO_USER == cmodule.gp.io[i]) { + snprintf_P(stemp, sizeof(stemp), PSTR("g%d"), i); + sysCfg.my_module.gp.io[i] = (!strlen(webServer->arg(stemp).c_str())) ? 0 : atoi(webServer->arg(stemp).c_str()); + gpios += F(", GPIO"); gpios += String(i); gpios += F(" "); gpios += String(sysCfg.my_module.gp.io[i]); + } + } + snprintf_P(stemp, sizeof(stemp), modules[sysCfg.module].name); + snprintf_P(log, sizeof(log), PSTR("HTTP: %s Module%s"), stemp, gpios.c_str()); + addLog(LOG_LEVEL_INFO, log); + break; + } + + restart = (!strlen(webServer->arg("r").c_str())) ? 1 : atoi(webServer->arg("r").c_str()); + if (restart) { + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Save parameters")); + page += F("
Parameters saved
"); + page += result; + page += F("
"); + page += FPSTR(HTTP_MSG_RSTRT); + if (HTTP_MANAGER == _httpflag) { + _httpflag = HTTP_ADMIN; + } else { + page += FPSTR(HTTP_BTN_MAIN); + } + showPage(page); + + restartflag = 2; + } else { + handleConfig(); + } +} + +void handleReset() +{ + if (httpUser()) { + return; + } + + char svalue[16]; // was MESSZ + + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Reset parameters")); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Default parameters")); + page += F("
Parameters reset to default
"); + page += FPSTR(HTTP_MSG_RSTRT); + page += FPSTR(HTTP_BTN_MAIN); + showPage(page); + + snprintf_P(svalue, sizeof(svalue), PSTR("reset 1")); + do_cmnd(svalue); +} + +void handleRestore() +{ + if (httpUser()) { + return; + } + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Restore")); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Restore Configuration")); + page += FPSTR(HTTP_FORM_RST); + page += FPSTR(HTTP_FORM_RST_UPG); + page.replace(F("{r1}"), F("restore")); + page += FPSTR(HTTP_BTN_CONF); + showPage(page); + + _uploaderror = 0; + _uploadfiletype = 1; +} + +void handleUpgrade() +{ + if (httpUser()) { + return; + } + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Upgrade")); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Firmware upgrade")); + page += FPSTR(HTTP_FORM_UPG); + page.replace(F("{o1}"), sysCfg.otaUrl); + page += FPSTR(HTTP_FORM_RST_UPG); + page.replace(F("{r1}"), F("upgrade")); + page += FPSTR(HTTP_BTN_MAIN); + showPage(page); + + _uploaderror = 0; + _uploadfiletype = 0; +} + +void handleUpgradeStart() +{ + if (httpUser()) { + return; + } + char svalue[100]; // was MESSZ + + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Firmware upgrade start")); + WIFI_configCounter(); + + if (strlen(webServer->arg("o").c_str())) { + snprintf_P(svalue, sizeof(svalue), PSTR("otaurl %s"), webServer->arg("o").c_str()); + do_cmnd(svalue); + } + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Info")); + page += F("
Upgrade started ...
"); + page += FPSTR(HTTP_MSG_RSTRT); + page += FPSTR(HTTP_BTN_MAIN); + showPage(page); + + snprintf_P(svalue, sizeof(svalue), PSTR("upgrade 1")); + do_cmnd(svalue); +} + +void handleUploadDone() +{ + if (httpUser()) { + return; + } + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: File upload done")); + + char error[80]; + char log[LOGSZ]; + + WIFI_configCounter(); + restartflag = 0; + mqttcounter = 0; + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Info")); + page += F("
Upload "); + if (_uploaderror) { + page += F("failed

"); + switch (_uploaderror) { + case 1: strcpy_P(error, PSTR("No file selected")); break; + case 2: strcpy_P(error, PSTR("Not enough space")); break; + case 3: strcpy_P(error, PSTR("Magic byte is not 0xE9")); break; + case 4: strcpy_P(error, PSTR("IDE flash size larger than device flash size")); break; + case 5: strcpy_P(error, PSTR("Upload buffer miscompare")); break; + case 6: strcpy_P(error, PSTR("Upload failed. Enable logging 3")); break; + case 7: strcpy_P(error, PSTR("Upload aborted")); break; + case 8: strcpy_P(error, PSTR("File invalid")); break; + case 9: strcpy_P(error, PSTR("File too large")); break; + default: + snprintf_P(error, sizeof(error), PSTR("Upload error code %d"), _uploaderror); + } + page += error; + snprintf_P(log, sizeof(log), PSTR("Upload: %s"), error); + addLog(LOG_LEVEL_DEBUG, log); + stop_flash_rotate = sysCfg.flag.stop_flash_rotate; + } else { + page += F("successful

Device will restart in a few seconds"); + restartflag = 2; + } + page += F("

"); + page += FPSTR(HTTP_BTN_MAIN); + showPage(page); +} + +void handleUploadLoop() +{ + // Based on ESP8266HTTPUpdateServer.cpp uses ESP8266WebServer Parsing.cpp and Cores Updater.cpp (Update) + char log[LOGSZ]; + boolean _serialoutput = (LOG_LEVEL_DEBUG <= seriallog_level); + + if (HTTP_USER == _httpflag) { + return; + } + if (_uploaderror) { + if (!_uploadfiletype) { + Update.end(); + } + return; + } + + HTTPUpload& upload = webServer->upload(); + + if (UPLOAD_FILE_START == upload.status) { + restartflag = 60; + if (0 == upload.filename.c_str()[0]) { + _uploaderror = 1; + return; + } + CFG_Save(1); // Free flash for upload + snprintf_P(log, sizeof(log), PSTR("Upload: File %s ..."), upload.filename.c_str()); + addLog(LOG_LEVEL_INFO, log); + if (!_uploadfiletype) { + mqttcounter = 60; +#ifdef USE_EMULATION + UDP_Disconnect(); +#endif // USE_EMULATION + if (sysCfg.flag.mqtt_enabled) { + mqttClient.disconnect(); + } + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if (!Update.begin(maxSketchSpace)) { //start with max available size + _uploaderror = 2; + return; + } + } + _colcount = 0; + } else if (!_uploaderror && (UPLOAD_FILE_WRITE == upload.status)) { + if (0 == upload.totalSize) + { + if (_uploadfiletype) { + if (upload.buf[0] != CONFIG_FILE_SIGN) { + _uploaderror = 8; + return; + } + if (upload.currentSize > sizeof(sysCfg)) { + _uploaderror = 9; + return; + } + } else { + if (upload.buf[0] != 0xE9) { + _uploaderror = 3; + return; + } + uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4); + if(bin_flash_size > ESP.getFlashChipRealSize()) { + _uploaderror = 4; + return; + } + upload.buf[2] = 3; // Force DOUT - ESP8285 + } + } + if (_uploadfiletype) { // config + if (!_uploaderror) { + if (upload.buf[1]) { + for (uint16_t i = 2; i < upload.currentSize; i++) { + upload.buf[i] ^= (CONFIG_FILE_XOR +i); + } + } + CFG_DefaultSet2(); + memcpy((char*)&sysCfg +16, upload.buf +16, upload.currentSize -16); + memcpy((char*)&sysCfg +8, upload.buf +8, 4); // Restore version and auto upgrade + } + } else { // firmware + if (!_uploaderror && (Update.write(upload.buf, upload.currentSize) != upload.currentSize)) { + _uploaderror = 5; + return; + } + if (_serialoutput) { + Serial.printf("."); + _colcount++; + if (!(_colcount % 80)) { + Serial.println(); + } + } + } + } else if(!_uploaderror && (UPLOAD_FILE_END == upload.status)) { + if (_serialoutput && (_colcount % 80)) { + Serial.println(); + } + if (!_uploadfiletype) { + if (!Update.end(true)) { // true to set the size to the current progress + if (_serialoutput) { + Update.printError(Serial); + } + _uploaderror = 6; + return; + } + } + if (!_uploaderror) { + snprintf_P(log, sizeof(log), PSTR("Upload: Successful %u bytes. Restarting"), upload.totalSize); + addLog(LOG_LEVEL_INFO, log); + } + } else if (UPLOAD_FILE_ABORTED == upload.status) { + restartflag = 0; + mqttcounter = 0; + _uploaderror = 7; + if (!_uploadfiletype) { + Update.end(); + } + } + delay(0); +} + +void handleCmnd() +{ + if (httpUser()) { + return; + } + char svalue[INPUT_BUFFER_SIZE]; // big to serve Backlog + + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Command")); + + uint8_t valid = 1; + if (sysCfg.web_password[0] != 0) { + if (!(!strcmp(webServer->arg("user").c_str(),WEB_USERNAME) && !strcmp(webServer->arg("password").c_str(),sysCfg.web_password))) { + valid = 0; + } + } + + String message = ""; + if (valid) { + byte curridx = logidx; + if (strlen(webServer->arg("cmnd").c_str())) { +// snprintf_P(svalue, sizeof(svalue), webServer->arg("cmnd").c_str()); + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), webServer->arg("cmnd").c_str()); + byte syslog_now = syslog_level; + syslog_level = 0; // Disable UDP syslog to not trigger hardware WDT + do_cmnd(svalue); + syslog_level = syslog_now; + } + + if (logidx != curridx) { + byte counter = curridx; + do { + if (Log[counter].length()) { + if (message.length()) { + message += F("\n"); + } + if (sysCfg.flag.mqtt_enabled) { + // [14:49:36 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}] > [RESULT = {"POWER":"OFF"}] +// message += Log[counter].substring(17 + strlen(PUB_PREFIX) + strlen(sysCfg.mqtt_topic)); + message += Log[counter].substring(Log[counter].lastIndexOf("/",Log[counter].indexOf("="))+1); + } else { + // [14:49:36 RSLT: RESULT = {"POWER":"OFF"}] > [RESULT = {"POWER":"OFF"}] + message += Log[counter].substring(Log[counter].indexOf(": ")+2); + } + } + counter++; + if (counter > MAX_LOG_LINES -1) { + counter = 0; + } + } while (counter != logidx); + } else { + message = F("Enable weblog 2 if response expected\n"); + } + } else { + message = F("Need user=&password=\n"); + } + webServer->send(200, FPSTR(HDR_CTYPE_PLAIN), message); +} + +void handleConsole() +{ + if (httpUser()) { + return; + } + + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Console")); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Console")); + page.replace(F(""), FPSTR(HTTP_SCRIPT_CONSOL)); + page.replace(F(""), F("")); + page += FPSTR(HTTP_FORM_CMND); + page += FPSTR(HTTP_BTN_MAIN); + showPage(page); +} + +void handleAjax() +{ + if (httpUser()) { + return; + } + char log[LOGSZ]; + char svalue[INPUT_BUFFER_SIZE]; // big to serve Backlog + byte cflg = 1; + byte counter = 99; + + if (strlen(webServer->arg("c1").c_str())) { + snprintf_P(svalue, sizeof(svalue), PSTR("%s"), webServer->arg("c1").c_str()); + snprintf_P(log, sizeof(log), PSTR("CMND: %s"), svalue); + addLog(LOG_LEVEL_INFO, log); + byte syslog_now = syslog_level; + syslog_level = 0; // Disable UDP syslog to not trigger hardware WDT + do_cmnd(svalue); + syslog_level = syslog_now; + } + + if (strlen(webServer->arg("c2").c_str())) { + counter = atoi(webServer->arg("c2").c_str()); + } + + String message = F(""); + message += String(logidx); + message += F(""); + message += String(logajaxflg); + if (!logajaxflg) { + counter = 99; + logajaxflg = 1; + } + message += F(""); + if (counter != logidx) { + if (99 == counter) { + counter = logidx; + cflg = 0; + } + do { + if (Log[counter].length()) { + if (cflg) { + message += F("\n"); + } else { + cflg = 1; + } + message += Log[counter]; + } + counter++; + if (counter > MAX_LOG_LINES -1) { + counter = 0; + } + } while (counter != logidx); + } + message += F(""); + webServer->send(200, FPSTR(HDR_CTYPE_XML), message); +} + +void handleInfo() +{ + if (httpUser()) { + return; + } + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Info")); + + char stopic[TOPSZ]; + + int freeMem = ESP.getFreeHeap(); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Information")); +// page += F("
 Information "); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); +// page += F(""); + page += F(""); + snprintf_P(stopic, sizeof(stopic), PSTR(" at %X"), CFG_Address()); + page += F(""); + page += F(""); + page += F(""); + for (byte i = 0; i < Maxdevice; i++) { + page += F(""); + } + page += F(""); + page += F(""); + page += F(""); + if (static_cast(WiFi.localIP()) != 0) { + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + } + if (static_cast(WiFi.softAPIP()) != 0) { + page += F(""); + page += F(""); + page += F(""); + } + page += F(""); + if (sysCfg.flag.mqtt_enabled) { + page += F(""); + page += F(""); + page += F(""); + page += F(""); +// page += F(""); + page += F(""); + page += F(""); + + getTopic_P(stopic, 0, sysCfg.mqtt_topic, ""); + page += F(""); + + } else { + page += F(""); + } + page += F(""); + page += F(""); + + page += F(""); + page += F(""); + + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F(""); + page += F("
Program version"); page += Version; page += F("
Build Date & Time"); page += getBuildDateTime(); page += F("
Core/SDK version"); page += ESP.getCoreVersion(); page += F("/"); page += String(ESP.getSdkVersion()); page += F("
Boot version"); page += String(ESP.getBootVersion()); page += F("
Uptime"); page += String(uptime); page += F(" Hours
Flash write count"); page += String(sysCfg.saveFlag); page += stopic; page += F("
Boot count"); page += String(sysCfg.bootcount); page += F("
Reset reason"); page += getResetReason(); page += F("
Friendly name "); + page += i +1; + page += F(""); page += sysCfg.friendlyname[i]; page += F("
 
AP"); page += String(sysCfg.sta_active +1); + page += F(" SSId (RSSI)"); page += sysCfg.sta_ssid[sysCfg.sta_active]; page += F(" ("); page += WIFI_getRSSIasQuality(WiFi.RSSI()); page += F("%)
Hostname"); page += Hostname; page += F("
IP address"); page += WiFi.localIP().toString(); page += F("
Gateway"); page += IPAddress(sysCfg.ip_address[1]).toString(); page += F("
Subnet mask"); page += IPAddress(sysCfg.ip_address[2]).toString(); page += F("
DNS server"); page += IPAddress(sysCfg.ip_address[3]).toString(); page += F("
MAC address"); page += WiFi.macAddress(); page += F("
AP IP address"); page += WiFi.softAPIP().toString(); page += F("
AP Gateway"); page += WiFi.softAPIP().toString(); page += F("
AP MAC address"); page += WiFi.softAPmacAddress(); page += F("
 
MQTT Host"); page += sysCfg.mqtt_host; page += F("
MQTT Port"); page += String(sysCfg.mqtt_port); page += F("
MQTT Client &
 Fallback Topic
"); page += MQTTClient; page += F("
MQTT User"); page += sysCfg.mqtt_user; page += F("
MQTT Password"); page += sysCfg.mqtt_pwd; page += F("
MQTT Topic"); page += sysCfg.mqtt_topic; page += F("
MQTT Group Topic"); page += sysCfg.mqtt_grptopic; page += F("
MQTT Full Topic"); page += stopic; page += F("
MQTTDisabled
 
Emulation"); +#ifdef USE_EMULATION + if (EMUL_WEMO == sysCfg.flag.emulation) { + page += F("Belkin WeMo"); + } + else if (EMUL_HUE == sysCfg.flag.emulation) { + page += F("Hue Bridge"); + } + else { + page += F("None"); + } +#else + page += F("Disabled"); +#endif // USE_EMULATION + page += F("
mDNS Discovery"); +#ifdef USE_DISCOVERY + page += F("Enabled"); + page += F("
mDNS Advertise"); +#ifdef WEBSERVER_ADVERTISE + page += F("Webserver"); +#else + page += F("Disabled"); +#endif // WEBSERVER_ADVERTISE +#else + page += F("Disabled"); +#endif // USE_DISCOVERY + page += F("
 
ESP Chip id"); page += String(ESP.getChipId()); page += F("
Flash Chip id"); page += String(ESP.getFlashChipId()); page += F("
Flash size"); page += String(ESP.getFlashChipRealSize() / 1024); page += F("kB
Program flash size"); page += String(ESP.getFlashChipSize() / 1024); page += F("kB
Program size"); page += String(ESP.getSketchSize() / 1024); page += F("kB
Free program space"); page += String(ESP.getFreeSketchSpace() / 1024); page += F("kB
Free memory"); page += String(freeMem / 1024); page += F("kB
"); +// page += F("
"); + page += FPSTR(HTTP_BTN_MAIN); + showPage(page); +} + +void handleRestart() +{ + if (httpUser()) { + return; + } + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Restarting")); + + String page = FPSTR(HTTP_HEAD); + page.replace(F("{v}"), F("Info")); + page += FPSTR(HTTP_MSG_RSTRT); + if (HTTP_MANAGER == _httpflag) { + _httpflag = HTTP_ADMIN; + } else { + page += FPSTR(HTTP_BTN_MAIN); + } + showPage(page); + + restartflag = 2; +} + +/********************************************************************************************/ + +void handleNotFound() +{ + if (captivePortal()) { // If captive portal redirect instead of displaying the error page. + return; + } + +#ifdef USE_EMULATION + String path = webServer->uri(); + if ((EMUL_HUE == sysCfg.flag.emulation) && (path.startsWith("/api"))) { + handle_hue_api(&path); + } else +#endif // USE_EMULATION + { + String message = F("File Not Found\n\nURI: "); + message += webServer->uri(); + message += F("\nMethod: "); + message += (webServer->method() == HTTP_GET) ? F("GET") : F("POST"); + message += F("\nArguments: "); + message += webServer->args(); + message += F("\n"); + for ( uint8_t i = 0; i < webServer->args(); i++ ) { + message += " " + webServer->argName(i) + ": " + webServer->arg(i) + "\n"; + } + setHeader(); + webServer->send(404, FPSTR(HDR_CTYPE_PLAIN), message); + } +} + +/* Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */ +boolean captivePortal() +{ + if ((HTTP_MANAGER == _httpflag) && !isIp(webServer->hostHeader())) { + addLog_P(LOG_LEVEL_DEBUG, PSTR("HTTP: Request redirected to captive portal")); + + webServer->sendHeader(F("Location"), String("http://") + webServer->client().localIP().toString(), true); + webServer->send(302, FPSTR(HDR_CTYPE_PLAIN), ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. + webServer->client().stop(); // Stop is needed because we sent no content length + return true; + } + return false; +} + +/** Is this an IP? */ +boolean isIp(String str) +{ + for (uint16_t i = 0; i < str.length(); i++) { + int c = str.charAt(i); + if (c != '.' && (c < '0' || c > '9')) { + return false; + } + } + return true; +} +#endif // USE_WEBSERVER