mirror of https://github.com/simbaja/ha_gehome.git
Merge pull request #68 from alexanv1/coffeemaker
Updates to Coffee Maker support
This commit is contained in:
commit
54880b878b
|
@ -0,0 +1,33 @@
|
||||||
|
"""GE Home Button Entities"""
|
||||||
|
import async_timeout
|
||||||
|
import logging
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .entities import GeErdButton
|
||||||
|
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 buttons."""
|
||||||
|
|
||||||
|
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
|
# This should be a NOP, but let's be safe
|
||||||
|
with async_timeout.timeout(20):
|
||||||
|
await coordinator.initialization_future
|
||||||
|
|
||||||
|
apis = coordinator.appliance_apis.values()
|
||||||
|
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
|
||||||
|
entities = [
|
||||||
|
entity
|
||||||
|
for api in apis
|
||||||
|
for entity in api.entities
|
||||||
|
if isinstance(entity, GeErdButton)
|
||||||
|
]
|
||||||
|
_LOGGER.debug(f'Found {len(entities):d} buttons ')
|
||||||
|
async_add_entities(entities)
|
|
@ -1,5 +1,4 @@
|
||||||
"""Constants for the gehome integration."""
|
"""Constants for the gehome integration."""
|
||||||
from gehomesdk.clients.const import LOGIN_URL
|
|
||||||
|
|
||||||
DOMAIN = "ge_home"
|
DOMAIN = "ge_home"
|
||||||
|
|
||||||
|
|
|
@ -121,10 +121,10 @@ class ApplianceApi:
|
||||||
|
|
||||||
def build_entities_list(self) -> None:
|
def build_entities_list(self) -> None:
|
||||||
"""Build the entities list, adding anything new."""
|
"""Build the entities list, adding anything new."""
|
||||||
from ..entities import GeErdEntity
|
from ..entities import GeErdEntity, GeErdButton
|
||||||
entities = [
|
entities = [
|
||||||
e for e in self.get_all_entities()
|
e for e in self.get_all_entities()
|
||||||
if not isinstance(e, GeErdEntity) or e.erd_code in self.appliance.known_properties
|
if not isinstance(e, GeErdEntity) or isinstance(e, GeErdButton) or e.erd_code in self.appliance.known_properties
|
||||||
]
|
]
|
||||||
|
|
||||||
for entity in entities:
|
for entity in entities:
|
||||||
|
|
|
@ -3,16 +3,23 @@ from typing import List
|
||||||
|
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from gehomesdk import (
|
from gehomesdk import (
|
||||||
|
GeAppliance,
|
||||||
ErdCode,
|
ErdCode,
|
||||||
ErdApplianceType
|
ErdApplianceType,
|
||||||
|
ErdCcmBrewSettings
|
||||||
)
|
)
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
from .base import ApplianceApi
|
from .base import ApplianceApi
|
||||||
from ..entities import (
|
from ..entities import (
|
||||||
GeCcmPotNotPresentBinarySensor,
|
GeCcmPotNotPresentBinarySensor,
|
||||||
GeErdSensor,
|
GeErdSensor,
|
||||||
GeErdBinarySensor,
|
GeErdBinarySensor,
|
||||||
GeCcm
|
GeErdButton,
|
||||||
|
GeCcmBrewStrengthSelect,
|
||||||
|
GeCcmBrewTemperatureNumber,
|
||||||
|
GeCcmBrewCupsNumber,
|
||||||
|
GeCcmBrewSettingsButton
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -22,23 +29,38 @@ class CcmApi(ApplianceApi):
|
||||||
"""API class for Cafe Coffee Maker objects"""
|
"""API class for Cafe Coffee Maker objects"""
|
||||||
APPLIANCE_TYPE = ErdApplianceType.CAFE_COFFEE_MAKER
|
APPLIANCE_TYPE = ErdApplianceType.CAFE_COFFEE_MAKER
|
||||||
|
|
||||||
|
def __init__(self, coordinator: DataUpdateCoordinator, appliance: GeAppliance):
|
||||||
|
super().__init__(coordinator, appliance)
|
||||||
|
|
||||||
|
self._brew_strengh_entity = GeCcmBrewStrengthSelect(self)
|
||||||
|
self._brew_temperature_entity = GeCcmBrewTemperatureNumber(self)
|
||||||
|
self._brew_cups_entity = GeCcmBrewCupsNumber(self)
|
||||||
|
|
||||||
def get_all_entities(self) -> List[Entity]:
|
def get_all_entities(self) -> List[Entity]:
|
||||||
base_entities = super().get_all_entities()
|
base_entities = super().get_all_entities()
|
||||||
|
|
||||||
units = self.hass.config.units
|
|
||||||
|
|
||||||
ccm_entities = [
|
ccm_entities = [
|
||||||
GeErdBinarySensor(self, ErdCode.CCM_IS_BREWING, device_class_override="heat"),
|
GeErdBinarySensor(self, ErdCode.CCM_IS_BREWING),
|
||||||
GeErdBinarySensor(self, ErdCode.CCM_IS_DESCALING),
|
GeErdBinarySensor(self, ErdCode.CCM_IS_DESCALING),
|
||||||
GeErdSensor(self, ErdCode.CCM_BREW_STRENGTH),
|
GeCcmBrewSettingsButton(self),
|
||||||
GeErdSensor(self, ErdCode.CCM_BREW_CUPS),
|
GeErdButton(self, ErdCode.CCM_CANCEL_DESCALING),
|
||||||
GeErdSensor(self, ErdCode.CCM_BREW_TEMPERATURE),
|
GeErdButton(self, ErdCode.CCM_START_DESCALING),
|
||||||
|
GeErdButton(self, ErdCode.CCM_CANCEL_BREWING),
|
||||||
|
self._brew_strengh_entity,
|
||||||
|
self._brew_temperature_entity,
|
||||||
|
self._brew_cups_entity,
|
||||||
GeErdSensor(self, ErdCode.CCM_CURRENT_WATER_TEMPERATURE),
|
GeErdSensor(self, ErdCode.CCM_CURRENT_WATER_TEMPERATURE),
|
||||||
GeErdBinarySensor(self, ErdCode.CCM_OUT_OF_WATER, device_class_override="problem"),
|
GeErdBinarySensor(self, ErdCode.CCM_OUT_OF_WATER, device_class_override="problem"),
|
||||||
GeCcmPotNotPresentBinarySensor(self, ErdCode.CCM_POT_PRESENT, device_class_override="problem"),
|
GeCcmPotNotPresentBinarySensor(self, ErdCode.CCM_POT_PRESENT, device_class_override="problem")
|
||||||
GeCcm(self, units)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
entities = base_entities + ccm_entities
|
entities = base_entities + ccm_entities
|
||||||
return entities
|
return entities
|
||||||
|
|
||||||
|
async def start_brewing(self) -> None:
|
||||||
|
"""Aggregate brew settings and start brewing."""
|
||||||
|
|
||||||
|
new_mode = ErdCcmBrewSettings(self._brew_cups_entity.value,
|
||||||
|
self._brew_strengh_entity.brew_strength,
|
||||||
|
self._brew_temperature_entity.brew_temperature)
|
||||||
|
await self.appliance.async_set_erd_value(ErdCode.CCM_BREW_SETTINGS, new_mode)
|
|
@ -1,2 +1,5 @@
|
||||||
from .ge_ccm_pot_not_present_binary_sensor import GeCcmPotNotPresentBinarySensor
|
from .ge_ccm_pot_not_present_binary_sensor import GeCcmPotNotPresentBinarySensor
|
||||||
from .ge_ccm import GeCcm
|
from .ge_ccm_brew_strength import GeCcmBrewStrengthSelect
|
||||||
|
from .ge_ccm_brew_temperature import GeCcmBrewTemperatureNumber
|
||||||
|
from .ge_ccm_brew_cups import GeCcmBrewCupsNumber
|
||||||
|
from .ge_ccm_brew_settings import GeCcmBrewSettingsButton
|
|
@ -1,8 +0,0 @@
|
||||||
from homeassistant.components.water_heater import (
|
|
||||||
SUPPORT_OPERATION_MODE,
|
|
||||||
SUPPORT_TARGET_TEMPERATURE
|
|
||||||
)
|
|
||||||
|
|
||||||
GE_CCM = SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE
|
|
||||||
MAX_CUPS = 10
|
|
||||||
MIN_CUPS = 2
|
|
|
@ -1,150 +0,0 @@
|
||||||
"""GE Home Sensor Entities - Advantium"""
|
|
||||||
import logging
|
|
||||||
from typing import Any, Dict, List, Mapping, Optional, Set
|
|
||||||
from random import randrange
|
|
||||||
|
|
||||||
from gehomesdk import (
|
|
||||||
ErdCode,
|
|
||||||
ErdUnitType,
|
|
||||||
ErdCcmBrewSettings,
|
|
||||||
ErdCcmBrewStrength
|
|
||||||
)
|
|
||||||
|
|
||||||
from homeassistant.const import ATTR_TEMPERATURE
|
|
||||||
from homeassistant.util.unit_system import UnitSystem
|
|
||||||
|
|
||||||
from ...const import DOMAIN
|
|
||||||
from ...devices import ApplianceApi
|
|
||||||
from ..common import GeWaterHeater
|
|
||||||
from .const import *
|
|
||||||
from .ge_ccm_options import CcmBrewOptionsConverter
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class GeCcm(GeWaterHeater):
|
|
||||||
"""GE Appliance Cafe Coffee Maker"""
|
|
||||||
|
|
||||||
icon = "mdi:coffee-maker"
|
|
||||||
|
|
||||||
def __init__(self, api: ApplianceApi, units: UnitSystem):
|
|
||||||
super().__init__(api)
|
|
||||||
self._options = CcmBrewOptionsConverter(units)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def supported_features(self):
|
|
||||||
return GE_CCM
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unique_id(self) -> str:
|
|
||||||
return f"{DOMAIN}_{self.serial_number}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> Optional[str]:
|
|
||||||
return f"{self.serial_number} Coffee Maker"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def unit_type(self) -> Optional[ErdUnitType]:
|
|
||||||
try:
|
|
||||||
return self.appliance.get_erd_value(ErdCode.UNIT_TYPE)
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_temperature(self) -> Optional[int]:
|
|
||||||
return self.appliance.get_erd_value(ErdCode.CCM_CURRENT_WATER_TEMPERATURE)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_brewing(self) -> bool:
|
|
||||||
return self.appliance.get_erd_value(ErdCode.CCM_IS_BREWING)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_descaling(self) -> bool:
|
|
||||||
return self.appliance.get_erd_value(ErdCode.CCM_IS_DESCALING)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_operation(self) -> Optional[str]:
|
|
||||||
try:
|
|
||||||
settings: ErdCcmBrewSettings = self.appliance.get_erd_value(ErdCode.CCM_BREW_SETTINGS)
|
|
||||||
if self.is_descaling:
|
|
||||||
return "Descale"
|
|
||||||
if not self.is_brewing:
|
|
||||||
return "Off"
|
|
||||||
return self._options.to_option_string(settings)
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def operation_list(self) -> List[str]:
|
|
||||||
return self._options.options
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_brew_setting(self) -> ErdCcmBrewSettings:
|
|
||||||
"""Get the current brew setting."""
|
|
||||||
return self.appliance.get_erd_value(ErdCode.CCM_BREW_SETTINGS)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def target_temperature(self) -> Optional[int]:
|
|
||||||
"""Return the temperature we try to reach."""
|
|
||||||
try:
|
|
||||||
return self.appliance.get_erd_value(ErdCode.CCM_BREW_TEMPERATURE)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def min_temp(self) -> int:
|
|
||||||
"""Return the minimum temperature."""
|
|
||||||
min_temp, _, _ = self.appliance.get_erd_value(ErdCode.CCM_BREW_TEMPERATURE_RANGE)
|
|
||||||
return min_temp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_temp(self) -> int:
|
|
||||||
"""Return the maximum temperature."""
|
|
||||||
_, max_temp, _ = self.appliance.get_erd_value(ErdCode.CCM_BREW_TEMPERATURE_RANGE)
|
|
||||||
return max_temp
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extra_state_attributes(self) -> Optional[Mapping[str, Any]]:
|
|
||||||
data = {}
|
|
||||||
|
|
||||||
data["unit_type"] = self._stringify(self.unit_type)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
@property
|
|
||||||
def can_set_temperature(self) -> bool:
|
|
||||||
"""Indicates whether we can set the temperature based on the current mode"""
|
|
||||||
return not self.is_descaling
|
|
||||||
|
|
||||||
async def async_set_operation_mode(self, operation_mode: str):
|
|
||||||
"""Set the operation mode."""
|
|
||||||
|
|
||||||
#try to get the mode/setting for the selection
|
|
||||||
try:
|
|
||||||
if operation_mode not in ["Off","Descale"]:
|
|
||||||
new_mode = self._options.from_option_string(operation_mode)
|
|
||||||
new_mode = ErdCcmBrewSettings(new_mode.number_of_cups, new_mode.brew_strength, self.target_temperature)
|
|
||||||
|
|
||||||
await self.appliance.async_set_erd_value(ErdCode.CCM_BREW_SETTINGS, new_mode)
|
|
||||||
elif operation_mode == "Off":
|
|
||||||
await self.appliance.async_set_erd_value(ErdCode.CCM_CANCEL_BREWING, True)
|
|
||||||
await self.appliance.async_set_erd_value(ErdCode.CCM_CANCEL_DESCALING, True)
|
|
||||||
elif operation_mode == "Descale":
|
|
||||||
await self.appliance.async_set_erd_value(ErdCode.CCM_START_DESCALING, True)
|
|
||||||
except:
|
|
||||||
_LOGGER.error(f"Error Attempting to set mode to {operation_mode}.", exc_info=True)
|
|
||||||
|
|
||||||
async def async_set_temperature(self, **kwargs):
|
|
||||||
"""Set the brew temperature"""
|
|
||||||
|
|
||||||
target_temp = kwargs.get(ATTR_TEMPERATURE)
|
|
||||||
if target_temp is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# get the current strength/cups
|
|
||||||
strength: ErdCcmBrewStrength = self.appliance.get_erd_value(ErdCode.CCM_BREW_STRENGTH)
|
|
||||||
cups: int = self.appliance.get_erd_value(ErdCode.CCM_BREW_CUPS)
|
|
||||||
new_mode = ErdCcmBrewSettings(cups, strength, target_temp)
|
|
||||||
|
|
||||||
await self.appliance.async_set_erd_value(ErdCode.CCM_BREW_SETTINGS, new_mode)
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
from gehomesdk import ErdCode
|
||||||
|
from ...devices import ApplianceApi
|
||||||
|
from ..common import GeErdNumber
|
||||||
|
from .ge_ccm_cached_value import GeCcmCachedValue
|
||||||
|
|
||||||
|
class GeCcmBrewCupsNumber(GeErdNumber, GeCcmCachedValue):
|
||||||
|
def __init__(self, api: ApplianceApi):
|
||||||
|
GeErdNumber.__init__(self, api = api, erd_code = ErdCode.CCM_BREW_CUPS, min_value=1, max_value=10, mode="box")
|
||||||
|
GeCcmCachedValue.__init__(self)
|
||||||
|
|
||||||
|
self._set_value = None
|
||||||
|
|
||||||
|
async def async_set_value(self, value):
|
||||||
|
GeCcmCachedValue.set_value(self, value)
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return self.get_value(device_value = super().value)
|
|
@ -0,0 +1,13 @@
|
||||||
|
from gehomesdk import ErdCode
|
||||||
|
from ...devices import ApplianceApi
|
||||||
|
from ..common import GeErdButton
|
||||||
|
|
||||||
|
class GeCcmBrewSettingsButton(GeErdButton):
|
||||||
|
def __init__(self, api: ApplianceApi):
|
||||||
|
super().__init__(api, erd_code=ErdCode.CCM_BREW_SETTINGS)
|
||||||
|
|
||||||
|
async def async_press(self) -> None:
|
||||||
|
"""Handle the button press."""
|
||||||
|
|
||||||
|
# Forward the call up to the Coffee Maker device to handle
|
||||||
|
await self.api.start_brewing()
|
|
@ -0,0 +1,47 @@
|
||||||
|
import logging
|
||||||
|
from typing import List, Any, Optional
|
||||||
|
|
||||||
|
from gehomesdk import ErdCode, ErdCcmBrewStrength
|
||||||
|
from ...devices import ApplianceApi
|
||||||
|
from ..common import GeErdSelect, OptionsConverter
|
||||||
|
from .ge_ccm_cached_value import GeCcmCachedValue
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class GeCcmBrewStrengthOptionsConverter(OptionsConverter):
|
||||||
|
def __init__(self):
|
||||||
|
self._default = ErdCcmBrewStrength.MEDIUM
|
||||||
|
|
||||||
|
@property
|
||||||
|
def options(self) -> List[str]:
|
||||||
|
return [i.stringify() for i in [ErdCcmBrewStrength.LIGHT, ErdCcmBrewStrength.MEDIUM, ErdCcmBrewStrength.BOLD, ErdCcmBrewStrength.GOLD]]
|
||||||
|
|
||||||
|
def from_option_string(self, value: str) -> Any:
|
||||||
|
try:
|
||||||
|
return ErdCcmBrewStrength[value.upper()]
|
||||||
|
except:
|
||||||
|
_LOGGER.warn(f"Could not set brew strength to {value.upper()}")
|
||||||
|
return self._default
|
||||||
|
|
||||||
|
def to_option_string(self, value: ErdCcmBrewStrength) -> Optional[str]:
|
||||||
|
try:
|
||||||
|
return value.stringify()
|
||||||
|
except:
|
||||||
|
return self._default.stringify()
|
||||||
|
|
||||||
|
class GeCcmBrewStrengthSelect(GeErdSelect, GeCcmCachedValue):
|
||||||
|
def __init__(self, api: ApplianceApi):
|
||||||
|
GeErdSelect.__init__(self, api = api, erd_code = ErdCode.CCM_BREW_STRENGTH, converter = GeCcmBrewStrengthOptionsConverter())
|
||||||
|
GeCcmCachedValue.__init__(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brew_strength(self) -> ErdCcmBrewStrength:
|
||||||
|
return self._converter.from_option_string(self.current_option)
|
||||||
|
|
||||||
|
async def async_select_option(self, value):
|
||||||
|
GeCcmCachedValue.set_value(self, value)
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_option(self):
|
||||||
|
return self.get_value(device_value = super().current_option)
|
|
@ -0,0 +1,22 @@
|
||||||
|
from gehomesdk import ErdCode
|
||||||
|
from ...devices import ApplianceApi
|
||||||
|
from ..common import GeErdNumber
|
||||||
|
from .ge_ccm_cached_value import GeCcmCachedValue
|
||||||
|
|
||||||
|
class GeCcmBrewTemperatureNumber(GeErdNumber, GeCcmCachedValue):
|
||||||
|
def __init__(self, api: ApplianceApi):
|
||||||
|
min_temp, max_temp, _ = api.appliance.get_erd_value(ErdCode.CCM_BREW_TEMPERATURE_RANGE)
|
||||||
|
GeErdNumber.__init__(self, api = api, erd_code = ErdCode.CCM_BREW_TEMPERATURE, min_value=min_temp, max_value=max_temp, mode="slider")
|
||||||
|
GeCcmCachedValue.__init__(self)
|
||||||
|
|
||||||
|
async def async_set_value(self, value):
|
||||||
|
GeCcmCachedValue.set_value(self, value)
|
||||||
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
return int(self.get_value(device_value = super().value))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brew_temperature(self) -> int:
|
||||||
|
return self.value
|
|
@ -0,0 +1,20 @@
|
||||||
|
class GeCcmCachedValue():
|
||||||
|
def __init__(self):
|
||||||
|
self._set_value = None
|
||||||
|
self._last_device_value = None
|
||||||
|
|
||||||
|
def get_value(self, device_value):
|
||||||
|
|
||||||
|
# If the last device value is different from the current one, return the device value which overrides the set value
|
||||||
|
if self._last_device_value != device_value:
|
||||||
|
self._last_device_value = device_value
|
||||||
|
self._set_value = None
|
||||||
|
return device_value
|
||||||
|
|
||||||
|
if self._set_value is not None:
|
||||||
|
return self._set_value
|
||||||
|
|
||||||
|
return device_value
|
||||||
|
|
||||||
|
def set_value(self, set_value):
|
||||||
|
self._set_value = set_value
|
|
@ -1,61 +0,0 @@
|
||||||
import logging
|
|
||||||
from typing import List, Any, NamedTuple, Optional
|
|
||||||
|
|
||||||
from gehomesdk import ErdCcmBrewStrength, ErdCcmBrewSettings
|
|
||||||
from homeassistant.const import TEMP_FAHRENHEIT
|
|
||||||
from homeassistant.util.unit_system import UnitSystem
|
|
||||||
|
|
||||||
from custom_components.ge_home.entities.ccm.const import MAX_CUPS, MIN_CUPS
|
|
||||||
from ..common import OptionsConverter
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class CcmBrewOption(NamedTuple):
|
|
||||||
strength: ErdCcmBrewStrength
|
|
||||||
cups: int
|
|
||||||
|
|
||||||
def stringify(self):
|
|
||||||
return f"{self.strength.stringify()} -- {self.cups} cups"
|
|
||||||
|
|
||||||
class CcmBrewOptionsConverter(OptionsConverter):
|
|
||||||
def __init__(self, units: UnitSystem):
|
|
||||||
super().__init__()
|
|
||||||
self._units = units
|
|
||||||
self._options = self._build_options()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def options(self) -> List[str]:
|
|
||||||
options = ["Off"]
|
|
||||||
options.extend([i.stringify() for i in self._options])
|
|
||||||
options.extend(["Descale"])
|
|
||||||
|
|
||||||
return options
|
|
||||||
|
|
||||||
def from_option_string(self, value: str) -> Optional[ErdCcmBrewSettings]:
|
|
||||||
try:
|
|
||||||
if value in ["Off","Descale"]:
|
|
||||||
return None
|
|
||||||
s = value.split(" -- ")[0].upper()
|
|
||||||
c = value.split(" -- ")[1].replace(" cups","")
|
|
||||||
return ErdCcmBrewSettings(int(c), ErdCcmBrewStrength[s], 200)
|
|
||||||
except:
|
|
||||||
_LOGGER.error(f"Could not convert brew options '{value}'", exc_info=True)
|
|
||||||
|
|
||||||
#return a default if we can't interpret it
|
|
||||||
return ErdCcmBrewSettings(4, ErdCcmBrewStrength.MEDIUM, 200)
|
|
||||||
|
|
||||||
def to_option_string(self, value: ErdCcmBrewSettings) -> Optional[str]:
|
|
||||||
try:
|
|
||||||
o = CcmBrewOption(value.brew_strength, value.number_of_cups)
|
|
||||||
return o.stringify()
|
|
||||||
except:
|
|
||||||
#return a default if we can't interpret it
|
|
||||||
return CcmBrewOption(ErdCcmBrewStrength.MEDIUM, 4)
|
|
||||||
|
|
||||||
def _build_options(self) -> List[CcmBrewOption]:
|
|
||||||
options = []
|
|
||||||
for s in filter(lambda x: x != ErdCcmBrewStrength.UNKNOWN, ErdCcmBrewStrength):
|
|
||||||
for c in range(MIN_CUPS, MAX_CUPS, 2):
|
|
||||||
options.append(CcmBrewOption(s, c))
|
|
||||||
|
|
||||||
return options
|
|
|
@ -9,6 +9,8 @@ from .ge_erd_light import GeErdLight
|
||||||
from .ge_erd_timer_sensor import GeErdTimerSensor
|
from .ge_erd_timer_sensor import GeErdTimerSensor
|
||||||
from .ge_erd_property_sensor import GeErdPropertySensor
|
from .ge_erd_property_sensor import GeErdPropertySensor
|
||||||
from .ge_erd_switch import GeErdSwitch
|
from .ge_erd_switch import GeErdSwitch
|
||||||
|
from .ge_erd_button import GeErdButton
|
||||||
|
from .ge_erd_number import GeErdNumber
|
||||||
from .ge_water_heater import GeWaterHeater
|
from .ge_water_heater import GeWaterHeater
|
||||||
from .ge_erd_select import GeErdSelect
|
from .ge_erd_select import GeErdSelect
|
||||||
from .ge_climate import GeClimate
|
from .ge_climate import GeClimate
|
|
@ -0,0 +1,17 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from homeassistant.components.button import ButtonEntity
|
||||||
|
|
||||||
|
from gehomesdk import ErdCodeType
|
||||||
|
from ...devices import ApplianceApi
|
||||||
|
from .ge_erd_entity import GeErdEntity
|
||||||
|
|
||||||
|
|
||||||
|
class GeErdButton(GeErdEntity, ButtonEntity):
|
||||||
|
def __init__(self, api: ApplianceApi, erd_code: ErdCodeType, erd_override: str = None):
|
||||||
|
super().__init__(api, erd_code, erd_override=erd_override)
|
||||||
|
|
||||||
|
"""GE Entity for buttons"""
|
||||||
|
async def async_press(self) -> None:
|
||||||
|
"""Handle the button press."""
|
||||||
|
await self.appliance.async_set_erd_value(self.erd_code, True)
|
|
@ -83,7 +83,7 @@ class GeErdEntity(GeEntity):
|
||||||
try:
|
try:
|
||||||
value = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
|
value = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return ErdMeasurementUnits.Imperial
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def _get_icon(self):
|
def _get_icon(self):
|
||||||
|
@ -143,5 +143,7 @@ class GeErdEntity(GeEntity):
|
||||||
return "mdi:snowflake"
|
return "mdi:snowflake"
|
||||||
if self.erd_code_class == ErdCodeClass.WATERSOFTENER_SENSOR:
|
if self.erd_code_class == ErdCodeClass.WATERSOFTENER_SENSOR:
|
||||||
return "mdi:water"
|
return "mdi:water"
|
||||||
|
if self.erd_code_class == ErdCodeClass.CCM_SENSOR:
|
||||||
|
return "mdi:coffee-maker"
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
import logging
|
||||||
|
from typing import Optional
|
||||||
|
from gehomesdk.erd.erd_data_type import ErdDataType
|
||||||
|
from homeassistant.components.number import NumberEntity
|
||||||
|
|
||||||
|
from homeassistant.const import (
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
TEMP_FAHRENHEIT,
|
||||||
|
)
|
||||||
|
from gehomesdk import ErdCodeType, ErdCodeClass
|
||||||
|
from .ge_erd_entity import GeErdEntity
|
||||||
|
from ...devices import ApplianceApi
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class GeErdNumber(GeErdEntity, NumberEntity):
|
||||||
|
"""GE Entity for numbers"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
api: ApplianceApi,
|
||||||
|
erd_code: ErdCodeType,
|
||||||
|
erd_override: str = None,
|
||||||
|
icon_override: str = None,
|
||||||
|
device_class_override: str = None,
|
||||||
|
uom_override: str = None,
|
||||||
|
data_type_override: ErdDataType = None,
|
||||||
|
min_value: float = 1,
|
||||||
|
max_value: float = 100,
|
||||||
|
step_value: float = 1,
|
||||||
|
mode: str = "auto"
|
||||||
|
):
|
||||||
|
super().__init__(api, erd_code, erd_override, icon_override, device_class_override)
|
||||||
|
self._uom_override = uom_override
|
||||||
|
self._data_type_override = data_type_override
|
||||||
|
self._min_value = min_value
|
||||||
|
self._max_value = max_value
|
||||||
|
self._step_value = step_value
|
||||||
|
self._mode = mode
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
try:
|
||||||
|
value = self.appliance.get_erd_value(self.erd_code)
|
||||||
|
return self._convert_value_from_device(value)
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def unit_of_measurement(self) -> Optional[str]:
|
||||||
|
return self._get_uom()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _data_type(self) -> ErdDataType:
|
||||||
|
if self._data_type_override is not None:
|
||||||
|
return self._data_type_override
|
||||||
|
|
||||||
|
return self.appliance.get_erd_code_data_type(self.erd_code)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def min_value(self) -> float:
|
||||||
|
return self._convert_value_from_device(self._min_value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def max_value(self) -> float:
|
||||||
|
return self._convert_value_from_device(self._max_value)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def step(self) -> float:
|
||||||
|
return self._step_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self) -> float:
|
||||||
|
return self._mode
|
||||||
|
|
||||||
|
def _convert_value_from_device(self, value):
|
||||||
|
"""Convert to expected data type"""
|
||||||
|
|
||||||
|
if self._data_type == ErdDataType.INT:
|
||||||
|
return int(round(value))
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
def _get_uom(self):
|
||||||
|
"""Select appropriate units"""
|
||||||
|
|
||||||
|
#if we have an override, just use it
|
||||||
|
if self._uom_override:
|
||||||
|
return self._uom_override
|
||||||
|
|
||||||
|
if self.device_class == DEVICE_CLASS_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 None
|
||||||
|
|
||||||
|
def _get_device_class(self) -> Optional[str]:
|
||||||
|
if self._device_class_override:
|
||||||
|
return self._device_class_override
|
||||||
|
|
||||||
|
if self.erd_code_class in [
|
||||||
|
ErdCodeClass.RAW_TEMPERATURE,
|
||||||
|
ErdCodeClass.NON_ZERO_TEMPERATURE,
|
||||||
|
]:
|
||||||
|
return DEVICE_CLASS_TEMPERATURE
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_icon(self):
|
||||||
|
if self.erd_code_class == ErdCodeClass.DOOR:
|
||||||
|
if self.state.lower().endswith("open"):
|
||||||
|
return "mdi:door-open"
|
||||||
|
if self.state.lower().endswith("closed"):
|
||||||
|
return "mdi:door-closed"
|
||||||
|
return super()._get_icon()
|
||||||
|
|
||||||
|
async def async_set_value(self, value):
|
||||||
|
"""Sets the ERD value, assumes that the data type is correct"""
|
||||||
|
|
||||||
|
if self._data_type == ErdDataType.INT:
|
||||||
|
value = int(round(value))
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.appliance.async_set_erd_value(self.erd_code, value)
|
||||||
|
except:
|
||||||
|
_LOGGER.warning(f"Could not set {self.name} to {value}")
|
|
@ -9,11 +9,9 @@ from homeassistant.const import (
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_BATTERY,
|
DEVICE_CLASS_BATTERY,
|
||||||
DEVICE_CLASS_POWER_FACTOR,
|
DEVICE_CLASS_POWER_FACTOR,
|
||||||
DEVICE_CLASS_TIMESTAMP,
|
|
||||||
TEMP_CELSIUS,
|
|
||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
from gehomesdk import ErdCode, ErdCodeType, ErdCodeClass, ErdMeasurementUnits
|
from gehomesdk import ErdCodeType, ErdCodeClass
|
||||||
from .ge_erd_entity import GeErdEntity
|
from .ge_erd_entity import GeErdEntity
|
||||||
from ...devices import ApplianceApi
|
from ...devices import ApplianceApi
|
||||||
|
|
||||||
|
@ -45,7 +43,7 @@ class GeErdSensor(GeErdEntity, SensorEntity):
|
||||||
|
|
||||||
# if it's a numeric data type, return it directly
|
# if it's a numeric data type, return it directly
|
||||||
if self._data_type in [ErdDataType.INT, ErdDataType.FLOAT]:
|
if self._data_type in [ErdDataType.INT, ErdDataType.FLOAT]:
|
||||||
return value
|
return self._convert_numeric_value_from_device(value)
|
||||||
|
|
||||||
# otherwise, return a stringified version
|
# otherwise, return a stringified version
|
||||||
# TODO: perhaps enhance so that there's a list of variables available
|
# TODO: perhaps enhance so that there's a list of variables available
|
||||||
|
@ -80,6 +78,14 @@ class GeErdSensor(GeErdEntity, SensorEntity):
|
||||||
# return TEMP_CELSIUS
|
# return TEMP_CELSIUS
|
||||||
#return TEMP_FAHRENHEIT
|
#return TEMP_FAHRENHEIT
|
||||||
|
|
||||||
|
def _convert_numeric_value_from_device(self, value):
|
||||||
|
"""Convert to expected data type"""
|
||||||
|
|
||||||
|
if self._data_type == ErdDataType.INT:
|
||||||
|
return int(round(value))
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
def _get_uom(self):
|
def _get_uom(self):
|
||||||
"""Select appropriate units"""
|
"""Select appropriate units"""
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"name": "GE Home (SmartHQ)",
|
"name": "GE Home (SmartHQ)",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://github.com/simbaja/ha_gehome",
|
"documentation": "https://github.com/simbaja/ha_gehome",
|
||||||
"requirements": ["gehomesdk==0.4.21","magicattr==0.1.5"],
|
"requirements": ["gehomesdk==0.4.22","magicattr==0.1.5","slixmpp==1.7.1"],
|
||||||
"codeowners": ["@simbaja"],
|
"codeowners": ["@simbaja"],
|
||||||
"version": "0.6.0"
|
"version": "0.6.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
"""GE Home Number Entities"""
|
||||||
|
import async_timeout
|
||||||
|
import logging
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .entities import GeErdNumber
|
||||||
|
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 numbers."""
|
||||||
|
|
||||||
|
coordinator: GeHomeUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||||
|
|
||||||
|
# This should be a NOP, but let's be safe
|
||||||
|
with async_timeout.timeout(20):
|
||||||
|
await coordinator.initialization_future
|
||||||
|
|
||||||
|
apis = coordinator.appliance_apis.values()
|
||||||
|
_LOGGER.debug(f'Found {len(apis):d} appliance APIs')
|
||||||
|
entities = [
|
||||||
|
entity
|
||||||
|
for api in apis
|
||||||
|
for entity in api.entities
|
||||||
|
if isinstance(entity, GeErdNumber)
|
||||||
|
]
|
||||||
|
_LOGGER.debug(f'Found {len(entities):d} numbers ')
|
||||||
|
async_add_entities(entities)
|
|
@ -34,7 +34,7 @@ from .const import (
|
||||||
)
|
)
|
||||||
from .devices import ApplianceApi, get_appliance_api_type
|
from .devices import ApplianceApi, get_appliance_api_type
|
||||||
|
|
||||||
PLATFORMS = ["binary_sensor", "sensor", "switch", "water_heater", "select", "climate", "light"]
|
PLATFORMS = ["binary_sensor", "sensor", "switch", "water_heater", "select", "climate", "light", "button", "number"]
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -261,6 +261,7 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
|
||||||
api = self.appliance_apis[appliance.mac_addr]
|
api = self.appliance_apis[appliance.mac_addr]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
|
|
||||||
for entity in api.entities:
|
for entity in api.entities:
|
||||||
if entity.enabled:
|
if entity.enabled:
|
||||||
_LOGGER.debug(f"Updating {entity} ({entity.unique_id}, {entity.entity_id})")
|
_LOGGER.debug(f"Updating {entity} ({entity.unique_id}, {entity.entity_id})")
|
||||||
|
|
Loading…
Reference in New Issue