aiohomematic 2025.9.1__py3-none-any.whl → 2025.9.2__py3-none-any.whl

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.

Potentially problematic release.


This version of aiohomematic might be problematic. Click here for more details.

Files changed (55) hide show
  1. aiohomematic/caches/dynamic.py +1 -6
  2. aiohomematic/central/__init__.py +34 -23
  3. aiohomematic/central/xml_rpc_server.py +1 -1
  4. aiohomematic/client/__init__.py +35 -29
  5. aiohomematic/client/json_rpc.py +44 -12
  6. aiohomematic/client/xml_rpc.py +53 -20
  7. aiohomematic/const.py +2 -2
  8. aiohomematic/decorators.py +56 -21
  9. aiohomematic/model/__init__.py +1 -1
  10. aiohomematic/model/calculated/__init__.py +1 -1
  11. aiohomematic/model/calculated/climate.py +1 -1
  12. aiohomematic/model/calculated/data_point.py +2 -2
  13. aiohomematic/model/calculated/operating_voltage_level.py +7 -21
  14. aiohomematic/model/calculated/support.py +20 -0
  15. aiohomematic/model/custom/__init__.py +1 -1
  16. aiohomematic/model/custom/climate.py +18 -18
  17. aiohomematic/model/custom/cover.py +1 -1
  18. aiohomematic/model/custom/data_point.py +1 -1
  19. aiohomematic/model/custom/light.py +1 -1
  20. aiohomematic/model/custom/lock.py +1 -1
  21. aiohomematic/model/custom/siren.py +1 -1
  22. aiohomematic/model/custom/switch.py +1 -1
  23. aiohomematic/model/custom/valve.py +1 -1
  24. aiohomematic/model/data_point.py +18 -18
  25. aiohomematic/model/device.py +21 -20
  26. aiohomematic/model/event.py +3 -8
  27. aiohomematic/model/generic/__init__.py +1 -1
  28. aiohomematic/model/generic/binary_sensor.py +1 -1
  29. aiohomematic/model/generic/button.py +1 -1
  30. aiohomematic/model/generic/data_point.py +3 -5
  31. aiohomematic/model/generic/number.py +1 -1
  32. aiohomematic/model/generic/select.py +1 -1
  33. aiohomematic/model/generic/sensor.py +1 -1
  34. aiohomematic/model/generic/switch.py +4 -4
  35. aiohomematic/model/generic/text.py +1 -1
  36. aiohomematic/model/hub/binary_sensor.py +1 -1
  37. aiohomematic/model/hub/button.py +2 -2
  38. aiohomematic/model/hub/data_point.py +4 -7
  39. aiohomematic/model/hub/number.py +1 -1
  40. aiohomematic/model/hub/select.py +2 -2
  41. aiohomematic/model/hub/sensor.py +1 -1
  42. aiohomematic/model/hub/switch.py +3 -3
  43. aiohomematic/model/hub/text.py +1 -1
  44. aiohomematic/model/support.py +1 -40
  45. aiohomematic/model/update.py +5 -4
  46. aiohomematic/property_decorators.py +327 -0
  47. aiohomematic/support.py +70 -85
  48. {aiohomematic-2025.9.1.dist-info → aiohomematic-2025.9.2.dist-info}/METADATA +7 -5
  49. aiohomematic-2025.9.2.dist-info/RECORD +78 -0
  50. aiohomematic_support/client_local.py +5 -5
  51. aiohomematic/model/decorators.py +0 -194
  52. aiohomematic-2025.9.1.dist-info/RECORD +0 -78
  53. {aiohomematic-2025.9.1.dist-info → aiohomematic-2025.9.2.dist-info}/WHEEL +0 -0
  54. {aiohomematic-2025.9.1.dist-info → aiohomematic-2025.9.2.dist-info}/licenses/LICENSE +0 -0
  55. {aiohomematic-2025.9.1.dist-info → aiohomematic-2025.9.2.dist-info}/top_level.txt +0 -0
@@ -42,7 +42,8 @@ from aiohomematic.exceptions import (
42
42
  NoConnectionException,
43
43
  UnsupportedException,
44
44
  )
45
- from aiohomematic.support import extract_exc_args, get_tls_context
45
+ from aiohomematic.property_decorators import info_property
46
+ from aiohomematic.support import LogContextMixin, extract_exc_args, get_tls_context, log_boundary_error
46
47
 
47
48
  _LOGGER: Final = logging.getLogger(__name__)
48
49
 
@@ -83,7 +84,7 @@ _OS_ERROR_CODES: Final[dict[int, str]] = {
83
84
 
84
85
 
85
86
  # noinspection PyProtectedMember,PyUnresolvedReferences
86
- class XmlRpcProxy(xmlrpc.client.ServerProxy):
87
+ class XmlRpcProxy(xmlrpc.client.ServerProxy, LogContextMixin):
87
88
  """ServerProxy implementation with ThreadPoolExecutor when request is executing."""
88
89
 
89
90
  def __init__(
@@ -95,7 +96,7 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy):
95
96
  **kwargs: Any,
96
97
  ) -> None:
97
98
  """Initialize new proxy for server and get local ip."""
98
- self.interface_id: Final = interface_id
99
+ self._interface_id: Final = interface_id
99
100
  self._connection_state: Final = connection_state
100
101
  self._looper: Final = Looper()
101
102
  self._proxy_executor: Final = (
@@ -120,11 +121,21 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy):
120
121
  supported_methods.append(_XmlRpcMethod.PING)
121
122
  self._supported_methods = tuple(supported_methods)
122
123
 
124
+ @info_property(log_context=True)
125
+ def interface_id(self) -> str:
126
+ """Return the interface_id."""
127
+ return self._interface_id
128
+
123
129
  @property
124
130
  def supported_methods(self) -> tuple[str, ...]:
125
131
  """Return the supported methods."""
126
132
  return self._supported_methods
127
133
 
134
+ @info_property(log_context=True)
135
+ def tls(self) -> bool:
136
+ """Return tls."""
137
+ return self._tls
138
+
128
139
  async def __async_request(self, *args, **kwargs): # type: ignore[no-untyped-def]
129
140
  """Call method on server side."""
130
141
  parent = xmlrpc.client.ServerProxy
@@ -134,7 +145,7 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy):
134
145
  raise UnsupportedException(f"__ASYNC_REQUEST: method '{method} not supported by backend.")
135
146
 
136
147
  if method in _VALID_XMLRPC_COMMANDS_ON_NO_CONNECTION or not self._connection_state.has_issue(
137
- issuer=self, iid=self.interface_id
148
+ issuer=self, iid=self._interface_id
138
149
  ):
139
150
  args = _cleanup_args(*args)
140
151
  _LOGGER.debug("__ASYNC_REQUEST: %s", args)
@@ -148,35 +159,57 @@ class XmlRpcProxy(xmlrpc.client.ServerProxy):
148
159
  executor=self._proxy_executor,
149
160
  )
150
161
  )
151
- self._connection_state.remove_issue(issuer=self, iid=self.interface_id)
162
+ self._connection_state.remove_issue(issuer=self, iid=self._interface_id)
152
163
  return result
153
- raise NoConnectionException(f"No connection to {self.interface_id}")
164
+ raise NoConnectionException(f"No connection to {self._interface_id}")
154
165
  except BaseHomematicException:
155
166
  raise
156
167
  except SSLError as sslerr:
157
- message = f"SSLError on {self.interface_id}: {extract_exc_args(exc=sslerr)}"
168
+ message = f"SSLError on {self._interface_id}: {extract_exc_args(exc=sslerr)}"
169
+ level = logging.ERROR
158
170
  if sslerr.args[0] in _SSL_ERROR_CODES:
159
- _LOGGER.debug(message)
160
- else:
161
- _LOGGER.error(message)
171
+ message = (
172
+ f"{message} - {sslerr.args[0]}: {sslerr.args[1]}. "
173
+ f"Please check your configuration for {self._interface_id}."
174
+ )
175
+ if not self._connection_state.add_issue(issuer=self, iid=self._interface_id):
176
+ level = logging.DEBUG
177
+
178
+ log_boundary_error(
179
+ logger=_LOGGER,
180
+ boundary="xml-rpc",
181
+ action=str(args[0]),
182
+ err=sslerr,
183
+ level=level,
184
+ message=message,
185
+ log_context=self.log_context,
186
+ )
162
187
  raise NoConnectionException(message) from sslerr
163
188
  except OSError as oserr:
164
- message = f"OSError on {self.interface_id}: {extract_exc_args(exc=oserr)}"
165
- if oserr.args[0] in _OS_ERROR_CODES:
166
- if self._connection_state.add_issue(issuer=self, iid=self.interface_id):
167
- _LOGGER.error(message)
168
- else:
169
- _LOGGER.debug(message)
170
- else:
171
- _LOGGER.error(message)
189
+ message = f"OSError on {self._interface_id}: {extract_exc_args(exc=oserr)}"
190
+ level = (
191
+ logging.ERROR
192
+ if oserr.args[0] in _OS_ERROR_CODES
193
+ and not self._connection_state.add_issue(issuer=self, iid=self._interface_id)
194
+ else logging.DEBUG
195
+ )
196
+
197
+ log_boundary_error(
198
+ logger=_LOGGER,
199
+ boundary="xml-rpc",
200
+ action=str(args[0]),
201
+ err=oserr,
202
+ level=level,
203
+ log_context=self.log_context,
204
+ )
172
205
  raise NoConnectionException(message) from oserr
173
206
  except xmlrpc.client.Fault as flt:
174
- ctx = RpcContext(protocol="xml-rpc", method=str(args[0]), interface=self.interface_id)
207
+ ctx = RpcContext(protocol="xml-rpc", method=str(args[0]), interface=self._interface_id)
175
208
  raise map_xmlrpc_fault(code=flt.faultCode, fault_string=flt.faultString, ctx=ctx) from flt
176
209
  except TypeError as terr:
177
210
  raise ClientException(terr) from terr
178
211
  except xmlrpc.client.ProtocolError as perr:
179
- if not self._connection_state.has_issue(issuer=self, iid=self.interface_id):
212
+ if not self._connection_state.has_issue(issuer=self, iid=self._interface_id):
180
213
  if perr.errmsg == "Unauthorized":
181
214
  raise AuthFailure(perr) from perr
182
215
  raise NoConnectionException(perr.errmsg) from perr
aiohomematic/const.py CHANGED
@@ -19,7 +19,7 @@ import sys
19
19
  from types import MappingProxyType
20
20
  from typing import Any, Final, NamedTuple, Required, TypeAlias, TypedDict
21
21
 
22
- VERSION: Final = "2025.9.1"
22
+ VERSION: Final = "2025.9.2"
23
23
 
24
24
  # Detect test speedup mode via environment
25
25
  _TEST_SPEEDUP: Final = (
@@ -856,7 +856,7 @@ __all__ = tuple(
856
856
  )
857
857
  and (
858
858
  getattr(obj, "__module__", __name__) == __name__
859
- if not isinstance(obj, (int, float, str, bytes, tuple, frozenset, dict))
859
+ if not isinstance(obj, int | float | str | bytes | tuple | frozenset | dict)
860
860
  else True
861
861
  )
862
862
  )
@@ -13,15 +13,12 @@ from functools import wraps
13
13
  import inspect
14
14
  import logging
15
15
  from time import monotonic
16
- from typing import Any, Final, ParamSpec, TypeVar, cast
16
+ from typing import Any, Final, cast, overload
17
17
  from weakref import WeakKeyDictionary
18
18
 
19
19
  from aiohomematic.context import IN_SERVICE_VAR
20
20
  from aiohomematic.exceptions import BaseHomematicException
21
- from aiohomematic.support import build_log_context_from_obj, extract_exc_args
22
-
23
- P = ParamSpec("P")
24
- R = TypeVar("R")
21
+ from aiohomematic.support import LogContextMixin, log_boundary_error
25
22
 
26
23
  _LOGGER_PERFORMANCE: Final = logging.getLogger(f"{__package__}.performance")
27
24
 
@@ -30,26 +27,59 @@ _LOGGER_PERFORMANCE: Final = logging.getLogger(f"{__package__}.performance")
30
27
  _SERVICE_CALLS_CACHE: WeakKeyDictionary[type, tuple[str, ...]] = WeakKeyDictionary()
31
28
 
32
29
 
33
- def inspector( # noqa: C901
30
+ @overload
31
+ def inspector[**P, R](
32
+ func: Callable[P, R],
33
+ /,
34
+ *,
35
+ log_level: int = ...,
36
+ re_raise: bool = ...,
37
+ no_raise_return: Any = ...,
38
+ measure_performance: bool = ...,
39
+ ) -> Callable[P, R]: ...
40
+
41
+
42
+ @overload
43
+ def inspector[**P, R](
44
+ func: None = ...,
45
+ /,
46
+ *,
47
+ log_level: int = ...,
48
+ re_raise: bool = ...,
49
+ no_raise_return: Any = ...,
50
+ measure_performance: bool = ...,
51
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
52
+
53
+
54
+ def inspector[**P, R]( # noqa: C901
55
+ func: Callable[P, R] | None = None,
56
+ /,
57
+ *,
34
58
  log_level: int = logging.ERROR,
35
59
  re_raise: bool = True,
36
60
  no_raise_return: Any = None,
37
61
  measure_performance: bool = False,
38
- ) -> Callable[[Callable[P, R]], Callable[P, R]]:
62
+ ) -> Callable[[Callable[P, R]], Callable[P, R]] | Callable[P, R]:
39
63
  """
40
64
  Support with exception handling and performance measurement.
41
65
 
42
66
  A decorator that works for both synchronous and asynchronous functions,
43
67
  providing common functionality such as exception handling and performance measurement.
44
68
 
69
+ Can be used both with and without parameters:
70
+ - @inspector
71
+ - @inspector(log_level=logging.ERROR, re_raise=True, ...)
72
+
45
73
  Args:
74
+ func: The function to decorate when used without parameters.
46
75
  log_level: Logging level for exceptions.
47
76
  re_raise: Whether to re-raise exceptions.
48
77
  no_raise_return: Value to return when an exception is caught and not re-raised.
49
78
  measure_performance: Whether to measure function execution time.
50
79
 
51
80
  Returns:
52
- A decorator that wraps sync or async functions.
81
+ Either the decorated function (when used without parameters) or
82
+ a decorator that wraps sync or async functions (when used with parameters).
53
83
 
54
84
  """
55
85
 
@@ -71,16 +101,16 @@ def inspector( # noqa: C901
71
101
  """Handle exceptions for decorated functions with structured logging."""
72
102
  if not is_sub_service_call and log_level > logging.NOTSET:
73
103
  logger = logging.getLogger(func.__module__)
74
- extra = {
75
- "err_type": exc.__class__.__name__,
76
- "err": extract_exc_args(exc=exc),
77
- "function": func.__name__,
78
- **build_log_context_from_obj(obj=context_obj),
79
- }
80
- if log_level >= logging.ERROR:
81
- logger.exception("service_error", extra=extra)
82
- else:
83
- logger.log(level=log_level, msg="service_error", extra=extra)
104
+ log_context = context_obj.log_context if isinstance(context_obj, LogContextMixin) else None
105
+ # Reuse centralized boundary logging to ensure consistent 'extra' structure
106
+ log_boundary_error(
107
+ logger=logger,
108
+ boundary="service",
109
+ action=func.__name__,
110
+ err=exc,
111
+ level=log_level,
112
+ log_context=log_context,
113
+ )
84
114
  if re_raise or not is_homematic:
85
115
  raise exc
86
116
  return cast(R, no_raise_return)
@@ -168,10 +198,15 @@ def inspector( # noqa: C901
168
198
  setattr(wrap_sync_function, "ha_service", True)
169
199
  return wrap_sync_function
170
200
 
201
+ # If used without parameters: @inspector
202
+ if func is not None:
203
+ return create_wrapped_decorator(func)
204
+
205
+ # If used with parameters: @inspector(...)
171
206
  return create_wrapped_decorator
172
207
 
173
208
 
174
- def _log_performance_message(func: Callable, start: float, *args: P.args, **kwargs: P.kwargs) -> None: # type: ignore[valid-type]
209
+ def _log_performance_message[**P](func: Callable[P, Any], start: float, *args: P.args, **kwargs: P.kwargs) -> None:
175
210
  delta = monotonic() - start
176
211
  caller = str(args[0]) if len(args) > 0 else ""
177
212
 
@@ -245,8 +280,8 @@ def measure_execution_time[CallableT: Callable[..., Any]](func: CallableT) -> Ca
245
280
  _log_performance_message(func, start, *args, **kwargs)
246
281
 
247
282
  if inspect.iscoroutinefunction(func):
248
- return async_measure_wrapper # type: ignore[return-value]
249
- return measure_wrapper # type: ignore[return-value]
283
+ return cast(CallableT, async_measure_wrapper)
284
+ return cast(CallableT, measure_wrapper)
250
285
 
251
286
 
252
287
  # Define public API for this module
@@ -45,7 +45,7 @@ _ALLOWED_INTERNAL_PARAMETERS: Final[tuple[Parameter, ...]] = (Parameter.DIRECTIO
45
45
  _LOGGER: Final = logging.getLogger(__name__)
46
46
 
47
47
 
48
- @inspector()
48
+ @inspector
49
49
  def create_data_points_and_events(device: hmd.Device) -> None:
50
50
  """Create the data points associated to this device."""
51
51
  for channel in device.channels.values():
@@ -59,7 +59,7 @@ _CALCULATED_DATA_POINTS: Final = (ApparentTemperature, DewPoint, FrostPoint, Ope
59
59
  _LOGGER: Final = logging.getLogger(__name__)
60
60
 
61
61
 
62
- @inspector()
62
+ @inspector
63
63
  def create_calculated_data_points(channel: hmd.Channel) -> None:
64
64
  """Decides which data point category should be used, and creates the required data points."""
65
65
  for dp in _CALCULATED_DATA_POINTS:
@@ -16,8 +16,8 @@ from aiohomematic.model.calculated.support import (
16
16
  calculate_frost_point,
17
17
  calculate_vapor_concentration,
18
18
  )
19
- from aiohomematic.model.decorators import state_property
20
19
  from aiohomematic.model.generic import DpSensor
20
+ from aiohomematic.property_decorators import state_property
21
21
  from aiohomematic.support import element_matches_key
22
22
 
23
23
  _LOGGER: Final = logging.getLogger(__name__)
@@ -23,7 +23,6 @@ from aiohomematic.const import (
23
23
  from aiohomematic.model import device as hmd
24
24
  from aiohomematic.model.custom import definition as hmed
25
25
  from aiohomematic.model.data_point import BaseDataPoint, NoneTypeDataPoint
26
- from aiohomematic.model.decorators import cached_slot_property, config_property, state_property
27
26
  from aiohomematic.model.generic import data_point as hmge
28
27
  from aiohomematic.model.support import (
29
28
  DataPointNameData,
@@ -33,6 +32,7 @@ from aiohomematic.model.support import (
33
32
  generate_unique_id,
34
33
  get_data_point_name_data,
35
34
  )
35
+ from aiohomematic.property_decorators import cached_slot_property, config_property, state_property
36
36
 
37
37
  _LOGGER: Final = logging.getLogger(__name__)
38
38
 
@@ -306,7 +306,7 @@ class CalculatedDataPoint[ParameterT: GenericParameterType](BaseDataPoint):
306
306
  @property
307
307
  def _should_fire_data_point_updated_callback(self) -> bool:
308
308
  """Check if a data point has been updated or refreshed."""
309
- if self.fired_recently:
309
+ if self.fired_recently: # pylint: disable=using-constant-test
310
310
  return False
311
311
 
312
312
  if (relevant_values_data_point := self._relevant_values_data_points) is not None and len(
@@ -13,8 +13,9 @@ from typing import Any, Final
13
13
  from aiohomematic.const import CalulatedParameter, DataPointCategory, Parameter, ParameterType, ParamsetKey
14
14
  from aiohomematic.model import device as hmd
15
15
  from aiohomematic.model.calculated.data_point import CalculatedDataPoint
16
- from aiohomematic.model.decorators import state_property
16
+ from aiohomematic.model.calculated.support import calculate_operating_voltage_level
17
17
  from aiohomematic.model.generic import DpFloat, DpSensor
18
+ from aiohomematic.property_decorators import state_property
18
19
  from aiohomematic.support import element_matches_key, extract_exc_args
19
20
 
20
21
  _BATTERY_QTY: Final = "Battery Qty"
@@ -139,32 +140,17 @@ class OperatingVoltageLevel[SensorT: float | None](CalculatedDataPoint[SensorT])
139
140
  def value(self) -> float | None:
140
141
  """Return the value."""
141
142
  try:
142
- if (low_bat_limit := self._low_bat_limit) is None or self._voltage_max is None:
143
- return None
144
- if self._dp_operating_voltage and self._dp_operating_voltage.value is not None:
145
- return max(
146
- 0,
147
- min(
148
- 100,
149
- float(
150
- round(
151
- (
152
- (float(self._dp_operating_voltage.value) - low_bat_limit)
153
- / (self._voltage_max - low_bat_limit)
154
- * 100
155
- ),
156
- 1,
157
- )
158
- ),
159
- ),
160
- )
143
+ return calculate_operating_voltage_level(
144
+ operating_voltage=self._dp_operating_voltage.value,
145
+ low_bat_limit=self._low_bat_limit_default,
146
+ voltage_max=self._voltage_max,
147
+ )
161
148
  except Exception as exc:
162
149
  _LOGGER.debug(
163
150
  "OperatingVoltageLevel: Failed to calculate sensor for %s: %s",
164
151
  self._channel.name,
165
152
  extract_exc_args(exc=exc),
166
153
  )
167
- return None
168
154
  return None
169
155
 
170
156
  @property
@@ -174,3 +174,23 @@ def calculate_frost_point(temperature: float, humidity: int) -> float | None:
174
174
  extract_exc_args(exc=verr),
175
175
  )
176
176
  return None
177
+
178
+
179
+ def calculate_operating_voltage_level(
180
+ operating_voltage: float | None, low_bat_limit: float | None, voltage_max: float | None
181
+ ) -> float | None:
182
+ """Return the operating voltage level."""
183
+ if operating_voltage is None or low_bat_limit is None or voltage_max is None:
184
+ return None
185
+ return max(
186
+ 0,
187
+ min(
188
+ 100,
189
+ float(
190
+ round(
191
+ ((float(operating_voltage) - low_bat_limit) / (voltage_max - low_bat_limit) * 100),
192
+ 1,
193
+ )
194
+ ),
195
+ ),
196
+ )
@@ -151,7 +151,7 @@ __all__ = [
151
151
  _LOGGER: Final = logging.getLogger(__name__)
152
152
 
153
153
 
154
- @inspector()
154
+ @inspector
155
155
  def create_custom_data_points(device: hmd.Device) -> None:
156
156
  """Decides which data point category should be used, and creates the required data points."""
157
157
 
@@ -26,8 +26,8 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
26
26
  from aiohomematic.model.custom.data_point import CustomDataPoint
27
27
  from aiohomematic.model.custom.support import CustomConfig
28
28
  from aiohomematic.model.data_point import CallParameterCollector, bind_collector
29
- from aiohomematic.model.decorators import config_property, state_property
30
29
  from aiohomematic.model.generic import DpAction, DpBinarySensor, DpFloat, DpInteger, DpSelect, DpSensor, DpSwitch
30
+ from aiohomematic.property_decorators import config_property, state_property
31
31
 
32
32
  _LOGGER: Final = logging.getLogger(__name__)
33
33
 
@@ -351,15 +351,15 @@ class BaseCustomDpClimate(CustomDataPoint):
351
351
  async def set_profile(self, profile: ClimateProfile, collector: CallParameterCollector | None = None) -> None:
352
352
  """Set new profile."""
353
353
 
354
- @inspector()
354
+ @inspector
355
355
  async def enable_away_mode_by_calendar(self, start: datetime, end: datetime, away_temperature: float) -> None:
356
356
  """Enable the away mode by calendar on thermostat."""
357
357
 
358
- @inspector()
358
+ @inspector
359
359
  async def enable_away_mode_by_duration(self, hours: int, away_temperature: float) -> None:
360
360
  """Enable the away mode by duration on thermostat."""
361
361
 
362
- @inspector()
362
+ @inspector
363
363
  async def disable_away_mode(self) -> None:
364
364
  """Disable the away mode on thermostat."""
365
365
 
@@ -375,7 +375,7 @@ class BaseCustomDpClimate(CustomDataPoint):
375
375
  return True
376
376
  return super().is_state_change(**kwargs)
377
377
 
378
- @inspector()
378
+ @inspector
379
379
  async def copy_schedule(self, target_climate_data_point: BaseCustomDpClimate) -> None:
380
380
  """Copy schedule to target device."""
381
381
 
@@ -388,7 +388,7 @@ class BaseCustomDpClimate(CustomDataPoint):
388
388
  values=raw_schedule,
389
389
  )
390
390
 
391
- @inspector()
391
+ @inspector
392
392
  async def copy_schedule_profile(
393
393
  self,
394
394
  source_profile: ScheduleProfile,
@@ -418,7 +418,7 @@ class BaseCustomDpClimate(CustomDataPoint):
418
418
  do_validate=False,
419
419
  )
420
420
 
421
- @inspector()
421
+ @inspector
422
422
  async def get_schedule_profile(self, profile: ScheduleProfile) -> PROFILE_DICT:
423
423
  """Return a schedule by climate profile."""
424
424
  if not self._supports_schedule:
@@ -426,7 +426,7 @@ class BaseCustomDpClimate(CustomDataPoint):
426
426
  schedule_data = await self._get_schedule_profile(profile=profile)
427
427
  return schedule_data.get(profile, {})
428
428
 
429
- @inspector()
429
+ @inspector
430
430
  async def get_schedule_profile_weekday(self, profile: ScheduleProfile, weekday: ScheduleWeekday) -> WEEKDAY_DICT:
431
431
  """Return a schedule by climate profile."""
432
432
  if not self._supports_schedule:
@@ -478,7 +478,7 @@ class BaseCustomDpClimate(CustomDataPoint):
478
478
 
479
479
  return schedule_data
480
480
 
481
- @inspector()
481
+ @inspector
482
482
  async def set_schedule_profile(
483
483
  self, profile: ScheduleProfile, profile_data: PROFILE_DICT, do_validate: bool = True
484
484
  ) -> None:
@@ -518,7 +518,7 @@ class BaseCustomDpClimate(CustomDataPoint):
518
518
  values=_get_raw_schedule_paramset(schedule_data=schedule_data),
519
519
  )
520
520
 
521
- @inspector()
521
+ @inspector
522
522
  async def set_simple_schedule_profile(
523
523
  self,
524
524
  profile: ScheduleProfile,
@@ -531,7 +531,7 @@ class BaseCustomDpClimate(CustomDataPoint):
531
531
  )
532
532
  await self.set_schedule_profile(profile=profile, profile_data=profile_data)
533
533
 
534
- @inspector()
534
+ @inspector
535
535
  async def set_schedule_profile_weekday(
536
536
  self,
537
537
  profile: ScheduleProfile,
@@ -559,7 +559,7 @@ class BaseCustomDpClimate(CustomDataPoint):
559
559
  values=_get_raw_schedule_paramset(schedule_data=schedule_data),
560
560
  )
561
561
 
562
- @inspector()
562
+ @inspector
563
563
  async def set_simple_schedule_profile_weekday(
564
564
  self,
565
565
  profile: ScheduleProfile,
@@ -844,7 +844,7 @@ class CustomDpRfThermostat(BaseCustomDpClimate):
844
844
  value=_HM_WEEK_PROFILE_POINTERS_TO_NAMES[profile_idx], collector=collector
845
845
  )
846
846
 
847
- @inspector()
847
+ @inspector
848
848
  async def enable_away_mode_by_calendar(self, start: datetime, end: datetime, away_temperature: float) -> None:
849
849
  """Enable the away mode by calendar on thermostat."""
850
850
  await self._client.set_value(
@@ -854,14 +854,14 @@ class CustomDpRfThermostat(BaseCustomDpClimate):
854
854
  value=_party_mode_code(start=start, end=end, away_temperature=away_temperature),
855
855
  )
856
856
 
857
- @inspector()
857
+ @inspector
858
858
  async def enable_away_mode_by_duration(self, hours: int, away_temperature: float) -> None:
859
859
  """Enable the away mode by duration on thermostat."""
860
860
  start = datetime.now() - timedelta(minutes=10)
861
861
  end = datetime.now() + timedelta(hours=hours)
862
862
  await self.enable_away_mode_by_calendar(start=start, end=end, away_temperature=away_temperature)
863
863
 
864
- @inspector()
864
+ @inspector
865
865
  async def disable_away_mode(self) -> None:
866
866
  """Disable the away mode on thermostat."""
867
867
  start = datetime.now() - timedelta(hours=11)
@@ -1095,7 +1095,7 @@ class CustomDpIpThermostat(BaseCustomDpClimate):
1095
1095
  if profile_idx := self._profiles.get(profile):
1096
1096
  await self._dp_active_profile.send_value(value=profile_idx, collector=collector)
1097
1097
 
1098
- @inspector()
1098
+ @inspector
1099
1099
  async def enable_away_mode_by_calendar(self, start: datetime, end: datetime, away_temperature: float) -> None:
1100
1100
  """Enable the away mode by calendar on thermostat."""
1101
1101
  await self._client.put_paramset(
@@ -1109,14 +1109,14 @@ class CustomDpIpThermostat(BaseCustomDpClimate):
1109
1109
  },
1110
1110
  )
1111
1111
 
1112
- @inspector()
1112
+ @inspector
1113
1113
  async def enable_away_mode_by_duration(self, hours: int, away_temperature: float) -> None:
1114
1114
  """Enable the away mode by duration on thermostat."""
1115
1115
  start = datetime.now() - timedelta(minutes=10)
1116
1116
  end = datetime.now() + timedelta(hours=hours)
1117
1117
  await self.enable_away_mode_by_calendar(start=start, end=end, away_temperature=away_temperature)
1118
1118
 
1119
- @inspector()
1119
+ @inspector
1120
1120
  async def disable_away_mode(self) -> None:
1121
1121
  """Disable the away mode on thermostat."""
1122
1122
  await self._client.put_paramset(
@@ -18,8 +18,8 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
18
18
  from aiohomematic.model.custom.data_point import CustomDataPoint
19
19
  from aiohomematic.model.custom.support import CustomConfig, ExtendedConfig
20
20
  from aiohomematic.model.data_point import CallParameterCollector, bind_collector
21
- from aiohomematic.model.decorators import state_property
22
21
  from aiohomematic.model.generic import DpAction, DpFloat, DpSelect, DpSensor
22
+ from aiohomematic.property_decorators import state_property
23
23
 
24
24
  _LOGGER: Final = logging.getLogger(__name__)
25
25
 
@@ -15,7 +15,6 @@ from aiohomematic.model.custom import definition as hmed
15
15
  from aiohomematic.model.custom.const import CDPD, DeviceProfile, Field
16
16
  from aiohomematic.model.custom.support import CustomConfig
17
17
  from aiohomematic.model.data_point import BaseDataPoint, NoneTypeDataPoint
18
- from aiohomematic.model.decorators import state_property
19
18
  from aiohomematic.model.generic import data_point as hmge
20
19
  from aiohomematic.model.support import (
21
20
  DataPointNameData,
@@ -24,6 +23,7 @@ from aiohomematic.model.support import (
24
23
  check_channel_is_the_only_primary_channel,
25
24
  get_custom_data_point_name,
26
25
  )
26
+ from aiohomematic.property_decorators import state_property
27
27
  from aiohomematic.support import get_channel_address
28
28
 
29
29
  _LOGGER: Final = logging.getLogger(__name__)
@@ -16,8 +16,8 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
16
16
  from aiohomematic.model.custom.data_point import CustomDataPoint
17
17
  from aiohomematic.model.custom.support import CustomConfig, ExtendedConfig
18
18
  from aiohomematic.model.data_point import CallParameterCollector, bind_collector
19
- from aiohomematic.model.decorators import state_property
20
19
  from aiohomematic.model.generic import DpAction, DpFloat, DpInteger, DpSelect, DpSensor, GenericDataPoint
20
+ from aiohomematic.property_decorators import state_property
21
21
 
22
22
  _DIMMER_OFF: Final = 0.0
23
23
  _EFFECT_OFF: Final = "Off"
@@ -15,8 +15,8 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
15
15
  from aiohomematic.model.custom.data_point import CustomDataPoint
16
16
  from aiohomematic.model.custom.support import CustomConfig, ExtendedConfig
17
17
  from aiohomematic.model.data_point import CallParameterCollector, bind_collector
18
- from aiohomematic.model.decorators import state_property
19
18
  from aiohomematic.model.generic import DpAction, DpSensor, DpSwitch
19
+ from aiohomematic.property_decorators import state_property
20
20
 
21
21
 
22
22
  class _LockActivity(StrEnum):
@@ -17,8 +17,8 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
17
17
  from aiohomematic.model.custom.data_point import CustomDataPoint
18
18
  from aiohomematic.model.custom.support import CustomConfig
19
19
  from aiohomematic.model.data_point import CallParameterCollector, bind_collector
20
- from aiohomematic.model.decorators import state_property
21
20
  from aiohomematic.model.generic import DpAction, DpBinarySensor, DpSensor
21
+ from aiohomematic.property_decorators import state_property
22
22
 
23
23
  _SMOKE_DETECTOR_ALARM_STATUS_IDLE_OFF: Final = "IDLE_OFF"
24
24
 
@@ -16,8 +16,8 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
16
16
  from aiohomematic.model.custom.data_point import CustomDataPoint
17
17
  from aiohomematic.model.custom.support import CustomConfig, ExtendedConfig
18
18
  from aiohomematic.model.data_point import CallParameterCollector, bind_collector
19
- from aiohomematic.model.decorators import state_property
20
19
  from aiohomematic.model.generic import DpAction, DpBinarySensor, DpSwitch
20
+ from aiohomematic.property_decorators import state_property
21
21
 
22
22
  _LOGGER: Final = logging.getLogger(__name__)
23
23
 
@@ -16,8 +16,8 @@ from aiohomematic.model.custom.const import DeviceProfile, Field
16
16
  from aiohomematic.model.custom.data_point import CustomDataPoint
17
17
  from aiohomematic.model.custom.support import CustomConfig
18
18
  from aiohomematic.model.data_point import CallParameterCollector, bind_collector
19
- from aiohomematic.model.decorators import state_property
20
19
  from aiohomematic.model.generic import DpAction, DpBinarySensor, DpSwitch
20
+ from aiohomematic.property_decorators import state_property
21
21
 
22
22
  _LOGGER: Final = logging.getLogger(__name__)
23
23