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.

7-4-1-1

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

7-4-1-2

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.

sequence

The detailed steps are as follows.

  1. 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
    
  2. Send a VSS signal
    After step 1, PAL converts the received data to a VSS signal and notifies Kura.
  3. Read the VSS signal
    Use the TUI to get the value. If you have not modified pocket-pal, the path to get is Vehicle.Speed.
  4. Check the returned VSS signal
    The TUI returns the value of Vehicle.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/activate
    

    Alternatively, 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