# 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`](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`](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 | ~2–4 GB minimum (Lite) | ~150–500 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 | 30–60 s | 5–15 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 1–4 h for the first image build **Packages (Ubuntu 22.04):** ```bash 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_.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 ```bash 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 ```bash 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: ```bitbake 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: ```bitbake 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 ```bash 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`: ```bitbake 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`: ```bitbake 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`: ```bash #!/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`: ```ini [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`: ```ini [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): ```bitbake SUMMARY = "Pure-Python LDF parser (LIN Description File)" HOMEPAGE = "https://github.com/c4deszes/ldfparser" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://LICENSE;md5=" SRC_URI = "https://files.pythonhosted.org/packages/source/l/ldfparser/ldfparser-${PV}.tar.gz" SRC_URI[sha256sum] = "" 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: ```bash 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: ```bash # 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`: ```bitbake 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] = "" 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`: ```bitbake 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`: ```bitbake 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`): ```ini [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: ```ini [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): ```yaml 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/`: ```bash bitbake ecu-tests-image ``` First run: 1–4 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 ```bash # 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@` (no password by default thanks to `debug-tweaks` — disable that for production builds, see §13). ```bash 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: ```bitbake 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 integrity** — `secureboot` 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. --- ## 16. Related docs - [`docs/09_raspberry_pi_deployment.md`](09_raspberry_pi_deployment.md) — run the framework on stock Raspberry Pi OS (the lighter path). - [`docs/10_build_custom_image.md`](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`](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`](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`](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.