blebox-uniapi 2.5.5__tar.gz → 2.5.7__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.5 → blebox_uniapi-2.5.7}/CONTRIBUTING.rst +8 -4
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/MANIFEST.in +0 -1
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/PKG-INFO +17 -5
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/__init__.py +1 -1
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/box.py +6 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/box_types.py +36 -4
- blebox_uniapi-2.5.7/blebox_uniapi/button.py +141 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/climate.py +38 -7
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/cover.py +29 -2
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/error.py +0 -2
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/sensor.py +89 -20
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/session.py +2 -2
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi.egg-info/PKG-INFO +18 -6
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi.egg-info/SOURCES.txt +13 -1
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/setup.cfg +2 -1
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/setup.py +2 -1
- blebox_uniapi-2.5.7/tests/test_box.py +236 -0
- blebox_uniapi-2.5.7/tests/test_box_types.py +80 -0
- blebox_uniapi-2.5.7/tests/test_button.py +266 -0
- blebox_uniapi-2.5.7/tests/test_climate.py +424 -0
- blebox_uniapi-2.5.7/tests/test_cover.py +962 -0
- blebox_uniapi-2.5.7/tests/test_light.py +1369 -0
- blebox_uniapi-2.5.7/tests/test_light_unit.py +141 -0
- blebox_uniapi-2.5.7/tests/test_sensor.py +587 -0
- blebox_uniapi-2.5.7/tests/test_session.py +233 -0
- blebox_uniapi-2.5.7/tests/test_switch.py +447 -0
- blebox_uniapi-2.5.7/tests/test_unknown.py +59 -0
- blebox_uniapi-2.5.7/tests/test_update.py +70 -0
- blebox_uniapi-2.5.5/blebox_uniapi/button.py +0 -75
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/HISTORY.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/LICENSE +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/README.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/binary_sensor.py +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/feature.py +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/jfollow.py +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/light.py +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/switch.py +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi/update.py +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi.egg-info/dependency_links.txt +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi.egg-info/not-zip-safe +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi.egg-info/requires.txt +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/blebox_uniapi.egg-info/top_level.txt +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/Makefile +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/authors.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/conf.py +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/contributing.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/history.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/index.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/installation.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/make.bat +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/docs/readme.rst +0 -0
- {blebox_uniapi-2.5.5 → blebox_uniapi-2.5.7}/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.7
|
|
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
|
|
@@ -187,6 +191,7 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
187
191
|
"minimum": "thermo.minimumTemp",
|
|
188
192
|
"maximum": "thermo.maximumTemp",
|
|
189
193
|
"temperature": lambda x: f"sensors[?id == `{x}`]|[0]|value",
|
|
194
|
+
"temperature.state": lambda x: f"sensors[?id == `{x}`]|[0]|state",
|
|
190
195
|
"state": "thermo.state",
|
|
191
196
|
"mode": "thermo.mode",
|
|
192
197
|
"safetySensorId": "thermo.safetyTempSensor.sensorId",
|
|
@@ -199,6 +204,7 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
199
204
|
"temperature",
|
|
200
205
|
{
|
|
201
206
|
"temperature": lambda x: f"sensors[?id == `{x}`]|[0]|value",
|
|
207
|
+
"temperature.state": lambda x: f"sensors[?id == `{x}`]|[0]|state",
|
|
202
208
|
},
|
|
203
209
|
]
|
|
204
210
|
],
|
|
@@ -526,7 +532,7 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
526
532
|
{
|
|
527
533
|
"temperature": "tempSensor.sensors[?id == `0`]|[0]|value",
|
|
528
534
|
"trend": "tempSensor.sensors[?id == `0`]|[0]|trend",
|
|
529
|
-
"state": "tempSensor.sensors[?id == `0`]|[0]|state",
|
|
535
|
+
"temperature.state": "tempSensor.sensors[?id == `0`]|[0]|state",
|
|
530
536
|
"elapsed": "tempSensor.sensors[?id == `0`]|[0]|elapsedTimeS",
|
|
531
537
|
},
|
|
532
538
|
]
|
|
@@ -678,7 +684,9 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
678
684
|
"multiSensor",
|
|
679
685
|
{
|
|
680
686
|
"temperature": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
687
|
+
"temperature.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
681
688
|
"wind": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
689
|
+
"wind.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
682
690
|
},
|
|
683
691
|
]
|
|
684
692
|
],
|
|
@@ -700,8 +708,11 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
700
708
|
"multiSensor",
|
|
701
709
|
{
|
|
702
710
|
"temperature": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
711
|
+
"temperature.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
703
712
|
"wind": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
713
|
+
"wind.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
704
714
|
"humidity": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
715
|
+
"humidity.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
705
716
|
},
|
|
706
717
|
]
|
|
707
718
|
],
|
|
@@ -723,9 +734,13 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
723
734
|
"multiSensor",
|
|
724
735
|
{
|
|
725
736
|
"illuminance": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
737
|
+
"illuminance.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
726
738
|
"temperature": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
739
|
+
"temperature.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
727
740
|
"wind": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'wind']|[0]|value",
|
|
741
|
+
"wind.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'wind']|[0]|state",
|
|
728
742
|
"humidity": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|value",
|
|
743
|
+
"humidity.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[0]|state",
|
|
729
744
|
},
|
|
730
745
|
]
|
|
731
746
|
],
|
|
@@ -747,22 +762,39 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
747
762
|
"multiSensor",
|
|
748
763
|
{
|
|
749
764
|
"frequency": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'frequency']|[0]|value",
|
|
765
|
+
"frequency.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'frequency']|[0]|state",
|
|
750
766
|
"current": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'current']|[0]|value",
|
|
767
|
+
"current.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'current']|[0]|state",
|
|
751
768
|
"voltage": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'voltage']|[0]|value",
|
|
769
|
+
"voltage.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'voltage']|[0]|state",
|
|
752
770
|
"apparentPower": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'apparentPower']|[0]|value",
|
|
771
|
+
"apparentPower.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'apparentPower']|[0]|state",
|
|
753
772
|
"activePower": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'activePower']|[0]|value",
|
|
773
|
+
"activePower.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'activePower']|[0]|state",
|
|
754
774
|
"reactivePower": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reactivePower']|[0]|value",
|
|
775
|
+
"reactivePower.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reactivePower']|[0]|state",
|
|
755
776
|
"reverseActiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reverseActiveEnergy']|[0]|value",
|
|
777
|
+
"reverseActiveEnergy.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reverseActiveEnergy']|[0]|state",
|
|
756
778
|
"forwardActiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'forwardActiveEnergy']|[0]|value",
|
|
779
|
+
"forwardActiveEnergy.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'forwardActiveEnergy']|[0]|state",
|
|
757
780
|
"forwardReactiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'forwardReactiveEnergy']|[0]|value",
|
|
781
|
+
"forwardReactiveEnergy.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'forwardReactiveEnergy']|[0]|state",
|
|
758
782
|
"reverseReactiveEnergy": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reverseReactiveEnergy']|[0]|value",
|
|
783
|
+
"reverseReactiveEnergy.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'reverseReactiveEnergy']|[0]|state",
|
|
759
784
|
"illuminance": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'illuminance']|[0]|value",
|
|
785
|
+
"illuminance.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'illuminance']|[0]|state",
|
|
760
786
|
"temperature": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'temperature']|[0]|value",
|
|
787
|
+
"temperature.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'temperature']|[0]|state",
|
|
761
788
|
"wind": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'wind']|[0]|value",
|
|
789
|
+
"wind.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'wind']|[0]|state",
|
|
762
790
|
"humidity": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'humidity']|[0]|value",
|
|
791
|
+
"humidity.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'humidity']|[0]|state",
|
|
763
792
|
"openStatus": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'openStatus']|[0]|value",
|
|
793
|
+
"openStatus.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'openStatus']|[0]|state",
|
|
764
794
|
"co2": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2']|[0]|value",
|
|
795
|
+
"co2.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2']|[0]|state",
|
|
765
796
|
"co2Definition": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2Definition']|[0]|value",
|
|
797
|
+
"co2Definition.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2Definition']|[0]|state",
|
|
766
798
|
},
|
|
767
799
|
]
|
|
768
800
|
],
|
|
@@ -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
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
from enum import IntEnum
|
|
1
2
|
from .sensor import Temperature
|
|
2
3
|
from typing import Optional, Any, Union
|
|
3
4
|
from .feature import Feature
|
|
4
5
|
from blebox_uniapi.jfollow import follow
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
class BleboxThermoState(IntEnum):
|
|
9
|
+
"""Possible states of a thermo regulator, as reported by the device."""
|
|
10
|
+
|
|
11
|
+
OFF = 0
|
|
12
|
+
ON = 1
|
|
13
|
+
BOOST = 3
|
|
14
|
+
SAFETY_OFF = 4
|
|
15
|
+
|
|
16
|
+
|
|
7
17
|
class Climate(Temperature):
|
|
8
18
|
_is_on: Optional[bool]
|
|
9
19
|
_desired: Union[float, int, None]
|
|
@@ -90,12 +100,18 @@ class Climate(Temperature):
|
|
|
90
100
|
raw = self.raw_value("state")
|
|
91
101
|
if raw is not None: # no reading
|
|
92
102
|
alias = self._alias
|
|
93
|
-
return product.expect_int(alias, raw,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
103
|
+
return product.expect_int(alias, raw, max(BleboxThermoState), 0) in (
|
|
104
|
+
BleboxThermoState.ON,
|
|
105
|
+
BleboxThermoState.BOOST,
|
|
106
|
+
)
|
|
97
107
|
return None
|
|
98
108
|
|
|
109
|
+
def _read_safety_off(self) -> bool:
|
|
110
|
+
"""Return True when the safety system forced the output off."""
|
|
111
|
+
if self._product.last_data is None:
|
|
112
|
+
return False
|
|
113
|
+
return self.raw_value("state") == BleboxThermoState.SAFETY_OFF
|
|
114
|
+
|
|
99
115
|
def _read_operating_state(self) -> Optional[int]:
|
|
100
116
|
"""Return current operating state"""
|
|
101
117
|
if self._product.last_data is not None:
|
|
@@ -107,13 +123,13 @@ class Climate(Temperature):
|
|
|
107
123
|
return None
|
|
108
124
|
|
|
109
125
|
def _read_is_heating(self) -> Optional[bool]:
|
|
110
|
-
if not self._product.last_data:
|
|
126
|
+
if not self._product.last_data or self.current is None or self.desired is None:
|
|
111
127
|
return None
|
|
112
128
|
|
|
113
129
|
return self.is_on and (self.current < self.desired)
|
|
114
130
|
|
|
115
131
|
def _read_is_cooling(self) -> Optional[bool]:
|
|
116
|
-
if not self._product.last_data:
|
|
132
|
+
if not self._product.last_data or self.current is None or self.desired is None:
|
|
117
133
|
return None
|
|
118
134
|
|
|
119
135
|
return self.is_on and (self.current > self.desired)
|
|
@@ -131,7 +147,22 @@ class Climate(Temperature):
|
|
|
131
147
|
def after_update(self) -> None:
|
|
132
148
|
self._is_on = self._read_is_on()
|
|
133
149
|
self._desired = self._read_temperature("desired")
|
|
134
|
-
|
|
150
|
+
|
|
151
|
+
state = self._read_state("temperature")
|
|
152
|
+
if self._state_is_error(state):
|
|
153
|
+
self._error = True
|
|
154
|
+
self._current = None
|
|
155
|
+
else:
|
|
156
|
+
self._error = False
|
|
157
|
+
if self._state_is_initializing(state):
|
|
158
|
+
self._current = None
|
|
159
|
+
else:
|
|
160
|
+
self._current = self._read_temperature("temperature")
|
|
161
|
+
|
|
162
|
+
if self._read_safety_off():
|
|
163
|
+
self._error = True
|
|
164
|
+
|
|
165
|
+
self._native_value = self._current
|
|
135
166
|
self._is_heating = self._read_is_heating()
|
|
136
167
|
self._is_cooling = self._read_is_cooling()
|
|
137
168
|
self._havc_action = self._read_operating_state()
|
|
@@ -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
|
|
|
@@ -305,7 +318,7 @@ class GateBox(Gate):
|
|
|
305
318
|
if button_type is None:
|
|
306
319
|
return False
|
|
307
320
|
|
|
308
|
-
return button_type ==
|
|
321
|
+
return button_type == GateBoxExtraButtonType.STOP
|
|
309
322
|
|
|
310
323
|
|
|
311
324
|
class GateBoxB(GateBox):
|
|
@@ -328,9 +341,23 @@ class GateBoxB(GateBox):
|
|
|
328
341
|
return raw_value("position")
|
|
329
342
|
|
|
330
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
|
+
|
|
331
347
|
# note: if control type is unknown we assume it is not open/close
|
|
332
348
|
# and has the stop feature via secondary button command.
|
|
333
|
-
|
|
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
|
|
334
361
|
|
|
335
362
|
def read_cover_type(
|
|
336
363
|
self, alias: str, raw_value: Any, product: "Box"
|
|
@@ -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
|
|