Skip to content

Commit

Permalink
Merge branch 'master' into group-id-token-not-required
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonesywolf committed Jan 26, 2024
2 parents 50b0651 + 1bc3b3a commit 92de9a9
Show file tree
Hide file tree
Showing 23 changed files with 319 additions and 100 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
9 changes: 9 additions & 0 deletions ocpp/ocpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
7 changes: 5 additions & 2 deletions ocpp1.6/central_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions ocpp1.6/charge_point.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
51 changes: 31 additions & 20 deletions ocpp1.6/types/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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) {
Expand Down
15 changes: 12 additions & 3 deletions ocpp1.6/v16.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
70 changes: 69 additions & 1 deletion ocpp1.6_test/common_test.go
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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)
}
10 changes: 8 additions & 2 deletions ocpp1.6_test/ocpp16_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"reflect"
"testing"
"time"

"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -708,6 +714,7 @@ func (suite *OcppV16TestSuite) SetupTest() {
return defaultMessageId
}}
ocppj.SetMessageIdGenerator(suite.messageIdGenerator.generateId)
types.DateTimeFormat = time.RFC3339
}

func (suite *OcppV16TestSuite) TestIsConnected() {
Expand All @@ -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))
}
5 changes: 1 addition & 4 deletions ocpp1.6_test/proto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
2 changes: 0 additions & 2 deletions ocpp2.0.1/charging_station.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 5 additions & 2 deletions ocpp2.0.1/csms.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion ocpp2.0.1/provisioning/set_network_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 92de9a9

Please sign in to comment.