blebox-uniapi 2.5.7__tar.gz → 2.5.8__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.7 → blebox_uniapi-2.5.8}/PKG-INFO +1 -1
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/__init__.py +1 -1
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/box.py +4 -1
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/box_types.py +8 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/sensor.py +15 -4
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/PKG-INFO +1 -1
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/setup.cfg +1 -1
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/setup.py +1 -1
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_box.py +18 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_sensor.py +97 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/CONTRIBUTING.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/HISTORY.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/LICENSE +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/MANIFEST.in +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/README.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/binary_sensor.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/button.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/climate.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/cover.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/error.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/feature.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/jfollow.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/light.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/session.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/switch.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/update.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/SOURCES.txt +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/dependency_links.txt +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/not-zip-safe +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/requires.txt +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/top_level.txt +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/Makefile +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/authors.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/conf.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/contributing.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/history.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/index.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/installation.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/make.bat +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/readme.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/usage.rst +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_box_types.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_button.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_climate.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_cover.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_light.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_light_unit.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_session.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_switch.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_unknown.py +0 -0
- {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_update.py +0 -0
|
@@ -387,7 +387,10 @@ class Box:
|
|
|
387
387
|
await self._session.async_api_get_ota("/api/ota/check")
|
|
388
388
|
for _ in range(3):
|
|
389
389
|
await asyncio.sleep(1)
|
|
390
|
-
|
|
390
|
+
try:
|
|
391
|
+
response = await self._session.async_api_get("/api/device/state")
|
|
392
|
+
except HttpError:
|
|
393
|
+
response = await self._session.async_api_get("/info")
|
|
391
394
|
if response is None:
|
|
392
395
|
raise UnsupportedBoxResponse("Device returned non-JSON response")
|
|
393
396
|
response = response.get("device", response)
|
|
@@ -795,6 +795,14 @@ BOX_TYPE_CONF: dict[str, dict[int, dict[str, Any]]] = {
|
|
|
795
795
|
"co2.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2']|[0]|state",
|
|
796
796
|
"co2Definition": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2Definition']|[0]|value",
|
|
797
797
|
"co2Definition.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'co2Definition']|[0]|state",
|
|
798
|
+
"gaugePressure": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'gaugePressure']|[0]|value",
|
|
799
|
+
"gaugePressure.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'gaugePressure']|[0]|state",
|
|
800
|
+
"liquidHeight": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'liquidHeight']|[0]|value",
|
|
801
|
+
"liquidHeight.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'liquidHeight']|[0]|state",
|
|
802
|
+
"fillLevel": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'fillLevel']|[0]|value",
|
|
803
|
+
"fillLevel.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'fillLevel']|[0]|state",
|
|
804
|
+
"volume": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'volume']|[0]|value",
|
|
805
|
+
"volume.state": lambda x: f"multiSensor.sensors[?id == `{x}`]|[?type == 'volume']|[0]|state",
|
|
798
806
|
},
|
|
799
807
|
]
|
|
800
808
|
],
|
|
@@ -105,6 +105,7 @@ class BleboxSensorState(IntEnum):
|
|
|
105
105
|
ERROR = 3
|
|
106
106
|
ABOVE_RANGE = 4
|
|
107
107
|
BELOW_RANGE = 5
|
|
108
|
+
NOT_CONFIGURED = 6
|
|
108
109
|
|
|
109
110
|
|
|
110
111
|
class BaseSensor(Feature):
|
|
@@ -114,6 +115,7 @@ class BaseSensor(Feature):
|
|
|
114
115
|
_sensor_type: Optional[str]
|
|
115
116
|
_sensor_id: Optional[int]
|
|
116
117
|
_error: bool = False
|
|
118
|
+
_needs_configuration: bool = False
|
|
117
119
|
|
|
118
120
|
def __init__(
|
|
119
121
|
self,
|
|
@@ -145,17 +147,22 @@ class BaseSensor(Feature):
|
|
|
145
147
|
def is_error(self) -> bool:
|
|
146
148
|
return self._error
|
|
147
149
|
|
|
150
|
+
@property
|
|
151
|
+
def needs_configuration(self) -> bool:
|
|
152
|
+
return self._needs_configuration
|
|
153
|
+
|
|
148
154
|
def _read_state(self, name: str) -> Optional[int]:
|
|
149
155
|
if self._product.last_data is None:
|
|
150
156
|
return None
|
|
151
157
|
raw = self.raw_value(f"{name}.state")
|
|
152
|
-
if
|
|
153
|
-
|
|
154
|
-
return
|
|
158
|
+
state = int(raw) if isinstance(raw, (int, float)) else None
|
|
159
|
+
self._needs_configuration = state == BleboxSensorState.NOT_CONFIGURED
|
|
160
|
+
return state
|
|
155
161
|
|
|
156
162
|
@staticmethod
|
|
157
163
|
def _state_is_error(state: Optional[int]) -> bool:
|
|
158
|
-
|
|
164
|
+
# note: unconfigured probe reports garbage values
|
|
165
|
+
return state in (BleboxSensorState.ERROR, BleboxSensorState.NOT_CONFIGURED)
|
|
159
166
|
|
|
160
167
|
@staticmethod
|
|
161
168
|
def _state_is_initializing(state: Optional[int]) -> bool:
|
|
@@ -201,6 +208,10 @@ class BaseSensor(Feature):
|
|
|
201
208
|
@SensorFactory.register("openStatus", unit="")
|
|
202
209
|
@SensorFactory.register("co2", unit="ppm")
|
|
203
210
|
@SensorFactory.register("co2Definition", unit="")
|
|
211
|
+
@SensorFactory.register("gaugePressure", unit="mbar")
|
|
212
|
+
@SensorFactory.register("liquidHeight", unit="cm")
|
|
213
|
+
@SensorFactory.register("fillLevel", unit="percentage", scale=10)
|
|
214
|
+
@SensorFactory.register("volume", unit="L")
|
|
204
215
|
class GenericSensor(BaseSensor):
|
|
205
216
|
def __init__(
|
|
206
217
|
# base sensor params
|
|
@@ -234,3 +234,21 @@ async def test_async_ota_check_returns_silently_when_no_available_fv(
|
|
|
234
234
|
with patch("asyncio.sleep", new=AsyncMock()):
|
|
235
235
|
await box.async_ota_check()
|
|
236
236
|
assert box.available_firmware_version is None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
async def test_async_ota_check_falls_back_to_info(mock_session, sample_data, config):
|
|
240
|
+
box = Box(mock_session, sample_data, config, None)
|
|
241
|
+
mock_session.async_api_get_ota = AsyncMock(return_value=None)
|
|
242
|
+
mock_session.async_api_get = AsyncMock(
|
|
243
|
+
side_effect=[
|
|
244
|
+
error.HttpError("Request to /api/device/state failed with HTTP 404"),
|
|
245
|
+
{"device": {"availableFv": "2.0", "fv": "1.5"}},
|
|
246
|
+
]
|
|
247
|
+
)
|
|
248
|
+
with patch("asyncio.sleep", new=AsyncMock()):
|
|
249
|
+
await box.async_ota_check()
|
|
250
|
+
mock_session.async_api_get.assert_has_calls(
|
|
251
|
+
[mock.call("/api/device/state"), mock.call("/info")]
|
|
252
|
+
)
|
|
253
|
+
assert box.available_firmware_version == "2.0"
|
|
254
|
+
assert box.firmware_version == "1.5"
|
|
@@ -462,6 +462,103 @@ class TestMultiSensorEnergy(DefaultBoxTest):
|
|
|
462
462
|
assert entity.native_value == 1500
|
|
463
463
|
|
|
464
464
|
|
|
465
|
+
class TestMultiSensorTank(DefaultBoxTest):
|
|
466
|
+
"""Tests for multiSensor tank level sensors (apiLevel 20230606)."""
|
|
467
|
+
|
|
468
|
+
DEVCLASS = "sensors"
|
|
469
|
+
ENTITY_CLASS = BleBoxSensorEntity
|
|
470
|
+
|
|
471
|
+
DEV_INFO_PATH = "state"
|
|
472
|
+
|
|
473
|
+
DEVICE_INFO = json.loads(
|
|
474
|
+
"""
|
|
475
|
+
{
|
|
476
|
+
"device": {
|
|
477
|
+
"deviceName": "My tankSensor",
|
|
478
|
+
"type": "multiSensor",
|
|
479
|
+
"product": "tankSensor",
|
|
480
|
+
"hv": "tS-1.0",
|
|
481
|
+
"fv": "0.1",
|
|
482
|
+
"universe": 0,
|
|
483
|
+
"apiLevel": "20230606",
|
|
484
|
+
"id": "aa00112233bb",
|
|
485
|
+
"ip": "172.0.0.1",
|
|
486
|
+
"availableFv": null
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
"""
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
def patch_version(apiLevel):
|
|
493
|
+
return f'{{ "device": {{ "apiLevel": {apiLevel} }} }}'
|
|
494
|
+
|
|
495
|
+
DEVICE_INFO_FUTURE = jmerge(DEVICE_INFO, patch_version(future_date()))
|
|
496
|
+
DEVICE_INFO_LATEST = jmerge(
|
|
497
|
+
DEVICE_INFO, patch_version(get_latest_api_level("multiSensor"))
|
|
498
|
+
)
|
|
499
|
+
DEVICE_INFO_UNSUPPORTED = jmerge(DEVICE_INFO, patch_version(20180603))
|
|
500
|
+
DEVICE_INFO_UNSPECIFIED_API = None
|
|
501
|
+
|
|
502
|
+
STATE_DEFAULT = json.loads(
|
|
503
|
+
"""
|
|
504
|
+
{
|
|
505
|
+
"multiSensor": {
|
|
506
|
+
"sensors": [
|
|
507
|
+
{"type": "gaugePressure", "id": 0, "value": 98, "state": 2},
|
|
508
|
+
{"type": "liquidHeight", "id": 1, "value": 100, "state": 2},
|
|
509
|
+
{"type": "fillLevel", "id": 2, "value": 205, "state": 2},
|
|
510
|
+
{"type": "volume", "id": 3, "value": 2000, "state": 2}
|
|
511
|
+
]
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
"""
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
DEVICE_EXTENDED_INFO = STATE_DEFAULT
|
|
518
|
+
DEVICE_EXTENDED_INFO_PATH = "/state/extended"
|
|
519
|
+
|
|
520
|
+
@pytest.mark.parametrize(
|
|
521
|
+
"index,unit,value",
|
|
522
|
+
[
|
|
523
|
+
(0, "mbar", 98),
|
|
524
|
+
(1, "cm", 100),
|
|
525
|
+
(2, "percentage", 20.5),
|
|
526
|
+
(3, "L", 2000),
|
|
527
|
+
],
|
|
528
|
+
)
|
|
529
|
+
async def test_sensor_value(self, aioclient_mock, index, unit, value):
|
|
530
|
+
entity = await self.updated(aioclient_mock, self.STATE_DEFAULT, index=index)
|
|
531
|
+
assert entity._feature.unit == unit
|
|
532
|
+
assert entity.native_value == value
|
|
533
|
+
assert entity._feature.needs_configuration is False
|
|
534
|
+
|
|
535
|
+
@pytest.mark.parametrize("value", [None, 9999])
|
|
536
|
+
async def test_not_configured_state_reports_error(self, aioclient_mock, value):
|
|
537
|
+
"""state == 6 (probe not configured) carries a meaningless or null value."""
|
|
538
|
+
|
|
539
|
+
state_json = jmerge(
|
|
540
|
+
self.STATE_DEFAULT,
|
|
541
|
+
json.dumps({"multiSensor": {"sensors": [{"state": 6, "value": value}]}}),
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
entity = await self.updated(aioclient_mock, state_json, index=0)
|
|
545
|
+
|
|
546
|
+
assert entity.native_value is None
|
|
547
|
+
assert entity._feature.is_error is True
|
|
548
|
+
assert entity._feature.needs_configuration is True
|
|
549
|
+
|
|
550
|
+
async def test_error_state_does_not_need_configuration(self, aioclient_mock):
|
|
551
|
+
state_json = jmerge(
|
|
552
|
+
self.STATE_DEFAULT,
|
|
553
|
+
'{ "multiSensor": { "sensors": [ { "state": 3 } ] } }',
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
entity = await self.updated(aioclient_mock, state_json, index=0)
|
|
557
|
+
|
|
558
|
+
assert entity._feature.is_error is True
|
|
559
|
+
assert entity._feature.needs_configuration is False
|
|
560
|
+
|
|
561
|
+
|
|
465
562
|
class TestAirSensor(DefaultBoxTest):
|
|
466
563
|
"""Tests for sensors representing BleBox airSensor."""
|
|
467
564
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|