From 1b73e493a9608599b7fa40dda6fec1a6b2294cb8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Sun, 31 Dec 2023 13:04:13 +0100 Subject: [PATCH 1/8] 0.8.38 * fix Grid-Profile JSON #1304 --- src/CHANGES.md | 3 +++ src/defines.h | 2 +- src/web/html/grid_info.json | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 32efd79b..f6e7445e 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,8 @@ # Development Changes +## 0.8.38 - 2023-12-31 +* fix Grid-Profile JSON #1304 + ## 0.8.37 - 2023-12-30 * added grid profiles * format version of grid profile diff --git a/src/defines.h b/src/defines.h index 787e9ccf..05cdc5e0 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 37 +#define VERSION_PATCH 38 //------------------------------------- typedef struct { diff --git a/src/web/html/grid_info.json b/src/web/html/grid_info.json index 5ac2e0b2..f14babe3 100644 --- a/src/web/html/grid_info.json +++ b/src/web/html/grid_info.json @@ -17,7 +17,7 @@ {"0x2600": "BE_C10_26"}, {"0x2900": "NL_NEN-EN50549-1_2019"}, {"0x2a00": "PL_PN-EN 50549-1:2019"}, - {"0x3700": "CH_NA EEA-NE7–CH2020"} + {"0x3700": "CH_NA EEA-NE7–CH2020"}, {"0xe100": "LN_50Hz"} ], "grp_codes": [ From 2571e3c9f9b0abcb9033c183f2c3eee93d3a879a Mon Sep 17 00:00:00 2001 From: rejoe2 Date: Sun, 31 Dec 2023 14:24:23 +0100 Subject: [PATCH 2/8] MI - add grid profile request Note: this doesn't deliver entire grid profile infos... --- src/hm/Communication.h | 47 ++++++++++++++++++++++++++++-------------- src/hm/hmInverter.h | 2 ++ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 9f96533d..4aa9ddcc 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -353,6 +353,12 @@ class Communication : public CommQueue<> { miDataDecode(p, q); } else if (p->packet[0] == (0x0f + ALL_FRAMES)) miHwDecode(p, q); + + else if (p->packet[0] == ( 0x10 + ALL_FRAMES)) { + // MI response from get Grid Profile information request + miGPFDecode(p, q); + } + else if ((p->packet[0] == 0x88) || (p->packet[0] == 0x92)) { record_t<> *rec = q->iv->getRecordStruct(RealTimeRunData_Debug); // choose the record structure rec->ts = q->ts; @@ -650,22 +656,31 @@ class Communication : public CommQueue<> { (mCbPayload)(InverterDevInform_Simple, q->iv); q->iv->miMultiParts++; } - //if(q->iv->miMultiParts > 5) - //closeRequest(q->iv, true); - //else - //if(q->iv->miMultiParts < 6) - // mState = States::WAIT; - - /*if (mPayload[iv->id].multi_parts > 5) { - iv->setQueuedCmdFinished(); - mPayload[iv->id].complete = true; - mPayload[iv->id].rxTmo = true; - mPayload[iv->id].requested= false; - iv->radioStatistics.rxSuccess++; - } - if (mHighPrioIv == NULL) - mHighPrioIv = iv; - */ + } + + inline void miGPFDecode(packet_t *p, const queue_s *q) { + record_t<> *rec = q->iv->getRecordStruct(InverterDevInform_Simple); // choose the record structure + rec->ts = q->ts; + + q->iv->setValue(2, rec, (uint32_t) (((p->packet[10] << 8) | p->packet[11]))); //FLD_GRID_PROFILE_CODE + q->iv->setValue(3, rec, (uint32_t) (((p->packet[12] << 8) | p->packet[13]))); //FLD_GRID_PROFILE_VERSION + +/* according to xlsx (different start byte -1!) + Polling Grid-connected Protection Parameter File Command - Receipt + byte[10] ST1 indicates the status of the grid-connected protection file. ST1=1 indicates the default grid-connected protection file, ST=2 indicates that the grid-connected protection file is configured and normal, ST=3 indicates that the grid-connected protection file cannot be recognized, ST=4 indicates that the grid-connected protection file is damaged + byte[11] byte[12] CountryStd variable indicates the national standard code of the grid-connected protection file + byte[13] byte[14] Version indicates the version of the grid-connected protection file + byte[15] byte[16] +*/ + /*if(mSerialDebug) { + DPRINT(DBG_INFO,F("ST1 ")); + DBGPRINTLN(String(p->packet[9])); + DPRINT(DBG_INFO,F("CountryStd ")); + DBGPRINTLN(String((p->packet[10] << 8) + p->packet[11])); + DPRINT(DBG_INFO,F("Version ")); + DBGPRINTLN(String((p->packet[12] << 8) + p->packet[13])); + }*/ + q->iv->miMultiParts = 7; // indicate we are ready } inline void miDataDecode(packet_t *p, const queue_s *q) { diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 776b9c6f..757562bb 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -215,6 +215,8 @@ class Inverter { record_t<> *rec = getRecordStruct(InverterDevInform_Simple); if (getChannelFieldValue(CH0, FLD_PART_NUM, rec) == 0) cb(0x0f, false); // hard- and firmware version for missing HW part nr, delivered by frame 1 + else if((getChannelFieldValue(CH0, FLD_GRID_PROFILE_CODE, rec) == 0) && generalConfig->readGrid) // read grid profile + cb(0x10, false); // legacy GPF command else cb(((type == INV_TYPE_4CH) ? MI_REQ_4CH : MI_REQ_CH1), false); } From 29d2bdefab2021847678f89493bc7ca450c4392d Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 1 Jan 2024 15:20:14 +0100 Subject: [PATCH 3/8] PROMETHEUS_EP: Add NRF-radio statistics and power-limit metrics --- doc/prometheus_ep_description.md | 21 ++-- src/web/web.h | 165 ++++++++++++++++--------------- 2 files changed, 98 insertions(+), 88 deletions(-) diff --git a/doc/prometheus_ep_description.md b/doc/prometheus_ep_description.md index 711299dd..651cd937 100644 --- a/doc/prometheus_ep_description.md +++ b/doc/prometheus_ep_description.md @@ -1,10 +1,10 @@ # Prometheus Endpoint Metrics available for AhoyDTU device, inverters and channels. -Prometheus metrics provided at `/metrics`. +Prometheus metrics provided at `/metrics`. ## Labels -| Label name | Description | +| Label name | Description | |:-------------|:--------------------------------------| | version | current installed version of AhoyDTU | | image | currently not used | @@ -19,11 +19,21 @@ Prometheus metrics provided at `/metrics`. |----------------------------------------------|---------|----------------------------------------------------------|--------------| | `ahoy_solar_info` | Gauge | Information about the AhoyDTU device | version, image, devicename | | `ahoy_solar_uptime` | Counter | Seconds since boot of the AhoyDTU device | devicename | -| `ahoy_solar_rssi_db` | Gauge | Quality of the Wifi STA connection | devicename | +| `ahoy_solar_freeheap` | Gauge | free heap memory of the AhoyDTU device | devicename | +| `ahoy_solar_wifi_rssi_db` | Gauge | Quality of the Wifi STA connection | devicename | | `ahoy_solar_inverter_info` | Gauge | Information about the configured inverter(s) | name, serial | | `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_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_ack` | Gauge | Power Limit acknowledged by 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_fail` | Counter | NRF24 statistic of inverter | inverter | +| `ahoy_solar_inverter_radio_rx_fail_answer` | 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_retransmits` | Counter | NRF24 statistic of inverter | 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_P_AC_watt` | Gauge | AC power of inverter [W] | inverter | @@ -46,9 +56,4 @@ Prometheus metrics provided at `/metrics`. | `ahoy_solar_YieldDay_wattHours` | Counter | Energy converted to AC per day [Wh] | inverter, channel | | `ahoy_solar_YieldTotal_kilowattHours` | Counter | Energy converted to AC since reset [kWh] | inverter, channel | | `ahoy_solar_Irradiation_ratio` | Gauge | ratio DC Power over set maximum power per channel [%] | inverter, channel | -| `ahoy_solar_radio_rx_success` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_rx_fail` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_rx_fail_answer` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_frame_cnt` | Gauge | NRF24 statistic | | -| `ahoy_solar_radio_tx_cnt` | Gauge | NRF24 statistic | | diff --git a/src/web/web.h b/src/web/web.h index 1e87547b..990f1983 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -623,17 +623,45 @@ class Web { #ifdef ENABLE_PROMETHEUS_EP // Note // Prometheus exposition format is defined here: https://github.com/prometheus/docs/blob/main/content/docs/instrumenting/exposition_formats.md - // TODO: Check packetsize for MAX_NUM_INVERTERS. Successfully Tested with 4 Inverters (each with 4 channels) - enum { - metricsStateStart, - metricsStateInverter1, metricsStateInverter2, metricsStateInverter3, metricsStateInverter4, - metricStateRealtimeFieldId, metricStateRealtimeInverterId, + // 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 * metricPrefix = "ahoy_solar_"; + 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, + metricStateRealtimeFieldId=metricsStateInverterRetransmits+1, // ensure that this state follows the last per_inverter state + metricStateRealtimeInverterId, metricsStateAlarmData, + metricsStateStart, metricsStateEnd - } metricsStep; + } MetricStep_t; + MetricStep_t metricsStep; + typedef struct { + const char *type; + const char *format; + const std::function *iv)> valueFunc; + } InverterMetric_t; + InverterMetric_t inverterMetrics[13] = { + { "info", "info{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_available", "is_available {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->isAvailable();} }, + { "is_producing", "is_producing {inverter=\"%s\"} %d\n", [](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_ack", "power_limit_ack {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return (iv->powerLimitAck)?1:0;} }, + { "max_power", "max_power {inverter=\"%s\"} %d\n", [](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_fail", "radio_rx_fail {inverter=\"%s\"} %d\n", [](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_frame_cnt", "radio_frame_cnt {inverter=\"%s\"} %d\n", [](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_retransmits", "radio_retransmits {inverter=\"%s\"} %d\n", [](Inverter<> *iv)-> uint64_t {return iv->radioStatistics.retransmits;} } + }; int metricsInverterId; uint8_t metricsFieldId; - bool metricDeclared; + bool metricDeclared, metricTotalDeclard; void showMetrics(AsyncWebServerRequest *request) { DPRINTLN(DBG_VERBOSE, F("web::showMetrics")); @@ -654,79 +682,58 @@ class Web { // 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 & NRF Statistics : fit to one packet - snprintf(type,sizeof(type),"# TYPE ahoy_solar_info gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_info{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n", + case metricsStateStart: // System Info : fit to one packet + snprintf(type,sizeof(type),"# TYPE %sinfo gauge\n",metricPrefix); + snprintf(topic,sizeof(topic),"%sinfo{version=\"%s\",image=\"\",devicename=\"%s\"} 1\n",metricPrefix, mApp->getVersion(), mConfig->sys.deviceName); metrics = String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE ahoy_solar_freeheap gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_freeheap{devicename=\"%s\"} %u\n",mConfig->sys.deviceName,ESP.getFreeHeap()); + snprintf(type,sizeof(type),"# TYPE %sfreeheap gauge\n",metricPrefix); + snprintf(topic,sizeof(topic),"%sfreeheap{devicename=\"%s\"} %u\n",metricPrefix,mConfig->sys.deviceName,ESP.getFreeHeap()); metrics += String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE ahoy_solar_uptime counter\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_uptime{devicename=\"%s\"} %u\n", mConfig->sys.deviceName, mApp->getUptime()); + snprintf(type,sizeof(type),"# TYPE %suptime counter\n",metricPrefix); + snprintf(topic,sizeof(topic),"%suptime{devicename=\"%s\"} %u\n",metricPrefix, mConfig->sys.deviceName, mApp->getUptime()); metrics += String(type) + String(topic); - snprintf(type,sizeof(type),"# TYPE ahoy_solar_wifi_rssi_db gauge\n"); - snprintf(topic,sizeof(topic),"ahoy_solar_wifi_rssi_db{devicename=\"%s\"} %d\n", mConfig->sys.deviceName, WiFi.RSSI()); + snprintf(type,sizeof(type),"# TYPE %swifi_rssi_db gauge\n",metricPrefix); + snprintf(topic,sizeof(topic),"%swifi_rssi_db{devicename=\"%s\"} %d\n",metricPrefix, mConfig->sys.deviceName, WiFi.RSSI()); metrics += String(type) + String(topic); - // NRF Statistics - // @TODO 2023-10-01: the statistic data is now available per inverter - /*stat = mApp->getNrfStatistics(); - metrics += radioStatistic(F("rx_success"), stat->rxSuccess); - metrics += radioStatistic(F("rx_fail"), stat->rxFail); - metrics += radioStatistic(F("rx_fail_answer"), stat->rxFailNoAnser); - metrics += radioStatistic(F("frame_cnt"), stat->frmCnt); - metrics += radioStatistic(F("tx_cnt"), stat->txCnt); - metrics += radioStatistic(F("retrans_cnt"), stat->retransmits);*/ - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); // Next is Inverter information - metricsInverterId = 0; - metricsStep = metricsStateInverter1; + metricsStep = metricsStateInverterInfo; break; - case metricsStateInverter1: // Information about all inverters configured : fit to one packet - metrics = "# TYPE ahoy_solar_inverter_info gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_info{name=\"%s\",serial=\"%12llx\"} 1\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->serial.u64;}); + // 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: + metrics = "# TYPE ahoy_solar_inverter_" + String(inverterMetrics[metricsStep].type) + " gauge\n"; + metrics += inverterMetric(topic, sizeof(topic),(String("ahoy_solar_inverter_") + inverterMetrics[metricsStep].format).c_str(), inverterMetrics[metricsStep].valueFunc); len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - metricsStep = metricsStateInverter2; - break; - - case metricsStateInverter2: // Information about all inverters configured : fit to one packet - metrics += "# TYPE ahoy_solar_inverter_is_enabled gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_enabled {inverter=\"%s\"} %d\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->config->enabled;}); - - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - metricsStep = metricsStateInverter3; - break; - - case metricsStateInverter3: // Information about all inverters configured : fit to one packet - metrics += "# TYPE ahoy_solar_inverter_is_available gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_available {inverter=\"%s\"} %d\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isAvailable();}); - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - metricsStep = metricsStateInverter4; - break; - - case metricsStateInverter4: // Information about all inverters configured : fit to one packet - metrics += "# TYPE ahoy_solar_inverter_is_producing gauge\n"; - metrics += inverterMetric(topic, sizeof(topic),"ahoy_solar_inverter_is_producing {inverter=\"%s\"} %d\n", - [](Inverter<> *iv,IApp *mApp)-> uint64_t {return iv->isProducing();}); - len = snprintf((char *)buffer,maxLen,"%s",metrics.c_str()); - // Start Realtime Field loop + // ugly hack to increment the enum + metricsStep = static_cast( static_cast(metricsStep) + 1); + // Prepare Realtime Field loop, which may be startet next metricsFieldId = FLD_UDC; - metricsStep = metricStateRealtimeFieldId; 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; @@ -741,7 +748,6 @@ class Web { metrics = ""; if (metricsInverterId < mSys->getNumInverters()) { // process all channels of this inverter - iv = mSys->getInverterByPos(metricsInverterId); if (NULL != iv) { rec = iv->getRecordStruct(RealTimeRunData_Debug); @@ -755,22 +761,27 @@ class Web { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(metricsChannelId, rec)); // Declare metric only once if (channel != 0 && !metricDeclared) { - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s %s\n", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), promType.c_str()); + snprintf(type, sizeof(type), "# TYPE %s%s%s %s\n",metricPrefix, 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]; total[0] = 0; if (metricDeclared) { - // A declaration and value for channels has been delivered. So declare and deliver a _total metric + // A declaration and value for channels have been delivered. So declare and deliver a _total metric strncpy(total,"_total",sizeof(total)); } - snprintf(type, sizeof(type), "# TYPE ahoy_solar_%s%s%s %s\n", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total, promType.c_str()); - metrics += type; - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s%s{inverter=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), total,iv->config->name); + 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()); + metrics += type; + metricTotalDeclard = true; + } + snprintf(topic, sizeof(topic), "%s%s%s%s{inverter=\"%s\"}",metricPrefix, 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) { @@ -778,7 +789,7 @@ class Web { } else { snprintf(chName,sizeof(chName),"ch%1d",channel); } - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\",channel=\"%s\"}", iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\",channel=\"%s\"}",metricPrefix, iv->getFieldName(metricsChannelId, rec), promUnit.c_str(), iv->config->name,chName); } snprintf(val, sizeof(val), " %.3f\n", iv->getValue(metricsChannelId, rec)); metrics += topic; @@ -808,7 +819,7 @@ class Web { case metricsStateAlarmData: // Alarm Info loop : fit to one packet // Perform grouping on metrics according to Prometheus exposition format specification - snprintf(type, sizeof(type),"# TYPE ahoy_solar_%s gauge\n",fields[FLD_LAST_ALARM_CODE]); + snprintf(type, sizeof(type),"# TYPE %s%s gauge\n",metricPrefix,fields[FLD_LAST_ALARM_CODE]); metrics = type; for (metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { @@ -820,7 +831,7 @@ class Web { alarmChannelId = 0; if (alarmChannelId < rec->length) { std::tie(promUnit, promType) = convertToPromUnits(iv->getUnit(alarmChannelId, rec)); - snprintf(topic, sizeof(topic), "ahoy_solar_%s%s{inverter=\"%s\"}", iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); + snprintf(topic, sizeof(topic), "%s%s%s{inverter=\"%s\"}",metricPrefix, iv->getFieldName(alarmChannelId, rec), promUnit.c_str(), iv->config->name); snprintf(val, sizeof(val), " %.3f\n", iv->getValue(alarmChannelId, rec)); metrics += topic; metrics += val; @@ -831,11 +842,13 @@ class Web { metricsStep = metricsStateEnd; break; - case metricsStateEnd: default: // end of transmission + DBGPRINT("E: Prometheus: Bad metricsStep="); + DBGPRINTLN(String(metricsStep)); + case metricsStateEnd: len = 0; break; - } + } // switch return len; }); request->send(response); @@ -843,27 +856,19 @@ class Web { // Traverse all inverters and collect the metric via valueFunc - String inverterMetric(char *buffer, size_t len, const char *format, std::function *iv, IApp *mApp)> valueFunc) { + String inverterMetric(char *buffer, size_t len, const char *format, std::function *iv)> valueFunc) { Inverter<> *iv; String metric = ""; for (int metricsInverterId = 0; metricsInverterId < mSys->getNumInverters();metricsInverterId++) { iv = mSys->getInverterByPos(metricsInverterId); if (NULL != iv) { - snprintf(buffer,len,format,iv->config->name, valueFunc(iv,mApp)); + snprintf(buffer,len,format,iv->config->name, valueFunc(iv)); metric += String(buffer); } } return metric; } - String radioStatistic(String statistic, uint32_t value) { - char type[60], topic[80], val[25]; - snprintf(type, sizeof(type), "# TYPE ahoy_solar_radio_%s counter",statistic.c_str()); - snprintf(topic, sizeof(topic), "ahoy_solar_radio_%s",statistic.c_str()); - snprintf(val, sizeof(val), "%d", value); - return ( String(type) + "\n" + String(topic) + " " + String(val) + "\n"); - } - std::pair convertToPromUnits(String shortUnit) { if(shortUnit == "A") return {"_ampere", "gauge"}; if(shortUnit == "V") return {"_volt", "gauge"}; From 2cda39c9f9511eb078a14aad8c096d59687e42d4 Mon Sep 17 00:00:00 2001 From: lumapu Date: Mon, 1 Jan 2024 23:02:14 +0100 Subject: [PATCH 4/8] 0.8.39 * fix MqTT dis_night_comm in the morning #1309 * seperated offset for sunrise and sunset #1308 --- src/CHANGES.md | 4 ++++ src/app.cpp | 21 ++++++++++++--------- src/config/settings.h | 19 +++++++++++++------ src/defines.h | 4 ++-- src/publisher/pubMqtt.h | 10 +++++----- src/web/RestApi.h | 14 ++++++++------ src/web/html/index.html | 38 +++++++++++++++++++------------------- src/web/html/setup.html | 16 +++++++++++----- src/web/web.h | 8 +++++--- 9 files changed, 79 insertions(+), 55 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index f6e7445e..66056890 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,9 @@ # Development Changes +## 0.8.39 - 2024-01-01 +* fix MqTT dis_night_comm in the morning #1309 +* seperated offset for sunrise and sunset #1308 + ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 diff --git a/src/app.cpp b/src/app.cpp index 0bba14da..8e70778a 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -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 //----------------------------------------------------------------------------- @@ -226,15 +226,18 @@ void app::tickCalcSunrise(void) { if (mSunrise == 0) // on boot/reboot calc sun values for current time ah::calculateSunriseSunset(mTimestamp, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); - if (mTimestamp > (mSunset + mConfig->sun.offsetSec)) // current time is past communication stop, calc sun values for next day + if (mTimestamp > (mSunset + mConfig->sun.offsetSecEvening)) // current time is past communication stop, calc sun values for next day ah::calculateSunriseSunset(mTimestamp + 86400, mCalculatedTimezoneOffset, mConfig->sun.lat, mConfig->sun.lon, &mSunrise, &mSunset); tickIVCommunication(); - uint32_t nxtTrig = mSunset + mConfig->sun.offsetSec + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop + uint32_t nxtTrig = mSunset + mConfig->sun.offsetSecEvening + 60; // set next trigger to communication stop, +60 for safety that it is certain past communication stop onceAt(std::bind(&app::tickCalcSunrise, this), nxtTrig, "Sunri"); - if (mMqttEnabled) + if (mMqttEnabled) { tickSun(); + nxtTrig = mSunrise - mConfig->sun.offsetSecMorning + 1; // one second safety to trigger correctly + onceAt(std::bind(&app::tickSun, this), nxtTrig, "mqSr"); // trigger on sunrise to update 'dis_night_comm' + } } //----------------------------------------------------------------------------- @@ -251,14 +254,14 @@ void app::tickIVCommunication(void) { iv->commEnabled = !iv->config->disNightCom; // if sun.disNightCom is false, communication is always on if (!iv->commEnabled) { // inverter communication only during the day - if (mTimestamp < (mSunrise - mConfig->sun.offsetSec)) { // current time is before communication start, set next trigger to communication start - nxtTrig = mSunrise - mConfig->sun.offsetSec; + if (mTimestamp < (mSunrise - mConfig->sun.offsetSecMorning)) { // current time is before communication start, set next trigger to communication start + nxtTrig = mSunrise - mConfig->sun.offsetSecMorning; } else { - if (mTimestamp >= (mSunset + mConfig->sun.offsetSec)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise + if (mTimestamp >= (mSunset + mConfig->sun.offsetSecEvening)) { // current time is past communication stop, nothing to do. Next update will be done at midnight by tickCalcSunrise nxtTrig = 0; } else { // current time lies within communication start/stop time, set next trigger to communication stop iv->commEnabled = true; - nxtTrig = mSunset + mConfig->sun.offsetSec; + nxtTrig = mSunset + mConfig->sun.offsetSecEvening; } } if (nxtTrig != 0) @@ -279,7 +282,7 @@ void app::tickIVCommunication(void) { //----------------------------------------------------------------------------- void app::tickSun(void) { // only used and enabled by MQTT (see setup()) - if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSec)) + if (!mMqtt.tickerSun(mSunrise, mSunset, mConfig->sun.offsetSecMorning, mConfig->sun.offsetSecEvening)) once(std::bind(&app::tickSun, this), 1, "mqSun"); // MQTT not connected, retry } diff --git a/src/config/settings.h b/src/config/settings.h index fe2ad1b0..c493eb1a 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -30,7 +30,7 @@ * https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html#flash-layout * */ -#define CONFIG_VERSION 7 +#define CONFIG_VERSION 8 #define PROT_MASK_INDEX 0x0001 @@ -106,7 +106,8 @@ typedef struct { typedef struct { float lat; float lon; - uint16_t offsetSec; + uint16_t offsetSecMorning; + uint16_t offsetSecEvening; } cfgSun_t; typedef struct { @@ -420,7 +421,8 @@ class settings { mCfg.sun.lat = 0.0; mCfg.sun.lon = 0.0; - mCfg.sun.offsetSec = 0; + mCfg.sun.offsetSecMorning = 0; + mCfg.sun.offsetSecEvening = 0; mCfg.serial.showIv = false; mCfg.serial.debug = false; @@ -496,6 +498,9 @@ class settings { if(mCfg.configVersion < 7) { mCfg.led.luminance = 255; } + if(mCfg.configVersion < 8) { + mCfg.sun.offsetSecEvening = mCfg.sun.offsetSecMorning; + } } } @@ -625,11 +630,13 @@ class settings { if(set) { obj[F("lat")] = mCfg.sun.lat; obj[F("lon")] = mCfg.sun.lon; - obj[F("offs")] = mCfg.sun.offsetSec; + obj[F("offs")] = mCfg.sun.offsetSecMorning; + obj[F("offsEve")] = mCfg.sun.offsetSecEvening; } else { getVal(obj, F("lat"), &mCfg.sun.lat); getVal(obj, F("lon"), &mCfg.sun.lon); - getVal(obj, F("offs"), &mCfg.sun.offsetSec); + getVal(obj, F("offs"), &mCfg.sun.offsetSecMorning); + getVal(obj, F("offsEve"), &mCfg.sun.offsetSecEvening); } } diff --git a/src/defines.h b/src/defines.h index 05cdc5e0..ad321921 100644 --- a/src/defines.h +++ b/src/defines.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 //----------------------------------------------------------------------------- @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 38 +#define VERSION_PATCH 39 //------------------------------------- typedef struct { diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index e062439c..9834f29a 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.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 //----------------------------------------------------------------------------- @@ -134,14 +134,14 @@ class PubMqtt { #endif } - bool tickerSun(uint32_t sunrise, uint32_t sunset, uint32_t offs) { + bool tickerSun(uint32_t sunrise, uint32_t sunset, uint16_t offsM, uint16_t offsE) { if (!mClient.connected()) return false; publish(subtopics[MQTT_SUNRISE], String(sunrise).c_str(), true); publish(subtopics[MQTT_SUNSET], String(sunset).c_str(), true); - publish(subtopics[MQTT_COMM_START], String(sunrise - offs).c_str(), true); - publish(subtopics[MQTT_COMM_STOP], String(sunset + offs).c_str(), true); + publish(subtopics[MQTT_COMM_START], String(sunrise - offsM).c_str(), true); + publish(subtopics[MQTT_COMM_STOP], String(sunset + offsE).c_str(), true); Inverter<> *iv; for(uint8_t i = 0; i < MAX_NUM_INVERTERS; i++) { @@ -155,7 +155,7 @@ class PubMqtt { snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "comm_disabled"); - publish(mSubTopic, (((*mUtcTimestamp > (sunset + offs)) || (*mUtcTimestamp < (sunrise - offs))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); + publish(mSubTopic, (((*mUtcTimestamp > (sunset + offsE)) || (*mUtcTimestamp < (sunrise - offsM))) ? dict[STR_TRUE] : dict[STR_FALSE]), true); return true; } diff --git a/src/web/RestApi.h b/src/web/RestApi.h index a74f4f14..c920a8e3 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://ahoydtu.de +// 2024 Ahoy, https://ahoydtu.de // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- @@ -600,7 +600,8 @@ class RestApi { void getSun(JsonObject obj) { obj[F("lat")] = mConfig->sun.lat ? String(mConfig->sun.lat, 5) : ""; obj[F("lon")] = mConfig->sun.lat ? String(mConfig->sun.lon, 5) : ""; - obj[F("offs")] = mConfig->sun.offsetSec; + obj[F("offsSr")] = mConfig->sun.offsetSecMorning; + obj[F("offsSs")] = mConfig->sun.offsetSecEvening; } void getPinout(JsonObject obj) { @@ -685,10 +686,11 @@ class RestApi { void getIndex(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); - obj[F("ts_now")] = mApp->getTimestamp(); - obj[F("ts_sunrise")] = mApp->getSunrise(); - obj[F("ts_sunset")] = mApp->getSunset(); - obj[F("ts_offset")] = mConfig->sun.offsetSec; + obj[F("ts_now")] = mApp->getTimestamp(); + obj[F("ts_sunrise")] = mApp->getSunrise(); + obj[F("ts_sunset")] = mApp->getSunset(); + obj[F("ts_offsSr")] = mConfig->sun.offsetSecMorning; + obj[F("ts_offsSs")] = mConfig->sun.offsetSecEvening; JsonArray inv = obj.createNestedArray(F("inverter")); Inverter<> *iv; diff --git a/src/web/html/index.html b/src/web/html/index.html index baa70742..3ac72e89 100644 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -45,12 +45,12 @@ function apiCb(obj) { var e = document.getElementById("apiResult"); - if(obj["success"]) { + if(obj.success) { e.innerHTML = " command executed"; getAjax("/api/index", parse); } else - e.innerHTML = " Error: " + obj["error"]; + e.innerHTML = " Error: " + obj.error; } function setTime() { @@ -68,9 +68,9 @@ } function parseSys(obj) { - ts = obj["ts_now"]; - var date = new Date(obj["ts_now"] * 1000); - var up = obj["generic"]["ts_uptime"]; + ts = obj.ts_now; + var date = new Date(obj.ts_now * 1000); + var up = obj.generic["ts_uptime"]; var days = parseInt(up / 86400) % 365; var hrs = parseInt(up / 3600) % 24; var min = parseInt(up / 60) % 60; @@ -83,8 +83,8 @@ + ("0"+min).substr(-2) + ":" + ("0"+sec).substr(-2); var dSpan = document.getElementById("date"); - if(0 != obj["ts_now"]) { - if(obj["ts_now"] < 1680000000) + if(0 != obj.ts_now) { + if(obj.ts_now < 1680000000) setTime(); else dSpan.innerHTML = toIsoDateStr(date); @@ -98,18 +98,18 @@ e.addEventListener("click", setTime); } - if(obj["disNightComm"]) { - if(((obj["ts_sunrise"] - obj["ts_offset"]) < obj["ts_now"]) - && ((obj["ts_sunset"] + obj["ts_offset"]) > obj["ts_now"])) { - commInfo = "Polling inverter(s), will pause at sunset " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + if(obj.disNightComm) { + if(((obj.ts_sunrise - obj.ts_offsSr) < 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')); } else { commInfo = "Night time, inverter polling disabled, "; - if(obj["ts_now"] > (obj["ts_sunrise"] - obj["ts_offset"])) { - commInfo += "paused at " + (new Date((obj["ts_sunset"] + obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + 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')); } else { - commInfo += "will start polling at " + (new Date((obj["ts_sunrise"] - obj["ts_offset"]) * 1000).toLocaleString('de-DE')); + commInfo += "will start polling at " + (new Date((obj.ts_sunrise - obj.ts_offsSr) * 1000).toLocaleString('de-DE')); } } } @@ -190,11 +190,11 @@ function parse(obj) { if(null != obj) { if(exeOnce) - parseNav(obj["generic"]); - parseGeneric(obj["generic"]); + parseNav(obj.generic); + parseGeneric(obj.generic); parseSys(obj); - parseIv(obj["inverter"], obj.ts_now); - parseWarn(obj["warnings"]); + parseIv(obj.inverter, obj.ts_now); + parseWarn(obj.warnings); if(exeOnce) { window.setInterval("tick()", 1000); exeOnce = false; @@ -210,7 +210,7 @@ } function parseRelease(obj) { - release = obj["name"].substring(6); + release = obj.name.substring(6); getAjax("/api/index", parse); } diff --git a/src/web/html/setup.html b/src/web/html/setup.html index f172f925..aef43dcf 100644 --- a/src/web/html/setup.html +++ b/src/web/html/setup.html @@ -227,8 +227,12 @@
-
Offset (pre sunrise, post sunset)
-
+
Offset (sunrise)
+
+
+
+
Offset (sunset)
+
@@ -889,9 +893,11 @@ function parseSun(obj) { document.getElementsByName("sunLat")[0].value = obj["lat"]; document.getElementsByName("sunLon")[0].value = obj["lon"]; - const sel = document.getElementsByName("sunOffs")[0]; - for(var i = 0; i <= 60; i++) { - sel.appendChild(opt(i, i + " minutes", (i == (obj["offs"] / 60)))); + for(p of [["sunOffsSr", "offsSr"], ["sunOffsSs", "offsSs"]]) { + const sel = document.getElementsByName(p[0])[0]; + for(var i = 0; i <= 60; i++) { + sel.appendChild(opt(i, i + " minutes", (i == (obj[p[1]] / 60)))); + } } } diff --git a/src/web/web.h b/src/web/web.h index 1e87547b..d10fa62d 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2022 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778 // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -541,11 +541,13 @@ class Web { if (request->arg("sunLat") == "" || (request->arg("sunLon") == "")) { mConfig->sun.lat = 0.0; mConfig->sun.lon = 0.0; - mConfig->sun.offsetSec = 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.offsetSec = request->arg("sunOffs").toInt() * 60; + mConfig->sun.offsetSecMorning = request->arg("sunOffsSr").toInt() * 60; + mConfig->sun.offsetSecEvening = request->arg("sunOffsSs").toInt() * 60; } // mqtt From ea29e49c93cdb1fa017886b441ce0a23d9a73a4e Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 00:21:06 +0100 Subject: [PATCH 5/8] 0.8.39 * fix MqTT dis_night_comm in the morning #1309 #1286 * seperated offset for sunrise and sunset #1308 * **BREAKING CHANGE**: powerlimit (active power control) now has one decimal place (MqTT / API) #1199 --- src/CHANGES.md | 3 ++- src/hm/Communication.h | 4 ++-- src/hm/hmDefines.h | 4 ++-- src/hm/hmInverter.h | 8 ++++---- src/hm/hmRadio.h | 10 +++++----- src/hms/hmsRadio.h | 10 +++++----- src/web/html/visualization.html | 4 ++-- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index 66056890..67e94621 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,8 +1,9 @@ # Development Changes ## 0.8.39 - 2024-01-01 -* fix MqTT dis_night_comm in the morning #1309 +* fix MqTT dis_night_comm in the morning #1309 #1286 * seperated offset for sunrise and sunset #1308 +* **BREAKING CHANGE**: powerlimit (active power control) now has one decimal place (MqTT / API) #1199 ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 9f96533d..79f5d34a 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy +// 2024 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -392,7 +392,7 @@ class Communication : public CommQueue<> { DBGPRINT(F("has ")); if(!accepted) DBGPRINT(F("not ")); DBGPRINT(F("accepted power limit set point ")); - DBGPRINT(String(q->iv->powerLimit[0])); + DBGPRINT(String(q->iv->powerLimit[0]/10)); DBGPRINT(F(" with PowerLimitControl ")); DBGPRINTLN(String(q->iv->powerLimit[1])); q->iv->actPowerLimit = 0xffff; // unknown, readback current value diff --git a/src/hm/hmDefines.h b/src/hm/hmDefines.h index 55259289..a2a2d6b4 100644 --- a/src/hm/hmDefines.h +++ b/src/hm/hmDefines.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// 2024 Ahoy, https://github.com/lumpapu/ahoy +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __HM_DEFINES_H__ diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 776b9c6f..fa8e1b87 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -1,6 +1,6 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778 -// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ +// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778 +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- #ifndef __HM_INVERTER_H__ @@ -110,7 +110,7 @@ class Inverter { uint8_t id; // unique id uint8_t type; // integer which refers to inverter type uint16_t alarmMesIndex; // Last recorded Alarm Message Index - uint16_t powerLimit[2]; // limit power output + uint16_t powerLimit[2]; // limit power output (multiplied by 10) float actPowerLimit; // actual power limit bool powerLimitAck; // acknowledged power limit (default: false) uint8_t devControlCmd; // carries the requested cmd @@ -152,7 +152,7 @@ class Inverter { Inverter() { ivGen = IV_HM; - powerLimit[0] = 0xffff; // 65535 W Limit -> unlimited + powerLimit[0] = 0xffff; // 6553.5 W Limit -> unlimited powerLimit[1] = AbsolutNonPersistent; // default power limit setting powerLimitAck = false; actPowerLimit = 0xffff; // init feedback from inverter to -1 diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 0754f83c..6539dd21 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy +// 2024 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -177,10 +177,10 @@ class HmRadio : public Radio { mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor mTxBuf[cnt++] = 0x00; if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet - mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit - mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit - mTxBuf[cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings - mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling + mTxBuf[cnt++] = (data[0] >> 8) & 0xff; // power limit, multiplied by 10 (because of fraction) + mTxBuf[cnt++] = (data[0] ) & 0xff; // power limit + mTxBuf[cnt++] = (data[1] >> 8) & 0xff; // setting for persistens handlings + mTxBuf[cnt++] = (data[1] ) & 0xff; // setting for persistens handling } } else { //MI 2nd gen. specific uint16_t powerMax = ((iv->powerLimit[1] == RelativNonPersistent) ? 0 : iv->getMaxPower()); diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index d2779012..6b502816 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://github.com/lumpapu/ahoy +// 2024 Ahoy, https://github.com/lumpapu/ahoy // Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed //----------------------------------------------------------------------------- @@ -50,10 +50,10 @@ class CmtRadio : public Radio { mTxBuf[cnt++] = cmd; // cmd -> 0 on, 1 off, 2 restart, 11 active power, 12 reactive power, 13 power factor mTxBuf[cnt++] = 0x00; if(cmd >= ActivePowerContr && cmd <= PFSet) { // ActivePowerContr, ReactivePowerContr, PFSet - mTxBuf[cnt++] = ((data[0] * 10) >> 8) & 0xff; // power limit - mTxBuf[cnt++] = ((data[0] * 10) ) & 0xff; // power limit - mTxBuf[cnt++] = ((data[1] ) >> 8) & 0xff; // setting for persistens handlings - mTxBuf[cnt++] = ((data[1] ) ) & 0xff; // setting for persistens handling + mTxBuf[cnt++] = (data[0] >> 8) & 0xff; // power limit, multiplied by 10 (because of fraction) + mTxBuf[cnt++] = (data[0] ) & 0xff; // power limit + mTxBuf[cnt++] = (data[1] >> 8) & 0xff; // setting for persistens handlings + mTxBuf[cnt++] = (data[1] ) & 0xff; // setting for persistens handling } sendPacket(iv, cnt, isRetransmit); diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index 9567504c..d8896e14 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -399,7 +399,7 @@ var html = ml("div", {}, [ ml("div", {class: "row mb-3"}, [ ml("div", {class: "col-12 col-sm-5 my-2"}, "Limit Value"), - ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number"}, "")), + ml("div", {class: "col-8 col-sm-5"}, ml("input", {name: "limit", type: "number", step: "0.1", min: 1}, "")), ml("div", {class: "col-4 col-sm-2"}, sel("type", opt, "pct")) ]), ml("div", {class: "row mb-3"}, [ @@ -450,7 +450,7 @@ var obj = new Object(); obj.id = id; obj.cmd = cmd; - obj.val = val; + obj.val = Math.round(val*10); getAjax("/api/ctrl", ctrlCb, "POST", JSON.stringify(obj)); } From 54b9e2f3ea6532df551edbf92c68e78cd40fb2c5 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 00:29:36 +0100 Subject: [PATCH 6/8] 0.8.39 * merge Prometheus metrics fix #1310 * merge MI grid profile request #1306 --- src/hm/Communication.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 7802bf32..53106524 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -351,10 +351,9 @@ class Communication : public CommQueue<> { // small MI or MI 1500 data responses to 0x09, 0x11, 0x36, 0x37, 0x38 and 0x39 //mPayload[iv->id].txId = p->packet[0]; miDataDecode(p, q); - } else if (p->packet[0] == (0x0f + ALL_FRAMES)) + } else if (p->packet[0] == (0x0f + ALL_FRAMES)) { miHwDecode(p, q); - - else if (p->packet[0] == ( 0x10 + ALL_FRAMES)) { + } else if (p->packet[0] == ( 0x10 + ALL_FRAMES)) { // MI response from get Grid Profile information request miGPFDecode(p, q); } @@ -665,13 +664,13 @@ class Communication : public CommQueue<> { q->iv->setValue(2, rec, (uint32_t) (((p->packet[10] << 8) | p->packet[11]))); //FLD_GRID_PROFILE_CODE q->iv->setValue(3, rec, (uint32_t) (((p->packet[12] << 8) | p->packet[13]))); //FLD_GRID_PROFILE_VERSION -/* according to xlsx (different start byte -1!) - Polling Grid-connected Protection Parameter File Command - Receipt - byte[10] ST1 indicates the status of the grid-connected protection file. ST1=1 indicates the default grid-connected protection file, ST=2 indicates that the grid-connected protection file is configured and normal, ST=3 indicates that the grid-connected protection file cannot be recognized, ST=4 indicates that the grid-connected protection file is damaged - byte[11] byte[12] CountryStd variable indicates the national standard code of the grid-connected protection file - byte[13] byte[14] Version indicates the version of the grid-connected protection file - byte[15] byte[16] -*/ + /* according to xlsx (different start byte -1!) + Polling Grid-connected Protection Parameter File Command - Receipt + byte[10] ST1 indicates the status of the grid-connected protection file. ST1=1 indicates the default grid-connected protection file, ST=2 indicates that the grid-connected protection file is configured and normal, ST=3 indicates that the grid-connected protection file cannot be recognized, ST=4 indicates that the grid-connected protection file is damaged + byte[11] byte[12] CountryStd variable indicates the national standard code of the grid-connected protection file + byte[13] byte[14] Version indicates the version of the grid-connected protection file + byte[15] byte[16] + */ /*if(mSerialDebug) { DPRINT(DBG_INFO,F("ST1 ")); DBGPRINTLN(String(p->packet[9])); From 27ad75b7d39fcc38d21f28885783fa4efed1c49d Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 00:45:42 +0100 Subject: [PATCH 7/8] 0.8.39 * merge update documentation / readme #1305 --- doc/screenshots/inverterSettings.png | Bin 0 -> 22006 bytes doc/screenshots/settings.png | Bin 0 -> 95179 bytes manual/Getting_Started.md | 3 +-- manual/User_Manual.md | 19 ++++++++++--------- manual/ahoy_config.md | 27 ++++++++++++++------------- src/CHANGES.md | 3 +++ 6 files changed, 28 insertions(+), 24 deletions(-) create mode 100644 doc/screenshots/inverterSettings.png create mode 100644 doc/screenshots/settings.png diff --git a/doc/screenshots/inverterSettings.png b/doc/screenshots/inverterSettings.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9484c166d00ef7df2bcee0562e093902dedac9 GIT binary patch literal 22006 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

|Rb3|~#t-{|@of>C% z1#%}_=zm4Q4SQFN`>qig__WJ1!2k9}?N{v+xz{BO7gd|1mQQ30|RRy;(SUpT_cCQa|?HVnRt4m!t%< z)g}zx;D{E1Y}S{Q7Ei!R#cvDN0XOWWRZts|=pbo6#tPKvmEQIH8i$S1!?vc;=Ow3` zONK)*TEEu$NWI&bjkK0~bmX)!gBq>fV9nHRrW2i@IahzU6@RWcBr@?g3B1;daAaYO z$iZI3`1oy;w?V_{v3$_zK+PdLO^!6TypMcwJiDL!q)vH9q%HkSLmysr@rd5MNjd~% z-LXQ`nJ)eP4vOu0r_FTQ<9_%3oK$!@{8n=O_?WuFWcy6~JLdz&ub!OU8g0?8 z1W1;Hkk0I@xZ`D~WPC$}`tAnpEqF1y2RW6=Z;yN$+E~KJ2}b=EV{wt;m6*L%g9PIN zeF!90H@~)u@}SFFO<7|QonP<|6m?3t?ve=VU&Vi=0_{j%!X4 zTjW^v66@daj@scaD>3z1&I$sxTWEn-*t&>9L>4nPRBX1bN?}ZP;2hN<$GSbmZsp2_ z!PUX|nh~m4RAlKyf|S)$!CeP0C9fL0MSZ4rQm|hHYOxg4A+1E>q!jt%vKkil@oHtf z%_CIP&}a+k9Aj0e;fhaC^=&eM&@Hj0Qnp$;C=zprlqeS6mw9UK@nz^zQ3fnu3rXLA z$Bz~ixeWD2lgZtZYOS{QAIdD}BP%gaQq984zb(pajcrvP$x=C%zQ;_vJv>#Q%XK`n zs`5lBHbjFgycJrDNu@#zy9(nw5t+>AF%Ulfr@zAA=bUrvW*jdPD6wkBS>u|;#3k8t zU{50r0%&%ZGs3Yg8%M4{JszZb7I;pbygB#P4WH($KzPgai*Z~CLuSS_%hXv_#t1Yy zVMUy=>Irw#iQKOE$|Vc=nVL^=lCmhKU6!k&KSRR*Xp>Ge940iK(Q?aMp&2*W?m?86 zcs>AlGg~{y5HPwCv1|#AdH|h=jH_N&M1MIO(W0gWHDq;{_gtL1RH^a7h16+o#5FnL zz`OwWGz{f`cy=Hve_cb@Rd$a`TU4g+7uVGT#Qll&ua}%&>_Yi&l~>B1#fkMpV#CQO zqTD`0=;;Ap%Js9QLI;Mva8s+D2pgd%w2+j%8thx=T(GMS$qjsl{?Uk05 zepDMh)r!jPO=uNb)tjGr!VE6fdnok>X^Y^}*W2OIsAs)^iY6-)207fCux-^aHBs0t zi#s=wbmgm&wAvACdRVfZ;L^uy0uO$?xD~~EkVVDxtnI06E4*zVm9Xg$TBN4`ebF6hK8`oQaYdg_=#B`#P@R&P%#g9hBx}a6 zm~cvuf7Ym9;H}6B0bnET-2j5Iuw21QZD*S& zQX=_*ZtjdZwk{~{_r&gguSFC&_3K!u_M1p~0iGQ>6u zYQwv>zAnCP4*E59?E5Qy5j(*-1De&j!4EK#o#}{|e$%Rk;fK$BtPPg=HZ$XL@uG}? zP2X(N=aGF70V=J8)H7aI5)>q8qx=|c{=4!OTH(njs&hc@sy9hK94DpwL!Pd@UG=C# nl#|+Q9t5f6-sUU5AaSacDnS7}SFV%zQ-($QE;s)Kt^qrq literal 0 HcmV?d00001 diff --git a/doc/screenshots/settings.png b/doc/screenshots/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..179a5d077e428858f2fb1bd6f542acf8ea461ce4 GIT binary patch literal 95179 zcmb@uWmKHO)-?zOcWIz;YuqKcd*f~i4ha(69fG@Sa3{DE+${<2E+J^J5L~8t-y3r0 zoB1_s{;*j6R9Dq=&Z(-i_u19qDoQeF$b`sHP*7-KSxGf0s8?VpC>R4oc*rNlt4i}w zP~=cxNihv~gQHah9e~U>{OJ|%F?vi)z6rjU_E86IZ9gGS^lB;nWmv~m75f}lv`|Xv zHVRK;llkVu%%}$Oar91KIF-}hR+VRnm-DURBe0o;A@r)%-iz+z} zqQwR9j{$wffdLE8Oo~e(6dau>G_s063}Cnjh9ji#?ULh7?Gh^Zrgqk29FY5;$K6LT zsNi?ByKw6|{uKMs`|gx4U&|H!2?tEzup6Lq((|XFFL~oXU-HuFjX2~ZLoO%MFW`_u z!Kt`*wEr3Exm%+}NS7(A!WFCKkbk^M|4sw)>>GJ^oEf~C0p%xHU_u>I!oc(N{xg;P zL?{XDoY)nr?zkkcrHI!>FJpayOpP9&$Dp&EUqY?Z+HsBTWsbv0&>@AHD!Z@O6(H09 zzT%Ge@+h~v3)@KcR{O0WuMAurJ7}Q4f8%U>Xky=)&r`mg_g_1Ib0l5?fk29px}~7T z8`^*m$l%S*&C|46B`JPih8j`TgH|Fz`*PIcIfLsCYD6Lh9KECsU&NI)kQ41-4_ z%(dU_{j&E{SM$XZqR>u=vQN2>uAT|l4gO⋙8kh@~5>(X;d4I@O7L1M{U>l`|s2x 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 should be set to `1` = `ON` and `0` = `OFF` } ``` +**beginning from verson `0.8.39` the wattage and percentage has one decimal place!** -### Power Limit relative persistent [%] +### Power Limit (active power control) relative persistent [%] ```json { @@ -205,10 +206,10 @@ The `` should be set to `1` = `ON` and `0` = `OFF` "val": } ``` -The `VALUE` represents a percent number in a range of `[2 .. 100]` +The `VALUE` represents a percent number in a range of `[2.0 .. 100.0]` -### Power Limit absolute persistent [Watts] +### Power Limit (active power control) absolute persistent [Watts] ```json { @@ -217,10 +218,10 @@ The `VALUE` represents a percent number in a range of `[2 .. 100]` "val": } ``` -The `VALUE` represents watts in a range of `[0 .. 65535]` +The `VALUE` represents watts in a range of `[1.0 .. 6553.5]` -### Power Limit relative non persistent [%] +### Power Limit (active power control) relative non persistent [%] ```json { @@ -229,10 +230,10 @@ The `VALUE` represents watts in a range of `[0 .. 65535]` "val": } ``` -The `VALUE` represents a percent number in a range of `[2 .. 100]` +The `VALUE` represents a percent number in a range of `[2.0 .. 100.0]` -### Power Limit absolute non persistent [Watts] +### Power Limit (active power control) absolute non persistent [Watts] ```json { @@ -241,7 +242,7 @@ The `VALUE` represents a percent number in a range of `[2 .. 100]` "val": } ``` -The `VALUE` represents watts in a range of `[0 .. 65535]` +The `VALUE` represents watts in a range of `[1.0 .. 6553.5]` @@ -328,7 +329,7 @@ Send Power Limit: - If the DC voltage is missing for a few seconds, the microcontroller in the inverter goes off and forgets everything that was temporary/non-persistent in the RAM: YieldDay, error memory, non-persistent limit. ### Update your AHOY-DTU Firmware To update your AHOY-DTU, you have to download the latest firmware package. -Here are the [latest stable releases](https://github.com/lumapu/ahoy/releases/) and [latest development builds](https://nightly.link/lumapu/ahoy/workflows/compile_development/development03/ahoydtu_dev.zip) available for download. +Here are the [latest stable releases](https://github.com/lumapu/ahoy/releases/) and [latest development builds](https://fw.ahoydtu.de/dev) available for download. As soon as you have downloaded the firmware package, unzip it. On the WebUI, navigate to Update and press on select firmware file. From the unzipped files, select the right .bin file for your hardware and needs. - If you use an ESP8266, select the file ending with esp8266.bin diff --git a/manual/ahoy_config.md b/manual/ahoy_config.md index 73427b6f..1971848f 100644 --- a/manual/ahoy_config.md +++ b/manual/ahoy_config.md @@ -1,19 +1,18 @@ - +# Ahoy configuration -## Ahoy configuration +## Prerequists +You have build your own hardware (or purchased one). The firmware is already loaded on the ESP and the WebUI is accessible from your browser. - So far we have built our own DTU, written a program on it and put it into operation. +## Start But how do I get my data from the inverter? -To do this, we need to configure the DTU. - The following steps are required: 1. Set the pinning to communicate with the radio module. 2. Check if Ahoy has a current time -3. Set inverter data +3. Configure the inverter data (e.g. serialnumber) ### 1.) Set the pinning -Once you are in the web interface, you will find the "System Config" sub-item in the Setup area (left). +Once you are in the web interface, you will find the "System Config" sub-item in the Setup area. This is where you tell the ESP how you connected the radio module. Note the schematics you saw earlier. - If you haven't noticed them yet, here's another table of connections. @@ -38,7 +37,7 @@ Note the schematics you saw earlier. - If you haven't noticed them yet, here's a | FCSB| GPIO21 | GPIO3| GPIO8 -### 2.) Set current time (normal skip this step) +### 2.) Set current time (standard: skip this step) Ahoy needs a current date and time to talk to the inverter. It works without, but it is recommended to include a time. This allows you to analyze information from the inverter in more detail. Normally, a date/time should be automatically retrieved from the NTP server. However, it may happen that the firewall of some routers does not allow this. @@ -51,20 +50,22 @@ Now it's time to place the inverter. This is necessary because it is not the inv Each inverter has its own S.Nr. This also serves as an identity for communication between the DTU and the inverter. -The S.Nr is a 12-digit number. You can look it up [here (german)](https://github.com/lumapu/ahoy/wiki/Hardware#wie-ist-die-serien-nummer-der-inverter-aufgebaut) for more information. +The S.Nr is a 12-digit number. Check [here (german)](https://github.com/lumapu/ahoy/wiki/Hardware#wie-ist-die-serien-nummer-der-inverter-aufgebaut) for more information. + #### set pv-modules (not necessary) Click on "Add Inverter" and enter the S.No. and a name. Please keep the name short! -![grafik](https://github.com/DanielR92/ahoy/assets/25644396/b52a2d5d-513c-4895-848a-01ce129f93c1) +![grafik](https://github.com/lumapu/ahoy/doc/screenshots/settings.png) -![grafik](https://github.com/DanielR92/ahoy/assets/25644396/b508824f-08a7-4b9c-bc41-29dfee02dced) +![grafik](https://github.com/lumapu/ahoy/doc/screenshots/inverterSettings.png) In the upper tab "Inputs" you can enter the data of the solar modules. These are only used directly in Ahoy for calculation and have no influence on the inverter. #### set radio parameter (not necessary, only for EU) In the next tab "Radio" you can adjust the power and other parameters if necessary. However, these should be left as default (EU only). -#### advanced options (not necessary) +#### advanced options (not necessary to be changed) In the "Advanced" section, you can customize more settings. Save and reboot. -# Done - Now check the live site + +## ✅ Done - Now check the live site diff --git a/src/CHANGES.md b/src/CHANGES.md index 67e94621..a29901ac 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -4,6 +4,9 @@ * fix MqTT dis_night_comm in the morning #1309 #1286 * seperated offset for sunrise and sunset #1308 * **BREAKING CHANGE**: powerlimit (active power control) now has one decimal place (MqTT / API) #1199 +* merge Prometheus metrics fix #1310 +* merge MI grid profile request #1306 +* merge update documentation / readme #1305 ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 From 40097aba1807241e910eabb4ee4fca0552a909b8 Mon Sep 17 00:00:00 2001 From: lumapu Date: Tue, 2 Jan 2024 01:03:40 +0100 Subject: [PATCH 8/8] 0.8.39 * add `getLossRate` to radio statistics and to MqTT #1199 --- src/CHANGES.md | 1 + src/defines.h | 4 ++++ src/hm/Communication.h | 2 +- src/hm/hmInverter.h | 35 +++++++++++++++-------------------- src/hm/hmRadio.h | 2 +- src/hms/hmsRadio.h | 2 +- src/publisher/pubMqttIvData.h | 10 +++++++--- 7 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/CHANGES.md b/src/CHANGES.md index a29901ac..c83130c8 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -7,6 +7,7 @@ * merge Prometheus metrics fix #1310 * merge MI grid profile request #1306 * merge update documentation / readme #1305 +* add `getLossRate` to radio statistics and to MqTT #1199 ## 0.8.38 - 2023-12-31 * fix Grid-Profile JSON #1304 diff --git a/src/defines.h b/src/defines.h index ad321921..d5aa04c2 100644 --- a/src/defines.h +++ b/src/defines.h @@ -103,6 +103,10 @@ typedef struct { uint32_t frmCnt; uint32_t txCnt; uint32_t retransmits; + uint16_t ivRxCnt; // last iv rx frames (from GetLossRate) + uint16_t ivTxCnt; // last iv tx frames (from GetLossRate) + uint16_t dtuRxCnt; // current DTU rx frames (since last GetLossRate) + uint16_t dtuTxCnt; // current DTU tx frames (since last GetLossRate) } statistics_t; #endif /*__DEFINES_H__*/ diff --git a/src/hm/Communication.h b/src/hm/Communication.h index 53106524..a448884e 100644 --- a/src/hm/Communication.h +++ b/src/hm/Communication.h @@ -151,7 +151,7 @@ class Communication : public CommQueue<> { if(validateIvSerial(&p->packet[1], q->iv)) { q->iv->radioStatistics.frmCnt++; - q->iv->mDtuRxCnt++; + q->iv->radioStatistics.dtuRxCnt++; if (p->packet[0] == (TX_REQ_INFO + ALL_FRAMES)) { // response from get information command if(parseFrame(p)) diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 91b8480e..37a469a7 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -141,12 +141,6 @@ class Inverter { 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 - uint16_t mIvRxCnt; // last iv rx frames (from GetLossRate) - uint16_t mIvTxCnt; // last iv tx frames (from GetLossRate) - uint16_t mDtuRxCnt; // cur dtu rx frames (since last GetLossRate) - uint16_t mDtuTxCnt; // cur dtu tx frames (since last getLoassRate) - uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debu - static uint32_t *timestamp; // system timestamp static cfgInst_t *generalConfig; // general inverter configuration from setup @@ -171,10 +165,6 @@ class Inverter { mIsSingleframeReq = false; radio = NULL; commEnabled = true; - mIvRxCnt = 0; - mIvTxCnt = 0; - mDtuRxCnt = 0; - mDtuTxCnt = 0; memset(&radioStatistics, 0, sizeof(statistics_t)); memset(heuristics.txRfQuality, -6, 5); @@ -605,21 +595,25 @@ class Inverter { uint16_t rxCnt = (pyld[0] << 8) + pyld[1]; uint16_t txCnt = (pyld[2] << 8) + pyld[3]; - if (mIvRxCnt || mIvTxCnt) { // there was successful GetLossRate in the past + if (radioStatistics.ivRxCnt || radioStatistics.ivTxCnt) { // there was successful GetLossRate in the past DPRINT_IVID(DBG_INFO, id); - DBGPRINTLN("Inv loss: " + - String (mDtuTxCnt - (rxCnt - mIvRxCnt)) + " of " + - String (mDtuTxCnt) + ", DTU loss: " + - String (txCnt - mIvTxCnt - mDtuRxCnt) + " of " + - String (txCnt - mIvTxCnt)); + DBGPRINT(F("Inv loss: ")); + DBGPRINT(String (radioStatistics.dtuTxCnt - (rxCnt - radioStatistics.ivRxCnt))); + DBGPRINT(F(" of ")); + DBGPRINT(String (radioStatistics.dtuTxCnt)); + DBGPRINT(F(", DTU loss: ")); + DBGPRINT(String (txCnt - radioStatistics.ivTxCnt - radioStatistics.dtuRxCnt)); + DBGPRINT(F(" of ")); + DBGPRINTLN(String (txCnt - radioStatistics.ivTxCnt)); } - mIvRxCnt = rxCnt; - mIvTxCnt = txCnt; - mDtuRxCnt = 0; // start new interval - mDtuTxCnt = 0; // start new interval + radioStatistics.ivRxCnt = rxCnt; + radioStatistics.ivTxCnt = txCnt; + radioStatistics.dtuRxCnt = 0; // start new interval + radioStatistics.dtuTxCnt = 0; // start new interval return true; } + return false; } @@ -811,6 +805,7 @@ class Inverter { bool mDevControlRequest; // true if change needed uint8_t mGridLen = 0; uint8_t mGridProfile[MAX_GRID_LENGTH]; + uint8_t mGetLossInterval; // request iv every AHOY_GET_LOSS_INTERVAL RealTimeRunData_Debug }; template diff --git a/src/hm/hmRadio.h b/src/hm/hmRadio.h index 6539dd21..c7b9581c 100644 --- a/src/hm/hmRadio.h +++ b/src/hm/hmRadio.h @@ -339,7 +339,7 @@ class HmRadio : public Radio { mMillis = millis(); mLastIv = iv; - iv->mDtuTxCnt++; + iv->radioStatistics.dtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/hms/hmsRadio.h b/src/hms/hmsRadio.h index 6b502816..3b3893ac 100644 --- a/src/hms/hmsRadio.h +++ b/src/hms/hmsRadio.h @@ -112,7 +112,7 @@ class CmtRadio : public Radio { if(CMT_ERR_RX_IN_FIFO == status) mIrqRcvd = true; } - iv->mDtuTxCnt++; + iv->radioStatistics.dtuTxCnt++; } uint64_t getIvId(Inverter<> *iv) { diff --git a/src/publisher/pubMqttIvData.h b/src/publisher/pubMqttIvData.h index 9a364ec9..3a38a5ed 100644 --- a/src/publisher/pubMqttIvData.h +++ b/src/publisher/pubMqttIvData.h @@ -195,12 +195,16 @@ class PubMqttIvData { inline void sendRadioStat(uint8_t start) { snprintf(mSubTopic, 32 + MAX_NAME_LENGTH, "%s/radio_stat", mIv->config->name); - snprintf(mVal, 100, "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d}", + snprintf(mVal, 140, "{\"tx\":%d,\"success\":%d,\"fail\":%d,\"no_answer\":%d,\"retransmits\":%d,\"lossIvRx\":%d,\"lossIvTx\":%d,\"lossDtuRx\":%d,\"lossDtuTx\":%d}", mIv->radioStatistics.txCnt, mIv->radioStatistics.rxSuccess, mIv->radioStatistics.rxFail, mIv->radioStatistics.rxFailNoAnser, - mIv->radioStatistics.retransmits); + mIv->radioStatistics.retransmits, + mIv->radioStatistics.ivRxCnt, + mIv->radioStatistics.ivTxCnt, + mIv->radioStatistics.dtuRxCnt, + mIv->radioStatistics.dtuTxCnt); mPublish(mSubTopic, mVal, false, QOS_0); } @@ -263,7 +267,7 @@ class PubMqttIvData { bool mRTRDataHasBeenSent; char mSubTopic[32 + MAX_NAME_LENGTH + 1]; - char mVal[100]; + char mVal[140]; bool mZeroValues; // makes sure that yield day is sent even if no inverter is online std::queue *mSendList;