Feature: First version of GridProfile Parser which shows all values contained in the profile.
This commit is contained in:
parent
f851acab4d
commit
06651f365a
@ -10,6 +10,7 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void onGridProfileStatus(AsyncWebServerRequest* request);
|
void onGridProfileStatus(AsyncWebServerRequest* request);
|
||||||
|
void onGridProfileRawdata(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
AsyncWebServer* _server;
|
AsyncWebServer* _server;
|
||||||
};
|
};
|
||||||
@ -5,6 +5,8 @@
|
|||||||
#include "GridProfileParser.h"
|
#include "GridProfileParser.h"
|
||||||
#include "../Hoymiles.h"
|
#include "../Hoymiles.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <frozen/map.h>
|
||||||
|
#include <frozen/string.h>
|
||||||
|
|
||||||
const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> GridProfileParser::_profileTypes = { {
|
const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> GridProfileParser::_profileTypes = { {
|
||||||
{ 0x02, 0x00, "no data (yet)" },
|
{ 0x02, 0x00, "no data (yet)" },
|
||||||
@ -16,6 +18,263 @@ const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> GridProfileParser::_pr
|
|||||||
{ 0x37, 0x00, "Swiss - CH_NA EEA-NE7-CH2020" },
|
{ 0x37, 0x00, "Swiss - CH_NA EEA-NE7-CH2020" },
|
||||||
} };
|
} };
|
||||||
|
|
||||||
|
constexpr frozen::map<uint8_t, frozen::string, 12> profile_section = {
|
||||||
|
{ 0x00, "Voltage (H/LVRT)" },
|
||||||
|
{ 0x10, "Frequency (H/LFRT)" },
|
||||||
|
{ 0x20, "Island Detection (ID)" },
|
||||||
|
{ 0x30, "Reconnection (RT)" },
|
||||||
|
{ 0x40, "Ramp Rates (RR)" },
|
||||||
|
{ 0x50, "Frequency Watt (FW)" },
|
||||||
|
{ 0x60, "Volt Watt (VW)" },
|
||||||
|
{ 0x70, "Active Power Control (APC)" },
|
||||||
|
{ 0x80, "Volt Var (VV)" },
|
||||||
|
{ 0x90, "Specified Power Factor (SPF)" },
|
||||||
|
{ 0xA0, "Reactive Power Control (RPC)" },
|
||||||
|
{ 0xB0, "Watt Power Factor (WPF)" },
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GridProfilePartialValue_t {
|
||||||
|
frozen::string Name;
|
||||||
|
frozen::string Unit;
|
||||||
|
uint8_t Dividor;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr GridProfilePartialValue_t make_value(frozen::string Name, frozen::string Unit, uint8_t divisor)
|
||||||
|
{
|
||||||
|
GridProfilePartialValue_t v = { Name, Unit, divisor };
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr frozen::map<uint8_t, GridProfilePartialValue_t, 0x38> value_names = {
|
||||||
|
{ 0x01, make_value("Nominale Voltage (NV)", "V", 10) },
|
||||||
|
{ 0x02, make_value("Low Voltage 1 (LV1)", "V", 10) },
|
||||||
|
{ 0x03, make_value("LV1 Maximum Trip Time (MTT)", "s", 10) },
|
||||||
|
{ 0x04, make_value("High Voltage 1 (HV1)", "V", 10) },
|
||||||
|
{ 0x05, make_value("HV1 Maximum Trip Time (MTT)", "s", 10) },
|
||||||
|
{ 0x06, make_value("Low Voltage 2 (LV2)", "V", 10) },
|
||||||
|
{ 0x07, make_value("LV2 Maximum Trip Time (MTT)", "s", 10) },
|
||||||
|
{ 0x08, make_value("High Voltage 2 (HV2)", "V", 10) },
|
||||||
|
{ 0x09, make_value("HV2 Maximum Trip Time (MTT)", "s", 10) },
|
||||||
|
{ 0x0a, make_value("10mins Average High Voltage (AHV)", "V", 10) },
|
||||||
|
{ 0x0b, make_value("High Voltage 3 (HV3)", "V", 10) },
|
||||||
|
{ 0x0c, make_value("HV3 Maximum Trip Time (MTT)", "s", 10) },
|
||||||
|
{ 0x0d, make_value("Nominal Frequency", "Hz", 100) },
|
||||||
|
{ 0x0e, make_value("Low Frequency 1 (LF1)", "Hz", 100) },
|
||||||
|
{ 0x0f, make_value("LF1 Maximum Trip Time (MTT)", "s", 10) },
|
||||||
|
{ 0x10, make_value("High Frequency 1 (HF1)", "Hz", 100) },
|
||||||
|
{ 0x11, make_value("HF1 Maximum Trip time (MTT)", "s", 10) },
|
||||||
|
{ 0x12, make_value("Low Frequency 2 (LF2)", "Hz", 100) },
|
||||||
|
{ 0x13, make_value("LF2 Maximum Trip Time (MTT)", "s", 10) },
|
||||||
|
{ 0x14, make_value("High Frequency 2 (HF2)", "Hz", 100) },
|
||||||
|
{ 0x15, make_value("HF2 Maximum Trip time (MTT)", "s", 10) },
|
||||||
|
{ 0x16, make_value("ID Function Activated", "bool", 1) },
|
||||||
|
{ 0x17, make_value("Reconnect Time (RT)", "s", 10) },
|
||||||
|
{ 0x18, make_value("Reconnect High Voltage (RHV)", "V", 10) },
|
||||||
|
{ 0x19, make_value("Reconnect Low Voltage (RLV)", "V", 10) },
|
||||||
|
{ 0x1a, make_value("Reconnect High Frequency (RHF)", "Hz", 100) },
|
||||||
|
{ 0x1b, make_value("Reconnect Low Frequency (RLF)", "Hz", 100) },
|
||||||
|
{ 0x1c, make_value("Normal Ramp up Rate(RUR_NM)", "Rated%/s", 100) },
|
||||||
|
{ 0x1d, make_value("Soft Start Ramp up Rate (RUR_SS)", "Rated%/s", 100) },
|
||||||
|
{ 0x1e, make_value("FW Function Activated", "bool", 1) },
|
||||||
|
{ 0x1f, make_value("Start of Frequency Watt Droop (Fstart)", "Hz", 100) },
|
||||||
|
{ 0x20, make_value("FW Droop Slope (Kpower_Freq)", "Pn%/Hz", 10) },
|
||||||
|
{ 0x21, make_value("Recovery Ramp Rate (RRR)", "Pn%/s", 100) },
|
||||||
|
{ 0x22, make_value("Recovery High Frequency (RVHF)", "Hz", 100) },
|
||||||
|
{ 0x23, make_value("Recovery Low Frequency (RVLF)", "Hz", 100) },
|
||||||
|
{ 0x24, make_value("VW Function Activated", "bool", 1) },
|
||||||
|
{ 0x25, make_value("Start of Voltage Watt Droop (Vstart)", "V", 10) },
|
||||||
|
{ 0x26, make_value("End of Voltage Watt Droop (Vend)", "V", 10) },
|
||||||
|
{ 0x27, make_value("Droop Slope (Kpower_Volt)", "Pn%/V", 100) },
|
||||||
|
{ 0x28, make_value("APC Function Activated", "bool", 1) },
|
||||||
|
{ 0x29, make_value("Power Ramp Rate (PRR)", "Pn%/s", 100) },
|
||||||
|
{ 0x2a, make_value("VV Function Activated", "bool", 1) },
|
||||||
|
{ 0x2b, make_value("Voltage Set Point V1", "V", 10) },
|
||||||
|
{ 0x2c, make_value("Reactive Set Point Q1", "%Pn", 10) },
|
||||||
|
{ 0x2d, make_value("Voltage Set Point V2", "V", 10) },
|
||||||
|
{ 0x2e, make_value("Voltage Set Point V3", "V", 10) },
|
||||||
|
{ 0x2f, make_value("Voltage Set Point V4", "V", 10) },
|
||||||
|
{ 0x30, make_value("Reactive Set Point Q4", "%Pn", 10) },
|
||||||
|
{ 0x31, make_value("Setting Time (Tr)", "s", 10) },
|
||||||
|
{ 0x32, make_value("SPF Function Activated", "bool", 1) },
|
||||||
|
{ 0x33, make_value("Power Factor (PF)", "", 100) },
|
||||||
|
{ 0x34, make_value("RPC Function Activated", "bool", 1) },
|
||||||
|
{ 0x35, make_value("Reactive Power (VAR)", "%Sn", 1) },
|
||||||
|
{ 0x36, make_value("WPF Function Activated", "bool", 1) },
|
||||||
|
{ 0x37, make_value("Start of Power of WPF (Pstart)", "%Pn", 10) },
|
||||||
|
{ 0x38, make_value("Power Factor ar Rated Power (PFRP)", "", 100) },
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::array<const GridProfileValue_t, SECTION_VALUE_COUNT> GridProfileParser::_profile_values = { {
|
||||||
|
// Voltage (H/LVRT)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x00, 0x00, 0x01 },
|
||||||
|
{ 0x00, 0x00, 0x02 },
|
||||||
|
{ 0x00, 0x00, 0x03 },
|
||||||
|
{ 0x00, 0x00, 0x04 },
|
||||||
|
{ 0x00, 0x00, 0x05 },
|
||||||
|
|
||||||
|
// Version 0x03
|
||||||
|
{ 0x00, 0x03, 0x01 },
|
||||||
|
{ 0x00, 0x03, 0x02 },
|
||||||
|
{ 0x00, 0x03, 0x03 },
|
||||||
|
{ 0x00, 0x03, 0x05 },
|
||||||
|
{ 0x00, 0x03, 0x06 },
|
||||||
|
{ 0x00, 0x03, 0x07 },
|
||||||
|
{ 0x00, 0x03, 0x08 },
|
||||||
|
{ 0x00, 0x03, 0x09 },
|
||||||
|
|
||||||
|
// Version 0x0a
|
||||||
|
{ 0x00, 0x0a, 0x01 },
|
||||||
|
{ 0x00, 0x0a, 0x02 },
|
||||||
|
{ 0x00, 0x0a, 0x03 },
|
||||||
|
{ 0x00, 0x0a, 0x04 },
|
||||||
|
{ 0x00, 0x0a, 0x05 },
|
||||||
|
{ 0x00, 0x0a, 0x06 },
|
||||||
|
{ 0x00, 0x0a, 0x07 },
|
||||||
|
{ 0x00, 0x0a, 0x0a },
|
||||||
|
|
||||||
|
// Version 0x0b
|
||||||
|
{ 0x00, 0x0b, 0x01 },
|
||||||
|
{ 0x00, 0x0b, 0x02 },
|
||||||
|
{ 0x00, 0x0b, 0x03 },
|
||||||
|
{ 0x00, 0x0b, 0x04 },
|
||||||
|
{ 0x00, 0x0b, 0x05 },
|
||||||
|
{ 0x00, 0x0b, 0x06 },
|
||||||
|
{ 0x00, 0x0b, 0x07 },
|
||||||
|
{ 0x00, 0x0b, 0x08 },
|
||||||
|
{ 0x00, 0x0b, 0x09 },
|
||||||
|
{ 0x00, 0x0b, 0x0a },
|
||||||
|
|
||||||
|
// Version 0x0c
|
||||||
|
{ 0x00, 0x0c, 0x01 },
|
||||||
|
{ 0x00, 0x0c, 0x02 },
|
||||||
|
{ 0x00, 0x0c, 0x03 },
|
||||||
|
{ 0x00, 0x0c, 0x04 },
|
||||||
|
{ 0x00, 0x0c, 0x05 },
|
||||||
|
{ 0x00, 0x0c, 0x06 },
|
||||||
|
{ 0x00, 0x0c, 0x07 },
|
||||||
|
{ 0x00, 0x0c, 0x08 },
|
||||||
|
{ 0x00, 0x0c, 0x09 },
|
||||||
|
{ 0x00, 0x0c, 0x0b },
|
||||||
|
{ 0x00, 0x0c, 0x0c },
|
||||||
|
{ 0x00, 0x0c, 0x0a },
|
||||||
|
|
||||||
|
// Frequency (H/LFRT)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x10, 0x00, 0x0d },
|
||||||
|
{ 0x10, 0x00, 0x0e },
|
||||||
|
{ 0x10, 0x00, 0x0f },
|
||||||
|
{ 0x10, 0x00, 0x10 },
|
||||||
|
{ 0x10, 0x00, 0x11 },
|
||||||
|
|
||||||
|
// Version 0x03
|
||||||
|
{ 0x10, 0x03, 0x0d },
|
||||||
|
{ 0x10, 0x03, 0x0e },
|
||||||
|
{ 0x10, 0x03, 0x0f },
|
||||||
|
{ 0x10, 0x03, 0x10 },
|
||||||
|
{ 0x10, 0x03, 0x11 },
|
||||||
|
{ 0x10, 0x03, 0x12 },
|
||||||
|
{ 0x10, 0x03, 0x13 },
|
||||||
|
{ 0x10, 0x03, 0x14 },
|
||||||
|
{ 0x10, 0x03, 0x15 },
|
||||||
|
|
||||||
|
// Island Detection (ID)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x20, 0x00, 0x16 },
|
||||||
|
|
||||||
|
// Reconnection (RT)
|
||||||
|
// Version 0x03
|
||||||
|
{ 0x30, 0x03, 0x17 },
|
||||||
|
{ 0x30, 0x03, 0x18 },
|
||||||
|
{ 0x30, 0x03, 0x19 },
|
||||||
|
{ 0x30, 0x03, 0x1a },
|
||||||
|
{ 0x30, 0x03, 0x1b },
|
||||||
|
|
||||||
|
// Ramp Rates (RR)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x40, 0x00, 0x1c },
|
||||||
|
{ 0x40, 0x00, 0x1d },
|
||||||
|
|
||||||
|
// Frequency Watt (FW)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x50, 0x00, 0x1e },
|
||||||
|
{ 0x50, 0x00, 0x1f },
|
||||||
|
{ 0x50, 0x00, 0x20 },
|
||||||
|
{ 0x50, 0x00, 0x21 },
|
||||||
|
|
||||||
|
// Version 0x01
|
||||||
|
{ 0x50, 0x01, 0x1e },
|
||||||
|
{ 0x50, 0x01, 0x1f },
|
||||||
|
{ 0x50, 0x01, 0x20 },
|
||||||
|
{ 0x50, 0x01, 0x21 },
|
||||||
|
{ 0x50, 0x01, 0x22 },
|
||||||
|
|
||||||
|
// Version 0x08
|
||||||
|
{ 0x50, 0x08, 0x1e },
|
||||||
|
{ 0x50, 0x08, 0x1f },
|
||||||
|
{ 0x50, 0x08, 0x20 },
|
||||||
|
{ 0x50, 0x08, 0x21 },
|
||||||
|
{ 0x50, 0x08, 0x22 },
|
||||||
|
{ 0x50, 0x08, 0x23 },
|
||||||
|
|
||||||
|
// Volt Watt (VW)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x60, 0x00, 0x24 },
|
||||||
|
{ 0x60, 0x00, 0x25 },
|
||||||
|
{ 0x60, 0x00, 0x26 },
|
||||||
|
{ 0x60, 0x00, 0x27 },
|
||||||
|
|
||||||
|
// Version 0x04
|
||||||
|
{ 0x60, 0x04, 0x24 },
|
||||||
|
{ 0x60, 0x04, 0x25 },
|
||||||
|
{ 0x60, 0x04, 0x26 },
|
||||||
|
{ 0x60, 0x04, 0x27 },
|
||||||
|
|
||||||
|
// Active Power Control (APC)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x70, 0x00, 0x28 },
|
||||||
|
|
||||||
|
// Version 0x02
|
||||||
|
{ 0x70, 0x02, 0x28 },
|
||||||
|
{ 0x70, 0x02, 0x29 },
|
||||||
|
|
||||||
|
// Volt Var (VV)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x80, 0x00, 0x2a },
|
||||||
|
{ 0x80, 0x00, 0x2b },
|
||||||
|
{ 0x80, 0x00, 0x2c },
|
||||||
|
{ 0x80, 0x00, 0x2d },
|
||||||
|
{ 0x80, 0x00, 0x2e },
|
||||||
|
{ 0x80, 0x00, 0x2f },
|
||||||
|
{ 0x80, 0x00, 0x30 },
|
||||||
|
|
||||||
|
// Version 0x01
|
||||||
|
{ 0x80, 0x01, 0x2a },
|
||||||
|
{ 0x80, 0x01, 0x2b },
|
||||||
|
{ 0x80, 0x01, 0x2c },
|
||||||
|
{ 0x80, 0x01, 0x2d },
|
||||||
|
{ 0x80, 0x01, 0x2e },
|
||||||
|
{ 0x80, 0x01, 0x2f },
|
||||||
|
{ 0x80, 0x01, 0x30 },
|
||||||
|
{ 0x80, 0x01, 0x31 },
|
||||||
|
|
||||||
|
// Specified Power Factor (SPF)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0x90, 0x00, 0x32 },
|
||||||
|
{ 0x90, 0x00, 0x33 },
|
||||||
|
|
||||||
|
// Reactive Power Control (RPC)
|
||||||
|
// Version 0x02
|
||||||
|
{ 0xa0, 0x02, 0x34 },
|
||||||
|
{ 0xa0, 0x02, 0x35 },
|
||||||
|
|
||||||
|
// Watt Power Factor (WPF)
|
||||||
|
// Version 0x00
|
||||||
|
{ 0xb0, 0x00, 0x36 },
|
||||||
|
{ 0xb0, 0x00, 0x37 },
|
||||||
|
{ 0xb0, 0x00, 0x38 },
|
||||||
|
} };
|
||||||
|
|
||||||
GridProfileParser::GridProfileParser()
|
GridProfileParser::GridProfileParser()
|
||||||
: Parser()
|
: Parser()
|
||||||
{
|
{
|
||||||
@ -51,7 +310,9 @@ String GridProfileParser::getProfileName()
|
|||||||
String GridProfileParser::getProfileVersion()
|
String GridProfileParser::getProfileVersion()
|
||||||
{
|
{
|
||||||
char buffer[10];
|
char buffer[10];
|
||||||
|
HOY_SEMAPHORE_TAKE();
|
||||||
snprintf(buffer, sizeof(buffer), "%d.%d.%d", (_payloadGridProfile[2] >> 4) & 0x0f, _payloadGridProfile[2] & 0x0f, _payloadGridProfile[3]);
|
snprintf(buffer, sizeof(buffer), "%d.%d.%d", (_payloadGridProfile[2] >> 4) & 0x0f, _payloadGridProfile[2] & 0x0f, _payloadGridProfile[3]);
|
||||||
|
HOY_SEMAPHORE_GIVE();
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,3 +326,70 @@ std::vector<uint8_t> GridProfileParser::getRawData()
|
|||||||
HOY_SEMAPHORE_GIVE();
|
HOY_SEMAPHORE_GIVE();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::list<GridProfileSection_t> GridProfileParser::getProfile()
|
||||||
|
{
|
||||||
|
std::list<GridProfileSection_t> l;
|
||||||
|
|
||||||
|
if (_gridProfileLength > 4) {
|
||||||
|
uint16_t pos = 4;
|
||||||
|
do {
|
||||||
|
uint8_t section_id = _payloadGridProfile[pos];
|
||||||
|
uint8_t section_version = _payloadGridProfile[pos + 1];
|
||||||
|
int8_t section_start = getSectionStart(section_id, section_version);
|
||||||
|
uint8_t section_size = getSectionSize(section_id, section_version);
|
||||||
|
pos += 2;
|
||||||
|
|
||||||
|
GridProfileSection_t section;
|
||||||
|
try {
|
||||||
|
section.SectionName = profile_section.at(section_id).data();
|
||||||
|
} catch (const std::out_of_range&) {
|
||||||
|
section.SectionName = "Unknown";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t val_id = 0; val_id < section_size; val_id++) {
|
||||||
|
auto value_setting = value_names.at(_profile_values[section_start + val_id].Type);
|
||||||
|
|
||||||
|
float value = (_payloadGridProfile[pos] << 8) | _payloadGridProfile[pos + 1];
|
||||||
|
value /= value_setting.Dividor;
|
||||||
|
|
||||||
|
GridProfileItem_t v;
|
||||||
|
v.Name = value_setting.Name.data();
|
||||||
|
v.Unit = value_setting.Unit.data();
|
||||||
|
v.Value = value;
|
||||||
|
section.items.push_back(v);
|
||||||
|
|
||||||
|
pos += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
l.push_back(section);
|
||||||
|
|
||||||
|
} while (pos < _gridProfileLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t GridProfileParser::getSectionSize(uint8_t section_id, uint8_t section_version)
|
||||||
|
{
|
||||||
|
uint8_t count = 0;
|
||||||
|
for (auto& values : _profile_values) {
|
||||||
|
if (values.Section == section_id && values.Version == section_version) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t GridProfileParser::getSectionStart(uint8_t section_id, uint8_t section_version)
|
||||||
|
{
|
||||||
|
uint8_t count = -1;
|
||||||
|
for (auto& values : _profile_values) {
|
||||||
|
count++;
|
||||||
|
if (values.Section == section_id && values.Version == section_version) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Parser.h"
|
#include "Parser.h"
|
||||||
|
#include <list>
|
||||||
|
|
||||||
#define GRID_PROFILE_SIZE 141
|
#define GRID_PROFILE_SIZE 141
|
||||||
#define PROFILE_TYPE_COUNT 7
|
#define PROFILE_TYPE_COUNT 7
|
||||||
|
#define SECTION_VALUE_COUNT 113
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t lIdx;
|
uint8_t lIdx;
|
||||||
@ -11,6 +13,23 @@ typedef struct {
|
|||||||
const char* Name;
|
const char* Name;
|
||||||
} ProfileType_t;
|
} ProfileType_t;
|
||||||
|
|
||||||
|
struct GridProfileValue_t {
|
||||||
|
uint8_t Section;
|
||||||
|
uint8_t Version;
|
||||||
|
uint8_t Type;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GridProfileItem_t {
|
||||||
|
String Name;
|
||||||
|
String Unit;
|
||||||
|
float Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GridProfileSection_t {
|
||||||
|
String SectionName;
|
||||||
|
std::list<GridProfileItem_t> items;
|
||||||
|
};
|
||||||
|
|
||||||
class GridProfileParser : public Parser {
|
class GridProfileParser : public Parser {
|
||||||
public:
|
public:
|
||||||
GridProfileParser();
|
GridProfileParser();
|
||||||
@ -22,9 +41,15 @@ public:
|
|||||||
|
|
||||||
std::vector<uint8_t> getRawData();
|
std::vector<uint8_t> getRawData();
|
||||||
|
|
||||||
|
std::list<GridProfileSection_t> getProfile();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static uint8_t getSectionSize(uint8_t section_id, uint8_t section_version);
|
||||||
|
static int8_t getSectionStart(uint8_t section_id, uint8_t section_version);
|
||||||
|
|
||||||
uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {};
|
uint8_t _payloadGridProfile[GRID_PROFILE_SIZE] = {};
|
||||||
uint8_t _gridProfileLength = 0;
|
uint8_t _gridProfileLength = 0;
|
||||||
|
|
||||||
static const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> _profileTypes;
|
static const std::array<const ProfileType_t, PROFILE_TYPE_COUNT> _profileTypes;
|
||||||
|
static const std::array<const GridProfileValue_t, SECTION_VALUE_COUNT> _profile_values;
|
||||||
};
|
};
|
||||||
@ -14,6 +14,7 @@ void WebApiGridProfileClass::init(AsyncWebServer* server)
|
|||||||
_server = server;
|
_server = server;
|
||||||
|
|
||||||
_server->on("/api/gridprofile/status", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileStatus, this, _1));
|
_server->on("/api/gridprofile/status", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileStatus, this, _1));
|
||||||
|
_server->on("/api/gridprofile/rawdata", HTTP_GET, std::bind(&WebApiGridProfileClass::onGridProfileRawdata, this, _1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebApiGridProfileClass::loop()
|
void WebApiGridProfileClass::loop()
|
||||||
@ -26,6 +27,50 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 8192);
|
||||||
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
|
uint64_t serial = 0;
|
||||||
|
if (request->hasParam("inv")) {
|
||||||
|
String s = request->getParam("inv")->value();
|
||||||
|
serial = strtoll(s.c_str(), NULL, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
|
|
||||||
|
if (inv != nullptr) {
|
||||||
|
root["name"] = inv->GridProfile()->getProfileName();
|
||||||
|
root["version"] = inv->GridProfile()->getProfileVersion();
|
||||||
|
|
||||||
|
auto jsonSections = root.createNestedArray("sections");
|
||||||
|
auto profSections = inv->GridProfile()->getProfile();
|
||||||
|
|
||||||
|
for (auto &profSection : profSections) {
|
||||||
|
auto jsonSection = jsonSections.createNestedObject();
|
||||||
|
jsonSection["name"] = profSection.SectionName;
|
||||||
|
|
||||||
|
auto jsonItems = jsonSection.createNestedArray("items");
|
||||||
|
|
||||||
|
for (auto &profItem : profSection.items) {
|
||||||
|
auto jsonItem = jsonItems.createNestedObject();
|
||||||
|
|
||||||
|
jsonItem["n"] = profItem.Name;
|
||||||
|
jsonItem["u"] = profItem.Unit;
|
||||||
|
jsonItem["v"] = profItem.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response->setLength();
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WebApiGridProfileClass::onGridProfileRawdata(AsyncWebServerRequest* request)
|
||||||
|
{
|
||||||
|
if (!WebApi.checkCredentialsReadonly(request)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096);
|
AsyncJsonResponse* response = new AsyncJsonResponse(false, 4096);
|
||||||
JsonObject root = response->getRoot();
|
JsonObject root = response->getRoot();
|
||||||
|
|
||||||
@ -42,9 +87,6 @@ void WebApiGridProfileClass::onGridProfileStatus(AsyncWebServerRequest* request)
|
|||||||
auto data = inv->GridProfile()->getRawData();
|
auto data = inv->GridProfile()->getRawData();
|
||||||
|
|
||||||
copyArray(&data[0], data.size(), raw);
|
copyArray(&data[0], data.size(), raw);
|
||||||
|
|
||||||
root["name"] = inv->GridProfile()->getProfileName();
|
|
||||||
root["version"] = inv->GridProfile()->getProfileVersion();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response->setLength();
|
response->setLength();
|
||||||
|
|||||||
@ -19,6 +19,46 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="accordion" id="accordionProfile">
|
||||||
|
<div class="accordion-item" v-for="(section, index) in gridProfileList.sections">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" :data-bs-target="`#collapse${index}`" aria-expanded="true" :aria-controls="`collapse${index}`">
|
||||||
|
{{ section.name }}
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div :id="`collapse${index}`" class="accordion-collapse collapse" data-bs-parent="#accordionProfile">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<table class="table table-hover">
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="value in section.items">
|
||||||
|
<th>{{ value.n }}</th>
|
||||||
|
<td>
|
||||||
|
<tempplate v-if="value.u!='bool'">
|
||||||
|
{{ $n(value.v, 'decimal') }} {{ value.u }}
|
||||||
|
</tempplate>
|
||||||
|
<template v-else>
|
||||||
|
<StatusBadge :status="value.v==1" true_text="gridprofile.Enabled" false_text="gridprofile.Disabled"/>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div class="accordion" id="accordionDev">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseDev" aria-expanded="true" aria-controls="collapseDev">
|
||||||
|
{{ $t('gridprofile.GridprofileSupport') }}
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="collapseDev" class="accordion-collapse collapse" data-bs-parent="#accordionDev">
|
||||||
|
<div class="accordion-body">
|
||||||
<BootstrapAlert :show="true" variant="danger">
|
<BootstrapAlert :show="true" variant="danger">
|
||||||
<h4 class="info-heading">
|
<h4 class="info-heading">
|
||||||
<BIconInfoSquare class="fs-2" /> {{ $t('gridprofile.GridprofileSupport') }}
|
<BIconInfoSquare class="fs-2" /> {{ $t('gridprofile.GridprofileSupport') }}
|
||||||
@ -27,28 +67,37 @@
|
|||||||
<samp>
|
<samp>
|
||||||
{{ rawContent() }}
|
{{ rawContent() }}
|
||||||
</samp>
|
</samp>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
import BootstrapAlert from '@/components/BootstrapAlert.vue';
|
||||||
|
import type { GridProfileRawdata } from '@/types/GridProfileRawdata';
|
||||||
import type { GridProfileStatus } from "@/types/GridProfileStatus";
|
import type { GridProfileStatus } from "@/types/GridProfileStatus";
|
||||||
import { BIconInfoSquare } from 'bootstrap-icons-vue';
|
import { BIconInfoSquare } from 'bootstrap-icons-vue';
|
||||||
import { defineComponent, type PropType } from 'vue';
|
import { defineComponent, type PropType } from 'vue';
|
||||||
|
import StatusBadge from './StatusBadge.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
BootstrapAlert,
|
BootstrapAlert,
|
||||||
BIconInfoSquare,
|
BIconInfoSquare,
|
||||||
|
StatusBadge,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
gridProfileList: { type: Object as PropType<GridProfileStatus>, required: true },
|
gridProfileList: { type: Object as PropType<GridProfileStatus>, required: true },
|
||||||
|
gridProfileRawList: { type: Object as PropType<GridProfileRawdata>, required: true },
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
rawContent() {
|
rawContent() {
|
||||||
return () => {
|
return () => {
|
||||||
return this.gridProfileList.raw.map(function (x) {
|
return this.gridProfileRawList.raw.map(function (x) {
|
||||||
let y = x.toString(16); // to hex
|
let y = x.toString(16); // to hex
|
||||||
y = ("00" + y).substr(-2); // zero-pad to 2-digits
|
y = ("00" + y).substr(-2); // zero-pad to 2-digits
|
||||||
return y
|
return y
|
||||||
@ -56,7 +105,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
hasValidData() {
|
hasValidData() {
|
||||||
return this.gridProfileList.raw.reduce((sum, x) => sum + x, 0) > 0;
|
return this.gridProfileRawList.raw.reduce((sum, x) => sum + x, 0) > 0;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -157,6 +157,8 @@
|
|||||||
"NoInfoLong": "@:devinfo.NoInfoLong",
|
"NoInfoLong": "@:devinfo.NoInfoLong",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
|
"Enabled": "@:wifistationinfo.Enabled",
|
||||||
|
"Disabled": "@:wifistationinfo.Disabled",
|
||||||
"GridprofileSupport": "Unterstütze die Entwicklung",
|
"GridprofileSupport": "Unterstütze die Entwicklung",
|
||||||
"GridprofileSupportLong": "Weitere Informationen sind <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">hier</a> zu finden."
|
"GridprofileSupportLong": "Weitere Informationen sind <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">hier</a> zu finden."
|
||||||
},
|
},
|
||||||
|
|||||||
@ -157,6 +157,8 @@
|
|||||||
"NoInfoLong": "@:devinfo.NoInfoLong",
|
"NoInfoLong": "@:devinfo.NoInfoLong",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
|
"Enabled": "@:wifistationinfo.Enabled",
|
||||||
|
"Disabled": "@:wifistationinfo.Disabled",
|
||||||
"GridprofileSupport": "Support the development",
|
"GridprofileSupport": "Support the development",
|
||||||
"GridprofileSupportLong": "Please see <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">here</a> for further information."
|
"GridprofileSupportLong": "Please see <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">here</a> for further information."
|
||||||
},
|
},
|
||||||
|
|||||||
@ -157,6 +157,8 @@
|
|||||||
"NoInfoLong": "@:devinfo.NoInfoLong",
|
"NoInfoLong": "@:devinfo.NoInfoLong",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"Version": "Version",
|
"Version": "Version",
|
||||||
|
"Enabled": "@:wifistationinfo.Enabled",
|
||||||
|
"Disabled": "@:wifistationinfo.Disabled",
|
||||||
"GridprofileSupport": "Support the development",
|
"GridprofileSupport": "Support the development",
|
||||||
"GridprofileSupportLong": "Please see <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">here</a> for further information."
|
"GridprofileSupportLong": "Please see <a href=\"https://github.com/tbnobody/OpenDTU/wiki/Grid-Profile-Parser\" target=\"_blank\">here</a> for further information."
|
||||||
},
|
},
|
||||||
|
|||||||
3
webapp/src/types/GridProfileRawdata.ts
Normal file
3
webapp/src/types/GridProfileRawdata.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface GridProfileRawdata {
|
||||||
|
raw: Array<number>;
|
||||||
|
}
|
||||||
@ -1,5 +1,16 @@
|
|||||||
export interface GridProfileStatus {
|
export interface GridProfileValue {
|
||||||
raw: Array<number>;
|
n: string;
|
||||||
name: String;
|
u: string;
|
||||||
version: String;
|
v: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GridProfileSection {
|
||||||
|
name: string;
|
||||||
|
items: Array<GridProfileValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GridProfileStatus {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
sections: Array<GridProfileSection>;
|
||||||
}
|
}
|
||||||
@ -189,7 +189,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<GridProfile v-if="!gridProfileLoading" :gridProfileList="gridProfileList" />
|
<GridProfile v-if="!gridProfileLoading" :gridProfileList="gridProfileList" :gridProfileRawList="gridProfileRawList" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@ -361,6 +361,7 @@ import InverterTotalInfo from '@/components/InverterTotalInfo.vue';
|
|||||||
import type { DevInfoStatus } from '@/types/DevInfoStatus';
|
import type { DevInfoStatus } from '@/types/DevInfoStatus';
|
||||||
import type { EventlogItems } from '@/types/EventlogStatus';
|
import type { EventlogItems } from '@/types/EventlogStatus';
|
||||||
import type { GridProfileStatus } from '@/types/GridProfileStatus';
|
import type { GridProfileStatus } from '@/types/GridProfileStatus';
|
||||||
|
import type { GridProfileRawdata } from '@/types/GridProfileRawdata';
|
||||||
import type { LimitConfig } from '@/types/LimitConfig';
|
import type { LimitConfig } from '@/types/LimitConfig';
|
||||||
import type { LimitStatus } from '@/types/LimitStatus';
|
import type { LimitStatus } from '@/types/LimitStatus';
|
||||||
import type { Inverter, LiveData } from '@/types/LiveDataStatus';
|
import type { Inverter, LiveData } from '@/types/LiveDataStatus';
|
||||||
@ -421,6 +422,7 @@ export default defineComponent({
|
|||||||
devInfoLoading: true,
|
devInfoLoading: true,
|
||||||
gridProfileView: {} as bootstrap.Modal,
|
gridProfileView: {} as bootstrap.Modal,
|
||||||
gridProfileList: {} as GridProfileStatus,
|
gridProfileList: {} as GridProfileStatus,
|
||||||
|
gridProfileRawList: {} as GridProfileRawdata,
|
||||||
gridProfileLoading: true,
|
gridProfileLoading: true,
|
||||||
|
|
||||||
limitSettingView: {} as bootstrap.Modal,
|
limitSettingView: {} as bootstrap.Modal,
|
||||||
@ -613,7 +615,13 @@ export default defineComponent({
|
|||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
this.gridProfileList = data;
|
this.gridProfileList = data;
|
||||||
|
|
||||||
|
fetch("/api/gridprofile/rawdata?inv=" + serial, { headers: authHeader() })
|
||||||
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
.then((data) => {
|
||||||
|
this.gridProfileRawList = data;
|
||||||
this.gridProfileLoading = false;
|
this.gridProfileLoading = false;
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.gridProfileView.show();
|
this.gridProfileView.show();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user