8000 Improve overheat reporting by rytilahti · Pull Request #1335 · python-kasa/python-kasa · GitHub
[go: up one dir, main page]

Skip to content

Improve overheat reporting #1335

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

Merged
merged 5 commits into from
Dec 11, 2024
Merged
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
2 changes: 1 addition & 1 deletion docs/tutorial.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,5 @@
True
>>> for feat in dev.features.values():
>>> print(f"{feat.name}: {feat.value}")
Device ID: 0000000000000000000000000000000000000000\nState: True\nSignal Level: 2\nRSSI: -52\nSSID: #MASKED_SSID#\nOverheated: False\nReboot: <Action>\nBrightness: 50\nCloud connection: True\nHSV: HSV(hue=0, saturation=100, value=50)\nColor temperature: 2700\nAuto update enabled: True\nUpdate available: None\nCurrent firmware version: 1.1.6 Build 240130 Rel.173828\nAvailable firmware version: None\nCheck latest firmware: <Action>\nLight effect: Party\nLight preset: Light preset 1\nSmooth transition on: 2\nSmooth transition off: 2\nDevice time: 2024-02-23 02:40:15+01:00
Device ID: 0000000000000000000000000000000000000000\nState: True\nSignal Level: 2\nRSSI: -52\nSSID: #MASKED_SSID#\nReboot: <Action>\nBrightness: 50\nCloud connection: True\nHSV: HSV(hue=0, saturation=100, value=50)\nColor temperature: 2700\nAuto update enabled: True\nUpdate available: None\nCurrent firmware version: 1.1.6 Build 240130 Rel.173828\nAvailable firmware version: None\nCheck latest firmware: <Action>\nLight effect: Party\nLight preset: Light preset 1\nSmooth transition on: 2\nSmooth transition off: 2\nOverheated: False\nDevice time: 2024-02-23 02:40:15+01:00
"""
2 changes: 1 addition & 1 deletion kasa/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
Signal Level (signal_level): 2
RSSI (rssi): -52
SSID (ssid): #MASKED_SSID#
Overheated (overheated): False
Reboot (reboot): <Action>
Brightness (brightness): 100
Cloud connection (cloud_connection): True
Expand All @@ -39,6 +38,7 @@
Light preset (light_preset): Not set
Smooth transition on (smooth_transition_on): 2
Smooth transition off (smooth_transition_off): 2
Overheated (overheated): False
Device time (device_time): 2024-02-23 02:40:15+01:00

To see whether a device supports a feature, check for the existence of it:
Expand Down
2 changes: 2 additions & 0 deletions kasa/smart/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from .lightstripeffect import LightStripEffect
from .lighttransition import LightTransition
from .motionsensor import MotionSensor
from .overheatprotection import OverheatProtection
from .reportmode import ReportMode
from .temperaturecontrol import TemperatureControl
from .temperaturesensor import TemperatureSensor
Expand Down Expand Up @@ -64,4 +65,5 @@
"FrostProtection",
"Thermostat",
"SmartLightEffect",
"OverheatProtection",
]
2 changes: 1 addition & 1 deletion kasa/smart/modules/contactsensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class ContactSensor(SmartModule):
"""Implementation of contact sensor module."""

REQUIRED_COMPONENT = None # we depend on availability of key
REQUIRED_KEY_ON_PARENT = "open"
SYSINFO_LOOKUP_KEYS = ["open"]

def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
Expand Down
41 changes: 41 additions & 0 deletions kasa/smart/modules/overheatprotection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Overheat module."""

from __future__ import annotations

from ...feature import Feature
from ..smartmodule import SmartModule


class OverheatProtection(SmartModule):
"""Implementation for overheat_protection."""

SYSINFO_LOOKUP_KEYS = ["overheated", "overheat_status"]

8000
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
self._device,
container=self,
id="overheated",
name="Overheated",
attribute_getter="overheated",
icon="mdi:heat-wave",
type=Feature.Type.BinarySensor,
category=Feature.Category.Info,
)
)

@property
def overheated(self) -> bool:
"""Return True if device reports overheating."""
if (value := self._device.sys_info.get("overheat_status")) is not None:
# Value can be normal, cooldown, or overheated.
# We report all but normal as overheated.
return value != "normal"

return self._device.sys_info["overheated"]

def query(self) -> dict:
"""Query to execute during the update cycle."""
return {}
18 changes: 2 additions & 16 deletions kasa/smart/smartdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,8 @@ async def _initialize_modules(self) -> None:
) or mod.__name__ in child_modules_to_skip:
continue
required_component = cast(str, mod.REQUIRED_COMPONENT)
if required_component in self._components or (
mod.REQUIRED_KEY_ON_PARENT
and self.sys_info.get(mod.REQUIRED_KEY_ON_PARENT) is not None
if required_component in self._components or any(
self.sys_info.get(key) is not None for key in mod.SYSINFO_LOOKUP_KEYS
):
_LOGGER.debug(
"Device %s, found required %s, adding %s to modules.",
Expand Down Expand Up @@ -440,19 +439,6 @@ async def _initialize_features(self) -> None:
)
)

if "overheated" in self._info:
self._add_feature(
Feature(
self,
id="overheated",
name="Overheated",
attribute_getter=lambda x: x._info["overheated"],
icon="mdi:heat-wave",
type=Feature.Type.BinarySensor,
category=Feature.Category.Info,
)
)

# We check for the key available, and not for the property truthiness,
# as the value is falsy when the device is off.
if "on_time" in self._info:
Expand Down
4 changes: 2 additions & 2 deletions kasa/smart/smartmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ class SmartModule(Module):
NAME: str
#: Module is initialized, if the given component is available
REQUIRED_COMPONENT: str | None = None
#: Module is initialized, if the given key available in the main sysinfo
REQUIRED_KEY_ON_PARENT: str | None = None
#: Module is initialized, if any of the given keys exists in the sysinfo
SYSINFO_LOOKUP_KEYS: list[str] = []
#: Query to execute during the main update cycle
QUERY_GETTER_NAME: str

Expand Down
58 changes: 58 additions & 0 deletions tests/smart/test_smartdevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,61 @@ async def test_smart_temp_range(dev: Device):
light = dev.modules.get(Module.Light)
assert light
assert light.valid_temperature_range


@device_smart
async def test_initialize_modules_sysinfo_lookup_keys(
dev: SmartDevice, mocker: MockerFixture
):
"""Test that matching modules using SYSINFO_LOOKUP_KEYS are initialized correctly."""

class AvailableKey(SmartModule):
SYSINFO_LOOKUP_KEYS = ["device_id"]

class NonExistingKey(SmartModule):
SYSINFO_LOOKUP_KEYS = ["this_does_not_exist"]

# The __init_subclass__ hook in smartmodule checks the path,
# so we have to manually add these for testing.
mocker.patch.dict(
"kasa.smart.smartmodule.SmartModule.REGISTERED_MODULES",
{
AvailableKey._module_name(): AvailableKey,
NonExistingKey._module_name(): NonExistingKey,
},
)

# We have an already initialized device, so we try to initialize the modules again
await dev._initialize_modules()

assert "AvailableKey" in dev.modules
assert "NonExistingKey" not in dev.modules


@device_smart
async def test_initialize_modules_required_component(
dev: SmartDevice, mocker: MockerFixture
):
"""Test that matching modules using REQUIRED_COMPONENT are initialized correctly."""

class AvailableComponent(SmartModule):
REQUIRED_COMPONENT = "device"

class NonExistingComponent(SmartModule):
REQUIRED_COMPONENT = "this_does_not_exist"

# The __init_subclass__ hook in smartmodule checks the path,
# so we have to manually add these for testing.
mocker.patch.dict(
"kasa.smart.smartmodule.SmartModule.REGISTERED_MODULES",
{
AvailableComponent._module_name(): AvailableComponent,
NonExistingComponent._module_name(): NonExistingComponent,
},
)

# We have an already initialized device, so we try to initialize the modules again
await dev._initialize_modules()

assert "AvailableComponent" in dev.modules
assert "NonExistingComponent" not in dev.modules
Loading
2A79
0