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.
- bwt_api-0.6.0/.devcontainer/devcontainer.json +7 -0
- {bwt_api-0.5.0/src/bwt_api.egg-info → bwt_api-0.6.0}/PKG-INFO +4 -2
- {bwt_api-0.5.0 → bwt_api-0.6.0}/pyproject.toml +4 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/setup.cfg +1 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/api.py +38 -3
- bwt_api-0.6.0/src/bwt_api/bwt.py +44 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/skeleton.py +0 -1
- {bwt_api-0.5.0 → bwt_api-0.6.0/src/bwt_api.egg-info}/PKG-INFO +4 -2
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/SOURCES.txt +4 -2
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/requires.txt +1 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/tests/test_api.py +12 -12
- bwt_api-0.6.0/tests/test_distinguish.py +64 -0
- bwt_api-0.5.0/tests/pytest.ini +0 -2
- {bwt_api-0.5.0 → bwt_api-0.6.0}/.coveragerc +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/.github/workflows/python-package.yml +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/.github/workflows/python-publish.yml +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/.gitignore +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/AUTHORS.rst +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/CHANGELOG.rst +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/CONTRIBUTING.rst +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/LICENSE.txt +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/README.rst +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/setup.py +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/__init__.py +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/data.py +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/error.py +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api/exception.py +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/dependency_links.txt +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/entry_points.txt +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/not-zip-safe +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/src/bwt_api.egg-info/top_level.txt +0 -0
- {bwt_api-0.5.0 → bwt_api-0.6.0}/tests/conftest.py +0 -0
- {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.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: bwt_api
|
|
3
|
-
Version: 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"
|
|
@@ -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
|
|
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.")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: bwt_api
|
|
3
|
-
Version: 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/
|
|
29
|
-
tests/
|
|
30
|
+
tests/test_api.py
|
|
31
|
+
tests/test_distinguish.py
|
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
bwt_api-0.5.0/tests/pytest.ini
DELETED
|
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
|