Add PC Electric/ Garo charger (#2147)

This commit is contained in:
andig 2022-01-15 21:02:53 +01:00 • committed by GitHub
parent b258731e5f
commit 9517c61fe6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 661 additions and 1 deletions

View file

@ -16,7 +16,7 @@ Evcc is an extensible EV Charge Controller with PV integration implemented in [G
- simple and clean user interface
- multiple [chargers](https://docs.evcc.io/docs/devices/chargers):
- Open source: [openWB](https://openwb.de/), [EVSEWifi](https://www.evse-wifi.de) (includes smartWB)
- Other commercial: ABL eMH1, Alfen Eve, cFos PowerBrain, Daheimladen, go-eCharger, Heidelberg Energy Control, Innogy eBox, KEBA/BMW, NRGkick, Wallbe, Mobile Charger Connect, EEBUS (experimental)
- Other commercial: ABL eMH1, Alfen Eve, cFos PowerBrain, Daheimladen, go-eCharger, Heidelberg Energy Control, Innogy eBox, KEBA/BMW, NRGkick, PC Electric (includes Garo), Wallbe, Mobile Charger Connect, EEBUS (experimental)
- Build-your-own: Phoenix (includes ESL Walli), [SimpleEVSE](https://www.evse-wifi.de/produkt-schlagwort/simple-evse-wb/)
- Smart-Home outlets: FritzDECT, Shelly, Tasmota, TP-Link
- multiple [meters](https://docs.evcc.io/docs/devices/meters): ModBus (Eastron SDM, MPM3PM, SBC ALE3 and many more), Discovergy (using HTTP plugin), SMA Sunny Home Manager and Energy Meter, KOSTAL Smart Energy Meter (KSEM, EMxx), any Sunspec-compatible inverter or home battery devices (Fronius, SMA, SolarEdge, KOSTAL, STECA, E3DC, ...), Tesla PowerWall, LG ESS HOME

279
charger/pcelectric.go Normal file
View file

@ -0,0 +1,279 @@
package charger
import (
"fmt"
"net/http"
"strings"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/charger/pcelectric"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/request"
)
// PCElectric charger implementation
type PCElectric struct {
*request.Helper
log *util.Logger
uri string // http://garo2216247:8080/servlet
slaveIndex int // 0 = Master, 1..n Slave
meter string // <CENTRAL100|CENTRAL101|INTERNAL|EXTERNAL|TWIN>
lbmode bool // true/false (wird automatisch bestimmt)
serialNumber int // 1234567
}
func init() {
registry.Add("garo", NewPCElectricFromConfig)
registry.Add("pcelectric", NewPCElectricFromConfig)
}
//go:generate go run ../cmd/tools/decorate.go -f decoratePCE -b *PCElectric -r api.Charger -t "api.Meter,CurrentPower,func() (float64, error)" -t "api.MeterEnergy,TotalEnergy,func() (float64, error)" -t "api.MeterCurrent,Currents,func() (float64, float64, float64, error)"
// NewPCElectricFromConfig creates a PCElectric charger from generic config
func NewPCElectricFromConfig(other map[string]interface{}) (api.Charger, error) {
cc := struct {
URI string
SlaveIndex int
Meter string
}{
Meter: "CENTRAL100",
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
wb, err := NewPCElectric(util.DefaultScheme(cc.URI, "http"), cc.SlaveIndex, cc.Meter)
if err == nil && wb.slaveIndex == 0 { // Nur Master hat den Zähler...leider
var res pcelectric.MeterInfo
if err := wb.GetJSON(wb.meter, &res); err == nil && res.MeterSerial != "" {
return decoratePCE(wb, wb.currentPower, wb.totalEnergy, wb.currents), nil
}
wb.meter = ""
}
return wb, err
}
// NewPCElectric creates PCElectric charger
func NewPCElectric(uri string, slaveIndex int, meter string) (*PCElectric, error) {
log := util.NewLogger("pce")
uri = strings.TrimSuffix(strings.TrimRight(uri, "/"), "/servlet") + "/servlet/rest/chargebox"
wb := &PCElectric{
Helper: request.NewHelper(log),
log: log,
uri: uri,
slaveIndex: slaveIndex,
meter: strings.ToUpper(meter),
}
// Nur Master: lb Config auslesen.
// Ohne Loadbalancer: Steuerung über currentlimit
// Mit Loadbalander: Steuerung über loadBalancingFuse
var lbconfig pcelectric.LbConfig
uri = fmt.Sprintf("%s/lbconfig/false", wb.uri)
if err := wb.GetJSON(uri, &lbconfig); err == nil {
wb.lbmode = lbconfig.MasterLoadBalanced
wb.serialNumber = lbconfig.Slaves[wb.slaveIndex].SerialNumber
log.DEBUG.Printf("lbmode: %t serial: %d ", wb.lbmode, wb.serialNumber)
}
return wb, nil
}
// Status implements the api.Charger interface
func (wb *PCElectric) Status() (api.ChargeStatus, error) {
var chargeStatus int
var sessionStartTime int64
if wb.slaveIndex == 0 {
var status pcelectric.Status
uri := fmt.Sprintf("%s/status", wb.uri)
if err := wb.GetJSON(uri, &status); err != nil {
return api.StatusNone, err
}
chargeStatus = status.ChargeStatus
sessionStartTime = status.SessionStartTime
} else {
var status pcelectric.SlaveStatus
uri := fmt.Sprintf("%s/slaves/false", wb.uri)
if err := wb.GetJSON(uri, &status); err != nil {
return api.StatusNone, err
}
if wb.slaveIndex >= len(status) {
return api.StatusNone, nil
}
chargeStatus = status[wb.slaveIndex].ChargeStatus
sessionStartTime = status[wb.slaveIndex].SessionStartTime
}
wb.log.DEBUG.Printf("chargeStatus: %d", chargeStatus)
var res api.ChargeStatus
switch chargeStatus {
case 0x00, 0x10: // notconnected
res = api.StatusA
case 0x30: // connected
res = api.StatusB
case 0x40: // charging
res = api.StatusC
case 0x42, // chargepaused
0x50, // chargefinished
0x60: // chargecancelled
res = api.StatusB
case 0x90: // unavailable
if sessionStartTime > 0 {
res = api.StatusB
} else {
res = api.StatusF
}
case 0x95, // dcfault
0x96, // dchardwarefault
0x9A, // cpfault
0x9B: // cpshorted
res = api.StatusE
case 0x70, // overheat
0x80, // criticaltemperature
0x91, // reserved
0x9C, // remotedisabled
0x9D, // dlmfault
0xA0, // cablefault
0xA1,
0xA2, // lockingfault
0xA3,
0xA4, // contactorfault
0xA8, // rcdfault
0xF0, // wait
0xF1: // ventfault
res = api.StatusF
default: // generalfault
res = api.StatusF
}
return res, nil
}
// Enabled implements the api.Charger interface
func (wb *PCElectric) Enabled() (bool, error) {
var res pcelectric.Status
uri := fmt.Sprintf("%s/status", wb.uri)
err := wb.GetJSON(uri, &res)
if err == nil && res.PowerMode == "ON" {
return true, err
}
return false, err
}
// Enable implements the api.Charger interface
func (wb *PCElectric) Enable(enable bool) error {
if wb.slaveIndex > 0 {
return nil //Slave wird immer mit dem Master geschaltet!
}
// Master Only !!
mode := "ALWAYS_OFF"
if enable {
mode = "ALWAYS_ON"
}
uri := fmt.Sprintf("%s/mode/%s", wb.uri, mode)
req, err := request.New(http.MethodPost, uri, nil, request.JSONEncoding)
if err == nil {
_, err = wb.DoBody(req)
}
return err
}
func (wb *PCElectric) MinCurrent(current int64) error {
data := pcelectric.MinCurrentLimitStruct{
{
MinCurrentLimit: int(current), // default=6
SerialNumber: wb.serialNumber,
TwinSerial: -1,
},
}
uri := fmt.Sprintf("%s/mincurrentlimit", wb.uri)
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
_, err = wb.DoBody(req)
}
return err
}
// MaxCurrent implements the api.Charger interface
func (wb *PCElectric) MaxCurrent(current int64) error {
if wb.slaveIndex > 0 {
return nil //Slave wird immer mit dem Master geschaltet!
}
// Ohne Loadbalancer Regelung über currentlimit:
if !wb.lbmode {
data := pcelectric.ReducedIntervals{
ReducedIntervalsEnabled: true,
ReducedCurrentIntervals: []pcelectric.ReducedCurrentInterval{
{
SchemaId: 1,
Start: "00:00:00",
Stop: "24:00:00",
Weekday: 8,
ChargeLimit: int(current),
},
},
}
uri := fmt.Sprintf("%s/currentlimit", wb.uri)
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
_, err = wb.DoBody(req)
}
return err
}
// Mit Loadbalancer Regelung über lbconfig/LoadBalancingFuse
var data pcelectric.LbConfigShort
uri := fmt.Sprintf("%s/lbconfig/false", wb.uri)
if err := wb.GetJSON(uri, &data); err != nil {
return err
}
data.LoadBalancingFuse = int(current)
uri = fmt.Sprintf("%s/lbconfig", wb.uri)
req, err := request.New(http.MethodPost, uri, request.MarshalJSON(data), request.JSONEncoding)
if err == nil {
_, err = wb.DoBody(req)
}
return err
}
// CurrentPower implements the api.Meter interface W
func (wb *PCElectric) currentPower() (float64, error) {
l1, l2, l3, err := wb.currents()
return 230 * (l1 + l2 + l3), err
}
// TotalEnergy implements the api.MeterEnergy interface kwh
func (wb *PCElectric) totalEnergy() (float64, error) {
var res pcelectric.MeterInfo
uri := fmt.Sprintf("%s/meterinfo/%s", wb.uri, wb.meter)
err := wb.GetJSON(uri, &res)
return float64(res.AccEnergy) / 1e3, err
}
// Currents implements the api.MeterCurrents interface A
func (wb *PCElectric) currents() (float64, float64, float64, error) {
var res pcelectric.MeterInfo
uri := fmt.Sprintf("%s/meterinfo/%s", wb.uri, wb.meter)
err := wb.GetJSON(uri, &res)
return float64(res.Phase1Current) / 10, float64(res.Phase2Current) / 10, float64(res.Phase3Current) / 10, err
}

244
charger/pcelectric/types.go Normal file
View file

@ -0,0 +1,244 @@
package pcelectric
// /servlet/rest/chargebox/status
type Status struct {
SerialNumber int64 // 2216247,
Connector string // "NOT_CONNECTED",
Mode string // "ALWAYS_ON",
CurrentLimit int // 11,
FactoryCurrentLimit int // 32,
SwitchCurrentLimit int // 32,
PowerMode string // "OFF",
CurrentChargingCurrent int // -1,
CurrentChargingPower int // -1,
AccSessionEnergy int // 0,
SessionStartTime int64 // 1641489661842
ChargeboxTime string // "22:25"
AccSessionMillis int64 // 0,
LatestReading int // 0,
ChargeStatus int // 0,
NrOfPhases int // 1,
MainCharger struct {
Reference string // "Garage",
SerialNumber int // 2216247,
LastContact int64 // 1640781615305,
Online bool // false,
LoadBalanced bool // true,
Phase int // 16,
ProductId int // 121,
MeterStatus int // 1,
MeterSerial string // "",
ChargeStatus int // 0,
PilotLevel int // 6,
AccEnergy int // -1,
Connector string // "NOT_CONNECTED",
AccSessionEnergy int // 0,
SessionStartValue int64 // -1,
AccSessionMillis int64 // 0,
CurrentChargingCurrent int // -1,
CurrentChargingPower int // 0,
NrOfPhases int // 1,
TwinSerial int // -1,
}
TwinCharger interface{}
}
// /servlet/rest/chargebox/slaves/false
type SlaveStatus []struct {
Reference string // "Garage",
SerialNumber int // 2216247,
LastContact int64 // 1640781615305,
Online bool // false,
LoadBalanced bool // true,
Phase int // 16,
ProductId int // 121,
MeterStatus int // 1,
MeterSerial string // "",
ChargeStatus int // 0,
PilotLevel int // 6,
AccEnergy int // -1,
FirmwareVersion int // 7
FirmwareRevision int // 9
WifiCardStatus int // 2
Connector string // "NOT_CONNECTED",
AccSessionEnergy int // 0,
SessionStartValue int // -1,
AccSessionMillis int64 // 0,
SessionStartTime int64 // 1641136092090
CurrentChargingCurrent int // -1,
CurrentChargingPower int // 0,
NrOfPhases int // 1,
TwinSerial int // -1,
CableLockMode int // 0
MinCurrentLimit int // 6
DipSwitchSettings int // 8188
}
type ReducedIntervals struct {
ReducedIntervalsEnabled bool `json:"reducedIntervalsEnabled"`
ReducedCurrentIntervals []ReducedCurrentInterval `json:"reducedCurrentIntervals,omitempty"`
}
type ReducedCurrentInterval struct {
SchemaId int `json:"schemaId"`
Start string `json:"start"`
Stop string `json:"stop"`
Weekday int `json:"weekday"`
ChargeLimit int `json:"chargeLimit"`
}
// /servlet/rest/chargebox/meterinfo/CENTRAL100
type MeterInfo struct {
Success int // 0,
AccEnergy int64 // 169100,
Phase1Current int // 158,
Phase2Current int // 157,
Phase3Current int // 156,
Phase1InstPower int // 3,
Phase2InstPower int // 3,
Phase3InstPower int // 3,
ReadTime int64 // 1640783279023,
GridNetType string // "UNKNOWN",
MeterSerial string // "116223V",
Type int // 341,
ApparentPower int // 9
}
// /servlet/rest/chargebox/lbconfig/false
type LbConfigShort struct {
LoadBalancingFuse int `json:"loadBalancingFuse"` // 32
LoadBalancingPower int `json:"loadBalancingPower"` // 0
LoadBalancingFuse101 int `json:"loadBalancingFuse101"` // 32
LoadBalancingPower101 int `json:"loadBalancingPower101"` // 0
MasterPhase int `json:"masterPhase"` // 16
MasterLoadBalanced bool `json:"masterLoadBalanced"` // true
}
type LbConfig struct {
LoadBalancingFuse int // 16
LoadBalancingPower int // 0
LoadBalancingFuse101 int // 16
LoadBalancingPower101 int // 0
MasterPhase int // 16
MasterLoadBalanced bool // true
Slaves []struct {
Reference string // "Garage"
SerialNumber int // 2216247
LastContact int64 // 1640970181816
Online bool // false
LoadBalanced bool // true
Phase int // 16
ProductId int // 121
MeterStatus int // 1
MeterSerial string // ""
ChargeStatus int // 144
PilotLevel int // 6
AccEnergy int // -1
FirmwareVersion int // 7
FirmwareRevision int // 9
WifiCardStatus int // 2
Connector string // "UNAVAILABLE"
AccSessionEnergy int // 0
SessionStartValue int // -1
AccSessionMillis int64 // 174723
SessionStartTime int64 // 1640957660208
CurrentChargingCurrent int // -1
CurrentChargingPower int // 0
NrOfPhases int // 1
TwinSerial int // -1
CableLockMode int // 0
MinCurrentLimit int // 6
DipSwitchSettings int // 8188
}
}
type Config struct {
OcppConnected bool // false
MaxChargeCurrent int // 32
ProductId string // "81"
ProgramVersion string // "7.9"
FirmwareVersion int // 7
FirmwareRevision int // 9
SmallFirmwareVersion int // 2
SmallFirmwareRevision int // 15
LargeFirmwareVersion int // 7
LargeFirmwareRevision int // 9
LbVersion2 bool // true
SerialNumber int // 2216247
MeterSerialNumber string // ""
MeterType int // 0
FactoryChargeLimit int // 32
SwitchChargeLimit int // 32
RfidReaderPresent bool // true
RfidMode string // "RFID_WIFI"
PowerbackupStatus int // 2
LastTemperature int // 25
WarningTemperature int // 65
CutoffTemperature int // 70
ReducedIntervalsEnabled bool // true
ReducedCurrentIntervals []struct {
SchemaId int // 1
Start string // "00:00:00"
Stop string // "00:00:00"
Weekday int // 8
ChargeLimit int // 6
}
SoftwareVersion int // 185
AvailableVersion int // 185
UpdateUrl string // "http%3A%2F%2F85.11.39.104%2Fchargebox_185.tgz"
NetworkMode int // 1
NetworkType int // 0
NetworkSSID string // "SchlockeNet"
WebNetworkPassword string // ""
NetworkAPChannel int // 6
EthNetworkMode int // 0
GcConfigTimestamp int64 // null
GcloudActivated bool // false
GcActivatedFrom int64 // null
EthGateway string // ""
EthDNS string // ""
EthIP string // ""
EthMask int // 24
LocalLoadBalanced bool // false
GroupLoadBalanced bool // true
GroupLoadBalanced101 bool // false
EnergyReportEnabled bool // false
Master bool // true
Timezone string // null
SlaveList []struct {
Reference string // Garage
SerialNumber int // 2216247
LastContact int64 // 1640960502344
Online bool // false
LoadBalanced bool // true
Phase int // 16
ProductId int // 121
MeterStatus int // 1
MeterSerial string // ""
ChargeStatus int // 144
PilotLevel int // 6
AccEnergy int // -1
FirmwareVersion int // 7
FirmwareRevision int // 9
WifiCardStatus int // 2
Connector string // "UNAVAILABLE"
AccSessionEnergy int // 0
SessionStartValue int // -1
AccSessionMillis int // 5635
SessionStartTime int64 // 1640957660208
CurrentChargingCurrent int // -1
CurrentChargingPower int // 0
NrOfPhases int // 1
TwinSerial int // -1
CableLockMode int // 0
MinCurrentLimit int // 6
DipSwitchSettings int // 8188
}
}
type MinCurrentLimitStruct []struct {
MinCurrentLimit int `json:"minCurrentLimit"`
SerialNumber int `json:"serialNumber"`
TwinSerial int `json:"twinSerial"` // -1
}

View file

@ -0,0 +1,137 @@
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 decoratePCE(base *PCElectric, meter func() (float64, error), meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error)) api.Charger {
switch {
case meter == nil && meterCurrent == nil && meterEnergy == nil:
return base
case meter != nil && meterCurrent == nil && meterEnergy == nil:
return &struct {
*PCElectric
api.Meter
}{
PCElectric: base,
Meter: &decoratePCEMeterImpl{
meter: meter,
},
}
case meter == nil && meterCurrent == nil && meterEnergy != nil:
return &struct {
*PCElectric
api.MeterEnergy
}{
PCElectric: base,
MeterEnergy: &decoratePCEMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter != nil && meterCurrent == nil && meterEnergy != nil:
return &struct {
*PCElectric
api.Meter
api.MeterEnergy
}{
PCElectric: base,
Meter: &decoratePCEMeterImpl{
meter: meter,
},
MeterEnergy: &decoratePCEMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter == nil && meterCurrent != nil && meterEnergy == nil:
return &struct {
*PCElectric
api.MeterCurrent
}{
PCElectric: base,
MeterCurrent: &decoratePCEMeterCurrentImpl{
meterCurrent: meterCurrent,
},
}
case meter != nil && meterCurrent != nil && meterEnergy == nil:
return &struct {
*PCElectric
api.Meter
api.MeterCurrent
}{
PCElectric: base,
Meter: &decoratePCEMeterImpl{
meter: meter,
},
MeterCurrent: &decoratePCEMeterCurrentImpl{
meterCurrent: meterCurrent,
},
}
case meter == nil && meterCurrent != nil && meterEnergy != nil:
return &struct {
*PCElectric
api.MeterCurrent
api.MeterEnergy
}{
PCElectric: base,
MeterCurrent: &decoratePCEMeterCurrentImpl{
meterCurrent: meterCurrent,
},
MeterEnergy: &decoratePCEMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
case meter != nil && meterCurrent != nil && meterEnergy != nil:
return &struct {
*PCElectric
api.Meter
api.MeterCurrent
api.MeterEnergy
}{
PCElectric: base,
Meter: &decoratePCEMeterImpl{
meter: meter,
},
MeterCurrent: &decoratePCEMeterCurrentImpl{
meterCurrent: meterCurrent,
},
MeterEnergy: &decoratePCEMeterEnergyImpl{
meterEnergy: meterEnergy,
},
}
}
return nil
}
type decoratePCEMeterImpl struct {
meter func() (float64, error)
}
func (impl *decoratePCEMeterImpl) CurrentPower() (float64, error) {
return impl.meter()
}
type decoratePCEMeterCurrentImpl struct {
meterCurrent func() (float64, float64, float64, error)
}
func (impl *decoratePCEMeterCurrentImpl) Currents() (float64, float64, float64, error) {
return impl.meterCurrent()
}
type decoratePCEMeterEnergyImpl struct {
meterEnergy func() (float64, error)
}
func (impl *decoratePCEMeterEnergyImpl) TotalEnergy() (float64, error) {
return impl.meterEnergy()
}