#include "AmsWebServer.h" #include "version.h" #include "AmsStorage.h" #include "hexutils.h" #include "root/head_html.h" #include "root/foot_html.h" #include "root/index_html.h" #include "root/application_js.h" #include "root/setup_html.h" #include "root/meter_html.h" #include "root/wifi_html.h" #include "root/mqtt_html.h" #include "root/web_html.h" #include "root/domoticz_html.h" #include "root/entsoe_html.h" #include "root/ntp_html.h" #include "root/gpio_html.h" #include "root/debugging_html.h" #include "root/restart_html.h" #include "root/restartwait_html.h" #include "root/boot_css.h" #include "root/gaugemeter_js.h" #include "root/github_svg.h" #include "root/upload_html.h" #include "root/delete_html.h" #include "root/reset_html.h" #include "root/temperature_html.h" #include "root/price_html.h" #include "root/notfound_html.h" #include "root/data_json.h" #include "root/tempsensor_json.h" #include "base64.h" AmsWebServer::AmsWebServer(RemoteDebug* Debug, HwTools* hw, EntsoeApi* eapi) { this->debugger = Debug; this->hw = hw; this->eapi = eapi; } void AmsWebServer::setup(AmsConfiguration* config, GpioConfig* gpioConfig, MeterConfig* meterConfig, AmsData* meterState, MQTTClient* mqtt) { this->config = config; this->gpioConfig = gpioConfig; this->meterConfig = meterConfig; this->meterState = meterState; this->mqtt = mqtt; server.on("/", HTTP_GET, std::bind(&AmsWebServer::indexHtml, this)); server.on("/", HTTP_POST, std::bind(&AmsWebServer::handleSetup, this)); server.on("/application.js", HTTP_GET, std::bind(&AmsWebServer::applicationJs, this)); server.on("/temperature", HTTP_GET, std::bind(&AmsWebServer::temperature, this)); server.on("/temperature", HTTP_POST, std::bind(&AmsWebServer::temperaturePost, this)); server.on("/temperature.json", HTTP_GET, std::bind(&AmsWebServer::temperatureJson, this)); server.on("/price", HTTP_GET, std::bind(&AmsWebServer::price, this)); server.on("/meter", HTTP_GET, std::bind(&AmsWebServer::configMeterHtml, this)); server.on("/wifi", HTTP_GET, std::bind(&AmsWebServer::configWifiHtml, this)); server.on("/mqtt", HTTP_GET, std::bind(&AmsWebServer::configMqttHtml, this)); server.on("/web", HTTP_GET, std::bind(&AmsWebServer::configWebHtml, this)); server.on("/domoticz",HTTP_GET, std::bind(&AmsWebServer::configDomoticzHtml, this)); server.on("/entsoe",HTTP_GET, std::bind(&AmsWebServer::configEntsoeHtml, this)); server.on("/boot.css", HTTP_GET, std::bind(&AmsWebServer::bootCss, this)); server.on("/gaugemeter.js", HTTP_GET, std::bind(&AmsWebServer::gaugemeterJs, this)); server.on("/github.svg", HTTP_GET, std::bind(&AmsWebServer::githubSvg, this)); server.on("/data.json", HTTP_GET, std::bind(&AmsWebServer::dataJson, this)); server.on("/save", HTTP_POST, std::bind(&AmsWebServer::handleSave, this)); server.on("/ntp", HTTP_GET, std::bind(&AmsWebServer::configNtpHtml, this)); server.on("/gpio", HTTP_GET, std::bind(&AmsWebServer::configGpioHtml, this)); server.on("/debugging", HTTP_GET, std::bind(&AmsWebServer::configDebugHtml, this)); server.on("/firmware", HTTP_GET, std::bind(&AmsWebServer::firmwareHtml, this)); server.on("/firmware", HTTP_POST, std::bind(&AmsWebServer::uploadPost, this), std::bind(&AmsWebServer::firmwareUpload, this)); server.on("/upgrade", HTTP_GET, std::bind(&AmsWebServer::firmwareDownload, this)); server.on("/restart", HTTP_GET, std::bind(&AmsWebServer::restartHtml, this)); server.on("/restart", HTTP_POST, std::bind(&AmsWebServer::restartPost, this)); server.on("/restart-wait", HTTP_GET, std::bind(&AmsWebServer::restartWaitHtml, this)); server.on("/is-alive", HTTP_GET, std::bind(&AmsWebServer::isAliveCheck, this)); server.on("/mqtt-ca", HTTP_GET, std::bind(&AmsWebServer::mqttCa, this)); server.on("/mqtt-ca", HTTP_POST, std::bind(&AmsWebServer::mqttCaDelete, this), std::bind(&AmsWebServer::mqttCaUpload, this)); server.on("/mqtt-cert", HTTP_GET, std::bind(&AmsWebServer::mqttCert, this)); server.on("/mqtt-cert", HTTP_POST, std::bind(&AmsWebServer::mqttCertDelete, this), std::bind(&AmsWebServer::mqttCertUpload, this)); server.on("/mqtt-key", HTTP_GET, std::bind(&AmsWebServer::mqttKey, this)); server.on("/mqtt-key", HTTP_POST, std::bind(&AmsWebServer::mqttKeyDelete, this), std::bind(&AmsWebServer::mqttKeyUpload, this)); server.on("/reset", HTTP_GET, std::bind(&AmsWebServer::factoryResetHtml, this)); server.on("/reset", HTTP_POST, std::bind(&AmsWebServer::factoryResetPost, this)); server.onNotFound(std::bind(&AmsWebServer::notFound, this)); server.begin(); // Web server start config->getWebConfig(webConfig); MqttConfig mqttConfig; config->getMqttConfig(mqttConfig); mqttEnabled = strlen(mqttConfig.host) > 0; } void AmsWebServer::setTimezone(Timezone* tz) { this->tz = tz; } void AmsWebServer::setMqttEnabled(bool enabled) { mqttEnabled = enabled; } void AmsWebServer::loop() { server.handleClient(); if(maxPwr == 0 && meterState->getListType() > 1 && meterConfig->mainFuse > 0 && meterConfig->distributionSystem > 0) { if(meterState->isThreePhase()) { int voltage = meterConfig->distributionSystem == 2 ? 400 : 230; maxPwr = meterConfig->mainFuse * sqrt(3) * voltage; } else { maxPwr = meterConfig->mainFuse * 230; } } } bool AmsWebServer::checkSecurity(byte level) { bool access = WiFi.getMode() == WIFI_AP || webConfig.security < level; if(!access && webConfig.security >= level && server.hasHeader("Authorization")) { printD(" forcing web security"); String expectedAuth = String(webConfig.username) + ":" + String(webConfig.password); String providedPwd = server.header("Authorization"); providedPwd.replace("Basic ", ""); String expectedBase64 = base64::encode(expectedAuth); access = providedPwd.equals(expectedBase64); } if(!access) { printD(" no access, requesting user/pass"); server.sendHeader("WWW-Authenticate", "Basic realm=\"Secure Area\""); server.setContentLength(0); server.send(401, "text/html", ""); } if(access) printD(" access granted"); else printD(" access denied"); return access; } void AmsWebServer::temperature() { printD("Serving /temperature.html over http..."); if(!checkSecurity(2)) return; server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); server.setContentLength(HEAD_HTML_LEN + TEMPERATURE_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent_P(TEMPERATURE_HTML); server.sendContent_P(FOOT_HTML); } void AmsWebServer::temperaturePost() { if(!checkSecurity(1)) return; printD("Saving temperature sensors..."); for(int i = 0; i < 32; i++) { if(!server.hasArg("sensor" + String(i, DEC))) break; String address = server.arg("sensor" + String(i, DEC)); String name = server.arg("sensor" + String(i, DEC) + "name").substring(0,16); bool common = server.hasArg("sensor" + String(i, DEC) + "common") && server.arg("sensor" + String(i, DEC) + "common") == "true"; if(debugger->isActive(RemoteDebug::DEBUG)) { debugger->printf("Addr: %s, name: %s\n", address.c_str(), name.c_str()); } uint8_t hexStr[8]; fromHex(hexStr, address, 8); config->updateTempSensorConfig(hexStr, name.c_str(), common); delay(1); } if(config->save()) { printD("Successfully saved temperature sensors"); server.sendHeader("Location", String("/temperature"), true); server.send (302, "text/plain", ""); } } void AmsWebServer::temperatureJson() { printD("Serving /temperature.json over http..."); if(!checkSecurity(2)) return; int count = hw->getTempSensorCount(); int size = 32 + (count * 72); char buf[size]; snprintf(buf, 16, "{\"c\":%d,\"s\":[", count); for(int i = 0; i < count; i++) { TempSensorData* data = hw->getTempSensorData(i); TempSensorConfig* conf = config->getTempSensorConfig(data->address); char* pos = buf+strlen(buf); snprintf_P(pos, 72, TEMPSENSOR_JSON, i, toHex(data->address, 8).c_str(), conf == NULL ? "" : String(conf->name).substring(0,16).c_str(), conf == NULL || conf->common ? 1 : 0, data->lastRead ); delay(1); } char* pos = buf+strlen(buf); snprintf(count == 0 ? pos : pos-1, 8, "]}"); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); server.setContentLength(strlen(buf)); server.send(200, "application/json", buf); } void AmsWebServer::price() { printD("Serving /price.html over http..."); if(!checkSecurity(2)) return; server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); String html = String((const __FlashStringHelper*) PRICE_HTML); for(int i = 0; i < 24; i++) { tmElements_t tm; breakTime(tz->toLocal(time(nullptr)) + (SECS_PER_HOUR * i), tm); char ts[5]; sprintf(ts, "%02d:00", tm.Hour); html.replace("${time" + String(i) + "}", String(ts)); double price = eapi->getValueForHour(i); if(price == ENTSOE_NO_VALUE) { html.replace("${price" + String(i) + "}", "--"); } else { html.replace("${price" + String(i) + "}", String(price, 4)); } } server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } void AmsWebServer::indexHtml() { printD("Serving /index.html over http..."); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); if(WiFi.getMode() == WIFI_AP) { SystemConfig sys; config->getSystemConfig(sys); WiFiConfig wifi; config->clearWifi(wifi); String html = String((const __FlashStringHelper*) SETUP_HTML); for(int i = 0; i<255; i++) { html.replace("${config.boardType" + String(i) + "}", sys.boardType == i ? "selected" : ""); } for(int i = 0; i<5; i++) { html.replace("${config.meterType" + String(i) + "}", sys.boardType == i ? "selected" : ""); } html.replace("${config.wifiSsid}", wifi.ssid); html.replace("${config.wifiPassword}", wifi.psk); html.replace("${config.wifiStaticIp}", strlen(wifi.ip) > 0 ? "checked" : ""); html.replace("${config.wifiIp}", wifi.ip); html.replace("${config.wifiGw}", wifi.gateway); html.replace("${config.wifiSubnet}", wifi.subnet); html.replace("${config.wifiDns1}", wifi.dns1); html.replace("${config.wifiDns2}", wifi.dns2); html.replace("${config.wifiHostname}", wifi.hostname); server.send(200, "text/html", html); } else { if(!checkSecurity(2)) return; String html = String((const __FlashStringHelper*) INDEX_HTML); double u1 = meterState->getL1Voltage(); double u2 = meterState->getL2Voltage(); double u3 = meterState->getL3Voltage(); double i1 = meterState->getL1Current(); double i2 = meterState->getL2Current(); double i3 = meterState->getL3Current(); double tpi = meterState->getActiveImportCounter(); double tpo = meterState->getActiveExportCounter(); double tqi = meterState->getReactiveImportCounter(); double tqo = meterState->getReactiveExportCounter(); html.replace("{P}", String(meterState->getActiveImportPower())); html.replace("{PO}", String(meterState->getActiveExportPower())); html.replace("{de}", meterConfig->productionCapacity > 0 ? "" : "none"); html.replace("{dn}", meterConfig->productionCapacity > 0 ? "none" : ""); html.replace("{ti}", meterConfig->productionCapacity > 0 ? "Import" : "Use"); html.replace("{3p}", meterState->isThreePhase() ? "" : "none"); html.replace("{U1}", u1 > 0 ? String(u1, 1) : ""); html.replace("{I1}", u1 > 0 ? String(i1, 1) : ""); html.replace("{U2}", u2 > 0 ? String(u2, 1) : ""); html.replace("{I2}", u2 > 0 ? String(i2, 1) : ""); html.replace("{U3}", u3 > 0 ? String(u3, 1) : ""); html.replace("{I3}", u3 > 0 ? String(i3, 1) : ""); html.replace("{tPI}", tpi > 0 ? String(tpi, 1) : ""); html.replace("{tPO}", tpi > 0 ? String(tpo, 1) : ""); html.replace("{tQI}", tpi > 0 ? String(tqi, 1) : ""); html.replace("{tQO}", tpi > 0 ? String(tqo, 1) : ""); html.replace("{da}", tpi > 0 ? "" : "none"); double vcc = hw->getVcc(); html.replace("{vcc}", vcc > 0 ? String(vcc, 2) : ""); double temp = hw->getTemperature(); html.replace("{temp}", temp > 0 ? String(temp, 1) : ""); int rssi = hw->getWifiRssi(); html.replace("{rssi}", vcc > 0 ? String(rssi) : ""); html.replace("{cs}", String((uint32_t)(millis64()/1000), 10)); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } } void AmsWebServer::applicationJs() { printD("Serving /application.js over http..."); server.sendHeader("Cache-Control", "public, max-age=3600"); server.send_P(200, "application/javascript", APPLICATION_JS); } void AmsWebServer::configMeterHtml() { printD("Serving /meter.html over http..."); if(!checkSecurity(1)) return; String html = String((const __FlashStringHelper*) METER_HTML); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); html.replace("{m}", String(meterConfig->mainFuse)); for(int i = 0; i<5; i++) { html.replace("{m" + String(i) + "}", meterConfig->type == i ? "selected" : ""); } html.replace("{d}", String(meterConfig->distributionSystem)); for(int i = 0; i<3; i++) { html.replace("{d" + String(i) + "}", meterConfig->distributionSystem == i ? "selected" : ""); } html.replace("{f}", String(meterConfig->mainFuse)); for(int i = 0; i<64; i++) { html.replace("{f" + String(i) + "}", meterConfig->mainFuse == i ? "selected" : ""); } html.replace("{p}", String(meterConfig->productionCapacity)); if(meterConfig->type == METER_TYPE_OMNIPOWER) { String encryptionKeyHex = "0x"; encryptionKeyHex += toHex(meterConfig->encryptionKey, 16); html.replace("{e}", encryptionKeyHex); String authenticationKeyHex = "0x"; authenticationKeyHex += toHex(meterConfig->authenticationKey, 16); html.replace("{a}", authenticationKeyHex); } else { html.replace("{e}", ""); html.replace("{a}", ""); } html.replace("{s}", meterConfig->substituteMissing ? "checked" : ""); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } void AmsWebServer::configWifiHtml() { printD("Serving /wifi.html over http..."); if(!checkSecurity(1)) return; String html = String((const __FlashStringHelper*) WIFI_HTML); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); WiFiConfig wifi; config->getWiFiConfig(wifi); html.replace("{s}", wifi.ssid); html.replace("{p}", wifi.psk); html.replace("{st}", strlen(wifi.ip) > 0 ? "checked" : ""); html.replace("{i}", wifi.ip); html.replace("{g}", wifi.gateway); html.replace("{sn}", wifi.subnet); html.replace("{d1}", wifi.dns1); html.replace("{d2}", wifi.dns2); html.replace("{h}", wifi.hostname); html.replace("{m}", wifi.mdns ? "checked" : ""); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } void AmsWebServer::configMqttHtml() { printD("Serving /mqtt.html over http..."); if(!checkSecurity(1)) return; String html = String((const __FlashStringHelper*) MQTT_HTML); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); MqttConfig mqtt; config->getMqttConfig(mqtt); html.replace("{m}", strlen(mqtt.host) == 0 ? "" : "checked"); html.replace("{h}", mqtt.host); if(mqtt.port > 0) { html.replace("{p}", String(mqtt.port)); } else { html.replace("{p}", String(1883)); } html.replace("{i}", mqtt.clientId); html.replace("{t}", mqtt.publishTopic); html.replace("{st}", mqtt.subscribeTopic); html.replace("{u}", mqtt.username); html.replace("{pw}", mqtt.password); html.replace("{f}", String(mqtt.payloadFormat)); for(int i = 0; i<4; i++) { html.replace("{f" + String(i) + "}", mqtt.payloadFormat == i ? "selected" : ""); } html.replace("{s}", mqtt.ssl ? "checked" : ""); if(SPIFFS.begin()) { html.replace("{dcu}", SPIFFS.exists(FILE_MQTT_CA) ? "none" : ""); html.replace("{dcf}", SPIFFS.exists(FILE_MQTT_CA) ? "" : "none"); html.replace("{deu}", SPIFFS.exists(FILE_MQTT_CERT) ? "none" : ""); html.replace("{def}", SPIFFS.exists(FILE_MQTT_CERT) ? "" : "none"); html.replace("{dku}", SPIFFS.exists(FILE_MQTT_KEY) ? "none" : ""); html.replace("{dkf}", SPIFFS.exists(FILE_MQTT_KEY) ? "" : "none"); SPIFFS.end(); } else { html.replace("{dcu}", ""); html.replace("{dcf}", "none"); html.replace("{deu}", ""); html.replace("{def}", "none"); html.replace("{dku}", ""); html.replace("{dkf}", "none"); } server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } void AmsWebServer::configDomoticzHtml() { printD("Serving /domoticz.html over http..."); if(!checkSecurity(1)) return; String html = String((const __FlashStringHelper*) DOMOTICZ_HTML); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); DomoticzConfig domo; config->getDomoticzConfig(domo); html.replace("{elidx}", domo.elidx ? String(domo.elidx, 10) : ""); html.replace("{vl1idx}", domo.vl1idx ? String(domo.vl1idx, 10) : ""); html.replace("{vl2idx}", domo.vl2idx ? String(domo.vl2idx, 10) : ""); html.replace("{vl3idx}", domo.vl3idx ? String(domo.vl3idx, 10) : ""); html.replace("{cl1idx}", domo.cl1idx ? String(domo.cl1idx, 10) : ""); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } void AmsWebServer::configEntsoeHtml() { printD("Serving /entsoe.html over http..."); if(!checkSecurity(1)) return; EntsoeConfig entsoe; config->getEntsoeConfig(entsoe); String html = String((const __FlashStringHelper*) ENTSOE_HTML); html.replace("{et}", entsoe.token); html.replace("{em}", String(entsoe.multiplier / 1000.0, 3)); html.replace("{eaNo1}", strcmp(entsoe.area, "10YNO-1--------2") == 0 ? "selected" : ""); html.replace("{eaNo2}", strcmp(entsoe.area, "10YNO-2--------T") == 0 ? "selected" : ""); html.replace("{eaNo3}", strcmp(entsoe.area, "10YNO-3--------J") == 0 ? "selected" : ""); html.replace("{eaNo4}", strcmp(entsoe.area, "10YNO-4--------9") == 0 ? "selected" : ""); html.replace("{eaNo5}", strcmp(entsoe.area, "10Y1001A1001A48H") == 0 ? "selected" : ""); html.replace("{eaSe1}", strcmp(entsoe.area, "10Y1001A1001A44P") == 0 ? "selected" : ""); html.replace("{eaSe2}", strcmp(entsoe.area, "10Y1001A1001A45N") == 0 ? "selected" : ""); html.replace("{eaSe3}", strcmp(entsoe.area, "10Y1001A1001A46L") == 0 ? "selected" : ""); html.replace("{eaSe4}", strcmp(entsoe.area, "10Y1001A1001A47J") == 0 ? "selected" : ""); html.replace("{eaDk1}", strcmp(entsoe.area, "10YDK-1--------W") == 0 ? "selected" : ""); html.replace("{eaDk2}", strcmp(entsoe.area, "10YDK-2--------M") == 0 ? "selected" : ""); html.replace("{ecNOK}", strcmp(entsoe.area, "NOK") == 0 ? "selected" : ""); html.replace("{ecSEK}", strcmp(entsoe.area, "SEK") == 0 ? "selected" : ""); html.replace("{ecDKK}", strcmp(entsoe.area, "DKK") == 0 ? "selected" : ""); html.replace("{ecEUR}", strcmp(entsoe.area, "EUR") == 0 ? "selected" : ""); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } void AmsWebServer::configWebHtml() { printD("Serving /web.html over http..."); if(!checkSecurity(1)) return; String html = String((const __FlashStringHelper*) WEB_HTML); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); html.replace("{as}", String(webConfig.security)); for(int i = 0; i<3; i++) { html.replace("{as" + String(i) + "}", webConfig.security == i ? "selected" : ""); } html.replace("{au}", webConfig.username); html.replace("{ap}", webConfig.password); server.setContentLength(html.length() + HEAD_HTML_LEN + FOOT_HTML_LEN); server.send_P(200, "text/html", HEAD_HTML); server.sendContent(html); server.sendContent_P(FOOT_HTML); } void AmsWebServer::bootCss() { printD("Serving /boot.css over http..."); server.sendHeader("Cache-Control", "public, max-age=3600"); server.send_P(200, "text/css", BOOT_CSS); } void AmsWebServer::gaugemeterJs() { printD("Serving /gaugemeter.js over http..."); server.sendHeader("Cache-Control", "public, max-age=3600"); server.send_P(200, "application/javascript", GAUGEMETER_JS); } void AmsWebServer::githubSvg() { printD("Serving /github.svg over http..."); server.sendHeader("Cache-Control", "public, max-age=3600"); server.send_P(200, "image/svg+xml", GITHUB_SVG); } void AmsWebServer::dataJson() { printD("Serving /data.json over http..."); uint64_t now = millis64(); if(!checkSecurity(2)) return; float vcc = hw->getVcc(); int rssi = hw->getWifiRssi(); uint8_t espStatus; #if defined(ESP8266) if(vcc == 0) { espStatus = 0; } else if(vcc > 3.1 && vcc < 3.5) { espStatus = 1; } else if(vcc > 3.0 && vcc < 3.6) { espStatus = 2; } else { espStatus = 3; } #elif defined(ESP32) if(vcc == 0) { espStatus = 0; } else if(vcc > 2.8 && vcc < 3.5) { espStatus = 1; } else if(vcc > 2.2 && vcc < 3.6) { espStatus = 2; } else { espStatus = 3; } #endif uint8_t hanStatus; if(meterConfig->type == 0) { hanStatus = 0; } else if(now - meterState->getLastUpdateMillis() < 15000) { hanStatus = 1; } else if(now - meterState->getLastUpdateMillis() < 30000) { hanStatus = 2; } else { hanStatus = 3; } uint8_t wifiStatus; if(rssi > -75) { wifiStatus = 1; } else if(rssi > -95) { wifiStatus = 2; } else { wifiStatus = 3; } uint8_t mqttStatus; if(!mqttEnabled) { mqttStatus = 0; } else if(mqtt->connected()) { mqttStatus = 1; } else if(mqtt->lastError() == 0) { mqttStatus = 2; } else { mqttStatus = 3; } char json[280]; snprintf_P(json, sizeof(json), DATA_JSON, maxPwr == 0 ? meterState->isThreePhase() ? 20000 : 10000 : maxPwr, meterConfig->productionCapacity, meterConfig->mainFuse == 0 ? 32 : meterConfig->mainFuse, meterState->getActiveImportPower(), meterState->getActiveExportPower(), meterState->getReactiveImportPower(), meterState->getReactiveExportPower(), meterState->getActiveImportCounter(), meterState->getReactiveExportCounter(), meterState->getReactiveImportCounter(), meterState->getReactiveExportCounter(), meterState->getL1Voltage(), meterState->getL2Voltage(), meterState->getL3Voltage(), meterState->getL1Current(), meterState->getL2Current(), meterState->getL3Current(), vcc, rssi, hw->getTemperature(), (uint32_t) (now / 1000), ESP.getFreeHeap(), espStatus, hanStatus, wifiStatus, mqttStatus, (int) mqtt->lastError() ); server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); server.setContentLength(strlen(json)); server.send(200, "application/json", json); } void AmsWebServer::handleSetup() { printD("Handling setup method from http"); if(!server.hasArg("wifiSsid") || server.arg("wifiSsid").isEmpty() || !server.hasArg("wifiPassword") || server.arg("wifiPassword").isEmpty()) { server.sendHeader("Location", String("/"), true); server.send (302, "text/plain", ""); } else { SystemConfig sys { server.arg("board").toInt() }; config->clearGpio(*gpioConfig); config->clearMeter(*meterConfig); switch(sys.boardType) { case 0: // roarfred gpioConfig->hanPin = 3; gpioConfig->apPin = 0; gpioConfig->ledPin = 2; gpioConfig->ledInverted = true; gpioConfig->tempSensorPin = 5; break; case 1: // Arnio Kamstrup gpioConfig->hanPin = 3; gpioConfig->apPin = 0; gpioConfig->ledPin = 2; gpioConfig->ledInverted = true; gpioConfig->tempSensorPin = 5; gpioConfig->ledPinRed = 13; gpioConfig->ledPinGreen = 14; gpioConfig->ledRgbInverted = true; break; case 2: // spenceme gpioConfig->hanPin = 3; gpioConfig->apPin = 0; gpioConfig->ledPin = 2; gpioConfig->ledInverted = true; gpioConfig->tempSensorPin = 5; gpioConfig->vccBootLimit = 33; break; case 101: // D1 gpioConfig->hanPin = 5; gpioConfig->apPin = 4; gpioConfig->ledPin = 2; gpioConfig->ledInverted = true; gpioConfig->tempSensorPin = 14; gpioConfig->vccMultiplier = 1100; break; case 100: // ESP8266 gpioConfig->hanPin = 3; gpioConfig->ledPin = 2; gpioConfig->ledInverted = true; break; case 201: // D32 gpioConfig->hanPin = 16; gpioConfig->apPin = 4; gpioConfig->ledPin = 5; gpioConfig->ledInverted = true; gpioConfig->tempSensorPin = 14; gpioConfig->vccPin = 35; gpioConfig->vccMultiplier = 2250; break; case 202: // Feather gpioConfig->hanPin = 16; gpioConfig->ledPin = 2; gpioConfig->ledInverted = false; gpioConfig->tempSensorPin = 14; break; case 203: // DevKitC gpioConfig->hanPin = 16; gpioConfig->ledPin = 2; gpioConfig->ledInverted = false; break; case 200: // ESP32 gpioConfig->hanPin = 16; gpioConfig->apPin = 0; gpioConfig->ledPin = 2; gpioConfig->ledInverted = false; gpioConfig->tempSensorPin = 14; break; } WiFiConfig wifi; config->clearWifi(wifi); strcpy(wifi.ssid, server.arg("wifiSsid").c_str()); strcpy(wifi.psk, server.arg("wifiPassword").c_str()); if(server.hasArg("wifiIpType") && server.arg("wifiIpType").toInt() == 1) { strcpy(wifi.ip, server.arg("wifiIp").c_str()); strcpy(wifi.gateway, server.arg("wifiGw").c_str()); strcpy(wifi.subnet, server.arg("wifiSubnet").c_str()); strcpy(wifi.dns1, server.arg("wifiDns1").c_str()); } if(server.hasArg("wifiHostname") && !server.arg("wifiHostname").isEmpty()) { strcpy(wifi.hostname, server.arg("wifiHostname").c_str()); wifi.mdns = true; } else { wifi.mdns = false; } MqttConfig mqttConfig; config->clearMqtt(mqttConfig); config->clearAuth(webConfig); NtpConfig ntp; config->clearNtp(ntp); bool success = true; if(!config->setSystemConfig(sys)) { printD("Unable to set system config"); success = false; } if(!config->setWiFiConfig(wifi)) { printD("Unable to set WiFi config"); success = false; } if(!config->setMqttConfig(mqttConfig)) { printD("Unable to set MQTT config"); success = false; } if(!config->setWebConfig(webConfig)) { printD("Unable to set web config"); success = false; } if(!config->setMeterConfig(*meterConfig)) { printD("Unable to set meter config"); success = false; } if(!config->setGpioConfig(*gpioConfig)) { printD("Unable to set GPIO config"); success = false; } if(!config->setNtpConfig(ntp)) { printD("Unable to set NTP config"); success = false; } if(success && config->save()) { performRestart = true; server.sendHeader("Location","/restart-wait"); server.send(303); } else { printE("Error saving configuration"); String html = "