Skip to content

feat: accept QR landing URL as encryption key, native scan in companion app - #133

Open
g4bri3lDev wants to merge 2 commits into
mainfrom
feat/qr-code-key-scan
Open

g4bri3lDev wants to merge 2 commits into
mainfrom
feat/qr-code-key-scan

Conversation

@g4bri3lDev

@g4bri3lDev g4bri3lDev commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

What

  • The encryption-key (setup) and reauth fields now accept either the 32-hex key or the opendisplay.org/l/?... link from the device's on-screen QR code. The link is decoded with py-opendisplay's parse_landing_url and checked against the OD###### device name.
  • New errors:
    • qr_wrong_device: the QR code belongs to another device.
    • qr_key_hidden: the QR code's key slot is all zeros. The firmware zero-fills it when the show_key_on_screen security flag is off, and the flow only asks for a key once the device required one, so the key is hidden rather than unset.
  • A small frontend module (frontend/qr-scan.js, registered via add_extra_js_url) adds a scan button to that field inside a companion app that reports hasBarCodeScanner. It opens the app's native scanner over the external bus (bar_code/scan). A scanned OpenDisplay link is put into the field and the step is submitted right away (via step-flow-form's submit()); the Python side decodes and validates it, so a wrong device or hidden key shows up as a form error. Any other QR code keeps the scanner open with "That's not an OpenDisplay QR code" (bar_code/notify). Everywhere else the field stays plain text, and a pasted link works too.
  • manifest.json: after_dependencies: ["frontend"].

Why the JS is needed

The app opens its scanner only when the frontend sends bar_code/scan, and the HA frontend only does that from the Z-Wave add-device dialog. There's no scan option for config-flow fields, and the frontend's barcode listeners are private, so the module wraps the external bus's receiveMessage and passes every message through so the frontend still acknowledges it. It depends on frontend internals (ha-selector-text, step-flow-form and its submit()) and falls back to the plain field if those change. add_extra_js_url modules are injected into index.html, so an app webview that was already open only shows the button after a reload.

Depends on

py-opendisplay 7.17.0 (OpenDisplay/py-opendisplay#165, parse_landing_url), pinned in manifest.json and pyproject.toml by the second commit.

Tests

  • tests/test_qr.py: hex keys, landing URLs, wrong device, hidden key, malformed input.
  • tests/test_frontend.py: static path + extra JS URL are registered once, and not at all on headless instances.
  • tests/test_config_flow.py: QR URL during setup, wrong-device/hidden-key/malformed errors, and reauth.

Manual testing

  • Tested with the iOS companion app against a dev instance: the scan button appears in the encryption-key field, and scanning the display's QR code fills in the key and submits the step.
  • Known quirk in the iOS app's own scanner (not this PR): if the phone reports "face up" when the scanner opens, e.g. when pointed down at a display on a desk, the camera preview is sideways, because the scanner screen is locked to portrait and the camera orientation isn't updated for face-up. Holding the phone upright when tapping scan avoids it.

@g4bri3lDev
g4bri3lDev force-pushed the feat/qr-code-key-scan branch from 48405e9 to c6f8bf3 Compare September 25, 2026 19:39
…on app

- Encryption-key and reauth fields now accept either the 32-hex key or the
  opendisplay.org/l/?... link from the device's on-screen QR code, decoded
  with py-opendisplay's parse_landing_url and checked against the OD######
  name. A QR code for another device gives qr_wrong_device; an all-zero key
  slot, which here means the device hides its key, gives qr_key_hidden.
- Ship a small frontend module (registered via add_extra_js_url) that, inside
  a companion app reporting hasBarCodeScanner, adds a scan button to that
  field and opens the native scanner over the external bus (bar_code/scan).
  A scanned OpenDisplay link is put into the field and the step is submitted
  right away; any other QR code keeps the scanner open with a message.
  Elsewhere the field stays plain text.
Adds parse_landing_url(), which the encryption-key and reauth fields use to
read the link from the device's on-screen QR code.
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.

1 participant