Writable Configuration Examples¶
For applications that need to send data back to the vehicle, you can configure a variety of CAN writes: raw CAN messages, performing reverse translation of an individual CAN signal, or send diagnostic requests.
Most of these examples build on configurations started for reading data from the bus, so you are strongly encouraged to read, understand and try the read-only configurations before continuing.
Named Numeric Signal Write Request¶
We want to send a single numeric value to the VI, and have it translated back
into a CAN signal in a message on a high speed bus attached to controller 1. The
signal is 7 bits wide, starting from bit 5 in message ID 0x102
. We want the
name of the signal that will be sent to the VI to be my_openxc_measurement
.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"writable": true
}
}
}
}
}
This configuration is the same as the read-only example One Bus, One Numeric Signal
with the addition of the writable
flag to the signal. When this flag is
true, an OpenXC message sent back to the VI from an app with the name
my_openxc_measurement
will be translated to a CAN signal in a new message
and written to the bus.
If the VI is configured to use the JSON output format, sending this OpenXC single-valued message to the VI via USB or UART (Bluetooth) would trigger a CAN write:
{"name": "my_openxc_measurement", "value": 42}
With the tools from the OpenXC Python library you can send that from a terminal with the command:
openxc-control write --name my_openxc_measurement --value 42
and if you compiled the firmware with DEBUG=1
, you can view the view the
logs to make sure the write went through with this command:
openxc-control write --name my_openxc_measurement --value 42 --log-mode stderr
Named Boolean Signal Write Request¶
We want to send a single boolean value to the VI, and have it translated back
into a CAN signal in a message on a high speed bus attached to controller 1. The
signal is 1 bits wide, starting from bit 3 in message ID 0x201
. We want the
name of the signal that will be sent to the VI to be my_boolean_request
.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_boolean_request",
"bit_position": 3,
"bit_size": 1,
"writable": true,
"encoder": "booleanWriter"
}
}
}
}
}
In addition to setting writable
to true, We set the encoder
for
the signal to the built-in booleanWriter
. This will handle converting a
true
or false
value from the user back to a 1 or 0 in the outgoing CAN
message.
If the VI is configured to use the JSON output format, sending this OpenXC single-valued message to the VI via USB or UART (Bluetooth) would trigger a CAN write:
{"name": "my_boolean_request", "value": true}
With the tools from the OpenXC Python library you can send that from a terminal with the command:
openxc-control write --name my_boolean_request --value true
Named State-based Signal Write Request¶
We want to send a state as a string to the VI, and have it translated back into
a numeric CAN signal in a message on a high speed bus attached to controller 1.
As in One Bus, One State-based Signal, the signal is 3 bits wide, starting from bit 28 in
message ID 0x104
. We want the name of the signal for OpenXC app developers
to be active_state
. There are 6 valid states from 0-5 in the CAN signal, but
we want the app developer to send the strings a
through f
to the VI.
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_state_request",
"bit_position": 28,
"bit_size": 3,
"states": {
"a": [0],
"b": [1],
"c": [2],
"d": [3],
"e": [4],
"f": [5]
},
"writable": true
}
}
}
}
}
The writable
field is all that is required - the signal will be
automatically configured to use the built-in stateWriter
as its
encoder
because the signal has a states
array. If a user sends the
VI the value c
in a write request with the name my_state_request
, it
will be encoded as 2
in the CAN signal in the outgoing message.
If the VI is configured to use the JSON output format, sending this OpenXC single-valued message to the VI via USB or UART (Bluetooth) would trigger a CAN write:
{"name": "my_state_request", "value": "a"}
With the tools from the OpenXC Python library you can send that from a terminal with the command:
openxc-control write --name my_state_request --value "\"a\""
Becuase of the way string escaping works from the command prompt, you have to
add escaped \"
characters so the tool knows you want to send a string.
Named, Transformed Written Signal¶
We want to write the same signal as Named Numeric Signal Write Request but round any values below 100 down to 0 before sending (similar to the read-only example Custom Transformed Numeric Signal).
To accomplish this, we need to know a little C - we will write a custom signal encoder to make the transformation. Here’s the JSON configuration:
{ "buses": {
"hs": {
"controller": 1,
"speed": 500000
}
},
"messages": {
"0x102": {
"bus": "hs",
"signals": {
"My_Signal": {
"generic_name": "my_openxc_measurement",
"bit_position": 5,
"bit_size": 7,
"factor": -1.0,
"offset": 1400,
"encoder": "ourRoundingWriteEncoder"
}
}
}
},
"extra_sources": [
"my_handlers.cpp"
]
}
We set the encoder
for the signal to ourRoundingWriteEncoder
, and we’ll
define that in a separate file named my_handlers.cpp
. The extra_sources
field is also set, meaning that our custom C/C++ code will be included with the
firmware build.
In my_handlers.cpp
:
/* Round the value down to 0 if it's less than 100 before writing to CAN. */
uint64_t ourRoundingWriteEncoder(CanSignal* signal, CanSignal* signals,
int signalCount, double value, bool* send) {
if(value < 100) {
value = 0;
}
// encodeSignal pulls the CAN signal definition from the CanSignal struct
// and encodes the value into the right bits of a 64-bit return value.
return encodeSignal(signal, value);
}
Signal encoders are responsible for transforming a float, string or boolean value into a 64-bit integer, to be used in the outgoing message.
Composite Write Request¶
When the app developer sends a numeric measurement to the VI, we want to send:
- 1 arbitrary CAN message with the ID
0x34
on a high speed bus connected to controller 1, with the value0x1234
. - The value sent by the developer encoded into the message ID
0x35
in a signal starting at bit 0, 4 bits wide on the same high speed bus. We don’t want this value to be writable by the app developer unless a part of these 3 writes combined. - A boolean signal in the message
0x101
on a medium speed bus connected to controller 2, starting at bit 12 and 1 bit wide. If the numeric value from the user is greater than 100, the boolean value should betrue
.
{ "name": "passthrough",
"buses": {
"hs": {
"controller": 1,
"raw_writable": true,
"speed": 500000
},
"ms": {
"controller": 2,
"speed": 125000
}
},
"messages": {
"0x35": {
"bus": "hs",
"signals": {
"My_Numeric_Signal": {
"generic_name": "my_number_signal",
"bit_position": 0,
"bit_size": 4
}
}
}
"0x101": {
"bus": "ms",
"signals": {
"My_Other_Signal": {
"generic_name": "my_value_is_over_100_signal",
"bit_position": 12,
"bit_size": 1
}
}
}
},
"commands": [
{"name": "my_command",
"handler": "handleMyCommand"}
],
"extra_sources": [
"my_handlers.cpp"
]
}
We added a commands
field, which contains an array of JSON objects with
name
and handler
fields. The name of the command, my_command
is what
app developers will send to the VI. The handler
is the name of a C++
function will define in one of the files listed in extra_sources
.
In the configuration, also note that:
- The raw CAN message that we want to send isn’t included. Since
raw_writable
is true for thehs
bus, there’s no need to define it in the configuration. - The
my_number_signal
signal doesn’t have thewritable
flag set to true (it’s omitted, and the default isfalse
). This means an app developer will not be able to send write requests formy_number_signal
directly.
In my_handlers.cpp
:
void handleMyCommand(const char* name, openxc_DynamicField* value,
openxc_DynamicField* event, CanSignal* signals, int signalCount) {
// Look up the numeric and boolean signals we need to send and abort if
// either is missing
CanSignal* numericSignal = lookupSignal("my_number_signal", signals,
signalCount);
CanSignal* booleanSignal = lookupSignal("my_value_is_over_100_signal",
signals, signalCount);
if(numericSignal == NULL || booleanSignal == NULL) {
debug("Unable to find signals, can't send trio");
return;
}
// Build and enqueue the arbitrary CAN message to be sent - note that none
// of the CAN messages we enqueue in the handler will be sent until after
// it returns - interaction with the car via CAN must be asynchronous.
CanMessage message = {0x34, 0x12345};
CanBus* bus = lookupBus(0, getCanBuses(), getCanBusCount());
if(bus != NULL) {
can::write::enqueueMessage(bus, &message);
}
// Send the numeric signal
can::write::encodeAndSendSignal(numericSignal, value,
// the last parameter is true, meaning we want to force sending
// this signal even though it's not marked writable in the
// config
true);
// For the boolean signal, send 'true' if the value sent by the user is
// greater than 100
can::write::encodeAndSendBooleanSignal(booleanSignal, value > 100, true);
}