Browse Source

Merged xoseperez/espurna into master

i18n
lobradov 6 years ago
parent
commit
aa73aa692a
34 changed files with 4003 additions and 3423 deletions
  1. +19
    -0
      CHANGELOG.md
  2. +21
    -6
      README.md
  3. +2
    -2
      code/build.sh
  4. +4
    -1
      code/espurna/config/arduino.h
  5. +38
    -15
      code/espurna/config/general.h
  6. +7
    -7
      code/espurna/config/hardware.h
  7. +7
    -0
      code/espurna/config/prototypes.h
  8. +1
    -1
      code/espurna/config/version.h
  9. BIN
      code/espurna/data/index.html.gz
  10. +4
    -22
      code/espurna/domoticz.ino
  11. +15
    -3
      code/espurna/espurna.ino
  12. +23
    -9
      code/espurna/homeassitant.ino
  13. +6
    -2
      code/espurna/influxdb.ino
  14. +63
    -13
      code/espurna/mdns.ino
  15. +10
    -8
      code/espurna/mqtt.ino
  16. +4
    -0
      code/espurna/nofuss.ino
  17. +2
    -3
      code/espurna/ntp.ino
  18. +167
    -0
      code/espurna/scheduler.ino
  19. +15
    -10
      code/espurna/settings.ino
  20. +3118
    -3090
      code/espurna/static/index.html.gz.h
  21. +1
    -1
      code/espurna/utils.ino
  22. +122
    -68
      code/espurna/wifi.ino
  23. +10
    -0
      code/espurna/ws.ino
  24. +20
    -5
      code/html/custom.css
  25. +75
    -0
      code/html/custom.js
  26. +75
    -11
      code/html/index.html
  27. +98
    -92
      code/memanalyzer.py
  28. +63
    -51
      code/ota.py
  29. +13
    -2
      code/platformio.ini
  30. +0
    -1
      code/requirements.txt
  31. BIN
      images/icons/collaborate.png
  32. BIN
      images/icons/documentation.png
  33. BIN
      images/icons/features.png
  34. BIN
      images/icons/hardware.png

+ 19
- 0
CHANGELOG.md View File

@ -3,6 +3,25 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/) The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
## [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 ## [1.11.4] 2018-01-09
### Fixed ### Fixed
- Fix bug in RF Bridge when RF code contains the stop byte. Check overflow (#357) - Fix bug in RF Bridge when RF code contains the stop byte. Check overflow (#357)


+ 21
- 6
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 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. It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
> **Current Release Version is 1.11.4**, 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 ## 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)) * Support for **multiple ESP8266-based boards** ([check list](https://bitbucket.org/xoseperez/espurna/wiki/Hardware))
* Power saving options * Power saving options
* Wifi **AP Mode** or **STA mode** * Wifi **AP Mode** or **STA mode**
* Up to 5 different networks can be defined * Up to 5 different networks can be defined
* Supports static IP * Supports static IP
* Scans for strongest network if more than one defined * 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) * Defaults to AP mode (also available after double clicking the main button)
* Network visibility * 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 * Switch management
* Support for **push buttons** and **toggle switches** * Support for **push buttons** and **toggle switches**
* Configurable **status on boot** per switch (always ON, always OFF, same as before or toggle) * Configurable **status on boot** per switch (always ON, always OFF, same as before or toggle)
@ -38,6 +39,7 @@ It uses the Arduino Core for ESP8266 framework and a number of 3rd party librari
* Change LED notification mode * Change LED notification mode
* Remote reset the board * Remote reset the board
* Fully configurable in webUI (broker, user, password, QoS, keep alive time, retain flag, client ID) * 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) * **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) * [**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 * [**Domoticz**](https://domoticz.com/) integration via MQTT
@ -110,6 +112,19 @@ 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) * 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) * 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 ## Contribute
There are several ways to contribute to ESpurna development. You can contribute to the repository by doing: There are several ways to contribute to ESpurna development. You can contribute to the repository by doing:


+ 2
- 2
code/build.sh View File

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

+ 4
- 1
code/espurna/config/arduino.h View File

@ -74,12 +74,14 @@
//#define INFLUXDB_SUPPORT 1 //#define INFLUXDB_SUPPORT 1
//#define IR_SUPPORT 1 //#define IR_SUPPORT 1
//#define LLMNR_SUPPORT 1 // Only with Arduino Core 2.4.0 //#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 MQTT_SUPPORT 0
//#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0 //#define NETBIOS_SUPPORT 1 // Only with Arduino Core 2.4.0
//#define NOFUSS_SUPPORT 1 //#define NOFUSS_SUPPORT 1
//#define NTP_SUPPORT 0 //#define NTP_SUPPORT 0
//#define RF_SUPPORT 1 //#define RF_SUPPORT 1
//#define SCHEDULER_SUPPORT 0
//#define SPIFFS_SUPPORT 1 //#define SPIFFS_SUPPORT 1
//#define SSDP_SUPPORT 1 //#define SSDP_SUPPORT 1
//#define TELNET_SUPPORT 0 //#define TELNET_SUPPORT 0
@ -92,6 +94,7 @@
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
//#define ANALOG_SUPPORT 1 //#define ANALOG_SUPPORT 1
//#define BH1750_SUPPORT 1
//#define BMX280_SUPPORT 1 //#define BMX280_SUPPORT 1
//#define DALLAS_SUPPORT 1 //#define DALLAS_SUPPORT 1
//#define DHT_SUPPORT 1 //#define DHT_SUPPORT 1


+ 38
- 15
code/espurna/config/general.h View File

@ -20,14 +20,15 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#ifdef ESPURNA_CORE #ifdef ESPURNA_CORE
#define ALEXA_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
#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 #endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -318,6 +319,9 @@ PROGMEM const char* const custom_reset_string[] = {
//#define WIFI2_SSID "..." //#define WIFI2_SSID "..."
//#define WIFI2_PASS "..." //#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 // WEB
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -372,8 +376,12 @@ PROGMEM const char* const custom_reset_string[] = {
// MDNS / LLMNR / NETBIOS / SSDP // 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 #endif
#ifndef LLMNR_SUPPORT #ifndef LLMNR_SUPPORT
@ -393,7 +401,7 @@ PROGMEM const char* const custom_reset_string[] = {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef SPIFFS_SUPPORT #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 #endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -424,7 +432,7 @@ PROGMEM const char* const custom_reset_string[] = {
#ifndef MQTT_USE_ASYNC #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 #endif
// MQTT OVER SSL // MQTT OVER SSL
@ -448,7 +456,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_SSL_FINGERPRINT "" // SSL fingerprint of the server #define MQTT_SSL_FINGERPRINT "" // SSL fingerprint of the server
#define MQTT_ENABLED 0 // Do not enable MQTT connection by default #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 // autoconnect to the first MQTT broker found if none defined
#define MQTT_SERVER "" // Default MQTT broker address #define MQTT_SERVER "" // Default MQTT broker address
#define MQTT_USER "" // Default MQTT broker usename #define MQTT_USER "" // Default MQTT broker usename
@ -604,7 +612,6 @@ PROGMEM const char* const custom_reset_string[] = {
#define DOMOTICZ_ENABLED 0 // Disable domoticz by default #define DOMOTICZ_ENABLED 0 // Disable domoticz by default
#define DOMOTICZ_IN_TOPIC "domoticz/in" // Default subscription topic #define DOMOTICZ_IN_TOPIC "domoticz/in" // Default subscription topic
#define DOMOTICZ_OUT_TOPIC "domoticz/out" // Default publication 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 // HOME ASSISTANT
@ -672,6 +679,22 @@ PROGMEM const char* const custom_reset_string[] = {
#endif #endif
#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 // NTP
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -686,7 +709,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes #define NTP_UPDATE_INTERVAL 1800 // NTP check every 30 minutes
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// FAUXMO
// ALEXA
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// This setting defines whether Alexa support should be built into the firmware // This setting defines whether Alexa support should be built into the firmware


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

@ -177,7 +177,7 @@
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1 #define BUTTON1_RELAY 1
#define BUTTON2_PIN 14 #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 #define BUTTON2_RELAY 1
// Relays // Relays
@ -199,7 +199,7 @@
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH #define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1 #define BUTTON1_RELAY 1
#define BUTTON2_PIN 14 #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 #define BUTTON2_RELAY 1
// Relays // Relays
@ -375,14 +375,14 @@
#define DEVICE "SONOFF_DUAL_R2" #define DEVICE "SONOFF_DUAL_R2"
// Buttons // 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 BUTTON1_RELAY 1
#define BUTTON2_RELAY 2 #define BUTTON2_RELAY 2
#define BUTTON3_RELAY 1 #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 #define BUTTON3_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Relays // Relays


+ 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; typedef std::function<void(void)> ws_on_after_parse_callback_f;
void wsOnAfterParseRegister(ws_on_after_parse_callback_f callback); 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 // MQTT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


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

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

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


+ 4
- 22
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> #include <ArduinoJson.h>
bool _dcz_enabled = false; bool _dcz_enabled = false;
unsigned long _dcz_skip_time = 0;
unsigned long _dcz_last_idx = 0;
unsigned long _dcz_last_time = 0;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Private methods // Private methods
@ -28,13 +25,6 @@ int _domoticzRelay(unsigned int idx) {
return -1; 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) { void _domoticzMqttSubscribe(bool value) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC); 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"]; unsigned long idx = root["idx"];
int relayID = _domoticzRelay(idx); int relayID = _domoticzRelay(idx);
if (relayID >= 0) { if (relayID >= 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
unsigned long value = root["nvalue"]; unsigned long value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx); DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx);
relayStatus(relayID, value == 1); relayStatus(relayID, value == 1);
} }
} }
@ -89,11 +74,12 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
}; };
#if WEB_SUPPORT
void _domoticzWebSocketOnSend(JsonObject& root) { void _domoticzWebSocketOnSend(JsonObject& root) {
root["dczVisible"] = 1; root["dczVisible"] = 1;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1; root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczSkip"] = getSetting("dczSkip", DOMOTICZ_SKIP_TIME);
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC); root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC); root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -115,9 +101,10 @@ void _domoticzWebSocketOnSend(JsonObject& root) {
} }
#endif // WEB_SUPPORT
void _domoticzConfigure() { void _domoticzConfigure() {
_dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1; _dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
_dcz_skip_time = 1000 * getSetting("dczSkip", DOMOTICZ_SKIP_TIME).toInt();
_domoticzMqttSubscribe(_dcz_enabled); _domoticzMqttSubscribe(_dcz_enabled);
} }
@ -129,14 +116,9 @@ template<typename T> void domoticzSend(const char * key, T nvalue, const char *
if (!_dcz_enabled) return; if (!_dcz_enabled) return;
unsigned int idx = getSetting(key).toInt(); unsigned int idx = getSetting(key).toInt();
if (idx > 0) { if (idx > 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
char payload[128]; char payload[128];
snprintf(payload, sizeof(payload), "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue); 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); mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload);
} }
} }


+ 15
- 3
code/espurna/espurna.ino View File

@ -158,7 +158,7 @@ void welcome() {
#if LLMNR_SUPPORT #if LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR")); DEBUG_MSG_P(PSTR(" LLMNR"));
#endif #endif
#if MDNS_SUPPORT
#if MDNS_SERVER_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS")); DEBUG_MSG_P(PSTR(" MDNS"));
#endif #endif
#if NETBIOS_SUPPORT #if NETBIOS_SUPPORT
@ -173,6 +173,9 @@ void welcome() {
#if RF_SUPPORT #if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF")); DEBUG_MSG_P(PSTR(" RF"));
#endif #endif
#if SCHEDULER_SUPPORT
DEBUG_MSG_P(PSTR(" SCHEDULER"));
#endif
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR")); DEBUG_MSG_P(PSTR(" SENSOR"));
#endif #endif
@ -325,8 +328,8 @@ void setup() {
mqttSetup(); mqttSetup();
#endif #endif
#if MDNS_SUPPORT
mdnsSetup();
#if MDNS_SERVER_SUPPORT
mdnsServerSetup();
#endif #endif
#if LLMNR_SUPPORT #if LLMNR_SUPPORT
llmnrSetup(); llmnrSetup();
@ -378,6 +381,9 @@ void setup() {
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
sensorSetup(); sensorSetup();
#endif #endif
#if SCHEDULER_SUPPORT
schSetup();
#endif
// Prepare configuration for version 2.0 // Prepare configuration for version 2.0
migrate(); migrate();
@ -436,6 +442,12 @@ void loop() {
#if THINGSPEAK_SUPPORT #if THINGSPEAK_SUPPORT
tspkLoop(); tspkLoop();
#endif #endif
#if SCHEDULER_SUPPORT
schLoop();
#endif
#if MDNS_CLIENT_SUPPORT
mdnsClientLoop();
#endif
// Power saving delay // Power saving delay
delay(_loopDelay); 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> #include <ArduinoJson.h>
bool _haEnabled = false; bool _haEnabled = false;
bool _haSendFlag = false;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -19,21 +20,19 @@ void _haWebSocketOnSend(JsonObject& root) {
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX); 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")); DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
String output; String output;
if (add) {
if (_haEnabled) {
DynamicJsonBuffer jsonBuffer; DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject(); JsonObject& root = jsonBuffer.createObject();
@ -84,18 +83,33 @@ void haSend(bool add) {
mqttSendRaw(topic.c_str(), output.c_str()); mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); 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() { void haSetup() {
_haConfigure(); _haConfigure();
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_haWebSocketOnSend); wsOnSendRegister(_haWebSocketOnSend);
wsOnAfterParseRegister(_haConfigure); wsOnAfterParseRegister(_haConfigure);
#endif #endif
// On MQTT connect check if we have something to send
mqttRegister([](unsigned int type, const char * topic, const char * payload) { 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 #endif // HOMEASSISTANT_SUPPORT

+ 6
- 2
code/espurna/influxdb.ino View File

@ -41,8 +41,12 @@ template<typename T> bool idbSend(const char * topic, T payload) {
if (!_idb_enabled) return true; if (!_idb_enabled) return true;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) 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(); int port = getSetting("idbPort", INFLUXDB_PORT).toInt();
DEBUG_MSG("[INFLUXDB] Sending to %s:%d\n", host, port); DEBUG_MSG("[INFLUXDB] Sending to %s:%d\n", host, port);


+ 63
- 13
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 #if MQTT_SUPPORT
void mdnsFindMQTT() {
void _mdnsFindMQTT() {
int count = MDNS.queryService("mqtt", "tcp"); int count = MDNS.queryService("mqtt", "tcp");
DEBUG_MSG_P(PSTR("[MQTT] MQTT brokers found: %d\n"), count); DEBUG_MSG_P(PSTR("[MQTT] MQTT brokers found: %d\n"), count);
for (int i=0; i<count; i++) { for (int i=0; i<count; i++) {
@ -22,9 +24,10 @@ void mdnsFindMQTT() {
mqttSetBrokerIfNone(MDNS.IP(i), MDNS.port(i)); mqttSetBrokerIfNone(MDNS.IP(i), MDNS.port(i));
} }
} }
#endif #endif
void _mdnsStart() {
void _mdnsServerStart() {
if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) { if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) {
DEBUG_MSG_P(PSTR("[MDNS] OK\n")); DEBUG_MSG_P(PSTR("[MDNS] OK\n"));
} else { } else {
@ -32,7 +35,9 @@ void _mdnsStart() {
} }
} }
void mdnsSetup() {
// -----------------------------------------------------------------------------
void mdnsServerSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt()); MDNS.addService("http", "tcp", getSetting("webPort", WEB_PORT).toInt());
@ -62,13 +67,58 @@ void mdnsSetup() {
MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer); MDNS.addServiceTxt("arduino", "tcp", "free_space", (const char *) buffer);
} }
_mdns_wifi_onSTA = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
_mdnsStart();
});
_mdns_wifi_onAP = WiFi.onSoftAPModeStationConnected([](WiFiEventSoftAPModeStationConnected ipInfo) {
_mdnsStart();
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();
}
}); });
} }
#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

+ 10
- 8
code/espurna/mqtt.ino View File

@ -233,10 +233,6 @@ void _mqttWebSocketOnSend(JsonObject& root) {
root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1; root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1;
} }
void _mqttConfigure() {
if (getSetting("mqttClientID").length() == 0) delSetting("mqttClientID");
}
#endif #endif
void _mqttCallback(unsigned int type, const char * topic, const char * payload) { void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
@ -347,8 +343,12 @@ void mqttConnect() {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX; _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(); unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
if (_mqtt_user) free(_mqtt_user); if (_mqtt_user) free(_mqtt_user);
@ -483,6 +483,7 @@ void mqttConfigure() {
_mqtt_qos = getSetting("mqttQoS", MQTT_QOS).toInt(); _mqtt_qos = getSetting("mqttQoS", MQTT_QOS).toInt();
_mqtt_retain = getSetting("mqttRetain", MQTT_RETAIN).toInt() == 1; _mqtt_retain = getSetting("mqttRetain", MQTT_RETAIN).toInt() == 1;
_mqtt_keepalive = getSetting("mqttKeep", MQTT_KEEPALIVE).toInt(); _mqtt_keepalive = getSetting("mqttKeep", MQTT_KEEPALIVE).toInt();
if (getSetting("mqttClientID").length() == 0) delSetting("mqttClientID");
// Enable // Enable
if (getSetting("mqttServer", MQTT_SERVER).length() == 0) { if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
@ -508,8 +509,9 @@ void mqttSetBrokerIfNone(IPAddress ip, unsigned int port) {
void mqttSetup() { void mqttSetup() {
DEBUG_MSG_P(PSTR("[MQTT] Async %s, Autoconnect %s\n"),
DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"),
MQTT_USE_ASYNC ? "ENABLED" : "DISABLED", MQTT_USE_ASYNC ? "ENABLED" : "DISABLED",
ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED",
MQTT_AUTOCONNECT ? "ENABLED" : "DISABLED" MQTT_AUTOCONNECT ? "ENABLED" : "DISABLED"
); );
@ -564,7 +566,7 @@ void mqttSetup() {
#if WEB_SUPPORT #if WEB_SUPPORT
wsOnSendRegister(_mqttWebSocketOnSend); wsOnSendRegister(_mqttWebSocketOnSend);
wsOnAfterParseRegister(_mqttConfigure);
wsOnAfterParseRegister(mqttConfigure);
#endif #endif
} }


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

@ -27,6 +27,10 @@ void _nofussWebSocketOnSend(JsonObject& root) {
void _nofussConfigure() { void _nofussConfigure() {
String nofussServer = getSetting("nofussServer", NOFUSS_SERVER); String nofussServer = getSetting("nofussServer", NOFUSS_SERVER);
#if MDNS_CLIENT_SUPPORT
nofussServer = mdnsResolve(nofussServer);
#endif
if (nofussServer.length() == 0) { if (nofussServer.length() == 0) {
setSetting("nofussEnabled", 0); setSetting("nofussEnabled", 0);
_nofussEnabled = false; _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 <WiFiClient.h>
#include <Ticker.h> #include <Ticker.h>
WiFiEventHandler _ntp_wifi_onSTA;
Ticker _ntp_delay; 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 #if WEB_SUPPORT


+ 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

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

@ -308,6 +308,14 @@ void settingsSetup() {
}); });
#endif #endif
#if MQTT_SUPPORT
Embedis::command( F("MQTT.RESET"), [](Embedis* e) {
mqttConfigure();
mqttDisconnect();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
#if NOFUSS_SUPPORT #if NOFUSS_SUPPORT
Embedis::command( F("NOFUSS"), [](Embedis* e) { Embedis::command( F("NOFUSS"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
@ -337,22 +345,19 @@ void settingsSetup() {
deferredReset(100, CUSTOM_RESET_TERMINAL); 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(); wifiConfigure();
wifiDisconnect(); wifiDisconnect();
DEBUG_MSG_P(PSTR("+OK\n")); 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")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });


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


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

@ -11,7 +11,7 @@ Ticker _defer_reset;
String getIdentifier() { String getIdentifier() {
char buffer[20]; 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); return String(buffer);
} }


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

@ -41,6 +41,11 @@ String getNetwork() {
return WiFi.SSID(); 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() { void wifiDisconnect() {
jw.disconnect(); 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) { 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) { 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")); 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 wifiClean(unsigned char num) {
bool changed = false; 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 #if WEB_SUPPORT
wsOnSendRegister(_wifiWebSocketOnSend); wsOnSendRegister(_wifiWebSocketOnSend);


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

@ -220,6 +220,12 @@ void _wsOnStart(JsonObject& root) {
char chipid[7]; char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId()); 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; root["webMode"] = WEB_MODE_NORMAL;
@ -229,6 +235,10 @@ void _wsOnStart(JsonObject& root) {
root["manufacturer"] = MANUFACTURER; root["manufacturer"] = MANUFACTURER;
root["chipid"] = String(chipid); root["chipid"] = String(chipid);
root["mac"] = WiFi.macAddress(); 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["device"] = DEVICE;
root["hostname"] = getSetting("hostname"); root["hostname"] = getSetting("hostname");
root["network"] = getNetwork(); root["network"] = getNetwork();


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

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

+ 75
- 0
code/html/custom.js View File

@ -1,6 +1,7 @@
var websock; var websock;
var password = false; var password = false;
var maxNetworks; var maxNetworks;
var maxSchedules;
var messages = []; var messages = [];
var webhost; var webhost;
@ -100,6 +101,7 @@ function validateForm(form) {
// These fields will always be a list of values // These fields will always be a list of values
var is_group = [ var is_group = [
"ssid", "pass", "gw", "mask", "ip", "dns", "ssid", "pass", "gw", "mask", "ip", "dns",
"schSwitch","schAction","schHour","schMinute","schWDs",
"relayBoot", "relayPulse", "relayTime", "relayBoot", "relayPulse", "relayTime",
"mqttGroup", "mqttGroupInv", "mqttGroup", "mqttGroupInv",
"dczRelayIdx", "dczMagnitude", "dczRelayIdx", "dczMagnitude",
@ -144,6 +146,13 @@ function getData(form) {
} }
}); });
// Post process
if ("schSwitch" in data) {
data["schSwitch"].push(0xFF);
} else {
data["schSwitch"] = [0xFF];
}
return data; return data;
} }
@ -465,6 +474,39 @@ function moreNetwork() {
$(".more", parent).toggle(); $(".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 // Relays
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -820,6 +862,28 @@ function processData(data) {
return; 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 // Relays
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -827,9 +891,17 @@ function processData(data) {
if (key == "relayStatus") { if (key == "relayStatus") {
initRelays(data[key]); initRelays(data[key]);
for (var i in data[key]) { for (var i in data[key]) {
// Set the status for each relay
$("input.relayStatus[data='" + i + "']") $("input.relayStatus[data='" + i + "']")
.prop("checked", data[key][i]) .prop("checked", data[key][i])
.iphoneStyle("refresh"); .iphoneStyle("refresh");
// Populate the relay SELECTs
$("select.isrelay").append(
$("<option></option>").attr("value",i).text("Switch #" + i)
);
} }
return; return;
} }
@ -1055,6 +1127,9 @@ $(function() {
$(".button-add-network").on('click', function() { $(".button-add-network").on('click', function() {
$(".more", addNetwork()).toggle(); $(".more", addNetwork()).toggle();
}); });
$(".button-add-schedule").on('click', function() {
$("div.more", addSchedule()).toggle();
});
$(document).on('change', 'input', hasChanged); $(document).on('change', 'input', hasChanged);
$(document).on('change', 'select', hasChanged); $(document).on('change', 'select', hasChanged);


+ 75
- 11
code/html/index.html View File

@ -100,6 +100,10 @@
<a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a> <a href="#" class="pure-menu-link" data="panel-relay">SWITCHES</a>
</li> </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"> <li class="pure-menu-item module module-color">
<a href="#" class="pure-menu-link" data="panel-color">LIGHTS</a> <a href="#" class="pure-menu-link" data="panel-color">LIGHTS</a>
</li> </li>
@ -186,6 +190,15 @@
<div class="pure-u-1-2 pure-u-lg-1-4">Network</div> <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-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-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> <div class="pure-u-11-24 pure-u-lg-17-24"><span class="right" name="deviceip"></span></div>
@ -530,8 +543,7 @@
<fieldset> <fieldset>
<div id="networks">
</div>
<div id="networks"></div>
<button type="button" class="pure-button button-add-network">Add network</button> <button type="button" class="pure-button button-add-network">Add network</button>
@ -539,6 +551,27 @@
</div> </div>
</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="panel" id="panel-mqtt">
<div class="header"> <div class="header">
@ -718,14 +751,6 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="dczEnabled" tabindex="30" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="dczEnabled" tabindex="30" /></div>
</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"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Domoticz IN Topic</label> <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" /> <input class="pure-u-1 pure-u-lg-3-4" name="dczTopicIn" type="text" tabindex="31" />
@ -1023,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-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> <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> </div>


+ 98
- 92
code/memanalyzer.py View File

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
#-------------------------------------------------------------------------------
# coding=utf-8
# -------------------------------------------------------------------------------
# ESPurna module memory analyser # ESPurna module memory analyser
# xose.perez@gmail.com # xose.perez@gmail.com
# #
@ -11,23 +12,28 @@
# https://github.com/Sermus/ESP8266_memory_analyzer # https://github.com/Sermus/ESP8266_memory_analyzer
# by Andrey Filimonov # by Andrey Filimonov
# #
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
from __future__ import print_function
import argparse
import os
import re
import shlex
import sys
from collections import OrderedDict from collections import OrderedDict
from sortedcontainers import SortedDict from sortedcontainers import SortedDict
import shlex
import commands
import subprocess 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" objdump_binary = "xtensa-lx106-elf-objdump"
sections = OrderedDict([ sections = OrderedDict([
("data", "Initialized Data (RAM)"), ("data", "Initialized Data (RAM)"),
@ -38,7 +44,8 @@ sections = OrderedDict([
]) ])
description = "ESPurna Memory Analyzer v0.1" description = "ESPurna Memory Analyzer v0.1"
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
def file_size(file): def file_size(file):
try: try:
@ -46,8 +53,8 @@ def file_size(file):
except: except:
return 0 return 0
def analyse_memory(elf_file):
def analyse_memory(elf_file):
command = "%s -t '%s' " % (objdump_binary, elf_file) command = "%s -t '%s' " % (objdump_binary, elf_file)
response = subprocess.check_output(shlex.split(command)) response = subprocess.check_output(shlex.split(command))
if isinstance(response, bytes): 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("{0: >10}|{1: >30}|{2: >12}|{3: >12}|{4: >8}".format("Section", "Description", "Start (hex)", "End (hex)", "Used space"));
# print("------------------------------------------------------------------------------"); # 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: for line in lines:
if sectionStartToken in line:
if section_start_token in line:
data = line.split(' ') 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(' ') 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 break
sectionLength = sectionEnd - sectionStart
section_length = section_end - section_start
# if i < 3: # if i < 3:
# usedRAM += sectionLength
# usedRAM += section_length
# if i == 3: # 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 # i += 1
# print("Total Used RAM : %d" % usedRAM) # print("Total Used RAM : %d" % usedRAM)
# print("Free RAM : %d" % (TOTAL_DRAM - usedRAM)) # print("Free RAM : %d" % (TOTAL_DRAM - usedRAM))
# print("Free IRam : %d" % usedIRAM) # print("Free IRam : %d" % usedIRAM)
return(ret)
return ret
def run(env, modules):
def run(env_, modules_):
flags = "" flags = ""
for item in modules.items():
for item in modules_.items():
flags += "-D%s_SUPPORT=%d " % item 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) subprocess.check_call(command, shell=True)
def modules_get(): def modules_get():
modules = SortedDict()
modules_ = SortedDict()
for line in open("espurna/config/arduino.h"): for line in open("espurna/config/arduino.h"):
m = re.search(r'(\w*)_SUPPORT', line) m = re.search(r'(\w*)_SUPPORT', line)
if m: 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: try:
@ -120,23 +126,23 @@ try:
args = parser.parse_args() args = parser.parse_args()
# Hello # Hello
print
print description
print
print()
print(description)
print()
# Check xtensa-lx106-elf-objdump is in the path # 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) sys.exit(1)
# Load list of all modules # Load list of all modules
available_modules = modules_get() available_modules = modules_get()
if args.list > 0: if args.list > 0:
print "List of available modules:\n"
print("List of available modules:\n")
for key, value in available_modules.items(): for key, value in available_modules.items():
print "* " + key
print
print("* " + key)
print()
sys.exit(0) sys.exit(0)
# Which modules to test? # Which modules to test?
@ -150,7 +156,7 @@ try:
# Check test modules exist # Check test modules exist
for module in test_modules: for module in test_modules:
if module not in available_modules: if module not in available_modules:
print "Module %s not found" % module
print("Module %s not found" % module)
sys.exit(2) sys.exit(2)
# Define base configuration # Define base configuration
@ -163,19 +169,19 @@ try:
# Show init message # Show init message
if len(test_modules) > 0: 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: 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( 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 # Build the core without modules to get base memory usage
@ -183,13 +189,13 @@ try:
base = analyse_memory(".pioenvs/%s/firmware.elf" % env) base = analyse_memory(".pioenvs/%s/firmware.elf" % env)
base['size'] = file_size(".pioenvs/%s/firmware.bin" % env) base['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
print(output_format.format( 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 # Test each module
@ -198,18 +204,18 @@ try:
modules[module] = 1 modules[module] = 1
run(env, modules) 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) results[module]['size'] = file_size(".pioenvs/%s/firmware.bin" % env)
modules[module] = 0 modules[module] = 0
print(output_format.format( 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 # Test all modules
@ -223,23 +229,23 @@ try:
if len(test_modules) > 1: if len(test_modules) > 1:
print(output_format.format( 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( 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: except:


+ 63
- 51
code/ota.py View File

@ -1,31 +1,40 @@
#!/usr/bin/env python #!/usr/bin/env python
#-------------------------------------------------------------------------------
# coding=utf-8
# -------------------------------------------------------------------------------
# ESPurna OTA manager # ESPurna OTA manager
# xose.perez@gmail.com # xose.perez@gmail.com
# #
# Requires PlatformIO Core # Requires PlatformIO Core
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
from __future__ import print_function
import sys
import argparse
import re import re
import logging
import socket import socket
import argparse
import subprocess import subprocess
import sys
from time import sleep from time import sleep
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
#-------------------------------------------------------------------------------
try:
# noinspection PyUnresolvedReferences
input = raw_input # Python2
except NameError:
pass # Python3
# -------------------------------------------------------------------------------
devices = [] devices = []
description = "ESPurna OTA Manager v0.1" description = "ESPurna OTA Manager v0.1"
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
def on_service_state_change(zeroconf, service_type, name, state_change): def on_service_state_change(zeroconf, service_type, name, state_change):
'''
"""
Callback that adds discovered devices to "devices" list Callback that adds discovered devices to "devices" list
'''
"""
if state_change is ServiceStateChange.Added: if state_change is ServiceStateChange.Added:
info = zeroconf.get_service_info(service_type, name) info = zeroconf.get_service_info(service_type, name)
@ -46,10 +55,11 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
device['free_space'] = info.properties.get('free_space') device['free_space'] = info.properties.get('free_space')
devices.append(device) devices.append(device)
def list(): def list():
'''
"""
Shows the list of discovered devices Shows the list of discovered devices
'''
"""
output_format="{:>3} {:<25}{:<25}{:<15}{:<15}{:<30}{:<10}{:<10}{:<10}" output_format="{:>3} {:<25}{:<25}{:<15}{:<15}{:<30}{:<10}{:<10}{:<10}"
print(output_format.format( print(output_format.format(
"#", "#",
@ -62,7 +72,7 @@ def list():
"SDK_SIZE", "SDK_SIZE",
"FREE_SPACE" "FREE_SPACE"
)) ))
print "-" * 146
print("-" * 146)
index = 0 index = 0
for device in devices: for device in devices:
@ -79,12 +89,13 @@ def list():
device.get('free_space', ''), device.get('free_space', ''),
)) ))
print
print()
def get_boards(): def get_boards():
'''
"""
Grabs board types fro hardware.h file Grabs board types fro hardware.h file
'''
"""
boards = [] boards = []
for line in open("espurna/config/hardware.h"): for line in open("espurna/config/hardware.h"):
m = re.search(r'defined\((\w*)\)', line) m = re.search(r'defined\((\w*)\)', line)
@ -92,10 +103,11 @@ def get_boards():
boards.append(m.group(1)) boards.append(m.group(1))
return sorted(boards) return sorted(boards)
def flash(): def flash():
'''
"""
Grabs info from the user about what device to flash Grabs info from the user about what device to flash
'''
"""
# Choose the board # Choose the board
try: try:
@ -103,13 +115,13 @@ def flash():
except: except:
index = 0 index = 0
if index < 0 or len(devices) < index: 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 return None
board = {'board': '', 'ip': '', 'size': 0 , 'auth': '', 'flags': ''}
board = {'board': '', 'ip': '', 'size': 0, 'auth': '', 'flags': ''}
if index > 0: if index > 0:
device = devices[index-1]
device = devices[index - 1]
board['board'] = device.get('device', '') board['board'] = device.get('device', '')
board['ip'] = device.get('ip', '') 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['size'] = int(device.get('mem_size', 0) if device.get('mem_size', 0) == device.get('sdk_size', 0) else 0) / 1024
@ -117,79 +129,79 @@ def flash():
# Choose board type if none before # Choose board type if none before
if len(board['board']) == 0: if len(board['board']) == 0:
print
print()
count = 1 count = 1
boards = get_boards() boards = get_boards()
for name in boards: for name in boards:
print "%3d\t%s" % (count, name)
print("%3d\t%s" % (count, name))
count = count + 1 count = count + 1
print
print()
try: try:
index = int(input("Choose the board type you want to flash: ")) index = int(input("Choose the board type you want to flash: "))
except: except:
index = 0 index = 0
if index < 1 or len(boards) < index: 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 return None
board['board'] = boards[index-1]
board['board'] = boards[index - 1]
# Choose board size of none before # Choose board size of none before
if board['size'] == 0: if board['size'] == 0:
try: try:
board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): ")) board['size'] = int(input("Board memory size (1 for 1M, 4 for 4M): "))
except: except:
print "Wrong memory size"
print("Wrong memory size")
return None return None
# Choose IP of none before # Choose IP of none before
if len(board['ip']) == 0: if len(board['ip']) == 0:
try: 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: except:
print "Wrong IP"
print("Wrong IP")
return None return None
board['auth'] = raw_input("Authorization key of the device to flash: ")
board['flags'] = raw_input("Extra flags for the build: ")
board['auth'] = input("Authorization key of the device to flash: ")
board['flags'] = input("Extra flags for the build: ")
return board return board
def run(device, env): def run(device, env):
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 = "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) command = command % (device['ip'], device['board'], device['auth'], device['flags'], env)
subprocess.check_call(command, shell=True) subprocess.check_call(command, shell=True)
#-------------------------------------------------------------------------------
# -------------------------------------------------------------------------------
if __name__ == '__main__': if __name__ == '__main__':
# Parse command line options # Parse command line options
parser = argparse.ArgumentParser(description=description) 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("-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("-f", "--flash", help="flash device", default=0, action='count')
parser.add_argument("-s", "--sort", help="sort devices list by field", default='hostname') parser.add_argument("-s", "--sort", help="sort devices list by field", default='hostname')
args = parser.parse_args() 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 # Look for sevices
zeroconf = Zeroconf() zeroconf = Zeroconf()
browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change]) browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change])
sleep(1)
sleep(5)
zeroconf.close() zeroconf.close()
if len(devices) == 0:
print("Nothing found!\n")
sys.exit(0)
# Sort list # Sort list
field = args.sort.lower() field = args.sort.lower()
if field not in devices[0]: if field not in devices[0]:
print "Unknown field '%s'\n" % field
print("Unknown field '%s'\n" % field)
sys.exit(1) sys.exit(1)
devices = sorted(devices, key=lambda device: device.get(field, '')) devices = sorted(devices, key=lambda device: device.get(field, ''))
@ -208,14 +220,14 @@ if __name__ == '__main__':
env = "esp8266-%sm-ota" % device['size'] env = "esp8266-%sm-ota" % device['size']
# Summary # 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("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 = input("\nAre these values right [y/N]: ")
print()
if response == "y": if response == "y":
run(device, env) run(device, env)

+ 13
- 2
code/platformio.ini View File

@ -24,9 +24,10 @@ lib_deps =
https://github.com/krosk93/espsoftwareserial#a770677 https://github.com/krosk93/espsoftwareserial#a770677
SparkFun BME280 SparkFun BME280
PMS Library 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/hlw8012.git#1.1.0
https://bitbucket.org/xoseperez/fauxmoesp.git#2.4.0
https://bitbucket.org/xoseperez/fauxmoesp.git#2.4.1
https://bitbucket.org/xoseperez/nofuss.git#0.2.5 https://bitbucket.org/xoseperez/nofuss.git#0.2.5
https://bitbucket.org/xoseperez/debounceevent.git#2.0.1 https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
https://github.com/xoseperez/my92xx#3.0.0 https://github.com/xoseperez/my92xx#3.0.0
@ -167,6 +168,16 @@ upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200 monitor_baud = 115200
extra_scripts = ${common.extra_scripts} 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] [env:itead-sonoff-basic]


+ 0
- 1
code/requirements.txt View File

@ -1,5 +1,4 @@
enum-compat==0.0.2 enum-compat==0.0.2
enum34==1.1.6
netifaces==0.10.6 netifaces==0.10.6
ordereddict==1.1 ordereddict==1.1
six==1.11.0 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