Skip to content

BusStateMonitor Class

Pushes ICanBus.BusState transitions to a protocol instance so it can abort or pause controlled transmissions on degradation (ErrWarning/ErrPassive/BusOff) and resume once the bus recovers (SRS FR-RAW-051).

public sealed class BusStateMonitor : System.IDisposable

Inheritance Object → BusStateMonitor

Implements IDisposable

Remarks

ICanBus.BusState is a plain getter with no dedicated change event, and an adapter's ICanBus.ErrorFrameReceived/ICanBus.FaultOccurred are not guaranteed to fire on every transition. The reliable mechanism is therefore a self-rearming poll driven through the owning 's own (default 50 ms) rather than a Timer or a free-running thread -- this keeps the monitor inside the existing single-mailbox event-driven-actor model (FR-RAW-020..022) instead of reintroducing a busy-loop/free-running-timer anti-pattern. The two bus events are subscribed additionally, purely as low-latency hints: they trigger an immediate out-of-band recheck (Post(Action)) so e.g. a BusState.BusOff is observed near-instantly instead of waiting up to one poll interval. The hint deliberately does not touch the poll timer; the self-rearming poll is the independent reliability floor.

Hints are coalesced. A bus-off or error-passive storm raises ICanBus.ErrorFrameReceived far faster than any loop can drain it (thousands per second on a shorted or badly terminated bus), so at most one un-run hint recheck is ever outstanding in the mailbox: further hints arriving while it is queued are dropped rather than posted. This is lossless with respect to what the monitor reports, because a recheck is a sample of a level (ICanBus.BusState, a plain getter), not the delivery of a queued event -- N back-to-back samples of an unchanged level yield exactly what one sample yields. What is dropped is the redundant mailbox traffic, which would otherwise starve the very protocol work the state change exists to abort. The gate is released before the sample is taken, so a hint that races an in-flight recheck posts a fresh one and the last hint of a storm is always followed by a sample taken after it.

What coalescing does not promise: the hints were never a transition log, and an error frame is not a state transition. If the controller passes through ErrWarning and ErrPassive on its way to BusOff faster than the loop samples, the intermediate levels are missed and StateChanged reports one ErrActive -> BusOff edge -- exactly as it already does when the hints are unavailable and the poll alone drives sampling. Every edge that is sampled is still reported individually and in order, with Previous chained to the last reported state, so a subscriber never sees a gap or a re-ordering. Sampling granularity remains tunable the way it always was: shorten pollInterval.

Loop-thread cost: each poll tick reads ICanBus.BusState synchronously on the actor's own loop thread. If a particular adapter's BusState getter is slow or blocking, that stalls this protocol instance's loop for the duration -- a known, accepted tradeoff of reusing the actor (which keeps state-change handling single-writer-safe) rather than a bug to work around here.

Lifetime: the poll loop also stops on its own once the owning actor is disposed (a re-arm then observes ObjectDisposedException and quietly ceases). Calling Dispose is still required to detach the two bus event subscriptions, which are independent of the actor's lifetime.

Constructors

BusStateMonitor(ICanBus, IProtocolActor, Nullable<TimeSpan>) Constructor

Wraps bus and drives its state polling through actor.

public BusStateMonitor(CanKit.Abstractions.API.Can.ICanBus bus, CanKit.Pro.Actor.IProtocolActor actor, System.Nullable<System.TimeSpan> pollInterval=null);

Parameters

bus ICanBus

The bus whose ICanBus.BusState is observed.

actor IProtocolActor

The protocol instance's actor; the poll runs on its loop (FR-RAW-020/051).

pollInterval Nullable<TimeSpan>

Poll cadence; must be > TimeSpan.Zero when given. Defaults to 50 ms.

Properties

BusStateMonitor.CurrentState Property

The most recently observed BusState. Reflects the bus's actual state at construction time and is updated on every observed transition.

public CanKit.Abstractions.API.Common.Definitions.BusState CurrentState { get; }

Property Value

BusState

Events

BusStateMonitor.StateChanged Event

Raised on the actor's loop whenever the observed state differs from the last-seen one -- for both degrading (e.g. ErrActive → BusOff) and recovering (e.g. BusOff → ErrActive) transitions, since a protocol needs to know when to resume, not only when to abort (FR-RAW-051). Edge-triggered: never raised while the state is unchanged.

public event EventHandler<BusStateChangedEventArgs>? StateChanged;

Event Type

EventHandler<BusStateChangedEventArgs>