GREE Controller 0.15.0
This commit is contained in:
@@ -39,6 +39,40 @@ HA_TO_MODE = {value: key for key, value in MODE_TO_HA.items()}
|
||||
FAN_TO_NAME = {0: "auto", 1: "low", 2: "medium_low", 3: "medium", 4: "medium_high", 5: "high"}
|
||||
NAME_TO_FAN = {value: key for key, value in FAN_TO_NAME.items()}
|
||||
|
||||
VERTICAL_SWING_TO_VALUE = {
|
||||
SWING_OFF: 0,
|
||||
SWING_ON: 1,
|
||||
"fixed_upper": 2,
|
||||
"fixed_upper_middle": 3,
|
||||
"fixed_middle": 4,
|
||||
"fixed_lower_middle": 5,
|
||||
"fixed_lower": 6,
|
||||
"swing_upper": 7,
|
||||
"swing_upper_middle": 8,
|
||||
"swing_middle": 9,
|
||||
"swing_lower_middle": 10,
|
||||
"swing_lower": 11,
|
||||
}
|
||||
HORIZONTAL_SWING_TO_VALUE = {
|
||||
SWING_OFF: 0,
|
||||
SWING_ON: 1,
|
||||
"fixed_left": 2,
|
||||
"fixed_left_middle": 3,
|
||||
"fixed_middle": 4,
|
||||
"fixed_right_middle": 5,
|
||||
"fixed_right": 6,
|
||||
}
|
||||
VALUE_TO_VERTICAL_SWING = {value: mode for mode, value in VERTICAL_SWING_TO_VALUE.items()}
|
||||
VALUE_TO_HORIZONTAL_SWING = {value: mode for mode, value in HORIZONTAL_SWING_TO_VALUE.items()}
|
||||
|
||||
|
||||
def _louver_mode(raw: Any, modes: dict[int, str]) -> str:
|
||||
try:
|
||||
value = int(raw)
|
||||
except (TypeError, ValueError):
|
||||
value = 0
|
||||
return modes.get(value, SWING_OFF)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -126,14 +160,15 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
||||
"""Home Assistant climate entity controlled through the Rust service."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_translation_key = "direct_control"
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_min_temp = 8.0
|
||||
_attr_max_temp = 30.0
|
||||
_attr_target_temperature_step = 1.0
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT, HVACMode.DRY, HVACMode.FAN_ONLY]
|
||||
_attr_fan_modes = list(NAME_TO_FAN)
|
||||
_attr_swing_modes = [SWING_OFF, SWING_ON]
|
||||
_attr_swing_horizontal_modes = [SWING_OFF, SWING_ON]
|
||||
_attr_swing_modes = list(VERTICAL_SWING_TO_VALUE)
|
||||
_attr_swing_horizontal_modes = list(HORIZONTAL_SWING_TO_VALUE)
|
||||
_attr_supported_features = (
|
||||
ClimateEntityFeature.TARGET_TEMPERATURE
|
||||
| ClimateEntityFeature.FAN_MODE
|
||||
@@ -200,11 +235,11 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
||||
|
||||
@property
|
||||
def swing_mode(self) -> str:
|
||||
return SWING_ON if self._device.get("swing_vertical", False) else SWING_OFF
|
||||
return _louver_mode(self._device.get("swing_vertical", 0), VALUE_TO_VERTICAL_SWING)
|
||||
|
||||
@property
|
||||
def swing_horizontal_mode(self) -> str:
|
||||
return SWING_ON if self._device.get("swing_horizontal", False) else SWING_OFF
|
||||
return _louver_mode(self._device.get("swing_horizontal", 0), VALUE_TO_HORIZONTAL_SWING)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
@@ -219,6 +254,8 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
||||
"air": bool(device.get("air", False)),
|
||||
"health": bool(device.get("health", False)),
|
||||
"sleep": bool(device.get("sleep", False)),
|
||||
"vertical_louver_position": int(device.get("swing_vertical", 0) or 0),
|
||||
"horizontal_louver_position": int(device.get("swing_horizontal", 0) or 0),
|
||||
"last_seen": device.get("last_seen"),
|
||||
}
|
||||
|
||||
@@ -250,15 +287,20 @@ class GreeControllerClimate(CoordinatorEntity[GreeControllerCoordinator], Climat
|
||||
await self._command({"fan_speed": NAME_TO_FAN[fan_mode]})
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
await self._command({"swing_vertical": swing_mode == SWING_ON})
|
||||
value = VERTICAL_SWING_TO_VALUE.get(swing_mode)
|
||||
if value is not None:
|
||||
await self._command({"swing_vertical": value})
|
||||
|
||||
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
|
||||
await self._command({"swing_horizontal": swing_horizontal_mode == SWING_ON})
|
||||
value = HORIZONTAL_SWING_TO_VALUE.get(swing_horizontal_mode)
|
||||
if value is not None:
|
||||
await self._command({"swing_horizontal": value})
|
||||
|
||||
class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], ClimateEntity):
|
||||
"""Full climate entity for a controller thermostat zone."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_translation_key = "zone_thermostat"
|
||||
_attr_name = "Thermostat"
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_min_temp = 8.0
|
||||
@@ -266,8 +308,8 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
||||
_attr_target_temperature_step = 0.5
|
||||
_attr_hvac_modes = [HVACMode.OFF, HVACMode.AUTO, HVACMode.COOL, HVACMode.HEAT]
|
||||
_attr_preset_modes = ["auto", "comfort", "sleep", "away"]
|
||||
_attr_swing_modes = [SWING_OFF, SWING_ON]
|
||||
_attr_swing_horizontal_modes = [SWING_OFF, SWING_ON]
|
||||
_attr_swing_modes = list(VERTICAL_SWING_TO_VALUE)
|
||||
_attr_swing_horizontal_modes = list(HORIZONTAL_SWING_TO_VALUE)
|
||||
|
||||
def __init__(self, coordinator: GreeControllerCoordinator, zone_id: str) -> None:
|
||||
super().__init__(coordinator)
|
||||
@@ -343,11 +385,11 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
||||
|
||||
@property
|
||||
def swing_mode(self) -> str:
|
||||
return SWING_ON if self._device.get("swing_vertical", False) else SWING_OFF
|
||||
return _louver_mode(self._device.get("swing_vertical", 0), VALUE_TO_VERTICAL_SWING)
|
||||
|
||||
@property
|
||||
def swing_horizontal_mode(self) -> str:
|
||||
return SWING_ON if self._device.get("swing_horizontal", False) else SWING_OFF
|
||||
return _louver_mode(self._device.get("swing_horizontal", 0), VALUE_TO_HORIZONTAL_SWING)
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any]:
|
||||
@@ -378,6 +420,8 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
||||
"actual_mode": zone.get("actual_mode"),
|
||||
"actual_setpoint": zone.get("actual_setpoint"),
|
||||
"current_schedule": zone.get("current_schedule_name"),
|
||||
"vertical_louver_position": int(self._device.get("swing_vertical", 0) or 0),
|
||||
"horizontal_louver_position": int(self._device.get("swing_horizontal", 0) or 0),
|
||||
}
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
@@ -406,17 +450,15 @@ class GreeControllerZoneClimate(CoordinatorEntity[GreeControllerCoordinator], Cl
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
device_id = str(self._zone.get("device_id") or "").strip()
|
||||
if device_id:
|
||||
await self.coordinator.async_device_command(
|
||||
device_id, {"swing_vertical": swing_mode == SWING_ON}
|
||||
)
|
||||
value = VERTICAL_SWING_TO_VALUE.get(swing_mode)
|
||||
if device_id and value is not None:
|
||||
await self.coordinator.async_device_command(device_id, {"swing_vertical": value})
|
||||
|
||||
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
|
||||
device_id = str(self._zone.get("device_id") or "").strip()
|
||||
if device_id:
|
||||
await self.coordinator.async_device_command(
|
||||
device_id, {"swing_horizontal": swing_horizontal_mode == SWING_ON}
|
||||
)
|
||||
value = HORIZONTAL_SWING_TO_VALUE.get(swing_horizontal_mode)
|
||||
if device_id and value is not None:
|
||||
await self.coordinator.async_device_command(device_id, {"swing_horizontal": value})
|
||||
|
||||
async def async_turn_on(self) -> None:
|
||||
await self.coordinator.async_zone_control(self._zone_id, {"power": True})
|
||||
|
||||
@@ -67,6 +67,10 @@ class GreeControllerCoordinator(DataUpdateCoordinator[dict[str, dict]]):
|
||||
expected["target_temperature"] = float(int(value + 0.5))
|
||||
if "fan_speed" in expected:
|
||||
expected["fan_speed"] = min(5, max(0, int(expected["fan_speed"])))
|
||||
if "swing_vertical" in expected:
|
||||
expected["swing_vertical"] = min(11, max(0, int(expected["swing_vertical"])))
|
||||
if "swing_horizontal" in expected:
|
||||
expected["swing_horizontal"] = min(6, max(0, int(expected["swing_horizontal"])))
|
||||
return expected
|
||||
|
||||
def _overlay_pending_device_commands(self, devices: dict[str, dict[str, Any]]) -> None:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "gree_controller",
|
||||
"name": "GREE Controller",
|
||||
"version": "0.14.24",
|
||||
"version": "0.15.0",
|
||||
"config_flow": true,
|
||||
"integration_type": "hub",
|
||||
"iot_class": "local_polling",
|
||||
|
||||
@@ -18,5 +18,91 @@
|
||||
"abort": {
|
||||
"already_configured": "GREE Controller is already configured"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"climate": {
|
||||
"direct_control": {
|
||||
"name": "Direct control",
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"auto": "Auto",
|
||||
"low": "Low",
|
||||
"medium_low": "Medium-low",
|
||||
"medium": "Medium",
|
||||
"medium_high": "Medium-high",
|
||||
"high": "High"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"on": "Full range (Auto)",
|
||||
"fixed_upper": "Fixed: Top",
|
||||
"fixed_upper_middle": "Fixed: Upper-middle",
|
||||
"fixed_middle": "Fixed: Middle",
|
||||
"fixed_lower_middle": "Fixed: Lower-middle",
|
||||
"fixed_lower": "Fixed: Bottom",
|
||||
"swing_upper": "Swing: Top",
|
||||
"swing_upper_middle": "Swing: Upper-middle",
|
||||
"swing_middle": "Swing: Middle",
|
||||
"swing_lower_middle": "Swing: Lower-middle",
|
||||
"swing_lower": "Swing: Bottom"
|
||||
}
|
||||
},
|
||||
"swing_horizontal_mode": {
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"on": "Full range (Auto)",
|
||||
"fixed_left": "Fixed: Left",
|
||||
"fixed_left_middle": "Fixed: Left-middle",
|
||||
"fixed_middle": "Fixed: Middle",
|
||||
"fixed_right_middle": "Fixed: Middle-right",
|
||||
"fixed_right": "Fixed: Right"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"zone_thermostat": {
|
||||
"name": "Thermostat",
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"auto": "Auto schedule",
|
||||
"comfort": "Comfort",
|
||||
"sleep": "Sleep",
|
||||
"away": "Away"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"on": "Full range (Auto)",
|
||||
"fixed_upper": "Fixed: Top",
|
||||
"fixed_upper_middle": "Fixed: Upper-middle",
|
||||
"fixed_middle": "Fixed: Middle",
|
||||
"fixed_lower_middle": "Fixed: Lower-middle",
|
||||
"fixed_lower": "Fixed: Bottom",
|
||||
"swing_upper": "Swing: Top",
|
||||
"swing_upper_middle": "Swing: Upper-middle",
|
||||
"swing_middle": "Swing: Middle",
|
||||
"swing_lower_middle": "Swing: Lower-middle",
|
||||
"swing_lower": "Swing: Bottom"
|
||||
}
|
||||
},
|
||||
"swing_horizontal_mode": {
|
||||
"state": {
|
||||
"off": "Off",
|
||||
"on": "Full range (Auto)",
|
||||
"fixed_left": "Fixed: Left",
|
||||
"fixed_left_middle": "Fixed: Left-middle",
|
||||
"fixed_middle": "Fixed: Middle",
|
||||
"fixed_right_middle": "Fixed: Middle-right",
|
||||
"fixed_right": "Fixed: Right"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,91 @@
|
||||
"abort": {
|
||||
"already_configured": "GREE Controller jest już skonfigurowany"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"climate": {
|
||||
"direct_control": {
|
||||
"name": "Sterowanie bezpośrednie",
|
||||
"state_attributes": {
|
||||
"fan_mode": {
|
||||
"state": {
|
||||
"auto": "Automatyczny",
|
||||
"low": "Niski",
|
||||
"medium_low": "Średnio-niski",
|
||||
"medium": "Średni",
|
||||
"medium_high": "Średnio-wysoki",
|
||||
"high": "Wysoki"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"state": {
|
||||
"off": "Wyłączony",
|
||||
"on": "Pełny zakres (Auto)",
|
||||
"fixed_upper": "Stała: Góra",
|
||||
"fixed_upper_middle": "Stała: Środek-góra",
|
||||
"fixed_middle": "Stała: Środek",
|
||||
"fixed_lower_middle": "Stała: Środek-dół",
|
||||
"fixed_lower": "Stała: Dół",
|
||||
"swing_upper": "Ruch: Góra",
|
||||
"swing_upper_middle": "Ruch: Środek-góra",
|
||||
"swing_middle": "Ruch: Środek",
|
||||
"swing_lower_middle": "Ruch: Środek-dół",
|
||||
"swing_lower": "Ruch: Dół"
|
||||
}
|
||||
},
|
||||
"swing_horizontal_mode": {
|
||||
"state": {
|
||||
"off": "Wyłączony",
|
||||
"on": "Pełny zakres (Auto)",
|
||||
"fixed_left": "Stała: Lewo",
|
||||
"fixed_left_middle": "Stała: Lewo-środek",
|
||||
"fixed_middle": "Stała: Środek",
|
||||
"fixed_right_middle": "Stała: Środek-prawo",
|
||||
"fixed_right": "Stała: Prawo"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"zone_thermostat": {
|
||||
"name": "Termostat",
|
||||
"state_attributes": {
|
||||
"preset_mode": {
|
||||
"state": {
|
||||
"auto": "Harmonogram (Auto)",
|
||||
"comfort": "Komfort",
|
||||
"sleep": "Sen",
|
||||
"away": "Poza domem"
|
||||
}
|
||||
},
|
||||
"swing_mode": {
|
||||
"state": {
|
||||
"off": "Wyłączony",
|
||||
"on": "Pełny zakres (Auto)",
|
||||
"fixed_upper": "Stała: Góra",
|
||||
"fixed_upper_middle": "Stała: Środek-góra",
|
||||
"fixed_middle": "Stała: Środek",
|
||||
"fixed_lower_middle": "Stała: Środek-dół",
|
||||
"fixed_lower": "Stała: Dół",
|
||||
"swing_upper": "Ruch: Góra",
|
||||
"swing_upper_middle": "Ruch: Środek-góra",
|
||||
"swing_middle": "Ruch: Środek",
|
||||
"swing_lower_middle": "Ruch: Środek-dół",
|
||||
"swing_lower": "Ruch: Dół"
|
||||
}
|
||||
},
|
||||
"swing_horizontal_mode": {
|
||||
"state": {
|
||||
"off": "Wyłączony",
|
||||
"on": "Pełny zakres (Auto)",
|
||||
"fixed_left": "Stała: Lewo",
|
||||
"fixed_left_middle": "Stała: Lewo-środek",
|
||||
"fixed_middle": "Stała: Środek",
|
||||
"fixed_right_middle": "Stała: Środek-prawo",
|
||||
"fixed_right": "Stała: Prawo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## 0.15.0
|
||||
|
||||
- Adds full vertical and horizontal louver positions to manual unit control and thermostat cards, with compact dropdowns that preserve the existing view layout.
|
||||
- Extends legacy automations, Visual Flow and the bundled Home Assistant climate entities with granular louver positions; legacy boolean swing API payloads remain accepted.
|
||||
- Direct add-on access no longer shows `Invalid access token` on initial page load; that validation message appears only after a token is submitted.
|
||||
- Home Assistant climate cards localize direct-control names, fan modes, thermostat presets and all vertical/horizontal louver positions while keeping stable technical mode IDs for services and automations.
|
||||
|
||||
## 0.14.24
|
||||
|
||||
- Public Custom Charts fetch chart metadata before language assets, so a chart loads only its selected language pack; missing chart IDs fall back to English.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: "GREE Controller"
|
||||
version: "0.14.24"
|
||||
version: "0.15.0"
|
||||
slug: "gree_controller"
|
||||
description: "Local GREE HVAC controller with Web UI and Home Assistant integration"
|
||||
url: "https://git.linuxiarz.pl/gru/gree-controller-ha-addon/"
|
||||
|
||||
Reference in New Issue
Block a user