Keba: add modbus tcp support with phase switching (#7485)

This commit is contained in:
andig 2023-05-18 17:00:59 +02:00 • committed by GitHub
parent cba97832db
commit 13dd7d578e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 996 additions and 72 deletions

276
charger/keba-modbus.go Normal file
View file

@ -0,0 +1,276 @@
package charger
import (
"encoding/binary"
"encoding/hex"
"fmt"
"strconv"
"strings"
"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"
)
// https://www.keba.com/en/emobility/service-support/downloads/Downloads
// https://www.keba.com/download/x/dea7ae6b84/kecontactp30modbustcp_pgen.pdf
// Keba is an api.Charger implementation
type Keba struct {
conn *modbus.Connection
}
const (
kebaRegChargingState = 1000
kebaRegCableState = 1004
kebaRegCurrents = 1008 // 6 regs, mA
kebaRegSerial = 1014 // leading zeros trimmed
kebaRegProduct = 1016
kebaRegFirmware = 1018
kebaRegPower = 1020 // mW
kebaRegEnergy = 1036 // Wh
kebaRegVoltages = 1040 // 6 regs, V
kebaRegRfid = 1500 // hex
kebaRegSessionEnergy = 1502 // Wh
kebaRegPhaseSource = 1550
kebaRegPhaseState = 1552
kebaRegFailsafeTimeout = 1602
kebaRegMaxCurrent = 5004 // mA
kebaRegEnable = 5014
kebaRegTriggerPhase = 5052
)
func init() {
registry.Add("keba-modbus", NewKebaFromConfig)
}
// go:generate go run ../cmd/tools/decorate.go -f decorateKeba -b *Keba -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.Identifier,Identify,func() (string, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error"
// NewKebaFromConfig creates a new Keba ModbusTCP charger
func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := modbus.TcpSettings{
ID: 255,
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
wb, err := NewKeba(cc.URI, cc.ID)
if err != nil {
return nil, err
}
// features
var (
currentPower, totalEnergy func() (float64, error)
currents func() (float64, float64, float64, error)
identify func() (string, error)
)
b, err := wb.conn.ReadHoldingRegisters(kebaRegProduct, 2)
if err != nil {
return nil, err
}
if features := binary.BigEndian.Uint32(b); (features/100)%10 > 0 {
currentPower = wb.currentPower
totalEnergy = wb.totalEnergy
currents = wb.currents
}
if features := binary.BigEndian.Uint32(b); features%10 > 0 {
identify = wb.identify
}
// phases
b, err = wb.conn.ReadHoldingRegisters(kebaRegPhaseSource, 2)
if err != nil {
return nil, err
}
var phases func(int) error
if source := binary.BigEndian.Uint32(b); source == 3 {
phases = wb.phases1p3p
}
return decorateKeba(wb, currentPower, totalEnergy, currents, identify, phases), nil
}
// NewKeba creates a new charger
func NewKeba(uri string, slaveID uint8) (*Keba, error) {
conn, err := modbus.NewConnection(uri, "", "", 0, modbus.Tcp, slaveID)
if err != nil {
return nil, err
}
if !sponsor.IsAuthorized() {
return nil, api.ErrSponsorRequired
}
log := util.NewLogger("keba")
conn.Logger(log.TRACE)
// per Keba docs
// conn.Delay(500 * time.Millisecond)
wb := &Keba{
conn: conn,
}
return wb, err
}
// Status implements the api.Charger interface
func (wb *Keba) Status() (api.ChargeStatus, error) {
b, err := wb.conn.ReadHoldingRegisters(kebaRegCableState, 2)
if err != nil {
return api.StatusNone, err
}
fmt.Printf("1004: %0x\n", b)
switch status := binary.BigEndian.Uint32(b); status {
case 0:
return api.StatusA, nil
case 1, 3, 5:
return api.StatusB, nil
case 7:
b, err := wb.conn.ReadHoldingRegisters(kebaRegChargingState, 2)
if err != nil {
return api.StatusNone, err
}
fmt.Printf("1000: %0x\n", b)
if binary.BigEndian.Uint32(b) == 3 {
return api.StatusC, err
}
return api.StatusB, nil
default:
return api.StatusNone, fmt.Errorf("invalid status: %d", status)
}
}
// Enabled implements the api.Charger interface
func (wb *Keba) Enabled() (bool, error) {
b, err := wb.conn.ReadHoldingRegisters(kebaRegChargingState, 2)
if err != nil {
return false, err
}
fmt.Printf("1000: %0x\n", b)
return binary.BigEndian.Uint32(b) != 5, nil
}
// Enable implements the api.Charger interface
func (wb *Keba) Enable(enable bool) error {
var u uint16
if enable {
u = 1
}
_, err := wb.conn.WriteSingleRegister(kebaRegEnable, u)
return err
}
// MaxCurrent implements the api.Charger interface
func (wb *Keba) MaxCurrent(current int64) error {
return wb.MaxCurrentMillis(float64(current))
}
var _ api.ChargerEx = (*Keba)(nil)
// MaxCurrentMillis implements the api.ChargerEx interface
func (wb *Keba) MaxCurrentMillis(current float64) error {
u := uint16(current * 1000)
_, err := wb.conn.WriteSingleRegister(kebaRegMaxCurrent, u)
return err
}
// currentPower implements the api.Meter interface
func (wb *Keba) currentPower() (float64, error) {
b, err := wb.conn.ReadHoldingRegisters(kebaRegPower, 2)
if err != nil {
return 0, err
}
return float64(binary.BigEndian.Uint32(b)) / 1e3, nil
}
// totalEnergy implements the api.MeterEnergy interface
func (wb *Keba) totalEnergy() (float64, error) {
b, err := wb.conn.ReadHoldingRegisters(kebaRegEnergy, 2)
if err != nil {
return 0, err
}
return float64(binary.BigEndian.Uint32(b)) / 1e3, nil
}
// currents implements the api.PhaseCurrents interface
func (wb *Keba) currents() (float64, float64, float64, error) {
var res [3]float64
for i := uint16(0); i < 3; i++ {
b, err := wb.conn.ReadHoldingRegisters(kebaRegCurrents+2*i, 2)
if err != nil {
return 0, 0, 0, err
}
res[i] = float64(binary.BigEndian.Uint32(b)) / 1e3
}
return res[0], res[1], res[2], nil
}
// identify implements the api.Identifier interface
func (wb *Keba) identify() (string, error) {
b, err := wb.conn.ReadHoldingRegisters(kebaRegRfid, 2)
if err != nil {
return "", err
}
id := hex.EncodeToString(b)
if id == "00000000" {
id = ""
}
return id, nil
}
// phases1p3p implements the api.PhaseSwitcher interface
func (wb *Keba) phases1p3p(phases int) error {
var u uint16
if phases == 3 {
u = 1
}
_, err := wb.conn.WriteSingleRegister(kebaRegTriggerPhase, u)
return err
}
var _ api.Diagnosis = (*Keba)(nil)
// Diagnose implements the api.Diagnosis interface
func (wb *Keba) Diagnose() {
if b, err := wb.conn.ReadHoldingRegisters(kebaRegSerial, 2); err == nil {
fmt.Printf("\tSerial:\t%s\n", strings.TrimLeft(strconv.Itoa(int(binary.BigEndian.Uint32(b))), "0"))
}
if b, err := wb.conn.ReadHoldingRegisters(kebaRegFirmware, 2); err == nil {
fmt.Printf("\tFirmware:\t%d.%d.%d\n", b[0], b[1], b[2])
}
if b, err := wb.conn.ReadHoldingRegisters(kebaRegProduct, 2); err == nil {
fmt.Printf("\tProduct:\t%6d\n", binary.BigEndian.Uint32(b))
}
if b, err := wb.conn.ReadHoldingRegisters(kebaRegPhaseSource, 2); err == nil {
fmt.Printf("\tPhases source:\t%d\n", binary.BigEndian.Uint32(b))
}
if b, err := wb.conn.ReadHoldingRegisters(kebaRegPhaseState, 2); err == nil {
fmt.Printf("\tPhases state:\t%d\n", binary.BigEndian.Uint32(b))
}
if b, err := wb.conn.ReadHoldingRegisters(kebaRegFailsafeTimeout, 2); err == nil {
fmt.Printf("\tFailsafe timeout:\t%ds\n", binary.BigEndian.Uint32(b))
}
}

View file

@ -0,0 +1,593 @@
package charger
// Code generated by github.com/evcc-io/evcc/cmd/tools/decorate.go. DO NOT EDIT.
import (
"github.com/evcc-io/evcc/api"
)
func decorateKeba(base *Keba, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), identifier func() (string, error), phaseSwitcher func(int) error) api.Charger {
switch {
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return base
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Meter
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Keba
api.MeterEnergy
}{
Keba: base,
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Meter
api.MeterEnergy
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.PhaseCurrents
}{
Keba: base,
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Meter
api.PhaseCurrents
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.MeterEnergy
api.PhaseCurrents
}{
Keba: base,
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Meter
api.MeterEnergy
api.PhaseCurrents
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
api.Meter
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
api.MeterEnergy
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
api.Meter
api.MeterEnergy
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
api.PhaseCurrents
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
api.Meter
api.PhaseCurrents
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
api.MeterEnergy
api.PhaseCurrents
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil:
return &struct {
*Keba
api.Identifier
api.Meter
api.MeterEnergy
api.PhaseCurrents
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.PhaseSwitcher
}{
Keba: base,
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Meter
api.PhaseSwitcher
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.MeterEnergy
api.PhaseSwitcher
}{
Keba: base,
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Meter
api.MeterEnergy
api.PhaseSwitcher
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Meter
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Meter
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.Meter
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.MeterEnergy
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.Meter
api.MeterEnergy
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.Meter
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case identifier != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil:
return &struct {
*Keba
api.Identifier
api.Meter
api.MeterEnergy
api.PhaseCurrents
api.PhaseSwitcher
}{
Keba: base,
Identifier: &decorateKebaIdentifierImpl{
identifier: identifier,
},
Meter: &decorateKebaMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
PhaseSwitcher: &decorateKebaPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
}
return nil
}
type decorateKebaIdentifierImpl struct {
identifier func() (string, error)
}
func (impl *decorateKebaIdentifierImpl) Identify() (string, error) {
return impl.identifier()
}
type decorateKebaMeterImpl struct {
meter func() (float64, error)
}
func (impl *decorateKebaMeterImpl) CurrentPower() (float64, error) {
return impl.meter()
}
type decorateKebaMeterEnergyImpl struct {
meterEnergy func() (float64, error)
}
func (impl *decorateKebaMeterEnergyImpl) TotalEnergy() (float64, error) {
return impl.meterEnergy()
}
type decorateKebaPhaseCurrentsImpl struct {
phaseCurrents func() (float64, float64, float64, error)
}
func (impl *decorateKebaPhaseCurrentsImpl) Currents() (float64, float64, float64, error) {
return impl.phaseCurrents()
}
type decorateKebaPhaseSwitcherImpl struct {
phaseSwitcher func(int) error
}
func (impl *decorateKebaPhaseSwitcherImpl) Phases1p3p(phases int) error {
return impl.phaseSwitcher(phases)
}

View file

@ -18,34 +18,29 @@ const (
udpTimeout = time.Second
)
// RFID contains access credentials
type RFID struct {
Tag string
}
// Keba is an api.Charger implementation with configurable getters and setters.
type Keba struct {
// KebaUdp is an api.Charger implementation
type KebaUdp struct {
log *util.Logger
conn string
rfid RFID
rfid keba.RFID
timeout time.Duration
recv chan keba.UDPMsg
sender *keba.Sender
}
func init() {
registry.Add("keba", NewKebaFromConfig)
registry.Add("keba-udp", NewKebaUdpFromConfig)
}
//go:generate go run ../cmd/tools/decorate.go -f decorateKeba -b *Keba -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)"
//go:generate go run ../cmd/tools/decorate.go -f decorateKebaUdp -b *KebaUdp -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.PhaseCurrents,Currents,func() (float64, float64, float64, error)"
// NewKebaFromConfig creates a new configurable charger
func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
// NewKebaUdpFromConfig creates a new Keba UDP charger
func NewKebaUdpFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
URI string
Serial string
Timeout time.Duration
RFID RFID
RFID keba.RFID
}{
Timeout: udpTimeout,
}
@ -54,7 +49,7 @@ func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
return nil, err
}
k, err := NewKeba(cc.URI, cc.Serial, cc.RFID, cc.Timeout)
k, err := NewKebaUdp(cc.URI, cc.Serial, cc.RFID, cc.Timeout)
if err != nil {
return nil, err
}
@ -65,14 +60,14 @@ func NewKebaFromConfig(other map[string]interface{}) (api.Charger, error) {
}
if energy > 0 {
return decorateKeba(k, k.currentPower, k.totalEnergy, k.currents), nil
return decorateKebaUdp(k, k.currentPower, k.totalEnergy, k.currents), nil
}
return k, err
}
// NewKeba creates a new charger
func NewKeba(uri, serial string, rfid RFID, timeout time.Duration) (*Keba, error) {
// NewKebaUdp creates a new charger
func NewKebaUdp(uri, serial string, rfid keba.RFID, timeout time.Duration) (*KebaUdp, error) {
log := util.NewLogger("keba")
instance, err := keba.Instance(log)
@ -84,7 +79,7 @@ func NewKeba(uri, serial string, rfid RFID, timeout time.Duration) (*Keba, error
conn := util.DefaultPort(uri, keba.Port)
sender, err := keba.NewSender(log, conn)
c := &Keba{
c := &KebaUdp{
log: log,
conn: conn,
rfid: rfid,
@ -103,7 +98,7 @@ func NewKeba(uri, serial string, rfid RFID, timeout time.Duration) (*Keba, error
return c, err
}
func (c *Keba) receive(report int, resC chan<- keba.UDPMsg, errC chan<- error, closeC <-chan struct{}) {
func (c *KebaUdp) receive(report int, resC chan<- keba.UDPMsg, errC chan<- error, closeC <-chan struct{}) {
t := time.NewTimer(c.timeout)
defer close(resC)
defer close(errC)
@ -129,7 +124,7 @@ func (c *Keba) receive(report int, resC chan<- keba.UDPMsg, errC chan<- error, c
}
}
func (c *Keba) roundtrip(msg string, report int, res interface{}) error {
func (c *KebaUdp) roundtrip(msg string, report int, res interface{}) error {
resC := make(chan keba.UDPMsg)
errC := make(chan error)
closeC := make(chan struct{})
@ -172,7 +167,7 @@ func (c *Keba) roundtrip(msg string, report int, res interface{}) error {
}
// Status implements the api.Charger interface
func (c *Keba) Status() (api.ChargeStatus, error) {
func (c *KebaUdp) Status() (api.ChargeStatus, error) {
var kr keba.Report2
err := c.roundtrip("report", 2, &kr)
if err != nil {
@ -193,7 +188,7 @@ func (c *Keba) Status() (api.ChargeStatus, error) {
}
// Enabled implements the api.Charger interface
func (c *Keba) Enabled() (bool, error) {
func (c *KebaUdp) Enabled() (bool, error) {
var kr keba.Report2
err := c.roundtrip("report", 2, &kr)
if err != nil {
@ -204,7 +199,7 @@ func (c *Keba) Enabled() (bool, error) {
}
// enableRFID sends RFID credentials to enable charge
func (c *Keba) enableRFID() error {
func (c *KebaUdp) enableRFID() error {
// check if authorization required
var kr keba.Report2
if err := c.roundtrip("report", 2, &kr); err != nil {
@ -227,7 +222,7 @@ func (c *Keba) enableRFID() error {
}
// Enable implements the api.Charger interface
func (c *Keba) Enable(enable bool) error {
func (c *KebaUdp) Enable(enable bool) error {
if enable {
if err := c.enableRFID(); err != nil {
return err
@ -249,7 +244,7 @@ func (c *Keba) Enable(enable bool) error {
}
// MaxCurrent implements the api.Charger interface
func (c *Keba) MaxCurrent(current int64) error {
func (c *KebaUdp) MaxCurrent(current int64) error {
d := 1000 * current
var resp string
@ -260,10 +255,10 @@ func (c *Keba) MaxCurrent(current int64) error {
return nil
}
var _ api.ChargerEx = (*Keba)(nil)
var _ api.ChargerEx = (*KebaUdp)(nil)
// MaxCurrentMillis implements the api.ChargerEx interface
func (c *Keba) MaxCurrentMillis(current float64) error {
func (c *KebaUdp) MaxCurrentMillis(current float64) error {
d := int(1000 * current)
var resp string
@ -278,7 +273,7 @@ func (c *Keba) MaxCurrentMillis(current float64) error {
}
// currentPower implements the api.Meter interface
func (c *Keba) currentPower() (float64, error) {
func (c *KebaUdp) currentPower() (float64, error) {
var kr keba.Report3
err := c.roundtrip("report", 3, &kr)
@ -287,7 +282,7 @@ func (c *Keba) currentPower() (float64, error) {
}
// totalEnergy implements the api.MeterEnergy interface
func (c *Keba) totalEnergy() (float64, error) {
func (c *KebaUdp) totalEnergy() (float64, error) {
var kr keba.Report3
err := c.roundtrip("report", 3, &kr)
@ -296,7 +291,7 @@ func (c *Keba) totalEnergy() (float64, error) {
}
// currents implements the api.PhaseCurrents interface
func (c *Keba) currents() (float64, float64, float64, error) {
func (c *KebaUdp) currents() (float64, float64, float64, error) {
var kr keba.Report3
err := c.roundtrip("report", 3, &kr)
@ -304,19 +299,19 @@ func (c *Keba) currents() (float64, float64, float64, error) {
return float64(kr.I1) / 1e3, float64(kr.I2) / 1e3, float64(kr.I3) / 1e3, err
}
var _ api.Identifier = (*Keba)(nil)
var _ api.Identifier = (*KebaUdp)(nil)
// Identify implements the api.Identifier interface
func (c *Keba) Identify() (string, error) {
func (c *KebaUdp) Identify() (string, error) {
var kr keba.Report100
err := c.roundtrip("report", 100, &kr)
return kr.RFIDTag, err
}
var _ api.Diagnosis = (*Keba)(nil)
var _ api.Diagnosis = (*KebaUdp)(nil)
// Diagnose implements the api.Diagnosis interface
func (c *Keba) Diagnose() {
func (c *KebaUdp) Diagnose() {
var kr keba.Report100
if err := c.roundtrip("report", 100, &kr); err == nil {
fmt.Printf("%+v\n", kr)

View file

@ -6,104 +6,104 @@ import (
"github.com/evcc-io/evcc/api"
)
func decorateKeba(base *Keba, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Charger {
func decorateKebaUdp(base *KebaUdp, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error)) api.Charger {
switch {
case meter == nil && meterEnergy == nil && phaseCurrents == nil:
return base
case meter != nil && meterEnergy == nil && phaseCurrents == nil:
return &struct {
*Keba
*KebaUdp
api.Meter
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
KebaUdp: base,
Meter: &decorateKebaUdpMeterImpl{
meter: meter,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents == nil:
return &struct {
*Keba
*KebaUdp
api.MeterEnergy
}{
Keba: base,
MeterEnergy: &decorateKebaMeterEnergyImpl{
KebaUdp: base,
MeterEnergy: &decorateKebaUdpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents == nil:
return &struct {
*Keba
*KebaUdp
api.Meter
api.MeterEnergy
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
KebaUdp: base,
Meter: &decorateKebaUdpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
MeterEnergy: &decorateKebaUdpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter == nil && meterEnergy == nil && phaseCurrents != nil:
return &struct {
*Keba
*KebaUdp
api.PhaseCurrents
}{
Keba: base,
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
KebaUdp: base,
PhaseCurrents: &decorateKebaUdpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter != nil && meterEnergy == nil && phaseCurrents != nil:
return &struct {
*Keba
*KebaUdp
api.Meter
api.PhaseCurrents
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
KebaUdp: base,
Meter: &decorateKebaUdpMeterImpl{
meter: meter,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
PhaseCurrents: &decorateKebaUdpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter == nil && meterEnergy != nil && phaseCurrents != nil:
return &struct {
*Keba
*KebaUdp
api.MeterEnergy
api.PhaseCurrents
}{
Keba: base,
MeterEnergy: &decorateKebaMeterEnergyImpl{
KebaUdp: base,
MeterEnergy: &decorateKebaUdpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
PhaseCurrents: &decorateKebaUdpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
case meter != nil && meterEnergy != nil && phaseCurrents != nil:
return &struct {
*Keba
*KebaUdp
api.Meter
api.MeterEnergy
api.PhaseCurrents
}{
Keba: base,
Meter: &decorateKebaMeterImpl{
KebaUdp: base,
Meter: &decorateKebaUdpMeterImpl{
meter: meter,
},
MeterEnergy: &decorateKebaMeterEnergyImpl{
MeterEnergy: &decorateKebaUdpMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseCurrents: &decorateKebaPhaseCurrentsImpl{
PhaseCurrents: &decorateKebaUdpPhaseCurrentsImpl{
phaseCurrents: phaseCurrents,
},
}
@ -112,26 +112,26 @@ func decorateKeba(base *Keba, meter func() (float64, error), meterEnergy func()
return nil
}
type decorateKebaMeterImpl struct {
type decorateKebaUdpMeterImpl struct {
meter func() (float64, error)
}
func (impl *decorateKebaMeterImpl) CurrentPower() (float64, error) {
func (impl *decorateKebaUdpMeterImpl) CurrentPower() (float64, error) {
return impl.meter()
}
type decorateKebaMeterEnergyImpl struct {
type decorateKebaUdpMeterEnergyImpl struct {
meterEnergy func() (float64, error)
}
func (impl *decorateKebaMeterEnergyImpl) TotalEnergy() (float64, error) {
func (impl *decorateKebaUdpMeterEnergyImpl) TotalEnergy() (float64, error) {
return impl.meterEnergy()
}
type decorateKebaPhaseCurrentsImpl struct {
type decorateKebaUdpPhaseCurrentsImpl struct {
phaseCurrents func() (float64, float64, float64, error)
}
func (impl *decorateKebaPhaseCurrentsImpl) Currents() (float64, float64, float64, error) {
func (impl *decorateKebaUdpPhaseCurrentsImpl) Currents() (float64, float64, float64, error) {
return impl.phaseCurrents()
}

View file

@ -1,5 +1,10 @@
package keba
// RFID contains access credentials
type RFID struct {
Tag string
}
// Report contains report id and device serial
type Report struct {
ID int `json:"ID,string"`

View file

@ -0,0 +1,21 @@
template: keba-modbus
products:
- brand: KEBA
description:
generic: KeContact P20, P30, C/X Series
- brand: BMW
description:
generic: i Wallbox
capabilities: ["1p3p", "mA", "rfid"]
requirements:
evcc: ["sponsorship"]
description:
de: Erfordert Firmwareversion 3.10.42 (C-series) bzw. 1.11 (X-series)
en: Requires firmware version 3.10.42 (C-series) bzw. 1.11 (X-series)
params:
- name: modbus
choice: ["tcpip"]
id: 255
render: |
type: keba-modbus
{{- include "modbus" . }}

View file

@ -2,10 +2,10 @@ template: keba
products:
- brand: KEBA
description:
generic: KeContact P20, P30, C/X Series
generic: KeContact P20, P30, C/X Series (legacy UDP protocol)
- brand: BMW
description:
generic: i Wallbox
generic: i Wallbox (legacy UDP protocol)
capabilities: ["mA", "rfid"]
requirements:
description:
@ -25,7 +25,7 @@ params:
de: Die Seriennummer, ermöglicht es auch mit der Wallbox zu kommunizieren wenn evcc in Docker läuft.
en: The serial number, allows to communicate with the Wallbox when running evcc in docker
render: |
type: keba
type: keba-udp
uri: {{ .host }}
{{ if ne .rfid "" }}
rfid:

View file

@ -0,0 +1,17 @@
product:
brand: KEBA
description: KeContact P20, P30, C/X Series
capabilities: ["1p3p", "mA", "rfid"]
requirements: ["sponsorship"]
description: |
Erfordert Firmwareversion 3.10.42 (C-series) bzw. 1.11 (X-series)
render:
- default: |
type: template
template: keba-modbus
# Modbus TCP
modbus: tcpip
id: 255
host: 192.0.2.2 # Hostname
port: 502 # Port

View file

@ -0,0 +1,17 @@
product:
brand: BMW
description: i Wallbox
capabilities: ["1p3p", "mA", "rfid"]
requirements: ["sponsorship"]
description: |
Erfordert Firmwareversion 3.10.42 (C-series) bzw. 1.11 (X-series)
render:
- default: |
type: template
template: keba-modbus
# Modbus TCP
modbus: tcpip
id: 255
host: 192.0.2.2 # Hostname
port: 502 # Port

View file

@ -1,6 +1,6 @@
product:
brand: KEBA
description: KeContact P20, P30, C/X Series
description: KeContact P20, P30, C/X Series (legacy UDP protocol)
capabilities: ["mA", "rfid"]
description: |
Es muss eine sogenannte UDP Funktion über den DIP Schalter 1.3 eingeschaltet (ON) werden. Die Installationsanleitung der Wallbox hilft hier weiter.

View file

@ -1,6 +1,6 @@
product:
brand: BMW
description: i Wallbox
description: i Wallbox (legacy UDP protocol)
capabilities: ["mA", "rfid"]
description: |
Es muss eine sogenannte UDP Funktion über den DIP Schalter 1.3 eingeschaltet (ON) werden. Die Installationsanleitung der Wallbox hilft hier weiter.