//----------------------------------------------------------------------------- // 2023 Ahoy, https://ahoydtu.de // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- #ifndef __WEB_API_H__ #define __WEB_API_H__ #include "../utils/dbg.h" #ifdef ESP32 #include "AsyncTCP.h" #else #include "ESPAsyncTCP.h" #endif #include "../appInterface.h" #include "../hm/hmSystem.h" #include "../utils/helper.h" #include "AsyncJson.h" #if defined(ETHERNET) #include "AsyncWebServer_ESP32_W5500.h" #else #include "ESPAsyncWebServer.h" #endif #if defined(F) && defined(ESP32) #undef F #define F(sl) (sl) #endif const uint8_t acList[] = {FLD_UAC, FLD_IAC, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; const uint8_t acListHmt[] = {FLD_UAC_1N, FLD_IAC_1, FLD_PAC, FLD_F, FLD_PF, FLD_T, FLD_YT, FLD_YD, FLD_PDC, FLD_EFF, FLD_Q, FLD_MP}; const uint8_t dcList[] = {FLD_UDC, FLD_IDC, FLD_PDC, FLD_YD, FLD_YT, FLD_IRR, FLD_MP}; template<class HMSYSTEM> class RestApi { public: RestApi() { mTimezoneOffset = 0; mHeapFree = 0; mHeapFreeBlk = 0; mHeapFrag = 0; } void setup(IApp *app, HMSYSTEM *sys, AsyncWebServer *srv, settings_t *config) { mApp = app; mSrv = srv; mSys = sys; mRadioNrf = (HmRadio<>*)mApp->getRadioObj(true); #if defined(ESP32) mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false); #endif mConfig = config; 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("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); } uint32_t getTimezoneOffset(void) { return mTimezoneOffset; } void ctrlRequest(JsonObject obj) { DynamicJsonDocument json(128); JsonObject dummy = json.as<JsonObject>(); if(obj[F("path")] == "ctrl") setCtrl(obj, dummy); else if(obj[F("path")] == "setup") setSetup(obj, dummy); } private: void onApi(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, String("onApi: ") + String((uint16_t)request->method())); // 1 == Get, 3 == POST mHeapFree = ESP.getFreeHeap(); #ifndef ESP32 mHeapFreeBlk = ESP.getMaxFreeBlockSize(); mHeapFrag = ESP.getHeapFragmentation(); #endif AsyncJsonResponse* response = new AsyncJsonResponse(false, 6000); JsonObject root = response->getRoot(); String path = request->url().substring(5); if(path == "html/system") getHtmlSystem(request, root); else if(path == "html/logout") getHtmlLogout(request, root); else if(path == "html/reboot") getHtmlReboot(request, root); else if(path == "html/save") getHtmlSave(request, root); else if(path == "system") getSysInfo(request, root); else if(path == "generic") getGeneric(request, root); else if(path == "reboot") getReboot(request, root); else if(path == "inverter/list") getInverterList(root); else if(path == "index") getIndex(request, root); else if(path == "setup") getSetup(request, root); #if !defined(ETHERNET) else if(path == "setup/networks") getNetworks(root); #endif /* !defined(ETHERNET) */ else if(path == "live") getLive(request,root); else { if(path.substring(0, 12) == "inverter/id/") getInverter(root, request->url().substring(17).toInt()); else if(path.substring(0, 15) == "inverter/alarm/") getIvAlarms(root, request->url().substring(20).toInt()); else if(path.substring(0, 17) == "inverter/version/") getIvVersion(root, request->url().substring(22).toInt()); else if(path.substring(0, 19) == "inverter/radiostat/") getIvStatistis(root, request->url().substring(24).toInt()); else if(path.substring(0, 16) == "inverter/pwrack/") getIvPowerLimitAck(root, request->url().substring(21).toInt()); else getNotFound(root, F("http://") + request->host() + F("/api/")); } //DPRINTLN(DBG_INFO, "API mem usage: " + String(root.memoryUsage())); response->addHeader("Access-Control-Allow-Origin", "*"); response->addHeader("Access-Control-Allow-Headers", "content-type"); response->setLength(); request->send(response); } void onApiPost(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, "onApiPost"); #if defined(ETHERNET) // workaround for AsyncWebServer_ESP32_W5500, because it can't distinguish // between HTTP_GET and HTTP_POST if both are registered if(request->method() == HTTP_GET) onApi(request); #endif } void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, "onApiPostBody"); if(0 == index) { if(NULL != mTmpBuf) delete[] mTmpBuf; mTmpBuf = new uint8_t[total+1]; mTmpSize = total; } if(mTmpSize >= (len + index)) memcpy(&mTmpBuf[index], data, len); if((len + index) != total) return; // not last frame - nothing to do DynamicJsonDocument json(1000); DeserializationError err = deserializeJson(json, (const char *)mTmpBuf, mTmpSize); JsonObject obj = json.as<JsonObject>(); AsyncJsonResponse* response = new AsyncJsonResponse(false, 200); JsonObject root = response->getRoot(); 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::IncompleteInput: root[F("error")] = F("Incomplete input"); 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); delete[] mTmpBuf; mTmpBuf = NULL; } void getNotFound(JsonObject obj, String url) { JsonObject ep = obj.createNestedObject("avail_endpoints"); ep[F("inverter/list")] = url + F("inverter/list"); ep[F("inverter/id/0")] = url + F("inverter/id/0"); ep[F("inverter/alarm/0")] = url + F("inverter/alarm/0"); ep[F("inverter/version/0")] = url + F("inverter/version/0"); ep[F("generic")] = url + F("generic"); ep[F("index")] = url + F("index"); ep[F("setup")] = url + F("setup"); ep[F("system")] = url + F("system"); ep[F("live")] = url + F("live"); } void onDwnldSetup(AsyncWebServerRequest *request) { AsyncWebServerResponse *response; File fp = LittleFS.open("/settings.json", "r"); if(!fp) { DPRINTLN(DBG_ERROR, F("failed to load settings")); response = request->beginResponse(200, F("application/json; charset=utf-8"), "{}"); } else { String tmp = fp.readString(); int i = 0; // remove all passwords while (i != -1) { i = tmp.indexOf("\"pwd\":", i); if(-1 != i) { i+=7; tmp.remove(i, tmp.indexOf("\"", i)-i); } } response = request->beginResponse(200, F("application/json; charset=utf-8"), tmp); } String filename = ah::getDateTimeStrFile(gTimezone.toLocal(mApp->getTimestamp())); filename += "_v" + String(mApp->getVersion()); response->addHeader("Content-Type", "application/octet-stream"); response->addHeader("Content-Description", "File Transfer"); response->addHeader("Content-Disposition", "attachment; filename=" + filename + "_ahoy_setup.json"); request->send(response); fp.close(); } void getGeneric(AsyncWebServerRequest *request, JsonObject obj) { obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); obj[F("ts_uptime")] = mApp->getUptime(); obj[F("ts_now")] = mApp->getTimestamp(); obj[F("version")] = String(mApp->getVersion()); obj[F("build")] = String(AUTO_GIT_HASH); obj[F("menu_prot")] = mApp->getProtection(request); obj[F("menu_mask")] = (uint16_t)(mConfig->sys.protectionMask ); obj[F("menu_protEn")] = (bool) (strlen(mConfig->sys.adminPwd) > 0); #if defined(ESP32) obj[F("esp_type")] = F("ESP32"); #else obj[F("esp_type")] = F("ESP8266"); #endif } void getSysInfo(AsyncWebServerRequest *request, JsonObject obj) { #if !defined(ETHERNET) obj[F("ssid")] = mConfig->sys.stationSsid; obj[F("ap_pwd")] = mConfig->sys.apPwd; obj[F("hidd")] = mConfig->sys.isHidden; #endif /* !defined(ETHERNET) */ obj[F("device_name")] = mConfig->sys.deviceName; obj[F("dark_mode")] = (bool)mConfig->sys.darkMode; obj[F("sched_reboot")] = (bool)mConfig->sys.schedReboot; obj[F("mac")] = WiFi.macAddress(); obj[F("hostname")] = mConfig->sys.deviceName; obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); obj[F("prot_mask")] = mConfig->sys.protectionMask; obj[F("sdk")] = ESP.getSdkVersion(); obj[F("cpu_freq")] = ESP.getCpuFreqMHz(); obj[F("heap_free")] = mHeapFree; obj[F("sketch_total")] = ESP.getFreeSketchSpace(); obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb getGeneric(request, obj); getRadioNrf(obj.createNestedObject(F("radioNrf"))); #if defined(ESP32) getRadioCmtInfo(obj.createNestedObject(F("radioCmt"))); #endif getMqttInfo(obj.createNestedObject(F("mqtt"))); #if defined(ESP32) obj[F("chip_revision")] = ESP.getChipRevision(); obj[F("chip_model")] = ESP.getChipModel(); obj[F("chip_cores")] = ESP.getChipCores(); obj[F("heap_total")] = ESP.getHeapSize(); //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("heap_frag")] = mHeapFrag; obj[F("max_free_blk")] = mHeapFreeBlk; obj[F("core_version")] = ESP.getCoreVersion(); obj[F("flash_size")] = ESP.getFlashChipRealSize() / 1024; // in kb obj[F("reboot_reason")] = ESP.getResetReason(); #endif //obj[F("littlefs_total")] = LittleFS.totalBytes(); //obj[F("littlefs_used")] = LittleFS.usedBytes(); uint8_t max; mApp->getSchedulerInfo(&max); obj[F("schMax")] = max; } void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) { getSysInfo(request, obj.createNestedObject(F("system"))); getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("html")] = F("<a href=\"/factory\" class=\"btn\">AhoyFactory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>"); } void getHtmlLogout(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = 3; obj[F("refresh_url")] = "/"; obj[F("html")] = F("successfully logged out"); } void getHtmlReboot(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = 20; obj[F("refresh_url")] = "/"; obj[F("html")] = F("rebooting ..."); } void getHtmlSave(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj["pending"] = (bool)mApp->getSavePending(); obj["success"] = (bool)mApp->getLastSaveSucceed(); obj["reboot"] = (bool)mApp->getShouldReboot(); } void getReboot(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = 10; obj[F("refresh_url")] = "/"; obj[F("html")] = F("reboot. Autoreload after 10 seconds"); } void getIvStatistis(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { obj[F("error")] = F("inverter not found!"); return; } obj[F("name")] = String(iv->config->name); obj[F("rx_success")] = iv->radioStatistics.rxSuccess; obj[F("rx_fail")] = iv->radioStatistics.rxFail; obj[F("rx_fail_answer")] = iv->radioStatistics.rxFailNoAnser; obj[F("frame_cnt")] = iv->radioStatistics.frmCnt; obj[F("tx_cnt")] = iv->radioStatistics.txCnt; obj[F("retransmits")] = iv->radioStatistics.retransmits; } void getIvPowerLimitAck(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { obj[F("error")] = F("inverter not found!"); return; } obj["ack"] = (bool)iv->powerLimitAck; } 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) continue; JsonObject obj2 = invArr.createNestedObject(); obj2[F("enabled")] = (bool)iv->config->enabled; 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("freq")] = iv->config->frequency; obj2[F("disnightcom")] = (bool)iv->config->disNightCom; obj2[F("add2total")] = (bool)iv->config->add2Total; if(0xff == iv->config->powerLevel) { if((IV_HMT == iv->ivGen) || (IV_HMS == iv->ivGen)) obj2[F("pa")] = 30; // 20dBm else obj2[F("pa")] = 1; // low } else obj2[F("pa")] = iv->config->powerLevel; for(uint8_t j = 0; j < iv->channels; j ++) { obj2[F("ch_yield_cor")][j] = (double)iv->config->yieldCor[j]; obj2[F("ch_name")][j] = iv->config->chName[j]; obj2[F("ch_max_pwr")][j] = iv->config->chMaxPwr[j]; } } obj[F("interval")] = String(mConfig->inst.sendInterval); obj[F("max_num_inverters")] = MAX_NUM_INVERTERS; obj[F("rstMid")] = (bool)mConfig->inst.rstYieldMidNight; obj[F("rstNotAvail")] = (bool)mConfig->inst.rstValsNotAvail; obj[F("rstComStop")] = (bool)mConfig->inst.rstValsCommStop; obj[F("strtWthtTm")] = (bool)mConfig->inst.startWithoutTime; obj[F("rstMaxMid")] = (bool)mConfig->inst.rstMaxValsMidNight; obj[F("yldEff")] = mConfig->inst.yieldEffiency; obj[F("gap")] = mConfig->inst.gapMs; } void getInverter(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { obj[F("error")] = F("inverter not found!"); return; } record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); obj[F("id")] = id; obj[F("enabled")] = (bool)iv->config->enabled; obj[F("name")] = String(iv->config->name); obj[F("serial")] = String(iv->config->serial.u64, HEX); obj[F("version")] = String(iv->getFwVersion()); obj[F("power_limit_read")] = ah::round3(iv->actPowerLimit); obj[F("power_limit_ack")] = iv->powerLimitAck; obj[F("max_pwr")] = iv->getMaxPower(); obj[F("ts_last_success")] = rec->ts; obj[F("generation")] = iv->ivGen; obj[F("status")] = (uint8_t)iv->status; obj[F("alarm_cnt")] = iv->alarmCnt; obj[F("rssi")] = iv->rssi; JsonArray ch = obj.createNestedArray("ch"); // AC uint8_t pos; obj[F("ch_name")][0] = "AC"; JsonArray ch0 = ch.createNestedArray(); if(IV_HMT == iv->ivGen) { for (uint8_t fld = 0; fld < sizeof(acListHmt); fld++) { pos = (iv->getPosByChFld(CH0, acListHmt[fld], rec)); ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; } } else { for (uint8_t fld = 0; fld < sizeof(acList); fld++) { pos = (iv->getPosByChFld(CH0, acList[fld], rec)); ch0[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; } } // DC for(uint8_t j = 0; j < iv->channels; j ++) { obj[F("ch_name")][j+1] = iv->config->chName[j]; obj[F("ch_max_pwr")][j+1] = iv->config->chMaxPwr[j]; JsonArray cur = ch.createNestedArray(); for (uint8_t fld = 0; fld < sizeof(dcList); fld++) { pos = (iv->getPosByChFld((j+1), dcList[fld], rec)); cur[fld] = (0xff != pos) ? ah::round3(iv->getValue(pos, rec)) : 0.0; } } } void getIvAlarms(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { obj[F("error")] = F("inverter not found!"); return; } record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); obj[F("iv_id")] = id; obj[F("iv_name")] = String(iv->config->name); obj[F("cnt")] = iv->alarmCnt; obj[F("last_id")] = iv->getChannelFieldValue(CH0, FLD_EVT, rec); JsonArray alarm = obj.createNestedArray(F("alarm")); for(uint8_t i = 0; i < 10; i++) { alarm[i][F("code")] = iv->lastAlarm[i].code; alarm[i][F("str")] = iv->getAlarmStr(iv->lastAlarm[i].code); alarm[i][F("start")] = iv->lastAlarm[i].start; alarm[i][F("end")] = iv->lastAlarm[i].end; } } void getIvVersion(JsonObject obj, uint8_t id) { Inverter<> *iv = mSys->getInverterByPos(id); if(NULL == iv) { obj[F("error")] = F("inverter not found!"); return; } record_t<> *rec = iv->getRecordStruct(InverterDevInform_Simple); obj[F("name")] = String(iv->config->name); obj[F("serial")] = String(iv->config->serial.u64, HEX); obj[F("generation")] = iv->ivGen; obj[F("max_pwr")] = iv->getMaxPower(); obj[F("part_num")] = iv->getChannelFieldValueInt(CH0, FLD_PART_NUM, rec); obj[F("hw_ver")] = iv->getChannelFieldValueInt(CH0, FLD_HW_VERSION, rec); obj[F("prod_cw")] = ((iv->config->serial.b[3] & 0x0f) * 10 + (((iv->config->serial.b[2] >> 4) & 0x0f))); obj[F("prod_year")] = ((iv->config->serial.b[3] >> 4) & 0x0f) + 2014; rec = iv->getRecordStruct(InverterDevInform_All); char buf[10]; uint16_t val; val = iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_MONTH_DAY, rec); snprintf(buf, 10, "-%02d-%02d", (val / 100), (val % 100)); obj[F("fw_date")] = String(iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_YEAR, rec)) + String(buf); val = iv->getChannelFieldValueInt(CH0, FLD_FW_BUILD_HOUR_MINUTE, rec); snprintf(buf, 10, "%02d:%02d", (val / 100), (val % 100)); obj[F("fw_time")] = String(buf); val = iv->getChannelFieldValueInt(CH0, FLD_FW_VERSION, rec); snprintf(buf, 10, "%d.%02d.%02d", (val / 10000), ((val % 10000) / 100), (val % 100)); obj[F("fw_ver")] = String(buf); obj[F("boot_ver")] = iv->getChannelFieldValueInt(CH0, FLD_BOOTLOADER_VER, rec); } void getMqtt(JsonObject obj) { obj[F("broker")] = String(mConfig->mqtt.broker); obj[F("clientId")] = String(mConfig->mqtt.clientId); 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); obj[F("interval")] = String(mConfig->mqtt.interval); } void getNtp(JsonObject obj) { obj[F("addr")] = String(mConfig->ntp.addr); obj[F("port")] = String(mConfig->ntp.port); obj[F("interval")] = String(mConfig->ntp.interval); } 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("offs")] = mConfig->sun.offsetSec; } void getPinout(JsonObject obj) { obj[F("cs")] = mConfig->nrf.pinCs; obj[F("ce")] = mConfig->nrf.pinCe; obj[F("irq")] = mConfig->nrf.pinIrq; obj[F("sclk")] = mConfig->nrf.pinSclk; obj[F("mosi")] = mConfig->nrf.pinMosi; obj[F("miso")] = mConfig->nrf.pinMiso; obj[F("led0")] = mConfig->led.led0; obj[F("led1")] = mConfig->led.led1; obj[F("led_high_active")] = mConfig->led.led_high_active; } #if defined(ESP32) void getRadioCmt(JsonObject obj) { obj[F("sclk")] = mConfig->cmt.pinSclk; obj[F("sdio")] = mConfig->cmt.pinSdio; obj[F("csb")] = mConfig->cmt.pinCsb; obj[F("fcsb")] = mConfig->cmt.pinFcsb; obj[F("gpio3")] = mConfig->cmt.pinIrq; obj[F("en")] = (bool) mConfig->cmt.enabled; } void getRadioCmtInfo(JsonObject obj) { obj[F("en")] = (bool) mConfig->cmt.enabled; if(mConfig->cmt.enabled) { obj[F("isconnected")] = mRadioCmt->isChipConnected(); obj[F("sn")] = String(mRadioCmt->getDTUSn(), HEX); } } #endif void getRadioNrf(JsonObject obj) { obj[F("en")] = (bool) mConfig->nrf.enabled; if(mConfig->nrf.enabled) { obj[F("isconnected")] = mRadioNrf->isChipConnected(); obj[F("dataRate")] = mRadioNrf->getDataRate(); obj[F("sn")] = String(mRadioNrf->getDTUSn(), HEX); } } void getSerial(JsonObject obj) { obj[F("show_live_data")] = mConfig->serial.showIv; obj[F("debug")] = mConfig->serial.debug; obj[F("priv")] = mConfig->serial.privacyLog; obj[F("wholeTrace")] = mConfig->serial.printWholeTrace; } 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 getDisplay(JsonObject obj) { obj[F("disp_typ")] = (uint8_t)mConfig->plugin.display.type; obj[F("disp_pwr")] = (bool)mConfig->plugin.display.pwrSaveAtIvOffline; obj[F("disp_screensaver")] = (uint8_t)mConfig->plugin.display.screenSaver; obj[F("disp_rot")] = (uint8_t)mConfig->plugin.display.rot; obj[F("disp_cont")] = (uint8_t)mConfig->plugin.display.contrast; obj[F("disp_clk")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_clk; obj[F("disp_data")] = (mConfig->plugin.display.type == 0) ? DEF_PIN_OFF : mConfig->plugin.display.disp_data; obj[F("disp_cs")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_cs; obj[F("disp_dc")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_dc; obj[F("disp_rst")] = (mConfig->plugin.display.type < 3) ? DEF_PIN_OFF : mConfig->plugin.display.disp_reset; obj[F("disp_bsy")] = (mConfig->plugin.display.type < 10) ? DEF_PIN_OFF : mConfig->plugin.display.disp_busy; obj[F("pir_pin")] = mConfig->plugin.display.pirPin; } void getMqttInfo(JsonObject obj) { obj[F("enabled")] = (mConfig->mqtt.broker[0] != '\0'); obj[F("connected")] = mApp->getMqttIsConnected(); obj[F("tx_cnt")] = mApp->getMqttTxCnt(); obj[F("rx_cnt")] = mApp->getMqttRxCnt(); obj[F("interval")] = mConfig->mqtt.interval; } void getIndex(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("ts_now")] = mApp->getTimestamp(); obj[F("ts_sunrise")] = mApp->getSunrise(); obj[F("ts_sunset")] = mApp->getSunset(); obj[F("ts_offset")] = mConfig->sun.offsetSec; JsonArray inv = obj.createNestedArray(F("inverter")); Inverter<> *iv; bool disNightCom = false; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { iv = mSys->getInverterByPos(i); if(NULL == iv) continue; record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); JsonObject invObj = inv.createNestedObject(); invObj[F("enabled")] = (bool)iv->config->enabled; invObj[F("id")] = i; invObj[F("name")] = String(iv->config->name); invObj[F("cur_pwr")] = ah::round3(iv->getChannelFieldValue(CH0, FLD_PAC, rec)); invObj[F("is_avail")] = iv->isAvailable(); invObj[F("is_producing")] = iv->isProducing(); invObj[F("ts_last_success")] = iv->getLastTs(rec); if(iv->config->disNightCom) disNightCom = true; } obj[F("disNightComm")] = disNightCom; JsonArray warn = obj.createNestedArray(F("warnings")); if(!mRadioNrf->isChipConnected() && mConfig->nrf.enabled) warn.add(F("your NRF24 module can't be reached, check the wiring, pinout and enable")); if(!mApp->getSettingsValid()) warn.add(F("your settings are invalid")); if(mApp->getRebootRequestState()) warn.add(F("reboot your ESP to apply all your configuration changes")); if(0 == mApp->getTimestamp()) warn.add(F("time not set. No communication to inverter possible")); } void getSetup(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); getSysInfo(request, 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"))); #if defined(ESP32) getRadioCmt(obj.createNestedObject(F("radioCmt"))); #endif getRadioNrf(obj.createNestedObject(F("radioNrf"))); getSerial(obj.createNestedObject(F("serial"))); getStaticIp(obj.createNestedObject(F("static_ip"))); getDisplay(obj.createNestedObject(F("display"))); } #if !defined(ETHERNET) void getNetworks(JsonObject obj) { mApp->getAvailNetworks(obj); } #endif /* !defined(ETHERNET) */ void getLive(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = mConfig->inst.sendInterval; for (uint8_t fld = 0; fld < sizeof(acList); fld++) { obj[F("ch0_fld_units")][fld] = String(units[fieldUnits[acList[fld]]]); obj[F("ch0_fld_names")][fld] = String(fields[acList[fld]]); } for (uint8_t fld = 0; fld < sizeof(dcList); fld++) { obj[F("fld_units")][fld] = String(units[fieldUnits[dcList[fld]]]); obj[F("fld_names")][fld] = String(fields[dcList[fld]]); } Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i ++) { iv = mSys->getInverterByPos(i); bool parse = false; if(NULL != iv) parse = iv->config->enabled; obj[F("iv")][i] = parse; } } bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]); bool accepted = true; if(NULL == iv) { jsonOut[F("error")] = F("inverter index invalid: ") + jsonIn[F("id")].as<String>(); return false; } jsonOut[F("id")] = jsonIn[F("id")]; if(F("power") == jsonIn[F("cmd")]) accepted = iv->setDevControlRequest((jsonIn[F("val")] == 1) ? TurnOn : TurnOff); else if(F("restart") == jsonIn[F("cmd")]) accepted = iv->setDevControlRequest(Restart); else if(0 == strncmp("limit_", jsonIn[F("cmd")].as<const char*>(), 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; accepted = iv->setDevControlRequest(ActivePowerContr); } else if(F("dev") == jsonIn[F("cmd")]) { DPRINTLN(DBG_INFO, F("dev cmd")); iv->setDevCommand(jsonIn[F("val")].as<int>()); } else { jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as<String>() + "'"; return false; } if(!accepted) { jsonOut[F("error")] = F("inverter does not accept dev control request at this moment"); return false; } return true; } bool setSetup(JsonObject jsonIn, JsonObject jsonOut) { #if !defined(ETHERNET) if(F("scan_wifi") == jsonIn[F("cmd")]) mApp->scanAvailNetworks(); else #endif /* !defined(ETHERNET) */ 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 if(F("save_iv") == jsonIn[F("cmd")]) { Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")], false); iv->config->enabled = jsonIn[F("en")]; iv->config->serial.u64 = jsonIn[F("ser")]; snprintf(iv->config->name, MAX_NAME_LENGTH, "%s", jsonIn[F("name")].as<const char*>()); for(uint8_t i = 0; i < 6; i++) { iv->config->chMaxPwr[i] = jsonIn[F("ch")][i][F("pwr")]; iv->config->yieldCor[i] = jsonIn[F("ch")][i][F("yld")]; snprintf(iv->config->chName[i], MAX_NAME_LENGTH, "%s", jsonIn[F("ch")][i][F("name")].as<const char*>()); } mApp->initInverter(jsonIn[F("id")]); iv->config->frequency = jsonIn[F("freq")]; iv->config->powerLevel = jsonIn[F("pa")]; iv->config->disNightCom = jsonIn[F("disnightcom")]; iv->config->add2Total = jsonIn[F("add2total")]; mApp->saveSettings(false); // without reboot } else { jsonOut[F("error")] = F("unknown cmd"); return false; } return true; } IApp *mApp; HMSYSTEM *mSys; HmRadio<> *mRadioNrf; #if defined(ESP32) CmtRadio<> *mRadioCmt; #endif AsyncWebServer *mSrv; settings_t *mConfig; uint32_t mTimezoneOffset; uint32_t mHeapFree, mHeapFreeBlk; uint8_t mHeapFrag; uint8_t *mTmpBuf = NULL; uint32_t mTmpSize; }; #endif /*__WEB_API_H__*/