Browse Source

Merge branch 'dev' into light_ir

Conflicts:
	code/espurna/config/hardware.h
fastled
Xose Pérez 7 years ago
parent
commit
bded412de9
39 changed files with 4412 additions and 3897 deletions
  1. +35
    -0
      CHANGELOG.md
  2. +3
    -1
      README.md
  3. +5
    -4
      code/.gitignore
  4. +2
    -1
      code/build.sh
  5. +3
    -1
      code/espurna/config/arduino.h
  6. +23
    -13
      code/espurna/config/general.h
  7. +54
    -9
      code/espurna/config/hardware.h
  8. +1
    -1
      code/espurna/config/version.h
  9. BIN
      code/espurna/data/index.html.gz
  10. +129
    -21
      code/espurna/dht.ino
  11. +6
    -0
      code/espurna/espurna.ino
  12. +27
    -1
      code/espurna/hardware.ino
  13. +39
    -31
      code/espurna/homeassitant.ino
  14. +42
    -7
      code/espurna/light.ino
  15. +1
    -1
      code/espurna/ntp.ino
  16. +25
    -22
      code/espurna/power.h
  17. +65
    -42
      code/espurna/power.ino
  18. +1
    -3
      code/espurna/power_emon.ino
  19. +1
    -1
      code/espurna/power_hlw8012.ino
  20. +3461
    -3563
      code/espurna/static/index.html.gz.h
  21. +13
    -3
      code/espurna/telnet.ino
  22. +29
    -11
      code/espurna/web.ino
  23. +13
    -2
      code/espurna/wifi.ino
  24. +3
    -7
      code/html/custom.css
  25. +214
    -94
      code/html/custom.js
  26. +7
    -0
      code/html/grids-responsive-1.0.0.min.css
  27. +110
    -36
      code/html/index.html
  28. +0
    -5
      code/html/jquery-1.12.3.min.js
  29. +4
    -0
      code/html/jquery-3.2.1.min.js
  30. +2
    -0
      code/html/jquery.wheelcolorpicker-3.0.3.css
  31. +2
    -2
      code/html/jquery.wheelcolorpicker-3.0.3.min.js
  32. +1
    -1
      code/html/nouislider-10.1.0.min.css
  33. +3
    -0
      code/html/nouislider-10.1.0.min.js
  34. +0
    -3
      code/html/nouislider.min.js
  35. +11
    -0
      code/html/pure-1.0.0.min.css
  36. +0
    -1
      code/html/pure-min.css
  37. +1
    -1
      code/html/side-menu.css
  38. +1
    -1
      code/ota_flash.sh
  39. +75
    -8
      code/platformio.ini

+ 35
- 0
CHANGELOG.md View File

@ -3,6 +3,41 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.9.7] 2017-10-25
### Fixed
- Fix Alexa interface switching on all lights (#256)
## [1.9.6] 2017-10-23
### Fixed
- Fix power report in Domoticz (#236)
- Fix Sonoff POW in AP mode (#241)
- Fix Home Automation auto-discovery (support for single relay switches and RGB lights, #235)
- Check WS authentication only on start event
### Added
- Support for 2.4.0 RC2 Arduino Core that fixes KRACK vulnerablity (pre-built images are compiled against this, #242)
- Support for ManCaveMade ESPLive board (thanks to Michael A. Cox)
- Support for InterMIT Tech QuinLED 2.6 (thanks to Colin Shorts)
- Support for Magic Home LED Controller 2.0 (thanks to users @gimi87 and @soif, #231)
- Support for Arilux AL-LC06 (thanks to Martijn Kruissen)
- Support for Xenon SM-PW702U Wifi boards (thanks to Joshua Harden, #212)
- Support for Authometion LYT8266 (testing, thanks to Joe Blellik, #213)
- Support for an external button for D1 Mini boards (thanks to user @PieBru, #239)
- Option to query relay status via MQTT or WS (thanks to Wesley Tuzza)
- Automatically install dependencies for web interface builder (thanks to Hermann Kraus)
- Support for HSV and IR for Magic Home LED Controller (optional, disabled by default, thanks to Wesley Tuzza)
- Added option to report DS18B20 temperatures based on changes (thanks to Michael A. Cox)
- Safer buffer handling for websocket data (thanks to Hermann Kraus & Björn Bergman)
- Updates HL8012 library with energy counting support (thanks to Hermann Kraus)
- Added option to disable light color persistence to avoid flickering (#191)
- Option to enable TELNET in STA mode from web UI (#203)
### Changed
- Changed default MQTT base topic to "{identifier}" (no leading slashes, #208)
- Prevent reconnecting when in AP mode if a web session or a telnet session is active (#244)
- Web UI checks for pending changes before reset/reconnect options (#226)
- Increase WIFI connect timeout and reconnect interval
## [1.9.5] 2017-09-28
### Fixed
- Revert to JustWifi 1.1.4 (#228)


+ 3
- 1
README.md View File

@ -4,7 +4,7 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switch
It was originally developed with the **[IteadStudio Sonoff](https://www.itead.cc/sonoff-wifi-wireless-switch.html)** in mind but now it supports a growing number of ESP8266-based boards.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
> **Current Release Version is 1.9.5**, read the [changelog](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
> **Current Release Version is 1.9.7**, read the [changelog](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
> **NOTICE**: Default flash layout changed in 1.8.3, as an unpredicted consequence devices will not be able to persist/retrieve configuration if flashed with 1.8.3 via **OTA** from **PlatformIO**. Please check issue #187.
@ -112,6 +112,8 @@ Here is the list of supported hardware. For more information please refer to the
|![EXS Wifi Relay v3.1](images/devices/exs-wifi-relay-v31.jpg)|||
|**EXS Wifi Relay v3.1**|||
**Other supported boards:** Itead Sonoff LED, Huacanxing H802, WiOn 50055, ManCaveMade ESP-Live, InterMitTech QuinLED 2.6, Arilux AL-LC06, Xenon SM-PW702U, Authometion LYT8266.
## License
Copyright (C) 2016-2017 by Xose Pérez (@xoseperez)


+ 5
- 4
code/.gitignore View File

@ -1,4 +1,5 @@
.clang_complete
.gcc-flags.json
.pioenvs
.piolibdeps
.clang_complete
.gcc-flags.json
.pioenvs
.piolibdeps
.vscode/c_cpp_properties.json

+ 2
- 1
code/build.sh View File

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

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

@ -10,7 +10,7 @@
//#define NODEMCU_LOLIN
//#define WEMOS_D1_MINI_RELAYSHIELD
//#define TINKERMAN_ESPURNA_H06
//#define TINKERMAN_ESPURNA_H07
//#define TINKERMAN_ESPURNA_H08
//#define ITEAD_SONOFF_BASIC
//#define ITEAD_SONOFF_RF
//#define ITEAD_SONOFF_TH
@ -49,6 +49,8 @@
//#define MANCAVEMADE_ESPLIVE
//#define INTERMITTECH_QUINLED
//#define ARILUX_AL_LC06
//#define XENON_SM_PW702U
//#define AUTHOMETION_LYT8266
//--------------------------------------------------------------------------------
// Features (values below are non-default values)


+ 23
- 13
code/espurna/config/general.h View File

@ -18,8 +18,8 @@
#define TELNET_SUPPORT 1 // Enable telnet support by default
#endif
#ifndef TELNET_ONLY_AP
#define TELNET_ONLY_AP 1 // By default, allow only connections via AP interface
#ifndef TELNET_STA
#define TELNET_STA 0 // By default, disallow connections via STA interface
#endif
#define TELNET_PORT 23 // Port to listen to telnet clients
@ -258,8 +258,8 @@ PROGMEM const char* const custom_reset_string[] = {
// WIFI
// -----------------------------------------------------------------------------
#define WIFI_CONNECT_TIMEOUT 30000 // Connecting timeout for WIFI in ms
#define WIFI_RECONNECT_INTERVAL 120000 // If could not connect to WIFI, retry after this time in ms
#define WIFI_CONNECT_TIMEOUT 60000 // Connecting timeout for WIFI in ms
#define WIFI_RECONNECT_INTERVAL 180000 // If could not connect to WIFI, retry after this time in ms
#define WIFI_MAX_NETWORKS 5 // Max number of WIFI connection configurations
#define WIFI_AP_MODE AP_MODE_ALONE
@ -311,8 +311,9 @@ PROGMEM const char* const custom_reset_string[] = {
// This will only be enabled if WEB_SUPPORT is 1 (this is the default value)
#define API_ENABLED 0 // Do not enable API by default
#define API_ENABLED 0 // Do not enable API by default
#define API_BUFFER_SIZE 10 // Size of the buffer for HTTP GET API responses
#define API_REAL_TIME_VALUES 0 // Show filtered/median values by default (0 => median, 1 => real time)
// -----------------------------------------------------------------------------
// MDNS
@ -383,7 +384,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_USER "" // Default MQTT broker usename
#define MQTT_PASS "" // Default MQTT broker password
#define MQTT_PORT 1883 // MQTT broker port
#define MQTT_TOPIC "/test/switch/{identifier}" // Default MQTT base topic
#define MQTT_TOPIC "{identifier}" // Default MQTT base topic
#define MQTT_RETAIN true // MQTT retain flag
#define MQTT_QOS 0 // MQTT QoS value for all messages
#define MQTT_KEEPALIVE 30 // MQTT keepalive value
@ -491,14 +492,29 @@ PROGMEM const char* const custom_reset_string[] = {
#endif
#define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out
#ifndef LIGHT_PWM_FREQUENCY
#define LIGHT_PWM_FREQUENCY 1000 // PWM frequency
#endif
#ifndef LIGHT_MAX_PWM
#define LIGHT_MAX_PWM 4095 // Maximum PWM value
#endif
#ifndef LIGHT_LIMIT_PWM
#define LIGHT_LIMIT_PWM LIGHT_MAX_PWM // Limit PWM to this value (prevent 100% power)
#endif
#ifndef LIGHT_MAX_VALUE
#define LIGHT_MAX_VALUE 255 // Maximum light value
#endif
#define LIGHT_MAX_BRIGHTNESS 255 // Maximun brightness value
#define LIGHT_STEP 32 // Step size
#define LIGHT_USE_COLOR 1 // Use 3 first channels as RGB
#define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value
#define LIGHT_USE_GAMMA 0 // Use gamma correction for color channels
#define LIGHT_USE_CSS 1 // Use CSS style to report colors (1=> "#FF0000", 0=> "255,0,0")
// -----------------------------------------------------------------------------
// POWER METERING
@ -540,9 +556,9 @@ PROGMEM const char* const custom_reset_string[] = {
#endif
#define POWER_VOLTAGE 230 // Default voltage
#define POWER_MIN_READ_INTERVAL 2000 // Minimum read interval
#define POWER_READ_INTERVAL 6000 // Default reading interval (6 seconds)
#define POWER_REPORT_INTERVAL 60000 // Default report interval (1 minute)
#define POWER_REPORT_BUFFER 12 // Default buffer size ()
#define POWER_CURRENT_DECIMALS 2 // Decimals for current values
#define POWER_VOLTAGE_DECIMALS 0 // Decimals for voltage values
#define POWER_POWER_DECIMALS 0 // Decimals for power values
@ -582,9 +598,6 @@ PROGMEM const char* const custom_reset_string[] = {
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
#undef POWER_REPORT_BUFFER
#define POWER_REPORT_BUFFER 60 // Override median buffer size
#ifndef V9261F_PIN
#define V9261F_PIN 2 // TX pin from the V9261F
#endif
@ -605,9 +618,6 @@ PROGMEM const char* const custom_reset_string[] = {
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560
#undef POWER_REPORT_BUFFER
#define POWER_REPORT_BUFFER 60 // Override median buffer size
#ifndef ECH1560_CLK_PIN
#define ECH1560_CLK_PIN 4 // Default CLK pin
#endif


+ 54
- 9
code/espurna/config/hardware.h View File

@ -1,4 +1,4 @@
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// Configuration HELP
// -----------------------------------------------------------------------------
//
@ -63,11 +63,6 @@
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
// IR
#define IR_SUPPORT 1
#define IR_PIN 4
#define IR_BUTTON_SET 1
// -----------------------------------------------------------------------------
// ESPurna
// -----------------------------------------------------------------------------
@ -114,11 +109,11 @@
#define HLW8012_CF1_PIN 13
#define HLW8012_CF_PIN 14
#elif defined(TINKERMAN_ESPURNA_H07)
#elif defined(TINKERMAN_ESPURNA_H08)
// Info
#define MANUFACTURER "TINKERMAN"
#define DEVICE "ESPURNA_H07"
#define DEVICE "ESPURNA_H08"
// Buttons
#define BUTTON1_PIN 4
@ -147,7 +142,7 @@
// LEDs
#define LED1_PIN 2
#define LED1_PIN_INVERSE 0
#define LED1_PIN_INVERSE 1
// HLW8012
#define POWER_PROVIDER POWER_PROVIDER_HLW8012
@ -1045,6 +1040,56 @@
#define LIGHT_CH4_INVERSE 0
#define LIGHT_CH5_INVERSE 0
// -----------------------------------------------------------------------------
// XENON SM-PW701U
// -----------------------------------------------------------------------------
#elif defined(XENON_SM_PW702U)
// Info
#define MANUFACTURER "XENON"
#define DEVICE "SM_PW702U"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 4
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// AUTHOMETION LYT8266
// https://authometion.com/shop/en/home/13-lyt8266.html
// -----------------------------------------------------------------------------
#elif defined(AUTHOMETION_LYT8266)
// Info
#define MANUFACTURER "AUTHOMETION"
#define DEVICE "LYT8266"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Channels
#define LIGHT_CH1_PIN 13 // RED
#define LIGHT_CH2_PIN 12 // GREEN
#define LIGHT_CH3_PIN 14 // BLUE
#define LIGHT_CH4_PIN 2 // WHITE
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
#define LIGHT_ENABLE_PIN 15
// -----------------------------------------------------------------------------
// Unknown hardware
// -----------------------------------------------------------------------------


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

@ -1,4 +1,4 @@
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.9.6b"
#define APP_VERSION "1.9.8b"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"

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


+ 129
- 21
code/espurna/dht.ino View File

@ -8,20 +8,139 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#if DHT_SUPPORT
#include <DHT.h>
#include <Adafruit_Sensor.h>
DHT dht(DHT_PIN, DHT_TYPE, DHT_TIMING);
double _dhtTemperature = 0;
unsigned int _dhtHumidity = 0;
// -----------------------------------------------------------------------------
// HAL
// https://github.com/gosouth/DHT22/blob/master/main/DHT22.c
// -----------------------------------------------------------------------------
#define DHT_MAX_DATA 5
#define DHT_MAX_ERRORS 5
#define DHT_MIN_INTERVAL 2000
#define DHT_OK 0
#define DHT_CHECKSUM_ERROR -1
#define DHT_TIMEOUT_ERROR -2
#define DHT11 11
#define DHT22 22
#define DHT21 21
#define AM2301 21
unsigned long _getSignalLevel(unsigned char gpio, int usTimeOut, bool state) {
unsigned long uSec = 1;
while (digitalRead(gpio) == state) {
if (++uSec > usTimeOut) return 0;
delayMicroseconds(1);
}
return uSec;
}
int readDHT(unsigned char gpio, unsigned char type) {
static unsigned long last_ok = 0;
if (millis() - last_ok < DHT_MIN_INTERVAL) return DHT_OK;
unsigned long low = 0;
unsigned long high = 0;
static unsigned char errors = 0;
uint8_t dhtData[DHT_MAX_DATA] = {0};
uint8_t byteInx = 0;
uint8_t bitInx = 7;
// Send start signal to DHT sensor
if (++errors > DHT_MAX_ERRORS) {
errors = 0;
digitalWrite(gpio, HIGH);
delay(250);
}
pinMode(gpio, OUTPUT);
digitalWrite(gpio, LOW);
delay(20);
noInterrupts();
digitalWrite(gpio, HIGH);
delayMicroseconds(40);
pinMode(gpio, INPUT_PULLUP);
delayMicroseconds(10);
// DHT will keep the line low for 80 us and then high for 80us
low = _getSignalLevel(gpio, 85, LOW);
if (low==0) return DHT_TIMEOUT_ERROR;
high = _getSignalLevel(gpio, 85, HIGH);
if (high==0) return DHT_TIMEOUT_ERROR;
// No errors, read the 40 data bits
for( int k = 0; k < 40; k++ ) {
// Starts new data transmission with >50us low signal
low = _getSignalLevel(gpio, 56, LOW);
if (low==0) return DHT_TIMEOUT_ERROR;
// Check to see if after >70us rx data is a 0 or a 1
high = _getSignalLevel(gpio, 75, HIGH);
if (high==0) return DHT_TIMEOUT_ERROR;
// add the current read to the output data
// since all dhtData array where set to 0 at the start,
// only look for "1" (>28us us)
if (high > low) dhtData[byteInx] |= (1 << bitInx);
// index to next byte
if (bitInx == 0) {
bitInx = 7;
++byteInx;
} else {
--bitInx;
}
}
interrupts();
// Verify checksum
if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) {
return DHT_CHECKSUM_ERROR;
}
// Get humidity from Data[0] and Data[1]
if (type == DHT11) {
_dhtHumidity = dhtData[0];
} else {
_dhtHumidity = dhtData[0] * 256 + dhtData[1];
_dhtHumidity /= 10;
}
// Get temp from Data[2] and Data[3]
if (type == DHT11) {
_dhtTemperature = dhtData[2];
} else {
_dhtTemperature = (dhtData[2] & 0x7F) * 256 + dhtData[3];
_dhtTemperature /= 10;
if (dhtData[2] & 0x80) _dhtTemperature *= -1;
}
last_ok = millis();
errors = 0;
return DHT_OK;
}
int readDHT() {
return readDHT(DHT_PIN, DHT_TYPE);
}
// -----------------------------------------------------------------------------
// Values
// -----------------------------------------------------------------------------
double getDHTTemperature(bool celsius) {
return celsius ? _dhtTemperature : _dhtTemperature * 1.8 + 32;
}
double getDHTTemperature() {
return _dhtTemperature;
return getDHTTemperature(true);
}
unsigned int getDHTHumidity() {
@ -30,8 +149,6 @@ unsigned int getDHTHumidity() {
void dhtSetup() {
dht.begin();
#if WEB_SUPPORT
apiRegister(DHT_TEMPERATURE_TOPIC, DHT_TEMPERATURE_TOPIC, [](char * buffer, size_t len) {
dtostrf(_dhtTemperature, 1-len, 1, buffer);
@ -50,21 +167,12 @@ void dhtLoop() {
if ((millis() - last_update > DHT_UPDATE_INTERVAL) || (last_update == 0)) {
last_update = millis();
unsigned char tmpUnits = getSetting("tmpUnits", TMP_UNITS).toInt();
// Read sensor data
double h = dht.readHumidity();
double t = dht.readTemperature(tmpUnits == TMP_FAHRENHEIT);
// Check if readings are valid
if (isnan(h) || isnan(t)) {
DEBUG_MSG_P(PSTR("[DHT] Error reading sensor\n"));
} else {
if (readDHT(DHT_PIN, DHT_TYPE) == DHT_OK) {
_dhtTemperature = t;
_dhtHumidity = h;
unsigned char tmpUnits = getSetting("tmpUnits", TMP_UNITS).toInt();
double t = getDHTTemperature(tmpUnits == TMP_CELSIUS);
unsigned int h = getDHTHumidity();
char temperature[6];
char humidity[6];


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

@ -149,6 +149,9 @@ void welcome() {
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_TELNET"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
@ -185,6 +188,9 @@ void welcome() {
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif


+ 27
- 1
code/espurna/hardware.ino View File

@ -461,7 +461,7 @@ void hwUpwardsCompatibility() {
setSetting("board", 38);
#elif defined(TINKERMAN_ESPURNA_H07)
#elif defined(TINKERMAN_ESPURNA_H08)
setSetting("board", 39);
setSetting("ledGPIO", 1, 2);
@ -531,6 +531,32 @@ void hwUpwardsCompatibility() {
setSetting("chLogic", 5, 0);
setSetting("relays", 1);
#elif defined(XENON_SM_PW702U)
setSetting("board", 44);
setSetting("ledGPIO", 1, 4);
setSetting("ledLogic", 1, 0);
setSetting("btnGPIO", 1, 13);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 1, 12);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(AUTHOMETION_LYT8266)
setSetting("board", 45);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("chGPIO", 1, 13);
setSetting("chGPIO", 2, 12);
setSetting("chGPIO", 3, 14);
setSetting("chGPIO", 4, 2);
setSetting("chLogic", 1, 0);
setSetting("chLogic", 2, 0);
setSetting("chLogic", 3, 0);
setSetting("chLogic", 4, 0);
setSetting("relays", 1);
setSetting("enGPIO", 15);
#else
#error "UNSUPPORTED HARDWARE!"


+ 39
- 31
code/espurna/homeassitant.ino View File

@ -10,49 +10,57 @@ Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
void haSend() {
void haSend(bool add) {
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
String component = String("light");
String output;
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (add) {
root["name"] = getSetting("hostname");
root["platform"] = "mqtt";
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (relayCount()) {
root["state_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, false);
root["command_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, true);
}
root["name"] = getSetting("hostname");
root["platform"] = "mqtt";
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
root["brightness"] = 1;
root["brightness_state_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, false);
root["brightness_command_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, true);
root["rgb"] = 1;
root["rgb_state_topic"] = getTopic(MQTT_TOPIC_COLOR, false);
root["rgb_command_topic"] = getTopic(MQTT_TOPIC_COLOR, true);
root["color_temp"] = 1;
root["color_temp_command_topic"] = getTopic(MQTT_TOPIC_MIRED, true);
if (relayCount()) {
root["state_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, false);
root["command_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, true);
root["payload_on"] = String("1");
root["payload_off"] = String("0");
}
if (lightChannels() > 3) {
root["white_value"] = 1;
root["white_value_state_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, false);
root["white_value_command_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
root["brightness_state_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, false);
root["brightness_command_topic"] = getTopic(MQTT_TOPIC_BRIGHTNESS, true);
root["rgb_state_topic"] = getTopic(MQTT_TOPIC_COLOR, false);
root["rgb_command_topic"] = getTopic(MQTT_TOPIC_COLOR, true);
root["color_temp_command_topic"] = getTopic(MQTT_TOPIC_MIRED, true);
}
String output;
root.printTo(output);
if (lightChannels() > 3) {
root["white_value_state_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, false);
root["white_value_command_topic"] = getTopic(MQTT_TOPIC_CHANNEL, 3, true);
}
#endif // LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
root.printTo(output);
}
#if LIGHT_PROVIDER == LIGHT_PROVIDER_NONE
String component = String("switch");
#else
String component = String("light");
#endif
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX)
+ String("/") + component + "/" + getSetting("hostname");
String topic = getSetting("haPrefix", HOMEASSISTANT_PREFIX) +
"/" + component +
"/" + getSetting("hostname") +
"/config";
mqttSendRaw(topic.c_str(), output.c_str());


+ 42
- 7
code/espurna/light.ino View File

@ -148,6 +148,24 @@ void _toRGB(char * rgb, size_t len) {
_toRGB(rgb, len, false);
}
void _toLong(char * color, size_t len, bool applyBrightness) {
if (!lightHasColor()) return;
float b = applyBrightness ? (float) _brightness / LIGHT_MAX_BRIGHTNESS : 1;
snprintf_P(color, len, PSTR("%d,%d,%d"),
(int) (_channels[0].value * b),
(int) (_channels[1].value * b),
(int) (_channels[2].value * b)
);
}
void _toLong(char * color, size_t len) {
_toLong(color, len, false);
}
// Thanks to Sacha Telgenhof for sharing this code in his AiLight library
// https://github.com/stelgenhof/AiLight
void _fromKelvin(unsigned long kelvin) {
@ -185,15 +203,15 @@ void _fromMireds(unsigned long mireds) {
unsigned int _toPWM(unsigned long value, bool bright, bool gamma, bool reverse) {
value = constrain(value, 0, LIGHT_MAX_VALUE);
if (bright) value *= ((float) _brightness / LIGHT_MAX_BRIGHTNESS);
unsigned int pwm = gamma ? gamma_table[value] : map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_MAX_PWM);
if (reverse) pwm = LIGHT_MAX_PWM - pwm;
unsigned int pwm = gamma ? gamma_table[value] : map(value, 0, LIGHT_MAX_VALUE, 0, LIGHT_LIMIT_PWM);
if (reverse) pwm = LIGHT_LIMIT_PWM - pwm;
return pwm;
}
// Returns a PWM valule for the given channel ID
unsigned int _toPWM(unsigned char id) {
if (id < _channels.size()) {
bool isColor = (lightHasColor() && id < 3);
bool isColor = lightHasColor() && (id < 3);
bool bright = isColor;
bool gamma = isColor & (getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1);
return _toPWM(_channels[id].shadow, bright, gamma, _channels[id].reverse);
@ -215,7 +233,7 @@ void _shadow() {
bool useWhite = getSetting("useWhite", LIGHT_USE_WHITE).toInt() == 1;
if (_lightState && useWhite && _channels.size() > 3) {
if (_lightState && useWhite && (_channels.size() > 3)) {
if (_channels[0].shadow == _channels[1].shadow && _channels[1].shadow == _channels[2].shadow ) {
_channels[3].shadow = _channels[0].shadow * ((float) _brightness / LIGHT_MAX_BRIGHTNESS);
_channels[2].shadow = 0;
@ -232,6 +250,10 @@ void _lightProviderUpdate() {
_shadow();
#ifdef LIGHT_ENABLE_PIN
digitalWrite(LIGHT_ENABLE_PIN, _lightState);
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
if (_lightState) {
@ -248,6 +270,7 @@ void _lightProviderUpdate() {
} else {
_my9291->setColor((my9291_color_t) { 0, 0, 0, 0, 0 });
_my9291->setState(false);
}
@ -369,12 +392,16 @@ unsigned char lightWhiteChannels() {
void lightMQTT() {
char buffer[8];
char buffer[12];
if (lightHasColor()) {
// Color
_toRGB(buffer, 8, false);
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, 12, false);
} else {
_toLong(buffer, 12, false);
}
mqttSend(MQTT_TOPIC_COLOR, buffer);
// Brightness
@ -494,7 +521,11 @@ void _lightAPISetup() {
apiRegister(MQTT_TOPIC_COLOR, MQTT_TOPIC_COLOR,
[](char * buffer, size_t len) {
_toRGB(buffer, len, false);
if (getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1) {
_toRGB(buffer, len, false);
} else {
_toLong(buffer, len, false);
}
},
[](const char * payload) {
lightColor(payload);
@ -556,6 +587,10 @@ void _lightAPISetup() {
void lightSetup() {
#ifdef LIGHT_ENABLE_PIN
pinMode(LIGHT_ENABLE_PIN, OUTPUT);
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
_my9291 = new my9291(MY9291_DI_PIN, MY9291_DCKI_PIN, MY9291_COMMAND, MY9291_CHANNELS);


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

@ -16,7 +16,7 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
// NTP
// -----------------------------------------------------------------------------
void ntpConnect() {
void ntpConfigure() {
NTP.begin(
getSetting("ntpServer1", NTP_SERVER),
getSetting("ntpOffset", NTP_TIME_OFFSET).toInt(),


+ 25
- 22
code/espurna/power.h View File

@ -8,36 +8,41 @@ class MedianFilter {
public:
MedianFilter(unsigned char size) {
_size = size;
_data = new double[_size+1];
reset();
MedianFilter() {
_data = new std::vector<double>();
}
~MedianFilter() {
if (_data) delete _data;
}
virtual void reset() {
if (_pointer > 1) _data[0] = _data[_pointer-1];
_pointer = 1;
for (unsigned char i=1; i<=_size; i++) _data[i] = 0;
double last = _data->empty() ? 0 : _data->back();
_data->clear();
add(last);
}
virtual void add(double value) {
if (_pointer <= _size) {
_data[_pointer] = value;
++_pointer;
}
_data->push_back(value);
}
virtual double average(bool do_reset = false) {
virtual double median(bool do_reset = false) {
double sum = 0;
if (_pointer > 2) {
if (_data->size() == 1) {
sum = _data->back();
} else if (_data->size() == 2) {
sum = _data->front();
} else if (_data->size() > 2) {
for (unsigned char i = 1; i<_pointer-1; i++) {
for (unsigned char i = 1; i <= _data->size() - 2; i++) {
double previous = _data[i-1];
double current = _data[i];
double next = _data[i+1];
double previous = _data->at(i-1);
double current = _data->at(i);
double next = _data->at(i+1);
if (previous > current) std::swap(previous, current);
if (current > next) std::swap(current, next);
@ -47,7 +52,7 @@ class MedianFilter {
}
sum /= (_pointer - 2);
sum /= (_data->size() - 2);
}
@ -58,13 +63,11 @@ class MedianFilter {
}
virtual unsigned char count() {
return _pointer - 1;
return _data->size();
}
private:
double *_data;
unsigned char _size = 0;
unsigned char _pointer = 0;
std::vector<double> *_data;
};

+ 65
- 42
code/espurna/power.ino View File

@ -17,22 +17,25 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _power_enabled = false;
bool _power_ready = false;
bool _power_newdata = false;
bool _power_realtime = API_REAL_TIME_VALUES;
unsigned long _power_read_interval = POWER_READ_INTERVAL;
unsigned long _power_report_interval = POWER_REPORT_INTERVAL;
double _power_current = 0;
double _power_voltage = 0;
double _power_apparent = 0;
double _power_energy = 0;
MedianFilter _filter_current = MedianFilter(POWER_REPORT_BUFFER);
MedianFilter _filter_current = MedianFilter();
#if POWER_HAS_ACTIVE
double _power_active = 0;
double _power_reactive = 0;
double _power_factor = 0;
MedianFilter _filter_voltage = MedianFilter(POWER_REPORT_BUFFER);
MedianFilter _filter_active = MedianFilter(POWER_REPORT_BUFFER);
MedianFilter _filter_apparent = MedianFilter(POWER_REPORT_BUFFER);
MedianFilter _filter_voltage = MedianFilter();
MedianFilter _filter_active = MedianFilter();
MedianFilter _filter_apparent = MedianFilter();
#endif
#if POWER_HAS_ENERGY
@ -48,37 +51,35 @@ MedianFilter _filter_current = MedianFilter(POWER_REPORT_BUFFER);
void _powerAPISetup() {
apiRegister(MQTT_TOPIC_CURRENT, MQTT_TOPIC_CURRENT, [](char * buffer, size_t len) {
if (_power_ready) {
dtostrf(getCurrent(), len-1, POWER_CURRENT_DECIMALS, buffer);
} else {
buffer = NULL;
}
dtostrf(_power_realtime ? _powerCurrent() : getCurrent(), 1-len, POWER_CURRENT_DECIMALS, buffer);
});
apiRegister(MQTT_TOPIC_VOLTAGE, MQTT_TOPIC_VOLTAGE, [](char * buffer, size_t len) {
if (_power_ready) {
snprintf_P(buffer, len, PSTR("%d"), getVoltage());
} else {
buffer = NULL;
}
snprintf_P(buffer, len, PSTR("%d"), (int) (_power_realtime ? _powerVoltage() : getVoltage()));
});
apiRegister(MQTT_TOPIC_POWER_APPARENT, MQTT_TOPIC_POWER_APPARENT, [](char * buffer, size_t len) {
if (_power_ready) {
snprintf_P(buffer, len, PSTR("%d"), getApparentPower());
} else {
buffer = NULL;
}
snprintf_P(buffer, len, PSTR("%d"), (int) (_power_realtime ? _powerApparentPower() : getApparentPower()));
});
#if POWER_HAS_ENERGY
apiRegister(MQTT_TOPIC_ENERGY_TOTAL, MQTT_TOPIC_ENERGY_TOTAL, [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%lu"), (int) (_power_realtime ? _powerEnergy() : getPowerEnergy()));
});
#endif
#if POWER_HAS_ACTIVE
apiRegister(MQTT_TOPIC_POWER_ACTIVE, MQTT_TOPIC_POWER_ACTIVE, [](char * buffer, size_t len) {
if (_power_ready) {
snprintf_P(buffer, len, PSTR("%d"), getActivePower());
} else {
buffer = NULL;
}
snprintf_P(buffer, len, PSTR("%d"), (int) (_power_realtime ? _powerActivePower() : getActivePower()));
});
apiRegister(MQTT_TOPIC_POWER_FACTOR, MQTT_TOPIC_POWER_FACTOR, [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), (int) (100 * (_power_realtime ? _powerPowerFactor() : getPowerFactor())));
});
#endif
}
@ -118,16 +119,18 @@ void _powerRead() {
_filter_active.add(active);
#endif
/* THERE IS A BUG HERE SOMEWHERE :)
// Debug
/*
char current_buffer[10];
dtostrf(current, sizeof(current_buffer)-1, POWER_CURRENT_DECIMALS, current_buffer);
dtostrf(current, 1-sizeof(current_buffer), POWER_CURRENT_DECIMALS, current_buffer);
DEBUG_MSG_P(PSTR("[POWER] Current: %sA\n"), current_buffer);
DEBUG_MSG_P(PSTR("[POWER] Voltage: %sA\n"), int(voltage));
DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), int(apparent));
DEBUG_MSG_P(PSTR("[POWER] Voltage: %dV\n"), (int) voltage);
DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), (int) apparent);
DEBUG_MSG_P(PSTR("[POWER] Energy: %dJ\n"), (int) _power_energy);
#if POWER_HAS_ACTIVE
DEBUG_MSG_P(PSTR("[POWER] Active Power: %dW\n"), int(active));
DEBUG_MSG_P(PSTR("[POWER] Reactive Power: %dW\n"), int(reactive));
DEBUG_MSG_P(PSTR("[POWER] Power Factor: %d%%\n"), int(100 * factor));
DEBUG_MSG_P(PSTR("[POWER] Active Power: %dW\n"), (int) active);
DEBUG_MSG_P(PSTR("[POWER] Reactive Power: %dW\n"), (int) reactive);
DEBUG_MSG_P(PSTR("[POWER] Power Factor: %d%%\n"), (int) (100 * factor));
#endif
*/
@ -169,11 +172,11 @@ void _powerRead() {
void _powerReport() {
// Get the fitered values
_power_current = _filter_current.average(true);
_power_current = _filter_current.median(true);
#if POWER_HAS_ACTIVE
_power_apparent = _filter_apparent.average(true);
_power_voltage = _filter_voltage.average(true);
_power_active = _filter_active.average(true);
_power_apparent = _filter_apparent.median(true);
_power_voltage = _filter_voltage.median(true);
_power_active = _filter_active.median(true);
if (_power_active > _power_apparent) _power_apparent = _power_active;
_power_reactive = (_power_apparent > _power_active) ? sqrt(_power_apparent * _power_apparent - _power_active * _power_active) : 0;
_power_factor = (_power_apparent > 0) ? _power_active / _power_apparent : 1;
@ -187,10 +190,9 @@ void _powerReport() {
double energy_delta = _power_energy - _power_last_energy;
_power_last_energy = _power_energy;
#else
double energy_delta = power * (POWER_REPORT_INTERVAL / 1000.);
double energy_delta = power * (_power_report_interval / 1000.);
_power_energy += energy_delta;
#endif
_power_ready = true;
char buf_current[10];
char buf_energy_delta[10];
@ -268,6 +270,10 @@ double getApparentPower() {
return roundTo(_power_apparent, POWER_POWER_DECIMALS);
}
double getPowerEnergy() {
roundTo(_power_energy, POWER_ENERGY_DECIMALS);
}
#if POWER_HAS_ACTIVE
double getActivePower() {
return roundTo(_power_active, POWER_POWER_DECIMALS);
@ -281,15 +287,20 @@ double getPowerFactor() {
return roundTo(_power_factor, 2);
}
double getPowerEnergy() {
roundTo(_power_energy, POWER_ENERGY_DECIMALS);
}
#endif
// -----------------------------------------------------------------------------
// PUBLIC API
// -----------------------------------------------------------------------------
unsigned long powerReadInterval() {
return _power_read_interval;
}
unsigned long powerReportInterval() {
return _power_report_interval;
}
bool powerEnabled() {
return _power_enabled;
}
@ -309,6 +320,17 @@ void powerResetCalibration() {
}
void powerConfigure() {
_power_realtime = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
_power_read_interval = atol(getSetting("pwrReadEvery", POWER_READ_INTERVAL).c_str());
_power_report_interval = atol(getSetting("pwrReportEvery", POWER_REPORT_INTERVAL).c_str());
if (_power_read_interval < POWER_MIN_READ_INTERVAL) {
_power_read_interval = POWER_MIN_READ_INTERVAL;
setSetting("pwrReadEvery", _power_read_interval);
}
if (_power_report_interval < _power_read_interval) {
_power_report_interval = _power_read_interval;
setSetting("pwrReportEvery", _power_report_interval);
}
_powerConfigureProvider();
}
@ -329,6 +351,7 @@ void powerSetup() {
moveSetting("powerRatioP", "pwrRatioP");
_powerSetupProvider();
powerConfigure();
// API
#if WEB_SUPPORT
@ -349,7 +372,7 @@ void powerLoop() {
}
static unsigned long last = 0;
if (millis() - last > POWER_REPORT_INTERVAL) {
if (millis() - last > _power_report_interval) {
last = millis();
_powerReport();
}


+ 1
- 3
code/espurna/power_emon.ino View File

@ -141,8 +141,6 @@ void _powerSetupProvider() {
brzo_i2c_end_transaction();
#endif
_powerConfigureProvider();
_emon.warmup();
}
@ -152,7 +150,7 @@ void _powerLoopProvider(bool before) {
if (before) {
static unsigned long last = 0;
if (millis() - last > POWER_READ_INTERVAL) {
if (millis() - last > powerReadInterval()) {
last = millis();
_power_newdata = true;
}


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

@ -151,7 +151,7 @@ void _powerLoopProvider(bool before) {
if (before) {
static unsigned long last = 0;
if (millis() - last > POWER_READ_INTERVAL) {
if (millis() - last > powerReadInterval()) {
last = millis();
_power_newdata = true;


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


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

@ -23,6 +23,7 @@ void _telnetDisconnect(unsigned char clientId) {
_telnetClients[clientId]->free();
_telnetClients[clientId] = NULL;
delete _telnetClients[clientId];
wifiReconnectCheck();
DEBUG_MSG_P(PSTR("[TELNET] Client #%d disconnected\n"), clientId);
}
@ -57,8 +58,9 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
void _telnetNewClient(AsyncClient *client) {
#if TELNET_ONLY_AP
if (client->localIP() != WiFi.softAPIP()) {
if (client->localIP() != WiFi.softAPIP()) {
bool telnetSTA = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
if (!telnetSTA) {
DEBUG_MSG_P(PSTR("[TELNET] Rejecting - Only local connections\n"));
client->onDisconnect([](void *s, AsyncClient *c) {
c->free();
@ -67,7 +69,7 @@ void _telnetNewClient(AsyncClient *client) {
client->close(true);
return;
}
#endif
}
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (!_telnetClients[i] || !_telnetClients[i]->connected()) {
@ -95,6 +97,7 @@ void _telnetNewClient(AsyncClient *client) {
}, 0);
DEBUG_MSG_P(PSTR("[TELNET] Client #%d connected\n"), i);
wifiReconnectCheck();
return;
}
@ -114,6 +117,13 @@ void _telnetNewClient(AsyncClient *client) {
// Public API
// -----------------------------------------------------------------------------
bool telnetConnected() {
for (unsigned char i = 0; i < TELNET_MAX_CLIENTS; i++) {
if (_telnetClients[i] && _telnetClients[i]->connected()) return true;
}
return false;
}
unsigned char telnetWrite(unsigned char ch) {
char data[1] = {ch};
return _telnetWrite(data, 1);


+ 29
- 11
code/espurna/web.ino View File

@ -176,10 +176,16 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
}
#if HOMEASSISTANT_SUPPORT
if (action.equals("ha_send") && root.containsKey("data")) {
if (action.equals("ha_add") && root.containsKey("data")) {
String value = root["data"];
setSetting("haPrefix", value);
haSend();
haSend(true);
wsSend_P(client_id, PSTR("{\"message\": 6}"));
}
if (action.equals("ha_del") && root.containsKey("data")) {
String value = root["data"];
setSetting("haPrefix", value);
haSend(false);
wsSend_P(client_id, PSTR("{\"message\": 6}"));
}
#endif
@ -436,7 +442,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
#endif
#if NTP_SUPPORT
if (changedNTP) ntpConnect();
if (changedNTP) ntpConfigure();
#endif
}
@ -473,9 +479,9 @@ void _wsStart(uint32_t client_id) {
root["webMode"] = WEB_MODE_NORMAL;
root["app"] = APP_NAME;
root["version"] = APP_VERSION;
root["build"] = buildTime();
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
root["manufacturer"] = String(MANUFACTURER);
root["chipid"] = chipid;
@ -524,6 +530,7 @@ void _wsStart(uint32_t client_id) {
root["useColor"] = getSetting("useColor", LIGHT_USE_COLOR).toInt() == 1;
root["useWhite"] = getSetting("useWhite", LIGHT_USE_WHITE).toInt() == 1;
root["useGamma"] = getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1;
root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1;
if (lightHasColor()) {
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
@ -558,6 +565,7 @@ void _wsStart(uint32_t client_id) {
root["apiEnabled"] = getSetting("apiEnabled", API_ENABLED).toInt() == 1;
root["apiKey"] = getSetting("apiKey");
root["apiRealTime"] = getSetting("apiRealTime", API_REAL_TIME_VALUES).toInt() == 1;
root["tmpUnits"] = getSetting("tmpUnits", TMP_UNITS).toInt();
@ -649,6 +657,8 @@ void _wsStart(uint32_t client_id) {
root["pwrVoltage"] = getVoltage();
root["pwrApparent"] = getApparentPower();
root["pwrEnergy"] = getPowerEnergy();
root["pwrReadEvery"] = powerReadInterval();
root["pwrReportEvery"] = powerReportInterval();
#if POWER_HAS_ACTIVE
root["pwrActive"] = getActivePower();
root["pwrReactive"] = getReactivePower();
@ -688,6 +698,11 @@ void _wsStart(uint32_t client_id) {
}
#endif
#if TELNET_SUPPORT
root["telnetVisible"] = 1;
root["telnetSTA"] = getSetting("telnetSTA", TELNET_STA).toInt() == 1;
#endif
root["maxNetworks"] = WIFI_MAX_NETWORKS;
JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
@ -731,22 +746,25 @@ bool _wsAuth(AsyncWebSocketClient * client) {
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
// Authorize
#ifndef NOWSAUTH
if (!_wsAuth(client)) return;
#endif
if (type == WS_EVT_CONNECT) {
// Authorize
#ifndef NOWSAUTH
if (!_wsAuth(client)) return;
#endif
IPAddress ip = client->remoteIP();
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
_wsStart(client->id());
client->_tempObject = new WebSocketIncommingBuffer(&_wsParse, true);
wifiReconnectCheck();
} else if(type == WS_EVT_DISCONNECT) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u disconnected\n"), client->id());
if (client->_tempObject) {
delete (WebSocketIncommingBuffer *) client->_tempObject;
}
wifiReconnectCheck();
} else if(type == WS_EVT_ERROR) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u error(%u): %s\n"), client->id(), *((uint16_t*)arg), (char*)data);


+ 13
- 2
code/espurna/wifi.ino View File

@ -45,12 +45,23 @@ bool createAP() {
return jw.createAP();
}
void wifiReconnectCheck() {
bool connected = false;
#if WEB_SUPPORT
if (wsConnected()) connected = true;
#endif
#if TELNET_SUPPORT
if (telnetConnected()) connected = true;
#endif
jw.setReconnectTimeout(connected ? 0 : WIFI_RECONNECT_INTERVAL);
}
void wifiConfigure() {
jw.setHostname(getSetting("hostname").c_str());
jw.setSoftAP(getSetting("hostname").c_str(), getSetting("adminPass", ADMIN_PASS).c_str());
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
jw.setReconnectTimeout(WIFI_RECONNECT_INTERVAL);
wifiReconnectCheck();
jw.setAPMode(WIFI_AP_MODE);
jw.cleanNetworks();
@ -254,7 +265,7 @@ void wifiSetup() {
// NTP connection reset
#if NTP_SUPPORT
if (code == MESSAGE_CONNECTED) {
ntpConnect();
ntpConfigure();
}
#endif


+ 3
- 7
code/html/custom.css View File

@ -29,7 +29,7 @@
}
.pure-button {
color: white;
padding: 8px 12px;
padding: 8px 8px;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
@ -47,6 +47,7 @@
}
.button-reset,
.button-reconnect,
.button-ha-del,
.button-rfb-forget {
background: rgb(202, 60, 60);
}
@ -55,7 +56,7 @@
margin-left: 5px;
}
.button-upgrade-browse,
.button-ha-send,
.button-ha-add,
.button-apikey {
background: rgb(0, 202, 0);
margin-left: 5px;
@ -134,7 +135,6 @@ input[name=upgrade] {
margin-top: -50px;
margin-left: -200px;
}
div.state {
border-top: 1px solid #eee;
margin-top: 20px;
@ -149,7 +149,6 @@ div.state {
font-size: 80%;
font-weight: bold;
}
.right {
text-align: right;
}
@ -173,15 +172,12 @@ div.state {
margin: 10px 2px;
padding: 20px;
}
#panel-rfb input {
margin-right: 5px;
}
#panel-rfb label {
padding-top: 5px;
}
#panel-rfb input {
text-align: center;
}

+ 214
- 94
code/html/custom.js View File

@ -1,10 +1,37 @@
var websock;
var password = false;
var maxNetworks;
var useWhite = false;
var messages = [];
var webhost;
var numChanged = 0;
var numReset = 0;
var numReconnect = 0;
var numReload = 0;
var useWhite = false;
// -----------------------------------------------------------------------------
// Messages
// -----------------------------------------------------------------------------
function initMessages() {
messages[01] = "Remote update started";
messages[02] = "OTA update started";
messages[03] = "Error parsing data!";
messages[04] = "The file does not look like a valid configuration backup or is corrupted";
messages[05] = "Changes saved. You should reboot your board now";
messages[06] = "Home Assistant auto-discovery message sent";
messages[07] = "Passwords do not match!";
messages[08] = "Changes saved";
messages[09] = "No changes detected";
messages[10] = "Session expired, please reload page...";
}
// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------
// http://www.the-art-of-web.com/javascript/validate-password/
function checkPassword(str) {
// at least one number, one lowercase and one uppercase letter
@ -13,6 +40,10 @@ function checkPassword(str) {
return re.test(str);
}
function zeroPad(number, positions) {
return ("0".repeat(positions) + number).slice(-positions);
}
function validateForm(form) {
// password
@ -42,8 +73,59 @@ function valueSet(data, name, value) {
data.push({'name': name, 'value': value});
}
function zeroPad(number, positions) {
return ("0".repeat(positions) + number).slice(-positions);
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))];
return result;
}
function generateAPIKey() {
var apikey = randomString(16, '@#');
$("input[name=\"apiKey\"]").val(apikey);
return false;
}
function forgetCredentials() {
$.ajax({
'method': 'GET',
'url': '/',
'async': false,
'username': "logmeout",
'password': "123456",
'headers': { "Authorization": "Basic xxx" }
}).done(function(data) {
return false;
// If we don't get an error, we actually got an error as we expect an 401!
}).fail(function(){
// We expect to get an 401 Unauthorized error! In this case we are successfully
// logged out and we redirect the user.
return true;
});
}
function getJson(str) {
try {
return JSON.parse(str);
} catch (e) {
return false;
}
}
// -----------------------------------------------------------------------------
// Actions
// -----------------------------------------------------------------------------
function doReload(milliseconds) {
milliseconds = (typeof milliseconds == 'undefined') ? 0 : parseInt(milliseconds);
setTimeout(function() {
window.location.reload();
}, milliseconds);
}
function doUpdate() {
@ -71,6 +153,21 @@ function doUpdate() {
.prop("checked", false)
.iphoneStyle("refresh");
numChanged = 0;
setTimeout(function() {
if (numReset > 0) {
var response = window.confirm("You have to reset the board for the changes to take effect, do you want to do it now?");
if (response == true) doReset(false);
} else if (numReconnect > 0) {
var response = window.confirm("You have to reset the wifi connection for the changes to take effect, do you want to do it now?");
if (response == true) doReconnect(false);
} else if (numReload > 0) {
var response = window.confirm("You have to reload the page to see the latest changes, do you want to do it now?");
if (response == true) doReload();
}
numReset = numReconnect = numReload = 0;
}, 1000);
}
return false;
@ -108,9 +205,7 @@ function doUpgrade() {
$("#upgrade-progress").hide();
if (data == 'OK') {
alert("Firmware image uploaded, board rebooting. This page will be refreshed in 5 seconds.");
setTimeout(function() {
window.location.reload();
}, 5000);
doReload(5000);
} else {
alert("There was an error trying to upload the new image, please try again (" + data + ").");
}
@ -146,27 +241,47 @@ function doUpdatePassword() {
return false;
}
function doReset() {
var response = window.confirm("Are you sure you want to reset the device?");
if (response == false) return false;
function doReset(ask) {
ask = (typeof ask == 'undefined') ? true : ask;
if (numChanged > 0) {
var response = window.confirm("Some changes have not been saved yet, do you want to save them first?");
if (response == true) return doUpdate();
}
if (ask) {
var response = window.confirm("Are you sure you want to reset the device?");
if (response == false) return false;
}
websock.send(JSON.stringify({'action': 'reset'}));
doReload(5000);
return false;
}
function doReconnect() {
var response = window.confirm("Are you sure you want to disconnect from the current WIFI network?");
if (response == false) return false;
function doReconnect(ask) {
ask = (typeof ask == 'undefined') ? true : ask;
if (numChanged > 0) {
var response = window.confirm("Some changes have not been saved yet, do you want to save them first?");
if (response == true) return doUpdate();
}
if (ask) {
var response = window.confirm("Are you sure you want to disconnect from the current WIFI network?");
if (response == false) return false;
}
websock.send(JSON.stringify({'action': 'reconnect'}));
doReload(5000);
return false;
}
function doToggle(element, value) {
var relayID = parseInt(element.attr("data"));
websock.send(JSON.stringify({'action': 'relay', 'data': { 'id': relayID, 'status': value ? 1 : 0 }}));
return false;
}
function backupSettings() {
function doBackup() {
document.getElementById('downloader').src = webhost + 'config';
return false;
}
@ -196,7 +311,7 @@ function onFileUpload(event) {
}
function restoreSettings() {
function doRestore() {
if (typeof window.FileReader !== 'function') {
alert("The file API isn't supported on this browser yet.");
} else {
@ -205,24 +320,16 @@ function restoreSettings() {
return false;
}
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))];
return result;
}
function doGenerateAPIKey() {
var apikey = randomString(16, '@#');
$("input[name=\"apiKey\"]").val(apikey);
function doToggle(element, value) {
var relayID = parseInt(element.attr("data"));
websock.send(JSON.stringify({'action': 'relay', 'data': { 'id': relayID, 'status': value ? 1 : 0 }}));
return false;
}
// -----------------------------------------------------------------------------
// Visualization
// -----------------------------------------------------------------------------
function showPanel() {
$(".panel").hide();
$("#" + $(this).attr("data")).show();
@ -236,6 +343,10 @@ function toggleMenu() {
$("#menuLink").toggleClass('active');
}
// -----------------------------------------------------------------------------
// Templates
// -----------------------------------------------------------------------------
function createRelays(count) {
var current = $("#relays > div").length;
@ -270,7 +381,7 @@ function createIdxs(count) {
for (var id=0; id<count; id++) {
var line = $(template).clone();
$(line).find("input").each(function() {
$(this).attr("data", id).attr("tabindex", 40+id);
$(this).attr("data", id).attr("tabindex", 40+id).attr("original", "");
});
if (count > 1) $(".id", line).html(" " + id);
line.appendTo("#idxs");
@ -300,7 +411,7 @@ function addNetwork() {
var template = $("#networkTemplate").children();
var line = $(template).clone();
$(line).find("input").each(function() {
$(this).attr("tabindex", tabindex++);
$(this).attr("tabindex", tabindex++).attr("original", "");
});
$(line).find(".button-del-network").on('click', delNetwork);
$(line).find(".button-more-network").on('click', moreNetwork);
@ -405,9 +516,6 @@ function initColorsExtras() {
});
}
function initChannels(num) {
// check if already initialized
@ -495,31 +603,17 @@ function rfbSend() {
websock.send(JSON.stringify({'action': 'rfbsend', 'data' : {'id' : input.attr("data_id"), 'status': input.attr("data_status"), 'data': input.val()}}));
}
function forgetCredentials() {
$.ajax({
'method': 'GET',
'url': '/',
'async': false,
'username': "logmeout",
'password': "123456",
'headers': { "Authorization": "Basic xxx" }
}).done(function(data) {
return false;
// If we don't get an error, we actually got an error as we expect an 401!
}).fail(function(){
// We expect to get an 401 Unauthorized error! In this case we are successfully
// logged out and we redirect the user.
return true;
});
}
// -----------------------------------------------------------------------------
// Processing
// -----------------------------------------------------------------------------
function processData(data) {
// title
if ("app" in data) {
var title = data.app;
if ("version" in data) {
title = title + " " + data.version;
if ("app_name" in data) {
var title = data.app_name;
if ("app_version" in data) {
title = title + " " + data.app_version;
}
$(".pure-menu-heading").html(title);
if ("hostname" in data) {
@ -543,9 +637,7 @@ function processData(data) {
if (data.action == "reload") {
if (password) forgetCredentials();
setTimeout(function() {
window.location.reload();
}, 1000);
doReload(1000);
}
if (data.action == "rfbLearn") {
@ -570,7 +662,7 @@ function processData(data) {
for (var i in nodes) {
var node = nodes[i];
var element = $("input[name=rfbcode][data_id=" + node["id"] + "][data_status=" + node["status"] + "]");
if (element.length) element.val(node["data"]);
if (element.length) element.val(node["data"]).attr("original", node["data"]);
}
return;
}
@ -596,6 +688,7 @@ function processData(data) {
$("[name='animation']").val(data[key]);
return;
}
if (key == "anim_speed") {
initColorsExtras();
var slider = $("#animSpeed");
@ -651,7 +744,7 @@ function processData(data) {
var wifi = data.wifi[i];
Object.keys(wifi).forEach(function(key) {
var element = $("input[name=" + key + "]", line);
if (element.length) element.val(wifi[key]);
if (element.length) element.val(wifi[key]).attr("original", wifi[key]);
});
}
@ -686,7 +779,7 @@ function processData(data) {
for (var i in idxs) {
var element = $(".dczRelayIdx[data=" + i + "]");
if (element.length > 0) element.val(idxs[i]);
if (element.length > 0) element.val(idxs[i]).attr("original", idxs[i]);
}
return;
@ -732,7 +825,7 @@ function processData(data) {
} else {
var pre = element.attr("pre") || "";
var post = element.attr("post") || "";
element.val(pre + data[key] + post);
element.val(pre + data[key] + post).attr("original", data[key]);
}
return;
}
@ -749,7 +842,7 @@ function processData(data) {
// Look for SELECTs
var element = $("select[name=" + key + "]");
if (element.length > 0) {
element.val(data[key]);
element.val(data[key]).attr("original", data[key]);
return;
}
@ -757,19 +850,51 @@ function processData(data) {
// Auto generate an APIKey if none defined yet
if ($("input[name='apiKey']").val() == "") {
doGenerateAPIKey();
generateAPIKey();
}
}
function getJson(str) {
try {
return JSON.parse(str);
} catch (e) {
return false;
function hasChanged() {
var newValue, originalValue;
if ($(this).attr('type') == 'checkbox') {
newValue = $(this).prop("checked")
originalValue = $(this).attr("original") == "true";
} else {
newValue = $(this).val();
originalValue = $(this).attr("original");
}
var hasChanged = $(this).attr("hasChanged") || 0;
var action = $(this).attr("action");
if (typeof originalValue == 'undefined') return;
if (action == 'none') return;
if (newValue != originalValue) {
if (hasChanged == 0) {
++numChanged;
if (action == "reconnect") ++numReconnect;
if (action == "reset") ++numReset;
if (action == "reload") ++numReload;
$(this).attr("hasChanged", 1);
}
} else {
if (hasChanged == 1) {
--numChanged;
if (action == "reconnect") --numReconnect;
if (action == "reset") --numReset;
if (action == "reload") --numReload;
$(this).attr("hasChanged", 0);
}
}
}
// -----------------------------------------------------------------------------
// Init & connect
// -----------------------------------------------------------------------------
function connect(host) {
if (typeof host === 'undefined') {
@ -799,33 +924,24 @@ function connect(host) {
};
}
function initMessages() {
messages[01] = "Remote update started";
messages[02] = "OTA update started";
messages[03] = "Error parsing data!";
messages[04] = "The file does not look like a valid configuration backup or is corrupted";
messages[05] = "Changes saved. You should reboot your board now";
messages[06] = "Home Assistant auto-discovery message sent";
messages[07] = "Passwords do not match!";
messages[08] = "Changes saved";
messages[09] = "No changes detected";
messages[10] = "Session expired, please reload page...";
}
function init() {
initMessages();
$("#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-reset").on('click', doReset);
$(".button-reconnect").on('click', doReconnect);
$(".button-settings-backup").on('click', backupSettings);
$(".button-settings-restore").on('click', restoreSettings);
$(".button-settings-backup").on('click', doBackup);
$(".button-settings-restore").on('click', doRestore);
$('#uploader').on('change', onFileUpload);
$(".button-apikey").on('click', doGenerateAPIKey);
$(".button-upgrade").on('click', doUpgrade);
$(".button-apikey").on('click', generateAPIKey);
$(".button-upgrade-browse").on('click', function() {
$("input[name='upgrade']")[0].click();
return false;
@ -834,14 +950,18 @@ function init() {
var fileName = $(this).val();
$("input[name='filename']").val(fileName.replace(/^.*[\\\/]/, ''));
});
$('progress').attr({ value: 0, max: 100 });
$(".pure-menu-link").on('click', showPanel);
$(".button-add-network").on('click', function() {
$("div.more", addNetwork()).toggle();
});
$(".button-ha-send").on('click', function() {
websock.send(JSON.stringify({'action': 'ha_send', 'data': $("input[name='haPrefix']").val()}));
$(".button-ha-add").on('click', function() {
websock.send(JSON.stringify({'action': 'ha_add', 'data': $("input[name='haPrefix']").val()}));
});
$(".button-ha-del").on('click', function() {
websock.send(JSON.stringify({'action': 'ha_del', 'data': $("input[name='haPrefix']").val()}));
});
$(document).on('change', 'input', hasChanged);
$(document).on('change', 'select', hasChanged);
$.ajax({
'method': 'GET',


+ 7
- 0
code/html/grids-responsive-1.0.0.min.css
File diff suppressed because it is too large
View File


+ 110
- 36
code/html/index.html View File

@ -8,13 +8,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- build:css style.css -->
<link rel="stylesheet" href="pure-min.css" />
<link rel="stylesheet" href="pure-1.0.0.min.css" />
<link rel="stylesheet" href="side-menu.css" />
<link rel="stylesheet" href="grids-responsive-min.css" />
<link rel="stylesheet" href="grids-responsive-1.0.0.min.css" />
<link rel="stylesheet" href="checkboxes.css" />
<link rel="stylesheet" href="custom.css" />
<link rel="stylesheet" href="wheelcolorpicker.css" />
<link rel="stylesheet" href="nouislider.min.css" />
<link rel="stylesheet" href="jquery.wheelcolorpicker-3.0.3.css" />
<link rel="stylesheet" href="nouislider-10.1.0.min.css" />
<!-- endbuild -->
</head>
@ -126,7 +126,7 @@
</ul>
<div class="main-buttons">
<button class="pure-button button-update">Update</button>
<button class="pure-button button-update">Save</button>
<button class="pure-button button-reconnect">Reconnect</button>
<button class="pure-button button-reset">Reset</button>
</div>
@ -225,8 +225,8 @@
</div>
<div class="pure-g module module-emon module-hlw module-v9261f module-ech1560">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrEnergy">Energy (Agg)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrEnergy" post=" J" readonly />
<label class="pure-u-1 pure-u-sm-1-4" for="pwrEnergy">Energy</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrEnergy" post=" J (aggregated)" readonly />
</div>
<div class="pure-u-1 state">
@ -249,11 +249,14 @@
<div class="pure-u-1 pure-u-sm-1-4">IP</div>
<div class="pure-u-1 pure-u-sm-17-24"><span class="right" name="deviceip"></span></div>
<div class="pure-u-1 pure-u-sm-1-4">ESPurna version</div>
<div class="pure-u-1 pure-u-sm-17-24"><span class="right" name="version"></span></div>
<div class="pure-u-1 pure-u-sm-1-4">Firmware name</div>
<div class="pure-u-1 pure-u-sm-17-24"><span class="right" name="app_name"></span></div>
<div class="pure-u-1 pure-u-sm-1-4">ESPurna build</div>
<div class="pure-u-1 pure-u-sm-17-24"><span class="right" name="build"></span></div>
<div class="pure-u-1 pure-u-sm-1-4">Firmware version</div>
<div class="pure-u-1 pure-u-sm-17-24"><span class="right" name="app_version"></span></div>
<div class="pure-u-1 pure-u-sm-1-4">Firmware build</div>
<div class="pure-u-1 pure-u-sm-17-24"><span class="right" name="app_build"></span></div>
<div class="pure-u-1 pure-u-sm-1-4">Current time</div>
<div class="pure-u-1 pure-u-sm-17-24"><span class="right" name="time"></span></div>
@ -302,7 +305,7 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="hostname">Hostname</label>
<input name="hostname" class="pure-u-1 pure-u-md-3-4" type="text" tabindex="1" />
<input name="hostname" class="pure-u-1 pure-u-md-3-4" type="text" action="reset" tabindex="1" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">This name will identify this device in your network (http://&lt;hostname&gt;.local). For this setting to take effect you should restart the wifi interface clicking the "Reconnect" button.</div>
</div>
@ -357,7 +360,7 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="btnDelay">Double click delay</label>
<input name="btnDelay" class="pure-u-1 pure-u-md-3-4" type="number" min="0" step="100" max="1000" tabindex="6" />
<input name="btnDelay" class="pure-u-1 pure-u-md-3-4" type="number" action="reset" min="0" step="100" max="1000" tabindex="6" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Delay in milliseconds to detect a double click (from 0 to 1000ms).<br />
The lower this number the faster the device will respond to button clicks but the harder it will be to get a double click.
@ -369,7 +372,7 @@
<div class="pure-g module module-color">
<div class="pure-u-1 pure-u-sm-1-4"><label for="useColor">Use colorpicker</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useColor" tabindex="8" /></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useColor" action="reload" tabindex="8" /></div>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Use color picker for the first 3 channels as RGB.<br />Will only work if the device has at least 3 dimmable channels.<br />Reload the page to update the web interface.</div>
@ -377,7 +380,7 @@
<div class="pure-g module module-color">
<div class="pure-u-1 pure-u-sm-1-4"><label for="useWhite">Use white channel</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useWhite" tabindex="9" /></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9" /></div>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Use forth dimmable channel as white when first 3 have the same RGB value.<br />Will only work if the device has at least 4 dimmable channels.<br />Reload the page to update the web interface.</div>
@ -391,23 +394,38 @@
<div class="pure-u-1 pure-u-md-3-4 hint">Use gamma correction for RGB channels.<br />Will only work if "use colorpicker" above is also ON.</div>
</div>
<div class="pure-g module module-color">
<div class="pure-u-1 pure-u-sm-1-4"><label for="useCSS">Use CSS style</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useCSS" tabindex="11" /></div>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Use CSS style to report colors to MQTT and REST API. <br />Red will be reported as "#FF0000" if ON, otherwise "255,0,0"</div>
</div>
<div class="pure-g module module-alexa">
<div class="pure-u-1 pure-u-sm-1-4"><label for="alexaEnabled">Alexa integration</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="alexaEnabled" tabindex="11" /></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="alexaEnabled" tabindex="12" /></div>
</div>
<div class="pure-g module module-ha">
<label class="pure-u-1 pure-u-md-1-4" for="haPrefix">Home Assistant Prefix</label>
<input class="pure-u-1 pure-u-md-1-2" name="haPrefix" type="text" tabindex="31" />
<div class="pure-u-1-3 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-ha-send">Send</button></div>
<input class="pure-u-1 pure-u-md-1-4" name="haPrefix" type="text" tabindex="13" />
<div class="pure-u-1-2 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-ha-add">Add</button></div>
<div class="pure-u-1-2 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-ha-del">Delete</button></div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Home Assistant auto-discovery feature.</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
Home Assistant auto-discovery feature.<br />
Add should immediately add the device to your HA console. Messages are retained so the device should be there even after a HA reboot<br />
To remove the device click on the Del button (retained message will be deleted) and reboot HA.<br />
You might want to disable CSS style (above) so Home Assistant can parse the color.
</div>
</div>
<div class="pure-g module module-ds module-dht">
<label class="pure-u-1 pure-u-sm-1-4" for="tmpUnits">Temperature units</label>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="12" value="0"> Celsius (&deg;C)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="13" value="1"> Fahrenheit (&deg;F)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="14" value="0"> Celsius (&deg;C)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="15" value="1"> Fahrenheit (&deg;F)</input></div>
</div>
</fieldset>
@ -427,7 +445,7 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass1">Admin password</label>
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="11" autocomplete="false" />
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" action="reset" tabindex="11" autocomplete="false" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-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 />
@ -435,13 +453,13 @@
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Admin password (repeat)</label>
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="12" autocomplete="false" />
<label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Repeat password</label>
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" action="reset" tabindex="12" autocomplete="false" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="webPort">HTTP port</label>
<input name="webPort" class="pure-u-1 pure-u-md-3-4" type="text" tabindex="13" />
<input name="webPort" class="pure-u-1 pure-u-md-3-4" type="text" action="reset" tabindex="13" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
This is the port for the web interface and API requests.<br />
@ -454,6 +472,17 @@
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="apiEnabled" /></div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-sm-1-4"><label for="apiRealTime">Real time API</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="apiRealTime" /></div>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
By default, some magnitudes are being preprocessed and filtered to avoid spurious values.<br />
If you want to get real-time values (not preprocessed) in the API turn on this setting.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="apiKey">HTTP API Key</label>
<input name="apiKey" class="pure-u-3-4 pure-u-md-1-2" type="text" tabindex="14" />
@ -466,6 +495,15 @@
</div>
</div>
<div class="pure-g module module-telnet">
<div class="pure-u-1 pure-u-sm-1-4"><label for="telnetSTA">Enable TELNET</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="telnetSTA" /></div>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Turn ON to be able to telnet to your device while connected to your home router.<br />TELNET is always enabled in AP mode.</div>
</div>
<div class="pure-g module module-nofuss">
<div class="pure-u-1 pure-u-sm-1-4"><label for="nofussEnabled">Automatic remote updates (NoFUSS)</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="nofussEnabled" /></div>
@ -671,7 +709,7 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="dczTopicOut">Domoticz OUT Topic</label>
<input class="pure-u-1 pure-u-md-3-4" name="dczTopicOut" type="text" tabindex="32" />
<input class="pure-u-1 pure-u-md-3-4" name="dczTopicOut" type="text" action="reconnect" tabindex="32" />
</div>
<div class="pure-g module module-dht module-ds">
@ -772,7 +810,7 @@
<div class="header">
<h1>POWER CALIBRATION</h1>
<h2>
Calibrate your power monitor device. Use a pure resistive load and introduce the expected values for active power, current and voltage. Use the nominal values or a multimeter to get the proper numbers. Set any field to 0 to leave the calibration value untouched.
Configure and calibrate your power monitor device. Use a pure resistive load and introduce the expected values for active power, current and voltage. Use the nominal values or a multimeter to get the proper numbers. Set any field to 0 to leave the calibration value untouched.
</h2>
</div>
@ -780,6 +818,42 @@
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrReadEvery">Read interval</label>
<select class="pure-u-1 pure-u-sm-1-4" name="pwrReadEvery">
<option value=2000>2 seconds</option>
<option value=6000>6 seconds</option>
<option value=10000>10 seconds</option>
<option value=15000>15 seconds</option>
<option value=30000>30 seconds</option>
<option value=60000>60 seconds</option>
</select>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
Select the interval between readings. These will be filtered and averaged for the report.<br />
The default and recommended value is 6 seconds.
</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrReportEvery">Report interval</label>
<select class="pure-u-1 pure-u-sm-1-4" name="pwrReportEvery">
<option value=6000>6 seconds</option>
<option value=10000>10 seconds</option>
<option value=30000>30 seconds</option>
<option value=60000>1 minute</option>
<option value=300000>5 minutes</option>
</select>
<div class="pure-u-0 pure-u-md-1-2">&nbsp;</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
Select the interval between reports via MQTT.<br />
This can't be less than the reading interval above.<br />
The default and recommended value is 1 minute.
</div>
</div>
<div class="pure-g module module-hlw module-emon module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedP">AC RMS Active Power</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedP" type="text" size="8" tabindex="51" placeholder="0" />
@ -877,7 +951,7 @@
<div class="pure-g">
<label class="pure-u-md-1-6 pure-u-1-4" for="ssid">Network SSID</label>
<div class="pure-u-md-3-4 pure-u-5-8"><input name="ssid" type="text" class="pure-u-23-24" value="" size="8" tabindex="0" placeholder="Network SSID" required autocomplete="false" /></div>
<div class="pure-u-md-3-4 pure-u-5-8"><input name="ssid" type="text" action="reconnect" class="pure-u-23-24" value="" size="8" tabindex="0" placeholder="Network SSID" required autocomplete="false" /></div>
<div class="pure-u-md-1-12 pure-u-1-8"><button type="button" class="pure-button button-more-network pure-u-1">...</button></div>
<div class="more">
@ -885,27 +959,27 @@
<div class="break"></div>
<label class="pure-u-md-1-6 pure-u-1-4" for="pass">Password</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="pass" type="password" value="" tabindex="0" autocomplete="false" />
<input class="pure-u-md-5-6 pure-u-3-4" name="pass" type="password" action="reconnect" value="" tabindex="0" autocomplete="false" />
<div class="break"></div>
<label class="pure-u-md-1-6 pure-u-1-4" for="ip">Static IP</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="ip" type="text" value="" size="15" tabindex="0" autocomplete="false" />
<input class="pure-u-md-5-6 pure-u-3-4" name="ip" type="text" action="reconnect" value="" size="15" tabindex="0" autocomplete="false" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Leave empty for DNS negotiation</div>
<label class="pure-u-md-1-6 pure-u-1-4" for="gw">Gateway IP</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="gw" type="text" value="" size="15" tabindex="0" autocomplete="false" />
<input class="pure-u-md-5-6 pure-u-3-4" name="gw" type="text" action="reconnect" value="" size="15" tabindex="0" autocomplete="false" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Set when using a static IP</div>
<label class="pure-u-md-1-6 pure-u-1-4" for="mask">Network Mask</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="mask" type="text" value="255.255.255.0" size="15" tabindex="0" autocomplete="false" />
<input class="pure-u-md-5-6 pure-u-3-4" name="mask" type="text" action="reconnect" value="255.255.255.0" size="15" tabindex="0" autocomplete="false" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Usually 255.255.255.0 for /24 networks</div>
<label class="pure-u-md-1-6 pure-u-1-4" for="dns">DNS IP</label>
<input class="pure-u-md-5-6 pure-u-3-4" name="dns" type="text" value="8.8.8.8" size="15" tabindex="0" autocomplete="false" />
<input class="pure-u-md-5-6 pure-u-3-4" name="dns" type="text" action="reconnect" value="8.8.8.8" size="15" tabindex="0" autocomplete="false" />
<div class="pure-u-md-1-6 pure-u-1-4"></div>
<div class="pure-u-md-5-6 pure-u-3-4 hint">Set the Domain Name Server IP to use when using a static IP</div>
@ -984,11 +1058,11 @@
</body>
<!-- build:js script.js -->
<script src="jquery-1.12.3.min.js"></script>
<script src="jquery-3.2.1.min.js"></script>
<script src="checkboxes.js"></script>
<script src="custom.js"></script>
<script src="jquery.wheelcolorpicker-3.0.2.min.js"></script>
<script src="nouislider.min.js"></script>
<script src="jquery.wheelcolorpicker-3.0.3.min.js"></script>
<script src="nouislider-10.1.0.min.js"></script>
<!-- endbuild -->
</html>

+ 0
- 5
code/html/jquery-1.12.3.min.js
File diff suppressed because it is too large
View File


+ 4
- 0
code/html/jquery-3.2.1.min.js
File diff suppressed because it is too large
View File


code/html/wheelcolorpicker.css → code/html/jquery.wheelcolorpicker-3.0.3.css View File

@ -31,6 +31,8 @@
}
.jQWCP-wWheel {
background-repeat: no-repeat;
background-position: center;
background-size: contain;
position: relative;
float: left;

code/html/jquery.wheelcolorpicker-3.0.2.min.js → code/html/jquery.wheelcolorpicker-3.0.3.min.js View File

@ -1,10 +1,10 @@
/**
* jQuery Wheel Color Picker v3.0.2
* jQuery Wheel Color Picker v3.0.3
*
* http://www.jar2.net/projects/jquery-wheelcolorpicker
*
* Author : Fajar Chandra
* Date : 2017.02.07
* Date : 2017.09.03
*
* Copyright © 2011-2017 Fajar Chandra. All rights reserved.
* Released under MIT License.

code/html/nouislider.min.css → code/html/nouislider-10.1.0.min.css View File

@ -1 +1 @@
/*! nouislider - 10.0.0 - 2017-05-28 14:52:48 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base{width:100%;height:100%;position:relative;z-index:1}.noUi-connect{position:absolute;right:0;top:0;left:0;bottom:0}.noUi-origin{position:absolute;height:0;width:0}.noUi-handle{position:relative;z-index:1}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:top .3s,right .3s,bottom .3s,left .3s;transition:top .3s,right .3s,bottom .3s,left .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-base,.noUi-handle{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}.noUi-target{background:#FAFAFA;border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-connect{background:#3FB8AF;border-radius:4px;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;white-space:nowrap;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate3d(-50%,50%,0);transform:translate3d(-50%,50%,0)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0);padding-left:25px}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center;white-space:nowrap}.noUi-horizontal .noUi-tooltip{-webkit-transform:translate(-50%,0);transform:translate(-50%,0);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);top:50%;right:120%}
/*! nouislider - 10.1.0 - 2017-07-28 13:09:54 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base{width:100%;height:100%;position:relative;z-index:1}.noUi-connect{position:absolute;right:0;top:0;left:0;bottom:0}.noUi-origin{position:absolute;height:0;width:0}.noUi-handle{position:relative;z-index:1}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:top .3s,right .3s,bottom .3s,left .3s;transition:top .3s,right .3s,bottom .3s,left .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-base,.noUi-handle{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}.noUi-target{background:#FAFAFA;border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-connect{background:#3FB8AF;border-radius:4px;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;white-space:nowrap;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate3d(-50%,50%,0);transform:translate3d(-50%,50%,0)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0);padding-left:25px}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center;white-space:nowrap}.noUi-horizontal .noUi-tooltip{-webkit-transform:translate(-50%,0);transform:translate(-50%,0);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);top:50%;right:120%}

+ 3
- 0
code/html/nouislider-10.1.0.min.js
File diff suppressed because it is too large
View File


+ 0
- 3
code/html/nouislider.min.js
File diff suppressed because it is too large
View File


+ 11
- 0
code/html/pure-1.0.0.min.css
File diff suppressed because it is too large
View File


+ 0
- 1
code/html/pure-min.css
File diff suppressed because it is too large
View File


+ 1
- 1
code/html/side-menu.css View File

@ -25,6 +25,7 @@ This is the parent `<div>` that contains the menu and the content area.
*/
#layout {
position: relative;
left: 0;
padding-left: 0;
}
#layout.active #menu {
@ -245,4 +246,3 @@ Hides the menu at `48em`, but modify this based on your app's needs.
left: 150px;
}
}

+ 1
- 1
code/ota_flash.sh View File

@ -173,7 +173,7 @@ if [ "$auth" == "" ]; then
fi
if [ "$flags" == "" ]; then
read -p "Extra flags for the build: " -e -i "-DTELNET_ONLY_AP=0" flags
read -p "Extra flags for the build: " -e -i "" flags
fi
read -p "Environment to build: " -e -i "esp8266-1m-ota" env


+ 75
- 8
code/platformio.ini View File

@ -10,10 +10,9 @@ build_flags = -g -DMQTT_MAX_PACKET_SIZE=400 ${env.ESPURNA_FLAGS}
debug_flags = -DDEBUG_ESP_CORE -DDEBUG_ESP_SSL -DDEBUG_ESP_WIFI -DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_HTTP_UPDATE -DDEBUG_ESP_HTTP_SERVER -DDEBUG_ESP_UPDATER -DDEBUG_ESP_OTA -DDEBUG_TLS_MEM
build_flags_512k = ${common.build_flags} -Wl,-Tesp8266.flash.512k0.ld
build_flags_1m = ${common.build_flags} -Wl,-Tesp8266.flash.1m0.ld
#https://github.com/me-no-dev/ESPAsyncTCP#036ea44
#https://github.com/me-no-dev/ESPAsyncTCP#9b0cc37 // 2.3.0 compatible
#https://github.com/me-no-dev/ESPAsyncTCP#3795e16 // 2.4.0-rc2 compatible
lib_deps =
DHT sensor library
Adafruit Unified Sensor
https://github.com/xoseperez/Time
ArduinoJson
https://github.com/me-no-dev/ESPAsyncTCP#9b0cc37
@ -28,7 +27,7 @@ lib_deps =
EspSoftwareSerial
https://bitbucket.org/xoseperez/justwifi.git#1.1.4
https://bitbucket.org/xoseperez/hlw8012.git#1.1.0
https://bitbucket.org/xoseperez/fauxmoesp.git#2.2.0
https://bitbucket.org/xoseperez/fauxmoesp.git#2.2.1
https://bitbucket.org/xoseperez/nofuss.git#0.2.5
https://bitbucket.org/xoseperez/emonliteesp.git#0.2.0
https://bitbucket.org/xoseperez/debounceevent.git#2.0.1
@ -127,23 +126,23 @@ upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:tinkerman-espurna-h07]
[env:tinkerman-espurna-h08]
platform = ${common.platform}
framework = arduino
board = esp12e
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DTINKERMAN_ESPURNA_H07
build_flags = ${common.build_flags} -DTINKERMAN_ESPURNA_H08
upload_speed = 460800
monitor_baud = 115200
[env:tinkerman-espurna-h07-ota]
[env:tinkerman-espurna-h08-ota]
platform = ${common.platform}
framework = arduino
board = esp12e
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DTINKERMAN_ESPURNA_H07
build_flags = ${common.build_flags} -DTINKERMAN_ESPURNA_H08
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
@ -935,6 +934,28 @@ upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:wemos-ech1560]
platform = ${common.platform}
framework = arduino
board = d1_mini
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DGENERIC_ECH1560
upload_speed = 460800
monitor_baud = 115200
[env:wemos-ech1560-ota]
platform = ${common.platform}
framework = arduino
board = d1_mini
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DGENERIC_ECH1560
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:esp01-ech1560]
platform = ${common.platform}
framework = arduino
@ -1003,6 +1024,52 @@ upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:xenon-sm-pw702u]
platform = ${common.platform}
framework = arduino
board = esp12e
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DXENON_SM_PW702U
monitor_baud = 115200
[env:xenon-sm-pw702u-ota]
platform = ${common.platform}
framework = arduino
board = esp12e
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DXENON_SM_PW702U
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:authometion-lyt8266]
platform = ${common.platform}
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DAUTHOMETION_LYT8266
monitor_baud = 115200
[env:authometion-lyt8266-ota]
platform = ${common.platform}
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DAUTHOMETION_LYT8266
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
# ------------------------------------------------------------------------------
# GENERIC OTA ENVIRONMENTS
# ------------------------------------------------------------------------------


Loading…
Cancel
Save