app/rcc/ai/skills/can_modbus.md
Both are bus-layer protocols with their own framing semantics. The dashboard's frame parser still runs on top of whatever the bus driver extracts, but the bus drivers do enough work that you usually don't need a custom parser.
CAN is message-oriented: each message has an ID and 0–8 data bytes. The driver expects you to decode IDs into named signals.
io.canbus.listPlugins{} returns Qt's CAN plugin list (peakcan,
socketcan, vectorcan, ixxatcan, tinycan, virtualcan...). Most
embedded/development setups use socketcan on Linux, peakcan on
Windows.io.canbus.setPluginIndex{index} from the list above.io.canbus.listInterfaces{} returns physical channels for the
selected plugin (e.g. can0, vcan0).io.canbus.setInterfaceIndex{index}.io.canbus.setBitrate{bitrate} — common: 250000, 500000, 1000000.io.canbus.setCanFd{enabled} if the bus uses CAN-FD.io.connect{}.For real CAN networks, import a DBC file via the Project Editor's DBC importer (no API endpoint yet — surface this to the user). The importer generates groups + datasets for every signal in every message.
Multiplexed signals: simple multiplexing is supported. The importer
recognises the message's MultiplexorSwitch selector and emits one
dataset per muxed signal titled <name> (mux N). The configured
Built-In signal map extracts the selector first and only updates a
muxed signal when the selector's raw value equals its mux value, so
muxed datasets don't decode noise from other payloads sharing the same
bits — they keep their last valid value when the selector doesn't
match. Extended multiplexing (SG_MUL_VAL_, SwitchAndSignal
intermediates, value ranges) is not supported; those signals are
skipped and the post-import dialog reports how many were dropped. Tell
the user to switch the source to Lua or JavaScript and write a custom
parser if extended mux is required.
If the user has a custom protocol on CAN, you'll write a frame parser
that reads the raw id, dlc, data[] and extracts your fields. The CAN
driver feeds the parser one message at a time. Frames are published as
binary: standard = [ID_hi, ID_lo, DLC, payload...], extended =
[0x80|ID28..24, ID23..16, ID15..8, ID7..0, DLC, payload], zero-padded
to 11/13 bytes; parse with decoderMethod: 3 (Binary).
Modbus is request/response: the dashboard polls registers from one or more slave devices on a fixed interval.
io.modbus.setProtocolIndex{index} — io.modbus.listProtocols{}
returns the choices: 0 = Modbus RTU (over serial), 1 = Modbus TCP.io.modbus.setSerialPortIndex{index} (listSerialPorts first),
io.modbus.setBaudRate{baudRate} (listBaudRates),
io.modbus.setDataBitsIndex / setParityIndex / setStopBitsIndex
(each has a list*).
io.modbus.setSlaveAddress{address} — the slave id.io.modbus.setHost{host}, io.modbus.setPort{port} (default 502).io.modbus.setPollInterval{intervalMs} — default 100ms is sane. Faster
intervals can saturate slow RTU devices.io.modbus.addRegisterGroup{...} for each, with:
type: 0 = HoldingRegisters, 1 = InputRegisters, 2 = Coils,
3 = DiscreteInputsstartAddress and countio.connect{}.For complex slave devices, the Project Editor has a Modbus Map Importer that takes CSV/XML/JSON register descriptions and generates groups + datasets. Surface this to the user when they have a vendor map file.
Each register group becomes one dataset entry. The frame parser sees the formatted register values; the default parser is fine for most setups.