Skip to content

IJ1939Node Interface

One application-layer SAE J1939 node bound to a single physical bus and a single 64-bit J1939Name identity. The node owns the SAE J1939-81 address-claim state (SRS FR-J1939-003/004), routes outbound PGNs through direct 29-bit frames (≤ 8 bytes) or the shared J1939-TP channel (> 8 bytes, SRS FR-J1939-006), and surfaces every inbound application PGN (including Request-PGN, SRS FR-J1939-005) via MessageReceived.

public interface IJ1939Node : System.IDisposable, System.IAsyncDisposable

Implements IDisposable, IAsyncDisposable

Remarks

The node composes on the CanKit.Pro L2 services (ICanBusService for RX demux and TX confirmation, IProtocolActor for single-writer state, DeadlineScheduler for the SAE J1939-81 §4.4.3.3 250 ms arbitration window and periodic-send timing) and never touches vendor-specific SDKs directly.

IDisposable.Dispose is thread-safe and idempotent. It unwinds the underlying subscriptions and transport channel and faults an in-flight ClaimAddressAsync(byte, CancellationToken) with ObjectDisposedException -- or, for a claim that has already lost and still owes its Cannot Claim, with J1939CannotClaimException. It does not cancel an in-flight SendAsync(J1939Message, CancellationToken): a single-frame send ends as its confirmation does (a node that owns its bus service disposes that service last, which cancels a confirmation still awaited there), and a multi-frame send fails with ObjectDisposedException once the transport channel is torn down. ClaimState and Address keep the last values they had.

Called from a MessageReceived or AddressClaimChanged handler, which run on the node's actor, neither IDisposable.Dispose nor IAsyncDisposable.DisposeAsync waits for the teardown: it runs on the actor once that handler's turn has ended, a failure of it is reported through BackgroundExceptionOccurred, and a IAsyncDisposable.DisposeAsync called from anywhere else completes when it has.

Properties

IJ1939Node.Address Property

The node's claimed source address, or null when it has none (NotClaimed / CannotClaim).

System.Nullable<byte> Address { get; }

Property Value

Nullable<Byte>

IJ1939Node.ClaimState Property

Current SAE J1939-81 claim state (SRS FR-J1939-003/004).

CanKit.Pro.J1939.J1939ClaimState ClaimState { get; }

Property Value

J1939ClaimState

IJ1939Node.Name Property

The node's immutable 64-bit SAE J1939-81 NAME (from Name).

CanKit.Pro.Addressing.J1939Name Name { get; }

Property Value

J1939Name

IJ1939Node.Options Property

Node options captured at construction.

CanKit.Pro.J1939.J1939NodeOptions Options { get; }

Property Value

J1939NodeOptions

Methods

IJ1939Node.ClaimAddressAsync(byte, CancellationToken) Method

Runs the SAE J1939-81 address-claim procedure for preferredAddress (SRS FR-J1939-003): 1. Transmit Address Claim (PGN 0xEE00, SA = preferred) carrying the node's NAME. 2. Listen for contending claims within ClaimAnnounceTimeout (default 250 ms). 3. On a losing contest (peer's NAME numerically lower, or equal — SAE J1939-81 §4.4.3.3 gives the address only to the lower NAME), transition to CannotClaim, broadcast Cannot Claim (SA = 0xFE) per SAE J1939-81 §4.4.3.4 and throw J1939CannotClaimException (SRS FR-J1939-004). An equal NAME does not scan for another address.

The Cannot Claim, and an arbitrary-address node's next claim after losing, go out after the pseudo-random 0..153 ms backoff of SAE J1939-81 §4.4.4.3 -- the low byte of the NAME's bytes summed, times 0.6 ms -- so two nodes colliding on an address do not answer in lockstep (#58). The task faults after that Cannot Claim has gone out, in the order step 3 gives, so disposing the node on the exception cannot suppress the frame; a further claim started before it drops the Cannot Claim and faults this one at once.

System.Threading.Tasks.Task ClaimAddressAsync(byte preferredAddress, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken));

Parameters

preferredAddress Byte

cancellationToken CancellationToken

Returns

Task

Exceptions

J1939CannotClaimException
The preferred address was lost to a higher-priority NAME and no fallback was available, or to a peer with this node's own NAME (SAE J1939-81 §4.4.3).

InvalidOperationException
A claim is still in arbitration: it is not silently replaced -- await it, or cancel it, before claiming again (#58). The move an arbitrary-address node makes on its own after losing a claimed address is not such a claim: nothing awaits it, so this call replaces it -- in its backoff or in its announcement window -- and the node claims preferredAddress instead of finishing the move (#355).

IJ1939Node.RequestPgnAsync(uint, byte, CancellationToken) Method

Sends a Request-PGN (SAE J1939-21 PGN 0xEA00, SRS FR-J1939-005). The 3-byte payload carries requestedPgn little-endian; the request itself is a direct single-frame PGN. A destination of 0xFF makes the request global (every node responds with its usual PGN); any other value targets a specific ECU.

System.Threading.Tasks.Task RequestPgnAsync(uint requestedPgn, byte destinationAddress=byte.MaxValue, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken));

Parameters

requestedPgn UInt32

destinationAddress Byte

cancellationToken CancellationToken

Returns

Task

IJ1939Node.SendAsync(J1939Message, CancellationToken) Method

Sends message on the bus (SRS FR-J1939-001/006). Payloads ≤ 8 bytes go as one direct 29-bit CAN frame; larger payloads are broken into a TP.BAM (global destination) or TP.CM (specific destination) session on the shared J1939-TP channel. The task completes when the last frame of the message has been TX-confirmed (single frame) or when the TP session finishes (multi-frame).

System.Threading.Tasks.Task SendAsync(CanKit.Pro.J1939.J1939Message message, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken));

Parameters

message J1939Message

cancellationToken CancellationToken

Returns

Task

Exceptions

J1939NoAddressException
The node has not yet claimed an address.

IJ1939Node.StartPeriodicSend(J1939Message, TimeSpan) Method

Starts sending message periodically at the given period (SRS FR-J1939-007). Every emission — single-frame and multi-frame alike — is driven by the node's actor / SendAsync loop on top of the L2 DeadlineScheduler. Each iteration re-checks the SAE J1939-81 claim state through SendAsync(J1939Message, CancellationToken)'s pre-flight gate, so a concurrent address loss stops wire traffic automatically and a subsequent successful claim (possibly on a different SA) resumes it — the emitted 29-bit ID is composed from the currently-claimed SA on every tick. Send failures are surfaced via BackgroundExceptionOccurred. Disposing the returned handle stops the schedule. Multiple concurrent schedules for the same PGN are allowed (callers may want to send the same PGN to two destinations). The caller supplies period; mapping application PGNs to their SAE J1939-71 standard rate is the caller's responsibility.

Payload snapshot:message's payload is snapshotted into an owned buffer when this method returns and every emission transmits that snapshot. In-place mutation of the caller's original buffer after StartPeriodicSend is NOT observed on the wire — this matches J1939Message's "payload is copied by the sender" contract . To change the transmitted data, dispose the returned handle and start a fresh schedule with a new J1939Message.

System.IDisposable StartPeriodicSend(CanKit.Pro.J1939.J1939Message message, System.TimeSpan period);

Parameters

message J1939Message

period TimeSpan

Returns

IDisposable

Exceptions

J1939NoAddressException
The node has not yet claimed an address (matches SendAsync(J1939Message, CancellationToken)'s pre-flight gate).

Events

IJ1939Node.AddressClaimChanged Event

Raised whenever the node's ClaimState transitions (a claim starts, succeeds, is lost, or Cannot Claim is broadcast).

event EventHandler<J1939ClaimEventArgs>? AddressClaimChanged;

Event Type

EventHandler<J1939ClaimEventArgs>

IJ1939Node.BackgroundExceptionOccurred Event

Raised when a background failure (subscription faulted, actor exception, event-handler throw, TP channel background error) must be surfaced to the application.

Raised on a thread-pool thread, never on the node's actor loop, so a handler may wait on the node -- a send, a claim, IDisposable.Dispose, which then waits for the teardown as any other caller's does -- without deadlocking the actor (#356). Reports are delivered one at a time, in the order they were made; a handler that blocks holds up the reports behind it, not the node. Each handler is called on its own, so one that throws does not keep the report from the handlers after it; its exception is dropped. A report may reach its handlers after IDisposable.Dispose has returned.

event EventHandler<Exception>? BackgroundExceptionOccurred;

Event Type

EventHandler<Exception>

IJ1939Node.MessageReceived Event

Raised on the actor's loop thread for every inbound application PGN — both direct single-frame PGNs and reassembled J1939-TP payloads (SRS FR-J1939-001/006). Request PGN (0xEA00) messages are also surfaced here so applications can respond in kind (SRS FR-J1939-005). Handlers must not throw; a throwing handler is caught and reported via BackgroundExceptionOccurred.

event EventHandler<J1939Message>? MessageReceived;

Event Type

EventHandler<J1939Message>