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).
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
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.
Property Value¶
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.