From a23c8cd3c57c07c7406a8bd2cedba64e2ed0e6dd Mon Sep 17 00:00:00 2001 From: achgut <114326418+achgut@users.noreply.github.com> Date: Fri, 10 Mar 2023 10:42:59 +0100 Subject: [PATCH] Add Siemens Versicharge Gen3 (#6613) --- README.md | 2 +- charger/versicharge.go | 238 ++++++++++++++++++ templates/definition/charger/versicharge.yaml | 16 ++ templates/docs/charger/versicharge_0.yaml | 15 ++ templates/evcc.io/brands.json | 1 + 5 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 charger/versicharge.go create mode 100644 templates/definition/charger/versicharge.yaml create mode 100644 templates/docs/charger/versicharge_0.yaml diff --git a/README.md b/README.md index 9659e226d..9b9ba266b 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ evcc is an extensible EV Charge Controller with PV integration implemented in [G - simple and clean user interface - wide range of supported [chargers](https://docs.evcc.io/docs/devices/chargers): - - ABL eMH1, Alfen (Eve), Bender (CC612/613), cFos (PowerBrain), Daheimladen, Ebee (Wallbox), Ensto (Chago Wallbox), [EVSEWifi/ smartWB](https://www.evse-wifi.de), Garo (GLB, GLB+, LS4), go-eCharger, HardyBarth (eCB1, cPH1, cPH2), Heidelberg (Energy Control), Innogy (eBox), Juice (Charger Me), KEBA/BMW, Menneckes (Amedio, Amtron Premium/Xtra, Amtron ChargeConrol), NRGkick, [openWB (includes Pro)](https://openwb.de/), Optec (Mobility One), PC Electric (includes Garo), TechniSat (Technivolt), [Tinkerforge Warp Charger](https://www.warp-charger.com), Ubitricity (Heinz), Vestel, Wallbe, Webasto (Live), Mobile Charger Connect and many more + - ABL eMH1, Alfen (Eve), Bender (CC612/613), cFos (PowerBrain), Daheimladen, Ebee (Wallbox), Ensto (Chago Wallbox), [EVSEWifi/ smartWB](https://www.evse-wifi.de), Garo (GLB, GLB+, LS4), go-eCharger, HardyBarth (eCB1, cPH1, cPH2), Heidelberg (Energy Control), Innogy (eBox), Juice (Charger Me), KEBA/BMW, Menneckes (Amedio, Amtron Premium/Xtra, Amtron ChargeConrol), NRGkick, [openWB (includes Pro)](https://openwb.de/), Optec (Mobility One), PC Electric (includes Garo), Siemens, TechniSat (Technivolt), [Tinkerforge Warp Charger](https://www.warp-charger.com), Ubitricity (Heinz), Vestel, Wallbe, Webasto (Live), Mobile Charger Connect and many more - experimental EEBus support (Elli, PMCC) - experimental OCPP support - Build-your-own: Phoenix Contact (includes ESL Walli), [EVSE DIN](http://evracing.cz/simple-evse-wallbox) diff --git a/charger/versicharge.go b/charger/versicharge.go new file mode 100644 index 000000000..9bb94ae88 --- /dev/null +++ b/charger/versicharge.go @@ -0,0 +1,238 @@ +package charger + +// LICENSE + +// Copyright (c) 2023 premultiply +// Initial implementation and testing by achgut, Flo56958 + +// This module is NOT covered by the MIT license. All rights reserved. + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import ( + "encoding/binary" + "fmt" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/modbus" + "github.com/evcc-io/evcc/util/sponsor" +) + +const ( + versiRegBrand = 0 // 5 RO ASCII + versiRegProductionDate = 5 // 2 RO UNIT16[] + versiRegSerial = 7 // 5 RO ASCII + versiRegModel = 12 // 10 RO ASCII + versiRegFirmware = 31 // 10 RO ASCII + versiRegModbusTable = 41 // 1 RO UINT16 + versiRegMeterType = 30 // 1 RO UINT16 + versiRegErrorCode = 1600 // 1 RO INT16 + versiRegTemp = 1602 // 1 RO INT16 + versiRegChargeStatus = 1601 // 1 RO INT16 + versiRegPause = 1629 // 1 RW UNIT16 + versiRegMaxCurrent = 1633 // 1 RW UNIT16 + versiRegCurrents = 1647 // 3 RO UINT16 + versiRegVoltages = 1651 // 3 RO UINT16 + versiRegPowers = 1662 // 3 RO UINT16 + versiRegTotalEnergy = 1692 // 2 RO UINT32 +) + +// Versicharge is an api.Charger implementation for Versicharge wallboxes with Ethernet (SW modells). +// It uses Modbus TCP to communicate with the wallbox at id 2 (default). + +type Versicharge struct { + conn *modbus.Connection +} + +func init() { + registry.Add("versicharge", NewVersichargeFromConfig) +} + +// NewVersichargeFromConfig creates a Versicharge charger from generic config +func NewVersichargeFromConfig(other map[string]interface{}) (api.Charger, error) { + cc := modbus.TcpSettings{ + ID: 2, + } + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + return NewVersicharge(cc.URI, cc.ID) +} + +// NewVersicharge creates a Versicharge charger +func NewVersicharge(uri string, id uint8) (*Versicharge, error) { + conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, id) + if err != nil { + return nil, err + } + + if !sponsor.IsAuthorized() { + return nil, api.ErrSponsorRequired + } + + log := util.NewLogger("versicharge") + conn.Logger(log.TRACE) + + wb := &Versicharge{ + conn: conn, + } + + return wb, nil +} + +// Status implements the api.Charger interface +func (wb *Versicharge) Status() (api.ChargeStatus, error) { + b, err := wb.conn.ReadHoldingRegisters(versiRegChargeStatus, 1) + if err != nil { + return api.StatusNone, err + } + + s := binary.BigEndian.Uint16(b) + + switch s { + case 1: // Available + return api.StatusA, nil + case 2, 4, 5: // Preparing, Suspended EV, Suspended EVSE + return api.StatusB, nil + case 3: // Charging + return api.StatusC, nil + default: + return api.StatusNone, fmt.Errorf("invalid status: %d", s) + } +} + +// Enabled implements the api.Charger interface +func (wb *Versicharge) Enabled() (bool, error) { + b, err := wb.conn.ReadHoldingRegisters(versiRegPause, 1) + if err != nil { + return false, err + } + + return binary.BigEndian.Uint16(b) == 2, nil +} + +// Enable implements the api.Charger interface +func (wb *Versicharge) Enable(enable bool) error { + var u uint16 = 1 + if enable { + u = 2 + } + + _, err := wb.conn.WriteSingleRegister(versiRegPause, u) + + return err +} + +// MaxCurrent implements the api.Charger interface +func (wb *Versicharge) MaxCurrent(current int64) error { + if current < 6 { + return fmt.Errorf("invalid current %d", current) + } + + _, err := wb.conn.WriteSingleRegister(versiRegMaxCurrent, uint16(current)) + + return err +} + +var _ api.Meter = (*Versicharge)(nil) + +// CurrentPower implements the api.Meter interface +func (wb *Versicharge) CurrentPower() (float64, error) { + b, err := wb.conn.ReadHoldingRegisters(versiRegPowers, 3) + if err != nil { + return 0, err + } + + var sum float64 + for i := 0; i < 3; i++ { + sum += float64(binary.BigEndian.Uint16(b[2*i:])) + } + + return sum, nil +} + +var _ api.MeterEnergy = (*Versicharge)(nil) + +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Versicharge) TotalEnergy() (float64, error) { + b, err := wb.conn.ReadHoldingRegisters(versiRegTotalEnergy, 2) + if err != nil { + return 0, err + } + + return float64(binary.BigEndian.Uint32(b)) / 1e4, err +} + +// getPhaseValues returns 3 sequential register values +func (wb *Versicharge) getPhaseValues(reg uint16) (float64, float64, float64, error) { + b, err := wb.conn.ReadHoldingRegisters(reg, 3) + if err != nil { + return 0, 0, 0, err + } + + var res [3]float64 + for i := 0; i < 3; i++ { + res[i] = float64(binary.BigEndian.Uint16(b[2*i:])) + } + + return res[0], res[1], res[2], nil +} + +var _ api.PhaseCurrents = (*Versicharge)(nil) + +// Currents implements the api.PhaseCurrents interface +func (wb *Versicharge) Currents() (float64, float64, float64, error) { + return wb.getPhaseValues(versiRegCurrents) +} + +var _ api.PhaseVoltages = (*Versicharge)(nil) + +// Voltages implements the api.PhaseVoltages interface +func (wb *Versicharge) Voltages() (float64, float64, float64, error) { + return wb.getPhaseValues(versiRegVoltages) +} + +var _ api.Diagnosis = (*Versicharge)(nil) + +// Diagnose implements the api.Diagnosis interface +func (wb *Versicharge) Diagnose() { + if b, err := wb.conn.ReadHoldingRegisters(versiRegBrand, 5); err == nil { + fmt.Printf("Brand:\t\t\t%s\n", b) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegModel, 10); err == nil { + fmt.Printf("Model:\t\t\t%s\n", b) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegSerial, 5); err == nil { + fmt.Printf("Serial:\t\t\t%s\n", b) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegProductionDate, 2); err == nil { + fmt.Printf("Production Date:\t%d.%d.%d\n", b[3], b[2], binary.BigEndian.Uint16(b[0:2])) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegFirmware, 10); err == nil { + fmt.Printf("Firmware:\t\t%s\n", b) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegModbusTable, 1); err == nil { + fmt.Printf("Modbus Table:\t\t%d\n", b[1]) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegMeterType, 1); err == nil { + fmt.Printf("Meter Type:\t\t%d\n", b[1]) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegTemp, 1); err == nil { + fmt.Printf("Temperature PCB:\t%d°C\n\n", b[1]) + } + if b, err := wb.conn.ReadHoldingRegisters(versiRegErrorCode, 1); err == nil { + fmt.Printf("EVSE Error Code:\t%d\n\n", b[1]) + } +} diff --git a/templates/definition/charger/versicharge.yaml b/templates/definition/charger/versicharge.yaml new file mode 100644 index 000000000..b0afac592 --- /dev/null +++ b/templates/definition/charger/versicharge.yaml @@ -0,0 +1,16 @@ +template: versicharge +products: + - brand: Siemens + description: + generic: Versicharge GEN3 +requirements: + description: + de: Erfordert Firmware >= 2.121.5 + en: Requires firmware >= 2.121.5 +params: + - name: modbus + choice: ["tcpip"] + id: 2 +render: | + type: versicharge + {{- include "modbus" . }} diff --git a/templates/docs/charger/versicharge_0.yaml b/templates/docs/charger/versicharge_0.yaml new file mode 100644 index 000000000..f860021de --- /dev/null +++ b/templates/docs/charger/versicharge_0.yaml @@ -0,0 +1,15 @@ +product: + brand: Siemens + description: Versicharge GEN3 +description: | + Erfordert Firmware >= 2.121.5 +render: + - default: | + type: template + template: versicharge + + # Modbus TCP + modbus: tcpip + id: 2 + host: 192.0.2.2 # Hostname + port: 502 # Port diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json index 3740a908b..2d3405a55 100644 --- a/templates/evcc.io/brands.json +++ b/templates/evcc.io/brands.json @@ -37,6 +37,7 @@ "Pracht", "Schrack", "SENEC", + "Siemens", "SMA", "Sonnen", "Stark in Strom",