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.
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/CONTRIBUTING.rst +8 -4
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/MANIFEST.in +0 -1
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/PKG-INFO +17 -5
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/__init__.py +1 -1
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/box.py +6 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/box_types.py +41 -3
- blebox_uniapi-2.5.6/blebox_uniapi/button.py +141 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/cover.py +65 -10
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/error.py +0 -2
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/sensor.py +4 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/session.py +2 -2
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/PKG-INFO +18 -6
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/SOURCES.txt +13 -1
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/setup.cfg +2 -1
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/setup.py +2 -1
- blebox_uniapi-2.5.6/tests/test_box.py +236 -0
- blebox_uniapi-2.5.6/tests/test_box_types.py +80 -0
- blebox_uniapi-2.5.6/tests/test_button.py +266 -0
- blebox_uniapi-2.5.6/tests/test_climate.py +386 -0
- blebox_uniapi-2.5.6/tests/test_cover.py +962 -0
- blebox_uniapi-2.5.6/tests/test_light.py +1369 -0
- blebox_uniapi-2.5.6/tests/test_light_unit.py +141 -0
- blebox_uniapi-2.5.6/tests/test_sensor.py +477 -0
- blebox_uniapi-2.5.6/tests/test_session.py +233 -0
- blebox_uniapi-2.5.6/tests/test_switch.py +447 -0
- blebox_uniapi-2.5.6/tests/test_unknown.py +59 -0
- blebox_uniapi-2.5.6/tests/test_update.py +70 -0
- blebox_uniapi-2.5.4/blebox_uniapi/button.py +0 -75
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/HISTORY.rst +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/LICENSE +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/README.rst +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/binary_sensor.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/climate.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/feature.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/jfollow.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/light.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/switch.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi/update.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/dependency_links.txt +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/not-zip-safe +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/requires.txt +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/blebox_uniapi.egg-info/top_level.txt +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/Makefile +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/authors.rst +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/conf.py +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/contributing.rst +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/history.rst +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/index.rst +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/installation.rst +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/make.bat +0 -0
- {blebox_uniapi-2.5.4 → blebox_uniapi-2.5.6}/docs/readme.rst +0 -0
- {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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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,22 +1,36 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: blebox_uniapi
|
|
3
|
-
Version: 2.5.
|
|
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
|
-
|
|
@@ -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
|
-
|
|
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
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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 ==
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
2
|
-
Name:
|
|
3
|
-
Version: 2.5.
|
|
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
|
|
@@ -23,7 +23,7 @@ test_requirements = [
|
|
|
23
23
|
]
|
|
24
24
|
|
|
25
25
|
setup(
|
|
26
|
-
version="2.5.
|
|
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,
|