Protocol Stack & Application Decoders
CAN & CANopen Studio features an extensible application decoder architecture.
Supported Decoder Profiles
1. Generic CANopen (CiA 301 / CiA 402)
- CiA 301 (Base Communications):
- Network Management (
NMT, COB-ID0x000) - Synchronization (
SYNC, COB-ID0x080) - Emergency frames (
EMCY, COB-ID0x081..0x0FF) - Heartbeat & Boot-up (COB-ID
0x701..0x77F) - SDO Request (
0x600 + NodeID) and SDO Response (0x580 + NodeID) - CiA 402 (Drives & Motion Control):
- Statusword (
0x6041) bitfield decoding: Not Ready to Switch On, Switch On Disabled, Ready to Switch On, Switched On, Operation Enabled, Quick Stop, Fault - Controlword (
0x6040) bitfield decoding - Target & Actual Velocity (
0x606C) in RPM
2. SEVCON Gen4 AC/PMSM Inverter
- TPDO1 (
0x148): Inverter status vectors (v1 to v4) - TPDO2 (
0x441): Motor status monitor (m1 to m4) - TPDO3 (
0x156): Heatsink temperature (°C), raw motor temperature, auxiliary voltage - TPDO4 (
0x270): Control mode, speed limit steps, target torque (0x6071) - TPDO5 (
0x473): Actual motor speed (0x606C) and maximum speed (0x6080) in RPM
3. De Haardt Track Safety Transponder
- Speed mode decoding:
0: Emergency Stop (Red Flag)1: Pit Lane Speed (Very Slow)2: Slow Speed (Yellow Flag)3: Normal Speed (Green Flag)4: Boost / Fast Mode
4. J1939 Extended (29-bit)
- Extracts Priority (3 bits), Parameter Group Number (PGN, 18 bits), Source Address (8 bits), and Destination Address.
Creating a Custom Decoder
To create a new decoder, simply create a class inheriting from BaseDeviceDecoder decorated with @register_decoder:
from canopen_stack import BaseDeviceDecoder, CanopenMessage, register_decoder
@register_decoder
class MyCustomBmsDecoder(BaseDeviceDecoder):
@property
def device_name(self) -> str:
return "Custom BMS Battery Monitor"
@property
def custom_cob_ids(self):
return {
0x190: "BMS Status & Voltage",
0x290: "BMS Cell Temperatures",
}
def can_decode(self, message: CanopenMessage) -> bool:
return message.arbitration_id in (0x190, 0x290)
def decode(self, message: CanopenMessage):
cid = message.arbitration_id
data = message.data
signals = {}
if cid == 0x190 and len(data) >= 4:
voltage = int.from_bytes(data[0:2], "little") * 0.1
current = int.from_bytes(data[2:4], "little", signed=True) * 0.1
signals["pack_voltage_v"] = voltage
signals["pack_current_a"] = current
message.decoded_info = f"BMS -> Voltage: {voltage:.1f}V | Current: {current:.1f}A"
return signals