⚠️ Breaking change: theCentralSystemandCSMSinterfaces now include aSendRequestAsyncCtxmethod for context-aware, cancelable server→client sends — breaking only for external implementors of those interfaces, not for callers; see #34.
Community-maintained friendly fork of
lorenzodonini/ocpp-go(MIT licensed). The upstream project is the canonical one — this fork is not official.Why this fork exists, in priority order:
- Adopt well-developed, open upstream pull requests that fix real bugs or restore build/test health.
- Keep the library correct and faithful to upstream's design.
- Incorporate good-practice improvements from other forks that are ahead of upstream
master— always with sound engineering and tests.Upstream authorship is preserved. Adopted changes retain their original commit authorship; see
NOTICE.mdfor contributor credits andCHANGELOG.mdfor notable divergences (both planned). We aim to send fixes back upstream rather than compete with it, and build/test restoration is currently in progress.What actually changed between the OCPP 2.0.1 and OCPP 2.1 JSON schemas — a field-by-field diff of the two published Part 3 schema sets: 27 new messages, one new required field, and every enum, requiredness and
maxLengthchange in between. Reproducible from the script alongside it.Contributions are welcome. Thanks to the original author and to all previous contributors whose work this fork builds on.
Open Charge Point Protocol implementation in Go.
The library targets modern charge points and central systems, running OCPP version 1.6+.
Given that SOAP will no longer be supported in future versions of OCPP, only OCPP-J is supported in this library. There are currently no plans of supporting OCPP-S.
Go version 1.21+ is required.
go get github.com/lorenzodonini/ocpp-goYou will also need to fetch some dependencies:
cd <path-to-ocpp-go>
export GO111MODULE=on
go mod downloadYour application may either act as a Central System (server) or as a Charge Point (client).
Note: Releases 0.10.0 introduced breaking changes in some API, due to refactoring. The functionality remains the same, but naming changed.
Planned milestones and features:
- Dedicated package for configuration management
- OCPP 1.6 (documentation available here)
- OCPP 1.6 Security extension (documentation available here)
- OCPP 2.0.1 (examples working, but will need more real-world testing) (documentation available here)
- What actually changed between the OCPP 2.0.1 and OCPP 2.1 JSON schemas — a field-level comparison of the two versions' published payload schemas, with a self-contained reproduction script.
The library offers several advanced features, especially at websocket and ocpp-j level.
All incoming and outgoing messages are validated by default, using the validator package. Constraints are defined on every request/response struct, as per OCPP specs.
Validation may be disabled at a package level if needed:
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.
The ws and ocppj packages offer the possibility to enable verbose logs, via your logger of choice, e.g.:
// 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.
For the standard library's log/slog, a ready-made adapter is provided in the logging/slogadapter package:
ocppj.SetLogger(slogadapter.New(slog.Default()))
ws.SetLogger(slogadapter.New(slog.Default()))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.
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:
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.
To configure the server to send out pings, the PingPeriod and PongWait must be set to a value greater than 0:
cfg := ws.NewServerTimeoutConfig()
cfg.PingPeriod = 10 * time.Second
cfg.PongWait = 20 * time.Second
websocketServer.SetTimeoutConfig(cfg)To disable sending ping messages, set the PingPeriod value to 0.
The websocket layer supports an opt-in per-message read limit (unlimited by default).
Exceeding the limit drops the connection, so size it well above the largest legitimate
message. The limit can be set at the facade layer without importing the ws package:
// central system with a 1 MB inbound limit, no ws import needed
cs := ocpp16.NewDefaultCentralSystem(ocpp16.WithServerReadLimit(1 << 20))
// same knob for OCPP 2.0.1, where a large NotifyReport can reach hundreds of KB
csms := ocpp2.NewDefaultCSMS(ocpp2.WithServerReadLimit(1 << 20))When configuring your own ws.Server, use the construction-safe option instead of a
bare ServerTimeoutConfig literal (which would silently zero the write/ping deadlines):
server := ws.NewServer(ws.WithServerReadLimit(1 << 20))Contributions are welcome! Please refer to the testing guide for instructions on how to run the tests.