From ab7ea3ad6a6324f922bb68a957fc940158d9f92e Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 11 Jan 2026 14:14:41 +0100 Subject: [PATCH 01/35] wip --- server/eebus/eebus.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go index c39bda28e7..40b175c94a 100644 --- a/server/eebus/eebus.go +++ b/server/eebus/eebus.go @@ -95,13 +95,11 @@ func NewServer(other Config) (*EEBus, error) { return nil, err } - log := util.NewLogger("eebus") + serial := cc.ShipID - protectedID := machine.ProtectedID("evcc-eebus") - serial := fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8]) - - if len(cc.ShipID) != 0 { - serial = cc.ShipID + if serial == "" { + protectedID := machine.ProtectedID("evcc-eebus") + serial = fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8]) } certificate, err := tls.X509KeyPair([]byte(cc.Certificate.Public), []byte(cc.Certificate.Private)) @@ -133,7 +131,7 @@ func NewServer(other Config) (*EEBus, error) { } c := &EEBus{ - log: log, + log: util.NewLogger("eebus"), Ski: ski, clients: make(map[string][]Device), } From 94a9299756e620d515d996f04899715c71a3dd6d Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 11 Jan 2026 14:11:03 +0100 Subject: [PATCH 02/35] wip --- cmd/configure/eebus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/configure/eebus.go b/cmd/configure/eebus.go index bba5e2a8e1..d5c1f04fec 100644 --- a/cmd/configure/eebus.go +++ b/cmd/configure/eebus.go @@ -11,7 +11,7 @@ import ( // configureEEBus setup EEBus func (c *CmdConfigure) configureEEBus(other map[string]any) error { conf := eebus.Config{ - URI: ":4712", + Port: 4712, } if err := util.DecodeOther(other, &conf); err != nil { From ab4edbaa50abeb86e27e2c65526a18018b3e9f27 Mon Sep 17 00:00:00 2001 From: andig Date: Sun, 11 Jan 2026 14:24:04 +0100 Subject: [PATCH 03/35] wip --- cmd/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/setup.go b/cmd/setup.go index ad030e55a5..156aaa7d41 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", From d46db9ef173c5b22e51877c29e8b8030b98aaeb1 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 13 Jan 2026 09:25:01 +0100 Subject: [PATCH 04/35] wip --- cmd/configure/eebus.go | 53 ------------------------------------------ 1 file changed, 53 deletions(-) delete mode 100644 cmd/configure/eebus.go diff --git a/cmd/configure/eebus.go b/cmd/configure/eebus.go deleted file mode 100644 index d5c1f04fec..0000000000 --- a/cmd/configure/eebus.go +++ /dev/null @@ -1,53 +0,0 @@ -package configure - -import ( - "fmt" - - "github.com/evcc-io/evcc/cmd/shutdown" - "github.com/evcc-io/evcc/server/eebus" - "github.com/evcc-io/evcc/util" -) - -// configureEEBus setup EEBus -func (c *CmdConfigure) configureEEBus(other map[string]any) error { - conf := eebus.Config{ - Port: 4712, - } - - if err := util.DecodeOther(other, &conf); err != nil { - return err - } - - srv, err := eebus.NewServer(conf) - if err == nil { - eebus.Instance = srv - go eebus.Instance.Run() - shutdown.Register(eebus.Instance.Shutdown) - } - - return err -} - -// eebusCertificate creates EEBUS certificate and returns private/public key -func (c *CmdConfigure) eebusCertificate() (map[string]any, error) { - var eebusConfig map[string]any - - cert, err := eebus.CreateCertificate() - if err != nil { - return eebusConfig, fmt.Errorf("%s", c.localizedString("Error_EEBUS_Certificate_Create")) - } - - pubKey, privKey, err := eebus.GetX509KeyPair(cert) - if err != nil { - return eebusConfig, fmt.Errorf("%s", c.localizedString("Error_EEBUS_Certificate_Use")) - } - - eebusConfig = map[string]any{ - "certificate": map[string]string{ - "public": pubKey, - "private": privKey, - }, - } - - return eebusConfig, nil -} From 158636df9bcda53ec0060ede9030b36329e34ad3 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 13 Jan 2026 09:30:29 +0100 Subject: [PATCH 05/35] wip --- api/globalconfig/types.go | 2 +- cmd/root.go | 4 ++-- cmd/setup.go | 2 +- server/eebus/eebus.go | 16 ++-------------- server/eebus/types.go | 7 ++++--- 5 files changed, 10 insertions(+), 21 deletions(-) diff --git a/api/globalconfig/types.go b/api/globalconfig/types.go index 3756f2f8c0..f21853574b 100644 --- a/api/globalconfig/types.go +++ b/api/globalconfig/types.go @@ -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/cmd/root.go b/cmd/root.go index 2db4daebf6..4e967cf57b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -329,11 +329,11 @@ 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: conf.EEBus.IsConfigured()} 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} diff --git a/cmd/setup.go b/cmd/setup.go index 156aaa7d41..648959f704 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -782,7 +782,7 @@ func configureEEBus(conf *eebus.Config) error { } } - if !conf.Configured() { + if !conf.IsConfigured() { return nil } diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go index e0736339ff..757368c687 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" @@ -90,7 +88,7 @@ var Instance *EEBus func NewServer(other Config) (*EEBus, error) { cc := Config{ - URI: ":4712", + Port: 4712, } if err := mergo.Merge(&cc, other, mergo.WithOverride); err != nil { @@ -109,22 +107,12 @@ 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 diff --git a/server/eebus/types.go b/server/eebus/types.go index 43aef380de..48a43979fa 100644 --- a/server/eebus/types.go +++ b/server/eebus/types.go @@ -15,13 +15,14 @@ type Certificate struct { } type Config struct { - URI string + URI_ string `mapstructure:"uri"` // TODO deprecated + Port int ShipID string Interfaces []string Certificate 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 } From 0ca452212556a66a10556f8de2ee8334572f1a20 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 13 Jan 2026 09:57:56 +0100 Subject: [PATCH 06/35] wip --- cmd/setup.go | 17 ++++++++++++++++- core/keys/global.go | 1 + server/eebus/eebus.go | 6 +----- server/eebus/types.go | 35 ++++++++++++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index 648959f704..b5652caea7 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -782,8 +782,23 @@ func configureEEBus(conf *eebus.Config) error { } } + if !conf.IsConfigured() && settings.Exists(keys.EEBusJSON) { + *conf = eebus.Config{} + if err := settings.Json(keys.EEBusJSON, &conf); err != nil { + return err + } + } + if !conf.IsConfigured() { - return nil + cc, err := eebus.DefaultConfig() + if err != nil { + return err + } + + *conf = *cc + if err := settings.SetJson(keys.EEBusJSON, conf); err != nil { + return err + } } var err error diff --git a/core/keys/global.go b/core/keys/global.go index 22e4edb2d9..4f4c17f6cf 100644 --- a/core/keys/global.go +++ b/core/keys/global.go @@ -9,6 +9,7 @@ const ( Mqtt = "mqtt" Influx = "influx" EEBus = "eebus" + EEBusJSON = "eebus_json" // TODO this is ugly Hems = "hems" Shm = "shm" Messaging = "messaging" diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go index 757368c687..e29bd5b5d5 100644 --- a/server/eebus/eebus.go +++ b/server/eebus/eebus.go @@ -32,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 { @@ -96,10 +95,8 @@ func NewServer(other Config) (*EEBus, error) { } serial := cc.ShipID - if serial == "" { - protectedID := machine.ProtectedID("evcc-eebus") - serial = fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8]) + serial = createShipID() } certificate, err := tls.X509KeyPair([]byte(cc.Certificate.Public), []byte(cc.Certificate.Private)) @@ -107,7 +104,6 @@ func NewServer(other Config) (*EEBus, error) { return nil, err } - // TODO: get the voltage from the site configuration, err := eebusapi.NewConfiguration( BrandName, BrandName, Model, serial, model.DeviceTypeTypeEnergyManagementSystem, diff --git a/server/eebus/types.go b/server/eebus/types.go index 48a43979fa..c444dd2c02 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" @@ -26,3 +31,31 @@ type Config struct { func (c Config) IsConfigured() bool { return len(c.Certificate.Public) > 0 && len(c.Certificate.Private) > 0 } + +func createShipID() string { + protectedID := machine.ProtectedID("evcc-eebus") + return fmt.Sprintf("%s-%0x", "EVCC", protectedID[:8]) +} + +func DefaultConfig() (*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 +} From 560b73727abbe3e75cc2d3c96584f781bce742e2 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 13 Jan 2026 14:19:25 +0100 Subject: [PATCH 07/35] wip --- cmd/setup.go | 17 +++++++---------- core/keys/global.go | 1 - 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index b5652caea7..3efeab1eae 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -164,7 +164,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 == "" { @@ -776,15 +776,12 @@ 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 { + // TODO delete if not needed any more + if err := migrateYamlToJson[eebus.Config](keys.EEBus); err != nil { return err } - } - if !conf.IsConfigured() && settings.Exists(keys.EEBusJSON) { - *conf = eebus.Config{} - if err := settings.Json(keys.EEBusJSON, &conf); err != nil { + if err := settings.Json(keys.EEBus, &conf); err != nil { return err } } @@ -796,7 +793,7 @@ func configureEEBus(conf *eebus.Config) error { } *conf = *cc - if err := settings.SetJson(keys.EEBusJSON, conf); err != nil { + if err := settings.SetJson(keys.EEBus, conf); err != nil { return err } } @@ -998,13 +995,13 @@ func migrateYamlToJson[T any](key string) error { func configureModbusProxy(conf *[]globalconfig.ModbusProxy) error { if settings.Exists(keys.ModbusProxy) { - // TODO: delete if not needed any more + // TODO delete if not needed any more if err := migrateYamlToJson[[]globalconfig.ModbusProxy](keys.ModbusProxy); err != nil { return err } if err := settings.Json(keys.ModbusProxy, &conf); err != nil { - return fmt.Errorf("failed to read modbusproxy setting: %w", err) + return err } } diff --git a/core/keys/global.go b/core/keys/global.go index 4f4c17f6cf..22e4edb2d9 100644 --- a/core/keys/global.go +++ b/core/keys/global.go @@ -9,7 +9,6 @@ const ( Mqtt = "mqtt" Influx = "influx" EEBus = "eebus" - EEBusJSON = "eebus_json" // TODO this is ugly Hems = "hems" Shm = "shm" Messaging = "messaging" From 5e6ad8e95de8960e5d53a016b87d03ee63fa7f02 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 13 Jan 2026 18:03:04 +0100 Subject: [PATCH 08/35] Simplify --- cmd/setup.go | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index 3efeab1eae..84989e56f6 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -777,11 +777,7 @@ func configureEEBus(conf *eebus.Config) error { // migrate settings if settings.Exists(keys.EEBus) { // TODO delete if not needed any more - if err := migrateYamlToJson[eebus.Config](keys.EEBus); err != nil { - return err - } - - if err := settings.Json(keys.EEBus, &conf); err != nil { + if err := migrateYamlToJson(keys.EEBus, conf); err != nil { return err } } @@ -977,30 +973,26 @@ func configureDevices(conf globalconfig.All) error { } // migrateYamlToJson converts a settings value from yaml to json if needed -func migrateYamlToJson[T any](key string) error { - var err error +func migrateYamlToJson[T any](key string, res *T) 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) + if err := settings.Yaml(key, new(T), res); err != nil { + return err } - return err + settings.SetJson(key, res) + log.INFO.Printf("migrated %s setting to JSON", key) + + return nil } 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 { - return err - } - - if err := settings.Json(keys.ModbusProxy, &conf); err != nil { + if err := migrateYamlToJson(keys.ModbusProxy, conf); err != nil { return err } } From a69ecdf2131366e58dacb471c3fb47f16c606f29 Mon Sep 17 00:00:00 2001 From: andig Date: Tue, 13 Jan 2026 18:04:39 +0100 Subject: [PATCH 09/35] wip --- cmd/setup.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/setup.go b/cmd/setup.go index 84989e56f6..ae4e6fef31 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -975,8 +975,7 @@ func configureDevices(conf globalconfig.All) error { // migrateYamlToJson converts a settings value from yaml to json if needed func migrateYamlToJson[T any](key string, res *T) error { if settings.IsJson(key) { - // already JSON, nothing to do - return nil + return settings.Json(key, res) } if err := settings.Yaml(key, new(T), res); err != nil { From 00a5b4978d984eeccccb1787ab6504160f03e48e Mon Sep 17 00:00:00 2001 From: andig Date: Wed, 14 Jan 2026 14:23:20 +0100 Subject: [PATCH 10/35] Add test --- cmd/helper.go | 17 +++++++++++++++++ cmd/helper_test.go | 34 ++++++++++++++++++++++++++++++++++ cmd/setup.go | 18 ------------------ 3 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 cmd/helper_test.go diff --git a/cmd/helper.go b/cmd/helper.go index 981aaea320..937dbca984 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,19 @@ 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) + log.INFO.Printf("migrated %s setting to JSON", key) + + return nil +} diff --git a/cmd/helper_test.go b/cmd/helper_test.go new file mode 100644 index 0000000000..7f2c81d8f1 --- /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/setup.go b/cmd/setup.go index ae4e6fef31..1d38046b51 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -776,7 +776,6 @@ func configureOCPP(cfg *ocpp.Config, externalUrl string) { func configureEEBus(conf *eebus.Config) error { // migrate settings if settings.Exists(keys.EEBus) { - // TODO delete if not needed any more if err := migrateYamlToJson(keys.EEBus, conf); err != nil { return err } @@ -972,25 +971,8 @@ 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, 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) - log.INFO.Printf("migrated %s setting to JSON", key) - - return nil -} - func configureModbusProxy(conf *[]globalconfig.ModbusProxy) error { if settings.Exists(keys.ModbusProxy) { - // TODO delete if not needed any more if err := migrateYamlToJson(keys.ModbusProxy, conf); err != nil { return err } From 0373b01f43148ee30591f481286862b3efd70aa3 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 15 Jan 2026 16:12:41 +0100 Subject: [PATCH 11/35] Update cmd/helper.go --- cmd/helper.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/helper.go b/cmd/helper.go index 937dbca984..f0d76e6c24 100644 --- a/cmd/helper.go +++ b/cmd/helper.go @@ -133,7 +133,6 @@ func migrateYamlToJson[T any](key string, res *T) error { } settings.SetJson(key, res) - log.INFO.Printf("migrated %s setting to JSON", key) return nil } From 65f26ad283eb80e0eb95f77d4f7356d141b98fbb Mon Sep 17 00:00:00 2001 From: Maschga <88616799+Maschga@users.noreply.github.com> Date: Thu, 22 Jan 2026 08:40:16 +0100 Subject: [PATCH 12/35] EEBUS: configure by default (#26882) --- api/globalconfig/types.go | 19 ++--- assets/js/components/Config/EebusModal.vue | 83 ++++++++++++++++--- assets/js/components/Config/PropertyField.vue | 4 +- .../components/Config/defaultYaml/eebus.yaml | 12 --- assets/js/types/evcc.ts | 15 +++- cmd/root.go | 2 +- i18n/en.json | 11 +++ server/eebus/types.go | 27 ++++-- util/redact.go | 8 ++ 9 files changed, 137 insertions(+), 44 deletions(-) delete mode 100644 assets/js/components/Config/defaultYaml/eebus.yaml create mode 100644 util/redact.go diff --git a/api/globalconfig/types.go b/api/globalconfig/types.go index f21853574b..3dd28f9b4b 100644 --- a/api/globalconfig/types.go +++ b/api/globalconfig/types.go @@ -79,13 +79,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 +90,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 +117,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, } } diff --git a/assets/js/components/Config/EebusModal.vue b/assets/js/components/Config/EebusModal.vue index d4e4996697..304ef031b1 100644 --- a/assets/js/components/Config/EebusModal.vue +++ b/assets/js/components/Config/EebusModal.vue @@ -1,26 +1,89 @@ - diff --git a/assets/js/components/Config/PropertyField.vue b/assets/js/components/Config/PropertyField.vue index 14d6da7b42..beb150ef42 100644 --- a/assets/js/components/Config/PropertyField.vue +++ b/assets/js/components/Config/PropertyField.vue @@ -204,7 +204,9 @@ export default { return this.property === "icon"; }, textarea() { - return ["accessToken", "refreshToken", "identifiers"].includes(this.property); + return ["accessToken", "refreshToken", "identifiers", "interfaces"].includes( + this.property + ); }, boolean() { return this.type === "Bool"; diff --git a/assets/js/components/Config/defaultYaml/eebus.yaml b/assets/js/components/Config/defaultYaml/eebus.yaml deleted file mode 100644 index 73a0d5dedb..0000000000 --- a/assets/js/components/Config/defaultYaml/eebus.yaml +++ /dev/null @@ -1,12 +0,0 @@ -#shipid: EVCC-1234567890abcdef -#interfaces: -# - eth0 -#certificate: -# public: | -# -----BEGIN CERTIFICATE----- -# 1234567890abcdef== -# -----END CERTIFICATE----- -# private: | -# -----BEGIN EC PRIVATE KEY----- -# 1234567890abcdef -# -----END EC PRIVATE KEY----- diff --git a/assets/js/types/evcc.ts b/assets/js/types/evcc.ts index c6714a5cc3..03d565d958 100644 --- a/assets/js/types/evcc.ts +++ b/assets/js/types/evcc.ts @@ -84,7 +84,7 @@ export interface State { hems?: Hems; shm?: ShmConfig; sponsor?: Sponsor; - eebus?: any; + eebus?: Eebus; modbusproxy?: ModbusProxy[]; messaging?: any; interval?: number; @@ -440,6 +440,19 @@ export enum MODBUS_PROTOCOL { RTU = "rtu", } +export type Certificate = { + public: string; + private: string; +}; + +export type Eebus = { + uri: string; + port: number; + shipid: string; + interfaces?: string[]; + certificate?: Certificate; +}; + export type ModbusProxy = { port: number; readonly: MODBUS_PROXY_READONLY; diff --git a/cmd/root.go b/cmd/root.go index 4e967cf57b..c991b4dcdb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -329,7 +329,7 @@ func runRoot(cmd *cobra.Command, args []string) { } // publish initial settings - valueChan <- util.Param{Key: keys.EEBus, Val: conf.EEBus.IsConfigured()} + valueChan <- util.Param{Key: keys.EEBus, Val: conf.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} diff --git a/i18n/en.json b/i18n/en.json index 1f8d28b98a..1ca73c2cb4 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -172,7 +172,18 @@ "loading": "Loading YAML editor…" }, "eebus": { + "certificate": { + "private": "Private key", + "public": "Public certificate" + }, "description": "Configuration that enables evcc to communicate with other EEBus devices.", + "https": "HTTPS connection", + "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.", + "shipid": "SHIP-ID", + "shipidHelp": "From evcc predefined shipid. It's tied to the HTTPS certificates below - if either changes, pairing with devices must be redone.", "title": "EEBus" }, "experimental": { diff --git a/server/eebus/types.go b/server/eebus/types.go index c444dd2c02..7804eb24ff 100644 --- a/server/eebus/types.go +++ b/server/eebus/types.go @@ -16,15 +16,16 @@ 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 `mapstructure:"uri"` // TODO deprecated - Port int - 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"` } // IsConfigured returns true if the EEbus server is configured @@ -32,6 +33,20 @@ 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]) diff --git a/util/redact.go b/util/redact.go new file mode 100644 index 0000000000..1ede070ffe --- /dev/null +++ b/util/redact.go @@ -0,0 +1,8 @@ +package util + +func Masked(s any) string { + if s != "" { + return "***" + } + return "" +} From 9aed2f7953e2ba5cf04316111cc2ebd4909739c6 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 22 Jan 2026 09:09:30 +0100 Subject: [PATCH 13/35] Update i18n/en.json Co-authored-by: Michael Geers --- i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/en.json b/i18n/en.json index 1ca73c2cb4..07a26c2077 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -179,7 +179,7 @@ "description": "Configuration that enables evcc to communicate with other EEBus devices.", "https": "HTTPS connection", "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.", + "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.", "shipid": "SHIP-ID", From 686f955eb1b6ee395867b7240eddb1e8db16fac0 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 22 Jan 2026 09:10:24 +0100 Subject: [PATCH 14/35] Update i18n/en.json Co-authored-by: Michael Geers --- i18n/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/en.json b/i18n/en.json index 07a26c2077..432d43a455 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -177,7 +177,7 @@ "public": "Public certificate" }, "description": "Configuration that enables evcc to communicate with other EEBus devices.", - "https": "HTTPS connection", + "https": "Certificats", "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", From ff7ca30a44045e3043306607ff6da7e83bf3c993 Mon Sep 17 00:00:00 2001 From: andig Date: Thu, 22 Jan 2026 09:52:19 +0100 Subject: [PATCH 15/35] Publish ski --- cmd/root.go | 9 ++++++++- server/eebus/eebus.go | 6 +++--- server/eebus/types.go | 8 ++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index c991b4dcdb..1c29bd7ede 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" @@ -329,7 +330,13 @@ func runRoot(cmd *cobra.Command, args []string) { } // publish initial settings - valueChan <- util.Param{Key: keys.EEBus, Val: conf.EEBus} + valueChan <- util.Param{Key: keys.EEBus, Val: struct { + Config eebus.Config `json:"config"` + Ski string `json:"ski"` + }{ + Config: conf.EEBus, + Ski: eebus.Ski(), + }} 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} diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go index e29bd5b5d5..6d37e432b1 100644 --- a/server/eebus/eebus.go +++ b/server/eebus/eebus.go @@ -78,7 +78,7 @@ type EEBus struct { mux sync.Mutex log *util.Logger - Ski string + ski string clients map[string][]Device } @@ -129,7 +129,7 @@ func NewServer(other Config) (*EEBus, error) { c := &EEBus{ log: util.NewLogger("eebus"), - Ski: ski, + ski: ski, clients: make(map[string][]Device), } @@ -207,7 +207,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/types.go b/server/eebus/types.go index 7804eb24ff..24091ef516 100644 --- a/server/eebus/types.go +++ b/server/eebus/types.go @@ -74,3 +74,11 @@ func DefaultConfig() (*Config, error) { return &res, nil } + +// Ski returns true if the EEbus server is configured +func Ski() string { + if Instance == nil { + return "" + } + return Instance.ski +} From 952f02835b2d6aa535b4269c39656554bd818ca9 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Thu, 22 Jan 2026 11:41:26 +0100 Subject: [PATCH 16/35] merge --- api/globalconfig/types.go | 7 +++++ cmd/root.go | 33 ++++++++---------------- cmd/setup.go | 3 +++ server/http_config_site_other_handler.go | 11 +++----- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/api/globalconfig/types.go b/api/globalconfig/types.go index 3dd28f9b4b..8292c41738 100644 --- a/api/globalconfig/types.go +++ b/api/globalconfig/types.go @@ -18,6 +18,13 @@ import ( "github.com/evcc-io/evcc/util/modbus" ) +// Info for publishing config, status and source to UI and external systems +type Info struct { + Config any `json:"config,omitempty"` + Status any `json:"status,omitempty"` + FromYaml bool `json:"fromYaml,omitempty"` +} + type All struct { Network Network Ocpp ocpp.Config diff --git a/cmd/root.go b/cmd/root.go index 1c29bd7ede..91aad84c89 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -177,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.Info{ Config: conf.Ocpp, Status: ocpp.GetStatus(), }} @@ -330,12 +327,14 @@ func runRoot(cmd *cobra.Command, args []string) { } // publish initial settings - valueChan <- util.Param{Key: keys.EEBus, Val: struct { - Config eebus.Config `json:"config"` - Ski string `json:"ski"` - }{ + valueChan <- util.Param{Key: keys.EEBus, Val: globalconfig.Info{ Config: conf.EEBus, - Ski: eebus.Ski(), + Status: struct { + Ski string `json:"ski"` + }{ + Ski: eebus.Ski(), + }, + FromYaml: fromYaml.eebus, }} valueChan <- util.Param{Key: keys.Shm, Val: conf.SHM} valueChan <- util.Param{Key: keys.Influx, Val: conf.Influx} @@ -344,26 +343,16 @@ func runRoot(cmd *cobra.Command, args []string) { 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.Info{ Config: conf.Ocpp, Status: ocpp.GetStatus(), }} - valueChan <- util.Param{Key: keys.Sponsor, Val: struct { - Status sponsor.Status `json:"status"` - FromYaml bool `json:"fromYaml"` - }{ + valueChan <- util.Param{Key: keys.Sponsor, Val: globalconfig.Info{ Status: sponsor.GetStatus(), 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"` - }{ + valueChan <- util.Param{Key: keys.Hems, Val: globalconfig.Info{ Config: conf.HEMS, Status: hemsapi.GetStatus(hems), FromYaml: fromYaml.hems, diff --git a/cmd/setup.go b/cmd/setup.go index 1d38046b51..db7102b870 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -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_.:-]+$`) @@ -779,6 +780,8 @@ func configureEEBus(conf *eebus.Config) error { if err := migrateYamlToJson(keys.EEBus, conf); err != nil { return err } + } else if conf.IsConfigured() { + fromYaml.eebus = true } if !conf.IsConfigured() { diff --git a/server/http_config_site_other_handler.go b/server/http_config_site_other_handler.go index a7c6904b9c..c871af836c 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,10 +27,7 @@ func updateSponsortokenHandler(pub publisher) func(w http.ResponseWriter, r *htt return } - pub(keys.Sponsor, struct { - Status sponsor.Status `json:"status"` - FromYaml bool `json:"fromYaml"` - }{ + pub(keys.Sponsor, globalconfig.Info{ Status: sponsor.GetStatus(), FromYaml: false, }) @@ -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.Info{ Status: sponsor.Status{}, FromYaml: false, }) From 914a926842510d375a73fa93d0096518ff39852b Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Thu, 22 Jan 2026 11:45:22 +0100 Subject: [PATCH 17/35] refactor --- cmd/root.go | 8 ++------ server/eebus/eebus.go | 8 ++++++++ server/eebus/test/cs_test.go | 5 +++-- server/eebus/types.go | 2 +- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 91aad84c89..7db4537c5f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -328,12 +328,8 @@ func runRoot(cmd *cobra.Command, args []string) { // publish initial settings valueChan <- util.Param{Key: keys.EEBus, Val: globalconfig.Info{ - Config: conf.EEBus, - Status: struct { - Ski string `json:"ski"` - }{ - Ski: eebus.Ski(), - }, + Config: conf.EEBus, + Status: eebus.GetStatus(), FromYaml: fromYaml.eebus, }} valueChan <- util.Param{Key: keys.Shm, Val: conf.SHM} diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go index 6d37e432b1..a819422e65 100644 --- a/server/eebus/eebus.go +++ b/server/eebus/eebus.go @@ -85,6 +85,14 @@ type EEBus struct { var Instance *EEBus +func GetStatus() any { + return struct { + Ski string `json:"ski"` + }{ + Ski: Ski(), + } +} + func NewServer(other Config) (*EEBus, error) { cc := Config{ Port: 4712, diff --git a/server/eebus/test/cs_test.go b/server/eebus/test/cs_test.go index df31cde9d7..64047fb98f 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 24091ef516..a7e64f843e 100644 --- a/server/eebus/types.go +++ b/server/eebus/types.go @@ -75,7 +75,7 @@ func DefaultConfig() (*Config, error) { return &res, nil } -// Ski returns true if the EEbus server is configured +// Ski returns the EEbus server SKI func Ski() string { if Instance == nil { return "" From 985bc96a60f27d29b4c3a5eb5f8799f240d5fa1e Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Thu, 22 Jan 2026 11:50:37 +0100 Subject: [PATCH 18/35] fix lint --- assets/js/components/Config/EebusModal.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/assets/js/components/Config/EebusModal.vue b/assets/js/components/Config/EebusModal.vue index 304ef031b1..7f6e4cce96 100644 --- a/assets/js/components/Config/EebusModal.vue +++ b/assets/js/components/Config/EebusModal.vue @@ -70,7 +70,8 @@