-
Notifications
You must be signed in to change notification settings - Fork 9
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
Xiaomi Pro 2 with DisplayDash: Hang in COMM
state in BluePy.wait_notify
#5
Comments
Err, and as usual, as soon as you post something, things get clearer of course 😅 - this should work assuming that bluepy's However, in my case, it seems to always ~immediately return with |
@The-Compiler The |
It looks like it, yes - though I don't see them being logged anywhere, only when I print them in |
That's odd. For now you could add a new function To find the actual solution you would need to find out why it keeps receiving messages out of nowhere.. |
So I tried this: diff --git i/lib/python/miauth/mi/miclient.py w/lib/python/miauth/mi/miclient.py
index bc6eff8..c135c2d 100644
--- i/lib/python/miauth/mi/miclient.py
+++ w/lib/python/miauth/mi/miclient.py
@@ -229,13 +229,16 @@ def on_send_did_state():
def on_confirm_state():
self.ble.write(UUID.UPNP, MiCommand.CMD_AUTH)
+ def on_comm_state():
+ self.ble.disable_notify(self.ble.channels[UUID.RX])
+
self.seq = ((MiClient.State.INIT, None),
(MiClient.State.RECV_INFO, on_recv_info_state()),
(MiClient.State.SEND_KEY, on_send_key_state),
(MiClient.State.RECV_KEY, None),
(MiClient.State.SEND_DID, on_send_did_state),
(MiClient.State.CONFIRM, on_confirm_state),
- (MiClient.State.COMM, None),
+ (MiClient.State.COMM, on_comm_state),
)
self.seq_idx = 0
@@ -255,6 +258,7 @@ def on_confirm_state():
return self.register() # return because of recursion
else:
+ self.ble.enable_notify(self.ble.channels[UUID.RX])
break
def save_token(self, filename):
@@ -284,6 +288,9 @@ def on_send_did_state():
f"{self.remote_info.hex(' ')} != {expected_remote_info.hex(' ')}"
self.ble.write(UUID.AVDTP, MiCommand.CMD_SEND_INFO)
+ def on_comm_state():
+ self.ble.disable_notify(self.ble.channels[UUID.RX])
+
self.seq = (
(MiClient.State.INIT, None),
(MiClient.State.SEND_KEY, on_send_key_state),
@@ -291,7 +298,7 @@ def on_send_did_state():
(MiClient.State.RECV_INFO, on_recv_info_state),
(MiClient.State.SEND_DID, on_send_did_state),
(MiClient.State.CONFIRM, None),
- (MiClient.State.COMM, None),
+ (MiClient.State.COMM, on_comm_state),
)
self.seq_idx = 0
@@ -299,6 +306,8 @@ def on_send_did_state():
while self.get_state() != MiClient.State.COMM:
self.ble.wait_notify(secs=3.0)
+ self.ble.enable_notify(self.ble.channels[UUID.RX])
+
def comm(self, cmd):
if self.get_state() != MiClient.State.COMM:
raise Exception("Not in COMM state. Retry maybe.")
but with that, I now seem to continue getting some kind of endless messages after the login:
any idea on what could be going on there, or how I would debug this? Maybe I should try to sniff/log the comms ScooterHackingUtility does (which works just fine), and see if I notice something compared to miauth? The only exotic thing about my scooter - other than SHFW - is that I have a Display Dash built in. From what I understood, that only uses the UART bus, but maybe that causes those BLE messages somehow too? |
Since it's a single-wire UART line the first thing you should do is to sniff the bus in order to see if that Display Dash is sending messages on the bus. Given the fact that these issues don't appear on my stock scooter I highly suspect this to be the cause. In my Python implementation I don't expect any rouge messages to appear, the state machine can't handle that. It would be interesting to know if my Java implementation is able to deal with that (but I think yes). Regarding: SHU, we don't have access to the source code to check how they designed the timeouts and filters, so I really don't bother. All I can say is that the EC protocol described here has been modeled 1:1 after the official app, a sniff you can find here. Edit: I had a look at the stats Display Dash shows and it must be sending messages to the bus, because most of these stats need to be read from ESC and aren't transported over the heartbeat command. So to fix the endless message bug in |
@The-Compiler This kind of response handling could get you out of the endless message loop during COMM states. Decrypting each packet like this will also give you insights into what the commands are, I could imagine dropping all packets not designated to the client device (identified by the destination byte in the command). |
I ended up just trying to run the m365-mi mqttdump.py example with the mqtt bits hacked out (and some other bugfixes): diff --git i/examples/mqttdump.py w/examples/mqttdump.py
index 788dd86..a09f06b 100644
--- i/examples/mqttdump.py
+++ w/examples/mqttdump.py
@@ -26,7 +26,7 @@
from mim365mi.m365scooter import M365Scooter
-from message import *
+#from message import *
import struct
@@ -47,7 +47,7 @@
from miauth.mi.micrypto import MiCrypto
-from paho.mqtt import client as mqtt_client
+#from paho.mqtt import client as mqtt_client
port = 1883
broker = '127.0.0.1'
topic = "m365/test/"
@@ -67,7 +67,7 @@ def on_connect(client, userdata, flags, rc):
return client
def main():
- mqtt = connect_mqtt()
+ #mqtt = connect_mqtt()
mc = M365Scooter(btle.Peripheral(), args.mac, debug=args.debug)
def lol(reg, payload):
@@ -165,13 +165,13 @@ def lol(reg, payload):
print("Retrieving serial number")
- mc.comm_simplex("55aa032001 10 0e")
- #print("Serial number:", resp.decode())
+ resp = mc.comm("55aa032001 10 0e")
+ print("Serial number:", resp.decode())
time.sleep(3)
- #print("Retrieving firmware version")
- #resp = mc.comm("55aa032001 1a 10")
- #print("Firmware version:", f"{resp[0]}.{resp[1]}")
+ print("Retrieving firmware version")
+ resp = mc.comm("55aa032001 1a 10")
+ print("Firmware version:", f"{resp[0]}.{resp[1]}")
cmd = str(battery_info._raw_bytes.hex())
print("Sending command:", cmd)
And that seems to connect and start dumping things immediately:
Note that however it seems to hang waiting for an answer to the serial number: ^CTraceback (most recent call last):
File "/home/florian/proj/xiaomi-garmin/py/m365-mi/examples/mqttdump.py", line 266, in <module>
main()
File "/home/florian/proj/xiaomi-garmin/py/m365-mi/examples/mqttdump.py", line 168, in main
resp = mc.comm("55aa032001 10 0e")
File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/miauth/mi/miclient.py", line 356, in comm
while self.p.waitForNotifications(3.0):
File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 560, in waitForNotifications
resp = self._getResp(['ntfy','ind'], timeout)
File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 407, in _getResp
resp = self._waitResp(wantType + ['ntfy', 'ind'], timeout)
File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 342, in _waitResp
fds = self._poller.poll(timeout*1000)
KeyboardInterrupt So yeah, I guess indeed it would probably be a good strategy to somehow drop whatever isn't what we actually requested. However, I guess that won't fix Right now I don't feel like I really understand how this all works (only really started with all this recently). Given that I now have a (more or less) working communication example, I'll probably first focus on trying to port it all to the Garmin ecosystem. After that I will probably have a better understanding of it all, and might come back here - but no promises I'm afraid, as I do have a lot on my plate with other projects as well... |
Quick update: It's indeed the DisplayDash... With it disconnected, |
COMM
state in BluePy.wait_notify
COMM
state in BluePy.wait_notify
@The-Compiler Alright, that makes sense so far. Made some changes to the handler since that was a TODO after all. Can you test if #7 solves the issue? |
Closing with #7. For future issues, please open a new issue. |
I'm trying to connect to my Xiaomi Pro 2 (BLE 1.3.6, though I tried 1.2.9 too) using miauth. However, it seems to hang after successfully getting to comms state:
It hangs here:
which seems to make sense to me after reading through the code:
MiClient.register
never gets over this line:miauth/lib/python/miauth/mi/miclient.py
Line 243 in 090cf23
Because
BluePy.wait_notify
is actually an endless loop, calling bluepy'swaitForNotifications
over and over again:miauth/lib/python/miauth/ble/blue.py
Lines 94 to 96 in 090cf23
So I don't see how this could possibly work. Surely I must be missing something?
The text was updated successfully, but these errors were encountered: