Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Eufy X10 Pro Omni #68

Open
RickSisco opened this issue Mar 21, 2024 · 137 comments
Open

Add support for Eufy X10 Pro Omni #68

RickSisco opened this issue Mar 21, 2024 · 137 comments

Comments

@RickSisco
Copy link

Are there plans to add support for the X10 Pro Omni vac?

@CodeFoodPixels
Copy link
Owner

As part of the work I'm doing on the better-dps branch, I'm making it so that devices with different commands can be supported. I'd need people to contribute/test the commands for the vacuums though.

@russilker
Copy link

Happy to be a tester for my X10 Pro Omni as needed.

@RickSisco
Copy link
Author

RickSisco commented Mar 22, 2024 via email

@damfuu
Copy link

damfuu commented Mar 22, 2024

I am happy to help as well.

@RoadXY
Copy link

RoadXY commented Mar 22, 2024

Does that mean there might be some light at the horizon for: #46 ass well? 😎

@CodeFoodPixels
Copy link
Owner

CodeFoodPixels commented Mar 22, 2024 via email

@daschick111
Copy link

Count me in as a tester for the #46 issue

@gmagnus1
Copy link

Happy to help here as well.

@terabyte128
Copy link

terabyte128 commented Mar 28, 2024

Hello, I've been doing some work with my X10 to try and figure out where the setup process stalls. So far, I've noticed a couple of things:

First, the Eufy API endpoint to list devices seems to have changed. When hitting api/v1/device/list/devices-and-groups, I don't see anything in the items array.

{
  "res_code": 1,
  "message": "",
  "items": [],
  "global_config": {
    "enabled_multi_color_modes": [
      0,
      1,
      2,
      3
    ]
  }
}

I was able to MITM the traffic from the Eufy Clean app, though, and it looks like it instead makes a request to /api/v1/device/v2, which actually gives the device info:

{
  "res_code": 1,
  "message": "",
  "devices": [
    {
      "id": "<snipped>",
      "sn": "",
      "name": "X10 Pro Omni",
      "alias_name": "Dustin",
      "bluetooth": null,
      "wifi": {
        "mac": "<snipped>",
        "wifi_ssid": "",
        "lan_ip_addr": ""
      },
      "product": {
        "id": "<uuid>",
        "name": "X10 Pro Omni",
        "region": "[{\"regions\":[\"ALL\"],\"date\":\"2022-09-01 20:22:49\"}]",
        "default_name": "RoboVac",
        "icon_url": "https://d1teb1w17vl5yo.cloudfront.net/eufyhome/products/T2182_addProduct.png",
        "category": "Home",
        "appliance": "Cleaning",
        "connect_type": 2,
        "description": "T2182 eufy RoboVac L80, connected via EufyHome",
        "product_code": "T2351",
        "wifi_ssid_prefix": "eufy Clean X10 Pro Omni",
        "wifi_ssid_prefix_full": "eufy Clean X10 Pro Omni-",
        "index": 1,
        "create_time": 1640585895,
        "update_time": 1707192290,
        "is_show": false,
        "tuya_pid": "<id>",
        "app_ble_ssid_prefix": "eufy Clean X10 Pro Omni-",
        "device_ble_ssid_prefix": "eufy Clean X10 Omni-",
        "wifi_ssid_prefix_list": [
          "eufy Clean X10 Omni-",
          "eufy Clean X10 Pro Omni-"
        ],
        "device_ble_ssid_prefix_list": [
          "eufy Clean X10 Omni-",
          "eufy Clean X10 Pro Omni-"
        ]
      },
      "user_id": "<uuid>",
      "owner_info": null,
      "home_id": "<uuid>",
      "home_name": "",
      "room_id": "<uuid>",
      "room_name": "Default Room",
      "connect_type": 2,
      "grant_by": 0,
      "software_version": "",
      "index": 0,
      "device_key": "",
      "create_time": 1711493109,
      "update_time": 1711493549,
      "hardware_version": "",
      "scale_type": "",
      "local_code": "",
      "needUpdate": false,
      "setting": {
        "id": "",
        "device_id": "",
        "is_default": true
      },
      "update_packages": []
    }
  ],
  "groups": []
}

Next, I connected to the Tuya API and tried to retrieve the device with:

tuya.get_device("<device id>")

with both devices[0]['id'] and devices[0]['product']['id']

but I've only gotten back

{'t': 1711599563374, 'success': False, 'errorCode': 'PERMISSION_DENIED', 'status': 'error', 'errorMsg': 'PERMISSION_DENIED'}

and I'm not sure what to do from here. It looks like the response from Tuya is pretty generic, so I possibly messed up something about the parameters.

This is the code that I've been playing around with, for reference:

import requests

from tuyawebapi import TuyaAPISession

login = requests.post(
    "https://home-api.eufylife.com/v1/user/email/login",
    json={
        "client_id": "eufyhome-app",
        "client_secret": "GQCpr9dSp3uQpsOMgJ4xQ",
        "email": "<snipped>",
        "password": "<snipped>",
    },
)
login.raise_for_status()
login_data = login.json()

access_token = login_data["access_token"]
refresh_token = login_data["refresh_token"]
base = login_data["user_info"]["request_host"]

phone_code = login_data["user_info"]["phone_code"]
country = login_data["user_info"]["country"]
timezone = login_data["user_info"]["timezone"]
user_id = login_data["user_info"]["id"]

session = requests.Session()
session.headers["token"] = access_token

devices = session.get(f"{base}/v1/device/v2", headers={"category": "Home"})
devices.raise_for_status()
devices_data = devices.json()

tuya = TuyaAPISession(
    username=f"eh-{user_id}",
    region="AZ",
    timezone=timezone,
    phone_code=phone_code,
)

I also port-scanned the vacuum, in case that's helpful, but didn't find any of the known local Tuya ports to be open:

Nmap scan report for <snipped>
Host is up (0.010s latency).
Not shown: 1000 closed tcp ports (conn-refused)
PORT     STATE SERVICE
9668/tcp open  tec5-sdctp

Nmap done: 1 IP address (1 host up) scanned in 2.08 seconds

Unfortunately I wasn't able to glean that much other useful information from the app either. Starting a cleaning cycle triggers a bunch of requests to log.eufylife.com/push_log_hdfs and log.eufylife.com/push_log_es but nothing that is obviously the start signal. I'm happy to share more details about the flows. Note that I was only snooping on HTTP traffic, though, so if the app communicated locally with the vacuum over something else, I wouldn't have seen it.

@CodeFoodPixels
Copy link
Owner

That's some amazing work @terabyte128! Looks like the eufy endpoint you found works for older devices too, so I'm going to switch over to using that.

@terabyte128
Copy link

terabyte128 commented Mar 28, 2024

Thanks! I also noticed that the vacuum is sending out broadcast UDP packets to port 9667 (not 6666/6667), so that may also be part of the puzzle. Unfortunately I'm not sure how to decrypt them.
image
(I doubt there's anything personal in the payload, but I'm gonna avoid posting it publicly – if you want it, let me know.)

@RickSisco
Copy link
Author

RickSisco commented Mar 28, 2024 via email

@terabyte128
Copy link

terabyte128 commented Mar 28, 2024

@CodeFoodPixels do you know how the username eh-{user_id} was deduced? (from here). I think there might be something wrong with how I'm using the Tuya API.

Entering a random ID and calling list_homes() gives more or less the same answer as when I use my real ID, though, so it's hard to tell if my login is correct or not.

For example:

tuya = TuyaAPISession(
    username=f"FAKEUSERID"
    region="AZ",
    timezone=timezone,
    phone_code=phone_code,
)

gives the response:

[{'geoName': '', 'rooms': [], 'gmtModified': 1711668412, 'role': 2, 'gid': 192829708, 'groupId': 192829708, 'displayOrder': 1, 'admin': True, 'dealStatus': 2, 'gmtCreate': 1711668412, 'ownerId': '192829708', 'uid': 'az1711668412228EOl6W', 'groupUserId': 157327173, 'background': '', 'name': 'My Home', 'id': 147452158, 'status': True}]

and making the same request with eh-{user_id} gives the exact same response shape, just with different IDs etc.

@CodeFoodPixels
Copy link
Owner

Thanks! I also noticed that the vacuum is sending out broadcast UDP packets to port 9667 (not 6666/6667), so that may also be part of the puzzle.

Good spot! Doing a quick search through a decompiled version of the eufy app, 9667 and 9668 seem to be used in place of 6667 and 6668.

@CodeFoodPixels do you know how the username eh-{user_id} was deduced?

It was deduced before I had anything to do with this, but again, looking at the app code, it's right. Did you use an actual ID or literally FAKEUSERID?

@terabyte128
Copy link

I literally

It was deduced before I had anything to do with this, but again, looking at the app code, it's right. Did you use an actual ID or literally FAKEUSERID?

I literally used FAKEUSERID, lol. Maybe that's a way for them to defend against attackers trying to enumerate valid user IDs? Especially since they don't seem to require any sort of client secret.

@terabyte128
Copy link

I didn't notice any requests from the app going directly to Tuya servers, but I wasn't specifically looking for that – I'll take another look.

@CodeFoodPixels
Copy link
Owner

I think the contact with the tuya servers is mainly to get keys etc, possibly also when you're not on the same network as the vacuum.

@CodeFoodPixels
Copy link
Owner

@terabyte128 If you want some code to play around with, you can use this as a base: https://github.com/CodeFoodPixels/robovac-schema

@terabyte128
Copy link

terabyte128 commented Mar 29, 2024

I see, I wonder if it only contacts the Tuya server for local keys on initial setup. Getting them on different LANs is a good tip. Thanks for the link – I’ll poke at it more this weekend!

@terabyte128
Copy link

terabyte128 commented Mar 31, 2024

I was able to capture some more information! I found that the app was making requests to /api.json, but on a different server than was encoded in tuyawebapi.py – in my case, https://a1-us.iotbing.com/api.json. Not sure if that makes a difference.

Perhaps more importantly, this is what the request format looks like:

Host:             a1-us.iotbing.com
Content-Type:     application/x-www-form-urlencoded
User-Agent:       EufyHome/20 CFNetwork/1474 Darwin/23.0.0
Connection:       keep-alive
Accept:           */*
Accept-Language:  en-US,en;q=0.9
Content-Length:   1555
Accept-Encoding:  gzip, deflate, br
Cache-Control:    no-cache
==== FORM DATA ===
lang:              en
bizData:           {"nd":1,"customDomainSupport":"1"}
deviceId:          83C29192-633C-4829-8CC0-CB30B65E6167
et:                0.0.2
osSystem:          17.0
bundleId:          com.eufylife.EufyHome
time:              1711840368
lon:               0
channel:           sdk
nd:                1
appVersion:        3.1.0
ttid:              appstore_d
os:                IOS
v:                 1.0
sid:               <snipped>
sign:              <snipped>
platform:          iPhone 11 Pro
postData:          USQUo1wT3zC1Mggnw4Dq8GiAp+pAFKpnkC1Bfiz53pphbXbtpMH2two1Bt/d9foJlYJXqxCUBe23WxRQy2w56kfAXQ18GEWFRK+u5TSULsgRJRW6q1ZOYkKBAU4Is++NMseQ2kMUXr/T3f+UvA4JeD+6xcoTqwrkd3zOa7Sbw878YMxkYklrFaaKOvK8lB+KL2h
zkOx/5rG34U8DQBwMv+aoVlMCZldQVNtd+5ahJhBweBIQ9gLZZkX90JarFy4F6dt4a+R3jwsZcv7L61ly3gJhopchbtzlWv0ETtx5F0NAN2MclLuyMvfoke4vR2XlihNzvdbZfVqEpzhvvMqDw2LFwVXy1Pfw8SHVRzuQCixxmI4lNJx/Jz3Ay++XKMQFTGPumbU7ryn4N+3ptsxGMIN/7
UP5WS3P5NQL50XvrjPsowkPAuMiCwnGw/Hfj29uIi2IobRL8MBspo6iM6pNCNZjexCFDJB4BkS4Vxk8YbqfIbCMmuHQJHoIE/qmico5q3684aVhNnI+fR/nRVRExHCkvl33+mpK8pSqVTRTyficFrpnHBbLnC1HdBpniayunxI5XNHjLfW3Pooj5Vp6DXE1t6pvuxqBmqpftQfQyFpKjwL
UAX2xccHevYnEMInLPxWuThci6yvQdU5yJEZOssfHpUpLnzDIcLTB8i5jc6PxZAnyi/FJPAiH5SUEhr0poIr0U4QCHS5AakCsNtz2p7bcwkrDQ9BwzMX7RoyxJKjDhc6nCyj9B3Pk6vXOyzx3WjEka0fSGfRRP+VlUY2z2sK2hRdF3ge6WH9IgesVmBokh2XQGggBGuSwab37PmgR6Iava
9rfU6bWO8qQX4kJGw==
uid:               az1711493114821bjS7C
sdkVersion:        4.3.0
timeZoneId:        America/Los_Angeles
requestId:         <snipped>
lat:               0
gid:               192606234
clientId:          <snipped>
deviceCoreVersion: 5.3.0
a:                 smartlife.m.api.batch.invoke
cp:                gzip

The most interesting thing here is that postData appears to be encrypted, which is different than what tuyawebapi.py does. (Simply base64-decoding it doesn't produce anything readable.) Looking at this link supports this idea as well, and it also offers a function that can apparently perform the encryption.

That's as far as I've gotten for now. I haven't looked into how to obtain tuya_bmpkey or tuya_appsecret.

@terabyte128
Copy link

I have another progress update. Long story short, from what I can tell it appears that the Eufy Clean app doesn't communicate directly with the vacuum at all. Instead, it sends all commands through a Eufy MQTT server (in my case, aiot-mqtt-us.anker.com:8883). The MQTT server uses TLS.

  • The username for the MQTT server appears to be {user id}-eufy_home, with no password.
  • The topic in my case is cmd/eufy_home/T2351/{vacuum ID}/req (not sure what T2351 represents)
  • The commands themselves look like:
{
    "head": {
        "client_id": "android-eufy_home-{user id}-eufy_android_Android SDK built for arm64_{some random hex digits, maybe a version number?}",
        "cmd": 65537,
        "cmd_status": 2,
        "msg_seq": 1,
        "seed": "",
        "sess_id": "android-eufy_home-{user id}-eufy_android_Android SDK built for arm64_{same hex digits as above}",
        "sign_code": 0,
        "timestamp": 1712295579241,
        "version": "1.0.0.1"
    },
    "payload": "{\"account_id\":\"{account id}\",\"data\":{\"153\":\"BwjpnJTm6jE\\u003d\"},\"device_sn\":\"{vacuum id}\",\"protocol\":2,\"t\":1712295579241}"
}

payload['data'] appears to be base64-encoded, and happily, not encrypted. The bytes seem to correspond exactly to the action performed, and don't change. For example (first digit is the key in the data dict):

154 16,10,14,10,2,8,2,26,0,34,2,8,1,42,2,8,1  # turn on 'auto' mode
154 14,10,12,10,2,8,2,26,0,34,2,8,1,42,0  # turn off 'auto' mode

Unfortunately I haven't been able to send out commands on my own yet. I suspect there might be another piece of auth that I'm missing before I can publish commands to the topic, and/or it might want a client certificate.

@RickSisco
Copy link
Author

RickSisco commented Apr 5, 2024 via email

@RoadXY
Copy link

RoadXY commented Apr 8, 2024

T2351 is the model number of your vacuum.
T2351 = X10 Pro omni

https://support.eufy.com/s/article/T2351-EU-DOC

@terabyte128
Copy link

terabyte128 commented Apr 9, 2024

I was able to connect to the MQTT server and inspect all the messages sent to the relevant topics. I could also publish on the topics and cause the vacuum to perform actions. The MQTT topics are:

  • cmd/eufy_home/T2351/{VACUUM_SERIAL}/req: requests send from the app to the vacuum
  • cmd/eufy_home/T2351/{VACUUM_SERIAL}/res responses from the vacuum

Below is the snippet that I used. The important pieces are:

  • The MQTT user is {USER_ID}-eufy_home
  • You can only subscribe to the topics namespaced under cmd/eufy_home/T2351/{VACUUM_SERIAL}
  • The MQTT server does require the client to present a certificate. I got it by instrumenting the Android Eufy Clean app. I'm not sure I should post the private key here, but you can pretty easily get it yourself. Here's the Frida snippet I used:
Java.perform(function () {
  let MqttConnectionNew = Java.use("com.anker.aiot_sdk.mqtt.MqttConnectionNew");
  MqttConnectionNew["s"].implementation = function (domainMqttBean) {
    console.log(
      `MqttConnectionNew.s is called: domainMqttBean=${domainMqttBean}`,
    );
    let result = this["s"](domainMqttBean);
    console.log(`MqttConnectionNew.s result=${result}`);
    return result;
  };
});

and the code I wrote to connect to the MQTT server:

import json
from base64 import b64decode

import paho.mqtt.client as mqtt
from paho.mqtt.enums import CallbackAPIVersion

# the following can be retrieved using existing scripts against Eufy's API
USER_ID = "<snipped>"
VACUUM_SERIAL = "<snipped>"

client = mqtt.Client(
    CallbackAPIVersion.VERSION2,
    client_id=(
        f"android-eufy_home-{USER_ID}-eufy_android_Android"
        f" SDK built for arm64_{USER_ID}"
    ),
)
client.tls_set(
    certfile="./certs/certificate.pem",
    keyfile="./certs/private_key.pem",
)


def print_bytes(bs: bytes):
    ret = ""
    for b in bs:
        formatted = f"{b:02x} "
        ret += formatted

    return ret


def on_message(client: mqtt.Client, userdata, message: mqtt.MQTTMessage):
    topic = message.topic
    payload = json.loads(message.payload)

    if isinstance(payload["payload"], str):
        payload["payload"] = json.loads(payload["payload"])

    if "res" in topic:
        return  # comment this if you also want the vacuum's responses printed out
        print("<-- ", end="")
    else:
        print("--> ", end="")

    for k, v in payload["payload"]["data"].items():
        try:
            decoded = b64decode(v)
            print(f"{k}: {print_bytes(decoded)}")
            print(v)
        except:
            print(f"failed to decode {k}: {v}")

    # print(topic)
    # print(json.dumps(payload, indent=4))


client.on_message = on_message
client.username = f"{USER_ID}-eufy_home"
client.connect("aiot-mqtt-us.anker.com", 8883)

client.subscribe(f"cmd/eufy_home/T2351/{VACUUM_SERIAL}/#")

client.loop_forever()

The Python snippet just prints out the payloads of each message. Next step is figuring out what they actually mean. It seems like most payloads carry the entire configuration of the vaccum's "mode", but since they're variable length, it's difficult to grok what bytes mean what. Here are some examples:

--> 154: 10 0a 0e 0a 02 08 02 1a 00 22 02 08 01 2a 02 08 01  # smart mode on
--> 154: 0e 0a 0c 0a 02 08 02 1a 00 22 02 08 01 2a 00  # smart mode off
--> 154: 10 0a 0e 0a 02 08 02 1a 02 08 02 22 02 08 01 2a 00 # fast, vacuum+mop, standard suction, medium water level

@terabyte128
Copy link

terabyte128 commented Apr 12, 2024

Eufy appears to be using some obscenely complicated protocol to generate the "set mode" payloads (seems like they're using an AST to build up the message like programming language syntax...why???) so rather than try and wade through all the code, I just brute-forced it. Below is the file that corresponds to the base64-encoded messages associated with each mode-tuple. Every "mode" message appears to be in the format of:

{
    "154": "<mode code>"
}

codes.csv

@terabyte128
Copy link

@CodeFoodPixels
Copy link
Owner

Are they encoded as protobufs? That was the case for another device: #40 (comment)

@terabyte128
Copy link

Hmm, could be, I'll dig into that.

@samonthenetuk
Copy link

Has anyone got this working with their x10 pro? I just got my new hoover yesterday and cannot get it work.

@martijnpoppen
Copy link

martijnpoppen commented Oct 7, 2024

@todanator i did by connecting a mqtt client and test it. But also checked in the decompiled android app.

With that info you can open the proto file and in there you can find the different parameters

In the android app there's a list with dps so you can find which dps belongs to the correct proto file

Edit:
I looked up the DPS for you:
So in your case you need 171

ACCESSORIES_STATUS = "168";
AUTO_RETURN_CLEAN = "156";
BATTERY_LEVEL = "163";
BOOSTIQ_SWITCH = "159";
CLEANING_PARAMETERS = "154";
CLEANING_STATISTICS = "167";
DEBUG_SETTING = "166";
DEVICE_ANALYSIS = "179";
DEVICE_CHILD_LOCK = "176";
DEVICE_INFO = "169";
DEVICE_MEDIA = "174";
DEVICE_STATION = "173";
DEVICE_TIPS_CODE = "178";
DEVICE_UPDATE_STATE = "180";
DEVICE_WARN_CODE = "177";
DO_NOT_DISTRUB = "157";
ERROR_REPORT = "164";
FIND_ROBOT = "160";
LANGUAGE_SETTING = "162";
LOCAL_TIMER_INFO = "164";
MAP_EDIT = "170";
MODE_CONTROL = "152";
MULTIPLE_MAPS_CTRL = "171";
MULTIPLE_MAPS_MANAGE = "172";
POWER_SWITCH = "151";
REMIND_REPORT = "165";
REMOTE_CONTROL_DIRECTION = "155";
SUCTION_GEAR = "158";
VOLUME_LEVEL = "161";
WORKING_STATUS = "153";

@martijnpoppen
Copy link

@todanator I also checked the map request, but it seems like we only get mapIds but not really a way to retrieve the mapUrl from Tuya.

Response of the maps request:

[
  {
    "key": "Result",
    "value": 2,
    "errCode": 107
  },
  {
    "key": "p2p.MapInfo",
    "releases": 2,
    "mapId": 107
  },
  {
    "key": "CompleteMaps",
    "completeMap": [
      {
        "mapId": 107
      }
    ]
  }
]

@todanator
Copy link

Awesome! Thanks @martijnpoppen - this unblocks me for now 💪🏻

@reynolpe
Copy link

I can help test if still needed. I have an x10 Omni pro. Would love to have it working with home assistant.

@jlightfo666
Copy link

I to would be happy to help test and give access to a vm of choice on the same network as the robot.

@todanator
Copy link

For anyone who wants a quick and dirty way to bring basic functionality into HA, you can use @martijnpoppen's SDK to just quickly start a Eufy Clean scene which and be any custom vacuum scenario.

For my own stop-gap solution, I just stood up nodejs endpoint that calls those scenes by number. HA has the ability to make REST calls.

So my setup is HA Button -> HA REST call -> NodeJS endpoint -> Eufy Scene via SDK

@Rustymage
Copy link

For anyone who wants a quick and dirty way to bring basic functionality into HA, you can use @martijnpoppen's SDK to just quickly start a Eufy Clean scene which and be any custom vacuum scenario.

For my own stop-gap solution, I just stood up nodejs endpoint that calls those scenes by number. HA has the ability to make REST calls.

So my setup is HA Button -> HA REST call -> NodeJS endpoint -> Eufy Scene via SDK

I wish I was smart enough to understand this.

@hades200082
Copy link

@todanator Do you have a docker compose for your REST solution?

@chutson92
Copy link

Has there been any additional progress made here?

@devPeete
Copy link

Has there been any additional progress made here?

Sorry, not from my side currently. Maybe I will have some time in the upcoming weeks

@lankhaar
Copy link

I very recently bought myself a Eufy X10 Pro Omni, so now I'm here to join the rescue team of developers, hoping to speed up the process of integrating the device (and hopefully others) into HA.

I've read through the entire issue to get up to speed with all the findings of other devs. Thanks @CodeFoodPixels , @terabyte128 , @martijnpoppen and all others for your hard work so far!

@CodeFoodPixels , is this project still maintained (as in; do you merge any incoming PRs if I happen to make one)? And what's the status of the draft PR you made from the better dps branch? Is it somewhat stable and recommended to fork from, or is it recommended to fork from main branch?

@martijnpoppen, could you maybe elaborate a little bit on how to get the local key for a Eufy device? I used to have a Tuya vacuum before buying this one and getting the local key for that was simple through the Tuya developer portal, however since Eufy is in charge of that account in this case, I'm not sure how to get the local key in that case.

I wish y'all a lovely weekend and hope that together we can make some big progress in the near future on this topic!

@lee-b
Copy link

lee-b commented Dec 1, 2024

If I understand correctly, Eufy's server has no passwords per device, and may be piping any commands, from anyone, to those devices, which contain cameras, maps of building layouts, wifi credentials, etc.?

If so, this is unacceptable for me. I sort of suspected as much and use my eufy without connectivity for now.

BUT, it should be possible to just set up an internal rabbitmq server and fake the DNS entry to point to it, right? Does the Eufy check the server's cert? Anyone tried something like this yet?

Even a proxy that whitelists known-safe commands would be a big improvement.

@hades200082
Copy link

@lee-b All very good points, but I don't see how they relate to getting it to work with Home Assistant?

Also, most of what you suggest would also remove the control of the robot from the Eufy app.

One of the things I love about Home Assistant is that it makes it possible to create home automation without needing to remove the "normal" non-smart-home way that things work. i.e. A light switch still works as a light switch, but HA can also control it.

It sounds like you need a separate stack to handle what you need.

@lee-b
Copy link

lee-b commented Dec 1, 2024

@lee-b well, I was thinking that the implementation would be pretty lightweight, so I might think about adding it as an optional feature to home assistant -- private/local eufy support, rather than cloud. It wouldn't remove control from the eufy app if the phone is vpn'd into your local network and using local DNS; it would make that private too.

@martijnpoppen
Copy link

@lankhaar you can get the localKey via the TuyaApi. However for the X10 you don't need the localKey as that use MQTT. Eufy is moving to MQTT for all their new devices (Security, Lights, Cleaning)

@lankhaar
Copy link

lankhaar commented Dec 3, 2024

Hi @martijnpoppen , thanks for your response! Which TuyaApi endpoint would I need? From what I've found there used to be a tuya.m.my.group.device.list action, but this no longer works.

Is the local key also not required for local control? My ultimate goal would be to decouple the device from the cloud which is why I'm looking for the local key ;-)

I've tried to send the vacuum to clean using your SDK (it's a great SDK btw xD), which does work when my device is accessible from the internet, however when I drop all traffic in my firewall for that device, it no longer works. This of course doesn't come as a surprise, so I'm currently looking for a way to get the local key to test full local control. This is the output when I send commands to the device without it being accessible from the cloud:

image

As expected, it's only sending data, but not actually doing anything with it.

My goal:

  • Test your SDK with cloud (I got that to work already)
  • Test your SDK with local access (WIP, still looking for a reliable way to get the device's localKey)
  • I will either try to rewrite your SDK to Python for HA, or wrap your SDK in an express app and integrate it with HA through an addon.

@martijnpoppen
Copy link

martijnpoppen commented Dec 4, 2024

@lankhaar as said in my previous post
The x10 doesn't have a local key. It doesn't work via Tuya anymore only via MQTT so there's not local control

So as far as I know it's not possible. I also tried multiple things for that
But if you check my SDK and log the responses you'll see it doesn't even find it on Tuya.

Edit: To add on this. The X8 Pro, X9 and S1 are still working via Tuya. However also for these there's no local support. They do have a localkey but when you connect them locally you can only use key: string|number while these Robovacs communicate the same as the X10 (via protobufs). Via Tuya cloud this is possible. (btw a protobuf is not seen as a string)
So unless you rewrite the Local TuyaApi i think it's not possible to have one of these Robovacs running via local control. Before I was building my SDK my Homey app only supported local control. So I tried evrything to keep it local.
Also monitiored all traffic from the G30, X9 and the X10 and saw how they actually were completely different

@leeandy1
Copy link

leeandy1 commented Dec 4, 2024

I have been following this thread for some time in the hope of a solution for the X10. I know in an ideal world local control would be the desired outcome, but I am sure there are many users that would appreciate any type of Home Assistant integration for a start. I wish I had the skills to write an app or integration and could help with this.

I'm sure I say it for many people in that we are grateful for everyone that is spending their own time to help on this. If it's testing that's needed I will be the first to put my hand up and help.

@lankhaar
Copy link

lankhaar commented Dec 4, 2024

@martijnpoppen , thanks again for the elaborate response! I'm sorry for the confusion. I thought you meant that I don't need the local key to make an integration, but it wasn't clear initially that in fact there is no local key at all.

Are you aware of how Tuya/Eufy authenticate it's messages to the vacuum then?

@kmaid
Copy link

kmaid commented Dec 30, 2024

@martijnpoppen. I have been trying out your library and ported some of it to python. I can connect and list devices! I amusingly get all my notifications in German instead of English now 😆. Might not want to hardcode that value when logging in.

@martijnpoppen
Copy link

@kmaid yes still need to fix that. I got that from another repo on GitHub ;)

@leeandy1
Copy link

leeandy1 commented Jan 7, 2025

There has been some progress made in this repo https://github.com/arturonaredo/robovac

Using this fork I can get the X10 to show up in Home Assistant. No control or sensors but at least it gives some light at the end of the tunnel.

@cronner
Copy link

cronner commented Jan 7, 2025

Wasn't mqtt figured out, can't it be added that way

@Biyadh00
Copy link

Biyadh00 commented Jan 8, 2025

There has been some progress made in this repo https://github.com/arturonaredo/robovac

Using this fork I can get the X10 to show up in Home Assistant. No control or sensors but at least it gives some light at the end of the tunnel.

Error
Config flow could not be loaded: {"message":"Invalid handler specified"}

@J2-Tech
Copy link

J2-Tech commented Jan 8, 2025

I am also getting the same error while attempting to configure the eufy robovac integration.

That being said, i made a very simple, very basic and probably not very secure REST API wrapper over @martijnpoppen 's work
https://github.com/J2-Tech/eufy-clean-rest-wrapper

It does not integrate as easily with home assistant, and in the end i'm not using it to automate my X10 because i am not skilled enough to understand how to de-compile the android application, reverse engineer the API and get the floor maps, schedules, etc. However, @martijnpoppen's SDK supports sending basic commands to the vacuum, so perhaps it could be a good reference

@CBDesignS
Copy link

CBDesignS commented Jan 9, 2025

There has been some progress made in this repo https://github.com/arturonaredo/robovac
Using this fork I can get the X10 to show up in Home Assistant. No control or sensors but at least it gives some light at the end of the tunnel.

Error Config flow could not be loaded: {"message":"Invalid handler specified"}

the logged error when debugging is enabled is :-
Error occurred loading flow for integration robovac: No module named 'custom_components.robovac.tuyawebapi'

open robovac/config_flow.py look for the line "from .tuyawebapi import TuyaAPISession" (about line 58) and remove that line. save file then you should be able to run and login.

My x10 shows up as an Robomatic 3000 then you can try to add the device and IP address. it then fails with more calls to the tuya api called from vacuum.py and these calls cause a system reboot that takes forever to restart.

@jandrli
Copy link

jandrli commented Jan 9, 2025

Using Tuya's cloud solution is applicable if more direct methods have failed so far. Yes, in the case of my S1, it's pretty stripped down. Basically, I can just run a cleanup. Pause/stop/return don't work much, the other sensors are not available including the mode (I solved the automation dependent on it via the cleaning time sensor). However, at least it remembers the last map settings from the original app, which is enough for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests