Skip to content

Commit 074ddd6

Browse files
committed
Add GetMonitoringReport feature and tests
Signed-off-by: Lorenzo Donini <[email protected]>
1 parent afa3c9f commit 074ddd6

File tree

5 files changed

+225
-1
lines changed

5 files changed

+225
-1
lines changed

ocpp2.0/core.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
GetInstalledCertificateIdsFeatureName = "GetInstalledCertificateIds"
3030
GetLocalListVersionFeatureName = "GetLocalListVersion"
3131
GetLogFeatureName = "GetLog"
32+
GetMonitoringReportFeatureName = "GetMonitoringReport"
3233
// GetConfigurationFeatureName = "GetConfiguration"
3334
// HeartbeatFeatureName = "Heartbeat"
3435
// MeterValuesFeatureName = "MeterValues"
@@ -78,6 +79,7 @@ type ChargePointCoreListener interface {
7879
OnGetInstalledCertificateIds(request *GetInstalledCertificateIdsRequest) (confirmation *GetInstalledCertificateIdsConfirmation, err error)
7980
OnGetLocalListVersion(request *GetLocalListVersionRequest) (confirmation *GetLocalListVersionConfirmation, err error)
8081
OnGetLog(request *GetLogRequest) (confirmation *GetLogConfirmation, err error)
82+
OnGetMonitoringReport(request *GetMonitoringReportRequest) (confirmation *GetMonitoringReportConfirmation, err error)
8183
// OnGetConfiguration(request *GetConfigurationRequest) (confirmation *GetConfigurationConfirmation, err error)
8284
// OnRemoteStartTransaction(request *RemoteStartTransactionRequest) (confirmation *RemoteStartTransactionConfirmation, err error)
8385
// OnRemoteStopTransaction(request *RemoteStopTransactionRequest) (confirmation *RemoteStopTransactionConfirmation, err error)
@@ -113,6 +115,7 @@ var CoreProfile = ocpp.NewProfile(
113115
GetInstalledCertificateIdsFeature{},
114116
GetLocalListVersionFeature{},
115117
GetLogFeature{},
118+
GetMonitoringReportFeature{},
116119

117120
//GetConfigurationFeature{},
118121
//HeartbeatFeature{},

ocpp2.0/get_monitoring_report.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package ocpp2
2+
3+
import (
4+
"gopkg.in/go-playground/validator.v9"
5+
"reflect"
6+
)
7+
8+
// -------------------- Get Monitoring Report (CSMS -> CS) --------------------
9+
10+
// Monitoring criteria contained in GetMonitoringReportRequest.
11+
type MonitoringCriteriaType string
12+
13+
const (
14+
MonitoringCriteriaThresholdMonitoring MonitoringCriteriaType = "ThresholdMonitoring"
15+
MonitoringCriteriaDeltaMonitoring MonitoringCriteriaType = "DeltaMonitoring"
16+
MonitoringCriteriaPeriodicMonitoring MonitoringCriteriaType = "PeriodicMonitoring"
17+
)
18+
19+
func isValidMonitoringCriteriaType(fl validator.FieldLevel) bool {
20+
status := MonitoringCriteriaType(fl.Field().String())
21+
switch status {
22+
case MonitoringCriteriaThresholdMonitoring, MonitoringCriteriaDeltaMonitoring, MonitoringCriteriaPeriodicMonitoring:
23+
return true
24+
default:
25+
return false
26+
}
27+
}
28+
29+
// The field definition of the GetMonitoringReport request payload sent by the CSMS to the Charging Station.
30+
type GetMonitoringReportRequest struct {
31+
RequestID *int `json:"requestId,omitempty" validate:"omitempty,gte=0"` // The Id of the request.
32+
MonitoringCriteria []MonitoringCriteriaType `json:"monitoringCriteria,omitempty" validate:"omitempty,max=3,dive,monitoringCriteria"` // This field contains criteria for components for which a monitoring report is requested.
33+
ComponentVariable []ComponentVariable `json:"componentVariable,omitempty" validate:"omitempty,dive"` // This field specifies the components and variables for which a monitoring report is requested.
34+
}
35+
36+
// This field definition of the GetMonitoringReport confirmation payload, sent by the Charging Station to the CSMS in response to a GetMonitoringReportRequest.
37+
// In case the request was invalid, or couldn't be processed, an error will be sent instead.
38+
type GetMonitoringReportConfirmation struct {
39+
Status GenericDeviceModelStatus `json:"status" validate:"required,genericDeviceModelStatus"` // This field indicates whether the Charging Station was able to accept the request.
40+
}
41+
42+
// A CSMS can request the Charging Station to send a report about configured monitoring settings per component and variable.
43+
// Optionally, this list can be filtered on monitoringCriteria and componentVariables.
44+
// The CSMS sends a GetMonitoringReportRequest to the Charging Station.
45+
// The Charging Station then responds with a GetMonitoringReportResponse.
46+
// Asynchronously, the Charging Station will then send a NotifyMonitoringReportRequest to the CSMS for each report part.
47+
type GetMonitoringReportFeature struct{}
48+
49+
func (f GetMonitoringReportFeature) GetFeatureName() string {
50+
return GetMonitoringReportFeatureName
51+
}
52+
53+
func (f GetMonitoringReportFeature) GetRequestType() reflect.Type {
54+
return reflect.TypeOf(GetMonitoringReportRequest{})
55+
}
56+
57+
func (f GetMonitoringReportFeature) GetConfirmationType() reflect.Type {
58+
return reflect.TypeOf(GetMonitoringReportConfirmation{})
59+
}
60+
61+
func (r GetMonitoringReportRequest) GetFeatureName() string {
62+
return GetMonitoringReportFeatureName
63+
}
64+
65+
func (c GetMonitoringReportConfirmation) GetFeatureName() string {
66+
return GetMonitoringReportFeatureName
67+
}
68+
69+
// Creates a new GetMonitoringReportRequest. All fields are optional and may be set afterwards.
70+
func NewGetMonitoringReportRequest() *GetMonitoringReportRequest {
71+
return &GetMonitoringReportRequest{}
72+
}
73+
74+
// Creates a new GetMonitoringReportConfirmation, containing all required fields. There are no optional fields for this message.
75+
func NewGetMonitoringReportConfirmation(status GenericDeviceModelStatus) *GetMonitoringReportConfirmation {
76+
return &GetMonitoringReportConfirmation{Status: status}
77+
}
78+
79+
func init() {
80+
_ = Validate.RegisterValidation("monitoringCriteria", isValidMonitoringCriteriaType)
81+
}

ocpp2.0/v2.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,8 @@ func (cp *chargePoint) handleIncomingRequest(request ocpp.Request, requestId str
452452
confirmation, err = cp.coreListener.OnGetLocalListVersion(request.(*GetLocalListVersionRequest))
453453
case GetLogFeatureName:
454454
confirmation, err = cp.coreListener.OnGetLog(request.(*GetLogRequest))
455+
case GetMonitoringReportFeatureName:
456+
confirmation, err = cp.coreListener.OnGetMonitoringReport(request.(*GetMonitoringReportRequest))
455457
//case GetConfigurationFeatureName:
456458
// confirmation, err = cp.coreListener.OnGetConfiguration(request.(*GetConfigurationRequest))
457459
//case RemoteStartTransactionFeatureName:
@@ -565,6 +567,7 @@ type CSMS interface {
565567
GetInstalledCertificateIds(clientId string, callback func(*GetInstalledCertificateIdsConfirmation, error), typeOfCertificate CertificateUse, props ...func(*GetInstalledCertificateIdsRequest)) error
566568
GetLocalListVersion(clientId string, callback func(*GetLocalListVersionConfirmation, error), props ...func(*GetLocalListVersionRequest)) error
567569
GetLog(clientId string, callback func(*GetLogConfirmation, error), logType LogType, requestID int, logParameters LogParameters, props ...func(*GetLogRequest)) error
570+
GetMonitoringReport(clientId string, callback func(*GetMonitoringReportConfirmation, error), props ...func(*GetMonitoringReportRequest)) error
568571
//GetConfiguration(clientId string, callback func(*GetConfigurationConfirmation, error), keys []string, props ...func(*GetConfigurationRequest)) error
569572
//RemoteStartTransaction(clientId string, callback func(*RemoteStartTransactionConfirmation, error), idTag string, props ...func(*RemoteStartTransactionRequest)) error
570573
//RemoteStopTransaction(clientId string, callback func(*RemoteStopTransactionConfirmation, error), transactionId int, props ...func(request *RemoteStopTransactionRequest)) error
@@ -897,6 +900,21 @@ func (cs *csms) GetLog(clientId string, callback func(*GetLogConfirmation, error
897900
return cs.SendRequestAsync(clientId, request, genericCallback)
898901
}
899902

903+
func (cs * csms) GetMonitoringReport(clientId string, callback func(*GetMonitoringReportConfirmation, error), props ...func(*GetMonitoringReportRequest)) error {
904+
request := NewGetMonitoringReportRequest()
905+
for _, fn := range props {
906+
fn(request)
907+
}
908+
genericCallback := func(confirmation ocpp.Confirmation, protoError error) {
909+
if confirmation != nil {
910+
callback(confirmation.(*GetMonitoringReportConfirmation), protoError)
911+
} else {
912+
callback(nil, protoError)
913+
}
914+
}
915+
return cs.SendRequestAsync(clientId, request, genericCallback)
916+
}
917+
900918
//
901919
//// Retrieves the configuration values for the provided configuration keys.
902920
//func (cs *centralSystem) GetConfiguration(clientId string, callback func(confirmation *GetConfigurationConfirmation, err error), keys []string, props ...func(request *GetConfigurationRequest)) error {
@@ -1153,7 +1171,7 @@ func (cs *csms) SetChargePointDisconnectedHandler(handler func(chargePointId str
11531171
// In case of network issues (i.e. the remote host couldn't be reached), the function returns an error directly. In this case, the callback is never called.
11541172
func (cs *csms) SendRequestAsync(clientId string, request ocpp.Request, callback func(confirmation ocpp.Confirmation, err error)) error {
11551173
switch request.GetFeatureName() {
1156-
case CancelReservationFeatureName, CertificateSignedFeatureName, ChangeAvailabilityFeatureName, ClearCacheFeatureName, ClearChargingProfileFeatureName, ClearDisplayFeatureName, ClearVariableMonitoringFeatureName, CostUpdatedFeatureName, CustomerInformationFeatureName, DataTransferFeatureName, DeleteCertificateFeatureName, GetBaseReportFeatureName, GetChargingProfilesFeatureName, GetCompositeScheduleFeatureName, GetDisplayMessagesFeatureName, GetInstalledCertificateIdsFeatureName, GetLocalListVersionFeatureName, GetLogFeatureName:
1174+
case CancelReservationFeatureName, CertificateSignedFeatureName, ChangeAvailabilityFeatureName, ClearCacheFeatureName, ClearChargingProfileFeatureName, ClearDisplayFeatureName, ClearVariableMonitoringFeatureName, CostUpdatedFeatureName, CustomerInformationFeatureName, DataTransferFeatureName, DeleteCertificateFeatureName, GetBaseReportFeatureName, GetChargingProfilesFeatureName, GetCompositeScheduleFeatureName, GetDisplayMessagesFeatureName, GetInstalledCertificateIdsFeatureName, GetLocalListVersionFeatureName, GetLogFeatureName, GetMonitoringReportFeatureName:
11571175
break
11581176
//case ChangeConfigurationFeatureName, DataTransferFeatureName, GetConfigurationFeatureName, RemoteStartTransactionFeatureName, RemoteStopTransactionFeatureName, ResetFeatureName, UnlockConnectorFeatureName,
11591177
// GetLocalListVersionFeatureName, SendLocalListFeatureName,
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package ocpp2_test
2+
3+
import (
4+
"fmt"
5+
"github.com/lorenzodonini/ocpp-go/ocpp2.0"
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/mock"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// Test
12+
func (suite *OcppV2TestSuite) TestGetMonitoringReportRequestValidation() {
13+
t := suite.T()
14+
componentVariables := []ocpp2.ComponentVariable{
15+
{
16+
Component: ocpp2.Component{ Name: "component1", Instance: "instance1", EVSE: &ocpp2.EVSE{ID: 2, ConnectorID: newInt(2)}},
17+
Variable: ocpp2.Variable{ Name: "variable1", Instance: "instance1"},
18+
},
19+
}
20+
var requestTable = []GenericTestEntry{
21+
{ocpp2.GetMonitoringReportRequest{RequestID: newInt(42), MonitoringCriteria: []ocpp2.MonitoringCriteriaType{ocpp2.MonitoringCriteriaThresholdMonitoring, ocpp2.MonitoringCriteriaDeltaMonitoring, ocpp2.MonitoringCriteriaPeriodicMonitoring}, ComponentVariable: componentVariables}, true},
22+
{ocpp2.GetMonitoringReportRequest{RequestID: newInt(42), MonitoringCriteria: []ocpp2.MonitoringCriteriaType{}, ComponentVariable: componentVariables}, true},
23+
{ocpp2.GetMonitoringReportRequest{RequestID: newInt(42), ComponentVariable: componentVariables}, true},
24+
{ocpp2.GetMonitoringReportRequest{RequestID: newInt(42), ComponentVariable: []ocpp2.ComponentVariable{}}, true},
25+
{ocpp2.GetMonitoringReportRequest{RequestID: newInt(42)}, true},
26+
{ocpp2.GetMonitoringReportRequest{}, true},
27+
{ocpp2.GetMonitoringReportRequest{RequestID: newInt(-1)}, false},
28+
{ocpp2.GetMonitoringReportRequest{MonitoringCriteria: []ocpp2.MonitoringCriteriaType{ocpp2.MonitoringCriteriaThresholdMonitoring, ocpp2.MonitoringCriteriaDeltaMonitoring, ocpp2.MonitoringCriteriaPeriodicMonitoring, ocpp2.MonitoringCriteriaThresholdMonitoring}}, false},
29+
{ocpp2.GetMonitoringReportRequest{MonitoringCriteria: []ocpp2.MonitoringCriteriaType{"invalidMonitoringCriteria"}}, false},
30+
{ocpp2.GetMonitoringReportRequest{ComponentVariable: []ocpp2.ComponentVariable{ { Variable: ocpp2.Variable{ Name: "variable1", Instance: "instance1"}}}}, false},
31+
}
32+
ExecuteGenericTestTable(t, requestTable)
33+
}
34+
35+
func (suite *OcppV2TestSuite) TestGetMonitoringReportConfirmationValidation() {
36+
t := suite.T()
37+
var confirmationTable = []GenericTestEntry{
38+
{ocpp2.GetMonitoringReportConfirmation{Status: ocpp2.GenericDeviceModelStatusAccepted}, true},
39+
{ocpp2.GetMonitoringReportConfirmation{Status: "invalidDeviceModelStatus"}, false},
40+
{ocpp2.GetMonitoringReportConfirmation{}, false},
41+
}
42+
ExecuteGenericTestTable(t, confirmationTable)
43+
}
44+
45+
func (suite *OcppV2TestSuite) TestGetMonitoringReportE2EMocked() {
46+
t := suite.T()
47+
wsId := "test_id"
48+
messageId := defaultMessageId
49+
wsUrl := "someUrl"
50+
requestID := newInt(42)
51+
monitoringCriteria := []ocpp2.MonitoringCriteriaType{ocpp2.MonitoringCriteriaThresholdMonitoring, ocpp2.MonitoringCriteriaPeriodicMonitoring}
52+
componentVariable := ocpp2.ComponentVariable{
53+
Component: ocpp2.Component{ Name: "component1", Instance: "instance1", EVSE: &ocpp2.EVSE{ID: 2, ConnectorID: newInt(2)}},
54+
Variable: ocpp2.Variable{ Name: "variable1", Instance: "instance1"},
55+
}
56+
componentVariables := []ocpp2.ComponentVariable{componentVariable}
57+
status := ocpp2.GenericDeviceModelStatusAccepted
58+
requestJson := fmt.Sprintf(`[2,"%v","%v",{"requestId":%v,"monitoringCriteria":["%v","%v"],"componentVariable":[{"component":{"name":"%v","instance":"%v","evse":{"id":%v,"connectorId":%v}},"variable":{"name":"%v","instance":"%v"}}]}]`,
59+
messageId, ocpp2.GetMonitoringReportFeatureName, *requestID, monitoringCriteria[0], monitoringCriteria[1], componentVariable.Component.Name, componentVariable.Component.Instance, componentVariable.Component.EVSE.ID, *componentVariable.Component.EVSE.ConnectorID, componentVariable.Variable.Name, componentVariable.Variable.Instance)
60+
responseJson := fmt.Sprintf(`[3,"%v",{"status":"%v"}]`, messageId, status)
61+
getMonitoringReportConfirmation := ocpp2.NewGetMonitoringReportConfirmation(status)
62+
channel := NewMockWebSocket(wsId)
63+
64+
coreListener := MockChargePointCoreListener{}
65+
coreListener.On("OnGetMonitoringReport", mock.Anything).Return(getMonitoringReportConfirmation, nil).Run(func(args mock.Arguments) {
66+
request, ok := args.Get(0).(*ocpp2.GetMonitoringReportRequest)
67+
require.True(t, ok)
68+
require.NotNil(t, request)
69+
assert.Equal(t, *requestID, *request.RequestID)
70+
require.Len(t, request.MonitoringCriteria, len(monitoringCriteria))
71+
assert.Equal(t, monitoringCriteria[0], request.MonitoringCriteria[0])
72+
assert.Equal(t, monitoringCriteria[1], request.MonitoringCriteria[1])
73+
require.Len(t, request.ComponentVariable, len(componentVariables))
74+
assert.Equal(t, componentVariable.Component.Name, request.ComponentVariable[0].Component.Name)
75+
assert.Equal(t, componentVariable.Component.Instance, request.ComponentVariable[0].Component.Instance)
76+
require.NotNil(t, request.ComponentVariable[0].Component.EVSE)
77+
assert.Equal(t, componentVariable.Component.EVSE.ID, request.ComponentVariable[0].Component.EVSE.ID)
78+
assert.Equal(t, *componentVariable.Component.EVSE.ConnectorID, *request.ComponentVariable[0].Component.EVSE.ConnectorID)
79+
assert.Equal(t, componentVariable.Variable.Name, request.ComponentVariable[0].Variable.Name)
80+
assert.Equal(t, componentVariable.Variable.Instance, request.ComponentVariable[0].Variable.Instance)
81+
})
82+
setupDefaultCentralSystemHandlers(suite, nil, expectedCentralSystemOptions{clientId: wsId, rawWrittenMessage: []byte(requestJson), forwardWrittenMessage: true})
83+
setupDefaultChargePointHandlers(suite, coreListener, expectedChargePointOptions{serverUrl: wsUrl, clientId: wsId, createChannelOnStart: true, channel: channel, rawWrittenMessage: []byte(responseJson), forwardWrittenMessage: true})
84+
// Run Test
85+
suite.csms.Start(8887, "somePath")
86+
err := suite.chargePoint.Start(wsUrl)
87+
require.Nil(t, err)
88+
resultChannel := make(chan bool, 1)
89+
err = suite.csms.GetMonitoringReport(wsId, func(confirmation *ocpp2.GetMonitoringReportConfirmation, err error) {
90+
require.Nil(t, err)
91+
require.NotNil(t, confirmation)
92+
assert.Equal(t, status, confirmation.Status)
93+
resultChannel <- true
94+
}, func(request *ocpp2.GetMonitoringReportRequest) {
95+
request.RequestID = requestID
96+
request.MonitoringCriteria = monitoringCriteria
97+
request.ComponentVariable = componentVariables
98+
})
99+
require.Nil(t, err)
100+
result := <-resultChannel
101+
assert.True(t, result)
102+
}
103+
104+
func (suite *OcppV2TestSuite) TestGetMonitoringReportInvalidEndpoint() {
105+
messageId := defaultMessageId
106+
requestID := newInt(42)
107+
monitoringCriteria := []ocpp2.MonitoringCriteriaType{ocpp2.MonitoringCriteriaThresholdMonitoring, ocpp2.MonitoringCriteriaPeriodicMonitoring}
108+
componentVariable := ocpp2.ComponentVariable{
109+
Component: ocpp2.Component{ Name: "component1", Instance: "instance1", EVSE: &ocpp2.EVSE{ID: 2, ConnectorID: newInt(2)}},
110+
Variable: ocpp2.Variable{ Name: "variable1", Instance: "instance1"},
111+
}
112+
GetMonitoringReportRequest := ocpp2.NewGetMonitoringReportRequest()
113+
requestJson := fmt.Sprintf(`[2,"%v","%v",{"requestId":%v,"monitoringCriteria":["%v","%v"],"componentVariable":[{"component":{"name":"%v","instance":"%v","evse":{"id":%v,"connectorId":%v}},"variable":{"name":"%v","instance":"%v"}}]}]`,
114+
messageId, ocpp2.GetMonitoringReportFeatureName, *requestID, monitoringCriteria[0], monitoringCriteria[1], componentVariable.Component.Name, componentVariable.Component.Instance, componentVariable.Component.EVSE.ID, *componentVariable.Component.EVSE.ConnectorID, componentVariable.Variable.Name, componentVariable.Variable.Instance)
115+
testUnsupportedRequestFromChargePoint(suite, GetMonitoringReportRequest, requestJson, messageId)
116+
}

ocpp2.0_test/ocpp2_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,12 @@ func (coreListener MockChargePointCoreListener) OnGetLog(request *ocpp2.GetLogRe
352352
return conf, args.Error(1)
353353
}
354354

355+
func (coreListener MockChargePointCoreListener) OnGetMonitoringReport(request *ocpp2.GetMonitoringReportRequest) (confirmation *ocpp2.GetMonitoringReportConfirmation, err error) {
356+
args := coreListener.MethodCalled("OnGetMonitoringReport", request)
357+
conf := args.Get(0).(*ocpp2.GetMonitoringReportConfirmation)
358+
return conf, args.Error(1)
359+
}
360+
355361
//func (coreListener MockChargePointCoreListener) OnGetConfiguration(request *ocpp2.GetConfigurationRequest) (confirmation *ocpp2.GetConfigurationConfirmation, err error) {
356362
// args := coreListener.MethodCalled("OnGetConfiguration", request)
357363
// conf := args.Get(0).(*ocpp2.GetConfigurationConfirmation)

0 commit comments

Comments
 (0)