ocpp/protocol
OCPP protocol state machines — a pure, sans-IO layer above the message
models in ocpp.
The ocpp/* modules answer “what do OCPP payloads look like?”; this
layer answers “what happens next on a connection?”. It implements the
OCPP-J RPC rules from part 4 of the spec (one CALL in flight per
direction, FIFO queueing, correlation of anonymous CALLRESULTs, call
timeouts, SEND exemption, CALLRESULTERROR matching) as plain functions
over plain data, with no processes, sockets, or clocks. One generic
machine serves OCPP 1.6, 2.0.1 and 2.1 alike: version specifics enter
only as injected codec functions taken from the per-version dispatch
modules.
Shape of a machine
Every machine follows the same discipline:
init(config, now) -> #(model, Effect(output))starts it. Every machine takes the clock at init — the station needs it (its first transition emits a deadline-bearing boot CALL), and the endpoint and CSMS accept it unused so adapters can treat all machines uniformly.update(model, msg, now) -> #(model, Effect(output))advances it. The model is an opaque value;msgis an input from the socket, the application, or a timer;nowis an absolute millisecond timestamp from a monotonic clock — the only way time enters.Effect(output)(seeocpp/protocol/effect) is data, not closures: an ordered list of outputs — frames to write, responses to deliver, violations to report — that the embedding adapter interprets.next_deadline(model) -> Option(Int)reports the earliest instant the machine needs to be woken with aTick. The adapter keeps one timer aimed at it; deadlines live inside the state that owns them, so changing state cancels them structurally (no set/cancel-timer effects), and a staleTickis a harmless no-op.
Because everything is a value, transitions are trivially unit-testable and adapter-agnostic: the same machine can be embedded in an OTP actor, a JavaScript event loop, or a replay/simulation harness.
Modules
ocpp/protocol/effect— effects-as-data:Effect(out)withnone/emit/batch/map/to_list.ocpp/protocol/endpoint— the symmetric OCPP-J RPC endpoint machine, generic over a version’s request/response types via injected codecs.ocpp/protocol/station— the charging-station machine: wraps the endpoint and adds boot registration (with pending/rejected retries) and heartbeats, queueing application calls until the CSMS accepts the station.ocpp/protocol/csms— the per-connection CSMS machine: wraps the endpoint, tracks the peer station’s registration from the application’s boot replies, and (under itsRejectUnregisteredenforcement policy) answers non-boot CALLs from an unregistered station with a CALLERRORSecurityError.ocpp/protocol/v1_6— thin config constructors binding the generic machines to the OCPP 1.6 unions and codecs inocpp/v1_6/dispatch; the entry point for applications speaking 1.6.ocpp/protocol/v2_0_1— the same for OCPP 2.0.1, overocpp/v2_0_1/dispatch; the entry point for applications speaking 2.0.1.ocpp/protocol/v2_1— the same for OCPP 2.1, overocpp/v2_1/dispatch; the entry point for applications speaking 2.1, including its send-only (fire-and-forget) actions, which travel viaSendRequested/SendReceived.
Dependencies
The layer is deliberately small, but not stdlib-only: it depends on
gleam_stdlib, on gleam_json (frames are built and serialized as
gleam/json values, and the injected codecs traffic in Json, matching
the dispatch modules’ signatures), and on the ocpp package (ocpp/rpc,
ocpp/transport/json/frame, and the per-version dispatch codecs). If
this layer is extracted as its own package, gleam_json — already a
transitive dependency via ocpp — must be a direct dependency too.
Types
How a CSMS answered a station’s boot notification — the boot vocabulary
shared by the two role machines. The station machine reads it out of the
responses to its own boot CALLs (ocpp/protocol/station); the CSMS
machine reads it out of the application’s replies to inbound boot CALLs
(ocpp/protocol/csms). It lives here so neither role module has to
import the other.
pub type BootStatus {
BootAccepted
BootPending
BootRejected
}
Constructors
-
BootAcceptedThe station is registered and may operate normally.
-
BootPendingThe CSMS is not yet ready; the station boots again after the returned interval.
-
BootRejectedThe CSMS refuses registration; the station boots again after the returned interval.
Values
pub fn earliest(
deadlines: List(option.Option(Int)),
) -> option.Option(Int)
Merge the next_deadline values of stacked machines (or of a machine and
the application’s own deadlines) into the single earliest instant to aim
the one timer at. None entries mean “nothing to wake up for” and are
ignored; the result is None only when every entry is.