smartbox 2.3.2__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.
Files changed (76) hide show
  1. {smartbox-2.3.2 → smartbox-2.4.0}/.github/workflows/publish-to-pypi.yml +2 -2
  2. {smartbox-2.3.2 → smartbox-2.4.0}/.github/workflows/python-package.yml +1 -1
  3. {smartbox-2.3.2 → smartbox-2.4.0}/.pre-commit-config.yaml +3 -3
  4. {smartbox-2.3.2 → smartbox-2.4.0}/PKG-INFO +1 -1
  5. {smartbox-2.3.2 → smartbox-2.4.0}/pyproject.toml +1 -1
  6. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/models.py +7 -1
  7. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/reseller.py +12 -0
  8. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/update_manager.py +27 -0
  9. {smartbox-2.3.2 → smartbox-2.4.0}/tests/test_update_manager.py +58 -0
  10. {smartbox-2.3.2 → smartbox-2.4.0}/tox.ini +0 -2
  11. {smartbox-2.3.2 → smartbox-2.4.0}/uv.lock +267 -267
  12. {smartbox-2.3.2 → smartbox-2.4.0}/.github/FUNDING.yml +0 -0
  13. {smartbox-2.3.2 → smartbox-2.4.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {smartbox-2.3.2 → smartbox-2.4.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {smartbox-2.3.2 → smartbox-2.4.0}/.github/ISSUE_TEMPLATE/new-reseller.md +0 -0
  16. {smartbox-2.3.2 → smartbox-2.4.0}/.github/dependabot.yml +0 -0
  17. {smartbox-2.3.2 → smartbox-2.4.0}/.gitignore +0 -0
  18. {smartbox-2.3.2 → smartbox-2.4.0}/.vscode/settings.json +0 -0
  19. {smartbox-2.3.2 → smartbox-2.4.0}/CHANGELOG.md +0 -0
  20. {smartbox-2.3.2 → smartbox-2.4.0}/CODEOWNERS +0 -0
  21. {smartbox-2.3.2 → smartbox-2.4.0}/LICENSE +0 -0
  22. {smartbox-2.3.2 → smartbox-2.4.0}/README.md +0 -0
  23. {smartbox-2.3.2 → smartbox-2.4.0}/api-notes.md +0 -0
  24. {smartbox-2.3.2 → smartbox-2.4.0}/codecov.yml +0 -0
  25. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/__init__.py +0 -0
  26. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/cmd.py +0 -0
  27. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/error.py +0 -0
  28. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/py.typed +0 -0
  29. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/session.py +0 -0
  30. {smartbox-2.3.2 → smartbox-2.4.0}/src/smartbox/socket.py +0 -0
  31. {smartbox-2.3.2 → smartbox-2.4.0}/tests/__init__.py +0 -0
  32. {smartbox-2.3.2 → smartbox-2.4.0}/tests/common.py +0 -0
  33. {smartbox-2.3.2 → smartbox-2.4.0}/tests/conftest.py +0 -0
  34. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/acm/1/samples.json +0 -0
  35. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/acm/1/setup.json +0 -0
  36. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/acm/1/status.json +0 -0
  37. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/connected.json +0 -0
  38. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr/0/samples.json +0 -0
  39. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr/0/setup.json +0 -0
  40. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr/0/status.json +0 -0
  41. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/htr_system/power_limit.json +0 -0
  42. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/mgr/away_status.json +0 -0
  43. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/mgr/nodes.json +0 -0
  44. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/pmo/3/samples.json +0 -0
  45. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/pmo/3/setup.json +0 -0
  46. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device1/pmo/3/status.json +0 -0
  47. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/connected.json +0 -0
  48. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/0/samples.json +0 -0
  49. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/0/setup.json +0 -0
  50. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/0/status.json +0 -0
  51. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/1/samples.json +0 -0
  52. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/1/setup.json +0 -0
  53. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/1/status.json +0 -0
  54. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/2/samples.json +0 -0
  55. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/2/setup.json +0 -0
  56. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/2/status.json +0 -0
  57. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/3/samples.json +0 -0
  58. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/3/setup.json +0 -0
  59. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/3/status.json +0 -0
  60. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/4/samples.json +0 -0
  61. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/4/setup.json +0 -0
  62. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/4/status.json +0 -0
  63. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/5/samples.json +0 -0
  64. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/5/setup.json +0 -0
  65. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_mod/5/status.json +0 -0
  66. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/htr_system/power_limit.json +0 -0
  67. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/mgr/away_status.json +0 -0
  68. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs/device2/mgr/nodes.json +0 -0
  69. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/devs.json +0 -0
  70. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/grouped_devs.json +0 -0
  71. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/groups/test_home/guest_users.json +0 -0
  72. {smartbox-2.3.2 → smartbox-2.4.0}/tests/fixtures/test_path.json +0 -0
  73. {smartbox-2.3.2 → smartbox-2.4.0}/tests/test_cmd.py +0 -0
  74. {smartbox-2.3.2 → smartbox-2.4.0}/tests/test_models.py +0 -0
  75. {smartbox-2.3.2 → smartbox-2.4.0}/tests/test_reseller.py +0 -0
  76. {smartbox-2.3.2 → 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@v5
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.0.0
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
@@ -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.1
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.26
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: v5.0.0
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.9.4
25
+ rev: v0.14.11
26
26
  hooks:
27
27
  - id: ruff
28
28
  args: [--fix, --exit-non-zero-on-fix]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smartbox
3
- Version: 2.3.2
3
+ Version: 2.4.0
4
4
  Summary: Python API to control heating 'smart boxes'
5
5
  Project-URL: Homepage, https://github.com/ajtudela/smartbox
6
6
  Project-URL: Issues, https://github.com/ajtudela/smartbox/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "smartbox"
3
- version = "2.3.2"
3
+ version = "2.4.0"
4
4
  requires-python = ">=3.13"
5
5
  authors = [
6
6
  { name="Alberto Tudela", email="ajtudela@gmail.com" },
@@ -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/",
@@ -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)
@@ -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
+ )
@@ -1,8 +1,6 @@
1
1
  [tox]
2
2
  envlist =
3
-
4
3
  py313
5
-
6
4
  [testenv]
7
5
  deps =
8
6
  freezegun