|
@ -7,6 +7,7 @@ |
|
|
#define __WEB_API_H__ |
|
|
#define __WEB_API_H__ |
|
|
|
|
|
|
|
|
#include "../utils/dbg.h" |
|
|
#include "../utils/dbg.h" |
|
|
|
|
|
#include "../config/config.h" |
|
|
#ifdef ESP32 |
|
|
#ifdef ESP32 |
|
|
#include "AsyncTCP.h" |
|
|
#include "AsyncTCP.h" |
|
|
#else |
|
|
#else |
|
@ -17,14 +18,15 @@ |
|
|
#include "../utils/helper.h" |
|
|
#include "../utils/helper.h" |
|
|
#include "AsyncJson.h" |
|
|
#include "AsyncJson.h" |
|
|
#include "ESPAsyncWebServer.h" |
|
|
#include "ESPAsyncWebServer.h" |
|
|
|
|
|
#include "../plugins/SML_OBIS_Parser.h" |
|
|
|
|
|
|
|
|
#if defined(F) && defined(ESP32) |
|
|
#if defined(F) && defined(ESP32) |
|
|
#undef F |
|
|
#undef F |
|
|
#define F(sl) (sl) |
|
|
#define F(sl) (sl) |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
|
|
|
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q}; |
|
|
const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; |
|
|
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR}; |
|
|
const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP}; |
|
|
|
|
|
|
|
|
template <class HMSYSTEM> |
|
|
template <class HMSYSTEM> |
|
|
class RestApi { |
|
|
class RestApi { |
|
@ -45,8 +47,8 @@ class RestApi { |
|
|
mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); |
|
|
mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); |
|
|
mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody( |
|
|
mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody( |
|
|
std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); |
|
|
std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); |
|
|
|
|
|
|
|
|
mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); |
|
|
mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); |
|
|
|
|
|
mSrv->on("/get_chartdata", HTTP_GET, std::bind(&RestApi::onGetChartData, this, std::placeholders::_1)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
uint32_t getTimezoneOffset(void) { |
|
|
uint32_t getTimezoneOffset(void) { |
|
@ -73,39 +75,38 @@ class RestApi { |
|
|
mHeapFrag = ESP.getHeapFragmentation(); |
|
|
mHeapFrag = ESP.getHeapFragmentation(); |
|
|
#endif |
|
|
#endif |
|
|
|
|
|
|
|
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); |
|
|
|
|
|
JsonObject root = response->getRoot(); |
|
|
|
|
|
|
|
|
|
|
|
String path = request->url().substring(5); |
|
|
String path = request->url().substring(5); |
|
|
if(path == "html/system") getHtmlSystem(request, root); |
|
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); |
|
|
else if(path == "html/logout") getHtmlLogout(request, root); |
|
|
JsonObject root = response->getRoot(); |
|
|
else if(path == "html/reboot") getHtmlReboot(request, root); |
|
|
if(path == "html/system") getHtmlSystem(request, root); |
|
|
else if(path == "html/save") getHtmlSave(request, root); |
|
|
else if(path == "html/logout") getHtmlLogout(request, root); |
|
|
else if(path == "system") getSysInfo(request, root); |
|
|
else if(path == "html/reboot") getHtmlReboot(request, root); |
|
|
else if(path == "generic") getGeneric(request, root); |
|
|
else if(path == "html/save") getHtmlSave(request, root); |
|
|
else if(path == "reboot") getReboot(request, root); |
|
|
else if(path == "system") getSysInfo(request, root); |
|
|
else if(path == "statistics") getStatistics(root); |
|
|
else if(path == "generic") getGeneric(request, root); |
|
|
else if(path == "inverter/list") getInverterList(root); |
|
|
else if(path == "reboot") getReboot(request, root); |
|
|
else if(path == "index") getIndex(request, root); |
|
|
else if(path == "statistics") getStatistics(root); |
|
|
else if(path == "setup") getSetup(request, root); |
|
|
else if(path == "inverter/list") getInverterList(root); |
|
|
else if(path == "setup/networks") getNetworks(root); |
|
|
else if(path == "index") getIndex(request, root); |
|
|
else if(path == "live") getLive(request, root); |
|
|
else if(path == "setup") getSetup(request, root); |
|
|
else if(path == "record/info") getRecord(root, InverterDevInform_All); |
|
|
else if(path == "setup/networks") getNetworks(root); |
|
|
else if(path == "record/alarm") getRecord(root, AlarmData); |
|
|
else if(path == "live") getLive(request, root); |
|
|
else if(path == "record/config") getRecord(root, SystemConfigPara); |
|
|
else if(path == "record/info") getRecord(root, InverterDevInform_All); |
|
|
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); |
|
|
else if(path == "record/alarm") getRecord(root, AlarmData); |
|
|
else { |
|
|
else if(path == "record/config") getRecord(root, SystemConfigPara); |
|
|
if(path.substring(0, 12) == "inverter/id/") |
|
|
else if(path == "record/live") getRecord(root, RealTimeRunData_Debug); |
|
|
getInverter(root, request->url().substring(17).toInt()); |
|
|
else { |
|
|
else |
|
|
if(path.substring(0, 12) == "inverter/id/") |
|
|
getNotFound(root, F("http://") + request->host() + F("/api/")); |
|
|
getInverter(root, request->url().substring(17).toInt()); |
|
|
} |
|
|
else |
|
|
|
|
|
getNotFound(root, F("http://") + request->host() + F("/api/")); |
|
|
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
|
|
|
} |
|
|
response->addHeader("Access-Control-Allow-Origin", "*"); |
|
|
//DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage()));
|
|
|
response->addHeader("Access-Control-Allow-Headers", "content-type"); |
|
|
response->addHeader("Access-Control-Allow-Origin", "*"); |
|
|
response->setLength(); |
|
|
response->addHeader("Access-Control-Allow-Headers", "content-type"); |
|
|
request->send(response); |
|
|
response->setLength(); |
|
|
|
|
|
request->send(response); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void onApiPost(AsyncWebServerRequest *request) { |
|
|
void onApiPost(AsyncWebServerRequest *request) { |
|
@ -159,6 +160,25 @@ class RestApi { |
|
|
ep[F("record/live")] = url + F("record/live"); |
|
|
ep[F("record/live")] = url + F("record/live"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
unsigned int get_int_length (int value) |
|
|
|
|
|
{ |
|
|
|
|
|
unsigned int length = 1; |
|
|
|
|
|
unsigned int base10 = 10, last_base10; |
|
|
|
|
|
|
|
|
|
|
|
if (value < 0) { |
|
|
|
|
|
length++; |
|
|
|
|
|
value = -value; |
|
|
|
|
|
} |
|
|
|
|
|
while ((unsigned int)value >= base10) { |
|
|
|
|
|
length++; |
|
|
|
|
|
last_base10 = base10; |
|
|
|
|
|
base10 *= 10; |
|
|
|
|
|
if (base10 <= last_base10) { |
|
|
|
|
|
break; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
return length; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
void onDwnldSetup(AsyncWebServerRequest *request) { |
|
|
void onDwnldSetup(AsyncWebServerRequest *request) { |
|
|
AsyncWebServerResponse *response; |
|
|
AsyncWebServerResponse *response; |
|
@ -189,6 +209,132 @@ class RestApi { |
|
|
fp.close(); |
|
|
fp.close(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void onGetChartData(AsyncWebServerRequest *request) { |
|
|
|
|
|
AsyncWebServerResponse *response; |
|
|
|
|
|
File ac_hist; |
|
|
|
|
|
unsigned char *ac_hist_buf, *cur_ac_hist_buf, *end_ac_hist_buf; |
|
|
|
|
|
uint16_t ac_power, cur_interval, length = 0; |
|
|
|
|
|
size_t ac_hist_size; |
|
|
|
|
|
|
|
|
|
|
|
// phase 1: count mem needed for CSV String
|
|
|
|
|
|
|
|
|
|
|
|
if ((ac_hist = mSys->open_hist()) && |
|
|
|
|
|
(ac_hist_size = ac_hist.size()) && |
|
|
|
|
|
(ac_hist_size <= (AHOY_MAX_PAC_SUN_HOUR - AHOY_MIN_PAC_SUN_HOUR) * 60 / AHOY_PAC_INTERVAL * 4) && |
|
|
|
|
|
(ac_hist_buf = (unsigned char *)malloc (ac_hist_size))) { |
|
|
|
|
|
ac_hist.read (ac_hist_buf, ac_hist_size); |
|
|
|
|
|
mSys->close_hist (ac_hist); |
|
|
|
|
|
ac_hist.close(); |
|
|
|
|
|
cur_ac_hist_buf = ac_hist_buf; |
|
|
|
|
|
end_ac_hist_buf = cur_ac_hist_buf + ac_hist_size; |
|
|
|
|
|
while (cur_ac_hist_buf < end_ac_hist_buf) { |
|
|
|
|
|
cur_interval = *cur_ac_hist_buf++; |
|
|
|
|
|
cur_interval += (*cur_ac_hist_buf++) << 8; |
|
|
|
|
|
ac_power = *cur_ac_hist_buf++; |
|
|
|
|
|
ac_power += (*cur_ac_hist_buf++) << 8; |
|
|
|
|
|
if (cur_interval < 600 / AHOY_PAC_INTERVAL) { |
|
|
|
|
|
length += 1 + 4 + 1; // +1: comma, +1: lf
|
|
|
|
|
|
} else { |
|
|
|
|
|
length += 1 + 5 + 1; |
|
|
|
|
|
} |
|
|
|
|
|
length += get_int_length (ac_power); |
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
length += 1 + 6; // reserve longest power value ,-abcde
|
|
|
|
|
|
#endif |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (mSys->get_cur_value (&cur_interval, &ac_power)) { |
|
|
|
|
|
if (cur_interval < 600 / AHOY_PAC_INTERVAL) { |
|
|
|
|
|
length += 1 + 4 + 1; |
|
|
|
|
|
} else { |
|
|
|
|
|
length += 1 + 5 + 1; |
|
|
|
|
|
} |
|
|
|
|
|
length += get_int_length (ac_power); |
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
length += 1 + 6; // reserve longest power value ,-abcde
|
|
|
|
|
|
#endif |
|
|
|
|
|
} |
|
|
|
|
|
length += sizeof (AHOY_CHARTDATA_HDR); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// phase 2: concatenate CSV string
|
|
|
|
|
|
|
|
|
|
|
|
if (length) { |
|
|
|
|
|
char *content = NULL; |
|
|
|
|
|
unsigned int index; |
|
|
|
|
|
|
|
|
|
|
|
if ((content = (char *)malloc (length))) { |
|
|
|
|
|
uint16_t minutes; |
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
int sml_power; |
|
|
|
|
|
File sml_hist = sml_open_hist (); |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
strcpy (content, AHOY_CHARTDATA_HDR); |
|
|
|
|
|
index = strlen (content); |
|
|
|
|
|
|
|
|
|
|
|
cur_ac_hist_buf = ac_hist_buf; |
|
|
|
|
|
end_ac_hist_buf = cur_ac_hist_buf + ac_hist_size; |
|
|
|
|
|
while (cur_ac_hist_buf < end_ac_hist_buf) { |
|
|
|
|
|
cur_interval = *cur_ac_hist_buf++; |
|
|
|
|
|
cur_interval += (*cur_ac_hist_buf++) << 8; |
|
|
|
|
|
ac_power = *cur_ac_hist_buf++; |
|
|
|
|
|
ac_power += (*cur_ac_hist_buf++) << 8; |
|
|
|
|
|
|
|
|
|
|
|
minutes = cur_interval * AHOY_PAC_INTERVAL; |
|
|
|
|
|
|
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
if ((sml_power = sml_find_hist_power(sml_hist, cur_interval)) == -1) { |
|
|
|
|
|
snprintf (&content[index], length - index, "\n%u:%02u,%u,", |
|
|
|
|
|
minutes / 60, minutes % 60, ac_power); |
|
|
|
|
|
} else { |
|
|
|
|
|
snprintf (&content[index], length - index, "\n%u:%02u,%u,%d", |
|
|
|
|
|
minutes / 60, minutes % 60, ac_power, sml_power); |
|
|
|
|
|
} |
|
|
|
|
|
#else |
|
|
|
|
|
snprintf (&content[index], length - index, "\n%u:%02u,%u", |
|
|
|
|
|
minutes / 60, minutes % 60, ac_power); |
|
|
|
|
|
#endif |
|
|
|
|
|
index += strlen (&content[index]); |
|
|
|
|
|
} |
|
|
|
|
|
free (ac_hist_buf); |
|
|
|
|
|
if (mSys->get_cur_value (&cur_interval, &ac_power)) { |
|
|
|
|
|
uint16_t minutes = cur_interval * AHOY_PAC_INTERVAL; |
|
|
|
|
|
|
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
if ((sml_power = sml_find_hist_power(sml_hist, cur_interval)) == -1) { |
|
|
|
|
|
snprintf (&content[index], length - index, "\n%u:%02u,%u,", |
|
|
|
|
|
minutes / 60, minutes % 60, ac_power); |
|
|
|
|
|
} else { |
|
|
|
|
|
snprintf (&content[index], length - index, "\n%u:%02u,%u,%d", |
|
|
|
|
|
minutes / 60, minutes % 60, ac_power, sml_power); |
|
|
|
|
|
} |
|
|
|
|
|
#else |
|
|
|
|
|
snprintf (&content[index], length - index, "\n%u:%02u,%u", |
|
|
|
|
|
minutes / 60, minutes % 60, ac_power); |
|
|
|
|
|
#endif |
|
|
|
|
|
index += strlen (&content[index]); |
|
|
|
|
|
} |
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
sml_close_hist (sml_hist); |
|
|
|
|
|
#endif |
|
|
|
|
|
response = request->beginResponse(200, F("text/plain"), content); |
|
|
|
|
|
free (content); |
|
|
|
|
|
} else { |
|
|
|
|
|
response = request->beginResponse(200, F("text/plain"), AHOY_CHARTDATA_HDR "\nno memory"); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
response = request->beginResponse(200, F("text/plain"), AHOY_CHARTDATA_HDR "\nno value found"); |
|
|
|
|
|
} |
|
|
|
|
|
if (response) { |
|
|
|
|
|
response->addHeader("Content-Description", "File Transfer"); |
|
|
|
|
|
response->addHeader("Content-Disposition", "attachment; filename=chartdata.csv"); |
|
|
|
|
|
request->send(response); |
|
|
|
|
|
} else { |
|
|
|
|
|
request->send(404); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
void getGeneric(AsyncWebServerRequest *request, JsonObject obj) { |
|
|
void getGeneric(AsyncWebServerRequest *request, JsonObject obj) { |
|
|
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); |
|
|
obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); |
|
|
obj[F("ts_uptime")] = mApp->getUptime(); |
|
|
obj[F("ts_uptime")] = mApp->getUptime(); |
|
@ -337,6 +483,10 @@ class RestApi { |
|
|
obj[F("version")] = String(iv->getFwVersion()); |
|
|
obj[F("version")] = String(iv->getFwVersion()); |
|
|
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit); |
|
|
obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit); |
|
|
obj[F("ts_last_success")] = rec->ts; |
|
|
obj[F("ts_last_success")] = rec->ts; |
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
// design: no value og inverter but I want this value to be displayed prominently
|
|
|
|
|
|
obj[F("grid_power")] = sml_get_obis_pac (); |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
JsonArray ch = obj.createNestedArray("ch"); |
|
|
JsonArray ch = obj.createNestedArray("ch"); |
|
|
|
|
|
|
|
@ -469,15 +619,15 @@ class RestApi { |
|
|
warn.add(F("time not set. No communication to inverter possible")); |
|
|
warn.add(F("time not set. No communication to inverter possible")); |
|
|
/*if(0 == mSys->getNumInverters())
|
|
|
/*if(0 == mSys->getNumInverters())
|
|
|
warn.add(F("no inverter configured"));*/ |
|
|
warn.add(F("no inverter configured"));*/ |
|
|
|
|
|
#ifdef AHOY_MQTT_SUPPORT |
|
|
if((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) |
|
|
if((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) |
|
|
warn.add(F("MQTT is not connected")); |
|
|
warn.add(F("MQTT is not connected")); |
|
|
|
|
|
|
|
|
JsonArray info = obj.createNestedArray(F("infos")); |
|
|
JsonArray info = obj.createNestedArray(F("infos")); |
|
|
if(mApp->getMqttIsConnected()) |
|
|
if(mApp->getMqttIsConnected()) |
|
|
info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); |
|
|
info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); |
|
|
if(mConfig->mqtt.interval > 0) |
|
|
if(mConfig->mqtt.interval > 0) |
|
|
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); |
|
|
info.add(F("MQTT publishes in a fixed interval of ") + String(mConfig->mqtt.interval) + F(" seconds")); |
|
|
|
|
|
#endif |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
void getSetup(AsyncWebServerRequest *request, JsonObject obj) { |
|
|
void getSetup(AsyncWebServerRequest *request, JsonObject obj) { |
|
@ -501,6 +651,10 @@ class RestApi { |
|
|
void getLive(AsyncWebServerRequest *request, JsonObject obj) { |
|
|
void getLive(AsyncWebServerRequest *request, JsonObject obj) { |
|
|
getGeneric(request, obj.createNestedObject(F("generic"))); |
|
|
getGeneric(request, obj.createNestedObject(F("generic"))); |
|
|
obj[F("refresh")] = mConfig->nrf.sendInterval; |
|
|
obj[F("refresh")] = mConfig->nrf.sendInterval; |
|
|
|
|
|
#ifdef AHOY_SML_OBIS_SUPPORT |
|
|
|
|
|
// additionally here for correct chart titles
|
|
|
|
|
|
obj[F("grid_power")] = sml_get_obis_pac (); |
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
for (uint8_t fld = 0; fld < sizeof(acList); fld++) { |
|
|
for (uint8_t fld = 0; fld < sizeof(acList); fld++) { |
|
|
obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]); |
|
|
obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]); |
|
@ -512,6 +666,7 @@ class RestApi { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
Inverter<> *iv; |
|
|
Inverter<> *iv; |
|
|
|
|
|
|
|
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { |
|
|
for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { |
|
|
iv = mSys->getInverterByPos(i); |
|
|
iv = mSys->getInverterByPos(i); |
|
|
bool parse = false; |
|
|
bool parse = false; |
|
@ -593,10 +748,12 @@ class RestApi { |
|
|
mApp->setTimestamp(jsonIn[F("val")]); |
|
|
mApp->setTimestamp(jsonIn[F("val")]); |
|
|
else if(F("sync_ntp") == jsonIn[F("cmd")]) |
|
|
else if(F("sync_ntp") == jsonIn[F("cmd")]) |
|
|
mApp->setTimestamp(0); // 0: update ntp flag
|
|
|
mApp->setTimestamp(0); // 0: update ntp flag
|
|
|
else if(F("serial_utc_offset") == jsonIn[F("cmd")]) |
|
|
else if(F("serial_utc_offset") == jsonIn[F("cmd")]) { |
|
|
mTimezoneOffset = jsonIn[F("val")]; |
|
|
mTimezoneOffset = jsonIn[F("val")]; |
|
|
|
|
|
#ifdef AHOY_MQTT_SUPPORT |
|
|
else if(F("discovery_cfg") == jsonIn[F("cmd")]) { |
|
|
else if(F("discovery_cfg") == jsonIn[F("cmd")]) { |
|
|
mApp->setMqttDiscoveryFlag(); // for homeassistant
|
|
|
mApp->setMqttDiscoveryFlag(); // for homeassistant
|
|
|
|
|
|
#endif |
|
|
} else { |
|
|
} else { |
|
|
jsonOut[F("error")] = F("unknown cmd"); |
|
|
jsonOut[F("error")] = F("unknown cmd"); |
|
|
return false; |
|
|
return false; |
|
|