“Shanghai interchange” by Denys Nevozhai on Unsplash“Shanghai interchange” by Denys Nevozhai

CAN BUS tools

Cannes (named after the city in France 🇫🇷) is an elixir WIP-wrapper around cantools which is written in python. The wrapper makes it easier to handle CAN messages in Elixir projects. Individual functionalities of cantools are implemented using Porcelain. Porcelain implements a saner approach to launching and communicating with external OS processes from Elixir. Built on top of Erlang’s ports, it provides richer functionality and simpler API.

Why a wrapper for CAN?

As part of my master thesis (A Man-In-The-Middle Approach Towards Dataflow Management for OBD-II Interfaces in Vehicles) I had to deal with a variety of CAN messages. Since I wanted to develop my approach using Elixir, I needed a simple way to encode and decode different CAN messages in Elixir.

hex.pm version hex.pm downloads hex.pm licence


INFO:
Cannes is still under development and by far not complete. Feel free to contribute.
See General Informations for more about this project.

Prerequisites

  • Install Python v3.x
  • Install Elixir v1.11+
  • Setup a virtualenv and activate it
  • Run pip install -r requirements.txt to install the python dependencies
  • Run mix deps.get to install the Hex dependencies

Installation

To use Cannes in your Mix projects, first add Cannes as a dependency.

1
2
3
4
5
def deps do
[
  {:cannes, "~> 0.0.5"}
]
end

Components of the library

“Brief general overview of the internal components.

Dumper.ex

The dumper module is for communicating with linux can sockets over candump.

In order to spawns a candump process on the given interface you can use the following snippet.

1
2
3
4
5
6
iex> Cannes.Dumper.start("vcan0")
%Porcelain.Process{
  err: nil,
  out: #Function<63.104660160/2 in Stream.unfold/2>,
  pid: #PID<0.212.0>
}

There is also a function for stopping a running candump process carefully and for checking if the candump process is still running.

If you want to get a stream of formatted can messages you can do this by calling the following function.

1
2
3
4
5
6
iex> Cannes.Dumper.get_formatted_stream(proc)
#Stream<[
  enum: #Function<63.104660160/2 in Stream.unfold/2>,
  funs: [#Function<51.104660160/1 in Stream.reject/2>,
#Function<38.104660160/1 in Stream.map/2>]
]>
You can also get an unformatted string which will be computationally more efficient (e.g. just for storing) with the get_stream function.

This raw stream can also be formatted afterwards by passing the stored binary to format_candump_string.

1
2
3
4
5
6
7
iex> Cannes.Dumper.format_candump_string("(1398128227.045337) can0 133#0000000098")
%{
  data: <<0, 0, 0, 0, 0, 0, 0, 9>>,
  identifier: <<1, 51>>,
  interface: "can0",
  timestamp: 1398128227.045337
}

Compostor.ex

This module is the counterpart to the Cannes.Dumper. With the help of the included functions it is possible to send CAN messages.

Mock.ex

The Cannes.Mock module provices some useful functions for simulating simple CAN-messages. You can get an infinite stream of formatted can messages by calling the file_stream function. Currently we use a simple logging file as blueprints for the messages.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
iex> stream = Cannes.Mock.file_stream()
#Function<63.104660160/2 in Stream.unfold/2>
iex> Enum.take(stream, 5)
[
  %{
    data: <<0, 0, 0, 0, 0, 208, 50, 0>>,
    identifier: <<1, 102>>,
    interface: "can0",
    timestamp: 1398128223.803317
  },
  %{
    data: <<0, 0, 0, 0, 0, 0, 0, 0>>,
    identifier: <<1, 88>>,
    interface: "can0",
    timestamp: 1398128223.804583
  },
  %{
    data: <<0, 0, 0, 5, 80, 1, 8, 0>>,
    identifier: <<1, 97>>,
    interface: "can0",
    timestamp: 1398128223.804828
  },
  %{
    data: <<0, 0, 1, 0, 144, 161, 65, 0>>,
    identifier: <<1, 145>>,
    interface: "can0",
    timestamp: 1398128223.805039
  },
  %{
    data: <<0, 0, 0, 0, 0, 0, 0, 0>>,
    identifier: <<1, 142>>,
    interface: "can0",
    timestamp: 1398128223.80535
  }
]

iex> Cannes.Mock.file_stream(true) |> Enum.take(2)
[
  %{
    data: <<0, 0, 0, 0, 208, 50, 0, 9>>,
    identifier: <<1, 102>>,
    interface: "can0",
    timestamp: 1611064041
  },
  %{
    data: <<0, 0, 0, 0, 0, 0, 0, 10>>,
    identifier: <<1, 88>>,
    interface: "can0",
    timestamp: 1611064041
  }
]

Tools.ex

Cannes.Tools facilitates a usefull wrapper for the python library called cantool.

Using the decode_message function it is possible to decode given signal data as a message with given frame ID or given name frame_id_or_name. The function returns a dictionary of signal name value entries. If decode_choices is false, scaled values are not converted to selection strings (if any). If the scaling is false, no scaling of signals is performed.

1
2
3
4
5
6
7
8
iex> Cannes.Tools.decode_message(2024, <<0x04, 0x41, 0x0C, 0x02, 0x6A, 0x00, 0x00, 0x00>>)
%{
  'ParameterID_Service01' => 'S1_PID_0C_EngineRPM',
  'S1_PID_0C_EngineRPM' => 154.5,
  'length' => 4,
  'response' => 4,
  'service' => 'Show current data'
}


The counterpart to this is the encode_message function. It encodes given signal data as a message with the given frame ID or name frame_id_or_name. The return value is a dictionary of signal name value entries. If scaling is false, no scaling of the signals is performed. If padding is true, unused bits are encoded as 1. If strict is true, all signal values must be within their allowed ranges or an exception will be thrown.

1
2
iex> Cannes.Tools.encode_message(2024, %{"ParameterID_Service01" => "S1_PID_0C_EngineRPM", "S1_PID_0C_EngineRPM" => 154.5, "length" => 4, "response" => 4, "service" => "Show current data "})
<<4, 65, 12, 2, 106, 0, 0, 0>>