Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/open-ocpp-trace-input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@ocpp-debugkit/toolkit': minor
---

Read the Open OCPP Trace interchange format. `parseTrace()` auto-detects it and `parseOpenOcppTrace()` parses it directly, mapping records onto the internal event model with raw-frame precedence, messageId-based action derivation, and unknown-field tolerance. `deriveOpenOcppTraceView()` exposes the format's consumer view, checked in CI against the specification's 15 conformance fixtures.
24 changes: 17 additions & 7 deletions CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

## Active Milestone

**v0.4.0 - Open OCPP Trace Interop (next)**
**v0.4.0 - Open OCPP Trace Interop (in progress)**

v0.3.1 is published to npm (16 detection rules, 15 scenarios, trace diffing,
rich scenario assertions, and the ci/anonymize/diff CLI commands). Next up is
interoperability with the Open OCPP Trace format: reading and writing the
shared v1.1 interchange format so DebugKit exchanges traces with other OCPP
tools, checked against the shared conformance fixtures.
rich scenario assertions, and the ci/anonymize/diff CLI commands). The interop
milestone reads and writes the shared Open OCPP Trace v1.1 interchange format,
checked against the specification's conformance fixtures. The input half
(#121) has landed; the exporter and `convert` CLI command (#122) remain.

## What's Done

Expand Down Expand Up @@ -226,10 +226,20 @@ tools, checked against the shared conformance fixtures.
- ✅ `@ocpp-debugkit/toolkit@0.3.1` published to npm
- ✅ Git tag `v0.3.1` + GitHub release `v0.3.1` created

### Open OCPP Trace Input Adapter (PR #124)

- ✅ `parseOpenOcppTrace()` reads the Open OCPP Trace v1.1 interchange format
(JSONL or JSON array of records); `parseTrace()` auto-detects and delegates
(Issue #121)
- ✅ Raw-frame precedence, messageId-based action derivation, unknown-field
tolerance; shared untrusted-input limits extracted to `parseLimits.ts`
- ✅ `deriveOpenOcppTraceView()` exposes the format's consumer view
- ✅ 15 specification conformance fixtures vendored and asserted in CI

## What's Next

1. **v0.4.0 - Open OCPP Trace Interop** - read and write the Open OCPP Trace
v1.1 interchange format (#121, #122), checked against the shared fixtures
1. **v0.4.0 - Open OCPP Trace Interop** - reading (#121) is done; remaining:
the exporter and `convert` CLI command (#122), then the `v0.4.0` release
2. **v0.5.0 - OCPP 2.0.1 Support** - extend the engine beyond 1.6J: message
set, device model, scenarios, and detection
3. **v1.0.0 - Stable FOSS Ecosystem** - API stabilization, 20+ scenarios, docs
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ validate behavior against known scenarios.
- **Replay Engine** — Deterministic, pure replay engine with step forward/back,
jump-to-event, and configurable playback speed.
- **Report Generation** — Export session analysis as Markdown or HTML reports.
- **Open OCPP Trace Interop** - Read the vendor-neutral
[Open OCPP Trace](https://github.com/open-ocpp-trace/specification) interchange
format, so traces from other OCPP tools can be inspected and analyzed here.
- **React Components** — Reusable, SSR-safe components for building custom
inspector UIs (SessionTimeline, MessageInspector, FailureSummary,
ReportViewer, ReplayControls).
Expand Down
35 changes: 35 additions & 0 deletions docs/trace-format-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,41 @@ A JSON array of raw OCPP message arrays, with no event wrapper:

---

## Open OCPP Trace Format (interop)

DebugKit also reads the
[Open OCPP Trace format](https://github.com/open-ocpp-trace/specification), a
vendor-neutral interchange format for OCPP traces. This lets traces produced by
other tools (simulators, proxies, CSMS test suites) be inspected and analyzed
here without hand-converting them.

The format is a stream of records, one OCPP-J frame per record, as JSONL or a
JSON array of records:

```jsonl
{"schemaVersion":"1.1","timestamp":"2024-01-15T10:30:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T10:30:00.500Z","transport":"json","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"status":"Accepted"},"raw":"[3,\"msg-001\",{\"status\":\"Accepted\"}]"}
```

`parseTrace()` detects this format automatically; `parseOpenOcppTrace()` parses
it directly. How records are consumed:

- `direction` maps to the internal directions: `cp-to-csms` becomes
`CS_TO_CSMS`, `csms-to-cp` becomes `CSMS_TO_CS`.
- `raw`, when present, is the authoritative frame. If it disagrees with the
decomposed fields, the frame from `raw` wins and a warning is recorded.
- A response (`CALLRESULT` / `CALLERROR`) may omit `action`; its effective
action is derived by correlating on `messageId`. `deriveOpenOcppTraceView()`
reports that correlation as the format's consumer view.
- Unknown fields are ignored, so a trace from a later minor version of the
format still parses. The same size and event-count [limits](#limits) apply.

The format is governed independently at
[open-ocpp-trace/specification](https://github.com/open-ocpp-trace/specification),
which ships a conformance suite that DebugKit's parser is checked against.

---

## OCPP 1.6 JSON Message Structure

OCPP 1.6 JSON uses WebSocket text frames containing JSON arrays. There are
Expand Down
9 changes: 9 additions & 0 deletions packages/toolkit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ report generation, React components, and CLI.

- **Trace Parser** — Parse OCPP 1.6 JSON traces in JSON Object, JSONL, or bare
array format. Safe parsing with size and event-count limits.
- **Open OCPP Trace Interop** - Read the vendor-neutral
[Open OCPP Trace](https://github.com/open-ocpp-trace/specification) format via
`parseOpenOcppTrace()` (and `parseTrace()` auto-detection), checked against the
specification's conformance fixtures.
- **Failure Detection** — 16 detection rules (4 critical, 10 warning, 2 info)
covering common failure patterns: failed authorization, connector faults,
station offline, heartbeat timeout, meter value gaps, invalid stop reasons,
Expand Down Expand Up @@ -245,6 +249,11 @@ Each event has a `message` field containing a raw OCPP 1.6 JSON array:
- **CallResult:** `[3, "UniqueId", { ...payload }]`
- **CallError:** `[4, "UniqueId", "ErrorCode", "ErrorDescription", {}]`

`parseTrace()` additionally auto-detects the vendor-neutral
[Open OCPP Trace](https://github.com/open-ocpp-trace/specification) interchange
format (records carrying `messageType` and `direction`); parse it directly with
`parseOpenOcppTrace()`.

See the [trace format specification](https://github.com/ocpp-debugkit/toolkit/blob/main/docs/trace-format-spec.md) for full details.

## Links
Expand Down
16 changes: 16 additions & 0 deletions packages/toolkit/src/core/__fixtures__/open-ocpp-trace/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Vendored Open OCPP Trace fixtures

Conformance fixtures from the Open OCPP Trace specification, vendored here so
DebugKit's parser is checked against them in CI.

- **Source:** [open-ocpp-trace/specification](https://github.com/open-ocpp-trace/specification)
(`fixtures/`). All data is synthetic.
- Each `<name>/trace.jsonl` is a trace in the shared format. Each
`<name>/expected.json` is the consumer view a conformant implementation
derives from it: correlation pairs, effective actions, unanswered calls, and
orphan responses.
- [`../../openOcppTrace.conformance.test.ts`](../../openOcppTrace.conformance.test.ts)
asserts that `deriveOpenOcppTraceView()` reproduces every `expected.json` and
that `parseOpenOcppTrace()` parses every trace.

Do not edit these by hand. Refresh them from the specification when it changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
{
"schemaVersion": "1.1",
"counts": {
"records": 20,
"calls": 10,
"callResults": 10,
"callErrors": 0
},
"records": [
{
"index": 0,
"messageType": "CALL",
"messageId": "msg-001",
"action": "BootNotification"
},
{
"index": 1,
"messageType": "CALLRESULT",
"messageId": "msg-001",
"action": "BootNotification",
"correlatesWith": 0
},
{
"index": 2,
"messageType": "CALL",
"messageId": "msg-002",
"action": "StatusNotification"
},
{
"index": 3,
"messageType": "CALLRESULT",
"messageId": "msg-002",
"action": "StatusNotification",
"correlatesWith": 2
},
{
"index": 4,
"messageType": "CALL",
"messageId": "msg-003",
"action": "StatusNotification"
},
{
"index": 5,
"messageType": "CALLRESULT",
"messageId": "msg-003",
"action": "StatusNotification",
"correlatesWith": 4
},
{
"index": 6,
"messageType": "CALL",
"messageId": "msg-004",
"action": "Authorize"
},
{
"index": 7,
"messageType": "CALLRESULT",
"messageId": "msg-004",
"action": "Authorize",
"correlatesWith": 6
},
{
"index": 8,
"messageType": "CALL",
"messageId": "msg-005",
"action": "StartTransaction"
},
{
"index": 9,
"messageType": "CALLRESULT",
"messageId": "msg-005",
"action": "StartTransaction",
"correlatesWith": 8
},
{
"index": 10,
"messageType": "CALL",
"messageId": "msg-006",
"action": "StatusNotification"
},
{
"index": 11,
"messageType": "CALLRESULT",
"messageId": "msg-006",
"action": "StatusNotification",
"correlatesWith": 10
},
{
"index": 12,
"messageType": "CALL",
"messageId": "msg-hb-1",
"action": "Heartbeat"
},
{
"index": 13,
"messageType": "CALLRESULT",
"messageId": "msg-hb-1",
"action": "Heartbeat",
"correlatesWith": 12
},
{
"index": 14,
"messageType": "CALL",
"messageId": "msg-007",
"action": "MeterValues"
},
{
"index": 15,
"messageType": "CALLRESULT",
"messageId": "msg-007",
"action": "MeterValues",
"correlatesWith": 14
},
{
"index": 16,
"messageType": "CALL",
"messageId": "msg-008",
"action": "StatusNotification"
},
{
"index": 17,
"messageType": "CALLRESULT",
"messageId": "msg-008",
"action": "StatusNotification",
"correlatesWith": 16
},
{
"index": 18,
"messageType": "CALL",
"messageId": "msg-009",
"action": "StopTransaction"
},
{
"index": 19,
"messageType": "CALLRESULT",
"messageId": "msg-009",
"action": "StopTransaction",
"correlatesWith": 18
}
],
"unansweredCalls": [],
"orphanResponses": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-003","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-003\",\"firmwareVersion\":\"1.0.0\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T12:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T12:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","connectorId":0,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-002","action":"StatusNotification","payload":{"connectorId":0,"status":"Available","errorCode":"NoError"},"raw":"[2,\"msg-002\",\"StatusNotification\",{\"connectorId\":0,\"status\":\"Available\",\"errorCode\":\"NoError\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"StatusNotification","payload":{"connectorId":1,"status":"Preparing","errorCode":"NoError"},"raw":"[2,\"msg-003\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Preparing\",\"errorCode\":\"NoError\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{},"raw":"[3,\"msg-003\",{}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:15.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-002"},"raw":"[2,\"msg-004\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-002\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:15.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"idTagInfo":{"status":"Accepted","expiryDate":"2024-12-31T23:59:59.000Z"}},"raw":"[3,\"msg-004\",{\"idTagInfo\":{\"status\":\"Accepted\",\"expiryDate\":\"2024-12-31T23:59:59.000Z\"}}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-002","meterStart":0,"timestamp":"2024-01-15T12:02:30.000Z"},"raw":"[2,\"msg-005\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-002\",\"meterStart\":0,\"timestamp\":\"2024-01-15T12:02:30.000Z\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{"transactionId":100002,"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-005\",{\"transactionId\":100002,\"idTagInfo\":{\"status\":\"Accepted\"}}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:31.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"StatusNotification","payload":{"connectorId":1,"status":"Charging","errorCode":"NoError"},"raw":"[2,\"msg-006\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Charging\",\"errorCode\":\"NoError\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:02:31.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{},"raw":"[3,\"msg-006\",{}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-hb-1","action":"Heartbeat","payload":{},"raw":"[2,\"msg-hb-1\",\"Heartbeat\",{}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T12:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T12:07:30.500Z\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:15:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-007","action":"MeterValues","payload":{"connectorId":1,"transactionId":100002,"meterValue":[{"timestamp":"2024-01-15T12:15:00.000Z","sampledValue":[{"value":"3500","measurand":"Energy.Active.Import.Register","unit":"Wh"}]}]},"raw":"[2,\"msg-007\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100002,\"meterValue\":[{\"timestamp\":\"2024-01-15T12:15:00.000Z\",\"sampledValue\":[{\"value\":\"3500\",\"measurand\":\"Energy.Active.Import.Register\",\"unit\":\"Wh\"}]}]}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:15:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-007","payload":{},"raw":"[3,\"msg-007\",{}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:18:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-008","action":"StatusNotification","payload":{"connectorId":1,"status":"Faulted","errorCode":"ConnectorLockFailure","info":"Connector lock mechanism failure detected"},"raw":"[2,\"msg-008\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Faulted\",\"errorCode\":\"ConnectorLockFailure\",\"info\":\"Connector lock mechanism failure detected\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:18:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-008","payload":{},"raw":"[3,\"msg-008\",{}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:18:05.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-009","action":"StopTransaction","payload":{"transactionId":100002,"idTag":"SYNTHETIC-TAG-002","meterStop":3500,"timestamp":"2024-01-15T12:18:05.000Z","reason":"Other"},"raw":"[2,\"msg-009\",\"StopTransaction\",{\"transactionId\":100002,\"idTag\":\"SYNTHETIC-TAG-002\",\"meterStop\":3500,\"timestamp\":\"2024-01-15T12:18:05.000Z\",\"reason\":\"Other\"}]"}
{"schemaVersion":"1.1","timestamp":"2024-01-15T12:18:05.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-003","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-009","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-009\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"schemaVersion": "1.1",
"counts": {
"records": 8,
"calls": 4,
"callResults": 4,
"callErrors": 0
},
"records": [
{
"index": 0,
"messageType": "CALL",
"messageId": "msg-001",
"action": "BootNotification"
},
{
"index": 1,
"messageType": "CALLRESULT",
"messageId": "msg-001",
"action": "BootNotification",
"correlatesWith": 0
},
{
"index": 2,
"messageType": "CALL",
"messageId": "msg-002",
"action": "StatusNotification"
},
{
"index": 3,
"messageType": "CALLRESULT",
"messageId": "msg-002",
"action": "StatusNotification",
"correlatesWith": 2
},
{
"index": 4,
"messageType": "CALL",
"messageId": "msg-hb-1",
"action": "Heartbeat"
},
{
"index": 5,
"messageType": "CALLRESULT",
"messageId": "msg-hb-1",
"action": "Heartbeat",
"correlatesWith": 4
},
{
"index": 6,
"messageType": "CALL",
"messageId": "msg-003",
"action": "DiagnosticsStatusNotification"
},
{
"index": 7,
"messageType": "CALLRESULT",
"messageId": "msg-003",
"action": "DiagnosticsStatusNotification",
"correlatesWith": 6
}
],
"unansweredCalls": [],
"orphanResponses": []
}
Loading
Loading