evcc-io/charger/evsedin.go
2026-02-17 11:10:03 +01:00

200 lines
4.5 KiB
Go

package charger
import (
"context"
"encoding/binary"
"fmt"
"time"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/modbus"
"github.com/volkszaehler/mbmd/encoding"
)
// EvseDIN charger implementation
type EvseDIN struct {
conn *modbus.Connection
current uint16
}
const (
evseRegCurrent = 1000
evseRegStatus = 1002
evseRegFirmware = 1005
evseRegConfig = 2005
)
func init() {
registry.AddCtx("evsedin", NewEvseDINFromConfig)
}
// https://www.evracing.cz/user/documents/upload/EVSE-WB-DIN_latest.pdf
// NewEvseDINFromConfig creates an EVSE DIN charger from generic config
func NewEvseDINFromConfig(ctx context.Context, other map[string]any) (api.Charger, error) {
cc := modbus.Settings{
Baudrate: 9600,
Comset: "8N1",
ID: 1,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
return NewEvseDIN(ctx, cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.Protocol(), cc.ID)
}
//go:generate go tool decorate -f decorateEvseDIN -b *EvseDIN -r api.Charger -t api.ChargerEx
// NewEvseDIN creates EVSE DIN charger
func NewEvseDIN(ctx context.Context, uri, device, comset string, baudrate int, proto modbus.Protocol, slaveID uint8) (api.Charger, error) {
log := util.NewLogger("evse")
conn, err := modbus.NewConnection(ctx, uri, device, comset, baudrate, proto, slaveID)
if err != nil {
return nil, err
}
conn.Logger(log.TRACE)
conn.Delay(200 * time.Millisecond)
evse := &EvseDIN{
conn: conn,
current: 6, // assume min current
}
var maxCurrentMillis func(float64) error
// check firmware
bFirmware, err := evse.conn.ReadHoldingRegisters(evseRegFirmware, 1)
if err != nil {
return nil, err
}
if encoding.Uint16(bFirmware) >= 17 {
// check configuration
bConfig, err := evse.conn.ReadHoldingRegisters(evseRegConfig, 1)
if err != nil {
return nil, err
}
config := encoding.Uint16(bConfig)
// enable mA feature if not enabled yet
if config&0x0080 == 0 {
b := make([]byte, 2)
config |= 0x0080 // set milliAmps config bit7
binary.BigEndian.PutUint16(b, config)
if _, err := evse.conn.WriteMultipleRegisters(evseRegConfig, 1, b); err != nil {
return nil, err
}
}
if config&0x0080 != 0 {
maxCurrentMillis = evse.maxCurrentMillis
evse.current = 600 // assume min current
}
}
current, err := evse.getCurrent()
if err != nil {
return nil, fmt.Errorf("current limit: %w", err)
}
if current > 0 {
evse.current = current
}
return decorateEvseDIN(evse, maxCurrentMillis), nil
}
// Status implements the api.Charger interface
func (evse *EvseDIN) Status() (api.ChargeStatus, error) {
b, err := evse.conn.ReadHoldingRegisters(evseRegStatus, 1)
if err != nil {
return api.StatusNone, err
}
s := binary.BigEndian.Uint16(b)
switch s {
case 1: // not connected
return api.StatusA, nil
case 2: // connected, not charging
return api.StatusB, nil
case 3, 4: // charging
return api.StatusC, nil
default:
return api.StatusNone, fmt.Errorf("invalid status: %d", s)
}
}
// Enabled implements the api.Charger interface
func (evse *EvseDIN) Enabled() (bool, error) {
b, err := evse.conn.ReadHoldingRegisters(evseRegCurrent, 1)
if err != nil {
return false, err
}
return binary.BigEndian.Uint16(b) != 0, nil
}
// Enable implements the api.Charger interface
func (evse *EvseDIN) Enable(enable bool) error {
b := make([]byte, 2)
if enable {
binary.BigEndian.PutUint16(b, evse.current)
}
_, err := evse.conn.WriteMultipleRegisters(evseRegCurrent, 1, b)
return err
}
// MaxCurrent implements the api.Charger interface
func (evse *EvseDIN) MaxCurrent(current int64) error {
if current < 6 {
return fmt.Errorf("invalid current %d", current)
}
if err := evse.setCurrent(uint16(current)); err != nil {
return err
}
evse.current = uint16(current)
return nil
}
// maxCurrentMillis implements the api.ChargerEx interface
func (evse *EvseDIN) maxCurrentMillis(current float64) error {
if current < 6 {
return fmt.Errorf("invalid current %.2f", current)
}
u := uint16(current * 100) // 0.01A Steps
if err := evse.setCurrent(u); err != nil {
return err
}
evse.current = u
return nil
}
func (evse *EvseDIN) setCurrent(current uint16) error {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, current)
_, err := evse.conn.WriteMultipleRegisters(evseRegCurrent, 1, b)
return err
}
func (evse *EvseDIN) getCurrent() (uint16, error) {
b, err := evse.conn.ReadHoldingRegisters(evseRegCurrent, 1)
if err != nil {
return 0, err
}
return binary.BigEndian.Uint16(b), nil
}