From 2087ce22c123bdbc78de2513711639135b0f2dd2 Mon Sep 17 00:00:00 2001 From: Hebezo Date: Fri, 27 Feb 2026 00:21:10 +0100 Subject: [PATCH 1/3] feat: add QR scanner view and improve registration logic for QR callback --- custom_components/siedle/__init__.py | 347 +++++++++++++++++++++++- custom_components/siedle/config_flow.py | 42 +-- 2 files changed, 370 insertions(+), 19 deletions(-) diff --git a/custom_components/siedle/__init__.py b/custom_components/siedle/__init__.py index cb65673..a303701 100644 --- a/custom_components/siedle/__init__.py +++ b/custom_components/siedle/__init__.py @@ -91,12 +91,20 @@ async def async_setup(hass: HomeAssistant, config: dict): """Set up the Siedle component.""" hass.data.setdefault(DOMAIN, {}) - # Register QR callback API endpoint only once - if not any(isinstance(view, SiedleQRCallbackView) for view in hass.http.app.router._resources): + # Register QR callback and scanner API endpoints only once + registered_names = {getattr(r, 'name', None) for r in hass.http.app.router._resources} + + if "api:siedle:qr_callback" not in registered_names: _LOGGER.info("Registering Siedle QR callback view at /api/siedle/qr_callback") hass.http.register_view(SiedleQRCallbackView()) else: - _LOGGER.info("Siedle QR callback view already registered") + _LOGGER.debug("Siedle QR callback view already registered") + + if "api:siedle:qr_scanner" not in registered_names: + _LOGGER.info("Registering Siedle QR scanner view at /api/siedle/qr_scanner") + hass.http.register_view(SiedleQRScannerView()) + else: + _LOGGER.debug("Siedle QR scanner view already registered") return True @@ -709,6 +717,339 @@ async def _async_update_data(self): raise UpdateFailed(f"Error communicating with API: {err}") +QR_SCANNER_HTML = """ + + + + + Siedle QR-Code Scanner + + + +
+
+

🔐 Siedle QR-Code Scanner

+

Scannen Sie Ihren Siedle QR-Code

+
+
+
+ ⚠️ Kein HTTPS: Die Kamera benötigt eine sichere Verbindung (HTTPS). + Bitte verwenden Sie die manuelle Eingabe unten oder richten Sie HTTPS ein + (z.B. über Nabu Casa oder einen Reverse Proxy). +
+ +
+
+
+
+ Kamera wird initialisiert... +
+ +
+
+
QR-Code Inhalt einfügen
+ +
+ 💡 So geht's: Öffnen Sie eine QR-Scanner App (z.B. die Kamera-App), + scannen Sie den Siedle QR-Code und kopieren Sie den erkannten Text. + Fügen Sie ihn dann hier ein. +
+ +
+
+
+ + + +""" + + +class SiedleQRScannerView(HomeAssistantView): + """Serve the QR code scanner page directly from Home Assistant.""" + + url = "/api/siedle/qr_scanner" + name = "api:siedle:qr_scanner" + requires_auth = False + + async def get(self, request): + """Serve the QR scanner HTML page.""" + from aiohttp import web + + config_flow_id = request.query.get("config_flow_id", "") + callback_url = request.query.get("callback_url", "") + + # If callback_url not provided as parameter, derive from request + if not callback_url: + scheme = request.headers.get("X-Forwarded-Proto", request.url.scheme) + host = request.headers.get("X-Forwarded-Host", request.host) + callback_url = f"{scheme}://{host}/api/siedle/qr_callback" + _LOGGER.debug("Derived callback_url from request: %s", callback_url) + + try: + html_content = QR_SCANNER_HTML.replace( + "%%CONFIG_FLOW_ID%%", config_flow_id + ).replace( + "%%CALLBACK_URL%%", callback_url + ) + return web.Response(text=html_content, content_type="text/html") + except Exception as err: + _LOGGER.error("Error building QR scanner page: %s", err) + return web.Response(text="Internal error", status=500) + + class SiedleQRCallbackView(HomeAssistantView): """Handle QR code callback from external scanner.""" diff --git a/custom_components/siedle/config_flow.py b/custom_components/siedle/config_flow.py index 8ab7903..063ae8f 100644 --- a/custom_components/siedle/config_flow.py +++ b/custom_components/siedle/config_flow.py @@ -81,30 +81,40 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle the initial step - redirect to external QR scanner.""" - # Stelle sicher, dass die QR Callback View registriert ist - from . import SiedleQRCallbackView + # Stelle sicher, dass die QR Views registriert sind + from . import SiedleQRCallbackView, SiedleQRScannerView try: - view_registered = any( - hasattr(view, 'name') and view.name == "api:siedle:qr_callback" - for view in self.hass.http.app.router._resources - ) - if not view_registered: - _LOGGER.info("Registering Siedle QR callback view at /api/siedle/qr_callback") + registered_names = { + getattr(r, 'name', None) for r in self.hass.http.app.router._resources + } + if "api:siedle:qr_callback" not in registered_names: + _LOGGER.info("Registering Siedle QR callback view") self.hass.http.register_view(SiedleQRCallbackView()) - else: - _LOGGER.debug("Siedle QR callback view already registered") + if "api:siedle:qr_scanner" not in registered_names: + _LOGGER.info("Registering Siedle QR scanner view") + self.hass.http.register_view(SiedleQRScannerView()) except Exception as e: _LOGGER.error("Error registering view: %s", e) # Build callback URL for QR scanner - base_url = self.hass.config.external_url or self.hass.config.internal_url - if not base_url: - base_url = "http://homeassistant.local:8123" + # Camera requires HTTPS (secure context). Prefer local HTTPS, + # fall back to external hosted scanner if only HTTP is available. + external = self.hass.config.external_url + internal = self.hass.config.internal_url + base_url = external or internal or "http://homeassistant.local:8123" flow_id = self.flow_id - callback_url = quote(f"{base_url}/api/siedle/qr_callback", safe=':/') - qr_scanner_url = f"{CONF_QR_CODE_URL}?config_flow_id={flow_id}&callback_url={callback_url}" - + callback_url = quote(f"{base_url}/api/siedle/qr_callback", safe='') + + # Check if we have HTTPS available -> use local scanner + if (external and external.startswith("https://")) or \ + (internal and internal.startswith("https://")): + # Use HTTPS URL for local scanner page + https_base = external if (external and external.startswith("https://")) else internal + qr_scanner_url = f"{https_base}/api/siedle/qr_scanner?config_flow_id={flow_id}&callback_url={callback_url}" + else: + # No HTTPS - fall back to external hosted scanner page + qr_scanner_url = f"{CONF_QR_CODE_URL}?config_flow_id={flow_id}&callback_url={callback_url}" return self.async_external_step( step_id="qr_scan", url=qr_scanner_url, From eb88b970d6f9527a87e933b1304a05fc814238aa Mon Sep 17 00:00:00 2001 From: Hebezo Date: Fri, 27 Feb 2026 00:24:04 +0100 Subject: [PATCH 2/3] docs: update QR scanner instructions for HTTPS, HTTP, and manual input methods --- README.md | 5 ++++- README_DE.md | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8ce9049..4c4ccc7 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,10 @@ Or manually in HACS: 1. In HA, go to **Settings → Devices & Services → Add Integration** 2. Search for **"Siedle"** -3. You'll be redirected to a QR scanner page +3. You'll be redirected to a QR scanner page: + - **HTTPS setup** (e.g., Nabu Casa, Reverse Proxy): The QR scanner page is served directly by your Home Assistant instance. The phone camera can scan the QR code directly. + - **HTTP setup** (local network without HTTPS): Since the browser camera requires a secure context (HTTPS), you will be redirected to an external scanner page hosted at `stefan-altheimer.de`. The scanned data is sent back to your local HA instance via the callback URL. + - **Manual input**: If the camera doesn't work (e.g., desktop browser, no camera permission), the scanner page offers a manual input field. Simply scan the QR code with any QR scanner app on your phone, copy the text content and paste it into the field. 4. Scan the QR code from the Siedle app (with the second device or webcam) 5. The data will be automatically transmitted to HA diff --git a/README_DE.md b/README_DE.md index 7488fd4..03425aa 100644 --- a/README_DE.md +++ b/README_DE.md @@ -77,7 +77,10 @@ Oder manuell in HACS: 1. Gehe in HA zu **Einstellungen → Geräte & Dienste → Integration hinzufügen** 2. Suche nach **"Siedle"** -3. Du wirst zu einer QR-Scanner-Seite weitergeleitet +3. Du wirst zu einer QR-Scanner-Seite weitergeleitet: + - **HTTPS-Setup** (z.B. Nabu Casa, Reverse Proxy): Die QR-Scanner-Seite wird direkt von deiner Home Assistant-Instanz ausgeliefert. Die Handy-Kamera kann den QR-Code direkt scannen. + - **HTTP-Setup** (lokales Netzwerk ohne HTTPS): Da die Browser-Kamera einen sicheren Kontext (HTTPS) benötigt, wirst du auf eine extern gehostete Scanner-Seite unter `stefan-altheimer.de` weitergeleitet. Die gescannten Daten werden über die Callback-URL an deine lokale HA-Instanz zurückgesendet. + - **Manuelle Eingabe**: Falls die Kamera nicht funktioniert (z.B. Desktop-Browser, keine Kamera-Berechtigung), bietet die Scanner-Seite ein manuelles Eingabefeld. Scanne den QR-Code einfach mit einer beliebigen QR-Scanner-App auf deinem Handy, kopiere den Textinhalt und füge ihn in das Feld ein. 4. Scanne den QR-Code von der Siedle App (mit dem zweiten Gerät oder der Webcam) 5. Die Daten werden automatisch an HA übermittelt From f7d0f80f7f2f93807e5c632ae6a7ab95d690b4fc Mon Sep 17 00:00:00 2001 From: Stefan Altheimer Date: Fri, 27 Feb 2026 00:32:44 +0100 Subject: [PATCH 3/3] Update custom_components/siedle/config_flow.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- custom_components/siedle/config_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/siedle/config_flow.py b/custom_components/siedle/config_flow.py index 063ae8f..1d5c931 100644 --- a/custom_components/siedle/config_flow.py +++ b/custom_components/siedle/config_flow.py @@ -81,7 +81,7 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle the initial step - redirect to external QR scanner.""" - # Stelle sicher, dass die QR Views registriert sind + # Ensure that the QR views are registered from . import SiedleQRCallbackView, SiedleQRScannerView try: registered_names = {