Comments ▾
Figures ▾
Tables ▾

M5Stack Cardputer Zero · Volume 10

M5Stack Cardputer Zero Volume 10 — Custom App Development

Building Linux apps for the CM0: the czdev/AppBuilder LVGL flow, .deb packaging to the AppStore, and Linux-native drop-box services


10.1 About this volume

Vol 10 covers writing your own software for the Cardputer Zero. There are two distinct paths, and the choice between them is the whole story:

  1. A graphical on-screen app sized for the 320×170 LCD — written in C against LVGL, built and tested with the czdev / AppBuilder toolkit (desktop SDL2 emulator → cross-build → .deb → install from the on-device AppStore). This is the “feels like a Cardputer app” path: it lands in the launcher next to 2048, Calculator, AudioSpectrum, and the rest of the org’s app catalog.
  2. A normal Linux program — a Python script, a C/C++ binary, a shell pipeline, a systemd service. No special SDK, no LCD framework: it runs on the Zero exactly as it would on a Raspberry Pi Zero 2 W, because the Zero is that silicon class (RP3A0 / BCM2710A1, quad Cortex-A53 @ 1.0 GHz, aarch64, 512 MB LPDDR2, Debian/Raspberry Pi OS). For headless work — capture boxes, SSH-driven tools, cron jobs — this is the right path and the LCD framework is irrelevant.

Both are first-class. Use path 1 when the deliverable is something you touch on the screen; use path 2 when it’s something that runs in the background or over the network. §3 works a path-1 example end to end; §4 works a path-2 example.

Figure 1 — The LVGL widgets demo — the on-screen UI toolkit that czdev/AppBuilder builds path-1 apps against for the 320×170 LCD. Image: lvgl.io.
Figure 1 — The LVGL widgets demo — the on-screen UI toolkit that czdev/AppBuilder builds path-1 apps against for the 320×170 LCD. Image: lvgl.io.

Why earlier drafts of this volume said “custom firmware.” Drafts written before the device shipped assumed the Zero was an ESP32-S3 microcontroller handheld (the same class as the Cardputer original and ADV), so this volume described a PlatformIO / ESP-IDF / Arduino firmware build — pio run --target upload, esp_wifi_set_promiscuous(), #ifdef feature flags, flashing a monolithic binary over USB-CDC. That was a plausible-but-wrong textbook assumption: every other Cardputer is an ESP32, so “more of the same” was the natural guess. It is wrong. The Zero boots a full Linux OS off a microSD card. There is no firmware image to flash, no sketch, no platformio.ini, no ESP-IDF. You write Linux apps and packages, not firmware. This volume is rewritten accordingly; the ESP32 heritage of the series is covered as lineage only (see Vol 6 and the Cardputer ADV deep dive, which is a genuinely different, ESP32-class machine).

When do you actually need to write your own app? The Zero already runs the entire Debian aarch64 archive plus the org’s curated app catalog, so reach for a custom build only when:

  • You want an on-screen tool tailored to the 320×170 LCD (a status dashboard, a single-purpose front panel, a field utility you’ll laminate instructions for).
  • You’re building a headless appliance — a drop box, a logger, a scheduled collector — that should come up at boot with no operator.
  • You want to drive the Cap EXT bus or a USB peripheral under Linux (LoRa, CC1101, RTL-SDR, a USB Wi-Fi adapter) from your own logic rather than an existing tool.

10.2 Custom app development workflow

10.2.1 Two toolchains, one device

                         ┌──────────────────────────────────────────────┐
                         │     M5Stack Cardputer Zero (CM0 / aarch64)    │
                         │        Raspberry Pi OS / Debian, microSD       │
                         └──────────────────────────────────────────────┘
                                 ▲                              ▲
              path 1: on-screen  │                              │  path 2: any Linux program
              LVGL app via czdev │                              │  (apt + gcc/python/rust)
                                 │                              │
   ┌─────────────────────────────┴───────┐      ┌──────────────┴──────────────────────┐
   │  develop on PC (macOS/Linux/Windows) │      │  edit on PC or on-device over SSH     │
   │  ── SDL2 emulator renders 320×170 ── │      │  ── cross-compile or build natively ──│
   │  czdev build  →  .deb  →  AppStore   │      │  scp / git pull  →  run  →  systemd   │
   └──────────────────────────────────────┘      └──────────────────────────────────────┘

The Zero is one machine that accepts two kinds of work product: a .deb package that the on-device AppStore installs, or any executable Linux artifact you put on the SD card. Nothing about path 2 is Zero-specific — apt install python3-scapy, write a script, done. The rest of this section therefore concentrates on path 1, the part that is Zero-specific.

10.2.2 The czdev / AppBuilder toolkit (path 1)

czdev is the CLI front end of m5stack/CardputerZero-AppBuilder — an online build system plus a desktop toolkit for building LVGL apps sized for the 320×170 LCD.^[m5stack/CardputerZero-AppBuilder (the czdev CLI + SDL2 emulator). The emulator is built in Rust; the publishing commands need only Python 3. Exact flag set and library versions — verify on receipt.]

Table 1 — 2.2 The czdev / AppBuilder toolkit (path 1)

CapabilityWhat it does
Desktop emulator (SDL2)Renders the 320×170 LCD inside a keyboard skin on your macOS / Linux / Windows (MSYS2) workstation, so you iterate on layout and input with no physical device.
Scans for app-builder.jsonWalks a repo, finds the app manifest, builds the LVGL C source, and packages the result as a .deb.
Online modeSubmit any public Git repo and get back a ready-to-install .deb — no local cross-toolchain needed.
Store commandslogin / publish / unpublish / bump push a built app to the app store and manage versions.

Verified subcommands: doctor, list, build, run, watch, deploy, plus login / publish / unpublish / bump. The exact argument syntax is not reproduced here from memory — run czdev doctor (environment check) and czdev --help on the toolkit you install and treat that as authoritative. A typical inner loop:

# one-time: confirm the toolchain + emulator deps are present
czdev doctor

# scaffold from the LVGL starter template (CardputerZero/template)
git clone https://github.com/CardputerZero/template.git my-app
cd my-app

# iterate: build + run in the SDL2 emulator on your PC (no device)
czdev build        # compile the LVGL app
czdev run          # launch it in the 320x170 emulator skin
czdev watch        # rebuild + reload on file change

# package + ship
czdev build --release        # produce the .deb  (exact flag: verify with --help)
czdev publish                # push to the app store (after `czdev login`)

10.2.3 The app manifest — app-builder.json

Each app carries an app-builder.json at its repo root. AppBuilder reads it to know the app’s identity, entry point, and packaging metadata, then emits a Debian package with that metadata baked into the control fields. Treat the precise schema as authoritative-on-the-tool (read the template repo’s manifest and the AppBuilder README); conceptually it carries at least:

{
  "name": "sys-dash",                 // package/app id (→ Debian Package: field)
  "title": "System Dashboard",        // launcher display name
  "version": "0.1.0",                 // → Debian Version:
  "entry": "src/main.c",              // LVGL C entry translation unit
  "icon": "assets/icon.png"
  // architecture (arm64), dependencies, category, etc. — see template repo
}

10.2.4 The C app ABI — cz_app.h

The on-screen app ABI is deliberately tiny. An app includes one header and implements two functions; the shell/launcher hands you an LVGL parent object to build your UI into, and delivers events back to you.^[ABI per the AppBuilder/template README: #include <cz_app.h>, implement void app_main(lv_obj_t *parent) and void app_event(int type, void *data). The two function signatures are confirmed; the app_event type enumeration and the data payload layout are not documented to me — treat them as “verify on receipt” and read the template/cz_app.h before relying on any specific event code.]

#include <cz_app.h>     // the Cardputer Zero app ABI
#include <lvgl.h>       // LVGL — sized for the 320x170 ST7789v3 panel

// Called once when the app is launched. `parent` is an LVGL object
// (the app's root container) already attached to the active screen.
// Build your entire UI as children of `parent`.
void app_main(lv_obj_t *parent)
{
    lv_obj_t *label = lv_label_create(parent);
    lv_label_set_text(label, "Hello, Cardputer Zero");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

// Called by the shell to deliver events (key, tick, focus, exit...).
// The exact `type` codes + `data` layout are toolkit-defined — read
// cz_app.h in the template before switching on specific values.
void app_event(int type, void *data)
{
    (void)type;
    (void)data;
}

That is the entire contract for a minimal app: implement app_main, optionally handle app_event, declare it in app-builder.json, and let AppBuilder produce the .deb. Everything else — widgets, timers, input — is ordinary LVGL.

10.2.5 Distribution — the Debian app store

Built apps are Debian packages, distributed through a real APT repository, browsed and installed on-device:

Table 2 — Built apps are Debian packages, distributed through a real APT repository, browsed and installed on-device

ComponentRole
m5stack/CardputerZeroRepositoryThe official .deb repository, served via GitHub Pages.
CardputerZero/packagesAPT repository for Cardputer Zero apps (the apt source the device points at).
CardputerZero/AppStoreThe on-device app to browse + install apps.
cardputerzero.github.ioThe CardputerZero Hub — online web UI + app store front.

So the publish path is: czdev build --release.debczdev publish (or a PR to the repo) → the package lands in CardputerZeroRepository / CardputerZero/packages → it appears in the on-device AppStore → install. Because it’s standard APT under the hood, you can also side-load during development:

# side-load a .deb straight onto the device for testing (over SSH/scp)
scp sys-dash_0.1.0_arm64.deb  cardputer@<zero-ip>:/tmp/
ssh cardputer@<zero-ip> 'sudo apt install /tmp/sys-dash_0.1.0_arm64.deb'

Tip. During development you rarely need to publish. The emulator (czdev run) covers layout/input iteration on your PC; a scp + apt install of the local .deb covers on-device verification. Publish only once the app is a keeper — and use czdev bump to version it so the AppStore can offer updates.


10.3 Worked example: on-screen system dashboard (LVGL via czdev)

Goal: a small always-on system dashboard for the 320×170 LCD — battery state of charge + voltage + current from the BQ27220 fuel gauge, plus CPU temperature and load. A genuinely useful field panel (am I about to run flat? is the SoC thermal-throttling during a capture?), and a clean end-to-end demonstration of the path-1 flow: source → emulator → .deb → install.

10.3.1 Where the data comes from

Table 3 — 3.1 Where the data comes from

ReadingSource on the ZeroHow to read it
CPU temperature/sys/class/thermal/thermal_zone0/temp (milli-°C)plain file read — same as any Pi
Load average/proc/loadavgplain file read
Battery SoC / voltage / currentBQ27220 fuel gauge on an I²C busI²C register reads — bus number + 7-bit address: verify on receipt (BQ27220 default address is commonly 0x55; the register/command map is in the TI datasheet)

The CPU/temperature/load readings are rock-solid (standard Linux sysfs/procfs). The fuel-gauge path depends on which I²C bus the BQ27220 sits on and whether a kernel driver already exposes it under /sys/class/power_supply/. Check /sys/class/power_supply/ first — if the gauge is bound to a power_supply driver, read capacity, voltage_now, current_now from there and skip raw I²C entirely. Only fall back to direct I²C (via libi2c / /dev/i2c-N) if no driver is present.

10.3.2 Reading the sensors (plain C, no LVGL)

#include <stdio.h>

// CPU temperature in degrees C, or -1000 on error.
double cpu_temp_c(void)
{
    FILE *f = fopen("/sys/class/thermal/thermal_zone0/temp", "r");
    if (!f) return -1000.0;
    long milli = 0;
    if (fscanf(f, "%ld", &milli) != 1) milli = -1000000;
    fclose(f);
    return milli / 1000.0;
}

// Battery percent, preferring a kernel power_supply driver if present.
// Returns 0..100, or -1 if unavailable.
int battery_percent(void)
{
    FILE *f = fopen("/sys/class/power_supply/bq27220/capacity", "r");
    // NOTE: the exact power_supply node name is device-dependent —
    // enumerate /sys/class/power_supply/ on the real device and adjust.
    if (!f) return -1;                 // fall back to raw I2C (BQ27220) here
    int pct = -1;
    if (fscanf(f, "%d", &pct) != 1) pct = -1;
    fclose(f);
    return pct;
}

Note (don’t fabricate the register map). If you must talk to the BQ27220 directly over I²C, do not hard-code command codes from memory — open the TI BQ27220 technical reference, use the documented standard-command set (Voltage, Current, StateOfCharge, …), and confirm the bus/address on the actual device with i2cdetect -y <bus>. This volume deliberately does not print a guessed register table.

10.3.3 The LVGL app (path-1 entry point)

#include <cz_app.h>
#include <lvgl.h>

static lv_obj_t *lbl_batt, *lbl_temp, *lbl_load;

// forward decls from §3.2
double cpu_temp_c(void);
int    battery_percent(void);

// Refresh callback — LVGL timer fires it on the UI thread.
static void refresh_cb(lv_timer_t *t)
{
    (void)t;
    int    pct  = battery_percent();
    double temp = cpu_temp_c();

    if (pct >= 0) lv_label_set_text_fmt(lbl_batt, "Batt: %d%%", pct);
    else          lv_label_set_text(lbl_batt, "Batt: n/a");

    lv_label_set_text_fmt(lbl_temp, "CPU:  %.1f C", temp);
    // load average via /proc/loadavg omitted for brevity
}

void app_main(lv_obj_t *parent)
{
    // Simple vertical stack of three labels. 320x170 is small —
    // big text, few elements. (LVGL v8/v9 API; confirm the LVGL
    // version the toolkit ships and adjust calls if needed.)
    lbl_batt = lv_label_create(parent);
    lv_obj_align(lbl_batt, LV_ALIGN_TOP_LEFT, 8, 8);

    lbl_temp = lv_label_create(parent);
    lv_obj_align(lbl_temp, LV_ALIGN_TOP_LEFT, 8, 40);

    lbl_load = lv_label_create(parent);
    lv_obj_align(lbl_load, LV_ALIGN_TOP_LEFT, 8, 72);

    // refresh every 2 s
    lv_timer_create(refresh_cb, 2000, NULL);
    refresh_cb(NULL);   // paint immediately
}

void app_event(int type, void *data)
{
    // Handle exit/key events here once the cz_app.h event codes
    // are confirmed from the template repo.
    (void)type; (void)data;
}

Uncertainty flags (per dossier §5.2). The LVGL calls above (lv_label_create, lv_label_set_text_fmt, lv_obj_align, lv_timer_create) are standard LVGL v8/v9 and should port directly, but confirm the LVGL major version the AppBuilder toolkit bundles — a few signatures changed between v8 and v9. The cz_app.h app_event enumeration is not verified here. The BQ27220 sysfs node name and I²C address are verify-on-receipt.

10.3.4 Build, emulate, package, install

# 1. Develop on the PC — render in the SDL2 emulator, no device needed
czdev build
czdev run                     # 320x170 skin opens; check layout + readings
                              # (battery/temp will read host values in the
                              #  emulator — wire mock data for UI work)

# 2. Produce the arm64 .deb
czdev build --release         # exact flag: czdev build --help

# 3. Side-load onto the real device over SSH and verify on hardware
scp sys-dash_0.1.0_arm64.deb  cardputer@<zero-ip>:/tmp/
ssh cardputer@<zero-ip> 'sudo apt install /tmp/sys-dash_0.1.0_arm64.deb'
#   → now launchable from the on-device shell/launcher

# 4. (Keeper) publish to the store so it shows up in the AppStore
czdev login
czdev publish

That is the full path-1 lifecycle: a real on-screen tool, tested without hardware in the emulator, packaged as a Debian .deb, side-loaded for hardware verification, then published to the AppStore for one-tap install on any Zero.


10.4 Worked example: Linux capture / drop-box

Goal: turn the Zero into an unattended network capture drop box — boot straight into a capture service that writes timestamped pcaps to the SD card, reachable over Ethernet or SSH for retrieval, no operator and no screen interaction. This is a pure path-2 (normal Linux) job: nothing here is Cardputer-specific, which is exactly the point — the Zero runs the same tcpdump / scapy / tshark a Linux laptop does, just slower (quad A53 @ 1 GHz, 512 MB).

10.4.1 What makes the Zero a good drop box

  • Real RJ45 10/100 Ethernet + on-module 2.4 GHz Wi-Fi + BLE → wired tap and wireless exfil/management.
  • Battery (1500 mAh LiPo, BQ27220 gauge) + USB-C → survives being unplugged, runs off a power bank.
  • USB-A host port → plug in a known monitor-mode USB Wi-Fi adapter or an SDR.
  • microSD → cheap, swappable capture storage; pop it out and read it on a laptop.
  • 84 × 54 × 23 mm → fits where a laptop or even a Pi-in-a-case won’t.

Posture warning. A drop box is a full computer with SSH credentials and a trivially-readable microSD. Deploy only on networks you own or are authorized to test; treat the SD card as plaintext. Operational-security implications are covered in Vol 11.

10.4.2 The simplest version: tcpdump as a systemd service

A one-line capture command, supervised by systemd so it starts at boot and restarts on failure. Capture to the SD card with file rotation so a long run doesn’t fill the card or produce one unreadable monster pcap:

# /usr/local/bin/dropbox-capture.sh
#!/bin/sh
IFACE="${1:-eth0}"                 # eth0 (wired) by default
OUT=/media/sd/captures            # mount point of the microSD
mkdir -p "$OUT"
exec tcpdump -i "$IFACE" -nn -s 0 \
     -w "$OUT/cap-%Y%m%d-%H%M%S.pcap" \
     -G 600 -W 144                 # rotate every 600 s, keep 144 files (~24 h)
# /etc/systemd/system/dropbox-capture.service
[Unit]
Description=Drop-box packet capture
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/dropbox-capture.sh eth0
Restart=on-failure
RestartSec=5
# tcpdump needs CAP_NET_RAW/CAP_NET_ADMIN; run as root or grant caps:
# AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now dropbox-capture.service
journalctl -u dropbox-capture -f          # watch it

Retrieve over the network without touching the box:

# from your laptop
rsync -avz cardputer@<zero-ip>:/media/sd/captures/ ./loot/

10.4.3 The scriptable version: a Python/scapy collector

When you want filtering, parsing, or custom logging rather than raw pcap, a small scapy sniffer is easy to schedule the same way (apt install python3-scapy). It writes pcap via libpcap under the hood and can branch on packet contents:

#!/usr/bin/env python3
# /usr/local/bin/dropbox_collect.py — minimal scapy drop-box collector
import time, pathlib
from scapy.all import sniff, wrpcap   # libpcap-backed

OUT = pathlib.Path("/media/sd/captures")
OUT.mkdir(parents=True, exist_ok=True)
batch, IFACE = [], "eth0"

def on_pkt(pkt):
    batch.append(pkt)
    if len(batch) >= 500:                       # flush in batches
        fn = OUT / f"cap-{int(time.time())}.pcap"
        wrpcap(str(fn), batch)
        batch.clear()

# count=0 → run forever; store=False → don't hold everything in 512 MB RAM
sniff(iface=IFACE, prn=on_pkt, store=False)

Wrap it in a sibling dropbox-collect.service exactly like §4.2. Note the store=False — on a 512 MB box you must stream to disk, never accumulate in RAM.

10.4.4 Wi-Fi monitor mode — the honest caveat

For wired capture (eth0) the above is complete and reliable. For Wi-Fi capture you hit the standard Linux monitor-mode reality, and the dossier is blunt about it:

  • The on-module 2.4 GHz b/g/n radio’s monitor/injection support is driver-limited (it’s a Broadcom SDIO part, the same lineage as the Pi Zero 2 W — historically poor for real monitor mode and injection).
  • For serious Wi-Fi capture/attack work, attach a known monitor-mode USB adapter to the USB-A host port (e.g. an RTL8812AU / AR9271-class adapter with proper Linux support), and capture on that interface instead.

The mechanics are ordinary Linux:

# bring an interface into monitor mode (USB adapter strongly preferred)
sudo airmon-ng start wlan1              # aircrack-ng's wrapper, or:
sudo ip link set wlan1 down
sudo iw dev wlan1 set type monitor
sudo ip link set wlan1 up
sudo iw dev wlan1 set channel 6

# then capture on the monitor interface
sudo tcpdump -i wlan1 -w /media/sd/captures/wifi-%Y%m%d-%H%M%S.pcap -G 600 -W 144

Everything flows through libpcap, so tcpdump, tshark, scapy, kismet, and hcxdumptool all consume the same interface. The Zero is fully capable of running these — it just won’t crack big handshakes on-box (that’s a laptop/Pi 5 job; see Vol 11 for the honest performance envelope). For the Wi-Fi tooling catalog and the USB-adapter recommendation in depth, see the pen-test workflow material and the Cyberdecks Linux-handheld peers (the uConsole/PicoCalc cohort shares this exact USB-adapter story).


10.5 Cross-family code organization

The old version of this section showed #ifdef M5_CARDPUTER_ZERO feature flags toggling HAS_INTERNAL_IMU, BATTERY_CAPACITY_MAH 700, etc., to share a firmware codebase with the ADV. That is doubly wrong: the values were guesses (the Zero has a 1500 mAh battery and, on the Full model, a BMI270+BMM150 IMU), and — more fundamentally — there is no shared firmware codebase to flag into, because the Zero is Linux and the ADV is an ESP32 microcontroller. They do not compile from the same source tree at all.

10.5.1 The real portability story

   Cardputer ORIGINAL / ADV                 Cardputer ZERO
   ──────────────────────────               ────────────────────────────
   ESP32-S3 microcontroller                 Raspberry Pi CM0 (aarch64)
   Arduino / ESP-IDF firmware               Debian/Raspberry Pi OS userland
   one flashed binary, bare metal           processes, packages, systemd
                       │                          │
                       └──── NO shared code ──────┘
                         (different ISA, different
                          build system, different OS)

   Cardputer ZERO  ◄────── portability lives HERE ──────►  any Pi / Debian arm64
                     your C/Python/Rust runs unchanged on a
                     Pi Zero 2 W, Pi 3/4/5, or any Debian aarch64 box

Your Zero code’s natural portability target is other Linux machines, not the other Cardputers. A Python collector or an LVGL app you write for the Zero runs, essentially unchanged, on a Raspberry Pi Zero 2 W (same SoC), a Pi 4/5, or any Debian aarch64 system. That is the axis to design for: standard Linux APIs (sysfs, procfs, libpcap, libgpiod, I²C/SPI via /dev), so the same source builds everywhere. Conditioning on runtime differences (does this board have the IMU? which I²C bus is the fuel gauge on?) is done the Linux way — probe /sys, read a config file, check i2cdetect — not with compile-time board macros.

10.5.2 If you genuinely want to share with the ADV

You can build something that works with both the Zero and an ESP32 Cardputer ADV — but the sharing is at the protocol level, not the source level. Both can talk to the same Cap module over the shared 14-pin EXT bus (e.g. a Cap LoRa or Cap CC1101): the ADV drives it with ESP32 firmware, the Zero drives it with a Linux program, and they interoperate because they speak the same on-the-wire / on-the-bus protocol to the module (or to each other over BLE/LoRa/Wi-Fi). See Vol 9 for the BLE/RF interop patterns (the Chameleon Ultra section is the worked CM0-correct example) and the Cardputer ADV deep dive for the ESP32 side.

Table 4 — 5.2 If you genuinely want to share with the ADV

You want to share…Right mechanismWrong mechanism
UI/app logic across Zero units + other PisPlain Linux C/Python, standard libs#ifdef board macros
Behavior between a Zero and an ADVA common wire protocol (BLE GATT, LoRa frame, UART/I²C command set to a shared Cap module)A shared Arduino sketch (impossible — different OS/ISA)
Hardware-presence handling on the ZeroRuntime probe of /sys, i2cdetect, config fileCompile-time HAS_* flags

10.6 Common build pitfalls

These are Linux app/packaging pitfalls — the ESP32-era ones (OTA partition mismatch, BMI270 Arduino-library compile errors, #if HAS_INTERNAL_IMU) no longer apply.

Table 5 — 6. Common build pitfalls

PitfallCauseFix
.deb installs on your PC but not on the ZeroBuilt for amd64, Zero is arm64 (aarch64)Cross-compile for arm64, or build on-device / in an arm64 container; confirm Architecture: arm64 in the package, not amd64/all
App won’t launch; missing .so at runtime.deb Depends: metadata doesn’t list the LVGL/SDL/runtime libsDeclare runtime deps in app-builder.json / control file; test with apt install ./pkg.deb on a clean image (apt resolves deps; dpkg -i won’t)
Emulator works, device doesn’t (or vice-versa)SDL2 emulator vs on-device LVGL display backend differ; LVGL v8↔v9 API driftPin the LVGL version the toolkit ships; don’t mix headers; re-verify changed signatures
apt install ./app.deb rejects the packageMalformed Debian metadata — bad version string, missing maintainer, wrong section/control fieldsValidate with lintian app.deb; keep app-builder.json fields well-formed; use czdev bump for versions
I²C / SPI / GPIO reads fail as non-root/dev/i2c-*, /dev/spidev*, GPIO not group-accessible to your userAdd user to i2c/spi/gpio groups (or ship a udev rule in the .deb); enable the buses via raspi-config / dtoverlays; or run the service as root with explicit caps
tcpdump/scapy can’t open the interfaceService runs unprivileged; capture needs CAP_NET_RAW/CAP_NET_ADMINRun as root, or grant AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN in the unit (§4.2)
Camera app gets no framesIMX219 needs the libcamera stack + correct device-tree overlay; legacy raspistill/V4L2 quirksUse libcamera/rpicam-apps; load the IMX219 dtoverlay (m5stack/m5stack-linux-dtoverlays); confirm CSI is enabled. Full model only — Lite has no camera
Process OOM-killed under load512 MB RAM, no swap by defaultStream to disk (store=False in scapy), avoid buffering whole captures; add zram/swap cautiously (SD wear); don’t run heavy DSP/GNU-Radio flowgraphs on-box
Wi-Fi monitor/injection “doesn’t work”On-module Broadcom radio is driver-limited for monitor modeUse a known monitor-mode USB adapter on the USB-A port (§4.4) — not a code bug

10.7 Resources

End of Vol 10. Next: Vol 11 covers operational posture — Zero-specific legal, ethical, and operational considerations for drop-box, education, and fleet-ops deployments on a real Linux computer.

Comments (0)

  1. Loading…

Comments are held for moderation — nothing appears until approved.