From a891b5566962c454db20153f523af177117f4a57 Mon Sep 17 00:00:00 2001 From: sepehr-safari Date: Fri, 17 Jul 2026 13:08:58 +0300 Subject: [PATCH 1/2] feat(core): parse the Open OCPP Trace interchange format Read the vendor-neutral Open OCPP Trace format (JSONL or a JSON array of records) into the internal event model. parseTrace() auto-detects it and delegates to parseOpenOcppTrace(); records map onto the OCPP-J frame with raw-frame precedence, messageId-based action derivation, and unknown-field tolerance, reusing the normalize pipeline and the shared untrusted-input limits (extracted to parseLimits.ts). deriveOpenOcppTraceView() reports the format's consumer view. The 15 specification conformance fixtures are vendored and asserted against it in CI, so DebugKit is a checked reference consumer of the format. --- .changeset/open-ocpp-trace-input.md | 5 + README.md | 3 + docs/trace-format-spec.md | 35 ++ packages/toolkit/README.md | 9 + .../__fixtures__/open-ocpp-trace/README.md | 16 + .../connector-fault/expected.json | 143 +++++ .../connector-fault/trace.jsonl | 20 + .../diagnostics-failure/expected.json | 65 +++ .../diagnostics-failure/trace.jsonl | 8 + .../open-ocpp-trace/failed-auth/expected.json | 104 ++++ .../open-ocpp-trace/failed-auth/trace.jsonl | 14 + .../heartbeat-irregular/expected.json | 65 +++ .../heartbeat-irregular/trace.jsonl | 8 + .../invalid-stop-reason/expected.json | 130 +++++ .../invalid-stop-reason/trace.jsonl | 18 + .../meter-anomaly/expected.json | 104 ++++ .../open-ocpp-trace/meter-anomaly/trace.jsonl | 14 + .../meter-value-gap/expected.json | 117 ++++ .../meter-value-gap/trace.jsonl | 16 + .../normal-session/expected.json | 156 ++++++ .../normal-session/trace.jsonl | 22 + .../short-session/expected.json | 91 +++ .../open-ocpp-trace/short-session/trace.jsonl | 12 + .../slow-csms-response/expected.json | 39 ++ .../slow-csms-response/trace.jsonl | 4 + .../station-offline/expected.json | 104 ++++ .../station-offline/trace.jsonl | 14 + .../status-transition-violation/expected.json | 78 +++ .../status-transition-violation/trace.jsonl | 10 + .../unexpected-start/expected.json | 65 +++ .../unexpected-start/trace.jsonl | 8 + .../unexpected-stop-reason/expected.json | 130 +++++ .../unexpected-stop-reason/trace.jsonl | 18 + .../unresponsive-csms/expected.json | 32 ++ .../unresponsive-csms/trace.jsonl | 3 + packages/toolkit/src/core/index.ts | 15 + .../core/openOcppTrace.conformance.test.ts | 69 +++ .../toolkit/src/core/openOcppTrace.test.ts | 211 +++++++ packages/toolkit/src/core/openOcppTrace.ts | 522 ++++++++++++++++++ packages/toolkit/src/core/parseLimits.ts | 50 ++ packages/toolkit/src/core/parser.ts | 66 +-- packages/toolkit/src/index.ts | 9 + tests/external-fixture/test.mjs | 39 ++ 43 files changed, 2609 insertions(+), 52 deletions(-) create mode 100644 .changeset/open-ocpp-trace-input.md create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/README.md create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/trace.jsonl create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/expected.json create mode 100644 packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/trace.jsonl create mode 100644 packages/toolkit/src/core/openOcppTrace.conformance.test.ts create mode 100644 packages/toolkit/src/core/openOcppTrace.test.ts create mode 100644 packages/toolkit/src/core/openOcppTrace.ts create mode 100644 packages/toolkit/src/core/parseLimits.ts diff --git a/.changeset/open-ocpp-trace-input.md b/.changeset/open-ocpp-trace-input.md new file mode 100644 index 0000000..713af49 --- /dev/null +++ b/.changeset/open-ocpp-trace-input.md @@ -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. diff --git a/README.md b/README.md index 381df72..119432f 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/docs/trace-format-spec.md b/docs/trace-format-spec.md index 12967f9..2bfc86f 100644 --- a/docs/trace-format-spec.md +++ b/docs/trace-format-spec.md @@ -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 diff --git a/packages/toolkit/README.md b/packages/toolkit/README.md index 1613b6f..30c8f7e 100644 --- a/packages/toolkit/README.md +++ b/packages/toolkit/README.md @@ -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, @@ -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 diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/README.md b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/README.md new file mode 100644 index 0000000..5a27475 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/README.md @@ -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 `/trace.jsonl` is a trace in the shared format. Each + `/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. diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/expected.json new file mode 100644 index 0000000..aee60bf --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/expected.json @@ -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": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/trace.jsonl new file mode 100644 index 0000000..e24405f --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/connector-fault/trace.jsonl @@ -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\"}}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/expected.json new file mode 100644 index 0000000..419e850 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/expected.json @@ -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": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/trace.jsonl new file mode 100644 index 0000000..83d7e27 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/diagnostics-failure/trace.jsonl @@ -0,0 +1,8 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-010","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-010\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T06:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T06:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","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-15T06:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","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-15T06:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T06:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T06:07:30.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:12:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"DiagnosticsStatusNotification","payload":{"status":"DiagnosisFailed"},"raw":"[2,\"msg-003\",\"DiagnosticsStatusNotification\",{\"status\":\"DiagnosisFailed\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:12:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-010","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{},"raw":"[3,\"msg-003\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/expected.json new file mode 100644 index 0000000..4b79185 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/expected.json @@ -0,0 +1,104 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 14, + "calls": 7, + "callResults": 7, + "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": "Authorize" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-005", + "action": "Authorize", + "correlatesWith": 8 + }, + { + "index": 10, + "messageType": "CALL", + "messageId": "msg-006", + "action": "Authorize" + }, + { + "index": 11, + "messageType": "CALLRESULT", + "messageId": "msg-006", + "action": "Authorize", + "correlatesWith": 10 + }, + { + "index": 12, + "messageType": "CALL", + "messageId": "msg-007", + "action": "StatusNotification" + }, + { + "index": 13, + "messageType": "CALLRESULT", + "messageId": "msg-007", + "action": "StatusNotification", + "correlatesWith": 12 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/trace.jsonl new file mode 100644 index 0000000..84e0d0b --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/failed-auth/trace.jsonl @@ -0,0 +1,14 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-002","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-002\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T11:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T11:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","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-15T11:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","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-15T11:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{},"raw":"[3,\"msg-003\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:15.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-INVALID"},"raw":"[2,\"msg-004\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-INVALID\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:15.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"idTagInfo":{"status":"Invalid"}},"raw":"[3,\"msg-004\",{\"idTagInfo\":{\"status\":\"Invalid\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:20.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-INVALID"},"raw":"[2,\"msg-005\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-INVALID\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:20.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{"idTagInfo":{"status":"Invalid"}},"raw":"[3,\"msg-005\",{\"idTagInfo\":{\"status\":\"Invalid\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-INVALID"},"raw":"[2,\"msg-006\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-INVALID\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{"idTagInfo":{"status":"Invalid"}},"raw":"[3,\"msg-006\",{\"idTagInfo\":{\"status\":\"Invalid\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:40.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-007","action":"StatusNotification","payload":{"connectorId":1,"status":"Faulted","errorCode":"OtherError","info":"Authorization failed after 3 attempts"},"raw":"[2,\"msg-007\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Faulted\",\"errorCode\":\"OtherError\",\"info\":\"Authorization failed after 3 attempts\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T11:02:40.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-002","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-007","payload":{},"raw":"[3,\"msg-007\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/expected.json new file mode 100644 index 0000000..8332789 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/expected.json @@ -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-hb-1", + "action": "Heartbeat" + }, + { + "index": 3, + "messageType": "CALLRESULT", + "messageId": "msg-hb-1", + "action": "Heartbeat", + "correlatesWith": 2 + }, + { + "index": 4, + "messageType": "CALL", + "messageId": "msg-hb-2", + "action": "Heartbeat" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-hb-2", + "action": "Heartbeat", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-hb-3", + "action": "Heartbeat" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-hb-3", + "action": "Heartbeat", + "correlatesWith": 6 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/trace.jsonl new file mode 100644 index 0000000..bde593d --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/heartbeat-irregular/trace.jsonl @@ -0,0 +1,8 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T12:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-014","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-014\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T12:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","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:05:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","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:05:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T12:05:00.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T12:05:00.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T12:15:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-hb-2","action":"Heartbeat","payload":{},"raw":"[2,\"msg-hb-2\",\"Heartbeat\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T12:15:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-2","payload":{"currentTime":"2024-01-15T12:15:00.500Z"},"raw":"[3,\"msg-hb-2\",{\"currentTime\":\"2024-01-15T12:15:00.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T12:25:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-hb-3","action":"Heartbeat","payload":{},"raw":"[2,\"msg-hb-3\",\"Heartbeat\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T12:25:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-014","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-3","payload":{"currentTime":"2024-01-15T12:25:00.500Z"},"raw":"[3,\"msg-hb-3\",{\"currentTime\":\"2024-01-15T12:25:00.500Z\"}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/expected.json new file mode 100644 index 0000000..61b725f --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/expected.json @@ -0,0 +1,130 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 18, + "calls": 9, + "callResults": 9, + "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": "Authorize" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-003", + "action": "Authorize", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-004", + "action": "StartTransaction" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "StartTransaction", + "correlatesWith": 6 + }, + { + "index": 8, + "messageType": "CALL", + "messageId": "msg-005", + "action": "StatusNotification" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-005", + "action": "StatusNotification", + "correlatesWith": 8 + }, + { + "index": 10, + "messageType": "CALL", + "messageId": "msg-hb-1", + "action": "Heartbeat" + }, + { + "index": 11, + "messageType": "CALLRESULT", + "messageId": "msg-hb-1", + "action": "Heartbeat", + "correlatesWith": 10 + }, + { + "index": 12, + "messageType": "CALL", + "messageId": "msg-006", + "action": "MeterValues" + }, + { + "index": 13, + "messageType": "CALLRESULT", + "messageId": "msg-006", + "action": "MeterValues", + "correlatesWith": 12 + }, + { + "index": 14, + "messageType": "CALL", + "messageId": "msg-007", + "action": "StopTransaction" + }, + { + "index": 15, + "messageType": "CALLRESULT", + "messageId": "msg-007", + "action": "StopTransaction", + "correlatesWith": 14 + }, + { + "index": 16, + "messageType": "CALL", + "messageId": "msg-008", + "action": "StatusNotification" + }, + { + "index": 17, + "messageType": "CALLRESULT", + "messageId": "msg-008", + "action": "StatusNotification", + "correlatesWith": 16 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/trace.jsonl new file mode 100644 index 0000000..8d68574 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/invalid-stop-reason/trace.jsonl @@ -0,0 +1,18 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-007","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-007\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T20:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T20:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","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-15T20:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-007"},"raw":"[2,\"msg-003\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-007\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{"idTagInfo":{"status":"Accepted","expiryDate":"2024-12-31T23:59:59.000Z"}},"raw":"[3,\"msg-003\",{\"idTagInfo\":{\"status\":\"Accepted\",\"expiryDate\":\"2024-12-31T23:59:59.000Z\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:02:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-007","meterStart":0,"timestamp":"2024-01-15T20:02:30.000Z"},"raw":"[2,\"msg-004\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-007\",\"meterStart\":0,\"timestamp\":\"2024-01-15T20:02:30.000Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:02:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"transactionId":100007,"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-004\",{\"transactionId\":100007,\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:02:31.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"StatusNotification","payload":{"connectorId":1,"status":"Charging","errorCode":"NoError"},"raw":"[2,\"msg-005\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Charging\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:02:31.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{},"raw":"[3,\"msg-005\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","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-15T20:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T20:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T20:07:30.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:15:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"MeterValues","payload":{"connectorId":1,"transactionId":100007,"meterValue":[{"timestamp":"2024-01-15T20:15:00.000Z","sampledValue":[{"value":"5000","measurand":"Energy.Active.Import.Register","unit":"Wh"}]}]},"raw":"[2,\"msg-006\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100007,\"meterValue\":[{\"timestamp\":\"2024-01-15T20:15:00.000Z\",\"sampledValue\":[{\"value\":\"5000\",\"measurand\":\"Energy.Active.Import.Register\",\"unit\":\"Wh\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:15:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{},"raw":"[3,\"msg-006\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:30:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-007","action":"StopTransaction","payload":{"transactionId":100007,"idTag":"SYNTHETIC-TAG-007","meterStop":5000,"timestamp":"2024-01-15T20:30:00.000Z","reason":"PowerSurge"},"raw":"[2,\"msg-007\",\"StopTransaction\",{\"transactionId\":100007,\"idTag\":\"SYNTHETIC-TAG-007\",\"meterStop\":5000,\"timestamp\":\"2024-01-15T20:30:00.000Z\",\"reason\":\"PowerSurge\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:30:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-007","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-007\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:30:01.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-008","action":"StatusNotification","payload":{"connectorId":1,"status":"Available","errorCode":"NoError"},"raw":"[2,\"msg-008\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Available\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T20:30:01.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-007","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-008","payload":{},"raw":"[3,\"msg-008\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/expected.json new file mode 100644 index 0000000..ef98d0f --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/expected.json @@ -0,0 +1,104 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 14, + "calls": 7, + "callResults": 7, + "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": "Authorize" + }, + { + "index": 3, + "messageType": "CALLRESULT", + "messageId": "msg-002", + "action": "Authorize", + "correlatesWith": 2 + }, + { + "index": 4, + "messageType": "CALL", + "messageId": "msg-003", + "action": "StartTransaction" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-003", + "action": "StartTransaction", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-004", + "action": "MeterValues" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "MeterValues", + "correlatesWith": 6 + }, + { + "index": 8, + "messageType": "CALL", + "messageId": "msg-005", + "action": "MeterValues" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-005", + "action": "MeterValues", + "correlatesWith": 8 + }, + { + "index": 10, + "messageType": "CALL", + "messageId": "msg-006", + "action": "Heartbeat" + }, + { + "index": 11, + "messageType": "CALLRESULT", + "messageId": "msg-006", + "action": "Heartbeat", + "correlatesWith": 10 + }, + { + "index": 12, + "messageType": "CALL", + "messageId": "msg-007", + "action": "StopTransaction" + }, + { + "index": 13, + "messageType": "CALLRESULT", + "messageId": "msg-007", + "action": "StopTransaction", + "correlatesWith": 12 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/trace.jsonl new file mode 100644 index 0000000..09e0c32 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-anomaly/trace.jsonl @@ -0,0 +1,14 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-012","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-012\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T14:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T14:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-002","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-012"},"raw":"[2,\"msg-002\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-012\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-002\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-012","meterStart":0},"raw":"[2,\"msg-003\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-012\",\"meterStart\":0}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{"idTagInfo":{"status":"Accepted"},"transactionId":100012},"raw":"[3,\"msg-003\",{\"idTagInfo\":{\"status\":\"Accepted\"},\"transactionId\":100012}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:05:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"MeterValues","payload":{"connectorId":1,"transactionId":100012,"meterValue":[{"sampledValue":[{"value":"5000"}]}]},"raw":"[2,\"msg-004\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100012,\"meterValue\":[{\"sampledValue\":[{\"value\":\"5000\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:05:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{},"raw":"[3,\"msg-004\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:06:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"MeterValues","payload":{"connectorId":1,"transactionId":100012,"meterValue":[{"sampledValue":[{"value":"3000"}]}]},"raw":"[2,\"msg-005\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100012,\"meterValue\":[{\"sampledValue\":[{\"value\":\"3000\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:06:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{},"raw":"[3,\"msg-005\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:10:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"Heartbeat","payload":{},"raw":"[2,\"msg-006\",\"Heartbeat\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:10:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{"currentTime":"2024-01-15T14:10:00.500Z"},"raw":"[3,\"msg-006\",{\"currentTime\":\"2024-01-15T14:10:00.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:30:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-007","action":"StopTransaction","payload":{"transactionId":100012,"idTag":"SYNTHETIC-TAG-012","meterStop":3000,"reason":"EVDisconnected"},"raw":"[2,\"msg-007\",\"StopTransaction\",{\"transactionId\":100012,\"idTag\":\"SYNTHETIC-TAG-012\",\"meterStop\":3000,\"reason\":\"EVDisconnected\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:30:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-012","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-007","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-007\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/expected.json new file mode 100644 index 0000000..5b32d76 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/expected.json @@ -0,0 +1,117 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 16, + "calls": 8, + "callResults": 8, + "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": "Authorize" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-003", + "action": "Authorize", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-004", + "action": "StartTransaction" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "StartTransaction", + "correlatesWith": 6 + }, + { + "index": 8, + "messageType": "CALL", + "messageId": "msg-005", + "action": "StatusNotification" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-005", + "action": "StatusNotification", + "correlatesWith": 8 + }, + { + "index": 10, + "messageType": "CALL", + "messageId": "msg-hb-1", + "action": "Heartbeat" + }, + { + "index": 11, + "messageType": "CALLRESULT", + "messageId": "msg-hb-1", + "action": "Heartbeat", + "correlatesWith": 10 + }, + { + "index": 12, + "messageType": "CALL", + "messageId": "msg-006", + "action": "StopTransaction" + }, + { + "index": 13, + "messageType": "CALLRESULT", + "messageId": "msg-006", + "action": "StopTransaction", + "correlatesWith": 12 + }, + { + "index": 14, + "messageType": "CALL", + "messageId": "msg-007", + "action": "StatusNotification" + }, + { + "index": 15, + "messageType": "CALLRESULT", + "messageId": "msg-007", + "action": "StatusNotification", + "correlatesWith": 14 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/trace.jsonl new file mode 100644 index 0000000..33f1a3b --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/meter-value-gap/trace.jsonl @@ -0,0 +1,16 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-006","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-006\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T18:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T18:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","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-15T18:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-006"},"raw":"[2,\"msg-003\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-006\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{"idTagInfo":{"status":"Accepted","expiryDate":"2024-12-31T23:59:59.000Z"}},"raw":"[3,\"msg-003\",{\"idTagInfo\":{\"status\":\"Accepted\",\"expiryDate\":\"2024-12-31T23:59:59.000Z\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:02:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-006","meterStart":0,"timestamp":"2024-01-15T18:02:30.000Z"},"raw":"[2,\"msg-004\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-006\",\"meterStart\":0,\"timestamp\":\"2024-01-15T18:02:30.000Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:02:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"transactionId":100006,"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-004\",{\"transactionId\":100006,\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:02:31.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"StatusNotification","payload":{"connectorId":1,"status":"Charging","errorCode":"NoError"},"raw":"[2,\"msg-005\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Charging\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:02:31.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{},"raw":"[3,\"msg-005\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","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-15T18:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T18:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T18:07:30.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:30:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"StopTransaction","payload":{"transactionId":100006,"idTag":"SYNTHETIC-TAG-006","meterStop":0,"timestamp":"2024-01-15T18:30:00.000Z","reason":"EVDisconnected"},"raw":"[2,\"msg-006\",\"StopTransaction\",{\"transactionId\":100006,\"idTag\":\"SYNTHETIC-TAG-006\",\"meterStop\":0,\"timestamp\":\"2024-01-15T18:30:00.000Z\",\"reason\":\"EVDisconnected\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:30:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-006\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:30:01.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-007","action":"StatusNotification","payload":{"connectorId":1,"status":"Available","errorCode":"NoError"},"raw":"[2,\"msg-007\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Available\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T18:30:01.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-006","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-007","payload":{},"raw":"[3,\"msg-007\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/expected.json new file mode 100644 index 0000000..df44d67 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/expected.json @@ -0,0 +1,156 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 22, + "calls": 11, + "callResults": 11, + "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": "MeterValues" + }, + { + "index": 17, + "messageType": "CALLRESULT", + "messageId": "msg-008", + "action": "MeterValues", + "correlatesWith": 16 + }, + { + "index": 18, + "messageType": "CALL", + "messageId": "msg-009", + "action": "StopTransaction" + }, + { + "index": 19, + "messageType": "CALLRESULT", + "messageId": "msg-009", + "action": "StopTransaction", + "correlatesWith": 18 + }, + { + "index": 20, + "messageType": "CALL", + "messageId": "msg-010", + "action": "StatusNotification" + }, + { + "index": 21, + "messageType": "CALLRESULT", + "messageId": "msg-010", + "action": "StatusNotification", + "correlatesWith": 20 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/trace.jsonl new file mode 100644 index 0000000..37de35b --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/normal-session/trace.jsonl @@ -0,0 +1,22 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:00: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","chargePointSerialNumber":"CS-SYNTHETIC-001","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-001\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T10:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T10:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","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-15T10:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","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-15T10:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{},"raw":"[3,\"msg-003\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:02:15.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-001"},"raw":"[2,\"msg-004\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-001\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:02:15.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"idTagInfo":{"status":"Accepted","expiryDate":"2024-12-31T23:59:59.000Z","parentIdTag":"SYNTHETIC-PARENT-001"}},"raw":"[3,\"msg-004\",{\"idTagInfo\":{\"status\":\"Accepted\",\"expiryDate\":\"2024-12-31T23:59:59.000Z\",\"parentIdTag\":\"SYNTHETIC-PARENT-001\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:02:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-001","meterStart":0,"timestamp":"2024-01-15T10:02:30.000Z"},"raw":"[2,\"msg-005\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-001\",\"meterStart\":0,\"timestamp\":\"2024-01-15T10:02:30.000Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:02:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{"transactionId":100001,"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-005\",{\"transactionId\":100001,\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:02:31.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","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-15T10:02:31.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{},"raw":"[3,\"msg-006\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","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-15T10:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T10:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T10:07:30.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:15:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-007","action":"MeterValues","payload":{"connectorId":1,"transactionId":100001,"meterValue":[{"timestamp":"2024-01-15T10:15:00.000Z","sampledValue":[{"value":"5000","measurand":"Energy.Active.Import.Register","unit":"Wh"}]}]},"raw":"[2,\"msg-007\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100001,\"meterValue\":[{\"timestamp\":\"2024-01-15T10:15:00.000Z\",\"sampledValue\":[{\"value\":\"5000\",\"measurand\":\"Energy.Active.Import.Register\",\"unit\":\"Wh\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:15:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-007","payload":{},"raw":"[3,\"msg-007\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:30:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-008","action":"MeterValues","payload":{"connectorId":1,"transactionId":100001,"meterValue":[{"timestamp":"2024-01-15T10:30:00.000Z","sampledValue":[{"value":"10000","measurand":"Energy.Active.Import.Register","unit":"Wh"}]}]},"raw":"[2,\"msg-008\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100001,\"meterValue\":[{\"timestamp\":\"2024-01-15T10:30:00.000Z\",\"sampledValue\":[{\"value\":\"10000\",\"measurand\":\"Energy.Active.Import.Register\",\"unit\":\"Wh\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:30:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-008","payload":{},"raw":"[3,\"msg-008\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:35:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-009","action":"StopTransaction","payload":{"transactionId":100001,"idTag":"SYNTHETIC-TAG-001","meterStop":10000,"timestamp":"2024-01-15T10:35:00.000Z","reason":"EVDisconnected"},"raw":"[2,\"msg-009\",\"StopTransaction\",{\"transactionId\":100001,\"idTag\":\"SYNTHETIC-TAG-001\",\"meterStop\":10000,\"timestamp\":\"2024-01-15T10:35:00.000Z\",\"reason\":\"EVDisconnected\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:35:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-009","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-009\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:35:01.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-010","action":"StatusNotification","payload":{"connectorId":1,"status":"Available","errorCode":"NoError"},"raw":"[2,\"msg-010\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Available\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T10:35:01.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-001","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-010","payload":{},"raw":"[3,\"msg-010\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/expected.json new file mode 100644 index 0000000..133daad --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/expected.json @@ -0,0 +1,91 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 12, + "calls": 6, + "callResults": 6, + "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": "Authorize" + }, + { + "index": 3, + "messageType": "CALLRESULT", + "messageId": "msg-002", + "action": "Authorize", + "correlatesWith": 2 + }, + { + "index": 4, + "messageType": "CALL", + "messageId": "msg-003", + "action": "StartTransaction" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-003", + "action": "StartTransaction", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-004", + "action": "MeterValues" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "MeterValues", + "correlatesWith": 6 + }, + { + "index": 8, + "messageType": "CALL", + "messageId": "msg-005", + "action": "Heartbeat" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-005", + "action": "Heartbeat", + "correlatesWith": 8 + }, + { + "index": 10, + "messageType": "CALL", + "messageId": "msg-006", + "action": "StopTransaction" + }, + { + "index": 11, + "messageType": "CALLRESULT", + "messageId": "msg-006", + "action": "StopTransaction", + "correlatesWith": 10 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/trace.jsonl new file mode 100644 index 0000000..358801a --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/short-session/trace.jsonl @@ -0,0 +1,12 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-013","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-013\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T09:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T09:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:01.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-002","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-013"},"raw":"[2,\"msg-002\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-013\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:01.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-002\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:02.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-013","meterStart":0},"raw":"[2,\"msg-003\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-013\",\"meterStart\":0}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:02.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{"idTagInfo":{"status":"Accepted"},"transactionId":100013},"raw":"[3,\"msg-003\",{\"idTagInfo\":{\"status\":\"Accepted\"},\"transactionId\":100013}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:03.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"MeterValues","payload":{"connectorId":1,"transactionId":100013,"meterValue":[{"sampledValue":[{"value":"100"}]}]},"raw":"[2,\"msg-004\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100013,\"meterValue\":[{\"sampledValue\":[{\"value\":\"100\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:03.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{},"raw":"[3,\"msg-004\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:04.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"Heartbeat","payload":{},"raw":"[2,\"msg-005\",\"Heartbeat\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:04.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{"currentTime":"2024-01-15T09:00:04.500Z"},"raw":"[3,\"msg-005\",{\"currentTime\":\"2024-01-15T09:00:04.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:10.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"StopTransaction","payload":{"transactionId":100013,"idTag":"SYNTHETIC-TAG-013","meterStop":100,"reason":"EVDisconnected"},"raw":"[2,\"msg-006\",\"StopTransaction\",{\"transactionId\":100013,\"idTag\":\"SYNTHETIC-TAG-013\",\"meterStop\":100,\"reason\":\"EVDisconnected\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T09:00:10.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-013","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-006\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/expected.json new file mode 100644 index 0000000..0a5e39d --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/expected.json @@ -0,0 +1,39 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 4, + "calls": 2, + "callResults": 2, + "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": "Heartbeat" + }, + { + "index": 3, + "messageType": "CALLRESULT", + "messageId": "msg-002", + "action": "Heartbeat", + "correlatesWith": 2 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/trace.jsonl new file mode 100644 index 0000000..2609a9d --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/slow-csms-response/trace.jsonl @@ -0,0 +1,4 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-011","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-011","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-011\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:00:15.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-011","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T06:00:15.000Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T06:00:15.000Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:00:16.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-011","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-002","action":"Heartbeat","payload":{},"raw":"[2,\"msg-002\",\"Heartbeat\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T06:00:16.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-011","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{"currentTime":"2024-01-15T06:00:16.500Z"},"raw":"[3,\"msg-002\",{\"currentTime\":\"2024-01-15T06:00:16.500Z\"}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/expected.json new file mode 100644 index 0000000..cfded5a --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/expected.json @@ -0,0 +1,104 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 14, + "calls": 7, + "callResults": 7, + "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": "Authorize" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-003", + "action": "Authorize", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-004", + "action": "StartTransaction" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "StartTransaction", + "correlatesWith": 6 + }, + { + "index": 8, + "messageType": "CALL", + "messageId": "msg-005", + "action": "StatusNotification" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-005", + "action": "StatusNotification", + "correlatesWith": 8 + }, + { + "index": 10, + "messageType": "CALL", + "messageId": "msg-hb-1", + "action": "Heartbeat" + }, + { + "index": 11, + "messageType": "CALLRESULT", + "messageId": "msg-hb-1", + "action": "Heartbeat", + "correlatesWith": 10 + }, + { + "index": 12, + "messageType": "CALL", + "messageId": "msg-006", + "action": "MeterValues" + }, + { + "index": 13, + "messageType": "CALLRESULT", + "messageId": "msg-006", + "action": "MeterValues", + "correlatesWith": 12 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/trace.jsonl new file mode 100644 index 0000000..c63c891 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/station-offline/trace.jsonl @@ -0,0 +1,14 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-004","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-004\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T14:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T14:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","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-15T14:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-004"},"raw":"[2,\"msg-003\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-004\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{"idTagInfo":{"status":"Accepted","expiryDate":"2024-12-31T23:59:59.000Z"}},"raw":"[3,\"msg-003\",{\"idTagInfo\":{\"status\":\"Accepted\",\"expiryDate\":\"2024-12-31T23:59:59.000Z\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-004","meterStart":0,"timestamp":"2024-01-15T14:02:30.000Z"},"raw":"[2,\"msg-004\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-004\",\"meterStart\":0,\"timestamp\":\"2024-01-15T14:02:30.000Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"transactionId":100004,"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-004\",{\"transactionId\":100004,\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:31.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"StatusNotification","payload":{"connectorId":1,"status":"Charging","errorCode":"NoError"},"raw":"[2,\"msg-005\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Charging\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:02:31.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{},"raw":"[3,\"msg-005\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","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-15T14:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T14:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T14:07:30.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:15:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"MeterValues","payload":{"connectorId":1,"transactionId":100004,"meterValue":[{"timestamp":"2024-01-15T14:15:00.000Z","sampledValue":[{"value":"2000","measurand":"Energy.Active.Import.Register","unit":"Wh"}]}]},"raw":"[2,\"msg-006\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100004,\"meterValue\":[{\"timestamp\":\"2024-01-15T14:15:00.000Z\",\"sampledValue\":[{\"value\":\"2000\",\"measurand\":\"Energy.Active.Import.Register\",\"unit\":\"Wh\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T14:15:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-004","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{},"raw":"[3,\"msg-006\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/expected.json new file mode 100644 index 0000000..07219b9 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/expected.json @@ -0,0 +1,78 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 10, + "calls": 5, + "callResults": 5, + "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-hb-1", + "action": "Heartbeat" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-hb-1", + "action": "Heartbeat", + "correlatesWith": 6 + }, + { + "index": 8, + "messageType": "CALL", + "messageId": "msg-004", + "action": "StatusNotification" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "StatusNotification", + "correlatesWith": 8 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/trace.jsonl new file mode 100644 index 0000000..52ba1c9 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/status-transition-violation/trace.jsonl @@ -0,0 +1,10 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-009","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-009\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T08:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T08:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","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-15T08:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"StatusNotification","payload":{"connectorId":1,"status":"Available","errorCode":"NoError"},"raw":"[2,\"msg-003\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Available\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{},"raw":"[3,\"msg-003\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","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-15T08:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T08:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T08:07:30.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:12:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"StatusNotification","payload":{"connectorId":1,"status":"Finishing","errorCode":"NoError"},"raw":"[2,\"msg-004\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Finishing\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:12:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-009","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{},"raw":"[3,\"msg-004\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/expected.json new file mode 100644 index 0000000..b810f80 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/expected.json @@ -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": "StartTransaction" + }, + { + "index": 1, + "messageType": "CALLRESULT", + "messageId": "msg-001", + "action": "StartTransaction", + "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": "MeterValues" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-003", + "action": "MeterValues", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-004", + "action": "StopTransaction" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "StopTransaction", + "correlatesWith": 6 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/trace.jsonl new file mode 100644 index 0000000..3c44d30 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-start/trace.jsonl @@ -0,0 +1,8 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-008","meterStart":0,"timestamp":"2024-01-15T22:00:00.000Z"},"raw":"[2,\"msg-001\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-008\",\"meterStart\":0,\"timestamp\":\"2024-01-15T22:00:00.000Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"transactionId":100008,"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-001\",{\"transactionId\":100008,\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:00:01.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-002","action":"StatusNotification","payload":{"connectorId":1,"status":"Charging","errorCode":"NoError"},"raw":"[2,\"msg-002\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Charging\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:00:01.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:00:10.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"MeterValues","payload":{"connectorId":1,"transactionId":100008,"meterValue":[{"timestamp":"2024-01-15T22:00:10.000Z","sampledValue":[{"value":"100","measurand":"Energy.Active.Import.Register","unit":"Wh"}]}]},"raw":"[2,\"msg-003\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100008,\"meterValue\":[{\"timestamp\":\"2024-01-15T22:00:10.000Z\",\"sampledValue\":[{\"value\":\"100\",\"measurand\":\"Energy.Active.Import.Register\",\"unit\":\"Wh\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:00:10.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{},"raw":"[3,\"msg-003\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:01:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"StopTransaction","payload":{"transactionId":100008,"idTag":"SYNTHETIC-TAG-008","meterStop":100,"timestamp":"2024-01-15T22:01:30.000Z","reason":"EVDisconnected"},"raw":"[2,\"msg-004\",\"StopTransaction\",{\"transactionId\":100008,\"idTag\":\"SYNTHETIC-TAG-008\",\"meterStop\":100,\"timestamp\":\"2024-01-15T22:01:30.000Z\",\"reason\":\"EVDisconnected\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T22:01:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-008","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-004\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/expected.json new file mode 100644 index 0000000..61b725f --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/expected.json @@ -0,0 +1,130 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 18, + "calls": 9, + "callResults": 9, + "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": "Authorize" + }, + { + "index": 5, + "messageType": "CALLRESULT", + "messageId": "msg-003", + "action": "Authorize", + "correlatesWith": 4 + }, + { + "index": 6, + "messageType": "CALL", + "messageId": "msg-004", + "action": "StartTransaction" + }, + { + "index": 7, + "messageType": "CALLRESULT", + "messageId": "msg-004", + "action": "StartTransaction", + "correlatesWith": 6 + }, + { + "index": 8, + "messageType": "CALL", + "messageId": "msg-005", + "action": "StatusNotification" + }, + { + "index": 9, + "messageType": "CALLRESULT", + "messageId": "msg-005", + "action": "StatusNotification", + "correlatesWith": 8 + }, + { + "index": 10, + "messageType": "CALL", + "messageId": "msg-hb-1", + "action": "Heartbeat" + }, + { + "index": 11, + "messageType": "CALLRESULT", + "messageId": "msg-hb-1", + "action": "Heartbeat", + "correlatesWith": 10 + }, + { + "index": 12, + "messageType": "CALL", + "messageId": "msg-006", + "action": "MeterValues" + }, + { + "index": 13, + "messageType": "CALLRESULT", + "messageId": "msg-006", + "action": "MeterValues", + "correlatesWith": 12 + }, + { + "index": 14, + "messageType": "CALL", + "messageId": "msg-007", + "action": "StopTransaction" + }, + { + "index": 15, + "messageType": "CALLRESULT", + "messageId": "msg-007", + "action": "StopTransaction", + "correlatesWith": 14 + }, + { + "index": 16, + "messageType": "CALL", + "messageId": "msg-008", + "action": "StatusNotification" + }, + { + "index": 17, + "messageType": "CALLRESULT", + "messageId": "msg-008", + "action": "StatusNotification", + "correlatesWith": 16 + } + ], + "unansweredCalls": [], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/trace.jsonl new file mode 100644 index 0000000..04d2dbb --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unexpected-stop-reason/trace.jsonl @@ -0,0 +1,18 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-005","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-005\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:00:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-001","payload":{"currentTime":"2024-01-15T16:00:00.500Z","interval":300,"status":"Accepted"},"raw":"[3,\"msg-001\",{\"currentTime\":\"2024-01-15T16:00:00.500Z\",\"interval\":300,\"status\":\"Accepted\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:01:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","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-15T16:01:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{},"raw":"[3,\"msg-002\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:02:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-003","action":"Authorize","payload":{"idTag":"SYNTHETIC-TAG-005"},"raw":"[2,\"msg-003\",\"Authorize\",{\"idTag\":\"SYNTHETIC-TAG-005\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:02:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-003","payload":{"idTagInfo":{"status":"Accepted","expiryDate":"2024-12-31T23:59:59.000Z"}},"raw":"[3,\"msg-003\",{\"idTagInfo\":{\"status\":\"Accepted\",\"expiryDate\":\"2024-12-31T23:59:59.000Z\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:02:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-004","action":"StartTransaction","payload":{"connectorId":1,"idTag":"SYNTHETIC-TAG-005","meterStart":0,"timestamp":"2024-01-15T16:02:30.000Z"},"raw":"[2,\"msg-004\",\"StartTransaction\",{\"connectorId\":1,\"idTag\":\"SYNTHETIC-TAG-005\",\"meterStart\":0,\"timestamp\":\"2024-01-15T16:02:30.000Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:02:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-004","payload":{"transactionId":100005,"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-004\",{\"transactionId\":100005,\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:02:31.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-005","action":"StatusNotification","payload":{"connectorId":1,"status":"Charging","errorCode":"NoError"},"raw":"[2,\"msg-005\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Charging\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:02:31.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-005","payload":{},"raw":"[3,\"msg-005\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:07:30.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","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-15T16:07:30.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-hb-1","payload":{"currentTime":"2024-01-15T16:07:30.500Z"},"raw":"[3,\"msg-hb-1\",{\"currentTime\":\"2024-01-15T16:07:30.500Z\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:20:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-006","action":"MeterValues","payload":{"connectorId":1,"transactionId":100005,"meterValue":[{"timestamp":"2024-01-15T16:20:00.000Z","sampledValue":[{"value":"4000","measurand":"Energy.Active.Import.Register","unit":"Wh"}]}]},"raw":"[2,\"msg-006\",\"MeterValues\",{\"connectorId\":1,\"transactionId\":100005,\"meterValue\":[{\"timestamp\":\"2024-01-15T16:20:00.000Z\",\"sampledValue\":[{\"value\":\"4000\",\"measurand\":\"Energy.Active.Import.Register\",\"unit\":\"Wh\"}]}]}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:20:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-006","payload":{},"raw":"[3,\"msg-006\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:25:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-007","action":"StopTransaction","payload":{"transactionId":100005,"idTag":"SYNTHETIC-TAG-005","meterStop":4000,"timestamp":"2024-01-15T16:25:00.000Z","reason":"Other"},"raw":"[2,\"msg-007\",\"StopTransaction\",{\"transactionId\":100005,\"idTag\":\"SYNTHETIC-TAG-005\",\"meterStop\":4000,\"timestamp\":\"2024-01-15T16:25:00.000Z\",\"reason\":\"Other\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:25:00.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-007","payload":{"idTagInfo":{"status":"Accepted"}},"raw":"[3,\"msg-007\",{\"idTagInfo\":{\"status\":\"Accepted\"}}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:25:01.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","connectorId":1,"direction":"cp-to-csms","messageType":"CALL","messageId":"msg-008","action":"StatusNotification","payload":{"connectorId":1,"status":"Available","errorCode":"NoError"},"raw":"[2,\"msg-008\",\"StatusNotification\",{\"connectorId\":1,\"status\":\"Available\",\"errorCode\":\"NoError\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T16:25:01.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-005","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-008","payload":{},"raw":"[3,\"msg-008\",{}]"} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/expected.json b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/expected.json new file mode 100644 index 0000000..4aa437e --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/expected.json @@ -0,0 +1,32 @@ +{ + "schemaVersion": "1.1", + "counts": { + "records": 3, + "calls": 2, + "callResults": 1, + "callErrors": 0 + }, + "records": [ + { + "index": 0, + "messageType": "CALL", + "messageId": "msg-001", + "action": "BootNotification" + }, + { + "index": 1, + "messageType": "CALL", + "messageId": "msg-002", + "action": "Heartbeat" + }, + { + "index": 2, + "messageType": "CALLRESULT", + "messageId": "msg-002", + "action": "Heartbeat", + "correlatesWith": 1 + } + ], + "unansweredCalls": [0], + "orphanResponses": [] +} diff --git a/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/trace.jsonl b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/trace.jsonl new file mode 100644 index 0000000..3945b15 --- /dev/null +++ b/packages/toolkit/src/core/__fixtures__/open-ocpp-trace/unresponsive-csms/trace.jsonl @@ -0,0 +1,3 @@ +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:00:00.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-015","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-001","action":"BootNotification","payload":{"chargePointVendor":"SyntheticVendor","chargePointModel":"SM-100","chargePointSerialNumber":"CS-SYNTHETIC-015","firmwareVersion":"1.0.0"},"raw":"[2,\"msg-001\",\"BootNotification\",{\"chargePointVendor\":\"SyntheticVendor\",\"chargePointModel\":\"SM-100\",\"chargePointSerialNumber\":\"CS-SYNTHETIC-015\",\"firmwareVersion\":\"1.0.0\"}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:00:05.000Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-015","direction":"cp-to-csms","messageType":"CALL","messageId":"msg-002","action":"Heartbeat","payload":{},"raw":"[2,\"msg-002\",\"Heartbeat\",{}]"} +{"schemaVersion":"1.1","timestamp":"2024-01-15T08:00:05.500Z","ocppVersion":"1.6","transport":"json","chargePointId":"CS-SYNTHETIC-015","direction":"csms-to-cp","messageType":"CALLRESULT","messageId":"msg-002","payload":{"currentTime":"2024-01-15T08:00:05.500Z"},"raw":"[3,\"msg-002\",{\"currentTime\":\"2024-01-15T08:00:05.500Z\"}]"} diff --git a/packages/toolkit/src/core/index.ts b/packages/toolkit/src/core/index.ts index 8544a39..4b3bf95 100644 --- a/packages/toolkit/src/core/index.ts +++ b/packages/toolkit/src/core/index.ts @@ -12,6 +12,21 @@ export * from './schemas.js'; export { parseTrace, ParseError, MAX_INPUT_SIZE_BYTES, MAX_EVENT_COUNT } from './parser.js'; export type { TraceFormat } from './parser.js'; +// Open OCPP Trace interop (https://github.com/open-ocpp-trace/specification) +export { + parseOpenOcppTrace, + deriveOpenOcppTraceView, + looksLikeOpenOcppTrace, + OPEN_OCPP_TRACE_SCHEMA_VERSION, +} from './openOcppTrace.js'; +export type { + OpenOcppTraceRecord, + OpenOcppTraceView, + OpenOcppTraceViewRecord, + OpenOcppTraceDirection, + OpenOcppTraceMessageType, +} from './openOcppTrace.js'; + // Normalizer export { normalizeEvents, diff --git a/packages/toolkit/src/core/openOcppTrace.conformance.test.ts b/packages/toolkit/src/core/openOcppTrace.conformance.test.ts new file mode 100644 index 0000000..17953ef --- /dev/null +++ b/packages/toolkit/src/core/openOcppTrace.conformance.test.ts @@ -0,0 +1,69 @@ +/** + * Conformance test: the vendored Open OCPP Trace specification fixtures. + * + * For each fixture, DebugKit (acting as a reference consumer) must derive the + * exact consumer view the specification pins down in `expected.json`, and must + * parse the trace into one normalized event per record. The fixtures are + * vendored from https://github.com/open-ocpp-trace/specification; see the + * README in the fixtures directory. + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync, readdirSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { + parseOpenOcppTrace, + deriveOpenOcppTraceView, + type OpenOcppTraceView, +} from './openOcppTrace.js'; +import { buildSessionTimeline } from './timeline.js'; +import { detectFailures } from './detection.js'; + +const fixturesDir = join( + dirname(fileURLToPath(import.meta.url)), + '__fixtures__', + 'open-ocpp-trace', +); + +const fixtureNames = readdirSync(fixturesDir) + .filter((name) => statSync(join(fixturesDir, name)).isDirectory()) + .sort(); + +describe('Open OCPP Trace conformance fixtures', () => { + it('vendors the full 15-scenario corpus', () => { + expect(fixtureNames).toHaveLength(15); + }); + + describe.each(fixtureNames)('%s', (name) => { + const dir = join(fixturesDir, name); + const trace = readFileSync(join(dir, 'trace.jsonl'), 'utf8'); + const expected = JSON.parse( + readFileSync(join(dir, 'expected.json'), 'utf8'), + ) as OpenOcppTraceView; + + it('derives the expected consumer view', () => { + expect(deriveOpenOcppTraceView(trace)).toEqual(expected); + }); + + it('parses into one event per record with no warnings', () => { + const result = parseOpenOcppTrace(trace); + expect(result.warnings).toEqual([]); + expect(result.events).toHaveLength(expected.counts.records); + }); + + it('preserves the verbatim frame from raw', () => { + const result = parseOpenOcppTrace(trace); + for (const event of result.events) { + expect(Array.isArray(event.rawMessage)).toBe(true); + expect(event.messageId).toBe(event.rawMessage[1]); + } + }); + + it('flows through timeline and detection without throwing', () => { + const result = parseOpenOcppTrace(trace); + const sessions = buildSessionTimeline(result.events); + expect(() => detectFailures(result.events, sessions)).not.toThrow(); + }); + }); +}); diff --git a/packages/toolkit/src/core/openOcppTrace.test.ts b/packages/toolkit/src/core/openOcppTrace.test.ts new file mode 100644 index 0000000..ec48358 --- /dev/null +++ b/packages/toolkit/src/core/openOcppTrace.test.ts @@ -0,0 +1,211 @@ +/** + * Unit tests for the Open OCPP Trace adapter: detection, direction mapping, + * action derivation, raw precedence, unknown-field tolerance, malformed-record + * handling, and the untrusted-input limits. + */ + +import { describe, it, expect } from 'vitest'; +import { + parseOpenOcppTrace, + deriveOpenOcppTraceView, + looksLikeOpenOcppTrace, +} from './openOcppTrace.js'; +import { parseTrace, ParseError, MAX_INPUT_SIZE_BYTES } from './parser.js'; + +/** A minimal well-formed boot exchange in the Open OCPP Trace format (JSONL). */ +const bootExchange = [ + JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2024-01-15T10:00:00.000Z', + ocppVersion: '1.6', + transport: 'json', + chargePointId: 'CP1', + direction: 'cp-to-csms', + messageType: 'CALL', + messageId: 'm1', + action: 'BootNotification', + payload: { chargePointVendor: 'V', chargePointModel: 'M' }, + raw: '[2,"m1","BootNotification",{"chargePointVendor":"V","chargePointModel":"M"}]', + }), + JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2024-01-15T10:00:00.500Z', + transport: 'json', + direction: 'csms-to-cp', + messageType: 'CALLRESULT', + messageId: 'm1', + payload: { status: 'Accepted' }, + raw: '[3,"m1",{"status":"Accepted"}]', + }), +].join('\n'); + +describe('looksLikeOpenOcppTrace', () => { + it('recognizes JSONL records', () => { + expect(looksLikeOpenOcppTrace(bootExchange)).toBe(true); + }); + + it('recognizes a JSON array of records', () => { + const arr = JSON.stringify(bootExchange.split('\n').map((l) => JSON.parse(l))); + expect(looksLikeOpenOcppTrace(arr)).toBe(true); + }); + + it('rejects the internal bare-array format', () => { + expect(looksLikeOpenOcppTrace('[[2,"m1","Heartbeat",{}]]')).toBe(false); + }); + + it('rejects the internal JSONL format (message array, internal direction)', () => { + const internal = JSON.stringify({ + direction: 'CS_TO_CSMS', + message: [2, 'm1', 'Heartbeat', {}], + }); + expect(looksLikeOpenOcppTrace(internal)).toBe(false); + }); + + it('rejects non-JSON and empty input', () => { + expect(looksLikeOpenOcppTrace('not json')).toBe(false); + expect(looksLikeOpenOcppTrace('')).toBe(false); + }); +}); + +describe('parseOpenOcppTrace - direction mapping', () => { + it('maps cp-to-csms and csms-to-cp to the internal directions', () => { + const { events, warnings } = parseOpenOcppTrace(bootExchange); + expect(warnings).toEqual([]); + expect(events).toHaveLength(2); + expect(events[0]?.direction).toBe('CS_TO_CSMS'); + expect(events[1]?.direction).toBe('CSMS_TO_CS'); + }); +}); + +describe('parseTrace - auto-detection', () => { + it('delegates the Open OCPP Trace format to its parser', () => { + expect(parseTrace(bootExchange)).toEqual(parseOpenOcppTrace(bootExchange)); + }); + + it('still parses the internal formats', () => { + const internal = JSON.stringify({ + events: [{ direction: 'CS_TO_CSMS', message: [2, 'm1', 'Heartbeat', {}] }], + }); + const result = parseTrace(internal); + expect(result.events).toHaveLength(1); + expect(result.events[0]?.action).toBe('Heartbeat'); + }); +}); + +describe('parseOpenOcppTrace - action derivation', () => { + it('derives a response action from its correlated CALL', () => { + const view = deriveOpenOcppTraceView(bootExchange); + const response = view.records[1]; + expect(response?.messageType).toBe('CALLRESULT'); + expect(response?.action).toBe('BootNotification'); + expect(response?.correlatesWith).toBe(0); + expect(view.unansweredCalls).toEqual([]); + expect(view.orphanResponses).toEqual([]); + }); + + it('reports an unanswered CALL', () => { + const oneCall = JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2024-01-15T10:00:00.000Z', + transport: 'json', + direction: 'cp-to-csms', + messageType: 'CALL', + messageId: 'm9', + action: 'Heartbeat', + payload: {}, + }); + const view = deriveOpenOcppTraceView(oneCall); + expect(view.unansweredCalls).toEqual([0]); + expect(view.orphanResponses).toEqual([]); + }); +}); + +describe('parseOpenOcppTrace - raw precedence', () => { + it('uses raw when it disagrees with the decomposed fields, and warns', () => { + const record = JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2024-01-15T10:00:00.000Z', + transport: 'json', + direction: 'cp-to-csms', + messageType: 'CALL', + messageId: 'm1', + action: 'Heartbeat', + payload: {}, + // raw carries a different action than the `action` field. + raw: '[2,"m1","BootNotification",{}]', + }); + const { events, warnings } = parseOpenOcppTrace(record); + expect(events).toHaveLength(1); + // raw wins. + expect(events[0]?.action).toBe('BootNotification'); + expect(warnings.some((w) => w.message.includes('disagrees'))).toBe(true); + }); + + it('falls back to the decomposed fields when raw is not valid JSON', () => { + const record = JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2024-01-15T10:00:00.000Z', + transport: 'json', + direction: 'cp-to-csms', + messageType: 'CALL', + messageId: 'm1', + action: 'Heartbeat', + payload: {}, + raw: '[2,"m1","BootNotif', // truncated + }); + const { events, warnings } = parseOpenOcppTrace(record); + expect(events).toHaveLength(1); + expect(events[0]?.action).toBe('Heartbeat'); + expect(warnings.some((w) => w.message.includes('not valid JSON'))).toBe(true); + }); +}); + +describe('parseOpenOcppTrace - tolerance and limits', () => { + it('ignores unknown fields (forward compatibility)', () => { + const record = JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2024-01-15T10:00:00.000Z', + transport: 'json', + direction: 'cp-to-csms', + messageType: 'CALL', + messageId: 'm1', + action: 'Heartbeat', + payload: {}, + raw: '[2,"m1","Heartbeat",{}]', + futureField: { anything: true }, + }); + const { events, warnings } = parseOpenOcppTrace(record); + expect(events).toHaveLength(1); + expect(warnings).toEqual([]); + }); + + it('skips a malformed record with a warning and keeps the rest', () => { + const input = [ + JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2024-01-15T10:00:00.000Z', + transport: 'json', + direction: 'cp-to-csms', + messageType: 'CALL', + messageId: 'm1', + action: 'Heartbeat', + payload: {}, + raw: '[2,"m1","Heartbeat",{}]', + }), + JSON.stringify({ not: 'a valid record' }), + ].join('\n'); + const { events, warnings } = parseOpenOcppTrace(input); + expect(events).toHaveLength(1); + expect(warnings.length).toBeGreaterThanOrEqual(1); + }); + + it('throws when there are no usable records', () => { + const input = JSON.stringify({ not: 'a record' }); + expect(() => parseOpenOcppTrace(input)).toThrow(ParseError); + }); + + it('enforces the input size limit', () => { + const oversized = 'x'.repeat(MAX_INPUT_SIZE_BYTES + 1); + expect(() => parseOpenOcppTrace(oversized)).toThrow(ParseError); + }); +}); diff --git a/packages/toolkit/src/core/openOcppTrace.ts b/packages/toolkit/src/core/openOcppTrace.ts new file mode 100644 index 0000000..6ac6fee --- /dev/null +++ b/packages/toolkit/src/core/openOcppTrace.ts @@ -0,0 +1,522 @@ +/** + * Open OCPP Trace format adapter. + * + * Reads the vendor-neutral Open OCPP Trace interchange format + * (https://github.com/open-ocpp-trace/specification) into DebugKit's internal + * event model, so traces produced by other tools can be inspected, analyzed, + * diffed, and replayed here. + * + * The format is a stream of records (JSONL, or a JSON array of records), each + * describing one OCPP-J frame. This module maps a record onto the internal + * `RawOcppMessage` frame and reuses the normal `normalizeEvents` pipeline, so + * downstream analysis is identical to any other input format. + * + * Two behaviors are specific to the format: + * - `raw`, when present, is the authoritative frame. Any disagreement between + * `raw` and the decomposed fields is surfaced as a warning (raw still wins), + * consistent with the skip-and-flag policy for untrusted input (ADR-0007). + * - Responses (CALLRESULT/CALLERROR) may omit `action`; the effective action + * is derived by messageId correlation. `deriveOpenOcppTraceView` exposes that + * correlation as the format's consumer view, the same view the specification's + * conformance fixtures pin down. + * + * @see ADR-0007 (malformed trace handling) + */ + +import { z } from 'zod'; +import { rawOcppMessageSchema } from './schemas.js'; +import { normalizeEvents } from './normalizer.js'; +import { ParseError, validateInputSize, validateEventCount } from './parseLimits.js'; +import type { + Direction, + Event, + ParseResult, + ParseWarning, + RawOcppMessage, + TraceEventInput, +} from './types.js'; + +/** The Open OCPP Trace schema version this adapter targets. */ +export const OPEN_OCPP_TRACE_SCHEMA_VERSION = '1.1'; + +// --------------------------------------------------------------------------- +// Record type + schema +// --------------------------------------------------------------------------- + +/** Wire direction as expressed by the Open OCPP Trace format. */ +export type OpenOcppTraceDirection = 'cp-to-csms' | 'csms-to-cp'; + +/** OCPP-J frame kind as expressed by the Open OCPP Trace format. */ +export type OpenOcppTraceMessageType = 'CALL' | 'CALLRESULT' | 'CALLERROR'; + +/** + * A single Open OCPP Trace record (format major version 1). + * Unknown fields are ignored on input for forward compatibility. + */ +export interface OpenOcppTraceRecord { + schemaVersion: string; + timestamp: string; + ocppVersion?: string; + transport: 'json' | 'soap'; + chargePointId?: string; + connectorId?: number; + direction: OpenOcppTraceDirection; + messageType: OpenOcppTraceMessageType; + messageId?: string; + action?: string; + payload?: unknown; + raw?: string; + error?: { code?: string; description?: string; details?: unknown }; + meta?: Record; +} + +// Object schemas strip unknown keys by default, which is exactly the +// forward-compatibility rule: unknown fields are ignored, not rejected. +const openOcppTraceRecordSchema = z.object({ + schemaVersion: z.string(), + timestamp: z.string(), + ocppVersion: z.string().optional(), + transport: z.enum(['json', 'soap']), + chargePointId: z.string().optional(), + connectorId: z.number().int().nonnegative().optional(), + direction: z.enum(['cp-to-csms', 'csms-to-cp']), + messageType: z.enum(['CALL', 'CALLRESULT', 'CALLERROR']), + messageId: z.string().optional(), + action: z.string().optional(), + payload: z.unknown().optional(), + raw: z.string().optional(), + error: z + .object({ + code: z.string().optional(), + description: z.string().optional(), + details: z.unknown().optional(), + }) + .optional(), + meta: z.record(z.string(), z.unknown()).optional(), +}); + +// --------------------------------------------------------------------------- +// Detection +// --------------------------------------------------------------------------- + +function isRecordShape(value: unknown): boolean { + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + return false; + } + const record = value as Record; + const messageType = record.messageType; + const direction = record.direction; + return ( + (messageType === 'CALL' || messageType === 'CALLRESULT' || messageType === 'CALLERROR') && + (direction === 'cp-to-csms' || direction === 'csms-to-cp') + ); +} + +/** + * Cheap, allocation-light check for whether an input is the Open OCPP Trace + * format. Examines only the first record (first array element, or first + * non-empty line for JSONL). Never throws; returns false on anything it can't + * confidently identify, so `parseTrace` can fall through to its own detection. + */ +export function looksLikeOpenOcppTrace(input: string): boolean { + const trimmed = input.trim(); + if (trimmed === '') return false; + + try { + if (trimmed.startsWith('[')) { + const parsed: unknown = JSON.parse(trimmed); + if (!Array.isArray(parsed) || parsed.length === 0) return false; + return isRecordShape(parsed[0]); + } + const firstLine = trimmed + .split('\n') + .map((line) => line.trim()) + .find((line) => line !== ''); + if (firstLine === undefined) return false; + return isRecordShape(JSON.parse(firstLine)); + } catch { + return false; + } +} + +// --------------------------------------------------------------------------- +// Record extraction + validation +// --------------------------------------------------------------------------- + +function errorMessage(e: unknown): string { + return e instanceof Error ? e.message : 'unknown error'; +} + +/** Split input into per-record JSON values (JSON array or JSONL). */ +function extractRawRecords(input: string): { values: unknown[]; warnings: ParseWarning[] } { + const warnings: ParseWarning[] = []; + + if (input.startsWith('[')) { + let parsed: unknown; + try { + parsed = JSON.parse(input); + } catch (e) { + throw new ParseError(`Invalid JSON array - ${errorMessage(e)}`); + } + if (!Array.isArray(parsed)) { + throw new ParseError('Expected a JSON array of Open OCPP Trace records.'); + } + return { values: parsed, warnings }; + } + + const values: unknown[] = []; + const lines = input.split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]?.trim() ?? ''; + if (line === '') continue; + try { + values.push(JSON.parse(line)); + } catch (e) { + warnings.push({ + index: i, + message: `Line ${i + 1}: invalid JSON - ${errorMessage(e)}`, + rawInput: line.slice(0, 200), + }); + } + } + return { values, warnings }; +} + +/** Validate extracted values into records, flagging malformed ones. */ +function parseRecords(input: string): { records: OpenOcppTraceRecord[]; warnings: ParseWarning[] } { + const { values, warnings } = extractRawRecords(input); + const records: OpenOcppTraceRecord[] = []; + + values.forEach((value, i) => { + const result = openOcppTraceRecordSchema.safeParse(value); + if (result.success) { + records.push(result.data as OpenOcppTraceRecord); + } else { + warnings.push({ + index: i, + message: `Record ${i + 1}: invalid Open OCPP Trace record - ${ + result.error.issues[0]?.message ?? 'unknown error' + }`, + }); + } + }); + + return { records, warnings }; +} + +// --------------------------------------------------------------------------- +// Record -> internal frame mapping +// --------------------------------------------------------------------------- + +function mapDirection(direction: OpenOcppTraceDirection): Direction { + return direction === 'cp-to-csms' ? 'CS_TO_CSMS' : 'CSMS_TO_CS'; +} + +function deepEqual(a: unknown, b: unknown): boolean { + if (Object.is(a, b)) return true; + if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false; + if (Array.isArray(a) !== Array.isArray(b)) return false; + const keysA = Object.keys(a as object); + const keysB = Object.keys(b as object); + if (keysA.length !== keysB.length) return false; + return keysA.every( + (key) => + Object.hasOwn(b as object, key) && + deepEqual((a as Record)[key], (b as Record)[key]), + ); +} + +/** Reconstruct the OCPP-J frame from the record's decomposed fields. */ +function frameFromFields( + rec: OpenOcppTraceRecord, + messageId: string, + index: number, + warnings: ParseWarning[], +): RawOcppMessage | undefined { + switch (rec.messageType) { + case 'CALL': + if (rec.action === undefined) { + warnings.push({ index, message: `Record ${index + 1}: CALL record has no action.` }); + return undefined; + } + return [2, messageId, rec.action, rec.payload ?? {}]; + case 'CALLRESULT': + return [3, messageId, rec.payload ?? {}]; + case 'CALLERROR': { + const code = rec.error?.code; + if (code === undefined) { + warnings.push({ + index, + message: `Record ${index + 1}: CALLERROR record has no error.code.`, + }); + return undefined; + } + return [4, messageId, code, rec.error?.description ?? '', rec.error?.details ?? {}]; + } + } +} + +/** Flag any disagreement between `raw` and the decomposed fields (raw wins). */ +function checkRawAgreement( + frame: RawOcppMessage, + rec: OpenOcppTraceRecord, + index: number, + warnings: ParseWarning[], +): void { + const typeId = frame[0]; + const rawType: OpenOcppTraceMessageType = + typeId === 2 ? 'CALL' : typeId === 3 ? 'CALLRESULT' : 'CALLERROR'; + + const disagree = (field: string): void => { + warnings.push({ + index, + message: `Record ${index + 1}: raw frame disagrees with the record's ${field}; using raw.`, + }); + }; + + if (rawType !== rec.messageType) disagree('messageType'); + if (rec.messageId !== undefined && frame[1] !== rec.messageId) disagree('messageId'); + if (rawType === 'CALL' && rec.action !== undefined && frame[2] !== rec.action) disagree('action'); + if (rawType === 'CALLERROR' && rec.error?.code !== undefined && frame[2] !== rec.error.code) { + disagree('error.code'); + } + + const rawPayload = + rawType === 'CALL' ? frame[3] : rawType === 'CALLRESULT' ? frame[2] : undefined; + if ( + rec.payload !== undefined && + rawPayload !== undefined && + !deepEqual(rawPayload, rec.payload) + ) { + disagree('payload'); + } +} + +/** Parse `raw` into a frame, or return undefined (with a warning) to fall back. */ +function frameFromRaw( + rec: OpenOcppTraceRecord, + index: number, + warnings: ParseWarning[], +): RawOcppMessage | undefined { + let parsed: unknown; + try { + parsed = JSON.parse(rec.raw as string); + } catch { + warnings.push({ + index, + message: `Record ${index + 1}: raw is not valid JSON; using decomposed fields.`, + }); + return undefined; + } + const result = rawOcppMessageSchema.safeParse(parsed); + if (!result.success) { + warnings.push({ + index, + message: `Record ${index + 1}: raw is not a valid OCPP-J frame; using decomposed fields.`, + }); + return undefined; + } + const frame = result.data as RawOcppMessage; + checkRawAgreement(frame, rec, index, warnings); + return frame; +} + +function recordToEventInput( + rec: OpenOcppTraceRecord, + index: number, +): { input?: TraceEventInput; warnings: ParseWarning[] } { + const warnings: ParseWarning[] = []; + + if (rec.messageId === undefined) { + warnings.push({ + index, + message: `Record ${index + 1}: has no messageId and cannot be mapped to an event.`, + }); + return { warnings }; + } + + // `raw` is the authoritative frame when present and usable; otherwise + // reconstruct it from the decomposed fields. (Record-level metadata beyond + // the frame, such as ocppVersion / transport / connectorId / meta, has no + // slot in the internal event model and is not surfaced yet.) + let frame: RawOcppMessage | undefined; + if (rec.raw !== undefined) { + frame = frameFromRaw(rec, index, warnings); + } + if (frame === undefined) { + frame = frameFromFields(rec, rec.messageId, index, warnings); + } + if (frame === undefined) return { warnings }; + + const validated = rawOcppMessageSchema.safeParse(frame); + if (!validated.success) { + warnings.push({ + index, + message: `Record ${index + 1}: does not form a valid OCPP-J frame - ${ + validated.error.issues[0]?.message ?? 'unknown error' + }`, + }); + return { warnings }; + } + + return { + input: { + timestamp: rec.timestamp, + direction: mapDirection(rec.direction), + message: validated.data as RawOcppMessage, + }, + warnings, + }; +} + +// --------------------------------------------------------------------------- +// parseOpenOcppTrace() +// --------------------------------------------------------------------------- + +/** + * Parse an Open OCPP Trace document into normalized DebugKit events. + * + * Accepts JSONL (one record per line) or a JSON array of records. Applies the + * same untrusted-input limits as {@link parseTrace}. Malformed records are + * skipped with a warning rather than aborting the parse. + * + * @throws {ParseError} for structural errors (invalid JSON array, size/count + * limits, or a document with no usable records). + */ +export function parseOpenOcppTrace(input: string): ParseResult { + validateInputSize(input); + + const trimmed = input.trim(); + if (trimmed === '') { + throw new ParseError('Input is empty.'); + } + + const { records, warnings } = parseRecords(trimmed); + + const eventInputs: TraceEventInput[] = []; + records.forEach((rec, i) => { + const mapped = recordToEventInput(rec, i); + warnings.push(...mapped.warnings); + if (mapped.input !== undefined) eventInputs.push(mapped.input); + }); + + validateEventCount(eventInputs.length); + if (eventInputs.length === 0) { + throw new ParseError('Trace contains no valid Open OCPP Trace records.'); + } + + const events: Event[] = normalizeEvents(eventInputs); + return { events, warnings }; +} + +// --------------------------------------------------------------------------- +// Consumer view (conformance) +// --------------------------------------------------------------------------- + +/** One record's entry in the consumer view. */ +export interface OpenOcppTraceViewRecord { + index: number; + messageType: OpenOcppTraceMessageType; + messageId?: string; + /** Effective action: explicit for a CALL, derived by correlation for a response. */ + action?: string; + /** Index of the correlated CALL, for a correlated response. */ + correlatesWith?: number; +} + +/** + * The consumer view of an Open OCPP Trace document: the correlation structure a + * conformant consumer derives from it. This is the shape the specification's + * conformance fixtures (`expected.json`) pin down. + */ +export interface OpenOcppTraceView { + schemaVersion: string; + counts: { records: number; calls: number; callResults: number; callErrors: number }; + records: OpenOcppTraceViewRecord[]; + unansweredCalls: number[]; + orphanResponses: number[]; +} + +/** + * Find the CALL a response correlates with: the most recent preceding CALL with + * the same messageId, the opposite direction, that is not already answered. + */ +function findCall( + records: OpenOcppTraceRecord[], + responseIndex: number, + answered: Set, +): number { + const response = records[responseIndex]; + if (response?.messageId === undefined) return -1; + for (let j = responseIndex - 1; j >= 0; j--) { + const candidate = records[j]; + if ( + candidate !== undefined && + candidate.messageType === 'CALL' && + candidate.messageId === response.messageId && + candidate.direction !== response.direction && + !answered.has(j) + ) { + return j; + } + } + return -1; +} + +function buildView(records: OpenOcppTraceRecord[]): OpenOcppTraceView { + const view: OpenOcppTraceViewRecord[] = records.map((rec, index) => { + const entry: OpenOcppTraceViewRecord = { index, messageType: rec.messageType }; + if (rec.messageId !== undefined) entry.messageId = rec.messageId; + if (rec.messageType === 'CALL' && rec.action !== undefined) entry.action = rec.action; + return entry; + }); + + const answered = new Set(); + const orphanResponses: number[] = []; + + for (let i = 0; i < records.length; i++) { + if (records[i]?.messageType === 'CALL') continue; + const match = findCall(records, i, answered); + if (match === -1) { + orphanResponses.push(i); + } else { + answered.add(match); + const entry = view[i]; + const callAction = records[match]?.action; + if (entry !== undefined) { + if (callAction !== undefined) entry.action = callAction; + entry.correlatesWith = match; + } + } + } + + const unansweredCalls: number[] = []; + records.forEach((rec, i) => { + if (rec.messageType === 'CALL' && !answered.has(i)) unansweredCalls.push(i); + }); + + return { + schemaVersion: records[0]?.schemaVersion ?? OPEN_OCPP_TRACE_SCHEMA_VERSION, + counts: { + records: records.length, + calls: records.filter((r) => r.messageType === 'CALL').length, + callResults: records.filter((r) => r.messageType === 'CALLRESULT').length, + callErrors: records.filter((r) => r.messageType === 'CALLERROR').length, + }, + records: view, + unansweredCalls, + orphanResponses, + }; +} + +/** + * Derive the {@link OpenOcppTraceView} for an Open OCPP Trace document. This is + * DebugKit acting as a reference consumer of the format: given a trace, it + * reports the correlation structure (pairs, effective actions, unanswered + * calls, orphan responses) that any conformant consumer must produce. + */ +export function deriveOpenOcppTraceView(input: string): OpenOcppTraceView { + validateInputSize(input); + const { records } = parseRecords(input.trim()); + return buildView(records); +} diff --git a/packages/toolkit/src/core/parseLimits.ts b/packages/toolkit/src/core/parseLimits.ts new file mode 100644 index 0000000..9a881ec --- /dev/null +++ b/packages/toolkit/src/core/parseLimits.ts @@ -0,0 +1,50 @@ +/** + * Shared untrusted-input limits and the parse error type. + * + * Every entry point that ingests untrusted trace input (the internal formats + * in `parser.ts`, the Open OCPP Trace format in `openOcppTrace.ts`) applies + * these same limits, so no input path can bypass them. + * + * @see ADR-0007 (malformed trace handling) + * @see docs/trace-format-spec.md + */ + +/** Maximum input size in bytes (10 MB). */ +export const MAX_INPUT_SIZE_BYTES = 10 * 1024 * 1024; + +/** Maximum number of events after parsing. */ +export const MAX_EVENT_COUNT = 10_000; + +/** Error thrown when input exceeds size limits or has structural problems. */ +export class ParseError extends Error { + constructor(message: string) { + super(message); + this.name = 'ParseError'; + } +} + +/** + * Validate the input size before any parsing. + * @throws {ParseError} if the input exceeds the maximum size. + */ +export function validateInputSize(input: string): void { + const byteLength = Buffer.byteLength(input, 'utf8'); + if (byteLength > MAX_INPUT_SIZE_BYTES) { + throw new ParseError( + `Input size (${byteLength} bytes) exceeds the maximum allowed size ` + + `(${MAX_INPUT_SIZE_BYTES} bytes).`, + ); + } +} + +/** + * Validate the event count after parsing. + * @throws {ParseError} if the event count exceeds the maximum. + */ +export function validateEventCount(count: number): void { + if (count > MAX_EVENT_COUNT) { + throw new ParseError( + `Event count (${count}) exceeds the maximum allowed count ` + `(${MAX_EVENT_COUNT}).`, + ); + } +} diff --git a/packages/toolkit/src/core/parser.ts b/packages/toolkit/src/core/parser.ts index c1d1b53..36c0ab8 100644 --- a/packages/toolkit/src/core/parser.ts +++ b/packages/toolkit/src/core/parser.ts @@ -23,59 +23,14 @@ import { traceSchema, } from './schemas.js'; import { normalizeEvents } from './normalizer.js'; +import { ParseError, validateInputSize, validateEventCount } from './parseLimits.js'; +import { looksLikeOpenOcppTrace, parseOpenOcppTrace } from './openOcppTrace.js'; import type { Event, ParseResult, ParseWarning, TraceEventInput } from './types.js'; -// --------------------------------------------------------------------------- -// Limits (ADR-0007, docs/trace-format-spec.md) -// --------------------------------------------------------------------------- - -/** Maximum input size in bytes (10 MB). */ -export const MAX_INPUT_SIZE_BYTES = 10 * 1024 * 1024; - -/** Maximum number of events after parsing. */ -export const MAX_EVENT_COUNT = 10_000; - -// --------------------------------------------------------------------------- -// Errors -// --------------------------------------------------------------------------- - -/** Error thrown when input exceeds size limits or has structural problems. */ -export class ParseError extends Error { - constructor(message: string) { - super(message); - this.name = 'ParseError'; - } -} - -// --------------------------------------------------------------------------- -// Input size validation -// --------------------------------------------------------------------------- - -/** - * Validate the input size before any parsing. - * @throws {ParseError} if the input exceeds the maximum size. - */ -function validateInputSize(input: string): void { - const byteLength = Buffer.byteLength(input, 'utf8'); - if (byteLength > MAX_INPUT_SIZE_BYTES) { - throw new ParseError( - `Input size (${byteLength} bytes) exceeds the maximum allowed size ` + - `(${MAX_INPUT_SIZE_BYTES} bytes).`, - ); - } -} - -/** - * Validate the event count after parsing. - * @throws {ParseError} if the event count exceeds the maximum. - */ -function validateEventCount(events: TraceEventInput[]): void { - if (events.length > MAX_EVENT_COUNT) { - throw new ParseError( - `Event count (${events.length}) exceeds the maximum allowed count ` + `(${MAX_EVENT_COUNT}).`, - ); - } -} +// The untrusted-input limits and the parse error type live in `parseLimits.ts` +// so every input path shares them. Re-exported here to keep their public +// import path (`@ocpp-debugkit/toolkit/core`) unchanged. +export { ParseError, MAX_INPUT_SIZE_BYTES, MAX_EVENT_COUNT } from './parseLimits.js'; // --------------------------------------------------------------------------- // Format detection @@ -291,6 +246,13 @@ export function parseTrace(input: string): ParseResult { throw new ParseError('Input is empty.'); } + // The Open OCPP Trace interchange format (records with `messageType` + + // `direction`) has its own parser; detect and delegate before the internal + // format detection, which does not recognize it. + if (looksLikeOpenOcppTrace(trimmed)) { + return parseOpenOcppTrace(trimmed); + } + // Detect format and parse accordingly const format = detectFormat(trimmed); @@ -329,7 +291,7 @@ export function parseTrace(input: string): ParseResult { } // Validate event count - validateEventCount(events); + validateEventCount(events.length); if (events.length === 0) { throw new ParseError('Trace contains no valid events.'); diff --git a/packages/toolkit/src/index.ts b/packages/toolkit/src/index.ts index 41788fb..1ec2be6 100644 --- a/packages/toolkit/src/index.ts +++ b/packages/toolkit/src/index.ts @@ -22,6 +22,15 @@ export { type TraceFormat, } from './core/index.js'; +// Open OCPP Trace interop +export { + parseOpenOcppTrace, + deriveOpenOcppTraceView, + OPEN_OCPP_TRACE_SCHEMA_VERSION, + type OpenOcppTraceRecord, + type OpenOcppTraceView, +} from './core/index.js'; + export { normalizeEvents, normalizeTimestamp, diff --git a/tests/external-fixture/test.mjs b/tests/external-fixture/test.mjs index efeec34..fb9ea8a 100644 --- a/tests/external-fixture/test.mjs +++ b/tests/external-fixture/test.mjs @@ -102,6 +102,44 @@ const sessions = core.buildSessionTimeline(parseResult.events); const failures = core.detectFailures(parseResult.events, sessions); assert(Array.isArray(failures), 'detectFailures returns an array'); +// Open OCPP Trace interop +assert(typeof core.parseOpenOcppTrace === 'function', 'parseOpenOcppTrace is a function'); +assert(typeof core.deriveOpenOcppTraceView === 'function', 'deriveOpenOcppTraceView is a function'); +const openTrace = [ + JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2026-01-01T00:00:00Z', + transport: 'json', + direction: 'cp-to-csms', + messageType: 'CALL', + messageId: 'm1', + action: 'BootNotification', + payload: {}, + raw: '[2,"m1","BootNotification",{}]', + }), + JSON.stringify({ + schemaVersion: '1.1', + timestamp: '2026-01-01T00:00:01Z', + transport: 'json', + direction: 'csms-to-cp', + messageType: 'CALLRESULT', + messageId: 'm1', + payload: { status: 'Accepted' }, + raw: '[3,"m1",{"status":"Accepted"}]', + }), +].join('\n'); +const openResult = core.parseOpenOcppTrace(openTrace); +assert( + openResult.events.length === 2, + `parseOpenOcppTrace produces 2 events (got ${openResult.events.length})`, +); +assert(core.parseTrace(openTrace).events.length === 2, 'parseTrace auto-detects Open OCPP Trace'); +const openView = core.deriveOpenOcppTraceView(openTrace); +assert( + openView.records[1].action === 'BootNotification', + 'response action derived by messageId correlation', +); + console.log(' /core tests passed\n'); // --- /scenarios --- @@ -216,6 +254,7 @@ assert( 'root buildSessionTimeline is a function', ); assert(typeof toolkit.fixtures === 'object', 'root fixtures is an object'); +assert(typeof toolkit.parseOpenOcppTrace === 'function', 'root parseOpenOcppTrace is a function'); console.log(' root barrel tests passed\n'); From c917b20abd0c57cd9811e047e8b2a36aee04118e Mon Sep 17 00:00:00 2001 From: sepehr-safari Date: Fri, 17 Jul 2026 13:39:37 +0300 Subject: [PATCH 2/2] docs: record the Open OCPP Trace input adapter in CURRENT_STATE --- CURRENT_STATE.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/CURRENT_STATE.md b/CURRENT_STATE.md index 0c0a21e..e76a871 100644 --- a/CURRENT_STATE.md +++ b/CURRENT_STATE.md @@ -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 @@ -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