mirror of
https://github.com/lumapu/ahoy.git
synced 2025-05-25 14:56:11 +02:00
added interface class for app
refactored web and webApi -> now RestApi.h fix calcSunrise fixed calcSunrise trigger calculation display zero values on /live added changes from #483
This commit is contained in:
parent
a8d7e430f7
commit
49b530a743
19 changed files with 1366 additions and 1511 deletions
|
@ -114,13 +114,13 @@ inverter/ctrl/limit_persistent_relative/0 70
|
||||||
|
|
||||||
### Power Limit absolute persistent [Watts]
|
### Power Limit absolute persistent [Watts]
|
||||||
```mqtt
|
```mqtt
|
||||||
<TOPIC>/ctrl/limit_persistent_relative/<INVERTER_ID>
|
<TOPIC>/ctrl/limit_persistent_absolute/<INVERTER_ID>
|
||||||
```
|
```
|
||||||
with a payload `[0 .. 65535]`
|
with a payload `[0 .. 65535]`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```mqtt
|
```mqtt
|
||||||
inverter/ctrl/limit_persistent_relative/0 600
|
inverter/ctrl/limit_persistent_absolute/0 600
|
||||||
```
|
```
|
||||||
|
|
||||||
### Power Limit relative non persistent [%]
|
### Power Limit relative non persistent [%]
|
||||||
|
@ -136,13 +136,13 @@ inverter/ctrl/limit_nonpersistent_relative/0 70
|
||||||
|
|
||||||
### Power Limit absolute non persistent [Watts]
|
### Power Limit absolute non persistent [Watts]
|
||||||
```mqtt
|
```mqtt
|
||||||
<TOPIC>/ctrl/limit_nonpersistent_relative/<INVERTER_ID>
|
<TOPIC>/ctrl/limit_nonpersistent_absolute/<INVERTER_ID>
|
||||||
```
|
```
|
||||||
with a payload `[0 .. 65535]`
|
with a payload `[0 .. 65535]`
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
```mqtt
|
```mqtt
|
||||||
inverter/ctrl/limit_nonpersistent_relative/0 600
|
inverter/ctrl/limit_nonpersistent_absolute/0 600
|
||||||
```
|
```
|
||||||
|
|
||||||
## Control via REST API
|
## Control via REST API
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
# Changelog
|
# 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
|
## 0.5.51
|
||||||
* improved scheduler, @beegee3 #483
|
* improved scheduler, @beegee3 #483
|
||||||
* refactored get NTP time, @beegee3 #483
|
* refactored get NTP time, @beegee3 #483
|
||||||
|
|
43
src/app.cpp
43
src/app.cpp
|
@ -13,9 +13,7 @@
|
||||||
#include "utils/sun.h"
|
#include "utils/sun.h"
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
app::app() : ah::Scheduler() {
|
app::app() : ah::Scheduler() {}
|
||||||
mWeb = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
@ -72,10 +70,19 @@ void app::setup() {
|
||||||
#endif
|
#endif
|
||||||
setupLed();
|
setupLed();
|
||||||
|
|
||||||
mWeb = new web(this, mConfig, &mStat, mVersion);
|
mWeb.setup(this, mSys, mConfig);
|
||||||
mWeb->setup();
|
mWeb.setProtection(strlen(mConfig->sys.adminPwd) != 0);
|
||||||
mWeb->setProtection(strlen(mConfig->sys.adminPwd) != 0);
|
everySec(std::bind(&WebType::tickSecond, &mWeb));
|
||||||
everySec(std::bind(&web::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
|
// Plugins
|
||||||
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
|
#if defined(ENA_NOKIA) || defined(ENA_SSD1306)
|
||||||
|
@ -98,11 +105,11 @@ void app::loop(void) {
|
||||||
mWifi.loop();
|
mWifi.loop();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
mWeb->loop();
|
mWeb.loop();
|
||||||
|
|
||||||
if (mFlagSendDiscoveryConfig) {
|
if (mFlagSendDiscoveryConfig) {
|
||||||
mFlagSendDiscoveryConfig = false;
|
mFlagSendDiscoveryConfig = false;
|
||||||
mMqtt.sendMqttDiscoveryConfig(mConfig->mqtt.topic);
|
mMqtt.sendDiscoveryConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
mSys->Radio.loop();
|
mSys->Radio.loop();
|
||||||
|
@ -154,7 +161,7 @@ void app::tickCalcSunrise(void) {
|
||||||
}
|
}
|
||||||
ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset);
|
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);
|
onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig);
|
||||||
if (mConfig->mqtt.broker[0] > 0) {
|
if (mConfig->mqtt.broker[0] > 0) {
|
||||||
once(std::bind(&PubMqttType::tickerSun, &mMqtt), 1);
|
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);
|
mSys->Radio.sendControlPacket(iv->radioId.u64, iv->devControlCmd, iv->powerLimit);
|
||||||
mPayload.setTxCmd(iv, iv->devControlCmd);
|
mPayload.setTxCmd(iv, iv->devControlCmd);
|
||||||
iv->clearCmdQueue();
|
iv->clearCmdQueue();
|
||||||
iv->enqueCommand<InfoCommand>(SystemConfigPara);
|
iv->enqueCommand<InfoCommand>(SystemConfigPara); // read back power limit
|
||||||
} else {
|
} else {
|
||||||
uint8_t cmd = iv->getQueuedCmd();
|
uint8_t cmd = iv->getQueuedCmd();
|
||||||
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket"));
|
DPRINTLN(DBG_INFO, F("(#") + String(iv->id) + F(") sendTimePacket"));
|
||||||
|
@ -239,17 +246,6 @@ void app::handleIntr(void) {
|
||||||
mSys->Radio.handleIntr();
|
mSys->Radio.handleIntr();
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
void app::scanAvailNetworks(void) {
|
|
||||||
mWifi.scanAvailNetworks();
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
void app::getAvailNetworks(JsonObject obj) {
|
|
||||||
mWifi.getAvailNetworks(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void app::resetSystem(void) {
|
void app::resetSystem(void) {
|
||||||
snprintf(mVersion, 12, "%d.%d.%d", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
|
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) {
|
void app::mqttSubRxCb(JsonObject obj) {
|
||||||
if(NULL != mWeb)
|
mApi.ctrlRequest(obj);
|
||||||
mWeb->apiCtrlRequest(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
106
src/app.h
106
src/app.h
|
@ -6,12 +6,15 @@
|
||||||
#ifndef __APP_H__
|
#ifndef __APP_H__
|
||||||
#define __APP_H__
|
#define __APP_H__
|
||||||
|
|
||||||
|
|
||||||
#include "utils/dbg.h"
|
#include "utils/dbg.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <RF24.h>
|
#include <RF24.h>
|
||||||
#include <RF24_config.h>
|
#include <RF24_config.h>
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
#include "appInterface.h"
|
||||||
|
|
||||||
#include "config/settings.h"
|
#include "config/settings.h"
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#include "utils/crc.h"
|
#include "utils/crc.h"
|
||||||
|
@ -23,6 +26,7 @@
|
||||||
#include "hm/payload.h"
|
#include "hm/payload.h"
|
||||||
#include "wifi/ahoywifi.h"
|
#include "wifi/ahoywifi.h"
|
||||||
#include "web/web.h"
|
#include "web/web.h"
|
||||||
|
#include "web/RestApi.h"
|
||||||
|
|
||||||
#include "publisher/pubMqtt.h"
|
#include "publisher/pubMqtt.h"
|
||||||
#include "publisher/pubSerial.h"
|
#include "publisher/pubSerial.h"
|
||||||
|
@ -36,6 +40,8 @@
|
||||||
|
|
||||||
typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
|
typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
|
||||||
typedef Payload<HmSystemType> PayloadType;
|
typedef Payload<HmSystemType> PayloadType;
|
||||||
|
typedef Web<HmSystemType> WebType;
|
||||||
|
typedef RestApi<HmSystemType> RestApiType;
|
||||||
typedef PubMqtt<HmSystemType> PubMqttType;
|
typedef PubMqtt<HmSystemType> PubMqttType;
|
||||||
typedef PubSerial<HmSystemType> PubSerialType;
|
typedef PubSerial<HmSystemType> PubSerialType;
|
||||||
|
|
||||||
|
@ -45,9 +51,8 @@ typedef PubSerial<HmSystemType> PubSerialType;
|
||||||
typedef MonochromeDisplay<HmSystemType> MonoDisplayType;
|
typedef MonochromeDisplay<HmSystemType> MonoDisplayType;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class web;
|
|
||||||
|
|
||||||
class app : public ah::Scheduler {
|
class app : public IApp, public ah::Scheduler {
|
||||||
public:
|
public:
|
||||||
app();
|
app();
|
||||||
~app() {}
|
~app() {}
|
||||||
|
@ -59,35 +64,77 @@ class app : public ah::Scheduler {
|
||||||
void saveValues(void);
|
void saveValues(void);
|
||||||
void resetPayload(Inverter<>* iv);
|
void resetPayload(Inverter<>* iv);
|
||||||
bool getWifiApActive(void);
|
bool getWifiApActive(void);
|
||||||
void scanAvailNetworks(void);
|
|
||||||
void getAvailNetworks(JsonObject obj);
|
|
||||||
|
|
||||||
void saveSettings(void) {
|
uint32_t getUptime() {
|
||||||
mSettings.saveSettings();
|
return Scheduler::getUptime();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getTimestamp() {
|
||||||
|
return Scheduler::getTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool saveSettings() {
|
||||||
|
return mSettings.saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool eraseSettings(bool eraseWifi = false) {
|
bool eraseSettings(bool eraseWifi = false) {
|
||||||
return mSettings.eraseSettings(eraseWifi);
|
return mSettings.eraseSettings(eraseWifi);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t getIrqPin(void) {
|
statistics_t *getStatistics() {
|
||||||
return mConfig->nrf.pinIrq;
|
return &mStat;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Serial2u64(const char *val) {
|
void scanAvailNetworks() {
|
||||||
char tmp[3];
|
mWifi.scanAvailNetworks();
|
||||||
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 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) {
|
String getTimeStr(uint32_t offset = 0) {
|
||||||
|
@ -107,21 +154,8 @@ class app : public ah::Scheduler {
|
||||||
Scheduler::setTimestamp(newTime);
|
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;
|
HmSystemType *mSys;
|
||||||
bool mShouldReboot;
|
bool mShouldReboot;
|
||||||
bool mFlagSendDiscoveryConfig;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void resetSystem(void);
|
void resetSystem(void);
|
||||||
|
@ -169,11 +203,13 @@ class app : public ah::Scheduler {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mUpdateNtp;
|
bool mUpdateNtp;
|
||||||
|
bool mFlagSendDiscoveryConfig;
|
||||||
|
|
||||||
bool mShowRebootRequest;
|
bool mShowRebootRequest;
|
||||||
|
|
||||||
ahoywifi mWifi;
|
ahoywifi mWifi;
|
||||||
web *mWeb;
|
WebType mWeb;
|
||||||
|
RestApiType mApi;
|
||||||
PayloadType mPayload;
|
PayloadType mPayload;
|
||||||
PubSerialType mPubSerial;
|
PubSerialType mPubSerial;
|
||||||
|
|
||||||
|
|
39
src/appInterface.h
Normal file
39
src/appInterface.h
Normal file
|
@ -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__*/
|
|
@ -13,7 +13,7 @@
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 5
|
#define VERSION_MINOR 5
|
||||||
#define VERSION_PATCH 51
|
#define VERSION_PATCH 52
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
|
@ -149,7 +149,7 @@ class PubMqtt {
|
||||||
return mRxCnt;
|
return mRxCnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMqttDiscoveryConfig(const char *topic) {
|
void sendDiscoveryConfig(void) {
|
||||||
DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig"));
|
DPRINTLN(DBG_VERBOSE, F("sendMqttDiscoveryConfig"));
|
||||||
|
|
||||||
char stateTopic[64], discoveryTopic[64], buffer[512], name[32], uniq_id[32];
|
char stateTopic[64], discoveryTopic[64], buffer[512], name[32], uniq_id[32];
|
||||||
|
@ -172,14 +172,14 @@ class PubMqtt {
|
||||||
} else {
|
} else {
|
||||||
snprintf(name, 32, "%s CH%d %s", iv->config->name, rec->assign[i].ch, iv->getFieldName(i, rec));
|
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(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));
|
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 *devCls = getFieldDeviceClass(rec->assign[i].fieldId);
|
||||||
const char *stateCls = getFieldStateClass(rec->assign[i].fieldId);
|
const char *stateCls = getFieldStateClass(rec->assign[i].fieldId);
|
||||||
|
|
||||||
doc[F("name")] = name;
|
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("unit_of_meas")] = iv->getUnit(i, rec);
|
||||||
doc[F("uniq_id")] = String(iv->config->serial.u64, HEX) + "_" + uniq_id;
|
doc[F("uniq_id")] = String(iv->config->serial.u64, HEX) + "_" + uniq_id;
|
||||||
doc[F("dev")] = deviceObj;
|
doc[F("dev")] = deviceObj;
|
||||||
|
|
|
@ -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));
|
sprintf(str, "%04d-%02d-%02d %02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));
|
||||||
return String(str);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ namespace ah {
|
||||||
void ip2Char(uint8_t ip[], char *str);
|
void ip2Char(uint8_t ip[], char *str);
|
||||||
double round3(double value);
|
double round3(double value);
|
||||||
String getDateTimeStr(time_t t);
|
String getDateTimeStr(time_t t);
|
||||||
|
uint64_t Serial2u64(const char *val);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /*__HELPER_H__*/
|
#endif /*__HELPER_H__*/
|
||||||
|
|
|
@ -103,14 +103,9 @@ namespace ah {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
inline void checkEvery(void) {
|
inline void checkEvery(void) {
|
||||||
bool expired;
|
|
||||||
sP *p = mStack.getFront();
|
sP *p = mStack.getFront();
|
||||||
while(NULL != p) {
|
while(NULL != p) {
|
||||||
if(mDiffSeconds >= p->d.timeout) expired = true;
|
if(mDiffSeconds >= p->d.timeout) { // expired
|
||||||
else if((p->d.timeout--) == 0) expired = true;
|
|
||||||
else expired = false;
|
|
||||||
|
|
||||||
if(expired) {
|
|
||||||
(p->d.c)();
|
(p->d.c)();
|
||||||
yield();
|
yield();
|
||||||
if(0 == p->d.reload)
|
if(0 == p->d.reload)
|
||||||
|
@ -120,10 +115,12 @@ namespace ah {
|
||||||
p = mStack.get(p);
|
p = mStack.get(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else { // not expired
|
||||||
|
p->d.timeout -= mDiffSeconds;
|
||||||
p = mStack.get(p);
|
p = mStack.get(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inline void checkAt(void) {
|
inline void checkAt(void) {
|
||||||
sPAt *p = mStackAt.getFront();
|
sPAt *p = mStackAt.getFront();
|
||||||
|
|
538
src/web/RestApi.h
Normal file
538
src/web/RestApi.h
Normal file
|
@ -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 HMSYSTEM>
|
||||||
|
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<JsonObject>();
|
||||||
|
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<JsonObject>();
|
||||||
|
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("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>();
|
||||||
|
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<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;
|
||||||
|
iv->devControlCmd = ActivePowerContr;
|
||||||
|
iv->devControlRequest = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as<String>() + "'";
|
||||||
|
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__*/
|
|
@ -191,15 +191,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function tick() {
|
function tick() {
|
||||||
|
if(0 != ts)
|
||||||
|
document.getElementById("date").innerHTML = (new Date((ts+tickCnt) * 1000)).toLocaleString('de-DE');
|
||||||
if(++tickCnt >= 10) {
|
if(++tickCnt >= 10) {
|
||||||
tickCnt = 0;
|
tickCnt = 0;
|
||||||
getAjax('/api/index', parse);
|
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) {
|
function parse(obj) {
|
||||||
|
|
|
@ -68,7 +68,6 @@
|
||||||
|
|
||||||
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
for(var j = 0; j < root.ch0_fld_names.length; j++) {
|
||||||
var val = Math.round(iv["ch"][0][j] * 100) / 100;
|
var val = Math.round(iv["ch"][0][j] * 100) / 100;
|
||||||
if(val > 0) {
|
|
||||||
var sub = div(["subgrp"]);
|
var sub = div(["subgrp"]);
|
||||||
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
sub.appendChild(span(val + " " + span(root["ch0_fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
||||||
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
sub.appendChild(span(root["ch0_fld_names"][j], ["info"]));
|
||||||
|
@ -82,7 +81,6 @@
|
||||||
case 10: total[j] += val; break; // Q_AC
|
case 10: total[j] += val; break; // Q_AC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
main.appendChild(ch0);
|
main.appendChild(ch0);
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,11 +90,9 @@
|
||||||
|
|
||||||
for(var j = 0; j < root.fld_names.length; j++) {
|
for(var j = 0; j < root.fld_names.length; j++) {
|
||||||
var val = Math.round(iv["ch"][i][j] * 100) / 100;
|
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(val + " " + span(root["fld_units"][j], ["unit"]).innerHTML, ["value"]));
|
||||||
ch.appendChild(span(root["fld_names"][j], ["info"]));
|
ch.appendChild(span(root["fld_names"][j], ["info"]));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
main.appendChild(ch);
|
main.appendChild(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
722
src/web/web.cpp
722
src/web/web.cpp
|
@ -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("<h1>Factory Reset</h1>"
|
|
||||||
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
|
||||||
refresh = 120;
|
|
||||||
}
|
|
||||||
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
|
||||||
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<InfoCommand>(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("<!doctype html><html><head><title>Update</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Update: ");
|
|
||||||
if(reboot)
|
|
||||||
html += "success";
|
|
||||||
else
|
|
||||||
html += "failed";
|
|
||||||
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
|
||||||
|
|
||||||
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", "<rn>");
|
|
||||||
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("<rn>"))
|
|
||||||
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<String, String> 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
|
|
694
src/web/web.h
694
src/web/web.h
|
@ -14,75 +14,677 @@
|
||||||
#include "ESPAsyncTCP.h"
|
#include "ESPAsyncTCP.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ESPAsyncWebServer.h"
|
#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
|
#define WEB_SERIAL_BUF_SIZE 2048
|
||||||
|
|
||||||
class app;
|
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinLed0", "pinLed1"};
|
||||||
class webApi;
|
|
||||||
|
|
||||||
class web {
|
template<class HMSYSTEM>
|
||||||
|
class Web {
|
||||||
public:
|
public:
|
||||||
web(app *main, settings_t *config, statistics_t *stat, char version[]);
|
Web(void) {
|
||||||
~web() {}
|
mProtected = true;
|
||||||
|
mLogoutTimeout = 0;
|
||||||
|
|
||||||
void setup(void);
|
memset(mSerialBuf, 0, WEB_SERIAL_BUF_SIZE);
|
||||||
void loop(void);
|
mSerialBufFill = 0;
|
||||||
void tickSecond();
|
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));
|
||||||
|
|
||||||
void onUpdate(AsyncWebServerRequest *request);
|
mWeb->on("/setup", HTTP_GET, std::bind(&Web::onSetup, this, std::placeholders::_1));
|
||||||
void showUpdate(AsyncWebServerRequest *request);
|
mWeb->on("/save", HTTP_ANY, std::bind(&Web::showSave, this, std::placeholders::_1));
|
||||||
void showUpdate2(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
|
||||||
|
|
||||||
void serialCb(String msg);
|
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));
|
||||||
void apiCtrlRequest(JsonObject obj);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void onConnect(AsyncEventSourceClient *client);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
void onLive(AsyncWebServerRequest *request);
|
|
||||||
void showWebApi(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
void onSerial(AsyncWebServerRequest *request);
|
|
||||||
void onSystem(AsyncWebServerRequest *request);
|
|
||||||
|
|
||||||
#ifdef ENABLE_JSON_EP
|
#ifdef ENABLE_JSON_EP
|
||||||
void showJson(void);
|
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);
|
||||||
|
|
||||||
|
registerDebugCb(std::bind(&Web::serialCb, this, std::placeholders::_1)); // dbg.h
|
||||||
|
}
|
||||||
|
|
||||||
|
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", "<rn>");
|
||||||
|
/*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("<rn>"))
|
||||||
|
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 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 showUpdate(AsyncWebServerRequest *request) {
|
||||||
|
bool reboot = !Update.hasError();
|
||||||
|
|
||||||
|
String html = F("<!doctype html><html><head><title>Update</title><meta http-equiv=\"refresh\" content=\"20; URL=/\"></head><body>Update: ");
|
||||||
|
if(reboot)
|
||||||
|
html += "success";
|
||||||
|
else
|
||||||
|
html += "failed";
|
||||||
|
html += F("<br/><br/>rebooting ... auto reload after 20s</body></html>");
|
||||||
|
|
||||||
|
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("<h1>Factory Reset</h1>"
|
||||||
|
"<p><a href=\"/factory?reset=1\">RESET</a><br/><br/><a href=\"/factory?reset=0\">CANCEL</a><br/></p>");
|
||||||
|
refresh = 120;
|
||||||
|
}
|
||||||
|
request->send(200, F("text/html"), F("<!doctype html><html><head><title>Factory Reset</title><meta http-equiv=\"refresh\" content=\"") + String(refresh) + F("; URL=/\"></head><body>") + content + F("</body></html>"));
|
||||||
|
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<InfoCommand>(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) {
|
||||||
|
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
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_PROMETHEUS_EP
|
#ifdef ENABLE_PROMETHEUS_EP
|
||||||
void showMetrics(void);
|
void showMetrics(void) {
|
||||||
std::pair<String, String> convertToPromUnits(String shortUnit);
|
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<String, String> 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
|
#endif
|
||||||
|
|
||||||
AsyncWebServer *mWeb;
|
AsyncWebServer *mWeb;
|
||||||
AsyncEventSource *mEvts;
|
AsyncEventSource *mEvts;
|
||||||
bool mProtected;
|
bool mProtected;
|
||||||
uint32_t mLogoutTimeout;
|
uint32_t mLogoutTimeout;
|
||||||
|
IApp *mApp;
|
||||||
|
HMSYSTEM *mSys;
|
||||||
|
|
||||||
settings_t *mConfig;
|
settings_t *mConfig;
|
||||||
statistics_t *mStat;
|
|
||||||
char *mVersion;
|
|
||||||
app *mMain;
|
|
||||||
webApi *mApi;
|
|
||||||
|
|
||||||
bool mSerialAddTime;
|
bool mSerialAddTime;
|
||||||
char mSerialBuf[WEB_SERIAL_BUF_SIZE];
|
char mSerialBuf[WEB_SERIAL_BUF_SIZE];
|
||||||
|
|
|
@ -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<JsonObject>();
|
|
||||||
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<JsonObject>();
|
|
||||||
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("<a href=\"/factory\" class=\"btn\">Factory Reset</a><br/><br/><a href=\"/reboot\" class=\"btn\">Reboot</a>");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
|
||||||
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<String>();
|
|
||||||
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<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;
|
|
||||||
iv->devControlCmd = ActivePowerContr;
|
|
||||||
iv->devControlRequest = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
jsonOut[F("error")] = F("unknown cmd: '") + jsonIn["cmd"].as<String>() + "'";
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -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__*/
|
|
|
@ -17,7 +17,7 @@
|
||||||
ahoywifi::ahoywifi() {
|
ahoywifi::ahoywifi() {
|
||||||
mCnt = 0;
|
mCnt = 0;
|
||||||
mConnected = false;
|
mConnected = false;
|
||||||
mInitNtp = true;
|
mReconnect = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp) {
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
void ahoywifi::loop() {
|
void ahoywifi::loop() {
|
||||||
#if !defined(AP_ONLY)
|
#if !defined(AP_ONLY)
|
||||||
if(!mConnected) {
|
if(mReconnect) {
|
||||||
delay(100);
|
delay(100);
|
||||||
mCnt++;
|
mCnt++;
|
||||||
if((mCnt % 50) == 0)
|
if((mCnt % 50) == 0)
|
||||||
|
@ -56,11 +56,7 @@ void ahoywifi::loop() {
|
||||||
WiFi.reconnect();
|
WiFi.reconnect();
|
||||||
mCnt = 0;
|
mCnt = 0;
|
||||||
}
|
}
|
||||||
} else if(mInitNtp) {
|
|
||||||
getNtpTime();
|
|
||||||
mInitNtp = false;
|
|
||||||
}
|
}
|
||||||
mCnt = 0;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -209,6 +205,7 @@ void ahoywifi::sendNTPpacket(IPAddress& address) {
|
||||||
void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) {
|
void ahoywifi::onConnect(const WiFiEventStationModeGotIP& event) {
|
||||||
if(!mConnected) {
|
if(!mConnected) {
|
||||||
mConnected = true;
|
mConnected = true;
|
||||||
|
mReconnect = false;
|
||||||
DBGPRINTLN(F("\n[WiFi] Connected"));
|
DBGPRINTLN(F("\n[WiFi] Connected"));
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
DBGPRINTLN(F("[WiFi] AP disabled"));
|
DBGPRINTLN(F("[WiFi] AP disabled"));
|
||||||
|
@ -223,6 +220,7 @@ void ahoywifi::sendNTPpacket(IPAddress& address) {
|
||||||
void ahoywifi::onDisconnect(const WiFiEventStationModeDisconnected& event) {
|
void ahoywifi::onDisconnect(const WiFiEventStationModeDisconnected& event) {
|
||||||
if(mConnected) {
|
if(mConnected) {
|
||||||
mConnected = false;
|
mConnected = false;
|
||||||
|
mReconnect = true;
|
||||||
DPRINTLN(DBG_INFO, "[WiFi] Connection Lost");
|
DPRINTLN(DBG_INFO, "[WiFi] Connection Lost");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ class ahoywifi {
|
||||||
WiFiEventHandler wifiDisconnectHandler;
|
WiFiEventHandler wifiDisconnectHandler;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool mConnected, mInitNtp;
|
bool mConnected, mReconnect;
|
||||||
uint8_t mCnt;
|
uint8_t mCnt;
|
||||||
uint32_t *mUtcTimestamp;
|
uint32_t *mUtcTimestamp;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue