Skip to content

Commit 06651f3

Browse files
committed
Feature: First version of GridProfile Parser which shows all values contained in the profile.
1 parent f851aca commit 06651f3

11 files changed

Lines changed: 491 additions & 18 deletions

File tree

include/WebApi_gridprofile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class WebApiGridProfileClass {
1010

1111
private:
1212
void onGridProfileStatus(AsyncWebServerRequest* request);
13+
void onGridProfileRawdata(AsyncWebServerRequest* request);
1314

1415
AsyncWebServer* _server;
1516
};

lib/Hoymiles/src/parser/GridProfileParser.cpp

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include "GridProfileParser.h"
66
#include "../Hoymiles.h"
77
#include <cstring>
8+
#include <frozen/map.h>
9+
#include <frozen/string.h>
810

911
const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> GridProfileParser::_profileTypes = { {
1012
{ 0x02, 0x00, "no data (yet)" },
@@ -16,6 +18,263 @@ const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> GridProfileParser::_pr
1618
{ 0x37, 0x00, "Swiss - CH_NA EEA-NE7-CH2020" },
1719
} };
1820

21+
constexpr frozen::map<uint8_t, frozen::string, 12> profile_section = {
22+
{ 0x00, "Voltage (H/LVRT)" },
23+
{ 0x10, "Frequency (H/LFRT)" },
24+
{ 0x20, "Island Detection (ID)" },
25+
{ 0x30, "Reconnection (RT)" },
26+
{ 0x40, "Ramp Rates (RR)" },
27+
{ 0x50, "Frequency Watt (FW)" },
28+
{ 0x60, "Volt Watt (VW)" },
29+
{ 0x70, "Active Power Control (APC)" },
30+
{ 0x80, "Volt Var (VV)" },
31+
{ 0x90, "Specified Power Factor (SPF)" },
32+
{ 0xA0, "Reactive Power Control (RPC)" },
33+
{ 0xB0, "Watt Power Factor (WPF)" },
34+
};
35+
36+
struct GridProfilePartialValue_t {
37+
frozen::string Name;
38+
frozen::string Unit;
39+
uint8_t Dividor;
40+
};
41+
42+
constexpr GridProfilePartialValue_t make_value(frozen::string Name, frozen::string Unit, uint8_t divisor)
43+
{
44+
GridProfilePartialValue_t v = { Name, Unit, divisor };
45+
return v;
46+
}
47+
48+
constexpr frozen::map<uint8_t, GridProfilePartialValue_t, 0x38> value_names = {
49+
{ 0x01, make_value("Nominale Voltage (NV)", "V", 10) },
50+
{ 0x02, make_value("Low Voltage 1 (LV1)", "V", 10) },
51+
{ 0x03, make_value("LV1 Maximum Trip Time (MTT)", "s", 10) },
52+
{ 0x04, make_value("High Voltage 1 (HV1)", "V", 10) },
53+
{ 0x05, make_value("HV1 Maximum Trip Time (MTT)", "s", 10) },
54+
{ 0x06, make_value("Low Voltage 2 (LV2)", "V", 10) },
55+
{ 0x07, make_value("LV2 Maximum Trip Time (MTT)", "s", 10) },
56+
{ 0x08, make_value("High Voltage 2 (HV2)", "V", 10) },
57+
{ 0x09, make_value("HV2 Maximum Trip Time (MTT)", "s", 10) },
58+
{ 0x0a, make_value("10mins Average High Voltage (AHV)", "V", 10) },
59+
{ 0x0b, make_value("High Voltage 3 (HV3)", "V", 10) },
60+
{ 0x0c, make_value("HV3 Maximum Trip Time (MTT)", "s", 10) },
61+
{ 0x0d, make_value("Nominal Frequency", "Hz", 100) },
62+
{ 0x0e, make_value("Low Frequency 1 (LF1)", "Hz", 100) },
63+
{ 0x0f, make_value("LF1 Maximum Trip Time (MTT)", "s", 10) },
64+
{ 0x10, make_value("High Frequency 1 (HF1)", "Hz", 100) },
65+
{ 0x11, make_value("HF1 Maximum Trip time (MTT)", "s", 10) },
66+
{ 0x12, make_value("Low Frequency 2 (LF2)", "Hz", 100) },
67+
{ 0x13, make_value("LF2 Maximum Trip Time (MTT)", "s", 10) },
68+
{ 0x14, make_value("High Frequency 2 (HF2)", "Hz", 100) },
69+
{ 0x15, make_value("HF2 Maximum Trip time (MTT)", "s", 10) },
70+
{ 0x16, make_value("ID Function Activated", "bool", 1) },
71+
{ 0x17, make_value("Reconnect Time (RT)", "s", 10) },
72+
{ 0x18, make_value("Reconnect High Voltage (RHV)", "V", 10) },
73+
{ 0x19, make_value("Reconnect Low Voltage (RLV)", "V", 10) },
74+
{ 0x1a, make_value("Reconnect High Frequency (RHF)", "Hz", 100) },
75+
{ 0x1b, make_value("Reconnect Low Frequency (RLF)", "Hz", 100) },
76+
{ 0x1c, make_value("Normal Ramp up Rate(RUR_NM)", "Rated%/s", 100) },
77+
{ 0x1d, make_value("Soft Start Ramp up Rate (RUR_SS)", "Rated%/s", 100) },
78+
{ 0x1e, make_value("FW Function Activated", "bool", 1) },
79+
{ 0x1f, make_value("Start of Frequency Watt Droop (Fstart)", "Hz", 100) },
80+
{ 0x20, make_value("FW Droop Slope (Kpower_Freq)", "Pn%/Hz", 10) },
81+
{ 0x21, make_value("Recovery Ramp Rate (RRR)", "Pn%/s", 100) },
82+
{ 0x22, make_value("Recovery High Frequency (RVHF)", "Hz", 100) },
83+
{ 0x23, make_value("Recovery Low Frequency (RVLF)", "Hz", 100) },
84+
{ 0x24, make_value("VW Function Activated", "bool", 1) },
85+
{ 0x25, make_value("Start of Voltage Watt Droop (Vstart)", "V", 10) },
86+
{ 0x26, make_value("End of Voltage Watt Droop (Vend)", "V", 10) },
87+
{ 0x27, make_value("Droop Slope (Kpower_Volt)", "Pn%/V", 100) },
88+
{ 0x28, make_value("APC Function Activated", "bool", 1) },
89+
{ 0x29, make_value("Power Ramp Rate (PRR)", "Pn%/s", 100) },
90+
{ 0x2a, make_value("VV Function Activated", "bool", 1) },
91+
{ 0x2b, make_value("Voltage Set Point V1", "V", 10) },
92+
{ 0x2c, make_value("Reactive Set Point Q1", "%Pn", 10) },
93+
{ 0x2d, make_value("Voltage Set Point V2", "V", 10) },
94+
{ 0x2e, make_value("Voltage Set Point V3", "V", 10) },
95+
{ 0x2f, make_value("Voltage Set Point V4", "V", 10) },
96+
{ 0x30, make_value("Reactive Set Point Q4", "%Pn", 10) },
97+
{ 0x31, make_value("Setting Time (Tr)", "s", 10) },
98+
{ 0x32, make_value("SPF Function Activated", "bool", 1) },
99+
{ 0x33, make_value("Power Factor (PF)", "", 100) },
100+
{ 0x34, make_value("RPC Function Activated", "bool", 1) },
101+
{ 0x35, make_value("Reactive Power (VAR)", "%Sn", 1) },
102+
{ 0x36, make_value("WPF Function Activated", "bool", 1) },
103+
{ 0x37, make_value("Start of Power of WPF (Pstart)", "%Pn", 10) },
104+
{ 0x38, make_value("Power Factor ar Rated Power (PFRP)", "", 100) },
105+
};
106+
107+
const std::array<const GridProfileValue_t, SECTION_VALUE_COUNT> GridProfileParser::_profile_values = { {
108+
// Voltage (H/LVRT)
109+
// Version 0x00
110+
{ 0x00, 0x00, 0x01 },
111+
{ 0x00, 0x00, 0x02 },
112+
{ 0x00, 0x00, 0x03 },
113+
{ 0x00, 0x00, 0x04 },
114+
{ 0x00, 0x00, 0x05 },
115+
116+
// Version 0x03
117+
{ 0x00, 0x03, 0x01 },
118+
{ 0x00, 0x03, 0x02 },
119+
{ 0x00, 0x03, 0x03 },
120+
{ 0x00, 0x03, 0x05 },
121+
{ 0x00, 0x03, 0x06 },
122+
{ 0x00, 0x03, 0x07 },
123+
{ 0x00, 0x03, 0x08 },
124+
{ 0x00, 0x03, 0x09 },
125+
126+
// Version 0x0a
127+
{ 0x00, 0x0a, 0x01 },
128+
{ 0x00, 0x0a, 0x02 },
129+
{ 0x00, 0x0a, 0x03 },
130+
{ 0x00, 0x0a, 0x04 },
131+
{ 0x00, 0x0a, 0x05 },
132+
{ 0x00, 0x0a, 0x06 },
133+
{ 0x00, 0x0a, 0x07 },
134+
{ 0x00, 0x0a, 0x0a },
135+
136+
// Version 0x0b
137+
{ 0x00, 0x0b, 0x01 },
138+
{ 0x00, 0x0b, 0x02 },
139+
{ 0x00, 0x0b, 0x03 },
140+
{ 0x00, 0x0b, 0x04 },
141+
{ 0x00, 0x0b, 0x05 },
142+
{ 0x00, 0x0b, 0x06 },
143+
{ 0x00, 0x0b, 0x07 },
144+
{ 0x00, 0x0b, 0x08 },
145+
{ 0x00, 0x0b, 0x09 },
146+
{ 0x00, 0x0b, 0x0a },
147+
148+
// Version 0x0c
149+
{ 0x00, 0x0c, 0x01 },
150+
{ 0x00, 0x0c, 0x02 },
151+
{ 0x00, 0x0c, 0x03 },
152+
{ 0x00, 0x0c, 0x04 },
153+
{ 0x00, 0x0c, 0x05 },
154+
{ 0x00, 0x0c, 0x06 },
155+
{ 0x00, 0x0c, 0x07 },
156+
{ 0x00, 0x0c, 0x08 },
157+
{ 0x00, 0x0c, 0x09 },
158+
{ 0x00, 0x0c, 0x0b },
159+
{ 0x00, 0x0c, 0x0c },
160+
{ 0x00, 0x0c, 0x0a },
161+
162+
// Frequency (H/LFRT)
163+
// Version 0x00
164+
{ 0x10, 0x00, 0x0d },
165+
{ 0x10, 0x00, 0x0e },
166+
{ 0x10, 0x00, 0x0f },
167+
{ 0x10, 0x00, 0x10 },
168+
{ 0x10, 0x00, 0x11 },
169+
170+
// Version 0x03
171+
{ 0x10, 0x03, 0x0d },
172+
{ 0x10, 0x03, 0x0e },
173+
{ 0x10, 0x03, 0x0f },
174+
{ 0x10, 0x03, 0x10 },
175+
{ 0x10, 0x03, 0x11 },
176+
{ 0x10, 0x03, 0x12 },
177+
{ 0x10, 0x03, 0x13 },
178+
{ 0x10, 0x03, 0x14 },
179+
{ 0x10, 0x03, 0x15 },
180+
181+
// Island Detection (ID)
182+
// Version 0x00
183+
{ 0x20, 0x00, 0x16 },
184+
185+
// Reconnection (RT)
186+
// Version 0x03
187+
{ 0x30, 0x03, 0x17 },
188+
{ 0x30, 0x03, 0x18 },
189+
{ 0x30, 0x03, 0x19 },
190+
{ 0x30, 0x03, 0x1a },
191+
{ 0x30, 0x03, 0x1b },
192+
193+
// Ramp Rates (RR)
194+
// Version 0x00
195+
{ 0x40, 0x00, 0x1c },
196+
{ 0x40, 0x00, 0x1d },
197+
198+
// Frequency Watt (FW)
199+
// Version 0x00
200+
{ 0x50, 0x00, 0x1e },
201+
{ 0x50, 0x00, 0x1f },
202+
{ 0x50, 0x00, 0x20 },
203+
{ 0x50, 0x00, 0x21 },
204+
205+
// Version 0x01
206+
{ 0x50, 0x01, 0x1e },
207+
{ 0x50, 0x01, 0x1f },
208+
{ 0x50, 0x01, 0x20 },
209+
{ 0x50, 0x01, 0x21 },
210+
{ 0x50, 0x01, 0x22 },
211+
212+
// Version 0x08
213+
{ 0x50, 0x08, 0x1e },
214+
{ 0x50, 0x08, 0x1f },
215+
{ 0x50, 0x08, 0x20 },
216+
{ 0x50, 0x08, 0x21 },
217+
{ 0x50, 0x08, 0x22 },
218+
{ 0x50, 0x08, 0x23 },
219+
220+
// Volt Watt (VW)
221+
// Version 0x00
222+
{ 0x60, 0x00, 0x24 },
223+
{ 0x60, 0x00, 0x25 },
224+
{ 0x60, 0x00, 0x26 },
225+
{ 0x60, 0x00, 0x27 },
226+
227+
// Version 0x04
228+
{ 0x60, 0x04, 0x24 },
229+
{ 0x60, 0x04, 0x25 },
230+
{ 0x60, 0x04, 0x26 },
231+
{ 0x60, 0x04, 0x27 },
232+
233+
// Active Power Control (APC)
234+
// Version 0x00
235+
{ 0x70, 0x00, 0x28 },
236+
237+
// Version 0x02
238+
{ 0x70, 0x02, 0x28 },
239+
{ 0x70, 0x02, 0x29 },
240+
241+
// Volt Var (VV)
242+
// Version 0x00
243+
{ 0x80, 0x00, 0x2a },
244+
{ 0x80, 0x00, 0x2b },
245+
{ 0x80, 0x00, 0x2c },
246+
{ 0x80, 0x00, 0x2d },
247+
{ 0x80, 0x00, 0x2e },
248+
{ 0x80, 0x00, 0x2f },
249+
{ 0x80, 0x00, 0x30 },
250+
251+
// Version 0x01
252+
{ 0x80, 0x01, 0x2a },
253+
{ 0x80, 0x01, 0x2b },
254+
{ 0x80, 0x01, 0x2c },
255+
{ 0x80, 0x01, 0x2d },
256+
{ 0x80, 0x01, 0x2e },
257+
{ 0x80, 0x01, 0x2f },
258+
{ 0x80, 0x01, 0x30 },
259+
{ 0x80, 0x01, 0x31 },
260+
261+
// Specified Power Factor (SPF)
262+
// Version 0x00
263+
{ 0x90, 0x00, 0x32 },
264+
{ 0x90, 0x00, 0x33 },
265+
266+
// Reactive Power Control (RPC)
267+
// Version 0x02
268+
{ 0xa0, 0x02, 0x34 },
269+
{ 0xa0, 0x02, 0x35 },
270+
271+
// Watt Power Factor (WPF)
272+
// Version 0x00
273+
{ 0xb0, 0x00, 0x36 },
274+
{ 0xb0, 0x00, 0x37 },
275+
{ 0xb0, 0x00, 0x38 },
276+
} };
277+
19278
GridProfileParser::GridProfileParser()
20279
: Parser()
21280
{
@@ -51,7 +310,9 @@ String GridProfileParser::getProfileName()
51310
String GridProfileParser::getProfileVersion()
52311
{
53312
char buffer[10];
313+
HOY_SEMAPHORE_TAKE();
54314
snprintf(buffer, sizeof(buffer), "%d.%d.%d", (_payloadGridProfile[2] >> 4) & 0x0f, _payloadGridProfile[2] & 0x0f, _payloadGridProfile[3]);
315+
HOY_SEMAPHORE_GIVE();
55316
return buffer;
56317
}
57318

@@ -65,3 +326,70 @@ std::vector<uint8_t> GridProfileParser::getRawData()
65326
HOY_SEMAPHORE_GIVE();
66327
return ret;
67328
}
329+
330+
std::list<GridProfileSection_t> GridProfileParser::getProfile()
331+
{
332+
std::list<GridProfileSection_t> l;
333+
334+
if (_gridProfileLength > 4) {
335+
uint16_t pos = 4;
336+
do {
337+
uint8_t section_id = _payloadGridProfile[pos];
338+
uint8_t section_version = _payloadGridProfile[pos + 1];
339+
int8_t section_start = getSectionStart(section_id, section_version);
340+
uint8_t section_size = getSectionSize(section_id, section_version);
341+
pos += 2;
342+
343+
GridProfileSection_t section;
344+
try {
345+
section.SectionName = profile_section.at(section_id).data();
346+
} catch (const std::out_of_range&) {
347+
section.SectionName = "Unknown";
348+
break;
349+
}
350+
351+
for (uint8_t val_id = 0; val_id < section_size; val_id++) {
352+
auto value_setting = value_names.at(_profile_values[section_start + val_id].Type);
353+
354+
float value = (_payloadGridProfile[pos] << 8) | _payloadGridProfile[pos + 1];
355+
value /= value_setting.Dividor;
356+
357+
GridProfileItem_t v;
358+
v.Name = value_setting.Name.data();
359+
v.Unit = value_setting.Unit.data();
360+
v.Value = value;
361+
section.items.push_back(v);
362+
363+
pos += 2;
364+
}
365+
366+
l.push_back(section);
367+
368+
} while (pos < _gridProfileLength);
369+
}
370+
371+
return l;
372+
}
373+
374+
uint8_t GridProfileParser::getSectionSize(uint8_t section_id, uint8_t section_version)
375+
{
376+
uint8_t count = 0;
377+
for (auto& values : _profile_values) {
378+
if (values.Section == section_id && values.Version == section_version) {
379+
count++;
380+
}
381+
}
382+
return count;
383+
}
384+
385+
int8_t GridProfileParser::getSectionStart(uint8_t section_id, uint8_t section_version)
386+
{
387+
uint8_t count = -1;
388+
for (auto& values : _profile_values) {
389+
count++;
390+
if (values.Section == section_id && values.Version == section_version) {
391+
break;
392+
}
393+
}
394+
return count;
395+
}

0 commit comments

Comments
 (0)