From 10e3f0dcf97f4b9cd82307214237613c07c065a7 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 15 Jun 2026 09:55:10 +0200 Subject: [PATCH] EEBus: update eebus-go, ship-go and spine-go to dev (#30633) --- go.mod | 14 ++++----- go.sum | 34 ++++++++++++--------- hems/eebus/events.go | 34 +++++++++++++++++++-- server/eebus/eebus.go | 53 ++++++++++++++++++++++----------- server/eebus/eebus_test.go | 7 +++-- server/eebus/test/controlbox.go | 27 +++++++++++------ 6 files changed, 117 insertions(+), 52 deletions(-) diff --git a/go.mod b/go.mod index 1813a94cb..6a656ab5f 100644 --- a/go.mod +++ b/go.mod @@ -29,9 +29,9 @@ require ( github.com/denisbrodbeck/machineid v1.0.1 github.com/dylanmei/iso8601 v0.1.0 github.com/eclipse/paho.mqtt.golang v1.5.1 - github.com/enbility/eebus-go v0.7.0 - github.com/enbility/ship-go v0.6.0 - github.com/enbility/spine-go v0.7.0 + github.com/enbility/eebus-go v0.7.1-0.20260520155652-11bc53084557 + github.com/enbility/ship-go v0.6.1-0.20260518113001-134687068e3c + github.com/enbility/spine-go v0.7.1-0.20260520153416-0104ce40c885 github.com/evcc-io/openapi-mcp v0.6.1-0.20260503092507-6199c7ad3baf github.com/evcc-io/optimizer v0.0.0-20260531165648-b5cbfebdaa65 github.com/evcc-io/rct v0.2.0 @@ -122,7 +122,6 @@ require ( require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect - github.com/ahmetb/go-linq/v3 v3.2.0 // indirect github.com/andybalholm/cascadia v1.3.3 // indirect github.com/antihax/optional v1.0.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect @@ -178,6 +177,7 @@ require ( github.com/google/renameio/v2 v2.0.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/gosimple/unidecode v1.0.1 // indirect + github.com/govalues/decimal v0.1.36 // indirect github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -192,7 +192,7 @@ require ( github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mergermarket/go-pkcs7 v0.0.0-20170926155232-153b18ea13c9 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect - github.com/miekg/dns v1.1.62 // indirect + github.com/miekg/dns v1.1.72 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -216,8 +216,8 @@ require ( github.com/quic-go/webtransport-go v0.10.0 // indirect github.com/relvacode/iso8601 v1.6.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rickb777/date v1.21.1 // indirect - github.com/rickb777/plural v1.4.2 // indirect + github.com/rickb777/period v1.0.22 // indirect + github.com/rickb777/plural v1.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect diff --git a/go.sum b/go.sum index 89f12ba06..0b303692a 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWso github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/WulfgarW/sensonet v0.0.7 h1:WkrZF7UNoQVyQIvpaUPqZfKTvHfFXTRmSF/zMQKJo+Q= github.com/WulfgarW/sensonet v0.0.7/go.mod h1:GNOik1nTPswwtZ4bLpIRglEn9dNgEG79iOhVUq4IKgA= -github.com/ahmetb/go-linq/v3 v3.2.0 h1:BEuMfp+b59io8g5wYzNoFe9pWPalRklhlhbiU3hYZDE= -github.com/ahmetb/go-linq/v3 v3.2.0/go.mod h1:haQ3JfOeWK8HpVxMtHHEMPVgBKiYyQ+f1/kLZh/cj9U= github.com/andig/go-powerwall v0.3.0 h1:6g50+8xEwxrnzoSaPsxNRpc2fld8gARk0X78NEexFGI= github.com/andig/go-powerwall v0.3.0/go.mod h1:Xk09mD+7RTCuuHMX5RxlqgEUeh9oZdIX0rL0qiY6l/4= github.com/andig/gosunspec v0.0.0-20260523125438-3accc276abc0 h1:i34M/0yMHLaDJP6CQ3KMpeKVD+n9XrV5nPb3tdMKFF8= @@ -138,14 +136,14 @@ github.com/eclipse/paho.golang v0.23.0 h1:KHgl2wz6EJo7cMBmkuhpt7C576vP+kpPv7jjvS github.com/eclipse/paho.golang v0.23.0/go.mod h1:nQRhTkoZv8EAiNs5UU0/WdQIx2NrnWUpL9nsGJTQN04= github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE= github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU= -github.com/enbility/eebus-go v0.7.0 h1:Uh3i+HMmTYecWA+BBlYYhNFuNtqzWWQarbv4z9n/aQI= -github.com/enbility/eebus-go v0.7.0/go.mod h1:ftoVhXGC00IEcfN4RZSb1PbBIglE9i3JYqwrjhXnYSA= +github.com/enbility/eebus-go v0.7.1-0.20260520155652-11bc53084557 h1:YQvO8wiUG1OEjFAkKtLTrCFze8KpG7YZ1TwrQKj9H54= +github.com/enbility/eebus-go v0.7.1-0.20260520155652-11bc53084557/go.mod h1:L1nBEy/dBASQRoz/9JF7KEmqFdnXHLbZqDpxiA0ezr4= github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a h1:foChWb8lhzqa6lWDRs6COYMdp649YlUirFP8GqoT0JQ= github.com/enbility/go-avahi v0.0.0-20240909195612-d5de6b280d7a/go.mod h1:H64mhYcAQUGUUnVqMdZQf93kPecH4M79xwH95Lddt3U= -github.com/enbility/ship-go v0.6.0 h1:1ft5NJJHqqGU3/ryYwQj8xBYJLFbf0q2cP9mjlYHlgw= -github.com/enbility/ship-go v0.6.0/go.mod h1:JJp8EQcJhUhTpZ2LSEU4rpdaM3E2n08tswWFWtmm/wU= -github.com/enbility/spine-go v0.7.0 h1:UZeghFgnM3VFU0ghc57Htt6gnxwP9jLppfU2GUMJGgY= -github.com/enbility/spine-go v0.7.0/go.mod h1:IF1sBTr7p3wXqlejeBJcJ8BYFlzzRaZcJsGw8XjgEgc= +github.com/enbility/ship-go v0.6.1-0.20260518113001-134687068e3c h1:ZNYyr/SDaAMHi6ubJqWuoId8QUu5V4xXWCIRf7VnfCY= +github.com/enbility/ship-go v0.6.1-0.20260518113001-134687068e3c/go.mod h1:EvRFx83phCwPQrOuWw2CX5t0c6mcFVzIk6gQEz4Lijg= +github.com/enbility/spine-go v0.7.1-0.20260520153416-0104ce40c885 h1:nBqjAPONpI/PDRa17kHjwloCnk/Ejv5C5laXwXLGm1U= +github.com/enbility/spine-go v0.7.1-0.20260520153416-0104ce40c885/go.mod h1:ddWZU5BQGyzRGF20Z7rUKFFzbCp+cfu9mZgYtOMW/fM= github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6 h1:XOYvxKtT1oxT37w/5oEiRLuPbm9FuJPt3fiYhX0h8Po= github.com/enbility/zeroconf/v2 v2.0.0-20240920094356-be1cae74fda6/go.mod h1:BszP9qFV14mPXgyIREbgIdQtWxbAj3OKqvK02HihMoM= github.com/evcc-io/modbus v0.0.0-20250501165638-8b6f1fbdb7ea h1:F6eyC8V8wvc3ranlsR4coAls+OPBkVvxyX0a2VqdlPc= @@ -266,6 +264,8 @@ github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo= github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= +github.com/govalues/decimal v0.1.36 h1:dojDpsSvrk0ndAx8+saW5h9WDIHdWpIwrH/yhl9olyU= +github.com/govalues/decimal v0.1.36/go.mod h1:Ee7eI3Llf7hfqDZtpj8Q6NCIgJy1iY3kH1pSwDrNqlM= github.com/gregdel/pushover v1.4.0 h1:P77WAJ2zPG+b0mEsmMjWGrPMuvhkh9k3v7OviwsoveE= github.com/gregdel/pushover v1.4.0/go.mod h1:EcaO66Nn1StkpEm1iKtBTV3d2A16SoMsVER1PthX7to= github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa h1:Rsn6ARgNkXrsXJIzhkE4vQr5Gbx2LvtEMv4BJOK4LyU= @@ -364,8 +364,8 @@ github.com/mergermarket/go-pkcs7 v0.0.0-20170926155232-153b18ea13c9/go.mod h1:GH github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI= +github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -420,6 +420,8 @@ github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3 github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc= github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/ff v1.2.0/go.mod h1:ljiF7yxtUvZaxUDyUqQa0+uiEOgwVboj+Q2S2+0nq40= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw= +github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= github.com/philippseith/signalr v0.8.0 h1:CvylMNn7TkJi4adUlk75K08OwljdmBqo6jd12Pz2Guw= github.com/philippseith/signalr v0.8.0/go.mod h1:ZIAyv2b3xIsh+8j++0Omtp0Xe4CwDnwfyyZBEh5Z9uk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -447,10 +449,12 @@ github.com/relvacode/iso8601 v1.6.0 h1:eFXUhMJN3Gz8Rcq82f9DTMW0svjtAVuIEULglM7QH github.com/relvacode/iso8601 v1.6.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rickb777/date v1.21.1 h1:tUcQS8riIRoYK5kUAv5aevllFEYUEk2x8OYDyoldOn4= -github.com/rickb777/date v1.21.1/go.mod h1:gnDexsbXViZr2fCKMrY3m6IfAF5U2vSkEaiGJcNFaLQ= -github.com/rickb777/plural v1.4.2 h1:Kl/syFGLFZ5EbuV8c9SVud8s5HI2HpCCtOMw2U1kS+A= -github.com/rickb777/plural v1.4.2/go.mod h1:kdmXUpmKBJTS0FtG/TFumd//VBWsNTD7zOw7x4umxNw= +github.com/rickb777/expect v1.0.6 h1:1JE3CfYGyuhN5OTu5nqvIF3VjS2AoqoctcGlbNTVl3w= +github.com/rickb777/expect v1.0.6/go.mod h1:raunaduUM/p8CzpTZeDmoexwlIFF+Peg0Mj/p//9mkA= +github.com/rickb777/period v1.0.22 h1:/X41JreTYsjifLDGBCaVN+s5r5/G0sTbKoHBaISv2ns= +github.com/rickb777/period v1.0.22/go.mod h1:liTmui1MSVgOqkJemF3K6c35CqiEHp0oGHCNZIXnIMA= +github.com/rickb777/plural v1.4.7 h1:rBRAxp9aTFYzWTLWIE/UTwKcaqSSAV2ml7aOUFYpAGo= +github.com/rickb777/plural v1.4.7/go.mod h1:DB19dtrplGS5s6VJVHn7tvmFYPoE83p1xqio3oVnNRM= github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0= github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= @@ -465,6 +469,8 @@ github.com/sandrolain/httpcache v1.4.0 h1:Jf4Vx62X2ybvNPSpPvI1kT3xvMdDG1AsApQjOQ github.com/sandrolain/httpcache v1.4.0/go.mod h1:kHBuXveitSn39SNPBhdf/ybG272X706HJ2RJqOQ+Em0= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU= +github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U= github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= diff --git a/hems/eebus/events.go b/hems/eebus/events.go index 409431e2a..0cd4f60ab 100644 --- a/hems/eebus/events.go +++ b/hems/eebus/events.go @@ -2,9 +2,11 @@ package eebus import ( eebusapi "github.com/enbility/eebus-go/api" + ucapi "github.com/enbility/eebus-go/usecases/api" "github.com/enbility/eebus-go/usecases/cs/lpc" "github.com/enbility/eebus-go/usecases/cs/lpp" spineapi "github.com/enbility/spine-go/api" + "github.com/enbility/spine-go/model" "github.com/evcc-io/evcc/server/eebus" ) @@ -27,9 +29,17 @@ func (c *EEBus) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spineapi.E // and invoke `ApproveOrDenyConsumptionLimit` for each // // Use Case LPC, Scenario 1 - case lpc.WriteApprovalRequired: + case lpc.LimitWriteApprovalRequired: c.consumptionWriteApprovalRequired() + // An incoming device configuration write (e.g. failsafe values) needs to be + // approved or denied. eebus-go <= v0.7.0 applied these automatically, so we + // keep that behaviour by approving all pending configuration writes. + // + // Use Case LPC, Scenario 2 + case lpc.ConfigurationWriteApprovalRequired: + c.approveDeviceConfigurations(c.cs.CsLPCInterface.PendingDeviceConfigurations(), c.cs.CsLPCInterface.ApproveOrDenyDeviceConfiguration) + // Failsafe limit for the consumed active (real) power of the // Controllable System data update received // @@ -69,9 +79,17 @@ func (c *EEBus) UseCaseEvent(_ spineapi.DeviceRemoteInterface, entity spineapi.E // and invoke `ApproveOrDenyProductionLimit` for each // // Use Case LPP, Scenario 1 - case lpp.WriteApprovalRequired: + case lpp.LimitWriteApprovalRequired: c.productionWriteApprovalRequired() + // An incoming device configuration write (e.g. failsafe values) needs to be + // approved or denied. eebus-go <= v0.7.0 applied these automatically, so we + // keep that behaviour by approving all pending configuration writes. + // + // Use Case LPP, Scenario 2 + case lpp.ConfigurationWriteApprovalRequired: + c.approveDeviceConfigurations(c.cs.CsLPPInterface.PendingDeviceConfigurations(), c.cs.CsLPPInterface.ApproveOrDenyDeviceConfiguration) + // Failsafe limit for the produced active (real) power of the // Controllable System data update received // @@ -156,6 +174,18 @@ func (c *EEBus) productionWriteApprovalRequired() { } } +// approveDeviceConfigurations approves all pending device configuration writes, +// preserving the automatic behaviour of eebus-go <= v0.7.0 +func (c *EEBus) approveDeviceConfigurations( + pending map[model.MsgCounterType][]ucapi.PendingDeviceConfiguration, + approve func(msgCounter model.MsgCounterType, approve bool, reason string), +) { + for msg, configs := range pending { + c.log.DEBUG.Println("approving device configuration write:", msg, configs) + approve(msg, true, "") + } +} + func (c *EEBus) updateFailsafeConsumptionActivePowerLimit() { limit, _, err := c.cs.CsLPCInterface.FailsafeConsumptionActivePowerLimit() if err != nil { diff --git a/server/eebus/eebus.go b/server/eebus/eebus.go index 38a66dfc0..3675add09 100644 --- a/server/eebus/eebus.go +++ b/server/eebus/eebus.go @@ -69,7 +69,7 @@ type EnergyGuard struct { type EEBus struct { service eebusapi.ServiceInterface - remoteServices []shipapi.RemoteService + remoteServices []shipapi.RemoteMdnsService cem CustomerEnergyManagement cs ControllableSystem @@ -115,9 +115,13 @@ func NewServer(other Config) (*EEBus, error) { configuration, err := eebusapi.NewConfiguration( BrandName, BrandName, Model, serial, + []shipapi.DeviceCategoryType{shipapi.DeviceCategoryTypeEnergyManagementSystem}, model.DeviceTypeTypeEnergyManagementSystem, []model.EntityTypeType{model.EntityTypeTypeCEM}, cc.Port, certificate, time.Second*4, + // no SHIP Pairing (and thus no ring buffer persistence): remote services are + // trusted by their configured SKI via RegisterRemoteService + nil, nil, ) if err != nil { return nil, err @@ -220,10 +224,11 @@ func (c *EEBus) RegisterDevice(ski, ip string, device Device) error { return errors.New("device ski can not be identical to host ski") } + identity := shipapi.NewServiceIdentity(ski, "", "") if len(ip) > 0 { - c.service.RemoteServiceForSKI(ski).SetIPv4(ip) + identity.IPv4 = ip } - c.service.RegisterRemoteSKI(ski) + c.service.RegisterRemoteService(identity) c.mux.Lock() defer c.mux.Unlock() @@ -248,7 +253,7 @@ func (c *EEBus) UnregisterDevice(ski string, device Device) { // which calls back into evcc's connect(ski, false) — and that needs to // acquire c.mux. Holding c.mux across this cross-layer call would // deadlock the same goroutine on its own non-reentrant mutex. See #28942. - defer c.service.UnregisterRemoteSKI(ski) + defer c.service.UnregisterRemoteService(shipapi.NewServiceIdentity(ski, "", "")) } } c.mux.Unlock() @@ -270,7 +275,7 @@ func (c *EEBus) EnergyGuard() *EnergyGuard { return &c.eg } -func (c *EEBus) RemoteServices() []shipapi.RemoteService { +func (c *EEBus) RemoteServices() []shipapi.RemoteMdnsService { c.mux.Lock() defer c.mux.Unlock() return c.remoteServices @@ -314,32 +319,32 @@ func (c *EEBus) connect(ski string, connected bool) { } } -func (c *EEBus) RemoteSKIConnected(service eebusapi.ServiceInterface, ski string) { - c.connect(ski, true) +func (c *EEBus) RemoteServiceConnected(service eebusapi.ServiceInterface, identity shipapi.ServiceIdentity) { + c.connect(identity.SKI, true) } -func (c *EEBus) RemoteSKIDisconnected(service eebusapi.ServiceInterface, ski string) { - c.connect(ski, false) +func (c *EEBus) RemoteServiceDisconnected(service eebusapi.ServiceInterface, identity shipapi.ServiceIdentity) { + c.connect(identity.SKI, false) } // report all currently visible EEBUS services // this is needed to provide an UI for pairing with other devices // if not all incoming pairing requests should be accepted -func (c *EEBus) VisibleRemoteServicesUpdated(service eebusapi.ServiceInterface, entries []shipapi.RemoteService) { +func (c *EEBus) VisibleRemoteMdnsServicesUpdated(service eebusapi.ServiceInterface, entries []shipapi.RemoteMdnsService) { c.mux.Lock() defer c.mux.Unlock() c.remoteServices = slices.Clone(entries) } -// Provides the SHIP ID the remote service reported during the handshake process -// This needs to be persisted and passed on for future remote service connections -// when using `PairRemoteService` -func (c *EEBus) ServiceShipIDUpdate(ski string, shipdID string) {} +// Provides updated service information (ShipID, fingerprint, ...) discovered +// during the handshake process. This needs to be persisted and passed on for +// future remote service connections when using `RegisterRemoteService` +func (c *EEBus) ServiceUpdated(identity shipapi.ServiceIdentity) {} // Provides the current pairing state for the remote service // This is called whenever the state changes and can be used to // provide user information for the pairing/connection process -func (c *EEBus) ServicePairingDetailUpdate(ski string, detail *shipapi.ConnectionStateDetail) { +func (c *EEBus) ServicePairingDetailUpdate(identity shipapi.ServiceIdentity, detail *shipapi.ConnectionStateDetail) { if detail.State() != shipapi.ConnectionStateReceivedPairingRequest { return } @@ -347,12 +352,26 @@ func (c *EEBus) ServicePairingDetailUpdate(ski string, detail *shipapi.Connectio c.mux.Lock() defer c.mux.Unlock() - if clients, ok := c.clients[ski]; !ok || len(clients) == 0 { + if clients, ok := c.clients[identity.SKI]; !ok || len(clients) == 0 { // this is an unknown SKI, so deny pairing - c.service.CancelPairingWithSKI(ski) + c.service.CancelPairing(identity) } } +// SHIP Pairing Service events: evcc trusts remote services by configured SKI via +// RegisterRemoteService and does not use SHIP Pairing; logged for visibility only +func (c *EEBus) ServiceAutoTrusted(service eebusapi.ServiceInterface, identity shipapi.ServiceIdentity) { + c.log.INFO.Printf("service trusted: %s", identity.SKI) +} + +func (c *EEBus) ServiceAutoTrustFailed(service eebusapi.ServiceInterface, identity shipapi.ServiceIdentity, reason error) { + c.log.INFO.Printf("service trust failed: %s: %v", identity.SKI, reason) +} + +func (c *EEBus) ServiceAutoTrustRemoved(service eebusapi.ServiceInterface, identity shipapi.ServiceIdentity, reason string) { + c.log.INFO.Printf("service trust removed: %s: %s", identity.SKI, reason) +} + // EEBUS Logging interface func (c *EEBus) Trace(args ...any) { diff --git a/server/eebus/eebus_test.go b/server/eebus/eebus_test.go index c9d3a3957..25412737c 100644 --- a/server/eebus/eebus_test.go +++ b/server/eebus/eebus_test.go @@ -6,6 +6,7 @@ import ( eebusapi "github.com/enbility/eebus-go/api" eebusmocks "github.com/enbility/eebus-go/mocks" + shipapi "github.com/enbility/ship-go/api" spineapi "github.com/enbility/spine-go/api" "github.com/evcc-io/evcc/util" "github.com/stretchr/testify/require" @@ -40,13 +41,13 @@ var _ Device = (*mockDevice)(nil) // TestUnregisterDevice_MutexNotHeldDuringShipCall is the regression guard // for issue #28942. It asserts that c.mux is NOT held at the point -// UnregisterRemoteSKI is called. The pre-fix code held c.mux across that +// UnregisterRemoteService is called. The pre-fix code held c.mux across that // cross-layer call, and ship-go's synchronous HandleConnectionClosed // callback chain re-entered connect(ski, false) on the same goroutine, // which then deadlocked on c.mux.Lock() (Go mutexes are non-reentrant). // // The assertion uses a goroutine that tries to briefly acquire c.mux from -// inside the mock's UnregisterRemoteSKI implementation; if the lock is +// inside the mock's UnregisterRemoteService implementation; if the lock is // held, the acquisition times out and the test fails. func TestUnregisterDevice_MutexNotHeldDuringShipCall(t *testing.T) { dev := &mockDevice{} @@ -56,7 +57,7 @@ func TestUnregisterDevice_MutexNotHeldDuringShipCall(t *testing.T) { } service := eebusmocks.NewServiceInterface(t) - service.EXPECT().UnregisterRemoteSKI("aabbcc").Run(func(string) { + service.EXPECT().UnregisterRemoteService(shipapi.NewServiceIdentity("aabbcc", "", "")).Run(func(shipapi.ServiceIdentity) { acquired := make(chan struct{}) go func() { c.mux.Lock() diff --git a/server/eebus/test/controlbox.go b/server/eebus/test/controlbox.go index 08df10fc4..e680caad1 100644 --- a/server/eebus/test/controlbox.go +++ b/server/eebus/test/controlbox.go @@ -50,10 +50,10 @@ func createControlbox(ctx context.Context, remoteSki string, port int) (*control configuration, err := api.NewConfiguration( "Demo", "Demo", "ControlBox", "123456789", - // []shipapi.DeviceCategoryType{shipapi.DeviceCategoryTypeGridConnectionHub}, + []shipapi.DeviceCategoryType{shipapi.DeviceCategoryTypeGridConnectionHub}, model.DeviceTypeTypeElectricitySupplySystem, []model.EntityTypeType{model.EntityTypeTypeGridGuard}, - port, certificate, time.Second*60) + port, certificate, time.Second*60, nil, nil) if err != nil { return nil, err } @@ -73,7 +73,7 @@ func createControlbox(ctx context.Context, remoteSki string, port int) (*control h.uclpp = lpp.NewLPP(localEntity, h.OnLPPEvent) h.myService.AddUseCase(h.uclpp) - h.myService.RegisterRemoteSKI(remoteSki) + h.myService.RegisterRemoteService(shipapi.NewServiceIdentity(remoteSki, "", "")) h.myService.Start() go func() { @@ -146,23 +146,32 @@ func (h *controlbox) OnLPPEvent(ski string, device spineapi.DeviceRemoteInterfac // EEBUSServiceHandler -func (h *controlbox) RemoteSKIConnected(service api.ServiceInterface, ski string) { +func (h *controlbox) RemoteServiceConnected(service api.ServiceInterface, identity shipapi.ServiceIdentity) { h.isConnected = true } -func (h *controlbox) RemoteSKIDisconnected(service api.ServiceInterface, ski string) { +func (h *controlbox) RemoteServiceDisconnected(service api.ServiceInterface, identity shipapi.ServiceIdentity) { h.isConnected = false } -func (h *controlbox) VisibleRemoteServicesUpdated(service api.ServiceInterface, entries []shipapi.RemoteService) { +func (h *controlbox) VisibleRemoteMdnsServicesUpdated(service api.ServiceInterface, entries []shipapi.RemoteMdnsService) { } -func (h *controlbox) ServiceShipIDUpdate(ski string, shipdID string) { +func (h *controlbox) ServiceUpdated(identity shipapi.ServiceIdentity) { } -func (h *controlbox) ServicePairingDetailUpdate(ski string, detail *shipapi.ConnectionStateDetail) { +func (h *controlbox) ServicePairingDetailUpdate(identity shipapi.ServiceIdentity, detail *shipapi.ConnectionStateDetail) { } -func (h *controlbox) AllowWaitingForTrust(ski string) bool { +func (h *controlbox) ServiceAutoTrusted(service api.ServiceInterface, identity shipapi.ServiceIdentity) { +} + +func (h *controlbox) ServiceAutoTrustFailed(service api.ServiceInterface, identity shipapi.ServiceIdentity, reason error) { +} + +func (h *controlbox) ServiceAutoTrustRemoved(service api.ServiceInterface, identity shipapi.ServiceIdentity, reason string) { +} + +func (h *controlbox) AllowWaitingForTrust(identity shipapi.ServiceIdentity) bool { return true }