Browse Source

APIs for sensor data, REST & domoticz

fastled
Xose Pérez 7 years ago
parent
commit
4708c26d54
16 changed files with 403 additions and 214 deletions
  1. +1
    -0
      code/espurna/config/all.h
  2. +9
    -9
      code/espurna/config/arduino.h
  3. +14
    -0
      code/espurna/config/prototypes.h
  4. +1
    -1
      code/espurna/config/sensors.h
  5. BIN
      code/espurna/data/index.html.gz
  6. BIN
      code/espurna/data/script.js.gz
  7. +9
    -3
      code/espurna/dht.ino
  8. +3
    -60
      code/espurna/domoticz.ino
  9. +6
    -0
      code/espurna/ds18b20.ino
  10. +6
    -0
      code/espurna/emon.ino
  11. +2
    -12
      code/espurna/espurna.ino
  12. +19
    -0
      code/espurna/pow.ino
  13. +148
    -25
      code/espurna/relay.ino
  14. +149
    -93
      code/espurna/web.ino
  15. +4
    -4
      code/html/custom.js
  16. +32
    -7
      code/html/index.html

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

@ -1,5 +1,6 @@
#include "version.h"
#include "arduino.h"
#include "prototypes.h"
#include "debug.h"
#include "general.h"
#include "hardware.h"


+ 9
- 9
code/espurna/config/arduino.h View File

@ -28,14 +28,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

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

@ -0,0 +1,14 @@
#include <Arduino.h>
#include <functional>
#include <NtpClientLib.h>
#include <ESPAsyncWebServer.h>
#include <AsyncMqttClient.h>
#include <functional>
typedef std::function<char *(void)> apiGetCallbackFunction;
typedef std::function<void(const char *)> 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<typename T> bool setSetting(const String& key, T value);
template<typename T> String getSetting(const String& key, T defaultValue);
template<typename T> void domoticzSend(const char * key, T value);

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

@ -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"


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


BIN
code/espurna/data/script.js.gz View File


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

@ -17,7 +17,7 @@ char dhtTemperature[6];
char dhtHumidity[6];
// -----------------------------------------------------------------------------
// DHT
// Values
// -----------------------------------------------------------------------------
char * getDHTTemperature() {
@ -30,12 +30,12 @@ char * getDHTHumidity() {
void dhtSetup() {
dht.begin();
apiRegister("/api/temperature", "temperature", getDHTTemperature);
apiRegister("/api/humidity", "humidity", getDHTHumidity);
}
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)) {
@ -62,6 +62,12 @@ void dhtLoop() {
mqttSend(getSetting("dhtTmpTopic", DHT_TEMPERATURE_TOPIC).c_str(), dhtTemperature);
mqttSend(getSetting("dhtHumTopic", DHT_HUMIDITY_TOPIC).c_str(), dhtHumidity);
// Send to Domoticz
#if ENABLE_DOMOTICZ
domoticzSend("dczTmpIdx", dhtTemperature);
domoticzSend("dczHumIdx", dhtHumidity);
#endif
// Update websocket clients
char buffer[100];
sprintf_P(buffer, PSTR("{\"dhtVisible\": 1, \"dhtTmp\": %s, \"dhtHum\": %s}"), dhtTemperature, dhtHumidity);


+ 3
- 60
code/espurna/domoticz.ino View File

@ -8,70 +8,13 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#if ENABLE_DOMOTICZ
#include <Hash.h>
#include <ArduinoJson.h>
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<relayCount(); relayID++) {
if (domoticzIdx(relayID) == idx) {
return relayID;
}
}
return -1;
}
void domoticzSend(unsigned int relayID) {
unsigned int idx = domoticzIdx(relayID);
template<typename T> 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

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

@ -26,6 +26,7 @@ char * getDSTemperature() {
void dsSetup() {
ds18b20.begin();
apiRegister("/api/temperature", "temperature", getDSTemperature);
}
void dsLoop() {
@ -55,6 +56,11 @@ void dsLoop() {
// Send MQTT messages
mqttSend(getSetting("dsTmpTopic", DS_TEMPERATURE_TOPIC).c_str(), dsTemperature);
// Send to Domoticz
#if ENABLE_DOMOTICZ
domoticzSend("dczTmpIdx", dsTemperature);
#endif
// Update websocket clients
char buffer[100];
sprintf_P(buffer, PSTR("{\"dsVisible\": 1, \"dsTmp\": %s}"), dsTemperature);


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

@ -52,6 +52,9 @@ void powerMonitorSetup() {
getSetting("emonRatio", EMON_CURRENT_RATIO).toFloat()
);
emon.setPrecision(EMON_CURRENT_PRECISION);
apiRegister("/api/power", "power", getPower);
}
void powerMonitorLoop() {
@ -104,6 +107,9 @@ void powerMonitorLoop() {
double p = (sum - max - min) * mainsVoltage / (measurements - 2);
sprintf(power, "%d", int(p));
mqttSend(getSetting("emonPowerTopic", EMON_POWER_TOPIC).c_str(), power);
#if ENABLE_DOMOTICZ
domoticzSend("dczPowIdx", power);
#endif
sum = 0;
measurements = 0;
}


+ 2
- 12
code/espurna/espurna.ino View File

@ -19,20 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include "config/all.h"
// -----------------------------------------------------------------------------
// PROTOTYPES
// GLOBALS
// -----------------------------------------------------------------------------
#include <NtpClientLib.h>
#include <ESPAsyncWebServer.h>
#include <AsyncMqttClient.h>
void mqttRegister(void (*callback)(unsigned int, const char *, const char *));
template<typename T> bool setSetting(const String& key, T value);
template<typename T> 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


+ 19
- 0
code/espurna/pow.ino View File

@ -10,6 +10,8 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#if ENABLE_POW
#include <HLW8012.h>
#include <Hash.h>
#include <ArduinoJson.h>
#define POW_USE_INTERRUPTS 1
@ -142,6 +144,19 @@ void powSetup() {
powAttachInterrupts();
#endif
apiRegister("/api/power", "power", []() {
sprintf(apibuffer, "%d", getActivePower());
return apibuffer;
});
apiRegister("/api/current", "current", []() {
dtostrf(getCurrent(), 5, 2, apibuffer);
return apibuffer;
});
apiRegister("/api/voltage", "voltage", []() {
sprintf(apibuffer, "%d", getVoltage());
return apibuffer;
});
}
void powLoop() {
@ -200,6 +215,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;


+ 148
- 25
code/espurna/relay.ino View File

@ -9,35 +9,24 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <EEPROM.h>
#include <ArduinoJson.h>
#include <vector>
#include <functional>
typedef struct {
unsigned char pin;
bool reverse;
} relay_t;
std::vector<relay_t> _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<relayCount(); relayID++) {
char url[15];
sprintf(url, "/api/relay/%d", relayID);
char key[10];
sprintf(key, "relay%d", relayID);
apiRegister(url, key,
[relayID]() {
return (char *) (relayStatus(relayID) ? "1" : "0");
},
[relayID](const char * payload) {
unsigned int value = payload[0] - '0';
if (value == 2) {
relayToggle(relayID);
} else {
relayStatus(relayID, value == 1);
}
});
}
}
//------------------------------------------------------------------------------
// WebSockets
//------------------------------------------------------------------------------
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;
}
}
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("[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("[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());


+ 149
- 93
code/espurna/web.ino View File

@ -14,17 +14,25 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <AsyncJson.h>
#include <ArduinoJson.h>
#include <Ticker.h>
#include <vector>
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<web_api_t> _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<config.size(); i++) {
@ -137,10 +145,10 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
#if ENABLE_DOMOTICZ
if (key == "dczIdx") {
if (dczIdx >= 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; i<relayCount(); i++) {
dczIdx.add(domoticzIdx(i));
dczRelayIdx.add(relayToIdx(i));
}
#if ENABLE_DHT
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
root["dczHumIdx"] = getSetting("dczHumIdx").toInt();
#endif
#if ENABLE_DS18B20
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
#endif
#if ENABLE_EMON
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
#endif
#if ENABLE_POW
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
#endif
#endif
#if ENABLE_FAUXMO
@ -453,7 +478,7 @@ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventTy
// WEBSERVER
// -----------------------------------------------------------------------------
void _logRequest(AsyncWebServerRequest *request) {
void webLogRequest(AsyncWebServerRequest *request) {
DEBUG_MSG("[WEBSERVER] Request: %s %s\n", request->methodToString(), 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,63 @@ 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());
}
}
char * value = strdup((api.getFn)());
if (asJson) {
char buffer[64];
sprintf_P(buffer, PSTR("{ \"%s\": %s }"), api.key, value);
request->send(200, "application/json", buffer);
} else {
request->send(200, "text/plain", value);
}
};
}
void apiRegister(const char * url, const char * key, apiGetCallbackFunction getFn, apiPutCallbackFunction putFn) {
_logRequest(request);
// Store it
web_api_t api;
api.url = strdup(url);
api.key = strdup(key);
api.getFn = getFn;
api.putFn = putFn;
_apis.push_back(api);
if (!_apiAuth(request)) return;
// 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 +580,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<relayCount(); i++) {
output += "Relay #" + String(i) + String(": ") + String(relayStatus(i) ? "1" : "0") + "\n";
for (unsigned int i=0; i < _apis.size(); i++) {
output += _apis[i].key + String(" -> ") + _apis[i].url + String("\n<br />");
}
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 +667,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<relayCount(); relayID++) {
char buffer[15];
sprintf(buffer, "/api/relay/%d", relayID);
server.on(buffer, HTTP_GET + HTTP_PUT, _onRelayStatusWrapper(relayID));
}
server.on("/api/relay", HTTP_GET, _onRelay);
server.on("/apis", HTTP_GET, _onAPIs);
// Serve static files
char lastModified[50];


+ 4
- 4
code/html/custom.js View File

@ -128,7 +128,7 @@ function createIdxs(count) {
for (var id=0; id<count; id++) {
var line = $(template).clone();
$(line).find("input").each(function() {
$(this).attr("data", id).attr("tabindex", 43+id);
$(this).attr("data", id).attr("tabindex", 40+id);
});
if (count > 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]);
}


+ 32
- 7
code/html/index.html View File

@ -265,7 +265,11 @@
<input name="apiKey" class="pure-u-3-4 pure-u-md-1-2" type="text" tabindex="13" />
<div class=" pure-u-1-4 pure-u-md-1-4"><button class="pure-button button-apikey pure-u-23-24">Generate</button></div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">This is the key you will have to pass with every HTTP request to the API, either to get or write values.</div>
<div class="pure-u-1 pure-u-md-3-4 hint">
This is the key you will have to pass with every HTTP request to the API, either to get or write values.<br />
All API calls must contain the <strong>apikey</strong> parameter with the value above.<br />
To know what APIs are enabled do a call to <strong>/apis</strong>.
</div>
</div>
</fieldset>
@ -327,12 +331,15 @@
<label class="pure-u-1 pure-u-md-1-4" for="mqttTopic">MQTT Root Topic</label>
<input class="pure-u-1 pure-u-md-3-4" name="mqttTopic" type="text" size="20" tabindex="25" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">This is the root topic for this device. The {identifier} placeholder will be replaces by the device hostname.<br />
<div class="pure-u-1 pure-u-md-3-4 hint">
This is the root topic for this device. The {identifier} placeholder will be replaces by the device hostname.<br />
- <strong>&lt;root&gt;/relay/#</strong> 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.<br />
- <strong>&lt;root&gt;/led/#</strong> 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.<br />
- <strong>&lt;root&gt;/button/#</strong> For each button in the board subscribe to this topic to know when it is pressed (payload 1) or released (payload 0).<br />
- <strong>&lt;root&gt;/ip</strong> The device will report to this topic its IP.<br />
- <strong>&lt;root&gt;/heartbeat</strong> The device will report to this topic every few minutes.<br />
- <strong>&lt;root&gt;/version</strong> The device will report to this topic its firmware version on boot.<br />
</div>
</div>
</fieldset>
@ -363,6 +370,24 @@
<input class="pure-u-1 pure-u-md-3-4" name="dczTopicOut" type="text" tabindex="32" placeholder="domoticz/out" />
</div>
<div class="pure-g modude module-dht module-ds">
<label class="pure-u-1 pure-u-sm-1-4" for="dczTmpIdx">Temperature IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczTmpIdx" type="number" min="0" tabindex="33" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
<div class="pure-g modude module-dht">
<label class="pure-u-1 pure-u-sm-1-4" for="dczHumIdx">Humidity IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczHumIdx" type="number" min="0" tabindex="34" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
<div class="pure-g modude module-pow module-emon">
<label class="pure-u-1 pure-u-sm-1-4" for="dczPowIdx">Power IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczPowIdx" type="number" min="0" tabindex="35" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
<div id="idxs">
</div>
@ -386,21 +411,21 @@
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="powExpectedPower">AC RMS Active Power</label>
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedPower" type="text" size="8" tabindex="41" placeholder="0" />
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedPower" type="text" size="8" tabindex="51" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">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.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="powExpectedVoltage">AC RMS Voltage</label>
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedVoltage" type="text" size="8" tabindex="41" placeholder="0" />
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedVoltage" type="text" size="8" tabindex="52" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In Volts (V). Enter your the nominal AC voltage for your household or facility, or use multimeter to get this value.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="powExpectedCurrent">AC RMS Current</label>
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedCurrent" type="text" size="8" tabindex="41" placeholder="0" />
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedCurrent" type="text" size="8" tabindex="55" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">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.</div>
</div>
@ -479,8 +504,8 @@
<div id="idxTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-sm-1-4">Relay<span class="id"></span> IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24 dczIdx" name="dczIdx" type="number" min="0" tabindex="0" data="0" /></div>
<label class="pure-u-1 pure-u-sm-1-4" for="dczRelayIdx">Relay<span class="id"></span> IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24 dczRelayIdx" name="dczRelayIdx" type="number" min="0" tabindex="0" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
</div>


Loading…
Cancel
Save