ocppctl
ocppctl is a command-line tool for OCPP 1.6J, 2.0.1 and 2.1, in both roles, built on go-ocpp. As a Charging Station it probes a CSMS, sends any message, runs one or thousands of simulated stations and runs scenarios with expectations. As a CSMS it accepts real or simulated stations, answers them from a model, streams everything they send and sends them CALLs on request. Offline, it validates payloads, decodes frames and computes certificate hashes. Output is text, a table or JSON, with stable exit codes.
It is meant for CSMS development and QA, station and firmware development, commissioning and troubleshooting, and test setups without hardware. It is a test and diagnostic tool, not a compliance test: passing a scenario says what you checked, not that a product conforms.
Install
Section titled “Install”Pre-built binaries for Linux (x86-64, ARM64, ARMv7), macOS (Apple Silicon, Intel) and Windows (x86-64, ARM64) are on GitHub Releases, with SHA-256 checksums.
VERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/otfabric/ocppctl/releases/latest | sed 's|.*/v||')case "$(uname -m)" in x86_64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; armv7l) ARCH=armv7 ;; esacFILE="ocppctl-${VERSION}-linux-${ARCH}.tar.gz"BASE="https://github.com/otfabric/ocppctl/releases/download/v${VERSION}"curl -fsSLO "${BASE}/${FILE}" && curl -fsSLO "${BASE}/ocppctl-${VERSION}-checksums.txt"grep "${FILE}" "ocppctl-${VERSION}-checksums.txt" | sha256sum -c -mkdir -p ocppctl-dl && tar -xzf "${FILE}" -C ocppctl-dlsudo install -m 0755 "$(find ocppctl-dl -type f -name 'ocppctl*' | head -n1)" /usr/local/bin/ocppctlVERSION=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/otfabric/ocppctl/releases/latest | sed 's|.*/v||')case "$(uname -m)" in arm64) ARCH=arm64 ;; x86_64) ARCH=amd64 ;; esacFILE="ocppctl-${VERSION}-darwin-${ARCH}.tar.gz"BASE="https://github.com/otfabric/ocppctl/releases/download/v${VERSION}"curl -fsSLO "${BASE}/${FILE}" && curl -fsSLO "${BASE}/ocppctl-${VERSION}-checksums.txt"grep "${FILE}" "ocppctl-${VERSION}-checksums.txt" | shasum -a 256 -c -mkdir -p ocppctl-dl && tar -xzf "${FILE}" -C ocppctl-dlsudo install -m 0755 "$(find ocppctl-dl -type f -name 'ocppctl*' | head -n1)" /usr/local/bin/ocppctlgo install github.com/otfabric/ocppctl@latestThe Windows commands (PowerShell) are in the README. Check the install with ocppctl version.
Command tree
Section titled “Command tree”ocppctl├── client act as a Charging Station│ ├── probe connect, report what the handshake negotiated, disconnect│ ├── call <Action> send one CALL, print the response│ ├── send <Action> send one SEND (OCPP 2.1)│ ├── boot | heartbeat | authorize | status | meter | data-transfer│ ├── transaction start | stop│ ├── run run simulated stations from a model│ ├── script <file> run a scenario with expectations│ └── generate-config write the commented station model├── server act as a CSMS│ ├── start accept stations, answer them from a model, stream events│ ├── call <Action> send a CALL to a connected station│ ├── stations list the connected stations│ └── generate-config write the commented CSMS model├── actions list the actions of a version├── validate <Action> validate a payload against the OCA schema├── frame decode | encode the OCPP-J frame codec├── cert hash CertificateHashData of a certificate├── completion bash, zsh, fish or PowerShell└── versionAction names are case-sensitive, as in OCPP (BootNotification). Every command has --help with examples.
Connecting to a CSMS
Section titled “Connecting to a CSMS”Every client command takes the CSMS endpoint and the station identity as --url ws[s]://host[:port]/path/<identity>.
| Flag | Default | Meaning |
|---|---|---|
--ocpp-version |
2.1,2.0.1,1.6 |
Versions to offer, in order of preference; the CSMS picks one. Give one to force it |
--password-file |
HTTP Basic password (profiles 1 and 2), or OCPPCTL_PASSWORD. Never a flag value or in the URL |
|
--tls-ca |
system roots | CAs that verify the CSMS certificate |
--tls-cert, --tls-key |
The station’s client certificate (profile 3) | |
--insecure-transport |
off | Allow ws:// (profiles 0 and 1) |
--compression |
off | Offer permessage-deflate |
--timeout, --connect-timeout |
30s, 10s |
Response timeout of a CALL; dial, TLS and handshake |
There is no way to skip the verification of the CSMS certificate: trust a test CA with --tls-ca.
Is my CSMS reachable?
Section titled “Is my CSMS reachable?”client probe connects, reports what the handshake negotiated and disconnects, without sending an OCPP message:
$ ocppctl client probe --url wss://localhost:19193/CS001 --tls-ca ca.pem --tls-cert cs001.pem --tls-key cs001.keytarget wss://localhost:19193/CS001identity CS001offered 2.1, 2.0.1, 1.6negotiated 2.1tls TLS 1.3, TLS_AES_128_GCM_SHA256, certificate CN=localhost (issuer CN=Test Root CA, valid until 2027-10-18), client certificate sentauthentication certificatecompression not negotiatedhandshake 7 ms--each-version tries every version on its own connection and lists which the CSMS accepts. A refusal says why: HTTP 401 is the password, HTTP 404 an unknown identity or a wrong path, no_common_version a CSMS that speaks none of the offered versions, transport_tls_failed an untrusted certificate.
Sending messages
Section titled “Sending messages”# Register the station: the command succeeds whatever the statusocppctl client boot --url wss://csms.example.com/ocpp/CS001 --format json | jq -r .response.status
# Any message, from the command line, a file or stdinocppctl client call Authorize --url "$URL" --ocpp-version 2.0.1 \ --payload '{"idToken":{"idToken":"04A1B2C3D4E5F6","type":"ISO14443"}}'ocppctl client call StatusNotification --url "$URL" --boot --payload @status.json
# A shortcut as a payload builder: build, edit, sendocppctl client meter --ocpp-version 1.6 --connector 1 --energy-wh 1500 --payload-only | jq '.meterValue[0].sampledValue[0].context = "Sample.Clock"' | ocppctl client call MeterValues --url "$URL" --ocpp-version 1.6 --payload -The payload is validated against the schema of the version the CSMS chose before it is sent. An invalid one exits 3 and nothing is sent; the JSON error names the field, the reason and the CALLERROR code a peer would answer with. --raw sends it anyway, to see how a CSMS copes. --boot sends a BootNotification first and requires Accepted.
The shortcuts boot, heartbeat, authorize, status, meter, data-transfer and transaction start|stop build the payload for the negotiated version: StartTransaction and StopTransaction in 1.6, TransactionEvent Started and Ended in 2.x.
A CALL that may change state and gets no answer in time exits 5 with outcome_unknown: the CSMS may have acted on it, and ocppctl does not send it again.
Simulating stations
Section titled “Simulating stations”client run keeps a simulated station connected: it boots until Accepted, sends StatusNotifications and heartbeats at the CSMS’s interval, charges the sessions of its model with meter values, and answers the CSMS (remote start and stop, Reset, TriggerMessage, configuration keys and the device model, and per-action answers from the model). It reconnects with the OCPP back-off.
# One station from the built-in modelocppctl client run --url ws://localhost:9000/CS001 --insecure-transport
# Your model, OCPP 2.0.1 only, for ten minutes, as JSON Linesocppctl client generate-config --output station.yamlocppctl client run --url wss://csms.example.com/ocpp/CS001 --ocpp-version 2.0.1 \ --config station.yaml --duration 10m --format json > events.jsonl
# A load test: 100 stations, 50 ms apart, for 15 minutesocppctl client run --url 'wss://csms.example.com/ocpp/LOAD{001..100}' --stagger 50ms --duration 15m --format json > load.jsonlstation: {vendor: Example, model: Simulator, serial: SIM-0001, firmware: 1.0.0}evses: - {id: 1, connectors: [1]} - {id: 2, connectors: [1]}meter_interval: 60ssessions: - {after: 30s, evse: 1, connector: 1, id_token: 04A1B2C3D4E5F6, power_w: 11000, duration: 5m}answers: UnlockConnector: {status: Unlocked}A multi-station run ends with a summary of calls, latency and errors on stderr, and exits 7 when some stations failed.
Scenarios in CI
Section titled “Scenarios in CI”client script runs a YAML scenario of calls, expected answers, and CALLs expected from the CSMS:
name: remote-start-2.0.1version: "2.0.1"steps: - call: BootNotification payload: {reason: PowerUp, chargingStation: {vendorName: Example, model: Simulator}} expect: {status: Accepted} - name: remote start wait_for: RequestStartTransaction # a CALL from the CSMS timeout: 60s expect: idToken.idToken: {present: true} answer: {status: Accepted} save: {token: idToken.idToken, remote: remoteStartId} - expect_no_call: 2s # the CSMS stays silentSteps are call, send, wait_for, expect_no_call, sleep, disconnect and reconnect. Expectations match fields by path with equality, regex, present or one_of; save keeps a field for later payloads; ${now}, ${uuid}, ${identity} and --var are variables.
ocppctl client script remote-start.yaml --url wss://csms-staging.example.com/ocpp/CI001 \ --keep-going --format json > result.jsonExit 0 when every step passed, 7 when some failed, 3 for an invalid scenario, 4 when the CSMS cannot be reached.
A CSMS for station development
Section titled “A CSMS for station development”# Terminal 1: every version, no TLS, no authentication, events as JSON Linesocppctl server start --insecure-transport --no-auth --format json | tee events.jsonl
# Terminal 2: point the station at ws://<this host>:9000/<identity>, thenocppctl server stationsocppctl server call Reset --station CS001 --yes --payload '{"type":"Immediate"}'ocppctl server call GetVariables --station CS001 \ --payload '{"getVariableData":[{"component":{"name":"OCPPCommCtrlr"},"variable":{"name":"HeartbeatInterval"}}]}'The server answers from a model (server generate-config): boot status and interval per station, a token list, 1.6 transaction ids and per-action answers. It serves security profiles 0 to 3: --passwords-file for profile 2, --tls-client-ca for profile 3 (the identity is the certificate’s CommonName). server call and server stations reach it through a control socket in the working directory. A CALL that may change the station needs --yes; read-only ones such as GetVariables do not. The server listens on all interfaces by default.
Offline tools
Section titled “Offline tools”ocppctl actions --ocpp-version 2.0.1 --format table # the actions of a version, and who sends themocppctl validate BootNotification --ocpp-version 1.6 --response --payload @boot-response.jsonocppctl frame decode '[2,"19223201","Heartbeat",{}]' --ocpp-version 1.6ocppctl cert hash --cert cs001.pem --issuer ca.pem # CertificateHashData for DeleteCertificateOutput formats and exit codes
Section titled “Output formats and exit codes”--format text|json|table (or OCPPCTL_OUTPUT_FORMAT) controls the result on stdout; progress and the --debug frame trace go to stderr. Streams (client run, server start) write one event per line, as JSON Lines with --format json. With JSON, a failure is a single error object with kind, code, message and an ocpp object (the CALLERROR code, the field that failed, the HTTP status of a refused handshake).
| Code | Meaning |
|---|---|
| 0 | Success |
| 2 | Usage, or server call of a state-changing action without --yes |
| 3 | Invalid input; nothing was sent |
| 4 | Transport: cannot connect, TLS, handshake refused, no common version, connection lost |
| 5 | Timeout (outcome_unknown when the request may have changed state) |
| 6 | The peer answered with a CALLERROR or an invalid response, or did not accept the boot |
| 7 | Partial success: failed scenario steps or stations |
| 10 | Output or internal error, or interrupted |
Environment variables
Section titled “Environment variables”Most flags have an OCPPCTL_* variable, and a flag on the command line wins. The ones you set most often:
| Variable | Flag |
|---|---|
OCPPCTL_URL |
--url |
OCPPCTL_PASSWORD_FILE, OCPPCTL_PASSWORD |
--password-file, or the password itself |
OCPPCTL_OCPP_VERSION |
--ocpp-version |
OCPPCTL_TLS_CA, OCPPCTL_TLS_CERT, OCPPCTL_TLS_KEY |
--tls-ca, --tls-cert, --tls-key |
OCPPCTL_OUTPUT_FORMAT |
--format |
OCPPCTL_CONTROL_SOCKET |
--control-socket |
The README lists all of them.
Tested against
Section titled “Tested against”ocppctl v0.1.1 was run against the five reference implementations of ocpp-interop v0.4.1, with every fixture sample in both directions. Of 1218 exchanges, 1154 passed, 29 were answered with a CALLERROR, 32 were skipped because the reference could not send the message, and 3 failed on schema-invalid responses of ocpp-go, which ocppctl refuses. Each one is explained in docs/KNOWN_ISSUES.md.