Files
Hank MuellerandClaude Opus 5 cde2bbc772 Add lights/jets/salt control, app-traffic mimicry, and optional local read path
- New entities: number (salt output level 0-10), switch (lights, jets); the
  read-only salt sensor is replaced by the settable number.
- Look identical to the official app on the wire: send the app's Dalvik UA on
  REST, okhttp/4.12.0 on the mTLS creds fetch, and "mqtt-<uuid>" client ids.
- Consolidate every constant value into constants.py (well commented); protocol.py
  keeps only the command builders; const.py removed.
- Optional local read path: a `local_status_url` config option polls the dongle's
  local /status endpoint for telemetry instead of the cloud MQTT subscription
  (control stays cloud MQTT). State getters handle both schemas.

No private/internal values in the repo (the spa IP is runtime config only).

Fixes bug-41mqxddz7zeh

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F6B8b3iYNv6QUftK2FDfYb
2026-09-01 15:12:24 +00:00

3.6 KiB
Raw Permalink Blame History

Hot Spring Connected Spa — Home Assistant integration

Monitor and control a Watkins / Hot Spring "Connected Spa" hot tub from Home Assistant, without the vendor mobile app.

  • Climate — target setpoint + current water temperature (heater).
  • Number — FreshWater salt output level (010), settable.
  • Switches — spa lights (all zones) and jets.
  • Sensors — water temperature, salt cartridge state.
  • Live telemetry over the vendor's HiveMQ Cloud MQTT broker, or — optionally — polled from the spa's local /status endpoint (see below).

Requests are sent with the same User-Agents and MQTT client-id format the official app uses, so the traffic is indistinguishable from it.

This is an unofficial integration reverse-engineered from the official app. It talks to Watkins' cloud; there is no local control path. If Watkins changes their API it may break.

Security / credentials

This repository contains no secrets:

  • Your account e-mail + password are entered in the config-flow UI when you add the integration and stored in Home Assistant's encrypted config entry — never in this repo.
  • The vendor mutual-TLS client certificate required to fetch broker credentials is Watkins material (extractable from the app APK). It is not redistributed here. You provide it as three PEM files on the host (below).

Prerequisites: the client certificate

The integration needs three files in a directory on the Home Assistant host (default /config/hotspring/certs/):

File Origin
client1_cert.pem client cert from the app APK (res/raw/client1.p12)
client1_key.pem its private key
ca_cert.pem firmware CA (res/raw/ca_cert.der)

To produce them from the APK's client1.p12 and ca_cert.der (the P12 is protected by a short static password embedded in the app — recover it from the decompiled APK and pass it to openssl below):

P12PASS=...   # static password embedded in the app APK
openssl pkcs12 -legacy -in client1.p12 -passin pass:"$P12PASS" -clcerts -nokeys -out client1_cert.pem
openssl pkcs12 -legacy -in client1.p12 -passin pass:"$P12PASS" -nocerts -nodes -out client1_key.pem
openssl x509   -inform der -in ca_cert.der -out ca_cert.pem

Install

  1. Copy custom_components/hotspring/ into your Home Assistant config/custom_components/.
  2. Place the three PEM files in /config/hotspring/certs/.
  3. Restart Home Assistant.
  4. Settings → Devices & Services → Add Integration → “Hot Spring Connected Spa”, and sign in with your account.

How it works

  1. POST iotsupportportalapi.watkinsmfg.com/api/v1/login/ → JWT.
  2. GET /user_spa_details/ → the spa's rootTopic (MQTT prefix).
  3. mTLS GET iotfirmwareota.watkinsmfg.com:8577/ (client cert) → rotating HiveMQ broker host/user/pass.
  4. Subscribe <rootTopic>/# for telemetry; publish <rootTopic>/<subsystem>/control with {"<subsystem>":{"control":{…}}} for commands (temperature is an integer string in the spa's unit).

The reusable protocol client lives under custom_components/hotspring/api/ (cloud.py, spa.py, protocol.py); all constant values are in constants.py.

Optional: local read path

If Home Assistant is on the spa's LAN, set Local /status URL in the config flow (e.g. http://<spa-ip>/status). Telemetry is then polled from the dongle's local HTTP endpoint instead of subscribed over the cloud broker, keeping monitoring on-LAN and off the vendor cloud. Control still uses the cloud MQTT connection. Leave it blank to use cloud MQTT for reads.

License

MIT — see LICENSE.