Keba: add modbus tcp support with phase switching (#7485)
This commit is contained in:
parent
cba97832db
commit
13dd7d578e
11 changed files with 996 additions and 72 deletions
276
charger/keba-modbus.go
Normal file
276
charger/keba-modbus.go
Normal 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))
|
||||
}
|
||||
}
|
||||
593
charger/keba-modbus_decorators.go
Normal file
593
charger/keba-modbus_decorators.go
Normal 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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"`
|
||||
21
templates/definition/charger/keba-modbus.yaml
Normal file
21
templates/definition/charger/keba-modbus.yaml
Normal 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" . }}
|
||||
|
|
@ -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:
|
||||
17
templates/docs/charger/keba-modbus_0.yaml
Normal file
17
templates/docs/charger/keba-modbus_0.yaml
Normal 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
|
||||
17
templates/docs/charger/keba-modbus_1.yaml
Normal file
17
templates/docs/charger/keba-modbus_1.yaml
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue