Skip to content

Commit 6a25791

Browse files
committed
Created python package and implemented industry best practices.
Supports python 2 and 3 (to the best of my testing ability at the time)
1 parent 0e46ca4 commit 6a25791

12 files changed

+180
-101
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
.*.swp
33
*.o
44
a.out
5-
5+
*~
6+
.#*

board/.gitignore

-1
This file was deleted.

lib/__init__.py

Whitespace-only changes.

lib/panda.py panda/__init__.py

+53-50
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# python library to interface with panda
2+
from __future__ import print_function
3+
import binascii
24
import struct
35
import hashlib
46
import socket
57
import usb1
6-
from usb1 import USBErrorIO, USBErrorOverflow
78

8-
try:
9-
from hexdump import hexdump
10-
except:
11-
pass
9+
__version__ = '0.0.1'
10+
11+
class PandaHashMismatchException(Exception):
12+
def __init__(self, hash_, expected_hash):
13+
super(PandaHashMismatchException, self).__init__(
14+
"Hash '%s' did not match the expected hash '%s'"%\
15+
(binascii.hexlify(hash_), binascii.hexlify(expected_hash)))
1216

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

3438
def can_recv(self):
3539
ret = []
36-
while 1:
40+
while True:
3741
try:
3842
dat, addr = self.sock.recvfrom(0x200*0x10)
3943
if addr == (self.ip, self.port):
@@ -61,7 +65,8 @@ def controlRead(self, request_type, request, value, index, length, timeout=0):
6165
return self.__recv()
6266

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

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

7580
class Panda(object):
81+
REQUEST_TYPE = usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE
82+
7683
def __init__(self, serial=None, claim=True):
7784
if serial == "WIFI":
7885
self.handle = WifiHandle()
79-
print "opening WIFI device"
86+
print("opening WIFI device")
8087
else:
8188
context = usb1.USBContext()
8289

8390
self.handle = None
8491
for device in context.getDeviceList(skip_on_error=True):
8592
if device.getVendorID() == 0xbbaa and device.getProductID() == 0xddcc:
8693
if serial is None or device.getSerialNumber() == serial:
87-
print "opening device", device.getSerialNumber()
94+
print("opening device", device.getSerialNumber())
8895
self.handle = device.open()
8996
if claim:
9097
self.handle.claimInterface(0)
@@ -109,7 +116,7 @@ def list():
109116
# ******************* health *******************
110117

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

122129
def enter_bootloader(self):
123130
try:
124-
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd1, 0, 0, '')
125-
except Exception:
131+
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xd1, 0, 0, b'')
132+
except Exception as e:
133+
print(e)
126134
pass
127135

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

133143
def get_secret(self):
134-
dat = str(self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd0, 1, 0, 0x10))
135-
return dat.encode("hex")
144+
return self.handle.controlRead(Panda.REQUEST_TYPE, 0xd0, 1, 0, 0x10)
136145

137146
# ******************* configuration *******************
138147

139148
def set_controls_allowed(self, on):
140-
if on:
141-
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdc, 0x1337, 0, '')
142-
else:
143-
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdc, 0, 0, '')
149+
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xdc, (0x1337 if on else 0), 0, b'')
144150

145151
def set_gmlan(self, on, bus=2):
146-
if on:
147-
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdb, 1, bus, '')
148-
else:
149-
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xdb, 0, bus, '')
152+
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xdb, 1, bus, b'')
150153

151154
def set_uart_baud(self, uart, rate):
152-
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe1, uart, rate, '')
155+
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xe1, uart, rate, b'')
153156

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

158161
def set_uart_callback(self, uart, install):
159-
self.handle.controlWrite(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe3, uart, int(install), '')
162+
self.handle.controlWrite(Panda.REQUEST_TYPE, 0xe3, uart, int(install), b'')
160163

161164
# ******************* can *******************
162165

163166
def can_send_many(self, arr):
164167
snds = []
168+
transmit = 1
169+
extended = 4
165170
for addr, _, dat, bus in arr:
166-
transmit = 1
167-
extended = 4
168171
if addr >= 0x800:
169172
rir = (addr << 3) | transmit | extended
170173
else:
171174
rir = (addr << 21) | transmit
172175
snd = struct.pack("II", rir, len(dat) | (bus << 4)) + dat
173-
snd = snd.ljust(0x10, '\x00')
176+
snd = snd.ljust(0x10, b'\x00')
174177
snds.append(snd)
175178

176-
while 1:
179+
while True:
177180
try:
178-
self.handle.bulkWrite(3, ''.join(snds))
181+
print("DAT: %s"%b''.join(snds).__repr__())
182+
self.handle.bulkWrite(3, b''.join(snds))
179183
break
180-
except (USBErrorIO, USBErrorOverflow):
181-
print "CAN: BAD SEND MANY, RETRYING"
184+
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
185+
print("CAN: BAD SEND MANY, RETRYING")
182186

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

186190
def can_recv(self):
187-
dat = ""
188-
while 1:
191+
dat = bytearray()
192+
while True:
189193
try:
190194
dat = self.handle.bulkRead(1, 0x10*256)
191195
break
192-
except (USBErrorIO, USBErrorOverflow):
193-
print "CAN: BAD RECV, RETRYING"
196+
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
197+
print("CAN: BAD RECV, RETRYING")
194198
return parse_can_buffer(dat)
195199

196200
# ******************* serial *******************
197201

198202
def serial_read(self, port_number):
199-
return self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe0, port_number, 0, 0x40)
203+
return self.handle.controlRead(Panda.REQUEST_TYPE, 0xe0, port_number, 0, 0x40)
200204

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

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

210214
def kline_drain(self, bus=2):
211215
# drain buffer
212-
bret = ""
213-
while 1:
214-
ret = self.handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xe0, bus, 0, 0x40)
216+
bret = bytearray()
217+
while True:
218+
ret = self.handle.controlRead(Panda.REQUEST_TYPE, 0xe0, bus, 0, 0x40)
215219
if len(ret) == 0:
216220
break
217-
bret += str(ret)
221+
bret += ret
218222
return bret
219223

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

226230
def kline_send(self, x, bus=2, checksum=True):
@@ -238,13 +242,12 @@ def get_checksum(dat):
238242
self.handle.bulkWrite(2, chr(bus)+ts)
239243
echo = self.kline_ll_recv(len(ts), bus=bus)
240244
if echo != ts:
241-
print "**** ECHO ERROR %d ****" % i
242-
print echo.encode("hex")
243-
print ts.encode("hex")
245+
print("**** ECHO ERROR %d ****" % i)
246+
print(binascii.hexlify(echo))
247+
print(binascii.hexlify(ts))
244248
assert echo == ts
245249

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

setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[bdist_wheel]
2+
universal=1

setup.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#-*- coding: utf-8 -*-
2+
3+
"""
4+
Panda CAN Controller Dongle
5+
~~~~~
6+
7+
Setup
8+
`````
9+
10+
$ pip install . # or python setup.py install
11+
"""
12+
13+
import codecs
14+
import os
15+
import re
16+
from setuptools import setup, Extension
17+
18+
here = os.path.abspath(os.path.dirname(__file__))
19+
20+
def read(*parts):
21+
"""Taken from pypa pip setup.py:
22+
intentionally *not* adding an encoding option to open, See:
23+
https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690
24+
"""
25+
return codecs.open(os.path.join(here, *parts), 'r').read()
26+
27+
28+
def find_version(*file_paths):
29+
version_file = read(*file_paths)
30+
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
31+
version_file, re.M)
32+
if version_match:
33+
return version_match.group(1)
34+
raise RuntimeError("Unable to find version string.")
35+
36+
setup(
37+
name='panda',
38+
version=find_version("panda", "__init__.py"),
39+
url='https://github.com/commaai/panda',
40+
author='Comma.ai',
41+
author_email='',
42+
packages=[
43+
'panda',
44+
],
45+
platforms='any',
46+
license='MIT',
47+
install_requires=[
48+
'libusb1 >= 1.6.4',
49+
'hexdump >= 3.3',
50+
'pycrypto >= 2.6.1',
51+
'tqdm >= 4.14.0',
52+
],
53+
ext_modules = [],
54+
description="Code powering the comma.ai panda",
55+
long_description=open(os.path.join(os.path.dirname(__file__),
56+
'README.md')).read(),
57+
classifiers=[
58+
'Development Status :: 2 - Pre-Alpha',
59+
"Natural Language :: English",
60+
"Programming Language :: Python :: 2",
61+
"Programming Language :: Python :: 3",
62+
"Topic :: System :: Hardware",
63+
],
64+
)

tests/__init__.py

Whitespace-only changes.

tests/can_printer.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
#!/usr/bin/env python
2+
from __future__ import print_function
23
import os
3-
import struct
4+
import sys
45
import time
56
from collections import defaultdict
6-
from panda.lib.panda import Panda
7+
8+
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
9+
from panda import Panda
710

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

3336
if __name__ == "__main__":
3437
can_printer()
35-

tests/debug_console.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/usr/bin/env python
2+
from __future__ import print_function
23
import os
34
import sys
4-
import usb1
55
import time
66
import select
7-
from panda.lib.panda import Panda
7+
8+
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), ".."))
9+
from panda import Panda
810

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

1921
pandas = map(lambda x: Panda(x, False), serials)
20-
while 1:
22+
while True:
2123
for i, panda in enumerate(pandas):
22-
while 1:
24+
while True:
2325
ret = panda.serial_read(port_number)
2426
if len(ret) > 0:
25-
sys.stdout.write(setcolor[i] + ret + unsetcolor)
27+
sys.stdout.write(setcolor[i] + ret.decode('utf8') + unsetcolor)
2628
sys.stdout.flush()
2729
else:
2830
break
29-
if select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []):
31+
if select.select([sys.stdin], [], [], 0)[0][0] == sys.stdin:
3032
ln = sys.stdin.readline()
3133
panda.serial_write(port_number, ln)
3234
time.sleep(0.01)
33-

0 commit comments

Comments
 (0)