From 64caa9b816a17815e65e36c5d2ee128d3109b226 Mon Sep 17 00:00:00 2001 From: Artem Makhutov Date: Sun, 8 May 2022 12:39:42 +0200 Subject: [PATCH] Add Dadapower charging station (#3141) --- charger/dadapower.go | 242 ++++++++++++++++++++ templates/definition/charger/dadapower.yaml | 13 ++ templates/docs/charger/dadapower_0.yaml | 14 ++ templates/evcc.io/brands.json | 1 + 4 files changed, 270 insertions(+) create mode 100644 charger/dadapower.go create mode 100644 templates/definition/charger/dadapower.yaml create mode 100644 templates/docs/charger/dadapower_0.yaml diff --git a/charger/dadapower.go b/charger/dadapower.go new file mode 100644 index 000000000..4a267200a --- /dev/null +++ b/charger/dadapower.go @@ -0,0 +1,242 @@ +package charger + +import ( + "encoding/binary" + "errors" + "fmt" + "time" + + "github.com/evcc-io/evcc/api" + "github.com/evcc-io/evcc/util" + "github.com/evcc-io/evcc/util/modbus" +) + +const ( + dadapowerRegFailsafeTimeout = 102 + dadapowerRegModel = 105 + dadapowerRegSerial = 106 // 6 + dadapowerRegFirmware = 112 // 6 + dadapowerRegChargingAllowed = 1000 + dadapowerRegChargeCurrentLimit = 1001 + dadapowerRegActivePhases = 1002 + dadapowerRegActiveEnergy = 1009 + dadapowerRegPlugState = 1016 + dadapowerRegEnergyImportSession = 1017 + dadapowerRegEnergyImportTotal = 1025 + dadapowerRegIdentification = 1040 // 20 +) + +var dadapowerRegCurrents = []uint16{1006, 1007, 1008} + +// Dadapower charger implementation +type Dadapower struct { + log *util.Logger + conn *modbus.Connection + regOffset uint16 +} + +func init() { + registry.Add("dadapower", NewDadapowerFromConfig) +} + +// NewDadapowerFromConfig creates a Dadapower charger from generic config +func NewDadapowerFromConfig(other map[string]interface{}) (api.Charger, error) { + cc := modbus.TcpSettings{} + + if err := util.DecodeOther(other, &cc); err != nil { + return nil, err + } + + return NewDadapower(cc.URI, cc.ID) +} + +// NewDadapower creates a Dadapower charger +func NewDadapower(uri string, id uint8) (*Dadapower, error) { + conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, id) + if err != nil { + return nil, err + } + + log := util.NewLogger("dadapower") + conn.Logger(log.TRACE) + + wb := &Dadapower{ + log: log, + conn: conn, + } + + // 5min failsafe timeout + if _, err := wb.conn.WriteSingleRegister(dadapowerRegFailsafeTimeout, 5*60); err != nil { + return nil, fmt.Errorf("could not set failsafe timeout: %v", err) + } + + // The charging station may have multiple charging ports - use offset for register addresses for each port + if id > 1 { + wb.regOffset = (uint16(id) - 1) * 1000 + } + + go wb.heartbeat() + + return wb, nil +} + +func (wb *Dadapower) heartbeat() { + for range time.NewTicker(time.Minute).C { + if _, err := wb.conn.ReadInputRegisters(dadapowerRegFailsafeTimeout, 1); err != nil { + wb.log.ERROR.Println("heartbeat:", err) + } + } +} + +// Status implements the api.Charger interface +func (wb *Dadapower) Status() (api.ChargeStatus, error) { + b, err := wb.conn.ReadInputRegisters(dadapowerRegPlugState+wb.regOffset, 1) + + if err != nil { + return api.StatusNone, err + } + + switch binary.BigEndian.Uint16(b) { + case 0x0A: // ready + return api.StatusA, nil + case 0x0B: // EV is present + return api.StatusB, nil + case 0x0C: // charging + return api.StatusC, nil + case 0x0D: // charging with ventilation + return api.StatusD, nil + case 0x0E: // failure (e.g. diode check, RCD failure) + return api.StatusE, nil + default: + return api.StatusNone, errors.New("invalid response") + } +} + +// Enabled implements the api.Charger interface +func (wb *Dadapower) Enabled() (bool, error) { + b, err := wb.conn.ReadInputRegisters(dadapowerRegChargingAllowed+wb.regOffset, 1) + if err != nil { + return false, err + } + + return binary.BigEndian.Uint16(b) > 0, nil +} + +// Enable implements the api.Charger interface +func (wb *Dadapower) Enable(enable bool) error { + var u uint16 + if enable { + u = 1 + } + + _, err := wb.conn.WriteSingleRegister(dadapowerRegChargingAllowed+wb.regOffset, u) + + return err +} + +var _ api.ChargerEx = (*Dadapower)(nil) + +// MaxCurrentMillis implements the api.ChargerEx interface +func (wb *Dadapower) MaxCurrentMillis(current float64) error { + if current < 6 { + return fmt.Errorf("invalid current %v", current) + } + + _, err := wb.conn.WriteSingleRegister(dadapowerRegChargeCurrentLimit+wb.regOffset, uint16(current*100)) + + return err +} + +// MaxCurrent implements the api.Charger interface +func (wb *Dadapower) MaxCurrent(current int64) error { + return wb.MaxCurrentMillis(float64(current)) +} + +var _ api.Meter = (*Dadapower)(nil) + +// CurrentPower implements the api.Meter interface +func (wb *Dadapower) CurrentPower() (float64, error) { + b, err := wb.conn.ReadInputRegisters(dadapowerRegActiveEnergy+wb.regOffset, 2) + if err != nil { + return 0, err + } + + return float64(binary.BigEndian.Uint32(b)), err +} + +var _ api.MeterEnergy = (*Dadapower)(nil) + +// TotalEnergy implements the api.MeterEnergy interface +func (wb *Dadapower) TotalEnergy() (float64, error) { + b, err := wb.conn.ReadInputRegisters(dadapowerRegEnergyImportTotal+wb.regOffset, 4) + if err != nil { + return 0, err + } + + return float64(binary.BigEndian.Uint64(b)) / 1e3, err +} + +var _ api.ChargeRater = (*Dadapower)(nil) + +// ChargedEnergy implements the api.ChargeRater interface +func (wb *Dadapower) ChargedEnergy() (float64, error) { + b, err := wb.conn.ReadInputRegisters(dadapowerRegEnergyImportSession+wb.regOffset, 4) + if err != nil { + return 0, err + } + + return float64(binary.BigEndian.Uint64(b)) / 1e3, err +} + +var _ api.MeterCurrent = (*Dadapower)(nil) + +// Currents implements the api.MeterCurrent interface +func (wb *Dadapower) Currents() (float64, float64, float64, error) { + var currents []float64 + for _, regCurrent := range dadapowerRegCurrents { + b, err := wb.conn.ReadInputRegisters(regCurrent+wb.regOffset, 1) + if err != nil { + return 0, 0, 0, err + } + + currents = append(currents, float64(binary.BigEndian.Uint16(b))/100) + } + + return currents[0], currents[1], currents[2], nil +} + +var _ api.ChargePhases = (*Dadapower)(nil) + +// Phases1p3p implements the api.ChargePhases interface +func (wb *Dadapower) Phases1p3p(phases int) error { + _, err := wb.conn.WriteSingleRegister(dadapowerRegActivePhases+wb.regOffset, uint16(phases)) + return err +} + +var _ api.Identifier = (*Dadapower)(nil) + +// Identify implements the api.Identifier interface +func (wb *Dadapower) Identify() (string, error) { + u, err := wb.conn.ReadInputRegisters(dadapowerRegIdentification+wb.regOffset, 20) + if err != nil { + return "", err + } + + return string(u), nil +} + +// Diagnose implements the Diagnosis interface +func (wb *Dadapower) Diagnose() { + if b, err := wb.conn.ReadInputRegisters(dadapowerRegModel, 1); err == nil { + fmt.Printf("Model:\t%d\n", b[1]) + } + if b, err := wb.conn.ReadInputRegisters(dadapowerRegSerial, 6); err == nil { + fmt.Printf("Serial:\t%s\n", b) + } + if b, err := wb.conn.ReadInputRegisters(dadapowerRegFirmware, 6); err == nil { + fmt.Printf("Firmware:\t%s\n", b) + } + if b, err := wb.conn.ReadInputRegisters(dadapowerRegIdentification, 20); err == nil { + fmt.Printf("Identification:\t%s\n", b) + } +} diff --git a/templates/definition/charger/dadapower.yaml b/templates/definition/charger/dadapower.yaml new file mode 100644 index 000000000..bdbff4a85 --- /dev/null +++ b/templates/definition/charger/dadapower.yaml @@ -0,0 +1,13 @@ +template: dadapower +products: + - brand: Dadapower + description: + generic: Premium Wallbox +capabilities: ["1p3p", "rfid"] +params: + - name: modbus + choice: ["tcpip"] + id: 1 +render: | + type: dadapower + {{- include "modbus" . }} diff --git a/templates/docs/charger/dadapower_0.yaml b/templates/docs/charger/dadapower_0.yaml new file mode 100644 index 000000000..bb50a878d --- /dev/null +++ b/templates/docs/charger/dadapower_0.yaml @@ -0,0 +1,14 @@ +product: + brand: Dadapower + description: Premium Wallbox +capabilities: ["1p3p", "rfid"] +render: + - default: | + type: template + template: dadapower + + # Modbus TCP + modbus: tcpip + id: 1 + host: 192.0.2.2 # Hostname + port: 502 # Port diff --git a/templates/evcc.io/brands.json b/templates/evcc.io/brands.json index c5273114a..d7ab6e7fe 100644 --- a/templates/evcc.io/brands.json +++ b/templates/evcc.io/brands.json @@ -6,6 +6,7 @@ "Bender", "BMW", "cFos", + "Dadapower", "DaheimLaden", "Easee", "Ebee",