From 100e7a559ac77816d391e7563e5d75bdb3e027d9 Mon Sep 17 00:00:00 2001 From: Thomas Vandahl Date: Fri, 29 Sep 2023 16:25:13 +0200 Subject: [PATCH] Ocpp: add support for configurable ChargingRateUnit (#10048) --- charger/ocpp.go | 61 ++++++++++++++++++-------- charger/ocpp_test.go | 2 +- templates/definition/charger/ocpp.yaml | 9 ++++ 3 files changed, 53 insertions(+), 19 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index e8aa04edb..bf7a964cf 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -11,6 +11,7 @@ import ( "github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/charger/ocpp" + "github.com/evcc-io/evcc/core/loadpoint" "github.com/evcc-io/evcc/util" "github.com/lorenzodonini/ocpp-go/ocpp1.6/core" "github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging" @@ -30,6 +31,8 @@ type OCPP struct { meterValuesSample string timeout time.Duration phaseSwitching bool + chargingRateUnit types.ChargingRateUnitType + lp loadpoint.API } const defaultIdTag = "evcc" @@ -50,11 +53,13 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) { Timeout time.Duration BootNotification *bool GetConfiguration *bool + ChargingRateUnit string }{ - Connector: 1, - IdTag: defaultIdTag, - ConnectTimeout: ocppConnectTimeout, - Timeout: ocppTimeout, + Connector: 1, + IdTag: defaultIdTag, + ConnectTimeout: ocppConnectTimeout, + Timeout: ocppTimeout, + ChargingRateUnit: "A", } if err := util.DecodeOther(other, &cc); err != nil { @@ -67,7 +72,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.ConnectTimeout, cc.Timeout) + cc.ConnectTimeout, cc.Timeout, cc.ChargingRateUnit) if err != nil { return c, err } @@ -104,6 +109,7 @@ func NewOCPP(id string, connector int, idtag string, meterValues string, meterInterval time.Duration, boot, noConfig bool, connectTimeout, timeout time.Duration, + chargingRateUnit string, ) (*OCPP, error) { unit := "ocpp" if id != "" { @@ -147,9 +153,12 @@ func NewOCPP(id string, connector int, idtag string, ocpp.KeyMeterValuesSampledData, ocpp.KeyMeterValueSampleInterval, ocpp.KeyConnectorSwitch3to1PhaseSupported, + ocpp.KeyChargingScheduleAllowedChargingRateUnit, } _ = keys + c.chargingRateUnit = types.ChargingRateUnitType(chargingRateUnit) + // noConfig mode disables GetConfiguration if noConfig { c.meterValuesSample = meterValues @@ -205,6 +214,11 @@ func NewOCPP(id string, connector int, idtag string, c.idtag = *opt.Value c.log.DEBUG.Printf("overriding default `idTag` with Alfen-specific value: %s", c.idtag) } + + case ocpp.KeyChargingScheduleAllowedChargingRateUnit: + if *opt.Value == "W" { + c.chargingRateUnit = types.ChargingRateUnitWatts + } } if err != nil { @@ -319,7 +333,7 @@ func (c *OCPP) Enable(enable bool) (err error) { rc <- err }, c.idtag, func(request *core.RemoteStartTransactionRequest) { request.ConnectorId = &c.connector - request.ChargingProfile = getTxChargingProfile(c.current, c.phases, 0) + request.ChargingProfile = c.getTxChargingProfile(c.current, 0) }) } else { // if no transaction is running, the vehicle may have stopped it (which is ok) or an unknown transaction is running @@ -361,8 +375,8 @@ func (c *OCPP) setChargingProfile(connectorId int, profile *types.ChargingProfil return c.wait(err, rc) } -// updatePeriod sets a single charging schedule period with given current and phases -func (c *OCPP) updatePeriod(current float64, phases int) error { +// updatePeriod sets a single charging schedule period with given current +func (c *OCPP) updatePeriod(current float64) error { // current period can only be updated if transaction is active if enabled, err := c.Enabled(); err != nil || !enabled { return err @@ -375,7 +389,7 @@ func (c *OCPP) updatePeriod(current float64, phases int) error { current = math.Trunc(10*current) / 10 - err = c.setChargingProfile(c.connector, getTxChargingProfile(current, phases, txn)) + err = c.setChargingProfile(c.connector, c.getTxChargingProfile(current, txn)) if err != nil { err = fmt.Errorf("set charging profile: %w", err) } @@ -383,13 +397,19 @@ func (c *OCPP) updatePeriod(current float64, phases int) error { return err } -func getTxChargingProfile(current float64, phases, transactionId int) *types.ChargingProfile { +func (c *OCPP) getTxChargingProfile(current float64, transactionId int) *types.ChargingProfile { period := types.NewChargingSchedulePeriod(0, current) - - // TODO add phases support - // if phases != 0 { - // period.NumberPhases = &phases - // } + if c.chargingRateUnit == types.ChargingRateUnitWatts { + var phases int + // get (expectedly) active phases from loadpoint + if c.lp != nil { + phases = c.lp.GetPhases() + } + if phases == 0 { + phases = 3 + } + period = types.NewChargingSchedulePeriod(0, math.Trunc(230.0*current*float64(phases))) + } return &types.ChargingProfile{ ChargingProfileId: 1, @@ -398,7 +418,7 @@ func getTxChargingProfile(current float64, phases, transactionId int) *types.Cha ChargingProfilePurpose: types.ChargingProfilePurposeTxProfile, ChargingProfileKind: types.ChargingProfileKindRelative, ChargingSchedule: &types.ChargingSchedule{ - ChargingRateUnit: types.ChargingRateUnitAmperes, + ChargingRateUnit: c.chargingRateUnit, ChargingSchedulePeriod: []types.ChargingSchedulePeriod{period}, }, } @@ -413,7 +433,7 @@ var _ api.ChargerEx = (*OCPP)(nil) // MaxCurrentMillis implements the api.ChargerEx interface func (c *OCPP) MaxCurrentMillis(current float64) error { - err := c.updatePeriod(current, c.phases) + err := c.updatePeriod(current) if err == nil { c.current = current } @@ -442,7 +462,7 @@ func (c *OCPP) phases1p3p(phases int) error { // 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) + return c.updatePeriod(c.current) } // // Identify implements the api.Identifier interface @@ -450,3 +470,8 @@ func (c *OCPP) phases1p3p(phases int) error { // func (c *OCPP) Identify() (string, error) { // return "", errors.New("not implemented") // } + +// LoadpointControl implements loadpoint.Controller +func (c *OCPP) LoadpointControl(lp loadpoint.API) { + c.lp = lp +} diff --git a/charger/ocpp_test.go b/charger/ocpp_test.go index 81f067240..f4ea887a2 100644 --- a/charger/ocpp_test.go +++ b/charger/ocpp_test.go @@ -98,7 +98,7 @@ func (suite *ocppTestSuite) TestConnect() { suite.True(cp.IsConnected()) // start cp server - c, err := NewOCPP("test", ocppTestConnector, "", "", 0, false, false, ocppTestConnectTimeout, ocppTestTimeout) + c, err := NewOCPP("test", ocppTestConnector, "", "", 0, false, false, ocppTestConnectTimeout, ocppTestTimeout, "A") if err != nil { suite.NoError(err) return diff --git a/templates/definition/charger/ocpp.yaml b/templates/definition/charger/ocpp.yaml index f524f2059..cc677cb45 100644 --- a/templates/definition/charger/ocpp.yaml +++ b/templates/definition/charger/ocpp.yaml @@ -70,6 +70,12 @@ params: description: de: Liste der Zählerwerte en: List of meter values + - name: chargingrateunit + advanced: true + type: string + description: + de: Einheit, in der ChargingProfile-Werte gesetzt werden ("W" oder "A") + en: Unit for setting ChargingProfile values ("W" or "A") render: | {{ include "ocpp" . }} {{- if ne .getconfiguration "true" }} @@ -84,3 +90,6 @@ render: | {{- if .metervalues }} metervalues: {{ .metervalues }} {{- end }} + {{- if .chargingrateunit }} + chargingrateunit: {{ .chargingrateunit }} + {{- end }}