mysa-client 0.1.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.
- mysa_client-0.1.0/.github/workflows/ci.yml +51 -0
- mysa_client-0.1.0/.github/workflows/release.yml +54 -0
- mysa_client-0.1.0/.gitignore +25 -0
- mysa_client-0.1.0/CHANGELOG.md +17 -0
- mysa_client-0.1.0/LICENSE +21 -0
- mysa_client-0.1.0/PKG-INFO +188 -0
- mysa_client-0.1.0/README.md +162 -0
- mysa_client-0.1.0/docs/cloud-auth.md +75 -0
- mysa_client-0.1.0/docs/cloud-state.md +232 -0
- mysa_client-0.1.0/docs/cloud-workflows.md +238 -0
- mysa_client-0.1.0/docs/releasing.md +47 -0
- mysa_client-0.1.0/examples/README.md +27 -0
- mysa_client-0.1.0/examples/cloud/README.md +41 -0
- mysa_client-0.1.0/examples/cloud/bootstrap.success.json +30 -0
- mysa_client-0.1.0/examples/cloud/capabilities.lite.json +32 -0
- mysa_client-0.1.0/examples/cloud/cognito-refresh.request.json +31 -0
- mysa_client-0.1.0/examples/cloud/device-claim.minimal.json +36 -0
- mysa_client-0.1.0/examples/cloud/devices.success.json +27 -0
- mysa_client-0.1.0/examples/cloud/firmware-availability.no-update.json +31 -0
- mysa_client-0.1.0/examples/cloud/firmware-trigger.empty-object.json +31 -0
- mysa_client-0.1.0/examples/cloud/firmware-trigger.no-body-error.json +26 -0
- mysa_client-0.1.0/examples/cloud/hold.permanent.json +43 -0
- mysa_client-0.1.0/examples/cloud/iot-policy.limited.json +29 -0
- mysa_client-0.1.0/examples/cloud/legacy-state.mixed-time.json +44 -0
- mysa_client-0.1.0/examples/cloud/live-mode.request.json +30 -0
- mysa_client-0.1.0/examples/cloud/schedule-create.full-body.json +54 -0
- mysa_client-0.1.0/examples/cloud/schedule-update.full-body.json +51 -0
- mysa_client-0.1.0/examples/cloud/schedule-v1-clear.legacy-host.json +38 -0
- mysa_client-0.1.0/examples/cloud/schedule-v1-clear.modern-host.json +40 -0
- mysa_client-0.1.0/examples/cloud/schedule-v2-delete.with-body.json +33 -0
- mysa_client-0.1.0/examples/cloud/schedule-v2-update.full-body.json +45 -0
- mysa_client-0.1.0/examples/cloud/shortcut.replace-actions.json +40 -0
- mysa_client-0.1.0/examples/cloud/state-batch.success.json +33 -0
- mysa_client-0.1.0/examples/cloud/state-history.success.json +31 -0
- mysa_client-0.1.0/examples/cloud/state-update.ac-fan.json +36 -0
- mysa_client-0.1.0/examples/cloud/state-update.ac-swing.json +35 -0
- mysa_client-0.1.0/examples/cloud/state-update.remote-tracking.json +34 -0
- mysa_client-0.1.0/examples/cloud/state-update.setpoint.json +36 -0
- mysa_client-0.1.0/examples/cloud/state.future-telemetry.json +42 -0
- mysa_client-0.1.0/examples/cloud/usage.expanded.json +54 -0
- mysa_client-0.1.0/examples/cloud/usage.summary.json +41 -0
- mysa_client-0.1.0/examples/cloud_basic.py +163 -0
- mysa_client-0.1.0/examples/homekit_basic.py +224 -0
- mysa_client-0.1.0/pyproject.toml +115 -0
- mysa_client-0.1.0/src/mysa_client/__init__.py +4 -0
- mysa_client-0.1.0/src/mysa_client/cloud/README.md +460 -0
- mysa_client-0.1.0/src/mysa_client/cloud/__init__.py +90 -0
- mysa_client-0.1.0/src/mysa_client/cloud/auth.py +243 -0
- mysa_client-0.1.0/src/mysa_client/cloud/client.py +1026 -0
- mysa_client-0.1.0/src/mysa_client/cloud/commands.py +169 -0
- mysa_client-0.1.0/src/mysa_client/cloud/const.py +279 -0
- mysa_client-0.1.0/src/mysa_client/cloud/exceptions.py +50 -0
- mysa_client-0.1.0/src/mysa_client/cloud/models.py +683 -0
- mysa_client-0.1.0/src/mysa_client/cloud/observations.py +160 -0
- mysa_client-0.1.0/src/mysa_client/cloud/schedules.py +84 -0
- mysa_client-0.1.0/src/mysa_client/cloud/srp.py +162 -0
- mysa_client-0.1.0/src/mysa_client/cloud/trace.py +37 -0
- mysa_client-0.1.0/src/mysa_client/cloud/validation.py +97 -0
- mysa_client-0.1.0/src/mysa_client/homekit/README.md +161 -0
- mysa_client-0.1.0/src/mysa_client/homekit/__init__.py +62 -0
- mysa_client-0.1.0/src/mysa_client/homekit/advertisement.py +106 -0
- mysa_client-0.1.0/src/mysa_client/homekit/controller.py +420 -0
- mysa_client-0.1.0/src/mysa_client/homekit/device.py +718 -0
- mysa_client-0.1.0/src/mysa_client/homekit/exceptions.py +59 -0
- mysa_client-0.1.0/src/mysa_client/homekit/models.py +144 -0
- mysa_client-0.1.0/src/mysa_client/py.typed +0 -0
- mysa_client-0.1.0/tests/__init__.py +1 -0
- mysa_client-0.1.0/tests/cloud/test_client.py +541 -0
- mysa_client-0.1.0/tests/cloud/test_protocol_edges.py +293 -0
- mysa_client-0.1.0/tests/cloud/test_srp.py +127 -0
- mysa_client-0.1.0/tests/cloud_helpers.py +446 -0
- mysa_client-0.1.0/tests/conftest.py +30 -0
- mysa_client-0.1.0/tests/fixtures/app_4201_new_models.json +599 -0
- mysa_client-0.1.0/tests/fixtures/bootstrap.json +33 -0
- mysa_client-0.1.0/tests/fixtures/capabilities_bb_v2_0_l.json +109 -0
- mysa_client-0.1.0/tests/fixtures/devices.json +24 -0
- mysa_client-0.1.0/tests/fixtures/homekit_bb_v2_0_l.json +66 -0
- mysa_client-0.1.0/tests/fixtures/state_batch.json +119 -0
- mysa_client-0.1.0/tests/fixtures/state_history.json +11 -0
- mysa_client-0.1.0/tests/fixtures/usage.json +15 -0
- mysa_client-0.1.0/tests/homekit/__init__.py +1 -0
- mysa_client-0.1.0/tests/homekit/test_homekit_protocol.py +240 -0
- mysa_client-0.1.0/tests/homekit/test_homekit_refresh.py +229 -0
- mysa_client-0.1.0/tests/homekit/test_pairing_protocol_failures.py +154 -0
- mysa_client-0.1.0/tests/homekit_helpers.py +107 -0
- mysa_client-0.1.0/tests/test_examples.py +442 -0
- mysa_client-0.1.0/tests/test_protocol_boundaries.py +62 -0
- mysa_client-0.1.0/tools/check_distribution.py +61 -0
- mysa_client-0.1.0/tools/check_docs.py +257 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: Validate client
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
workflow_call:
|
|
9
|
+
|
|
10
|
+
permissions: {}
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
validate:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
strategy:
|
|
16
|
+
fail-fast: false
|
|
17
|
+
matrix:
|
|
18
|
+
python: ["3.11", "3.12", "3.13", "3.14"]
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python }}
|
|
26
|
+
- name: Install validation dependencies
|
|
27
|
+
run: |
|
|
28
|
+
python -m pip install --upgrade pip
|
|
29
|
+
python -m pip install '.[homekit]' --group test --group lint --group build
|
|
30
|
+
- name: Check formatting, lint, types, and docs
|
|
31
|
+
run: |
|
|
32
|
+
ruff format --check .
|
|
33
|
+
ruff check .
|
|
34
|
+
mypy --python-version '${{ matrix.python }}'
|
|
35
|
+
python tools/check_docs.py
|
|
36
|
+
- name: Build and inspect release artifacts
|
|
37
|
+
run: |
|
|
38
|
+
python -m build
|
|
39
|
+
python -m twine check --strict dist/*
|
|
40
|
+
python tools/check_distribution.py
|
|
41
|
+
- name: Test the installed wheel
|
|
42
|
+
run: |
|
|
43
|
+
python -m pip install --force-reinstall --no-deps dist/*.whl
|
|
44
|
+
python -m pytest --cov=mysa_client --cov-report=term-missing
|
|
45
|
+
python examples/cloud_basic.py --help
|
|
46
|
+
python examples/homekit_basic.py --help
|
|
47
|
+
- name: Verify a cloud-only installation
|
|
48
|
+
run: |
|
|
49
|
+
python -m venv /tmp/mysa-cloud-only
|
|
50
|
+
/tmp/mysa-cloud-only/bin/python -m pip install dist/*.whl
|
|
51
|
+
/tmp/mysa-cloud-only/bin/python -I -c 'import importlib.util; import mysa_client.cloud; assert importlib.util.find_spec("aiohomekit") is None; assert importlib.util.find_spec("homeassistant") is None'
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish client
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
tag:
|
|
7
|
+
description: Existing release tag matching the package version (for example v0.1.0)
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
|
|
11
|
+
permissions: {}
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
validate:
|
|
15
|
+
uses: ./.github/workflows/ci.yml
|
|
16
|
+
|
|
17
|
+
publish:
|
|
18
|
+
needs: validate
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
environment: pypi
|
|
21
|
+
permissions:
|
|
22
|
+
id-token: write
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
fetch-depth: 0
|
|
28
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
29
|
+
with:
|
|
30
|
+
python-version: "3.14"
|
|
31
|
+
- name: Verify the tag names the validated commit and package version
|
|
32
|
+
env:
|
|
33
|
+
RELEASE_TAG: ${{ inputs.tag }}
|
|
34
|
+
run: |
|
|
35
|
+
python - <<'PY'
|
|
36
|
+
import os
|
|
37
|
+
import subprocess
|
|
38
|
+
import tomllib
|
|
39
|
+
from pathlib import Path
|
|
40
|
+
version = tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"]
|
|
41
|
+
tag = os.environ["RELEASE_TAG"]
|
|
42
|
+
assert tag == f"v{version}", "Release tag must match package metadata"
|
|
43
|
+
def revision(ref):
|
|
44
|
+
return subprocess.check_output(["git", "rev-parse", "--verify", ref], text=True).strip()
|
|
45
|
+
assert revision(f"refs/tags/{tag}^{{commit}}") == revision("HEAD"), "Tag must identify the validated commit"
|
|
46
|
+
PY
|
|
47
|
+
- name: Build, inspect, and publish through PyPI trusted publishing
|
|
48
|
+
run: |
|
|
49
|
+
python -m pip install --upgrade pip
|
|
50
|
+
python -m pip install --group build
|
|
51
|
+
python -m build
|
|
52
|
+
python -m twine check --strict dist/*
|
|
53
|
+
python tools/check_distribution.py
|
|
54
|
+
python -m twine upload --non-interactive dist/*
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.venv/
|
|
6
|
+
venv/
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
|
|
10
|
+
# Tools
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
.mypy_cache/
|
|
14
|
+
.coverage
|
|
15
|
+
coverage.xml
|
|
16
|
+
htmlcov/
|
|
17
|
+
|
|
18
|
+
# Editors
|
|
19
|
+
.vscode/
|
|
20
|
+
.idea/
|
|
21
|
+
.DS_Store
|
|
22
|
+
|
|
23
|
+
.env
|
|
24
|
+
*.flow
|
|
25
|
+
*.har
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-10-12)
|
|
4
|
+
|
|
5
|
+
Initial standalone release, extracted from Mysa Local for Home Assistant.
|
|
6
|
+
|
|
7
|
+
- Extract the cloud and HomeKit protocol clients into `mysa_client`.
|
|
8
|
+
- Add caller-owned session, credential persistence, and semantic device APIs.
|
|
9
|
+
- Add standalone usage examples and document reverse-engineering and hardware
|
|
10
|
+
validation limits.
|
|
11
|
+
|
|
12
|
+
- Preserve the existing protocol APIs and MIT attribution; keep HTTP sessions,
|
|
13
|
+
Zeroconf, polling, and credential persistence under caller ownership.
|
|
14
|
+
- Support Python 3.11–3.14, cloud-only installation, the optional HomeKit extra,
|
|
15
|
+
and typed wheel distributions.
|
|
16
|
+
- Cover authentication, retries, parsing, pairing rollback, and example
|
|
17
|
+
lifecycle with an independent test suite that does not install Home Assistant.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nikita Sokolsky
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mysa-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Standalone async cloud and HomeKit clients for Mysa thermostats
|
|
5
|
+
Project-URL: Repository, https://github.com/SokolskyNikita/mysa-client
|
|
6
|
+
Project-URL: Issues, https://github.com/SokolskyNikita/mysa-client/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/SokolskyNikita/mysa-client/blob/main/CHANGELOG.md
|
|
8
|
+
Author: Nikita Sokolsky
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Requires-Dist: aiohttp<4,>=3.11
|
|
21
|
+
Provides-Extra: homekit
|
|
22
|
+
Requires-Dist: aiohomekit<5,>=4.0.1; extra == 'homekit'
|
|
23
|
+
Requires-Dist: cryptography>=48.0.1; extra == 'homekit'
|
|
24
|
+
Requires-Dist: zeroconf>=0.149.16; extra == 'homekit'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Mysa Client
|
|
28
|
+
|
|
29
|
+
`mysa-client` is an asynchronous Python library for the reverse-engineered Mysa
|
|
30
|
+
cloud API and the Mysa thermostat HomeKit interface. It exposes the two
|
|
31
|
+
protocols independently; it does not choose between them or provide an
|
|
32
|
+
application polling, persistence, or device-state confirmation policy.
|
|
33
|
+
|
|
34
|
+
The API is based on behavior observed in the Mysa apps and, where noted, on
|
|
35
|
+
Baseboard Lite hardware. It is not a vendor API contract. The only hardware
|
|
36
|
+
validation recorded so far is on two Baseboard Lite `BB-V2-0-L` thermostats
|
|
37
|
+
running firmware `3.17.5.13`. Other model behavior is app/code-derived or
|
|
38
|
+
simulated and should be treated as experimental until checked on matching
|
|
39
|
+
hardware. See the [cloud protocol guide](https://github.com/SokolskyNikita/mysa-client/blob/main/src/mysa_client/cloud/README.md) and
|
|
40
|
+
[HomeKit protocol guide](https://github.com/SokolskyNikita/mysa-client/blob/main/src/mysa_client/homekit/README.md) for limits and
|
|
41
|
+
evidence.
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
Python 3.11 or newer is required. Install the cloud client from PyPI:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
python -m pip install mysa-client
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
That installs the cloud API and its `aiohttp` dependency. HomeKit support is an
|
|
52
|
+
optional extra:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
python -m pip install 'mysa-client[homekit]'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
To run the examples or develop the library, clone this repository and install
|
|
59
|
+
from its checkout:
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
git clone https://github.com/SokolskyNikita/mysa-client.git
|
|
63
|
+
cd mysa-client
|
|
64
|
+
python -m pip install -e '.[homekit]' --group test --group lint --group build
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Import from `mysa_client.cloud` or `mysa_client.homekit`. Importing the package
|
|
68
|
+
root does not load either protocol's optional transport dependencies.
|
|
69
|
+
|
|
70
|
+
## Cloud example
|
|
71
|
+
|
|
72
|
+
The [cloud example](https://github.com/SokolskyNikita/mysa-client/blob/main/examples/cloud_basic.py) signs in with SRP, saves the
|
|
73
|
+
refresh token through a caller-owned callback, discovers devices, and reads
|
|
74
|
+
state. It sends a setpoint command only when `MYSA_SET_CELSIUS` is explicitly
|
|
75
|
+
set in the environment. Never put an account password or token in source
|
|
76
|
+
control. The example creates its token file with owner-only permissions.
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
export MYSA_EMAIL='account@example.com'
|
|
80
|
+
export MYSA_PASSWORD='your-password'
|
|
81
|
+
export MYSA_REFRESH_TOKEN_FILE="$HOME/.config/mysa-client/refresh-token"
|
|
82
|
+
python examples/cloud_basic.py
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
To request a heating target, explicitly set a safe value for your device and
|
|
86
|
+
run the example again:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
MYSA_SET_CELSIUS=20.5 python examples/cloud_basic.py
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The command's successful HTTP response means the API accepted the request. It
|
|
93
|
+
does not prove that the thermostat applied it. Read reported state separately
|
|
94
|
+
and use an application-specific confirmation policy.
|
|
95
|
+
|
|
96
|
+
## HomeKit example
|
|
97
|
+
|
|
98
|
+
The [HomeKit example](https://github.com/SokolskyNikita/mysa-client/blob/main/examples/homekit_basic.py) discovers nearby accessories,
|
|
99
|
+
can complete PIN pairing and persist the returned credentials, and can load
|
|
100
|
+
those credentials for reads, events, and an explicitly requested target write.
|
|
101
|
+
It creates and closes its own asynchronous Zeroconf instance and stops the
|
|
102
|
+
protocol controller and device session on exit. Pairing credentials are secret
|
|
103
|
+
key material; keep the JSON file private and make a protected backup.
|
|
104
|
+
|
|
105
|
+
List discovered accessories:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
python examples/homekit_basic.py --discover
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Pair using the accessory ID shown by discovery and the code printed on the
|
|
112
|
+
thermostat's faceplate:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
python examples/homekit_basic.py --pair-id 'AA:BB:CC:DD:EE:FF' --pin 123-45-678
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Load the saved pairing, observe events, and read semantic values:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
python examples/homekit_basic.py --pairing-file "$HOME/.config/mysa-client/pairing.json"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
HomeKit write acceptance also is not physical confirmation. The example reads
|
|
125
|
+
back the target after a requested write, but the caller remains responsible
|
|
126
|
+
for freshness and application policy.
|
|
127
|
+
|
|
128
|
+
## Ownership and error handling
|
|
129
|
+
|
|
130
|
+
Both clients use resources supplied by the caller. Cloud `MysaAuth` and
|
|
131
|
+
`MysaClient` share a caller-owned `aiohttp.ClientSession`; close it with an
|
|
132
|
+
`async with` block. The cloud client uses a 20-second request timeout and
|
|
133
|
+
refreshes/retries once after a 401. It does not automatically retry an
|
|
134
|
+
ambiguous command after a timeout, connection loss, cancellation, 429, or
|
|
135
|
+
server error. `MysaRateLimitError.retry_after` carries the server's backoff hint
|
|
136
|
+
when available. Authentication, connection, rate-limit, not-found, and API
|
|
137
|
+
errors derive from `MysaError`.
|
|
138
|
+
|
|
139
|
+
`HomeKitProtocol` receives an externally managed asynchronous Zeroconf
|
|
140
|
+
instance. Start and stop the protocol within that instance's lifetime, close
|
|
141
|
+
each loaded `HomeKitDevice`, and then close Zeroconf. Reads have a default
|
|
142
|
+
three-second deadline; controller discovery/pairing and accessory metadata
|
|
143
|
+
operations have bounded internal timeouts. Writes have no fixed caller
|
|
144
|
+
deadline and are not replayed automatically. HomeKit errors derive from
|
|
145
|
+
`HomeKitError`, including typed read and rejected-write errors. Pairing
|
|
146
|
+
completion awaits the caller's credential-save callback before fetching
|
|
147
|
+
accessory details; if saving fails, the library attempts bounded key removal.
|
|
148
|
+
That cleanup is best effort if the accessory is unreachable.
|
|
149
|
+
|
|
150
|
+
The caller owns long-term refresh-token and pairing-data storage, polling,
|
|
151
|
+
freshness, retries after ambiguous outcomes, validation against advertised
|
|
152
|
+
capabilities, and command confirmation. HomeKit `state` is a cached view, not
|
|
153
|
+
a freshness guarantee. Cloud reported state can lag behind a successful
|
|
154
|
+
response. A fresh read after a write is useful evidence, but may still not
|
|
155
|
+
prove the device's physical behavior.
|
|
156
|
+
|
|
157
|
+
## Public API
|
|
158
|
+
|
|
159
|
+
The public cloud surface is exported from `mysa_client.cloud`: `MysaAuth`,
|
|
160
|
+
`Tokens`, `MysaClient`, response models, model and command enums, typed errors,
|
|
161
|
+
and the documented command helpers. HomeKit's public semantic API is exported
|
|
162
|
+
from `mysa_client.homekit`: `HomeKitProtocol`, `HomeKitDevice`, `PairingSession`,
|
|
163
|
+
`Field`, mode/action and event models, discovery records, and typed errors.
|
|
164
|
+
|
|
165
|
+
The Home Assistant consumer also uses supported helpers from
|
|
166
|
+
`mysa_client.cloud.commands`, `.const`, `.observations`, `.trace`, and
|
|
167
|
+
`.validation`. Those submodule helpers are documented in the [cloud API
|
|
168
|
+
surface](https://github.com/SokolskyNikita/mysa-client/blob/main/src/mysa_client/cloud/README.md#public-api-and-integration-helpers).
|
|
169
|
+
They are part of the maintained consumer API even when they are not re-exported
|
|
170
|
+
at `mysa_client.cloud`. Other underscored names and implementation details are
|
|
171
|
+
private.
|
|
172
|
+
|
|
173
|
+
See [examples](https://github.com/SokolskyNikita/mysa-client/tree/main/examples),
|
|
174
|
+
[cloud authentication](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/cloud-auth.md),
|
|
175
|
+
[cloud state and models](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/cloud-state.md),
|
|
176
|
+
[cloud workflows](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/cloud-workflows.md),
|
|
177
|
+
the protocol guides, and the [change log](https://github.com/SokolskyNikita/mysa-client/blob/main/CHANGELOG.md).
|
|
178
|
+
|
|
179
|
+
## Development and release
|
|
180
|
+
|
|
181
|
+
The project is licensed under MIT; see [LICENSE](https://github.com/SokolskyNikita/mysa-client/blob/main/LICENSE). Protocol behavior
|
|
182
|
+
and test fixtures are maintained in this repository. Automated tests and
|
|
183
|
+
documentation checks use simulated data and do not contact Mysa or local
|
|
184
|
+
thermostats. Hardware validation must be performed separately with explicit
|
|
185
|
+
operator intent and reversible commands.
|
|
186
|
+
|
|
187
|
+
Release order, tag requirements, and PyPI trusted-publisher setup are documented
|
|
188
|
+
in [releasing](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/releasing.md).
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Mysa Client
|
|
2
|
+
|
|
3
|
+
`mysa-client` is an asynchronous Python library for the reverse-engineered Mysa
|
|
4
|
+
cloud API and the Mysa thermostat HomeKit interface. It exposes the two
|
|
5
|
+
protocols independently; it does not choose between them or provide an
|
|
6
|
+
application polling, persistence, or device-state confirmation policy.
|
|
7
|
+
|
|
8
|
+
The API is based on behavior observed in the Mysa apps and, where noted, on
|
|
9
|
+
Baseboard Lite hardware. It is not a vendor API contract. The only hardware
|
|
10
|
+
validation recorded so far is on two Baseboard Lite `BB-V2-0-L` thermostats
|
|
11
|
+
running firmware `3.17.5.13`. Other model behavior is app/code-derived or
|
|
12
|
+
simulated and should be treated as experimental until checked on matching
|
|
13
|
+
hardware. See the [cloud protocol guide](https://github.com/SokolskyNikita/mysa-client/blob/main/src/mysa_client/cloud/README.md) and
|
|
14
|
+
[HomeKit protocol guide](https://github.com/SokolskyNikita/mysa-client/blob/main/src/mysa_client/homekit/README.md) for limits and
|
|
15
|
+
evidence.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
Python 3.11 or newer is required. Install the cloud client from PyPI:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
python -m pip install mysa-client
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That installs the cloud API and its `aiohttp` dependency. HomeKit support is an
|
|
26
|
+
optional extra:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
python -m pip install 'mysa-client[homekit]'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To run the examples or develop the library, clone this repository and install
|
|
33
|
+
from its checkout:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
git clone https://github.com/SokolskyNikita/mysa-client.git
|
|
37
|
+
cd mysa-client
|
|
38
|
+
python -m pip install -e '.[homekit]' --group test --group lint --group build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Import from `mysa_client.cloud` or `mysa_client.homekit`. Importing the package
|
|
42
|
+
root does not load either protocol's optional transport dependencies.
|
|
43
|
+
|
|
44
|
+
## Cloud example
|
|
45
|
+
|
|
46
|
+
The [cloud example](https://github.com/SokolskyNikita/mysa-client/blob/main/examples/cloud_basic.py) signs in with SRP, saves the
|
|
47
|
+
refresh token through a caller-owned callback, discovers devices, and reads
|
|
48
|
+
state. It sends a setpoint command only when `MYSA_SET_CELSIUS` is explicitly
|
|
49
|
+
set in the environment. Never put an account password or token in source
|
|
50
|
+
control. The example creates its token file with owner-only permissions.
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
export MYSA_EMAIL='account@example.com'
|
|
54
|
+
export MYSA_PASSWORD='your-password'
|
|
55
|
+
export MYSA_REFRESH_TOKEN_FILE="$HOME/.config/mysa-client/refresh-token"
|
|
56
|
+
python examples/cloud_basic.py
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To request a heating target, explicitly set a safe value for your device and
|
|
60
|
+
run the example again:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
MYSA_SET_CELSIUS=20.5 python examples/cloud_basic.py
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The command's successful HTTP response means the API accepted the request. It
|
|
67
|
+
does not prove that the thermostat applied it. Read reported state separately
|
|
68
|
+
and use an application-specific confirmation policy.
|
|
69
|
+
|
|
70
|
+
## HomeKit example
|
|
71
|
+
|
|
72
|
+
The [HomeKit example](https://github.com/SokolskyNikita/mysa-client/blob/main/examples/homekit_basic.py) discovers nearby accessories,
|
|
73
|
+
can complete PIN pairing and persist the returned credentials, and can load
|
|
74
|
+
those credentials for reads, events, and an explicitly requested target write.
|
|
75
|
+
It creates and closes its own asynchronous Zeroconf instance and stops the
|
|
76
|
+
protocol controller and device session on exit. Pairing credentials are secret
|
|
77
|
+
key material; keep the JSON file private and make a protected backup.
|
|
78
|
+
|
|
79
|
+
List discovered accessories:
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
python examples/homekit_basic.py --discover
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Pair using the accessory ID shown by discovery and the code printed on the
|
|
86
|
+
thermostat's faceplate:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
python examples/homekit_basic.py --pair-id 'AA:BB:CC:DD:EE:FF' --pin 123-45-678
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Load the saved pairing, observe events, and read semantic values:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
python examples/homekit_basic.py --pairing-file "$HOME/.config/mysa-client/pairing.json"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
HomeKit write acceptance also is not physical confirmation. The example reads
|
|
99
|
+
back the target after a requested write, but the caller remains responsible
|
|
100
|
+
for freshness and application policy.
|
|
101
|
+
|
|
102
|
+
## Ownership and error handling
|
|
103
|
+
|
|
104
|
+
Both clients use resources supplied by the caller. Cloud `MysaAuth` and
|
|
105
|
+
`MysaClient` share a caller-owned `aiohttp.ClientSession`; close it with an
|
|
106
|
+
`async with` block. The cloud client uses a 20-second request timeout and
|
|
107
|
+
refreshes/retries once after a 401. It does not automatically retry an
|
|
108
|
+
ambiguous command after a timeout, connection loss, cancellation, 429, or
|
|
109
|
+
server error. `MysaRateLimitError.retry_after` carries the server's backoff hint
|
|
110
|
+
when available. Authentication, connection, rate-limit, not-found, and API
|
|
111
|
+
errors derive from `MysaError`.
|
|
112
|
+
|
|
113
|
+
`HomeKitProtocol` receives an externally managed asynchronous Zeroconf
|
|
114
|
+
instance. Start and stop the protocol within that instance's lifetime, close
|
|
115
|
+
each loaded `HomeKitDevice`, and then close Zeroconf. Reads have a default
|
|
116
|
+
three-second deadline; controller discovery/pairing and accessory metadata
|
|
117
|
+
operations have bounded internal timeouts. Writes have no fixed caller
|
|
118
|
+
deadline and are not replayed automatically. HomeKit errors derive from
|
|
119
|
+
`HomeKitError`, including typed read and rejected-write errors. Pairing
|
|
120
|
+
completion awaits the caller's credential-save callback before fetching
|
|
121
|
+
accessory details; if saving fails, the library attempts bounded key removal.
|
|
122
|
+
That cleanup is best effort if the accessory is unreachable.
|
|
123
|
+
|
|
124
|
+
The caller owns long-term refresh-token and pairing-data storage, polling,
|
|
125
|
+
freshness, retries after ambiguous outcomes, validation against advertised
|
|
126
|
+
capabilities, and command confirmation. HomeKit `state` is a cached view, not
|
|
127
|
+
a freshness guarantee. Cloud reported state can lag behind a successful
|
|
128
|
+
response. A fresh read after a write is useful evidence, but may still not
|
|
129
|
+
prove the device's physical behavior.
|
|
130
|
+
|
|
131
|
+
## Public API
|
|
132
|
+
|
|
133
|
+
The public cloud surface is exported from `mysa_client.cloud`: `MysaAuth`,
|
|
134
|
+
`Tokens`, `MysaClient`, response models, model and command enums, typed errors,
|
|
135
|
+
and the documented command helpers. HomeKit's public semantic API is exported
|
|
136
|
+
from `mysa_client.homekit`: `HomeKitProtocol`, `HomeKitDevice`, `PairingSession`,
|
|
137
|
+
`Field`, mode/action and event models, discovery records, and typed errors.
|
|
138
|
+
|
|
139
|
+
The Home Assistant consumer also uses supported helpers from
|
|
140
|
+
`mysa_client.cloud.commands`, `.const`, `.observations`, `.trace`, and
|
|
141
|
+
`.validation`. Those submodule helpers are documented in the [cloud API
|
|
142
|
+
surface](https://github.com/SokolskyNikita/mysa-client/blob/main/src/mysa_client/cloud/README.md#public-api-and-integration-helpers).
|
|
143
|
+
They are part of the maintained consumer API even when they are not re-exported
|
|
144
|
+
at `mysa_client.cloud`. Other underscored names and implementation details are
|
|
145
|
+
private.
|
|
146
|
+
|
|
147
|
+
See [examples](https://github.com/SokolskyNikita/mysa-client/tree/main/examples),
|
|
148
|
+
[cloud authentication](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/cloud-auth.md),
|
|
149
|
+
[cloud state and models](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/cloud-state.md),
|
|
150
|
+
[cloud workflows](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/cloud-workflows.md),
|
|
151
|
+
the protocol guides, and the [change log](https://github.com/SokolskyNikita/mysa-client/blob/main/CHANGELOG.md).
|
|
152
|
+
|
|
153
|
+
## Development and release
|
|
154
|
+
|
|
155
|
+
The project is licensed under MIT; see [LICENSE](https://github.com/SokolskyNikita/mysa-client/blob/main/LICENSE). Protocol behavior
|
|
156
|
+
and test fixtures are maintained in this repository. Automated tests and
|
|
157
|
+
documentation checks use simulated data and do not contact Mysa or local
|
|
158
|
+
thermostats. Hardware validation must be performed separately with explicit
|
|
159
|
+
operator intent and reversible commands.
|
|
160
|
+
|
|
161
|
+
Release order, tag requirements, and PyPI trusted-publisher setup are documented
|
|
162
|
+
in [releasing](https://github.com/SokolskyNikita/mysa-client/blob/main/docs/releasing.md).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Cloud authentication
|
|
2
|
+
|
|
3
|
+
The [cloud protocol reference](../src/mysa_client/cloud/README.md) owns endpoint availability, S/R/L terminology, observed limitations, and recommended client policy. This page supplies detailed wire mechanics; Home Assistant's implementation status belongs to its [cloud tracker](https://github.com/SokolskyNikita/mysa-homeassistant/blob/main/docs/implementation/cloud.md).
|
|
4
|
+
|
|
5
|
+
The protocols were reverse-engineered from the original Mysa apps. Hardware observations cover Baseboard Lite `BB-V2-0-L`, firmware `3.17.5.13`. Other-model behavior remains app/code-derived unless explicitly stated.
|
|
6
|
+
|
|
7
|
+
## Connection and authentication
|
|
8
|
+
|
|
9
|
+
### Service hosts
|
|
10
|
+
|
|
11
|
+
| Host | Use |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `mysa-backend.mysa.cloud` | Current HTTPS API and migrated legacy REST routes |
|
|
14
|
+
| `app-prod.mysa.cloud` | Legacy REST host; still advertised as `regionApiBaseUrl`, with some push-registration and account routes |
|
|
15
|
+
| `cognito-idp.us-east-1.amazonaws.com` | Sign-in and token refresh |
|
|
16
|
+
| `i18n.mysa.cloud` | Static country and time-zone lists |
|
|
17
|
+
| `chunk.mysa.cloud/track` | Analytics |
|
|
18
|
+
|
|
19
|
+
Routing depends on feature flags: `MONOLITH_API_ACCESS` moves legacy routes to
|
|
20
|
+
`mysa-backend`, and `DISABLE_LEGACY_FALLBACK` disables retrying them on `app-prod`.
|
|
21
|
+
Both were enabled for the tested account. A host name alone therefore does not
|
|
22
|
+
identify the protocol family. Mysa Local's host selection is described in the
|
|
23
|
+
[cloud guide](../src/mysa_client/cloud/README.md#how-it-is-organized).
|
|
24
|
+
|
|
25
|
+
### Cognito sign-in
|
|
26
|
+
|
|
27
|
+
Current and legacy clients share these public Cognito identifiers:
|
|
28
|
+
|
|
29
|
+
| Setting | Value |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| AWS region | `us-east-1` |
|
|
32
|
+
| User pool | `us-east-1_GUFWfhI7g` |
|
|
33
|
+
| App client | `6cktj934gasnc72f7jo2cmf6rt` |
|
|
34
|
+
| Identity pool | `us-east-1:e27f246e-ad23-41ce-ac80-eda86f8be424` |
|
|
35
|
+
|
|
36
|
+
The app client has no client secret. Cognito requests go to `https://cognito-idp.us-east-1.amazonaws.com/` with:
|
|
37
|
+
|
|
38
|
+
```http
|
|
39
|
+
X-Amz-Target: AWSCognitoIdentityProviderService.<Action>
|
|
40
|
+
Content-Type: application/x-amz-json-1.1
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
No AWS request signature is required for these user-pool calls. Sign-in uses `InitiateAuth` with `AuthFlow: "USER_SRP_AUTH"` and `AuthParameters` containing `USERNAME` and `SRP_A`. A `PASSWORD_VERIFIER` challenge follows. `RespondToAuthChallenge` sends `PASSWORD_CLAIM_SIGNATURE`, `PASSWORD_CLAIM_SECRET_BLOCK`, `TIMESTAMP` and `USERNAME` as `ChallengeResponses`, plus the challenge session if supplied. The response contains `IdToken`, `AccessToken`, `RefreshToken` and `ExpiresIn: 3600`.
|
|
44
|
+
|
|
45
|
+
The Python SRP implementation uses Cognito's 3072-bit group, SHA-256 and a 16-byte derived authentication key. Use the challenge's `USER_ID_FOR_SRP` when computing the response, even when sign-in began with an email address. Cognito also expects an English UTC timestamp with an unpadded day, for example `Fri Jan 2 03:04:05 UTC 2026`. The [SRP implementation](../src/mysa_client/cloud/srp.py) contains the calculation.
|
|
46
|
+
|
|
47
|
+
API authorization headers, token handling and supported challenges are described
|
|
48
|
+
in the cloud guide: [authentication](../src/mysa_client/cloud/README.md#using-the-client) and [request handling](../src/mysa_client/cloud/README.md#errors-timeouts-retries-and-caller-responsibilities).
|
|
49
|
+
|
|
50
|
+
Refresh uses `GetTokensFromRefreshToken` with:
|
|
51
|
+
|
|
52
|
+
[Cognito refresh request (credential placeholder only)](../examples/cloud/cognito-refresh.request.json)
|
|
53
|
+
|
|
54
|
+
The observed refresh response supplied ID and access tokens with a one-hour
|
|
55
|
+
lifetime and no replacement refresh token. The Identity Pool is not needed for
|
|
56
|
+
HTTPS; the examined mobile app uses it for SNS push registration. Its credential exchange and
|
|
57
|
+
MQTT role are covered in the [MQTT connection model](https://github.com/SokolskyNikita/mysa-homeassistant/blob/main/docs/mqtt.md#connection-and-authorization-model).
|
|
58
|
+
|
|
59
|
+
For HTTP errors and retries, see the [cloud error reference](../src/mysa_client/cloud/README.md#errors-timeouts-retries-and-caller-responsibilities).
|
|
60
|
+
The examined mobile app treats 409 as success generally; the Python client handles it specially
|
|
61
|
+
only for [device claims](cloud-workflows.md#claiming-an-older-model).
|
|
62
|
+
|
|
63
|
+
### Browser access
|
|
64
|
+
|
|
65
|
+
The tested APIs do not support ordinary cross-origin browser access:
|
|
66
|
+
|
|
67
|
+
| Service | Browser behavior |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| Cognito | Browser sign-in allowed |
|
|
70
|
+
| `mysa-backend` | Authorization/Content-Type preflights returned 404; actual responses also lacked `Access-Control-Allow-Origin` |
|
|
71
|
+
| `app-prod` | Legacy-route preflights allowed access, but actual responses lacked `Access-Control-Allow-Origin`; newer pairing, state and bootstrap routes returned 403 |
|
|
72
|
+
|
|
73
|
+
Successful Cognito sign-in therefore does not make Mysa's API responses readable
|
|
74
|
+
by a webpage. Cloud requests need a backend, such as Home Assistant, or an
|
|
75
|
+
extension with the appropriate host permissions.
|