Skip to content

Commit

Permalink
Created python package and implemented industry best practices.
Browse files Browse the repository at this point in the history
Supports python 2 and 3 (to the best of my testing ability at the time)
  • Loading branch information
diamondman committed Jun 14, 2017
1 parent 0e46ca4 commit 6a25791
Show file tree
Hide file tree
Showing 12 changed files with 180 additions and 101 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.*.swp
*.o
a.out

*~
.#*
1 change: 0 additions & 1 deletion board/.gitignore

This file was deleted.

Empty file removed lib/__init__.py
Empty file.
103 changes: 53 additions & 50 deletions lib/panda.py → panda/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# python library to interface with panda
from __future__ import print_function
import binascii
import struct
import hashlib
import socket
import usb1
from usb1 import USBErrorIO, USBErrorOverflow

try:
from hexdump import hexdump
except:
pass
__version__ = '0.0.1'

class PandaHashMismatchException(Exception):
def __init__(self, hash_, expected_hash):
super(PandaHashMismatchException, self).__init__(
"Hash '%s' did not match the expected hash '%s'"%\
(binascii.hexlify(hash_), binascii.hexlify(expected_hash)))

def parse_can_buffer(dat):
ret = []
Expand All @@ -33,7 +37,7 @@ def __init__(self, ip="192.168.0.10", port=1338):

def can_recv(self):
ret = []
while 1:
while True:
try:
dat, addr = self.sock.recvfrom(0x200*0x10)
if addr == (self.ip, self.port):
Expand Down Expand Up @@ -61,7 +65,8 @@ def controlRead(self, request_type, request, value, index, length, timeout=0):
return self.__recv()

def bulkWrite(self, endpoint, data, timeout=0):
assert len(data) <= 0x10
if len(data) > 0x10:
raise ValueError("Data must not be longer than 0x10")
self.sock.send(struct.pack("HH", endpoint, len(data))+data)
self.__recv() # to /dev/null

Expand All @@ -73,18 +78,20 @@ def close(self):
self.sock.close()

class Panda(object):
REQUEST_TYPE = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE

def __init__(self, serial=None, claim=True):
if serial == "WIFI":
self.handle = WifiHandle()
print "opening WIFI device"
print("opening WIFI device")
else:
context = usb1.USBContext()

self.handle = None
for device in context.getDeviceList(skip_on_error=True):
if device.getVendorID() == 0xbbaa and device.getProductID() == 0xddcc:
if serial is None or device.getSerialNumber() == serial:
print "opening device", device.getSerialNumber()
print("opening device", device.getSerialNumber())
self.handle = device.open()
if claim:
self.handle.claimInterface(0)
Expand All @@ -109,7 +116,7 @@ def list():
# ******************* health *******************

def health(self):
dat = self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd2, 0, 0, 13)
dat = self.handle.controlRead(Panda.REQUEST_TYPE, 0xd2, 0, 0, 13)
a = struct.unpack("IIBBBBB", dat)
return {"voltage": a[0], "current": a[1],
"started": a[2], "controls_allowed": a[3],
Expand All @@ -121,82 +128,79 @@ def health(self):

def enter_bootloader(self):
try:
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd1, 0, 0, '')
except Exception:
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xd1, 0, 0, b'')
except Exception as e:
print(e)
pass

def get_serial(self):
dat = str(self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd0, 0, 0, 0x20))
assert dat[0x1c:] == hashlib.sha1(dat[0:0x1c]).digest()[0:4]
dat = self.handle.controlRead(Panda.REQUEST_TYPE, 0xd0, 0, 0, 0x20)
hashsig, calc_hash = dat[0x1c:], hashlib.sha1(dat[0:0x1c]).digest()[0:4]
if hashsig != calc_hash:
raise PandaHashMismatchException(calc_hash, hashsig)
return [dat[0:0x10], dat[0x10:0x10+10]]

def get_secret(self):
dat = str(self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd0, 1, 0, 0x10))
return dat.encode("hex")
return self.handle.controlRead(Panda.REQUEST_TYPE, 0xd0, 1, 0, 0x10)

# ******************* configuration *******************

def set_controls_allowed(self, on):
if on:
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdc, 0x1337, 0, '')
else:
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdc, 0, 0, '')
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xdc, (0x1337 if on else 0), 0, b'')

def set_gmlan(self, on, bus=2):
if on:
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdb, 1, bus, '')
else:
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdb, 0, bus, '')
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xdb, 1, bus, b'')

def set_uart_baud(self, uart, rate):
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe1, uart, rate, '')
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xe1, uart, rate, b'')

def set_uart_parity(self, uart, parity):
# parity, 0=off, 1=even, 2=odd
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe2, uart, parity, '')
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xe2, uart, parity, b'')

def set_uart_callback(self, uart, install):
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe3, uart, int(install), '')
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xe3, uart, int(install), b'')

# ******************* can *******************

def can_send_many(self, arr):
snds = []
transmit = 1
extended = 4
for addr, _, dat, bus in arr:
transmit = 1
extended = 4
if addr >= 0x800:
rir = (addr << 3) | transmit | extended
else:
rir = (addr << 21) | transmit
snd = struct.pack("II", rir, len(dat) | (bus << 4)) + dat
snd = snd.ljust(0x10, '\x00')
snd = snd.ljust(0x10, b'\x00')
snds.append(snd)

while 1:
while True:
try:
self.handle.bulkWrite(3, ''.join(snds))
print("DAT: %s"%b''.join(snds).__repr__())
self.handle.bulkWrite(3, b''.join(snds))
break
except (USBErrorIO, USBErrorOverflow):
print "CAN: BAD SEND MANY, RETRYING"
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
print("CAN: BAD SEND MANY, RETRYING")

def can_send(self, addr, dat, bus):
self.can_send_many([[addr, None, dat, bus]])

def can_recv(self):
dat = ""
while 1:
dat = bytearray()
while True:
try:
dat = self.handle.bulkRead(1, 0x10*256)
break
except (USBErrorIO, USBErrorOverflow):
print "CAN: BAD RECV, RETRYING"
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
print("CAN: BAD RECV, RETRYING")
return parse_can_buffer(dat)

# ******************* serial *******************

def serial_read(self, port_number):
return self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe0, port_number, 0, 0x40)
return self.handle.controlRead(Panda.REQUEST_TYPE, 0xe0, port_number, 0, 0x40)

def serial_write(self, port_number, ln):
return self.handle.bulkWrite(2, chr(port_number) + ln)
Expand All @@ -205,22 +209,22 @@ def serial_write(self, port_number, ln):

# pulse low for wakeup
def kline_wakeup(self):
ret = self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xf0, 0, 0, "")
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xf0, 0, 0, b'')

def kline_drain(self, bus=2):
# drain buffer
bret = ""
while 1:
ret = self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe0, bus, 0, 0x40)
bret = bytearray()
while True:
ret = self.handle.controlRead(Panda.REQUEST_TYPE, 0xe0, bus, 0, 0x40)
if len(ret) == 0:
break
bret += str(ret)
bret += ret
return bret

def kline_ll_recv(self, cnt, bus=2):
echo = ""
echo = bytearray()
while len(echo) != cnt:
echo += str(self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe0, bus, 0, cnt-len(echo)))
echo += self.handle.controlRead(Panda.REQUEST_TYPE, 0xe0, bus, 0, cnt-len(echo))
return echo

def kline_send(self, x, bus=2, checksum=True):
Expand All @@ -238,13 +242,12 @@ def get_checksum(dat):
self.handle.bulkWrite(2, chr(bus)+ts)
echo = self.kline_ll_recv(len(ts), bus=bus)
if echo != ts:
print "**** ECHO ERROR %d ****" % i
print echo.encode("hex")
print ts.encode("hex")
print("**** ECHO ERROR %d ****" % i)
print(binascii.hexlify(echo))
print(binascii.hexlify(ts))
assert echo == ts

def kline_recv(self, bus=2):
msg = self.kline_ll_recv(2, bus=bus)
msg += self.kline_ll_recv(ord(msg[1])-2, bus=bus)
return msg

2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[bdist_wheel]
universal=1
64 changes: 64 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#-*- coding: utf-8 -*-

"""
Panda CAN Controller Dongle
~~~~~
Setup
`````
$ pip install . # or python setup.py install
"""

import codecs
import os
import re
from setuptools import setup, Extension

here = os.path.abspath(os.path.dirname(__file__))

def read(*parts):
"""Taken from pypa pip setup.py:
intentionally *not* adding an encoding option to open, See:
https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690
"""
return codecs.open(os.path.join(here, *parts), 'r').read()


def find_version(*file_paths):
version_file = read(*file_paths)
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")

setup(
name='panda',
version=find_version("panda", "__init__.py"),
url='https://github.com/commaai/panda',
author='Comma.ai',
author_email='',
packages=[
'panda',
],
platforms='any',
license='MIT',
install_requires=[
'libusb1 >= 1.6.4',
'hexdump >= 3.3',
'pycrypto >= 2.6.1',
'tqdm >= 4.14.0',
],
ext_modules = [],
description="Code powering the comma.ai panda",
long_description=open(os.path.join(os.path.dirname(__file__),
'README.md')).read(),
classifiers=[
'Development Status :: 2 - Pre-Alpha',
"Natural Language :: English",
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 3",
"Topic :: System :: Hardware",
],
)
Empty file removed tests/__init__.py
Empty file.
12 changes: 7 additions & 5 deletions tests/can_printer.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
#!/usr/bin/env python
from __future__ import print_function
import os
import struct
import sys
import time
from collections import defaultdict
from panda.lib.panda import Panda

sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
from panda import Panda

# fake
def sec_since_boot():
Expand All @@ -16,7 +19,7 @@ def can_printer():
lp = sec_since_boot()
msgs = defaultdict(list)
canbus = int(os.getenv("CAN", 0))
while 1:
while True:
can_recv = p.can_recv()
for address, _, dat, src in can_recv:
if src == canbus:
Expand All @@ -27,9 +30,8 @@ def can_printer():
dd += "%5.2f\n" % (sec_since_boot() - start)
for k,v in sorted(zip(msgs.keys(), map(lambda x: x[-1].encode("hex"), msgs.values()))):
dd += "%s(%6d) %s\n" % ("%04X(%4d)" % (k,k),len(msgs[k]), v)
print dd
print(dd)
lp = sec_since_boot()

if __name__ == "__main__":
can_printer()

15 changes: 8 additions & 7 deletions tests/debug_console.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#!/usr/bin/env python
from __future__ import print_function
import os
import sys
import usb1
import time
import select
from panda.lib.panda import Panda

sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
from panda import Panda

setcolor = ["\033[1;32;40m", "\033[1;31;40m"]
unsetcolor = "\033[00m"
Expand All @@ -17,17 +19,16 @@
serials = filter(lambda x: x==os.getenv("SERIAL"), serials)

pandas = map(lambda x: Panda(x, False), serials)
while 1:
while True:
for i, panda in enumerate(pandas):
while 1:
while True:
ret = panda.serial_read(port_number)
if len(ret) > 0:
sys.stdout.write(setcolor[i] + ret + unsetcolor)
sys.stdout.write(setcolor[i] + ret.decode('utf8') + unsetcolor)
sys.stdout.flush()
else:
break
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
if select.select([sys.stdin], [], [], 0)[0][0] == sys.stdin:
ln = sys.stdin.readline()
panda.serial_write(port_number, ln)
time.sleep(0.01)

Loading

0 comments on commit 6a25791

Please sign in to comment.