Browse Source

Merge branch 'dev' into v2

v2
Xose Pérez 6 years ago
parent
commit
8c80580fe1
109 changed files with 19821 additions and 19730 deletions
  1. +2
    -2
      .travis.yml
  2. +2
    -2
      README.md
  3. +13
    -3
      code/espurna/api.ino
  4. +5
    -3
      code/espurna/button.ino
  5. +0
    -4
      code/espurna/config/all.h
  6. +2
    -0
      code/espurna/config/arduino.h
  7. +7
    -1
      code/espurna/config/device.h
  8. +30
    -4
      code/espurna/config/general.h
  9. +20
    -0
      code/espurna/config/prototypes.h
  10. BIN
      code/espurna/data/index.all.html.gz
  11. BIN
      code/espurna/data/index.light.html.gz
  12. BIN
      code/espurna/data/index.rfbridge.html.gz
  13. BIN
      code/espurna/data/index.rfm69.html.gz
  14. BIN
      code/espurna/data/index.sensor.html.gz
  15. BIN
      code/espurna/data/index.small.html.gz
  16. +72
    -0
      code/espurna/device.ino
  17. +9
    -4
      code/espurna/homeassistant.ino
  18. +1
    -1
      code/espurna/mqtt.ino
  19. +53
    -13
      code/espurna/relay.ino
  20. +6
    -2
      code/espurna/rfm69.ino
  21. +7
    -3
      code/espurna/sensors/CSE7766Sensor.h
  22. +7
    -0
      code/espurna/settings.ino
  23. +3114
    -3105
      code/espurna/static/index.all.html.gz.h
  24. +2974
    -2966
      code/espurna/static/index.light.html.gz.h
  25. +2579
    -2565
      code/espurna/static/index.rfbridge.html.gz.h
  26. +4055
    -4035
      code/espurna/static/index.rfm69.html.gz.h
  27. +2630
    -2616
      code/espurna/static/index.sensor.html.gz.h
  28. +2535
    -2521
      code/espurna/static/index.small.html.gz.h
  29. +7
    -3
      code/espurna/system.ino
  30. +37
    -9
      code/espurna/thinkspeak.ino
  31. +12
    -21
      code/espurna/utils.ino
  32. +0
    -1
      code/gulpfile.js
  33. +60
    -14
      code/html/custom.css
  34. +108
    -31
      code/html/custom.js
  35. +102
    -52
      code/html/index.html
  36. BIN
      code/html/vendor/images/sort_asc_disabled.png
  37. BIN
      code/html/vendor/images/sort_desc_disabled.png
  38. +18
    -4
      code/ota.py
  39. +1270
    -1737
      code/package-lock.json
  40. +5
    -6
      code/package.json
  41. +79
    -2
      code/platformio.ini
  42. BIN
      images/devices/aithinker-ai-light.jpg
  43. BIN
      images/devices/arilux-al-lc01.jpg
  44. BIN
      images/devices/arilux-al-lc06.jpg
  45. BIN
      images/devices/arilux-e27.jpg
  46. BIN
      images/devices/authometion-lyt8266.jpg
  47. BIN
      images/devices/electrodragon-wifi-iot.jpg
  48. BIN
      images/devices/exs-wifi-relay-v31.jpg
  49. BIN
      images/devices/exs-wifi-relay-v50.jpg
  50. BIN
      images/devices/geiger_espurna_configuration.png
  51. BIN
      images/devices/geiger_espurna_status.png
  52. BIN
      images/devices/geiger_grafana_dashboard.png
  53. BIN
      images/devices/geiger_scope_following_pulses.png
  54. BIN
      images/devices/geiger_scope_single_pulse.png
  55. BIN
      images/devices/geiger_wiring_diagram.png
  56. BIN
      images/devices/generic-ag-l4-1.jpg
  57. BIN
      images/devices/generic-ag-l4-2.jpg
  58. BIN
      images/devices/generic-ag-l4-3.jpg
  59. BIN
      images/devices/generic-ag-l4-4.jpg
  60. BIN
      images/devices/generic-ag-l4-5.jpg
  61. BIN
      images/devices/generic-geiger-diy.png
  62. BIN
      images/devices/generic-relay-40.jpg
  63. BIN
      images/devices/generic-rgbled-10.jpg
  64. BIN
      images/devices/generic-v9261f.jpg
  65. BIN
      images/devices/heygo-hy02.jpg
  66. BIN
      images/devices/huacanxing-h801.jpg
  67. BIN
      images/devices/iWoole-led-desk-lamp-module-esp-m2.jpg
  68. BIN
      images/devices/iWoole-led-desk-lamp-module-front.jpg
  69. BIN
      images/devices/iWoole-led-desk-lamp-module-rear.jpg
  70. BIN
      images/devices/iWoole-led-desk-lamp-open1.jpg
  71. BIN
      images/devices/iWoole-led-desk-lamp-open2.jpg
  72. BIN
      images/devices/iWoole-led-desk-lamp.jpg
  73. BIN
      images/devices/intermittech-quinled-2.6.jpg
  74. BIN
      images/devices/itead-1ch-inching.jpg
  75. BIN
      images/devices/itead-bn-sz01.jpg
  76. BIN
      images/devices/itead-motor.jpg
  77. BIN
      images/devices/itead-s20.jpg
  78. BIN
      images/devices/itead-s26.jpg
  79. BIN
      images/devices/itead-slampher.jpg
  80. BIN
      images/devices/itead-sonoff-4ch-pro.jpg
  81. BIN
      images/devices/itead-sonoff-4ch.jpg
  82. BIN
      images/devices/itead-sonoff-b1.jpg
  83. BIN
      images/devices/itead-sonoff-basic.jpg
  84. BIN
      images/devices/itead-sonoff-dual.jpg
  85. BIN
      images/devices/itead-sonoff-ifan02.jpg
  86. BIN
      images/devices/itead-sonoff-led.jpg
  87. BIN
      images/devices/itead-sonoff-pow.jpg
  88. BIN
      images/devices/itead-sonoff-rf.jpg
  89. BIN
      images/devices/itead-sonoff-sv.jpg
  90. BIN
      images/devices/itead-sonoff-t1.jpg
  91. BIN
      images/devices/itead-sonoff-th.jpg
  92. BIN
      images/devices/jangoe-wifi-relay.jpg
  93. BIN
      images/devices/jorgegarcia-wifi-relays.jpg
  94. BIN
      images/devices/kmc-70011.jpg
  95. BIN
      images/devices/lingan-swa1.jpg
  96. BIN
      images/devices/lohas-9w.jpg
  97. BIN
      images/devices/magichome-led-controller.jpg
  98. BIN
      images/devices/mancavemade-esp-live.jpg
  99. BIN
      images/devices/neo-coolcam-wifi.jpg
  100. BIN
      images/devices/nodemcu-lolin-v3.jpg

+ 2
- 2
.travis.yml View File

@ -8,10 +8,10 @@ cache:
- "~/.npm" - "~/.npm"
- "~/.platformio" - "~/.platformio"
- "$TRAVIS_BUILD_DIR/code/.piolibdeps" - "$TRAVIS_BUILD_DIR/code/.piolibdeps"
- "$TRAVIS_BUILD_DIR/code/espurna/node_modules"
install: install:
- pip install -U platformio - pip install -U platformio
- cd code ; npm install --only=dev ; cd ..
- npm install -g npm@latest
- cd code ; npm ci ; cd ..
env: env:
global: global:
- BUILDER_TOTAL_THREADS=4 - BUILDER_TOTAL_THREADS=4


+ 2
- 2
README.md View File

@ -261,8 +261,8 @@ Here is the list of supported hardware. For more information please refer to the
|**Itead Sonoff SV**|**Itead 1CH Inching**|**Itead Motor Clockwise/Anticlockwise**| |**Itead Sonoff SV**|**Itead 1CH Inching**|**Itead Motor Clockwise/Anticlockwise**|
|![Jan Goedeke Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.jpg)|![Jorge García Wifi + Relays Board Kit](images/devices/jorgegarcia-wifi-relays.jpg)|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)| |![Jan Goedeke Wifi Relay (NO/NC)](images/devices/jangoe-wifi-relay.jpg)|![Jorge García Wifi + Relays Board Kit](images/devices/jorgegarcia-wifi-relays.jpg)|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|
|**Jan Goedeke Wifi Relay (NO/NC)**|**Jorge García Wifi + Relays Board Kit**|**EXS Wifi Relay v3.1**| |**Jan Goedeke Wifi Relay (NO/NC)**|**Jorge García Wifi + Relays Board Kit**|**EXS Wifi Relay v3.1**|
|![Allterco Shelly1](images/devices/allterco-shelly1.jpg)|||
|**Alterco Shelly1**|||
|![EXS Wifi Relay v5.0](images/devices/exs-wifi-relay-v50.jpg)|![Allterco Shelly1](images/devices/allterco-shelly1.jpg)||
|**EXS Wifi Relay v5.0**|**Alterco Shelly1**||
|![ManCaveMade ESP-Live](images/devices/mancavemade-esp-live.jpg)|![Wemos D1 Mini Relay Shield](images/devices/wemos-d1-mini-relayshield.jpg)|![Witty Cloud](images/devices/witty-cloud.jpg)| |![ManCaveMade ESP-Live](images/devices/mancavemade-esp-live.jpg)|![Wemos D1 Mini Relay Shield](images/devices/wemos-d1-mini-relayshield.jpg)|![Witty Cloud](images/devices/witty-cloud.jpg)|
|**ManCaveMade ESP-Live**|**Wemos D1 Mini Relay Shield**|**Witty Cloud**| |**ManCaveMade ESP-Live**|**Wemos D1 Mini Relay Shield**|**Witty Cloud**|
|![IKE ESPike](images/devices/ike-espike.jpg)|![Pilotak ESP DIN](images/devices/pilotak-esp-din.jpg)|![Arniex Swifitch](images/devices/arniex-swifitch.jpg)| |![IKE ESPike](images/devices/ike-espike.jpg)|![Pilotak ESP DIN](images/devices/pilotak-esp-din.jpg)|![Arniex Swifitch](images/devices/arniex-swifitch.jpg)|


+ 13
- 3
code/espurna/api.ino View File

@ -21,6 +21,7 @@ typedef struct {
api_put_callback_f putFn = NULL; api_put_callback_f putFn = NULL;
} web_api_t; } web_api_t;
std::vector<web_api_t> _apis; std::vector<web_api_t> _apis;
bool _api_restful = API_RESTFUL;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -33,8 +34,14 @@ void _apiWebSocketOnSend(JsonObject& root) {
root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1; root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
root["apiKey"] = getSetting("apiKey"); root["apiKey"] = getSetting("apiKey");
root["apiRealTime"] = apiRealTime(); root["apiRealTime"] = apiRealTime();
root["apiRestFul"] = _api_restful;
} }
void _apiConfigure() {
_api_restful = getSetting("apiRestFul", API_RESTFUL).toInt() == 1;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// API // API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -160,9 +167,11 @@ bool _apiRequestCallback(AsyncWebServerRequest *request) {
// Check if its a PUT // Check if its a PUT
if (api.putFn != NULL) { if (api.putFn != NULL) {
if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
(api.putFn)((p->value()).c_str());
if (!_api_restful || (request->method() == HTTP_PUT)) {
if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
(api.putFn)((p->value()).c_str());
}
} }
} }
@ -221,6 +230,7 @@ void apiSetup() {
settingsRegisterKeyCheck(_apiKeyCheck); settingsRegisterKeyCheck(_apiKeyCheck);
webRequestRegister(_apiRequestCallback); webRequestRegister(_apiRequestCallback);
wsOnSendRegister(_apiWebSocketOnSend); wsOnSendRegister(_apiWebSocketOnSend);
espurnaRegisterReload(_apiConfigure);
} }
#endif // API_SUPPORT #endif // API_SUPPORT

+ 5
- 3
code/espurna/button.ino View File

@ -100,12 +100,14 @@ void _buttonExecuteEvent(unsigned int id, unsigned char event) {
DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event); DEBUG_MSG_P(PSTR("[BUTTON] Button #%u event %u\n"), id, event);
if (event == 0) return; if (event == 0) return;
unsigned char action = _buttonGetAction(id, event);
#if MQTT_SUPPORT #if MQTT_SUPPORT
buttonMQTT(id, event);
if (action != BUTTON_MODE_NONE || BUTTON_MQTT_SEND_ALL_EVENTS) {
buttonMQTT(id, event);
}
#endif #endif
unsigned char action = _buttonGetAction(id, event);
if (action == BUTTON_MODE_TOGGLE) { if (action == BUTTON_MODE_TOGGLE) {
if (RELAY_NONE != _buttons[id].relayID) { if (RELAY_NONE != _buttons[id].relayID) {
relayToggle(_buttons[id].relayID); relayToggle(_buttons[id].relayID);


+ 0
- 4
code/espurna/config/all.h View File

@ -35,7 +35,3 @@
#include "sensors.h" #include "sensors.h"
#include "webui.h" #include "webui.h"
#include "progmem.h" #include "progmem.h"
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif

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

@ -118,6 +118,8 @@
//#define XIAOMI_SMART_DESK_LAMP //#define XIAOMI_SMART_DESK_LAMP
//#define ALLTERCO_SHELLY2 //#define ALLTERCO_SHELLY2
//#define PHYX_ESP12_RGB //#define PHYX_ESP12_RGB
//#define IWOOLE_LED_TABLE_LAMP
//#define EXS_WIFI_RELAY_V50
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Features (values below are non-default values) // Features (values below are non-default values)


+ 7
- 1
code/espurna/config/device.h View File

@ -103,6 +103,9 @@ enum devices {
DEVICE_XIAOMI_SMART_DESK_LAMP, DEVICE_XIAOMI_SMART_DESK_LAMP,
DEVICE_ALLTERCO_SHELLY2, DEVICE_ALLTERCO_SHELLY2,
DEVICE_PHYX_ESP12_RGB, DEVICE_PHYX_ESP12_RGB,
DEVICE_IWOOLE_LED_TABLE_LAMP,
DEVICE_EXS_WIFI_RELAY_V50,
DEVICE_BESTEK_MRJ1011,
DEVICE_LAST DEVICE_LAST
@ -117,10 +120,12 @@ enum devices {
defined(ALLTERCO_SHELLY1) || \ defined(ALLTERCO_SHELLY1) || \
defined(ARNIEX_SWIFITCH) || \ defined(ARNIEX_SWIFITCH) || \
defined(BH_ONOFRE) || \ defined(BH_ONOFRE) || \
defined(BESTEK_MRJ1011) || \
defined(ELECTRODRAGON_WIFI_IOT) || \ defined(ELECTRODRAGON_WIFI_IOT) || \
defined(ESTINK_WIFI_POWER_STRIP) || \ defined(ESTINK_WIFI_POWER_STRIP) || \
defined(EUROMATE_WIFI_STECKER_SCHUKO) || \ defined(EUROMATE_WIFI_STECKER_SCHUKO) || \
defined(EXS_WIFI_RELAY_V31) || \ defined(EXS_WIFI_RELAY_V31) || \
defined(EXS_WIFI_RELAY_V50) || \
defined(GENERIC_8CH) || \ defined(GENERIC_8CH) || \
defined(GENERIC_ESP01S_RELAY_V40) || \ defined(GENERIC_ESP01S_RELAY_V40) || \
defined(GENERIC_ESP01S_RGBLED_V10) || \ defined(GENERIC_ESP01S_RGBLED_V10) || \
@ -220,7 +225,8 @@ enum devices {
defined(HUACANXING_H802) || \ defined(HUACANXING_H802) || \
defined(INTERMITTECH_QUINLED) || \ defined(INTERMITTECH_QUINLED) || \
defined(ITEAD_BNSZ01) || \ defined(ITEAD_BNSZ01) || \
defined(ITEAD_SONOFF_LED)
defined(ITEAD_SONOFF_LED) || \
defined(IWOOLE_LED_TABLE_LAMP)
#undef ESPURNA_IMAGE #undef ESPURNA_IMAGE
#define ESPURNA_IMAGE ESPURNA_DIMMER #define ESPURNA_IMAGE ESPURNA_DIMMER


+ 30
- 4
code/espurna/config/general.h View File

@ -136,7 +136,8 @@
// EEPROM // EEPROM
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#define EEPROM_SIZE 4096 // EEPROM size in bytes
#define EEPROM_SIZE SPI_FLASH_SEC_SIZE // EEPROM size in bytes (1 sector = 4096 bytes)
//#define EEPROM_RORATE_SECTORS 2 // Number of sectors to use for EEPROM rotation //#define EEPROM_RORATE_SECTORS 2 // Number of sectors to use for EEPROM rotation
// If not defined the firmware will use a number based // If not defined the firmware will use a number based
// on the number of available sectors // on the number of available sectors
@ -153,8 +154,17 @@
// HEARTBEAT // HEARTBEAT
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#ifndef HEARTBEAT_ENABLED
#define HEARTBEAT_ENABLED 1
#define HEARTBEAT_NONE 0 // Never send heartbeat
#define HEARTBEAT_ONCE 1 // Send it only once upon MQTT connection
#define HEARTBEAT_REPEAT 2 // Send it upon MQTT connection and every HEARTBEAT_INTERVAL
// Backwards compatibility check
#if defined(HEARTBEAT_ENABLED) && (HEARTBEAT_ENABLED == 0)
#define HEARTBEAT_MODE HEARTBEAT_NONE
#endif
#ifndef HEARTBEAT_MODE
#define HEARTBEAT_MODE HEARTBEAT_REPEAT
#endif #endif
#ifndef HEARTBEAT_INTERVAL #ifndef HEARTBEAT_INTERVAL
@ -214,6 +224,8 @@
#ifndef BUTTON_LNGLNGCLICK_DELAY #ifndef BUTTON_LNGLNGCLICK_DELAY
#define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click #define BUTTON_LNGLNGCLICK_DELAY 10000 // Time in ms holding the button down to get a long-long click
#define BUTTON_MQTT_SEND_ALL_EVENTS 0 // 0 - to send only events the are bound to actions
// 1 - to send all button events to MQTT
#endif #endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -446,6 +458,11 @@
#define API_ENABLED 0 // Do not enable API by default #define API_ENABLED 0 // Do not enable API by default
#endif #endif
#ifndef API_RESTFUL
#define API_RESTFUL 1 // A restful API requires changes to be issued as PUT requests
// Setting this to 0 will allow using GET to change relays, for instance
#endif
#ifndef API_BUFFER_SIZE #ifndef API_BUFFER_SIZE
#define API_BUFFER_SIZE 15 // Size of the buffer for HTTP GET API responses #define API_BUFFER_SIZE 15 // Size of the buffer for HTTP GET API responses
#endif #endif
@ -769,7 +786,7 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
#ifndef SETTINGS_AUTOSAVE #ifndef SETTINGS_AUTOSAVE
#define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit
#define SETTINGS_AUTOSAVE 1 // Autosave settings or force manual commit
#endif #endif
#define SETTINGS_MAX_LIST_COUNT 10 // Maximum index for settings lists #define SETTINGS_MAX_LIST_COUNT 10 // Maximum index for settings lists
@ -987,6 +1004,11 @@
#define THINGSPEAK_URL "/update" #define THINGSPEAK_URL "/update"
#define THINGSPEAK_MIN_INTERVAL 15000 // Minimum interval between POSTs (in millis) #define THINGSPEAK_MIN_INTERVAL 15000 // Minimum interval between POSTs (in millis)
#define THINGSPEAK_FIELDS 8 // Number of fields
#ifndef THINGSPEAK_TRIES
#define THINGSPEAK_TRIES 3 // Number of tries when sending data (minimum 1)
#endif
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// SCHEDULER // SCHEDULER
@ -1321,6 +1343,10 @@
#define RFM69_MAX_TOPICS 50 #define RFM69_MAX_TOPICS 50
#endif #endif
#ifndef RFM69_MAX_NODES
#define RFM69_MAX_NODES 255
#endif
#ifndef RFM69_DEFAULT_TOPIC #ifndef RFM69_DEFAULT_TOPIC
#define RFM69_DEFAULT_TOPIC "/rfm69gw/{node}/{key}" #define RFM69_DEFAULT_TOPIC "/rfm69gw/{node}/{key}"
#endif #endif


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

@ -2,6 +2,7 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <functional> #include <functional>
#include <pgmspace.h> #include <pgmspace.h>
#include <core_version.h>
extern "C" { extern "C" {
#include "user_interface.h" #include "user_interface.h"
@ -35,6 +36,25 @@ extern "C" {
void custom_crash_callback(struct rst_info*, uint32_t, uint32_t); void custom_crash_callback(struct rst_info*, uint32_t, uint32_t);
} }
// Core version 2.4.2 and higher changed the cont_t structure to a pointer:
// https://github.com/esp8266/Arduino/commit/5d5ea92a4d004ab009d5f642629946a0cb8893dd#diff-3fa12668b289ccb95b7ab334833a4ba8L35
// Core version 2.5.0 introduced EspClass helper method:
// https://github.com/esp8266/Arduino/commit/0e0e34c614fe8a47544c9998201b1d9b3c24eb18
extern "C" {
#include <cont.h>
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) \
|| defined(ARDUINO_ESP8266_RELEASE_2_4_0) \
|| defined(ARDUINO_ESP8266_RELEASE_2_4_1)
extern cont_t g_cont;
#define getFreeStack() cont_get_free_stack(&g_cont)
#elif defined(ARDUINO_ESP8266_RELEASE_2_4_2)
extern cont_t* g_pcont;
#define getFreeStack() cont_get_free_stack(g_pcont)
#else
#define getFreeStack() ESP.getFreeContStack()
#endif
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Domoticz // Domoticz
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


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


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


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


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


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


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


+ 72
- 0
code/espurna/device.ino View File

@ -2052,6 +2052,9 @@ void _deviceLoad() {
setSetting("device", "XIAOMI_SMART_DESK_LAMP"); setSetting("device", "XIAOMI_SMART_DESK_LAMP");
setSetting("fw", ESPURNA_DIMMER); setSetting("fw", ESPURNA_DIMMER);
// This button doubles as switch here and as encoder mode switch below
// Clicking it (for less than 500ms) will turn the light on and off
// Double and Long clicks will not work as these are used to modify the encoder action
setSetting("btnGPIO", 0, 2); setSetting("btnGPIO", 0, 2);
setSetting("btnGPIO", 1, 14); setSetting("btnGPIO", 1, 14);
setSetting("btnRelay", 0, 0); setSetting("btnRelay", 0, 0);
@ -2059,6 +2062,8 @@ void _deviceLoad() {
setSetting("btnDblClick", 0, BUTTON_MODE_NONE); setSetting("btnDblClick", 0, BUTTON_MODE_NONE);
setSetting("btnLngClick", 0, BUTTON_MODE_NONE); setSetting("btnLngClick", 0, BUTTON_MODE_NONE);
setSetting("btnLngLngClick", 0, BUTTON_MODE_NONE); setSetting("btnLngLngClick", 0, BUTTON_MODE_NONE);
// Hidden button will enter AP mode if dblclick and reset the device when long-long-clicked
setSetting("btnDblClick", 1, BUTTON_MODE_AP); setSetting("btnDblClick", 1, BUTTON_MODE_AP);
setSetting("btnLngLngClick", 1, BUTTON_MODE_RESET); setSetting("btnLngLngClick", 1, BUTTON_MODE_RESET);
@ -2069,6 +2074,12 @@ void _deviceLoad() {
setSetting("litChLogic", 0, 0); setSetting("litChLogic", 0, 0);
setSetting("litChLogic", 1, 0); setSetting("litChLogic", 1, 0);
// Encoder
// If mode is ENCODER_MODE_RATIO, the value ratio between both channels is changed
// when the button is not pressed, and the overall brightness when pressed
// If mode is ENCODER_MODE_CHANNEL, the first channel value is changed
// when the button is not pressed, and the second channel when pressed
// If no ENCODERX_BUTTON_PIN defined it will only change the value of the first defined channel
setSetting("enc1stGPIO", 0, 12); setSetting("enc1stGPIO", 0, 12);
setSetting("enc2ndGPIO", 0, 13); setSetting("enc2ndGPIO", 0, 13);
setSetting("encBtnGPIO", 0, 2); setSetting("encBtnGPIO", 0, 2);
@ -2106,6 +2117,67 @@ void _deviceLoad() {
setSetting("litChLogic", 1, 0); setSetting("litChLogic", 1, 0);
setSetting("litChLogic", 3, 0); setSetting("litChLogic", 3, 0);
#elif defined(IWOOLE_LED_TABLE_LAMP)
// iWoole LED Table Lamp
// http://iwoole.com/newst-led-smart-night-light-7w-smart-table-light-rgbw-wifi-app-remote-control-110v-220v-us-eu-plug-smart-lamp-google-home-decore-p00022p1.html
setSetting("board", DEVICE_IWOOLE_LED_TABLE_LAMP);
setSetting("device", "IWOOLE_LED_TABLE_LAMP");
setSetting("fw", ESPURNA_DIMMER);
setSetting("rlyProvider", RELAY_PROVIDER_LIGHT);
setSetting("litChGPIO", 0, 12);
setSetting("litChGPIO", 1, 5);
setSetting("litChGPIO", 2, 14);
setSetting("litChGPIO", 3, 4);
setSetting("litChLogic", 0, 0);
setSetting("litChLogic", 1, 0);
setSetting("litChLogic", 2, 0);
setSetting("litChLogic", 3, 0);
#elif defined(EXS_WIFI_RELAY_V50)
setSetting("board", DEVICE_EXS_WIFI_RELAY_V50);
setSetting("device", "EXS_WIFI_RELAY_V50");
setSetting("fw", ESPURNA_BASIC);
setSetting("btnGPIO", 0, 5);
setSetting("btnGPIO", 1, 4);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnMode", 0, BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH);
setSetting("btnMode", 1, BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH);
setSetting("rlyGPIO", 0, 14);
setSetting("rlyGPIO", 1, 13);
setSetting("rlyResetGPIO", 0, 16);
setSetting("rlyResetGPIO", 1, 12);
setSetting("rlyType", 0, RELAY_TYPE_LATCHED);
setSetting("rlyType", 0, RELAY_TYPE_LATCHED);
setSetting("ledGPIO", 1, 15);
setSetting("ledLogic", 1, 0);
#elif defined(BESTEK_MRJ1011)
// Bestek Smart Plug with 2 USB ports
// https://www.bestekcorp.com/bestek-smart-plug-works-with-amazon-alexa-google-assistant-and-ifttt-with-2-usb
setSetting("board", DEVICE_BESTEK_MRJ1011);
setSetting("device", "BESTEK_MRJ1011");
setSetting("fw", ESPURNA_BASIC);
setSetting("btnGPIO", 0, 13);
setSetting("btnMode", 0, BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH);
setSetting("btnRelay", 0, 0);
setSetting("rlyGPIO", 0, 12);
setSetting("rlyType", 0, RELAY_TYPE_NORMAL);
setSetting("ledGPIO", 1, 4);
setSetting("ledLogic", 1, 1);
#endif #endif
} }


+ 9
- 4
code/espurna/homeassistant.ino View File

@ -165,7 +165,7 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject(); JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config); _haSendSwitch(i, config);
output += type + ":\n";
output += "\n" + type + ":\n";
bool first = true; bool first = true;
for (auto kv : config) { for (auto kv : config) {
if (first) { if (first) {
@ -176,7 +176,6 @@ String _haGetConfig() {
} }
output += kv.key + String(": ") + kv.value.as<String>() + String("\n"); output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
} }
output += "\n";
jsonBuffer.clear(); jsonBuffer.clear();
@ -190,7 +189,7 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject(); JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config); _haSendMagnitude(i, config);
output += "sensor:\n";
output += "\nsensor:\n";
bool first = true; bool first = true;
for (auto kv : config) { for (auto kv : config) {
if (first) { if (first) {
@ -199,7 +198,9 @@ String _haGetConfig() {
} else { } else {
output += " "; output += " ";
} }
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
String value = kv.value.as<String>();
value.replace("%", "'%'");
output += kv.key + String(": ") + value + String("\n");
} }
output += "\n"; output += "\n";
@ -267,10 +268,12 @@ void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& d
#if TERMINAL_SUPPORT #if TERMINAL_SUPPORT
void _haInitCommands() { void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) { settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str()); DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) { settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1"); setSetting("haEnabled", "1");
_haConfigure(); _haConfigure();
@ -279,6 +282,7 @@ void _haInitCommands() {
#endif #endif
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) { settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0"); setSetting("haEnabled", "0");
_haConfigure(); _haConfigure();
@ -287,6 +291,7 @@ void _haInitCommands() {
#endif #endif
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
} }
#endif #endif


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

@ -306,7 +306,7 @@ unsigned long _mqttNextMessageId() {
EEPROMr.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF); EEPROMr.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF); EEPROMr.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF); EEPROMr.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROMr.commit();
saveSettings();
} }
id++; id++;


+ 53
- 13
code/espurna/relay.ino View File

@ -175,11 +175,19 @@ void _relayProcess(bool mode) {
#endif #endif
if (!_relayRecursive) { if (!_relayRecursive) {
relayPulse(id); relayPulse(id);
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave);
// We will trigger a commit only if
// we care about current relay status on boot
unsigned char boot_mode = getSetting("relayBoot", id, RELAY_BOOT_MODE).toInt();
bool do_commit = ((RELAY_BOOT_SAME == boot_mode) || (RELAY_BOOT_TOGGLE == boot_mode));
_relaySaveTicker.once_ms(RELAY_SAVE_DELAY, relaySave, do_commit);
#if WEB_SUPPORT #if WEB_SUPPORT
wsSend(_relayWebSocketUpdate); wsSend(_relayWebSocketUpdate);
#endif #endif
} }
#if DOMOTICZ_SUPPORT #if DOMOTICZ_SUPPORT
@ -385,16 +393,41 @@ void relaySync(unsigned char id) {
} }
void relaySave() {
void relaySave(bool do_commit) {
// Relay status is stored in a single byte
// This means that, atm,
// we are only storing the status of the first 8 relays.
unsigned char bit = 1; unsigned char bit = 1;
unsigned char mask = 0; unsigned char mask = 0;
for (unsigned int i=0; i < _relays.size(); i++) {
unsigned char count = _relays.size();
if (count > 8) count = 8;
for (unsigned int i=0; i < count; i++) {
if (relayStatus(i)) mask += bit; if (relayStatus(i)) mask += bit;
bit += bit; bit += bit;
} }
EEPROMr.write(EEPROM_RELAY_STATUS, mask); EEPROMr.write(EEPROM_RELAY_STATUS, mask);
DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
EEPROMr.commit();
DEBUG_MSG_P(PSTR("[RELAY] Setting relay mask: %d\n"), mask);
// The 'do_commit' flag controls wether we are commiting this change or not.
// It is useful to set it to 'false' if the relay change triggering the
// save involves a relay whose boot mode is independent from current mode,
// thus storing the last relay value is not absolutely necessary.
// Nevertheless, we store the value in the EEPROM buffer so it will be written
// on the next commit.
if (do_commit) {
// We are actually enqueuing the commit so it will be
// executed on the main loop, in case this is called from a callback
saveSettings();
}
}
void relaySave() {
relaySave(true);
} }
void relayToggle(unsigned char id, bool report, bool group_report) { void relayToggle(unsigned char id, bool report, bool group_report) {
@ -485,27 +518,34 @@ void _relayBoot() {
DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask); DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
// Walk the relays // Walk the relays
bool status = false;
bool status;
for (unsigned int i=0; i<_relays.size(); i++) { for (unsigned int i=0; i<_relays.size(); i++) {
unsigned char boot_mode = getSetting("rlyBoot", i, RELAY_BOOT_MODE).toInt(); unsigned char boot_mode = getSetting("rlyBoot", i, RELAY_BOOT_MODE).toInt();
DEBUG_MSG_P(PSTR("[RELAY] Relay #%d boot mode %d\n"), i, boot_mode); DEBUG_MSG_P(PSTR("[RELAY] Relay #%d boot mode %d\n"), i, boot_mode);
status = false;
switch (boot_mode) { switch (boot_mode) {
case RELAY_BOOT_SAME: case RELAY_BOOT_SAME:
status = ((mask & bit) == bit);
if (i < 8) {
status = ((mask & bit) == bit);
}
break; break;
case RELAY_BOOT_TOGGLE: case RELAY_BOOT_TOGGLE:
status = ((mask & bit) != bit);
mask ^= bit;
trigger_save = true;
if (i < 8) {
status = ((mask & bit) != bit);
mask ^= bit;
trigger_save = true;
}
break; break;
case RELAY_BOOT_ON: case RELAY_BOOT_ON:
status = true; status = true;
break; break;
case RELAY_BOOT_OFF: case RELAY_BOOT_OFF:
default: default:
status = false;
break; break;
} }
_relays[i].current_status = !status; _relays[i].current_status = !status;
_relays[i].target_status = status; _relays[i].target_status = status;
#if RELAY_PROVIDER == RELAY_PROVIDER_STM #if RELAY_PROVIDER == RELAY_PROVIDER_STM
@ -519,7 +559,7 @@ void _relayBoot() {
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode // Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) { if (trigger_save) {
EEPROMr.write(EEPROM_RELAY_STATUS, mask); EEPROMr.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.commit();
saveSettings();
} }
_relayRecursive = false; _relayRecursive = false;
@ -814,7 +854,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_CONNECT_EVENT) { if (type == MQTT_CONNECT_EVENT) {
// Send status on connect // Send status on connect
#if not HEARTBEAT_REPORT_RELAY
#if (HEARTBEAT_MODE == HEARTBEAT_NONE) or (not HEARTBEAT_REPORT_RELAY)
relayMQTT(); relayMQTT();
#endif #endif


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

@ -27,7 +27,7 @@ struct _node_t {
unsigned char lastPacketID = 0; unsigned char lastPacketID = 0;
}; };
_node_t _rfm69_node_info[255];
_node_t _rfm69_node_info[RFM69_MAX_NODES];
unsigned char _rfm69_node_count; unsigned char _rfm69_node_count;
unsigned long _rfm69_packet_count; unsigned long _rfm69_packet_count;
@ -117,6 +117,9 @@ void _rfm69Debug(const char * level, packet_t * data) {
void _rfm69Process(packet_t * data) { void _rfm69Process(packet_t * data) {
// Is node beyond RFM69_MAX_NODES?
if (data->senderID >= RFM69_MAX_NODES) return;
// Count seen nodes and packets // Count seen nodes and packets
if (_rfm69_node_info[data->senderID].count == 0) ++_rfm69_node_count; if (_rfm69_node_info[data->senderID].count == 0) ++_rfm69_node_count;
++_rfm69_packet_count; ++_rfm69_packet_count;
@ -237,9 +240,10 @@ void _rfm69Loop() {
} }
void _rfm69Clear() { void _rfm69Clear() {
for(unsigned int i=0; i<255; i++) {
for(unsigned int i=0; i<RFM69_MAX_NODES; i++) {
_rfm69_node_info[i].duplicates = 0; _rfm69_node_info[i].duplicates = 0;
_rfm69_node_info[i].missing = 0; _rfm69_node_info[i].missing = 0;
_rfm69_node_info[i].count = 0;
} }
_rfm69_node_count = 0; _rfm69_node_count = 0;
_rfm69_packet_count = 0; _rfm69_packet_count = 0;


+ 7
- 3
code/espurna/sensors/CSE7766Sensor.h View File

@ -22,7 +22,7 @@ class CSE7766Sensor : public BaseSensor {
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
CSE7766Sensor(): BaseSensor(), _data() { CSE7766Sensor(): BaseSensor(), _data() {
_count = 4;
_count = 6;
_sensor_id = SENSOR_CSE7766_ID; _sensor_id = SENSOR_CSE7766_ID;
} }
@ -161,7 +161,9 @@ class CSE7766Sensor : public BaseSensor {
if (index == 0) return MAGNITUDE_CURRENT; if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE; if (index == 1) return MAGNITUDE_VOLTAGE;
if (index == 2) return MAGNITUDE_POWER_ACTIVE; if (index == 2) return MAGNITUDE_POWER_ACTIVE;
if (index == 3) return MAGNITUDE_ENERGY;
if (index == 3) return MAGNITUDE_POWER_APPARENT;
if (index == 4) return MAGNITUDE_POWER_FACTOR;
if (index == 5) return MAGNITUDE_ENERGY;
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
@ -170,7 +172,9 @@ class CSE7766Sensor : public BaseSensor {
if (index == 0) return _current; if (index == 0) return _current;
if (index == 1) return _voltage; if (index == 1) return _voltage;
if (index == 2) return _active; if (index == 2) return _active;
if (index == 3) return _energy;
if (index == 3) return _voltage * _current;
if (index == 4) return ((_voltage > 0) && (_current > 0)) ? 100 * _active / _voltage / _current : 100;
if (index == 5) return _energy;
return 0; return 0;
} }


+ 7
- 0
code/espurna/settings.ino View File

@ -379,6 +379,13 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("\n+OK\n")); DEBUG_MSG_P(PSTR("\n+OK\n"));
}); });
#if not SETTINGS_AUTOSAVE
settingsRegisterCommand(F("SAVE"), [](Embedis* e) {
_settings_save = true;
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
#endif
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


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


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


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


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


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


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


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

@ -90,15 +90,19 @@ void systemLoop() {
// Heartbeat // Heartbeat
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
#if HEARTBEAT_ENABLED
// Heartbeat
#if HEARTBEAT_MODE == HEARTBEAT_ONCE
if (_system_send_heartbeat) {
_system_send_heartbeat = false;
heartbeat();
}
#elif HEARTBEAT_MODE == HEARTBEAT_REPEAT
static unsigned long last_hbeat = 0; static unsigned long last_hbeat = 0;
if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) { if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) {
_system_send_heartbeat = false; _system_send_heartbeat = false;
last_hbeat = millis(); last_hbeat = millis();
heartbeat(); heartbeat();
} }
#endif // HEARTBEAT_ENABLED
#endif // HEARTBEAT_MODE == HEARTBEAT_REPEAT
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Load Average calculation // Load Average calculation


+ 37
- 9
code/espurna/thinkspeak.ino View File

@ -27,10 +27,11 @@ const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
"%s\r\n"; "%s\r\n";
bool _tspk_enabled = false; bool _tspk_enabled = false;
char * _tspk_queue[8] = {NULL};
char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL};
bool _tspk_flush = false; bool _tspk_flush = false;
unsigned long _tspk_last_flush = 0; unsigned long _tspk_last_flush = 0;
unsigned char _tspk_tries = 0;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -99,12 +100,23 @@ void _tspkPost(String data) {
}, 0); }, 0);
_tspk_client->onData([](void * arg, AsyncClient * c, void * response, size_t len) { _tspk_client->onData([](void * arg, AsyncClient * c, void * response, size_t len) {
char * b = (char *) response; char * b = (char *) response;
b[len] = 0; b[len] = 0;
char * p = strstr((char *)response, "\r\n\r\n"); char * p = strstr((char *)response, "\r\n\r\n");
unsigned int code = (p != NULL) ? atoi(&p[4]) : 0; unsigned int code = (p != NULL) ? atoi(&p[4]) : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code); DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_tspk_last_flush = millis();
if ((0 == code) && (--_tspk_tries > 0)) {
_tspk_flush = true;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing\n"));
} else {
_tspkClearQueue();
}
_tspk_client->close(true); _tspk_client->close(true);
}, NULL); }, NULL);
_tspk_client->onConnect([data](void * arg, AsyncClient * client) { _tspk_client->onConnect([data](void * arg, AsyncClient * client) {
@ -120,7 +132,7 @@ void _tspkPost(String data) {
} }
#endif #endif
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str());
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), THINGSPEAK_URL, data.c_str());
char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()]; char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
snprintf_P(buffer, sizeof(buffer), snprintf_P(buffer, sizeof(buffer),
@ -166,7 +178,7 @@ void _tspkPost(String data) {
DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n")); DEBUG_MSG_P(PSTR("[THINGSPEAK] Warning: certificate doesn't match\n"));
} }
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s\n"), data.c_str());
DEBUG_MSG_P(PSTR("[THINGSPEAK] POST %s?%s\n"), THINGSPEAK_URL, data.c_str());
char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()]; char buffer[strlen_P(THINGSPEAK_REQUEST_TEMPLATE) + strlen(THINGSPEAK_URL) + strlen(THINGSPEAK_HOST) + data.length()];
snprintf_P(buffer, sizeof(buffer), snprintf_P(buffer, sizeof(buffer),
THINGSPEAK_REQUEST_TEMPLATE, THINGSPEAK_REQUEST_TEMPLATE,
@ -184,6 +196,15 @@ void _tspkPost(String data) {
unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0; unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code); DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_tspk_client.stop(); _tspk_client.stop();
_tspk_last_flush = millis();
if ((0 == code) && (--_tspk_tries > 0)) {
_tspk_flush = true;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Re-enqueuing\n"));
} else {
_tspkClearQueue();
}
return; return;
} }
@ -201,25 +222,33 @@ void _tspkEnqueue(unsigned char index, char * payload) {
_tspk_queue[index] = strdup(payload); _tspk_queue[index] = strdup(payload);
} }
void _tspkClearQueue() {
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) {
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
}
}
}
void _tspkFlush() { void _tspkFlush() {
String data;
_tspk_flush = false;
// Walk the fields // Walk the fields
for (unsigned char id=0; id<8; id++) {
String data;
for (unsigned char id=0; id<THINGSPEAK_FIELDS; id++) {
if (_tspk_queue[id] != NULL) { if (_tspk_queue[id] != NULL) {
if (data.length() > 0) data = data + String("&"); if (data.length() > 0) data = data + String("&");
data = data + String("field") + String(id+1) + String("=") + String(_tspk_queue[id]); data = data + String("field") + String(id+1) + String("=") + String(_tspk_queue[id]);
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
} }
} }
// POST data if any // POST data if any
if (data.length() > 0) { if (data.length() > 0) {
data = data + String("&api_key=") + getSetting("tspkKey"); data = data + String("&api_key=") + getSetting("tspkKey");
_tspk_tries = THINGSPEAK_TRIES;
_tspkPost(data); _tspkPost(data);
_tspk_last_flush = millis();
} }
} }
@ -281,7 +310,6 @@ void tspkLoop() {
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return; if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return;
if (_tspk_flush && (millis() - _tspk_last_flush > THINGSPEAK_MIN_INTERVAL)) { if (_tspk_flush && (millis() - _tspk_last_flush > THINGSPEAK_MIN_INTERVAL)) {
_tspkFlush(); _tspkFlush();
_tspk_flush = false;
} }
} }


+ 12
- 21
code/espurna/utils.ino View File

@ -8,11 +8,6 @@ Module key prefix: esp (shared with others)
*/ */
extern "C" {
#include <cont.h>
extern cont_t g_cont;
}
#include <Ticker.h> #include <Ticker.h>
Ticker _defer_reset; Ticker _defer_reset;
@ -100,10 +95,6 @@ unsigned int getUsedHeap() {
return getInitialFreeHeap() - getFreeHeap(); return getInitialFreeHeap() - getFreeHeap();
} }
unsigned int getFreeStack() {
return cont_get_free_stack(&g_cont);
}
String getEspurnaModules() { String getEspurnaModules() {
return FPSTR(espurna_modules); return FPSTR(espurna_modules);
} }
@ -161,7 +152,7 @@ unsigned long getUptime() {
} }
#if HEARTBEAT_ENABLED
#if HEARTBEAT_MODE != HEARTBEAT_NONE
void heartbeat() { void heartbeat() {
@ -263,7 +254,7 @@ void heartbeat() {
} }
#endif /// HEARTBEAT_ENABLED
#endif /// HEARTBEAT_MODE != HEARTBEAT_NONE
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// INFO // INFO
@ -304,7 +295,7 @@ void _info_print_memory_layout_line(const char * name, unsigned long bytes) {
void infoMemory(const char * name, unsigned int total_memory, unsigned int free_memory) { void infoMemory(const char * name, unsigned int total_memory, unsigned int free_memory) {
DEBUG_MSG_P( DEBUG_MSG_P(
PSTR("[MAIN] %-6s: %5u bytes total - %5u bytes used (%2u%%) - %5u bytes free (%2u%%)\n"),
PSTR("[MAIN] %-6s: %5u bytes initially | %5u bytes used (%2u%%) | %5u bytes free (%2u%%)\n"),
name, name,
total_memory, total_memory,
total_memory - free_memory, total_memory - free_memory,
@ -361,12 +352,12 @@ void info() {
FSInfo fs_info; FSInfo fs_info;
bool fs = SPIFFS.info(fs_info); bool fs = SPIFFS.info(fs_info);
if (fs) { if (fs) {
DEBUG_MSG_P(PSTR("[MAIN] SPIFFS total size: %8u bytes / %4d sectors\n"), fs_info.totalBytes, sectors(fs_info.totalBytes));
DEBUG_MSG_P(PSTR("[MAIN] used size: %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR("[MAIN] block size: %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR("[MAIN] page size: %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR("[MAIN] max files: %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR("[MAIN] max length: %8u\n"), fs_info.maxPathLength);
DEBUG_MSG_P(PSTR("[MAIN] SPIFFS total size : %8u bytes / %4d sectors\n"), fs_info.totalBytes, info_bytes2sectors(fs_info.totalBytes));
DEBUG_MSG_P(PSTR("[MAIN] used size : %8u bytes\n"), fs_info.usedBytes);
DEBUG_MSG_P(PSTR("[MAIN] block size : %8u bytes\n"), fs_info.blockSize);
DEBUG_MSG_P(PSTR("[MAIN] page size : %8u bytes\n"), fs_info.pageSize);
DEBUG_MSG_P(PSTR("[MAIN] max files : %8u\n"), fs_info.maxOpenFiles);
DEBUG_MSG_P(PSTR("[MAIN] max length : %8u\n"), fs_info.maxPathLength);
} else { } else {
DEBUG_MSG_P(PSTR("[MAIN] No SPIFFS partition\n")); DEBUG_MSG_P(PSTR("[MAIN] No SPIFFS partition\n"));
} }
@ -497,13 +488,13 @@ void resetReason(unsigned char reason) {
EEPROMr.commit(); EEPROMr.commit();
} }
void reset(unsigned char reason) {
resetReason(reason);
void reset() {
ESP.restart(); ESP.restart();
} }
void deferredReset(unsigned long delay, unsigned char reason) { void deferredReset(unsigned long delay, unsigned char reason) {
_defer_reset.once_ms(delay, reset, reason);
resetReason(reason);
_defer_reset.once_ms(delay, reset);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 0
- 1
code/gulpfile.js View File

@ -31,7 +31,6 @@ const runSequence = require('run-sequence');
const through = require('through2'); const through = require('through2');
const htmlmin = require('gulp-htmlmin'); const htmlmin = require('gulp-htmlmin');
const uglify = require('gulp-uglify');
const inline = require('gulp-inline'); const inline = require('gulp-inline');
const inlineImages = require('gulp-css-base64'); const inlineImages = require('gulp-css-base64');
const favicon = require('gulp-base64-favicon'); const favicon = require('gulp-base64-favicon');


+ 60
- 14
code/html/custom.css View File

@ -50,10 +50,6 @@ h2 {
display: block; display: block;
} }
.content {
margin: 0;
}
.page { .page {
margin-top: 10px; margin-top: 10px;
} }
@ -98,16 +94,12 @@ div.center {
display: none; display: none;
} }
#credentials {
font-size: 200%;
height: 100px;
left: 50%;
margin-left: -200px;
margin-top: -50px;
position: fixed;
text-align: center;
top: 50%;
width: 400px;
#password .content {
margin: 0 auto;
}
#layout .content {
margin: 0;
} }
div.state { div.state {
@ -206,6 +198,10 @@ div.state {
background: rgb(255, 128, 0); /* orange */ background: rgb(255, 128, 0); /* orange */
} }
.button-generate-password {
background: rgb(66, 184, 221); /* blue */
}
.button-upgrade-browse, .button-upgrade-browse,
.button-clear-filters, .button-clear-filters,
.button-clear-messages, .button-clear-messages,
@ -448,3 +444,53 @@ table.dataTable.display tbody td {
height: 400px; height: 400px;
margin-bottom: 10px; margin-bottom: 10px;
} }
/* -----------------------------------------------------------------------------
Password input controls
-------------------------------------------------------------------------- */
.password-reveal {
font-family: EmojiSymbols,Segoe UI Symbol;
background: rgba(0,0,0,0);
display: inline-block;
float: right;
z-index: 50;
margin-top: 6px;
margin-left: -30px;
vertical-align: middle;
font-size: 1.2em;
height: 100%;
}
.password-reveal:after {
content: "👁";
}
input[type="password"] + .password-reveal {
color: rgba(205, 205, 205, 0.3);
}
input[type="text"] + .password-reveal {
color: rgba(66, 184, 221, 0.8);
}
.no-select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
input::-ms-clear,
input::-ms-reveal {
display: none;
}
/* css minifier must not combine these.
* style will not apply otherwise */
input::-ms-input-placeholder {
color: #ccd;
}
input::placeholder {
color: #ccc;
}

+ 108
- 31
code/html/custom.js View File

@ -146,8 +146,7 @@ function loadTimeZones() {
} }
function validateForm(form) {
function validatePassword(password) {
// http://www.the-art-of-web.com/javascript/validate-password/ // http://www.the-art-of-web.com/javascript/validate-password/
// at least one lowercase and one uppercase letter or number // at least one lowercase and one uppercase letter or number
// at least eight characters (letters, numbers or special characters) // at least eight characters (letters, numbers or special characters)
@ -155,21 +154,45 @@ function validateForm(form) {
// MUST be 8..63 printable ASCII characters. See: // MUST be 8..63 printable ASCII characters. See:
// https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution) // https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution)
// https://github.com/xoseperez/espurna/issues/1151 // https://github.com/xoseperez/espurna/issues/1151
var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{8,63}$/; var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{8,63}$/;
return (
(password !== undefined)
&& (typeof password === "string")
&& (password.length > 0)
&& re_password.test(password)
);
}
function validateFormPasswords(form) {
var passwords = $("input[name='adminPass1'],input[name='adminPass2']", form);
var adminPass1 = passwords.first().val(),
adminPass2 = passwords.last().val();
var formValidity = passwords.first()[0].checkValidity();
if (formValidity && (adminPass1.length === 0) && (adminPass2.length === 0)) {
return true;
}
var validPass1 = validatePassword(adminPass1),
validPass2 = validatePassword(adminPass2);
if (formValidity && validPass1 && validPass2) {
return true;
}
// password
var adminPass1 = $("input[name='adminPass']", form).first().val();
if (adminPass1.length > 0 && !re_password.test(adminPass1)) {
if (!formValidity || (adminPass1.length > 0 && !validPass1)) {
alert("The password you have entered is not valid, it must be 8..63 characters and have at least 1 lowercase and 1 uppercase / number!"); alert("The password you have entered is not valid, it must be 8..63 characters and have at least 1 lowercase and 1 uppercase / number!");
return false;
} }
var adminPass2 = $("input[name='adminPass']", form).last().val();
if (adminPass1 !== adminPass2) { if (adminPass1 !== adminPass2) {
alert("Passwords are different!"); alert("Passwords are different!");
return false;
} }
return false;
}
function validateFormHostname(form) {
// RFCs mandate that a hostname's labels may contain only // RFCs mandate that a hostname's labels may contain only
// the ASCII letters 'a' through 'z' (case-insensitive), // the ASCII letters 'a' through 'z' (case-insensitive),
// the digits '0' through '9', and the hyphen. // the digits '0' through '9', and the hyphen.
@ -182,18 +205,21 @@ function validateForm(form) {
var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,30}[A-Za-z0-9]$'); var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,30}[A-Za-z0-9]$');
var hostname = $("input[name='hostname']", form); var hostname = $("input[name='hostname']", form);
var hasChanged = ("true" === hostname.attr("hasChanged"));
if (!hasChanged) {
if ("true" !== hostname.attr("hasChanged")) {
return true; return true;
} }
if (!re_hostname.test(hostname.val())) {
alert("Hostname cannot be empty and may only contain the ASCII letters ('A' through 'Z' and 'a' through 'z'), the digits '0' through '9', and the hyphen ('-')! They can neither start or end with an hyphen.");
return false;
if (re_hostname.test(hostname.val())) {
return true;
} }
return true;
alert("Hostname cannot be empty and may only contain the ASCII letters ('A' through 'Z' and 'a' through 'z'), the digits '0' through '9', and the hyphen ('-')! They can neither start or end with an hyphen.");
return false;
}
function validateForm(form) {
return validateFormPasswords(form) && validateFormHostname(form);
} }
function getValue(element) { function getValue(element) {
@ -225,6 +251,12 @@ function addValue(data, name, value) {
"node", "key", "topic" "node", "key", "topic"
]; ];
// join both adminPass 1 and 2
if (name.startsWith("adminPass")) {
name = "adminPass";
}
if (name in data) { if (name in data) {
if (!Array.isArray(data[name])) { if (!Array.isArray(data[name])) {
data[name] = [data[name]]; data[name] = [data[name]];
@ -260,26 +292,67 @@ function getData(form) {
} }
function randomString(length, chars) {
var mask = "";
if (chars.indexOf("a") > -1) { mask += "abcdefghijklmnopqrstuvwxyz"; }
if (chars.indexOf("A") > -1) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
if (chars.indexOf("#") > -1) { mask += "0123456789"; }
if (chars.indexOf("@") > -1) { mask += "ABCDEF"; }
if (chars.indexOf("!") > -1) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
var result = "";
for (var i = length; i > 0; --i) {
result += mask[Math.round(Math.random() * (mask.length - 1))];
function randomString(length, args) {
if (typeof args === "undefined") {
args = {
lowercase: true,
uppercase: true,
numbers: true,
special: true
}
} }
return result;
var mask = "";
if (args.lowercase) { mask += "abcdefghijklmnopqrstuvwxyz"; }
if (args.uppercase) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
if (args.numbers || args.hex) { mask += "0123456789"; }
if (args.hex) { mask += "ABCDEF"; }
if (args.special) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
var source = new Uint32Array(length);
var result = new Array(length);
window.crypto.getRandomValues(source).forEach(function(value, i) {
result[i] = mask[value % mask.length];
});
return result.join("");
} }
function generateAPIKey() { function generateAPIKey() {
var apikey = randomString(16, "@#");
var apikey = randomString(16, {hex: true});
$("input[name='apiKey']").val(apikey); $("input[name='apiKey']").val(apikey);
return false; return false;
} }
function generatePassword() {
var password = "";
do {
password = randomString(10);
} while (!validatePassword(password));
return password;
}
function toggleVisiblePassword() {
var elem = this.previousElementSibling;
if (elem.type === "password") {
elem.type = "text";
} else {
elem.type = "password";
}
return false;
}
function doGeneratePassword() {
$("input", $("#formPassword"))
.val(generatePassword())
.each(function() {
this.type = "text";
});
return false;
}
function getJson(str) { function getJson(str) {
try { try {
return JSON.parse(str); return JSON.parse(str);
@ -303,7 +376,7 @@ function sendConfig(data) {
function setOriginalsFromValues(force) { function setOriginalsFromValues(force) {
var force = (true === force); var force = (true === force);
$("input,select").each(function() { $("input,select").each(function() {
var initial = (null === $(this).attr("original"));
var initial = (undefined === $(this).attr("original"));
if (force || initial) { if (force || initial) {
$(this).attr("original", $(this).val()); $(this).attr("original", $(this).val());
} }
@ -422,7 +495,7 @@ function doUpgrade() {
function doUpdatePassword() { function doUpdatePassword() {
var form = $("#formPassword"); var form = $("#formPassword");
if (validateForm(form)) {
if (validateFormPasswords(form)) {
sendConfig(getData(form)); sendConfig(getData(form));
} }
return false; return false;
@ -476,11 +549,11 @@ function doReconnect(ask) {
function doUpdate() { function doUpdate() {
var form = $("#formSave");
if (validateForm(form)) {
var forms = $(".form-settings");
if (validateForm(forms)) {
// Get data // Get data
sendConfig(getData(form));
sendConfig(getData(forms));
// Empty special fields // Empty special fields
$(".pwrExpected").val(0); $(".pwrExpected").val(0);
@ -753,6 +826,7 @@ function addNetwork() {
$(this).attr("tabindex", tabindex); $(this).attr("tabindex", tabindex);
tabindex++; tabindex++;
}); });
$(".password-reveal", line).on("click", toggleVisiblePassword);
$(line).find(".button-del-network").on("click", delNetwork); $(line).find(".button-del-network").on("click", delNetwork);
$(line).find(".button-more-network").on("click", moreNetwork); $(line).find(".button-more-network").on("click", moreNetwork);
line.appendTo("#networks"); line.appendTo("#networks");
@ -1575,12 +1649,15 @@ $(function() {
createCheckboxes(); createCheckboxes();
setInterval(function() { keepTime(); }, 1000); setInterval(function() { keepTime(); }, 1000);
$(".password-reveal").on("click", toggleVisiblePassword);
$("#menuLink").on("click", toggleMenu); $("#menuLink").on("click", toggleMenu);
$(".pure-menu-link").on("click", showPanel); $(".pure-menu-link").on("click", showPanel);
$("progress").attr({ value: 0, max: 100 }); $("progress").attr({ value: 0, max: 100 });
$(".button-update").on("click", doUpdate); $(".button-update").on("click", doUpdate);
$(".button-update-password").on("click", doUpdatePassword); $(".button-update-password").on("click", doUpdatePassword);
$(".button-generate-password").on("click", doGeneratePassword);
$(".button-reboot").on("click", doReboot); $(".button-reboot").on("click", doReboot);
$(".button-reconnect").on("click", doReconnect); $(".button-reconnect").on("click", doReconnect);
$(".button-wifi-scan").on("click", doScan); $(".button-wifi-scan").on("click", doScan);


+ 102
- 52
code/html/index.html View File

@ -28,40 +28,45 @@
<div class="content"> <div class="content">
<form id="formPassword" class="pure-form" action="/" method="post">
<form id="formPassword" class="pure-form" autocomplete="off">
<div class="panel block">
<div class="panel block" id="panel-password">
<div class="header"> <div class="header">
<h1>SECURITY</h1> <h1>SECURITY</h1>
<h2>Before using this device you have to change the default password for the user 'admin'. This password will be used for the <strong>AP mode hotspot</strong>, the <strong>web interface</strong> (where you are now) and the <strong>over-the-air updates</strong>.</h2>
<h2>Before using this device you have to change the default password for the user <strong>admin</strong>. This password will be used for the <strong>AP mode hotspot</strong>, the <strong>web interface</strong> (where you are now) and the <strong>over-the-air updates</strong>.</h2>
</div> </div>
<div class="page"> <div class="page">
<fieldset> <fieldset>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Admin password</label>
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" maxlength="63" type="password" tabindex="1" autocomplete="false" />
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
It must be <strong>8..63 characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and have at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
<label class="pure-u-1 pure-u-lg-1-4" for="adminPass1">New Password</label>
<input class="pure-u-1 pure-u-lg-3-4" name="adminPass1" minlength="8" maxlength="63" type="password" tabindex="1" autocomplete="false" spellcheck="false" required />
<span class="no-select password-reveal"></span>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Repeat password</label>
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" type="password" tabindex="2" autocomplete="false" />
<label class="pure-u-1 pure-u-lg-1-4" for="adminPass2">Repeat password</label>
<input class="pure-u-1 pure-u-lg-3-4" name="adminPass2" minlength="8" maxlength="63" type="password" tabindex="2" autocomplete="false" spellcheck="false" required />
<span class="no-select password-reveal"></span>
</div> </div>
</fieldset>
<div class="pure-u-0 pure-u-lg-1-4 more"></div>
<button class="pure-button button-update-password" type="button">Update</button>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1 hint">
Password must be <strong>8..63 characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and have at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
</div>
<div class="pure-g">
<button class="pure-u-11-24 pure-u-lg-1-4 pure-button button-generate-password" type="button" title="Generate password based on password policy">Generate</button>
<div class="pure-u-2-24 pure-u-lg-1-2"></div>
<button class="pure-u-11-24 pure-u-lg-1-4 pure-button button-update-password" type="button" title="Save new password">Save</button>
</div>
</fieldset>
</div> </div>
</div> </div>
</form> </form>
</div> <!-- content --> </div> <!-- content -->
@ -309,8 +314,7 @@
</div> </div>
</div> </div>
<form id="formSave" class="pure-form" action="/" method="post" enctype="multipart/form-data">
<form id="form-general" class="pure-form form-settings">
<div class="panel" id="panel-general"> <div class="panel" id="panel-general">
<div class="header"> <div class="header">
@ -382,7 +386,9 @@
</fieldset> </fieldset>
</div> </div>
</div> </div>
</form>
<form id="form-relay" class="pure-form form-settings">
<div class="panel" id="panel-relay"> <div class="panel" id="panel-relay">
<div class="header"> <div class="header">
@ -414,8 +420,10 @@
</div> </div>
</div> </div>
</form>
<!-- removeIf(!light) -->
<!-- removeIf(!light) -->
<form id="form-color" class="pure-form form-settings">
<div class="panel" id="panel-color"> <div class="panel" id="panel-color">
<div class="header"> <div class="header">
@ -501,8 +509,10 @@
</fieldset> </fieldset>
</div> </div>
</div> </div>
<!-- endRemoveIf(!light) -->
</form>
<!-- endRemoveIf(!light) -->
<form id="form-admin" class="pure-form form-settings">
<div class="panel" id="panel-admin"> <div class="panel" id="panel-admin">
<div class="header"> <div class="header">
@ -516,25 +526,25 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Settings</label> <label class="pure-u-1 pure-u-lg-1-4">Settings</label>
<div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
<div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-restore pure-u-23-24">Restore</button></div>
<div class="pure-u-1-3 pure-u-lg-1-4"><button class="pure-button button-settings-factory pure-u-1">Factory Reset</button></div>
<div class="pure-u-1-3 pure-u-lg-1-4"><button type="button" class="pure-button button-settings-backup pure-u-23-24">Backup</button></div>
<div class="pure-u-1-3 pure-u-lg-1-4"><button type="button" class="pure-button button-settings-restore pure-u-23-24">Restore</button></div>
<div class="pure-u-1-3 pure-u-lg-1-4"><button type="button" class="pure-button button-settings-factory pure-u-1">Factory Reset</button></div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Admin password</label> <label class="pure-u-1 pure-u-lg-1-4">Admin password</label>
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" maxlength="63" type="password" action="reboot" tabindex="11" autocomplete="false" />
<input name="adminPass1" class="pure-u-1 pure-u-lg-3-4" placeholder="New password" minlength="8" maxlength="63" type="password" action="reboot" tabindex="11" autocomplete="false" spellcheck="false" />
<span class="no-select password-reveal"></span>
<div class="pure-u-1 pure-u-lg-1-4"></div>
<input name="adminPass2" class="pure-u-1 pure-u-lg-3-4" placeholder="Repeat password" minlength="8" maxlength="63" type="password" action="reboot" tabindex="12" autocomplete="false" spellcheck="false" />
<span class="no-select password-reveal"></span>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br /> The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).<br />
It must be <strong>8..63 characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and have at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div> It must be <strong>8..63 characters</strong> (numbers and letters and any of these special characters: _,.;:~!?@#$%^&amp;*&lt;&gt;\|(){}[]) and have at least <strong>one lowercase</strong> and <strong>one uppercase</strong> or <strong>one number</strong>.</div>
</div> </div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Repeat password</label>
<input name="adminPass" class="pure-u-1 pure-u-lg-3-4" maxlength="63" type="password" action="reboot" tabindex="12" autocomplete="false" />
</div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">HTTP port</label> <label class="pure-u-1 pure-u-lg-1-4">HTTP port</label>
<input name="webPort" class="pure-u-1 pure-u-lg-1-4" type="text" action="reboot" tabindex="13" /> <input name="webPort" class="pure-u-1 pure-u-lg-1-4" type="text" action="reboot" tabindex="13" />
@ -556,6 +566,17 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></div>
</div> </div>
<div class="pure-g module module-api">
<label class="pure-u-1 pure-u-lg-1-4">Restful API</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiRestFul" /></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-3-4 hint">
If enabled, API requests to change a status (like a relay) must be done using PUT.
If disabled you can issue them as GET requests (easier from a browser).
</div>
</div>
<div class="pure-g module module-api"> <div class="pure-g module module-api">
<label class="pure-u-1 pure-u-lg-1-4">Real time API</label> <label class="pure-u-1 pure-u-lg-1-4">Real time API</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiRealTime" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiRealTime" /></div>
@ -570,7 +591,7 @@
<div class="pure-g module module-api"> <div class="pure-g module module-api">
<label class="pure-u-1 pure-u-lg-1-4">HTTP API Key</label> <label class="pure-u-1 pure-u-lg-1-4">HTTP API Key</label>
<input name="apiKey" class="pure-u-3-4 pure-u-lg-1-2" type="text" tabindex="14" /> <input name="apiKey" class="pure-u-3-4 pure-u-lg-1-2" type="text" tabindex="14" />
<div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-apikey pure-u-23-24">Auto</button></div>
<div class="pure-u-1-4 pure-u-lg-1-4"><button type="button" class="pure-button button-apikey pure-u-23-24">Auto</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
This is the key you will have to pass with every HTTP request to the API, either to get or write values. This is the key you will have to pass with every HTTP request to the API, either to get or write values.
@ -603,8 +624,8 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Upgrade</label> <label class="pure-u-1 pure-u-lg-1-4">Upgrade</label>
<input class="pure-u-1-2 pure-u-lg-1-2" name="filename" type="text" readonly /> <input class="pure-u-1-2 pure-u-lg-1-2" name="filename" type="text" readonly />
<div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div>
<div class=" pure-u-1-4 pure-u-lg-1-8"><button class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div>
<div class="pure-u-1-4 pure-u-lg-1-8"><button type="button" class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div>
<div class="pure-u-1-4 pure-u-lg-1-8"><button type="button" class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" target="_blank"><strong>two-step update</strong></a>.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" target="_blank"><strong>two-step update</strong></a>.</div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
@ -615,7 +636,9 @@
</fieldset> </fieldset>
</div> </div>
</div> </div>
</form>
<form id="form-wifi" class="pure-form form-settings">
<div class="panel" id="panel-wifi"> <div class="panel" id="panel-wifi">
<div class="header"> <div class="header">
@ -657,7 +680,9 @@
</fieldset> </fieldset>
</div> </div>
</div> </div>
</form>
<form id="form-schedule" class="pure-form form-settings">
<div class="panel" id="panel-schedule"> <div class="panel" id="panel-schedule">
<div class="header"> <div class="header">
@ -681,8 +706,10 @@
</div> </div>
</div> </div>
</form>
<!-- removeIf(!rfm69) -->
<!-- removeIf(!rfm69) -->
<form id="form-mapping" class="pure-form form-settings">
<div class="panel" id="panel-mapping"> <div class="panel" id="panel-mapping">
<div class="header"> <div class="header">
@ -711,10 +738,12 @@
</fieldset> </fieldset>
</div> </div>
</div> </div>
</form>
<form id="form-messages" class="pure-form">
<div class="panel" id="panel-messages"> <div class="panel" id="panel-messages">
<div class="header"> <div class="header">
<h1>MESSAGES</h1> <h1>MESSAGES</h1>
<h2> <h2>
@ -746,15 +775,17 @@
</tbody> </tbody>
</table> </table>
<button class="pure-button button-clear-filters">Clear filters</button>
<button class="pure-button button-clear-messages">Clear messages</button>
<button class="pure-button button-clear-counts">Clear counts</button>
<button type="button" class="pure-button button-clear-filters">Clear filters</button>
<button type="button" class="pure-button button-clear-messages">Clear messages</button>
<button type="button" class="pure-button button-clear-counts">Clear counts</button>
</div> </div>
</div> </div>
<!-- endRemoveIf(!rfm69) -->
</form>
<!-- endRemoveIf(!rfm69) -->
<form id="form-mqtt" class="pure-form form-settings">
<div class="panel" id="panel-mqtt"> <div class="panel" id="panel-mqtt">
<div class="header"> <div class="header">
@ -783,12 +814,13 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">MQTT User</label> <label class="pure-u-1 pure-u-lg-1-4">MQTT User</label>
<input class="pure-u-1 pure-u-lg-1-4" name="mqttUser" type="text" tabindex="23" placeholder="Leave blank if no user" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-1-4" name="mqttUser" type="text" tabindex="23" placeholder="Leave blank if no user" autocomplete="off" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">MQTT Password</label> <label class="pure-u-1 pure-u-lg-1-4">MQTT Password</label>
<input class="pure-u-1 pure-u-lg-1-4" name="mqttPass" type="password" tabindex="24" placeholder="Leave blank if no pass" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-1-4" name="mqttPass" type="password" tabindex="24" placeholder="Leave blank if no pass" autocomplete="new-password" spellcheck="false" />
<span class="no-select password-reveal"></span>
</div> </div>
<div class="pure-g"> <div class="pure-g">
@ -873,7 +905,9 @@
</div> </div>
</div> </div>
</form>
<form id="form-ntp" class="pure-form form-settings">
<div class="panel" id="panel-ntp"> <div class="panel" id="panel-ntp">
<div class="header"> <div class="header">
@ -917,7 +951,9 @@
</div> </div>
</div> </div>
</form>
<form id="form-domoticz" class="pure-form form-settings">
<div class="panel" id="panel-domoticz"> <div class="panel" id="panel-domoticz">
<div class="header"> <div class="header">
@ -964,7 +1000,9 @@
</div> </div>
</div> </div>
</form>
<form id="form-ha" class="pure-form form-settings">
<div class="panel" id="panel-ha"> <div class="panel" id="panel-ha">
<div class="header"> <div class="header">
@ -1001,7 +1039,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Configuration</label> <label class="pure-u-1 pure-u-lg-1-4">Configuration</label>
<div class="pure-u-1-4 pure-u-lg-3-4"><button class="pure-button button-ha-config pure-u-1-3">Show</button></div>
<div class="pure-u-1-4 pure-u-lg-3-4"><button type="button" class="pure-button button-ha-config pure-u-1-3">Show</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
These are the settings you should copy to your Home Assistant "configuration.yaml" file. These are the settings you should copy to your Home Assistant "configuration.yaml" file.
@ -1018,7 +1056,9 @@
</div> </div>
</div> </div>
</form>
<form id="form-thingspeak" class="pure-form form-settings">
<div class="panel" id="panel-thingspeak"> <div class="panel" id="panel-thingspeak">
<div class="header"> <div class="header">
@ -1060,7 +1100,9 @@
</div> </div>
</div> </div>
</form>
<form id="form-idb" class="pure-form form-settings">
<div class="panel" id="panel-idb"> <div class="panel" id="panel-idb">
<div class="header"> <div class="header">
@ -1096,19 +1138,22 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Username</label> <label class="pure-u-1 pure-u-lg-1-4">Username</label>
<input class="pure-u-1 pure-u-lg-3-4" name="idbUser" type="text" tabindex="44" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-3-4" name="idbUser" type="text" tabindex="44" autocomplete="off" />
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Password</label> <label class="pure-u-1 pure-u-lg-1-4">Password</label>
<input class="pure-u-1 pure-u-lg-3-4" name="idbPass" type="password" tabindex="45" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-3-4" name="idbPass" type="password" tabindex="45" autocomplete="new-password" spellcheck="false" />
<span class="no-select password-reveal"></span>
</div> </div>
</fieldset> </fieldset>
</div> </div>
</div> </div>
</form>
<form id="form-dbg" class="pure-form">
<div class="panel" id="panel-dbg"> <div class="panel" id="panel-dbg">
<div class="header"> <div class="header">
@ -1127,12 +1172,12 @@
Write a command and click send to execute it on the device. The output will be shown in the debug text area below. Write a command and click send to execute it on the device. The output will be shown in the debug text area below.
</div> </div>
<input name="dbgcmd" class="pure-u-3-4" type="text" tabindex="2" /> <input name="dbgcmd" class="pure-u-3-4" type="text" tabindex="2" />
<div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-dbgcmd pure-u-23-24">Send</button></div>
<div class="pure-u-1-4 pure-u-lg-1-4"><button type="button" class="pure-button button-dbgcmd pure-u-23-24">Send</button></div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<textarea class="pure-u-1 terminal" id="weblog" name="weblog" wrap="off" readonly></textarea> <textarea class="pure-u-1 terminal" id="weblog" name="weblog" wrap="off" readonly></textarea>
<div class=" pure-u-1-4 pure-u-lg-1-4"><button class="pure-button button-dbg-clear pure-u-23-24">Clear</button></div>
<div class="pure-u-1-4 pure-u-lg-1-4"><button type="button" class="pure-button button-dbg-clear pure-u-23-24">Clear</button></div>
</div> </div>
</fieldset> </fieldset>
@ -1140,8 +1185,10 @@
</div> </div>
</div> </div>
</form>
<!-- removeIf(!sensor) -->
<!-- removeIf(!sensor) -->
<form id="form-sns" class="pure-form form-settings">
<div class="panel" id="panel-sns"> <div class="panel" id="panel-sns">
<div class="header"> <div class="header">
@ -1297,9 +1344,11 @@
</div> </div>
</div> </div>
<!-- endRemoveIf(!sensor) -->
</form>
<!-- endRemoveIf(!sensor) -->
<!-- removeIf(!rfbridge) -->
<!-- removeIf(!rfbridge) -->
<form id="form-rfb" class="pure-form form-settings">
<div class="panel" id="panel-rfb"> <div class="panel" id="panel-rfb">
<div class="header"> <div class="header">
@ -1319,10 +1368,10 @@
<div id="rfbNodes"></div> <div id="rfbNodes"></div>
</fieldset> </fieldset>
</div> </div>
</div>
<!-- endRemoveIf(!rfbridge) -->
</div>
</form> </form>
<!-- endRemoveIf(!rfbridge) -->
</div> <!-- content --> </div> <!-- content -->
@ -1363,7 +1412,8 @@
<div class="pure-u-1-6 pure-u-lg-1-12"><button type="button" class="pure-button button-more-network pure-u-1">...</button></div> <div class="pure-u-1-6 pure-u-lg-1-12"><button type="button" class="pure-button button-more-network pure-u-1">...</button></div>
<label class="pure-u-1 pure-u-lg-1-4 more">Password</label> <label class="pure-u-1 pure-u-lg-1-4 more">Password</label>
<input class="pure-u-1 pure-u-lg-3-4 more" name="wifiPass" type="password" action="reconnect" value="" tabindex="0" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-3-4 more" name="wifiPass" type="password" action="reconnect" value="" tabindex="0" autocomplete="new-password" spellcheck="false" />
<span class="no-select password-reveal more"></span>
<label class="pure-u-1 pure-u-lg-1-4 more">Static IP</label> <label class="pure-u-1 pure-u-lg-1-4 more">Static IP</label>
<input class="pure-u-1 pure-u-lg-3-4 more" name="wifiIP" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" /> <input class="pure-u-1 pure-u-lg-3-4 more" name="wifiIP" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" />
@ -1586,7 +1636,7 @@
<!-- removeIf(!rfm69) --> <!-- removeIf(!rfm69) -->
<div id="nodeTemplate" class="template"> <div id="nodeTemplate" class="template">
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-md-1-6 pure-u-1-2"><input name="node" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Node ID"></div>
<div class="pure-u-md-1-6 pure-u-1-2"><input name="node" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Node ID" autocomplete="false"></div>
<div class="pure-u-md-1-6 pure-u-1-2"><input name="key" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Key"></div> <div class="pure-u-md-1-6 pure-u-1-2"><input name="key" type="text" class="pure-u-11-12" value="" size="8" tabindex="0" placeholder="Key"></div>
<div class="pure-u-md-1-2 pure-u-3-4"><input name="topic" type="text" class="pure-md-11-12 pure-u-23-24" value="" size="8" tabindex="0" placeholder="MQTT Topic"></div> <div class="pure-u-md-1-2 pure-u-3-4"><input name="topic" type="text" class="pure-md-11-12 pure-u-23-24" value="" size="8" tabindex="0" placeholder="MQTT Topic"></div>
<div class="pure-u-md-1-6 pure-u-1-4"><button type="button" class="pure-button button-del-mapping pure-u-5-6 pure-u-md-5-6">Del</button></div> <div class="pure-u-md-1-6 pure-u-1-4"><button type="button" class="pure-button button-del-mapping pure-u-5-6 pure-u-md-5-6">Del</button></div>


BIN
code/html/vendor/images/sort_asc_disabled.png View File

Before After
Width: 19  |  Height: 19  |  Size: 148 B

BIN
code/html/vendor/images/sort_desc_disabled.png View File

Before After
Width: 19  |  Height: 19  |  Size: 146 B

+ 18
- 4
code/ota.py View File

@ -15,6 +15,7 @@ import socket
import subprocess import subprocess
import sys import sys
import time import time
import os
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
@ -232,13 +233,24 @@ def boardname(board):
def store(device, env): def store(device, env):
source = ".pioenvs/%s/firmware.elf" % env source = ".pioenvs/%s/firmware.elf" % env
destination = ".pioenvs/elfs/%s.elf" % boardname(device).lower() destination = ".pioenvs/elfs/%s.elf" % boardname(device).lower()
dst_dir = os.path.dirname(destination)
if not os.path.exists(dst_dir):
os.mkdir(dst_dir)
shutil.move(source, destination) shutil.move(source, destination)
def run(device, env): def run(device, env):
print("Building and flashing image over-the-air...") print("Building and flashing image over-the-air...")
command = "ESPURNA_IP=\"%s\" ESPURNA_BOARD=\"%s\" ESPURNA_AUTH=\"%s\" 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)
environ = os.environ.copy()
environ["ESPURNA_IP"] = device["ip"]
environ["ESPURNA_BOARD"] = device["board"]
environ["ESPURNA_AUTH"] = device["auth"]
environ["ESPURNA_FLAGS"] = device["flags"]
command = ("platformio", "run", "--silent", "--environment", env, "-t", "upload")
subprocess.check_call(command, env=environ)
store(device, env) store(device, env)
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
@ -308,6 +320,8 @@ if __name__ == '__main__':
if len(queue) == 0: if len(queue) == 0:
sys.exit(0) sys.exit(0)
queue = sorted(queue, key=lambda device: device.get('board', ''))
# Flash eash board # Flash eash board
for board in queue: for board in queue:
@ -315,7 +329,7 @@ if __name__ == '__main__':
if args.core > 0: if args.core > 0:
board['flags'] = "-DESPURNA_CORE " + board['flags'] board['flags'] = "-DESPURNA_CORE " + board['flags']
env = "esp8266-%sm-ota" % board['size']
env = "esp8266-%dm-ota" % board['size']
# Summary # Summary
print() print()


+ 1270
- 1737
code/package-lock.json
File diff suppressed because it is too large
View File


+ 5
- 6
code/package.json View File

@ -1,12 +1,11 @@
{ {
"name": "esp8266-filesystem-builder", "name": "esp8266-filesystem-builder",
"version": "0.2.1",
"version": "0.2.2",
"description": "Gulp based build system for ESP8266 file system files", "description": "Gulp based build system for ESP8266 file system files",
"main": "gulpfile.js", "main": "gulpfile.js",
"author": "Xose Pérez <xose.perez@gmail.com>", "author": "Xose Pérez <xose.perez@gmail.com>",
"license": "GPL-3.0", "license": "GPL-3.0",
"devDependencies": { "devDependencies": {
"del": "^2.2.1",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-append-prepend": "^1.0.4", "gulp-append-prepend": "^1.0.4",
"gulp-base64-favicon": "^1.0.2", "gulp-base64-favicon": "^1.0.2",
@ -15,12 +14,12 @@
"gulp-css-base64": "^1.3.4", "gulp-css-base64": "^1.3.4",
"gulp-csslint": "^1.0.0", "gulp-csslint": "^1.0.0",
"gulp-gzip": "^1.4.0", "gulp-gzip": "^1.4.0",
"gulp-htmllint": "0.0.14",
"gulp-htmlmin": "^2.0.0",
"gulp-htmllint": "0.0.16",
"gulp-htmlmin": "^5.0.1",
"gulp-inline": "^0.1.1", "gulp-inline": "^0.1.1",
"gulp-jsonlint": "^1.2.1", "gulp-jsonlint": "^1.2.1",
"gulp-remove-code": "^3.0.2",
"gulp-rename": "^1.3.0",
"gulp-remove-code": "^3.0.4",
"gulp-rename": "^1.4.0",
"gulp-replace": "^1.0.0", "gulp-replace": "^1.0.0",
"gulp-uglify": "^1.5.3", "gulp-uglify": "^1.5.3",
"run-sequence": "^2.2.1", "run-sequence": "^2.2.1",


+ 79
- 2
code/platformio.ini View File

@ -18,6 +18,7 @@ platform_150 = espressif8266@1.5.0
platform_160 = espressif8266@1.6.0 platform_160 = espressif8266@1.6.0
platform_173 = espressif8266@1.7.3 platform_173 = espressif8266@1.7.3
platform_180 = espressif8266@1.8.0 platform_180 = espressif8266@1.8.0
platform_latest = ${common.platform_180}
platform = ${common.platform_150} platform = ${common.platform_150}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -193,7 +194,7 @@ monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:travis02] [env:travis02]
platform = ${common.platform_173}
platform = ${common.platform_latest}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_4m} board = ${common.board_4m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
@ -204,7 +205,7 @@ monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:travis03] [env:travis03]
platform = ${common.platform_173}
platform = ${common.platform_latest}
framework = ${common.framework} framework = ${common.framework}
board = ${common.board_4m} board = ${common.board_4m}
board_build.flash_mode = ${common.flash_mode} board_build.flash_mode = ${common.flash_mode}
@ -1490,6 +1491,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:exs-wifi-relay-v50]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_4m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_4m1m} -DEXS_WIFI_RELAY_V50
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:exs-wifi-relay-v50-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_4m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_4m1m} -DEXS_WIFI_RELAY_V50
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:wemos-v9261f] [env:wemos-v9261f]
platform = ${common.platform} platform = ${common.platform}
framework = ${common.framework} framework = ${common.framework}
@ -2192,6 +2218,32 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:iwoole-led-table-lamp]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DIWOOLE_LED_TABLE_LAMP
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:iwoole-led-table-lamp-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DIWOOLE_LED_TABLE_LAMP
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# GENERIC OTA ENVIRONMENTS # GENERIC OTA ENVIRONMENTS
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -2640,3 +2692,28 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags} upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed} monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts} extra_scripts = ${common.extra_scripts}
[env:bestek-mrj1011]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBESTEK_MRJ1011
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:bestek-mrj1011-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DBESTEK_MRJ1011
upload_speed = ${common.upload_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}

BIN
images/devices/aithinker-ai-light.jpg View File

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

BIN
images/devices/arilux-al-lc01.jpg View File

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

BIN
images/devices/arilux-al-lc06.jpg View File

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

BIN
images/devices/arilux-e27.jpg View File

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

BIN
images/devices/authometion-lyt8266.jpg View File

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

BIN
images/devices/electrodragon-wifi-iot.jpg View File

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

BIN
images/devices/exs-wifi-relay-v31.jpg View File

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

BIN
images/devices/exs-wifi-relay-v50.jpg View File

Before After
Width: 2633  |  Height: 2481  |  Size: 1.3 MiB

BIN
images/devices/geiger_espurna_configuration.png View File

Before After
Width: 2076  |  Height: 1564  |  Size: 247 KiB Width: 2076  |  Height: 1564  |  Size: 123 KiB

BIN
images/devices/geiger_espurna_status.png View File

Before After
Width: 1800  |  Height: 1496  |  Size: 325 KiB Width: 1800  |  Height: 1496  |  Size: 166 KiB

BIN
images/devices/geiger_grafana_dashboard.png View File

Before After
Width: 2414  |  Height: 658  |  Size: 457 KiB Width: 2414  |  Height: 658  |  Size: 263 KiB

BIN
images/devices/geiger_scope_following_pulses.png View File

Before After
Width: 800  |  Height: 480  |  Size: 39 KiB Width: 800  |  Height: 480  |  Size: 14 KiB

BIN
images/devices/geiger_scope_single_pulse.png View File

Before After
Width: 800  |  Height: 480  |  Size: 38 KiB Width: 800  |  Height: 480  |  Size: 13 KiB

BIN
images/devices/geiger_wiring_diagram.png View File

Before After
Width: 2152  |  Height: 864  |  Size: 1.9 MiB Width: 2152  |  Height: 864  |  Size: 1.4 MiB

BIN
images/devices/generic-ag-l4-1.jpg View File

Before After
Width: 960  |  Height: 1280  |  Size: 200 KiB Width: 960  |  Height: 1280  |  Size: 190 KiB

BIN
images/devices/generic-ag-l4-2.jpg View File

Before After
Width: 960  |  Height: 1280  |  Size: 234 KiB Width: 960  |  Height: 1280  |  Size: 221 KiB

BIN
images/devices/generic-ag-l4-3.jpg View File

Before After
Width: 960  |  Height: 1280  |  Size: 232 KiB Width: 960  |  Height: 1280  |  Size: 217 KiB

BIN
images/devices/generic-ag-l4-4.jpg View File

Before After
Width: 960  |  Height: 1280  |  Size: 166 KiB Width: 960  |  Height: 1280  |  Size: 158 KiB

BIN
images/devices/generic-ag-l4-5.jpg View File

Before After
Width: 1280  |  Height: 960  |  Size: 141 KiB Width: 1280  |  Height: 960  |  Size: 134 KiB

BIN
images/devices/generic-geiger-diy.png View File

Before After
Width: 400  |  Height: 400  |  Size: 148 KiB Width: 400  |  Height: 400  |  Size: 129 KiB

BIN
images/devices/generic-relay-40.jpg View File

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

BIN
images/devices/generic-rgbled-10.jpg View File

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

BIN
images/devices/generic-v9261f.jpg View File

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

BIN
images/devices/heygo-hy02.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 8.4 KiB Width: 400  |  Height: 400  |  Size: 8.2 KiB

BIN
images/devices/huacanxing-h801.jpg View File

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

BIN
images/devices/iWoole-led-desk-lamp-module-esp-m2.jpg View File

Before After
Width: 457  |  Height: 730  |  Size: 240 KiB

BIN
images/devices/iWoole-led-desk-lamp-module-front.jpg View File

Before After
Width: 1660  |  Height: 748  |  Size: 320 KiB

BIN
images/devices/iWoole-led-desk-lamp-module-rear.jpg View File

Before After
Width: 1660  |  Height: 754  |  Size: 290 KiB

BIN
images/devices/iWoole-led-desk-lamp-open1.jpg View File

Before After
Width: 807  |  Height: 921  |  Size: 202 KiB

BIN
images/devices/iWoole-led-desk-lamp-open2.jpg View File

Before After
Width: 1660  |  Height: 716  |  Size: 350 KiB

BIN
images/devices/iWoole-led-desk-lamp.jpg View File

Before After
Width: 890  |  Height: 921  |  Size: 44 KiB

BIN
images/devices/intermittech-quinled-2.6.jpg View File

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

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

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

BIN
images/devices/itead-bn-sz01.jpg View File

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

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

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

BIN
images/devices/itead-s20.jpg View File

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

BIN
images/devices/itead-s26.jpg View File

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

BIN
images/devices/itead-slampher.jpg View File

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

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

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

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

Before After
Width: 400  |  Height: 400  |  Size: 9.5 KiB Width: 400  |  Height: 400  |  Size: 9.4 KiB

BIN
images/devices/itead-sonoff-b1.jpg View File

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

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

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

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

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

BIN
images/devices/itead-sonoff-ifan02.jpg View File

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

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

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

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

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

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

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

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

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

BIN
images/devices/itead-sonoff-t1.jpg View File

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

BIN
images/devices/itead-sonoff-th.jpg View File

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

BIN
images/devices/jangoe-wifi-relay.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 38 KiB Width: 400  |  Height: 400  |  Size: 37 KiB

BIN
images/devices/jorgegarcia-wifi-relays.jpg View File

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

BIN
images/devices/kmc-70011.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 9.2 KiB Width: 400  |  Height: 400  |  Size: 8.8 KiB

BIN
images/devices/lingan-swa1.jpg View File

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

BIN
images/devices/lohas-9w.jpg View File

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

BIN
images/devices/magichome-led-controller.jpg View File

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

BIN
images/devices/mancavemade-esp-live.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 9.4 KiB Width: 400  |  Height: 400  |  Size: 8.8 KiB

BIN
images/devices/neo-coolcam-wifi.jpg View File

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

BIN
images/devices/nodemcu-lolin-v3.jpg View File

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

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save