Skip to content

feat(viewer,build,balena): add arm64/Qt6 pi3-64 board and the Rock Pi 4 fleet; keep 32-bit pi3 as legacy - #2985

Merged
vpetersson merged 4 commits into
masterfrom
feature/pi3-64-qt6-board
Jun 7, 2026
Merged

feat(viewer,build,balena): add arm64/Qt6 pi3-64 board and the Rock Pi 4 fleet; keep 32-bit pi3 as legacy#2985
vpetersson merged 4 commits into
masterfrom
feature/pi3-64-qt6-board

Conversation

@vpetersson

@vpetersson vpetersson commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Issues Fixed

Continues #2906 (Phase 2, revised). Supersedes #2917 — same goal, different approach: rather than deleting the Qt 5 toolchain and moving Pi 2 / Pi 3 to Qt 6, this keeps Qt 5 (now fixed up on master) and adds a separate 64-bit Qt 6 image stream for the Pi 3. Also adds the new screenly_ose/anthias-rockpi4 balena fleet.

Description

New board pi3-64. A 64-bit (arm64) Qt 6 viewer image for Raspberry Pi 3 hardware running a 64-bit OS, with its own image stream, disk image, and balena fleet (screenly_ose/anthias-pi3-64, device type raspberrypi3-64). The legacy 32-bit armhf / Qt 5 pi3 board is left untouched and flagged as legacy/maintenance.

pi3-64 reuses the existing pi4-64 path — arm64, Qt 6 from Debian Trixie apt, eglfs + KMS config, and in-process QtMultimedia video (QMediaPlayer → QML VideoOutput, ffmpeg/libavcodec backend with V4L2 HW decode; no external player, no cage/wayland). VideoCore IV hardware decode is H.264-only.

Devices pick the stream by OS architecture: a Pi 3 reports the same model string on both arches, so bin/install.sh / bin/upgrade_containers.sh select pi3-64 when uname -m is aarch64 and the legacy pi3 otherwise. There is no in-place arch switch — moving a device to the 64-bit stream requires reflashing to a 64-bit OS, so existing 32-bit Pi 3 / Pi 2 deployments are unaffected.

New Rock Pi 4 fleet. screenly_ose/anthias-rockpi4 (balena device type rockpi-4b-rk3399) joins the OTA deploy + disk-image pipeline. There is no rockpi4-specific image build — the fleet runs the generic arm64 containers, so bin/balena_ota_deploy.sh / bin/deploy_to_balena.sh pin the <short-hash>-arm64 image tags for it (and strip the /dev/vchiq mount; no VideoCore on RK3399), and the release preflight verifies the arm64 images exist.

That fleet surfaced a real gap: balena ships no anthias_host_agent service, so host:board_subtype was never published and resolve_device_key() stayed arm64 — whose HW-decode set is empty, i.e. every video upload would have been rejected on the fleet. Root-cause fix: the model-string → subtype table moves to the dependency-free anthias_common.device_helper.detect_board_subtype (single source, imported by host_agent), and anthias_common.board.get_board_subtype now falls back to reading /proc/device-tree/model in-container when Redis has no value. The device tree is kernel-global — the same mechanism get_device_type has always used for Pi detection — so the rockpi4 fleet resolves its {h264, hevc} envelope without a host-side daemon, and compose installs whose host_agent died self-heal too. Redis (the host_agent value) remains authoritative when present.

Both fleets exist in balena cloud (0 devices so far); the first master docker-build.yaml run after merge produces the <short-hash>-{pi3-64,arm64} GHCR images the pipeline depends on. The branch is current with master (no conflicts).

Interplay with #3001 (PulseAudio for Qt 6 viewers): the Pi 3B+ verification below reproduced the pa_context_connect() failed symptom that PR fixes. Its start_pulseaudio gates on the pulseaudio package being present, which it adds inside the is_qt6 apt block — pi3-64 is in is_qt6 here, so the new board inherits the audio fix automatically once both PRs land. The two PRs touch the same three files; whichever merges second carries a trivial textual rebase.

Hardware verification (Raspberry Pi 3 Model B+ 1 GB, 64-bit Debian Trixie)

The three pi3-64 images were built locally (arm64 via QEMU) and deployed to a real Pi 3B+:

  • Detection: model "Raspberry Pi 3" + uname -m=aarch64 resolves pi3-64; the eglfs no-display guard parks the viewer correctly while no screen is attached.
  • Full stack (server / celery / viewer / redis) runs with ~400 MB headroom on the 788 MB board; ANTHIAS_LOW_RAM=1 engages (single-QWebEngineView mode).
  • Qt 6 WebEngine initializes cleanly on Pi 3 silicon — the viewer reaches the Anthias service start D-Bus handshake and runs stably (this is the exact point where the 32-bit Qt 5 build hits its intermittent WebEngine-init heap corruption; see docs/board-enablement.md).
  • H.264 hardware decode works in the shipped image: h264_v4l2m2m (bcm2835-codec, /dev/video10) decodes 720p at ~4× realtime.
  • Codec gate verified both ways on-device: an H.264 upload processes and stays active; an HEVC upload is rejected and auto-disabled with "Video codec 'hevc' is not hardware-decoded on this device. Supported: h264." plus the libx264 re-encode recipe.
  • Not yet covered: on-screen rendering (test device had no display attached; the viewer auto-starts from the display guard once HDMI is connected), and Rock Pi 4 on balenaOS (the fleet is new — first device provisioning will be the validation).

Checklist

  • I have performed a self-review of my own code.
  • New and existing unit tests pass locally and on CI with my changes.
  • I have done an end-to-end test for Raspberry Pi devices (pi3-64 on a real Pi 3B+; on-screen rendering pending a connected display — see above).
  • I have tested my changes for x86 devices.
  • I added a documentation for the changes I have made (when necessary).

🤖 Generated with Claude Code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new pi3-64 board target to support Raspberry Pi 3 hardware running a 64-bit OS with the Qt 6 / eglfs viewer stack, while keeping the existing 32-bit pi3 (Qt 5) stream as legacy/maintenance. This extends the build/deploy pipeline, runtime board selection, and documentation so the new board is treated similarly to pi4-64.

Changes:

  • Introduce pi3-64 as a first-class build target (image builder contexts, Dockerfile template, codecs, and media-player routing).
  • Update install/upgrade/runtime scripts to select pi3-64 on Pi 3 hardware when uname -m reports aarch64, and to apply the eglfs/KMS runtime DRM-card rewrite on pi3-64.
  • Wire pi3-64 through balena tooling + GitHub Actions workflows and update docs / Raspberry Pi Imager JSON generation + tests.

Reviewed changes

Copilot reviewed 19 out of 27 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
website/content/docs/installation-options.md Document pi3-64 as the recommended 64-bit Pi 3 option; keep 32-bit Pi 3 as legacy.
website/content/docs/balena-fleet-deployment.md Include pi3-64 in the supported $BOARD_TYPE list and explain legacy vs recommended.
tools/raspberry_pi_imager/tests/test_build_pi_imager_json.py Extend Pi Imager JSON tests to cover pi3-64 artifacts and non-maintenance behavior.
tools/raspberry_pi_imager/build_pi_imager_json.py Add pi3-64 to supported boards while keeping maintenance suffix limited to pi2/pi3.
tools/image_builder/utils.py Add pi3-64 build parameters and treat it as a Qt 6 board in viewer context.
tools/image_builder/constants.py Add pi3-64 to build target options.
tests/test_processing.py Add test asserting Pi 3-64 HW decode codec set is H.264-only.
tests/test_media_player.py Add test ensuring DEVICE_TYPE=pi3-64 forces MPV path despite model-string reporting pi3.
src/anthias_viewer/media_player.py Force MPV selection when DEVICE_TYPE indicates pi3-64 to avoid Qt5/GStreamer routing.
src/anthias_viewer/init.py Update rotation/platform comments to include pi3-64 under eglfs behavior.
src/anthias_server/processing.py Add pi3-64 to HW decode codec mapping (H.264 only).
docs/board-enablement.md Document the new pi3-64 stream and selection behavior.
docs/balena-fleet-host-config.md Include pi3-64 in fleet host-config documentation and key applicability.
docker/eglfs-kms.json Add shared eglfs KMS config template file.
docker/Dockerfile.viewer.j2 Treat pi3-64 like pi4-64 for eglfs/KMS; copy the shared eglfs-kms.json.
bin/upgrade_containers.sh Select pi3-64 on Pi 3 hardware when running aarch64; otherwise keep legacy pi3.
bin/start_viewer.sh Apply runtime DRM-card selection rewrite for eglfs/KMS on both pi4-64 and pi3-64.
bin/install.sh Select pi3-64 vs legacy pi3 based on uname -m on Pi 3 hardware.
bin/deploy_to_balena.sh Allow pi3-64 as a valid --board option.
bin/balena_unpin_devices.py Include anthias-pi3-64 fleet in the default fleet set.
bin/balena_ota_deploy.sh Map board pi3-64 to the anthias-pi3-64 fleet for OTA deploys.
bin/balena_fleet_maintenance.py Add fleet → device-type mapping for screenly_ose/anthias-pi3-64.
balena-host-config.json Add host config entry for pi3-64 mirroring Pi 3 settings.
.github/workflows/scripts/mirror-latest-tags.sh Mirror tags for the new pi3-64 board images.
.github/workflows/docker-build.yaml Build/publish GHCR images for pi3-64 in the main matrix.
.github/workflows/deploy-balena-manual.yaml Add pi3-64 to manual deploy workflow inputs and “all” expansion.
.github/workflows/build-balena-disk-image.yaml Add pi3-64 to disk-image build/release workflows and fleet/image mapping.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread website/content/docs/installation-options.md Outdated
…gacy

Revises issue #2906 Phase 2. The original plan (delete the Qt 5 toolchain,
force Pi 2/Pi 3 onto Qt 6) is abandoned: Qt 5 was fixed up on master and
stays. Instead, add a NEW board target `pi3-64` — a 64-bit (arm64) Qt 6
viewer image for Raspberry Pi 3 hardware on a 64-bit OS — as its own image
stream, disk image, and balena fleet. The legacy 32-bit armhf/Qt5 `pi3`
board is left untouched and flagged as legacy/maintenance.

pi3-64 mirrors the existing `pi4-64` path (Qt 6, eglfs_kms; video played
in-process by AnthiasViewer's QtMultimedia pipeline — QMediaPlayer + the
ffmpeg/libavcodec backend with V4L2 HW decode, no external player).
VideoCore IV is H.264-only HW decode. Board selection is by `uname -m`: a
Pi 3 on a 64-bit OS gets `pi3-64`, a 32-bit OS keeps `pi3` (the model
string is identical on both arches).

- image_builder: pi3-64 build params (arm64) + is_qt6; constants.
- Dockerfile.viewer.j2 + start_viewer.sh: pi3-64 shares the pi4-64 eglfs
  KMS path; renamed board-agnostic eglfs-kms-pi4.json -> eglfs-kms.json.
- Detection: install.sh / upgrade_containers.sh (aarch64 Pi 3 -> pi3-64).
- Runtime: media_player force_mpv set (selects MPVMediaPlayer, the
  QtMultimedia D-Bus shim); processing codec grid {'h264'}.
- CI: docker-build matrix + mirror-latest-tags.
- Balena (fleet screenly_ose/anthias-pi3-64, device type raspberrypi3-64):
  disk-image + manual-deploy workflows, balena_ota_deploy.sh,
  balena_fleet_maintenance.py, balena_unpin_devices.py, deploy_to_balena.sh,
  balena-host-config.json.
- Pi Imager: SUPPORTED_BOARDS += pi3-64 (non-maintenance); pi3 stays legacy.
- Docs + tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@vpetersson
vpetersson force-pushed the feature/pi3-64-qt6-board branch from edc37af to d8538ad Compare June 3, 2026 05:07
vpetersson and others added 2 commits June 6, 2026 18:19
Copilot review: the list is introduced as 'links to the images', so the
new pi3-64 entry should be navigable like the surrounding bullets. Link
the label to the release-images section.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Wires the anthias-rockpi4 balena fleet (device type rockpi-4b-rk3399)
into the OTA deploy + disk-image pipeline. The fleet has no
board-specific image build: it runs the generic arm64 containers, so
bin/balena_ota_deploy.sh / bin/deploy_to_balena.sh map the rockpi4
board to the <short-hash>-arm64 image tags (and strip the /dev/vchiq
mount — no VideoCore on RK3399), and the disk-image preflight verifies
the arm64 images exist.

Root-cause fix for the fleet's codec gate: balena ships no
anthias_host_agent service, so host:board_subtype was never published
and resolve_device_key() stayed 'arm64' — whose HW-decode set is empty,
rejecting every video upload. The model-string → subtype table moves to
the dependency-free anthias_common.device_helper.detect_board_subtype
(single source, imported by host_agent), and
anthias_common.board.get_board_subtype now falls back to reading
/proc/device-tree/model in-container when Redis has no value. The
device tree is kernel-global — the same mechanism get_device_type has
always used for Pi detection — so the rockpi4 fleet resolves its
{h264, hevc} envelope without a host-side daemon, and compose installs
whose host_agent died self-heal too.

- build-balena-disk-image.yaml: rockpi4 in both matrices, fleet +
  rockpi-4b-rk3399 image cases, arm64 images in the preflight check.
- deploy-balena-manual.yaml: rockpi4 board option.
- balena-host-config.json: rockpi4 declared {} (config.txt is
  RPi-only; the reconcile hard-fails on a missing key).
- balena_fleet_maintenance.py / balena_unpin_devices.py: fleet added.
- tests: get_board_subtype Redis-first + device-tree-fallback order;
  detect_board_subtype patch targets follow the move.
- docs: board-enablement, balena-fleet-host-config,
  installation-options.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@vpetersson vpetersson changed the title feat(viewer,build): add arm64/Qt6 pi3-64 board; keep 32-bit pi3 as legacy feat(viewer,build,balena): add arm64/Qt6 pi3-64 board and the Rock Pi 4 fleet; keep 32-bit pi3 as legacy Jun 6, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jun 6, 2026

Copy link
Copy Markdown

@vpetersson
vpetersson merged commit 10c68b2 into master Jun 7, 2026
10 checks passed
@vpetersson vpetersson mentioned this pull request Jun 7, 2026
5 tasks
vpetersson added a commit that referenced this pull request Jun 7, 2026
- CalVer (YYYY.0M.MICRO); still June 2026, micro 1 -> 2
- Ships the Qt 6 video audio fix (#3001) — PulseAudio in the viewer
  container; videos were silent on pi4-64/pi5/x86/arm64 since the
  QtMultimedia migration
- Adds the arm64/Qt6 pi3-64 board and the Rock Pi 4 fleet (#2985)
- Page-load watchdog so a stalled fetch can't freeze the display
  (#3003), Sentry error tracking for the Django services (#3007)
- Redis data persisted to the mounted volume so device identity
  survives recreation (#2983); unpinner also rolls OS + supervisor
  updates (#2984)
- Streamed backup downloads (#3005), 12-hour AM/PM asset times
  (#3002), BuildKit frontend via mirror.gcr.io (#3008)

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
vpetersson added a commit that referenced this pull request Jul 5, 2026
* fix(build): ship +rpt1 ffmpeg on pi3-64 so H.264 uses HW decode

- pi3-64 was omitted from the _rpt1-ffmpeg-pin.j2 board gate, so it
  shipped stock Debian ffmpeg (no v4l2_request/SAND/vout-drm) while
  its sibling pi4-64 got the Pi-patched +rpt1 build
- consequence: QtMultimedia can't engage the VideoCore IV bcm2835
  H.264 decoder and falls back to software; SW can't sustain 1080p30
  on the Pi 3 (measured 0.74x realtime) and drops frames / stalls
  presentation under load (issue #3084)
- the gate (#2885) predates the pi3-64 board (#2985), which was
  grouped with pi4-64 everywhere else; add pi3-64 here too
- verified via rendered Dockerfiles: pi3-64/pi4-64/pi5/arm64 now
  carry the +rpt1 pin; x86 and the Qt5 boards (pi2/pi3) stay on stock

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* docs(build): clarify pi3-64 uses V4L2 M2M, not v4l2_request

Copilot review: the Pi 3-64 comment implied the HW-decode path is
v4l2_request/DRM. It's actually the stateful bcm2835-codec V4L2 M2M
decoder (h264_v4l2m2m). Reword to say so and anchor to the on-device
result (stock=SW, +rpt1=/dev/video10). Comment-only; gate unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants