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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions CURRENT_STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

## Active milestone

**S1 — Engine core (0.1.0): in progress.** S0 — Foundation is complete (see
[ROADMAP.md](ROADMAP.md)).
**S1 — Engine core (0.1.0): complete.** Next up: **S2 — Detection + conformance**
(see [ROADMAP.md](ROADMAP.md)).

## What's done

Expand All @@ -32,24 +32,30 @@

**Exit criteria met:** CI green on macOS + Linux; `native doctor --strict` clean.

### S1 — Engine core ✅

The pure-Zig, headless OCPP engine under `src/ocpp/`, mirroring the toolkit's
conformance contract (behavior, not source) and tested via
`native test -Dplatform=null`:

- **Types & value boundary** (#11) — canonical `Event` / `Session` / trace /
parse-result types, the `Direction` / `MessageType` / `Status` enums, and the
`std.json.Value` payload boundary (ADR-0005).
- **Event normalizer** (#12) — message classification, ISO 8601 / epoch
timestamp normalization, and two-pass direction inference.
- **Trace parser** (#13) — JSON object / JSONL / bare-array formats, structural
validation, per-entry warnings, and untrusted-input limits (10 MB, 10 000
events), into a caller-owned arena.
- **Session timeline** (#14) — transactionId correlation, connector / time-based
distribution of un-keyed events, and session status.

**Exit criteria met:** the vendored `normal-session` fixture parses and
correlates end to end (one completed session, transactionId 100001); engine
tests green headlessly on macOS + Linux.

## What's in progress

**S1 — Engine core (0.1.0).** Building the pure-Zig engine module by module:

- **Engine types & value boundary** (#11) — the `src/ocpp/` foundation: canonical
`Event` / `Session` / trace / parse-result types, the `Direction` /
`MessageType` / `Status` enums, and the `std.json.Value` payload boundary
(ADR-0005). Headless, imports no UI/runtime modules, tested via
`native test -Dplatform=null`.
- **Event normalizer** (#12) — message classification, ISO 8601 / epoch-second /
epoch-millisecond timestamp normalization, and two-pass direction inference
(the CS↔CSMS action tables), producing canonical `Event`s. The toolkit's
normalizer test cases are ported.
- **Trace parser** (#13) — JSON object / JSONL / bare-array formats with format
detection, structural validation, per-entry warnings for malformed data, and
the untrusted-input limits (10 MB input, 10 000 events). Parses into a
caller-owned arena. The toolkit's parser test cases are ported.
- Next: session timeline (#14).
- Nothing in flight — S1 is closed; S2 is next.

## What's next

Expand All @@ -67,7 +73,7 @@ checks Studio's detected failures against locked goldens.
| --- | --- |
| `repo` (tooling, CI) | ✅ done for S0 |
| `docs` (docs, ADRs) | ✅ done for S0 |
| `ocpp` (engine) | 🚧 in progress (S1) |
| `ocpp` (engine) | ✅ done for S1 |
| `ui` (native views) | ⬜ placeholder (S3) |
| `capture` (live proxy) | ⬜ not started (S5) |
| `cli` (headless) | ⬜ not started (S4) |
Expand Down
15 changes: 9 additions & 6 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ the stable part.
| Milestone | Version | Theme | Status |
| --- | --- | --- | --- |
| **S0** | 0.0.x | Foundation | ✅ Complete |
| **S1** | 0.1.0 | Engine core | Next up |
| **S2** | 0.1.0 | Detection + conformance | Planned |
| **S1** | 0.1.0 | Engine core | ✅ Complete |
| **S2** | 0.1.0 | Detection + conformance | Next up |
| **S3** | 0.2.0 | Inspector UI | Planned |
| **S4** | 0.3.0 | Analysis parity+ | Planned |
| **S5** | 0.4.0 | Live capture ⭐ | Planned |
Expand All @@ -22,12 +22,15 @@ Repo, tooling, and CI. A native window that opens, an automation-driven smoke
test, founding docs, and architecture decision records. **Exit criteria met:**
CI green on macOS + Linux; `native doctor --strict` clean.

## S1 — Engine core (0.1.0)
## S1 — Engine core (0.1.0)

The pure-Zig OCPP engine, headless and testable: canonical `Event` / `Session`
/ `Failure` types, a parser for the three input formats (JSON object, JSONL,
bare array), the normalizer (message-direction inference and timestamp
normalization), and session correlation by transaction id.
types and the `std.json.Value` value boundary (ADR-0005), a parser for the three
input formats (JSON object, JSONL, bare array) with untrusted-input limits, the
normalizer (message classification, timestamp normalization, and direction
inference), and session correlation by transaction id. **Exit criteria met:** the
vendored `normal-session` fixture parses and correlates end to end; engine tests
run green headlessly (`native test -Dplatform=null`) on macOS + Linux.

## S2 — Detection + conformance (0.1.0)

Expand Down
2 changes: 2 additions & 0 deletions src/ocpp/ocpp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
pub const types = @import("types.zig");
pub const normalizer = @import("normalizer.zig");
pub const parser = @import("parser.zig");
pub const timeline = @import("timeline.zig");

test {
_ = types;
_ = normalizer;
_ = parser;
_ = timeline;
}
33 changes: 33 additions & 0 deletions src/ocpp/testdata/normal-session.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"traceId": "fixture-normal-session",
"metadata": {
"stationId": "CS-SYNTHETIC-001",
"ocppVersion": "1.6",
"source": "synthetic-fixture",
"description": "Normal charging session: boot -> authorize -> start transaction -> meter values -> stop transaction. No failures expected."
},
"events": [
{ "timestamp": "2024-01-15T10:00:00.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-001", "BootNotification", { "chargePointVendor": "SyntheticVendor", "chargePointModel": "SM-100", "chargePointSerialNumber": "CS-SYNTHETIC-001", "firmwareVersion": "1.0.0" }] },
{ "timestamp": "2024-01-15T10:00:00.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-001", { "currentTime": "2024-01-15T10:00:00.500Z", "interval": 300, "status": "Accepted" }] },
{ "timestamp": "2024-01-15T10:01:00.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-002", "StatusNotification", { "connectorId": 0, "status": "Available", "errorCode": "NoError" }] },
{ "timestamp": "2024-01-15T10:01:00.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-002", {}] },
{ "timestamp": "2024-01-15T10:02:00.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-003", "StatusNotification", { "connectorId": 1, "status": "Preparing", "errorCode": "NoError" }] },
{ "timestamp": "2024-01-15T10:02:00.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-003", {}] },
{ "timestamp": "2024-01-15T10:02:15.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-004", "Authorize", { "idTag": "SYNTHETIC-TAG-001" }] },
{ "timestamp": "2024-01-15T10:02:15.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-004", { "idTagInfo": { "status": "Accepted", "expiryDate": "2024-12-31T23:59:59.000Z", "parentIdTag": "SYNTHETIC-PARENT-001" } }] },
{ "timestamp": "2024-01-15T10:02:30.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-005", "StartTransaction", { "connectorId": 1, "idTag": "SYNTHETIC-TAG-001", "meterStart": 0, "timestamp": "2024-01-15T10:02:30.000Z" }] },
{ "timestamp": "2024-01-15T10:02:30.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-005", { "transactionId": 100001, "idTagInfo": { "status": "Accepted" } }] },
{ "timestamp": "2024-01-15T10:02:31.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-006", "StatusNotification", { "connectorId": 1, "status": "Charging", "errorCode": "NoError" }] },
{ "timestamp": "2024-01-15T10:02:31.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-006", {}] },
{ "timestamp": "2024-01-15T10:07:30.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-hb-1", "Heartbeat", {}] },
{ "timestamp": "2024-01-15T10:07:30.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-hb-1", { "currentTime": "2024-01-15T10:07:30.500Z" }] },
{ "timestamp": "2024-01-15T10:15:00.000Z", "direction": "CS_TO_CSMS", "message": [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" }] }] }] },
{ "timestamp": "2024-01-15T10:15:00.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-007", {}] },
{ "timestamp": "2024-01-15T10:30:00.000Z", "direction": "CS_TO_CSMS", "message": [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" }] }] }] },
{ "timestamp": "2024-01-15T10:30:00.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-008", {}] },
{ "timestamp": "2024-01-15T10:35:00.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-009", "StopTransaction", { "transactionId": 100001, "idTag": "SYNTHETIC-TAG-001", "meterStop": 10000, "timestamp": "2024-01-15T10:35:00.000Z", "reason": "EVDisconnected" }] },
{ "timestamp": "2024-01-15T10:35:00.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-009", { "idTagInfo": { "status": "Accepted" } }] },
{ "timestamp": "2024-01-15T10:35:01.000Z", "direction": "CS_TO_CSMS", "message": [2, "msg-010", "StatusNotification", { "connectorId": 1, "status": "Available", "errorCode": "NoError" }] },
{ "timestamp": "2024-01-15T10:35:01.500Z", "direction": "CSMS_TO_CS", "message": [3, "msg-010", {}] }
]
}
Loading
Loading