Skip to content

fix(cec): support HDMI-CEC display power on Pi 4/5 second micro-HDMI port#3085

Open
federicopratto wants to merge 3 commits into
Screenly:masterfrom
federicopratto:fix/cec-display-power-pi4-pi5
Open

fix(cec): support HDMI-CEC display power on Pi 4/5 second micro-HDMI port#3085
federicopratto wants to merge 3 commits into
Screenly:masterfrom
federicopratto:fix/cec-display-power-pi4-pi5

Conversation

@federicopratto

@federicopratto federicopratto commented Jun 18, 2026

Copy link
Copy Markdown

Fixes #2863

What this fixes

HDMI-CEC display power (#2886, experimental) still doesn't work on Raspberry Pi 4/5 when the display is connected to the second micro-HDMI port. Symptom: "Display Power (CEC): CEC error" / "Unknown" in System Info, and the Settings buttons do nothing.

Root cause

Pi 4 and Pi 5 each expose two kernel CEC adapters, one per micro-HDMI port (/dev/cec0, /dev/cec1), but only the port that's actually connected gets a valid physical address — the other one stays at f.f.f.f. The Python cec library Anthias uses doesn't enumerate both: it specifically looks at /dev/cec0. The fix in #2863 mounts both real paths into the container, but that's not enough — if the TV is on the second port, cec.init() either opens the wrong adapter, or finds nothing at all.

Confirmed on hardware (Raspberry Pi 5, kernel 6.18.34-rpt-rpi-2712):

$ cec-ctl -d /dev/cec0 --playback --logical-address
Physical Address           : f.f.f.f
$ cec-ctl -d /dev/cec1 --playback --logical-address
Physical Address           : 3.0.0.0
Logical Address            : 4 (Playback Device 1)

Fix

bin/upgrade_containers.sh now uses cec-ctl to detect which of the two /dev/cecN devices reports a valid physical address, and remaps it to the fixed path /dev/cec0 inside the container — regardless of which real index it has on the host. If cec-ctl isn't available, it falls back to the previous behavior (mounting both under their real names: degraded, not broken). The pi5 logic is also extended to pi4-64, since ansible/roles/system/templates/config.txt.j2 confirms both use vc4-kms-v3d.

diagnostics.py::cec_available() now also recognizes /dev/cec1, as a safeguard for the fallback scenario above.

Testing

  • Verified end-to-end on real Raspberry Pi 5 hardware: both the query (is_on()) and the commands (standby() / power_on()) — the TV physically turns off and on.
  • New unit test test_cec_available_true_when_only_cec1_present in tests/test_diagnostics.py — passes (37/37 in the file).
  • New Playwright integration test test_display_power_section_visible_with_cec1_only (+ cec1_stub_device fixture) in tests/test_app.py, following the same pattern as the existing /dev/vchiq test. I couldn't run it locally due to a Docker infrastructure issue unrelated to this change (Dockerfile rendering for the x86 target); it should run fine in CI — confirmation during review would be appreciated.
  • The pi4-64 extension is justified by architecture but not verified on real Pi 4 hardware. Confirmation from someone with one on hand is very welcome.

Files changed

  • bin/upgrade_containers.sh
  • src/anthias_server/lib/diagnostics.py
  • tests/test_diagnostics.py
  • tests/test_app.py

@federicopratto federicopratto requested a review from a team as a code owner June 18, 2026 16:07
@vpetersson

Copy link
Copy Markdown
Contributor

Thank you for your contribution, but could you please translate this to English? We can't accept contributions with Spanish comments in the code base or the PR.

@vpetersson vpetersson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for digging into this — the diagnostics change and the new tests are good. But there's a hard blocker in bin/upgrade_containers.sh plus a couple of things worth confirming before this can land. Details inline.

Summary

  • 🔴 The x86|arm64 branch body is a literal ... placeholder — it aborts the upgrade (#!/bin/bash -e, ... → command-not-found → exit 127) on every x86/arm64 device, and deletes the logic that strips the /dev/vchiq mount so those boards can boot at all. Must be restored.
  • 🟡 The primary remap path depends on cec-ctl being installed on the host, which Anthias doesn't manage — on a stock Pi OS host it silently falls back to the old broken behavior.
  • 🟡 The fallback mounts both /dev/cec0 and /dev/cec1 under devices:, which fails container start if a host exposes only one — and pi4-64 is unverified on real hardware.
  • 🟢 Minor: cec-ctl --playback is a mutating probe; Spanish comments in an otherwise-English file.

Comment thread bin/upgrade_containers.sh Outdated
Comment thread bin/upgrade_containers.sh
sed -i 's|/dev/vchiq:/dev/vchiq|/dev/cec0:/dev/cec0|g' \
pi4-64|pi5)
CEC_DEV=""
if command -v cec-ctl >/dev/null 2>&1; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 This remap path only triggers when cec-ctl exists on the host. cec-ctl ships in v4l-utils, which isn't installed by default on Raspberry Pi OS, and Anthias is userspace-only (doesn't manage host packages). On a stock Pi host this takes the else fallback — which, per your own comment below, won't work if the live port is /dev/cec1.

So for the majority of real devices this likely degrades to the old broken behavior. Can you confirm whether the target hosts actually ship cec-ctl? If not, the fix's real-world reach is much narrower than the PR implies and should be documented (or the detection moved somewhere that has cec-ctl available).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirmed, and you're right to push on this. Checked our own ansible
playbooks — grep -rn "v4l-utils\|cec-ctl\|cec-utils" ansible/
returns nothing, so Anthias's installer never provisions it. On my
test Pi 5, apt-mark showmanual lists v4l-utils as manually
installed, not part of the base image — I'd installed it myself while
diagnosing this, so a genuinely stock host almost certainly doesn't
have it.

So yes: on a typical install today, this falls through to the
degraded fallback in most cases, same as you suspected. Two ways I
see to actually close that gap rather than just document it:

  1. Have upgrade_containers.sh install v4l-utils itself on
    pi4-64|pi5 when cec-ctl is missing — but that crosses the
    "Anthias doesn't manage host packages" line you mentioned, so I
    don't want to do that without a green light.
  2. Run the cec-ctl probe from inside a throwaway container using
    the server image instead (adding v4l-utils to
    Dockerfile.server/Dockerfile.test, passing
    --device=/dev/cec0 --device=/dev/cec1 to a docker run), so
    detection never depends on anything installed on the host at all.

(2) feels more in line with the project's existing philosophy, but
it's more surface area than this PR currently touches. Happy to do
whichever you'd rather see — or land this as-is with the limitation
called out explicitly (comment + PR description) and open a
follow-up issue for the container-based probe.

Comment thread bin/upgrade_containers.sh Outdated
Comment thread bin/upgrade_containers.sh Outdated
Comment thread bin/upgrade_containers.sh Outdated
…p mutating CEC probe

- Restore the x86|arm64 case body that was accidentally replaced with
  a placeholder, breaking the upgrade on every non-Pi device.
- Guard each /dev/cecN mount in the pi4-64|pi5 fallback path with
  [ -e ], since devices: entries fail container start if a listed
  host path doesn't exist and pi4-64 isn't confirmed to always expose
  both nodes.
- Swap cec-ctl --playback for a bare invocation when probing physical
  address, avoiding the active CEC bus scan / logical-address claim
  that --playback (and --show-topology) trigger.
- Translate inline comments to English for consistency with the rest
  of the file.
@sonarqubecloud

Copy link
Copy Markdown

@federicopratto

Copy link
Copy Markdown
Author

waiting for the review...

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (master@3092ee2). Learn more about missing BASE report.

Additional details and impacted files
@@            Coverage Diff            @@
##             master    #3085   +/-   ##
=========================================
  Coverage          ?   86.56%           
=========================================
  Files             ?       61           
  Lines             ?     5902           
  Branches          ?      655           
=========================================
  Hits              ?     5109           
  Misses            ?      630           
  Partials          ?      163           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@vpetersson vpetersson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up review

Re-reviewed against the current head. Resolved the four threads whose fixes I confirmed in the code (the ... placeholder, the unconditional dual-node mount, the --playback mutating probe, and the Spanish comments). One substantive concern remains open, plus one nit.

🟡 The remap only fires when cec-ctl is on the host — so it doesn't work out-of-the-box for the case it targets

cec-ctl ships in v4l-utils, which Anthias never provisions (grep -rn "v4l-utils\|cec-ctl" ansible/ is empty) and, being userspace-only, doesn't manage. On a stock Raspberry Pi OS host, command -v cec-ctl fails and the code takes the else fallback.

On real Pi 4/5, both /dev/cec0 and /dev/cec1 always exist (one controller per micro-HDMI) regardless of what's plugged in — only the physical address differs. So the fallback mounts both under their real names, the container's cec.init() opens its hard-coded default /dev/cec0, and if the TV is on the second port that adapter is still f.f.f.f → the "CEC error" symptom persists, unchanged from before the PR. The happy path effectively only fires for users who manually installed v4l-utils.

Suggested direction: move the adapter selection into the container, where Anthias owns the toolchain — the cec==0.2.8 lib is already there. Mount both nodes unconditionally (guarded with [[ -e ]]) and have _CEC_SET_SCRIPT / _CEC_QUERY_SCRIPT in src/anthias_server/lib/diagnostics.py iterate the available adapters and pick the one whose physical address isn't f.f.f.f, instead of relying on a host binary that isn't installed. That makes the second-port case work regardless of host packaging and drops the host-side cec-ctl dependency entirely.

🟢 Minor: stale header comment

bin/upgrade_containers.sh:154-164 still says "Pi 5 also exposes /dev/cec1 … so we map both." The primary path no longer maps both — it remaps the single live node to /dev/cec0. Worth updating so the comment matches the new logic.

✅ Correct

  • pi4-64 is the right token (Pi 4 is always pi4-64); extending pi5pi4-64|pi5 is sound — both are vc4-kms-v3d.
  • The multi-line MOUNT_REPL sed (\n\1 per node, applied to both the server and celery vchiq lines) is correct GNU-sed, and the template mount lines (docker-compose.yml.tmpl:29,153) match the anchor exactly.
  • cec_available() adding /dev/cec1 and the two new tests are consistent.
  • Idempotent — compose is re-rendered from the template before the sed each run, so no accumulation.

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.

[BUG] CEC Error even changing computer monitor to regular TV

2 participants