Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Planned milestones and features:
- [x] OCPP 1.6 Security extension (documentation available [here](docs/ocpp1.6-security-extension.md))
- [x] OCPP 2.0.1 (examples working, but will need more real-world testing) (documentation
available [here](docs/ocpp-2.0.1.md))
- [x] OCPP 2.1 (beta) (documentation available [here](docs/ocpp-2.1.md))

### Features

Expand Down Expand Up @@ -95,15 +96,18 @@ The websocket package supports configuring ping pong for both endpoints.

By default, the client sends a ping every 54 seconds and waits for a pong for 60 seconds, before timing out.
The values can be configured as follows:

```go
cfg := ws.NewClientTimeoutConfig()
cfg.PingPeriod = 10 * time.Second
cfg.PongWait = 20 * time.Second
websocketClient.SetTimeoutConfig(cfg)
```

By default, the server does not send out any pings and waits for a ping from the client for 60 seconds, before timing out.
By default, the server does not send out any pings and waits for a ping from the client for 60 seconds, before timing
out.
To configure the server to send out pings, the `PingPeriod` and `PongWait` must be set to a value greater than 0:

```go
cfg := ws.NewServerTimeoutConfig()
cfg.PingPeriod = 10 * time.Second
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ services:
go test -v -covermode=count -coverprofile=coverage.out ./ocppj
go test -v -covermode=count -coverprofile=ocpp16.out -coverpkg=github.com/lorenzodonini/ocpp-go/ocpp1.6/... github.com/lorenzodonini/ocpp-go/ocpp1.6_test
go test -v -covermode=count -coverprofile=ocpp201.out -coverpkg=github.com/lorenzodonini/ocpp-go/ocpp2.0.1/... github.com/lorenzodonini/ocpp-go/ocpp2.0.1_test
go test -v -covermode=count -coverprofile=ocpp21.out -coverpkg=github.com/lorenzodonini/ocpp-go/ocpp2.1/... github.com/lorenzodonini/ocpp-go/ocpp2.1_test
sed '1d;$d' ocpp16.out >> coverage.out
sed '1d;$d' ocpp201.out >> coverage.out
sed '1d;$d' ocpp21.out >> coverage.out

integration_test:
image: cimg/go:1.22.5
Expand Down
127 changes: 127 additions & 0 deletions docs/ocpp-2.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
## OCPP 2.1 Usage

OCPP 2.1 is now supported (experimental) in this library.

Requests and responses in OCPP 2.1 are handled the same way they were in v1.6 and 2.0.1.
There are additional message types on the websocket layer, which are handled automatically by the library.

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.

Below are very minimal setup code snippets, to get you started.
CSMS is now the equivalent of the Central System,
while the Charging Station is the new equivalent of a Charge Point.

Refer to the [examples folder](../example/2.1) for a full working example.
More in-depth documentation for v2.1 will follow.

### CSMS

To start a CSMS instance, run the following:

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

csms := ocpp2.NewCSMS(nil, nil)

// Set callback handlers for connect/disconnect
csms.SetNewChargingStationHandler(func (chargingStation ocpp2.ChargingStationConnection) {
log.Printf("new charging station %v connected", chargingStation.ID())
})
csms.SetChargingStationDisconnectedHandler(func (chargingStation ocpp2.ChargingStationConnection) {
log.Printf("charging station %v disconnected", chargingStation.ID())
})

// Set handler for profile callbacks
handler := &CSMSHandler{}
csms.SetAuthorizationHandler(handler)
csms.SetAvailabilityHandler(handler)
csms.SetDiagnosticsHandler(handler)
csms.SetFirmwareHandler(handler)
csms.SetLocalAuthListHandler(handler)
csms.SetMeterHandler(handler)
csms.SetProvisioningHandler(handler)
csms.SetRemoteControlHandler(handler)
csms.SetReservationHandler(handler)
csms.SetTariffCostHandler(handler)
csms.SetTransactionsHandler(handler)


// Start central system
listenPort := 8887
log.Printf("starting CSMS")
csms.Start(listenPort, "/{ws}") // This call starts server in daemon mode and is blocking
log.Println("stopped CSMS")
```

#### Sending requests

Similarly to v2.0.1, you may send requests using the simplified API, e.g.

```go
err := csms.GetLocalListVersion(chargingStationID, myCallback)
if err != nil {
log.Printf("error sending message: %v", err)
}
```

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.

### Charging Station

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

```go
chargingStationID := "cs0001"
csmsUrl = "ws://localhost:8887"
chargingStation := ocpp2.NewChargingStation(chargingStationID, nil, nil)

// Set a handler for all callback functions
handler := &ChargingStationHandler{}
chargingStation.SetAvailabilityHandler(handler)
chargingStation.SetAuthorizationHandler(handler)
chargingStation.SetDataHandler(handler)
chargingStation.SetDiagnosticsHandler(handler)
chargingStation.SetDisplayHandler(handler)
chargingStation.SetFirmwareHandler(handler)
chargingStation.SetISO15118Handler(handler)
chargingStation.SetLocalAuthListHandler(handler)
chargingStation.SetProvisioningHandler(handler)
chargingStation.SetRemoteControlHandler(handler)
chargingStation.SetReservationHandler(handler)
chargingStation.SetSmartChargingHandler(handler)
chargingStation.SetTariffCostHandler(handler)
chargingStation.SetTransactionsHandler(handler)

// Connects to CSMS
err := chargingStation.Start(csmsUrl)
if err != nil {
log.Println(err)
} else {
log.Printf("connected to CSMS at %v", csmsUrl)
mainRoutine() // ... your program logic goes here
}

// Disconnect
chargingStation.Stop()
log.Println("disconnected from CSMS")
```

#### Sending requests

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

```go
bootResp, err := chargingStation.BootNotification(provisioning.BootReasonPowerUp, "model1", "vendor1")
if err != nil {
log.Printf("error sending message: %v", err)
} else {
log.Printf("status: %v, interval: %v, current time: %v", bootResp.Status, bootResp.Interval, bootResp.CurrentTime.String())
}
```

Or you may build requests manually and send them using either the synchronous or asynchronous API.
26 changes: 26 additions & 0 deletions example/2.1/chargingstation/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
############################
# STEP 1 build executable binary
############################
FROM golang:alpine AS builder

ENV GO111MODULE on
WORKDIR $GOPATH/src/github.com/lorenzodonini/ocpp-go
COPY . .
# Fetch dependencies.
RUN go mod download
# Build the binary.
RUN go build -ldflags="-w -s" -o /go/bin/charging_station example/2.0.1/chargingstation/*.go

############################
# STEP 2 build a small image
############################
FROM alpine

COPY --from=builder /go/bin/charging_station /bin/charging_station

# Add CA certificates
# It currently throws a warning on alpine: WARNING: ca-certificates.crt does not contain exactly one certificate or CRL: skipping.
# Ignore the warning.
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* && update-ca-certificates

CMD [ "charging_station" ]
10 changes: 10 additions & 0 deletions example/2.1/chargingstation/authorization_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import (
"github.com/lorenzodonini/ocpp-go/ocpp2.0.1/authorization"
)

func (handler *ChargingStationHandler) OnClearCache(request *authorization.ClearCacheRequest) (response *authorization.ClearCacheResponse, err error) {
logDefault(request.GetFeatureName()).Infof("cleared mocked cache")
return authorization.NewClearCacheResponse(authorization.ClearCacheStatusAccepted), nil
}
37 changes: 37 additions & 0 deletions example/2.1/chargingstation/availability_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

import (
"github.com/lorenzodonini/ocpp-go/ocpp2.0.1/availability"
)

func (handler *ChargingStationHandler) OnChangeAvailability(request *availability.ChangeAvailabilityRequest) (response *availability.ChangeAvailabilityResponse, err error) {
if request.Evse == nil {
// Changing availability for the entire charging station
handler.availability = request.OperationalStatus
// TODO: recursively update the availability for all evse/connectors
response = availability.NewChangeAvailabilityResponse(availability.ChangeAvailabilityStatusAccepted)
return
}
reqEvse := request.Evse
if e, ok := handler.evse[reqEvse.ID]; ok {
// Changing availability for a specific EVSE
if reqEvse.ConnectorID != nil {
// Changing availability for a specific connector
if !e.hasConnector(*reqEvse.ConnectorID) {
response = availability.NewChangeAvailabilityResponse(availability.ChangeAvailabilityStatusRejected)
} else {
connector := e.connectors[*reqEvse.ConnectorID]
connector.availability = request.OperationalStatus
e.connectors[*reqEvse.ConnectorID] = connector
response = availability.NewChangeAvailabilityResponse(availability.ChangeAvailabilityStatusAccepted)
}
return
}
e.availability = request.OperationalStatus
response = availability.NewChangeAvailabilityResponse(availability.ChangeAvailabilityStatusAccepted)
return
}
// No EVSE with such ID found
response = availability.NewChangeAvailabilityResponse(availability.ChangeAvailabilityStatusRejected)
return
}
Loading
Loading