CANopen Driver
Overview
CANopen — the EN 50325-4 / CiA 301 standard application layer on top of the CAN bus (common in motor drives / encoders / IO modules). Rewritten natively in 2026-07 — the old delegation to Apache PLC4j canopen has been removed (PLC4X CANopen only supports the local SocketCAN transport and cannot be carried over TCP, so connect died on edges without a physical CAN bus). It now uses the in-house SDO expedited upload implementation in plantpulse-plc-protocol (CanopenSdoClient) plus a transport abstraction (CanTransport) — the same native pattern as AB-ETH / BACnet.
| Item | Value |
|---|---|
opc_type | CANOPEN |
| Implementation class | plantpulse.driver.protocol.canopen.CANOpenDriver |
| Library | In-house native (canopen in plantpulse-plc-protocol — SDO expedited upload) |
| Inheritance | BaseProtocolDriver (not PLC4j) |
getProtocol() | "canopen:tcp" (retains the old PLC4X scheme notation — log/diagnostics compatibility) |
| Default port | 20200 (CAN-over-TCP gateway) |
| Status | Stable (SDO expedited polling) |
| read | OK (SDO expedited upload, values ≤ 4 bytes) |
| write | ❌ (read-only — SDO download not implemented) |
| Security | None |
The default transport (tcp) communicates with CANopen nodes located beyond a CAN ↔ TCP gateway. CAN frames are carried over TCP using canId(4B BE) + dlc(1B) + data[dlc]'s own binary framing (no handshake / bus name negotiation — identical framing to the simulator). transport=socketcan (local can0) is currently a stub (requires AF_CAN native bindings / JNI), so connect fails — the physical CAN interface track is planned as follow-up.
Class structure
BaseProtocolDriver
└── CANOpenDriver — connect() 에서 CanTransport(tcp/socketcan) + CanopenSdoClient 생성
A read failure (Abort / timeout / socket) invalidates the socket (connected=false + close) so that the Edge is prompted to reconnect.
Wire format summary (in-house implementation — SDO expedited upload)
- Request: COB-ID
0x600+nodeId, data[0x40, index LE(2B), subindex, 0×4]. - Response: COB-ID
0x580+nodeId, command0x43(4B) /0x47(3B) /0x4B(2B) /0x4F(1B) — value little-endian.0x80= Abort (code u32 LE) → communication exception. - Segmented / block transfer not supported (values > 4B, e.g. long strings) — expedited is sufficient for scalar tag polling.
OPC registration options
| Field | Meaning | Default |
|---|---|---|
opc_agent_ip | CAN-TCP gateway IP | 192.168.1.20 |
opc_agent_port | Gateway TCP port | 20200 |
options.read-timeout | SDO response timeout (ms) | 3000 |
options.transport | tcp / socketcan (stub) | tcp |
options.can-interface | Interface name in socketcan mode | can0 |
The node-id option from old forms/documents is deprecated — the node ID is now the first field of the tag address (see below). The request-timeout / heartbeat options are also not accepted by the native driver (the timeout is read-timeout).
Tag plc_address format (new contract)
<nodeId>:<index>:<subindex>[:<TYPE>]
| Field | Meaning | Notation |
|---|---|---|
nodeId | CANopen node id (1..127) | Decimal |
index | Object dictionary index (0..0xFFFF) | 0x-prefixed hex (0x6041) or decimal |
subindex | Sub index (0..255) | 0x-prefixed hex or decimal |
TYPE (optional) | LE byte interpretation type | If omitted, inferred from data_type → if that is also absent, UINT16 |
| Notation | Meaning |
|---|---|
1:0x6041:0:UINT16 | Node 1, DS402 Statusword |
3:0x2000:0:REAL | Node 3, manufacturer area float32 |
1:0x6064:0:INT32 | Position actual value (32-bit signed) |
5:8192:1 | Node 5, index decimal 8192 (= 0x2000), TYPE omitted → UINT16 |
TYPE tokens (aliases accepted)
| Token (alias) | Size | Interpretation |
|---|---|---|
BOOL (BOOLEAN) | 1 | true unless 0 |
UINT8 (U8, USINT, BYTE) / INT8 (I8, SINT) | 1 | u8 / i8 |
UINT16 (U16, UINT, WORD) / INT16 (I16, INT) | 2 | u16 / i16 (default UINT16) |
UINT32 (U32, UDINT, DWORD) / INT32 (I32, DINT) | 4 | u32 / i32 |
REAL (FLOAT, F32, REAL32) | 4 | IEEE-754 float (LE) |
STRING (STR, VISIBLE_STRING) | ≤4 | ASCII (trailing NUL stripped) |
When TYPE is omitted, it is inferred from data_type: Boolean→BOOL, Integer→INT16, Long→INT32, Float→REAL, Double→REAL (4B approximation), String→STRING.
Supported / unsupported
- read: SDO expedited upload (≤ 4 bytes) — OK.
- write: unsupported —
isWriteSupported() = falseandwrite()always returnfalse. (SDO download is a follow-up.) - Segmented / block SDO, PDO mapping, NMT / Heartbeat monitoring, SYNC, EMCY, LSS: unsupported.
- Direct SocketCAN / USB-CAN: stub (CAN-over-TCP gateway assumed).
curl registration example
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_CANOPEN_DRIVE1",
"opc_type": "CANOPEN",
"opc_name": "Servo Drive 1",
"opc_agent_ip": "192.168.1.20",
"opc_agent_port": "20200",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": { "read-timeout": "3000" },
"tag_list": [
{"tag_id":"OPC_CANOPEN_DRIVE1_T01","tag_name":"Statusword","plc_address":"1:0x6041:0:UINT16","data_type":"Integer"},
{"tag_id":"OPC_CANOPEN_DRIVE1_T02","tag_name":"ActualPos","plc_address":"1:0x6064:0:INT32","data_type":"Long"}
]
}'
References
- CiA 301 (Application Layer) / CiA 402 (Drives) standards.
- Code:
plantpulse-edge-driver/src/plantpulse/driver/protocol/canopen/CANOpenDriver.java+ thecanopenpackage inplantpulse-plc-protocol. - Operations: the gateway framing is a proprietary binary format (
canId+dlc+data) — different from the socketcand text protocol. Addresses should be validated against the device EDS file.