v0.6.8 Develop -> Main (#194)

* - updated water heater naming

* - initial support for built-in AC units

* Add support for Built-In AC Unit

Built-In AC unit operation and function is similar to a WAC.
`gehomesdk` version bumped from 0.4.25 to 0.4.27 to include latest
ErdApplianceType enum.

* Update README.md

Update README.md to include Built-In AC as supported device

* - updated zero serial number detection (resolves #89)

* - updated version
- updated changelog

* - hopefully fixed recursion bug with numbers

* - added cooktop support

* - fixed circular reference

* Add support for fridges with reduced support for ERD codes (no turbo mode, no current temperature reporting, no temperature setpoint limit reporting, no door status reporting). This change has been tested on a Fisher & Paykel RF610AA.

* - added dual dishwasher support
- updated documentation
- version bumps

* - added water heater support

* - added basic espresso maker device

* - bugfixes

* - rewrote initialization (resolves #99)

* - added logic to prevent double registration of entities

* - added correct min/max temps for water heaters

* Fix CoffeeMaker after the NumberEntity refactoring

* - added fix for CGY366P2TS1 (#116) to try to get the cooktop status, but fail more gracefully if it isn't supported

* - fixed region setting in update coordinator

* - version bump
- doc update
- string fixes

* - updated the temperature conversion to use non-deprecated HASS methods

* - updated documentation (@gleepwurp)

* - more documentation updates

* - updated dishwasher for new functionality
- updated documentation

* updated uom for liquid volume per HA specifications

* - fixed typo in oven (#149)

* - updated change log
- fixed oven light control (#144)

* - fixed issues with dishwasher (#155)
- added oim descaling sensor (#154)
- version bump

* - updated change log

* updated dual dishwasher for changes to gehomesdk (#161)

Co-authored-by: na4ma4 <na4ma4@users.noreply.github.com>

* await disconnect when unloading (#169)

Fixes simbaja#164.

* Check for upper oven light when there is a lower oven (#174)

Resolves issue #121

* - added oven warming drawers
- simplified oven entity logic

* - fixed issues with the new oven initialization logic

* - fixed bad type import for warming drawer

* -added iot class (#181)

* - updated the gehomesdk version requirement

* - gehomesdk version bump

* - added dehumidifier (#114)

* - dehumidifier appliance type fix

* - added oven state sensors (#175)

* - updated change logs
- updated sdk version requirement

* - removed target select
- added dehumidifier entity
- sdk version bump

* - updated dehumidifier icon

* - added humidifier platform

* - fixed typos in humidifier class

* - fixed copy/paste error

* - sdk version requirement bump

* - sdk version bump

* - updated dehumidifier to handle target precision
- updated dehumidifier sensor value conversion

* - missed a commit

* - SDK version bump

---------

Co-authored-by: Rob Schmidt <rwschmidt26@gmail.com>
Co-authored-by: Federico Sevilla <federico@sevilla.id.au>
Co-authored-by: alexanv1 <44785744+alexanv1@users.noreply.github.com>
Co-authored-by: na4ma4 <25967676+na4ma4@users.noreply.github.com>
Co-authored-by: na4ma4 <na4ma4@users.noreply.github.com>
Co-authored-by: Alex Peters <alex@peters.net>
Co-authored-by: Chris Petersen <154074+ex-nerd@users.noreply.github.com>
This commit is contained in:
simbaja 2023-09-10 19:30:38 -04:00 committed by GitHub
parent 3ad1350ee0
commit 3b6e657272
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 501 additions and 41 deletions

View File

@ -1,6 +1,17 @@
# GE Home Appliances (SmartHQ) Changelog
## 0.6.8
- Added Dehumidifier [#114]
- Added oven drawer sensors
- Added oven current state sensors [#175]
- Added descriptors to manifest [#181]
- Bugfix: Fixed issue with oven lights [#174]
- Bugfix: Fixed issues with dual dishwasher [#161]
- Bugfix: Fixed disconnection issue [#169]
## 0.6.7
- Bugfix: fixed issues with dishwasher [#155]

View File

@ -25,7 +25,7 @@ from .oim import OimApi
from .coffee_maker import CcmApi
from .dual_dishwasher import DualDishwasherApi
from .espresso_maker import EspressoMakerApi
from .dehumidifier import DehumidifierApi
_LOGGER = logging.getLogger(__name__)
@ -77,6 +77,8 @@ def get_appliance_api_type(appliance_type: ErdApplianceType) -> Type:
return CcmApi
if appliance_type == ErdApplianceType.ESPRESSO_MAKER:
return EspressoMakerApi
if appliance_type == ErdApplianceType.DEHUMIDIFIER:
return DehumidifierApi
# Fallback
return ApplianceApi

View File

@ -0,0 +1,44 @@
import logging
from typing import List
from homeassistant.helpers.entity import Entity
from gehomesdk import (
ErdCode,
ErdApplianceType,
ErdOnOff
)
from .base import ApplianceApi
from ..entities import (
GeErdSensor,
GeErdSelect,
GeErdPropertySensor,
GeErdSwitch,
ErdOnOffBoolConverter,
GeDehumidifierFanSpeedSensor,
GeDehumidifier
)
_LOGGER = logging.getLogger(__name__)
class DehumidifierApi(ApplianceApi):
"""API class for Dehumidifier objects"""
APPLIANCE_TYPE = ErdApplianceType.DEHUMIDIFIER
def get_all_entities(self) -> List[Entity]:
base_entities = super().get_all_entities()
dhum_entities = [
GeErdSwitch(self, ErdCode.AC_POWER_STATUS, bool_converter=ErdOnOffBoolConverter(), icon_on_override="mdi:power-on", icon_off_override="mdi:power-off"),
GeDehumidifierFanSpeedSensor(self, ErdCode.AC_FAN_SETTING, icon_override="mdi:fan"),
GeErdSensor(self, ErdCode.DHUM_CURRENT_HUMIDITY),
GeErdSensor(self, ErdCode.DHUM_TARGET_HUMIDITY),
GeErdPropertySensor(self, ErdCode.DHUM_MAINTENANCE, "empty_bucket", device_class_override="problem"),
GeErdPropertySensor(self, ErdCode.DHUM_MAINTENANCE, "clean_filter", device_class_override="problem"),
GeDehumidifier(self)
]
entities = base_entities + dhum_entities
return entities

View File

@ -19,18 +19,23 @@ class DualDishwasherApi(ApplianceApi):
lower_entities = [
GeErdSensor(self, ErdCode.DISHWASHER_CYCLE_STATE, erd_override="lower_cycle_state", icon_override="mdi:state-machine"),
GeErdSensor(self, ErdCode.DISHWASHER_RINSE_AGENT, erd_override="lower_rinse_agent", icon_override="mdi:shimmer"),
GeErdSensor(self, ErdCode.DISHWASHER_TIME_REMAINING, erd_override="lower_time_remaining"),
GeErdBinarySensor(self, ErdCode.DISHWASHER_DOOR_STATUS, erd_override="lower_door_status"),
#Reminders
GeErdPropertySensor(self, ErdCode.DISHWASHER_REMINDERS, "add_rinse_aid", erd_override="lower_reminder", icon_override="mdi:shimmer"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_REMINDERS, "clean_filter", erd_override="lower_reminder", icon_override="mdi:dishwasher-alert"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_REMINDERS, "sanitized", erd_override="lower_reminder", icon_override="mdi:silverware-clean"),
#User Setttings
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "sound", erd_override="lower_setting", icon_override="mdi:volume-high"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "mute", erd_override="lower_setting", icon_override="mdi:volume-mute"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "lock_control", erd_override="lower_setting", icon_override="mdi:lock"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "sabbath", erd_override="lower_setting", icon_override="mdi:star-david"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "cycle_mode", erd_override="lower_setting", icon_override="mdi:state-machine"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "presoak", erd_override="lower_setting", icon_override="mdi:water"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "bottle_jet", erd_override="lower_setting", icon_override="mdi:bottle-tonic-outline"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "wash_temp", erd_override="lower_setting", icon_override="mdi:coolant-temperature"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "rinse_aid", erd_override="lower_setting", icon_override="mdi:shimmer"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "dry_option", erd_override="lower_setting", icon_override="mdi:fan"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "wash_zone", erd_override="lower_setting", icon_override="mdi:dock-top"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "delay_hours", erd_override="lower_setting", icon_override="mdi:clock-fast")
@ -39,18 +44,23 @@ class DualDishwasherApi(ApplianceApi):
upper_entities = [
#GeDishwasherControlLockedSwitch(self, ErdCode.USER_INTERFACE_LOCKED),
GeErdSensor(self, ErdCode.DISHWASHER_UPPER_CYCLE_STATE, erd_override="upper_cycle_state", icon_override="mdi:state-machine"),
GeErdSensor(self, ErdCode.DISHWASHER_UPPER_RINSE_AGENT, erd_override="upper_rinse_agent", icon_override="mdi:shimmer"),
GeErdSensor(self, ErdCode.DISHWASHER_UPPER_TIME_REMAINING, erd_override="upper_time_remaining"),
GeErdBinarySensor(self, ErdCode.DISHWASHER_UPPER_DOOR_STATUS, erd_override="upper_door_status"),
#Reminders
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_REMINDERS, "add_rinse_aid", erd_override="upper_reminder", icon_override="mdi:shimmer"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_REMINDERS, "clean_filter", erd_override="upper_reminder", icon_override="mdi:dishwasher-alert"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_REMINDERS, "sanitized", erd_override="upper_reminder", icon_override="mdi:silverware-clean"),
#User Setttings
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "sound", erd_override="upper_setting", icon_override="mdi:volume-high"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "mute", erd_override="upper_setting", icon_override="mdi:volume-mute"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "lock_control", erd_override="upper_setting", icon_override="mdi:lock"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "sabbath", erd_override="upper_setting", icon_override="mdi:star-david"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "cycle_mode", erd_override="upper_setting", icon_override="mdi:state-machine"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "presoak", erd_override="upper_setting", icon_override="mdi:water"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "bottle_jet", erd_override="upper_setting", icon_override="mdi:bottle-tonic-outline"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "wash_temp", erd_override="upper_setting", icon_override="mdi:coolant-temperature"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "rinse_aid", erd_override="upper_setting", icon_override="mdi:shimmer"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "dry_option", erd_override="upper_setting", icon_override="mdi:fan"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "wash_zone", erd_override="upper_setting", icon_override="mdi:dock-top"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_UPPER_USER_SETTING, "delay_hours", erd_override="upper_setting", icon_override="mdi:clock-fast")

View File

@ -11,7 +11,8 @@ from gehomesdk import (
ErdCooktopConfig,
CooktopStatus,
ErdOvenLightLevel,
ErdOvenLightLevelAvailability
ErdOvenLightLevelAvailability,
ErdOvenWarmingState
)
from .base import ApplianceApi
@ -23,6 +24,7 @@ from ..entities import (
GeErdPropertyBinarySensor,
GeOven,
GeOvenLightLevelSelect,
GeOvenWarmingStateSelect,
UPPER_OVEN,
LOWER_OVEN
)
@ -49,6 +51,10 @@ class OvenApi(ApplianceApi):
lower_light : ErdOvenLightLevel = self.try_get_erd_value(ErdCode.LOWER_OVEN_LIGHT)
lower_light_availability: ErdOvenLightLevelAvailability = self.try_get_erd_value(ErdCode.LOWER_OVEN_LIGHT_AVAILABILITY)
upper_warm_drawer : ErdOvenWarmingState = self.try_get_erd_value(ErdCode.UPPER_OVEN_WARMING_DRAWER_STATE)
lower_warm_drawer : ErdOvenWarmingState = self.try_get_erd_value(ErdCode.LOWER_OVEN_WARMING_DRAWER_STATE)
warm_drawer : ErdOvenWarmingState = self.try_get_erd_value(ErdCode.WARMING_DRAWER_STATE)
_LOGGER.debug(f"Oven Config: {oven_config}")
_LOGGER.debug(f"Cooktop Config: {cooktop_config}")
oven_entities = []
@ -56,44 +62,43 @@ class OvenApi(ApplianceApi):
if oven_config.has_lower_oven:
oven_entities.extend([
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_MODE),
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_TIME_REMAINING),
GeErdTimerSensor(self, ErdCode.UPPER_OVEN_KITCHEN_TIMER),
GeErdSensor(self, ErdCode.UPPER_OVEN_USER_TEMP_OFFSET),
GeErdSensor(self, ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE),
GeErdBinarySensor(self, ErdCode.UPPER_OVEN_REMOTE_ENABLED),
GeErdSensor(self, ErdCode.LOWER_OVEN_COOK_MODE),
GeErdSensor(self, ErdCode.LOWER_OVEN_CURRENT_STATE),
GeErdSensor(self, ErdCode.LOWER_OVEN_COOK_TIME_REMAINING),
GeErdTimerSensor(self, ErdCode.LOWER_OVEN_KITCHEN_TIMER),
GeErdSensor(self, ErdCode.LOWER_OVEN_USER_TEMP_OFFSET),
GeErdSensor(self, ErdCode.LOWER_OVEN_DISPLAY_TEMPERATURE),
GeErdBinarySensor(self, ErdCode.LOWER_OVEN_REMOTE_ENABLED),
GeOven(self, LOWER_OVEN, True, self._temperature_code(has_lower_raw_temperature)),
GeOven(self, UPPER_OVEN, True, self._temperature_code(has_upper_raw_temperature)),
GeOven(self, LOWER_OVEN, True, self._temperature_code(has_lower_raw_temperature))
])
if has_upper_raw_temperature:
oven_entities.append(GeErdSensor(self, ErdCode.UPPER_OVEN_RAW_TEMPERATURE))
if has_lower_raw_temperature:
oven_entities.append(GeErdSensor(self, ErdCode.LOWER_OVEN_RAW_TEMPERATURE))
if lower_light_availability is None or lower_light_availability.is_available or lower_light is not None:
oven_entities.append(GeOvenLightLevelSelect(self, ErdCode.LOWER_OVEN_LIGHT))
else:
oven_entities.extend([
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_MODE, self._single_name(ErdCode.UPPER_OVEN_COOK_MODE)),
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_TIME_REMAINING, self._single_name(ErdCode.UPPER_OVEN_COOK_TIME_REMAINING)),
GeErdTimerSensor(self, ErdCode.UPPER_OVEN_KITCHEN_TIMER, self._single_name(ErdCode.UPPER_OVEN_KITCHEN_TIMER)),
GeErdSensor(self, ErdCode.UPPER_OVEN_USER_TEMP_OFFSET, self._single_name(ErdCode.UPPER_OVEN_USER_TEMP_OFFSET)),
GeErdSensor(self, ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE, self._single_name(ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE)),
GeErdBinarySensor(self, ErdCode.UPPER_OVEN_REMOTE_ENABLED, self._single_name(ErdCode.UPPER_OVEN_REMOTE_ENABLED)),
GeOven(self, UPPER_OVEN, False, self._temperature_code(has_upper_raw_temperature))
])
if has_upper_raw_temperature:
oven_entities.append(GeErdSensor(self, ErdCode.UPPER_OVEN_RAW_TEMPERATURE, self._single_name(ErdCode.UPPER_OVEN_RAW_TEMPERATURE)))
if upper_light_availability is None or upper_light_availability.is_available or upper_light is not None:
oven_entities.append(GeOvenLightLevelSelect(self, ErdCode.UPPER_OVEN_LIGHT, self._single_name(ErdCode.UPPER_OVEN_LIGHT)))
if lower_warm_drawer is not None:
oven_entities.append(GeOvenWarmingStateSelect(self, ErdCode.LOWER_OVEN_WARMING_DRAWER_STATE))
oven_entities.extend([
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_MODE, self._single_name(ErdCode.UPPER_OVEN_COOK_MODE, ~oven_config.has_lower_oven)),
GeErdSensor(self, ErdCode.UPPER_OVEN_CURRENT_STATE, self._single_name(ErdCode.UPPER_OVEN_CURRENT_STATE, ~oven_config.has_lower_oven)),
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_TIME_REMAINING, self._single_name(ErdCode.UPPER_OVEN_COOK_TIME_REMAINING, ~oven_config.has_lower_oven)),
GeErdTimerSensor(self, ErdCode.UPPER_OVEN_KITCHEN_TIMER, self._single_name(ErdCode.UPPER_OVEN_KITCHEN_TIMER, ~oven_config.has_lower_oven)),
GeErdSensor(self, ErdCode.UPPER_OVEN_USER_TEMP_OFFSET, self._single_name(ErdCode.UPPER_OVEN_USER_TEMP_OFFSET, ~oven_config.has_lower_oven)),
GeErdSensor(self, ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE, self._single_name(ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE, ~oven_config.has_lower_oven)),
GeErdBinarySensor(self, ErdCode.UPPER_OVEN_REMOTE_ENABLED, self._single_name(ErdCode.UPPER_OVEN_REMOTE_ENABLED, ~oven_config.has_lower_oven)),
GeOven(self, UPPER_OVEN, False, self._temperature_code(has_upper_raw_temperature))
])
if has_upper_raw_temperature:
oven_entities.append(GeErdSensor(self, ErdCode.UPPER_OVEN_RAW_TEMPERATURE, self._single_name(ErdCode.UPPER_OVEN_RAW_TEMPERATURE, ~oven_config.has_lower_oven)))
if upper_light_availability is None or upper_light_availability.is_available or upper_light is not None:
oven_entities.append(GeOvenLightLevelSelect(self, ErdCode.UPPER_OVEN_LIGHT, self._single_name(ErdCode.UPPER_OVEN_LIGHT, ~oven_config.has_lower_oven)))
if upper_warm_drawer is not None:
oven_entities.append(GeOvenWarmingStateSelect(self, ErdCode.UPPER_OVEN_WARMING_DRAWER_STATE, self._single_name(ErdCode.UPPER_OVEN_WARMING_DRAWER_STATE, ~oven_config.has_lower_oven)))
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:
cooktop_status: CooktopStatus = self.try_get_erd_value(ErdCode.COOKTOP_STATUS)
@ -110,11 +115,16 @@ class OvenApi(ApplianceApi):
return base_entities + oven_entities + cooktop_entities
def _single_name(self, erd_code: ErdCode):
return erd_code.name.replace(UPPER_OVEN+"_","").replace("_", " ").title()
def _single_name(self, erd_code: ErdCode, make_single: bool):
name = erd_code.name
if make_single:
name = name.replace(UPPER_OVEN+"_","")
return name.replace("_", " ").title()
def _camel_to_snake(self, s):
return ''.join(['_'+c.lower() if c.isupper() else c for c in s]).lstrip('_')
def _temperature_code(self, has_raw: bool):
return "RAW_TEMPERATURE" if has_raw else "DISPLAY_TEMPERATURE"
return "RAW_TEMPERATURE" if has_raw else "DISPLAY_TEMPERATURE"

View File

@ -9,4 +9,5 @@ from .hood import *
from .water_softener import *
from .water_heater import *
from .opal_ice_maker import *
from .ccm import *
from .ccm import *
from .dehumidifier import *

View File

@ -13,4 +13,5 @@ from .ge_erd_button import GeErdButton
from .ge_erd_number import GeErdNumber
from .ge_water_heater import GeAbstractWaterHeater
from .ge_erd_select import GeErdSelect
from .ge_climate import GeClimate
from .ge_climate import GeClimate
from .ge_humidifier import GeHumidifier

View File

@ -145,5 +145,9 @@ class GeErdEntity(GeEntity):
return "mdi:water"
if self.erd_code_class == ErdCodeClass.CCM_SENSOR:
return "mdi:coffee-maker"
if self.erd_code_class == ErdCodeClass.HUMIDITY:
return "mdi:water-percent"
if self.erd_code_class == ErdCodeClass.DEHUMIDIFIER_SENSOR:
return "mdi:air-humidifier"
return None

View File

@ -9,6 +9,7 @@ from homeassistant.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_HUMIDITY,
TEMP_FAHRENHEIT,
)
from gehomesdk import ErdCodeType, ErdCodeClass
@ -111,6 +112,8 @@ class GeErdSensor(GeErdEntity, SensorEntity):
return "%"
if self.device_class == DEVICE_CLASS_POWER_FACTOR:
return "%"
if self.erd_code_class == ErdCodeClass.HUMIDITY:
return "%"
if self.erd_code_class == ErdCodeClass.FLOW_RATE:
#if self._measurement_system == ErdMeasurementUnits.METRIC:
# return "lpm"
@ -135,6 +138,8 @@ class GeErdSensor(GeErdEntity, SensorEntity):
return DEVICE_CLASS_POWER
if self.erd_code_class == ErdCodeClass.ENERGY:
return DEVICE_CLASS_ENERGY
if self.erd_code_class == ErdCodeClass.HUMIDITY:
return DEVICE_CLASS_HUMIDITY
return None
@ -144,7 +149,7 @@ class GeErdSensor(GeErdEntity, SensorEntity):
if self.device_class in [DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_ENERGY]:
return SensorStateClass.MEASUREMENT
if self.erd_code_class in [ErdCodeClass.FLOW_RATE, ErdCodeClass.PERCENTAGE]:
if self.erd_code_class in [ErdCodeClass.FLOW_RATE, ErdCodeClass.PERCENTAGE, ErdCodeClass.HUMIDITY]:
return SensorStateClass.MEASUREMENT
if self.erd_code_class in [ErdCodeClass.LIQUID_VOLUME]:
return SensorStateClass.TOTAL_INCREASING

View File

@ -0,0 +1,105 @@
import abc
import logging
from typing import Coroutine, Any, Optional
from homeassistant.components.humidifier import HumidifierEntity, HumidifierDeviceClass
from homeassistant.components.humidifier.const import HumidifierEntityFeature
from gehomesdk import ErdCodeType, ErdOnOff
from ...const import DOMAIN
from ...devices import ApplianceApi
from .ge_entity import GeEntity
DEFAULT_TARGET_PRECISION = 5
_LOGGER = logging.getLogger(__name__)
class GeHumidifier(GeEntity, HumidifierEntity, metaclass=abc.ABCMeta):
"""GE Humidifier Abstract Entity """
def __init__(
self,
api: ApplianceApi,
device_class: HumidifierDeviceClass,
power_status_erd_code: ErdCodeType,
target_humidity_erd_code: ErdCodeType,
current_humidity_erd_code: ErdCodeType,
range_min: int,
range_max: int,
target_precision = DEFAULT_TARGET_PRECISION
):
super().__init__(api)
self._device_class = device_class
self._power_status_erd_code = power_status_erd_code
self._target_humidity_erd_code = target_humidity_erd_code
self._current_humidity_erd_code = current_humidity_erd_code
self._range_min = range_min
self._range_max = range_max
self._target_precision = target_precision
@property
def unique_id(self) -> str:
return f"{DOMAIN}_{self.serial_or_mac}_{self._device_class}"
@property
def name(self) -> Optional[str]:
return f"{self.serial_or_mac} {self._device_class.title()}"
@property
def target_humidity(self) -> int | None:
return int(self.appliance.get_erd_value(self._target_humidity_erd_code))
@property
def current_humidity(self) -> int | None:
return int(self.appliance.get_erd_value(self._current_humidity_erd_code))
@property
def min_humidity(self) -> int:
return self._range_min
@property
def max_humidity(self) -> int:
return self._range_max
@property
def supported_features(self) -> HumidifierEntityFeature:
return HumidifierEntityFeature(HumidifierEntityFeature.MODES)
@property
def is_on(self) -> bool:
return self.appliance.get_erd_value(self._power_status_erd_code) == ErdOnOff.ON
@property
def device_class(self):
return self._device_class
async def async_set_humidity(self, humidity: int) -> Coroutine[Any, Any, None]:
# round to target precision
target = round(humidity / self._target_precision) * self._target_precision
# if it's the same, just exit
if self.target_humidity == target:
return
_LOGGER.debug(f"Setting Target Humidity from {self.target_humidity} to {target}")
# make sure we're on
if not self.is_on:
await self.async_turn_on()
# set the target humidity
await self.appliance.async_set_erd_value(
self._target_humidity_erd_code,
target,
)
async def async_turn_on(self):
await self.appliance.async_set_erd_value(
self._power_status_erd_code, ErdOnOff.ON
)
async def async_turn_off(self):
await self.appliance.async_set_erd_value(
self._power_status_erd_code, ErdOnOff.OFF
)

View File

@ -0,0 +1,2 @@
from .dehumidifier import GeDehumidifier
from .dehumidifier_fan_speed_sensor import GeDehumidifierFanSpeedSensor

View File

@ -0,0 +1,3 @@
SMART_DRY = "Smart Dry"
DEFAULT_MIN_HUMIDITY = 35
DEFAULT_MAX_HUMIDITY = 80

View File

@ -0,0 +1,74 @@
"""GE Home Dehumidifier"""
import logging
from homeassistant.components.humidifier import HumidifierDeviceClass
from homeassistant.components.humidifier.const import HumidifierEntityFeature
from gehomesdk import ErdCode, DehumidifierTargetRange
from ...devices import ApplianceApi
from ..common import GeHumidifier
from .const import *
from .dehumidifier_fan_options import DehumidifierFanSettingOptionsConverter
_LOGGER = logging.getLogger(__name__)
class GeDehumidifier(GeHumidifier):
"""GE Dehumidifier"""
icon = "mdi:air-humidifier"
def __init__(self, api: ApplianceApi):
#try to get the range
range: DehumidifierTargetRange = api.try_get_erd_value(ErdCode.DHUM_TARGET_HUMIDITY_RANGE)
low = DEFAULT_MIN_HUMIDITY if range is None else range.min_humidity
high = DEFAULT_MAX_HUMIDITY if range is None else range.max_humidity
#try to get the fan mode and determine feature
mode = api.try_get_erd_value(ErdCode.AC_FAN_SETTING)
self._has_fan = mode is not None
self._mode_converter = DehumidifierFanSettingOptionsConverter()
#initialize the dehumidifier
super().__init__(api,
HumidifierDeviceClass.DEHUMIDIFIER,
ErdCode.AC_POWER_STATUS,
ErdCode.DHUM_TARGET_HUMIDITY,
ErdCode.DHUM_CURRENT_HUMIDITY,
low,
high
)
@property
def supported_features(self) -> HumidifierEntityFeature:
if self._has_fan:
return HumidifierEntityFeature(HumidifierEntityFeature.MODES)
else:
return HumidifierEntityFeature(0)
@property
def mode(self) -> str | None:
if not self._has_fan:
raise NotImplementedError()
return self._mode_converter.to_option_string(
self.appliance.get_erd_value(ErdCode.AC_FAN_SETTING)
)
@property
def available_modes(self) -> list[str] | None:
if not self._has_fan:
raise NotImplementedError()
return self._mode_converter.options
async def async_set_mode(self, mode: str) -> None:
if not self._has_fan:
raise NotImplementedError()
"""Change the selected mode."""
_LOGGER.debug(f"Setting mode from {self.mode} to {mode}")
new_state = self._mode_converter.from_option_string(mode)
await self.appliance.async_set_erd_value(ErdCode.AC_FAN_SETTING, new_state)

View File

@ -0,0 +1,29 @@
import logging
from typing import List, Any, Optional
from gehomesdk import ErdAcFanSetting
from ..common import OptionsConverter
from .const import SMART_DRY
_LOGGER = logging.getLogger(__name__)
class DehumidifierFanSettingOptionsConverter(OptionsConverter):
@property
def options(self) -> List[str]:
return [SMART_DRY] + [i.stringify() for i in [ErdAcFanSetting.LOW, ErdAcFanSetting.MED, ErdAcFanSetting.HIGH]]
def from_option_string(self, value: str) -> Any:
try:
if value == SMART_DRY:
return ErdAcFanSetting.DEFAULT
return ErdAcFanSetting[value.upper()]
except:
_LOGGER.warn(f"Could not set fan setting to {value.upper()}")
return ErdAcFanSetting.DEFAULT
def to_option_string(self, value: ErdAcFanSetting) -> Optional[str]:
try:
if value is not None:
return SMART_DRY if value == ErdAcFanSetting.DEFAULT else value.stringify()
except:
pass
return SMART_DRY

View File

@ -0,0 +1,40 @@
from ...devices import ApplianceApi
from ..common import GeErdSensor
from .dehumidifier_fan_options import DehumidifierFanSettingOptionsConverter
from gehomesdk import ErdCodeType, ErdCodeClass, ErdDataType, ErdAcFanSetting
class GeDehumidifierFanSpeedSensor(GeErdSensor):
def __init__(
self,
api: ApplianceApi,
erd_code: ErdCodeType,
erd_override: str = None,
icon_override: str = None,
device_class_override: str = None,
state_class_override: str = None,
uom_override: str = None,
data_type_override: ErdDataType = None
):
super().__init__(
api,
erd_code,
erd_override,
icon_override,
device_class_override,
state_class_override,
uom_override,
data_type_override
)
self._converter = DehumidifierFanSettingOptionsConverter()
@property
def native_value(self):
try:
value: ErdAcFanSetting = self.appliance.get_erd_value(self.erd_code)
return self._converter.to_option_string(value)
except KeyError:
return None

View File

@ -1,3 +1,4 @@
from .ge_oven import GeOven
from .ge_oven_light_level_select import GeOvenLightLevelSelect
from .ge_oven_warming_state_select import GeOvenWarmingStateSelect
from .const import UPPER_OVEN, LOWER_OVEN

View File

@ -0,0 +1,56 @@
import logging
from typing import List, Any, Optional
from gehomesdk import ErdCodeType, ErdOvenWarmingState
from ...devices import ApplianceApi
from ..common import GeErdSelect, OptionsConverter
_LOGGER = logging.getLogger(__name__)
class OvenWarmingStateOptionsConverter(OptionsConverter):
@property
def options(self) -> List[str]:
return [i.stringify() for i in ErdOvenWarmingState]
def from_option_string(self, value: str) -> Any:
try:
return ErdOvenWarmingState[value.upper()]
except:
_LOGGER.warn(f"Could not set Oven warming state to {value.upper()}")
return ErdOvenWarmingState.OFF
def to_option_string(self, value: ErdOvenWarmingState) -> Optional[str]:
try:
if value is not None:
return value.stringify()
except:
pass
return ErdOvenWarmingState.OFF.stringify()
class GeOvenWarmingStateSelect(GeErdSelect):
def __init__(self, api: ApplianceApi, erd_code: ErdCodeType, erd_override: str = None):
#check to see if we have a status
value: ErdOvenWarmingState = api.try_get_erd_value(erd_code)
self._has_status = value is not None and value != ErdOvenWarmingState.NOT_AVAILABLE
self._assumed_state = ErdOvenWarmingState.OFF
super().__init__(api, erd_code, OvenWarmingStateOptionsConverter(self._availability), erd_override=erd_override)
@property
def assumed_state(self) -> bool:
return not self._has_status
@property
def current_option(self):
if self.assumed_state:
return self._assumed_state
return self._converter.to_option_string(self.appliance.get_erd_value(self.erd_code))
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
_LOGGER.debug(f"Setting select from {self.current_option} to {option}")
new_state = self._converter.from_option_string(option)
await self.appliance.async_set_erd_value(self.erd_code, new_state)
self._assumed_state = new_state

View File

@ -0,0 +1,36 @@
"""GE Home Humidifier Entities"""
import logging
from typing import Callable
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers import entity_registry as er
from .entities import GeHumidifier
from .const import DOMAIN
from .devices import ApplianceApi
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable):
"""GE Home Water Heaters."""
_LOGGER.debug('Adding GE "Humidifiers"')
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
registry = er.async_get(hass)
@callback
def async_devices_discovered(apis: list[ApplianceApi]):
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
entities = [
entity
for api in apis
for entity in api.entities
if isinstance(entity, GeHumidifier)
if not registry.async_is_registered(entity.entity_id)
]
_LOGGER.debug(f'Found {len(entities):d} unregistered humidifiers')
async_add_entities(entities)
async_dispatcher_connect(hass, coordinator.signal_ready, async_devices_discovered)

View File

@ -2,8 +2,10 @@
"domain": "ge_home",
"name": "GE Home (SmartHQ)",
"config_flow": true,
"integration_type": "hub",
"iot_class": "cloud_push",
"documentation": "https://github.com/simbaja/ha_gehome",
"requirements": ["gehomesdk==0.5.10","magicattr==0.1.6","slixmpp==1.8.3"],
"requirements": ["gehomesdk==0.5.23","magicattr==0.1.6","slixmpp==1.8.3"],
"codeowners": ["@simbaja"],
"version": "0.6.7"
"version": "0.6.8"
}

View File

@ -35,7 +35,18 @@ from .const import (
)
from .devices import ApplianceApi, get_appliance_api_type
PLATFORMS = ["binary_sensor", "sensor", "switch", "water_heater", "select", "climate", "light", "button", "number"]
PLATFORMS = [
"binary_sensor",
"sensor",
"switch",
"water_heater",
"select",
"climate",
"light",
"button",
"number",
"humidifier"
]
_LOGGER = logging.getLogger(__name__)
@ -252,7 +263,7 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
_LOGGER.info("ge_home shutting down")
if self.client:
self.client.clear_event_handlers()
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."""

13
info.md
View File

@ -69,6 +69,13 @@ A/C Controls:
#### Features
{% if version_installed.split('.') | map('int') < '0.6.8'.split('.') | map('int') %}
- Added Dehumidifier (#114)
- Added oven drawer sensors
- Added oven current state sensors (#175)
- Added descriptors to manifest (#181)
{% endif %}
{% if version_installed.split('.') | map('int') < '0.6.7'.split('.') | map('int') %}
- Added OIM descaling sensor (#154)
{% endif %}
@ -116,6 +123,12 @@ A/C Controls:
#### Bugfixes
{% 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)
- Bugfix: Fixed disconnection issue (#169)
{% endif %}
{% if version_installed.split('.') | map('int') < '0.6.7'.split('.') | map('int') %}
- Bugfix: fixed issues with dishwasher (#155)
{% endif %}