diff --git a/code/espurna/config/all.h b/code/espurna/config/all.h index ddbefc6d..1bbc90a9 100644 --- a/code/espurna/config/all.h +++ b/code/espurna/config/all.h @@ -1,5 +1,6 @@ #include "version.h" #include "arduino.h" +#include "prototypes.h" #include "debug.h" #include "general.h" #include "hardware.h" diff --git a/code/espurna/config/arduino.h b/code/espurna/config/arduino.h index 340029f6..489c42b8 100644 --- a/code/espurna/config/arduino.h +++ b/code/espurna/config/arduino.h @@ -30,14 +30,14 @@ //#define ESPURNA //-------------------------------------------------------------------------------- -// Features (values below are default values) +// Features (values below are non-default values) //-------------------------------------------------------------------------------- -//#define ENABLE_DHT 0 -//#define ENABLE_DS18B20 0 -//#define ENABLE_EMON 0 -//#define ENABLE_HLW8018 0 -//#define ENABLE_RF 0 -//#define ENABLE_FAUXMO 1 -//#define ENABLE_NOFUSS 0 -//#define ENABLE_DOMOTICZ 1 +//#define ENABLE_DHT 1 +//#define ENABLE_DS18B20 1 +//#define ENABLE_EMON 1 +//#define ENABLE_HLW8018 1 +//#define ENABLE_RF 1 +//#define ENABLE_FAUXMO 0 +//#define ENABLE_NOFUSS 1 +//#define ENABLE_DOMOTICZ 0 diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index 88fb2ad6..ebea1ec5 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -80,7 +80,7 @@ #define MQTT_MESSAGE_EVENT 2 // Custom get and set postfixes -// Use something like "/status" or "/set", with trailing slash +// Use something like "/status" or "/set", with leading slash #define MQTT_USE_GETTER "" #define MQTT_USE_SETTER "" diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h new file mode 100644 index 00000000..2c4fe604 --- /dev/null +++ b/code/espurna/config/prototypes.h @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include +#include + +typedef std::function apiGetCallbackFunction; +typedef std::function apiPutCallbackFunction; +void apiRegister(const char * url, const char * key, apiGetCallbackFunction getFn, apiPutCallbackFunction putFn = NULL); +void mqttRegister(void (*callback)(unsigned int, const char *, const char *)); +template bool setSetting(const String& key, T value); +template String getSetting(const String& key, T defaultValue); +template void domoticzSend(const char * key, T value); diff --git a/code/espurna/config/sensors.h b/code/espurna/config/sensors.h index d6cbcd80..fa69ee5a 100644 --- a/code/espurna/config/sensors.h +++ b/code/espurna/config/sensors.h @@ -14,7 +14,7 @@ //-------------------------------------------------------------------------------- #define DHT_PIN 14 -#define DHT_UPDATE_INTERVAL 300000 +#define DHT_UPDATE_INTERVAL 60000 #define DHT_TYPE DHT22 #define DHT_TIMING 11 #define DHT_TEMPERATURE_TOPIC "/temperature" diff --git a/code/espurna/data/index.html.gz b/code/espurna/data/index.html.gz index 116a554a..4e662552 100644 Binary files a/code/espurna/data/index.html.gz and b/code/espurna/data/index.html.gz differ diff --git a/code/espurna/data/script.js.gz b/code/espurna/data/script.js.gz index 168524e0..30e476c9 100644 Binary files a/code/espurna/data/script.js.gz and b/code/espurna/data/script.js.gz differ diff --git a/code/espurna/dht.ino b/code/espurna/dht.ino index 21e8f1f1..61ba98f5 100644 --- a/code/espurna/dht.ino +++ b/code/espurna/dht.ino @@ -13,29 +13,33 @@ Copyright (C) 2016-2017 by Xose Pérez DHT dht(DHT_PIN, DHT_TYPE, DHT_TIMING); -char dhtTemperature[6]; -char dhtHumidity[6]; +double _dhtTemperature = 0; +unsigned int _dhtHumidity = 0; // ----------------------------------------------------------------------------- -// DHT +// Values // ----------------------------------------------------------------------------- -char * getDHTTemperature() { - return dhtTemperature; +double getDHTTemperature() { + return _dhtTemperature; } -char * getDHTHumidity() { - return dhtHumidity; +unsigned int getDHTHumidity() { + return _dhtHumidity; } void dhtSetup() { dht.begin(); + apiRegister("/api/temperature", "temperature", [](char * buffer, size_t len) { + dtostrf(_dhtTemperature, len-1, 1, buffer); + }); + apiRegister("/api/humidity", "humidity", [](char * buffer, size_t len) { + snprintf(buffer, len, "%d", _dhtHumidity); + }); } void dhtLoop() { - if (!mqttConnected()) return; - // Check if we should read new data static unsigned long last_update = 0; if ((millis() - last_update > DHT_UPDATE_INTERVAL) || (last_update == 0)) { @@ -52,19 +56,30 @@ void dhtLoop() { } else { - dtostrf(t, 4, 1, dhtTemperature); - itoa((int) h, dhtHumidity, 10); + _dhtTemperature = t; + _dhtHumidity = h; + + char temperature[6]; + char humidity[6]; + dtostrf(t, 4, 1, temperature); + itoa((unsigned int) h, humidity, 10); - DEBUG_MSG("[DHT] Temperature: %s\n", dhtTemperature); - DEBUG_MSG("[DHT] Humidity: %s\n", dhtHumidity); + DEBUG_MSG("[DHT] Temperature: %s\n", temperature); + DEBUG_MSG("[DHT] Humidity: %s\n", humidity); // Send MQTT messages - mqttSend(getSetting("dhtTmpTopic", DHT_TEMPERATURE_TOPIC).c_str(), dhtTemperature); - mqttSend(getSetting("dhtHumTopic", DHT_HUMIDITY_TOPIC).c_str(), dhtHumidity); + mqttSend(getSetting("dhtTmpTopic", DHT_TEMPERATURE_TOPIC).c_str(), temperature); + mqttSend(getSetting("dhtHumTopic", DHT_HUMIDITY_TOPIC).c_str(), humidity); + + // Send to Domoticz + #if ENABLE_DOMOTICZ + domoticzSend("dczTmpIdx", temperature); + domoticzSend("dczHumIdx", humidity); + #endif // Update websocket clients char buffer[100]; - sprintf_P(buffer, PSTR("{\"dhtVisible\": 1, \"dhtTmp\": %s, \"dhtHum\": %s}"), dhtTemperature, dhtHumidity); + sprintf_P(buffer, PSTR("{\"dhtVisible\": 1, \"dhtTmp\": %s, \"dhtHum\": %s}"), temperature, humidity); wsSend(buffer); } diff --git a/code/espurna/domoticz.ino b/code/espurna/domoticz.ino index 2bbbcf14..607acb8c 100644 --- a/code/espurna/domoticz.ino +++ b/code/espurna/domoticz.ino @@ -8,70 +8,13 @@ Copyright (C) 2016-2017 by Xose Pérez #if ENABLE_DOMOTICZ -#include -#include - -void domoticzMQTTCallback(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("[DOMOTICZ] Error parsing data\n"); - return; - } - - // IDX - unsigned long idx = root["idx"]; - int relayID = domoticzRelay(idx); - if (relayID >= 0) { - unsigned long value = root["nvalue"]; - DEBUG_MSG("[DOMOTICZ] Received value %d for IDX %d\n", value, idx); - relayStatus(relayID, value == 1); - } - - } - - } - -} - -int domoticzIdx(unsigned int relayID) { - return getSetting("dczIdx" + String(relayID)).toInt(); -} - -int domoticzRelay(unsigned int idx) { - for (int relayID=0; relayID void domoticzSend(const char * key, T value) { + unsigned int idx = getSetting(key).toInt(); if (idx > 0) { - unsigned int value = relayStatus(relayID) ? 1 : 0; char payload[45]; - sprintf(payload, "{\"idx\": %d, \"nvalue\": %d, \"svalue\": \"\"}", idx, value); + sprintf(payload, "{\"idx\": %d, \"nvalue\": %s, \"svalue\": \"\"}", idx, String(value).c_str()); mqttSendRaw(getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC).c_str(), payload); } } -void domoticzSetup() { - mqttRegister(domoticzMQTTCallback); -} - #endif diff --git a/code/espurna/ds18b20.ino b/code/espurna/ds18b20.ino index adc95da2..2011530f 100644 --- a/code/espurna/ds18b20.ino +++ b/code/espurna/ds18b20.ino @@ -14,18 +14,21 @@ Copyright (C) 2016-2017 by Xose Pérez OneWire oneWire(DS_PIN); DallasTemperature ds18b20(&oneWire); -char dsTemperature[6]; +double _dsTemperature = 0; // ----------------------------------------------------------------------------- // DS18B20 // ----------------------------------------------------------------------------- -char * getDSTemperature() { - return dsTemperature; +double getDSTemperature() { + return _dsTemperature; } void dsSetup() { ds18b20.begin(); + apiRegister("/api/temperature", "temperature", [](char * buffer, size_t len) { + dtostrf(_dsTemperature, len-1, 1, buffer); + }); } void dsLoop() { @@ -48,16 +51,23 @@ void dsLoop() { } else { - dtostrf(t, 4, 1, dsTemperature); + _dsTemperature = t; - DEBUG_MSG("[DS18B20] Temperature: %s\n", dsTemperature); + char temperature[6]; + dtostrf(t, 5, 1, temperature); + DEBUG_MSG("[DS18B20] Temperature: %s\n", temperature); // Send MQTT messages - mqttSend(getSetting("dsTmpTopic", DS_TEMPERATURE_TOPIC).c_str(), dsTemperature); + mqttSend(getSetting("dsTmpTopic", DS_TEMPERATURE_TOPIC).c_str(), temperature); + + // Send to Domoticz + #if ENABLE_DOMOTICZ + domoticzSend("dczTmpIdx", temperature); + #endif // Update websocket clients char buffer[100]; - sprintf_P(buffer, PSTR("{\"dsVisible\": 1, \"dsTmp\": %s}"), dsTemperature); + sprintf_P(buffer, PSTR("{\"dsVisible\": 1, \"dsTmp\": %s}"), temperature); wsSend(buffer); } diff --git a/code/espurna/emon.ino b/code/espurna/emon.ino index ecf28301..c9abb550 100644 --- a/code/espurna/emon.ino +++ b/code/espurna/emon.ino @@ -11,8 +11,8 @@ Copyright (C) 2016-2017 by Xose Pérez #include EmonLiteESP emon; -double current; -char power[8]; +double _current = 0; +unsigned int _power = 0; // ----------------------------------------------------------------------------- // EMON @@ -22,12 +22,12 @@ void setCurrentRatio(float value) { emon.setCurrentRatio(value); } -char * getPower() { - return power; +unsigned int getPower() { + return _power; } double getCurrent() { - return current; + return _current; } unsigned int currentCallback() { @@ -52,6 +52,11 @@ void powerMonitorSetup() { getSetting("emonRatio", EMON_CURRENT_RATIO).toFloat() ); emon.setPrecision(EMON_CURRENT_PRECISION); + + apiRegister("/api/power", "power", [](char * buffer, size_t len) { + snprintf(buffer, len, "%d", _power); + }); + } void powerMonitorLoop() { @@ -74,38 +79,46 @@ void powerMonitorLoop() { // Safety check: do not read current if relay is OFF if (!relayStatus(0)) { - current = 0; + _current = 0; } else { - current = emon.getCurrent(EMON_SAMPLES); - current -= EMON_CURRENT_OFFSET; - if (current < 0) current = 0; + _current = emon.getCurrent(EMON_SAMPLES); + _current -= EMON_CURRENT_OFFSET; + if (_current < 0) _current = 0; } if (measurements == 0) { - max = min = current; + max = min = _current; } else { - if (current > max) max = current; - if (current < min) min = current; + if (_current > max) max = _current; + if (_current < min) min = _current; } - sum += current; + sum += _current; ++measurements; float mainsVoltage = getSetting("emonMains", EMON_MAINS_VOLTAGE).toFloat(); - //DEBUG_MSG("[ENERGY] Power now: %dW\n", int(current * mainsVoltage)); + //DEBUG_MSG("[ENERGY] Power now: %dW\n", int(_current * mainsVoltage)); // Update websocket clients char text[20]; - sprintf_P(text, PSTR("{\"emonPower\": %d}"), int(current * mainsVoltage)); + sprintf_P(text, PSTR("{\"emonPower\": %d}"), int(_current * mainsVoltage)); wsSend(text); // Send MQTT messages averaged every EMON_MEASUREMENTS if (measurements == EMON_MEASUREMENTS) { - double p = (sum - max - min) * mainsVoltage / (measurements - 2); - sprintf(power, "%d", int(p)); - mqttSend(getSetting("emonPowerTopic", EMON_POWER_TOPIC).c_str(), power); + + _power = (int) ((sum - max - min) * mainsVoltage / (measurements - 2)); sum = 0; measurements = 0; + + char power[6]; + snprintf(power, "%d", 6, _power); + mqttSend(getSetting("emonPowerTopic", EMON_POWER_TOPIC).c_str(), power); + #if ENABLE_DOMOTICZ + domoticzSend("dczPowIdx", power); + #endif + + } next_measurement += EMON_INTERVAL; diff --git a/code/espurna/espurna.ino b/code/espurna/espurna.ino index 3fca7dd0..cafc7f22 100644 --- a/code/espurna/espurna.ino +++ b/code/espurna/espurna.ino @@ -19,20 +19,13 @@ along with this program. If not, see . */ -#include #include "config/all.h" // ----------------------------------------------------------------------------- -// PROTOTYPES +// GLOBALS // ----------------------------------------------------------------------------- -#include -#include -#include - -void mqttRegister(void (*callback)(unsigned int, const char *, const char *)); -template bool setSetting(const String& key, T value); -template String getSetting(const String& key, T defaultValue); +char apibuffer[64]; // ----------------------------------------------------------------------------- // METHODS @@ -114,9 +107,6 @@ void setup() { webSetup(); ntpSetup(); - #if ENABLE_DOMOTICZ - domoticzSetup(); - #endif #if ENABLE_FAUXMO fauxmoSetup(); #endif diff --git a/code/espurna/pow.ino b/code/espurna/pow.ino index 09258c4f..ac2e43b9 100644 --- a/code/espurna/pow.ino +++ b/code/espurna/pow.ino @@ -10,6 +10,8 @@ Copyright (C) 2016-2017 by Xose Pérez #if ENABLE_POW #include +#include +#include HLW8012 hlw8012; bool _powEnabled = false; @@ -141,6 +143,17 @@ void powSetup() { // Retrieve calibration values powRetrieveCalibration(); + // API definitions + apiRegister("/api/power", "power", [](char * buffer, size_t len) { + snprintf(buffer, len, "%d", getActivePower()); + }); + apiRegister("/api/current", "current", [](char * buffer, size_t len) { + dtostrf(getCurrent(), len-1, 2, buffer); + }); + apiRegister("/api/voltage", "voltage", [](char * buffer, size_t len) { + snprintf(buffer, len, "%d", getVoltage()); + }); + } void powLoop() { @@ -211,6 +224,10 @@ void powLoop() { mqttSend(getSetting("powRPowerTopic", POW_RPOWER_TOPIC).c_str(), String(reactive).c_str()); mqttSend(getSetting("powPFactorTopic", POW_PFACTOR_TOPIC).c_str(), String(factor).c_str()); + #if ENABLE_DOMOTICZ + domoticzSend("dczPowIdx", power); + #endif + power_sum = current_sum = voltage_sum = 0; report_count = POW_REPORT_EVERY; diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index 3a2c6b5f..a5c0e892 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -9,35 +9,24 @@ Copyright (C) 2016-2017 by Xose Pérez #include #include #include +#include typedef struct { unsigned char pin; bool reverse; } relay_t; std::vector _relays; -bool recursive = false; + #ifdef SONOFF_DUAL unsigned char dualRelayStatus = 0; #endif +bool recursive = false; + // ----------------------------------------------------------------------------- // RELAY // ----------------------------------------------------------------------------- -void relayMQTT(unsigned char id) { - if (id >= _relays.size()) return; - String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER); - char buffer[strlen(MQTT_RELAY_TOPIC) + mqttGetter.length() + 3]; - sprintf(buffer, "%s/%d%s", MQTT_RELAY_TOPIC, id, mqttGetter.c_str()); - mqttSend(buffer, relayStatus(id) ? "1" : "0"); -} - -void relayMQTT() { - for (unsigned int i=0; i < _relays.size(); i++) { - relayMQTT(i); - } -} - String relayString() { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.createObject(); @@ -50,11 +39,6 @@ String relayString() { return output; } -void relayWS() { - String output = relayString(); - wsSend(output.c_str()); -} - bool relayStatus(unsigned char id) { #ifdef SONOFF_DUAL if (id >= 2) return false; @@ -91,19 +75,19 @@ bool relayStatus(unsigned char id, bool status, bool report) { digitalWrite(_relays[id].pin, _relays[id].reverse ? !status : status); #endif + if (report) relayMQTT(id); if (!recursive) { relaySync(id); relaySave(); + relayWS(); } #ifdef ENABLE_DOMOTICZ - domoticzSend(id); + relayDomoticzSend(id); #endif } - if (report) relayMQTT(id); - if (!recursive) relayWS(); return changed; } @@ -181,6 +165,134 @@ unsigned char relayCount() { return _relays.size(); } +//------------------------------------------------------------------------------ +// REST API +//------------------------------------------------------------------------------ + +void relaySetupAPI() { + + // API entry points (protected with apikey) + for (unsigned int relayID=0; relayID= 0) { + unsigned long value = root["nvalue"]; + DEBUG_MSG("[DOMOTICZ] Received value %d for IDX %d\n", value, idx); + relayStatus(relayID, value == 1); + } + + } + + } + + }); +} + +#endif + +//------------------------------------------------------------------------------ +// MQTT +//------------------------------------------------------------------------------ + +void relayMQTT(unsigned char id) { + if (id >= _relays.size()) return; + String mqttGetter = getSetting("mqttGetter", MQTT_USE_GETTER); + char buffer[strlen(MQTT_RELAY_TOPIC) + mqttGetter.length() + 3]; + sprintf(buffer, "%s/%d%s", MQTT_RELAY_TOPIC, id, mqttGetter.c_str()); + mqttSend(buffer, relayStatus(id) ? "1" : "0"); +} + +void relayMQTT() { + for (unsigned int i=0; i < _relays.size(); i++) { + relayMQTT(i); + } +} + void relayMQTTCallback(unsigned int type, const char * topic, const char * payload) { String mqttSetter = getSetting("mqttSetter", MQTT_USE_SETTER); @@ -220,6 +332,14 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo } +void relaySetupMQTT() { + mqttRegister(relayMQTTCallback); +} + +//------------------------------------------------------------------------------ +// Setup +//------------------------------------------------------------------------------ + void relaySetup() { #ifdef SONOFF_DUAL @@ -253,10 +373,13 @@ void relaySetup() { if (relayMode == RELAY_MODE_OFF) relayStatus(i, false); if (relayMode == RELAY_MODE_ON) relayStatus(i, true); } - if (relayMode == RELAY_MODE_SAME) relayRetrieve(); - mqttRegister(relayMQTTCallback); + relaySetupAPI(); + relaySetupMQTT(); + #if ENABLE_DOMOTICZ + relayDomoticzSetup(); + #endif DEBUG_MSG("[RELAY] Number of relays: %d\n", _relays.size()); diff --git a/code/espurna/web.ino b/code/espurna/web.ino index 41d085fb..0c00e78f 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -14,17 +14,25 @@ Copyright (C) 2016-2017 by Xose Pérez #include #include #include +#include AsyncWebServer server(80); AsyncWebSocket ws("/ws"); +Ticker deferred; typedef struct { IPAddress ip; unsigned long timestamp = 0; } ws_ticket_t; - ws_ticket_t _ticket[WS_BUFFER_SIZE]; -Ticker deferred; + +typedef struct { + char * url; + char * key; + apiGetCallbackFunction getFn = NULL; + apiPutCallbackFunction putFn = NULL; +} web_api_t; +std::vector _apis; // ----------------------------------------------------------------------------- // WEBSOCKETS @@ -101,7 +109,7 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) { bool fauxmoEnabled = false; #endif unsigned int network = 0; - unsigned int dczIdx = 0; + unsigned int dczRelayIdx = 0; String adminPass; for (unsigned int i=0; i= relayCount()) continue; - key = key + String(dczIdx); - ++dczIdx; + if (key == "dczRelayIdx") { + if (dczRelayIdx >= relayCount()) continue; + key = key + String(dczRelayIdx); + ++dczRelayIdx; } #else @@ -319,11 +327,28 @@ void _wsStart(uint32_t client_id) { root["dczTopicIn"] = getSetting("dczTopicIn", DOMOTICZ_IN_TOPIC); root["dczTopicOut"] = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC); - JsonArray& dczIdx = root.createNestedArray("dczIdx"); + JsonArray& dczRelayIdx = root.createNestedArray("dczRelayIdx"); for (byte i=0; imethodToString(), request->url().c_str()); } @@ -464,46 +489,7 @@ bool _authenticate(AsyncWebServerRequest *request) { return request->authenticate(HTTP_USERNAME, httpPassword); } -void _onAuth(AsyncWebServerRequest *request) { - - _logRequest(request); - - if (!_authenticate(request)) return request->requestAuthentication(); - - IPAddress ip = request->client()->remoteIP(); - unsigned long now = millis(); - unsigned short index; - for (index = 0; index < WS_BUFFER_SIZE; index++) { - if (_ticket[index].ip == ip) break; - if (_ticket[index].timestamp == 0) break; - if (now - _ticket[index].timestamp > WS_TIMEOUT) break; - } - if (index == WS_BUFFER_SIZE) { - request->send(423); - } else { - _ticket[index].ip = ip; - _ticket[index].timestamp = now; - request->send(204); - } - -} - -void _onHome(AsyncWebServerRequest *request) { - - _logRequest(request); - - if (!_authenticate(request)) return request->requestAuthentication(); - - String password = getSetting("adminPass", ADMIN_PASS); - if (password.equals(ADMIN_PASS)) { - request->send(SPIFFS, "/password.html"); - } else { - request->send(SPIFFS, "/index.html"); - } - -} - -bool _apiAuth(AsyncWebServerRequest *request) { +bool _authAPI(AsyncWebServerRequest *request) { if (getSetting("apiEnabled").toInt() == 0) { DEBUG_MSG("[WEBSERVER] HTTP API is not enabled\n"); @@ -528,11 +514,68 @@ bool _apiAuth(AsyncWebServerRequest *request) { } -void _onRelay(AsyncWebServerRequest *request) { +ArRequestHandlerFunction _bindAPI(unsigned int apiID) { + + return [apiID](AsyncWebServerRequest *request) { + webLogRequest(request); + + if (!_authAPI(request)) return; + + bool asJson = false; + if (request->hasHeader("Accept")) { + AsyncWebHeader* h = request->getHeader("Accept"); + asJson = h->value().equals("application/json"); + } + + web_api_t api = _apis[apiID]; + if (request->method() == HTTP_PUT) { + if (request->hasParam("value", true)) { + AsyncWebParameter* p = request->getParam("value", true); + (api.putFn)((p->value()).c_str()); + } + } - _logRequest(request); + char value[10]; + (api.getFn)(value, 10); - if (!_apiAuth(request)) return; + // jump over leading spaces + char *p = value; + while ((unsigned char) *p == ' ') ++p; + + if (asJson) { + char buffer[64]; + sprintf_P(buffer, PSTR("{ \"%s\": %s }"), api.key, p); + request->send(200, "application/json", buffer); + } else { + request->send(200, "text/plain", p); + } + + }; + +} + +void apiRegister(const char * url, const char * key, apiGetCallbackFunction getFn, apiPutCallbackFunction putFn) { + + // Store it + web_api_t api; + api.url = strdup(url); + 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; + server.on(url, methods, _bindAPI(_apis.size() - 1)); + +} + +void _onAPIs(AsyncWebServerRequest *request) { + + webLogRequest(request); + + if (!_authAPI(request)) return; bool asJson = false; if (request->hasHeader("Accept")) { @@ -542,55 +585,80 @@ void _onRelay(AsyncWebServerRequest *request) { String output; if (asJson) { - output = relayString(); + 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[i].url + String("\n
"); } request->send(200, "text/plain", output); } -}; +} -ArRequestHandlerFunction _onRelayStatusWrapper(unsigned int relayID) { +void _onHome(AsyncWebServerRequest *request) { - return [relayID](AsyncWebServerRequest *request) { + DEBUG_MSG("[DEBUG] Free heap: %d bytes\n", ESP.getFreeHeap()); - _logRequest(request); + FSInfo fs_info; + if (SPIFFS.info(fs_info)) { + DEBUG_MSG("[DEBUG] File system total size: %d bytes\n", fs_info.totalBytes); + DEBUG_MSG(" used size : %d bytes\n", fs_info.usedBytes); + DEBUG_MSG(" block size: %d bytes\n", fs_info.blockSize); + DEBUG_MSG(" page size : %d bytes\n", fs_info.pageSize); + DEBUG_MSG(" max files : %d\n", fs_info.maxOpenFiles); + DEBUG_MSG(" max length: %d\n", fs_info.maxPathLength); + } else { + DEBUG_MSG("[DEBUG] Error, FS not accesible!\n"); + } - if (!_apiAuth(request)) return; + webLogRequest(request); - if (request->method() == HTTP_PUT) { - if (request->hasParam("status", true)) { - AsyncWebParameter* p = request->getParam("status", true); - unsigned int value = p->value().toInt(); - if (value == 2) { - relayToggle(relayID); - } else { - relayStatus(relayID, value == 1); - } - } - } + if (!_authenticate(request)) return request->requestAuthentication(); - bool asJson = false; - if (request->hasHeader("Accept")) { - AsyncWebHeader* h = request->getHeader("Accept"); - asJson = h->value().equals("application/json"); - } + String password = getSetting("adminPass", ADMIN_PASS); + if (password.equals(ADMIN_PASS)) { + request->send(SPIFFS, "/password.html"); + } else { + request->send(SPIFFS, "/index.html"); + } - String output; - if (asJson) { - output = String("{\"relayStatus\": ") + String(relayStatus(relayID) ? "1" : "0") + "}"; - request->send(200, "application/json", output); - } else { - request->send(200, "text/plain", relayStatus(relayID) ? "1" : "0"); - } +} - }; +void _onAuth(AsyncWebServerRequest *request) { + + webLogRequest(request); + + if (!_authenticate(request)) return request->requestAuthentication(); + + IPAddress ip = request->client()->remoteIP(); + unsigned long now = millis(); + unsigned short index; + for (index = 0; index < WS_BUFFER_SIZE; index++) { + if (_ticket[index].ip == ip) break; + if (_ticket[index].timestamp == 0) break; + if (now - _ticket[index].timestamp > WS_TIMEOUT) break; + } + if (index == WS_BUFFER_SIZE) { + request->send(423); + } else { + _ticket[index].ip = ip; + _ticket[index].timestamp = now; + request->send(204); + } } +AsyncWebServer * getServer() { + return &server; +} + void webSetup() { // Setup websocket @@ -604,14 +672,7 @@ void webSetup() { server.on("/", HTTP_GET, _onHome); server.on("/index.html", HTTP_GET, _onHome); server.on("/auth", HTTP_GET, _onAuth); - - // API entry points (protected with apikey) - for (unsigned int relayID=0; relayID 1) $(".id", line).html(" " + id); line.appendTo("#idxs"); @@ -264,12 +264,12 @@ function processData(data) { } // Domoticz - if (key == "dczIdx") { - var idxs = data.dczIdx; + if (key == "dczRelayIdx") { + var idxs = data.dczRelayIdx; createIdxs(idxs.length); for (var i in idxs) { - var element = $(".dczIdx[data=" + i + "]"); + var element = $(".dczRelayIdx[data=" + i + "]"); if (element.length > 0) element.val(idxs[i]); } diff --git a/code/html/index.html b/code/html/index.html index b28c70e5..4aecb410 100644 --- a/code/html/index.html +++ b/code/html/index.html @@ -265,7 +265,11 @@
 
-
This is the key you will have to pass with every HTTP request to the API, either to get or write values.
+
+ This is the key you will have to pass with every HTTP request to the API, either to get or write values.
+ All API calls must contain the apikey parameter with the value above.
+ To know what APIs are enabled do a call to /apis. +
@@ -327,12 +331,15 @@
 
-
This is the root topic for this device. The {identifier} placeholder will be replaces by the device hostname.
+
+ This is the root topic for this device. The {identifier} placeholder will be replaces by the device hostname.
- <root>/relay/# Send a 0 or a 1 as a payload to this topic to switch it on or off. You can also send a 2 to toggle its current state. Replace # with the relay ID (starting from 0). If the board has only one relay it will be 0.
- <root>/led/# Send a 0 or a 1 as a payload to this topic to set the onboard LED to the given state, send a 3 to turn it back to WIFI indicator. Replace # with the LED ID (starting from 0). If the board has only one LED it will be 0.
+ - <root>/button/# For each button in the board subscribe to this topic to know when it is pressed (payload 1) or released (payload 0).
- <root>/ip The device will report to this topic its IP.
- <root>/version The device will report to this topic its firmware version on boot.
- <root>/status The device will report a 1 to this topic every few minutes. Upon MQTT disconnecting this will be set to 0. +
@@ -363,6 +370,24 @@ +
+ +
+
Set to 0 to disable notifications.
+
+ +
+ +
+
Set to 0 to disable notifications.
+
+ +
+ +
+
Set to 0 to disable notifications.
+
+
@@ -386,21 +411,21 @@
- +
 
In Watts (W). If you are using a pure resistive load like a bulb this will be writen on it, otherwise use a socket multimeter to get this value.
- +
 
In Volts (V). Enter your the nominal AC voltage for your household or facility, or use multimeter to get this value.
- +
 
In Ampers (A). If you are using a pure resistive load like a bulb this will the ratio between the two previous values, i.e. power / voltage. You can also use a current clamp around one fo the power wires to get this value.
@@ -479,8 +504,8 @@
- -
+ +
Set to 0 to disable notifications.