//-----------------------------------------------------------------------------
// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778
// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed
//-----------------------------------------------------------------------------
# ifndef __WEB_H__
# define __WEB_H__
# include "../utils/dbg.h"
# ifdef ESP32
# include "AsyncTCP.h"
# include "Update.h"
# else
# include "ESPAsyncTCP.h"
# endif
# include "../appInterface.h"
# include "../hm/hmSystem.h"
# include "../utils/helper.h"
# if defined(ETHERNET)
# include "AsyncWebServer_ESP32_W5500.h"
# else /* defined(ETHERNET) */
# include "ESPAsyncWebServer.h"
# endif /* defined(ETHERNET) */
# include "html/h/api_js.h"
# include "html/h/colorBright_css.h"
# include "html/h/colorDark_css.h"
# include "html/h/favicon_ico.h"
# include "html/h/grid_info_json.h"
# include "html/h/index_html.h"
# include "html/h/login_html.h"
# include "html/h/serial_html.h"
# include "html/h/setup_html.h"
# include "html/h/style_css.h"
# include "html/h/system_html.h"
# include "html/h/save_html.h"
# include "html/h/update_html.h"
# include "html/h/visualization_html.h"
# include "html/h/about_html.h"
# include "html/h/wizard_html.h"
# include "html/h/history_html.h"
# define WEB_SERIAL_BUF_SIZE 2048
const char * const pinArgNames [ ] = { " pinCs " , " pinCe " , " pinIrq " , " pinSclk " , " pinMosi " , " pinMiso " , " pinLed0 " , " pinLed1 " , " pinLed2 " , " pinLedHighActive " , " pinLedLum " , " pinCmtSclk " , " pinSdio " , " pinCsb " , " pinFcsb " , " pinGpio3 " } ;
template < class HMSYSTEM >
class Web {
public :
Web ( void ) : mWeb ( 80 ) , mEvts ( " /events " ) {
memset ( mSerialBuf , 0 , WEB_SERIAL_BUF_SIZE ) ;
}
void setup ( IApp * app , HMSYSTEM * sys , settings_t * config ) {
mApp = app ;
mSys = sys ;
mConfig = config ;
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 ( " /colors.css " , HTTP_GET , std : : bind ( & Web : : onColor , 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 ( " /grid_info.json " , HTTP_GET , std : : bind ( & Web : : onGridInfoJson , 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 : : showHtml , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /erasetrue " , HTTP_ANY , std : : bind ( & Web : : showHtml , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /factory " , HTTP_ANY , std : : bind ( & Web : : showHtml , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /factorytrue " , HTTP_ANY , std : : bind ( & Web : : showHtml , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /setup " , HTTP_GET , std : : bind ( & Web : : onSetup , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /wizard " , HTTP_GET , std : : bind ( & Web : : onWizard , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /save " , HTTP_POST , std : : bind ( & Web : : showSave , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /live " , HTTP_ANY , std : : bind ( & Web : : onLive , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /history " , HTTP_ANY , std : : bind ( & Web : : onHistory , this , std : : placeholders : : _1 ) ) ;
# ifdef ENABLE_PROMETHEUS_EP
mWeb . on ( " /metrics " , HTTP_ANY , std : : bind ( & Web : : showMetrics , this , std : : placeholders : : _1 ) ) ;
# endif
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 ( " /update " , HTTP_GET , std : : bind ( & Web : : onUpdate , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /upload " , HTTP_POST , std : : bind ( & Web : : onUpload , this , std : : placeholders : : _1 ) ,
std : : bind ( & Web : : onUpload2 , 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 ) ) ;
mWeb . on ( " /about " , HTTP_GET , std : : bind ( & Web : : onAbout , this , std : : placeholders : : _1 ) ) ;
mWeb . on ( " /debug " , HTTP_GET , std : : bind ( & Web : : onDebug , this , std : : placeholders : : _1 ) ) ;
mEvts . onConnect ( std : : bind ( & Web : : onConnect , this , std : : placeholders : : _1 ) ) ;
mWeb . addHandler ( & mEvts ) ;
mWeb . begin ( ) ;
registerDebugCb ( std : : bind ( & Web : : serialCb , this , std : : placeholders : : _1 ) ) ; // dbg.h
mUploadFail = false ;
}
void tickSecond ( ) {
if ( mSerialClientConnnected ) {
if ( mSerialBufFill > 0 ) {
mEvts . send ( mSerialBuf , " serial " , millis ( ) ) ;
memset ( mSerialBuf , 0 , WEB_SERIAL_BUF_SIZE ) ;
mSerialBufFill = 0 ;
}
}
}
AsyncWebServer * getWebSrvPtr ( void ) {
return & mWeb ;
}
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 onUpload2 ( AsyncWebServerRequest * request , String filename , size_t index , uint8_t * data , size_t len , bool final ) {
if ( ! index ) {
mUploadFail = false ;
mUploadFp = LittleFS . open ( " /tmp.json " , " w " ) ;
if ( ! mUploadFp ) {
DPRINTLN ( DBG_ERROR , F ( " can't open file! " ) ) ;
mUploadFail = true ;
mUploadFp . close ( ) ;
return ;
}
}
mUploadFp . write ( data , len ) ;
if ( final ) {
mUploadFp . close ( ) ;
# if !defined(ETHERNET)
char pwd [ PWD_LEN ] ;
strncpy ( pwd , mConfig - > sys . stationPwd , PWD_LEN ) ; // backup WiFi PWD
# endif
if ( ! mApp - > readSettings ( " /tmp.json " ) ) {
mUploadFail = true ;
DPRINTLN ( DBG_ERROR , F ( " upload JSON error! " ) ) ;
} else {
LittleFS . remove ( " /tmp.json " ) ;
# if !defined(ETHERNET)
strncpy ( mConfig - > sys . stationPwd , pwd , PWD_LEN ) ; // restore WiFi PWD
# endif
for ( uint8_t i = 0 ; i < MAX_NUM_INVERTERS ; i + + ) {
if ( ( mConfig - > inst . iv [ i ] . serial . u64 ! = 0 ) & & ( mConfig - > inst . iv [ i ] . serial . u64 < 138999999999 ) ) { // hexadecimal
mConfig - > inst . iv [ i ] . serial . u64 = ah : : Serial2u64 ( String ( mConfig - > inst . iv [ i ] . serial . u64 ) . c_str ( ) ) ;
}
}
mApp - > saveSettings ( true ) ;
}
if ( ! mUploadFail )
DPRINTLN ( DBG_INFO , F ( " upload finished! " ) ) ;
}
}
void serialCb ( String msg ) {
if ( ! mSerialClientConnnected )
return ;
msg . replace ( " \r \n " , " <rn> " ) ;
if ( mSerialAddTime ) {
if ( ( 13 + mSerialBufFill ) < WEB_SERIAL_BUF_SIZE ) {
if ( mApp - > getTimestamp ( ) > 0 ) {
strncpy ( & mSerialBuf [ mSerialBufFill ] , ah : : getTimeStrMs ( mApp - > getTimestampMs ( ) + mApp - > getTimezoneOffset ( ) * 1000 ) . c_str ( ) , 12 ) ;
mSerialBuf [ mSerialBufFill + 12 ] = ' ' ;
mSerialBufFill + = 13 ;
}
} else {
mSerialBufFill = 0 ;
mEvts . send ( " webSerial, buffer overflow!<rn> " , " serial " , millis ( ) ) ;
return ;
}
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!<rn> " , " serial " , millis ( ) ) ;
}
}
private :
inline void checkRedirect ( AsyncWebServerRequest * request ) {
if ( ( mConfig - > sys . protectionMask & PROT_MASK_INDEX ) ! = PROT_MASK_INDEX )
request - > redirect ( F ( " /index " ) ) ;
else if ( ( mConfig - > sys . protectionMask & PROT_MASK_LIVE ) ! = PROT_MASK_LIVE )
request - > redirect ( F ( " /live " ) ) ;
else if ( ( mConfig - > sys . protectionMask & PROT_MASK_HISTORY ) ! = PROT_MASK_HISTORY )
request - > redirect ( F ( " /history " ) ) ;
else if ( ( mConfig - > sys . protectionMask & PROT_MASK_SERIAL ) ! = PROT_MASK_SERIAL )
request - > redirect ( F ( " /serial " ) ) ;
else if ( ( mConfig - > sys . protectionMask & PROT_MASK_SYSTEM ) ! = PROT_MASK_SYSTEM )
request - > redirect ( F ( " /system " ) ) ;
else
request - > redirect ( F ( " /login " ) ) ;
}
void checkProtection ( AsyncWebServerRequest * request ) {
if ( mApp - > isProtected ( request - > client ( ) - > remoteIP ( ) . toString ( ) . c_str ( ) ) ) {
checkRedirect ( request ) ;
return ;
}
}
void getPage ( AsyncWebServerRequest * request , uint16_t mask , const uint8_t * zippedHtml , uint32_t len ) {
if ( CHECK_MASK ( mConfig - > sys . protectionMask , mask ) )
checkProtection ( request ) ;
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , zippedHtml , len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
response - > addHeader ( F ( " content-type " ) , " text/html; charset=UTF-8 " ) ;
if ( request - > hasParam ( " v " ) )
response - > addHeader ( F ( " Cache-Control " ) , F ( " max-age=604800 " ) ) ;
request - > send ( response ) ;
}
void onUpdate ( AsyncWebServerRequest * request ) {
getPage ( request , PROT_MASK_UPDATE , update_html , update_html_len ) ;
}
void showUpdate ( AsyncWebServerRequest * request ) {
# if defined(ETHERNET)
// workaround for AsyncWebServer_ESP32_W5500, because it can't distinguish
// between HTTP_GET and HTTP_POST if both are registered
if ( request - > method ( ) = = HTTP_GET )
onUpdate ( request ) ;
# endif
bool reboot = ( ! Update . hasError ( ) ) ;
String html = F ( " <!doctype html><html><head><title>Update</title><meta http-equiv= \" refresh \" content= \" " ) ;
# if defined(ETHERNET) && defined(CONFIG_IDF_TARGET_ESP32S3)
html + = F ( " 5 " ) ;
# else
html + = F ( " 20 " ) ;
# endif
html + = F ( " ; URL=/ \" ></head><body>Update: " ) ;
if ( reboot )
html + = " success " ;
else
html + = " failed " ;
html + = F ( " <br/><br/>rebooting ...</body></html> " ) ;
AsyncWebServerResponse * response = request - > beginResponse ( 200 , F ( " text/html; charset=UTF-8 " ) , html ) ;
response - > addHeader ( " Connection " , " close " ) ;
request - > send ( response ) ;
mApp - > setRebootFlag ( ) ;
}
void onUpload ( AsyncWebServerRequest * request ) {
bool reboot = ! mUploadFail ;
String html = F ( " <!doctype html><html><head><title>Upload</title><meta http-equiv= \" refresh \" content= \" 20; URL=/ \" ></head><body>Upload: " ) ;
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; charset=UTF-8 " ) , html ) ;
response - > addHeader ( " Connection " , " close " ) ;
request - > send ( response ) ;
mApp - > setRebootFlag ( ) ;
}
void onConnect ( AsyncEventSourceClient * client ) {
DPRINTLN ( DBG_VERBOSE , " onConnect " ) ;
mSerialClientConnnected = true ;
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 ) {
getPage ( request , PROT_MASK_INDEX , index_html , index_html_len ) ;
}
void onLogin ( AsyncWebServerRequest * request ) {
DPRINTLN ( DBG_VERBOSE , F ( " onLogin " ) ) ;
if ( request - > args ( ) > 0 ) {
if ( String ( request - > arg ( " pwd " ) ) = = String ( mConfig - > sys . adminPwd ) ) {
mApp - > unlock ( request - > client ( ) - > remoteIP ( ) . toString ( ) . c_str ( ) ) ;
request - > redirect ( " / " ) ;
}
}
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , login_html , login_html_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
request - > send ( response ) ;
}
void onLogout ( AsyncWebServerRequest * request ) {
DPRINTLN ( DBG_VERBOSE , F ( " onLogout " ) ) ;
checkProtection ( request ) ;
mApp - > lock ( ) ;
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , system_html , system_html_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
request - > send ( response ) ;
}
void onColor ( AsyncWebServerRequest * request ) {
DPRINTLN ( DBG_VERBOSE , F ( " onColor " ) ) ;
AsyncWebServerResponse * response ;
if ( mConfig - > sys . darkMode )
response = request - > beginResponse_P ( 200 , F ( " text/css " ) , colorDark_css , colorDark_css_len ) ;
else
response = request - > beginResponse_P ( 200 , F ( " text/css " ) , colorBright_css , colorBright_css_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
if ( request - > hasParam ( " v " ) ) {
response - > addHeader ( F ( " Cache-Control " ) , F ( " max-age=604800 " ) ) ;
}
request - > send ( response ) ;
}
void onCss ( AsyncWebServerRequest * request ) {
DPRINTLN ( DBG_VERBOSE , F ( " onCss " ) ) ;
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/css " ) , style_css , style_css_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
if ( request - > hasParam ( " v " ) ) {
response - > addHeader ( F ( " Cache-Control " ) , F ( " max-age=604800 " ) ) ;
}
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 " ) ;
if ( request - > hasParam ( " v " ) )
response - > addHeader ( F ( " Cache-Control " ) , F ( " max-age=604800 " ) ) ;
request - > send ( response ) ;
}
void onGridInfoJson ( AsyncWebServerRequest * request ) {
DPRINTLN ( DBG_VERBOSE , F ( " onGridInfoJson " ) ) ;
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " application/json; charset=utf-8 " ) , grid_info_json , grid_info_json_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
if ( request - > hasParam ( " v " ) )
response - > addHeader ( F ( " Cache-Control " ) , F ( " max-age=604800 " ) ) ;
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 ) {
checkProtection ( request ) ;
request - > redirect ( " /wizard " ) ;
}
void onReboot ( AsyncWebServerRequest * request ) {
mApp - > setRebootFlag ( ) ;
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , system_html , system_html_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
request - > send ( response ) ;
}
void showHtml ( AsyncWebServerRequest * request ) {
checkProtection ( request ) ;
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , system_html , system_html_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
request - > send ( response ) ;
}
void onSetup ( AsyncWebServerRequest * request ) {
getPage ( request , PROT_MASK_SETUP , setup_html , setup_html_len ) ;
}
void onWizard ( AsyncWebServerRequest * request ) {
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , wizard_html , wizard_html_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
response - > addHeader ( F ( " content-type " ) , " text/html; charset=UTF-8 " ) ;
request - > send ( response ) ;
}
void showSave ( AsyncWebServerRequest * request ) {
DPRINTLN ( DBG_VERBOSE , F ( " showSave " ) ) ;
checkProtection ( request ) ;
if ( request - > args ( ) = = 0 )
return ;
char buf [ 20 ] = { 0 } ;
// general
# if !defined(ETHERNET)
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 ( " ap_pwd " ) ! = " " )
request - > arg ( " ap_pwd " ) . toCharArray ( mConfig - > sys . apPwd , PWD_LEN ) ;
mConfig - > sys . isHidden = ( request - > arg ( " hidd " ) = = " on " ) ;
# endif /* !defined(ETHERNET) */
if ( request - > arg ( " device " ) ! = " " )
request - > arg ( " device " ) . toCharArray ( mConfig - > sys . deviceName , DEVNAME_LEN ) ;
mConfig - > sys . darkMode = ( request - > arg ( " darkMode " ) = = " on " ) ;
mConfig - > sys . schedReboot = ( request - > arg ( " schedReboot " ) = = " on " ) ;
mConfig - > sys . region = ( request - > arg ( " region " ) ) . toInt ( ) ;
mConfig - > sys . timezone = ( request - > arg ( " timezone " ) ) . toInt ( ) - 12 ;
if ( request - > arg ( " cstLnk " ) ! = " " ) {
request - > arg ( " cstLnk " ) . toCharArray ( mConfig - > plugin . customLink , MAX_CUSTOM_LINK_LEN ) ;
request - > arg ( " cstLnkTxt " ) . toCharArray ( mConfig - > plugin . customLinkText , MAX_CUSTOM_LINK_TEXT_LEN ) ;
} else {
mConfig - > plugin . customLink [ 0 ] = ' \0 ' ;
mConfig - > plugin . customLinkText [ 0 ] = ' \0 ' ;
}
// protection
if ( request - > arg ( " adminpwd " ) ! = " {PWD} " ) {
request - > arg ( " adminpwd " ) . toCharArray ( mConfig - > sys . adminPwd , PWD_LEN ) ;
mApp - > lock ( ) ;
}
mConfig - > sys . protectionMask = 0x0000 ;
for ( uint8_t i = 0 ; i < 7 ; i + + ) {
if ( request - > arg ( " protMask " + String ( i ) ) = = " on " )
mConfig - > sys . protectionMask | = ( 1 < < i ) ;
}
// 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 ) ;
if ( request - > arg ( " invInterval " ) ! = " " )
mConfig - > inst . sendInterval = request - > arg ( " invInterval " ) . toInt ( ) ;
mConfig - > inst . rstYieldMidNight = ( request - > arg ( " invRstMid " ) = = " on " ) ;
mConfig - > inst . rstValsCommStop = ( request - > arg ( " invRstComStop " ) = = " on " ) ;
mConfig - > inst . rstValsNotAvail = ( request - > arg ( " invRstNotAvail " ) = = " on " ) ;
mConfig - > inst . startWithoutTime = ( request - > arg ( " strtWthtTm " ) = = " on " ) ;
mConfig - > inst . readGrid = ( request - > arg ( " rdGrid " ) = = " on " ) ;
mConfig - > inst . rstMaxValsMidNight = ( request - > arg ( " invRstMaxMid " ) = = " on " ) ;
mConfig - > inst . yieldEffiency = ( request - > arg ( " yldEff " ) ) . toFloat ( ) ;
// pinout
for ( uint8_t i = 0 ; i < 16 ; i + + ) {
uint8_t pin = request - > arg ( String ( pinArgNames [ i ] ) ) . toInt ( ) ;
switch ( i ) {
case 0 : mConfig - > nrf . pinCs = ( ( pin ! = 0xff ) ? pin : DEF_NRF_CS_PIN ) ; break ;
case 1 : mConfig - > nrf . pinCe = ( ( pin ! = 0xff ) ? pin : DEF_NRF_CE_PIN ) ; break ;
case 2 : mConfig - > nrf . pinIrq = ( ( pin ! = 0xff ) ? pin : DEF_NRF_IRQ_PIN ) ; break ;
case 3 : mConfig - > nrf . pinSclk = ( ( pin ! = 0xff ) ? pin : DEF_NRF_SCLK_PIN ) ; break ;
case 4 : mConfig - > nrf . pinMosi = ( ( pin ! = 0xff ) ? pin : DEF_NRF_MOSI_PIN ) ; break ;
case 5 : mConfig - > nrf . pinMiso = ( ( pin ! = 0xff ) ? pin : DEF_NRF_MISO_PIN ) ; break ;
case 6 : mConfig - > led . led [ 0 ] = pin ; break ;
case 7 : mConfig - > led . led [ 1 ] = pin ; break ;
case 8 : mConfig - > led . led [ 2 ] = pin ; break ;
case 9 : mConfig - > led . high_active = pin ; break ; // this is not really a pin but a polarity, but handling it close to here makes sense
case 10 : mConfig - > led . luminance = pin ; break ; // this is not really a pin but a polarity, but handling it close to here makes sense
case 11 : mConfig - > cmt . pinSclk = pin ; break ;
case 12 : mConfig - > cmt . pinSdio = pin ; break ;
case 13 : mConfig - > cmt . pinCsb = pin ; break ;
case 14 : mConfig - > cmt . pinFcsb = pin ; break ;
case 15 : mConfig - > cmt . pinIrq = pin ; break ;
}
}
mConfig - > nrf . enabled = ( request - > arg ( " nrfEnable " ) = = " on " ) ;
mConfig - > cmt . enabled = ( request - > arg ( " cmtEnable " ) = = " on " ) ;
// ntp
if ( request - > arg ( " ntpAddr " ) ! = " " ) {
request - > arg ( " ntpAddr " ) . toCharArray ( mConfig - > ntp . addr , NTP_ADDR_LEN ) ;
mConfig - > ntp . port = request - > arg ( " ntpPort " ) . toInt ( ) & 0xffff ;
mConfig - > ntp . interval = request - > arg ( " ntpIntvl " ) . toInt ( ) & 0xffff ;
}
// sun
if ( request - > arg ( " sunLat " ) = = " " | | ( request - > arg ( " sunLon " ) = = " " ) ) {
mConfig - > sun . lat = 0.0 ;
mConfig - > sun . lon = 0.0 ;
mConfig - > sun . offsetSecMorning = 0 ;
mConfig - > sun . offsetSecEvening = 0 ;
} else {
mConfig - > sun . lat = request - > arg ( " sunLat " ) . toFloat ( ) ;
mConfig - > sun . lon = request - > arg ( " sunLon " ) . toFloat ( ) ;
mConfig - > sun . offsetSecMorning = request - > arg ( " sunOffsSr " ) . toInt ( ) * 60 ;
mConfig - > sun . offsetSecEvening = request - > arg ( " sunOffsSs " ) . toInt ( ) * 60 ;
}
// 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 ( " mqttClientId " ) . toCharArray ( mConfig - > mqtt . clientId , MQTT_CLIENTID_LEN ) ;
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 ( ) ;
mConfig - > mqtt . interval = request - > arg ( " mqttInterval " ) . toInt ( ) ;
// serial console
mConfig - > serial . debug = ( request - > arg ( " serDbg " ) = = " on " ) ;
mConfig - > serial . privacyLog = ( request - > arg ( " priv " ) = = " on " ) ;
mConfig - > serial . printWholeTrace = ( request - > arg ( " wholeTrace " ) = = " on " ) ;
mConfig - > serial . showIv = ( request - > arg ( " serEn " ) = = " on " ) ;
// display
mConfig - > plugin . display . pwrSaveAtIvOffline = ( request - > arg ( " disp_pwr " ) = = " on " ) ;
mConfig - > plugin . display . graph_size = request - > arg ( " disp_graph_size " ) . toInt ( ) ;
mConfig - > plugin . display . rot = request - > arg ( " disp_rot " ) . toInt ( ) ;
mConfig - > plugin . display . type = request - > arg ( " disp_typ " ) . toInt ( ) ;
mConfig - > plugin . display . contrast = ( mConfig - > plugin . display . type = = DISP_TYPE_T0_NONE ) | | // contrast available only according optionsMap in setup.html, otherwise default value
( mConfig - > plugin . display . type = = DISP_TYPE_T10_EPAPER ) ? 140 : request - > arg ( " disp_cont " ) . toInt ( ) ;
mConfig - > plugin . display . screenSaver = ( ( mConfig - > plugin . display . type = = DISP_TYPE_T1_SSD1306_128X64 ) // screensaver available only according optionsMap in setup.html, otherwise default value
| | ( mConfig - > plugin . display . type = = DISP_TYPE_T2_SH1106_128X64 )
| | ( mConfig - > plugin . display . type = = DISP_TYPE_T4_SSD1306_128X32 )
| | ( mConfig - > plugin . display . type = = DISP_TYPE_T5_SSD1306_64X48 )
| | ( mConfig - > plugin . display . type = = DISP_TYPE_T6_SSD1309_128X64 ) ) ? request - > arg ( " disp_screensaver " ) . toInt ( ) : 0 ;
mConfig - > plugin . display . graph_ratio = ( ( mConfig - > plugin . display . type = = DISP_TYPE_T1_SSD1306_128X64 ) // display graph available only according optionsMap in setup.html, otherwise has to be 0
| | ( mConfig - > plugin . display . type = = DISP_TYPE_T2_SH1106_128X64 )
| | ( mConfig - > plugin . display . type = = DISP_TYPE_T3_PCD8544_84X48 )
| | ( mConfig - > plugin . display . type = = DISP_TYPE_T6_SSD1309_128X64 ) ) ? request - > arg ( " disp_graph_ratio " ) . toInt ( ) : 0 ;
// available pins according pinMap in setup.html, otherwise default value
mConfig - > plugin . display . disp_data = ( mConfig - > plugin . display . type = = DISP_TYPE_T0_NONE ) ? DEF_PIN_OFF : request - > arg ( " disp_data " ) . toInt ( ) ;
mConfig - > plugin . display . disp_clk = ( mConfig - > plugin . display . type = = DISP_TYPE_T0_NONE ) ? DEF_PIN_OFF : request - > arg ( " disp_clk " ) . toInt ( ) ;
mConfig - > plugin . display . disp_cs = ( mConfig - > plugin . display . type ! = DISP_TYPE_T3_PCD8544_84X48 )
& & ( mConfig - > plugin . display . type ! = DISP_TYPE_T10_EPAPER ) ? DEF_PIN_OFF : request - > arg ( " disp_cs " ) . toInt ( ) ;
mConfig - > plugin . display . disp_dc = ( mConfig - > plugin . display . type ! = DISP_TYPE_T3_PCD8544_84X48 )
& & ( mConfig - > plugin . display . type ! = DISP_TYPE_T10_EPAPER ) ? DEF_PIN_OFF : request - > arg ( " disp_dc " ) . toInt ( ) ;
mConfig - > plugin . display . disp_reset = ( mConfig - > plugin . display . type ! = DISP_TYPE_T10_EPAPER ) ? DEF_PIN_OFF : request - > arg ( " disp_rst " ) . toInt ( ) ;
mConfig - > plugin . display . disp_busy = ( mConfig - > plugin . display . type ! = DISP_TYPE_T10_EPAPER ) ? DEF_PIN_OFF : request - > arg ( " disp_bsy " ) . toInt ( ) ;
mConfig - > plugin . display . pirPin = ( mConfig - > plugin . display . screenSaver ! = DISP_TYPE_T2_SH1106_128X64 ) ? DEF_PIN_OFF : request - > arg ( " pir_pin " ) . toInt ( ) ; // pir pin only for motion screensaver
// otherweise default value
mApp - > saveSettings ( ( request - > arg ( " reboot " ) = = " on " ) ) ;
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , save_html , save_html_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
request - > send ( response ) ;
}
void onLive ( AsyncWebServerRequest * request ) {
getPage ( request , PROT_MASK_LIVE , visualization_html , visualization_html_len ) ;
}
void onHistory ( AsyncWebServerRequest * request ) {
getPage ( request , PROT_MASK_HISTORY , history_html , history_html_len ) ;
}
void onAbout ( AsyncWebServerRequest * request ) {
AsyncWebServerResponse * response = request - > beginResponse_P ( 200 , F ( " text/html; charset=UTF-8 " ) , about_html , about_html_len ) ;
response - > addHeader ( F ( " Content-Encoding " ) , " gzip " ) ;
response - > addHeader ( F ( " content-type " ) , " text/html; charset=UTF-8 " ) ;
if ( request - > hasParam ( " v " ) ) {
response - > addHeader ( F ( " Cache-Control " ) , F ( " max-age=604800 " ) ) ;
}
request - > send ( response ) ;
}
void onDebug ( AsyncWebServerRequest * request ) {
mApp - > getSchedulerNames ( ) ;
AsyncWebServerResponse * response = request - > beginResponse ( 200 , F ( " text/html; charset=UTF-8 " ) , " ok " ) ;
request - > send ( response ) ;
}
void onSerial ( AsyncWebServerRequest * request ) {
getPage ( request , PROT_MASK_SERIAL , serial_html , serial_html_len ) ;
}
void onSystem ( AsyncWebServerRequest * request ) {
getPage ( request , PROT_MASK_SYSTEM , system_html , system_html_len ) ;
}
# ifdef ENABLE_PROMETHEUS_EP
// Note
// Prometheus exposition format is defined here: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md
// NOTE: Grouping for fields with channels and totals is currently not working
// TODO: Handle grouping and sorting for independant from channel number
// NOTE: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels)
const char * metricConstPrefix = " ahoy_solar_ " ;
const char * metricConstInverterFormat = " {inverter= \" %s \" } %d \n " ;
typedef enum {
metricsStateInverterInfo = 0 , metricsStateInverterEnabled = 1 , metricsStateInverterAvailable = 2 ,
metricsStateInverterProducing = 3 , metricsStateInverterPowerLimitRead = 4 , metricsStateInverterPowerLimitAck = 5 ,
metricsStateInverterMaxPower = 6 , metricsStateInverterRxSuccess = 7 , metricsStateInverterRxFail = 8 ,
metricsStateInverterRxFailAnswer = 9 , metricsStateInverterFrameCnt = 10 , metricsStateInverterTxCnt = 11 ,
metricsStateInverterRetransmits = 12 , metricsStateInverterIvRxCnt = 13 , metricsStateInverterIvTxCnt = 14 ,
metricsStateInverterDtuRxCnt = 15 , metricsStateInverterDtuTxCnt = 16 ,
metricStateRealtimeFieldId = metricsStateInverterDtuTxCnt + 1 , // ensure that this state follows the last per_inverter state
metricStateRealtimeInverterId ,
metricsStateAlarmData ,
metricsStateStart ,
metricsStateEnd
} MetricStep_t ;
MetricStep_t metricsStep = metricsStateInverterInfo ;
typedef struct {
const char * topic ;
const char * type ;
const char * format ;
const std : : function < uint64_t ( Inverter < > * iv ) > valueFunc ;
} InverterMetric_t ;
InverterMetric_t inverterMetrics [ 17 ] = {
{ " info " , " gauge " , " {name= \" %s \" ,serial= \" %12llx \" } 1 \n " , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > config - > serial . u64 ; } } ,
{ " is_enabled " , " gauge " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > config - > enabled ; } } ,
{ " is_available " , " gauge " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > isAvailable ( ) ; } } ,
{ " is_producing " , " gauge " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > isProducing ( ) ; } } ,
{ " power_limit_read " , " gauge " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return ( int64_t ) ah : : round3 ( iv - > actPowerLimit ) ; } } ,
{ " power_limit_ack " , " gauge " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return ( iv - > powerLimitAck ) ? 1 : 0 ; } } ,
{ " max_power " , " gauge " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > getMaxPower ( ) ; } } ,
{ " radio_rx_success " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . rxSuccess ; } } ,
{ " radio_rx_fail " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . rxFail ; } } ,
{ " radio_rx_fail_answer " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . rxFailNoAnser ; } } ,
{ " radio_frame_cnt " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . frmCnt ; } } ,
{ " radio_tx_cnt " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . txCnt ; } } ,
{ " radio_retransmits " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . retransmits ; } } ,
{ " radio_iv_loss_cnt " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . ivLoss ; } } ,
{ " radio_iv_sent_cnt " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . ivSent ; } } ,
{ " radio_dtu_loss_cnt " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . dtuLoss ; } } ,
{ " radio_dtu_sent_cnt " , " counter " , metricConstInverterFormat , [ ] ( Inverter < > * iv ) - > uint64_t { return iv - > radioStatistics . dtuSent ; } }
} ;
void showMetrics ( AsyncWebServerRequest * request ) {
DPRINTLN ( DBG_VERBOSE , F ( " web::showMetrics " ) ) ;
metricsStep = metricsStateStart ;
AsyncWebServerResponse * response = request - > beginChunkedResponse ( F ( " text/plain " ) ,
[ this ] ( uint8_t * buffer , size_t maxLen , size_t filledLength ) - > size_t
{
Inverter < > * iv ;
record_t < > * rec ;
String promUnit , promType ;
String metrics ;
char type [ 60 ] , topic [ 100 ] , val [ 25 ] ;
size_t len = 0 ;
int alarmChannelId ;
int metricsChannelId ;
// Perform grouping on metrics according to format specification
// Each step must return at least one character. Otherwise the processing of AsyncWebServerResponse stops.
// So several "Info:" blocks are used to keep the transmission going
switch ( metricsStep ) {
case metricsStateStart : // System Info : fit to one packet
snprintf ( type , sizeof ( type ) , " # TYPE %sinfo gauge \n " , metricConstPrefix ) ;
snprintf ( topic , sizeof ( topic ) , " %sinfo{version= \" %s \" ,image= \" \" ,devicename= \" %s \" } 1 \n " , metricConstPrefix ,
mApp - > getVersion ( ) , mConfig - > sys . deviceName ) ;
metrics = String ( type ) + String ( topic ) ;
snprintf ( type , sizeof ( type ) , " # TYPE %sfreeheap gauge \n " , metricConstPrefix ) ;
snprintf ( topic , sizeof ( topic ) , " %sfreeheap{devicename= \" %s \" } %u \n " , metricConstPrefix , mConfig - > sys . deviceName , ESP . getFreeHeap ( ) ) ;
metrics + = String ( type ) + String ( topic ) ;
snprintf ( type , sizeof ( type ) , " # TYPE %suptime counter \n " , metricConstPrefix ) ;
snprintf ( topic , sizeof ( topic ) , " %suptime{devicename= \" %s \" } %u \n " , metricConstPrefix , mConfig - > sys . deviceName , mApp - > getUptime ( ) ) ;
metrics + = String ( type ) + String ( topic ) ;
snprintf ( type , sizeof ( type ) , " # TYPE %swifi_rssi_db gauge \n " , metricConstPrefix ) ;
snprintf ( topic , sizeof ( topic ) , " %swifi_rssi_db{devicename= \" %s \" } %d \n " , metricConstPrefix , mConfig - > sys . deviceName , WiFi . RSSI ( ) ) ;
metrics + = String ( type ) + String ( topic ) ;
len = snprintf ( reinterpret_cast < char * > ( buffer ) , maxLen , " %s " , metrics . c_str ( ) ) ;
// Next is Inverter information
metricsStep = metricsStateInverterInfo ;
break ;
// Information about all inverters configured : each metric for all inverters must fit to one network packet
case metricsStateInverterInfo :
case metricsStateInverterEnabled :
case metricsStateInverterAvailable :
case metricsStateInverterProducing :
case metricsStateInverterPowerLimitRead :
case metricsStateInverterPowerLimitAck :
case metricsStateInverterMaxPower :
case metricsStateInverterRxSuccess :
case metricsStateInverterRxFail :
case metricsStateInverterRxFailAnswer :
case metricsStateInverterFrameCnt :
case metricsStateInverterTxCnt :
case metricsStateInverterRetransmits :
case metricsStateInverterIvRxCnt :
case metricsStateInverterIvTxCnt :
case metricsStateInverterDtuRxCnt :
case metricsStateInverterDtuTxCnt :
metrics = " # TYPE ahoy_solar_inverter_ " + String ( inverterMetrics [ metricsStep ] . topic ) + " " + String ( inverterMetrics [ metricsStep ] . type ) + " \n " ;
metrics + = inverterMetric ( topic , sizeof ( topic ) ,
( String ( " ahoy_solar_inverter_ " ) + inverterMetrics [ metricsStep ] . topic +
inverterMetrics [ metricsStep ] . format ) . c_str ( ) ,
inverterMetrics [ metricsStep ] . valueFunc ) ;
len = snprintf ( reinterpret_cast < char * > ( buffer ) , maxLen , " %s " , metrics . c_str ( ) ) ;
// ugly hack to increment the enum
metricsStep = static_cast < MetricStep_t > ( static_cast < int > ( metricsStep ) + 1 ) ;
// Prepare Realtime Field loop, which may be startet next
metricsFieldId = FLD_UDC ;
break ;
case metricStateRealtimeFieldId : // Iterate over all defined fields
if ( metricsFieldId < FLD_LAST_ALARM_CODE ) {
metrics = " # Info: processing realtime field # " + String ( metricsFieldId ) + " \n " ;
metricDeclared = false ;
metricTotalDeclard = false ;
metricsInverterId = 0 ;
metricsStep = metricStateRealtimeInverterId ;
} else {
metrics = " # Info: all realtime fields processed \n " ;
metricsStep = metricsStateAlarmData ;
}
len = snprintf ( reinterpret_cast < char * > ( buffer ) , maxLen , " %s " , metrics . c_str ( ) ) ;
break ;
case metricStateRealtimeInverterId : // Iterate over all inverters for this field
metrics = " " ;
if ( metricsInverterId < mSys - > getNumInverters ( ) ) {
// process all channels of this inverter
iv = mSys - > getInverterByPos ( metricsInverterId ) ;
if ( NULL ! = iv ) {
rec = iv - > getRecordStruct ( RealTimeRunData_Debug ) ;
for ( metricsChannelId = 0 ; metricsChannelId < rec - > length ; metricsChannelId + + ) {
uint8_t channel = rec - > assign [ metricsChannelId ] . ch ;
// Try inverter channel (channel 0) or any channel with maxPwr > 0
if ( 0 = = channel | | 0 ! = iv - > config - > chMaxPwr [ channel - 1 ] ) {
if ( metricsFieldId = = iv - > getByteAssign ( metricsChannelId , rec ) - > fieldId ) {
// This is the correct field to report
std : : tie ( promUnit , promType ) = convertToPromUnits ( iv - > getUnit ( metricsChannelId , rec ) ) ;
// Declare metric only once
if ( channel ! = 0 & & ! metricDeclared ) {
snprintf ( type , sizeof ( type ) , " # TYPE %s%s%s %s \n " , metricConstPrefix , iv - > getFieldName ( metricsChannelId , rec ) , promUnit . c_str ( ) , promType . c_str ( ) ) ;
metrics + = type ;
metricDeclared = true ;
}
// report value
if ( 0 = = channel ) {
// Report a _total value if also channel values were reported. Otherwise report without _total
char total [ 7 ] ;
if ( metricDeclared ) {
// A declaration and value for channels have been delivered. So declare and deliver a _total metric
strncpy ( total , " _total " , 6 ) ;
}
if ( ! metricTotalDeclard ) {
snprintf ( type , sizeof ( type ) , " # TYPE %s%s%s%s %s \n " , metricConstPrefix , iv - > getFieldName ( metricsChannelId , rec ) , promUnit . c_str ( ) , total , promType . c_str ( ) ) ;
metrics + = type ;
metricTotalDeclard = true ;
}
snprintf ( topic , sizeof ( topic ) , " %s%s%s%s{inverter= \" %s \" } " , metricConstPrefix , iv - > getFieldName ( metricsChannelId , rec ) , promUnit . c_str ( ) , total , iv - > config - > name ) ;
} else {
// Report (non zero) channel value
// Use a fallback channel name (ch0, ch1, ...)if non is given by user
char chName [ MAX_NAME_LENGTH ] ;
if ( iv - > config - > chName [ channel - 1 ] [ 0 ] ! = 0 ) {
strncpy ( chName , iv - > config - > chName [ channel - 1 ] , sizeof ( chName ) ) ;
} else {
snprintf ( chName , sizeof ( chName ) , " ch%1d " , channel ) ;
}
snprintf ( topic , sizeof ( topic ) , " %s%s%s{inverter= \" %s \" ,channel= \" %s \" } " , metricConstPrefix , iv - > getFieldName ( metricsChannelId , rec ) , promUnit . c_str ( ) , iv - > config - > name , chName ) ;
}
snprintf ( val , sizeof ( val ) , " %.3f \n " , iv - > getValue ( metricsChannelId , rec ) ) ;
metrics + = topic ;
metrics + = val ;
}
}
}
if ( metrics . length ( ) < 1 ) {
metrics = " # Info: Field # " + String ( metricsFieldId ) + " ( " + fields [ metricsFieldId ] +
" ) not available for inverter # " + String ( metricsInverterId ) + " . Skipping remaining inverters \n " ;
metricsFieldId + + ; // Process next field Id
metricsStep = metricStateRealtimeFieldId ;
}
} else {
metrics = " # Info: No data for field # " + String ( metricsFieldId ) + " ( " + fields [ metricsFieldId ] +
" ) of inverter # " + String ( metricsInverterId ) + " . Skipping remaining inverters \n " ;
metricsFieldId + + ; // Process next field Id
metricsStep = metricStateRealtimeFieldId ;
}
// Stay in this state and try next inverter
metricsInverterId + + ;
} else {
metrics = " # Info: All inverters for field # " + String ( metricsFieldId ) + " processed. \n " ;
metricsFieldId + + ; // Process next field Id
metricsStep = metricStateRealtimeFieldId ;
}
len = snprintf ( reinterpret_cast < char * > ( buffer ) , maxLen , " %s " , metrics . c_str ( ) ) ;
break ;
case metricsStateAlarmData : // Alarm Info loop : fit to one packet
// Perform grouping on metrics according to Prometheus exposition format specification
snprintf ( type , sizeof ( type ) , " # TYPE %s%s gauge \n " , metricConstPrefix , fields [ FLD_LAST_ALARM_CODE ] ) ;
metrics = type ;
for ( metricsInverterId = 0 ; metricsInverterId < mSys - > getNumInverters ( ) ; metricsInverterId + + ) {
iv = mSys - > getInverterByPos ( metricsInverterId ) ;
if ( NULL ! = iv ) {
rec = iv - > getRecordStruct ( AlarmData ) ;
// simple hack : there is only one channel with alarm data
// TODO: find the right one channel with the alarm id
alarmChannelId = 0 ;
if ( alarmChannelId < rec - > length ) {
std : : tie ( promUnit , promType ) = convertToPromUnits ( iv - > getUnit ( alarmChannelId , rec ) ) ;
snprintf ( topic , sizeof ( topic ) , " %s%s%s{inverter= \" %s \" } " , metricConstPrefix , iv - > getFieldName ( alarmChannelId , rec ) , promUnit . c_str ( ) , iv - > config - > name ) ;
snprintf ( val , sizeof ( val ) , " %.3f \n " , iv - > getValue ( alarmChannelId , rec ) ) ;
metrics + = topic ;
metrics + = val ;
}
}
}
len = snprintf ( reinterpret_cast < char * > ( buffer ) , maxLen , " %s " , metrics . c_str ( ) ) ;
metricsStep = metricsStateEnd ;
break ;
default : // end of transmission
DBGPRINT ( " E: Prometheus: Bad metricsStep= " ) ;
DBGPRINTLN ( String ( metricsStep ) ) ;
case metricsStateEnd :
len = 0 ;
break ;
} // switch
return len ;
} ) ;
request - > send ( response ) ;
}
// Traverse all inverters and collect the metric via valueFunc
String inverterMetric ( char * buffer , size_t len , const char * format , std : : function < uint64_t ( Inverter < > * iv ) > valueFunc ) {
String metric = " " ;
for ( int metricsInverterId = 0 ; metricsInverterId < mSys - > getNumInverters ( ) ; metricsInverterId + + ) {
Inverter < > * iv = mSys - > getInverterByPos ( metricsInverterId ) ;
if ( NULL ! = iv ) {
snprintf ( buffer , len , format , iv - > config - > name , valueFunc ( iv ) ) ;
metric + = String ( buffer ) ;
}
}
return metric ;
}
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 { " _wattHours " , " counter " } ;
if ( shortUnit = = " kWh " ) return { " _kilowattHours " , " counter " } ;
if ( shortUnit = = " °C " ) return { " _celsius " , " gauge " } ;
if ( shortUnit = = " var " ) return { " _var " , " gauge " } ;
if ( shortUnit = = " Hz " ) return { " _hertz " , " gauge " } ;
return { " " , " gauge " } ;
}
private :
int metricsInverterId = 0 ;
uint8_t metricsFieldId = 0 ;
bool metricDeclared = false , metricTotalDeclard = false ;
# endif
private :
AsyncWebServer mWeb ;
AsyncEventSource mEvts ;
IApp * mApp = nullptr ;
HMSYSTEM * mSys = nullptr ;
settings_t * mConfig = nullptr ;
bool mSerialAddTime = true ;
char mSerialBuf [ WEB_SERIAL_BUF_SIZE ] ;
uint16_t mSerialBufFill = 0 ;
bool mSerialClientConnnected = false ;
File mUploadFp ;
bool mUploadFail = false ;
} ;
# endif /*__WEB_H__*/