Browse Source

Merge branch 'dev' into inching

Conflicts:
	code/data/index.html.gz
	code/espurna/relay.ino
fastled
Xose Pérez 7 years ago
parent
commit
4e342add05
80 changed files with 776 additions and 1009 deletions
  1. +47
    -8
      CHANGELOG.md
  2. +43
    -36
      README.md
  3. +3
    -8
      code/build-all
  4. +0
    -1
      code/data/fsversion
  5. BIN
      code/data/index.html.gz
  6. BIN
      code/data/script.js.gz
  7. BIN
      code/data/style.css.gz
  8. +0
    -0
      code/espurna/button.ino
  9. +0
    -0
      code/espurna/config/all.h
  10. +0
    -0
      code/espurna/config/debug.h
  11. +12
    -3
      code/espurna/config/general.h
  12. +62
    -33
      code/espurna/config/hardware.h
  13. +0
    -0
      code/espurna/config/sensors.h
  14. +1
    -1
      code/espurna/config/version.h
  15. +0
    -0
      code/espurna/data/favicon.ico
  16. +0
    -0
      code/espurna/data/images/check.png
  17. BIN
      code/espurna/data/index.html.gz
  18. +0
    -0
      code/espurna/data/password.html.gz
  19. BIN
      code/espurna/data/script.js.gz
  20. BIN
      code/espurna/data/style.css.gz
  21. +2
    -2
      code/espurna/dht.ino
  22. +78
    -0
      code/espurna/domoticz.ino
  23. +1
    -1
      code/espurna/ds18b20.ino
  24. +1
    -1
      code/espurna/emon.ino
  25. +8
    -14
      code/espurna/espurna.ino
  26. +6
    -7
      code/espurna/fauxmo.ino
  27. +11
    -7
      code/espurna/led.ino
  28. +56
    -37
      code/espurna/mqtt.ino
  29. +0
    -0
      code/espurna/nofuss.ino
  30. +0
    -0
      code/espurna/ntp.ino
  31. +0
    -0
      code/espurna/ota.ino
  32. +1
    -1
      code/espurna/pow.ino
  33. +26
    -16
      code/espurna/relay.ino
  34. +0
    -0
      code/espurna/rf.ino
  35. +0
    -0
      code/espurna/settings.ino
  36. +96
    -3
      code/espurna/web.ino
  37. +18
    -3
      code/espurna/wifi.ino
  38. +7
    -5
      code/gulpfile.js
  39. +24
    -2
      code/html/custom.css
  40. +118
    -9
      code/html/custom.js
  41. +0
    -1
      code/html/fsversion
  42. +122
    -59
      code/html/index.html
  43. +0
    -115
      code/lib/DebounceEvent/DebounceEvent.cpp
  44. +0
    -62
      code/lib/DebounceEvent/DebounceEvent.h
  45. +0
    -1
      code/lib/WProgram/WProgram.h
  46. +1
    -18
      code/pio_hooks.py
  47. +0
    -132
      code/platformio.custom.ini
  48. +32
    -9
      code/platformio.ini
  49. +0
    -21
      docs/Configuration.md
  50. +0
    -77
      docs/Filesystem.md
  51. +0
    -56
      docs/Firmware.md
  52. +0
    -216
      docs/Hardware.md
  53. +0
    -28
      docs/OTA.md
  54. +0
    -3
      docs/Sensors.md
  55. +0
    -13
      docs/Troubleshooting.md
  56. BIN
      docs/images/electrodragon-flash.jpg
  57. BIN
      docs/images/s20-flash.jpg
  58. BIN
      docs/images/slampher-flash1.jpg
  59. BIN
      docs/images/slampher-flash2.jpg
  60. BIN
      docs/images/sonoff-dual-flash.jpg
  61. BIN
      docs/images/sonoff-flash.jpg
  62. BIN
      docs/images/sonoff-pow-flash.jpg
  63. BIN
      docs/images/sonoff-rf-flash.jpg
  64. BIN
      docs/images/sonoff-th-flash.jpg
  65. BIN
      images/devices/1ch-inching.jpg
  66. BIN
      images/devices/d1mini.jpg
  67. BIN
      images/devices/electrodragon-relay-board.jpg
  68. BIN
      images/devices/motor-switch.jpg
  69. BIN
      images/devices/s20.jpg
  70. BIN
      images/devices/slampher.jpg
  71. BIN
      images/devices/sonoff-4ch.jpg
  72. BIN
      images/devices/sonoff-basic.jpg
  73. BIN
      images/devices/sonoff-dual.jpg
  74. BIN
      images/devices/sonoff-led.jpg
  75. BIN
      images/devices/sonoff-pow.jpg
  76. BIN
      images/devices/sonoff-rf.jpg
  77. BIN
      images/devices/sonoff-sv.jpg
  78. BIN
      images/devices/sonoff-th10-th16.jpg
  79. BIN
      images/devices/sonoff-touch.jpg
  80. BIN
      images/devices/workchoice-ecoplug.jpg

+ 47
- 8
CHANGELOG.md View File

@ -3,23 +3,64 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
## [1.4.2] 2017-01-09
### Fixed
- Fixed error in relay identification from MQTT messages (issue #31)
## [1.4.1] 2017-01-05
### Added
- Alexa support by default on all devices
- Added support for Wemos D1 Mini board with official Relay Shield
### Fixed
- Multi-packet websocket frames
## [1.4.0] 2016-12-31
### Added
- Domoticz support via MQTT (https://www.domoticz.com/wiki/MQTT)
- Support for static IP connections
### Fixed
- Enforce minimum password strength in web interface (#16)
### Changed
- Using default client_id provided by AsyncMqttClient
- Allow up to 5 different WIFI networks
### Removed
- File system version file
## [1.3.1] 2016-12-31
### Fixed
- data_dir fix for PlatformIO
## [1.3.0] 2016-12-30
### Changed
- Arduino IDE support (changes in the folder structure and documentation)
## [1.2.0] 2016-12-27
### Added ### Added
- Added last-modified header to static contents
- Added support for multi-button boards (SONOFF_4CH)
- Force password changing if it's the default one
- Added Last-Modified header to static contents
- Added DNS captive portal for AP mode
- Added support for Sonoff 4CH
- Added support for WorkChoice ecoPlug (ECOPLUG). Thanks to David Myers - Added support for WorkChoice ecoPlug (ECOPLUG). Thanks to David Myers
- Added support for Sonoff SV - Added support for Sonoff SV
- Added DNS captive portal for AP mode
- Force password changing if it's the default one
- Added support for Sonoff Touch
- Comment out hardware selection in hardware.h if using Arduino IDE - Comment out hardware selection in hardware.h if using Arduino IDE
- Added support for MQTT get/set suffixes (/status, /set, ...)
- Added support for LED notifications via MQTT
- Added EEPROM check commands to terminal interface
### Changed ### Changed
- Using unreleased AsyncMqttClient with stability improvements - Using unreleased AsyncMqttClient with stability improvements
- Better decoupling between MQTT and relays/websockets - Better decoupling between MQTT and relays/websockets
- Skipping retained MQTT messages (configurable)
### Fixed ### Fixed
- Issue #14 MQTT Connection with Username an Password not working
- Issue #11 Compile error when building sonoff-dual-debug - Issue #11 Compile error when building sonoff-dual-debug
- Issue #14 MQTT Connection with Username an Password not working
- Issue #17 Moved static variable 'pending' to class variable
## [1.1.0] 2016-12-06 ## [1.1.0] 2016-12-06
### Added ### Added
@ -105,8 +146,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
### Changed ### Changed
- Moving wifi management to library (JustWifi) - Moving wifi management to library (JustWifi)
### Changed
- Split code into modules - Split code into modules
## [0.9.6] 2016-08-12 ## [0.9.6] 2016-08-12


+ 43
- 36
README.md View File

@ -1,37 +1,50 @@
# ESPurna
# ESPurna Firmware
ESPurna ("spark" in Catalan) is a custom C firmware for ESP8266 based smart switches.
It was originally developed with the **[ITead Sonoff][1]** in mind but now it supports a growing number of ESP8266-based boards.
ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switches.
It was originally developed with the **[IteadStudio Sonoff](https://www.itead.cc/sonoff-wifi-wireless-switch.html)** in mind but now it supports a growing number of ESP8266-based boards.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
**Current Release Version is 1.4.2**, read the [changelog](CHANGELOG.md).
## Features ## Features
* **Asynchronous WebServer for configuration** and simple relay toggle with **basic authentication**
* Communication between webserver and webclient via **websockets** with secure ticket check
* **Flashing firmware Over-The-Air** (OTA)
* Up to **3 configurable WIFI networks**, connects to the strongest signal
* **MQTT support** with configurable host and topic
* **REST API** to query and set relay statuses
* Support for **multi-relay boards** (Sonoff Dual, Electrodragon ESP Relay Board,...)
* Manual switch ON/OFF with button (single click the button)
* AP mode backup (double click the button)
* Manual reset the board (long click the button)
* Visual status of the connection via the LED
* **Alexa** integration (Amazon Echo or Dot) by emulating a Belkin WeMo switch
* Support for **automatic over-the-air updates** through the [NoFUSS Library][2]
* Support for **DHT22** and **DS18B20** sensors
* Support for the **HLW8012** power sensor present in the Sonoff POW
* Support for **current monitoring** through the [EmonLiteESP Library][3] using a non-intrusive current sensor ([requires some hacking][4])
* Command line configuration
## Index
* [Supported hardware](docs/Hardware.md)
* [Build and flash the firmware](docs/Firmware.md)
* [Build and flash the filesystem](docs/Filesystem.md)
* [Configuration](docs/Configuration.md)
* [Over-the-air updates](docs/OTA.md)
* [Sensors](docs/Sensors.md)
* [Troubleshooting](docs/Troubleshooting.md)
* Support for **multiple ESP8266-based boards** ([check list](#supported-hardware))
* Wifi **AP Mode** or **STA mode** with **multiple network definitions** and static IP support
* **MQTT** enabled
* Switch on/off and toggle relays
* LED notifications
* Support for different **sensors**
* DHT11 / DHT22 / DHT21 / AM2301
* DS18B20
* HLW8012 (Sonoff POW)
* Non-invasive current sensor using the [EmonLiteESP Library](https://bitbucket.org/xoseperez/emonliteesp) (requires some hacking)
* Fast asynchronous **HTTP Server**
* Basic authentication
* Web-based configuration
* Relay switching from the web
* Websockets-based communication between the device and the browser
* **REST API**
* GET and PUT relay status
* **Command line configuration**
* **Over-The-Air** (OTA) updates even for 1Mb boards
* Manually from PlatformIO or Arduino Inside
* Automatic updates through the [NoFUSS Library](https://bitbucket.org/xoseperez/nofuss)
* **Alexa** integration using the [FauxmoESP Library](https://bitbucket.org/xoseperez/fauxmoesp)
* [**Domoticz**](https://domoticz.com/) integration via MQTT
## Documentation
For more information please refer to the [ESPurna Wiki](https://bitbucket.org/xoseperez/espurna/wiki/Home).
## Supported hardware
|![IteadStudio S20](images/devices/s20.jpg) **IteadStudio S20**|![IteadStudio Slampher](images/devices/slampher.jpg) **IteadStudio Slampher**|![IteadStudio Sonoff 4CH](images/devices/sonoff-4ch.jpg) **IteadStudio Sonoff 4CH**|
|![IteadStudio Sonoff Basic](images/devices/sonoff-basic.jpg) **IteadStudio Sonoff Basic**|![IteadStudio Motor Switch](images/devices/motor-switch.jpg) **IteadStudio Motor Switch**|![IteadStudio 1CH Inching](images/devices/1ch-inching.jpg) **IteadStudio 1CH Inching**|
|![IteadStudio Sonoff Dual](images/devices/sonoff-dual.jpg) **IteadStudio Sonoff Dual**|![IteadStudio Sonoff POW](images/devices/sonoff-pow.jpg) **IteadStudio Sonoff POW**|![IteadStudio Sonoff TH10/TH16](images/devices/sonoff-th10-th16.jpg) **IteadStudio Sonoff TH10/TH16**|
|![IteadStudio Sonoff RF](images/devices/sonoff-rf.jpg) **IteadStudio Sonoff RF**|![IteadStudio Sonoff SV](images/devices/sonoff-sv.jpg) **IteadStudio Sonoff SV**|![IteadStudio Sonoff Touch](images/devices/sonoff-touch.jpg) **IteadStudio Sonoff Touch**|
|![Wemos D1 Mini Relay Shield](images/devices/d1mini.jpg) **Wemos D1 Mini Relay Shield**|![Electrodragon Relay Board](images/devices/electrodragon-relay-board.jpg) **Electrodragon Relay Board**|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg) **WorkChoice EcoPlug**|
## License ## License
@ -49,9 +62,3 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
[1]: https://www.itead.cc/sonoff-wifi-wireless-switch.html
[2]: https://bitbucket.org/xoseperez/nofuss
[3]: https://bitbucket.org/xoseperez/emonliteesp
[4]: http://tinkerman.cat/your-laundry-is-done/

+ 3
- 8
code/build-all View File

@ -1,13 +1,10 @@
#!/bin/bash #!/bin/bash
mv platformio.ini platformio.backup
cp platformio.custom.ini platformio.ini
# Environments to build # Environments to build
ENVIRONMENTS="sonoff-debug sonoff-dht22-debug sonoff-ds18b20-debug s20-debug sonoff-pow-debug slampher-debug"
ENVIRONMENTS="sonoff-debug sonoff-dht22-debug sonoff-ds18b20-debug sonoff-pow-debug sonoff-dual-debug sonoff-4ch-debug electrodragon-debug ecoplug-debug"
# Get current version # Get current version
version=`cat src/config/version.h | grep APP_VERSION | awk '{print $3}' | sed 's/"//g'`
version=`cat espurna/config/version.h | grep APP_VERSION | awk '{print $3}' | sed 's/"//g'`
echo $version echo $version
# Create output folder # Create output folder
@ -15,11 +12,9 @@ mkdir -p firmware
# Build all the required firmwares # Build all the required firmwares
for environment in $ENVIRONMENTS; do for environment in $ENVIRONMENTS; do
platformio run -vv -e $environment
platformio run -e $environment
mv .pioenvs/$environment/firmware.bin firmware/espurna-$version-$environment.bin mv .pioenvs/$environment/firmware.bin firmware/espurna-$version-$environment.bin
done done
platformio run -vv -t uploadfs -e node-debug platformio run -vv -t uploadfs -e node-debug
mv .pioenvs/node-debug/spiffs.bin firmware/espurna-$version-spiffs.bin mv .pioenvs/node-debug/spiffs.bin firmware/espurna-$version-spiffs.bin
mv platformio.backup platformio.ini

+ 0
- 1
code/data/fsversion View File

@ -1 +0,0 @@
1.1.0

BIN
code/data/index.html.gz View File


BIN
code/data/script.js.gz View File


BIN
code/data/style.css.gz View File


code/src/button.ino → code/espurna/button.ino View File


code/src/config/all.h → code/espurna/config/all.h View File


code/src/config/debug.h → code/espurna/config/debug.h View File


code/src/config/general.h → code/espurna/config/general.h View File

@ -6,7 +6,6 @@
#define HOSTNAME DEVICE #define HOSTNAME DEVICE
#define BUFFER_SIZE 1024 #define BUFFER_SIZE 1024
#define HEARTBEAT_INTERVAL 300000 #define HEARTBEAT_INTERVAL 300000
#define FS_VERSION_FILE "/fsversion"
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// RELAY // RELAY
@ -51,7 +50,7 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define WIFI_RECONNECT_INTERVAL 300000 #define WIFI_RECONNECT_INTERVAL 300000
#define WIFI_MAX_NETWORKS 3
#define WIFI_MAX_NETWORKS 5
#define ADMIN_PASS "fibonacci" #define ADMIN_PASS "fibonacci"
#define HTTP_USERNAME "admin" #define HTTP_USERNAME "admin"
#define WS_BUFFER_SIZE 5 #define WS_BUFFER_SIZE 5
@ -77,6 +76,8 @@
#define MQTT_QOS 0 #define MQTT_QOS 0
#define MQTT_KEEPALIVE 30 #define MQTT_KEEPALIVE 30
#define MQTT_RECONNECT_DELAY 10000 #define MQTT_RECONNECT_DELAY 10000
#define MQTT_SKIP_RETAINED 1
#define MQTT_SKIP_TIME 1000
#define MQTT_RELAY_TOPIC "/relay" #define MQTT_RELAY_TOPIC "/relay"
#define MQTT_LED_TOPIC "/led" #define MQTT_LED_TOPIC "/led"
#define MQTT_IP_TOPIC "/ip" #define MQTT_IP_TOPIC "/ip"
@ -94,6 +95,14 @@
#define MQTT_USE_SETTER "" #define MQTT_USE_SETTER ""
// -----------------------------------------------------------------------------
// DOMOTICZ
// -----------------------------------------------------------------------------
#define ENABLE_DOMOTICZ 1
#define DOMOTICZ_IN_TOPIC "domoticz/in"
#define DOMOTICZ_OUT_TOPIC "domoticz/out"
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// NTP // NTP
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -107,4 +116,4 @@
// FAUXO // FAUXO
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#define FAUXMO_ENABLED 0
#define FAUXMO_ENABLED 1

code/src/config/hardware.h → code/espurna/config/hardware.h View File

@ -7,12 +7,12 @@
//#define NODEMCUV2 //#define NODEMCUV2
//#define SONOFF //#define SONOFF
//#define SONOFF_TH //#define SONOFF_TH
//#define SLAMPHER
//#define S20
//#define SONOFF_SV
//#define SONOFF_POW //#define SONOFF_POW
//#define SONOFF_DUAL //#define SONOFF_DUAL
//#define SONOFF_4CH //#define SONOFF_4CH
//#define SONOFF_SV
//#define SLAMPHER
//#define S20
//#define ESP_RELAY_BOARD //#define ESP_RELAY_BOARD
//#define ECOPLUG //#define ECOPLUG
//#define ESPURNA //#define ESPURNA
@ -22,11 +22,15 @@
//#define ENABLE_EMON 1 //#define ENABLE_EMON 1
//#define ENABLE_HLW8018 1 //#define ENABLE_HLW8018 1
//#define ENABLE_RF 1 //#define ENABLE_RF 1
//#define ENABLE_FAUXMO 1
//#define ENABLE_FAUXMO 0
//#define ENABLE_NOFUSS 1 //#define ENABLE_NOFUSS 1
#ifndef ENABLE_FAUXMO
#define ENABLE_FAUXMO 1
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// NODEMCUv2 development board
// Development boards
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#if defined(NODEMCUV2) #if defined(NODEMCUV2)
@ -35,6 +39,16 @@
#define DEVICE "LOLIN" #define DEVICE "LOLIN"
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#elif defined(D1_RELAYSHIELD)
#define MANUFACTURER "WEMOS"
#define DEVICE "D1_MINI"
#define RELAY1_PIN 5
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 2 #define LED1_PIN 2
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
@ -48,6 +62,7 @@
#define DEVICE "SONOFF" #define DEVICE "SONOFF"
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 13 #define LED1_PIN 13
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
@ -57,6 +72,37 @@
#define DEVICE "SONOFF_TH" #define DEVICE "SONOFF_TH"
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#elif defined(SONOFF_SV)
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_SV"
#define BUTTON1_PIN 0
#define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#elif defined(SLAMPHER)
#define MANUFACTURER "ITEAD"
#define DEVICE "SLAMPHER"
#define BUTTON1_PIN 0
#define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#elif defined(S20)
#define MANUFACTURER "ITEAD"
#define DEVICE "S20"
#define BUTTON1_PIN 0
#define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 13 #define LED1_PIN 13
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
@ -66,6 +112,7 @@
#define DEVICE "SONOFF_TOUCH" #define DEVICE "SONOFF_TOUCH"
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 13 #define LED1_PIN 13
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
@ -75,6 +122,7 @@
#define DEVICE "SONOFF_POW" #define DEVICE "SONOFF_POW"
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 15 #define LED1_PIN 15
#define LED1_PIN_INVERSE 0 #define LED1_PIN_INVERSE 0
#define ENABLE_POW 1 #define ENABLE_POW 1
@ -98,36 +146,13 @@
#define BUTTON3_PIN 10 #define BUTTON3_PIN 10
#define BUTTON4_PIN 14 #define BUTTON4_PIN 14
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define RELAY2_PIN 5 #define RELAY2_PIN 5
#define RELAY2_PIN_INVERSE 0
#define RELAY3_PIN 4 #define RELAY3_PIN 4
#define RELAY3_PIN_INVERSE 0
#define RELAY4_PIN 15 #define RELAY4_PIN 15
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#elif defined(SONOFF_SV)
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_SV"
#define BUTTON1_PIN 0
#define RELAY1_PIN 12
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#elif defined(SLAMPHER)
#define MANUFACTURER "ITEAD"
#define DEVICE "SLAMPHER"
#define BUTTON1_PIN 0
#define RELAY1_PIN 12
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#elif defined(S20)
#define MANUFACTURER "ITEAD"
#define DEVICE "S20"
#define BUTTON1_PIN 0
#define RELAY1_PIN 12
#define RELAY4_PIN_INVERSE 0
#define LED1_PIN 13 #define LED1_PIN 13
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
@ -153,7 +178,9 @@
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define BUTTON2_PIN 2 #define BUTTON2_PIN 2
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define RELAY2_PIN 13 #define RELAY2_PIN 13
#define RELAY2_PIN_INVERSE 0
#define LED1_PIN 16 #define LED1_PIN 16
#define LED1_PIN_INVERSE 0 #define LED1_PIN_INVERSE 0
@ -166,7 +193,8 @@
#define MANUFACTURER "WORKCHOICE" #define MANUFACTURER "WORKCHOICE"
#define DEVICE "ECOPLUG" #define DEVICE "ECOPLUG"
#define BUTTON1_PIN 13 #define BUTTON1_PIN 13
#define RELAY_PIN 15
#define RELAY1_PIN 15
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 2 #define LED1_PIN 2
#define LED1_PIN_INVERSE 0 #define LED1_PIN_INVERSE 0
@ -180,6 +208,7 @@
#define DEVICE "ESPURNA" #define DEVICE "ESPURNA"
#define BUTTON1_PIN 0 #define BUTTON1_PIN 0
#define RELAY1_PIN 12 #define RELAY1_PIN 12
#define RELAY1_PIN_INVERSE 0
#define LED1_PIN 13 #define LED1_PIN 13
#define LED1_PIN_INVERSE 0 #define LED1_PIN_INVERSE 0

code/src/config/sensors.h → code/espurna/config/sensors.h View File


code/src/config/version.h → code/espurna/config/version.h View File

@ -1,4 +1,4 @@
#define APP_NAME "ESPurna" #define APP_NAME "ESPurna"
#define APP_VERSION "1.2.0"
#define APP_VERSION "1.4.2"
#define APP_AUTHOR "xose.perez@gmail.com" #define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat" #define APP_WEBSITE "http://tinkerman.cat"

code/data/favicon.ico → code/espurna/data/favicon.ico View File


code/data/images/check.png → code/espurna/data/images/check.png View File


BIN
code/espurna/data/index.html.gz View File


code/data/password.html.gz → code/espurna/data/password.html.gz View File


BIN
code/espurna/data/script.js.gz View File


BIN
code/espurna/data/style.css.gz View File


code/src/dht.ino → code/espurna/dht.ino View File

@ -60,8 +60,8 @@ void dhtLoop() {
DEBUG_MSG("[DHT] Humidity: %s\n", dhtHumidity); DEBUG_MSG("[DHT] Humidity: %s\n", dhtHumidity);
// Send MQTT messages // Send MQTT messages
mqttSend((char *) getSetting("dhtTmpTopic", DHT_TEMPERATURE_TOPIC).c_str(), dhtTemperature);
mqttSend((char *) getSetting("dhtHumTopic", DHT_HUMIDITY_TOPIC).c_str(), dhtHumidity);
mqttSend(getSetting("dhtTmpTopic", DHT_TEMPERATURE_TOPIC).c_str(), dhtTemperature);
mqttSend(getSetting("dhtHumTopic", DHT_HUMIDITY_TOPIC).c_str(), dhtHumidity);
// Update websocket clients // Update websocket clients
char buffer[100]; char buffer[100];

+ 78
- 0
code/espurna/domoticz.ino View File

@ -0,0 +1,78 @@
/*
ESPurna
DOMOTICZ MODULE
Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if ENABLE_DOMOTICZ
#include <Hash.h>
#include <ArduinoJson.h>
void domoticzMQTTCallback(unsigned int type, const char * topic, const char * payload) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribeRaw(dczTopicOut.c_str());
}
if (type == MQTT_MESSAGE_EVENT) {
// Check topic
if (dczTopicOut.equals(topic)) {
// Parse response
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) payload);
if (!root.success()) {
DEBUG_MSG("[DOMOTICZ] Error parsing data\n");
return;
}
// IDX
unsigned long idx = root["idx"];
int relayID = domoticzRelay(idx);
if (relayID >= 0) {
unsigned long value = root["nvalue"];
DEBUG_MSG("[DOMOTICZ] Received value %d for IDX %d\n", value, idx);
relayStatus(relayID, value == 1);
}
}
}
}
int domoticzIdx(unsigned int relayID) {
return getSetting("dczIdx" + String(relayID)).toInt();
}
int domoticzRelay(unsigned int idx) {
for (int relayID=0; relayID<relayCount(); relayID++) {
if (domoticzIdx(relayID) == idx) {
return relayID;
}
}
return -1;
}
void domoticzSend(unsigned int relayID) {
unsigned int idx = domoticzIdx(relayID);
if (idx > 0) {
unsigned int value = relayStatus(relayID) ? 1 : 0;
char payload[45];
sprintf(payload, "{\"idx\": %d, \"nvalue\": %d, \"svalue\": \"\"}", idx, value);
mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload);
}
}
void domoticzSetup() {
mqttRegister(domoticzMQTTCallback);
}
#endif

code/src/ds18b20.ino → code/espurna/ds18b20.ino View File

@ -54,7 +54,7 @@ void dsLoop() {
DEBUG_MSG("[DS18B20] Temperature: %s\n", dsTemperature); DEBUG_MSG("[DS18B20] Temperature: %s\n", dsTemperature);
// Send MQTT messages // Send MQTT messages
mqttSend((char *) getSetting("dsTmpTopic", DS_TEMPERATURE_TOPIC).c_str(), dsTemperature);
mqttSend(getSetting("dsTmpTopic", DS_TEMPERATURE_TOPIC).c_str(), dsTemperature);
// Update websocket clients // Update websocket clients
char buffer[100]; char buffer[100];

code/src/emon.ino → code/espurna/emon.ino View File

@ -104,7 +104,7 @@ void powerMonitorLoop() {
if (measurements == EMON_MEASUREMENTS) { if (measurements == EMON_MEASUREMENTS) {
double p = (sum - max - min) * mainsVoltage / (measurements - 2); double p = (sum - max - min) * mainsVoltage / (measurements - 2);
sprintf(power, "%d", int(p)); sprintf(power, "%d", int(p));
mqttSend((char *) getSetting("emonPowerTopic", EMON_POWER_TOPIC).c_str(), power);
mqttSend(getSetting("emonPowerTopic", EMON_POWER_TOPIC).c_str(), power);
sum = 0; sum = 0;
measurements = 0; measurements = 0;
} }

code/src/main.ino → code/espurna/espurna.ino View File

@ -28,7 +28,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <NtpClientLib.h> #include <NtpClientLib.h>
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <AsyncMqttClient.h> #include <AsyncMqttClient.h>
#include "FS.h"
void mqttRegister(void (*callback)(unsigned int, const char *, const char *)); void mqttRegister(void (*callback)(unsigned int, const char *, const char *));
template<typename T> bool setSetting(const String& key, T value); template<typename T> bool setSetting(const String& key, T value);
template<typename T> String getSetting(const String& key, T defaultValue); template<typename T> String getSetting(const String& key, T defaultValue);
@ -48,18 +48,6 @@ void hardwareSetup() {
SPIFFS.begin(); SPIFFS.begin();
} }
void getFSVersion(char * buffer) {
File h = SPIFFS.open(FS_VERSION_FILE, "r");
if (!h) {
DEBUG_MSG("[SPIFFS] Could not open file system version file.\n");
strcpy(buffer, APP_VERSION);
return;
}
size_t size = h.size();
h.readBytes(buffer, size - 1);
h.close();
}
void hardwareLoop() { void hardwareLoop() {
// Heartbeat // Heartbeat
@ -67,7 +55,7 @@ void hardwareLoop() {
if (mqttConnected()) { if (mqttConnected()) {
if ((millis() - last_heartbeat > HEARTBEAT_INTERVAL) || (last_heartbeat == 0)) { if ((millis() - last_heartbeat > HEARTBEAT_INTERVAL) || (last_heartbeat == 0)) {
last_heartbeat = millis(); last_heartbeat = millis();
mqttSend((char *) MQTT_HEARTBEAT_TOPIC, (char *) "1");
mqttSend(MQTT_HEARTBEAT_TOPIC, "1");
DEBUG_MSG("[BEAT] Free heap: %d\n", ESP.getFreeHeap()); DEBUG_MSG("[BEAT] Free heap: %d\n", ESP.getFreeHeap());
DEBUG_MSG("[NTP] Time: %s\n", (char *) NTP.getTimeDateString().c_str()); DEBUG_MSG("[NTP] Time: %s\n", (char *) NTP.getTimeDateString().c_str());
} }
@ -125,6 +113,9 @@ void setup() {
webSetup(); webSetup();
ntpSetup(); ntpSetup();
#if ENABLE_DOMOTICZ
domoticzSetup();
#endif
#if ENABLE_FAUXMO #if ENABLE_FAUXMO
fauxmoSetup(); fauxmoSetup();
#endif #endif
@ -159,6 +150,9 @@ void loop() {
mqttLoop(); mqttLoop();
ntpLoop(); ntpLoop();
#if ENABLE_FAUXMO
fauxmoLoop();
#endif
#ifndef SONOFF_DUAL #ifndef SONOFF_DUAL
settingsLoop(); settingsLoop();
#endif #endif

code/src/fauxmo.ino → code/espurna/fauxmo.ino View File

@ -32,15 +32,14 @@ void fauxmoSetup() {
fauxmo.addDevice((hostname + "_" + i).c_str()); fauxmo.addDevice((hostname + "_" + i).c_str());
} }
} }
fauxmo.onMessage([relays](const char * name, bool state) {
fauxmo.onMessage([relays](unsigned char device_id, const char * name, bool state) {
DEBUG_MSG("[FAUXMO] %s state: %s\n", name, state ? "ON" : "OFF"); DEBUG_MSG("[FAUXMO] %s state: %s\n", name, state ? "ON" : "OFF");
unsigned int id = 0;
if (relays > 1) {
id = name[strlen(name)-1] - '0';
if (id >= relays) id = 0;
}
relayStatus(id, state);
relayStatus(device_id, state);
}); });
} }
void fauxmoLoop() {
fauxmo.handle();
}
#endif #endif

code/src/led.ino → code/espurna/led.ino View File

@ -22,25 +22,25 @@ std::vector<led_t> _leds;
bool ledAuto; bool ledAuto;
bool ledStatus(unsigned char id) { bool ledStatus(unsigned char id) {
if (id <= _leds.size()) return false;
if (id >= _leds.size()) return false;
bool status = digitalRead(_leds[id].pin); bool status = digitalRead(_leds[id].pin);
return _leds[id].reverse ? !status : status; return _leds[id].reverse ? !status : status;
} }
bool ledStatus(unsigned char id, bool status) { bool ledStatus(unsigned char id, bool status) {
if (id <= _leds.size()) return false;
if (id >= _leds.size()) return false;
bool s = _leds[id].reverse ? !status : status; bool s = _leds[id].reverse ? !status : status;
digitalWrite(_leds[id].pin, _leds[id].reverse ? !status : status); digitalWrite(_leds[id].pin, _leds[id].reverse ? !status : status);
return status; return status;
} }
bool ledToggle(unsigned char id) { bool ledToggle(unsigned char id) {
if (id <= _leds.size()) return false;
if (id >= _leds.size()) return false;
return ledStatus(id, !ledStatus(id)); return ledStatus(id, !ledStatus(id));
} }
void ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn) { void ledBlink(unsigned char id, unsigned long delayOff, unsigned long delayOn) {
if (id <= _leds.size()) return;
if (id >= _leds.size()) return;
static unsigned long next = millis(); static unsigned long next = millis();
if (next < millis()) { if (next < millis()) {
next += (ledToggle(id) ? delayOn : delayOff); next += (ledToggle(id) ? delayOn : delayOff);
@ -74,13 +74,16 @@ void ledMQTTCallback(unsigned int type, const char * topic, const char * payload
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Match topic // Match topic
String t = String(topic);
String t = String(topic + mqttTopicRootLength());
if (!t.startsWith(MQTT_LED_TOPIC)) return; if (!t.startsWith(MQTT_LED_TOPIC)) return;
if (!t.endsWith(mqttSetter)) return; if (!t.endsWith(mqttSetter)) return;
// Get led ID // Get led ID
unsigned int ledID = topic[strlen(MQTT_LED_TOPIC)+1] - '0';
if (ledID >= ledCount()) ledID = 0;
unsigned int ledID = topic[strlen(topic) - mqttSetter.length() - 1] - '0';
if (ledID >= ledCount()) {
DEBUG_MSG("[LED] Wrong ledID (%d)\n", ledID);
return;
}
// get value // get value
unsigned int value = (char)payload[0] - '0'; unsigned int value = (char)payload[0] - '0';
@ -134,6 +137,7 @@ void ledSetup() {
mqttRegister(ledMQTTCallback); mqttRegister(ledMQTTCallback);
DEBUG_MSG("[LED] Number of leds: %d\n", _leds.size()); DEBUG_MSG("[LED] Number of leds: %d\n", _leds.size());
DEBUG_MSG("[LED] Led auto indicator is %s\n", ledAuto ? "ON" : "OFF" );
} }

code/src/mqtt.ino → code/espurna/mqtt.ino View File

@ -15,6 +15,9 @@ AsyncMqttClient mqtt;
String mqttTopic; String mqttTopic;
std::vector<void (*)(unsigned int, const char *, const char *)> _mqtt_callbacks; std::vector<void (*)(unsigned int, const char *, const char *)> _mqtt_callbacks;
#if MQTT_SKIP_RETAINED
unsigned long mqttConnectedAt = 0;
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// MQTT // MQTT
@ -34,17 +37,32 @@ void buildTopics() {
mqttTopic.replace("{identifier}", getSetting("hostname")); mqttTopic.replace("{identifier}", getSetting("hostname"));
} }
unsigned int mqttTopicRootLength() {
return mqttTopic.length();
}
void mqttSendRaw(const char * topic, const char * message) {
if (mqtt.connected()) {
DEBUG_MSG("[MQTT] Sending %s %s\n", topic, message);
mqtt.publish(topic, MQTT_QOS, MQTT_RETAIN, message);
}
}
void mqttSend(const char * topic, const char * message) { void mqttSend(const char * topic, const char * message) {
if (!mqtt.connected()) return;
String path = mqttTopic + String(topic); String path = mqttTopic + String(topic);
DEBUG_MSG("[MQTT] Sending %s %s\n", (char *) path.c_str(), message);
mqtt.publish(path.c_str(), MQTT_QOS, MQTT_RETAIN, message);
mqttSendRaw(path.c_str(), message);
}
void mqttSubscribeRaw(const char * topic) {
if (mqtt.connected()) {
DEBUG_MSG("[MQTT] Subscribing to %s\n", topic);
mqtt.subscribe(topic, MQTT_QOS);
}
} }
void mqttSubscribe(const char * topic) { void mqttSubscribe(const char * topic) {
String path = mqttTopic + String(topic); String path = mqttTopic + String(topic);
DEBUG_MSG("[MQTT] Subscribing to %s\n", (char *) path.c_str());
mqtt.subscribe(path.c_str(), MQTT_QOS);
mqttSubscribeRaw(path.c_str());
} }
void mqttRegister(void (*callback)(unsigned int, const char *, const char *)) { void mqttRegister(void (*callback)(unsigned int, const char *, const char *)) {
@ -55,16 +73,17 @@ void _mqttOnConnect(bool sessionPresent) {
DEBUG_MSG("[MQTT] Connected!\n"); DEBUG_MSG("[MQTT] Connected!\n");
#if MQTT_SKIP_RETAINED
mqttConnectedAt = millis();
#endif
// Build MQTT topics // Build MQTT topics
buildTopics(); buildTopics();
mqtt.setWill((mqttTopic + MQTT_HEARTBEAT_TOPIC).c_str(), MQTT_QOS, MQTT_RETAIN, (char *) "0"); mqtt.setWill((mqttTopic + MQTT_HEARTBEAT_TOPIC).c_str(), MQTT_QOS, MQTT_RETAIN, (char *) "0");
// Say hello and report our IP and VERSION // Say hello and report our IP and VERSION
mqttSend((char *) MQTT_IP_TOPIC, (char *) getIP().c_str());
mqttSend((char *) MQTT_VERSION_TOPIC, (char *) APP_VERSION);
char buffer[50];
getFSVersion(buffer);
mqttSend((char *) MQTT_FSVERSION_TOPIC, buffer);
mqttSend(MQTT_IP_TOPIC, getIP().c_str());
mqttSend(MQTT_VERSION_TOPIC, APP_VERSION);
// Send connect event to subscribers // Send connect event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
@ -86,12 +105,24 @@ void _mqttOnDisconnect(AsyncMqttClientDisconnectReason reason) {
void _mqttOnMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { void _mqttOnMessage(char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
DEBUG_MSG("[MQTT] Received %s %c\n", topic, payload[0]);
char message[len+1];
strlcpy(message, payload, len+1);
DEBUG_MSG("[MQTT] Received %s => %s", topic, message);
#if MQTT_SKIP_RETAINED
if (millis() - mqttConnectedAt < MQTT_SKIP_TIME) {
DEBUG_MSG(" - SKIPPED\n");
return;
}
#endif
DEBUG_MSG("\n");
// Send message event to subscribers // Send message event to subscribers
// Topic is set to the specific part each one might be checking // Topic is set to the specific part each one might be checking
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(*_mqtt_callbacks[i])(MQTT_MESSAGE_EVENT, topic + mqttTopic.length(), payload);
(*_mqtt_callbacks[i])(MQTT_MESSAGE_EVENT, topic, message);
} }
} }
@ -100,32 +131,20 @@ void mqttConnect() {
if (!mqtt.connected()) { if (!mqtt.connected()) {
String host = getSetting("mqttServer", MQTT_SERVER);
String port = getSetting("mqttPort", MQTT_PORT);
String user = getSetting("mqttUser");
String pass = getSetting("mqttPassword");
if (host.length() == 0) return;
DEBUG_MSG("[MQTT] Connecting to broker at %s", (char *) host.c_str());
mqtt.setServer(host.c_str(), port.toInt());
mqtt
.setKeepAlive(MQTT_KEEPALIVE)
.setCleanSession(false)
.setClientId(getSetting("hostname", HOSTNAME).c_str());
if ((user != "") && (pass != "")) {
DEBUG_MSG(" as user '%s'.\n", (char *) user.c_str());
char username[user.length()+1];
user.toCharArray(username, user.length()+1);
char password[pass.length()+1];
pass.toCharArray(password, pass.length()+1);
mqtt.setCredentials(username, password);
} else {
DEBUG_MSG(" anonymously\n");
char * host = strdup(getSetting("mqttServer", MQTT_SERVER).c_str());
if (strlen(host) == 0) return;
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
char * user = strdup(getSetting("mqttUser").c_str());
char * pass = strdup(getSetting("mqttPassword").c_str());
DEBUG_MSG("[MQTT] Connecting to broker at %s", host);
mqtt.setServer(host, port);
mqtt.setKeepAlive(MQTT_KEEPALIVE).setCleanSession(false);
if ((strlen(user) > 0) && (strlen(pass) > 0)) {
DEBUG_MSG(" as user '%s'.", user);
mqtt.setCredentials(user, pass);
} }
DEBUG_MSG("\n");
mqtt.connect(); mqtt.connect();
} }

code/src/nofuss.ino → code/espurna/nofuss.ino View File


code/src/ntp.ino → code/espurna/ntp.ino View File


code/src/ota.ino → code/espurna/ota.ino View File


code/src/pow.ino → code/espurna/pow.ino View File

@ -151,7 +151,7 @@ void powLoop() {
wsSend(buffer); wsSend(buffer);
if (--report_count == 0) { if (--report_count == 0) {
mqttSend((char *) getSetting("powPowerTopic", POW_POWER_TOPIC).c_str(), (char *) String(power).c_str());
mqttSend(getSetting("powPowerTopic", POW_POWER_TOPIC).c_str(), String(power).c_str());
report_count = POW_REPORT_EVERY; report_count = POW_REPORT_EVERY;
} }

code/src/relay.ino → code/espurna/relay.ino View File

@ -12,7 +12,11 @@ Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <vector> #include <vector>
std::vector<unsigned char> _relays;
typedef struct {
unsigned char pin;
bool reverse;
} relay_t;
std::vector<relay_t> _relays;
bool recursive = false; bool recursive = false;
#ifdef SONOFF_DUAL #ifdef SONOFF_DUAL
unsigned char dualRelayStatus = 0; unsigned char dualRelayStatus = 0;
@ -28,7 +32,7 @@ void relayMQTT(unsigned char id) {
String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER); String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER);
char buffer[strlen(MQTT_RELAY_TOPIC) + mqttGetter.length() + 3]; char buffer[strlen(MQTT_RELAY_TOPIC) + mqttGetter.length() + 3];
sprintf(buffer, "%s/%d%s", MQTT_RELAY_TOPIC, id, mqttGetter.c_str()); sprintf(buffer, "%s/%d%s", MQTT_RELAY_TOPIC, id, mqttGetter.c_str());
mqttSend(buffer, (char *) (relayStatus(id) ? "1" : "0"));
mqttSend(buffer, relayStatus(id) ? "1" : "0");
} }
void relayMQTT() { void relayMQTT() {
@ -60,7 +64,8 @@ bool relayStatus(unsigned char id) {
return ((dualRelayStatus & (1 << id)) > 0); return ((dualRelayStatus & (1 << id)) > 0);
#else #else
if (id >= _relays.size()) return false; if (id >= _relays.size()) return false;
return (digitalRead(_relays[id]) == HIGH);
bool status = (digitalRead(_relays[id].pin) == HIGH);
return _relays[id].reverse ? !status : status;
#endif #endif
} }
@ -151,7 +156,7 @@ bool relayStatus(unsigned char id, bool status, bool report) {
Serial.flush(); Serial.flush();
#else #else
digitalWrite(_relays[id], status);
digitalWrite(_relays[id].pin, _relays[id].reverse ? !status : status);
#endif #endif
if (!recursive) { if (!recursive) {
@ -160,6 +165,10 @@ bool relayStatus(unsigned char id, bool status, bool report) {
relaySave(); relaySave();
} }
#ifdef ENABLE_DOMOTICZ
domoticzSend(id);
#endif
} }
if (report) relayMQTT(id); if (report) relayMQTT(id);
@ -242,8 +251,6 @@ unsigned char relayCount() {
void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) { void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) {
static bool isFirstMessage = true;
String mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER); String mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER);
String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER); String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER);
bool sameSetGet = mqttGetter.compareTo(mqttSetter) == 0; bool sameSetGet = mqttGetter.compareTo(mqttSetter) == 0;
@ -264,7 +271,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Match topic // Match topic
String t = String(topic);
String t = String(topic + mqttTopicRootLength());
if (!t.startsWith(MQTT_RELAY_TOPIC)) return; if (!t.startsWith(MQTT_RELAY_TOPIC)) return;
if (!t.endsWith(mqttSetter)) return; if (!t.endsWith(mqttSetter)) return;
@ -278,8 +285,11 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
} }
// Get relay ID // Get relay ID
unsigned int relayID = topic[strlen(MQTT_RELAY_TOPIC)+1] - '0';
if (relayID >= relayCount()) relayID = 0;
unsigned int relayID = topic[strlen(topic) - mqttSetter.length() - 1] - '0';
if (relayID >= relayCount()) {
DEBUG_MSG("[RELAY] Wrong relayID (%d)\n", relayID);
return;
}
// Action to perform // Action to perform
if (value == 2) { if (value == 2) {
@ -297,22 +307,22 @@ void relaySetup() {
#ifdef SONOFF_DUAL #ifdef SONOFF_DUAL
// Two dummy relays for the dual // Two dummy relays for the dual
_relays.push_back(0);
_relays.push_back(0);
_relays.push_back((relay_t) {0, 0});
_relays.push_back((relay_t) {0, 0});
#else #else
#ifdef RELAY1_PIN #ifdef RELAY1_PIN
_relays.push_back(RELAY1_PIN);
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_PIN_INVERSE });
#endif #endif
#ifdef RELAY2_PIN #ifdef RELAY2_PIN
_relays.push_back(RELAY2_PIN);
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_PIN_INVERSE });
#endif #endif
#ifdef RELAY3_PIN #ifdef RELAY3_PIN
_relays.push_back(RELAY3_PIN);
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_PIN_INVERSE });
#endif #endif
#ifdef RELAY4_PIN #ifdef RELAY4_PIN
_relays.push_back(RELAY4_PIN);
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_PIN_INVERSE });
#endif #endif
#endif #endif
@ -321,7 +331,7 @@ void relaySetup() {
byte relayMode = getSetting("relayMode", RELAY_MODE).toInt(); byte relayMode = getSetting("relayMode", RELAY_MODE).toInt();
for (unsigned int i=0; i < _relays.size(); i++) { for (unsigned int i=0; i < _relays.size(); i++) {
pinMode(_relays[i], OUTPUT);
pinMode(_relays[i].pin, OUTPUT);
if (relayMode == RELAY_MODE_OFF) relayStatus(i, false); if (relayMode == RELAY_MODE_OFF) relayStatus(i, false);
if (relayMode == RELAY_MODE_ON) relayStatus(i, true); if (relayMode == RELAY_MODE_ON) relayStatus(i, true);
} }

code/src/rf.ino → code/espurna/rf.ino View File


code/src/settings.ino → code/espurna/settings.ino View File


code/src/web.ino → code/espurna/web.ino View File

@ -94,6 +94,7 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
bool fauxmoEnabled = false; bool fauxmoEnabled = false;
#endif #endif
unsigned int network = 0; unsigned int network = 0;
unsigned int dczIdx = 0;
String adminPass; String adminPass;
for (unsigned int i=0; i<config.size(); i++) { for (unsigned int i=0; i<config.size(); i++) {
@ -108,6 +109,24 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
continue; continue;
} }
#else
if (key.startsWith("pow")) continue;
#endif
#if ENABLE_DOMOTICZ
if (key == "dczIdx") {
if (dczIdx >= relayCount()) continue;
key = key + String(dczIdx);
++dczIdx;
}
#else
if (key.startsWith("dcz")) continue;
#endif #endif
// Check password // Check password
@ -142,10 +161,23 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
} }
if (key == "pass") { if (key == "pass") {
key = key + String(network); key = key + String(network);
}
if (key == "ip") {
key = key + String(network);
}
if (key == "gw") {
key = key + String(network);
}
if (key == "mask") {
key = key + String(network);
}
if (key == "dns") {
key = key + String(network);
++network; ++network;
} }
if (value != getSetting(key)) { if (value != getSetting(key)) {
//DEBUG_MSG("[WEBSOCKET] Storing %s = %s\n", key.c_str(), value.c_str());
setSetting(key, value); setSetting(key, value);
dirty = true; dirty = true;
if (key.startsWith("mqtt")) dirtyMQTT = true; if (key.startsWith("mqtt")) dirtyMQTT = true;
@ -165,6 +197,26 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
} }
#endif #endif
// Clean wifi networks
for (int i = 0; i < network; i++) {
if (getSetting("pass" + String(i)).length() == 0) delSetting("pass" + String(i));
if (getSetting("ip" + String(i)).length() == 0) delSetting("ip" + String(i));
if (getSetting("gw" + String(i)).length() == 0) delSetting("gw" + String(i));
if (getSetting("mask" + String(i)).length() == 0) delSetting("mask" + String(i));
if (getSetting("dns" + String(i)).length() == 0) delSetting("dns" + String(i));
}
for (int i = network; i<WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() > 0) {
dirty = true;
}
delSetting("ssid" + String(i));
delSetting("pass" + String(i));
delSetting("ip" + String(i));
delSetting("gw" + String(i));
delSetting("mask" + String(i));
delSetting("dns" + String(i));
}
// Save settings // Save settings
if (dirty) { if (dirty) {
@ -225,7 +277,7 @@ void _wsStart(uint32_t client_id) {
root["device"] = String(DEVICE); root["device"] = String(DEVICE);
root["hostname"] = getSetting("hostname", HOSTNAME); root["hostname"] = getSetting("hostname", HOSTNAME);
root["network"] = getNetwork(); root["network"] = getNetwork();
root["ip"] = getIP();
root["deviceip"] = getIP();
root["mqttStatus"] = mqttConnected(); root["mqttStatus"] = mqttConnected();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER); root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
@ -249,6 +301,19 @@ void _wsStart(uint32_t client_id) {
root["apiEnabled"] = getSetting("apiEnabled").toInt() == 1; root["apiEnabled"] = getSetting("apiEnabled").toInt() == 1;
root["apiKey"] = getSetting("apiKey"); root["apiKey"] = getSetting("apiKey");
#if ENABLE_DOMOTICZ
root["dczVisible"] = 1;
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& dczIdx = root.createNestedArray("dczIdx");
for (byte i=0; i<relayCount(); i++) {
dczIdx.add(domoticzIdx(i));
}
#endif
#if ENABLE_FAUXMO #if ENABLE_FAUXMO
root["fauxmoVisible"] = 1; root["fauxmoVisible"] = 1;
root["fauxmoEnabled"] = getSetting("fauxmoEnabled", FAUXMO_ENABLED).toInt() == 1; root["fauxmoEnabled"] = getSetting("fauxmoEnabled", FAUXMO_ENABLED).toInt() == 1;
@ -283,11 +348,17 @@ void _wsStart(uint32_t client_id) {
root["powActivePower"] = getActivePower(); root["powActivePower"] = getActivePower();
#endif #endif
root["maxNetworks"] = WIFI_MAX_NETWORKS;
JsonArray& wifi = root.createNestedArray("wifi"); JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<3; i++) {
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() == 0) break;
JsonObject& network = wifi.createNestedObject(); JsonObject& network = wifi.createNestedObject();
network["ssid"] = getSetting("ssid" + String(i)); network["ssid"] = getSetting("ssid" + String(i));
network["pass"] = getSetting("pass" + String(i)); network["pass"] = getSetting("pass" + String(i));
network["ip"] = getSetting("ip" + String(i));
network["gw"] = getSetting("gw" + String(i));
network["mask"] = getSetting("mask" + String(i));
network["dns"] = getSetting("dns" + String(i));
} }
String output; String output;
@ -318,6 +389,8 @@ bool _wsAuth(AsyncWebSocketClient * client) {
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
static uint8_t * message;
// Authorize // Authorize
#ifndef NOWSAUTH #ifndef NOWSAUTH
if (!_wsAuth(client)) return; if (!_wsAuth(client)) return;
@ -334,7 +407,27 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
} else if(type == WS_EVT_PONG) { } else if(type == WS_EVT_PONG) {
DEBUG_MSG("[WEBSOCKET] #%u pong(%u): %s\n", client->id(), len, len ? (char*) data : ""); DEBUG_MSG("[WEBSOCKET] #%u pong(%u): %s\n", client->id(), len, len ? (char*) data : "");
} else if(type == WS_EVT_DATA) { } else if(type == WS_EVT_DATA) {
_wsParse(client->id(), data, len);
AwsFrameInfo * info = (AwsFrameInfo*)arg;
// First packet
if (info->index == 0) {
//Serial.printf("Before malloc: %d\n", ESP.getFreeHeap());
message = (uint8_t*) malloc(info->len);
//Serial.printf("After malloc: %d\n", ESP.getFreeHeap());
}
// Store data
memcpy(message + info->index, data, len);
// Last packet
if (info->index + len == info->len) {
_wsParse(client->id(), message, info->len);
//Serial.printf("Before free: %d\n", ESP.getFreeHeap());
free(message);
//Serial.printf("After free: %d\n", ESP.getFreeHeap());
}
} }
} }

code/src/wifi.ino → code/espurna/wifi.ino View File

@ -52,9 +52,24 @@ void wifiConfigure() {
jw.setSoftAP(getSetting("hostname", HOSTNAME).c_str(), getSetting("adminPass", ADMIN_PASS).c_str()); jw.setSoftAP(getSetting("hostname", HOSTNAME).c_str(), getSetting("adminPass", ADMIN_PASS).c_str());
jw.setAPMode(AP_MODE_ALONE); jw.setAPMode(AP_MODE_ALONE);
jw.cleanNetworks(); jw.cleanNetworks();
if (getSetting("ssid0").length() > 0) jw.addNetwork(getSetting("ssid0").c_str(), getSetting("pass0").c_str());
if (getSetting("ssid1").length() > 0) jw.addNetwork(getSetting("ssid1").c_str(), getSetting("pass1").c_str());
if (getSetting("ssid2").length() > 0) jw.addNetwork(getSetting("ssid2").c_str(), getSetting("pass2").c_str());
for (int i = 0; i< WIFI_MAX_NETWORKS; i++) {
if (getSetting("ssid" + String(i)).length() == 0) break;
if (getSetting("ip" + String(i)).length() == 0) {
jw.addNetwork(
getSetting("ssid" + String(i)).c_str(),
getSetting("pass" + String(i)).c_str()
);
} else {
jw.addNetwork(
getSetting("ssid" + String(i)).c_str(),
getSetting("pass" + String(i)).c_str(),
getSetting("ip" + String(i)).c_str(),
getSetting("gw" + String(i)).c_str(),
getSetting("mask" + String(i)).c_str(),
getSetting("dns" + String(i)).c_str()
);
}
}
} }
void wifiSetup() { void wifiSetup() {

+ 7
- 5
code/gulpfile.js View File

@ -34,9 +34,11 @@ const useref = require('gulp-useref');
const gulpif = require('gulp-if'); const gulpif = require('gulp-if');
const inline = require('gulp-inline'); const inline = require('gulp-inline');
const destination = 'espurna/data/';
/* Clean destination folder */ /* Clean destination folder */
gulp.task('clean', function() { gulp.task('clean', function() {
del(['data/*']);
del([ destination + '*']);
return true; return true;
}); });
@ -46,7 +48,7 @@ gulp.task('files', function() {
'html/**/*.{jpg,jpeg,png,ico,gif}', 'html/**/*.{jpg,jpeg,png,ico,gif}',
'html/fsversion' 'html/fsversion'
]) ])
.pipe(gulp.dest('data/'));
.pipe(gulp.dest(destination));
}); });
@ -66,7 +68,7 @@ gulp.task('inline', function() {
minifyJS: true minifyJS: true
})) }))
.pipe(gzip()) .pipe(gzip())
.pipe(gulp.dest('data'));
.pipe(gulp.dest(destination));
}) })
/* Process HTML, CSS, JS */ /* Process HTML, CSS, JS */
@ -83,10 +85,10 @@ gulp.task('html', function() {
minifyJS: true minifyJS: true
}))) })))
.pipe(gzip()) .pipe(gzip())
.pipe(gulp.dest('data'));
.pipe(gulp.dest(destination));
}); });
/* Build file system */ /* Build file system */
gulp.task('buildfs_split', ['clean', 'files', 'html']); gulp.task('buildfs_split', ['clean', 'files', 'html']);
gulp.task('buildfs_inline', ['clean', 'files', 'inline']); gulp.task('buildfs_inline', ['clean', 'files', 'inline']);
gulp.task('default', ['buildfs_inline']);
gulp.task('default', ['buildfs_split']);

+ 24
- 2
code/html/custom.css View File

@ -41,6 +41,15 @@
background: rgb(0, 202, 0); background: rgb(0, 202, 0);
margin-left: 5px; margin-left: 5px;
} }
.button-add-network {
background: rgb(28, 184, 65);
}
.button-del-network {
background: rgb(202, 60, 60);
}
.button-more-network {
background: rgb(223, 117, 20);
}
.pure-g { .pure-g {
margin-bottom: 20px; margin-bottom: 20px;
} }
@ -57,10 +66,23 @@ div.hint {
font-size: 80%; font-size: 80%;
color: #ccc; color: #ccc;
} }
.break {
margin-top: 5px;
}
#networks .pure-g {
padding-bottom: 10px;
margin-bottom: 5px;
border-bottom: 2px dashed #e5e5e5;
}
#networks div.more {
display: none;
}
.module { .module {
display: none; display: none;
} }
#relayTemplate {
.template {
display: none; display: none;
} }
.pure-form .center {
margin: .5em 0 .2em;
}

+ 118
- 9
code/html/custom.js View File

@ -1,10 +1,42 @@
var websock; var websock;
var password = false; var password = false;
var maxNetworks;
// http://www.the-art-of-web.com/javascript/validate-password/
function checkPassword(str) {
// at least one number, one lowercase and one uppercase letter
// at least eight characters that are letters, numbers or the underscore
var re = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])\w{8,}$/;
return re.test(str);
}
function validateForm() {
var form = $("#formSave");
// password
var adminPass1 = $("input[name='adminPass1']", form).val();
if (adminPass1.length > 0 && !checkPassword(adminPass1)) {
alert("The password you have entered is not valid, it must have at least 8 characters, 1 lower and 1 uppercase and 1 number!");
return false;
}
var adminPass2 = $("input[name='adminPass2']", form).val();
if (adminPass1 != adminPass2) {
alert("Passwords are different!");
return false;
}
return true;
}
function doUpdate() { function doUpdate() {
var data = $("#formSave").serializeArray();
websock.send(JSON.stringify({'config': data}));
$(".powExpected").val(0);
if (validateForm()) {
var data = $("#formSave").serializeArray();
websock.send(JSON.stringify({'config': data}));
$(".powExpected").val(0);
}
return false; return false;
} }
@ -84,6 +116,55 @@ function createRelays(count) {
} }
function createIdxs(count) {
var current = $("#idxs > div").length;
if (current > 0) return;
var template = $("#idxTemplate .pure-g")[0];
for (var id=0; id<count; id++) {
var line = $(template).clone();
$(line).find("input").each(function() {
$(this).attr("data", id).attr("tabindex", 43+id);
});
if (count > 1) $(".id", line).html(" " + id);
line.appendTo("#idxs");
}
}
function delNetwork() {
var parent = $(this).parents(".pure-g");
$(parent).remove();
}
function moreNetwork() {
var parent = $(this).parents(".pure-g");
$("div.more", parent).toggle();
}
function addNetwork() {
var numNetworks = $("#networks > div").length;
if (numNetworks >= maxNetworks) {
alert("Max number of networks reached");
return;
}
var tabindex = 200 + numNetworks * 10;
var template = $("#networkTemplate").children();
var line = $(template).clone();
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex++);
});
$(line).find(".button-del-network").on('click', delNetwork);
$(line).find(".button-more-network").on('click', moreNetwork);
line.appendTo("#networks");
return line;
}
function processData(data) { function processData(data) {
// title // title
@ -130,17 +211,32 @@ function processData(data) {
} }
if (key == "maxNetworks") {
maxNetworks = parseInt(data.maxNetworks);
return;
}
// Wifi // Wifi
if (key == "wifi") { if (key == "wifi") {
var groups = $("#panel-wifi .pure-g");
for (var i in data.wifi) {
var networks = data.wifi;
for (var i in networks) {
// add a new row
var line = addNetwork();
// fill in the blanks
var wifi = data.wifi[i]; var wifi = data.wifi[i];
Object.keys(wifi).forEach(function(key) { Object.keys(wifi).forEach(function(key) {
var id = "input[name=" + key + "]";
if ($(id, groups[i]).length) $(id, groups[i]).val(wifi[key]);
var element = $("input[name=" + key + "]", line);
if (element.length) element.val(wifi[key]);
}); });
};
}
return; return;
} }
// Relay status // Relay status
@ -162,6 +258,19 @@ function processData(data) {
} }
// Domoticz
if (key == "dczIdx") {
var idxs = data.dczIdx;
createIdxs(idxs.length);
for (var i in idxs) {
var element = $(".dczIdx[data=" + i + "]");
if (element.length > 0) element.val(idxs[i]);
}
return;
}
// Messages // Messages
if (key == "message") { if (key == "message") {
@ -172,7 +281,6 @@ function processData(data) {
// Enable options // Enable options
if (key.endsWith("Visible")) { if (key.endsWith("Visible")) {
var module = key.slice(0,-7); var module = key.slice(0,-7);
console.log(module);
$(".module-" + module).show(); $(".module-" + module).show();
return; return;
} }
@ -244,6 +352,7 @@ function init() {
$(".button-reconnect").on('click', doReconnect); $(".button-reconnect").on('click', doReconnect);
$(".button-apikey").on('click', doGenerateAPIKey); $(".button-apikey").on('click', doGenerateAPIKey);
$(".pure-menu-link").on('click', showPanel); $(".pure-menu-link").on('click', showPanel);
$(".button-add-network").on('click', addNetwork);
$.ajax({ $.ajax({
'method': 'GET', 'method': 'GET',


+ 0
- 1
code/html/fsversion View File

@ -1 +0,0 @@
1.1.0

+ 122
- 59
code/html/index.html View File

@ -52,6 +52,10 @@
<a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a> <a href="#" class="pure-menu-link" data="panel-mqtt">MQTT</a>
</li> </li>
<li class="pure-menu-item module module-dcz">
<a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a>
</li>
<li class="pure-menu-item module module-pow"> <li class="pure-menu-item module module-pow">
<a href="#" class="pure-menu-link" data="panel-power">POWER</a> <a href="#" class="pure-menu-link" data="panel-power">POWER</a>
</li> </li>
@ -107,8 +111,8 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="ip">IP</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="ip" readonly />
<label class="pure-u-1 pure-u-sm-1-4" for="deviceip">IP</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="deviceip" readonly />
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -139,14 +143,6 @@
<div id="relays"> <div id="relays">
</div> </div>
<div id="relayTemplate">
<div class="pure-g">
<div class="pure-u-1 pure-u-sm-1-4"><label >Relay<span class="relay_id"></span> Status</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" class="relayStatus" data="0" /></div>
</div>
</div>
</fieldset> </fieldset>
</form> </form>
@ -242,14 +238,16 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass1">Admin password</label> <label class="pure-u-1 pure-u-md-1-4" for="adminPass1">Admin password</label>
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="3" />
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="11" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div> <div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
It should have at least <strong>eight characters</strong> (letters, numbers or the underscore) and at least <strong>one number</strong>, <strong>one lowercase</strong> and <strong>one uppercase</strong> letter.</div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Admin password (repeat)</label> <label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Admin password (repeat)</label>
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="3" />
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="12" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -259,7 +257,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="apiKey">HTTP API Key</label> <label class="pure-u-1 pure-u-md-1-4" for="apiKey">HTTP API Key</label>
<input name="apiKey" class="pure-u-3-4 pure-u-md-1-2" type="text" tabindex="4" />
<input name="apiKey" class="pure-u-3-4 pure-u-md-1-2" type="text" tabindex="13" />
<div class=" pure-u-1-4 pure-u-md-1-4"><button class="pure-button button-apikey pure-u-23-24">Generate</button></div> <div class=" pure-u-1-4 pure-u-md-1-4"><button class="pure-button button-apikey pure-u-23-24">Generate</button></div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div> <div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">This is the key you will have to pass with every HTTP request to the API, either to get or write values.</div> <div class="pure-u-1 pure-u-md-3-4 hint">This is the key you will have to pass with every HTTP request to the API, either to get or write values.</div>
@ -273,51 +271,17 @@
<div class="header"> <div class="header">
<h1>WIFI</h1> <h1>WIFI</h1>
<h2>You can configure up to 3 different WiFi networks. The device will try to connect in order of signal strength.</h2>
<h2>You can configure up to 5 different WiFi networks. The device will try to connect in order of signal strength.</h2>
</div> </div>
<div class="page"> <div class="page">
<fieldset> <fieldset>
<legend>First network</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<label for="ssid">SSID</label>
<input type="text" class="pure-u-23-24" tabindex="10" name="ssid" />
</div>
<div class="pure-u-1 pure-u-md-1-2">
<label for="pass">Password</label>
<input type="text" class="pure-u-23-24" tabindex="11" name="pass" />
</div>
</div>
<legend>Second network</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<label for="ssid">SSID</label>
<input type="text" class="pure-u-23-24" tabindex="12" name="ssid" />
</div>
<div class="pure-u-1 pure-u-md-1-2">
<label for="pass">Password</label>
<input type="text" class="pure-u-23-24" tabindex="13" name="pass" />
</div>
<div id="networks">
</div> </div>
<legend>Third network</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-md-1-2">
<label for="ssid">SSID</label>
<input type="text" class="pure-u-23-24" tabindex="14" name="ssid" />
</div>
<div class="pure-u-1 pure-u-md-1-2">
<label for="pass">Password</label>
<input type="text" class="pure-u-23-24" tabindex="15" name="pass" />
</div>
</div>
<button type="button" class="pure-button button-add-network">Add network</button>
</fieldset> </fieldset>
</div> </div>
@ -336,29 +300,65 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttServer">MQTT Server</label> <label class="pure-u-1 pure-u-md-1-4" for="mqttServer">MQTT Server</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttServer" type="text" size="20" tabindex="10" placeholder="MQTT Server" />
<input class="pure-u-1 pure-u-md-3-4" name="mqttServer" type="text" size="20" tabindex="21" placeholder="MQTT Server" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttPort">MQTT Port</label> <label class="pure-u-1 pure-u-md-1-4" for="mqttPort">MQTT Port</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttPort" type="text" size="6" tabindex="11" placeholder="1883" />
<input class="pure-u-1 pure-u-md-3-4" name="mqttPort" type="text" size="6" tabindex="22" placeholder="1883" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttUser">MQTT User</label> <label class="pure-u-1 pure-u-md-1-4" for="mqttUser">MQTT User</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttUser" type="text" size="20" tabindex="12" placeholder="Leave blank if no user/pass" />
<input class="pure-u-1 pure-u-md-3-4" name="mqttUser" type="text" size="20" tabindex="23" placeholder="Leave blank if no user/pass" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttPassword">MQTT Password</label> <label class="pure-u-1 pure-u-md-1-4" for="mqttPassword">MQTT Password</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttPassword" type="text" size="20" tabindex="13" placeholder="Leave blank if no user/pass" />
<input class="pure-u-1 pure-u-md-3-4" name="mqttPassword" type="text" size="20" tabindex="24" placeholder="Leave blank if no user/pass" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttTopic">MQTT Topic</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttTopic" type="text" size="20" tabindex="14" />
<label class="pure-u-1 pure-u-md-1-4" for="mqttTopic">MQTT Root Topic</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttTopic" type="text" size="20" tabindex="25" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div> <div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Send a 0 or a 1 as a payload to the provided topic below to switch it on or off. You can also send a 2 to toggle its current state. The switch will also report its current open/close status to the same topic and its IP address, hertbeat, firmware version and file system version to the topic you define plus "/ip", "/heartbeat", "/version" and "/fsversion" respectively. </div>
<div class="pure-u-1 pure-u-md-3-4 hint">This is the root topic for this device. The {identifier} placeholder will be replaces by the device hostname.<br />
- <strong>&lt;root&gt;/relay/#</strong> Send a 0 or a 1 as a payload to this topic to switch it on or off. You can also send a 2 to toggle its current state. Replace # with the relay ID (starting from 0). If the board has only one relay it will be 0.<br />
- <strong>&lt;root&gt;/led/#</strong> Send a 0 or a 1 as a payload to this topic to set the onboard LED to the given state, send a 3 to turn it back to WIFI indicator. Replace # with the LED ID (starting from 0). If the board has only one LED it will be 0.<br />
- <strong>&lt;root&gt;/ip</strong> The device will report to this topic its IP.<br />
- <strong>&lt;root&gt;/heartbeat</strong> The device will report to this topic every few minutes.<br />
- <strong>&lt;root&gt;/version</strong> The device will report to this topic its firmware version on boot.<br />
</div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-domoticz">
<div class="header">
<h1>DOMOTICZ</h1>
<h2>
Configure the connection to your Domoticz server.
</h2>
</div>
<div class="page">
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="dczTopicIn">Domoticz IN Topic</label>
<input class="pure-u-1 pure-u-md-3-4" name="dczTopicIn" type="text" tabindex="31" placeholder="domoticz/in" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="dczTopicOut">Domoticz OUT Topic</label>
<input class="pure-u-1 pure-u-md-3-4" name="dczTopicOut" type="text" tabindex="32" placeholder="domoticz/out" />
</div>
<div id="idxs">
</div> </div>
</fieldset> </fieldset>
@ -381,7 +381,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="powExpectedPower">AC RMS Active Power</label> <label class="pure-u-1 pure-u-md-1-4" for="powExpectedPower">AC RMS Active Power</label>
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedPower" type="text" size="8" tabindex="40" placeholder="0" />
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedPower" type="text" size="8" tabindex="41" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div> <div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">If you are using a pure resistive load like a bulb this will be writen on it, otherwise use a socket multimeter to get this value.</div> <div class="pure-u-1 pure-u-md-3-4 hint">If you are using a pure resistive load like a bulb this will be writen on it, otherwise use a socket multimeter to get this value.</div>
</div> </div>
@ -396,6 +396,69 @@
</div> <!-- layout --> </div> <!-- layout -->
<!-- Templates -->
<div id="networkTemplate" class="template">
<div class="pure-g">
<label class="pure-u-md-1-6 pure-u-1-4" for="ssid">Network SSID</label>
<div class="pure-u-md-3-4 pure-u-5-8"><input name="ssid" type="text" class="pure-u-23-24" value="" size="8" tabindex="0" placeholder="Network SSID" required /></div>
<div class="pure-u-md-1-12 pure-u-1-8"><button type="button" class="pure-button button-more-network pure-u-1">...</button></div>
<div class="more">
<div class="break"></div>
<label class="pure-u-md-1-6 pure-u-1-4" for="pass">Password</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="pass" type="password" value="" tabindex="0" />
<div class="break"></div>
<label class="pure-u-md-1-6 pure-u-1-4" for="ip">Static IP</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="ip" type="text" value="" size="15" tabindex="0" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Leave empty for DNS negotiation</div>
<label class="pure-u-md-1-6 pure-u-1-4" for="gw">Gateway IP</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="gw" type="text" value="" size="15" tabindex="0" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Set when using a static IP</div>
<label class="pure-u-md-1-6 pure-u-1-4" for="mask">Network Mask</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="mask" type="text" value="" size="15" tabindex="0" placeholder="255.255.255.0" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Usually 255.255.255.0 for /24 networks</div>
<label class="pure-u-md-1-6 pure-u-1-4" for="dns">DNS IP</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="dns" type="text" value="" size="15" tabindex="0" placeholder="8.8.8.8" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Set the Domain Name Server IP to use when using a static IP</div>
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-1-6 pure-u-1-4"><button type="button" class="pure-button button-del-network pure-u-5-6 pure-u-md-5-6">Del</button></div>
</div>
</div>
</div>
<div id="relayTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Relay<span class="relay_id"></span> Status</label>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" class="relayStatus" data="0" /></div>
</div>
</div>
<div id="idxTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Relay<span class="id"></span> IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24 dczIdx" name="dczIdx" type="number" min="0" tabindex="0" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
</div>
</body> </body>
<!-- build:js script.js --> <!-- build:js script.js -->


+ 0
- 115
code/lib/DebounceEvent/DebounceEvent.cpp View File

@ -1,115 +0,0 @@
/*
Debounce buttons and trigger events
Copyright (C) 2015 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "DebounceEvent.h"
DebounceEvent::DebounceEvent(uint8_t pin, callback_t callback, uint8_t defaultStatus, unsigned long delay) {
_callback = callback;
DebounceEvent(pin, defaultStatus, delay);
}
DebounceEvent::DebounceEvent(uint8_t pin, uint8_t defaultStatus, unsigned long delay) {
// store configuration
_pin = pin;
_status = _defaultStatus = defaultStatus;
_delay = delay;
// set up button
if (_defaultStatus == LOW) {
pinMode(_pin, INPUT);
} else {
pinMode(_pin, INPUT_PULLUP);
}
}
bool DebounceEvent::loop() {
// holds whether status has changed or not
bool changed = false;
_event = EVENT_NONE;
if (digitalRead(_pin) != _status) {
// Debounce
delay(_delay);
uint8_t newStatus = digitalRead(_pin);
if (newStatus != _status) {
changed = true;
_clicked = false;
_status = newStatus;
// released
if (_status == _defaultStatus) {
// get event
if (millis() - _this_start > LONG_CLICK_DELAY) {
_event = EVENT_LONG_CLICK;
} else if (millis() - _last_start < DOUBLE_CLICK_DELAY ) {
_event = EVENT_DOUBLE_CLICK;
} else {
// We are not setting the event type here because we still don't
// know what kind of event it will be (it might be a double click).
// Instead we are setting the _clicked variable to check later
_clicked = true;
changed = false;
}
// pressed
} else {
_last_start = _this_start;
_this_start = millis();
_event = EVENT_PRESSED;
}
}
}
if (_clicked && (millis() - _this_start > DOUBLE_CLICK_DELAY) && (!changed) && (_status == _defaultStatus)) {
_clicked = false;
changed = true;
_event = EVENT_SINGLE_CLICK;
}
if (changed) {
if (_callback) {
_callback(_pin, EVENT_CHANGED);
_callback(_pin, _event);
}
}
return changed;
}
bool DebounceEvent::pressed() {
return (_status != _defaultStatus);
}
uint8_t DebounceEvent::getEvent() {
return _event;
}

+ 0
- 62
code/lib/DebounceEvent/DebounceEvent.h View File

@ -1,62 +0,0 @@
/*
Debounce buttons and trigger events
Copyright (C) 2015 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _DEBOUNCE_EVENT_h
#define _DEBOUNCE_EVENT_h
#define DEBOUNCE_DELAY 50
#define LONG_CLICK_DELAY 1000
#define DOUBLE_CLICK_DELAY 500
#define EVENT_NONE 0
#define EVENT_CHANGED 1
#define EVENT_PRESSED 2
#define EVENT_RELEASED 3
#define EVENT_SINGLE_CLICK 3
#define EVENT_DOUBLE_CLICK 4
#define EVENT_LONG_CLICK 5
typedef void(*callback_t)(uint8_t pin, uint8_t event);
class DebounceEvent {
private:
uint8_t _pin;
uint8_t _status;
uint8_t _event;
bool _clicked = false;
unsigned long _this_start;
unsigned long _last_start;
uint8_t _defaultStatus;
unsigned long _delay;
callback_t _callback = false;
public:
DebounceEvent(uint8_t pin, callback_t callback, uint8_t defaultStatus = HIGH, unsigned long delay = DEBOUNCE_DELAY);
DebounceEvent(uint8_t pin, uint8_t defaultStatus = HIGH, unsigned long delay = DEBOUNCE_DELAY);
bool pressed();
bool loop();
uint8_t getEvent();
};
#endif

+ 0
- 1
code/lib/WProgram/WProgram.h View File

@ -1 +0,0 @@
#include <Arduino.h>

+ 1
- 18
code/pio_hooks.py View File

@ -5,24 +5,7 @@ import socket
from SCons.Script import DefaultEnvironment from SCons.Script import DefaultEnvironment
env = DefaultEnvironment() env = DefaultEnvironment()
def is_valid_ip(ip):
try:
socket.inet_aton(ip)
return True
except socket.error:
return False
def before_build_spiffs(source, target, env): def before_build_spiffs(source, target, env):
env.Execute("gulp buildfs_split")
def before_upload(source, target, env):
upload_port = env.get('UPLOAD_PORT', False)
if upload_port and upload_port[0] == '/':
cmd = ["mosquitto_sub", "-t", upload_port, "-h", "192.168.1.10", "-N", "-C", "1"]
ip = subprocess.check_output(cmd)
if is_valid_ip(ip):
env['UPLOAD_PORT'] = '"' + ip + '"'
env.Execute("gulp")
#env.AddPreAction("uploadfs", before_upload)
#env.AddPreAction("upload", before_upload)
env.AddPreAction(".pioenvs/%s/spiffs.bin" % env['PIOENV'], before_build_spiffs) env.AddPreAction(".pioenvs/%s/spiffs.bin" % env['PIOENV'], before_build_spiffs)

+ 0
- 132
code/platformio.custom.ini View File

@ -1,132 +0,0 @@
#
# PLEASE NOTE:
# This platformio.ini file is not standard and uses a custom feature.
# The "include" option is not supported by the official version.
# Check my PR for this feature here:
# https://github.com/platformio/platformio/pull/790
#
[platformio]
env_default = node-debug
[common]
platform = espressif8266_stage
framework = arduino
extra_script = pio_hooks.py
#lib_ignore = FauxmoESP, ESPAsyncUDP
lib_deps =
DHT sensor library
Adafruit Unified Sensor
Time
ArduinoJson
ESPAsyncTCP
ESPAsyncWebServer
https://github.com/marvinroger/async-mqtt-client
ESPAsyncUDP
Embedis
NtpClientLib
https://bitbucket.org/xoseperez/justwifi.git
https://bitbucket.org/xoseperez/nofuss.git
https://bitbucket.org/xoseperez/hlw8012.git
https://bitbucket.org/xoseperez/emonliteesp.git
https://bitbucket.org/xoseperez/fauxmoESP.git
https://github.com/jccprj/RemoteSwitch-arduino-library
[ota]
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
[def]
build_flags_debug = -g -Wl,-Tesp8266.flash.1m128.ld -DDEBUG_PORT=Serial -DENABLE_FAUXMO=1
build_flags_ota = -Wl,-Tesp8266.flash.1m128.ld -DENABLE_FAUXMO=1
# ------------------------------------------------------------------------------
[env:node-debug]
include = common
board = nodemcuv2
platform = espressif8266
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial
[env:node-debug-ota]
include = env:node-debug,ota
[env:sonoff-debug]
include = common
board = esp01_1m
build_flags = ${def.build_flags_debug} -DSONOFF
[env:sonoff-ota]
include = env:sonoff-debug,ota
build_flags = ${def.build_flags_ota} -DSONOFF
[env:sonoff-dht22-debug]
include = env:sonoff-debug
build_flags = ${def.build_flags_debug} -DSONOFF -DENABLE_DHT=1
[env:sonoff-ds18b20-debug]
include = env:sonoff-debug
build_flags = ${def.build_flags_debug} -DSONOFF -DENABLE_DS18B20=1
[env:sonoff-pow-debug]
include = common
board = esp01_1m
build_flags = ${def.build_flags_debug} -DSONOFF_POW
[env:sonoff-pow-ota]
include = env:sonoff-pow-debug,ota
build_flags = ${def.build_flags_ota} -DSONOFF_POW
[env:sonoff-dual-debug]
include = common
board = esp12e
build_flags = ${def.build_flags_debug} -DSONOFF_DUAL
[env:sonoff-dual-ota]
include = env:sonoff-dual-debug,ota
build_flags = ${def.build_flags_ota} -DSONOFF_DUAL
[env:slampher-debug]
include = common
board = esp01_1m
build_flags = ${def.build_flags_debug} -DSLAMPHER
[env:slampher-ota]
include = env:slampher-debug,ota
build_flags = ${def.build_flags_ota} -DSLAMPHER
[env:s20-debug]
include = common
board = esp01_1m
build_flags = ${def.build_flags_debug} -DS20
[env:s20-ota]
include = env:s20-debug,ota
build_flags = ${def.build_flags_ota} -DS20
[env:electrodragon-debug]
include = common
board = esp12e
build_flags = -g -DDEBUG_PORT=Serial -DESP_RELAY_BOARD -DENABLE_FAUXMO=1 -DENABLE_DHT=1
[env:electrodragon-ota]
include = env:electrodragon-debug,ota
build_flags = -DESP_RELAY_BOARD -DENABLE_FAUXMO=1 -DENABLE_DHT=1
# ------------------------------------------------------------------------------
[env:studio-lamp-device]
topic = /home/studio/lamp/ip
include = env:sonoff-debug-ota
platform = espressif8266_stage
build_flags = -g -Wl,-Tesp8266.flash.1m128.ld -DDEBUG_PORT=Serial -DSONOFF -DENABLE_FAUXMO=1
lib_ignore =
[env:living-lamp-device]
topic = /home/living/lamp/ip
include = env:s20-debug-ota
platform = espressif8266_stage
build_flags = -g -Wl,-Tesp8266.flash.1m128.ld -DDEBUG_PORT=Serial -DS20 -DENABLE_FAUXMO=1
lib_ignore =

+ 32
- 9
code/platformio.ini View File

@ -1,5 +1,7 @@
[platformio] [platformio]
env_default = node-debug
env_default = d1-debug
src_dir = espurna
data_dir = espurna/data
[common] [common]
lib_deps = lib_deps =
@ -10,21 +12,42 @@ lib_deps =
ESPAsyncTCP ESPAsyncTCP
ESPAsyncWebServer ESPAsyncWebServer
https://github.com/marvinroger/async-mqtt-client https://github.com/marvinroger/async-mqtt-client
ESPAsyncUDP
Embedis Embedis
NtpClientLib NtpClientLib
OneWire OneWire
DallasTemperature DallasTemperature
https://bitbucket.org/xoseperez/justwifi.git
JustWifi
HLW8012
https://bitbucket.org/xoseperez/fauxmoesp.git
https://bitbucket.org/xoseperez/nofuss.git https://bitbucket.org/xoseperez/nofuss.git
https://bitbucket.org/xoseperez/hlw8012.git
https://bitbucket.org/xoseperez/emonliteesp.git https://bitbucket.org/xoseperez/emonliteesp.git
https://bitbucket.org/xoseperez/fauxmoESP.git
https://github.com/jccprj/RemoteSwitch-arduino-library
lib_ignore = FauxmoESP, ESPAsyncUDP
https://bitbucket.org/xoseperez/debounceevent.git
https://github.com/xoseperez/RemoteSwitch-arduino-library.git
lib_ignore =
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
[env:d1-debug]
platform = espressif8266
framework = arduino
board = d1_mini
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -DD1_RELAYSHIELD -DDEBUG_PORT=Serial -DDEBUG_FAUXMO=Serial -DNOWSAUTH
[env:d1-debug-ota]
platform = espressif8266
framework = arduino
board = d1_mini
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py
build_flags = -g -DD1_RELAYSHIELD -DDEBUG_PORT=Serial -DDEBUG_FAUXMO=Serial -DNOWSAUTH
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
[env:node-debug] [env:node-debug]
platform = espressif8266 platform = espressif8266
framework = arduino framework = arduino
@ -32,7 +55,7 @@ board = nodemcuv2
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py extra_script = pio_hooks.py
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial -DNOWSAUTH
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial -DDEBUG_FAUXMO=Serial -DNOWSAUTH
[env:node-debug-ota] [env:node-debug-ota]
platform = espressif8266 platform = espressif8266
@ -41,7 +64,7 @@ board = nodemcuv2
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
extra_script = pio_hooks.py extra_script = pio_hooks.py
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial -DNOWSAUTH
build_flags = -g -DNODEMCUV2 -DDEBUG_PORT=Serial -DDEBUG_FAUXMO=Serial -DNOWSAUTH
upload_speed = 115200 upload_speed = 115200
upload_port = "192.168.4.1" upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266 upload_flags = --auth=fibonacci --port 8266


+ 0
- 21
docs/Configuration.md View File

@ -1,21 +0,0 @@
# Configuration
*TODO*
## First boot
On normal boot (i.e. button not pressed) it will execute the firmware. It configures the hardware (button, LED, relay), the SPIFFS memory access, the WIFI, the WebServer and MQTT connection and whatever other enabled modules.
Obviously the default values for WIFI network and MQTT will probably not match your requirements. The device will start in Soft AP creating a WIFI SSID named "DEVICE_XXXXXX", where DEVICE will be an identifier of your device and XXXXXX are the last 3 bytes of the radio MAC. Connect with phone, PC, laptop, whatever to that network, password is "fibonacci". Once connected browse to 192.168.4.1. It will then present an **authentication challenge**. Default user and password are "admin" and "fibonacci" (again). Then you will be presented a configuration page where you will be able to define different configuration parameters, including changing the default password. The same password is used for the WIFI Access Point, for the web interface and for the OTA firmware upload.
## WiFi configuration
You can configure up to three WIFI networks. It will then try to connect to the configure WIFI networks in order of signal strength. If none of the 3 attempts succeed it will default to SoftAP mode again. You can also switch to SoftAP mode by double click the on-board button or reset the board long clicking it.
## MQTT
Once connected to an access point (so in station mode) the board will try to connect the MQTT server defined.
The device will publish the relay state to the given topic and it will subscribe to the same topic for remote switching. Don't worry, it avoids infinite loops.
You can also use "{identifier}" as place holder in the topic. It will be translated to your device ID (same as the soft AP network it creates).

+ 0
- 77
docs/Filesystem.md View File

@ -1,77 +0,0 @@
# Filesystem
## Introduction
Normally when you flash an ESP8266 you only flash the firmware, like for any other microcontroller. But the ESP8266 has plenty of room and normally it is split into different partitions. One such partition is used to store web files like a normal webserver.
Problem is that, even thou the ESP8266 is a very capable microcontroller it has its limitations and does not handle very well concurrent connections. This is specially true for the *default* webserver that comes with the Arduino Core for ESP8266.
On the other side, to provide a good user experience and work with a comfortable development environment you end up having different files: scripts, stylesheet files, images,... The browser will load the index.html file and quickly request for all those other files and the ESP8266 will easily struggle trying to serve them all.
So the trick here is to squeeze them all into one single compressed file just before uploading it. Luckily, we can do that automatically.
## Web interface build process
The build process reads the HTML files, looks for the stylesheet and script files linked there, grabs them, minifies them and includes them inline, in the same order they are loaded. The resulting single HTML file is then cleaned, further minified and compressed resulting in a single index.html.gz file. This way the ESP8266 webserver can serve it really fast.
To build this file we are using **[Gulp][1]**, a build system built in [node.js][2]. So you will need node (and [npm][3], its package manager) first. [Read the documentation][4] on how to install them.
Once you have node and npm installed, go to the 'code' folder and install all the dependencies with:
```
npm install
```
It will take a minute or two. Then you are ready to build the webserver files with:
```
gulp
```
It will create a populate a 'data' folder with all the required files.
## Images
Along with the HTML, CSS and JS files compressed into the index.html.gz file, the ESPurna firmware uses some images in its web interface. These are the favicon.ico file and a **sprite** for the iPhone style buttons in the interface. Again the idea is to use the minimum possible number of files.
## Flashing it
### Using PlatformIO
[PlatformIO][5] allows the developer to define hooks to be executed before or after certain actions. This is really cool since we can plug one such hook to the *uploadfs* target to automatically build the web interface before uploading it to the board.
This is done by specifying the script with the hook definitions in the *extra_script* option in the platformio.ini file. The script, written in python, binds the target file (spiffs.bin) to a method that executes the gulp command we have seen before.
```
#!/bin/python
from SCons.Script import DefaultEnvironment
env = DefaultEnvironment()
def before_build_spiffs(source, target, env):
env.Execute("gulp buildfs")
env.AddPreAction(".pioenvs/%s/spiffs.bin" % env['PIOENV'], before_build_spiffs)
```
The included platformio.ini file has all this already configured, so you don't have to worry about it. Just type:
```
pio run -t uploadfs -e sonoff
```
(or whatever other enviroment) and you are good to go.
### Using Arduino IDE
First you will have to manually build the data folder contents using gulp (see [instructions above](#web-interface-build-process)).
Then you will have to have the "[ESP8266 Sketch Data Upload][6]" utility installed. Check the instructions in the previous link. The data folder should be a subfolder of the code folder for the tool to find it. Then just execute it and it will upload the data folder contents to your board.
[1]: http://gulpjs.com/
[2]: https://nodejs.org/en/
[3]: https://www.npmjs.com/
[4]: https://docs.npmjs.com/getting-started/installing-node
[5]: http://www.platformio.org
[6]: https://github.com/esp8266/Arduino/blob/master/doc/filesystem.md#uploading-files-to-file-system

+ 0
- 56
docs/Firmware.md View File

@ -1,56 +0,0 @@
# Firmware
## Build the firmware
The project is ready to be build using [PlatformIO][1].
Please refer to their web page for instructions on how to install the builder.
If you are using PlatformIO /strongly recommended) it will take care of the library dependencies. Otherwise you will have to install them manually:
* Benoit Blanchon's [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
* Hristo Gochkov's [ESPAsyncTCP](https://github.com/me-no-dev/ESPAsyncTCP)
* Hristo Gochkov's [ESPAsyncUDP](https://github.com/me-no-dev/ESPAsyncUDP)
* Hristo Gochkov's [ESPAsyncWebServer](https://github.com/me-no-dev/ESPAsyncWebServer)
* Marvin Roger's [AyncMqttClient](https://github.com/marvinroger/async-mqtt-client)
* Adafruit's [DHT Sensor Library](https://github.com/adafruit/DHT-sensor-library) (required if compiling with DHT support: -DENABLE_DHT)
* Adafruit's [Unified Sensor Library](https://github.com/adafruit/Adafruit_Sensor) (required if compiling with DHT support: -DENABLE_DHT)
* Paul Stoffregen (et al.) [OneWire](https://github.com/PaulStoffregen/OneWire)
* Miles Burton (et al.) [DallasTemperature](https://github.com/milesburton/Arduino-Temperature-Control-Library)
* The PatternAgents (et al.) [Embedis](https://github.com/thingSoC/embedis)
* German Martin's [NtpCLientLib](https://github.com/gmag11/NtpClient)
* Michael Maregolis & Paul Stoffregen's [Time](https://github.com/PaulStoffregen/Time)
* Randy Simons' [RemoteSwitch](https://github.com/jccprj/RemoteSwitch-arduino-library) (required if using custom RF module: -DENABLE_RF)
And my own libraries:
* [JustWifi](https://bitbucket.org/xoseperez/justwifi.git)
* [FauxmoESP](https://bitbucket.org/xoseperez/fauxmoesp.git) (required if compiling with WeMo emulation support: -DENABLE_FAUXMO)
* [HLW8012](https://bitbucket.org/xoseperez/hlw8012.git) (required if compiling for Sonoff POW: -DENABLE_POW)
* [EmonLiteESP](https://bitbucket.org/xoseperez/emonliteesp.git) (required if compiling with Energy Monitoring support: -DENABLE_EMON)
* [NoFUSS](https://bitbucket.org/xoseperez/nofuss.git) (required if compiling with NoFUSS Automatic OTA support: -DENABLE_NOFUSS)
**Note**: The fauxmoESP library requires the staging version of Arduino Core for ESP8266. Either you are using Arduino IDE or PlatformIO you will have to manually install this. Check the [documentation for the fauxmoESP library](https://bitbucket.org/xoseperez/fauxmoesp) for more info.
Once you have all the code, you can check if it's working by:
```bash
> pio run -e node-debug
```
If it compiles you are ready to flash the firmware.
## Flash your board
Wire your board (check the [Hardware page](Hardware.md)) and flash the firmware (with ```upload```):
```bash
> pio run -t upload -e sonoff
```
(or any other environment, depending on the board you are working with).
Library dependencies are automatically managed via PlatformIO Library Manager or included via submodules and linked from the "lib" folder.
Once the firmware is uploaded next step is to upload the web interface. Check how to [build and flash the filesystem](Filesystem.md).
[1]: http://www.platformio.org

+ 0
- 216
docs/Hardware.md View File

@ -1,216 +0,0 @@
# Supported Hardware
This is the official list of supported hardware for the ESPurna firmware. The hardware configuration for each of these boards can be selected by supplying the build flag (see [Firmware section](Firmware.h)).
**CAUTION: Never ever connect any of these devices to your computer and to mains at the same time. Never ever manipulate them while connected to mains. Seriously. I don't want you to die. I hold no responsibility for any damage to you, your family, your house,... for any action or results derived from flashing or using these devices.**
* [IteadStudio Sonoff](#iteadstudio-sonoff)
* [IteadStudio Sonoff RF](#iteadstudio-sonoff-rf)
* [IteadStudio Sonoff TH](#iteadstudio-sonoff-th)
* [IteadStudio Sonoff POW](#iteadstudio-sonoff-pow)
* [IteadStudio Sonoff DUAL](#iteadstudio-sonoff-dual)
* [IteadStudio Sonoff TOUCH](#iteadstudio-sonoff-touch)
* [IteadStudio Sonoff 4CH](#iteadstudio-sonoff-4ch)
* [IteadStudio Sonoff SV](#iteadstudio-sonoff-sv)
* [IteadStudio Slampher](#iteadstudio-slampher)
* [IteadStudio S20](#iteadstudio-s20)
* [Electrodragon ESP Relay Board](#electrodragon-esp-relay-board)
* [WorkChoice EcoPlug](#workchoice-ecoplug)
## IteadStudio Sonoff
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-wifi-wireless-switch.html](https://www.itead.cc/sonoff-wifi-wireless-switch.html)|
|Build flag|SONOFF|
The [IteadStudio Sonoff][1] has an ESP8266 on board with a 8Mbit flash memory chip, a mains to 3V3 transformer and a relay (GPIO12). It also features a button (GPIO0), an LED (GPIO13) and an unpopulated header you can use to reprogram it.
### Flashing
![Sonoff - Inside front view](images/sonoff-flash.jpg)
The unpopulated header in the Sonoff has all the required pins. My board has a 5 pins header in-line with the button. They are (from the button outwards) 3V3, RX, TX, GND and GPIO14.
Last one is not necessary. Mind it's a **3V3 device**, if connected to 5V you will probably fry it. Button is connected to GPIO0 on the ESP8266 chip, so to enter flash mode you have to hold the button pressed while powering on the board, then you can release it again.
## IteadStudio Sonoff RF
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-rf.html][2]|
|Build flag|SONOFF_RF|
### Flashing
![Sonoff POW - Inside back view](images/sonoff-rf-flash.jpg)
The Sonoff RF has the same unpopulated header as the Sonoff. It is a 5 pins header in-line with the button. They are (from the button outwards) 3V3, RX, TX, GND and GPIO14.
Solder a male or female header here and connect your USB-to-UART programmer (remember **it's a 3V3 device**). This time through **the button is not connected to GPIO0** but to a EFM8BB1 microcontroller that also monitors the RF module output.
There are a couple of ways to enter flash mode. Some recommend to move 0Ohm R9 resistor to R21 to connect the button directly to the ESP8266 GPIO0 and use it in the same way as for the Sonoff or Sonoff TH. The drawback is the by doing that you lose the RF capability.
My recommendation is to **temporary shortcut the bottom pad of the unpopulated R21 footprint** (see the image above) and connect your USB-to-UART board at the same time. You will have to do it just once (unless there is something really wrong in the firmware) and use OTA updates from there on.
## IteadStudio Sonoff TH
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-th.html](https://www.itead.cc/sonoff-th.html)|
|Build flag|SONOFF_TH|
### Flashing
![Sonoff TH - Inside back view](images/sonoff-th-flash.jpg)
You have all the required pins in an unpopulated header in one of the corners of the board (see top left corner in the image above). Solder a 4 pins male or female header here and connect it to your favourite USB-to-UART module. Remember: **it's a 3V3 device**!!.
As in the Sonoff the button is connected to GPIO0, so to enter flash mode press and hold the button and connect the programmer to your computer to power the board.
## IteadStudio Sonoff POW
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-pow.html](https://www.itead.cc/sonoff-pow.html)|
|Build flag|SONOFF_POW|
### Flashing
![Sonoff POW - Inside back view](images/sonoff-pow-flash.jpg)
Same as for the [Sonoff TH](#iteadstudio-sonoff-th) above.
## IteadStudio Sonoff DUAL
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-dual.html](https://www.itead.cc/sonoff-dual.html)|
|Build flag|SONOFF_DUAL|
### Flashing
![Sonoff DUAL - Inside back view](images/sonoff-dual-flash.jpg)
The Sonoff Dual it's a bit tricky to flash since GPIO0 is not connected to the button as in the TH or POW, but to the pin 15 in the SIL F330 chip that manages the buttons and the relays. SO you have to locate a pad connected to GPIO and short it to ground while powering the device.
In the picture above you have a location of an available and easily accessible GPIO0 pad. The other required pins are brought out in the top header. Remember it's a *3V3* device.
Once flashed use OTA to update the firmware or the filesystem.
## IteadStudio Sonoff TOUCH
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-touch.html](https://www.itead.cc/sonoff-touch.html)|
|Build flag|SONOFF_TOUCH|
### Flashing
*TODO*
## IteadStudio Sonoff 4CH
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-4ch.html](https://www.itead.cc/sonoff-4ch.html)|
|Build flag|SONOFF_4CH|
### Flashing
*TODO*
## IteadStudio Sonoff SV
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/sonoff-sv.html](https://www.itead.cc/sonoff-sv.html)|
|Build flag|SONOFF_SV|
### Flashing
*TODO*
## IteadStudio Slampher
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/slampher.html](https://www.itead.cc/slampher.html)|
|Build flag|SLAMPHER|
### Flashing
![Slampher - Inside front view](images/slampher-flash1.jpg)
![Slampher - Flashing short](images/slampher-flash2.jpg)
There is a 4 pin unpopulated header in a border near the ESP8266 chip. Starting form the little white mark this header brings out 3V3, RX, TX and GND. Solder a male or female header here and connect your USB-to-UART programmer (remember it's a 3V3 device). This time through **the button is not connected to GPIO0** but to a EFM8BB1 microcontroller that also monitors the RF module output.
There are a couple of ways to enter flash mode. Some recommend to move R21 to R20 (at the top right of the first picture above) to connect the button directly to the ESP8266 GPIO0 and use it in the same way as for the Sonoff or Sonoff TH. The drawback is the by doing that you lose the RF capability.
My recommendation is to **temporary shortcut the right pad of the unpopulated R20 footprint** (see second image above) and connect your USB-to-UART board at the same time. You will have to do it just once (unless there is something really wrong in the firmware) and use OTA updates from there on.
## IteadStudio S20 Smart Socket
|Property|Value|
|---|---|
|Manufacturer|Itead Studio|
|Web page|[https://www.itead.cc/smart-socket.html](https://www.itead.cc/smart-socket.html)|
|Build flag|S20|
### Flashing
![S20 Smart Socket - Inside front view](images/s20-flash.jpg)
There is a labeled header in the front of the PCB and the button is connected to GPIO0, so no problems here.
Solder a 4 pin male or female header and connect it to your USB-to-UART bridge. Again, remember **it's a 3V3 device**. Then press and hold the button and connect the programmer to your computer. The microcontroller will boot into flash mode and you are ready to update the firmware.
## Electrodragon ESP Relay Board
|Property|Value|
|---|---|
|Manufacturer|Electrodragon|
|Web page|[http://www.electrodragon.com/product/wifi-iot-relay-board-based-esp8266/](http://www.electrodragon.com/product/wifi-iot-relay-board-based-esp8266/)|
|Build flag|ESP_RELAY_BOARD|
### Flashing
![Electrodragon ESP Relay Board - Front view](images/electrodragon-flash.jpg)
The Electrodragon ESP Relay Board is pretty easy to flash IF you do not follow their wiki, it's all wrong. Check the picture above and note that:
* Power the board from the 5V pin, GND to GND
* The RX pin in the header should go to your programmer TX pin and
* The TX pin in the header should go to your programmer RX pin
* The button labeled BTN2 is connected to GPIO0, so hold it down while powering the board, I've had better results keeping it down until the flashing starts
## WorkChoice EcoPlug
|Property|Value|
|---|---|
|Manufacturer|WorkChoice|
|Web page (non-official)|[http://thegreatgeekery.blogspot.com.es/2016/02/ecoplug-wifi-switch-hacking.html](http://thegreatgeekery.blogspot.com.es/2016/02/ecoplug-wifi-switch-hacking.html)|
|Build flag|ECOPLUG|
### Flashing
*TODO*
[1]: https://www.itead.cc/sonoff-wifi-wireless-switch.html
[2]: https://www.itead.cc/sonoff-rf.html
[2]: https://www.itead.cc/sonoff-th.html
[4]: https://www.itead.cc/sonoff-pow.html
[5]: https://www.itead.cc/slampher.html
[6]: https://www.itead.cc/smart-socket.html

+ 0
- 28
docs/OTA.md View File

@ -1,28 +0,0 @@
# Over-the-air updates
## Manually driven OTA updates
Once you have flashed your board with the ESPurna firmware you can flash it again over-the-air using PlatformIO and the ```ota``` environment:
```bash
> pio run -t upload -e node-debug-ota
> pio run -t uploadfs -e node-debug-ota
```
When using OTA environment it defaults to the IP address of the device in SoftAP mode. If you want to flash it when connected to your home network best way is to supply the IP of the device:
```bash
> pio run -t upload -e node-debug-ota --upload-port 192.168.1.151
> pio run -t uploadfs -e node-debug-ota --upload-port 192.168.1.151
```
Please note that if you have changed the admin password from the web interface you will have to change it too in the platformio.ini file for the OTA to work. Check for the *upload_flags* option in your ota-enabled environment.
## Automatic OTA updates
You can also use the automatic OTA update feature. Check the [NoFUSS library][6] for more info.
This options is disabled by default. Enable it in your firmware with the -DENABLE_FUSS build flag.
[6]: https://bitbucket.org/xoseperez/nofuss

+ 0
- 3
docs/Sensors.md View File

@ -1,3 +0,0 @@
# Sensors
*TODO*

+ 0
- 13
docs/Troubleshooting.md View File

@ -1,13 +0,0 @@
# Troubleshooting
## Problems resetting the board
After flashing the firmware via serial do a hard reset of the device (unplug & plug). There is an issue with the ESP.reset() method. Check [https://github.com/esp8266/Arduino/issues/1017][1] for more info.
## Can't find espresiff8266_stage platform
The fauxmoESP library requires the staging version of Arduino Core for ESP8266. Either you are using Arduino IDE or PlatformIO you will have to manually install this. Check the [documentation for the fauxmoESP library][2] for more info.
[1]: https://github.com/esp8266/Arduino/issues/1017
[2]: https://bitbucket.org/xoseperez/fauxmoesp

BIN
docs/images/electrodragon-flash.jpg View File

Before After
Width: 800  |  Height: 533  |  Size: 247 KiB

BIN
docs/images/s20-flash.jpg View File

Before After
Width: 800  |  Height: 533  |  Size: 267 KiB

BIN
docs/images/slampher-flash1.jpg View File

Before After
Width: 800  |  Height: 600  |  Size: 206 KiB

BIN
docs/images/slampher-flash2.jpg View File

Before After
Width: 800  |  Height: 600  |  Size: 189 KiB

BIN
docs/images/sonoff-dual-flash.jpg View File

Before After
Width: 800  |  Height: 533  |  Size: 228 KiB

BIN
docs/images/sonoff-flash.jpg View File

Before After
Width: 800  |  Height: 457  |  Size: 206 KiB

BIN
docs/images/sonoff-pow-flash.jpg View File

Before After
Width: 800  |  Height: 533  |  Size: 246 KiB

BIN
docs/images/sonoff-rf-flash.jpg View File

Before After
Width: 800  |  Height: 533  |  Size: 263 KiB

BIN
docs/images/sonoff-th-flash.jpg View File

Before After
Width: 800  |  Height: 524  |  Size: 252 KiB

BIN
images/devices/1ch-inching.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 28 KiB

BIN
images/devices/d1mini.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 49 KiB

BIN
images/devices/electrodragon-relay-board.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 30 KiB

BIN
images/devices/motor-switch.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 26 KiB

BIN
images/devices/s20.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 11 KiB

BIN
images/devices/slampher.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 16 KiB

BIN
images/devices/sonoff-4ch.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 11 KiB

BIN
images/devices/sonoff-basic.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 18 KiB

BIN
images/devices/sonoff-dual.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 13 KiB

BIN
images/devices/sonoff-led.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 19 KiB

BIN
images/devices/sonoff-pow.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 11 KiB

BIN
images/devices/sonoff-rf.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 20 KiB

BIN
images/devices/sonoff-sv.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 26 KiB

BIN
images/devices/sonoff-th10-th16.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 12 KiB

BIN
images/devices/sonoff-touch.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 6.8 KiB

BIN
images/devices/workchoice-ecoplug.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 8.6 KiB

Loading…
Cancel
Save