From 5bb4b65e8ab463d3ec143f1433a03515ed5a93f0 Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Thu, 2 Jan 2025 18:47:59 +0000 Subject: [PATCH 1/5] wip --- "charger/weidm\303\274ller.go" | 82 +++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go" index efebcd1171..7094e27f79 100644 --- "a/charger/weidm\303\274ller.go" +++ "b/charger/weidm\303\274ller.go" @@ -2,7 +2,7 @@ package charger // LICENSE -// Copyright (c) 2023 premultiply +// Copyright (c) 2023-2025 premultiply // This module is NOT covered by the MIT license. All rights reserved. @@ -18,8 +18,10 @@ package charger // SOFTWARE. import ( + "context" "encoding/binary" "fmt" + "time" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" @@ -30,29 +32,34 @@ import ( // Weidmüller charger implementation type Weidmüller struct { - log *util.Logger - conn *modbus.Connection - curr uint16 + log *util.Logger + conn *modbus.Connection + curr uint16 + hasMeter bool } const ( - wmRegCarStatus = 301 // GD_ID_EVCC_CAR_STATE CHAR - wmRegEvccStatus = 302 // GD_ID_EVCC_EVSE_STATE UINT16 - wmRegPhases = 318 // GD_ID_EVCC_PHASES_LLM UINT16 - wmRegVoltages = 400 // GD_ID_CM_VOLTAGE_PHASE UINT32 - wmRegCurrents = 406 // GD_ID_CM_CURRENT_PHASE UINT32 - wmRegActivePower = 418 // GD_ID_CM_ACTIVE_POWER UINT32 - wmRegTotalEnergy = 457 // GD_ID_CM_CONSUMED_ENERGY_TOTAL_WH UINT64 - wmRegCurrentLimit = 702 // GD_ID_AUT_USER_CURRENT_LIMIT UINT16 - wmRegCardId = 1000 // GD_ID_RFID_TAG_UID CHAR[21] + wmRegCarStatus = 301 // GD_ID_EVCC_CAR_STATE CHAR + wmRegEvccStatus = 302 // GD_ID_EVCC_EVSE_STATE UINT16 + wmRegPhases = 318 // GD_ID_EVCC_PHASES_LLM UINT16 + wmRegVoltages = 400 // GD_ID_CM_VOLTAGE_PHASE UINT32 + wmRegCurrents = 406 // GD_ID_CM_CURRENT_PHASE UINT32 + wmRegActivePower = 418 // GD_ID_CM_ACTIVE_POWER UINT32 + wmRegTotalEnergy = 457 // GD_ID_CM_CONSUMED_ENERGY_TOTAL_WH UINT64 + wmRegCardId = 1000 // GD_ID_RFID_TAG_UID CHAR[21] + wmRegTimeout = 11050 // GD_ID_LCM_TIMEOUT UINT32 + wmRegCurrentLimit = 11052 // GD_ID_LCM_ACTUAL_CURRENT_LIMIT UINT16 (A) + + wmHeartbeatInterval = 5 * time.Second + wmTimeout = 65535 // ms ) func init() { - registry.Add("weidmüller", NewWeidmüllerFromConfig) + registry.AddCtx("weidmüller", NewWeidmüllerFromConfig) } // NewWeidmüllerFromConfig creates a Weidmüller charger from generic config -func NewWeidmüllerFromConfig(other map[string]interface{}) (api.Charger, error) { +func NewWeidmüllerFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error) { cc := modbus.TcpSettings{ ID: 255, } @@ -61,11 +68,27 @@ func NewWeidmüllerFromConfig(other map[string]interface{}) (api.Charger, error) return nil, err } - return NewWeidmüller(cc.URI, cc.ID) + //return NewWeidmüller(ctx, cc.URI, cc.ID) + + wb, err := NewWeidmüller(ctx, cc.URI, cc.ID) + if err != nil { + return nil, err + } + + var totalEnergy func() (float64, error) + + // check presence of energy meter + if b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2); err == nil && binary.BigEndian.Uint32(b) > 0 { + totalEnergy = wb.totalEnergy + } + + return decorateWeidmüller(wb, totalEnergy), nil } +//go:generate decorate -f decorateWeidmüller -b *Weidmüller -r api.Charger -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" + // NewWeidmüller creates Weidmüller charger -func NewWeidmüller(uri string, id uint8) (api.Charger, error) { +func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, error) { conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, id) if err != nil { return nil, err @@ -93,9 +116,30 @@ func NewWeidmüller(uri string, id uint8) (api.Charger, error) { wb.curr = curr } + // failsafe + go wb.heartbeat(ctx, wmHeartbeatInterval) + return wb, err } +func (wb *Weidmüller) heartbeat(ctx context.Context, timeout time.Duration) { + b := make([]byte, 4) + + binary.BigEndian.PutUint32(b, wmTimeout) + + for tick := time.Tick(timeout); ; { + select { + case <-tick: + case <-ctx.Done(): + return + } + + if _, err := wb.conn.WriteMultipleRegisters(wmRegTimeout, 2, b); err != nil { + wb.log.ERROR.Println("heartbeat:", err) + } + } +} + func (wb *Weidmüller) setCurrent(current uint16) error { b := make([]byte, 2) binary.BigEndian.PutUint16(b, current) @@ -184,10 +228,8 @@ func (wb *Weidmüller) CurrentPower() (float64, error) { return float64(encoding.Uint32LswFirst(b)) / 1e3, err } -var _ api.MeterEnergy = (*Weidmüller)(nil) - // TotalEnergy implements the api.MeterEnergy interface -func (wb *Weidmüller) TotalEnergy() (float64, error) { +func (wb *Weidmüller) totalEnergy() (float64, error) { b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2) if err != nil { return 0, err From 11d752e1d4a11ce9d62a842a1f2915b9a1953a36 Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Thu, 2 Jan 2025 19:48:23 +0000 Subject: [PATCH 2/5] wip --- "charger/weidm\303\274ller.go" | 2 -- "charger/weidm\303\274ller_decorators.go" | 35 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 "charger/weidm\303\274ller_decorators.go" diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go" index 7094e27f79..26519b9e97 100644 --- "a/charger/weidm\303\274ller.go" +++ "b/charger/weidm\303\274ller.go" @@ -68,8 +68,6 @@ func NewWeidmüllerFromConfig(ctx context.Context, other map[string]interface{}) return nil, err } - //return NewWeidmüller(ctx, cc.URI, cc.ID) - wb, err := NewWeidmüller(ctx, cc.URI, cc.ID) if err != nil { return nil, err diff --git "a/charger/weidm\303\274ller_decorators.go" "b/charger/weidm\303\274ller_decorators.go" new file mode 100644 index 0000000000..1df7545327 --- /dev/null +++ "b/charger/weidm\303\274ller_decorators.go" @@ -0,0 +1,35 @@ +package charger + +// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT. + +import ( + "github.com/evcc-io/evcc/api" +) + +func decorateWeidmüller(base *Weidmüller, meterEnergy func() (float64, error)) api.Charger { + switch { + case meterEnergy == nil: + return base + + case meterEnergy != nil: + return &struct { + *Weidmüller + api.MeterEnergy + }{ + Weidmüller: base, + MeterEnergy: &decorateWeidmüllerMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + } + + return nil +} + +type decorateWeidmüllerMeterEnergyImpl struct { + meterEnergy func() (float64, error) +} + +func (impl *decorateWeidmüllerMeterEnergyImpl) TotalEnergy() (float64, error) { + return impl.meterEnergy() +} From 48d2647c8674ec3a942ee4817d0990365046f13d Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:19:39 +0100 Subject: [PATCH 3/5] wip --- "charger/weidm\303\274ller.go" | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go" index 26519b9e97..03bcb8a797 100644 --- "a/charger/weidm\303\274ller.go" +++ "b/charger/weidm\303\274ller.go" @@ -68,19 +68,7 @@ func NewWeidmüllerFromConfig(ctx context.Context, other map[string]interface{}) return nil, err } - wb, err := NewWeidmüller(ctx, cc.URI, cc.ID) - if err != nil { - return nil, err - } - - var totalEnergy func() (float64, error) - - // check presence of energy meter - if b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2); err == nil && binary.BigEndian.Uint32(b) > 0 { - totalEnergy = wb.totalEnergy - } - - return decorateWeidmüller(wb, totalEnergy), nil + return NewWeidmüller(ctx, cc.URI, cc.ID) } //go:generate decorate -f decorateWeidmüller -b *Weidmüller -r api.Charger -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" @@ -117,6 +105,11 @@ func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, err // failsafe go wb.heartbeat(ctx, wmHeartbeatInterval) + // check presence of energy meter + if b, err := wb.conn.ReadHoldingRegisters(wmRegTotalEnergy, 2); err == nil && binary.BigEndian.Uint32(b) > 0 { + return decorateWeidmüller(wb, wb.totalEnergy), nil + } + return wb, err } From 7d1653d073d8f20087bc96a8c31b93e344e27231 Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Thu, 2 Jan 2025 21:23:13 +0100 Subject: [PATCH 4/5] wip --- "charger/weidm\303\274ller.go" | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go" index 03bcb8a797..55e8647896 100644 --- "a/charger/weidm\303\274ller.go" +++ "b/charger/weidm\303\274ller.go" @@ -32,10 +32,9 @@ import ( // Weidmüller charger implementation type Weidmüller struct { - log *util.Logger - conn *modbus.Connection - curr uint16 - hasMeter bool + log *util.Logger + conn *modbus.Connection + curr uint16 } const ( @@ -110,7 +109,7 @@ func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, err return decorateWeidmüller(wb, wb.totalEnergy), nil } - return wb, err + return wb, nil } func (wb *Weidmüller) heartbeat(ctx context.Context, timeout time.Duration) { From 1ca38df8639ed8e2c39a2e0f1510bece37395fdb Mon Sep 17 00:00:00 2001 From: andig Date: Fri, 3 Jan 2025 09:41:30 +0100 Subject: [PATCH 5/5] =?UTF-8?q?Update=20charger/weidm=C3=BCller.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- "charger/weidm\303\274ller.go" | 1 - 1 file changed, 1 deletion(-) diff --git "a/charger/weidm\303\274ller.go" "b/charger/weidm\303\274ller.go" index 55e8647896..9afbae9195 100644 --- "a/charger/weidm\303\274ller.go" +++ "b/charger/weidm\303\274ller.go" @@ -114,7 +114,6 @@ func NewWeidmüller(ctx context.Context, uri string, id uint8) (api.Charger, err func (wb *Weidmüller) heartbeat(ctx context.Context, timeout time.Duration) { b := make([]byte, 4) - binary.BigEndian.PutUint32(b, wmTimeout) for tick := time.Tick(timeout); ; {