Feature: Add support for HERF inverters
This commit is contained in:
parent
10cd2e4201
commit
f995287a6e
@ -75,3 +75,6 @@ Generated using: `git log --date=short --pretty=format:"* %h%x09%ad%x09%s" | gre
|
|||||||
| TSUN TSOL-M350 | NRF24L01+ | 1 | 1 | 1 |
|
| TSUN TSOL-M350 | NRF24L01+ | 1 | 1 | 1 |
|
||||||
| TSUN TSOL-M800 | NRF24L01+ | 2 | 2 | 1 |
|
| TSUN TSOL-M800 | NRF24L01+ | 2 | 2 | 1 |
|
||||||
| TSUN TSOL-M1600 | NRF24L01+ | 4 | 2 | 1 |
|
| TSUN TSOL-M1600 | NRF24L01+ | 4 | 2 | 1 |
|
||||||
|
| E-Star HERF-800 | NRF24L01+ | 2 | 2 | 1 |
|
||||||
|
| E-Star HERF-1600 | NRF24L01+ | 4 | 2 | 1 |
|
||||||
|
| E-Star HERF-1800 | NRF24L01+ | 4 | 2 | 1 |
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2023 Thomas Basler and others
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
*/
|
*/
|
||||||
#include "Hoymiles.h"
|
#include "Hoymiles.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "inverters/HERF_2CH.h"
|
||||||
|
#include "inverters/HERF_4CH.h"
|
||||||
#include "inverters/HMS_1CH.h"
|
#include "inverters/HMS_1CH.h"
|
||||||
#include "inverters/HMS_1CHv2.h"
|
#include "inverters/HMS_1CHv2.h"
|
||||||
#include "inverters/HMS_2CH.h"
|
#include "inverters/HMS_2CH.h"
|
||||||
@ -168,6 +170,10 @@ std::shared_ptr<InverterAbstract> HoymilesClass::addInverter(const char* name, c
|
|||||||
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
|
i = std::make_shared<HM_2CH>(_radioNrf.get(), serial);
|
||||||
} else if (HM_1CH::isValidSerial(serial)) {
|
} else if (HM_1CH::isValidSerial(serial)) {
|
||||||
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
|
i = std::make_shared<HM_1CH>(_radioNrf.get(), serial);
|
||||||
|
} else if (HERF_2CH::isValidSerial(serial)) {
|
||||||
|
i = std::make_shared<HERF_2CH>(_radioNrf.get(), serial);
|
||||||
|
} else if (HERF_4CH::isValidSerial(serial)) {
|
||||||
|
i = std::make_shared<HERF_4CH>(_radioNrf.get(), serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i) {
|
if (i) {
|
||||||
|
|||||||
62
lib/Hoymiles/src/inverters/HERF_2CH.cpp
Normal file
62
lib/Hoymiles/src/inverters/HERF_2CH.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "HERF_2CH.h"
|
||||||
|
|
||||||
|
static const byteAssign_t byteAssignment[] = {
|
||||||
|
{ TYPE_DC, CH0, FLD_UDC, UNIT_V, 2, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH0, FLD_IDC, UNIT_A, 6, 2, 100, false, 2 },
|
||||||
|
{ TYPE_DC, CH0, FLD_PDC, UNIT_W, 10, 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_CH_IRR, CH0, CMD_CALC, false, 3 },
|
||||||
|
|
||||||
|
{ TYPE_DC, CH1, FLD_UDC, UNIT_V, 4, 2, 10, false, 1 },
|
||||||
|
{ TYPE_DC, CH1, FLD_IDC, UNIT_A, 8, 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_CH_IRR, CH1, 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_Q, UNIT_VAR, 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_INV, CH0, FLD_YD, UNIT_WH, CALC_TOTAL_YD, 0, CMD_CALC, false, 0 },
|
||||||
|
{ TYPE_INV, CH0, FLD_YT, UNIT_KWH, CALC_TOTAL_YT, 0, CMD_CALC, false, 3 },
|
||||||
|
{ TYPE_INV, CH0, FLD_PDC, UNIT_W, CALC_TOTAL_PDC, 0, CMD_CALC, false, 1 },
|
||||||
|
{ TYPE_INV, CH0, FLD_EFF, UNIT_PCT, CALC_TOTAL_EFF, 0, CMD_CALC, false, 3 }
|
||||||
|
};
|
||||||
|
|
||||||
|
HERF_2CH::HERF_2CH(HoymilesRadio* radio, const uint64_t serial)
|
||||||
|
: HM_Abstract(radio, serial) {};
|
||||||
|
|
||||||
|
bool HERF_2CH::isValidSerial(const uint64_t serial)
|
||||||
|
{
|
||||||
|
// serial >= 0x282100000000 && serial <= 0x282199999999
|
||||||
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
|
return preSerial == 0x2821;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HERF_2CH::typeName() const
|
||||||
|
{
|
||||||
|
return "HERF-800-2T";
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteAssign_t* HERF_2CH::getByteAssignment() const
|
||||||
|
{
|
||||||
|
return byteAssignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t HERF_2CH::getByteAssignmentSize() const
|
||||||
|
{
|
||||||
|
return sizeof(byteAssignment) / sizeof(byteAssignment[0]);
|
||||||
|
}
|
||||||
13
lib/Hoymiles/src/inverters/HERF_2CH.h
Normal file
13
lib/Hoymiles/src/inverters/HERF_2CH.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HM_Abstract.h"
|
||||||
|
|
||||||
|
class HERF_2CH : public HM_Abstract {
|
||||||
|
public:
|
||||||
|
explicit HERF_2CH(HoymilesRadio* radio, const uint64_t serial);
|
||||||
|
static bool isValidSerial(const uint64_t serial);
|
||||||
|
String typeName() const;
|
||||||
|
const byteAssign_t* getByteAssignment() const;
|
||||||
|
uint8_t getByteAssignmentSize() const;
|
||||||
|
};
|
||||||
20
lib/Hoymiles/src/inverters/HERF_4CH.cpp
Normal file
20
lib/Hoymiles/src/inverters/HERF_4CH.cpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2022-2024 Thomas Basler and others
|
||||||
|
*/
|
||||||
|
#include "HERF_4CH.h"
|
||||||
|
|
||||||
|
HERF_4CH::HERF_4CH(HoymilesRadio* radio, const uint64_t serial)
|
||||||
|
: HM_4CH(radio, serial) {};
|
||||||
|
|
||||||
|
bool HERF_4CH::isValidSerial(const uint64_t serial)
|
||||||
|
{
|
||||||
|
// serial >= 0x280100000000 && serial <= 0x280199999999
|
||||||
|
uint16_t preSerial = (serial >> 32) & 0xffff;
|
||||||
|
return preSerial == 0x2801;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HERF_4CH::typeName() const
|
||||||
|
{
|
||||||
|
return "HERF-1600/1800-4T";
|
||||||
|
}
|
||||||
11
lib/Hoymiles/src/inverters/HERF_4CH.h
Normal file
11
lib/Hoymiles/src/inverters/HERF_4CH.h
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HM_4CH.h"
|
||||||
|
|
||||||
|
class HERF_4CH : public HM_4CH {
|
||||||
|
public:
|
||||||
|
explicit HERF_4CH(HoymilesRadio* radio, const uint64_t serial);
|
||||||
|
static bool isValidSerial(const uint64_t serial);
|
||||||
|
String typeName() const;
|
||||||
|
};
|
||||||
@ -11,3 +11,5 @@
|
|||||||
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
| HMS_4CH | HMS-1600/1800/2000-4T | 1164 |
|
||||||
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
| HMT_4CH | HMT-1600/1800/2000-4T | 1361 |
|
||||||
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
| HMT_6CH | HMT-1800/2250-6T | 1382 |
|
||||||
|
| HERF_2CH | HERF 800 | 2821 |
|
||||||
|
| HERF_4CH | HERF 1800 | 2801 |
|
||||||
|
|||||||
@ -52,7 +52,11 @@ const devInfo_t devInfo[] = {
|
|||||||
{ { 0x10, 0x32, 0x71, ALL }, 2000, "HMT-2000-4T" }, // 0
|
{ { 0x10, 0x32, 0x71, ALL }, 2000, "HMT-2000-4T" }, // 0
|
||||||
|
|
||||||
{ { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800-6T" }, // 01
|
{ { 0x10, 0x33, 0x11, ALL }, 1800, "HMT-1800-6T" }, // 01
|
||||||
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250-6T" } // 01
|
{ { 0x10, 0x33, 0x31, ALL }, 2250, "HMT-2250-6T" }, // 01
|
||||||
|
|
||||||
|
{ { 0xF1, 0x01, 0x14, ALL }, 800, "HERF-800" }, // 00
|
||||||
|
{ { 0xF1, 0x01, 0x24, ALL }, 1600, "HERF-1600" }, // 00
|
||||||
|
{ { 0xF1, 0x01, 0x22, ALL }, 1800, "HERF-1800" }, // 00
|
||||||
};
|
};
|
||||||
|
|
||||||
DevInfoParser::DevInfoParser()
|
DevInfoParser::DevInfoParser()
|
||||||
|
|||||||
@ -129,7 +129,10 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial cannot be zero!";
|
retMsg["message"] = "Serial cannot be zero!";
|
||||||
retMsg["code"] = WebApiError::DtuSerialZero;
|
retMsg["code"] = WebApiError::DtuSerialZero;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -185,8 +188,7 @@ void WebApiDtuClass::onDtuAdminPost(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
CONFIG_T& config = Configuration.get();
|
CONFIG_T& config = Configuration.get();
|
||||||
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
config.Dtu.Serial = serial;
|
||||||
config.Dtu.Serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
|
||||||
config.Dtu.PollInterval = root["pollinterval"].as<uint32_t>();
|
config.Dtu.PollInterval = root["pollinterval"].as<uint32_t>();
|
||||||
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
|
config.Dtu.Nrf.PaLevel = root["nrf_palevel"].as<uint8_t>();
|
||||||
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
|
config.Dtu.Cmt.PaLevel = root["cmt_palevel"].as<int8_t>();
|
||||||
|
|||||||
@ -129,7 +129,10 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::InverterSerialZero;
|
retMsg["code"] = WebApiError::InverterSerialZero;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -158,7 +161,7 @@ void WebApiInverterClass::onInverterAdd(AsyncWebServerRequest* request)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
inverter->Serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
inverter->Serial = serial;
|
||||||
|
|
||||||
strncpy(inverter->Name, root["name"].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
strncpy(inverter->Name, root["name"].as<String>().c_str(), INV_MAX_NAME_STRLEN);
|
||||||
|
|
||||||
@ -233,7 +236,10 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::InverterSerialZero;
|
retMsg["code"] = WebApiError::InverterSerialZero;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -261,7 +267,7 @@ void WebApiInverterClass::onInverterEdit(AsyncWebServerRequest* request)
|
|||||||
|
|
||||||
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root["id"].as<uint8_t>()];
|
INVERTER_CONFIG_T& inverter = Configuration.get().Inverter[root["id"].as<uint8_t>()];
|
||||||
|
|
||||||
uint64_t new_serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
uint64_t new_serial = serial;
|
||||||
uint64_t old_serial = inverter.Serial;
|
uint64_t old_serial = inverter.Serial;
|
||||||
|
|
||||||
// Interpret the string as a hex value and convert it to uint64_t
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
|||||||
@ -100,7 +100,10 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::LimitSerialZero;
|
retMsg["code"] = WebApiError::LimitSerialZero;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -129,7 +132,6 @@ void WebApiLimitClass::onLimitPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
|
||||||
uint16_t limit = root["limit_value"].as<uint16_t>();
|
uint16_t limit = root["limit_value"].as<uint16_t>();
|
||||||
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
PowerLimitControlType type = root["limit_type"].as<PowerLimitControlType>();
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,10 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root["serial"].as<uint64_t>() == 0) {
|
// Interpret the string as a hex value and convert it to uint64_t
|
||||||
|
const uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
||||||
|
|
||||||
|
if (serial == 0) {
|
||||||
retMsg["message"] = "Serial must be a number > 0!";
|
retMsg["message"] = "Serial must be a number > 0!";
|
||||||
retMsg["code"] = WebApiError::PowerSerialZero;
|
retMsg["code"] = WebApiError::PowerSerialZero;
|
||||||
response->setLength();
|
response->setLength();
|
||||||
@ -101,7 +104,6 @@ void WebApiPowerClass::onPowerPost(AsyncWebServerRequest* request)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t serial = strtoll(root["serial"].as<String>().c_str(), NULL, 16);
|
|
||||||
auto inv = Hoymiles.getInverterBySerial(serial);
|
auto inv = Hoymiles.getInverterBySerial(serial);
|
||||||
if (inv == nullptr) {
|
if (inv == nullptr) {
|
||||||
retMsg["message"] = "Invalid inverter specified!";
|
retMsg["message"] = "Invalid inverter specified!";
|
||||||
|
|||||||
114
webapp/src/components/InputSerial.vue
Normal file
114
webapp/src/components/InputSerial.vue
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<template>
|
||||||
|
<input v-model="inputSerial" type="text" :id="id" :required="required" class="form-control" :class="inputClass" />
|
||||||
|
<BootstrapAlert show :variant="formatShow" v-if="formatHint">{{ formatHint }}</BootstrapAlert>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import BootstrapAlert from './BootstrapAlert.vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
const chars32 = '0123456789ABCDEFGHJKLMNPRSTUVWXY';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
BootstrapAlert,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
'modelValue': { type: [String, Number], required: true },
|
||||||
|
'id': String,
|
||||||
|
'inputClass': String,
|
||||||
|
'required': Boolean,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
inputSerial: "",
|
||||||
|
formatHint: "",
|
||||||
|
formatShow: "info",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
model: {
|
||||||
|
get(): any {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(value: any) {
|
||||||
|
this.$emit('update:modelValue', value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: function (val) {
|
||||||
|
this.inputSerial = val;
|
||||||
|
},
|
||||||
|
inputSerial: function (val) {
|
||||||
|
const serial = val.toString().toUpperCase(); // Convert to lowercase for case-insensitivity
|
||||||
|
|
||||||
|
if (serial == "") {
|
||||||
|
this.formatHint = "";
|
||||||
|
this.model = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formatShow = "info";
|
||||||
|
|
||||||
|
// Contains only numbers
|
||||||
|
if (/^[\d]{12}$/.test(serial)) {
|
||||||
|
this.model = serial;
|
||||||
|
this.formatHint = this.$t('inputserial.format_hoymiles');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains numbers and hex characters but at least one number
|
||||||
|
else if (/^(?=.*\d)[\dA-F]{12}$/.test(serial)) {
|
||||||
|
this.model = serial;
|
||||||
|
this.formatHint = this.$t('inputserial.format_converted');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has format: xxxxxxxxx-xxx
|
||||||
|
else if (/^((A01)|(A11)|(A21))[\dA-HJ-NR-YP]{6}-[\dA-HJ-NP-Z]{3}$/.test(serial)) {
|
||||||
|
if (this.checkHerfChecksum(serial)) {
|
||||||
|
this.model = this.convertHerfToHoy(serial);
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.formatHint = this.$t('inputserial.format_herf_valid', { serial: this.model });
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.formatHint = this.$t('inputserial.format_herf_invalid');
|
||||||
|
this.formatShow = "danger";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any other format
|
||||||
|
} else {
|
||||||
|
this.formatHint = this.$t('inputserial.format_unknown');
|
||||||
|
this.formatShow = "danger";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
checkHerfChecksum(sn: string) {
|
||||||
|
const chars64 = 'HMFLGW5XC301234567899Z67YRT2S8ABCDEFGHJKDVEJ4KQPUALMNPRSTUVWXYNB';
|
||||||
|
|
||||||
|
const checksum = sn.substring(sn.indexOf("-") + 1);
|
||||||
|
const serial = sn.substring(0, sn.indexOf("-"));
|
||||||
|
|
||||||
|
const first_char = '1';
|
||||||
|
const i = chars32.indexOf(first_char)
|
||||||
|
const sum1: number = Array.from(serial).reduce((sum, c) => sum + c.charCodeAt(0), 0) & 31;
|
||||||
|
const sum2: number = Array.from(serial).reduce((sum, c) => sum + chars32.indexOf(c), 0) & 31;
|
||||||
|
const ext = first_char + chars64[sum1 + i] + chars64[sum2 + i];
|
||||||
|
|
||||||
|
return checksum == ext;
|
||||||
|
},
|
||||||
|
convertHerfToHoy(sn: string) {
|
||||||
|
let sn_int: bigint = 0n;
|
||||||
|
|
||||||
|
for (let i = 0; i < 9; i++) {
|
||||||
|
const pos: bigint = BigInt(chars32.indexOf(sn[i].toUpperCase()));
|
||||||
|
const shift: bigint = BigInt(42 - 5 * i - (i <= 2 ? 0 : 2));
|
||||||
|
sn_int |= (pos << shift);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sn_int.toString(16);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -618,5 +618,12 @@
|
|||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"ValueSelected": "Ausgewählt",
|
"ValueSelected": "Ausgewählt",
|
||||||
"ValueActive": "Aktiv"
|
"ValueActive": "Aktiv"
|
||||||
|
},
|
||||||
|
"inputserial": {
|
||||||
|
"format_hoymiles": "Hoymiles Seriennummerformat",
|
||||||
|
"format_converted": "Bereits konvertierte Seriennummer",
|
||||||
|
"format_herf_valid": "E-Star HERF Format (wird konvertiert gespeichert): {serial}",
|
||||||
|
"format_herf_invalid": "E-Star HERF Format: Ungültige Prüfsumme",
|
||||||
|
"format_unknown": "Unbekanntes Format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -619,5 +619,12 @@
|
|||||||
"Number": "Number",
|
"Number": "Number",
|
||||||
"ValueSelected": "Selected",
|
"ValueSelected": "Selected",
|
||||||
"ValueActive": "Active"
|
"ValueActive": "Active"
|
||||||
|
},
|
||||||
|
"inputserial": {
|
||||||
|
"format_hoymiles": "Hoymiles serial number format",
|
||||||
|
"format_converted": "Already converted serial number",
|
||||||
|
"format_herf_valid": "E-Star HERF format (will be saved converted): {serial}",
|
||||||
|
"format_herf_invalid": "E-Star HERF format: Invalid checksum",
|
||||||
|
"format_unknown": "Unknown format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -618,5 +618,12 @@
|
|||||||
"Name": "Nom",
|
"Name": "Nom",
|
||||||
"ValueSelected": "Sélectionné",
|
"ValueSelected": "Sélectionné",
|
||||||
"ValueActive": "Activé"
|
"ValueActive": "Activé"
|
||||||
|
},
|
||||||
|
"inputserial": {
|
||||||
|
"format_hoymiles": "Hoymiles serial number format",
|
||||||
|
"format_converted": "Already converted serial number",
|
||||||
|
"format_herf_valid": "E-Star HERF format (will be saved converted): {serial}",
|
||||||
|
"format_herf_invalid": "E-Star HERF format: Invalid checksum",
|
||||||
|
"format_unknown": "Unknown format"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export interface DevInfoStatus {
|
export interface DevInfoStatus {
|
||||||
serial: number;
|
serial: string;
|
||||||
valid_data: boolean;
|
valid_data: boolean;
|
||||||
fw_bootloader_version: number;
|
fw_bootloader_version: number;
|
||||||
fw_build_version: number;
|
fw_build_version: number;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ export interface InverterChannel {
|
|||||||
|
|
||||||
export interface Inverter {
|
export interface Inverter {
|
||||||
id: string;
|
id: string;
|
||||||
serial: number;
|
serial: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
order: number;
|
order: number;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
export interface LimitConfig {
|
export interface LimitConfig {
|
||||||
serial: number;
|
serial: string;
|
||||||
limit_value: number;
|
limit_value: number;
|
||||||
limit_type: number;
|
limit_type: number;
|
||||||
}
|
}
|
||||||
@ -22,7 +22,7 @@ export interface InverterStatistics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Inverter {
|
export interface Inverter {
|
||||||
serial: number;
|
serial: string;
|
||||||
name: string;
|
name: string;
|
||||||
order: number;
|
order: number;
|
||||||
data_age: number;
|
data_age: number;
|
||||||
|
|||||||
@ -346,7 +346,7 @@ export default defineComponent({
|
|||||||
showAlertLimit: false,
|
showAlertLimit: false,
|
||||||
|
|
||||||
powerSettingView: {} as bootstrap.Modal,
|
powerSettingView: {} as bootstrap.Modal,
|
||||||
powerSettingSerial: 0,
|
powerSettingSerial: "",
|
||||||
powerSettingLoading: true,
|
powerSettingLoading: true,
|
||||||
alertMessagePower: "",
|
alertMessagePower: "",
|
||||||
alertTypePower: "info",
|
alertTypePower: "info",
|
||||||
@ -515,7 +515,7 @@ export default defineComponent({
|
|||||||
this.heartInterval && clearTimeout(this.heartInterval);
|
this.heartInterval && clearTimeout(this.heartInterval);
|
||||||
this.isFirstFetchAfterConnect = true;
|
this.isFirstFetchAfterConnect = true;
|
||||||
},
|
},
|
||||||
onShowEventlog(serial: number) {
|
onShowEventlog(serial: string) {
|
||||||
this.eventLogLoading = true;
|
this.eventLogLoading = true;
|
||||||
fetch("/api/eventlog/status?inv=" + serial + "&locale=" + this.$i18n.locale, { headers: authHeader() })
|
fetch("/api/eventlog/status?inv=" + serial + "&locale=" + this.$i18n.locale, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
@ -526,7 +526,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.eventLogView.show();
|
this.eventLogView.show();
|
||||||
},
|
},
|
||||||
onShowDevInfo(serial: number) {
|
onShowDevInfo(serial: string) {
|
||||||
this.devInfoLoading = true;
|
this.devInfoLoading = true;
|
||||||
fetch("/api/devinfo/status?inv=" + serial, { headers: authHeader() })
|
fetch("/api/devinfo/status?inv=" + serial, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
@ -538,7 +538,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.devInfoView.show();
|
this.devInfoView.show();
|
||||||
},
|
},
|
||||||
onShowGridProfile(serial: number) {
|
onShowGridProfile(serial: string) {
|
||||||
this.gridProfileLoading = true;
|
this.gridProfileLoading = true;
|
||||||
fetch("/api/gridprofile/status?inv=" + serial, { headers: authHeader() })
|
fetch("/api/gridprofile/status?inv=" + serial, { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
@ -555,9 +555,9 @@ export default defineComponent({
|
|||||||
|
|
||||||
this.gridProfileView.show();
|
this.gridProfileView.show();
|
||||||
},
|
},
|
||||||
onShowLimitSettings(serial: number) {
|
onShowLimitSettings(serial: string) {
|
||||||
this.showAlertLimit = false;
|
this.showAlertLimit = false;
|
||||||
this.targetLimitList.serial = 0;
|
this.targetLimitList.serial = "";
|
||||||
this.targetLimitList.limit_value = 0;
|
this.targetLimitList.limit_value = 0;
|
||||||
this.targetLimitType = 1;
|
this.targetLimitType = 1;
|
||||||
this.targetLimitTypeText = this.$t('home.Relative');
|
this.targetLimitTypeText = this.$t('home.Relative');
|
||||||
@ -611,9 +611,9 @@ export default defineComponent({
|
|||||||
this.targetLimitType = type;
|
this.targetLimitType = type;
|
||||||
},
|
},
|
||||||
|
|
||||||
onShowPowerSettings(serial: number) {
|
onShowPowerSettings(serial: string) {
|
||||||
this.showAlertPower = false;
|
this.showAlertPower = false;
|
||||||
this.powerSettingSerial = 0;
|
this.powerSettingSerial = "";
|
||||||
this.powerSettingLoading = true;
|
this.powerSettingLoading = true;
|
||||||
fetch("/api/power/status", { headers: authHeader() })
|
fetch("/api/power/status", { headers: authHeader() })
|
||||||
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
.then((response) => handleResponse(response, this.$emitter, this.$router))
|
||||||
|
|||||||
@ -8,8 +8,7 @@
|
|||||||
<form class="form-inline" v-on:submit.prevent="onSubmit">
|
<form class="form-inline" v-on:submit.prevent="onSubmit">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('inverteradmin.Serial') }}</label>
|
<label>{{ $t('inverteradmin.Serial') }}</label>
|
||||||
<input v-model="newInverterData.serial" type="number" class="form-control ml-sm-2 mr-sm-4 my-2"
|
<InputSerial v-model="newInverterData.serial" inputClass="ml-sm-2 mr-sm-4 my-2" required />
|
||||||
required />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>{{ $t('inverteradmin.Name') }}</label>
|
<label>{{ $t('inverteradmin.Name') }}</label>
|
||||||
@ -91,7 +90,7 @@
|
|||||||
<label for="inverter-serial" class="col-form-label">
|
<label for="inverter-serial" class="col-form-label">
|
||||||
{{ $t('inverteradmin.InverterSerial') }}
|
{{ $t('inverteradmin.InverterSerial') }}
|
||||||
</label>
|
</label>
|
||||||
<input v-model="selectedInverterData.serial" type="number" id="inverter-serial" class="form-control" />
|
<InputSerial v-model="selectedInverterData.serial" id="inverter-serial" />
|
||||||
<label for="inverter-name" class="col-form-label">{{ $t('inverteradmin.InverterName') }}
|
<label for="inverter-name" class="col-form-label">{{ $t('inverteradmin.InverterName') }}
|
||||||
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.InverterNameHint')" />
|
<BIconInfoCircle v-tooltip :title="$t('inverteradmin.InverterNameHint')" />
|
||||||
</label>
|
</label>
|
||||||
@ -207,6 +206,7 @@ import BasePage from '@/components/BasePage.vue';
|
|||||||
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
import BootstrapAlert from "@/components/BootstrapAlert.vue";
|
||||||
import CardElement from '@/components/CardElement.vue';
|
import CardElement from '@/components/CardElement.vue';
|
||||||
import InputElement from '@/components/InputElement.vue';
|
import InputElement from '@/components/InputElement.vue';
|
||||||
|
import InputSerial from '@/components/InputSerial.vue';
|
||||||
import ModalDialog from '@/components/ModalDialog.vue';
|
import ModalDialog from '@/components/ModalDialog.vue';
|
||||||
import type { Inverter } from '@/types/InverterConfig';
|
import type { Inverter } from '@/types/InverterConfig';
|
||||||
import { authHeader, handleResponse } from '@/utils/authentication';
|
import { authHeader, handleResponse } from '@/utils/authentication';
|
||||||
@ -235,6 +235,7 @@ export default defineComponent({
|
|||||||
BootstrapAlert,
|
BootstrapAlert,
|
||||||
CardElement,
|
CardElement,
|
||||||
InputElement,
|
InputElement,
|
||||||
|
InputSerial,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
BIconInfoCircle,
|
BIconInfoCircle,
|
||||||
BIconPencil,
|
BIconPencil,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user