From 768656e0c3182af611bbe58716b239a7ed382a12 Mon Sep 17 00:00:00 2001
From: Maschga <88616799+Maschga@users.noreply.github.com>
Date: Sun, 1 Feb 2026 11:59:02 +0100
Subject: [PATCH] EEBUS: configure by default (#26944)
---
api/globalconfig/types.go | 28 ++--
assets/js/components/Config/DeviceCard.vue | 13 +-
assets/js/components/Config/EebusModal.vue | 147 ++++++++++++++++--
assets/js/components/Config/HemsModal.vue | 2 +-
assets/js/components/Config/JsonModal.vue | 12 +-
.../components/Config/PropertyCertField.vue | 3 +-
assets/js/components/Config/PropertyField.vue | 4 +-
.../components/Config/defaultYaml/eebus.yaml | 12 --
assets/js/types/evcc.ts | 25 ++-
assets/js/views/Config.vue | 109 +++++--------
cmd/helper.go | 16 ++
cmd/helper_test.go | 34 ++++
cmd/root.go | 34 ++--
cmd/setup.go | 46 ++----
i18n/bg.json | 7 +-
i18n/bs.json | 5 +-
i18n/cs.json | 7 +-
i18n/da.json | 7 +-
i18n/de.json | 26 +++-
i18n/el.json | 7 +-
i18n/en.json | 26 +++-
i18n/es.json | 7 +-
i18n/fi.json | 7 +-
i18n/fr.json | 7 +-
i18n/hr.json | 7 +-
i18n/hu.json | 7 +-
i18n/it.json | 7 +-
i18n/lb.json | 7 +-
i18n/lt.json | 7 +-
i18n/nl.json | 7 +-
i18n/no.json | 7 +-
i18n/pl.json | 7 +-
i18n/pt.json | 7 +-
i18n/ro.json | 7 +-
i18n/ru.json | 7 +-
i18n/sl.json | 7 +-
i18n/sv.json | 7 +-
i18n/ta.json | 4 +-
i18n/tr.json | 7 +-
i18n/uk.json | 7 +-
i18n/zh-Hans.json | 4 +-
server/eebus/eebus.go | 44 ++----
server/eebus/test/cs_test.go | 5 +-
server/eebus/types.go | 73 ++++++++-
server/http.go | 2 +-
server/http_config_site_other_handler.go | 15 +-
server/http_global_settings_handler.go | 15 +-
tests/config-eebus.evcc.yaml | 27 ++++
tests/config-eebus.spec.ts | 137 ++++++++++++++++
tests/config-ocpp.spec.ts | 2 -
util/redact.go | 8 +
util/sponsor/auth.go | 4 +-
52 files changed, 712 insertions(+), 324 deletions(-)
delete mode 100644 assets/js/components/Config/defaultYaml/eebus.yaml
create mode 100644 cmd/helper_test.go
create mode 100644 tests/config-eebus.evcc.yaml
create mode 100644 tests/config-eebus.spec.ts
create mode 100644 util/redact.go
diff --git a/api/globalconfig/types.go b/api/globalconfig/types.go
index 3756f2f8c..4925bae63 100644
--- a/api/globalconfig/types.go
+++ b/api/globalconfig/types.go
@@ -18,6 +18,13 @@ import (
"github.com/evcc-io/evcc/util/modbus"
)
+// ConfigStatus for publishing config, status and source to UI and external systems
+type ConfigStatus struct {
+ Config any `json:"config,omitempty"`
+ Status any `json:"status,omitempty"`
+ FromYaml bool `json:"fromYaml,omitempty"`
+}
+
type All struct {
Network Network
Ocpp ocpp.Config
@@ -79,13 +86,6 @@ func (c Hems) Redacted() any {
var _ api.Redactor = (*Mqtt)(nil)
-func masked(s any) string {
- if s != "" {
- return "***"
- }
- return ""
-}
-
type Mqtt struct {
mqtt.Config `mapstructure:",squash"`
Topic string `json:"topic"`
@@ -97,12 +97,12 @@ func (m Mqtt) Redacted() any {
Config: mqtt.Config{
Broker: m.Broker,
User: m.User,
- Password: masked(m.Password),
+ Password: util.Masked(m.Password),
ClientID: m.ClientID,
Insecure: m.Insecure,
- CaCert: masked(m.CaCert),
- ClientCert: masked(m.ClientCert),
- ClientKey: masked(m.ClientKey),
+ CaCert: util.Masked(m.CaCert),
+ ClientCert: util.Masked(m.ClientCert),
+ ClientKey: util.Masked(m.ClientKey),
},
Topic: m.Topic,
}
@@ -124,10 +124,10 @@ func (c Influx) Redacted() any {
return Influx{
URL: c.URL,
Database: c.Database,
- Token: masked(c.Token),
+ Token: util.Masked(c.Token),
Org: c.Org,
User: c.User,
- Password: masked(c.Password),
+ Password: util.Masked(c.Password),
Insecure: c.Insecure,
}
}
@@ -147,7 +147,7 @@ type MessagingEventTemplate struct {
Title, Msg string
}
-func (c Messaging) Configured() bool {
+func (c Messaging) IsConfigured() bool {
return len(c.Services) > 0 || len(c.Events) > 0
}
diff --git a/assets/js/components/Config/DeviceCard.vue b/assets/js/components/Config/DeviceCard.vue
index 5c8eddbf4..aa8c030a8 100644
--- a/assets/js/components/Config/DeviceCard.vue
+++ b/assets/js/components/Config/DeviceCard.vue
@@ -6,9 +6,10 @@
'round-box--error': error,
'round-box--warning': warning,
'root--unconfigured': unconfigured,
+ 'root--with-tags': $slots.tags,
}"
>
-
@@ -67,7 +70,7 @@ export default {
}
let title = `${this.$t("config.main.name")}: ${this.name}`;
if (!this.editable) {
- title += `${this.$t("config.main.yaml")}
`;
+ title += `${this.$t("config.general.fromYamlHint")}
`;
}
return `${title}
`;
},
@@ -105,6 +108,8 @@ export default {
list-style-type: none;
border-radius: 1rem;
padding: 1rem 1.5rem;
+}
+.root--with-tags {
min-height: 8rem;
}
.root--unconfigured {
diff --git a/assets/js/components/Config/EebusModal.vue b/assets/js/components/Config/EebusModal.vue
index d4e499669..ff7c48df5 100644
--- a/assets/js/components/Config/EebusModal.vue
+++ b/assets/js/components/Config/EebusModal.vue
@@ -1,26 +1,153 @@
-
+ >
+
+
+ {{ $t("config.general.fromYamlHint") }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t("config.eebus.descriptionAdvanced") }}
+
+
+
+
+
+
+
+
+
+
+ {{ $t("config.eebus.certificate.title") }}
+
+
+
+
+
+
+
+
+
+
-
diff --git a/assets/js/components/Config/HemsModal.vue b/assets/js/components/Config/HemsModal.vue
index cd293ef25..cab449f58 100644
--- a/assets/js/components/Config/HemsModal.vue
+++ b/assets/js/components/Config/HemsModal.vue
@@ -31,7 +31,7 @@
- {{ $t("config.hems.yamlConfigured") }}
+ {{ $t("config.general.fromYamlHint") }}
diff --git a/assets/js/components/Config/JsonModal.vue b/assets/js/components/Config/JsonModal.vue
index e3338bf55..010028e01 100644
--- a/assets/js/components/Config/JsonModal.vue
+++ b/assets/js/components/Config/JsonModal.vue
@@ -88,6 +88,7 @@ export default {
saveMethod: { type: String, default: "post" },
storeValuesInArray: Boolean,
size: { type: String },
+ confirmRemove: String,
},
emits: ["changed", "open"],
data() {
@@ -121,7 +122,13 @@ export default {
await this.load();
},
async load() {
- this.serverValues = this.stateKey ? store.state[this.stateKey] : store.state;
+ if (this.stateKey) {
+ // Support nested keys like "eebus.config"
+ const keys = this.stateKey.split(".");
+ this.serverValues = keys.reduce((obj, key) => obj?.[key], store.state);
+ } else {
+ this.serverValues = store.state;
+ }
if (this.transformReadValues) {
this.serverValues = this.transformReadValues(this.serverValues);
}
@@ -155,6 +162,9 @@ export default {
this.saving = false;
},
async remove() {
+ if (this.confirmRemove && !window.confirm(this.confirmRemove)) {
+ return;
+ }
this.removing = true;
this.error = "";
try {
diff --git a/assets/js/components/Config/PropertyCertField.vue b/assets/js/components/Config/PropertyCertField.vue
index c71722301..df64a242d 100644
--- a/assets/js/components/Config/PropertyCertField.vue
+++ b/assets/js/components/Config/PropertyCertField.vue
@@ -1,6 +1,6 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
{{ $t("config.section.services") }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t("config.section.system") }}
@@ -387,7 +378,11 @@
:extMeters="extMeters"
@changed="yamlChanged"
/>
-
+
@@ -656,12 +651,6 @@ export default defineComponent({
vehicleOptions(): VehicleOption[] {
return this.vehicles.map((v) => ({ key: v.name, name: v.config?.title || v.name }));
},
- shmTags(): DeviceTags {
- const { vendorId, deviceId } = store.state?.shm || {};
- // TODO: use incoming SEMP connections to determin configured/active status
- const value = !!vendorId || !!deviceId;
- return { configured: { value } };
- },
hems() {
return store.state?.hems;
},
@@ -698,30 +687,8 @@ export default defineComponent({
// @ts-expect-error: telemetry property exists but not in TypeScript definitions
return store.state?.telemetry === true;
},
- eebusTags(): DeviceTags {
- return { configured: { value: store.state?.eebus || false } };
- },
- ocppTags(): DeviceTags {
- const ocpp = store.state?.ocpp;
- const stations = ocpp?.status?.stations || [];
- if (stations.length === 0) {
- return { configured: { value: false } };
- }
-
- const connected = stations.filter((s) => s.status === "connected").length;
- const configured = stations.filter((s) => s.status === "configured").length;
- const detected = stations.filter((s) => s.status === "unknown").length;
- const total = connected + configured;
-
- const tags: Record
= {
- connections: { value: `${connected}/${total}` },
- };
-
- if (detected > 0) {
- tags["detected"] = { value: detected };
- }
-
- return tags;
+ eebus() {
+ return store.state?.eebus;
},
modbusproxyTags(): DeviceTags {
const config = store.state?.modbusproxy || [];
diff --git a/cmd/helper.go b/cmd/helper.go
index 981aaea32..f0d76e6c2 100644
--- a/cmd/helper.go
+++ b/cmd/helper.go
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/evcc-io/evcc/cmd/shutdown"
+ "github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/config"
"go.yaml.in/yaml/v4"
@@ -120,3 +121,18 @@ func deviceHeader[T any](dev config.Device[T]) string {
return name
}
+
+// migrateYamlToJson converts a settings value from yaml to json if needed
+func migrateYamlToJson[T any](key string, res *T) error {
+ if settings.IsJson(key) {
+ return settings.Json(key, res)
+ }
+
+ if err := settings.Yaml(key, new(T), res); err != nil {
+ return err
+ }
+
+ settings.SetJson(key, res)
+
+ return nil
+}
diff --git a/cmd/helper_test.go b/cmd/helper_test.go
new file mode 100644
index 000000000..7f2c81d8f
--- /dev/null
+++ b/cmd/helper_test.go
@@ -0,0 +1,34 @@
+package cmd
+
+import (
+ "testing"
+
+ "github.com/evcc-io/evcc/server/db"
+ "github.com/evcc-io/evcc/server/db/settings"
+ "github.com/stretchr/testify/require"
+)
+
+func TestMigrateYaml(t *testing.T) {
+ require.NoError(t, db.NewInstance("sqlite", ":memory:"))
+
+ const key = "foo"
+
+ type T struct {
+ Key string
+ Val []string
+ }
+
+ expect := T{
+ Key: "foo",
+ Val: []string{"a", "b"},
+ }
+
+ require.NoError(t, settings.SetYaml(key, expect))
+ require.False(t, settings.IsJson(key))
+
+ var res T
+ require.NoError(t, migrateYamlToJson(key, &res))
+ require.True(t, settings.IsJson(key))
+
+ require.Equal(t, expect, res)
+}
diff --git a/cmd/root.go b/cmd/root.go
index fe123aab3..511672865 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -20,6 +20,7 @@ import (
"github.com/evcc-io/evcc/push"
"github.com/evcc-io/evcc/server"
"github.com/evcc-io/evcc/server/db"
+ "github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/server/mcp"
"github.com/evcc-io/evcc/server/network"
"github.com/evcc-io/evcc/server/updater"
@@ -176,10 +177,7 @@ func runRoot(cmd *cobra.Command, args []string) {
ocppCS := ocpp.Instance()
ocppCS.SetUpdated(func() {
// republish when OCPP state updates
- valueChan <- util.Param{Key: keys.Ocpp, Val: struct {
- Config ocpp.Config `json:"config"`
- Status ocpp.Status `json:"status"`
- }{
+ valueChan <- util.Param{Key: keys.Ocpp, Val: globalconfig.ConfigStatus{
Config: conf.Ocpp,
Status: ocpp.GetStatus(),
}}
@@ -329,35 +327,29 @@ func runRoot(cmd *cobra.Command, args []string) {
}
// publish initial settings
- valueChan <- util.Param{Key: keys.EEBus, Val: conf.EEBus.Configured()}
+ valueChan <- util.Param{Key: keys.EEBus, Val: globalconfig.ConfigStatus{
+ Config: conf.EEBus.Redacted(),
+ Status: eebus.GetStatus(),
+ FromYaml: fromYaml.eebus,
+ }}
valueChan <- util.Param{Key: keys.Shm, Val: conf.SHM}
valueChan <- util.Param{Key: keys.Influx, Val: conf.Influx}
valueChan <- util.Param{Key: keys.Interval, Val: conf.Interval}
- valueChan <- util.Param{Key: keys.Messaging, Val: conf.Messaging.Configured()}
+ valueChan <- util.Param{Key: keys.Messaging, Val: conf.Messaging.IsConfigured()}
valueChan <- util.Param{Key: keys.ModbusProxy, Val: conf.ModbusProxy}
valueChan <- util.Param{Key: keys.Mqtt, Val: conf.Mqtt}
valueChan <- util.Param{Key: keys.Network, Val: conf.Network}
- valueChan <- util.Param{Key: keys.Ocpp, Val: struct {
- Config ocpp.Config `json:"config"`
- Status ocpp.Status `json:"status"`
- }{
+ valueChan <- util.Param{Key: keys.Ocpp, Val: globalconfig.ConfigStatus{
Config: conf.Ocpp,
Status: ocpp.GetStatus(),
}}
- valueChan <- util.Param{Key: keys.Sponsor, Val: struct {
- Status sponsor.Status `json:"status"`
- FromYaml bool `json:"fromYaml"`
- }{
- Status: sponsor.GetStatus(),
+ valueChan <- util.Param{Key: keys.Sponsor, Val: globalconfig.ConfigStatus{
+ Status: sponsor.RedactedStatus(),
FromYaml: fromYaml.sponsor,
}}
- valueChan <- util.Param{Key: keys.Hems, Val: struct {
- Config globalconfig.Hems `json:"config"`
- Status *hemsapi.Status `json:"status,omitempty"`
- FromYaml bool `json:"fromYaml"`
- }{
- Config: conf.HEMS,
+ valueChan <- util.Param{Key: keys.Hems, Val: globalconfig.ConfigStatus{
+ Config: conf.HEMS.Redacted(),
Status: hemsapi.GetStatus(hems),
FromYaml: fromYaml.hems,
}}
diff --git a/cmd/setup.go b/cmd/setup.go
index a11340c5f..3ee232e75 100644
--- a/cmd/setup.go
+++ b/cmd/setup.go
@@ -73,7 +73,7 @@ var conf = globalconfig.All{
Topic: "evcc",
},
EEBus: eebus.Config{
- URI: ":4712",
+ Port: 4712,
},
Database: globalconfig.DB{
Type: "sqlite",
@@ -84,6 +84,7 @@ var conf = globalconfig.All{
var fromYaml struct {
sponsor bool
hems bool
+ eebus bool
}
var nameRE = regexp.MustCompile(`^[a-zA-Z0-9_.:-]+$`)
@@ -164,7 +165,7 @@ func configureCircuits(conf *[]config.Named) error {
children := slices.Clone(*conf)
- // TODO: check for circular references
+ // TODO check for circular references
NEXT:
for i, cc := range children {
if cc.Name == "" {
@@ -780,14 +781,23 @@ func configureOCPP(cfg *ocpp.Config, externalUrl string) {
func configureEEBus(conf *eebus.Config) error {
// migrate settings
if settings.Exists(keys.EEBus) {
- *conf = eebus.Config{}
- if err := settings.Yaml(keys.EEBus, new(map[string]any), &conf); err != nil {
+ if err := migrateYamlToJson(keys.EEBus, conf); err != nil {
return err
}
+ } else if conf.IsConfigured() {
+ fromYaml.eebus = true
}
- if !conf.Configured() {
- return nil
+ if !conf.IsConfigured() {
+ cc, err := eebus.DefaultConfig(conf)
+ if err != nil {
+ return err
+ }
+
+ *conf = *cc
+ if err := settings.SetJson(keys.EEBus, conf); err != nil {
+ return err
+ }
}
var err error
@@ -968,33 +978,11 @@ func configureDevices(conf globalconfig.All) error {
return joinErrors(errs...)
}
-// migrateYamlToJson converts a settings value from yaml to json if needed
-func migrateYamlToJson[T any](key string) error {
- var err error
- if settings.IsJson(key) {
- // already JSON, nothing to do
- return nil
- }
-
- var data T
- if err := settings.Yaml(key, new(T), &data); err == nil {
- settings.SetJson(key, data)
- log.INFO.Printf("migrated %s setting to JSON", key)
- }
-
- return err
-}
-
func configureModbusProxy(conf *[]globalconfig.ModbusProxy) error {
if settings.Exists(keys.ModbusProxy) {
- // TODO: delete if not needed any more
- if err := migrateYamlToJson[[]globalconfig.ModbusProxy](keys.ModbusProxy); err != nil {
+ if err := migrateYamlToJson(keys.ModbusProxy, conf); err != nil {
return err
}
-
- if err := settings.Json(keys.ModbusProxy, &conf); err != nil {
- return fmt.Errorf("failed to read modbusproxy setting: %w", err)
- }
}
// prevent panic
diff --git a/i18n/bg.json b/i18n/bg.json
index 7c384325c..08216a8b5 100644
--- a/i18n/bg.json
+++ b/i18n/bg.json
@@ -209,6 +209,7 @@
"dragList": "Списък за повторна поръчка",
"experimental": "Експериментален",
"forceSave": "Запази все пак",
+ "fromYamlHint": "Забележка: Конфигурира се чрез evcc.yaml. Премахнете записа от файла, за да активирате редактирането тук.",
"hideAdvancedSettings": "Скрий разширените настройки",
"invalidFileSelected": "Избраният файл е невалиден",
"noFileSelected": "Няма избран файл.",
@@ -247,8 +248,7 @@
"downloadCsv": "Изтегли CSV",
"eventsRecorded": "Записани {count} събития за ограничение на мрежата.",
"lastEvent": "Най-скорошно {timeAgo}.",
- "title": "Външен лимит",
- "yamlConfigured": "Забележка: Конфигурира се чрез evcc.yaml. Премахнете записа от файла, за да активирате редактирането тук."
+ "title": "Външен лимит"
},
"icon": {
"change": "промени",
@@ -390,8 +390,7 @@
"unconfigured": "не е конфигуриран",
"vehicles": "Моите Автомобили",
"welcomeBannerText": "Започнете с създаването на поне един **зарядно устройство**, **нагревател**, **мрежа**, **соларна система**, **батерия** или **допълнителен електромер**. Ако искате само да тествате, изберете **демонстрационно устройство**..",
- "welcomeBannerTitle": "Да конфигурираме вашата система!",
- "yaml": "Устройствата от evcc.yaml не могат да се редактират."
+ "welcomeBannerTitle": "Да конфигурираме вашата система!"
},
"messaging": {
"description": "Получавайте съобщения за вашите сесии на зареждане.",
diff --git a/i18n/bs.json b/i18n/bs.json
index 2f99fc17d..dd04a2959 100644
--- a/i18n/bs.json
+++ b/i18n/bs.json
@@ -209,6 +209,7 @@
"dragList": "Ponovo naručiva lista",
"experimental": "Eksperimentalni",
"forceSave": "Sačuvaj svejedno",
+ "fromYamlHint": "Napomena: Konfigurisano putem evcc.yaml. Uklonite unos iz datoteke da biste ovdje omogućili uređivanje.",
"hideAdvancedSettings": "Sakrij napredne postavke",
"invalidFileSelected": "Odabrana neispravna datoteka",
"noFileSelected": "Nije odabrana nijedna datoteka.",
@@ -247,8 +248,7 @@
"downloadCsv": "Preuzmi CSV",
"eventsRecorded": "Zabilježeno je {count} događaja ograničenja mreže.",
"lastEvent": "Najnoviji prije {timeAgo}.",
- "title": "Vanjski limit",
- "yamlConfigured": "Napomena: Konfigurisano putem evcc.yaml. Uklonite unos iz datoteke da biste ovdje omogućili uređivanje."
+ "title": "Vanjski limit"
},
"icon": {
"change": "promjena",
@@ -344,6 +344,7 @@
"thresholdDisableHelpZero": "Zaustavite kada se za {delay} ne može zadovoljiti minimalna potrebna snaga.",
"thresholdEnableHelpZero": " Počnite kada se minimalna potrebna snaga može zadovoljiti za {delay}."
},
+ "main": {},
"meter": {
"save": "Sačuvaj"
},
diff --git a/i18n/cs.json b/i18n/cs.json
index 5b286d897..e23116553 100644
--- a/i18n/cs.json
+++ b/i18n/cs.json
@@ -209,6 +209,7 @@
"dragList": "Seznam umožňující úpravy pořadí",
"experimental": "Experimentální nastavení",
"forceSave": "Vynutit uložení",
+ "fromYamlHint": "Poznámka: Konfigurace se provádí prostřednictvím souboru evcc.yaml. Chcete-li zde povolit úpravy, odstraňte záznam ze souboru.",
"hideAdvancedSettings": "Skrýt pokročilá nastavení",
"invalidFileSelected": "Zvolen neplatný soubor",
"noFileSelected": "Nebyl zvolen žádný soubor.",
@@ -247,8 +248,7 @@
"downloadCsv": "Stáhnout CSV",
"eventsRecorded": "Zaznamenáno {count} událostí omezení mřížky.",
"lastEvent": "Nejnovější {timeAgo}.",
- "title": "Vnější limit",
- "yamlConfigured": "Poznámka: Konfigurace se provádí prostřednictvím souboru evcc.yaml. Chcete-li zde povolit úpravy, odstraňte záznam ze souboru."
+ "title": "Vnější limit"
},
"icon": {
"change": "Změnit",
@@ -390,8 +390,7 @@
"unconfigured": "není konfigurováno",
"vehicles": "Má vozidla",
"welcomeBannerText": "Začněte vytvořením alespoň jednoho **nabíječe**, **ohřívače**, **sítě**, **solárního panelu**, **baterie** nebo **dalšího měřiče**. Pokud chcete pouze otestovat, vyberte **demo zařízení**.",
- "welcomeBannerTitle": "Pojďme nakonfigurovat váš systém!",
- "yaml": "Zařízení z evcc.yaml nelze upravovat."
+ "welcomeBannerTitle": "Pojďme nakonfigurovat váš systém!"
},
"messaging": {
"description": "Dostávejte notifikace o průběhu nabíjení.",
diff --git a/i18n/da.json b/i18n/da.json
index 78a847c44..577cd8d59 100644
--- a/i18n/da.json
+++ b/i18n/da.json
@@ -209,6 +209,7 @@
"dragList": "Rækkefølgen kan ændres",
"experimental": "Eksperimentel",
"forceSave": "Gem alligevel",
+ "fromYamlHint": "Bemærk: Er konfigureret via evcc.yaml. Fjern indtastningen fra filen for at aktivere redigering her.",
"hideAdvancedSettings": "Skjul avancerede indstillinger",
"invalidFileSelected": "Ugyldig fil er valgt",
"noFileSelected": "Ingen fil valgt.",
@@ -247,8 +248,7 @@
"downloadCsv": "Download CSV",
"eventsRecorded": "Registrerede {count} netbegrænsningshændelser.",
"lastEvent": "Sidst {timeAgo}.",
- "title": "Ekstern grænse",
- "yamlConfigured": "Bemærk: Er konfigureret via evcc.yaml. Fjern indtastningen fra filen for at aktivere redigering her."
+ "title": "Ekstern grænse"
},
"icon": {
"change": "ændre",
@@ -390,8 +390,7 @@
"unconfigured": "ikke konfigureret",
"vehicles": "Mine køretøjer",
"welcomeBannerText": "Start med at oprette mindst én **lader**, **varmer**, **elnet**, **sol**, **batteri** eller **ekstra måler**. Hvis du blot vil teste, kan du vælge en **demoenhed**.",
- "welcomeBannerTitle": "Lad os konfigurere dit system!",
- "yaml": "Enheder fra evcc.yaml kan ikke ændres her."
+ "welcomeBannerTitle": "Lad os konfigurere dit system!"
},
"messaging": {
"description": "Modtag beskeder om dine opladningssessioner.",
diff --git a/i18n/de.json b/i18n/de.json
index fe124f286..0b769fe24 100644
--- a/i18n/de.json
+++ b/i18n/de.json
@@ -172,7 +172,23 @@
"loading": "Lade YAML-Editor …"
},
"eebus": {
- "description": "Grundkonfiguration für die Kommunikation mit anderen EEBus-Geräten.",
+ "certificate": {
+ "private": "Privater Schlüssel",
+ "public": "Öffentliches Zertifikat",
+ "title": "Zertifikate"
+ },
+ "description": "Konfiguration zur Kommunikation mit EEBus-kompatiblen Geräten wie Wallboxen oder dem Steuergerät des Netzbetreibers. Alle notwendigen Initialisierungen und die Zertifikatsgenerierung erfolgen automatisch beim ersten Start.",
+ "descriptionAdvanced": "Keine Änderungen erforderlich. Bei Änderung ist Neukoppelung aller Geräte erforderlich.",
+ "interfaces": "Schnittstellen",
+ "interfacesHelp": "Begrenzt die Netzwerkschnittstellen, die EEBus nutzen soll, um Kommunikationsprobleme zu vermeiden. Feld leer lassen, um alle Schnittstellen zu verwenden. Ein Eintrag pro Zeile.",
+ "port": "Port",
+ "portHelp": "Der zu verwendende Port.",
+ "removeConfirm": "Die gesamte EEBus-Konfiguration wird entfernt. Neue Zertifikate und Kennungen werden beim nächsten Start generiert. Bist du sicher?",
+ "shipid": "SHIP-ID",
+ "shipidExplain": "Permanente Gerätekennung zur Identifikation im EEBus-Netzwerk.",
+ "shipidHelp": "Diese SHIP-ID ist mit den unten stehenden Zertifikaten verknüpft.",
+ "ski": "SKI",
+ "skiExplain": "Eindeutige Sicherheitskennung zum Koppeln von EEBus-Geräten.",
"title": "EEBus"
},
"experimental": {
@@ -209,6 +225,7 @@
"dragList": "Sortierbare Liste",
"experimental": "Experimentell",
"forceSave": "Trotzdem speichern",
+ "fromYamlHint": "Hinweis: Konfiguriert über evcc.yaml. Entferne den Eintrag aus der Datei, um die Bearbeitung hier zu aktivieren.",
"hideAdvancedSettings": "Erweiterte Einstellungen ausblenden",
"invalidFileSelected": "Ungültige Datei ausgewählt",
"noFileSelected": "Keine Datei ausgewählt.",
@@ -247,8 +264,7 @@
"downloadCsv": "CSV herunterladen",
"eventsRecorded": "{count} Netzbegrenzungen gespeichert.",
"lastEvent": "Zuletzt {timeAgo}.",
- "title": "Externe Begrenzung",
- "yamlConfigured": "Hinweis: Konfiguriert über evcc.yaml. Entferne den Eintrag aus der Datei, um die Bearbeitung hier zu aktivieren."
+ "title": "Externe Begrenzung"
},
"icon": {
"change": "ändern",
@@ -390,8 +406,7 @@
"unconfigured": "nicht konfiguriert",
"vehicles": "Meine Fahrzeuge",
"welcomeBannerText": "Lege zunächst eine **Wallbox**, **Heizung**, **Netzzähler**, **Solar**, **Batterie** oder **zusätzlichen Zähler** an. Falls du nur testen möchtest, wähle ein **Demogerät**.",
- "welcomeBannerTitle": "Los geht's mit der Konfiguration!",
- "yaml": "Geräte aus evcc.yaml sind nicht editierbar."
+ "welcomeBannerTitle": "Los geht's mit der Konfiguration!"
},
"messaging": {
"description": "Benachrichtigungen über Ladevorgänge und andere Ereignisse erhalten.",
@@ -543,6 +558,7 @@
"integrations": "Integrationen",
"loadpoints": "Laden & Heizen",
"meter": "PV & Batterie",
+ "services": "Dienste",
"system": "System",
"vehicles": "Fahrzeuge"
},
diff --git a/i18n/el.json b/i18n/el.json
index c13fe61fb..573221b19 100644
--- a/i18n/el.json
+++ b/i18n/el.json
@@ -209,6 +209,7 @@
"dragList": "Λίστα με αναδιάταξη",
"experimental": "Πειραματικό",
"forceSave": "Αποθήκευση ούτως ή άλλως",
+ "fromYamlHint": "Σημείωση: Διαμορφώνεται μέσω του αρχείου evcc.yaml. Διαγράψτε την καταχώριση από το αρχείο για να ενεργοποιήσετε την επεξεργασία εδώ.",
"hideAdvancedSettings": "Απόκρυψη ρυθμίσεων για προχωρημένους",
"invalidFileSelected": "Επιλέχθηκε μη έγκυρο αρχείο",
"noFileSelected": "Δεν έχει επιλεγεί αρχείο.",
@@ -247,8 +248,7 @@
"downloadCsv": "Λήψη CSV",
"eventsRecorded": "Καταγράφηκαν {count} συμβάντα περιορισμού πλέγματος.",
"lastEvent": "Πιο πρόσφατο {timeAgo}.",
- "title": "Εξωτερικό όριο",
- "yamlConfigured": "Σημείωση: Διαμορφώνεται μέσω του αρχείου evcc.yaml. Διαγράψτε την καταχώριση από το αρχείο για να ενεργοποιήσετε την επεξεργασία εδώ."
+ "title": "Εξωτερικό όριο"
},
"icon": {
"change": "αλλαγή",
@@ -390,8 +390,7 @@
"unconfigured": "δεν έχει ρυθμιστεί",
"vehicles": "Τα Οχήματά μου",
"welcomeBannerText": "Ξεκινήστε δημιουργώντας τουλάχιστον έναν **φορτιστή**, **θερμαντήρα**, **πλέγμα**, **ηλιακό**, **μπαταρία** ή **επιπλέον μετρητή**. Αν θέλετε απλώς να δοκιμάσετε, επιλέξτε μια **συσκευή επίδειξης**.",
- "welcomeBannerTitle": "Ας διαμορφώσουμε το σύστημά σας!",
- "yaml": "Συσκευές που ορίζονται στο evcc.yaml δεν είναι εδώ προς επεξεργασία."
+ "welcomeBannerTitle": "Ας διαμορφώσουμε το σύστημά σας!"
},
"messaging": {
"description": "Λάβετε μηνύματα σχετικά με τις συνεδρίες φόρτισης.",
diff --git a/i18n/en.json b/i18n/en.json
index 1f8d28b98..ac1cfc274 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -172,7 +172,23 @@
"loading": "Loading YAML editor…"
},
"eebus": {
- "description": "Configuration that enables evcc to communicate with other EEBus devices.",
+ "certificate": {
+ "private": "Private key",
+ "public": "Public certificate",
+ "title": "Certificates"
+ },
+ "description": "Configuration that enables evcc to communicate with EEBus compatible devices like chargers or a control unit of your grid operator. All relevant initialization and certificate generation is done automatically on first start.",
+ "descriptionAdvanced": "No changes required. Only perform changes if you really know what you're doing. If you change either the SHIP-id or the certificates, you'll need to pair your devices again.",
+ "interfaces": "Interfaces",
+ "interfacesHelp": "Limit the network interfaces that EEBus should use to avoid communication problems. Leave the field blank to use all interfaces. One entry per line.",
+ "port": "Port",
+ "portHelp": "The port to be used.",
+ "removeConfirm": "All EEBus configuration will be removed. New certificates and identifiers will be generated on next start. Are you sure?",
+ "shipid": "SHIP-ID",
+ "shipidExplain": "Permanent device identifier for identification in the EEBus network.",
+ "shipidHelp": "This SHIP-ID is linked to the certificates below.",
+ "ski": "SKI",
+ "skiExplain": "Unique security identifier for pairing EEBus devices.",
"title": "EEBus"
},
"experimental": {
@@ -209,6 +225,7 @@
"dragList": "Reorderable list",
"experimental": "Experimental",
"forceSave": "Save anyway",
+ "fromYamlHint": "Note: Configured via evcc.yaml. Remove the entry from the file to enable editing here.",
"hideAdvancedSettings": "Hide advanced settings",
"invalidFileSelected": "Invalid file selected",
"noFileSelected": "No file selected.",
@@ -247,8 +264,7 @@
"downloadCsv": "Download CSV",
"eventsRecorded": "Recorded {count} grid limitation events.",
"lastEvent": "Most recent {timeAgo}.",
- "title": "External Limit",
- "yamlConfigured": "Note: Configured via evcc.yaml. Remove the entry from the file to enable editing here."
+ "title": "External Limit"
},
"icon": {
"change": "change",
@@ -390,8 +406,7 @@
"unconfigured": "not configured",
"vehicles": "My Vehicles",
"welcomeBannerText": "Start with creating at least one **charger**, **heater**, **grid**, **solar**, **battery** or **additional meter**. If you just want to test, pick a **demo device**.",
- "welcomeBannerTitle": "Let's configure your system!",
- "yaml": "Devices from evcc.yaml are not editable."
+ "welcomeBannerTitle": "Let's configure your system!"
},
"messaging": {
"description": "Receive messages about your charging sessions.",
@@ -543,6 +558,7 @@
"integrations": "Integrations",
"loadpoints": "Charging & Heating",
"meter": "Solar & Battery",
+ "services": "Services",
"system": "System",
"vehicles": "Vehicles"
},
diff --git a/i18n/es.json b/i18n/es.json
index aba55ef1f..779787a43 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -209,6 +209,7 @@
"dragList": "Lista reordenable",
"experimental": "Experimental",
"forceSave": "Guardar de todos modos",
+ "fromYamlHint": "Nota: Configurado a través de evcc.yaml. Elimina la entrada del archivo para poder editar aquí.",
"hideAdvancedSettings": "Ocultar configuración avanzada",
"invalidFileSelected": "Se ha seleccionado un archivo no válido",
"noFileSelected": "No hay ningún archivo seleccionado.",
@@ -247,8 +248,7 @@
"downloadCsv": "Descargar CSV",
"eventsRecorded": "Se registraron {count} eventos de limitación de la red.",
"lastEvent": "Más reciente {timeAgo}.",
- "title": "Límite externo",
- "yamlConfigured": "Nota: Configurado a través de evcc.yaml. Elimina la entrada del archivo para poder editar aquí."
+ "title": "Límite externo"
},
"icon": {
"change": "cambio",
@@ -390,8 +390,7 @@
"unconfigured": "no configurado",
"vehicles": "Mis vehículos",
"welcomeBannerText": "Empiece por crear al menos un **cargador**, **calentador**, **red**, **solar**, **batería** o **contador adicional**. Si solo desea realizar una prueba, elija un **dispositivo de demostración**.",
- "welcomeBannerTitle": "¡Configuremos su sistema!",
- "yaml": "Los dispositivos de evcc.yaml no se pueden editar."
+ "welcomeBannerTitle": "¡Configuremos su sistema!"
},
"messaging": {
"description": "Recibe mensajes sobre tus sesiones de carga.",
diff --git a/i18n/fi.json b/i18n/fi.json
index 290e4b0a9..53ea68b59 100644
--- a/i18n/fi.json
+++ b/i18n/fi.json
@@ -209,6 +209,7 @@
"dragList": "Järjestettävä lista",
"experimental": "Kokeellinen",
"forceSave": "Tallenna joka tapauksessa",
+ "fromYamlHint": "Huomio: Asetus on evcc.yaml-tiedostosta. Poista asetus tiedostosta, jotta voisit muokata sitä täällä.",
"hideAdvancedSettings": "Piilota edistyneet asetukset",
"invalidFileSelected": "Valittu virheellinen tiedosto",
"noFileSelected": "Ei valittua tiedostoa.",
@@ -247,8 +248,7 @@
"downloadCsv": "Lataa CSV-taulukko",
"eventsRecorded": "Tallennettu {count} verkon rajoitustapahtumaa.",
"lastEvent": "Viimeisin {timeAgo}.",
- "title": "Ulkoinen raja",
- "yamlConfigured": "Huomio: Asetus on evcc.yaml-tiedostosta. Poista asetus tiedostosta, jotta voisit muokata sitä täällä."
+ "title": "Ulkoinen raja"
},
"icon": {
"change": "vaihda",
@@ -390,8 +390,7 @@
"unconfigured": "ei määritetty",
"vehicles": "Minun ajoneuvot",
"welcomeBannerText": "Aloita luomalla vähintään yksi **laturi**, **lämmitin**, **sähköverkko**, **aurinkopaneeli**, **akku** tai **lisämittari**. Jos haluat vain testata, valitse **esittelylaite**.",
- "welcomeBannerTitle": "Määritetään järjestelmäsi!",
- "yaml": "evcc.yaml-asetuksissa määritettyjä laitteita ei voi muokata."
+ "welcomeBannerTitle": "Määritetään järjestelmäsi!"
},
"messaging": {
"description": "Vastaanota viestejä koskien lataustapahtumiasi.",
diff --git a/i18n/fr.json b/i18n/fr.json
index 1e52d016b..60267322f 100644
--- a/i18n/fr.json
+++ b/i18n/fr.json
@@ -209,6 +209,7 @@
"dragList": "Liste à ordonner",
"experimental": "Expérimental",
"forceSave": "Enregistrer quand même",
+ "fromYamlHint": "Remarque : configuré via evcc.yaml. Supprimez l'entrée du fichier pour pouvoir effectuer des modifications ici.",
"hideAdvancedSettings": "Masquer les paramètres avancés",
"invalidFileSelected": "Fichier sélectionné non valide",
"noFileSelected": "Aucun fichier sélectionné.",
@@ -247,8 +248,7 @@
"downloadCsv": "Télécharger CSV",
"eventsRecorded": "Enregistrement de {count} événements liés à la limitation de la grille.",
"lastEvent": "Le plus récent {timeAgo}.",
- "title": "Limite externe",
- "yamlConfigured": "Remarque : configuré via evcc.yaml. Supprimez l'entrée du fichier pour pouvoir effectuer des modifications ici."
+ "title": "Limite externe"
},
"icon": {
"change": "changer",
@@ -390,8 +390,7 @@
"unconfigured": "non configuré",
"vehicles": "Mes véhicules",
"welcomeBannerText": "Commencez par créer au moins un **chargeur**, un **chauffage**, un **réseau**, un **système solaire**, une **batterie** ou un **compteur supplémentaire**. Si vous souhaitez simplement tester, choisissez un **appareil de démonstration**.",
- "welcomeBannerTitle": "Configurons votre système !",
- "yaml": "Un appareil provenant du evcc.yaml n’est pas modifiable."
+ "welcomeBannerTitle": "Configurons votre système !"
},
"messaging": {
"description": "Recevoir des messages à propos de vos sessions de charge.",
diff --git a/i18n/hr.json b/i18n/hr.json
index 9f855d687..c6c412457 100644
--- a/i18n/hr.json
+++ b/i18n/hr.json
@@ -209,6 +209,7 @@
"dragList": "Preurediv popis",
"experimental": "Eksperimentalno",
"forceSave": "Svejedno spremi",
+ "fromYamlHint": "Napomena: Konfigurirano putem evcc.yaml. Uklonite unos iz datoteke kako biste ovdje omogućili uređivanje.",
"hideAdvancedSettings": "Sakrij napredne postavke",
"invalidFileSelected": "Neispravna datoteka",
"noFileSelected": "Datoteka nije odabrana.",
@@ -247,8 +248,7 @@
"downloadCsv": "Preuzmi CSV",
"eventsRecorded": "Zabilježeno je {count} događaja ograničenja mreže.",
"lastEvent": "Najnoviji {timeAgo}",
- "title": "Vanjski limit",
- "yamlConfigured": "Napomena: Konfigurirano putem evcc.yaml. Uklonite unos iz datoteke kako biste ovdje omogućili uređivanje."
+ "title": "Vanjski limit"
},
"icon": {
"change": "promjeni",
@@ -390,8 +390,7 @@
"unconfigured": "nije postavljeno",
"vehicles": "Moja vozila",
"welcomeBannerText": "Počnite s izradom barem jednog **punjača**, **grijača**, **mreže**, **solarne ploče**, **baterije** ili **dodatnog brojila**. Ako samo želite testirati, odaberite **demo uređaj**.",
- "welcomeBannerTitle": "Konfigurirajmo vaš sustav!",
- "yaml": "U evcc.yaml datoteci definirani uređaji se ne mogu uređivati."
+ "welcomeBannerTitle": "Konfigurirajmo vaš sustav!"
},
"messaging": {
"description": "Primajte poruke o svojim sesijama punjenja.",
diff --git a/i18n/hu.json b/i18n/hu.json
index 3442c06ae..5628d7c4a 100644
--- a/i18n/hu.json
+++ b/i18n/hu.json
@@ -209,6 +209,7 @@
"dragList": "Átrendezhető lista",
"experimental": "Kísérleti",
"forceSave": "Mentés mindenképpen",
+ "fromYamlHint": "Megjegyzés: Az evcc.yaml fájlban konfigurálható. A szerkesztés engedélyezéséhez távolítsa el a bejegyzést a fájlból.",
"hideAdvancedSettings": "Speciális beállítások elrejtése",
"invalidFileSelected": "Érvénytelen fájl kiválasztva",
"noFileSelected": "Nincs fájl kiválasztva.",
@@ -247,8 +248,7 @@
"downloadCsv": "CSV letöltése",
"eventsRecorded": "{count} rácskorlátozási esemény rögzítve.",
"lastEvent": "Legutóbbi {timeAgo}.",
- "title": "Külső határ",
- "yamlConfigured": "Megjegyzés: Az evcc.yaml fájlban konfigurálható. A szerkesztés engedélyezéséhez távolítsa el a bejegyzést a fájlból."
+ "title": "Külső határ"
},
"icon": {
"change": "változtatás",
@@ -390,8 +390,7 @@
"unconfigured": "nincs konfigurálva",
"vehicles": "Járműveim",
"welcomeBannerText": "Kezdje azzal, hogy létrehoz legalább egy **töltőt**, **fűtőt**, **hálózatot**, **napelemet**, **akkumulátort** vagy **kiegészítő mérőt**. Ha csak tesztelni szeretne, válasszon egy **demo eszközt**.",
- "welcomeBannerTitle": "Konfiguráljuk a rendszerét!",
- "yaml": "Az evcc.yaml fájlban szereplő eszközök nem szerkeszthetők."
+ "welcomeBannerTitle": "Konfiguráljuk a rendszerét!"
},
"messaging": {
"description": "Értesítési üzenetek beállítása a töltési folyamatokról.",
diff --git a/i18n/it.json b/i18n/it.json
index 3595d9a0a..dc6fa78a6 100644
--- a/i18n/it.json
+++ b/i18n/it.json
@@ -209,6 +209,7 @@
"dragList": "Elenco riordinabile",
"experimental": "Sperimentale",
"forceSave": "Salva comunque",
+ "fromYamlHint": "Nota: configurato tramite evcc.yaml. Rimuovere la voce dal file per abilitare la modifica qui.",
"hideAdvancedSettings": "Nascondi impostazioni avanzate",
"invalidFileSelected": "File non valido selezionato",
"noFileSelected": "Nessun file selezionato.",
@@ -247,8 +248,7 @@
"downloadCsv": "Scarica CSV",
"eventsRecorded": "Registrati {count} eventi di limitazione della griglia.",
"lastEvent": "Più recente {timeAgo}.",
- "title": "HEMSLimite esterno",
- "yamlConfigured": "Nota: configurato tramite evcc.yaml. Rimuovere la voce dal file per abilitare la modifica qui."
+ "title": "HEMSLimite esterno"
},
"icon": {
"change": "cambiamento",
@@ -390,8 +390,7 @@
"unconfigured": "non configurato",
"vehicles": "I miei veicoli",
"welcomeBannerText": "Inizia creando almeno un **caricabatterie**, un **riscaldatore**, una **rete**, un **impianto solare**, una **batteria** o un **contatore aggiuntivo**. Se desideri solo effettuare un test, scegli un **dispositivo demo**.",
- "welcomeBannerTitle": "Configuriamo il tuo sistema!",
- "yaml": "I dispositivi di evcc.yaml non sono modificabili."
+ "welcomeBannerTitle": "Configuriamo il tuo sistema!"
},
"messaging": {
"description": "Ricevi messaggi sulle sessioni di ricarica.",
diff --git a/i18n/lb.json b/i18n/lb.json
index 065f84d43..638b319f6 100644
--- a/i18n/lb.json
+++ b/i18n/lb.json
@@ -209,6 +209,7 @@
"dragList": "Sortéirbar Lëscht",
"experimental": "Experimentell",
"forceSave": "Trotzdem späicheren",
+ "fromYamlHint": "Bemierkung: Konfiguréiert iwwer evcc.yaml. Läscht d'Astellungen aus der Datei fir d'Ännerung hei z'erméiglechen.",
"hideAdvancedSettings": "Fortgeschratten Astellunge verstoppen",
"invalidFileSelected": "Ongülteg Datei ausgewielt",
"noFileSelected": "Keng Datei ausgewielt.",
@@ -247,8 +248,7 @@
"downloadCsv": "CSV eroflueden",
"eventsRecorded": "{count} gespäichert Limitatiounen vum Netz.",
"lastEvent": "Méi rezent {timeAgo}.",
- "title": "Äusserlech Grenz",
- "yamlConfigured": "Bemierkung: Konfiguréiert iwwer evcc.yaml. Läscht d'Astellungen aus der Datei fir d'Ännerung hei z'erméiglechen."
+ "title": "Äusserlech Grenz"
},
"icon": {
"change": "änneren",
@@ -390,8 +390,7 @@
"unconfigured": "net konfiguréiert",
"vehicles": "Meng Gefierer",
"welcomeBannerText": "Fänkt un mat der Erstelle vun op d'mannst engem **Ladegerät**, **Heizer**, **Netz**, **Solarpanel**, **Batterie** oder **zousätzlechen Zähler**. Wann Dir just testen wëllt, wielt e **Demonstratiounsapparat**.",
- "welcomeBannerTitle": "Loosst eis de System konfiguréieren!",
- "yaml": "Geräter aus der evcc.yaml Datei si net editiérbar."
+ "welcomeBannerTitle": "Loosst eis de System konfiguréieren!"
},
"messaging": {
"description": "Notifikatiounen iwwer Är Opluedsessioune kréien.",
diff --git a/i18n/lt.json b/i18n/lt.json
index e033dc35c..74635f368 100644
--- a/i18n/lt.json
+++ b/i18n/lt.json
@@ -209,6 +209,7 @@
"dragList": "Galima sukeisti vietomis",
"experimental": "Eksperimentinis",
"forceSave": "Vistiek įrašyti",
+ "fromYamlHint": "Pastaba: Sukonfigūruota naudojant evcc.yaml. Pašalinkite įrašą iš failo, kad būtų galima redaguoti čia.",
"hideAdvancedSettings": "Slėpti išplėstinius nustatymus",
"invalidFileSelected": "Pasirinktas netinkamas failas",
"noFileSelected": "Nepasirinktas failas.",
@@ -247,8 +248,7 @@
"downloadCsv": "Atsisiųsti CSV failą",
"eventsRecorded": "Užfiksuota {count} tinklo galios apribojimo įvykių.",
"lastEvent": "Naujausias {timeAgo}.",
- "title": "Išorinė riba",
- "yamlConfigured": "Pastaba: Sukonfigūruota naudojant evcc.yaml. Pašalinkite įrašą iš failo, kad būtų galima redaguoti čia."
+ "title": "Išorinė riba"
},
"icon": {
"change": "pakeisti",
@@ -390,8 +390,7 @@
"unconfigured": "nesukonfigūruota",
"vehicles": "Mano automobiliai",
"welcomeBannerText": "Pradžiai sukonfigūruokite bent vieną **įkroviklį**, **šildytuvą**, **tinklo skaitiklį**, **saulės jėgainę** ar **papildomą skaitiklį**. Jei norite tik pasibandyti, pasirinkite **demonstracinį įrenginį**.",
- "welcomeBannerTitle": "Sukonfigūruokime jūsų sistemą!",
- "yaml": "Įtaisas iš evcc.yaml , čia konfigūruoti neina."
+ "welcomeBannerTitle": "Sukonfigūruokime jūsų sistemą!"
},
"messaging": {
"description": "Gaukite pranešimus apie įkrovimo sesijas.",
diff --git a/i18n/nl.json b/i18n/nl.json
index a3cb61afc..696605eb0 100644
--- a/i18n/nl.json
+++ b/i18n/nl.json
@@ -209,6 +209,7 @@
"dragList": "Sorteerbare lijst",
"experimental": "Experimenteel",
"forceSave": "Toch opslaan",
+ "fromYamlHint": "Opmerking: Geconfigureerd via evcc.yaml. Verwijder de betreffende regels uit het bestand om hier bewerkingen mogelijk te maken.",
"hideAdvancedSettings": "Verberg geavanceerde instellingen",
"invalidFileSelected": "Ongeldig bestand geselecteerd",
"noFileSelected": "Geen bestand geselecteerd.",
@@ -247,8 +248,7 @@
"downloadCsv": "Download CSV",
"eventsRecorded": "{count} gebeurtenissen met rasterbeperkingen geregistreerd.",
"lastEvent": "Meest recent {timeAgo}.",
- "title": "Externe limiet",
- "yamlConfigured": "Opmerking: Geconfigureerd via evcc.yaml. Verwijder de betreffende regels uit het bestand om hier bewerkingen mogelijk te maken."
+ "title": "Externe limiet"
},
"icon": {
"change": "wijzig",
@@ -390,8 +390,7 @@
"unconfigured": "Niet geconfigureerd",
"vehicles": "Mijn voertuigen",
"welcomeBannerText": "Begin met het aanmaken van minstens één **oplader**, **verwarmer**, **netstroom**, **zonnepanelen**, **thuisbatterij** of **extra meter**. Als je gewoon wilt testen, kies dan een **demo-apparaat**.",
- "welcomeBannerTitle": "Start met het configureren van uw systeem!",
- "yaml": "Apparaten geconfigureerd in evcc.yaml kunnen hier niet worden bewerkt."
+ "welcomeBannerTitle": "Start met het configureren van uw systeem!"
},
"messaging": {
"description": "Ontvang berichten over je oplaadsessies.",
diff --git a/i18n/no.json b/i18n/no.json
index 5a65be3c1..3f1b657f5 100644
--- a/i18n/no.json
+++ b/i18n/no.json
@@ -209,6 +209,7 @@
"dragList": "Omordningsbar liste",
"experimental": "Eksperimentell",
"forceSave": "Lagre likevel",
+ "fromYamlHint": "Merk: Konfigurert via evcc.yaml. Fjern oppføringen fra filen for å aktivere redigering her.",
"hideAdvancedSettings": "Skjul avanserte innstillinger",
"invalidFileSelected": "Ugyldig fil valgt",
"noFileSelected": "Ingen fil valgt.",
@@ -247,8 +248,7 @@
"downloadCsv": "Last ned CSV",
"eventsRecorded": "Registrerte {count} hendelser med rutenettbegrensning.",
"lastEvent": "Siste {timeAgo}.",
- "title": "Ekstern grense",
- "yamlConfigured": "Merk: Konfigurert via evcc.yaml. Fjern oppføringen fra filen for å aktivere redigering her."
+ "title": "Ekstern grense"
},
"icon": {
"change": "endring",
@@ -390,8 +390,7 @@
"unconfigured": "ikke konfigurert",
"vehicles": "Mine kjøretøy",
"welcomeBannerText": "Begynn med å opprette minst én **lader**, **varmeapparat**, **nett**, **solcelle**, **batteri** eller **ekstra måler**. Hvis du bare vil teste, velger du en **demoenhet**.",
- "welcomeBannerTitle": "La oss konfigurere systemet ditt!",
- "yaml": "Enheter fra evcc.yaml kan ikke redigeres."
+ "welcomeBannerTitle": "La oss konfigurere systemet ditt!"
},
"messaging": {
"description": "Motta meldinger om ladingsøktene dine.",
diff --git a/i18n/pl.json b/i18n/pl.json
index d8d3cc14f..4354d8fac 100644
--- a/i18n/pl.json
+++ b/i18n/pl.json
@@ -209,6 +209,7 @@
"dragList": "Lista do ponownego zamówienia",
"experimental": "Eksperymentalny",
"forceSave": "Zapisz mimo to",
+ "fromYamlHint": "Uwaga: Konfiguracja odbywa się za pomocą pliku evcc.yaml. Aby umożliwić edycję w tym miejscu, usuń wpis z pliku.",
"hideAdvancedSettings": "Ukryj ustawienia zaawansowane",
"invalidFileSelected": "Wybrano nieprawidłowy plik",
"noFileSelected": "Nie wybrano pliku.",
@@ -247,8 +248,7 @@
"downloadCsv": "Pobierz CSV",
"eventsRecorded": "Zarejestrowano {count} zdarzeń związanych z ograniczeniami sieci.",
"lastEvent": "Najnowsze {timeAgo}.",
- "title": "Limit zewnętrzny",
- "yamlConfigured": "Uwaga: Konfiguracja odbywa się za pomocą pliku evcc.yaml. Aby umożliwić edycję w tym miejscu, usuń wpis z pliku."
+ "title": "Limit zewnętrzny"
},
"icon": {
"change": "zmień",
@@ -390,8 +390,7 @@
"unconfigured": "nie skonfigurowane",
"vehicles": "Moje pojazdy",
"welcomeBannerText": "Zacznij od utworzenia co najmniej jednej **ładowarki**, **grzejnika**, **sieci**, **panelu słonecznego**, **baterii** lub **dodatkowego licznika**. Jeśli chcesz tylko przetestować, wybierz **urządzenie demonstracyjne**.",
- "welcomeBannerTitle": "Skonfigurujmy Twój system!",
- "yaml": "Urządzenia z pliku evcc.yaml nie mogą być edytowane."
+ "welcomeBannerTitle": "Skonfigurujmy Twój system!"
},
"messaging": {
"description": "Otrzymuj wiadomości o sesjach ładowania.",
diff --git a/i18n/pt.json b/i18n/pt.json
index f0186911d..520f32899 100644
--- a/i18n/pt.json
+++ b/i18n/pt.json
@@ -209,6 +209,7 @@
"dragList": "Lista reordenável",
"experimental": "Experimental",
"forceSave": "Guardar na mesma",
+ "fromYamlHint": "Nota: Configurado através do evcc.yaml. Remova a entrada do ficheiro para permitir a edição aqui.",
"hideAdvancedSettings": "Ocultar definições avançadas",
"invalidFileSelected": "Ficheiro selecionado inválido",
"noFileSelected": "Nenhum ficheiro selecionado.",
@@ -247,8 +248,7 @@
"downloadCsv": "Descarregar CSV",
"eventsRecorded": "Registou {count} eventos de limitação da rede.",
"lastEvent": "Mais recente {timeAgo}.",
- "title": "Limite externo",
- "yamlConfigured": "Nota: Configurado através do evcc.yaml. Remova a entrada do ficheiro para permitir a edição aqui."
+ "title": "Limite externo"
},
"icon": {
"change": "alterar",
@@ -390,8 +390,7 @@
"unconfigured": "não configurado",
"vehicles": "Os meus Veículos",
"welcomeBannerText": "Comece por criar pelo menos um **carregador**, **aquecedor**, **rede**, **solar**, **bateria** ou **medidor adicional**. Se quiser apenas testar, escolha um **dispositivo de demonstração**.",
- "welcomeBannerTitle": "Configuremos agora o seu sistema!",
- "yaml": "Os dispositivos de evcc.yaml não são editáveis."
+ "welcomeBannerTitle": "Configuremos agora o seu sistema!"
},
"messaging": {
"description": "Receber mensagens sobre as sessões de carregamento.",
diff --git a/i18n/ro.json b/i18n/ro.json
index 3c5e21c2f..cb41b14b1 100644
--- a/i18n/ro.json
+++ b/i18n/ro.json
@@ -209,6 +209,7 @@
"dragList": "Listă reordonabilă",
"experimental": "Experimental",
"forceSave": "Salvați oricum",
+ "fromYamlHint": "Notă: Configurat prin evcc.yaml. Ștergeți intrarea din fișier pentru a activa editarea aici.",
"hideAdvancedSettings": "Ascunde setările avansate",
"invalidFileSelected": "Fișier invalid selectat",
"noFileSelected": "Nu a fost selectat niciun fișier.",
@@ -247,8 +248,7 @@
"downloadCsv": "Descărcați CSV",
"eventsRecorded": "Au fost înregistrate {count} evenimente de limitare a grilei.",
"lastEvent": "Cel mai recent {timeAgo}.",
- "title": "Limită externă",
- "yamlConfigured": "Notă: Configurat prin evcc.yaml. Ștergeți intrarea din fișier pentru a activa editarea aici."
+ "title": "Limită externă"
},
"icon": {
"change": "schimbare",
@@ -390,8 +390,7 @@
"unconfigured": "neconfigurat",
"vehicles": "Masinile mele",
"welcomeBannerText": "Începeți prin a crea cel puțin un **încărcător**, **încălzitor**, **rețea**, **sistem solar**, **baterie** sau **contor suplimentar**. Dacă doriți doar să testați, alegeți un **dispozitiv demo**.",
- "welcomeBannerTitle": "Să configurăm sistemul tău!",
- "yaml": "Dispozitivele din evcc.yaml nu pot fi editate."
+ "welcomeBannerTitle": "Să configurăm sistemul tău!"
},
"messaging": {
"description": "Primiți mesaje despre sesiunile dvs. de încărcare.",
diff --git a/i18n/ru.json b/i18n/ru.json
index 9c56e3767..06868aaa8 100644
--- a/i18n/ru.json
+++ b/i18n/ru.json
@@ -209,6 +209,7 @@
"dragList": "Список, который можно переупорядочить",
"experimental": "Экспериментальный",
"forceSave": "Сохранить в любом случае",
+ "fromYamlHint": "Примечание: Настраивается через evcc.yaml. Удалите запись из файла, чтобы включить редактирование здесь.",
"hideAdvancedSettings": "Скрыть расширенные настройки",
"invalidFileSelected": "Выбран недействительный файл",
"noFileSelected": "Файл не выбран.",
@@ -247,8 +248,7 @@
"downloadCsv": "Скачать CSV",
"eventsRecorded": "Зарегистрировано {count} событий ограничения сетки.",
"lastEvent": "Последнее {timeAgo}.",
- "title": "Внешний предел",
- "yamlConfigured": "Примечание: Настраивается через evcc.yaml. Удалите запись из файла, чтобы включить редактирование здесь."
+ "title": "Внешний предел"
},
"icon": {
"change": "изменение",
@@ -390,8 +390,7 @@
"unconfigured": "не настроен",
"vehicles": "Мои транспортные средства",
"welcomeBannerText": "Начните с создания хотя бы одного **зарядного устройства**, **нагревателя**, **сети**, **солнечной батареи**, **аккумулятора** или **дополнительного счетчика**. Если вы просто хотите протестировать, выберите **демонстрационное устройство**.",
- "welcomeBannerTitle": "Давайте настроим вашу систему!",
- "yaml": "Устройства из evcc.yaml не подлежат редактированию."
+ "welcomeBannerTitle": "Давайте настроим вашу систему!"
},
"messaging": {
"description": "Получайте сообщения о ваших сеансах зарядки.",
diff --git a/i18n/sl.json b/i18n/sl.json
index 49da47ea8..1ba021b13 100644
--- a/i18n/sl.json
+++ b/i18n/sl.json
@@ -209,6 +209,7 @@
"dragList": "Seznam, ki ga je mogoče prerazporediti",
"experimental": "Eksperimentalno",
"forceSave": "Vseeno shrani",
+ "fromYamlHint": "Opomba: Konfigurirano prek evcc.yaml. Odstranite vnos iz datoteke, da omogočite urejanje tukaj.",
"hideAdvancedSettings": "Skrij napredne nastavitve",
"invalidFileSelected": "Izbrana neveljavna datoteka",
"noFileSelected": "Izbrana ni nobena datoteka.",
@@ -247,8 +248,7 @@
"downloadCsv": "Prenesi CSV",
"eventsRecorded": "Zabeleženo {count} dogodkov omejitve omrežja.",
"lastEvent": "Najnovejše {timeAgo}.",
- "title": "Zunanja meja",
- "yamlConfigured": "Opomba: Konfigurirano prek evcc.yaml. Odstranite vnos iz datoteke, da omogočite urejanje tukaj."
+ "title": "Zunanja meja"
},
"icon": {
"change": "sprememba",
@@ -390,8 +390,7 @@
"unconfigured": "ni konfigurirano",
"vehicles": "Moja vozila",
"welcomeBannerText": "Začnite z ustvarjanjem vsaj enega **polnilnika**, **grelca**, **omrežja**, **sončne energije**, **baterije** ali **dodatnega merilnika**. Če želite samo preizkusiti, izberite **demo napravo**.",
- "welcomeBannerTitle": "Konfigurirajmo vaš sistem!",
- "yaml": "Naprav iz datoteke evcc.yaml ni mogoče urejati."
+ "welcomeBannerTitle": "Konfigurirajmo vaš sistem!"
},
"messaging": {
"description": "Prejemanje sporočil o vaših sejah polnjenja.",
diff --git a/i18n/sv.json b/i18n/sv.json
index ecb7366fb..e48850614 100644
--- a/i18n/sv.json
+++ b/i18n/sv.json
@@ -209,6 +209,7 @@
"dragList": "Sorterbar lista",
"experimental": "Experimentella funktioner",
"forceSave": "Spara ändå",
+ "fromYamlHint": "Obs: Konfigurerad via evcc.yaml. Ta bort posten från filen för att aktivera redigering här.",
"hideAdvancedSettings": "Dölj avancerade inställningar",
"invalidFileSelected": "Ogiltig fil vald",
"noFileSelected": "Ingen fil vald.",
@@ -247,8 +248,7 @@
"downloadCsv": "Hämta CSV",
"eventsRecorded": "Registrerade {count} händelser med nätbegränsning.",
"lastEvent": "Senaste {timeAgo}.",
- "title": "Extern gräns",
- "yamlConfigured": "Obs: Konfigurerad via evcc.yaml. Ta bort posten från filen för att aktivera redigering här."
+ "title": "Extern gräns"
},
"icon": {
"change": "ändra",
@@ -390,8 +390,7 @@
"unconfigured": "ej konfigurerad",
"vehicles": "Mina fordon",
"welcomeBannerText": "Börja med att skapa minst en **laddare**, **värmare**, **nät**, **solcell**, **batteri** eller **extra mätare**. Om du bara vill testa, välj en **demoenhet**.",
- "welcomeBannerTitle": "Låt oss konfigurera ditt system!",
- "yaml": "Konfigurera i evcc.yaml. Kan inte ändras i UI."
+ "welcomeBannerTitle": "Låt oss konfigurera ditt system!"
},
"messaging": {
"description": "Få notiser om dina laddningar.",
diff --git a/i18n/ta.json b/i18n/ta.json
index 0c6ef0637..26206c1f1 100644
--- a/i18n/ta.json
+++ b/i18n/ta.json
@@ -154,6 +154,7 @@
"delete": "நீக்கு",
"docsLink": "ஆவணங்களைக் காண்க.",
"experimental": "சோதனை",
+ "fromYamlHint": "Evcc.yaml இல் கட்டமைக்கப்பட்டுள்ளது. இடைமுகம் இல் திருத்த முடியாது.",
"hideAdvancedSettings": "மேம்பட்ட அமைப்புகளை மறைக்க",
"invalidFileSelected": "தவறான கோப்பு தேர்ந்தெடுக்கப்பட்டது",
"noFileSelected": "எந்த கோப்பும் தேர்ந்தெடுக்கப்படவில்லை.",
@@ -314,8 +315,7 @@
"name": "பெயர்",
"title": "உள்ளமைவு",
"unconfigured": "கட்டமைக்கப்படவில்லை",
- "vehicles": "என் வாகனங்கள்",
- "yaml": "Evcc.yaml இல் கட்டமைக்கப்பட்டுள்ளது. இடைமுகம் இல் திருத்த முடியாது."
+ "vehicles": "என் வாகனங்கள்"
},
"messaging": {
"description": "உங்கள் சார்சிங் அமர்வுகள் பற்றிய செய்திகளைப் பெறுங்கள்.",
diff --git a/i18n/tr.json b/i18n/tr.json
index 92299f498..9f758d5d5 100644
--- a/i18n/tr.json
+++ b/i18n/tr.json
@@ -209,6 +209,7 @@
"dragList": "Yeniden sıralanabilir liste",
"experimental": "Deneysel",
"forceSave": "Yine de kaydet",
+ "fromYamlHint": "Not: evcc.yaml aracılığıyla yapılandırıldı. Burada düzenlemeyi etkinleştirmek için dosyadan öğeyi kaldır.",
"hideAdvancedSettings": "“Gelişmiş ayarları gizle”",
"invalidFileSelected": "Geçersiz dosya seçildi",
"noFileSelected": "Seçili dosya yok.",
@@ -247,8 +248,7 @@
"downloadCsv": "CSV'yi indir",
"eventsRecorded": "{count} şebeke sınırlama etkinliği kaydedildi.",
"lastEvent": "En son {timeAgo}.",
- "title": "Dış Limit",
- "yamlConfigured": "Not: evcc.yaml aracılığıyla yapılandırıldı. Burada düzenlemeyi etkinleştirmek için dosyadan öğeyi kaldır."
+ "title": "Dış Limit"
},
"icon": {
"change": "değiştir",
@@ -390,8 +390,7 @@
"unconfigured": "yapılandırılmadı",
"vehicles": "Araçlarım",
"welcomeBannerText": "Önce bir **doldurma cihazı**, **ısıtıcı**, **elektrik sayacı**, **güneş enerjisi**, **batarya** veya **ek sayaç** oluştur. Sadece denemek istiyorsan, bir **demo cihazı** seç.",
- "welcomeBannerTitle": "Haydi sistemini yapılandıralım!",
- "yaml": "evcc.yaml'daki cihazlar düzenlenemez."
+ "welcomeBannerTitle": "Haydi sistemini yapılandıralım!"
},
"messaging": {
"description": "Dolum oturumları hakkında bildirimler al.",
diff --git a/i18n/uk.json b/i18n/uk.json
index 7d6d7f240..3951fbe44 100644
--- a/i18n/uk.json
+++ b/i18n/uk.json
@@ -209,6 +209,7 @@
"dragList": "Список, який можна перевпорядкувати",
"experimental": "Експериментальний",
"forceSave": "Зберегти все одно",
+ "fromYamlHint": "Примітка: Налаштовано через evcc.yaml. Видаліть запис із файлу, щоб дозволити редагування тут.",
"hideAdvancedSettings": "Приховати розширені налаштування",
"invalidFileSelected": "Вибрано недійсний файл",
"noFileSelected": "Не вибрано файл.",
@@ -247,8 +248,7 @@
"downloadCsv": "Завантажити CSV-файл",
"eventsRecorded": "Записано {count} подій обмеження мережі.",
"lastEvent": "Найновіші {timeAgo}.",
- "title": "Зовнішнє обмеження",
- "yamlConfigured": "Примітка: Налаштовано через evcc.yaml. Видаліть запис із файлу, щоб дозволити редагування тут."
+ "title": "Зовнішнє обмеження"
},
"icon": {
"change": "зміна",
@@ -390,8 +390,7 @@
"unconfigured": "не налаштовано",
"vehicles": "Мій транспорт",
"welcomeBannerText": "Почніть зі створення принаймні одного **зарядного пристрою**, **обігрівача**, **мережевої**, **сонячної батареї**, **акумулятора** або **додаткового лічильника**. Якщо ви просто хочете протестувати, виберіть **демонстраційний пристрій**.",
- "welcomeBannerTitle": "Давайте налаштуємо вашу систему!",
- "yaml": "Пристрої з evcc.yaml не можна редагувати."
+ "welcomeBannerTitle": "Давайте налаштуємо вашу систему!"
},
"messaging": {
"description": "Отримувати повідомлення про ваші сеанси зарядки.",
diff --git a/i18n/zh-Hans.json b/i18n/zh-Hans.json
index a44dabc61..d8f2a648f 100644
--- a/i18n/zh-Hans.json
+++ b/i18n/zh-Hans.json
@@ -171,6 +171,7 @@
"delete": "删除",
"docsLink": "查看文档。",
"experimental": "实验性",
+ "fromYamlHint": "evcc.yaml 中的设备不可编辑。",
"hideAdvancedSettings": "隐藏高级设置",
"off": "关",
"on": "开",
@@ -306,8 +307,7 @@
"name": "名称",
"title": "配置",
"unconfigured": "未配置",
- "vehicles": "我的车辆",
- "yaml": "evcc.yaml 中的设备不可编辑。"
+ "vehicles": "我的车辆"
},
"messaging": {
"description": "接收有关您充电会话及其他事件的消息。",
diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go
index 8788e25eb..794ceaae3 100644
--- a/server/eebus/eebus.go
+++ b/server/eebus/eebus.go
@@ -4,9 +4,7 @@ import (
"crypto/tls"
"errors"
"fmt"
- "net"
"slices"
- "strconv"
"strings"
"sync"
"time"
@@ -34,7 +32,6 @@ import (
"github.com/enbility/spine-go/model"
"github.com/enbility/spine-go/spine"
"github.com/evcc-io/evcc/util"
- "github.com/evcc-io/evcc/util/machine"
)
type Device interface {
@@ -82,29 +79,33 @@ type EEBus struct {
mux sync.Mutex
log *util.Logger
- Ski string
+ ski string
clients map[string][]Device
}
var Instance *EEBus
+func GetStatus() any {
+ return struct {
+ Ski string `json:"ski"`
+ }{
+ Ski: Ski(),
+ }
+}
+
func NewServer(other Config) (*EEBus, error) {
cc := Config{
- URI: ":4712",
+ Port: 4712,
}
if err := mergo.Merge(&cc, other, mergo.WithOverride); err != nil {
return nil, err
}
- log := util.NewLogger("eebus")
-
- protectedID := machine.ProtectedID("evcc-eebus")
- serial := fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8])
-
- if len(cc.ShipID) != 0 {
- serial = cc.ShipID
+ serial := cc.ShipID
+ if serial == "" {
+ serial = createShipID()
}
certificate, err := tls.X509KeyPair([]byte(cc.Certificate.Public), []byte(cc.Certificate.Private))
@@ -112,22 +113,11 @@ func NewServer(other Config) (*EEBus, error) {
return nil, err
}
- _, portValue, err := net.SplitHostPort(cc.URI)
- if err != nil {
- return nil, err
- }
-
- port, err := strconv.Atoi(portValue)
- if err != nil {
- return nil, err
- }
-
- // TODO: get the voltage from the site
configuration, err := eebusapi.NewConfiguration(
BrandName, BrandName, Model, serial,
model.DeviceTypeTypeEnergyManagementSystem,
[]model.EntityTypeType{model.EntityTypeTypeCEM},
- port, certificate, time.Second*4,
+ cc.Port, certificate, time.Second*4,
)
if err != nil {
return nil, err
@@ -147,8 +137,8 @@ func NewServer(other Config) (*EEBus, error) {
}
c := &EEBus{
- log: log,
- Ski: ski,
+ log: util.NewLogger("eebus"),
+ ski: ski,
clients: make(map[string][]Device),
}
@@ -226,7 +216,7 @@ func (c *EEBus) RegisterDevice(ski, ip string, device Device) error {
ski = shiputil.NormalizeSKI(ski)
c.log.TRACE.Printf("registering ski: %s", ski)
- if ski == c.Ski {
+ if ski == c.ski {
return errors.New("device ski can not be identical to host ski")
}
diff --git a/server/eebus/test/cs_test.go b/server/eebus/test/cs_test.go
index df31cde9d..64047fb98 100644
--- a/server/eebus/test/cs_test.go
+++ b/server/eebus/test/cs_test.go
@@ -38,12 +38,13 @@ func TestEEBus(t *testing.T) {
})
require.NoError(t, err, "server")
- require.NotEmpty(t, srv.Ski, "server ski")
server.Instance = srv
go srv.Run()
- box, err := createControlbox(t.Context(), server.Instance.Ski, remotePort)
+ require.NotEmpty(t, server.Ski(), "server ski")
+
+ box, err := createControlbox(t.Context(), server.Ski(), remotePort)
require.NoError(t, err, "controlbox")
eventC := make(chan api.EventType, 1)
diff --git a/server/eebus/types.go b/server/eebus/types.go
index 43aef380d..1bcb52b3e 100644
--- a/server/eebus/types.go
+++ b/server/eebus/types.go
@@ -1,6 +1,11 @@
package eebus
-import "github.com/evcc-io/evcc/util"
+import (
+ "fmt"
+
+ "github.com/evcc-io/evcc/util"
+ "github.com/evcc-io/evcc/util/machine"
+)
const (
BrandName string = "EVCC"
@@ -11,17 +16,69 @@ const (
var DeviceCode = util.Getenv("EEBUS_DEVICE_CODE", "EVCC_HEMS_01")
type Certificate struct {
- Public, Private string
+ Public string `json:"public"`
+ Private string `json:"private"`
}
type Config struct {
- URI string
- ShipID string
- Interfaces []string
- Certificate Certificate
+ URI_ string `mapstructure:"uri" json:"uri"` // TODO deprecated
+ Port int `json:"port"`
+ ShipID string `json:"shipid"`
+ Interfaces []string `json:"interfaces,omitempty"`
+ Certificate Certificate `json:"certificate"`
}
-// Configured returns true if the EEbus server is configured
-func (c Config) Configured() bool {
+// IsConfigured returns true if the EEbus server is configured
+func (c Config) IsConfigured() bool {
return len(c.Certificate.Public) > 0 && len(c.Certificate.Private) > 0
}
+
+// Redacted implements the redactor interface used by the tee publisher
+func (c Config) Redacted() any {
+ return Config{
+ URI_: c.URI_,
+ Port: c.Port,
+ ShipID: c.ShipID,
+ Interfaces: c.Interfaces,
+ Certificate: Certificate{
+ Public: c.Certificate.Public,
+ Private: util.Masked(c.Certificate.Private),
+ },
+ }
+}
+
+func createShipID() string {
+ protectedID := machine.ProtectedID("evcc-eebus")
+ return fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8])
+}
+
+func DefaultConfig(conf *Config) (*Config, error) {
+ cert, err := CreateCertificate()
+ if err != nil {
+ return nil, err
+ }
+
+ public, private, err := GetX509KeyPair(cert)
+ if err != nil {
+ return nil, err
+ }
+
+ res := Config{
+ Port: 4712,
+ ShipID: createShipID(),
+ Certificate: Certificate{
+ Public: public,
+ Private: private,
+ },
+ }
+
+ return &res, nil
+}
+
+// Ski returns the EEbus server SKI
+func Ski() string {
+ if Instance == nil {
+ return ""
+ }
+ return Instance.ski
+}
diff --git a/server/http.go b/server/http.go
index 170c6d008..c42e2ec92 100644
--- a/server/http.go
+++ b/server/http.go
@@ -297,7 +297,6 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, pub publisher, cache *uti
// yaml handlers
for key, fun := range map[string]func() (any, any){
- keys.EEBus: func() (any, any) { return map[string]any{}, eebus.Config{} },
keys.Hems: func() (any, any) { return map[string]any{}, config.Typed{} },
keys.Tariffs: func() (any, any) { return map[string]any{}, globalconfig.Tariffs{} },
keys.Messaging: func() (any, any) { return map[string]any{}, globalconfig.Messaging{} }, // has default
@@ -316,6 +315,7 @@ func (s *HTTPd) RegisterSystemHandler(site *core.Site, pub publisher, cache *uti
keys.ModbusProxy: func() any { return new([]globalconfig.ModbusProxy) }, // slice
keys.Shm: func() any { return new(shm.Config) },
keys.Influx: func() any { return new(globalconfig.Influx) },
+ keys.EEBus: func() any { return new(eebus.Config) },
} {
routes["update"+key] = route{Method: "POST", Pattern: "/" + key, HandlerFunc: settingsSetJsonHandler(key, pub, fun)}
routes["delete"+key] = route{Method: "DELETE", Pattern: "/" + key, HandlerFunc: settingsDeleteJsonHandler(key, pub, fun())}
diff --git a/server/http_config_site_other_handler.go b/server/http_config_site_other_handler.go
index a7c6904b9..c6dd55f37 100644
--- a/server/http_config_site_other_handler.go
+++ b/server/http_config_site_other_handler.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"net/http"
+ "github.com/evcc-io/evcc/api/globalconfig"
"github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/util/sponsor"
@@ -26,11 +27,8 @@ func updateSponsortokenHandler(pub publisher) func(w http.ResponseWriter, r *htt
return
}
- pub(keys.Sponsor, struct {
- Status sponsor.Status `json:"status"`
- FromYaml bool `json:"fromYaml"`
- }{
- Status: sponsor.GetStatus(),
+ pub(keys.Sponsor, globalconfig.ConfigStatus{
+ Status: sponsor.RedactedStatus(),
FromYaml: false,
})
}
@@ -39,7 +37,7 @@ func updateSponsortokenHandler(pub publisher) func(w http.ResponseWriter, r *htt
settings.SetString(keys.SponsorToken, req.Token)
setConfigDirty()
- jsonWrite(w, sponsor.GetStatus())
+ jsonWrite(w, sponsor.RedactedStatus())
}
}
@@ -47,10 +45,7 @@ func deleteSponsorTokenHandler(pub publisher) func(w http.ResponseWriter, r *htt
return func(w http.ResponseWriter, r *http.Request) {
settings.SetString(keys.SponsorToken, "")
- pub(keys.Sponsor, struct {
- Status sponsor.Status `json:"status"`
- FromYaml bool `json:"fromYaml"`
- }{
+ pub(keys.Sponsor, globalconfig.ConfigStatus{
Status: sponsor.Status{},
FromYaml: false,
})
diff --git a/server/http_global_settings_handler.go b/server/http_global_settings_handler.go
index 316017508..a268e0821 100644
--- a/server/http_global_settings_handler.go
+++ b/server/http_global_settings_handler.go
@@ -9,6 +9,7 @@ import (
"strings"
"time"
+ "github.com/evcc-io/evcc/core/keys"
"github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/util/redact"
"github.com/gorilla/mux"
@@ -80,6 +81,12 @@ func settingsSetYamlHandler(key string, other, struc any) http.HandlerFunc {
}
}
+func allowPub(key string) bool {
+ // don't publish on update - would overwrite globalconfig.Info struct with config
+ // TODO come up with a general solution once all endpoinds use Info
+ return key != keys.EEBus
+}
+
func settingsSetJsonHandler(key string, pub publisher, newStruc func() any) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
struc := newStruc()
@@ -104,7 +111,9 @@ func settingsSetJsonHandler(key string, pub publisher, newStruc func() any) http
settings.SetJson(key, struc)
setConfigDirty()
- pub(key, struc)
+ if allowPub(key) {
+ pub(key, struc)
+ }
jsonWrite(w, true)
}
@@ -115,7 +124,9 @@ func settingsDeleteJsonHandler(key string, pub publisher, struc any) http.Handle
settings.SetString(key, "")
setConfigDirty()
- pub(key, struc)
+ if allowPub(key) {
+ pub(key, struc)
+ }
jsonWrite(w, true)
}
diff --git a/tests/config-eebus.evcc.yaml b/tests/config-eebus.evcc.yaml
new file mode 100644
index 000000000..6a4d543d7
--- /dev/null
+++ b/tests/config-eebus.evcc.yaml
@@ -0,0 +1,27 @@
+interval: 0.1s
+
+eebus:
+ shipid: EVCC_HEMS_01
+ certificate:
+ public: |
+ -----BEGIN CERTIFICATE-----
+ MIIBuzCCAWKgAwIBAgIQatuHkiaPT4obu8RHSmfOwzAKBggqhkjOPQQDAjA+MQsw
+ CQYDVQQGEwJERTENMAsGA1UEChMERVZDQzEJMAcGA1UECxMAMRUwEwYDVQQDDAxF
+ VkNDX0hFTVNfMDEwHhcNMjYwMTMwMTAzNjUzWhcNMzYwMTI4MTAzNjUzWjA+MQsw
+ CQYDVQQGEwJERTENMAsGA1UEChMERVZDQzEJMAcGA1UECxMAMRUwEwYDVQQDDAxF
+ VkNDX0hFTVNfMDEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ8Z40GPe8HzV17
+ ZhbBPkGBwhu8emdvzO5OWJUPSaX1R8rrWSaNwlh6COPG3fOVvUzCYcQbgmMFXK/o
+ nhuAslXTo0IwQDAOBgNVHQ8BAf8EBAMCB4AwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
+ HQ4EFgQUdQegutt1VxBfwDAlPdBpvrp7ztowCgYIKoZIzj0EAwIDRwAwRAIgS2ia
+ A+q0Tiy7NLyEjuKCV8QCVPjxt2OnfbSb9ZlDT3YCIDkwJoLc4+lsfvidDimUB+/I
+ ZvF1asyp3PVL7BDSFHil
+ -----END CERTIFICATE-----
+ private: |
+ -----BEGIN EC PRIVATE KEY-----
+ MHcCAQEEIIVZ2cLeTiENyCchgpj7eyJ5JKP5qpgj8eHUbLxn8Du1oAoGCCqGSM49
+ AwEHoUQDQgAEPGeNBj3vB81de2YWwT5BgcIbvHpnb8zuTliVD0ml9UfK61kmjcJY
+ egjjxt3zlb1MwmHEG4JjBVyv6J4bgLJV0w==
+ -----END EC PRIVATE KEY-----
+
+circuits:
+ - name: main
diff --git a/tests/config-eebus.spec.ts b/tests/config-eebus.spec.ts
new file mode 100644
index 000000000..fcc16fb93
--- /dev/null
+++ b/tests/config-eebus.spec.ts
@@ -0,0 +1,137 @@
+import { test, expect } from "@playwright/test";
+import { start, stop, baseUrl, restart } from "./evcc";
+import { expectModalHidden, expectModalVisible } from "./utils";
+
+test.use({ baseURL: baseUrl() });
+
+test.afterEach(async () => {
+ await stop();
+});
+
+test.describe("eebus", async () => {
+ test("first start, auto-configured", async ({ page }) => {
+ await start();
+ await page.goto("/#/config");
+
+ await page.getByTestId("eebus").getByRole("button", { name: "edit" }).click();
+ const modal = page.getByTestId("eebus-modal");
+ await expectModalVisible(modal);
+
+ await expect(modal.getByLabel("SHIP-ID")).not.toBeEmpty();
+ await expect(modal.getByLabel("SKI")).not.toBeEmpty();
+
+ await page.getByRole("button", { name: "Show advanced settings" }).click();
+ await expect(modal.getByLabel("Port")).toHaveValue("4712");
+ await expect(modal.getByLabel("Interfaces")).toBeVisible();
+ await expect(modal.getByLabel("Public certificate")).not.toBeEmpty();
+ await expect(modal.getByLabel("Private key")).toHaveValue("***");
+ });
+
+ test("change values", async ({ page }) => {
+ await start();
+ await page.goto("/#/config");
+
+ const eebusCard = page.getByTestId("eebus");
+
+ await eebusCard.getByRole("button", { name: "edit" }).click();
+ const modal = page.getByTestId("eebus-modal");
+ await expectModalVisible(modal);
+
+ const shipidReadonly = modal.locator("#eebus-shipid-display");
+ const ski = modal.getByLabel("SKI");
+ const shipidEditable = modal.locator("#eebus-shipid");
+ const port = modal.getByLabel("Port");
+ const interfaces = modal.getByLabel("Interfaces");
+ const publicCertificate = modal.getByLabel("Public certificate");
+ const privateKey = modal.getByLabel("Private key");
+
+ await page.getByRole("button", { name: "Show advanced settings" }).click();
+ await shipidEditable.fill("EVCC-1234");
+ await port.fill("4321");
+ await interfaces.fill("eth0");
+ await publicCertificate.fill("public certificate");
+ await privateKey.fill("private key");
+
+ // validate connection
+ await modal.getByRole("button", { name: "Save" }).click();
+ await expectModalHidden(modal);
+
+ // restart button appears
+ const restartButton = page
+ .getByTestId("bottom-banner")
+ .getByRole("button", { name: "Restart" });
+ await expect(restartButton).toBeVisible();
+
+ await restart();
+ await page.reload();
+
+ await eebusCard.getByRole("button", { name: "edit" }).click();
+
+ await page.getByRole("button", { name: "Show advanced settings" }).click();
+ await expect(shipidReadonly).toHaveValue("EVCC-1234");
+ await expect(ski).not.toBeVisible();
+ await expect(shipidEditable).toHaveValue("EVCC-1234");
+ await expect(port).toHaveValue("4321");
+ await expect(interfaces).toHaveValue("eth0");
+ await expect(publicCertificate).toHaveValue("public certificate");
+ await expect(privateKey).toHaveValue("***");
+ });
+ test("delete: ski changes", async ({ page }) => {
+ await start();
+ await page.goto("/#/config");
+
+ const eebusCard = page.getByTestId("eebus");
+
+ await page.getByTestId("eebus").getByRole("button", { name: "edit" }).click();
+ const modal = page.getByTestId("eebus-modal");
+ await expectModalVisible(modal);
+
+ // remember ski
+ const ski = modal.getByLabel("SKI");
+ const skiValue = await ski.inputValue();
+
+ page.on("dialog", async (dialog) => await dialog.accept());
+ await modal.getByRole("button", { name: "Remove" }).click();
+
+ // restart button appears
+ const restartButton = page
+ .getByTestId("bottom-banner")
+ .getByRole("button", { name: "Restart" });
+ await expect(restartButton).toBeVisible();
+
+ await restart();
+ await page.reload();
+
+ await eebusCard.getByRole("button", { name: "edit" }).click();
+
+ await expect(ski).not.toHaveValue(skiValue);
+ });
+
+ test("configured in yaml", async ({ page }) => {
+ await start("config-eebus.evcc.yaml");
+ await page.goto("/#/config");
+
+ await page.getByTestId("eebus").getByRole("button", { name: "edit" }).click();
+ const modal = page.getByTestId("eebus-modal");
+ await expectModalVisible(modal);
+
+ // yaml configuration note is visible
+ await expect(modal.getByText("Configured via evcc.yaml", { exact: false })).toBeVisible();
+
+ // SHIP-ID readonly field is visible with configured value
+ await expect(modal.getByLabel("SHIP-ID")).toBeVisible();
+ await expect(modal.getByLabel("SHIP-ID")).toHaveValue("EVCC_HEMS_01");
+ await expect(modal.getByLabel("SHIP-ID")).toHaveAttribute("readonly");
+
+ // SKI readonly field is visible
+ await expect(modal.getByLabel("SKI")).toBeVisible();
+ await expect(modal.getByLabel("SKI")).toHaveAttribute("readonly");
+
+ // no advanced settings button
+ await expect(modal.getByRole("button", { name: "Show advanced settings" })).not.toBeVisible();
+
+ // no action buttons
+ await expect(modal.getByRole("button", { name: "Save" })).not.toBeVisible();
+ await expect(modal.getByRole("button", { name: "Remove" })).not.toBeVisible();
+ });
+});
diff --git a/tests/config-ocpp.spec.ts b/tests/config-ocpp.spec.ts
index 6f999ac51..62b86eef9 100644
--- a/tests/config-ocpp.spec.ts
+++ b/tests/config-ocpp.spec.ts
@@ -25,7 +25,6 @@ test.describe("ocpp", () => {
// Open OCPP card
const ocppCard = page.getByTestId("ocpp");
- await expect(ocppCard).toContainText(["Configured", "no"].join(""));
await ocppCard.getByRole("button", { name: "edit" }).click();
const ocppModal = page.getByTestId("ocpp-modal");
@@ -44,7 +43,6 @@ test.describe("ocpp", () => {
// Navigate back to evcc config page
await page.goto("/#/config");
- await expect(ocppCard).toContainText(["Connections", "0/0", "Detected", "1"].join(""));
await ocppCard.getByRole("button", { name: "edit" }).click();
await expectModalVisible(ocppModal);
await expect(ocppModal).toContainText("Detected station IDs");
diff --git a/util/redact.go b/util/redact.go
new file mode 100644
index 000000000..1ede070ff
--- /dev/null
+++ b/util/redact.go
@@ -0,0 +1,8 @@
+package util
+
+func Masked(s any) string {
+ if s != "" {
+ return "***"
+ }
+ return ""
+}
diff --git a/util/sponsor/auth.go b/util/sponsor/auth.go
index 57dbb7c28..eecb0e68e 100644
--- a/util/sponsor/auth.go
+++ b/util/sponsor/auth.go
@@ -119,8 +119,8 @@ type Status struct {
Token string `json:"token,omitempty"`
}
-// GetStatus returns the sponsorship status
-func GetStatus() Status {
+// RedactedStatus returns the sponsorship status
+func RedactedStatus() Status {
mu.RLock()
defer mu.RUnlock()