Part2 Practice
7. Developing a custom PAL to connect CAN and Kura 🚗
7-1. Introduction
This page explains how readers can develop their own PAL to connect CAN and Kura. This page assumes the following knowledge:
7-2. Set Up the Development Environment
7-2-1. Clone the yoriito-tutorial repository
This tutorial uses sample PAL called pocket-pal.
yoriito-tutorial repository contains pocket-pal, so at first, please clone yoriito-tutorial. pocket-pal is located in the pocket-pal directory.
$ git clone https://github.com/yoriito/yoriito-tutorial.git
pocket-pal uses vcan device, so you need a vcan (Virtual CAN interface), so create it as follows:
$ bash yoriito-tutorial/pocket-pal/script/setup_vcan.sh
This script performs the following operations, so you can also execute them manually:
# load vcan module
$ sudo modprobe vcan
# create virtual CAN device (vcan0) as a network device
$ sudo ip link add dev vcan0 type vcan
# set status to `UP`
$ sudo ip link set up vcan0
7-2-2. Start the development Docker container
yoriito-tutorial provides a development Docker container for yoriito project.
From the root of yoriito-tutorial repository, launch VS Code and start the development Docker container for the pocket-pal directory by following the steps described there.
When you run shell commands in the following location in VS Code, they will be executed inside the development Docker container.
Warning
⚠️ Unless otherwise noted, the following assumptions apply to subsequent code blocks:
- Commands are executed inside this development container.
- The current directory is /work/pocket-pal in the cloned repository.
7-3. Develop a PAL
7-3-1. Design of pocket-pal-cc
Before staring your custom PAL, let's take a look at the design of pocket-pal-cc. pocket-pal-cc consists of three C++ source files and CMakeLists.txt as follows.
$ tree -L 1 pocket-pal-cc
pocket-pal-cc
├── build
├── can_broker.h
├── CMakeLists.txt
├── main.cpp
└── vss_broker.h
2 directories, 4 files
Their responsibilities are as follows.
| file | responsibility |
|---|---|
main.cpp |
- Implements the entry point of pocket-pal - Receives CAN from vcan, converts it to VSS, and writes it to Kura - Receives VSS from Kura, converts it to CAN, and writes it to vcan - Manages the threads that execute the above functions |
can_broker.h |
- Provides write functions to vcan - Provides read functions from vcan |
vss_broker.h |
- Provides write functions to Kura - Provides subscribe functions for specific VSS data in Kura |
CMakeLists.txt |
- Describes CMake configuration for pocket-pal-cc - Adds header search paths - Links libraries |
build |
- Working directory for building |
7-3-2. Build and run pocket-pal-cc
Now you understand how pocket-pal-cc works, let's build and run it. You can build and run it with the following steps:
# Build
$ mkdir pocket-pal-cc/build && cd pocket-pal-cc/build
$ cmake ..
$ make
# Run
$ ./pocket-pal
This sample pal runs in a loop, reading CAN frames from vcan, converting them to VSS, and writing them to Kura. For more details, see the following diagram.
7-3-3. Modify pocket-pal-cc
Modify CAN → VSS conversion
Here we modify the implementation that receives a CAN signal from vcan, converts it to VSS, and writes it to Kura. This process is implemented in the main.cpp(link).
void on_can(std::shared_ptr<CANBroker> can_broker){
while(running) {
auto frame = can_broker.get()->read();
if (!frame.has_value()){
continue;
}
auto valid_frame = frame.value();
std::cout << "[CAN][RECV]" << "id: " << valid_frame.can_id << ", data: " << valid_frame.data << std::endl;
VSSBroker::write<double>(VEHICLE_SPEED, 100.f, true, true);
std::cout << "[VSS][SEND]" << "path: " << VEHICLE_SPEED << ", payload: " << 100.0f << std::endl;
}
}
As shown above, the data received from vcan is stored in the variable valid_frame. The code writes the value 100.f to Kura with the path VEHICLE_SPEED (=Vehicle.Speed).
For example, if you want to modify the implementation so that when the received CAN ID is 0x100, the first byte of the CAN payload is treated as vehicle speed and written to Kura, it will look like this.
void on_can(std::shared_ptr<CANBroker> can_broker){
while(running) {
auto frame = can_broker.get()->read();
if (!frame.has_value()){
continue;
}
auto valid_frame = frame.value();
std::cout << "[CAN][RECV]" << "id: " << valid_frame.can_id << ", data: " << valid_frame.data << std::endl;
- VSSBroker::write<double>(VEHICLE_SPEED, 100.f, true, true);
- std::cout << "[VSS][SEND]" << "path: " << VEHICLE_SPEED << ", payload: " << 100.0f << std::endl;
+ if (valid_frame.can_id == 0x100) {
+ auto speed = static_cast<double>(valid_frame.data[0]);
+ VSSBroker::write<double>(VEHICLE_SPEED, speed, true, true);
+ std::cout << "[VSS][SEND]" << "path: " << VEHICLE_SPEED << ", payload: " << speed << std::endl;
+ }
}
}
When you actually develop a PAL, you will update VSS data in Kura based on the received CAN frame in this way.
Modify VSS → CAN conversion
Here we modify the implementation that receives VSS from Kura, converts it to CAN, and writes it to vcan. This process is implemented in the main.cpp(link).
As shown in above link, the data received from Kura is stored in the variable data_point. The code writes a hard-coded payload to vcan with CAN ID 256.
For example, if you want to modify the implementation so that when the received VSS path is Vehicle.Speed, you send a CAN frame with ID 255 to vcan,
using the received vehicle speed as the first byte of the payload, it will look like this.
void on_vss(std::shared_ptr<CANBroker> can_broker){
...
- // TODO: users need to build any can frame from data_point
- can_broker.get()->write(256, {0x27, 0x10, 0x1F, 0x40, 0x00, 0x00, 0x00, 0x00});
- std::cout << "[CAN][SEND]" << "id: " << 256 << ", payload" << "0x27, 0x10, 0x1F, 0x40, 0x00, 0x00, 0x00, 0x00" << std::endl;
+ if (path == "Vehicle.Speed") {
+ auto speed = static_cast<char>(value);
+ can_broker.get()->write(255, {speed, 0x10, 0x1F, 0x40, 0x00, 0x00, 0x00, 0x00});
+ std::cout << "[CAN][SEND]" << "id: " << 255 << ", payload" << speed << ", 0x10, 0x1F, 0x40, 0x00, 0x00, 0x00, 0x00" << std::endl;
+ }
}
}
}
When you actually develop a PAL, you will send CAN frames to vcan based on the received VSS data in this way.
7-4. Verify the Behavior
This section explains how to verify the behavior by writing CAN data to pocket-pal-cc or writing VSS data and checking the result.
7-4-1. Connect vshot
To verify the behavior of pocket-pal-cc, you need an application that reads and writes VSS to Kura. For this purpose, a simple CLI command (vshot) is provided.
You can use this CLI with the following commands.
# build
$ git clone https://github.com/yoriito/kura.git
$ cd kura/client/vshot
$ make build
$ make install
# run
$ vshot tui # Use TUI mode
$ vshot -i # Use interactive mode
$ vshot get-current Vehicle.Speed # Get the current value of Vehicle.Speed
$ vshot set-current Vehicle.Speed 100 # Set the current value of Vehicle.Speed to 100
For example, when you run it as a TUI, the following screen is displayed.

When you press Go to Headless Mode, the following screen is displayed.

To write VSS to Kura, set the path and value in the set_request field and press the set button, as shown in the video.
7-4-2. Write a CAN frame to vcan and verify that it is written to Kura as VSS
First, run pocket-pal-cc inside the development Docker container:
$ ./pocket-pal-cc/build/pocket-pal
Then, follow these steps.
The detailed steps are as follows.
- Send a CAN frame
Run the following command in a shell. You can run this either inside the development Docker container or on the host OS.
$ cansend vcan0 123#1122334455667788 - Send a VSS signal
After step 1, PAL converts the received data to a VSS signal and notifies Kura. - Read the VSS signal
Use the TUI to get the value. If you have not modified pocket-pal, the path to get isVehicle.Speed. - Check the returned VSS signal
The TUI returns the value ofVehicle.Speed. If you have not modified pocket-pal, you should get 100.
7-4-3. Write VSS to Kura and verify that a CAN frame is written to vcan
Refer to Write a CAN frame to vcan and verify that it is written to Kura as VSS and write VSS data to Kura from the TUI.
You can monitor vcan with the following command (this can also be run either inside the development Docker container or on the host OS).
$ candump vcan0
7-5. Troubleshooting
7-5-1. No such device error occurs when running pocket-pal-cc
- Symptom
$ ./pocket-pal-cc/build/pocket-pal ioctl SIOCGIFINDEX failed, errno=19 (No such device) terminate called after throwing an instance of 'std::runtime_error' what(): Failed to get interface index Aborted (core dumped) - Cause
The vcan device has not been created. - Countermeasure
Create vcan by link.
7-5-2. Cannot install required pip packages for the TUI
- Symptom
$ python3 -m pip install textual grpcio protobuf textual_autocomplete error: externally-managed-environment × This environment is externally managed ╰─> To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install. If you wish to install a non-Debian-packaged Python package, create a virtual environment using python3 -m venv path/to/venv. Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make sure you have pypy3-venv installed. If you wish to install a non-Kali-packaged Python application, it may be easiest to use pipx install xyz, which will manage a virtual environment for you. Make sure you have pipx installed. For more information, refer to the following: * https://www.kali.org/docs/general-use/python3-external-packages/ * /usr/share/doc/python3.13/README.venv note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages. hint: See PEP 668 for the detailed specification. - Cause
From Python 3.12 onward, installing packages with pip is assumed to be done in a venv environment. -
Countermeasure
Use a venv as follows.$ python3 -m venv . $ source bin/activateAlternatively, if you are fine with installing packages into the global system, you can avoid the problem with the following command.
$ python3 -m pip install textual grpcio protobuf textual_autocomplete --break-system-packages
7-5-3. When starting the TUI, VISS server(localhost:50052) is not running. is displayed
- Symptom
$ ./main.py VISS server(localhost:50052) is not running. - Cause
The development Docker container in the pocket-pal repository is not running. - Countermeasure
Start the development Docker container by referring to link
7-5-4. Cannot use vcan on WSL2
- Symptom
$ sudo modprobe vcan modprobe: FATAL: Module vcan not found in directory /lib/modules/.... - Cause
The standard WSL2 kernel does not include the vcan module. - Countermeasure
Resolve the issue by referring to the following information.
Note that the location of configuration items may have changed.
Within this project, the following procedure has been confirmed to work.
git clone --depth 1 -b linux-msft-wsl-6.6.y https://github.com/microsoft/WSL2-Linux-Kernel.git cd ~/WSL2-Linux-Kernel make mrproper zcat /proc/config.gz > .config printf "CONFIG_CAN=m\nCONFIG_CAN_RAW=m\nCONFIG_CAN_BCM=m\nCONFIG_CAN_GW=m\nCONFIG_CAN_DEV=m\nCONFIG_CAN_VCAN=m\n" >> .config make olddefconfig make modules -j$(nproc) sudo make modules_install