Merge branch 'mqtt_rework' into dev

This commit is contained in:
Thomas Basler 2023-02-13 18:39:29 +01:00
commit 7bbb278eaf
29 changed files with 730 additions and 566 deletions

View File

@ -56,7 +56,7 @@ You can "talk" to the OpenDTU with a command line tool like `curl`. The output i
```
~$ curl http://192.168.10.10/api/livedata/status
{"inverters":[{"serial":"11418186xxxx","name":"HM600","data_age":4,"reachable":true,"producing":true,"limit_relative":100,"limit_absolute":600,"0":{"Power":{"v":70.69999695,"u":"W","d":1},"Voltage":{"v":233,"u":"V","d":1},"Current":{"v":0.300000012,"u":"A","d":2},"Power DC":{"v":74,"u":"W","d":2},"YieldDay":{"v":23,"u":"Wh","d":2},"YieldTotal":{"v":150.5050049,"u":"kWh","d":2},"Frequency":{"v":50.02000046,"u":"Hz","d":2},"Temperature":{"v":8.300000191,"u":"°C","d":1},"PowerFactor":{"v":1,"u":"","d":3},"ReactivePower":{"v":0.100000001,"u":"var","d":1},"Efficiency":{"v":95.54053497,"u":"%","d":2}},"1":{"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":1,"u":"V","d":1},"Current":{"v":0.02,"u":"A","d":2},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":49.0320015,"u":"kWh","d":3},"Irradiation":{"v":0,"u":"%","d":2}},"2":{"Power":{"v":74,"u":"W","d":1},"Voltage":{"v":42.40000153,"u":"V","d":1},"Current":{"v":1.74000001,"u":"A","d":2},"YieldDay":{"v":23,"u":"Wh","d":0},"YieldTotal":{"v":101.4729996,"u":"kWh","d":3},"Irradiation":{"v":18.04878044,"u":"%","d":2}},"events":3},{"serial":"11418180xxxx","name":"HM800","data_age":11,"reachable":true,"producing":true,"limit_relative":100,"limit_absolute":800,"0":{"Power":{"v":70.09999847,"u":"W","d":1},"Voltage":{"v":233.1000061,"u":"V","d":1},"Current":{"v":0.300000012,"u":"A","d":2},"Power DC":{"v":73.59999847,"u":"W","d":2},"YieldDay":{"v":48,"u":"Wh","d":2},"YieldTotal":{"v":48.5399971,"u":"kWh","d":2},"Frequency":{"v":50.02000046,"u":"Hz","d":2},"Temperature":{"v":11.39999962,"u":"°C","d":1},"PowerFactor":{"v":1,"u":"","d":3},"ReactivePower":{"v":0.100000001,"u":"var","d":1},"Efficiency":{"v":95.24456024,"u":"%","d":2}},"1":{"Power":{"v":36.5,"u":"W","d":1},"Voltage":{"v":39.09999847,"u":"V","d":1},"Current":{"v":0.930000007,"u":"A","d":2},"YieldDay":{"v":31,"u":"Wh","d":0},"YieldTotal":{"v":4.301000118,"u":"kWh","d":3},"Irradiation":{"v":8.902439117,"u":"%","d":2}},"2":{"Power":{"v":37.09999847,"u":"W","d":1},"Voltage":{"v":40.79999924,"u":"V","d":1},"Current":{"v":0.910000026,"u":"A","d":2},"YieldDay":{"v":17,"u":"Wh","d":0},"YieldTotal":{"v":44.23899841,"u":"kWh","d":3},"Irradiation":{"v":9.048780441,"u":"%","d":2}},"events":1}],"total":{"Power":{"v":140.7999878,"u":"W","d":1},"YieldDay":{"v":71,"u":"Wh","d":0},"YieldTotal":{"v":199.0449982,"u":"kWh","d":2}}}
{"inverters":[{"serial":"11617160xxxx","name":"Meine Solaranlage","data_age":6983,"reachable":false,"producing":false,"limit_relative":0,"limit_absolute":-1,"AC":{"0":{"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"Power DC":{"v":0,"u":"W","d":1},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3},"Frequency":{"v":0,"u":"Hz","d":2},"PowerFactor":{"v":0,"u":"","d":3},"ReactivePower":{"v":0,"u":"var","d":1},"Efficiency":{"v":0,"u":"%","d":3}}},"DC":{"0":{"name":{"u":""},"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3},"Irradiation":{"v":0,"u":"%","d":3}},"1":{"name":{"u":""},"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3},"Irradiation":{"v":0,"u":"%","d":3}},"2":{"name":{"u":""},"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3},"Irradiation":{"v":0,"u":"%","d":3}},"3":{"name":{"u":""},"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3}}},"INV":{"0":{"Temperature":{"v":0,"u":"°C","d":1}}},"events":0},{"serial":"11417160xxxx","name":"test","data_age":6983,"reachable":false,"producing":false,"limit_relative":0,"limit_absolute":-1,"AC":{"0":{"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"Power DC":{"v":0,"u":"W","d":1},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3},"Frequency":{"v":0,"u":"Hz","d":2},"PowerFactor":{"v":0,"u":"","d":3},"ReactivePower":{"v":0,"u":"var","d":1},"Efficiency":{"v":0,"u":"%","d":3}}},"DC":{"0":{"name":{"u":"test 1"},"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3},"Irradiation":{"v":0,"u":"%","d":3}},"1":{"name":{"u":"test 2"},"Power":{"v":0,"u":"W","d":1},"Voltage":{"v":0,"u":"V","d":1},"Current":{"v":0,"u":"A","d":2},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":3},"Irradiation":{"v":0,"u":"%","d":3}}},"INV":{"0":{"Temperature":{"v":0,"u":"°C","d":1}}},"events":0}],"total":{"Power":{"v":0,"u":"W","d":1},"YieldDay":{"v":0,"u":"Wh","d":0},"YieldTotal":{"v":0,"u":"kWh","d":2}},"hints":{"time_sync":false,"radio_problem":false,"default_password":false}}
```
@ -67,284 +67,382 @@ To enhance readability (and filter information) use the JSON command line proces
{
"inverters": [
{
"serial": "11418186xxxx",
"name": "HM600",
"data_age": 4,
"reachable": true,
"producing": true,
"limit_relative": 100,
"limit_absolute": 600,
"0": {
"Power": {
"v": 70.69999695,
"u": "W",
"d": 1
},
"Voltage": {
"v": 233,
"u": "V",
"d": 1
},
"Current": {
"v": 0.300000012,
"u": "A",
"d": 2
},
"Power DC": {
"v": 74,
"u": "W",
"d": 2
},
"YieldDay": {
"v": 23,
"u": "Wh",
"d": 2
},
"YieldTotal": {
"v": 150.5050049,
"u": "kWh",
"d": 2
},
"Frequency": {
"v": 50.02000046,
"u": "Hz",
"d": 2
},
"Temperature": {
"v": 8.300000191,
"u": "°C",
"d": 1
},
"PowerFactor": {
"v": 1,
"u": "",
"d": 3
},
"ReactivePower": {
"v": 0.100000001,
"u": "var",
"d": 1
},
"Efficiency": {
"v": 95.54053497,
"u": "%",
"d": 2
"serial": "116171603546",
"name": "Meine Solaranlage",
"data_age": 7038,
"reachable": false,
"producing": false,
"limit_relative": 0,
"limit_absolute": -1,
"AC": {
"0": {
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"Power DC": {
"v": 0,
"u": "W",
"d": 1
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
},
"Frequency": {
"v": 0,
"u": "Hz",
"d": 2
},
"PowerFactor": {
"v": 0,
"u": "",
"d": 3
},
"ReactivePower": {
"v": 0,
"u": "var",
"d": 1
},
"Efficiency": {
"v": 0,
"u": "%",
"d": 3
}
}
},
"1": {
"Power": {
"v": 0,
"u": "W",
"d": 1
"DC": {
"0": {
"name": {
"u": ""
},
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 0,
"u": "%",
"d": 3
}
},
"Voltage": {
"v": 1,
"u": "V",
"d": 1
"1": {
"name": {
"u": ""
},
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 0,
"u": "%",
"d": 3
}
},
"Current": {
"v": 0.02,
"u": "A",
"d": 2
"2": {
"name": {
"u": ""
},
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 0,
"u": "%",
"d": 3
}
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 49.0320015,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 0,
"u": "%",
"d": 2
"3": {
"name": {
"u": ""
},
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
}
}
},
"2": {
"Power": {
"v": 74,
"u": "W",
"d": 1
},
"Voltage": {
"v": 42.40000153,
"u": "V",
"d": 1
},
"Current": {
"v": 1.74000001,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 23,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 101.4729996,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 18.04878044,
"u": "%",
"d": 2
"INV": {
"0": {
"Temperature": {
"v": 0,
"u": "°C",
"d": 1
}
}
},
"events": 3
"events": 0
},
{
"serial": "11418180xxxx",
"name": "HM800",
"data_age": 11,
"reachable": true,
"producing": true,
"limit_relative": 100,
"limit_absolute": 800,
"0": {
"Power": {
"v": 70.09999847,
"u": "W",
"d": 1
},
"Voltage": {
"v": 233.1000061,
"u": "V",
"d": 1
},
"Current": {
"v": 0.300000012,
"u": "A",
"d": 2
},
"Power DC": {
"v": 73.59999847,
"u": "W",
"d": 2
},
"YieldDay": {
"v": 48,
"u": "Wh",
"d": 2
},
"YieldTotal": {
"v": 48.5399971,
"u": "kWh",
"d": 2
},
"Frequency": {
"v": 50.02000046,
"u": "Hz",
"d": 2
},
"Temperature": {
"v": 11.39999962,
"u": "°C",
"d": 1
},
"PowerFactor": {
"v": 1,
"u": "",
"d": 3
},
"ReactivePower": {
"v": 0.100000001,
"u": "var",
"d": 1
},
"Efficiency": {
"v": 95.24456024,
"u": "%",
"d": 2
"serial": "114171603548",
"name": "test",
"data_age": 7038,
"reachable": false,
"producing": false,
"limit_relative": 0,
"limit_absolute": -1,
"AC": {
"0": {
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"Power DC": {
"v": 0,
"u": "W",
"d": 1
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
},
"Frequency": {
"v": 0,
"u": "Hz",
"d": 2
},
"PowerFactor": {
"v": 0,
"u": "",
"d": 3
},
"ReactivePower": {
"v": 0,
"u": "var",
"d": 1
},
"Efficiency": {
"v": 0,
"u": "%",
"d": 3
}
}
},
"1": {
"Power": {
"v": 36.5,
"u": "W",
"d": 1
"DC": {
"0": {
"name": {
"u": "test 1"
},
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 0,
"u": "%",
"d": 3
}
},
"Voltage": {
"v": 39.09999847,
"u": "V",
"d": 1
},
"Current": {
"v": 0.930000007,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 31,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 4.301000118,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 8.902439117,
"u": "%",
"d": 2
"1": {
"name": {
"u": "test 2"
},
"Power": {
"v": 0,
"u": "W",
"d": 1
},
"Voltage": {
"v": 0,
"u": "V",
"d": 1
},
"Current": {
"v": 0,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 0,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 0,
"u": "%",
"d": 3
}
}
},
"2": {
"Power": {
"v": 37.09999847,
"u": "W",
"d": 1
},
"Voltage": {
"v": 40.79999924,
"u": "V",
"d": 1
},
"Current": {
"v": 0.910000026,
"u": "A",
"d": 2
},
"YieldDay": {
"v": 17,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 44.23899841,
"u": "kWh",
"d": 3
},
"Irradiation": {
"v": 9.048780441,
"u": "%",
"d": 2
"INV": {
"0": {
"Temperature": {
"v": 0,
"u": "°C",
"d": 1
}
}
},
"events": 1
"events": 0
}
],
"total": {
"Power": {
"v": 140.7999878,
"v": 0,
"u": "W",
"d": 1
},
"YieldDay": {
"v": 71,
"v": 0,
"u": "Wh",
"d": 0
},
"YieldTotal": {
"v": 199.0449982,
"v": 0,
"u": "kWh",
"d": 2
}
},
"hints": {
"time_sync": false,
"radio_problem": false,
"default_password": false
}
}
```

View File

@ -25,7 +25,7 @@ enum {
const char* const stateClasses[] = { 0, "measurement", "total_increasing" };
typedef struct {
uint8_t fieldId; // field id
FieldId_t fieldId; // field id
uint8_t deviceClsId; // device class
uint8_t stateClsId; // state class
} byteAssign_fieldDeviceClass_t;
@ -57,7 +57,7 @@ public:
private:
void publish(const String& subtopic, const String& payload);
void publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear = false);
void publishField(std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear = false);
void publishInverterButton(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* deviceClass, const char* subTopic, const char* payload);
void publishInverterNumber(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* icon, const char* category, const char* commandTopic, const char* stateTopic, const char* unitOfMeasure, int16_t min = 1, int16_t max = 100);
void publishInverterBinarySensor(std::shared_ptr<InverterAbstract> inv, const char* caption, const char* subTopic, const char* payload_on, const char* payload_off);

View File

@ -10,16 +10,16 @@ public:
void init();
void loop();
static String getTopic(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId);
static String getTopic(std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
private:
void publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId);
void publishField(std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
void onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total);
uint32_t _lastPublishStats[INV_MAX_COUNT];
uint32_t _lastPublish;
uint8_t _publishFields[14] = {
FieldId_t _publishFields[14] = {
FLD_UDC,
FLD_IDC,
FLD_PDC,

View File

@ -12,7 +12,7 @@ public:
private:
void onPrometheusMetricsGet(AsyncWebServerRequest* request);
void addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, const char* channelName = NULL);
void addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, const char* channelName = NULL);
AsyncWebServer* _server;
};

View File

@ -13,7 +13,7 @@ public:
private:
void generateJsonResponse(JsonVariant& root);
void addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic = "");
void addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, String topic = "");
void addTotalField(JsonObject& root, String name, float value, String unit, uint8_t digits);
void onLivedataStatus(AsyncWebServerRequest* request);
void onWebsocketEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len);

View File

@ -32,12 +32,7 @@ String HM_1CH::typeName()
return F("HM-300, HM-350, HM-400");
}
const byteAssign_t* HM_1CH::getByteAssignment()
const std::list<byteAssign_t>* HM_1CH::getByteAssignment()
{
return byteAssignment;
}
uint8_t HM_1CH::getAssignmentCount()
{
return sizeof(byteAssignment) / sizeof(byteAssign_t);
return &byteAssignment;
}

View File

@ -2,35 +2,37 @@
#pragma once
#include "HM_Abstract.h"
#include <list>
class HM_1CH : public HM_Abstract {
public:
explicit HM_1CH(uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const byteAssign_t* getByteAssignment();
uint8_t getAssignmentCount();
const std::list<byteAssign_t>* getByteAssignment();
private:
const byteAssign_t byteAssignment[18] = {
{ CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ CH1, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ CH1, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ CH1, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
{ CH1, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
{ CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 12, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 8, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
{ CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
{ CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
{ CH0, FLD_PRA, UNIT_VA, 20, 2, 10, false, 1 },
{ CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
{ CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
{ CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
{ CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
{ CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 14, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 22, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 18, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 20, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 16, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 24, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 26, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 28, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
};

View File

@ -33,12 +33,7 @@ String HM_2CH::typeName()
return F("HM-600, HM-700, HM-800");
}
const byteAssign_t* HM_2CH::getByteAssignment()
const std::list<byteAssign_t>* HM_2CH::getByteAssignment()
{
return byteAssignment;
}
uint8_t HM_2CH::getAssignmentCount()
{
return sizeof(byteAssignment) / sizeof(byteAssign_t);
return &byteAssignment;
}

View File

@ -8,36 +8,37 @@ public:
explicit HM_2CH(uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const byteAssign_t* getByteAssignment();
uint8_t getAssignmentCount();
const std::list<byteAssign_t>* getByteAssignment();
private:
const byteAssign_t byteAssignment[24] = {
{ CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ CH1, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ CH1, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ CH1, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 6, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 14, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ CH2, FLD_UDC, UNIT_V, 8, 2, 10, false, 1 },
{ CH2, FLD_IDC, UNIT_A, 10, 2, 100, false, 2 },
{ CH2, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ CH2, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ CH2, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 8, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 10, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 12, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 24, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 18, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
{ CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
{ CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
{ CH0, FLD_PRA, UNIT_VA, 32, 2, 10, false, 1 },
{ CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
{ CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
{ CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
{ CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
{ CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 26, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 34, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 32, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 28, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 36, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 38, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 40, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
};

View File

@ -32,12 +32,7 @@ String HM_4CH::typeName()
return F("HM-1000, HM-1200, HM-1500");
}
const byteAssign_t* HM_4CH::getByteAssignment()
const std::list<byteAssign_t>* HM_4CH::getByteAssignment()
{
return byteAssignment;
}
uint8_t HM_4CH::getAssignmentCount()
{
return sizeof(byteAssignment) / sizeof(byteAssign_t);
return &byteAssignment;
}

View File

@ -8,50 +8,51 @@ public:
explicit HM_4CH(uint64_t serial);
static bool isValidSerial(uint64_t serial);
String typeName();
const byteAssign_t* getByteAssignment();
uint8_t getAssignmentCount();
const std::list<byteAssign_t>* getByteAssignment();
private:
const byteAssign_t byteAssignment[36] = {
{ CH1, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ CH1, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ CH1, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
{ CH1, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
{ CH1, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
{ CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
const std::list<byteAssign_t> byteAssignment = {
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 4, 2, 100, false, 2 },
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 8, 2, 10, false, 1 },
{ TYPE_DC, CH0, FLD_YD, UNIT_WH, 20, 2, 1, false, 0 },
{ TYPE_DC, CH0, FLD_YT, UNIT_KWH, 12, 4, 1000, false, 3 },
{ TYPE_DC, CH0, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH0, CMD_CALC, false, 3 },
{ CH2, FLD_UDC, UNIT_V, CALC_UDC_CH, CH1, CMD_CALC, false, 1 },
{ CH2, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ CH2, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ CH2, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ CH2, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
{ CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, CALC_UDC_CH, CH0, CMD_CALC, false, 1 },
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
{ TYPE_DC, CH1, FLD_PDC, UNIT_W, 10, 2, 10, false, 1 },
{ TYPE_DC, CH1, FLD_YD, UNIT_WH, 22, 2, 1, false, 0 },
{ TYPE_DC, CH1, FLD_YT, UNIT_KWH, 16, 4, 1000, false, 3 },
{ TYPE_DC, CH1, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH1, CMD_CALC, false, 3 },
{ CH3, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ CH3, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
{ CH3, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
{ CH3, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
{ CH3, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
{ CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ TYPE_DC, CH2, FLD_UDC, UNIT_V, 24, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_IDC, UNIT_A, 26, 2, 100, false, 2 },
{ TYPE_DC, CH2, FLD_PDC, UNIT_W, 30, 2, 10, false, 1 },
{ TYPE_DC, CH2, FLD_YD, UNIT_WH, 42, 2, 1, false, 0 },
{ TYPE_DC, CH2, FLD_YT, UNIT_KWH, 34, 4, 1000, false, 3 },
{ TYPE_DC, CH2, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH2, CMD_CALC, false, 3 },
{ CH4, FLD_UDC, UNIT_V, CALC_UDC_CH, CH3, CMD_CALC, false, 1 },
{ CH4, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
{ CH4, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
{ CH4, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
{ CH4, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ CH4, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH4, CMD_CALC, false, 3 },
{ TYPE_DC, CH3, FLD_UDC, UNIT_V, CALC_UDC_CH, CH2, CMD_CALC, false, 1 },
{ TYPE_DC, CH3, FLD_IDC, UNIT_A, 28, 2, 100, false, 2 },
{ TYPE_DC, CH3, FLD_PDC, UNIT_W, 32, 2, 10, false, 1 },
{ TYPE_DC, CH3, FLD_YD, UNIT_WH, 44, 2, 1, false, 0 },
{ TYPE_DC, CH3, FLD_YT, UNIT_KWH, 38, 4, 1000, false, 3 },
{ TYPE_DC, CH3, FLD_IRR, UNIT_PCT, CALC_IRR_CH, CH3, CMD_CALC, false, 3 },
{ CH0, FLD_UAC, UNIT_V, 46, 2, 10, false, 1 },
{ CH0, FLD_IAC, UNIT_A, 54, 2, 100, false, 2 },
{ CH0, FLD_PAC, UNIT_W, 50, 2, 10, false, 1 },
{ CH0, FLD_PRA, UNIT_VA, 52, 2, 10, false, 1 },
{ CH0, FLD_F, UNIT_HZ, 48, 2, 100, false, 2 },
{ CH0, FLD_PF, UNIT_NONE, 56, 2, 1000, false, 3 },
{ CH0, FLD_T, UNIT_C, 58, 2, 10, true, 1 },
{ CH0, FLD_EVT_LOG, UNIT_NONE, 60, 2, 1, false, 0 },
{ CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
{ TYPE_AC, CH0, FLD_UAC, UNIT_V, 46, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_IAC, UNIT_A, 54, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PAC, UNIT_W, 50, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_PRA, UNIT_VA, 52, 2, 10, false, 1 },
{ TYPE_AC, CH0, FLD_F, UNIT_HZ, 48, 2, 100, false, 2 },
{ TYPE_AC, CH0, FLD_PF, UNIT_NONE, 56, 2, 1000, false, 3 },
{ TYPE_INV, CH0, FLD_T, UNIT_C, 58, 2, 10, true, 1 },
{ TYPE_INV, CH0, FLD_EVT_LOG, UNIT_NONE, 60, 2, 1, false, 0 },
{ TYPE_AC, CH0, FLD_YD, UNIT_WH, CALC_YD_CH0, 0, CMD_CALC, false, 0 },
{ TYPE_AC, CH0, FLD_YT, UNIT_KWH, CALC_YT_CH0, 0, CMD_CALC, false, 3 },
{ TYPE_AC, CH0, FLD_PDC, UNIT_W, CALC_PDC_CH0, 0, CMD_CALC, false, 1 },
{ TYPE_AC, CH0, FLD_EFF, UNIT_PCT, CALC_EFF_CH0, 0, CMD_CALC, false, 3 }
};
};

View File

@ -40,14 +40,14 @@ bool HM_Abstract::sendAlarmLogRequest(HoymilesRadio* radio, bool force)
}
if (!force) {
if (Statistics()->hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
if ((uint8_t)Statistics()->getChannelFieldValue(CH0, FLD_EVT_LOG) == _lastAlarmLogCnt) {
if (Statistics()->hasChannelFieldValue(TYPE_INV, CH0, FLD_EVT_LOG)) {
if ((uint8_t)Statistics()->getChannelFieldValue(TYPE_INV, CH0, FLD_EVT_LOG) == _lastAlarmLogCnt) {
return false;
}
}
}
_lastAlarmLogCnt = (uint8_t)Statistics()->getChannelFieldValue(CH0, FLD_EVT_LOG);
_lastAlarmLogCnt = (uint8_t)Statistics()->getChannelFieldValue(TYPE_INV, CH0, FLD_EVT_LOG);
time_t now;
time(&now);

View File

@ -30,7 +30,7 @@ void InverterAbstract::init()
// Not possible in constructor --> virtual function
// Not possible in verifyAllFragments --> Because no data if nothing is ever received
// It has to be executed because otherwise the getChannelCount method in stats always returns 0
_statisticsParser.get()->setByteAssignment(getByteAssignment(), getAssignmentCount());
_statisticsParser.get()->setByteAssignment(getByteAssignment());
}
uint64_t InverterAbstract::serial()
@ -60,11 +60,14 @@ const char* InverterAbstract::name()
bool InverterAbstract::isProducing()
{
if (!Statistics()->hasChannelFieldValue(CH0, FLD_PAC)) {
return false;
float totalAc = 0;
for (auto& c : Statistics()->getChannelsByType(TYPE_AC)) {
if (Statistics()->hasChannelFieldValue(TYPE_AC, c, FLD_PAC)) {
totalAc += Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
}
}
return Statistics()->getChannelFieldValue(CH0, FLD_PAC) > 0;
return totalAc > 0;
}
bool InverterAbstract::isReachable()

View File

@ -11,6 +11,7 @@
#include "types.h"
#include <Arduino.h>
#include <cstdint>
#include <list>
#define MAX_NAME_LENGTH 32
@ -38,8 +39,7 @@ public:
void setName(const char* name);
const char* name();
virtual String typeName() = 0;
virtual const byteAssign_t* getByteAssignment() = 0;
virtual uint8_t getAssignmentCount() = 0;
virtual const std::list<byteAssign_t>* getByteAssignment() = 0;
bool isProducing();
bool isReachable();

View File

@ -28,10 +28,9 @@ const calcFunc_t calcFunctions[] = {
{ CALC_IRR_CH, &calcIrradiation }
};
void StatisticsParser::setByteAssignment(const byteAssign_t* byteAssignment, const uint8_t count)
void StatisticsParser::setByteAssignment(const std::list<byteAssign_t>* byteAssignment)
{
_byteAssignment = byteAssignment;
_byteAssignmentCount = count;
}
void StatisticsParser::clearBuffer()
@ -50,31 +49,26 @@ void StatisticsParser::appendFragment(uint8_t offset, uint8_t* payload, uint8_t
_statisticLength += len;
}
uint8_t StatisticsParser::getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId)
const byteAssign_t* StatisticsParser::getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
const byteAssign_t* b = _byteAssignment;
uint8_t pos;
for (pos = 0; pos < _byteAssignmentCount; pos++) {
if (b[pos].ch == channel && b[pos].fieldId == fieldId) {
return pos;
for (auto const& i : *_byteAssignment) {
if (i.type == type && i.ch == channel && i.fieldId == fieldId) {
return &i;
}
}
return 0xff;
return NULL;
}
float StatisticsParser::getChannelFieldValue(uint8_t channel, uint8_t fieldId)
float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
if (pos == 0xff) {
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
if (pos == NULL) {
return 0;
}
const byteAssign_t* b = _byteAssignment;
uint8_t ptr = b[pos].start;
uint8_t end = ptr + b[pos].num;
uint16_t div = b[pos].div;
uint8_t ptr = pos->start;
uint8_t end = ptr + pos->num;
uint16_t div = pos->div;
if (CMD_CALC != div) {
// Value is a static value
@ -85,9 +79,9 @@ float StatisticsParser::getChannelFieldValue(uint8_t channel, uint8_t fieldId)
} while (++ptr != end);
float result;
if (b[pos].isSigned && b[pos].num == 2) {
if (pos->isSigned && pos->num == 2) {
result = static_cast<float>(static_cast<int16_t>(val));
} else if (b[pos].isSigned && b[pos].num == 4) {
} else if (pos->isSigned && pos->num == 4) {
result = static_cast<float>(static_cast<int32_t>(val));
} else {
result = static_cast<float>(val);
@ -97,62 +91,71 @@ float StatisticsParser::getChannelFieldValue(uint8_t channel, uint8_t fieldId)
return result;
} else {
// Value has to be calculated
return calcFunctions[b[pos].start].func(this, b[pos].num);
return calcFunctions[pos->start].func(this, pos->num);
}
return 0;
}
bool StatisticsParser::hasChannelFieldValue(uint8_t channel, uint8_t fieldId)
bool StatisticsParser::hasChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
return pos != 0xff;
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
return pos != NULL;
}
const char* StatisticsParser::getChannelFieldUnit(uint8_t channel, uint8_t fieldId)
const char* StatisticsParser::getChannelFieldUnit(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
const byteAssign_t* b = _byteAssignment;
return units[b[pos].unitId];
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
return units[pos->unitId];
}
const char* StatisticsParser::getChannelFieldName(uint8_t channel, uint8_t fieldId)
const char* StatisticsParser::getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
const byteAssign_t* b = _byteAssignment;
return fields[b[pos].fieldId];
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
return fields[pos->fieldId];
}
uint8_t StatisticsParser::getChannelFieldDigits(uint8_t channel, uint8_t fieldId)
uint8_t StatisticsParser::getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
uint8_t pos = getAssignIdxByChannelField(channel, fieldId);
return _byteAssignment[pos].digits;
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
return pos->digits;
}
uint8_t StatisticsParser::getChannelCount()
std::list<ChannelType_t> StatisticsParser::getChannelTypes()
{
const byteAssign_t* b = _byteAssignment;
uint8_t cnt = 0;
for (uint8_t pos = 0; pos < _byteAssignmentCount; pos++) {
if (b[pos].ch > cnt) {
cnt = b[pos].ch;
return {
TYPE_AC,
TYPE_DC,
TYPE_INV
};
}
const char* StatisticsParser::getChannelTypeName(ChannelType_t type)
{
return channelsTypes[type];
}
std::list<ChannelNum_t> StatisticsParser::getChannelsByType(ChannelType_t type)
{
std::list<ChannelNum_t> l;
for (auto const& b : *_byteAssignment) {
if (b.type == type) {
l.push_back(b.ch);
}
}
return cnt;
l.unique();
return l;
}
uint16_t StatisticsParser::getChannelMaxPower(uint8_t channel)
uint16_t StatisticsParser::getStringMaxPower(uint8_t channel)
{
return _chanMaxPower[channel];
return _stringMaxPower[channel];
}
void StatisticsParser::setChannelMaxPower(uint8_t channel, uint16_t power)
void StatisticsParser::setStringMaxPower(uint8_t channel, uint16_t power)
{
if (channel < CH4) {
_chanMaxPower[channel] = power;
if (channel < sizeof(_stringMaxPower) / sizeof(_stringMaxPower[0])) {
_stringMaxPower[channel] = power;
}
}
@ -174,8 +177,8 @@ uint32_t StatisticsParser::getRxFailureCount()
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0)
{
float yield = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
yield += iv->getChannelFieldValue(i, FLD_YT);
for (auto& channel : iv->getChannelsByType(TYPE_DC)) {
yield += iv->getChannelFieldValue(TYPE_DC, channel, FLD_YT);
}
return yield;
}
@ -183,8 +186,8 @@ static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0)
static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0)
{
float yield = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
yield += iv->getChannelFieldValue(i, FLD_YD);
for (auto& channel : iv->getChannelsByType(TYPE_DC)) {
yield += iv->getChannelFieldValue(TYPE_DC, channel, FLD_YD);
}
return yield;
}
@ -192,14 +195,14 @@ static float calcYieldDayCh0(StatisticsParser* iv, uint8_t arg0)
// arg0 = channel of source
static float calcUdcCh(StatisticsParser* iv, uint8_t arg0)
{
return iv->getChannelFieldValue(arg0, FLD_UDC);
return iv->getChannelFieldValue(TYPE_DC, static_cast<ChannelNum_t>(arg0), FLD_UDC);
}
static float calcPowerDcCh0(StatisticsParser* iv, uint8_t arg0)
{
float dcPower = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
dcPower += iv->getChannelFieldValue(i, FLD_PDC);
for (auto& channel : iv->getChannelsByType(TYPE_DC)) {
dcPower += iv->getChannelFieldValue(TYPE_DC, channel, FLD_PDC);
}
return dcPower;
}
@ -207,15 +210,19 @@ static float calcPowerDcCh0(StatisticsParser* iv, uint8_t arg0)
// arg0 = channel
static float calcEffiencyCh0(StatisticsParser* iv, uint8_t arg0)
{
float acPower = iv->getChannelFieldValue(CH0, FLD_PAC);
float dcPower = 0;
for (uint8_t i = 1; i <= iv->getChannelCount(); i++) {
dcPower += iv->getChannelFieldValue(i, FLD_PDC);
float acPower = 0;
for (auto& channel : iv->getChannelsByType(TYPE_AC)) {
acPower += iv->getChannelFieldValue(TYPE_AC, channel, FLD_PAC);
}
float dcPower = 0;
for (auto& channel : iv->getChannelsByType(TYPE_DC)) {
dcPower += iv->getChannelFieldValue(TYPE_DC, channel, FLD_PDC);
}
if (dcPower > 0) {
return acPower / dcPower * 100.0f;
}
return 0.0;
}
@ -223,8 +230,8 @@ static float calcEffiencyCh0(StatisticsParser* iv, uint8_t arg0)
static float calcIrradiation(StatisticsParser* iv, uint8_t arg0)
{
if (NULL != iv) {
if (iv->getChannelMaxPower(arg0 - 1) > 0)
return iv->getChannelFieldValue(arg0, FLD_PDC) / iv->getChannelMaxPower(arg0 - 1) * 100.0f;
if (iv->getStringMaxPower(arg0) > 0)
return iv->getChannelFieldValue(TYPE_DC, static_cast<ChannelNum_t>(arg0), FLD_PDC) / iv->getStringMaxPower(arg0) * 100.0f;
}
return 0.0;
}

View File

@ -3,11 +3,12 @@
#include "Parser.h"
#include <Arduino.h>
#include <cstdint>
#include <list>
#define STATISTIC_PACKET_SIZE (4 * 16)
// units
enum {
enum UnitId_t {
UNIT_V = 0,
UNIT_A,
UNIT_W,
@ -22,7 +23,7 @@ enum {
const char* const units[] = { "V", "A", "W", "Wh", "kWh", "Hz", "°C", "%", "var", "" };
// field types
enum {
enum FieldId_t {
FLD_UDC = 0,
FLD_IDC,
FLD_PDC,
@ -54,7 +55,7 @@ enum {
enum { CMD_CALC = 0xffff };
// CH0 is default channel (freq, ac, temp)
enum {
enum ChannelNum_t {
CH0 = 0,
CH1,
CH2,
@ -62,10 +63,18 @@ enum {
CH4
};
enum ChannelType_t {
TYPE_AC = 0,
TYPE_DC,
TYPE_INV
};
const char* const channelsTypes[] = { "AC", "DC", "INV" };
typedef struct {
uint8_t ch; // channel 0 - 4
uint8_t fieldId; // field id
uint8_t unitId; // uint id
ChannelType_t type;
ChannelNum_t ch; // channel 0 - 4
FieldId_t fieldId; // field id
UnitId_t unitId; // uint id
uint8_t start; // pos of first byte in buffer
uint8_t num; // number of bytes in buffer
uint16_t div; // divisor / calc command
@ -78,19 +87,21 @@ public:
void clearBuffer();
void appendFragment(uint8_t offset, uint8_t* payload, uint8_t len);
void setByteAssignment(const byteAssign_t* byteAssignment, const uint8_t count);
void setByteAssignment(const std::list<byteAssign_t>* byteAssignment);
uint8_t getAssignIdxByChannelField(uint8_t channel, uint8_t fieldId);
float getChannelFieldValue(uint8_t channel, uint8_t fieldId);
bool hasChannelFieldValue(uint8_t channel, uint8_t fieldId);
const char* getChannelFieldUnit(uint8_t channel, uint8_t fieldId);
const char* getChannelFieldName(uint8_t channel, uint8_t fieldId);
uint8_t getChannelFieldDigits(uint8_t channel, uint8_t fieldId);
const byteAssign_t* getAssignmentByChannelField(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
float getChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
bool hasChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
const char* getChannelFieldUnit(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
const char* getChannelFieldName(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
uint8_t getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
uint8_t getChannelCount();
std::list<ChannelType_t> getChannelTypes();
const char* getChannelTypeName(ChannelType_t type);
std::list<ChannelNum_t> getChannelsByType(ChannelType_t type);
uint16_t getChannelMaxPower(uint8_t channel);
void setChannelMaxPower(uint8_t channel, uint16_t power);
uint16_t getStringMaxPower(uint8_t channel);
void setStringMaxPower(uint8_t channel, uint16_t power);
void resetRxFailureCount();
void incrementRxFailureCount();
@ -99,10 +110,9 @@ public:
private:
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {};
uint8_t _statisticLength = 0;
uint16_t _chanMaxPower[CH4];
uint16_t _stringMaxPower[CH4];
const byteAssign_t* _byteAssignment;
uint8_t _byteAssignmentCount;
const std::list<byteAssign_t>* _byteAssignment;
uint32_t _rxFailureCount = 0;
};

View File

@ -37,7 +37,8 @@ std::map<DisplayType_t, std::function<U8G2*(uint8_t, uint8_t, uint8_t, uint8_t)>
};
DisplayGraphicClass::DisplayGraphicClass()
{}
{
}
DisplayGraphicClass::~DisplayGraphicClass()
{
@ -88,7 +89,7 @@ void DisplayGraphicClass::printText(const char* text, uint8_t line)
break;
}
// get the font height, to calculate the textheight
// get the font height, to calculate the textheight
_dispY += (_display->getMaxCharHeight()) + 1;
// calculate the starting position of the text
@ -126,9 +127,11 @@ void DisplayGraphicClass::loop()
isprod++;
}
totalPower += inv->Statistics()->getChannelFieldValue(CH0, FLD_PAC);
totalYieldDay += inv->Statistics()->getChannelFieldValue(CH0, FLD_YD);
totalYieldTotal += inv->Statistics()->getChannelFieldValue(CH0, FLD_YT);
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) {
totalPower += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
totalYieldDay += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
totalYieldTotal += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
}
}
_display->clearBuffer();

View File

@ -65,13 +65,15 @@ void MqttHandleHassClass::publishConfig()
publishInverterBinarySensor(inv, "Producing", "status/producing", "1", "0");
// Loop all channels
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
for (uint8_t f = 0; f < DEVICE_CLS_ASSIGN_LIST_LEN; f++) {
bool clear = false;
if (c > 0 && !config.Mqtt_Hass_IndividualPanels) {
clear = true;
for (auto& t : inv->Statistics()->getChannelTypes()) {
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
for (uint8_t f = 0; f < DEVICE_CLS_ASSIGN_LIST_LEN; f++) {
bool clear = false;
if (t == TYPE_DC && !config.Mqtt_Hass_IndividualPanels) {
clear = true;
}
publishField(inv, t, c, deviceFieldAssignment[f], clear);
}
publishField(inv, c, deviceFieldAssignment[f], clear);
}
}
@ -79,32 +81,40 @@ void MqttHandleHassClass::publishConfig()
}
}
void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear)
void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, byteAssign_fieldDeviceClass_t fieldType, bool clear)
{
if (!inv->Statistics()->hasChannelFieldValue(channel, fieldType.fieldId)) {
if (!inv->Statistics()->hasChannelFieldValue(type, channel, fieldType.fieldId)) {
return;
}
String serial = inv->serialString();
String fieldName;
if (channel == CH0 && fieldType.fieldId == FLD_PDC) {
if (type == TYPE_AC && fieldType.fieldId == FLD_PDC) {
fieldName = "PowerDC";
} else {
fieldName = inv->Statistics()->getChannelFieldName(channel, fieldType.fieldId);
fieldName = inv->Statistics()->getChannelFieldName(type, channel, fieldType.fieldId);
}
String chanNum;
if (type == TYPE_DC) {
// TODO(tbnobody)
chanNum = static_cast<uint8_t>(channel) + 1;
} else {
chanNum = channel;
}
String configTopic = "sensor/dtu_" + serial
+ "/" + "ch" + String(channel) + "_" + fieldName
+ "/" + "ch" + chanNum + "_" + fieldName
+ "/config";
if (!clear) {
String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, channel, fieldType.fieldId);
String stateTopic = MqttSettings.getPrefix() + MqttHandleInverter.getTopic(inv, type, channel, fieldType.fieldId);
const char* devCls = deviceClasses[fieldType.deviceClsId];
const char* stateCls = stateClasses[fieldType.stateClsId];
String name;
if (channel == CH0) {
if (type != TYPE_DC) {
name = String(inv->name()) + " " + fieldName;
} else {
name = String(inv->name()) + " CH" + String(channel) + " " + fieldName;
@ -115,7 +125,7 @@ void MqttHandleHassClass::publishField(std::shared_ptr<InverterAbstract> inv, ui
root[F("stat_t")] = stateTopic;
root[F("uniq_id")] = serial + "_ch" + String(channel) + "_" + fieldName;
String unit_of_measure = inv->Statistics()->getChannelFieldUnit(channel, fieldType.fieldId);
String unit_of_measure = inv->Statistics()->getChannelFieldUnit(type, channel, fieldType.fieldId);
if (unit_of_measure != "") {
root[F("unit_of_meas")] = unit_of_measure;
}

View File

@ -96,15 +96,18 @@ void MqttHandleInverterClass::loop()
_lastPublishStats[i] = lastUpdate;
// Loop all channels
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
if (c > 0) {
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
if (inv_cfg != nullptr) {
MqttSettings.publish(inv->serialString() + "/" + String(c) + "/name", inv_cfg->channel[c - 1].Name);
for (auto& t : inv->Statistics()->getChannelTypes()) {
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
if (t == TYPE_DC) {
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
if (inv_cfg != nullptr) {
// TODO(tbnobody)
MqttSettings.publish(inv->serialString() + "/" + String(static_cast<uint8_t>(c) + 1) + "/name", inv_cfg->channel[c].Name);
}
}
for (uint8_t f = 0; f < sizeof(_publishFields) / sizeof(FieldId_t); f++) {
publishField(inv, t, c, _publishFields[f]);
}
}
for (uint8_t f = 0; f < sizeof(_publishFields); f++) {
publishField(inv, c, _publishFields[f]);
}
}
}
@ -116,31 +119,39 @@ void MqttHandleInverterClass::loop()
}
}
void MqttHandleInverterClass::publishField(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId)
void MqttHandleInverterClass::publishField(std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
String topic = getTopic(inv, channel, fieldId);
String topic = getTopic(inv, type, channel, fieldId);
if (topic == "") {
return;
}
MqttSettings.publish(topic, String(inv->Statistics()->getChannelFieldValue(channel, fieldId)));
MqttSettings.publish(topic, String(inv->Statistics()->getChannelFieldValue(type, channel, fieldId)));
}
String MqttHandleInverterClass::getTopic(std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId)
String MqttHandleInverterClass::getTopic(std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
{
if (!inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
if (!inv->Statistics()->hasChannelFieldValue(type, channel, fieldId)) {
return String("");
}
String chanName;
if (channel == 0 && fieldId == FLD_PDC) {
if (type == TYPE_AC && fieldId == FLD_PDC) {
chanName = "powerdc";
} else {
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
chanName = inv->Statistics()->getChannelFieldName(type, channel, fieldId);
chanName.toLowerCase();
}
return inv->serialString() + "/" + String(channel) + "/" + chanName;
String chanNum;
if (type == TYPE_DC) {
// TODO(tbnobody)
chanNum = static_cast<uint8_t>(channel) + 1;
} else {
chanNum = channel;
}
return inv->serialString() + "/" + chanNum + "/" + chanName;
}
void MqttHandleInverterClass::onMqttMessage(const espMqttClientTypes::MessageProperties& properties, const char* topic, const uint8_t* payload, size_t len, size_t index, size_t total)

View File

@ -59,7 +59,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
max_channels = INV_MAX_CHAN_COUNT;
} else {
obj[F("type")] = inv->typeName();
max_channels = inv->Statistics()->getChannelCount();
max_channels = inv->Statistics()->getChannelsByType(TYPE_DC).size();
}
JsonArray channel = obj.createNestedArray("channel");
@ -167,7 +167,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
if (inv != nullptr) {
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setChannelMaxPower(c, inverter->channel[c].MaxChannelPower);
inv->Statistics()->setStringMaxPower(c, inverter->channel[c].MaxChannelPower);
}
}
@ -296,7 +296,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
if (inv != nullptr) {
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setChannelMaxPower(c, inverter.channel[c].MaxChannelPower);
inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower);
}
}

View File

@ -63,39 +63,48 @@ void WebApiPrometheusClass::onPrometheusMetricsGet(AsyncWebServerRequest* reques
serial.c_str(), i, name, inv->Statistics()->getLastUpdate() / 1000);
// Loop all channels
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
addField(stream, serial, i, inv, c, FLD_PAC);
addField(stream, serial, i, inv, c, FLD_UAC);
addField(stream, serial, i, inv, c, FLD_IAC);
if (c == 0) {
addField(stream, serial, i, inv, c, FLD_PDC, "PowerDC");
} else {
addField(stream, serial, i, inv, c, FLD_PDC);
for (auto& t : inv->Statistics()->getChannelTypes()) {
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
addField(stream, serial, i, inv, t, c, FLD_PAC);
addField(stream, serial, i, inv, t, c, FLD_UAC);
addField(stream, serial, i, inv, t, c, FLD_IAC);
if (t == TYPE_AC) {
addField(stream, serial, i, inv, t, c, FLD_PDC, "PowerDC");
} else {
addField(stream, serial, i, inv, t, c, FLD_PDC);
}
addField(stream, serial, i, inv, t, c, FLD_UDC);
addField(stream, serial, i, inv, t, c, FLD_IDC);
addField(stream, serial, i, inv, t, c, FLD_YD);
addField(stream, serial, i, inv, t, c, FLD_YT);
addField(stream, serial, i, inv, t, c, FLD_F);
addField(stream, serial, i, inv, t, c, FLD_T);
addField(stream, serial, i, inv, t, c, FLD_PF);
addField(stream, serial, i, inv, t, c, FLD_PRA);
addField(stream, serial, i, inv, t, c, FLD_EFF);
addField(stream, serial, i, inv, t, c, FLD_IRR);
}
addField(stream, serial, i, inv, c, FLD_UDC);
addField(stream, serial, i, inv, c, FLD_IDC);
addField(stream, serial, i, inv, c, FLD_YD);
addField(stream, serial, i, inv, c, FLD_YT);
addField(stream, serial, i, inv, c, FLD_F);
addField(stream, serial, i, inv, c, FLD_T);
addField(stream, serial, i, inv, c, FLD_PF);
addField(stream, serial, i, inv, c, FLD_PRA);
addField(stream, serial, i, inv, c, FLD_EFF);
addField(stream, serial, i, inv, c, FLD_IRR);
}
}
stream->addHeader(F("Cache-Control"), F("no-cache"));
request->send(stream);
}
void WebApiPrometheusClass::addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, const char* channelName)
void WebApiPrometheusClass::addField(AsyncResponseStream* stream, String& serial, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, const char* channelName)
{
if (inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
const char* chanName = (channelName == NULL) ? inv->Statistics()->getChannelFieldName(channel, fieldId) : channelName;
if (idx == 0 && channel == 0) {
stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(channel, fieldId));
if (inv->Statistics()->hasChannelFieldValue(type, channel, fieldId)) {
const char* chanName = (channelName == NULL) ? inv->Statistics()->getChannelFieldName(type, channel, fieldId) : channelName;
if (idx == 0 && type == TYPE_AC && channel == 0) {
stream->printf("# HELP opendtu_%s in %s\n", chanName, inv->Statistics()->getChannelFieldUnit(type, channel, fieldId));
stream->printf("# TYPE opendtu_%s gauge\n", chanName);
}
stream->printf("opendtu_%s{serial=\"%s\",unit=\"%d\",name=\"%s\",channel=\"%d\"} %f\n", chanName, serial.c_str(), idx, inv->name(), channel, inv->Statistics()->getChannelFieldValue(channel, fieldId));
stream->printf("opendtu_%s{serial=\"%s\",unit=\"%d\",name=\"%s\",type=\"%s\",channel=\"%d\"} %f\n",
chanName,
serial.c_str(),
idx,
inv->name(),
inv->Statistics()->getChannelTypeName(type),
channel,
inv->Statistics()->getChannelFieldValue(type, channel, fieldId));
}
}

View File

@ -111,36 +111,39 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
}
// Loop all channels
for (uint8_t c = 0; c <= inv->Statistics()->getChannelCount(); c++) {
if (c > 0) {
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
if (inv_cfg != nullptr) {
invObject[String(c)][F("name")]["u"] = inv_cfg->channel[c - 1].Name;
for (auto& t : inv->Statistics()->getChannelTypes()) {
JsonObject chanTypeObj = invObject.createNestedObject(inv->Statistics()->getChannelTypeName(t));
for (auto& c : inv->Statistics()->getChannelsByType(t)) {
if (t == TYPE_DC) {
INVERTER_CONFIG_T* inv_cfg = Configuration.getInverterConfig(inv->serial());
if (inv_cfg != nullptr) {
chanTypeObj[String(static_cast<uint8_t>(c))][F("name")]["u"] = inv_cfg->channel[c].Name;
}
}
addField(chanTypeObj, i, inv, t, c, FLD_PAC);
addField(chanTypeObj, i, inv, t, c, FLD_UAC);
addField(chanTypeObj, i, inv, t, c, FLD_IAC);
if (t == TYPE_AC) {
addField(chanTypeObj, i, inv, t, c, FLD_PDC, F("Power DC"));
} else {
addField(chanTypeObj, i, inv, t, c, FLD_PDC);
}
addField(chanTypeObj, i, inv, t, c, FLD_UDC);
addField(chanTypeObj, i, inv, t, c, FLD_IDC);
addField(chanTypeObj, i, inv, t, c, FLD_YD);
addField(chanTypeObj, i, inv, t, c, FLD_YT);
addField(chanTypeObj, i, inv, t, c, FLD_F);
addField(chanTypeObj, i, inv, t, c, FLD_T);
addField(chanTypeObj, i, inv, t, c, FLD_PF);
addField(chanTypeObj, i, inv, t, c, FLD_PRA);
addField(chanTypeObj, i, inv, t, c, FLD_EFF);
if (t == TYPE_DC && inv->Statistics()->getStringMaxPower(c) > 0) {
addField(chanTypeObj, i, inv, t, c, FLD_IRR);
}
}
addField(invObject, i, inv, c, FLD_PAC);
addField(invObject, i, inv, c, FLD_UAC);
addField(invObject, i, inv, c, FLD_IAC);
if (c == 0) {
addField(invObject, i, inv, c, FLD_PDC, F("Power DC"));
} else {
addField(invObject, i, inv, c, FLD_PDC);
}
addField(invObject, i, inv, c, FLD_UDC);
addField(invObject, i, inv, c, FLD_IDC);
addField(invObject, i, inv, c, FLD_YD);
addField(invObject, i, inv, c, FLD_YT);
addField(invObject, i, inv, c, FLD_F);
addField(invObject, i, inv, c, FLD_T);
addField(invObject, i, inv, c, FLD_PF);
addField(invObject, i, inv, c, FLD_PRA);
addField(invObject, i, inv, c, FLD_EFF);
if (c > 0 && inv->Statistics()->getChannelMaxPower(c - 1) > 0) {
addField(invObject, i, inv, c, FLD_IRR);
}
}
if (inv->Statistics()->hasChannelFieldValue(CH0, FLD_EVT_LOG)) {
if (inv->Statistics()->hasChannelFieldValue(TYPE_INV, CH0, FLD_EVT_LOG)) {
invObject[F("events")] = inv->EventLog()->getEntryCount();
} else {
invObject[F("events")] = -1;
@ -150,9 +153,11 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
_newestInverterTimestamp = inv->Statistics()->getLastUpdate();
}
totalPower += inv->Statistics()->getChannelFieldValue(CH0, FLD_PAC);
totalYieldDay += inv->Statistics()->getChannelFieldValue(CH0, FLD_YD);
totalYieldTotal += inv->Statistics()->getChannelFieldValue(CH0, FLD_YT);
for (auto& c : inv->Statistics()->getChannelsByType(TYPE_AC)) {
totalPower += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_PAC);
totalYieldDay += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YD);
totalYieldTotal += inv->Statistics()->getChannelFieldValue(TYPE_AC, c, FLD_YT);
}
}
JsonObject totalObj = root.createNestedObject("total");
@ -172,18 +177,20 @@ void WebApiWsLiveClass::generateJsonResponse(JsonVariant& root)
}
}
void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, uint8_t channel, uint8_t fieldId, String topic)
void WebApiWsLiveClass::addField(JsonObject& root, uint8_t idx, std::shared_ptr<InverterAbstract> inv, ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, String topic)
{
if (inv->Statistics()->hasChannelFieldValue(channel, fieldId)) {
if (inv->Statistics()->hasChannelFieldValue(type, channel, fieldId)) {
String chanName;
if (topic == "") {
chanName = inv->Statistics()->getChannelFieldName(channel, fieldId);
chanName = inv->Statistics()->getChannelFieldName(type, channel, fieldId);
} else {
chanName = topic;
}
root[String(channel)][chanName]["v"] = inv->Statistics()->getChannelFieldValue(channel, fieldId);
root[String(channel)][chanName]["u"] = inv->Statistics()->getChannelFieldUnit(channel, fieldId);
root[String(channel)][chanName]["d"] = inv->Statistics()->getChannelFieldDigits(channel, fieldId);
String chanNum;
chanNum = channel;
root[chanNum][chanName]["v"] = inv->Statistics()->getChannelFieldValue(type, channel, fieldId);
root[chanNum][chanName]["u"] = inv->Statistics()->getChannelFieldUnit(type, channel, fieldId);
root[chanNum][chanName]["d"] = inv->Statistics()->getChannelFieldDigits(type, channel, fieldId);
}
}

View File

@ -149,7 +149,7 @@ void setup()
if (inv != nullptr) {
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
inv->Statistics()->setChannelMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
}
}
MessageOutput.println(F(" done"));

View File

@ -1,11 +1,21 @@
<template>
<div class="card" :class="{ 'border-info': channelNumber == 0 }">
<div v-if="channelNumber >= 1" class="card-header">
<template v-if="channelData.name.u != ''">{{ channelData.name.u }}</template>
<template v-else>{{ $t('inverterchannelinfo.String', { num: channelNumber }) }}</template>
<div class="card" :class="{
'border-info': channelType == 'AC',
'border-secondary': channelType == 'INV'
}">
<div v-if="channelType == 'INV'" class="card-header text-bg-secondary">
{{ $t('inverterchannelinfo.General') }}
</div>
<div v-if="channelNumber == 0" class="card-header text-bg-info">
{{ $t('inverterchannelinfo.Phase', { num: channelNumber + 1 }) }}</div>
<div v-if="channelType == 'DC'" class="card-header">
<template v-if="channelData.name.u != ''">{{ channelData.name.u }}</template>
<template v-else>{{ $t('inverterchannelinfo.String', { num: channelNumber + 1 }) }}</template>
</div>
<div v-if="channelType == 'AC'" class="card-header text-bg-info">
{{ $t('inverterchannelinfo.Phase', { num: channelNumber + 1 }) }}
</div>
<div class="card-body">
<table class="table table-striped table-hover">
<thead>
@ -21,10 +31,10 @@
<th scope="row">{{ $t('inverterchannelproperty.' + key) }}</th>
<td style="text-align: right">
{{ $n(property.v, 'decimal', {
minimumFractionDigits: property.d,
maximumFractionDigits: property.d})
minimumFractionDigits: property.d,
maximumFractionDigits: property.d})
}}
</td>
</td>
<td>{{ property.u }}</td>
</template>
</tr>
@ -41,6 +51,7 @@ import { defineComponent, type PropType } from 'vue';
export default defineComponent({
props: {
channelData: { type: Object as PropType<InverterStatistics>, required: true },
channelType: { type: String, required: true },
channelNumber: { type: Number, required: true },
},
});

View File

@ -263,6 +263,7 @@
"inverterchannelinfo": {
"String": "String {num}",
"Phase": "Phase {num}",
"General": "Allgemein",
"Property": "Eigenschaft",
"Value": "Wert",
"Unit": "Einheit"

View File

@ -263,6 +263,7 @@
"inverterchannelinfo": {
"String": "String {num}",
"Phase": "Phase {num}",
"General": "General",
"Property": "Property",
"Value": "Value",
"Unit": "Unit"

View File

@ -263,6 +263,7 @@
"inverterchannelinfo": {
"String": "Ligne {num}",
"Phase": "Phase {num}",
"General": "General",
"Property": "Propriété",
"Value": "Valeur",
"Unit": "Unité"

View File

@ -29,7 +29,9 @@ export interface Inverter {
limit_relative: number;
limit_absolute: number;
events: number;
[key: number]: InverterStatistics;
AC: InverterStatistics[];
DC: InverterStatistics[];
INV: InverterStatistics[];
}
export interface Total {

View File

@ -92,11 +92,12 @@
</div>
</div>
<div class="card-body">
<div class="row flex-row-reverse flex-wrap-reverse align-items-end g-3">
<template v-for="channel in 5" :key="channel">
<div v-if="inverter[channel - 1]" :class="`col order-${5 - channel}`">
<InverterChannelInfo :channelData="inverter[channel - 1]"
:channelNumber="channel - 1" />
<div class="row flex-row-reverse flex-wrap-reverse g-3">
<template v-for="chanType in [{obj: inverter.INV, name: 'INV'}, {obj: inverter.AC, name: 'AC'}, {obj: inverter.DC, name: 'DC'}].reverse()">
<div v-for="channel in Object.keys(chanType.obj).sort().reverse().map(x=>+x)" :key="channel" class="col">
<InverterChannelInfo :channelData="chanType.obj[channel]"
:channelType="chanType.name"
:channelNumber="channel" />
</div>
</template>
</div>