This repository contains several CAN (Controller Area Network) C++ utilities which could simplify collecting, decoding, transcoding and transferring CAN messages to cloud.
Most of the code in the repository is designed to run on an edge device (for example, an embedded telemetry device). However, utilities like CAN DBC parser or CAN frame packet buffer can also be used on server side, thus providing some of the essential tools in IOT telemetry ecosystems.
- DBC parser
- A complete, customizable and efficient DBC parser written in C++ with full DBC syntax support for all keywords.
- Vehicle-To-Cloud Transcoder
- Edge-computing telemetric component that groups, filters, and aggregates CAN signals. Can drastically reduce the amount of data sent from the device over the network. Uses the DBC parser to read and define the CAN network.
- Download Boost and move it to your include path
The project requires only headers from Boost, so no libraries need to be built.
You can compile the example as follows:
$ g++ -std=c++20 example/example.cpp dbc/dbc_parser.cpp v2c/v2c_transcoder.cpp -I . -o can_example
can-utils
has been tested with Clang, GCC and MSVC on Windows and Linux. It requires C++20.
Build, then run without any command line arguments:
$ ./can_example
The example program parses example.dbc, generates millions of random frames, aggregates them with v2c_transcoder
, and prints the decoded raw signals to the console.
Example output:
New frame_packet (from 2121812 frames):
can_frame at t: 1683709842.116000s, can_id: 4
SOCavg: 574
can_frame at t: 1683709842.116000s, can_id: 6
RawBattCurrent: 10914
SmoothBattCurrent: 10921
BattVoltage: 32760
can_frame at t: 1683709842.516000s, can_id: 2
GPSAccuracy: 118
GPSLongitude: -106019721
GPSLatitude: 26758102
can_frame at t: 1683709842.516000s, can_id: 3
GPSAltitude: -8084
can_frame at t: 1683709842.516000s, can_id: 5
GPSSpeed: 2160
can_frame at t: 1683709842.516000s, can_id: 7
PowerState: 2
can_frame at t: 1683709842.616000s, can_id: 4
SOCavg: 163
can_frame at t: 1683709842.616000s, can_id: 6
RawBattCurrent: -27877
SmoothBattCurrent: -27827
BattVoltage: 32731
...
The signal values are raw decoded bytes, not scaled by the signal's factor or offset.
The parser can be used as follows:
custom_dbc dbc_impl; // custom class that implements your logic and data structures
bool success = can::parse_dbc(dbc_content, std::ref(dbc_impl)); // parses the DBC
// dbc_impl is now populated by the parser and can be used
The behavior of the parser is customized by user-defined callbacks invoked when parsing a DBC keyword.
Defining the following callback would print all BO_
objects (messages) in the DBC, and call add_message()
on dbc_impl
:
inline void tag_invoke(
def_bo_cpo, dbc_impl& this_,
uint32_t msg_id, std::string msg_name, size_t msg_size, size_t transmitter_ord
) {
std::cout << "New message '" << msg_name << "' with ID = " << msg_id << std::endl;
this_.add_message(msg_id, msg_name, msg_size);
}
The full list of callback function signatures, with examples, can be found here.
V2C is modeled as a node in the CAN network. It reads CAN frames as input, aggregates their values, and encodes them back into CAN frame_packets
.
To use it, initialize v2c_transcoder
and then call its transcode(t, frame)
method with frames read from the CAN socket.
transcode()
periodically returns a frame_packet
containing the aggregated can_frames
, ready to be sent over the network.
can::v2c_transcoder transcoder;
can::parse_dbc(read_file("example/example.dbc"), std::ref(transcoder));
while (true) {
// read a frame from the CAN socket
can_frame frame = read_frame();
auto t = std::chrono::system_clock::now();
auto fp = transcoder.transcode(t, frame);
if (fp) {
// send the frame_packet over the network
send_frame_packet(fp);
}
}
The transcoder's message groups, aggregation types and sampling/sending windows are customized through the DBC directly:
EV_ V2CTxTime: 0 [0|60000] "ms" 2000 1 DUMMY_NODE_VECTOR1 V2C;
EV_ GPSGroupTxFreq: 0 [0|60000] "ms" 600 11 DUMMY_NODE_VECTOR1 V2C;
EV_ EnergyGroupTxFreq: 0 [0|60000] "ms" 500 13 DUMMY_NODE_VECTOR1 V2C;
BA_ "AggType" SG_ 7 PowerState "LAST";
BA_ "AggType" SG_ 4 SOCavg "LAST";
BA_ "AggType" SG_ 6 RawBattCurrent "AVG";
BA_ "AggType" SG_ 6 SmoothBattCurrent "AVG";
A more in-depth explanation can be found here.
When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
You may merge a Pull Request once you have the sign-off from other developers, or you may request the reviewer to merge it for you.
Copyright (c) 2001-2023 Mireo, EU
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
-
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
-
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
-
Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Maintained and authored by Mireo.