Skip to content

Commit

Permalink
Add support for Hood device (#635)
Browse files Browse the repository at this point in the history
  • Loading branch information
ollo69 authored Nov 1, 2023
1 parent e5967b4 commit 88f029e
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 3 deletions.
3 changes: 2 additions & 1 deletion custom_components/smartthinq_sensors/device_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
DeviceType.DRYER: "mdi:tumble-dryer",
DeviceType.STYLER: "mdi:palette-swatch-outline",
DeviceType.DISHWASHER: "mdi:dishwasher",
DeviceType.HOOD: "mdi:scent-off",
DeviceType.MICROWAVE: "mdi:microwave",
DeviceType.REFRIGERATOR: "mdi:fridge-outline",
DeviceType.RANGE: "mdi:stove",
Expand Down Expand Up @@ -372,6 +373,6 @@ def get_wrapper_device(
return LGERangeDevice(lge_device)
if dev_type in (DeviceType.AC, DeviceType.WATER_HEATER):
return LGETempDevice(lge_device)
if dev_type == DeviceType.MICROWAVE:
if dev_type in (DeviceType.HOOD, DeviceType.MICROWAVE):
return LGEBaseDevice(lge_device)
return None
15 changes: 14 additions & 1 deletion custom_components/smartthinq_sensors/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from . import LGEDevice
from .const import DOMAIN, LGE_DEVICES, LGE_DISCOVERY_NEW
from .wideq import DeviceType, MicroWaveFeatures
from .wideq import DeviceType, HoodFeatures, MicroWaveFeatures

ATTR_FAN_MODE = "fan_mode"
ATTR_FAN_MODES = "fan_modes"
Expand Down Expand Up @@ -62,6 +62,11 @@ class LGEFanWrapperDescription:
turn_off_fn=lambda x: x.device.power(False),
turn_on_fn=lambda x: x.device.power(True),
)
HOOD_WRAPPER = LGEFanWrapperDescription(
fanspeed_fn=None,
fanspeeds_fn=lambda x: x.device.vent_speeds,
set_fanspeed_fn=lambda x, option: x.device.set_vent_speed(option),
)
MICROWAVE_WRAPPER = LGEFanWrapperDescription(
fanspeed_fn=None,
fanspeeds_fn=lambda x: x.device.vent_speeds,
Expand Down Expand Up @@ -96,6 +101,13 @@ class ThinQFanEntityDescription(FanEntityDescription, ThinQFanRequiredKeysMixin)
wrapper_description=AIRPURIFIER_WRAPPER,
),
)
HOOD_DEVICE: tuple[ThinQFanEntityDescription, ...] = (
ThinQFanEntityDescription(
key=HoodFeatures.VENT_SPEED,
name="Fan",
wrapper_description=HOOD_WRAPPER,
),
)
MICROWAVE_DEVICE: tuple[ThinQFanEntityDescription, ...] = (
ThinQFanEntityDescription(
key=MicroWaveFeatures.VENT_SPEED,
Expand All @@ -107,6 +119,7 @@ class ThinQFanEntityDescription(FanEntityDescription, ThinQFanRequiredKeysMixin)
FAN_ENTITIES = {
DeviceType.FAN: FAN_DEVICE,
DeviceType.AIR_PURIFIER: AIRPURIFIER_DEVICE,
DeviceType.HOOD: HOOD_DEVICE,
DeviceType.MICROWAVE: MICROWAVE_DEVICE,
}

Expand Down
11 changes: 10 additions & 1 deletion custom_components/smartthinq_sensors/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from . import LGEDevice
from .const import DOMAIN, LGE_DEVICES, LGE_DISCOVERY_NEW
from .device_helpers import LGEBaseDevice
from .wideq import DeviceType, MicroWaveFeatures
from .wideq import DeviceType, HoodFeatures, MicroWaveFeatures

_LOGGER = logging.getLogger(__name__)

Expand All @@ -36,6 +36,14 @@ class ThinQLightEntityDescription(LightEntityDescription):
turn_on_fn: Callable[[Any], Awaitable[None]] | None = None


HOOD_LIGHT: tuple[ThinQLightEntityDescription, ...] = (
ThinQLightEntityDescription(
key=HoodFeatures.LIGHT_MODE,
name="Light",
effects_fn=lambda x: x.device.light_modes,
set_effect_fn=lambda x, option: x.device.set_light_mode(option),
),
)
MICROWAVE_LIGHT: tuple[ThinQLightEntityDescription, ...] = (
ThinQLightEntityDescription(
key=MicroWaveFeatures.LIGHT_MODE,
Expand All @@ -46,6 +54,7 @@ class ThinQLightEntityDescription(LightEntityDescription):
)

LIGHT_ENTITIES = {
DeviceType.HOOD: HOOD_LIGHT,
DeviceType.MICROWAVE: MICROWAVE_LIGHT,
}

Expand Down
8 changes: 8 additions & 0 deletions custom_components/smartthinq_sensors/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,13 @@ class ThinQSensorEntityDescription(SensorEntityDescription):
native_unit_of_measurement=UnitOfPower.WATT,
),
)
HOOD_SENSORS: tuple[ThinQSensorEntityDescription, ...] = (
ThinQSensorEntityDescription(
key=DEFAULT_SENSOR,
icon=DEFAULT_ICON,
value_fn=lambda x: x.power_state,
),
)
MICROWAVE_SENSORS: tuple[ThinQSensorEntityDescription, ...] = (
ThinQSensorEntityDescription(
key=DEFAULT_SENSOR,
Expand All @@ -517,6 +524,7 @@ class ThinQSensorEntityDescription(SensorEntityDescription):
DeviceType.AC: AC_SENSORS,
DeviceType.AIR_PURIFIER: AIR_PURIFIER_SENSORS,
DeviceType.DEHUMIDIFIER: DEHUMIDIFIER_SENSORS,
DeviceType.HOOD: HOOD_SENSORS,
DeviceType.MICROWAVE: MICROWAVE_SENSORS,
DeviceType.RANGE: RANGE_SENSORS,
DeviceType.REFRIGERATOR: REFRIGERATOR_SENSORS,
Expand Down
8 changes: 8 additions & 0 deletions custom_components/smartthinq_sensors/wideq/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,11 @@ class MicroWaveFeatures(StrEnum):
SOUND = "sound"
VENT_SPEED = "vent_speed"
WEIGHT_UNIT = "weight_unit"


class HoodFeatures(StrEnum):
"""Features for LG Hood devices."""

LIGHT_MODE = "light_mode"
HOOD_STATE = "hood_state"
VENT_SPEED = "vent_speed"
234 changes: 234 additions & 0 deletions custom_components/smartthinq_sensors/wideq/devices/hood.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
"""------------------for Hood"""
from __future__ import annotations

from copy import deepcopy
from enum import Enum
from functools import cached_property

from ..const import BIT_OFF, HoodFeatures, StateOptions
from ..core_async import ClientAsync
from ..device import Device, DeviceStatus
from ..device_info import DeviceInfo

ITEM_STATE_OFF = "@OV_STATE_INITIAL_W"

STATE_LAMPLEVEL = "LampLevel"
STATE_VENTLEVEL = "VentLevel"

CMD_LAMPMODE = "lampOnOff"
CMD_LAMPLEVEL = "lampLevel"
CMD_VENTMODE = "ventOnOff"
CMD_VENTLEVEL = "ventLevel"

CMD_SET_VENTLAMP = "setCookStart"

KEY_DATASET = "dataSetList"
KEY_HOODSTATE = "hoodState"

CMD_VENTLAMP_DICT = {
"command": "Set",
"ctrlKey": CMD_SET_VENTLAMP,
KEY_DATASET: {KEY_HOODSTATE: {}},
}

HOOD_CMD = {
CMD_SET_VENTLAMP: CMD_VENTLAMP_DICT,
}

MODE_ENABLE = "ENABLE"
MODE_DISABLE = "DISABLE"


class LightLevel(Enum):
"""The light level for a Hood device."""

OFF = "0"
LOW = "1"
HIGH = "2"


class VentSpeed(Enum):
"""The vent speed for a Hood device."""

OFF = "0"
LOW = "1"
MID = "2"
HIGH = "3"
TURBO = "4"
MAX = "5"


class HoodDevice(Device):
"""A higher-level interface for a hood."""

def __init__(self, client: ClientAsync, device_info: DeviceInfo):
"""Init the device."""
super().__init__(client, device_info, HoodStatus(self))

def reset_status(self):
self._status = HoodStatus(self)
return self._status

# Settings
def _prepare_command_ventlamp(self):
"""Prepare vent / lamp command."""
if not self._status:
return {}

status_data = self._status.data
vent_level = status_data.get(STATE_VENTLEVEL, "0")
lamp_level = status_data.get(STATE_LAMPLEVEL, "0")
return {
CMD_VENTMODE: MODE_ENABLE if vent_level != "0" else MODE_DISABLE,
CMD_VENTLEVEL: int(vent_level),
CMD_LAMPMODE: MODE_ENABLE if lamp_level != "0" else MODE_DISABLE,
CMD_LAMPLEVEL: int(lamp_level),
}

def _prepare_command(self, ctrl_key, command, key, value):
"""
Prepare command for specific device.
Overwrite for specific device settings.
"""
if (cmd_key := HOOD_CMD.get(ctrl_key)) is None:
return None

if ctrl_key == CMD_SET_VENTLAMP:
full_cmd = self._prepare_command_ventlamp()
else:
full_cmd = {}

cmd = deepcopy(cmd_key)
def_cmd = cmd[KEY_DATASET].get(KEY_HOODSTATE, {})
cmd[KEY_DATASET][KEY_HOODSTATE] = {**def_cmd, **full_cmd, **command}

return cmd

# Light
@cached_property
def _supported_light_modes(self) -> dict[str, str]:
"""Get display scroll speed list."""
key = self._get_state_key(STATE_LAMPLEVEL)
if not (mapping := self.model_info.enum_range_values(key)):
return {}
mode_list = [e.value for e in LightLevel]
return {LightLevel(k).name: k for k in mapping if k in mode_list}

@property
def light_modes(self) -> list[str]:
"""Get display scroll speed list."""
return list(self._supported_light_modes)

async def set_light_mode(self, mode: str):
"""Set light mode."""
if mode not in self.light_modes:
raise ValueError(f"Invalid light mode: {mode}")

level = self._supported_light_modes[mode]
status = MODE_ENABLE if level != "0" else MODE_DISABLE
cmd = {CMD_LAMPMODE: status, CMD_LAMPLEVEL: int(level)}

await self.set(CMD_SET_VENTLAMP, cmd, key=STATE_LAMPLEVEL, value=level)

# Vent
@cached_property
def _supported_vent_speeds(self) -> dict[str, str]:
"""Get vent speed."""
key = self._get_state_key(STATE_VENTLEVEL)
if not (mapping := self.model_info.enum_range_values(key)):
return {}
mode_list = [e.value for e in VentSpeed]
return {VentSpeed(k).name: k for k in mapping if k in mode_list}

@property
def vent_speeds(self) -> list[str]:
"""Get vent speed list."""
return list(self._supported_vent_speeds)

async def set_vent_speed(self, option: str):
"""Set vent speed."""
if option not in self.vent_speeds:
raise ValueError(f"Invalid vent mode: {option}")

level = self._supported_vent_speeds[option]
mode = MODE_ENABLE if level != "0" else MODE_DISABLE
cmd = {CMD_VENTMODE: mode, CMD_VENTLEVEL: int(level)}

await self.set(CMD_SET_VENTLAMP, cmd, key=STATE_VENTLEVEL, value=level)

async def set(
self, ctrl_key, command, *, key=None, value=None, data=None, ctrl_path=None
):
"""Set a device's control for `key` to `value`."""
await super().set(
ctrl_key, command, key=key, value=value, data=data, ctrl_path=ctrl_path
)
if self._status and key is not None:
self._status.update_status(key, value)

async def poll(self) -> HoodStatus | None:
"""Poll the device's current state."""
res = await self._device_poll()
if not res:
return None

self._status = HoodStatus(self, res)
return self._status


class HoodStatus(DeviceStatus):
"""
Higher-level information about a hood current status.
:param device: The Device instance.
:param data: JSON data from the API.
"""

_device: HoodDevice

@property
def hood_state(self):
"""Return hood state."""
status = self.lookup_enum("HoodState")
if status is None:
return None
if status == ITEM_STATE_OFF:
status = BIT_OFF
return self._update_feature(HoodFeatures.HOOD_STATE, status)

@property
def is_on(self):
"""Return if device is on."""
res = self.device_features.get(HoodFeatures.HOOD_STATE)
if res and res != StateOptions.OFF:
return True
return False

@property
def light_mode(self):
"""Get light mode."""
if (value := self.lookup_range(STATE_LAMPLEVEL)) is None:
return None
try:
status = LightLevel(value).name
except ValueError:
return None
return self._update_feature(HoodFeatures.LIGHT_MODE, status, False)

@property
def vent_speed(self):
"""Get vent speed."""
if (value := self.lookup_range(STATE_VENTLEVEL)) is None:
return None
try:
status = VentSpeed(value).name
except ValueError:
return None
return self._update_feature(HoodFeatures.VENT_SPEED, status, False)

def _update_features(self):
_ = [
self.hood_state,
self.light_mode,
self.vent_speed,
]
3 changes: 3 additions & 0 deletions custom_components/smartthinq_sensors/wideq/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .devices.dehumidifier import DeHumidifierDevice
from .devices.dishwasher import DishWasherDevice
from .devices.fan import FanDevice
from .devices.hood import HoodDevice
from .devices.microwave import MicroWaveDevice
from .devices.range import RangeDevice
from .devices.refrigerator import RefrigeratorDevice
Expand Down Expand Up @@ -49,6 +50,8 @@ def get_lge_device(
return [DishWasherDevice(client, device_info)]
if device_type == DeviceType.FAN:
return [FanDevice(client, device_info)]
if device_type == DeviceType.HOOD:
return [HoodDevice(client, device_info)]
if device_type == DeviceType.MICROWAVE:
return [MicroWaveDevice(client, device_info)]
if device_type == DeviceType.RANGE:
Expand Down

0 comments on commit 88f029e

Please sign in to comment.