smartbox 2.6.2__tar.gz → 2.7.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.7.0/.env.example +16 -0
- smartbox-2.7.0/.github/ISSUE_TEMPLATE/new-reseller.md +19 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/.github/workflows/python-package.yml +2 -2
- {smartbox-2.6.2 → smartbox-2.7.0}/.gitignore +6 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/CHANGELOG.md +56 -2
- smartbox-2.7.0/PKG-INFO +254 -0
- smartbox-2.7.0/README.md +234 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/api-notes.md +269 -11
- {smartbox-2.6.2 → smartbox-2.7.0}/codecov.yml +0 -2
- {smartbox-2.6.2 → smartbox-2.7.0}/pyproject.toml +6 -2
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/__init__.py +5 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/cmd.py +84 -2
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/error.py +13 -0
- smartbox-2.7.0/src/smartbox/retry.py +78 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/session.py +4 -1
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/socket.py +16 -30
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/update_manager.py +112 -102
- smartbox-2.7.0/src/smartbox/ws_user.py +758 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_cmd.py +186 -1
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_session.py +8 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_socket.py +172 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_update_manager.py +34 -0
- smartbox-2.7.0/tests/test_ws_user.py +887 -0
- smartbox-2.7.0/tools/probe_ws_user.py +491 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/uv.lock +1 -1
- smartbox-2.6.2/.github/ISSUE_TEMPLATE/new-reseller.md +0 -18
- smartbox-2.6.2/PKG-INFO +0 -202
- smartbox-2.6.2/README.md +0 -182
- {smartbox-2.6.2 → smartbox-2.7.0}/.github/FUNDING.yml +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/.github/dependabot.yml +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/.pre-commit-config.yaml +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/.vscode/settings.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/CODEOWNERS +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/LICENSE +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/ajtudela-pr-64-audit.md +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/models.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/py.typed +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/src/smartbox/reseller.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/__init__.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/common.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/conftest.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/acm/1/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/acm/1/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/acm/1/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/acm/1/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/connected.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/htr/0/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/htr/0/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/htr/0/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/htr/0/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/htr_system/power_limit.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/mgr/away_status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/mgr/nodes.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/pmo/3/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/pmo/3/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/pmo/3/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device1/pmo/3/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/connected.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/0/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/0/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/0/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/0/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/1/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/1/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/1/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/1/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/2/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/2/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/2/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/2/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/3/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/3/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/3/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/3/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/4/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/4/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/4/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/4/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/5/samples.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/5/setup.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/5/status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_mod/5/version.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/htr_system/power_limit.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/mgr/away_status.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs/device2/mgr/nodes.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/devs.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/grouped_devs.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/groups/test_home/guest_users.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_prog_addr2.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_prog_addr3.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_prog_addr4.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_prog_addr5.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_prog_addr6.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_setup_addr2.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_setup_addr3.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_setup_addr4.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_setup_addr5.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_setup_addr6.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_setup_no_boost.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/htr_status_boost_off.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/socket_update_status_frame.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/socket_update_sync_lost_frame.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/live/socket_update_version_frame.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/fixtures/test_path.json +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_live_payloads.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_models.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_reseller.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tests/test_session_reliability.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/api_functional_tests.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/away_probe.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/capture_prog.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/live_check.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/probe_extra_options.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/probe_modified_auto.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/probe_settle_window.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/prog_functional_tests.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/tools/watch_status.py +0 -0
- {smartbox-2.6.2 → smartbox-2.7.0}/webapi-spec.md +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copy this file to ".env" and fill in your values so the `smartbox` CLI does
|
|
2
|
+
# not need the credentials on every invocation. A real shell environment
|
|
3
|
+
# variable always overrides the value set here, and an explicit command-line
|
|
4
|
+
# option overrides both.
|
|
5
|
+
|
|
6
|
+
# Your username/password as used for the mobile app / web app (required).
|
|
7
|
+
SMARTBOX_USERNAME=
|
|
8
|
+
SMARTBOX_PASSWORD=
|
|
9
|
+
|
|
10
|
+
# API name for your heater vendor, e.g. "api" or "api-foo" (see README).
|
|
11
|
+
SMARTBOX_API_NAME=api
|
|
12
|
+
|
|
13
|
+
# Only needed if your reseller is not built into the package.
|
|
14
|
+
SMARTBOX_BASIC_AUTH_CREDS=
|
|
15
|
+
SMARTBOX_X_REFERER=
|
|
16
|
+
SMARTBOX_X_SERIAL_ID=
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: New reseller
|
|
3
|
+
about: Add a new available reseller.
|
|
4
|
+
title: "[RESELLER] New available reseller"
|
|
5
|
+
labels: reseller
|
|
6
|
+
assignees: Delmael
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
> [!NOTE]
|
|
11
|
+
> If you do not see your current reseller in the available list, you can ask to add it. We cannot ensure your device go through this platform.
|
|
12
|
+
> See [How to capture the values](https://github.com/ajtudela/smartbox/blob/main/api-notes.md#basic-auth-credential) — open your reseller's web app, watch the network tab, log in and read the `POST .../api/v2/client/token` request headers.
|
|
13
|
+
|
|
14
|
+
* [ ] I have checked with the `smartbox resellers` command that my reseller is not already present
|
|
15
|
+
* **Website url (mandatory)** (the `x-referer` header):
|
|
16
|
+
* Name of the reseller:
|
|
17
|
+
* API name (the `api-<...>` part of the request host, e.g. `api-foo`):
|
|
18
|
+
* Basic auth credential (the base64 string after `Basic ` in the `authorization` header):
|
|
19
|
+
* Serial id (the `x-serialid` header):
|
|
@@ -30,7 +30,7 @@ jobs:
|
|
|
30
30
|
- name: Install the project
|
|
31
31
|
run: uv sync --all-extras --dev
|
|
32
32
|
- name: Lint with ruff
|
|
33
|
-
run: uv run ruff check src/smartbox
|
|
33
|
+
run: uv run ruff check src/smartbox tests
|
|
34
34
|
|
|
35
35
|
typechecker:
|
|
36
36
|
runs-on: ubuntu-latest
|
|
@@ -52,7 +52,7 @@ jobs:
|
|
|
52
52
|
- name: Install the project
|
|
53
53
|
run: uv sync --all-extras --dev
|
|
54
54
|
- name: Typecheck with mypy
|
|
55
|
-
run: uv run mypy src/smartbox
|
|
55
|
+
run: uv run mypy src/smartbox tests
|
|
56
56
|
|
|
57
57
|
build:
|
|
58
58
|
runs-on: ubuntu-latest
|
|
@@ -1,5 +1,59 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.0
|
|
4
|
+
|
|
5
|
+
### New
|
|
6
|
+
* Per-user `ws_user` websocket transport (`WsUserSocketSession`,
|
|
7
|
+
`check_ws_user_support`): one connection serves every device of the
|
|
8
|
+
account, replacing N per-device `socket_io` sessions. Wire protocol
|
|
9
|
+
and every failure mode verified live (api-notes.md, "Transports —
|
|
10
|
+
socket_io vs ws_user"). Selected via the new `ws_user_socket=` kwarg
|
|
11
|
+
on `UpdateManager`; the default transport is unchanged.
|
|
12
|
+
* `WsUserUnsupportedError` + the fallback doctrine: only deterministic
|
|
13
|
+
handshake rejections (HTTP 401/403/404/410 in the one-shot probe on a
|
|
14
|
+
fresh token; 404/410 in the run loop, or 401/403 that keep rejecting
|
|
15
|
+
freshly minted tokens) signal "endpoint unsupported"; transient
|
|
16
|
+
trouble is retried and never demotes.
|
|
17
|
+
* In `ws_user` mode `UpdateManager` ignores the SocketSession
|
|
18
|
+
pass-through kwargs (warned at construction) — the shared socket's
|
|
19
|
+
liveness is heartbeat-driven, not knob-driven.
|
|
20
|
+
|
|
21
|
+
### Breaking
|
|
22
|
+
* `UpdateManager.socket_session` now returns `None` when the manager
|
|
23
|
+
runs in `ws_user` mode (previously always a `SocketSession`); use the
|
|
24
|
+
new `ws_user_socket` accessor to distinguish modes.
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
* CLI: every authentication option also resolves from a `SMARTBOX_*`
|
|
28
|
+
environment variable, and the `smartbox` console script loads a `.env`
|
|
29
|
+
file (searched upward from the working directory) before parsing
|
|
30
|
+
options. Precedence: command line > shell environment > `.env`. See
|
|
31
|
+
`.env.example`.
|
|
32
|
+
|
|
33
|
+
### Fixes
|
|
34
|
+
* CLI: `.env` parsing now follows python-dotenv semantics — an inline
|
|
35
|
+
`#` starts a comment only when it is preceded by whitespace and lies
|
|
36
|
+
outside quotes, and quoted values unquote even when a trailing
|
|
37
|
+
comment follows them. Previously inline comments silently became
|
|
38
|
+
part of the value and quote-stripping was defeated, corrupting
|
|
39
|
+
credentials written in the conventional quoted style.
|
|
40
|
+
|
|
41
|
+
### Refactor
|
|
42
|
+
* `UpdateManager`: `subscribe_to_node_status`/`_setup`/`_version`/`_prog`
|
|
43
|
+
share one `_subscribe_to_node_key` implementation.
|
|
44
|
+
|
|
45
|
+
### Tests & CI
|
|
46
|
+
* Socket tests for the namespace callbacks, websocket cleanup, SIGINT
|
|
47
|
+
handler and cancellation during connect; coverage floor of 95%
|
|
48
|
+
(`fail_under`) enforced.
|
|
49
|
+
* ruff and mypy run over `tests/` in CI; `socket.py` is no longer
|
|
50
|
+
excluded from codecov.
|
|
51
|
+
|
|
52
|
+
### Docs
|
|
53
|
+
* README rewritten: library usage, command table, `.env` section and an
|
|
54
|
+
actionable Support section. api-notes documents how to capture the
|
|
55
|
+
Basic Auth credential and the new-reseller issue template points to it.
|
|
56
|
+
|
|
3
57
|
## 2.6.2
|
|
4
58
|
|
|
5
59
|
Port round of upstream PR #64 (ajtudela, `improvements/fix_bugs`, 19
|
|
@@ -77,8 +131,8 @@ version bump).
|
|
|
77
131
|
device group move, node select/power, group geo_data/extra_data, invite
|
|
78
132
|
flows (invite/revoke/confirm — confirm is unauthenticated), quiet-home
|
|
79
133
|
notification config GET/POST + test, encrypted-wifi-credentials and
|
|
80
|
-
geocoding helpers. Read-only-verified paths pinned from the
|
|
81
|
-
|
|
134
|
+
geocoding helpers. Read-only-verified paths pinned from the official
|
|
135
|
+
vendor app; all writes marked **unverified on live hardware** in docstrings.
|
|
82
136
|
New base helpers: `_api_delete`, host-relative `_api_get` (params not
|
|
83
137
|
logged — may carry secrets) and `_api_post_path` (auth optional).
|
|
84
138
|
* Away-status writes default `enabled: true` to match the vendor app shape
|
smartbox-2.7.0/PKG-INFO
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: smartbox
|
|
3
|
+
Version: 2.7.0
|
|
4
|
+
Summary: Python API to control heating 'smart boxes'
|
|
5
|
+
Project-URL: Homepage, https://github.com/ajtudela/smartbox
|
|
6
|
+
Project-URL: Issues, https://github.com/ajtudela/smartbox/issues
|
|
7
|
+
Author-email: Alberto Tudela <ajtudela@gmail.com>, Delmael <delmael@outlook.com>, graham33 <graham@grahambennett.org>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.14.2
|
|
14
|
+
Requires-Dist: aiohttp<4,>=3
|
|
15
|
+
Requires-Dist: asyncclick
|
|
16
|
+
Requires-Dist: jq
|
|
17
|
+
Requires-Dist: pydantic<3,>=2
|
|
18
|
+
Requires-Dist: python-socketio<6,>=5
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# smartbox
|
|
22
|
+

|
|
23
|
+
[](https://badge.fury.io/py/smartbox)
|
|
24
|
+
[](https://codecov.io/gh/ajtudela/smartbox)
|
|
25
|
+
[](https://pypi.python.org/pypi/smartbox/)
|
|
26
|
+
[](https://pypi.python.org/pypi/smartbox/)
|
|
27
|
+
|
|
28
|
+
Python API to control heating 'smart boxes' (Helki and its resellers: Elnur, Haverland, Climastar, Technotherm, HJM, ...). It wraps the REST API and the socket.io channel, and ships a `smartbox` command line tool.
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
pip install smartbox
|
|
33
|
+
|
|
34
|
+
The package is importable as `smartbox` and ships `py.typed`.
|
|
35
|
+
|
|
36
|
+
## Library usage
|
|
37
|
+
|
|
38
|
+
`AsyncSmartboxSession` is the entry point. Use it as an async context manager so its HTTP client is closed for you; when you already have an `aiohttp.ClientSession` (for example Home Assistant's shared one) pass it as `websession=` and it will not be closed on exit.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import asyncio
|
|
42
|
+
from smartbox import AsyncSmartboxSession
|
|
43
|
+
|
|
44
|
+
async def main() -> None:
|
|
45
|
+
async with AsyncSmartboxSession(
|
|
46
|
+
api_name="api", # or SMARTBOX_API_NAME
|
|
47
|
+
username="you@example.com",
|
|
48
|
+
password="secret",
|
|
49
|
+
raw_response=False, # return Pydantic models instead of dicts
|
|
50
|
+
) as session:
|
|
51
|
+
for device in await session.get_devices():
|
|
52
|
+
for node in await session.get_nodes(device.dev_id):
|
|
53
|
+
status = await session.get_node_status(device.dev_id, node.model_dump())
|
|
54
|
+
print(device.name, node.name, status.mtemp, status.stemp)
|
|
55
|
+
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With `raw_response=True` (the default) every method returns the raw `dict`/`list` from the API. With `raw_response=False` they return models from `smartbox.models` (`HtrNodeStatus`, `DefaultNodeSetup`, ...); the models are lenient (unknown keys kept, fields optional) because the API is undocumented and varies by reseller.
|
|
60
|
+
|
|
61
|
+
Constructor options worth knowing: `retry_attempts`/`backoff_factor` (transient 5xx / connection errors are retried with exponential backoff), `x_referer` / `x_serial_id` / `basic_auth_credentials` for a reseller that is not built in.
|
|
62
|
+
|
|
63
|
+
For live updates over socket.io use `UpdateManager`:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from smartbox import AsyncSmartboxSession, UpdateManager
|
|
67
|
+
|
|
68
|
+
async with AsyncSmartboxSession(...) as session:
|
|
69
|
+
manager = UpdateManager(session, device_id)
|
|
70
|
+
manager.subscribe_to_node_status(
|
|
71
|
+
lambda node_type, addr, status: print(node_type, addr, status)
|
|
72
|
+
)
|
|
73
|
+
await manager.run() # runs until cancelled
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Websocket transports
|
|
77
|
+
|
|
78
|
+
Status updates arrive over one of two transports; both are implemented
|
|
79
|
+
in this library and verified live (api-notes.md, "Transports —
|
|
80
|
+
socket_io vs ws_user"):
|
|
81
|
+
|
|
82
|
+
* **`socket_io`** (default): a per-device socket.io connection. The
|
|
83
|
+
`UpdateManager` owns it; `run()` runs the loop, `cancel()` is
|
|
84
|
+
terminal.
|
|
85
|
+
* **`ws_user`** (the vendor web apps' channel): ONE plain-WebSocket
|
|
86
|
+
connection (`WsUserSocketSession`) serves every device of the
|
|
87
|
+
account. Its server-side proxy PINGs keep the session supervised, and
|
|
88
|
+
sockets are cut by the server exactly at the access token's 4 h
|
|
89
|
+
expiry (close code 1006) — the reconnect with a fresh token is
|
|
90
|
+
mandatory and part of the steady state.
|
|
91
|
+
|
|
92
|
+
Caller contract for the shared ws_user socket:
|
|
93
|
+
|
|
94
|
+
socket_ = WsUserSocketSession(session)
|
|
95
|
+
# The caller owns run() — and must SUPERVISE it: run() exits on any
|
|
96
|
+
# failure (it is re-runnable; only cancel() is terminal), and a
|
|
97
|
+
# parked UpdateManager never observes a shared-socket death, so
|
|
98
|
+
# without a supervisor the transport dies silently. Exceptions
|
|
99
|
+
# surface when the task is awaited, so keep and await it.
|
|
100
|
+
task = asyncio.create_task(supervise(socket_.run()))
|
|
101
|
+
socket_.add_device(dev_id, dev_data_cb, update_cb) # register (any time)
|
|
102
|
+
socket_.remove_device(dev_id) # unregister
|
|
103
|
+
await socket_.cancel() # TERMINAL: later run() calls no-op
|
|
104
|
+
|
|
105
|
+
`run()` raises `InvalidAuthError` (rejected credentials — start a
|
|
106
|
+
reauthentication flow) and `WsUserUnsupportedError` (deterministic
|
|
107
|
+
404/410 — the host does not serve ws_user; fall back to socket_io).
|
|
108
|
+
|
|
109
|
+
`UpdateManager(session, dev_id, ws_user_socket=socket_)` switches an
|
|
110
|
+
individual manager onto the shared connection: `run()` registers the
|
|
111
|
+
manager's device and parks until `cancel()` (the shared socket's own
|
|
112
|
+
restarts are handled by its supervisor, not by per-device consumers).
|
|
113
|
+
Pass-through SocketSession kwargs are ignored in this mode (warned).
|
|
114
|
+
|
|
115
|
+
## `smartbox` command line tool
|
|
116
|
+
|
|
117
|
+
Use the `smartbox` tool to read status information from your heaters (nodes) and change settings.
|
|
118
|
+
|
|
119
|
+
### Options
|
|
120
|
+
|
|
121
|
+
`-u`/`--username` and `-p`/`--password` (your mobile-app / web-app credentials) are required for every command except `resellers`. `-v`/`--verbose` enables debug logging.
|
|
122
|
+
|
|
123
|
+
These are only needed when your reseller is not built into the package:
|
|
124
|
+
|
|
125
|
+
* `-a`/`--api-name`: the API name for your heater vendor, from the 'API Host' entry under the 'Version' menu of the mobile/web app. For a host `api-foo.xxxx` or `api.xxxx` use `api-foo` or `api`.
|
|
126
|
+
* `-b`/`--basic-auth-creds`: the HTTP Basic Auth credential used for the initial authentication, as a base64 string. See [Basic Auth Credential](./api-notes.md#basic-auth-credential) in api-notes.md for how to capture it.
|
|
127
|
+
* `-r`/`--x-referer`: the `x-referer` header value.
|
|
128
|
+
* `-i`/`--x-serial-id`: the `x-serialid` header value.
|
|
129
|
+
|
|
130
|
+
### Configuration via environment variables / `.env`
|
|
131
|
+
|
|
132
|
+
Every option above can be supplied through an environment variable instead of being typed on each invocation. The command line always takes precedence over the environment.
|
|
133
|
+
|
|
134
|
+
| Option | Environment variable |
|
|
135
|
+
| ------------------------- | --------------------------- |
|
|
136
|
+
| `-a`/`--api-name` | `SMARTBOX_API_NAME` |
|
|
137
|
+
| `-b`/`--basic-auth-creds` | `SMARTBOX_BASIC_AUTH_CREDS` |
|
|
138
|
+
| `-u`/`--username` | `SMARTBOX_USERNAME` |
|
|
139
|
+
| `-p`/`--password` | `SMARTBOX_PASSWORD` |
|
|
140
|
+
| `-r`/`--x-referer` | `SMARTBOX_X_REFERER` |
|
|
141
|
+
| `-i`/`--x-serial-id` | `SMARTBOX_X_SERIAL_ID` |
|
|
142
|
+
|
|
143
|
+
The `smartbox` command also reads a `.env` file (searched for in the working directory and its parents) before parsing options, so the usual workflow is to copy [`.env.example`](.env.example) to `.env`, fill it in once, and then run commands without any auth flags:
|
|
144
|
+
|
|
145
|
+
cp .env.example .env
|
|
146
|
+
# edit .env
|
|
147
|
+
smartbox devices
|
|
148
|
+
|
|
149
|
+
### Commands
|
|
150
|
+
|
|
151
|
+
In the examples below `<auth options...>` stands for the options above (or nothing, when they come from `.env`).
|
|
152
|
+
|
|
153
|
+
Read-only, across every device/node:
|
|
154
|
+
|
|
155
|
+
| Command | What it shows |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `devices` | the devices the account can see |
|
|
158
|
+
| `homes` | devices grouped into homes |
|
|
159
|
+
| `nodes` | the nodes of each device |
|
|
160
|
+
| `status` | live status of every node |
|
|
161
|
+
| `setup` | configuration of every node |
|
|
162
|
+
| `prog` | weekly heating schedule of every node |
|
|
163
|
+
| `device-away-status` | away status of every device |
|
|
164
|
+
| `device-connected-status` | whether each device is online |
|
|
165
|
+
| `device-power-limit` | power limit (watts) of every device |
|
|
166
|
+
| `health-check` | whether the API is alive |
|
|
167
|
+
| `api-version` | API build info |
|
|
168
|
+
| `resellers` | resellers with a built-in configuration |
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
smartbox <auth options...> status
|
|
172
|
+
smartbox <auth options...> prog
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`guests` needs a home id:
|
|
176
|
+
|
|
177
|
+
smartbox <auth options...> guests -h <home id>
|
|
178
|
+
|
|
179
|
+
`node-samples` reads the temperature/consumption history of one node (`-s`/`-e` default to one hour before/after now):
|
|
180
|
+
|
|
181
|
+
smartbox <auth options...> node-samples -d <device id> -n <node addr> [-s <start unix ts>] [-e <end unix ts>]
|
|
182
|
+
|
|
183
|
+
Writes take named options, one per field to change:
|
|
184
|
+
|
|
185
|
+
smartbox <auth options...> set-status -d <device id> -n <node addr> [--mode auto] [--stemp 21.5 --units C] [--locked false]
|
|
186
|
+
smartbox <auth options...> set-setup -d <device id> -n <node addr> [--control-mode 1] [--offset 0.0] [--units C] [--true-radiant-enabled true] [--window-mode-enabled false] [--priority low]
|
|
187
|
+
smartbox <auth options...> set-prog -d <device id> -n <node addr> '{"prog": {"0": [2, 2, ...], ...}}'
|
|
188
|
+
smartbox <auth options...> set-device-away-status -d <device id> [--away true] [--enabled true] [--forced false]
|
|
189
|
+
smartbox <auth options...> set-device-power-limit -d <device id> <watts>
|
|
190
|
+
|
|
191
|
+
`socket` opens a long-lived socket.io connection and prints `dev_data` and `update` events until interrupted:
|
|
192
|
+
|
|
193
|
+
smartbox <auth options...> socket -d <device id>
|
|
194
|
+
|
|
195
|
+
See [api-notes.md](./api-notes.md) for notes on the REST and socket.io endpoints.
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
Prerequisites:
|
|
200
|
+
|
|
201
|
+
uv
|
|
202
|
+
python >=3.14.2
|
|
203
|
+
|
|
204
|
+
Clone the repo, install dependencies and install pre-commit hooks:
|
|
205
|
+
|
|
206
|
+
git clone https://github.com/ajtudela/smartbox
|
|
207
|
+
cd smartbox
|
|
208
|
+
uv sync
|
|
209
|
+
pre-commit install
|
|
210
|
+
|
|
211
|
+
## Testing
|
|
212
|
+
|
|
213
|
+
Run the full suite:
|
|
214
|
+
|
|
215
|
+
uv run pytest
|
|
216
|
+
|
|
217
|
+
Generate a coverage XML (e.g. for use in an editor):
|
|
218
|
+
|
|
219
|
+
uv run pytest --cov-report xml:cov.xml --cov smartbox --cov-append tests/
|
|
220
|
+
|
|
221
|
+
`tox` runs the tests against the installed package:
|
|
222
|
+
|
|
223
|
+
uv run tox
|
|
224
|
+
uv run tox -e py # the single environment declared in pyproject.toml
|
|
225
|
+
|
|
226
|
+
## Changelog
|
|
227
|
+
|
|
228
|
+
Release notes are kept in [CHANGELOG.md](./CHANGELOG.md).
|
|
229
|
+
|
|
230
|
+
## Support
|
|
231
|
+
|
|
232
|
+
This is a community project, maintained on a best-effort basis and provided without warranty.
|
|
233
|
+
|
|
234
|
+
### Getting help and reporting problems
|
|
235
|
+
|
|
236
|
+
Open an issue at <https://github.com/ajtudela/smartbox/issues>. Templates are provided for the common cases:
|
|
237
|
+
|
|
238
|
+
* **Bug report** — a command or method misbehaves. Include the `smartbox` command you ran (or the code), the full output with `-v`/`--verbose`, and your reseller. **Redact the access token and any Basic Auth credential** before pasting logs.
|
|
239
|
+
* **Feature request** — a missing endpoint or option.
|
|
240
|
+
* **New reseller** — your heater vendor is not in the built-in list. The template asks for `api-name`, `x-referer`, `x-serial-id` and the Basic Auth credential; see ["Capturing it from the reseller's web app"](./api-notes.md#capturing-it-from-the-resellers-web-app) in api-notes.md for how to read them from the login request. Once added, everyone using that vendor benefits.
|
|
241
|
+
|
|
242
|
+
For questions about the API itself rather than this library, see [api-notes.md](./api-notes.md).
|
|
243
|
+
|
|
244
|
+
### Supporting the maintainers
|
|
245
|
+
|
|
246
|
+
If this library is useful to you, you can buy the maintainers a coffee:
|
|
247
|
+
|
|
248
|
+
[![Buy a coffee to ajtudela][buymeacoffee-shield]][buymeacoffee-ajtudela]
|
|
249
|
+
[![Buy a coffee to delmael][buymeacoffee-shield]][buymeacoffee-delmael]
|
|
250
|
+
|
|
251
|
+
[buymeacoffee-ajtudela]: https://www.buymeacoffee.com/ajtudela
|
|
252
|
+
[buymeacoffee-delmael]: https://www.buymeacoffee.com/delmael
|
|
253
|
+
|
|
254
|
+
[buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png
|
smartbox-2.7.0/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# smartbox
|
|
2
|
+

|
|
3
|
+
[](https://badge.fury.io/py/smartbox)
|
|
4
|
+
[](https://codecov.io/gh/ajtudela/smartbox)
|
|
5
|
+
[](https://pypi.python.org/pypi/smartbox/)
|
|
6
|
+
[](https://pypi.python.org/pypi/smartbox/)
|
|
7
|
+
|
|
8
|
+
Python API to control heating 'smart boxes' (Helki and its resellers: Elnur, Haverland, Climastar, Technotherm, HJM, ...). It wraps the REST API and the socket.io channel, and ships a `smartbox` command line tool.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
pip install smartbox
|
|
13
|
+
|
|
14
|
+
The package is importable as `smartbox` and ships `py.typed`.
|
|
15
|
+
|
|
16
|
+
## Library usage
|
|
17
|
+
|
|
18
|
+
`AsyncSmartboxSession` is the entry point. Use it as an async context manager so its HTTP client is closed for you; when you already have an `aiohttp.ClientSession` (for example Home Assistant's shared one) pass it as `websession=` and it will not be closed on exit.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import asyncio
|
|
22
|
+
from smartbox import AsyncSmartboxSession
|
|
23
|
+
|
|
24
|
+
async def main() -> None:
|
|
25
|
+
async with AsyncSmartboxSession(
|
|
26
|
+
api_name="api", # or SMARTBOX_API_NAME
|
|
27
|
+
username="you@example.com",
|
|
28
|
+
password="secret",
|
|
29
|
+
raw_response=False, # return Pydantic models instead of dicts
|
|
30
|
+
) as session:
|
|
31
|
+
for device in await session.get_devices():
|
|
32
|
+
for node in await session.get_nodes(device.dev_id):
|
|
33
|
+
status = await session.get_node_status(device.dev_id, node.model_dump())
|
|
34
|
+
print(device.name, node.name, status.mtemp, status.stemp)
|
|
35
|
+
|
|
36
|
+
asyncio.run(main())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
With `raw_response=True` (the default) every method returns the raw `dict`/`list` from the API. With `raw_response=False` they return models from `smartbox.models` (`HtrNodeStatus`, `DefaultNodeSetup`, ...); the models are lenient (unknown keys kept, fields optional) because the API is undocumented and varies by reseller.
|
|
40
|
+
|
|
41
|
+
Constructor options worth knowing: `retry_attempts`/`backoff_factor` (transient 5xx / connection errors are retried with exponential backoff), `x_referer` / `x_serial_id` / `basic_auth_credentials` for a reseller that is not built in.
|
|
42
|
+
|
|
43
|
+
For live updates over socket.io use `UpdateManager`:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from smartbox import AsyncSmartboxSession, UpdateManager
|
|
47
|
+
|
|
48
|
+
async with AsyncSmartboxSession(...) as session:
|
|
49
|
+
manager = UpdateManager(session, device_id)
|
|
50
|
+
manager.subscribe_to_node_status(
|
|
51
|
+
lambda node_type, addr, status: print(node_type, addr, status)
|
|
52
|
+
)
|
|
53
|
+
await manager.run() # runs until cancelled
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Websocket transports
|
|
57
|
+
|
|
58
|
+
Status updates arrive over one of two transports; both are implemented
|
|
59
|
+
in this library and verified live (api-notes.md, "Transports —
|
|
60
|
+
socket_io vs ws_user"):
|
|
61
|
+
|
|
62
|
+
* **`socket_io`** (default): a per-device socket.io connection. The
|
|
63
|
+
`UpdateManager` owns it; `run()` runs the loop, `cancel()` is
|
|
64
|
+
terminal.
|
|
65
|
+
* **`ws_user`** (the vendor web apps' channel): ONE plain-WebSocket
|
|
66
|
+
connection (`WsUserSocketSession`) serves every device of the
|
|
67
|
+
account. Its server-side proxy PINGs keep the session supervised, and
|
|
68
|
+
sockets are cut by the server exactly at the access token's 4 h
|
|
69
|
+
expiry (close code 1006) — the reconnect with a fresh token is
|
|
70
|
+
mandatory and part of the steady state.
|
|
71
|
+
|
|
72
|
+
Caller contract for the shared ws_user socket:
|
|
73
|
+
|
|
74
|
+
socket_ = WsUserSocketSession(session)
|
|
75
|
+
# The caller owns run() — and must SUPERVISE it: run() exits on any
|
|
76
|
+
# failure (it is re-runnable; only cancel() is terminal), and a
|
|
77
|
+
# parked UpdateManager never observes a shared-socket death, so
|
|
78
|
+
# without a supervisor the transport dies silently. Exceptions
|
|
79
|
+
# surface when the task is awaited, so keep and await it.
|
|
80
|
+
task = asyncio.create_task(supervise(socket_.run()))
|
|
81
|
+
socket_.add_device(dev_id, dev_data_cb, update_cb) # register (any time)
|
|
82
|
+
socket_.remove_device(dev_id) # unregister
|
|
83
|
+
await socket_.cancel() # TERMINAL: later run() calls no-op
|
|
84
|
+
|
|
85
|
+
`run()` raises `InvalidAuthError` (rejected credentials — start a
|
|
86
|
+
reauthentication flow) and `WsUserUnsupportedError` (deterministic
|
|
87
|
+
404/410 — the host does not serve ws_user; fall back to socket_io).
|
|
88
|
+
|
|
89
|
+
`UpdateManager(session, dev_id, ws_user_socket=socket_)` switches an
|
|
90
|
+
individual manager onto the shared connection: `run()` registers the
|
|
91
|
+
manager's device and parks until `cancel()` (the shared socket's own
|
|
92
|
+
restarts are handled by its supervisor, not by per-device consumers).
|
|
93
|
+
Pass-through SocketSession kwargs are ignored in this mode (warned).
|
|
94
|
+
|
|
95
|
+
## `smartbox` command line tool
|
|
96
|
+
|
|
97
|
+
Use the `smartbox` tool to read status information from your heaters (nodes) and change settings.
|
|
98
|
+
|
|
99
|
+
### Options
|
|
100
|
+
|
|
101
|
+
`-u`/`--username` and `-p`/`--password` (your mobile-app / web-app credentials) are required for every command except `resellers`. `-v`/`--verbose` enables debug logging.
|
|
102
|
+
|
|
103
|
+
These are only needed when your reseller is not built into the package:
|
|
104
|
+
|
|
105
|
+
* `-a`/`--api-name`: the API name for your heater vendor, from the 'API Host' entry under the 'Version' menu of the mobile/web app. For a host `api-foo.xxxx` or `api.xxxx` use `api-foo` or `api`.
|
|
106
|
+
* `-b`/`--basic-auth-creds`: the HTTP Basic Auth credential used for the initial authentication, as a base64 string. See [Basic Auth Credential](./api-notes.md#basic-auth-credential) in api-notes.md for how to capture it.
|
|
107
|
+
* `-r`/`--x-referer`: the `x-referer` header value.
|
|
108
|
+
* `-i`/`--x-serial-id`: the `x-serialid` header value.
|
|
109
|
+
|
|
110
|
+
### Configuration via environment variables / `.env`
|
|
111
|
+
|
|
112
|
+
Every option above can be supplied through an environment variable instead of being typed on each invocation. The command line always takes precedence over the environment.
|
|
113
|
+
|
|
114
|
+
| Option | Environment variable |
|
|
115
|
+
| ------------------------- | --------------------------- |
|
|
116
|
+
| `-a`/`--api-name` | `SMARTBOX_API_NAME` |
|
|
117
|
+
| `-b`/`--basic-auth-creds` | `SMARTBOX_BASIC_AUTH_CREDS` |
|
|
118
|
+
| `-u`/`--username` | `SMARTBOX_USERNAME` |
|
|
119
|
+
| `-p`/`--password` | `SMARTBOX_PASSWORD` |
|
|
120
|
+
| `-r`/`--x-referer` | `SMARTBOX_X_REFERER` |
|
|
121
|
+
| `-i`/`--x-serial-id` | `SMARTBOX_X_SERIAL_ID` |
|
|
122
|
+
|
|
123
|
+
The `smartbox` command also reads a `.env` file (searched for in the working directory and its parents) before parsing options, so the usual workflow is to copy [`.env.example`](.env.example) to `.env`, fill it in once, and then run commands without any auth flags:
|
|
124
|
+
|
|
125
|
+
cp .env.example .env
|
|
126
|
+
# edit .env
|
|
127
|
+
smartbox devices
|
|
128
|
+
|
|
129
|
+
### Commands
|
|
130
|
+
|
|
131
|
+
In the examples below `<auth options...>` stands for the options above (or nothing, when they come from `.env`).
|
|
132
|
+
|
|
133
|
+
Read-only, across every device/node:
|
|
134
|
+
|
|
135
|
+
| Command | What it shows |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `devices` | the devices the account can see |
|
|
138
|
+
| `homes` | devices grouped into homes |
|
|
139
|
+
| `nodes` | the nodes of each device |
|
|
140
|
+
| `status` | live status of every node |
|
|
141
|
+
| `setup` | configuration of every node |
|
|
142
|
+
| `prog` | weekly heating schedule of every node |
|
|
143
|
+
| `device-away-status` | away status of every device |
|
|
144
|
+
| `device-connected-status` | whether each device is online |
|
|
145
|
+
| `device-power-limit` | power limit (watts) of every device |
|
|
146
|
+
| `health-check` | whether the API is alive |
|
|
147
|
+
| `api-version` | API build info |
|
|
148
|
+
| `resellers` | resellers with a built-in configuration |
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
smartbox <auth options...> status
|
|
152
|
+
smartbox <auth options...> prog
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`guests` needs a home id:
|
|
156
|
+
|
|
157
|
+
smartbox <auth options...> guests -h <home id>
|
|
158
|
+
|
|
159
|
+
`node-samples` reads the temperature/consumption history of one node (`-s`/`-e` default to one hour before/after now):
|
|
160
|
+
|
|
161
|
+
smartbox <auth options...> node-samples -d <device id> -n <node addr> [-s <start unix ts>] [-e <end unix ts>]
|
|
162
|
+
|
|
163
|
+
Writes take named options, one per field to change:
|
|
164
|
+
|
|
165
|
+
smartbox <auth options...> set-status -d <device id> -n <node addr> [--mode auto] [--stemp 21.5 --units C] [--locked false]
|
|
166
|
+
smartbox <auth options...> set-setup -d <device id> -n <node addr> [--control-mode 1] [--offset 0.0] [--units C] [--true-radiant-enabled true] [--window-mode-enabled false] [--priority low]
|
|
167
|
+
smartbox <auth options...> set-prog -d <device id> -n <node addr> '{"prog": {"0": [2, 2, ...], ...}}'
|
|
168
|
+
smartbox <auth options...> set-device-away-status -d <device id> [--away true] [--enabled true] [--forced false]
|
|
169
|
+
smartbox <auth options...> set-device-power-limit -d <device id> <watts>
|
|
170
|
+
|
|
171
|
+
`socket` opens a long-lived socket.io connection and prints `dev_data` and `update` events until interrupted:
|
|
172
|
+
|
|
173
|
+
smartbox <auth options...> socket -d <device id>
|
|
174
|
+
|
|
175
|
+
See [api-notes.md](./api-notes.md) for notes on the REST and socket.io endpoints.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
Prerequisites:
|
|
180
|
+
|
|
181
|
+
uv
|
|
182
|
+
python >=3.14.2
|
|
183
|
+
|
|
184
|
+
Clone the repo, install dependencies and install pre-commit hooks:
|
|
185
|
+
|
|
186
|
+
git clone https://github.com/ajtudela/smartbox
|
|
187
|
+
cd smartbox
|
|
188
|
+
uv sync
|
|
189
|
+
pre-commit install
|
|
190
|
+
|
|
191
|
+
## Testing
|
|
192
|
+
|
|
193
|
+
Run the full suite:
|
|
194
|
+
|
|
195
|
+
uv run pytest
|
|
196
|
+
|
|
197
|
+
Generate a coverage XML (e.g. for use in an editor):
|
|
198
|
+
|
|
199
|
+
uv run pytest --cov-report xml:cov.xml --cov smartbox --cov-append tests/
|
|
200
|
+
|
|
201
|
+
`tox` runs the tests against the installed package:
|
|
202
|
+
|
|
203
|
+
uv run tox
|
|
204
|
+
uv run tox -e py # the single environment declared in pyproject.toml
|
|
205
|
+
|
|
206
|
+
## Changelog
|
|
207
|
+
|
|
208
|
+
Release notes are kept in [CHANGELOG.md](./CHANGELOG.md).
|
|
209
|
+
|
|
210
|
+
## Support
|
|
211
|
+
|
|
212
|
+
This is a community project, maintained on a best-effort basis and provided without warranty.
|
|
213
|
+
|
|
214
|
+
### Getting help and reporting problems
|
|
215
|
+
|
|
216
|
+
Open an issue at <https://github.com/ajtudela/smartbox/issues>. Templates are provided for the common cases:
|
|
217
|
+
|
|
218
|
+
* **Bug report** — a command or method misbehaves. Include the `smartbox` command you ran (or the code), the full output with `-v`/`--verbose`, and your reseller. **Redact the access token and any Basic Auth credential** before pasting logs.
|
|
219
|
+
* **Feature request** — a missing endpoint or option.
|
|
220
|
+
* **New reseller** — your heater vendor is not in the built-in list. The template asks for `api-name`, `x-referer`, `x-serial-id` and the Basic Auth credential; see ["Capturing it from the reseller's web app"](./api-notes.md#capturing-it-from-the-resellers-web-app) in api-notes.md for how to read them from the login request. Once added, everyone using that vendor benefits.
|
|
221
|
+
|
|
222
|
+
For questions about the API itself rather than this library, see [api-notes.md](./api-notes.md).
|
|
223
|
+
|
|
224
|
+
### Supporting the maintainers
|
|
225
|
+
|
|
226
|
+
If this library is useful to you, you can buy the maintainers a coffee:
|
|
227
|
+
|
|
228
|
+
[![Buy a coffee to ajtudela][buymeacoffee-shield]][buymeacoffee-ajtudela]
|
|
229
|
+
[![Buy a coffee to delmael][buymeacoffee-shield]][buymeacoffee-delmael]
|
|
230
|
+
|
|
231
|
+
[buymeacoffee-ajtudela]: https://www.buymeacoffee.com/ajtudela
|
|
232
|
+
[buymeacoffee-delmael]: https://www.buymeacoffee.com/delmael
|
|
233
|
+
|
|
234
|
+
[buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png
|