From 7694dbcbaf7f0db1b8bb525c7fc1a2da3ec5b5b8 Mon Sep 17 00:00:00 2001 From: Dominik Niakamal Date: Tue, 28 Mar 2023 18:44:03 +0200 Subject: [PATCH 1/5] Changed brand 'D-Parts' to 'EM2GO', Updated template docs and definition for em2go, Updated em2go.go: completed ModBus registers, added Status F, Voltages, Powers, extended diagnosis. --- charger/em2go.go | 130 +++++++++++++++++++----- templates/definition/charger/em2go.yaml | 11 +- templates/docs/charger/em2go_0.yaml | 9 +- templates/evcc.io/brands.json | 2 +- 4 files changed, 118 insertions(+), 34 deletions(-) diff --git a/charger/em2go.go b/charger/em2go.go index dda23f5dc0..e14c44f1cf 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -37,23 +37,32 @@ type Em2Go struct { } const ( - em2GoRegStatus = 0 - em2GoRegCurrents = 6 - em2GoRegPower = 12 - em2GoRegEnergy = 28 - em2GoRegCurrent = 32 - em2GoRegSerial = 38 - em2GoRegChargedEnergy = 72 - em2GoRegChargeDuration = 78 - em2GoRegChargeMode = 93 - em2GoRegChargeCommand = 95 + em2GoRegStatus = 0 // Uint16 RO ENUM + em2goRegConnectorState = 2 // Uint16 RO ENUM + em2goRegErrorCode = 4 // Uint16 RO ENUM + em2GoRegCurrents = 6 // 3xUint32 RO 0.1A + em2GoRegPower = 12 // Uint32 RO 1W + em2GoRegPowers = 16 // 3xUint32 RO 1W + em2GoRegEnergy = 28 // Uint32 RO 0.1KWh + em2GoRegMaxCurrent = 32 // Uint16 RO 0.1A + em2GoRegMinCurrent = 34 // Uint16 RO 0.1A + em2goRegCableMaxCurrent = 36 // Uint16 RO 0.1A + em2GoRegSerial = 38 // Chr[16] RO UTF16 + em2GoRegChargedEnergy = 72 // Uint16 RO 0.1kWh + em2GoRegChargeDuration = 78 // Uint32 RO 1s + em2goRegSafeCurrent = 87 // Uint16 WR 0.1A + em2goRegCommTimeout = 89 // Uint16 WR 1s + em2goRegCurrentLimit = 91 // Uint16 WR 0.1A + em2GoRegChargeMode = 93 // Uint16 WR ENUM + em2GoRegChargeCommand = 95 // Uint16 WR ENUM + em2goRegVoltages = 109 // 3xUint32 RO 0.1V ) func init() { registry.Add("em2go", NewEm2GoFromConfig) } -// NewEm2GoFromConfig creates a Mennekes Em2Go charger from generic config +// NewEm2GoFromConfig creates a Em2Go charger from generic config func NewEm2GoFromConfig(other map[string]interface{}) (api.Charger, error) { cc := modbus.TcpSettings{ ID: 0xff, @@ -105,8 +114,10 @@ func (wb *Em2Go) Status() (api.ChargeStatus, error) { return api.StatusA, nil case 2, 3: return api.StatusB, nil - case 4: + case 4, 6: return api.StatusC, nil + case 5, 7: + return api.StatusF, nil default: return api.StatusNone, fmt.Errorf("invalid status: %0x", b[1]) } @@ -145,7 +156,7 @@ func (wb *Em2Go) MaxCurrentMillis(current float64) error { b := make([]byte, 2) binary.BigEndian.PutUint16(b, uint16(10*current)) - _, err := wb.conn.WriteMultipleRegisters(em2GoRegCurrent, 1, b) + _, err := wb.conn.WriteMultipleRegisters(em2goRegCurrentLimit, 1, b) return err } @@ -175,16 +186,44 @@ func (wb *Em2Go) TotalEnergy() (float64, error) { var _ api.PhaseCurrents = (*Em2Go)(nil) -// Currents implements the api.MeterCurrent interface +// Currents implements the api.PhaseCurrents interface func (wb *Em2Go) Currents() (float64, float64, float64, error) { - b, err := wb.conn.ReadHoldingRegisters(em2GoRegCurrents, 3) + b, err := wb.conn.ReadHoldingRegisters(em2GoRegCurrents, 6) if err != nil { return 0, 0, 0, err } return float64(binary.BigEndian.Uint16(b)) / 10, - float64(binary.BigEndian.Uint16(b[2:])) / 10, - float64(binary.BigEndian.Uint16(b[4:])) / 10, nil + float64(binary.BigEndian.Uint16(b[4:])) / 10, + float64(binary.BigEndian.Uint16(b[8:])) / 10, nil +} + +var _ api.PhaseVoltages = (*Em2Go)(nil) + +// Currents implements the api.PhaseVoltages interface +func (wb *Em2Go) Voltages() (float64, float64, float64, error) { + b, err := wb.conn.ReadHoldingRegisters(em2goRegVoltages, 6) + if err != nil { + return 0, 0, 0, err + } + + return float64(binary.BigEndian.Uint16(b)) / 10, + float64(binary.BigEndian.Uint16(b[4:])) / 10, + float64(binary.BigEndian.Uint16(b[8:])) / 10, nil +} + +var _ api.PhasePowers = (*Em2Go)(nil) + +// Currents implements the api.PhasePowers interface +func (wb *Em2Go) Powers() (float64, float64, float64, error) { + b, err := wb.conn.ReadHoldingRegisters(em2GoRegPowers, 6) + if err != nil { + return 0, 0, 0, err + } + + return float64(binary.BigEndian.Uint16(b)) / 10, + float64(binary.BigEndian.Uint16(b[8:])) / 10, + float64(binary.BigEndian.Uint16(b[8:])) / 10, nil } var _ api.ChargeRater = (*Em2Go)(nil) @@ -211,18 +250,57 @@ func (wb *Em2Go) ChargingTime() (time.Duration, error) { return time.Duration(binary.BigEndian.Uint32(b)) * time.Second, nil } +// utf16BytesToString converts UTF-16 encoded bytes, in big or little endian byte order, +// to a UTF-8 encoded string. +func utf16BytesToString(b []byte, o binary.ByteOrder) string { + utf := make([]uint16, (len(b)+(2-1))/2) + for i := 0; i+(2-1) < len(b); i += 2 { + utf[i/2] = o.Uint16(b[i:]) + } + if len(b)/2 < len(utf) { + utf[len(utf)-1] = utf8.RuneError + } + return string(utf16.Decode(utf)) +} + var _ api.Diagnosis = (*Em2Go)(nil) // Diagnose implements the api.Diagnosis interface func (wb *Em2Go) Diagnose() { - var serial []byte - for reg := 0; reg < 8; reg++ { - b, err := wb.conn.ReadHoldingRegisters(em2GoRegSerial+2*uint16(reg), 2) - if err != nil { - return - } - serial = append(serial, b...) + if b, err := wb.conn.ReadHoldingRegisters(em2GoRegStatus, 1); err == nil { + fmt.Printf("\tCharging Station Status:\t%d\n", binary.BigEndian.Uint16(b)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2goRegConnectorState, 1); err == nil { + fmt.Printf("\tConnector State:\t%d\n", binary.BigEndian.Uint16(b)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2goRegErrorCode, 1); err == nil { + fmt.Printf("\tError Code:\t%d\n", binary.BigEndian.Uint16(b)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2GoRegMaxCurrent, 1); err == nil { + fmt.Printf("\tEVSE Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2GoRegMaxCurrent, 1); err == nil { + fmt.Printf("\tEVSE Min. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2goRegCableMaxCurrent, 1); err == nil { + fmt.Printf("\tCable Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2GoRegSerial, 16); err == nil { + fmt.Printf("\tSerial:\t%s\n", utf16BytesToString(b, binary.BigEndian)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2goRegSafeCurrent, 1); err == nil { + fmt.Printf("\tSafe Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2goRegCommTimeout, 1); err == nil { + fmt.Printf("\tConnection Timeout:\t%d\n", binary.BigEndian.Uint16(b)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2goRegCurrentLimit, 1); err == nil { + fmt.Printf("\tCurrent Limit:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2GoRegChargeMode, 1); err == nil { + fmt.Printf("\tCharge Mode:\t%d\n", binary.BigEndian.Uint16(b)) + } + if b, err := wb.conn.ReadHoldingRegisters(em2GoRegChargeCommand, 1); err == nil { + fmt.Printf("\tCharge Command:\t%d\n", binary.BigEndian.Uint16(b)) } - - fmt.Printf("Serial: %s\n", string(serial)) } diff --git a/templates/definition/charger/em2go.yaml b/templates/definition/charger/em2go.yaml index 934cdf1243..ac1d69a74b 100644 --- a/templates/definition/charger/em2go.yaml +++ b/templates/definition/charger/em2go.yaml @@ -1,12 +1,17 @@ template: em2go products: - - brand: D-Parts + - brand: EM2GO description: - generic: Em2Go + generic: Pro Power/OCPP/ONC +capabilities: ["mA", "rfid"] +requirements: + description: + de: Für die Nutzung mit evcc muss die Wallbox mit einer aktuellen Firmware mit Modbus-Unterstützung betrieben werden. Bitte mit dem Hersteller abklären. + en: For use with evcc, the wallbox must be operated with a recent firmware including Modbus support. Please contact vendor. params: - name: modbus choice: ["tcpip"] id: 255 render: | type: em2go - {{- include "modbus" . }} + {{- include "modbus" . }} \ No newline at end of file diff --git a/templates/docs/charger/em2go_0.yaml b/templates/docs/charger/em2go_0.yaml index 40a7a1c2a3..9341e99e20 100644 --- a/templates/docs/charger/em2go_0.yaml +++ b/templates/docs/charger/em2go_0.yaml @@ -1,13 +1,14 @@ product: - brand: D-Parts - description: Em2Go + brand: EM2GO + description: Pro Power/OCPP/ONC +capabilities: ["mA", "rfid"] render: - default: | type: template - template: em2go + template: em2go # Modbus TCP modbus: tcpip id: 255 host: 192.0.2.2 # Hostname - port: 502 # Port + port: 502 # Port \ No newline at end of file diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json index 7beb5aa8f6..50e3b2439d 100644 --- a/templates/evcc.io/brands.json +++ b/templates/evcc.io/brands.json @@ -7,7 +7,6 @@ "Bender", "BMW", "cFos", - "D-Parts", "Dadapower", "DaheimLaden", "E.ON Drive", @@ -15,6 +14,7 @@ "Ebee", "echarge", "Elli", + "EM2GO", "Ensto", "Etrel", "EVBox", From 3a7565b02a8bef8fbc5bf1dd270f5cd88e2aeefc Mon Sep 17 00:00:00 2001 From: Dominik Niakamal Date: Tue, 28 Mar 2023 23:33:27 +0200 Subject: [PATCH 2/5] Fix test --- charger/em2go.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/charger/em2go.go b/charger/em2go.go index e14c44f1cf..b3ca6d246d 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -21,6 +21,8 @@ import ( "encoding/binary" "fmt" "time" + "unicode/utf16" + "unicode/utf8" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" @@ -286,7 +288,15 @@ func (wb *Em2Go) Diagnose() { fmt.Printf("\tCable Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) } if b, err := wb.conn.ReadHoldingRegisters(em2GoRegSerial, 16); err == nil { - fmt.Printf("\tSerial:\t%s\n", utf16BytesToString(b, binary.BigEndian)) + var serial []byte + for reg := 0; reg < 8; reg++ { + b, err := wb.conn.ReadHoldingRegisters(em2GoRegSerial+2*uint16(reg), 2) + if err != nil { + return + } + serial = append(serial, b...) + } + fmt.Printf("\tSerial:\t%s\n", string(serial)) } if b, err := wb.conn.ReadHoldingRegisters(em2goRegSafeCurrent, 1); err == nil { fmt.Printf("\tSafe Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) From ab48a67d4bf1a10389e2a14bf297558c8242c51b Mon Sep 17 00:00:00 2001 From: Dominik Niakamal Date: Tue, 28 Mar 2023 23:47:01 +0200 Subject: [PATCH 3/5] Fix test --- charger/em2go.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/charger/em2go.go b/charger/em2go.go index b3ca6d246d..38d9b29222 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -252,19 +252,6 @@ func (wb *Em2Go) ChargingTime() (time.Duration, error) { return time.Duration(binary.BigEndian.Uint32(b)) * time.Second, nil } -// utf16BytesToString converts UTF-16 encoded bytes, in big or little endian byte order, -// to a UTF-8 encoded string. -func utf16BytesToString(b []byte, o binary.ByteOrder) string { - utf := make([]uint16, (len(b)+(2-1))/2) - for i := 0; i+(2-1) < len(b); i += 2 { - utf[i/2] = o.Uint16(b[i:]) - } - if len(b)/2 < len(utf) { - utf[len(utf)-1] = utf8.RuneError - } - return string(utf16.Decode(utf)) -} - var _ api.Diagnosis = (*Em2Go)(nil) // Diagnose implements the api.Diagnosis interface From f77fcecb62e47f74e3176555c70c6c278bdbe001 Mon Sep 17 00:00:00 2001 From: Dominik Niakamal Date: Wed, 29 Mar 2023 00:38:57 +0200 Subject: [PATCH 4/5] =?UTF-8?q?Removed=20PhasePowers=20as=20it=C2=B4s=20no?= =?UTF-8?q?t=20being=20unused=20by=20chargers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- charger/em2go.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/charger/em2go.go b/charger/em2go.go index 38d9b29222..721d5c85f8 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -214,20 +214,6 @@ func (wb *Em2Go) Voltages() (float64, float64, float64, error) { float64(binary.BigEndian.Uint16(b[8:])) / 10, nil } -var _ api.PhasePowers = (*Em2Go)(nil) - -// Currents implements the api.PhasePowers interface -func (wb *Em2Go) Powers() (float64, float64, float64, error) { - b, err := wb.conn.ReadHoldingRegisters(em2GoRegPowers, 6) - if err != nil { - return 0, 0, 0, err - } - - return float64(binary.BigEndian.Uint16(b)) / 10, - float64(binary.BigEndian.Uint16(b[8:])) / 10, - float64(binary.BigEndian.Uint16(b[8:])) / 10, nil -} - var _ api.ChargeRater = (*Em2Go)(nil) // ChargedEnergy implements the api.ChargeRater interface From 99599683b7e7d017e0907edc7f4ca11b1b283aa8 Mon Sep 17 00:00:00 2001 From: Dominik Niakamal Date: Wed, 29 Mar 2023 12:18:22 +0200 Subject: [PATCH 5/5] Fix build, fix CurrentPower --- charger/em2go.go | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/charger/em2go.go b/charger/em2go.go index 721d5c85f8..ead227f12c 100644 --- a/charger/em2go.go +++ b/charger/em2go.go @@ -21,8 +21,6 @@ import ( "encoding/binary" "fmt" "time" - "unicode/utf16" - "unicode/utf8" "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/util" @@ -42,10 +40,10 @@ const ( em2GoRegStatus = 0 // Uint16 RO ENUM em2goRegConnectorState = 2 // Uint16 RO ENUM em2goRegErrorCode = 4 // Uint16 RO ENUM - em2GoRegCurrents = 6 // 3xUint32 RO 0.1A + em2GoRegCurrents = 6 // 3xUint16 RO 0.1A em2GoRegPower = 12 // Uint32 RO 1W - em2GoRegPowers = 16 // 3xUint32 RO 1W - em2GoRegEnergy = 28 // Uint32 RO 0.1KWh + em2GoRegPowers = 16 // 3xUint16 RO 1W + em2GoRegEnergy = 28 // Uint16 RO 0.1KWh em2GoRegMaxCurrent = 32 // Uint16 RO 0.1A em2GoRegMinCurrent = 34 // Uint16 RO 0.1A em2goRegCableMaxCurrent = 36 // Uint16 RO 0.1A @@ -57,7 +55,7 @@ const ( em2goRegCurrentLimit = 91 // Uint16 WR 0.1A em2GoRegChargeMode = 93 // Uint16 WR ENUM em2GoRegChargeCommand = 95 // Uint16 WR ENUM - em2goRegVoltages = 109 // 3xUint32 RO 0.1V + em2goRegVoltages = 109 // 3xUint16 RO 0.1V ) func init() { @@ -183,7 +181,7 @@ func (wb *Em2Go) TotalEnergy() (float64, error) { return 0, err } - return rs485.RTUUint32ToFloat64(b) / 10, nil + return float64(binary.BigEndian.Uint16(b)) / 10, nil } var _ api.PhaseCurrents = (*Em2Go)(nil) @@ -223,7 +221,7 @@ func (wb *Em2Go) ChargedEnergy() (float64, error) { return 0, err } - return rs485.RTUUint32ToFloat64(b) / 10, nil + return float64(binary.BigEndian.Uint16(b)) / 10, nil } var _ api.ChargeTimer = (*Em2Go)(nil) @@ -260,17 +258,15 @@ func (wb *Em2Go) Diagnose() { if b, err := wb.conn.ReadHoldingRegisters(em2goRegCableMaxCurrent, 1); err == nil { fmt.Printf("\tCable Max. Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) } - if b, err := wb.conn.ReadHoldingRegisters(em2GoRegSerial, 16); err == nil { - var serial []byte - for reg := 0; reg < 8; reg++ { - b, err := wb.conn.ReadHoldingRegisters(em2GoRegSerial+2*uint16(reg), 2) - if err != nil { - return - } - serial = append(serial, b...) + var serial []byte + for reg := 0; reg < 8; reg++ { + b, err := wb.conn.ReadHoldingRegisters(em2GoRegSerial+2*uint16(reg), 2) + if err != nil { + return } - fmt.Printf("\tSerial:\t%s\n", string(serial)) + serial = append(serial, b...) } + fmt.Printf("\tSerial: %s\n", string(serial)) if b, err := wb.conn.ReadHoldingRegisters(em2goRegSafeCurrent, 1); err == nil { fmt.Printf("\tSafe Current:\t%.1fA\n", float64(binary.BigEndian.Uint16(b)/10)) }