TKK_E32230469/firmware_iot_esp12f/upsSvr.h

538 lines
16 KiB
C

//todo:
// - more accurate TX/RX indication (for security concern), e.g. favicon is served static, thus not display TX/RX arrow.
// - Login and apikey access for next version
#include "wifi.h"
#include "upsCfg.h"
#ifndef UPSIO_H
#include "upsIo.h"
#endif
//#include <AsyncTCP.h> //for esp32
#include <ESPAsyncTCP.h> //for esp8266
#include <ESPAsyncWebServer.h>
const char* http_username = "admin";
const char* http_password = "admin123";
String base64Decode(const String& input) {
const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
String out;
int buf = 0, bits = 0;
for (size_t i = 0; i < input.length(); i++) {
char c = input[i];
if (c == '=') break;
const char* p = strchr(b64, c);
if (!p) continue;
buf = (buf << 6) | (p - b64);
bits += 6;
if (bits >= 8) {
bits -= 8;
out += (char)((buf >> bits) & 0xFF);
buf &= (1 << bits) - 1;
}
}
return out;
}
bool checkAuth(AsyncWebServerRequest *request) {
if (!request->hasHeader("Authorization")) return false;
String auth = request->header("Authorization");
if (!auth.startsWith("Basic ")) return false;
String b64 = auth.substring(6);
b64.trim();
String decoded = base64Decode(b64);
int sep = decoded.indexOf(':');
if (sep < 0) return false;
String user = decoded.substring(0, sep);
String pass = decoded.substring(sep + 1);
return user.equals(http_username) && pass.equals(http_password);
}
int8_t wifiStatePrev = 99;
bool configChanged = false;
AsyncWebServer server(80);
void applyConfigs() {
autoPowerOn = (cfgAutoOn >= AUTO_POWER_ON); //auto power on
//cfgChargeMode: charge mode:
switch (cfgChargeMode) {
case CHARGE_MODE_ALWAYS:
{
chargeAlwaysActive = true;
}
break;
case CHARGE_MODE_RANGE_90_100:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 100;
chargePercentageForResume = 90;
}
break;
case CHARGE_MODE_RANGE_85_100:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 100;
chargePercentageForResume = 85;
}
break;
case CHARGE_MODE_RANGE_80_100:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 100;
chargePercentageForResume = 80;
}
break;
case CHARGE_MODE_RANGE_90_95:
{
chargeAlwaysActive = false;
chargeToFull = false;
chargePercentageForStop = 95;
chargePercentageForResume = 90;
}
break;
case CHARGE_MODE_RANGE_85_95:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 95;
chargePercentageForResume = 85;
}
break;
case CHARGE_MODE_RANGE_80_95:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 95;
chargePercentageForResume = 80;
}
break;
case CHARGE_MODE_RANGE_85_90:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 90;
chargePercentageForResume = 85;
}
break;
case CHARGE_MODE_RANGE_80_90:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 90;
chargePercentageForResume = 80;
}
break;
case CHARGE_MODE_RANGE_80_85:
{
chargeAlwaysActive = false;
chargeToFull = true;
chargePercentageForStop = 85;
chargePercentageForResume = 80;
}
break;
}
//cfgInputLostTriggerChargeMode:
switch (cfgInputLostTriggerChargeMode) {
case INPUT_LOST_TRIGGER_CHARGE_NONE:
{
chargeAfterInputLost = false;
}
break;
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_ALWAYS:
{
chargeAfterInputLost = true;
fullChargeAfterInputLost = true;
}
break;
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_95:
{
chargeAfterInputLost = true;
fullChargeAfterInputLost = false;
triggerFullChargeAfterInputLostAtPercentage = 95;
}
break;
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_90:
{
chargeAfterInputLost = true;
fullChargeAfterInputLost = false;
triggerFullChargeAfterInputLostAtPercentage = 90;
}
break;
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_85:
{
chargeAfterInputLost = true;
fullChargeAfterInputLost = false;
triggerFullChargeAfterInputLostAtPercentage = 85;
}
break;
case INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80:
{
chargeAfterInputLost = true;
fullChargeAfterInputLost = false;
triggerFullChargeAfterInputLostAtPercentage = 80;
}
break;
}
//cfgChargeOvervoltageProtection:
chargeOvervoltageProtection = (cfgChargeOvervoltageProtection > 0);
//cfgChargeAutomaticReducer:
chargeReduceAtBatteryFull = (cfgChargeAutomaticReducer > 0);
}
bool getShutdownSuggestionFlag() {
if (cfgShutdownSuggestion == SHUTDOWN_SUGGESTION_DISABLED) return false;
if (!startupReady) return false;
switch (cfgShutdownSuggestion) {
case SHUTDOWN_SUGGESTION_ON_BATT:
{
return powerOutputActive && !inputVoltageKeepup;
}
break;
case SHUTDOWN_SUGGESTION_ON_BATT_75:
{
return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 3);
}
break;
case SHUTDOWN_SUGGESTION_ON_BATT_50:
{
return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 2);
}
break;
case SHUTDOWN_SUGGESTION_ON_BATT_25:
{
return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 1);
}
break;
case SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL:
{
return powerOutputActive && !inputVoltageKeepup && (batteryLevel <= 1) && isUpsBatteryCritical();
}
break;
default:
{
return false;
}
}
return false;
}
void handleGetState(AsyncWebServerRequest *request) {
if (!checkAuth(request)) {
setNetworkIndicatorInTransmission(true, false);
AsyncWebServerResponse *response = request->beginResponse(401, "application/json", "{\"success\":0}");
// response->addHeader("WWW-Authenticate", "Basic realm=\"UPS Login\""); // Removed to prevent native browser prompt
request->send(response);
setNetworkIndicatorInTransmission(true, true);
return;
}
setNetworkIndicatorInTransmission(true, false);
bool isRefresh = true;
String v = "";
uint8_t i = 0;
if (request->hasParam("on")) {
isRefresh = false;
v = request->getParam("on")->value();
bool tgtState = v != "0";
Serial.print("Switch request: ");
Serial.println(tgtState);
if (tgtState != powerOutputActive) {
setOutputPowerState(tgtState);
Serial.println("Switching...");
}
}
if (request->hasParam("auto")) {
isRefresh = false;
v = request->getParam("auto")->value();
uint8_t tmpAutoOnMode = v.toInt();
if ((tmpAutoOnMode != cfgAutoOn) && (tmpAutoOnMode <= AUTO_POWER_PERSIST)) {
cfgAutoOn = tmpAutoOnMode;
configChanged = true;
}
}
if (request->hasParam("shutdown")) {
isRefresh = false;
v = request->getParam("shutdown")->value();
uint16_t tSec = v.toInt();
if (tSec > 999) tSec = 999;
uint32_t nanoT = ((uint32_t)tSec) * 1000000UL;
if (nanoT < 999999) nanoT = 999999;
bool ok = setPersistentShutdown(nanoT);
if ((cfgShutdownInSeconds != tSec && ok)) {
cfgShutdownInSeconds = tSec;
configChanged = true;
}
Serial.print("Timed Shutdown request: ");
Serial.println(tSec);
//Serial.println(ok);
}
if (request->hasParam("shutlv")) {
isRefresh = false;
v = request->getParam("shutlv")->value();
int16_t lv = v.toInt();
if (lv >= SHUTDOWN_SUGGESTION_DISABLED && lv <= SHUTDOWN_SUGGESTION_ON_BATT_CRITICAL) {
if (lv != cfgShutdownSuggestion) {
cfgShutdownSuggestion = lv;
configChanged = true;
Serial.print("Shutdown level changed: ");
Serial.println(lv);
}
} else {
Serial.print("Invalid shutdown level: ");
Serial.println(v);
}
}
//battery management params:
//cfgChargeMode (charge mode):
if (request->hasParam("cm")) {
isRefresh = false;
v = request->getParam("cm")->value();
i = v.toInt();
if (i <= CHARGE_MODE_RANGE_80_85) {
cfgChargeMode = i;
configChanged = true;
Serial.print("CM: ");
Serial.println(i);
}
}
//cfgInputLostTriggerChargeMode (charge on input lost):
if (request->hasParam("coil")) {
isRefresh = false;
v = request->getParam("coil")->value();
i = v.toInt();
if (i <= INPUT_LOST_TRIGGER_CHARGE_ACTIVE_FULL_80) {
cfgInputLostTriggerChargeMode = i;
configChanged = true;
Serial.print("COIL: ");
Serial.println(i);
}
}
//cfgChargeOvervoltageProtection (charge overvoltage protection):
if (request->hasParam("covp")) {
isRefresh = false;
v = request->getParam("covp")->value();
i = v.toInt();
if (i > 1) i = 1;
if (i != cfgChargeOvervoltageProtection) {
cfgChargeOvervoltageProtection = i;
configChanged = true;
Serial.print("COVP: ");
Serial.println(i);
}
}
//reset "On-Going Full Charge" triggered after input lost (reset ongoing full charge):
if (request->hasParam("rofc")) {
isRefresh = false;
v = request->getParam("rofc")->value();
i = v.toInt();
if (i > 0 && needFullChargeOnce) {
resetOnGoingFullChargeAfterLineLost();
Serial.print("ROFC: ");
Serial.println(i);
}
}
//cfgChargeAutomaticReducer (charge automatic reducer)
if (request->hasParam("car")) {
isRefresh = false;
v = request->getParam("car")->value();
i = v.toInt();
if (i > 1) i = 1;
if (i != cfgChargeAutomaticReducer) {
cfgChargeAutomaticReducer = i;
configChanged = true;
Serial.print("CAR: ");
Serial.println(i);
}
}
DynamicJsonDocument jsonDoc(1536);
String jsonResponse;
//String stateOn = isUpsOn()?"1":"0";
//String autoOn = String(cfgAutoOn);
//String voltageValue = String(batteryVoltage, 2);
//2 battery, 1 Input line or AC, 0 OFF
//String lineActive = isUpsPoweredFromBattery()?"2":isInputLinePowered()?"1":isUpsOn()?"2":"0";
uint8_t lineActiveState = isUpsPoweredFromBattery() ? 2 : isInputLinePowered() ? 1
: isUpsOn() ? 2
: 0;
//String batt = String(batteryLevel);
//String battMax = "4";
// String battBlink = IsUpsChargingOrBlink()?"1":"0";
// String chargeLv = IsUpsChargingOrBlink()?(String(UpsBattMinimalLevelUnderChargingOrBlink())):(String(UpsBatteryLevel()));
// String battCritical = IsUpsBatteryCritical()?"1":"0";
// String isTimedShut = timedShutdownFlag?"1":"0";
// String onShutRemaining = String(GetShutdownCountdown());
// String shutdownSuggest = GetShutdownSuggestionFlag()?"1":"0";
// String shutdownSuggestMode = String(cfgShutdownSuggestion);
// String shutdownLastPeriod = String(timeShutdownLastPeriod);
//json vars:
jsonDoc["success"] = isRefresh ? 2 : 1;
jsonDoc["on"] = powerOutputActive ? 1 : 0;
;
jsonDoc["state"] = isUpsOn() ? 1 : 0;
jsonDoc["v"] = String(batteryVoltage, 2);
jsonDoc["batt"] = batteryLevel;
jsonDoc["battP"] = batteryPercentage;
jsonDoc["line"] = lineActiveState;
jsonDoc["auto"] = cfgAutoOn;
jsonDoc["battBlink"] = isUpsBatteryIndicatorChargingOrBlink() ? 1 : 0;
jsonDoc["battCritical"] = isUpsBatteryCritical() ? 1 : 0;
jsonDoc["chargeLv"] = getRawBatteryLevelFromPercentage(batteryPercentage); //real level
jsonDoc["batt"] = batteryLevel; //staged level
jsonDoc["battMax"] = 4;
jsonDoc["timedShutdown"] = (persistentShutdown && powerOutputActive) ? 1 : 0;
jsonDoc["shutdownRemain"] = getPersistentShutdownTick() / 1000000;
jsonDoc["shutdownSuggestMode"] = cfgShutdownSuggestion;
jsonDoc["shutdownSuggest"] = getShutdownSuggestionFlag() ? 1 : 0; //internal flag raised if cfgShutdownSuggestion fullfill the condition
jsonDoc["shutLastTimer"] = cfgShutdownInSeconds;
//charge configuration:
jsonDoc["chgError"] = chargeError; //error state
jsonDoc["charging"] = getChargingState(); //on charging state
jsonDoc["chgMode"] = cfgChargeMode;
jsonDoc["chgOVP"] = cfgChargeOvervoltageProtection; //over voltage protection
jsonDoc["chgReducer"] = cfgChargeAutomaticReducer;
jsonDoc["chgFullTrig"] = cfgInputLostTriggerChargeMode;
jsonDoc["chgOFC"] = needFullChargeOnce ? 1 : 0; //when input lost and "Ongoing Full Charge" flag triggered
jsonDoc["vSub"] = String(batteryVoltageUnfiltered, 2);
jsonDoc["chgSwitch"] = isChargeActive ? 1 : 0;
jsonDoc["inputUV"] = inputUnderVoltage ? 1 : 0;
jsonDoc["inputVExist"] = inputVoltageKeepup ? 1 : 0;
jsonDoc["btn"] = buttonState ? 1 : 0;
jsonDoc["btnShort"] = buttonShortPress ? 1 : 0;
jsonDoc["btnLong"] = buttonLongPress ? 1 : 0;
jsonDoc["keepPower"] = keepPowerState ? 1 : 0;
jsonDoc["display"] = displayActive ? 1 : 0;
jsonDoc["rssi"] = rssi;
jsonDoc["chargeAllowed"] = chargeAllowed ? 1 : 0;
serializeJson(jsonDoc, jsonResponse);
request->send(200, "application/json", jsonResponse);
setNetworkIndicatorInTransmission(true, true);
}
void onRootRequest(AsyncWebServerRequest *request) {
setNetworkIndicatorInTransmission(true, false);
Serial.println("onRootRequest");
bool isOK = false;
if (spiffsOk) {
if (LittleFS.exists("/index.html")) {
isOK = true;
request->send(LittleFS, "/index.html", "text/html");
setNetworkIndicatorInTransmission(true, true);
}
}
if (!isOK) {
request->send(404, "text/text", "404: Not Found");
setNetworkIndicatorInTransmission(true, true);
}
}
void upsServerInit() {
bool b = initConfigs();
if (b) {
loadCfg();
applyConfigs();
}
//transfer configuration to ups library:
if (cfgAutoOn >= AUTO_POWER_ON) {
autoPowerOn = true;
restartAutoPowerOn();
} else {
autoPowerOn = false;
}
//for favicon
server.serveStatic("/favicon.ico", LittleFS, "/favicon.ico");
server.serveStatic("*/favicon.ico", LittleFS, "/favicon.ico");
//login endpoint: validate credentials
server.on("/login", HTTP_GET, [](AsyncWebServerRequest *request) {
setNetworkIndicatorInTransmission(true, false);
if (!checkAuth(request)) {
request->send(401, "application/json", "{\"success\":0}");
} else {
request->send(200, "application/json", "{\"success\":1}");
}
setNetworkIndicatorInTransmission(true, true);
});
//logout endpoint: clear basic auth by returning 401
server.on("/logout", HTTP_GET, [](AsyncWebServerRequest *request) {
setNetworkIndicatorInTransmission(true, false);
AsyncWebServerResponse *response = request->beginResponse(401, "application/json", "{\"success\":0,\"message\":\"Logged out\"}");
request->send(response);
setNetworkIndicatorInTransmission(true, true);
});
//for RESTapi
server.on("/state", HTTP_GET, handleGetState);
//root
server.on("/", onRootRequest);
server.on("/*", onRootRequest);
//Redirect all not-found requests to the root
server.onNotFound(onRootRequest);
server.begin();
Serial.println("Server is running!");
}
void upsServerRoutine() {
WiFiManagerRoutine();
//updating wifi state:
if (wifiCurrentState != wifiStatePrev) {
wifiStatePrev = wifiCurrentState;
setNetworkState(wifiStatePrev);
}
//update signal:
indicatorNetworkSignalLevel = wifiSignalLevel;
//manage auto on mode:
if (cfgAutoOn == AUTO_POWER_PERSIST) {
if (inputVoltageKeepup && inputVoltageOK) {
if (restartAutoPowerOn()) Serial.println("Automatic ON restarted");
}
}
//if button long pressed -------------------------------------------
if (buttonLongPressIgnore && buttonLongPress) {
buttonLongPressIgnore = true;
Serial.println("Long Pressed Button Action is Reserved!");
//(not used)
}
//------------------------------------------------------------------
//saving configuration:
if (configChanged) {
configChanged = false;
saveCfg();
applyConfigs();
}
}