Higher CAN protocol layers for .NET, built on CanKit.
CanKit gives .NET a single, fast, vendor-neutral API for raw CAN and CAN FD frames. CanKit.Pro adds the layer above it — the plumbing every real protocol stack (ISO-TP, J1939, CANopen, UDS) needs and that people otherwise rebuild, slightly differently and slightly wrong, in each stack:
- several protocol instances sharing one bus without fighting over
ReceiveAsync, - a documented threading model instead of ad-hoc locks and busy loops,
- timeouts that are actually checked rather than merely stored,
- validated CAN-ID and J1939 field arithmetic,
- one answer to "was this frame really sent?", whatever the adapter does about TX echo.
CanKit.Pro is not a fork of CanKit. It consumes CanKit from nuget.org exactly like your own application does. Adapters,
ICanBus, frames and timing stay upstream, where they belong; this repository ships only the layers above.
Published to nuget.org, versioned and released together.
L2 — the raw-CAN service layer.
| Package | What it gives you | Depends on |
|---|---|---|
CanKit.Pro.RawCan |
Multi-protocol demultiplexing: N independent, filtered, read-only views of one ICanBus, reconfigurable at runtime. Plus SendConfirmed, a uniform TX-confirmation over hardware echo. |
CanKit.Abstractions |
CanKit.Pro.Actor |
ProtocolActor: single-mailbox, single-writer execution with an event-driven timer queue and one background-exception channel. |
— |
CanKit.Pro.Addressing |
Validated 11/29-bit CAN IDs, J1939 PGN/priority/PDU/source-address composition, J1939 NAME and PGN catalogues. | — |
CanKit.Pro.Reliability |
Deadlines whose expiry is guaranteed to be checked, and a BusStateMonitor that pushes ErrWarning/ErrPassive/BusOff transitions and recovery. |
CanKit.Abstractions, CanKit.Pro.Actor |
L3/L4 — transports and application protocols.
| Package | What it gives you |
|---|---|
CanKit.Pro.IsoTp |
ISO 15765-2: SF/FF/CF/FC codec, bounds-checked PCI parsing, STmin handling, and an actor-driven IIsoTpChannel over CAN and CAN FD. |
CanKit.Pro.J1939Tp |
SAE J1939-21 transport: TP.BAM broadcast and TP.CM connection mode (RTS/CTS/EndOfMsgAck), multi-session. |
CanKit.Pro.CANopen |
CiA 301: SDO client/server incl. block transfer, static and dynamic PDO mapping, NMT, heartbeat and node guarding, EMCY, object dictionary. |
CanKit.Pro.J1939 |
J1939 node: address claim with arbitrary-address fallback, fixed-rate periodic send, SPN catalogue over J1939-71. |
CanKit.Pro.Uds |
ISO 14229-1 client over ISO-TP: session control, security access, read/write by identifier, routine control, upload/download, P2/P2* timing and 0x78 response-pending. |
Everything targets netstandard2.0 and net10.0.
dotnet add package CanKit.Pro.RawCan
# CanKit itself: the core, plus the adapter for the hardware you talk to
dotnet add package CanKit.Core
dotnet add package CanKit.Adapter.Virtual # loopback, no hardware
# dotnet add package CanKit.Adapter.PCAN # or Kvaser, Vector, SocketCAN, ZLG, ControlCANusing CanKit.Core;
using CanKit.Pro.Actor;
using CanKit.Pro.RawCan;
using CanKit.Pro.Reliability;
using var bus = CanBus.Open("virtual://demo/0",
cfg => cfg.SetProtocolMode(CanProtocolMode.Can20).Baud(500_000));
using var service = new CanBusService(bus);
// One bus, two protocol instances, two disjoint ID ranges — neither starves the other,
// and a slow consumer cannot block the fast one.
using var isoTp = service.Subscribe(CanIdFilter.Range(0x700, 0x7FF));
using var j1939 = service.Subscribe(view => view.IsExtendedFrame);
await foreach (var frame in isoTp.Frames.WithCancellation(token))
{
// A read-only CanFrameView: no ownership, no disposal, no aliasing surprises.
}
// "Did it actually go out?" — a real echo match where the bus provides one, driver acceptance
// otherwise, and flagged so you can tell which you got.
var tx = await service.SendConfirmed(CanFrame.Classic(0x123, new byte[] { 1, 2, 3 }));
if (!tx.Confirmed) Console.WriteLine(tx.FailureReason); // Timeout, BusOff or Rejected — never a hang
// Timeouts and bus health, on the protocol instance's own single-threaded loop.
using var actor = new ProtocolActor();
using var monitor = new BusStateMonitor(bus, actor);
monitor.StateChanged += (_, e) => { if (e.Current.IsTransmitBlocked()) AbortActiveTransfer(); };
var deadline = new DeadlineScheduler(actor).Arm(TimeSpan.FromMilliseconds(150), OnTimeout);A runnable version, needing no hardware:
dotnet run --project samples/CanKit.Pro.Sample.Demux| Getting started | Install, open a bus, the four packages in context |
| Architecture (arc42) | Layer model L0–L4, building blocks, runtime views, ADRs — German |
| Requirements (SRS) | The FR-RAW-* requirements the code and tests cite — German |
| Release process | GitVersion + semantic-release, how a commit becomes a NuGet package |
| Licensing | Why this is MIT although it grew out of an Apache-2.0 fork |
| Migration from CanKit.Pro.legacy | What moved, what stayed, what changed on the way |
| Contributing | Branching, Conventional Commits, running the tests |
The layer model these packages implement (L2, "Raw-CAN service layer") exists to carry the layers above it. In rough order:
- Publish the L3/L4 packages. They are implemented and tested; what remains is settling their public APIs and, for several of them, validating against real hardware rather than only the loopback adapter.
- Source generators for object dictionaries and PGN definitions; DBC and EDS import.
- XCP, DeviceNet, CANopen Safety.
Architecture and requirements for all of these are in docs/.
Every package shares one version, derived from the commit history:
Conventional Commits on main drive
semantic-release, which decides the number, writes the
changelog, tags, and publishes to nuget.org. Builds that are not releases are stamped by a
GitVersion step in the pipeline, so a CI artifact is identifiable
without being a release. See docs/release-process.md.
MIT. CanKit is a separate project under Apache-2.0 — see THIRD-PARTY-NOTICES.md and docs/licensing.md.
Not affiliated with the CanKit project.