OCPP: use TxProfile and enable phase switching (#4946) (#4969)

This commit is contained in:
andig 2022-10-27 23:07:59 +02:00 • committed by GitHub
parent 5d7ca8f575
commit 59e5ad767c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 198 additions and 41 deletions

View file

@ -29,6 +29,7 @@ type OCPP struct {
current float64
meterValuesSample string
timeout time.Duration
phaseSwitching bool
}
func init() {
@ -86,10 +87,15 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) {
currentsG = c.currents
}
return decorateOCPP(c, powerG, totalEnergyG, currentsG), nil
var phasesS func(int) error
if c.phaseSwitching {
phasesS = c.phases1p3p
}
return decorateOCPP(c, powerG, totalEnergyG, currentsG, phasesS), nil
}
//go:generate go run ../cmd/tools/decorate.go -f decorateOCPP -b *OCPP -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)"
// go:generate go run ../cmd/tools/decorate.go -f decorateOCPP -b *OCPP -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)" -t "api.PhaseSwitcher,Phases1p3p,func(int) (error)"
// NewOCPP creates OCPP charger
func NewOCPP(id string, connector int, idtag string, meterValues string, meterInterval time.Duration, quirks bool, timeout time.Duration) (*OCPP, error) {
@ -180,6 +186,12 @@ func NewOCPP(id string, connector int, idtag string, meterValues string, meterIn
if val, err = strconv.Atoi(*opt.Value); err == nil {
meterSampleInterval = time.Duration(val) * time.Second
}
case ocpp.KeyConnectorSwitch3to1PhaseSupported:
var val bool
if val, err = strconv.ParseBool(*opt.Value); err == nil {
c.phaseSwitching = val
}
}
if err != nil {
@ -301,6 +313,7 @@ func (c *OCPP) Enable(enable bool) error {
rc <- err
}, c.idtag, func(request *core.RemoteStartTransactionRequest) {
request.ConnectorId = &c.connector
request.ChargingProfile = getTxChargingProfile(c.current, c.phases)
})
} else {
err = ocpp.Instance().RemoteStopTransaction(c.cp.ID(), func(resp *core.RemoteStopTransactionConfirmation, err error) {
@ -317,7 +330,7 @@ func (c *OCPP) Enable(enable bool) error {
return c.wait(err, rc)
}
func (c *OCPP) setChargingProfile(connectorid int, profile *types.ChargingProfile) error {
func (c *OCPP) setChargingProfile(connectorId int, profile *types.ChargingProfile) error {
c.log.TRACE.Printf("SetChargingProfileRequest: %+v (%+v)", profile, *profile.ChargingSchedule)
rc := make(chan error, 1)
@ -329,37 +342,40 @@ func (c *OCPP) setChargingProfile(connectorid int, profile *types.ChargingProfil
}
rc <- err
}, connectorid, profile)
}, connectorId, profile)
return c.wait(err, rc)
}
// setPeriod sets a single charging schedule period with given current and phases
func (c *OCPP) setPeriod(current float64, phases int) error {
period := types.NewChargingSchedulePeriod(0, current)
c.log.TRACE.Printf("current phases: %d, current current: %f", phases, current)
if phases > 0 {
period.NumberPhases = &phases
// updatePeriod sets a single charging schedule period with given current and phases
func (c *OCPP) updatePeriod(current float64, phases int) error {
// current period can only be updated if transaction is active
if enabled, err := c.Enabled(); err != nil || !enabled {
return err
}
// connectorID: 0 - profile will be applied to all connectors
err := c.setChargingProfile(0, getMaxChargingProfile(period))
c.log.TRACE.Printf("update period with phases: %d, current: %f", phases, current)
err := c.setChargingProfile(c.connector, getTxChargingProfile(current, phases))
if err != nil {
err = fmt.Errorf("failed to set charging profile: %w", err)
err = fmt.Errorf("set charging profile: %w", err)
}
return err
}
func getMaxChargingProfile(period types.ChargingSchedulePeriod) *types.ChargingProfile {
func getTxChargingProfile(current float64, phases int) *types.ChargingProfile {
period := types.NewChargingSchedulePeriod(0, current)
if phases != 0 {
period.NumberPhases = &phases
}
return &types.ChargingProfile{
ChargingProfileId: 1,
StackLevel: 1,
ChargingProfilePurpose: types.ChargingProfilePurposeChargePointMaxProfile,
ChargingProfileKind: types.ChargingProfileKindAbsolute,
StackLevel: 0,
ChargingProfilePurpose: types.ChargingProfilePurposeTxProfile,
ChargingProfileKind: types.ChargingProfileKindRelative,
ChargingSchedule: &types.ChargingSchedule{
StartSchedule: types.NewDateTime(time.Now().Add(-1 * time.Hour)),
ChargingRateUnit: types.ChargingRateUnitAmperes,
ChargingSchedulePeriod: []types.ChargingSchedulePeriod{period},
},
@ -375,7 +391,7 @@ var _ api.ChargerEx = (*OCPP)(nil)
// MaxCurrentMillis implements the api.ChargerEx interface
func (c *OCPP) MaxCurrentMillis(current float64) error {
err := c.setPeriod(current, c.phases)
err := c.updatePeriod(current, c.phases)
if err == nil {
c.current = current
}
@ -397,21 +413,18 @@ func (c *OCPP) currents() (float64, float64, float64, error) {
return c.cp.Currents()
}
// // TODO: Phases1p3p implements the api.PhaseSwitcher interface
// func (c *OCPP) Phases1p3p(phases int) error {
// if !c.phaseSwitchingSupported {
// return fmt.Errorf("phase switching is not supported by the charger")
// }
// Phases1p3p implements the api.PhaseSwitcher interface
func (c *OCPP) phases1p3p(phases int) error {
c.phases = phases
// err := c.setPeriod(c.current, phases)
// if err == nil {
// c.phases = phases
// }
// return err
// }
// NOTE: this will currently _never_ do anything since
// loadpoint disabled the charger before switching so
// updatePeriod will short-circuit
return c.updatePeriod(c.current, c.phases)
}
// // Identify implements the api.Identifier interface
// Unless charger uses vehicle ID as idTag in authorize.req it is not possible to implement this in ocpp1.6
// func (c *OCPP) Identify() (string, error) {
// return "", errors.New("not implemented")
// }

View file

@ -6,12 +6,12 @@ import (
"github.com/evcc-io/evcc/api"
)
func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error)) api.Charger {
func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() (float64, error), meterCurrent func() (float64, float64, float64, error), phaseSwitcher func(int) error) api.Charger {
switch {
case meter == nil && meterCurrent == nil && meterEnergy == nil:
case meter == nil && meterCurrent == nil && meterEnergy == nil && phaseSwitcher == nil:
return base
case meter != nil && meterCurrent == nil && meterEnergy == nil:
case meter != nil && meterCurrent == nil && meterEnergy == nil && phaseSwitcher == nil:
return &struct {
*OCPP
api.Meter
@ -22,7 +22,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
case meter == nil && meterCurrent == nil && meterEnergy != nil:
case meter == nil && meterCurrent == nil && meterEnergy != nil && phaseSwitcher == nil:
return &struct {
*OCPP
api.MeterEnergy
@ -33,7 +33,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
case meter != nil && meterCurrent == nil && meterEnergy != nil:
case meter != nil && meterCurrent == nil && meterEnergy != nil && phaseSwitcher == nil:
return &struct {
*OCPP
api.Meter
@ -48,7 +48,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
case meter == nil && meterCurrent != nil && meterEnergy == nil:
case meter == nil && meterCurrent != nil && meterEnergy == nil && phaseSwitcher == nil:
return &struct {
*OCPP
api.MeterCurrent
@ -59,7 +59,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
case meter != nil && meterCurrent != nil && meterEnergy == nil:
case meter != nil && meterCurrent != nil && meterEnergy == nil && phaseSwitcher == nil:
return &struct {
*OCPP
api.Meter
@ -74,7 +74,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
case meter == nil && meterCurrent != nil && meterEnergy != nil:
case meter == nil && meterCurrent != nil && meterEnergy != nil && phaseSwitcher == nil:
return &struct {
*OCPP
api.MeterCurrent
@ -89,7 +89,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
},
}
case meter != nil && meterCurrent != nil && meterEnergy != nil:
case meter != nil && meterCurrent != nil && meterEnergy != nil && phaseSwitcher == nil:
return &struct {
*OCPP
api.Meter
@ -107,6 +107,142 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func()
meterEnergy: meterEnergy,
},
}
case meter == nil && meterCurrent == nil && meterEnergy == nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.PhaseSwitcher
}{
OCPP: base,
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterCurrent == nil && meterEnergy == nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.Meter
api.PhaseSwitcher
}{
OCPP: base,
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter == nil && meterCurrent == nil && meterEnergy != nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.MeterEnergy
api.PhaseSwitcher
}{
OCPP: base,
MeterEnergy: &decorateOCPPMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterCurrent == nil && meterEnergy != nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.Meter
api.MeterEnergy
api.PhaseSwitcher
}{
OCPP: base,
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
MeterEnergy: &decorateOCPPMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter == nil && meterCurrent != nil && meterEnergy == nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.MeterCurrent
api.PhaseSwitcher
}{
OCPP: base,
MeterCurrent: &decorateOCPPMeterCurrentImpl{
meterCurrent: meterCurrent,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterCurrent != nil && meterEnergy == nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.Meter
api.MeterCurrent
api.PhaseSwitcher
}{
OCPP: base,
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
MeterCurrent: &decorateOCPPMeterCurrentImpl{
meterCurrent: meterCurrent,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter == nil && meterCurrent != nil && meterEnergy != nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.MeterCurrent
api.MeterEnergy
api.PhaseSwitcher
}{
OCPP: base,
MeterCurrent: &decorateOCPPMeterCurrentImpl{
meterCurrent: meterCurrent,
},
MeterEnergy: &decorateOCPPMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
case meter != nil && meterCurrent != nil && meterEnergy != nil && phaseSwitcher != nil:
return &struct {
*OCPP
api.Meter
api.MeterCurrent
api.MeterEnergy
api.PhaseSwitcher
}{
OCPP: base,
Meter: &decorateOCPPMeterImpl{
meter: meter,
},
MeterCurrent: &decorateOCPPMeterCurrentImpl{
meterCurrent: meterCurrent,
},
MeterEnergy: &decorateOCPPMeterEnergyImpl{
meterEnergy: meterEnergy,
},
PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{
phaseSwitcher: phaseSwitcher,
},
}
}
return nil
@ -135,3 +271,11 @@ type decorateOCPPMeterEnergyImpl struct {
func (impl *decorateOCPPMeterEnergyImpl) TotalEnergy() (float64, error) {
return impl.meterEnergy()
}
type decorateOCPPPhaseSwitcherImpl struct {
phaseSwitcher func(int) error
}
func (impl *decorateOCPPPhaseSwitcherImpl) Phases1p3p(phases int) error {
return impl.phaseSwitcher(phases)
}