This commit is contained in:
Mateusz Gruszczyński
2026-08-24 23:58:44 +02:00
parent 9191fba212
commit 25e1a854e3
9 changed files with 71 additions and 17 deletions
+10 -2
View File
@@ -1,8 +1,8 @@
# GREE Controller v0.6.3 - build and validation report # GREE Controller v0.6.4 - build and validation report
## Scope ## Scope
Version 0.6.3 includes the notification, reverse-proxy, mobile decimal-input, log filtering and thermostat-copy changes described below, the Rust type-safety hotfix for communication-failure notifications, and a clearer persistent visual state for the selected house climate mode. Version 0.6.4 includes the notification, reverse-proxy, mobile decimal-input, log filtering and thermostat-copy changes described below, the Rust type-safety hotfix for communication-failure notifications, and a clearer persistent visual state for the selected house climate mode.
## Implemented changes ## Implemented changes
@@ -114,3 +114,11 @@ Validation performed in the build environment:
- The selected whole-house climate mode (Cool / Heat / Off) now has a persistent illuminated border, accent fill and glowing status dot. - The selected whole-house climate mode (Cool / Heat / Off) now has a persistent illuminated border, accent fill and glowing status dot.
- The active mode is still derived from persisted `settings.house_mode`, so the highlight survives refreshes and reconnects. - The active mode is still derived from persisted `settings.house_mode`, so the highlight survives refreshes and reconnects.
- Mode buttons now expose `aria-pressed` for an explicit accessible selected state. - Mode buttons now expose `aria-pressed` for an explicit accessible selected state.
## 0.6.4 Home Assistant zone thermostat naming
- Zone climate entities now use a descriptive entity name, `Thermostat`.
- New zone entities are therefore created as `climate.<zone_name>_thermostat` instead of falling back to ambiguous numeric suffixes such as `_2`.
- Existing zone climate registry entries are migrated on integration reload to the same scheme based on the zone's current name, fixing stale IDs left after zone renames (for example `climate.jan_2` for a zone currently named Igor).
- Migration is collision-safe: if the desired `climate.<zone_name>_thermostat` ID is already occupied, the existing entity ID is retained and a warning is logged instead of overwriting another entity.
Generated
+1 -1
View File
@@ -633,7 +633,7 @@ dependencies = [
[[package]] [[package]]
name = "gree-controller" name = "gree-controller"
version = "0.6.3" version = "0.6.4"
dependencies = [ dependencies = [
"aes", "aes",
"aes-gcm", "aes-gcm",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "gree-controller" name = "gree-controller"
version = "0.6.3" version = "0.6.4"
edition = "2021" edition = "2021"
authors = ["GREE Controller contributors"] authors = ["GREE Controller contributors"]
description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support" description = "Standalone local GREE HVAC controller with Web UI, SQLite and Home Assistant sensor support"
+8 -8
View File
@@ -1,10 +1,10 @@
623bd769512ef75f51e9bf7fb685d4d59ec84d7ccc75c826fe72c5538be737c5 ./.env.example 623bd769512ef75f51e9bf7fb685d4d59ec84d7ccc75c826fe72c5538be737c5 ./.env.example
a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore a4ec3874a2e3ab1bad28fb40bb620f7b01f64d01ad9b699306bf70ada31227db ./.gitignore
afa44c1b588eddb83ad1300e62ef320bf0264ff05f19c53d42a761a8046c97b4 ./BUILD_REPORT.md 58037959419a8de25bcf9095b020bc3ab4c1559b975764f8d24b6359ad063ad6 ./BUILD_REPORT.md
98bf4f4c86d8c6384ca55fac266f9ccae812680ebc6536063194e369669de627 ./Cargo.lock 436fd46220ef759d8204f24bdd94157c965cc39976b0f9baa79cebb367ee5c85 ./Cargo.lock
0971917d970910ff09b0164a36e0c8f32b270ad3115a01a5bf4a53798c1e1854 ./Cargo.toml 53f3f794080df48e11f5726678f7817c225ab64001dd1081d48c5aa780b2a7d0 ./Cargo.toml
19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE 19b2943504acb8f8de280f873a8dbec4bb6ebbe3870b158f5655d4fb8c298f5f ./LICENSE
58471cab1a26e1212864a497e9e7b2730d849b7dd175dec7458a5a69dec1eadb ./README.md be12639245e0929b12d11946b58c1d74f261733724bc4096a9ac68b12df30f24 ./README.md
f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs
674f8cf1ab9f1a49188e20b8e3977f1e2b7e9b8ef975315266730c44a55bbb48 ./docs/API.md 674f8cf1ab9f1a49188e20b8e3977f1e2b7e9b8ef975315266730c44a55bbb48 ./docs/API.md
234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md 234dd200e380a13ecd3e61b4ea455f6f08d64ce89382077dee80684acadb9703 ./docs/HOME_ASSISTANT_MIGRATION.md
@@ -13,17 +13,17 @@ f2c8eb28fedb26bb8f5636e1805e7a782cc472b87d882f6752d520f16216a248 ./build.rs
47e18e6b1723f1f6eb2516be6d83bbebd31b8155d9d3cdfccc0973177f3d8441 ./docs/PROJECT_SPEC.md 47e18e6b1723f1f6eb2516be6d83bbebd31b8155d9d3cdfccc0973177f3d8441 ./docs/PROJECT_SPEC.md
7748b1230029439a405139750b2e0607b8e463bfd56ad86982394105069697c5 ./docs/REVERSE_PROXY.md 7748b1230029439a405139750b2e0607b8e463bfd56ad86982394105069697c5 ./docs/REVERSE_PROXY.md
28f1403d84b01b37bebb72548aa39a75bb14bdbb611a43bfa761319957cd6f39 ./docs/SECURITY.md 28f1403d84b01b37bebb72548aa39a75bb14bdbb611a43bfa761319957cd6f39 ./docs/SECURITY.md
925ffa324b1b2cfad7ca40354e8255548f70627f95c4bb345b957d3f7e53694c ./home-assistant/README.md 38ac4aec7b971ae7df8db77faf5f9cb9702710ddf9a97def448fbe9e9ff138b1 ./home-assistant/README.md
f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py f8e8559fe10fe523ac5bc9aac25c6e26e862f679d502e8f3c39f38a0a8e40911 ./home-assistant/custom_components/gree_controller/__init__.py
b8e7c0dd722bfac1d1d8d5c2a4b5ff6b2d6fa89525b9ff5c121ee1d09623a7fd ./home-assistant/custom_components/gree_controller/api.py b8e7c0dd722bfac1d1d8d5c2a4b5ff6b2d6fa89525b9ff5c121ee1d09623a7fd ./home-assistant/custom_components/gree_controller/api.py
b0314469ddb7c69ee4a96c677815af30cb97713e93911a05597f2fdc3d10f489 ./home-assistant/custom_components/gree_controller/climate.py abcca32b090e5a5d02574a3a340e97321f9879ae6c94ef12eb1f42d967eae2d5 ./home-assistant/custom_components/gree_controller/climate.py
5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./home-assistant/custom_components/gree_controller/config_flow.py 5e4aef2143e81bedb5a15dd4be5c71b64a3ab448ec6d33a20851edd098e5f529 ./home-assistant/custom_components/gree_controller/config_flow.py
071f721b75af2ed68812c9be3a62b7ae63771e56e567a569f59ca976f8ff42f1 ./home-assistant/custom_components/gree_controller/const.py 071f721b75af2ed68812c9be3a62b7ae63771e56e567a569f59ca976f8ff42f1 ./home-assistant/custom_components/gree_controller/const.py
8dd51322798cad28d4d19938fe459aa2a4d3b64b075532149f3c8b62049a6c8b ./home-assistant/custom_components/gree_controller/coordinator.py 8dd51322798cad28d4d19938fe459aa2a4d3b64b075532149f3c8b62049a6c8b ./home-assistant/custom_components/gree_controller/coordinator.py
5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py 5a96fe8f5c035c34f1339370270cd078056202d09e236dec75735be11de92a7d ./home-assistant/custom_components/gree_controller/entity_map.py
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/icon.png c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/icon.png
c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png c4fb75c246db651087900ebfc2291ff41ac87652cd6194fc0b776b0005c1cbcf ./home-assistant/custom_components/gree_controller/logo.png
a789da0f9f7178916227aa1a52575687090c8e82b463a53f1e16edb13f44599c ./home-assistant/custom_components/gree_controller/manifest.json 0247514d881e563e45d272dbd10f2363ded1f3fb11b307a6ad9a0566dcff3648 ./home-assistant/custom_components/gree_controller/manifest.json
d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assistant/custom_components/gree_controller/number.py d0206766d76f70ea1c75a618e41bf1e8e8c0429f42ee97f40daac1ea12f16658 ./home-assistant/custom_components/gree_controller/number.py
5c914c2faa18a9cb9eb442dc111c9416f89a630578f820beb021d5381aa1b2d0 ./home-assistant/custom_components/gree_controller/sensor.py 5c914c2faa18a9cb9eb442dc111c9416f89a630578f820beb021d5381aa1b2d0 ./home-assistant/custom_components/gree_controller/sensor.py
7c23374d1c82e4b361089386ff89265322b80def0f4db9d3fd2c039867033105 ./home-assistant/custom_components/gree_controller/switch.py 7c23374d1c82e4b361089386ff89265322b80def0f4db9d3fd2c039867033105 ./home-assistant/custom_components/gree_controller/switch.py
@@ -66,5 +66,5 @@ e98bdd7204349cce1ec6f57283509697af0bbc72280622a6c3efa6fed242db4f ./web/favicon.
002c172392a00006a5128fc4cb881b0361a2d3e76298dbd80c0a013f4e1f9dde ./web/index.html 002c172392a00006a5128fc4cb881b0361a2d3e76298dbd80c0a013f4e1f9dde ./web/index.html
fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest fd26156e9f1d6713d3def564ad000553d9a16a24376059701db2ee762c99ee6c ./web/manifest.webmanifest
80ea1c50c3729adb4dd392b12d73b4247659a8b6b948ee76bd2f5df213eae918 ./web/styles.css 80ea1c50c3729adb4dd392b12d73b4247659a8b6b948ee76bd2f5df213eae918 ./web/styles.css
b82ee4b915076be30211bc260a9bc33117e5b1890bde7f37ac45aa28d7a7cafe ./web/sw.js 03fdeecaa4c68308668c09077e5390286c27d2a8e296367bec6b002fb9b8b3e8 ./web/sw.js
d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js d505d793ce7cc9485b45b78bba1c0d51887adc7451ab59a42702946e5b991382 ./web/theme-init.js
+2 -2
View File
@@ -4,7 +4,7 @@
Standalone local GREE air-conditioner controller written in Rust. It runs on a regular Linux host or an LXC container and provides a mobile-first web interface without depending on the vendor cloud. Standalone local GREE air-conditioner controller written in Rust. It runs on a regular Linux host or an LXC container and provides a mobile-first web interface without depending on the vendor cloud.
Current version: **0.6.3**. Current version: **0.6.4**.
## Highlights ## Highlights
@@ -46,7 +46,7 @@ See [`BUILD_REPORT.md`](BUILD_REPORT.md) for package validation details and [`do
On Debian, Ubuntu or an LXC container: On Debian, Ubuntu or an LXC container:
```bash ```bash
unzip gree-controller-v0.6.3.zip unzip gree-controller-v0.6.4.zip
cd gree-controller cd gree-controller
chmod +x scripts/*.sh chmod +x scripts/*.sh
./scripts/dev.sh ./scripts/dev.sh
+2
View File
@@ -122,5 +122,7 @@ The recommended `combined` strategy keeps the GREE sensor as the primary input a
Each controller zone is exposed as a full Home Assistant `climate` entity with current/target temperature and HVAC modes: Off, Auto (follow the controller house mode), Cool and Heat. The existing zone target `number` and enabled `switch` remain available for compatibility. Each controller zone is exposed as a full Home Assistant `climate` entity with current/target temperature and HVAC modes: Off, Auto (follow the controller house mode), Cool and Heat. The existing zone target `number` and enabled `switch` remain available for compatibility.
From version 0.6.4, zone climate entities use `climate.<zone_name>_thermostat`, for example `climate.igor_thermostat`. On integration reload, existing zone climate registry entries are migrated to this scheme using the zone's current name. If the target entity ID is already occupied, the old ID is retained and Home Assistant logs a warning.
When the controller detects optional GREE properties, the integration also creates switches for supported features such as panel light, Quiet, Turbo, X-FAN, Air, Health and native Sleep. Reload the integration (or restart Home Assistant) after upgrading so newly added entity types are created. When the controller detects optional GREE properties, the integration also creates switches for supported features such as panel light, Quiet, Turbo, X-FAN, Air, Health and native Sleep. Reload the integration (or restart Home Assistant) after upgrading so newly added entity types are created.
@@ -20,6 +20,7 @@ from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import slugify
from . import GreeControllerRuntimeData from . import GreeControllerRuntimeData
from .const import DOMAIN from .const import DOMAIN
@@ -73,11 +74,54 @@ async def async_setup_entry(
for zone in runtime.coordinator.plan.get("zones", []): for zone in runtime.coordinator.plan.get("zones", []):
zone_id = str(zone.get("zone_id") or "").strip() zone_id = str(zone.get("zone_id") or "").strip()
if zone_id: if zone_id:
_ensure_zone_thermostat_entity_id(registry, zone)
entities.append(GreeControllerZoneClimate(runtime.coordinator, zone_id)) entities.append(GreeControllerZoneClimate(runtime.coordinator, zone_id))
async_add_entities(entities) async_add_entities(entities)
def _ensure_zone_thermostat_entity_id(
registry: er.EntityRegistry, zone: dict[str, Any]
) -> None:
"""Keep zone climate entity IDs stable and descriptive.
Zone climate entities use ``climate.<zone_name>_thermostat``. Existing
registry entries are migrated to the same scheme, which also fixes stale
IDs left behind after a zone was renamed (for example ``climate.jan_2``
for a zone currently named Igor).
"""
zone_id = str(zone.get("zone_id") or "").strip()
if not zone_id:
return
unique_id = f"{zone_id}-zone-climate"
current_entity_id = registry.async_get_entity_id("climate", DOMAIN, unique_id)
if current_entity_id is None:
return
zone_name = str(zone.get("zone_name") or zone_id).strip() or zone_id
object_id = slugify(zone_name) or slugify(zone_id) or "zone"
desired_entity_id = f"climate.{object_id}_thermostat"
if current_entity_id == desired_entity_id:
return
occupied = registry.async_get(desired_entity_id)
if occupied is not None and occupied.entity_id != current_entity_id:
_LOGGER.warning(
"Cannot rename zone thermostat %s to %s because that entity ID is already in use",
current_entity_id,
desired_entity_id,
)
return
registry.async_update_entity(current_entity_id, new_entity_id=desired_entity_id)
_LOGGER.info(
"Renamed zone thermostat entity %s to %s",
current_entity_id,
desired_entity_id,
)
class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], ClimateEntity): class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], ClimateEntity):
"""Home Assistant climate entity controlled through the Rust service.""" """Home Assistant climate entity controlled through the Rust service."""
@@ -216,7 +260,7 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
"""Full climate entity for a controller thermostat zone.""" """Full climate entity for a controller thermostat zone."""
_attr_has_entity_name = True _attr_has_entity_name = True
_attr_name = None _attr_name = "Thermostat"
_attr_temperature_unit = UnitOfTemperature.CELSIUS _attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_min_temp = 8.0 _attr_min_temp = 8.0
_attr_max_temp = 30.0 _attr_max_temp = 30.0
@@ -1,7 +1,7 @@
{ {
"domain": "gree_controller", "domain": "gree_controller",
"name": "GREE Controller", "name": "GREE Controller",
"version": "0.6.3", "version": "0.6.4",
"config_flow": true, "config_flow": true,
"integration_type": "hub", "integration_type": "hub",
"iot_class": "local_polling", "iot_class": "local_polling",
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE = 'gree-controller-v063'; const CACHE = 'gree-controller-v064';
const SCOPE = new URL(self.registration.scope).pathname.replace(/\/$/, ''); const SCOPE = new URL(self.registration.scope).pathname.replace(/\/$/, '');
const path = value => `${SCOPE}${value.startsWith('/') ? value : `/${value}`}` || '/'; const path = value => `${SCOPE}${value.startsWith('/') ? value : `/${value}`}` || '/';
const ASSETS = [path('/'), path('/styles.css'), path('/app.js'), path('/theme-init.js'), path('/favicon.svg'), path('/manifest.webmanifest'), path('/lang/index.json'), path('/lang/en.json')]; const ASSETS = [path('/'), path('/styles.css'), path('/app.js'), path('/theme-init.js'), path('/favicon.svg'), path('/manifest.webmanifest'), path('/lang/index.json'), path('/lang/en.json')];