mirror of https://github.com/simbaja/ha_gehome.git
Implement fridges and ovens as water heaters
This commit is contained in:
parent
5f27c9dbd4
commit
283271f709
18
README.md
18
README.md
|
@ -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)
|
|
@ -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__)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
Before Width: | Height: | Size: 88 KiB |
Loading…
Reference in New Issue