In part 1 of this series I introduced you to the Controller Area Network (CAN) found on vehicles today. In part 2, how to hook up a Raspberry Pi to CAN. Then in part 3, I discussed how to use the pre-built utilities to both monitor and reverse engineer the packetry that travels on the bus,.
Now let’t discuss how to write your own code to receive and send packets to the various devices in your car. We are going to use SocketCAN, an extension to Berkeley sockets API that comes pre-installed on the Raspberry Pi.
SocketCAN is open source contributed to the Linux kernel by Volkswagen Research. Form the programmers point of view SocketCAN essentially adds a new protocol family to Sockets called PF_CAN. So you have already done any network programming, this API will be familiar to you.
SocketCAN has official documentation and the source code is available on GitHub. There are a number of great articles on how to get started including this one by Craig Peacock.
While I am going to use C/C++ API in this article, there is also a Python interface to SocketCAN available.
Opening a Socket
We will use the standard Berkeley sockets interface functions: socket(), ioctl() and bind(). We start by handing the socket() function the protocol family PF_CAN, and the protocol CAN_RAW. This method allows us to view all the frames on the bus as raw data. There is also a datagram mode, SOCK_DGRAM
, I’ll discuss that in a later article.
The we need to retrieve the index number of the CAN interface using SIOCGIFINDEX ioctl(). Then we can use bind() to hook up the socket.
In this example, we find the index number of can0 and bind our file descriptor.
// create a CAN socket
int fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
// Get the index number of the network interface
struct ifreq ifr;
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr);
struct sockaddr_can addr;
// fill out Interface request structure
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(fd, (struct sockaddr *)&addr, sizeof(addr))
An alternative way to fill in the can_ifindex
is to use the if_nametoindex()
function:
// Get the index number of the network interface
unsigned int ifindex = if_nametoindex("can0");
if (ifindex == 0) fail!
When you are done, you can close the socket with the usual close() API call.
close(fd);
Transferring Data
At this point raw read/write calls to the CAN socket. The data on CAN is transferred using the can_frame structure found in the can.h include. The important parts of can_frame are:
can_id - the Frame ID
can_dlc - frame payload length in bytes (0..8)
data - up to 8 bytes of frame data.
These frames are broadcast to all device on the network. Some of the device will care about the frame with a specific can_id others will ignore it. The can_id is also used for arbitration, lower numbers have higher priority.
We can use the standard socket read() function to receive data from the CAN bus.
struct can_frame frame;
int nbytes = read(fd, &frame, sizeof(struct can_frame));
and write to send data.
// tell headlights to flash
struct can_frame frame;
frame.can_id = 0x208;
frame.can_dlc = 7;
memcpy(frame.data,
{0x08, 0xA2, 0x65, 0x1E, 0x1E, 0x00, 0x2D}, 7);
write(fd, &frame, sizeof(struct can_frame));
It’s all fairly simple.
Some sample code
I posted a piece of C++ sample code on GitHub. The C++ class CANClient will open the specified socket, in this case can0. Then it will spawn a thread which sits in a loop that sleeps and waits for frame to arrive. It will then call a callback to print out a dump of the packet.
Assuming that you previously setup your Pi with git-core, and cmake then please download and try out my sample code.
git clone https://github.com/vinthewrench/cantest1.git
cd cantest1
cmake .
make
# this will leave a executable in bin/cantest1
Running the sample will produce something like the following.
bin/cantest
208 [7] 08 A2 65 1E 1E 00 2D ..e...-
293 [8] 10 00 00 20 00 FF FF FF ........
3D9 [7] 00 0A 0A 0A 0A 0A FF .......
1E1 [8] 00 00 FF FF 00 00 00 00 ........
3E6 [3] 0B 35 31
....
Too much noise
The CAN bus is often chatty with items that your application might not care about. SocketCAN has the ability to specify filters to control the reception of frames. This is explained in section 4.1.1 of the documentation. The CAN_RAW_FILTER has a lot of features that are beyond the scope of this post. But for now here is a simple example of a filter that will trigger only on packets with a frame_id of 3E6.
struct can_filter filter;
filter.can_id = 0x03E6;
filter.can_mask = 0x7ff
setsockopt(fd, SOL_CAN_RAW, CAN_RAW_FILTER,
&filter, sizeof(filter));
Now you know something
Now you know a few things about the data networks running on your vehicle. You know how to hook up a Raspberry to it and enough to get started coding. In a future article I hope to talk about how the On-board diagnostics (OBD) protocol works and how to write code to get information and very carefully change some parameters.
good article on socketcan https://copperhilltech.com/what-is-socketcan/