From a05132b777486b8a96d597f9a610cf426cb014f8 Mon Sep 17 00:00:00 2001 From: HomeCore Admin Date: Wed, 16 Sep 2026 14:43:43 +0000 Subject: [PATCH] HomeCore Controller v1.0.2 staged from HomeCore Admin --- homecore_controller/CHANGELOG.md | 17 ++++++ homecore_controller/app/main.py | 88 +++++++++++++++++++++++++++----- homecore_controller/config.yaml | 2 +- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/homecore_controller/CHANGELOG.md b/homecore_controller/CHANGELOG.md index f652a20..c4ef831 100644 --- a/homecore_controller/CHANGELOG.md +++ b/homecore_controller/CHANGELOG.md @@ -1,5 +1,22 @@ # HomeCore Controller Changelog +## 1.0.2 + +- Reworks HomeCore setup and Settings from Waterbeck field testing. +- Adds a real Add Devices flow: HomeCore now scans supported Home Assistant entities and lets the customer explicitly add selected devices. +- Splits Devices into My Devices and Add Devices, with HomeCore name, room assignment, favourites, rename and remove controls. +- Removing a device from HomeCore leaves the underlying Home Assistant device/configuration untouched. +- Removes room icon selection; room creation and editing now use the room name only. +- Reorganises Settings into Home, Rooms, Devices, People & access, Activity and Controller. +- Adds a first-run setup panel guiding the customer to create rooms and add devices. +- Replaces the Local indicator with Cloud connected / Syncing / Offline states. +- Removes duplicate Overview navigation when there are no rooms; one-room navigation is simplified. +- Improves empty Overview cards and setup/activity empty states. +- Removes duplicate home-name presentation from Overview; the saved HomeCore home name remains in the header. +- Adds explicit Home Assistant device-scan diagnostics so an empty device list is no longer silent. +- Preserves the separate HomeCore Updater and manual per-controller Admin deployment architecture. +- Automatic rollout remains disabled. + ## 1.0.1 - Repackages the complete HomeCore 1.0 product as a repository-safe update from 0.1.18. diff --git a/homecore_controller/app/main.py b/homecore_controller/app/main.py index 13f11c6..9aaeea6 100644 --- a/homecore_controller/app/main.py +++ b/homecore_controller/app/main.py @@ -17,7 +17,7 @@ from cryptography.hazmat.primitives import serialization import uvicorn from updater import start_update_manager -VERSION = "1.0.1" +VERSION = "1.0.2" print( "HOMECORE SUPERVISOR: token " @@ -206,7 +206,7 @@ CATEGORY_LABELS = { SESSIONS = {} LOCK = threading.RLock() -APP_HTML = '\n\n\n\n\nHomeCore\n\n\n\n
Loading home…
\n
\n
HomeCore

Settings

\n
\n
◉
HomeCore Intercom
Someone is at the door
\n' +APP_HTML = '\n\n\n\n\nHomeCore\n\n\n\n
Loading home…
\n
\n
HomeCore

Settings

\n
\n
◉
HomeCore Intercom
Someone is at the door
\n\n\n' def build_router(version, data_dir, load_state, options, ha_version): @@ -499,21 +499,33 @@ def build_router(version, data_dir, load_state, options, ha_version): return ["view"] return [] - def normalize(raw, product): + def normalize(raw, product, include_unadded=False): entity_id = str(raw.get("entity_id") or "") if "." not in entity_id: return None + domain = entity_id.split(".", 1)[0] if domain not in DEVICE_DOMAINS: return None attrs = raw.get("attributes") or {} category = category_for(entity_id, attrs) + + # Ignore binary/event noise unless it is a real doorbell/intercom signal. if category == "other" and domain in ("binary_sensor", "event"): return None override = (product.get("devices") or {}).get(entity_id, {}) - if override.get("hidden"): + + # A device becomes part of HomeCore only when the customer explicitly + # adds it. Legacy configured entries from 1.0.1 count as added. + added = bool(override.get("added")) + if override and "added" not in override: + added = True + + if not include_unadded and not added: + return None + if not include_unadded and override.get("hidden"): return None name = ( @@ -532,6 +544,7 @@ def build_router(version, data_dir, load_state, options, ha_version): "state": raw.get("state"), "room_id": override.get("room_id") or "", "favorite": bool(override.get("favorite")), + "added": added, "available": raw.get("state") not in ("unavailable", "unknown"), "last_changed": raw.get("last_changed"), "last_updated": raw.get("last_updated"), @@ -558,17 +571,23 @@ def build_router(version, data_dir, load_state, options, ha_version): ) return item + def all_homeassistant_states(): + states = ha_request("GET", "/states") or [] + if not isinstance(states, list): + raise RuntimeError("Home Assistant did not return an entity-state list") + return states + def get_devices(ctx=None): product = load_product() - states = ha_request("GET", "/states") or [] out = [] - for raw in states: - item = normalize(raw, product) + for raw in all_homeassistant_states(): + item = normalize(raw, product, include_unadded=False) if not item: continue if ctx and not can_view(ctx, item["category"]): continue out.append(item) + out.sort( key=lambda x: ( x["room_id"], @@ -578,6 +597,27 @@ def build_router(version, data_dir, load_state, options, ha_version): ) return out + def get_discovered_devices(ctx=None): + product = load_product() + states = all_homeassistant_states() + out = [] + for raw in states: + item = normalize(raw, product, include_unadded=True) + if not item: + continue + if ctx and not can_view(ctx, item["category"]): + continue + out.append(item) + + out.sort( + key=lambda x: ( + 0 if x.get("added") else 1, + x["category"], + x["name"].lower(), + ) + ) + return states, out + def overview(devices): lighting = [x for x in devices if x["category"] == "lighting"] heating = [x for x in devices if x["category"] == "heating"] @@ -835,6 +875,18 @@ def build_router(version, data_dir, load_state, options, ha_version): ctx = auth_context(request) return {"devices": get_devices(ctx)} + @router.get("/api/v1/device-discovery") + def device_discovery(request: Request): + ctx = auth_context(request) + require_owner(ctx) + states, discovered = get_discovered_devices(ctx) + return { + "total_states": len(states), + "supported_count": len(discovered), + "added_count": sum(1 for x in discovered if x.get("added")), + "devices": discovered, + } + @router.get("/api/v1/devices/{entity_id:path}") def device(entity_id: str, request: Request): ctx = auth_context(request) @@ -1039,7 +1091,6 @@ def build_router(version, data_dir, load_state, options, ha_version): room = { "id": "room_" + uuid.uuid4().hex[:12], "name": name, - "icon": str(body.get("icon") or "⌂")[:8], "order": len(product["rooms"]), } product["rooms"].append(room) @@ -1075,10 +1126,6 @@ def build_router(version, data_dir, load_state, options, ha_version): "Room name is required", ) room["name"] = name[:60] - if "icon" in body: - room["icon"] = str( - body.get("icon") or "⌂" - )[:8] if "order" in body: room["order"] = int(body["order"]) @@ -1149,6 +1196,10 @@ def build_router(version, data_dir, load_state, options, ha_version): value["favorite"] = bool( body["favorite"] ) + if "added" in body: + value["added"] = bool( + body["added"] + ) if "hidden" in body: value["hidden"] = bool( body["hidden"] @@ -1166,6 +1217,19 @@ def build_router(version, data_dir, load_state, options, ha_version): "settings": value, } + @router.get("/api/v1/cloud/status") + def cloud_status(request: Request): + auth_context(request) + api = options()["cloud_api"].rstrip("/") + try: + response = httpx.get(api + "/health", timeout=5) + return { + "connected": response.status_code < 500, + "status": "connected" if response.status_code < 500 else "offline", + } + except Exception: + return {"connected": False, "status": "offline"} + @router.get("/api/v1/cloud/access") def cloud_access(request: Request): ctx = auth_context(request) diff --git a/homecore_controller/config.yaml b/homecore_controller/config.yaml index 5fd6b51..c407e54 100644 --- a/homecore_controller/config.yaml +++ b/homecore_controller/config.yaml @@ -1,5 +1,5 @@ name: HomeCore Controller -version: "1.0.1" +version: "1.0.2" slug: homecore_controller description: HomeCore whole-home control system for Home Assistant Green. url: https://homecore.home-core.co.uk