evcc-io/charger/ocpp/connector_core.go

143 lines
3.9 KiB
Go

package ocpp
import (
"strings"
"time"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
)
// timestampValid returns false if status timestamps are outdated
func (conn *Connector) timestampValid(t time.Time) bool {
// reject if expired
if conn.clock.Since(t) > Timeout {
return false
}
// assume having a timestamp is better than not
if conn.status.Timestamp == nil {
return true
}
// reject older values than we already have
return !t.Before(conn.status.Timestamp.Time)
}
func (conn *Connector) OnStatusNotification(request *core.StatusNotificationRequest) (*core.StatusNotificationConfirmation, error) {
conn.mu.Lock()
defer conn.mu.Unlock()
if conn.status == nil {
conn.status = request
close(conn.statusC) // signal initial status received
} else if request.Timestamp == nil || conn.timestampValid(request.Timestamp.Time) {
conn.status = request
} else {
conn.log.TRACE.Printf("ignoring status: %s < %s", request.Timestamp.Time, conn.status.Timestamp)
}
if conn.isWaitingForAuth() {
if conn.remoteIdTag != "" {
conn.RemoteStartTransactionRequest(conn.remoteIdTag)
} else {
conn.log.DEBUG.Printf("waiting for local authentication")
}
}
return new(core.StatusNotificationConfirmation), nil
}
func getSampleKey(s types.SampledValue) types.Measurand {
if s.Phase != "" {
return s.Measurand + types.Measurand("."+string(s.Phase))
}
return s.Measurand
}
func (conn *Connector) OnMeterValues(request *core.MeterValuesRequest) (*core.MeterValuesConfirmation, error) {
conn.mu.Lock()
defer conn.mu.Unlock()
if request.TransactionId != nil && conn.txnId == 0 && conn.status != nil &&
(conn.status.Status == core.ChargePointStatusCharging ||
conn.status.Status == core.ChargePointStatusSuspendedEV ||
conn.status.Status == core.ChargePointStatusSuspendedEVSE) {
conn.log.DEBUG.Printf("recovered transaction: %d", *request.TransactionId)
conn.txnId = *request.TransactionId
}
for _, meterValue := range sortByAge(request.MeterValue) {
if meterValue.Timestamp == nil {
// this should be done before the sorting, but lets assume either all or no sample has a timestamp
meterValue.Timestamp = types.NewDateTime(conn.clock.Now())
}
// ignore old meter value requests
if !meterValue.Timestamp.Time.Before(conn.meterUpdated) {
for _, sample := range meterValue.SampledValue {
sample.Value = strings.TrimSpace(sample.Value)
conn.measurements[getSampleKey(sample)] = sample
conn.meterUpdated = meterValue.Timestamp.Time
}
}
}
return new(core.MeterValuesConfirmation), nil
}
func (conn *Connector) OnStartTransaction(request *core.StartTransactionRequest) (*core.StartTransactionConfirmation, error) {
conn.mu.Lock()
defer conn.mu.Unlock()
conn.txnId = int(instance.txnId.Add(1))
conn.idTag = request.IdTag
res := &core.StartTransactionConfirmation{
IdTagInfo: &types.IdTagInfo{
Status: types.AuthorizationStatusAccepted,
},
TransactionId: conn.txnId,
}
return res, nil
}
func (conn *Connector) assumeMeterStopped() {
conn.meterUpdated = conn.clock.Now()
if _, ok := conn.measurements[types.MeasurandPowerActiveImport]; ok {
conn.measurements[types.MeasurandPowerActiveImport] = types.SampledValue{
Value: "0",
Unit: types.UnitOfMeasureW,
}
}
for phase := 1; phase <= 3; phase++ {
if _, ok := conn.measurements[getPhaseKey(types.MeasurandCurrentImport, phase)]; ok {
conn.measurements[getPhaseKey(types.MeasurandCurrentImport, phase)] = types.SampledValue{
Value: "0",
Unit: types.UnitOfMeasureA,
}
}
}
}
func (conn *Connector) OnStopTransaction(request *core.StopTransactionRequest) (*core.StopTransactionConfirmation, error) {
conn.mu.Lock()
defer conn.mu.Unlock()
conn.txnId = 0
conn.idTag = ""
res := &core.StopTransactionConfirmation{
IdTagInfo: &types.IdTagInfo{
Status: types.AuthorizationStatusAccepted, // accept
},
}
conn.assumeMeterStopped()
return res, nil
}