Skip to content

Commit

Permalink
Merge pull request #13 from grid-x/feat/sync_with_master
Browse files Browse the repository at this point in the history
Feat/sync with master
  • Loading branch information
Marco Trettner authored Nov 16, 2022
2 parents 2dfb208 + 2963c13 commit e40e2fd
Show file tree
Hide file tree
Showing 75 changed files with 2,473 additions and 297 deletions.
21 changes: 10 additions & 11 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,29 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: 1.15.x
- name: Checkout code
uses: actions/checkout@v2
go-version: ^1.18
cache: true
- name: Setup toxiproxy
run: |
wget -O toxiproxy-2.1.4.deb https://github.com/Shopify/toxiproxy/releases/download/v2.1.4/toxiproxy_2.1.4_amd64.deb
sudo dpkg -i toxiproxy-2.1.4.deb
sudo cp ws/toxiproxy.service /lib/systemd/system/
sudo systemctl start toxiproxy
- name: Install goveralls
run: |
go get golang.org/x/tools/cmd/cover
go get github.com/mattn/goveralls
- name: Run tests
run: |
go test ./ws ./ocppj -v -covermode=count -coverprofile=coverage.out
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=ocpp20.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=ocpp201.out -coverpkg=github.com/lorenzodonini/ocpp-go/ocpp2.0.1/... github.com/lorenzodonini/ocpp-go/ocpp2.0.1_test
sed '1d;$d' ocpp16.out >> coverage.out
sed '1d;$d' ocpp20.out >> coverage.out
sed '1d;$d' ocpp201.out >> coverage.out
- name: Install goveralls
run: go install github.com/mattn/goveralls@latest
- name: Publish coverage
env:
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: goveralls -coverprofile=coverage.out -service=github -repotoken $COVERALLS_TOKEN
run: goveralls -coverprofile=coverage.out -service=github
28 changes: 23 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,42 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- uses: docker/setup-buildx-action@v1
uses: actions/checkout@v3
- name: Setup QEMU
uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Release Central System 1.6 example
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: true
tags: ldonini/ocpp1.6-central-system:${{ github.event.release.tag_name }}
file: example/1.6/cs/Dockerfile
context: .
- name: Release Charge Point 1.6 example
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
push: true
tags: ldonini/ocpp1.6-charge-point:${{ github.event.release.tag_name }}
file: example/1.6/cp/Dockerfile
context: .
- name: Release CSMS 2.0.1 example
uses: docker/build-push-action@v3
with:
push: true
tags: ldonini/ocpp2.0.1-csms:${{ github.event.release.tag_name }}
file: example/2.0.1/csms/Dockerfile
platforms: linux/amd64,linux/arm64
context: .
- name: Release charging station 2.0.1 example
uses: docker/build-push-action@v3
with:
push: true
tags: ldonini/ocpp2.0.1-charging-station:${{ github.event.release.tag_name }}
file: example/2.0.1/chargingstation/Dockerfile
platforms: linux/amd64,linux/arm64
context: .
184 changes: 180 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ There are currently no plans of supporting OCPP-S.
Planned milestones and features:

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

## OCPP 1.6 Usage

Expand Down Expand Up @@ -244,8 +245,9 @@ To send requests to the central station, you have two options. You may either us
bootConf, err := chargePoint.BootNotification("model1", "vendor1")
if err != nil {
log.Fatal(err)
} else {
log.Printf("status: %v, interval: %v, current time: %v", bootConf.Status, bootConf.Interval, bootConf.CurrentTime.String())
}
log.Printf("status: %v, interval: %v, current time: %v", bootConf.Status, bootConf.Interval, bootConf.CurrentTime.String())
// ... do something with the confirmation
```

Expand Down Expand Up @@ -323,6 +325,180 @@ Then run the following:
docker-compose -f example/1.6/docker-compose.tls.yml up charge-point
```

## OCPP 2.0 Usage
## Advanced Features

The library offers several advanced features, especially at websocket and ocpp-j level.

### Automatic message validation

All incoming and outgoing messages are validated by default, using the [validator](gopkg.in/go-playground/validator) package.
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,
> 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()
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true})
log.SetLevel(logrus.DebugLevel) // Debug level needed to see all logs
// Pass own logger to ws and ocppj packages
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.

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
websocketServer.SetTimeoutConfig(cfg)
```

> A server-initiated ping may be supported in a future release.
## OCPP 2.0.1 Usage

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),
which also require their own handlers to be implemented.

The library API to the lower websocket and ocpp-j layers remains unchanged.

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.0.1) for a full working example.
More in-depth documentation for v2.0.1 will follow.

**Bug reports for this version are welcome.**

### CSMS

To start a CSMS instance, run the following:
```go
import "github.com/lorenzodonini/ocpp-go/ocpp2.0.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 v1.6, 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.
Feel free

### 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 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 {
log.Printf("error sending message: %v", err)
} else {
log.Printf("status: %v, interval: %v, current time: %v", bootResp.Status, bootResp.Interval, bootResp.CurrentTime.String())
}
```

Documentation will follow, once the protocol is fully implemented.
Or you may build requests manually and send them using either the synchronous or asynchronous API.
2 changes: 1 addition & 1 deletion example/1.6/cp/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ COPY . .
# Fetch dependencies.
RUN go mod download
# Build the binary.
RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/charge_point example/1.6/cp/*.go
RUN go build -ldflags="-w -s" -o /go/bin/charge_point example/1.6/cp/*.go

############################
# STEP 2 build a small image
Expand Down
3 changes: 1 addition & 2 deletions example/1.6/cp/charge_point_sim.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"strconv"
"time"
Expand Down Expand Up @@ -42,7 +41,7 @@ func setupTlsChargePoint(chargePointID string) ocpp16.ChargePoint {
// Load CA cert
caPath, ok := os.LookupEnv(envVarCACertificate)
if ok {
caCert, err := ioutil.ReadFile(caPath)
caCert, err := os.ReadFile(caPath)
if err != nil {
log.Warn(err)
} else if !certPool.AppendCertsFromPEM(caCert) {
Expand Down
10 changes: 5 additions & 5 deletions example/1.6/create-test-certificates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ mkdir -p certs/cs
mkdir -p certs/cp
cd certs
# Create CA
openssl req -new -x509 -nodes -days 120 -extensions v3_ca -keyout ca.key -out ca.crt -subj "/CN=ocpp-go-example"
openssl req -new -x509 -nodes -sha256 -days 120 -extensions v3_ca -keyout ca.key -out ca.crt -subj "/CN=ocpp-go-example"
# Generate self-signed central-system certificate
openssl genrsa -out cs/central-system.key 4096
openssl req -new -out cs/central-system.csr -key cs/central-system.key -config ../openssl-cs.conf
openssl x509 -req -in cs/central-system.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out cs/central-system.crt -days 120 -extensions req_ext -extfile ../openssl-cs.conf
openssl req -new -out cs/central-system.csr -key cs/central-system.key -config ../openssl-cs.conf -sha256
openssl x509 -req -in cs/central-system.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out cs/central-system.crt -days 120 -extensions req_ext -extfile ../openssl-cs.conf -sha256
# Generate self-signed charge-point certificate
openssl genrsa -out cp/charge-point.key 4096
openssl req -new -out cp/charge-point.csr -key cp/charge-point.key -config ../openssl-cp.conf
openssl x509 -req -in cp/charge-point.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out cp/charge-point.crt -days 120 -extensions req_ext -extfile ../openssl-cp.conf
openssl req -new -out cp/charge-point.csr -key cp/charge-point.key -config ../openssl-cp.conf -sha256
openssl x509 -req -in cp/charge-point.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out cp/charge-point.crt -days 120 -extensions req_ext -extfile ../openssl-cp.conf -sha256
2 changes: 1 addition & 1 deletion example/1.6/cs/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ COPY . .
# Fetch dependencies.
RUN go mod download
# Build the binary.
RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/central_system example/1.6/cs/*.go
RUN go build -ldflags="-w -s" -o /go/bin/central_system example/1.6/cs/*.go

############################
# STEP 2 build a small image
Expand Down
3 changes: 1 addition & 2 deletions example/1.6/cs/central_system_sim.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"os"
"strconv"
"time"
Expand Down Expand Up @@ -51,7 +50,7 @@ func setupTlsCentralSystem() ocpp16.CentralSystem {
certPool = systemPool
} else {
certPool = x509.NewCertPool()
data, err := ioutil.ReadFile(caCertificate)
data, err := os.ReadFile(caCertificate)
if err != nil {
log.Fatalf("couldn't read CA certificate from %v: %v", caCertificate, err)
}
Expand Down
26 changes: 26 additions & 0 deletions example/2.0.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" ]
Loading

0 comments on commit e40e2fd

Please sign in to comment.