Merge pull request #75 from simbaja/dev

Merge 0.6.0 Changes into Master
This commit is contained in:
simbaja 2022-04-30 19:54:01 -04:00 committed by GitHub
commit 64fe8e62bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 928 additions and 110 deletions

View File

@ -1,6 +1,18 @@
# GE Home Appliances (SmartHQ) Changelog
## 0.6.0
- Requires HA 2021.12.x or later
- Enabled authentication to both US and EU regions
- Changed the sensors to use native value/uom
- Changed the temperatures to always be natively fahrenheit (API appears to always use this system) (@vignatyuk)
- Initial support for Microwaves (@mbcomer, @mnestor)
- Initial support for Water Softeners (@npentell, @drjeff)
- Initial support for Opal Ice Makers (@mbcomer, @knobunc)
- Initial support for Coffee Makers (@alexanv1)
- Updated deprecated icons (@mjmeli, @schmittx)
## 0.5.0
- Initial support for oven hoods (@digitalbites)
@ -9,7 +21,7 @@
- Fixed device info when serial not present (@Xe138)
- Fixed issue with ovens when raw temperature not available (@chadohalloran)
- Fixed issue where Split A/C temperature sensors report UOM incorrectly (@RobertusIT)
- Added convertable drawer mode, proximity light, and interior lights to fridge (@grotto27, @elwing00)
- Added convertable drawer mode, proximity light, and interior lights to fridge (@groto27, @elwing00)
## 0.4.3
- Enabled support for appliances without serial numbers

View File

@ -12,12 +12,18 @@ Integration for GE WiFi-enabled appliances into Home Assistant. This integratio
- Dishwasher
- Laundry (Washer/Dryer)
- Whole Home Water Filter
- Whole Home Water Softener
- A/C (Portable, Split, Window)
- Range Hoods
- Range Hood
- Advantium
- Microwave
- Opal Ice Maker
- Coffee Maker
**Forked from Andrew Mark's [repository](https://github.com/ajmarks/ha_components).**
## Updates
Unfortunately, I'm pretty much at the end of what I can do without assistance from others with these devices that can help provide logs. I'll do what I can to make updates if there's something broken, but I am not really able to add new functionality if I can't get a little help to do so.
## Home Assistant UI Examples
Entities card:

View File

@ -1,19 +1,38 @@
"""The ge_home integration."""
import logging
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_REGION
from .const import DOMAIN
from .update_coordinator import GeHomeUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass: HomeAssistant, config: dict):
return True
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)
if config_entry.version == 1:
new = {**config_entry.data}
new[CONF_REGION] = "US"
config_entry.version = 2
hass.config_entries.async_update_entry(config_entry, data=new)
_LOGGER.info("Migration to version %s successful", config_entry.version)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up the ge_home component."""

View File

@ -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)

View File

@ -7,11 +7,17 @@ import aiohttp
import asyncio
import async_timeout
from gehomesdk import GeAuthFailedError, GeNotAuthenticatedError, GeGeneralServerError, async_get_oauth2_token
from gehomesdk import (
GeAuthFailedError,
GeNotAuthenticatedError,
GeGeneralServerError,
async_get_oauth2_token,
LOGIN_REGIONS
)
import voluptuous as vol
from homeassistant import config_entries, core
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_REGION
from .const import DOMAIN # pylint:disable=unused-import
from .exceptions import HaAuthError, HaCannotConnect, HaAlreadyConfigured
@ -19,7 +25,11 @@ from .exceptions import HaAuthError, HaCannotConnect, HaAlreadyConfigured
_LOGGER = logging.getLogger(__name__)
GEHOME_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_REGION): vol.In(LOGIN_REGIONS.keys())
}
)
async def validate_input(hass: core.HomeAssistant, data):
@ -30,7 +40,7 @@ async def validate_input(hass: core.HomeAssistant, data):
# noinspection PyBroadException
try:
with async_timeout.timeout(10):
_ = await async_get_oauth2_token(session, data[CONF_USERNAME], data[CONF_PASSWORD])
_ = await async_get_oauth2_token(session, data[CONF_USERNAME], data[CONF_PASSWORD], data[CONF_REGION])
except (asyncio.TimeoutError, aiohttp.ClientError):
raise HaCannotConnect('Connection failure')
except (GeAuthFailedError, GeNotAuthenticatedError):
@ -47,7 +57,7 @@ async def validate_input(hass: core.HomeAssistant, data):
class GeHomeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for GE Home."""
VERSION = 1
VERSION = 2
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_PUSH
async def _async_validate_input(self, user_input):

View File

@ -1,5 +1,4 @@
"""Constants for the gehome integration."""
from gehomesdk.clients.const import LOGIN_URL
DOMAIN = "ge_home"
@ -13,4 +12,4 @@ RETRY_OFFLINE_COUNT = 5
SERVICE_SET_TIMER = "set_timer"
SERVICE_CLEAR_TIMER = "clear_timer"
SERVICE_SET_INT_VALUE = "set_int_value"
SERVICE_SET_INT_VALUE = "set_int_value"

View File

@ -16,6 +16,10 @@ from .wac import WacApi
from .sac import SacApi
from .pac import PacApi
from .hood import HoodApi
from .microwave import MicrowaveApi
from .water_softener import WaterSoftenerApi
from .oim import OimApi
from .coffee_maker import CcmApi
_LOGGER = logging.getLogger(__name__)
@ -37,6 +41,8 @@ def get_appliance_api_type(appliance_type: ErdApplianceType) -> Type:
return WasherDryerApi
if appliance_type == ErdApplianceType.POE_WATER_FILTER:
return WaterFilterApi
if appliance_type == ErdApplianceType.WATER_SOFTENER:
return WaterSoftenerApi
if appliance_type == ErdApplianceType.ADVANTIUM:
return AdvantiumApi
if appliance_type == ErdApplianceType.AIR_CONDITIONER:
@ -47,6 +53,12 @@ def get_appliance_api_type(appliance_type: ErdApplianceType) -> Type:
return PacApi
if appliance_type == ErdApplianceType.HOOD:
return HoodApi
if appliance_type == ErdApplianceType.MICROWAVE:
return MicrowaveApi
if appliance_type == ErdApplianceType.OPAL_ICE_MAKER:
return OimApi
if appliance_type == ErdApplianceType.CAFE_COFFEE_MAKER:
return CcmApi
# Fallback
return ApplianceApi

View File

@ -2,7 +2,7 @@ import logging
from typing import List
from homeassistant.helpers.entity import Entity
from gehomesdk.erd import ErdCode, ErdApplianceType
from gehomesdk.erd import ErdCode, ErdApplianceType, ErdDataType
from .base import ApplianceApi
from ..entities import GeAdvantium, GeErdSensor, GeErdBinarySensor, GeErdPropertySensor, GeErdPropertyBinarySensor, UPPER_OVEN
@ -29,8 +29,8 @@ class AdvantiumApi(ApplianceApi):
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "cook_mode"),
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "termination_reason", icon_override="mdi:information-outline"),
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "preheat_status", icon_override="mdi:fire"),
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "temperature", icon_override="mdi:thermometer"),
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "power_level", icon_override="mdi:gauge"),
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "temperature", icon_override="mdi:thermometer", data_type_override=ErdDataType.INT),
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "power_level", icon_override="mdi:gauge", data_type_override=ErdDataType.INT),
GeErdPropertySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "warm_status", icon_override="mdi:radiator"),
GeErdPropertyBinarySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "door_status", device_class_override="door"),
GeErdPropertyBinarySensor(self, ErdCode.ADVANTIUM_COOK_STATUS, "sensing_active", icon_on_override="mdi:flash-auto", icon_off_override="mdi:flash-off"),

View File

@ -121,10 +121,10 @@ class ApplianceApi:
def build_entities_list(self) -> None:
"""Build the entities list, adding anything new."""
from ..entities import GeErdEntity
from ..entities import GeErdEntity, GeErdButton
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:

View File

@ -0,0 +1,66 @@
import logging
from typing import List
from homeassistant.helpers.entity import Entity
from gehomesdk import (
GeAppliance,
ErdCode,
ErdApplianceType,
ErdCcmBrewSettings
)
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .base import ApplianceApi
from ..entities import (
GeCcmPotNotPresentBinarySensor,
GeErdSensor,
GeErdBinarySensor,
GeErdButton,
GeCcmBrewStrengthSelect,
GeCcmBrewTemperatureNumber,
GeCcmBrewCupsNumber,
GeCcmBrewSettingsButton
)
_LOGGER = logging.getLogger(__name__)
class CcmApi(ApplianceApi):
"""API class for Cafe Coffee Maker objects"""
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]:
base_entities = super().get_all_entities()
ccm_entities = [
GeErdBinarySensor(self, ErdCode.CCM_IS_BREWING),
GeErdBinarySensor(self, ErdCode.CCM_IS_DESCALING),
GeCcmBrewSettingsButton(self),
GeErdButton(self, ErdCode.CCM_CANCEL_DESCALING),
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),
GeErdBinarySensor(self, ErdCode.CCM_OUT_OF_WATER, device_class_override="problem"),
GeCcmPotNotPresentBinarySensor(self, ErdCode.CCM_POT_PRESENT, device_class_override="problem")
]
entities = base_entities + ccm_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)

View File

@ -23,14 +23,14 @@ class DishwasherApi(ApplianceApi):
GeErdSensor(self, ErdCode.DISHWASHER_CYCLE_STATE, icon_override="mdi:state-machine"),
GeErdSensor(self, ErdCode.DISHWASHER_OPERATING_MODE),
GeErdSensor(self, ErdCode.DISHWASHER_PODS_REMAINING_VALUE, uom_override="pods"),
GeErdSensor(self, ErdCode.DISHWASHER_RINSE_AGENT, icon_override="mdi:sparkles"),
GeErdSensor(self, ErdCode.DISHWASHER_RINSE_AGENT, icon_override="mdi:shimmer"),
GeErdSensor(self, ErdCode.DISHWASHER_TIME_REMAINING),
GeErdBinarySensor(self, ErdCode.DISHWASHER_DOOR_STATUS),
#User Setttings
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "sound", icon_override="mdi:volume-high"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "lock_control", icon_override="mdi:lock"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "sabbath", icon_override="mdi:judaism"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "sabbath", icon_override="mdi:star-david"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "cycle_mode", icon_override="mdi:state-machine"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "presoak", icon_override="mdi:water"),
GeErdPropertySensor(self, ErdCode.DISHWASHER_USER_SETTING, "bottle_jet", icon_override="mdi:bottle-tonic-outline"),

View File

@ -14,7 +14,8 @@ from gehomesdk import (
ErdFilterStatus,
HotWaterStatus,
FridgeModelInfo,
ErdConvertableDrawerMode
ErdConvertableDrawerMode,
ErdDataType
)
from .base import ApplianceApi
@ -110,7 +111,7 @@ class FridgeApi(ApplianceApi):
GeErdSensor(self, ErdCode.HOT_WATER_SET_TEMP),
GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "status", icon_override="mdi:information-outline"),
GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "time_until_ready", icon_override="mdi:timer-outline"),
GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "current_temp", device_class_override=DEVICE_CLASS_TEMPERATURE),
GeErdPropertySensor(self, ErdCode.HOT_WATER_STATUS, "current_temp", device_class_override=DEVICE_CLASS_TEMPERATURE, data_type_override=ErdDataType.INT),
GeErdPropertyBinarySensor(self, ErdCode.HOT_WATER_STATUS, "faulted", device_class_override=DEVICE_CLASS_PROBLEM),
GeDispenser(self)
])

View File

@ -14,7 +14,7 @@ from .base import ApplianceApi
from ..entities import (
GeHoodLightLevelSelect,
GeHoodFanSpeedSelect,
GeErdSensor,
GeErdTimerSensor,
GeErdSwitch,
ErdOnOffBoolConverter
)
@ -45,7 +45,7 @@ class HoodApi(ApplianceApi):
if light_availability and light_availability.is_available:
hood_entities.append(GeHoodLightLevelSelect(self, ErdCode.HOOD_LIGHT_LEVEL))
if timer_availability == ErdOnOff.ON:
hood_entities.append(GeErdSensor(self, ErdCode.HOOD_TIMER))
hood_entities.append(GeErdTimerSensor(self, ErdCode.HOOD_TIMER))
entities = base_entities + hood_entities
return entities

View File

@ -0,0 +1,56 @@
import logging
from typing import List
from homeassistant.helpers.entity import Entity
from gehomesdk import (
ErdCode,
ErdApplianceType,
ErdHoodFanSpeedAvailability,
ErdHoodLightLevelAvailability,
ErdOnOff
)
from .base import ApplianceApi
from ..entities import (
GeHoodLightLevelSelect,
GeHoodFanSpeedSelect,
GeErdPropertySensor,
GeErdPropertyBinarySensor,
GeErdBinarySensor,
GeErdTimerSensor
)
_LOGGER = logging.getLogger(__name__)
class MicrowaveApi(ApplianceApi):
"""API class for Microwave objects"""
APPLIANCE_TYPE = ErdApplianceType.MICROWAVE
def get_all_entities(self) -> List[Entity]:
base_entities = super().get_all_entities()
#get the availabilities
fan_availability: ErdHoodFanSpeedAvailability = self.try_get_erd_value(ErdCode.HOOD_FAN_SPEED_AVAILABILITY)
light_availability: ErdHoodLightLevelAvailability = self.try_get_erd_value(ErdCode.HOOD_LIGHT_LEVEL_AVAILABILITY)
mwave_entities = [
GeErdBinarySensor(self, ErdCode.MICROWAVE_REMOTE_ENABLE),
GeErdPropertySensor(self, ErdCode.MICROWAVE_STATE, "status"),
GeErdPropertyBinarySensor(self, ErdCode.MICROWAVE_STATE, "door_status", device_class_override="door"),
GeErdPropertySensor(self, ErdCode.MICROWAVE_STATE, "cook_mode", icon_override="mdi:food-turkey"),
GeErdPropertySensor(self, ErdCode.MICROWAVE_STATE, "power_level", icon_override="mdi:gauge"),
GeErdPropertySensor(self, ErdCode.MICROWAVE_STATE, "temperature", icon_override="mdi:thermometer"),
GeErdTimerSensor(self, ErdCode.MICROWAVE_COOK_TIMER),
GeErdTimerSensor(self, ErdCode.MICROWAVE_KITCHEN_TIMER)
]
if fan_availability and fan_availability.is_available:
mwave_entities.append(GeHoodFanSpeedSelect(self, ErdCode.HOOD_FAN_SPEED))
#for now, represent as a select
if light_availability and light_availability.is_available:
mwave_entities.append(GeHoodLightLevelSelect(self, ErdCode.HOOD_LIGHT_LEVEL))
entities = base_entities + mwave_entities
return entities

View File

@ -0,0 +1,40 @@
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 (
OimLightLevelOptionsConverter,
GeErdSensor,
GeErdBinarySensor,
GeErdSelect,
GeErdSwitch,
ErdOnOffBoolConverter
)
_LOGGER = logging.getLogger(__name__)
class OimApi(ApplianceApi):
"""API class for Oven Hood objects"""
APPLIANCE_TYPE = ErdApplianceType.HOOD
def get_all_entities(self) -> List[Entity]:
base_entities = super().get_all_entities()
oim_entities = [
GeErdSensor(self, ErdCode.OIM_STATUS),
GeErdBinarySensor(self, ErdCode.OIM_FILTER_STATUS, device_class_override="problem"),
GeErdSelect(self, ErdCode.OIM_LIGHT_LEVEL, OimLightLevelOptionsConverter()),
GeErdSwitch(self, ErdCode.OIM_POWER, bool_converter=ErdOnOffBoolConverter(), icon_on_override="mdi:power-on", icon_off_override="mdi:power-off"),
]
entities = base_entities + oim_entities
return entities

View File

@ -1,5 +1,6 @@
import logging
from typing import List
from gehomesdk.erd.erd_data_type import ErdDataType
from homeassistant.const import DEVICE_CLASS_POWER_FACTOR
from homeassistant.helpers.entity import Entity
@ -8,7 +9,8 @@ from gehomesdk import (
ErdApplianceType,
OvenConfiguration,
ErdCooktopConfig,
CooktopStatus
CooktopStatus,
ErdOvenLightLevelAvailability
)
from .base import ApplianceApi
@ -19,6 +21,7 @@ from ..entities import (
GeErdPropertySensor,
GeErdPropertyBinarySensor,
GeOven,
GeOvenLightLevelSelect,
UPPER_OVEN,
LOWER_OVEN
)
@ -40,6 +43,9 @@ class OvenApi(ApplianceApi):
has_upper_raw_temperature = self.has_erd_code(ErdCode.UPPER_OVEN_RAW_TEMPERATURE)
has_lower_raw_temperature = self.has_erd_code(ErdCode.LOWER_OVEN_RAW_TEMPERATURE)
upper_light_availability: ErdOvenLightLevelAvailability = self.try_get_erd_value(ErdCode.UPPER_OVEN_LIGHT_AVAILABILITY)
lower_light_availability: ErdOvenLightLevelAvailability = self.try_get_erd_value(ErdCode.LOWER_OVEN_LIGHT_AVAILABILITY)
_LOGGER.debug(f"Oven Config: {oven_config}")
_LOGGER.debug(f"Cooktop Config: {cooktop_config}")
oven_entities = []
@ -68,6 +74,8 @@ class OvenApi(ApplianceApi):
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:
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)),
@ -80,6 +88,8 @@ class OvenApi(ApplianceApi):
])
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:
oven_entities.append(GeOvenLightLevelSelect(self, ErdCode.UPPER_OVEN_LIGHT, self._single_name(ErdCode.UPPER_OVEN_LIGHT)))
if cooktop_config == ErdCooktopConfig.PRESENT:
@ -92,7 +102,7 @@ class OvenApi(ApplianceApi):
cooktop_entities.append(GeErdPropertyBinarySensor(self, ErdCode.COOKTOP_STATUS, prop+".on"))
cooktop_entities.append(GeErdPropertyBinarySensor(self, ErdCode.COOKTOP_STATUS, prop+".synchronized"))
if not v.on_off_only:
cooktop_entities.append(GeErdPropertySensor(self, ErdCode.COOKTOP_STATUS, prop+".power_pct", icon_override="mdi:fire", device_class_override=DEVICE_CLASS_POWER_FACTOR))
cooktop_entities.append(GeErdPropertySensor(self, ErdCode.COOKTOP_STATUS, prop+".power_pct", icon_override="mdi:fire", device_class_override=DEVICE_CLASS_POWER_FACTOR, data_type_override=ErdDataType.INT))
return base_entities + oven_entities + cooktop_entities

View File

@ -5,7 +5,7 @@ from homeassistant.helpers.entity import Entity
from gehomesdk.erd import ErdCode, ErdApplianceType
from .base import ApplianceApi
from ..entities import GeSacClimate, GeSacTemperatureSensor, GeErdSensor, GeErdSwitch, ErdOnOffBoolConverter
from ..entities import GeSacClimate, GeErdSensor, GeErdSwitch, ErdOnOffBoolConverter
_LOGGER = logging.getLogger(__name__)
@ -19,8 +19,8 @@ class SacApi(ApplianceApi):
sac_entities = [
GeSacClimate(self),
GeSacTemperatureSensor(self, ErdCode.AC_TARGET_TEMPERATURE),
GeSacTemperatureSensor(self, ErdCode.AC_AMBIENT_TEMPERATURE),
GeErdSensor(self, ErdCode.AC_TARGET_TEMPERATURE),
GeErdSensor(self, ErdCode.AC_AMBIENT_TEMPERATURE),
GeErdSensor(self, ErdCode.AC_FAN_SETTING, icon_override="mdi:fan"),
GeErdSensor(self, ErdCode.AC_OPERATION_MODE),
GeErdSwitch(self, ErdCode.AC_POWER_STATUS, bool_converter=ErdOnOffBoolConverter(), icon_on_override="mdi:power-on", icon_off_override="mdi:power-off"),

View File

@ -7,6 +7,7 @@ from gehomesdk import ErdCode, ErdApplianceType
from .base import ApplianceApi
from ..entities import (
GeErdSensor,
GeErdPropertySensor,
GeErdBinarySensor,
GeErdFilterPositionSelect,
)
@ -28,7 +29,7 @@ class WaterFilterApi(ApplianceApi):
GeErdFilterPositionSelect(self, ErdCode.WH_FILTER_POSITION),
GeErdBinarySensor(self, ErdCode.WH_FILTER_MANUAL_MODE, icon_on_override="mdi:human", icon_off_override="mdi:robot"),
GeErdBinarySensor(self, ErdCode.WH_FILTER_LEAK_VALIDITY, device_class_override="moisture"),
GeErdSensor(self, ErdCode.WH_FILTER_FLOW_RATE),
GeErdPropertySensor(self, ErdCode.WH_FILTER_FLOW_RATE, "flow_rate"),
GeErdSensor(self, ErdCode.WH_FILTER_DAY_USAGE),
GeErdSensor(self, ErdCode.WH_FILTER_LIFE_REMAINING),
GeErdBinarySensor(self, ErdCode.WH_FILTER_FLOW_ALERT, device_class_override="moisture"),

View File

@ -0,0 +1,38 @@
import logging
from typing import List
from homeassistant.helpers.entity import Entity
from gehomesdk import ErdCode, ErdApplianceType
from .base import ApplianceApi
from ..entities import (
GeErdSensor,
GeErdPropertySensor,
GeErdBinarySensor,
GeErdShutoffPositionSelect,
)
_LOGGER = logging.getLogger(__name__)
class WaterSoftenerApi(ApplianceApi):
"""API class for water softener objects"""
APPLIANCE_TYPE = ErdApplianceType.WATER_SOFTENER
def get_all_entities(self) -> List[Entity]:
base_entities = super().get_all_entities()
ws_entities = [
GeErdBinarySensor(self, ErdCode.WH_FILTER_MANUAL_MODE, icon_on_override="mdi:human", icon_off_override="mdi:robot"),
GeErdPropertySensor(self, ErdCode.WH_FILTER_FLOW_RATE, "flow_rate"),
GeErdBinarySensor(self, ErdCode.WH_FILTER_FLOW_ALERT, device_class_override="moisture"),
GeErdSensor(self, ErdCode.WH_FILTER_DAY_USAGE),
GeErdSensor(self, ErdCode.WH_SOFTENER_ERROR_CODE, icon_override="mdi:alert-circle"),
GeErdBinarySensor(self, ErdCode.WH_SOFTENER_LOW_SALT, icon_on_override="mdi:alert", icon_off_override="mdi:grain"),
GeErdSensor(self, ErdCode.WH_SOFTENER_SHUTOFF_VALVE_STATE, icon_override="mdi:state-machine"),
GeErdSensor(self, ErdCode.WH_SOFTENER_SALT_LIFE_REMAINING, icon_override="mdi:calendar-clock"),
GeErdShutoffPositionSelect(self, ErdCode.WH_SOFTENER_SHUTOFF_VALVE_CONTROL),
]
entities = base_entities + ws_entities
return entities

View File

@ -5,4 +5,7 @@ from .oven import *
from .water_filter import *
from .advantium import *
from .ac import *
from .hood import *
from .hood import *
from .water_softener import *
from .opal_ice_maker import *
from .ccm import *

View File

@ -1,4 +1,3 @@
from .ge_wac_climate import GeWacClimate
from .ge_sac_climate import GeSacClimate
from .ge_pac_climate import GePacClimate
from .ge_sac_temperature_sensor import GeSacTemperatureSensor
from .ge_pac_climate import GePacClimate

View File

@ -66,11 +66,6 @@ class GePacClimate(GeClimate):
#construct the converter based on the available modes
self._hvac_mode_converter = PacHvacModeOptionsConverter(self._modes)
@property
def temperature_unit(self):
#SAC appears to be hard coded to use Fahrenheit internally, no matter what the display shows
return TEMP_FAHRENHEIT
@property
def min_temp(self) -> float:
temp = 64

View File

@ -69,12 +69,6 @@ class GeSacClimate(GeClimate):
#construct the converter based on the available modes
self._hvac_mode_converter = SacHvacModeOptionsConverter(self._modes)
@property
def temperature_unit(self):
#SAC appears to be hard coded to use Fahrenheit internally, no matter what the display shows
return TEMP_FAHRENHEIT
@property
def min_temp(self) -> float:
temp = 60

View File

@ -1,15 +0,0 @@
import logging
from typing import Any, List, Optional
from homeassistant.const import (
TEMP_FAHRENHEIT
)
from ..common import GeErdSensor
class GeSacTemperatureSensor(GeErdSensor):
"""Class for Split A/C temperature sensors"""
@property
def _temp_units(self) -> Optional[str]:
#SAC appears to be hard coded to use Fahrenheit internally, no matter what the display shows
return TEMP_FAHRENHEIT

View File

@ -0,0 +1,5 @@
from .ge_ccm_pot_not_present_binary_sensor import GeCcmPotNotPresentBinarySensor
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

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,8 @@
from ..common import GeErdBinarySensor
class GeCcmPotNotPresentBinarySensor(GeErdBinarySensor):
@property
def is_on(self) -> bool:
"""Return True if entity is not pot present."""
return not self._boolify(self.appliance.get_erd_value(self.erd_code))

View File

@ -9,6 +9,8 @@ from .ge_erd_light import GeErdLight
from .ge_erd_timer_sensor import GeErdTimerSensor
from .ge_erd_property_sensor import GeErdPropertySensor
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_erd_select import GeErdSelect
from .ge_climate import GeClimate

View File

@ -82,10 +82,12 @@ class GeClimate(GeEntity, ClimateEntity):
@property
def temperature_unit(self):
measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
if measurement_system == ErdMeasurementUnits.METRIC:
return TEMP_CELSIUS
#appears to always be Fahrenheit internally, hardcode this
return TEMP_FAHRENHEIT
#measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
#if measurement_system == ErdMeasurementUnits.METRIC:
# return TEMP_CELSIUS
#return TEMP_FAHRENHEIT
@property
def supported_features(self):

View File

@ -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)

View File

@ -83,7 +83,7 @@ class GeErdEntity(GeEntity):
try:
value = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
except KeyError:
return None
return ErdMeasurementUnits.Imperial
return value
def _get_icon(self):
@ -104,7 +104,7 @@ class GeErdEntity(GeEntity):
if self.erd_code_class == ErdCodeClass.LOCK_CONTROL:
return "mdi:lock-outline"
if self.erd_code_class == ErdCodeClass.SABBATH_CONTROL:
return "mdi:judaism"
return "mdi:star-david"
if self.erd_code_class == ErdCodeClass.COOLING_CONTROL:
return "mdi:snowflake"
if self.erd_code_class == ErdCodeClass.OVEN_SENSOR:
@ -138,6 +138,12 @@ class GeErdEntity(GeEntity):
if self.erd_code_class == ErdCodeClass.FAN:
return "mdi:fan"
if self.erd_code_class == ErdCodeClass.LIGHT:
return "mdi:lightbulb"
return "mdi:lightbulb"
if self.erd_code_class == ErdCodeClass.OIM_SENSOR:
return "mdi:snowflake"
if self.erd_code_class == ErdCodeClass.WATERSOFTENER_SENSOR:
return "mdi:water"
if self.erd_code_class == ErdCodeClass.CCM_SENSOR:
return "mdi:coffee-maker"
return None

View File

@ -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}")

View File

@ -1,7 +1,7 @@
from typing import Optional
import magicattr
from gehomesdk import ErdCode, ErdCodeType, ErdMeasurementUnits
from gehomesdk import ErdCode, ErdCodeType, ErdMeasurementUnits, ErdDataType
from ...devices import ApplianceApi
from .ge_erd_sensor import GeErdSensor
@ -11,13 +11,14 @@ class GeErdPropertySensor(GeErdSensor):
def __init__(
self, api: ApplianceApi, erd_code: ErdCodeType, erd_property: str,
erd_override: str = None, icon_override: str = None, device_class_override: str = None,
state_class_override: str = None, uom_override: str = None
state_class_override: str = None, uom_override: str = None, data_type_override: ErdDataType = None
):
super().__init__(
api, erd_code, erd_override=erd_override,
icon_override=icon_override, device_class_override=device_class_override,
state_class_override=state_class_override,
uom_override=uom_override
uom_override=uom_override,
data_type_override=data_type_override
)
self.erd_property = erd_property
self._erd_property_cleansed = erd_property.replace(".","_").replace("[","_").replace("]","_")
@ -33,9 +34,17 @@ class GeErdPropertySensor(GeErdSensor):
return f"{base_string} {property_name}"
@property
def state(self) -> Optional[str]:
def native_value(self):
try:
value = magicattr.get(self.appliance.get_erd_value(self.erd_code), self.erd_property)
# if it's a numeric data type, return it directly
if self._data_type in [ErdDataType.INT, ErdDataType.FLOAT]:
return value
# otherwise, return a stringified version
# TODO: perhaps enhance so that there's a list of variables available
# for the stringify function to consume...
return self._stringify(value, temp_units=self._temp_units)
except KeyError:
return None
return self._stringify(value, temp_units=self._temp_units)
return None

View File

@ -1,6 +1,7 @@
import logging
from typing import Optional
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from gehomesdk.erd.erd_data_type import ErdDataType
from homeassistant.components.sensor import SensorEntity, SensorStateClass
from homeassistant.const import (
DEVICE_CLASS_ENERGY,
@ -8,29 +9,15 @@ from homeassistant.const import (
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_POWER_FACTOR,
DEVICE_CLASS_TIMESTAMP,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
#from homeassistant.components.sensor import (
# STATE_CLASS_MEASUREMENT,
# STATE_CLASS_TOTAL_INCREASING
#)
# For now, let's not force the newer version, we'll use the same constants
# but it'll be optional.
# TODO: Force the usage of new HA
STATE_CLASS_MEASUREMENT = "measurement"
STATE_CLASS_TOTAL_INCREASING = 'total_increasing'
from homeassistant.helpers.entity import Entity
from gehomesdk import ErdCode, ErdCodeType, ErdCodeClass, ErdMeasurementUnits
from gehomesdk import ErdCodeType, ErdCodeClass
from .ge_erd_entity import GeErdEntity
from ...devices import ApplianceApi
_LOGGER = logging.getLogger(__name__)
class GeErdSensor(GeErdEntity, Entity):
class GeErdSensor(GeErdEntity, SensorEntity):
"""GE Entity for sensors"""
def __init__(
@ -42,34 +29,62 @@ class GeErdSensor(GeErdEntity, Entity):
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)
self._uom_override = uom_override
self._state_class_override = state_class_override
self._data_type_override = data_type_override
@property
def state(self) -> Optional[str]:
def native_value(self):
try:
value = self.appliance.get_erd_value(self.erd_code)
# if it's a numeric data type, return it directly
if self._data_type in [ErdDataType.INT, ErdDataType.FLOAT]:
return self._convert_numeric_value_from_device(value)
# otherwise, return a stringified version
# TODO: perhaps enhance so that there's a list of variables available
# for the stringify function to consume...
return self._stringify(value, temp_units=self._temp_units)
except KeyError:
return None
# TODO: perhaps enhance so that there's a list of variables available
# for the stringify function to consume...
return self._stringify(value, temp_units=self._temp_units)
@property
def unit_of_measurement(self) -> Optional[str]:
def native_unit_of_measurement(self) -> Optional[str]:
return self._get_uom()
@property
def state_class(self) -> Optional[str]:
return self._get_state_class()
@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 _temp_units(self) -> Optional[str]:
if self._measurement_system == ErdMeasurementUnits.METRIC:
return TEMP_CELSIUS
return TEMP_FAHRENHEIT
#based on testing, all API values are in Fahrenheit, so we'll redefine
#this property to be the configured temperature unit and set the native
#unit differently
return self.api.hass.config.units.temperature_unit
#if self._measurement_system == ErdMeasurementUnits.METRIC:
# return TEMP_CELSIUS
#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):
"""Select appropriate units"""
@ -83,7 +98,10 @@ class GeErdSensor(GeErdEntity, Entity):
in [ErdCodeClass.RAW_TEMPERATURE, ErdCodeClass.NON_ZERO_TEMPERATURE]
or self.device_class == DEVICE_CLASS_TEMPERATURE
):
return self._temp_units
#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
if (
self.erd_code_class == ErdCodeClass.BATTERY
or self.device_class == DEVICE_CLASS_BATTERY
@ -94,12 +112,12 @@ class GeErdSensor(GeErdEntity, Entity):
if self.device_class == DEVICE_CLASS_POWER_FACTOR:
return "%"
if self.erd_code_class == ErdCodeClass.FLOW_RATE:
if self._measurement_system == ErdMeasurementUnits.METRIC:
return "lpm"
#if self._measurement_system == ErdMeasurementUnits.METRIC:
# return "lpm"
return "gpm"
if self.erd_code_class == ErdCodeClass.LIQUID_VOLUME:
if self._measurement_system == ErdMeasurementUnits.METRIC:
return "l"
#if self._measurement_system == ErdMeasurementUnits.METRIC:
# return "l"
return "g"
return None
@ -125,11 +143,11 @@ class GeErdSensor(GeErdEntity, Entity):
return self._state_class_override
if self.device_class in [DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_ENERGY]:
return STATE_CLASS_MEASUREMENT
return SensorStateClass.MEASUREMENT
if self.erd_code_class in [ErdCodeClass.FLOW_RATE, ErdCodeClass.PERCENTAGE]:
return STATE_CLASS_MEASUREMENT
return SensorStateClass.MEASUREMENT
if self.erd_code_class in [ErdCodeClass.LIQUID_VOLUME]:
return STATE_CLASS_TOTAL_INCREASING
return SensorStateClass.TOTAL_INCREASING
return None

View File

@ -34,9 +34,10 @@ class GeWaterHeater(GeEntity, WaterHeaterEntity, metaclass=abc.ABCMeta):
@property
def temperature_unit(self):
measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
if measurement_system == ErdMeasurementUnits.METRIC:
return TEMP_CELSIUS
#It appears that the GE API is alwasy Fehrenheit
#measurement_system = self.appliance.get_erd_value(ErdCode.TEMPERATURE_UNIT)
#if measurement_system == ErdMeasurementUnits.METRIC:
# return TEMP_CELSIUS
return TEMP_FAHRENHEIT
@property

View File

@ -0,0 +1 @@
from .oim_light_level_options import OimLightLevelOptionsConverter

View File

@ -0,0 +1,26 @@
import logging
from typing import List, Any, Optional
from gehomesdk import ErdCodeType, ErdOimLightLevel, ErdCode
from ...devices import ApplianceApi
from ..common import GeErdSelect, OptionsConverter
_LOGGER = logging.getLogger(__name__)
class OimLightLevelOptionsConverter(OptionsConverter):
@property
def options(self) -> List[str]:
return [i.stringify() for i in ErdOimLightLevel]
def from_option_string(self, value: str) -> Any:
try:
return ErdOimLightLevel[value.upper()]
except:
_LOGGER.warn(f"Could not set hood light level to {value.upper()}")
return ErdOimLightLevel.OFF
def to_option_string(self, value: ErdOimLightLevel) -> Optional[str]:
try:
if value is not None:
return value.stringify()
except:
pass
return ErdOimLightLevel.OFF.stringify()

View File

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

View File

@ -0,0 +1,66 @@
import logging
from typing import List, Any, Optional
from gehomesdk import ErdCodeType, ErdOvenLightLevelAvailability, ErdOvenLightLevel, ErdCode
from ...devices import ApplianceApi
from ..common import GeErdSelect, OptionsConverter
_LOGGER = logging.getLogger(__name__)
class OvenLightLevelOptionsConverter(OptionsConverter):
def __init__(self, availability: ErdOvenLightLevelAvailability):
super().__init__()
self.availability = availability
self.excluded_levels = [ErdOvenLightLevel.NOT_AVAILABLE]
if not availability or not availability.dim_available:
self.excluded_levels.append(ErdOvenLightLevel.DIM)
@property
def options(self) -> List[str]:
return [i.stringify() for i in ErdOvenLightLevel if i not in self.excluded_levels]
def from_option_string(self, value: str) -> Any:
try:
return ErdOvenLightLevel[value.upper()]
except:
_LOGGER.warn(f"Could not set Oven light level to {value.upper()}")
return ErdOvenLightLevel.OFF
def to_option_string(self, value: ErdOvenLightLevel) -> Optional[str]:
try:
if value is not None:
return value.stringify()
except:
pass
return ErdOvenLightLevel.OFF.stringify()
class GeOvenLightLevelSelect(GeErdSelect):
def __init__(self, api: ApplianceApi, erd_code: ErdCodeType, erd_override: str = None):
self._availability: ErdOvenLightLevelAvailability = api.try_get_erd_value(ErdCode.LOWER_OVEN_LIGHT_AVAILABILITY)
#check to see if we have a status
value: ErdOvenLightLevel = api.try_get_erd_value(erd_code)
self._has_status = value is not None and value != ErdOvenLightLevel.NOT_AVAILABLE
self._assumed_state = ErdOvenLightLevel.OFF
super().__init__(api, erd_code, OvenLightLevelOptionsConverter(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 @@
from .shutoff_position import GeErdShutoffPositionSelect

View File

@ -0,0 +1,65 @@
import logging
from typing import List, Any, Optional
from gehomesdk import ErdCodeType, ErdWaterSoftenerShutoffValveState, ErdCode
from ...devices import ApplianceApi
from ..common import GeErdSelect, OptionsConverter
_LOGGER = logging.getLogger(__name__)
class FilterPositionOptionsConverter(OptionsConverter):
@property
def options(self) -> List[str]:
return [i.name.title()
for i in ErdWaterSoftenerShutoffValveState
if i not in [ErdWaterSoftenerShutoffValveState.UNKNOWN, ErdWaterSoftenerShutoffValveState.TRANSITION]]
def from_option_string(self, value: str) -> Any:
try:
return ErdWaterSoftenerShutoffValveState[value.upper()]
except:
_LOGGER.warn(f"Could not set filter position to {value.upper()}")
return ErdWaterSoftenerShutoffValveState.UNKNOWN
def to_option_string(self, value: Any) -> Optional[str]:
try:
if value is not None:
return value.name.title()
except:
pass
return ErdWaterSoftenerShutoffValveState.UNKNOWN.name.title()
class GeErdShutoffPositionSelect(GeErdSelect):
def __init__(self, api: ApplianceApi, erd_code: ErdCodeType):
super().__init__(api, erd_code, FilterPositionOptionsConverter(), icon_override="mdi:valve")
@property
def current_option(self):
"""Return the current selected option"""
#if we're transitioning or don't know what the mode is, don't allow changes
mode: ErdWaterSoftenerShutoffValveState = self.appliance.get_erd_value(ErdCode.WH_SOFTENER_SHUTOFF_VALVE_STATE)
if mode in [ErdWaterSoftenerShutoffValveState.TRANSITION, ErdWaterSoftenerShutoffValveState.UNKNOWN]:
return mode.name.title()
return self._converter.to_option_string(self.appliance.get_erd_value(self.erd_code))
@property
def options(self) -> List[str]:
"""Return a list of options"""
#if we're transitioning or don't know what the mode is, don't allow changes
mode: ErdWaterSoftenerShutoffValveState = self.appliance.get_erd_value(ErdCode.WH_SOFTENER_SHUTOFF_VALVE_STATE)
if mode in [ErdWaterSoftenerShutoffValveState.TRANSITION, ErdWaterSoftenerShutoffValveState.UNKNOWN]:
return mode.name.title()
return self._converter.options
async def async_select_option(self, option: str) -> None:
value = self._converter.from_option_string(option)
if value in [ErdWaterSoftenerShutoffValveState.UNKNOWN, ErdWaterSoftenerShutoffValveState.TRANSITION]:
_LOGGER.debug("Cannot set position to transition/unknown")
return
if self.appliance.get_erd_value(ErdCode.WH_SOFTENER_SHUTOFF_VALVE_STATE) == ErdWaterSoftenerShutoffValveState.TRANSITION:
_LOGGER.debug("Cannot set position if in transition")
return
return await super().async_select_option(option)

View File

@ -3,7 +3,7 @@
"name": "GE Home (SmartHQ)",
"config_flow": true,
"documentation": "https://github.com/simbaja/ha_gehome",
"requirements": ["gehomesdk==0.4.12","magicattr==0.1.5"],
"requirements": ["gehomesdk==0.4.25","magicattr==0.1.5","slixmpp==1.7.1"],
"codeowners": ["@simbaja"],
"version": "0.5.0"
"version": "0.6.0"
}

View File

@ -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)

View File

@ -34,7 +34,7 @@ from .const import (
)
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__)
@ -261,6 +261,7 @@ class GeHomeUpdateCoordinator(DataUpdateCoordinator):
api = self.appliance_apis[appliance.mac_addr]
except KeyError:
return
for entity in api.entities:
if entity.enabled:
_LOGGER.debug(f"Updating {entity} ({entity.unique_id}, {entity.entity_id})")

View File

@ -1,6 +1,6 @@
{
"name": "GE Home (SmartHQ)",
"homeassistant": "2021.7.1",
"domains": ["binary_sensor", "sensor", "switch", "water_heater", "select"],
"homeassistant": "2021.12.0",
"domains": ["binary_sensor", "sensor", "switch", "water_heater", "select", "button", "climate", "light", "number"],
"iot_class": "Cloud Polling"
}

32
info.md
View File

@ -2,15 +2,25 @@
Integration for GE WiFi-enabled appliances into Home Assistant. This integration currently support the following devices:
- Fridge
- Fridge
- Oven
- Dishwasher
- Laundry (Washer/Dryer)
- Whole Home Water Filter
- Whole Home Water Softener
- A/C (Portable, Split, Window)
- Range Hood
- Advantium
- Microwave
- Opal Ice Maker
- Coffee Maker
**Forked from Andrew Mark's [repository](https://github.com/ajmarks/ha_components).**
## Updates
Unfortunately, I'm pretty much at the end of what I can do without assistance from others with these devices that can help provide logs. I'll do what I can to make updates if there's something broken, but I am not really able to add new functionality if I can't get a little help to do so.
## Home Assistant UI Examples
Entities card:
@ -34,6 +44,13 @@ A/C Controls:
#### Breaking Changes
{% if version_installed.split('.') | map('int') < '0.6.0'.split('.') | map('int') %}
- Requires HA version 2021.12.0 or later
- Enabled authentication to both US and EU regions (may require re-auth)
- Changed the sensors to use native value/uom
- Changed the temperatures to always be natively fahrenheit (API appears to always use this system) (@vignatyuk)
{% endif %}
{% if version_installed.split('.') | map('int') < '0.4.0'.split('.') | map('int') %}
- Laundry support changes will cause entity names to be different, you will need to fix in HA (uninstall, reboot, delete leftover entitites, install, reboot)
{% endif %}
@ -46,6 +63,13 @@ A/C Controls:
#### Features
{% if version_installed.split('.') | map('int') < '0.6.0'.split('.') | map('int') %}
- Initial support for Water Softeners (@npentell, @drjeff)
- Initial support for Opal Ice Makers (@mbcomer, @knobunc)
- Initial support for Microwaves (@mbcomer, @mnestor)
- Initial support for Coffee Makers (@alexanv1)
{% endif %}
{% if version_installed.split('.') | map('int') < '0.5.0'.split('.') | map('int') %}
- Support for Oven Hood units (@digitalbites)
- Added extended mode support for ovens
@ -67,12 +91,16 @@ A/C Controls:
#### Bugfixes
{% if version_installed.split('.') | map('int') < '0.6.0'.split('.') | map('int') %}
- Updated deprecated icons (@mjmeli, @schmittx)
{% endif %}
{% if version_installed.split('.') | map('int') < '0.5.0'.split('.') | map('int') %}
- Advantium fixes (@willhayslett)
- Fixed device info when serial not present (@Xe138)
- Fixed issue with ovens when raw temperature not available (@chadohalloran)
- Fixed issue where Split A/C temperature sensors report UOM incorrectly (@RobertusIT)
- Added convertable drawer mode, proximity light, and interior lights to fridge (@grotto27, @elwing00)
- Added convertable drawer mode, proximity light, and interior lights to fridge (@groto27, @elwing00)
{% endif %}
{% if version_installed.split('.') | map('int') < '0.4.3'.split('.') | map('int') %}