pub struct Client<E: ProtocolError> { /* private fields */ }Expand description
The OCPP client engine, generic over one version’s protocol error type. OCPP1_6Client
and OCPP2_0_1Client are just Client<OCPP1_6Error> / Client<OCPP2_0_1Error> - the
dispatch/timeout/error machinery below is written once and shared by every version.
Implementations§
Source§impl<E: ProtocolError> Client<E>
impl<E: ProtocolError> Client<E>
Sourcepub fn from_transport(
sink: Box<dyn TransportSink>,
stream: Box<dyn TransportStream>,
timeout: Duration,
executor: Box<dyn Executor>,
timer: Box<dyn Timer>,
) -> Self
pub fn from_transport( sink: Box<dyn TransportSink>, stream: Box<dyn TransportStream>, timeout: Duration, executor: Box<dyn Executor>, timer: Box<dyn Timer>, ) -> Self
Build a client over any transport - the WebSocket adapter used by connect_1_6 is
just one implementation of TransportSink/TransportStream; tests and non-WebSocket
transports (an embedded framed link, an in-memory fake for unit tests) construct a
client the same way. executor/timer are likewise pluggable: the tokio-runtime
feature provides TokioExecutor/TokioTimer; embedded users supply their own (e.g.
backed by embassy-executor/embassy-time).
Sourcepub fn from_transport_with_reconnect(
sink: Box<dyn TransportSink>,
stream: Box<dyn TransportStream>,
timeout: Duration,
executor: Box<dyn Executor>,
timer: Box<dyn Timer>,
reconnector: Option<Box<dyn Reconnector>>,
reconnect_policy: ReconnectPolicy,
) -> Self
pub fn from_transport_with_reconnect( sink: Box<dyn TransportSink>, stream: Box<dyn TransportStream>, timeout: Duration, executor: Box<dyn Executor>, timer: Box<dyn Timer>, reconnector: Option<Box<dyn Reconnector>>, reconnect_policy: ReconnectPolicy, ) -> Self
Same as Client::from_transport, but with automatic reconnect: when the transport
closes (TransportStream::recv returns Ok(None)/Err(_)), the background read loop
calls reconnector.connect() (backing off per reconnect_policy between failed
attempts) instead of exiting, and swaps in the new transport once one succeeds.
reconnector: None reproduces from_transport’s behavior - the read loop exits on
disconnect and the client goes quiet. connect_1_6/connect_2_0_1/connect_2_1 use
this constructor with a WebSocket-backed Reconnector.
Sourcepub fn from_transport_with_config(
sink: Box<dyn TransportSink>,
stream: Box<dyn TransportStream>,
executor: Box<dyn Executor>,
timer: Box<dyn Timer>,
config: ClientConfig,
) -> Self
pub fn from_transport_with_config( sink: Box<dyn TransportSink>, stream: Box<dyn TransportStream>, executor: Box<dyn Executor>, timer: Box<dyn Timer>, config: ClientConfig, ) -> Self
The constructor the other two delegate to: everything optional lives in ClientConfig
instead of a growing positional parameter list.
Spawns two background tasks on executor: the read loop, and a keepalive task. The
keepalive task is spawned even when config.keepalive is Disabled, where it simply
parks until someone calls Client::set_ping_interval - otherwise a client built with
keepalive off could never have it turned on later, which is exactly what a CSMS writing
WebSocketPingInterval needs to do.
Sourcepub async fn call<A: Action>(
&self,
request: A::Request,
) -> Result<A::Response, ClientError<E>>
pub async fn call<A: Action>( &self, request: A::Request, ) -> Result<A::Response, ClientError<E>>
Send a CALL for A and wait for the matching CALLRESULT/CALLERROR.
Sourcepub async fn on<A, F, FF>(&self, callback: F)
pub async fn on<A, F, FF>(&self, callback: F)
Register a handler for CALLs the other side sends for action A. Replaces any
previously registered handler for the same action.
Sourcepub async fn wait_for<A, F, FF>(
&self,
callback: F,
) -> Result<A::Request, ClientError<E>>
pub async fn wait_for<A, F, FF>( &self, callback: F, ) -> Result<A::Request, ClientError<E>>
Wait for exactly one CALL for action A (bounded by the client’s timeout), answer
it with callback, and return the parsed request. Only useful in tests.
The registration is removed again on the way out, whichever way that is. Leaving it in place left the action bound to a channel with no reader, so any later CALL for it was queued and silently forgotten - the peer got no CALLRESULT and no CALLERROR either, which looks exactly like the client having hung.
Note this does not restore a handler that Client::on had registered for the same
action beforehand; registering replaces, as on’s own docs say.
Sourcepub async fn send_notification<A: SendAction>(
&self,
payload: A::Payload,
) -> Result<(), ClientError<E>>
pub async fn send_notification<A: SendAction>( &self, payload: A::Payload, ) -> Result<(), ClientError<E>>
Send a SEND (OCPP-J 2.1 only) fire-and-forget message: writes the frame and returns as
soon as the transport accepts it - no waiter, no timeout, since the spec forbids the
receiver from ever replying to a SEND.
Sourcepub async fn on_notification<A, F, FF>(&self, callback: F)
pub async fn on_notification<A, F, FF>(&self, callback: F)
Register a handler for SEND (OCPP-J 2.1 only) messages of action A. Unlike
Client::on, callback returns nothing - the spec forbids replying to a SEND, so
there’s no response to send back. Replaces any previously registered handler for the
same action.
Sourcepub async fn send_ping(&self) -> Result<(), ClientError<E>>
pub async fn send_ping(&self) -> Result<(), ClientError<E>>
Send one ping and wait for the matching pong, bounded by the client’s timeout.
The pong is matched by correlation token, not arrival order: the ping carries an 8-byte token as its payload and only a pong echoing that exact payload resolves this call. RFC 6455 requires peers to echo ping payloads, so this is exact against any compliant server; a pong that echoes something else is ignored, and this call times out.
This is the manual, one-shot ping. For scheduled keepalive - including detecting a peer
that has stopped answering and forcing a redial - see Client::set_ping_interval and
KeepaliveBehavior.
Sourcepub async fn pending_request_count(&self) -> usize
pub async fn pending_request_count(&self) -> usize
How many requests are still waiting for a CALLRESULT/CALLERROR.
Test-only instrumentation: this table is bookkeeping that should return to zero once every
request has either been answered or given up, and a leak in it is otherwise invisible from
outside - it shows up only as memory growth on a charge point that has been running for
weeks. tests/ocpp_1_6_bookkeeping.rs asserts on it.
Sourcepub async fn pending_ping_count(&self) -> usize
pub async fn pending_ping_count(&self) -> usize
How many pings are still waiting for a pong. Test-only, same rationale as
Client::pending_request_count.
Sourcepub fn ping_interval(&self) -> Option<Duration>
pub fn ping_interval(&self) -> Option<Duration>
The keepalive ping interval currently in force, or None when keepalive is off.
This is the value to report for OCPPCommCtrlr.WebSocketPingInterval (2.0.1/2.1) or the
WebSocketPingInterval configuration key (1.6) - None maps to the spec’s 0. Cheap
and non-blocking, so a GetVariables/GetConfiguration handler can call it directly.
Sourcepub fn set_ping_interval(&self, interval: Option<Duration>)
pub fn set_ping_interval(&self, interval: Option<Duration>)
Change the keepalive ping interval on a live connection, for a CSMS writing
WebSocketPingInterval via SetVariables/ChangeConfiguration.
None - or Some(Duration::ZERO), matching the spec’s 0 - disables pinging. Takes
effect immediately: the keepalive task is woken rather than finishing the interval it was
already waiting out, so shortening a 1-hour interval doesn’t take up to an hour to apply.
Enabling works even on a client built with KeepaliveBehavior::Disabled.
Sourcepub fn force_reconnect(&self)
pub fn force_reconnect(&self)
Abandon the current transport and redial, without waiting for it to notice it is dead.
This is what keepalive escalates to after KeepalivePolicy::max_missed unanswered pings,
exposed because a caller with its own liveness signal (an application-level heartbeat
going unanswered, say) has the same problem. A half-open TCP connection can otherwise
keep the read loop parked until the OS timeout, which no amount of protocol-level
bookkeeping can shorten.
No-op when the client was built without a reconnector: there would be nothing to redial with, and dropping the current connection anyway would just make the client deaf.
pub async fn on_ping<F: FnMut(Self) -> FF + Send + Sync + 'static, FF: Future<Output = ()> + Send>( &self, callback: F, )
Sourcepub async fn on_reconnect<F: FnMut(Self) -> FF + Send + Sync + 'static, FF: Future<Output = ()> + Send>(
&self,
callback: F,
)
pub async fn on_reconnect<F: FnMut(Self) -> FF + Send + Sync + 'static, FF: Future<Output = ()> + Send>( &self, callback: F, )
Register a callback that fires every time the background read loop redials
successfully after a disconnect (see Client::from_transport_with_reconnect). Never
fires for the initial connection, only for later reconnects - the initial Client is
already handed back post-connect, so callers run their own post-connect setup (e.g.
BootNotification) right after connect_1_6/from_transport_with_reconnect returns.
This is the hook for redoing that setup (or resyncing any other session state) after a
dropped-and-restored connection; this crate does not re-run BootNotification or replay
any state on its own.
Sourcepub async fn disconnect(&self) -> Result<(), ClientError<E>>
pub async fn disconnect(&self) -> Result<(), ClientError<E>>
Shut this client down for good: close the transport, stop the read loop, stop keepalive, and do not redial.
The shutdown is sticky and takes precedence over every automatic recovery path. That
matters because closing the transport looks exactly like a dropped connection from the read
loop’s side - it previously produced an EOF the reconnector dutifully redialled, so on the
default crate::ConnectOptions (reconnect enabled) there was no way to stop a client at
all. After this returns:
- the read loop has been told to exit rather than redial, whether it was parked in
recvor sees the EOF from the close; - the keepalive task stops pinging, and
Client::set_ping_intervalcannot restart it; Client::force_reconnectis a no-op;- further
call/send_*/send_pingreturnClientError::Closedinstead of writing to a dead transport and waiting out the timeout.
Idempotent: calling it again is a no-op returning Ok(()). Reconnecting afterwards means
building a new Client.
This only covers deliberate shutdown. An unrequested drop is still redialled as before.
Sourcepub fn is_closed(&self) -> bool
pub fn is_closed(&self) -> bool
Whether Client::disconnect has been called.
This reflects deliberate shutdown only - it stays false while a connection is dropped and
being redialled, because such a client is still live and will resume on its own. There is
deliberately no “is the socket up right now” accessor: it would be stale the moment it
returned, and Client::on_reconnect is the reliable way to observe reconnection.