diff --git a/.gitignore b/.gitignore index 894a44c..dbe3bf0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +*.bin +py9b.si4project/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/README.md b/README.md index 4200768..ceec741 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Ninebot/Xiaomi electric scooter communication library ## Requirements * Python 2.x.x [www.python.org] +* ProgressBar [pip install progressbar] * PySerial [pip install pyserial] - for direct serial link backend * PyGatt [pip install pygatt] - for BLED112 dongle backend * nRFUARTBridge [https://github.com/flowswitch/nRFUARTBridge] - for Android BLE-TCP backend diff --git a/fwupd.py b/fwupd.py index 64ea53d..9c363da 100644 --- a/fwupd.py +++ b/fwupd.py @@ -1,6 +1,10 @@ #!python2-32 +from __future__ import print_function from sys import argv, exit -from os.path import getsize +import os +import argparse +from progressbar import ProgressBar + from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.tcp import TCPLink from py9b.link.ble import BLELink @@ -10,65 +14,114 @@ from py9b.transport.xiaomi import XiaomiTransport from py9b.command.regio import ReadRegs, WriteRegs from py9b.command.update import * +PING_RETRIES = 20 + def checksum(s, data): for c in data: s += ord(c) return (s & 0xFFFFFFFF) -fw_dev = BT.BMS -fw_name = "bms.bin" -fw_size = getsize(fw_name) -fw_page_size = 0x80 +def UpdateFirmware(link, tran, dev, fwfile): + fwfile.seek(0, os.SEEK_END) + fw_size = fwfile.tell() + fwfile.seek(0) + fw_page_size = 0x80 -link = SerialLink(timeout=0.5) -#link = TCPLink() -#link = BLELink() - -with link: - print "Scanning..." - ports = link.scan() - print ports - - tran = XiaomiTransport(link) - - #link.open(("192.168.1.45", 6000)) - link.open(ports[0][1]) - print "Connected" - - print "Pinging..." - for retry in xrange(20): - print ".", + print('Pinging...', end='') + for retry in range(PING_RETRIES): + print('.', end='') try: - tran.execute(ReadRegs(BT.BMS, 0x10, "14s")) + if dev==BT.BLE: + tran.execute(ReadRegs(dev, 0, '13s')) + else: + tran.execute(ReadRegs(dev, 0x10, '14s')) except LinkTimeoutException: continue break else: - exit("Timed out !") - print "" + print('Timed out !') + return False + print('OK') + + print('Locking...') + tran.execute(WriteRegs(BT.ESC, 0x70, '", hexlify(data).upper() - self._dev.char_write_handle(self._wr_handle, bytearray(data)) + size = len(data) + ofs = 0 + while size: + chunk_sz = min(size, _write_chunk_size) + self._dev.char_write_handle(self._wr_handle, bytearray(data[ofs:ofs+chunk_sz])) + ofs += chunk_sz + size -= chunk_sz __all__ = ["BLELink"] diff --git a/py9b/link/tcp.py b/py9b/link/tcp.py index b9702ea..c38a588 100644 --- a/py9b/link/tcp.py +++ b/py9b/link/tcp.py @@ -6,6 +6,7 @@ from .base import BaseLink, LinkTimeoutException, LinkOpenException HOST, PORT = "127.0.0.1", 6000 +_write_chunk_size = 20 # as in android dumps def recvall(sock, size): data = "" @@ -63,7 +64,13 @@ class TCPLink(BaseLink): def write(self, data): if self.dump: print ">", hexlify(data).upper() - self.sock.sendall(data) + size = len(data) + ofs = 0 + while size: + chunk_sz = min(size, _write_chunk_size) + self.sock.sendall(data[ofs:ofs+chunk_sz]) + ofs += chunk_sz + size -= chunk_sz __all__ = ["TCPLink"] diff --git a/py9b/transport/base.py b/py9b/transport/base.py index 42d24b7..12e6fc3 100644 --- a/py9b/transport/base.py +++ b/py9b/transport/base.py @@ -7,12 +7,15 @@ def checksum(data): return (s & 0xFFFF) ^ 0xFFFF + class BaseTransport(object): - DEV01 = 1 + MOTOR = 0x01 ESC = 0x20 BLE = 0x21 BMS = 0x22 HOST = 0x3E + + DeviceNames = { MOTOR : "MOTOR", ESC : "ESC", BLE : "BLE", BMS : "BMS", HOST : "HOST" } def __init__(self, link): self.link = link @@ -31,5 +34,9 @@ class BaseTransport(object): rsp = self.recv() return command.handle_response(rsp) + @staticmethod + def GetDeviceName(dev): + return BaseTransport.DeviceNames.get(dev, "%02X" % (dev)) + __all__ = ["checksum", "BaseTransport"] diff --git a/py9b/transport/packet.py b/py9b/transport/packet.py index 8022ac0..11bd833 100644 --- a/py9b/transport/packet.py +++ b/py9b/transport/packet.py @@ -1,4 +1,5 @@ from binascii import hexlify +from .base import BaseTransport as BT class BasePacket(object): def __init__(self, src=0, dst=0, cmd=0, arg=0, data=""): @@ -9,7 +10,7 @@ class BasePacket(object): self.data = data def __str__(self): - return "%02X->%02X: %02X @%02X %s" % (self.src, self.dst, self.cmd, self.arg, hexlify(self.data).upper()) + return "%s->%s: %02X @%02X %s" % (BT.GetDeviceName(self.src), BT.GetDeviceName(self.dst), self.cmd, self.arg, hexlify(self.data).upper()) __all__ = ["BasePacket"] diff --git a/py9b/transport/xiaomi.py b/py9b/transport/xiaomi.py index c9f9e68..3807867 100644 --- a/py9b/transport/xiaomi.py +++ b/py9b/transport/xiaomi.py @@ -14,26 +14,30 @@ class XiaomiTransport(BT): MASTER2BMS = 0x22 BMS2MASTER = 0x25 - DEV01 = 0x01 + MOTOR = 0x01 DEVFF = 0xFF - _SaDa2Addr = { BT.HOST : { BT.DEV01 : DEV01, BT.ESC : MASTER2ESC, BT.BLE : MASTER2BLE, BT.BMS : MASTER2BMS }, - BT.ESC : { BT.HOST : ESC2MASTER, BT.BLE : MASTER2BLE, BT.BMS : MASTER2BMS, BT.DEV01 : DEV01 }, - BT.BMS : { BT.HOST : BMS2MASTER, BT.ESC : BMS2MASTER, BT.DEV01 : DEV01 }, - BT.DEV01 : {BT.HOST : DEV01, BT.ESC : DEV01, BT.BMS : DEV01 } } + _SaDa2Addr = { BT.HOST : { BT.MOTOR : MOTOR, BT.ESC : MASTER2ESC, BT.BLE : MASTER2BLE, BT.BMS : MASTER2BMS }, + BT.ESC : { BT.HOST : ESC2MASTER, BT.BLE : MASTER2BLE, BT.BMS : MASTER2BMS, BT.MOTOR : MOTOR }, + BT.BMS : { BT.HOST : BMS2MASTER, BT.ESC : BMS2MASTER, BT.MOTOR : MOTOR }, + BT.MOTOR : {BT.HOST : MOTOR, BT.ESC : MOTOR, BT.BMS : MOTOR } } # TBC _BleAddr2SaDa = { MASTER2ESC : (BT.HOST, BT.ESC), ESC2MASTER : (BT.ESC, BT.HOST), MASTER2BMS : (BT.HOST, BT.BMS), BMS2MASTER : (BT.BMS, BT.HOST), - DEV01 : (BT.DEV01, BT.HOST) } + MASTER2BLE : (BT.HOST, BT.BLE), + BLE2MASTER : (BT.BLE, BT.HOST), + MOTOR : (BT.MOTOR, BT.HOST) } _BmsAddr2SaDa = { MASTER2ESC : (BT.BMS, BT.ESC), ESC2MASTER : (BT.ESC, BT.BMS), MASTER2BMS : (BT.ESC, BT.BMS), BMS2MASTER : (BT.BMS, BT.ESC), - DEV01 : (BT.DEV01, BT.BMS) } + MASTER2BLE : (BT.BMS, BT.BLE), + BLE2MASTER : (BT.BLE, BT.BMS), + MOTOR : (BT.MOTOR, BT.BMS) } def __init__(self, link, device=BT.HOST): @@ -78,7 +82,7 @@ class XiaomiTransport(BT): print "Checksum mismatch !" return None sa, da = self._split_addr(ord(pkt[1])) - return BasePacket(sa, da, ord(pkt[2]), ord(pkt[3]), pkt[4:-2]) # sa, da, cmd, param, data + return BasePacket(sa, da, ord(pkt[2]), ord(pkt[3]), pkt[4:-2]) # sa, da, cmd, arg, data def send(self, packet): diff --git a/read_bms.py b/read_bms.py index 2e3bb12..06ce38c 100644 --- a/read_bms.py +++ b/read_bms.py @@ -10,9 +10,9 @@ from py9b.command.regio import ReadRegs READ_CHUNK_SIZE = 0x10 -link = SerialLink(dump=True) +#link = SerialLink(dump=True) #link = TCPLink() -#link = BLELink() +link = BLELink() with link: print "Scanning..." diff --git a/read_esc.py b/read_esc.py index 5000004..cd24172 100644 --- a/read_esc.py +++ b/read_esc.py @@ -2,13 +2,15 @@ from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.tcp import TCPLink from py9b.link.ble import BLELink +from py9b.link.serial import SerialLink from py9b.transport.base import BaseTransport as BT from py9b.transport.packet import BasePacket as PKT from py9b.transport.xiaomi import XiaomiTransport +from py9b.command.regio import ReadRegs -READ_CHUNK_SIZE = 0x40 +READ_CHUNK_SIZE = 0x10 -#link = SerialLink() +#link = SerialLink(dump=True) #link = TCPLink() link = BLELink() @@ -23,20 +25,19 @@ with link: link.open(ports[0][1]) print "Connected" - req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x01, arg=0, data=chr(READ_CHUNK_SIZE)) - hfo = open("EscRegs.bin", "wb") - for i in xrange(0, 0x200, READ_CHUNK_SIZE): + for i in xrange(0x0, 0x100, READ_CHUNK_SIZE): print ".", - req.arg = i>>1 - for retry in xrange(3): - tran.send(req) + for retry in xrange(5): try: - rsp = tran.recv() + data = tran.execute(ReadRegs(BT.ESC, i>>1, "16s"))[0] except LinkTimeoutException: continue break - hfo.write(rsp.data) + else: + print "No response !" + break + hfo.write(data) hfo.close() link.close() diff --git a/read_esc_ll.py b/read_esc_ll.py new file mode 100644 index 0000000..b28a58e --- /dev/null +++ b/read_esc_ll.py @@ -0,0 +1,43 @@ +#!python2-32 +from py9b.link.base import LinkOpenException, LinkTimeoutException +from py9b.link.tcp import TCPLink +from py9b.link.ble import BLELink +from py9b.link.serial import SerialLink +from py9b.transport.base import BaseTransport as BT +from py9b.transport.packet import BasePacket as PKT +from py9b.transport.xiaomi import XiaomiTransport + +READ_CHUNK_SIZE = 0x40 + +#link = SerialLink() +#link = TCPLink() +link = BLELink() + +with link: + print "Scanning..." + ports = link.scan() + print ports + + tran = XiaomiTransport(link) + + #link.open(("192.168.1.45", 6000)) + link.open(ports[0][1]) + print "Connected" + + req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x01, arg=0, data=chr(READ_CHUNK_SIZE)) + + hfo = open("EscRegs.bin", "wb") + for i in xrange(0, 0x200, READ_CHUNK_SIZE): + print ".", + req.arg = i>>1 + for retry in xrange(3): + tran.send(req) + try: + rsp = tran.recv() + except LinkTimeoutException: + continue + break + hfo.write(rsp.data) + + hfo.close() + link.close() diff --git a/sniffer.py b/sniffer.py new file mode 100644 index 0000000..942f174 --- /dev/null +++ b/sniffer.py @@ -0,0 +1,64 @@ +#!python2-32 +from struct import unpack +from py9b.link.base import LinkOpenException, LinkTimeoutException +#from py9b.link.tcp import TCPLink +#from py9b.link.ble import BLELink +from py9b.link.serial import SerialLink +from py9b.transport.base import BaseTransport as BT +from py9b.transport.packet import BasePacket as PKT +from py9b.transport.xiaomi import XiaomiTransport + +link = SerialLink() +#link = TCPLink() +#link = BLELink() + +with link: + print "Scanning..." + ports = link.scan() + print ports + + tran = XiaomiTransport(link) + + #link.open(("192.168.1.45", 6000)) + link.open(ports[0][1]) + print "Connected" + + last_esc_64 = "" + last_esc_65 = "" + last_ble_64 = "" + try: + while True: + try: + rsp = tran.recv() + if not rsp: + continue + if rsp.src==BT.HOST and rsp.dst==BT.ESC and rsp.cmd in (0x64, 0x65): + if len(rsp.data)==5: + if rsp.data==last_esc_65: + continue + ll, throttle, brake, u2, u3 = unpack("ESC: TH: %02X, BR: %02X, %02X %02X" % (throttle, brake, u2, u3) + last_esc_65 = rsp.data + continue + elif len(rsp.data)==7: + if rsp.data==last_esc_64: + continue + ll, throttle, brake, u2, u3, ver = unpack("ESC: TH: %02X, BR: %02X, %02X %02X, VER: %04X" % (throttle, brake, u2, u3, ver) + last_esc_64 = rsp.data + continue + elif rsp.src==BT.HOST and rsp.dst==BT.BLE and rsp.cmd==0x64: + if len(rsp.data)==4: + if rsp.data==last_ble_64: + continue + u0, u1, u2, u3 = unpack("BLE: %02X %02X %02X %02X" % (u0, u1, u2, u3) + last_ble_64 = rsp.data + continue + print rsp + except LinkTimeoutException: + pass + except KeyboardInterrupt: + pass + + link.close() diff --git a/wr_esc.py b/wr_esc.py index 6348504..aa0a02e 100644 --- a/wr_esc.py +++ b/wr_esc.py @@ -1,3 +1,4 @@ +#!python2-32 from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.tcp import TCPLink from py9b.link.ble import BLELink @@ -6,8 +7,8 @@ from py9b.transport.packet import BasePacket as PKT from py9b.transport.xiaomi import XiaomiTransport #link = SerialLink() -#link = TCPLink() -link = BLELink() +link = TCPLink() +#link = BLELink() with link: print "Scanning..." @@ -16,8 +17,8 @@ with link: tran = XiaomiTransport(link) - #link.open(("192.168.1.45", 6000)) - link.open(ports[0][1]) + link.open(("192.168.1.45", 6000)) + #link.open(ports[0][1]) print "Connected" req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x02, arg=0x41, data="\xCE\xAB\x00\x00") diff --git a/wr_esc_sn.py b/wr_esc_sn.py new file mode 100644 index 0000000..fc7c74e --- /dev/null +++ b/wr_esc_sn.py @@ -0,0 +1,82 @@ +#!python2-32 +from __future__ import print_function +from sys import exit +from py9b.link.base import LinkOpenException, LinkTimeoutException +from py9b.link.tcp import TCPLink +from py9b.link.ble import BLELink +from py9b.link.serial import SerialLink +from py9b.transport.base import BaseTransport as BT +from py9b.transport.packet import BasePacket as PKT +from py9b.transport.xiaomi import XiaomiTransport +from py9b.command.regio import ReadRegs, WriteRegs +from py9b.command.mfg import WriteSN + + +new_sn = "16133/00101234" + + +def CalcSnAuth(oldsn, newsn, uid3): + s = 0 + for i in xrange(0x0E): + s += ord(oldsn[i]) + s *= ord(newsn[i]) + s += uid3+(uid3<<4) + s &= 0xFFFFFFFF + if (s & 0x80000000)!=0: + s = 0x100000000-s + + return s % 1000000 + + +#link = SerialLink(dump=True) +#link = TCPLink() +link = BLELink(dump=True) + +with link: + print("Scanning...") + ports = link.scan() + print(ports) + + tran = XiaomiTransport(link) + + #link.open(("192.168.1.45", 6000)) + link.open(ports[0][1]) + print("Connected") + + print("Pinging...") + for retry in xrange(20): + print(".", end="") + try: + old_sn = tran.execute(ReadRegs(BT.ESC, 0x10, "14s"))[0] + except LinkTimeoutException: + continue + break + else: + exit("Timed out !") + print("") + + + #lock + tran.execute(WriteRegs(BT.ESC, 0x70, "