Skip to content

v.1.12.0-beta1

Pre-release
Pre-release

Choose a tag to compare

@jeppesens jeppesens released this 25 Jun 12:15
0f144e6

Eufy Robovac MQTT — v1.12.0-beta1

The biggest release since the live map: Eufy's older, non-MQTT vacuums now work. This adds a full Tuya Cloud path for legacy/scalar robovacs that never spoke Eufy's MQTT protocol, an opt-in direct LAN ("local Tuya") transport that replaces 30-second cloud polling with near-instant push, unified "Eufy" app (v2) login for accounts migrated off the old Eufy Clean app, and first-class modelling for the RoboVac S1 / S1 Pro.

This is a consolidation release: it folds the community legacy/Tuya work tracked in #110 (Tuya Cloud), #111 (S1 Pro), #122 (unified-app login) and #126 (local Tuya) into one branch on top of main, hardens it against an adversarial review, and fixes #131.

⚠️ This is a beta (v1.12.0-beta1). The Tuya Cloud and local transports were validated against the S1 / S1 Pro family; behaviour on other legacy models may need a protocol-version tweak or a productId mapping. Please file an issue with the model and (redacted) diagnostics if a device misbehaves.
New dependency: tinytuya>=1.18.0,<2.0.0, installed automatically by HACS/Home Assistant on upgrade.
The live map and live X/Y position are MQTT-only. Eufy delivers them over a separate encrypted P2P channel (eufy_mega) that hasn't been reverse-engineered, so they're unavailable on either Tuya transport. Everything else — start/stop/pause/return, fan speed, battery, errors, lifetime stats — works.


✨ Highlights

☁️ Tuya Cloud support for legacy (non-MQTT) vacuums

Older Eufy robovacs that talk to Tuya's cloud instead of Anker's MQTT broker are now discovered and controllable from Home Assistant — previously they simply never appeared. A new Tuya Cloud client logs in alongside the existing Eufy login, discovers any Tuya-backed vacuums on the account, and drives them over a plain-value DPS command/parse path (no protobuf).

  • Transport is decided per device at runtime from the Eufy API's mqtt flag — not by model name. MQTT devices keep their full push experience; Tuya-only devices fall onto the cloud path.
  • What works on the legacy path: start/auto clean, play/resume, pause, stop, return to base, find robot, fan speed, spot clean, room clean and edge clean — plus battery, error code + message, charging state, fan speed and work-mode reporting.
  • 30-second polling with backoff. Cloud devices refresh every 30 s; on repeated failures the interval backs off exponentially (capped at 5 min) and the device is marked unavailable after 5 consecutive failures rather than hanging silently.
  • Resilient login. EU-first / US-fallback regions, automatic re-login on session expiry (one retry per command), and a clear aggregate error in the log if every login endpoint fails.

Legacy devices get a deliberately reduced entity set — no dock/station controls, consumables, scenes, zone/area cleaning or numeric controls, because those commands have no legacy equivalent. They keep the vacuum entity, Suction Level, Find Robot and the universal sensors. See Upgrade notes.

⚡ Optional local (LAN) push transport — goodbye 30-second lag

For any Tuya-discovered vacuum, you can opt in to a direct LAN socket to the dock (Tuya local protocol, port 6668) instead of cloud polling:

  • Near-instant updates — state changes push to HA within seconds, and the connection keeps working even while the Eufy/Tuya cloud is unreachable.
  • No key wrangling. The 16-character local key is supplied automatically from your Tuya Cloud login — you never extract or paste it.
  • Per-device, opt-in via Configure → enter the dock's LAN address and (if needed) pick the Tuya protocol version. Host/version fields only appear for devices that actually have a local key.
  • Graceful fallback. If the socket can't open (wrong host, firewall, dock offline, IP changed), the coordinator silently drops back to 30-second cloud polling — the dashboard never goes unavailable just because the LAN address shifted.

🔒 Security model: the local transport is AES-encrypted with your device's local key — it is not TLS and performs no certificate validation, so it's intended for a trusted LAN only. The local key grants full local control of the dock; treat it as a secret and never paste integration debug logs (which can contain it) into public issues. tinytuya is deliberately kept out of the manifest loggers for exactly this reason.

🔑 Unified "Eufy" app (v2) login

Accounts that were migrated to the new "Eufy" app could no longer authenticate against the old Eufy Clean endpoint. Login now tries the v2 unified-app endpoint first (/v1/user/v2/email/login) and falls back to v1, and device discovery gains a home-api fallback plus reconstruction of device entries when the AIOT device list comes back empty (the common unified-app case). Migrated accounts — and their MQTT and legacy devices — are discovered again.

🤖 RoboVac S1 / S1 Pro (T2080 / T2080A)

The S1 Pro (T2080A) is now modelled ("Robovac S1 Pro") alongside the existing S1 (T2080), both in the S-series list. Model resolution no longer truncates codes to five characters, so T2080A is no longer mis-detected as T2080. Crucially, #131: a Tuya-discovered device that exposes a valid local key is kept even when its exact model can't be resolved, instead of being dropped — no device is hard-coded to a model.

🏷️ Manual room-name overrides (Clean Room on Tuya transports)

The room list and the Clean Room select normally arrive over the same encrypted P2P channel as the map — so on Tuya transports the dropdown is empty. You can now supply your own mapping in Configure → the Rooms field, one id: name per line:

1: Lounge
2: Kitchen
3: Playroom

The Clean Room select reappears with those names, and picking one fires room_clean with the matching ID. Overrides also work on MQTT (for friendlier dashboard names) and take priority over device-supplied names. Room IDs are stable across sessions and change only when the dock re-maps the floor.

🩺 Downloadable diagnostics

The integration's device/entry page now offers Download diagnostics — a redacted JSON dump listing per-device api_type, connection_type, current activity, battery, last_update_success, poll interval and consecutive cloud-failure count (device IDs truncated; credentials, tokens, keys and certificates redacted). Paste it into an issue without leaking secrets.


🧩 What's new at a glance

Area Change
Device support Tuya Cloud (legacy/non-MQTT) vacuums; S1 Pro (T2080A) modelling
Transports mqtt (push, unchanged) · cloud (Tuya REST, 30 s poll) · local (Tuya LAN, push) — chosen per device at runtime
Login Unified Eufy app v2 login with v1 fallback + home-api device fallback
Options Per-device Host, Tuya protocol version (3.1/3.3/3.4/3.5, default 3.3), Rooms overrides
Re-auth Expired credentials now trigger HA's Re-authenticate flow instead of failing silently
Diagnostics Downloadable, redacted per-device diagnostics
Manifest integration_type devicehub; new tinytuya requirement

🐛 Bug fixes & hardening

Much of this release is the result of running the consolidated branch through an adversarial review (#126 review + a follow-up pass):

  • No more no-op dropdowns on legacy devices. Legacy vacuums were shown Cleaning Mode / Water Level / Mop Intensity / Cleaning Intensity selects and dock switches whose commands silently did nothing (build_legacy_command returns {}). These novel-only entities are now gated off for legacy devices, and stale ones are pruned from the registry at startup so they stop lingering as "unavailable".
  • Local key can't leak into logs. tinytuya is excluded from the manifest loggers, so HA's debug toggle can no longer surface the device local key or decrypted DPS into logs users paste into issues.
  • Thread-safety. All tinytuya device access (status / send / receive / open / close) is serialized behind an asyncio.Locktinytuya is not thread-safe and the listener and sender ran on separate executor threads. disconnect() now closes the socket before cancelling the listener so close() never races an in-flight receive().
  • Options no longer lose overrides for offline devices. Saving options used to drop per-device overrides for any device whose coordinator failed to init; saves now seed from existing overrides and only remove them when explicitly cleared.
  • S1 Pro no longer dropped during discovery (#131) — see Highlights. Unknown-model devices with a local key are kept and logged so a productId mapping can be added later.
  • Visible errors instead of silent logs. Bad Eufy credentials now trigger re-auth (ConfigEntryAuthFailed); unreachable servers or zero initialisable devices surface "retrying setup" (ConfigEntryNotReady); failed commands raise a HomeAssistantError you can see, instead of only a log line.
  • Dock controls fail loudly before config arrives. Dock selects/switches/number raise "Dock configuration not yet received from device" if changed too early, rather than silently no-op'ing.
  • Setup correctness. Removed a duplicate options update-listener that caused a spurious reload mid-setup and double reloads later; socket-leak, reconnect-after-failure and re-fetch-on-reconnect fixes in the local transport; model resolution no longer truncates codes (T2080AT2080, T22610T2261).

⚙️ Config & options

  • Login discovers MQTT and Tuya Cloud devices; the entry is titled after the discovered device(s).
  • Re-authenticate flow: when credentials expire, HA prompts for just the password (username locked) and reloads the entry.
  • Per-device options (Settings → Devices & Services → Eufy Robovac MQTT → Configure), shown after the existing global settings (map size, marker style, desktop/mobile notifications):
    • Host — the dock's LAN IP or hostname; entering it promotes that Tuya device to local push, blank keeps it on cloud. Validated as a bare IP/hostname (a scheme or :port is rejected).
    • Tuya protocol version3.1 / 3.3 / 3.4 / 3.5 (default 3.3; try 3.4/3.5 on newer models if 3.3 won't connect). Only shown for devices with a local key.
    • Rooms — multi-line id: name overrides (# comments allowed; last duplicate wins; rendered back sorted).
  • Transport is per-device at runtime from the Eufy API mqtt flag — open Configure to see which of your devices are eligible for local transport.

🛠️ Internal & dev

  • New modules:
    • api/tuya_cloud.py — Tuya Cloud REST client: HMAC-SHA256 request signing, AES/RSA password derivation, EU/US region fallback, device discovery, re-login on SID expiry.
    • api/local_tuya.pyLocalTuyaClient: push transport over the Tuya v3.x LAN protocol (port 6668) on tinytuya, lock-serialized executor access, 5 s receive cycle, 5 s→60 s reconnect backoff.
    • api/legacy_commands.py / api/legacy_parser.py — plain-value DPS command builder + parser mirroring the novel build_command / update_state contracts.
    • diagnostics.py — redacted config-entry diagnostics.
    • _orphan_cleanup.pyprune_orphan_entities(), wired into sensor.py and select.py setup.
  • Coordinator dispatches inbound DPS by api_type (novel/scalar/legacy) via _parse_dps(), outbound by build_device_command(), and selects a transport by connection_type (mqtt/cloud/local) in initialize(). New _fall_back_to_cloud() handles local→cloud degradation; _CLOUD_POLL_INTERVAL=30s, _MAX_BACKOFF_INTERVAL=5min, _FAILURE_THRESHOLD=5. Existing MQTT devices default to mqtt so current behaviour is unchanged.
  • cloud.py / http.py: v2→v1 login fallback table, home-api device fallback, AIOT-empty reconstruction, _resolve_model() / _resolve_tuya_model() (no more [:5] truncation), shared injected aiohttp websession.
  • const.py: LEGACY_DPS_MAP (+reverse), LEGACY_WORK_STATUS_MAP, LEGACY_CLEAN_SPEEDS, LEGACY_WORK_MODES; TUYA_PRODUCT_MODELS (placeholder, empty); Tuya creds + TUYA_REGIONS; EUFY_API_LOGIN_V2, EUFY_API_DEVICE_LIST_HOME; CONF_LOCAL_DEVICES/CONF_LOCAL_HOST/CONF_LOCAL_VERSION/CONF_ROOM_NAMES; T2080A"Robovac S1 Pro".
  • manifest.json: version 1.12.0, integration_type hub, loggers (custom_components.robovac_mqtt, paho.mqtt), tinytuya>=1.18.0,<2.0.0.
  • Tests: new test_tuya_cloud.py, test_local_tuya.py, test_coordinator_local.py, test_legacy_commands.py, test_legacy_parser.py, test_diagnostics.py, test_orphan_cleanup.py, test_room_overrides.py, plus expanded cloud/http/coordinator/config-flow/entity coverage and JS signing/encryption reference fixtures.
  • Tooling/CI: uv dev-dependency group mirroring CI's lint/test jobs (requires-python>=3.14.2); flake8/isort/mypy/pylint cleanup; stopped tracking the generated uv.lock.

⬆️ Upgrade notes

  • tinytuya (>=1.18.0,<2.0.0) is a new requirement, installed automatically — no manual action in a standard HACS/HA setup.
  • integration_type is now hub (was device), reflecting that one config entry manages multiple robots. No action needed.
  • No breaking changes to existing MQTT entity IDs. MQTT devices behave exactly as before.
  • Legacy (Tuya) devices expose fewer entities by design — dock/station controls, consumables, scenes, zone/area cleaning and numeric controls are not available on that protocol. room_clean on a legacy device starts a full room clean (it can't target individual rooms).
  • Beta note: the per-device option fields (Host / protocol version / Rooms) ship ahead of their UI translations, so in -beta1 they may render with raw field keys in the Configure dialog. Function is unaffected; friendly labels land before the stable v1.12.0.

📊 Stats

47 files changed · +6,511 / −682 · 6 new modules (api/tuya_cloud.py, api/local_tuya.py, api/legacy_commands.py, api/legacy_parser.py, diagnostics.py, _orphan_cleanup.py)


⚠️ Known issues (beta)

  • The Configure dialog makes you set a notification target before it will save. The options form currently marks the global settings — including Mobile notification service — as required, so saving Configure prompts you to pick (or type) a notification target even if you don't use mobile alerts. Workaround: choose any mobile_app_* service from the dropdown (or type a placeholder) — it has no effect unless you've also enabled desktop/mobile error notifications. This will be relaxed to optional before stable v1.12.0.
  • Per-device fields show a raw device id instead of a name. The per-device Host / Tuya protocol version / Rooms fields ship ahead of their UI translations, so they render with a raw key like 1a2b3c4d__rooms or 1a2b3c4d__host (the first 8 characters of the device id) rather than a friendly label — this is the unfamiliar "id" you may see in the form. They are all optional: leave them blank to keep the device on cloud with no room overrides. Friendly labels land before stable v1.12.0.

Full Changelog: v1.11.2...v1.12.0-beta1