Skip to main content
Three calls: one when a charge point connects, one per frame, one when the socket closes. Shared setup (install, API key, config, shutdown, troubleshooting) is in Getting started.

Model

EVPanda records a connection: everything on one WebSocket, from handshake to close, under one connection ID. The session mints the connection ID and carries the identity, so per frame calls pass neither. A reconnect calls connection() again and gets a fresh ID. FROM_CP is a frame the charge point sent you. TO_CP is one you sent it.

Steps

1

Start the client

One client at boot, shared across the process. It needs a charger network API key.
2

Identify the charge point at the handshake

Your CSMS already does this before it decides whether to accept the connection. Use the same value.
3

Open a session when the socket opens

connection() records the connect and returns the handle.
One session per socket. Sharing one across connections merges unrelated traffic; minting one per frame makes every frame its own session.
4

Capture inbound frames

Capture before you handle, so a frame that breaks your handler is still recorded.
5

Capture outbound frames

A CSMS writes from more than one place: replies from the read loop, and calls your operators or schedulers start. Put the write and the capture behind one helper so nothing reaches the socket uncaptured.
Capture after a successful write. A frame that failed to send never reached the charge point, and recording it as sent makes the validation engine look for a response that was never coming.
6

Close the session when the socket closes

Attach it where you clean up your own connection state, somewhere that runs on an abrupt drop and not only on a clean shutdown.
A charge point that loses power never closes cleanly. If disconnect() only runs on your graceful path, sessions never end and flapping chargers look healthy.

Complete example

Uses gorilla/websocket. Nothing here depends on it.
csms.go

Per-SDK notes

The client and session are safe from multiple goroutines, but most WebSocket libraries allow only one concurrent writer. Keep the capture call inside the helper that holds the write lock, so capture and write cannot disagree about what was sent.
message() takes a BodyInput, so raw.toString() and the raw Buffer both work. A string is encoded as UTF-8.
Delivery runs on a background daemon thread, so the calls never block the event loop and never await. Frames may be bytes or str.

Multi-tenant CSMS

Add the tenant pair at the handshake. Optional but all or nothing.
Send a tenant pair on OCPP even with one operator. The ingestion API requires it on OCPP messages and rejects those that omit it after a successful delivery, so nothing in the SDK reports the loss. A constant value works.

Flat primitives

connection() is built on three lower level calls. Use them when a session handle does not fit, such as replaying an archive.
The message call requires both the frame and the direction, and drops the message if either is missing. Connect and disconnect use neither.
If you mint connection IDs yourself, the ID must be stable for the whole socket and fresh on every reconnect. Reusing the charger ID as the connection ID collapses a charger’s entire history into one endless session.

Common mistakes

Tune the network

Two settings shape what OCPP traffic becomes an issue. Both under the network’s Settings tab.

Next

Data flow

Event shapes and limits.

Getting started

Config, stats, shutdown, troubleshooting.