python-hilo 2025.2.2__tar.gz → 2025.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. python_hilo-2025.4.1/PKG-INFO +125 -0
  2. python_hilo-2025.4.1/README.md +89 -0
  3. python_hilo-2025.4.1/pyhilo/__init__.py +27 -0
  4. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/api.py +7 -9
  5. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/const.py +9 -8
  6. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/device/__init__.py +6 -3
  7. python_hilo-2025.4.1/pyhilo/device/climate.py +99 -0
  8. python_hilo-2025.4.1/pyhilo/device/graphql_value_mapper.py +481 -0
  9. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/devices.py +12 -4
  10. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/event.py +1 -0
  11. python_hilo-2025.4.1/pyhilo/graphql.py +614 -0
  12. python_hilo-2025.4.1/pyhilo/oauth2.py +51 -0
  13. python_hilo-2025.4.1/pyhilo/oauth2helper.py +65 -0
  14. python_hilo-2025.4.1/pyhilo/util/state.py +166 -0
  15. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/websocket.py +2 -4
  16. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyproject.toml +4 -3
  17. python_hilo-2025.2.2/PKG-INFO +0 -54
  18. python_hilo-2025.2.2/README.md +0 -18
  19. python_hilo-2025.2.2/pyhilo/__init__.py +0 -3
  20. python_hilo-2025.2.2/pyhilo/device/climate.py +0 -48
  21. python_hilo-2025.2.2/pyhilo/oauth2.py +0 -77
  22. python_hilo-2025.2.2/pyhilo/util/state.py +0 -116
  23. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/LICENSE +0 -0
  24. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/device/light.py +0 -0
  25. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/device/sensor.py +0 -0
  26. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/device/switch.py +0 -0
  27. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/exceptions.py +0 -0
  28. {python_hilo-2025.2.2 → python_hilo-2025.4.1}/pyhilo/util/__init__.py +0 -0
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.3
2
+ Name: python-hilo
3
+ Version: 2025.4.1
4
+ Summary: A Python3, async interface to the Hilo API
5
+ License: MIT
6
+ Author: David Vallee Delisle
7
+ Author-email: me@dvd.dev
8
+ Maintainer: David Vallee Delisle
9
+ Maintainer-email: me@dvd.dev
10
+ Requires-Python: >=3.9.0,<4.0.0
11
+ Classifier: Framework :: aiohttp
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: Implementation :: CPython
21
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
22
+ Classifier: Topic :: Home Automation
23
+ Requires-Dist: aiofiles (>=23.2.1)
24
+ Requires-Dist: aiohttp (>=3.8.0)
25
+ Requires-Dist: aiosignal (>=1.2.0)
26
+ Requires-Dist: async-timeout (>=4.0.0)
27
+ Requires-Dist: attrs (>=21.2.0)
28
+ Requires-Dist: backoff (>=1.11.1)
29
+ Requires-Dist: gql (>=3.5.2,<4.0.0)
30
+ Requires-Dist: python-dateutil (>=2.8.2)
31
+ Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
32
+ Requires-Dist: voluptuous (>=0.13.1)
33
+ Requires-Dist: websockets (>=8.1,<16.0)
34
+ Project-URL: Repository, https://github.com/dvd-dev/python-hilo
35
+ Description-Content-Type: text/markdown
36
+
37
+ # python-hilo
38
+
39
+ [![GitHub Release][releases-shield]][releases]
40
+ [![License][license-shield]](LICENSE)
41
+
42
+ [![Build Status][build-shield]][build]
43
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
44
+
45
+ `python-hilo` (aka `pyhilo`) is a Python 3.11, `asyncio`-driven interface to the unofficial
46
+ Hilo API from Hydro Quebec. This is meant to be integrated into Home Assistant but can also
47
+ be used as a standalone library.
48
+
49
+ Home assistant integration is available [here](https://github.com/dvd-dev/hilo)
50
+
51
+ ## TODO
52
+ - Type everything: almost done, got a few "type: ignore" to fix
53
+
54
+ ## Later?
55
+ - Full docstrings and doc generation
56
+ - Unit testing
57
+ - Functional testing
58
+
59
+ If anyone wants to contribute, feel free to submit a PR. If you'd like to sync up first, you can
60
+ fire me an email me@dvd.dev
61
+
62
+ ## Setting up development environment
63
+
64
+ The easiest way to start, is by opening a CodeSpace here on GitHub, or by using
65
+ the [Dev Container][devcontainer] feature of Visual Studio Code.
66
+
67
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
68
+
69
+ This Python project is fully managed using the [Poetry][poetry] dependency
70
+ manager. But also relies on the use of NodeJS for certain checks during
71
+ development.
72
+
73
+ You need at least:
74
+
75
+ - Python 3.11+
76
+ - [Poetry][poetry-install]
77
+ - NodeJS 20+ (including NPM)
78
+
79
+ To install all packages, including all development requirements:
80
+
81
+ ```bash
82
+ npm install
83
+ poetry install
84
+ ```
85
+
86
+ As this repository uses the [pre-commit][pre-commit] framework, all changes
87
+ are linted and tested with each commit. You can run all checks and tests
88
+ manually, using the following command:
89
+
90
+ ```bash
91
+ poetry run pre-commit run --all-files
92
+ ```
93
+
94
+ To run just the Python tests:
95
+
96
+ ```bash
97
+ poetry run pytest
98
+ ```
99
+
100
+ ## Authors & contributors
101
+
102
+ The original setup of this repository is by [David Vallée Delisle][dvd-dev].
103
+
104
+ Credits to [@frenck][frenck] for the base container configuration.
105
+ The license of python-wled can be found in
106
+ [third_party/python-wled/LICENSE](third_party/python-wled/LICENSE).
107
+
108
+ For a full list of all authors and contributors,
109
+ check [the contributor's page][contributors].
110
+
111
+
112
+
113
+ [build-shield]: https://github.com/dvd-dev/python-hilo/actions/workflows/tests.yaml/badge.svg
114
+ [build]: https://github.com/dvd-dev/python-hilo/actions/workflows/tests.yaml
115
+ [releases-shield]: https://img.shields.io/github/release/dvd-dev/python-hilo.svg
116
+ [releases]: https://github.com/dvd-dev/python-hilo/releases
117
+ [license-shield]: https://img.shields.io/github/license/dvd-dev/python-hilo.svg
118
+ [devcontainer-shield]: https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode
119
+ [devcontainer]: https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/dvd-dev/python-hilo
120
+ [poetry-install]: https://python-poetry.org/docs/#installation
121
+ [poetry]: https://python-poetry.org
122
+ [pre-commit]: https://pre-commit.com/
123
+ [dvd-dev]: https://github.com/dvd-dev
124
+ [frenck]: https://github.com/frenck
125
+ [contributors]: https://github.com/dvd-dev/python-hilo/graphs/contributors
@@ -0,0 +1,89 @@
1
+ # python-hilo
2
+
3
+ [![GitHub Release][releases-shield]][releases]
4
+ [![License][license-shield]](LICENSE)
5
+
6
+ [![Build Status][build-shield]][build]
7
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
8
+
9
+ `python-hilo` (aka `pyhilo`) is a Python 3.11, `asyncio`-driven interface to the unofficial
10
+ Hilo API from Hydro Quebec. This is meant to be integrated into Home Assistant but can also
11
+ be used as a standalone library.
12
+
13
+ Home assistant integration is available [here](https://github.com/dvd-dev/hilo)
14
+
15
+ ## TODO
16
+ - Type everything: almost done, got a few "type: ignore" to fix
17
+
18
+ ## Later?
19
+ - Full docstrings and doc generation
20
+ - Unit testing
21
+ - Functional testing
22
+
23
+ If anyone wants to contribute, feel free to submit a PR. If you'd like to sync up first, you can
24
+ fire me an email me@dvd.dev
25
+
26
+ ## Setting up development environment
27
+
28
+ The easiest way to start, is by opening a CodeSpace here on GitHub, or by using
29
+ the [Dev Container][devcontainer] feature of Visual Studio Code.
30
+
31
+ [![Open in Dev Containers][devcontainer-shield]][devcontainer]
32
+
33
+ This Python project is fully managed using the [Poetry][poetry] dependency
34
+ manager. But also relies on the use of NodeJS for certain checks during
35
+ development.
36
+
37
+ You need at least:
38
+
39
+ - Python 3.11+
40
+ - [Poetry][poetry-install]
41
+ - NodeJS 20+ (including NPM)
42
+
43
+ To install all packages, including all development requirements:
44
+
45
+ ```bash
46
+ npm install
47
+ poetry install
48
+ ```
49
+
50
+ As this repository uses the [pre-commit][pre-commit] framework, all changes
51
+ are linted and tested with each commit. You can run all checks and tests
52
+ manually, using the following command:
53
+
54
+ ```bash
55
+ poetry run pre-commit run --all-files
56
+ ```
57
+
58
+ To run just the Python tests:
59
+
60
+ ```bash
61
+ poetry run pytest
62
+ ```
63
+
64
+ ## Authors & contributors
65
+
66
+ The original setup of this repository is by [David Vallée Delisle][dvd-dev].
67
+
68
+ Credits to [@frenck][frenck] for the base container configuration.
69
+ The license of python-wled can be found in
70
+ [third_party/python-wled/LICENSE](third_party/python-wled/LICENSE).
71
+
72
+ For a full list of all authors and contributors,
73
+ check [the contributor's page][contributors].
74
+
75
+
76
+
77
+ [build-shield]: https://github.com/dvd-dev/python-hilo/actions/workflows/tests.yaml/badge.svg
78
+ [build]: https://github.com/dvd-dev/python-hilo/actions/workflows/tests.yaml
79
+ [releases-shield]: https://img.shields.io/github/release/dvd-dev/python-hilo.svg
80
+ [releases]: https://github.com/dvd-dev/python-hilo/releases
81
+ [license-shield]: https://img.shields.io/github/license/dvd-dev/python-hilo.svg
82
+ [devcontainer-shield]: https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode
83
+ [devcontainer]: https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/dvd-dev/python-hilo
84
+ [poetry-install]: https://python-poetry.org/docs/#installation
85
+ [poetry]: https://python-poetry.org
86
+ [pre-commit]: https://pre-commit.com/
87
+ [dvd-dev]: https://github.com/dvd-dev
88
+ [frenck]: https://github.com/frenck
89
+ [contributors]: https://github.com/dvd-dev/python-hilo/graphs/contributors
@@ -0,0 +1,27 @@
1
+ """Define the hilo package."""
2
+ from pyhilo.api import API
3
+ from pyhilo.const import UNMONITORED_DEVICES
4
+ from pyhilo.device import HiloDevice
5
+ from pyhilo.device.switch import Switch
6
+ from pyhilo.devices import Devices
7
+ from pyhilo.event import Event
8
+ from pyhilo.exceptions import HiloError, InvalidCredentialsError, WebsocketError
9
+ from pyhilo.oauth2 import AuthCodeWithPKCEImplementation
10
+ from pyhilo.util import from_utc_timestamp, time_diff
11
+ from pyhilo.websocket import WebsocketEvent
12
+
13
+ __all__ = [
14
+ "API",
15
+ "Devices",
16
+ "HiloDevice",
17
+ "Event",
18
+ "HiloError",
19
+ "InvalidCredentialsError",
20
+ "WebsocketError",
21
+ "AuthCodeWithPKCEImplementation",
22
+ "from_utc_timestamp",
23
+ "time_diff",
24
+ "WebsocketEvent",
25
+ "UNMONITORED_DEVICES",
26
+ "Switch",
27
+ ]
@@ -12,7 +12,6 @@ from urllib import parse
12
12
  from aiohttp import ClientSession
13
13
  from aiohttp.client_exceptions import ClientResponseError
14
14
  import backoff
15
- from homeassistant.helpers import config_entry_oauth2_flow
16
15
 
17
16
  from pyhilo.const import (
18
17
  ANDROID_CLIENT_ENDPOINT,
@@ -67,7 +66,7 @@ class API:
67
66
  self,
68
67
  *,
69
68
  session: ClientSession,
70
- oauth_session: config_entry_oauth2_flow.OAuth2Session,
69
+ oauth_session,
71
70
  request_retries: int = REQUEST_RETRY,
72
71
  log_traces: bool = False,
73
72
  ) -> None:
@@ -98,7 +97,7 @@ class API:
98
97
  cls,
99
98
  *,
100
99
  session: ClientSession,
101
- oauth_session: config_entry_oauth2_flow.OAuth2Session,
100
+ oauth_session,
102
101
  request_retries: int = REQUEST_RETRY,
103
102
  log_traces: bool = False,
104
103
  ) -> API:
@@ -141,7 +140,7 @@ class API:
141
140
  await self._oauth_session.async_ensure_token_valid()
142
141
 
143
142
  access_token = str(self._oauth_session.token["access_token"])
144
- LOG.debug(f"ic-dev21 access token is {access_token}")
143
+ LOG.debug(f"Websocket access token is {access_token}")
145
144
 
146
145
  return str(self._oauth_session.token["access_token"])
147
146
 
@@ -239,9 +238,8 @@ class API:
239
238
  kwargs["headers"]["authorization"] = f"Bearer {access_token}"
240
239
  kwargs["headers"]["Host"] = host
241
240
 
242
- # ic-dev21 trying Leicas suggestion
243
241
  if endpoint.startswith(AUTOMATION_CHALLENGE_ENDPOINT):
244
- # remove Ocp-Apim-Subscription-Key header to avoid 401 error
242
+ # remove Ocp-Apim-Subscription-Key header to avoid 401 error (Thanks Leicas)
245
243
  kwargs["headers"].pop("Ocp-Apim-Subscription-Key", None)
246
244
  kwargs["headers"]["authorization"] = f"Bearer {access_token}"
247
245
 
@@ -370,7 +368,7 @@ class API:
370
368
 
371
369
  async def _async_post_init(self) -> None:
372
370
  """Perform some post-init actions."""
373
- LOG.debug("Websocket postinit")
371
+ LOG.debug("Websocket _async_post_init running")
374
372
  await self._get_fid()
375
373
  await self._get_device_token()
376
374
 
@@ -494,11 +492,11 @@ class API:
494
492
  },
495
493
  )
496
494
 
497
- async def get_location_id(self) -> int:
495
+ async def get_location_ids(self) -> tuple[int, str]:
498
496
  url = f"{API_AUTOMATION_ENDPOINT}/Locations"
499
497
  LOG.debug(f"LocationId URL is {url}")
500
498
  req: list[dict[str, Any]] = await self.async_request("get", url)
501
- return int(req[0]["id"])
499
+ return (req[0]["id"], req[0]["locationHiloId"])
502
500
 
503
501
  async def get_devices(self, location_id: int) -> list[dict[str, Any]]:
504
502
  """Get list of all devices"""
@@ -3,12 +3,11 @@ import platform
3
3
  from typing import Final
4
4
 
5
5
  import aiohttp
6
- import homeassistant.core
7
6
 
8
7
  LOG: Final = logging.getLogger(__package__)
9
8
  DEFAULT_STATE_FILE: Final = "hilo_state.yaml"
10
9
  REQUEST_RETRY: Final = 9
11
- PYHILO_VERSION: Final = "2025.2.02"
10
+ PYHILO_VERSION: Final = "2025.4.01"
12
11
  # TODO: Find a way to keep previous line in sync with pyproject.toml automatically
13
12
 
14
13
  CONTENT_TYPE_FORM: Final = "application/x-www-form-urlencoded"
@@ -46,7 +45,7 @@ AUTOMATION_CHALLENGE_ENDPOINT: Final = "/ChallengeHub"
46
45
 
47
46
 
48
47
  # Request constants
49
- DEFAULT_USER_AGENT: Final = f"PyHilo/{PYHILO_VERSION} HomeAssistant/{homeassistant.core.__version__} aiohttp/{aiohttp.__version__} Python/{platform.python_version()}"
48
+ DEFAULT_USER_AGENT: Final = f"PyHilo/{PYHILO_VERSION} aiohttp/{aiohttp.__version__} Python/{platform.python_version()}"
50
49
 
51
50
 
52
51
  # NOTE(dvd): Not sure how to get new ones so I'm using the ones from my emulator
@@ -196,7 +195,7 @@ HILO_DEVICE_TYPES: Final = {
196
195
  }
197
196
 
198
197
  HILO_UNIT_CONVERSION: Final = {
199
- "Celcius": "°C",
198
+ "Celsius": "°C",
200
199
  "DB": "dB",
201
200
  "Integer": "dB",
202
201
  "Mbar": "mbar",
@@ -209,7 +208,7 @@ HILO_READING_TYPES: Final = {
209
208
  "BatteryPercent": "Percentage",
210
209
  "Co2": "PPM",
211
210
  "ColorTemperature": "Integer",
212
- "CurrentTemperature": "Celcius",
211
+ "CurrentTemperature": "Celsius",
213
212
  "Disconnected": "null",
214
213
  "DrmsState": "OnOff",
215
214
  "firmwareVersion": "null",
@@ -217,8 +216,8 @@ HILO_READING_TYPES: Final = {
217
216
  "Hue": "Integer",
218
217
  "Humidity": "Percentage",
219
218
  "Intensity": "Percentage",
220
- "MaxTempSetpoint": "Celcius",
221
- "MinTempSetpoint": "Celcius",
219
+ "MaxTempSetpoint": "Celsius",
220
+ "MinTempSetpoint": "Celsius",
222
221
  "Noise": "DB",
223
222
  "onlineStatus": "null",
224
223
  "OnOff": "OnOff",
@@ -226,7 +225,7 @@ HILO_READING_TYPES: Final = {
226
225
  "Pressure": "Mbar",
227
226
  "Saturation": "Integer",
228
227
  "Status": "OnOff",
229
- "TargetTemperature": "Celcius",
228
+ "TargetTemperature": "Celsius",
230
229
  "Unpaired": "null",
231
230
  "WifiStatus": "Integer",
232
231
  "zigBeePairingActivated": "OnOff",
@@ -269,3 +268,5 @@ UNMONITORED_DEVICES: Final = [
269
268
  "43094",
270
269
  "43100",
271
270
  ]
271
+
272
+ STATE_UNKNOWN: Final = "unknown"
@@ -1,12 +1,11 @@
1
1
  """Define devices"""
2
+
2
3
  from __future__ import annotations
3
4
 
4
5
  from dataclasses import dataclass, field
5
6
  from datetime import datetime
6
7
  from typing import TYPE_CHECKING, Any, Dict, Union, cast
7
8
 
8
- from homeassistant.const import STATE_UNKNOWN
9
-
10
9
  from pyhilo.const import (
11
10
  HILO_DEVICE_ATTRIBUTES,
12
11
  HILO_LIST_ATTRIBUTES,
@@ -16,6 +15,7 @@ from pyhilo.const import (
16
15
  JASCO_MODELS,
17
16
  JASCO_OUTLETS,
18
17
  LOG,
18
+ STATE_UNKNOWN,
19
19
  )
20
20
  from pyhilo.util import camel_to_snake, from_utc_timestamp
21
21
 
@@ -32,10 +32,11 @@ def get_device_attributes() -> list[DeviceAttribute]:
32
32
 
33
33
  class HiloDevice:
34
34
  def __init__(
35
- self, api: API, **kwargs: Dict[str, Union[str, int, Dict[Any, Any]]]
35
+ self, api: API, **kwargs: Dict[str, str | int | Dict[Any, Any]]
36
36
  ) -> None:
37
37
  self._api = api
38
38
  self.id = 0
39
+ self.hilo_id: str = ""
39
40
  self.location_id = 0
40
41
  self.type = "Unknown"
41
42
  self.name = "Unknown"
@@ -59,6 +60,7 @@ class HiloDevice:
59
60
  value = val.get("value")
60
61
  reading = {
61
62
  "deviceId": self.id,
63
+ "hiloId": self.hilo_id,
62
64
  "locationId": self.location_id,
63
65
  "timeStampUTC": datetime.utcnow().isoformat(),
64
66
  "value": value,
@@ -234,6 +236,7 @@ class DeviceReading:
234
236
  self.id = 0
235
237
  self.value: Union[int, bool, str] = 0
236
238
  self.device_id = 0
239
+ self.hilo_id: str = ""
237
240
  self.device_attribute: DeviceAttribute
238
241
  self.__dict__.update({camel_to_snake(k): v for k, v in kwargs.items()})
239
242
  self.unit_of_measurement = (
@@ -0,0 +1,99 @@
1
+ """Climate object."""
2
+ from __future__ import annotations
3
+
4
+ from typing import Any, cast
5
+
6
+ from pyhilo import API
7
+ from pyhilo.const import LOG
8
+ from pyhilo.device import HiloDevice
9
+
10
+
11
+ class Climate(HiloDevice):
12
+ """
13
+ Represents a climate device within the Hilo ecosystem.
14
+
15
+ This class provides methods to interact with and control climate-related
16
+ devices such as thermostats.
17
+ """
18
+
19
+ def __init__(
20
+ self, api: API, **kwargs: dict[str, str | int | dict[Any, Any]]
21
+ ) -> None:
22
+ """Initialize the Climate object.
23
+
24
+ Args:
25
+ api: The Hilo API instance.
26
+ **kwargs: Keyword arguments containing device data.
27
+ """
28
+ super().__init__(api, **kwargs)
29
+ LOG.debug("Setting up Climate device: %s", self.name)
30
+
31
+ @property
32
+ def current_temperature(self) -> float:
33
+ """
34
+ Gets the current temperature reported by the device.
35
+
36
+ Returns:
37
+ float: The current temperature.
38
+ """
39
+ return cast(float, self.get_value("current_temperature", 0))
40
+
41
+ @property
42
+ def target_temperature(self) -> float:
43
+ """
44
+ Gets the target temperature set for the device.
45
+
46
+ Returns:
47
+ float: The target temperature.
48
+ """
49
+ return cast(float, self.get_value("target_temperature", 0))
50
+
51
+ @property
52
+ def max_temp(self) -> float:
53
+ """
54
+ Gets the maximum temperature setpoint allowed for the device.
55
+
56
+ Returns:
57
+ float: The maximum temperature. Defaults to 36.0 if not defined.
58
+ """
59
+ value = self.get_value("max_temp_setpoint", 0)
60
+
61
+ if value is None or value == 0:
62
+ return 36.0
63
+ return float(value)
64
+
65
+ @property
66
+ def min_temp(self) -> float:
67
+ """
68
+ Gets the minimum temperature setpoint allowed for the device.
69
+
70
+ Returns:
71
+ float: The minimum temperature. Defaults to 5.0 if not defined.
72
+ """
73
+ value = self.get_value("min_temp_setpoint", 0)
74
+
75
+ if value is None or value == 0:
76
+ return 5.0
77
+ return float(value)
78
+
79
+ @property
80
+ def hvac_action(self) -> str:
81
+ """
82
+ Gets the current HVAC action of the device.
83
+
84
+ Returns:
85
+ str: 'heating' if heating is active, 'idle' otherwise.
86
+ """
87
+ attr = self.get_value("heating", 0)
88
+ return "heating" if attr > 0 else "idle"
89
+
90
+ async def async_set_temperature(self, temperature: float) -> None:
91
+ """
92
+ Sets the target temperature of the device.
93
+
94
+ Args:
95
+ temperature: The desired target temperature.
96
+ """
97
+ if temperature != self.target_temperature:
98
+ LOG.info("%s Setting temperature to %s", self._tag, temperature)
99
+ await self.set_attribute("target_temperature", str(temperature))