Skip to content

feat(settings): experimental HDMI-CEC display on/off#2886

Merged
vpetersson merged 5 commits into
masterfrom
cec-display-power
May 13, 2026
Merged

feat(settings): experimental HDMI-CEC display on/off#2886
vpetersson merged 5 commits into
masterfrom
cec-display-power

Conversation

@vpetersson
Copy link
Copy Markdown
Contributor

@vpetersson vpetersson commented May 13, 2026

Issues Fixed

Closes #2575.

Description

Adds a manual Turn display on / Turn display off control to the Settings page, gated behind an Experimental badge per Viktor's note on the issue that CEC is unreliable in practice.

  • New section sits under System controls, hidden when neither /dev/cec0 nor /dev/vchiq is present (covers most x86 PCs).
  • Synchronous CEC subprocess (mirroring the existing get_display_power() pattern) — short timeout, no Celery hop, so the success/failure surfaces immediately as a toast (the feedback loop the issue asks for).
  • Parallel POST /api/v2/display/<on|off> mirroring how Reboot / Shutdown are dual-exposed.

Test plan

  • Unit: pytest -m "not integration" — all pass.
  • Integration (Playwright + Chromium): four new tests covering hidden state, visible state, error toast, success toast — all pass.
  • Lint: ruff check + ruff format --check clean.
  • End-to-end on a real Raspberry Pi with a connected HDMI-CEC TV.
  • x86 sanity check (section should be hidden by default).

Screenshots

Local Playwright runs write screenshots to test-artifacts/cec/ (gitignored). The Upload integration test artifacts step in .github/workflows/test-runner.yml is gated on if: failure(), so these always-on captures are not surfaced by green CI runs — they're for the contributor's local review.

🤖 Generated with Claude Code

- Add `set_display_power(on)` + `cec_available()` to `lib/diagnostics`,
  mirroring the existing CEC-subprocess-with-timeout pattern.
- Add `/settings/display/<on|off>/` Django POST handler that surfaces
  success / failure to the operator as a toast (issue #2575 asks for a
  visible feedback loop).
- Add `POST /api/v2/display/<state>` mirroring reboot / shutdown; 200 on
  success, 502 on CEC failure, 400 on invalid state.
- Render-time gate via `cec_available()`: section is hidden when neither
  `/dev/cec0` nor `/dev/vchiq` exists (covers most x86 PCs).
- Mark the new section "Experimental" with an amber warning chip using
  the existing `--color-warning-soft` / `--color-warning-strong` tokens.
- Tests: unit (diagnostics, Django view, v2 API) + Playwright
  integration (hidden state, visible state, error toast, success toast).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vpetersson vpetersson requested a review from a team as a code owner May 13, 2026 12:00
@vpetersson vpetersson self-assigned this May 13, 2026
@vpetersson vpetersson requested a review from Copilot May 13, 2026 12:00
CI's `ruff format --check` flagged the five files added in the previous
commit. No functional changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an experimental HDMI-CEC “Display power” control to Anthias, exposed both via the Settings UI and a new v2 API endpoint, with render-time gating based on presence of common CEC device nodes.

Changes:

  • Add synchronous diagnostics.set_display_power() and diagnostics.cec_available() helpers for HDMI-CEC power on/off.
  • Add Settings-page POST handler + UI section (with “Experimental” badge) for turning the display on/off.
  • Add POST /api/v2/display/<on|off> endpoint mirroring the Settings action, plus unit/integration test coverage.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/test_template_views.py Adds POST view tests for the new settings display-power action.
tests/test_diagnostics.py Adds unit tests for set_display_power() and cec_available().
tests/test_app.py Adds Playwright integration coverage for hidden/visible states and toast behavior.
src/anthias_server/lib/diagnostics.py Implements CEC power set script, synchronous subprocess wrapper, and cec_available() gate.
src/anthias_server/app/views.py Adds settings_display_power POST view that flashes success/error toasts.
src/anthias_server/app/urls.py Registers the new settings route settings/display/<state>/.
src/anthias_server/app/templates/settings.html Renders the new “Display power” section when cec_available is true.
src/anthias_server/app/static/sass/_styles.scss Adds styling for the “Experimental” badge.
src/anthias_server/app/page_context.py Adds cec_available to the settings page context.
src/anthias_server/api/views/v2.py Exposes a v2 view class for display power.
src/anthias_server/api/views/mixins.py Adds DisplayPowerViewMixin POST handler for /api/v2/display/<state>.
src/anthias_server/api/urls/v2.py Registers the new v2 route v2/display/<state>.
src/anthias_server/api/tests/test_v2_endpoints.py Adds API tests for success/failure/invalid state cases.
src/anthias_server/api/serializers/mixins.py Adds an (empty) serializer mixin for schema consistency.

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

Comment thread src/anthias_server/app/views.py
Comment thread src/anthias_server/api/views/mixins.py
Comment thread src/anthias_server/lib/diagnostics.py
Comment thread tests/test_app.py Outdated
- views.py / mixins.py: short-circuit display-power POST/API on
  cec_available()=False so a stale form / direct curl can't burn a
  10 s libcec subprocess on non-CEC hardware. API now returns 503.
- diagnostics.set_display_power: when the subprocess emits neither
  the OK nor ERROR sentinel, fall back to the last stderr line (or
  the returncode) so the operator gets an actionable failure detail
  instead of a generic "unexpected CEC response." Message capped at
  ~240 chars to keep toasts / JSON responses sane.
- tests/test_app.py: write screenshots to relative `test-artifacts/cec`
  instead of an absolute `/usr/src/app/...` path so local runs and
  alternative CI layouts pick them up via the same `--output
  test-artifacts` convention pytest-playwright already uses.
- Tests updated to cover the new cec_available() guard (HTML + v2 API)
  and the new stderr / returncode fallbacks in set_display_power.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment thread src/anthias_server/api/views/mixins.py
Comment thread src/anthias_server/api/serializers/mixins.py Outdated
Copilot follow-up on PR #2886.

- `DisplayPowerViewSerializerMixin` was empty (matching the pattern
  the Reboot / Shutdown mixins use), but unlike those this endpoint
  *does* return a JSON body — `{message: ...}` on every status code.
  Add a read-only `message` field so drf-spectacular has a real shape
  to render.
- Declare `responses={200, 400, 502, 503: ...}` on the view's
  `@extend_schema` so the generated OpenAPI document mirrors what
  clients will actually receive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment thread src/anthias_server/lib/diagnostics.py Outdated
Comment thread src/anthias_server/app/templates/settings.html Outdated
Two Copilot follow-ups on PR #2886.

- set_display_power: the ERROR: sentinel branch previously returned the
  raw remainder of stdout. A chatty libcec build could blow up the
  toast / JSON response with a multi-line / multi-kilobyte payload.
  Extract the cap + last-line trim into `_trim_cec_detail` and apply
  it to both the ERROR branch and the unexpected-output fallback.
- settings.html: the "Display power" heading concatenated the title
  and the "Experimental" badge with no whitespace, so screen readers
  and copy/paste yielded "Display powerExperimental". Add a literal
  space + an explicit aria-label on the badge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vpetersson vpetersson requested a review from Copilot May 13, 2026 13:18
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comment thread tests/test_app.py
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

@vpetersson vpetersson merged commit 664466d into master May 13, 2026
17 checks passed
vpetersson added a commit that referenced this pull request May 13, 2026
Copilot follow-up on PR #2886. The `Upload integration test artifacts`
step in test-runner.yml runs only on `if: failure()`, so the always-on
screenshots are not surfaced on green CI runs. Reword the in-file
comment (and the PR body) to set accurate expectations: they're for
local-run review, not a CI deliverable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
vpetersson added a commit that referenced this pull request May 14, 2026
* chore(cec): tidy up follow-ups from PR #2886

Three small cleanups deferred from the CEC display-power PR:

- Gate the Playwright screenshot captures behind a
  `PYTEST_CAPTURE_SCREENSHOTS=1` env var. They were useful for the
  one-time UX review but ran on every CI cycle for no reason: the
  test-runner.yml upload-artifact step is `if: failure()`, so the
  PNGs on green runs got written and immediately discarded. Default
  is now off; set the env var for local UX work.
- Drop `test_display_power_success_toast_appearance`. It drove
  `Alpine.store('toasts').push('success', ...)` directly because the
  test container can't perform a real CEC success, so its only
  novel assertion was that the existing toast store accepts a
  'success' kind — already exercised by every other toast in the
  app. It only existed to capture a success-state screenshot;
  with the screenshot motivation gone, the test goes too.
- Declare `responses={200: None}` on RebootViewMixin /
  ShutdownViewMixin so the generated OpenAPI schema matches the
  empty response body those endpoints actually return. Aligns the
  pattern with DisplayPowerViewMixin (PR #2886) which declared its
  responses explicitly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(cec): parse PYTEST_CAPTURE_SCREENSHOTS as explicit truthy

`bool(os.environ.get(...))` enables the gate for any non-empty value
including '0' and 'false', which surprises anyone setting it to disable.
Switch to an explicit allowlist ('1', 'true', 'yes', 'on', case-insensitive).

Addresses Copilot review on #2887.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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] Blank screen after the display was turned of and then off again

2 participants