smartbox 2.3.1__tar.gz → 2.4.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.
- {smartbox-2.3.1 → smartbox-2.4.0}/.github/workflows/publish-to-pypi.yml +2 -2
- {smartbox-2.3.1 → smartbox-2.4.0}/.github/workflows/python-package.yml +7 -7
- {smartbox-2.3.1 → smartbox-2.4.0}/.pre-commit-config.yaml +3 -3
- {smartbox-2.3.1 → smartbox-2.4.0}/PKG-INFO +1 -1
- {smartbox-2.3.1 → smartbox-2.4.0}/pyproject.toml +1 -1
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/models.py +7 -1
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/reseller.py +17 -1
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/session.py +1 -1
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/update_manager.py +27 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/test_reseller.py +2 -4
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/test_update_manager.py +58 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tox.ini +0 -2
- {smartbox-2.3.1 → smartbox-2.4.0}/uv.lock +283 -244
- {smartbox-2.3.1 → smartbox-2.4.0}/.github/FUNDING.yml +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/.github/ISSUE_TEMPLATE/new-reseller.md +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/.github/dependabot.yml +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/.gitignore +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/.vscode/settings.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/CHANGELOG.md +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/CODEOWNERS +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/LICENSE +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/README.md +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/api-notes.md +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/codecov.yml +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/__init__.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/cmd.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/error.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/py.typed +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/src/smartbox/socket.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/__init__.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/common.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/conftest.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/acm/1/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/acm/1/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/acm/1/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/connected.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr/0/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr/0/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr/0/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr_system/power_limit.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/mgr/away_status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/mgr/nodes.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/pmo/3/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/pmo/3/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device1/pmo/3/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/connected.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/0/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/0/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/0/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/1/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/1/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/1/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/2/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/2/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/2/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/3/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/3/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/3/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/4/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/4/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/4/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/5/samples.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/5/setup.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/5/status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_system/power_limit.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/mgr/away_status.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs/device2/mgr/nodes.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/devs.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/grouped_devs.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/groups/test_home/guest_users.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/fixtures/test_path.json +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/test_cmd.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/test_models.py +0 -0
- {smartbox-2.3.1 → smartbox-2.4.0}/tests/test_session.py +0 -0
|
@@ -14,7 +14,7 @@ jobs:
|
|
|
14
14
|
permissions:
|
|
15
15
|
id-token: write
|
|
16
16
|
steps:
|
|
17
|
-
- uses: actions/checkout@
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
18
|
with:
|
|
19
19
|
fetch-depth: 0
|
|
20
20
|
- name: Install uv
|
|
@@ -22,7 +22,7 @@ jobs:
|
|
|
22
22
|
with:
|
|
23
23
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
24
24
|
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
|
25
|
-
uses: actions/setup-python@v6.
|
|
25
|
+
uses: actions/setup-python@v6.1.0
|
|
26
26
|
with:
|
|
27
27
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
28
28
|
- name: Build a binary wheel and a source tarball
|
|
@@ -18,13 +18,13 @@ jobs:
|
|
|
18
18
|
matrix:
|
|
19
19
|
python-version: [3.13, 3.14]
|
|
20
20
|
steps:
|
|
21
|
-
- uses: actions/checkout@
|
|
21
|
+
- uses: actions/checkout@v6
|
|
22
22
|
- name: Install uv
|
|
23
23
|
uses: astral-sh/setup-uv@v7
|
|
24
24
|
with:
|
|
25
25
|
python-version: ${{ matrix.python-version }}
|
|
26
26
|
- name: Set up Python ${{ matrix.python-version }}
|
|
27
|
-
uses: actions/setup-python@v6.
|
|
27
|
+
uses: actions/setup-python@v6.1.0
|
|
28
28
|
with:
|
|
29
29
|
python-version: ${{ matrix.python-version }}
|
|
30
30
|
- name: Install the project
|
|
@@ -40,13 +40,13 @@ jobs:
|
|
|
40
40
|
python-version: [3.13, 3.14]
|
|
41
41
|
|
|
42
42
|
steps:
|
|
43
|
-
- uses: actions/checkout@
|
|
43
|
+
- uses: actions/checkout@v6
|
|
44
44
|
- name: Install uv
|
|
45
45
|
uses: astral-sh/setup-uv@v7
|
|
46
46
|
with:
|
|
47
47
|
python-version: ${{ matrix.python-version }}
|
|
48
48
|
- name: Set up Python ${{ matrix.python-version }}
|
|
49
|
-
uses: actions/setup-python@v6.
|
|
49
|
+
uses: actions/setup-python@v6.1.0
|
|
50
50
|
with:
|
|
51
51
|
python-version: ${{ matrix.python-version }}
|
|
52
52
|
- name: Install the project
|
|
@@ -62,14 +62,14 @@ jobs:
|
|
|
62
62
|
python-version: [3.13, 3.14]
|
|
63
63
|
|
|
64
64
|
steps:
|
|
65
|
-
- uses: actions/checkout@
|
|
65
|
+
- uses: actions/checkout@v6
|
|
66
66
|
- name: Install uv
|
|
67
67
|
uses: astral-sh/setup-uv@v7
|
|
68
68
|
with:
|
|
69
69
|
python-version: ${{ matrix.python-version }}
|
|
70
70
|
|
|
71
71
|
- name: Set up Python ${{ matrix.python-version }}
|
|
72
|
-
uses: actions/setup-python@v6.
|
|
72
|
+
uses: actions/setup-python@v6.1.0
|
|
73
73
|
with:
|
|
74
74
|
python-version: ${{ matrix.python-version }}
|
|
75
75
|
- name: Install the project
|
|
@@ -77,7 +77,7 @@ jobs:
|
|
|
77
77
|
uv sync --all-extras --dev
|
|
78
78
|
uv run tox -e py
|
|
79
79
|
- name: "Upload coverage to Codecov"
|
|
80
|
-
uses: codecov/codecov-action@v5.5.
|
|
80
|
+
uses: codecov/codecov-action@v5.5.2
|
|
81
81
|
with:
|
|
82
82
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
83
83
|
fail_ci_if_error: true
|
|
@@ -2,13 +2,13 @@ repos:
|
|
|
2
2
|
|
|
3
3
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
|
4
4
|
# uv version.
|
|
5
|
-
rev: 0.
|
|
5
|
+
rev: 0.9.25
|
|
6
6
|
hooks:
|
|
7
7
|
# Update the uv lockfile
|
|
8
8
|
- id: uv-lock
|
|
9
9
|
|
|
10
10
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
11
|
-
rev:
|
|
11
|
+
rev: v6.0.0
|
|
12
12
|
hooks:
|
|
13
13
|
- id: trailing-whitespace
|
|
14
14
|
- id: end-of-file-fixer
|
|
@@ -22,7 +22,7 @@ repos:
|
|
|
22
22
|
- "--indent=4"
|
|
23
23
|
|
|
24
24
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
25
|
-
rev: v0.
|
|
25
|
+
rev: v0.14.11
|
|
26
26
|
hooks:
|
|
27
27
|
- id: ruff
|
|
28
28
|
args: [--fix, --exit-non-zero-on-fix]
|
|
@@ -83,6 +83,13 @@ class NodeSetup(RootModel[DefaultNodeSetup | PmoSetup]):
|
|
|
83
83
|
"""Get the root model directly."""
|
|
84
84
|
return getattr(self.root, name)
|
|
85
85
|
|
|
86
|
+
class NodeVersion(BaseModel):
|
|
87
|
+
"""NodeVersion model."""
|
|
88
|
+
|
|
89
|
+
hw_version: str
|
|
90
|
+
fw_version: str
|
|
91
|
+
uid: str
|
|
92
|
+
pid: str
|
|
86
93
|
|
|
87
94
|
class DefaultNodeStatus(BaseModel):
|
|
88
95
|
"""Default Node Status."""
|
|
@@ -166,7 +173,6 @@ class Node(BaseModel):
|
|
|
166
173
|
installed: bool
|
|
167
174
|
lost: bool | None = False
|
|
168
175
|
|
|
169
|
-
|
|
170
176
|
class Nodes(BaseModel):
|
|
171
177
|
"""Nodes model."""
|
|
172
178
|
|
|
@@ -31,6 +31,12 @@ class AvailableResellers:
|
|
|
31
31
|
api_url="api",
|
|
32
32
|
serial_id=1,
|
|
33
33
|
),
|
|
34
|
+
"api-lhz": SmartboxReseller(
|
|
35
|
+
name="Lucht LHZ",
|
|
36
|
+
web_url="https://smartcontrol.lucht-lhz.de/",
|
|
37
|
+
api_url="api-lhz",
|
|
38
|
+
serial_id=2,
|
|
39
|
+
),
|
|
34
40
|
"api-ehc": SmartboxReseller(
|
|
35
41
|
name="Electric Heating Company",
|
|
36
42
|
web_url="https://control.electric-heatingcompany.co.uk/",
|
|
@@ -55,6 +61,12 @@ class AvailableResellers:
|
|
|
55
61
|
api_url="api-climastar",
|
|
56
62
|
serial_id=8,
|
|
57
63
|
),
|
|
64
|
+
"api-iheatcontrol": SmartboxReseller(
|
|
65
|
+
name="iHeat Control",
|
|
66
|
+
web_url="https://app.iheatcontrol.com/",
|
|
67
|
+
api_url="api-evconfort",
|
|
68
|
+
serial_id=9,
|
|
69
|
+
),
|
|
58
70
|
"api-hjm": SmartboxReseller(
|
|
59
71
|
name="HJM",
|
|
60
72
|
web_url="https://api.calorhjm.com/",
|
|
@@ -107,7 +119,11 @@ class AvailableResellers:
|
|
|
107
119
|
def reseller(self) -> SmartboxReseller:
|
|
108
120
|
"""Get the reseller."""
|
|
109
121
|
reseller = next(
|
|
110
|
-
(
|
|
122
|
+
(
|
|
123
|
+
value
|
|
124
|
+
for key, value in self.resellers.items()
|
|
125
|
+
if key == self._api_url
|
|
126
|
+
),
|
|
111
127
|
None,
|
|
112
128
|
)
|
|
113
129
|
if reseller is None:
|
|
@@ -192,7 +192,7 @@ class AsyncSession:
|
|
|
192
192
|
self.expiry_time,
|
|
193
193
|
)
|
|
194
194
|
except ValidationError as e:
|
|
195
|
-
msg = "Received invalid auth response"
|
|
195
|
+
msg = f"Received invalid auth response {response.status} with msg: {response.reason}"
|
|
196
196
|
raise InvalidAuthError(msg) from e
|
|
197
197
|
|
|
198
198
|
async def check_refresh_auth(self) -> None:
|
|
@@ -251,6 +251,33 @@ class UpdateManager:
|
|
|
251
251
|
update_wrapper,
|
|
252
252
|
)
|
|
253
253
|
|
|
254
|
+
def subscribe_to_node_version(
|
|
255
|
+
self,
|
|
256
|
+
callback: Callable[[str, int, dict[str, Any]], None],
|
|
257
|
+
) -> None:
|
|
258
|
+
"""Subscribe to node version updates."""
|
|
259
|
+
|
|
260
|
+
def dev_data_wrapper(data: dict[str, Any]) -> None:
|
|
261
|
+
(callback(data["type"], int(data["addr"]), data["version"]),) # type: ignore[func-returns-value]
|
|
262
|
+
|
|
263
|
+
self.subscribe_to_dev_data(
|
|
264
|
+
"(.nodes[] | {addr, type, version})?",
|
|
265
|
+
dev_data_wrapper,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def update_wrapper(
|
|
269
|
+
data: dict[str, Any],
|
|
270
|
+
node_type: str,
|
|
271
|
+
addr: str,
|
|
272
|
+
) -> None:
|
|
273
|
+
(callback(node_type, int(addr), data),) # type: ignore[func-returns-value]
|
|
274
|
+
|
|
275
|
+
self.subscribe_to_updates(
|
|
276
|
+
r"^/(?P<node_type>[^/]+)/(?P<addr>\d+)/version",
|
|
277
|
+
self.BODY_PATH,
|
|
278
|
+
update_wrapper,
|
|
279
|
+
)
|
|
280
|
+
|
|
254
281
|
def _dev_data_cb(self, data: dict[str, Any]) -> None:
|
|
255
282
|
for sub in self._dev_data_subscriptions:
|
|
256
283
|
sub.match(data)
|
|
@@ -48,10 +48,8 @@ def test_reseller_invalid_data():
|
|
|
48
48
|
|
|
49
49
|
@pytest.mark.asyncio
|
|
50
50
|
async def test_all_resellers():
|
|
51
|
-
for
|
|
52
|
-
_session = AsyncSmartboxSession(
|
|
53
|
-
username="", password="", api_name=reseller.api_url
|
|
54
|
-
)
|
|
51
|
+
for key in AvailableResellers.resellers:
|
|
52
|
+
_session = AsyncSmartboxSession(username="", password="", api_name=key)
|
|
55
53
|
check = await _session.health_check()
|
|
56
54
|
assert check is not None
|
|
57
55
|
version = await _session.api_version()
|
|
@@ -224,3 +224,61 @@ def test_update_manager_subscribe_to_device_connected(update_manager):
|
|
|
224
224
|
update_data = {"path": "/connected", "body": {"connected": True}}
|
|
225
225
|
update_manager._update_cb(update_data)
|
|
226
226
|
callback.assert_called_once_with(update_data["body"]["connected"])
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def test_update_manager_subscribe_to_node_version(update_manager):
|
|
231
|
+
callback = MagicMock()
|
|
232
|
+
update_manager.subscribe_to_node_version(callback)
|
|
233
|
+
assert len(update_manager._dev_data_subscriptions) == 1
|
|
234
|
+
assert len(update_manager._update_subscriptions) == 1
|
|
235
|
+
|
|
236
|
+
# Test dev data callback
|
|
237
|
+
dev_data = {
|
|
238
|
+
"nodes": [
|
|
239
|
+
{
|
|
240
|
+
"type": "acm",
|
|
241
|
+
"addr": 2,
|
|
242
|
+
"version": {
|
|
243
|
+
"pid": "081c",
|
|
244
|
+
"fw_version": "1.6",
|
|
245
|
+
"hw_version": "1.0",
|
|
246
|
+
"uid": "test123",
|
|
247
|
+
},
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
}
|
|
251
|
+
update_manager._dev_data_cb(dev_data)
|
|
252
|
+
callback.assert_called_once_with(
|
|
253
|
+
"acm",
|
|
254
|
+
2,
|
|
255
|
+
{
|
|
256
|
+
"pid": "081c",
|
|
257
|
+
"fw_version": "1.6",
|
|
258
|
+
"hw_version": "1.0",
|
|
259
|
+
"uid": "test123",
|
|
260
|
+
},
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Test update callback
|
|
264
|
+
callback.reset_mock()
|
|
265
|
+
update_data = {
|
|
266
|
+
"path": "/acm/2/version",
|
|
267
|
+
"body": {
|
|
268
|
+
"pid": "081c",
|
|
269
|
+
"fw_version": "1.7",
|
|
270
|
+
"hw_version": "1.0",
|
|
271
|
+
"uid": "test123",
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
update_manager._update_cb(update_data)
|
|
275
|
+
callback.assert_called_once_with(
|
|
276
|
+
"acm",
|
|
277
|
+
2,
|
|
278
|
+
{
|
|
279
|
+
"pid": "081c",
|
|
280
|
+
"fw_version": "1.7",
|
|
281
|
+
"hw_version": "1.0",
|
|
282
|
+
"uid": "test123",
|
|
283
|
+
},
|
|
284
|
+
)
|