mcp_io_can

CAN bus adapter for mcp_io — Classic CAN 2.0A / 2.0B + CAN-FD over the same 4-Primitive surface, with CANopen and J1939 catalog helpers.

The web-safe core ships frame codecs, filters, and a paired in-memory transport. Production deployments opt in to the Linux SocketCAN binding via the separate lib/native.dart entry point.

Capability matrix

Area Support
Frames CAN 2.0A (11-bit), CAN 2.0B (29-bit), CAN-FD (DLC 9..15, BRS, ESI), RTR
Filter Acceptance code+mask, ID list, ID range, AnyOf
Capabilities can.send_frame / subscribe_frames · canopen.sdo_read / sdo_write / pdo_subscribe / nmt · j1939.send_pgn / subscribe_pgn
CANopen catalog CiA-301 standard objects (Identity 0x1018, Heartbeat 0x1017, Error Register 0x1001, ...), CiA-401 generic I/O, CiA-402 motion (Controlword/Statusword/Position/Velocity/Torque)
J1939 catalog Curated PGN dictionary (EEC1, EEC2, ET1, ETC1/2, CCVS1, DM1/2, ...) with byNumber / byAcronym lookup
Transports InMemoryCanTransport (web-safe, tests) · SocketCanTransport (Linux production via lib/native.dart) — vendor SDKs (PEAK / Vector / Kvaser) plug in via custom CanTransport

Web-safe quick start

import 'package:mcp_io_can/mcp_io_can.dart';

final transport = InMemoryCanTransport.loopback();
final adapter = CanAdapter(deviceId: 'bus', transport: transport);
await adapter.connect();

await adapter.execute(const Command(
  action: 'can.send_frame', target: 'can/0x180',
  args: {'data': [0x10, 0x20]},
));

adapter.subscribe(const TopicSpec(uri: 'can/#'))
    .listen((env) => print('frame: ${env.payload.value}'));

Linux SocketCAN production

import 'package:mcp_io_can/mcp_io_can.dart';
import 'package:mcp_io_can/native.dart';

final transport = SocketCanTransport(interfaceName: 'can0');
final adapter = CanAdapter(deviceId: 'engine-bus', transport: transport);
await adapter.connect();

The host system needs the kernel CAN modules (modprobe can, modprobe can_raw) and the interface up (ip link set up can0 type can bitrate 500000).

CANopen + J1939 helpers

// CANopen — read CiA-402 Statusword from node 5.
await adapter.execute(Command(
  action: 'canopen.sdo_read', target: '',
  args: {
    'nodeId': 5,
    'index': CiA402.statusword.index,
    'subIndex': CiA402.statusword.subIndex,
  },
));

// J1939 — look up Engine Controller 1 by acronym, send a PGN.
final pgn = J1939Pgns.byAcronym('EEC1')!;
await adapter.execute(Command(
  action: 'j1939.subscribe_pgn', target: '',
  args: {'pgn': pgn.pgn},
));

License

MIT — see LICENSE.

Libraries

mcp_io_can
CAN bus adapter for mcp_io (abstract + InMemory).
native
dart:io + dart:ffi-only additions for mcp_io_can.