Browse Source

Merge branch 'development03' into Zero-Export

pull/1155/head
DanielR92 2 years ago
parent
commit
dd2e246fe4
  1. 6
      doc/prometheus_ep_description.md
  2. 20
      src/CHANGES.md
  3. 31
      src/app.cpp
  4. 4
      src/app.h
  5. 1
      src/appInterface.h
  6. 3
      src/config/config.h
  7. 18
      src/config/settings.h
  8. 10
      src/defines.h
  9. 10
      src/hm/Communication.h
  10. 114
      src/hm/hmInverter.h
  11. 2
      src/hm/hmRadio.h
  12. 6
      src/hm/radio.h
  13. 2
      src/hms/hmsRadio.h
  14. 36
      src/publisher/pubMqttIvData.h
  15. 12
      src/web/RestApi.h
  16. 6
      src/web/html/index.html
  17. 4
      src/web/html/setup.html
  18. 27
      src/web/html/style.css
  19. 10
      src/web/html/system.html
  20. 29
      src/web/html/visualization.html
  21. 116
      src/web/web.h

6
doc/prometheus_ep_description.md

@ -25,7 +25,7 @@ Prometheus metrics provided at `/metrics`.
| `ahoy_solar_inverter_enabled` | Gauge | Is the inverter enabled? | inverter | | `ahoy_solar_inverter_enabled` | Gauge | Is the inverter enabled? | inverter |
| `ahoy_solar_inverter_is_available` | Gauge | is the inverter available? | inverter | | `ahoy_solar_inverter_is_available` | Gauge | is the inverter available? | inverter |
| `ahoy_solar_inverter_is_producing` | Gauge | Is the inverter producing? | inverter | | `ahoy_solar_inverter_is_producing` | Gauge | Is the inverter producing? | inverter |
| `ahoy_solar_inverter_power_limit_read` | Gauge | Power Limit read from inverter | inverter | | `ahoy_solar_inverter_power_limit_read` | Gauge | Power Limit read from inverter. Defaults to 65535 | inverter |
| `ahoy_solar_inverter_power_limit_ack` | Gauge | Power Limit acknowledged by inverter | inverter | | `ahoy_solar_inverter_power_limit_ack` | Gauge | Power Limit acknowledged by inverter | inverter |
| `ahoy_solar_inverter_max_power` | Gauge | Max Power of inverter | inverter | | `ahoy_solar_inverter_max_power` | Gauge | Max Power of inverter | inverter |
| `ahoy_solar_inverter_radio_rx_success` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_rx_success` | Counter | NRF24 statistic of inverter | inverter |
@ -34,6 +34,10 @@ Prometheus metrics provided at `/metrics`.
| `ahoy_solar_inverter_radio_frame_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_frame_cnt` | Counter | NRF24 statistic of inverter | inverter |
| `ahoy_solar_inverter_radio_tx_cnt` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_tx_cnt` | Counter | NRF24 statistic of inverter | inverter |
| `ahoy_solar_inverter_radio_retransmits` | Counter | NRF24 statistic of inverter | inverter | | `ahoy_solar_inverter_radio_retransmits` | Counter | NRF24 statistic of inverter | inverter |
| `ahoy_solar_inverter_radio_iv_rx_cnt` | Counter | NRF24 statistic of inverter | inverter |
| `ahoy_solar_inverter_radio_iv_tx_cnt` | Counter | NRF24 statistic of inverter | inverter |
| `ahoy_solar_inverter_radio_dtu_rx_cnt` | Counter | NRF24 statistic of inverter | inverter |
| `ahoy_solar_inverter_radio_dtu_rx_cnt` | Counter | NRF24 statistic of inverter | inverter |
| `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter | | `ahoy_solar_U_AC_volt` | Gauge | AC voltage of inverter [V] | inverter |
| `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter | | `ahoy_solar_I_AC_ampere` | Gauge | AC current of inverter [A] | inverter |
| `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter | | `ahoy_solar_P_AC_watt` | Gauge | AC power of inverter [W] | inverter |

20
src/CHANGES.md

@ -1,5 +1,25 @@
# Development Changes # Development Changes
## 0.8.44 - 2024-01-05
* fix MqTT transmission of data #1326
* live data is read much earlier / faster and more often
## 0.8.43 - 2024-01-04
* fix display of sunrise in `/system` #1308
* fix overflow of `getLossRate` calculation #1318
* improved MqTT by marking sent data and improved `last_success` resends #1319
* added timestamp for `max ac power` as tooltip #1324 #1123 #1199
* repaired Power-limit acknowledge #1322
* fix `max_power` in `/visualization` was set to `0` after sunset
## 0.8.42 - 2024-01-02
* add LED to display whether it's night time or not. Can be reused as output to control battery system #1308
* merge PR: beautifiying typography, added spaces between value and unit for `/visualization` #1314
* merge PR: Prometheus add `getLossRate` and bugfixing #1315
* add loss rate to `/visualization` in the statistics window
* corrected `getLossRate` infos for MqTT and prometheus
* added information about working IRQ for NRF24 and CMT2300A to `/system`
## 0.8.41 - 2024-01-02 ## 0.8.41 - 2024-01-02
* fix display timeout (OLED) to 60s * fix display timeout (OLED) to 60s
* change offs to signed value * change offs to signed value

31
src/app.cpp

@ -52,6 +52,7 @@ void app::setup() {
mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, &mConfig->inst.gapMs); mCommunication.setup(&mTimestamp, &mConfig->serial.debug, &mConfig->serial.privacyLog, &mConfig->serial.printWholeTrace, &mConfig->inst.gapMs);
mCommunication.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2)); mCommunication.addPayloadListener(std::bind(&app::payloadEventListener, this, std::placeholders::_1, std::placeholders::_2));
mCommunication.addPowerLimitAckListener([this] (Inverter<> *iv) { mMqtt.setPowerLimitAck(iv); });
mSys.setup(&mTimestamp, &mConfig->inst); mSys.setup(&mTimestamp, &mConfig->inst);
for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { for (uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) {
initInverter(i); initInverter(i);
@ -472,14 +473,11 @@ void app::mqttSubRxCb(JsonObject obj) {
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void app::setupLed(void) { void app::setupLed(void) {
uint8_t led_off = (mConfig->led.high_active) ? 0 : 255; uint8_t led_off = (mConfig->led.high_active) ? 0 : 255;
for(uint8_t i = 0; i < 3; i ++) {
if (mConfig->led.led0 != DEF_PIN_OFF) { if (mConfig->led.led[i] != DEF_PIN_OFF) {
pinMode(mConfig->led.led0, OUTPUT); pinMode(mConfig->led.led[i], OUTPUT);
analogWrite(mConfig->led.led0, led_off); analogWrite(mConfig->led.led[i], led_off);
} }
if (mConfig->led.led1 != DEF_PIN_OFF) {
pinMode(mConfig->led.led1, OUTPUT);
analogWrite(mConfig->led.led1, led_off);
} }
} }
@ -488,28 +486,35 @@ void app::updateLed(void) {
uint8_t led_off = (mConfig->led.high_active) ? 0 : 255; uint8_t led_off = (mConfig->led.high_active) ? 0 : 255;
uint8_t led_on = (mConfig->led.high_active) ? (mConfig->led.luminance) : (255-mConfig->led.luminance); uint8_t led_on = (mConfig->led.high_active) ? (mConfig->led.luminance) : (255-mConfig->led.luminance);
if (mConfig->led.led0 != DEF_PIN_OFF) { if (mConfig->led.led[0] != DEF_PIN_OFF) {
Inverter<> *iv; Inverter<> *iv;
for (uint8_t id = 0; id < mSys.getNumInverters(); id++) { for (uint8_t id = 0; id < mSys.getNumInverters(); id++) {
iv = mSys.getInverterByPos(id); iv = mSys.getInverterByPos(id);
if (NULL != iv) { if (NULL != iv) {
if (iv->isProducing()) { if (iv->isProducing()) {
// turn on when at least one inverter is producing // turn on when at least one inverter is producing
analogWrite(mConfig->led.led0, led_on); analogWrite(mConfig->led.led[0], led_on);
break; break;
} }
else if(iv->config->enabled) else if(iv->config->enabled)
analogWrite(mConfig->led.led0, led_off); analogWrite(mConfig->led.led[0], led_off);
} }
} }
} }
if (mConfig->led.led1 != DEF_PIN_OFF) { if (mConfig->led.led[1] != DEF_PIN_OFF) {
if (getMqttIsConnected()) { if (getMqttIsConnected()) {
analogWrite(mConfig->led.led1, led_on); analogWrite(mConfig->led.led[1], led_on);
} else { } else {
analogWrite(mConfig->led.led1, led_off); analogWrite(mConfig->led.led[1], led_off);
}
} }
if (mConfig->led.led[2] != DEF_PIN_OFF) {
if((mTimestamp > (mSunset + mConfig->sun.offsetSecEvening)) || (mTimestamp < (mSunrise + mConfig->sun.offsetSecMorning)))
analogWrite(mConfig->led.led[2], led_on);
else
analogWrite(mConfig->led.led[2], led_off);
} }
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

4
src/app.h

@ -185,10 +185,6 @@ class app : public IApp, public ah::Scheduler {
once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf"); once(std::bind(&PubMqttType::sendDiscoveryConfig, &mMqtt), 1, "disCf");
} }
void setMqttPowerLimitAck(Inverter<> *iv) {
mMqtt.setPowerLimitAck(iv);
}
bool getMqttIsConnected() { bool getMqttIsConnected() {
return mMqtt.isConnected(); return mMqtt.isConnected();
} }

1
src/appInterface.h

@ -52,7 +52,6 @@ class IApp {
virtual bool getRebootRequestState() = 0; virtual bool getRebootRequestState() = 0;
virtual bool getSettingsValid() = 0; virtual bool getSettingsValid() = 0;
virtual void setMqttDiscoveryFlag() = 0; virtual void setMqttDiscoveryFlag() = 0;
virtual void setMqttPowerLimitAck(Inverter<> *iv) = 0;
virtual bool getMqttIsConnected() = 0; virtual bool getMqttIsConnected() = 0;
virtual bool getNrfEnabled() = 0; virtual bool getNrfEnabled() = 0;

3
src/config/config.h

@ -142,6 +142,9 @@
#ifndef DEF_LED1 #ifndef DEF_LED1
#define DEF_LED1 DEF_PIN_OFF #define DEF_LED1 DEF_PIN_OFF
#endif #endif
#ifndef DEF_LED2
#define DEF_LED2 DEF_PIN_OFF
#endif
#ifdef LED_ACTIVE_HIGH #ifdef LED_ACTIVE_HIGH
#define LED_HIGH_ACTIVE true #define LED_HIGH_ACTIVE true
#else #else

18
src/config/settings.h

@ -118,8 +118,7 @@ typedef struct {
} cfgSerial_t; } cfgSerial_t;
typedef struct { typedef struct {
uint8_t led0; // first LED pin uint8_t led[3]; // LED pins
uint8_t led1; // second LED pin
bool high_active; // determines if LEDs are high or low active bool high_active; // determines if LEDs are high or low active
uint8_t luminance; // luminance of LED uint8_t luminance; // luminance of LED
} cfgLed_t; } cfgLed_t;
@ -492,8 +491,9 @@ class settings {
mCfg.inst.iv[i].add2Total = true; mCfg.inst.iv[i].add2Total = true;
} }
mCfg.led.led0 = DEF_LED0; mCfg.led.led[0] = DEF_LED0;
mCfg.led.led1 = DEF_LED1; mCfg.led.led[1] = DEF_LED1;
mCfg.led.led[2] = DEF_LED2;
mCfg.led.high_active = LED_HIGH_ACTIVE; mCfg.led.high_active = LED_HIGH_ACTIVE;
mCfg.led.luminance = 255; mCfg.led.luminance = 255;
@ -751,13 +751,15 @@ class settings {
void jsonLed(JsonObject obj, bool set = false) { void jsonLed(JsonObject obj, bool set = false) {
if(set) { if(set) {
obj[F("0")] = mCfg.led.led0; obj[F("0")] = mCfg.led.led[0];
obj[F("1")] = mCfg.led.led1; obj[F("1")] = mCfg.led.led[1];
obj[F("2")] = mCfg.led.led[2];
obj[F("act_high")] = mCfg.led.high_active; obj[F("act_high")] = mCfg.led.high_active;
obj[F("lum")] = mCfg.led.luminance; obj[F("lum")] = mCfg.led.luminance;
} else { } else {
getVal<uint8_t>(obj, F("0"), &mCfg.led.led0); getVal<uint8_t>(obj, F("0"), &mCfg.led.led[0]);
getVal<uint8_t>(obj, F("1"), &mCfg.led.led1); getVal<uint8_t>(obj, F("1"), &mCfg.led.led[1]);
getVal<uint8_t>(obj, F("2"), &mCfg.led.led[2]);
getVal<bool>(obj, F("act_high"), &mCfg.led.high_active); getVal<bool>(obj, F("act_high"), &mCfg.led.high_active);
getVal<uint8_t>(obj, F("lum"), &mCfg.led.luminance); getVal<uint8_t>(obj, F("lum"), &mCfg.led.luminance);
} }

10
src/defines.h

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 8 #define VERSION_MINOR 8
#define VERSION_PATCH 41 #define VERSION_PATCH 44
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {
@ -106,10 +106,10 @@ typedef struct {
uint32_t frmCnt; uint32_t frmCnt;
uint32_t txCnt; uint32_t txCnt;
uint32_t retransmits; uint32_t retransmits;
uint16_t ivRxCnt; // last iv rx frames (from GetLossRate) uint16_t ivLoss; // lost frames (from GetLossRate)
uint16_t ivTxCnt; // last iv tx frames (from GetLossRate) uint16_t ivSent; // sent frames (from GetLossRate)
uint16_t dtuRxCnt; // current DTU rx frames (since last GetLossRate) uint16_t dtuLoss; // current DTU lost frames (since last GetLossRate)
uint16_t dtuTxCnt; // current DTU tx frames (since last GetLossRate) uint16_t dtuSent; // current DTU sent frames (since last GetLossRate)
} statistics_t; } statistics_t;
#endif /*__DEFINES_H__*/ #endif /*__DEFINES_H__*/

10
src/hm/Communication.h

@ -19,6 +19,7 @@
#define MAX_BUFFER 250 #define MAX_BUFFER 250
typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType; typedef std::function<void(uint8_t, Inverter<> *)> payloadListenerType;
typedef std::function<void(Inverter<> *)> powerLimitAckListenerType;
typedef std::function<void(Inverter<> *)> alarmListenerType; typedef std::function<void(Inverter<> *)> alarmListenerType;
class Communication : public CommQueue<> { class Communication : public CommQueue<> {
@ -40,6 +41,10 @@ class Communication : public CommQueue<> {
mCbPayload = cb; mCbPayload = cb;
} }
void addPowerLimitAckListener(powerLimitAckListenerType cb) {
mCbPwrAck = cb;
}
void addAlarmListener(alarmListenerType cb) { void addAlarmListener(alarmListenerType cb) {
mCbAlarm = cb; mCbAlarm = cb;
} }
@ -151,7 +156,7 @@ class Communication : public CommQueue<> {
if(validateIvSerial(&p->packet[1], q->iv)) { if(validateIvSerial(&p->packet[1], q->iv)) {
q->iv->radioStatistics.frmCnt++; q->iv->radioStatistics.frmCnt++;
q->iv->radioStatistics.dtuRxCnt++; q->iv->mDtuRxCnt++;
if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command
if(parseFrame(p)) if(parseFrame(p))
@ -401,6 +406,7 @@ class Communication : public CommQueue<> {
DBGPRINT(F(" with PowerLimitControl ")); DBGPRINT(F(" with PowerLimitControl "));
DBGPRINTLN(String(q->iv->powerLimit[1])); DBGPRINTLN(String(q->iv->powerLimit[1]));
q->iv->actPowerLimit = 0xffff; // unknown, readback current value q->iv->actPowerLimit = 0xffff; // unknown, readback current value
(mCbPwrAck)(q->iv);
return accepted; return accepted;
} }
@ -492,6 +498,7 @@ class Communication : public CommQueue<> {
for (uint8_t i = 0; i < rec->length; i++) { for (uint8_t i = 0; i < rec->length; i++) {
q->iv->addValue(i, mPayload, rec); q->iv->addValue(i, mPayload, rec);
} }
rec->mqttSentStatus = MqttSentStatus::NEW_DATA;
q->iv->rssi = rssi; q->iv->rssi = rssi;
q->iv->doCalculations(); q->iv->doCalculations();
@ -921,6 +928,7 @@ class Communication : public CommQueue<> {
uint8_t mMaxFrameId; uint8_t mMaxFrameId;
uint8_t mPayload[MAX_BUFFER]; uint8_t mPayload[MAX_BUFFER];
payloadListenerType mCbPayload = NULL; payloadListenerType mCbPayload = NULL;
powerLimitAckListenerType mCbPwrAck = NULL;
alarmListenerType mCbAlarm = NULL; alarmListenerType mCbAlarm = NULL;
Heuristic mHeu; Heuristic mHeu;
uint32_t mLastEmptyQueueMillis = 0; uint32_t mLastEmptyQueueMillis = 0;

114
src/hm/hmInverter.h

@ -64,6 +64,20 @@ struct calcFunc_t {
func_t<T>* func; // function pointer func_t<T>* func; // function pointer
}; };
enum class MqttSentStatus : uint8_t {
NEW_DATA,
LAST_SUCCESS_SENT,
DATA_SENT
};
enum class InverterStatus : uint8_t {
OFF,
STARTING,
PRODUCING,
WAS_PRODUCING,
WAS_ON
};
template<class T=float> template<class T=float>
struct record_t { struct record_t {
byteAssign_t* assign; // assignment of bytes in payload byteAssign_t* assign; // assignment of bytes in payload
@ -71,6 +85,7 @@ struct record_t {
T *record; // data pointer T *record; // data pointer
uint32_t ts; // timestamp of last received payload uint32_t ts; // timestamp of last received payload
uint8_t pyldLen; // expected payload length for plausibility check uint8_t pyldLen; // expected payload length for plausibility check
MqttSentStatus mqttSentStatus; // indicates the current MqTT sent status
}; };
struct alarm_t { struct alarm_t {
@ -94,14 +109,6 @@ const calcFunc_t<T> calcFunctions[] = {
{ CALC_MPDC_CH, &calcMaxPowerDc } { CALC_MPDC_CH, &calcMaxPowerDc }
}; };
enum class InverterStatus : uint8_t {
OFF,
STARTING,
PRODUCING,
WAS_PRODUCING,
WAS_ON
};
template <class REC_TYP> template <class REC_TYP>
class Inverter { class Inverter {
public: public:
@ -124,26 +131,28 @@ class Inverter {
bool isConnected; // shows if inverter was successfully identified (fw version and hardware info) bool isConnected; // shows if inverter was successfully identified (fw version and hardware info)
InverterStatus status; // indicates the current inverter status InverterStatus status; // indicates the current inverter status
std::array<alarm_t, 10> lastAlarm; // holds last 10 alarms std::array<alarm_t, 10> lastAlarm; // holds last 10 alarms
uint8_t alarmNxtWrPos; // indicates the position in array (rolling buffer) int8_t rssi; // RSSI
uint16_t alarmCnt; // counts the total number of occurred alarms uint16_t alarmCnt; // counts the total number of occurred alarms
uint16_t alarmLastId; // lastId which was received uint16_t alarmLastId; // lastId which was received
int8_t rssi; // RSSI uint8_t mCmd; // holds the command to send
bool mGotFragment; // shows if inverter has sent at least one fragment
uint8_t miMultiParts; // helper info for MI multiframe msgs uint8_t miMultiParts; // helper info for MI multiframe msgs
uint8_t outstandingFrames; // helper info to count difference between expected and received frames uint8_t outstandingFrames; // helper info to count difference between expected and received frames
bool mGotFragment; // shows if inverter has sent at least one fragment
uint8_t curFrmCnt; // count received frames in current loop uint8_t curFrmCnt; // count received frames in current loop
bool mGotLastMsg; // shows if inverter has already finished transmission cycle bool mGotLastMsg; // shows if inverter has already finished transmission cycle
uint8_t mCmd; // holds the command to send
bool mIsSingleframeReq; // indicates this is a missing single frame request bool mIsSingleframeReq; // indicates this is a missing single frame request
Radio *radio; // pointer to associated radio class Radio *radio; // pointer to associated radio class
statistics_t radioStatistics; // information about transmitted, failed, ... packets statistics_t radioStatistics; // information about transmitted, failed, ... packets
HeuristicInv heuristics; // heuristic information / logic HeuristicInv heuristics; // heuristic information / logic
uint8_t curCmtFreq; // current used CMT frequency, used to check if freq. was changed during runtime uint8_t curCmtFreq; // current used CMT frequency, used to check if freq. was changed during runtime
bool commEnabled; // 'pause night communication' sets this field to false bool commEnabled; // 'pause night communication' sets this field to false
uint32_t tsMaxAcPower; // holds the timestamp when the MaxAC power was seen
static uint32_t *timestamp; // system timestamp static uint32_t *timestamp; // system timestamp
static cfgInst_t *generalConfig; // general inverter configuration from setup static cfgInst_t *generalConfig; // general inverter configuration from setup
public:
Inverter() { Inverter() {
ivGen = IV_HM; ivGen = IV_HM;
powerLimit[0] = 0xffff; // 6553.5 W Limit -> unlimited powerLimit[0] = 0xffff; // 6553.5 W Limit -> unlimited
@ -155,7 +164,6 @@ class Inverter {
alarmMesIndex = 0; alarmMesIndex = 0;
isConnected = false; isConnected = false;
status = InverterStatus::OFF; status = InverterStatus::OFF;
alarmNxtWrPos = 0;
alarmCnt = 0; alarmCnt = 0;
alarmLastId = 0; alarmLastId = 0;
rssi = -127; rssi = -127;
@ -165,6 +173,7 @@ class Inverter {
mIsSingleframeReq = false; mIsSingleframeReq = false;
radio = NULL; radio = NULL;
commEnabled = true; commEnabled = true;
tsMaxAcPower = 0;
memset(&radioStatistics, 0, sizeof(statistics_t)); memset(&radioStatistics, 0, sizeof(statistics_t));
memset(heuristics.txRfQuality, -6, 5); memset(heuristics.txRfQuality, -6, 5);
@ -177,28 +186,33 @@ class Inverter {
if(mDevControlRequest) { if(mDevControlRequest) {
cb(devControlCmd, true); cb(devControlCmd, true);
mDevControlRequest = false; mDevControlRequest = false;
} else if (IV_MI != ivGen) { } else if (IV_MI != ivGen) { // HM / HMS / HMT
mGetLossInterval++; mGetLossInterval++;
if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0)) if(mNextLive)
cb(AlarmData, false); // get last alarms cb(RealTimeRunData_Debug, false); // get live data
else if(0 == getFwVersion()) else {
cb(InverterDevInform_All, false); // get firmware version mNextLive = true;
else if(0 == getHwVersion()) if(actPowerLimit == 0xffff)
cb(InverterDevInform_Simple, false); // get hardware version
else if(actPowerLimit == 0xffff)
cb(SystemConfigPara, false); // power limit info cb(SystemConfigPara, false); // power limit info
else if(InitDataState != devControlCmd) { else if(InitDataState != devControlCmd) {
cb(devControlCmd, false); // custom command which was received by API cb(devControlCmd, false); // custom command which was received by API
devControlCmd = InitDataState; devControlCmd = InitDataState;
mGetLossInterval = 1; mGetLossInterval = 1;
} else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile } else if((alarmLastId != alarmMesIndex) && (alarmMesIndex != 0))
cb(AlarmData, false); // get last alarms
else if(0 == getFwVersion())
cb(InverterDevInform_All, false); // get firmware version
else if(0 == getHwVersion())
cb(InverterDevInform_Simple, false); // get hardware version
else if((0 == mGridLen) && generalConfig->readGrid) { // read grid profile
cb(GridOnProFilePara, false); cb(GridOnProFilePara, false);
} else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate } else if (mGetLossInterval > AHOY_GET_LOSS_INTERVAL) { // get loss rate
mGetLossInterval = 1; mGetLossInterval = 1;
cb(GetLossRate, false); cb(GetLossRate, false);
} else } else
cb(RealTimeRunData_Debug, false); // get live data cb(RealTimeRunData_Debug, false); // get live data
} else { }
} else { // MI
if(0 == getFwVersion()) if(0 == getFwVersion())
cb(0x0f, false); // get firmware version; for MI, this makes part of polling the device software and hardware version number cb(0x0f, false); // get firmware version; for MI, this makes part of polling the device software and hardware version number
else { else {
@ -313,8 +327,8 @@ class Inverter {
} }
if(rec == &recordMeas) { if(rec == &recordMeas) {
mNextLive = false; // live data received
DPRINTLN(DBG_VERBOSE, "add real time"); DPRINTLN(DBG_VERBOSE, "add real time");
// get last alarm message index and save it in the inverter object // get last alarm message index and save it in the inverter object
if (getPosByChFld(0, FLD_EVT, rec) == pos) { if (getPosByChFld(0, FLD_EVT, rec) == pos) {
if (alarmMesIndex < rec->record[pos]) { if (alarmMesIndex < rec->record[pos]) {
@ -498,6 +512,7 @@ class Inverter {
DPRINTLN(DBG_VERBOSE, F("hmInverter.h:initAssignment")); DPRINTLN(DBG_VERBOSE, F("hmInverter.h:initAssignment"));
rec->ts = 0; rec->ts = 0;
rec->length = 0; rec->length = 0;
rec->mqttSentStatus = MqttSentStatus::DATA_SENT; // nothing new to transmit
switch (cmd) { switch (cmd) {
case RealTimeRunData_Debug: case RealTimeRunData_Debug:
if (INV_TYPE_1CH == type) { if (INV_TYPE_1CH == type) {
@ -582,7 +597,7 @@ class Inverter {
void resetAlarms() { void resetAlarms() {
lastAlarm.fill({0, 0, 0}); lastAlarm.fill({0, 0, 0});
alarmNxtWrPos = 0; mAlarmNxtWrPos = 0;
alarmCnt = 0; alarmCnt = 0;
alarmLastId = 0; alarmLastId = 0;
@ -595,22 +610,35 @@ class Inverter {
uint16_t rxCnt = (pyld[0] << 8) + pyld[1]; uint16_t rxCnt = (pyld[0] << 8) + pyld[1];
uint16_t txCnt = (pyld[2] << 8) + pyld[3]; uint16_t txCnt = (pyld[2] << 8) + pyld[3];
if (radioStatistics.ivRxCnt || radioStatistics.ivTxCnt) { // there was successful GetLossRate in the past if (mIvRxCnt || mIvTxCnt) { // there was successful GetLossRate in the past
radioStatistics.ivSent = mDtuTxCnt;
if (rxCnt < mIvRxCnt) // overflow
radioStatistics.ivLoss = radioStatistics.ivSent - (rxCnt + ((uint16_t)65535 - mIvRxCnt) + 1);
else
radioStatistics.ivLoss = radioStatistics.ivSent - (rxCnt - mIvRxCnt);
if (txCnt < mIvTxCnt) // overflow
radioStatistics.dtuSent = txCnt + ((uint16_t)65535 - mIvTxCnt) + 1;
else
radioStatistics.dtuSent = txCnt - mIvTxCnt;
radioStatistics.dtuLoss = radioStatistics.dtuSent - mDtuRxCnt;
DPRINT_IVID(DBG_INFO, id); DPRINT_IVID(DBG_INFO, id);
DBGPRINT(F("Inv loss: ")); DBGPRINT(F("Inv loss: "));
DBGPRINT(String (radioStatistics.dtuTxCnt - (rxCnt - radioStatistics.ivRxCnt))); DBGPRINT(String(radioStatistics.ivLoss));
DBGPRINT(F(" of ")); DBGPRINT(F(" of "));
DBGPRINT(String (radioStatistics.dtuTxCnt)); DBGPRINT(String(radioStatistics.ivSent));
DBGPRINT(F(", DTU loss: ")); DBGPRINT(F(", DTU loss: "));
DBGPRINT(String (txCnt - radioStatistics.ivTxCnt - radioStatistics.dtuRxCnt)); DBGPRINT(String(radioStatistics.dtuLoss));
DBGPRINT(F(" of ")); DBGPRINT(F(" of "));
DBGPRINTLN(String (txCnt - radioStatistics.ivTxCnt)); DBGPRINTLN(String(radioStatistics.dtuSent));
} }
radioStatistics.ivRxCnt = rxCnt; mIvRxCnt = rxCnt;
radioStatistics.ivTxCnt = txCnt; mIvTxCnt = txCnt;
radioStatistics.dtuRxCnt = 0; // start new interval mDtuRxCnt = 0; // start new interval
radioStatistics.dtuTxCnt = 0; // start new interval mDtuTxCnt = 0; // start new interval
return true; return true;
} }
@ -785,9 +813,9 @@ class Inverter {
private: private:
inline void addAlarm(uint16_t code, uint32_t start, uint32_t end) { inline void addAlarm(uint16_t code, uint32_t start, uint32_t end) {
lastAlarm[alarmNxtWrPos] = alarm_t(code, start, end); lastAlarm[mAlarmNxtWrPos] = alarm_t(code, start, end);
if(++alarmNxtWrPos >= 10) // rolling buffer if(++mAlarmNxtWrPos >= 10) // rolling buffer
alarmNxtWrPos = 0; mAlarmNxtWrPos = 0;
} }
void toRadioId(void) { void toRadioId(void) {
@ -806,6 +834,14 @@ class Inverter {
uint8_t mGridLen = 0; uint8_t mGridLen = 0;
uint8_t mGridProfile[MAX_GRID_LENGTH]; uint8_t mGridProfile[MAX_GRID_LENGTH];
uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug
uint16_t mIvRxCnt = 0;
uint16_t mIvTxCnt = 0;
uint8_t mAlarmNxtWrPos = 0; // indicates the position in array (rolling buffer)
bool mNextLive = true; // first read live data after booting up then version etc.
public:
uint16_t mDtuRxCnt = 0;
uint16_t mDtuTxCnt = 0;
}; };
template <class REC_TYP> template <class REC_TYP>
@ -937,9 +973,11 @@ static T calcMaxPowerDc(Inverter<> *iv, uint8_t arg0) {
dcMaxPower = iv->getValue(i, rec); dcMaxPower = iv->getValue(i, rec);
} }
} }
if(dcPower > dcMaxPower) if(dcPower > dcMaxPower) {
iv->tsMaxAcPower = *iv->timestamp;
return dcPower; return dcPower;
} }
}
return dcMaxPower; return dcMaxPower;
} }

2
src/hm/hmRadio.h

@ -339,7 +339,7 @@ class HmRadio : public Radio {
mMillis = millis(); mMillis = millis();
mLastIv = iv; mLastIv = iv;
iv->radioStatistics.dtuTxCnt++; iv->mDtuTxCnt++;
} }
uint64_t getIvId(Inverter<> *iv) { uint64_t getIvId(Inverter<> *iv) {

6
src/hm/radio.h

@ -14,6 +14,8 @@
#include "../utils/dbg.h" #include "../utils/dbg.h"
#include "../utils/crc.h" #include "../utils/crc.h"
enum { IRQ_UNKNOWN = 0, IRQ_OK, IRQ_ERROR };
// forward declaration of class // forward declaration of class
template <class REC_TYP=float> template <class REC_TYP=float>
class Inverter; class Inverter;
@ -30,6 +32,7 @@ class Radio {
void handleIntr(void) { void handleIntr(void) {
mIrqRcvd = true; mIrqRcvd = true;
mIrqOk = IRQ_OK;
} }
void sendCmdPacket(Inverter<> *iv, uint8_t mid, uint8_t pid, bool isRetransmit, bool appendCrc16=true) { void sendCmdPacket(Inverter<> *iv, uint8_t mid, uint8_t pid, bool isRetransmit, bool appendCrc16=true) {
@ -65,6 +68,7 @@ class Radio {
public: public:
std::queue<packet_t> mBufCtrl; std::queue<packet_t> mBufCtrl;
uint8_t mIrqOk = IRQ_UNKNOWN;
protected: protected:
virtual void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) = 0; virtual void sendPacket(Inverter<> *iv, uint8_t len, bool isRetransmit, bool appendCrc16=true) = 0;
@ -77,6 +81,8 @@ class Radio {
CP_U32_LittleEndian(&mTxBuf[5], mDtuSn); CP_U32_LittleEndian(&mTxBuf[5], mDtuSn);
mTxBuf[9] = pid; mTxBuf[9] = pid;
memset(&mTxBuf[10], 0x00, (MAX_RF_PAYLOAD_SIZE-10)); memset(&mTxBuf[10], 0x00, (MAX_RF_PAYLOAD_SIZE-10));
if(IRQ_UNKNOWN == mIrqOk)
mIrqOk = IRQ_ERROR;
} }
void updateCrcs(uint8_t *len, bool appendCrc16=true) { void updateCrcs(uint8_t *len, bool appendCrc16=true) {

2
src/hms/hmsRadio.h

@ -112,7 +112,7 @@ class CmtRadio : public Radio {
if(CMT_ERR_RX_IN_FIFO == status) if(CMT_ERR_RX_IN_FIFO == status)
mIrqRcvd = true; mIrqRcvd = true;
} }
iv->radioStatistics.dtuTxCnt++; iv->mDtuTxCnt++;
} }
uint64_t getIvId(Inverter<> *iv) { uint64_t getIvId(Inverter<> *iv) {

36
src/publisher/pubMqttIvData.h

@ -1,5 +1,5 @@
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// 2023 Ahoy, https://ahoydtu.de // 2024 Ahoy, https://ahoydtu.de
// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -28,7 +28,6 @@ class PubMqttIvData {
mState = IDLE; mState = IDLE;
mZeroValues = false; mZeroValues = false;
memset(mIvLastRTRpub, 0, MAX_NUM_INVERTERS * sizeof(uint32_t));
mRTRDataHasBeenSent = false; mRTRDataHasBeenSent = false;
mTable[IDLE] = &PubMqttIvData::stateIdle; mTable[IDLE] = &PubMqttIvData::stateIdle;
@ -102,7 +101,7 @@ class PubMqttIvData {
mPos = 0; mPos = 0;
if(found) { if(found) {
record_t<> *rec = mIv->getRecordStruct(mCmd); record_t<> *rec = mIv->getRecordStruct(mCmd);
if((RealTimeRunData_Debug == mCmd) && mIv->getLastTs(rec) != 0 ) { //workaround for startup. Suspect, mCmd might cause to much messages.... if(MqttSentStatus::NEW_DATA == rec->mqttSentStatus) {
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/last_success", mIv->config->name); snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/last_success", mIv->config->name);
snprintf(mVal, 40, "%d", mIv->getLastTs(rec)); snprintf(mVal, 40, "%d", mIv->getLastTs(rec));
mPublish(mSubTopic, mVal, true, QOS_0); mPublish(mSubTopic, mVal, true, QOS_0);
@ -112,13 +111,14 @@ class PubMqttIvData {
snprintf(mVal, 40, "%d", mIv->rssi); snprintf(mVal, 40, "%d", mIv->rssi);
mPublish(mSubTopic, mVal, false, QOS_0); mPublish(mSubTopic, mVal, false, QOS_0);
} }
rec->mqttSentStatus = MqttSentStatus::LAST_SUCCESS_SENT;
} }
mIv->isProducing(); // recalculate status mIv->isProducing(); // recalculate status
mState = SEND_DATA; mState = SEND_DATA;
} else if(mSendTotals && mTotalFound) } else if(mSendTotals && mTotalFound) {
mState = SEND_TOTALS; mState = SEND_TOTALS;
else { } else {
mSendList->pop(); mSendList->pop();
mZeroValues = false; mZeroValues = false;
mState = START; mState = START;
@ -132,12 +132,8 @@ class PubMqttIvData {
DPRINT(DBG_WARN, "unknown record to publish!"); DPRINT(DBG_WARN, "unknown record to publish!");
return; return;
} }
uint32_t lastTs = mIv->getLastTs(rec);
bool pubData = (lastTs > 0);
if (mCmd == RealTimeRunData_Debug)
pubData &= (lastTs != mIvLastRTRpub[mIv->id]);
if (pubData) { if (MqttSentStatus::LAST_SUCCESS_SENT == rec->mqttSentStatus) {
if(mPos < rec->length) { if(mPos < rec->length) {
bool retained = false; bool retained = false;
if (mCmd == RealTimeRunData_Debug) { if (mCmd == RealTimeRunData_Debug) {
@ -172,21 +168,16 @@ class PubMqttIvData {
} else } else
mAllTotalFound = false; mAllTotalFound = false;
} }
} else }
mIvLastRTRpub[mIv->id] = lastTs;
uint8_t qos = QOS_0;
if(FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId)
qos = QOS_2;
if((mIvSend == mIv) || (NULL == mIvSend)) { // send only updated values, or all if the inverter is NULL uint8_t qos = (FLD_ACT_ACTIVE_PWR_LIMIT == rec->assign[mPos].fieldId) ? QOS_2 : QOS_0;
snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]); snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/ch%d/%s", mIv->config->name, rec->assign[mPos].ch, fields[rec->assign[mPos].fieldId]);
snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec))); snprintf(mVal, 40, "%g", ah::round3(mIv->getValue(mPos, rec)));
mPublish(mSubTopic, mVal, retained, qos); mPublish(mSubTopic, mVal, retained, qos);
}
mPos++; mPos++;
} else { } else {
sendRadioStat(rec->length); sendRadioStat(rec->length);
rec->mqttSentStatus = MqttSentStatus::DATA_SENT;
mState = FIND_NXT_IV; mState = FIND_NXT_IV;
} }
} else } else
@ -201,10 +192,10 @@ class PubMqttIvData {
mIv->radioStatistics.rxFail, mIv->radioStatistics.rxFail,
mIv->radioStatistics.rxFailNoAnser, mIv->radioStatistics.rxFailNoAnser,
mIv->radioStatistics.retransmits, mIv->radioStatistics.retransmits,
mIv->radioStatistics.ivRxCnt, mIv->radioStatistics.ivLoss,
mIv->radioStatistics.ivTxCnt, mIv->radioStatistics.ivSent,
mIv->radioStatistics.dtuRxCnt, mIv->radioStatistics.dtuLoss,
mIv->radioStatistics.dtuTxCnt); mIv->radioStatistics.dtuSent);
mPublish(mSubTopic, mVal, false, QOS_0); mPublish(mSubTopic, mVal, false, QOS_0);
} }
@ -263,7 +254,6 @@ class PubMqttIvData {
Inverter<> *mIv, *mIvSend; Inverter<> *mIv, *mIvSend;
uint8_t mPos; uint8_t mPos;
uint32_t mIvLastRTRpub[MAX_NUM_INVERTERS];
bool mRTRDataHasBeenSent; bool mRTRDataHasBeenSent;
char mSubTopic[32 + MAX_NAME_LENGTH + 1]; char mSubTopic[32 + MAX_NAME_LENGTH + 1];

12
src/web/RestApi.h

@ -405,6 +405,10 @@ class RestApi {
obj[F("frame_cnt")] = iv->radioStatistics.frmCnt; obj[F("frame_cnt")] = iv->radioStatistics.frmCnt;
obj[F("tx_cnt")] = iv->radioStatistics.txCnt; obj[F("tx_cnt")] = iv->radioStatistics.txCnt;
obj[F("retransmits")] = iv->radioStatistics.retransmits; obj[F("retransmits")] = iv->radioStatistics.retransmits;
obj[F("ivLoss")] = iv->radioStatistics.ivLoss;
obj[F("ivSent")] = iv->radioStatistics.ivSent;
obj[F("dtuLoss")] = iv->radioStatistics.dtuLoss;
obj[F("dtuSent")] = iv->radioStatistics.dtuSent;
} }
void getIvPowerLimitAck(JsonObject obj, uint8_t id) { void getIvPowerLimitAck(JsonObject obj, uint8_t id) {
@ -481,6 +485,7 @@ class RestApi {
obj[F("status")] = (uint8_t)iv->getStatus(); obj[F("status")] = (uint8_t)iv->getStatus();
obj[F("alarm_cnt")] = iv->alarmCnt; obj[F("alarm_cnt")] = iv->alarmCnt;
obj[F("rssi")] = iv->rssi; obj[F("rssi")] = iv->rssi;
obj[F("ts_max_ac_pwr")] = iv->tsMaxAcPower;
JsonArray ch = obj.createNestedArray("ch"); JsonArray ch = obj.createNestedArray("ch");
@ -627,8 +632,9 @@ class RestApi {
obj[F("sclk")] = mConfig->nrf.pinSclk; obj[F("sclk")] = mConfig->nrf.pinSclk;
obj[F("mosi")] = mConfig->nrf.pinMosi; obj[F("mosi")] = mConfig->nrf.pinMosi;
obj[F("miso")] = mConfig->nrf.pinMiso; obj[F("miso")] = mConfig->nrf.pinMiso;
obj[F("led0")] = mConfig->led.led0; obj[F("led0")] = mConfig->led.led[0];
obj[F("led1")] = mConfig->led.led1; obj[F("led1")] = mConfig->led.led[1];
obj[F("led2")] = mConfig->led.led[2];
obj[F("led_high_active")] = mConfig->led.high_active; obj[F("led_high_active")] = mConfig->led.high_active;
obj[F("led_lum")] = mConfig->led.luminance; obj[F("led_lum")] = mConfig->led.luminance;
} }
@ -648,6 +654,7 @@ class RestApi {
if(mConfig->cmt.enabled) { if(mConfig->cmt.enabled) {
obj[F("isconnected")] = mRadioCmt->isChipConnected(); obj[F("isconnected")] = mRadioCmt->isChipConnected();
obj[F("sn")] = String(mRadioCmt->getDTUSn(), HEX); obj[F("sn")] = String(mRadioCmt->getDTUSn(), HEX);
obj[F("irqOk")] = mRadioCmt->mIrqOk;
} }
} }
#endif #endif
@ -658,6 +665,7 @@ class RestApi {
obj[F("isconnected")] = mRadioNrf->isChipConnected(); obj[F("isconnected")] = mRadioNrf->isChipConnected();
obj[F("dataRate")] = mRadioNrf->getDataRate(); obj[F("dataRate")] = mRadioNrf->getDataRate();
obj[F("sn")] = String(mRadioNrf->getDTUSn(), HEX); obj[F("sn")] = String(mRadioNrf->getDTUSn(), HEX);
obj[F("irqOk")] = mRadioNrf->mIrqOk;
} }
} }

6
src/web/html/index.html

@ -99,17 +99,17 @@
} }
if(obj.disNightComm) { if(obj.disNightComm) {
if(((obj.ts_sunrise - obj.ts_offsSr) < obj.ts_now) if(((obj.ts_sunrise + obj.ts_offsSr) < obj.ts_now)
&& ((obj.ts_sunset + obj.ts_offsSs) > obj.ts_now)) { && ((obj.ts_sunset + obj.ts_offsSs) > obj.ts_now)) {
commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE'));
} }
else { else {
commInfo = "Night time, inverter polling disabled, "; commInfo = "Night time, inverter polling disabled, ";
if(obj.ts_now > (obj.ts_sunrise - obj.ts_offsSr)) { if(obj.ts_now > (obj.ts_sunrise + obj.ts_offsSr)) {
commInfo += "paused at " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE')); commInfo += "paused at " + (new Date((obj.ts_sunset + obj.ts_offsSs) * 1000).toLocaleString('de-DE'));
} }
else { else {
commInfo += "will start polling at " + (new Date((obj.ts_sunrise - obj.ts_offsSr) * 1000).toLocaleString('de-DE')); commInfo += "will start polling at " + (new Date((obj.ts_sunrise + obj.ts_offsSr) * 1000).toLocaleString('de-DE'));
} }
} }
} }

4
src/web/html/setup.html

@ -818,7 +818,7 @@
ml("div", {class: "col-2"}, cbDisNightCom) ml("div", {class: "col-2"}, cbDisNightCom)
]), ]),
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [
ml("div", {class: "col-10"}, "Include inverter to sum of total (should be checked by default)"), ml("div", {class: "col-10"}, "Include inverter to sum of total (should be checked by default, MqTT only)"),
ml("div", {class: "col-2"}, cbAddTotal) ml("div", {class: "col-2"}, cbAddTotal)
]) ])
]), ]),
@ -962,7 +962,7 @@
if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins; if ("ESP32-S3" == system.chip_model) pinList = esp32s3pins;
else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins; else if("ESP32-C3" == system["chip_model"]) pinList = esp32c3pins;
/*ENDIF_ESP32*/ /*ENDIF_ESP32*/
pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected']]; pins = [['led0', 'pinLed0', 'At least one inverter is producing'], ['led1', 'pinLed1', 'MqTT connected'], ['led2', 'pinLed2', 'Night time']];
for(p of pins) { for(p of pins) {
e.append( e.append(
ml("div", {class: "row mb-3"}, [ ml("div", {class: "row mb-3"}, [

27
src/web/html/style.css

@ -654,39 +654,24 @@ div.hr {
} }
.css-tooltip{ .tooltip{
position: relative; position: relative;
} }
.css-tooltip:hover:after{ .tooltip:hover:after {
content:attr(data-tooltip); content: attr(data);
background:#000; background: var(--nav-active);
padding: 5px; padding: 5px;
border-radius: 3px; border-radius: 3px;
display: inline-block; display: inline-block;
position: absolute; position: absolute;
transform: translate(-50%,-100%); transform: translate(-50%,-100%);
margin:0 auto; margin:0 auto;
color:#FFF; color: var(--fg2);
min-width: 100px; min-width: 100px;
min-width:150px;
top: -5px; top: -5px;
left: 50%; left: 50%;
text-align:center; text-align:center;
} font-size: 1rem;
.css-tooltip:hover:before {
top:-5px;
left: 50%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(0, 0, 0, 0);
border-top-color: #000;
border-width: 5px;
margin-left: -5px;
transform: translate(0,0px);
} }
#modal { #modal {

10
src/web/html/system.html

@ -44,12 +44,21 @@
return ml("div", {class: "head p-2 mt-3"}, ml("div", {class: "row"}, ml("div", {class: "col a-c"}, text))) return ml("div", {class: "head p-2 mt-3"}, ml("div", {class: "row"}, ml("div", {class: "col a-c"}, text)))
} }
function irqBadge(state) {
switch(state) {
case 0: return badge(false, "unknown", "warning"); break;
case 1: return badge(true, "true"); break;
default: return badge(false, "false"); break;
}
}
function parseRadio(obj) { function parseRadio(obj) {
const dr = ["1 M", "2 M", "250 k"] const dr = ["1 M", "2 M", "250 k"]
if(obj.radioNrf.en) { if(obj.radioNrf.en) {
lines = [ lines = [
tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "not ") + "connected")), tr("NRF24L01", badge(obj.radioNrf.isconnected, ((obj.radioNrf.isconnected) ? "" : "not ") + "connected")),
tr("Interrupt Pin working", irqBadge(obj.radioNrf.irqOk)),
tr("NRF24 Data Rate", dr[obj.radioNrf.dataRate] + "bps"), tr("NRF24 Data Rate", dr[obj.radioNrf.dataRate] + "bps"),
tr("DTU Radio ID", obj.radioNrf.sn) tr("DTU Radio ID", obj.radioNrf.sn)
]; ];
@ -67,6 +76,7 @@
if(obj.radioCmt.en) { if(obj.radioCmt.en) {
cmt = [ cmt = [
tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "not ") + "connected")), tr("CMT2300A", badge(obj.radioCmt.isconnected, ((obj.radioCmt.isconnected) ? "" : "not ") + "connected")),
tr("Interrupt Pin working", irqBadge(obj.radioCmt.irqOk)),
tr("DTU Radio ID", obj.radioCmt.sn) tr("DTU Radio ID", obj.radioCmt.sn)
]; ];
} else } else

29
src/web/html/visualization.html

@ -46,13 +46,14 @@
]); ]);
} }
function numMid(val, unit, des) { function numMid(val, unit, des, opt={class: "fs-6"}) {
return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [ return ml("div", {class: "col-6 col-sm-4 col-md-3 mb-2"}, [
ml("div", {class: "row"}, ml("div", {class: "row"},
ml("div", {class: "col"}, [ ml("div", {class: "col"}, [
ml("span", {class: "fs-6"}, String(Math.round(val * 100) / 100)), ml("span", opt, String(Math.round(val * 100) / 100)),
ml("span", {class: "fs-8 mx-1"}, unit) ml("span", {class: "fs-8 mx-1"}, unit)
])), ])
),
ml("div", {class: "row"}, ml("div", {class: "row"},
ml("div", {class: "col"}, ml("div", {class: "col"},
ml("span", {class: "fs-9"}, des) ml("span", {class: "fs-9"}, des)
@ -92,10 +93,10 @@
function ivHead(obj) { function ivHead(obj) {
if(0 != obj.status) { // only add totals if inverter is online if(0 != obj.status) { // only add totals if inverter is online
total[0] += obj.ch[0][2]; // P_AC total[0] += obj.ch[0][2]; // P_AC
total[3] += obj.ch[0][11]; // MAX P_AC
total[4] += obj.ch[0][8]; // P_DC total[4] += obj.ch[0][8]; // P_DC
total[5] += obj.ch[0][10]; // Q_AC total[5] += obj.ch[0][10]; // Q_AC
} }
total[3] += obj.ch[0][11]; // MAX P_AC
total[1] += obj.ch[0][7]; // YieldDay total[1] += obj.ch[0][7]; // YieldDay
total[2] += obj.ch[0][6]; // YieldTotal total[2] += obj.ch[0][6]; // YieldTotal
@ -107,8 +108,10 @@
if(65535 != obj.power_limit_read) { if(65535 != obj.power_limit_read) {
pwrLimit = obj.power_limit_read + "&nbsp;%"; pwrLimit = obj.power_limit_read + "&nbsp;%";
if(0 != obj.max_pwr) if(0 != obj.max_pwr)
pwrLimit += ", " + Math.round(obj.max_pwr * obj.power_limit_read / 100) + "W"; pwrLimit += ", " + Math.round(obj.max_pwr * obj.power_limit_read / 100) + "&nbsp;W";
} }
var maxAcPwr = toIsoDateStr(new Date(obj.ts_max_ac_pwr * 1000));
return ml("div", {class: "row mt-2"}, return ml("div", {class: "row mt-2"},
ml("div", {class: "col"}, [ ml("div", {class: "col"}, [
ml("div", {class: "p-2 " + clh}, ml("div", {class: "p-2 " + clh},
@ -134,7 +137,7 @@
]), ]),
ml("div", {class: "hr"}), ml("div", {class: "hr"}),
ml("div", {class: "row mt-2"},[ ml("div", {class: "row mt-2"},[
numMid(obj.ch[0][11], "W", "Max AC Power"), numMid(obj.ch[0][11], "W", "Max AC Power", {class: "fs-6 tooltip", data: maxAcPwr}),
numMid(obj.ch[0][8], "W", "DC Power"), numMid(obj.ch[0][8], "W", "DC Power"),
numMid(obj.ch[0][0], "V", "AC Voltage"), numMid(obj.ch[0][0], "V", "AC Voltage"),
numMid(obj.ch[0][1], "A", "AC Current"), numMid(obj.ch[0][1], "A", "AC Current"),
@ -193,9 +196,9 @@
if(obj.rssi > -127) { if(obj.rssi > -127) {
if(obj.generation < 2) if(obj.generation < 2)
ageInfo += " (RSSI: " + ((obj.rssi == -64) ? ">=" : "<") + " -64dBm)"; ageInfo += " (RSSI: " + ((obj.rssi == -64) ? "&gt;=" : "&lt;") + " -64&nbsp;dBm)";
else else
ageInfo += " (RSSI: " + obj.rssi + "dBm)"; ageInfo += " (RSSI: " + obj.rssi + "&nbsp;dBm)";
} }
return ml("div", {class: "mb-5"}, [ return ml("div", {class: "mb-5"}, [
@ -385,11 +388,13 @@
var html = ml("table", {class: "table"}, [ var html = ml("table", {class: "table"}, [
ml("tbody", {}, [ ml("tbody", {}, [
tr2(["TX count", obj.tx_cnt, ""]), tr2(["TX count", obj.tx_cnt, ""]),
tr2(["RX success", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + "%"]), tr2(["RX success", obj.rx_success, String(Math.round(obj.rx_success / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
tr2(["RX fail", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + "%"]), tr2(["RX fail", obj.rx_fail, String(Math.round(obj.rx_fail / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
tr2(["RX no answer", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + "%"]), tr2(["RX no answer", obj.rx_fail_answer, String(Math.round(obj.rx_fail_answer / obj.tx_cnt * 10000) / 100) + "&nbsp;%"]),
tr2(["RX fragments", obj.frame_cnt, ""]), tr2(["RX fragments", obj.frame_cnt, ""]),
tr2(["TX retransmits", obj.retransmits, ""]) tr2(["TX retransmits", obj.retransmits, ""]),
tr2(["Inverter loss rate", "lost " + obj.ivLoss + " of " + obj.ivSent, String(Math.round(obj.ivLoss / obj.ivSent * 10000) / 100) + "&nbsp;%"]),
tr2(["DTU loss rate", "lost " + obj.dtuLoss + " of " + obj.dtuSent, String(Math.round(obj.dtuLoss / obj.dtuSent * 10000) / 100) + "&nbsp;%"])
]) ])
]) ])
modal("Radio statistics for inverter " + obj.name, ml("div", {}, html)) modal("Radio statistics for inverter " + obj.name, ml("div", {}, html))

116
src/web/web.h

@ -39,7 +39,7 @@
#define WEB_SERIAL_BUF_SIZE 2048 #define WEB_SERIAL_BUF_SIZE 2048
const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLedHighActive", "pinLedLum", "pinCmtSclk", "pinSdio", "pinCsb", "pinFcsb", "pinGpio3"}; const char* const pinArgNames[] = {"pinCs", "pinCe", "pinIrq", "pinSclk", "pinMosi", "pinMiso", "pinLed0", "pinLed1", "pinLed2", "pinLedHighActive", "pinLedLum", "pinCmtSclk", "pinSdio", "pinCsb", "pinFcsb", "pinGpio3"};
template <class HMSYSTEM> template <class HMSYSTEM>
class Web { class Web {
@ -506,7 +506,7 @@ class Web {
// pinout // pinout
uint8_t pin; uint8_t pin;
for (uint8_t i = 0; i < 15; i++) { for (uint8_t i = 0; i < 16; i++) {
pin = request->arg(String(pinArgNames[i])).toInt(); pin = request->arg(String(pinArgNames[i])).toInt();
switch(i) { switch(i) {
case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_NRF_CS_PIN); break; case 0: mConfig->nrf.pinCs = ((pin != 0xff) ? pin : DEF_NRF_CS_PIN); break;
@ -515,15 +515,16 @@ class Web {
case 3: mConfig->nrf.pinSclk = ((pin != 0xff) ? pin : DEF_NRF_SCLK_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 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 5: mConfig->nrf.pinMiso = ((pin != 0xff) ? pin : DEF_NRF_MISO_PIN); break;
case 6: mConfig->led.led0 = pin; break; case 6: mConfig->led.led[0] = pin; break;
case 7: mConfig->led.led1 = pin; break; case 7: mConfig->led.led[1] = pin; break;
case 8: mConfig->led.high_active = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense case 8: mConfig->led.led[2] = pin; break;
case 9: mConfig->led.luminance = pin; break; // this is not really a pin but a polarity, but handling it close to here makes sense 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->cmt.pinSclk = pin; break; 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.pinSdio = pin; break; case 11: mConfig->cmt.pinSclk = pin; break;
case 12: mConfig->cmt.pinCsb = pin; break; case 12: mConfig->cmt.pinSdio = pin; break;
case 13: mConfig->cmt.pinFcsb = pin; break; case 13: mConfig->cmt.pinCsb = pin; break;
case 14: mConfig->cmt.pinIrq = pin; break; case 14: mConfig->cmt.pinFcsb = pin; break;
case 15: mConfig->cmt.pinIrq = pin; break;
} }
} }
@ -661,13 +662,16 @@ class Web {
// NOTE: Grouping for fields with channels and totals is currently not working // NOTE: Grouping for fields with channels and totals is currently not working
// TODO: Handle grouping and sorting for independant from channel number // 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) // NOTE: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels)
const char * metricPrefix = "ahoy_solar_"; const char * metricConstPrefix = "ahoy_solar_";
const char * metricConstInverterFormat = " {inverter=\"%s\"} %d\n";
typedef enum { typedef enum {
metricsStateInverterInfo=0, metricsStateInverterEnabled=1, metricsStateInverterAvailable=2, metricsStateInverterProducing=3, metricsStateInverterInfo=0, metricsStateInverterEnabled=1, metricsStateInverterAvailable=2,
metricsStateInverterPowerLimitRead=4, metricsStateInverterPowerLimitAck=5, metricsStateInverterMaxPower=6, metricsStateInverterProducing=3, metricsStateInverterPowerLimitRead=4, metricsStateInverterPowerLimitAck=5,
metricsStateInverterRxSuccess=7, metricsStateInverterRxFail=8, metricsStateInverterRxFailAnswer=9, metricsStateInverterMaxPower=6, metricsStateInverterRxSuccess=7, metricsStateInverterRxFail=8,
metricsStateInverterFrameCnt=10, metricsStateInverterTxCnt=11, metricsStateInverterRetransmits=12, metricsStateInverterRxFailAnswer=9, metricsStateInverterFrameCnt=10, metricsStateInverterTxCnt=11,
metricStateRealtimeFieldId=metricsStateInverterRetransmits+1, // ensure that this state follows the last per_inverter state metricsStateInverterRetransmits=12, metricsStateInverterIvRxCnt=13, metricsStateInverterIvTxCnt=14,
metricsStateInverterDtuRxCnt=15, metricsStateInverterDtuTxCnt=16,
metricStateRealtimeFieldId=metricsStateInverterDtuTxCnt+1, // ensure that this state follows the last per_inverter state
metricStateRealtimeInverterId, metricStateRealtimeInverterId,
metricsStateAlarmData, metricsStateAlarmData,
metricsStateStart, metricsStateStart,
@ -675,24 +679,29 @@ class Web {
} MetricStep_t; } MetricStep_t;
MetricStep_t metricsStep; MetricStep_t metricsStep;
typedef struct { typedef struct {
const char *topic;
const char *type; const char *type;
const char *format; const char *format;
const std::function<uint64_t(Inverter<> *iv)> valueFunc; const std::function<uint64_t(Inverter<> *iv)> valueFunc;
} InverterMetric_t; } InverterMetric_t;
InverterMetric_t inverterMetrics[13] = { InverterMetric_t inverterMetrics[17] = {
{ "info", "info{name=\"%s\",serial=\"%12llx\"} 1\n", [](Inverter<> *iv)-> uint64_t {return iv->config->serial.u64;} }, { "info", "gauge", " {name=\"%s\",serial=\"%12llx\"} 1\n", [](Inverter<> *iv)-> uint64_t {return iv->config->serial.u64;} },
{ "is_enabled", "is_enabled {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->config->enabled;} }, { "is_enabled", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->config->enabled;} },
{ "is_available", "is_available {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} }, { "is_available", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} },
{ "is_producing", "is_producing {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->isProducing();} }, { "is_producing", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->isProducing();} },
{ "power_limit_read", "power_limit_read {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return (int64_t)ah::round3(iv->actPowerLimit);} }, { "power_limit_read", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (int64_t)ah::round3(iv->actPowerLimit);} },
{ "power_limit_ack", "power_limit_ack {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} }, { "power_limit_ack", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} },
{ "max_power", "max_power {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->getMaxPower();} }, { "max_power", "gauge", metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->getMaxPower();} },
{ "radio_rx_success", "radio_rx_success {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxSuccess;} }, { "radio_rx_success", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxSuccess;} },
{ "radio_rx_fail", "radio_rx_fail {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFail;} }, { "radio_rx_fail", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFail;} },
{ "radio_rx_fail_answer", "radio_rx_fail_answer {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFailNoAnser;} }, { "radio_rx_fail_answer", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.rxFailNoAnser;} },
{ "radio_frame_cnt", "radio_frame_cnt {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.frmCnt;} }, { "radio_frame_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.frmCnt;} },
{ "radio_tx_cnt", "radio_tx_cnt {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.txCnt;} }, { "radio_tx_cnt", "counter" ,metricConstInverterFormat, [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.txCnt;} },
{ "radio_retransmits", "radio_retransmits {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.retransmits;} } { "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;} }
}; };
int metricsInverterId; int metricsInverterId;
uint8_t metricsFieldId; uint8_t metricsFieldId;
@ -718,21 +727,21 @@ class Web {
// So several "Info:" blocks are used to keep the transmission going // So several "Info:" blocks are used to keep the transmission going
switch (metricsStep) { switch (metricsStep) {
case metricsStateStart: // System Info : fit to one packet case metricsStateStart: // System Info : fit to one packet
snprintf(type,sizeof(type),"# TYPE %sinfo gauge\n",metricPrefix); snprintf(type,sizeof(type),"# TYPE %sinfo gauge\n",metricConstPrefix);
snprintf(topic,sizeof(topic),"%sinfo{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",metricPrefix, snprintf(topic,sizeof(topic),"%sinfo{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",metricConstPrefix,
mApp->getVersion(), mConfig->sys.deviceName); mApp->getVersion(), mConfig->sys.deviceName);
metrics = String(type) + String(topic); metrics = String(type) + String(topic);
snprintf(type,sizeof(type),"# TYPE %sfreeheap gauge\n",metricPrefix); snprintf(type,sizeof(type),"# TYPE %sfreeheap gauge\n",metricConstPrefix);
snprintf(topic,sizeof(topic),"%sfreeheap{devicename=\"%s\"} %u\n",metricPrefix,mConfig->sys.deviceName,ESP.getFreeHeap()); snprintf(topic,sizeof(topic),"%sfreeheap{devicename=\"%s\"} %u\n",metricConstPrefix,mConfig->sys.deviceName,ESP.getFreeHeap());
metrics += String(type) + String(topic); metrics += String(type) + String(topic);
snprintf(type,sizeof(type),"# TYPE %suptime counter\n",metricPrefix); snprintf(type,sizeof(type),"# TYPE %suptime counter\n",metricConstPrefix);
snprintf(topic,sizeof(topic),"%suptime{devicename=\"%s\"} %u\n",metricPrefix, mConfig->sys.deviceName, mApp->getUptime()); snprintf(topic,sizeof(topic),"%suptime{devicename=\"%s\"} %u\n",metricConstPrefix, mConfig->sys.deviceName, mApp->getUptime());
metrics += String(type) + String(topic); metrics += String(type) + String(topic);
snprintf(type,sizeof(type),"# TYPE %swifi_rssi_db gauge\n",metricPrefix); snprintf(type,sizeof(type),"# TYPE %swifi_rssi_db gauge\n",metricConstPrefix);
snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricPrefix, mConfig->sys.deviceName, WiFi.RSSI()); snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricConstPrefix, mConfig->sys.deviceName, WiFi.RSSI());
metrics += String(type) + String(topic); metrics += String(type) + String(topic);
len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
@ -754,8 +763,15 @@ class Web {
case metricsStateInverterFrameCnt: case metricsStateInverterFrameCnt:
case metricsStateInverterTxCnt: case metricsStateInverterTxCnt:
case metricsStateInverterRetransmits: case metricsStateInverterRetransmits:
metrics = "# TYPE ahoy_solar_inverter_" + String(inverterMetrics[metricsStep].type) + " gauge\n"; case metricsStateInverterIvRxCnt:
metrics += inverterMetric(topic, sizeof(topic),(String("ahoy_solar_inverter_") + inverterMetrics[metricsStep].format).c_str(), inverterMetrics[metricsStep].valueFunc); 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((char *)buffer,maxLen,"%s",metrics.c_str()); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str());
// ugly hack to increment the enum // ugly hack to increment the enum
metricsStep = static_cast<MetricStep_t>( static_cast<int>(metricsStep) + 1); metricsStep = static_cast<MetricStep_t>( static_cast<int>(metricsStep) + 1);
@ -796,7 +812,7 @@ class Web {
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec));
// Declare metric only once // Declare metric only once
if (channel != 0 && !metricDeclared) { if (channel != 0 && !metricDeclared) {
snprintf(type, sizeof(type), "# TYPE %s%s%s %s\n",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); snprintf(type, sizeof(type), "# TYPE %s%s%s %s\n",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str());
metrics += type; metrics += type;
metricDeclared = true; metricDeclared = true;
} }
@ -810,11 +826,11 @@ class Web {
strncpy(total,"_total",sizeof(total)); strncpy(total,"_total",sizeof(total));
} }
if (!metricTotalDeclard) { if (!metricTotalDeclard) {
snprintf(type, sizeof(type), "# TYPE %s%s%s%s %s\n",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); 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; metrics += type;
metricTotalDeclard = true; metricTotalDeclard = true;
} }
snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricConstPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name);
} else { } else {
// Report (non zero) channel value // Report (non zero) channel value
// Use a fallback channel name (ch0, ch1, ...)if non is given by user // Use a fallback channel name (ch0, ch1, ...)if non is given by user
@ -824,7 +840,7 @@ class Web {
} else { } else {
snprintf(chName,sizeof(chName),"ch%1d",channel); snprintf(chName,sizeof(chName),"ch%1d",channel);
} }
snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); 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)); snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec));
metrics += topic; metrics += topic;
@ -833,12 +849,14 @@ class Web {
} }
} }
if (metrics.length() < 1) { if (metrics.length() < 1) {
metrics = "# Info: Field #"+String(metricsFieldId)+" not available for inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; metrics = "# Info: Field #"+String(metricsFieldId)+" (" + fields[metricsFieldId] +
") not available for inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n";
metricsFieldId++; // Process next field Id metricsFieldId++; // Process next field Id
metricsStep = metricStateRealtimeFieldId; metricsStep = metricStateRealtimeFieldId;
} }
} else { } else {
metrics = "# Info: No data for field #"+String(metricsFieldId)+" of inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n"; metrics = "# Info: No data for field #"+String(metricsFieldId)+ " (" + fields[metricsFieldId] +
") of inverter #"+String(metricsInverterId)+". Skipping remaining inverters\n";
metricsFieldId++; // Process next field Id metricsFieldId++; // Process next field Id
metricsStep = metricStateRealtimeFieldId; metricsStep = metricStateRealtimeFieldId;
} }
@ -854,7 +872,7 @@ class Web {
case metricsStateAlarmData: // Alarm Info loop : fit to one packet case metricsStateAlarmData: // Alarm Info loop : fit to one packet
// Perform grouping on metrics according to Prometheus exposition format specification // Perform grouping on metrics according to Prometheus exposition format specification
snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricPrefix,fields[FLD_LAST_ALARM_CODE]); snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricConstPrefix,fields[FLD_LAST_ALARM_CODE]);
metrics = type; metrics = type;
for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) {
@ -866,7 +884,7 @@ class Web {
alarmChannelId = 0; alarmChannelId = 0;
if (alarmChannelId < rec->length) { if (alarmChannelId < rec->length) {
std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec));
snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\"}",metricPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); 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)); snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec));
metrics += topic; metrics += topic;
metrics += val; metrics += val;

Loading…
Cancel
Save