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.
Files changed (51) hide show
  1. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/PKG-INFO +1 -1
  2. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/__init__.py +1 -1
  3. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/box.py +4 -1
  4. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/box_types.py +8 -0
  5. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/sensor.py +15 -4
  6. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/PKG-INFO +1 -1
  7. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/setup.cfg +1 -1
  8. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/setup.py +1 -1
  9. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_box.py +18 -0
  10. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_sensor.py +97 -0
  11. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/CONTRIBUTING.rst +0 -0
  12. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/HISTORY.rst +0 -0
  13. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/LICENSE +0 -0
  14. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/MANIFEST.in +0 -0
  15. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/README.rst +0 -0
  16. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/binary_sensor.py +0 -0
  17. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/button.py +0 -0
  18. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/climate.py +0 -0
  19. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/cover.py +0 -0
  20. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/error.py +0 -0
  21. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/feature.py +0 -0
  22. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/jfollow.py +0 -0
  23. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/light.py +0 -0
  24. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/session.py +0 -0
  25. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/switch.py +0 -0
  26. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi/update.py +0 -0
  27. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/SOURCES.txt +0 -0
  28. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/dependency_links.txt +0 -0
  29. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/not-zip-safe +0 -0
  30. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/requires.txt +0 -0
  31. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/blebox_uniapi.egg-info/top_level.txt +0 -0
  32. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/Makefile +0 -0
  33. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/authors.rst +0 -0
  34. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/conf.py +0 -0
  35. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/contributing.rst +0 -0
  36. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/history.rst +0 -0
  37. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/index.rst +0 -0
  38. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/installation.rst +0 -0
  39. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/make.bat +0 -0
  40. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/readme.rst +0 -0
  41. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/docs/usage.rst +0 -0
  42. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_box_types.py +0 -0
  43. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_button.py +0 -0
  44. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_climate.py +0 -0
  45. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_cover.py +0 -0
  46. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_light.py +0 -0
  47. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_light_unit.py +0 -0
  48. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_session.py +0 -0
  49. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_switch.py +0 -0
  50. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_unknown.py +0 -0
  51. {blebox_uniapi-2.5.7 → blebox_uniapi-2.5.8}/tests/test_update.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blebox_uniapi
3
- Version: 2.5.7
3
+ Version: 2.5.8
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
@@ -2,4 +2,4 @@
2
2
 
3
3
  __author__ = """BleBox"""
4
4
  __email__ = "opensource@blebox.eu"
5
- __version__ = "2.5.7"
5
+ __version__ = "2.5.8"
@@ -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
- response = await self._session.async_api_get("/info")
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 not isinstance(raw, (int, float)):
153
- return None
154
- return int(raw)
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
- return state == BleboxSensorState.ERROR
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blebox_uniapi
3
- Version: 2.5.7
3
+ Version: 2.5.8
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
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 2.5.7
2
+ current_version = 2.5.8
3
3
  commit = True
4
4
  tag = True
5
5
  tag_name = v{new_version}
@@ -23,7 +23,7 @@ test_requirements = [
23
23
  ]
24
24
 
25
25
  setup(
26
- version="2.5.7",
26
+ version="2.5.8",
27
27
  author="BleBox",
28
28
  author_email="opensource@blebox.eu",
29
29
  python_requires=">=3.9",
@@ -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