-
-
Notifications
You must be signed in to change notification settings - Fork 238
Add temperature control module for smart #848
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| """Implementation of temperature control module.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import TYPE_CHECKING | ||
|
|
||
| from ...feature import Feature | ||
| from ..smartmodule import SmartModule | ||
|
|
||
| if TYPE_CHECKING: | ||
| from ..smartdevice import SmartDevice | ||
|
|
||
|
|
||
| class TemperatureControl(SmartModule): | ||
| """Implementation of temperature module.""" | ||
|
|
||
| REQUIRED_COMPONENT = "temperature_control" | ||
|
|
||
| def __init__(self, device: SmartDevice, module: str): | ||
| super().__init__(device, module) | ||
| self._add_feature( | ||
| Feature( | ||
| device, | ||
| "Target temperature", | ||
| container=self, | ||
| attribute_getter="target_temperature", | ||
| attribute_setter="set_target_temperature", | ||
| icon="mdi:thermometer", | ||
| ) | ||
| ) | ||
| # TODO: this might belong into its own module, temperature_correction? | ||
| self._add_feature( | ||
| Feature( | ||
| device, | ||
| "Temperature offset", | ||
| container=self, | ||
| attribute_getter="temperature_offset", | ||
| attribute_setter="set_temperature_offset", | ||
| minimum_value=-10, | ||
| maximum_value=10, | ||
| ) | ||
| ) | ||
|
|
||
| def query(self) -> dict: | ||
| """Query to execute during the update cycle.""" | ||
| # Target temperature is contained in the main device info response. | ||
| return {} | ||
|
|
||
| @property | ||
| def minimum_target_temperature(self) -> int: | ||
| """Minimum available target temperature.""" | ||
| return self._device.sys_info["min_control_temp"] | ||
|
|
||
| @property | ||
| def maximum_target_temperature(self) -> int: | ||
| """Minimum available target temperature.""" | ||
| return self._device.sys_info["max_control_temp"] | ||
|
|
||
| @property | ||
| def target_temperature(self) -> int: | ||
| """Return target temperature.""" | ||
| return self._device.sys_info["target_temperature"] | ||
|
|
||
| async def set_target_temperature(self, target: int): | ||
| """Set target temperature.""" | ||
| if ( | ||
| target < self.minimum_target_temperature | ||
| or target > self.maximum_target_temperature | ||
| ): | ||
| raise ValueError( | ||
| f"Invalid target temperature {target}, must be in range " | ||
| f"[{self.minimum_target_temperature},{self.maximum_target_temperature}]" | ||
| ) | ||
|
|
||
| return await self.call("set_device_info", {"target_temp": target}) | ||
|
|
||
| @property | ||
| def temperature_offset(self) -> int: | ||
| """Return temperature offset.""" | ||
| return self._device.sys_info["temp_offset"] | ||
|
|
||
| async def set_temperature_offset(self, offset: int): | ||
| """Set temperature offset.""" | ||
| if offset < -10 or offset > 10: | ||
| raise ValueError("Temperature offset must be [-10, 10]") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do we know this the allowed range? I couldn't work it out from the fixture
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I took a look how it's done here: https://github.com/mihai-dinculescu/tapo/blob/22d8dce597c3dc0ce6e07f89b8434a2d8954eb81/tapo/src/requests/set_device_info/trv.rs#L82 edit: realized we are not linking to that project, so I created #857. |
||
|
|
||
| return await self.call("set_device_info", {"temp_offset": offset}) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import pytest | ||
|
|
||
| from kasa.smart.modules import TemperatureSensor | ||
| from kasa.tests.device_fixtures import parametrize | ||
|
|
||
| temperature = parametrize( | ||
| "has temperature control", | ||
| component_filter="temperature_control", | ||
| protocol_filter={"SMART.CHILD"}, | ||
| ) | ||
|
|
||
|
|
||
| @temperature | ||
| @pytest.mark.parametrize( | ||
| "feature, type", | ||
| [ | ||
| ("target_temperature", int), | ||
| ("temperature_offset", int), | ||
| ], | ||
| ) | ||
| async def test_temperature_control_features(dev, feature, type): | ||
| """Test that features are registered and work as expected.""" | ||
| temp_module: TemperatureSensor = dev.modules["TemperatureControl"] | ||
|
|
||
| prop = getattr(temp_module, feature) | ||
| assert isinstance(prop, type) | ||
|
|
||
| feat = temp_module._module_features[feature] | ||
| assert feat.value == prop | ||
| assert isinstance(feat.value, type) | ||
|
|
||
| await feat.set_value(10) | ||
| await dev.update() | ||
| assert feat.value == 10 |
Uh oh!
There was an error while loading. Please reload this page.