Browse Source

Merge branch 'dev' into dev

ota
Xose Pérez 6 years ago
committed by GitHub
parent
commit
56921af517
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 1614 additions and 1659 deletions
  1. +2
    -2
      .travis.yml
  2. +2
    -2
      README.md
  3. +5
    -0
      code/espurna/config/general.h
  4. +24
    -0
      code/espurna/config/hardware.h
  5. +19
    -0
      code/espurna/config/prototypes.h
  6. +1
    -1
      code/espurna/homeassistant.ino
  7. +1
    -1
      code/espurna/migrate.ino
  8. +7
    -3
      code/espurna/sensors/CSE7766Sensor.h
  9. +37
    -9
      code/espurna/thinkspeak.ino
  10. +0
    -25
      code/espurna/utils.ino
  11. +0
    -1
      code/gulpfile.js
  12. +60
    -14
      code/html/custom.css
  13. +79
    -19
      code/html/custom.js
  14. +91
    -52
      code/html/index.html
  15. BIN
      code/html/vendor/images/sort_asc_disabled.png
  16. BIN
      code/html/vendor/images/sort_desc_disabled.png
  17. +18
    -4
      code/ota.py
  18. +1238
    -1519
      code/package-lock.json
  19. +5
    -7
      code/package.json
  20. +25
    -0
      code/platformio.ini
  21. BIN
      images/devices/aithinker-ai-light.jpg
  22. BIN
      images/devices/arilux-al-lc01.jpg
  23. BIN
      images/devices/arilux-al-lc06.jpg
  24. BIN
      images/devices/arilux-e27.jpg
  25. BIN
      images/devices/authometion-lyt8266.jpg
  26. BIN
      images/devices/electrodragon-wifi-iot.jpg
  27. BIN
      images/devices/exs-wifi-relay-v31.jpg
  28. BIN
      images/devices/geiger_espurna_configuration.png
  29. BIN
      images/devices/geiger_espurna_status.png
  30. BIN
      images/devices/geiger_grafana_dashboard.png
  31. BIN
      images/devices/geiger_scope_following_pulses.png
  32. BIN
      images/devices/geiger_scope_single_pulse.png
  33. BIN
      images/devices/geiger_wiring_diagram.png
  34. BIN
      images/devices/generic-ag-l4-1.jpg
  35. BIN
      images/devices/generic-ag-l4-2.jpg
  36. BIN
      images/devices/generic-ag-l4-3.jpg
  37. BIN
      images/devices/generic-ag-l4-4.jpg
  38. BIN
      images/devices/generic-ag-l4-5.jpg
  39. BIN
      images/devices/generic-geiger-diy.png
  40. BIN
      images/devices/generic-relay-40.jpg
  41. BIN
      images/devices/generic-rgbled-10.jpg
  42. BIN
      images/devices/generic-v9261f.jpg
  43. BIN
      images/devices/heygo-hy02.jpg
  44. BIN
      images/devices/huacanxing-h801.jpg
  45. BIN
      images/devices/intermittech-quinled-2.6.jpg
  46. BIN
      images/devices/itead-1ch-inching.jpg
  47. BIN
      images/devices/itead-bn-sz01.jpg
  48. BIN
      images/devices/itead-motor.jpg
  49. BIN
      images/devices/itead-s20.jpg
  50. BIN
      images/devices/itead-s26.jpg
  51. BIN
      images/devices/itead-slampher.jpg
  52. BIN
      images/devices/itead-sonoff-4ch-pro.jpg
  53. BIN
      images/devices/itead-sonoff-4ch.jpg
  54. BIN
      images/devices/itead-sonoff-b1.jpg
  55. BIN
      images/devices/itead-sonoff-basic.jpg
  56. BIN
      images/devices/itead-sonoff-dual.jpg
  57. BIN
      images/devices/itead-sonoff-ifan02.jpg
  58. BIN
      images/devices/itead-sonoff-led.jpg
  59. BIN
      images/devices/itead-sonoff-pow.jpg
  60. BIN
      images/devices/itead-sonoff-rf.jpg
  61. BIN
      images/devices/itead-sonoff-sv.jpg
  62. BIN
      images/devices/itead-sonoff-t1.jpg
  63. BIN
      images/devices/itead-sonoff-th.jpg
  64. BIN
      images/devices/jangoe-wifi-relay.jpg
  65. BIN
      images/devices/jorgegarcia-wifi-relays.jpg
  66. BIN
      images/devices/kmc-70011.jpg
  67. BIN
      images/devices/lingan-swa1.jpg
  68. BIN
      images/devices/lohas-9w.jpg
  69. BIN
      images/devices/magichome-led-controller.jpg
  70. BIN
      images/devices/mancavemade-esp-live.jpg
  71. BIN
      images/devices/neo-coolcam-wifi.jpg
  72. BIN
      images/devices/nodemcu-lolin-v3.jpg
  73. BIN
      images/devices/openenergymonitor-mqtt-relay.jpg
  74. BIN
      images/devices/schuko-wifi-plug.jpg
  75. BIN
      images/devices/tinkerman-espurna-h.jpg
  76. BIN
      images/devices/tonbux-powerstrip02.jpg
  77. BIN
      images/devices/wemos-d1-mini-relayshield.jpg
  78. BIN
      images/devices/wion-50055.jpg
  79. BIN
      images/devices/witty-cloud.jpg
  80. BIN
      images/devices/workchoice-ecoplug.jpg
  81. BIN
      images/devices/xenon-sm-pw702u.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

@ -4,8 +4,8 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smar
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries. It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.13.3a-brightgreen.svg)](CHANGELOG.md) [![version](https://img.shields.io/badge/version-1.13.3a-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.com/xoseperez/espurna.git/tree/dev/)
[![travis](https://travis-ci.org/xoseperez/espurna.git.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna.git)
[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.com/xoseperez/espurna/tree/dev/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard) [![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE) [![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
<br /> <br />


+ 5
- 0
code/espurna/config/general.h View File

@ -996,6 +996,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


+ 24
- 0
code/espurna/config/hardware.h View File

@ -2790,6 +2790,30 @@
#define LIGHT_CH3_INVERSE 0 #define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_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!!) // TEST boards (do not use!!)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


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

@ -36,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
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


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

@ -36,7 +36,7 @@ void _haSendMagnitude(unsigned char i, JsonObject& config) {
config["name"] = _haFixName(getSetting("hostname") + String(" ") + magnitudeTopic(type)); config["name"] = _haFixName(getSetting("hostname") + String(" ") + magnitudeTopic(type));
config.set("platform", "mqtt"); config.set("platform", "mqtt");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false); config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type);
config["unit_of_measurement"] = "\"" + magnitudeUnits(type) + "\"";
} }


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

@ -1175,7 +1175,7 @@ void migrate() {
setSetting("relayType", 0, RELAY_TYPE_NORMAL); setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL); setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(XIAOMI_SMART_DESK_LAMP)
#elif defined(PHYX_ESP12_RGB)
setSetting("board", 89); setSetting("board", 89);


+ 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;
} }


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

@ -25,10 +25,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;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -97,12 +98,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) {
@ -118,7 +130,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),
@ -164,7 +176,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,
@ -182,6 +194,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;
} }
@ -199,25 +220,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();
} }
} }
@ -278,7 +307,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;
} }
} }


+ 0
- 25
code/espurna/utils.ino View File

@ -6,31 +6,6 @@ Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/ */
// Core version 2.4.2 and higher changed the cont_t structure to a pointer:
// https://github.com/esp8266/Arduino/commit/5d5ea92a4d004ab009d5f642629946a0cb8893dd#diff-3fa12668b289ccb95b7ab334833a4ba8L35
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) \
|| defined(ARDUINO_ESP8266_RELEASE_2_4_0) \
|| defined(ARDUINO_ESP8266_RELEASE_2_4_1)
extern "C" {
#include <cont.h>
extern cont_t g_cont;
}
unsigned int getFreeStack() {
return cont_get_free_stack(&g_cont);
}
#else
extern "C" {
#include <cont.h>
extern cont_t* g_pcont;
}
unsigned int getFreeStack() {
return cont_get_free_stack(g_pcont);
}
#endif // defined(ARDUINO_ESP8266_RELEASE_2_3_0/2_4_0/2_4_1)
#include <Ticker.h> #include <Ticker.h>
Ticker _defer_reset; Ticker _defer_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;
.content #password {
margin: 0 auto;
}
.content #layout {
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;
}

+ 79
- 19
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,16 +154,26 @@ 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 validateForm(form) {
// password // password
var adminPass1 = $("input[name='adminPass']", form).first().val(); var adminPass1 = $("input[name='adminPass']", form).first().val();
if (adminPass1.length > 0 && !re_password.test(adminPass1)) {
if (!validatePassword(adminPass1)) {
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; return false;
} }
var adminPass2 = $("input[name='adminPass']", form).last().val();
var adminPass2 = $("input[name='adminPass_confirm']", form).last().val();
if (adminPass1 !== adminPass2) { if (adminPass1 !== adminPass2) {
alert("Passwords are different!"); alert("Passwords are different!");
return false; return false;
@ -225,6 +234,12 @@ function addValue(data, name, value) {
"node", "key", "topic" "node", "key", "topic"
]; ];
// join both adminPass and ..._confirm
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 +275,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);
@ -476,11 +532,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 +809,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 +1632,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);


+ 91
- 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">
<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="adminPass">New Password</label>
<input class="pure-u-1 pure-u-lg-3-4" name="adminPass" maxlength="63" type="password" tabindex="1" autocomplete="false" spellcheck="false" />
<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="adminPass_confirm">Repeat password</label>
<input class="pure-u-1 pure-u-lg-3-4" name="adminPass_confirm" type="password" tabindex="2" autocomplete="false" spellcheck="false" />
<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" 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="adminPass" class="pure-u-1 pure-u-lg-3-4" placeholder="New password" 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="adminPass_confirm" class="pure-u-1 pure-u-lg-3-4" placeholder="Repeat password" 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" />
@ -570,7 +580,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 +613,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 +625,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 +669,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 +695,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 +727,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 +764,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 +803,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="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>
<div class="pure-g"> <div class="pure-g">
@ -873,7 +894,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 +940,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 +989,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 +1028,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 +1045,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 +1089,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 +1127,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="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>
<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="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> </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 +1161,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 +1174,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 +1333,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 +1357,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 +1401,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="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> <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" /> <input class="pure-u-1 pure-u-lg-3-4 more" name="ip" type="text" action="reconnect" value="" maxlength="15" tabindex="0" autocomplete="false" />
@ -1586,7 +1625,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()


+ 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", "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-base64-favicon": "^1.0.2", "gulp-base64-favicon": "^1.0.2",
"gulp-crass": "^0.2.2", "gulp-crass": "^0.2.2",
"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-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",
"map-stream": "0.0.7", "map-stream": "0.0.7",
"run-sequence": "^2.2.1" "run-sequence": "^2.2.1"
} }


+ 25
- 0
code/platformio.ini View File

@ -2677,3 +2677,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/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/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

BIN
images/devices/openenergymonitor-mqtt-relay.jpg View File

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

BIN
images/devices/schuko-wifi-plug.jpg View File

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

BIN
images/devices/tinkerman-espurna-h.jpg View File

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

BIN
images/devices/tonbux-powerstrip02.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 6.2 KiB Width: 400  |  Height: 400  |  Size: 6.1 KiB

BIN
images/devices/wemos-d1-mini-relayshield.jpg View File

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

BIN
images/devices/wion-50055.jpg View File

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

BIN
images/devices/witty-cloud.jpg View File

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

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

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

BIN
images/devices/xenon-sm-pw702u.jpg View File

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

Loading…
Cancel
Save