blebox-uniapi 2.5.4__tar.gz → 2.5.6__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 (52) hide show
  1. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/CONTRIBUTING.rst +8 -4
  2. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/MANIFEST.in +0 -1
  3. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/PKG-INFO +17 -5
  4. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/__init__.py +1 -1
  5. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/box.py +6 -0
  6. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/box_types.py +41 -3
  7. blebox_uniapi-2.5.6/blebox_uniapi/button.py +141 -0
  8. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/cover.py +65 -10
  9. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/error.py +0 -2
  10. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/sensor.py +4 -0
  11. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/session.py +2 -2
  12. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/PKG-INFO +18 -6
  13. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/SOURCES.txt +13 -1
  14. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/setup.cfg +2 -1
  15. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/setup.py +2 -1
  16. blebox_uniapi-2.5.6/tests/test_box.py +236 -0
  17. blebox_uniapi-2.5.6/tests/test_box_types.py +80 -0
  18. blebox_uniapi-2.5.6/tests/test_button.py +266 -0
  19. blebox_uniapi-2.5.6/tests/test_climate.py +386 -0
  20. blebox_uniapi-2.5.6/tests/test_cover.py +962 -0
  21. blebox_uniapi-2.5.6/tests/test_light.py +1369 -0
  22. blebox_uniapi-2.5.6/tests/test_light_unit.py +141 -0
  23. blebox_uniapi-2.5.6/tests/test_sensor.py +477 -0
  24. blebox_uniapi-2.5.6/tests/test_session.py +233 -0
  25. blebox_uniapi-2.5.6/tests/test_switch.py +447 -0
  26. blebox_uniapi-2.5.6/tests/test_unknown.py +59 -0
  27. blebox_uniapi-2.5.6/tests/test_update.py +70 -0
  28. blebox_uniapi-2.5.4/blebox_uniapi/button.py +0 -75
  29. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/HISTORY.rst +0 -0
  30. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/LICENSE +0 -0
  31. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/README.rst +0 -0
  32. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/binary_sensor.py +0 -0
  33. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/climate.py +0 -0
  34. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/feature.py +0 -0
  35. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/jfollow.py +0 -0
  36. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/light.py +0 -0
  37. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/switch.py +0 -0
  38. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/update.py +0 -0
  39. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/dependency_links.txt +0 -0
  40. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/not-zip-safe +0 -0
  41. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/requires.txt +0 -0
  42. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/top_level.txt +0 -0
  43. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/Makefile +0 -0
  44. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/authors.rst +0 -0
  45. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/conf.py +0 -0
  46. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/contributing.rst +0 -0
  47. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/history.rst +0 -0
  48. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/index.rst +0 -0
  49. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/installation.rst +0 -0
  50. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/make.bat +0 -0
  51. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/readme.rst +0 -0
  52. {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/usage.rst +0 -0
@@ -122,7 +122,11 @@ Make sure all your changes are committed (including an entry in HISTORY.rst).
122
122
  Then run::
123
123
 
124
124
  $ bump2version patch # possible: major / minor / patch
125
- $ git push
126
- $ git push --tags
127
-
128
- Travis will then deploy to PyPI if tests pass.
125
+ $ git push --follow-tags
126
+
127
+ bump2version bumps the version in setup.py, blebox_uniapi/__init__.py and
128
+ setup.cfg, commits the change and creates a matching git tag (e.g. v2.6.0).
129
+ Pushing the tag triggers the "Publish to PyPI" GitHub Actions workflow
130
+ (.github/workflows/release.yml), which first runs lint + tests, then builds and
131
+ uploads to PyPI via Trusted Publishing (OIDC) from the blebox/blebox_uniapi repo.
132
+ The publish step only runs if the tests pass.
@@ -1,4 +1,3 @@
1
- include AUTHORS.rst
2
1
  include CONTRIBUTING.rst
3
2
  include HISTORY.rst
4
3
  include LICENSE
@@ -1,22 +1,36 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: blebox_uniapi
3
- Version: 2.5.4
3
+ Version: 2.5.6
4
4
  Summary: Python API for accessing BleBox smart home devices
5
5
  Home-page: https://github.com/blebox/blebox_uniapi
6
6
  Author: BleBox
7
7
  Author-email: opensource@blebox.eu
8
8
  License: Apache Software License 2.0
9
9
  Keywords: blebox_uniapi
10
- Platform: UNKNOWN
11
10
  Classifier: Development Status :: 2 - Pre-Alpha
12
11
  Classifier: Intended Audience :: Developers
13
12
  Classifier: License :: OSI Approved :: Apache Software License
14
13
  Classifier: Natural Language :: English
15
14
  Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
17
  Requires-Python: >=3.9
18
18
  Description-Content-Type: text/x-rst
19
19
  License-File: LICENSE
20
+ Requires-Dist: aiohttp>=3
21
+ Requires-Dist: jmespath>1.0.0
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: keywords
29
+ Dynamic: license
30
+ Dynamic: license-file
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
20
34
 
21
35
  ====================
22
36
  BleBox Python UniAPI
@@ -227,5 +241,3 @@ History
227
241
  ------------------
228
242
 
229
243
  * First release on PyPI.
230
-
231
-
@@ -2,4 +2,4 @@
2
2
 
3
3
  __author__ = """BleBox"""
4
4
  __email__ = "opensource@blebox.eu"
5
- __version__ = "2.5.4"
5
+ __version__ = "2.5.6"
@@ -111,6 +111,7 @@ class Box:
111
111
 
112
112
  self._data_path = config["api_path"]
113
113
  self._type = type
114
+ self._product = product
114
115
  self._unique_id = unique_id
115
116
  self._name = name
116
117
  self._address = address
@@ -212,6 +213,11 @@ class Box:
212
213
  def type(self) -> str:
213
214
  return self._type
214
215
 
216
+ @property
217
+ def product(self) -> str:
218
+ """Product name for informational purpose only - do not use it in any application logic."""
219
+ return self._product
220
+
215
221
  @property
216
222
  def unique_id(self) -> Any:
217
223
  return self._unique_id
@@ -1,3 +1,4 @@
1
+ from .button import GateBoxSecondOutput, TvLift
1
2
  from .cover import Gate, GateBox, GateBoxB, Shutter
2
3
  from typing import Union, Any
3
4
 
@@ -50,9 +51,8 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
50
51
  "set": lambda command: ("GET", f"/s/c/{command}")
51
52
  }, # dictionary with interaction methods
52
53
  "buttons": [
53
- "tvLift",
54
- {"lift": ""},
55
- ], # key used to set platform, list elements used in cls init, e.g. [<alias>, {"path": "state_value"}]
54
+ ["tvLift", {}, TvLift],
55
+ ], # key used to set platform, list elements used in cls init, e.g. [<alias>, {"path": "state_value"}, <optional type class>]
56
56
  }
57
57
  },
58
58
  # airSensor
@@ -135,11 +135,15 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
135
135
  {
136
136
  "position": "gate.currentPos",
137
137
  "gate_type": "gate.gateType",
138
+ "extra_button_type": "gate.extraButtonType",
138
139
  },
139
140
  "gatebox",
140
141
  GateBoxB,
141
142
  ]
142
143
  ],
144
+ "buttons": [
145
+ ["second_output", {}, GateBoxSecondOutput],
146
+ ],
143
147
  },
144
148
  },
145
149
  # gateController
@@ -380,6 +384,36 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
380
384
  ]
381
385
  ],
382
386
  },
387
+ 20220505: {
388
+ "api_path": "/state/extended",
389
+ "extended_state_path": "/state/extended",
390
+ "api": {
391
+ # note: old control api (i.e. /s/0, /s/1, /s/2) still supported but
392
+ # now deprecated. switchBox has now API consistent with switchBoxD
393
+ "on": lambda x=None: ("GET", f"/s/{x}/1", None),
394
+ "off": lambda x=None: ("GET", f"/s/{x}/0", None),
395
+ },
396
+ "switches": [
397
+ [
398
+ "relay",
399
+ {"state": lambda x: f"relays[?relay==`{x}`]|[0]|state"},
400
+ "relay",
401
+ ]
402
+ ],
403
+ "sensors": [
404
+ [
405
+ "switchBox.energy",
406
+ {
407
+ # note: switchbox/switchboxD sensors are currently not indexed (singletons)
408
+ "powerConsumption": lambda x: "powerMeasuring.powerConsumption[0]|value ",
409
+ "activePower": lambda x: "sensors[?type == 'activePower']|[0]|value",
410
+ "voltage": lambda x: "sensors[?type == 'voltage']|[0]|value",
411
+ "periodS": "powerMeasuring.powerConsumption[0]|periodS",
412
+ "measurement_enabled": "powerMeasuring.enabled",
413
+ },
414
+ ]
415
+ ],
416
+ },
383
417
  },
384
418
  # switchBoxD
385
419
  "switchBoxD": {
@@ -724,11 +758,15 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
724
758
  "reactivePower": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reactivePower']|[0]|value",
725
759
  "reverseActiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reverseActiveEnergy']|[0]|value",
726
760
  "forwardActiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'forwardActiveEnergy']|[0]|value",
761
+ "forwardReactiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'forwardReactiveEnergy']|[0]|value",
762
+ "reverseReactiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reverseReactiveEnergy']|[0]|value",
727
763
  "illuminance": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'illuminance']|[0]|value",
728
764
  "temperature": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'temperature']|[0]|value",
729
765
  "wind": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'wind']|[0]|value",
730
766
  "humidity": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'humidity']|[0]|value",
731
767
  "openStatus": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'openStatus']|[0]|value",
768
+ "co2": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2']|[0]|value",
769
+ "co2Definition": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2Definition']|[0]|value",
732
770
  },
733
771
  ]
734
772
  ],
@@ -0,0 +1,141 @@
1
+ from .cover import GateBoxControlType, GateBoxExtraButtonType
2
+ from .feature import Feature
3
+ from typing import TYPE_CHECKING, Any, Optional, Type
4
+
5
+ from enum import Enum, auto
6
+
7
+ if TYPE_CHECKING:
8
+ from .box import Box
9
+
10
+ TV_LIFT_CONTROL_TYPES_API = {
11
+ 0: {"1": "open_or_stop", "2": "close_or_stop"},
12
+ 1: {"1": "up_or_stop", "2": "down_or_stop"},
13
+ 2: {"1": "up_or_stop", "2": "down_or_stop"},
14
+ 3: {"1": "up_or_stop", "2": "down_or_stop"},
15
+ 4: {"1": "open_or_stop", "2": "close_or_stop", "3": "to_fav"},
16
+ }
17
+
18
+
19
+ class ControlType(Enum):
20
+ UP = auto()
21
+ DOWN = auto()
22
+ FAVORITE = auto()
23
+ OPEN = auto()
24
+ CLOSE = auto()
25
+
26
+
27
+ class Buttons:
28
+ """Buttons defines how a product derives its buttons from extended state."""
29
+
30
+ @staticmethod
31
+ def many_from_extended_state(
32
+ button_cls: Type["Button"],
33
+ product: "Box",
34
+ alias: str,
35
+ methods: dict,
36
+ extended_state: dict,
37
+ ) -> list["Button"]:
38
+ raise NotImplementedError # pragma: no cover
39
+
40
+
41
+ class TvLift(Buttons):
42
+ @staticmethod
43
+ def many_from_extended_state(
44
+ button_cls: Type["Button"],
45
+ product: "Box",
46
+ alias: str,
47
+ methods: dict,
48
+ extended_state: dict,
49
+ ) -> list["Button"]:
50
+ control_type = extended_state.get("tvLift", {}).get("controlType")
51
+ endpoints = TV_LIFT_CONTROL_TYPES_API.get(control_type, {})
52
+ return [
53
+ button_cls(product, f"{alias}_{endpoint}", methods, endpoint)
54
+ for endpoint in endpoints.values()
55
+ ]
56
+
57
+
58
+ class GateBoxSecondOutput(Buttons):
59
+ """GateBoxSecondOutput exposes the gateBox extra button output as a button.
60
+
61
+ WALK_IN and OTHER share one button name because what the output does is a
62
+ device setting the user can change at any time, not a hardware trait.
63
+ """
64
+
65
+ @staticmethod
66
+ def many_from_extended_state(
67
+ button_cls: Type["Button"],
68
+ product: "Box",
69
+ alias: str,
70
+ methods: dict,
71
+ extended_state: dict,
72
+ ) -> list["Button"]:
73
+ gate = extended_state.get("gate", {})
74
+ if gate.get("extraButtonType") not in (
75
+ GateBoxExtraButtonType.WALK_IN,
76
+ GateBoxExtraButtonType.OTHER,
77
+ ):
78
+ return []
79
+
80
+ # Api levels below 20230102 report no mode and cannot be wired that way.
81
+ if gate.get("openCloseMode") == GateBoxControlType.OPEN_CLOSE:
82
+ return []
83
+
84
+ return [button_cls(product, alias, methods, "second_output", "secondary")]
85
+
86
+
87
+ class Button(Feature):
88
+ def __init__(
89
+ self,
90
+ product: "Box",
91
+ alias: str,
92
+ methods: dict,
93
+ query_string: str,
94
+ api_command: str = "set",
95
+ ) -> None:
96
+ super().__init__(product, alias, methods)
97
+ self._device_class = "UPDATE"
98
+ self._query_string: str = query_string
99
+ self._api_command: str = api_command
100
+
101
+ @classmethod
102
+ def many_from_config(
103
+ cls, product: "Box", box_type_config: list, extended_state: Any
104
+ ) -> list["Button"]:
105
+ if not isinstance(extended_state, dict):
106
+ return []
107
+
108
+ features: list[Button] = []
109
+ for alias, methods, buttons in box_type_config:
110
+ features.extend(
111
+ buttons.many_from_extended_state(
112
+ cls, product, alias, methods, extended_state
113
+ )
114
+ )
115
+ return features
116
+
117
+ async def set(self) -> None:
118
+ await self.async_api_command(self._api_command, self.query_string)
119
+
120
+ def after_update(self) -> None:
121
+ pass
122
+
123
+ @property
124
+ def control_type(self) -> Optional[ControlType]:
125
+ """Return icon for endpoint."""
126
+ if "up" in self.query_string:
127
+ return ControlType.UP
128
+ elif "down" in self.query_string:
129
+ return ControlType.DOWN
130
+ elif "fav" in self.query_string:
131
+ return ControlType.FAVORITE
132
+ elif "open" in self.query_string:
133
+ return ControlType.OPEN
134
+ elif "close" in self.query_string:
135
+ return ControlType.CLOSE
136
+ else:
137
+ return None
138
+
139
+ @property
140
+ def query_string(self) -> str:
141
+ return self._query_string
@@ -65,6 +65,19 @@ class GateBoxControlType(IntEnum):
65
65
  OPEN_CLOSE = 2
66
66
 
67
67
 
68
+ class GateBoxExtraButtonType(IntEnum):
69
+ """GateBoxExtraButtonType defines gateBox extra button semantics.
70
+
71
+ Only meaningful outside OPEN_CLOSE mode, which takes the extra button output
72
+ for the close action and leaves this field unused.
73
+ """
74
+
75
+ DISABLED = 0
76
+ STOP = 1
77
+ WALK_IN = 2
78
+ OTHER = 3
79
+
80
+
68
81
  class GateBoxGateType(IntEnum):
69
82
  """GateBoxGateType defines possible gate/cover types reported by gateBox"""
70
83
 
@@ -101,7 +114,7 @@ class UnifiedCoverType(IntEnum):
101
114
  class Gate:
102
115
  _control_type: Optional[int]
103
116
 
104
- def __init__(self, control_type: int):
117
+ def __init__(self, control_type: int, is_calibrated: bool = False):
105
118
  self._control_type = control_type
106
119
 
107
120
  def read_state(self, alias: str, raw_value: Any, product: "Box") -> int:
@@ -148,6 +161,10 @@ class Gate:
148
161
  def close_command(self) -> str:
149
162
  return "close"
150
163
 
164
+ @property
165
+ def is_calibrated(self) -> None:
166
+ return None
167
+
151
168
  def stop_command(self, has_stop: bool) -> str:
152
169
  return "stop"
153
170
 
@@ -157,6 +174,11 @@ class Gate:
157
174
 
158
175
  class Shutter(Gate):
159
176
  _control_type: Optional[ShutterBoxControlType]
177
+ _is_calibrated: bool
178
+
179
+ def __init__(self, control_type: int, is_calibrated: bool = False):
180
+ super().__init__(control_type)
181
+ self._is_calibrated = is_calibrated
160
182
 
161
183
  @property
162
184
  def min_position(self) -> int:
@@ -185,14 +207,22 @@ class Shutter(Gate):
185
207
 
186
208
  @property
187
209
  def is_slider(self) -> bool:
188
- return self._control_type not in (
189
- ShutterBoxControlType.NO_CALIBRATION,
190
- ShutterBoxControlType.CURTAIN,
191
- ShutterBoxControlType.TILT_ONLY,
192
- ShutterBoxControlType.PERGOLA_ROOF_TILT_ONLY,
193
- ShutterBoxControlType.TILT_SHUTTER_WITHOUT_POSITIONING,
210
+ return (
211
+ self._control_type
212
+ not in (
213
+ ShutterBoxControlType.NO_CALIBRATION,
214
+ ShutterBoxControlType.CURTAIN,
215
+ ShutterBoxControlType.TILT_ONLY,
216
+ ShutterBoxControlType.PERGOLA_ROOF_TILT_ONLY,
217
+ ShutterBoxControlType.TILT_SHUTTER_WITHOUT_POSITIONING,
218
+ )
219
+ and self.is_calibrated is True
194
220
  )
195
221
 
222
+ @property
223
+ def is_calibrated(self) -> bool:
224
+ return self._is_calibrated
225
+
196
226
  def read_cover_type(
197
227
  self, alias: str, raw_value: Any, product: "Box"
198
228
  ) -> UnifiedCoverType:
@@ -288,7 +318,7 @@ class GateBox(Gate):
288
318
  if button_type is None:
289
319
  return False
290
320
 
291
- return button_type == 1
321
+ return button_type == GateBoxExtraButtonType.STOP
292
322
 
293
323
 
294
324
  class GateBoxB(GateBox):
@@ -311,9 +341,23 @@ class GateBoxB(GateBox):
311
341
  return raw_value("position")
312
342
 
313
343
  def read_has_stop(self, alias: str, raw_value: Any, product: "Box") -> bool:
344
+ if product.last_data is None:
345
+ return False
346
+
314
347
  # note: if control type is unknown we assume it is not open/close
315
348
  # and has the stop feature via secondary button command.
316
- return self._control_type != GateBoxControlType.OPEN_CLOSE
349
+ if self._control_type == GateBoxControlType.OPEN_CLOSE:
350
+ return False
351
+
352
+ # stop_command issues the same secondary command as the second_output button.
353
+ extra_button_type = raw_value("extra_button_type")
354
+ if extra_button_type in (
355
+ GateBoxExtraButtonType.WALK_IN,
356
+ GateBoxExtraButtonType.OTHER,
357
+ ):
358
+ return False
359
+
360
+ return True
317
361
 
318
362
  def read_cover_type(
319
363
  self, alias: str, raw_value: Any, product: "Box"
@@ -355,13 +399,20 @@ class Cover(Feature):
355
399
  extended_state: dict,
356
400
  ) -> None:
357
401
  control_type = None
402
+ is_calibrated: bool = False
358
403
  if extended_state and issubclass(subclass, Shutter):
359
404
  control_type = extended_state.get("shutter", {}).get("controlType", None)
405
+ is_calibrated = bool(
406
+ extended_state.get("shutter", {})
407
+ .get("calibrationParameters", {})
408
+ .get("isCalibrated", False)
409
+ )
410
+
360
411
  elif extended_state and issubclass(subclass, GateBoxB):
361
412
  control_type = extended_state.get("gate", {}).get("openCloseMode", None)
362
413
 
363
414
  self._device_class = dev_class
364
- self._attributes: GateT = subclass(control_type)
415
+ self._attributes: GateT = subclass(control_type, is_calibrated)
365
416
  self._tilt_current = None
366
417
  super().__init__(product, alias, methods)
367
418
 
@@ -383,6 +434,10 @@ class Cover(Feature):
383
434
  def tilt_current(self):
384
435
  return self._tilt_current
385
436
 
437
+ @property
438
+ def is_calibrated(self) -> Optional[bool]:
439
+ return self._attributes.is_calibrated
440
+
386
441
  @property
387
442
  def is_slider(self) -> Any:
388
443
  return self._attributes.is_slider
@@ -39,7 +39,6 @@ class UnsupportedBoxVersion(BoxError):
39
39
  pass
40
40
 
41
41
 
42
-
43
42
  class BadFieldExceedsMax(BoxError):
44
43
  def __init__(self, dev_name: str, field: str, value: int, max_value: int):
45
44
  self._dev_name = dev_name
@@ -103,7 +102,6 @@ class BadFieldNotRGBW(BoxError):
103
102
  return f"{self._dev_name}.{self._field} is {self._value} which is not a rgbw string"
104
103
 
105
104
 
106
-
107
105
  # misc errors
108
106
 
109
107
 
@@ -160,12 +160,16 @@ class BaseSensor(Feature):
160
160
  @SensorFactory.register("apparentPower", unit="va")
161
161
  @SensorFactory.register("reactivePower", unit="var")
162
162
  @SensorFactory.register("activePower", unit="W")
163
+ @SensorFactory.register("reverseReactiveEnergy", unit="kvarh", scale=1_000)
164
+ @SensorFactory.register("forwardReactiveEnergy", unit="kvarh", scale=1_000)
163
165
  @SensorFactory.register("reverseActiveEnergy", unit="kWh", scale=1_000)
164
166
  @SensorFactory.register("forwardActiveEnergy", unit="kWh", scale=1_000)
165
167
  @SensorFactory.register("illuminance", unit="lx", scale=100)
166
168
  @SensorFactory.register("humidity", unit="percentage", scale=100)
167
169
  @SensorFactory.register("wind", unit="m/s", scale=10)
168
170
  @SensorFactory.register("openStatus", unit="")
171
+ @SensorFactory.register("co2", unit="ppm")
172
+ @SensorFactory.register("co2Definition", unit="")
169
173
  class GenericSensor(BaseSensor):
170
174
  def __init__(
171
175
  # base sensor params
@@ -98,7 +98,7 @@ class ApiHost:
98
98
  try:
99
99
  return await self.async_request(path, self._session.get)
100
100
  except Exception as ex:
101
- logger.error(f"EXCEPTION DURING API CALL: {ex}")
101
+ logger.debug(f"EXCEPTION DURING API CALL: {ex}")
102
102
  raise ex
103
103
 
104
104
  async def async_api_get_ota(self, path: str) -> Optional[dict]:
@@ -107,7 +107,7 @@ class ApiHost:
107
107
  path, self._session.get, allow_ota_check_response=True
108
108
  )
109
109
  except Exception as ex:
110
- logger.error(f"EXCEPTION DURING API CALL: {ex}")
110
+ logger.debug(f"EXCEPTION DURING API CALL: {ex}")
111
111
  raise ex
112
112
 
113
113
  async def async_api_post(
@@ -1,22 +1,36 @@
1
- Metadata-Version: 2.1
2
- Name: blebox-uniapi
3
- Version: 2.5.4
1
+ Metadata-Version: 2.4
2
+ Name: blebox_uniapi
3
+ Version: 2.5.6
4
4
  Summary: Python API for accessing BleBox smart home devices
5
5
  Home-page: https://github.com/blebox/blebox_uniapi
6
6
  Author: BleBox
7
7
  Author-email: opensource@blebox.eu
8
8
  License: Apache Software License 2.0
9
9
  Keywords: blebox_uniapi
10
- Platform: UNKNOWN
11
10
  Classifier: Development Status :: 2 - Pre-Alpha
12
11
  Classifier: Intended Audience :: Developers
13
12
  Classifier: License :: OSI Approved :: Apache Software License
14
13
  Classifier: Natural Language :: English
15
14
  Classifier: Programming Language :: Python :: 3.9
16
15
  Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
17
  Requires-Python: >=3.9
18
18
  Description-Content-Type: text/x-rst
19
19
  License-File: LICENSE
20
+ Requires-Dist: aiohttp>=3
21
+ Requires-Dist: jmespath>1.0.0
22
+ Dynamic: author
23
+ Dynamic: author-email
24
+ Dynamic: classifier
25
+ Dynamic: description
26
+ Dynamic: description-content-type
27
+ Dynamic: home-page
28
+ Dynamic: keywords
29
+ Dynamic: license
30
+ Dynamic: license-file
31
+ Dynamic: requires-dist
32
+ Dynamic: requires-python
33
+ Dynamic: summary
20
34
 
21
35
  ====================
22
36
  BleBox Python UniAPI
@@ -227,5 +241,3 @@ History
227
241
  ------------------
228
242
 
229
243
  * First release on PyPI.
230
-
231
-
@@ -35,4 +35,16 @@ docs/index.rst
35
35
  docs/installation.rst
36
36
  docs/make.bat
37
37
  docs/readme.rst
38
- docs/usage.rst
38
+ docs/usage.rst
39
+ tests/test_box.py
40
+ tests/test_box_types.py
41
+ tests/test_button.py
42
+ tests/test_climate.py
43
+ tests/test_cover.py
44
+ tests/test_light.py
45
+ tests/test_light_unit.py
46
+ tests/test_sensor.py
47
+ tests/test_session.py
48
+ tests/test_switch.py
49
+ tests/test_unknown.py
50
+ tests/test_update.py
@@ -1,7 +1,8 @@
1
1
  [bumpversion]
2
- current_version = 2.5.4
2
+ current_version = 2.5.6
3
3
  commit = True
4
4
  tag = True
5
+ tag_name = v{new_version}
5
6
 
6
7
  [bumpversion:file:setup.py]
7
8
  search = version="{current_version}"
@@ -23,7 +23,7 @@ test_requirements = [
23
23
  ]
24
24
 
25
25
  setup(
26
- version="2.5.4",
26
+ version="2.5.6",
27
27
  author="BleBox",
28
28
  author_email="opensource@blebox.eu",
29
29
  python_requires=">=3.9",
@@ -34,6 +34,7 @@ setup(
34
34
  "Natural Language :: English",
35
35
  "Programming Language :: Python :: 3.9",
36
36
  "Programming Language :: Python :: 3.10",
37
+ "Programming Language :: Python :: 3.11",
37
38
  ],
38
39
  description="Python API for accessing BleBox smart home devices",
39
40
  install_requires=requirements,