diff --git a/code/espurna/config/general.h b/code/espurna/config/general.h index efd10af3..3a363b37 100644 --- a/code/espurna/config/general.h +++ b/code/espurna/config/general.h @@ -86,6 +86,7 @@ #define MQTT_RECONNECT_DELAY 10000 #define MQTT_SKIP_RETAINED 1 #define MQTT_SKIP_TIME 1000 +#define MQTT_ACTION_TOPIC "/action" #define MQTT_RELAY_TOPIC "/relay" #define MQTT_LED_TOPIC "/led" #define MQTT_BUTTON_TOPIC "/button" @@ -93,6 +94,8 @@ #define MQTT_VERSION_TOPIC "/version" #define MQTT_HEARTBEAT_TOPIC "/status" +#define MQTT_ACTION_RESET "reset" + #define MQTT_CONNECT_EVENT 0 #define MQTT_DISCONNECT_EVENT 1 #define MQTT_MESSAGE_EVENT 2 diff --git a/code/espurna/config/prototypes.h b/code/espurna/config/prototypes.h index c3a56929..c9373597 100644 --- a/code/espurna/config/prototypes.h +++ b/code/espurna/config/prototypes.h @@ -9,6 +9,7 @@ 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 *)); +char * mqttSubtopic(char * topic); 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/led.ino b/code/espurna/led.ino index 630a5790..73970023 100644 --- a/code/espurna/led.ino +++ b/code/espurna/led.ino @@ -73,9 +73,10 @@ void ledMQTTCallback(unsigned int type, const char * topic, const char * payload if (type == MQTT_MESSAGE_EVENT) { // Match topic - String t = String(topic + mqttTopicRootLength()); - if (!t.startsWith(MQTT_LED_TOPIC)) return; - if (!t.endsWith(mqttSetter)) return; + char * t = mqttSubtopic((char *) topic); + if (strncmp(t, MQTT_LED_TOPIC, strlen(MQTT_LED_TOPIC)) != 0) return; + int len = mqttSetter.length(); + if (strncmp(t + strlen(t) - len, mqttSetter.c_str(), len) != 0) return; // Get led ID unsigned int ledID = topic[strlen(topic) - mqttSetter.length() - 1] - '0'; diff --git a/code/espurna/mqtt.ino b/code/espurna/mqtt.ino index f5658b68..daaed144 100644 --- a/code/espurna/mqtt.ino +++ b/code/espurna/mqtt.ino @@ -36,8 +36,9 @@ void buildTopics() { mqttTopic.replace("{identifier}", getSetting("hostname")); } -unsigned int mqttTopicRootLength() { - return mqttTopic.length(); +char * mqttSubtopic(char * topic) { + int pos = min(mqttTopic.length(), strlen(topic)); + return topic + pos; } void mqttSendRaw(const char * topic, const char * message) { @@ -83,6 +84,9 @@ void _mqttOnConnect(bool sessionPresent) { mqttSend(MQTT_IP_TOPIC, getIP().c_str()); mqttSend(MQTT_VERSION_TOPIC, APP_VERSION); + // Subscribe to system topics + mqttSubscribe(MQTT_ACTION_TOPIC); + // Send connect event to subscribers for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { (*_mqtt_callbacks[i])(MQTT_CONNECT_EVENT, NULL, NULL); @@ -107,16 +111,22 @@ void _mqttOnMessage(char* topic, char* payload, AsyncMqttClientMessageProperties strlcpy(message, payload, len+1); DEBUG_MSG("[MQTT] Received %s => %s", topic, message); - #if MQTT_SKIP_RETAINED if (millis() - mqttConnectedAt < MQTT_SKIP_TIME) { DEBUG_MSG(" - SKIPPED\n"); return; } #endif - DEBUG_MSG("\n"); + // Check system topics + char * p = mqttSubtopic(topic); + if (strcmp(p, MQTT_ACTION_TOPIC) == 0) { + if (strcmp(payload, MQTT_ACTION_RESET) == 0) { + ESP.reset(); + } + } + // Send message event to subscribers // Topic is set to the specific part each one might be checking for (unsigned char i = 0; i < _mqtt_callbacks.size(); i++) { diff --git a/code/espurna/relay.ino b/code/espurna/relay.ino index ed4c99cf..4ad4d9e6 100644 --- a/code/espurna/relay.ino +++ b/code/espurna/relay.ino @@ -378,24 +378,21 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo sprintf(buffer, "%s/+%s", MQTT_RELAY_TOPIC, mqttSetter.c_str()); mqttSubscribe(buffer); - sprintf(buffer, "%s/pulse%s", MQTT_RELAY_TOPIC, mqttSetter.c_str()); - mqttSubscribe(buffer); - } if (type == MQTT_MESSAGE_EVENT) { // Match topic - if (strncmp(topic, "domoticz", 8) == 0) return; - String t = String(topic + mqttTopicRootLength()); - if (!t.startsWith(MQTT_RELAY_TOPIC)) return; - if (!t.endsWith(mqttSetter)) return; + char * t = mqttSubtopic((char *) topic); + if (strncmp(t, MQTT_RELAY_TOPIC, strlen(MQTT_RELAY_TOPIC)) != 0) return; + int len = mqttSetter.length(); + if (strncmp(t + strlen(t) - len, mqttSetter.c_str(), len) != 0) return; // Get value unsigned int value = (char)payload[0] - '0'; // Pulse topic - if (t.indexOf("pulse") > 0) { + if (strncmp(t + strlen(MQTT_RELAY_TOPIC) + 1, "pulse", 5) == 0) { relayPulseMode(value, !sameSetGet); return; } diff --git a/code/espurna/web.ino b/code/espurna/web.ino index d6d23355..777b5d17 100644 --- a/code/espurna/web.ino +++ b/code/espurna/web.ino @@ -521,6 +521,15 @@ bool _authAPI(AsyncWebServerRequest *request) { } +bool _asJson(AsyncWebServerRequest *request) { + bool asJson = false; + if (request->hasHeader("Accept")) { + AsyncWebHeader* h = request->getHeader("Accept"); + asJson = h->value().equals("application/json"); + } + return asJson; +} + ArRequestHandlerFunction _bindAPI(unsigned int apiID) { return [apiID](AsyncWebServerRequest *request) { @@ -528,11 +537,7 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) { if (!_authAPI(request)) return; - bool asJson = false; - if (request->hasHeader("Accept")) { - AsyncWebHeader* h = request->getHeader("Accept"); - asJson = h->value().equals("application/json"); - } + bool asJson = _asJson(request); web_api_t api = _apis[apiID]; if (request->method() == HTTP_PUT) { @@ -584,11 +589,7 @@ void _onAPIs(AsyncWebServerRequest *request) { if (!_authAPI(request)) return; - bool asJson = false; - if (request->hasHeader("Accept")) { - AsyncWebHeader* h = request->getHeader("Accept"); - asJson = h->value().equals("application/json"); - } + bool asJson = _asJson(request); String output; if (asJson) { @@ -609,6 +610,32 @@ void _onAPIs(AsyncWebServerRequest *request) { } +void _onRPC(AsyncWebServerRequest *request) { + + webLogRequest(request); + + if (!_authAPI(request)) return; + + //bool asJson = _asJson(request); + int response = 404; + + if (request->hasParam("action")) { + + AsyncWebParameter* p = request->getParam("action"); + String action = p->value(); + DEBUG_MSG("[RPC] Action: %s\n", action.c_str()); + + if (action.equals("reset")) { + response = 200; + deferred.once_ms(100, []() { ESP.reset(); }); + } + + } + + request->send(response); + +} + void _onHome(AsyncWebServerRequest *request) { DEBUG_MSG("[DEBUG] Free heap: %d bytes\n", ESP.getFreeHeap()); @@ -680,6 +707,7 @@ void webSetup() { server.on("/index.html", HTTP_GET, _onHome); server.on("/auth", HTTP_GET, _onAuth); server.on("/apis", HTTP_GET, _onAPIs); + server.on("/rpc", HTTP_GET, _onRPC); // Serve static files char lastModified[50];