dnandha / miauth

Authenticate and interact with Xiaomi devices over BLE
GNU Affero General Public License v3.0
28 stars 10 forks source link

WIP: Improve comm handler #7

Closed dnandha closed 2 years ago

dnandha commented 2 years ago

Solve #5 Needs testing

dnandha commented 2 years ago

Ah yes, what happens if you add the self.ble.stop_listen() after this line (under the condition that state is COMM state): https://github.com/dnandha/miauth/blob/3aadfb4bd290bf37d14a9f1c198d56d9dcce4e87/lib/python/miauth/mi/miclient.py#L72

Also while writing this I notice that after login() you'll have to set ble.listen back to True, otherwise you won't receive any message on comm(). A start_listening() function could be introduced.

The-Compiler commented 2 years ago

That somewhat works, but is unreliable I think, because those empty frames can appear anywhere - e.g. here:

Retrieving serial number
<- Empty data
Timeout, no answer received
b''   # a print(resp) I added because in one case I got a UnicodeDecodeError
Serial number: 
The-Compiler commented 2 years ago

Here's another one:

Retrieving serial number
<- Empty data
<- 55 ab 10 01 00 8a 90 2e 2e e4 1f 93 9a 42 c7 c2 f1 34 dd 43
b'U\xab\x10\x01\x00\x8a\x90..\xe4\x1f\x93\x9aB\xc7\xc2\xf14\xddC'
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 159, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 155, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 134, in mi_main
    print("Serial number:", resp.decode())
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xab in position 1: invalid start byte
dnandha commented 2 years ago

Ok I see. The serial number is sent across two packets and the decode error happens when only one packet is received, meaning: the empty data packet barges between those two serial number packets.

So instead of stop_listen() on the first empty data packet define a threshold, e.g. 5 empty packets. This should solve the issue.

The-Compiler commented 2 years ago

I tried this on top of your PR:

diff --git i/lib/python/miauth/mi/miclient.py w/lib/python/miauth/mi/miclient.py
index 19feaf7..485e7c4 100644
--- i/lib/python/miauth/mi/miclient.py
+++ w/lib/python/miauth/mi/miclient.py
@@ -35,6 +35,8 @@ class State(Enum):
         CONFIRM = 4
         COMM = 5

+    EMPTY_THRESHOLD = 5
+
     def __init__(self, ble: BLEBase, debug=False):
         self.ble = ble
         self.debug = debug
@@ -64,13 +66,18 @@ def __init__(self, ble: BLEBase, debug=False):
         self.token = b''
         self.keys = b''

-        # counter for sent uart commands
+        # counter for sent uart commands and empty responses
         self.uart_it = 0
+        self.empty_counter = 0

     def main_handler(self, data):
         if not data:
             if self.debug:
                 print("<- Empty data")
+                self.empty_counter += 1
+                if self.empty_counter == self.EMPTY_THRESHOLD:
+                    print(f"Got {self.EMPTY_THRESHOLD} empty packets")
+                    self.ble.stop_listening()
             return

         frm = data[0] + 0x100 * data[1]

but I still get a wild mixture of bluepy.btle.BTLEInternalError: Unexpected response (wr), cryptography.exceptions.InvalidTag, the UnicodeEncodeError from above, and this:

  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 123, in get_state
    return self.seq[self.seq_idx][0]
IndexError: tuple index out of range

I never really got things to work reliably so far, but right now, it looks like it doesn't seem to work at all. I still feel like I only understand a tiny fraction of it all so far, so I'll probably be of limited help...

dnandha commented 2 years ago

@The-Compiler For the stop_listen() you forgot a part: "under the condition that state is COMM state". Otherwise it will count the total empty packets and exit mid-way, but we're only interested in the empty packets of the final state.

dnandha commented 2 years ago

Something like this

>      def main_handler(self, data):
>          if not data:
>              if self.debug:
>                  print("<- Empty data")
> +            if self.get_state() == MiClient.State.COMM:
> +                self.empty_counter += 1
> +                if self.empty_counter >= self.EMPTY_THRESHOLD:
> +                    self.empty_counter = 0
> +                    print(f"Got {self.EMPTY_THRESHOLD} empty packets")
> +                    self.ble.stop_listening()
>              return
>  
>          frm = data[0] + 0x100 * data[1]

EDIT: only count up in COMM state...

The-Compiler commented 2 years ago

Right, that makes more sense. I've also had to set the initial seq to something which has a state in it, so that calling get_state works immediately after __init__ (that was the source of the IndexError: seq_idx being initialized to 0, but seq being ()).

diff --git i/lib/python/miauth/mi/miclient.py w/lib/python/miauth/mi/miclient.py
index 19feaf7..0372e5e 100644
--- i/lib/python/miauth/mi/miclient.py
+++ w/lib/python/miauth/mi/miclient.py
@@ -35,6 +35,8 @@ class State(Enum):
         CONFIRM = 4
         COMM = 5

+    EMPTY_THRESHOLD = 5
+
     def __init__(self, ble: BLEBase, debug=False):
         self.ble = ble
         self.debug = debug
@@ -47,7 +49,7 @@ def __init__(self, ble: BLEBase, debug=False):
         # state machine is supplied with a sequence of ...
         # if seq = [<state>, <on_state_enter_func()>]
         # TODO: create sequence controller class
-        self.seq = ()
+        self.seq = ((MiClient.State.INIT, None),)
         self.seq_idx = 0

         # buffer for send handler
@@ -64,13 +66,19 @@ def __init__(self, ble: BLEBase, debug=False):
         self.token = b''
         self.keys = b''

-        # counter for sent uart commands
+        # counter for sent uart commands and empty responses
         self.uart_it = 0
+        self.empty_counter = 0

     def main_handler(self, data):
         if not data:
             if self.debug:
                 print("<- Empty data")
+            if self.get_state() == MiClient.State.COMM:
+                self.empty_counter += 1
+                if self.empty_counter == self.EMPTY_THRESHOLD:
+                    print(f"Got {self.EMPTY_THRESHOLD} empty packets")
+                    self.ble.stop_listening()
             return

         frm = data[0] + 0x100 * data[1]

With that, I now seem to always get the partial serial number, however:

new state: State.CONFIRM
<- 21 00 00 00
Mi login successful!
new state: State.COMM
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
Got 5 empty packets
Retrieving serial number
<- 55 ab 10 01 00 69 af 3c 81 59 13 56 dd 1b 17 0c 07 cb 0f 29
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 133, in mi_main
    print("Serial number:", resp.decode())
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xab in position 1: invalid start byte
The-Compiler commented 2 years ago

Also while writing this I notice that after login() you'll have to set ble.listen back to True, otherwise you won't receive any message on comm(). A start_listening() function could be introduced.

I've tried that as well:

diff --git i/lib/python/miauth/ble/blue.py w/lib/python/miauth/ble/blue.py
index 3be5510..b8b4ce2 100644
--- i/lib/python/miauth/ble/blue.py
+++ w/lib/python/miauth/ble/blue.py
@@ -93,6 +93,9 @@ def disconnect(self):
     def stop_listening(self):
         self.listen = False

+    def start_listening(self):
+        self.listen = True
+
     def wait_notify(self, secs=1.0):
         while self.p.waitForNotifications(secs) and self.listen:
             continue
diff --git i/lib/python/miauth/mi/miclient.py w/lib/python/miauth/mi/miclient.py
index 19feaf7..b2c52f7 100644
--- i/lib/python/miauth/mi/miclient.py
+++ w/lib/python/miauth/mi/miclient.py
@@ -311,6 +319,8 @@ def on_send_did_state():
         while self.get_state() != MiClient.State.COMM:
             self.ble.wait_notify(secs=3.0)

+        self.ble.start_listening()
+
     def send_encrypted(self, cmd):
         res = MiCrypto.encrypt_uart(self.keys['app_key'],
                                     self.keys['app_iv'],

but no dice:

Mi login successful!
new state: State.COMM
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
Got 5 empty packets
Retrieving serial number
<- Empty data
<- 55 ab 10 01 00 17 a8 16 4a b5 79 a7 4d 86 b7 b6 73 f2 70 44
<- 24 a3 ba a0 fe a4 46 69 99 5b 3b f3
<- 55 ab 22 02 00 bc cd d6 66 e3 d8 3a 5d b8 3e a9 0d 79 09 d6
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 132, in mi_main
    resp = mc.comm("55aa032001100e")
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 368, in comm
    self.ble.wait_notify()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 100, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 47, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 105, in main_handler
    dst, cmd, dec = self.decrypt(self.received_data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 334, in decrypt
    dec = MiCrypto.decrypt_uart(
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/micrypto.py", line 122, in decrypt_uart
    raise Exception("Invalid header.")
Exception: Invalid header.
dnandha commented 2 years ago

Ok I see, so it stops listening before the command to retrieve the serial number has been sent. So the entry condition for the counter should be expanded to: self.get_state() == MiClient.State.COMM and self.received_data

Since I don't have a DisplayDash all I can do at this point is helping you resolve this issue on your end. Once it works, we'll merge.

The-Compiler commented 2 years ago

Still no luck:

Mi login successful!
new state: State.COMM
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
Got 5 empty packets
Retrieving serial number
<- 55 ab 10 01 00 34 6e 34 fe 1f d5 42 be 23 26 f5 00 85 cf 64
<- 62 65 9e 1b df 9c 01 8b 0b ea b4 f4
<- 55 ab 20 02 00 4a fa 24 44 b2 67 2c 9f e1 20 25 25 36 94 6d
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 132, in mi_main
    resp = mc.comm("55aa032001100e")
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 368, in comm
    self.ble.wait_notify()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 100, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 47, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 105, in main_handler
    dst, cmd, dec = self.decrypt(self.received_data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 334, in decrypt
    dec = MiCrypto.decrypt_uart(
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/micrypto.py", line 122, in decrypt_uart
    raise Exception("Invalid header.")
Exception: Invalid header.

Thanks for taking the time to help me get things running! :+1:

dnandha commented 2 years ago

Ah it's because self.received_data is reset too late. Can you copy this line to the newly introduced on_comm_state function? https://github.com/dnandha/miauth/blob/93898e3c98bdecdec8129ac73ca2b6b2f3688b46/lib/python/miauth/mi/miclient.py#L347

This way when entering COMM state, the field will be blanked and we only start counting empty packets once at least one correct message has been received.

The-Compiler commented 2 years ago

Not sure I follow - we currently don't have an on_comm_state anymore. But I assume you mean something like here? https://github.com/dnandha/miauth/issues/5#issuecomment-1186994668

This is what I ended up with, full diff on top of this PR now:

diff --git i/lib/python/miauth/ble/blue.py w/lib/python/miauth/ble/blue.py
index 3be5510..b8b4ce2 100644
--- i/lib/python/miauth/ble/blue.py
+++ w/lib/python/miauth/ble/blue.py
@@ -93,6 +93,9 @@ def disconnect(self):
     def stop_listening(self):
         self.listen = False

+    def start_listening(self):
+        self.listen = True
+
     def wait_notify(self, secs=1.0):
         while self.p.waitForNotifications(secs) and self.listen:
             continue
diff --git i/lib/python/miauth/mi/miclient.py w/lib/python/miauth/mi/miclient.py
index 19feaf7..dcb439f 100644
--- i/lib/python/miauth/mi/miclient.py
+++ w/lib/python/miauth/mi/miclient.py
@@ -35,6 +35,8 @@ class State(Enum):
         CONFIRM = 4
         COMM = 5

+    EMPTY_THRESHOLD = 5
+
     def __init__(self, ble: BLEBase, debug=False):
         self.ble = ble
         self.debug = debug
@@ -47,7 +49,7 @@ def __init__(self, ble: BLEBase, debug=False):
         # state machine is supplied with a sequence of ...
         # if seq = [<state>, <on_state_enter_func()>]
         # TODO: create sequence controller class
-        self.seq = ()
+        self.seq = ((MiClient.State.INIT, None),)
         self.seq_idx = 0

         # buffer for send handler
@@ -64,13 +66,19 @@ def __init__(self, ble: BLEBase, debug=False):
         self.token = b''
         self.keys = b''

-        # counter for sent uart commands
+        # counter for sent uart commands and empty responses
         self.uart_it = 0
+        self.empty_counter = 0

     def main_handler(self, data):
         if not data:
             if self.debug:
                 print("<- Empty data")
+            if self.get_state() == MiClient.State.COMM and self.received_data:
+                self.empty_counter += 1
+                if self.empty_counter == self.EMPTY_THRESHOLD:
+                    print(f"Got {self.EMPTY_THRESHOLD} empty packets")
+                    self.ble.stop_listening()
             return

         frm = data[0] + 0x100 * data[1]
@@ -296,6 +304,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.received_data = b''
+
         self.seq = (
             (MiClient.State.INIT, None),
             (MiClient.State.SEND_KEY, on_send_key_state),
@@ -303,7 +314,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

@@ -311,6 +322,8 @@ def on_send_did_state():
         while self.get_state() != MiClient.State.COMM:
             self.ble.wait_notify(secs=3.0)

+        self.ble.start_listening()
+
     def send_encrypted(self, cmd):
         res = MiCrypto.encrypt_uart(self.keys['app_key'],
                                     self.keys['app_iv'],

but now, the stop_listening never triggers, because self.recieved_data doesn't fill up anymore:

Mi login successful!
new state: State.COMM
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
[...]
^CTraceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 122, in mi_main
    mc.login()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 323, in login
    self.ble.wait_notify(secs=3.0)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 100, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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
dnandha commented 2 years ago

Ok, forget the thing I said about adding self.received_data to the entry condition. I too had to understand the situation and now it's more clear.

Coming back to this:

... but no dice:

Mi login successful!
new state: State.COMM
<- Empty data
<- Empty data
<- Empty data
<- Empty data
<- Empty data
Got 5 empty packets
Retrieving serial number
<- Empty data
<- 55 ab 10 01 00 17 a8 16 4a b5 79 a7 4d 86 b7 b6 73 f2 70 44
<- 24 a3 ba a0 fe a4 46 69 99 5b 3b f3
<- 55 ab 22 02 00 bc cd d6 66 e3 d8 3a 5d b8 3e a9 0d 79 09 d6
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 132, in mi_main
    resp = mc.comm("55aa032001100e")
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 368, in comm
    self.ble.wait_notify()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 100, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 47, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 105, in main_handler
    dst, cmd, dec = self.decrypt(self.received_data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 334, in decrypt
    dec = MiCrypto.decrypt_uart(
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/micrypto.py", line 122, in decrypt_uart
    raise Exception("Invalid header.")
Exception: Invalid header.

The problem here is not the empty data, but the extra received data in the end: "55 ab 22 02 00 bc cd d6 66 e3 d8 3a 5d b8 3e a9 0d 79 09 d6", something which must be coming from the DisplayDash. So there must also be an exit condition: When a new header is received ("55a?") -> done.

I'll update this PR given this and the information from your last post so that we're back on the same page.

Edit: Splitting COMM into RDY_TO_RCV and RDY_TO_SEND seems to be a good solution. Once a message has been successfully received and decoded we enter RDY_TO_SEND and stop receiving messages. Once a new command is sent we enter RDY_TO_RCV and start listening. This also gets rid of manually calling stop_listen() and start_listen().

dnandha commented 2 years ago

Just pushed, it still works with my scooter and I think the send / receive logic is now a bit more clear.

The-Compiler commented 2 years ago

Yay! That does seem to work quite nicely now.

One thing I noticed (already before the latest changes I think, but I can't test without the displaydash right now as I need to leave soon): It only seems to work for the first connection attempt, but not for subsequent ones, unless the scooter is rebooted.

With the code from your PR, I get:

Using Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
<- 55 ab 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 103, in mi_main
    mc.connect()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 116, in connect
    self.ble.connect()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 87, in connect
    self.enable_notify(ch)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 54, in enable_notify
    resp = self.p.writeCharacteristic(ch.valHandle + 1, val, True)
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 543, in writeCharacteristic
    return self._getResp('wr')
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 47, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 87, in main_handler
    if self.get_state() in [MiClient.State.RECV_INFO,
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 126, in get_state
    return self.seq[self.seq_idx][0]
IndexError: tuple index out of range

and when adding a proper initial seq again:

diff --git i/lib/python/miauth/mi/miclient.py w/lib/python/miauth/mi/miclient.py
index 6c76587..f53b0f2 100644
--- i/lib/python/miauth/mi/miclient.py
+++ w/lib/python/miauth/mi/miclient.py
@@ -47,7 +47,7 @@ def __init__(self, ble: BLEBase, debug=False):
         # state machine is supplied with a sequence of ...
         # if seq = [<state>, <on_state_enter_func()>]
         # TODO: create sequence controller class
-        self.seq = ()
+        self.seq = ((MiClient.State.INIT, None),)
         self.seq_idx = 0

         # authentication related stuff

I then get:

Using Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
enabling notifications for: 00000019-0000-1000-8000-00805f9b34fb
<- 55 ab 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 dd ff
Loading token from: ./mi_token
Logging in...
New state: State.SEND_KEY
<- 00 00 01 01
Mi ready to receive key
<- 55 ab 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- f9 ff
<- 00 00 01 00
Mi confirmed key receive
New state: State.RECV_KEY
<- 00 00 00 0d 01 00
Expecting 1 frames
<- 01 00 10 ac c7 f4 0d 2d 61 68 eb 67 40 3e 6f 24 2d 0e
<<-- 10 ac c7 f4 0d 2d 61 68 eb 67 40 3e 6f 24 2d 0e
New state: State.RECV_INFO
<- 55 ab 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Expecting 0 frames
<<-- 
New state: State.SEND_DID
HKDF result: 5a c9 e7 bf 68 f2 42 f9 41 4e c4 d0 6a bc 29 4f 8a bc c1 63 cc 54 18 92 91 26 bf 8d fd 96 4e b1 43 b4 da 31 48 5e b9 19 b8 0b 69 6a 2d ec 6a 25 be 30 a2 46 8a ce bb 0b 0c bf 96 18 0b da 35 90
DEV_KEY: 5a c9 e7 bf 68 f2 42 f9 41 4e c4 d0 6a bc 29 4f
APP_KEY: 8a bc c1 63 cc 54 18 92 91 26 bf 8d fd 96 4e b1
DEV_IV: 43 b4 da 31
APP_IV: 48 5e b9 19
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 122, in mi_main
    mc.login()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 348, in login
    self.ble.wait_notify(secs=3.0)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 100, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 47, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 89, in main_handler
    self.receive_handler(frm, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 168, in receive_handler
    self.next_state()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 151, in next_state
    self.exec_state()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 139, in exec_state
    f()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 319, in on_send_did_state
    assert self.remote_info == expected_remote_info, \
AssertionError:  != 82 cb fc 06 49 e2 f3 09 35 97 df b6 f5 87 c3 5a 65 22 a8 d4 5e 06 32 33 b3 bd 1c c5 59 80 04 5a

or sometimes:

Using Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
enabling notifications for: 00000019-0000-1000-8000-00805f9b34fb
<- 55 ab 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 df ff
Loading token from: ./mi_token
Logging in...
New state: State.SEND_KEY
<- 55 ab 0c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 f3 ff
<- 00 00 01 01
Mi ready to receive key
<- 55 ab 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 dd ff
<- 00 00 01 00
Mi confirmed key receive
New state: State.RECV_KEY
<- 00 00 00 0d 01 00
Expecting 1 frames
<- 01 00 25 c3 dd 63 57 47 b6 d3 84 76 31 08 48 9e 7e c6
<<-- 25 c3 dd 63 57 47 b6 d3 84 76 31 08 48 9e 7e c6
New state: State.RECV_INFO
<- 55 ab 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- f9 ff
<- 00 00 00 0c 02 00
Expecting 2 frames
<- 01 00 31 0b 25 13 83 f7 f1 1f 1d af 9f 8d 34 05 2e db fe 17
<- 02 00 1d 98 8c a4 51 0c 3d 56 86 28 09 f7 d7 38
<<-- 31 0b 25 13 83 f7 f1 1f 1d af 9f 8d 34 05 2e db fe 17 1d 98 8c a4 51 0c 3d 56 86 28 09 f7 d7 38
New state: State.SEND_DID
HKDF result: 45 49 1f c6 d3 59 ee da cb 34 f6 a3 10 fc bd a6 5a 1c b3 99 5e 8e fa a2 d7 f4 9d 25 db d7 9c b7 fb 4c f7 c5 ff 4d ec c0 b8 bf 3d 4f 82 fc 96 50 e3 11 1e b6 cf 49 b1 1c 5a e5 14 6e 0b 39 5f 80
DEV_KEY: 45 49 1f c6 d3 59 ee da cb 34 f6 a3 10 fc bd a6
APP_KEY: 5a 1c b3 99 5e 8e fa a2 d7 f4 9d 25 db d7 9c b7
DEV_IV: fb 4c f7 c5
APP_IV: ff 4d ec c0
<- 55 ab 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 df ff
<- 55 ab 0c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 f3 ff
<- 00 00 01 01
Mi ready to receive key
<- 00 00 01 00
Mi confirmed key receive
New state: State.CONFIRM
<- 21 00 00 00
Mi login successful!
New state: State.COMM_SEND
(Pause listening)
Retrieving serial number
-> 55 aa 03 20 01 10 0e
(Resuming listening)
-->> 55 ab 03 00 00 bf 7a c0 2e 69 d8 58 57 7d 35 a7 0b 81 fa
<- 55 ab 06 01 00 8c b9 52 9b 24 31 37 96 c4 37 cc 00 c1 6d 1f
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 132, in mi_main
    resp = mc.comm("55aa032001100e")
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 394, in comm
    self.ble.wait_notify()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 100, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 47, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 106, in main_handler
    dst, cmd, dec = self.decrypt(self.received_data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 362, in decrypt
    dec = MiCrypto.decrypt_uart(
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/micrypto.py", line 130, in decrypt_uart
    return aes_ccm.decrypt(nonce, ct, None)
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/cryptography/hazmat/primitives/ciphers/aead.py", line 141, in decrypt
    return aead._decrypt(
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/cryptography/hazmat/backends/openssl/aead.py", line 161, in _decrypt
    raise InvalidTag
cryptography.exceptions.InvalidTag

Any idea what that could be about?

(I left the keys in the output this time, as I guess they could be relevant - and I guess I can just register a new token anyways)

dnandha commented 2 years ago

@The-Compiler Yeah... first error is because message is handled before registration is started, second message is because the message received "55ab06" is not the message we wanted and message length is shorter than expected. I pushed a few fixes for these two issues, please report.

The-Compiler commented 2 years ago

First connection still works:

Using Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
BLE message received but no handler.
enabling notifications for: 00000019-0000-1000-8000-00805f9b34fb
Loading token from: ./mi_token
Logging in...
New state: State.INIT
New state: State.SEND_KEY
<- 00 00 01 01
Mi ready to receive key
<- Empty data
<- 00 00 01 00
Mi confirmed key receive
New state: State.RECV_KEY
<- 00 00 00 0d 01 00
Expecting 1 frames
<- 01 00 e7 74 49 f5 e6 22 a8 d6 d7 dd 08 94 39 40 1f 1d
<<-- e7 74 49 f5 e6 22 a8 d6 d7 dd 08 94 39 40 1f 1d
<- Empty data
New state: State.RECV_INFO
<- 00 00 00 0c 02 00
Expecting 2 frames
<- 01 00 73 4e f7 44 64 cc 3f 23 58 e8 e6 f0 16 b3 f5 36 fa d2
<- 02 00 11 83 f7 78 df f6 22 59 31 f1 cc 92 37 4e
<<-- 73 4e f7 44 64 cc 3f 23 58 e8 e6 f0 16 b3 f5 36 fa d2 11 83 f7 78 df f6 22 59 31 f1 cc 92 37 4e
New state: State.SEND_DID
HKDF result: ba d2 36 2b 77 16 cb 67 9f 01 26 f9 63 35 53 d7 fb 2c 82 57 0b 21 2c 65 a0 4b 86 09 10 b4 81 71 94 d5 69 5b 84 34 cf 89 5a f4 61 98 90 eb 0f ce 07 27 02 de 4d 13 db 38 1e 0b fb c1 8b 38 45 1d
DEV_KEY: ba d2 36 2b 77 16 cb 67 9f 01 26 f9 63 35 53 d7
APP_KEY: fb 2c 82 57 0b 21 2c 65 a0 4b 86 09 10 b4 81 71
DEV_IV: 94 d5 69 5b
APP_IV: 84 34 cf 89
<- Empty data
<- Empty data
<- 00 00 01 01
Mi ready to receive key
<- Empty data
<- 00 00 01 00
Mi confirmed key receive
New state: State.CONFIRM
<- 21 00 00 00
Mi login successful!
New state: State.COMM_SEND
(Pause listening)
Retrieving serial number
-> 55 aa 03 20 01 10 0e
(Resuming listening)
-->> 55 ab 03 00 00 c2 ef d1 6b 6b ed fb ef ea 8a 5a c4 3b f7
<- 55 ab 10 01 00 70 bb 96 2d 22 cf 9c d4 45 69 97 57 d9 65 bf
<- 80 35 e3 74 ad a7 22 bd c5 d6 2c f2
<<-- 23 01 10 32 35 37 30 31 2f 30 30 30 39 39 38 30 36
(Pause listening)
Serial number: 25701/00099806
Disconnecting

but the subsequent ones do not:

Using Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
enabling notifications for: 00000019-0000-1000-8000-00805f9b34fb
BLE message received but no handler.
BLE message received but no handler.
Loading token from: ./mi_token
Logging in...
New state: State.INIT
New state: State.SEND_KEY
<- 55 ab 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 dd ff
<- 00 00 01 01
Mi ready to receive key
<- 55 ab 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- f9 ff
<- 00 00 01 00
Mi confirmed key receive
New state: State.RECV_KEY
<- 00 00 00 0d 01 00
Expecting 1 frames
<- 55 ab 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Expecting 0 frames
<- 00 00 00 00 00 00 00 00 dd ff
Expecting 0 frames
<<-- 
New state: State.RECV_INFO
<<-- 
New state: State.SEND_DID
HKDF result: 49 bc 4f ec 9d 3c 4c a7 67 bb 19 17 1c ac 44 6e 43 c4 e2 ab 7a ad 1a 2f c4 10 ae 19 12 68 d1 c5 e5 f0 08 20 5a 44 62 13 b0 12 7f af e6 7f d9 cf 00 8b 2f 96 83 41 08 bf 59 d0 a3 b9 5e a8 6d c0
DEV_KEY: 49 bc 4f ec 9d 3c 4c a7 67 bb 19 17 1c ac 44 6e
APP_KEY: 43 c4 e2 ab 7a ad 1a 2f c4 10 ae 19 12 68 d1 c5
DEV_IV: e5 f0 08 20
APP_IV: 5a 44 62 13
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 122, in mi_main
    mc.login()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 355, in login
    self.ble.wait_notify(secs=3.0)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 101, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 46, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 87, in main_handler
    self.receive_handler(frm, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 166, in receive_handler
    self.next_state()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 149, in next_state
    self.exec_state()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 137, in exec_state
    f()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 326, in on_send_did_state
    assert self.remote_info == expected_remote_info, \
AssertionError:  != b9 91 2b 92 32 95 7b 01 63 77 8f ac 71 55 d6 e2 78 7c e1 c6 f5 50 bc 37 5f 91 51 b3 68 a0 b9 59
dnandha commented 2 years ago

So what's interesting is that in the first try no rogue messages appear during registration/login but in the second try they do. Almost like the DisplayDash has some internal logic when to stop and resume sending coupled to the authentication process.

Anyway, I think I can come up with additional sanity checks for the packages received for all the authentication states. Except a commit soon.

dnandha commented 2 years ago

@The-Compiler Alright, it's not your turn to test! Made the authentication process much more explicit.

The-Compiler commented 2 years ago

Yay! :tada:

It seems to be much more reliable now, but still sometimes fails with:

Using Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
enabling notifications for: 00000019-0000-1000-8000-00805f9b34fb
BLE message received but no handler.
BLE message received but no handler.
Loading token from: ./mi_token
Logging in...
New state: State.INIT
New state: State.SEND_KEY
<- 00 00 01 01
Mi ready to receive key
<- 55 ab 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 df ff
<- 55 ab 0c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 f3 ff
<- 00 00 01 00
Mi confirmed key receive
New state: State.RECV_KEY
<- 00 00 00 0d 01 00
Expecting 1 frames
<- 01 00 6e 1f 34 15 43 ba a4 d2 ed d7 9f 1a 23 48 86 18
<<-- 6e 1f 34 15 43 ba a4 d2 ed d7 9f 1a 23 48 86 18
New state: State.RECV_INFO
<- 55 ab 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 dd ff
<- 00 00 00 0c 02 00
Expecting 2 frames
<- 01 00 00 f7 64 f4 dd ac 0e 5e 62 3b 57 8c 39 5f 7e 50 a5 13
<- 02 00 8e 3e d0 ab 34 4a 12 51 2d 54 51 36 35 4e
<<-- 00 f7 64 f4 dd ac 0e 5e 62 3b 57 8c 39 5f 7e 50 a5 13 8e 3e d0 ab 34 4a 12 51 2d 54 51 36 35 4e
<- 55 ab 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
New state: State.SEND_DID
HKDF result: b5 cd 70 99 b4 9e c6 18 90 a4 c5 54 ce 4b 2b 2c 60 35 73 5e 88 35 48 a8 85 2d d7 54 af bc 37 8d 9a e6 d2 54 38 67 32 99 42 1a 41 a4 2f e6 3e c9 f8 50 30 a5 4f 47 77 73 d2 c2 bf 1c 3d a1 64 11
DEV_KEY: b5 cd 70 99 b4 9e c6 18 90 a4 c5 54 ce 4b 2b 2c
APP_KEY: 60 35 73 5e 88 35 48 a8 85 2d d7 54 af bc 37 8d
DEV_IV: 9a e6 d2 54
APP_IV: 38 67 32 99
<- f9 ff
<- 00 00 01 01
Mi ready to receive key
<- 00 00 01 00
Mi confirmed key receive
New state: State.CONFIRM
<- 21 00 00 00
Mi unknown response...
New state: State.COMM_SEND
(Pause listening)
Retrieving serial number
-> 55 aa 03 20 01 10 0e
(Resuming listening)
-->> 55 ab 03 00 00 97 6f 82 38 c8 ed 9c d1 14 76 85 86 85 f9
<- 55 ab 20 01 00 7d e2 c9 ea 77 8f 1a 08 d7 11 05 91 47 ca 1e
Rouge message received, ignoring.
<- 52 7e 9d 4b f9 73 01 ef ed ad 11 d6 26 28 f4 a3 80 36 33 e2
<- b0 06 b4 6b 72 38 33 ec
<- 55 ab 0c 02 00 b6 c1 08 07 98 20 ae ff f2 ff 7a 57 79 15 fc
Rouge message received, ignoring.
<- c6 f0 90 be d5 99 48 f3
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 132, in mi_main
    resp = mc.comm("55aa032001100e")
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 404, in comm
    self.ble.wait_notify()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 101, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 46, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 91, in main_handler
    dst, cmd, dec = self.decrypt(self.received_data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 372, in decrypt
    dec = MiCrypto.decrypt_uart(
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/micrypto.py", line 122, in decrypt_uart
    raise Exception("Invalid header.")
Exception: Invalid header.
dnandha commented 2 years ago

@The-Compiler Ok, very nice. Added an additional filter for rogue UART messages. Hope with this it's all clear.

The-Compiler commented 2 years ago

Looking great now, and working fine in 9 out of 10 cases.

In the remaining 1 out of 10, it seems to hang:

sing Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
BLE message received but no handler.
BLE message received but no handler.
BLE message received but no handler.
enabling notifications for: 00000019-0000-1000-8000-00805f9b34fb
Loading token from: ./mi_token
Logging in...
New state: State.INIT
New state: State.SEND_KEY
<- 55 ab 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- f9 ff
<- 00 00 01 01
Mi ready to receive key
<- 00 00 01 00
Mi confirmed key receive
New state: State.RECV_KEY
<- 00 00 00 0d 01 00
Expecting 1 frames
<- 55 ab 0c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 f3 ff
<- 01 00 9e f5 22 2c 1b d4 38 a3 0b 79 13 71 a5 6e 79 e9
<<-- 9e f5 22 2c 1b d4 38 a3 0b 79 13 71 a5 6e 79 e9
New state: State.RECV_INFO
<- 00 00 00 0c 02 00
Expecting 2 frames
<- 55 ab 22 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- 00 00 00 00 00 00 00 00 dd ff
<- 01 00 c6 25 64 17 ec 97 d2 e9 15 ad 7d 02 74 0a 9d 12 7d fb
<- 02 00 a5 ff 55 11 49 cb 34 3b 70 b8 71 04 28 ad
<<-- c6 25 64 17 ec 97 d2 e9 15 ad 7d 02 74 0a 9d 12 7d fb a5 ff 55 11 49 cb 34 3b 70 b8 71 04 28 ad
New state: State.SEND_DID
HKDF result: 19 2d c5 d4 d3 0d c1 b1 66 4e 21 c1 0b 65 cb c0 55 47 72 f5 71 c9 20 78 12 a9 1c f4 b1 16 52 0e 1d ac 0e 4e ac 7b 11 00 3e 8d 92 16 df 92 51 82 f3 ed 06 4f 38 f5 a4 84 3c 84 44 c1 cf fd 07 3c
DEV_KEY: 19 2d c5 d4 d3 0d c1 b1 66 4e 21 c1 0b 65 cb c0
APP_KEY: 55 47 72 f5 71 c9 20 78 12 a9 1c f4 b1 16 52 0e
DEV_IV: 1d ac 0e 4e
APP_IV: ac 7b 11 00
<- 00 00 01 01
Mi ready to receive key
<- 55 ab 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
<- f9 ff
<- 00 00 01 00
Mi confirmed key receive
New state: State.CONFIRM
<- 21 00 00 00
Mi login successful!
New state: State.COMM_SEND
(Pause listening)
Retrieving serial number
-> 55 aa 03 20 01 10 0e
(Resuming listening)
-->> 55 ab 03 00 00 3b bb 3c 5f 5b 26 93 01 22 7a 4f 42 29 fc
<- 55 ab 0c 01 00 2b c4 a4 58 31 da 6a 35 12 b3 d8 27 d3 1a 54
Rogue message received, start ignoring.
<- a3 dd 79 3d 92 15 7b f6
<- 55 ab 22 02 00 98 e2 a9 08 a5 90 12 d4 e6 8a 82 14 00 29 62
Rogue message received, start ignoring.
<- 9a 5e ca a5 56 f3 bb 8c 4c 20 c4 c1 dc 5b 7b d0 bc 1a 81 ba
<- 33 80 fe 78 64 73 c8 67 5a e9
<- 55 ab 06 03 00 5a dd 7a ab 69 a2 75 0a 62 98 b4 5a 95 ac d6
Rogue message received, start ignoring.
<- f1 f7
<- 55 ab 20 04 00 a2 4c 81 1e f6 1f 0b 33 3e 29 82 67 9e 38 a4
Rogue message received, start ignoring.
<- f2 52 8a 3f 26 78 5b 35 d5 ea f5 07 ae f6 6f cb 78 3f ab c2
<- e2 de 44 8f d5 20 b1 eb
<- 55 ab 22 05 00 3f 6c b5 e8 7b ea b2 93 d8 b4 7b 8c c8 a4 9c
Rogue message received, start ignoring.
<- e5 24 60 3a 1c da 9c 6b 47 ef 99 9d c7 29 33 2e d6 cc 81 ea
<- 7c 87 42 a8 56 fb fa 83 26 e7
<- 55 ab 0c 06 00 8f cf 9d f4 95 9b 9d ca fb c6 1a 07 da c5 9c
Rogue message received, start ignoring.
<- 3a c9 dc 46 d5 a9 a7 f2
<- 55 ab 06 07 00 de 0f 81 fc 51 ac 32 85 41 a7 a3 f3 fc 8a 51
Rogue message received, start ignoring.
<- 7f f7
<- 55 ab 20 08 00 7a ee 36 f3 61 8e cd 84 3d e2 6c bc a5 1c 5c
Rogue message received, start ignoring.
<- 24 0c a9 8f 3c c8 43 e6 fc d7 2b d9 c3 8c e3 bf 1f 78 f7 13
<- 59 8d f1 6a ba 07 a2 e9
<- 55 ab 22 09 00 52 c9 df 88 48 52 27 da d6 3e 37 4f 2e 2f 6f
Rogue message received, start ignoring.
<- 48 0d d5 2f cc ae 5b b0 1c 7e 0e 30 a2 64 1a df da 6a c5 70
<- 26 60 23 5d cd da 41 73 c2 ec
<- 55 ab 0c 0a 00 a7 8e b0 8d 18 98 d5 d9 0c 5d 53 70 fb da d2
Rogue message received, start ignoring.
<- e0 a2 0e fb 63 4b 0d f4
<- 55 ab 06 0b 00 74 63 8a f3 45 0f fe ed e0 63 3f 73 96 99 53
Rogue message received, start ignoring.
<- e4 f7
<- 55 ab 20 0c 00 64 86 dc d1 a1 a2 e9 4d 49 24 e6 5f e4 10 a5
Rogue message received, start ignoring.
[etc.]

but I can't actually spot the answer at all there, so there might be nothing we can do about that anyways?

dnandha commented 2 years ago

@The-Compiler In that case the expected response never came. Maybe something colliding on the bus. I've pushed a small change that gets you out of this situation by giving up after three non-expected / rogue messages. The comm() function should exit properly with an empty response. On your side: if you receive an empty response, simply resend the command.

Please test one last time, then I'm merging.

The-Compiler commented 2 years ago

Some more from testing for a couple of mins:

[...]
Mi login successful!
New state: State.COMM_SEND
(Pause listening)
Retrieving serial number
-> 55 aa 03 20 01 10 0e
(Resuming listening)
-->> 55 ab 03 00 00 47 b7 ee cf 7e f3 57 25 3f 0c b0 0a 4f fa
<- 55 ab 20 01 00 62 57 5e 91 00 5b a4 36 15 02 a9 ba f5 bb 97
<- 49 12 f3 1b d2 ec 0d 32 6c 2d 6c 31 51 f7 30 18 a2 43 d2 a2
<- 2a 5e a3 fb ee 3a 6d ed
<<-- 25 01 40 00 0e 02 0e 03 0e 05 0e 03 0e 01 0e 07 0e 04 0e 07 0e 02 0e 00 00 00 00 00 00 00 00 00 00
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 132, in mi_main
    resp = mc.comm("55aa032001100e")
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 412, in comm
    self.ble.wait_notify()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 101, in wait_notify
    while self.p.waitForNotifications(secs) and self.listen:
  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 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 46, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 106, in main_handler
    raise Exception("Invalid response received.")
Exception: Invalid response received.

and:

Mi login successful!
New state: State.COMM_SEND
(Pause listening)
Retrieving serial number
-> 55 aa 03 20 01 10 0e
(Resuming listening)
-->> 55 ab 03 00 00 b4 9f be 72 ef 3f ff bd 24 f8 ee 5b 2a f8
<- 55 ab 06 01 00 66 c1 06 cf 77 11 ba dd bb 08 82 77 42 ba 49
Traceback (most recent call last):
  File "/usr/lib/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 158, in <module>
    main()
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 154, in main
    mi_main(ble)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/cli.py", line 132, in mi_main
    resp = mc.comm("55aa032001100e")
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 411, in comm
    self.send_encrypted(cmd)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 373, in send_encrypted
    self.ble.write_chunked(UUID.TX, res)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 71, in write_chunked
    self.write(ch, chunk, resp=resp)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 66, in write
    self.channels[ch].write(data, resp)
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 200, in write
    return self.peripheral.writeCharacteristic(self.valHandle, val, withResponse)
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 543, in writeCharacteristic
    return self._getResp('wr')
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/bluepy/btle.py", line 416, in _getResp
    self.delegate.handleNotification(hnd, data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/ble/blue.py", line 46, in handleNotification
    self.handler(data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 99, in main_handler
    dst, cmd, dec = self.decrypt(self.received_data)
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/miclient.py", line 380, in decrypt
    dec = MiCrypto.decrypt_uart(
  File "/home/florian/proj/xiaomi-garmin/py/miauth/lib/python/miauth/mi/micrypto.py", line 130, in decrypt_uart
    return aes_ccm.decrypt(nonce, ct, None)
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/cryptography/hazmat/primitives/ciphers/aead.py", line 141, in decrypt
    return aead._decrypt(
  File "/home/florian/proj/xiaomi-garmin/py/.venv/lib/python3.10/site-packages/cryptography/hazmat/backends/openssl/aead.py", line 161, in _decrypt
    raise InvalidTag
cryptography.exceptions.InvalidTag

The "endless comms loop" issue can be reproduced by running the CLI immediately again after the previous invocation exits, before getting the disconnected double-beep from the BLE.

Note I'm also fine with ignoring the issues above - they seem to happen seldom enough that it shouldn't be a problem dealing with them somehow, and I'm already really thankful for all the fixes! :heart:

The-Compiler commented 2 years ago

Hmm, the "reconnecting before disconnect beep" issue actually seems like a separate one, as those are actually not rogue messages (or at least not shown as such). Here is the log from the second invocation (before the first fully disconnected):

Using Mi
Connecting
enabling notifications for: 6e400003-b5a3-f393-e0a9-e50e24dcca9e
enabling notifications for: 00000010-0000-1000-8000-00805f9b34fb
BLE message received but no handler.
BLE message received but no handler.
enabling notifications for: 00000019-0000-1000-8000-00805f9b34fb
Loading token from: ./mi_token
Logging in...
New state: State.INIT
New state: State.SEND_KEY
<- e2 00 00 00
<- 55 ab 20 20 00 5c 8b 63 c8 34 69 a3 3c 55 f9 f6 a7 62 85 c8
<- 1b 80 32 6a 52 05 d7 66 1d 87 e1 18 09 43 18 52 c3 d9 e8 e0
<- 6f c6 b6 42 9b c9 84 eb
<- 55 ab 0c 21 00 75 8a 07 74 26 53 1d c3 3f 21 e2 9d 6e 82 ff
<- 50 a0 ad 23 d9 fc 9c f5
<- 55 ab 22 22 00 e9 df e0 64 dc 9d 90 98 a0 25 aa fd 89 d3 9e
<- 2a 2c 54 7d 94 4a d0 10 73 ee 89 2e dd 9f 80 24 ac 97 26 f9
<- 9e 17 58 4f a2 a8 35 42 0c e9
<- 55 ab 06 23 00 1f 5e 1f e3 c1 c9 fc f7 0d f4 6b 90 0c 1a 2d
<- 8b f8
<- 55 ab 20 24 00 e6 45 d0 de 5c 37 76 23 3f 95 3b d6 78 b0 6c
<- f0 82 3c b5 05 84 5d 0d 3f 3d 2a a6 eb 63 7a 86 6b 36 35 44
<- a9 fd 40 2b 2f 6f 84 ed
<- 55 ab 0c 25 00 d2 28 0c 7e 75 55 c7 42 0b 43 c2 b0 c7 56 74
<- f7 b4 42 c0 aa e0 ef f4
<- 55 ab 22 26 00 15 23 bc e0 07 9a 1b 37 a1 d1 ff 68 71 90 3d
<- 08 e5 04 97 0b 87 be d9 bb f1 c5 c9 60 37 e1 01 96 3f d5 bf
<- fa 69 13 d5 a3 30 83 b4 b7 e9
<- 55 ab 22 27 00 ba 0e dd 4d a8 99 96 25 b1 9f 1d b4 b3 0e d5
<- 1a b4 e8 75 2d 23 d4 48 30 55 0a a1 03 7d 5f 1a 00 a5 d8 0f
<- b4 6d e5 5e d7 b3 ab 52 da eb
<- 55 ab 06 28 00 0c 7a 35 3d 6a f9 8a 70 59 7a a8 8e 43 f4 b5
<- 87 f8
<- 55 ab 20 29 00 a7 e5 e9 35 a1 30 f6 53 82 6d b0 75 32 99 2c
<- df 64 eb ff 4a 57 c2 36 94 5d 73 a7 cb 2a a9 12 f6 90 41 63
<- 80 8c 78 7e 0a c3 6d ea
<- 55 ab 0c 2a 00 15 2c 4f 94 a3 03 cf 40 fa da 94 b0 24 40 f1
<- 5c 76 be ff 90 a7 bd f4
<- 55 ab 22 2b 00 18 8d b0 fc 40 48 76 a4 31 36 b8 8b e5 b1 5e
<- ba 9b d3 6c ff 30 dd e2 3e cf a6 2c 42 cf f5 b5 29 09 3a 10
<- f3 34 cd 40 97 2d bf cb 07 e9
<- 55 ab 06 2c 00 25 ac 8a 3a d3 ab 0e 48 14 06 ab 85 e9 70 e7
<- da f8
<- 55 ab 20 2d 00 96 ce 95 a5 a5 0d ff ef 73 94 d2 48 4a 45 47
<- fc b4 7b 81 8f 85 a8 af d9 d9 0c 58 53 d5 b4 8a 8a 06 81 9f
<- 14 de 11 b8 5f 09 17 ea
<- 55 ab 0c 2e 00 15 3d 05 21 23 03 de 4f c6 45 1a 50 13 c1 00
<- ea d5 13 5b a3 25 bc f8
<- 55 ab 22 2f 00 91 72 a1 e3 99 e7 e5 5d 32 69 c1 00 59 07 9c
<- 6d d3 c6 3a 0e 04 0e 7b 1b 6f 7a 97 24 33 e0 c6 fe 30 8f ed
<- 2a cc 58 b9 90 97 3b 81 06 eb
<- 55 ab 06 30 00 16 42 a5 75 2e 48 0b 70 d2 01 d4 e3 f5 0f 4e
<- 8a f9
<- 55 ab 20 31 00 07 ea 2f 95 82 15 49 e3 07 c1 d5 3b 03 d0 3c
<- c2 2a 5a 5f 41 10 e3 3f d3 d1 17 58 1a 96 86 20 f5 ad 8d 2c
<- f8 22 7b 87 4d 3e cc ed
<- 55 ab 0c 32 00 68 40 cc 6f 92 b6 5b 65 99 b2 f4 98 21 02 7b
<- 3e d0 68 49 2c ee 88 f5
<- 55 ab 22 33 00 bc 47 e9 ac e9 3b eb 70 e1 8f dc c5 28 eb 32
<- 21 1d 9a 03 9e b3 75 04 52 f2 95 74 bd 8d 8c 5f ce fc 1c 70
<- ff 6c ba 60 70 8c ea 2f 26 e8
<- 55 ab 06 34 00 aa 9c 48 79 65 28 bd 72 d8 3d 14 c1 1b 88 10
<- 65 f9
<- 55 ab 20 35 00 3d 86 95 09 6e 80 50 4b 1a b7 f0 a5 67 5b 58
<- a4 7c d5 e4 bb cf 5c 8a ca e7 9a fb d3 3e ab 56 7a fc e4 d0
<- 97 e9 4f 20 84 9b 67 e8
<- 55 ab 0c 36 00 b5 cb 49 e6 e6 48 59 f0 55 b3 66 54 24 73 54
<- 7d 51 f3 9a 63 05 27 f5
<- 55 ab 22 37 00 67 a1 86 4e 99 61 44 af c2 72 69 4e f0 4f c1
<- f4 8f 0d 6f ce 9e 69 d4 fc b8 aa f5 ca bd 7d 9e 6d 8f da 10
<- bb a7 ff c9 3d 83 f2 5f 34 e6
<- 55 ab 06 38 00 0f 9c 0d cc 4e e0 f1 75 fa b4 48 07 85 bd 99
<- d1 f7
<- 55 ab 20 39 00 94 7d da 0b 84 0c 77 7f be 2f 5c 3d 6a f8 3d
<- 75 dc 37 94 06 16 91 3b dc 30 31 dc ef e5 51 71 a8 31 02 3d
<- 8d a3 09 52 99 de 38 ed
<- 55 ab 0c 3a 00 ee df 01 60 5e c2 d2 6f 9d a6 c7 d3 79 36 eb
<- 70 22 75 29 f4 bf d0 f3
<- 55 ab 22 3b 00 94 26 55 2e db b7 e7 3a 84 8e c0 62 aa 6f a8
<- 70 5f d3 b7 88 00 57 7c 88 15 a4 37 d8 2f 0e 52 22 03 fb e5
<- 24 0f 2d 96 bd 82 0e 10 d2 ec
<- 55 ab 06 3c 00 44 fe 5d 09 51 e4 f8 da 32 f3 9a 5d 30 16 c5
<- e7 f7
<- 55 ab 20 3d 00 22 17 05 9f c1 e0 09 d2 c9 fd 1c 7c 00 9f da
<- d9 0c 1a 46 5d 25 8e 7c 8e 0c 33 1e 62 91 fd 44 85 92 bd 00
<- 85 f4 4e 3e 19 26 6a ee
<- 55 ab 0c 3e 00 3b e2 a8 80 07 76 5e a9 69 82 8b 80 b5 bc 78
<- b4 62 f9 e2 c5 97 c0 f3
<- 55 ab 22 3f 00 cd 6d b8 83 c3 e1 25 8c 17 36 de 41 34 5a 41
<- d6 4e 4f 59 da 41 af 58 4f 18 38 aa 55 dd 48 44 88 34 93 e8
<- c6 8d 7f b6 ef 3c df 1c bf ea
<- 55 ab 06 40 00 df f9 50 63 f9 f4 bd e0 37 ee 04 7c cf 68 a6
<- 22 f6
<- 55 ab 20 41 00 77 47 78 6d 74 5b 4b 73 b6 0a e5 08 03 ef be
<- 29 02 e5 e8 d6 24 ec 66 9b 63 5e 4a 71 12 66 13 2d ed bb 87
<- 84 be dd 38 25 62 f1 ec
<- 55 ab 0c 42 00 71 93 80 54 7d e0 e3 ea 7c 92 35 bf 85 bb 92
<- 16 11 27 65 01 ff 28 f5
<- 55 ab 22 43 00 42 56 78 3d 2f 3a c9 66 af e5 8c 45 97 92 91
<- ae dc c4 17 55 c6 7f fc 37 e4 86 d2 9c 39 3f 54 81 16 a0 5f
<- 5e c7 35 7b 15 18 1f b9 50 eb
<- 55 ab 06 44 00 81 27 05 d4 e9 e9 77 0b ad 22 e3 03 02 fe 61
<- ca f8
<- 55 ab 20 45 00 5e f5 4a f9 b1 8e fa 15 f3 d0 d5 a9 72 05 ab
<- e9 13 32 d8 a1 00 0c a8 9f 06 29 c9 b0 1b dc bd 96 b9 32 a5
<- 9e 60 a8 6f 19 90 19 ea
<- 55 ab 0c 46 00 8e fa 75 80 55 5f 2e 10 a2 fc c2 a2 e3 0a c5
<- 8d de 1c 42 69 44 14 f5
<- 55 ab 22 47 00 65 42 86 97 f4 86 bc 79 0f 98 2c a2 4f 9a c0
<- 34 78 65 87 7a d1 8e 93 69 98 09 39 27 bb a8 18 4c 0e 19 1c
<- fe ba e8 1b 4b 70 5e 84 35 ec
<- 55 ab 06 48 00 4b 63 08 e1 06 33 a3 00 56 03 cf 67 4e 09 5a
<- fe fa
<- 55 ab 20 49 00 b7 fb 16 ec 10 1e 91 f7 33 4c df 46 7f 9b 30
<- a0 a9 cf 9a 3a 6a e0 ac ea 6d c6 d4 12 1e e6 7d 01 99 30 50
<- 11 bc a0 2c dc b1 98 ea
<- 55 ab 0c 4a 00 a8 58 3f 3e 46 35 1e e8 13 c5 c4 1d 50 99 8a
<- cd f0 3e 4e 3b 16 e5 f6
<- 55 ab 22 4b 00 04 d7 94 7a 01 82 d8 94 c4 f9 0c 52 37 ad 72
<- 67 0b d1 e2 75 4b 84 40 56 16 1e cc 10 bf 15 4d cd 91 a4 b1
<- af 17 7b 72 ac cd 11 e4 45 eb
<- 55 ab 06 4c 00 5d 01 da fb cd 78 03 64 f0 98 cd 35 e1 e3 a1
<- df f6
<- 55 ab 20 4d 00 b8 48 b3 37 51 3d 4a 14 53 02 9b e8 19 fe 49
<- 1d 5b 62 cc 60 d7 46 ab 36 fb ac c7 d2 0b 53 f2 a8 0b 96 d9
<- 25 87 05 f5 97 f0 a1 eb
<- 55 ab 0c 4e 00 dd 95 d3 07 8b dd a7 af 70 fc 20 b5 8f 01 8d
<- a0 2f 0b d2 77 b2 68 f4
<- 55 ab 22 4f 00 08 b7 fb 70 dc a6 b0 ca c6 af 95 59 7e 4b e4
<- fb 9b db c6 fa 7b 31 5f 3b 81 36 1d d0 4e 61 98 5e 2c bf 1d
<- 8b da 20 c6 ce c9 62 a7 a5 e7
<- 55 ab 06 50 00 d1 63 3f 96 48 70 f4 09 fe b7 16 99 09 e4 0d
<- 8d f8
[...]

but again, if the answer is "just don't do that", then I'm fine with that as well.

dnandha commented 2 years ago

@The-Compiler Yes, the answer is don't do that, but the program should tell you that, not me. Since BLE is sending an error code "e2 00 00 00" and I know that also "e0 00 00 00" exists, I'm now handling these and then raising an Exception.

dnandha commented 2 years ago

@The-Compiler If there's nothing to add from your side and the latest commit works I would merge this PR. Just give me a go.

The-Compiler commented 2 years ago

I was away today so I could only test now - as mentioned above I still see some weirdness, but it's definitely much more stable than before - so I'd say let's merge this, and I might get back to the remaining issues after I have a better understanding how the protocol works.

Thanks again for your patience and all the fixes, much appreciated! If there's a place I can donate a couple of Euros for beer/coffee/..., please let me know! If there's not, I suppose I could donate to Rollerplausch instead :slightly_smiling_face:

dnandha commented 2 years ago

@The-Compiler You're welcome Florian, as soon as you said you wanted to use this project get stuff running with your smart watch I was all into it. It's very refreshing to work with someone who knows how to run Python, communicate technical matters and use the GitHub arsenal. Good luck with your project(s) and hope to hear from you soon ;) I've added a donation button back into the Readme, feel free to chip in - thanks ❤️