diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a40d27..e68ccb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # GE Home Appliances (SmartHQ) Changelog +## 0.6.9 + +- Added additional fridge controls [#200] +- Bugfix: Additional auth stability improvements [#215, #211] +- Bugfix: Removed deprecated constants [#218] + ## 0.6.8 - Added Dehumidifier [#114] diff --git a/custom_components/ge_home/binary_sensor.py b/custom_components/ge_home/binary_sensor.py index fb808f8..0a35ef5 100644 --- a/custom_components/ge_home/binary_sensor.py +++ b/custom_components/ge_home/binary_sensor.py @@ -35,5 +35,13 @@ def async_devices_discovered(apis: list[ApplianceApi]): ] _LOGGER.debug(f'Found {len(entities):d} unregistered binary sensors') async_add_entities(entities) - - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) + + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/button.py b/custom_components/ge_home/button.py index cfbb843..748ee6b 100644 --- a/custom_components/ge_home/button.py +++ b/custom_components/ge_home/button.py @@ -34,4 +34,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f'Found {len(entities):d} unregistered buttons ') async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) \ No newline at end of file + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/climate.py b/custom_components/ge_home/climate.py index 8255fb8..4512b61 100644 --- a/custom_components/ge_home/climate.py +++ b/custom_components/ge_home/climate.py @@ -36,4 +36,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f'Found {len(entities):d} unregistered climate entities') async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) \ No newline at end of file + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/devices/cooktop.py b/custom_components/ge_home/devices/cooktop.py index 6fb3453..d681443 100644 --- a/custom_components/ge_home/devices/cooktop.py +++ b/custom_components/ge_home/devices/cooktop.py @@ -2,7 +2,7 @@ from typing import List from gehomesdk.erd.erd_data_type import ErdDataType -from homeassistant.const import DEVICE_CLASS_POWER_FACTOR +from homeassistant.components.sensor import SensorDeviceClass from homeassistant.helpers.entity import Entity from gehomesdk import ( ErdCode, @@ -44,7 +44,7 @@ def get_all_entities(self) -> List[Entity]: cooktop_entities.append(GeErdPropertyBinarySensor(self, ErdCode.COOKTOP_STATUS, prop+".on")) cooktop_entities.append(GeErdPropertyBinarySensor(self, ErdCode.COOKTOP_STATUS, prop+".synchronized")) if not v.on_off_only: - cooktop_entities.append(GeErdPropertySensor(self, ErdCode.COOKTOP_STATUS, prop+".power_pct", icon_override="mdi:fire", device_class_override=DEVICE_CLASS_POWER_FACTOR, data_type_override=ErdDataType.INT)) + cooktop_entities.append(GeErdPropertySensor(self, ErdCode.COOKTOP_STATUS, prop+".power_pct", icon_override="mdi:fire", device_class_override=SensorDeviceClass.POWER_FACTOR, data_type_override=ErdDataType.INT)) return base_entities + cooktop_entities diff --git a/custom_components/ge_home/devices/fridge.py b/custom_components/ge_home/devices/fridge.py index 24f0657..7c7aac8 100644 --- a/custom_components/ge_home/devices/fridge.py +++ b/custom_components/ge_home/devices/fridge.py @@ -1,5 +1,5 @@ from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM -from homeassistant.const import DEVICE_CLASS_TEMPERATURE +from homeassistant.components.sensor import SensorDeviceClass import logging from typing import List @@ -31,7 +31,8 @@ GeDispenser, GeErdPropertySensor, GeErdPropertyBinarySensor, - ConvertableDrawerModeOptionsConverter + ConvertableDrawerModeOptionsConverter, + GeFridgeIceControlSwitch ) _LOGGER = logging.getLogger(__name__) @@ -61,7 +62,10 @@ def get_all_entities(self) -> List[Entity]: proximity_light: ErdOnOff = self.try_get_erd_value(ErdCode.PROXIMITY_LIGHT) display_mode: ErdOnOff = self.try_get_erd_value(ErdCode.DISPLAY_MODE) lockout_mode: ErdOnOff = self.try_get_erd_value(ErdCode.LOCKOUT_MODE) - + turbo_cool: ErdOnOff = self.try_get_erd_value(ErdCode.TURBO_COOL_STATUS) + turbo_freeze: ErdOnOff = self.try_get_erd_value(ErdCode.TURBO_FREEZE_STATUS) + ice_boost: ErdOnOff = self.try_get_erd_value(ErdCode.FRIDGE_ICE_BOOST) + units = self.hass.config.units # Common entities @@ -80,8 +84,11 @@ def get_all_entities(self) -> List[Entity]: GeErdPropertySensor(self, ErdCode.CURRENT_TEMPERATURE, "fridge"), GeFridge(self), ]) + if turbo_cool is not None: + fridge_entities.append(GeErdSwitch(self, ErdCode.FRIDGE_ICE_BOOST)) if(ice_maker_control and ice_maker_control.status_fridge != ErdOnOff.NA): fridge_entities.append(GeErdPropertyBinarySensor(self, ErdCode.ICE_MAKER_CONTROL, "status_fridge")) + fridge_entities.append(GeFridgeIceControlSwitch(self, "fridge")) if(water_filter and water_filter != ErdFilterStatus.NA): fridge_entities.append(GeErdSensor(self, ErdCode.WATER_FILTER_STATUS)) if(air_filter and air_filter != ErdFilterStatus.NA): @@ -105,8 +112,13 @@ def get_all_entities(self) -> List[Entity]: GeErdPropertySensor(self, ErdCode.CURRENT_TEMPERATURE, "freezer"), GeFreezer(self), ]) + if turbo_freeze is not None: + freezer_entities.append(GeErdSwitch(self, ErdCode.TURBO_FREEZE_STATUS)) + if ice_boost is not None: + freezer_entities.append(GeErdSwitch(self, ErdCode.FRIDGE_ICE_BOOST)) if(ice_maker_control and ice_maker_control.status_freezer != ErdOnOff.NA): freezer_entities.append(GeErdPropertyBinarySensor(self, ErdCode.ICE_MAKER_CONTROL, "status_freezer")) + freezer_entities.append(GeFridgeIceControlSwitch(self, "freezer")) if(ice_bucket_status and ice_bucket_status.is_present_freezer): freezer_entities.append(GeErdPropertySensor(self, ErdCode.ICE_MAKER_BUCKET_STATUS, "state_full_freezer")) @@ -117,7 +129,7 @@ def get_all_entities(self) -> List[Entity]: GeErdSensor(self, ErdCode.HOT_WATER_SET_TEMP), GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "status", icon_override="mdi:information-outline"), GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "time_until_ready", icon_override="mdi:timer-outline"), - GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "current_temp", device_class_override=DEVICE_CLASS_TEMPERATURE, data_type_override=ErdDataType.INT), + GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "current_temp", device_class_override=SensorDeviceClass.TEMPERATURE, data_type_override=ErdDataType.INT), GeErdPropertyBinarySensor(self, ErdCode.HOT_WATER_STATUS, "faulted", device_class_override=DEVICE_CLASS_PROBLEM), GeDispenser(self) ]) diff --git a/custom_components/ge_home/devices/oven.py b/custom_components/ge_home/devices/oven.py index c83ce8d..9400991 100644 --- a/custom_components/ge_home/devices/oven.py +++ b/custom_components/ge_home/devices/oven.py @@ -2,7 +2,7 @@ from typing import List from gehomesdk.erd.erd_data_type import ErdDataType -from homeassistant.const import DEVICE_CLASS_POWER_FACTOR +from homeassistant.components.sensor import SensorDeviceClass from homeassistant.helpers.entity import Entity from gehomesdk import ( ErdCode, @@ -100,18 +100,27 @@ def get_all_entities(self) -> List[Entity]: if oven_config.has_warming_drawer and warm_drawer is not None: oven_entities.append(GeErdSensor(self, ErdCode.WARMING_DRAWER_STATE)) - if cooktop_config == ErdCooktopConfig.PRESENT: + if cooktop_config == ErdCooktopConfig.PRESENT: + # attempt to get the cooktop status using legacy status + cooktop_status_erd = ErdCode.COOKTOP_STATUS cooktop_status: CooktopStatus = self.try_get_erd_value(ErdCode.COOKTOP_STATUS) + + # if we didn't get it, try using the new version + if cooktop_status is None: + cooktop_status_erd = ErdCode.COOKTOP_STATUS_EXT + cooktop_status: CooktopStatus = self.try_get_erd_value(ErdCode.COOKTOP_STATUS_EXT) + + # if we got a status through either mechanism, we can add the entities if cooktop_status is not None: - cooktop_entities.append(GeErdBinarySensor(self, ErdCode.COOKTOP_STATUS)) + cooktop_entities.append(GeErdBinarySensor(self, cooktop_status_erd)) for (k, v) in cooktop_status.burners.items(): if v.exists: prop = self._camel_to_snake(k) - cooktop_entities.append(GeErdPropertyBinarySensor(self, ErdCode.COOKTOP_STATUS, prop+".on")) - cooktop_entities.append(GeErdPropertyBinarySensor(self, ErdCode.COOKTOP_STATUS, prop+".synchronized")) + cooktop_entities.append(GeErdPropertyBinarySensor(self, cooktop_status_erd, prop+".on")) + cooktop_entities.append(GeErdPropertyBinarySensor(self, cooktop_status_erd, prop+".synchronized")) if not v.on_off_only: - cooktop_entities.append(GeErdPropertySensor(self, ErdCode.COOKTOP_STATUS, prop+".power_pct", icon_override="mdi:fire", device_class_override=DEVICE_CLASS_POWER_FACTOR, data_type_override=ErdDataType.INT)) + cooktop_entities.append(GeErdPropertySensor(self, cooktop_status_erd, prop+".power_pct", icon_override="mdi:fire", device_class_override=SensorDeviceClass.POWER_FACTOR, data_type_override=ErdDataType.INT)) return base_entities + oven_entities + cooktop_entities diff --git a/custom_components/ge_home/devices/water_filter.py b/custom_components/ge_home/devices/water_filter.py index d466f67..7cebd6a 100644 --- a/custom_components/ge_home/devices/water_filter.py +++ b/custom_components/ge_home/devices/water_filter.py @@ -30,7 +30,7 @@ def get_all_entities(self) -> List[Entity]: GeErdBinarySensor(self, ErdCode.WH_FILTER_MANUAL_MODE, icon_on_override="mdi:human", icon_off_override="mdi:robot"), GeErdBinarySensor(self, ErdCode.WH_FILTER_LEAK_VALIDITY, device_class_override="moisture"), GeErdPropertySensor(self, ErdCode.WH_FILTER_FLOW_RATE, "flow_rate"), - GeErdSensor(self, ErdCode.WH_FILTER_DAY_USAGE), + GeErdSensor(self, ErdCode.WH_FILTER_DAY_USAGE, device_class_override="water"), GeErdPropertySensor(self, ErdCode.WH_FILTER_LIFE_REMAINING, "life_remaining"), GeErdBinarySensor(self, ErdCode.WH_FILTER_FLOW_ALERT, device_class_override="moisture"), ] diff --git a/custom_components/ge_home/devices/water_softener.py b/custom_components/ge_home/devices/water_softener.py index a730471..a0afa83 100644 --- a/custom_components/ge_home/devices/water_softener.py +++ b/custom_components/ge_home/devices/water_softener.py @@ -27,7 +27,7 @@ def get_all_entities(self) -> List[Entity]: GeErdBinarySensor(self, ErdCode.WH_FILTER_MANUAL_MODE, icon_on_override="mdi:human", icon_off_override="mdi:robot"), GeErdPropertySensor(self, ErdCode.WH_FILTER_FLOW_RATE, "flow_rate"), GeErdBinarySensor(self, ErdCode.WH_FILTER_FLOW_ALERT, device_class_override="moisture"), - GeErdSensor(self, ErdCode.WH_FILTER_DAY_USAGE), + GeErdSensor(self, ErdCode.WH_FILTER_DAY_USAGE, device_class_override="water"), GeErdSensor(self, ErdCode.WH_SOFTENER_ERROR_CODE, icon_override="mdi:alert-circle"), GeErdBinarySensor(self, ErdCode.WH_SOFTENER_LOW_SALT, icon_on_override="mdi:alert", icon_off_override="mdi:grain"), GeErdSensor(self, ErdCode.WH_SOFTENER_SHUTOFF_VALVE_STATE, icon_override="mdi:state-machine"), diff --git a/custom_components/ge_home/entities/ac/fan_mode_options.py b/custom_components/ge_home/entities/ac/fan_mode_options.py index 0bf78cd..c24e72f 100644 --- a/custom_components/ge_home/entities/ac/fan_mode_options.py +++ b/custom_components/ge_home/entities/ac/fan_mode_options.py @@ -1,11 +1,6 @@ import logging from typing import Any, List, Optional -from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, -) from gehomesdk import ErdAcFanSetting from ..common import OptionsConverter diff --git a/custom_components/ge_home/entities/ac/ge_biac_climate.py b/custom_components/ge_home/entities/ac/ge_biac_climate.py index f3b7453..5e62428 100644 --- a/custom_components/ge_home/entities/ac/ge_biac_climate.py +++ b/custom_components/ge_home/entities/ac/ge_biac_climate.py @@ -1,11 +1,7 @@ import logging from typing import Any, List, Optional -from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, -) +from homeassistant.components.climate import HVACMode from gehomesdk import ErdAcOperationMode from ...devices import ApplianceApi from ..common import GeClimate, OptionsConverter @@ -16,13 +12,13 @@ class BiacHvacModeOptionsConverter(OptionsConverter): @property def options(self) -> List[str]: - return [HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY] + return [HVACMode.AUTO, HVACMode.COOL, HVACMode.FAN_ONLY] def from_option_string(self, value: str) -> Any: try: return { - HVAC_MODE_AUTO: ErdAcOperationMode.ENERGY_SAVER, - HVAC_MODE_COOL: ErdAcOperationMode.COOL, - HVAC_MODE_FAN_ONLY: ErdAcOperationMode.FAN_ONLY + HVACMode.AUTO: ErdAcOperationMode.ENERGY_SAVER, + HVACMode.COOL: ErdAcOperationMode.COOL, + HVACMode.FAN_ONLY: ErdAcOperationMode.FAN_ONLY }.get(value) except: _LOGGER.warn(f"Could not set HVAC mode to {value.upper()}") @@ -30,14 +26,14 @@ def from_option_string(self, value: str) -> Any: def to_option_string(self, value: Any) -> Optional[str]: try: return { - ErdAcOperationMode.ENERGY_SAVER: HVAC_MODE_AUTO, - ErdAcOperationMode.AUTO: HVAC_MODE_AUTO, - ErdAcOperationMode.COOL: HVAC_MODE_COOL, - ErdAcOperationMode.FAN_ONLY: HVAC_MODE_FAN_ONLY + ErdAcOperationMode.ENERGY_SAVER: HVACMode.AUTO, + ErdAcOperationMode.AUTO: HVACMode.AUTO, + ErdAcOperationMode.COOL: HVACMode.COOL, + ErdAcOperationMode.FAN_ONLY: HVACMode.FAN_ONLY }.get(value) except: _LOGGER.warn(f"Could not determine operation mode mapping for {value}") - return HVAC_MODE_COOL + return HVACMode.COOL class GeBiacClimate(GeClimate): """Class for Built-In AC units""" diff --git a/custom_components/ge_home/entities/ac/ge_pac_climate.py b/custom_components/ge_home/entities/ac/ge_pac_climate.py index ba8eb75..558cdc8 100644 --- a/custom_components/ge_home/entities/ac/ge_pac_climate.py +++ b/custom_components/ge_home/entities/ac/ge_pac_climate.py @@ -1,17 +1,7 @@ import logging from typing import Any, List, Optional - -from homeassistant.const import ( - TEMP_FAHRENHEIT -) -from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, -) +from homeassistant.components.climate import HVACMode from gehomesdk import ErdCode, ErdAcOperationMode, ErdSacAvailableModes, ErdSacTargetTemperatureRange from ...devices import ApplianceApi from ..common import GeClimate, OptionsConverter @@ -25,19 +15,19 @@ def __init__(self, available_modes: ErdSacAvailableModes): @property def options(self) -> List[str]: - modes = [HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY] + modes = [HVACMode.COOL, HVACMode.FAN_ONLY] if self._available_modes and self._available_modes.has_heat: - modes.append(HVAC_MODE_HEAT) + modes.append(HVACMode.HEAT) if self._available_modes and self._available_modes.has_dry: - modes.append(HVAC_MODE_DRY) + modes.append(HVACMode.DRY) return modes def from_option_string(self, value: str) -> Any: try: return { - HVAC_MODE_COOL: ErdAcOperationMode.COOL, - HVAC_MODE_HEAT: ErdAcOperationMode.HEAT, - HVAC_MODE_FAN_ONLY: ErdAcOperationMode.FAN_ONLY, - HVAC_MODE_DRY: ErdAcOperationMode.DRY + HVACMode.COOL: ErdAcOperationMode.COOL, + HVACMode.HEAT: ErdAcOperationMode.HEAT, + HVACMode.FAN_ONLY: ErdAcOperationMode.FAN_ONLY, + HVACMode.DRY: ErdAcOperationMode.DRY }.get(value) except: _LOGGER.warn(f"Could not set HVAC mode to {value.upper()}") @@ -45,14 +35,14 @@ def from_option_string(self, value: str) -> Any: def to_option_string(self, value: Any) -> Optional[str]: try: return { - ErdAcOperationMode.COOL: HVAC_MODE_COOL, - ErdAcOperationMode.HEAT: HVAC_MODE_HEAT, - ErdAcOperationMode.DRY: HVAC_MODE_DRY, - ErdAcOperationMode.FAN_ONLY: HVAC_MODE_FAN_ONLY + ErdAcOperationMode.COOL: HVACMode.COOL, + ErdAcOperationMode.HEAT: HVACMode.HEAT, + ErdAcOperationMode.DRY: HVACMode.DRY, + ErdAcOperationMode.FAN_ONLY: HVACMode.FAN_ONLY }.get(value) except: _LOGGER.warn(f"Could not determine operation mode mapping for {value}") - return HVAC_MODE_COOL + return HVACMode.COOL class GePacClimate(GeClimate): """Class for Portable AC units""" diff --git a/custom_components/ge_home/entities/ac/ge_sac_climate.py b/custom_components/ge_home/entities/ac/ge_sac_climate.py index 7c7ed54..6198b01 100644 --- a/custom_components/ge_home/entities/ac/ge_sac_climate.py +++ b/custom_components/ge_home/entities/ac/ge_sac_climate.py @@ -1,16 +1,7 @@ import logging from typing import Any, List, Optional -from homeassistant.const import ( - TEMP_FAHRENHEIT -) -from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_DRY, - HVAC_MODE_FAN_ONLY, - HVAC_MODE_HEAT, -) +from homeassistant.components.climate import HVACMode from gehomesdk import ErdCode, ErdAcOperationMode, ErdSacAvailableModes, ErdSacTargetTemperatureRange from ...devices import ApplianceApi from ..common import GeClimate, OptionsConverter @@ -24,21 +15,21 @@ def __init__(self, available_modes: ErdSacAvailableModes): @property def options(self) -> List[str]: - modes = [HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY] + modes = [HVACMode.COOL, HVACMode.FAN_ONLY] if self._available_modes and self._available_modes.has_heat: - modes.append(HVAC_MODE_HEAT) - modes.append(HVAC_MODE_AUTO) + modes.append(HVACMode.HEAT) + modes.append(HVACMode.AUTO) if self._available_modes and self._available_modes.has_dry: - modes.append(HVAC_MODE_DRY) + modes.append(HVACMode.DRY) return modes def from_option_string(self, value: str) -> Any: try: return { - HVAC_MODE_AUTO: ErdAcOperationMode.AUTO, - HVAC_MODE_COOL: ErdAcOperationMode.COOL, - HVAC_MODE_HEAT: ErdAcOperationMode.HEAT, - HVAC_MODE_FAN_ONLY: ErdAcOperationMode.FAN_ONLY, - HVAC_MODE_DRY: ErdAcOperationMode.DRY + HVACMode.AUTO: ErdAcOperationMode.AUTO, + HVACMode.COOL: ErdAcOperationMode.COOL, + HVACMode.HEAT: ErdAcOperationMode.HEAT, + HVACMode.FAN_ONLY: ErdAcOperationMode.FAN_ONLY, + HVACMode.DRY: ErdAcOperationMode.DRY }.get(value) except: _LOGGER.warn(f"Could not set HVAC mode to {value.upper()}") @@ -46,16 +37,16 @@ def from_option_string(self, value: str) -> Any: def to_option_string(self, value: Any) -> Optional[str]: try: return { - ErdAcOperationMode.ENERGY_SAVER: HVAC_MODE_AUTO, - ErdAcOperationMode.AUTO: HVAC_MODE_AUTO, - ErdAcOperationMode.COOL: HVAC_MODE_COOL, - ErdAcOperationMode.HEAT: HVAC_MODE_HEAT, - ErdAcOperationMode.DRY: HVAC_MODE_DRY, - ErdAcOperationMode.FAN_ONLY: HVAC_MODE_FAN_ONLY + ErdAcOperationMode.ENERGY_SAVER: HVACMode.AUTO, + ErdAcOperationMode.AUTO: HVACMode.AUTO, + ErdAcOperationMode.COOL: HVACMode.COOL, + ErdAcOperationMode.HEAT: HVACMode.HEAT, + ErdAcOperationMode.DRY: HVACMode.DRY, + ErdAcOperationMode.FAN_ONLY: HVACMode.FAN_ONLY }.get(value) except: _LOGGER.warn(f"Could not determine operation mode mapping for {value}") - return HVAC_MODE_COOL + return HVACMode.COOL class GeSacClimate(GeClimate): """Class for Split AC units""" diff --git a/custom_components/ge_home/entities/ac/ge_wac_climate.py b/custom_components/ge_home/entities/ac/ge_wac_climate.py index a7b3980..4602af8 100644 --- a/custom_components/ge_home/entities/ac/ge_wac_climate.py +++ b/custom_components/ge_home/entities/ac/ge_wac_climate.py @@ -1,11 +1,7 @@ import logging from typing import Any, List, Optional -from homeassistant.components.climate.const import ( - HVAC_MODE_AUTO, - HVAC_MODE_COOL, - HVAC_MODE_FAN_ONLY, -) +from homeassistant.components.climate import HVACMode from gehomesdk import ErdAcOperationMode from ...devices import ApplianceApi from ..common import GeClimate, OptionsConverter @@ -16,13 +12,13 @@ class WacHvacModeOptionsConverter(OptionsConverter): @property def options(self) -> List[str]: - return [HVAC_MODE_AUTO, HVAC_MODE_COOL, HVAC_MODE_FAN_ONLY] + return [HVACMode.AUTO, HVACMode.COOL, HVACMode.FAN_ONLY] def from_option_string(self, value: str) -> Any: try: return { - HVAC_MODE_AUTO: ErdAcOperationMode.ENERGY_SAVER, - HVAC_MODE_COOL: ErdAcOperationMode.COOL, - HVAC_MODE_FAN_ONLY: ErdAcOperationMode.FAN_ONLY + HVACMode.AUTO: ErdAcOperationMode.ENERGY_SAVER, + HVACMode.COOL: ErdAcOperationMode.COOL, + HVACMode.FAN_ONLY: ErdAcOperationMode.FAN_ONLY }.get(value) except: _LOGGER.warn(f"Could not set HVAC mode to {value.upper()}") @@ -30,14 +26,14 @@ def from_option_string(self, value: str) -> Any: def to_option_string(self, value: Any) -> Optional[str]: try: return { - ErdAcOperationMode.ENERGY_SAVER: HVAC_MODE_AUTO, - ErdAcOperationMode.AUTO: HVAC_MODE_AUTO, - ErdAcOperationMode.COOL: HVAC_MODE_COOL, - ErdAcOperationMode.FAN_ONLY: HVAC_MODE_FAN_ONLY + ErdAcOperationMode.ENERGY_SAVER: HVACMode.AUTO, + ErdAcOperationMode.AUTO: HVACMode.AUTO, + ErdAcOperationMode.COOL: HVACMode.COOL, + ErdAcOperationMode.FAN_ONLY: HVACMode.FAN_ONLY }.get(value) except: _LOGGER.warn(f"Could not determine operation mode mapping for {value}") - return HVAC_MODE_COOL + return HVACMode.COOL class GeWacClimate(GeClimate): """Class for Window AC units""" diff --git a/custom_components/ge_home/entities/advantium/const.py b/custom_components/ge_home/entities/advantium/const.py index 7c487be..674dcae 100644 --- a/custom_components/ge_home/entities/advantium/const.py +++ b/custom_components/ge_home/entities/advantium/const.py @@ -1,8 +1,5 @@ -from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE -) +from homeassistant.components.water_heater import WaterHeaterEntityFeature SUPPORT_NONE = 0 -GE_ADVANTIUM_WITH_TEMPERATURE = (SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE) -GE_ADVANTIUM = SUPPORT_OPERATION_MODE +GE_ADVANTIUM_WITH_TEMPERATURE = (WaterHeaterEntityFeature.OPERATION_MODE | WaterHeaterEntityFeature.TARGET_TEMPERATURE) +GE_ADVANTIUM = WaterHeaterEntityFeature.OPERATION_MODE diff --git a/custom_components/ge_home/entities/advantium/ge_advantium.py b/custom_components/ge_home/entities/advantium/ge_advantium.py index 2d1372b..a92b9ee 100644 --- a/custom_components/ge_home/entities/advantium/ge_advantium.py +++ b/custom_components/ge_home/entities/advantium/ge_advantium.py @@ -15,7 +15,8 @@ ) from gehomesdk.erd.values.advantium.advantium_enums import CookAction, CookMode -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.components.sensor import SensorDeviceClass +from homeassistant.const import ATTR_TEMPERATURE from ...const import DOMAIN from ...devices import ApplianceApi from ..common import GeAbstractWaterHeater @@ -272,7 +273,7 @@ async def _ensure_operation_mode(self): async def _convert_target_temperature(self, temp_120v: int, temp_240v: int): unit_type = self.unit_type target_temp_f = temp_240v if unit_type in [ErdUnitType.TYPE_240V_MONOGRAM, ErdUnitType.TYPE_240V_CAFE] else temp_120v - if self.temperature_unit == TEMP_FAHRENHEIT: + if self.temperature_unit == SensorDeviceClass.FAHRENHEIT: return float(target_temp_f) else: return (target_temp_f - 32.0) * (5/9) diff --git a/custom_components/ge_home/entities/ccm/ge_ccm_brew_temperature.py b/custom_components/ge_home/entities/ccm/ge_ccm_brew_temperature.py index 86cac03..c7895d9 100644 --- a/custom_components/ge_home/entities/ccm/ge_ccm_brew_temperature.py +++ b/custom_components/ge_home/entities/ccm/ge_ccm_brew_temperature.py @@ -2,7 +2,6 @@ from ...devices import ApplianceApi from ..common import GeErdNumber from .ge_ccm_cached_value import GeCcmCachedValue -from homeassistant.const import TEMP_FAHRENHEIT class GeCcmBrewTemperatureNumber(GeErdNumber, GeCcmCachedValue): def __init__(self, api: ApplianceApi): diff --git a/custom_components/ge_home/entities/common/ge_climate.py b/custom_components/ge_home/entities/common/ge_climate.py index 7f44edb..c3c4583 100644 --- a/custom_components/ge_home/entities/common/ge_climate.py +++ b/custom_components/ge_home/entities/common/ge_climate.py @@ -4,15 +4,10 @@ from homeassistant.components.climate import ClimateEntity from homeassistant.const import ( ATTR_TEMPERATURE, - TEMP_FAHRENHEIT, - TEMP_CELSIUS, -) -from homeassistant.components.climate.const import ( - HVAC_MODE_FAN_ONLY, - SUPPORT_TARGET_TEMPERATURE, - SUPPORT_FAN_MODE, - HVAC_MODE_OFF + UnitOfTemperature, ) +from homeassistant.components.climate import ClimateEntityFeature, HVACMode +from homeassistant.components.water_heater import WaterHeaterEntityFeature from gehomesdk import ErdCode, ErdCodeType, ErdMeasurementUnits, ErdOnOff from ...const import DOMAIN from ...devices import ApplianceApi @@ -22,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) #by default, we'll support target temp and fan mode (derived classes can override) -GE_CLIMATE_SUPPORT = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE +GE_CLIMATE_SUPPORT = WaterHeaterEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.FAN_MODE class GeClimate(GeEntity, ClimateEntity): """GE Climate Base Entity (Window AC, Portable AC, etc)""" @@ -83,11 +78,11 @@ def fan_mode_erd_code(self): @property def temperature_unit(self): #appears to always be Fahrenheit internally, hardcode this - return TEMP_FAHRENHEIT + return UnitOfTemperature.FAHRENHEIT #measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT) #if measurement_system == ErdMeasurementUnits.METRIC: - # return TEMP_CELSIUS - #return TEMP_FAHRENHEIT + # return UnitOfTemperature.CELSIUS + #return UnitOfTempterature.FAHRENHEIT @property def supported_features(self): @@ -116,30 +111,30 @@ def max_temp(self) -> float: @property def hvac_mode(self): if not self.is_on: - return HVAC_MODE_OFF + return HVACMode.OFF return self._hvac_mode_converter.to_option_string(self.appliance.get_erd_value(self.hvac_mode_erd_code)) @property def hvac_modes(self) -> List[str]: - return [HVAC_MODE_OFF] + self._hvac_mode_converter.options + return [HVACMode.OFF] + self._hvac_mode_converter.options @property def fan_mode(self): - if self.hvac_mode == HVAC_MODE_FAN_ONLY: + if self.hvac_mode == HVACMode.FAN_ONLY: return self._fan_only_fan_mode_converter.to_option_string(self.appliance.get_erd_value(self.fan_mode_erd_code)) return self._fan_mode_converter.to_option_string(self.appliance.get_erd_value(self.fan_mode_erd_code)) @property def fan_modes(self) -> List[str]: - if self.hvac_mode == HVAC_MODE_FAN_ONLY: + if self.hvac_mode == HVACMode.FAN_ONLY: return self._fan_only_fan_mode_converter.options return self._fan_mode_converter.options async def async_set_hvac_mode(self, hvac_mode: str) -> None: _LOGGER.debug(f"Setting HVAC mode from {self.hvac_mode} to {hvac_mode}") if hvac_mode != self.hvac_mode: - if hvac_mode == HVAC_MODE_OFF: + if hvac_mode == HVACMode.OFF: await self.appliance.async_set_erd_value(self.power_status_erd_code, ErdOnOff.OFF) else: #if it's not on, turn it on @@ -156,7 +151,7 @@ async def async_set_fan_mode(self, fan_mode: str) -> None: _LOGGER.debug(f"Setting Fan mode from {self.fan_mode} to {fan_mode}") if fan_mode != self.fan_mode: converter = (self._fan_only_fan_mode_converter - if self.hvac_mode == HVAC_MODE_FAN_ONLY + if self.hvac_mode == HVACMode.FAN_ONLY else self._fan_mode_converter ) @@ -185,7 +180,7 @@ async def async_turn_off(self): await self.appliance.async_set_erd_value(self.power_status_erd_code, ErdOnOff.OFF) def _convert_temp(self, temperature_f: int): - if self.temperature_unit == TEMP_FAHRENHEIT: + if self.temperature_unit == UnitOfTemperature.FAHRENHEIT: return float(temperature_f) else: return (temperature_f - 32.0) * (5/9) diff --git a/custom_components/ge_home/entities/common/ge_entity.py b/custom_components/ge_home/entities/common/ge_entity.py index 34f4037..6104d20 100644 --- a/custom_components/ge_home/entities/common/ge_entity.py +++ b/custom_components/ge_home/entities/common/ge_entity.py @@ -10,7 +10,7 @@ class GeEntity: def __init__(self, api: ApplianceApi): self._api = api - self.hass = None + self._added = False @property def unique_id(self) -> str: @@ -56,6 +56,18 @@ def icon(self) -> Optional[str]: def device_class(self) -> Optional[str]: return self._get_device_class() + @property + def added(self) -> bool: + return self._added + + async def async_added_to_hass(self) -> None: + """Run when entity about to be added to hass.""" + self._added = True + + async def async_will_remove_from_hass(self) -> None: + """Run when entity will be removed from hass.""" + self._added = False + def _stringify(self, value: any, **kwargs) -> Optional[str]: if isinstance(value, timedelta): return str(value)[:-3] if value else "" diff --git a/custom_components/ge_home/entities/common/ge_erd_number.py b/custom_components/ge_home/entities/common/ge_erd_number.py index d773ad1..91026dd 100644 --- a/custom_components/ge_home/entities/common/ge_erd_number.py +++ b/custom_components/ge_home/entities/common/ge_erd_number.py @@ -5,7 +5,7 @@ NumberEntity, NumberDeviceClass, ) -from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.const import UnitOfTemperature from gehomesdk import ErdCodeType, ErdCodeClass from .ge_erd_entity import GeErdEntity from ...devices import ApplianceApi @@ -91,7 +91,7 @@ def _get_uom(self): #NOTE: it appears that the API only sets temperature in Fahrenheit, #so we'll hard code this UOM instead of using the device configured #settings - return TEMP_FAHRENHEIT + return UnitOfTemperature.FAHRENHEIT return None diff --git a/custom_components/ge_home/entities/common/ge_erd_sensor.py b/custom_components/ge_home/entities/common/ge_erd_sensor.py index 9dc917a..8466dd9 100644 --- a/custom_components/ge_home/entities/common/ge_erd_sensor.py +++ b/custom_components/ge_home/entities/common/ge_erd_sensor.py @@ -1,17 +1,9 @@ import logging from typing import Optional from gehomesdk.erd.erd_data_type import ErdDataType -from homeassistant.components.sensor import SensorEntity, SensorStateClass - -from homeassistant.const import ( - DEVICE_CLASS_ENERGY, - DEVICE_CLASS_POWER, - DEVICE_CLASS_TEMPERATURE, - DEVICE_CLASS_BATTERY, - DEVICE_CLASS_POWER_FACTOR, - DEVICE_CLASS_HUMIDITY, - TEMP_FAHRENHEIT, -) +from homeassistant.components.sensor import SensorDeviceClass, SensorEntity, SensorStateClass + +from homeassistant.const import UnitOfTemperature from gehomesdk import ErdCodeType, ErdCodeClass from .ge_erd_entity import GeErdEntity from ...devices import ApplianceApi @@ -76,8 +68,8 @@ def _temp_units(self) -> Optional[str]: return self.api.hass.config.units.temperature_unit #if self._measurement_system == ErdMeasurementUnits.METRIC: - # return TEMP_CELSIUS - #return TEMP_FAHRENHEIT + # return UnitOfTemperature.CELSIUS + #return UnitOfTemperature.FAHRENHEIT def _convert_numeric_value_from_device(self, value): """Convert to expected data type""" @@ -97,20 +89,20 @@ def _get_uom(self): if ( self.erd_code_class in [ErdCodeClass.RAW_TEMPERATURE, ErdCodeClass.NON_ZERO_TEMPERATURE] - or self.device_class == DEVICE_CLASS_TEMPERATURE + or self.device_class == SensorDeviceClass.TEMPERATURE ): #NOTE: it appears that the API only sets temperature in Fahrenheit, #so we'll hard code this UOM instead of using the device configured #settings - return TEMP_FAHRENHEIT + return UnitOfTemperature.FAHRENHEIT if ( self.erd_code_class == ErdCodeClass.BATTERY - or self.device_class == DEVICE_CLASS_BATTERY + or self.device_class == SensorDeviceClass.BATTERY ): return "%" if self.erd_code_class == ErdCodeClass.PERCENTAGE: return "%" - if self.device_class == DEVICE_CLASS_POWER_FACTOR: + if self.device_class == SensorDeviceClass.POWER_FACTOR: return "%" if self.erd_code_class == ErdCodeClass.HUMIDITY: return "%" @@ -131,15 +123,15 @@ def _get_device_class(self) -> Optional[str]: ErdCodeClass.RAW_TEMPERATURE, ErdCodeClass.NON_ZERO_TEMPERATURE, ]: - return DEVICE_CLASS_TEMPERATURE + return SensorDeviceClass.TEMPERATURE if self.erd_code_class == ErdCodeClass.BATTERY: - return DEVICE_CLASS_BATTERY + return SensorDeviceClass.BATTERY if self.erd_code_class == ErdCodeClass.POWER: - return DEVICE_CLASS_POWER + return SensorDeviceClass.POWER if self.erd_code_class == ErdCodeClass.ENERGY: - return DEVICE_CLASS_ENERGY + return SensorDeviceClass.ENERGY if self.erd_code_class == ErdCodeClass.HUMIDITY: - return DEVICE_CLASS_HUMIDITY + return SensorDeviceClass.HUMIDITY return None @@ -147,7 +139,7 @@ def _get_state_class(self) -> Optional[str]: if self._state_class_override: return self._state_class_override - if self.device_class in [DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_ENERGY]: + if self.device_class in [SensorDeviceClass.TEMPERATURE, SensorDeviceClass.ENERGY]: return SensorStateClass.MEASUREMENT if self.erd_code_class in [ErdCodeClass.FLOW_RATE, ErdCodeClass.PERCENTAGE, ErdCodeClass.HUMIDITY]: return SensorStateClass.MEASUREMENT diff --git a/custom_components/ge_home/entities/common/ge_erd_switch.py b/custom_components/ge_home/entities/common/ge_erd_switch.py index cf39f9a..0fb3703 100644 --- a/custom_components/ge_home/entities/common/ge_erd_switch.py +++ b/custom_components/ge_home/entities/common/ge_erd_switch.py @@ -29,5 +29,5 @@ async def async_turn_on(self, **kwargs): async def async_turn_off(self, **kwargs): """Turn the switch off.""" - _LOGGER.debug(f"Turning on {self.unique_id}") + _LOGGER.debug(f"Turning off {self.unique_id}") await self.appliance.async_set_erd_value(self.erd_code, self._converter.false_value()) diff --git a/custom_components/ge_home/entities/common/ge_water_heater.py b/custom_components/ge_home/entities/common/ge_water_heater.py index 55ae4d9..88b376a 100644 --- a/custom_components/ge_home/entities/common/ge_water_heater.py +++ b/custom_components/ge_home/entities/common/ge_water_heater.py @@ -3,10 +3,7 @@ from typing import Any, Dict, List, Optional from homeassistant.components.water_heater import WaterHeaterEntity -from homeassistant.const import ( - TEMP_FAHRENHEIT, - TEMP_CELSIUS -) +from homeassistant.const import UnitOfTemperature from gehomesdk import ErdCode, ErdMeasurementUnits from ...const import DOMAIN from .ge_erd_entity import GeEntity @@ -37,8 +34,8 @@ def temperature_unit(self): #It appears that the GE API is alwasy Fehrenheit #measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT) #if measurement_system == ErdMeasurementUnits.METRIC: - # return TEMP_CELSIUS - return TEMP_FAHRENHEIT + # return UnitOfTemperature.CELSIUS + return UnitOfTemperature.FAHRENHEIT @property def supported_features(self): diff --git a/custom_components/ge_home/entities/fridge/__init__.py b/custom_components/ge_home/entities/fridge/__init__.py index b277fcf..2d14761 100644 --- a/custom_components/ge_home/entities/fridge/__init__.py +++ b/custom_components/ge_home/entities/fridge/__init__.py @@ -1,4 +1,5 @@ from .ge_fridge import GeFridge from .ge_freezer import GeFreezer from .ge_dispenser import GeDispenser -from .convertable_drawer_mode_options import ConvertableDrawerModeOptionsConverter \ No newline at end of file +from .convertable_drawer_mode_options import ConvertableDrawerModeOptionsConverter +from .ge_fridge_ice_control_switch import GeFridgeIceControlSwitch \ No newline at end of file diff --git a/custom_components/ge_home/entities/fridge/const.py b/custom_components/ge_home/entities/fridge/const.py index f7ca729..ac71406 100644 --- a/custom_components/ge_home/entities/fridge/const.py +++ b/custom_components/ge_home/entities/fridge/const.py @@ -1,10 +1,7 @@ -from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE -) +from homeassistant.components.water_heater import WaterHeaterEntityFeature ATTR_DOOR_STATUS = "door_status" -GE_FRIDGE_SUPPORT = (SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE) +GE_FRIDGE_SUPPORT = (WaterHeaterEntityFeature.OPERATION_MODE | WaterHeaterEntityFeature.TARGET_TEMPERATURE) HEATER_TYPE_FRIDGE = "fridge" HEATER_TYPE_FREEZER = "freezer" diff --git a/custom_components/ge_home/entities/fridge/convertable_drawer_mode_options.py b/custom_components/ge_home/entities/fridge/convertable_drawer_mode_options.py index b9b933c..9705351 100644 --- a/custom_components/ge_home/entities/fridge/convertable_drawer_mode_options.py +++ b/custom_components/ge_home/entities/fridge/convertable_drawer_mode_options.py @@ -2,7 +2,7 @@ from typing import List, Any, Optional from gehomesdk import ErdConvertableDrawerMode -from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.const import UnitOfTemperature from homeassistant.util.unit_system import UnitSystem from ..common import OptionsConverter @@ -43,7 +43,7 @@ def to_option_string(self, value: ErdConvertableDrawerMode) -> Optional[str]: t = _TEMP_MAP.get(value, None) if t and self._units.is_metric: - t = self._units.temperature(float(t), TEMP_FAHRENHEIT) + t = self._units.temperature(float(t), UnitOfTemperature.FAHRENHEIT) t = round(t,1) if t: diff --git a/custom_components/ge_home/entities/fridge/ge_abstract_fridge.py b/custom_components/ge_home/entities/fridge/ge_abstract_fridge.py index 312a3dd..a024ca1 100644 --- a/custom_components/ge_home/entities/fridge/ge_abstract_fridge.py +++ b/custom_components/ge_home/entities/fridge/ge_abstract_fridge.py @@ -6,7 +6,7 @@ import logging from typing import Any, Dict, List, Optional -from homeassistant.const import ATTR_TEMPERATURE, TEMP_FAHRENHEIT +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.util.unit_conversion import TemperatureConverter from gehomesdk import ( ErdCode, @@ -117,7 +117,7 @@ def min_temp(self): return getattr(self.setpoint_limits, f"{self.heater_type}_min") except: _LOGGER.debug("No temperature setpoint limits available. Using hardcoded limits.") - return TemperatureConverter.convert(self.temp_limits[f"{self.heater_type}_min"], TEMP_FAHRENHEIT, self.temperature_unit) + return TemperatureConverter.convert(self.temp_limits[f"{self.heater_type}_min"], UnitOfTemperature.FAHRENHEIT, self.temperature_unit) @property def max_temp(self): @@ -126,7 +126,7 @@ def max_temp(self): return getattr(self.setpoint_limits, f"{self.heater_type}_max") except: _LOGGER.debug("No temperature setpoint limits available. Using hardcoded limits.") - return TemperatureConverter.convert(self.temp_limits[f"{self.heater_type}_max"], TEMP_FAHRENHEIT, self.temperature_unit) + return TemperatureConverter.convert(self.temp_limits[f"{self.heater_type}_max"], UnitOfTemperature.FAHRENHEIT, self.temperature_unit) @property def current_operation(self) -> str: diff --git a/custom_components/ge_home/entities/fridge/ge_dispenser.py b/custom_components/ge_home/entities/fridge/ge_dispenser.py index 04bc543..394af9e 100644 --- a/custom_components/ge_home/entities/fridge/ge_dispenser.py +++ b/custom_components/ge_home/entities/fridge/ge_dispenser.py @@ -3,7 +3,7 @@ import logging from typing import List, Optional, Dict, Any -from homeassistant.const import ATTR_TEMPERATURE, TEMP_FAHRENHEIT +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from homeassistant.util.unit_conversion import TemperatureConverter from gehomesdk import ( @@ -102,12 +102,12 @@ def target_temperature(self) -> Optional[int]: @property def min_temp(self): """Return the minimum temperature.""" - return TemperatureConverter.convert(self._min_temp, TEMP_FAHRENHEIT, self.temperature_unit) + return TemperatureConverter.convert(self._min_temp, UnitOfTemperature.FAHRENHEIT, self.temperature_unit) @property def max_temp(self): """Return the maximum temperature.""" - return TemperatureConverter.convert(self._max_temp, TEMP_FAHRENHEIT, self.temperature_unit) + return TemperatureConverter.convert(self._max_temp, UnitOfTemperature.FAHRENHEIT, self.temperature_unit) @property def extra_state_attributes(self) -> Dict[str, Any]: diff --git a/custom_components/ge_home/entities/fridge/ge_fridge_ice_control_switch.py b/custom_components/ge_home/entities/fridge/ge_fridge_ice_control_switch.py new file mode 100644 index 0000000..f86c59a --- /dev/null +++ b/custom_components/ge_home/entities/fridge/ge_fridge_ice_control_switch.py @@ -0,0 +1,47 @@ +import logging +from gehomesdk import ErdCode, IceMakerControlStatus, ErdOnOff + +from ...devices import ApplianceApi +from ..common import GeErdSwitch, BoolConverter + +_LOGGER = logging.getLogger(__name__) + +class GeFridgeIceControlSwitch(GeErdSwitch): + def __init__(self, api: ApplianceApi, control_type: str): + super().__init__(api, ErdCode.ICE_MAKER_CONTROL, BoolConverter()) + self._control_type = control_type + + @property + def control_status(self) -> IceMakerControlStatus: + return self.appliance.get_erd_value(ErdCode.ICE_MAKER_CONTROL) + + @property + def is_on(self) -> bool: + if self._control_type == "fridge": + return self.control_status.status_fridge == ErdOnOff.ON + else: + return self.control_status.status_freezer == ErdOnOff.ON + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + _LOGGER.debug(f"Turning on {self.unique_id}") + + old_status = self.control_status + if self._control_type == "fridge": + new_status = IceMakerControlStatus(ErdOnOff.ON, old_status.status_freezer) + else: + new_status = IceMakerControlStatus(old_status.status_fridge, ErdOnOff.ON) + + await self.appliance.async_set_erd_value(self.erd_code, new_status) + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + _LOGGER.debug(f"Turning off {self.unique_id}") + + old_status = self.control_status + if self._control_type == "fridge": + new_status = IceMakerControlStatus(ErdOnOff.OFF, old_status.status_freezer) + else: + new_status = IceMakerControlStatus(old_status.status_fridge, ErdOnOff.OFF) + + await self.appliance.async_set_erd_value(self.erd_code, new_status) diff --git a/custom_components/ge_home/entities/oven/const.py b/custom_components/ge_home/entities/oven/const.py index af9d602..8195571 100644 --- a/custom_components/ge_home/entities/oven/const.py +++ b/custom_components/ge_home/entities/oven/const.py @@ -1,13 +1,10 @@ import bidict -from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE -) +from homeassistant.components.water_heater import WaterHeaterEntityFeature from gehomesdk import ErdOvenCookMode SUPPORT_NONE = 0 -GE_OVEN_SUPPORT = (SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE) +GE_OVEN_SUPPORT = (WaterHeaterEntityFeature.OPERATION_MODE | WaterHeaterEntityFeature.TARGET_TEMPERATURE) OP_MODE_OFF = "Off" OP_MODE_BAKE = "Bake" diff --git a/custom_components/ge_home/entities/oven/ge_oven.py b/custom_components/ge_home/entities/oven/ge_oven.py index 297b2c1..710178f 100644 --- a/custom_components/ge_home/entities/oven/ge_oven.py +++ b/custom_components/ge_home/entities/oven/ge_oven.py @@ -10,7 +10,7 @@ OvenCookSetting ) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from ...const import DOMAIN from ...devices import ApplianceApi from ..common import GeAbstractWaterHeater @@ -56,8 +56,8 @@ def name(self) -> Optional[str]: def temperature_unit(self): measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT) if measurement_system == ErdMeasurementUnits.METRIC: - return TEMP_CELSIUS - return TEMP_FAHRENHEIT + return UnitOfTemperature.CELSIUS + return UnitOfTemperature.FAHRENHEIT @property def oven_select(self) -> str: @@ -155,7 +155,7 @@ async def async_set_operation_mode(self, operation_mode: str): target_temp = 0 elif self.target_temperature: target_temp = self.target_temperature - elif self.temperature_unit == TEMP_FAHRENHEIT: + elif self.temperature_unit == UnitOfTemperature.FAHRENHEIT: target_temp = 350 else: target_temp = 177 diff --git a/custom_components/ge_home/entities/water_heater/ge_water_heater.py b/custom_components/ge_home/entities/water_heater/ge_water_heater.py index 7954055..e9958c4 100644 --- a/custom_components/ge_home/entities/water_heater/ge_water_heater.py +++ b/custom_components/ge_home/entities/water_heater/ge_water_heater.py @@ -7,12 +7,9 @@ ErdWaterHeaterMode ) -from homeassistant.components.water_heater import ( - SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE -) +from homeassistant.components.water_heater import WaterHeaterEntityFeature -from homeassistant.const import ATTR_TEMPERATURE, TEMP_FAHRENHEIT +from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature from ...devices import ApplianceApi from ..common import GeAbstractWaterHeater from .heater_modes import WhHeaterModeConverter @@ -34,11 +31,11 @@ def heater_type(self) -> str: @property def supported_features(self): - return (SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE) + return (WaterHeaterEntityFeature.OPERATION_MODE | WaterHeaterEntityFeature.TARGET_TEMPERATURE) @property def temperature_unit(self): - return TEMP_FAHRENHEIT + return UnitOfTemperature.FAHRENHEIT @property def current_temperature(self) -> Optional[int]: diff --git a/custom_components/ge_home/humidifier.py b/custom_components/ge_home/humidifier.py index 4d1ed11..68aa896 100644 --- a/custom_components/ge_home/humidifier.py +++ b/custom_components/ge_home/humidifier.py @@ -33,4 +33,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f'Found {len(entities):d} unregistered humidifiers') async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/light.py b/custom_components/ge_home/light.py index b652d02..ba2a69c 100644 --- a/custom_components/ge_home/light.py +++ b/custom_components/ge_home/light.py @@ -37,4 +37,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f"Found {len(entities):d} unregistered lights") async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/manifest.json b/custom_components/ge_home/manifest.json index 969c1f8..33dd56f 100644 --- a/custom_components/ge_home/manifest.json +++ b/custom_components/ge_home/manifest.json @@ -5,7 +5,7 @@ "integration_type": "hub", "iot_class": "cloud_push", "documentation": "https://github.com/simbaja/ha_gehome", - "requirements": ["gehomesdk==0.5.23","magicattr==0.1.6","slixmpp==1.8.3"], + "requirements": ["gehomesdk==0.5.26","magicattr==0.1.6","slixmpp==1.8.3"], "codeowners": ["@simbaja"], - "version": "0.6.8" + "version": "0.6.9" } diff --git a/custom_components/ge_home/number.py b/custom_components/ge_home/number.py index 2b4213c..d864c09 100644 --- a/custom_components/ge_home/number.py +++ b/custom_components/ge_home/number.py @@ -34,4 +34,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f'Found {len(entities):d} unregisterd numbers') async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/select.py b/custom_components/ge_home/select.py index 613b03b..158af27 100644 --- a/custom_components/ge_home/select.py +++ b/custom_components/ge_home/select.py @@ -37,4 +37,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f"Found {len(entities):d} unregistered selects") async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/sensor.py b/custom_components/ge_home/sensor.py index 9732dbf..0bf7ba6 100644 --- a/custom_components/ge_home/sensor.py +++ b/custom_components/ge_home/sensor.py @@ -47,8 +47,16 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f'Found {len(entities):d} unregistered sensors') async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) - + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) + # register set_timer entity service platform.async_register_entity_service( SERVICE_SET_TIMER, diff --git a/custom_components/ge_home/switch.py b/custom_components/ge_home/switch.py index 3aa6f11..7c339ae 100644 --- a/custom_components/ge_home/switch.py +++ b/custom_components/ge_home/switch.py @@ -33,4 +33,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f'Found {len(entities):d} unregistered switches') async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/custom_components/ge_home/update_coordinator.py b/custom_components/ge_home/update_coordinator.py index b846bb4..944c229 100644 --- a/custom_components/ge_home/update_coordinator.py +++ b/custom_components/ge_home/update_coordinator.py @@ -3,7 +3,7 @@ import asyncio import async_timeout import logging -from typing import Any, Dict, Iterable, Optional, Tuple +from typing import Any, Callable, Dict, Iterable, Optional, Tuple, List from gehomesdk import ( EVENT_APPLIANCE_INITIAL_UPDATE, @@ -22,6 +22,7 @@ from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_REGION from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.entity import Entity from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import ( @@ -55,17 +56,17 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator): def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: """Set up the GeHomeUpdateCoordinator class.""" - self._hass = hass + super().__init__(hass, _LOGGER, name=DOMAIN) + self._config_entry = config_entry self._username = config_entry.data[CONF_USERNAME] self._password = config_entry.data[CONF_PASSWORD] self._region = config_entry.data[CONF_REGION] self._appliance_apis = {} # type: Dict[str, ApplianceApi] + self._signal_remove_callbacks = [] # type: List[Callable] self._reset_initialization() - super().__init__(hass, _LOGGER, name=DOMAIN) - def _reset_initialization(self): self.client = None # type: Optional[GeWebsocketClient] @@ -111,7 +112,11 @@ def appliance_apis(self) -> Dict[str, ApplianceApi]: @property def signal_ready(self) -> str: """Event specific per entry to signal readiness""" - return f"{DOMAIN}-ready-{self._config_entry.entry_id}" + return f"{DOMAIN}-ready-{self._config_entry.entry_id}" + + @property + def initialized(self) -> bool: + return self._init_done @property def online(self) -> bool: @@ -150,6 +155,9 @@ def maybe_add_appliance_api(self, appliance: GeAppliance): api = self.appliance_apis[mac_addr] api.appliance = appliance + def add_signal_remove_callback(self, cb: Callable): + self._signal_remove_callbacks.append(cb) + async def get_client(self) -> GeWebsocketClient: """Get a new GE Websocket client.""" if self.client: @@ -161,8 +169,7 @@ async def get_client(self) -> GeWebsocketClient: finally: self._reset_initialization() - loop = self._hass.loop - self.client = self.create_ge_client(event_loop=loop) + self.client = self.create_ge_client(event_loop=self.hass.loop) return self.client async def async_setup(self): @@ -201,9 +208,9 @@ async def async_start_client(self): async def async_begin_session(self): """Begins the ge_home session.""" _LOGGER.debug("Beginning session") - session = self._hass.helpers.aiohttp_client.async_get_clientsession() + session = self.hass.helpers.aiohttp_client.async_get_clientsession() await self.client.async_get_credentials(session) - fut = asyncio.ensure_future(self.client.async_run_client(), loop=self._hass.loop) + fut = asyncio.ensure_future(self.client.async_run_client(), loop=self.hass.loop) _LOGGER.debug("Client running") return fut @@ -211,6 +218,12 @@ async def async_reset(self): """Resets the coordinator.""" _LOGGER.debug("resetting the coordinator") entry = self._config_entry + + # remove all the callbacks for this coordinator + for c in self._signal_remove_callbacks: + c() + self._signal_remove_callbacks.clear() + unload_ok = all( await asyncio.gather( *[ @@ -263,7 +276,7 @@ def shutdown(self, event) -> None: _LOGGER.info("ge_home shutting down") if self.client: self.client.clear_event_handlers() - self._hass.loop.create_task(self.client.disconnect()) + self.hass.loop.create_task(self.client.disconnect()) async def on_device_update(self, data: Tuple[GeAppliance, Dict[ErdCodeType, Any]]): """Let HA know there's new state.""" @@ -272,24 +285,34 @@ async def on_device_update(self, data: Tuple[GeAppliance, Dict[ErdCodeType, Any] try: api = self.appliance_apis[appliance.mac_addr] except KeyError: + _LOGGER.warn(f"Could not find appliance {appliance.mac_addr} in known device list.") return - - for entity in api.entities: - if entity.enabled: - _LOGGER.debug(f"Updating {entity} ({entity.unique_id}, {entity.entity_id})") - entity.async_write_ha_state() + + self._update_entity_state(api.entities) async def _refresh_ha_state(self): entities = [ entity for api in self.appliance_apis.values() for entity in api.entities ] + + self._update_entity_state(entities) + + def _update_entity_state(self, entities: List[Entity]): + from .entities import GeEntity for entity in entities: + # if this is a GeEntity, check if it's been added + #if not, don't try to refresh this entity + if isinstance(entity, GeEntity): + gee: GeEntity = entity + if not gee.added: + _LOGGER.debug(f"Entity {entity} ({entity.unique_id}, {entity.entity_id}) not yet added, skipping update...") + continue if entity.enabled: try: _LOGGER.debug(f"Refreshing state for {entity} ({entity.unique_id}, {entity.entity_id}") entity.async_write_ha_state() except: - _LOGGER.debug(f"Could not refresh state for {entity} ({entity.unique_id}, {entity.entity_id}") + _LOGGER.warn(f"Could not refresh state for {entity} ({entity.unique_id}, {entity.entity_id}", exc_info=1) @property def all_appliances_updated(self) -> bool: diff --git a/custom_components/ge_home/water_heater.py b/custom_components/ge_home/water_heater.py index 19e8b4d..9bb0a8e 100644 --- a/custom_components/ge_home/water_heater.py +++ b/custom_components/ge_home/water_heater.py @@ -35,4 +35,12 @@ def async_devices_discovered(apis: list[ApplianceApi]): _LOGGER.debug(f'Found {len(entities):d} unregistered water heaters') async_add_entities(entities) - async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered) + #if we're already initialized at this point, call device + #discovery directly, otherwise add a callback based on the + #ready signal + if coordinator.initialized: + async_devices_discovered(coordinator.appliance_apis.values()) + else: + # add the ready signal and register the remove callback + coordinator.add_signal_remove_callback( + async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)) diff --git a/info.md b/info.md index aec528d..20477dc 100644 --- a/info.md +++ b/info.md @@ -69,6 +69,10 @@ A/C Controls: #### Features +{% if version_installed.split('.') | map('int') < '0.6.9'.split('.') | map('int') %} +- Added additional fridge controls (#200) +{% endif %} + {% if version_installed.split('.') | map('int') < '0.6.8'.split('.') | map('int') %} - Added Dehumidifier (#114) - Added oven drawer sensors @@ -123,6 +127,11 @@ A/C Controls: #### Bugfixes +{% if version_installed.split('.') | map('int') < '0.6.9'.split('.') | map('int') %} +- Bugfix: Additional auth stability improvements (#215, #211) +- Bugfix: Removed deprecated constants (#218) +{% endif %} + {% if version_installed.split('.') | map('int') < '0.6.8'.split('.') | map('int') %} - Bugfix: Fixed issue with oven lights (#174) - Bugfix: Fixed issues with dual dishwasher (#161)