Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions localazy/source/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
"tag": "Name tag",
"title": "Home-Assistant"
},
"allowdestructive": "Allow destructive commands",
"id": "Client ID",
"interval": "Interval",
"keepalive": "Keep-alive",
Expand Down
6 changes: 6 additions & 0 deletions src/AmsConfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ void AmsConfiguration::ackNetworkConfigChange() {
bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
if(hasConfig()) {
EEPROM.get(CONFIG_MQTT_START, config);
if(config.magic != 0xB6) {
if(config.magic != 0xA5) { // New magic for 2.4.11
if(config.magic != 0x9C) {
if(config.magic != 0x7B) {
Expand All @@ -156,6 +157,9 @@ bool AmsConfiguration::getMqttConfig(MqttConfig& config) {
}
config.rebootMinutes = config.ssl ? 5 : 0;
config.magic = 0xA5;
}
config.allowDestructiveCommands = false;
config.magic = 0xB6;
}
return true;
} else {
Expand All @@ -181,6 +185,7 @@ bool AmsConfiguration::setMqttConfig(MqttConfig& config) {
mqttChanged |= config.timeout != existing.timeout;
mqttChanged |= config.keepalive != existing.keepalive;
mqttChanged |= config.rebootMinutes != existing.rebootMinutes;
mqttChanged |= config.allowDestructiveCommands != existing.allowDestructiveCommands;
} else {
mqttChanged = true;
}
Expand Down Expand Up @@ -218,6 +223,7 @@ void AmsConfiguration::clearMqtt(MqttConfig& config) {
config.timeout = 1000;
config.keepalive = 60;
config.rebootMinutes = 0;
config.allowDestructiveCommands = false;
}

void AmsConfiguration::setMqttChanged() {
Expand Down
6 changes: 5 additions & 1 deletion src/AmsConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
#define REBOOT_CAUSE_FIRMWARE_UPDATE 8
#define REBOOT_CAUSE_MQTT_DISCONNECTED 9
#define REBOOT_CAUSE_SMART_CONFIG 10
#define REBOOT_CAUSE_MQTT_REBOOT 11
#define REBOOT_CAUSE_MQTT_FACTORY_RESET 12
#define REBOOT_CAUSE_MQTT_FIRMWARE_UPGRADE 13

struct ResetDataContainer {
uint8_t cause;
Expand Down Expand Up @@ -121,7 +124,8 @@ struct MqttConfig {
uint16_t timeout;
uint8_t keepalive;
uint8_t rebootMinutes;
}; // 684
bool allowDestructiveCommands; // appended; gated by MqttConfig.magic migration
}; // 685

struct WebConfig {
uint8_t security;
Expand Down
5 changes: 3 additions & 2 deletions src/AmsJsonGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ void AmsJsonGenerator::generateConfigurationJson(AmsConfiguration* config, JsonS
qsr = LittleFS.exists(FILE_MQTT_CERT);
qsk = LittleFS.exists(FILE_MQTT_KEY);
}
EMIT(PSTR(",\"q\":{\"h\":\"%s\",\"p\":%d,\"u\":\"%s\",\"a\":\"%s\",\"c\":\"%s\",\"b\":\"%s\",\"r\":\"%s\",\"m\":%d,\"s\":{\"e\":%s,\"c\":%s,\"r\":%s,\"k\":%s},\"t\":%d,\"d\":%d,\"i\":%d,\"k\":%d,\"e\":%s}"),
EMIT(PSTR(",\"q\":{\"h\":\"%s\",\"p\":%d,\"u\":\"%s\",\"a\":\"%s\",\"c\":\"%s\",\"b\":\"%s\",\"r\":\"%s\",\"m\":%d,\"s\":{\"e\":%s,\"c\":%s,\"r\":%s,\"k\":%s},\"t\":%d,\"d\":%d,\"i\":%d,\"k\":%d,\"e\":%s,\"dc\":%s}"),
mqttConfig.host,
mqttConfig.port,
mqttConfig.username,
Expand All @@ -203,7 +203,8 @@ void AmsJsonGenerator::generateConfigurationJson(AmsConfiguration* config, JsonS
mqttConfig.stateUpdateInterval,
mqttConfig.timeout,
mqttConfig.keepalive,
mqttConfig.rebootMinutes == 0 ? "null" : String(mqttConfig.rebootMinutes, 10).c_str()
mqttConfig.rebootMinutes == 0 ? "null" : String(mqttConfig.rebootMinutes, 10).c_str(),
mqttConfig.allowDestructiveCommands ? "true" : "false"
);

// Price
Expand Down
2 changes: 2 additions & 0 deletions src/AmsToMqttBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1950,6 +1950,8 @@ void MQTT_connect() {
}

if(mqttHandler != NULL) {
mqttHandler->setResetDataContainer(&rdc);
mqttHandler->setDataStorage(&ds);
mqttHandler->connect();
mqttHandler->publishSystem(&hw, ps, &ea);
if(ps != NULL && ps->hasPrice()) {
Expand Down
1 change: 1 addition & 0 deletions src/AmsWebServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,7 @@ void AmsWebServer::handleSave() {
mqtt.timeout = server.arg(F("qi")).toInt();
mqtt.keepalive = server.arg(F("qk")).toInt();
mqtt.rebootMinutes = server.arg(F("qe")).toInt();
mqtt.allowDestructiveCommands = server.arg(F("qdc")) == F("true");
} else {
config->clearMqtt(mqtt);
}
Expand Down
83 changes: 83 additions & 0 deletions src/mqtt/AmsMqttHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include "AmsStorage.h"
#include "LittleFS.h"
#include "Uptime.h"
#include "AmsJsonGenerator.h"
#include <ArduinoJson.h>

void AmsMqttHandler::setCaVerification(bool caVerification) {
this->caVerification = caVerification;
Expand Down Expand Up @@ -221,4 +223,85 @@ bool AmsMqttHandler::loop() {

bool AmsMqttHandler::isRebootSuggested() {
return rebootSuggested;
}

void AmsMqttHandler::rebootDevice(uint8_t cause) {
if(rdc != NULL) rdc->cause = cause;
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::INFO))
#endif
debugger->printf_P(PSTR("Rebooting (MQTT command)\n"));
debugger->flush();
delay(1000);
ESP.restart();
}

// Generic commands available to every payload handler. Accepts either a plain
// payload ("reboot") or a JSON object ({"action":"reboot"}). Destructive actions
// (reboot, factoryreset) require MqttConfig.allowDestructiveCommands; factoryreset
// additionally requires "confirm":true.
bool AmsMqttHandler::handleCommand(String &topic, String &payload) {
if(strcmp(topic.c_str(), subTopic) != 0) return false;

char action[24] = {0};
bool confirm = false;
String version;
if(payload.startsWith("{")) {
DynamicJsonDocument doc(512);
if(deserializeJson(doc, payload)) return false;
JsonObject obj = doc.as<JsonObject>();
if(!obj.containsKey(F("action"))) return false;
strncpy(action, obj[F("action")] | "", sizeof(action) - 1);
confirm = obj[F("confirm")] | false;
if(obj.containsKey(F("version"))) version = (const char*) (obj[F("version")] | "");
} else {
strncpy(action, payload.c_str(), sizeof(action) - 1);
}

if(strcmp_P(action, PSTR("fwupgrade")) == 0) {
const char* target = version.length() > 0 ? version.c_str() : updater->getNextVersion();
if(strlen(target) > 0 && strcmp(target, FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(target);
}
return true;
} else if(strcmp_P(action, PSTR("dayplot")) == 0) {
if(ds != NULL) {
char plotTopic[80];
snprintf_P(plotTopic, sizeof(plotTopic), PSTR("%s/dayplot"), pubTopic);
AmsJsonGenerator::generateDayPlotJson(ds, json, BUF_SIZE_COMMON);
mqtt.publish(plotTopic, json);
loop();
}
return true;
} else if(strcmp_P(action, PSTR("monthplot")) == 0) {
if(ds != NULL) {
char plotTopic[80];
snprintf_P(plotTopic, sizeof(plotTopic), PSTR("%s/monthplot"), pubTopic);
AmsJsonGenerator::generateMonthPlotJson(ds, json, BUF_SIZE_COMMON);
mqtt.publish(plotTopic, json);
loop();
}
return true;
} else if(strcmp_P(action, PSTR("reboot")) == 0) {
if(!mqttConfig.allowDestructiveCommands) {
debugger->printf_P(PSTR("Ignoring MQTT reboot: destructive commands disabled\n"));
return true;
}
rebootDevice(REBOOT_CAUSE_MQTT_REBOOT);
return true;
} else if(strcmp_P(action, PSTR("factoryreset")) == 0) {
if(!mqttConfig.allowDestructiveCommands) {
debugger->printf_P(PSTR("Ignoring MQTT factoryreset: destructive commands disabled\n"));
return true;
}
if(!confirm) {
debugger->printf_P(PSTR("Ignoring MQTT factoryreset: missing \"confirm\":true\n"));
return true;
}
LittleFS.format();
config->clear();
rebootDevice(REBOOT_CAUSE_MQTT_FACTORY_RESET);
return true;
}
return false;
}
10 changes: 10 additions & 0 deletions src/mqtt/AmsMqttHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "Arduino.h"
#include <MQTT.h>
#include "AmsData.h"
#include "AmsDataStorage.h"
#include "AmsConfiguration.h"
#include "EnergyAccounting.h"
#include "HwTools.h"
Expand Down Expand Up @@ -43,6 +44,8 @@ class AmsMqttHandler {

void setCaVerification(bool);
void setConfig(MqttConfig& mqttConfig);
void setResetDataContainer(ResetDataContainer* rdc) { this->rdc = rdc; }
void setDataStorage(AmsDataStorage* ds) { this->ds = ds; }

bool connect();
bool defaultSubscribe();
Expand Down Expand Up @@ -103,6 +106,13 @@ class AmsMqttHandler {

AmsFirmwareUpdater* updater = NULL;
bool rebootSuggested = false;
ResetDataContainer* rdc = NULL;
AmsDataStorage* ds = NULL;

// Generic device commands shared by all payload handlers (fwupgrade / reboot /
// factoryreset). Returns true if the message was a recognised command.
bool handleCommand(String &topic, String &payload);
void rebootDevice(uint8_t cause);

private:
#if defined(AMS_REMOTE_DEBUG)
Expand Down
8 changes: 2 additions & 6 deletions src/mqtt/HomeAssistantMqttHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -867,12 +867,8 @@ void HomeAssistantMqttHandler::onMessage(String &topic, String &payload) {
priceImportInit = 0;
priceExportInit = 0;
}
} else if(topic == subTopic) {
if(payload.equals("fwupgrade")) {
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(updater->getNextVersion());
}
}
} else {
handleCommand(topic, payload); // fwupgrade / reboot / factoryreset
}
}

Expand Down
42 changes: 8 additions & 34 deletions src/mqtt/JsonMqttHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,9 @@ void JsonMqttHandler::onMessage(String &topic, String &payload) {
#endif
debugger->printf_P(PSTR("Received command [%s] to [%s]\n"), payload.c_str(), topic.c_str());

if(handleCommand(topic, payload)) // fwupgrade / reboot / factoryreset (shared across handlers)
return;

if(topic == subTopic) {
#if defined(AMS_REMOTE_DEBUG)
if (debugger->isActive(RemoteDebug::DEBUG))
Expand All @@ -547,24 +550,8 @@ void JsonMqttHandler::onMessage(String &topic, String &payload) {
JsonObject obj = doc.as<JsonObject>();
if(obj.containsKey(F("action"))) {
const char* action = obj[F("action")];
if(strcmp_P(action, PSTR("fwupgrade")) == 0) {
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(updater->getNextVersion());
}
} else if(strcmp_P(action, PSTR("dayplot")) == 0) {
char pubTopic[192];
snprintf_P(pubTopic, 192, PSTR("%s/dayplot"), mqttConfig.publishTopic);
AmsJsonGenerator::generateDayPlotJson(ds, json, BUF_SIZE_COMMON);
bool ret = mqtt.publish(pubTopic, json);
loop();
} else if(strcmp_P(action, PSTR("monthplot")) == 0) {
char pubTopic[192];
snprintf_P(pubTopic, 192, PSTR("%s/monthplot"), mqttConfig.publishTopic);
AmsJsonGenerator::generateMonthPlotJson(ds, json, BUF_SIZE_COMMON);
bool ret = mqtt.publish(pubTopic, json);
loop();
#if defined(ESP32)
} else if(strcmp_P(action, PSTR("getconfig")) == 0) {
if(strcmp_P(action, PSTR("getconfig")) == 0) {
char pubTopic[192];
snprintf_P(pubTopic, 192, PSTR("%s/config"), mqttConfig.publishTopic);
AmsJsonGenerator::generateConfigurationJson(config, json, BUF_SIZE_COMMON);
Expand All @@ -588,26 +575,10 @@ void JsonMqttHandler::onMessage(String &topic, String &payload) {
hw->setup(&sys, &gpio);
}
}
#endif
}
#endif
}
}
} else if(payload.equals(F("fwupgrade"))) {
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(updater->getNextVersion());
}
} else if(payload.equals(F("dayplot"))) {
char pubTopic[192];
snprintf_P(pubTopic, 192, PSTR("%s/dayplot"), mqttConfig.publishTopic);
AmsJsonGenerator::generateDayPlotJson(ds, json, BUF_SIZE_COMMON);
bool ret = mqtt.publish(pubTopic, json);
loop();
} else if(payload.equals(F("monthplot"))) {
char pubTopic[192];
snprintf_P(pubTopic, 192, PSTR("%s/monthplot"), mqttConfig.publishTopic);
AmsJsonGenerator::generateMonthPlotJson(ds, json, BUF_SIZE_COMMON);
bool ret = mqtt.publish(pubTopic, json);
loop();
}
}
}
Expand Down Expand Up @@ -855,6 +826,9 @@ void JsonMqttHandler::handleConfigMessage(JsonObject& configObj) {
if(mqttObj.containsKey(F("e"))) {
newConfig.rebootMinutes = mqttObj[F("e")];
}
if(mqttObj.containsKey(F("dc"))) {
newConfig.allowDestructiveCommands = mqttObj[F("dc")];
}
config->setMqttConfig(newConfig);

if(newConfig.payloadFormat == 3) { // Domiticz
Expand Down
2 changes: 1 addition & 1 deletion src/mqtt/JsonMqttHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class JsonMqttHandler : public AmsMqttHandler {
private:
HwTools* hw;
bool hasExport = false;
AmsDataStorage* ds;
// ds is inherited from AmsMqttHandler (shared with dayplot/monthplot commands)

uint16_t appendJsonHeader(AmsData* data);
uint16_t appendJsonFooter(EnergyAccounting* ea, uint16_t pos);
Expand Down
8 changes: 1 addition & 7 deletions src/mqtt/RawMqttHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -413,11 +413,5 @@ bool RawMqttHandler::publishRaw(uint8_t* raw, size_t length) {
}

void RawMqttHandler::onMessage(String &topic, String &payload) {
if(topic == subTopic) {
if(payload.equals("fwupgrade")) {
if(strcmp(updater->getNextVersion(), FirmwareVersion::VersionString) != 0) {
updater->setTargetVersion(updater->getNextVersion());
}
}
}
handleCommand(topic, payload); // fwupgrade / reboot / factoryreset
}
2 changes: 1 addition & 1 deletion ui/dist/index.css

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions ui/dist/index.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions ui/src/routes/ConfigurationRoute.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,9 @@
<input name="qe" bind:value={configuration.q.e} type="number" min="0" max="240" class="in-l tr w-1/2"/>
</div>
</div>
<div class="my-1">
<label><input type="checkbox" name="qdc" value="true" bind:checked={configuration.q.dc} class="rounded mb-1"/> {translations.conf?.mqtt?.allowdestructive ?? "Allow destructive commands"}</label>
</div>
</div>
{/if}
{#if configuration?.q?.m == 3}
Expand Down
Loading