ecu-tests/docs/21_yocto_image_for_raspberry_pi.md

25 KiB
Raw Blame History

Yocto Image for Raspberry Pi — ECU Test Framework as a Bench Appliance

This guide explains how to build a custom Linux distribution with the Yocto Project so a Raspberry Pi is the test bench: power it on, it boots, the test framework runs the configured suites against a connected MUM and ECU, and reports land in a known location (or get pushed to a server). No PC in the loop.

If you only want to run the framework on a stock Raspberry Pi OS install, you're looking at docs/09_raspberry_pi_deployment.md. If you want a pre-baked Pi OS image (still Debian, just snapshotted with the framework already installed), see docs/10_build_custom_image.md. This document is about Yocto specifically — building a minimal, hardened, reproducible OS from sources around the framework.


1. Why Yocto vs. Raspberry Pi OS?

Concern Raspberry Pi OS (Debian) Yocto
First image up Hours First build: a day. Subsequent builds: hours.
Image size ~24 GB minimum (Lite) ~150500 MB realistic
Reproducibility Snapshot of apt state at image time Full source pinning via layer revisions
Auditing "what's installed" dpkg -l of a moving target Single manifest, version-pinned
Hardening / removing surface Have to disable / uninstall Just don't include the recipe
Boot time to test 3060 s 515 s with a tuned image
Building a fleet Re-snapshot per change Rebuild image, push artifact
Build host requirements Pi + SD card Linux build host with ~100 GB free and ~16 GB RAM ideally

Pick Yocto when the Pi is a deployed appliance, not a workstation — a permanent bench, a HIL rack, a customer-shipped test fixture. For day-to-day developer work the Pi OS path is fine.


2. Architecture

                                ┌────────────────────────────────┐
                                │  Raspberry Pi (Yocto image)    │
                                │                                │
                ┌── Ethernet ───┤  192.168.7.1   (host on RNDIS) │
                │               │                                │
┌───────────────┴──────────┐    │  ecu-test-framework systemd    │
│  MUM @ 192.168.7.2       │    │   service:                     │
│  USB-RNDIS (or wired)    │    │     pytest -m "hardware and    │
└──────────────────────────┘    │            mum and not slow"   │
                                │                                │
                                │  /opt/ecu-tests/   (the repo)  │
                                │  /opt/ecu-tests/.venv/         │
                                │                                │
                ┌── USB-serial ─┤  /dev/ttyUSB0  (Owon PSU)      │
                │               │                                │
┌───────────────┴──────────┐    │  /var/log/ecu-tests/           │
│  Owon PSU                │    │     report.html, junit.xml,    │
└──────────────────────────┘    │     summary.md                 │
                                │                                │
                                │  rsync/scp/HTTP push of /var/  │
                                │  log/ecu-tests/ to a server    │
                                └────────────────────────────────┘

Key choices made by this document:

  • meta-raspberrypi as the BSP for raspberrypi4-64 (or -3, -cm4, depending on your hardware).
  • meta-openembedded for Python + general userspace.
  • A new layer meta-ecu-tests holds: the framework recipe, recipes for the non-PyPI Python deps, the image recipe, and the systemd unit.
  • systemd init system (Yocto's core-image-minimal defaults to sysvinit; we override to systemd).
  • Pinned Yocto release: scarthgap (LTS, May 2024). Pick a current LTS at build time; this doc shows scarthgap.

3. Build-host prerequisites

A Linux machine (Ubuntu 22.04 LTS or Debian 12 are the smoothest; WSL2 works but is slower and consumes a lot of disk).

Resources:

  • 100 GB free disk (the first bitbake run downloads sources and builds toolchains)
  • 16 GB RAM ideal, 8 GB workable
  • Multi-core CPU; expect 14 h for the first image build

Packages (Ubuntu 22.04):

sudo apt update
sudo apt install -y \
    gawk wget git diffstat unzip texinfo gcc build-essential chrpath socat \
    cpio python3 python3-pip python3-pexpect xz-utils debianutils iputils-ping \
    python3-git python3-jinja2 python3-subunit zstd liblz4-tool file locales \
    libacl1
sudo locale-gen en_US.UTF-8

Make sure your user can run docker if you plan to use the kas-container shortcut; not required for the manual bitbake flow shown here.


4. Layer layout

~/yocto/
├── poky/                    # Yocto core, ~3 GB
├── meta-openembedded/       # community python/network/etc. layers
├── meta-raspberrypi/        # Raspberry Pi BSP
├── meta-ecu-tests/          # ← we create this
│   ├── conf/
│   │   └── layer.conf
│   ├── recipes-ecu-tests/
│   │   ├── ecu-test-framework_git.bb
│   │   ├── ecu-test-framework/
│   │   │   ├── ecu-test-framework.service
│   │   │   ├── ecu-test-runner.sh
│   │   │   └── push-reports.sh
│   │   └── python3-melexis/
│   │       ├── python3-pylin_1.2.0.bb
│   │       ├── python3-pymumclient_1.2.0.bb
│   │       └── python3-pylinframe_1.2.0.bb
│   ├── recipes-python/
│   │   └── python3-ldfparser_<ver>.bb
│   └── recipes-images/
│       └── ecu-tests-image.bb
└── build/                   # bitbake's TMPDIR (huge)

5. Setting up the build environment

5.1 Clone Yocto + BSP + needed layers

mkdir -p ~/yocto && cd ~/yocto

BRANCH=scarthgap

git clone -b $BRANCH https://git.yoctoproject.org/git/poky
git clone -b $BRANCH https://git.openembedded.org/meta-openembedded
git clone -b $BRANCH https://git.yoctoproject.org/git/meta-raspberrypi

5.2 Bootstrap the build directory

source poky/oe-init-build-env build
# you are now in ~/yocto/build/

5.3 Tell bitbake which layers exist

conf/bblayers.conf should look like:

BBLAYERS ?= " \
  ${TOPDIR}/../poky/meta \
  ${TOPDIR}/../poky/meta-poky \
  ${TOPDIR}/../poky/meta-yocto-bsp \
  ${TOPDIR}/../meta-openembedded/meta-oe \
  ${TOPDIR}/../meta-openembedded/meta-python \
  ${TOPDIR}/../meta-openembedded/meta-networking \
  ${TOPDIR}/../meta-raspberrypi \
  ${TOPDIR}/../meta-ecu-tests \
"

(meta-ecu-tests will be created in §6 — bitbake will warn until it exists, that's fine.)

5.4 Configure the build target

conf/local.conf — append/edit:

MACHINE = "raspberrypi4-64"

DISTRO = "poky"

# Init manager
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME:init_manager = "systemd"
VIRTUAL-RUNTIME:initscripts = ""
DISTRO_FEATURES_BACKFILL_CONSIDERED += "sysvinit"

# We want SSH for first-boot diagnosis
EXTRA_IMAGE_FEATURES ?= "debug-tweaks ssh-server-openssh"

# Make sure Python 3 ends up in the image
IMAGE_INSTALL:append = " python3 python3-modules"

# Speed up downloads and rebuilds by sharing caches
DL_DIR ?= "${TOPDIR}/downloads"
SSTATE_DIR ?= "${TOPDIR}/sstate-cache"
BB_NUMBER_THREADS = "${@oe.utils.cpu_count()}"
PARALLEL_MAKE = "-j${@oe.utils.cpu_count()}"

# Raspberry Pi specifics
ENABLE_UART = "1"            # serial console on UART
RPI_USE_U_BOOT = "0"
DISABLE_RPI_BOOT_LOGO = "1"

For a Raspberry Pi 3, change MACHINE = "raspberrypi3-64". For Compute Module 4: MACHINE = "raspberrypi-cm4". Each lists in meta-raspberrypi/conf/machine/.


6. Create meta-ecu-tests

6.1 Skeleton

cd ~/yocto
mkdir -p meta-ecu-tests/{conf,recipes-ecu-tests,recipes-python,recipes-images}
mkdir -p meta-ecu-tests/recipes-ecu-tests/ecu-test-framework
mkdir -p meta-ecu-tests/recipes-ecu-tests/python3-melexis

meta-ecu-tests/conf/layer.conf:

BBPATH .= ":${LAYERDIR}"
BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \
            ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "ecu-tests"
BBFILE_PATTERN_ecu-tests = "^${LAYERDIR}/"
BBFILE_PRIORITY_ecu-tests = "10"
LAYERSERIES_COMPAT_ecu-tests = "scarthgap"

LAYERDEPENDS_ecu-tests = "core meta-python openembedded-layer raspberrypi"

6.2 Recipe — the framework itself

meta-ecu-tests/recipes-ecu-tests/ecu-test-framework_git.bb:

SUMMARY = "ECU Test Framework (pytest-based MUM/PSU bench runner)"
DESCRIPTION = "Hardware-in-the-loop test suite for the 4SEVEN ALM ECU."
LICENSE = "CLOSED"
LIC_FILES_CHKSUM = ""

SRC_URI = " \
    git://your-git-host/ecu-tests.git;branch=main;protocol=https \
    file://ecu-test-framework.service \
    file://ecu-test-runner.sh \
    file://push-reports.sh \
"

SRCREV = "${AUTOREV}"
PV = "0.1+git${SRCPV}"
S = "${WORKDIR}/git"

inherit systemd

SYSTEMD_SERVICE:${PN} = "ecu-test-framework.service"
SYSTEMD_AUTO_ENABLE = "enable"

RDEPENDS:${PN} = " \
    python3-pytest \
    python3-pytest-html \
    python3-pytest-cov \
    python3-pytest-xdist \
    python3-pyserial \
    python3-pyyaml \
    python3-ldfparser \
    python3-pylin \
    python3-pymumclient \
    python3-pylinframe \
    bash \
"

do_install() {
    install -d ${D}/opt/ecu-tests
    cp -a ${S}/* ${D}/opt/ecu-tests/
    # Strip any committed venv / cache / reports
    rm -rf ${D}/opt/ecu-tests/.venv ${D}/opt/ecu-tests/.pytest_cache \
           ${D}/opt/ecu-tests/reports/*
    install -d ${D}/var/log/ecu-tests
    install -d ${D}/etc/ecu-tests
    install -m 0755 ${WORKDIR}/ecu-test-runner.sh ${D}/opt/ecu-tests/
    install -m 0755 ${WORKDIR}/push-reports.sh   ${D}/opt/ecu-tests/
    install -d ${D}${systemd_system_unitdir}
    install -m 0644 ${WORKDIR}/ecu-test-framework.service \
                    ${D}${systemd_system_unitdir}/
}

FILES:${PN} = " \
    /opt/ecu-tests \
    /var/log/ecu-tests \
    /etc/ecu-tests \
    ${systemd_system_unitdir}/ecu-test-framework.service \
"

Replace git://your-git-host/ecu-tests.git;branch=main;protocol=https with your actual remote. For an air-gapped build, ship the repo as a tarball: SRC_URI = "file://ecu-tests.tar.gz" and place it next to the recipe.

6.3 Recipe — runner script

meta-ecu-tests/recipes-ecu-tests/ecu-test-framework/ecu-test-runner.sh:

#!/bin/sh
set -eu

REPO=/opt/ecu-tests
LOG=/var/log/ecu-tests
RUN_TS=$(date -u +%Y%m%dT%H%M%SZ)
OUT="$LOG/$RUN_TS"
mkdir -p "$OUT"

cd "$REPO"

# Marker selection lives in /etc/ecu-tests/marker (a single line, e.g.:
#   hardware and mum and not slow
# Defaults to a safe non-slow MUM run.
MARKER=$(cat /etc/ecu-tests/marker 2>/dev/null || echo "hardware and mum and not slow")

ECU_TESTS_CONFIG=/etc/ecu-tests/test_config.yaml \
python3 -m pytest -m "$MARKER" -v \
    --junitxml="$OUT/junit.xml" \
    --html="$OUT/report.html" --self-contained-html \
    --tb=short 2>&1 | tee "$OUT/run.log" || true

# Symlink "latest" for convenience
ln -sfn "$RUN_TS" "$LOG/latest"

# Optional: rsync to a server. Reads RSYNC_DEST from
# /etc/ecu-tests/push.env. Silently no-ops if unset.
. /etc/ecu-tests/push.env 2>/dev/null || true
[ -n "${RSYNC_DEST:-}" ] && /opt/ecu-tests/push-reports.sh "$OUT" "$RSYNC_DEST" || true

push-reports.sh is a thin rsync wrapper — left as an exercise for your network setup (or replace with curl to an HTTP collector, or mosquitto_pub to MQTT — whatever your infra prefers).

6.4 Recipe — systemd unit

meta-ecu-tests/recipes-ecu-tests/ecu-test-framework/ecu-test-framework.service:

[Unit]
Description=ECU Test Framework one-shot run
After=network-online.target dev-ttyUSB0.device
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/opt/ecu-tests/ecu-test-runner.sh
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Pair this with a .timer if you want periodic runs, or leave as a one-shot triggered by reboot or systemctl start ecu-test-framework.service over SSH.

For continuous runs (every N minutes), add meta-ecu-tests/recipes-ecu-tests/ecu-test-framework/ecu-test-framework.timer:

[Unit]
Description=Run ECU tests every 30 minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=30min
Unit=ecu-test-framework.service

[Install]
WantedBy=timers.target

…and add it to SYSTEMD_SERVICE:${PN} in the recipe.

6.5 Recipe — python3-ldfparser

ldfparser is on PyPI but isn't in stock OpenEmbedded. Add a minimal recipe at meta-ecu-tests/recipes-python/python3-ldfparser_0.27.0.bb (update the version):

SUMMARY = "Pure-Python LDF parser (LIN Description File)"
HOMEPAGE = "https://github.com/c4deszes/ldfparser"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=<fill-in-md5>"

SRC_URI = "https://files.pythonhosted.org/packages/source/l/ldfparser/ldfparser-${PV}.tar.gz"
SRC_URI[sha256sum] = "<fill-in-sha256>"

S = "${WORKDIR}/ldfparser-${PV}"

inherit setuptools3 pypi

RDEPENDS:${PN} = "python3-lark python3-bitstruct"

Run bitbake -c devshell python3-ldfparser and use devtool to compute the hashes if you don't already have them, or fetch them with:

pip download ldfparser==0.27.0 --no-deps -d /tmp/ldfparser
sha256sum /tmp/ldfparser/ldfparser-0.27.0.tar.gz

6.6 Recipes — Melexis non-PyPI packages

pylin, pymumclient, and pylinframe ship inside the Melexis IDE installer; they're not on PyPI and you must source them from a legally-licensed Melexis install. Each gets a recipe that consumes a pre-staged tarball under ${BSPDIR}/downloads/.

Stage once on the build host:

# On a machine that has Melexis IDE installed
MELEXIS_SITE="/mnt/c/Program Files/Melexis/Melexis IDE/plugins/com.melexis.mlxide.python_1.2.0.202408130945/python/Lib/site-packages"
mkdir -p ~/yocto/downloads
for pkg in pylin pymumclient pylinframe; do
    tar -czf ~/yocto/downloads/${pkg}-1.2.0.tar.gz -C "$MELEXIS_SITE" $pkg
done

meta-ecu-tests/recipes-ecu-tests/python3-melexis/python3-pylin_1.2.0.bb:

SUMMARY  = "Melexis pylin — proprietary, not redistributable"
DESCRIPTION = "Vendored copy of the pylin package shipped with Melexis IDE."
LICENSE  = "Proprietary"
LIC_FILES_CHKSUM = ""

# License Mode:
# This recipe ships proprietary code. Yocto will refuse to build unless
# you whitelist it. In your conf/local.conf:
#   LICENSE_FLAGS_ACCEPTED += "commercial_pylin commercial_pymumclient commercial_pylinframe"
LICENSE_FLAGS = "commercial_pylin"

# The tarball must be pre-staged at DL_DIR/pylin-${PV}.tar.gz
SRC_URI = "file://pylin-${PV}.tar.gz"
SRC_URI[sha256sum] = "<fill-in>"

S = "${WORKDIR}"

RDEPENDS:${PN} = "python3 python3-modules"

do_install() {
    install -d ${D}${PYTHON_SITEPACKAGES_DIR}
    cp -a ${S}/pylin ${D}${PYTHON_SITEPACKAGES_DIR}/
}

FILES:${PN} = "${PYTHON_SITEPACKAGES_DIR}/pylin"

python3-pymumclient_1.2.0.bb and python3-pylinframe_1.2.0.bb are the same shape with the package name and LICENSE_FLAGS swapped.

Add to conf/local.conf:

LICENSE_FLAGS_ACCEPTED += "commercial_pylin commercial_pymumclient commercial_pylinframe"

License hygiene: the resulting image embeds proprietary packages. Treat the image artifact as proprietary — same access controls as the Melexis IDE installer.

6.7 Image recipe

meta-ecu-tests/recipes-images/ecu-tests-image.bb:

SUMMARY = "ECU bench image — Raspberry Pi as a test runner"
DESCRIPTION = "Minimal Linux image that boots, configures network, \
and runs the ECU test framework on a schedule."

LICENSE = "MIT"

IMAGE_FEATURES += "ssh-server-openssh"

IMAGE_INSTALL = " \
    packagegroup-core-boot \
    packagegroup-core-ssh-openssh \
    ${CORE_IMAGE_EXTRA_INSTALL} \
    \
    python3 \
    python3-pip \
    python3-pytest \
    python3-pytest-html \
    python3-pytest-cov \
    python3-pytest-xdist \
    python3-pyserial \
    python3-pyyaml \
    \
    python3-ldfparser \
    python3-pylin \
    python3-pymumclient \
    python3-pylinframe \
    \
    ecu-test-framework \
    \
    rsync openssh-sftp-server curl \
    htop nano vim-tiny \
    kernel-modules \
    chrony \
"

# Be explicit about init system in the image
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME:init_manager = "systemd"

inherit core-image

# Size constraint (raise if you add a lot of debug tools)
IMAGE_OVERHEAD_FACTOR = "1.3"
IMAGE_ROOTFS_EXTRA_SPACE = "524288"

7. Network configuration

The bench MUM exposes itself as a USB-RNDIS Ethernet device at 192.168.7.2/24 with the host expected at 192.168.7.1. Bake the host-side address into the image so the Pi takes it automatically.

meta-ecu-tests/recipes-ecu-tests/ecu-test-framework/files/20-mum.network (append to the recipe's SRC_URI and do_install):

[Match]
# usbX is what the Pi's kernel names the USB-RNDIS device. Verify
# with `ip link` on a running image and adjust if needed (it may be
# enxXXXXXXXXXXXX based on MAC address).
Name=usb0 enx*

[Network]
Address=192.168.7.1/24
LinkLocalAddressing=no
IPMasquerade=no
ConfigureWithoutCarrier=yes

The recipe installs this to /etc/systemd/network/20-mum.network. systemd-networkd is already enabled when systemd is the init manager.

For a wired connection to the lab network as well, add a second profile:

[Match]
Name=eth0

[Network]
DHCP=yes

8. USB / serial configuration

The Owon PSU is a USB-serial device, typically /dev/ttyUSB0. To keep the path stable across reboots when the host has other USB adapters, add a udev rule.

meta-ecu-tests/recipes-ecu-tests/ecu-test-framework/files/99-owon-psu.rules:

# Adjust idVendor/idProduct for your specific adapter (check `lsusb` on
# a booted image). The symlink lets config use /dev/owon_psu instead of
# /dev/ttyUSBn, which can shift if multiple adapters are present.
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", \
    MODE="0660", GROUP="dialout", SYMLINK+="owon_psu"

Install to /etc/udev/rules.d/99-owon-psu.rules. The framework's config/test_config.yaml then carries port: /dev/owon_psu regardless of enumeration order.


9. The configuration file shipped in the image

/etc/ecu-tests/test_config.yaml (installed by the recipe):

interface:
  type: mum
  host: 192.168.7.2
  lin_device: lin0
  power_device: power_out0
  bitrate: 19200
  boot_settle_seconds: 0.5
  ldf_path: /opt/ecu-tests/vendor/4SEVEN_color_lib_test.ldf

flash:
  enabled: false

power_supply:
  enabled: true
  port: /dev/owon_psu        # from the udev rule
  baudrate: 115200
  timeout: 2.0
  parity: N
  stopbits: 1
  idn_substr: OWON
  do_set: true
  set_voltage: 13.0
  set_current: 1.0

And /etc/ecu-tests/marker (single line):

hardware and mum and not slow

Operators can edit either over SSH without rebuilding the image.


10. Build, flash, boot

10.1 Build

From ~/yocto/build/:

bitbake ecu-tests-image

First run: 14 h depending on your machine. Subsequent rebuilds (with sstate-cache intact): minutes.

Output ends up at ~/yocto/build/tmp/deploy/images/raspberrypi4-64/ecu-tests-image-raspberrypi4-64.wic.bz2.

10.2 Flash

# Find the SD card
lsblk
# Assume /dev/sdX is the SD card; double-check before running!

bzcat ~/yocto/build/tmp/deploy/images/raspberrypi4-64/ecu-tests-image-raspberrypi4-64.wic.bz2 \
    | sudo dd of=/dev/sdX bs=4M conv=fsync status=progress
sync

Or use bmaptool from Yocto for faster flashing of sparse images.

10.3 First boot

  • Insert the SD card into the Pi.
  • Connect: power, USB-Ethernet (MUM), USB-serial (Owon PSU), and either Ethernet or HDMI+keyboard for diagnosis.
  • Boot.
  • SSH in: ssh root@<ip> (no password by default thanks to debug-tweaks — disable that for production builds, see §13).
journalctl -u ecu-test-framework.service -e
ls /var/log/ecu-tests/latest
cat /var/log/ecu-tests/latest/junit.xml | head

11. Updating the image

There are three ways to push updates without a full re-flash:

Approach When How
Re-flash Major changes, package adds bitbake ecu-tests-image → flash
In-place git pull Test-code-only changes git -C /opt/ecu-tests pull && systemctl restart ecu-test-framework
RAUC / Mender A/B Production fleets Adds an A/B partition layout and an update agent; out of scope for this doc

For developer iteration, the git-pull path is fastest. The image should ship with the framework's git remote so git pull works out of the box.


12. Air-gapped or no-network builds

Yocto can fetch everything locally if you stage:

  1. downloads/ populated by a one-time bitbake -c fetchall ecu-tests-image on a connected machine.
  2. sstate-cache/ similarly.

Then on the air-gapped builder set:

BB_NO_NETWORK = "1"
BB_FETCH_PREMIRRORONLY = "1"

And copy downloads/ and sstate-cache/ from the staging machine.


13. Hardening for production

Before shipping the image to a customer or a permanent installation:

  • Disable debug-tweaks in EXTRA_IMAGE_FEATURES. This reinstates root password requirement, removes the empty-password bypass, and hardens the SSH config.
  • Set a real ROOT_HOME password in a .bbappend for the base recipe, OR provision SSH keys at first boot, OR enforce password-only logins.
  • Read-only rootfs — Yocto supports IMAGE_FEATURES += "read-only-rootfs". Anything mutable (configs, logs) needs to move to /var on tmpfs or a persistent partition.
  • Watchdog — enable the hardware watchdog so the Pi reboots if it locks up. meta-raspberrypi exposes the BCM watchdog; pair with systemd's WatchdogSec=.
  • Boot-time integritysecureboot is not viable on Raspberry Pi to the degree it is on automotive ECUs, but you can checksum the rootfs and refuse to run tests if it's been tampered with.
  • TLS for report uploads — if the push step talks HTTP/MQTT to a collector, pin the server certificate.

14. Troubleshooting

Symptom Likely cause Fix
do_compile fails for python3-pylin with "license not accepted" Missing LICENSE_FLAGS_ACCEPTED entry Add the three commercial_* flags to conf/local.conf
pylin not importable in the running image Recipe installed to the wrong site-packages path Confirm PYTHON_SITEPACKAGES_DIR in the recipe matches the image's actual path (python3 -c "import site; print(site.getsitepackages())" on a booted image)
MUM unreachable at boot systemd-networkd profile didn't match the USB iface ip link to find the real name; widen the Name= glob
Tests fail with "ECU not responding" Same as above, or the MUM hasn't come up by the time the timer fires Add After=network-online.target (already done) and a startup delay in the runner
PSU port not found udev rule didn't match the adapter; check lsusb for VID/PID Adjust the rule and rebuild, or fall back to /dev/ttyUSB0 and hope nothing else enumerates first
bitbake runs out of disk TMPDIR fills up Mount ~/yocto/build on its own disk, or change TMPDIR in local.conf to a bigger volume
First build runs forever All-from-source compile This is normal; subsequent builds use the populated sstate-cache
Image too big for the SD card Too many extras in IMAGE_INSTALL Trim htop nano vim-tiny chrony etc. if you don't need them

15. What this gives you vs. running on the Pi directly

Pi OS + pi_install.sh Yocto image
Reproducible ad-hoc yes
Image footprint ~2 GB ~400 MB realistic
Boot to first test ~45 s ~12 s with a tuned image
Updates over the air manual feasible with RAUC/Mender
Day-to-day dev comfortable painful — every change rebuilds
Auditing the OS dpkg snapshot full source manifest

Use the Yocto path when the Pi is part of a deliverable, when you need to ship N benches identical, or when an auditor needs a list of every byte on the device.


  • docs/09_raspberry_pi_deployment.md — run the framework on stock Raspberry Pi OS (the lighter path).
  • docs/10_build_custom_image.md — a preseeded Pi OS image, the middle ground between vanilla Pi OS and a Yocto build.
  • docs/20_docker_image.md — if you'd rather the framework run from a container on a regular Linux host rather than as the host itself.
  • docs/14_power_supply.md — PSU port resolution; on the image the udev rule makes /dev/owon_psu stable, so the resolver's job is trivial.
  • docs/02_configuration_resolution.md — how ECU_TESTS_CONFIG selects /etc/ecu-tests/test_config.yaml at runtime.
  • vendor/automated_lin_test/install_packages.sh — the equivalent of the Melexis-recipe step for a developer venv on a workstation.