Ocpp: request list of required configuration keys (#4687)
This commit is contained in:
parent
edb635faa3
commit
04d198a4e2
2 changed files with 63 additions and 178 deletions
106
charger/ocpp.go
106
charger/ocpp.go
|
|
@ -22,14 +22,13 @@ const statusTimeout = 30 * time.Second
|
|||
|
||||
// OCPP charger implementation
|
||||
type OCPP struct {
|
||||
log *util.Logger
|
||||
cp *ocpp.CP
|
||||
connector int
|
||||
idtag string
|
||||
phases int
|
||||
current float64
|
||||
meterValuesSample string
|
||||
phaseSwitchingSupported bool
|
||||
log *util.Logger
|
||||
cp *ocpp.CP
|
||||
connector int
|
||||
idtag string
|
||||
phases int
|
||||
current float64
|
||||
meterValuesSample string
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -123,69 +122,68 @@ func NewOCPP(id string, connector int, idtag string, meterValues string, meterIn
|
|||
|
||||
var (
|
||||
rc = make(chan error, 1)
|
||||
options []core.ConfigurationKey
|
||||
meterSampleInterval time.Duration
|
||||
)
|
||||
|
||||
keys := []string{
|
||||
ocpp.KeyNumberOfConnectors,
|
||||
ocpp.KeyMeterValuesSampledData,
|
||||
ocpp.KeyMeterValueSampleInterval,
|
||||
ocpp.KeyChargeProfileMaxStackLevel,
|
||||
ocpp.KeyChargingScheduleAllowedChargingRateUnit,
|
||||
ocpp.KeyChargingScheduleMaxPeriods,
|
||||
ocpp.KeyConnectorSwitch3to1PhaseSupported,
|
||||
ocpp.KeyMaxChargingProfilesInstalled,
|
||||
}
|
||||
|
||||
// configured id may be empty, use registered id below
|
||||
err := ocpp.Instance().GetConfiguration(cp.ID(), func(resp *core.GetConfigurationConfirmation, err error) {
|
||||
if err != nil {
|
||||
rc <- err
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
// sort config options for printing
|
||||
sort.Slice(resp.ConfigurationKey, func(i, j int) bool {
|
||||
return resp.ConfigurationKey[i].Key < resp.ConfigurationKey[j].Key
|
||||
})
|
||||
|
||||
options = resp.ConfigurationKey
|
||||
rw := map[bool]string{false: "r/w", true: "r/o"}
|
||||
|
||||
// sort config options for printing
|
||||
sort.Slice(options, func(i, j int) bool {
|
||||
return options[i].Key < options[j].Key
|
||||
})
|
||||
|
||||
rw := map[bool]string{false: "r/w", true: "r/o"}
|
||||
|
||||
for _, opt := range options {
|
||||
c.log.TRACE.Printf("%s (%s): %s", opt.Key, rw[opt.Readonly], *opt.Value)
|
||||
|
||||
switch opt.Key {
|
||||
case ocpp.KeyMeterValuesSampledData:
|
||||
c.meterValuesSample = *opt.Value
|
||||
|
||||
case ocpp.KeyMeterValueSampleInterval:
|
||||
meterValuesSampleInterval, err := strconv.Atoi(*opt.Value)
|
||||
if err != nil {
|
||||
rc <- err
|
||||
return
|
||||
}
|
||||
meterSampleInterval = time.Duration(meterValuesSampleInterval) * time.Second
|
||||
|
||||
case string(ocpp.KeyConnectorSwitch3to1PhaseSupported):
|
||||
// Detection of 1 phase charging/switching support
|
||||
b, err := strconv.ParseBool(*opt.Value)
|
||||
if err != nil {
|
||||
rc <- err
|
||||
return
|
||||
for _, opt := range resp.ConfigurationKey {
|
||||
if opt.Value == nil {
|
||||
c.log.ERROR.Printf("%s (%s): %s", opt.Key, rw[opt.Readonly], "nil")
|
||||
continue
|
||||
}
|
||||
|
||||
c.phaseSwitchingSupported = b
|
||||
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 && c.connector > val {
|
||||
err = fmt.Errorf("connector %d exceeds max available connectors: %d", c.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
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc <- nil
|
||||
}, []string{})
|
||||
rc <- err
|
||||
}, keys)
|
||||
|
||||
if err := c.wait(err, rc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := cp.DetectCapabilities(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check supported connectors of charge point
|
||||
if supported := cp.GetNumberOfSupportedConnectors(); c.connector > supported {
|
||||
return nil, fmt.Errorf("configured connector is not available, max available connectors %d", supported)
|
||||
}
|
||||
|
||||
if meterValues != "" {
|
||||
if err := c.configure(ocpp.KeyMeterValuesSampledData, meterValues); err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package ocpp
|
|||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -13,38 +12,22 @@ import (
|
|||
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
|
||||
)
|
||||
|
||||
// Meter Profile Key
|
||||
const (
|
||||
// Core profile keys
|
||||
KeyNumberOfConnectors = "NumberOfConnectors"
|
||||
|
||||
// Meter profile keys
|
||||
KeyMeterValuesSampledData = "MeterValuesSampledData"
|
||||
KeyMeterValueSampleInterval = "MeterValueSampleInterval"
|
||||
|
||||
// Smart Charging profile keys
|
||||
KeyChargeProfileMaxStackLevel = "ChargeProfileMaxStackLevel"
|
||||
KeyChargingScheduleAllowedChargingRateUnit = "ChargingScheduleAllowedChargingRateUnit"
|
||||
KeyChargingScheduleMaxPeriods = "ChargingScheduleMaxPeriods"
|
||||
KeyConnectorSwitch3to1PhaseSupported = "ConnectorSwitch3to1PhaseSupported"
|
||||
KeyMaxChargingProfilesInstalled = "MaxChargingProfilesInstalled"
|
||||
)
|
||||
|
||||
type SmartchargingChargeProfileKey string
|
||||
|
||||
// Smart Charging Profile Key
|
||||
const (
|
||||
KeyChargeProfileMaxStackLevel SmartchargingChargeProfileKey = "ChargeProfileMaxStackLevel"
|
||||
KeyChargingScheduleAllowedChargingRateUnit SmartchargingChargeProfileKey = "ChargingScheduleAllowedChargingRateUnit"
|
||||
KeyChargingScheduleMaxPeriods SmartchargingChargeProfileKey = "ChargingScheduleMaxPeriods"
|
||||
KeyConnectorSwitch3to1PhaseSupported SmartchargingChargeProfileKey = "ConnectorSwitch3to1PhaseSupported"
|
||||
KeyMaxChargingProfilesInstalled SmartchargingChargeProfileKey = "MaxChargingProfilesInstalled"
|
||||
)
|
||||
|
||||
type smartChargingProfile struct {
|
||||
// Max StackLevel of a ChargingProfile. The number defined also indicates the max allowed
|
||||
// number of installed charging scheduls per Charging Profile Purpose
|
||||
ChargeProfileMaxStackLevel int
|
||||
// A list of supported quantities for use in a ChargingSchedule.
|
||||
// Allowed values: 'Current' and 'Power'
|
||||
ChargingScheduleAllowedChargingRateUnit []string
|
||||
// Maximum number of periods that may be defined per ChargingSchedule
|
||||
ChargingScheduleMaxPeriods int
|
||||
// Defines if this Charge Point support switching from 3 to 1 phase during a charging session.
|
||||
ConnectorSwitch3to1PhaseSupported bool
|
||||
// Maximum number of Charging profiles instsalled at a time.
|
||||
MaxChargingProfilesInstalled int
|
||||
}
|
||||
|
||||
type CP struct {
|
||||
mu sync.Mutex
|
||||
log *util.Logger
|
||||
|
|
@ -60,9 +43,6 @@ type CP struct {
|
|||
meterUpdated time.Time
|
||||
measurements map[string]types.SampledValue
|
||||
|
||||
supportedNumberOfConnectors int
|
||||
smartChargingCapabilities smartChargingProfile
|
||||
|
||||
txnCount int // change initial value to the last known global transaction. Needs persistence
|
||||
txnId int
|
||||
}
|
||||
|
|
@ -148,99 +128,6 @@ func (cp *CP) WatchDog(timeout time.Duration) {
|
|||
}()
|
||||
}
|
||||
|
||||
func (cp *CP) DetectCapabilities(opts []core.ConfigurationKey) error {
|
||||
options := make(map[string]core.ConfigurationKey)
|
||||
for _, opt := range opts {
|
||||
options[opt.Key] = opt
|
||||
}
|
||||
|
||||
var err error
|
||||
if cp.supportedNumberOfConnectors, err = parseIntOption("NumberOfConnectors", options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cp.smartChargingCapabilities, err = detectSmartChargingCapabilities(options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *CP) GetNumberOfSupportedConnectors() int {
|
||||
return cp.supportedNumberOfConnectors
|
||||
}
|
||||
|
||||
func detectSmartChargingCapabilities(options map[string]core.ConfigurationKey) (smartChargingProfile, error) {
|
||||
var profile smartChargingProfile
|
||||
|
||||
{ // required
|
||||
val, err := parseIntOption(KeyChargeProfileMaxStackLevel, options)
|
||||
if err != nil {
|
||||
return profile, err
|
||||
}
|
||||
|
||||
profile.ChargeProfileMaxStackLevel = val
|
||||
}
|
||||
|
||||
{ // required
|
||||
val, err := parseIntOption(KeyChargingScheduleMaxPeriods, options)
|
||||
if err != nil {
|
||||
return profile, err
|
||||
}
|
||||
|
||||
profile.ChargingScheduleMaxPeriods = val
|
||||
}
|
||||
|
||||
{ // required
|
||||
val, err := parseIntOption(KeyMaxChargingProfilesInstalled, options)
|
||||
if err != nil {
|
||||
return profile, err
|
||||
}
|
||||
|
||||
profile.MaxChargingProfilesInstalled = val
|
||||
}
|
||||
|
||||
{ // required
|
||||
opt, found := options[string(KeyChargingScheduleAllowedChargingRateUnit)]
|
||||
if !found || opt.Value == nil {
|
||||
return profile, fmt.Errorf("smart charging key '%s' not found", KeyChargingScheduleAllowedChargingRateUnit)
|
||||
}
|
||||
|
||||
vals := strings.Split(*opt.Value, ",")
|
||||
profile.ChargingScheduleAllowedChargingRateUnit = append(profile.ChargingScheduleAllowedChargingRateUnit, vals...)
|
||||
}
|
||||
|
||||
{ // optional
|
||||
var supported bool
|
||||
|
||||
if opt, ok := options[string(KeyConnectorSwitch3to1PhaseSupported)]; ok {
|
||||
var err error
|
||||
supported, err = strconv.ParseBool(*opt.Value)
|
||||
if err != nil {
|
||||
return profile, fmt.Errorf("invalid value for key: %s", opt.Key)
|
||||
}
|
||||
}
|
||||
|
||||
profile.ConnectorSwitch3to1PhaseSupported = supported
|
||||
}
|
||||
|
||||
return profile, nil
|
||||
}
|
||||
|
||||
func parseIntOption(key SmartchargingChargeProfileKey, options map[string]core.ConfigurationKey) (int, error) {
|
||||
opt, found := options[string(key)]
|
||||
if !found || opt.Value == nil {
|
||||
return 0, fmt.Errorf("smart charging key '%s' not found", key)
|
||||
}
|
||||
|
||||
val, err := strconv.Atoi(*opt.Value)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid value for key: %s", key)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// TransactionID returns the current transaction id
|
||||
func (cp *CP) TransactionID() int {
|
||||
cp.mu.Lock()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue