Browse Source

Merge branch 'dev' into sensors

webui
Xose Pérez 5 years ago
parent
commit
fb6eba57cd
109 changed files with 19846 additions and 19532 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. +30
    -4
      code/espurna/config/general.h
  8. +92
    -6
      code/espurna/config/hardware.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. +9
    -4
      code/espurna/homeassistant.ino
  17. +35
    -1
      code/espurna/migrate.ino
  18. +1
    -1
      code/espurna/mqtt.ino
  19. +56
    -16
      code/espurna/relay.ino
  20. +6
    -2
      code/espurna/rfm69.ino
  21. +7
    -3
      code/espurna/sensors/CSE7766Sensor.h
  22. +7
    -2
      code/espurna/settings.ino
  23. +3118
    -3110
      code/espurna/static/index.all.html.gz.h
  24. +2973
    -2965
      code/espurna/static/index.light.html.gz.h
  25. +2578
    -2565
      code/espurna/static/index.rfbridge.html.gz.h
  26. +4055
    -4035
      code/espurna/static/index.rfm69.html.gz.h
  27. +2634
    -2620
      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. +1238
    -1519
      code/package-lock.json
  40. +5
    -7
      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"
- "~/.platformio"
- "$TRAVIS_BUILD_DIR/code/.piolibdeps"
- "$TRAVIS_BUILD_DIR/code/espurna/node_modules"
install:
- pip install -U platformio
- cd code ; npm install --only=dev ; cd ..
- npm install -g npm@latest
- cd code ; npm ci ; cd ..
env:
global:
- 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**|
|![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**|
|![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**|**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)|


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

@ -19,6 +19,7 @@ typedef struct {
api_put_callback_f putFn = NULL;
} web_api_t;
std::vector<web_api_t> _apis;
bool _api_restful = API_RESTFUL;
// -----------------------------------------------------------------------------
@ -31,8 +32,14 @@ void _apiWebSocketOnSend(JsonObject& root) {
root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
root["apiKey"] = getSetting("apiKey");
root["apiRealTime"] = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
root["apiRestFul"] = _api_restful;
}
void _apiConfigure() {
_api_restful = getSetting("apiRestFul", API_RESTFUL).toInt() == 1;
}
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
@ -158,9 +165,11 @@ bool _apiRequestCallback(AsyncWebServerRequest *request) {
// Check if its a PUT
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());
}
}
}
@ -215,6 +224,7 @@ void apiSetup() {
wsOnSendRegister(_apiWebSocketOnSend);
wsOnReceiveRegister(_apiWebSocketOnReceive);
webRequestRegister(_apiRequestCallback);
espurnaRegisterReload(_apiConfigure);
}
#endif // API_SUPPORT

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

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


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

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

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

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


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

@ -148,7 +148,8 @@
// 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
// If not defined the firmware will use a number based
// on the number of available sectors
@ -165,8 +166,17 @@
// 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
#ifndef HEARTBEAT_INTERVAL
@ -226,6 +236,8 @@
#ifndef BUTTON_LNGLNGCLICK_DELAY
#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
//------------------------------------------------------------------------------
@ -458,6 +470,11 @@
#define API_ENABLED 0 // Do not enable API by default
#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
#define API_BUFFER_SIZE 15 // Size of the buffer for HTTP GET API responses
#endif
@ -781,7 +798,7 @@
// -----------------------------------------------------------------------------
#ifndef SETTINGS_AUTOSAVE
#define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit
#define SETTINGS_AUTOSAVE 1 // Autosave settings or force manual commit
#endif
#define SETTINGS_MAX_LIST_COUNT 10 // Maximum index for settings lists
@ -996,6 +1013,11 @@
#define THINGSPEAK_URL "/update"
#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
@ -1330,6 +1352,10 @@
#define RFM69_MAX_TOPICS 50
#endif
#ifndef RFM69_MAX_NODES
#define RFM69_MAX_NODES 255
#endif
#ifndef RFM69_DEFAULT_TOPIC
#define RFM69_DEFAULT_TOPIC "/rfm69gw/{node}/{key}"
#endif


+ 92
- 6
code/espurna/config/hardware.h View File

@ -70,7 +70,7 @@
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
#define BUTTON1_RELAY 1
// Hidden button will enter AP mode if dblclick and reset the device when long-long-clicked
#define RELAY1_PIN 12
@ -1385,6 +1385,36 @@
#define RELAY1_TYPE RELAY_TYPE_LATCHED
#define RELAY1_RESET_PIN 12
// -----------------------------------------------------------------------------
// EX-Store Wifi Relay v5.0
// -----------------------------------------------------------------------------
#elif defined(EXS_WIFI_RELAY_V50)
// Info
#define MANUFACTURER "EXS"
#define DEVICE "WIFI_RELAY_V50"
// Buttons
#define BUTTON1_PIN 5
#define BUTTON2_PIN 4
#define BUTTON1_RELAY 1
#define BUTTON2_RELAY 2
#define BUTTON1_MODE BUTTON_SWITCH | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
#define BUTTON2_MODE BUTTON_SWITCH | BUTTON_DEFAULT_HIGH | BUTTON_SET_PULLUP
// Relays
#define RELAY1_PIN 14
#define RELAY1_TYPE RELAY_TYPE_LATCHED
#define RELAY1_RESET_PIN 16
#define RELAY2_PIN 13
#define RELAY2_TYPE RELAY_TYPE_LATCHED
#define RELAY2_RESET_PIN 12
// LEDs
#define LED1_PIN 15
#define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// V9261F
// -----------------------------------------------------------------------------
@ -1902,7 +1932,7 @@
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
@ -1910,8 +1940,13 @@
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 4
#define LED1_PIN_INVERSE 0
#define LED1_PIN 0 // red
#define LED1_PIN_INVERSE 1
#define LED1_MODE LED_MODE_WIFI
#define LED2_PIN 15 // blue
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_RELAY
// HLW8012
#ifndef HLW8012_SUPPORT
@ -1921,8 +1956,11 @@
#define HLW8012_CF1_PIN 14
#define HLW8012_CF_PIN 5
#define HLW8012_CURRENT_R 0.001 // Current resistor
#define HLW8012_VOLTAGE_R_UP ( 2 * 1200000 ) // Upstream voltage resistor
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 25740
#define HLW8012_VOLTAGE_RATIO 313400
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// TONBUX XS-SSA06
@ -2765,6 +2803,54 @@
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
// -----------------------------------------------------------------------------
// 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
// -----------------------------------------------------------------------------
#elif defined(IWOOLE_LED_TABLE_LAMP)
// Info
#define MANUFACTURER "IWOOLE"
#define DEVICE "LED_TABLE_LAMP"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 12 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 14 // BLUE
#define LIGHT_CH4_PIN 4 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// -----------------------------------------------------------------------------
// 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
// -----------------------------------------------------------------------------
#elif defined(BESTEK_MRJ1011)
// Info
#define MANUFACTURER "BESTEK"
#define DEVICE "MRJ1011"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relay
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LED
#define LED1_PIN 4
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// TEST boards (do not use!!)


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

@ -2,6 +2,7 @@
#include <ArduinoJson.h>
#include <functional>
#include <pgmspace.h>
#include <core_version.h>
extern "C" {
#include "user_interface.h"
@ -35,6 +36,25 @@ extern "C" {
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
// -----------------------------------------------------------------------------


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


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

@ -163,7 +163,7 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
output += type + ":\n";
output += "\n" + type + ":\n";
bool first = true;
for (auto kv : config) {
if (first) {
@ -174,7 +174,6 @@ String _haGetConfig() {
}
output += kv.key + String(": ") + kv.value.as<String>() + String("\n");
}
output += "\n";
jsonBuffer.clear();
@ -188,7 +187,7 @@ String _haGetConfig() {
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
output += "sensor:\n";
output += "\nsensor:\n";
bool first = true;
for (auto kv : config) {
if (first) {
@ -197,7 +196,9 @@ String _haGetConfig() {
} else {
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";
@ -265,10 +266,12 @@ void _haWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& d
#if TERMINAL_SUPPORT
void _haInitCommands() {
settingsRegisterCommand(F("HA.CONFIG"), [](Embedis* e) {
DEBUG_MSG(_haGetConfig().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1");
_haConfigure();
@ -277,6 +280,7 @@ void _haInitCommands() {
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
});
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0");
_haConfigure();
@ -285,6 +289,7 @@ void _haInitCommands() {
#endif
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
#endif


+ 35
- 1
code/espurna/migrate.ino View File

@ -1175,7 +1175,7 @@ void migrate() {
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(XIAOMI_SMART_DESK_LAMP)
#elif defined(PHYX_ESP12_RGB)
setSetting("board", 89);
@ -1189,6 +1189,40 @@ void migrate() {
setSetting("chLogic", 1, 0);
setSetting("chLogic", 3, 0);
#elif defined(IWOOLE_LED_TABLE_LAMP)
setSetting("board", 90);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 0, 12);
setSetting("chGPIO", 1, 5);
setSetting("chGPIO", 2, 14);
setSetting("chGPIO", 3, 4);
setSetting("chLogic", 0, 0);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("relays", 1);
#elif defined(EXS_WIFI_RELAY_V50)
setSetting("board", 91);
setSetting("btnGPIO", 0, 5);
setSetting("btnGPIO", 1, 4);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 14);
setSetting("relayGPIO", 1, 13);
setSetting("relayResetGPIO", 0, 16);
setSetting("relayResetGPIO", 1, 12);
setSetting("relayType", 0, RELAY_TYPE_LATCHED);
setSetting("relayType", 0, RELAY_TYPE_LATCHED);
setSetting("ledGPIO", 1, 15);
setSetting("ledLogic", 1, 0);
#else
// Allow users to define new settings without migration config


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

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


+ 56
- 16
code/espurna/relay.ino View File

@ -173,11 +173,19 @@ void _relayProcess(bool mode) {
#endif
if (!_relayRecursive) {
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
wsSend(_relayWebSocketUpdate);
#endif
}
#if DOMOTICZ_SUPPORT
@ -383,16 +391,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 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;
bit += bit;
}
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) {
@ -474,27 +507,34 @@ void _relayBoot() {
DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
// Walk the relays
bool status = false;
bool status;
for (unsigned int i=0; i<_relays.size(); i++) {
unsigned char boot_mode = getSetting("relayBoot", i, RELAY_BOOT_MODE).toInt();
DEBUG_MSG_P(PSTR("[RELAY] Relay #%d boot mode %d\n"), i, boot_mode);
status = false;
switch (boot_mode) {
case RELAY_BOOT_SAME:
status = ((mask & bit) == bit);
if (i < 8) {
status = ((mask & bit) == bit);
}
break;
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;
case RELAY_BOOT_ON:
status = true;
break;
case RELAY_BOOT_OFF:
default:
status = false;
break;
}
_relays[i].current_status = !status;
_relays[i].target_status = status;
#if RELAY_PROVIDER == RELAY_PROVIDER_STM
@ -508,7 +548,7 @@ void _relayBoot() {
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) {
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.commit();
saveSettings();
}
_relayRecursive = false;
@ -766,7 +806,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_CONNECT_EVENT) {
// Send status on connect
#if not HEARTBEAT_REPORT_RELAY
#if (HEARTBEAT_MODE == HEARTBEAT_NONE) or (not HEARTBEAT_REPORT_RELAY)
relayMQTT();
#endif
@ -963,10 +1003,10 @@ void relaySetup() {
// Sonoff Dual and Sonoff RF Bridge
#if DUMMY_RELAY_COUNT > 0
unsigned int _delay_on[8] = {RELAY1_DELAY_ON, RELAY2_DELAY_ON, RELAY3_DELAY_ON, RELAY4_DELAY_ON, RELAY5_DELAY_ON, RELAY6_DELAY_ON, RELAY7_DELAY_ON, RELAY8_DELAY_ON};
unsigned int _delay_off[8] = {RELAY1_DELAY_OFF, RELAY2_DELAY_OFF, RELAY3_DELAY_OFF, RELAY4_DELAY_OFF, RELAY5_DELAY_OFF, RELAY6_DELAY_OFF, RELAY7_DELAY_OFF, RELAY8_DELAY_OFF};
// No delay_on or off for these devices to easily allow having more than
// 8 channels. This behaviour will be recovered with v2.
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL,0,_delay_on[i], _delay_off[i]});
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL, 0, 0, 0});
}
#else


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

@ -25,7 +25,7 @@ struct _node_t {
unsigned char lastPacketID = 0;
};
_node_t _rfm69_node_info[255];
_node_t _rfm69_node_info[RFM69_MAX_NODES];
unsigned char _rfm69_node_count;
unsigned long _rfm69_packet_count;
@ -115,6 +115,9 @@ void _rfm69Debug(const char * level, 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
if (_rfm69_node_info[data->senderID].count == 0) ++_rfm69_node_count;
++_rfm69_packet_count;
@ -235,9 +238,10 @@ void _rfm69Loop() {
}
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].missing = 0;
_rfm69_node_info[i].count = 0;
}
_rfm69_node_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() {
_count = 4;
_count = 6;
_sensor_id = SENSOR_CSE7766_ID;
}
@ -161,7 +161,9 @@ class CSE7766Sensor : public BaseSensor {
if (index == 0) return MAGNITUDE_CURRENT;
if (index == 1) return MAGNITUDE_VOLTAGE;
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;
}
@ -170,7 +172,9 @@ class CSE7766Sensor : public BaseSensor {
if (index == 0) return _current;
if (index == 1) return _voltage;
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;
}


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

@ -308,6 +308,13 @@ void _settingsInitCommands() {
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
}
// -----------------------------------------------------------------------------
@ -443,8 +450,6 @@ void settingsRegisterCommand(const String& name, void (*call)(Embedis*)) {
void settingsSetup() {
EEPROMr.begin(SPI_FLASH_SEC_SIZE);
_serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT
telnetWrite(ch);


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


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


+ 2578
- 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


+ 2634
- 2620
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

@ -89,15 +89,19 @@ void systemLoop() {
// 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;
if (_system_send_heartbeat || (last_hbeat == 0) || (millis() - last_hbeat > HEARTBEAT_INTERVAL)) {
_system_send_heartbeat = false;
last_hbeat = millis();
heartbeat();
}
#endif // HEARTBEAT_ENABLED
#endif // HEARTBEAT_MODE == HEARTBEAT_REPEAT
// -------------------------------------------------------------------------
// Load Average calculation


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

@ -25,10 +25,11 @@ const char THINGSPEAK_REQUEST_TEMPLATE[] PROGMEM =
"%s\r\n";
bool _tspk_enabled = false;
char * _tspk_queue[8] = {NULL};
char * _tspk_queue[THINGSPEAK_FIELDS] = {NULL};
bool _tspk_flush = false;
unsigned long _tspk_last_flush = 0;
unsigned char _tspk_tries = 0;
// -----------------------------------------------------------------------------
@ -97,12 +98,23 @@ void _tspkPost(String data) {
}, 0);
_tspk_client->onData([](void * arg, AsyncClient * c, void * response, size_t len) {
char * b = (char *) response;
b[len] = 0;
char * p = strstr((char *)response, "\r\n\r\n");
unsigned int code = (p != NULL) ? atoi(&p[4]) : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_tspk_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);
}, NULL);
_tspk_client->onConnect([data](void * arg, AsyncClient * client) {
@ -118,7 +130,7 @@ void _tspkPost(String data) {
}
#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()];
snprintf_P(buffer, sizeof(buffer),
@ -164,7 +176,7 @@ void _tspkPost(String data) {
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()];
snprintf_P(buffer, sizeof(buffer),
THINGSPEAK_REQUEST_TEMPLATE,
@ -182,6 +194,15 @@ void _tspkPost(String data) {
unsigned int code = (pos > 0) ? response.substring(pos + 4).toInt() : 0;
DEBUG_MSG_P(PSTR("[THINGSPEAK] Response value: %d\n"), code);
_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;
}
@ -199,25 +220,33 @@ void _tspkEnqueue(unsigned char index, char * 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() {
String data;
_tspk_flush = false;
// 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 (data.length() > 0) data = data + String("&");
data = data + String("field") + String(id+1) + String("=") + String(_tspk_queue[id]);
free(_tspk_queue[id]);
_tspk_queue[id] = NULL;
}
}
// POST data if any
if (data.length() > 0) {
data = data + String("&api_key=") + getSetting("tspkKey");
_tspk_tries = THINGSPEAK_TRIES;
_tspkPost(data);
_tspk_last_flush = millis();
}
}
@ -278,7 +307,6 @@ void tspkLoop() {
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return;
if (_tspk_flush && (millis() - _tspk_last_flush > THINGSPEAK_MIN_INTERVAL)) {
_tspkFlush();
_tspk_flush = false;
}
}


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

@ -6,11 +6,6 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/
extern "C" {
#include <cont.h>
extern cont_t g_cont;
}
#include <Ticker.h>
Ticker _defer_reset;
@ -81,10 +76,6 @@ unsigned int getUsedHeap() {
return getInitialFreeHeap() - getFreeHeap();
}
unsigned int getFreeStack() {
return cont_get_free_stack(&g_cont);
}
String getEspurnaModules() {
return FPSTR(espurna_modules);
}
@ -142,7 +133,7 @@ unsigned long getUptime() {
}
#if HEARTBEAT_ENABLED
#if HEARTBEAT_MODE != HEARTBEAT_NONE
void heartbeat() {
@ -244,7 +235,7 @@ void heartbeat() {
}
#endif /// HEARTBEAT_ENABLED
#endif /// HEARTBEAT_MODE != HEARTBEAT_NONE
// -----------------------------------------------------------------------------
// INFO
@ -285,7 +276,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) {
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,
total_memory,
total_memory - free_memory,
@ -342,12 +333,12 @@ void info() {
FSInfo fs_info;
bool fs = SPIFFS.info(fs_info);
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 {
DEBUG_MSG_P(PSTR("[MAIN] No SPIFFS partition\n"));
}
@ -477,13 +468,13 @@ void resetReason(unsigned char reason) {
EEPROMr.commit();
}
void reset(unsigned char reason) {
resetReason(reason);
void reset() {
ESP.restart();
}
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 htmlmin = require('gulp-htmlmin');
const uglify = require('gulp-uglify');
const inline = require('gulp-inline');
const inlineImages = require('gulp-css-base64');
const favicon = require('gulp-base64-favicon');


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

@ -50,10 +50,6 @@ h2 {
display: block;
}
.content {
margin: 0;
}
.page {
margin-top: 10px;
}
@ -98,16 +94,12 @@ div.center {
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 {
@ -206,6 +198,10 @@ div.state {
background: rgb(255, 128, 0); /* orange */
}
.button-generate-password {
background: rgb(66, 184, 221); /* blue */
}
.button-upgrade-browse,
.button-clear-filters,
.button-clear-messages,
@ -448,3 +444,53 @@ table.dataTable.display tbody td {
height: 400px;
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

@ -148,8 +148,7 @@ function loadTimeZones() {
}
function validateForm(form) {
function validatePassword(password) {
// http://www.the-art-of-web.com/javascript/validate-password/
// at least one lowercase and one uppercase letter or number
// at least eight characters (letters, numbers or special characters)
@ -157,21 +156,45 @@ function validateForm(form) {
// MUST be 8..63 printable ASCII characters. See:
// https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Target_users_(authentication_key_distribution)
// https://github.com/xoseperez/espurna/issues/1151
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!");
return false;
}
var adminPass2 = $("input[name='adminPass']", form).last().val();
if (adminPass1 !== adminPass2) {
alert("Passwords are different!");
return false;
}
return false;
}
function validateFormHostname(form) {
// RFCs mandate that a hostname's labels may contain only
// the ASCII letters 'a' through 'z' (case-insensitive),
// the digits '0' through '9', and the hyphen.
@ -184,18 +207,21 @@ function validateForm(form) {
var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,30}[A-Za-z0-9]$');
var hostname = $("input[name='hostname']", form);
var hasChanged = ("true" === hostname.attr("hasChanged"));
if (!hasChanged) {
if ("true" !== hostname.attr("hasChanged")) {
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) {
@ -227,6 +253,12 @@ function addValue(data, name, value) {
"node", "key", "topic"
];
// join both adminPass 1 and 2
if (name.startsWith("adminPass")) {
name = "adminPass";
}
if (name in data) {
if (!Array.isArray(data[name])) {
data[name] = [data[name]];
@ -262,26 +294,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() {
var apikey = randomString(16, "@#");
var apikey = randomString(16, {hex: true});
$("input[name='apiKey']").val(apikey);
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) {
try {
return JSON.parse(str);
@ -305,7 +378,7 @@ function sendConfig(data) {
function setOriginalsFromValues(force) {
var force = (true === force);
$("input,select").each(function() {
var initial = (null === $(this).attr("original"));
var initial = (undefined === $(this).attr("original"));
if (force || initial) {
$(this).attr("original", $(this).val());
}
@ -424,7 +497,7 @@ function doUpgrade() {
function doUpdatePassword() {
var form = $("#formPassword");
if (validateForm(form)) {
if (validateFormPasswords(form)) {
sendConfig(getData(form));
}
return false;
@ -478,11 +551,11 @@ function doReconnect(ask) {
function doUpdate() {
var form = $("#formSave");
if (validateForm(form)) {
var forms = $(".form-settings");
if (validateForm(forms)) {
// Get data
sendConfig(getData(form));
sendConfig(getData(forms));
// Empty special fields
$(".pwrExpected").val(0);
@ -756,6 +829,7 @@ function addNetwork() {
$(this).attr("tabindex", tabindex);
tabindex++;
});
$(".password-reveal", line).on("click", toggleVisiblePassword);
$(line).find(".button-del-network").on("click", delNetwork);
$(line).find(".button-more-network").on("click", moreNetwork);
line.appendTo("#networks");
@ -1578,12 +1652,15 @@ $(function() {
createCheckboxes();
setInterval(function() { keepTime(); }, 1000);
$(".password-reveal").on("click", toggleVisiblePassword);
$("#menuLink").on("click", toggleMenu);
$(".pure-menu-link").on("click", showPanel);
$("progress").attr({ value: 0, max: 100 });
$(".button-update").on("click", doUpdate);
$(".button-update-password").on("click", doUpdatePassword);
$(".button-generate-password").on("click", doGeneratePassword);
$(".button-reboot").on("click", doReboot);
$(".button-reconnect").on("click", doReconnect);
$(".button-wifi-scan").on("click", doScan);


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

@ -28,40 +28,45 @@
<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">
<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 class="page">
<fieldset>
<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 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>
</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>
</form>
</div> <!-- content -->
@ -309,8 +314,7 @@
</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="header">
@ -382,7 +386,9 @@
</fieldset>
</div>
</div>
</form>
<form id="form-relay" class="pure-form form-settings">
<div class="panel" id="panel-relay">
<div class="header">
@ -414,8 +420,10 @@
</div>
</div>
</form>
<!-- removeIf(!light) -->
<!-- removeIf(!light) -->
<form id="form-color" class="pure-form form-settings">
<div class="panel" id="panel-color">
<div class="header">
@ -501,8 +509,10 @@
</fieldset>
</div>
</div>
<!-- endRemoveIf(!light) -->
</form>
<!-- endRemoveIf(!light) -->
<form id="form-admin" class="pure-form form-settings">
<div class="panel" id="panel-admin">
<div class="header">
@ -516,25 +526,25 @@
<div class="pure-g">
<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 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" 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-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>
</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">
<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" />
@ -556,6 +566,17 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="apiEnabled" /></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">
<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>
@ -570,7 +591,7 @@
<div class="pure-g module module-api">
<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" />
<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-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.
@ -603,8 +624,8 @@
<div class="pure-g">
<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 />
<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-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>
@ -615,7 +636,9 @@
</fieldset>
</div>
</div>
</form>
<form id="form-wifi" class="pure-form form-settings">
<div class="panel" id="panel-wifi">
<div class="header">
@ -657,7 +680,9 @@
</fieldset>
</div>
</div>
</form>
<form id="form-schedule" class="pure-form form-settings">
<div class="panel" id="panel-schedule">
<div class="header">
@ -681,8 +706,10 @@
</div>
</div>
</form>
<!-- removeIf(!rfm69) -->
<!-- removeIf(!rfm69) -->
<form id="form-mapping" class="pure-form form-settings">
<div class="panel" id="panel-mapping">
<div class="header">
@ -711,10 +738,12 @@
</fieldset>
</div>
</div>
</form>
<form id="form-messages" class="pure-form">
<div class="panel" id="panel-messages">
<div class="header">
<h1>MESSAGES</h1>
<h2>
@ -746,15 +775,17 @@
</tbody>
</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>
<!-- endRemoveIf(!rfm69) -->
</form>
<!-- endRemoveIf(!rfm69) -->
<form id="form-mqtt" class="pure-form form-settings">
<div class="panel" id="panel-mqtt">
<div class="header">
@ -783,12 +814,13 @@
<div class="pure-g">
<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 class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">MQTT Password</label>
<input class="pure-u-1 pure-u-lg-1-4" name="mqttPassword" type="password" tabindex="24" placeholder="Leave blank if no pass" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-1-4" name="mqttPassword" type="password" tabindex="24" placeholder="Leave blank if no pass" autocomplete="new-password" spellcheck="false" />
<span class="no-select password-reveal"></span>
</div>
<div class="pure-g">
@ -873,7 +905,9 @@
</div>
</div>
</form>
<form id="form-ntp" class="pure-form form-settings">
<div class="panel" id="panel-ntp">
<div class="header">
@ -917,7 +951,9 @@
</div>
</div>
</form>
<form id="form-domoticz" class="pure-form form-settings">
<div class="panel" id="panel-domoticz">
<div class="header">
@ -964,7 +1000,9 @@
</div>
</div>
</form>
<form id="form-ha" class="pure-form form-settings">
<div class="panel" id="panel-ha">
<div class="header">
@ -1001,7 +1039,7 @@
<div class="pure-g">
<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-1 pure-u-lg-3-4 hint">
These are the settings you should copy to your Home Assistant "configuration.yaml" file.
@ -1018,7 +1056,9 @@
</div>
</div>
</form>
<form id="form-thingspeak" class="pure-form form-settings">
<div class="panel" id="panel-thingspeak">
<div class="header">
@ -1060,7 +1100,9 @@
</div>
</div>
</form>
<form id="form-idb" class="pure-form form-settings">
<div class="panel" id="panel-idb">
<div class="header">
@ -1096,19 +1138,22 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Username</label>
<input class="pure-u-1 pure-u-lg-3-4" name="idbUsername" type="text" tabindex="44" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-3-4" name="idbUsername" type="text" tabindex="44" autocomplete="off" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Password</label>
<input class="pure-u-1 pure-u-lg-3-4" name="idbPassword" type="password" tabindex="45" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-3-4" name="idbPassword" type="password" tabindex="45" autocomplete="new-password" spellcheck="false" />
<span class="no-select password-reveal"></span>
</div>
</fieldset>
</div>
</div>
</form>
<form id="form-dbg" class="pure-form">
<div class="panel" id="panel-dbg">
<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.
</div>
<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 class="pure-g">
<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>
</fieldset>
@ -1140,8 +1185,10 @@
</div>
</div>
</form>
<!-- removeIf(!sensor) -->
<!-- removeIf(!sensor) -->
<form id="form-sns" class="pure-form form-settings">
<div class="panel" id="panel-sns">
<div class="header">
@ -1305,9 +1352,11 @@
</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="header">
@ -1327,10 +1376,10 @@
<div id="rfbNodes"></div>
</fieldset>
</div>
</div>
<!-- endRemoveIf(!rfbridge) -->
</div>
</form>
<!-- endRemoveIf(!rfbridge) -->
</div> <!-- content -->
@ -1371,7 +1420,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>
<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="pass" type="password" action="reconnect" value="" tabindex="0" autocomplete="false" />
<input class="pure-u-1 pure-u-lg-3-4 more" name="pass" 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>
<input class="pure-u-1 pure-u-lg-3-4 more" name="ip" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" />
@ -1594,7 +1644,7 @@
<!-- removeIf(!rfm69) -->
<div id="nodeTemplate" class="template">
<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-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>


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 sys
import time
import os
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
@ -232,13 +233,24 @@ def boardname(board):
def store(device, env):
source = ".pioenvs/%s/firmware.elf" % env
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)
def run(device, env):
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)
# -------------------------------------------------------------------------------
@ -308,6 +320,8 @@ if __name__ == '__main__':
if len(queue) == 0:
sys.exit(0)
queue = sorted(queue, key=lambda device: device.get('board', ''))
# Flash eash board
for board in queue:
@ -315,7 +329,7 @@ if __name__ == '__main__':
if args.core > 0:
board['flags'] = "-DESPURNA_CORE " + board['flags']
env = "esp8266-%sm-ota" % board['size']
env = "esp8266-%dm-ota" % board['size']
# Summary
print()


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


+ 5
- 7
code/package.json View File

@ -1,25 +1,23 @@
{
"name": "esp8266-filesystem-builder",
"version": "0.2.1",
"version": "0.2.2",
"description": "Gulp based build system for ESP8266 file system files",
"main": "gulpfile.js",
"author": "Xose Pérez <xose.perez@gmail.com>",
"license": "GPL-3.0",
"devDependencies": {
"del": "^2.2.1",
"gulp": "^3.9.1",
"gulp-base64-favicon": "^1.0.2",
"gulp-crass": "^0.2.2",
"gulp-css-base64": "^1.3.4",
"gulp-csslint": "^1.0.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-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-uglify": "^1.5.3",
"map-stream": "0.0.7",
"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_173 = espressif8266@1.7.3
platform_180 = espressif8266@1.8.0
platform_latest = ${common.platform_180}
platform = ${common.platform_150}
# ------------------------------------------------------------------------------
@ -193,7 +194,7 @@ monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:travis02]
platform = ${common.platform_173}
platform = ${common.platform_latest}
framework = ${common.framework}
board = ${common.board_4m}
board_build.flash_mode = ${common.flash_mode}
@ -204,7 +205,7 @@ monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:travis03]
platform = ${common.platform_173}
platform = ${common.platform_latest}
framework = ${common.framework}
board = ${common.board_4m}
board_build.flash_mode = ${common.flash_mode}
@ -1500,6 +1501,31 @@ upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
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]
platform = ${common.platform}
framework = ${common.framework}
@ -2202,6 +2228,32 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
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
# ------------------------------------------------------------------------------
@ -2650,3 +2702,28 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
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