Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

range: add oven timers and cook timers #843

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions custom_components/smartthinq_sensors/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,50 @@ class ThinQSensorEntityDescription(SensorEntityDescription):
device_class=SensorDeviceClass.TEMPERATURE,
unit_fn=lambda x: x.oven_temp_unit,
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_UPPER_TIMER_TIME,
name="Oven upper timer",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.TIMESTAMP,
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_UPPER_TIMER_STATE,
name="Oven upper timer state",
icon="mdi:timer-cog-outline",
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_UPPER_COOK_TIMER_TIME,
name="Oven upper cook time",
icon="mdi:timer-check-outline",
device_class=SensorDeviceClass.TIMESTAMP,
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_UPPER_COOK_TIMER_STATE,
name="Oven upper cook time state",
icon="mdi:timer-cog-outline",
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_LOWER_TIMER_TIME,
name="Oven lower timer",
icon="mdi:timer-outline",
device_class=SensorDeviceClass.TIMESTAMP,
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_LOWER_TIMER_STATE,
name="Oven lower timer state",
icon="mdi:timer-cog-outline",
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_LOWER_COOK_TIMER_TIME,
name="Oven lower cook time",
icon="mdi:timer-check-outline",
device_class=SensorDeviceClass.TIMESTAMP,
),
ThinQSensorEntityDescription(
key=RangeFeatures.OVEN_LOWER_COOK_TIMER_STATE,
name="Oven lower cook time state",
icon="mdi:timer-cog-outline",
),
)
AIR_PURIFIER_SENSORS: tuple[ThinQSensorEntityDescription, ...] = (
ThinQSensorEntityDescription(
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 @@ -96,6 +96,14 @@ class RangeFeatures(StrEnum):
OVEN_UPPER_CURRENT_TEMP = "oven_upper_current_temp"
OVEN_UPPER_MODE = "oven_upper_mode"
OVEN_UPPER_STATE = "oven_upper_state"
OVEN_UPPER_TIMER_TIME = "oven_upper_timer"
OVEN_UPPER_COOK_TIMER_TIME = "oven_upper_cook_time"
OVEN_LOWER_TIMER_TIME = "oven_lower_timer"
OVEN_LOWER_COOK_TIMER_TIME = "oven_lower_cook_time"
OVEN_UPPER_TIMER_STATE = "oven_upper_timer_state"
OVEN_UPPER_COOK_TIMER_STATE = "oven_upper_cook_timer_state"
OVEN_LOWER_TIMER_STATE = "oven_lower_timer_state"
OVEN_LOWER_COOK_TIMER_STATE = "oven_lower_cook_timer_state"


class RefrigeratorFeatures(StrEnum):
Expand Down
115 changes: 115 additions & 0 deletions custom_components/smartthinq_sensors/wideq/devices/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from __future__ import annotations

from datetime import datetime, timedelta

from ..const import BIT_OFF, RangeFeatures, StateOptions, TemperatureUnit
from ..core_async import ClientAsync
from ..device import Device, DeviceStatus
Expand Down Expand Up @@ -127,6 +129,111 @@ def _get_bit_target_temp(self, key: str):

return target_temp

def _get_time_delta(
self, hour_key: str, min_key: str, sec_key: str
) -> timedelta | None:
"""Get the time remaining as a timedelta."""
try:
hours = self.to_int_or_none(self._data.get(hour_key))
mins = self.to_int_or_none(self._data.get(min_key))
secs = self.to_int_or_none(self._data.get(sec_key))

if any(x is not None for x in (hours, mins, secs)):
return timedelta(hours=hours or 0, minutes=mins or 0, seconds=secs or 0)
except (ValueError, TypeError):
pass
return None

def _get_finish_time(self, delta: timedelta | None) -> datetime | None:
"""Get the finish time based on the time delta."""
if delta is not None:
now = datetime.now().astimezone()
if delta.total_seconds() > 0:
return now + delta
# Return start of current day when timer is 0/off
return now.replace(hour=0, minute=0, second=0, microsecond=0).astimezone()
return None

@property
def oven_upper_timer_time(self) -> datetime | None:
"""Get the upper timer finish time."""
delta = self._get_time_delta("UpperTimerHour", "UpperTimerMin", "UpperTimerSec")
return self._update_feature(
RangeFeatures.OVEN_UPPER_TIMER_TIME, self._get_finish_time(delta), False
)

@property
def oven_upper_cook_timer_time(self) -> datetime | None:
"""Get the upper cook time finish time."""
delta = self._get_time_delta(
"UpperCookTimeHour", "UpperCookTimeMin", "UpperCookTimeSec"
)
return self._update_feature(
RangeFeatures.OVEN_UPPER_COOK_TIMER_TIME,
self._get_finish_time(delta),
False,
)
Comment on lines +165 to +175
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should find a way to check if the device provide this sensor (oven_upper_cook) and if not just return None, otherwise sensor for not existing cook zone will be created.
This is the same for all the other new properties.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. My oven only has upper, so the lower cook zone sensors are non-functional. I'll make the necessary changes for this.

Also, the stove top of my oven only reports whether any of the (five) burners are on, and it uses the front-left cooktop state to report it. So should cooktop state sensors also be modified (in a separate PR) in the same way you're suggesting here?

Thanks.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circling back around to this. I don't see any mechanism, in the current code, at least, for the detection of which sensors should or shouldn't be included. Rather, the optional entity_registry_enabled_default is used to control this regardless of the data coming from the device.

My range has no lower oven, but they are still enabled by default, and though my COOKTOP_LEFT_FRONT_STATE is valid, it's disabled by default.

CleanShot 2025-02-10 at 09 10 24

Should we just make the call, as is currently done, as to which sensors are enabled by default? Or should we dig deeper and see if there's a way to find out which ones ought to be enabled?

It may be difficult to discern which sensors should be enabled by default, since I observe that the sensors that do not exist for my range show the same values as those that do exist when the range is not in use. We could provide some type of observational state where the user is instructed to go operate their range, turning on and off each cooktop burner, each oven, each timer/setting, and the integration could start with all sensors disabled, and enable each one whose state changes during the observational period. But that sounds complicated and annoying for users.

If we can't find a good method to do this automatically, then we have to make the call. Personally, I think that this integration, being a HACS integration, is for those users that desire more granular control and visibility for their LG appliances/devices, so it would be better to enable everything and let the user disable the inapplicable sensors. Alternatively, we should disable everything and let the user enable them manually. We would then include a note in the read-me or a dialog during setup that not all sensors may apply to their specific appliances/devices.


@property
def oven_lower_timer_time(self) -> datetime | None:
"""Get the lower timer finish time."""
delta = self._get_time_delta("LowerTimerHour", "LowerTimerMin", "LowerTimerSec")
return self._update_feature(
RangeFeatures.OVEN_LOWER_TIMER_TIME, self._get_finish_time(delta), False
)

@property
def oven_lower_cook_timer_time(self) -> datetime | None:
"""Get the lower cook time finish time."""
delta = self._get_time_delta(
"LowerCookTimeHour", "LowerCookTimeMin", "LowerCookTimeSec"
)
return self._update_feature(
RangeFeatures.OVEN_LOWER_COOK_TIMER_TIME,
self._get_finish_time(delta),
False,
)

@property
def oven_upper_timer_state(self) -> str | None:
"""Get the upper timer state."""
delta = self._get_time_delta("UpperTimerHour", "UpperTimerMin", "UpperTimerSec")
state = (
StateOptions.ON if delta and delta.total_seconds() > 0 else StateOptions.OFF
)
return self._update_feature(RangeFeatures.OVEN_UPPER_TIMER_STATE, state)

@property
def oven_upper_cook_timer_state(self) -> str | None:
"""Get the upper cook time state."""
delta = self._get_time_delta(
"UpperCookTimeHour", "UpperCookTimeMin", "UpperCookTimeSec"
)
state = (
StateOptions.ON if delta and delta.total_seconds() > 0 else StateOptions.OFF
)
return self._update_feature(RangeFeatures.OVEN_UPPER_COOK_TIMER_STATE, state)

@property
def oven_lower_timer_state(self) -> str | None:
"""Get the lower timer state."""
delta = self._get_time_delta("LowerTimerHour", "LowerTimerMin", "LowerTimerSec")
state = (
StateOptions.ON if delta and delta.total_seconds() > 0 else StateOptions.OFF
)
return self._update_feature(RangeFeatures.OVEN_LOWER_TIMER_STATE, state)

@property
def oven_lower_cook_timer_state(self) -> str | None:
"""Get the lower cook time state."""
delta = self._get_time_delta(
"LowerCookTimeHour", "LowerCookTimeMin", "LowerCookTimeSec"
)
state = (
StateOptions.ON if delta and delta.total_seconds() > 0 else StateOptions.OFF
)
return self._update_feature(RangeFeatures.OVEN_LOWER_COOK_TIMER_STATE, state)

@property
def is_on(self):
"""Return if device is on."""
Expand Down Expand Up @@ -313,4 +420,12 @@ def _update_features(self):
self.oven_upper_state,
self.oven_upper_mode,
self.oven_upper_current_temp,
self.oven_upper_timer_time,
self.oven_upper_cook_timer_time,
self.oven_lower_timer_time,
self.oven_lower_cook_timer_time,
self.oven_upper_timer_state,
self.oven_upper_cook_timer_state,
self.oven_lower_timer_state,
self.oven_lower_cook_timer_state,
]