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.
Files changed (89) hide show
  1. mysa_client-0.1.0/.github/workflows/ci.yml +51 -0
  2. mysa_client-0.1.0/.github/workflows/release.yml +54 -0
  3. mysa_client-0.1.0/.gitignore +25 -0
  4. mysa_client-0.1.0/CHANGELOG.md +17 -0
  5. mysa_client-0.1.0/LICENSE +21 -0
  6. mysa_client-0.1.0/PKG-INFO +188 -0
  7. mysa_client-0.1.0/README.md +162 -0
  8. mysa_client-0.1.0/docs/cloud-auth.md +75 -0
  9. mysa_client-0.1.0/docs/cloud-state.md +232 -0
  10. mysa_client-0.1.0/docs/cloud-workflows.md +238 -0
  11. mysa_client-0.1.0/docs/releasing.md +47 -0
  12. mysa_client-0.1.0/examples/README.md +27 -0
  13. mysa_client-0.1.0/examples/cloud/README.md +41 -0
  14. mysa_client-0.1.0/examples/cloud/bootstrap.success.json +30 -0
  15. mysa_client-0.1.0/examples/cloud/capabilities.lite.json +32 -0
  16. mysa_client-0.1.0/examples/cloud/cognito-refresh.request.json +31 -0
  17. mysa_client-0.1.0/examples/cloud/device-claim.minimal.json +36 -0
  18. mysa_client-0.1.0/examples/cloud/devices.success.json +27 -0
  19. mysa_client-0.1.0/examples/cloud/firmware-availability.no-update.json +31 -0
  20. mysa_client-0.1.0/examples/cloud/firmware-trigger.empty-object.json +31 -0
  21. mysa_client-0.1.0/examples/cloud/firmware-trigger.no-body-error.json +26 -0
  22. mysa_client-0.1.0/examples/cloud/hold.permanent.json +43 -0
  23. mysa_client-0.1.0/examples/cloud/iot-policy.limited.json +29 -0
  24. mysa_client-0.1.0/examples/cloud/legacy-state.mixed-time.json +44 -0
  25. mysa_client-0.1.0/examples/cloud/live-mode.request.json +30 -0
  26. mysa_client-0.1.0/examples/cloud/schedule-create.full-body.json +54 -0
  27. mysa_client-0.1.0/examples/cloud/schedule-update.full-body.json +51 -0
  28. mysa_client-0.1.0/examples/cloud/schedule-v1-clear.legacy-host.json +38 -0
  29. mysa_client-0.1.0/examples/cloud/schedule-v1-clear.modern-host.json +40 -0
  30. mysa_client-0.1.0/examples/cloud/schedule-v2-delete.with-body.json +33 -0
  31. mysa_client-0.1.0/examples/cloud/schedule-v2-update.full-body.json +45 -0
  32. mysa_client-0.1.0/examples/cloud/shortcut.replace-actions.json +40 -0
  33. mysa_client-0.1.0/examples/cloud/state-batch.success.json +33 -0
  34. mysa_client-0.1.0/examples/cloud/state-history.success.json +31 -0
  35. mysa_client-0.1.0/examples/cloud/state-update.ac-fan.json +36 -0
  36. mysa_client-0.1.0/examples/cloud/state-update.ac-swing.json +35 -0
  37. mysa_client-0.1.0/examples/cloud/state-update.remote-tracking.json +34 -0
  38. mysa_client-0.1.0/examples/cloud/state-update.setpoint.json +36 -0
  39. mysa_client-0.1.0/examples/cloud/state.future-telemetry.json +42 -0
  40. mysa_client-0.1.0/examples/cloud/usage.expanded.json +54 -0
  41. mysa_client-0.1.0/examples/cloud/usage.summary.json +41 -0
  42. mysa_client-0.1.0/examples/cloud_basic.py +163 -0
  43. mysa_client-0.1.0/examples/homekit_basic.py +224 -0
  44. mysa_client-0.1.0/pyproject.toml +115 -0
  45. mysa_client-0.1.0/src/mysa_client/__init__.py +4 -0
  46. mysa_client-0.1.0/src/mysa_client/cloud/README.md +460 -0
  47. mysa_client-0.1.0/src/mysa_client/cloud/__init__.py +90 -0
  48. mysa_client-0.1.0/src/mysa_client/cloud/auth.py +243 -0
  49. mysa_client-0.1.0/src/mysa_client/cloud/client.py +1026 -0
  50. mysa_client-0.1.0/src/mysa_client/cloud/commands.py +169 -0
  51. mysa_client-0.1.0/src/mysa_client/cloud/const.py +279 -0
  52. mysa_client-0.1.0/src/mysa_client/cloud/exceptions.py +50 -0
  53. mysa_client-0.1.0/src/mysa_client/cloud/models.py +683 -0
  54. mysa_client-0.1.0/src/mysa_client/cloud/observations.py +160 -0
  55. mysa_client-0.1.0/src/mysa_client/cloud/schedules.py +84 -0
  56. mysa_client-0.1.0/src/mysa_client/cloud/srp.py +162 -0
  57. mysa_client-0.1.0/src/mysa_client/cloud/trace.py +37 -0
  58. mysa_client-0.1.0/src/mysa_client/cloud/validation.py +97 -0
  59. mysa_client-0.1.0/src/mysa_client/homekit/README.md +161 -0
  60. mysa_client-0.1.0/src/mysa_client/homekit/__init__.py +62 -0
  61. mysa_client-0.1.0/src/mysa_client/homekit/advertisement.py +106 -0
  62. mysa_client-0.1.0/src/mysa_client/homekit/controller.py +420 -0
  63. mysa_client-0.1.0/src/mysa_client/homekit/device.py +718 -0
  64. mysa_client-0.1.0/src/mysa_client/homekit/exceptions.py +59 -0
  65. mysa_client-0.1.0/src/mysa_client/homekit/models.py +144 -0
  66. mysa_client-0.1.0/src/mysa_client/py.typed +0 -0
  67. mysa_client-0.1.0/tests/__init__.py +1 -0
  68. mysa_client-0.1.0/tests/cloud/test_client.py +541 -0
  69. mysa_client-0.1.0/tests/cloud/test_protocol_edges.py +293 -0
  70. mysa_client-0.1.0/tests/cloud/test_srp.py +127 -0
  71. mysa_client-0.1.0/tests/cloud_helpers.py +446 -0
  72. mysa_client-0.1.0/tests/conftest.py +30 -0
  73. mysa_client-0.1.0/tests/fixtures/app_4201_new_models.json +599 -0
  74. mysa_client-0.1.0/tests/fixtures/bootstrap.json +33 -0
  75. mysa_client-0.1.0/tests/fixtures/capabilities_bb_v2_0_l.json +109 -0
  76. mysa_client-0.1.0/tests/fixtures/devices.json +24 -0
  77. mysa_client-0.1.0/tests/fixtures/homekit_bb_v2_0_l.json +66 -0
  78. mysa_client-0.1.0/tests/fixtures/state_batch.json +119 -0
  79. mysa_client-0.1.0/tests/fixtures/state_history.json +11 -0
  80. mysa_client-0.1.0/tests/fixtures/usage.json +15 -0
  81. mysa_client-0.1.0/tests/homekit/__init__.py +1 -0
  82. mysa_client-0.1.0/tests/homekit/test_homekit_protocol.py +240 -0
  83. mysa_client-0.1.0/tests/homekit/test_homekit_refresh.py +229 -0
  84. mysa_client-0.1.0/tests/homekit/test_pairing_protocol_failures.py +154 -0
  85. mysa_client-0.1.0/tests/homekit_helpers.py +107 -0
  86. mysa_client-0.1.0/tests/test_examples.py +442 -0
  87. mysa_client-0.1.0/tests/test_protocol_boundaries.py +62 -0
  88. mysa_client-0.1.0/tools/check_distribution.py +61 -0
  89. 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.