mysa-client 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,4 @@
1
+ """Standalone Mysa clients; import cloud or homekit explicitly.
2
+
3
+ HomeKit dependencies are optional and are never imported by this package root.
4
+ """
@@ -0,0 +1,460 @@
1
+ # Mysa cloud protocol
2
+
3
+ - [API varieties](#the-three-api-varieties)
4
+ - [Operations](#operations-and-relevance-to-home-assistant)
5
+ - [Read/write mappings](#readwrite-comparisons)
6
+ - [Recommended reads](#recommended-reconciliation-of-cloud-reads)
7
+ - [Recommended writes](#recommended-reconciliation-of-cloud-writes)
8
+ - [Integration status and validation](#integration-status-and-validation)
9
+
10
+ Supporting references: [authentication](../../../docs/cloud-auth.md),
11
+ [state and model fields](../../../docs/cloud-state.md),
12
+ [workflows](../../../docs/cloud-workflows.md), and
13
+ [firmware/connectivity](https://github.com/SokolskyNikita/mysa-homeassistant/blob/main/docs/firmware-and-connectivity.md).
14
+
15
+ ## Context and conventions
16
+
17
+ Mysa exposes overlapping cloud services: structured device state, older
18
+ telemetry, and stored account/device settings. A successful request does not
19
+ necessarily mean fresh data or an applied thermostat command. This guide
20
+ covers the useful operations, their API mappings, and **recommended cloud-only
21
+ selection rules** for an integrating application. Endpoint support and Mysa Local policy are tracked separately; see the [Home Assistant integration tracker](https://github.com/SokolskyNikita/mysa-homeassistant/blob/main/docs/implementation/cloud.md).
22
+
23
+ Applications should own automations, scenes, presence, notifications, and dashboards.
24
+ Mysa's cloud remains useful for device control, hardware settings, runtime,
25
+ firmware, and inspecting existing Mysa schedules that could affect commands.
26
+ HomeKit/cloud reconciliation is outside this document.
27
+
28
+ The three access paths used below are:
29
+
30
+ | Shorthand | API variety | HTTPS base URL |
31
+ | --- | --- | --- |
32
+ | **S** | Structured/current services | `https://mysa-backend.mysa.cloud` |
33
+ | **R** | Legacy REST through the modern host | `https://mysa-backend.mysa.cloud` |
34
+ | **L** | Legacy REST through the older host | `https://app-prod.mysa.cloud` |
35
+
36
+ **R and L are two entry points to overlapping services, not independent
37
+ thermostat measurements.** The distinction matters for route availability;
38
+ S and R share a hostname but have different response formats.
39
+
40
+ Notation: `{dev}` is the account's device ID, `{home}` its home ID, and `{id}`
41
+ the resource ID returned by creation/discovery. **ST** means S's `reported`
42
+ state sections, **TEL** its `latestTelemetry.reading`, **LT** the legacy
43
+ `DeviceStatesObj[dev]` telemetry, and **REC** the device's settings record.
44
+ Temperatures and setpoints are Celsius regardless of screen units. Decode
45
+ mode, lock, and fan enums by API/model before comparing them.
46
+
47
+ The live findings apply to two Baseboard Lites (`BB-V2-0-L`, firmware
48
+ `3.17.5.13`), checked October 10–12, 2026. Other-model mappings are identified
49
+ as such. This is a reverse-engineered protocol, not a vendor API contract.
50
+
51
+ ### Shared authentication
52
+
53
+ All three paths use the **same Cognito ID token**, sent raw in
54
+ `authorization` with **no `Bearer` prefix**. Send `accept: application/json`,
55
+ a descriptive `user-agent`, and `content-type: application/json` for JSON
56
+ bodies. No separate login or AWS request signing is needed for R or L.
57
+
58
+ Use `MysaAuth.sign_in(email, password)` for the Cognito SRP exchange, or
59
+ initialize it with a saved refresh token. `async_get_id_token()` handles
60
+ refresh; retain a replacement refresh token through `on_refresh_token` when
61
+ one is returned. The public Cognito identifiers are in [`const.py`](const.py),
62
+ and the exchange is described in the [authentication reference](../../../docs/cloud-auth.md#cognito-sign-in).
63
+ Credentials and token persistence belong to the caller.
64
+
65
+ <a id="requests-and-state"></a>
66
+
67
+ ## The three API varieties
68
+
69
+ ### S — structured/current services
70
+
71
+ Use the modern base URL and shared authentication above. Discover the account
72
+ with `GET /mobile/bootstrap?versionNumber={APP_VERSION}`, using `APP_VERSION`
73
+ from `const.py`, then request state for the discovered IDs:
74
+
75
+ [Batch-state exchange with existing response fixture](../../../examples/cloud/state-batch.success.json)
76
+
77
+ A batch returns `{device_id: {"data": state}}`; inspect individual entries for
78
+ failure. `GET /state/{dev}` returns one state object directly. ST contains
79
+ configuration/control sections such as `targetHeat`, `modes`, and
80
+ `physicalInterface`; TEL contains measurements. Where a section has
81
+ `desired` and `reported`, only `reported` is evidence of applied state.
82
+
83
+ Commands use section changes with `source: 3`:
84
+
85
+ [Setpoint request and accepted reply](../../../examples/cloud/state-update.setpoint.json)
86
+
87
+ This requests a setpoint change; it does not select heat mode. Source 3 appears
88
+ as `app` in change history. Current capability, usage, schedule, hold, and
89
+ live-mode routes are listed in the comparison tables. S's state routes return
90
+ 403 on L; changing the hostname does not provide a second structured API.
91
+
92
+ ### R — legacy REST on the modern host
93
+
94
+ Use the same modern base URL, ID token, and headers. Two distinct reads matter:
95
+
96
+ - `GET /devices/state` returns LT: `DeviceStatesObj[dev]`, generally with
97
+ independent `{v, t}` pairs for each field.
98
+ - `GET /devices` returns `DevicesObj[dev]` settings records; `GET /devices/{dev}`
99
+ reads one record. REC describes stored configuration, not live measurements.
100
+
101
+ Settings writes use `POST /devices/{dev}/` with the fields to change; preserve
102
+ the trailing slash. For example, `{"Format":"fahrenheit"}` changes the
103
+ thermostat's screen setting. The response can echo stored values without
104
+ proving application. Legacy history and schedule endpoints also use R.
105
+
106
+ ### L — legacy REST on the older host
107
+
108
+ Use `https://app-prod.mysa.cloud` with the **same authentication and request
109
+ formats as R**. For example, `GET /devices/state` and
110
+ `POST /devices/{dev}/` work on both. Treat L as a compatibility route for
111
+ specific operations, not an additional source to poll continuously.
112
+
113
+ Availability is operation-specific: saved-schedule event editing works on L
114
+ but returns 404 on R; some R discovery routes and current services return 403
115
+ on L. The client's existing `replace_device()` uses L's `/devices/purgeadd`.
116
+ Do not replace the entire client's base URL to obtain legacy telemetry.
117
+
118
+ ## Operations and relevance to Home Assistant
119
+
120
+ This is the consolidated operation inventory, independent of API flavor.
121
+ **Include** means a normal client capability; **Consider** means optional or
122
+ model-specific; **Skip** means no dedicated new client method or routine HA
123
+ polling. Existing maintenance helpers can remain even when a full app feature
124
+ is not recommended. Read/write labels describe the operation, not a host.
125
+
126
+ | Operation | What it does | HA recommendation |
127
+ | --- | --- | --- |
128
+ | Account/device discovery · read | Finds homes, device IDs, models, names, and membership | **Include** the discovery needed for setup; do not build account administration |
129
+ | Capabilities · read | Describes supported modes, controls, ranges, and model features | **Include**; combine with observed valid readings, rather than trusting every flag |
130
+ | Room temperature / humidity · read | Reports ambient conditions | **Include**; climate and humidity entities |
131
+ | Setpoint / operating mode · read/write | Selects the requested temperature and heating/cooling/off mode | **Include**; core climate controls |
132
+ | AC fan / swing · read/write | Selects fan speed and vent direction | **Include when supported**; honor the AC remote's available commands |
133
+ | Heating activity / runtime · read | Reports recent duty and accumulated heating time | **Include**; distinguish interval activity from an instantaneous relay state |
134
+ | Measured electrical data · read | Reports power/current/energy on hardware that measures it | **Consider by model**; skip Lite's zero/unmeasured fields; label calculated energy as an estimate |
135
+ | Setpoint limits · read/write | Restricts temperatures accepted at the thermostat | **Include**; hardware constraints remain useful with HA automations |
136
+ | Temperature offset · read/write | Calibrates the thermostat's ambient reading/control | **Include**; do not substitute an HA display-only correction |
137
+ | Button lock · read/write | Locks the physical controls | **Include** as device configuration |
138
+ | Wake / idle brightness · read/write | Sets the physical display's two brightness levels | **Include** as device configuration |
139
+ | Display units · read/write | Chooses Celsius/Fahrenheit on the thermostat screen | **Include**; independent of HA's display units |
140
+ | Heater profile · read/write | Selects the installed heater's switching profile | **Include** as installation configuration, not a routine automation target |
141
+ | Floor limits/probe, adaptive display, remote tracking, HVAC configuration · read/write | Configures model-specific sensors and control behavior | **Consider only on matching hardware**; Lite cannot validate these controls |
142
+ | Identity / installed firmware · read | Identifies hardware and current firmware | **Include** as device metadata |
143
+ | Device name / time zone · read/write | Stores Mysa's label and schedule time zone | **Read** for discovery/schedule interpretation; **skip cloud rename**, consider time-zone writes only for explicit Mysa schedule setup |
144
+ | Firmware availability / update · read/write | Checks for an offered version and requests installation | **Include**; user-initiated update, not an automatic consequence of polling |
145
+ | Cloud connection / data age · read | Describes API reachability and thermostat reporting freshness | **Include** separate health/freshness diagnostics |
146
+ | RSSI / internal temperatures / free heap · read | Helps diagnose radio, sensor, or firmware behavior | **Consider** diagnostics, disabled by default; not extra climate readings |
147
+ | Live reporting · write | Temporarily asks devices to upload more frequently | **Consider** for cloud responsiveness and troubleshooting; not a permanent background stream by default |
148
+ | Usage / reading history · read | Supplies runtime totals and past measurements | **Include runtime**; **consider** history for gaps/troubleshooting, not a duplicate HA recorder |
149
+ | State-change history · read | Explains what changed a target or mode | **Consider** on demand when Mysa and HA behavior disagree |
150
+ | Device schedules · read/write | Stores timed thermostat actions | **Include inspection/removal** of conflicting existing schedules; **exclude** in-place schedule editing as a client feature. Prefer HA scheduling |
151
+ | Hold / resume · read/write | Overrides a Mysa schedule temporarily or permanently, then resumes it | **Include when a Mysa schedule exists**; HA-only scheduling does not need a cloud hold for every command |
152
+ | Home/Away/Vacation shortcuts · read/write | Applies a stored bundle of device actions | **Consider inspection/cleanup** for coexistence; **skip new shortcut creation/apply UI**, use HA scenes |
153
+ | Demand-response state · read | Indicates a utility event that may affect behavior | **Consider** a read-only explanation/status; skip enrollment management |
154
+ | Device claim / replacement / removal · write | Changes the device's association with a Mysa account | **Consider** explicit setup/recovery tools only; not normal thermostat control |
155
+ | Saved schedule templates · read/write | Stores reusable schedules and applies copies to devices | **Skip** template management; recognize applied device schedules through the schedule operation above |
156
+ | Mysa zones / aggregate energy · read/write | Groups devices and summarizes a Mysa home/zone | **Skip**; use HA areas/groups and validated energy sources |
157
+ | Geofences / crossings · read/write | Runs Mysa actions when a phone enters or leaves | **Skip**; use HA presence and automations |
158
+ | Alerts / reminders / notifications · read/write | Stores thresholds, reminders, push tokens, and notification preferences; sends notifications | **Skip**; use HA automations and notification services |
159
+ | Mysa account/home/access management · read/write | Edits profiles/homes, transfers access, manages invitations, or deletes accounts | **Skip** beyond discovery required for setup |
160
+ | App/third-party services · read/write | Handles app flags, phone diagnostics, analytics, localization, Alexa authorization, and property-manager features | **Skip**; they do not improve thermostat control in HA |
161
+ | Obsolete provisioning helpers · read/write | Supplies legacy NFC or anonymous AC-setup credentials | **Skip**; separate from an authenticated HA thermostat client |
162
+ | Mysa eco / Early On / animation settings · read/write | Supplies app-specific energy/schedule behavior and display animation | **Skip new methods**; prioritize HA scheduling and the explicit hardware controls above. Early On is unavailable on Lite; eco/animation effects were not established |
163
+
164
+ ## Read/write comparisons
165
+
166
+ `—` means no applicable mapping in our evidence, not proof that no future
167
+ endpoint could expose it. **Reported** means the changed value appeared in ST;
168
+ **applied** means thermostat behavior independently confirmed it. **Stored**
169
+ means only a record round trip was established. Model-specific entries below
170
+ come from app/code mappings, not Lite hardware validation.
171
+
172
+ ### Read matrix
173
+
174
+ R and L cells name the data source, not just a similarly named field. `= R`
175
+ means the same route/format is available; it is not another vote for a value.
176
+
177
+ | Value | S | R | L |
178
+ | --- | --- | --- | --- |
179
+ | Room temperature / humidity | TEL `roomTemperature` / `humidity` | LT `CorrectedTemp.v` / `Humidity.v` | = R |
180
+ | Internal temperatures | TEL `rawTemperature`, `coreTemperature` | LT `SensorTemp.v`, `HeatSink.v` | = R |
181
+ | Heating target | ST `targetHeat.setpoint`; TEL `heatSetpoint` | LT `SetPoint.v` | = R |
182
+ | Operating mode | ST `modes.mode`; TEL `mode` uses a different enum | LT `Mode.v`, `TstatMode.v`, each with its own enum | = R |
183
+ | Cooling / AC controls | ST `targetCool.setpoint`, `modes.fan_mode`, `verticalSwingState`, `horizontalSwingState` | REC `SupportedModes` / `SupportedCaps` describes available commands, not current state | = R formats; other-model support not live-verified |
184
+ | Minimum / maximum target | ST `targetHeat.lockoutMin` / `lockoutMax` | REC `MinSetpoint` / `MaxSetpoint` | = R |
185
+ | Button lock | ST `physicalInterface.lockout` | LT `Lock.v`; REC `ButtonState` | = R |
186
+ | Wake / idle brightness | ST `physicalInterface.activeIntensity` / `idleIntensity` | REC `MaxBrightness` / `MinBrightness`; LT `Brightness.v` is only one value | = R |
187
+ | Screen units | ST `physicalInterface.format` | REC `Format` | = R |
188
+ | Temperature offset | ST `tracking.ambientOffset` | — | — |
189
+ | Heater profile | ST `bbConfig.controlType` | REC `DutyCycleOpt` | = R |
190
+ | Heating activity / interval runtime | TEL `dutyCycle`, `onTime` | LT `Duty.v`, `OnTime.v`, `OffTime.v` | = R |
191
+ | Electrical data | TEL `voltage`, `maxCurrent`, `powerConsumed`, `instantLoad`; ST `power` | LT `Voltage.v`, `Current.v`; REC `Voltage` is configuration | = R |
192
+ | RSSI / free heap | TEL `rssi`, `freeHeap` | LT `Rssi.v`, `FreeHeap.v` | = R |
193
+ | Connection / timestamps | `latestTelemetry.isConnected`, `lastConnected`; TEL `timestamp`; ST section timestamps | LT `Connected.v/t`, individual field `t`, top-level `Timestamp` | = R |
194
+ | Identity / names / time zone | ST `identity.model`, `fw`; bootstrap account data | REC `Id`, `Model`, `Name`, `Home`, `TimeZone` | = R |
195
+ | Capabilities | `GET /capabilities/{dev}` | `GET /devices/capabilities`; REC for model-specific command metadata | Capability-list route returns 403; REC still works |
196
+ | Firmware availability | — | `GET /devices/update_available/{dev}`; `/devices/firmware` lists versions | = R |
197
+ | Usage / raw history | `POST /telemetry/usage/{dev}` | `POST /batchReadings/v2/{dev}` | Same route, but bucket keys can differ |
198
+ | Change history | `GET /state/history/{dev}` | `POST /energy/setpoints/device/{dev}` for target changes | = R |
199
+ | Active schedules / holds | `GET /schedule?homeId={home}`; `/schedule/hold/{dev}` | `GET /schedules` (v1), `/schedules/v2/{dev}` (v2); LT `ScheduleMode` / `HoldTime` are hints | = R |
200
+ | Shortcuts / demand response | ST `drState` where present | `GET /homes/{home}/scenes`; `/devices/{dev}/drstate` | = R |
201
+
202
+ ### Write matrix
203
+
204
+ S field paths below are sent in `POST /state/{dev}/update`; R/L settings
205
+ fields are sent in `POST /devices/{dev}/`. These are available routes, not a
206
+ recommendation to use them all.
207
+
208
+ | Operation | S | R | L |
209
+ | --- | --- | --- | --- |
210
+ | Heating target | `targetHeat.setpoint`: applied on one Lite, unconfirmed on the other | — | — |
211
+ | Heat / off | `modes.mode` = 4 / 0: same mixed application | REC `Mode` object: stored only, **not a working mode command** | = R |
212
+ | AC target / fan / swing | `targetCool.setpoint`, `modes.fan_mode`, directional swing fields; model-specific | No verified direct control mapping | = R |
213
+ | Button lock | `physicalInterface.lockout` = 3 / 0: reported | `ButtonState` = `Locked` / `Unlocked`: reported | = R |
214
+ | Wake / idle brightness | `physicalInterface.activeIntensity` / `idleIntensity`: reported | `MaxBrightness` / `MinBrightness`: reported | = R |
215
+ | Temperature offset | `tracking.ambientOffset`: reported | — | — |
216
+ | Setpoint limits | `targetHeat.lockoutMin` / `lockoutMax`: reported | `MinSetpoint` / `MaxSetpoint`: reported | = R |
217
+ | Screen units | `physicalInterface.format` = `C` / `F`: applied | `Format` = `celsius` / `fahrenheit`: applied | = R |
218
+ | Heater profile | `bbConfig.controlType` = 8 / 9: reported | `DutyCycleOpt` = 8 / 9: reported | = R |
219
+ | Device name / time zone | — | `Name` / `TimeZone`: stored | = R |
220
+ | Schedules | `POST /schedule`; full-body `PUT /schedule/{id}`; `DELETE /schedule/{id}/attach/{dev}` | v1 `POST /schedules/device/update/{dev}`; v2 `POST /schedules/v2/`, `DELETE /schedules/v2/{id}` | = R, with v1 body difference noted below |
221
+ | Hold / resume | `POST /schedule/hold`; `DELETE /schedule/hold/{dev}`: stored/removed | — | — |
222
+ | Firmware trigger | `POST /devices/{dev}/firmware-update` with JSON `{}`: accepted | — | Same trigger returns 403 |
223
+ | Live reporting | `POST /mysa-legacy/stream/home/{home}` with `{"timeout":seconds}`: accepted | — | Same route returns 403 |
224
+ | Claim / remove / replace | — | `POST /devices`, `DELETE /devices/{dev}` | Claim/delete work; existing replacement uses `POST /devices/purgeadd` |
225
+
226
+ The remaining app-oriented operations are intentionally compact here: shortcut
227
+ edit/apply, alert, zone, geofence, and reminder record operations worked on R
228
+ and L; shortcut application was inconsistent. Saved-template create/rename/
229
+ apply/delete worked on both, but event editing worked only on L (R: 404).
230
+ `ecoMode` and `Animation` settings persisted on both without verified physical
231
+ effects. These results do not justify expanding the HA client with those app
232
+ features. Other account/app administration is known from app sources, not
233
+ validated as thermostat functionality.
234
+
235
+ ## Recommended reconciliation of cloud reads
236
+
237
+ The following is a conservative cloud-only selection policy for an integrating application. Home Assistant maintains its own acquisition and selection implementation in the [cloud tracker](https://github.com/SokolskyNikita/mysa-homeassistant/blob/main/docs/implementation/cloud.md).
238
+ Use **S + R** for normal cloud state acquisition: one S batch and one R legacy
239
+ state request at the configured cloud interval. Cache REC separately. Use L
240
+ as a replacement for a failing R route with backoff, not a third permanent
241
+ poll. Do not average values or count matching R/L responses as corroboration.
242
+
243
+ ### One selection rule, applied per field
244
+
245
+ 1. **Normalize.** Decode each source into the same units/enums and preserve
246
+ `source`, `observed_at`, and `fetched_at`. Parse ISO timestamps and Unix
247
+ seconds/milliseconds. Use the individual LT field's `t`, TEL's reading
248
+ timestamp, or the relevant ST section timestamp; never borrow a sibling
249
+ field's timestamp. Keep target temperature separate from an effective
250
+ target altered by a hold or other control behavior.
251
+ 2. **Validate.** Reject missing, malformed, non-finite, or semantically invalid
252
+ values. Zero and `false` are valid where the field allows them. A timestamp
253
+ more than one minute in the future is invalid (the existing HA guard); do not let it win a
254
+ newest-value comparison. A valid clock on one section does not validate
255
+ another section.
256
+ 3. **Choose by the field policy below.** For **Latest** fields, choose the
257
+ newest valid, fresh observation; ties prefer S, then R, then L. Include the
258
+ cached observation so a delayed response cannot roll a newer value back. An
259
+ un-timestamped measurement is not fresh merely because its HTTP response
260
+ just arrived. For **Config** fields, use the named authority/fallback;
261
+ their change timestamps may legitimately be old. REC is stored intent,
262
+ never another device measurement.
263
+ 4. **Keep provenance.** Retain last-known values with their original age when
264
+ no fresh candidate exists; expose stale/unavailable state rather than
265
+ refreshing their timestamps. A missing field or failed device in a batch
266
+ must not erase good data for other fields/devices. Keep pending writes
267
+ separate from confirmed readings.
268
+
269
+ For measurement freshness, a suggested starting limit is **35 minutes outside
270
+ live reporting**, because ordinary Lite uploads can be 12–30 minutes apart.
271
+ During an established live-reporting session, use **max(90 seconds, three poll
272
+ intervals)**. These are configurable policy defaults, not vendor guarantees
273
+ or a claim that a 30-minute-old temperature is real-time. A future-dated
274
+ measurement is rejected rather than having its clock silently corrected.
275
+ For a configuration field with an invalid timestamp, do not use that timestamp
276
+ to outrank another source; retain its unconfirmed/last-known status.
277
+
278
+ | Field / operation | Selection and HA behavior |
279
+ | --- | --- |
280
+ | Room temperature / humidity | **Latest** S TEL vs R/L LT. Use `CorrectedTemp`, never internal sensor temperatures. Accept valid humidity even when the Lite capability flag says false |
281
+ | Heating target / mode | Use the newest plausibly timestamped device report from ST or LT; use TEL only after matching target semantics and decoding its different mode enum. Unlike measurements, an unchanged target need not expire after 35 minutes. Without comparable timestamps prefer ST, then LT, and mark freshness unknown. Never use REC `Mode` or S `desired` as confirmation |
282
+ | Cooling target / fan / swing | **Config:** ST; REC only describes allowed choices. No verified legacy-state fallback in this guide |
283
+ | Minimum / maximum target | **Config:** ST pair, then REC pair if absent; validate minimum ≤ maximum. Never turn an uncertain/fallback value into permission to exceed known device limits |
284
+ | Button lock | Prefer the newest valid device report from ST or LT when timestamps are comparable; otherwise ST, then LT. REC is a stored-setting fallback only |
285
+ | Wake / idle brightness | **Config:** ST pair, then REC pair. LT's single brightness value cannot reconstruct both settings |
286
+ | Screen units | **Config:** REC `Format`, ST as fallback. ST has lagged the screen setting. This selects the configured screen units, not proof of physical application or HA's preferred display unit |
287
+ | Temperature offset | **Config:** ST only; absent means unknown, not zero |
288
+ | Heater profile | **Config:** ST, then REC `DutyCycleOpt` as stored configuration; validate against the device family |
289
+ | Heating activity | **Latest** normalized duty/interval observation. Keep each source's interval meaning; do not add R/L duty or runtime together or infer heater-off from Lite's zero power fields |
290
+ | Runtime / history | Prefer S usage `oH1` for runtime; R batch history only as an explicit fallback after bucket/unit normalization. Fetch bounded periods and do not sum overlapping sources. Historical samples do not become current readings |
291
+ | Identity / name / time zone / firmware | REC for identity/name/time zone; firmware information endpoint for offered/installed version, with ST identity as installed-version fallback. Cache metadata rather than treating old unchanged values as stale telemetry |
292
+ | Capabilities / model controls | S capabilities plus REC model/remote metadata. Preserve hardware limits; do not invent unsupported controls. Model-specific configuration has no general Lite fallback |
293
+ | Schedules / holds | Read S schedules and holds, plus legacy schedule sources when inspecting existing Mysa automation. An empty S list does not establish that no legacy schedule exists. Do not use REC `Schedule` or LT hold hints as the complete resource |
294
+ | Shortcuts / demand response | On-demand shortcut inspection through R/L. For demand response use ST when present, otherwise the dedicated R route; no merging of unrelated account mode fields |
295
+ | Connection / health | Keep request success and data freshness separate for S and legacy reads. A 200 or connected flag cannot make stale measurements fresh; track selected source and age per field. Do not let one failing API mark both paths failed |
296
+ | RSSI / internal temperatures / heap | Apply **Latest** only when diagnostics are requested/enabled. Discard from normal entity publication otherwise |
297
+ | Lite electrical values, unreliable energy summaries, and **Skip** operations | Do not request extra endpoints or create entities for them. Ignore incidental fields returned in shared responses |
298
+
299
+ Cache records at a slower interval (the integration currently uses 10 minutes),
300
+ usage at 15 minutes, firmware at six hours, and capabilities at 24 hours.
301
+ Refresh affected configuration after a command and after rediscovery. Read
302
+ optional schedules/history/diagnostics on demand or when that feature is
303
+ actually enabled. These are cloud fetch rules, not local/cloud routing rules.
304
+
305
+ ## Recommended reconciliation of cloud writes
306
+
307
+ **Choose one route per semantic command. Do not broadcast every write to S,
308
+ R, and L, and do not copy discrepant readings back into the other APIs.**
309
+ Most differences reflect lag or representation, not three stores to repair.
310
+
311
+ | Command | Preferred route | Alternative / verification |
312
+ | --- | --- | --- |
313
+ | Setpoint / mode; supported AC controls | S | No verified R/L replacement. Confirm device-reported values with the read policy; REC `Mode` is not a fallback |
314
+ | Offset | S | No verified alternative; confirm ST |
315
+ | Lock / brightness / setpoint limits | S | R settings are a validated alternative; L is the same legacy path on another host. Select an alternative for a known route/device limitation, not as routine fan-out. Confirm ST or equivalent LT; a REC echo alone is insufficient |
316
+ | Screen units | R settings | L if that route is unavailable; S is also supported. Read REC for configured units and ST for reported confirmation when it catches up |
317
+ | Heater profile | R settings | L as host fallback; S is also supported. Read REC and ST, preserving a pending/mismatched state if application is unconfirmed |
318
+ | Existing device schedule creation | S, only when explicitly using Mysa scheduling | Keep an existing legacy schedule in its native format unless deliberately migrating it. Avoid creating parallel S/v1/v2 schedules for the same intent. Full-body in-place editing is outside client scope |
319
+ | Schedule removal / hold / resume | Remove through the resource's native route; S for holds | Re-read that schedule/hold source. Removing a template is not removing its applied schedules; a hold record alone does not confirm an immediate target change |
320
+ | Firmware update / temporary live reporting | S dedicated endpoint | Confirm firmware availability/version or advancing telemetry, respectively; no R/L duplicate trigger |
321
+ | Claim / remove / replace | Existing explicit setup/recovery method | Use the documented operation and host; never run these as automatic write-repair strategies |
322
+ | Time zone | R, only for an explicit Mysa schedule configuration request | Verify REC; changing HA's time zone must not silently rewrite the thermostat |
323
+ | **Skip** operations, cloud renaming, app-only settings | No new write path | Use HA's corresponding features; no background synchronization into the Mysa app |
324
+
325
+ Validate capabilities and ranges, serialize changes per device, and send the
326
+ smallest semantic change. Send both brightness values together; preserve a
327
+ valid minimum/maximum pair. A setpoint change and a mode change are distinct
328
+ commands unless deliberately combined in one S body. Use the mode-specific
329
+ AC fan conversion in `commands.py`; raw enum values are not interchangeable.
330
+
331
+ Record an accepted command as **pending**, then re-read its confirmation
332
+ source. Matching `desired` or echoed REC is not physical confirmation. If the
333
+ requested value already matched the previous confirmed state, treat it as an
334
+ idempotent no-op, not a liveness test. A changed device report can confirm
335
+ application; where only REC is available, report stored configuration without
336
+ claiming device confirmation.
337
+
338
+ An explicit unsupported-route response can justify a validated alternative.
339
+ After a timeout or 200 without confirmation, re-read first: the command may
340
+ still apply. Do not immediately resend through another API, oscillate settings
341
+ to force synchronization, or apply a late retry after a newer user command.
342
+ Use bounded retries/backoff. Resource creation, firmware triggers, and other
343
+ non-idempotent actions require checking their outcome before any retry.
344
+
345
+ ## Other protocol and implementation details
346
+
347
+ ### Important wire rules
348
+
349
+ | Area | Rule |
350
+ | --- | --- |
351
+ | Time / mode | Legacy field timestamps can mix seconds and milliseconds in one response. S `modes.mode`, TEL `mode`, and LT mode fields have different enums; Lite heat is S mode 4 but TEL mode 3 |
352
+ | Telemetry quality | One Lite returned implausible future TEL timestamps while LT returned current readings. Freshness must be per field/source, not inferred from HTTP success |
353
+ | Electrical data | Lite current/power fields were zero and voltage was configured; legacy device energy summaries also produced implausible averages. Neither establishes measured consumption |
354
+ | Usage | `/telemetry/usage/{dev}` uses `timezone`, Unix-second `start`/`end`, `summary`, and `aggregatedReadings`; raw readings need the expanded request described in the [usage reference](../../../docs/cloud-state.md#usage) |
355
+ | Legacy batch history | `/batchReadings/v2/{dev}` takes `attributes`, `start`, `end`, `attributesProcesses`; relevant fields include `h`, `c`, `s`, `a.oC`, `a.oH`, `ms`, `fs`, `tm`, `t`. Use `sum` for runtime counters, `none` for raw values; normalize timestamp-keyed buckets before use |
356
+ | Modern schedules | Events use Sunday = `"0"`, local `HH:MM:00`, `clientKey`, and semantic `state`. Use the device's `TimeZone`. In-place PUT needs `name`, `events`, `deviceType`, `homeId`, `deviceIds`, `reusable`; a name/events-only body fails |
357
+ | Legacy v1 schedules | POST body uses `ScheduledActions`, mapping weekday names to `{Minute,SetPoint}` entries. R additionally requires `Device` and `Model`. Seven empty weekday arrays clear events |
358
+ | Legacy v2 schedules | POST body: `Schedule` with `Version`, `Owner`, `Events` (plus `Id` for update), `Devices`, and `Model`. GET uses the **device ID**; DELETE uses the **schedule ID** with `Devices` and `Model` in its body |
359
+ | Schedule discovery | Modern lists can omit legacy schedules; REC `Schedule` can retain a deleted ID. Inspect the actual legacy resources. Saved-template apply creates separate `V2Schedules` resources |
360
+ | Holds | `startTime` must be in the future; the client adds ten seconds. Temporary holds add `endTime`. Both are UTC ISO timestamps. Do not infer an immediate setpoint change merely from hold creation |
361
+ | Firmware | POST the trigger with JSON `{}`; a bodyless request returned 415. Trigger acceptance is not completed installation. No newer release was available during validation |
362
+ | Newer-model features | Lite rejected Smart Early On eligibility and had no newer OTA fleet. Remote tracking was not reported after a direct write. Gate these features by model; accepting `desired` is not support |
363
+
364
+ For legacy v2 schedules, `Events` maps weekday number → minute number → device
365
+ command. A Lite example is `{"0":{"1437":{"BB2L":"3|22|%|%|%|%|%"}}}`.
366
+ The pipe-separated command has its own legacy enum/field ordering; do not
367
+ insert S's mode number. Detailed other-model and provisioning notes remain in
368
+ the [state and model reference](../../../docs/cloud-state.md) and
369
+ [account/schedule workflows](../../../docs/cloud-workflows.md). This guide
370
+ owns the reconciled protocol conclusions and selection recommendations.
371
+
372
+ ### Integration status and validation
373
+
374
+ The client exposes the documented endpoints and semantic encoders, with
375
+ model-specific mappings that remain app/code-derived unless hardware evidence is
376
+ identified. Automated protocol tests use simulated responses and do not establish
377
+ device compatibility. Live validation currently covers two Baseboard Lite
378
+ `BB-V2-0-L` devices on firmware `3.17.5.13`; their cloud setpoint and mode
379
+ application differed. Other model profiles, full schedule execution, notification
380
+ delivery, and installation of a new firmware release remain unverified. The
381
+ [Home Assistant cloud tracker](https://github.com/SokolskyNikita/mysa-homeassistant/blob/main/docs/implementation/cloud.md)
382
+ separately records integration policy, gaps, and validation work.
383
+
384
+ ### Using the client
385
+
386
+ `MysaAuth` and `MysaClient` are exported by [`__init__.py`](__init__.py).
387
+ The caller supplies and closes the `aiohttp` session. See the complete
388
+ [cloud example](../../../examples/cloud_basic.py) for login, token persistence,
389
+ account discovery, reading, and an explicitly enabled setpoint write.
390
+
391
+ ### How it is organized
392
+
393
+ | File | Responsibility |
394
+ | --- | --- |
395
+ | [`auth.py`](auth.py), [`srp.py`](srp.py) | Cognito authentication, serialized token refresh, SRP calculations |
396
+ | [`client.py`](client.py) | HTTP requests, endpoint methods, response handling |
397
+ | [`commands.py`](commands.py) | Semantic controls, enum conversion, schedule encoding |
398
+ | [`models.py`](models.py) | Typed response parsing, preserving original JSON in `raw` |
399
+ | [`const.py`](const.py), [`exceptions.py`](exceptions.py) | Hosts, protocol constants/enums, errors |
400
+
401
+ The standalone package has no Home Assistant dependency. The integration owns
402
+ polling, per-field freshness, command confirmation, and local/cloud
403
+ reconciliation; see its
404
+ [architecture guide](https://github.com/SokolskyNikita/mysa-homeassistant/blob/main/docs/architecture.md).
405
+ The project metadata says `0.1.0`, but no release has been published yet.
406
+
407
+ ### Public API and integration helpers
408
+
409
+ The supported top-level API is exported by [`__init__.py`](__init__.py):
410
+ `MysaAuth`, `Tokens`, `MysaClient`, `Bootstrap`, `Capabilities`,
411
+ `DeviceRecord`, `DeviceState`, `FirmwareInfo`, `StateChange`, `UsageBucket`,
412
+ `UsageDay`, `Mode`, `AcFan`, `ButtonLock`, `HeaterType`, `DutyCycleOpt`,
413
+ `Swing`, `ModelFamily`, `MysaError` and its typed subclasses, plus
414
+ `ScheduleEventSpec`, `encode_schedule_events`, `make_state`, `model_family`,
415
+ `value_range`, `iso_utc`, and `jwt_claims`.
416
+
417
+ These additional submodule helpers are also supported because the Home
418
+ Assistant consumer imports them directly:
419
+
420
+ | Module | Supported names |
421
+ | --- | --- |
422
+ | [`commands.py`](commands.py) | `auto_temperature_changes`, `button_lock_changes`, `display_brightness_changes`, `display_unit_settings`, `fan_speed_changes`, `heater_type_settings`, `hvac_fan_changes`, `mode_changes`, `setpoint_limit_changes`, `swing_changes`, `temperature_changes`, `temperature_offset_changes` |
423
+ | [`const.py`](const.py) | `API_BASE`, `EXPERIMENTAL_CLOUD_FAMILIES`, `SWING_VERTICAL`, `model_family`; public models and enums are also re-exported at the package level |
424
+ | [`models.py`](models.py) | The response models exported by the package root, retained at this submodule path for direct imports |
425
+ | [`exceptions.py`](exceptions.py) | The public `MysaError` exception hierarchy, retained at this submodule path for direct imports |
426
+ | [`observations.py`](observations.py) | `MEASUREMENT_MAX_AGE`, `MEASUREMENTS`, `STATE_FIELDS`, `TELEMETRY_FIELDS`, `Observation`, `number`, `observe`, `valid_value` |
427
+ | [`trace.py`](trace.py) | `route_template` for redacted request-route reporting |
428
+ | [`validation.py`](validation.py) | `validate_basic_command` for conservative validation of experimental models |
429
+
430
+ Other names prefixed with an underscore are private implementation details.
431
+
432
+ ### Errors, timeouts, retries, and caller responsibilities
433
+
434
+ The current transport uses a 20-second timeout. A 401 causes one forced token
435
+ refresh and retry; a remaining 401 or 403 raises `MysaApiError`, not automatic
436
+ reauthentication. Route absence or permissions can explain 403. A 404 raises
437
+ `MysaNotFoundError`; only resource-specific helpers such as schedule/hold reads
438
+ normalize an absent resource to an empty result. A 429 raises
439
+ `MysaRateLimitError` with numeric `Retry-After` when supplied; timeout,
440
+ transport failure, or 5xx raises `MysaConnectionError`. There is no general
441
+ transport retry loop. A 401 retry only follows a forced token refresh; ambiguous
442
+ commands are never replayed after cancellation, timeout, or connection loss. The
443
+ legacy host fallback is limited to known operations and eligible failures; it is
444
+ not a general retry or a second measurement source. The caller owns polling,
445
+ backoff, freshness, and deciding how to reconcile an uncertain command.
446
+
447
+ Cognito failures use the `MysaAuthError` hierarchy. The implementation handles
448
+ the password-verifier SRP challenge; unsupported challenges raise
449
+ `MysaUnsupportedChallengeError`.
450
+
451
+ ### Validation references
452
+
453
+ HTTP/parser behavior is covered by [`tests/cloud/test_client.py`](../../../tests/cloud/test_client.py)
454
+ and SRP by [`tests/cloud/test_srp.py`](../../../tests/cloud/test_srp.py), using
455
+ fixtures rather than live devices. Live checks established the distinctions
456
+ between accepted writes, stored records, and reported/applied values above;
457
+ full scheduled execution, notification delivery, and installation of a new
458
+ firmware release were not established. The two Lites also differed in cloud
459
+ setpoint/mode application, so confirmation remains necessary even for a
460
+ supported route.
@@ -0,0 +1,90 @@
1
+ """Standalone async client for Mysa's cloud API.
2
+
3
+ Nothing in this package imports Home Assistant, so it can move to its own PyPI
4
+ package later.
5
+ """
6
+
7
+ from .auth import MysaAuth, Tokens, jwt_claims
8
+ from .client import MysaClient, iso_utc
9
+ from .commands import ScheduleEventSpec, encode_schedule_events, make_state
10
+ from .const import (
11
+ MODEL_FAMILIES,
12
+ MODEL_NAMES,
13
+ SWING_HORIZONTAL,
14
+ SWING_VERTICAL,
15
+ TESTED_MODELS,
16
+ WIFI_ONLY_FAMILIES,
17
+ AcFan,
18
+ ButtonLock,
19
+ ChangeSource,
20
+ DutyCycleOpt,
21
+ HeaterType,
22
+ Mode,
23
+ ModelFamily,
24
+ Swing,
25
+ model_family,
26
+ )
27
+ from .exceptions import (
28
+ MysaApiError,
29
+ MysaAuthError,
30
+ MysaConnectionError,
31
+ MysaError,
32
+ MysaInvalidCredentialsError,
33
+ MysaNotFoundError,
34
+ MysaRateLimitError,
35
+ MysaUnsupportedChallengeError,
36
+ )
37
+ from .models import (
38
+ Bootstrap,
39
+ Capabilities,
40
+ DeviceRecord,
41
+ DeviceState,
42
+ FirmwareInfo,
43
+ StateChange,
44
+ UsageBucket,
45
+ UsageDay,
46
+ value_range,
47
+ )
48
+
49
+ __all__ = [
50
+ "MODEL_FAMILIES",
51
+ "MODEL_NAMES",
52
+ "SWING_HORIZONTAL",
53
+ "SWING_VERTICAL",
54
+ "TESTED_MODELS",
55
+ "WIFI_ONLY_FAMILIES",
56
+ "AcFan",
57
+ "Bootstrap",
58
+ "ButtonLock",
59
+ "Capabilities",
60
+ "ChangeSource",
61
+ "DeviceRecord",
62
+ "DeviceState",
63
+ "DutyCycleOpt",
64
+ "FirmwareInfo",
65
+ "HeaterType",
66
+ "Mode",
67
+ "ModelFamily",
68
+ "MysaApiError",
69
+ "MysaAuth",
70
+ "MysaAuthError",
71
+ "MysaClient",
72
+ "MysaConnectionError",
73
+ "MysaError",
74
+ "MysaInvalidCredentialsError",
75
+ "MysaNotFoundError",
76
+ "MysaRateLimitError",
77
+ "MysaUnsupportedChallengeError",
78
+ "ScheduleEventSpec",
79
+ "StateChange",
80
+ "Swing",
81
+ "Tokens",
82
+ "UsageBucket",
83
+ "UsageDay",
84
+ "encode_schedule_events",
85
+ "iso_utc",
86
+ "jwt_claims",
87
+ "make_state",
88
+ "model_family",
89
+ "value_range",
90
+ ]