Browse Source

Merge branch 'master' into fauxmo

fastled
Xose Pérez 8 years ago
parent
commit
4a34e863b6
14 changed files with 326 additions and 142 deletions
  1. +4
    -0
      code/html/custom.css
  2. +92
    -62
      code/html/custom.js
  3. +1
    -1
      code/html/fsversion
  4. +14
    -1
      code/html/index.html
  5. +1
    -1
      code/src/button.ino
  6. +2
    -1
      code/src/defaults.h
  7. +1
    -1
      code/src/dht.ino
  8. +2
    -2
      code/src/emon.ino
  9. +6
    -6
      code/src/mqtt.ino
  10. +1
    -1
      code/src/pow.ino
  11. +18
    -14
      code/src/relay.ino
  12. +2
    -2
      code/src/rf.ino
  13. +1
    -1
      code/src/version.h
  14. +181
    -49
      code/src/web.ino

+ 4
- 0
code/html/custom.css View File

@ -37,6 +37,10 @@
.button-reconnect {
background: rgb(202, 60, 60);
}
.button-apikey {
background: rgb(0, 202, 0);
margin-left: 5px;
}
.pure-g {
margin-bottom: 20px;
}


+ 92
- 62
code/html/custom.js View File

@ -1,9 +1,8 @@
var websock;
var csrf;
function doUpdate() {
var data = $("#formSave").serializeArray();
websock.send(JSON.stringify({'csrf': csrf, 'config': data}));
websock.send(JSON.stringify({'config': data}));
$(".powExpected").val(0);
return false;
}
@ -11,19 +10,37 @@ function doUpdate() {
function doReset() {
var response = window.confirm("Are you sure you want to reset the device?");
if (response == false) return false;
websock.send(JSON.stringify({'csrf': csrf, 'action': 'reset'}));
websock.send(JSON.stringify({'action': 'reset'}));
return false;
}
function doReconnect() {
var response = window.confirm("Are you sure you want to disconnect from the current WIFI network?");
if (response == false) return false;
websock.send(JSON.stringify({'csrf': csrf, 'action': 'reconnect'}));
websock.send(JSON.stringify({'action': 'reconnect'}));
return false;
}
function doToggle(element, value) {
websock.send(JSON.stringify({'csrf': csrf, 'action': value ? 'on' : 'off'}));
websock.send(JSON.stringify({'action': value ? 'on' : 'off'}));
return false;
}
function randomString(length, chars) {
var mask = '';
if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz';
if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (chars.indexOf('#') > -1) mask += '0123456789';
if (chars.indexOf('@') > -1) mask += 'ABCDEF';
if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\';
var result = '';
for (var i = length; i > 0; --i) result += mask[Math.round(Math.random() * (mask.length - 1))];
return result;
}
function doGenerateAPIKey() {
var apikey = randomString(16, '@#');
$("input[name=\"apiKey\"]").val(apikey);
return false;
}
@ -31,6 +48,7 @@ function showPanel() {
$(".panel").hide();
$("#" + $(this).attr("data")).show();
if ($("#layout").hasClass('active')) toggleMenu();
$("input[type='checkbox']").iphoneStyle("calculateDimensions").iphoneStyle("refresh");
};
function toggleMenu() {
@ -41,36 +59,6 @@ function toggleMenu() {
function processData(data) {
// CSRF
if ("csrf" in data) {
csrf = data.csrf;
}
// messages
if ("message" in data) {
window.alert(data.message);
}
// pre-process
if ("network" in data) {
data.network = data.network.toUpperCase();
}
if ("mqttStatus" in data) {
data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
}
// relay
if ("relayStatus" in data) {
$("input[name='relayStatus']")
.prop("checked", data.relayStatus)
.iphoneStyle({
checkedLabel: 'ON',
uncheckedLabel: 'OFF',
onChange: doToggle
})
.iphoneStyle("refresh");
}
// title
if ("app" in data) {
$(".pure-menu-heading").html(data.app);
@ -81,9 +69,27 @@ function processData(data) {
document.title = title;
}
// automatic assign
Object.keys(data).forEach(function(key) {
// Wifi
if (key == "wifi") {
var groups = $("#panel-wifi .pure-g");
for (var i in data.wifi) {
var wifi = data.wifi[i];
Object.keys(wifi).forEach(function(key) {
var id = "input[name=" + key + "]";
if ($(id, groups[i]).length) $(id, groups[i]).val(wifi[key]);
});
};
return;
}
// Messages
if (key == "message") {
window.alert(data.message);
return;
}
// Enable options
if (key.endsWith("Visible")) {
var module = key.slice(0,-7);
@ -92,41 +98,40 @@ function processData(data) {
return;
}
// Pre-process
if (key == "network") {
data.network = data.network.toUpperCase();
}
if (key == "mqttStatus") {
data.mqttStatus = data.mqttStatus ? "CONNECTED" : "NOT CONNECTED";
}
// Look for INPUTs
var element = $("input[name=" + key + "]");
if (element.length > 0) {
if (element.attr('type') == 'checkbox') {
element
.prop("checked", data[key] == 1)
.iphoneStyle({
resizeContainer: false,
resizeHandle: false,
checkedLabel: 'ON',
uncheckedLabel: 'OFF'
})
.prop("checked", data[key])
.iphoneStyle("refresh");
} else {
element.val(data[key]);
}
return;
}
// Look for SELECTs
var element = $("select[name=" + key + "]");
if (element.length > 0) {
element.val(data[key]);
return;
}
});
// WIFI
var groups = $("#panel-wifi .pure-g");
for (var i in data.wifi) {
var wifi = data.wifi[i];
Object.keys(wifi).forEach(function(key) {
var id = "input[name=" + key + "]";
if ($(id, groups[i]).length) $(id, groups[i]).val(wifi[key]);
});
};
// Auto generate an APIKey if none defined yet
if ($("input[name='apiKey']").val() == "") {
doGenerateAPIKey();
}
}
@ -138,16 +143,10 @@ function getJson(str) {
}
}
function init() {
$("#menuLink").on('click', toggleMenu);
$(".button-update").on('click', doUpdate);
$(".button-reset").on('click', doReset);
$(".button-reconnect").on('click', doReconnect);
$(".pure-menu-link").on('click', showPanel);
var host = window.location.hostname;
//host = '192.168.1.115';
function initWebSocket(host) {
if (host === undefined) {
host = window.location.hostname;
}
websock = new WebSocket('ws://' + host + '/ws');
websock.onopen = function(evt) {};
websock.onclose = function(evt) {};
@ -156,6 +155,37 @@ function init() {
var data = getJson(evt.data);
if (data) processData(data);
};
}
function init() {
$("#menuLink").on('click', toggleMenu);
$(".button-update").on('click', doUpdate);
$(".button-reset").on('click', doReset);
$(".button-reconnect").on('click', doReconnect);
$(".button-apikey").on('click', doGenerateAPIKey);
$(".pure-menu-link").on('click', showPanel);
$("input[name='relayStatus']")
.iphoneStyle({
onChange: doToggle
});
$("input[type='checkbox']")
.iphoneStyle({
resizeContainer: true,
resizeHandle: true,
checkedLabel: 'ON',
uncheckedLabel: 'OFF'
})
.iphoneStyle("refresh");
$.ajax({
'method': 'GET',
'url': '/auth'
}).done(function(data) {
initWebSocket();
});
}


+ 1
- 1
code/html/fsversion View File

@ -1 +1 @@
1.0.1
1.0.2

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

@ -172,12 +172,25 @@
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="adminPass">Administrator password</label>
<label class="pure-u-1 pure-u-md-1-4" for="adminPass">Admin password</label>
<input name="adminPass" class="pure-u-1 pure-u-md-3-4" type="text" tabindex="3" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">The administrator password is used to access this web interface (user 'admin'), but also to connect to the device when in AP mode or to flash a new firmware over-the-air (OTA).</div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-sm-1-4"><label for="apiEnabled">Enable HTTP API</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="apiEnabled" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="apiKey">HTTP API Key</label>
<input name="apiKey" class="pure-u-3-4 pure-u-md-1-2" type="text" tabindex="4" />
<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>
</fieldset>
</div>
</div>


+ 1
- 1
code/src/button.ino View File

@ -21,7 +21,7 @@ void buttonSetup() {
void buttonLoop() {
if (button1.loop()) {
if (button1.getEvent() == EVENT_SINGLE_CLICK) toggleRelay();
if (button1.getEvent() == EVENT_SINGLE_CLICK) relayToggle(0);
if (button1.getEvent() == EVENT_DOUBLE_CLICK) createAP();
if (button1.getEvent() == EVENT_LONG_CLICK) ESP.reset();
}


+ 2
- 1
code/src/defaults.h View File

@ -75,7 +75,8 @@
#define WIFI_MAX_NETWORKS 3
#define ADMIN_PASS "fibonacci"
#define HTTP_USERNAME "admin"
#define CSRF_BUFFER_SIZE 5
#define WS_BUFFER_SIZE 5
#define WS_TIMEOUT 1800000
// -----------------------------------------------------------------------------
// OTA & NOFUSS


+ 1
- 1
code/src/dht.ino View File

@ -66,7 +66,7 @@ void dhtLoop() {
// Update websocket clients
char buffer[100];
sprintf_P(buffer, PSTR("{\"dhtVisible\": 1, \"dhtTmp\": %s, \"dhtHum\": %s}"), temperature, humidity);
webSocketSend(buffer);
wsSend(buffer);
}


+ 2
- 2
code/src/emon.ino View File

@ -71,7 +71,7 @@ void powerMonitorLoop() {
if (millis() > next_measurement) {
// Safety check: do not read current if relay is OFF
if (!digitalRead(RELAY_PIN)) {
if (!relayStatus(0)) {
current = 0;
} else {
current = emon.getCurrent(EMON_SAMPLES);
@ -95,7 +95,7 @@ void powerMonitorLoop() {
// Update websocket clients
char text[20];
sprintf_P(text, PSTR("{\"emonPower\": %d}"), int(current * mainsVoltage));
webSocketSend(text);
wsSend(text);
// Send MQTT messages averaged every EMON_MEASUREMENTS
if (measurements == EMON_MEASUREMENTS) {


+ 6
- 6
code/src/mqtt.ino View File

@ -46,7 +46,7 @@ void _mqttOnConnect(bool sessionPresent) {
DEBUG_MSG("[MQTT] Connected!\n");
// Send status via webSocket
webSocketSend((char *) "{\"mqttStatus\": true}");
wsSend((char *) "{\"mqttStatus\": true}");
// Build MQTT topics
buildTopics();
@ -59,7 +59,7 @@ void _mqttOnConnect(bool sessionPresent) {
mqttSend((char *) MQTT_FSVERSION_TOPIC, buffer);
// Publish current relay status
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) (digitalRead(RELAY_PIN) ? "1" : "0"));
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) (relayStatus(0) ? "1" : "0"));
// Subscribe to topic
DEBUG_MSG("[MQTT] Subscribing to %s\n", (char *) mqttTopic.c_str());
@ -70,7 +70,7 @@ void _mqttOnConnect(bool sessionPresent) {
void _mqttOnDisconnect(AsyncMqttClientDisconnectReason reason) {
// Send status via webSocket
webSocketSend((char *) "{\"mqttStatus\": false}");
wsSend((char *) "{\"mqttStatus\": false}");
}
@ -91,14 +91,14 @@ void _mqttOnMessage(char* topic, char* payload, AsyncMqttClientMessageProperties
// Action to perform
if ((char)payload[0] == '0') {
isCallbackMessage = true;
switchRelayOff();
relayStatus(0, false);
}
if ((char)payload[0] == '1') {
isCallbackMessage = true;
switchRelayOn();
relayStatus(0, true);
}
if ((char)payload[0] == '2') {
toggleRelay();
relayToggle(0);
}
isCallbackMessage = false;


+ 1
- 1
code/src/pow.ino View File

@ -146,7 +146,7 @@ void powLoop() {
char buffer[100];
sprintf_P(buffer, PSTR("{\"powVisible\": 1, \"powActivePower\": %d}"), power);
webSocketSend(buffer);
wsSend(buffer);
if (--report_count == 0) {
mqttSend((char *) getSetting("powPowerTopic", POW_POWER_TOPIC).c_str(), (char *) String(power).c_str());


+ 18
- 14
code/src/relay.ino View File

@ -13,7 +13,7 @@ Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
// RELAY
// -----------------------------------------------------------------------------
void switchRelayOn() {
void _relayOn(unsigned char id) {
if (!digitalRead(RELAY_PIN)) {
DEBUG_MSG("[RELAY] ON\n");
@ -23,11 +23,11 @@ void switchRelayOn() {
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) "1");
}
webSocketSend((char *) "{\"relayStatus\": true}");
wsSend((char *) "{\"relayStatus\": true}");
}
void switchRelayOff() {
void _relayOff(unsigned char id) {
if (digitalRead(RELAY_PIN)) {
DEBUG_MSG("[RELAY] OFF\n");
@ -37,23 +37,27 @@ void switchRelayOff() {
mqttSend((char *) MQTT_STATUS_TOPIC, (char *) "0");
}
webSocketSend((char *) "{\"relayStatus\": false}");
wsSend((char *) "{\"relayStatus\": false}");
}
void toggleRelay() {
if (digitalRead(RELAY_PIN)) {
switchRelayOff();
} else {
switchRelayOn();
}
void relayStatus(unsigned char id, bool status) {
status ? _relayOn(id) : _relayOff(id);
}
bool relayStatus(unsigned char id) {
return (digitalRead(RELAY_PIN) == HIGH);
}
void relayToggle(unsigned char id) {
relayStatus(id, !relayStatus(id));
}
void relaySetup() {
pinMode(RELAY_PIN, OUTPUT);
EEPROM.begin(4096);
byte relayMode = getSetting("relayMode", String(RELAY_MODE)).toInt();
if (relayMode == 0) switchRelayOff();
if (relayMode == 1) switchRelayOn();
if (relayMode == 2) EEPROM.read(0) == 1 ? switchRelayOn() : switchRelayOff();
if (relayMode == 0) relayStatus(0, false);
if (relayMode == 1) relayStatus(0, true);
if (relayMode == 2) relayStatus(0, EEPROM.read(0) == 1);
}

+ 2
- 2
code/src/rf.ino View File

@ -23,8 +23,8 @@ void rfLoop() {
return;
if (rfCode == 0) return;
DEBUG_MSG("[RF] Received code: %lu\n", rfCode);
if (rfCode == rfCodeON) switchRelayOn();
if (rfCode == rfCodeOFF) switchRelayOff();
if (rfCode == rfCodeON) relayStatus(0, true);
if (rfCode == rfCodeOFF) relayStatus(0, false);
rfCode = 0;
}


+ 1
- 1
code/src/version.h View File

@ -1,4 +1,4 @@
#define APP_NAME "Espurna"
#define APP_VERSION "1.0.1"
#define APP_VERSION "1.0.2"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"

+ 181
- 49
code/src/web.ino View File

@ -18,23 +18,28 @@ Copyright (C) 2016 by Xose Pérez <xose dot perez at gmail dot com>
AsyncWebServer server(80);
AsyncWebSocket ws("/ws");
unsigned long _csrf[CSRF_BUFFER_SIZE];
typedef struct {
IPAddress ip;
unsigned long timestamp = 0;
} ws_ticket_t;
ws_ticket_t _ticket[WS_BUFFER_SIZE];
// -----------------------------------------------------------------------------
// WEBSOCKETS
// -----------------------------------------------------------------------------
bool webSocketSend(char * payload) {
bool wsSend(char * payload) {
//DEBUG_MSG("[WEBSOCKET] Broadcasting '%s'\n", payload);
ws.textAll(payload);
}
bool webSocketSend(uint32_t client_id, char * payload) {
bool wsSend(uint32_t client_id, char * payload) {
//DEBUG_MSG("[WEBSOCKET] Sending '%s' to #%ld\n", payload, client_id);
ws.text(client_id, payload);
}
void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
// Parse JSON input
DynamicJsonBuffer jsonBuffer;
@ -45,16 +50,6 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
return;
}
// CSRF
unsigned long csrf = 0;
if (root.containsKey("csrf")) csrf = root["csrf"];
if (csrf != _csrf[client_id % CSRF_BUFFER_SIZE]) {
DEBUG_MSG("[WEBSOCKET] CSRF check failed\n");
ws.text(client_id, "{\"message\": \"Session expired, please reload page...\"}");
return;
}
// Check actions
if (root.containsKey("action")) {
@ -63,8 +58,8 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
if (action.equals("reset")) ESP.reset();
if (action.equals("reconnect")) wifiDisconnect();
if (action.equals("on")) switchRelayOn();
if (action.equals("off")) switchRelayOff();
if (action.equals("on")) relayStatus(0, true);
if (action.equals("off")) relayStatus(0, false);
};
@ -76,6 +71,7 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
bool dirty = false;
bool dirtyMQTT = false;
bool apiEnabled = false;
unsigned int network = 0;
for (unsigned int i=0; i<config.size(); i++) {
@ -97,6 +93,12 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
if (value.length() == 0) continue;
}
// Checkboxes
if (key == "apiEnabled") {
apiEnabled = true;
continue;
}
if (key == "ssid") {
key = key + String(network);
}
@ -113,6 +115,12 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
}
// Checkboxes
if (apiEnabled != (getSetting("apiEnabled").toInt() == 1)) {
setSetting("apiEnabled", String() + (apiEnabled ? 1 : 0));
dirty = true;
}
// Save settings
if (dirty) {
@ -146,7 +154,7 @@ void webSocketParse(uint32_t client_id, uint8_t * payload, size_t length) {
}
void webSocketStart(uint32_t client_id) {
void _wsStart(uint32_t client_id) {
char app[64];
sprintf(app, "%s %s", APP_NAME, APP_VERSION);
@ -157,12 +165,6 @@ void webSocketStart(uint32_t client_id) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
// CSRF
if (client_id < CSRF_BUFFER_SIZE) {
_csrf[client_id] = random(0x7fffffff);
}
root["csrf"] = _csrf[client_id % CSRF_BUFFER_SIZE];
root["app"] = app;
root["manufacturer"] = String(MANUFACTURER);
root["chipid"] = chipid;
@ -171,14 +173,16 @@ void webSocketStart(uint32_t client_id) {
root["hostname"] = getSetting("hostname", HOSTNAME);
root["network"] = getNetwork();
root["ip"] = getIP();
root["mqttStatus"] = mqttConnected() ? "1" : "0";
root["mqttStatus"] = mqttConnected();
root["mqttServer"] = getSetting("mqttServer", MQTT_SERVER);
root["mqttPort"] = getSetting("mqttPort", String(MQTT_PORT));
root["mqttUser"] = getSetting("mqttUser");
root["mqttPassword"] = getSetting("mqttPassword");
root["mqttTopic"] = getSetting("mqttTopic", MQTT_TOPIC);
root["relayStatus"] = digitalRead(RELAY_PIN) == HIGH;
root["relayStatus"] = relayStatus(0);
root["relayMode"] = getSetting("relayMode", String(RELAY_MODE));
root["apiEnabled"] = getSetting("apiEnabled").toInt() == 1;
root["apiKey"] = getSetting("apiKey");
#if ENABLE_DHT
root["dhtVisible"] = 1;
@ -217,15 +221,37 @@ void webSocketStart(uint32_t client_id) {
}
void webSocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
bool _wsAuth(AsyncWebSocketClient * client) {
IPAddress ip = client->remoteIP();
unsigned long now = millis();
unsigned short index = 0;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if ((_ticket[index].ip == ip) && (now - _ticket[index].timestamp < WS_TIMEOUT)) break;
}
if (index == WS_BUFFER_SIZE) {
DEBUG_MSG("[WEBSOCKET] Validation check failed\n");
ws.text(client->id(), "{\"message\": \"Session expired, please reload page...\"}");
return false;
}
return true;
}
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
// Authorize
#ifndef NOWSAUTH
if (!_wsAuth(client)) return;
#endif
if (type == WS_EVT_CONNECT) {
#if DEBUG_PORT
{
IPAddress ip = server.remoteIP(client->id());
DEBUG_MSG("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n", client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
}
#endif
webSocketStart(client->id());
IPAddress ip = client->remoteIP();
DEBUG_MSG("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n", client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
_wsStart(client->id());
} else if(type == WS_EVT_DISCONNECT) {
DEBUG_MSG("[WEBSOCKET] #%u disconnected\n", client->id());
} else if(type == WS_EVT_ERROR) {
@ -233,50 +259,156 @@ void webSocketEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsE
} else if(type == WS_EVT_PONG) {
DEBUG_MSG("[WEBSOCKET] #%u pong(%u): %s\n", client->id(), len, len ? (char*) data : "");
} else if(type == WS_EVT_DATA) {
webSocketParse(client->id(), data, len);
_wsParse(client->id(), data, len);
}
}
// -----------------------------------------------------------------------------
// WEBSERVER
// -----------------------------------------------------------------------------
void onHome(AsyncWebServerRequest *request) {
DEBUG_MSG("[WEBSERVER] Request: %s\n", request->url().c_str());
void _logRequest(AsyncWebServerRequest *request) {
DEBUG_MSG("[WEBSERVER] Request: %s %s\n", request->methodToString(), request->url().c_str());
}
bool _authenticate(AsyncWebServerRequest *request) {
String password = getSetting("adminPass", ADMIN_PASS);
char httpPassword[password.length() + 1];
password.toCharArray(httpPassword, password.length() + 1);
if (!request->authenticate(HTTP_USERNAME, httpPassword)) {
return request->requestAuthentication();
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();
request->send(SPIFFS, "/index.html");
}
void onRelayOn(AsyncWebServerRequest *request) {
DEBUG_MSG("[WEBSERVER] Request: %s\n", request->url().c_str());
switchRelayOn();
void _onRelayOn(AsyncWebServerRequest *request) {
_logRequest(request);
relayStatus(0, true);
request->send(200, "text/plain", "ON");
};
void onRelayOff(AsyncWebServerRequest *request) {
DEBUG_MSG("[WEBSERVER] Request: %s\n", request->url().c_str());
switchRelayOff();
void _onRelayOff(AsyncWebServerRequest *request) {
_logRequest(request);
relayStatus(0, false);
request->send(200, "text/plain", "OFF");
};
bool _apiAuth(AsyncWebServerRequest *request) {
if (getSetting("apiEnabled").toInt() == 0) {
DEBUG_MSG("[WEBSERVER] HTTP API is not enabled\n");
request->send(403);
return false;
}
if (!request->hasParam("apikey", (request->method() == HTTP_PUT))) {
DEBUG_MSG("[WEBSERVER] Missing apikey parameter\n");
request->send(403);
return false;
}
AsyncWebParameter* p = request->getParam("apikey", (request->method() == HTTP_PUT));
if (!p->value().equals(getSetting("apiKey"))) {
DEBUG_MSG("[WEBSERVER] Wrong apikey parameter\n");
request->send(403);
return false;
}
return true;
}
ArRequestHandlerFunction _onRelayStatusWrapper(unsigned int relayID) {
return [&](AsyncWebServerRequest *request) {
_logRequest(request);
if (!_apiAuth(request)) return;
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);
}
}
}
bool asJson = false;
if (request->hasHeader("Accept")) {
AsyncWebHeader* h = request->getHeader("Accept");
asJson = h->value().equals("application/json");
}
if (asJson) {
char buffer[20];
sprintf(buffer, "{\"status\": %d}", relayStatus(relayID) ? 1 : 0);
request->send(200, "application/json", buffer);
} else {
request->send(200, "text/plain", relayStatus(relayID) ? "1" : "0");
}
};
}
void webSetup() {
// Setup websocket plugin
ws.onEvent(webSocketEvent);
ws.onEvent(_wsEvent);
server.addHandler(&ws);
// Serve home (password protected)
server.on("/", HTTP_GET, onHome);
server.on("/index.html", HTTP_GET, onHome);
server.on("/", HTTP_GET, _onHome);
server.on("/index.html", HTTP_GET, _onHome);
server.on("/auth", HTTP_GET, _onAuth);
// API entry points (non protected)
server.on("/relay/on", HTTP_GET, onRelayOn);
server.on("/relay/off", HTTP_GET, onRelayOff);
server.on("/relay/on", HTTP_GET, _onRelayOn);
server.on("/relay/off", HTTP_GET, _onRelayOff);
server.on("/relay/0/status", HTTP_GET + HTTP_PUT, _onRelayStatusWrapper(0));
//server.on("/relay/1/status", HTTP_GET + HTTP_PUT, _onRelayStatusWrapper(1));
// Serve static files
server.serveStatic("/", SPIFFS, "/");


Loading…
Cancel
Save