Merge branch 'dev-herf' into dev

This commit is contained in:
Thomas Basler 2024-03-12 16:34:08 +01:00
commit 103207cead
22 changed files with 301 additions and 32 deletions

View File

@ -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 |

View File

@ -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) {
@ -271,4 +277,4 @@ void HoymilesClass::setMessageOutput(Print* output)
Print* HoymilesClass::getMessageOutput() Print* HoymilesClass::getMessageOutput()
{ {
return _messageOutput; return _messageOutput;
} }

View 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]);
}

View 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;
};

View 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";
}

View 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;
};

View File

@ -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 |

View File

@ -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()

View File

@ -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>();

View File

@ -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

View File

@ -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>();

View File

@ -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!";

View 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>

View File

@ -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"
} }
} }

View File

@ -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"
} }
} }

View File

@ -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"
} }
} }

View File

@ -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;
@ -8,4 +8,4 @@ export interface DevInfoStatus {
hw_version: number; hw_version: number;
hw_model_name: string; hw_model_name: string;
max_power: number; max_power: number;
} }

View File

@ -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;

View File

@ -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;
} }

View File

@ -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;
@ -53,4 +53,4 @@ export interface LiveData {
inverters: Inverter[]; inverters: Inverter[];
total: Total; total: Total;
hints: Hints; hints: Hints;
} }

View File

@ -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))

View File

@ -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,