Browse Source

Merge remote-tracking branch 'xoseperez/dev' into dev

i18n
Rene Hexel 6 years ago
parent
commit
4710cd9703
57 changed files with 4922 additions and 3517 deletions
  1. +44
    -0
      CHANGELOG.md
  2. +40
    -9
      README.md
  3. +2
    -2
      code/build.sh
  4. +1
    -1
      code/espurna/button.ino
  5. +7
    -2
      code/espurna/config/arduino.h
  6. +93
    -10
      code/espurna/config/general.h
  7. +48
    -7
      code/espurna/config/hardware.h
  8. +7
    -0
      code/espurna/config/prototypes.h
  9. +9
    -4
      code/espurna/config/sensors.h
  10. +1
    -1
      code/espurna/config/version.h
  11. BIN
      code/espurna/data/index.html.gz
  12. +6
    -6
      code/espurna/debug.ino
  13. +5
    -23
      code/espurna/domoticz.ino
  14. +26
    -5
      code/espurna/espurna.ino
  15. +23
    -9
      code/espurna/homeassitant.ino
  16. +7
    -3
      code/espurna/influxdb.ino
  17. +69
    -14
      code/espurna/mdns.ino
  18. +15
    -54
      code/espurna/mqtt.ino
  19. +4
    -0
      code/espurna/nofuss.ino
  20. +2
    -3
      code/espurna/ntp.ino
  21. +5
    -0
      code/espurna/relay.ino
  22. +167
    -0
      code/espurna/scheduler.ino
  23. +46
    -0
      code/espurna/sensor.ino
  24. +10
    -0
      code/espurna/sensors/AnalogSensor.h
  25. +1
    -1
      code/espurna/sensors/BMX280Sensor.h
  26. +6
    -3
      code/espurna/sensors/BaseSensor.h
  27. +10
    -0
      code/espurna/sensors/DHTSensor.h
  28. +13
    -0
      code/espurna/sensors/DallasSensor.h
  29. +10
    -0
      code/espurna/sensors/DigitalSensor.h
  30. +12
    -0
      code/espurna/sensors/ECH1560Sensor.h
  31. +8
    -0
      code/espurna/sensors/EmonADS1X15Sensor.h
  32. +5
    -0
      code/espurna/sensors/EmonAnalogSensor.h
  33. +10
    -0
      code/espurna/sensors/EventSensor.h
  34. +12
    -0
      code/espurna/sensors/HLW8012Sensor.h
  35. +12
    -0
      code/espurna/sensors/I2CSensor.h
  36. +12
    -0
      code/espurna/sensors/MHZ19Sensor.h
  37. +7
    -0
      code/espurna/sensors/PMSX003Sensor.h
  38. +5
    -0
      code/espurna/sensors/SI7021Sensor.h
  39. +10
    -0
      code/espurna/sensors/V9261FSensor.h
  40. +33
    -10
      code/espurna/settings.ino
  41. +3116
    -3077
      code/espurna/static/index.html.gz.h
  42. +265
    -0
      code/espurna/thinkspeak.ino
  43. +56
    -1
      code/espurna/utils.ino
  44. +122
    -68
      code/espurna/wifi.ino
  45. +10
    -0
      code/espurna/ws.ino
  46. +5
    -3
      code/extra_scripts.py
  47. +20
    -5
      code/html/custom.css
  48. +109
    -15
      code/html/custom.js
  49. +141
    -18
      code/html/index.html
  50. +98
    -92
      code/memanalyzer.py
  51. +151
    -68
      code/ota.py
  52. +26
    -2
      code/platformio.ini
  53. +0
    -1
      code/requirements.txt
  54. BIN
      images/icons/collaborate.png
  55. BIN
      images/icons/documentation.png
  56. BIN
      images/icons/features.png
  57. BIN
      images/icons/hardware.png

+ 44
- 0
CHANGELOG.md View File

@ -3,6 +3,50 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.12.0] 2018-01-11
### Added
- Scheduler (contributed by Stefano Cotterli, thank you!, #131)
- Added "wifi.scan" command to terminal
- Added ESPurna Switch board support
- Added support for python3 in memanalyzer and ota scripts (thanks to Ryan Jarvis)
- Added BSSID, RSSI, channels and distance to web UI status tab
- Added mDNS name resolving to MQTT, InfluxDB and NoFUSS modules (#129, disabled by default)
### Fixed
- Update FauxmoESP library to 2.4.1, solves dependency issue (#388)
- Fixed hardware definition in Sonoff Basic and Dual R2 causing wrong relay state on boot (#365)
### Changed
- Removed auto-recursion check in Domoticz module (#379)
- Rename terminal commands: reset.wifi to wifi.reset, reset.mqtt to mqtt.reset.
- Update JustWifi library to 1.1.6 (support for multiple SSIDs with the same name)
- Changed the way Home Assistant module handles disabling auto-discovery (#383)
## [1.11.4] 2018-01-09
### Fixed
- Fix bug in RF Bridge when RF code contains the stop byte. Check overflow (#357)
- Fixed typos in code and wiki (Thanks to Ryan Jarvis)
- Fix bug in magnitude topic and units (#355)
### Added
- Small core build to allow two-step flashing method for big binaries
- Thingspeak support (#371, disabled by default)
- Color synchronization between lights using MQTT (#362)
- Support for Arilux AL-LC02 (#347)
- Support for Tarpuna Shield for Wemos D1
- Build option to disable password checking (#373)
- Option to report sensor address via MQTT (#377, I2C address, GPIO, Dallas address,...)
- Added binary size to memanalyzer script
- Option to specify custom client ID for MQTT connection (#368)
- Cross-platform ESPurna OTA Manager implemented in python (untested)
- Terminal command to get or set digital GPIO
### Changed
- Using 2.3.0 for prebuilt binaries
- Fix delay in DHT sensor
- Allow MQTT keep alive value of up to 3600s
- Changed Sonoff 4CH Pro definitions to support built-in interlock mode (#333)
## [1.11.3] 2018-01-02
### Fixed
- Fix uninitialized PWM channels bug (#356)


+ 40
- 9
README.md View File

@ -4,25 +4,26 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switch
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.11.3**, read the [changelog](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
**Current Release Version is 1.12.0**
> **NOTICE**: Default flash layout changed in 1.8.3, as an unpredicted consequence devices will not be able to persist/retrieve configuration if flashed with 1.8.3 via **OTA** from **PlatformIO**. Please check issue #187.
Read the [changes log](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
> **NOTICE**: since version 1.9.0 the default **MQTT topics for commands have changed**. They all now end with "/set". This means you will have to change your controller software (Node-RED or alike) to send messages to -for instance- "/home/living/light/relay/0/set". The device will publish its state in "/home/living/light/relay/0" like before.
---
## Features
* *KRACK* vulnerability free (when built against Arduino Core 2.4.0 RC2)
* *KRACK* vulnerability free (when built against Arduino Core 2.4.0)
* Support for **multiple ESP8266-based boards** ([check list](https://bitbucket.org/xoseperez/espurna/wiki/Hardware))
* Power saving options
* Wifi **AP Mode** or **STA mode**
* Up to 5 different networks can be defined
* Supports static IP
* Scans for strongest network if more than one defined
* Handles correctly multiple AP with the same SSID
* Defaults to AP mode (also available after double clicking the main button)
* Network visibility
* Supports mDNS (service reporting and metadata)
* Supports NetBIOS, LLMNR and Netbios (when built against Arduino Core 2.4.0 RC2) and SSDP (experimental)
* Supports mDNS (service reporting and metadata) both server mode and client mode (.local name resolution)
* Supports NetBIOS, LLMNR and Netbios (when built against Arduino Core 2.4.0) and SSDP (experimental)
* Switch management
* Support for **push buttons** and **toggle switches**
* Configurable **status on boot** per switch (always ON, always OFF, same as before or toggle)
@ -37,6 +38,8 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Enable/disable pulse mode
* Change LED notification mode
* Remote reset the board
* Fully configurable in webUI (broker, user, password, QoS, keep alive time, retain flag, client ID)
* **Scheduler** to automatically turn on, off or toggle any relay at a given time and day
* **Alexa** integration using the [FauxmoESP Library](https://bitbucket.org/xoseperez/fauxmoesp)
* [**Google Assistant**](http://tinkerman.cat/using-google-assistant-control-your-esp8266-devices/) integration using IFTTT and Webhooks (Google Home, Allo)
* [**Domoticz**](https://domoticz.com/) integration via MQTT
@ -45,13 +48,14 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Support for lights (color, brightness, on/off state)
* Supports MQTT auto-discover feature (both switches and lights)
* [**InfluxDB**](https://www.influxdata.com/) integration via HTTP API
* [**Thingspeak**](https://thingspeak.com/) integration via HTTP API (HTTPS available for custom builds)
* Support for different **sensors**
* Environment
* **DHT11 / DHT22 / DHT21 / AM2301 / Itead's SI7021** (supports celsius & fahrenheit reporting)
* **DHT11 / DHT22 / DHT21 / AM2301 / Itead's SI7021**
* **BMP280** and **BME280** temperature, humidity (BME280) and pressure sensor by Bosch
* **SI7021** temperature and humidity sensor
* **SHT3X** temperature and humidity sensor over I2C (Wemos shield)
* **Dallas OneWire sensors** like the DS18B20 (supports celsius & fahrenheit reporting)
* **Dallas OneWire sensors** like the DS18B20
* **MHZ19** CO2 sensor
* **PMSX003** dust sensor
* **BH1750** luminosity sensor
@ -62,6 +66,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* **V9261F** power monitor chip
* Raw analog and digital sensors
* Simple pulse counter
* All temperature sensors support Fahrenheit and Celsius
* Support for LED lights
* MY92XX-based light bulbs and PWM LED strips (dimmers) up to 5 channels (RGB, cold white and warm white, for instance)
* RGB and HSV color codes supported
@ -69,6 +74,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Temperature color supported (in mired and kelvin) via MQTT / REST API
* Flicker-free PWM management
* Soft color transitions
* Color synchronization between light using MQTT
* Fast asynchronous **HTTP Server**
* Configurable port
* Basic authentication
@ -106,6 +112,31 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Extra long click (>10 seconds) to go back to factory settings (only main button)
* Specific definitions for touch button devices (ESPurna Switch, Sonoff Touch & T1)
## Notices
---
> **2018-01-11**: As of current version (1.12.0) ESPurna is tested using Arduino Core 2.3.0 and it's meant to be built against that version.
---
> **2017-08-26**: since version 1.9.0 the default **MQTT topics for commands have changed**. They all now end with "/set". This means you will have to change your controller software (Node-RED or alike) to send messages to -for instance- "/home/living/light/relay/0/set". The device will publish its state in "/home/living/light/relay/0" like before.
---
> **2017-07-24**: Default flash layout changed in 1.8.3, as an unpredicted consequence devices will not be able to persist/retrieve configuration if flashed with 1.8.3 via **OTA** from **PlatformIO**. Please check issue #187.
---
## Contribute
There are several ways to contribute to ESpurna development. You can contribute to the repository by doing:
* Pull requests (fixes, enhancements, new features... are very welcome)
* Documentation (I reckon I'm bad at it)
* Testing (filing issues or help resolving them, they take a lot of time and sometimes I don't have the required hardware to test them all)
And of course you can always buy me a beer, coffee, tea,... via the donation button below.
[![Donate to ESPurna Project](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest)
## Documentation
For more information please refer to the [ESPurna Wiki](https://bitbucket.org/xoseperez/espurna/wiki/Home).
@ -139,7 +170,7 @@ Here is the list of supported hardware. For more information please refer to the
|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|||
|**EXS Wifi Relay v3.1**|||
**Other supported boards:** Itead Sonoff LED, Itead Sonoff Dual R2, Huacanxing H802, WiOn 50055, ManCaveMade ESP-Live, InterMitTech QuinLED 2.6, Arilux AL-LC01, Arilux AL-LC06, Arilux AL-LC11, Arilux E27 light bulb, Xenon SM-PW702U, Authometion LYT8266, YJZK 2-gang switch.
**Other supported boards:** Itead Sonoff LED, Itead Sonoff Dual R2, Huacanxing H802, WiOn 50055, ManCaveMade ESP-Live, InterMitTech QuinLED 2.6, Arilux AL-LC01, Arilux AL-LC02, Arilux AL-LC06, Arilux AL-LC11, Arilux E27 light bulb, Xenon SM-PW702U, Authometion LYT8266, YJZK 2-gang switch.
## License


+ 2
- 2
code/build.sh View File

@ -42,10 +42,10 @@ node node_modules/gulp/bin/gulp.js || exit
# Build all the required firmwares
echo "--------------------------------------------------------------"
echo "Building firmware images..."
mkdir -p firmware/espurna-$version
mkdir -p ../firmware/espurna-$version
for environment in $environments; do
echo "* espurna-$version-$environment.bin"
platformio run --silent --environment $environment || exit
mv .pioenvs/$environment/firmware.bin firmware/espurna-$version/espurna-$version-$environment.bin
mv .pioenvs/$environment/firmware.bin ../firmware/espurna-$version/espurna-$version-$environment.bin
done
echo "--------------------------------------------------------------"

+ 1
- 1
code/espurna/button.ino View File

@ -80,7 +80,7 @@ uint8_t mapEvent(uint8_t event, uint8_t count, uint16_t length) {
void buttonEvent(unsigned int id, unsigned char event) {
DEBUG_MSG_P(PSTR("[BUTTON] Pressed #%d, event: %d\n"), id, event);
DEBUG_MSG_P(PSTR("[BUTTON] Button #%d event %d\n"), id, event);
if (event == 0) return;
#if MQTT_SUPPORT


+ 7
- 2
code/espurna/config/arduino.h View File

@ -58,6 +58,7 @@
//#define ARILUX_AL_LC01
//#define ARILUX_AL_LC11
//#define ARILUX_AL_LC02
//#define WEMOS_D1_TARPUNA_SHIELD
//--------------------------------------------------------------------------------
// Features (values below are non-default values)
@ -70,19 +71,22 @@
//#define DOMOTICZ_SUPPORT 0
//#define HOMEASSISTANT_SUPPORT 0
//#define I2C_SUPPORT 1
//#define INFLUXDB_SUPPORT 0
//#define INFLUXDB_SUPPORT 1
//#define IR_SUPPORT 1
//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define MDNS_SUPPORT 0
//#define MDNS_SERVER_SUPPORT 0
//#define MDNS_CLIENT_SUPPORT 1
//#define MQTT_SUPPORT 0
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1
//#define NTP_SUPPORT 0
//#define RF_SUPPORT 1
//#define SCHEDULER_SUPPORT 0
//#define SPIFFS_SUPPORT 1
//#define SSDP_SUPPORT 1
//#define TELNET_SUPPORT 0
//#define TERMINAL_SUPPORT 0
//#define THINGSPEAK_SUPPORT 0
//#define WEB_SUPPORT 0
//--------------------------------------------------------------------------------
@ -90,6 +94,7 @@
//--------------------------------------------------------------------------------
//#define ANALOG_SUPPORT 1
//#define BH1750_SUPPORT 1
//#define BMX280_SUPPORT 1
//#define DALLAS_SUPPORT 1
//#define DHT_SUPPORT 1


+ 93
- 10
code/espurna/config/general.h View File

@ -15,6 +15,22 @@
#define ARRAYINIT(type, name, ...) \
type name[] = {__VA_ARGS__};
//------------------------------------------------------------------------------
// ESPURNA CORE
//------------------------------------------------------------------------------
#ifdef ESPURNA_CORE
#define ALEXA_SUPPORT 0
#define SCHEDULER_SUPPORT 0
#define DOMOTICZ_SUPPORT 0
#define HOMEASSISTANT_SUPPORT 0
#define MQTT_SUPPORT 0
#define NTP_SUPPORT 0
#define WEB_SUPPORT 0
#define SENSOR_SUPPORT 0
#define I2C_SUPPORT 0
#endif
//------------------------------------------------------------------------------
// TELNET
//------------------------------------------------------------------------------
@ -75,7 +91,6 @@
//------------------------------------------------------------------------------
// General debug options and macros
#define DEBUG_FORMAT_MAX_LENGTH 80
#define DEBUG_SUPPORT DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT || DEBUG_TELNET_SUPPORT
#if DEBUG_SUPPORT
@ -180,10 +195,21 @@ PROGMEM const char* const custom_reset_string[] = {
// BUTTON
//------------------------------------------------------------------------------
#ifndef BUTTON_DEBOUNCE_DELAY
#define BUTTON_DEBOUNCE_DELAY 50 // Debounce delay (ms)
#endif
#ifndef BUTTON_DBLCLICK_DELAY
#define BUTTON_DBLCLICK_DELAY 500 // Time in ms to wait for a second (or third...) click
#endif
#ifndef BUTTON_LNGCLICK_DELAY
#define BUTTON_LNGCLICK_DELAY 1000 // Time in ms holding the button down to get a long click
#endif
#ifndef BUTTON_LNGLNGCLICK_DELAY
#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
#endif
#define BUTTON_EVENT_NONE 0
#define BUTTON_EVENT_PRESSED 1
@ -293,6 +319,9 @@ PROGMEM const char* const custom_reset_string[] = {
//#define WIFI2_SSID "..."
//#define WIFI2_PASS "..."
#define WIFI_RSSI_1M -30 // Calibrate it with your router reading the RSSI at 1m
#define WIFI_PROPAGATION_CONST 4 // This is typically something between 2.7 to 4.3 (free space is 2)
// -----------------------------------------------------------------------------
// WEB
// -----------------------------------------------------------------------------
@ -306,7 +335,7 @@ PROGMEM const char* const custom_reset_string[] = {
#endif
// This is not working at the moment!!
// Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core staging version.
// Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core 2.4.0
#define WEB_SSL_ENABLED 0 // Use HTTPS web interface
#define WEB_MODE_NORMAL 0
@ -347,8 +376,12 @@ PROGMEM const char* const custom_reset_string[] = {
// MDNS / LLMNR / NETBIOS / SSDP
// -----------------------------------------------------------------------------
#ifndef MDNS_SUPPORT
#define MDNS_SUPPORT 1 // Publish services using mDNS by default (1.84Kb)
#ifndef MDNS_SERVER_SUPPORT
#define MDNS_SERVER_SUPPORT 1 // Publish services using mDNS by default (1.48Kb)
#endif
#ifndef MDNS_CLIENT_SUPPORT
#define MDNS_CLIENT_SUPPORT 0 // Resolve mDNS names (3.44Kb)
#endif
#ifndef LLMNR_SUPPORT
@ -368,7 +401,7 @@ PROGMEM const char* const custom_reset_string[] = {
// -----------------------------------------------------------------------------
#ifndef SPIFFS_SUPPORT
#define SPIFFS_SUPPORT 0 // Do not add support for SPIFFS by default
#define SPIFFS_SUPPORT 0 // Do not add support for SPIFFS by default
#endif
// -----------------------------------------------------------------------------
@ -399,13 +432,13 @@ PROGMEM const char* const custom_reset_string[] = {
#ifndef MQTT_USE_ASYNC
#define MQTT_USE_ASYNC 1 // Use AysncMQTTClient (1) or PubSubClient
#define MQTT_USE_ASYNC 1 // Use AysncMQTTClient (1) or PubSubClient (0)
#endif
// MQTT OVER SSL
// Using MQTT over SSL works pretty well but generates problems with the web interface.
// It could be a good idea to use it in conjuntion with WEB_SUPPORT=0.
// Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core staging version.
// Requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core 2.4.0.
//
// You can use it with MQTT_USE_ASYNC=1 (AsyncMqttClient library)
// but you might experience hiccups on the web interface, so my recommendation is:
@ -423,7 +456,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_SSL_FINGERPRINT "" // SSL fingerprint of the server
#define MQTT_ENABLED 0 // Do not enable MQTT connection by default
#define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SUPPORT=1 will perform an autodiscover and
#define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SERVER_SUPPORT=1 will perform an autodiscover and
// autoconnect to the first MQTT broker found if none defined
#define MQTT_SERVER "" // Default MQTT broker address
#define MQTT_USER "" // Default MQTT broker usename
@ -580,7 +613,6 @@ PROGMEM const char* const custom_reset_string[] = {
#define DOMOTICZ_ENABLED 0 // Disable domoticz by default
#define DOMOTICZ_IN_TOPIC "domoticz/in" // Default subscription topic
#define DOMOTICZ_OUT_TOPIC "domoticz/out" // Default publication topic
#define DOMOTICZ_SKIP_TIME 2 // Avoid recursion skipping messages to same IDX within 2 seconds
// -----------------------------------------------------------------------------
// HOME ASSISTANT
@ -613,6 +645,57 @@ PROGMEM const char* const custom_reset_string[] = {
#define INFLUXDB_USERNAME "" // Default username
#define INFLUXDB_PASSWORD "" // Default password
// -----------------------------------------------------------------------------
// THINGSPEAK
// -----------------------------------------------------------------------------
#ifndef THINGSPEAK_SUPPORT
#define THINGSPEAK_SUPPORT 1 // Enable Thingspeak support by default (2.56Kb)
#endif
#define THINGSPEAK_ENABLED 0 // Thingspeak disabled by default
#define THINGSPEAK_APIKEY "" // Default API KEY
#define THINGSPEAK_USE_ASYNC 1 // Use AsyncClient instead of WiFiClientSecure
// THINGSPEAK OVER SSL
// Using THINGSPEAK over SSL works well but generates problems with the web interface,
// so you should compile it with WEB_SUPPORT to 0.
// When THINGSPEAK_USE_ASYNC is 1, requires ASYNC_TCP_SSL_ENABLED to 1 and ESP8266 Arduino Core 2.4.0.
#define THINGSPEAK_USE_SSL 0 // Use secure connection
#define THINGSPEAK_FINGERPRINT "78 60 18 44 81 35 BF DF 77 84 D4 0A 22 0D 9B 4E 6C DC 57 2C"
#define THINGSPEAK_HOST "api.thingspeak.com"
#if THINGSPEAK_USE_SSL
#define THINGSPEAK_PORT 443
#else
#define THINGSPEAK_PORT 80
#endif
#define THINGSPEAK_URL "/update"
#define THINGSPEAK_MIN_INTERVAL 15000 // Minimum interval between POSTs (in millis)
#ifndef ASYNC_TCP_SSL_ENABLED
#if THINGSPEAK_USE_SSL && THINGSPEAK_USE_ASYNC
#undef THINGSPEAK_SUPPORT // Thingspeak in ASYNC mode requires ASYNC_TCP_SSL_ENABLED
#endif
#endif
// -----------------------------------------------------------------------------
// SCHEDULER
// -----------------------------------------------------------------------------
#ifndef SCHEDULER_SUPPORT
#define SCHEDULER_SUPPORT 1 // Enable scheduler (1.77Kb)
#endif
#if SCHEDULER_SUPPORT
#undef NTP_SUPPORT
#define NTP_SUPPORT 1 // Scheduler needs NTP
#endif
#define SCHEDULER_UPDATE_SEC 5 // Scheduler perform switch at hh:mm:05
#define SCHEDULER_MAX_SCHEDULES 10 // Max schedules alowed
// -----------------------------------------------------------------------------
// NTP
// -----------------------------------------------------------------------------
@ -627,7 +710,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes
// -----------------------------------------------------------------------------
// FAUXMO
// ALEXA
// -----------------------------------------------------------------------------
// This setting defines whether Alexa support should be built into the firmware


+ 48
- 7
code/espurna/config/hardware.h View File

@ -64,6 +64,16 @@
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#elif defined(WEMOS_D1_TARPUNA_SHIELD)
// Info
#define MANUFACTURER "WEMOS"
#define DEVICE "D1_TARPUNA_SHIELD"
// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// -----------------------------------------------------------------------------
// ESPurna
// -----------------------------------------------------------------------------
@ -167,7 +177,7 @@
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
#define BUTTON2_PIN 14
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON2_RELAY 1
// Relays
@ -189,7 +199,7 @@
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
#define BUTTON2_PIN 14
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON2_RELAY 1
// Relays
@ -365,14 +375,14 @@
#define DEVICE "SONOFF_DUAL_R2"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON2_PIN 9
#define BUTTON3_PIN 10
#define BUTTON1_PIN 0 // Button 0 on header
#define BUTTON2_PIN 9 // Button 1 on header
#define BUTTON3_PIN 10 // Physical button
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
#define BUTTON3_RELAY 1
#define BUTTON1_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP
#define BUTTON1_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Relays
@ -444,6 +454,37 @@
#define BUTTON3_RELAY 3
#define BUTTON4_RELAY 4
// Sonoff 4CH Pro uses a secondary STM32 microcontroller to handle
// buttons and relays, but it also forwards button presses to the ESP8285.
// This allows ESPurna to handle button presses -almost- the same way
// as with other devices except:
// * Double click seems to break/disable the button on the STM32 side
// * With S6 switch to 1 (self-locking and inching modes) everything's OK
// * With S6 switch to 0 (interlock mode) if there is a relay ON
// and you click on another relay button, the STM32 sends a "press"
// event for the button of the first relay (to turn it OFF) but it
// does not send a "release" event. It's like it's holding the
// button down since you can see it is still LOW.
// Whatever reason the result is that it may actually perform a
// long click or long-long click.
// The configuration below make the button toggle the relay on press events
// and disables any possibly harmful combination with S6 set to 0.
// If you are sure you will only use S6 to 1 you can comment the
// BUTTON1_LNGCLICK and BUTTON1_LNGLNGCLICK options below to recover the
// AP mode and factory reset functionalities.
#define BUTTON1_PRESS BUTTON_MODE_TOGGLE
#define BUTTON1_CLICK BUTTON_MODE_NONE
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE
#define BUTTON2_PRESS BUTTON_MODE_TOGGLE
#define BUTTON2_CLICK BUTTON_MODE_NONE
#define BUTTON3_PRESS BUTTON_MODE_TOGGLE
#define BUTTON3_CLICK BUTTON_MODE_NONE
#define BUTTON4_PRESS BUTTON_MODE_TOGGLE
#define BUTTON4_CLICK BUTTON_MODE_NONE
// Relays
#define RELAY1_PIN 12
#define RELAY2_PIN 5


+ 7
- 0
code/espurna/config/prototypes.h View File

@ -33,6 +33,13 @@ void wsOnActionRegister(ws_on_action_callback_f callback);
typedef std::function<void(void)> ws_on_after_parse_callback_f;
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback);
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
#include "JustWifi.h"
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback);
// -----------------------------------------------------------------------------
// MQTT
// -----------------------------------------------------------------------------


+ 9
- 4
code/espurna/config/sensors.h View File

@ -36,6 +36,9 @@
#define HUMIDITY_DRY 2
#define HUMIDITY_WET 3
#define SENSOR_PUBLISH_ADDRESSES 0 // Publish sensor addresses
#define SENSOR_ADDRESS_TOPIC "address" // Topic to publish sensor addresses
//--------------------------------------------------------------------------------
// Sensor ID
// These should remain over time, do not modify them, only add new ones at the end
@ -161,8 +164,8 @@
#define DALLAS_PIN 14
#endif
#define DALLAS_RESOLUTION 9 // Not used atm
#define DALLAS_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds
#define DALLAS_RESOLUTION 9 // Not used atm
#define DALLAS_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds
//------------------------------------------------------------------------------
// DHTXX temperature/humidity sensor
@ -438,15 +441,17 @@
// Sensor helpers configuration
// =============================================================================
#ifndef SENSOR_SUPPORT
#if ANALOG_SUPPORT || BH1750_SUPPORT || BMX280_SUPPORT || DALLAS_SUPPORT \
|| DHT_SUPPORT || DIGITAL_SUPPORT || ECH1560_SUPPORT \
|| EMON_ADC121_SUPPORT || EMON_ADS1X15_SUPPORT \
|| EMON_ANALOG_SUPPORT || EVENTS_SUPPORT || HLW8012_SUPPORT \
|| MHZ19_SUPPORT || PMSX003_SUPPORT || SHT3X_I2C_SUPPORT \
|| SI7021_SUPPORT || V9261F_SUPPORT
#define SENSOR_SUPPORT 1
#else
#define SENSOR_SUPPORT 0
#endif
#endif
// -----------------------------------------------------------------------------


+ 1
- 1
code/espurna/config/version.h View File

@ -1,5 +1,5 @@
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.11.4a"
#define APP_VERSION "1.12.0"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"
#define CFG_VERSION 3

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


+ 6
- 6
code/espurna/debug.ino View File

@ -55,17 +55,17 @@ void debugSend(const char * format, ...) {
}
void debugSend_P(PGM_P format, ...) {
void debugSend_P(PGM_P format_P, ...) {
char f[DEBUG_FORMAT_MAX_LENGTH+1];
memcpy_P(f, format, DEBUG_FORMAT_MAX_LENGTH);
char format[strlen_P(format_P)+1];
memcpy_P(format, format_P, sizeof(format));
va_list args;
va_start(args, format);
va_start(args, format_P);
char test[1];
int len = ets_vsnprintf(test, 1, f, args) + 1;
int len = ets_vsnprintf(test, 1, format, args) + 1;
char * buffer = new char[len];
ets_vsnprintf(buffer, len, f, args);
ets_vsnprintf(buffer, len, format, args);
va_end(args);
#if DEBUG_SERIAL_SUPPORT


+ 5
- 23
code/espurna/domoticz.ino View File

@ -11,9 +11,6 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _dcz_enabled = false;
unsigned long _dcz_skip_time = 0;
unsigned long _dcz_last_idx = 0;
unsigned long _dcz_last_time = 0;
//------------------------------------------------------------------------------
// Private methods
@ -28,13 +25,6 @@ int _domoticzRelay(unsigned int idx) {
return -1;
}
bool _domoticzSkip(unsigned long idx) {
if (idx == _dcz_last_idx && (millis() - _dcz_last_time < _dcz_skip_time)) return true;
_dcz_last_idx = idx;
_dcz_last_time = millis();
return false;
}
void _domoticzMqttSubscribe(bool value) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -73,14 +63,9 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
unsigned long idx = root["idx"];
int relayID = _domoticzRelay(idx);
if (relayID >= 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
unsigned long value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx);
relayStatus(relayID, value == 1);
}
}
@ -89,15 +74,16 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
};
#if WEB_SUPPORT
void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczSkip"] = getSetting("dczSkip", DOMOTICZ_SKIP_TIME);
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& relays = root.createNestedArray("dczRelayIdx");
JsonArray& relays = root.createNestedArray("dczRelays");
for (byte i=0; i<relayCount(); i++) {
relays.add(domoticzIdx(i));
}
@ -115,9 +101,10 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
}
#endif // WEB_SUPPORT
void _domoticzConfigure() {
_dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
_dcz_skip_time = 1000 * getSetting("dczSkip", DOMOTICZ_SKIP_TIME).toInt();
_domoticzMqttSubscribe(_dcz_enabled);
}
@ -129,14 +116,9 @@ template<typename T> void domoticzSend(const char * key, T nvalue, const char *
if (!_dcz_enabled) return;
unsigned int idx = getSetting(key).toInt();
if (idx > 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
char payload[128];
snprintf(payload, sizeof(payload), "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue);
mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload);
}
}


+ 26
- 5
code/espurna/espurna.ino View File

@ -128,8 +128,7 @@ void welcome() {
// -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] MANUFACTURER: %s\n"), MANUFACTURER);
DEBUG_MSG_P(PSTR("[INIT] DEVICE: %s\n"), DEVICE);
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
@ -159,7 +158,7 @@ void welcome() {
#if LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SUPPORT
#if MDNS_SERVER_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS"));
#endif
#if NETBIOS_SUPPORT
@ -174,6 +173,9 @@ void welcome() {
#if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF"));
#endif
#if SCHEDULER_SUPPORT
DEBUG_MSG_P(PSTR(" SCHEDULER"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR"));
#endif
@ -189,6 +191,9 @@ void welcome() {
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
@ -286,6 +291,7 @@ void setup() {
if (getSetting("hostname").length() == 0) {
setSetting("hostname", getIdentifier());
}
setBoardName();
// Cache loop delay value to speed things (recommended max 250ms)
_loopDelay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
@ -322,8 +328,8 @@ void setup() {
mqttSetup();
#endif
#if MDNS_SUPPORT
mdnsSetup();
#if MDNS_SERVER_SUPPORT
mdnsServerSetup();
#endif
#if LLMNR_SUPPORT
llmnrSetup();
@ -357,6 +363,9 @@ void setup() {
#if INFLUXDB_SUPPORT
idbSetup();
#endif
#if THINGSPEAK_SUPPORT
tspkSetup();
#endif
#if RF_SUPPORT
rfSetup();
#endif
@ -372,6 +381,9 @@ void setup() {
#if SENSOR_SUPPORT
sensorSetup();
#endif
#if SCHEDULER_SUPPORT
schSetup();
#endif
// Prepare configuration for version 2.0
migrate();
@ -427,6 +439,15 @@ void loop() {
#if SENSOR_SUPPORT
sensorLoop();
#endif
#if THINGSPEAK_SUPPORT
tspkLoop();
#endif
#if SCHEDULER_SUPPORT
schLoop();
#endif
#if MDNS_CLIENT_SUPPORT
mdnsClientLoop();
#endif
// Power saving delay
delay(_loopDelay);


+ 23
- 9
code/espurna/homeassitant.ino View File

@ -11,6 +11,7 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _haEnabled = false;
bool _haSendFlag = false;
// -----------------------------------------------------------------------------
@ -19,21 +20,19 @@ void _haWebSocketOnSend(JsonObject& root) {
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
}
void _haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
if (enabled != _haEnabled) haSend(enabled);
_haEnabled = enabled;
}
void _haSend() {
// -----------------------------------------------------------------------------
// Pending message to send?
if (!_haSendFlag) return;
void haSend(bool add) {
// Are we connected?
if (!mqttConnected()) return;
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
String output;
if (add) {
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
@ -84,18 +83,33 @@ void haSend(bool add) {
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
_haSendFlag = false;
}
void _haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
_haSendFlag = (enabled != _haEnabled);
_haEnabled = enabled;
_haSend();
}
// -----------------------------------------------------------------------------
void haSetup() {
_haConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure);
#endif
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) haSend(_haEnabled);
if (type == MQTT_CONNECT_EVENT) _haSend();
});
}
#endif // HOMEASSISTANT_SUPPORT

+ 7
- 3
code/espurna/influxdb.ino View File

@ -1,6 +1,6 @@
/*
I2C MODULE
INFLUXDB MODULE
Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
@ -41,8 +41,12 @@ template<typename T> bool idbSend(const char * topic, T payload) {
if (!_idb_enabled) return true;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return true;
char host[64];
getSetting("idbHost", INFLUXDB_HOST).toCharArray(host, sizeof(host));
String h = getSetting("idbHost", INFLUXDB_HOST);
#if MDNS_CLIENT_SUPPORT
h = mdnsResolve(h);
#endif
char * host = strdup(h.c_str());
int port = getSetting("idbPort", INFLUXDB_PORT).toInt();
DEBUG_MSG("[INFLUXDB] Sending to %s:%d\n", host, port);


+ 69
- 14
code/espurna/mdns.ino View File

@ -6,15 +6,17 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if MDNS_SUPPORT
// -----------------------------------------------------------------------------
// mDNS Server
// -----------------------------------------------------------------------------
#include <ESP8266mDNS.h>
#if MDNS_SERVER_SUPPORT
WiFiEventHandler _mdns_wifi_onSTA;
WiFiEventHandler _mdns_wifi_onAP;
#include <ESP8266mDNS.h>
#if MQTT_SUPPORT
void mdnsFindMQTT() {
void _mdnsFindMQTT() {
int count = MDNS.queryService("mqtt", "tcp");
DEBUG_MSG_P(PSTR("[MQTT] MQTT brokers found: %d\n"), count);
for (int i=0; i<count; i++) {
@ -22,9 +24,10 @@ void mdnsFindMQTT() {
mqttSetBrokerIfNone(MDNS.IP(i), MDNS.port(i));
}
}
#endif
void _mdnsStart() {
void _mdnsServerStart() {
if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) {
DEBUG_MSG_P(PSTR("[MDNS] OK\n"));
} else {
@ -32,7 +35,9 @@ void _mdnsStart() {
}
}
void mdnsSetup() {
// -----------------------------------------------------------------------------
void mdnsServerSetup() {
#if WEB_SUPPORT
MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt());
@ -45,7 +50,7 @@ void mdnsSetup() {
// Public ESPurna related txt for OTA discovery
MDNS.addServiceTxt("arduino", "tcp", "app_name", APP_NAME);
MDNS.addServiceTxt("arduino", "tcp", "app_version", APP_VERSION);
MDNS.addServiceTxt("arduino", "tcp", "target_board", DEVICE_NAME);
MDNS.addServiceTxt("arduino", "tcp", "target_board", getBoardName());
{
char buffer[6];
itoa(ESP.getFlashChipRealSize() / 1024, buffer, 10);
@ -56,14 +61,64 @@ void mdnsSetup() {
itoa(ESP.getFlashChipSize() / 1024, buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "sdk_size", (const char *) buffer);
}
{
char buffer[6];
itoa(ESP.getFreeSketchSpace(), buffer, 10);
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer);
}
wifiRegister([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_CONNECTED) {
_mdnsServerStart();
#if MQTT_SUPPORT
_mdnsFindMQTT();
#endif // MQTT_SUPPORT
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
_mdnsServerStart();
}
_mdns_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
_mdnsStart();
});
_mdns_wifi_onAP = WiFi.onSoftAPModeStationConnected([](WiFiEventSoftAPModeStationConnected ipInfo) {
_mdnsStart();
});
}
#endif // MDNS_SUPPORT
#endif // MDNS_SERVER_SUPPORT
// -----------------------------------------------------------------------------
// mDNS Client
// -----------------------------------------------------------------------------
#if MDNS_CLIENT_SUPPORT
#include <WiFiUdp.h>
#include <mDNSResolver.h>
using namespace mDNSResolver;
WiFiUDP _mdns_udp;
Resolver _mdns_resolver(_mdns_udp);
String mdnsResolve(char * name) {
if (strlen(name) == 0) return String();
if (WiFi.status() != WL_CONNECTED) return String();
_mdns_resolver.setLocalIP(WiFi.localIP());
IPAddress ip = _mdns_resolver.search(name);
if (ip == INADDR_NONE) return String(name);
DEBUG_MSG_P(PSTR("[MDNS] '%s' resolved to '%s'\n"), name, ip.toString().c_str());
return ip.toString();
}
String mdnsResolve(String name) {
return mdnsResolve((char *) name.c_str());
}
void mdnsClientLoop() {
_mdns_resolver.loop();
}
#endif // MDNS_CLIENT_SUPPORT

+ 15
- 54
code/espurna/mqtt.ino View File

@ -233,10 +233,6 @@ void _mqttWebSocketOnSend(JsonObject& root) {
root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1;
}
void _mqttConfigure() {
if (getSetting("mqttClientID").length() == 0) delSetting("mqttClientID");
}
#endif
void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
@ -319,47 +315,6 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
}
#if MQTT_USE_ASYNC
bool mqttFormatFP(const char * fingerprint, unsigned char * bytearray) {
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (strlen(fingerprint) != 59) return false;
DEBUG_MSG_P(PSTR("[MQTT] Fingerprint %s\n"), fingerprint);
// walk the fingerprint
for (unsigned int i=0; i<20; i++) {
bytearray[i] = strtol(fingerprint + 3*i, NULL, 16);
}
return true;
}
#else
bool mqttFormatFP(const char * fingerprint, char * destination) {
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (strlen(fingerprint) != 59) return false;
DEBUG_MSG_P(PSTR("[MQTT] Fingerprint %s\n"), fingerprint);
// copy it
strncpy(destination, fingerprint, 59);
// walk the fingerprint replacing ':' for ' '
for (unsigned char i = 0; i<59; i++) {
if (destination[i] == ':') destination[i] = ' ';
}
return true;
}
#endif
void mqttEnabled(bool status) {
_mqtt_enabled = status;
setSetting("mqttEnabled", status ? 1 : 0);
@ -388,8 +343,12 @@ void mqttConnect() {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
}
char * host = strdup(getSetting("mqttServer", MQTT_SERVER).c_str());
if (strlen(host) == 0) return;
String h = getSetting("mqttServer", MQTT_SERVER);
#if MDNS_CLIENT_SUPPORT
h = mdnsResolve(h);
#endif
char * host = strdup(h.c_str());
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
if (_mqtt_user) free(_mqtt_user);
@ -423,7 +382,7 @@ void mqttConnect() {
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
unsigned char fp[20] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (sslFingerPrintArray(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
_mqtt.addServerFingerprint(fp);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
@ -450,7 +409,7 @@ void mqttConnect() {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
if (_mqtt_client_secure.connect(host, port)) {
char fp[60] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (sslFingerPrintChar(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (_mqtt_client_secure.verify(fp, host)) {
_mqtt.setClient(_mqtt_client_secure);
} else {
@ -524,6 +483,7 @@ void mqttConfigure() {
_mqtt_qos = getSetting("mqttQoS", MQTT_QOS).toInt();
_mqtt_retain = getSetting("mqttRetain", MQTT_RETAIN).toInt() == 1;
_mqtt_keepalive = getSetting("mqttKeep", MQTT_KEEPALIVE).toInt();
if (getSetting("mqttClientID").length() == 0) delSetting("mqttClientID");
// Enable
if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
@ -549,8 +509,11 @@ void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) {
void mqttSetup() {
DEBUG_MSG_P(PSTR("[MQTT] MQTT_USE_ASYNC = %d\n"), MQTT_USE_ASYNC);
DEBUG_MSG_P(PSTR("[MQTT] MQTT_AUTOCONNECT = %d\n"), MQTT_AUTOCONNECT);
DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"),
MQTT_USE_ASYNC ? "ENABLED" : "DISABLED",
ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED",
MQTT_AUTOCONNECT ? "ENABLED" : "DISABLED"
);
#if MQTT_USE_ASYNC
@ -592,8 +555,6 @@ void mqttSetup() {
#else // not MQTT_USE_ASYNC
DEBUG_MSG_P(PSTR("[MQTT] Using SYNC MQTT library\n"));
_mqtt.setCallback([](char* topic, byte* payload, unsigned int length) {
_mqttOnMessage(topic, (char *) payload, length);
});
@ -605,7 +566,7 @@ void mqttSetup() {
#if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend);
wsOnAfterParseRegister(_mqttConfigure);
wsOnAfterParseRegister(mqttConfigure);
#endif
}


+ 4
- 0
code/espurna/nofuss.ino View File

@ -27,6 +27,10 @@ void _nofussWebSocketOnSend(JsonObject& root) {
void _nofussConfigure() {
String nofussServer = getSetting("nofussServer", NOFUSS_SERVER);
#if MDNS_CLIENT_SUPPORT
nofussServer = mdnsResolve(nofussServer);
#endif
if (nofussServer.length() == 0) {
setSetting("nofussEnabled", 0);
_nofussEnabled = false;


+ 2
- 3
code/espurna/ntp.ino View File

@ -13,7 +13,6 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <WiFiClient.h>
#include <Ticker.h>
WiFiEventHandler _ntp_wifi_onSTA;
Ticker _ntp_delay;
// -----------------------------------------------------------------------------
@ -86,8 +85,8 @@ void ntpSetup() {
}
});
_ntp_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
_ntpConfigure();
wifiRegister([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_CONNECTED) _ntpConfigure();
});
#if WEB_SUPPORT


+ 5
- 0
code/espurna/relay.ino View File

@ -747,6 +747,11 @@ void relayLoop(void) {
relayInfluxDB(id);
#endif
#if THINGSPEAK_SUPPORT
tspkEnqueueRelay(id, status);
tspkFlush();
#endif
// Flag relay-based LEDs to update status
ledUpdate(true);


+ 167
- 0
code/espurna/scheduler.ino View File

@ -0,0 +1,167 @@
/*
SCHEDULER MODULE
Copyright (C) 2017 by faina09
Adapted by Xose Pérez <xose dot perez at gmail dot com>
*/
#if SCHEDULER_SUPPORT
#include <NtpClientLib.h>
#if WEB_SUPPORT
void _schWebSocketOnSend(JsonObject &root){
root["maxScheduled"] = SCHEDULER_MAX_SCHEDULES;
JsonArray &sch = root.createNestedArray("schedule");
for (byte i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
if (!hasSetting("schSwitch", i)) break;
JsonObject &scheduler = sch.createNestedObject();
scheduler["schSwitch"] = getSetting("schSwitch", i, "");
scheduler["schAction"] = getSetting("schAction", i, "");
scheduler["schHour"] = getSetting("schHour", i, "");
scheduler["schMinute"] = getSetting("schMinute", i, "");
scheduler["schWDs"] = getSetting("schWDs", i, "");
}
}
#endif // WEB_SUPPORT
void _schConfigure() {
bool delete_flag = false;
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) delete_flag = true;
if (delete_flag) {
delSetting("schSwitch", i);
delSetting("schAction", i);
delSetting("schHour", i);
delSetting("schMinute", i);
delSetting("schWDs", i);
} else {
#if DEBUG_SUPPORT
int sch_action = getSetting("schAction", i, 0).toInt();
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
String sch_weekdays = getSetting("schWDs", i, "");
DEBUG_MSG_P(
PSTR("[SCH] Schedule #%d: %s switch #%d at %02d:%02d on %s\n"),
i, sch_action == 0 ? "turn OFF" : sch_action == 1 ? "turn ON" : "toggle", sch_switch,
sch_hour, sch_minute, (char *) sch_weekdays.c_str()
);
#endif // DEBUG_SUPPORT
}
}
}
bool _schIsThisWeekday(String weekdays){
// Monday = 1, Tuesday = 2 ... Sunday = 7
int w = weekday(now()) - 1;
if (w == 0) w = 7;
char pch;
char * p = (char *) weekdays.c_str();
unsigned char position = 0;
while (pch = p[position++]) {
if ((pch - '0') == w) return true;
}
return false;
}
int _schMinutesLeft(unsigned char schedule_hour, unsigned char schedule_minute){
unsigned char now_hour;
unsigned char now_minute;
if (ntpConnected()) {
String value = NTP.getTimeDateString();
now_hour = value.substring(0, 2).toInt();
now_minute = value.substring(3, 5).toInt();
} else {
time_t t = now();
now_hour = hour(t);
now_minute = minute(t);
}
return (schedule_hour - now_hour) * 60 + schedule_minute - now_minute;
}
// -----------------------------------------------------------------------------
void schSetup() {
_schConfigure();
// Update websocket clients
#if WEB_SUPPORT
wsOnSendRegister(_schWebSocketOnSend);
wsOnAfterParseRegister(_schConfigure);
#endif
}
void schLoop() {
static unsigned long last_update = 0;
static int update_time = 0;
// Check if we should compare scheduled and actual times
if ((millis() - last_update > update_time) || (last_update == 0)) {
last_update = millis();
// Calculate next update time
unsigned char current_second = ntpConnected() ?
NTP.getTimeDateString().substring(6, 8).toInt() :
second(now())
;
update_time = (SCHEDULER_UPDATE_SEC + 60 - current_second) * 1000;
for (unsigned char i = 0; i < SCHEDULER_MAX_SCHEDULES; i++) {
int sch_switch = getSetting("schSwitch", i, 0xFF).toInt();
if (sch_switch == 0xFF) break;
String sch_weekdays = getSetting("schWDs", i, "");
if (_schIsThisWeekday(sch_weekdays)) {
int sch_hour = getSetting("schHour", i, 0).toInt();
int sch_minute = getSetting("schMinute", i, 0).toInt();
int minutes_to_trigger = _schMinutesLeft(sch_hour, sch_minute);
if (minutes_to_trigger == 0) {
int sch_action = getSetting("schAction", i, 0).toInt();
if (sch_action == 2) {
relayToggle(sch_switch);
} else {
relayStatus(sch_switch, sch_action);
}
DEBUG_MSG_P(PSTR("[SCH] Schedule #%d TRIGGERED!!\n"), sch_switch);
} else if (minutes_to_trigger > 0) {
DEBUG_MSG_P(
PSTR("[SCH] %d minutes to trigger schedule #%d\n"),
minutes_to_trigger, sch_switch
);
}
}
}
}
}
#endif // SCHEDULER_SUPPORT

+ 46
- 0
code/espurna/sensor.ino View File

@ -203,6 +203,19 @@ void _sensorPost() {
void _sensorInit() {
/*
This is temporal, in the future sensors will be initialized based on
soft configuration (data stored in EEPROM config) so you will be able
to define and configure new sensors on the fly
At the time being, only enabled sensors (those with *_SUPPORT to 1) are being
loaded and initialized here. If you want to add new sensors of the same type
just duplicate the block and change the arguments for the set* methods.
Check the DHT block below for an example
*/
#if ANALOG_SUPPORT
{
AnalogSensor * sensor = new AnalogSensor();
@ -244,6 +257,19 @@ void _sensorInit() {
}
#endif
/*
// Example on how to add a second DHT sensor
// DHT2_PIN and DHT2_TYPE should be defined in sensors.h file
#if DHT_SUPPORT
{
DHTSensor * sensor = new DHTSensor();
sensor->setGPIO(DHT2_PIN);
sensor->setType(DHT2_TYPE);
_sensors.push_back(sensor);
}
#endif
*/
#if DIGITAL_SUPPORT
{
DigitalSensor * sensor = new DigitalSensor();
@ -634,11 +660,23 @@ void sensorLoop() {
dtostrf(filtered, 1-sizeof(buffer), decimals, buffer);
#if MQTT_SUPPORT
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
mqttSend(_magnitudeTopic(magnitude.type).c_str(), magnitude.global, buffer);
} else {
mqttSend(_magnitudeTopic(magnitude.type).c_str(), buffer);
}
#if SENSOR_PUBLISH_ADDRESSES
char topic[32];
snprintf(topic, sizeof(topic), "%s/%s", SENSOR_ADDRESS_TOPIC, _magnitudeTopic(magnitude.type).c_str());
if (SENSOR_USE_INDEX || (_counts[magnitude.type] > 1)) {
mqttSend(topic, magnitude.global, magnitude.sensor->address(magnitude.local).c_str());
} else {
mqttSend(topic, magnitude.sensor->address(magnitude.local).c_str());
}
#endif // SENSOR_PUBLISH_ADDRESSES
#endif // MQTT_SUPPORT
#if INFLUXDB_SUPPORT
@ -649,6 +687,10 @@ void sensorLoop() {
}
#endif // INFLUXDB_SUPPORT
#if THINGSPEAK_SUPPORT
tspkEnqueueMeasurement(i, buffer);
#endif
#if DOMOTICZ_SUPPORT
{
char key[15];
@ -685,6 +727,10 @@ void sensorLoop() {
wsSend(_sensorWebSocketSendData);
#endif
#if THINGSPEAK_SUPPORT
if (report_count == 0) tspkFlush();
#endif
}
}


+ 10
- 0
code/espurna/sensors/AnalogSensor.h View File

@ -37,6 +37,16 @@ class AnalogSensor : public BaseSensor {
return String("ANALOG @ GPIO0");
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 1
- 1
code/espurna/sensors/BMX280Sensor.h View File

@ -150,7 +150,7 @@ class BMX280Sensor : public I2CSensor {
void getConfig(JsonObject& root) {
root["sensor_id"] = _sensor_id;
root["address"] = getAddress();
root["address"] = _address;
};
void setConfig(JsonObject& root) {


+ 6
- 3
code/espurna/sensors/BaseSensor.h View File

@ -46,6 +46,12 @@ class BaseSensor {
// Descriptive name of the sensor
virtual String description() {}
// Address of the sensor (it could be the GPIO or I2C address)
virtual String address(unsigned char index) {}
// Descriptive name of the slot # index
virtual String slot(unsigned char index) {};
// Type for slot # index
virtual unsigned char type(unsigned char index) {}
@ -61,9 +67,6 @@ class BaseSensor {
// Load the configuration manifest
static void manifest(JsonArray& root) {};
// Descriptive name of the slot # index
String slot(unsigned char index) { return description(); }
// Sensor ID
unsigned char getID() { return _sensor_id; };


+ 10
- 0
code/espurna/sensors/DHTSensor.h View File

@ -90,6 +90,16 @@ class DHTSensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 13
- 0
code/espurna/sensors/DallasSensor.h View File

@ -170,6 +170,19 @@ class DallasSensor : public BaseSensor {
return String(buffer);
}
// Address of the device
String address(unsigned char index) {
char buffer[20] = {0};
if (index < _count) {
uint8_t * address = _devices[index].address;
snprintf(buffer, sizeof(buffer), "%02X%02X%02X%02X%02X%02X%02X%02X",
address[0], address[1], address[2], address[3],
address[4], address[5], address[6], address[7]
);
}
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 10
- 0
code/espurna/sensors/DigitalSensor.h View File

@ -67,6 +67,16 @@ class DigitalSensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 12
- 0
code/espurna/sensors/ECH1560Sensor.h View File

@ -82,6 +82,18 @@ class ECH1560Sensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%i:%i", _clk, _miso);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 8
- 0
code/espurna/sensors/EmonADS1X15Sensor.h View File

@ -203,6 +203,14 @@ class EmonADS1X15Sensor : public EmonSensor {
return String(buffer);
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[10];
unsigned char channel = getChannel(index % _ports);
snprintf(buffer, sizeof(buffer), "0x%02X:%i", _address, channel);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) {


+ 5
- 0
code/espurna/sensors/EmonAnalogSensor.h View File

@ -59,6 +59,11 @@ class EmonAnalogSensor : public EmonSensor {
return String("EMON @ ANALOG @ GPIO0");
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String("0");
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 10
- 0
code/espurna/sensors/EventSensor.h View File

@ -81,6 +81,16 @@ class EventSensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 12
- 0
code/espurna/sensors/HLW8012Sensor.h View File

@ -162,6 +162,18 @@ class HLW8012Sensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[10];
snprintf(buffer, sizeof(buffer), "%i:%i:%i", _sel, _cf, _cf1);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 12
- 0
code/espurna/sensors/I2CSensor.h View File

@ -23,6 +23,18 @@ class I2CSensor : public BaseSensor {
return _address;
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[5];
snprintf(buffer, sizeof(buffer), "0x%02X", _address);
return String(buffer);
}
protected:
// Specific for I2C sensors


+ 12
- 0
code/espurna/sensors/MHZ19Sensor.h View File

@ -89,6 +89,18 @@ class MHZ19Sensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%i:%i", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
_error = SENSOR_ERROR_OK;


+ 7
- 0
code/espurna/sensors/PMSX003Sensor.h View File

@ -97,6 +97,13 @@ class PMSX003Sensor : public BaseSensor {
return String();
}
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
char buffer[6];
snprintf(buffer, sizeof(buffer), "%i:%i", _pin_rx, _pin_tx);
return String(buffer);
}
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) {


+ 5
- 0
code/espurna/sensors/SI7021Sensor.h View File

@ -85,6 +85,11 @@ class SI7021Sensor : public I2CSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Type for slot # index
unsigned char type(unsigned char index) {
if (index < _count) {


+ 10
- 0
code/espurna/sensors/V9261FSensor.h View File

@ -77,6 +77,16 @@ class V9261FSensor : public BaseSensor {
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
return description();
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_pin_rx);
}
// Loop-like method, call it in your main loop
void tick() {
_read();


+ 33
- 10
code/espurna/settings.ino View File

@ -250,6 +250,24 @@ void settingsSetup() {
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("GPIO"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
int pin = String(e->argv[1]).toInt();
//if (!gpioValid(pin)) {
// DEBUG_MSG_P(PSTR("-ERROR: Invalid GPIO\n"));
// return;
//}
if (e->argc > 2) {
bool state = String(e->argv[2]).toInt() == 1;
digitalWrite(pin, state);
}
DEBUG_MSG_P(PSTR("GPIO %d is %s\n"), pin, digitalRead(pin) == HIGH ? "HIGH" : "LOW");
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("HEAP"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Free HEAP: %d bytes\n"), getFreeHeap());
DEBUG_MSG_P(PSTR("+OK\n"));
@ -290,6 +308,14 @@ void settingsSetup() {
});
#endif
#if MQTT_SUPPORT
Embedis::command( F("MQTT.RESET"), [](Embedis* e) {
mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
#if NOFUSS_SUPPORT
Embedis::command( F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n"));
@ -319,22 +345,19 @@ void settingsSetup() {
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
#if MQTT_SUPPORT
Embedis::command( F("RESET.MQTT"), [](Embedis* e) {
mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
Embedis::command( F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("RESET.WIFI"), [](Embedis* e) {
Embedis::command( F("WIFI.RESET"), [](Embedis* e) {
wifiConfigure();
wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("UPTIME"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
Embedis::command( F("WIFI.SCAN"), [](Embedis* e) {
wifiScan();
DEBUG_MSG_P(PSTR("+OK\n"));
});


+ 3116
- 3077
code/espurna/static/index.html.gz.h
File diff suppressed because it is too large
View File


+ 265
- 0
code/espurna/thinkspeak.ino View File

@ -0,0 +1,265 @@
/*
THINGSPEAK MODULE
Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if THINGSPEAK_SUPPORT
#if THINGSPEAK_USE_ASYNC
#include <ESPAsyncTCP.h>
AsyncClient * _tspk_client;
#else
#include <ESP8266WiFi.h>
#endif
const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: ESPurna\r\n"
"Connection: close\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: %d\r\n\r\n"
"%s\r\n";
bool _tspk_enabled = false;
char * _tspk_queue[8] = {NULL};
bool _tspk_flush = false;
unsigned long _tspk_last_flush = 0;
// -----------------------------------------------------------------------------
void _tspkWebSocketOnSend(JsonObject& root) {
unsigned char visible = 0;
root["tspkEnabled"] = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
root["tspkKey"] = getSetting("tspkKey");
JsonArray& relays = root.createNestedArray("tspkRelays");
for (byte i=0; i<relayCount(); i++) {
relays.add(getSetting("tspkRelay", i, 0).toInt());
}
if (relayCount() > 0) visible = 1;
#if SENSOR_SUPPORT
JsonArray& list = root.createNestedArray("tspkMagnitudes");
for (byte i=0; i<magnitudeCount(); i++) {
JsonObject& element = list.createNestedObject();
element["name"] = magnitudeName(i);
element["type"] = magnitudeType(i);
element["index"] = magnitudeIndex(i);
element["idx"] = getSetting("tspkMagnitude", i, 0).toInt();
}
if (magnitudeCount() > 0) visible = 1;
#endif
root["tspkVisible"] = visible;
}
void _tspkConfigure() {
_tspk_enabled = getSetting("tspkEnabled", THINGSPEAK_ENABLED).toInt() == 1;
if (_tspk_enabled && (getSetting("tspkKey").length() == 0)) {
_tspk_enabled = false;
setSetting("tspkEnabled", 0);
}
}
#if THINGSPEAK_USE_ASYNC
void _tspkPost(String data) {
if (_tspk_client == NULL) {
_tspk_client = new AsyncClient();
}
_tspk_client->onDisconnect([](void *s, AsyncClient *c) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Disconnected\n"));
_tspk_client->free();
delete _tspk_client;
_tspk_client = NULL;
}, 0);
_tspk_client->onTimeout([](void *s, AsyncClient *c, uint32_t time) {
_tspk_client->close(true);
}, 0);
_tspk_client->onData([](void * arg, AsyncClient * c, void * response, size_t len) {
char * b = (char *) response;
b[len] = 0;
char * p = strstr((char *)response, "\r\n\r\n");
unsigned int code = (p != NULL) ? atoi(&p[4]) : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_tspk_client->close(true);
}, NULL);
_tspk_client->onConnect([data](void * arg, AsyncClient * client) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
#if THINGSPEAK_USE_SSL
uint8_t fp[20] = {0};
sslFingerPrintArray(THINGSPEAK_FINGERPRINT, fp);
SSL * ssl = _tspk_client->getSSL();
if (ssl_match_fingerprint(ssl, fp) != SSL_OK) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
}
#endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str());
char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
snprintf_P(buffer, sizeof(buffer),
THINGSPEAK_REQUEST_TEMPLATE,
THINGSPEAK_URL,
THINGSPEAK_HOST,
data.length(),
data.c_str()
);
client->write(buffer);
}, NULL);
#if ASYNC_TCP_SSL_ENABLED
bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT, THINGSPEAK_USE_SSL);
#else
bool connected = _tspk_client->connect(THINGSPEAK_HOST, THINGSPEAK_PORT);
#endif
if (!connected) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
_tspk_client->close(true);
}
}
#else // THINGSPEAK_USE_ASYNC
void _tspkPost(String data) {
#if THINGSPEAK_USE_SSL
WiFiClientSecure _tspk_client;
#else
WiFiClient _tspk_client;
#endif
if (_tspk_client.connect(THINGSPEAK_HOST, THINGSPEAK_PORT)) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connected to %s:%d\n"), THINGSPEAK_HOST, THINGSPEAK_PORT);
if (!_tspk_client.verify(THINGSPEAK_FINGERPRINT, THINGSPEAK_HOST)) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
}
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str());
char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
snprintf_P(buffer, sizeof(buffer),
THINGSPEAK_REQUEST_TEMPLATE,
THINGSPEAK_URL,
THINGSPEAK_HOST,
data.length(),
data.c_str()
);
_tspk_client.print(buffer);
nice_delay(100);
String response = _tspk_client.readString();
int pos = response.indexOf("\r\n\r\n");
unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_tspk_client.stop();
return;
}
DEBUG_MSG_P(PSTR("[THINGSPEAK] Connection failed\n"));
}
#endif // THINGSPEAK_USE_ASYNC
bool _tspkEnqueue(unsigned char index, char * payload) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Enqueuing field #%d with value %s\n"), index, payload);
--index;
if (_tspk_queue[index] != NULL) free(_tspk_queue[index]);
_tspk_queue[index] = strdup(payload);
}
void _tspkFlush() {
String data;
// Walk the fields
for (unsigned char id=0; id<8; id++) {
if (_tspk_queue[id] != NULL) {
if (data.length() > 0) data = data + String("&");
data = data + String("field") + String(id+1) + String("=") + String(_tspk_queue[id]);
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
}
}
// POST data if any
if (data.length() > 0) {
data = data + String("&api_key=") + getSetting("tspkKey");
_tspkPost(data);
_tspk_last_flush = millis();
}
}
// -----------------------------------------------------------------------------
bool tspkEnqueueRelay(unsigned char index, unsigned char status) {
if (!_tspk_enabled) return true;
unsigned char id = getSetting("tspkRelay", index, 0).toInt();
if (id > 0) {
char payload[3];
itoa(status ? 1 : 0, payload, 10);
_tspkEnqueue(id, payload);
}
}
bool tspkEnqueueMeasurement(unsigned char index, char * payload) {
if (!_tspk_enabled) return true;
unsigned char id = getSetting("tspkMagnitude", index, 0).toInt();
if (id > 0) {
_tspkEnqueue(id, payload);
}
}
void tspkFlush() {
_tspk_flush = true;
}
bool tspkEnabled() {
return _tspk_enabled;
}
void tspkSetup() {
_tspkConfigure();
#if WEB_SUPPORT
wsOnSendRegister(_tspkWebSocketOnSend);
wsOnAfterParseRegister(_tspkConfigure);
#endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] Async %s, SSL %s\n"),
THINGSPEAK_USE_ASYNC ? "ENABLED" : "DISABLED",
THINGSPEAK_USE_SSL ? "ENABLED" : "DISABLED"
);
}
void tspkLoop() {
if (!_tspk_enabled) return;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return;
if (_tspk_flush && (millis() - _tspk_last_flush > THINGSPEAK_MIN_INTERVAL)) {
_tspkFlush();
_tspk_flush = false;
}
}
#endif

+ 56
- 1
code/espurna/utils.ino View File

@ -11,10 +11,20 @@ Ticker _defer_reset;
String getIdentifier() {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("ESPURNA_%06X"), ESP.getChipId());
snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), APP_NAME, ESP.getChipId());
return String(buffer);
}
void setBoardName() {
#ifndef ESPURNA_CORE
setSetting("boardName", DEVICE_NAME);
#endif
}
String getBoardName() {
return getSetting("boardName", DEVICE_NAME);
}
String getCoreVersion() {
String version = ESP.getCoreVersion();
#ifdef ARDUINO_ESP8266_RELEASE
@ -191,6 +201,51 @@ void heartbeat() {
}
// -----------------------------------------------------------------------------
// SSL
// -----------------------------------------------------------------------------
#if ASYNC_TCP_SSL_ENABLED
bool sslCheckFingerPrint(const char * fingerprint) {
return (strlen(fingerprint) == 59);
}
bool sslFingerPrintArray(const char * fingerprint, unsigned char * bytearray) {
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (!sslCheckFingerPrint(fingerprint)) return false;
// walk the fingerprint
for (unsigned int i=0; i<20; i++) {
bytearray[i] = strtol(fingerprint + 3*i, NULL, 16);
}
return true;
}
bool sslFingerPrintChar(const char * fingerprint, char * destination) {
// check length (20 2-character digits ':' or ' ' separated => 20*2+19 = 59)
if (!sslCheckFingerPrint(fingerprint)) return false;
// copy it
strncpy(destination, fingerprint, 59);
// walk the fingerprint replacing ':' for ' '
for (unsigned char i = 0; i<59; i++) {
if (destination[i] == ':') destination[i] = ' ';
}
return true;
}
#endif
// -----------------------------------------------------------------------------
// Reset
// -----------------------------------------------------------------------------
unsigned char resetReason() {


+ 122
- 68
code/espurna/wifi.ino View File

@ -41,6 +41,11 @@ String getNetwork() {
return WiFi.SSID();
}
double wifiDistance(int rssi) {
double exponent = (double) (WIFI_RSSI_1M - rssi) / WIFI_PROPAGATION_CONST / 10.0;
return round(pow(10, exponent));
}
void wifiDisconnect() {
jw.disconnect();
}
@ -111,8 +116,7 @@ void wifiConfigure() {
}
}
// Scan for best network only if we have more than 1 defined
jw.scanNetworks(i>1);
jw.scanNetworks(true);
}
@ -130,26 +134,74 @@ void wifiStatus() {
}
if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) {
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), jw.getAPSSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.softAPIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), jw.getAPSSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.softAPIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
}
if ((WiFi.getMode() & WIFI_STA) == WIFI_STA) {
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST %s\n"), WiFi.hostname().c_str());
uint8_t * bssid = WiFi.BSSID();
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST %s\n"), WiFi.hostname().c_str());
DEBUG_MSG_P(PSTR("[WIFI] BSSID %02X:%02X:%02X:%02X:%02X:%02X\n"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], bssid[6]
);
DEBUG_MSG_P(PSTR("[WIFI] CH %d\n"), WiFi.channel());
DEBUG_MSG_P(PSTR("[WIFI] RSSI %d\n"), WiFi.RSSI());
}
DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
}
void wifiScan() {
DEBUG_MSG_P(PSTR("[WIFI] Start scanning\n"));
unsigned char result = WiFi.scanNetworks();
if (result == WIFI_SCAN_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n"));
} else if (result == 0) {
DEBUG_MSG_P(PSTR("[WIFI] No networks found\n"));
} else {
DEBUG_MSG_P(PSTR("[WIFI] %d networks found:\n"), result);
// Populate defined networks with scan data
for (int8_t i = 0; i < result; ++i) {
String ssid_scan;
int32_t rssi_scan;
uint8_t sec_scan;
uint8_t* BSSID_scan;
int32_t chan_scan;
bool hidden_scan;
WiFi.getNetworkInfo(i, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan, hidden_scan);
DEBUG_MSG_P(PSTR("[WIFI] - BSSID: %02X:%02X:%02X:%02X:%02X:%02X SEC: %s RSSI: %3d CH: %2d SSID: %s\n"),
BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5], BSSID_scan[6],
(sec_scan != ENC_TYPE_NONE ? "YES" : "NO "),
rssi_scan,
chan_scan,
(char *) ssid_scan.c_str()
);
}
}
WiFi.scanDelete();
}
bool wifiClean(unsigned char num) {
bool changed = false;
@ -234,81 +286,83 @@ void wifiInject() {
}
void wifiSetup() {
#if DEBUG_SUPPORT
#if WIFI_SLEEP_ENABLED
wifi_set_sleep_type(LIGHT_SLEEP_T);
#endif
void _wifiDebug(justwifi_messages_t code, char * parameter) {
wifiInject();
wifiConfigure();
if (code == MESSAGE_SCANNING) {
DEBUG_MSG_P(PSTR("[WIFI] Scanning\n"));
}
// Message callbacks
jw.onMessage([](justwifi_messages_t code, char * parameter) {
if (code == MESSAGE_SCAN_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n"));
}
#if DEBUG_SUPPORT
if (code == MESSAGE_NO_NETWORKS) {
DEBUG_MSG_P(PSTR("[WIFI] No networks found\n"));
}
if (code == MESSAGE_SCANNING) {
DEBUG_MSG_P(PSTR("[WIFI] Scanning\n"));
}
if (code == MESSAGE_NO_KNOWN_NETWORKS) {
DEBUG_MSG_P(PSTR("[WIFI] No known networks found\n"));
}
if (code == MESSAGE_SCAN_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Scan failed\n"));
}
if (code == MESSAGE_FOUND_NETWORK) {
DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter);
}
if (code == MESSAGE_NO_NETWORKS) {
DEBUG_MSG_P(PSTR("[WIFI] No networks found\n"));
}
if (code == MESSAGE_CONNECTING) {
DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter);
}
if (code == MESSAGE_NO_KNOWN_NETWORKS) {
DEBUG_MSG_P(PSTR("[WIFI] No known networks found\n"));
}
if (code == MESSAGE_CONNECT_WAITING) {
// too much noise
}
if (code == MESSAGE_FOUND_NETWORK) {
DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter);
}
if (code == MESSAGE_CONNECT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not connect to %s\n"), parameter);
}
if (code == MESSAGE_CONNECTING) {
DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter);
}
if (code == MESSAGE_CONNECTED) {
wifiStatus();
}
if (code == MESSAGE_CONNECT_WAITING) {
// too much noise
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
wifiStatus();
}
if (code == MESSAGE_CONNECT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not connect to %s\n"), parameter);
}
if (code == MESSAGE_DISCONNECTED) {
DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n"));
}
if (code == MESSAGE_CONNECTED) {
wifiStatus();
}
if (code == MESSAGE_ACCESSPOINT_CREATING) {
DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n"));
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
wifiStatus();
}
if (code == MESSAGE_ACCESSPOINT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n"));
}
}
if (code == MESSAGE_DISCONNECTED) {
DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n"));
}
#endif // DEBUG_SUPPORT
if (code == MESSAGE_ACCESSPOINT_CREATING) {
DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n"));
}
void wifiRegister(wifi_callback_f callback) {
jw.subscribe(callback);
}
if (code == MESSAGE_ACCESSPOINT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n"));
}
void wifiSetup() {
#endif // DEBUG_SUPPORT
#if WIFI_SLEEP_ENABLED
wifi_set_sleep_type(LIGHT_SLEEP_T);
#endif
#if MQTT_SUPPORT
#if MDNS_SUPPORT
if (code == MESSAGE_CONNECTED) mdnsFindMQTT();
#endif // MDNS_SUPPORT
#endif // MQTT_SUPPORT
wifiInject();
wifiConfigure();
});
// Message callbacks
#if DEBUG_SUPPORT
wifiRegister(_wifiDebug);
#endif
#if WEB_SUPPORT
wsOnSendRegister(_wifiWebSocketOnSend);


+ 10
- 0
code/espurna/ws.ino View File

@ -220,6 +220,12 @@ void _wsOnStart(JsonObject& root) {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
uint8_t * bssid = WiFi.BSSID();
char bssid_str[20];
snprintf_P(bssid_str, sizeof(bssid_str),
PSTR("%02X:%02X:%02X:%02X:%02X:%02X"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]
);
root["webMode"] = WEB_MODE_NORMAL;
@ -229,6 +235,10 @@ void _wsOnStart(JsonObject& root) {
root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress();
root["bssid"] = String(bssid_str);
root["channel"] = WiFi.channel();
root["rssi"] = WiFi.RSSI();
root["distance"] = wifiDistance(WiFi.RSSI());
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["network"] = getNetwork();


+ 5
- 3
code/extra_scripts.py View File

@ -1,4 +1,5 @@
#!/usr/bin/env python
import time
Import("env")
# ------------------------------------------------------------------------------
@ -31,11 +32,12 @@ def clr(color, text):
# ------------------------------------------------------------------------------
def check_size(source, target, env):
time.sleep(1)
size = target[0].get_size()
print clr(Color.LIGHT_BLUE, "Binary size: %s bytes" % size)
if size > 512000:
print clr(Color.LIGHT_RED, "File too large for OTA!")
Exit(1)
#if size > 512000:
# print clr(Color.LIGHT_RED, "File too large for OTA!")
# Exit(1)
# ------------------------------------------------------------------------------
# Hooks


+ 20
- 5
code/html/custom.css View File

@ -62,14 +62,20 @@
margin-left: 5px;
}
.button-add-network,
.button-add-schedule,
.button-rfb-learn {
background: rgb(28, 184, 65);
}
.button-del-network {
.button-del-network,
.button-del-schedule {
background: rgb(202, 60, 60);
letter-spacing: 0px;
}
.pure-button {
letter-spacing: 0;
}
.button-more-network,
.button-more-schedule,
.button-rfb-send {
background: rgb(223, 117, 20);
}
@ -103,10 +109,11 @@ div.hint {
.break {
margin-top: 5px;
}
#networks .pure-g {
#networks .pure-g,
#schedules .pure-g {
padding: 10px 0 10px 0;
margin-bottom: 5px;
border-bottom: 2px dashed #e5e5e5;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
}
#networks .more {
display: none;
@ -198,3 +205,11 @@ select {
width: 100%;
margin-bottom: 10px;
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #eee;
margin: 1em 0;
padding: 0;
}

+ 109
- 15
code/html/custom.js View File

@ -1,6 +1,7 @@
var websock;
var password = false;
var maxNetworks;
var maxSchedules;
var messages = [];
var webhost;
@ -100,9 +101,11 @@ function validateForm(form) {
// These fields will always be a list of values
var is_group = [
"ssid", "pass", "gw", "mask", "ip", "dns",
"schSwitch","schAction","schHour","schMinute","schWDs",
"relayBoot", "relayPulse", "relayTime",
"mqttGroup", "mqttGroupInv",
"dczRelayIdx",
"dczRelayIdx", "dczMagnitude",
"tspkRelay", "tspkMagnitude",
"ledMode",
"adminPass"
];
@ -143,6 +146,13 @@ function getData(form) {
}
});
// Post process
if ("schSwitch" in data) {
data["schSwitch"].push(0xFF);
} else {
data["schSwitch"] = [0xFF];
}
return data;
}
@ -394,36 +404,36 @@ function toggleMenu() {
}
// -----------------------------------------------------------------------------
// Domoticz
// Relays & magnitudes mapping
// -----------------------------------------------------------------------------
function createRelayIdxs(data) {
function createRelayList(data, container, template) {
var current = $("#domoticzRelays > div").length;
var current = $("#" + container + " > div").length;
if (current > 0) return;
var template = $("#relayIdxTemplate .pure-g")[0];
var template = $("#" + template + " .pure-g")[0];
for (var i=0; i<data.length; i++) {
var line = $(template).clone();
$("label", line).html("Switch #" + i);
$("input", line).attr("name", "dczRelayIdx" + i).attr("tabindex", 40 + i).val(data[i]);
line.appendTo("#domoticzRelays");
$("input", line).attr("tabindex", 40 + i).val(data[i]);
line.appendTo("#" + container);
}
}
function createMagnitudeIdxs(data) {
function createMagnitudeList(data, container, template) {
var current = $("#domoticzMagnitudes > div").length;
var current = $("#" + container + " > div").length;
if (current > 0) return;
var template = $("#magnitudeIdxTemplate .pure-g")[0];
var template = $("#" + template + " .pure-g")[0];
for (var i=0; i<data.length; i++) {
var line = $(template).clone();
$("label", line).html(magnitudeType(data[i].type) + " #" + parseInt(data[i].index));
$("div.hint", line).html(data[i].name);
$("input", line).attr("name", "dczMagnitude" + i).attr("tabindex", 40 + i).val(data[i].idx);
line.appendTo("#domoticzMagnitudes");
$("input", line).attr("tabindex", 40 + i).val(data[i].idx);
line.appendTo("#" + container);
}
}
@ -464,6 +474,39 @@ function moreNetwork() {
$(".more", parent).toggle();
}
// -----------------------------------------------------------------------------
// Relays scheduler
// -----------------------------------------------------------------------------
function delSchedule() {
var parent = $(this).parents(".pure-g");
$(parent).remove();
}
function moreSchedule() {
var parent = $(this).parents(".pure-g");
$("div.more", parent).toggle();
}
function addSchedule() {
var numSchedules = $("#schedules > div").length;
if (numSchedules >= maxSchedules) {
alert("Max number of schedules reached");
return;
}
var tabindex = 200 + numSchedules * 10;
var template = $("#scheduleTemplate").children();
var line = $(template).clone();
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex++);
});
$(line).find(".button-del-schedule").on('click', delSchedule);
$(line).find(".button-more-schedule").on('click', moreSchedule);
line.appendTo("#schedules");
return line;
}
// -----------------------------------------------------------------------------
// Relays
// -----------------------------------------------------------------------------
@ -683,6 +726,8 @@ function rfbSend() {
function processData(data) {
console.log(data);
// title
if ("app_name" in data) {
var title = data.app_name;
@ -817,6 +862,28 @@ function processData(data) {
return;
}
// -----------------------------------------------------------------------------
// Relays scheduler
// -----------------------------------------------------------------------------
if (key == "maxSchedules") {
maxSchedules = parseInt(data.maxSchedules);
return;
}
if (key == "schedule") {
var schedule = data.schedule;
for (var i in schedule) {
var line = addSchedule();
var schedule = data.schedule[i];
Object.keys(schedule).forEach(function(key) {
$("input[name=" + key + "]", line).val(schedule[key]);
$("select[name=" + key + "]", line).prop("value", schedule[key]);
});
}
return;
}
// ---------------------------------------------------------------------
// Relays
// ---------------------------------------------------------------------
@ -824,9 +891,17 @@ function processData(data) {
if (key == "relayStatus") {
initRelays(data[key]);
for (var i in data[key]) {
// Set the status for each relay
$("input.relayStatus[data='" + i + "']")
.prop("checked", data[key][i])
.iphoneStyle("refresh");
// Populate the relay SELECTs
$("select.isrelay").append(
$("<option></option>").attr("value",i).text("Switch #" + i)
);
}
return;
}
@ -842,14 +917,30 @@ function processData(data) {
// ---------------------------------------------------------------------
// Domoticz - Relays
if (key == "dczRelayIdx") {
createRelayIdxs(data[key]);
if (key == "dczRelays") {
createRelayList(data[key], "dczRelays", "dczRelayTemplate");
return;
}
// Domoticz - Magnitudes
if (key == "dczMagnitudes") {
createMagnitudeIdxs(data[key]);
createMagnitudeList(data[key], "dczMagnitudes", "dczMagnitudeTemplate");
return;
}
// ---------------------------------------------------------------------
// Thingspeak
// ---------------------------------------------------------------------
// Thingspeak - Relays
if (key == "tspkRelays") {
createRelayList(data[key], "tspkRelays", "tspkRelayTemplate");
return;
}
// Thingspeak - Magnitudes
if (key == "tspkMagnitudes") {
createMagnitudeList(data[key], "tspkMagnitudes", "tspkMagnitudeTemplate");
return;
}
@ -1036,6 +1127,9 @@ $(function() {
$(".button-add-network").on('click', function() {
$(".more", addNetwork()).toggle();
});
$(".button-add-schedule").on('click', function() {
$("div.more", addSchedule()).toggle();
});
$(document).on('change', 'input', hasChanged);
$(document).on('change', 'select', hasChanged);


+ 141
- 18
code/html/index.html View File

@ -100,6 +100,10 @@
<a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a>
</li>
<li class="pure-menu-item module module-relay">
<a href="#" class="pure-menu-link" data="panel-schedule">SCHEDULE</a>
</li>
<li class="pure-menu-item module module-color">
<a href="#" class="pure-menu-link" data="panel-color">LIGHTS</a>
</li>
@ -116,6 +120,10 @@
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
</li>
<li class="pure-menu-item module module-tspk">
<a href="#" class="pure-menu-link" data="panel-thingspeak">THINGSPEAK</a>
</li>
<li class="pure-menu-item module module-rfb">
<a href="#" class="pure-menu-link" data="panel-rfb">RFBRIDGE</a>
</li>
@ -182,6 +190,15 @@
<div class="pure-u-1-2 pure-u-lg-1-4">Network</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="network"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">BSSID</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="bssid"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">Channel</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="channel"></span></div>
<div class="pure-u-1-2 pure-u-lg-1-4">RSSI</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="rssi"></span> (<span name="distance" post="m"></span>)</div>
<div class="pure-u-1-2 pure-u-lg-1-4">IP</div>
<div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="deviceip"></span></div>
@ -324,10 +341,10 @@
<div class="pure-g module module-multirelay">
<label class="pure-u-1 pure-u-lg-1-4">Switch sync mode</label>
<select name="relaySync" class="pure-u-1 pure-u-lg-3-4" tabindex="3">
<option value="0">No synchonisation</option>
<option value="0">No synchronisation</option>
<option value="1">Zero or one switches active</option>
<option value="2">One and just one switch active</option>
<option value="3">All synchonised</option>
<option value="3">All synchronised</option>
</select>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Define how the different switches should be synchronized.</div>
@ -526,8 +543,7 @@
<fieldset>
<div id="networks">
</div>
<div id="networks"></div>
<button type="button" class="pure-button button-add-network">Add network</button>
@ -535,6 +551,27 @@
</div>
</div>
<div class="panel" id="panel-schedule">
<div class="header">
<h1>SCHEDULE</h1>
<h2>Turn switches ON and OFF based on the current time.</h2>
</div>
<div class="page">
<fieldset>
<div id="schedules"></div>
<button type="button" class="pure-button button-add-schedule">Add schedule</button>
</fieldset>
</div>
</div>
<div class="panel" id="panel-mqtt">
<div class="header">
@ -597,7 +634,7 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">MQTT Keep Alive</label>
<input class="pure-u-1 pure-u-lg-1-4" type="number" name="mqttKeep" min="10" max="60" tabindex="28" />
<input class="pure-u-1 pure-u-lg-1-4" type="number" name="mqttKeep" min="10" max="3600" tabindex="28" />
</div>
<div class="pure-g module module-mqttssl">
@ -714,14 +751,6 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="dczEnabled" tabindex="30" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Anti-recursion time</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-23-24" name="dczSkip" type="number" min="0" max="10" tabindex="31" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-1-2 hint">Skip messages from the same IDX for these many seconds</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Domoticz IN Topic</label>
<input class="pure-u-1 pure-u-lg-3-4" name="dczTopicIn" type="text" tabindex="31" />
@ -738,9 +767,49 @@
<div class="pure-u-1 hint">Set IDX to 0 to disable notifications from that component.</div>
</div>
<div id="domoticzRelays"></div>
<div id="dczRelays"></div>
<div id="domoticzMagnitudes"></div>
<div id="dczMagnitudes"></div>
</fieldset>
</div>
</div>
<div class="panel" id="panel-thingspeak">
<div class="header">
<h1>THINGSPEAK</h1>
<h2>
Send your sensors data to Thinkgspeak.
</h2>
</div>
<div class="page">
<fieldset>
<legend>General</legend>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Enable Thingspeak</label></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="tspkEnabled" tabindex="30" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Thingspeak API Key</label>
<input class="pure-u-1 pure-u-lg-3-4" name="tspkKey" type="text" tabindex="31" />
</div>
<legend>Sensors &amp; actuators</legend>
<div class="pure-g">
<div class="pure-u-1 hint">Enter the field number to send each data to, 0 disable notifications from that component.</div>
</div>
<div id="tspkRelays"></div>
<div id="tspkMagnitudes"></div>
</fieldset>
</div>
@ -979,7 +1048,46 @@
<div class="pure-u-1 pure-u-lg-3-4 hint more">Set the Domain Name Server IP to use when using a static IP</div>
<div class="pure-u-0 pure-u-lg-1-4 more"></div>
<button type="button" class="pure-button button-del-network more">Delete network</button>
<button class="pure-button button-del-network more" type="button">Delete network</button>
</div>
</div>
<div id="scheduleTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">When time is</label>
<div class="pure-u-1-4 pure-u-lg-1-5">
<input class="pure-u-2-3" name="schHour" type="number" min="0" step="1" max="23" size="20" />
<div class="pure-u-1-4 hint center"> h</div>
</div>
<div class="pure-u-1-4 pure-u-lg-1-5">
<input class="pure-u-2-3" name="schMinute" type="number" min="0" step="1" max="59" />
<div class="pure-u-1-4 hint center"> m</div>
</div>
<div class="pure-u-0 pure-u-lg-1-3"></div>
<label class="pure-u-1 pure-u-lg-1-4">And weekday is one of</label>
<div class="pure-u-2-5 pure-u-lg-1-5">
<input class="pure-u-23-24 pure-u-lg-23-24" name="schWDs" type="text" size="15" tabindex="0" value="1,2,3,4,5,6,7" />
</div>
<div class="pure-u-3-5 pure-u-lg-1-2 hint center">1 for Monday, 2 for Tuesday...</div>
<label class="pure-u-1 pure-u-lg-1-4">Action</label>
<div class="pure-u-1 pure-u-lg-1-5">
<select class="pure-u-1 pure-u-lg-23-24" name="schAction">
<option value="0">Turn OFF</option>
<option value="1">Turn ON</option>
<option value="2">Toggle</option>
</select>
</div>
<select class="pure-u-1 pure-u-lg-1-5 isrelay" name="schSwitch"></select>
<div class="pure-u-0 pure-u-lg-1-5"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<button class="pure-button button-del-schedule" type="button">Delete schedule</button>
</div>
@ -1028,14 +1136,14 @@
</div>
</div>
<div id="relayIdxTemplate" class="template">
<div id="dczRelayTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Switch</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-23-24 dczRelayIdx" name="dczRelayIdx" type="number" min="0" tabindex="0" data="0" /></div>
</div>
</div>
<div id="magnitudeIdxTemplate" class="template">
<div id="dczMagnitudeTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Magnitude</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-23-24 center" name="dczMagnitude" type="number" min="0" tabindex="0" data="0" /></div>
@ -1043,6 +1151,21 @@
</div>
</div>
<div id="tspkRelayTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Switch</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-23-24" name="tspkRelay" type="number" min="0" max="8" tabindex="0" data="0" /></div>
</div>
</div>
<div id="tspkMagnitudeTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Magnitude</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-23-24 center" name="tspkMagnitude" type="number" min="0" max="8" tabindex="0" data="0" /></div>
<div class="pure-u-1 pure-u-lg-1-2 hint center"></div>
</div>
</div>
<div id="colorRGBTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Color</label>


+ 98
- 92
code/memanalyzer.py View File

@ -1,5 +1,6 @@
#!/usr/bin/env python
#-------------------------------------------------------------------------------
# coding=utf-8
# -------------------------------------------------------------------------------
# ESPurna module memory analyser
# xose.perez@gmail.com
#
@ -11,23 +12,28 @@
# https://github.com/Sermus/ESP8266_memory_analyzer
# by Andrey Filimonov
#
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
from __future__ import print_function
import argparse
import os
import re
import shlex
import sys
from collections import OrderedDict
from sortedcontainers import SortedDict
import shlex
import commands
import subprocess
import sys
import os
import re
import argparse
#-------------------------------------------------------------------------------
if (sys.version_info > (3, 0)):
from subprocess import getstatusoutput as getstatusoutput
else:
from commands import getstatusoutput as getstatusoutput
# -------------------------------------------------------------------------------
TOTAL_IRAM = 32786;
TOTAL_DRAM = 81920;
env="esp8266-4m-ota"
TOTAL_IRAM = 32786
TOTAL_DRAM = 81920
env = "esp8266-4m-ota"
objdump_binary = "xtensa-lx106-elf-objdump"
sections = OrderedDict([
("data", "Initialized Data (RAM)"),
@ -38,7 +44,8 @@ sections = OrderedDict([
])
description = "ESPurna Memory Analyzer v0.1"
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
def file_size(file):
try:
@ -46,8 +53,8 @@ def file_size(file):
except:
return 0
def analyse_memory(elf_file):
def analyse_memory(elf_file):
command = "%s -t '%s' " % (objdump_binary, elf_file)
response = subprocess.check_output(shlex.split(command))
if isinstance(response, bytes):
@ -56,59 +63,58 @@ def analyse_memory(elf_file):
# print("{0: >10}|{1: >30}|{2: >12}|{3: >12}|{4: >8}".format("Section", "Description", "Start (hex)", "End (hex)", "Used space"));
# print("------------------------------------------------------------------------------");
ret={}
usedRAM = 0
usedIRAM = 0
i = 0
for (id, descr) in list(sections.items()):
sectionStartToken = " _%s_start" % id
sectionEndToken = " _%s_end" % id
sectionStart = -1
sectionEnd = -1
ret = {}
for (id_, descr) in list(sections.items()):
section_start_token = " _%s_start" % id_
section_end_token = " _%s_end" % id_
section_start = -1
section_end = -1
for line in lines:
if sectionStartToken in line:
if section_start_token in line:
data = line.split(' ')
sectionStart = int(data[0], 16)
section_start = int(data[0], 16)
if sectionEndToken in line:
if section_end_token in line:
data = line.split(' ')
sectionEnd = int(data[0], 16)
section_end = int(data[0], 16)
if sectionStart != -1 and sectionEnd != -1:
if section_start != -1 and section_end != -1:
break
sectionLength = sectionEnd - sectionStart
section_length = section_end - section_start
# if i < 3:
# usedRAM += sectionLength
# usedRAM += section_length
# if i == 3:
# usedIRAM = TOTAL_IRAM - sectionLength;
# usedIRAM = TOTAL_IRAM - section_length;
ret[id]=sectionLength
# print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id, descr, sectionStart, sectionEnd, sectionLength))
ret[id_] = section_length
# print("{0: >10}|{1: >30}|{2:12X}|{3:12X}|{4:8}".format(id_, descr, section_start, section_end, section_length))
# i += 1
# print("Total Used RAM : %d" % usedRAM)
# print("Free RAM : %d" % (TOTAL_DRAM - usedRAM))
# print("Free IRam : %d" % usedIRAM)
return(ret)
return ret
def run(env, modules):
def run(env_, modules_):
flags = ""
for item in modules.items():
for item in modules_.items():
flags += "-D%s_SUPPORT=%d " % item
command = "export ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s" % (flags, env)
command = "export ESPURNA_BOARD=\"WEMOS_D1_MINI_RELAYSHIELD\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s" % (flags, env_)
subprocess.check_call(command, shell=True)
def modules_get():
modules = SortedDict()
modules_ = SortedDict()
for line in open("espurna/config/arduino.h"):
m = re.search(r'(\w*)_SUPPORT', line)
if m:
modules[m.group(1)] = 0
del modules['LLMNR']
del modules['NETBIOS']
return modules
modules_[m.group(1)] = 0
del modules_['LLMNR']
del modules_['NETBIOS']
return modules_
try:
@ -120,23 +126,23 @@ try:
args = parser.parse_args()
# Hello
print
print description
print
print()
print(description)
print()
# Check xtensa-lx106-elf-objdump is in the path
status, result = commands.getstatusoutput(objdump_binary)
if status != 512:
print "xtensa-lx106-elf-objdump not found, please check it is in your PATH"
status, result = getstatusoutput(objdump_binary)
if status != 2 and status != 512:
print("xtensa-lx106-elf-objdump not found, please check it is in your PATH")
sys.exit(1)
# Load list of all modules
available_modules = modules_get()
if args.list > 0:
print "List of available modules:\n"
print("List of available modules:\n")
for key, value in available_modules.items():
print "* " + key
print
print("* " + key)
print()
sys.exit(0)
# Which modules to test?
@ -150,7 +156,7 @@ try:
# Check test modules exist
for module in test_modules:
if module not in available_modules:
print "Module %s not found" % module
print("Module %s not found" % module)
sys.exit(2)
# Define base configuration
@ -163,19 +169,19 @@ try:
# Show init message
if len(test_modules) > 0:
print "Analyzing module(s) %s on top of %s configuration\n" % (", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT")
print("Analyzing module(s) %s on top of %s configuration\n" % (", ".join(test_modules), "CORE" if args.core > 0 else "DEFAULT"))
else:
print "Analyzing %s configuration\n" % ("CORE" if args.core > 0 else "DEFAULT")
print("Analyzing %s configuration\n" % ("CORE" if args.core > 0 else "DEFAULT"))
output_format="{:<20}|{:<11}|{:<11}|{:<11}|{:<11}|{:<11}|{:<12}"
output_format = "{:<20}|{:<11}|{:<11}|{:<11}|{:<11}|{:<11}|{:<12}"
print(output_format.format(
"Module",
"Cache IRAM",
"Init RAM",
"R.O. RAM",
"Uninit RAM",
"Flash ROM",
"Binary size"
"Module",
"Cache IRAM",
"Init RAM",
"R.O. RAM",
"Uninit RAM",
"Flash ROM",
"Binary size"
))
# Build the core without modules to get base memory usage
@ -183,13 +189,13 @@ try:
base = analyse_memory(".pioenvs/%s/firmware.elf" % env)
base['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
print(output_format.format(
"CORE" if args.core == 1 else "DEFAULT",
base['text'],
base['data'],
base['rodata'],
base['bss'],
base['irom0_text'],
base['size'],
"CORE" if args.core == 1 else "DEFAULT",
base['text'],
base['data'],
base['rodata'],
base['bss'],
base['irom0_text'],
base['size'],
))
# Test each module
@ -198,18 +204,18 @@ try:
modules[module] = 1
run(env, modules)
results[module]=analyse_memory(".pioenvs/%s/firmware.elf" % env)
results[module] = analyse_memory(".pioenvs/%s/firmware.elf" % env)
results[module]['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
modules[module] = 0
print(output_format.format(
module,
results[module]['text'] - base['text'],
results[module]['data'] - base['data'],
results[module]['rodata'] - base['rodata'],
results[module]['bss'] - base['bss'],
results[module]['irom0_text'] - base['irom0_text'],
results[module]['size'] - base['size'],
module,
results[module]['text'] - base['text'],
results[module]['data'] - base['data'],
results[module]['rodata'] - base['rodata'],
results[module]['bss'] - base['bss'],
results[module]['irom0_text'] - base['irom0_text'],
results[module]['size'] - base['size'],
))
# Test all modules
@ -223,23 +229,23 @@ try:
if len(test_modules) > 1:
print(output_format.format(
"ALL MODULES",
total['text'] - base['text'],
total['data'] - base['data'],
total['rodata'] - base['rodata'],
total['bss'] - base['bss'],
total['irom0_text'] - base['irom0_text'],
total['size'] - base['size'],
"ALL MODULES",
total['text'] - base['text'],
total['data'] - base['data'],
total['rodata'] - base['rodata'],
total['bss'] - base['bss'],
total['irom0_text'] - base['irom0_text'],
total['size'] - base['size'],
))
print(output_format.format(
"TOTAL",
total['text'],
total['data'],
total['rodata'],
total['bss'],
total['irom0_text'],
total['size'],
"TOTAL",
total['text'],
total['data'],
total['rodata'],
total['bss'],
total['irom0_text'],
total['size'],
))
except:


+ 151
- 68
code/ota.py View File

@ -1,31 +1,40 @@
#!/usr/bin/env python
#-------------------------------------------------------------------------------
# coding=utf-8
# -------------------------------------------------------------------------------
# ESPurna OTA manager
# xose.perez@gmail.com
#
# Requires PlatformIO Core
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
from __future__ import print_function
import sys
import argparse
import re
import logging
import socket
import argparse
import subprocess
import sys
from time import sleep
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
#-------------------------------------------------------------------------------
try:
# noinspection PyUnresolvedReferences
input = raw_input # Python2
except NameError:
pass # Python3
# -------------------------------------------------------------------------------
devices = []
description = "ESPurna OTA Manager v0.1"
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
def on_service_state_change(zeroconf, service_type, name, state_change):
'''
"""
Callback that adds discovered devices to "devices" list
'''
"""
if state_change is ServiceStateChange.Added:
info = zeroconf.get_service_info(service_type, name)
@ -38,15 +47,20 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
device['app'] = info.properties.get('app_name', '')
device['version'] = info.properties.get('app_version', '')
device['device'] = info.properties.get('target_board', '')
device['mem_size'] = info.properties.get('mem_size', '')
device['sdk_size'] = info.properties.get('sdk_size', '')
if 'mem_size' in info.properties:
device['mem_size'] = info.properties.get('mem_size')
if 'sdk_size' in info.properties:
device['sdk_size'] = info.properties.get('sdk_size')
if 'free_space' in info.properties:
device['free_space'] = info.properties.get('free_space')
devices.append(device)
def list():
'''
"""
Shows the list of discovered devices
'''
output_format="{:>3} {:<25}{:<25}{:<15}{:<15}{:<30}{:<10}{:<10}"
"""
output_format="{:>3} {:<25}{:<25}{:<15}{:<15}{:<30}{:<10}{:<10}{:<10}"
print(output_format.format(
"#",
"HOSTNAME",
@ -56,8 +70,9 @@ def list():
"DEVICE",
"MEM_SIZE",
"SDK_SIZE",
"FREE_SPACE"
))
print "-" * 135
print("-" * 146)
index = 0
for device in devices:
@ -71,14 +86,16 @@ def list():
device.get('device', ''),
device.get('mem_size', ''),
device.get('sdk_size', ''),
device.get('free_space', ''),
))
print
print()
def get_boards():
'''
"""
Grabs board types fro hardware.h file
'''
"""
boards = []
for line in open("espurna/config/hardware.h"):
m = re.search(r'defined\((\w*)\)', line)
@ -86,10 +103,51 @@ def get_boards():
boards.append(m.group(1))
return sorted(boards)
def flash():
'''
def get_empty_board():
"""
Returns the empty structure of a board to flash
"""
board = {'board': '', 'ip': '', 'size': 0, 'auth': '', 'flags': ''}
return board
def get_board_by_index(index):
"""
Returns the required data to flash a given board
"""
board = {}
if 1 <= index and index <= len(devices):
device = devices[index - 1]
board['hostname'] = device.get('hostname')
board['board'] = device.get('device', '')
board['ip'] = device.get('ip', '')
board['size'] = int(device.get('mem_size', 0) if device.get('mem_size', 0) == device.get('sdk_size', 0) else 0) / 1024
return board
def get_board_by_hostname(hostname):
"""
Returns the required data to flash a given board
"""
hostname = hostname.lower()
for device in devices:
if device.get('hostname', '').lower() == hostname:
board = {}
board['hostname'] = device.get('hostname')
board['board'] = device.get('device')
if not board['board']:
return None
board['ip'] = device.get('ip')
if not board['ip']:
return None
board['size'] = int(device.get('sdk_size', 0)) / 1024
if board['size'] == 0:
return None
return board
return None
def input_board():
"""
Grabs info from the user about what device to flash
'''
"""
# Choose the board
try:
@ -97,92 +155,88 @@ def flash():
except:
index = 0
if index < 0 or len(devices) < index:
print "Board number must be between 1 and %s\n" % str(len(devices))
print("Board number must be between 1 and %s\n" % str(len(devices)))
return None
board = {'board': '', 'ip': '', 'size': 0 , 'auth': '', 'flags': ''}
if index > 0:
device = devices[index-1]
board['board'] = device.get('device', '')
board['ip'] = device.get('ip', '')
board['size'] = int(device.get('mem_size', 0) if device.get('mem_size', 0) == device.get('sdk_size', 0) else 0) / 1024
board = get_board_by_index(index);
# Choose board type if none before
if len(board['board']) == 0:
if len(board.get('board', '')) == 0:
print
print()
count = 1
boards = get_boards()
for name in boards:
print "%3d\t%s" % (count, name)
print("%3d\t%s" % (count, name))
count = count + 1
print
print()
try:
index = int(input("Choose the board type you want to flash: "))
except:
index = 0
if index < 1 or len(boards) < index:
print "Board number must be between 1 and %s\n" % str(len(boards))
print("Board number must be between 1 and %s\n" % str(len(boards)))
return None
board['board'] = boards[index-1]
board['board'] = boards[index - 1]
# Choose board size of none before
if board['size'] == 0:
if board.get('size', 0) == 0:
try:
board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): "))
except:
print "Wrong memory size"
print("Wrong memory size")
return None
# Choose IP of none before
if len(board['ip']) == 0:
if len(board.get('ip', '')) == 0:
try:
board['ip'] = raw_input("IP of the device to flash (empty for 192.168.4.1): ") or "192.168.4.1"
board['ip'] = input("IP of the device to flash (empty for 192.168.4.1): ") or "192.168.4.1"
except:
print "Wrong IP"
print("Wrong IP")
return None
board['auth'] = raw_input("Authorization key of the device to flash: ")
board['flags'] = raw_input("Extra flags for the build: ")
return board
def run(device, env):
print("Building and flashing image over-the-air...")
command = "export ESPURNA_IP=\"%s\"; export ESPURNA_BOARD=\"%s\"; export ESPURNA_AUTH=\"%s\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s -t upload"
command = command % (device['ip'], device['board'], device['auth'], device['flags'], env)
subprocess.check_call(command, shell=True)
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
if __name__ == '__main__':
# Parse command line options
parser = argparse.ArgumentParser(description=description)
#parser.add_argument("-v", "--verbose", help="show verbose output", default=0, action='count')
parser.add_argument("-c", "--core", help="flash ESPurna core", default=0, action='count')
parser.add_argument("-f", "--flash", help="flash device", default=0, action='count')
parser.add_argument("-o", "--flags", help="extra flags", default='')
parser.add_argument("-p", "--password", help="auth password", default='')
parser.add_argument("-s", "--sort", help="sort devices list by field", default='hostname')
parser.add_argument("hostnames", nargs='*', help="Hostnames to update")
args = parser.parse_args()
print
print description
print
# Enable logging if verbose
#logging.basicConfig(level=logging.DEBUG)
#logging.getLogger('zeroconf').setLevel(logging.DEBUG)
print()
print(description)
print()
# Look for sevices
zeroconf = Zeroconf()
browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change])
sleep(1)
sleep(5)
zeroconf.close()
if len(devices) == 0:
print("Nothing found!\n")
sys.exit(0)
# Sort list
field = args.sort.lower()
if field not in devices[0]:
print "Unknown field '%s'\n" % field
print("Unknown field '%s'\n" % field)
sys.exit(1)
devices = sorted(devices, key=lambda device: device.get(field, ''))
@ -191,20 +245,49 @@ if __name__ == '__main__':
# Flash device
if args.flash > 0:
device = flash()
if device:
env = "esp8266-%sm-ota" % device['size']
# Board(s) to flash
queue = []
# Check if hostnames
for hostname in args.hostnames:
board = get_board_by_hostname(hostname)
if board:
board['auth'] = args.password
board['flags'] = args.flags
queue.append(board)
# If no boards ask the user
if len(queue) == 0:
board = input_board()
if board:
board['auth'] = args.password or input("Authorization key of the device to flash: ")
board['flags'] = args.flags or input("Extra flags for the build: ")
queue.append(board)
# If still no boards quit
if len(queue) == 0:
sys.exit(0)
# Flash eash board
for board in queue:
# Flash core version?
if args.core > 0:
board['flags'] = "-DESPURNA_CORE " + board['flags']
env = "esp8266-%sm-ota" % board['size']
# Summary
print
print "ESPURNA_IP = %s" % device['ip']
print "ESPURNA_BOARD = %s" % device['board']
print "ESPURNA_AUTH = %s" % device['auth']
print "ESPURNA_FLAGS = %s" % device['flags']
print "ESPURNA_ENV = %s" % env
response = raw_input("\nAre these values right [y/N]: ")
print
print()
print("HOST = %s" % board.get('hostname', board['ip']))
print("IP = %s" % board['ip'])
print("BOARD = %s" % board['board'])
print("AUTH = %s" % board['auth'])
print("FLAGS = %s" % board['flags'])
print("ENV = %s" % env)
response = input("\nAre these values right [y/N]: ")
print()
if response == "y":
run(device, env)
run(board, env)

+ 26
- 2
code/platformio.ini View File

@ -24,9 +24,10 @@ lib_deps =
https://github.com/krosk93/espsoftwareserial#a770677
SparkFun BME280
PMS Library
https://bitbucket.org/xoseperez/justwifi.git#1.1.4
https://github.com/madpilot/mDNSResolver#4cfcda1
https://bitbucket.org/xoseperez/justwifi.git#1.1.6
https://bitbucket.org/xoseperez/hlw8012.git#1.1.0
https://bitbucket.org/xoseperez/fauxmoesp.git#2.3.0
https://bitbucket.org/xoseperez/fauxmoesp.git#2.4.1
https://bitbucket.org/xoseperez/nofuss.git#0.2.5
https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
https://github.com/xoseperez/my92xx#3.0.0
@ -37,6 +38,19 @@ extra_scripts = extra_scripts.py
# ------------------------------------------------------------------------------
[env:espurna-core]
platform = ${common.platform}
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DITEAD_SONOFF_BASIC -DESPURNA_CORE
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
# ------------------------------------------------------------------------------
[env:wemos-d1mini-relayshield]
platform = ${common.platform}
framework = arduino
@ -154,6 +168,16 @@ upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:tinkerman-espurna-switch]
platform = ${common.platform}
framework = arduino
board = esp12e
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DTINKERMAN_ESPURNA_SWITCH
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
# ------------------------------------------------------------------------------
[env:itead-sonoff-basic]


+ 0
- 1
code/requirements.txt View File

@ -1,5 +1,4 @@
enum-compat==0.0.2
enum34==1.1.6
netifaces==0.10.6
ordereddict==1.1
six==1.11.0


BIN
images/icons/collaborate.png View File

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

BIN
images/icons/documentation.png View File

Before After
Width: 256  |  Height: 256  |  Size: 8.0 KiB

BIN
images/icons/features.png View File

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

BIN
images/icons/hardware.png View File

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

Loading…
Cancel
Save