Browse Source

Merge branch 'dev' into ssl

Conflicts:
	code/espurna/static/index.html.gz.h
	code/espurna/web.ino
fastled
Xose Pérez 7 years ago
parent
commit
3dcaf61ee6
21 changed files with 5031 additions and 3447 deletions
  1. +1
    -0
      code/espurna/config/arduino.h
  2. +1
    -1
      code/espurna/config/debug.h
  3. +45
    -27
      code/espurna/config/general.h
  4. +60
    -23
      code/espurna/config/hardware.h
  5. BIN
      code/espurna/data/index.html.gz
  6. +15
    -7
      code/espurna/espurna.ino
  7. +38
    -24
      code/espurna/hardware.ino
  8. +436
    -182
      code/espurna/light.ino
  9. +58
    -45
      code/espurna/mqtt.ino
  10. +1
    -1
      code/espurna/ota.ino
  11. +27
    -9
      code/espurna/relay.ino
  12. +307
    -0
      code/espurna/rfbridge.ino
  13. +79
    -7
      code/espurna/settings.ino
  14. +3505
    -3036
      code/espurna/static/index.html.gz.h
  15. +89
    -51
      code/espurna/web.ino
  16. +37
    -3
      code/html/custom.css
  17. +169
    -9
      code/html/custom.js
  18. +115
    -20
      code/html/index.html
  19. +1
    -0
      code/html/nouislider.min.css
  20. +3
    -0
      code/html/nouislider.min.js
  21. +44
    -2
      code/platformio.ini

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

@ -30,6 +30,7 @@
//#define LED_CONTROLLER
//#define H801_LED_CONTROLLER
//#define ESPURNA_H
//#define SONOFF_RFBRIDGE
//--------------------------------------------------------------------------------
// Features (values below are non-default values)


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

@ -1,6 +1,6 @@
#define DEBUG_MESSAGE_MAX_LENGTH 80
#ifdef SONOFF_DUAL
#if defined(SONOFF_DUAL)
#undef DEBUG_PORT
#endif


+ 45
- 27
code/espurna/config/general.h View File

@ -24,7 +24,7 @@
// nc -ul 8111
//#define DEBUG_UDP_IP IPAddress(192, 168, 1, 100)
//#define DEBUG_UDP_PORT 8111
//#define DEBUG_UDP_PORT 8113
//--------------------------------------------------------------------------------
// EEPROM
@ -50,7 +50,7 @@
#define HEARTBEAT_REPORT_FREEHEAP 1
#define HEARTBEAT_REPORT_VCC 1
#define HEARTBEAT_REPORT_RELAY 1
#define HEARTBEAT_REPORT_COLOR 1
#define HEARTBEAT_REPORT_LIGHT 1
#define HEARTBEAT_REPORT_HOSTNAME 1
#define HEARTBEAT_REPORT_APP 1
#define HEARTBEAT_REPORT_VERSION 1
@ -136,6 +136,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define RELAY_PROVIDER_RELAY 0
#define RELAY_PROVIDER_DUAL 1
#define RELAY_PROVIDER_LIGHT 2
#define RELAY_PROVIDER_RFBRIDGE 3
// Pulse time in milliseconds
#define RELAY_PULSE_TIME 1.0
@ -186,7 +187,9 @@ PROGMEM const char* const custom_reset_string[] = {
#define WS_TIMEOUT 1800000 // Timeout for secured websocket
#define WEBSERVER_PORT 80 // HTTP port
#define DNS_PORT 53 // MDNS port
#define ENABLE_MDNS 1 // Enabled MDNS
#define ENABLE_MDNS 1 // Enable MDNS by default
#define ENABLE_API 0 // Do not enable API by default
#define API_BUFFER_SIZE 10 // Size of the buffer for HTTP GET API responses
// This is not working at the moment
// Requires ASYNC_TCP_SSL_ENABLED to 1
@ -243,7 +246,6 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_TOPIC_ACTION "action"
#define MQTT_TOPIC_RELAY "relay"
#define MQTT_TOPIC_LED "led"
#define MQTT_TOPIC_COLOR "color"
#define MQTT_TOPIC_BUTTON "button"
#define MQTT_TOPIC_IP "ip"
#define MQTT_TOPIC_VERSION "version"
@ -258,6 +260,16 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_TOPIC_HOSTNAME "host"
#define MQTT_TOPIC_TIME "time"
#define MQTT_TOPIC_ANALOG "analog"
#define MQTT_TOPIC_RFOUT "rfout"
#define MQTT_TOPIC_RFIN "rfin"
#define MQTT_TOPIC_RFLEARN "rflearn"
// Lights
#define MQTT_TOPIC_CHANNEL "channel"
#define MQTT_TOPIC_COLOR "color"
#define MQTT_TOPIC_BRIGHTNESS "brightness"
#define MQTT_TOPIC_MIRED "mired"
#define MQTT_TOPIC_KELVIN "kelvin"
#define MQTT_STATUS_ONLINE "1" // Value for the device ON message
#define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will)
@ -288,30 +300,28 @@ PROGMEM const char* const custom_reset_string[] = {
// LIGHT
// -----------------------------------------------------------------------------
#define ENABLE_GAMMA_CORRECTION 0
#define LIGHT_PROVIDER_NONE 0
#define LIGHT_PROVIDER_WS2812 1
#define LIGHT_PROVIDER_RGB 2
#define LIGHT_PROVIDER_RGBW 3
#define LIGHT_PROVIDER_MY9192 4
#define LIGHT_PROVIDER_RGB2W 5
#define LIGHT_DEFAULT_COLOR "#000080"
#define LIGHT_SAVE_DELAY 5
#define LIGHT_MAX_VALUE 255
// Settings for MY9291 bulbs (AI Light)
#define MY9291_DI_PIN 13
#define MY9291_DCKI_PIN 15
#define MY9291_COMMAND MY9291_COMMAND_DEFAULT
// Shared settings between RGB and RGBW lights
#define RGBW_INVERSE_LOGIC 1
#define RGBW_RED_PIN 14
#define RGBW_GREEN_PIN 5
#define RGBW_BLUE_PIN 12
#define RGBW_WHITE_PIN 13
#define LIGHT_PROVIDER_MY9192 1
#define LIGHT_PROVIDER_DIMMER 2
// LIGHT_PROVIDER_DIMMER can have from 1 to 5 different channels.
// They have to be defined for each device in the hardware.h file.
// If 3 or more channels first 3 will be considered RGB.
// Usual configurations are:
// 1 channels => W
// 2 channels => WW
// 3 channels => RGB
// 4 channels => RGBW
// 5 channels => RGBWW
#define LIGHT_SAVE_DELAY 5 // Persist color after 5 seconds to avoid wearing out
#define LIGHT_PWM_FREQUENCY 1000 // PWM frequency
#define LIGHT_MAX_PWM 4095 // Maximum PWM value
#define LIGHT_MAX_VALUE 255 // Maximum light value
#define LIGHT_MAX_BRIGHTNESS 255 // Maximun brightness value
#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
// -----------------------------------------------------------------------------
// DOMOTICZ
@ -354,3 +364,11 @@ PROGMEM const char* const custom_reset_string[] = {
// this device should be discoberable and respond to Alexa commands.
// Both ENABLE_FAUXMO and fauxmoEnabled should be 1 for Alexa support to work.
#define FAUXMO_ENABLED 1
// -----------------------------------------------------------------------------
// RFBRIDGE
// -----------------------------------------------------------------------------
#define RF_SEND_TIMES 4 // How many times to send the message
#define RF_SEND_DELAY 250 // Interval between sendings in ms

+ 60
- 23
code/espurna/config/hardware.h View File

@ -161,6 +161,7 @@
#define SERIAL_BAUDRATE 19230
#undef RELAY_PROVIDER
#define RELAY_PROVIDER RELAY_PROVIDER_DUAL
#define DUMMY_RELAY_COUNT 2
#elif defined(SONOFF_4CH)
@ -222,6 +223,33 @@
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#elif defined(ITEAD_BNSZ01)
#define MANUFACTURER "ITEAD"
#define DEVICE "BN-SZ01"
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#define LIGHT_CH1_PIN 12
#define LIGHT_CH1_INVERSE 0
#elif defined(SONOFF_RFBRIDGE)
#define MANUFACTURER "ITEAD"
#define DEVICE "RFBRIDGE"
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
#undef SERIAL_BAUDRATE
#define SERIAL_BAUDRATE 19200
#undef RELAY_PROVIDER
#define RELAY_PROVIDER RELAY_PROVIDER_RFBRIDGE
#define DUMMY_RELAY_COUNT 6
#define TRACK_RELAY_STATUS 0
// -----------------------------------------------------------------------------
// Electrodragon boards
// -----------------------------------------------------------------------------
@ -269,6 +297,11 @@
#define DEVICE "AI_LIGHT"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY9192
#define DUMMY_RELAY_COUNT 1
#define MY9291_DI_PIN 13
#define MY9291_DCKI_PIN 15
#define MY9291_COMMAND MY9291_COMMAND_DEFAULT
// -----------------------------------------------------------------------------
// LED Controller
@ -281,19 +314,18 @@
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_RGB
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#undef RGBW_INVERSE_LOGIC
#undef RGBW_RED_PIN
#undef RGBW_GREEN_PIN
#undef RGBW_BLUE_PIN
#undef RGBW_WHITE_PIN
#define LIGHT_CH1_PIN 14 // RED
#define LIGHT_CH2_PIN 5 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH4_PIN 13 // WHITE
#define RGBW_INVERSE_LOGIC 1
#define RGBW_RED_PIN 14
#define RGBW_GREEN_PIN 5
#define RGBW_BLUE_PIN 12
#define RGBW_WHITE_PIN 13
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// -----------------------------------------------------------------------------
// HUACANXING H801
@ -306,20 +338,20 @@
#define LED1_PIN 5
#define LED1_PIN_INVERSE 1
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_RGB2W
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
#undef RGBW_INVERSE_LOGIC
#undef RGBW_RED_PIN
#undef RGBW_GREEN_PIN
#undef RGBW_BLUE_PIN
#undef RGBW_WHITE_PIN
#define LIGHT_CH1_PIN 15 // RED
#define LIGHT_CH2_PIN 13 // GREEN
#define LIGHT_CH3_PIN 12 // BLUE
#define LIGHT_CH4_PIN 14 // WHITE1
#define LIGHT_CH5_PIN 4 // WHITE2
#define RGBW_INVERSE_LOGIC 1
#define RGBW_RED_PIN 15
#define RGBW_GREEN_PIN 13
#define RGBW_BLUE_PIN 12
#define RGBW_WHITE_PIN 14
#define RGBW_WHITE2_PIN 4
#define LIGHT_CH1_INVERSE 0
#define LIGHT_CH2_INVERSE 0
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
#define LIGHT_CH5_INVERSE 0
// -----------------------------------------------------------------------------
// Jan Goedeke Wifi Relay
@ -528,6 +560,11 @@
#define BUTTON_SET_PULLUP 4
#endif
// Does the board track the relay status?
#ifndef TRACK_RELAY_STATUS
#define TRACK_RELAY_STATUS 1
#endif
// Relay providers
#ifndef RELAY_PROVIDER
#define RELAY_PROVIDER RELAY_PROVIDER_RELAY


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


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

@ -88,10 +88,8 @@ void heartbeat() {
#if (HEARTBEAT_REPORT_RELAY)
relayMQTT();
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#if (HEARTBEAT_REPORT_COLOR)
mqttSend(MQTT_TOPIC_COLOR, lightColor().c_str());
#endif
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) & (HEARTBEAT_REPORT_LIGHT)
lightMQTT();
#endif
#if (HEARTBEAT_REPORT_VCC)
#if ENABLE_ADC_VCC
@ -120,19 +118,22 @@ unsigned char customReset() {
}
void hardwareSetup() {
EEPROM.begin(4096);
#ifdef DEBUG_PORT
DEBUG_PORT.begin(SERIAL_BAUDRATE);
if (customReset() == CUSTOM_RESET_HARDWARE) {
DEBUG_PORT.setDebugOutput(true);
}
#endif
#ifdef SONOFF_DUAL
#elif defined(SERIAL_BAUDRATE)
Serial.begin(SERIAL_BAUDRATE);
#endif
#if not EMBEDDED_WEB
SPIFFS.begin();
#endif
}
void hardwareLoop() {
@ -214,6 +215,9 @@ void setup() {
mqttSetup();
ntpSetup();
#ifdef SONOFF_RFBRIDGE
rfbSetup();
#endif
#if ENABLE_I2C
i2cSetup();
#endif
@ -250,6 +254,7 @@ void setup() {
// Prepare configuration for version 2.0
hwUpwardsCompatibility();
//settingsDump();
}
@ -267,9 +272,12 @@ void loop() {
#if ENABLE_FAUXMO
fauxmoLoop();
#endif
#ifndef SONOFF_DUAL
#if !defined(SONOFF_DUAL) & !defined(SONOFF_RFBRIDGE)
settingsLoop();
#endif
#ifdef SONOFF_RFBRIDGE
rfbLoop();
#endif
#if ENABLE_NOFUSS
nofussLoop();
#endif


+ 38
- 24
code/espurna/hardware.ino View File

@ -13,17 +13,6 @@ the migration to future version 2 will be straigh forward.
*/
#define RELAY_PROVIDER_RELAY 0
#define RELAY_PROVIDER_DUAL 1
#define RELAY_PROVIDER_LIGHT 2
#define LIGHT_PROVIDER_NONE 0
#define LIGHT_PROVIDER_WS2812 1
#define LIGHT_PROVIDER_RGB 2
#define LIGHT_PROVIDER_RGBW 3
#define LIGHT_PROVIDER_MY9192 4
void hwUpwardsCompatibility() {
unsigned int board = getSetting("board", 0).toInt();
@ -234,14 +223,17 @@ void hwUpwardsCompatibility() {
#ifdef LED_CONTROLLER
setSetting("board", 21);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_RGB);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 1, 2);
setSetting("ledLogic", 1, 1);
setSetting("redGPIO", 14);
setSetting("greenGPIO", 5);
setSetting("blueGPIO", 12);
setSetting("whiteGPIO", 13);
setSetting("lightLogic", 1);
setSetting("ch1GPIO", 14);
setSetting("ch2GPIO", 5);
setSetting("ch3GPIO", 12);
setSetting("ch4GPIO", 13);
setSetting("ch1Logic", 0);
setSetting("ch2Logic", 0);
setSetting("ch3Logic", 0);
setSetting("ch4Logic", 0);
#endif
#ifdef ITEAD_MOTOR
@ -267,15 +259,37 @@ void hwUpwardsCompatibility() {
#ifdef H801_LED_CONTROLLER
setSetting("board", 24);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_RGB2W);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 5, 1);
setSetting("ledLogic", 1, 1);
setSetting("redGPIO", 15);
setSetting("greenGPIO", 13);
setSetting("blueGPIO", 12);
setSetting("whiteGPIO", 14);
setSetting("white2GPIO", 4);
setSetting("lightLogic", 1);
setSetting("ch1GPIO", 15);
setSetting("ch2GPIO", 13);
setSetting("ch3GPIO", 12);
setSetting("ch4GPIO", 14);
setSetting("ch5GPIO", 4);
setSetting("ch1Logic", 0);
setSetting("ch2Logic", 0);
setSetting("ch3Logic", 0);
setSetting("ch4Logic", 0);
setSetting("ch5Logic", 0);
#endif
#ifdef ITEAD_BNSZ01
setSetting("board", 25);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_DIMMER);
setSetting("ledGPIO", 13, 1);
setSetting("ledLogic", 1, 1);
setSetting("ch1GPIO", 12);
setSetting("ch1Logic", 0);
#endif
#ifdef SONOFF_RFBRIDGE
setSetting("board", 26);
setSetting("ledGPIO", 1, 13);
setSetting("ledLogic", 1, 1);
setSetting("btnGPIO", 1, 0);
setSetting("relayProvider", RELAY_PROVIDER_RFBRIDGE);
#endif
saveSettings();


+ 436
- 182
code/espurna/light.ino View File

@ -9,55 +9,50 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#include <Ticker.h>
#include <ArduinoJson.h>
#include <vector>
Ticker colorTicker;
typedef struct {
unsigned char pin;
bool reverse;
unsigned char value;
unsigned char shadow;
} channel_t;
std::vector<channel_t> _channels;
bool _lightState = false;
unsigned int _lightColor[3] = {0};
unsigned int _brightness = LIGHT_MAX_BRIGHTNESS;
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
#include <my9291.h>
my9291 * _my9291;
#endif
#if ENABLE_GAMMA_CORRECTION
#define GAMMA_TABLE_SIZE (256)
#undef LIGHT_PWM_RANGE
#define LIGHT_PWM_RANGE (4095)
// Gamma Correction lookup table for gamma=2.8 and 12 bit (4095) full scale
const unsigned short gamma_table[GAMMA_TABLE_SIZE] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11,
12, 13, 15, 16, 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36,
38, 40, 43, 45, 48, 50, 53, 56, 59, 62, 65, 68, 71, 75, 78, 82,
85, 89, 93, 97, 101, 105, 110, 114, 119, 123, 128, 133, 138, 143, 149, 154,
159, 165, 171, 177, 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, 258,
266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360, 369, 379, 389, 399,
409, 419, 430, 440, 451, 462, 473, 485, 496, 508, 520, 532, 544, 556, 569, 582,
594, 608, 621, 634, 648, 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811,
827, 843, 859, 876, 893, 910, 927, 944, 962, 980, 998,1016,1034,1053,1072,1091,
1110,1130,1150,1170,1190,1210,1231,1252,1273,1294,1316,1338,1360,1382,1404,1427,
1450,1473,1497,1520,1544,1568,1593,1617,1642,1667,1693,1718,1744,1770,1797,1823,
1850,1877,1905,1932,1960,1988,2017,2045,2074,2103,2133,2162,2192,2223,2253,2284,
2315,2346,2378,2410,2442,2474,2507,2540,2573,2606,2640,2674,2708,2743,2778,2813,
2849,2884,2920,2957,2993,3030,3067,3105,3143,3181,3219,3258,3297,3336,3376,3416,
3456,3496,3537,3578,3619,3661,3703,3745,3788,3831,3874,3918,3962,4006,4050,4095 };
#endif
#ifndef LIGHT_PWM_FREQUENCY
#define LIGHT_PWM_FREQUENCY (1000)
#endif
#ifndef LIGHT_PWM_RANGE
#define LIGHT_PWM_RANGE (255)
#endif
// Gamma Correction lookup table for gamma=2.8 and 12 bit (4095) full scale
// TODO: move to PROGMEM
const unsigned short gamma_table[LIGHT_MAX_VALUE+1] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1,
2, 2, 2, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 9, 10, 11,
12, 13, 15, 16, 17, 18, 20, 21, 23, 25, 26, 28, 30, 32, 34, 36,
38, 40, 43, 45, 48, 50, 53, 56, 59, 62, 65, 68, 71, 75, 78, 82,
85, 89, 93, 97, 101, 105, 110, 114, 119, 123, 128, 133, 138, 143, 149, 154,
159, 165, 171, 177, 183, 189, 195, 202, 208, 215, 222, 229, 236, 243, 250, 258,
266, 273, 281, 290, 298, 306, 315, 324, 332, 341, 351, 360, 369, 379, 389, 399,
409, 419, 430, 440, 451, 462, 473, 485, 496, 508, 520, 532, 544, 556, 569, 582,
594, 608, 621, 634, 648, 662, 676, 690, 704, 719, 734, 749, 764, 779, 795, 811,
827, 843, 859, 876, 893, 910, 927, 944, 962, 980, 998,1016,1034,1053,1072,1091,
1110,1130,1150,1170,1190,1210,1231,1252,1273,1294,1316,1338,1360,1382,1404,1427,
1450,1473,1497,1520,1544,1568,1593,1617,1642,1667,1693,1718,1744,1770,1797,1823,
1850,1877,1905,1932,1960,1988,2017,2045,2074,2103,2133,2162,2192,2223,2253,2284,
2315,2346,2378,2410,2442,2474,2507,2540,2573,2606,2640,2674,2708,2743,2778,2813,
2849,2884,2920,2957,2993,3030,3067,3105,3143,3181,3219,3258,3297,3336,3376,3416,
3456,3496,3537,3578,3619,3661,3703,3745,3788,3831,3874,3918,3962,4006,4050,4095 };
// -----------------------------------------------------------------------------
// UTILS
// -----------------------------------------------------------------------------
void color_string2array(const char * rgb, unsigned int * array) {
void _fromRGB(const char * rgb) {
char * p = (char *) rgb;
if (strlen(p) == 0) return;
@ -65,177 +60,200 @@ void color_string2array(const char * rgb, unsigned int * array) {
// if color begins with a # then assume HEX RGB
if (p[0] == '#') {
++p;
unsigned long value = strtol(p, NULL, 16);
array[0] = (value >> 16) & 0xFF;
array[1] = (value >> 8) & 0xFF;
array[2] = (value) & 0xFF;
if (lightHasColor()) {
++p;
unsigned long value = strtoul(p, NULL, 16);
// RGBA values are interpreted like RGB + brightness
if (strlen(p) > 7) {
_channels[0].value = (value >> 24) & 0xFF;
_channels[1].value = (value >> 16) & 0xFF;
_channels[2].value = (value >> 8) & 0xFF;
_brightness = (value & 0xFF) * LIGHT_MAX_BRIGHTNESS / 255;
} else {
_channels[0].value = (value >> 16) & 0xFF;
_channels[1].value = (value >> 8) & 0xFF;
_channels[2].value = (value) & 0xFF;
}
// it's a temperature
} else if (p[strlen(p)-1] == 'K') {
}
p[strlen(p)-1] = 0;
unsigned int temperature = atoi(p);
color_temperature2array(temperature, array);
// it's a temperature in mireds
} else if (p[0] == 'M') {
if (lightHasColor()) {
unsigned long mireds = atol(p + 1);
_fromMireds(mireds);
}
// it's a temperature in kelvin
} else if (p[0] == 'K') {
if (lightHasColor()) {
unsigned long kelvin = atol(p + 1);
_fromKelvin(kelvin);
}
// otherwise assume decimal values separated by commas
} else {
char * tok;
tok = strtok(p, ",");
array[0] = atoi(tok);
tok = strtok(NULL, ",");
unsigned char count = 0;
unsigned char channels = _channels.size();
// if there are more than one value assume R,G,B
if (tok != NULL) {
array[1] = atoi(tok);
tok = strtok(p, ",");
while (tok != NULL) {
_channels[count].value = atoi(tok);
if (++count == channels) break;
tok = strtok(NULL, ",");
if (tok != NULL) {
array[2] = atoi(tok);
} else {
array[2] = 0;
}
}
// only one value set red, green and blue to the same value
} else {
array[2] = array[1] = array[0];
// RGB but less than 3 values received
if (lightHasColor() && (count < 3)) {
_channels[1].value = _channels[0].value;
_channels[2].value = _channels[0].value;
}
}
}
void color_array2rgb(unsigned int * array, char * rgb) {
unsigned long value = array[0];
value = (value << 8) + array[1];
value = (value << 8) + array[2];
sprintf(rgb, "#%06X", value);
void _toRGB(char * rgb, size_t len, bool applyBrightness) {
if (!lightHasColor()) return;
float b = applyBrightness ? (float) _brightness / LIGHT_MAX_BRIGHTNESS : 1;
unsigned long value = 0;
value += _channels[0].value * b;
value <<= 8;
value += _channels[1].value * b;
value <<= 8;
value += _channels[2].value * b;
snprintf(rgb, len, "#%06X", value);
}
void _toRGB(char * rgb, size_t len) {
_toRGB(rgb, len, false);
}
// Thanks to Sacha Telgenhof for sharing this code in his AiLight library
// https://github.com/stelgenhof/AiLight
void color_temperature2array(unsigned int temperature, unsigned int * array) {
void _fromKelvin(unsigned long kelvin) {
// Force boundaries and conversion
temperature = constrain(temperature, 1000, 40000) / 100;
// Check we have RGB channels
if (!lightHasColor()) return;
// Calculate colors
unsigned int red = (temperature <= 66)
unsigned int red = (kelvin <= 66)
? LIGHT_MAX_VALUE
: 329.698727446 * pow((temperature - 60), -0.1332047592);
unsigned int green = (temperature <= 66)
? 99.4708025861 * log(temperature) - 161.1195681661
: 288.1221695283 * pow(temperature, -0.0755148492);
unsigned int blue = (temperature >= 66)
: 329.698727446 * pow((kelvin - 60), -0.1332047592);
unsigned int green = (kelvin <= 66)
? 99.4708025861 * log(kelvin) - 161.1195681661
: 288.1221695283 * pow(kelvin, -0.0755148492);
unsigned int blue = (kelvin >= 66)
? LIGHT_MAX_VALUE
: ((temperature <= 19)
: ((kelvin <= 19)
? 0
: 138.5177312231 * log(temperature - 10) - 305.0447927307);
: 138.5177312231 * log(kelvin - 10) - 305.0447927307);
// Save values
array[0] = constrain(red, 0, LIGHT_MAX_VALUE);
array[1] = constrain(green, 0, LIGHT_MAX_VALUE);
array[2] = constrain(blue, 0, LIGHT_MAX_VALUE);
}
_channels[0].value = constrain(red, 0, LIGHT_MAX_VALUE);
_channels[1].value = constrain(green, 0, LIGHT_MAX_VALUE);
_channels[2].value = constrain(blue, 0, LIGHT_MAX_VALUE);
// Converts a color intensity value (0..255) to a pwm value
// This takes care of positive or negative logic
unsigned int _intensity2pwm(unsigned int intensity) {
unsigned int pwm;
#if ENABLE_GAMMA_CORRECTION
pwm = (intensity < GAMMA_TABLE_SIZE) ? gamma_table[intensity] : LIGHT_PWM_RANGE;
#else
// Support integer multiples of 256 (-1) for the LIGHT_PWM_RANGE
// The divide should happen at compile time
pwm = intensity * ( (LIGHT_PWM_RANGE+1) / (LIGHT_MAX_VALUE+1) );
#endif
}
#if RGBW_INVERSE_LOGIC != 1
pwm = LIGHT_PWM_RANGE - pwm;
#endif
// Color temperature is measured in mireds (kelvin = 1e6/mired)
void _fromMireds(unsigned long mireds) {
if (mireds == 0) mireds = 1;
unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100;
_fromKelvin(kelvin);
}
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;
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 bright = isColor;
bool gamma = isColor & (getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1);
return _toPWM(_channels[id].shadow, bright, gamma, _channels[id].reverse);
}
return 0;
}
// -----------------------------------------------------------------------------
// PROVIDER
// -----------------------------------------------------------------------------
void _lightProviderSet(bool state, unsigned int red, unsigned int green, unsigned int blue) {
void _shadow() {
unsigned int white = 0;
for (unsigned int i=0; i < _channels.size(); i++) {
_channels[i].shadow = _lightState ? _channels[i].value : 0;
}
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W)
// If all set to the same value use white instead
if ((red == green) && (green == blue)) {
white = red;
red = green = blue = 0;
}
#endif
if (lightHasColor()) {
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
_my9291->setState(state);
_my9291->setColor((my9291_color_t) { red, green, blue, white });
#endif
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W)
bool useWhite = getSetting("useWhite", LIGHT_USE_WHITE).toInt() == 1;
// Check state
if (!state) red = green = blue = white = 0;
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;
_channels[1].shadow = 0;
_channels[0].shadow = 0;
}
}
analogWrite(RGBW_RED_PIN, _intensity2pwm(red));
analogWrite(RGBW_GREEN_PIN, _intensity2pwm(green));
analogWrite(RGBW_BLUE_PIN, _intensity2pwm(blue));
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
analogWrite(RGBW_WHITE_PIN, _intensity2pwm(white));
#endif
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W)
analogWrite(RGBW_WHITE_PIN, _intensity2pwm(white));
analogWrite(RGBW_WHITE2_PIN, _intensity2pwm(white));
#endif
#endif
}
}
// -----------------------------------------------------------------------------
// LIGHT MANAGEMENT
// -----------------------------------------------------------------------------
void _lightProviderUpdate() {
void lightState(bool state) {
_lightState = state;
_lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2]);
}
_shadow();
bool lightState() {
return _lightState;
}
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
void lightColor(const char * color, bool save, bool forward) {
if (_lightState) {
color_string2array(color, _lightColor);
_lightProviderSet(_lightState, _lightColor[0], _lightColor[1], _lightColor[2]);
float ratio = (float) LIGHT_MAX_VALUE / LIGHT_MAX_PWM;
char rgb[8];
color_array2rgb(_lightColor, rgb);
unsigned int red = _toPWM(0) * ratio;
unsigned int green = _toPWM(1) * ratio;
unsigned int blue = _toPWM(2) * ratio;
unsigned int white = _toPWM(3) * ratio;
_my9291->setColor((my9291_color_t) { red, green, blue, white });
_my9291->setState(true);
// Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
if (save) colorTicker.once(LIGHT_SAVE_DELAY, _lightColorSave);
} else {
// Report color to MQTT broker
if (forward) mqttSend(MQTT_TOPIC_COLOR, rgb);
_my9291->setState(false);
// Report color to WS clients
char message[20];
sprintf(message, "{\"color\": \"%s\"}", rgb);
wsSend(message);
}
}
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER
for (unsigned int i=0; i < _channels.size(); i++) {
analogWrite(_channels[i].pin, _toPWM(i));
}
#endif
String lightColor() {
char rgb[8];
color_array2rgb(_lightColor, rgb);
return String(rgb);
}
// -----------------------------------------------------------------------------
@ -243,76 +261,312 @@ String lightColor() {
// -----------------------------------------------------------------------------
void _lightColorSave() {
setSetting("color", lightColor());
for (unsigned int i=0; i < _channels.size(); i++) {
setSetting("ch", i, _channels[i].value);
}
setSetting("brightness", _brightness);
saveSettings();
}
void _lightColorRestore() {
String color = getSetting("color", LIGHT_DEFAULT_COLOR);
color_string2array(color.c_str(), _lightColor);
for (unsigned int i=0; i < _channels.size(); i++) {
_channels[i].value = getSetting("ch", i, 0).toInt();
}
_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt();
lightUpdate(false, false);
}
// -----------------------------------------------------------------------------
// MQTT
// -----------------------------------------------------------------------------
void lightMQTTCallback(unsigned int type, const char * topic, const char * payload) {
void _lightMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_COLOR);
if (lightHasColor()) {
mqttSubscribe(MQTT_TOPIC_BRIGHTNESS);
mqttSubscribe(MQTT_TOPIC_MIRED);
mqttSubscribe(MQTT_TOPIC_KELVIN);
mqttSubscribe(MQTT_TOPIC_COLOR);
}
char buffer[strlen(MQTT_TOPIC_CHANNEL) + 3];
sprintf(buffer, "%s/+", MQTT_TOPIC_CHANNEL);
mqttSubscribe(buffer);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttSubtopic((char *) topic);
if (!t.equals(MQTT_TOPIC_COLOR)) return;
lightColor(payload, true, mqttForward());
// Color temperature in mireds
if (t.equals(MQTT_TOPIC_MIRED)) {
_fromMireds(atol(payload));
lightUpdate(true, mqttForward());
}
// Color temperature in kelvins
if (t.equals(MQTT_TOPIC_KELVIN)) {
_fromKelvin(atol(payload));
lightUpdate(true, mqttForward());
}
// Color
if (t.equals(MQTT_TOPIC_COLOR)) {
lightColor(payload);
lightUpdate(true, mqttForward());
}
// Brightness
if (t.equals(MQTT_TOPIC_BRIGHTNESS)) {
_brightness = constrain(atoi(payload), 0, LIGHT_MAX_BRIGHTNESS);
lightUpdate(true, mqttForward());
}
// Channel
if (t.startsWith(MQTT_TOPIC_CHANNEL)) {
unsigned int channelID = t.substring(strlen(MQTT_TOPIC_CHANNEL)+1).toInt();
if (channelID >= _channels.size()) {
DEBUG_MSG_P(PSTR("[LIGHT] Wrong channelID (%d)\n"), channelID);
return;
}
lightChannel(channelID, atoi(payload));
lightUpdate(true, mqttForward());
}
}
}
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
unsigned char lightChannels() {
return _channels.size();
}
bool lightHasColor() {
bool useColor = getSetting("useColor", LIGHT_USE_COLOR).toInt() == 1;
return useColor && (_channels.size() > 2);
}
unsigned char lightWhiteChannels() {
return _channels.size() % 3;
}
void lightMQTT() {
char buffer[8];
if (lightHasColor()) {
// Color
_toRGB(buffer, 8, false);
mqttSend(MQTT_TOPIC_COLOR, buffer);
// Brightness
sprintf(buffer, "%d", _brightness);
mqttSend(MQTT_TOPIC_BRIGHTNESS, buffer);
}
// Channels
for (unsigned int i=0; i < _channels.size(); i++) {
sprintf(buffer, "%d", _channels[i].value);
mqttSend(MQTT_TOPIC_CHANNEL, i, buffer);
}
}
void lightUpdate(bool save, bool forward) {
_lightProviderUpdate();
// Report color & brightness to MQTT broker
if (forward) lightMQTT();
// Report color to WS clients (using current brightness setting)
{
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["colorVisible"] = 1;
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;
if (lightHasColor()) {
root["color"] = lightColor();
root["brightness"] = lightBrightness();
}
JsonArray& channels = root.createNestedArray("channels");
for (unsigned char id=0; id < lightChannels(); id++) {
channels.add(lightChannel(id));
}
String output;
root.printTo(output);
wsSend(output.c_str());
}
// Delay saving to EEPROM 5 seconds to avoid wearing it out unnecessarily
if (save) colorTicker.once(LIGHT_SAVE_DELAY, _lightColorSave);
};
void lightState(bool state) {
_lightState = state;
}
bool lightState() {
return _lightState;
}
void lightColor(const char * color) {
_fromRGB(color);
}
String lightColor() {
char rgb[8];
_toRGB(rgb, 8, false);
return String(rgb);
}
unsigned int lightChannel(unsigned char id) {
if (id <= _channels.size()) {
return _channels[id].value;
}
return 0;
}
void lightChannel(unsigned char id, unsigned int value) {
if (id <= _channels.size()) {
_channels[id].value = constrain(value, 0, LIGHT_MAX_VALUE);
}
}
unsigned int lightBrightness() {
return _brightness;
}
void lightBrightness(unsigned int b) {
_brightness = constrain(b, 0, LIGHT_MAX_BRIGHTNESS);
}
// -----------------------------------------------------------------------------
// SETUP
// -----------------------------------------------------------------------------
void _lightAPISetup() {
// API entry points (protected with apikey)
if (lightHasColor()) {
apiRegister(MQTT_TOPIC_COLOR, MQTT_TOPIC_COLOR,
[](char * buffer, size_t len) {
_toRGB(buffer, len, false);
},
[](const char * payload) {
lightColor(payload);
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_BRIGHTNESS, MQTT_TOPIC_BRIGHTNESS,
[](char * buffer, size_t len) {
snprintf(buffer, len, "%d", _brightness);
},
[](const char * payload) {
lightBrightness(atoi(payload));
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_KELVIN, MQTT_TOPIC_KELVIN,
[](char * buffer, size_t len) {},
[](const char * payload) {
_fromKelvin(atol(payload));
lightUpdate(true, true);
}
);
apiRegister(MQTT_TOPIC_MIRED, MQTT_TOPIC_MIRED,
[](char * buffer, size_t len) {},
[](const char * payload) {
_fromMireds(atol(payload));
lightUpdate(true, true);
}
);
}
for (unsigned int id=0; id<lightChannels(); id++) {
char url[15];
sprintf(url, "%s/%d", MQTT_TOPIC_CHANNEL, id);
char key[10];
sprintf(key, "%s%d", MQTT_TOPIC_CHANNEL, id);
apiRegister(url, key,
[id](char * buffer, size_t len) {
snprintf(buffer, len, "%d", lightChannel(id));
},
[id](const char * payload) {
lightChannel(id, atoi(payload));
lightUpdate(true, true);
}
);
}
}
void lightSetup() {
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
_my9291 = new my9291(MY9291_DI_PIN, MY9291_DCKI_PIN, MY9291_COMMAND);
_channels.push_back((channel_t) {0, false, 0});
_channels.push_back((channel_t) {0, false, 0});
_channels.push_back((channel_t) {0, false, 0});
_channels.push_back((channel_t) {0, false, 0});
#endif
#if (LIGHT_PROVIDER == LIGHT_PROVIDER_RGB) || (LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW)
analogWriteRange(LIGHT_PWM_RANGE);
analogWriteFreq(LIGHT_PWM_FREQUENCY);
pinMode(RGBW_RED_PIN, OUTPUT);
pinMode(RGBW_GREEN_PIN, OUTPUT);
pinMode(RGBW_BLUE_PIN, OUTPUT);
#if LIGHT_PROVIDER == LIGHT_PROVIDER_RGBW
pinMode(RGBW_WHITE_PIN, OUTPUT);
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_RGB2W
pinMode(RGBW_WHITE_PIN, OUTPUT);
pinMode(RGBW_WHITE2_PIN, OUTPUT);
#endif
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER
_lightColorRestore();
#ifdef LIGHT_CH1_PIN
_channels.push_back((channel_t) {LIGHT_CH1_PIN, LIGHT_CH1_INVERSE, 0});
#endif
// API entry points (protected with apikey)
apiRegister(MQTT_TOPIC_COLOR, MQTT_TOPIC_COLOR,
[](char * buffer, size_t len) {
snprintf(buffer, len, "%s", lightColor().c_str());
},
[](const char * payload) {
lightColor(payload, true, mqttForward());
#ifdef LIGHT_CH2_PIN
_channels.push_back((channel_t) {LIGHT_CH2_PIN, LIGHT_CH2_INVERSE, 0});
#endif
#ifdef LIGHT_CH3_PIN
_channels.push_back((channel_t) {LIGHT_CH3_PIN, LIGHT_CH3_INVERSE, 0});
#endif
#ifdef LIGHT_CH4_PIN
_channels.push_back((channel_t) {LIGHT_CH4_PIN, LIGHT_CH4_INVERSE, 0});
#endif
#ifdef LIGHT_CH5_PIN
_channels.push_back((channel_t) {LIGHT_CH5_PIN, LIGHT_CH5_INVERSE, 0});
#endif
analogWriteRange(LIGHT_MAX_PWM+1);
analogWriteFreq(LIGHT_PWM_FREQUENCY);
for (unsigned int i=0; i < _channels.size(); i++) {
pinMode(_channels[i].pin, OUTPUT);
}
);
mqttRegister(lightMQTTCallback);
#endif
_lightColorRestore();
_lightAPISetup();
mqttRegister(_lightMQTTCallback);
}


+ 58
- 45
code/espurna/mqtt.ino View File

@ -24,7 +24,10 @@ PubSubClient mqtt(mqttWiFiClient);
bool _mqttConnected = false;
#endif
String mqttTopic;
String _mqttTopic;
String _mqttSetter;
String _mqttGetter;
bool _mqttForward;
char *_mqttUser = 0;
char *_mqttPass = 0;
@ -53,30 +56,17 @@ void mqttDisconnect() {
mqtt.disconnect();
}
void buildTopics() {
// Replace identifier
mqttTopic = getSetting("mqttTopic", MQTT_TOPIC);
mqttTopic.replace("{identifier}", getSetting("hostname"));
if (!mqttTopic.endsWith("/")) mqttTopic = mqttTopic + "/";
}
bool mqttForward() {
return _mqttForward;
}
String mqttSubtopic(char * topic) {
String response;
String t = String(topic);
String mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER);
if (t.startsWith(mqttTopic) && t.endsWith(mqttSetter)) {
response = t.substring(mqttTopic.length(), t.length() - mqttSetter.length());
if (t.startsWith(_mqttTopic) && t.endsWith(_mqttSetter)) {
response = t.substring(_mqttTopic.length(), t.length() - _mqttSetter.length());
}
return response;
}
void mqttSendRaw(const char * topic, const char * message) {
@ -107,7 +97,7 @@ void _mqttFlush() {
String output;
root.printTo(output);
String path = mqttTopic + String(MQTT_TOPIC_JSON);
String path = _mqttTopic + String(MQTT_TOPIC_JSON);
mqttSendRaw(path.c_str(), output.c_str());
for (unsigned char i = 0; i < _mqtt_queue.size(); i++) {
@ -128,8 +118,7 @@ void mqttSend(const char * topic, const char * message, bool force) {
_mqtt_queue.push_back(element);
mqttFlushTicker.once_ms(MQTT_USE_JSON_DELAY, _mqttFlush);
} else {
String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER);
String path = mqttTopic + String(topic) + mqttGetter;
String path = _mqttTopic + String(topic) + _mqttGetter;
mqttSendRaw(path.c_str(), message);
}
}
@ -161,17 +150,42 @@ void mqttSubscribeRaw(const char * topic) {
}
void mqttSubscribe(const char * topic) {
String mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER);
String path = mqttTopic + String(topic) + mqttSetter;
String path = _mqttTopic + String(topic) + _mqttSetter;
mqttSubscribeRaw(path.c_str());
}
void mqttRegister(void (*callback)(unsigned int, const char *, const char *)) {
_mqtt_callbacks.push_back(callback);
}
// -----------------------------------------------------------------------------
// Callbacks
// -----------------------------------------------------------------------------
void mqttRegister(void (*callback)(unsigned int, const char *, const char *)) {
_mqtt_callbacks.push_back(callback);
void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_ACTION);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttSubtopic((char *) topic);
// Actions
if (t.equals(MQTT_TOPIC_ACTION)) {
if (strcmp(payload, MQTT_ACTION_RESET) == 0) {
customReset(CUSTOM_RESET_MQTT);
ESP.restart();
}
}
}
}
void _mqttOnConnect() {
@ -183,14 +197,11 @@ void _mqttOnConnect() {
#endif
// Build MQTT topics
buildTopics();
mqttConfigure();
// Send first Heartbeat
heartbeat();
// Subscribe to system topics
mqttSubscribe(MQTT_TOPIC_ACTION);
// Send connect event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(*_mqtt_callbacks[i])(MQTT_CONNECT_EVENT, NULL, NULL);
@ -211,26 +222,18 @@ void _mqttOnDisconnect() {
void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
if (len == 0) return;
char message[len + 1];
strlcpy(message, (char *) payload, len + 1);
DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s"), topic, message);
#if MQTT_SKIP_RETAINED
if (millis() - mqttConnectedAt < MQTT_SKIP_TIME) {
DEBUG_MSG_P(PSTR(" - SKIPPED\n"));
DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s - SKIPPED\n"), topic, message);
return;
}
#endif
DEBUG_MSG_P(PSTR("\n"));
// Check system topics
String t = mqttSubtopic((char *) topic);
if (t.equals(MQTT_TOPIC_ACTION)) {
if (strcmp(message, MQTT_ACTION_RESET) == 0) {
customReset(CUSTOM_RESET_MQTT);
ESP.restart();
}
}
DEBUG_MSG_P(PSTR("[MQTT] Received %s => %s\n"), topic, message);
// Send message event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
@ -288,7 +291,7 @@ void mqttConnect() {
_mqttUser = strdup(getSetting("mqttUser").c_str());
_mqttPass = strdup(getSetting("mqttPassword").c_str());
if (_mqttWill) free(_mqttWill);
_mqttWill = strdup((mqttTopic + MQTT_TOPIC_STATUS).c_str());
_mqttWill = strdup((_mqttTopic + MQTT_TOPIC_STATUS).c_str());
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d"), host, port);
mqtt.setServer(host, port);
@ -340,14 +343,20 @@ void mqttConnect() {
free(host);
String mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER);
String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER);
_mqttForward = !mqttGetter.equals(mqttSetter);
}
}
void mqttConfigure() {
// Replace identifier
_mqttTopic = getSetting("mqttTopic", MQTT_TOPIC);
_mqttTopic.replace("{identifier}", getSetting("hostname"));
if (!_mqttTopic.endsWith("/")) _mqttTopic = _mqttTopic + "/";
_mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER);
_mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER);
_mqttForward = !_mqttGetter.equals(_mqttSetter);
}
void mqttSetup() {
#if MQTT_USE_ASYNC
mqtt.onConnect([](bool sessionPresent) {
@ -369,9 +378,11 @@ void mqttSetup() {
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
DEBUG_MSG_P(PSTR("[MQTT] Not authorized\n"));
}
#if ASYNC_TCP_SSL_ENABLED
if (reason == AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT) {
DEBUG_MSG_P(PSTR("[MQTT] Bad fingerprint\n"));
}
#endif
_mqttOnDisconnect();
});
mqtt.onMessage([](char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
@ -388,7 +399,9 @@ void mqttSetup() {
_mqttOnMessage(topic, (char *) payload, length);
});
#endif
buildTopics();
mqttRegister(_mqttCallback);
}
void mqttLoop() {


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

@ -35,7 +35,7 @@ void otaSetup() {
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%\r"), (progress / (total / 100)));
DEBUG_MSG_P(PSTR("[OTA] Progress: %u%%%%\r"), (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {


+ 27
- 9
code/espurna/relay.ino View File

@ -20,6 +20,7 @@ typedef struct {
unsigned long delay_off;
unsigned int floodWindowStart;
unsigned char floodWindowChanges;
bool scheduled;
unsigned int scheduledStatusTime;
bool scheduledStatus;
bool scheduledReport;
@ -40,6 +41,10 @@ void relayProviderStatus(unsigned char id, bool status) {
if (id >= _relays.size()) return;
#if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE
rfbStatus(id, status);
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
_dual_status ^= (1 << id);
Serial.flush();
@ -52,6 +57,7 @@ void relayProviderStatus(unsigned char id, bool status) {
#if RELAY_PROVIDER == RELAY_PROVIDER_LIGHT
lightState(status);
lightUpdate(true, true);
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_RELAY
@ -64,6 +70,10 @@ bool relayProviderStatus(unsigned char id) {
if (id >= _relays.size()) return false;
#if RELAY_PROVIDER == RELAY_PROVIDER_RFBRIDGE
return _relays[id].scheduledStatus;
#endif
#if RELAY_PROVIDER == RELAY_PROVIDER_DUAL
return ((_dual_status & (1 << id)) > 0);
#endif
@ -142,7 +152,9 @@ bool relayStatus(unsigned char id, bool status, bool report) {
bool changed = false;
#if TRACK_RELAY_STATUS
if (relayStatus(id) != status) {
#endif
unsigned int currentTime = millis();
unsigned int floodWindowEnd = _relays[id].floodWindowStart + 1000 * RELAY_FLOOD_WINDOW;
@ -169,6 +181,7 @@ bool relayStatus(unsigned char id, bool status, bool report) {
}
_relays[id].scheduled = true;
_relays[id].scheduledStatus = status;
if (report) _relays[id].scheduledReport = true;
@ -178,7 +191,9 @@ bool relayStatus(unsigned char id, bool status, bool report) {
changed = true;
#if TRACK_RELAY_STATUS
}
#endif
return changed;
}
@ -400,16 +415,14 @@ void relayInfluxDB(unsigned char id) {
void relaySetup() {
#if defined(SONOFF_DUAL)
// Two dummy relays for the dual
_relays.push_back((relay_t) {0, 0, 0, RELAY1_DELAY_ON, RELAY1_DELAY_OFF});
_relays.push_back((relay_t) {0, 0, 0, RELAY2_DELAY_ON, RELAY2_DELAY_OFF});
#elif defined(AI_LIGHT) | defined(LED_CONTROLLER) | defined(H801_LED_CONTROLLER)
// Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge
#ifdef DUMMY_RELAY_COUNT
// One dummy relay for the AI Thinker Light & Magic Home and H801 led controllers
_relays.push_back((relay_t) {0, 0, 0, RELAY1_DELAY_ON, RELAY1_DELAY_OFF});
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, 0});
_relays[i].scheduled = false;
}
#else
@ -454,7 +467,11 @@ void relayLoop(void) {
unsigned int currentTime = millis();
bool status = _relays[id].scheduledStatus;
#if TRACK_RELAY_STATUS
if (relayStatus(id) != status && currentTime >= _relays[id].scheduledStatusTime) {
#else
if (_relays[id].scheduled && currentTime >= _relays[id].scheduledStatusTime) {
#endif
DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, status ? "ON" : "OFF");
@ -486,6 +503,7 @@ void relayLoop(void) {
relayInfluxDB(id);
#endif
_relays[id].scheduled = false;
_relays[id].scheduledReport = false;
}


+ 307
- 0
code/espurna/rfbridge.ino View File

@ -0,0 +1,307 @@
/*
ITEAD RF BRIDGE MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#ifdef SONOFF_RFBRIDGE
// -----------------------------------------------------------------------------
// DEFINITIONS
// -----------------------------------------------------------------------------
#define RF_MESSAGE_SIZE 9
#define RF_CODE_START 0xAA
#define RF_CODE_ACK 0xA0
#define RF_CODE_LEARN 0xA1
#define RF_CODE_LEARN_KO 0xA2
#define RF_CODE_LEARN_OK 0xA3
#define RF_CODE_RFIN 0xA4
#define RF_CODE_RFOUT 0xA5
#define RF_CODE_STOP 0x55
// -----------------------------------------------------------------------------
// GLOBALS TO THE MODULE
// -----------------------------------------------------------------------------
unsigned char _uartbuf[RF_MESSAGE_SIZE+3] = {0};
unsigned char _uartpos = 0;
unsigned char _learnId = 0;
bool _learnStatus = true;
bool _rfbin = false;
// -----------------------------------------------------------------------------
// PRIVATES
// -----------------------------------------------------------------------------
void _rfbAck() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending ACK\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_ACK);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
}
void _rfbLearn() {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending LEARN\n"));
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_LEARN);
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
char wsb[100];
sprintf_P(wsb, PSTR("{\"action\": \"rfbLearn\", \"data\":{\"id\": %d, \"status\": %d}}"), _learnId, _learnStatus ? 1 : 0);
wsSend(wsb);
}
void _rfbSend(byte * message) {
Serial.println();
Serial.write(RF_CODE_START);
Serial.write(RF_CODE_RFOUT);
for (unsigned char j=0; j<RF_MESSAGE_SIZE; j++) {
Serial.write(message[j]);
}
Serial.write(RF_CODE_STOP);
Serial.flush();
Serial.println();
}
void _rfbSend(byte * message, int times) {
char buffer[RF_MESSAGE_SIZE];
_rfbToChar(message, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Sending MESSAGE '%s' %d time(s)\n"), buffer, times);
for (int i=0; i<times; i++) {
if (i>0) {
unsigned long start = millis();
while (millis() - start < RF_SEND_DELAY) delay(1);
}
_rfbSend(message);
}
}
void _rfbDecode() {
byte action = _uartbuf[0];
char buffer[RF_MESSAGE_SIZE * 2 + 1] = {0};
DEBUG_MSG_P(PSTR("[RFBRIDGE] Action 0x%02X\n"), action);
if (action == RF_CODE_LEARN_KO) {
_rfbAck();
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn timeout\n"));
wsSend("{\"action\": \"rfbTimeout\"}");
}
if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
_rfbToChar(&_uartbuf[1], buffer);
mqttSend(MQTT_TOPIC_RFIN, buffer);
_rfbAck();
}
if (action == RF_CODE_LEARN_OK) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Learn success\n"));
rfbStore(_learnId, _learnStatus, buffer);
// Websocket update
char wsb[100];
sprintf_P(wsb, PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"%s\"}]}"), _learnId, _learnStatus ? 1 : 0, buffer);
wsSend(wsb);
}
if (action == RF_CODE_RFIN) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
// Look for the code
unsigned char id, status;
bool found = false;
for (id=0; id<relayCount(); id++) {
for (status=0; status<2; status++) {
String code = rfbRetrieve(id, status == 1);
if (code.length()) {
if (code.endsWith(&buffer[12])) {
found = true;
break;
}
}
}
if (found) break;
}
if (found) {
_rfbin = true;
relayStatus(id, status == 1);
}
}
}
void _rfbReceive() {
static bool receiving = false;
while (Serial.available()) {
yield();
byte c = Serial.read();
//DEBUG_MSG_P(PSTR("[RFBRIDGE] Received 0x%02X\n"), c);
if (receiving) {
if (c == RF_CODE_STOP) {
_rfbDecode();
receiving = false;
} else {
_uartbuf[_uartpos++] = c;
}
} else if (c == RF_CODE_START) {
_uartpos = 0;
receiving = true;
}
}
}
bool _rfbCompare(const char * code1, const char * code2) {
return strcmp(&code1[12], &code2[12]) == 0;
}
bool _rfbSameOnOff(unsigned char id) {
return _rfbCompare(rfbRetrieve(id, true).c_str(), rfbRetrieve(id, false).c_str());
}
/*
From an hexa char array ("A220EE...") to a byte array (half the size)
*/
bool _rfbToArray(const char * in, byte * out) {
if (strlen(in) != RF_MESSAGE_SIZE * 2) return false;
char tmp[3] = {0};
for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
memcpy(tmp, &in[p*2], 2);
out[p] = strtol(tmp, NULL, 16);
}
return true;
}
/*
From a byte array to an hexa char array ("A220EE...", double the size)
*/
bool _rfbToChar(byte * in, char * out) {
for (unsigned char p = 0; p<RF_MESSAGE_SIZE; p++) {
sprintf(&out[p*2], "%02X", in[p]);
}
return true;
}
void _rfbMqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
char buffer[strlen(MQTT_TOPIC_RFLEARN) + 3];
sprintf(buffer, "%s/+", MQTT_TOPIC_RFLEARN);
mqttSubscribe(buffer);
mqttSubscribe(MQTT_TOPIC_RFOUT);
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttSubtopic((char *) topic);
// Check if should go into learn mode
if (t.startsWith(MQTT_TOPIC_RFLEARN)) {
_learnId = t.substring(strlen(MQTT_TOPIC_RFLEARN)+1).toInt();
if (_learnId >= relayCount()) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Wrong learnID (%d)\n"), _learnId);
return;
}
_learnStatus = (char)payload[0] != '0';
_rfbLearn();
}
if (t.equals(MQTT_TOPIC_RFOUT)) {
byte message[RF_MESSAGE_SIZE];
if (_rfbToArray(payload, message)) {
_rfbSend(message, 1);
}
}
}
}
// -----------------------------------------------------------------------------
// PUBLIC
// -----------------------------------------------------------------------------
void rfbStore(unsigned char id, bool status, const char * code) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Storing %d-%s => '%s'\n"), id, status ? "ON" : "OFF", code);
char key[8] = {0};
sprintf(key, "rfb%d%s", id, status ? "on" : "off");
setSetting(key, code);
}
String rfbRetrieve(unsigned char id, bool status) {
char key[8] = {0};
sprintf(key, "rfb%d%s", id, status ? "on" : "off");
return getSetting(key);
}
void rfbStatus(unsigned char id, bool status) {
String value = rfbRetrieve(id, status);
if (value.length() > 0) {
bool same = _rfbSameOnOff(id);
byte message[RF_MESSAGE_SIZE];
_rfbToArray(value.c_str(), message);
unsigned char times = RF_SEND_TIMES;
if (same) times = _rfbin ? 0 : 1;
_rfbSend(message, times);
}
}
void rfbLearn(unsigned char id, bool status) {
_learnId = id;
_learnStatus = status;
_rfbLearn();
}
void rfbForget(unsigned char id, bool status) {
char key[8] = {0};
sprintf(key, "rfb%d%s", id, status ? "on" : "off");
delSetting(key);
// Websocket update
char wsb[100];
sprintf_P(wsb, PSTR("{\"rfb\":[{\"id\": %d, \"status\": %d, \"data\": \"\"}]}"), id, status ? 1 : 0);
wsSend(wsb);
}
// -----------------------------------------------------------------------------
// SETUP & LOOP
// -----------------------------------------------------------------------------
void rfbSetup() {
mqttRegister(_rfbMqttCallback);
}
void rfbLoop() {
_rfbReceive();
}
#endif

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

@ -13,7 +13,11 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#define AUTO_SAVE 1
#ifdef DEBUG_PORT
Embedis embedis(DEBUG_PORT);
#else
Embedis embedis(Serial);
#endif
// -----------------------------------------------------------------------------
// Settings
@ -134,14 +138,64 @@ void settingsSetup() {
});
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
Embedis::command( F("COLOR"), [](Embedis* e) {
if (e->argc > 1) {
String color = String(e->argv[1]);
lightColor(color.c_str(), true, true);
if (lightHasColor()) {
Embedis::command( F("COLOR"), [](Embedis* e) {
if (e->argc > 1) {
String color = String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
e->stream->printf("Color: %s\n", lightColor().c_str());
e->response(Embedis::OK);
});
Embedis::command( F("BRIGHTNESS"), [](Embedis* e) {
if (e->argc > 1) {
lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true);
}
e->stream->printf("Brightness: %d\n", lightBrightness());
e->response(Embedis::OK);
});
Embedis::command( F("MIRED"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("M") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
e->stream->printf("Color: %s\n", lightColor().c_str());
e->response(Embedis::OK);
});
Embedis::command( F("KELVIN"), [](Embedis* e) {
if (e->argc > 1) {
String color = String("K") + String(e->argv[1]);
lightColor(color.c_str());
lightUpdate(true, true);
}
e->stream->printf("Color: %s\n", lightColor().c_str());
e->response(Embedis::OK);
});
}
e->stream->printf("Color: %s\n", lightColor().c_str());
e->response(Embedis::OK);
});
Embedis::command( F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) {
return e->response(Embedis::ARGS_ERROR);
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
int value = String(e->argv[2]).toInt();
lightChannel(id, value);
lightUpdate(true, true);
}
e->stream->printf("Channel #%d: %d\n", id, lightChannel(id));
e->response(Embedis::OK);
});
#endif
Embedis::command( F("EEPROM"), [](Embedis* e) {
@ -175,6 +229,15 @@ void settingsSetup() {
}
void settingsDump() {
unsigned int size = settingsKeyCount();
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
DEBUG_MSG_P(PSTR("%s => %s\n"), key.c_str(), value.c_str());
}
}
void settingsLoop() {
embedis.process();
}
@ -199,6 +262,14 @@ String getSetting(const String& key) {
return getSetting(key, "");
}
bool setBoolSetting(const String& key, bool value, bool defaultValue) {
if (value == defaultValue) {
return delSetting(key);
} else {
return setSetting(key, value ? 1 : 0);
}
}
template<typename T> bool setSetting(const String& key, T value) {
return Embedis::set(key, String(value));
}
@ -228,4 +299,5 @@ void saveSettings() {
#if not AUTO_SAVE
EEPROM.commit();
#endif
settingsDump();
}

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


+ 89
- 51
code/espurna/web.ino View File

@ -97,6 +97,21 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
ESP.restart();
}
#ifdef SONOFF_RFBRIDGE
if (action.equals("rfblearn") && root.containsKey("data")) {
JsonObject& data = root["data"];
rfbLearn(data["id"], data["status"]);
}
if (action.equals("rfbforget") && root.containsKey("data")) {
JsonObject& data = root["data"];
rfbForget(data["id"], data["status"]);
}
if (action.equals("rfbsend") && root.containsKey("data")) {
JsonObject& data = root["data"];
rfbStore(data["id"], data["status"], data["data"].as<const char*>());
}
#endif
if (action.equals("restore") && root.containsKey("data")) {
JsonObject& data = root["data"];
@ -134,7 +149,7 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
if (data.containsKey("status")) {
bool state = (strcmp(data["status"], "1") == 0);
bool status = (strcmp(data["status"], "1") == 0);
unsigned int relayID = 0;
if (data.containsKey("id")) {
@ -142,16 +157,36 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
relayID = value.toInt();
}
relayStatus(relayID, state);
relayStatus(relayID, status);
}
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (action.equals("color") && root.containsKey("data")) {
lightColor(root["data"], true, true);
if (lightHasColor()) {
if (action.equals("color") && root.containsKey("data")) {
lightColor(root["data"]);
lightUpdate(true, true);
}
if (action.equals("brightness") && root.containsKey("data")) {
lightBrightness(root["data"]);
lightUpdate(true, true);
}
}
if (action.equals("channel") && root.containsKey("data")) {
JsonObject& data = root["data"];
if (data.containsKey("id") && data.containsKey("value")) {
lightChannel(data["id"], data["value"]);
lightUpdate(true, true);
}
}
#endif
};
@ -171,6 +206,9 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
bool apiEnabled = false;
bool dstEnabled = false;
bool mqttUseJson = false;
bool useColor = false;
bool useWhite = false;
bool useGamma = false;
#if ASYNC_TCP_SSL_ENABLED
bool mqttUseSSL = false;
#endif
@ -259,29 +297,17 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
}
// Checkboxes
if (key == "apiEnabled") {
apiEnabled = true;
continue;
}
if (key == "ntpDST") {
dstEnabled = true;
continue;
}
if (key == "mqttUseJson") {
mqttUseJson = true;
continue;
}
if (key == "apiEnabled") { apiEnabled = true; continue; }
if (key == "ntpDST") { dstEnabled = true; continue; }
if (key == "mqttUseJson") { mqttUseJson = true; continue; }
if (key == "useColor") { useColor = true; continue; }
if (key == "useWhite") { useWhite = true; continue; }
if (key == "useGamma") { useGamma = true; continue; }
#if ASYNC_TCP_SSL_ENABLED
if (key == "mqttUseSSL") {
mqttUseSSL = true;
continue;
}
if (key == "mqttUseSSL") { mqttUseSSL = true; continue; }
#endif
#if ENABLE_FAUXMO
if (key == "fauxmoEnabled") {
fauxmoEnabled = true;
continue;
}
if (key == "fauxmoEnabled") { fauxmoEnabled = true; continue; }
#endif
if (key == "ssid") {
@ -317,29 +343,17 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
if (webMode == WEB_MODE_NORMAL) {
// Checkboxes
if (apiEnabled != (getSetting("apiEnabled").toInt() == 1)) {
setSetting("apiEnabled", apiEnabled);
save = changed = true;
}
if (dstEnabled != (getSetting("ntpDST").toInt() == 1)) {
setSetting("ntpDST", dstEnabled);
save = changed = changedNTP = true;
}
if (mqttUseJson != (getSetting("mqttUseJson").toInt() == 1)) {
setSetting("mqttUseJson", mqttUseJson);
save = changed = true;
}
setBoolSetting("apiEnabled", apiEnabled, ENABLE_API);
setBoolSetting("ntpDST", dstEnabled, NTP_DAY_LIGHT);
setBoolSetting("mqttUseJson", mqttUseJson, MQTT_USE_JSON);
setBoolSetting("useColor", useColor, LIGHT_USE_COLOR);
setBoolSetting("useWhite", useWhite, LIGHT_USE_WHITE);
setBoolSetting("useGamma", useGamma, LIGHT_USE_GAMMA);
#if ASYNC_TCP_SSL_ENABLED
if (mqttUseSSL != (getSetting("mqttUseSSL", 0). toInt() == 1)) {
setSetting("mqttUseSSL", mqttUseSSL);
save = changed = changedMQTT = true;
}
setBoolSetting("mqttUseSSL", mqttUseSSL, MQTT_USE_SSL);
#endif
#if ENABLE_FAUXMO
if (fauxmoEnabled != (getSetting("fauxmoEnabled").toInt() == 1)) {
setSetting("fauxmoEnabled", fauxmoEnabled);
save = changed = true;
}
setBoolSetting("fauxmoEnabled", fauxmoEnabled, FAUXMO_ENABLED);
#endif
// Clean wifi networks
@ -383,7 +397,7 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
#if ENABLE_INFLUXDB
influxDBConfigure();
#endif
buildTopics();
mqttConfigure();
#if ENABLE_RF
rfBuildCodes();
@ -477,7 +491,17 @@ void _wsStart(uint32_t client_id) {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
root["colorVisible"] = 1;
root["color"] = lightColor();
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;
if (lightHasColor()) {
root["color"] = lightColor();
root["brightness"] = lightBrightness();
}
JsonArray& channels = root.createNestedArray("channels");
for (unsigned char id=0; id < lightChannels(); id++) {
channels.add(lightChannel(id));
}
#endif
root["relayMode"] = getSetting("relayMode", RELAY_MODE);
@ -492,7 +516,7 @@ void _wsStart(uint32_t client_id) {
root["webPort"] = getSetting("webPort", WEBSERVER_PORT).toInt();
root["apiEnabled"] = getSetting("apiEnabled").toInt() == 1;
root["apiEnabled"] = getSetting("apiEnabled", ENABLE_API).toInt() == 1;
root["apiKey"] = getSetting("apiKey");
root["tmpUnits"] = getSetting("tmpUnits", TMP_UNITS).toInt();
@ -590,6 +614,20 @@ void _wsStart(uint32_t client_id) {
root["powPowerFactor"] = String(getPowerFactor(), 2);
#endif
#ifdef SONOFF_RFBRIDGE
root["rfbVisible"] = 1;
root["rfbCount"] = relayCount();
JsonArray& rfb = root.createNestedArray("rfb");
for (byte id=0; id<relayCount(); id++) {
for (byte status=0; status<2; status++) {
JsonObject& node = rfb.createNestedObject();
node["id"] = id;
node["status"] = status;
node["data"] = rfbRetrieve(id, status == 1);
}
}
#endif
root["maxNetworks"] = WIFI_MAX_NETWORKS;
JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
@ -689,7 +727,7 @@ bool _authenticate(AsyncWebServerRequest *request) {
bool _authAPI(AsyncWebServerRequest *request) {
if (getSetting("apiEnabled").toInt() == 0) {
if (getSetting("apiEnabled", ENABLE_API).toInt() == 0) {
DEBUG_MSG_P(PSTR("[WEBSERVER] HTTP API is not enabled\n"));
request->send(403);
return false;
@ -739,8 +777,8 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
}
// Get response from callback
char value[10];
(api.getFn)(value, 10);
char value[API_BUFFER_SIZE];
(api.getFn)(value, API_BUFFER_SIZE);
char *p = ltrim(value);
// The response will be a 404 NOT FOUND if the resource is not available
@ -803,7 +841,7 @@ void _onAPIs(AsyncWebServerRequest *request) {
} else {
for (unsigned int i=0; i < _apis.size(); i++) {
output += _apis[i].key + String(" -> ") + _apis[i].url + String("\n<br />");
output += _apis[i].key + String(" -> ") + _apis[i].url + String("\n");
}
request->send(200, "text/plain", output);
}


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

@ -46,7 +46,8 @@
background: #1f8dd6;
}
.button-reset,
.button-reconnect {
.button-reconnect,
.button-rfb-forget {
background: rgb(202, 60, 60);
}
.button-upgrade {
@ -58,13 +59,15 @@
background: rgb(0, 202, 0);
margin-left: 5px;
}
.button-add-network {
.button-add-network,
.button-rfb-learn {
background: rgb(28, 184, 65);
}
.button-del-network {
background: rgb(202, 60, 60);
}
.button-more-network {
.button-more-network,
.button-rfb-send {
background: rgb(223, 117, 20);
}
.button-settings-backup,
@ -149,3 +152,34 @@ div.state {
.right {
text-align: right;
}
.slider {
margin-top: 10px;
}
.slider .noUi-target {
background: #D2D2D2;
}
.slider .noUi-connect {
background: #FFFFFF;
}
.slider .noUi-tooltip {
font-size: 10px;
border: 0px;
padding: 0px;
margin-bottom: -4px;
#panel-rfb fieldset {
margin: 10px 2px;
padding: 20px;
}
#panel-rfb input {
margin-right: 5px;
}
#panel-rfb label {
padding-top: 5px;
}
#panel-rfb input {
text-align: center;
}

+ 169
- 9
code/html/custom.js View File

@ -4,6 +4,7 @@ var maxNetworks;
var protocol;
var host;
var port;
var useWhite = false;
// http://www.the-art-of-web.com/javascript/validate-password/
function checkPassword(str) {
@ -32,12 +33,6 @@ function validateForm(form) {
}
function doColor() {
var color = $(this).wheelColorPicker('getValue', 'css');
websock.send(JSON.stringify({'action': 'color', 'data' : color}));
}
function doUpdate() {
var form = $("#formSave");
if (validateForm(form)) {
@ -285,6 +280,130 @@ function addNetwork() {
}
function initColor() {
// check if already initialized
var done = $("#colors > div").length;
if (done > 0) return;
// add template
var template = $("#colorTemplate").children();
var line = $(template).clone();
line.appendTo("#colors");
// init color wheel
$('input[name="color"]').wheelColorPicker({
sliders: 'wrgbp'
}).on('sliderup', function() {
var value = $(this).wheelColorPicker('getValue', 'css');
websock.send(JSON.stringify({'action': 'color', 'data' : value}));
});
// init bright slider
noUiSlider.create($("#brightness").get(0), {
start: 255,
connect: [true, false],
tooltips: true,
format: {
to: function (value) { return parseInt(value); },
from: function (value) { return value; }
},
orientation: "horizontal",
range: { 'min': 0, 'max': 255}
}).on("change", function() {
var value = parseInt(this.get());
websock.send(JSON.stringify({'action': 'brightness', 'data' : value}));
});
}
function initChannels(num) {
// check if already initialized
var done = $("#channels > div").length > 0;
if (done) return;
// does it have color channels?
var colors = $("#colors > div").length > 0;
// calculate channels to create
var max = num;
if (colors) {
max = num % 3;
if ((max > 0) & useWhite) max--;
}
var start = num - max;
// add templates
var template = $("#channelTemplate").children();
for (var i=0; i<max; i++) {
var channel_id = start + i;
var line = $(template).clone();
$(".slider", line).attr("data", channel_id);
$("label", line).html("Channel " + (channel_id + 1));
noUiSlider.create($(".slider", line).get(0), {
start: 0,
connect: [true, false],
tooltips: true,
format: {
to: function (value) { return parseInt(value); },
from: function (value) { return value; }
},
orientation: "horizontal",
range: { 'min': 0, 'max': 255 }
}).on("change", function() {
var id = $(this.target).attr("data");
var value = parseInt(this.get());
websock.send(JSON.stringify({'action': 'channel', 'data': { 'id': id, 'value': value }}));
});
line.appendTo("#channels");
}
}
function addRfbNode() {
var numNodes = $("#rfbNodes > fieldset").length;
var template = $("#rfbNodeTemplate").children();
var line = $(template).clone();
var status = true;
$("span", line).html(numNodes+1);
$(line).find("input").each(function() {
$(this).attr("data_id", numNodes);
$(this).attr("data_status", status ? 1 : 0);
status = !status;
});
$(line).find(".button-rfb-learn").on('click', rfbLearn);
$(line).find(".button-rfb-forget").on('click', rfbForget);
$(line).find(".button-rfb-send").on('click', rfbSend);
line.appendTo("#rfbNodes");
return line;
}
function rfbLearn() {
var parent = $(this).parents(".pure-g");
var input = $("input", parent);
websock.send(JSON.stringify({'action': 'rfblearn', 'data' : {'id' : input.attr("data_id"), 'status': input.attr("data_status")}}));
}
function rfbForget() {
var parent = $(this).parents(".pure-g");
var input = $("input", parent);
websock.send(JSON.stringify({'action': 'rfbforget', 'data' : {'id' : input.attr("data_id"), 'status': input.attr("data_status")}}));
}
function rfbSend() {
var parent = $(this).parents(".pure-g");
var input = $("input", parent);
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',
@ -338,15 +457,59 @@ function processData(data) {
}, 1000);
}
if (data.action == "rfbLearn") {
// Nothing to do?
}
if (data.action == "rfbTimeout") {
// Nothing to do?
}
return;
}
if (key == "rfbCount") {
for (var i=0; i<data.rfbCount; i++) addRfbNode();
return;
}
if (key == "rfb") {
var nodes = data.rfb;
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"]);
}
return;
}
if (key == "color") {
initColor();
$("input[name='color']").wheelColorPicker('setValue', data[key], true);
return;
}
if (key == "brightness") {
var slider = $("#brightness");
if (slider.length) slider.get(0).noUiSlider.set(data[key]);
return;
}
if (key == "channels") {
var len = data[key].length;
initChannels(len);
for (var i=0; i<len; i++) {
var slider = $("div.channels[data=" + i + "]");
if (slider.length) slider.get(0).noUiSlider.set(data[key][i]);
}
return;
}
if (key == "useWhite") {
useWhite = data[key];
}
if (key == "maxNetworks") {
maxNetworks = parseInt(data.maxNetworks);
return;
@ -540,9 +703,6 @@ function init() {
$(".button-add-network").on('click', function() {
$("div.more", addNetwork()).toggle();
});
$('input[name="color"]').wheelColorPicker({
sliders: 'wsvp'
}).on('sliderup', doColor);
var protocol = location.protocol;
var host = window.location.hostname;


+ 115
- 20
code/html/index.html View File

@ -14,6 +14,7 @@
<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" />
<!-- endbuild -->
</head>
@ -45,7 +46,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="1" />
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="1" 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 />
@ -54,7 +55,7 @@
<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="2" />
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="2" autocomplete="false" />
</div>
<button class="pure-button button-update-password">Update</button>
@ -114,6 +115,10 @@
<a href="#" class="pure-menu-link" data="panel-power">POWER</a>
</li>
<li class="pure-menu-item module module-rfb">
<a href="#" class="pure-menu-link" data="panel-rfb">RFBRIDGE</a>
</li>
<li class="pure-menu-item">
<a href="#" class="pure-menu-link" data="panel-admin">ADMIN</a>
</li>
@ -154,9 +159,10 @@
<div id="relays">
</div>
<div class="pure-g module module-color">
<label class="pure-u-1 pure-u-sm-1-4">Color</label>
<input class="pure-u-1 pure-u-sm-1-4" data-wcp-layout="block" name="color" readonly />
<div id="colors">
</div>
<div id="channels">
</div>
<div class="pure-g module module-analog">
@ -342,15 +348,39 @@
</div>
</div>
<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-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>
</div>
<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-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>
</div>
<div class="pure-g module module-color">
<div class="pure-u-1 pure-u-sm-1-4"><label for="useGamma">Use gamma correction</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="useGamma" tabindex="10" /></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 gamma correction for RGB channels.<br />Will only work if "use colorpicker" above is also ON.</div>
</div>
<div class="pure-g module module-fauxmo">
<div class="pure-u-1 pure-u-sm-1-4"><label for="fauxmoEnabled">Alexa integration</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="fauxmoEnabled" tabindex="7" /></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="fauxmoEnabled" tabindex="11" /></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="8" value="0"> Celsius (ºC)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="9" value="1"> Fahrenheit (ºF)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="12" value="0"> Celsius (ºC)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="13" value="1"> Fahrenheit (ºF)</input></div>
</div>
<div class="pure-g">
@ -376,7 +406,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" />
<input name="adminPass1" class="pure-u-1 pure-u-md-3-4" type="password" 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 />
@ -385,7 +415,7 @@
<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" />
<input name="adminPass2" class="pure-u-1 pure-u-md-3-4" type="password" tabindex="12" autocomplete="false" />
</div>
<div class="pure-g">
@ -472,12 +502,12 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttUser">MQTT User</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttUser" type="text" size="20" tabindex="23" placeholder="Leave blank if no user/pass" />
<input class="pure-u-1 pure-u-md-3-4" name="mqttUser" type="text" size="20" tabindex="23" placeholder="Leave blank if no user/pass" autocomplete="false" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttPassword">MQTT Password</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttPassword" type="password" size="20" tabindex="24" placeholder="Leave blank if no user/pass" />
<input class="pure-u-1 pure-u-md-3-4" name="mqttPassword" type="password" size="20" tabindex="24" placeholder="Leave blank if no user/pass" autocomplete="false" />
</div>
<div class="pure-g module module-mqttssl">
@ -674,12 +704,12 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="idbUsername">Username</label>
<input class="pure-u-1 pure-u-md-3-4" name="idbUsername" type="text" tabindex="44" />
<input class="pure-u-1 pure-u-md-3-4" name="idbUsername" type="text" tabindex="44" autocomplete="false" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="idbPassword">Password</label>
<input class="pure-u-1 pure-u-md-3-4" name="idbPassword" type="password" tabindex="45" />
<input class="pure-u-1 pure-u-md-3-4" name="idbPassword" type="password" tabindex="45" autocomplete="false" />
</div>
</fieldset>
@ -734,18 +764,63 @@
</div>
</form>
<div class="panel" id="panel-rfb">
<div class="header">
<h1>RFBRIDGE</h1>
<h2>
Sonoff 433 RF Bridge Configuration<br /><br />
To learn a new code click <strong>LEARN</strong>, the Sonoff RFBridge will beep, then press a button on the remote, the RFBridge will then double beep and the new code should show up. If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br /><br />
Modify or create new codes manually (all codes must be 18 characters long) and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br /><br />
Delete any code clicking the <strong>FORGET</strong> button.
</h2>
</div>
<div class="page">
<div id="rfbNodes" />
</div>
</div>
</div> <!-- content -->
</div> <!-- layout -->
<!-- Templates -->
<div id="rfbNodeTemplate" class="template">
<fieldset>
<legend>&nbsp;Switch <span></span>&nbsp;</legend>
<div class="pure-g">
<label class="pure-u-1-2 pure-u-sm-1-4">Switch ON</label>
<input class="pure-u-1-2 pure-u-sm-1-3" type="text" maxlength="18" name="rfbcode" data_id="1" data_status="1" />
<div class="pure-u-1-3 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-learn">LEARN</button></div>
<div class="pure-u-1-3 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-send">SAVE</button></div>
<div class="pure-u-1-3 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-forget">FORGET</button></div>
</div>
<div class="pure-g">
<label class="pure-u-1-2 pure-u-sm-1-4">Switch OFF</label>
<input class="pure-u-1-2 pure-u-sm-1-3" type="text" maxlength="18" name="rfbcode" data_id="1" data_status="0" />
<div class="pure-u-1-3 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-learn">LEARN</button></div>
<div class="pure-u-1-3 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-send">SAVE</button></div>
<div class="pure-u-1-3 pure-u-md-1-8"><button type="button" class="pure-u-23-24 pure-button button-rfb-forget">FORGET</button></div>
</div>
</fieldset>
</div>
<div id="networkTemplate" class="template">
<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 /></div>
<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-1-12 pure-u-1-8"><button type="button" class="pure-button button-more-network pure-u-1">...</button></div>
<div class="more">
@ -753,27 +828,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" />
<input class="pure-u-md-5-6 pure-u-3-4" name="pass" type="password" 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" />
<input class="pure-u-md-5-6 pure-u-3-4" name="ip" type="text" 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" />
<input class="pure-u-md-5-6 pure-u-3-4" name="gw" type="text" 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" />
<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" />
<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" />
<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" />
<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>
@ -801,6 +876,25 @@
</div>
</div>
<div id="colorTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Color</label>
<input class="pure-u-1 pure-u-sm-1-4" data-wcp-layout="block" name="color" readonly />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Brightness</label>
<div class="slider pure-u-1 pure-u-sm-1-4" id="brightness"></div>
</div>
</div>
<div id="channelTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Channel #</label>
<div class="slider channels pure-u-1 pure-u-sm-1-4" data="99"></div>
</div>
</div>
<iframe id="downloader" style="display:none;"></iframe>
<input id="uploader" type="file" style="display:none;" />
@ -811,6 +905,7 @@
<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>
<!-- endbuild -->
</html>

+ 1
- 0
code/html/nouislider.min.css View File

@ -0,0 +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%}

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


+ 44
- 2
code/platformio.ini View File

@ -278,6 +278,27 @@ upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
[env:rfbridge-debug]
platform = espressif8266
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} -DSONOFF_RFBRIDGE
[env:rfbridge-debug-ota]
platform = espressif8266
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} -DSONOFF_RFBRIDGE
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=Algernon1 --port 8266
[env:1ch-inching-debug]
platform = espressif8266
framework = arduino
@ -464,7 +485,7 @@ board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = -g -Wl,-Tesp8266.flash.1m128.ld -DH801_LED_CONTROLLER -DDEBUG_PORT=Serial1
build_flags = -g -Wl,-Tesp8266.flash.1m0.ld -DH801_LED_CONTROLLER -DDEBUG_PORT=Serial1
[env:h801-debug-ota]
platform = espressif8266
@ -473,7 +494,28 @@ board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = -g -Wl,-Tesp8266.flash.1m128.ld -DH801_LED_CONTROLLER -DDEBUG_PORT=Serial1
build_flags = -g -Wl,-Tesp8266.flash.1m0.ld -DH801_LED_CONTROLLER -DDEBUG_PORT=Serial1
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
[env:bnsz01-debug]
platform = espressif8266
framework = arduino
board = esp8285
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DITEAD_BNSZ01
[env:bnsz01-debug-ota]
platform = espressif8266
framework = arduino
board = esp8285
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DITEAD_BNSZ01
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266

Loading…
Cancel
Save