Browse Source

Merge

fastled
Xose Pérez 7 years ago
parent
commit
c6b26efbb7
20 changed files with 3445 additions and 3278 deletions
  1. +0
    -2
      code/espurna/analog.ino
  2. +11
    -1
      code/espurna/button.ino
  3. +2
    -0
      code/espurna/config/arduino.h
  4. +7
    -4
      code/espurna/config/general.h
  5. +26
    -0
      code/espurna/config/hardware.h
  6. +1
    -0
      code/espurna/config/prototypes.h
  7. +63
    -0
      code/espurna/domoticz.ino
  8. +71
    -60
      code/espurna/emon.ino
  9. +3
    -0
      code/espurna/espurna.ino
  10. +67
    -47
      code/espurna/hlw8012.ino
  11. +38
    -4
      code/espurna/mqtt.ino
  12. +1
    -1
      code/espurna/ota.ino
  13. +54
    -108
      code/espurna/relay.ino
  14. +6
    -0
      code/espurna/settings.ino
  15. +3023
    -3019
      code/espurna/static/index.html.gz.h
  16. +13
    -0
      code/espurna/utils.ino
  17. +21
    -9
      code/espurna/web.ino
  18. +6
    -2
      code/html/custom.js
  19. +25
    -15
      code/html/index.html
  20. +7
    -6
      code/platformio.ini

+ 0
- 2
code/espurna/analog.ino View File

@ -8,8 +8,6 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#if ENABLE_ANALOG #if ENABLE_ANALOG
int _analog = 0;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ANALOG // ANALOG
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 11
- 1
code/espurna/button.ino View File

@ -92,6 +92,16 @@ void buttonEvent(unsigned int id, unsigned char event) {
relayToggle(_buttons[id].relayID - 1); relayToggle(_buttons[id].relayID - 1);
} }
} }
if (action == BUTTON_MODE_ON) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, true);
}
}
if (action == BUTTON_MODE_OFF) {
if (_buttons[id].relayID > 0) {
relayStatus(_buttons[id].relayID - 1, false);
}
}
if (action == BUTTON_MODE_AP) createAP(); if (action == BUTTON_MODE_AP) createAP();
if (action == BUTTON_MODE_RESET) { if (action == BUTTON_MODE_RESET) {
customReset(CUSTOM_RESET_HARDWARE); customReset(CUSTOM_RESET_HARDWARE);
@ -174,7 +184,7 @@ void buttonLoop() {
} }
// Otherwise check if any of the other two BUTTONs // Otherwise check if any of the other two BUTTONs
// (in the header) has been pressent, but we should
// (in the header) has been pressed, but we should
// ensure that we only toggle one of them to avoid // ensure that we only toggle one of them to avoid
// the synchronization going mad // the synchronization going mad
// This loop is generic for any PSB-04 module // This loop is generic for any PSB-04 module


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

@ -45,3 +45,5 @@
//#define ENABLE_NOFUSS 1 //#define ENABLE_NOFUSS 1
//#define ENABLE_DOMOTICZ 0 //#define ENABLE_DOMOTICZ 0
//#define ENABLE_ANALOG 1 //#define ENABLE_ANALOG 1
//#define ENABLE_INFLUXDB 0
//#define ENABLE_I2C 1

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

@ -100,6 +100,7 @@ PROGMEM const char* const custom_reset_string[] = {
#define BUTTON_EVENT_NONE 0 #define BUTTON_EVENT_NONE 0
#define BUTTON_EVENT_PRESSED 1 #define BUTTON_EVENT_PRESSED 1
#define BUTTON_EVENT_RELEASED 2
#define BUTTON_EVENT_CLICK 2 #define BUTTON_EVENT_CLICK 2
#define BUTTON_EVENT_DBLCLICK 3 #define BUTTON_EVENT_DBLCLICK 3
#define BUTTON_EVENT_LNGCLICK 4 #define BUTTON_EVENT_LNGCLICK 4
@ -107,10 +108,12 @@ PROGMEM const char* const custom_reset_string[] = {
#define BUTTON_MODE_NONE 0 #define BUTTON_MODE_NONE 0
#define BUTTON_MODE_TOGGLE 1 #define BUTTON_MODE_TOGGLE 1
#define BUTTON_MODE_AP 2
#define BUTTON_MODE_RESET 3
#define BUTTON_MODE_PULSE 4
#define BUTTON_MODE_FACTORY 5
#define BUTTON_MODE_ON 2
#define BUTTON_MODE_OFF 3
#define BUTTON_MODE_AP 4
#define BUTTON_MODE_RESET 5
#define BUTTON_MODE_PULSE 6
#define BUTTON_MODE_FACTORY 7
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// RELAY // RELAY


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

@ -496,6 +496,32 @@
#define BUTTON4_RELAY 0 #define BUTTON4_RELAY 0
#endif #endif
#ifndef RELAY1_DELAY_ON
#define RELAY1_DELAY_ON 0
#endif
#ifndef RELAY2_DELAY_ON
#define RELAY2_DELAY_ON 0
#endif
#ifndef RELAY3_DELAY_ON
#define RELAY3_DELAY_ON 0
#endif
#ifndef RELAY4_DELAY_ON
#define RELAY4_DELAY_ON 0
#endif
#ifndef RELAY1_DELAY_OFF
#define RELAY1_DELAY_OFF 0
#endif
#ifndef RELAY2_DELAY_OFF
#define RELAY2_DELAY_OFF 0
#endif
#ifndef RELAY3_DELAY_OFF
#define RELAY3_DELAY_OFF 0
#endif
#ifndef RELAY4_DELAY_OFF
#define RELAY4_DELAY_OFF 0
#endif
#ifndef RELAY1_LED #ifndef RELAY1_LED
#define RELAY1_LED 0 #define RELAY1_LED 0
#endif #endif


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

@ -17,3 +17,4 @@ template<typename T> String getSetting(const String& key, unsigned int index, T
template<typename T> void domoticzSend(const char * key, T value); 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> void domoticzSend(const char * key, T nvalue, const char * svalue);
template<typename T> bool influxDBSend(const char * topic, T payload); template<typename T> bool influxDBSend(const char * topic, T payload);
char * ltrim(char * s);

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

@ -8,6 +8,8 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#if ENABLE_DOMOTICZ #if ENABLE_DOMOTICZ
#include <ArduinoJson.h>
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue) { template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue) {
unsigned int idx = getSetting(key).toInt(); unsigned int idx = getSetting(key).toInt();
if (idx > 0) { if (idx > 0) {
@ -21,4 +23,65 @@ template<typename T> void domoticzSend(const char * key, T nvalue) {
domoticzSend(key, nvalue, ""); domoticzSend(key, nvalue, "");
} }
void relayDomoticzSend(unsigned int relayID) {
char buffer[15];
sprintf(buffer, "dczRelayIdx%d", relayID);
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
}
int relayFromIdx(unsigned int idx) {
for (int relayID=0; relayID<relayCount(); relayID++) {
if (relayToIdx(relayID) == idx) {
return relayID;
}
}
return -1;
}
int relayToIdx(unsigned int relayID) {
char buffer[15];
sprintf(buffer, "dczRelayIdx%d", relayID);
return getSetting(buffer).toInt();
}
void domoticzSetup() {
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribeRaw(dczTopicOut.c_str());
}
if (type == MQTT_MESSAGE_EVENT) {
// Check topic
if (dczTopicOut.equals(topic)) {
// Parse response
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[DOMOTICZ] Error parsing data\n"));
return;
}
// IDX
unsigned long idx = root["idx"];
int relayID = relayFromIdx(idx);
if (relayID >= 0) {
unsigned long value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx);
relayStatus(relayID, value == 1);
}
}
}
});
}
#endif #endif

+ 71
- 60
code/espurna/emon.ino View File

@ -25,8 +25,10 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#define ADC121_REG_CONVH 0x07 #define ADC121_REG_CONVH 0x07
EmonLiteESP emon; EmonLiteESP emon;
double _current = 0;
unsigned int _power = 0;
bool _emonReady = false;
double _emonCurrent = 0;
unsigned int _emonPower = 0;
unsigned int _emonVoltage = 0;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Provider // Provider
@ -54,28 +56,40 @@ unsigned int currentCallback() {
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// EMON
// HAL
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void setCurrentRatio(float value) { void setCurrentRatio(float value) {
emon.setCurrentRatio(value); emon.setCurrentRatio(value);
} }
unsigned int getPower() {
return _power;
unsigned int getApparentPower() {
return int(getCurrent() * getVoltage());
} }
double getCurrent() { double getCurrent() {
return _current;
double current = emon.getCurrent(EMON_SAMPLES);
current -= EMON_CURRENT_OFFSET;
if (current < 0) current = 0;
return current;
} }
unsigned int getVoltage() {
return getSetting("emonVoltage", EMON_MAINS_VOLTAGE).toInt();
}
// -----------------------------------------------------------------------------
void powerMonitorSetup() { void powerMonitorSetup() {
// backwards compatibility // backwards compatibility
String tmp; String tmp;
tmp = getSetting("pwMainsVoltage", EMON_MAINS_VOLTAGE); tmp = getSetting("pwMainsVoltage", EMON_MAINS_VOLTAGE);
setSetting("emonMains", tmp);
setSetting("emonVoltage", tmp);
delSetting("pwMainsVoltage"); delSetting("pwMainsVoltage");
tmp = getSetting("emonMains", EMON_MAINS_VOLTAGE);
setSetting("emonVoltage", tmp);
delSetting("emonMains");
tmp = getSetting("pwCurrentRatio", EMON_CURRENT_RATIO); tmp = getSetting("pwCurrentRatio", EMON_CURRENT_RATIO);
setSetting("emonRatio", tmp); setSetting("emonRatio", tmp);
delSetting("pwCurrentRatio"); delSetting("pwCurrentRatio");
@ -98,7 +112,19 @@ void powerMonitorSetup() {
#endif #endif
apiRegister(EMON_APOWER_TOPIC, EMON_APOWER_TOPIC, [](char * buffer, size_t len) { apiRegister(EMON_APOWER_TOPIC, EMON_APOWER_TOPIC, [](char * buffer, size_t len) {
snprintf(buffer, len, "%d", _power);
if (_emonReady) {
snprintf(buffer, len, "%d", _emonPower);
} else {
buffer = NULL;
}
});
apiRegister(EMON_CURRENT_TOPIC, EMON_CURRENT_TOPIC, [](char * buffer, size_t len) {
if (_emonReady) {
dtostrf(_emonCurrent, len-1, 3, buffer);
} else {
buffer = NULL;
}
}); });
} }
@ -119,80 +145,65 @@ void powerMonitorLoop() {
if (millis() > next_measurement) { if (millis() > next_measurement) {
// Safety check: do not read current if relay is OFF
// You could be monitoring another line with the current clamp...
//if (!relayStatus(0)) {
// _current = 0;
//} else {
_current = emon.getCurrent(EMON_SAMPLES);
_current -= EMON_CURRENT_OFFSET;
if (_current < 0) _current = 0;
//}
if (measurements == 0) {
max = min = _current;
} else {
if (_current > max) max = _current;
if (_current < min) min = _current;
}
sum += _current;
++measurements;
int voltage = getVoltage();
float mainsVoltage = getSetting("emonMains", EMON_MAINS_VOLTAGE).toFloat();
{
char current[6];
dtostrf(_current, 5, 2, current);
DEBUG_MSG_P(PSTR("[ENERGY] Current: %sA\n"), current);
DEBUG_MSG_P(PSTR("[ENERGY] Power: %dW\n"), int(_current * mainsVoltage));
double current = getCurrent();
if (measurements == 0) {
max = min = current;
} else {
if (_emonCurrent > max) max = current;
if (_emonCurrent < min) min = current;
}
sum += current;
++measurements;
// Update websocket clients
char text[64];
sprintf_P(text, PSTR("{\"emonVisible\": 1, \"powApparentPower\": %d}"), int(_current * mainsVoltage));
wsSend(text);
DEBUG_MSG_P(PSTR("[ENERGY] Current: %sA\n"), String(current, 3).c_str());
DEBUG_MSG_P(PSTR("[ENERGY] Power: %dW\n"), int(current * voltage));
// Update websocket clients
if (wsConnected()) {
char text[100];
sprintf_P(text, PSTR("{\"emonVisible\": 1, \"emonApparentPower\": %d, \"emonCurrent\": %s}"), int(current * voltage), String(current, 3).c_str());
wsSend(text);
}
}
// Send MQTT messages averaged every EMON_MEASUREMENTS // Send MQTT messages averaged every EMON_MEASUREMENTS
if (measurements == EMON_MEASUREMENTS) { if (measurements == EMON_MEASUREMENTS) {
// Calculate average current (removing max and min values) and create C-string
double average = (sum - max - min) / (measurements - 2);
dtostrf(average, 5, 2, current);
char *c = current;
while ((unsigned char) *c == ' ') ++c;
// Calculate average apparent power from current and create C-string
_power = (int) (average * mainsVoltage);
char power[6];
snprintf(power, 6, "%d", _power);
// Calculate average current (removing max and min values)
_emonCurrent = (sum - max - min) / (measurements - 2);
_emonPower = (int) (_emonCurrent * voltage);
_emonReady = true;
// Calculate energy increment (ppower times time) and create C-string
double energy_inc = (double) _power * EMON_INTERVAL * EMON_MEASUREMENTS / 1000.0 / 3600.0;
char energy_buf[11];
dtostrf(energy_inc, 10, 3, energy_buf);
char *e = energy_buf;
while ((unsigned char) *e == ' ') ++e;
// Calculate energy increment (ppower times time)
double energy_delta = (double) _emonPower * EMON_INTERVAL * EMON_MEASUREMENTS / 1000.0 / 3600.0;
// Report values to MQTT broker // Report values to MQTT broker
mqttSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), power);
mqttSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), c);
mqttSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), e);
mqttSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), String(_emonPower).c_str());
mqttSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), String(_emonCurrent, 3).c_str());
mqttSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
// Report values to Domoticz // Report values to Domoticz
#if ENABLE_DOMOTICZ #if ENABLE_DOMOTICZ
{ {
char buffer[20]; char buffer[20];
snprintf(buffer, 20, "%s;%s", power, e);
snprintf(buffer, 20, "%d;%s", _emonPower, String(energy_delta, 3).c_str());
domoticzSend("dczPowIdx", 0, buffer); domoticzSend("dczPowIdx", 0, buffer);
snprintf(buffer, 20, "%s", e);
snprintf(buffer, 20, "%s", String(energy_delta, 3).c_str());
domoticzSend("dczEnergyIdx", 0, buffer); domoticzSend("dczEnergyIdx", 0, buffer);
snprintf(buffer, 20, "%s", c);
snprintf(buffer, 20, "%s", String(_emonCurrent, 3).c_str());
domoticzSend("dczCurrentIdx", 0, buffer); domoticzSend("dczCurrentIdx", 0, buffer);
} }
#endif #endif
#if ENABLE_INFLUXDB #if ENABLE_INFLUXDB
influxDBSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), power);
//influxDBSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), c);
//influxDBSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), e);
influxDBSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), _emonPower);
influxDBSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), String(_emonCurrent, 3).c_str());
influxDBSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
#endif #endif
// Reset counters // Reset counters


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

@ -250,6 +250,9 @@ void setup() {
#if ENABLE_EMON #if ENABLE_EMON
powerMonitorSetup(); powerMonitorSetup();
#endif #endif
#if ENABLE_DOMOTICZ
domoticzSetup();
#endif
// Prepare configuration for version 2.0 // Prepare configuration for version 2.0
hwUpwardsCompatibility(); hwUpwardsCompatibility();


+ 67
- 47
code/espurna/hlw8012.ino View File

@ -12,10 +12,13 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <HLW8012.h> #include <HLW8012.h>
#include <Hash.h> #include <Hash.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <EEPROM.h>
HLW8012 hlw8012; HLW8012 hlw8012;
bool _hlw8012Enabled = false; bool _hlw8012Enabled = false;
bool _hlwReady = false;
int _hlwPower = 0;
double _hlwCurrent = 0;
int _hlwVoltage = 0;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// POW // POW
@ -92,6 +95,8 @@ void hlw8012Reset() {
hlw8012SaveCalibration(); hlw8012SaveCalibration();
} }
// -----------------------------------------------------------------------------
// HAL
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
unsigned int getActivePower() { unsigned int getActivePower() {
@ -154,13 +159,25 @@ void hlw8012Setup() {
// API definitions // API definitions
apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) { apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) {
snprintf(buffer, len, "%d", getActivePower());
if (_hlwReady) {
snprintf(buffer, len, "%d", _hlwPower);
} else {
buffer = NULL;
}
}); });
apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) { apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) {
dtostrf(getCurrent(), len-1, 3, buffer);
if (_hlwReady) {
dtostrf(_hlwCurrent, len-1, 3, buffer);
} else {
buffer = NULL;
}
}); });
apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) { apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) {
snprintf(buffer, len, "%d", getVoltage());
if (_hlwReady) {
snprintf(buffer, len, "%d", _hlwVoltage);
} else {
buffer = NULL;
}
}); });
} }
@ -202,9 +219,6 @@ void hlw8012Loop() {
unsigned int power = getActivePower(); unsigned int power = getActivePower();
unsigned int voltage = getVoltage(); unsigned int voltage = getVoltage();
double current = getCurrent(); double current = getCurrent();
unsigned int apparent = getApparentPower();
double factor = getPowerFactor();
unsigned int reactive = getReactivePower();
if (power > 0) { if (power > 0) {
power_spike = (power_previous == 0); power_spike = (power_previous == 0);
@ -230,43 +244,49 @@ void hlw8012Loop() {
} }
voltage_previous = voltage; voltage_previous = voltage;
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
if (wsConnected()) {
unsigned int apparent = getApparentPower();
double factor = getPowerFactor();
unsigned int reactive = getReactivePower();
root["powVisible"] = 1;
root["powActivePower"] = power;
root["powCurrent"] = String(current, 3);
root["powVoltage"] = voltage;
root["powApparentPower"] = apparent;
root["powReactivePower"] = reactive;
root["powPowerFactor"] = String(factor, 2);
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
String output;
root.printTo(output);
wsSend(output.c_str());
root["powVisible"] = 1;
root["powActivePower"] = power;
root["powCurrent"] = String(current, 3);
root["powVoltage"] = voltage;
root["powApparentPower"] = apparent;
root["powReactivePower"] = reactive;
root["powPowerFactor"] = String(factor, 2);
String output;
root.printTo(output);
wsSend(output.c_str());
}
if (--report_count == 0) { if (--report_count == 0) {
power = power_sum / HLW8012_REPORT_EVERY;
current = current_sum / HLW8012_REPORT_EVERY;
voltage = voltage_sum / HLW8012_REPORT_EVERY;
apparent = current * voltage;
reactive = (apparent > power) ? sqrt(apparent * apparent - power * power) : 0;
factor = (apparent > 0) ? (double) power / apparent : 1;
if (factor > 1) factor = 1;
// Update globals
_hlwPower = power_sum / HLW8012_REPORT_EVERY;
_hlwCurrent = current_sum / HLW8012_REPORT_EVERY;
_hlwVoltage = voltage_sum / HLW8012_REPORT_EVERY;
_hlwReady = true;
// Calculate energy increment (ppower times time) and create C-string
double energy_inc = (double) power * HLW8012_REPORT_EVERY * HLW8012_UPDATE_INTERVAL / 1000.0 / 3600.0;
char energy_buf[11];
dtostrf(energy_inc, 11, 3, energy_buf);
char *e = energy_buf;
while ((unsigned char) *e == ' ') ++e;
// Calculate subproducts (apparent and reactive power, power factor and delta energy)
unsigned int apparent = _hlwCurrent * _hlwVoltage;
unsigned int reactive = (apparent > _hlwPower) ? sqrt(apparent * apparent - _hlwPower * _hlwPower) : 0;
double factor = (apparent > 0) ? (double) _hlwPower / apparent : 1;
if (factor > 1) factor = 1;
double energy_delta = (double) _hlwPower * HLW8012_REPORT_EVERY * HLW8012_UPDATE_INTERVAL / 1000.0 / 3600.0;
// Report values to MQTT broker // Report values to MQTT broker
mqttSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(power).c_str());
mqttSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), e);
mqttSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(current, 3).c_str());
mqttSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(voltage).c_str());
mqttSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
mqttSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
mqttSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
mqttSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
mqttSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str()); mqttSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
mqttSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str()); mqttSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
mqttSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str()); mqttSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
@ -275,25 +295,25 @@ void hlw8012Loop() {
#if ENABLE_DOMOTICZ #if ENABLE_DOMOTICZ
{ {
char buffer[20]; char buffer[20];
snprintf(buffer, 20, "%d;%s", power, e);
snprintf(buffer, 20, "%d;%s", _hlwPower, String(energy_delta, 3).c_str());
domoticzSend("dczPowIdx", 0, buffer); domoticzSend("dczPowIdx", 0, buffer);
snprintf(buffer, 20, "%s", e);
snprintf(buffer, 20, "%s", String(energy_delta, 3).c_str());
domoticzSend("dczEnergyIdx", 0, buffer); domoticzSend("dczEnergyIdx", 0, buffer);
snprintf(buffer, 20, "%d", voltage);
snprintf(buffer, 20, "%d", _hlwVoltage);
domoticzSend("dczVoltIdx", 0, buffer); domoticzSend("dczVoltIdx", 0, buffer);
snprintf(buffer, 20, "%s", String(current).c_str());
snprintf(buffer, 20, "%s", String(_hlwCurrent).c_str());
domoticzSend("dczCurrentIdx", 0, buffer); domoticzSend("dczCurrentIdx", 0, buffer);
} }
#endif #endif
#if ENABLE_INFLUXDB #if ENABLE_INFLUXDB
influxDBSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(power).c_str());
//influxDBSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), e);
//influxDBSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(current, 3).c_str());
//influxDBSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(voltage).c_str());
//influxDBSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
//influxDBSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
//influxDBSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
influxDBSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
influxDBSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
influxDBSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
influxDBSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
influxDBSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
influxDBSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
influxDBSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
#endif #endif
// Reset counters // Reset counters


+ 38
- 4
code/espurna/mqtt.ino View File

@ -81,11 +81,12 @@ String mqttSubtopic(char * topic) {
void mqttSendRaw(const char * topic, const char * message) { void mqttSendRaw(const char * topic, const char * message) {
if (mqtt.connected()) { if (mqtt.connected()) {
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#if MQTT_USE_ASYNC #if MQTT_USE_ASYNC
mqtt.publish(topic, MQTT_QOS, MQTT_RETAIN, message);
unsigned int packetId = mqtt.publish(topic, MQTT_QOS, MQTT_RETAIN, message);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s (PID %d)\n"), topic, message, packetId);
#else #else
mqtt.publish(topic, message, MQTT_RETAIN); mqtt.publish(topic, message, MQTT_RETAIN);
DEBUG_MSG_P(PSTR("[MQTT] Sending %s => %s\n"), topic, message);
#endif #endif
} }
} }
@ -149,8 +150,13 @@ void mqttSend(const char * topic, unsigned int index, const char * message) {
void mqttSubscribeRaw(const char * topic) { void mqttSubscribeRaw(const char * topic) {
if (mqtt.connected() && (strlen(topic) > 0)) { if (mqtt.connected() && (strlen(topic) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s\n"), topic);
mqtt.subscribe(topic, MQTT_QOS);
#if MQTT_USE_ASYNC
unsigned int packetId = mqtt.subscribe(topic, MQTT_QOS);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s (PID %d)\n"), topic, packetId);
#else
mqtt.subscribe(topic, MQTT_QOS);
DEBUG_MSG_P(PSTR("[MQTT] Subscribing to %s\n"), topic);
#endif
} }
} }
@ -205,6 +211,8 @@ void _mqttOnDisconnect() {
void _mqttOnMessage(char* topic, char* payload, unsigned int len) { void _mqttOnMessage(char* topic, char* payload, unsigned int len) {
if (len == 0) return;
char message[len + 1]; char message[len + 1];
strlcpy(message, (char *) payload, len + 1); strlcpy(message, (char *) payload, len + 1);
@ -318,11 +326,37 @@ void mqttSetup() {
_mqttOnConnect(); _mqttOnConnect();
}); });
mqtt.onDisconnect([](AsyncMqttClientDisconnectReason reason) { mqtt.onDisconnect([](AsyncMqttClientDisconnectReason reason) {
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
DEBUG_MSG_P(PSTR("[MQTT] TCP Disconnected\n"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
DEBUG_MSG_P(PSTR("[MQTT] Identifier Rejected\n"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
DEBUG_MSG_P(PSTR("[MQTT] Server unavailable\n"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
DEBUG_MSG_P(PSTR("[MQTT] Malformed credentials\n"));
}
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(); _mqttOnDisconnect();
}); });
mqtt.onMessage([](char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) { mqtt.onMessage([](char* topic, char* payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
_mqttOnMessage(topic, payload, len); _mqttOnMessage(topic, payload, len);
}); });
mqtt.onSubscribe([](uint16_t packetId, uint8_t qos) {
DEBUG_MSG_P(PSTR("[MQTT] Subscribe ACK for PID %d\n"), packetId);
});
mqtt.onPublish([](uint16_t packetId) {
DEBUG_MSG_P(PSTR("[MQTT] Publish ACK for PID %d\n"), packetId);
});
#else #else
mqtt.setCallback([](char* topic, byte* payload, unsigned int length) { mqtt.setCallback([](char* topic, byte* payload, unsigned int length) {
_mqttOnMessage(topic, (char *) payload, length); _mqttOnMessage(topic, (char *) payload, length);


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

@ -35,7 +35,7 @@ void otaSetup() {
}); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 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) { ArduinoOTA.onError([](ota_error_t error) {


+ 54
- 108
code/espurna/relay.ino View File

@ -16,6 +16,8 @@ typedef struct {
unsigned char pin; unsigned char pin;
bool reverse; bool reverse;
unsigned char led; unsigned char led;
unsigned long delay_on;
unsigned long delay_off;
unsigned int floodWindowStart; unsigned int floodWindowStart;
unsigned char floodWindowChanges; unsigned char floodWindowChanges;
bool scheduled; bool scheduled;
@ -90,22 +92,6 @@ bool relayProviderStatus(unsigned char id) {
// RELAY // RELAY
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
String relayString() {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) {
relay.add(relayStatus(i));
}
String output;
root.printTo(output);
return output;
}
bool relayStatus(unsigned char id) {
return relayProviderStatus(id);
}
void relayPulse(unsigned char id) { void relayPulse(unsigned char id) {
byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt(); byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
@ -169,24 +155,36 @@ bool relayStatus(unsigned char id, bool status, bool report) {
if (relayStatus(id) != status) { if (relayStatus(id) != status) {
#endif #endif
unsigned int floodWindowEnd = _relays[id].floodWindowStart + 1000 * RELAY_FLOOD_WINDOW;
unsigned int currentTime = millis(); unsigned int currentTime = millis();
unsigned int floodWindowEnd = _relays[id].floodWindowStart + 1000 * RELAY_FLOOD_WINDOW;
unsigned long delay = status ? _relays[id].delay_on : _relays[id].delay_off;
_relays[id].floodWindowChanges++; _relays[id].floodWindowChanges++;
_relays[id].scheduledStatusTime = currentTime;
_relays[id].scheduledStatusTime = currentTime + delay;
// If currentTime is off-limits the floodWindow...
if (currentTime < _relays[id].floodWindowStart || floodWindowEnd <= currentTime) {
if (currentTime >= floodWindowEnd || currentTime < _relays[id].floodWindowStart) {
// We reset the floodWindow
_relays[id].floodWindowStart = currentTime; _relays[id].floodWindowStart = currentTime;
_relays[id].floodWindowChanges = 1; _relays[id].floodWindowChanges = 1;
// If currentTime is in the floodWindow and there have been too many requests...
} else if (_relays[id].floodWindowChanges >= RELAY_FLOOD_CHANGES) { } else if (_relays[id].floodWindowChanges >= RELAY_FLOOD_CHANGES) {
_relays[id].scheduledStatusTime = floodWindowEnd;
// We schedule the changes to the end of the floodWindow
// unless it's already delayed beyond that point
if (floodWindowEnd - delay > currentTime) {
_relays[id].scheduledStatusTime = floodWindowEnd;
}
} }
_relays[id].scheduled = true; _relays[id].scheduled = true;
_relays[id].scheduledStatus = status; _relays[id].scheduledStatus = status;
_relays[id].scheduledReport = (report ? true : _relays[id].scheduledReport);
if (report) _relays[id].scheduledReport = true;
DEBUG_MSG_P(PSTR("[RELAY] Scheduled %d => %s in %u ms\n"),
DEBUG_MSG_P(PSTR("[RELAY] #%d scheduled %s in %u ms\n"),
id, status ? "ON" : "OFF", id, status ? "ON" : "OFF",
(_relays[id].scheduledStatusTime - currentTime)); (_relays[id].scheduledStatusTime - currentTime));
@ -203,6 +201,10 @@ bool relayStatus(unsigned char id, bool status) {
return relayStatus(id, status, true); return relayStatus(id, status, true);
} }
bool relayStatus(unsigned char id) {
return relayProviderStatus(id);
}
void relaySync(unsigned char id) { void relaySync(unsigned char id) {
if (_relays.size() > 1) { if (_relays.size() > 1) {
@ -316,78 +318,17 @@ void relaySetupAPI() {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void relayWS() { void relayWS() {
String output = relayString();
wsSend(output.c_str());
}
//------------------------------------------------------------------------------
// Domoticz
//------------------------------------------------------------------------------
#if ENABLE_DOMOTICZ
void relayDomoticzSend(unsigned int relayID) {
char buffer[15];
sprintf(buffer, "dczRelayIdx%d", relayID);
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
}
int relayFromIdx(unsigned int idx) {
for (int relayID=0; relayID<relayCount(); relayID++) {
if (relayToIdx(relayID) == idx) {
return relayID;
}
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
JsonArray& relay = root.createNestedArray("relayStatus");
for (unsigned char i=0; i<relayCount(); i++) {
relay.add(relayStatus(i));
} }
return -1;
}
int relayToIdx(unsigned int relayID) {
char buffer[15];
sprintf(buffer, "dczRelayIdx%d", relayID);
return getSetting(buffer).toInt();
}
void relayDomoticzSetup() {
mqttRegister([](unsigned int type, const char * topic, const char * payload) {
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribeRaw(dczTopicOut.c_str());
}
if (type == MQTT_MESSAGE_EVENT) {
// Check topic
if (dczTopicOut.equals(topic)) {
// Parse response
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject((char *) payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[DOMOTICZ] Error parsing data\n"));
return;
}
// IDX
unsigned long idx = root["idx"];
int relayID = relayFromIdx(idx);
if (relayID >= 0) {
unsigned long value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %d for IDX %d\n"), value, idx);
relayStatus(relayID, value == 1);
}
}
}
});
String output;
root.printTo(output);
wsSend(output.c_str());
} }
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// MQTT // MQTT
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -397,15 +338,6 @@ void relayMQTT(unsigned char id) {
mqttSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0"); mqttSend(MQTT_TOPIC_RELAY, id, relayStatus(id) ? "1" : "0");
} }
#if ENABLE_INFLUXDB
void relayInfluxDB(unsigned char id) {
if (id >= _relays.size()) return;
char buffer[10];
sprintf(buffer, "%s,id=%d", MQTT_TOPIC_RELAY, id);
influxDBSend(buffer, relayStatus(id) ? "1" : "0");
}
#endif
void relayMQTT() { void relayMQTT() {
for (unsigned int i=0; i < _relays.size(); i++) { for (unsigned int i=0; i < _relays.size(); i++) {
relayMQTT(i); relayMQTT(i);
@ -463,6 +395,19 @@ void relaySetupMQTT() {
mqttRegister(relayMQTTCallback); mqttRegister(relayMQTTCallback);
} }
//------------------------------------------------------------------------------
// InfluxDB
//------------------------------------------------------------------------------
#if ENABLE_INFLUXDB
void relayInfluxDB(unsigned char id) {
if (id >= _relays.size()) return;
char buffer[10];
sprintf(buffer, "%s,id=%d", MQTT_TOPIC_RELAY, id);
influxDBSend(buffer, relayStatus(id) ? "1" : "0");
}
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Setup // Setup
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -481,16 +426,16 @@ void relaySetup() {
#else #else
#ifdef RELAY1_PIN #ifdef RELAY1_PIN
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_PIN_INVERSE, RELAY1_LED });
_relays.push_back((relay_t) { RELAY1_PIN, RELAY1_PIN_INVERSE, RELAY1_LED, RELAY1_DELAY_ON, RELAY1_DELAY_OFF });
#endif #endif
#ifdef RELAY2_PIN #ifdef RELAY2_PIN
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_PIN_INVERSE, RELAY2_LED });
_relays.push_back((relay_t) { RELAY2_PIN, RELAY2_PIN_INVERSE, RELAY2_LED, RELAY2_DELAY_ON, RELAY2_DELAY_OFF });
#endif #endif
#ifdef RELAY3_PIN #ifdef RELAY3_PIN
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_PIN_INVERSE, RELAY3_LED });
_relays.push_back((relay_t) { RELAY3_PIN, RELAY3_PIN_INVERSE, RELAY3_LED, RELAY3_DELAY_ON, RELAY3_DELAY_OFF });
#endif #endif
#ifdef RELAY4_PIN #ifdef RELAY4_PIN
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_PIN_INVERSE, RELAY4_LED });
_relays.push_back((relay_t) { RELAY4_PIN, RELAY4_PIN_INVERSE, RELAY4_LED, RELAY4_DELAY_ON, RELAY4_DELAY_OFF });
#endif #endif
#endif #endif
@ -507,9 +452,6 @@ void relaySetup() {
relaySetupAPI(); relaySetupAPI();
relaySetupMQTT(); relaySetupMQTT();
#if ENABLE_DOMOTICZ
relayDomoticzSetup();
#endif
DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size()); DEBUG_MSG_P(PSTR("[RELAY] Number of relays: %d\n"), _relays.size());
@ -530,17 +472,21 @@ void relayLoop(void) {
if (_relays[id].scheduled && currentTime >= _relays[id].scheduledStatusTime) { if (_relays[id].scheduled && currentTime >= _relays[id].scheduledStatusTime) {
#endif #endif
DEBUG_MSG_P(PSTR("[RELAY] %d => %s\n"), id, status ? "ON" : "OFF");
DEBUG_MSG_P(PSTR("[RELAY] #%d set to %s\n"), id, status ? "ON" : "OFF");
// Call the provider to perform the action
relayProviderStatus(id, status); relayProviderStatus(id, status);
// Change the binded LED if any
if (_relays[id].led > 0) { if (_relays[id].led > 0) {
ledStatus(_relays[id].led - 1, status); ledStatus(_relays[id].led - 1, status);
} }
// Send MQTT report if requested
if (_relays[id].scheduledReport) { if (_relays[id].scheduledReport) {
relayMQTT(id); relayMQTT(id);
} }
if (!recursive) { if (!recursive) {
relayPulse(id); relayPulse(id);
relaySync(id); relaySync(id);


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

@ -94,6 +94,12 @@ void settingsSetup() {
e->response(Embedis::OK); e->response(Embedis::OK);
}); });
Embedis::command( F("RESTART"), [](Embedis* e) {
e->response(Embedis::OK);
customReset(CUSTOM_RESET_TERMINAL);
ESP.restart();
});
Embedis::command( F("RESET"), [](Embedis* e) { Embedis::command( F("RESET"), [](Embedis* e) {
e->response(Embedis::OK); e->response(Embedis::OK);
customReset(CUSTOM_RESET_TERMINAL); customReset(CUSTOM_RESET_TERMINAL);


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


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

@ -0,0 +1,13 @@
/*
UTILS MODULE
Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
char * ltrim(char * s) {
char *p = s;
while ((unsigned char) *p == ' ') ++p;
return p;
}

+ 21
- 9
code/espurna/web.ino View File

@ -43,6 +43,10 @@ char _last_modified[50];
// WEBSOCKETS // WEBSOCKETS
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool wsConnected() {
return (ws.count() > 0);
}
bool wsSend(const char * payload) { bool wsSend(const char * payload) {
if (ws.count() > 0) { if (ws.count() > 0) {
ws.textAll(payload); ws.textAll(payload);
@ -555,8 +559,9 @@ void _wsStart(uint32_t client_id) {
#if ENABLE_EMON #if ENABLE_EMON
root["emonVisible"] = 1; root["emonVisible"] = 1;
root["emonPower"] = getPower();
root["emonMains"] = getSetting("emonMains", EMON_MAINS_VOLTAGE);
root["emonApparentPower"] = getApparentPower();
root["emonCurrent"] = getCurrent();
root["emonVoltage"] = getVoltage();
root["emonRatio"] = getSetting("emonRatio", EMON_CURRENT_RATIO); root["emonRatio"] = getSetting("emonRatio", EMON_CURRENT_RATIO);
#endif #endif
@ -723,13 +728,13 @@ bool _asJson(AsyncWebServerRequest *request) {
ArRequestHandlerFunction _bindAPI(unsigned int apiID) { ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
return [apiID](AsyncWebServerRequest *request) { return [apiID](AsyncWebServerRequest *request) {
webLogRequest(request);
webLogRequest(request);
if (!_authAPI(request)) return; if (!_authAPI(request)) return;
bool asJson = _asJson(request);
web_api_t api = _apis[apiID]; web_api_t api = _apis[apiID];
// Check if its a PUT
if (api.putFn != NULL) { if (api.putFn != NULL) {
if (request->hasParam("value", request->method() == HTTP_PUT)) { if (request->hasParam("value", request->method() == HTTP_PUT)) {
AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT); AsyncWebParameter* p = request->getParam("value", request->method() == HTTP_PUT);
@ -737,14 +742,21 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
} }
} }
// Get response from callback
char value[10]; char value[10];
(api.getFn)(value, 10); (api.getFn)(value, 10);
char *p = ltrim(value);
// jump over leading spaces
char *p = value;
while ((unsigned char) *p == ' ') ++p;
// The response will be a 404 NOT FOUND if the resource is not available
if (*value == NULL) {
DEBUG_MSG_P(PSTR("[API] Sending 404 response\n"));
request->send(404);
return;
}
DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), p);
if (asJson) {
// Format response according to the Accept header
if (_asJson(request)) {
char buffer[64]; char buffer[64];
sprintf_P(buffer, PSTR("{ \"%s\": %s }"), api.key, p); sprintf_P(buffer, PSTR("{ \"%s\": %s }"), api.key, p);
request->send(200, "application/json", buffer); request->send(200, "application/json", buffer);


+ 6
- 2
code/html/custom.js View File

@ -506,7 +506,9 @@ function processData(data) {
} else if (element.attr('type') == 'radio') { } else if (element.attr('type') == 'radio') {
element.val([data[key]]); element.val([data[key]]);
} else { } else {
element.val(data[key]);
var pre = element.attr("pre") || "";
var post = element.attr("post") || "";
element.val(pre + data[key] + post);
} }
return; return;
} }
@ -514,7 +516,9 @@ function processData(data) {
// Look for SPANs // Look for SPANs
var element = $("span[name=" + key + "]"); var element = $("span[name=" + key + "]");
if (element.length > 0) { if (element.length > 0) {
element.html(data[key]);
var pre = element.attr("pre") || "";
var post = element.attr("post") || "";
element.html(pre + data[key] + post);
return; return;
} }


+ 25
- 15
code/html/index.html View File

@ -45,7 +45,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass1">Admin password</label> <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-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint"> <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 /> 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 +54,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Admin password (repeat)</label> <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> </div>
<button class="pure-button button-update-password">Update</button> <button class="pure-button button-update-password">Update</button>
@ -183,12 +183,22 @@
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="dhtHum" readonly /> <input class="pure-u-1 pure-u-sm-3-4" type="text" name="dhtHum" readonly />
</div> </div>
<div class="pure-g module module-emon">
<label class="pure-u-1 pure-u-sm-1-4" for="emonApparentPower">Apparent Power (VA)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="emonApparentPower" readonly />
</div>
<div class="pure-g module module-emon">
<label class="pure-u-1 pure-u-sm-1-4" for="emonCurrent">Current (A)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="emonCurrent" readonly />
</div>
<div class="pure-g module module-pow"> <div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powActivePower">Active Power (W)</label> <label class="pure-u-1 pure-u-sm-1-4" for="powActivePower">Active Power (W)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powActivePower" readonly /> <input class="pure-u-1 pure-u-sm-3-4" type="text" name="powActivePower" readonly />
</div> </div>
<div class="pure-g module module-pow module-emon">
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powApparentPower">Apparent Power (VA)</label> <label class="pure-u-1 pure-u-sm-1-4" for="powApparentPower">Apparent Power (VA)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powApparentPower" readonly /> <input class="pure-u-1 pure-u-sm-3-4" type="text" name="powApparentPower" readonly />
</div> </div>
@ -370,7 +380,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass1">Admin password</label> <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-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint"> <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 /> 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 />
@ -379,7 +389,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass2">Admin password (repeat)</label> <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>
<div class="pure-g"> <div class="pure-g">
@ -466,12 +476,12 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttUser">MQTT User</label> <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>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="mqttPassword">MQTT Password</label> <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>
<div class="pure-g"> <div class="pure-g">
@ -651,12 +661,12 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="idbUsername">Username</label> <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>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="idbPassword">Password</label> <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> </div>
</fieldset> </fieldset>
@ -767,7 +777,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-md-1-6 pure-u-1-4" for="ssid">Network SSID</label> <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="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"> <div class="more">
@ -775,27 +785,27 @@
<div class="break"></div> <div class="break"></div>
<label class="pure-u-md-1-6 pure-u-1-4" for="pass">Password</label> <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> <div class="break"></div>
<label class="pure-u-md-1-6 pure-u-1-4" for="ip">Static IP</label> <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-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> <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> <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-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> <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> <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-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> <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> <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-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> <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>


+ 7
- 6
code/platformio.ini View File

@ -12,9 +12,9 @@ lib_deps =
Adafruit Unified Sensor Adafruit Unified Sensor
https://github.com/xoseperez/Time https://github.com/xoseperez/Time
ArduinoJson ArduinoJson
https://github.com/me-no-dev/ESPAsyncTCP#36b6b5a
https://github.com/me-no-dev/ESPAsyncWebServer#bab5457
https://github.com/marvinroger/async-mqtt-client#f1b4576
https://github.com/me-no-dev/ESPAsyncTCP#991f855
https://github.com/me-no-dev/ESPAsyncWebServer#a94265d
https://github.com/marvinroger/async-mqtt-client#v0.8.1
PubSubClient PubSubClient
Embedis Embedis
NtpClientLib NtpClientLib
@ -90,7 +90,6 @@ build_flags = ${common.build_flags} -DESPURNA_H
platform = espressif8266 platform = espressif8266
framework = arduino framework = arduino
board = esp12e board = esp12e
board_flash_mode = dout
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DESPURNA_H build_flags = ${common.build_flags} -DESPURNA_H
@ -183,6 +182,7 @@ upload_flags = --auth=fibonacci --port 8266
platform = espressif8266 platform = espressif8266
framework = arduino framework = arduino
board = esp8285 board = esp8285
board_flash_mode = dout
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DSONOFF_4CH build_flags = ${common.build_flags_1m} -DSONOFF_4CH
@ -191,6 +191,7 @@ build_flags = ${common.build_flags_1m} -DSONOFF_4CH
platform = espressif8266 platform = espressif8266
framework = arduino framework = arduino
board = esp8285 board = esp8285
board_flash_mode = dout
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DSONOFF_4CH build_flags = ${common.build_flags_1m} -DSONOFF_4CH
@ -201,7 +202,7 @@ upload_flags = --auth=fibonacci --port 8266
[env:sonoff-touch-debug] [env:sonoff-touch-debug]
platform = espressif8266 platform = espressif8266
framework = arduino framework = arduino
board = esp01_1m
board = esp8285
board_flash_mode = dout board_flash_mode = dout
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}
@ -210,7 +211,7 @@ build_flags = ${common.build_flags_1m} -DSONOFF_TOUCH
[env:sonoff-touch-debug-ota] [env:sonoff-touch-debug-ota]
platform = espressif8266 platform = espressif8266
framework = arduino framework = arduino
board = esp01_1m
board = esp8285
board_flash_mode = dout board_flash_mode = dout
lib_deps = ${common.lib_deps} lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore} lib_ignore = ${common.lib_ignore}


Loading…
Cancel
Save