-
-
Notifications
You must be signed in to change notification settings - Fork 39
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
Comments
As part of the work I'm doing on the |
Happy to be a tester for my X10 Pro Omni as needed. |
If step by step instructions are supplied to test the commands, I would be
willing to help test on my X10 Pro Omni
…On Thu, Mar 21, 2024 at 11:11 PM russilker ***@***.***> wrote:
Happy to be a tester for my X10 Pro Omni as needed.
—
Reply to this email directly, view it on GitHub
<#68 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABYCTSZ4Y3RTYSPTFPQUHXLYZOOMXAVCNFSM6AAAAABFA2MBG2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMJUGI3DQOBSHA>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
I am happy to help as well. |
Does that mean there might be some light at the horizon for: #46 ass well? 😎 |
Yep, all of these are related to #28
…On Fri, 22 Mar 2024, 15:08 Robin van Kekem, ***@***.***> wrote:
Does that mean there might be some light at the horizon for: #46
<#46> ass well? 😎
—
Reply to this email directly, view it on GitHub
<#68 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABEPFVAGWZHQUAFZGC4ZUDLYZRCQJAVCNFSM6AAAAABFA2MBG2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMJVGMYDKMRZHA>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Count me in as a tester for the #46 issue |
Happy to help here as well. |
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 {
"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 {
"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 but I've only gotten back
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:
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 |
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. |
Great work Sam. Look forward to seeing the progress Luke. :)
…On Thu, Mar 28, 2024 at 7:08 PM Sam Wolfson ***@***.***> wrote:
Thanks! I also noticed that the vacuum is sending out broadcast UDP
packets from port *9667* (not 6666/6667), so that may also be part of the
puzzle. Unfortunately I'm not sure how to decrypt them.
image.png (view on web)
<https://github.com/CodeFoodPixels/robovac/assets/1189703/d571e910-cb67-4ae5-b4fe-9a050c496620>
(I doubt there's anything personal in the payload, but I'm gonna avoid
posting it publicly – if you want it, let me know.)
—
Reply to this email directly, view it on GitHub
<#68 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABYCTS5ZHRNAFOCU4UOF74DY2SPGDAVCNFSM6AAAAABFA2MBG2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMRWGI4TEMRYG4>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
@CodeFoodPixels do you know how the username Entering a random ID and calling 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 |
Good spot! Doing a quick search through a decompiled version of the eufy app,
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 |
I literally
I literally used |
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. |
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. |
@terabyte128 If you want some code to play around with, you can use this as a base: https://github.com/CodeFoodPixels/robovac-schema |
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! |
I was able to capture some more information! I found that the app was making requests to Perhaps more importantly, this is what the request format looks like:
The most interesting thing here is that That's as far as I've gotten for now. I haven't looked into how to obtain |
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,
{
"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}"
}
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. |
Thanks for the update. Glad to see you finding progress.
…On Fri, Apr 5, 2024 at 1:45 AM Sam Wolfson ***@***.***> wrote:
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.
—
Reply to this email directly, view it on GitHub
<#68 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABYCTS3Y7ISHWKZJZUPUJA3Y3Y3A7AVCNFSM6AAAAABFA2MBG2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDAMZYHE4TGNJTG4>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
T2351 is the model number of your vacuum. |
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:
Below is the snippet that I used. The important pieces are:
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:
|
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>"
} |
Are they encoded as protobufs? That was the case for another device: #40 (comment) |
Hmm, could be, I'll dig into that. |
Has anyone got this working with their x10 pro? I just got my new hoover yesterday and cannot get it work. |
@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:
|
@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:
|
Awesome! Thanks @martijnpoppen - this unblocks me for now 💪🏻 |
I can help test if still needed. I have an x10 Omni pro. Would love to have it working with home assistant. |
I to would be happy to help test and give access to a vm of choice on the same network as the robot. |
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. |
@todanator Do you have a docker compose for your REST solution? |
Has there been any additional progress made here? |
Sorry, not from my side currently. Maybe I will have some time in the upcoming weeks |
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! |
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. |
@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 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. |
@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) |
Hi @martijnpoppen , thanks for your response! Which TuyaApi endpoint would I need? From what I've found there used to be a 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: As expected, it's only sending data, but not actually doing anything with it. My goal:
|
@lankhaar as said in my previous post So as far as I know it's not possible. I also tried multiple things for that 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 |
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. |
@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? |
@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. |
@kmaid yes still need to fix that. I got that from another repo on GitHub ;) |
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. |
Wasn't mqtt figured out, can't it be added that way |
Error |
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 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 |
the logged error when debugging is enabled is :- 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. |
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. |
Are there plans to add support for the X10 Pro Omni vac?
The text was updated successfully, but these errors were encountered: