Browse Source

merge dev branch

fastled
Pawel Raszewski 6 years ago
parent
commit
548f6edd44
40 changed files with 5005 additions and 4757 deletions
  1. +1
    -0
      code/.gitignore
  2. +51
    -0
      code/core_version.py
  3. +15
    -0
      code/espurna/alexa.ino
  4. +15
    -4
      code/espurna/analog.ino
  5. +181
    -0
      code/espurna/api.ino
  6. +2
    -4
      code/espurna/button.ino
  7. +5
    -1
      code/espurna/config/all.h
  8. +1
    -0
      code/espurna/config/arduino.h
  9. +22
    -8
      code/espurna/config/general.h
  10. +42
    -30
      code/espurna/config/hardware.h
  11. +15
    -5
      code/espurna/config/prototypes.h
  12. +1
    -1
      code/espurna/config/sensors.h
  13. +1
    -1
      code/espurna/config/version.h
  14. +15
    -4
      code/espurna/counter.ino
  15. BIN
      code/espurna/data/index.html.gz
  16. +22
    -20
      code/espurna/debug.ino
  17. +4
    -3
      code/espurna/dht.ino
  18. +21
    -0
      code/espurna/domoticz.ino
  19. +1
    -1
      code/espurna/ds18b20.ino
  20. +45
    -26
      code/espurna/espurna.ino
  21. +11
    -3
      code/espurna/hardware.ino
  22. +18
    -0
      code/espurna/homeassitant.ino
  23. +37
    -18
      code/espurna/influxdb.ino
  24. +12
    -21
      code/espurna/light.ino
  25. +0
    -1
      code/espurna/light_ir.ino
  26. +18
    -0
      code/espurna/llmnr.ino
  27. +21
    -8
      code/espurna/mqtt.ino
  28. +1
    -2
      code/espurna/ota.ino
  29. +9
    -9
      code/espurna/power.ino
  30. +1
    -1
      code/espurna/relay.ino
  31. +37
    -39
      code/espurna/settings.ino
  32. +3459
    -3470
      code/espurna/static/index.html.gz.h
  33. +66
    -11
      code/espurna/utils.ino
  34. +26
    -997
      code/espurna/web.ino
  35. +7
    -1
      code/espurna/wifi.ino
  36. +18
    -12
      code/espurna/ws.h
  37. +755
    -0
      code/espurna/ws.ino
  38. +1
    -35
      code/html/custom.js
  39. +17
    -14
      code/html/index.html
  40. +31
    -7
      code/platformio.ini

+ 1
- 0
code/.gitignore View File

@ -3,3 +3,4 @@
.pioenvs
.piolibdeps
.vscode/c_cpp_properties.json
core_version.h

+ 51
- 0
code/core_version.py View File

@ -0,0 +1,51 @@
#!/bin/python
import json
import commands
import subprocess
import os
import sys
def core_version(env):
# Get the core folder
fwdir = env["FRAMEWORK_ARDUINOESP8266_DIR"]
# Get the core version
with open(fwdir + '/package.json') as data_file:
data = json.load(data_file)
core_version = data["version"].upper().replace(".", "_").replace("-", "_")
print "CORE VERSION: %s" % core_version
# Get git version
pr = subprocess.Popen(
"git --git-dir .git rev-parse --short=8 HEAD 2>/dev/null || echo ffffffff",
cwd = fwdir,
shell = True, stdout = subprocess.PIPE, stderr = subprocess.PIPE )
(out, error) = pr.communicate()
git_version = str(out).replace('\n', "")
print "GIT VERSION: %s" % git_version
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_RELEASE=" + core_version)
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_RELEASE_" + core_version)
#env["BUILD_FLAGS"][0] += str(" -DARDUINO_ESP8266_GIT_VER=" + git_version)
with open('espurna/config/core_version.h', 'w') as the_file:
the_file.write('#define ARDUINO_ESP8266_RELEASE "%s"\n' % core_version)
the_file.write('#define ARDUINO_ESP8266_RELEASE_%s\n' % core_version)
the_file.write('#define ARDUINO_ESP8266_GIT_VER "%s"\n' % git_version)
#env.Append(
# CFLAGS = [
# str("-DARDUINO_ESP8266_RELEASE=" + core_version),
# str("-DARDUINO_ESP8266_RELEASE_" + core_version),
# str("-DARDUINO_ESP8266_GIT_VER=" + git_version)
# ]
#)
#print " -DARDUINO_ESP8266_RELEASE=" + core_version +
# " -DARDUINO_ESP8266_RELEASE_" + core_version +
# " -DARDUINO_ESP8266_GIT_VER=" + git_version
Import('env')
core_version(env)

+ 15
- 0
code/espurna/alexa.ino View File

@ -20,6 +20,15 @@ bool _alexa_change = false;
unsigned int _alexa_device_id = 0;
bool _alexa_state = false;
#if WEB_SUPPORT
void _alexaWSSend(JsonObject& root) {
root["alexaVisible"] = 1;
root["alexaEnabled"] = getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1;
}
#endif
// -----------------------------------------------------------------------------
void alexaConfigure() {
alexa.enable(getSetting("alexaEnabled", ALEXA_ENABLED).toInt() == 1);
}
@ -29,8 +38,14 @@ void alexaSetup() {
// Backwards compatibility
moveSetting("fauxmoEnabled", "alexaEnabled");
// Load & cache settings
alexaConfigure();
#if WEB_SUPPORT
// Websockets
wsRegister(_alexaWSSend);
#endif
unsigned int relays = relayCount();
String hostname = getSetting("hostname");
if (relays == 1) {


+ 15
- 4
code/espurna/analog.ino View File

@ -12,6 +12,13 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
// ANALOG
// -----------------------------------------------------------------------------
void _analogWSSend(JsonObject& root) {
root["analogVisible"] = 1;
root["analogValue"] = getAnalog();
}
// -----------------------------------------------------------------------------
unsigned int getAnalog() {
return analogRead(ANALOG_PIN);
}
@ -21,9 +28,15 @@ void analogSetup() {
pinMode(ANALOG_PIN, INPUT);
#if WEB_SUPPORT
// Websocket register
wsRegister(_analogWSSend);
// API register
apiRegister(ANALOG_TOPIC, ANALOG_TOPIC, [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), getAnalog());
});
#endif
DEBUG_MSG_P(PSTR("[ANALOG] Monitoring analog values\n"));
@ -51,14 +64,12 @@ void analogLoop() {
// Send to InfluxDB
#if INFLUXDB_SUPPORT
influxDBSend(MQTT_TOPIC_ANALOG, analog);
idbSend(MQTT_TOPIC_ANALOG, analog);
#endif
// Update websocket clients
#if WEB_SUPPORT
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"analogVisible\": 1, \"analogValue\": %d}"), analog);
wsSend(buffer);
wsSend(_analogWSSend);
#endif
}


+ 181
- 0
code/espurna/api.ino View File

@ -0,0 +1,181 @@
/*
API MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if WEB_SUPPORT
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <vector>
typedef struct {
char * url;
char * key;
api_get_callback_f getFn = NULL;
api_put_callback_f putFn = NULL;
} web_api_t;
std::vector<web_api_t> _apis;
// -----------------------------------------------------------------------------
// API
// -----------------------------------------------------------------------------
bool _authAPI(AsyncWebServerRequest *request) {
if (getSetting("apiEnabled", API_ENABLED).toInt() == 0) {
DEBUG_MSG_P(PSTR("[WEBSERVER] HTTP API is not enabled\n"));
request->send(403);
return false;
}
if (!request->hasParam("apikey", (request->method() == HTTP_PUT))) {
DEBUG_MSG_P(PSTR("[WEBSERVER] Missing apikey parameter\n"));
request->send(403);
return false;
}
AsyncWebParameter* p = request->getParam("apikey", (request->method() == HTTP_PUT));
if (!p->value().equals(getSetting("apiKey"))) {
DEBUG_MSG_P(PSTR("[WEBSERVER] Wrong apikey parameter\n"));
request->send(403);
return false;
}
return true;
}
bool _asJson(AsyncWebServerRequest *request) {
bool asJson = false;
if (request->hasHeader("Accept")) {
AsyncWebHeader* h = request->getHeader("Accept");
asJson = h->value().equals("application/json");
}
return asJson;
}
ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
return [apiID](AsyncWebServerRequest *request) {
webLog(request);
if (!_authAPI(request)) return;
web_api_t api = _apis[apiID];
// Check if its a PUT
if (api.putFn != NULL) {
if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
(api.putFn)((p->value()).c_str());
}
}
// Get response from callback
char value[API_BUFFER_SIZE];
(api.getFn)(value, API_BUFFER_SIZE);
// The response will be a 404 NOT FOUND if the resource is not available
if (!value) {
DEBUG_MSG_P(PSTR("[API] Sending 404 response\n"));
request->send(404);
return;
}
DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), value);
// Format response according to the Accept header
if (_asJson(request)) {
char buffer[64];
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
request->send(200, "application/json", buffer);
} else {
request->send(200, "text/plain", value);
}
};
}
void _onAPIs(AsyncWebServerRequest *request) {
webLog(request);
if (!_authAPI(request)) return;
bool asJson = _asJson(request);
String output;
if (asJson) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
for (unsigned int i=0; i < _apis.size(); i++) {
root[_apis[i].key] = _apis[i].url;
}
root.printTo(output);
request->send(200, "application/json", output);
} else {
for (unsigned int i=0; i < _apis.size(); i++) {
output += _apis[i].key + String(" -> ") + _apis[i].url + String("\n");
}
request->send(200, "text/plain", output);
}
}
void _onRPC(AsyncWebServerRequest *request) {
webLog(request);
if (!_authAPI(request)) return;
//bool asJson = _asJson(request);
int response = 404;
if (request->hasParam("action")) {
AsyncWebParameter* p = request->getParam("action");
String action = p->value();
DEBUG_MSG_P(PSTR("[RPC] Action: %s\n"), action.c_str());
if (action.equals("reset")) {
response = 200;
deferredReset(100, CUSTOM_RESET_RPC);
}
}
request->send(response);
}
// -----------------------------------------------------------------------------
void apiRegister(const char * url, const char * key, api_get_callback_f getFn, api_put_callback_f putFn) {
// Store it
web_api_t api;
char buffer[40];
snprintf_P(buffer, sizeof(buffer), PSTR("/api/%s"), url);
api.url = strdup(buffer);
api.key = strdup(key);
api.getFn = getFn;
api.putFn = putFn;
_apis.push_back(api);
// Bind call
unsigned int methods = HTTP_GET;
if (putFn != NULL) methods += HTTP_PUT;
webServer()->on(buffer, methods, _bindAPI(_apis.size() - 1));
}
void apiSetup() {
webServer()->on("/apis", HTTP_GET, _onAPIs);
webServer()->on("/rpc", HTTP_GET, _onRPC);
}
#endif // WEB_SUPPORT

+ 2
- 4
code/espurna/button.ino View File

@ -104,15 +104,13 @@ void buttonEvent(unsigned int id, unsigned char event) {
}
if (action == BUTTON_MODE_AP) createAP();
if (action == BUTTON_MODE_RESET) {
customReset(CUSTOM_RESET_HARDWARE);
ESP.restart();
deferredReset(100, CUSTOM_RESET_HARDWARE);
}
if (action == BUTTON_MODE_PULSE) relayPulseToggle();
if (action == BUTTON_MODE_FACTORY) {
DEBUG_MSG_P(PSTR("\n\nFACTORY RESET\n\n"));
settingsFactoryReset();
customReset(CUSTOM_RESET_FACTORY);
ESP.restart();
deferredReset(100, CUSTOM_RESET_FACTORY);
}
}


+ 5
- 1
code/espurna/config/all.h View File

@ -5,9 +5,13 @@
#include "sensors.h"
#include "prototypes.h"
#ifdef USE_CORE_VERSION_H
#include "core_version.h"
#endif
/*
If you want to modify the stock configuration but you don't want to touch
the repo files you can either define USE_CUSTOM_H or remove the
the repo files you can either define USE_CUSTOM_H or remove the
"#ifdef USE_CUSTOM_H" & "#endif" lines and add a "custom.h"
file to this same folder.
Check https://bitbucket.org/xoseperez/espurna/issues/104/general_customh


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

@ -69,6 +69,7 @@
//#define I2C_SUPPORT 1
//#define INFLUXDB_SUPPORT 0
//#define IR_SUPPORT 1
//#define LLMNR_SUPPORT 1
//#define MDNS_SUPPORT 0
//#define NOFUSS_SUPPORT 1
//#define NTP_SUPPORT 0


+ 22
- 8
code/espurna/config/general.h View File

@ -9,6 +9,10 @@
#define ADMIN_PASS "fibonacci" // Default password (WEB, OTA, WIFI)
#define DEVICE_NAME MANUFACTURER "_" DEVICE // Concatenate both to get a unique device name
#define LOOP_DELAY_TIME 10 // Delay for this millis in the main loop [0-250]
#define ARRAYINIT(type, name, ...) \
type name[] = {__VA_ARGS__};
//------------------------------------------------------------------------------
// TELNET
@ -59,10 +63,9 @@
//------------------------------------------------------------------------------
// General debug options and macros
#define DEBUG_MESSAGE_MAX_LENGTH 80
#define DEBUG_FORMAT_MAX_LENGTH 80
#define DEBUG_SUPPORT DEBUG_SERIAL_SUPPORT || DEBUG_UDP_SUPPORT || DEBUG_TELNET_SUPPORT
#if DEBUG_SUPPORT
#define DEBUG_MSG(...) debugSend(__VA_ARGS__)
#define DEBUG_MSG_P(...) debugSend_P(__VA_ARGS__)
@ -82,11 +85,15 @@
#endif
//------------------------------------------------------------------------------
// CRASH
// SYSTEM CHECK
//------------------------------------------------------------------------------
#define CRASH_SAFE_TIME 60000 // The system is considered stable after these many millis
#define CRASH_COUNT_MAX 5 // After this many crashes on boot
#ifndef SYSTEM_CHECK_ENABLED
#define SYSTEM_CHECK_ENABLED 1 // Enable crash check by default
#endif
#define SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis
#define SYSTEM_CHECK_MAX 5 // After this many crashes on boot
// the system is flagged as unstable
//------------------------------------------------------------------------------
@ -263,6 +270,7 @@ PROGMEM const char* const custom_reset_string[] = {
#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
#define WIFI_SLEEP_ENABLED 1 // Enable WiFi light sleep
// Optional hardcoded configuration (up to 2 different networks)
//#define WIFI1_SSID "..."
@ -317,13 +325,17 @@ PROGMEM const char* const custom_reset_string[] = {
#define API_REAL_TIME_VALUES 0 // Show filtered/median values by default (0 => median, 1 => real time)
// -----------------------------------------------------------------------------
// MDNS
// MDNS & LLMNR
// -----------------------------------------------------------------------------
#ifndef MDNS_SUPPORT
#define MDNS_SUPPORT 1 // Publish services using mDNS by default
#endif
#ifndef LLMNR_SUPPORT
#define LLMNR_SUPPORT 0 // Publish device using LLMNR protocol by default - requires 2.4.0
#endif
// -----------------------------------------------------------------------------
// SPIFFS
// -----------------------------------------------------------------------------
@ -475,7 +487,7 @@ PROGMEM const char* const custom_reset_string[] = {
// Available light providers (do not change)
#define LIGHT_PROVIDER_NONE 0
#define LIGHT_PROVIDER_MY9192 1 // works with MY9231 also (Sonoff B1)
#define LIGHT_PROVIDER_MY92XX 1 // works with MY9291 and MY9231
#define LIGHT_PROVIDER_DIMMER 2
// LIGHT_PROVIDER_DIMMER can have from 1 to 5 different channels.
@ -500,7 +512,7 @@ PROGMEM const char* const custom_reset_string[] = {
#ifndef LIGHT_MAX_PWM
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
#define LIGHT_MAX_PWM 255
#endif
@ -664,6 +676,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define DOMOTICZ_ENABLED 0 // Disable domoticz by default
#define DOMOTICZ_IN_TOPIC "domoticz/in" // Default subscription topic
#define DOMOTICZ_OUT_TOPIC "domoticz/out" // Default publication topic
#define DOMOTICZ_SKIP_TIME 2 // Avoid recursion skipping messages to same IDX within 2 seconds
// -----------------------------------------------------------------------------
// HOME ASSISTANT
@ -673,6 +686,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define HOMEASSISTANT_SUPPORT 1 // Build with home assistant support
#endif
#define HOMEASSISTANT_ENABLED 0 // Integration not enabled by default
#define HOMEASSISTANT_PREFIX "homeassistant" // Default MQTT prefix
// -----------------------------------------------------------------------------


+ 42
- 30
code/espurna/config/hardware.h View File

@ -321,7 +321,6 @@
#define RELAY_PROVIDER RELAY_PROVIDER_DUAL
#define DUMMY_RELAY_COUNT 2
#define DEBUG_SERIAL_SUPPORT 0
#define TERMINAL_SUPPORT 0
// Buttons
#define BUTTON3_RELAY 1
@ -464,7 +463,7 @@
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// Channels
// Light
#define LIGHT_CHANNELS 1
#define LIGHT_CH1_PIN 12
#define LIGHT_CH1_INVERSE 0
@ -497,12 +496,17 @@
#define MANUFACTURER "ITEAD"
#define DEVICE "SONOFF_B1"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY9192
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
#define MY9291_DI_PIN 12
#define MY9291_DCKI_PIN 14
#define MY9291_COMMAND MY9291_COMMAND_DEFAULT
#define MY9291_CHANNELS 5
// Light
#define LIGHT_CHANNELS 5
#define MY92XX_MODEL MY92XX_MODEL_MY9231
#define MY92XX_CHIPS 2
#define MY92XX_DI_PIN 12
#define MY92XX_DCKI_PIN 14
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 4, 3, 5, 0, 1
#elif defined(ITEAD_SONOFF_LED)
@ -517,7 +521,7 @@
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// Channels
// Light
#define LIGHT_CHANNELS 2
#define LIGHT_CH1_PIN 12 // Cold white
#define LIGHT_CH2_PIN 14 // Warm white
@ -531,12 +535,12 @@
#define DEVICE "SONOFF_T1_1CH"
// Buttons
#define BUTTON1_PIN 9
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 5
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
@ -551,7 +555,7 @@
// Buttons
#define BUTTON1_PIN 0
#define BUTTON2_PIN 10
#define BUTTON2_PIN 9
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
@ -561,7 +565,7 @@
// Relays
#define RELAY1_PIN 12
#define RELAY2_PIN 4
#define RELAY2_PIN 5
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_TYPE RELAY_TYPE_NORMAL
@ -700,12 +704,17 @@
#define MANUFACTURER "AITHINKER"
#define DEVICE "AI_LIGHT"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY9192
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
#define MY9291_DI_PIN 13
#define MY9291_DCKI_PIN 15
#define MY9291_COMMAND MY9291_COMMAND_DEFAULT
#define MY9291_CHANNELS 4
// Light
#define LIGHT_CHANNELS 4
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 13
#define MY92XX_DCKI_PIN 15
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 0, 1, 2, 3
// -----------------------------------------------------------------------------
// LED Controller
@ -724,7 +733,7 @@
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
// Channels
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 14 // RED
#define LIGHT_CH2_PIN 5 // GREEN
@ -755,7 +764,7 @@
#define LED1_PIN 2
#define LED1_PIN_INVERSE 1
// Channels
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 5 // RED
#define LIGHT_CH2_PIN 12 // GREEN
@ -788,7 +797,7 @@
#define LED1_PIN 5
#define LED1_PIN_INVERSE 1
// Channels
// Light
#define LIGHT_CHANNELS 5
#define LIGHT_CH1_PIN 15 // RED
#define LIGHT_CH2_PIN 13 // GREEN
@ -810,7 +819,7 @@
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Channels
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 12 // RED
#define LIGHT_CH2_PIN 14 // GREEN
@ -1047,7 +1056,7 @@
#define LED1_PIN 5
#define LED1_PIN_INVERSE 1
// Channels
// Light
#define LIGHT_CHANNELS 2
#define LIGHT_CH1_PIN 0
#define LIGHT_CH2_PIN 2
@ -1067,7 +1076,7 @@
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Channels
// Light
#define LIGHT_CHANNELS 5
#define LIGHT_CH1_PIN 14 // RED
#define LIGHT_CH2_PIN 12 // GREEN
@ -1087,14 +1096,17 @@
#define MANUFACTURER "ARILUX"
#define DEVICE "E27"
#define RELAY_PROVIDER RELAY_PROVIDER_LIGHT
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY9192
#define LIGHT_PROVIDER LIGHT_PROVIDER_MY92XX
#define DUMMY_RELAY_COUNT 1
// Channels
#define MY9291_CHANNELS 4
#define MY9291_DI_PIN 13
#define MY9291_DCKI_PIN 15
#define MY9291_COMMAND MY9291_COMMAND_DEFAULT
// Light
#define LIGHT_CHANNELS 4
#define MY92XX_MODEL MY92XX_MODEL_MY9291
#define MY92XX_CHIPS 1
#define MY92XX_DI_PIN 13
#define MY92XX_DCKI_PIN 15
#define MY92XX_COMMAND MY92XX_COMMAND_DEFAULT
#define MY92XX_MAPPING 0, 1, 2, 3
// -----------------------------------------------------------------------------
// XENON SM-PW701U
@ -1133,7 +1145,7 @@
#define LIGHT_PROVIDER LIGHT_PROVIDER_DIMMER
#define DUMMY_RELAY_COUNT 1
// Channels
// Light
#define LIGHT_CHANNELS 4
#define LIGHT_CH1_PIN 13 // RED
#define LIGHT_CH2_PIN 12 // GREEN


+ 15
- 5
code/espurna/config/prototypes.h View File

@ -2,22 +2,32 @@
#include <NtpClientLib.h>
#include <ESPAsyncWebServer.h>
#include <AsyncMqttClient.h>
#include <ArduinoJson.h>
#include <functional>
#include <FastLED.h>
typedef std::function<void(char *, size_t)> apiGetCallbackFunction;
typedef std::function<void(const char *)> apiPutCallbackFunction;
void apiRegister(const char * url, const char * key, apiGetCallbackFunction getFn, apiPutCallbackFunction putFn = NULL);
AsyncWebServer * webServer();
void mqttRegister(void (*callback)(unsigned int, const char *, const char *));
typedef std::function<void(char *, size_t)> api_get_callback_f;
typedef std::function<void(const char *)> api_put_callback_f;
void apiRegister(const char * url, const char * key, api_get_callback_f getFn, api_put_callback_f putFn = NULL);
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
void mqttRegister(mqtt_callback_f callback);
String mqttSubtopic(char * topic);
typedef std::function<void(JsonObject&)> ws_callback_f;
void wsRegister(ws_callback_f sender, ws_callback_f receiver = NULL);
void wsSend(ws_callback_f sender);
template<typename T> bool setSetting(const String& key, T value);
template<typename T> bool setSetting(const String& key, unsigned int index, T value);
template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> String getSetting(const String& key, unsigned int index, T defaultValue);
template<typename T> void domoticzSend(const char * key, T value);
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue);
template<typename T> bool influxDBSend(const char * topic, T payload);
template<typename T> bool idbSend(const char * topic, T payload);
char * ltrim(char * s);

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

@ -36,9 +36,9 @@
#define DHT_UPDATE_INTERVAL 60000
#endif
#define DHT_TIMING 11
#define DHT_TEMPERATURE_TOPIC "temperature"
#define DHT_HUMIDITY_TOPIC "humidity"
#define DHT_TEMPERATURE_DECIMALS 1 // Decimals for temperature values
#define HUMIDITY_NORMAL 0
#define HUMIDITY_COMFORTABLE 1


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

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

+ 15
- 4
code/espurna/counter.ino View File

@ -26,6 +26,13 @@ void ICACHE_RAM_ATTR _counterISR() {
}
}
#if WEB_SUPPORT
void _counterWSSend(JsonObject& root) {
root["counterVisible"] = 1;
root["counterValue"] = getCounter();
}
#endif
unsigned long getCounter() {
return _counterValue;
}
@ -36,9 +43,15 @@ void counterSetup() {
attachInterrupt(COUNTER_PIN, _counterISR, COUNTER_INTERRUPT_MODE);
#if WEB_SUPPORT
// Websockets
wsRegister(_counterWSSend);
// API
apiRegister(COUNTER_TOPIC, COUNTER_TOPIC, [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), getCounter());
});
#endif
DEBUG_MSG_P(PSTR("[COUNTER] Counter on GPIO %d\n"), COUNTER_PIN);
@ -62,9 +75,7 @@ void counterLoop() {
// Update websocket clients
#if WEB_SUPPORT
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"counterVisible\": 1, \"counterValue\": %d}"), _counterValue);
wsSend(buffer);
wsSend(_counterWSSend);
#endif
// Do we have to report?
@ -80,7 +91,7 @@ void counterLoop() {
// Send to InfluxDB
#if INFLUXDB_SUPPORT
influxDBSend(COUNTER_TOPIC, _counterValue);
idbSend(COUNTER_TOPIC, _counterValue);
#endif
}


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


+ 22
- 20
code/espurna/debug.ino View File

@ -18,73 +18,75 @@ WiFiUDP udpDebug;
void debugSend(const char * format, ...) {
char buffer[DEBUG_MESSAGE_MAX_LENGTH+1];
va_list args;
va_start(args, format);
int len = ets_vsnprintf(buffer, DEBUG_MESSAGE_MAX_LENGTH, format, args);
char test[1];
int len = ets_vsnprintf(test, 1, format, args) + 1;
char * buffer = new char[len];
ets_vsnprintf(buffer, len, format, args);
va_end(args);
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.printf(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
DEBUG_PORT.printf(" (...)\n");
}
#endif
#if DEBUG_UDP_SUPPORT
#if SYSTEM_CHECK_ENABLED
if (systemCheck()) {
#endif
udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
udpDebug.write(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
udpDebug.write(" (...)\n");
}
udpDebug.endPacket();
delay(1);
#if SYSTEM_CHECK_ENABLED
}
#endif
#endif
#if DEBUG_TELNET_SUPPORT
_telnetWrite(buffer, strlen(buffer));
#endif
free(buffer);
}
void debugSend_P(PGM_P format, ...) {
char f[DEBUG_MESSAGE_MAX_LENGTH+1];
memcpy_P(f, format, DEBUG_MESSAGE_MAX_LENGTH);
char buffer[DEBUG_MESSAGE_MAX_LENGTH+1];
char f[DEBUG_FORMAT_MAX_LENGTH+1];
memcpy_P(f, format, DEBUG_FORMAT_MAX_LENGTH);
va_list args;
va_start(args, format);
int len = ets_vsnprintf(buffer, DEBUG_MESSAGE_MAX_LENGTH, f, args);
char test[1];
int len = ets_vsnprintf(test, 1, f, args) + 1;
char * buffer = new char[len];
ets_vsnprintf(buffer, len, f, args);
va_end(args);
#if DEBUG_SERIAL_SUPPORT
DEBUG_PORT.printf(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
DEBUG_PORT.printf(" (...)\n");
}
#endif
#if DEBUG_UDP_SUPPORT
#if SYSTEM_CHECK_ENABLED
if (systemCheck()) {
#endif
udpDebug.beginPacket(DEBUG_UDP_IP, DEBUG_UDP_PORT);
udpDebug.write(buffer);
if (len > DEBUG_MESSAGE_MAX_LENGTH) {
udpDebug.write(" (...)\n");
}
udpDebug.endPacket();
delay(1);
#if SYSTEM_CHECK_ENABLED
}
#endif
#endif
#if DEBUG_TELNET_SUPPORT
_telnetWrite(buffer, strlen(buffer));
#endif
free(buffer);
}
// -----------------------------------------------------------------------------


+ 4
- 3
code/espurna/dht.ino View File

@ -136,7 +136,8 @@ int readDHT() {
// -----------------------------------------------------------------------------
double getDHTTemperature(bool celsius) {
return celsius ? _dhtTemperature : _dhtTemperature * 1.8 + 32;
double value = celsius ? _dhtTemperature : _dhtTemperature * 1.8 + 32;
return roundTo(value, DHT_TEMPERATURE_DECIMALS);
}
double getDHTTemperature() {
@ -208,8 +209,8 @@ void dhtLoop() {
#endif
#if INFLUXDB_SUPPORT
influxDBSend(getSetting("dhtTmpTopic", DHT_TEMPERATURE_TOPIC).c_str(), temperature);
influxDBSend(getSetting("dhtHumTopic", DHT_HUMIDITY_TOPIC).c_str(), humidity);
idbSend(getSetting("dhtTmpTopic", DHT_TEMPERATURE_TOPIC).c_str(), temperature);
idbSend(getSetting("dhtHumTopic", DHT_HUMIDITY_TOPIC).c_str(), humidity);
#endif
// Update websocket clients


+ 21
- 0
code/espurna/domoticz.ino View File

@ -11,6 +11,9 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _dcz_enabled = false;
unsigned long _dcz_skip_time = 0;
unsigned long _dcz_last_idx = 0;
unsigned long _dcz_last_time = 0;
//------------------------------------------------------------------------------
// Private methods
@ -25,6 +28,13 @@ int _domoticzRelay(unsigned int idx) {
return -1;
}
bool _domoticzSkip(unsigned long idx) {
if (idx == _dcz_last_idx && (millis() - _dcz_last_time < _dcz_skip_time)) return true;
_dcz_last_idx = idx;
_dcz_last_time = millis();
return false;
}
void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
@ -52,9 +62,14 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
unsigned long idx = root["idx"];
int relayID = _domoticzRelay(idx);
if (relayID >= 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
unsigned long value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx);
relayStatus(relayID, value == 1);
}
}
@ -71,9 +86,14 @@ template<typename T> void domoticzSend(const char * key, T nvalue, const char *
if (!_dcz_enabled) return;
unsigned int idx = getSetting(key).toInt();
if (idx > 0) {
// Skip message if recursive
if (_domoticzSkip(idx)) return;
char payload[128];
snprintf(payload, sizeof(payload), "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"%s\"}", idx, String(nvalue).c_str(), svalue);
mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload);
}
}
@ -96,6 +116,7 @@ int domoticzIdx(unsigned int relayID) {
void domoticzConfigure() {
_dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
_dcz_skip_time = 1000 * getSetting("dczSkip", DOMOTICZ_SKIP_TIME).toInt();
}
void domoticzSetup() {


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

@ -119,7 +119,7 @@ void dsLoop() {
#endif
#if INFLUXDB_SUPPORT
influxDBSend(getSetting("dsTmpTopic", DS18B20_TEMPERATURE_TOPIC).c_str(), _dsTemperatureStr);
idbSend(getSetting("dsTmpTopic", DS18B20_TEMPERATURE_TOPIC).c_str(), _dsTemperatureStr);
#endif
}


+ 45
- 26
code/espurna/espurna.ino View File

@ -21,12 +21,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "config/all.h"
#include <EEPROM.h>
#include <FastLED.h> // W.T.H. is this include ALSO needed here ?!?!!
// -----------------------------------------------------------------------------
// METHODS
// -----------------------------------------------------------------------------
unsigned long _loopDelay = 0;
void hardwareSetup() {
EEPROM.begin(EEPROM_SIZE);
@ -49,18 +50,11 @@ void hardwareSetup() {
pinMode(16, OUTPUT);
digitalWrite(16, HIGH); //Defualt CT input (pin B, solder jumper B)
#endif
}
void hardwareLoop() {
// System check
static bool checked = false;
if (!checked && (millis() > CRASH_SAFE_TIME)) {
// Check system as stable
systemCheck(true);
checked = true;
}
// Heartbeat
static unsigned long last_uptime = 0;
if ((millis() - last_uptime > HEARTBEAT_INTERVAL) || (last_uptime == 0)) {
@ -87,7 +81,8 @@ void welcome() {
DEBUG_MSG_P(PSTR("[INIT] CPU chip ID: 0x%06X\n"), ESP.getChipId());
DEBUG_MSG_P(PSTR("[INIT] CPU frequency: %d MHz\n"), ESP.getCpuFreqMHz());
DEBUG_MSG_P(PSTR("[INIT] SDK version: %s\n"), ESP.getSdkVersion());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), ESP.getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] Core version: %s\n"), getCoreVersion().c_str());
DEBUG_MSG_P(PSTR("[INIT] Core revision: %s\n"), getCoreRevision().c_str());
DEBUG_MSG_P(PSTR("\n"));
// -------------------------------------------------------------------------
@ -173,6 +168,9 @@ void welcome() {
#if INFLUXDB_SUPPORT
DEBUG_MSG_P(PSTR(" INFLUXDB"));
#endif
#if LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS"));
#endif
@ -202,10 +200,10 @@ void welcome() {
// -------------------------------------------------------------------------
unsigned char custom_reset = customReset();
if (custom_reset > 0) {
unsigned char reason = resetReason();
if (reason > 0) {
char buffer[32];
strcpy_P(buffer, custom_reset_string[custom_reset-1]);
strcpy_P(buffer, custom_reset_string[reason-1]);
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), buffer);
} else {
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
@ -215,6 +213,8 @@ void welcome() {
#if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[INIT] Power: %d mV\n"), ESP.getVcc());
#endif
DEBUG_MSG_P(PSTR("[INIT] Power saving delay value: %lu ms\n"), _loopDelay);
DEBUG_MSG_P(PSTR("\n"));
}
@ -225,10 +225,9 @@ void setup() {
hardwareSetup();
// Question system stability
systemCheck(false);
// Show welcome message and system configuration
welcome();
#if SYSTEM_CHECK_ENABLED
systemCheck(false);
#endif
// Init persistance and terminal features
settingsSetup();
@ -236,6 +235,13 @@ void setup() {
setSetting("hostname", getIdentifier());
}
// Cache loop delay value to speed things (recommended max 250ms)
_loopDelay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
// Show welcome message and system configuration
welcome();
// Basic modules, will always run
wifiSetup();
otaSetup();
#if TELNET_SUPPORT
@ -243,10 +249,15 @@ void setup() {
#endif
// Do not run the next services if system is flagged stable
if (!systemCheck()) return;
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return;
#endif
// Init webserver required before any module that uses API
#if WEB_SUPPORT
webSetup();
wsSetup();
apiSetup();
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
@ -260,7 +271,6 @@ void setup() {
#ifdef ITEAD_SONOFF_RFBRIDGE
rfbSetup();
#endif
#if POWER_PROVIDER != POWER_PROVIDER_NONE
powerSetup();
#endif
@ -277,7 +287,7 @@ void setup() {
nofussSetup();
#endif
#if INFLUXDB_SUPPORT
influxDBSetup();
idbSetup();
#endif
#if DS18B20_SUPPORT
dsSetup();
@ -300,6 +310,12 @@ void setup() {
#if DOMOTICZ_SUPPORT
domoticzSetup();
#endif
#if HOMEASSISTANT_SUPPORT
haSetup();
#endif
#if LLMNR_SUPPORT
llmnrSetup();
#endif
// Prepare configuration for version 2.0
hwUpwardsCompatibility();
@ -315,23 +331,23 @@ void loop() {
wifiLoop();
otaLoop();
// Do not run the next services if system is flagged stable
if (!systemCheck()) return;
#if SYSTEM_CHECK_ENABLED
systemCheckLoop();
// Do not run the next services if system is flagged stable
if (!systemCheck()) return;
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
lightLoop();
#endif
buttonLoop();
relayLoop();
buttonLoop();
ledLoop();
mqttLoop();
#ifdef ITEAD_SONOFF_RFBRIDGE
rfbLoop();
#endif
#if POWER_PROVIDER != POWER_PROVIDER_NONE
powerLoop();
#endif
@ -363,4 +379,7 @@ void loop() {
irLoop();
#endif
// Power saving delay
delay(_loopDelay);
}

+ 11
- 3
code/espurna/hardware.ino View File

@ -13,6 +13,8 @@ the migration to future version 2 will be straigh forward.
*/
#include <my92xx.h>
void hwUpwardsCompatibility() {
unsigned int board = getSetting("board", 0).toInt();
@ -222,7 +224,9 @@ void hwUpwardsCompatibility() {
setSetting("board", 20);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY9192);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9291);
setSetting("myChips", 1);
setSetting("myDIGPIO", 13);
setSetting("myDCKIGPIO", 15);
setSetting("relays", 1);
@ -349,7 +353,9 @@ void hwUpwardsCompatibility() {
setSetting("board", 28);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY9192);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9231);
setSetting("myChips", 2);
setSetting("myDIGPIO", 12);
setSetting("myDCKIGPIO", 14);
setSetting("relays", 1);
@ -561,7 +567,9 @@ void hwUpwardsCompatibility() {
setSetting("board", 46);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
setSetting("lightProvider", LIGHT_PROVIDER_MY9192);
setSetting("lightProvider", LIGHT_PROVIDER_MY92XX);
setSetting("myModel", MY92XX_MODEL_MY9291);
setSetting("myChips", 1);
setSetting("myDIGPIO", 13);
setSetting("myDCKIGPIO", 15);
setSetting("relays", 1);


+ 18
- 0
code/espurna/homeassitant.ino View File

@ -10,6 +10,8 @@ Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _haEnabled = false;
void haSend(bool add) {
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
@ -29,6 +31,9 @@ void haSend(bool add) {
root["command_topic"] = getTopic(MQTT_TOPIC_RELAY, 0, true);
root["payload_on"] = String("1");
root["payload_off"] = String("0");
root["availability_topic"] = getTopic(MQTT_TOPIC_STATUS, false);
root["payload_available"] = String("1");
root["payload_not_available"] = String("0");
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
@ -63,8 +68,21 @@ void haSend(bool add) {
"/config";
mqttSendRaw(topic.c_str(), output.c_str());
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
}
void haConfigure() {
bool enabled = getSetting("haEnabled", HOMEASSISTANT_ENABLED).toInt() == 1;
if (enabled != _haEnabled) haSend(enabled);
_haEnabled = enabled;
}
void haSetup() {
haConfigure();
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) haSend(_haEnabled);
});
}
#endif // HOMEASSISTANT_SUPPORT

+ 37
- 18
code/espurna/influxdb.ino View File

@ -11,18 +11,33 @@ Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
#include "ESPAsyncTCP.h"
#include "SyncClient.h"
bool _influxdb_enabled = false;
SyncClient _influx_client;
bool _idb_enabled = false;
SyncClient _idb_client;
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
void _idbWSSend(JsonObject& root) {
root["idbVisible"] = 1;
root["idbHost"] = getSetting("idbHost");
root["idbPort"] = getSetting("idbPort", INFLUXDB_PORT).toInt();
root["idbDatabase"] = getSetting("idbDatabase");
root["idbUsername"] = getSetting("idbUsername");
root["idbPassword"] = getSetting("idbPassword");
}
#endif
template<typename T> bool influxDBSend(const char * topic, T payload) {
// -----------------------------------------------------------------------------
if (!_influxdb_enabled) return true;
template<typename T> bool idbSend(const char * topic, T payload) {
if (!_idb_enabled) return true;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return true;
DEBUG_MSG("[INFLUXDB] Sending\n");
_influx_client.setTimeout(2);
if (!_influx_client.connect(getSetting("idbHost").c_str(), getSetting("idbPort", INFLUXDB_PORT).toInt())) {
_idb_client.setTimeout(2);
if (!_idb_client.connect(getSetting("idbHost").c_str(), getSetting("idbPort", INFLUXDB_PORT).toInt())) {
DEBUG_MSG("[INFLUXDB] Connection failed\n");
return false;
}
@ -37,29 +52,33 @@ template<typename T> bool influxDBSend(const char * topic, T payload) {
getSetting("idbHost").c_str(), getSetting("idbPort", INFLUXDB_PORT).toInt(),
strlen(data), data);
if (_influx_client.printf(request) > 0) {
while (_influx_client.connected() && _influx_client.available() == 0) delay(1);
while (_influx_client.available()) _influx_client.read();
if (_influx_client.connected()) _influx_client.stop();
if (_idb_client.printf(request) > 0) {
while (_idb_client.connected() && _idb_client.available() == 0) delay(1);
while (_idb_client.available()) _idb_client.read();
if (_idb_client.connected()) _idb_client.stop();
return true;
}
_influx_client.stop();
_idb_client.stop();
DEBUG_MSG("[INFLUXDB] Sent failed\n");
while (_influx_client.connected()) delay(0);
while (_idb_client.connected()) delay(0);
return false;
}
bool influxdbEnabled() {
return _influxdb_enabled;
bool idbEnabled() {
return _idb_enabled;
}
void influxDBConfigure() {
_influxdb_enabled = getSetting("idbHost").length() > 0;
void idbConfigure() {
_idb_enabled = getSetting("idbHost").length() > 0;
}
void influxDBSetup() {
influxDBConfigure();
void idbSetup() {
idbConfigure();
#if WEB_SUPPORT
wsRegister(_idbWSSend);
#endif
}
#endif

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

@ -31,9 +31,9 @@ std::vector<channel_t> _channels;
bool _lightState = false;
unsigned int _brightness = LIGHT_MAX_BRIGHTNESS;
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
#include <my9291.h>
my9291 * _my9291;
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
#include <my92xx.h>
my92xx * _my92xx;
#endif
// Gamma Correction lookup table (8 bit)
@ -387,24 +387,15 @@ void _lightProviderUpdate() {
digitalWrite(LIGHT_ENABLE_PIN, _lightState);
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
if (_lightState) {
unsigned int red = _toPWM(0);
unsigned int green = _toPWM(1);
unsigned int blue = _toPWM(2);
unsigned int white = _toPWM(3);
unsigned int warm = _toPWM(4);
_my9291->setColor((my9291_color_t) { red, green, blue, white, warm });
_my9291->setState(true);
} else {
_my9291->setColor((my9291_color_t) { 0, 0, 0, 0, 0 });
_my9291->setState(false);
ARRAYINIT(unsigned char, channels, MY92XX_MAPPING);
for (unsigned char i=0; i<_channels.size(); i++) {
_my92xx->setChannel(channels[i], _toPWM(i));
}
_my92xx->setState(_lightState);
_my92xx->update();
#endif
@ -803,10 +794,10 @@ void lightSetup() {
pinMode(LIGHT_ENABLE_PIN, OUTPUT);
#endif
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY9192
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
_my9291 = new my9291(MY9291_DI_PIN, MY9291_DCKI_PIN, MY9291_COMMAND, MY9291_CHANNELS);
for (unsigned char i=0; i<MY9291_CHANNELS; i++) {
_my92xx = new my92xx(MY92XX_MODEL, MY92XX_CHIPS, MY92XX_DI_PIN, MY92XX_DCKI_PIN, MY92XX_COMMAND);
for (unsigned char i=0; i<LIGHT_CHANNELS; i++) {
_channels.push_back((channel_t) {0, false, 0});
}


+ 0
- 1
code/espurna/light_ir.ino View File

@ -21,7 +21,6 @@ Not currently Implemented :
*/
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
#include <FastLED.h>


+ 18
- 0
code/espurna/llmnr.ino View File

@ -0,0 +1,18 @@
/*
LLMNR MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if LLMNR_SUPPORT
#include <ESP8266LLMNR.h>
void llmnrSetup() {
LLMNR.begin(getSetting("hostname").c_str());
DEBUG_MSG_P(PSTR("[LLMNR] Configured\n"));
}
#endif // LLMNR_SUPPORT

+ 21
- 8
code/espurna/mqtt.ino View File

@ -44,7 +44,7 @@ char *_mqtt_will;
unsigned long _mqtt_connected_at = 0;
#endif
std::vector<void (*)(unsigned int, const char *, const char *)> _mqtt_callbacks;
std::vector<mqtt_callback_f> _mqtt_callbacks;
typedef struct {
char * topic;
@ -180,7 +180,19 @@ void mqttSubscribe(const char * topic) {
mqttSubscribeRaw(path.c_str());
}
void mqttRegister(void (*callback)(unsigned int, const char *, const char *)) {
void mqttUnsubscribeRaw(const char * topic) {
if (_mqtt.connected() && (strlen(topic) > 0)) {
#if MQTT_USE_ASYNC
unsigned int packetId = _mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s (PID %d)\n"), topic, packetId);
#else
_mqtt.unsubscribe(topic);
DEBUG_MSG_P(PSTR("[MQTT] Unsubscribing to %s\n"), topic);
#endif
}
}
void mqttRegister(mqtt_callback_f callback) {
_mqtt_callbacks.push_back(callback);
}
@ -190,7 +202,6 @@ void mqttRegister(void (*callback)(unsigned int, const char *, const char *)) {
void _mqttCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribe(MQTT_TOPIC_ACTION);
@ -205,8 +216,7 @@ void _mqttCallback(unsigned int type, const char * topic, const char * payload)
// Actions
if (t.equals(MQTT_TOPIC_ACTION)) {
if (strcmp(payload, MQTT_ACTION_RESET) == 0) {
customReset(CUSTOM_RESET_MQTT);
ESP.restart();
deferredReset(100, CUSTOM_RESET_MQTT);
}
}
@ -226,9 +236,12 @@ void _mqttOnConnect() {
// Send first Heartbeat
heartbeat();
// Clean subscriptions
mqttUnsubscribeRaw("#");
// Send connect event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(*_mqtt_callbacks[i])(MQTT_CONNECT_EVENT, NULL, NULL);
(_mqtt_callbacks[i])(MQTT_CONNECT_EVENT, NULL, NULL);
}
}
@ -239,7 +252,7 @@ void _mqttOnDisconnect() {
// Send disconnect event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(*_mqtt_callbacks[i])(MQTT_DISCONNECT_EVENT, NULL, NULL);
(_mqtt_callbacks[i])(MQTT_DISCONNECT_EVENT, NULL, NULL);
}
}
@ -261,7 +274,7 @@ void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
// Send message event to subscribers
for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) {
(*_mqtt_callbacks[i])(MQTT_MESSAGE_EVENT, topic, message);
(_mqtt_callbacks[i])(MQTT_MESSAGE_EVENT, topic, message);
}
}


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

@ -30,12 +30,11 @@ void otaSetup() {
});
ArduinoOTA.onEnd([]() {
customReset(CUSTOM_RESET_OTA);
DEBUG_MSG_P(PSTR("\n[OTA] End\n"));
#if WEB_SUPPORT
wsSend_P(PSTR("{\"action\": \"reload\"}"));
#endif
delay(100);
deferredReset(100, CUSTOM_RESET_OTA);
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {


+ 9
- 9
code/espurna/power.ino View File

@ -234,16 +234,16 @@ void _powerReport() {
#endif
#if INFLUXDB_SUPPORT
if (influxdbEnabled()) {
influxDBSend(MQTT_TOPIC_CURRENT, buf_current);
influxDBSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str());
influxDBSend(MQTT_TOPIC_ENERGY_DELTA, buf_energy_delta);
influxDBSend(MQTT_TOPIC_ENERGY_TOTAL, buf_energy_total);
if (idbEnabled()) {
idbSend(MQTT_TOPIC_CURRENT, buf_current);
idbSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str());
idbSend(MQTT_TOPIC_ENERGY_DELTA, buf_energy_delta);
idbSend(MQTT_TOPIC_ENERGY_TOTAL, buf_energy_total);
#if POWER_HAS_ACTIVE
influxDBSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str());
influxDBSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str());
influxDBSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
influxDBSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str());
idbSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str());
idbSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str());
idbSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
idbSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str());
#endif
}
#endif


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

@ -493,7 +493,7 @@ void relayInfluxDB(unsigned char id) {
if (id >= _relays.size()) return;
char buffer[10];
snprintf_P(buffer, sizeof(buffer), PSTR("%s,id=%d"), MQTT_TOPIC_RELAY, id);
influxDBSend(buffer, relayStatus(id) ? "1" : "0");
idbSend(buffer, relayStatus(id) ? "1" : "0");
}
#endif


+ 37
- 39
code/espurna/settings.ino View File

@ -118,7 +118,7 @@ void settingsSetup() {
Embedis::hardware( F("WIFI"), [](Embedis* e) {
StreamString s;
WiFi.printDiag(s);
e->response(s);
DEBUG_MSG(s.c_str());
}, 0);
// -------------------------------------------------------------------------
@ -126,58 +126,57 @@ void settingsSetup() {
Embedis::command( F("RESET.WIFI"), [](Embedis* e) {
wifiConfigure();
wifiDisconnect();
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("RESET.MQTT"), [](Embedis* e) {
mqttConfigure();
mqttDisconnect();
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("INFO"), [](Embedis* e) {
welcome();
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("UPTIME"), [](Embedis* e) {
e->stream->printf("Uptime: %d seconds\n", getUptime());
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Uptime: %d seconds\n"), getUptime());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("RESET"), [](Embedis* e) {
e->response(Embedis::OK);
customReset(CUSTOM_RESET_TERMINAL);
ESP.restart();
DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL);
});
Embedis::command( F("ERASE.CONFIG"), [](Embedis* e) {
e->response(Embedis::OK);
customReset(CUSTOM_RESET_TERMINAL);
DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL);
ESP.eraseConfig();
*((int*) 0) = 0; // see https://github.com/esp8266/Arduino/issues/1494
});
#if NOFUSS_SUPPORT
Embedis::command( F("NOFUSS"), [](Embedis* e) {
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("+OK\n"));
nofussRun();
});
#endif
Embedis::command( F("FACTORY.RESET"), [](Embedis* e) {
settingsFactoryReset();
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("HEAP"), [](Embedis* e) {
e->stream->printf("Free HEAP: %d bytes\n", ESP.getFreeHeap());
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Free HEAP: %d bytes\n"), ESP.getFreeHeap());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("RELAY"), [](Embedis* e) {
if (e->argc < 2) {
return e->response(Embedis::ARGS_ERROR);
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
@ -188,8 +187,8 @@ void settingsSetup() {
relayStatus(id, value == 1);
}
}
e->stream->printf("Status: %s\n", relayStatus(id) ? "true" : "false");
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Status: %s\n"), relayStatus(id) ? "true" : "false");
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
@ -202,8 +201,8 @@ void settingsSetup() {
lightColor(color.c_str());
lightUpdate(true, true);
}
e->stream->printf("Color: %s\n", lightColor().c_str());
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("BRIGHTNESS"), [](Embedis* e) {
@ -211,8 +210,8 @@ void settingsSetup() {
lightBrightness(String(e->argv[1]).toInt());
lightUpdate(true, true);
}
e->stream->printf("Brightness: %d\n", lightBrightness());
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Brightness: %d\n"), lightBrightness());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("MIRED"), [](Embedis* e) {
@ -221,8 +220,8 @@ void settingsSetup() {
lightColor(color.c_str());
lightUpdate(true, true);
}
e->stream->printf("Color: %s\n", lightColor().c_str());
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("KELVIN"), [](Embedis* e) {
@ -231,15 +230,15 @@ void settingsSetup() {
lightColor(color.c_str());
lightUpdate(true, true);
}
e->stream->printf("Color: %s\n", lightColor().c_str());
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Color: %s\n"), lightColor().c_str());
DEBUG_MSG_P(PSTR("+OK\n"));
});
}
Embedis::command( F("CHANNEL"), [](Embedis* e) {
if (e->argc < 2) {
return e->response(Embedis::ARGS_ERROR);
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
}
int id = String(e->argv[1]).toInt();
if (e->argc > 2) {
@ -247,17 +246,17 @@ void settingsSetup() {
lightChannel(id, value);
lightUpdate(true, true);
}
e->stream->printf("Channel #%d: %d\n", id, lightChannel(id));
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Channel #%d: %d\n"), id, lightChannel(id));
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
Embedis::command( F("EEPROM"), [](Embedis* e) {
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
e->stream->printf("Number of keys: %d\n", settingsKeyCount());
e->stream->printf("Free EEPROM: %d bytes (%d%%)\n", freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), settingsKeyCount());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("+OK\n"));
});
Embedis::command( F("DUMP"), [](Embedis* e) {
@ -265,25 +264,24 @@ void settingsSetup() {
for (unsigned int i=0; i<size; i++) {
String key = settingsKeyName(i);
String value = getSetting(key);
e->stream->printf("+%s => %s\n", key.c_str(), value.c_str());
DEBUG_MSG_P(PSTR("+%s => %s\n"), key.c_str(), value.c_str());
}
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if DEBUG_SUPPORT
Embedis::command( F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
Embedis::command( F("DUMP.RAW"), [](Embedis* e) {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) e->stream->printf("\n[%04X] ", i);
e->stream->printf("%02X ", EEPROM.read(i));
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
DEBUG_MSG_P(PSTR("%02X "), EEPROM.read(i));
}
e->stream->printf("\n");
e->response(Embedis::OK);
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
DEBUG_MSG_P(PSTR("[SETTINGS] EEPROM size: %d bytes\n"), SPI_FLASH_SEC_SIZE);


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


+ 66
- 11
code/espurna/utils.ino View File

@ -6,12 +6,33 @@ Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#include <Ticker.h>
Ticker _defer_reset;
String getIdentifier() {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), DEVICE, ESP.getChipId());
return String(buffer);
}
String getCoreVersion() {
String version = ESP.getCoreVersion();
#ifdef ARDUINO_ESP8266_RELEASE
if (version.equals("00000000")) {
version = String(ARDUINO_ESP8266_RELEASE);
}
#endif
return version;
}
String getCoreRevision() {
#ifdef ARDUINO_ESP8266_GIT_VER
return String(ARDUINO_ESP8266_GIT_VER);
#else
return String("");
#endif
}
String buildTime() {
const char time_now[] = __TIME__; // hh:mm:ss
@ -96,13 +117,13 @@ void heartbeat() {
#if (HEARTBEAT_REPORT_UPTIME)
mqttSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#if INFLUXDB_SUPPORT
influxDBSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
idbSend(MQTT_TOPIC_UPTIME, String(uptime_seconds).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_FREEHEAP)
mqttSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#if INFLUXDB_SUPPORT
influxDBSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
idbSend(MQTT_TOPIC_FREEHEAP, String(free_heap).c_str());
#endif
#endif
#if (HEARTBEAT_REPORT_RELAY)
@ -120,30 +141,53 @@ void heartbeat() {
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
#endif
// Send info to websocket clients
{
char buffer[200];
snprintf_P(
buffer,
sizeof(buffer) - 1,
PSTR("{\"time\": \"%s\", \"uptime\": %lu, \"heap\": %lu}"),
ntpDateTime().c_str(), uptime_seconds, free_heap
);
wsSend(buffer);
}
}
// -----------------------------------------------------------------------------
void customReset(unsigned char status) {
EEPROM.write(EEPROM_CUSTOM_RESET, status);
EEPROM.commit();
}
unsigned char customReset() {
unsigned char resetReason() {
static unsigned char status = 255;
if (status == 255) {
status = EEPROM.read(EEPROM_CUSTOM_RESET);
if (status > 0) customReset(0);
if (status > 0) resetReason(0);
if (status > CUSTOM_RESET_MAX) status = 0;
}
return status;
}
void resetReason(unsigned char reason) {
EEPROM.write(EEPROM_CUSTOM_RESET, reason);
EEPROM.commit();
}
void reset(unsigned char reason) {
resetReason(reason);
ESP.restart();
}
void deferredReset(unsigned long delay, unsigned char reason) {
_defer_reset.once_ms(delay, reset, reason);
}
// -----------------------------------------------------------------------------
#if SYSTEM_CHECK_ENABLED
// Call this method on boot with start=true to increase the crash counter
// Call it again once the system is stable to decrease the counter
// If the counter reaches CRASH_COUNT_MAX then the system is flagged as unstable
// If the counter reaches SYSTEM_CHECK_MAX then the system is flagged as unstable
// setting _systemOK = false;
//
// An unstable system will only have serial access, WiFi in AP mode and OTA
@ -156,7 +200,7 @@ void systemCheck(bool stable) {
value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System OK\n"));
} else {
if (++value > CRASH_COUNT_MAX) {
if (++value > SYSTEM_CHECK_MAX) {
_systemStable = false;
value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
@ -170,6 +214,17 @@ bool systemCheck() {
return _systemStable;
}
void systemCheckLoop() {
static bool checked = false;
if (!checked && (millis() > SYSTEM_CHECK_TIME)) {
// Check system as stable
systemCheck(true);
checked = true;
}
}
#endif
// -----------------------------------------------------------------------------
char * ltrim(char * s) {


+ 26
- 997
code/espurna/web.ino
File diff suppressed because it is too large
View File


+ 7
- 1
code/espurna/wifi.ino View File

@ -66,7 +66,9 @@ void wifiConfigure() {
jw.cleanNetworks();
// If system is flagged unstable we do not init wifi networks
if (!systemCheck()) return;
#if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return;
#endif
int i;
for (i = 0; i< WIFI_MAX_NETWORKS; i++) {
@ -172,6 +174,10 @@ void wifiInject() {
void wifiSetup() {
#if WIFI_SLEEP_ENABLED
wifi_set_sleep_type(LIGHT_SLEEP_T);
#endif
wifiInject();
wifiConfigure();


code/espurna/web.h → code/espurna/ws.h View File

@ -19,8 +19,8 @@ class WebSocketIncommingBuffer {
public:
WebSocketIncommingBuffer(AwsMessageHandler cb, bool terminate_string = true, bool cb_on_fragments = false) :
_cb(cb),
_cb_on_fragments(cb_on_fragments),
_terminate_string(terminate_string),
_cb_on_fragments(cb_on_fragments),
_buffer(0)
{}
@ -30,19 +30,24 @@ class WebSocketIncommingBuffer {
void data_event(AsyncWebSocketClient *client, AwsFrameInfo *info, uint8_t *data, size_t len) {
if((info->final || _cb_on_fragments) &&
!_terminate_string && info->index == 0 && info->len == len) {
if ((info->final || _cb_on_fragments)
&& !_terminate_string
&& info->index == 0
&& info->len == len) {
/* The whole message is in a single frame and we got all of it's
data therefore we can parse it without copying the data first.*/
_cb(client, data, len);
} else {
if (info->len > MAX_WS_MSG_SIZE) return;
/* Check if previous fragment was discarded because it was too long. */
if (!_cb_on_fragments && info->num > 0 && !_buffer) return;
//if (!_cb_on_fragments && info->num > 0 && !_buffer) return;
if (!_buffer) _buffer = new std::vector<uint8_t>();
if (!_buffer) {
_buffer = new std::vector<uint8_t>();
}
if (info->index == 0) {
//New frame => preallocate memory
if (_cb_on_fragments) {
@ -59,16 +64,17 @@ class WebSocketIncommingBuffer {
}
}
}
//assert(_buffer->size() == info->index);
_buffer->insert(_buffer->end(), data, data+len);
if (info->index + len == info->len &&
(info->final || _cb_on_fragments)) {
if (info->index + len == info->len
&& (info->final || _cb_on_fragments)) {
// Frame/message complete
if (_terminate_string) {
_buffer->push_back(0);
}
if (_terminate_string) _buffer->push_back(0);
_cb(client, _buffer->data(), _buffer->size());
_buffer->clear();
}
}
}

+ 755
- 0
code/espurna/ws.ino View File

@ -0,0 +1,755 @@
/*
WEBSOCKET MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if WEB_SUPPORT
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <Ticker.h>
#include <vector>
#include "ws.h"
AsyncWebSocket _ws("/ws");
Ticker _web_defer;
std::vector<ws_callback_f> _ws_sender_callbacks;
std::vector<ws_callback_f> _ws_receiver_callbacks;
// -----------------------------------------------------------------------------
// Private methods
// -----------------------------------------------------------------------------
void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
wsSend_P(PSTR("{\"mqttStatus\": true}"));
}
if (type == MQTT_DISCONNECT_EVENT) {
wsSend_P(PSTR("{\"mqttStatus\": false}"));
}
}
void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing: %s\n"), length ? (char*) payload : "");
// Get client ID
uint32_t client_id = client->id();
// Parse JSON input
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Error parsing data\n"));
wsSend_P(client_id, PSTR("{\"message\": 3}"));
return;
}
// Check actions
if (root.containsKey("action")) {
String action = root["action"];
DEBUG_MSG_P(PSTR("[WEBSOCKET] Requested action: %s\n"), action.c_str());
if (action.equals("reset")) {
deferredReset(100, CUSTOM_RESET_WEB);
}
#ifdef ITEAD_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"];
if (!data.containsKey("app") || (data["app"] != APP_NAME)) {
wsSend_P(client_id, PSTR("{\"message\": 4}"));
return;
}
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
for (auto element : data) {
if (strcmp(element.key, "app") == 0) continue;
if (strcmp(element.key, "version") == 0) continue;
setSetting(element.key, element.value.as<char*>());
}
saveSettings();
wsSend_P(client_id, PSTR("{\"message\": 5}"));
}
if (action.equals("reconnect")) {
// Let the HTTP request return and disconnect after 100ms
_web_defer.once_ms(100, wifiDisconnect);
}
if (action.equals("relay") && root.containsKey("data")) {
JsonObject& data = root["data"];
if (data.containsKey("status")) {
unsigned char value = relayParsePayload(data["status"]);
if (value == 3) {
relayWS();
} else if (value < 3) {
unsigned int relayID = 0;
if (data.containsKey("id")) {
String value = data["id"];
relayID = value.toInt();
}
// Action to perform
if (value == 0) {
relayStatus(relayID, false);
} else if (value == 1) {
relayStatus(relayID, true);
} else if (value == 2) {
relayToggle(relayID);
}
}
}
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
if (action.equals("rgb") && root.containsKey("data")) {
lightColor((const char *) root["data"], true);
lightUpdate(true, true);
}
if (action.equals("brightness") && root.containsKey("data")) {
lightBrightness(root["data"]);
lightUpdate(true, true);
}
if (action.equals("hsv") && root.containsKey("data")) {
lightColor((const char *) root["data"], false);
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);
}
}
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
if (action.equals("anim_mode") && root.containsKey("data")) {
lightAnimMode(root["data"]);
lightUpdate(true, true);
}
if (action.equals("anim_speed") && root.containsKey("data")) {
lightAnimSpeed(root["data"]);
lightUpdate(true, true);
}
#endif //LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
#endif //LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
};
// Check config
if (root.containsKey("config") && root["config"].is<JsonArray&>()) {
JsonArray& config = root["config"];
DEBUG_MSG_P(PSTR("[WEBSOCKET] Parsing configuration data\n"));
unsigned char webMode = WEB_MODE_NORMAL;
bool save = false;
bool changed = false;
bool changedMQTT = false;
bool changedNTP = false;
unsigned int network = 0;
unsigned int dczRelayIdx = 0;
String adminPass;
for (unsigned int i=0; i<config.size(); i++) {
String key = config[i]["name"];
String value = config[i]["value"];
// Skip firmware filename
if (key.equals("filename")) continue;
#if POWER_PROVIDER != POWER_PROVIDER_NONE
if (key == "pwrExpectedP") {
powerCalibrate(POWER_MAGNITUDE_ACTIVE, value.toFloat());
changed = true;
continue;
}
if (key == "pwrExpectedV") {
powerCalibrate(POWER_MAGNITUDE_VOLTAGE, value.toFloat());
changed = true;
continue;
}
if (key == "pwrExpectedC") {
powerCalibrate(POWER_MAGNITUDE_CURRENT, value.toFloat());
changed = true;
continue;
}
if (key == "pwrExpectedF") {
powerCalibrate(POWER_MAGNITUDE_POWER_FACTOR, value.toFloat());
changed = true;
continue;
}
if (key == "pwrResetCalibration") {
if (value.toInt() == 1) {
powerResetCalibration();
changed = true;
}
continue;
}
#endif
#if DOMOTICZ_SUPPORT
if (key == "dczRelayIdx") {
if (dczRelayIdx >= relayCount()) continue;
key = key + String(dczRelayIdx);
++dczRelayIdx;
}
#else
if (key.startsWith("dcz")) continue;
#endif
// Web portions
if (key == "webPort") {
if ((value.toInt() == 0) || (value.toInt() == 80)) {
save = changed = true;
delSetting(key);
continue;
}
}
if (key == "webMode") {
webMode = value.toInt();
continue;
}
// Check password
if (key == "adminPass1") {
adminPass = value;
continue;
}
if (key == "adminPass2") {
if (!value.equals(adminPass)) {
wsSend_P(client_id, PSTR("{\"message\": 7}"));
return;
}
if (value.length() == 0) continue;
wsSend_P(client_id, PSTR("{\"action\": \"reload\"}"));
key = String("adminPass");
}
if (key == "ssid") {
key = key + String(network);
}
if (key == "pass") {
key = key + String(network);
}
if (key == "ip") {
key = key + String(network);
}
if (key == "gw") {
key = key + String(network);
}
if (key == "mask") {
key = key + String(network);
}
if (key == "dns") {
key = key + String(network);
++network;
}
if (value != getSetting(key)) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] Storing %s = %s\n", key.c_str(), value.c_str()));
setSetting(key, value);
save = changed = true;
if (key.startsWith("mqtt")) changedMQTT = true;
#if NTP_SUPPORT
if (key.startsWith("ntp")) changedNTP = true;
#endif
}
}
if (webMode == WEB_MODE_NORMAL) {
// Clean wifi networks
int i = 0;
while (i < network) {
if (!hasSetting("ssid", i)) {
delSetting("ssid", i);
break;
}
if (!hasSetting("pass", i)) delSetting("pass", i);
if (!hasSetting("ip", i)) delSetting("ip", i);
if (!hasSetting("gw", i)) delSetting("gw", i);
if (!hasSetting("mask", i)) delSetting("mask", i);
if (!hasSetting("dns", i)) delSetting("dns", i);
++i;
}
while (i < WIFI_MAX_NETWORKS) {
if (hasSetting("ssid", i)) {
save = changed = true;
}
delSetting("ssid", i);
delSetting("pass", i);
delSetting("ip", i);
delSetting("gw", i);
delSetting("mask", i);
delSetting("dns", i);
++i;
}
}
// Save settings
if (save) {
wsConfigure();
saveSettings();
wifiConfigure();
otaConfigure();
if (changedMQTT) {
mqttConfigure();
mqttDisconnect();
}
#if ALEXA_SUPPORT
alexaConfigure();
#endif
#if INFLUXDB_SUPPORT
idbConfigure();
#endif
#if DOMOTICZ_SUPPORT
domoticzConfigure();
#endif
#if NOFUSS_SUPPORT
nofussConfigure();
#endif
#if RF_SUPPORT
rfBuildCodes();
#endif
#if POWER_PROVIDER != POWER_PROVIDER_NONE
powerConfigure();
#endif
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
#if LIGHT_SAVE_ENABLED == 0
lightSave();
#endif
#endif
#if NTP_SUPPORT
if (changedNTP) ntpConfigure();
#endif
#if HOMEASSISTANT_SUPPORT
haConfigure();
#endif
}
if (changed) {
wsSend_P(client_id, PSTR("{\"message\": 8}"));
} else {
wsSend_P(client_id, PSTR("{\"message\": 9}"));
}
}
}
void _wsStart(uint32_t client_id) {
char chipid[7];
snprintf_P(chipid, sizeof(chipid), PSTR("%06X"), ESP.getChipId());
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
bool changePassword = false;
#if WEB_FORCE_PASS_CHANGE
String adminPass = getSetting("adminPass", ADMIN_PASS);
if (adminPass.equals(ADMIN_PASS)) changePassword = true;
#endif
if (changePassword) {
root["webMode"] = WEB_MODE_PASSWORD;
} else {
root["webMode"] = WEB_MODE_NORMAL;
root["app_name"] = APP_NAME;
root["app_version"] = APP_VERSION;
root["app_build"] = buildTime();
root["manufacturer"] = MANUFACTURER;
root["chipid"] = chipid;
root["mac"] = WiFi.macAddress();
root["device"] = DEVICE;
root["hostname"] = getSetting("hostname");
root["network"] = getNetwork();
root["deviceip"] = getIP();
root["time"] = ntpDateTime();
root["uptime"] = getUptime();
root["heap"] = ESP.getFreeHeap();
root["sketch_size"] = ESP.getSketchSize();
root["free_size"] = ESP.getFreeSketchSpace();
#if NTP_SUPPORT
root["ntpVisible"] = 1;
root["ntpStatus"] = ntpConnected();
root["ntpServer1"] = getSetting("ntpServer1", NTP_SERVER);
root["ntpServer2"] = getSetting("ntpServer2");
root["ntpServer3"] = getSetting("ntpServer3");
root["ntpOffset"] = getSetting("ntpOffset", NTP_TIME_OFFSET).toInt();
root["ntpDST"] = getSetting("ntpDST", NTP_DAY_LIGHT).toInt() == 1;
#endif
root["mqttStatus"] = mqttConnected();
root["mqttEnabled"] = mqttEnabled();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", MQTT_PORT);
root["mqttUser"] = getSetting("mqttUser");
root["mqttPassword"] = getSetting("mqttPassword");
#if ASYNC_TCP_SSL_ENABLED
root["mqttsslVisible"] = 1;
root["mqttUseSSL"] = getSetting("mqttUseSSL", 0).toInt() == 1;
root["mqttFP"] = getSetting("mqttFP");
#endif
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
root["mqttUseJson"] = getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1;
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
relay.add(relayStatus(relayID));
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
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;
root["useCSS"] = getSetting("useCSS", LIGHT_USE_CSS).toInt() == 1;
bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
root["useRGB"] = useRGB;
if (lightHasColor()) {
if (useRGB) {
root["rgb"] = lightColor(true);
root["brightness"] = lightBrightness();
} else {
root["hsv"] = lightColor(false);
}
#ifdef LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
root["anim_mode"] = lightAnimMode();
root["anim_speed"] = lightAnimSpeed();
#endif // LIGHT_PROVIDER_EXPERIMENTAL_RGB_ONLY_HSV_IR
}
JsonArray& channels = root.createNestedArray("channels");
for (unsigned char id=0; id < lightChannels(); id++) {
channels.add(lightChannel(id));
}
#endif
root["relayMode"] = getSetting("relayMode", RELAY_MODE);
root["relayPulseMode"] = getSetting("relayPulseMode", RELAY_PULSE_MODE);
root["relayPulseTime"] = getSetting("relayPulseTime", RELAY_PULSE_TIME).toFloat();
if (relayCount() > 1) {
root["multirelayVisible"] = 1;
root["relaySync"] = getSetting("relaySync", RELAY_SYNC);
}
root["btnDelay"] = getSetting("btnDelay", BUTTON_DBLCLICK_DELAY).toInt();
root["webPort"] = getSetting("webPort", WEB_PORT).toInt();
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();
root["tmpCorrection"] = getSetting("tmpCorrection", TMP_CORRECTION).toFloat();
#if HOMEASSISTANT_SUPPORT
root["haVisible"] = 1;
root["haPrefix"] = getSetting("haPrefix", HOMEASSISTANT_PREFIX);
#endif // HOMEASSISTANT_SUPPORT
#if DOMOTICZ_SUPPORT
root["dczVisible"] = 1;
root["dczEnabled"] = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
root["dczSkip"] = getSetting("dczSkip", DOMOTICZ_SKIP_TIME);
root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC);
root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
JsonArray& dczRelayIdx = root.createNestedArray("dczRelayIdx");
for (byte i=0; i<relayCount(); i++) {
dczRelayIdx.add(domoticzIdx(i));
}
#if DHT_SUPPORT
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
root["dczHumIdx"] = getSetting("dczHumIdx").toInt();
#endif
#if DS18B20_SUPPORT
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
#endif
#if ANALOG_SUPPORT
root["dczAnaIdx"] = getSetting("dczAnaIdx").toInt();
#endif
#if POWER_PROVIDER != POWER_PROVIDER_NONE
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt();
root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt();
#if POWER_HAS_ACTIVE
root["dczVoltIdx"] = getSetting("dczVoltIdx").toInt();
#endif
#endif
#endif
#if DS18B20_SUPPORT
root["dsVisible"] = 1;
root["dsTmp"] = getDSTemperatureStr();
#endif
#if DHT_SUPPORT
root["dhtVisible"] = 1;
root["dhtTmp"] = getDHTTemperature();
root["dhtHum"] = getDHTHumidity();
#endif
#if RF_SUPPORT
root["rfVisible"] = 1;
root["rfChannel"] = getSetting("rfChannel", RF_CHANNEL);
root["rfDevice"] = getSetting("rfDevice", RF_DEVICE);
#endif
#if POWER_PROVIDER != POWER_PROVIDER_NONE
root["pwrVisible"] = 1;
root["pwrCurrent"] = getCurrent();
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();
root["pwrFactor"] = int(100 * getPowerFactor());
#endif
#if (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121)
root["emonVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
root["hlwVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
root["v9261fVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560
root["ech1560fVisible"] = 1;
#endif
#endif
#if NOFUSS_SUPPORT
root["nofussVisible"] = 1;
root["nofussEnabled"] = getSetting("nofussEnabled", NOFUSS_ENABLED).toInt() == 1;
root["nofussServer"] = getSetting("nofussServer", NOFUSS_SERVER);
#endif
#ifdef ITEAD_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
#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++) {
if (getSetting("ssid" + String(i)).length() == 0) break;
JsonObject& network = wifi.createNestedObject();
network["ssid"] = getSetting("ssid" + String(i));
network["pass"] = getSetting("pass" + String(i));
network["ip"] = getSetting("ip" + String(i));
network["gw"] = getSetting("gw" + String(i));
network["mask"] = getSetting("mask" + String(i));
network["dns"] = getSetting("dns" + String(i));
}
// Module setters
for (unsigned char i = 0; i < _ws_sender_callbacks.size(); i++) {
(_ws_sender_callbacks[i])(root);
}
}
String output;
root.printTo(output);
wsSend(client_id, (char *) output.c_str());
}
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if (type == WS_EVT_CONNECT) {
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);
} else if(type == WS_EVT_PONG) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u pong(%u): %s\n"), client->id(), len, len ? (char*) data : "");
} else if(type == WS_EVT_DATA) {
//DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u data(%u): %s\n"), client->id(), len, len ? (char*) data : "");
WebSocketIncommingBuffer *buffer = (WebSocketIncommingBuffer *)client->_tempObject;
AwsFrameInfo * info = (AwsFrameInfo*)arg;
buffer->data_event(client, info, data, len);
}
}
// -----------------------------------------------------------------------------
// Piblic API
// -----------------------------------------------------------------------------
bool wsConnected() {
return (_ws.count() > 0);
}
void wsRegister(ws_callback_f sender, ws_callback_f receiver) {
_ws_sender_callbacks.push_back(sender);
if (receiver) _ws_receiver_callbacks.push_back(receiver);
}
void wsSend(ws_callback_f sender) {
if (_ws.count() > 0) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
sender(root);
String output;
root.printTo(output);
wsSend((char *) output.c_str());
}
}
void wsSend(const char * payload) {
if (_ws.count() > 0) {
_ws.textAll(payload);
}
}
void wsSend_P(PGM_P payload) {
if (_ws.count() > 0) {
char buffer[strlen_P(payload)];
strcpy_P(buffer, payload);
_ws.textAll(buffer);
}
}
void wsSend(uint32_t client_id, const char * payload) {
_ws.text(client_id, payload);
}
void wsSend_P(uint32_t client_id, PGM_P payload) {
char buffer[strlen_P(payload)];
strcpy_P(buffer, payload);
_ws.text(client_id, buffer);
}
void wsConfigure() {
_ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str());
}
void wsSetup() {
_ws.onEvent(_wsEvent);
wsConfigure();
webServer()->addHandler(&_ws);
mqttRegister(_wsMQTTCallback);
}
#endif // WEB_SUPPORT

+ 1
- 35
code/html/custom.js View File

@ -21,7 +21,6 @@ function initMessages() {
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";
@ -91,24 +90,6 @@ function generateAPIKey() {
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);
@ -626,14 +607,12 @@ function processData(data) {
password = data.webMode == 1;
$("#layout").toggle(data.webMode == 0);
$("#password").toggle(data.webMode == 1);
$("#credentials").hide();
}
// Actions
if (key == "action") {
if (data.action == "reload") {
if (password) forgetCredentials();
doReload(1000);
}
@ -954,24 +933,11 @@ function init() {
$(".button-add-network").on('click', function() {
$("div.more", addNetwork()).toggle();
});
$(".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',
'url': window.location.href + 'auth'
}).done(function(data) {
connect();
}).fail(function(){
$("#credentials").show();
});
connect();
}


+ 17
- 14
code/html/index.html View File

@ -21,10 +21,6 @@
<body>
<div id="credentials" class="webmode">
Wrong credentials
</div>
<div id="password" class="webmode">
<div class="content">
@ -416,24 +412,25 @@
</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-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-1 pure-u-sm-1-4"><label for="haEnabled">Home Assistant</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="haEnabled" tabindex="13" /></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">
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 />
Home Assistant auto-discovery feature. Enable and save to add the device to your HA console.
You might want to disable CSS style (above) so Home Assistant can parse the color.
</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-4" name="haPrefix" type="text" tabindex="14" />
</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="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 class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="15" value="0"> Celsius (&deg;C)</input></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="radio" name="tmpUnits" tabindex="16" value="1"> Fahrenheit (&deg;F)</input></div>
</div>
<div class="pure-g module module-ds module-dht">
@ -719,6 +716,12 @@
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="dczEnabled" tabindex="30" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4" for="dczSkip">Anti-recursion time</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczSkip" type="number" min="0" max="10" tabindex="31" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Skips in/out messages from the same IDX within this time in seconds</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="dczTopicIn">Domoticz IN Topic</label>
<input class="pure-u-1 pure-u-md-3-4" name="dczTopicIn" type="text" tabindex="31" />


+ 31
- 7
code/platformio.ini View File

@ -1,21 +1,19 @@
[platformio]
env_default = nodemcu-lolin
env_default = wemos-d1mini-relayshield
src_dir = espurna
data_dir = espurna/data
[common]
platform = espressif8266
#platform = espressif8266_stage
#platform = espressif8266
platform = espressif8266_stage
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#9b0cc37 // 2.3.0 compatible
#https://github.com/me-no-dev/ESPAsyncTCP#289a681 // 2.4.0-rc2 compatible
lib_deps =
https://github.com/xoseperez/Time
ArduinoJson
https://github.com/me-no-dev/ESPAsyncTCP#9b0cc37
https://github.com/me-no-dev/ESPAsyncTCP#a57560d
https://github.com/me-no-dev/ESPAsyncWebServer#313f337
https://github.com/marvinroger/async-mqtt-client#v0.8.1
PubSubClient
@ -31,11 +29,13 @@ lib_deps =
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
https://github.com/xoseperez/my9291#2.0.0
https://github.com/xoseperez/my92xx#3.0.0
https://github.com/xoseperez/RemoteSwitch-arduino-library.git
https://github.com/FastLED/FastLED#v3.1.6
https://github.com/markszabo/IRremoteESP8266#v2.2.0
lib_ignore =
#extra_scripts = post:core_version.py
extra_scripts =
# ------------------------------------------------------------------------------
@ -48,6 +48,7 @@ lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DWEMOS_D1_MINI_RELAYSHIELD -DDEBUG_FAUXMO=Serial -DNOWSAUTH
upload_speed = 460800
monitor_baud = 115200
extra_scripts = ${common.extra_scripts}
[env:wemos-d1mini-relayshield-ssl]
platform = espressif8266_stage
@ -229,6 +230,29 @@ upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:itead-sonoff-th]
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} -DITEAD_SONOFF_TH
monitor_baud = 115200
[env:itead-sonoff-th-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} -DITEAD_SONOFF_TH
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:itead-sonoff-pow]
platform = ${common.platform}
framework = arduino


Loading…
Cancel
Save