OCPP: use TxDefaultProfile, add more apis (BC) (#14888)
This commit is contained in:
parent
6cefd83f10
commit
1d5a068859
5 changed files with 1242 additions and 141 deletions
226
charger/ocpp.go
226
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
|
||||
current, err := c.getCurrent()
|
||||
|
||||
return current > 0, err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
c.enabled = enable
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// enableProfile pauses/resumes existing transaction by profile update
|
||||
func (c *OCPP) enableProfile(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))
|
||||
}
|
||||
|
||||
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().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,26 +400,14 @@ 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()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// current period can only be updated if transaction is active
|
||||
if txn == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
current = math.Trunc(10*current) / 10
|
||||
|
||||
err = c.setChargingProfile(c.getTxChargingProfile(current, txn))
|
||||
// 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 {
|
||||
err = fmt.Errorf("set charging profile: %w", err)
|
||||
}
|
||||
|
|
@ -449,7 +415,38 @@ func (c *OCPP) updatePeriod(current float64) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (c *OCPP) getTxChargingProfile(current float64, transactionId int) *types.ChargingProfile {
|
||||
// getCurrent returns the internal current offered by the chargepoint
|
||||
func (c *OCPP) getCurrent() (float64, error) {
|
||||
var current float64
|
||||
|
||||
if c.hasMeasurement(types.MeasurandCurrentOffered) {
|
||||
return c.getMaxCurrent()
|
||||
}
|
||||
|
||||
// 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) 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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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://<evcc>:8887/<stationid>`.
|
||||
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://<evcc>:8887/<stationid>`.
|
||||
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 }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue