code style cleanup

legit-fork
informatic 2019-10-20 08:49:22 +02:00
parent 1b8297b809
commit af23f5fe6f
40 changed files with 1214 additions and 1785 deletions

234
fwupd.py
View File

@ -14,137 +14,161 @@ from py9b.command.update import *
PING_RETRIES = 20 PING_RETRIES = 20
def checksum(s, data): def checksum(s, data):
for c in data: for c in data:
s += ord(c) s += ord(c)
return (s & 0xFFFFFFFF) return s & 0xFFFFFFFF
def UpdateFirmware(link, tran, dev, fwfile): def UpdateFirmware(link, tran, dev, fwfile):
fwfile.seek(0, os.SEEK_END) fwfile.seek(0, os.SEEK_END)
fw_size = fwfile.tell() fw_size = fwfile.tell()
fwfile.seek(0) fwfile.seek(0)
fw_page_size = 0x80 fw_page_size = 0x80
print('Pinging...', end='') print("Pinging...", end="")
for retry in range(PING_RETRIES): for retry in range(PING_RETRIES):
print('.', end='') print(".", end="")
try: try:
if dev==BT.BLE: if dev == BT.BLE:
tran.execute(ReadRegs(dev, 0, '13s')) tran.execute(ReadRegs(dev, 0, "13s"))
else: else:
tran.execute(ReadRegs(dev, 0x10, '14s')) tran.execute(ReadRegs(dev, 0x10, "14s"))
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
else: else:
print('Timed out !') print("Timed out !")
return False return False
print('OK') print("OK")
if args.interface!='blefleet': if args.interface != "blefleet":
print('Locking...') print("Locking...")
tran.execute(WriteRegs(BT.ESC, 0x70, '<H', 0x0001)) tran.execute(WriteRegs(BT.ESC, 0x70, "<H", 0x0001))
else: else:
print('Not Locking...') print("Not Locking...")
print('Starting...') print("Starting...")
tran.execute(StartUpdate(dev, fw_size)) tran.execute(StartUpdate(dev, fw_size))
print('Writing...') print("Writing...")
pb = ProgressBar(maxval=fw_size//fw_page_size+1).start() pb = ProgressBar(maxval=fw_size // fw_page_size + 1).start()
page = 0 page = 0
chk = 0 chk = 0
while fw_size: while fw_size:
pb.update(page) pb.update(page)
chunk_sz = min(fw_size, fw_page_size) chunk_sz = min(fw_size, fw_page_size)
data = fwfile.read(chunk_sz) data = fwfile.read(chunk_sz)
chk = checksum(chk, data) chk = checksum(chk, data)
#tran.execute(WriteUpdate(dev, page, data)) # tran.execute(WriteUpdate(dev, page, data))
tran.execute(WriteUpdate(dev, page, data+b'\x00'*(fw_page_size-chunk_sz))) # TODO: Ninebot wants this padding. Will it work on M365 too? tran.execute(
page += 1 WriteUpdate(dev, page, data + b"\x00" * (fw_page_size - chunk_sz))
fw_size -= chunk_sz ) # TODO: Ninebot wants this padding. Will it work on M365 too?
pb.finish() page += 1
fw_size -= chunk_sz
pb.finish()
print('Finalizing...') print("Finalizing...")
tran.execute(FinishUpdate(dev, chk ^ 0xFFFFFFFF)) tran.execute(FinishUpdate(dev, chk ^ 0xFFFFFFFF))
print("Reboot")
tran.execute(RebootUpdate(dev))
print("Done")
return True
print('Reboot')
tran.execute(RebootUpdate(dev))
print('Done')
return True
########################################################################################## ##########################################################################################
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, parser = argparse.ArgumentParser(
description='Xiaomi/Ninebot firmware flasher', formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Example 1: %(prog)s ble ble_patched.bin - flash ble_patched.bin to BLE using default communication parameters' description="Xiaomi/Ninebot firmware flasher",
'\nExample 2: %(prog)s -i tcp -a 127.0.1.10:6000 bms bms115.bin - flash bms115.bin to BMS over TCP-BLE bridge at 127.0.1.10:6000' epilog="Example 1: %(prog)s ble ble_patched.bin - flash ble_patched.bin to BLE using default communication parameters"
'\nExample 3: %(prog)s -i serial -a COM2 esc CFW.bin - flash CFW.bin to ESC via COM2' "\nExample 2: %(prog)s -i tcp -a 127.0.1.10:6000 bms bms115.bin - flash bms115.bin to BMS over TCP-BLE bridge at 127.0.1.10:6000"
'\nExample 4: %(prog)s -i ble -a 12:34:56:78:9A:BC -p ninebot extbms bms107.bin - flash bms107.bin to Ninebot\'s external BMS via BLE, use specified BLE address') "\nExample 3: %(prog)s -i serial -a COM2 esc CFW.bin - flash CFW.bin to ESC via COM2"
"\nExample 4: %(prog)s -i ble -a 12:34:56:78:9A:BC -p ninebot extbms bms107.bin - flash bms107.bin to Ninebot's external BMS via BLE, use specified BLE address",
)
devices = {'ble' : BT.BLE, 'esc' : BT.ESC, 'bms' : BT.BMS, 'extbms' : BT.EXTBMS } devices = {"ble": BT.BLE, "esc": BT.ESC, "bms": BT.BMS, "extbms": BT.EXTBMS}
parser.add_argument('device', help='target device', type=str.lower, choices=devices) parser.add_argument("device", help="target device", type=str.lower, choices=devices)
parser.add_argument('file', type=argparse.FileType('rb'), help='firmware file') parser.add_argument("file", type=argparse.FileType("rb"), help="firmware file")
parser.add_argument('-i', '--interface', help='communication interface, default: %(default)s', type=str.lower, parser.add_argument(
choices=('ble', 'serial', 'tcp', 'blefleet'), default='ble') "-i",
"--interface",
help="communication interface, default: %(default)s",
type=str.lower,
choices=("ble", "serial", "tcp", "blefleet"),
default="ble",
)
parser.add_argument('-a', '--address', help='communication address (ble: BDADDR, serial: port, tcp: host:port), default: first available') parser.add_argument(
"-a",
"--address",
help="communication address (ble: BDADDR, serial: port, tcp: host:port), default: first available",
)
protocols = {'xiaomi' : XiaomiTransport, 'ninebot' : NinebotTransport } protocols = {"xiaomi": XiaomiTransport, "ninebot": NinebotTransport}
parser.add_argument('-p', '--protocol', help='communication protocol, default: %(default)s', type=str.lower, parser.add_argument(
choices=protocols, default='xiaomi') "-p",
"--protocol",
help="communication protocol, default: %(default)s",
type=str.lower,
choices=protocols,
default="xiaomi",
)
if len(argv)==1: if len(argv) == 1:
parser.print_usage() parser.print_usage()
exit() exit()
args = parser.parse_args() args = parser.parse_args()
if args.device=='extbms' and args.protocol!='ninebot': if args.device == "extbms" and args.protocol != "ninebot":
exit('Only Ninebot supports External BMS !') exit("Only Ninebot supports External BMS !")
dev = devices.get(args.device) dev = devices.get(args.device)
if args.interface=='ble': if args.interface == "ble":
try: try:
from py9b.link.ble import BLELink from py9b.link.ble import BLELink
except: except:
exit('BLE is not supported on your system !') exit("BLE is not supported on your system !")
link = BLELink() link = BLELink()
elif args.interface=='tcp': elif args.interface == "tcp":
from py9b.link.tcp import TCPLink from py9b.link.tcp import TCPLink
link = TCPLink()
elif args.interface=='serial': link = TCPLink()
from py9b.link.serial import SerialLink elif args.interface == "serial":
link = SerialLink() from py9b.link.serial import SerialLink
elif args.interface=='blefleet':
try: link = SerialLink()
from py9b.link.blefleet import BLELink elif args.interface == "blefleet":
except: try:
exit('BLE is not supported on your system !') from py9b.link.blefleet import BLELink
link = BLELink() except:
exit("BLE is not supported on your system !")
link = BLELink()
else: else:
exit('!!! BUG !!! Unknown interface selected: '+args.interface) exit("!!! BUG !!! Unknown interface selected: " + args.interface)
with link: with link:
tran = protocols.get(args.protocol)(link) tran = protocols.get(args.protocol)(link)
if args.address: if args.address:
addr = args.address addr = args.address
else: else:
print('Scanning...') print("Scanning...")
ports = link.scan() ports = link.scan()
if not ports: if not ports:
exit("No interfaces found !") exit("No interfaces found !")
print('Connecting to', ports[0][0]) print("Connecting to", ports[0][0])
addr = ports[0][1] addr = ports[0][1]
link.open(addr) link.open(addr)
print('Connected') print("Connected")
try: try:
UpdateFirmware(link, tran, dev, args.file) UpdateFirmware(link, tran, dev, args.file)
except Exception as e: except Exception as e:
print('Error:', e) print("Error:", e)
raise raise

32
lock.py
View File

@ -1,31 +1,33 @@
#!python2-32 #!python2-32
from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.base import LinkOpenException, LinkTimeoutException
from py9b.link.tcp import TCPLink from py9b.link.tcp import TCPLink
#from py9b.link.ble import BLELink
# from py9b.link.ble import BLELink
from py9b.link.serial import SerialLink from py9b.link.serial import SerialLink
from py9b.transport.base import BaseTransport as BT from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
#from py9b.transport.xiaomi import XiaomiTransport
# from py9b.transport.xiaomi import XiaomiTransport
from py9b.transport.ninebot import NinebotTransport from py9b.transport.ninebot import NinebotTransport
from py9b.command.regio import ReadRegs, WriteRegs from py9b.command.regio import ReadRegs, WriteRegs
#link = SerialLink(dump=True) # link = SerialLink(dump=True)
link = TCPLink() link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
link.open(("127.0.0.20:6000")) link.open(("127.0.0.20:6000"))
# link.open(ports[0][1]) # link.open(ports[0][1])
print "Connected" print("Connected")
print('Locking...') print("Locking...")
tran.execute(WriteRegs(BT.ESC, 0x70, '<H', 0x0001)) tran.execute(WriteRegs(BT.ESC, 0x70, "<H", 0x0001))
link.close() link.close()

View File

@ -18,27 +18,28 @@ with link:
link.open(devs[0]) link.open(devs[0])
data = tran.execute(ReadRegs(BT.ESC, 0x68, "<H"))[0] data = tran.execute(ReadRegs(BT.ESC, 0x68, "<H"))[0]
print('BLE version: %04x' % data) print("BLE version: %04x" % data)
if data >= 0x81: if data >= 0x81:
print('Connected, fetching keys...') print("Connected, fetching keys...")
keys = link.fetch_keys() keys = link.fetch_keys()
tran.keys = keys tran.keys = keys
print('keys:', keys) print("keys:", keys)
# Recover longer keystream # Recover longer keystream
req = PKT(src=BT.HOST, dst=BT.BMS, cmd=0x01, arg=0x50, data=bytearray([0x20])) req = PKT(src=BT.HOST, dst=BT.BMS, cmd=0x01, arg=0x50, data=bytearray([0x20]))
tran.send(req) tran.send(req)
resp = tran.recv() resp = tran.recv()
tran.keys += resp.data[9:] tran.keys += resp.data[9:]
print('Got %d bytes of keystream' % (len(tran.keys),)) print("Got %d bytes of keystream" % (len(tran.keys),))
data = tran.execute(ReadRegs(BT.ESC, 0x68, "<H")) data = tran.execute(ReadRegs(BT.ESC, 0x68, "<H"))
print('Version reported after encryption: %04x' % data) print("Version reported after encryption: %04x" % data)
data = tran.execute(ReadRegs(BT.ESC, 0x1A, "<H"))[0] data = tran.execute(ReadRegs(BT.ESC, 0x1A, "<H"))[0]
print('ESC version: %04x' % data) print("ESC version: %04x" % data)
data = tran.execute(ReadRegs(BT.BMS, 0x17, "<H"))[0] data = tran.execute(ReadRegs(BT.BMS, 0x17, "<H"))[0]
print('BMS version: %04x' % data) print("BMS version: %04x" % data)
print('Serial:', tran.execute(ReadRegs(BT.ESC, 0x10, "12s"))[0].decode()) print("ESC Serial:", tran.execute(ReadRegs(BT.ESC, 0x10, "12s"))[0].decode())
print("BMS Serial:", tran.execute(ReadRegs(BT.BMS, 0x10, "12s"))[0].decode())

View File

@ -1,32 +1,34 @@
#!python2-32 #!python2-32
from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.base import LinkOpenException, LinkTimeoutException
from py9b.link.tcp import TCPLink from py9b.link.tcp import TCPLink
#from py9b.link.ble import BLELink
# from py9b.link.ble import BLELink
from py9b.link.serial import SerialLink from py9b.link.serial import SerialLink
from py9b.transport.base import BaseTransport as BT from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
#from py9b.transport.xiaomi import XiaomiTransport
# from py9b.transport.xiaomi import XiaomiTransport
from py9b.transport.ninebot import NinebotTransport from py9b.transport.ninebot import NinebotTransport
from py9b.command.regio import ReadRegs, WriteRegs from py9b.command.regio import ReadRegs, WriteRegs
#link = SerialLink(dump=True) # link = SerialLink(dump=True)
link = TCPLink() link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
link.open(("127.0.0.20:6000")) link.open(("127.0.0.20:6000"))
# link.open(ports[0][1]) # link.open(ports[0][1])
print "Connected" print("Connected")
print 'Reading passwd...' print("Reading passwd...")
pwd = tran.execute(ReadRegs(BT.ESC, 0x17, "6s")) pwd = tran.execute(ReadRegs(BT.ESC, 0x17, "6s"))
print "Passwd:", pwd print("Passwd:", pwd)
link.close() link.close()

View File

@ -1,31 +1,33 @@
#!python2-32 #!python2-32
from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.base import LinkOpenException, LinkTimeoutException
from py9b.link.tcp import TCPLink from py9b.link.tcp import TCPLink
#from py9b.link.ble import BLELink
# from py9b.link.ble import BLELink
from py9b.link.serial import SerialLink from py9b.link.serial import SerialLink
from py9b.transport.base import BaseTransport as BT from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
#from py9b.transport.xiaomi import XiaomiTransport
# from py9b.transport.xiaomi import XiaomiTransport
from py9b.transport.ninebot import NinebotTransport from py9b.transport.ninebot import NinebotTransport
from py9b.command.regio import ReadRegs, WriteRegs from py9b.command.regio import ReadRegs, WriteRegs
#link = SerialLink(dump=True) # link = SerialLink(dump=True)
link = TCPLink() link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
link.open(("127.0.0.20:6000")) link.open(("127.0.0.20:6000"))
# link.open(ports[0][1]) # link.open(ports[0][1])
print "Connected" print("Connected")
print('Power off...') print("Power off...")
tran.execute(WriteRegs(BT.ESC, 0x79, '<H', 0x0001)) tran.execute(WriteRegs(BT.ESC, 0x79, "<H", 0x0001))
link.close() link.close()

View File

@ -3,17 +3,16 @@ from ..transport.base import BaseTransport as BT
class InvalidResponse(Exception): class InvalidResponse(Exception):
pass pass
class BaseCommand(object): class BaseCommand(object):
def __init__(self, src=BT.HOST, dst=0, cmd=0, arg=0, data="", has_response=True): def __init__(self, src=BT.HOST, dst=0, cmd=0, arg=0, data="", has_response=True):
self.has_response = has_response self.has_response = has_response
self.request = PKT(src, dst, cmd, arg, data) self.request = PKT(src, dst, cmd, arg, data)
def handle_response(self, response):
def handle_response(self, response): return True
return True
__all__ = ["BaseCommand", "InvalidResponse"] __all__ = ["BaseCommand", "InvalidResponse"]

View File

@ -3,15 +3,21 @@ from .base import BaseCommand, InvalidResponse
class ReadMem(BaseCommand): class ReadMem(BaseCommand):
def __init__(self, dev, addr, format): def __init__(self, dev, addr, format):
super(ReadMem, self).__init__(dst=dev, cmd=0x80, arg=calcsize(format), data=pack("<H", addr), has_response=True) super(ReadMem, self).__init__(
self.dev = dev dst=dev,
self.format = format cmd=0x80,
arg=calcsize(format),
data=pack("<H", addr),
has_response=True,
)
self.dev = dev
self.format = format
def handle_response(self, response): def handle_response(self, response):
if len(response.data)!=calcsize(self.format): if len(response.data) != calcsize(self.format):
raise InvalidResponse("ReadMem {0:X}".format(self.dev)) raise InvalidResponse("ReadMem {0:X}".format(self.dev))
return unpack(self.format, response.data) return unpack(self.format, response.data)
__all__=["ReadMem"] __all__ = ["ReadMem"]

View File

@ -5,20 +5,22 @@ from .base import BaseCommand, InvalidResponse
class AuthError(Exception): class AuthError(Exception):
pass pass
class WriteSN(BaseCommand): class WriteSN(BaseCommand):
def __init__(self, dev, sn, auth): def __init__(self, dev, sn, auth):
super(WriteSN, self).__init__(dst=dev, cmd=0x18, arg=0x10, data=pack("<14sL", sn, auth), has_response=True) super(WriteSN, self).__init__(
self.dev = dev dst=dev, cmd=0x18, arg=0x10, data=pack("<14sL", sn, auth), has_response=True
)
self.dev = dev
def handle_response(self, response): def handle_response(self, response):
if len(response.data)!=0: if len(response.data) != 0:
raise InvalidResponse("WriteSN {0:X}".format(self.dev)) raise InvalidResponse("WriteSN {0:X}".format(self.dev))
if response.arg!=1: if response.arg != 1:
raise AuthError("WriteSN {0:X}".format(self.dev)) raise AuthError("WriteSN {0:X}".format(self.dev))
return True return True
__all__=["AuthError", "WriteSN"] __all__ = ["AuthError", "WriteSN"]

View File

@ -5,42 +5,62 @@ from .base import BaseCommand, InvalidResponse
class ReadRegs(BaseCommand): class ReadRegs(BaseCommand):
def __init__(self, dev, reg, format): def __init__(self, dev, reg, format):
super(ReadRegs, self).__init__(dst=dev, cmd=0x01, arg=reg, data=pack("<B", calcsize(format)), has_response=True) super(ReadRegs, self).__init__(
self.dev = dev dst=dev,
self.reg = reg cmd=0x01,
self.format = format arg=reg,
data=pack("<B", calcsize(format)),
has_response=True,
)
self.dev = dev
self.reg = reg
self.format = format
def handle_response(self, response): def handle_response(self, response):
if response.arg!=self.reg or len(response.data)!=calcsize(self.format): if response.arg != self.reg or len(response.data) != calcsize(self.format):
raise InvalidResponse("ReadRegs {0:X}:{1:X}: @{2:X} [{3:X}]".format(self.dev, self.reg, response.arg, len(response.data))) raise InvalidResponse(
return unpack(self.format, response.data) "ReadRegs {0:X}:{1:X}: @{2:X} [{3:X}]".format(
self.dev, self.reg, response.arg, len(response.data)
)
)
return unpack(self.format, response.data)
class WriteProtectError(Exception): class WriteProtectError(Exception):
pass pass
class WriteRegs(BaseCommand): class WriteRegs(BaseCommand):
def __init__(self, dev, reg, format, *args): def __init__(self, dev, reg, format, *args):
super(WriteRegs, self).__init__(dst=dev, cmd=0x02, arg=reg, data=pack(format, *args), has_response=True) super(WriteRegs, self).__init__(
self.dev = dev dst=dev, cmd=0x02, arg=reg, data=pack(format, *args), has_response=True
self.reg = reg )
self.dev = dev
self.reg = reg
def handle_response(self, response): def handle_response(self, response):
if response.cmd==0x02: # xiaomi style if response.cmd == 0x02: # xiaomi style
if response.arg!=self.reg or len(response.data)!=1: if response.arg != self.reg or len(response.data) != 1:
raise InvalidResponse("WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)) raise InvalidResponse(
if unpack("<B", response.data)[0]!=1: "WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)
raise WriteProtectError("WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)) )
elif response.cmd==0x05: # ninebot style if unpack("<B", response.data)[0] != 1:
if len(response.data)!=0: raise WriteProtectError(
raise InvalidResponse("WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)) "WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)
if response.arg!=0: )
raise WriteProtectError("WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)) elif response.cmd == 0x05: # ninebot style
else: if len(response.data) != 0:
raise InvalidResponse("WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)) raise InvalidResponse(
return True "WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)
)
if response.arg != 0:
raise WriteProtectError(
"WriteRegs {0:X}:{1:X}".format(self.dev, self.reg)
)
else:
raise InvalidResponse("WriteRegs {0:X}:{1:X}".format(self.dev, self.reg))
return True
__all__=["ReadRegs", "WriteRegs", "WriteProtectError"] __all__ = ["ReadRegs", "WriteRegs", "WriteProtectError"]

View File

@ -5,68 +5,98 @@ from .base import BaseCommand, InvalidResponse
# error codes: # error codes:
# 1 - invalid parameter # 1 - invalid parameter
# 2 - erase error # 2 - erase error
# 3 - flash error # 3 - flash error
# 4 - not locked # 4 - not locked
# 5 - address error # 5 - address error
# 6 - command in progress # 6 - command in progress
# 7 - invalid cmd/len # 7 - invalid cmd/len
UpdateErrorCodes = { 0: 'OK', 1: 'Out of bounds', 2: 'Erase error', 3: 'Write error', UpdateErrorCodes = {
4: 'Not locked', 5: 'Invalid address', 6: 'Command in progress', 7: 'Invalid payload len'} 0: "OK",
1: "Out of bounds",
2: "Erase error",
3: "Write error",
4: "Not locked",
5: "Invalid address",
6: "Command in progress",
7: "Invalid payload len",
}
class UpdateError(Exception): class UpdateError(Exception):
pass pass
class StartUpdate(BaseCommand): class StartUpdate(BaseCommand):
def __init__(self, dev, size): def __init__(self, dev, size):
super(StartUpdate, self).__init__(dst=dev, cmd=0x07, data=pack("<L", size), has_response=True) super(StartUpdate, self).__init__(
self.dev = dev dst=dev, cmd=0x07, data=pack("<L", size), has_response=True
)
self.dev = dev
def handle_response(self, response): def handle_response(self, response):
if not len(response.data) in (0, 1): if not len(response.data) in (0, 1):
raise InvalidResponse("StartUpdate {0:X}".format(self.dev)) raise InvalidResponse("StartUpdate {0:X}".format(self.dev))
if response.arg!=0: if response.arg != 0:
raise UpdateError("StartUpdate {0:X}: {1:s}".format(self.dev, UpdateErrorCodes.get(response.arg, str(response.arg)))) raise UpdateError(
return True "StartUpdate {0:X}: {1:s}".format(
self.dev, UpdateErrorCodes.get(response.arg, str(response.arg))
)
)
return True
class WriteUpdate(BaseCommand): class WriteUpdate(BaseCommand):
def __init__(self, dev, page, data): def __init__(self, dev, page, data):
super(WriteUpdate, self).__init__(dst=dev, cmd=0x08, arg=page & 0xFF, data=data, has_response=True) super(WriteUpdate, self).__init__(
self.dev = dev dst=dev, cmd=0x08, arg=page & 0xFF, data=data, has_response=True
self.page = page )
self.dev = dev
self.page = page
def handle_response(self, response): def handle_response(self, response):
if not len(response.data) in (0, 1): if not len(response.data) in (0, 1):
raise InvalidResponse("WriteUpdate {0:X} @{1:X}".format(self.dev, self.page)) raise InvalidResponse(
if response.arg!=0: "WriteUpdate {0:X} @{1:X}".format(self.dev, self.page)
raise UpdateError("WriteUpdate {0:X} @{1:X}: {2:s}".format(self.dev, self.page, UpdateErrorCodes.get(response.arg, str(response.arg)))) )
return True if response.arg != 0:
raise UpdateError(
"WriteUpdate {0:X} @{1:X}: {2:s}".format(
self.dev,
self.page,
UpdateErrorCodes.get(response.arg, str(response.arg)),
)
)
return True
class FinishUpdate(BaseCommand): class FinishUpdate(BaseCommand):
def __init__(self, dev, checksum): def __init__(self, dev, checksum):
super(FinishUpdate, self).__init__(dst=dev, cmd=0x09, data=pack("<L", checksum), has_response=True) super(FinishUpdate, self).__init__(
self.dev = dev dst=dev, cmd=0x09, data=pack("<L", checksum), has_response=True
)
self.dev = dev
def handle_response(self, response): def handle_response(self, response):
if not len(response.data) in (0, 1): if not len(response.data) in (0, 1):
raise InvalidResponse("FinishUpdate {0:X}".format(self.dev)) raise InvalidResponse("FinishUpdate {0:X}".format(self.dev))
if response.arg!=0: if response.arg != 0:
raise UpdateError("FinishUpdate {0:X}: {1:s}".format(self.dev, UpdateErrorCodes.get(response.arg, str(response.arg)))) raise UpdateError(
return True "FinishUpdate {0:X}: {1:s}".format(
self.dev, UpdateErrorCodes.get(response.arg, str(response.arg))
)
)
return True
class RebootUpdate(BaseCommand): class RebootUpdate(BaseCommand):
def __init__(self, dev): def __init__(self, dev):
super(RebootUpdate, self).__init__(dst=dev, cmd=0x0A, has_response=False) super(RebootUpdate, self).__init__(dst=dev, cmd=0x0A, has_response=False)
self.dev = dev self.dev = dev
def handle_response(self, response): def handle_response(self, response):
return True return True
__all__ = ["UpdateError", "StartUpdate", "WriteUpdate", "FinishUpdate", "RebootUpdate"] __all__ = ["UpdateError", "StartUpdate", "WriteUpdate", "FinishUpdate", "RebootUpdate"]

View File

@ -1,110 +0,0 @@
"""BLE link using BlueGiga adapter via PyGatt/BGAPI"""
import pygatt
from .base import BaseLink, LinkTimeoutException, LinkOpenException
from binascii import hexlify
SCAN_TIMEOUT = 3
try:
import queue
except ImportError:
import queue as queue
class Fifo():
def __init__(self):
self.q = queue.Queue()
def write(self, data): # put bytes
for b in data:
self.q.put(b)
def read(self, size=1, timeout=None): # but read string
res = ''
for i in range(size):
res += chr(self.q.get(True, timeout))
return res
#_cccd_uuid = '00002902-0000-1000-8000-00805f9b34fb'
_rx_char_uuid = '6e400002-b5a3-f393-e0a9-e50e24dcca9e'
_tx_char_uuid = '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
_write_chunk_size = 20 # as in android dumps
class BLELink(BaseLink):
def __init__(self, *args, **kwargs):
super(BLELink, self).__init__(*args, **kwargs)
self._adapter = None
self._dev = None
self._wr_handle = None
self._rx_fifo = Fifo()
def __enter__(self):
self._adapter = pygatt.BGAPIBackend()
self._adapter.start()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def _make_rx_cb(self): # this is a closure :)
def rx_cb(handle, value):
self._rx_fifo.write(value)
return rx_cb
def scan(self):
res = []
devices = self._adapter.scan(timeout=SCAN_TIMEOUT)
for dev in devices:
if dev['name'].startswith(('MISc', 'NBSc')):
res.append((dev['name'], dev['address']))
return res
def open(self, port):
try:
self._dev = self._adapter.connect(port, address_type=pygatt.BLEAddressType.random)
self._dev.subscribe(_tx_char_uuid, callback=self._make_rx_cb())
self._wr_handle = self._dev.get_handle(_rx_char_uuid)
except pygatt.exceptions.NotConnectedError:
raise LinkOpenException
def close(self):
if self._dev:
self._dev.disconnect()
self._dev = None
if self._adapter:
self._adapter.stop()
def read(self, size):
try:
data = self._rx_fifo.read(size, timeout=self.timeout)
except queue.Empty:
raise LinkTimeoutException
if self.dump:
print('<', hexlify(data).upper())
return data
def write(self, data):
if self.dump:
print('>', hexlify(data).upper())
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']

View File

@ -1,109 +0,0 @@
"""BLE link using BlueGiga adapter via PyGatt/BGAPI"""
import pygatt
from .base import BaseLink, LinkTimeoutException, LinkOpenException
from binascii import hexlify
SCAN_TIMEOUT = 3
try:
import queue
except ImportError:
import queue as queue
class Fifo():
def __init__(self):
self.q = queue.Queue()
def write(self, data): # put bytes
for b in data:
self.q.put(b)
def read(self, size=1, timeout=None): # but read string
res = ''
for i in range(size):
res += chr(self.q.get(True, timeout))
return res
#_cccd_uuid = '00002902-0000-1000-8000-00805f9b34fb'
_rx_char_uuid = eval(input('RX UUID?'))
_tx_char_uuid = eval(input('TX UUID?'))
_write_chunk_size = 20 # as in android dumps
class BLELink(BaseLink):
def __init__(self, *args, **kwargs):
super(BLELink, self).__init__(*args, **kwargs)
self._adapter = None
self._dev = None
self._wr_handle = None
self._rx_fifo = Fifo()
def __enter__(self):
self._adapter = pygatt.BGAPIBackend()
self._adapter.start()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def _make_rx_cb(self): # this is a closure :)
def rx_cb(handle, value):
self._rx_fifo.write(value)
return rx_cb
def scan(self):
res = []
devices = self._adapter.scan(timeout=SCAN_TIMEOUT)
for dev in devices:
if dev['name'].startswith(('MISc', 'NBSc')):
res.append((dev['name'], dev['address']))
return res
def open(self, port):
try:
self._dev = self._adapter.connect(port, address_type=pygatt.BLEAddressType.random)
self._dev.subscribe(_tx_char_uuid, callback=self._make_rx_cb())
self._wr_handle = self._dev.get_handle(_rx_char_uuid)
except pygatt.exceptions.NotConnectedError:
raise LinkOpenException
def close(self):
if self._dev:
self._dev.disconnect()
self._dev = None
if self._adapter:
self._adapter.stop()
def read(self, size):
try:
data = self._rx_fifo.read(size, timeout=self.timeout)
except queue.Empty:
raise LinkTimeoutException
if self.dump:
print('<', hexlify(data).upper())
return data
def write(self, data):
if self.dump:
print('>', hexlify(data).upper())
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']

View File

@ -1,243 +0,0 @@
"""BLE link using ABLE"""
try:
from able import GATT_SUCCESS, Advertisement, BluetoothDispatcher
except ImportError:
exit('error importing able')
try:
from .base import BaseLink, LinkTimeoutException, LinkOpenException
except ImportError:
exit('error importing .base')
from binascii import hexlify
from kivy.logger import Logger
from kivy.properties import StringProperty
try:
import queue
except ImportError:
import queue as queue
SCAN_TIMEOUT = 3
_write_chunk_size = 20
identity = bytearray([
0x4e, 0x42, 0x21, 0x00, 0x00, 0x00, 0x00, 0xDE, # Ninebot Bluetooth ID 4E422100000000DE
0x4e, 0x42, 0x21, 0x00, 0x00, 0x00, 0x00, 0xDF # Xiaomi Bluetooth ID 4E422100000000DF
])
service_ids = {
'retail': '6e400001-b5a3-f393-e0a9-e50e24dcca9e' #service UUID
}
receive_ids = {
'retail': '6e400002-b5a3-f393-e0a9-e50e24dcca9e' #receive characteristic UUID
}
transmit_ids = {
'retail': '6e400003-b5a3-f393-e0a9-e50e24dcca9e' #transmit characteristic UUID
}
scoot_found = False
class Fifo():
def __init__(self):
self.q = queue.Queue()
def write(self, data): # put bytes
for b in data:
self.q.put(b)
def read(self, size=1, timeout=None): # but read string
res = ''
for i in range(size):
res += chr(self.q.get(True, timeout))
return res
class ScootBT(BluetoothDispatcher):
def __init__(self):
super(ScootBT, self).__init__()
self.rx_fifo = Fifo()
self.ble_device = None
self.state = StringProperty()
self.dump = True
self.tx_characteristic = None
self.rx_characteristic = None
self.timeout = SCAN_TIMEOUT
self.set_queue_timeout(self.timeout)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def discover(self):
self.start_scan()
self.state = 'scan'
print((self.state))
def on_device(self, device, rssi, advertisement):
global scoot_found
if self.state != 'scan':
return
Logger.debug("on_device event {}".format(list(advertisement)))
self.addr = device.getAddress()
if self.addr and address.startswith(self.addr):
print((self.addr))
self.ble_device = device
self.scoot_found = True
self.stop_scan()
else:
for ad in advertisement:
print(ad)
if ad.ad_type == Advertisement.ad_types.manufacturer_specific_data:
if ad.data.startswith(self.identity):
scoot_found = True
else:
break
elif ad.ad_type == Advertisement.ad_types.complete_local_name:
name = str(ad.data)
if scoot_found:
self.state = 'found'
print((self.state))
self.ble_device = device
Logger.debug("Scooter detected: {}".format(name))
self.stop_scan()
def on_scan_completed(self):
if self.ble_device:
self.connect_gatt(self.ble_device)
self.state = 'connected'
print((self.state))
else:
self.start_scan()
def on_connection_state_change(self, status, state):
if status == GATT_SUCCESS and state:
self.discover_services()
self.state = 'discover'
print((self.state))
else:
self.close_gatt()
self.rx_characteristic = None
self.tx_characteristic = None
self.services = None
def on_services(self, status, services):
self.services = services
for uuid in list(receive_ids.values()):
self.rx_characteristic = self.services.search(uuid)
print(('RX: '+uuid))
for uuid in list(transmit_ids.values()):
self.tx_characteristic = self.services.search(uuid)
print(('TX: '+uuid))
self.enable_notifications(self.tx_characteristic)
def on_characteristic_changed(self, characteristic):
if characteristic == self.tx_characteristic:
data = characteristic.getValue()
self.rx_fifo.write(data)
def open(self, port):
self.addr = port
if self.ble_device == None:
self.discover()
if self.state!='connected':
self.connect_gatt(self.ble_device)
else:
return
def close(self):
if self.ble_device != None:
self.close_gatt()
self.services = None
print('close')
def read(self, size):
print('read')
if self.ble_device:
try:
data = self.rx_fifo.read(size, timeout=self.timeout)
except queue.Empty:
raise LinkTimeoutException
if self.dump:
print('<', hexlify(data).upper())
return data
else:
print('BLE not connected')
self.discover()
def write(self, data):
print('write')
if self.ble_device:
if self.dump:
print('>', hexlify(data).upper())
size = len(data)
ofs = 0
while size:
chunk_sz = min(size, _write_chunk_size)
self.write_characteristic(self.rx_characteristic, bytearray(data[ofs:ofs+chunk_sz]))
ofs += chunk_sz
size -= chunk_sz
else:
print('BLE not connected')
self.discover()
def scan(self):
self.discover()
class BLELink(BaseLink):
def __init__(self, *args, **kwargs):
super(BLELink, self).__init__(*args, **kwargs)
self._adapter = None
def __enter__(self):
self._adapter = ScootBT()
self._adapter.discover()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def scan(self):
devices = self._adapter.scan()
def open(self, port):
self._adapter.open(port)
def close(self):
self._adapter.close()
def read(self, size):
self._adapter.read(size)
def write(self, data):
self._adapter.write(data)
__all__ = ['BLELink']

View File

@ -1,61 +0,0 @@
"""Direct serial link"""
import serial
import serial.tools.list_ports as lp
from binascii import hexlify
from .base import BaseLink, LinkTimeoutException, LinkOpenException
class SerialLink(BaseLink):
def __init__(self, *args, **kwargs):
super(SerialLink, self).__init__(*args, **kwargs)
self.com = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def scan(self):
ports = lp.comports()
res = [("%s %04X:%04X" % (port.device, port.vid, port.pid), port.device) for port in ports]
return res
def open(self, port):
try:
self.com = serial.Serial(port, 115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout)
except serial.SerialException:
raise LinkOpenException
def close(self):
if self.com:
self.com.close()
self.com = None
def read(self, size):
try:
data = self.com.read(size)
except serial.SerialTimeoutException:
raise LinkTimeoutException
if len(data)<size:
raise LinkTimeoutException
if self.dump:
print("<", hexlify(data).upper())
return data
def write(self, data):
if self.dump:
print(">", hexlify(data).upper())
self.com.write(data)
__all__ = ["SerialLink"]

View File

@ -1,80 +0,0 @@
"""TCP-BLE bridge link"""
import socket
from binascii import hexlify
from .base import BaseLink, LinkTimeoutException, LinkOpenException
HOST, PORT = "127.0.0.1", 6000
_write_chunk_size = 20 # 20 as in android dumps
def recvall(sock, size):
data = ""
while len(data)<size:
try:
pkt = sock.recv(size-len(data))
except socket.timeout:
raise LinkTimeoutException()
data+=pkt
return data
class TCPLink(BaseLink):
def __init__(self, *args, **kwargs):
super(TCPLink, self).__init__(*args, **kwargs)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.connected = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def scan(self):
res = [("Android UART Bridge", HOST+':'+str(PORT))]
return res
def open(self, port):
p = port.partition(':')
host = p[0]
port = int(p[2], 10)
print(host, port)
try:
self.sock.connect((host, port))
except socket.timeout:
raise LinkOpenException
self.connected = True
def close(self):
if self.connected:
self.sock.close()
self.connected = False
def read(self, size):
data = recvall(self.sock, size)
if data and self.dump:
print("<", hexlify(data).upper())
return data
def write(self, data):
if self.dump:
print(">", hexlify(data).upper())
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"]

View File

@ -1,34 +1,32 @@
class LinkTimeoutException(Exception): class LinkTimeoutException(Exception):
pass pass
class LinkOpenException(Exception): class LinkOpenException(Exception):
pass pass
class BaseLink(object): class BaseLink(object):
DEF_TIMEOUT = 1 DEF_TIMEOUT = 1
def __init__(self, timeout=DEF_TIMEOUT, dump=False): def __init__(self, timeout=DEF_TIMEOUT, dump=False):
self.dump = dump self.dump = dump
self.timeout = timeout self.timeout = timeout
def scan(self): def scan(self):
raise NotImplementedError() raise NotImplementedError()
def open(self, port):
raise NotImplementedError()
def open(self, port): def close(self):
raise NotImplementedError() pass
def read(self, size):
def close(self): raise NotImplementedError()
pass
def write(self, data):
raise NotImplementedError()
def read(self, size):
raise NotImplementedError()
def write(self, data):
raise NotImplementedError()
__all__ = ["LinkTimeoutException", "LinkOpenException", "BaseLink"] __all__ = ["LinkTimeoutException", "LinkOpenException", "BaseLink"]

View File

@ -13,26 +13,28 @@ try:
except ImportError: except ImportError:
import Queue as queue import Queue as queue
class Fifo():
class Fifo:
def __init__(self): def __init__(self):
self.q = queue.Queue() self.q = queue.Queue()
def write(self, data): # put bytes def write(self, data): # put bytes
for b in data: for b in data:
self.q.put(b) self.q.put(b)
def read(self, size=1, timeout=None): # but read string def read(self, size=1, timeout=None): # but read string
res = '' res = ""
for i in xrange(size): for i in xrange(size):
res += chr(self.q.get(True, timeout)) res += chr(self.q.get(True, timeout))
return res return res
#_cccd_uuid = '00002902-0000-1000-8000-00805f9b34fb' # _cccd_uuid = '00002902-0000-1000-8000-00805f9b34fb'
_rx_char_uuid = '6e400002-b5a3-f393-e0a9-e50e24dcca9e' _rx_char_uuid = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
_tx_char_uuid = '6e400003-b5a3-f393-e0a9-e50e24dcca9e' _tx_char_uuid = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
_write_chunk_size = 20 # as in android dumps
_write_chunk_size = 20 # as in android dumps
class BLELink(BaseLink): class BLELink(BaseLink):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -42,42 +44,41 @@ class BLELink(BaseLink):
self._wr_handle = None self._wr_handle = None
self._rx_fifo = Fifo() self._rx_fifo = Fifo()
def __enter__(self): def __enter__(self):
self._adapter = pygatt.GATTToolBackend() self._adapter = pygatt.GATTToolBackend()
self._adapter.start() self._adapter.start()
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
self.close() self.close()
def _make_rx_cb(self): # this is a closure :)
def _make_rx_cb(self): # this is a closure :)
def rx_cb(handle, value): def rx_cb(handle, value):
self._rx_fifo.write(value) self._rx_fifo.write(value)
return rx_cb
return rx_cb
def scan(self): def scan(self):
res = [] res = []
self._adapter.reset() self._adapter.reset()
devices = self._adapter.scan(timeout=SCAN_TIMEOUT) devices = self._adapter.scan(timeout=SCAN_TIMEOUT)
for dev in devices: for dev in devices:
if dev['name'] and dev['name'].startswith((u'MISc', u'NBSc', u'JP2', u'Seg')): if dev["name"] and dev["name"].startswith(
res.append((dev['name'], dev['address'])) (u"MISc", u"NBSc", u"JP2", u"Seg")
):
res.append((dev["name"], dev["address"]))
return res return res
def open(self, port): def open(self, port):
try: try:
self._dev = self._adapter.connect(port, address_type=pygatt.BLEAddressType.random) self._dev = self._adapter.connect(
port, address_type=pygatt.BLEAddressType.random
)
self._dev.subscribe(_tx_char_uuid, callback=self._make_rx_cb()) self._dev.subscribe(_tx_char_uuid, callback=self._make_rx_cb())
self._wr_handle = self._dev.get_handle(_rx_char_uuid) self._wr_handle = self._dev.get_handle(_rx_char_uuid)
except pygatt.exceptions.NotConnectedError: except pygatt.exceptions.NotConnectedError:
raise LinkOpenException raise LinkOpenException
def close(self): def close(self):
if self._dev: if self._dev:
self._dev.disconnect() self._dev.disconnect()
@ -85,27 +86,27 @@ class BLELink(BaseLink):
if self._adapter: if self._adapter:
self._adapter.stop() self._adapter.stop()
def read(self, size): def read(self, size):
try: try:
data = self._rx_fifo.read(size, timeout=self.timeout) data = self._rx_fifo.read(size, timeout=self.timeout)
except queue.Empty: except queue.Empty:
raise LinkTimeoutException raise LinkTimeoutException
if self.dump: if self.dump:
print('<', hexlify(data).upper()) print("<", hexlify(data).upper())
return data return data
def write(self, data): def write(self, data):
if self.dump: if self.dump:
print('>', hexlify(data).upper()) print(">", hexlify(data).upper())
size = len(data) size = len(data)
ofs = 0 ofs = 0
while size: while size:
chunk_sz = min(size, _write_chunk_size) chunk_sz = min(size, _write_chunk_size)
self._dev.char_write_handle(self._wr_handle, bytearray(data[ofs:ofs+chunk_sz])) self._dev.char_write_handle(
self._wr_handle, bytearray(data[ofs : ofs + chunk_sz])
)
ofs += chunk_sz ofs += chunk_sz
size -= chunk_sz size -= chunk_sz
__all__ = ['BLELink'] __all__ = ["BLELink"]

View File

@ -4,24 +4,25 @@ from bleak import discover, BleakClient
from py9b.link.base import BaseLink from py9b.link.base import BaseLink
from threading import Thread from threading import Thread
_rx_char_uuid = '6e400002-b5a3-f393-e0a9-e50e24dcca9e' _rx_char_uuid = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
_tx_char_uuid = '6e400003-b5a3-f393-e0a9-e50e24dcca9e' _tx_char_uuid = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
_keys_char_uuid = '00000014-0000-1000-8000-00805f9b34fb' _keys_char_uuid = "00000014-0000-1000-8000-00805f9b34fb"
try: try:
import queue import queue
except ImportError: except ImportError:
import Queue as queue import Queue as queue
class Fifo():
class Fifo:
def __init__(self): def __init__(self):
self.q = queue.Queue() self.q = queue.Queue()
def write(self, data): # put bytes def write(self, data): # put bytes
for b in data: for b in data:
self.q.put(b) self.q.put(b)
def read(self, size=1, timeout=None): # but read string def read(self, size=1, timeout=None): # but read string
res = bytearray() res = bytearray()
for i in range(size): for i in range(size):
res.append(self.q.get(True, timeout)) res.append(self.q.get(True, timeout))
@ -29,12 +30,13 @@ class Fifo():
def run_worker(loop): def run_worker(loop):
print('Starting event loop', loop) print("Starting event loop", loop)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
loop.run_forever() loop.run_forever()
class BleakLink(BaseLink): class BleakLink(BaseLink):
def __init__(self, device='hci0', loop=None, *args, **kwargs): def __init__(self, device="hci0", loop=None, *args, **kwargs):
self.device = device self.device = device
self.timeout = 5 self.timeout = 5
self.loop = loop or asyncio.get_event_loop() self.loop = loop or asyncio.get_event_loop()
@ -59,11 +61,19 @@ class BleakLink(BaseLink):
self.close() self.close()
def close(self): def close(self):
asyncio.run_coroutine_threadsafe(self._client.disconnect(), self.loop).result(10) asyncio.run_coroutine_threadsafe(self._client.disconnect(), self.loop).result(
10
)
def scan(self, timeout=1): def scan(self, timeout=1):
future = asyncio.run_coroutine_threadsafe(discover(timeout=timeout, device=self.device), self.loop) future = asyncio.run_coroutine_threadsafe(
return [(dev.name, dev.address) for dev in future.result(timeout*2) if dev.name.startswith(('MISc', 'NBSc'))] discover(timeout=timeout, device=self.device), self.loop
)
return [
(dev.name, dev.address)
for dev in future.result(timeout * 2)
if dev.name.startswith(("MISc", "NBSc"))
]
def open(self, port): def open(self, port):
fut = asyncio.run_coroutine_threadsafe(self._connect(port), self.loop) fut = asyncio.run_coroutine_threadsafe(self._connect(port), self.loop)
@ -72,17 +82,20 @@ class BleakLink(BaseLink):
async def _connect(self, port): async def _connect(self, port):
self._client = BleakClient(port[1], device=self.device) self._client = BleakClient(port[1], device=self.device)
await self._client.connect() await self._client.connect()
print('connected') print("connected")
await self._client.start_notify(_tx_char_uuid, self._data_received) await self._client.start_notify(_tx_char_uuid, self._data_received)
print('services:', list(await self._client.get_services())) print("services:", list(await self._client.get_services()))
def _data_received(self, sender, data): def _data_received(self, sender, data):
print('<<', ' '.join(map(lambda b: '%02x' % b, data))) print("<<", " ".join(map(lambda b: "%02x" % b, data)))
self._rx_fifo.write(data) self._rx_fifo.write(data)
def write(self, data): def write(self, data):
print('>>', ' '.join(map(lambda b: '%02x' % b, data))) print(">>", " ".join(map(lambda b: "%02x" % b, data)))
fut = asyncio.run_coroutine_threadsafe(self._client.write_gatt_char(_rx_char_uuid, bytearray(data), False), self.loop) fut = asyncio.run_coroutine_threadsafe(
self._client.write_gatt_char(_rx_char_uuid, bytearray(data), False),
self.loop,
)
return fut.result(3) return fut.result(3)
def read(self, size): def read(self, size):
@ -93,4 +106,6 @@ class BleakLink(BaseLink):
return data return data
def fetch_keys(self): def fetch_keys(self):
return asyncio.run_coroutine_threadsafe(self._client.read_gatt_char(_keys_char_uuid), self.loop).result(5) return asyncio.run_coroutine_threadsafe(
self._client.read_gatt_char(_keys_char_uuid), self.loop
).result(5)

View File

@ -9,101 +9,100 @@ SCAN_TIMEOUT = 3
try: try:
import queue import queue
except ImportError: except ImportError:
import Queue as queue import Queue as queue
class Fifo():
def __init__(self):
self.q = queue.Queue()
def write(self, data): # put bytes
for b in data:
self.q.put(b)
def read(self, size=1, timeout=None): # but read string
res = ''
for i in xrange(size):
res += chr(self.q.get(True, timeout))
return res
#_cccd_uuid = '00002902-0000-1000-8000-00805f9b34fb' class Fifo:
_rx_char_uuid = input('RX UUID?') def __init__(self):
_tx_char_uuid = input('TX UUID?') self.q = queue.Queue()
_write_chunk_size = 20 # as in android dumps
def write(self, data): # put bytes
for b in data:
self.q.put(b)
def read(self, size=1, timeout=None): # but read string
res = ""
for i in xrange(size):
res += chr(self.q.get(True, timeout))
return res
# _cccd_uuid = '00002902-0000-1000-8000-00805f9b34fb'
_rx_char_uuid = input("RX UUID?")
_tx_char_uuid = input("TX UUID?")
_write_chunk_size = 20 # as in android dumps
class BLELink(BaseLink): class BLELink(BaseLink):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(BLELink, self).__init__(*args, **kwargs) super(BLELink, self).__init__(*args, **kwargs)
self._adapter = None self._adapter = None
self._dev = None self._dev = None
self._wr_handle = None self._wr_handle = None
self._rx_fifo = Fifo() self._rx_fifo = Fifo()
def __enter__(self):
self._adapter = pygatt.BGAPIBackend()
self._adapter.start()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def _make_rx_cb(self): # this is a closure :)
def rx_cb(handle, value):
self._rx_fifo.write(value)
return rx_cb
def scan(self):
res = []
devices = self._adapter.scan(timeout=SCAN_TIMEOUT)
for dev in devices:
if dev["name"].startswith((u"MISc", u"NBSc")):
res.append((dev["name"], dev["address"]))
return res
def open(self, port):
try:
self._dev = self._adapter.connect(
port, address_type=pygatt.BLEAddressType.random
)
self._dev.subscribe(_tx_char_uuid, callback=self._make_rx_cb())
self._wr_handle = self._dev.get_handle(_rx_char_uuid)
except pygatt.exceptions.NotConnectedError:
raise LinkOpenException
def close(self):
if self._dev:
self._dev.disconnect()
self._dev = None
if self._adapter:
self._adapter.stop()
def read(self, size):
try:
data = self._rx_fifo.read(size, timeout=self.timeout)
except queue.Empty:
raise LinkTimeoutException
if self.dump:
print("<", hexlify(data).upper())
return data
def write(self, data):
if self.dump:
print(">", hexlify(data).upper())
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
def __enter__(self): __all__ = ["BLELink"]
self._adapter = pygatt.BGAPIBackend()
self._adapter.start()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def _make_rx_cb(self): # this is a closure :)
def rx_cb(handle, value):
self._rx_fifo.write(value)
return rx_cb
def scan(self):
res = []
devices = self._adapter.scan(timeout=SCAN_TIMEOUT)
for dev in devices:
if dev['name'].startswith((u'MISc', u'NBSc')):
res.append((dev['name'], dev['address']))
return res
def open(self, port):
try:
self._dev = self._adapter.connect(port, address_type=pygatt.BLEAddressType.random)
self._dev.subscribe(_tx_char_uuid, callback=self._make_rx_cb())
self._wr_handle = self._dev.get_handle(_rx_char_uuid)
except pygatt.exceptions.NotConnectedError:
raise LinkOpenException
def close(self):
if self._dev:
self._dev.disconnect()
self._dev = None
if self._adapter:
self._adapter.stop()
def read(self, size):
try:
data = self._rx_fifo.read(size, timeout=self.timeout)
except queue.Empty:
raise LinkTimeoutException
if self.dump:
print '<', hexlify(data).upper()
return data
def write(self, data):
if self.dump:
print '>', hexlify(data).upper()
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']

View File

@ -1,14 +1,15 @@
"""BLE link using ABLE""" """BLE link using ABLE"""
from __future__ import absolute_import from __future__ import absolute_import
try: try:
from able import GATT_SUCCESS, Advertisement, BluetoothDispatcher from able import GATT_SUCCESS, Advertisement, BluetoothDispatcher
except ImportError: except ImportError:
exit('error importing able') exit("error importing able")
try: try:
from .base import BaseLink, LinkTimeoutException, LinkOpenException from .base import BaseLink, LinkTimeoutException, LinkOpenException
except ImportError: except ImportError:
exit('error importing .base') exit("error importing .base")
from binascii import hexlify from binascii import hexlify
from kivy.logger import Logger from kivy.logger import Logger
from kivy.properties import StringProperty from kivy.properties import StringProperty
@ -22,36 +23,50 @@ SCAN_TIMEOUT = 3
_write_chunk_size = 20 _write_chunk_size = 20
identity = bytearray([ identity = bytearray(
0x4e, 0x42, 0x21, 0x00, 0x00, 0x00, 0x00, 0xDE, # Ninebot Bluetooth ID 4E422100000000DE [
0x4e, 0x42, 0x21, 0x00, 0x00, 0x00, 0x00, 0xDF # Xiaomi Bluetooth ID 4E422100000000DF 0x4E,
]) 0x42,
0x21,
0x00,
0x00,
0x00,
0x00,
0xDE, # Ninebot Bluetooth ID 4E422100000000DE
0x4E,
0x42,
0x21,
0x00,
0x00,
0x00,
0x00,
0xDF, # Xiaomi Bluetooth ID 4E422100000000DF
]
)
service_ids = { service_ids = {"retail": "6e400001-b5a3-f393-e0a9-e50e24dcca9e"} # service UUID
'retail': '6e400001-b5a3-f393-e0a9-e50e24dcca9e' #service UUID
}
receive_ids = { receive_ids = {
'retail': '6e400002-b5a3-f393-e0a9-e50e24dcca9e' #receive characteristic UUID "retail": "6e400002-b5a3-f393-e0a9-e50e24dcca9e" # receive characteristic UUID
} }
transmit_ids = { transmit_ids = {
'retail': '6e400003-b5a3-f393-e0a9-e50e24dcca9e' #transmit characteristic UUID "retail": "6e400003-b5a3-f393-e0a9-e50e24dcca9e" # transmit characteristic UUID
} }
scoot_found = False scoot_found = False
class Fifo(): class Fifo:
def __init__(self): def __init__(self):
self.q = queue.Queue() self.q = queue.Queue()
def write(self, data): # put bytes def write(self, data): # put bytes
for b in data: for b in data:
self.q.put(b) self.q.put(b)
def read(self, size=1, timeout=None): # but read string def read(self, size=1, timeout=None): # but read string
res = '' res = ""
for i in xrange(size): for i in xrange(size):
res += chr(self.q.get(True, timeout)) res += chr(self.q.get(True, timeout))
return res return res
@ -69,24 +84,20 @@ class ScootBT(BluetoothDispatcher):
self.timeout = SCAN_TIMEOUT self.timeout = SCAN_TIMEOUT
self.set_queue_timeout(self.timeout) self.set_queue_timeout(self.timeout)
def __enter__(self): def __enter__(self):
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
self.close() self.close()
def discover(self): def discover(self):
self.start_scan() self.start_scan()
self.state = 'scan' self.state = "scan"
print(self.state) print(self.state)
def on_device(self, device, rssi, advertisement): def on_device(self, device, rssi, advertisement):
global scoot_found global scoot_found
if self.state != 'scan': if self.state != "scan":
return return
Logger.debug("on_device event {}".format(list(advertisement))) Logger.debug("on_device event {}".format(list(advertisement)))
self.addr = device.getAddress() self.addr = device.getAddress()
@ -106,26 +117,24 @@ class ScootBT(BluetoothDispatcher):
elif ad.ad_type == Advertisement.ad_types.complete_local_name: elif ad.ad_type == Advertisement.ad_types.complete_local_name:
name = str(ad.data) name = str(ad.data)
if scoot_found: if scoot_found:
self.state = 'found' self.state = "found"
print(self.state) print(self.state)
self.ble_device = device self.ble_device = device
Logger.debug("Scooter detected: {}".format(name)) Logger.debug("Scooter detected: {}".format(name))
self.stop_scan() self.stop_scan()
def on_scan_completed(self): def on_scan_completed(self):
if self.ble_device: if self.ble_device:
self.connect_gatt(self.ble_device) self.connect_gatt(self.ble_device)
self.state = 'connected' self.state = "connected"
print(self.state) print(self.state)
else: else:
self.start_scan() self.start_scan()
def on_connection_state_change(self, status, state): def on_connection_state_change(self, status, state):
if status == GATT_SUCCESS and state: if status == GATT_SUCCESS and state:
self.discover_services() self.discover_services()
self.state = 'discover' self.state = "discover"
print(self.state) print(self.state)
else: else:
self.close_gatt() self.close_gatt()
@ -133,73 +142,68 @@ class ScootBT(BluetoothDispatcher):
self.tx_characteristic = None self.tx_characteristic = None
self.services = None self.services = None
def on_services(self, status, services): def on_services(self, status, services):
self.services = services self.services = services
for uuid in receive_ids.values(): for uuid in receive_ids.values():
self.rx_characteristic = self.services.search(uuid) self.rx_characteristic = self.services.search(uuid)
print('RX: '+uuid) print("RX: " + uuid)
for uuid in transmit_ids.values(): for uuid in transmit_ids.values():
self.tx_characteristic = self.services.search(uuid) self.tx_characteristic = self.services.search(uuid)
print('TX: '+uuid) print("TX: " + uuid)
self.enable_notifications(self.tx_characteristic) self.enable_notifications(self.tx_characteristic)
def on_characteristic_changed(self, characteristic): def on_characteristic_changed(self, characteristic):
if characteristic == self.tx_characteristic: if characteristic == self.tx_characteristic:
data = characteristic.getValue() data = characteristic.getValue()
self.rx_fifo.write(data) self.rx_fifo.write(data)
def open(self, port): def open(self, port):
self.addr = port self.addr = port
if self.ble_device == None: if self.ble_device == None:
self.discover() self.discover()
if self.state!='connected': if self.state != "connected":
self.connect_gatt(self.ble_device) self.connect_gatt(self.ble_device)
else: else:
return return
def close(self): def close(self):
if self.ble_device != None: if self.ble_device != None:
self.close_gatt() self.close_gatt()
self.services = None self.services = None
print('close') print("close")
def read(self, size): def read(self, size):
print('read') print("read")
if self.ble_device: if self.ble_device:
try: try:
data = self.rx_fifo.read(size, timeout=self.timeout) data = self.rx_fifo.read(size, timeout=self.timeout)
except queue.Empty: except queue.Empty:
raise LinkTimeoutException raise LinkTimeoutException
if self.dump: if self.dump:
print '<', hexlify(data).upper() print("<", hexlify(data).upper())
return data return data
else: else:
print('BLE not connected') print("BLE not connected")
self.discover() self.discover()
def write(self, data): def write(self, data):
print('write') print("write")
if self.ble_device: if self.ble_device:
if self.dump: if self.dump:
print '>', hexlify(data).upper() print(">", hexlify(data).upper())
size = len(data) size = len(data)
ofs = 0 ofs = 0
while size: while size:
chunk_sz = min(size, _write_chunk_size) chunk_sz = min(size, _write_chunk_size)
self.write_characteristic(self.rx_characteristic, bytearray(data[ofs:ofs+chunk_sz])) self.write_characteristic(
self.rx_characteristic, bytearray(data[ofs : ofs + chunk_sz])
)
ofs += chunk_sz ofs += chunk_sz
size -= chunk_sz size -= chunk_sz
else: else:
print('BLE not connected') print("BLE not connected")
self.discover() self.discover()
def scan(self): def scan(self):
self.discover() self.discover()
@ -209,35 +213,28 @@ class BLELink(BaseLink):
super(BLELink, self).__init__(*args, **kwargs) super(BLELink, self).__init__(*args, **kwargs)
self._adapter = None self._adapter = None
def __enter__(self): def __enter__(self):
self._adapter = ScootBT() self._adapter = ScootBT()
self._adapter.discover() self._adapter.discover()
return self return self
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
self.close() self.close()
def scan(self): def scan(self):
devices = self._adapter.scan() devices = self._adapter.scan()
def open(self, port): def open(self, port):
self._adapter.open(port) self._adapter.open(port)
def close(self): def close(self):
self._adapter.close() self._adapter.close()
def read(self, size): def read(self, size):
self._adapter.read(size) self._adapter.read(size)
def write(self, data): def write(self, data):
self._adapter.write(data) self._adapter.write(data)
__all__ = ['BLELink'] __all__ = ["BLELink"]

View File

@ -5,57 +5,59 @@ import serial
import serial.tools.list_ports as lp import serial.tools.list_ports as lp
from binascii import hexlify from binascii import hexlify
from .base import BaseLink, LinkTimeoutException, LinkOpenException from .base import BaseLink, LinkTimeoutException, LinkOpenException
class SerialLink(BaseLink): class SerialLink(BaseLink):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SerialLink, self).__init__(*args, **kwargs) super(SerialLink, self).__init__(*args, **kwargs)
self.com = None self.com = None
def __enter__(self):
def __enter__(self): return self
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def __exit__(self, exc_type, exc_value, traceback):
self.close() def scan(self):
ports = lp.comports()
res = [
def scan(self): ("%s %04X:%04X" % (port.device, port.vid, port.pid), port.device)
ports = lp.comports() for port in ports
res = [("%s %04X:%04X" % (port.device, port.vid, port.pid), port.device) for port in ports] ]
return res return res
def open(self, port):
def open(self, port): try:
try: self.com = serial.Serial(
self.com = serial.Serial(port, 115200, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout) port,
except serial.SerialException: 115200,
raise LinkOpenException parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=self.timeout,
def close(self): )
if self.com: except serial.SerialException:
self.com.close() raise LinkOpenException
self.com = None
def close(self):
if self.com:
def read(self, size): self.com.close()
try: self.com = None
data = self.com.read(size)
except serial.SerialTimeoutException: def read(self, size):
raise LinkTimeoutException try:
if len(data)<size: data = self.com.read(size)
raise LinkTimeoutException except serial.SerialTimeoutException:
if self.dump: raise LinkTimeoutException
print("<", hexlify(data).upper()) if len(data) < size:
return data raise LinkTimeoutException
if self.dump:
print("<", hexlify(data).upper())
def write(self, data): return data
if self.dump:
print(">", hexlify(data).upper()) def write(self, data):
self.com.write(data) if self.dump:
print(">", hexlify(data).upper())
self.com.write(data)
__all__ = ["SerialLink"]
__all__ = ["SerialLink"]

View File

@ -6,75 +6,69 @@ from .base import BaseLink, LinkTimeoutException, LinkOpenException
HOST, PORT = "127.0.0.1", 6000 HOST, PORT = "127.0.0.1", 6000
_write_chunk_size = 20 # 20 as in android dumps _write_chunk_size = 20 # 20 as in android dumps
def recvall(sock, size): def recvall(sock, size):
data = "" data = ""
while len(data)<size: while len(data) < size:
try: try:
pkt = sock.recv(size-len(data)) pkt = sock.recv(size - len(data))
except socket.timeout: except socket.timeout:
raise LinkTimeoutException() raise LinkTimeoutException()
data+=pkt data += pkt
return data return data
class TCPLink(BaseLink): class TCPLink(BaseLink):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(TCPLink, self).__init__(*args, **kwargs) super(TCPLink, self).__init__(*args, **kwargs)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout) self.sock.settimeout(self.timeout)
self.connected = False self.connected = False
def __enter__(self):
return self
def __enter__(self): def __exit__(self, exc_type, exc_value, traceback):
return self self.close()
def scan(self):
res = [("Android UART Bridge", HOST + ":" + str(PORT))]
return res
def __exit__(self, exc_type, exc_value, traceback): def open(self, port):
self.close() p = port.partition(":")
host = p[0]
port = int(p[2], 10)
print(host, port)
try:
self.sock.connect((host, port))
except socket.timeout:
raise LinkOpenException
self.connected = True
def close(self):
if self.connected:
self.sock.close()
self.connected = False
def scan(self): def read(self, size):
res = [("Android UART Bridge", HOST+':'+str(PORT))] data = recvall(self.sock, size)
return res if data and self.dump:
print("<", hexlify(data).upper())
return data
def write(self, data):
def open(self, port): if self.dump:
p = port.partition(':') print(">", hexlify(data).upper())
host = p[0] size = len(data)
port = int(p[2], 10) ofs = 0
print(host, port) while size:
try: chunk_sz = min(size, _write_chunk_size)
self.sock.connect((host, port)) self.sock.sendall(data[ofs : ofs + chunk_sz])
except socket.timeout: ofs += chunk_sz
raise LinkOpenException size -= chunk_sz
self.connected = True
def close(self):
if self.connected:
self.sock.close()
self.connected = False
def read(self, size):
data = recvall(self.sock, size)
if data and self.dump:
print("<", hexlify(data).upper())
return data
def write(self, data):
if self.dump:
print(">", hexlify(data).upper())
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"] __all__ = ["TCPLink"]

View File

@ -1,47 +0,0 @@
"""Ninebot packet transport"""
from struct import pack, unpack
from .base import checksum, BaseTransport as BT
from .packet import BasePacket
class NinebotTransport(BT):
def __init__(self, link, device=BT.HOST):
super(NinebotTransport, self).__init__(link)
self.device = device
def _wait_pre(self):
while True:
while True:
c = self.link.read(1)
if c=="\x5A":
break
while True:
c = self.link.read(1)
if c=="\xA5":
return True
if c!="\x5A":
break # start waiting 5A again, else - this is 5A, so wait for A5
def recv(self):
self._wait_pre()
pkt = self.link.read(1)
l = ord(pkt)+6
for i in range(l):
pkt += self.link.read(1)
ck_calc = checksum(pkt[0:-2])
ck_pkt = unpack("<H", pkt[-2:])[0]
if ck_pkt!=ck_calc:
print("Checksum mismatch !")
return None
return BasePacket(ord(pkt[1]), ord(pkt[2]), ord(pkt[3]), ord(pkt[4]), pkt[5:-2]) # sa, da, cmd, arg, data
def send(self, packet):
pkt = pack("<BBBBB", len(packet.data), packet.src, packet.dst, packet.cmd, packet.arg)+packet.data
pkt = "\x5A\xA5" + pkt + pack("<H", checksum(pkt))
self.link.write(pkt)
__all__ = ["NinebotTransport"]

View File

@ -1,95 +0,0 @@
"""Xiaomi packet transport"""
from struct import pack, unpack
from .base import checksum, BaseTransport as BT
from .packet import BasePacket
class XiaomiTransport(BT):
MASTER2ESC = 0x20
ESC2MASTER = 0x23
MASTER2BLE = 0x21
BLE2MASTER = 0x24
MASTER2BMS = 0x22
BMS2MASTER = 0x25
MOTOR = 0x01
DEVFF = 0xFF
_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),
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),
MASTER2BLE : (BT.BMS, BT.BLE),
BLE2MASTER : (BT.BLE, BT.BMS),
MOTOR : (BT.MOTOR, BT.BMS) }
def __init__(self, link, device=BT.HOST):
super(XiaomiTransport, self).__init__(link)
self.device = device
def _make_addr(self, src, dst):
return XiaomiTransport._SaDa2Addr[src][dst]
def _split_addr(self, addr):
if self.device==BT.BMS:
return XiaomiTransport._BmsAddr2SaDa[addr]
else:
return XiaomiTransport._BleAddr2SaDa[addr]
def _wait_pre(self):
while True:
while True:
c = self.link.read(1)
if c=="\x55":
break
while True:
c = self.link.read(1)
if c=="\xAA":
return True
if c!="\x55":
break # start waiting 55 again, else - this is 55, so wait for AA
def recv(self):
self._wait_pre()
pkt = self.link.read(1)
l = ord(pkt)+3
for i in range(l):
pkt += self.link.read(1)
ck_calc = checksum(pkt[0:-2])
ck_pkt = unpack("<H", pkt[-2:])[0]
if ck_pkt!=ck_calc:
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, arg, data
def send(self, packet):
dev = self._make_addr(packet.src, packet.dst)
pkt = pack("<BBBB", len(packet.data)+2, dev, packet.cmd, packet.arg)+packet.data
pkt = "\x55\xAA" + pkt + pack("<H", checksum(pkt))
self.link.write(pkt)
__all__ = ["XiaomiTransport"]

View File

@ -1,43 +1,58 @@
"""Transport abstract class""" """Transport abstract class"""
def checksum(data):
s = 0
for c in data:
s += c
return (s & 0xFFFF) ^ 0xFFFF
def checksum(data):
s = 0
for c in data:
s += c
return (s & 0xFFFF) ^ 0xFFFF
class BaseTransport(object): class BaseTransport(object):
MOTOR = 0x01 MOTOR = 0x01
ESC = 0x20 ESC = 0x20
BLE = 0x21 BLE = 0x21
BMS = 0x22 BMS = 0x22
EXTBMS = 0x23 EXTBMS = 0x23
HOST = 0x3E HOST = 0x3E
DeviceNames = { MOTOR : "MOTOR", ESC : "ESC", BLE : "BLE", BMS : "BMS", EXTBMS : "EXTBMS", HOST : "HOST" }
def __init__(self, link): DeviceNames = {
self.link = link MOTOR: "MOTOR",
ESC: "ESC",
BLE: "BLE",
BMS: "BMS",
EXTBMS: "EXTBMS",
HOST: "HOST",
}
def recv(self): def __init__(self, link):
raise NotImplementedError() self.link = link
def send(self, src, dst, cmd, arg, data=""): def recv(self):
raise NotImplementedError() raise NotImplementedError()
def execute(self, command): def send(self, src, dst, cmd, arg, data=""):
self.send(command.request) raise NotImplementedError()
if not command.has_response:
return True
#TODO: retry ?
rsp = self.recv()
return command.handle_response(rsp)
@staticmethod def execute(self, command):
def GetDeviceName(dev): self.send(command.request)
return BaseTransport.DeviceNames.get(dev, "%02X" % (dev)) if not command.has_response:
return True
# TODO: retry ?
exc = None
for n in range(1):
try:
rsp = self.recv()
return command.handle_response(rsp)
except Exception as e:
print("retry")
exc = e
pass
raise exc
@staticmethod
def GetDeviceName(dev):
return BaseTransport.DeviceNames.get(dev, "%02X" % (dev))
__all__ = ["checksum", "BaseTransport"] __all__ = ["checksum", "BaseTransport"]

View File

@ -5,43 +5,52 @@ from .packet import BasePacket
class NinebotTransport(BT): class NinebotTransport(BT):
def __init__(self, link, device=BT.HOST): def __init__(self, link, device=BT.HOST):
super(NinebotTransport, self).__init__(link) super(NinebotTransport, self).__init__(link)
self.device = device self.device = device
def _wait_pre(self):
while True:
while True:
c = self.link.read(1)
if c == b"\x5A":
break
while True:
c = self.link.read(1)
if c == b"\xA5":
return True
if c != b"\x5A":
break # start waiting 5A again, else - this is 5A, so wait for A5
def _wait_pre(self): def recv(self):
while True: self._wait_pre()
while True: pkt = self.link.read(1)
c = self.link.read(1) l = ord(pkt) + 6
if c==b"\x5A": for i in range(l):
break pkt += self.link.read(1)
while True: ck_calc = checksum(pkt[0:-2])
c = self.link.read(1) ck_pkt = unpack("<H", pkt[-2:])[0]
if c==b"\xA5": if ck_pkt != ck_calc:
return True print("Checksum mismatch !")
if c!=b"\x5A": return None
break # start waiting 5A again, else - this is 5A, so wait for A5 return BasePacket(
pkt[1], pkt[2], pkt[3], pkt[4], pkt[5:-2]
) # sa, da, cmd, arg, data
def send(self, packet):
def recv(self): pkt = (
self._wait_pre() pack(
pkt = self.link.read(1) "<BBBBB",
l = ord(pkt)+6 len(packet.data),
for i in range(l): packet.src,
pkt += self.link.read(1) packet.dst,
ck_calc = checksum(pkt[0:-2]) packet.cmd,
ck_pkt = unpack("<H", pkt[-2:])[0] packet.arg,
if ck_pkt!=ck_calc: )
print("Checksum mismatch !") + packet.data
return None )
return BasePacket(pkt[1], pkt[2], pkt[3], pkt[4], pkt[5:-2]) # sa, da, cmd, arg, data pkt = b"\x5A\xA5" + pkt + pack("<H", checksum(pkt))
self.link.write(pkt)
def send(self, packet):
pkt = pack("<BBBBB", len(packet.data), packet.src, packet.dst, packet.cmd, packet.arg)+packet.data
pkt = b"\x5A\xA5" + pkt + pack("<H", checksum(pkt))
self.link.write(pkt)
__all__ = ["NinebotTransport"] __all__ = ["NinebotTransport"]

View File

@ -1,6 +1,7 @@
from binascii import hexlify from binascii import hexlify
from .base import BaseTransport as BT from .base import BaseTransport as BT
class BasePacket(object): class BasePacket(object):
def __init__(self, src=0, dst=0, cmd=0, arg=0, data=""): def __init__(self, src=0, dst=0, cmd=0, arg=0, data=""):
self.src = src self.src = src
@ -10,7 +11,13 @@ class BasePacket(object):
self.data = data self.data = data
def __str__(self): def __str__(self):
return "%s->%s: %02X @%02X %s" % (BT.GetDeviceName(self.src), BT.GetDeviceName(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"] __all__ = ["BasePacket"]

View File

@ -14,93 +14,108 @@ class XiaomiTransport(BT):
MASTER2BMS = 0x22 MASTER2BMS = 0x22
BMS2MASTER = 0x25 BMS2MASTER = 0x25
MOTOR = 0x01 MOTOR = 0x01
DEVFF = 0xFF DEVFF = 0xFF
_SaDa2Addr = { BT.HOST : { BT.MOTOR : MOTOR, BT.ESC : MASTER2ESC, BT.BLE : MASTER2BLE, BT.BMS : MASTER2BMS }, _SaDa2Addr = {
BT.ESC : { BT.HOST : ESC2MASTER, BT.BLE : MASTER2BLE, BT.BMS : MASTER2BMS, BT.MOTOR : MOTOR }, BT.HOST: {
BT.BMS : { BT.HOST : BMS2MASTER, BT.ESC : BMS2MASTER, BT.MOTOR : MOTOR }, BT.MOTOR: MOTOR,
BT.MOTOR : {BT.HOST : MOTOR, BT.ESC : MOTOR, BT.BMS : 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 # TBC
_BleAddr2SaDa = { MASTER2ESC : (BT.HOST, BT.ESC), _BleAddr2SaDa = {
ESC2MASTER : (BT.ESC, BT.HOST), MASTER2ESC: (BT.HOST, BT.ESC),
MASTER2BMS : (BT.HOST, BT.BMS), ESC2MASTER: (BT.ESC, BT.HOST),
BMS2MASTER : (BT.BMS, BT.HOST), MASTER2BMS: (BT.HOST, BT.BMS),
MASTER2BLE : (BT.HOST, BT.BLE), BMS2MASTER: (BT.BMS, BT.HOST),
BLE2MASTER : (BT.BLE, BT.HOST), MASTER2BLE: (BT.HOST, BT.BLE),
MOTOR : (BT.MOTOR, BT.HOST) } 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),
MASTER2BLE : (BT.BMS, BT.BLE),
BLE2MASTER : (BT.BLE, BT.BMS),
MOTOR : (BT.MOTOR, BT.BMS) }
_BmsAddr2SaDa = {
MASTER2ESC: (BT.BMS, BT.ESC),
ESC2MASTER: (BT.ESC, BT.BMS),
MASTER2BMS: (BT.ESC, BT.BMS),
BMS2MASTER: (BT.BMS, BT.ESC),
MASTER2BLE: (BT.BMS, BT.BLE),
BLE2MASTER: (BT.BLE, BT.BMS),
MOTOR: (BT.MOTOR, BT.BMS),
}
def __init__(self, link, device=BT.HOST): def __init__(self, link, device=BT.HOST):
super(XiaomiTransport, self).__init__(link) super(XiaomiTransport, self).__init__(link)
self.device = device self.device = device
self.keys = None self.keys = None
def _make_addr(self, src, dst): def _make_addr(self, src, dst):
return XiaomiTransport._SaDa2Addr[src][dst] return XiaomiTransport._SaDa2Addr[src][dst]
def _split_addr(self, addr): def _split_addr(self, addr):
if self.device==BT.BMS: if self.device == BT.BMS:
return XiaomiTransport._BmsAddr2SaDa[addr] return XiaomiTransport._BmsAddr2SaDa[addr]
else: else:
return XiaomiTransport._BleAddr2SaDa[addr] return XiaomiTransport._BleAddr2SaDa[addr]
def _wait_pre(self): def _wait_pre(self):
while True: while True:
while True: while True:
c = self.link.read(1) c = self.link.read(1)
if c == b'\x55': if c == b"\x55":
break break
while True: while True:
c = self.link.read(1) c = self.link.read(1)
if c == b'\xaa' or c == b'\xab': if c == b"\xaa" or c == b"\xab":
return c return c
if c != b'\x55': if c != b"\x55":
break # start waiting 55 again, else - this is 55, so wait for AA break # start waiting 55 again, else - this is 55, so wait for AA
def recv(self): def recv(self):
ver = self._wait_pre() ver = self._wait_pre()
pkt = self.link.read(1) pkt = self.link.read(1)
l = ord(pkt) + 3 + (4 if ver == b'\xab' else 0) l = ord(pkt) + 3 + (4 if ver == b"\xab" else 0)
for i in range(l): for i in range(l):
pkt.extend(self.link.read(1)) pkt.extend(self.link.read(1))
ck_calc = checksum(pkt[0:-2]) ck_calc = checksum(pkt[0:-2])
ck_pkt = unpack("<H", pkt[-2:])[0] ck_pkt = unpack("<H", pkt[-2:])[0]
if ck_pkt!=ck_calc: if ck_pkt != ck_calc:
print("Checksum mismatch !") print("Checksum mismatch !")
return None return None
if ver == b'\xab': if ver == b"\xab":
# Drops 2 bytes of garbage and 2 bytes of checksum, first 2 bytes # Drops 2 bytes of garbage and 2 bytes of checksum, first 2 bytes
# of garbage are dropped below # of garbage are dropped below
pkt[1:] = self.encrypt(pkt[1:])[:-4] pkt[1:] = self.encrypt(pkt[1:])[:-4]
sa, da = self._split_addr(pkt[1]) sa, da = self._split_addr(pkt[1])
return BasePacket(sa, da, pkt[2], pkt[3], pkt[4:-2]) # sa, da, cmd, arg, data return BasePacket(sa, da, pkt[2], pkt[3], pkt[4:-2]) # sa, da, cmd, arg, data
def send(self, packet): def send(self, packet):
dev = self._make_addr(packet.src, packet.dst) dev = self._make_addr(packet.src, packet.dst)
if self.keys: if self.keys:
pkt = pack("<B", len(packet.data)+2) pkt = pack("<B", len(packet.data) + 2)
pkt += self.encrypt(pack("<BBB", dev, packet.cmd, packet.arg)+packet.data + (b'\x00' * 4)) pkt += self.encrypt(
pkt = b'\x55\xab' + pkt + pack("<H", checksum(pkt)) pack("<BBB", dev, packet.cmd, packet.arg) + packet.data + (b"\x00" * 4)
)
pkt = b"\x55\xab" + pkt + pack("<H", checksum(pkt))
else: else:
pkt = pack("<BBBB", len(packet.data)+2, dev, packet.cmd, packet.arg)+packet.data pkt = (
pkt = b'\x55\xaa' + pkt + pack("<H", checksum(pkt)) pack("<BBBB", len(packet.data) + 2, dev, packet.cmd, packet.arg)
+ packet.data
)
pkt = b"\x55\xaa" + pkt + pack("<H", checksum(pkt))
self.link.write(pkt) self.link.write(pkt)
def encrypt(self, data): def encrypt(self, data):

View File

@ -11,35 +11,35 @@ from py9b.command.regio import ReadRegs
READ_CHUNK_SIZE = 0x10 READ_CHUNK_SIZE = 0x10
#link = SerialLink(dump=True) # link = SerialLink(dump=True)
#link = TCPLink() # link = TCPLink()
link = BLELink() link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
#link.open(("127.0.0.1", 6000)) # link.open(("127.0.0.1", 6000))
link.open(ports[0][1]) link.open(ports[0][1])
print "Connected" print("Connected")
hfo = open("BmsRegs.bin", "wb") hfo = open("BmsRegs.bin", "wb")
for i in xrange(0x0, 0x100, READ_CHUNK_SIZE): for i in xrange(0x0, 0x100, READ_CHUNK_SIZE):
print ".", print(".")
for retry in xrange(5): for retry in xrange(5):
try: try:
data = tran.execute(ReadRegs(BT.BMS, i>>1, "16s"))[0] data = tran.execute(ReadRegs(BT.BMS, i >> 1, "16s"))[0]
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
else: else:
print "No response !" print("No response !")
break break
hfo.write(data) hfo.write(data)
hfo.close() hfo.close()
link.close() link.close()

View File

@ -10,37 +10,37 @@ from py9b.transport.xiaomi import XiaomiTransport
READ_CHUNK_SIZE = 0x10 READ_CHUNK_SIZE = 0x10
link = SerialLink(dump=True) link = SerialLink(dump=True)
#link = TCPLink() # link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
tran = XiaomiTransport(link) tran = XiaomiTransport(link)
#link.open(("127.0.0.1", 6000)) # link.open(("127.0.0.1", 6000))
link.open(ports[0][1]) link.open(ports[0][1])
print "Connected" print("Connected")
req = PKT(src=BT.HOST, dst=BT.BMS, cmd=0x01, arg=0, data=chr(READ_CHUNK_SIZE)) req = PKT(src=BT.HOST, dst=BT.BMS, cmd=0x01, arg=0, data=chr(READ_CHUNK_SIZE))
hfo = open("BmsRegs.bin", "wb") hfo = open("BmsRegs.bin", "wb")
for i in xrange(0x0, 0x100, READ_CHUNK_SIZE): for i in xrange(0x0, 0x100, READ_CHUNK_SIZE):
print ".", print(".")
req.arg = i>>1 req.arg = i >> 1
for retry in xrange(5): for retry in xrange(5):
tran.send(req) tran.send(req)
try: try:
rsp = tran.recv() rsp = tran.recv()
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
else: else:
print "No response !" print("No response !")
break break
hfo.write(rsp.data) hfo.write(rsp.data)
hfo.close() hfo.close()
link.close() link.close()

View File

@ -13,33 +13,33 @@ SIZE = 0x800
READ_CHUNK_SIZE = 0x10 READ_CHUNK_SIZE = 0x10
link = SerialLink(dump=True) link = SerialLink(dump=True)
#link = TCPLink() # link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
tran = XiaomiTransport(link) tran = XiaomiTransport(link)
#link.open(("127.0.0.1", 6000)) # link.open(("127.0.0.1", 6000))
link.open(ports[0][1]) link.open(ports[0][1])
print "Connected" print("Connected")
hfo = open("BmsEep.bin", "wb") hfo = open("BmsEep.bin", "wb")
for i in xrange(ADDR, ADDR+SIZE, READ_CHUNK_SIZE): for i in xrange(ADDR, ADDR + SIZE, READ_CHUNK_SIZE):
print ".", print(".")
for retry in xrange(5): for retry in xrange(5):
try: try:
data = tran.execute(ReadMem(BT.BMS, i, "16s"))[0] data = tran.execute(ReadMem(BT.BMS, i, "16s"))[0]
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
else: else:
print "No response !" print("No response !")
break break
hfo.write(data) hfo.write(data)
hfo.close() hfo.close()
link.close() link.close()

View File

@ -12,34 +12,34 @@ from py9b.command.regio import ReadRegs
READ_CHUNK_SIZE = 0x10 READ_CHUNK_SIZE = 0x10
link = SerialLink() link = SerialLink()
#link = TCPLink() # link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
#link.open(("127.0.0.1", 6000)) # link.open(("127.0.0.1", 6000))
link.open(ports[0][1]) link.open(ports[0][1])
print "Connected" print("Connected")
hfo = open("EscRegs.bin", "wb") hfo = open("EscRegs.bin", "wb")
for i in xrange(0x0, 0x200, READ_CHUNK_SIZE): for i in xrange(0x0, 0x200, READ_CHUNK_SIZE):
print ".", print(".")
for retry in xrange(5): for retry in xrange(5):
try: try:
data = tran.execute(ReadRegs(BT.ESC, i>>1, "16s"))[0] data = tran.execute(ReadRegs(BT.ESC, i >> 1, "16s"))[0]
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
else: else:
print "No response !" print("No response !")
break break
hfo.write(data) hfo.write(data)
hfo.close() hfo.close()
link.close() link.close()

View File

@ -9,35 +9,35 @@ from py9b.transport.xiaomi import XiaomiTransport
READ_CHUNK_SIZE = 0x40 READ_CHUNK_SIZE = 0x40
#link = SerialLink() # link = SerialLink()
#link = TCPLink() # link = TCPLink()
link = BLELink() link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
tran = XiaomiTransport(link) tran = XiaomiTransport(link)
#link.open(("127.0.0.1", 6000)) # link.open(("127.0.0.1", 6000))
link.open(ports[0][1]) link.open(ports[0][1])
print "Connected" print("Connected")
req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x01, arg=0, data=chr(READ_CHUNK_SIZE)) req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x01, arg=0, data=chr(READ_CHUNK_SIZE))
hfo = open("EscRegs.bin", "wb") hfo = open("EscRegs.bin", "wb")
for i in xrange(0, 0x200, READ_CHUNK_SIZE): for i in xrange(0, 0x200, READ_CHUNK_SIZE):
print ".", print(".")
req.arg = i>>1 req.arg = i >> 1
for retry in xrange(3): for retry in xrange(3):
tran.send(req) tran.send(req)
try: try:
rsp = tran.recv() rsp = tran.recv()
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
hfo.write(rsp.data) hfo.write(rsp.data)
hfo.close() hfo.close()
link.close() link.close()

View File

@ -17,92 +17,115 @@ from py9b.command.regio import ReadRegs
READ_CHUNK_SIZE = 0x10 READ_CHUNK_SIZE = 0x10
def ReadAllRegs(link, tran, dev, hfo): def ReadAllRegs(link, tran, dev, hfo):
size = 0x200 if dev==BT.ESC else 0x100 size = 0x200 if dev == BT.ESC else 0x100
pb = ProgressBar(maxval=size).start() pb = ProgressBar(maxval=size).start()
for i in xrange(0x0, size, READ_CHUNK_SIZE): for i in xrange(0x0, size, READ_CHUNK_SIZE):
pb.update(i) pb.update(i)
for retry in xrange(5): for retry in xrange(5):
try: try:
data = tran.execute(ReadRegs(dev, i>>1, '16s'))[0] data = tran.execute(ReadRegs(dev, i >> 1, "16s"))[0]
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
else: else:
print('No response !') print("No response !")
return False return False
hfo.write(data) hfo.write(data)
pb.finish() pb.finish()
print('OK') print("OK")
return True return True
########################################################################################## ##########################################################################################
parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, parser = argparse.ArgumentParser(
description='Xiaomi/Ninebot register reader', formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='Example 1: %(prog)s esc esc_regs.bin - read ESC regs to esc_regs.bin using default communication parameters' description="Xiaomi/Ninebot register reader",
'\nExample 2: %(prog)s -i tcp -a 127.0.1.10:6000 bms bms_regs.bin - flash BMS regs over TCP-BLE bridge at 127.0.1.10:6000' epilog="Example 1: %(prog)s esc esc_regs.bin - read ESC regs to esc_regs.bin using default communication parameters"
'\nExample 3: %(prog)s -i serial -a COM2 esc esc_regs.bin - read ESC regs via COM2' "\nExample 2: %(prog)s -i tcp -a 127.0.1.10:6000 bms bms_regs.bin - flash BMS regs over TCP-BLE bridge at 127.0.1.10:6000"
'\nExample 4: %(prog)s -i ble -a 12:34:56:78:9A:BC esc esc_regs.bin - read ESC regs via BLE, use specified BLE address') "\nExample 3: %(prog)s -i serial -a COM2 esc esc_regs.bin - read ESC regs via COM2"
"\nExample 4: %(prog)s -i ble -a 12:34:56:78:9A:BC esc esc_regs.bin - read ESC regs via BLE, use specified BLE address",
devices = {'esc' : BT.ESC, 'bms' : BT.BMS, 'extbms' : BT.EXTBMS } )
parser.add_argument('device', help='device to read from', type=str.lower, choices=devices)
parser.add_argument('file', type=argparse.FileType('wb'), help='output file') devices = {"esc": BT.ESC, "bms": BT.BMS, "extbms": BT.EXTBMS}
parser.add_argument(
"device", help="device to read from", type=str.lower, choices=devices
)
parser.add_argument('-i', '--interface', help='communication interface, default: %(default)s', type=str.lower, parser.add_argument("file", type=argparse.FileType("wb"), help="output file")
choices=('ble', 'serial', 'tcp'), default='ble')
parser.add_argument('-a', '--address', help='communication address (ble: BDADDR, serial: port, tcp: host:port), default: first available') parser.add_argument(
"-i",
"--interface",
help="communication interface, default: %(default)s",
type=str.lower,
choices=("ble", "serial", "tcp"),
default="ble",
)
protocols = {'xiaomi' : XiaomiTransport, 'ninebot' : NinebotTransport } parser.add_argument(
parser.add_argument('-p', '--protocol', help='communication protocol, default: %(default)s', type=str.lower, "-a",
choices=protocols, default='xiaomi') "--address",
help="communication address (ble: BDADDR, serial: port, tcp: host:port), default: first available",
)
if len(argv)==1: protocols = {"xiaomi": XiaomiTransport, "ninebot": NinebotTransport}
parser.print_usage() parser.add_argument(
exit() "-p",
"--protocol",
help="communication protocol, default: %(default)s",
type=str.lower,
choices=protocols,
default="xiaomi",
)
if len(argv) == 1:
parser.print_usage()
exit()
args = parser.parse_args() args = parser.parse_args()
if args.device=='extbms' and args.protocol!='ninebot': if args.device == "extbms" and args.protocol != "ninebot":
exit('Only Ninebot supports External BMS !') exit("Only Ninebot supports External BMS !")
dev = devices.get(args.device) dev = devices.get(args.device)
if args.interface=='ble': if args.interface == "ble":
try: try:
from py9b.link.ble import BLELink from py9b.link.ble import BLELink
except: except:
exit('BLE is not supported on your system !') exit("BLE is not supported on your system !")
link = BLELink() link = BLELink()
elif args.interface=='tcp': elif args.interface == "tcp":
from py9b.link.tcp import TCPLink from py9b.link.tcp import TCPLink
link = TCPLink()
elif args.interface=='serial': link = TCPLink()
from py9b.link.serial import SerialLink elif args.interface == "serial":
link = SerialLink() from py9b.link.serial import SerialLink
link = SerialLink()
else: else:
exit('!!! BUG !!! Unknown interface selected: '+args.interface) exit("!!! BUG !!! Unknown interface selected: " + args.interface)
with link: with link:
tran = protocols.get(args.protocol)(link) tran = protocols.get(args.protocol)(link)
if args.address: if args.address:
addr = args.address addr = args.address
else: else:
print('Scanning...') print("Scanning...")
ports = link.scan() ports = link.scan()
if not ports: if not ports:
exit('No interfaces found !') exit("No interfaces found !")
print('Connecting to', ports[0][0]) print("Connecting to", ports[0][0])
addr = ports[0][1] addr = ports[0][1]
link.open(addr) link.open(addr)
print('Connected') print("Connected")
try: try:
ReadAllRegs(link, tran, dev, args.file) ReadAllRegs(link, tran, dev, args.file)
args.file.close() args.file.close()
except Exception as e: except Exception as e:
print('Error:', e) print("Error:", e)
raise raise

View File

@ -1,31 +1,33 @@
#!python2-32 #!python2-32
from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.base import LinkOpenException, LinkTimeoutException
from py9b.link.tcp import TCPLink from py9b.link.tcp import TCPLink
#from py9b.link.ble import BLELink
# from py9b.link.ble import BLELink
from py9b.link.serial import SerialLink from py9b.link.serial import SerialLink
from py9b.transport.base import BaseTransport as BT from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
#from py9b.transport.xiaomi import XiaomiTransport
# from py9b.transport.xiaomi import XiaomiTransport
from py9b.transport.ninebot import NinebotTransport from py9b.transport.ninebot import NinebotTransport
from py9b.command.regio import ReadRegs, WriteRegs from py9b.command.regio import ReadRegs, WriteRegs
#link = SerialLink(dump=True) # link = SerialLink(dump=True)
link = TCPLink() link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
link.open(("127.0.0.20:6000")) link.open(("127.0.0.20:6000"))
# link.open(ports[0][1]) # link.open(ports[0][1])
print "Connected" print("Connected")
print('Reboot...') print("Reboot...")
tran.execute(WriteRegs(BT.ESC, 0x78, '<H', 0x0001)) tran.execute(WriteRegs(BT.ESC, 0x78, "<H", 0x0001))
link.close() link.close()

View File

@ -1,8 +1,9 @@
#!python2-32 #!python2-32
from struct import unpack from struct import unpack
from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.base import LinkOpenException, LinkTimeoutException
#from py9b.link.tcp import TCPLink
#from py9b.link.ble import BLELink # from py9b.link.tcp import TCPLink
# from py9b.link.ble import BLELink
from py9b.link.serial import SerialLink from py9b.link.serial import SerialLink
from py9b.transport.base import BaseTransport as BT from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
@ -10,57 +11,63 @@ from py9b.transport.xiaomi import XiaomiTransport
from py9b.transport.ninebot import NinebotTransport from py9b.transport.ninebot import NinebotTransport
link = SerialLink() link = SerialLink()
#link = TCPLink() # link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
#link.open(("127.0.0.1", 6000)) # link.open(("127.0.0.1", 6000))
link.open(ports[0][1]) link.open(ports[0][1])
print "Connected" print("Connected")
last_esc_64 = "" last_esc_64 = ""
last_esc_65 = "" last_esc_65 = ""
last_ble_64 = "" last_ble_64 = ""
try: try:
while True: while True:
try: try:
rsp = tran.recv() rsp = tran.recv()
if not rsp: if not rsp:
continue continue
if rsp.src==BT.HOST and rsp.dst==BT.ESC and rsp.cmd in (0x64, 0x65): if rsp.src == BT.HOST and rsp.dst == BT.ESC and rsp.cmd in (0x64, 0x65):
if len(rsp.data)==5: if len(rsp.data) == 5:
if rsp.data==last_esc_65: if rsp.data == last_esc_65:
continue continue
ll, throttle, brake, u2, u3 = unpack("<BBBBB", rsp.data) ll, throttle, brake, u2, u3 = unpack("<BBBBB", rsp.data)
print "BLE->ESC: TH: %02X, BR: %02X, %02X %02X" % (throttle, brake, u2, u3) print(
last_esc_65 = rsp.data "BLE->ESC: TH: %02X, BR: %02X, %02X %02X"
continue % (throttle, brake, u2, u3)
elif len(rsp.data)==7: )
if rsp.data==last_esc_64: last_esc_65 = rsp.data
continue continue
ll, throttle, brake, u2, u3, ver = unpack("<BBBBBH", rsp.data) elif len(rsp.data) == 7:
print "BLE->ESC: TH: %02X, BR: %02X, %02X %02X, VER: %04X" % (throttle, brake, u2, u3, ver) if rsp.data == last_esc_64:
last_esc_64 = rsp.data continue
continue ll, throttle, brake, u2, u3, ver = unpack("<BBBBBH", rsp.data)
elif rsp.src==BT.HOST and rsp.dst==BT.BLE and rsp.cmd==0x64: print(
if len(rsp.data)==4: "BLE->ESC: TH: %02X, BR: %02X, %02X %02X, VER: %04X"
if rsp.data==last_ble_64: % (throttle, brake, u2, u3, ver)
continue )
u0, u1, u2, u3 = unpack("<BBBB", rsp.data) last_esc_64 = rsp.data
print "ESC->BLE: %02X %02X %02X %02X" % (u0, u1, u2, u3) continue
last_ble_64 = rsp.data elif rsp.src == BT.HOST and rsp.dst == BT.BLE and rsp.cmd == 0x64:
continue if len(rsp.data) == 4:
print rsp if rsp.data == last_ble_64:
except LinkTimeoutException: continue
pass u0, u1, u2, u3 = unpack("<BBBB", rsp.data)
except KeyboardInterrupt: print("ESC->BLE: %02X %02X %02X %02X" % (u0, u1, u2, u3))
pass last_ble_64 = rsp.data
continue
link.close() print(rsp)
except LinkTimeoutException:
pass
except KeyboardInterrupt:
pass
link.close()

View File

@ -4,26 +4,26 @@ from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
from py9b.transport.xiaomi import XiaomiTransport from py9b.transport.xiaomi import XiaomiTransport
#link = SerialLink() # link = SerialLink()
with TCPLink() as link: with TCPLink() as link:
ports = link.scan() ports = link.scan()
print ports print(ports)
tran = XiaomiTransport(link)
#link.open(("127.0.0.1", 6000)) tran = XiaomiTransport(link)
link.open(ports[0][1])
#req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x01, arg=0x10, data="\x10") # link.open(("127.0.0.1", 6000))
req = PKT(src=BT.HOST, dst=BT.BMS, cmd=0x01, arg=0x10, data="\x10") link.open(ports[0][1])
while raw_input("Press ENTER to send...")!="q": # req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x01, arg=0x10, data="\x10")
tran.send(req) req = PKT(src=BT.HOST, dst=BT.BMS, cmd=0x01, arg=0x10, data="\x10")
try:
rsp = tran.recv()
except LinkTimeoutException:
print "No response"
continue
print rsp
link.close() while raw_input("Press ENTER to send...") != "q":
tran.send(req)
try:
rsp = tran.recv()
except LinkTimeoutException:
print("No response")
continue
print(rsp)
link.close()

View File

@ -1,31 +1,33 @@
#!python2-32 #!python2-32
from py9b.link.base import LinkOpenException, LinkTimeoutException from py9b.link.base import LinkOpenException, LinkTimeoutException
from py9b.link.tcp import TCPLink from py9b.link.tcp import TCPLink
#from py9b.link.ble import BLELink
# from py9b.link.ble import BLELink
from py9b.link.serial import SerialLink from py9b.link.serial import SerialLink
from py9b.transport.base import BaseTransport as BT from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
#from py9b.transport.xiaomi import XiaomiTransport
# from py9b.transport.xiaomi import XiaomiTransport
from py9b.transport.ninebot import NinebotTransport from py9b.transport.ninebot import NinebotTransport
from py9b.command.regio import ReadRegs, WriteRegs from py9b.command.regio import ReadRegs, WriteRegs
#link = SerialLink(dump=True) # link = SerialLink(dump=True)
link = TCPLink() link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
link.open(("127.0.0.20:6000")) link.open(("127.0.0.20:6000"))
# link.open(ports[0][1]) # link.open(ports[0][1])
print "Connected" print("Connected")
print('Unlocking...') print("Unlocking...")
tran.execute(WriteRegs(BT.ESC, 0x71, '<H', 0x0001)) tran.execute(WriteRegs(BT.ESC, 0x71, "<H", 0x0001))
link.close() link.close()

View File

@ -6,27 +6,27 @@ from py9b.transport.base import BaseTransport as BT
from py9b.transport.packet import BasePacket as PKT from py9b.transport.packet import BasePacket as PKT
from py9b.transport.xiaomi import XiaomiTransport from py9b.transport.xiaomi import XiaomiTransport
#link = SerialLink() # link = SerialLink()
link = TCPLink() link = TCPLink()
#link = BLELink() # link = BLELink()
with link: with link:
print "Scanning..." print("Scanning...")
ports = link.scan() ports = link.scan()
print ports print(ports)
tran = XiaomiTransport(link) tran = XiaomiTransport(link)
link.open(("127.0.0.1", 6000)) link.open(("127.0.0.1", 6000))
#link.open(ports[0][1]) # link.open(ports[0][1])
print "Connected" print("Connected")
req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x02, arg=0x41, data="\xCE\xAB\x00\x00") req = PKT(src=BT.HOST, dst=BT.ESC, cmd=0x02, arg=0x41, data="\xCE\xAB\x00\x00")
tran.send(req) tran.send(req)
try: try:
rsp = tran.recv() rsp = tran.recv()
finally: finally:
link.close() link.close()
print rsp print(rsp)

View File

@ -14,75 +14,75 @@ from py9b.command.mfg import WriteSN
from time import sleep from time import sleep
#new_sn = "16133/00101234" # new_sn = "16133/00101234"
#new_sn = "N2GTR1826C1234" # new_sn = "N2GTR1826C1234"
def CalcSnAuth(oldsn, newsn, uid3): def CalcSnAuth(oldsn, newsn, uid3):
s = 0 s = 0
for i in xrange(0x0E): for i in xrange(0x0E):
s += ord(oldsn[i]) s += ord(oldsn[i])
s *= ord(newsn[i]) s *= ord(newsn[i])
s += uid3+(uid3<<4) s += uid3 + (uid3 << 4)
s &= 0xFFFFFFFF s &= 0xFFFFFFFF
if (s & 0x80000000)!=0: if (s & 0x80000000) != 0:
s = 0x100000000-s s = 0x100000000 - s
return s % 1000000 return s % 1000000
#link = SerialLink(dump=True) # link = SerialLink(dump=True)
#link = TCPLink() # link = TCPLink()
link = BLELink(dump=True) link = BLELink(dump=True)
with link: with link:
print("Scanning...") print("Scanning...")
ports = link.scan() ports = link.scan()
print(ports) print(ports)
#tran = XiaomiTransport(link) # tran = XiaomiTransport(link)
tran = NinebotTransport(link) tran = NinebotTransport(link)
#link.open(("192.168.1.45", 6000)) # link.open(("192.168.1.45", 6000))
link.open(ports[0][1]) link.open(ports[0][1])
print("Connected") print("Connected")
print("Pinging...") print("Pinging...")
for retry in xrange(20): for retry in xrange(20):
print(".", end="") print(".", end="")
try: try:
old_sn = tran.execute(ReadRegs(BT.ESC, 0x10, "14s"))[0] old_sn = tran.execute(ReadRegs(BT.ESC, 0x10, "14s"))[0]
except LinkTimeoutException: except LinkTimeoutException:
continue continue
break break
else: else:
exit("Timed out !") exit("Timed out !")
print("") print("")
# lock
#lock # tran.execute(WriteRegs(BT.ESC, 0x70, "<H", 0x01))
#tran.execute(WriteRegs(BT.ESC, 0x70, "<H", 0x01))
old_sn = tran.execute(ReadRegs(BT.ESC, 0x10, "14s"))[0] old_sn = tran.execute(ReadRegs(BT.ESC, 0x10, "14s"))[0]
print("Old S/N:", old_sn) print("Old S/N:", old_sn)
uid3 = tran.execute(ReadRegs(BT.ESC, 0xDE, "<L"))[0] uid3 = tran.execute(ReadRegs(BT.ESC, 0xDE, "<L"))[0]
print("UID3: %08X" % (uid3)) print("UID3: %08X" % (uid3))
auth = CalcSnAuth(old_sn, new_sn, uid3) auth = CalcSnAuth(old_sn, new_sn, uid3)
#auth = 0 # auth = 0
print("Auth: %08X" % (auth)) print("Auth: %08X" % (auth))
try: try:
tran.execute(WriteSN(BT.ESC, new_sn, auth)) tran.execute(WriteSN(BT.ESC, new_sn, auth))
print("OK") print("OK")
except LinkTimeoutException: except LinkTimeoutException:
print("Timeout !") print("Timeout !")
# save config and restart # save config and restart
tran.execute(WriteRegs(BT.ESC, 0x78, "<H", 0x01)) tran.execute(WriteRegs(BT.ESC, 0x78, "<H", 0x01))
sleep(3) sleep(3)
old_sn = tran.execute(ReadRegs(BT.ESC, 0x10, "14s"))[0] old_sn = tran.execute(ReadRegs(BT.ESC, 0x10, "14s"))[0]
print("Current S/N:", old_sn) print("Current S/N:", old_sn)
link.close() link.close()