-
Notifications
You must be signed in to change notification settings - Fork 131
/
central_system_sim.go
247 lines (233 loc) · 9.94 KB
/
central_system_sim.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package main
import (
"crypto/tls"
"crypto/x509"
"os"
"strconv"
"time"
"github.com/sirupsen/logrus"
ocpp16 "github.com/lorenzodonini/ocpp-go/ocpp1.6"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/core"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/firmware"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/localauth"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/remotetrigger"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/reservation"
"github.com/lorenzodonini/ocpp-go/ocpp1.6/types"
"github.com/lorenzodonini/ocpp-go/ocppj"
"github.com/lorenzodonini/ocpp-go/ws"
)
const (
defaultListenPort = 8887
defaultHeartbeatInterval = 600
envVarServerPort = "SERVER_LISTEN_PORT"
envVarTls = "TLS_ENABLED"
envVarCaCertificate = "CA_CERTIFICATE_PATH"
envVarServerCertificate = "SERVER_CERTIFICATE_PATH"
envVarServerCertificateKey = "SERVER_CERTIFICATE_KEY_PATH"
)
var log *logrus.Logger
var centralSystem ocpp16.CentralSystem
func setupCentralSystem() ocpp16.CentralSystem {
return ocpp16.NewCentralSystem(nil, nil)
}
func setupTlsCentralSystem() ocpp16.CentralSystem {
var certPool *x509.CertPool
// Load CA certificates
caCertificate, ok := os.LookupEnv(envVarCaCertificate)
if !ok {
log.Infof("no %v found, using system CA pool", envVarCaCertificate)
systemPool, err := x509.SystemCertPool()
if err != nil {
log.Fatalf("couldn't get system CA pool: %v", err)
}
certPool = systemPool
} else {
certPool = x509.NewCertPool()
data, err := os.ReadFile(caCertificate)
if err != nil {
log.Fatalf("couldn't read CA certificate from %v: %v", caCertificate, err)
}
ok = certPool.AppendCertsFromPEM(data)
if !ok {
log.Fatalf("couldn't read CA certificate from %v", caCertificate)
}
}
certificate, ok := os.LookupEnv(envVarServerCertificate)
if !ok {
log.Fatalf("no required %v found", envVarServerCertificate)
}
key, ok := os.LookupEnv(envVarServerCertificateKey)
if !ok {
log.Fatalf("no required %v found", envVarServerCertificateKey)
}
server := ws.NewTLSServer(certificate, key, &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: certPool,
})
return ocpp16.NewCentralSystem(nil, server)
}
// Run for every connected Charge Point, to simulate some functionality
func exampleRoutine(chargePointID string, handler *CentralSystemHandler) {
// Wait for some time
time.Sleep(2 * time.Second)
// Reserve a connector
reservationID := 42
clientIdTag := "l33t"
connectorID := 1
expiryDate := types.NewDateTime(time.Now().Add(1 * time.Hour))
cb1 := func(confirmation *reservation.ReserveNowConfirmation, err error) {
if err != nil {
logDefault(chargePointID, reservation.ReserveNowFeatureName).Errorf("error on request: %v", err)
} else if confirmation.Status == reservation.ReservationStatusAccepted {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("connector %v reserved for client %v until %v (reservation ID %d)", connectorID, clientIdTag, expiryDate.FormatTimestamp(), reservationID)
} else {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("couldn't reserve connector %v: %v", connectorID, confirmation.Status)
}
}
e := centralSystem.ReserveNow(chargePointID, cb1, connectorID, expiryDate, clientIdTag, reservationID)
if e != nil {
logDefault(chargePointID, reservation.ReserveNowFeatureName).Errorf("couldn't send message: %v", e)
return
}
// Wait for some time
time.Sleep(1 * time.Second)
// Cancel the reservation
cb2 := func(confirmation *reservation.CancelReservationConfirmation, err error) {
if err != nil {
logDefault(chargePointID, reservation.CancelReservationFeatureName).Errorf("error on request: %v", err)
} else if confirmation.Status == reservation.CancelReservationStatusAccepted {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("reservation %v canceled successfully", reservationID)
} else {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("couldn't cancel reservation %v", reservationID)
}
}
e = centralSystem.CancelReservation(chargePointID, cb2, reservationID)
if e != nil {
logDefault(chargePointID, reservation.ReserveNowFeatureName).Errorf("couldn't send message: %v", e)
return
}
// Wait for some time
time.Sleep(5 * time.Second)
// Get current local list version
cb3 := func(confirmation *localauth.GetLocalListVersionConfirmation, err error) {
if err != nil {
logDefault(chargePointID, localauth.GetLocalListVersionFeatureName).Errorf("error on request: %v", err)
} else {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("current local list version: %v", confirmation.ListVersion)
}
}
e = centralSystem.GetLocalListVersion(chargePointID, cb3)
if e != nil {
logDefault(chargePointID, localauth.GetLocalListVersionFeatureName).Errorf("couldn't send message: %v", e)
return
}
// Wait for some time
time.Sleep(5 * time.Second)
configKey := "MeterValueSampleInterval"
configValue := "10"
// Change meter sampling values time
cb4 := func(confirmation *core.ChangeConfigurationConfirmation, err error) {
if err != nil {
logDefault(chargePointID, core.ChangeConfigurationFeatureName).Errorf("error on request: %v", err)
} else if confirmation.Status == core.ConfigurationStatusNotSupported {
logDefault(chargePointID, confirmation.GetFeatureName()).Warnf("couldn't update configuration for unsupported key: %v", configKey)
} else if confirmation.Status == core.ConfigurationStatusRejected {
logDefault(chargePointID, confirmation.GetFeatureName()).Warnf("couldn't update configuration for readonly key: %v", configKey)
} else {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("updated configuration for key %v to: %v", configKey, configValue)
}
}
e = centralSystem.ChangeConfiguration(chargePointID, cb4, configKey, configValue)
if e != nil {
logDefault(chargePointID, localauth.GetLocalListVersionFeatureName).Errorf("couldn't send message: %v", e)
return
}
// Wait for some time
time.Sleep(5 * time.Second)
// Trigger a heartbeat message
cb5 := func(confirmation *remotetrigger.TriggerMessageConfirmation, err error) {
if err != nil {
logDefault(chargePointID, remotetrigger.TriggerMessageFeatureName).Errorf("error on request: %v", err)
} else if confirmation.Status == remotetrigger.TriggerMessageStatusAccepted {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("%v triggered successfully", core.HeartbeatFeatureName)
} else if confirmation.Status == remotetrigger.TriggerMessageStatusRejected {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("%v trigger was rejected", core.HeartbeatFeatureName)
}
}
e = centralSystem.TriggerMessage(chargePointID, cb5, core.HeartbeatFeatureName)
if e != nil {
logDefault(chargePointID, remotetrigger.TriggerMessageFeatureName).Errorf("couldn't send message: %v", e)
return
}
// Wait for some time
time.Sleep(5 * time.Second)
// Trigger a diagnostics status notification
cb6 := func(confirmation *remotetrigger.TriggerMessageConfirmation, err error) {
if err != nil {
logDefault(chargePointID, remotetrigger.TriggerMessageFeatureName).Errorf("error on request: %v", err)
} else if confirmation.Status == remotetrigger.TriggerMessageStatusAccepted {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("%v triggered successfully", firmware.GetDiagnosticsFeatureName)
} else if confirmation.Status == remotetrigger.TriggerMessageStatusRejected {
logDefault(chargePointID, confirmation.GetFeatureName()).Infof("%v trigger was rejected", firmware.GetDiagnosticsFeatureName)
}
}
e = centralSystem.TriggerMessage(chargePointID, cb6, firmware.DiagnosticsStatusNotificationFeatureName)
if e != nil {
logDefault(chargePointID, remotetrigger.TriggerMessageFeatureName).Errorf("couldn't send message: %v", e)
return
}
}
// Start function
func main() {
// Load config from ENV
var listenPort = defaultListenPort
port, _ := os.LookupEnv(envVarServerPort)
if p, err := strconv.Atoi(port); err == nil {
listenPort = p
} else {
log.Printf("no valid %v environment variable found, using default port", envVarServerPort)
}
// Check if TLS enabled
t, _ := os.LookupEnv(envVarTls)
tlsEnabled, _ := strconv.ParseBool(t)
// Prepare OCPP 1.6 central system
if tlsEnabled {
centralSystem = setupTlsCentralSystem()
} else {
centralSystem = setupCentralSystem()
}
// Support callbacks for all OCPP 1.6 profiles
handler := &CentralSystemHandler{chargePoints: map[string]*ChargePointState{}}
centralSystem.SetCoreHandler(handler)
centralSystem.SetLocalAuthListHandler(handler)
centralSystem.SetFirmwareManagementHandler(handler)
centralSystem.SetReservationHandler(handler)
centralSystem.SetRemoteTriggerHandler(handler)
centralSystem.SetSmartChargingHandler(handler)
// Add callbacks for OCPP 1.6 security profiles
centralSystem.SetSecurityHandler(handler)
centralSystem.SetSecureFirmwareHandler(handler)
centralSystem.SetLogHandler(handler)
// Add handlers for dis/connection of charge points
centralSystem.SetNewChargePointHandler(func(chargePoint ocpp16.ChargePointConnection) {
handler.chargePoints[chargePoint.ID()] = &ChargePointState{connectors: map[int]*ConnectorInfo{}, transactions: map[int]*TransactionInfo{}}
log.WithField("client", chargePoint.ID()).Info("new charge point connected")
go exampleRoutine(chargePoint.ID(), handler)
})
centralSystem.SetChargePointDisconnectedHandler(func(chargePoint ocpp16.ChargePointConnection) {
log.WithField("client", chargePoint.ID()).Info("charge point disconnected")
delete(handler.chargePoints, chargePoint.ID())
})
ocppj.SetLogger(log.WithField("logger", "ocppj"))
ws.SetLogger(log.WithField("logger", "websocket"))
// Run central system
log.Infof("starting central system on port %v", listenPort)
centralSystem.Start(listenPort, "/{ws}")
log.Info("stopped central system")
}
func init() {
log = logrus.New()
log.SetFormatter(&logrus.TextFormatter{FullTimestamp: true})
// Set this to DebugLevel if you want to retrieve verbose logs from the ocppj and websocket layers
log.SetLevel(logrus.ErrorLevel)
}