diff --git a/go.mod b/go.mod index 38b2fd2a..7dba0b0b 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/gorilla/websocket v1.4.1 github.com/kr/pretty v0.1.0 // indirect github.com/leodido/go-urn v1.1.0 // indirect + github.com/relvacode/iso8601 v1.3.0 // indirect github.com/sirupsen/logrus v1.4.2 github.com/stretchr/testify v1.8.0 golang.org/x/sys v0.0.0-20220804214406-8e32c043e418 // indirect diff --git a/go.sum b/go.sum index 502662eb..b1223cf9 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,8 @@ github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8= github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/relvacode/iso8601 v1.3.0 h1:HguUjsGpIMh/zsTczGN3DVJFxTU/GX+MMmzcKoMO7ko= +github.com/relvacode/iso8601 v1.3.0/go.mod h1:FlNp+jz+TXpyRqgmM7tnzHHzBnz776kmAH2h3sZCn0I= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/ocpp/ocpp.go b/ocpp/ocpp.go index 9014a077..2defbf5f 100644 --- a/ocpp/ocpp.go +++ b/ocpp/ocpp.go @@ -112,3 +112,12 @@ func (p *Profile) ParseResponse(featureName string, rawResponse interface{}, res responseType := feature.GetResponseType() return responseParser(rawResponse, responseType) } + +// Dialect is the OCPP version the Endpoint supports +type Dialect int + +const ( + _ Dialect = iota + V16 + V2 +) diff --git a/ocpp1.6/central_system.go b/ocpp1.6/central_system.go index 73d5f291..a7b0c29d 100644 --- a/ocpp1.6/central_system.go +++ b/ocpp1.6/central_system.go @@ -33,6 +33,7 @@ func newCentralSystem(server *ocppj.Server) centralSystem { if server == nil { panic("server must not be nil") } + server.SetDialect(ocpp.V16) return centralSystem{ server: server, callbackQueue: callbackqueue.New(), @@ -404,12 +405,14 @@ func (cs *centralSystem) SendRequestAsync(clientId string, request ocpp.Request, } func (cs *centralSystem) Start(listenPort int, listenPath string) { - // Overriding some protocol-specific values in the lower layers globally - ocppj.FormationViolation = ocppj.FormatViolationV16 // Start server cs.server.Start(listenPort, listenPath) } +func (cs *centralSystem) Stop() { + cs.server.Stop() +} + func (cs *centralSystem) sendResponse(chargePointId string, confirmation ocpp.Response, err error, requestId string) { if err != nil { // Send error response diff --git a/ocpp1.6/charge_point.go b/ocpp1.6/charge_point.go index 414e8829..f09c700d 100644 --- a/ocpp1.6/charge_point.go +++ b/ocpp1.6/charge_point.go @@ -333,8 +333,6 @@ func (cp *chargePoint) sendResponse(confirmation ocpp.Response, err error, reque } func (cp *chargePoint) Start(centralSystemUrl string) error { - // Overriding some protocol-specific values in the lower layers globally - ocppj.FormationViolation = ocppj.FormatViolationV16 // Start client cp.stopC = make(chan struct{}, 1) err := cp.client.Start(centralSystemUrl) diff --git a/ocpp1.6/types/datetime.go b/ocpp1.6/types/datetime.go index a75101f0..c451b0a5 100644 --- a/ocpp1.6/types/datetime.go +++ b/ocpp1.6/types/datetime.go @@ -2,14 +2,13 @@ package types import ( "encoding/json" - "strings" + "errors" "time" -) -// ISO8601 time format, assuming Zulu timestamp. -const ISO8601 = "2006-01-02T15:04:05Z" + "github.com/relvacode/iso8601" +) -// DateTimeFormat to be used for all OCPP messages. +// DateTimeFormat to be used when serializing all OCPP messages. // // The default dateTime format is RFC3339. // Change this if another format is desired. @@ -25,24 +24,36 @@ func NewDateTime(time time.Time) *DateTime { return &DateTime{Time: time} } +// Creates a new DateTime struct, containing a time.Now() value. +func Now() *DateTime { + return &DateTime{Time: time.Now()} +} + +func null(b []byte) bool { + if len(b) != 4 { + return false + } + if b[0] != 'n' && b[1] != 'u' && b[2] != 'l' && b[3] != 'l' { + return false + } + return true +} + func (dt *DateTime) UnmarshalJSON(input []byte) error { - strInput := string(input) - strInput = strings.Trim(strInput, `"`) - if DateTimeFormat == "" { - var defaultTime time.Time - err := json.Unmarshal(input, &defaultTime) - if err != nil { - return err - } - dt.Time = defaultTime.Local() + // Do not parse null timestamps + if null(input) { + return nil + } + // Assert that timestamp is a string + if len(input) > 0 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] } else { - newTime, err := time.Parse(DateTimeFormat, strInput) - if err != nil { - return err - } - dt.Time = newTime.Local() + return errors.New("timestamp not enclosed in double quotes") } - return nil + // Parse ISO8601 + var err error + dt.Time, err = iso8601.Parse(input) + return err } func (dt *DateTime) MarshalJSON() ([]byte, error) { diff --git a/ocpp1.6/v16.go b/ocpp1.6/v16.go index be035d71..853ec147 100644 --- a/ocpp1.6/v16.go +++ b/ocpp1.6/v16.go @@ -139,15 +139,22 @@ func NewChargePoint(id string, endpoint *ocppj.Client, client ws.WsClient) Charg client = ws.NewClient() } client.SetRequestedSubProtocol(types.V16Subprotocol) - cp := chargePoint{confirmationHandler: make(chan ocpp.Response, 1), errorHandler: make(chan error, 1), callbacks: callbackqueue.New()} if endpoint == nil { dispatcher := ocppj.NewDefaultClientDispatcher(ocppj.NewFIFOClientQueue(0)) endpoint = ocppj.NewClient(id, client, dispatcher, nil, core.Profile, localauth.Profile, firmware.Profile, reservation.Profile, remotetrigger.Profile, smartcharging.Profile) } + endpoint.SetDialect(ocpp.V16) + + cp := chargePoint{ + client: endpoint, + confirmationHandler: make(chan ocpp.Response, 1), + errorHandler: make(chan error, 1), + callbacks: callbackqueue.New(), + } + // Callback invoked by dispatcher, whenever a queued request is canceled, due to timeout. endpoint.SetOnRequestCanceled(cp.onRequestTimeout) - cp.client = endpoint cp.client.SetResponseHandler(func(confirmation ocpp.Response, requestId string) { cp.confirmationHandler <- confirmation @@ -249,9 +256,11 @@ type CentralSystem interface { SendRequestAsync(clientId string, request ocpp.Request, callback func(ocpp.Response, error)) error // Starts running the central system on the specified port and URL. // The central system runs as a daemon and handles incoming charge point connections and messages. - // + // The function blocks forever, so it is suggested to wrap it in a goroutine, in case other functionality needs to be executed on the main program thread. Start(listenPort int, listenPath string) + // Stops the central system, clearing all pending requests. + Stop() // Errors returns a channel for error messages. If it doesn't exist it es created. Errors() <-chan error } diff --git a/ocpp1.6_test/common_test.go b/ocpp1.6_test/common_test.go index 7680d76b..e7c342e2 100644 --- a/ocpp1.6_test/common_test.go +++ b/ocpp1.6_test/common_test.go @@ -1,8 +1,13 @@ package ocpp16_test import ( - "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" + "encoding/json" + "strings" "time" + + "github.com/relvacode/iso8601" + + "github.com/lorenzodonini/ocpp-go/ocpp1.6/types" ) // Utility functions @@ -114,3 +119,66 @@ func (suite *OcppV16TestSuite) TestMeterValueValidation() { } ExecuteGenericTestTable(suite.T(), testTable) } + +func (suite *OcppV16TestSuite) TestUnmarshalDateTime() { + testTable := []struct { + RawDateTime string + ExpectedValid bool + ExpectedTime time.Time + ExpectedError error + }{ + {"\"2019-03-01T10:00:00Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01 10:00:00+00:00\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: ' '}}, + {"\"null\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: 110}}, + {"\"\"", false, time.Time{}, &iso8601.RangeError{Element: "month", Min: 1, Max: 12}}, + {"null", true, time.Time{}, nil}, + } + for _, dt := range testTable { + jsonStr := []byte(dt.RawDateTime) + var dateTime types.DateTime + err := json.Unmarshal(jsonStr, &dateTime) + if dt.ExpectedValid { + suite.NoError(err) + suite.NotNil(dateTime) + suite.True(dt.ExpectedTime.Equal(dateTime.Time)) + } else { + suite.Error(err) + suite.ErrorAs(err, &dt.ExpectedError) + } + } +} + +func (suite *OcppV16TestSuite) TestMarshalDateTime() { + testTable := []struct { + Time time.Time + Format string + ExpectedFormattedString string + }{ + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "", "2019-03-01T10:00:00Z"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC3339, "2019-03-01T10:00:00Z"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC822, "01 Mar 19 10:00 UTC"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC1123, "Fri, 01 Mar 2019 10:00:00 UTC"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "invalidFormat", "invalidFormat"}, + } + for _, dt := range testTable { + dateTime := types.NewDateTime(dt.Time) + types.DateTimeFormat = dt.Format + rawJson, err := dateTime.MarshalJSON() + suite.NoError(err) + formatted := strings.Trim(string(rawJson), "\"") + suite.Equal(dt.ExpectedFormattedString, formatted) + } +} + +func (suite *OcppV16TestSuite) TestNowDateTime() { + now := types.Now() + suite.NotNil(now) + suite.True(time.Now().Sub(now.Time) < 1*time.Second) +} diff --git a/ocpp1.6_test/ocpp16_test.go b/ocpp1.6_test/ocpp16_test.go index 3e206edd..137fa7b9 100644 --- a/ocpp1.6_test/ocpp16_test.go +++ b/ocpp1.6_test/ocpp16_test.go @@ -7,6 +7,7 @@ import ( "net/http" "reflect" "testing" + "time" "github.com/stretchr/testify/require" @@ -510,6 +511,7 @@ func setupDefaultCentralSystemHandlers(suite *OcppV16TestSuite, coreListener cor }) suite.centralSystem.SetCoreHandler(coreListener) suite.mockWsServer.On("Start", mock.AnythingOfType("int"), mock.AnythingOfType("string")).Return(options.startReturnArgument) + suite.mockWsServer.On("Stop").Return() suite.mockWsServer.On("Write", mock.AnythingOfType("string"), mock.Anything).Return(options.writeReturnArgument).Run(func(args mock.Arguments) { clientId := args.String(0) data := args.Get(1) @@ -594,6 +596,8 @@ func testUnsupportedRequestFromChargePoint(suite *OcppV16TestSuite, request ocpp assert.Nil(t, err) _, ok := <-resultChannel assert.True(t, ok) + // Stop the central system + suite.centralSystem.Stop() } func testUnsupportedRequestFromCentralSystem(suite *OcppV16TestSuite, request ocpp.Request, requestJson string, messageId string) { @@ -634,6 +638,8 @@ func testUnsupportedRequestFromCentralSystem(suite *OcppV16TestSuite, request oc assert.Nil(t, err) _, ok := <-resultChannel assert.True(t, ok) + // Stop the central system + suite.centralSystem.Stop() } type GenericTestEntry struct { @@ -708,6 +714,7 @@ func (suite *OcppV16TestSuite) SetupTest() { return defaultMessageId }} ocppj.SetMessageIdGenerator(suite.messageIdGenerator.generateId) + types.DateTimeFormat = time.RFC3339 } func (suite *OcppV16TestSuite) TestIsConnected() { @@ -720,8 +727,7 @@ func (suite *OcppV16TestSuite) TestIsConnected() { assert.False(t, suite.chargePoint.IsConnected()) } -//TODO: implement generic protocol tests - +// TODO: implement generic protocol tests func TestOcpp16Protocol(t *testing.T) { suite.Run(t, new(OcppV16TestSuite)) } diff --git a/ocpp1.6_test/proto_test.go b/ocpp1.6_test/proto_test.go index 39815663..b843d039 100644 --- a/ocpp1.6_test/proto_test.go +++ b/ocpp1.6_test/proto_test.go @@ -154,8 +154,5 @@ func (suite *OcppV16TestSuite) TestCentralSystemSendResponseError() { } func (suite *OcppV16TestSuite) TestErrorCodes() { - t := suite.T() - suite.mockWsServer.On("Start", mock.AnythingOfType("int"), mock.AnythingOfType("string")).Return(nil) - suite.centralSystem.Start(8887, "somePath") - assert.Equal(t, ocppj.FormatViolationV16, ocppj.FormationViolation) + suite.Equal(ocppj.FormatViolationV16, ocppj.FormatErrorType(suite.ocppjCentralSystem)) } diff --git a/ocpp2.0.1/charging_station.go b/ocpp2.0.1/charging_station.go index c1ef9ea0..065af9a2 100644 --- a/ocpp2.0.1/charging_station.go +++ b/ocpp2.0.1/charging_station.go @@ -593,8 +593,6 @@ func (cs *chargingStation) sendResponse(response ocpp.Response, err error, reque } func (cs *chargingStation) Start(csmsUrl string) error { - // Overriding some protocol-specific values in the lower layers globally - ocppj.FormationViolation = ocppj.FormatViolationV2 // Start client cs.stopC = make(chan struct{}, 1) err := cs.client.Start(csmsUrl) diff --git a/ocpp2.0.1/csms.go b/ocpp2.0.1/csms.go index 7b66ac70..0a54442b 100644 --- a/ocpp2.0.1/csms.go +++ b/ocpp2.0.1/csms.go @@ -53,6 +53,7 @@ func newCSMS(server *ocppj.Server) csms { if server == nil { panic("server must not be nil") } + server.SetDialect(ocpp.V2) return csms{ server: server, callbackQueue: callbackqueue.New(), @@ -810,12 +811,14 @@ func (cs *csms) SendRequestAsync(clientId string, request ocpp.Request, callback } func (cs *csms) Start(listenPort int, listenPath string) { - // Overriding some protocol-specific values in the lower layers globally - ocppj.FormationViolation = ocppj.FormatViolationV2 // Start server cs.server.Start(listenPort, listenPath) } +func (cs *csms) Stop() { + cs.server.Stop() +} + func (cs *csms) sendResponse(chargingStationID string, response ocpp.Response, err error, requestId string) { if err != nil { // Send error response diff --git a/ocpp2.0.1/provisioning/set_network_profile.go b/ocpp2.0.1/provisioning/set_network_profile.go index 7f1cda16..867d7bf1 100644 --- a/ocpp2.0.1/provisioning/set_network_profile.go +++ b/ocpp2.0.1/provisioning/set_network_profile.go @@ -151,7 +151,7 @@ type APN struct { type NetworkConnectionProfile struct { OCPPVersion OCPPVersion `json:"ocppVersion" validate:"required,ocppVersion"` // The OCPP version used for this communication function. OCPPTransport OCPPTransport `json:"ocppTransport" validate:"required,ocppTransport"` // Defines the transport protocol (only OCPP-J is supported by this library). - CSMSUrl string `json:"ocppCsmsUrl" validate:"required,max=512,url"` // URL of the CSMS(s) that this Charging Station communicates with. + CSMSUrl string `json:"ocppCsmsUrl" validate:"required,max=512"` // URL of the CSMS(s) that this Charging Station communicates with. MessageTimeout int `json:"messageTimeout" validate:"gte=-1"` // Duration in seconds before a message send by the Charging Station via this network connection times out. SecurityProfile int `json:"securityProfile"` // The security profile used when connecting to the CSMS with this NetworkConnectionProfile. OCPPInterface OCPPInterface `json:"ocppInterface" validate:"required,ocppInterface"` // Applicable Network Interface. diff --git a/ocpp2.0.1/types/datetime.go b/ocpp2.0.1/types/datetime.go index b9ba6f9e..c451b0a5 100644 --- a/ocpp2.0.1/types/datetime.go +++ b/ocpp2.0.1/types/datetime.go @@ -2,11 +2,13 @@ package types import ( "encoding/json" - "strings" + "errors" "time" + + "github.com/relvacode/iso8601" ) -// DateTimeFormat to be used for all OCPP messages. +// DateTimeFormat to be used when serializing all OCPP messages. // // The default dateTime format is RFC3339. // Change this if another format is desired. @@ -22,36 +24,43 @@ func NewDateTime(time time.Time) *DateTime { return &DateTime{Time: time} } -// Creates a new DateTime struct, embedding a struct generated using time.Now(). +// Creates a new DateTime struct, containing a time.Now() value. func Now() *DateTime { return &DateTime{Time: time.Now()} } +func null(b []byte) bool { + if len(b) != 4 { + return false + } + if b[0] != 'n' && b[1] != 'u' && b[2] != 'l' && b[3] != 'l' { + return false + } + return true +} + func (dt *DateTime) UnmarshalJSON(input []byte) error { - strInput := string(input) - strInput = strings.Trim(strInput, `"`) - if DateTimeFormat == "" { - var defaultTime time.Time - err := json.Unmarshal(input, &defaultTime) - if err != nil { - return err - } - dt.Time = defaultTime.Local() + // Do not parse null timestamps + if null(input) { + return nil + } + // Assert that timestamp is a string + if len(input) > 0 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] } else { - newTime, err := time.Parse(DateTimeFormat, strInput) - if err != nil { - return err - } - dt.Time = newTime.Local() + return errors.New("timestamp not enclosed in double quotes") } - return nil + // Parse ISO8601 + var err error + dt.Time, err = iso8601.Parse(input) + return err } func (dt *DateTime) MarshalJSON() ([]byte, error) { if DateTimeFormat == "" { return json.Marshal(dt.Time) } - timeStr := FormatTimestamp(dt.Time) + timeStr := dt.FormatTimestamp() return json.Marshal(timeStr) } diff --git a/ocpp2.0.1/v2.go b/ocpp2.0.1/v2.go index 8627d8ee..6e58efed 100644 --- a/ocpp2.0.1/v2.go +++ b/ocpp2.0.1/v2.go @@ -34,8 +34,10 @@ type ChargingStationConnection interface { TLSConnectionState() *tls.ConnectionState } -type ChargingStationValidationHandler ws.CheckClientHandler -type ChargingStationConnectionHandler func(chargePoint ChargingStationConnection) +type ( + ChargingStationValidationHandler ws.CheckClientHandler + ChargingStationConnectionHandler func(chargePoint ChargingStationConnection) +) // -------------------- v2.0 Charging Station -------------------- @@ -204,16 +206,22 @@ func NewChargingStation(id string, endpoint *ocppj.Client, client ws.WsClient) C client = ws.NewClient() } client.SetRequestedSubProtocol(types.V201Subprotocol) - cs := chargingStation{responseHandler: make(chan ocpp.Response, 1), errorHandler: make(chan error, 1), callbacks: callbackqueue.New()} if endpoint == nil { dispatcher := ocppj.NewDefaultClientDispatcher(ocppj.NewFIFOClientQueue(0)) endpoint = ocppj.NewClient(id, client, dispatcher, nil, authorization.Profile, availability.Profile, data.Profile, diagnostics.Profile, display.Profile, firmware.Profile, iso15118.Profile, localauth.Profile, meter.Profile, provisioning.Profile, remotecontrol.Profile, reservation.Profile, security.Profile, smartcharging.Profile, tariffcost.Profile, transactions.Profile) } + endpoint.SetDialect(ocpp.V2) + + cs := chargingStation{ + client: endpoint, + responseHandler: make(chan ocpp.Response, 1), + errorHandler: make(chan error, 1), + callbacks: callbackqueue.New(), + } // Callback invoked by dispatcher, whenever a queued request is canceled, due to timeout. endpoint.SetOnRequestCanceled(cs.onRequestTimeout) - cs.client = endpoint cs.client.SetResponseHandler(func(confirmation ocpp.Response, requestId string) { cs.responseHandler <- confirmation @@ -382,6 +390,8 @@ type CSMS interface { // The function blocks forever, so it is suggested to wrap it in a goroutine, in case other functionality needs to be executed on the main program thread. Start(listenPort int, listenPath string) + // Stops the CSMS, clearing all pending requests. + Stop() // Errors returns a channel for error messages. If it doesn't exist it es created. Errors() <-chan error } diff --git a/ocpp2.0.1_test/common_test.go b/ocpp2.0.1_test/common_test.go index 728e25f1..d3adf79f 100644 --- a/ocpp2.0.1_test/common_test.go +++ b/ocpp2.0.1_test/common_test.go @@ -1,9 +1,12 @@ package ocpp2_test import ( + "encoding/json" "strings" "time" + "github.com/relvacode/iso8601" + "github.com/lorenzodonini/ocpp-go/ocpp2.0.1/display" "github.com/lorenzodonini/ocpp-go/ocpp2.0.1/types" ) @@ -278,3 +281,66 @@ func (suite *OcppV2TestSuite) TestMessageInfoValidation() { } ExecuteGenericTestTable(suite.T(), testTable) } + +func (suite *OcppV2TestSuite) TestUnmarshalDateTime() { + testTable := []struct { + RawDateTime string + ExpectedValid bool + ExpectedTime time.Time + ExpectedError error + }{ + {"\"2019-03-01T10:00:00Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000Z\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000+01:00\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000\"", true, time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01T10:00:00.000+01\"", true, time.Date(2019, 3, 1, 9, 0, 0, 0, time.UTC), nil}, + {"\"2019-03-01 10:00:00+00:00\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: ' '}}, + {"\"null\"", false, time.Time{}, &iso8601.UnexpectedCharacterError{Character: 110}}, + {"\"\"", false, time.Time{}, &iso8601.RangeError{Element: "month", Min: 1, Max: 12}}, + {"null", true, time.Time{}, nil}, + } + for _, dt := range testTable { + jsonStr := []byte(dt.RawDateTime) + var dateTime types.DateTime + err := json.Unmarshal(jsonStr, &dateTime) + if dt.ExpectedValid { + suite.NoError(err) + suite.NotNil(dateTime) + suite.True(dt.ExpectedTime.Equal(dateTime.Time)) + } else { + suite.Error(err) + suite.ErrorAs(err, &dt.ExpectedError) + } + } +} + +func (suite *OcppV2TestSuite) TestMarshalDateTime() { + testTable := []struct { + Time time.Time + Format string + ExpectedFormattedString string + }{ + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "", "2019-03-01T10:00:00Z"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC3339, "2019-03-01T10:00:00Z"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC822, "01 Mar 19 10:00 UTC"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), time.RFC1123, "Fri, 01 Mar 2019 10:00:00 UTC"}, + {time.Date(2019, 3, 1, 10, 0, 0, 0, time.UTC), "invalidFormat", "invalidFormat"}, + } + for _, dt := range testTable { + dateTime := types.NewDateTime(dt.Time) + types.DateTimeFormat = dt.Format + rawJson, err := dateTime.MarshalJSON() + suite.NoError(err) + formatted := strings.Trim(string(rawJson), "\"") + suite.Equal(dt.ExpectedFormattedString, formatted) + } +} + +func (suite *OcppV2TestSuite) TestNowDateTime() { + now := types.Now() + suite.NotNil(now) + suite.True(time.Now().Sub(now.Time) < 1*time.Second) +} diff --git a/ocpp2.0.1_test/ocpp2_test.go b/ocpp2.0.1_test/ocpp2_test.go index dc1cc53d..edf56201 100644 --- a/ocpp2.0.1_test/ocpp2_test.go +++ b/ocpp2.0.1_test/ocpp2_test.go @@ -7,6 +7,7 @@ import ( "net/http" "reflect" "testing" + "time" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" @@ -908,6 +909,7 @@ func setupDefaultCSMSHandlers(suite *OcppV2TestSuite, options expectedCSMSOption assert.Equal(t, options.clientId, chargingStation.ID()) }) suite.mockWsServer.On("Start", mock.AnythingOfType("int"), mock.AnythingOfType("string")).Return(options.startReturnArgument) + suite.mockWsServer.On("Stop").Return() suite.mockWsServer.On("Write", mock.AnythingOfType("string"), mock.Anything).Return(options.writeReturnArgument).Run(func(args mock.Arguments) { clientId := args.String(0) data := args.Get(1) @@ -1026,6 +1028,8 @@ func testUnsupportedRequestFromChargingStation(suite *OcppV2TestSuite, request o require.Nil(t, err) result := <-resultChannel assert.True(t, result) + // Stop the CSMS + suite.csms.Stop() } func testUnsupportedRequestFromCentralSystem(suite *OcppV2TestSuite, request ocpp.Request, requestJson string, messageId string, handlers ...interface{}) { @@ -1066,6 +1070,8 @@ func testUnsupportedRequestFromCentralSystem(suite *OcppV2TestSuite, request ocp assert.Nil(t, err) _, ok := <-resultChannel assert.True(t, ok) + // Stop the CSMS + suite.csms.Stop() } type GenericTestEntry struct { @@ -1141,6 +1147,7 @@ func (suite *OcppV2TestSuite) SetupTest() { return defaultMessageId }} ocppj.SetMessageIdGenerator(suite.messageIdGenerator.generateId) + types.DateTimeFormat = time.RFC3339 } func (suite *OcppV2TestSuite) TestIsConnected() { diff --git a/ocpp2.0.1_test/proto_test.go b/ocpp2.0.1_test/proto_test.go index 95b3af2d..5a28ecff 100644 --- a/ocpp2.0.1_test/proto_test.go +++ b/ocpp2.0.1_test/proto_test.go @@ -159,8 +159,5 @@ func (suite *OcppV2TestSuite) TestCentralSystemSendResponseError() { } func (suite *OcppV2TestSuite) TestErrorCodes() { - t := suite.T() - suite.mockWsServer.On("Start", mock.AnythingOfType("int"), mock.AnythingOfType("string")).Return(nil) - suite.csms.Start(8887, "somePath") - assert.Equal(t, ocppj.FormatViolationV2, ocppj.FormationViolation) + suite.Equal(ocppj.FormatViolationV2, ocppj.FormatErrorType(suite.ocppjServer)) } diff --git a/ocpp2.0.1_test/set_network_profile_test.go b/ocpp2.0.1_test/set_network_profile_test.go index 6e61e9c2..9e34cf66 100644 --- a/ocpp2.0.1_test/set_network_profile_test.go +++ b/ocpp2.0.1_test/set_network_profile_test.go @@ -71,7 +71,6 @@ func (suite *OcppV2TestSuite) TestSetNetworkProfileRequestValidation() { {provisioning.SetNetworkProfileRequest{ConfigurationSlot: -1, ConnectionData: provisioning.NetworkConnectionProfile{OCPPVersion: provisioning.OCPPVersion20, OCPPTransport: provisioning.OCPPTransportJSON, CSMSUrl: "http://someUrl:8767", MessageTimeout: 30, SecurityProfile: 1, OCPPInterface: provisioning.OCPPInterfaceWired0, VPN: vpn, APN: apn}}, false}, {provisioning.SetNetworkProfileRequest{ConfigurationSlot: 2, ConnectionData: provisioning.NetworkConnectionProfile{OCPPVersion: "OCPP01", OCPPTransport: provisioning.OCPPTransportJSON, CSMSUrl: "http://someUrl:8767", MessageTimeout: 30, SecurityProfile: 1, OCPPInterface: provisioning.OCPPInterfaceWired0, VPN: vpn, APN: apn}}, false}, {provisioning.SetNetworkProfileRequest{ConfigurationSlot: 2, ConnectionData: provisioning.NetworkConnectionProfile{OCPPVersion: provisioning.OCPPVersion20, OCPPTransport: "ProtoBuf", CSMSUrl: "http://someUrl:8767", MessageTimeout: 30, SecurityProfile: 1, OCPPInterface: provisioning.OCPPInterfaceWired0, VPN: vpn, APN: apn}}, false}, - {provisioning.SetNetworkProfileRequest{ConfigurationSlot: 2, ConnectionData: provisioning.NetworkConnectionProfile{OCPPVersion: provisioning.OCPPVersion20, OCPPTransport: provisioning.OCPPTransportJSON, CSMSUrl: "http://invalidUrl{}", MessageTimeout: 30, SecurityProfile: 1, OCPPInterface: provisioning.OCPPInterfaceWired0, VPN: vpn, APN: apn}}, false}, {provisioning.SetNetworkProfileRequest{ConfigurationSlot: 2, ConnectionData: provisioning.NetworkConnectionProfile{OCPPVersion: provisioning.OCPPVersion20, OCPPTransport: provisioning.OCPPTransportJSON, CSMSUrl: ">512.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................", MessageTimeout: 30, SecurityProfile: 1, OCPPInterface: provisioning.OCPPInterfaceWired0, VPN: vpn, APN: apn}}, false}, {provisioning.SetNetworkProfileRequest{ConfigurationSlot: 2, ConnectionData: provisioning.NetworkConnectionProfile{OCPPVersion: provisioning.OCPPVersion20, OCPPTransport: provisioning.OCPPTransportJSON, CSMSUrl: "http://someUrl:8767", MessageTimeout: -2, SecurityProfile: 1, OCPPInterface: provisioning.OCPPInterfaceWired0, VPN: vpn, APN: apn}}, false}, {provisioning.SetNetworkProfileRequest{ConfigurationSlot: 2, ConnectionData: provisioning.NetworkConnectionProfile{OCPPVersion: provisioning.OCPPVersion20, OCPPTransport: provisioning.OCPPTransportJSON, CSMSUrl: "http://someUrl:8767", MessageTimeout: 30, SecurityProfile: 1, OCPPInterface: "invalidInterface", VPN: vpn, APN: apn}}, false}, diff --git a/ocppj/central_system_test.go b/ocppj/central_system_test.go index 5d6ec7ed..e2506f68 100644 --- a/ocppj/central_system_test.go +++ b/ocppj/central_system_test.go @@ -117,7 +117,7 @@ func (suite *OcppJTestSuite) TestCentralSystemInvalidMessageHook() { serializedPayload, err := json.Marshal(mockPayload) require.NoError(t, err) invalidMessage := fmt.Sprintf("[2,\"%v\",\"%s\",%v]", mockID, MockFeatureName, string(serializedPayload)) - expectedError := fmt.Sprintf("[4,\"%v\",\"%v\",\"%v\",{}]", mockID, ocppj.FormationViolation, "json: cannot unmarshal number into Go struct field MockRequest.mockValue of type string") + expectedError := fmt.Sprintf("[4,\"%v\",\"%v\",\"%v\",{}]", mockID, ocppj.FormatErrorType(suite.centralSystem), "json: cannot unmarshal number into Go struct field MockRequest.mockValue of type string") writeHook := suite.mockServer.On("Write", mockChargePointId, mock.Anything).Return(nil).Run(func(args mock.Arguments) { data := args.Get(1).([]byte) assert.Equal(t, expectedError, string(data)) @@ -138,7 +138,7 @@ func (suite *OcppJTestSuite) TestCentralSystemInvalidMessageHook() { err = suite.mockServer.MessageHandler(mockChargePoint, []byte(invalidMessage)) ocppErr, ok := err.(*ocpp.Error) require.True(t, ok) - assert.Equal(t, ocppj.FormationViolation, ocppErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.centralSystem), ocppErr.Code) // Setup hook 2 mockError := ocpp.NewError(ocppj.InternalError, "custom error", mockID) expectedError = fmt.Sprintf("[4,\"%v\",\"%v\",\"%v\",{}]", mockError.MessageId, mockError.Code, mockError.Description) @@ -197,7 +197,7 @@ func (suite *OcppJTestSuite) TestCentralSystemSendRequestFailed() { suite.serverDispatcher.CreateClient(mockChargePointId) mockRequest := newMockRequest("mockValue") err := suite.centralSystem.SendRequest(mockChargePointId, mockRequest) - //TODO: currently the network error is not returned by SendRequest, but is only generated internally + // TODO: currently the network error is not returned by SendRequest, but is only generated internally assert.Nil(t, err) // Assert that pending request was removed time.Sleep(500 * time.Millisecond) @@ -578,7 +578,7 @@ func (suite *OcppJTestSuite) TestEnqueueMultipleRequests() { assert.False(t, q.IsEmpty()) assert.Equal(t, messagesToQueue, q.Size()) // Analyze enqueued bundle - var i = 0 + var i int for !q.IsEmpty() { popped := q.Pop() require.NotNil(t, popped) diff --git a/ocppj/charge_point_test.go b/ocppj/charge_point_test.go index e0da3a16..aea81dde 100644 --- a/ocppj/charge_point_test.go +++ b/ocppj/charge_point_test.go @@ -122,7 +122,7 @@ func (suite *OcppJTestSuite) TestChargePointInvalidMessageHook() { serializedPayload, err := json.Marshal(mockPayload) require.NoError(t, err) invalidMessage := fmt.Sprintf("[2,\"%v\",\"%s\",%v]", mockID, MockFeatureName, string(serializedPayload)) - expectedError := fmt.Sprintf("[4,\"%v\",\"%v\",\"%v\",{}]", mockID, ocppj.FormationViolation, "json: cannot unmarshal number into Go struct field MockRequest.mockValue of type string") + expectedError := fmt.Sprintf("[4,\"%v\",\"%v\",\"%v\",{}]", mockID, ocppj.FormatErrorType(suite.chargePoint), "json: cannot unmarshal number into Go struct field MockRequest.mockValue of type string") writeHook := suite.mockClient.On("Write", mock.Anything).Return(nil).Run(func(args mock.Arguments) { data := args.Get(0).([]byte) assert.Equal(t, expectedError, string(data)) @@ -142,7 +142,7 @@ func (suite *OcppJTestSuite) TestChargePointInvalidMessageHook() { err = suite.mockClient.MessageHandler([]byte(invalidMessage)) ocppErr, ok := err.(*ocpp.Error) require.True(t, ok) - assert.Equal(t, ocppj.FormationViolation, ocppErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), ocppErr.Code) // Setup hook 2 mockError := ocpp.NewError(ocppj.InternalError, "custom error", mockID) expectedError = fmt.Sprintf("[4,\"%v\",\"%v\",\"%v\",{}]", mockError.MessageId, mockError.Code, mockError.Description) @@ -193,7 +193,7 @@ func (suite *OcppJTestSuite) TestChargePointSendRequestFailed() { _ = suite.chargePoint.Start("someUrl") mockRequest := newMockRequest("mockValue") err := suite.chargePoint.SendRequest(mockRequest) - //TODO: currently the network error is not returned by SendRequest, but is only generated internally + // TODO: currently the network error is not returned by SendRequest, but is only generated internally assert.Nil(t, err) // Assert that pending request was removed time.Sleep(500 * time.Millisecond) @@ -472,7 +472,7 @@ func (suite *OcppJTestSuite) TestClientEnqueueMultipleRequests() { require.False(t, suite.clientRequestQueue.IsEmpty()) assert.Equal(t, messagesToQueue, suite.clientRequestQueue.Size()) // Analyze enqueued bundle - var i = 0 + var i int for !suite.clientRequestQueue.IsEmpty() { popped := suite.clientRequestQueue.Pop() require.NotNil(t, popped) diff --git a/ocppj/ocppj.go b/ocppj/ocppj.go index 5999eaaf..5688e42a 100644 --- a/ocppj/ocppj.go +++ b/ocppj/ocppj.go @@ -198,14 +198,25 @@ const ( FormatViolationV16 ocpp.ErrorCode = "FormationViolation" // Payload for Action is syntactically incorrect or not conform the PDU structure for Action. This is only valid for OCPP 1.6 ) -var ( - FormationViolation = FormatViolationV16 // Used as constant, but can be overwritten depending on protocol version. Sett FormatViolationV16 and FormatViolationV2. -) +type dialector interface { + Dialect() ocpp.Dialect +} + +func FormatErrorType(d dialector) ocpp.ErrorCode { + switch d.Dialect() { + case ocpp.V16: + return FormatViolationV16 + case ocpp.V2: + return FormatViolationV2 + default: + panic(fmt.Sprintf("invalid dialect: %v", d)) + } +} func IsErrorCodeValid(fl validator.FieldLevel) bool { code := ocpp.ErrorCode(fl.Field().String()) switch code { - case NotImplemented, NotSupported, InternalError, MessageTypeNotSupported, ProtocolError, SecurityError, FormationViolation, PropertyConstraintViolation, OccurrenceConstraintViolation, TypeConstraintViolation, GenericError: + case NotImplemented, NotSupported, InternalError, MessageTypeNotSupported, ProtocolError, SecurityError, FormatViolationV16, FormatViolationV2, PropertyConstraintViolation, OccurrenceConstraintViolation, TypeConstraintViolation, GenericError: return true } return false @@ -308,9 +319,20 @@ func jsonMarshal(t interface{}) ([]byte, error) { // An OCPP-J endpoint is one of the two entities taking part in the communication. // The endpoint keeps state for supported OCPP profiles and current pending requests. type Endpoint struct { + dialect ocpp.Dialect Profiles []*ocpp.Profile } +// Sets endpoint dialect. +func (endpoint *Endpoint) SetDialect(d ocpp.Dialect) { + endpoint.dialect = d +} + +// Gets endpoint dialect. +func (endpoint *Endpoint) Dialect() ocpp.Dialect { + return endpoint.dialect +} + // Adds support for a new profile on the endpoint. func (endpoint *Endpoint) AddProfile(profile *ocpp.Profile) { endpoint.Profiles = append(endpoint.Profiles, profile) @@ -378,25 +400,25 @@ func parseRawJsonConfirmation(raw interface{}, confirmationType reflect.Type) (o func (endpoint *Endpoint) ParseMessage(arr []interface{}, pendingRequestState ClientState) (Message, error) { // Checking message fields if len(arr) < 3 { - return nil, ocpp.NewError(FormationViolation, "Invalid message. Expected array length >= 3", "") + return nil, ocpp.NewError(FormatErrorType(endpoint), "Invalid message. Expected array length >= 3", "") } rawTypeId, ok := arr[0].(float64) if !ok { - return nil, ocpp.NewError(FormationViolation, fmt.Sprintf("Invalid element %v at 0, expected message type (int)", arr[0]), "") + return nil, ocpp.NewError(FormatErrorType(endpoint), fmt.Sprintf("Invalid element %v at 0, expected message type (int)", arr[0]), "") } typeId := MessageType(rawTypeId) uniqueId, ok := arr[1].(string) if !ok { - return nil, ocpp.NewError(FormationViolation, fmt.Sprintf("Invalid element %v at 1, expected unique ID (string)", arr[1]), "") + return nil, ocpp.NewError(FormatErrorType(endpoint), fmt.Sprintf("Invalid element %v at 1, expected unique ID (string)", arr[1]), uniqueId) } // Parse message if typeId == CALL { if len(arr) != 4 { - return nil, ocpp.NewError(FormationViolation, "Invalid Call message. Expected array length 4", uniqueId) + return nil, ocpp.NewError(FormatErrorType(endpoint), "Invalid Call message. Expected array length 4", uniqueId) } action, ok := arr[2].(string) if !ok { - return nil, ocpp.NewError(FormationViolation, fmt.Sprintf("Invalid element %v at 2, expected action (string)", arr[2]), uniqueId) + return nil, ocpp.NewError(FormatErrorType(endpoint), fmt.Sprintf("Invalid element %v at 2, expected action (string)", arr[2]), uniqueId) } profile, ok := endpoint.GetProfileForFeature(action) @@ -405,7 +427,7 @@ func (endpoint *Endpoint) ParseMessage(arr []interface{}, pendingRequestState Cl } request, err := profile.ParseRequest(action, arr[3], parseRawJsonRequest) if err != nil { - return nil, ocpp.NewError(FormationViolation, err.Error(), uniqueId) + return nil, ocpp.NewError(FormatErrorType(endpoint), err.Error(), uniqueId) } call := Call{ MessageTypeId: CALL, @@ -427,7 +449,7 @@ func (endpoint *Endpoint) ParseMessage(arr []interface{}, pendingRequestState Cl profile, _ := endpoint.GetProfileForFeature(request.GetFeatureName()) confirmation, err := profile.ParseResponse(request.GetFeatureName(), arr[2], parseRawJsonConfirmation) if err != nil { - return nil, ocpp.NewError(FormationViolation, err.Error(), uniqueId) + return nil, ocpp.NewError(FormatErrorType(endpoint), err.Error(), uniqueId) } callResult := CallResult{ MessageTypeId: CALL_RESULT, @@ -446,7 +468,7 @@ func (endpoint *Endpoint) ParseMessage(arr []interface{}, pendingRequestState Cl return nil, nil } if len(arr) < 4 { - return nil, ocpp.NewError(FormationViolation, "Invalid Call Error message. Expected array length >= 4", uniqueId) + return nil, ocpp.NewError(FormatErrorType(endpoint), "Invalid Call Error message. Expected array length >= 4", uniqueId) } var details interface{} if len(arr) > 4 { @@ -454,7 +476,7 @@ func (endpoint *Endpoint) ParseMessage(arr []interface{}, pendingRequestState Cl } rawErrorCode, ok := arr[2].(string) if !ok { - return nil, ocpp.NewError(FormationViolation, fmt.Sprintf("Invalid element %v at 2, expected rawErrorCode (string)", arr[2]), rawErrorCode) + return nil, ocpp.NewError(FormatErrorType(endpoint), fmt.Sprintf("Invalid element %v at 2, expected rawErrorCode (string)", arr[2]), rawErrorCode) } errorCode := ocpp.ErrorCode(rawErrorCode) errorDescription := "" diff --git a/ocppj/ocppj_test.go b/ocppj/ocppj_test.go index dcdbb1d4..97a6f984 100644 --- a/ocppj/ocppj_test.go +++ b/ocppj/ocppj_test.go @@ -29,7 +29,6 @@ type MockWebSocket struct { func (websocket MockWebSocket) ID() string { return websocket.id - } func (websocket MockWebSocket) RemoteAddr() net.Addr { @@ -375,6 +374,9 @@ func (suite *OcppJTestSuite) SetupTest() { suite.serverRequestMap = ocppj.NewFIFOQueueMap(queueCapacity) suite.serverDispatcher = ocppj.NewDefaultServerDispatcher(suite.serverRequestMap) suite.centralSystem = ocppj.NewServer(suite.mockServer, suite.serverDispatcher, nil, mockProfile) + defaultDialect := ocpp.V16 // set default to version 1.6 format error *for test only + suite.centralSystem.SetDialect(defaultDialect) + suite.chargePoint.SetDialect(defaultDialect) } func (suite *OcppJTestSuite) TearDownTest() { @@ -405,8 +407,6 @@ func (suite *OcppJTestSuite) TestGetProfileForFeature() { assert.Equal(t, "mock", profile.Name) } -//func (suite *OcppJTestSuite) TestAddFeature - func (suite *OcppJTestSuite) TestGetProfileForInvalidFeature() { t := suite.T() profile, ok := suite.chargePoint.GetProfileForFeature("test") @@ -535,7 +535,7 @@ func (suite *OcppJTestSuite) TestParseMessageInvalidLength() { protoErr := err.(*ocpp.Error) require.NotNil(t, protoErr) assert.Equal(t, "", protoErr.MessageId) - assert.Equal(t, ocppj.FormationViolation, protoErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), protoErr.Code) assert.Equal(t, "Invalid message. Expected array length >= 3", protoErr.Description) } @@ -553,7 +553,7 @@ func (suite *OcppJTestSuite) TestParseMessageInvalidTypeId() { protoErr := err.(*ocpp.Error) require.NotNil(t, protoErr) assert.Equal(t, "", protoErr.MessageId) - assert.Equal(t, ocppj.FormationViolation, protoErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), protoErr.Code) assert.Equal(t, fmt.Sprintf("Invalid element %v at 0, expected message type (int)", invalidTypeId), protoErr.Description) } @@ -570,7 +570,7 @@ func (suite *OcppJTestSuite) TestParseMessageInvalidMessageId() { protoErr := err.(*ocpp.Error) require.NotNil(t, protoErr) assert.Equal(t, "", protoErr.MessageId) - assert.Equal(t, ocppj.FormationViolation, protoErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), protoErr.Code) assert.Equal(t, fmt.Sprintf("Invalid element %v at 1, expected unique ID (string)", invalidMessageId), protoErr.Description) } @@ -625,7 +625,7 @@ func (suite *OcppJTestSuite) TestParseMessageInvalidCall() { protoErr := err.(*ocpp.Error) require.NotNil(t, protoErr) assert.Equal(t, messageId, protoErr.MessageId) - assert.Equal(t, ocppj.FormationViolation, protoErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), protoErr.Code) assert.Equal(t, "Invalid Call message. Expected array length 4", protoErr.Description) } @@ -645,7 +645,7 @@ func (suite *OcppJTestSuite) TestParseMessageInvalidActionCall() { protoErr := err.(*ocpp.Error) require.NotNil(t, protoErr) assert.Equal(t, protoErr.MessageId, messageId) // unique id is returned even after invalid type cast error - assert.Equal(t, ocppj.FormationViolation, protoErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), protoErr.Code) assert.Equal(t, "Invalid element 42 at 2, expected action (string)", protoErr.Description) } @@ -680,7 +680,7 @@ func (suite *OcppJTestSuite) TestParseMessageInvalidCallError() { protoErr := err.(*ocpp.Error) require.NotNil(t, protoErr) assert.Equal(t, messageId, protoErr.MessageId) - assert.Equal(t, ocppj.FormationViolation, protoErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), protoErr.Code) assert.Equal(t, "Invalid Call Error message. Expected array length >= 4", protoErr.Description) } @@ -701,7 +701,7 @@ func (suite *OcppJTestSuite) TestParseMessageInvalidRawErrorCode() { protoErr := err.(*ocpp.Error) require.NotNil(t, protoErr) assert.Equal(t, protoErr.MessageId, "") // unique id is never set after invalid type cast return - assert.Equal(t, ocppj.FormationViolation, protoErr.Code) + assert.Equal(t, ocppj.FormatErrorType(suite.chargePoint), protoErr.Code) assert.Equal(t, "Invalid element 42 at 2, expected rawErrorCode (string)", protoErr.Description) } @@ -787,8 +787,7 @@ func (suite *OcppJTestSuite) TestParseCall() { assert.Equal(t, mockValue, mockRequest.MockValue) } -//TODO: implement further ocpp-j protocol tests - +// TODO: implement further ocpp-j protocol tests type testLogger struct { c chan string } @@ -796,18 +795,23 @@ type testLogger struct { func (l *testLogger) Debug(args ...interface{}) { l.c <- "debug" } + func (l *testLogger) Debugf(format string, args ...interface{}) { l.c <- "debugf" } + func (l *testLogger) Info(args ...interface{}) { l.c <- "info" } + func (l *testLogger) Infof(format string, args ...interface{}) { l.c <- "infof" } + func (l *testLogger) Error(args ...interface{}) { l.c <- "error" } + func (l *testLogger) Errorf(format string, args ...interface{}) { l.c <- "errorf" }