bwt-api 1.0.3__tar.gz → 1.1.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.
Files changed (48) hide show
  1. {bwt_api-1.0.3 → bwt_api-1.1.0}/PKG-INFO +1 -1
  2. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/data.py +12 -46
  3. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/silk_api.py +13 -4
  4. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/PKG-INFO +1 -1
  5. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/scm_file_list.json +26 -26
  6. bwt_api-1.1.0/src/bwt_api.egg-info/scm_version.json +8 -0
  7. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0104.json +1 -1
  8. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0201.json +2 -2
  9. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/test_api.py +44 -3
  10. bwt_api-1.0.3/src/bwt_api.egg-info/scm_version.json +0 -8
  11. {bwt_api-1.0.3 → bwt_api-1.1.0}/.coveragerc +0 -0
  12. {bwt_api-1.0.3 → bwt_api-1.1.0}/.devcontainer/devcontainer.json +0 -0
  13. {bwt_api-1.0.3 → bwt_api-1.1.0}/.github/workflows/python-package.yml +0 -0
  14. {bwt_api-1.0.3 → bwt_api-1.1.0}/.github/workflows/python-publish.yml +0 -0
  15. {bwt_api-1.0.3 → bwt_api-1.1.0}/.gitignore +0 -0
  16. {bwt_api-1.0.3 → bwt_api-1.1.0}/LICENSE.txt +0 -0
  17. {bwt_api-1.0.3 → bwt_api-1.1.0}/README.rst +0 -0
  18. {bwt_api-1.0.3 → bwt_api-1.1.0}/pyproject.toml +0 -0
  19. {bwt_api-1.0.3 → bwt_api-1.1.0}/setup.cfg +0 -0
  20. {bwt_api-1.0.3 → bwt_api-1.1.0}/setup.py +0 -0
  21. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/__init__.py +0 -0
  22. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/api.py +0 -0
  23. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/bwt.py +0 -0
  24. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/bwt_api.py +0 -0
  25. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/error.py +0 -0
  26. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/exception.py +0 -0
  27. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/skeleton.py +0 -0
  28. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api/smart_dos_api.py +0 -0
  29. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/SOURCES.txt +0 -0
  30. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/dependency_links.txt +0 -0
  31. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/entry_points.txt +0 -0
  32. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/not-zip-safe +0 -0
  33. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/requires.txt +0 -0
  34. {bwt_api-1.0.3 → bwt_api-1.1.0}/src/bwt_api.egg-info/top_level.txt +0 -0
  35. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/conftest.py +0 -0
  36. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/perla/GetCurrentData.json +0 -0
  37. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/perla/GetDailyData.json +0 -0
  38. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/perla/GetMonthlyData.json +0 -0
  39. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/perla/GetYearlyData.json +0 -0
  40. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/silk/registers.json +0 -0
  41. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0202.json +0 -0
  42. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0208.json +0 -0
  43. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0401.json +0 -0
  44. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0402.json +0 -0
  45. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0503.json +0 -0
  46. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/data/smartdos/gatt_0505.json +0 -0
  47. {bwt_api-1.0.3 → bwt_api-1.1.0}/tests/test_distinguish.py +0 -0
  48. {bwt_api-1.0.3 → bwt_api-1.1.0}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bwt_api
3
- Version: 1.0.3
3
+ Version: 1.1.0
4
4
  Summary: Python API to access the local BWT Perla API.
5
5
  Home-page: https://github.com/dkarv/bwt_api
6
6
  Author: dkarv
@@ -33,55 +33,21 @@ class SmartDosStatus(enum.IntEnum):
33
33
  def _missing_(cls, value):
34
34
  return None
35
35
 
36
- @classmethod
37
- def normal(cls) -> list["SmartDosStatus"]:
38
- """Return SmartDosStatus values considered normal/OK."""
39
- return [
40
- cls.STANDBY,
41
- cls.METERING_ACTIVE,
42
- ]
36
+ def is_fatal(self) -> bool:
37
+ return self not in WARNING_CODES
43
38
 
44
- @classmethod
45
- def warning(cls) -> list["SmartDosStatus"]:
46
- """Return SmartDosStatus values that indicate warnings."""
47
- return [
48
- cls.MINERAL_CONTAINER_LEVEL_LOW,
49
- cls.SMART_MINERAL_RUNNING_LOW,
50
- ]
39
+ def is_normal(self) -> bool:
40
+ return self in NORMAL_CODES
51
41
 
52
- @classmethod
53
- def error(cls) -> list["SmartDosStatus"]:
54
- """Return SmartDosStatus values that indicate errors."""
55
- return [
56
- cls.MINERAL_CONTAINER_EMPTY,
57
- cls.PUMP_FAULT,
58
- cls.PUMP_POWER_FAULT,
59
- cls.SMART_MINERAL_RUN_OUT,
60
- cls.AQA_VOLUME_ALARM,
61
- cls.AQA_WATCH_ALARM,
62
- cls.AQA_MAXFLOW_ALARM,
63
- cls.PUMP_CONTROL_FAULT,
64
- ]
42
+ NORMAL_CODES = [
43
+ SmartDosStatus.STANDBY,
44
+ SmartDosStatus.METERING_ACTIVE,
45
+ ]
65
46
 
66
- @classmethod
67
- def category(cls, value) -> BwtStatus | None:
68
- """Return the `BwtStatus` category for a SmartDosStatus value.
69
-
70
- Accepts either an int or a SmartDosStatus member. Returns `BwtStatus.OK`,
71
- `BwtStatus.WARNING`, or `BwtStatus.ERROR`, or `None` for unknown values.
72
- """
73
- try:
74
- status = cls(value)
75
- except Exception:
76
- return None
77
-
78
- if status in cls.normal():
79
- return BwtStatus.OK
80
- if status in cls.warning():
81
- return BwtStatus.WARNING
82
- if status in cls.error():
83
- return BwtStatus.ERROR
84
- return None
47
+ WARNING_CODES = [
48
+ SmartDosStatus.MINERAL_CONTAINER_LEVEL_LOW,
49
+ SmartDosStatus.SMART_MINERAL_RUNNING_LOW,
50
+ ]
85
51
 
86
52
 
87
53
  class SubstanceType(enum.IntEnum):
@@ -25,10 +25,10 @@ class BwtSilkApi:
25
25
  async def close(self):
26
26
  await self._session.close()
27
27
 
28
- async def get_registers(self) -> list[int]:
29
- """Internal method to fetch json from the endpoint and handle general errors."""
28
+ async def __get_json(self, path: str):
29
+ """Fetch JSON from a Silk HTTP endpoint and handle general errors."""
30
30
  try:
31
- async with self._session.get(f"http://{self._host}:80/silk/registers") as response:
31
+ async with self._session.get(f"http://{self._host}:80{path}") as response:
32
32
  self._logger.debug(
33
33
  "Response status: %s, content-type: %s",
34
34
  response.status,
@@ -37,10 +37,19 @@ class BwtSilkApi:
37
37
  if response.status == 200:
38
38
  json = await response.json(content_type=None)
39
39
  self._logger.debug("Raw response: %s", json)
40
- return json["params"]
40
+ return json
41
41
  else:
42
42
  text = await response.text()
43
43
  self._logger.warning("Unknown response with status %s: %s", response.status, text)
44
44
  raise ApiException(f"Unknown response: {text}")
45
45
  except aiohttp.ClientConnectorError as e:
46
46
  raise ConnectException from e
47
+
48
+ async def get_registers(self) -> list[int]:
49
+ """Fetch softener register values from /silk/registers."""
50
+ json = await self.__get_json("/silk/registers")
51
+ return json["params"]
52
+
53
+ async def get_status(self) -> dict:
54
+ """Fetch firmware / product metadata from /silk/status."""
55
+ return await self.__get_json("/silk/status")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bwt_api
3
- Version: 1.0.3
3
+ Version: 1.1.0
4
4
  Summary: Python API to access the local BWT Perla API.
5
5
  Home-page: https://github.com/dkarv/bwt_api
6
6
  Author: dkarv
@@ -1,41 +1,41 @@
1
1
  {
2
2
  "files": [
3
- "tox.ini",
3
+ ".coveragerc",
4
+ ".devcontainer/devcontainer.json",
5
+ ".github/workflows/python-package.yml",
6
+ ".github/workflows/python-publish.yml",
7
+ ".gitignore",
4
8
  "LICENSE.txt",
5
9
  "README.rst",
6
- "setup.py",
7
10
  "pyproject.toml",
8
11
  "setup.cfg",
9
- ".gitignore",
10
- ".coveragerc",
11
- ".github/workflows/python-publish.yml",
12
- ".github/workflows/python-package.yml",
13
- ".devcontainer/devcontainer.json",
14
- "tests/test_api.py",
15
- "tests/test_distinguish.py",
12
+ "setup.py",
13
+ "src/bwt_api/__init__.py",
14
+ "src/bwt_api/api.py",
15
+ "src/bwt_api/bwt.py",
16
+ "src/bwt_api/bwt_api.py",
17
+ "src/bwt_api/data.py",
18
+ "src/bwt_api/error.py",
19
+ "src/bwt_api/exception.py",
20
+ "src/bwt_api/silk_api.py",
21
+ "src/bwt_api/skeleton.py",
22
+ "src/bwt_api/smart_dos_api.py",
16
23
  "tests/conftest.py",
24
+ "tests/data/perla/GetCurrentData.json",
25
+ "tests/data/perla/GetDailyData.json",
26
+ "tests/data/perla/GetMonthlyData.json",
27
+ "tests/data/perla/GetYearlyData.json",
17
28
  "tests/data/silk/registers.json",
29
+ "tests/data/smartdos/gatt_0104.json",
30
+ "tests/data/smartdos/gatt_0201.json",
18
31
  "tests/data/smartdos/gatt_0202.json",
19
32
  "tests/data/smartdos/gatt_0208.json",
20
33
  "tests/data/smartdos/gatt_0401.json",
21
- "tests/data/smartdos/gatt_0104.json",
34
+ "tests/data/smartdos/gatt_0402.json",
22
35
  "tests/data/smartdos/gatt_0503.json",
23
36
  "tests/data/smartdos/gatt_0505.json",
24
- "tests/data/smartdos/gatt_0402.json",
25
- "tests/data/smartdos/gatt_0201.json",
26
- "tests/data/perla/GetMonthlyData.json",
27
- "tests/data/perla/GetDailyData.json",
28
- "tests/data/perla/GetCurrentData.json",
29
- "tests/data/perla/GetYearlyData.json",
30
- "src/bwt_api/data.py",
31
- "src/bwt_api/smart_dos_api.py",
32
- "src/bwt_api/exception.py",
33
- "src/bwt_api/__init__.py",
34
- "src/bwt_api/bwt.py",
35
- "src/bwt_api/api.py",
36
- "src/bwt_api/error.py",
37
- "src/bwt_api/bwt_api.py",
38
- "src/bwt_api/skeleton.py",
39
- "src/bwt_api/silk_api.py"
37
+ "tests/test_api.py",
38
+ "tests/test_distinguish.py",
39
+ "tox.ini"
40
40
  ]
41
41
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.1.0",
3
+ "distance": 0,
4
+ "node": "g0d54cc96494e57410f12e506bd2ca275aca1ac57",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-27"
8
+ }
@@ -1,5 +1,5 @@
1
1
  {
2
- "ssid": "Graf WLAN",
2
+ "ssid": "MyWiFi",
3
3
  "rssi": null,
4
4
  "rssiAvg": "-54.00",
5
5
  "rssiSig": "0.00",
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "fwRev": "1.2.0",
3
3
  "hwRev": "2.4.0(A)",
4
- "productCode": "3HZR-1R37",
5
- "iotDevId": "5b29b9e9-24c5-4eea-a599-927922a89f5a",
4
+ "productCode": "1AAA-2BBB",
5
+ "iotDevId": "abcdefgh-1234-5678-abcd-1234567890ab",
6
6
  "iotDevType": "bwt_bewados",
7
7
  "iotDevVariant": "dev",
8
8
  "uptime": 6510811,
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  import pytest
6
6
  from aiohttp.client_reqrep import ClientResponse
7
7
 
8
- from bwt_api.api import BwtApi, BwtSmartDosApi, treated_to_blended
8
+ from bwt_api.api import BwtApi, BwtSmartDosApi, BwtSilkApi, treated_to_blended
9
9
  from bwt_api.error import BwtError
10
10
  from bwt_api.data import CurrentResponse, Hardness, BwtStatus, SmartDosStatus, SubstanceType
11
11
 
@@ -316,7 +316,7 @@ async def test_smartdos_get_wifi_info():
316
316
  )
317
317
  async with BwtSmartDosApi("host") as api:
318
318
  result = await api.get_wifi_info()
319
- assert result.ssid == "Graf WLAN"
319
+ assert result.ssid == "MyWiFi"
320
320
  assert result.rssiAvg == "-54.00"
321
321
  assert result.mac == "AA:BB:CC:DD:EE:FF"
322
322
 
@@ -332,7 +332,7 @@ async def test_smartdos_get_gatt_0201():
332
332
  async with BwtSmartDosApi("host") as api:
333
333
  result = await api.get_gatt_0201()
334
334
  assert result["fwRev"] == "1.2.0"
335
- assert result["productCode"] == "3HZR-1R37"
335
+ assert result["productCode"] == "1AAA-2BBB"
336
336
 
337
337
 
338
338
  async def test_smartdos_unknown_response():
@@ -451,3 +451,44 @@ def test_treated_to_blended():
451
451
  assert treated_to_blended(191, 21, 4) == pytest.approx(235.9411)
452
452
  # Edge case: hardness_in == 0 should return treated as-is, not divide by zero
453
453
  assert treated_to_blended(100, 0, 0) == 100
454
+
455
+
456
+ async def test_silk_get_registers():
457
+ with aioresponses() as mocked:
458
+ mocked.get(
459
+ "http://host:80/silk/registers",
460
+ status=200,
461
+ body='{"params":[0, -1, 8, 30, 250]}',
462
+ headers={"Content-Type": "application/json"},
463
+ )
464
+ async with BwtSilkApi("host") as api:
465
+ result = await api.get_registers()
466
+ assert result == [0, -1, 8, 30, 250]
467
+
468
+
469
+ async def test_silk_get_status():
470
+ with aioresponses() as mocked:
471
+ mocked.get(
472
+ "http://host:80/silk/status",
473
+ status=200,
474
+ body='{"version":"2.3.7","gitver":1234,"productCode":"SILK"}',
475
+ headers={"Content-Type": "application/json"},
476
+ )
477
+ async with BwtSilkApi("host") as api:
478
+ result = await api.get_status()
479
+ assert result["version"] == "2.3.7"
480
+ assert result["gitver"] == 1234
481
+ assert result["productCode"] == "SILK"
482
+
483
+
484
+ async def test_silk_get_status_error():
485
+ with aioresponses() as mocked:
486
+ mocked.get(
487
+ "http://host:80/silk/status",
488
+ status=500,
489
+ body="boom",
490
+ headers={"Content-Type": "text/plain"},
491
+ )
492
+ async with BwtSilkApi("host") as api:
493
+ with pytest.raises(ApiException):
494
+ await api.get_status()
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.0.3",
3
- "distance": 0,
4
- "node": "g59008971f6a96776cdf5cac562c45f27f148eec7",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-08-04"
8
- }
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