From 122452f117f266729e7b9e27b7c57f01a77b9b44 Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Sun, 4 Aug 2024 17:52:59 +0000 Subject: [PATCH 1/9] OCPP: automatic charger configuration --- charger/ocpp.go | 290 ++++++++++++++++++++++---------------- charger/ocpp/connector.go | 2 +- charger/ocpp/const.go | 16 +-- charger/ocpp_test.go | 10 -- 4 files changed, 177 insertions(+), 141 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index 4e604637d1..7dbc071eeb 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -15,27 +15,34 @@ import ( "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/remotetrigger" "github.com/lorenzodonini/ocpp-go/ocpp1.6/smartcharging" "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" ) // OCPP charger implementation type OCPP struct { - log *util.Logger - conn *ocpp.Connector - idtag string - phases int - current float64 - meterValuesSample string - timeout time.Duration - phaseSwitching bool - remoteStart bool - chargingRateUnit types.ChargingRateUnitType - lp loadpoint.API - bootNotification *core.BootNotificationRequest -} - -const defaultIdTag = "evcc" // RemoteStartTransaction only + log *util.Logger + conn *ocpp.Connector + idtag string + phases int + current float64 + meterValuesSample string + timeout time.Duration + phaseSwitching bool + remoteStart bool + chargingRateUnit types.ChargingRateUnitType + hasRemoteTriggerFeature bool + chargingProfileId int + stackLevel int + lp loadpoint.API + bootNotification *core.BootNotificationRequest +} + +const ( + defaultIdTag = "evcc" // RemoteStartTransaction only + desiredMeasurands = "Energy.Active.Import.Register,Power.Active.Import,SoC,Current.Offered,Power.Offered,Current.Import,Voltage" +) func init() { registry.Add("ocpp", NewOCPPFromConfig) @@ -166,113 +173,117 @@ func NewOCPP(id string, connector int, idtag string, case <-cp.HasConnected(): } - // see who's there - if boot { - conn.TriggerMessageRequest(core.BootNotificationFeatureName) - select { - case <-time.After(timeout): - c.log.WARN.Printf("boot notification timeout") - case res := <-cp.BootNotificationRequest(): - c.bootNotification = res - } - } - var ( - rc = make(chan error, 1) - meterSampleInterval time.Duration - ) + rc = make(chan error, 1) - keys := []string{ - ocpp.KeyNumberOfConnectors, - ocpp.KeyMeterValuesSampledData, - ocpp.KeyMeterValueSampleInterval, - ocpp.KeyConnectorSwitch3to1PhaseSupported, - ocpp.KeyChargingScheduleAllowedChargingRateUnit, - } - _ = keys + // If a key value is defined as a CSL, it MAY be accompanied with a [KeyName]MaxLength key, indicating the + // max length of the CSL in items. If this key is not set, a safe value of 1 (one) item SHOULD be assumed. + MeterValuesSampledDataMaxLength int = 1 + ) c.chargingRateUnit = types.ChargingRateUnitType(chargingRateUnit) - // noConfig mode disables GetConfiguration - if noConfig { - c.meterValuesSample = meterValues - if meterInterval == 0 { - meterInterval = 10 * time.Second - } - } else { - // fix timing issue in EVBox when switching OCPP protocol version - time.Sleep(time.Second) - - err := ocpp.Instance().GetConfiguration(cp.ID(), func(resp *core.GetConfigurationConfirmation, err error) { - if err == nil { - // log unsupported configuration keys - if len(resp.UnknownKey) > 0 { - c.log.ERROR.Printf("unsupported keys: %v", resp.UnknownKey) + // fix timing issue in EVBox when switching OCPP protocol version + time.Sleep(time.Second) + + err = ocpp.Instance().GetConfiguration(cp.ID(), func(resp *core.GetConfigurationConfirmation, err error) { + if err == nil { + for _, opt := range resp.ConfigurationKey { + if opt.Value == nil { + continue } - // sort configuration keys for printing - slices.SortFunc(resp.ConfigurationKey, func(i, j core.ConfigurationKey) int { - return cmp.Compare(i.Key, j.Key) - }) + switch opt.Key { + case ocpp.KeyChargeProfileMaxStackLevel: + if val, err := strconv.Atoi(*opt.Value); err == nil { + c.stackLevel = val + } + + case ocpp.KeyChargingScheduleAllowedChargingRateUnit: + if *opt.Value == "Power" || *opt.Value == "W" { // "W" is not allowed by spec but used by some CPs + c.chargingRateUnit = types.ChargingRateUnitWatts + } + + case ocpp.KeyConnectorSwitch3to1PhaseSupported: + var val bool + if val, err = strconv.ParseBool(*opt.Value); err == nil { + c.phaseSwitching = val + } - rw := map[bool]string{false: "r/w", true: "r/o"} + case ocpp.KeyMaxChargingProfilesInstalled: + if val, err := strconv.Atoi(*opt.Value); err == nil { + c.chargingProfileId = val + } - for _, opt := range resp.ConfigurationKey { - if opt.Value == nil { - continue + case ocpp.KeyMeterValuesSampledDataMaxLength: + if val, err := strconv.Atoi(*opt.Value); err == nil { + MeterValuesSampledDataMaxLength = val } - c.log.TRACE.Printf("%s (%s): %s", opt.Key, rw[opt.Readonly], *opt.Value) - - switch opt.Key { - case ocpp.KeyNumberOfConnectors: - var val int - if val, err = strconv.Atoi(*opt.Value); err == nil && connector > val { - err = fmt.Errorf("connector %d exceeds max available connectors: %d", connector, val) - } - - case ocpp.KeyMeterValuesSampledData: - c.meterValuesSample = *opt.Value - - case ocpp.KeyMeterValueSampleInterval: - var val int - 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 - } - - case ocpp.KeyAlfenPlugAndChargeIdentifier: - if c.idtag == defaultIdTag { - 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" || *opt.Value == "Power" { - c.chargingRateUnit = types.ChargingRateUnitWatts - } + case ocpp.KeyNumberOfConnectors: + var val int + if val, err = strconv.Atoi(*opt.Value); err == nil && connector > val { + err = fmt.Errorf("connector %d exceeds max available connectors: %d", connector, val) } - if err != nil { - break + case ocpp.KeySupportedFeatureProfiles: + if !c.hasProperty(*opt.Value, smartcharging.ProfileName) { + err = fmt.Errorf("the mandatory SmartCharging profile is not supported") } + c.hasRemoteTriggerFeature = c.hasProperty(*opt.Value, remotetrigger.ProfileName) + + // vendor-specific keys + case ocpp.KeyAlfenPlugAndChargeIdentifier: + if c.idtag == defaultIdTag { + c.idtag = *opt.Value + c.log.DEBUG.Printf("overriding default `idTag` with Alfen-specific value: %s", c.idtag) + } + } + + if err != nil { + break } } + } + + rc <- err + }, nil) - rc <- err - }, nil) + if err := c.wait(err, rc); err != nil { + return nil, err + } - if err := c.wait(err, rc); err != nil { - return nil, err + // see who's there + if c.hasRemoteTriggerFeature { + if err := conn.TriggerMessageRequest(core.BootNotificationFeatureName); err == nil { + select { + case <-time.After(timeout): + c.log.DEBUG.Printf("BootNotification timeout") + case res := <-cp.BootNotificationRequest(): + if res != nil { + c.bootNotification = res + } + } } } - if meterValues != "" && meterValues != c.meterValuesSample { + if meterValues == "" { + // autodetect and set measurands + if MeterValuesSampledDataMaxLength > 0 { + sampledMeasurands := c.tryMeasurands(desiredMeasurands, ocpp.KeyMeterValuesSampledData) + if len(sampledMeasurands) > 0 { + m := c.constrainedJoin(sampledMeasurands, MeterValuesSampledDataMaxLength) + if err := c.configure(ocpp.KeyMeterValuesSampledData, m); err != nil { + return nil, err + } + + // configuration activated + c.meterValuesSample = m + c.log.DEBUG.Println("enabled MeterValuesSampledData measurands: ", m) + } + } + } else { // manual configuration will be deprecated in the future? + // manually override measurands if err := c.configure(ocpp.KeyMeterValuesSampledData, meterValues); err != nil { return nil, err } @@ -281,31 +292,40 @@ func NewOCPP(id string, connector int, idtag string, c.meterValuesSample = meterValues } - // get initial meter values and configure sample rate - if c.hasMeasurement(types.MeasurandPowerActiveImport) || c.hasMeasurement(types.MeasurandEnergyActiveImportRegister) { - conn.TriggerMessageRequest(core.MeterValuesFeatureName) - - // wait for meter values - select { - case <-time.After(timeout): - c.log.WARN.Println("meter value timeout") - case <-c.conn.MeterSampled(): + // trigger initial meter values + if c.hasRemoteTriggerFeature { + if err := conn.TriggerMessageRequest(core.MeterValuesFeatureName); err == nil { + // wait for meter values + select { + case <-time.After(timeout): + c.log.WARN.Println("meter timeout") + case <-c.conn.MeterSampled(): + } } + } - if meterInterval > 0 && meterInterval != meterSampleInterval { - if err := c.configure(ocpp.KeyMeterValueSampleInterval, strconv.Itoa(int(meterInterval.Seconds()))); err != nil { - return nil, err + // configure sample rate + if meterInterval > 0 { + if err := c.configure(ocpp.KeyMeterValueSampleInterval, strconv.Itoa(int(meterInterval.Seconds()))); err != nil { + return nil, err + } + } else { + for _, interval := range []int{4, 5, 10, 30, 45, 60} { // try known vendor constraints + if err := c.configure(ocpp.KeyMeterValueSampleInterval, strconv.Itoa(interval)); err == nil { + break } } + } - // HACK: setup watchdog for meter values if not happy with config - if meterInterval > 0 { - c.log.DEBUG.Println("enabling meter watchdog") - go conn.WatchDog(meterInterval) - } + if c.hasRemoteTriggerFeature { + c.log.DEBUG.Println("enabling meter watchdog") + go conn.WatchDog(10 * time.Second) } - // TODO: check for running transaction + // configure ping interval + if err := c.configure(ocpp.KeyWebSocketPingInterval, "10"); err != nil { + return nil, err + } return c, conn.Initialized() } @@ -317,7 +337,16 @@ func (c *OCPP) Connector() *ocpp.Connector { // hasMeasurement checks if meterValuesSample contains given measurement func (c *OCPP) hasMeasurement(val types.Measurand) bool { - return slices.Contains(strings.Split(c.meterValuesSample, ","), string(val)) + return c.hasProperty(c.meterValuesSample, string(val)) +} + +// hasProperty checks if comma-separated string contains given string ignoring whitespaces +func (c *OCPP) hasProperty(props string, prop string) bool { + p := strings.Split(props, ",") + for i := range p { + p[i] = strings.TrimSpace(p[i]) + } + return slices.Contains(p, prop) } func (c *OCPP) effectiveIdTag() string { @@ -327,6 +356,23 @@ func (c *OCPP) effectiveIdTag() string { return c.idtag } +func (c *OCPP) tryMeasurands(measurands string, key string) []string { + var accepted []string + for _, m := range strings.Split(measurands, ",") { + if err := c.configure(key, m); err == nil { + accepted = append(accepted, m) + } + } + return accepted +} + +func (c *OCPP) constrainedJoin(m []string, limit int) string { + if limit > 0 && len(m) > limit { + m = m[:limit] + } + return strings.Join(m, ",") +} + // configure updates CP configuration func (c *OCPP) configure(key, val string) error { rc := make(chan error, 1) @@ -473,8 +519,8 @@ func (c *OCPP) createTxDefaultChargingProfile(current float64) *types.ChargingPr } return &types.ChargingProfile{ - ChargingProfileId: 0, - StackLevel: 0, + ChargingProfileId: c.chargingProfileId, + StackLevel: c.stackLevel, ChargingProfilePurpose: types.ChargingProfilePurposeTxDefaultProfile, ChargingProfileKind: types.ChargingProfileKindAbsolute, ChargingSchedule: &types.ChargingSchedule{ diff --git a/charger/ocpp/connector.go b/charger/ocpp/connector.go index cd283963b1..7b93fb957e 100644 --- a/charger/ocpp/connector.go +++ b/charger/ocpp/connector.go @@ -86,7 +86,7 @@ func (conn *Connector) TriggerMessageRequest(feature remotetrigger.MessageTrigge // WatchDog triggers meter values messages if older than timeout. // Must be wrapped in a goroutine. func (conn *Connector) WatchDog(timeout time.Duration) { - tick := time.NewTicker(timeout) + tick := time.NewTicker(2 * time.Second) for ; true; <-tick.C { conn.mu.Lock() update := conn.txnId != 0 && conn.clock.Since(conn.meterUpdated) > timeout diff --git a/charger/ocpp/const.go b/charger/ocpp/const.go index 2ec6a14a6f..23adf372ae 100644 --- a/charger/ocpp/const.go +++ b/charger/ocpp/const.go @@ -2,19 +2,19 @@ package ocpp const ( // Core profile keys - KeyNumberOfConnectors = "NumberOfConnectors" + KeyMeterValueSampleInterval = "MeterValueSampleInterval" + KeyMeterValuesSampledData = "MeterValuesSampledData" + KeyMeterValuesSampledDataMaxLength = "MeterValuesSampledDataMaxLength" + KeyNumberOfConnectors = "NumberOfConnectors" + KeySupportedFeatureProfiles = "SupportedFeatureProfiles" + KeyWebSocketPingInterval = "WebSocketPingInterval" - // Meter profile keys - KeyMeterValuesSampledData = "MeterValuesSampledData" - KeyMeterValueSampleInterval = "MeterValueSampleInterval" - - // Smart Charging profile keys + // SmartCharging profile keys KeyChargeProfileMaxStackLevel = "ChargeProfileMaxStackLevel" KeyChargingScheduleAllowedChargingRateUnit = "ChargingScheduleAllowedChargingRateUnit" - KeyChargingScheduleMaxPeriods = "ChargingScheduleMaxPeriods" KeyConnectorSwitch3to1PhaseSupported = "ConnectorSwitch3to1PhaseSupported" KeyMaxChargingProfilesInstalled = "MaxChargingProfilesInstalled" - // Alfen specific keys + // Vendor specific keys KeyAlfenPlugAndChargeIdentifier = "PlugAndChargeIdentifier" ) diff --git a/charger/ocpp_test.go b/charger/ocpp_test.go index 355a215aff..2f557e42fe 100644 --- a/charger/ocpp_test.go +++ b/charger/ocpp_test.go @@ -111,16 +111,6 @@ func (suite *ocppTestSuite) TestConnect() { // status _, err = c1.Status() suite.Require().NoError(err) - - // power - f, err := c1.currentPower() - suite.Require().NoError(err) - suite.Equal(1e3, f) - - // energy - f, err = c1.totalEnergy() - suite.Require().NoError(err) - suite.Equal(1.2, f) } // takeover From 796338f74cc6b5b6527db8b8b3c64e2ddce5cb56 Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Sun, 4 Aug 2024 23:51:03 +0000 Subject: [PATCH 2/9] simplify --- charger/ocpp.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index 7dbc071eeb..cf00f0c42e 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -67,6 +67,7 @@ func NewOCPPFromConfig(other map[string]interface{}) (api.Charger, error) { }{ Connector: 1, IdTag: defaultIdTag, + MeterInterval: 10 * time.Second, ConnectTimeout: ocppConnectTimeout, Timeout: ocppTimeout, ChargingRateUnit: "A", @@ -282,8 +283,7 @@ func NewOCPP(id string, connector int, idtag string, c.log.DEBUG.Println("enabled MeterValuesSampledData measurands: ", m) } } - } else { // manual configuration will be deprecated in the future? - // manually override measurands + } else { // manually override measurands - may be deprecated in the future? if err := c.configure(ocpp.KeyMeterValuesSampledData, meterValues); err != nil { return nil, err } @@ -309,16 +309,9 @@ func NewOCPP(id string, connector int, idtag string, if err := c.configure(ocpp.KeyMeterValueSampleInterval, strconv.Itoa(int(meterInterval.Seconds()))); err != nil { return nil, err } - } else { - for _, interval := range []int{4, 5, 10, 30, 45, 60} { // try known vendor constraints - if err := c.configure(ocpp.KeyMeterValueSampleInterval, strconv.Itoa(interval)); err == nil { - break - } - } } if c.hasRemoteTriggerFeature { - c.log.DEBUG.Println("enabling meter watchdog") go conn.WatchDog(10 * time.Second) } From 450b66cd770d4881fcf94c3fd697d66e8e23764b Mon Sep 17 00:00:00 2001 From: premultiply <4681172+premultiply@users.noreply.github.com> Date: Mon, 5 Aug 2024 01:20:03 +0000 Subject: [PATCH 3/9] simplify --- charger/ocpp.go | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index 08d1a1777f..8a03529465 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -268,30 +268,21 @@ func NewOCPP(id string, connector int, idtag string, } } - if meterValues == "" { - // autodetect and set measurands - if MeterValuesSampledDataMaxLength > 0 { - sampledMeasurands := c.tryMeasurands(desiredMeasurands, ocpp.KeyMeterValuesSampledData) - if len(sampledMeasurands) > 0 { - m := c.constrainedJoin(sampledMeasurands, MeterValuesSampledDataMaxLength) - if err := c.configure(ocpp.KeyMeterValuesSampledData, m); err != nil { - return nil, err - } - - // configuration activated - c.meterValuesSample = m - c.log.DEBUG.Println("enabled MeterValuesSampledData measurands: ", m) - } - } - } else { // manually override measurands - may be deprecated in the future? - if err := c.configure(ocpp.KeyMeterValuesSampledData, meterValues); err != nil { - return nil, err + // autodetect measurands + if meterValues == "" && MeterValuesSampledDataMaxLength > 0 { + sampledMeasurands := c.tryMeasurands(desiredMeasurands, ocpp.KeyMeterValuesSampledData) + if len(sampledMeasurands) > 0 { + meterValues = c.constrainedJoin(sampledMeasurands, MeterValuesSampledDataMaxLength) } + } - // configuration activated - c.meterValuesSample = meterValues + // configure measurands + if err := c.configure(ocpp.KeyMeterValuesSampledData, meterValues); err != nil { + return nil, err } + c.meterValuesSample = meterValues + // trigger initial meter values if c.hasRemoteTriggerFeature { if err := conn.TriggerMessageRequest(core.MeterValuesFeatureName); err == nil { @@ -316,9 +307,7 @@ func NewOCPP(id string, connector int, idtag string, } // configure ping interval - if err := c.configure(ocpp.KeyWebSocketPingInterval, "10"); err != nil { - return nil, err - } + c.configure(ocpp.KeyWebSocketPingInterval, "30") return c, conn.Initialized() } From 90f959342aa2c9c3b005a9228f171625ba1ec931 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 5 Aug 2024 17:51:18 +0200 Subject: [PATCH 4/9] Update charger/ocpp.go --- charger/ocpp.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index 22edc0696c..bb897f5aff 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -349,13 +349,6 @@ func (c *OCPP) tryMeasurands(measurands string, key string) []string { return accepted } -func (c *OCPP) constrainedJoin(m []string, limit int) string { - if limit > 0 && len(m) > limit { - m = m[:limit] - } - return strings.Join(m, ",") -} - // configure updates CP configuration func (c *OCPP) configure(key, val string) error { rc := make(chan error, 1) From 310748324817477da9424a9035ba13fd929e1de9 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 5 Aug 2024 17:51:24 +0200 Subject: [PATCH 5/9] Update charger/ocpp.go --- charger/ocpp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index bb897f5aff..c02561611a 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -272,8 +272,8 @@ func NewOCPP(id string, connector int, idtag string, // autodetect measurands if meterValues == "" && MeterValuesSampledDataMaxLength > 0 { sampledMeasurands := c.tryMeasurands(desiredMeasurands, ocpp.KeyMeterValuesSampledData) - if len(sampledMeasurands) > 0 { - meterValues = c.constrainedJoin(sampledMeasurands, MeterValuesSampledDataMaxLength) + if len(sampledMeasurands) > MeterValuesSampledDataMaxLength { + meterValues = sampledMeasurands[:MeterValuesSampledDataMaxLength] } } From fdb2126501609e7e7993c414e3f2005baf2a6abd Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 5 Aug 2024 17:51:30 +0200 Subject: [PATCH 6/9] Update charger/ocpp.go --- charger/ocpp.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index c02561611a..0fc2f2f2d2 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -325,11 +325,9 @@ func (c *OCPP) hasMeasurement(val types.Measurand) bool { // hasProperty checks if comma-separated string contains given string ignoring whitespaces func (c *OCPP) hasProperty(props string, prop string) bool { - p := strings.Split(props, ",") - for i := range p { - p[i] = strings.TrimSpace(p[i]) - } - return slices.Contains(p, prop) + return slices.ContainsFunc(props, func(s string) bool { + return prop == slices.TrimSpace(s) + }) } func (c *OCPP) effectiveIdTag() string { From 4b7097af292fb194521a0d05ff4ec1f7c768cb75 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 5 Aug 2024 17:53:15 +0200 Subject: [PATCH 7/9] wip --- charger/ocpp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index 0fc2f2f2d2..b2d2dfbb7d 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -273,7 +273,7 @@ func NewOCPP(id string, connector int, idtag string, if meterValues == "" && MeterValuesSampledDataMaxLength > 0 { sampledMeasurands := c.tryMeasurands(desiredMeasurands, ocpp.KeyMeterValuesSampledData) if len(sampledMeasurands) > MeterValuesSampledDataMaxLength { - meterValues = sampledMeasurands[:MeterValuesSampledDataMaxLength] + meterValues = strings.Join(sampledMeasurands[:MeterValuesSampledDataMaxLength], ",") } } @@ -325,8 +325,8 @@ func (c *OCPP) hasMeasurement(val types.Measurand) bool { // hasProperty checks if comma-separated string contains given string ignoring whitespaces func (c *OCPP) hasProperty(props string, prop string) bool { - return slices.ContainsFunc(props, func(s string) bool { - return prop == slices.TrimSpace(s) + return slices.ContainsFunc(strings.Split(props, ","), func(s string) bool { + return prop == strings.TrimSpace(s) }) } From 8320d47b457a8a6452657b14112fc2ad87dbc140 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 5 Aug 2024 17:54:33 +0200 Subject: [PATCH 8/9] wip --- charger/ocpp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index b2d2dfbb7d..2ae769ca31 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -431,7 +431,7 @@ func (c *OCPP) Enabled() (bool, error) { } if c.hasMeasurement(types.MeasurandPowerOffered) { if v, err := c.getMaxPower(); err == nil { - return v > 0, err + return v > 0, nil } } From 3f8055e9e2492ceb943af109b42dff0742c595a8 Mon Sep 17 00:00:00 2001 From: andig Date: Mon, 5 Aug 2024 17:57:30 +0200 Subject: [PATCH 9/9] wip --- charger/ocpp.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/charger/ocpp.go b/charger/ocpp.go index 2ae769ca31..255cd7fa40 100644 --- a/charger/ocpp.go +++ b/charger/ocpp.go @@ -180,7 +180,7 @@ func NewOCPP(id string, connector int, idtag string, // If a key value is defined as a CSL, it MAY be accompanied with a [KeyName]MaxLength key, indicating the // max length of the CSL in items. If this key is not set, a safe value of 1 (one) item SHOULD be assumed. - MeterValuesSampledDataMaxLength int = 1 + meterValuesSampledDataMaxLength = 1 ) c.chargingRateUnit = types.ChargingRateUnitType(chargingRateUnit) @@ -219,7 +219,7 @@ func NewOCPP(id string, connector int, idtag string, case ocpp.KeyMeterValuesSampledDataMaxLength: if val, err := strconv.Atoi(*opt.Value); err == nil { - MeterValuesSampledDataMaxLength = val + meterValuesSampledDataMaxLength = val } case ocpp.KeyNumberOfConnectors: @@ -270,10 +270,10 @@ func NewOCPP(id string, connector int, idtag string, } // autodetect measurands - if meterValues == "" && MeterValuesSampledDataMaxLength > 0 { + if meterValues == "" && meterValuesSampledDataMaxLength > 0 { sampledMeasurands := c.tryMeasurands(desiredMeasurands, ocpp.KeyMeterValuesSampledData) - if len(sampledMeasurands) > MeterValuesSampledDataMaxLength { - meterValues = strings.Join(sampledMeasurands[:MeterValuesSampledDataMaxLength], ",") + if len(sampledMeasurands) > meterValuesSampledDataMaxLength { + meterValues = strings.Join(sampledMeasurands[:meterValuesSampledDataMaxLength], ",") } }