CAN bus and CANopen
tedge-dot has two connectors for CAN, and both use Linux SocketCAN:
canbuslistens to frames on the bus and decodes the signals you name. The signals are defined in a DBC file. This connector is push-based: a signal is published when its frame arrives.canopenpolls a CANopen node’s object dictionary with expedited SDO transfers (up to 4 bytes), and writes to it the same way.
What you’ll build: both connectors running against the demo’s simulators on one virtual bus,
vcan0. canbus-sim broadcasts ENGINE_STATUS frames at 10 Hz, and canopen-sim is a CANopen
node with node ID 1. Their CAN IDs don’t collide, just as on real hardware.
Run the demo
Section titled “Run the demo”-
Start the CAN simulators. Both run with host networking and privileges, because a CAN interface belongs to the host’s network namespace.
canbus-simcreatesvcan0if it does not exist yet:Terminal window docker compose -f /usr/share/tedge-dot/demo/docker-compose.yaml up -d canbus-sim canopen-simOn podman, create the interface yourself and start the containers directly:
Terminal window sudo modprobe vcansudo ip link add dev vcan0 type vcansudo ip link set up vcan0sudo podman run -d --name canbus-sim --network host --privileged \--restart unless-stopped ghcr.io/thin-edge/tedge-dot/canbus-sim:latestsudo podman run -d --name canopen-sim --network host --privileged \--restart unless-stopped ghcr.io/thin-edge/tedge-dot/canopen-sim:latest -
Enable the demo configs. The package ships them, together with the demo DBC file at
/usr/share/tedge-dot/demo/can/test.dbc:Terminal window sudo cp /usr/share/tedge-dot/demo/canbus.toml /usr/share/tedge-dot/demo/canopen.toml /etc/tedge/plugins/ot/The service picks up new files within a few seconds.
-
Watch the measurements.
Terminal window tedge mqtt sub 'te/+/+/+/+/m/+'You should see
rpmandcoolant_tempfrom theenginedevice, andanalog_inandtemperaturefrom the CANopen deviceplc1.
CAN bus with a DBC file
Section titled “CAN bus with a DBC file”A canbus device is a SocketCAN interface plus the DBC file that describes its frames:
[[device]]name = "engine"protocol_address = { interface = "vcan0", dbc_file = "/usr/share/tedge-dot/demo/can/test.dbc" }default_mode = "typed"points_from = ["demo-sim"]demo/config/canbus.toml on GitHubA point names a DBC message and a signal. The DBC supplies the CAN ID, bit position, length and byte order, so a CAN point address is short:
# Point library for the demo CAN bus simulator (contract §3.4).## The signals of a *device type*, with no connection information in it: the# SocketCAN interface and the DBC file belong to the instance# (`protocol_address`), these signals belong to every instance. Reference it# from a device with:## points_from = ["demo-sim"]## Packages install this at /usr/share/tedge-dot/points.d/canbus/demo-sim.toml.## The addresses name a message and a signal from the DBC the connector loads;# bit offsets, lengths, scaling and endianness all come from that DBC, which is# why a CAN point address is this short. See connectors/canbus/sim/test.dbc.
[library]protocol = "canbus"# The device *type* this list describes (§3.1): it names the parameter sets of every# device that references this library, which is what keeps two device types on the# same protocol from sharing a (tenant-wide) Cumulocity identifier.type = "canbus-demo-sim"description = "Demo CAN sender (canbus-sim in demo/docker-compose.yaml), signals from test.dbc"version = "1"
[[point]]id = "rpm"name = "Engine speed"description = "Crankshaft speed from the ENGINE_STATUS frame"datatype = "uint16"access = "read"address = { message_name = "ENGINE_STATUS", signal_name = "RPM" }
[[point]]id = "coolant_temp"name = "Coolant temperature"description = "Engine coolant temperature from the ENGINE_STATUS frame"datatype = "int8"access = "read"address = { message_name = "ENGINE_STATUS", signal_name = "COOLANT_TEMP" }
# Writable: the connector sends the frame with this signal set.[[point]]id = "brake_active"name = "Brake active"description = "Brake pedal flag in the ENGINE_STATUS frame"datatype = "bool"access = "read_write"address = { message_name = "ENGINE_STATUS", signal_name = "BRAKE_ACTIVE" }demo/points.d/canbus/demo-sim.toml on GitHubPoints to know:
- Names are resolved when the config loads. If the DBC file doesn’t contain the named message or signal, the configuration is rejected.
- The connector doesn’t apply DBC
factorandoffset. Scale values with the point’stransform, or in a flow. - The bit rate is set on the interface, not by the connector. For a real bus, bring the
interface up first, for example with
ip link set can0 type can bitrate 500000.bitrateinprotocol_addressis only logged. - Writes are read-modify-write. Writing a signal (
access = "read_write", likebrake_active) sends its frame with that signal changed. The connector takes the rest of the frame from the last copy it received, or from zeros if it hasn’t received one. - No heartbeat. A CAN signal can’t be read on demand, so
report.max_intervalhas no effect. A signal is published only when its frame arrives. Because buses repeat frames periodically,report = { on_change = true }or a deadband usually saves the most.
CANopen over SDO
Section titled “CANopen over SDO”The interface is shared by every node, so it goes in [connection]. Each node is a [[device]]
with its node_id (1–127):
[connection]interface = "vcan0"demo/config/canopen.toml on GitHub[[device]]name = "plc1"protocol_address = { node_id = 1 }default_mode = "typed"points_from = ["demo-sim"]demo/config/canopen.toml on GitHubA point addresses an object dictionary entry by index and subindex, as in the demo node’s
point library:
# Point library for the demo CANopen simulator (contract §3.4).## The object dictionary entries of a *device type*, with no connection# information in it: the interface (`[connection]`) and the node id# (`protocol_address`) belong to the instance, these objects belong to every# node of this type. Reference it from a device with:## points_from = ["demo-sim"]## Packages install this at /usr/share/tedge-dot/points.d/canopen/demo-sim.toml.## Addresses are object-dictionary index/subindex pairs, read and written with# expedited SDO transfers.
[library]protocol = "canopen"# The device *type* this list describes (§3.1): it names the parameter sets of every# device that references this library, which is what keeps two device types on the# same protocol from sharing a (tenant-wide) Cumulocity identifier.type = "canopen-demo-sim"description = "Demo CANopen node (canopen-sim in demo/docker-compose.yaml)"version = "1"
[[point]]id = "analog_in"name = "Analog input"description = "Raw analog input word at object 0x2000"datatype = "uint16"access = "read"address = { index = 0x2000, subindex = 0 }
[[point]]id = "temperature"name = "Temperature"description = "Signed temperature word at object 0x2001"datatype = "int16"access = "read"address = { index = 0x2001, subindex = 0 }
# Writable, so it appears as a Cumulocity device parameter (RFC 0003).[[point]]id = "digital_out"name = "Digital outputs"description = "Output byte at object 0x2002, one bit per channel"datatype = "uint8"access = "read_write"address = { index = 0x2002, subindex = 0 }demo/points.d/canopen/demo-sim.toml on GitHubFor a real drive, the packaged default /etc/tedge/plugins/ot/canopen.toml has commented CiA 402
examples (velocity actual value 0x606C, controlword 0x6040). The connector polls every poll_interval. Writes use SDO download, and a
writable point becomes a Cumulocity device parameter (see
Writing to devices from the cloud).
Going to real hardware
Section titled “Going to real hardware”Change vcan0 to your interface (for example can0), point dbc_file at your own DBC, and replace
points_from = ["demo-sim"] with your points or with a point library
for your device type. The packaged defaults in /etc/tedge/plugins/ot/ contain commented examples.