Feature: Added option to set runtime values to zero when inverter becames unreachable
This commit is contained in:
parent
4f85d5286d
commit
6127fbe940
@ -46,6 +46,7 @@ struct INVERTER_CONFIG_T {
|
|||||||
bool Command_Enable;
|
bool Command_Enable;
|
||||||
bool Command_Enable_Night;
|
bool Command_Enable_Night;
|
||||||
uint8_t ReachableThreshold;
|
uint8_t ReachableThreshold;
|
||||||
|
bool ZeroRuntimeDataIfUnrechable;
|
||||||
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
|
CHANNEL_CONFIG_T channel[INV_MAX_CHAN_COUNT];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -55,4 +55,9 @@ bool RealTimeRunDataCommand::handleResponse(InverterAbstract* inverter, fragment
|
|||||||
void RealTimeRunDataCommand::gotTimeout(InverterAbstract* inverter)
|
void RealTimeRunDataCommand::gotTimeout(InverterAbstract* inverter)
|
||||||
{
|
{
|
||||||
inverter->Statistics()->incrementRxFailureCount();
|
inverter->Statistics()->incrementRxFailureCount();
|
||||||
|
|
||||||
|
if (inverter->getZeroValuesIfUnreachable() && !inverter->isReachable()) {
|
||||||
|
Hoymiles.getMessageOutput()->println("Set runtime data to zero");
|
||||||
|
inverter->Statistics()->zeroRuntimeData();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -106,6 +106,16 @@ uint8_t InverterAbstract::getReachableThreshold()
|
|||||||
return _reachableThreshold;
|
return _reachableThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InverterAbstract::setZeroValuesIfUnreachable(bool enabled)
|
||||||
|
{
|
||||||
|
_zeroValuesIfUnreachable = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InverterAbstract::getZeroValuesIfUnreachable()
|
||||||
|
{
|
||||||
|
return _zeroValuesIfUnreachable;
|
||||||
|
}
|
||||||
|
|
||||||
bool InverterAbstract::sendChangeChannelRequest()
|
bool InverterAbstract::sendChangeChannelRequest()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -51,6 +51,9 @@ public:
|
|||||||
void setReachableThreshold(uint8_t threshold);
|
void setReachableThreshold(uint8_t threshold);
|
||||||
uint8_t getReachableThreshold();
|
uint8_t getReachableThreshold();
|
||||||
|
|
||||||
|
void setZeroValuesIfUnreachable(bool enabled);
|
||||||
|
bool getZeroValuesIfUnreachable();
|
||||||
|
|
||||||
void clearRxFragmentBuffer();
|
void clearRxFragmentBuffer();
|
||||||
void addRxFragment(uint8_t fragment[], uint8_t len);
|
void addRxFragment(uint8_t fragment[], uint8_t len);
|
||||||
uint8_t verifyAllFragments(CommandAbstract* cmd);
|
uint8_t verifyAllFragments(CommandAbstract* cmd);
|
||||||
@ -91,6 +94,8 @@ private:
|
|||||||
|
|
||||||
uint8_t _reachableThreshold = 3;
|
uint8_t _reachableThreshold = 3;
|
||||||
|
|
||||||
|
bool _zeroValuesIfUnreachable = false;
|
||||||
|
|
||||||
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
std::unique_ptr<AlarmLogParser> _alarmLogParser;
|
||||||
std::unique_ptr<DevInfoParser> _devInfoParser;
|
std::unique_ptr<DevInfoParser> _devInfoParser;
|
||||||
std::unique_ptr<PowerCommandParser> _powerCommandParser;
|
std::unique_ptr<PowerCommandParser> _powerCommandParser;
|
||||||
|
|||||||
@ -33,6 +33,28 @@ const calcFunc_t calcFunctions[] = {
|
|||||||
{ CALC_IRR_CH, &calcIrradiation }
|
{ CALC_IRR_CH, &calcIrradiation }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FieldId_t runtimeFields[] = {
|
||||||
|
FLD_UDC,
|
||||||
|
FLD_IDC,
|
||||||
|
FLD_PDC,
|
||||||
|
FLD_UAC,
|
||||||
|
FLD_IAC,
|
||||||
|
FLD_PAC,
|
||||||
|
FLD_F,
|
||||||
|
FLD_T,
|
||||||
|
FLD_PF,
|
||||||
|
FLD_Q,
|
||||||
|
FLD_UAC_1N,
|
||||||
|
FLD_UAC_2N,
|
||||||
|
FLD_UAC_3N,
|
||||||
|
FLD_UAC_12,
|
||||||
|
FLD_UAC_23,
|
||||||
|
FLD_UAC_31,
|
||||||
|
FLD_IAC_1,
|
||||||
|
FLD_IAC_2,
|
||||||
|
FLD_IAC_3,
|
||||||
|
};
|
||||||
|
|
||||||
StatisticsParser::StatisticsParser()
|
StatisticsParser::StatisticsParser()
|
||||||
: Parser()
|
: Parser()
|
||||||
{
|
{
|
||||||
@ -150,6 +172,47 @@ float StatisticsParser::getChannelFieldValue(ChannelType_t type, ChannelNum_t ch
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StatisticsParser::setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value)
|
||||||
|
{
|
||||||
|
const byteAssign_t* pos = getAssignmentByChannelField(type, channel, fieldId);
|
||||||
|
fieldSettings_t* setting = getSettingByChannelField(type, channel, fieldId);
|
||||||
|
|
||||||
|
if (pos == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ptr = pos->start + pos->num - 1;
|
||||||
|
uint8_t end = pos->start;
|
||||||
|
uint16_t div = pos->div;
|
||||||
|
|
||||||
|
if (CMD_CALC == div) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (setting != NULL) {
|
||||||
|
value -= setting->offset;
|
||||||
|
}
|
||||||
|
value *= static_cast<float>(div);
|
||||||
|
|
||||||
|
uint32_t val = 0;
|
||||||
|
if (pos->isSigned && pos->num == 2) {
|
||||||
|
val = static_cast<uint32_t>(static_cast<int16_t>(value));
|
||||||
|
} else if (pos->isSigned && pos->num == 4) {
|
||||||
|
val = static_cast<uint32_t>(static_cast<int32_t>(value));
|
||||||
|
} else {
|
||||||
|
val = static_cast<uint32_t>(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
HOY_SEMAPHORE_TAKE();
|
||||||
|
do {
|
||||||
|
_payloadStatistic[ptr] = val;
|
||||||
|
val >>= 8;
|
||||||
|
} while (--ptr >= end);
|
||||||
|
HOY_SEMAPHORE_GIVE();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
String StatisticsParser::getChannelFieldValueString(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
|
String StatisticsParser::getChannelFieldValueString(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId)
|
||||||
{
|
{
|
||||||
return String(
|
return String(
|
||||||
@ -253,6 +316,20 @@ uint32_t StatisticsParser::getRxFailureCount()
|
|||||||
return _rxFailureCount;
|
return _rxFailureCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void StatisticsParser::zeroRuntimeData()
|
||||||
|
{
|
||||||
|
// Loop all channels
|
||||||
|
for (auto& t : getChannelTypes()) {
|
||||||
|
for (auto& c : getChannelsByType(t)) {
|
||||||
|
for (uint8_t i = 0; i < (sizeof(runtimeFields) / sizeof(runtimeFields[0])); i++) {
|
||||||
|
if (hasChannelFieldValue(t, c, runtimeFields[i])) {
|
||||||
|
setChannelFieldValue(t, c, runtimeFields[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0)
|
static float calcYieldTotalCh0(StatisticsParser* iv, uint8_t arg0)
|
||||||
{
|
{
|
||||||
float yield = 0;
|
float yield = 0;
|
||||||
|
|||||||
@ -125,6 +125,8 @@ public:
|
|||||||
const char* getChannelFieldName(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 getChannelFieldDigits(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||||
|
|
||||||
|
bool setChannelFieldValue(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float value);
|
||||||
|
|
||||||
float getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
float getChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId);
|
||||||
void setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset);
|
void setChannelFieldOffset(ChannelType_t type, ChannelNum_t channel, FieldId_t fieldId, float offset);
|
||||||
|
|
||||||
@ -139,6 +141,8 @@ public:
|
|||||||
void incrementRxFailureCount();
|
void incrementRxFailureCount();
|
||||||
uint32_t getRxFailureCount();
|
uint32_t getRxFailureCount();
|
||||||
|
|
||||||
|
void zeroRuntimeData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {};
|
uint8_t _payloadStatistic[STATISTIC_PACKET_SIZE] = {};
|
||||||
uint8_t _statisticLength = 0;
|
uint8_t _statisticLength = 0;
|
||||||
|
|||||||
@ -111,6 +111,7 @@ bool ConfigurationClass::write()
|
|||||||
inv["command_enable"] = config.Inverter[i].Command_Enable;
|
inv["command_enable"] = config.Inverter[i].Command_Enable;
|
||||||
inv["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
|
inv["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
|
||||||
inv["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
|
inv["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
|
||||||
|
inv["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable;
|
||||||
|
|
||||||
JsonArray channel = inv.createNestedArray("channel");
|
JsonArray channel = inv.createNestedArray("channel");
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||||
@ -260,6 +261,7 @@ bool ConfigurationClass::read()
|
|||||||
config.Inverter[i].Command_Enable = inv["command_enable"] | true;
|
config.Inverter[i].Command_Enable = inv["command_enable"] | true;
|
||||||
config.Inverter[i].Command_Enable_Night = inv["command_enable_night"] | true;
|
config.Inverter[i].Command_Enable_Night = inv["command_enable_night"] | true;
|
||||||
config.Inverter[i].ReachableThreshold = inv["reachable_threshold"] | REACHABLE_THRESHOLD;
|
config.Inverter[i].ReachableThreshold = inv["reachable_threshold"] | REACHABLE_THRESHOLD;
|
||||||
|
config.Inverter[i].ZeroRuntimeDataIfUnrechable = inv["zero_runtime"] | false;
|
||||||
|
|
||||||
JsonArray channel = inv["channel"];
|
JsonArray channel = inv["channel"];
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||||
|
|||||||
@ -72,6 +72,7 @@ void InverterSettingsClass::init()
|
|||||||
|
|
||||||
if (inv != nullptr) {
|
if (inv != nullptr) {
|
||||||
inv->setReachableThreshold(config.Inverter[i].ReachableThreshold);
|
inv->setReachableThreshold(config.Inverter[i].ReachableThreshold);
|
||||||
|
inv->setZeroValuesIfUnreachable(config.Inverter[i].ZeroRuntimeDataIfUnrechable);
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||||
inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
|
inv->Statistics()->setStringMaxPower(c, config.Inverter[i].channel[c].MaxChannelPower);
|
||||||
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset);
|
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, config.Inverter[i].channel[c].YieldTotalOffset);
|
||||||
|
|||||||
@ -59,6 +59,7 @@ void WebApiInverterClass::onInverterList(AsyncWebServerRequest* request)
|
|||||||
obj["command_enable"] = config.Inverter[i].Command_Enable;
|
obj["command_enable"] = config.Inverter[i].Command_Enable;
|
||||||
obj["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
|
obj["command_enable_night"] = config.Inverter[i].Command_Enable_Night;
|
||||||
obj["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
|
obj["reachable_threshold"] = config.Inverter[i].ReachableThreshold;
|
||||||
|
obj["zero_runtime"] = config.Inverter[i].ZeroRuntimeDataIfUnrechable;
|
||||||
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial);
|
auto inv = Hoymiles.getInverterBySerial(config.Inverter[i].Serial);
|
||||||
uint8_t max_channels;
|
uint8_t max_channels;
|
||||||
@ -284,6 +285,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
inverter.Command_Enable = root["command_enable"] | true;
|
inverter.Command_Enable = root["command_enable"] | true;
|
||||||
inverter.Command_Enable_Night = root["command_enable_night"] | true;
|
inverter.Command_Enable_Night = root["command_enable_night"] | true;
|
||||||
inverter.ReachableThreshold = root["reachable_threshold"] | REACHABLE_THRESHOLD;
|
inverter.ReachableThreshold = root["reachable_threshold"] | REACHABLE_THRESHOLD;
|
||||||
|
inverter.ZeroRuntimeDataIfUnrechable = root["zero_runtime"] | false;
|
||||||
|
|
||||||
arrayCount++;
|
arrayCount++;
|
||||||
}
|
}
|
||||||
@ -315,6 +317,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
inv->setEnablePolling(inverter.Poll_Enable);
|
inv->setEnablePolling(inverter.Poll_Enable);
|
||||||
inv->setEnableCommands(inverter.Command_Enable);
|
inv->setEnableCommands(inverter.Command_Enable);
|
||||||
inv->setReachableThreshold(inverter.ReachableThreshold);
|
inv->setReachableThreshold(inverter.ReachableThreshold);
|
||||||
|
inv->setZeroValuesIfUnreachable(inverter.ZeroRuntimeDataIfUnrechable);
|
||||||
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
for (uint8_t c = 0; c < INV_MAX_CHAN_COUNT; c++) {
|
||||||
inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower);
|
inv->Statistics()->setStringMaxPower(c, inverter.channel[c].MaxChannelPower);
|
||||||
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset);
|
inv->Statistics()->setChannelFieldOffset(TYPE_DC, static_cast<ChannelNum_t>(c), FLD_YT, inverter.channel[c].YieldTotalOffset);
|
||||||
|
|||||||
@ -472,6 +472,8 @@
|
|||||||
"InverterHint": "*) Geben Sie die W<sub>p</sub> des Ports ein, um die Einstrahlung zu errechnen.",
|
"InverterHint": "*) Geben Sie die W<sub>p</sub> des Ports ein, um die Einstrahlung zu errechnen.",
|
||||||
"ReachableThreshold": "Erreichbarkeit Schwellenwert:",
|
"ReachableThreshold": "Erreichbarkeit Schwellenwert:",
|
||||||
"ReachableThresholdHint": "Legt fest, wie viele Anfragen fehlschlagen dürfen, bis der Wechselrichter als unerreichbar eingestuft wird.",
|
"ReachableThresholdHint": "Legt fest, wie viele Anfragen fehlschlagen dürfen, bis der Wechselrichter als unerreichbar eingestuft wird.",
|
||||||
|
"ZeroRuntime": "Nulle Laufzeit Daten",
|
||||||
|
"ZeroRuntimeHint": "Nulle Laufzeit Daten (keine Ertragsdaten), wenn der Wechselrichter nicht erreichbar ist.",
|
||||||
"Cancel": "@:maintenancereboot.Cancel",
|
"Cancel": "@:maintenancereboot.Cancel",
|
||||||
"Save": "@:dtuadmin.Save",
|
"Save": "@:dtuadmin.Save",
|
||||||
"DeleteMsg": "Soll der Wechselrichter \"{name}\" mit der Seriennummer {serial} wirklich gelöscht werden?",
|
"DeleteMsg": "Soll der Wechselrichter \"{name}\" mit der Seriennummer {serial} wirklich gelöscht werden?",
|
||||||
|
|||||||
@ -472,6 +472,8 @@
|
|||||||
"InverterHint": "*) Enter the W<sub>p</sub> of the channel to calculate irradiation.",
|
"InverterHint": "*) Enter the W<sub>p</sub> of the channel to calculate irradiation.",
|
||||||
"ReachableThreshold": "Reachable Threshold:",
|
"ReachableThreshold": "Reachable Threshold:",
|
||||||
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
|
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
|
||||||
|
"ZeroRuntime": "Zero runtime data",
|
||||||
|
"ZeroRuntimeHint": "Zero runtime data (no yield data) if inverter becomes unreachable.",
|
||||||
"Cancel": "@:maintenancereboot.Cancel",
|
"Cancel": "@:maintenancereboot.Cancel",
|
||||||
"Save": "@:dtuadmin.Save",
|
"Save": "@:dtuadmin.Save",
|
||||||
"DeleteMsg": "Are you sure you want to delete the inverter \"{name}\" with serial number {serial}?",
|
"DeleteMsg": "Are you sure you want to delete the inverter \"{name}\" with serial number {serial}?",
|
||||||
|
|||||||
@ -472,6 +472,8 @@
|
|||||||
"InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.",
|
"InverterHint": "*) Entrez le W<sub>p</sub> du canal pour calculer l'irradiation.",
|
||||||
"ReachableThreshold": "Reachable Threshold:",
|
"ReachableThreshold": "Reachable Threshold:",
|
||||||
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
|
"ReachableThresholdHint": "Defines how many requests are allowed to fail until the inverter is treated is not reachable.",
|
||||||
|
"ZeroRuntime": "Zero runtime data",
|
||||||
|
"ZeroRuntimeHint": "Zero runtime data (no yield data) if inverter becomes unreachable.",
|
||||||
"Cancel": "@:maintenancereboot.Cancel",
|
"Cancel": "@:maintenancereboot.Cancel",
|
||||||
"Save": "@:dtuadmin.Save",
|
"Save": "@:dtuadmin.Save",
|
||||||
"DeleteMsg": "Êtes-vous sûr de vouloir supprimer l'onduleur \"{name}\" avec le numéro de série \"{serial}\" ?",
|
"DeleteMsg": "Êtes-vous sûr de vouloir supprimer l'onduleur \"{name}\" avec le numéro de série \"{serial}\" ?",
|
||||||
|
|||||||
@ -182,6 +182,11 @@
|
|||||||
v-model="selectedInverterData.reachable_threshold"
|
v-model="selectedInverterData.reachable_threshold"
|
||||||
type="number" min="1" max="100"
|
type="number" min="1" max="100"
|
||||||
:tooltip="$t('inverteradmin.ReachableThresholdHint')" wide />
|
:tooltip="$t('inverteradmin.ReachableThresholdHint')" wide />
|
||||||
|
|
||||||
|
<InputElement :label="$t('inverteradmin.ZeroRuntime')"
|
||||||
|
v-model="selectedInverterData.zero_runtime"
|
||||||
|
type="checkbox"
|
||||||
|
:tooltip="$t('inverteradmin.ZeroRuntimeHint')" wide/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -257,6 +262,7 @@ declare interface Inverter {
|
|||||||
command_enable: boolean;
|
command_enable: boolean;
|
||||||
command_enable_night: boolean;
|
command_enable_night: boolean;
|
||||||
reachable_threshold: number;
|
reachable_threshold: number;
|
||||||
|
zero_runtime: boolean;
|
||||||
channel: Array<Channel>;
|
channel: Array<Channel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user