Implement fridges and ovens as water heaters

This commit is contained in:
Andrew Marks 2020-08-26 20:10:32 -04:00
parent 5f27c9dbd4
commit 283271f709
10 changed files with 573 additions and 48 deletions

View File

@ -8,11 +8,17 @@ Integration for Shark IQ Robot Vacuums
![Shark Vacuum Lovelace Card](https://raw.githubusercontent.com/ajmarks/ajmarks_ha_components/master/img/shark_vacuum_control.png) ![Shark Vacuum Lovelace Card](https://raw.githubusercontent.com/ajmarks/ajmarks_ha_components/master/img/shark_vacuum_control.png)
## `ge_kitchen` ## `ge_kitchen`
Integration for GE WiFi-enabled kitchen appliances. Right now, this is largely a proof of concept as HA doesn't yet Integration for GE WiFi-enabled kitchen appliances. So far, I've only done fridges and ovens (because that's what I
have entity types for ovens, refrigerators, etc., but I'm hoping this may provide some motivation to expand the platform have), but I hope to to dishwashers next. Because HA doesn't have Fridge or Oven platforms, both fridges and ovens are
roster. Currently, I've only really built out ovens, and that's purely as sensors, though I'll add some switches soon. primarily represented as water heater entities, which works surprisingly well. If anybody who has other GE appliances
If anybody who has other GE appliances sees this and wants to pitch in, please shoot me a message or make a PR. sees this and wants to pitch in, please shoot me a message or make a PR.
Oven sensors in action: Entities card:
![Oven Sensor Example](https://raw.githubusercontent.com/ajmarks/ajmarks_ha_components/master/img/oven_sensors.jpg) ![Entities](https://raw.githubusercontent.com/ajmarks/ajmarks_ha_components/master/img/appliance_entities.png)
Fridge Controls:
![Fridge controls](https://raw.githubusercontent.com/ajmarks/ajmarks_ha_components/master/img/fridge_controls.png)
Oven Controls:
![Fridge controls](https://raw.githubusercontent.com/ajmarks/ajmarks_ha_components/master/img/fridge_controls.png)

View File

@ -22,7 +22,7 @@ from .exceptions import AuthError, CannotConnect
from .update_coordinator import GeKitchenUpdateCoordinator from .update_coordinator import GeKitchenUpdateCoordinator
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
PLATFORMS = ["sensor", "binary_sensor", "switch"] PLATFORMS = ["binary_sensor", "sensor", "switch", "water_heater"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -11,11 +11,18 @@ from gekitchen.erd_types import *
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from .binary_sensor import GeErdBinarySensor, GeErdPropertyBinarySensor from .binary_sensor import GeErdBinarySensor
from .const import DOMAIN from .const import DOMAIN
from .entities import GeErdEntity from .entities import GeErdEntity
from .sensor import GeErdPropertySensor, GeErdSensor from .sensor import GeErdSensor
from .switch import GeErdSwitch from .switch import GeErdSwitch
from .water_heater import (
GeFreezerEntity,
GeFridgeEntity,
GeOvenHeaterEntity,
LOWER_OVEN,
UPPER_OVEN,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -125,15 +132,8 @@ class OvenApi(ApplianceApi):
oven_entities = [ oven_entities = [
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_MODE), GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_MODE),
GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_TIME_REMAINING), GeErdSensor(self, ErdCode.UPPER_OVEN_COOK_TIME_REMAINING),
GeErdSensor(self, ErdCode.UPPER_OVEN_CURRENT_STATE),
GeErdSensor(self, ErdCode.UPPER_OVEN_DELAY_TIME_REMAINING),
GeErdSensor(self, ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE),
GeErdSensor(self, ErdCode.UPPER_OVEN_ELAPSED_COOK_TIME),
GeErdSensor(self, ErdCode.UPPER_OVEN_KITCHEN_TIMER), GeErdSensor(self, ErdCode.UPPER_OVEN_KITCHEN_TIMER),
GeErdSensor(self, ErdCode.UPPER_OVEN_PROBE_DISPLAY_TEMP),
GeErdSensor(self, ErdCode.UPPER_OVEN_USER_TEMP_OFFSET), GeErdSensor(self, ErdCode.UPPER_OVEN_USER_TEMP_OFFSET),
GeErdSensor(self, ErdCode.UPPER_OVEN_RAW_TEMPERATURE),
GeErdBinarySensor(self, ErdCode.UPPER_OVEN_PROBE_PRESENT),
GeErdBinarySensor(self, ErdCode.UPPER_OVEN_REMOTE_ENABLED), GeErdBinarySensor(self, ErdCode.UPPER_OVEN_REMOTE_ENABLED),
] ]
@ -141,17 +141,13 @@ class OvenApi(ApplianceApi):
oven_entities.extend([ oven_entities.extend([
GeErdSensor(self, ErdCode.LOWER_OVEN_COOK_MODE), GeErdSensor(self, ErdCode.LOWER_OVEN_COOK_MODE),
GeErdSensor(self, ErdCode.LOWER_OVEN_COOK_TIME_REMAINING), GeErdSensor(self, ErdCode.LOWER_OVEN_COOK_TIME_REMAINING),
GeErdSensor(self, ErdCode.LOWER_OVEN_CURRENT_STATE),
GeErdSensor(self, ErdCode.LOWER_OVEN_DELAY_TIME_REMAINING),
GeErdSensor(self, ErdCode.LOWER_OVEN_DISPLAY_TEMPERATURE),
GeErdSensor(self, ErdCode.LOWER_OVEN_ELAPSED_COOK_TIME),
GeErdSensor(self, ErdCode.LOWER_OVEN_KITCHEN_TIMER),
GeErdSensor(self, ErdCode.LOWER_OVEN_PROBE_DISPLAY_TEMP),
GeErdSensor(self, ErdCode.LOWER_OVEN_USER_TEMP_OFFSET), GeErdSensor(self, ErdCode.LOWER_OVEN_USER_TEMP_OFFSET),
GeErdSensor(self, ErdCode.LOWER_OVEN_RAW_TEMPERATURE),
GeErdBinarySensor(self, ErdCode.LOWER_OVEN_PROBE_PRESENT),
GeErdBinarySensor(self, ErdCode.LOWER_OVEN_REMOTE_ENABLED), GeErdBinarySensor(self, ErdCode.LOWER_OVEN_REMOTE_ENABLED),
GeOvenHeaterEntity(self, LOWER_OVEN, True),
GeOvenHeaterEntity(self, UPPER_OVEN, True),
]) ])
else:
oven_entities.append(GeOvenHeaterEntity(self, UPPER_OVEN, False))
return base_entities + oven_entities return base_entities + oven_entities
@ -169,26 +165,9 @@ class FridgeApi(ApplianceApi):
# GeErdSensor(self, ErdCode.HOT_WATER_LOCAL_USE), # GeErdSensor(self, ErdCode.HOT_WATER_LOCAL_USE),
# GeErdSensor(self, ErdCode.HOT_WATER_SET_TEMP), # GeErdSensor(self, ErdCode.HOT_WATER_SET_TEMP),
# GeErdSensor(self, ErdCode.HOT_WATER_STATUS), # GeErdSensor(self, ErdCode.HOT_WATER_STATUS),
GeErdSensor(self, ErdCode.ICE_MAKER_BUCKET_STATUS), GeErdSwitch(self, ErdCode.SABBATH_MODE),
# GeErdSensor(self, ErdCode.ICE_MAKER_CONTROL), GeFreezerEntity(self),
# GeErdSensor(self, ErdCode.SETPOINT_LIMITS), GeFridgeEntity(self),
GeErdPropertySensor(self, ErdCode.TEMPERATURE_SETTING, "fridge"),
GeErdPropertySensor(self, ErdCode.TEMPERATURE_SETTING, "freezer"),
GeErdPropertySensor(self, ErdCode.CURRENT_TEMPERATURE, "fridge"),
GeErdPropertySensor(self, ErdCode.CURRENT_TEMPERATURE, "freezer"),
GeErdSwitch(self, ErdCode.TURBO_COOL_STATUS),
GeErdSwitch(self, ErdCode.TURBO_FREEZE_STATUS),
GeErdSensor(self, ErdCode.WATER_FILTER_STATUS),
] ]
entities = base_entities + fridge_entities entities = base_entities + fridge_entities
door_status = self.appliance.get_erd_value(ErdCode.DOOR_STATUS) # type: ErdDoorStatus
if door_status.fridge_right != ErdDoorStatus.NA:
entities.append(GeErdPropertyBinarySensor(self, ErdCode.DOOR_STATUS, "fridge_right"))
if door_status.fridge_left != ErdDoorStatus.NA:
entities.append(GeErdPropertyBinarySensor(self, ErdCode.DOOR_STATUS, "fridge_left"))
if door_status.freezer != ErdDoorStatus.NA:
entities.append(GeErdPropertyBinarySensor(self, ErdCode.DOOR_STATUS, "freezer"))
if door_status.drawer != ErdDoorStatus.NA:
entities.append(GeErdPropertyBinarySensor(self, ErdCode.DOOR_STATUS, "drawer"))
return entities return entities

View File

@ -6,7 +6,7 @@ from typing import Any, Dict, Optional, TYPE_CHECKING
from gekitchen import ErdCodeType, GeAppliance, translate_erd_code from gekitchen import ErdCodeType, GeAppliance, translate_erd_code
from gekitchen.erd_types import * from gekitchen.erd_types import *
from gekitchen.erd_constants import * from gekitchen.erd_constants import *
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -23,7 +23,6 @@ DOOR_ERD_CODES = {
ErdCode.DOOR_STATUS ErdCode.DOOR_STATUS
} }
RAW_TEMPERATURE_ERD_CODES = { RAW_TEMPERATURE_ERD_CODES = {
ErdCode.HOT_WATER_SET_TEMP,
ErdCode.LOWER_OVEN_RAW_TEMPERATURE, ErdCode.LOWER_OVEN_RAW_TEMPERATURE,
ErdCode.LOWER_OVEN_USER_TEMP_OFFSET, ErdCode.LOWER_OVEN_USER_TEMP_OFFSET,
ErdCode.UPPER_OVEN_RAW_TEMPERATURE, ErdCode.UPPER_OVEN_RAW_TEMPERATURE,
@ -32,6 +31,7 @@ RAW_TEMPERATURE_ERD_CODES = {
ErdCode.TEMPERATURE_SETTING, ErdCode.TEMPERATURE_SETTING,
} }
NONZERO_TEMPERATURE_ERD_CODES = { NONZERO_TEMPERATURE_ERD_CODES = {
ErdCode.HOT_WATER_SET_TEMP,
ErdCode.LOWER_OVEN_DISPLAY_TEMPERATURE, ErdCode.LOWER_OVEN_DISPLAY_TEMPERATURE,
ErdCode.LOWER_OVEN_PROBE_DISPLAY_TEMP, ErdCode.LOWER_OVEN_PROBE_DISPLAY_TEMP,
ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE, ErdCode.UPPER_OVEN_DISPLAY_TEMPERATURE,
@ -72,7 +72,7 @@ def boolify_erd_value(erd_code: ErdCodeType, value: Any) -> Optional[bool]:
return bool(value) return bool(value)
def stringify_erd_value(erd_code: ErdCodeType, value: Any, units: str) -> Optional[str]: def stringify_erd_value(erd_code: ErdCodeType, value: Any, units: Optional[str] = None) -> Optional[str]:
""" """
Convert an erd property value to a nice string Convert an erd property value to a nice string

View File

@ -3,7 +3,7 @@
"name": "GE Kitchen", "name": "GE Kitchen",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ge_kitchen", "documentation": "https://www.home-assistant.io/integrations/ge_kitchen",
"requirements": ["gekitchen==0.2.8"], "requirements": ["gekitchen==0.2.10"],
"dependencies": [], "dependencies": [],
"codeowners": ["@ajmarks"] "codeowners": ["@ajmarks"]
} }

540
ge_kitchen/water_heater.py Normal file
View File

@ -0,0 +1,540 @@
"""GE Kitchen Sensor Entities"""
import abc
import async_timeout
from datetime import timedelta
import logging
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, TYPE_CHECKING
from bidict import bidict
from gekitchen import (
ErdCode,
ErdDoorStatus,
ErdFilterStatus,
ErdFullNotFull,
ErdHotWaterStatus,
ErdMeasurementUnits,
ErdOnOff,
ErdOvenCookMode,
ErdPodStatus,
ErdPresent,
OVEN_COOK_MODE_MAP,
)
from gekitchen.erd_types import (
FridgeDoorStatus,
FridgeSetPointLimits,
FridgeSetPoints,
FridgeIceBucketStatus,
HotWaterStatus,
IceMakerControlStatus,
OvenCookMode,
OvenCookSetting,
)
from homeassistant.components.water_heater import (
SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE,
WaterHeaterEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant
from .entities import GeEntity, stringify_erd_value
from .const import DOMAIN
if TYPE_CHECKING:
from .appliance_api import ApplianceApi
from .update_coordinator import GeKitchenUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
ATTR_DOOR_STATUS = "door_status"
GE_FRIDGE_SUPPORT = (SUPPORT_OPERATION_MODE | SUPPORT_TARGET_TEMPERATURE)
HEATER_TYPE_FRIDGE = "fridge"
HEATER_TYPE_FREEZER = "freezer"
# Fridge/Freezer
OP_MODE_K_CUP = "K-Cup Brewing"
OP_MODE_NORMAL = "Normal"
OP_MODE_SABBATH = "Sabbath Mode"
OP_MODE_TURBO_COOL = "Turbo Cool"
OP_MODE_TURBO_FREEZE = "Turbo Freeze"
# Oven
OP_MODE_OFF = "Off"
OP_MODE_BAKE = "Bake"
OP_MODE_CONVMULTIBAKE = "Conv. Multi-Bake"
OP_MODE_CONVBAKE = "Convection Bake"
OP_MODE_CONVROAST = "Convection Roast"
OP_MODE_COOK_UNK = "Unknown"
UPPER_OVEN = "UPPER_OVEN"
LOWER_OVEN = "LOWER_OVEN"
COOK_MODE_OP_MAP = bidict({
ErdOvenCookMode.NOMODE: OP_MODE_OFF,
ErdOvenCookMode.CONVMULTIBAKE_NOOPTION: OP_MODE_CONVMULTIBAKE,
ErdOvenCookMode.CONVBAKE_NOOPTION: OP_MODE_CONVBAKE,
ErdOvenCookMode.CONVROAST_NOOPTION: OP_MODE_CONVROAST,
ErdOvenCookMode.BAKE_NOOPTION: OP_MODE_BAKE,
})
class GeAbstractFridgeEntity(GeEntity, WaterHeaterEntity, metaclass=abc.ABCMeta):
"""Mock a fridge or freezer as a water heater."""
@property
def heater_type(self) -> str:
raise NotImplementedError
@property
def turbo_erd_code(self) -> str:
raise NotImplementedError
@property
def turbo_mode(self) -> str:
raise NotImplementedError
@property
def operation_list(self) -> List[str]:
return [OP_MODE_NORMAL, OP_MODE_SABBATH, self.turbo_mode]
@property
def unique_id(self) -> str:
return f"{self.serial_number}-{self.heater_type}"
@property
def name(self) -> Optional[str]:
return f"GE {self.heater_type.title()} {self.serial_number}"
@property
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
@property
def target_temps(self) -> FridgeSetPoints:
"""Get the current temperature settings tuple."""
return self.appliance.get_erd_value(ErdCode.TEMPERATURE_SETTING)
@property
def target_temperature(self) -> int:
"""Return the temperature we try to reach."""
return getattr(self.target_temps, self.heater_type)
@property
def current_temperature(self) -> int:
"""Return the current temperature."""
current_temps = self.appliance.get_erd_value(ErdCode.CURRENT_TEMPERATURE)
return getattr(current_temps, self.heater_type)
async def async_set_temperature(self, **kwargs):
target_temp = kwargs.get(ATTR_TEMPERATURE)
if target_temp is None:
return
if not self.min_temp <= target_temp <= self.max_temp:
raise ValueError("Tried to set temperature out of device range")
if self.heater_type == HEATER_TYPE_FRIDGE:
new_temp = FridgeSetPoints(fridge=target_temp, freezer=self.target_temps.freezer)
elif self.heater_type == HEATER_TYPE_FREEZER:
new_temp = FridgeSetPoints(fridge=self.target_temps.fridge, freezer=target_temp)
else:
raise ValueError("Invalid heater_type")
await self.appliance.async_set_erd_value(ErdCode.TEMPERATURE_SETTING, new_temp)
@property
def supported_features(self):
return GE_FRIDGE_SUPPORT
@property
def setpoint_limits(self) -> FridgeSetPointLimits:
return self.appliance.get_erd_value(ErdCode.SETPOINT_LIMITS)
@property
def min_temp(self):
"""Return the minimum temperature."""
return getattr(self.setpoint_limits, f"{self.heater_type}_min")
@property
def max_temp(self):
"""Return the maximum temperature."""
return getattr(self.setpoint_limits, f"{self.heater_type}_max")
@property
def current_operation(self) -> str:
"""Get ther current operation mode."""
if self.appliance.get_erd_value(ErdCode.SABBATH_MODE):
return OP_MODE_SABBATH
if self.appliance.get_erd_value(self.turbo_erd_code):
return self.turbo_mode
return OP_MODE_NORMAL
async def async_set_sabbath_mode(self, sabbath_on: bool = True):
"""Set sabbath mode if it's changed"""
if self.appliance.get_erd_value(ErdCode.SABBATH_MODE) == sabbath_on:
return
await self.appliance.async_set_erd_value(ErdCode.SABBATH_MODE, sabbath_on)
async def async_set_operation_mode(self, operation_mode):
"""Set the operation mode."""
if operation_mode not in self.operation_list:
raise ValueError("Invalid operation mode")
if operation_mode == self.current_operation:
return
sabbath_mode = operation_mode == OP_MODE_SABBATH
await self.async_set_sabbath_mode(sabbath_mode)
if not sabbath_mode:
await self.appliance.async_set_erd_value(self.turbo_erd_code, operation_mode == self.turbo_mode)
@property
def door_status(self) -> FridgeDoorStatus:
"""Shorthand to get door status."""
return self.appliance.get_erd_value(ErdCode.DOOR_STATUS)
@property
def ice_maker_state_attrs(self) -> Dict[str, Any]:
"""Get state attributes for the ice maker, if applicable."""
data = {}
erd_val: FridgeIceBucketStatus = self.appliance.get_erd_value(ErdCode.ICE_MAKER_BUCKET_STATUS)
ice_bucket_status = getattr(erd_val, f"state_full_{self.heater_type}")
if ice_bucket_status != ErdFullNotFull.NA:
data["ice_bucket"] = ice_bucket_status.name.replace("_", " ").title()
erd_val: IceMakerControlStatus = self.appliance.get_erd_value(ErdCode.ICE_MAKER_CONTROL)
ice_control_status = getattr(erd_val, f"status_{self.heater_type}")
if ice_control_status != ErdOnOff.NA:
data["ice_maker"] = ice_control_status.name.replace("_", " ").title()
return data
@property
def door_state_attrs(self) -> Dict[str, Any]:
"""Get state attributes for the doors."""
return {}
@property
def other_state_attrs(self) -> Dict[str, Any]:
"""State attributes to be optionally overridden in subclasses."""
return {}
@property
def device_state_attributes(self) -> Dict[str, Any]:
door_attrs = self.door_state_attrs
ice_maker_attrs = self.ice_maker_state_attrs
other_attrs = self.other_state_attrs
return {**door_attrs, **ice_maker_attrs, **other_attrs}
class GeFridgeEntity(GeAbstractFridgeEntity):
heater_type = HEATER_TYPE_FRIDGE
turbo_erd_code = ErdCode.TURBO_COOL_STATUS
turbo_mode = OP_MODE_TURBO_COOL
icon = "mdi:fridge-bottom"
@property
def other_state_attrs(self) -> Dict[str, Any]:
"""Water filter state."""
filter_status: ErdFilterStatus = self.appliance.get_erd_value(ErdCode.WATER_FILTER_STATUS)
if filter_status == ErdFilterStatus.NA:
return {}
return {"water_filter_status": filter_status.name.replace("_", " ").title()}
@property
def door_state_attrs(self) -> Dict[str, Any]:
"""Get state attributes for the doors."""
data = {}
door_status = self.door_status
if not door_status:
return {}
door_right = door_status.fridge_right
door_left = door_status.fridge_left
drawer = door_status.drawer
if door_right and door_right != ErdDoorStatus.NA:
data["right_door"] = door_status.fridge_right.name.title()
if door_left and door_left != ErdDoorStatus.NA:
data["left_door"] = door_status.fridge_left.name.title()
if drawer and drawer != ErdDoorStatus.NA:
data["drawer"] = door_status.fridge_left.name.title()
if data:
all_closed = all(v == "Closed" for v in data.values())
data[ATTR_DOOR_STATUS] = "Closed" if all_closed else "Open"
return data
class GeFreezerEntity(GeAbstractFridgeEntity):
"""A freezer is basically a fridge."""
heater_type = HEATER_TYPE_FREEZER
turbo_erd_code = ErdCode.TURBO_FREEZE_STATUS
turbo_mode = OP_MODE_TURBO_FREEZE
icon = "mdi:fridge-top"
@property
def door_state_attrs(self) -> Optional[Dict[str, Any]]:
door_status = self.door_status.freezer
if door_status and door_status != ErdDoorStatus.NA:
return {ATTR_DOOR_STATUS: door_status.name.title()}
return {}
class GeFridgeWaterHeater(GeEntity, WaterHeaterEntity):
"""Entity for in-fridge water heaters"""
# These values are from FridgeHotWaterFragment.smali in the android app
min_temp = 90
max_temp = 185
@property
def hot_water_status(self) -> HotWaterStatus:
"""Access the main status value conveniently."""
return self.appliance.get_erd_value(ErdCode.HOT_WATER_STATUS)
@property
def unique_id(self) -> str:
"""Make a unique id."""
return f"{self.serial_number}-fridge-hot-water"
@property
def name(self) -> Optional[str]:
"""Name it reasonably."""
return f"GE Fridge Water Heater {self.serial_number}"
@property
def temperature_unit(self):
"""Select the appropriate temperature unit."""
measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
if measurement_system == ErdMeasurementUnits.METRIC:
return TEMP_CELSIUS
return TEMP_FAHRENHEIT
@property
def supports_k_cups(self) -> bool:
"""Return True if the device supports k-cup brewing."""
status = self.hot_water_status
return status.pod_status != ErdPodStatus.NA and status.brew_module != ErdPresent.NA
@property
def operation_list(self) -> List[str]:
"""Supported Operations List"""
ops_list = [OP_MODE_NORMAL, OP_MODE_SABBATH]
if self.supports_k_cups:
ops_list.append(OP_MODE_K_CUP)
return ops_list
async def async_set_temperature(self, **kwargs):
pass
async def async_set_operation_mode(self, operation_mode):
pass
@property
def supported_features(self):
pass
@property
def current_operation(self) -> str:
"""Get the current operation mode."""
if self.appliance.get_erd_value(ErdCode.SABBATH_MODE):
return OP_MODE_SABBATH
return OP_MODE_NORMAL
@property
def current_temperature(self) -> Optional[int]:
"""Return the current temperature."""
return self.hot_water_status.current_temp
class GeOvenHeaterEntity(GeEntity, WaterHeaterEntity):
"""Water Heater entity for ovens"""
icon = "mdi:stove"
def __init__(self, api: "ApplianceApi", oven_select: str = UPPER_OVEN, two_cavity: bool = False):
if oven_select not in (UPPER_OVEN, LOWER_OVEN):
raise ValueError(f"Invalid `oven_select` value ({oven_select})")
self._oven_select = oven_select
self._two_cavity = two_cavity
super().__init__(api)
@property
def supported_features(self):
return GE_FRIDGE_SUPPORT
@property
def unique_id(self) -> str:
return f"{self.serial_number}-{self.oven_select.lower()}"
@property
def name(self) -> Optional[str]:
if self._two_cavity:
oven_title = self.oven_select.replace("_", " ").title()
else:
oven_title = "Oven"
return f"GE {oven_title}"
@property
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
@property
def oven_select(self) -> str:
return self._oven_select
def get_erd_code(self, suffix: str) -> ErdCode:
"""Return the appropriate ERD code for this oven_select"""
return ErdCode[f"{self.oven_select}_{suffix}"]
@property
def current_temperature(self) -> Optional[int]:
current_temp = self.get_erd_value("DISPLAY_TEMPERATURE")
if current_temp:
return current_temp
return self.get_erd_value("RAW_TEMPERATURE")
@property
def current_operation(self) -> Optional[str]:
cook_setting = self.current_cook_setting
cook_mode = cook_setting.cook_mode
# TODO: simplify this lookup nonsense somehow
current_state = OVEN_COOK_MODE_MAP.inverse[cook_mode]
try:
return COOK_MODE_OP_MAP[current_state]
except KeyError:
_LOGGER.debug(f"Unable to map {current_state} to an operation mode")
return OP_MODE_COOK_UNK
@property
def operation_list(self) -> List[str]:
erd_code = self.get_erd_code("AVAILABLE_COOK_MODES")
cook_modes: Set[ErdOvenCookMode] = self.appliance.get_erd_value(erd_code)
op_modes = [o for o in (COOK_MODE_OP_MAP[c] for c in cook_modes) if o]
op_modes = [OP_MODE_OFF] + op_modes
return op_modes
@property
def current_cook_setting(self) -> OvenCookSetting:
"""Get the current cook mode."""
erd_code = self.get_erd_code("COOK_MODE")
return self.appliance.get_erd_value(erd_code)
@property
def target_temperature(self) -> Optional[int]:
"""Return the temperature we try to reach."""
cook_mode = self.current_cook_setting
if cook_mode.temperature:
return cook_mode.temperature
return None
@property
def min_temp(self) -> int:
"""Return the minimum temperature."""
min_temp, _ = self.appliance.get_erd_value(ErdCode.OVEN_MODE_MIN_MAX_TEMP)
return min_temp
@property
def max_temp(self) -> int:
"""Return the maximum temperature."""
_, max_temp = self.appliance.get_erd_value(ErdCode.OVEN_MODE_MIN_MAX_TEMP)
return max_temp
async def async_set_operation_mode(self, operation_mode: str):
"""Set the operation mode."""
erd_cook_mode = COOK_MODE_OP_MAP.inverse[operation_mode]
# Pick a temperature to set. If there's not one already set, default to
# good old 350F.
if operation_mode == OP_MODE_OFF:
target_temp = 0
elif self.target_temperature:
target_temp = self.target_temperature
elif self.temperature_unit == TEMP_FAHRENHEIT:
target_temp = 350
else:
target_temp = 177
new_cook_mode = OvenCookSetting(OVEN_COOK_MODE_MAP[erd_cook_mode], target_temp)
erd_code = self.get_erd_code("COOK_MODE")
await self.appliance.async_set_erd_value(erd_code, new_cook_mode)
async def async_set_temperature(self, **kwargs):
"""Set the cook temperature"""
target_temp = kwargs.get(ATTR_TEMPERATURE)
if target_temp is None:
return
current_op = self.current_operation
if current_op != OP_MODE_OFF:
erd_cook_mode = COOK_MODE_OP_MAP.inverse[current_op]
else:
erd_cook_mode = ErdOvenCookMode.BAKE_NOOPTION
new_cook_mode = OvenCookSetting(OVEN_COOK_MODE_MAP[erd_cook_mode], target_temp)
erd_code = self.get_erd_code("COOK_MODE")
await self.appliance.async_set_erd_value(erd_code, new_cook_mode)
def get_erd_value(self, suffix: str) -> Any:
erd_code = self.get_erd_code(suffix)
return self.appliance.get_erd_value(erd_code)
@property
def display_state(self) -> Optional[str]:
erd_code = self.get_erd_code("CURRENT_STATE")
erd_value = self.appliance.get_erd_value(erd_code)
return stringify_erd_value(erd_code, erd_value, self.temperature_unit)
@property
def device_state_attributes(self) -> Optional[Dict[str, Any]]:
probe_present = self.get_erd_value("PROBE_PRESENT")
data = {
"display_state": self.display_state,
"probe_present": probe_present,
"raw_temperature": self.get_erd_value("RAW_TEMPERATURE"),
}
if probe_present:
data["probe_temperature"] = self.get_erd_value("PROBE_DISPLAY_TEMP")
elapsed_time = self.get_erd_value("ELAPSED_COOK_TIME")
cook_time_left = self.get_erd_value("COOK_TIME_REMAINING")
kitchen_timer = self.get_erd_value("KITCHEN_TIMER")
delay_time = self.get_erd_value("DELAY_TIME_REMAINING")
if elapsed_time:
data["cook_time_elapsed"] = elapsed_time
if cook_time_left:
data["cook_time_left"] = cook_time_left
if kitchen_timer:
data["cook_time_remaining"] = kitchen_timer
if delay_time:
data["delay_time_remaining"] = delay_time
return data
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable):
"""GE Kitchen sensors."""
_LOGGER.debug('Adding GE "Water Heaters"')
coordinator: "GeKitchenUpdateCoordinator" = 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
_LOGGER.debug('Coordinator init future finished')
apis = list(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, WaterHeaterEntity)
]
_LOGGER.debug(f'Found {len(entities):d} "water heaters"')
async_add_entities(entities)

BIN
img/appliance_entities.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
img/fridge_control.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
img/oven_controls.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB