bwt-api 0.5.0__tar.gz → 0.6.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 (33) hide show
  1. bwt_api-0.6.0/.devcontainer/devcontainer.json +7 -0
  2. {bwt_api-0.5.0/src/bwt_api.egg-info → bwt_api-0.6.0}/PKG-INFO +4 -2
  3. {bwt_api-0.5.0 → bwt_api-0.6.0}/pyproject.toml +4 -0
  4. {bwt_api-0.5.0 → bwt_api-0.6.0}/setup.cfg +1 -0
  5. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/api.py +38 -3
  6. bwt_api-0.6.0/src/bwt_api/bwt.py +44 -0
  7. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/skeleton.py +0 -1
  8. {bwt_api-0.5.0 → bwt_api-0.6.0/src/bwt_api.egg-info}/PKG-INFO +4 -2
  9. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/SOURCES.txt +4 -2
  10. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/requires.txt +1 -0
  11. {bwt_api-0.5.0 → bwt_api-0.6.0}/tests/test_api.py +12 -12
  12. bwt_api-0.6.0/tests/test_distinguish.py +64 -0
  13. bwt_api-0.5.0/tests/pytest.ini +0 -2
  14. {bwt_api-0.5.0 → bwt_api-0.6.0}/.coveragerc +0 -0
  15. {bwt_api-0.5.0 → bwt_api-0.6.0}/.github/workflows/python-package.yml +0 -0
  16. {bwt_api-0.5.0 → bwt_api-0.6.0}/.github/workflows/python-publish.yml +0 -0
  17. {bwt_api-0.5.0 → bwt_api-0.6.0}/.gitignore +0 -0
  18. {bwt_api-0.5.0 → bwt_api-0.6.0}/AUTHORS.rst +0 -0
  19. {bwt_api-0.5.0 → bwt_api-0.6.0}/CHANGELOG.rst +0 -0
  20. {bwt_api-0.5.0 → bwt_api-0.6.0}/CONTRIBUTING.rst +0 -0
  21. {bwt_api-0.5.0 → bwt_api-0.6.0}/LICENSE.txt +0 -0
  22. {bwt_api-0.5.0 → bwt_api-0.6.0}/README.rst +0 -0
  23. {bwt_api-0.5.0 → bwt_api-0.6.0}/setup.py +0 -0
  24. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/__init__.py +0 -0
  25. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/data.py +0 -0
  26. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/error.py +0 -0
  27. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/exception.py +0 -0
  28. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/dependency_links.txt +0 -0
  29. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/entry_points.txt +0 -0
  30. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/not-zip-safe +0 -0
  31. {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/top_level.txt +0 -0
  32. {bwt_api-0.5.0 → bwt_api-0.6.0}/tests/conftest.py +0 -0
  33. {bwt_api-0.5.0 → bwt_api-0.6.0}/tox.ini +0 -0
@@ -0,0 +1,7 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/python
3
+ {
4
+ "name": "Python 3",
5
+ "image": "mcr.microsoft.com/devcontainers/python:1-3.13-bullseye",
6
+ "postCreateCommand": "pip install --upgrade pip && pip install .[testing]"
7
+ }
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: bwt_api
3
- Version: 0.5.0
3
+ Version: 0.6.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
@@ -22,7 +22,9 @@ Requires-Dist: flake8; extra == "testing"
22
22
  Requires-Dist: setuptools; extra == "testing"
23
23
  Requires-Dist: pytest; extra == "testing"
24
24
  Requires-Dist: pytest-cov; extra == "testing"
25
+ Requires-Dist: pytest-asyncio; extra == "testing"
25
26
  Requires-Dist: aioresponses; extra == "testing"
27
+ Dynamic: license-file
26
28
 
27
29
 
28
30
  bwt_api
@@ -7,3 +7,7 @@ build-backend = "setuptools.build_meta"
7
7
  # For smarter version schemes and other configuration options,
8
8
  # check out https://github.com/pypa/setuptools_scm
9
9
  version_scheme = "no-guess-dev"
10
+
11
+ [tool.pytest.ini_options]
12
+ pythonpath = ["src",]
13
+ asyncio_mode = "auto"
@@ -39,6 +39,7 @@ testing =
39
39
  setuptools
40
40
  pytest
41
41
  pytest-cov
42
+ pytest-asyncio
42
43
  aioresponses
43
44
 
44
45
  [options.entry_points]
@@ -6,7 +6,6 @@ import aiohttp
6
6
  import base64
7
7
  import logging
8
8
  from datetime import datetime
9
- from zoneinfo import ZoneInfo
10
9
 
11
10
  from bwt_api.error import BwtError
12
11
  from bwt_api.exception import ApiException, ConnectException, WrongCodeException
@@ -57,10 +56,10 @@ class BwtApi:
57
56
  raise ConnectException from e
58
57
 
59
58
  def _convert_datetime(self, input: str) -> datetime:
60
- # It looks like the device sends and even shows everything in UTC
59
+ # It looks like the device sends and even shows everything in local time
61
60
  return datetime.strptime(
62
61
  input, "%Y-%m-%d %H:%M:%S"
63
- ).replace(tzinfo=ZoneInfo("UTC"))
62
+ )# .replace(tzinfo=ZoneInfo("UTC"))
64
63
 
65
64
  async def get_current_data(self) -> CurrentResponse:
66
65
  """Get the current state of the BWT."""
@@ -135,6 +134,42 @@ class BwtApi:
135
134
  keys = [f"Month{month:02}_l" for month in range(1, 13)]
136
135
  return YearlyResponse(list(map(lambda k: raw[k], keys)))
137
136
 
137
+
138
+ class BwtSilkApi:
139
+ """BWT Silk Api."""
140
+ _session: aiohttp.ClientSession
141
+ _host: str
142
+
143
+ def __init__(self, host):
144
+ self._host = host
145
+ self._session = aiohttp.ClientSession()
146
+
147
+ async def __aenter__(self):
148
+ return self
149
+
150
+ async def __aexit__(self, *err):
151
+ await self.close()
152
+
153
+ async def close(self):
154
+ await self._session.close()
155
+
156
+ async def get_registers(self) -> list[int]:
157
+ """Internal method to fetch json from the endpoint and handle general errors."""
158
+ try:
159
+ async with self._session.get(f"http://{self._host}:80/silk/registers") as response:
160
+ _logger.debug(f"Response status: {response.status}, content-type: {response.headers['content-type']}")
161
+ if (response.status == 200):
162
+ json = await response.json(content_type=None)
163
+ _logger.debug(f"Raw response: {json}")
164
+ return json["params"]
165
+ else:
166
+ text = await response.text()
167
+ _logger.warning(f"Unknown response with status {response.status}: {text}")
168
+ raise ApiException(f"Unknown response: {text}")
169
+ except aiohttp.ClientConnectorError as e:
170
+ raise ConnectException from e
171
+
172
+
138
173
  def treated_to_blended(treated: int, hardness_in: int, hardness_out: int) -> float:
139
174
  if (hardness_in == 0 | hardness_in == hardness_out):
140
175
  return treated
@@ -0,0 +1,44 @@
1
+ """The BWT model check."""
2
+
3
+
4
+ from enum import Enum
5
+ import aiohttp
6
+ import logging
7
+
8
+ from bwt_api.exception import ConnectException
9
+
10
+ _logger = logging.getLogger(__name__)
11
+
12
+ class BwtModel(Enum):
13
+ """Enum for BWT models."""
14
+ PERLA_LOCAL_API = 1
15
+ PERLA_SILK = 2
16
+
17
+ async def determine_bwt_model(host: str) -> BwtModel:
18
+ """Determine the BWT model based on the api response."""
19
+
20
+ _logger.debug(f"Determining BWT model for host {host}")
21
+ timeout = aiohttp.ClientTimeout(total=3)
22
+ # Try :8080/api/GetCurrentData
23
+ try:
24
+ async with aiohttp.ClientSession() as session:
25
+ async with session.get(f"http://{host}:8080/api", timeout=timeout) as response:
26
+ res = await response.text()
27
+ _logger.debug(f"Response from {host}:8080/api: {response.status} - {res}")
28
+ if response.status == 404 and res == "Not Found":
29
+ return BwtModel.PERLA_LOCAL_API
30
+ except Exception:
31
+ pass
32
+
33
+ # Try :80/status
34
+ try:
35
+ async with aiohttp.ClientSession() as session:
36
+ async with session.get(f"http://{host}:80/silk/registers", timeout=timeout) as response:
37
+ res = await response.text()
38
+ _logger.debug(f"Response from {host}:80/silk/registers: {response.status} - {res}")
39
+ if response.status == 200 and res.startswith("""{"params":["""):
40
+ return BwtModel.PERLA_SILK
41
+ except Exception:
42
+ pass
43
+
44
+ raise ConnectException(f"Could not determine BWT model for host {host}. Please check the connection or the host address.")
@@ -5,7 +5,6 @@ import asyncio
5
5
 
6
6
 
7
7
  from bwt_api import __version__
8
- import bwt_api
9
8
  from bwt_api.exception import BwtException, WrongCodeException, ConnectException
10
9
  from .api import BwtApi
11
10
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: bwt_api
3
- Version: 0.5.0
3
+ Version: 0.6.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
@@ -22,7 +22,9 @@ Requires-Dist: flake8; extra == "testing"
22
22
  Requires-Dist: setuptools; extra == "testing"
23
23
  Requires-Dist: pytest; extra == "testing"
24
24
  Requires-Dist: pytest-cov; extra == "testing"
25
+ Requires-Dist: pytest-asyncio; extra == "testing"
25
26
  Requires-Dist: aioresponses; extra == "testing"
27
+ Dynamic: license-file
26
28
 
27
29
 
28
30
  bwt_api
@@ -9,10 +9,12 @@ pyproject.toml
9
9
  setup.cfg
10
10
  setup.py
11
11
  tox.ini
12
+ .devcontainer/devcontainer.json
12
13
  .github/workflows/python-package.yml
13
14
  .github/workflows/python-publish.yml
14
15
  src/bwt_api/__init__.py
15
16
  src/bwt_api/api.py
17
+ src/bwt_api/bwt.py
16
18
  src/bwt_api/data.py
17
19
  src/bwt_api/error.py
18
20
  src/bwt_api/exception.py
@@ -25,5 +27,5 @@ src/bwt_api.egg-info/not-zip-safe
25
27
  src/bwt_api.egg-info/requires.txt
26
28
  src/bwt_api.egg-info/top_level.txt
27
29
  tests/conftest.py
28
- tests/pytest.ini
29
- tests/test_api.py
30
+ tests/test_api.py
31
+ tests/test_distinguish.py
@@ -5,4 +5,5 @@ flake8
5
5
  setuptools
6
6
  pytest
7
7
  pytest-cov
8
+ pytest-asyncio
8
9
  aioresponses
@@ -168,10 +168,10 @@ async def test_current_data():
168
168
  in_hardness=Hardness(caco3=374, dH=21, fH=37, mmol=4),
169
169
  out_hardness=Hardness(caco3=71, dH=4, fH=7, mmol=1),
170
170
  holiday_mode=0,
171
- regeneration_last_1=datetime(2023, 11, 16, 4, 42,15,0,ZoneInfo("UTC")),
172
- regeneration_last_2=datetime(2023, 11, 15, 4, 41,48,0,ZoneInfo("UTC")),
173
- service_customer=datetime(2023, 5, 18, 10, 51,7,0,ZoneInfo("UTC")),
174
- service_technician=datetime(2021, 1, 25, 13, 14,6,0,ZoneInfo("UTC")),
171
+ regeneration_last_1=datetime(2023, 11, 16, 4, 42,15,0),
172
+ regeneration_last_2=datetime(2023, 11, 15, 4, 41,48,0),
173
+ service_customer=datetime(2023, 5, 18, 10, 51,7,0),
174
+ service_technician=datetime(2021, 1, 25, 13, 14,6,0),
175
175
  out_of_service=0,
176
176
  regeneration_count_1=754,
177
177
  regeneration_count_2=751,
@@ -203,10 +203,10 @@ async def test_empty_errors():
203
203
  in_hardness=Hardness(caco3=374, dH=21, fH=37, mmol=4),
204
204
  out_hardness=Hardness(caco3=71, dH=4, fH=7, mmol=1),
205
205
  holiday_mode=0,
206
- regeneration_last_1=datetime(2023, 11, 16, 4, 42,15,0,ZoneInfo("UTC")),
207
- regeneration_last_2=datetime(2023, 11, 15, 4, 41,48,0,ZoneInfo("UTC")),
208
- service_customer=datetime(2023, 5, 18, 10, 51,7,0,ZoneInfo("UTC")),
209
- service_technician=datetime(2021, 1, 25, 13, 14,6,0,ZoneInfo("UTC")),
206
+ regeneration_last_1=datetime(2023, 11, 16, 4, 42,15,0),
207
+ regeneration_last_2=datetime(2023, 11, 15, 4, 41,48,0),
208
+ service_customer=datetime(2023, 5, 18, 10, 51,7,0),
209
+ service_technician=datetime(2021, 1, 25, 13, 14,6,0),
210
210
  out_of_service=0,
211
211
  regeneration_count_1=754,
212
212
  regeneration_count_2=751,
@@ -238,10 +238,10 @@ async def test_perla_one():
238
238
  in_hardness=Hardness(caco3=409, dH=23, fH=41, mmol=4),
239
239
  out_hardness=Hardness(caco3=107, dH=6, fH=11, mmol=1),
240
240
  holiday_mode=0,
241
- regeneration_last_1=datetime(2025, 1, 15, 3, 0, 15, 0, ZoneInfo("UTC")),
242
- regeneration_last_2=datetime(1970, 1, 1, 0, 59, 59, 0, ZoneInfo("UTC")),
243
- service_customer=datetime(2024, 11, 25, 9, 13, 48, 0, ZoneInfo("UTC")),
244
- service_technician=datetime(2023, 8, 7, 11, 13, 51, 0, ZoneInfo("UTC")),
241
+ regeneration_last_1=datetime(2025, 1, 15, 3, 0, 15, 0),
242
+ regeneration_last_2=datetime(1970, 1, 1, 0, 59, 59, 0),
243
+ service_customer=datetime(2024, 11, 25, 9, 13, 48, 0),
244
+ service_technician=datetime(2023, 8, 7, 11, 13, 51, 0),
245
245
  out_of_service=0,
246
246
  regeneration_count_1=692,
247
247
  regeneration_count_2=0,
@@ -0,0 +1,64 @@
1
+ from datetime import datetime
2
+ from zoneinfo import ZoneInfo
3
+ import pytest
4
+
5
+ from bwt_api.api import BwtApi, treated_to_blended
6
+ from bwt_api.bwt import BwtModel, determine_bwt_model
7
+ from bwt_api.error import BwtError
8
+ from bwt_api.data import CurrentResponse, Hardness, BwtStatus
9
+
10
+ from aioresponses import aioresponses
11
+
12
+ from bwt_api.exception import ApiException, ConnectException, WrongCodeException
13
+
14
+ new_json = """
15
+ {
16
+ "ActiveErrorIDs" : "5,32,34",
17
+ "BlendedWaterSinceSetup_l" : 318383,
18
+ "CapacityColumn1_ml_dH" : 5485275,
19
+ "CapacityColumn2_ml_dH" : 3833994,
20
+ "CurrentFlowrate_l_h" : 0,
21
+ "DosingSinceSetup_ml" : 0,
22
+ "FirmwareVersion" : "2.0207",
23
+ "HardnessIN_CaCO3" : 374,
24
+ "HardnessIN_dH" : 21,
25
+ "HardnessIN_fH" : 37,
26
+ "HardnessIN_mmol_l" : 4,
27
+ "HardnessOUT_CaCO3" : 71,
28
+ "HardnessOUT_dH" : 4,
29
+ "HardnessOUT_fH" : 7,
30
+ "HardnessOUT_mmol_l" : 1,
31
+ "HolidayModeStartTime" : 0,
32
+ "LastRegenerationColumn1" : "2023-11-16 04:42:15",
33
+ "LastRegenerationColumn2" : "2023-11-15 04:41:48",
34
+ "LastServiceCustomer" : "2023-05-18 10:51:07",
35
+ "LastServiceTechnican" : "2021-01-25 13:14:06",
36
+ "OutOfService" : 0,
37
+ "RegenerationCountSinceSetup" : 1505,
38
+ "RegenerationCounterColumn1" : 754,
39
+ "RegenerationCounterColumn2" : 751,
40
+ "RegenerativLevel" : 20,
41
+ "RegenerativRemainingDays" : 26,
42
+ "RegenerativSinceSetup_g" : 245846,
43
+ "ShowError" : 2,
44
+ "WaterSinceSetup_l" : 261633,
45
+ "WaterTreatedCurrentDay_l" : 181,
46
+ "WaterTreatedCurrentMonth_l" : 3137,
47
+ "WaterTreatedCurrentYear_l" : 80700
48
+ }
49
+ """
50
+
51
+ silk_json = """{"params":[0,-1,18,23,285,29,16,2,0,9,8,1,8,4,296,158,0,52,2138,9,40,0,100,2275,11,20,50,1,1,0,160,141,20,0,678,1,-1,-1,6,1,1,15,355,-1,-1,-1,-1,0]}"""
52
+
53
+ async def test_local_api():
54
+ with aioresponses() as mocked:
55
+ mocked.get("http://host:8080/api", status=404, body="Not Found")
56
+ result = await determine_bwt_model("host")
57
+ assert result == BwtModel.PERLA_LOCAL_API
58
+
59
+ async def test_silk():
60
+ with aioresponses() as mocked:
61
+ mocked.get("http://host:8080/api", status=404, body="Some other webservice")
62
+ mocked.get("http://host:80/silk/registers", status=200, body=silk_json)
63
+ result = await determine_bwt_model("host")
64
+ assert result == BwtModel.PERLA_SILK
@@ -1,2 +0,0 @@
1
- [pytest]
2
- asyncio_mode=auto
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