diff --git a/User_Manual.md b/User_Manual.md index 3556c1d1..5f34676f 100644 --- a/User_Manual.md +++ b/User_Manual.md @@ -114,13 +114,13 @@ inverter/ctrl/limit_persistent_relative/0 70 ### Power Limit absolute persistent [Watts] ```mqtt -/ctrl/limit_persistent_relative/ +/ctrl/limit_persistent_absolute/ ``` with a payload `[0 .. 65535]` Example: ```mqtt -inverter/ctrl/limit_persistent_relative/0 600 +inverter/ctrl/limit_persistent_absolute/0 600 ``` ### Power Limit relative non persistent [%] @@ -136,13 +136,13 @@ inverter/ctrl/limit_nonpersistent_relative/0 70 ### Power Limit absolute non persistent [Watts] ```mqtt -/ctrl/limit_nonpersistent_relative/ +/ctrl/limit_nonpersistent_absolute/ ``` with a payload `[0 .. 65535]` Example: ```mqtt -inverter/ctrl/limit_nonpersistent_relative/0 600 +inverter/ctrl/limit_nonpersistent_absolute/0 600 ``` ## Control via REST API diff --git a/src/CHANGES.md b/src/CHANGES.md index e5b998f9..c9551461 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,13 @@ # Changelog +## 0.5.52 +* improved ahoyWifi class +* added interface class for app +* refactored web and webApi -> RestApi +* fix calcSunrise was not called every day +* added MQTT RX counter to index.html +* all values are displayed on /live even if they are 0 + ## 0.5.51 * improved scheduler, @beegee3 #483 * refactored get NTP time, @beegee3 #483 diff --git a/src/app.cpp b/src/app.cpp index b84dbe51..66acf863 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -13,9 +13,7 @@ #include "utils/sun.h" //----------------------------------------------------------------------------- -app::app() : ah::Scheduler() { - mWeb = NULL; -} +app::app() : ah::Scheduler() {} //----------------------------------------------------------------------------- @@ -72,10 +70,19 @@ void app::setup() { #endif setupLed(); - mWeb = new web(this, mConfig, &mStat, mVersion); - mWeb->setup(); - mWeb->setProtection(strlen(mConfig->sys.adminPwd) != 0); - everySec(std::bind(&web::tickSecond, mWeb)); + mWeb.setup(this, mSys, mConfig); + mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0); + everySec(std::bind(&WebType::tickSecond, &mWeb)); + + mApi.setup(this, mSys, mWeb.getWebSrvPtr(), mConfig); + /*mApi.registerCb(apiCbScanNetworks, std::bind(&app::scanAvailNetworks, this)); + #if !defined(AP_ONLY) + mApi.registerCb(apiCbMqttTxCnt, std::bind(&PubMqttType::getTxCnt, &mMqtt)); + mApi.registerCb(apiCbMqttRxCnt, std::bind(&PubMqttType::getRxCnt, &mMqtt)); + mApi.registerCb(apiCbMqttIsCon, std::bind(&PubMqttType::isConnected, &mMqtt)); + mApi.registerCb(apiCbMqttDiscvry, std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt)); + //mApi.registerCb(apiCbMqttDiscvry, std::bind(&app::setMqttDiscoveryFlag, this)); + #endif*/ // Plugins #if defined(ENA_NOKIA) || defined(ENA_SSD1306) @@ -98,11 +105,11 @@ void app::loop(void) { mWifi.loop(); #endif - mWeb->loop(); + mWeb.loop(); if (mFlagSendDiscoveryConfig) { mFlagSendDiscoveryConfig = false; - mMqtt.sendMqttDiscoveryConfig(mConfig->mqtt.topic); + mMqtt.sendDiscoveryConfig(); } mSys->Radio.loop(); @@ -154,7 +161,7 @@ void app::tickCalcSunrise(void) { } ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); - uint32_t nxtTrig = mTimestamp - ((mTimestamp + 1000) % 86400) + 86400; // next midnight + uint32_t nxtTrig = mTimestamp - ((mTimestamp - 10) % 86400) + 86400; // next midnight, -10 for safety that it is certain next day onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig); if (mConfig->mqtt.broker[0] > 0) { once(std::bind(&PubMqttType::tickerSun, &mMqtt), 1); @@ -215,7 +222,7 @@ void app::tickSend(void) { mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit); mPayload.setTxCmd(iv, iv->devControlCmd); iv->clearCmdQueue(); - iv->enqueCommand(SystemConfigPara); + iv->enqueCommand(SystemConfigPara); // read back power limit } else { uint8_t cmd = iv->getQueuedCmd(); DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket")); @@ -239,17 +246,6 @@ void app::handleIntr(void) { mSys->Radio.handleIntr(); } -//----------------------------------------------------------------------------- -void app::scanAvailNetworks(void) { - mWifi.scanAvailNetworks(); -} - -//----------------------------------------------------------------------------- -void app::getAvailNetworks(JsonObject obj) { - mWifi.getAvailNetworks(obj); -} - - //----------------------------------------------------------------------------- void app::resetSystem(void) { snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); @@ -277,8 +273,7 @@ void app::resetSystem(void) { //----------------------------------------------------------------------------- void app::mqttSubRxCb(JsonObject obj) { - if(NULL != mWeb) - mWeb->apiCtrlRequest(obj); + mApi.ctrlRequest(obj); } //----------------------------------------------------------------------------- diff --git a/src/app.h b/src/app.h index d29e6402..0b256270 100644 --- a/src/app.h +++ b/src/app.h @@ -6,12 +6,15 @@ #ifndef __APP_H__ #define __APP_H__ + #include "utils/dbg.h" #include #include #include #include +#include "appInterface.h" + #include "config/settings.h" #include "defines.h" #include "utils/crc.h" @@ -23,6 +26,7 @@ #include "hm/payload.h" #include "wifi/ahoywifi.h" #include "web/web.h" +#include "web/RestApi.h" #include "publisher/pubMqtt.h" #include "publisher/pubSerial.h" @@ -36,6 +40,8 @@ typedef HmSystem HmSystemType; typedef Payload PayloadType; +typedef Web WebType; +typedef RestApi RestApiType; typedef PubMqtt PubMqttType; typedef PubSerial PubSerialType; @@ -45,9 +51,8 @@ typedef PubSerial PubSerialType; typedef MonochromeDisplay MonoDisplayType; #endif -class web; -class app : public ah::Scheduler { +class app : public IApp, public ah::Scheduler { public: app(); ~app() {} @@ -59,35 +64,77 @@ class app : public ah::Scheduler { void saveValues(void); void resetPayload(Inverter<>* iv); bool getWifiApActive(void); - void scanAvailNetworks(void); - void getAvailNetworks(JsonObject obj); - void saveSettings(void) { - mSettings.saveSettings(); + uint32_t getUptime() { + return Scheduler::getUptime(); + } + + uint32_t getTimestamp() { + return Scheduler::getTimestamp(); + } + + bool saveSettings() { + return mSettings.saveSettings(); } bool eraseSettings(bool eraseWifi = false) { return mSettings.eraseSettings(eraseWifi); } - uint8_t getIrqPin(void) { - return mConfig->nrf.pinIrq; + statistics_t *getStatistics() { + return &mStat; } - uint64_t Serial2u64(const char *val) { - char tmp[3]; - uint64_t ret = 0ULL; - uint64_t u64; - memset(tmp, 0, 3); - for(uint8_t i = 0; i < 6; i++) { - tmp[0] = val[i*2]; - tmp[1] = val[i*2 + 1]; - if((tmp[0] == '\0') || (tmp[1] == '\0')) - break; - u64 = strtol(tmp, NULL, 16); - ret |= (u64 << ((5-i) << 3)); - } - return ret; + void scanAvailNetworks() { + mWifi.scanAvailNetworks(); + } + + void getAvailNetworks(JsonObject obj) { + mWifi.getAvailNetworks(obj); + } + + void setRebootFlag() { + mShouldReboot = true; + } + + const char *getVersion() { + return mVersion; + } + + uint32_t getSunrise() { + return mSunrise; + } + + uint32_t getSunset() { + return mSunset; + } + + bool getSettingsValid() { + return mSettings.getValid(); + } + + bool getRebootRequestState() { + return mShowRebootRequest; + } + + void setMqttDiscoveryFlag() { + mFlagSendDiscoveryConfig = true; + } + + bool getMqttIsConnected() { + return mMqtt.isConnected(); + } + + uint32_t getMqttTxCnt() { + return mMqtt.getTxCnt(); + } + + uint32_t getMqttRxCnt() { + return mMqtt.getRxCnt(); + } + + uint8_t getIrqPin(void) { + return mConfig->nrf.pinIrq; } String getTimeStr(uint32_t offset = 0) { @@ -107,21 +154,8 @@ class app : public ah::Scheduler { Scheduler::setTimestamp(newTime); } - inline uint32_t getSunrise(void) { - return mSunrise; - } - inline uint32_t getSunset(void) { - return mSunset; - } - - inline bool mqttIsConnected(void) { return mMqtt.isConnected(); } - inline bool getSettingsValid(void) { return mSettings.getValid(); } - inline bool getRebootRequestState(void) { return mShowRebootRequest; } - inline uint32_t getMqttTxCnt(void) { return mMqtt.getTxCnt(); } - HmSystemType *mSys; bool mShouldReboot; - bool mFlagSendDiscoveryConfig; private: void resetSystem(void); @@ -169,11 +203,13 @@ class app : public ah::Scheduler { } bool mUpdateNtp; + bool mFlagSendDiscoveryConfig; bool mShowRebootRequest; ahoywifi mWifi; - web *mWeb; + WebType mWeb; + RestApiType mApi; PayloadType mPayload; PubSerialType mPubSerial; diff --git a/src/appInterface.h b/src/appInterface.h new file mode 100644 index 00000000..6a611345 --- /dev/null +++ b/src/appInterface.h @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------------- +// 2022 Ahoy, https://ahoydtu.de +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +//----------------------------------------------------------------------------- + +#ifndef __IAPP_H__ +#define __IAPP_H__ + +#include "defines.h" + +// abstract interface to App. Make members of App accessible from child class +// like web or API without forward declaration +class IApp { + public: + virtual ~IApp() {} + virtual bool saveSettings() = 0; + virtual bool eraseSettings(bool eraseWifi) = 0; + virtual void setRebootFlag() = 0; + virtual const char *getVersion() = 0; + virtual statistics_t *getStatistics() = 0; + virtual void scanAvailNetworks() = 0; + virtual void getAvailNetworks(JsonObject obj) = 0; + + virtual uint32_t getUptime() = 0; + virtual uint32_t getTimestamp() = 0; + virtual uint32_t getSunrise() = 0; + virtual uint32_t getSunset() = 0; + virtual void setTimestamp(uint32_t newTime) = 0; + + virtual bool getRebootRequestState() = 0; + virtual bool getSettingsValid() = 0; + virtual void setMqttDiscoveryFlag() = 0; + + virtual bool getMqttIsConnected() = 0; + virtual uint32_t getMqttRxCnt() = 0; + virtual uint32_t getMqttTxCnt() = 0; +}; + +#endif /*__IAPP_H__*/ diff --git a/src/defines.h b/src/defines.h index d6c01e18..ac80b7a8 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 5 -#define VERSION_PATCH 51 +#define VERSION_PATCH 52 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 9a890f86..c52847b0 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -149,7 +149,7 @@ class PubMqtt { return mRxCnt; } - void sendMqttDiscoveryConfig(const char *topic) { + void sendDiscoveryConfig(void) { DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig")); char stateTopic[64], discoveryTopic[64], buffer[512], name[32], uniq_id[32]; @@ -172,14 +172,14 @@ class PubMqtt { } else { snprintf(name, 32, "%s CH%d %s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); } - snprintf(stateTopic, 64, "%s/%s/ch%d/%s", topic, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); + snprintf(stateTopic, 64, "/ch%d/%s", rec->assign[i].ch, iv->getFieldName(i, rec)); snprintf(discoveryTopic, 64, "%s/sensor/%s/ch%d_%s/config", MQTT_DISCOVERY_PREFIX, iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec)); snprintf(uniq_id, 32, "ch%d_%s", rec->assign[i].ch, iv->getFieldName(i, rec)); const char *devCls = getFieldDeviceClass(rec->assign[i].fieldId); const char *stateCls = getFieldStateClass(rec->assign[i].fieldId); doc[F("name")] = name; - doc[F("stat_t")] = stateTopic; + doc[F("stat_t")] = String(mCfgMqtt->topic) + "/" + String(iv->config->name) + String(stateTopic); doc[F("unit_of_meas")] = iv->getUnit(i, rec); doc[F("uniq_id")] = String(iv->config->serial.u64, HEX) + "_" + uniq_id; doc[F("dev")] = deviceObj; diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp index 5a9eaf22..cf0e13d3 100644 --- a/src/utils/helper.cpp +++ b/src/utils/helper.cpp @@ -39,4 +39,20 @@ namespace ah { sprintf(str, "%04d-%02d-%02d %02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t)); return String(str); } + + uint64_t Serial2u64(const char *val) { + char tmp[3]; + uint64_t ret = 0ULL; + uint64_t u64; + memset(tmp, 0, 3); + for(uint8_t i = 0; i < 6; i++) { + tmp[0] = val[i*2]; + tmp[1] = val[i*2 + 1]; + if((tmp[0] == '\0') || (tmp[1] == '\0')) + break; + u64 = strtol(tmp, NULL, 16); + ret |= (u64 << ((5-i) << 3)); + } + return ret; + } } diff --git a/src/utils/helper.h b/src/utils/helper.h index 1372fec8..460e4691 100644 --- a/src/utils/helper.h +++ b/src/utils/helper.h @@ -18,6 +18,7 @@ namespace ah { void ip2Char(uint8_t ip[], char *str); double round3(double value); String getDateTimeStr(time_t t); + uint64_t Serial2u64(const char *val); } #endif /*__HELPER_H__*/ diff --git a/src/utils/scheduler.h b/src/utils/scheduler.h index 130bccab..fc684ba9 100644 --- a/src/utils/scheduler.h +++ b/src/utils/scheduler.h @@ -103,14 +103,9 @@ namespace ah { private: inline void checkEvery(void) { - bool expired; sP *p = mStack.getFront(); while(NULL != p) { - if(mDiffSeconds >= p->d.timeout) expired = true; - else if((p->d.timeout--) == 0) expired = true; - else expired = false; - - if(expired) { + if(mDiffSeconds >= p->d.timeout) { // expired (p->d.c)(); yield(); if(0 == p->d.reload) @@ -120,8 +115,10 @@ namespace ah { p = mStack.get(p); } } - else + else { // not expired + p->d.timeout -= mDiffSeconds; p = mStack.get(p); + } } } diff --git a/src/web/RestApi.h b/src/web/RestApi.h new file mode 100644 index 00000000..94248302 --- /dev/null +++ b/src/web/RestApi.h @@ -0,0 +1,538 @@ +#ifndef __WEB_API_H__ +#define __WEB_API_H__ + +#include "../utils/dbg.h" +#ifdef ESP32 + #include "AsyncTCP.h" +#else + #include "ESPAsyncTCP.h" +#endif +#include "ESPAsyncWebServer.h" +#include "AsyncJson.h" +#include "../hm/hmSystem.h" +#include "../utils/helper.h" + +#include "../appInterface.h" + +template +class RestApi { + public: + RestApi() { + mTimezoneOffset = 0; + } + + void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) { + mApp = app; + mSrv = srv; + mSys = sys; + mConfig = config; + 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( + 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)); + } + + uint32_t getTimezoneOffset(void) { + return mTimezoneOffset; + } + + void ctrlRequest(JsonObject obj) { + /*char out[128]; + serializeJson(obj, out, 128); + DPRINTLN(DBG_INFO, "RestApi: " + String(out));*/ + DynamicJsonDocument json(128); + JsonObject dummy = json.to(); + if(obj[F("path")] == "ctrl") + setCtrl(obj, dummy); + else if(obj[F("path")] == "setup") + setSetup(obj, dummy); + } + + private: + void onApi(AsyncWebServerRequest *request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); + JsonObject root = response->getRoot(); + + Inverter<> *iv = mSys->getInverterByPos(0, false); + String path = request->url().substring(5); + if(path == "html/system") getHtmlSystem(root); + else if(path == "html/logout") getHtmlLogout(root); + else if(path == "html/save") getHtmlSave(root); + else if(path == "system") getSysInfo(root); + else if(path == "reboot") getReboot(root); + else if(path == "statistics") getStatistics(root); + else if(path == "inverter/list") getInverterList(root); + else if(path == "menu") getMenu(root); + else if(path == "index") getIndex(root); + else if(path == "setup") getSetup(root); + else if(path == "setup/networks") getNetworks(root); + else if(path == "live") getLive(root); + else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All)); + else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData)); + else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara)); + else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug)); + else + getNotFound(root, F("http://") + request->host() + F("/api/")); + + response->addHeader("Access-Control-Allow-Origin", "*"); + response->addHeader("Access-Control-Allow-Headers", "content-type"); + response->setLength(); + request->send(response); + } + + void onApiPost(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, "onApiPost"); + } + + void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { + DPRINTLN(DBG_VERBOSE, "onApiPostBody"); + DynamicJsonDocument json(200); + AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); + JsonObject root = response->getRoot(); + + DeserializationError err = deserializeJson(json, (const char *)data, len); + JsonObject obj = json.as(); + root[F("success")] = (err) ? false : true; + if(!err) { + String path = request->url().substring(5); + if(path == "ctrl") + root[F("success")] = setCtrl(obj, root); + else if(path == "setup") + root[F("success")] = setSetup(obj, root); + else { + root[F("success")] = false; + root[F("error")] = "Path not found: " + path; + } + } + else { + switch (err.code()) { + case DeserializationError::Ok: break; + case DeserializationError::InvalidInput: root[F("error")] = F("Invalid input"); break; + case DeserializationError::NoMemory: root[F("error")] = F("Not enough memory"); break; + default: root[F("error")] = F("Deserialization failed"); break; + } + } + + response->setLength(); + request->send(response); + } + + void getNotFound(JsonObject obj, String url) { + JsonObject ep = obj.createNestedObject("avail_endpoints"); + ep[F("system")] = url + F("system"); + ep[F("statistics")] = url + F("statistics"); + ep[F("inverter/list")] = url + F("inverter/list"); + ep[F("index")] = url + F("index"); + ep[F("setup")] = url + F("setup"); + ep[F("live")] = url + F("live"); + ep[F("record/info")] = url + F("record/info"); + ep[F("record/alarm")] = url + F("record/alarm"); + ep[F("record/config")] = url + F("record/config"); + ep[F("record/live")] = url + F("record/live"); + } + void onDwnldSetup(AsyncWebServerRequest *request) { + AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); + JsonObject root = response->getRoot(); + + getSetup(root); + + response->setLength(); + response->addHeader("Content-Type", "application/octet-stream"); + response->addHeader("Content-Description", "File Transfer"); + response->addHeader("Content-Disposition", "attachment; filename=ahoy_setup.json"); + request->send(response); + } + + void getSysInfo(JsonObject obj) { + obj[F("ssid")] = mConfig->sys.stationSsid; + obj[F("device_name")] = mConfig->sys.deviceName; + obj[F("version")] = String(mApp->getVersion()); + obj[F("build")] = String(AUTO_GIT_HASH); + + obj[F("ts_uptime")] = mApp->getUptime(); + obj[F("ts_now")] = mApp->getTimestamp(); + obj[F("ts_sunrise")] = mApp->getSunrise(); + obj[F("ts_sunset")] = mApp->getSunset(); + obj[F("wifi_rssi")] = WiFi.RSSI(); + obj[F("mac")] = WiFi.macAddress(); + obj[F("hostname")] = WiFi.getHostname(); + obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); + + obj[F("sdk")] = ESP.getSdkVersion(); + obj[F("cpu_freq")] = ESP.getCpuFreqMHz(); + obj[F("heap_free")] = ESP.getFreeHeap(); + obj[F("sketch_total")] = ESP.getFreeSketchSpace(); + obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb + + + getRadio(obj.createNestedObject(F("radio"))); + + #if defined(ESP32) + obj[F("heap_total")] = ESP.getHeapSize(); + obj[F("chip_revision")] = ESP.getChipRevision(); + obj[F("chip_model")] = ESP.getChipModel(); + obj[F("chip_cores")] = ESP.getChipCores(); + //obj[F("core_version")] = F("n/a"); + //obj[F("flash_size")] = F("n/a"); + //obj[F("heap_frag")] = F("n/a"); + //obj[F("max_free_blk")] = F("n/a"); + //obj[F("reboot_reason")] = F("n/a"); + #else + //obj[F("heap_total")] = F("n/a"); + //obj[F("chip_revision")] = F("n/a"); + //obj[F("chip_model")] = F("n/a"); + //obj[F("chip_cores")] = F("n/a"); + obj[F("core_version")] = ESP.getCoreVersion(); + obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb + obj[F("heap_frag")] = ESP.getHeapFragmentation(); + obj[F("max_free_blk")] = ESP.getMaxFreeBlockSize(); + obj[F("reboot_reason")] = ESP.getResetReason(); + #endif + //obj[F("littlefs_total")] = LittleFS.totalBytes(); + //obj[F("littlefs_used")] = LittleFS.usedBytes(); + + #if defined(ESP32) + obj[F("esp_type")] = F("ESP32"); + #else + obj[F("esp_type")] = F("ESP8266"); + #endif + } + + void getHtmlSystem(JsonObject obj) { + getMenu(obj.createNestedObject(F("menu"))); + getSysInfo(obj.createNestedObject(F("system"))); + obj[F("html")] = F("Factory Reset

Reboot"); + } + + void getHtmlLogout(JsonObject obj) { + getMenu(obj.createNestedObject(F("menu"))); + getSysInfo(obj.createNestedObject(F("system"))); + obj[F("refresh")] = 3; + obj[F("refresh_url")] = "/"; + obj[F("html")] = F("succesfully logged out"); + } + + void getHtmlSave(JsonObject obj) { + getMenu(obj.createNestedObject(F("menu"))); + getSysInfo(obj.createNestedObject(F("system"))); + obj[F("refresh")] = 2; + obj[F("refresh_url")] = "/setup"; + obj[F("html")] = F("settings succesfully save"); + } + + void getReboot(JsonObject obj) { + getMenu(obj.createNestedObject(F("menu"))); + getSysInfo(obj.createNestedObject(F("system"))); + obj[F("refresh")] = 10; + obj[F("refresh_url")] = "/"; + obj[F("html")] = F("reboot. Autoreload after 10 seconds"); + } + + void getStatistics(JsonObject obj) { + statistics_t *stat = mApp->getStatistics(); + obj[F("rx_success")] = stat->rxSuccess; + obj[F("rx_fail")] = stat->rxFail; + obj[F("rx_fail_answer")] = stat->rxFailNoAnser; + obj[F("frame_cnt")] = stat->frmCnt; + obj[F("tx_cnt")] = mSys->Radio.mSendCnt; + } + + void getInverterList(JsonObject obj) { + JsonArray invArr = obj.createNestedArray(F("inverter")); + + Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + if(NULL != iv) { + JsonObject obj2 = invArr.createNestedObject(); + obj2[F("id")] = i; + obj2[F("name")] = String(iv->config->name); + obj2[F("serial")] = String(iv->config->serial.u64, HEX); + obj2[F("channels")] = iv->channels; + obj2[F("version")] = String(iv->fwVersion); + + for(uint8_t j = 0; j < iv->channels; j ++) { + obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j]; + obj2[F("ch_name")][j] = iv->config->chName[j]; + } + } + } + obj[F("interval")] = String(mConfig->nrf.sendInterval); + obj[F("retries")] = String(mConfig->nrf.maxRetransPerPyld); + obj[F("max_num_inverters")] = MAX_NUM_INVERTERS; + } + + void getMqtt(JsonObject obj) { + obj[F("broker")] = String(mConfig->mqtt.broker); + obj[F("port")] = String(mConfig->mqtt.port); + obj[F("user")] = String(mConfig->mqtt.user); + obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); + obj[F("topic")] = String(mConfig->mqtt.topic); + } + + void getNtp(JsonObject obj) { + obj[F("addr")] = String(mConfig->ntp.addr); + obj[F("port")] = String(mConfig->ntp.port); + } + + void getSun(JsonObject obj) { + obj[F("lat")] = mConfig->sun.lat ? String(mConfig->sun.lat, 5) : ""; + obj[F("lon")] = mConfig->sun.lat ? String(mConfig->sun.lon, 5) : ""; + obj[F("disnightcom")] = mConfig->sun.disNightCom; + } + + void getPinout(JsonObject obj) { + obj[F("cs")] = mConfig->nrf.pinCs; + obj[F("ce")] = mConfig->nrf.pinCe; + obj[F("irq")] = mConfig->nrf.pinIrq; + obj[F("led0")] = mConfig->led.led0; + obj[F("led1")] = mConfig->led.led1; + } + + void getRadio(JsonObject obj) { + obj[F("power_level")] = mConfig->nrf.amplifierPower; + obj[F("isconnected")] = mSys->Radio.isChipConnected(); + obj[F("DataRate")] = mSys->Radio.getDataRate(); + obj[F("isPVariant")] = mSys->Radio.isPVariant(); + } + + void getSerial(JsonObject obj) { + obj[F("interval")] = (uint16_t)mConfig->serial.interval; + obj[F("show_live_data")] = mConfig->serial.showIv; + obj[F("debug")] = mConfig->serial.debug; + } + + void getStaticIp(JsonObject obj) { + char buf[16]; + ah::ip2Char(mConfig->sys.ip.ip, buf); obj[F("ip")] = String(buf); + ah::ip2Char(mConfig->sys.ip.mask, buf); obj[F("mask")] = String(buf); + ah::ip2Char(mConfig->sys.ip.dns1, buf); obj[F("dns1")] = String(buf); + ah::ip2Char(mConfig->sys.ip.dns2, buf); obj[F("dns2")] = String(buf); + ah::ip2Char(mConfig->sys.ip.gateway, buf); obj[F("gateway")] = String(buf); + } + + void getMenu(JsonObject obj) { + obj["name"][0] = "Live"; + obj["link"][0] = "/live"; + obj["name"][1] = "Serial / Control"; + obj["link"][1] = "/serial"; + obj["name"][2] = "Settings"; + obj["link"][2] = "/setup"; + obj["name"][3] = "-"; + obj["name"][4] = "REST API"; + obj["link"][4] = "/api"; + obj["trgt"][4] = "_blank"; + obj["name"][5] = "-"; + obj["name"][6] = "Update"; + obj["link"][6] = "/update"; + obj["name"][7] = "System"; + obj["link"][7] = "/system"; + obj["name"][8] = "-"; + obj["name"][9] = "Documentation"; + obj["link"][9] = "https://ahoydtu.de"; + obj["trgt"][9] = "_blank"; + if(strlen(mConfig->sys.adminPwd) > 0) { + obj["name"][10] = "-"; + obj["name"][11] = "Logout"; + obj["link"][11] = "/logout"; + } + } + + void getIndex(JsonObject obj) { + getMenu(obj.createNestedObject(F("menu"))); + getSysInfo(obj.createNestedObject(F("system"))); + getRadio(obj.createNestedObject(F("radio"))); + getStatistics(obj.createNestedObject(F("statistics"))); + obj["refresh_interval"] = mConfig->nrf.sendInterval; + + JsonArray inv = obj.createNestedArray(F("inverter")); + Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + JsonObject invObj = inv.createNestedObject(); + invObj[F("id")] = i; + invObj[F("name")] = String(iv->config->name); + invObj[F("version")] = String(iv->fwVersion); + invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp(), rec); + invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp(), rec); + invObj[F("ts_last_success")] = iv->getLastTs(rec); + } + } + + JsonArray warn = obj.createNestedArray(F("warnings")); + if(!mSys->Radio.isChipConnected()) + warn.add(F("your NRF24 module can't be reached, check the wiring and pinout")); + else if(!mSys->Radio.isPVariant()) + warn.add(F("your NRF24 module have not a plus(+), please check!")); + + if((!mApp->getMqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) + warn.add(F("MQTT is not connected")); + + JsonArray info = obj.createNestedArray(F("infos")); + if(mApp->getRebootRequestState()) + info.add(F("reboot your ESP to apply all your configuration changes!")); + if(!mApp->getSettingsValid()) + info.add(F("your settings are invalid")); + if(mApp->getMqttIsConnected()) + info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent, ") + String(mApp->getMqttRxCnt()) + F(" packets received")); + } + + void getSetup(JsonObject obj) { + getMenu(obj.createNestedObject(F("menu"))); + getSysInfo(obj.createNestedObject(F("system"))); + getInverterList(obj.createNestedObject(F("inverter"))); + getMqtt(obj.createNestedObject(F("mqtt"))); + getNtp(obj.createNestedObject(F("ntp"))); + getSun(obj.createNestedObject(F("sun"))); + getPinout(obj.createNestedObject(F("pinout"))); + getRadio(obj.createNestedObject(F("radio"))); + getSerial(obj.createNestedObject(F("serial"))); + getStaticIp(obj.createNestedObject(F("static_ip"))); + } + + void getNetworks(JsonObject obj) { + mApp->getAvailNetworks(obj); + } + + void getLive(JsonObject obj) { + getMenu(obj.createNestedObject(F("menu"))); + getSysInfo(obj.createNestedObject(F("system"))); + JsonArray invArr = obj.createNestedArray(F("inverter")); + obj["refresh_interval"] = mConfig->nrf.sendInterval; + + uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q}; + + Inverter<> *iv; + uint8_t pos; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + if(NULL != iv) { + record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); + JsonObject obj2 = invArr.createNestedObject(); + obj2[F("name")] = String(iv->config->name); + obj2[F("channels")] = iv->channels; + obj2[F("power_limit_read")] = ah::round3(iv->actPowerLimit); + obj2[F("last_alarm")] = String(iv->lastAlarmMsg); + obj2[F("ts_last_success")] = rec->ts; + + JsonArray ch = obj2.createNestedArray("ch"); + JsonArray ch0 = ch.createNestedArray(); + obj2[F("ch_names")][0] = "AC"; + for (uint8_t fld = 0; fld < sizeof(list); fld++) { + pos = (iv->getPosByChFld(CH0, list[fld], rec)); + ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; + obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + } + + for(uint8_t j = 1; j <= iv->channels; j ++) { + obj2[F("ch_names")][j] = String(iv->config->chName[j-1]); + JsonArray cur = ch.createNestedArray(); + for (uint8_t k = 0; k < 6; k++) { + switch(k) { + default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; + case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; + case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; + case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; + case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; + case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; + } + cur[k] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; + if(1 == j) { + obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + } + } + } + } + } + } + + void getRecord(JsonObject obj, record_t<> *rec) { + JsonArray invArr = obj.createNestedArray(F("inverter")); + + Inverter<> *iv; + uint8_t pos; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i); + if(NULL != iv) { + JsonArray obj2 = invArr.createNestedArray(); + for(uint8_t j = 0; j < rec->length; j++) { + byteAssign_t *assign = iv->getByteAssign(j, rec); + pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec)); + obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; + obj2[j]["unit"] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; + obj2[j]["val"] = (0xff != pos) ? String(iv->getValue(pos, rec)) : notAvail; + } + } + } + } + + bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { + Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); + if(NULL == iv) { + jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); + return false; + } + + if(F("power") == jsonIn[F("cmd")]) { + iv->devControlCmd = (jsonIn[F("val")] == 1) ? TurnOn : TurnOff; + iv->devControlRequest = true; + } else if(F("restart") == jsonIn[F("restart")]) { + iv->devControlCmd = Restart; + iv->devControlRequest = true; + } + else if(0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { + iv->powerLimit[0] = jsonIn["val"]; + if(F("limit_persistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativPersistent; + else if(F("limit_persistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutPersistent; + else if(F("limit_nonpersistent_relative") == jsonIn[F("cmd")]) + iv->powerLimit[1] = RelativNonPersistent; + else if(F("limit_nonpersistent_absolute") == jsonIn[F("cmd")]) + iv->powerLimit[1] = AbsolutNonPersistent; + iv->devControlCmd = ActivePowerContr; + iv->devControlRequest = true; + } + else { + jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as() + "'"; + return false; + } + + return true; + } + + bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { + if(F("scan_wifi") == jsonIn[F("cmd")]) { + mApp->scanAvailNetworks(); + } + else if(F("set_time") == jsonIn[F("cmd")]) + mApp->setTimestamp(jsonIn[F("val")]); + else if(F("sync_ntp") == jsonIn[F("cmd")]) + mApp->setTimestamp(0); // 0: update ntp flag + else if(F("serial_utc_offset") == jsonIn[F("cmd")]) + mTimezoneOffset = jsonIn[F("val")]; + else if(F("discovery_cfg") == jsonIn[F("cmd")]) { + mApp->setMqttDiscoveryFlag(); // for homeassistant + } + else { + jsonOut[F("error")] = F("unknown cmd"); + return false; + } + + return true; + } + + IApp *mApp; + HMSYSTEM *mSys; + AsyncWebServer *mSrv; + settings_t *mConfig; + + uint32_t mTimezoneOffset; +}; + +#endif /*__WEB_API_H__*/ diff --git a/src/web/html/index.html b/src/web/html/index.html index 6833e10d..7efe5334 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -191,15 +191,12 @@ } function tick() { + if(0 != ts) + document.getElementById("date").innerHTML = (new Date((ts+tickCnt) * 1000)).toLocaleString('de-DE'); if(++tickCnt >= 10) { tickCnt = 0; getAjax('/api/index', parse); } - else { - var dSpan = document.getElementById("date"); - if(0 != ts) - dSpan.innerHTML = (new Date((ts+tickCnt) * 1000)).toLocaleString('de-DE'); - } } function parse(obj) { diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index dc76f426..a911f79a 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -68,19 +68,17 @@ for(var j = 0; j < root.ch0_fld_names.length; j++) { var val = Math.round(iv["ch"][0][j] * 100) / 100; - if(val > 0) { - var sub = div(["subgrp"]); - sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"])); - sub.appendChild(span(root["ch0_fld_names"][j], ["info"])); - ch0.appendChild(sub); + var sub = div(["subgrp"]); + sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"])); + sub.appendChild(span(root["ch0_fld_names"][j], ["info"])); + ch0.appendChild(sub); - switch(j) { - case 2: total[j] += val; break; // P_AC - case 6: total[j] += val; break; // YieldTotal - case 7: total[j] += val; break; // YieldDay - case 8: total[j] += val; break; // P_DC - case 10: total[j] += val; break; // Q_AC - } + switch(j) { + case 2: total[j] += val; break; // P_AC + case 6: total[j] += val; break; // YieldTotal + case 7: total[j] += val; break; // YieldDay + case 8: total[j] += val; break; // P_DC + case 10: total[j] += val; break; // Q_AC } } main.appendChild(ch0); @@ -92,10 +90,8 @@ for(var j = 0; j < root.fld_names.length; j++) { var val = Math.round(iv["ch"][i][j] * 100) / 100; - if(val > 0) { - ch.appendChild(span(val + " " + span(root["fld_units"][j], ["unit"]).innerHTML, ["value"])); - ch.appendChild(span(root["fld_names"][j], ["info"])); - } + ch.appendChild(span(val + " " + span(root["fld_units"][j], ["unit"]).innerHTML, ["value"])); + ch.appendChild(span(root["fld_names"][j], ["info"])); } main.appendChild(ch); } diff --git a/src/web/web.cpp b/src/web/web.cpp deleted file mode 100644 index 0ea3c307..00000000 --- a/src/web/web.cpp +++ /dev/null @@ -1,722 +0,0 @@ -//----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ -//----------------------------------------------------------------------------- - -#if defined(ESP32) && defined(F) - #undef F - #define F(sl) (sl) -#endif - -#include "web.h" - -#include "../utils/ahoyTimer.h" -#include "../utils/helper.h" - -#include "html/h/index_html.h" -#include "html/h/login_html.h" -#include "html/h/style_css.h" -#include "html/h/api_js.h" -#include "html/h/favicon_ico.h" -#include "html/h/setup_html.h" -#include "html/h/visualization_html.h" -#include "html/h/update_html.h" -#include "html/h/serial_html.h" -#include "html/h/system_html.h" - -const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1"}; - -//----------------------------------------------------------------------------- -web::web(app *main, settings_t *config, statistics_t *stat, char version[]) { - mMain = main; - mConfig = config; - mStat = stat; - mVersion = version; - mWeb = new AsyncWebServer(80); - mEvts = new AsyncEventSource("/events"); - mApi = new webApi(mWeb, main, config, stat, version); - - mProtected = true; - mLogoutTimeout = 0; - - memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); - mSerialBufFill = 0; - mWebSerialTicker = 0; - mWebSerialInterval = 1000; // [ms] - mSerialAddTime = true; -} - - -//----------------------------------------------------------------------------- -void web::setup(void) { - DPRINTLN(DBG_VERBOSE, F("app::setup-begin")); - mWeb->begin(); - DPRINTLN(DBG_VERBOSE, F("app::setup-on")); - mWeb->on("/", HTTP_GET, std::bind(&web::onIndex, this, std::placeholders::_1)); - mWeb->on("/login", HTTP_ANY, std::bind(&web::onLogin, this, std::placeholders::_1)); - mWeb->on("/logout", HTTP_GET, std::bind(&web::onLogout, this, std::placeholders::_1)); - mWeb->on("/style.css", HTTP_GET, std::bind(&web::onCss, this, std::placeholders::_1)); - mWeb->on("/api.js", HTTP_GET, std::bind(&web::onApiJs, this, std::placeholders::_1)); - mWeb->on("/favicon.ico", HTTP_GET, std::bind(&web::onFavicon, this, std::placeholders::_1)); - mWeb->onNotFound ( std::bind(&web::showNotFound, this, std::placeholders::_1)); - mWeb->on("/reboot", HTTP_ANY, std::bind(&web::onReboot, this, std::placeholders::_1)); - mWeb->on("/system", HTTP_ANY, std::bind(&web::onSystem, this, std::placeholders::_1)); - mWeb->on("/erase", HTTP_ANY, std::bind(&web::showErase, this, std::placeholders::_1)); - mWeb->on("/factory", HTTP_ANY, std::bind(&web::showFactoryRst, this, std::placeholders::_1)); - - mWeb->on("/setup", HTTP_GET, std::bind(&web::onSetup, this, std::placeholders::_1)); - mWeb->on("/save", HTTP_ANY, std::bind(&web::showSave, this, std::placeholders::_1)); - - mWeb->on("/live", HTTP_ANY, std::bind(&web::onLive, this, std::placeholders::_1)); - mWeb->on("/api1", HTTP_POST, std::bind(&web::showWebApi, this, std::placeholders::_1)); - -#ifdef ENABLE_JSON_EP - mWeb->on("/json", HTTP_ANY, std::bind(&web::showJson, this, std::placeholders::_1)); -#endif -#ifdef ENABLE_PROMETHEUS_EP - mWeb->on("/metrics", HTTP_ANY, std::bind(&web::showMetrics, this, std::placeholders::_1)); -#endif - - mWeb->on("/update", HTTP_GET, std::bind(&web::onUpdate, this, std::placeholders::_1)); - mWeb->on("/update", HTTP_POST, std::bind(&web::showUpdate, this, std::placeholders::_1), - std::bind(&web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); - mWeb->on("/serial", HTTP_GET, std::bind(&web::onSerial, this, std::placeholders::_1)); - - - mEvts->onConnect(std::bind(&web::onConnect, this, std::placeholders::_1)); - mWeb->addHandler(mEvts); - - mApi->setup(); - - registerDebugCb(std::bind(&web::serialCb, this, std::placeholders::_1)); -} - -//----------------------------------------------------------------------------- -void web::loop(void) { - mApi->loop(); - - if(ah::checkTicker(&mWebSerialTicker, mWebSerialInterval)) { - if(mSerialBufFill > 0) { - mEvts->send(mSerialBuf, "serial", millis()); - memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); - mSerialBufFill = 0; - } - } -} - -//----------------------------------------------------------------------------- -void web::tickSecond() { - if(0 != mLogoutTimeout) { - mLogoutTimeout -= 1; - if(0 == mLogoutTimeout) { - if(strlen(mConfig->sys.adminPwd) > 0) - mProtected = true; - } - - DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout)); - } -} - -//----------------------------------------------------------------------------- -void web::setProtection(bool protect) { - mProtected = protect; -} - -//----------------------------------------------------------------------------- -void web::onConnect(AsyncEventSourceClient *client) { - DPRINTLN(DBG_VERBOSE, "onConnect"); - - if(client->lastId()) - DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId())); - - client->send("hello!", NULL, millis(), 1000); -} - - -//----------------------------------------------------------------------------- -void web::onIndex(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onIndex")); - - if(mProtected) { - request->redirect("/login"); - return; - } - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::onLogin(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onLogin")); - - if(request->args() > 0) { - if(String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { - mProtected = false; - request->redirect("/"); - } - } - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), login_html, login_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::onCss(AsyncWebServerRequest *request) { - mLogoutTimeout = LOGOUT_TIMEOUT; - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - - -//----------------------------------------------------------------------------- -void web::onApiJs(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onApiJs")); - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::onFavicon(AsyncWebServerRequest *request) { - static const char favicon_type[] PROGMEM = "image/x-icon"; - AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::showNotFound(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + request->url()); - String msg = F("File Not Found\n\nURL: "); - msg += request->url(); - msg += F("\nMethod: "); - msg += ( request->method() == HTTP_GET ) ? "GET" : "POST"; - msg += F("\nArguments: "); - msg += request->args(); - msg += "\n"; - - for(uint8_t i = 0; i < request->args(); i++ ) { - msg += " " + request->argName(i) + ": " + request->arg(i) + "\n"; - } - - request->send(404, F("text/plain"), msg); -} - - -//----------------------------------------------------------------------------- -void web::onReboot(AsyncWebServerRequest *request) { - mMain->mShouldReboot = true; - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::onSystem(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onSystem")); - - if(mProtected) { - request->redirect("/login"); - return; - } - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::onLogout(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onLogout")); - - if(mProtected) { - request->redirect("/login"); - return; - } - - mProtected = true; - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::showErase(AsyncWebServerRequest *request) { - if(mProtected) { - request->redirect("/login"); - return; - } - - DPRINTLN(DBG_VERBOSE, F("showErase")); - mMain->eraseSettings(false); - onReboot(request); -} - - -//----------------------------------------------------------------------------- -void web::showFactoryRst(AsyncWebServerRequest *request) { - if(mProtected) { - request->redirect("/login"); - return; - } - - DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); - String content = ""; - int refresh = 3; - if(request->args() > 0) { - if(request->arg("reset").toInt() == 1) { - refresh = 10; - if(mMain->eraseSettings(true)) - content = F("factory reset: success\n\nrebooting ... "); - else - content = F("factory reset: failed\n\nrebooting ... "); - } - else { - content = F("factory reset: aborted"); - refresh = 3; - } - } - else { - content = F("

Factory Reset

" - "

RESET

CANCEL

"); - refresh = 120; - } - request->send(200, F("text/html"), F("Factory Reset") + content + F("")); - if(refresh == 10) { - delay(1000); - ESP.restart(); - } -} - - -//----------------------------------------------------------------------------- -void web::onSetup(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onSetup")); - - if(mProtected) { - request->redirect("/login"); - return; - } - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), setup_html, setup_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::showSave(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("showSave")); - - if(mProtected) { - request->redirect("/login"); - return; - } - - if(request->args() > 0) { - char buf[20] = {0}; - - // general - if(request->arg("ssid") != "") - request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN); - if(request->arg("pwd") != "{PWD}") - request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN); - if(request->arg("device") != "") - request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN); - if(request->arg("adminpwd") != "{PWD}") { - request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN); - mProtected = (strlen(mConfig->sys.adminPwd) > 0); - } - - - // static ip - request->arg("ipAddr").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.ip, buf); - request->arg("ipMask").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.mask, buf); - request->arg("ipDns1").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.dns1, buf); - request->arg("ipDns2").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.dns2, buf); - request->arg("ipGateway").toCharArray(buf, 20); - ah::ip2Arr(mConfig->sys.ip.gateway, buf); - - - // inverter - Inverter<> *iv; - for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mMain->mSys->getInverterByPos(i, false); - // address - request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); - if(strlen(buf) == 0) - memset(buf, 0, 20); - iv->config->serial.u64 = mMain->Serial2u64(buf); - switch(iv->config->serial.b[4]) { - case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break; - case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break; - case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break; - default: break; - } - - // name - request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH); - - // max channel power / name - for(uint8_t j = 0; j < 4; j++) { - iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff; - request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH); - } - iv->initialized = true; - } - - if(request->arg("invInterval") != "") - mConfig->nrf.sendInterval = request->arg("invInterval").toInt(); - if(request->arg("invRetry") != "") - mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt(); - - // pinout - uint8_t pin; - for(uint8_t i = 0; i < 5; i ++) { - pin = request->arg(String(pinArgNames[i])).toInt(); - switch(i) { - default: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break; - case 1: mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_CE_PIN); break; - case 2: mConfig->nrf.pinIrq = ((pin != 0xff) ? pin : DEF_IRQ_PIN); break; - case 3: mConfig->led.led0 = pin; break; - case 4: mConfig->led.led1 = pin; break; - } - } - - // nrf24 amplifier power - mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03; - - // ntp - if(request->arg("ntpAddr") != "") { - request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN); - mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff; - } - - // sun - if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { - mConfig->sun.lat = 0.0; - mConfig->sun.lon = 0.0; - mConfig->sun.disNightCom = false; - } else { - mConfig->sun.lat = request->arg("sunLat").toFloat(); - mConfig->sun.lon = request->arg("sunLon").toFloat(); - mConfig->sun.disNightCom = (request->arg("sunDisNightCom") == "on"); - } - - // mqtt - if(request->arg("mqttAddr") != "") { - String addr = request->arg("mqttAddr"); - addr.trim(); - addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN); - } - else - mConfig->mqtt.broker[0] = '\0'; - request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); - if(request->arg("mqttPwd") != "{PWD}") - request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); - request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); - mConfig->mqtt.port = request->arg("mqttPort").toInt(); - - // serial console - if(request->arg("serIntvl") != "") { - mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff; - - mConfig->serial.debug = (request->arg("serDbg") == "on"); - mConfig->serial.showIv = (request->arg("serEn") == "on"); - // Needed to log TX buffers to serial console - mMain->mSys->Radio.mSerialDebug = mConfig->serial.debug; - } - mMain->saveSettings(); - - if(request->arg("reboot") == "on") - onReboot(request); - else { - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); - } - } -} - - -//----------------------------------------------------------------------------- -void web::onLive(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onLive")); - - if(mProtected) { - request->redirect("/login"); - return; - } - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), visualization_html, visualization_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::showWebApi(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("web::showWebApi")); - DPRINTLN(DBG_DEBUG, request->arg("plain")); - const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity. - DynamicJsonDocument response(capacity); - - // Parse JSON object - deserializeJson(response, request->arg("plain")); - // ToDo: error handling for payload - uint8_t iv_id = response["inverter"]; - uint8_t cmd = response["cmd"]; - Inverter<> *iv = mMain->mSys->getInverterByPos(iv_id); - if (NULL != iv) { - if (response["tx_request"] == (uint8_t)TX_REQ_INFO) { - // if the AlarmData is requested set the Alarm Index to the requested one - if (cmd == AlarmData || cmd == AlarmUpdate) { - // set the AlarmMesIndex for the request from user input - iv->alarmMesIndex = response["payload"]; - } - DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"])); - // process payload from web request corresponding to the cmd - iv->enqueCommand(cmd); - } - - - if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) { - if (response["cmd"] == (uint8_t)ActivePowerContr) { - uint16_t webapiPayload = response["payload"]; - uint16_t webapiPayload2 = response["payload2"]; - if (webapiPayload > 0 && webapiPayload < 10000) { - iv->devControlCmd = ActivePowerContr; - iv->powerLimit[0] = webapiPayload; - if (webapiPayload2 > 0) - iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check - else // if not set, set it to 0x0000 default - iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut - if (iv->powerLimit[1] & 0x0001) - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API")); - else - DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API")); - iv->devControlRequest = true; // queue it in the request loop - } - } - if (response["cmd"] == (uint8_t)TurnOff) { - iv->devControlCmd = TurnOff; - iv->devControlRequest = true; // queue it in the request loop - } - if (response["cmd"] == (uint8_t)TurnOn) { - iv->devControlCmd = TurnOn; - iv->devControlRequest = true; // queue it in the request loop - } - if (response["cmd"] == (uint8_t)CleanState_LockAndAlarm) { - iv->devControlCmd = CleanState_LockAndAlarm; - iv->devControlRequest = true; // queue it in the request loop - } - if (response["cmd"] == (uint8_t)Restart) { - iv->devControlCmd = Restart; - iv->devControlRequest = true; // queue it in the request loop - } - } - } - request->send(200, "text/json", "{success:true}"); -} - - -//----------------------------------------------------------------------------- -void web::onUpdate(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onUpdate")); - - /*if(mProtected) { - request->redirect("/login"); - return; - }*/ - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), update_html, update_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::showUpdate(AsyncWebServerRequest *request) { - bool reboot = !Update.hasError(); - - String html = F("UpdateUpdate: "); - if(reboot) - html += "success"; - else - html += "failed"; - html += F("

rebooting ... auto reload after 20s"); - - AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html); - response->addHeader("Connection", "close"); - request->send(response); - mMain->mShouldReboot = reboot; -} - - -//----------------------------------------------------------------------------- -void web::showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { - if(!index) { - Serial.printf("Update Start: %s\n", filename.c_str()); -#ifndef ESP32 - Update.runAsync(true); -#endif - if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { - Update.printError(Serial); - } - } - if(!Update.hasError()) { - if(Update.write(data, len) != len){ - Update.printError(Serial); - } - } - if(final) { - if(Update.end(true)) { - Serial.printf("Update Success: %uB\n", index+len); - } else { - Update.printError(Serial); - } - } -} - - -//----------------------------------------------------------------------------- -void web::onSerial(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, F("onSerial")); - - if(mProtected) { - request->redirect("/login"); - return; - } - - AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), serial_html, serial_html_len); - response->addHeader(F("Content-Encoding"), "gzip"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void web::serialCb(String msg) { - msg.replace("\r\n", ""); - if(mSerialAddTime) { - if((9 + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { - strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr(mApi->getTimezoneOffset()).c_str(), 9); - mSerialBufFill += 9; - } - else { - mSerialBufFill = 0; - mEvts->send("webSerial, buffer overflow!", "serial", millis()); - } - mSerialAddTime = false; - } - - if(msg.endsWith("")) - mSerialAddTime = true; - - uint16_t length = msg.length(); - if((length + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { - strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length); - mSerialBufFill += length; - } - else { - mSerialBufFill = 0; - mEvts->send("webSerial, buffer overflow!", "serial", millis()); - } -} - -//----------------------------------------------------------------------------- -void web::apiCtrlRequest(JsonObject obj) { - mApi->ctrlRequest(obj); -} - -//----------------------------------------------------------------------------- -#ifdef ENABLE_JSON_EP -void web::showJson(void) { - DPRINTLN(DBG_VERBOSE, F("web::showJson")); - String modJson; - - modJson = F("{\n"); - for(uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) { - Inverter<> *iv = mMain->mSys->getInverterByPos(id); - if(NULL != iv) { - char topic[40], val[25]; - snprintf(topic, 30, "\"%s\": {\n", iv->name); - modJson += String(topic); - for(uint8_t i = 0; i < iv->listLen; i++) { - snprintf(topic, 40, "\t\"ch%d/%s\"", iv->assign[i].ch, iv->getFieldName(i)); - snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i), iv->getUnit(i)); - modJson += String(topic) + ": " + String(val) + F(",\n"); - } - modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(iv->ts) + F("\"\n\t},\n"); - } - } - modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mMain->mTimestamp)) + F("\"\n}\n"); - - mWeb->send(200, F("application/json"), modJson); -} -#endif - - -//----------------------------------------------------------------------------- -#ifdef ENABLE_PROMETHEUS_EP -std::pair web::convertToPromUnits(String shortUnit) { - - if(shortUnit == "A") return {"ampere", "gauge"}; - if(shortUnit == "V") return {"volt", "gauge"}; - if(shortUnit == "%") return {"ratio", "gauge"}; - if(shortUnit == "W") return {"watt", "gauge"}; - if(shortUnit == "Wh") return {"watt_daily", "counter"}; - if(shortUnit == "kWh") return {"watt_total", "counter"}; - if(shortUnit == "°C") return {"celsius", "gauge"}; - - return {"", "gauge"}; -} - - -//----------------------------------------------------------------------------- -void web::showMetrics(void) { - DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); - String metrics; - char headline[80]; - - snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mVersion, mconfig->sys.deviceName); - metrics += "# TYPE ahoy_solar_info gauge\n" + String(headline) + "\n"; - - for(uint8_t id = 0; id < mMain->mSys->getNumInverters(); id++) { - Inverter<> *iv = mMain->mSys->getInverterByPos(id); - if(NULL != iv) { - char type[60], topic[60], val[25]; - for(uint8_t i = 0; i < iv->listLen; i++) { - uint8_t channel = iv->assign[i].ch; - if(channel == 0) { - String promUnit, promType; - std::tie(promUnit, promType) = convertToPromUnits( iv->getUnit(i) ); - snprintf(type, 60, "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i), promUnit.c_str(), promType.c_str()); - snprintf(topic, 60, "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i), promUnit.c_str(), iv->name); - snprintf(val, 25, "%.3f", iv->getValue(i)); - metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; - } - } - } - } - - mWeb->send(200, F("text/plain"), metrics); -} -#endif diff --git a/src/web/web.h b/src/web/web.h index 7560e039..5d8fe5fd 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -14,75 +14,677 @@ #include "ESPAsyncTCP.h" #endif #include "ESPAsyncWebServer.h" -#include "../app.h" -#include "webApi.h" + +#include "../appInterface.h" + +#include "../hm/hmSystem.h" +#include "../utils/ahoyTimer.h" +#include "../utils/helper.h" + +#include "html/h/index_html.h" +#include "html/h/login_html.h" +#include "html/h/style_css.h" +#include "html/h/api_js.h" +#include "html/h/favicon_ico.h" +#include "html/h/setup_html.h" +#include "html/h/visualization_html.h" +#include "html/h/update_html.h" +#include "html/h/serial_html.h" +#include "html/h/system_html.h" #define WEB_SERIAL_BUF_SIZE 2048 -class app; -class webApi; +const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1"}; -class web { +template +class Web { public: - web(app *main, settings_t *config, statistics_t *stat, char version[]); - ~web() {} + Web(void) { + mProtected = true; + mLogoutTimeout = 0; - void setup(void); - void loop(void); - void tickSecond(); + memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); + mSerialBufFill = 0; + mWebSerialTicker = 0; + mWebSerialInterval = 1000; // [ms] + mSerialAddTime = true; + } - void setProtection(bool protect); + void setup(IApp *app, HMSYSTEM *sys, settings_t *config) { + mApp = app; + mSys = sys; + mConfig = config; + mWeb = new AsyncWebServer(80); + mEvts = new AsyncEventSource("/events"); + + DPRINTLN(DBG_VERBOSE, F("app::setup-begin")); + mWeb->begin(); + DPRINTLN(DBG_VERBOSE, F("app::setup-on")); + mWeb->on("/", HTTP_GET, std::bind(&Web::onIndex, this, std::placeholders::_1)); + mWeb->on("/login", HTTP_ANY, std::bind(&Web::onLogin, this, std::placeholders::_1)); + mWeb->on("/logout", HTTP_GET, std::bind(&Web::onLogout, this, std::placeholders::_1)); + mWeb->on("/style.css", HTTP_GET, std::bind(&Web::onCss, this, std::placeholders::_1)); + mWeb->on("/api.js", HTTP_GET, std::bind(&Web::onApiJs, this, std::placeholders::_1)); + mWeb->on("/favicon.ico", HTTP_GET, std::bind(&Web::onFavicon, this, std::placeholders::_1)); + mWeb->onNotFound ( std::bind(&Web::showNotFound, this, std::placeholders::_1)); + mWeb->on("/reboot", HTTP_ANY, std::bind(&Web::onReboot, this, std::placeholders::_1)); + mWeb->on("/system", HTTP_ANY, std::bind(&Web::onSystem, this, std::placeholders::_1)); + mWeb->on("/erase", HTTP_ANY, std::bind(&Web::showErase, this, std::placeholders::_1)); + mWeb->on("/factory", HTTP_ANY, std::bind(&Web::showFactoryRst, this, std::placeholders::_1)); + + mWeb->on("/setup", HTTP_GET, std::bind(&Web::onSetup, this, std::placeholders::_1)); + mWeb->on("/save", HTTP_ANY, std::bind(&Web::showSave, this, std::placeholders::_1)); + + mWeb->on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1)); + mWeb->on("/api1", HTTP_POST, std::bind(&Web::showWebApi, this, std::placeholders::_1)); + + #ifdef ENABLE_JSON_EP + mWeb->on("/json", HTTP_ANY, std::bind(&Web::showJson, this, std::placeholders::_1)); + #endif + #ifdef ENABLE_PROMETHEUS_EP + mWeb->on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1)); + #endif + + mWeb->on("/update", HTTP_GET, std::bind(&Web::onUpdate, this, std::placeholders::_1)); + mWeb->on("/update", HTTP_POST, std::bind(&Web::showUpdate, this, std::placeholders::_1), + std::bind(&Web::showUpdate2, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + mWeb->on("/serial", HTTP_GET, std::bind(&Web::onSerial, this, std::placeholders::_1)); - void onUpdate(AsyncWebServerRequest *request); - void showUpdate(AsyncWebServerRequest *request); - void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final); + mEvts->onConnect(std::bind(&Web::onConnect, this, std::placeholders::_1)); + mWeb->addHandler(mEvts); - void serialCb(String msg); + registerDebugCb(std::bind(&Web::serialCb, this, std::placeholders::_1)); // dbg.h + } - void apiCtrlRequest(JsonObject obj); + void loop(void) { + if(ah::checkTicker(&mWebSerialTicker, mWebSerialInterval)) { + if(mSerialBufFill > 0) { + mEvts->send(mSerialBuf, "serial", millis()); + memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE); + mSerialBufFill = 0; + } + } + } + + void tickSecond() { + if(0 != mLogoutTimeout) { + mLogoutTimeout -= 1; + if(0 == mLogoutTimeout) { + if(strlen(mConfig->sys.adminPwd) > 0) + mProtected = true; + } + + DPRINTLN(DBG_DEBUG, "auto logout in " + String(mLogoutTimeout)); + } + } + + AsyncWebServer *getWebSrvPtr(void) { + return mWeb; + } + + void setProtection(bool protect) { + mProtected = protect; + } + void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { + if(!index) { + Serial.printf("Update Start: %s\n", filename.c_str()); + #ifndef ESP32 + Update.runAsync(true); + #endif + if(!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + Update.printError(Serial); + } + } + if(!Update.hasError()) { + if(Update.write(data, len) != len){ + Update.printError(Serial); + } + } + if(final) { + if(Update.end(true)) { + Serial.printf("Update Success: %uB\n", index+len); + } else { + Update.printError(Serial); + } + } + } + + void serialCb(String msg) { + msg.replace("\r\n", ""); + /*if(mSerialAddTime) { + if((9 + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { + strncpy(&mSerialBuf[mSerialBufFill], mMain->getTimeStr(mApi.getTimezoneOffset()).c_str(), 9); + mSerialBufFill += 9; + } + else { + mSerialBufFill = 0; + mEvts->send("webSerial, buffer overflow!", "serial", millis()); + } + mSerialAddTime = false; + }*/ + + // TODO: comment in + + if(msg.endsWith("")) + mSerialAddTime = true; + + uint16_t length = msg.length(); + if((length + mSerialBufFill) <= WEB_SERIAL_BUF_SIZE) { + strncpy(&mSerialBuf[mSerialBufFill], msg.c_str(), length); + mSerialBufFill += length; + } + else { + mSerialBufFill = 0; + mEvts->send("webSerial, buffer overflow!", "serial", millis()); + } + } private: - void onConnect(AsyncEventSourceClient *client); + void onUpdate(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onUpdate")); - void onIndex(AsyncWebServerRequest *request); - void onLogin(AsyncWebServerRequest *request); - void onLogout(AsyncWebServerRequest *request); - void onCss(AsyncWebServerRequest *request); - void onApiJs(AsyncWebServerRequest *request); - void onFavicon(AsyncWebServerRequest *request); - void showNotFound(AsyncWebServerRequest *request); - void onReboot(AsyncWebServerRequest *request); - void showErase(AsyncWebServerRequest *request); - void showFactoryRst(AsyncWebServerRequest *request); - void onSetup(AsyncWebServerRequest *request); - void showSave(AsyncWebServerRequest *request); + /*if(mProtected) { + request->redirect("/login"); + return; + }*/ - void onLive(AsyncWebServerRequest *request); - void showWebApi(AsyncWebServerRequest *request); + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), update_html, update_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } - void onSerial(AsyncWebServerRequest *request); - void onSystem(AsyncWebServerRequest *request); + void showUpdate(AsyncWebServerRequest *request) { + bool reboot = !Update.hasError(); + + String html = F("UpdateUpdate: "); + if(reboot) + html += "success"; + else + html += "failed"; + html += F("

rebooting ... auto reload after 20s"); + + AsyncWebServerResponse *response = request->beginResponse(200, F("text/html"), html); + response->addHeader("Connection", "close"); + request->send(response); + if(reboot) + mApp->setRebootFlag(); + } + + void onConnect(AsyncEventSourceClient *client) { + DPRINTLN(DBG_VERBOSE, "onConnect"); + + if(client->lastId()) + DPRINTLN(DBG_VERBOSE, "Client reconnected! Last message ID that it got is: " + String(client->lastId())); + + client->send("hello!", NULL, millis(), 1000); + } + + void onIndex(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onIndex")); + + if(mProtected) { + request->redirect("/login"); + return; + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), index_html, index_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onLogin(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onLogin")); + + if(request->args() > 0) { + if(String(request->arg("pwd")) == String(mConfig->sys.adminPwd)) { + mProtected = false; + request->redirect("/"); + } + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), login_html, login_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onLogout(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onLogout")); + + if(mProtected) { + request->redirect("/login"); + return; + } + + mProtected = true; + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onCss(AsyncWebServerRequest *request) { + mLogoutTimeout = LOGOUT_TIMEOUT; + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/css"), style_css, style_css_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onApiJs(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onApiJs")); + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/javascript"), api_js, api_js_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onFavicon(AsyncWebServerRequest *request) { + static const char favicon_type[] PROGMEM = "image/x-icon"; + AsyncWebServerResponse *response = request->beginResponse_P(200, favicon_type, favicon_ico, favicon_ico_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void showNotFound(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("showNotFound - ") + request->url()); + String msg = F("File Not Found\n\nURL: "); + msg += request->url(); + msg += F("\nMethod: "); + msg += ( request->method() == HTTP_GET ) ? "GET" : "POST"; + msg += F("\nArguments: "); + msg += request->args(); + msg += "\n"; + + for(uint8_t i = 0; i < request->args(); i++ ) { + msg += " " + request->argName(i) + ": " + request->arg(i) + "\n"; + } + + request->send(404, F("text/plain"), msg); + } + + void onReboot(AsyncWebServerRequest *request) { + mApp->setRebootFlag(); + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void showErase(AsyncWebServerRequest *request) { + if(mProtected) { + request->redirect("/login"); + return; + } + + DPRINTLN(DBG_VERBOSE, F("showErase")); + mApp->eraseSettings(false); + onReboot(request); + } + + void showFactoryRst(AsyncWebServerRequest *request) { + if(mProtected) { + request->redirect("/login"); + return; + } + + DPRINTLN(DBG_VERBOSE, F("showFactoryRst")); + String content = ""; + int refresh = 3; + if(request->args() > 0) { + if(request->arg("reset").toInt() == 1) { + refresh = 10; + if(mApp->eraseSettings(true)) + content = F("factory reset: success\n\nrebooting ... "); + else + content = F("factory reset: failed\n\nrebooting ... "); + } + else { + content = F("factory reset: aborted"); + refresh = 3; + } + } + else { + content = F("

Factory Reset

" + "

RESET

CANCEL

"); + refresh = 120; + } + request->send(200, F("text/html"), F("Factory Reset") + content + F("")); + if(refresh == 10) { + delay(1000); + ESP.restart(); + } + } + + void onSetup(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSetup")); + + if(mProtected) { + request->redirect("/login"); + return; + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), setup_html, setup_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void showSave(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("showSave")); + + if(mProtected) { + request->redirect("/login"); + return; + } + + if(request->args() > 0) { + char buf[20] = {0}; + + // general + if(request->arg("ssid") != "") + request->arg("ssid").toCharArray(mConfig->sys.stationSsid, SSID_LEN); + if(request->arg("pwd") != "{PWD}") + request->arg("pwd").toCharArray(mConfig->sys.stationPwd, PWD_LEN); + if(request->arg("device") != "") + request->arg("device").toCharArray(mConfig->sys.deviceName, DEVNAME_LEN); + if(request->arg("adminpwd") != "{PWD}") { + request->arg("adminpwd").toCharArray(mConfig->sys.adminPwd, PWD_LEN); + mProtected = (strlen(mConfig->sys.adminPwd) > 0); + } + + + // static ip + request->arg("ipAddr").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.ip, buf); + request->arg("ipMask").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.mask, buf); + request->arg("ipDns1").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.dns1, buf); + request->arg("ipDns2").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.dns2, buf); + request->arg("ipGateway").toCharArray(buf, 20); + ah::ip2Arr(mConfig->sys.ip.gateway, buf); + + + // inverter + Inverter<> *iv; + for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { + iv = mSys->getInverterByPos(i, false); + // address + request->arg("inv" + String(i) + "Addr").toCharArray(buf, 20); + if(strlen(buf) == 0) + memset(buf, 0, 20); + iv->config->serial.u64 = ah::Serial2u64(buf); + switch(iv->config->serial.b[4]) { + case 0x21: iv->type = INV_TYPE_1CH; iv->channels = 1; break; + case 0x41: iv->type = INV_TYPE_2CH; iv->channels = 2; break; + case 0x61: iv->type = INV_TYPE_4CH; iv->channels = 4; break; + default: break; + } + + // name + request->arg("inv" + String(i) + "Name").toCharArray(iv->config->name, MAX_NAME_LENGTH); + + // max channel power / name + for(uint8_t j = 0; j < 4; j++) { + iv->config->chMaxPwr[j] = request->arg("inv" + String(i) + "ModPwr" + String(j)).toInt() & 0xffff; + request->arg("inv" + String(i) + "ModName" + String(j)).toCharArray(iv->config->chName[j], MAX_NAME_LENGTH); + } + iv->initialized = true; + } + + if(request->arg("invInterval") != "") + mConfig->nrf.sendInterval = request->arg("invInterval").toInt(); + if(request->arg("invRetry") != "") + mConfig->nrf.maxRetransPerPyld = request->arg("invRetry").toInt(); + + // pinout + uint8_t pin; + for(uint8_t i = 0; i < 5; i ++) { + pin = request->arg(String(pinArgNames[i])).toInt(); + switch(i) { + default: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_CS_PIN); break; + case 1: mConfig->nrf.pinCe = ((pin != 0xff) ? pin : DEF_CE_PIN); break; + case 2: mConfig->nrf.pinIrq = ((pin != 0xff) ? pin : DEF_IRQ_PIN); break; + case 3: mConfig->led.led0 = pin; break; + case 4: mConfig->led.led1 = pin; break; + } + } + + // nrf24 amplifier power + mConfig->nrf.amplifierPower = request->arg("rf24Power").toInt() & 0x03; + + // ntp + if(request->arg("ntpAddr") != "") { + request->arg("ntpAddr").toCharArray(mConfig->ntp.addr, NTP_ADDR_LEN); + mConfig->ntp.port = request->arg("ntpPort").toInt() & 0xffff; + } + + // sun + if(request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { + mConfig->sun.lat = 0.0; + mConfig->sun.lon = 0.0; + mConfig->sun.disNightCom = false; + } else { + mConfig->sun.lat = request->arg("sunLat").toFloat(); + mConfig->sun.lon = request->arg("sunLon").toFloat(); + mConfig->sun.disNightCom = (request->arg("sunDisNightCom") == "on"); + } + + // mqtt + if(request->arg("mqttAddr") != "") { + String addr = request->arg("mqttAddr"); + addr.trim(); + addr.toCharArray(mConfig->mqtt.broker, MQTT_ADDR_LEN); + } + else + mConfig->mqtt.broker[0] = '\0'; + request->arg("mqttUser").toCharArray(mConfig->mqtt.user, MQTT_USER_LEN); + if(request->arg("mqttPwd") != "{PWD}") + request->arg("mqttPwd").toCharArray(mConfig->mqtt.pwd, MQTT_PWD_LEN); + request->arg("mqttTopic").toCharArray(mConfig->mqtt.topic, MQTT_TOPIC_LEN); + mConfig->mqtt.port = request->arg("mqttPort").toInt(); + + // serial console + if(request->arg("serIntvl") != "") { + mConfig->serial.interval = request->arg("serIntvl").toInt() & 0xffff; + + mConfig->serial.debug = (request->arg("serDbg") == "on"); + mConfig->serial.showIv = (request->arg("serEn") == "on"); + // Needed to log TX buffers to serial console + mSys->Radio.mSerialDebug = mConfig->serial.debug; + } + mApp->saveSettings(); + + if(request->arg("reboot") == "on") + onReboot(request); + else { + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + } + } + + void onLive(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onLive")); + + if(mProtected) { + request->redirect("/login"); + return; + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), visualization_html, visualization_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void showWebApi(AsyncWebServerRequest *request) { + // TODO: remove + DPRINTLN(DBG_VERBOSE, F("web::showWebApi")); + DPRINTLN(DBG_DEBUG, request->arg("plain")); + const size_t capacity = 200; // Use arduinojson.org/assistant to compute the capacity. + DynamicJsonDocument response(capacity); + + // Parse JSON object + deserializeJson(response, request->arg("plain")); + // ToDo: error handling for payload + uint8_t iv_id = response["inverter"]; + uint8_t cmd = response["cmd"]; + Inverter<> *iv = mSys->getInverterByPos(iv_id); + if (NULL != iv) { + if (response["tx_request"] == (uint8_t)TX_REQ_INFO) { + // if the AlarmData is requested set the Alarm Index to the requested one + if (cmd == AlarmData || cmd == AlarmUpdate) { + // set the AlarmMesIndex for the request from user input + iv->alarmMesIndex = response["payload"]; + } + DPRINTLN(DBG_INFO, F("Will make tx-request 0x15 with subcmd ") + String(cmd) + F(" and payload ") + String((uint16_t) response["payload"])); + // process payload from web request corresponding to the cmd + iv->enqueCommand(cmd); + } + + + if (response["tx_request"] == (uint8_t)TX_REQ_DEVCONTROL) { + if (response["cmd"] == (uint8_t)ActivePowerContr) { + uint16_t webapiPayload = response["payload"]; + uint16_t webapiPayload2 = response["payload2"]; + if (webapiPayload > 0 && webapiPayload < 10000) { + iv->devControlCmd = ActivePowerContr; + iv->powerLimit[0] = webapiPayload; + if (webapiPayload2 > 0) + iv->powerLimit[1] = webapiPayload2; // dev option, no sanity check + else // if not set, set it to 0x0000 default + iv->powerLimit[1] = AbsolutNonPersistent; // payload will be seted temporary in Watt absolut + if (iv->powerLimit[1] & 0x0001) + DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("% via REST API")); + else + DPRINTLN(DBG_INFO, F("Power limit for inverter ") + String(iv->id) + F(" set to ") + String(iv->powerLimit[0]) + F("W via REST API")); + iv->devControlRequest = true; // queue it in the request loop + } + } + if (response["cmd"] == (uint8_t)TurnOff) { + iv->devControlCmd = TurnOff; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)TurnOn) { + iv->devControlCmd = TurnOn; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)CleanState_LockAndAlarm) { + iv->devControlCmd = CleanState_LockAndAlarm; + iv->devControlRequest = true; // queue it in the request loop + } + if (response["cmd"] == (uint8_t)Restart) { + iv->devControlCmd = Restart; + iv->devControlRequest = true; // queue it in the request loop + } + } + } + request->send(200, "text/json", "{success:true}"); + } + + void onSerial(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSerial")); + + if(mProtected) { + request->redirect("/login"); + return; + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), serial_html, serial_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } + + void onSystem(AsyncWebServerRequest *request) { + DPRINTLN(DBG_VERBOSE, F("onSystem")); + + if(mProtected) { + request->redirect("/login"); + return; + } + + AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html"), system_html, system_html_len); + response->addHeader(F("Content-Encoding"), "gzip"); + request->send(response); + } #ifdef ENABLE_JSON_EP - void showJson(void); + void showJson(void) { + DPRINTLN(DBG_VERBOSE, F("web::showJson")); + String modJson; + + modJson = F("{\n"); + for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if(NULL != iv) { + char topic[40], val[25]; + snprintf(topic, 30, "\"%s\": {\n", iv->name); + modJson += String(topic); + for(uint8_t i = 0; i < iv->listLen; i++) { + snprintf(topic, 40, "\t\"ch%d/%s\"", iv->assign[i].ch, iv->getFieldName(i)); + snprintf(val, 25, "[%.3f, \"%s\"]", iv->getValue(i), iv->getUnit(i)); + modJson += String(topic) + ": " + String(val) + F(",\n"); + } + modJson += F("\t\"last_msg\": \"") + ah::getDateTimeStr(iv->ts) + F("\"\n\t},\n"); + } + } + modJson += F("\"json_ts\": \"") + String(ah::getDateTimeStr(mMain->mTimestamp)) + F("\"\n}\n"); + + mWeb->send(200, F("application/json"), modJson); + } #endif #ifdef ENABLE_PROMETHEUS_EP - void showMetrics(void); - std::pair convertToPromUnits(String shortUnit); + void showMetrics(void) { + DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); + String metrics; + char headline[80]; + + snprintf(headline, 80, "ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1", mApp->getVersion(), mconfig->sys.deviceName); + metrics += "# TYPE ahoy_solar_info gauge\n" + String(headline) + "\n"; + + for(uint8_t id = 0; id < mSys->getNumInverters(); id++) { + Inverter<> *iv = mSys->getInverterByPos(id); + if(NULL != iv) { + char type[60], topic[60], val[25]; + for(uint8_t i = 0; i < iv->listLen; i++) { + uint8_t channel = iv->assign[i].ch; + if(channel == 0) { + String promUnit, promType; + std::tie(promUnit, promType) = convertToPromUnits( iv->getUnit(i) ); + snprintf(type, 60, "# TYPE ahoy_solar_%s_%s %s", iv->getFieldName(i), promUnit.c_str(), promType.c_str()); + snprintf(topic, 60, "ahoy_solar_%s_%s{inverter=\"%s\"}", iv->getFieldName(i), promUnit.c_str(), iv->name); + snprintf(val, 25, "%.3f", iv->getValue(i)); + metrics += String(type) + "\n" + String(topic) + " " + String(val) + "\n"; + } + } + } + } + + mWeb->send(200, F("text/plain"), metrics); + } + + std::pair convertToPromUnits(String shortUnit) { + if(shortUnit == "A") return {"ampere", "gauge"}; + if(shortUnit == "V") return {"volt", "gauge"}; + if(shortUnit == "%") return {"ratio", "gauge"}; + if(shortUnit == "W") return {"watt", "gauge"}; + if(shortUnit == "Wh") return {"watt_daily", "counter"}; + if(shortUnit == "kWh") return {"watt_total", "counter"}; + if(shortUnit == "°C") return {"celsius", "gauge"}; + + return {"", "gauge"}; + } #endif AsyncWebServer *mWeb; AsyncEventSource *mEvts; bool mProtected; uint32_t mLogoutTimeout; + IApp *mApp; + HMSYSTEM *mSys; settings_t *mConfig; - statistics_t *mStat; - char *mVersion; - app *mMain; - webApi *mApi; bool mSerialAddTime; char mSerialBuf[WEB_SERIAL_BUF_SIZE]; diff --git a/src/web/webApi.cpp b/src/web/webApi.cpp deleted file mode 100644 index 68c32ca6..00000000 --- a/src/web/webApi.cpp +++ /dev/null @@ -1,574 +0,0 @@ -//----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ -//----------------------------------------------------------------------------- - -#if defined(ESP32) && defined(F) - #undef F - #define F(sl) (sl) -#endif - -#include "webApi.h" - -//----------------------------------------------------------------------------- -webApi::webApi(AsyncWebServer *srv, app *app, settings_t *config, statistics_t *stat, char version[]) { - mSrv = srv; - mApp = app; - mConfig = config; - mStat = stat; - mVersion = version; - - mTimezoneOffset = 0; -} - -//----------------------------------------------------------------------------- -void webApi::setup(void) { - mSrv->on("/api", HTTP_GET, std::bind(&webApi::onApi, this, std::placeholders::_1)); - mSrv->on("/api", HTTP_POST, std::bind(&webApi::onApiPost, this, std::placeholders::_1)).onBody( - std::bind(&webApi::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(&webApi::onDwnldSetup, this, std::placeholders::_1)); -} - -//----------------------------------------------------------------------------- -void webApi::loop(void) { -} - -//----------------------------------------------------------------------------- -void webApi::ctrlRequest(JsonObject obj) { - /*char out[128]; - serializeJson(obj, out, 128); - DPRINTLN(DBG_INFO, "webApi: " + String(out));*/ - DynamicJsonDocument json(128); - JsonObject dummy = json.to(); - if(obj[F("path")] == "ctrl") - setCtrl(obj, dummy); - else if(obj[F("path")] == "setup") - setSetup(obj, dummy); -} - - -//----------------------------------------------------------------------------- -void webApi::onApi(AsyncWebServerRequest *request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); - JsonObject root = response->getRoot(); - - Inverter<> *iv = mApp->mSys->getInverterByPos(0, false); - String path = request->url().substring(5); - if(path == "html/system") getHtmlSystem(root); - else if(path == "html/logout") getHtmlLogout(root); - else if(path == "html/save") getHtmlSave(root); - else if(path == "system") getSysInfo(root); - else if(path == "reboot") getReboot(root); - else if(path == "statistics") getStatistics(root); - else if(path == "inverter/list") getInverterList(root); - else if(path == "menu") getMenu(root); - else if(path == "index") getIndex(root); - else if(path == "setup") getSetup(root); - else if(path == "setup/networks") getNetworks(root); - else if(path == "live") getLive(root); - else if(path == "record/info") getRecord(root, iv->getRecordStruct(InverterDevInform_All)); - else if(path == "record/alarm") getRecord(root, iv->getRecordStruct(AlarmData)); - else if(path == "record/config") getRecord(root, iv->getRecordStruct(SystemConfigPara)); - else if(path == "record/live") getRecord(root, iv->getRecordStruct(RealTimeRunData_Debug)); - else - getNotFound(root, F("http://") + request->host() + F("/api/")); - - response->addHeader("Access-Control-Allow-Origin", "*"); - response->addHeader("Access-Control-Allow-Headers", "content-type"); - response->setLength(); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void webApi::onApiPost(AsyncWebServerRequest *request) { - DPRINTLN(DBG_VERBOSE, "onApiPost"); -} - - -//----------------------------------------------------------------------------- -void webApi::onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { - DPRINTLN(DBG_VERBOSE, "onApiPostBody"); - DynamicJsonDocument json(200); - AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); - JsonObject root = response->getRoot(); - - DeserializationError err = deserializeJson(json, (const char *)data, len); - JsonObject obj = json.as(); - root[F("success")] = (err) ? false : true; - if(!err) { - String path = request->url().substring(5); - if(path == "ctrl") - root[F("success")] = setCtrl(obj, root); - else if(path == "setup") - root[F("success")] = setSetup(obj, root); - else { - root[F("success")] = false; - root[F("error")] = "Path not found: " + path; - } - } - else { - switch (err.code()) { - case DeserializationError::Ok: break; - case DeserializationError::InvalidInput: root[F("error")] = F("Invalid input"); break; - case DeserializationError::NoMemory: root[F("error")] = F("Not enough memory"); break; - default: root[F("error")] = F("Deserialization failed"); break; - } - } - - response->setLength(); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void webApi::getNotFound(JsonObject obj, String url) { - JsonObject ep = obj.createNestedObject("avail_endpoints"); - ep[F("system")] = url + F("system"); - ep[F("statistics")] = url + F("statistics"); - ep[F("inverter/list")] = url + F("inverter/list"); - ep[F("index")] = url + F("index"); - ep[F("setup")] = url + F("setup"); - ep[F("live")] = url + F("live"); - ep[F("record/info")] = url + F("record/info"); - ep[F("record/alarm")] = url + F("record/alarm"); - ep[F("record/config")] = url + F("record/config"); - ep[F("record/live")] = url + F("record/live"); -} - - -//----------------------------------------------------------------------------- -void webApi::onDwnldSetup(AsyncWebServerRequest *request) { - AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192); - JsonObject root = response->getRoot(); - - getSetup(root); - - response->setLength(); - response->addHeader("Content-Type", "application/octet-stream"); - response->addHeader("Content-Description", "File Transfer"); - response->addHeader("Content-Disposition", "attachment; filename=ahoy_setup.json"); - request->send(response); -} - - -//----------------------------------------------------------------------------- -void webApi::getSysInfo(JsonObject obj) { - obj[F("ssid")] = mConfig->sys.stationSsid; - obj[F("device_name")] = mConfig->sys.deviceName; - obj[F("version")] = String(mVersion); - obj[F("build")] = String(AUTO_GIT_HASH); - obj[F("ts_uptime")] = mApp->getUptime(); - obj[F("ts_now")] = mApp->getTimestamp(); - obj[F("ts_sunrise")] = mApp->getSunrise(); - obj[F("ts_sunset")] = mApp->getSunset(); - obj[F("wifi_rssi")] = WiFi.RSSI(); - obj[F("mac")] = WiFi.macAddress(); - obj[F("hostname")] = WiFi.getHostname(); - obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); - - obj[F("sdk")] = ESP.getSdkVersion(); - obj[F("cpu_freq")] = ESP.getCpuFreqMHz(); - obj[F("heap_free")] = ESP.getFreeHeap(); - obj[F("sketch_total")] = ESP.getFreeSketchSpace(); - obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb - - - getRadio(obj.createNestedObject(F("radio"))); - -#if defined(ESP32) - obj[F("heap_total")] = ESP.getHeapSize(); - obj[F("chip_revision")] = ESP.getChipRevision(); - obj[F("chip_model")] = ESP.getChipModel(); - obj[F("chip_cores")] = ESP.getChipCores(); - //obj[F("core_version")] = F("n/a"); - //obj[F("flash_size")] = F("n/a"); - //obj[F("heap_frag")] = F("n/a"); - //obj[F("max_free_blk")] = F("n/a"); - //obj[F("reboot_reason")] = F("n/a"); -#else - //obj[F("heap_total")] = F("n/a"); - //obj[F("chip_revision")] = F("n/a"); - //obj[F("chip_model")] = F("n/a"); - //obj[F("chip_cores")] = F("n/a"); - obj[F("core_version")] = ESP.getCoreVersion(); - obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb - obj[F("heap_frag")] = ESP.getHeapFragmentation(); - obj[F("max_free_blk")] = ESP.getMaxFreeBlockSize(); - obj[F("reboot_reason")] = ESP.getResetReason(); -#endif - //obj[F("littlefs_total")] = LittleFS.totalBytes(); - //obj[F("littlefs_used")] = LittleFS.usedBytes(); - -#if defined(ESP32) - obj[F("esp_type")] = F("ESP32"); -#else - obj[F("esp_type")] = F("ESP8266"); -#endif -} - - -//----------------------------------------------------------------------------- -void webApi::getHtmlSystem(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); - getSysInfo(obj.createNestedObject(F("system"))); - obj[F("html")] = F("Factory Reset

Reboot"); -} - - -//----------------------------------------------------------------------------- -void webApi::getHtmlLogout(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); - getSysInfo(obj.createNestedObject(F("system"))); - obj[F("refresh")] = 3; - obj[F("refresh_url")] = "/"; - obj[F("html")] = F("succesfully logged out"); -} - - -//----------------------------------------------------------------------------- -void webApi::getHtmlSave(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); - getSysInfo(obj.createNestedObject(F("system"))); - obj[F("refresh")] = 2; - obj[F("refresh_url")] = "/setup"; - obj[F("html")] = F("settings succesfully save"); -} - - -//----------------------------------------------------------------------------- -void webApi::getReboot(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); - getSysInfo(obj.createNestedObject(F("system"))); - obj[F("refresh")] = 10; - obj[F("refresh_url")] = "/"; - obj[F("html")] = F("reboot. Autoreload after 10 seconds"); -} - - -//----------------------------------------------------------------------------- -void webApi::getStatistics(JsonObject obj) { - obj[F("rx_success")] = mStat->rxSuccess; - obj[F("rx_fail")] = mStat->rxFail; - obj[F("rx_fail_answer")] = mStat->rxFailNoAnser; - obj[F("frame_cnt")] = mStat->frmCnt; - obj[F("tx_cnt")] = mApp->mSys->Radio.mSendCnt; -} - - -//----------------------------------------------------------------------------- -void webApi::getInverterList(JsonObject obj) { - JsonArray invArr = obj.createNestedArray(F("inverter")); - - Inverter<> *iv; - for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mApp->mSys->getInverterByPos(i); - if(NULL != iv) { - JsonObject obj2 = invArr.createNestedObject(); - obj2[F("id")] = i; - obj2[F("name")] = String(iv->config->name); - obj2[F("serial")] = String(iv->config->serial.u64, HEX); - obj2[F("channels")] = iv->channels; - obj2[F("version")] = String(iv->fwVersion); - - for(uint8_t j = 0; j < iv->channels; j ++) { - obj2[F("ch_max_power")][j] = iv->config->chMaxPwr[j]; - obj2[F("ch_name")][j] = iv->config->chName[j]; - } - } - } - obj[F("interval")] = String(mConfig->nrf.sendInterval); - obj[F("retries")] = String(mConfig->nrf.maxRetransPerPyld); - obj[F("max_num_inverters")] = MAX_NUM_INVERTERS; -} - - -//----------------------------------------------------------------------------- -void webApi::getMqtt(JsonObject obj) { - obj[F("broker")] = String(mConfig->mqtt.broker); - obj[F("port")] = String(mConfig->mqtt.port); - obj[F("user")] = String(mConfig->mqtt.user); - obj[F("pwd")] = (strlen(mConfig->mqtt.pwd) > 0) ? F("{PWD}") : String(""); - obj[F("topic")] = String(mConfig->mqtt.topic); -} - - -//----------------------------------------------------------------------------- -void webApi::getNtp(JsonObject obj) { - obj[F("addr")] = String(mConfig->ntp.addr); - obj[F("port")] = String(mConfig->ntp.port); -} - -//----------------------------------------------------------------------------- -void webApi::getSun(JsonObject obj) { - obj[F("lat")] = mConfig->sun.lat ? String(mConfig->sun.lat, 5) : ""; - obj[F("lon")] = mConfig->sun.lat ? String(mConfig->sun.lon, 5) : ""; - obj[F("disnightcom")] = mConfig->sun.disNightCom; -} - - -//----------------------------------------------------------------------------- -void webApi::getPinout(JsonObject obj) { - obj[F("cs")] = mConfig->nrf.pinCs; - obj[F("ce")] = mConfig->nrf.pinCe; - obj[F("irq")] = mConfig->nrf.pinIrq; - obj[F("led0")] = mConfig->led.led0; - obj[F("led1")] = mConfig->led.led1; -} - - -//----------------------------------------------------------------------------- -void webApi::getRadio(JsonObject obj) { - obj[F("power_level")] = mConfig->nrf.amplifierPower; - obj[F("isconnected")] = mApp->mSys->Radio.isChipConnected(); - obj[F("DataRate")] = mApp->mSys->Radio.getDataRate(); - obj[F("isPVariant")] = mApp->mSys->Radio.isPVariant(); -} - - -//----------------------------------------------------------------------------- -void webApi::getSerial(JsonObject obj) { - obj[F("interval")] = (uint16_t)mConfig->serial.interval; - obj[F("show_live_data")] = mConfig->serial.showIv; - obj[F("debug")] = mConfig->serial.debug; -} - - -//----------------------------------------------------------------------------- -void webApi::getStaticIp(JsonObject obj) { - char buf[16]; - ah::ip2Char(mConfig->sys.ip.ip, buf); obj[F("ip")] = String(buf); - ah::ip2Char(mConfig->sys.ip.mask, buf); obj[F("mask")] = String(buf); - ah::ip2Char(mConfig->sys.ip.dns1, buf); obj[F("dns1")] = String(buf); - ah::ip2Char(mConfig->sys.ip.dns2, buf); obj[F("dns2")] = String(buf); - ah::ip2Char(mConfig->sys.ip.gateway, buf); obj[F("gateway")] = String(buf); -} - - -//----------------------------------------------------------------------------- -void webApi::getMenu(JsonObject obj) { - obj["name"][0] = "Live"; - obj["link"][0] = "/live"; - obj["name"][1] = "Serial / Control"; - obj["link"][1] = "/serial"; - obj["name"][2] = "Settings"; - obj["link"][2] = "/setup"; - obj["name"][3] = "-"; - obj["name"][4] = "REST API"; - obj["link"][4] = "/api"; - obj["trgt"][4] = "_blank"; - obj["name"][5] = "-"; - obj["name"][6] = "Update"; - obj["link"][6] = "/update"; - obj["name"][7] = "System"; - obj["link"][7] = "/system"; - obj["name"][8] = "-"; - obj["name"][9] = "Documentation"; - obj["link"][9] = "https://ahoydtu.de"; - obj["trgt"][9] = "_blank"; - if(strlen(mConfig->sys.adminPwd) > 0) { - obj["name"][10] = "-"; - obj["name"][11] = "Logout"; - obj["link"][11] = "/logout"; - } -} - - -//----------------------------------------------------------------------------- -void webApi::getIndex(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); - getSysInfo(obj.createNestedObject(F("system"))); - getRadio(obj.createNestedObject(F("radio"))); - getStatistics(obj.createNestedObject(F("statistics"))); - obj["refresh_interval"] = mConfig->nrf.sendInterval; - - JsonArray inv = obj.createNestedArray(F("inverter")); - Inverter<> *iv; - for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mApp->mSys->getInverterByPos(i); - if(NULL != iv) { - record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - JsonObject invObj = inv.createNestedObject(); - invObj[F("id")] = i; - invObj[F("name")] = String(iv->config->name); - invObj[F("version")] = String(iv->fwVersion); - invObj[F("is_avail")] = iv->isAvailable(mApp->getTimestamp(), rec); - invObj[F("is_producing")] = iv->isProducing(mApp->getTimestamp(), rec); - invObj[F("ts_last_success")] = iv->getLastTs(rec); - } - } - - JsonArray warn = obj.createNestedArray(F("warnings")); - if(!mApp->mSys->Radio.isChipConnected()) - warn.add(F("your NRF24 module can't be reached, check the wiring and pinout")); - else if(!mApp->mSys->Radio.isPVariant()) - warn.add(F("your NRF24 module have not a plus(+), please check!")); - - if((!mApp->mqttIsConnected()) && (String(mConfig->mqtt.broker).length() > 0)) - warn.add(F("MQTT is not connected")); - - JsonArray info = obj.createNestedArray(F("infos")); - if(mApp->getRebootRequestState()) - info.add(F("reboot your ESP to apply all your configuration changes!")); - if(!mApp->getSettingsValid()) - info.add(F("your settings are invalid")); - if(mApp->mqttIsConnected()) - info.add(F("MQTT is connected, ") + String(mApp->getMqttTxCnt()) + F(" packets sent")); -} - - -//----------------------------------------------------------------------------- -void webApi::getSetup(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); - getSysInfo(obj.createNestedObject(F("system"))); - getInverterList(obj.createNestedObject(F("inverter"))); - getMqtt(obj.createNestedObject(F("mqtt"))); - getNtp(obj.createNestedObject(F("ntp"))); - getSun(obj.createNestedObject(F("sun"))); - getPinout(obj.createNestedObject(F("pinout"))); - getRadio(obj.createNestedObject(F("radio"))); - getSerial(obj.createNestedObject(F("serial"))); - getStaticIp(obj.createNestedObject(F("static_ip"))); -} - - -//----------------------------------------------------------------------------- -void webApi::getNetworks(JsonObject obj) { - mApp->getAvailNetworks(obj); -} - - -//----------------------------------------------------------------------------- -void webApi::getLive(JsonObject obj) { - getMenu(obj.createNestedObject(F("menu"))); - getSysInfo(obj.createNestedObject(F("system"))); - JsonArray invArr = obj.createNestedArray(F("inverter")); - obj["refresh_interval"] = mConfig->nrf.sendInterval; - - uint8_t list[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q}; - - Inverter<> *iv; - uint8_t pos; - for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mApp->mSys->getInverterByPos(i); - if(NULL != iv) { - record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); - JsonObject obj2 = invArr.createNestedObject(); - obj2[F("name")] = String(iv->config->name); - obj2[F("channels")] = iv->channels; - obj2[F("power_limit_read")] = ah::round3(iv->actPowerLimit); - obj2[F("last_alarm")] = String(iv->lastAlarmMsg); - obj2[F("ts_last_success")] = rec->ts; - - JsonArray ch = obj2.createNestedArray("ch"); - JsonArray ch0 = ch.createNestedArray(); - obj2[F("ch_names")][0] = "AC"; - for (uint8_t fld = 0; fld < sizeof(list); fld++) { - pos = (iv->getPosByChFld(CH0, list[fld], rec)); - ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; - obj[F("ch0_fld_units")][fld] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; - obj[F("ch0_fld_names")][fld] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; - } - - for(uint8_t j = 1; j <= iv->channels; j ++) { - obj2[F("ch_names")][j] = String(iv->config->chName[j-1]); - JsonArray cur = ch.createNestedArray(); - for (uint8_t k = 0; k < 6; k++) { - switch(k) { - default: pos = (iv->getPosByChFld(j, FLD_UDC, rec)); break; - case 1: pos = (iv->getPosByChFld(j, FLD_IDC, rec)); break; - case 2: pos = (iv->getPosByChFld(j, FLD_PDC, rec)); break; - case 3: pos = (iv->getPosByChFld(j, FLD_YD, rec)); break; - case 4: pos = (iv->getPosByChFld(j, FLD_YT, rec)); break; - case 5: pos = (iv->getPosByChFld(j, FLD_IRR, rec)); break; - } - cur[k] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; - if(1 == j) { - obj[F("fld_units")][k] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; - obj[F("fld_names")][k] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; - } - } - } - } - } -} - - -//----------------------------------------------------------------------------- -void webApi::getRecord(JsonObject obj, record_t<> *rec) { - JsonArray invArr = obj.createNestedArray(F("inverter")); - - Inverter<> *iv; - uint8_t pos; - for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { - iv = mApp->mSys->getInverterByPos(i); - if(NULL != iv) { - JsonArray obj2 = invArr.createNestedArray(); - for(uint8_t j = 0; j < rec->length; j++) { - byteAssign_t *assign = iv->getByteAssign(j, rec); - pos = (iv->getPosByChFld(assign->ch, assign->fieldId, rec)); - obj2[j]["fld"] = (0xff != pos) ? String(iv->getFieldName(pos, rec)) : notAvail; - obj2[j]["unit"] = (0xff != pos) ? String(iv->getUnit(pos, rec)) : notAvail; - obj2[j]["val"] = (0xff != pos) ? String(iv->getValue(pos, rec)) : notAvail; - } - } - } -} - - -//----------------------------------------------------------------------------- -bool webApi::setCtrl(JsonObject jsonIn, JsonObject jsonOut) { - Inverter<> *iv = mApp->mSys->getInverterByPos(jsonIn[F("id")]); - if(NULL == iv) { - jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as(); - return false; - } - - if(F("power") == jsonIn[F("cmd")]) { - iv->devControlCmd = (jsonIn[F("val")] == 1) ? TurnOn : TurnOff; - iv->devControlRequest = true; - } else if(F("restart") == jsonIn[F("restart")]) { - iv->devControlCmd = Restart; - iv->devControlRequest = true; - } - else if(0 == strncmp("limit_", jsonIn[F("cmd")].as(), 6)) { - iv->powerLimit[0] = jsonIn["val"]; - if(F("limit_persistent_relative") == jsonIn[F("cmd")]) - iv->powerLimit[1] = RelativPersistent; - else if(F("limit_persistent_absolute") == jsonIn[F("cmd")]) - iv->powerLimit[1] = AbsolutPersistent; - else if(F("limit_nonpersistent_relative") == jsonIn[F("cmd")]) - iv->powerLimit[1] = RelativNonPersistent; - else if(F("limit_nonpersistent_absolute") == jsonIn[F("cmd")]) - iv->powerLimit[1] = AbsolutNonPersistent; - iv->devControlCmd = ActivePowerContr; - iv->devControlRequest = true; - } - else { - jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as() + "'"; - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -bool webApi::setSetup(JsonObject jsonIn, JsonObject jsonOut) { - if(F("scan_wifi") == jsonIn[F("cmd")]) - mApp->scanAvailNetworks(); - else if(F("set_time") == jsonIn[F("cmd")]) - mApp->setTimestamp(jsonIn[F("val")]); - else if(F("sync_ntp") == jsonIn[F("cmd")]) - mApp->setTimestamp(0); // 0: update ntp flag - else if(F("serial_utc_offset") == jsonIn[F("cmd")]) - mTimezoneOffset = jsonIn[F("val")]; - else if(F("discovery_cfg") == jsonIn[F("cmd")]) - mApp->mFlagSendDiscoveryConfig = true; // for homeassistant - else { - jsonOut[F("error")] = F("unknown cmd"); - return false; - } - - return true; -} diff --git a/src/web/webApi.h b/src/web/webApi.h deleted file mode 100644 index 04e16635..00000000 --- a/src/web/webApi.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef __WEB_API_H__ -#define __WEB_API_H__ - -#include "../utils/dbg.h" -#ifdef ESP32 - #include "AsyncTCP.h" -#else - #include "ESPAsyncTCP.h" -#endif -#include "ESPAsyncWebServer.h" -#include "AsyncJson.h" -#include "../app.h" - - -class app; - -class webApi { - public: - webApi(AsyncWebServer *srv, app *app, settings_t *config, statistics_t *stat, char version[]); - - void setup(void); - void loop(void); - - uint32_t getTimezoneOffset() { - return mTimezoneOffset; - } - - void ctrlRequest(JsonObject obj); - - private: - void onApi(AsyncWebServerRequest *request); - void onApiPost(AsyncWebServerRequest *request); - void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total); - void getNotFound(JsonObject obj, String url); - void onDwnldSetup(AsyncWebServerRequest *request); - - void getSysInfo(JsonObject obj); - void getHtmlSystem(JsonObject obj); - void getHtmlLogout(JsonObject obj); - void getHtmlSave(JsonObject obj); - void getReboot(JsonObject obj); - void getStatistics(JsonObject obj); - void getInverterList(JsonObject obj); - void getMqtt(JsonObject obj); - void getNtp(JsonObject obj); - void getSun(JsonObject obj); - void getPinout(JsonObject obj); - void getRadio(JsonObject obj); - void getSerial(JsonObject obj); - void getStaticIp(JsonObject obj); - - void getMenu(JsonObject obj); - void getIndex(JsonObject obj); - void getSetup(JsonObject obj); - void getNetworks(JsonObject obj); - void getLive(JsonObject obj); - void getRecord(JsonObject obj, record_t<> *rec); - - bool setCtrl(JsonObject jsonIn, JsonObject jsonOut); - bool setSetup(JsonObject jsonIn, JsonObject jsonOut); - - AsyncWebServer *mSrv; - app *mApp; - - settings_t *mConfig; - statistics_t *mStat; - char *mVersion; - - uint32_t mTimezoneOffset; -}; - -#endif /*__WEB_API_H__*/ diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 965c8197..a002ac78 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -17,7 +17,7 @@ ahoywifi::ahoywifi() { mCnt = 0; mConnected = false; - mInitNtp = true; + mReconnect = false; } @@ -47,7 +47,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp) { //----------------------------------------------------------------------------- void ahoywifi::loop() { #if !defined(AP_ONLY) - if(!mConnected) { + if(mReconnect) { delay(100); mCnt++; if((mCnt % 50) == 0) @@ -56,11 +56,7 @@ void ahoywifi::loop() { WiFi.reconnect(); mCnt = 0; } - } else if(mInitNtp) { - getNtpTime(); - mInitNtp = false; } - mCnt = 0; #endif } @@ -209,6 +205,7 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) { if(!mConnected) { mConnected = true; + mReconnect = false; DBGPRINTLN(F("\n[WiFi] Connected")); WiFi.mode(WIFI_STA); DBGPRINTLN(F("[WiFi] AP disabled")); @@ -223,6 +220,7 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { void ahoywifi::onDisconnect(const WiFiEventStationModeDisconnected& event) { if(mConnected) { mConnected = false; + mReconnect = true; DPRINTLN(DBG_INFO, "[WiFi] Connection Lost"); } } diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index 1b93ad8a..644952e9 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -47,7 +47,7 @@ class ahoywifi { WiFiEventHandler wifiDisconnectHandler; #endif - bool mConnected, mInitNtp; + bool mConnected, mReconnect; uint8_t mCnt; uint32_t *mUtcTimestamp; };