diff --git a/charger/ocpp.go b/charger/ocpp.go index 54098d30a2..82117d5921 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -24,18 +24,17 @@ type OCPP struct { log *util.Logger conn *ocpp.Connector idtag string - enabled bool phases int current float64 meterValuesSample string timeout time.Duration phaseSwitching bool - autoStart, noStop bool + remoteStart bool chargingRateUnit types.ChargingRateUnitType lp loadpoint.API } -const defaultIdTag = "evcc" +const defaultIdTag = "evcc" // RemoteStartTransaction only func init() { registry.Add("ocpp", NewOCPPFromConfig) @@ -54,8 +53,9 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) { BootNotification *bool GetConfiguration *bool ChargingRateUnit string - AutoStart bool - NoStop bool + AutoStart bool // deprecated, to be removed + NoStop bool // deprecated, to be removed + RemoteStart bool }{ Connector: 1, IdTag: defaultIdTag, @@ -73,7 +73,7 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) { c, err := NewOCPP(cc.StationId, cc.Connector, cc.IdTag, cc.MeterValues, cc.MeterInterval, - boot, noConfig, cc.AutoStart, cc.NoStop, + boot, noConfig, cc.RemoteStart, cc.ConnectTimeout, cc.Timeout, cc.ChargingRateUnit) if err != nil { return c, err @@ -94,20 +94,35 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) { currentsG = c.currents } + var voltagesG func() (float64, float64, float64, error) + if c.hasMeasurement(types.MeasurandVoltage + ".L3") { + voltagesG = c.voltages + } + var phasesS func(int) error if c.phaseSwitching { phasesS = c.phases1p3p } - return decorateOCPP(c, powerG, totalEnergyG, currentsG, phasesS), nil + var socG func() (float64, error) + if c.hasMeasurement(types.MeasueandSoC) { // typo in ocpp-go + socG = c.soc + } + + //var currentG func() (float64, error) + //if c.hasMeasurement(types.MeasurandCurrentOffered) { + // currentG = c.getMaxCurrent + //} + + return decorateOCPP(c, powerG, totalEnergyG, currentsG, voltagesG, phasesS, socG), 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.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) 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.PhaseCurrents,Currents,func() (float64, float64, float64, error)" -t "api.PhaseVoltages,Voltages,func() (float64, float64, float64, error)" -t "api.PhaseSwitcher,Phases1p3p,func(int) error" -t "api.Battery,Soc,func() (float64, error)" // NewOCPP creates OCPP charger func NewOCPP(id string, connector int, idtag string, meterValues string, meterInterval time.Duration, - boot, noConfig, autoStart, noStop bool, + boot, noConfig, remoteStart bool, connectTimeout, timeout time.Duration, chargingRateUnit string, ) (*OCPP, error) { @@ -135,12 +150,11 @@ func NewOCPP(id string, connector int, idtag string, } c := &OCPP{ - log: log, - conn: conn, - idtag: idtag, - autoStart: autoStart, - noStop: noStop, - timeout: timeout, + log: log, + conn: conn, + idtag: idtag, + remoteStart: remoteStart, + timeout: timeout, } c.log.DEBUG.Printf("waiting for chargepoint: %v", connectTimeout) @@ -232,7 +246,7 @@ func NewOCPP(id string, connector int, idtag string, } case ocpp.KeyChargingScheduleAllowedChargingRateUnit: - if *opt.Value == "W" { + if *opt.Value == "W" || *opt.Value == "Power" { c.chargingRateUnit = types.ChargingRateUnitWatts } } @@ -329,92 +343,56 @@ func (c *OCPP) wait(err error, rc chan error) error { // Status implements the api.Charger interface func (c *OCPP) Status() (api.ChargeStatus, error) { + if c.remoteStart { + needtxn, err := c.conn.NeedsTransaction() + if err != nil { + return api.StatusNone, err + } + + if needtxn { + // lock the cable by starting remote transaction after vehicle connected + if err := c.initTransaction(); err != nil { + return api.StatusNone, err + } + } + } + return c.conn.Status() } // Enabled implements the api.Charger interface func (c *OCPP) Enabled() (bool, error) { - return c.enabled, nil -} - -func (c *OCPP) Enable(enable bool) error { - txn, err := c.conn.TransactionID() - if err != nil { - return err - } - - if c.autoStart || (c.noStop && txn > 0) { - // if there is no transaction running, this is a no-op - if txn > 0 { - err = c.enableProfile(enable) - } - } else { - err = c.enableRemote(enable) - } + current, err := c.getCurrent() - if err == nil { - c.enabled = enable - } - - return err + return current > 0, err } -// enableProfile pauses/resumes existing transaction by profile update -func (c *OCPP) enableProfile(enable bool) error { +func (c *OCPP) Enable(enable bool) error { var current float64 if enable { current = c.current } - return c.updatePeriod(current) + return c.setCurrent(current) } -// enableRemote starts and terminates transaction by RemoteStart/Stop -func (c *OCPP) enableRemote(enable bool) error { - txn, err := c.conn.TransactionID() - if err != nil { - return err - } - +func (c *OCPP) initTransaction() error { rc := make(chan error, 1) - if enable { - if txn > 0 { - // we have the transaction id, treat as enabled - return nil - } - - err = ocpp.Instance().RemoteStartTransaction(c.conn.ChargePoint().ID(), func(resp *core.RemoteStartTransactionConfirmation, err error) { - if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted { - err = errors.New(string(resp.Status)) - } - - rc <- err - }, c.effectiveIdTag(), func(request *core.RemoteStartTransactionRequest) { - connector := c.conn.ID() - request.ConnectorId = &connector - request.ChargingProfile = c.getTxChargingProfile(c.current, 0) - }) - } else { - if txn == 0 { - // we have no transaction id, treat as disabled - return nil + err := ocpp.Instance().RemoteStartTransaction(c.conn.ChargePoint().ID(), func(resp *core.RemoteStartTransactionConfirmation, err error) { + if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted { + err = errors.New(string(resp.Status)) } - err = ocpp.Instance().RemoteStopTransaction(c.conn.ChargePoint().ID(), func(resp *core.RemoteStopTransactionConfirmation, err error) { - if err == nil && resp != nil && resp.Status != types.RemoteStartStopStatusAccepted { - err = errors.New(string(resp.Status)) - } - - rc <- err - }, txn) - } + rc <- err + }, c.effectiveIdTag(), func(request *core.RemoteStartTransactionRequest) { + connector := c.conn.ID() + request.ConnectorId = &connector + }) return c.wait(err, rc) } func (c *OCPP) setChargingProfile(profile *types.ChargingProfile) error { - connector := c.conn.ID() - rc := make(chan error, 1) err := ocpp.Instance().SetChargingProfile(c.conn.ChargePoint().ID(), func(resp *smartcharging.SetChargingProfileConfirmation, err error) { if err == nil && resp != nil && resp.Status != smartcharging.ChargingProfileStatusAccepted { @@ -422,34 +400,53 @@ func (c *OCPP) setChargingProfile(profile *types.ChargingProfile) error { } rc <- err - }, connector, profile) + }, c.conn.ID(), profile) return c.wait(err, rc) } -// updatePeriod sets a single charging schedule period with given current -func (c *OCPP) updatePeriod(current float64) error { - txn, err := c.conn.TransactionID() +// setCurrent sets the TxDefaultChargingProfile with given current +func (c *OCPP) setCurrent(current float64) error { + err := c.setChargingProfile(c.createTxDefaultChargingProfile(math.Trunc(10*current) / 10)) if err != nil { - return err + err = fmt.Errorf("set charging profile: %w", err) } - // current period can only be updated if transaction is active - if txn == 0 { - return nil - } + return err +} - current = math.Trunc(10*current) / 10 +// getCurrent returns the internal current offered by the chargepoint +func (c *OCPP) getCurrent() (float64, error) { + var current float64 - err = c.setChargingProfile(c.getTxChargingProfile(current, txn)) - if err != nil { - err = fmt.Errorf("set charging profile: %w", err) + if c.hasMeasurement(types.MeasurandCurrentOffered) { + return c.getMaxCurrent() } - return err + // fallback to GetCompositeSchedule request + rc := make(chan error, 1) + err := ocpp.Instance().GetCompositeSchedule(c.conn.ChargePoint().ID(), func(resp *smartcharging.GetCompositeScheduleConfirmation, err error) { + if err == nil && resp != nil && resp.Status != smartcharging.GetCompositeScheduleStatusAccepted { + err = errors.New(string(resp.Status)) + } + + if err == nil { + if resp.ChargingSchedule != nil && len(resp.ChargingSchedule.ChargingSchedulePeriod) > 0 { + current = resp.ChargingSchedule.ChargingSchedulePeriod[0].Limit + } else { + err = fmt.Errorf("invalid ChargingSchedule") + } + } + + rc <- err + }, c.conn.ID(), 1) + + err = c.wait(err, rc) + + return current, err } -func (c *OCPP) getTxChargingProfile(current float64, transactionId int) *types.ChargingProfile { +func (c *OCPP) createTxDefaultChargingProfile(current float64) *types.ChargingProfile { phases := c.phases period := types.NewChargingSchedulePeriod(0, current) if c.chargingRateUnit == types.ChargingRateUnitWatts { @@ -469,10 +466,9 @@ func (c *OCPP) getTxChargingProfile(current float64, transactionId int) *types.C } return &types.ChargingProfile{ - ChargingProfileId: 1, - TransactionId: transactionId, + ChargingProfileId: 0, StackLevel: 0, - ChargingProfilePurpose: types.ChargingProfilePurposeTxProfile, + ChargingProfilePurpose: types.ChargingProfilePurposeTxDefaultProfile, ChargingProfileKind: types.ChargingProfileKindRelative, ChargingSchedule: &types.ChargingSchedule{ ChargingRateUnit: c.chargingRateUnit, @@ -490,38 +486,52 @@ var _ api.ChargerEx = (*OCPP)(nil) // MaxCurrentMillis implements the api.ChargerEx interface func (c *OCPP) MaxCurrentMillis(current float64) error { - err := c.updatePeriod(current) + err := c.setCurrent(current) if err == nil { c.current = current } return err } -// CurrentPower implements the api.Meter interface +// getMaxCurrent implements the api.CurrentGetter interface +func (c *OCPP) getMaxCurrent() (float64, error) { + return c.conn.GetMaxCurrent() +} + +// currentPower implements the api.Meter interface func (c *OCPP) currentPower() (float64, error) { return c.conn.CurrentPower() } -// TotalEnergy implements the api.MeterTotal interface +// totalEnergy implements the api.MeterTotal interface func (c *OCPP) totalEnergy() (float64, error) { return c.conn.TotalEnergy() } -// Currents implements the api.PhaseCurrents interface +// currents implements the api.PhaseCurrents interface func (c *OCPP) currents() (float64, float64, float64, error) { return c.conn.Currents() } -// Phases1p3p implements the api.PhaseSwitcher interface +// voltages implements the api.PhaseVoltages interface +func (c *OCPP) voltages() (float64, float64, float64, error) { + return c.conn.Voltages() +} + +// phases1p3p implements the api.PhaseSwitcher interface func (c *OCPP) phases1p3p(phases int) error { c.phases = phases - // NOTE: this will currently _never_ do anything since - // loadpoint disabled the charger before switching so - // updatePeriod will short-circuit - return c.updatePeriod(c.current) + return c.setCurrent(c.current) +} + +// soc implements the api.Battery interface +func (c *OCPP) soc() (float64, error) { + return c.conn.Soc() } +var _ api.Identifier = (*OCPP)(nil) + // Identify implements the api.Identifier interface func (c *OCPP) Identify() (string, error) { return c.conn.IdTag(), nil diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index 463ef63ab8..8f6943c13b 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -158,12 +158,45 @@ func (conn *Connector) Status() (api.ChargeStatus, error) { return res, nil } +// NeedsTransaction checks if an initial RemoteStart of a transaction is required +func (conn *Connector) NeedsTransaction() (bool, error) { + if !conn.cp.Connected() { + return false, api.ErrTimeout + } + + conn.mu.Lock() + defer conn.mu.Unlock() + + return conn.txnId == 0 && conn.status.Status == core.ChargePointStatusPreparing, nil +} + // isMeterTimeout checks if meter values are outdated. // Must only be called while holding lock. func (conn *Connector) isMeterTimeout() bool { return conn.timeout > 0 && conn.clock.Since(conn.meterUpdated) > conn.timeout } +func (conn *Connector) GetMaxCurrent() (float64, error) { + if !conn.cp.Connected() { + return 0, api.ErrTimeout + } + + conn.mu.Lock() + defer conn.mu.Unlock() + + // fallthrough for last value on timeout when no transaction is running + if conn.txnId != 0 && conn.isMeterTimeout() { + return 0, api.ErrTimeout + } + + if m, ok := conn.measurements[types.MeasurandCurrentOffered]; ok { + f, err := strconv.ParseFloat(m.Value, 64) + return scale(f, m.Unit) / 1e3, err + } + + return 0, api.ErrNotAvailable +} + var _ api.Meter = (*Connector)(nil) func (conn *Connector) CurrentPower() (float64, error) { @@ -174,7 +207,7 @@ func (conn *Connector) CurrentPower() (float64, error) { conn.mu.Lock() defer conn.mu.Unlock() - // zero value on timeout when not charging + // zero value on timeout when no transaction is running if conn.isMeterTimeout() { if conn.txnId != 0 { return 0, api.ErrTimeout @@ -201,7 +234,7 @@ func (conn *Connector) TotalEnergy() (float64, error) { conn.mu.Lock() defer conn.mu.Unlock() - // fallthrough for last value on timeout when not charging + // fallthrough for last value on timeout when no transaction is running if conn.txnId != 0 && conn.isMeterTimeout() { return 0, api.ErrTimeout } @@ -214,6 +247,27 @@ func (conn *Connector) TotalEnergy() (float64, error) { return 0, api.ErrNotAvailable } +func (conn *Connector) Soc() (float64, error) { + if !conn.cp.Connected() { + return 0, api.ErrTimeout + } + + conn.mu.Lock() + defer conn.mu.Unlock() + + // fallthrough for last value on timeout when no transaction is running + if conn.txnId != 0 && conn.isMeterTimeout() { + return 0, api.ErrTimeout + } + + if m, ok := conn.measurements[types.MeasueandSoC]; ok { // typo in ocpp-go + f, err := strconv.ParseFloat(m.Value, 64) + return scale(f, m.Unit) / 1e3, err + } + + return 0, api.ErrNotAvailable +} + func scale(f float64, scale types.UnitOfMeasure) float64 { switch { case strings.HasPrefix(string(scale), "k"): @@ -239,7 +293,7 @@ func (conn *Connector) Currents() (float64, float64, float64, error) { conn.mu.Lock() defer conn.mu.Unlock() - // zero value on timeout when not charging + // zero value on timeout when no transaction is running if conn.isMeterTimeout() { if conn.txnId != 0 { return 0, 0, 0, api.ErrTimeout @@ -266,3 +320,35 @@ func (conn *Connector) Currents() (float64, float64, float64, error) { return currents[0], currents[1], currents[2], nil } + +func (conn *Connector) Voltages() (float64, float64, float64, error) { + if !conn.cp.Connected() { + return 0, 0, 0, api.ErrTimeout + } + + conn.mu.Lock() + defer conn.mu.Unlock() + + // fallthrough for last value on timeout when no transaction is running + if conn.txnId != 0 && conn.isMeterTimeout() { + return 0, 0, 0, api.ErrTimeout + } + + voltages := make([]float64, 0, 3) + + for phase := 1; phase <= 3; phase++ { + m, ok := conn.measurements[getPhaseKey(types.MeasurandVoltage, phase)] + if !ok { + return 0, 0, 0, api.ErrNotAvailable + } + + f, err := strconv.ParseFloat(m.Value, 64) + if err != nil { + return 0, 0, 0, fmt.Errorf("invalid voltage for phase %d: %w", phase, err) + } + + voltages = append(voltages, scale(f, m.Unit)) + } + + return voltages[0], voltages[1], voltages[2], nil +} diff --git a/charger/ocpp_decorators.go b/charger/ocpp_decorators.go index 9dfcba05fc..0f84169b13 100644 --- a/charger/ocpp_decorators.go +++ b/charger/ocpp_decorators.go @@ -6,12 +6,12 @@ import ( "github.com/evcc-io/evcc/api" ) -func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseSwitcher func(int) error) api.Charger { +func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() (float64, error), phaseCurrents func() (float64, float64, float64, error), phaseVoltages func() (float64, float64, float64, error), phaseSwitcher func(int) error, battery func() (float64, error)) api.Charger { switch { - case meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil: + case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: return base - case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil: + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: return &struct { *OCPP api.Meter @@ -22,7 +22,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() }, } - case meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil: + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: return &struct { *OCPP api.MeterEnergy @@ -33,7 +33,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() }, } - case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil: + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: return &struct { *OCPP api.Meter @@ -48,7 +48,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() }, } - case meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil: + case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: return &struct { *OCPP api.PhaseCurrents @@ -59,7 +59,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() }, } - case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil: + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: return &struct { *OCPP api.Meter @@ -74,7 +74,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() }, } - case meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil: + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: return &struct { *OCPP api.MeterEnergy @@ -89,7 +89,7 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() }, } - case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil: + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: return &struct { *OCPP api.Meter @@ -108,55 +108,996 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() }, } - case meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil: + case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: return &struct { *OCPP + api.PhaseVoltages + }{ + OCPP: base, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.MeterEnergy + api.PhaseVoltages + }{ + OCPP: base, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.MeterEnergy + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.MeterEnergy + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.MeterEnergy + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.PhaseSwitcher + }{ + OCPP: base, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Meter + api.PhaseSwitcher + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.MeterEnergy + api.PhaseSwitcher + }{ + OCPP: base, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == 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 battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.PhaseCurrents + api.PhaseSwitcher + }{ + OCPP: base, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Meter + api.PhaseCurrents + api.PhaseSwitcher + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.MeterEnergy + api.PhaseCurrents + api.PhaseSwitcher + }{ + OCPP: base, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Meter + api.MeterEnergy + api.PhaseCurrents + api.PhaseSwitcher + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.MeterEnergy + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.MeterEnergy + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.PhaseCurrents + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.PhaseCurrents + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.MeterEnergy + api.PhaseCurrents + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery == nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Meter + api.MeterEnergy + api.PhaseCurrents + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + } + + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + } + + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.MeterEnergy + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.MeterEnergy + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + } + + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.PhaseCurrents + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + } + + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.PhaseCurrents + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + } + + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.MeterEnergy + api.PhaseCurrents + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + } + + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.MeterEnergy + api.PhaseCurrents + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + } + + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.MeterEnergy + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.MeterEnergy + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.MeterEnergy + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher == nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.MeterEnergy + api.PhaseCurrents + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, + } + + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.PhaseSwitcher + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.PhaseSwitcher + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.MeterEnergy + api.PhaseSwitcher + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.MeterEnergy + api.PhaseSwitcher + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.PhaseCurrents + api.PhaseSwitcher + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.PhaseCurrents + api.PhaseSwitcher + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.MeterEnergy + api.PhaseCurrents api.PhaseSwitcher }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages == nil: + return &struct { + *OCPP + api.Battery + api.Meter + api.MeterEnergy + api.PhaseCurrents + api.PhaseSwitcher + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, + Meter: &decorateOCPPMeterImpl{ + meter: meter, + }, + MeterEnergy: &decorateOCPPMeterEnergyImpl{ + meterEnergy: meterEnergy, + }, + PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ + phaseCurrents: phaseCurrents, + }, + PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ + phaseSwitcher: phaseSwitcher, + }, + } + + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: + return &struct { + *OCPP + api.Battery + api.PhaseSwitcher + api.PhaseVoltages + }{ + OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } - case meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil: + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: return &struct { *OCPP + api.Battery api.Meter api.PhaseSwitcher + api.PhaseVoltages }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, Meter: &decorateOCPPMeterImpl{ meter: meter, }, PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } - case meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil: + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: return &struct { *OCPP + api.Battery api.MeterEnergy api.PhaseSwitcher + api.PhaseVoltages }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, MeterEnergy: &decorateOCPPMeterEnergyImpl{ meterEnergy: meterEnergy, }, PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } - case meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil: + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents == nil && phaseSwitcher != nil && phaseVoltages != nil: return &struct { *OCPP + api.Battery api.Meter api.MeterEnergy api.PhaseSwitcher + api.PhaseVoltages }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, Meter: &decorateOCPPMeterImpl{ meter: meter, }, @@ -166,31 +1107,47 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } - case meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil: + case battery != nil && meter == nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: return &struct { *OCPP + api.Battery api.PhaseCurrents api.PhaseSwitcher + api.PhaseVoltages }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, PhaseCurrents: &decorateOCPPPhaseCurrentsImpl{ phaseCurrents: phaseCurrents, }, PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } - case meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil: + case battery != nil && meter != nil && meterEnergy == nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: return &struct { *OCPP + api.Battery api.Meter api.PhaseCurrents api.PhaseSwitcher + api.PhaseVoltages }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, Meter: &decorateOCPPMeterImpl{ meter: meter, }, @@ -200,16 +1157,24 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } - case meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil: + case battery != nil && meter == nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: return &struct { *OCPP + api.Battery api.MeterEnergy api.PhaseCurrents api.PhaseSwitcher + api.PhaseVoltages }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, MeterEnergy: &decorateOCPPMeterEnergyImpl{ meterEnergy: meterEnergy, }, @@ -219,17 +1184,25 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } - case meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil: + case battery != nil && meter != nil && meterEnergy != nil && phaseCurrents != nil && phaseSwitcher != nil && phaseVoltages != nil: return &struct { *OCPP + api.Battery api.Meter api.MeterEnergy api.PhaseCurrents api.PhaseSwitcher + api.PhaseVoltages }{ OCPP: base, + Battery: &decorateOCPPBatteryImpl{ + battery: battery, + }, Meter: &decorateOCPPMeterImpl{ meter: meter, }, @@ -242,12 +1215,23 @@ func decorateOCPP(base *OCPP, meter func() (float64, error), meterEnergy func() PhaseSwitcher: &decorateOCPPPhaseSwitcherImpl{ phaseSwitcher: phaseSwitcher, }, + PhaseVoltages: &decorateOCPPPhaseVoltagesImpl{ + phaseVoltages: phaseVoltages, + }, } } return nil } +type decorateOCPPBatteryImpl struct { + battery func() (float64, error) +} + +func (impl *decorateOCPPBatteryImpl) Soc() (float64, error) { + return impl.battery() +} + type decorateOCPPMeterImpl struct { meter func() (float64, error) } @@ -279,3 +1263,11 @@ type decorateOCPPPhaseSwitcherImpl struct { func (impl *decorateOCPPPhaseSwitcherImpl) Phases1p3p(p0 int) error { return impl.phaseSwitcher(p0) } + +type decorateOCPPPhaseVoltagesImpl struct { + phaseVoltages func() (float64, float64, float64, error) +} + +func (impl *decorateOCPPPhaseVoltagesImpl) Voltages() (float64, float64, float64, error) { + return impl.phaseVoltages() +} diff --git a/charger/ocpp_test.go b/charger/ocpp_test.go index c2febf91b6..355a215aff 100644 --- a/charger/ocpp_test.go +++ b/charger/ocpp_test.go @@ -100,7 +100,7 @@ func (suite *ocppTestSuite) TestConnect() { suite.Require().True(cp1.IsConnected()) // 1st charge point- local - c1, err := NewOCPP("test-1", 1, "", "", 0, false, false, false, false, ocppTestConnectTimeout, ocppTestTimeout, "A") + c1, err := NewOCPP("test-1", 1, "", "", 0, false, false, true, ocppTestConnectTimeout, ocppTestTimeout, "A") suite.Require().NoError(err) // status and meter values @@ -159,7 +159,7 @@ func (suite *ocppTestSuite) TestConnect() { suite.Require().True(cp2.IsConnected()) // 2nd charge point - local - c2, err := NewOCPP("test-2", 1, "", "", 0, false, false, false, false, ocppTestConnectTimeout, ocppTestTimeout, "A") + c2, err := NewOCPP("test-2", 1, "", "", 0, false, false, true, ocppTestConnectTimeout, ocppTestTimeout, "A") suite.Require().NoError(err) { @@ -201,7 +201,7 @@ func (suite *ocppTestSuite) TestAutoStart() { suite.Require().True(cp1.IsConnected()) // 1st charge point- local - c1, err := NewOCPP("test-3", 1, "", "", 0, false, false, true, true, ocppTestConnectTimeout, ocppTestTimeout, "A") + c1, err := NewOCPP("test-3", 1, "", "", 0, false, false, false, ocppTestConnectTimeout, ocppTestTimeout, "A") suite.Require().NoError(err) // status and meter values diff --git a/templates/definition/charger/ocpp.yaml b/templates/definition/charger/ocpp.yaml index 6a01d32327..12a14a2bb0 100644 --- a/templates/definition/charger/ocpp.yaml +++ b/templates/definition/charger/ocpp.yaml @@ -12,8 +12,11 @@ requirements: Standardmäßig wird die erste eingehende Verbindung mit einer beliebigen Ladepunktkennung verwendet. Um mehrere Ladepunkte eindeutig zuordnen zu können müssen die jeweilige Stationskennung (`stationid: `) und Anschlussnummer (`connector: `) hinterlegt werden. Viele Wallboxen fügen die `stationid` automatisch der Backend-URL hinzu, bei manchen muss dies händisch geschehen `ws://:8887/`. - Gegebenenfalls benötigt der Ladepunkt eine vorkonfigurierte (virtuelle) Token-ID/RFID-Kennung (`idtag: `) mit der die Ladevorgänge ohne Authentifizierung gestartet werden können. Für Zählermesswerte sollte in der Wallbox ein kurzes Zeitintervall konfiguriert werden. + Nutzen Sie Ihre RFID-Tags (dies ermöglicht z.B. eine Fahrzeugidentifizierung) oder setzen Sie Ihre Wallbox auf "freies Laden" oder "Autostart" um die jeweils für die Ladefreigabe benötigte Transaktion zu erzeugen. + + Falls die Wallbox keinerlei Option bietet die Transaktionen lokal zu starten, kann die Option `remotetransaction` genutzt werden um automatisch eine Transaktion zu starten sobald ein Ladekabel angeschlossen wird. + Dies sollte nur in Ausnahmefällen erforderlich sein. Voraussetzungen: * Ggf. zuvor konfigurierte OCPP-Profile (z.B. durch eine andere Backend-Anbindung) in der Wallboxkonfiguration entfernen @@ -27,8 +30,11 @@ requirements: By default, the first incoming connection with any station identifier is used. In order to be able to clearly assign several charging points, the respective station identifier (`stationid: `) and connector number (`connector: `) must be configured. Many wallboxes automatically add the `station id` to the backend URL, some have to do this manually `ws://:8887/`. - The charger may need a pre-configured (virtual) token ID/RFID identifier (`idtag: `) with which the charging sessions can be started without authorization. If the charger supports sending metering values try to adjust the interval to a short time span. + Use your RFID tags (this allows e.g. vehicle identification) or set your charger to "free charging" or "autostart" to generate the transaction required for charging release. + + If the charger does not offer any option to start transactions locally, the `remotetransaction` option can be used to automatically start a transaction as soon as a cable is connected. + This should only be necessary in exceptional cases. Requirements: * If necessary, remove previously configured OCPP profiles (e.g. used for a different backend connection) in the charger configuration @@ -39,11 +45,19 @@ requirements: params: - preset: ocpp - name: autostart - advanced: true - type: bool + deprecated: true - name: nostop + deprecated: true + - name: remotestart advanced: true type: bool + default: false + description: + de: Automatisch eine für die Ladefreigabe benötigte Transaktion starten (RemoteStartTransaction) sobald ein Ladekabel mit der Wallbox verbunden wird + en: Automatically start a transaction required for charging release (RemoteStartTransaction) as soon as a cable is connected to the charger + help: + de: Nur verwenden wenn keinerlei Möglichkeit besteht Transaktionen seitens des Ladepunktes zu starten + en: Only use if there is no other way to start transactions from the charger - name: getconfiguration advanced: true type: bool @@ -68,14 +82,14 @@ params: advanced: true type: duration description: - de: Zählerwerte nach Intervall anfordern - en: Interval for requesting meter values + de: Gewünschtes Übertragungsintervall der Zählerwerte + en: Desired transmission interval of meter values - name: metervalues advanced: true type: string description: - de: Liste der Zählerwerte - en: List of meter values + de: Kommaseparierte Liste der zu sendenden Zählerwerte ("measurand") + en: Comma-separated list of meter values to send ("measurand") - name: chargingrateunit advanced: true type: string @@ -84,8 +98,7 @@ params: en: Unit for setting ChargingProfile values ("W" or "A") render: | {{ include "ocpp" . }} - autostart: {{ .autostart }} - nostop: {{ .nostop }} + remotestart: {{ .remotestart }} {{- if ne .getconfiguration "true" }} getconfiguration: {{ .getconfiguration }} {{- end }}