blebox-uniapi 2.2.2__tar.gz → 2.4.0__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.
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/HISTORY.rst +16 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/PKG-INFO +19 -1
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/__init__.py +1 -1
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/binary_sensor.py +41 -3
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/box.py +8 -99
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/box_types.py +180 -96
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/climate.py +11 -13
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/cover.py +176 -26
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/error.py +3 -38
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/feature.py +2 -1
- blebox_uniapi-2.4.0/blebox_uniapi/jfollow.py +11 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/light.py +5 -2
- blebox_uniapi-2.4.0/blebox_uniapi/sensor.py +252 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/session.py +3 -3
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi.egg-info/PKG-INFO +19 -1
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi.egg-info/SOURCES.txt +1 -3
- blebox_uniapi-2.4.0/blebox_uniapi.egg-info/requires.txt +2 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/setup.cfg +1 -1
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/setup.py +3 -2
- blebox_uniapi-2.4.0/tests/test_box.py +169 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_box_types.py +11 -1
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_climate.py +1 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_cover.py +24 -3
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_light.py +9 -15
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_light_unit.py +1 -1
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_switch.py +144 -160
- blebox_uniapi-2.2.2/blebox_uniapi/sensor.py +0 -209
- blebox_uniapi-2.2.2/blebox_uniapi.egg-info/requires.txt +0 -1
- blebox_uniapi-2.2.2/tests/__init__.py +0 -1
- blebox_uniapi-2.2.2/tests/conftest.py +0 -331
- blebox_uniapi-2.2.2/tests/test_box.py +0 -210
- blebox_uniapi-2.2.2/tests/test_error.py +0 -48
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/CONTRIBUTING.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/LICENSE +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/MANIFEST.in +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/README.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/button.py +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi/switch.py +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi.egg-info/dependency_links.txt +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi.egg-info/not-zip-safe +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/blebox_uniapi.egg-info/top_level.txt +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/Makefile +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/authors.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/conf.py +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/contributing.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/history.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/index.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/installation.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/make.bat +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/readme.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/docs/usage.rst +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_button.py +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_sensor.py +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_session.py +0 -0
- {blebox_uniapi-2.2.2 → blebox_uniapi-2.4.0}/tests/test_unknown.py +0 -0
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
=======
|
|
2
2
|
History
|
|
3
3
|
=======
|
|
4
|
+
2.4.0 (2024-06-03)
|
|
5
|
+
------------------
|
|
6
|
+
* Fix: Refactor sensor_factory by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/163
|
|
7
|
+
* Order in BOX_TYPES by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/164
|
|
8
|
+
* Feature: smart meter by @swistakm in https://github.com/blebox/blebox_uniapi/pull/168
|
|
9
|
+
* Smartmeter by @pvsti in https://github.com/blebox/blebox_uniapi/pull/170
|
|
10
|
+
* fix: resolve regressions in cover and climate due to jmespath introduction by @swistakm in https://github.com/blebox/blebox_uniapi/pull/173
|
|
11
|
+
|
|
12
|
+
2.3.0 (2024-03-13)
|
|
13
|
+
------------------
|
|
14
|
+
* feat: add new methods to cover feature enabling handling of tilt open/close actions by @swistakm in https://github.com/blebox/blebox_uniapi/pull/154
|
|
15
|
+
* add support for flood sensing for multisensors as binary moisture sensor by @swistakm in https://github.com/blebox/blebox_uniapi/pull/153
|
|
16
|
+
* feat/fix: Add Ruff and Pre-commit Configuration, Resolve Undefined Name by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/158
|
|
17
|
+
* gatebox and shutterbox improvements: by @swistakm in https://github.com/blebox/blebox_uniapi/pull/156
|
|
18
|
+
* BleBox Multisensor Illuminance Integration by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/161
|
|
19
|
+
|
|
4
20
|
2.2.2 (2024-02-07)
|
|
5
21
|
------------------
|
|
6
22
|
* fixed wind reading units to get proper raw m/s value (division by 10, see PR #150)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: blebox_uniapi
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
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
|
|
@@ -14,8 +14,10 @@ Classifier: Natural Language :: English
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.9
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.10
|
|
16
16
|
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/x-rst
|
|
17
18
|
License-File: LICENSE
|
|
18
19
|
Requires-Dist: aiohttp>=3
|
|
20
|
+
Requires-Dist: jmespath>1.0.0
|
|
19
21
|
|
|
20
22
|
====================
|
|
21
23
|
BleBox Python UniAPI
|
|
@@ -67,6 +69,22 @@ Credits
|
|
|
67
69
|
=======
|
|
68
70
|
History
|
|
69
71
|
=======
|
|
72
|
+
2.4.0 (2024-06-03)
|
|
73
|
+
------------------
|
|
74
|
+
* Fix: Refactor sensor_factory by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/163
|
|
75
|
+
* Order in BOX_TYPES by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/164
|
|
76
|
+
* Feature: smart meter by @swistakm in https://github.com/blebox/blebox_uniapi/pull/168
|
|
77
|
+
* Smartmeter by @pvsti in https://github.com/blebox/blebox_uniapi/pull/170
|
|
78
|
+
* fix: resolve regressions in cover and climate due to jmespath introduction by @swistakm in https://github.com/blebox/blebox_uniapi/pull/173
|
|
79
|
+
|
|
80
|
+
2.3.0 (2024-03-13)
|
|
81
|
+
------------------
|
|
82
|
+
* feat: add new methods to cover feature enabling handling of tilt open/close actions by @swistakm in https://github.com/blebox/blebox_uniapi/pull/154
|
|
83
|
+
* add support for flood sensing for multisensors as binary moisture sensor by @swistakm in https://github.com/blebox/blebox_uniapi/pull/153
|
|
84
|
+
* feat/fix: Add Ruff and Pre-commit Configuration, Resolve Undefined Name by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/158
|
|
85
|
+
* gatebox and shutterbox improvements: by @swistakm in https://github.com/blebox/blebox_uniapi/pull/156
|
|
86
|
+
* BleBox Multisensor Illuminance Integration by @Pastucha in https://github.com/blebox/blebox_uniapi/pull/161
|
|
87
|
+
|
|
70
88
|
2.2.2 (2024-02-07)
|
|
71
89
|
------------------
|
|
72
90
|
* fixed wind reading units to get proper raw m/s value (division by 10, see PR #150)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
from typing import Union
|
|
1
|
+
from typing import Union, TYPE_CHECKING
|
|
2
2
|
|
|
3
3
|
from .feature import Feature
|
|
4
4
|
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .box import Box
|
|
7
|
+
|
|
5
8
|
|
|
6
9
|
class BinarySensor(Feature):
|
|
7
10
|
"""Class representing sensor with bool state."""
|
|
@@ -13,17 +16,26 @@ class BinarySensor(Feature):
|
|
|
13
16
|
def many_from_config(
|
|
14
17
|
cls, product, box_type_config, extended_state
|
|
15
18
|
) -> list["Feature"]:
|
|
19
|
+
type_class_map = {
|
|
20
|
+
"rain": Rain,
|
|
21
|
+
"flood": Flood,
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
output_list = list()
|
|
17
25
|
sensors_list = extended_state.get("multiSensor").get("sensors", {})
|
|
18
26
|
alias, methods = box_type_config[0]
|
|
27
|
+
|
|
19
28
|
for sensor in sensors_list:
|
|
20
29
|
sensor_type = sensor.get("type")
|
|
21
30
|
sensor_id = sensor.get("id")
|
|
22
|
-
|
|
31
|
+
|
|
32
|
+
if sensor_type in type_class_map:
|
|
33
|
+
klass = type_class_map[sensor_type]
|
|
34
|
+
|
|
23
35
|
if methods.get(sensor_type) is not None:
|
|
24
36
|
value_method = {sensor_type: methods[sensor_type](sensor_id)}
|
|
25
37
|
output_list.append(
|
|
26
|
-
|
|
38
|
+
klass(
|
|
27
39
|
product=product,
|
|
28
40
|
alias=sensor_type + "_" + str(sensor_id),
|
|
29
41
|
methods=value_method,
|
|
@@ -56,3 +68,29 @@ class Rain(BinarySensor):
|
|
|
56
68
|
|
|
57
69
|
def after_update(self) -> None:
|
|
58
70
|
self._current = self._read_rain("rain")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class Flood(BinarySensor):
|
|
74
|
+
def __init__(self, product: "Box", alias: str, methods: dict):
|
|
75
|
+
self._device_class = "moisture"
|
|
76
|
+
super().__init__(product, alias, methods)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def state(self) -> bool:
|
|
80
|
+
return self._current > 0
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def device_class(self) -> str:
|
|
84
|
+
return self._device_class
|
|
85
|
+
|
|
86
|
+
def _read_flood(self, field: str) -> Union[float, int, None]:
|
|
87
|
+
product = self._product
|
|
88
|
+
|
|
89
|
+
if product.last_data is not None:
|
|
90
|
+
raw = self.raw_value(field)
|
|
91
|
+
if raw is not None: # no reading
|
|
92
|
+
return self.raw_value("flood")
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
def after_update(self) -> None:
|
|
96
|
+
self._current = self._read_flood("flood")
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import re
|
|
4
3
|
import asyncio
|
|
5
4
|
import time
|
|
6
5
|
|
|
7
6
|
from typing import Optional, Any, Dict
|
|
8
7
|
|
|
9
|
-
from .box_types import
|
|
8
|
+
from .box_types import _DEFAULT_API_LEVEL, get_conf, get_conf_set
|
|
10
9
|
from .button import Button
|
|
11
10
|
from .climate import Climate
|
|
12
11
|
from .cover import Cover
|
|
@@ -19,7 +18,6 @@ from .switch import Switch
|
|
|
19
18
|
from .error import (
|
|
20
19
|
UnsupportedBoxResponse,
|
|
21
20
|
UnsupportedBoxVersion,
|
|
22
|
-
JPathFailed,
|
|
23
21
|
BadFieldExceedsMax,
|
|
24
22
|
BadFieldLessThanMin,
|
|
25
23
|
BadFieldMissing,
|
|
@@ -107,7 +105,7 @@ class Box:
|
|
|
107
105
|
info, f"{location} has no hardware version"
|
|
108
106
|
) from ex
|
|
109
107
|
|
|
110
|
-
level = int(info.get("apiLevel",
|
|
108
|
+
level = int(info.get("apiLevel", _DEFAULT_API_LEVEL))
|
|
111
109
|
|
|
112
110
|
self._data_path = config["api_path"]
|
|
113
111
|
self._type = type
|
|
@@ -117,21 +115,15 @@ class Box:
|
|
|
117
115
|
self._firmware_version = firmware_version
|
|
118
116
|
self._hardware_version = hardware_version
|
|
119
117
|
self._api_version = level
|
|
120
|
-
|
|
121
118
|
self._model = config.get("model", type)
|
|
122
|
-
|
|
123
119
|
self._api = config.get("api", {})
|
|
124
|
-
|
|
125
120
|
self._features = self.create_features(config, info, extended_state)
|
|
126
|
-
|
|
127
121
|
self._config = config
|
|
128
|
-
|
|
129
|
-
self._update_last_data(None)
|
|
122
|
+
self._update_last_data(extended_state)
|
|
130
123
|
|
|
131
124
|
def create_features(
|
|
132
125
|
self, config: dict, info: dict, extended_state: Optional[dict]
|
|
133
126
|
) -> dict:
|
|
134
|
-
|
|
135
127
|
features = {}
|
|
136
128
|
for field, klass in {
|
|
137
129
|
"covers": Cover,
|
|
@@ -185,7 +177,7 @@ class Box:
|
|
|
185
177
|
product = device_type
|
|
186
178
|
if device_type == "wLightBox" and product == "wLightBoxS":
|
|
187
179
|
device_type = "wLightBoxS"
|
|
188
|
-
level = int(info.get("apiLevel",
|
|
180
|
+
level = int(info.get("apiLevel", _DEFAULT_API_LEVEL))
|
|
189
181
|
config_set = get_conf_set(device_type)
|
|
190
182
|
if not config_set:
|
|
191
183
|
raise UnsupportedBoxResponse(f"{device_type} is not a supported type")
|
|
@@ -260,9 +252,9 @@ class Box:
|
|
|
260
252
|
# Note that SwitchboxD is just an example. It is possible that APIs of other
|
|
261
253
|
# box types also exhibit this kind of behavior.
|
|
262
254
|
if (
|
|
263
|
-
isinstance(self._last_data, dict)
|
|
264
|
-
isinstance(new_data, dict)
|
|
265
|
-
self._last_data.keys() != new_data.keys()
|
|
255
|
+
isinstance(self._last_data, dict)
|
|
256
|
+
and isinstance(new_data, dict)
|
|
257
|
+
and self._last_data.keys() != new_data.keys()
|
|
266
258
|
):
|
|
267
259
|
# ... In such a case we need to merge both states instead of overwriting
|
|
268
260
|
# the old one as-is.
|
|
@@ -288,89 +280,6 @@ class Box:
|
|
|
288
280
|
self._last_real_update = None # force update
|
|
289
281
|
return await self._async_api(False, method, *args)
|
|
290
282
|
|
|
291
|
-
def follow(self, data: dict, path: str) -> Any:
|
|
292
|
-
"""
|
|
293
|
-
Return payload from device response json.
|
|
294
|
-
:param self:
|
|
295
|
-
:param data:
|
|
296
|
-
:param path:
|
|
297
|
-
:return:
|
|
298
|
-
"""
|
|
299
|
-
if data is None:
|
|
300
|
-
raise RuntimeError(f"bad argument: data {data}") # pragma: no cover
|
|
301
|
-
|
|
302
|
-
results = path.split("/")
|
|
303
|
-
current_tree = data
|
|
304
|
-
|
|
305
|
-
for chunk in results:
|
|
306
|
-
with_string_value = re.compile(r"^\[(.*)='(.*)']$")
|
|
307
|
-
match = with_string_value.match(chunk)
|
|
308
|
-
if match:
|
|
309
|
-
name = match.group(1)
|
|
310
|
-
value = match.group(2)
|
|
311
|
-
|
|
312
|
-
found = False
|
|
313
|
-
|
|
314
|
-
for item in current_tree:
|
|
315
|
-
if item[name] == value:
|
|
316
|
-
current_tree = item
|
|
317
|
-
found = True
|
|
318
|
-
break
|
|
319
|
-
|
|
320
|
-
if not found:
|
|
321
|
-
raise JPathFailed(f"with: {name}={value}", path, data)
|
|
322
|
-
|
|
323
|
-
continue # pragma: no cover
|
|
324
|
-
|
|
325
|
-
with_int_value = re.compile(r"^\[(.*)=(\d+)]$")
|
|
326
|
-
match = with_int_value.match(chunk)
|
|
327
|
-
if match:
|
|
328
|
-
name = match.group(1)
|
|
329
|
-
value = int(match.group(2))
|
|
330
|
-
|
|
331
|
-
found = False
|
|
332
|
-
|
|
333
|
-
if not isinstance(current_tree, list):
|
|
334
|
-
raise JPathFailed(
|
|
335
|
-
f"list expected but got {current_tree}", path, data
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
for item in current_tree:
|
|
339
|
-
if item[name] == value:
|
|
340
|
-
current_tree = item
|
|
341
|
-
found = True
|
|
342
|
-
break
|
|
343
|
-
|
|
344
|
-
if not found:
|
|
345
|
-
raise JPathFailed(f"with: {name}={value}", path, data)
|
|
346
|
-
continue # pragma: no cover
|
|
347
|
-
|
|
348
|
-
with_index = re.compile(r"^\[(\d+)]$")
|
|
349
|
-
match = with_index.match(chunk)
|
|
350
|
-
if match:
|
|
351
|
-
index = int(match.group(1))
|
|
352
|
-
if not isinstance(current_tree, list) or index >= len(current_tree):
|
|
353
|
-
raise JPathFailed(f"with value at index {index}", path, data)
|
|
354
|
-
|
|
355
|
-
current_tree = current_tree[index]
|
|
356
|
-
continue
|
|
357
|
-
|
|
358
|
-
if isinstance(current_tree, dict):
|
|
359
|
-
names = current_tree.keys()
|
|
360
|
-
if chunk not in names:
|
|
361
|
-
raise JPathFailed(
|
|
362
|
-
f"item '{chunk}' not among {list(names)}", path, data
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
current_tree = current_tree[chunk]
|
|
366
|
-
else:
|
|
367
|
-
raise JPathFailed(
|
|
368
|
-
f"unexpected item type: '{chunk}' not in: {current_tree}",
|
|
369
|
-
path,
|
|
370
|
-
data,
|
|
371
|
-
)
|
|
372
|
-
return current_tree
|
|
373
|
-
|
|
374
283
|
def expect_int(
|
|
375
284
|
self, field: str, raw_value: int, maximum: int = -1, minimum: int = 0
|
|
376
285
|
) -> int:
|
|
@@ -398,7 +307,7 @@ class Box:
|
|
|
398
307
|
if value is None:
|
|
399
308
|
raise BadFieldMissing(self.name, field)
|
|
400
309
|
|
|
401
|
-
if not
|
|
310
|
+
if not isinstance(value, int):
|
|
402
311
|
raise BadFieldNotANumber(self.name, field, value)
|
|
403
312
|
|
|
404
313
|
return self.check_int_range(value, field, max_value, min_value)
|