Plugins Prerequisites
The canbus-generator generates c++ plugins from json mapping files for the canbus-binding binding.
Install the canbus-binding devel version
If you have installed the canbus-binding from sources you already have the devel version installed.
Else install the “canbus-binding-devel” package using your package manager.
For redesk, Fedora or Opensuse:
sudo dnf install canbus-binding-devel
or for Ubuntu:
sudo apt install canbus-binding-dev
Install the generator
To generate your plugins you will need the canbus-generator.
For redesk, Fedora or Opensuse:
dnf install canbus-generator
or for Ubuntu:
sudo apt install canbus-generator-bin
or from the sources:
git clone https://github.com/redpesk-common/canbus-generator
cd canbus-generator
mkdir build
cd build
cmake ..
make
sudo make install
Write your json mapping file
In this json file will be defined all the messages you will receive from your connected device.
The generator uses OpenXC.Here is the original documentation.
You can find a plugin template by installing the “canbus-binding-plugin-template” package. This package contains a json mapping file, a simple plugin header file and a CMake file for easy build. After installation, these files can be found in your /usr/share/doc/ directory.
General Options
-
name
Set plugin’s name. It will be your plugin’s id in the binding. It has to be unique. -
version
Set plugin’s version. It will define which version of the decoders will be used. Available versions are v1 and v2, the latest being v2. -
extra_sources
Set sources to be added to the top of the generated plugin. It can be your own c++ signal decoders for example.
Message
The messages
key is an object with fields mapping from CAN message IDs
to signal definitions. The fields must be hex IDs of CAN messages
as strings (e.g. 0x90).
-
name
The name of the CAN message. Can be used to easily find a certain message among the others. -
bus
The name of the CAN bus where the messages can be found. -
comment
To give a piece of information about the message. Useful when reading the code. -
length
Message’s length in bytes. -
is_fd
Either True or False. Determines if your device uses CAN FD or not. -
is_extended
Either True or False. Determines if the CAN ID is 11 bits or 29 bits long. -
is_j1939
Either True or False. Determines if your devices use the j1939 protocol. -
byte_frame_is_big_endian
Either True or False. Determines if the whole frame will be read as a big endian or not. -
bit_position_reversed
Either True or False. Determines if index 0 of a byte starts at 0 or 0+size-1. -
signals
A list of CAN signal objects (described in the signal section) that are in this message, with the name of the signal as the key. -
max_frequency
If sending raw CAN messages to the output interfaces, this controls the maximum frequency (in Hz) that the message will be processed and let through. The default value (0) means that all messages will be processed, and there is no limit imposed by the firmware. If you want to make sure you don’t miss a change in value even when rate limiting, see the force_send_changed attribute. Defaults to 0 (no limit). -
max_signal_frequency
Setting the max signal frequency at the message level will cascade down to all of the signals within the message (unless overridden). The default value (0) means that all signals will be processed, and there is no limit imposed by the firmware. See the max_frequency flag documentation for the signal mapping for more information. If you want to make sure you don’t miss a change in value even when rate limiting, see the force_send_changed_signals attribute. Defaults to 0 (no limit). -
force_send_changed
Meant to be used in conjunction with max_frequency, if this is true a raw CAN message will be sent regardless of the given frequency if the value has changed (when using raw CAN passthrough). Defaults to true. -
force_send_changed_signals
Setting this value on a message will cascade down to all of the signals within the message (unless overridden). See the force_send_changed flag documentation for the signal mapping for more information. Defaults to false.
Signal
The attributes of a signal object within a message are:
-
name
The name of the associated generic signal name (from the OpenXC specification) that this should be translated to. Optional - if not specified, the signal is read and stored in memory, but not sent to the output bus. This is handy for combining the value of multiple signals into a composite measurement such as steering wheel angle with its sign. -
bit_position
The starting bit position of this signal within the message. -
bit_size
The width in bits of the signal. -
factor (optional)
The signal value is multiplied by this if set. -
offset (optional)
This is added to the signal value if set. -
min_value (optional)
The minimum value for the processed signal. -
max_value (optional)
The maximum value for the processed signal. -
send_same (optional)
If true, will re-send even if the value hasn’t changed. -
force_send_changed (optional)
If true, regardless of the frequency, it will send the value if it has changed. -
sign (optional)
If the data is signed it indicates the encode. -
bit_sign_position (optional)
The bit that indicates the sign of the signal in its CAN message. -
unit (optional)
The unit of the data. -
decoder (optional)
The name of a function that will be compiled with the firmware and should be applied to the signal’s value after the normal translation. See the Signal Decoder section for details. -
ignore (optional)
Setting this to true on a signal will silence output of the signal. The VI will not monitor the signal nor store any of its values. This is useful if you are using a custom decoder for an entire message, want to silence the normal output of the signals it handles. If you need to use the previously stored values of any of the signals, you can use the ignoreDecoder as the decoder for the signal. Defaults to false. -
enabled (optional)
Enable or disable all processing of a CAN signal. By default, a signal is enabled; if this flag is false, the signal will be left out of the generated source code. Defaults to true.
The difference between ignore, enabled and using an ignoreDecoder can be confusing. To summarize the difference:
- The enabled flag is the master control switch for a signal - when this is false, the signal (or message, or mapping) will not be included in the firmware at all. A common time to use this is if you want to have one configuration file with many options, only a few of which are enabled in any particular build.
- The ignore flag will not exclude a signal from the firmware, but it will not include it in the normal message processing pipeline. The most common use case is when you need to reference the bit field information for the signal from a custom decoder.
- Finally, use the ignoreDecoder for your signal’s decoder to both include it in the firmware and handle it during the normal message processing pipeline, but just silence its output. This is useful if you need to track the last known value for this signal for a calculation in a custom decoder.
-
states
This is a mapping between the desired descriptive states (e.g. off) and the corresponding numerical values from the CAN message (usually an integer). The raw values are specified as a list to accommodate multiple raw states being coalesced into a single final state (e.g. key off and key removed both mapping to just “off”). -
max_frequency (optional)
Some CAN signals are sent at a very high frequency, likely more often than will ever be useful to an application. This attribute sets the maximum frequency (Hz) that the signal will be processed and let through. The default value (0) means that all values will be processed, and there is no limit imposed by the firmware. If you want to make sure you don’t miss a change in value even when dropping messages, see the force_send_changed attribute. You probably don’t want to combine this attribute with send_same or else you risk missing a status change message. Defauls to 0 (no limit). -
send_same (optional)
By default, all signals are translated every time they are received from the CAN bus. By setting this to false, you can force a signal to be sent only if the value has actually changed. This works best with boolean and state based signals. Defaults to true. -
force_send_changed (optional)
Meant to be used in conjunction with max_frequency, if this is true a signal will be sent regardless of the given frequency if the value has changed. This is useful for state-based and boolean states, where the state change is the most important thing and you don’t want that message to be dropped. Defaults to false. -
writable (optional)
Set this attribute to true to allow this signal to be written back to the CAN bus by an application. By default, the value will be interpreted as a floating point number. Defaults to false. -
encoder (optional)
You can specify a custom function here to encode the value for CAN messages.
Diagnostic Messages
The diagnostic_messages
key is an array of objects describing a recurring diagnostic message request.
The attributes of each diagnostic message object are:
-
bus
The name of one of the previously defined CAN buses where this message should be requested. -
id
the arbitration ID for the request. -
mode
The diagnostic request mode, e.g. Mode 1 for powertrain diagnostic requests. -
frequency
The frequency in Hz to request this diagnostic message. The maximum allowed frequency is 10Hz. -
pid (optional)
If the mode uses PIDs, the pid to request. -
name (optional)
A human readable, string name for this request. If provided, the response will have a name field (much like a normal translated message) with this value in place of bus, id, mode and pid. -
decoder (optional)
When using a name, you can also specify a custom decoder function to parse the payload. This field is the name of a function (that matches the DiagnosticResponseDecoder function prototype). When a decoder is specified, the decoded value will be returned in the value field in place of payload. -
callback (optional)
This field is the name of a function (that matches the DiagnosticResponseCallback function prototype) that should be called every time a response is received to this request.
Signal Decoder
The default decoder for each signal is a simple passthrough, translating the signal’s value from engineering units to something more usable (using the defined factor and offset). Some signals require additional processing that you may wish to do within the binding and not on the host device. Other signals may need to be combined to make a composite signal that’s more meaningful to developers.
There is however a list of ready to use decoders provided by the canbus binding:
- decode_state
- decode_booleanl
- decode_ignore
- decode_noop
- decode_bytes
- decode_ascii
- decode_date
- decode_time
- decode_signal
- decode_obd2_response
You can also define your own decoder. To do so, create a signal-header.cpp
file that will be added to the top of the generated c++ plugin file by adding
its filename to the extra_sources
field. Then write you own decoder
in this file. Here is an example:
openxc_DynamicField decoder_t::decode_date(signal_t& signal, std::shared_ptr<message_t> message, bool* send)
{
float value = decoder_t::parse_signal_bitfield(signal, message);
AFB_DEBUG("Decoded message from parse_signal_bitfield: %f", value);
openxc_DynamicField decoded_value = build_DynamicField(value);
// Don't send if they is no changes
if ((signal.get_last_value() == value && !signal.get_send_same()) || !*send )
*send = false;
signal.set_last_value(value);
return decoded_value;
}
Json Example
{ "name": "NMEA2000",
"version" : "2.0",
"extra_sources": [],
"messages": {
"60160": {
"name": "Iso.Transport.Protocol.Data.Transfer",
"bus":"hs",
"comment":"ISO Transport Protocol, Data Transfer",
"length": 8,
"is_fd": false,
"is_extended": false,
"is_j1939": true,
"byte_frame_is_big_endian": true,
"bit_position_reversed": true,
"signals": {
"Sid": {
"bit_position": 0,
"bit_size": 8,
"sign": 0,
"name": "Iso.Transport.Protocol.Data.Transfer.Sid"
},
"Data": {
"bit_position": 8,
"bit_size": 56,
"decoder": "decoder_t::decode_bytes",
"sign": 0,
"name": "Iso.Transport.Protocol.Data.Transfer.Data"
}
}
},
"60160#1": {
"name": "60160",
"bus":"hs",
"comment":"Abstract signals for 60160",
"length": 8,
"is_fd": false,
"is_extended": false,
"is_j1939": true,
"byte_frame_is_big_endian": true,
"bit_position_reversed": true,
"signals": {
"signal.60160": {
"bit_position": 0,
"bit_size": 64,
"comment": "One signals",
"decoder": "decoder_t::decode_60160",
"sign": 0,
"name": "signal.60160"
}
}
}
}
}
Generate your plugin
To generate your plugin you’ll have to use the canbus-generator. Once you have installed the generator and written your json mapping file, simply use CMake to do it automatically (see next section) or execute this command:
can-config-generator -m you-mapping-file.json -o your-plugin-name.cpp
Build and install your plugin
To be able to build your plugin, change set(TARGET_NAME generated_plugin)
in
the CMakeLists.txt with your plugin’s name.
At the root of your plugin project (replace “generated-plugin” by the name of your plugin).
mkdir build && cd build
cmake ..
make generated-plugin
make install_generated-plugin
The make generated-plugin
will automatically generate you plugin’s sources.
You can also manually generate it with make generate_generated-plugin
.
Now activate the plugin in the canbus-binding. To do so, edit the control-canbus-binding.json file to make it look like the example down below. This file should be located in /usr/local/canbus-binding/etc/.
{
"$schema": "",
"metadata": {
"uid": "CAN bus",
"version": "2.0",
"api": "canbus",
"info": "CAN bus Configuration"
},
"config": {
"active_message_set": 0,
"dev-mapping": {
"hs": "can0",
"ls": "can0"
},
"diagnostic_bus": "hs"
},
"plugins": [
{
"uid": "generated-plugin",
"info": "custom generated plugin",
"libs": "generated-plugin.ctlso"
}
]
}
Congratulations, your plugin is now ready to be used.