Skip to content

Commit

Permalink
Fix validation for GetCompositeSchedule, run gofmt, run go vet and fi…
Browse files Browse the repository at this point in the history
…x a few implicitly copied locks
  • Loading branch information
initialed85 authored and lorenzodonini committed Jul 2, 2024
1 parent 9b48003 commit 9041b9f
Show file tree
Hide file tree
Showing 20 changed files with 99 additions and 56 deletions.
71 changes: 56 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ There are currently no plans of supporting OCPP-S.

Planned milestones and features:

- [x] OCPP 1.6
- [x] OCPP 2.0.1 (examples working, but will need more real-world testing)
- [ ] Dedicated package for configuration management
- [x] OCPP 1.6
- [x] OCPP 2.0.1 (examples working, but will need more real-world testing)
- [ ] Dedicated package for configuration management

## OCPP 1.6 Usage

Expand All @@ -31,6 +31,7 @@ go get github.com/lorenzodonini/ocpp-go
```

You will also need to fetch some dependencies:

```sh
cd <path-to-ocpp-go>
export GO111MODULE=on
Expand All @@ -42,6 +43,7 @@ Your application may either act as a [Central System](#central-system) (server)
### Central System

If you want to integrate the library into your custom Central System, you must implement the callbacks defined in the profile interfaces, e.g.:

```go
import (
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
Expand All @@ -65,7 +67,7 @@ func (handler *CentralSystemHandler) OnBootNotification(chargePointId string, re
return core.NewBootNotificationConfirmation(types.NewDateTime(time.Now()), defaultHeartbeatInterval, core.RegistrationStatusAccepted), nil
}

// further callbacks...
// further callbacks...
```

Every time a request from the charge point comes in, the respective callback function is called.
Expand All @@ -77,6 +79,7 @@ You need to implement at least all other callbacks defined in the `core.CentralS
Depending on which OCPP profiles you want to support in your application, you will need to implement additional callbacks as well.

To start a central system instance, simply run the following:

```go
centralSystem := ocpp16.NewCentralSystem(nil, nil)

Expand All @@ -102,6 +105,7 @@ log.Println("stopped central system")
#### Sending requests

To send requests to the charge point, you may either use the simplified API:

```go
err := centralSystem.ChangeAvailability("1234", myCallback, 1, core.AvailabilityTypeInoperative)
if err != nil {
Expand All @@ -110,6 +114,7 @@ if err != nil {
```

or create a message manually:

```go
request := core.NewChangeAvailabilityRequest(1, core.AvailabilityTypeInoperative)
err := centralSystem.SendRequestAsync("clientId", request, callbackFunction)
Expand All @@ -118,8 +123,9 @@ if err != nil {
}
```

In both cases, the request is sent asynchronously and the function returns right away.
In both cases, the request is sent asynchronously and the function returns right away.
You need to write the callback function to check for errors and handle the confirmation on your own:

```go
myCallback := func(confirmation *core.ChangeAvailabilityConfirmation, e error) {
if e != nil {
Expand All @@ -137,19 +143,22 @@ Since the initial `centralSystem.Start` call blocks forever, you may want to wra

You can take a look at the [full example](./example/1.6/cs/central_system_sim.go).
To run it, simply execute:

```bash
go run ./example/1.6/cs/*.go
```

#### Docker

A containerized version of the central system example is available:

```bash
docker pull ldonini/ocpp1.6-central-system:latest
docker run -it -p 8887:8887 --rm --name central-system ldonini/ocpp1.6-central-system:latest
```

You can also run it directly using docker-compose:

```sh
docker-compose -f example/1.6/docker-compose.yml up central-system
```
Expand All @@ -158,16 +167,18 @@ docker-compose -f example/1.6/docker-compose.yml up central-system

If you wish to test the central system using TLS, make sure you put your self-signed certificates inside the `example/1.6/certs` folder.

Feel free to use the utility script `cd example/1.6 && ./create-test-certificates.sh` for generating test certificates.
Feel free to use the utility script `cd example/1.6 && ./create-test-certificates.sh` for generating test certificates.

Then run the following:

```
docker-compose -f example/1.6/docker-compose.tls.yml up central-system
```

### Charge Point

If you want to integrate the library into your custom Charge Point, you must implement the callbacks defined in the profile interfaces, e.g.:

```go
import (
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
Expand Down Expand Up @@ -199,6 +210,7 @@ You need to implement at least all other callbacks defined in the `core.ChargePo
Depending on which OCPP profiles you want to support in your application, you will need to implement additional callbacks as well.

To start a charge point instance, simply run the following:

```go
chargePointId := "cp0001"
csUrl = "ws://localhost:8887"
Expand All @@ -213,7 +225,7 @@ err := chargePoint.Start(csUrl)
if err != nil {
log.Println(err)
} else {
log.Printf("connected to central system at %v", csUrl)
log.Printf("connected to central system at %v", csUrl)
mainRoutine() // ... your program logic goes here
}

Expand All @@ -225,6 +237,7 @@ log.Printf("disconnected from central system")
#### Sending requests

To send requests to the central station, you have two options. You may either use the simplified synchronous blocking API (recommended):

```go
bootConf, err := chargePoint.BootNotification("model1", "vendor1")
if err != nil {
Expand All @@ -236,11 +249,13 @@ if err != nil {
```

or create a message manually:

```go
request := core.NewBootNotificationRequest("model1", "vendor1")
```

You can then decide to send the message using a synchronous blocking call:

```go
// Synchronous call
confirmation, err := chargePoint.SendRequest(request)
Expand All @@ -250,7 +265,9 @@ if err != nil {
bootConf := confirmation.(*core.BootNotificationConfirmation)
// ... do something with the confirmation
```

or an asynchronous call:

```go
// Asynchronous call
err := chargePoint.SendRequestAsync(request, callbackFunction)
Expand All @@ -260,6 +277,7 @@ if err != nil {
```

In the latter case, you need to write the callback function and check for errors on your own:

```go
callback := func(confirmation ocpp.Response, e error) {
bootConf := confirmation.(*core.BootNotificationConfirmation)
Expand All @@ -275,8 +293,10 @@ callback := func(confirmation ocpp.Response, e error) {
When creating a message manually, you always need to perform type assertion yourself, as the `SendRequest` and `SendRequestAsync` APIs use generic `Request` and `Confirmation` interfaces.

#### Example

You can take a look at the [full example](./example/1.6/cp/charge_point_sim.go).
To run it, simply execute:

```bash
CLIENT_ID=chargePointSim CENTRAL_SYSTEM_URL=ws://<host>:8887 go run example/1.6/cp/*.go
```
Expand All @@ -286,6 +306,7 @@ You need to specify the URL of a running central station server via environment
#### Docker

A containerized version of the charge point example is available:

```bash
docker pull ldonini/ocpp1.6-charge-point:latest
docker run -e CLIENT_ID=chargePointSim -e CENTRAL_SYSTEM_URL=ws://<host>:8887 -it --rm --name charge-point ldonini/ocpp1.6-charge-point:latest
Expand All @@ -294,6 +315,7 @@ docker run -e CLIENT_ID=chargePointSim -e CENTRAL_SYSTEM_URL=ws://<host>:8887 -i
You need to specify the host, on which the central system is running, in order for the charge point to connect to it.

You can also run it directly using docker-compose:

```sh
docker-compose -f example/1.6/docker-compose.yml up charge-point
```
Expand All @@ -302,9 +324,10 @@ docker-compose -f example/1.6/docker-compose.yml up charge-point

If you wish to test the charge point using TLS, make sure you put your self-signed certificates inside the `example/1.6/certs` folder.

Feel free to use the utility script `cd example/1.6 && ./create-test-certificates.sh` for generating test certificates.
Feel free to use the utility script `cd example/1.6 && ./create-test-certificates.sh` for generating test certificates.

Then run the following:

```
docker-compose -f example/1.6/docker-compose.tls.yml up charge-point
```
Expand All @@ -319,18 +342,20 @@ All incoming and outgoing messages are validated by default, using the [validato
Constraints are defined on every request/response struct, as per OCPP specs.

Validation may be disabled at a package level if needed:

```go
ocppj.SetMessageValidation(false)
```
```

Use at your own risk, as this will disable validation for all messages!

> I will be evaluating the possibility to selectively disable validation for a specific message,
> I will be evaluating the possibility to selectively disable validation for a specific message,
> e.g. by passing message options.
### Verbose logging

The `ws` and `ocppj` packages offer the possibility to enable verbose logs, via your logger of choice, e.g.:

```go
// Setup your own logger
log = logrus.New()
Expand All @@ -340,17 +365,19 @@ log.SetLevel(logrus.DebugLevel) // Debug level needed to see all logs
ws.SetLogger(log.WithField("logger", "websocket"))
ocppj.SetLogger(log.WithField("logger", "ocppj"))
```

The logger you pass needs to conform to the `logging.Logger` interface.
Commonly used logging libraries, such as zap or logrus, adhere to this interface out-of-the-box.

If you are using a logger, that isn't conform, you can simply write an adapter between the `Logger` interface and your own logging system.

### Websocket ping-pong

The websocket package currently supports client-initiated pings only.
The websocket package currently supports client-initiated pings only.

If your setup requires the server to be the initiator of a ping-pong (e.g. for web-based charge points),
you may disable ping-pong entirely and just rely on the heartbeat mechanism:

```go
cfg := ws.NewServerTimeoutConfig()
cfg.PingWait = 0 // this instructs the server to wait forever
Expand All @@ -366,7 +393,7 @@ Experimental support for version 2.0.1 is now supported!
> Version 2.0 was skipped entirely, since it is considered obsolete.
Requests and responses in OCPP 2.0.1 are handled the same way they were in v1.6.
The notable change is that there are now significantly more supported messages and profiles (feature sets),
The notable change is that there are now significantly more supported messages and profiles (feature sets),
which also require their own handlers to be implemented.

The library API to the lower websocket and ocpp-j layers remains unchanged.
Expand All @@ -383,6 +410,7 @@ More in-depth documentation for v2.0.1 will follow.
### CSMS

To start a CSMS instance, run the following:

```go
import "github.com/lorenzodonini/ocpp-go/ocpp2.0.1"

Expand Down Expand Up @@ -420,6 +448,7 @@ log.Println("stopped CSMS")
#### Sending requests

Similarly to v1.6, you may send requests using the simplified API, e.g.

```go
err := csms.GetLocalListVersion(chargingStationID, myCallback)
if err != nil {
Expand All @@ -432,11 +461,12 @@ Or you may build requests manually and send them using the asynchronous API.
#### Docker image

There is a Dockerfile and a docker image available upstream.
Feel free
Feel free

### Charging Station

To start a charging station instance, simply run the following:

```go
chargingStationID := "cs0001"
csmsUrl = "ws://localhost:8887"
Expand Down Expand Up @@ -464,7 +494,7 @@ err := chargingStation.Start(csmsUrl)
if err != nil {
log.Println(err)
} else {
log.Printf("connected to CSMS at %v", csmsUrl)
log.Printf("connected to CSMS at %v", csmsUrl)
mainRoutine() // ... your program logic goes here
}

Expand All @@ -476,6 +506,7 @@ log.Println("disconnected from CSMS")
#### Sending requests

Similarly to v1.6 you may send requests using the simplified API (recommended), e.g.

```go
bootResp, err := chargingStation.BootNotification(provisioning.BootReasonPowerUp, "model1", "vendor1")
if err != nil {
Expand All @@ -485,4 +516,14 @@ if err != nil {
}
```

Or you may build requests manually and send them using either the synchronous or asynchronous API.
Or you may build requests manually and send them using either the synchronous or asynchronous API.

#### Contributing

If you're contributing a code change, you'll want to be sure the tests are passing first; here are the steps to check that:

- Install [toxiproxy](https://github.com/Shopify/toxiproxy) for your platform
- Shell 1 - `cd example/1.6 && ./create-test-certificates.sh ; cd ../..`
- Shell 1 - `cd example/2.0.1 && ./create-test-certificates.sh ; cd ../..`
- Shell 1 - `toxiproxy-server -port 8474 -host localhost`
- Shell 2 - `go fmt ./... && go vet ./... && go test -v -count=1 -failfast ./...`
2 changes: 1 addition & 1 deletion ocpp1.6/core/stop_transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func NewStopTransactionConfirmation() *StopTransactionConfirmation {
return &StopTransactionConfirmation{}
}

//TODO: advanced validation
// TODO: advanced validation
func init() {
_ = types.Validate.RegisterValidation("reason", isValidReason)
}
2 changes: 1 addition & 1 deletion ocpp1.6/smartcharging/get_composite_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type GetCompositeScheduleRequest struct {
// In case the request was invalid, or couldn't be processed, an error will be sent instead.
type GetCompositeScheduleConfirmation struct {
Status GetCompositeScheduleStatus `json:"status" validate:"required,compositeScheduleStatus"`
ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gt=0"`
ConnectorId *int `json:"connectorId,omitempty" validate:"omitempty,gte=0"`
ScheduleStart *types.DateTime `json:"scheduleStart,omitempty"`
ChargingSchedule *types.ChargingSchedule `json:"chargingSchedule,omitempty" validate:"omitempty"`
}
Expand Down
4 changes: 2 additions & 2 deletions ocpp1.6_test/cancel_reservation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (suite *OcppV16TestSuite) TestCancelReservationE2EMocked() {
cancelReservationConfirmation := reservation.NewCancelReservationConfirmation(status)
channel := NewMockWebSocket(wsId)

reservationListener := MockChargePointReservationListener{}
reservationListener := &MockChargePointReservationListener{}
reservationListener.On("OnCancelReservation", mock.Anything).Return(cancelReservationConfirmation, nil).Run(func(args mock.Arguments) {
request, ok := args.Get(0).(*reservation.CancelReservationRequest)
require.NotNil(t, request)
Expand All @@ -51,7 +51,7 @@ func (suite *OcppV16TestSuite) TestCancelReservationE2EMocked() {
})
setupDefaultCentralSystemHandlers(suite, nil, expectedCentralSystemOptions{clientId: wsId, rawWrittenMessage: []byte(requestJson), forwardWrittenMessage: true})
setupDefaultChargePointHandlers(suite, nil, expectedChargePointOptions{serverUrl: wsUrl, clientId: wsId, createChannelOnStart: true, channel: channel, rawWrittenMessage: []byte(responseJson), forwardWrittenMessage: true})
suite.chargePoint.SetReservationHandler(&reservationListener)
suite.chargePoint.SetReservationHandler(reservationListener)
// Run Test
suite.centralSystem.Start(8887, "somePath")
err := suite.chargePoint.Start(wsUrl)
Expand Down
4 changes: 2 additions & 2 deletions ocpp1.6_test/clear_charging_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (suite *OcppV16TestSuite) TestClearChargingProfileE2EMocked() {
ClearChargingProfileConfirmation := smartcharging.NewClearChargingProfileConfirmation(status)
channel := NewMockWebSocket(wsId)

smartChargingListener := MockChargePointSmartChargingListener{}
smartChargingListener := &MockChargePointSmartChargingListener{}
smartChargingListener.On("OnClearChargingProfile", mock.Anything).Return(ClearChargingProfileConfirmation, nil).Run(func(args mock.Arguments) {
request, ok := args.Get(0).(*smartcharging.ClearChargingProfileRequest)
require.True(t, ok)
Expand All @@ -65,7 +65,7 @@ func (suite *OcppV16TestSuite) TestClearChargingProfileE2EMocked() {
})
setupDefaultCentralSystemHandlers(suite, nil, expectedCentralSystemOptions{clientId: wsId, rawWrittenMessage: []byte(requestJson), forwardWrittenMessage: true})
setupDefaultChargePointHandlers(suite, nil, expectedChargePointOptions{serverUrl: wsUrl, clientId: wsId, createChannelOnStart: true, channel: channel, rawWrittenMessage: []byte(responseJson), forwardWrittenMessage: true})
suite.chargePoint.SetSmartChargingHandler(&smartChargingListener)
suite.chargePoint.SetSmartChargingHandler(smartChargingListener)
// Run Test
suite.centralSystem.Start(8887, "somePath")
err := suite.chargePoint.Start(wsUrl)
Expand Down
Loading

0 comments on commit 9041b9f

Please sign in to comment.