feat(T2276): Add Eufy T2276 (X8 Pro SES) model support#341
feat(T2276): Add Eufy T2276 (X8 Pro SES) model support#341damacus merged 11 commits intodamacus:mainfrom
Conversation
- Add T2276.py vacuum definition for Freddy
There was a problem hiding this comment.
Pull request overview
Adds/updates the model definition for Eufy Clean X8 Pro SES (T2276) to use Tuya protocol 3.5 and standard (human-readable) DPS codes, replacing the prior protobuf/base64-style DPS mapping referenced in Issue #42.
Changes:
- Set
protocol_version = 3.5for T2276. - Replace DPS codes (152–173) with standard Tuya DPS codes (1–135 range, e.g., 2/5/15/101/102…).
- Add support for additional reported datapoints/features (cleaning time/area, DND, BoostIQ).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Add DPS 2 activation after DPS 124 room selection in roomClean handler. The vacuum ACKs the selectRoomsClean payload on DPS 124 but requires DPS 2 = true to actually start navigating and cleaning. - Accept both "roomClean" and "room_clean" command names - Accept both "roomIds" and "room_ids" parameter names - Add RoboVacEntityFeature.ROOM to T2276 features
- Add comment explaining T2276 uses lowercase "auto" (confirmed via protocol 3.5 packet capture, unlike T2128's "Auto") - Rename fan speed key "quiet" to "pure" for UI consistency — the display logic maps device value "Quiet" to "Pure" - Rewrite test_t2276_command_mappings.py to match new protocol 3.5 DPS codes, replacing old protobuf-encoded assertions
Implements the 3-step SESS_KEY_NEG handshake required by protocol 3.5 devices (SESS_KEY_NEG_START → SESS_KEY_NEG_RESP → SESS_KEY_NEG_FINISH) directly in TuyaDevice, enabling the T2276 to connect without relying on an external library. Also adds protocol 3.4/3.5 message framing (AES-GCM, GCM tag, magic prefix/suffix 0x6699/0x9966) and TuyaCipher.set_session_key support. Tested against a live T2276 (Eufy X8 Pro SES) device.
The vacuum needs to ACK DPS 124 (room selection) before receiving DPS 2 (start). Without the delay both commands arrive almost simultaneously and the vacuum ignores the start command.
Two changes: - Reset failure counter after successful session key negotiation so a subsequent clean disconnect (EOF) doesn't compound with prior failures - Cap maximum backoff at 30s instead of 600s so HA reconnects quickly even after many consecutive failures (e.g. when the EufyHome app holds the device connection)
The asyncio.sleep(1) added in 45067a3 requires the asyncio import.
Use CONTROL_NEW (0x0d) for SET and DP_QUERY_NEW (0x10) for GET on
protocol 3.4+ devices, matching TinyTuya's implementation. Prepend
the v3.5 version header (b"3.5" + 12 zero bytes) to payloads for
commands not in the NO_PROTOCOL_HEADER_CMDS exemption set.
Additional v3.5 reliability improvements:
- Reset cipher to local key before session negotiation so stale
session keys don't corrupt the handshake
- Enforce 30s cooldown between connection attempts to prevent
rapid reconnect storms after EOF
- Disable heartbeats for v3.5 (device closes connection on ping)
- Skip pong timeout check for v3.5 (no pings sent)
- Auto-detect 4-byte retcode prefix in v3.5 response decryption
- Reset backoff flag after sleep so next cycle can reconnect
- Add _dps_to_request() helper for device22-style status queries
- Use v3.5 SET payload format: {"protocol":5, "t":…, "data":{"dps":…}}
Key fixes:
- T2276 RETURN_HOME (DPS 101) now sends boolean true instead of string
"return" — device requires boolean type for physical action triggers
- T2276 LOCATE (DPS 103) values mapping added for consistency
- async_return_to_base includes DPS 2 (START_PAUSE) trigger for models
with boolean start/pause, matching async_start pattern
- Gratuitous update parsing: find first '{' instead of fixed 4-byte
offset — handles retcode(4) + version_header(15) prefix correctly
- v3.5 DPS state extraction from nested {"protocol":4,"data":{"dps":{}}}
- async_get for v3.4+: connect-only (device pushes state via 0x08),
no explicit GET/UPDATEDPS which cause "json obj data unvalid"
- Reconnect cooldown reduced to 5s (was 30s, matching device idle timeout)
- Added SET_COMMAND and UPDATEDPS to gratuitous update handlers
All changes tested live on Eufy X8 Pro SES (T2276) via protocol 3.5:
start clean, return home, locate beep all confirmed working physically.
|
👍 thanks. Have you tried this on your vacuum? All the reviewing in the world is useless here. |
Resolves mypy no-untyped-def error in tuyalocalapi.py:1265. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Yes, the whole weekend was dedicated to finally getting this vacuum working. With plenty of trial and error, we finally got it fully functional. |
|
@stevendejongnl no more info required, thanks. You are the most incentivised to get your vacuum working ;) I really just need to check it for dodgy code at this point 😆 These DPS code are absolutely bonkers |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #341 +/- ##
==========================================
- Coverage 73.25% 69.04% -4.22%
==========================================
Files 56 56
Lines 1866 2016 +150
==========================================
+ Hits 1367 1392 +25
- Misses 499 624 +125 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Summary
Adds full support for the Eufy T2276 (X8 Pro SES) including native protocol 3.5 session key negotiation and physical command execution. The previous config used protobuf-encoded values on DPS 152–173 which caused "Incomplete read" errors (#42). The actual device uses human-readable DPS values on codes 1–135 (same structure as T2128).
All commands tested live on T2276 hardware: start clean, return home, locate beep, pause, room clean.
Changes
T2276.py (model definition)
trueto activate"auto"(confirmed via protocol capture, unlike T2128's"Auto")"pure"maps to"Quiet"(device value)vacuum.py
async_return_to_base: now includes DPS 2 (START_PAUSE) as execution trigger for models with boolean start/pause, matching theasync_startpatterntuyalocalapi.py
CONTROL_NEW(0x0d) for SET with{"protocol": 5, "t": …, "data": {"dps": {…}}}payloadb"3.5" + 12 zero bytesprepended before GCM encryption{to handle variable-length binary prefix (retcode + version header){"protocol": 4, "data": {"dps": {…}}}formatasync_getfor v3.4+: connect-only (device pushes state via gratuitous updates 0x08)DPS Reference (T2276)
true=start,false=pauseauto,Edge,SmallRoom,Nosweep,roomRunning,Locating,Recharge,Charging,standby,Paused,completedtruetriggers return to dockQuiet,Standard,Turbo,Boosttruetriggers beepContinue,Pause,Nosweepstart_clean,clean_result,reloc,keyeventsTest plan
{5: "auto", 2: true}) — confirmed physically working{101: true, 2: true}) — confirmed physically working{103: true}) — confirmed physically working{2: false}) — confirmed working{124: base64, 2: true}) — confirmed working (earlier session)