Skip to content

Commit

Permalink
Add Etrel/Sonnen charger
Browse files Browse the repository at this point in the history
  • Loading branch information
andig committed Jun 16, 2022
1 parent 151e323 commit 2593379
Showing 1 changed file with 116 additions and 99 deletions.
215 changes: 116 additions & 99 deletions charger/etrel.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,47 +20,53 @@ package charger
import (
"encoding/binary"
"fmt"
"time"
"math"

"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"
"github.com/volkszaehler/mbmd/encoding"
)

// https://github.com/RustyDust/sonnen-charger/blob/main/Etrel%20INCH%20SmartHome%20Modbus%20TCPRegisters.xlsx

const (
etrelRegSerial = 100
etrelRegBrand = 190
etrelRegModel = 210
etrelRegFirmware = 230
etrelRegChargeStatus = 1001
etrelRegCableStatus = 1004
etrelRegChargeTime = 1508
etrelRegMaxCurrent = 5004
etrelRegPower = 1020
etrelRegTotalEnergy = 1036
etrelRegSessionEnergy = 1502
etrelRegFailsafeTimeout = 2002
etrelRegAlive = 6000
// read
etrelRegChargeStatus = 0
etrelRegCurrents = 14 // 16, 18
etrelRegPower = 26
etrelRegSerial = 990
etrelRegModel = 1000
etrelRegHWVersion = 1010
etrelRegSWVersion = 1015
// etrelRegMaxPhaseCurrent = 2
// etrelRegTargetCurrent = 4 // power mgmt or modbus
// etrelRegSessionEnergy = 30
// etrelRegChargeTime = 32
// etrelRegCustomMaxCurrent = 1028

// write
etrelRegMaxCurrent = 8
// etrelRegStop = 1
// etrelRegPause = 2
// etrelRegMaxPower = 11
)

var etrelRegCurrents = []uint16{1008, 1010, 1012}

// Etrel is an api.ChargeController implementation for Etrel/Sonnen wallboxes
// Etrel is an api.Charger implementation for Etrel/Sonnen wallboxes
type Etrel struct {
log *util.Logger
conn *modbus.Connection
current uint16
current float32
}

func init() {
registry.Add("etrel", NewEtrelFromConfig)
}

// NewEtrelFromConfig creates a Vestel charger from generic config
// NewEtrelFromConfig creates a Etrel charger from generic config
func NewEtrelFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := modbus.TcpSettings{
ID: 255,
ID: 1,
}

if err := util.DecodeOther(other, &cc); err != nil {
Expand All @@ -70,16 +76,16 @@ func NewEtrelFromConfig(other map[string]interface{}) (api.Charger, error) {
return NewEtrel(cc.URI, cc.ID)
}

// NewEtrel creates a Vestel charger
// NewEtrel creates a Etrel charger
func NewEtrel(uri string, id uint8) (*Etrel, error) {
conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, id)
if err != nil {
return nil, err
}

if !sponsor.IsAuthorized() {
return nil, api.ErrSponsorRequired
}
// if !sponsor.IsAuthorized() {
// return nil, api.ErrSponsorRequired
// }

log := util.NewLogger("etrel")
conn.Logger(log.TRACE)
Expand All @@ -95,71 +101,90 @@ func NewEtrel(uri string, id uint8) (*Etrel, error) {

// Status implements the api.Charger interface
func (wb *Etrel) Status() (api.ChargeStatus, error) {
res := api.StatusA

b, err := wb.conn.ReadInputRegisters(etrelRegCableStatus, 1)
if err == nil && binary.BigEndian.Uint16(b) > 0 {
res = api.StatusB

b, err = wb.conn.ReadInputRegisters(etrelRegChargeStatus, 1)
if err == nil && binary.BigEndian.Uint16(b) > 0 {
res = api.StatusC
}
b, err := wb.conn.ReadInputRegisters(etrelRegChargeStatus, 1)
if err != nil {
return api.StatusNone, err
}

return res, err
wb.log.TRACE.Printf("etrelRegChargeStatus: %v", binary.BigEndian.Uint16(b))

// 0 Unknown
// 1 SocketAvailable
// 2 WaitingForVehicleToBeConnected
// 3 WaitingForVehicleToStart
// 4 Charging
// 5 ChargingPausedByEv
// 6 ChargingPausedByEvse
// 7 ChargingEnded
// 8 ChargingFault
// 9 UnpausingCharging
// 10 Unavailable

switch u := binary.BigEndian.Uint16(b); u {
case 1, 2:
return api.StatusA, nil
case 3, 5, 6, 7, 9:
return api.StatusB, nil
case 4:
return api.StatusC, nil
default:
return api.StatusNone, fmt.Errorf("invalid status: %d", u)
}
}

// Enabled implements the api.Charger interface
func (wb *Etrel) Enabled() (bool, error) {
b, err := wb.conn.ReadHoldingRegisters(etrelRegMaxCurrent, 1)
b, err := wb.conn.ReadHoldingRegisters(etrelRegMaxCurrent, 2)
if err != nil {
return false, err
}

return binary.BigEndian.Uint16(b) > 0, nil
wb.log.TRACE.Printf("etrelRegMaxCurrent: %v", encoding.Float32(b))

return encoding.Float32(b) > 0, nil
}

// Enable implements the api.Charger interface
func (wb *Etrel) Enable(enable bool) error {
var u uint16
var current float32
if enable {
u = wb.current
current = wb.current
}

_, err := wb.conn.WriteSingleRegister(etrelRegMaxCurrent, u)
return wb.setCurrent(current)
}

func (wb *Etrel) setCurrent(current float32) error {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, math.Float32bits(current))

_, err := wb.conn.WriteMultipleRegisters(etrelRegMaxCurrent, 2, b)
return err
}

// MaxCurrent implements the api.Charger interface
func (wb *Etrel) MaxCurrent(current int64) error {
return wb.MaxCurrentMillis(float64(current))
}

var _ api.ChargerEx = (*Etrel)(nil)

// MaxCurrentMilli implements the api.ChargerEx interface
func (wb *Etrel) MaxCurrentMillis(current float64) error {
if current < 6 {
return fmt.Errorf("invalid current %d", current)
return fmt.Errorf("invalid current %.1f", current)
}

u := uint16(current)
_, err := wb.conn.WriteSingleRegister(etrelRegMaxCurrent, u)
f := float32(current)

err := wb.setCurrent(f)
if err == nil {
wb.current = u
wb.current = f
}

return err
}

var _ api.ChargeTimer = (*Etrel)(nil)

// ChargingTime implements the api.ChargeTimer interface
func (wb *Etrel) ChargingTime() (time.Duration, error) {
b, err := wb.conn.ReadInputRegisters(etrelRegChargeTime, 2)
if err != nil {
return 0, err
}

secs := binary.BigEndian.Uint32(b)
return time.Duration(time.Duration(secs) * time.Second), nil
}

var _ api.Meter = (*Etrel)(nil)

// CurrentPower implements the api.Meter interface
Expand All @@ -169,62 +194,54 @@ func (wb *Etrel) CurrentPower() (float64, error) {
return 0, err
}

return float64(binary.BigEndian.Uint32(b)), err
return float64(encoding.Float32(b) * 1e3), err
}

var _ api.MeterEnergy = (*Etrel)(nil)
// var _ api.ChargeRater = (*Etrel)(nil)

// TotalEnergy implements the api.MeterEnergy interface
func (wb *Etrel) TotalEnergy() (float64, error) {
b, err := wb.conn.ReadInputRegisters(etrelRegTotalEnergy, 2)
if err != nil {
return 0, err
}
// // ChargedEnergy implements the api.ChargeRater interface
// func (wb *Etrel) ChargedEnergy() (float64, error) {
// b, err := wb.conn.ReadInputRegisters(etrelRegSessionEnergy, 2)
// if err != nil {
// return 0, err
// }

return float64(binary.BigEndian.Uint32(b)) / 10, err
}
// fmt.Printf("%.5f", encoding.Float32(b))

var _ api.ChargeRater = (*Etrel)(nil)

// ChargedEnergy implements the api.ChargeRater interface
func (wb *Etrel) ChargedEnergy() (float64, error) {
b, err := wb.conn.ReadInputRegisters(etrelRegSessionEnergy, 2)
if err != nil {
return 0, err
}

return float64(binary.BigEndian.Uint32(b)) / 1e3, err
}
// return float64(encoding.Float32(b)), err
// }

var _ api.MeterCurrent = (*Etrel)(nil)

// Currents implements the api.MeterCurrent interface
func (wb *Etrel) Currents() (float64, float64, float64, error) {
var currents []float64
for _, regCurrent := range etrelRegCurrents {
b, err := wb.conn.ReadInputRegisters(regCurrent, 1)
if err != nil {
return 0, 0, 0, err
}
b, err := wb.conn.ReadInputRegisters(etrelRegCurrents, 6)
if err != nil {
return 0, 0, 0, err
}

currents = append(currents, float64(binary.BigEndian.Uint16(b))/1e3)
var currents [3]float64
for i := 0; i < 3; i++ {
currents[i] = float64(encoding.Float32(b[4*i:]))
}

return currents[0], currents[1], currents[2], nil
}

// Diagnose implements the Diagnosis interface
var _ api.Diagnosis = (*Etrel)(nil)

// Diagnose implements the api.Diagnosis interface
func (wb *Etrel) Diagnose() {
// if b, err := wb.conn.ReadInputRegisters(etrelRegBrand, 10); err == nil {
// fmt.Printf("Brand:\t%s\n", b)
// }
// if b, err := wb.conn.ReadInputRegisters(etrelRegModel, 5); err == nil {
// fmt.Printf("Model:\t%s\n", b)
// }
// if b, err := wb.conn.ReadInputRegisters(etrelRegSerial, 25); err == nil {
// fmt.Printf("Serial:\t%s\n", b)
// }
// if b, err := wb.conn.ReadInputRegisters(etrelRegFirmware, 50); err == nil {
// fmt.Printf("Firmware:\t%s\n", b)
// }
if b, err := wb.conn.ReadInputRegisters(etrelRegModel, 10); err == nil {
fmt.Printf("Model:\t%s\n", b)
}
if b, err := wb.conn.ReadInputRegisters(etrelRegSerial, 10); err == nil {
fmt.Printf("Serial:\t%s\n", b)
}
if b, err := wb.conn.ReadInputRegisters(etrelRegHWVersion, 5); err == nil {
fmt.Printf("Hardware:\t%s\n", b)
}
if b, err := wb.conn.ReadInputRegisters(etrelRegSWVersion, 5); err == nil {
fmt.Printf("Software:\t%s\n", b)
}
}

0 comments on commit 2593379

Please sign in to comment.