2017-04-07 09:11:36 +08:00
|
|
|
# python library to interface with panda
|
2019-10-26 07:22:42 +08:00
|
|
|
import datetime
|
2017-04-07 09:11:36 +08:00
|
|
|
import struct
|
2017-04-29 07:56:40 +08:00
|
|
|
import hashlib
|
2017-04-18 16:12:04 +08:00
|
|
|
import socket
|
2017-04-07 09:11:36 +08:00
|
|
|
import usb1
|
2017-07-18 06:29:31 +08:00
|
|
|
import os
|
|
|
|
import time
|
2017-08-13 09:42:24 +08:00
|
|
|
import traceback
|
2020-05-03 03:18:26 +08:00
|
|
|
import sys
|
2020-05-30 02:52:03 +08:00
|
|
|
from .dfu import PandaDFU # pylint: disable=import-error
|
|
|
|
from .flash_release import flash_release # noqa pylint: disable=import-error
|
|
|
|
from .update import ensure_st_up_to_date # noqa pylint: disable=import-error
|
|
|
|
from .serial import PandaSerial # noqa pylint: disable=import-error
|
|
|
|
from .isotp import isotp_send, isotp_recv # pylint: disable=import-error
|
|
|
|
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2019-07-24 06:07:06 +08:00
|
|
|
__version__ = '0.0.9'
|
2017-06-14 09:50:47 +08:00
|
|
|
|
2017-07-25 06:16:22 +08:00
|
|
|
BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")
|
2021-03-24 21:44:32 +08:00
|
|
|
DEFAULT_FW_FN = os.path.join(BASEDIR, "board", "obj", "panda.bin.signed")
|
2017-07-25 06:16:22 +08:00
|
|
|
|
2018-01-24 10:40:53 +08:00
|
|
|
DEBUG = os.getenv("PANDADEBUG") is not None
|
|
|
|
|
2017-05-17 08:01:29 +08:00
|
|
|
def parse_can_buffer(dat):
|
|
|
|
ret = []
|
|
|
|
for j in range(0, len(dat), 0x10):
|
2020-06-01 16:49:26 +08:00
|
|
|
ddat = dat[j:j + 0x10]
|
2017-05-17 08:01:29 +08:00
|
|
|
f1, f2 = struct.unpack("II", ddat[0:8])
|
|
|
|
extended = 4
|
|
|
|
if f1 & extended:
|
|
|
|
address = f1 >> 3
|
|
|
|
else:
|
|
|
|
address = f1 >> 21
|
2020-06-01 16:49:26 +08:00
|
|
|
dddat = ddat[8:8 + (f2 & 0xF)]
|
2018-01-24 10:40:53 +08:00
|
|
|
if DEBUG:
|
2020-06-19 02:17:00 +08:00
|
|
|
print(f" R 0x{address:x}: 0x{dddat.hex()}")
|
2020-06-01 16:49:26 +08:00
|
|
|
ret.append((address, f2 >> 16, dddat, (f2 >> 4) & 0xFF))
|
2017-05-17 08:01:29 +08:00
|
|
|
return ret
|
|
|
|
|
|
|
|
class PandaWifiStreaming(object):
|
|
|
|
def __init__(self, ip="192.168.0.10", port=1338):
|
|
|
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
self.sock.setblocking(0)
|
|
|
|
self.ip = ip
|
|
|
|
self.port = port
|
2017-08-13 09:01:56 +08:00
|
|
|
self.kick()
|
|
|
|
|
|
|
|
def kick(self):
|
|
|
|
# must be called at least every 5 seconds
|
|
|
|
self.sock.sendto("hello", (self.ip, self.port))
|
2017-05-17 08:01:29 +08:00
|
|
|
|
|
|
|
def can_recv(self):
|
|
|
|
ret = []
|
2017-06-14 09:50:47 +08:00
|
|
|
while True:
|
2017-05-17 08:01:29 +08:00
|
|
|
try:
|
2020-06-01 16:49:26 +08:00
|
|
|
dat, addr = self.sock.recvfrom(0x200 * 0x10)
|
2017-05-17 08:01:29 +08:00
|
|
|
if addr == (self.ip, self.port):
|
|
|
|
ret += parse_can_buffer(dat)
|
2017-08-13 09:42:24 +08:00
|
|
|
except socket.error as e:
|
2017-08-22 04:57:05 +08:00
|
|
|
if e.errno != 35 and e.errno != 11:
|
2017-08-13 09:42:24 +08:00
|
|
|
traceback.print_exc()
|
2017-05-17 08:01:29 +08:00
|
|
|
break
|
|
|
|
return ret
|
|
|
|
|
2017-04-18 16:12:04 +08:00
|
|
|
# stupid tunneling of USB over wifi and SPI
|
|
|
|
class WifiHandle(object):
|
|
|
|
def __init__(self, ip="192.168.0.10", port=1337):
|
|
|
|
self.sock = socket.create_connection((ip, port))
|
|
|
|
|
|
|
|
def __recv(self):
|
|
|
|
ret = self.sock.recv(0x44)
|
2017-04-18 22:34:56 +08:00
|
|
|
length = struct.unpack("I", ret[0:4])[0]
|
2020-06-01 16:49:26 +08:00
|
|
|
return ret[4:4 + length]
|
2017-04-18 16:12:04 +08:00
|
|
|
|
|
|
|
def controlWrite(self, request_type, request, value, index, data, timeout=0):
|
2017-07-13 02:25:10 +08:00
|
|
|
# ignore data in reply, panda doesn't use it
|
|
|
|
return self.controlRead(request_type, request, value, index, 0, timeout)
|
2017-04-18 16:12:04 +08:00
|
|
|
|
|
|
|
def controlRead(self, request_type, request, value, index, length, timeout=0):
|
2017-04-18 22:34:56 +08:00
|
|
|
self.sock.send(struct.pack("HHBBHHH", 0, 0, request_type, request, value, index, length))
|
2017-04-18 16:12:04 +08:00
|
|
|
return self.__recv()
|
|
|
|
|
|
|
|
def bulkWrite(self, endpoint, data, timeout=0):
|
2017-06-14 09:50:47 +08:00
|
|
|
if len(data) > 0x10:
|
|
|
|
raise ValueError("Data must not be longer than 0x10")
|
2020-06-01 16:49:26 +08:00
|
|
|
self.sock.send(struct.pack("HH", endpoint, len(data)) + data)
|
2017-04-18 16:12:04 +08:00
|
|
|
self.__recv() # to /dev/null
|
|
|
|
|
|
|
|
def bulkRead(self, endpoint, length, timeout=0):
|
2017-04-18 22:34:56 +08:00
|
|
|
self.sock.send(struct.pack("HH", endpoint, 0))
|
2017-04-18 16:12:04 +08:00
|
|
|
return self.__recv()
|
|
|
|
|
2017-04-29 08:54:23 +08:00
|
|
|
def close(self):
|
|
|
|
self.sock.close()
|
|
|
|
|
2017-08-23 05:02:07 +08:00
|
|
|
# *** normal mode ***
|
|
|
|
|
2017-04-07 09:11:36 +08:00
|
|
|
class Panda(object):
|
2019-10-03 09:20:32 +08:00
|
|
|
|
|
|
|
# matches cereal.car.CarParams.SafetyModel
|
2019-11-21 03:56:26 +08:00
|
|
|
SAFETY_SILENT = 0
|
2019-12-21 07:51:32 +08:00
|
|
|
SAFETY_HONDA_NIDEC = 1
|
2017-10-01 10:20:33 +08:00
|
|
|
SAFETY_TOYOTA = 2
|
2019-10-03 09:20:32 +08:00
|
|
|
SAFETY_ELM327 = 3
|
|
|
|
SAFETY_GM = 4
|
2019-12-21 07:51:32 +08:00
|
|
|
SAFETY_HONDA_BOSCH_GIRAFFE = 5
|
2019-10-03 09:20:32 +08:00
|
|
|
SAFETY_FORD = 6
|
|
|
|
SAFETY_HYUNDAI = 8
|
2018-11-07 04:28:33 +08:00
|
|
|
SAFETY_CHRYSLER = 9
|
2019-10-03 09:20:32 +08:00
|
|
|
SAFETY_TESLA = 10
|
|
|
|
SAFETY_SUBARU = 11
|
|
|
|
SAFETY_MAZDA = 13
|
2020-02-27 03:11:56 +08:00
|
|
|
SAFETY_NISSAN = 14
|
2020-02-20 04:37:38 +08:00
|
|
|
SAFETY_VOLKSWAGEN_MQB = 15
|
2019-10-03 09:20:32 +08:00
|
|
|
SAFETY_ALLOUTPUT = 17
|
|
|
|
SAFETY_GM_ASCM = 18
|
2019-11-21 03:56:26 +08:00
|
|
|
SAFETY_NOOUTPUT = 19
|
2019-12-21 07:51:32 +08:00
|
|
|
SAFETY_HONDA_BOSCH_HARNESS = 20
|
2020-04-02 03:38:27 +08:00
|
|
|
SAFETY_VOLKSWAGEN_PQ = 21
|
2020-02-28 07:28:24 +08:00
|
|
|
SAFETY_SUBARU_LEGACY = 22
|
2020-06-13 09:03:31 +08:00
|
|
|
SAFETY_HYUNDAI_LEGACY = 23
|
2017-07-18 06:29:31 +08:00
|
|
|
|
2017-07-19 03:15:19 +08:00
|
|
|
SERIAL_DEBUG = 0
|
|
|
|
SERIAL_ESP = 1
|
|
|
|
SERIAL_LIN1 = 2
|
|
|
|
SERIAL_LIN2 = 3
|
|
|
|
|
2017-07-19 12:05:09 +08:00
|
|
|
GMLAN_CAN2 = 1
|
|
|
|
GMLAN_CAN3 = 2
|
|
|
|
|
2017-07-18 09:59:16 +08:00
|
|
|
REQUEST_IN = usb1.ENDPOINT_IN | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE
|
2017-07-18 09:58:04 +08:00
|
|
|
REQUEST_OUT = usb1.ENDPOINT_OUT | usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE
|
2017-06-14 09:50:47 +08:00
|
|
|
|
2019-09-28 06:22:13 +08:00
|
|
|
HW_TYPE_UNKNOWN = b'\x00'
|
|
|
|
HW_TYPE_WHITE_PANDA = b'\x01'
|
|
|
|
HW_TYPE_GREY_PANDA = b'\x02'
|
|
|
|
HW_TYPE_BLACK_PANDA = b'\x03'
|
|
|
|
HW_TYPE_PEDAL = b'\x04'
|
2019-10-26 07:22:42 +08:00
|
|
|
HW_TYPE_UNO = b'\x05'
|
2021-07-08 15:36:25 +08:00
|
|
|
HW_TYPE_DOS = b'\x05'
|
2019-08-29 03:57:42 +08:00
|
|
|
|
2020-08-17 19:02:41 +08:00
|
|
|
CLOCK_SOURCE_MODE_DISABLED = 0
|
|
|
|
CLOCK_SOURCE_MODE_FREE_RUNNING = 1
|
|
|
|
CLOCK_SOURCE_MODE_EXTERNAL_SYNC = 2
|
|
|
|
|
2021-06-22 21:29:47 +08:00
|
|
|
FLAG_HONDA_ALT_BRAKE = 1
|
|
|
|
FLAG_HONDA_BOSCH_LONG = 2
|
|
|
|
|
2017-04-07 09:11:36 +08:00
|
|
|
def __init__(self, serial=None, claim=True):
|
2017-07-15 03:30:34 +08:00
|
|
|
self._serial = serial
|
2017-07-25 06:16:22 +08:00
|
|
|
self._handle = None
|
|
|
|
self.connect(claim)
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
self._handle.close()
|
|
|
|
self._handle = None
|
|
|
|
|
2017-07-30 08:21:22 +08:00
|
|
|
def connect(self, claim=True, wait=False):
|
2020-06-01 16:49:26 +08:00
|
|
|
if self._handle is not None:
|
2017-07-25 06:16:22 +08:00
|
|
|
self.close()
|
|
|
|
|
|
|
|
if self._serial == "WIFI":
|
2017-06-21 08:40:24 +08:00
|
|
|
self._handle = WifiHandle()
|
2017-06-14 09:50:47 +08:00
|
|
|
print("opening WIFI device")
|
2017-07-30 23:26:48 +08:00
|
|
|
self.wifi = True
|
2017-04-18 16:12:04 +08:00
|
|
|
else:
|
|
|
|
context = usb1.USBContext()
|
2017-06-21 08:40:24 +08:00
|
|
|
self._handle = None
|
2017-07-30 23:26:48 +08:00
|
|
|
self.wifi = False
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2017-07-30 08:21:22 +08:00
|
|
|
while 1:
|
|
|
|
try:
|
|
|
|
for device in context.getDeviceList(skip_on_error=True):
|
|
|
|
if device.getVendorID() == 0xbbaa and device.getProductID() in [0xddcc, 0xddee]:
|
2017-08-29 09:54:31 +08:00
|
|
|
try:
|
|
|
|
this_serial = device.getSerialNumber()
|
|
|
|
except Exception:
|
|
|
|
continue
|
|
|
|
if self._serial is None or this_serial == self._serial:
|
|
|
|
self._serial = this_serial
|
2017-08-29 01:42:23 +08:00
|
|
|
print("opening device", self._serial, hex(device.getProductID()))
|
2017-07-30 08:21:22 +08:00
|
|
|
self.bootstub = device.getProductID() == 0xddee
|
|
|
|
self.legacy = (device.getbcdDevice() != 0x2300)
|
|
|
|
self._handle = device.open()
|
2021-04-16 21:32:27 +08:00
|
|
|
if sys.platform not in ["win32", "cygwin", "msys", "darwin"]:
|
2020-05-03 03:18:26 +08:00
|
|
|
self._handle.setAutoDetachKernelDriver(True)
|
2017-07-30 08:21:22 +08:00
|
|
|
if claim:
|
|
|
|
self._handle.claimInterface(0)
|
2020-06-01 16:49:26 +08:00
|
|
|
# self._handle.setInterfaceAltSetting(0, 0) # Issue in USB stack
|
2017-07-30 08:21:22 +08:00
|
|
|
break
|
|
|
|
except Exception as e:
|
|
|
|
print("exception", e)
|
2017-08-22 04:32:28 +08:00
|
|
|
traceback.print_exc()
|
2020-06-01 16:49:26 +08:00
|
|
|
if not wait or self._handle is not None:
|
2017-07-30 08:21:22 +08:00
|
|
|
break
|
2020-06-01 05:07:01 +08:00
|
|
|
context = usb1.USBContext() # New context needed so new devices show up
|
2020-06-01 16:49:26 +08:00
|
|
|
assert(self._handle is not None)
|
2017-07-30 08:21:22 +08:00
|
|
|
print("connected")
|
2017-07-25 06:16:22 +08:00
|
|
|
|
2018-03-11 03:22:22 +08:00
|
|
|
def reset(self, enter_bootstub=False, enter_bootloader=False):
|
2017-07-25 06:16:22 +08:00
|
|
|
# reset
|
|
|
|
try:
|
2017-07-28 05:29:07 +08:00
|
|
|
if enter_bootloader:
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 0, 0, b'')
|
2017-07-25 06:16:22 +08:00
|
|
|
else:
|
2017-07-28 05:29:07 +08:00
|
|
|
if enter_bootstub:
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_IN, 0xd1, 1, 0, b'')
|
|
|
|
else:
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'')
|
2017-07-25 06:16:22 +08:00
|
|
|
except Exception:
|
|
|
|
pass
|
2017-07-28 05:29:07 +08:00
|
|
|
if not enter_bootloader:
|
2018-03-11 03:22:22 +08:00
|
|
|
self.reconnect()
|
|
|
|
|
|
|
|
def reconnect(self):
|
|
|
|
self.close()
|
|
|
|
time.sleep(1.0)
|
|
|
|
success = False
|
|
|
|
# wait up to 15 seconds
|
|
|
|
for i in range(0, 15):
|
|
|
|
try:
|
|
|
|
self.connect()
|
|
|
|
success = True
|
|
|
|
break
|
|
|
|
except Exception:
|
2020-06-01 16:49:26 +08:00
|
|
|
print("reconnecting is taking %d seconds..." % (i + 1))
|
2017-08-26 03:02:26 +08:00
|
|
|
try:
|
2018-03-11 03:22:22 +08:00
|
|
|
dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(self._serial))
|
|
|
|
dfu.recover()
|
2017-08-26 03:02:26 +08:00
|
|
|
except Exception:
|
2018-03-11 03:22:22 +08:00
|
|
|
pass
|
|
|
|
time.sleep(1.0)
|
|
|
|
if not success:
|
|
|
|
raise Exception("reconnect failed")
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def flash_static(handle, code):
|
|
|
|
# confirm flasher is present
|
|
|
|
fr = handle.controlRead(Panda.REQUEST_IN, 0xb0, 0, 0, 0xc)
|
2019-09-26 07:52:03 +08:00
|
|
|
assert fr[4:8] == b"\xde\xad\xd0\x0d"
|
2018-03-11 03:22:22 +08:00
|
|
|
|
|
|
|
# unlock flash
|
|
|
|
print("flash: unlocking")
|
|
|
|
handle.controlWrite(Panda.REQUEST_IN, 0xb1, 0, 0, b'')
|
|
|
|
|
2019-07-24 06:07:06 +08:00
|
|
|
# erase sectors 1 through 3
|
2018-03-11 03:22:22 +08:00
|
|
|
print("flash: erasing")
|
2019-07-24 06:07:06 +08:00
|
|
|
for i in range(1, 4):
|
|
|
|
handle.controlWrite(Panda.REQUEST_IN, 0xb2, i, 0, b'')
|
2018-03-11 03:22:22 +08:00
|
|
|
|
|
|
|
# flash over EP2
|
|
|
|
STEP = 0x10
|
|
|
|
print("flash: flashing")
|
|
|
|
for i in range(0, len(code), STEP):
|
2020-06-01 16:49:26 +08:00
|
|
|
handle.bulkWrite(2, code[i:i + STEP])
|
2018-03-11 03:22:22 +08:00
|
|
|
|
|
|
|
# reset
|
|
|
|
print("flash: resetting")
|
|
|
|
try:
|
|
|
|
handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'')
|
|
|
|
except Exception:
|
|
|
|
pass
|
2017-08-23 00:30:44 +08:00
|
|
|
|
2021-03-24 21:44:32 +08:00
|
|
|
def flash(self, fn=DEFAULT_FW_FN, code=None, reconnect=True):
|
2019-03-14 11:58:00 +08:00
|
|
|
print("flash: main version is " + self.get_version())
|
2017-07-25 06:16:22 +08:00
|
|
|
if not self.bootstub:
|
2017-07-28 05:29:07 +08:00
|
|
|
self.reset(enter_bootstub=True)
|
2017-07-25 06:16:22 +08:00
|
|
|
assert(self.bootstub)
|
|
|
|
|
2017-08-23 04:36:20 +08:00
|
|
|
if code is None:
|
2019-09-26 07:52:03 +08:00
|
|
|
with open(fn, "rb") as f:
|
2017-08-23 04:36:20 +08:00
|
|
|
code = f.read()
|
2017-07-25 06:16:22 +08:00
|
|
|
|
2017-08-23 00:30:44 +08:00
|
|
|
# get version
|
2019-03-14 11:58:00 +08:00
|
|
|
print("flash: bootstub version is " + self.get_version())
|
2017-08-23 00:30:44 +08:00
|
|
|
|
2018-03-11 03:22:22 +08:00
|
|
|
# do flash
|
|
|
|
Panda.flash_static(self._handle, code)
|
2017-07-25 06:16:22 +08:00
|
|
|
|
2018-03-11 03:22:22 +08:00
|
|
|
# reconnect
|
|
|
|
if reconnect:
|
|
|
|
self.reconnect()
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2019-04-10 05:09:18 +08:00
|
|
|
def recover(self, timeout=None):
|
2020-04-14 06:12:06 +08:00
|
|
|
self.reset(enter_bootstub=True)
|
2017-08-29 01:42:23 +08:00
|
|
|
self.reset(enter_bootloader=True)
|
2019-04-10 05:09:18 +08:00
|
|
|
t_start = time.time()
|
2017-08-29 01:42:23 +08:00
|
|
|
while len(PandaDFU.list()) == 0:
|
|
|
|
print("waiting for DFU...")
|
|
|
|
time.sleep(0.1)
|
2019-04-10 05:09:18 +08:00
|
|
|
if timeout is not None and (time.time() - t_start) > timeout:
|
|
|
|
return False
|
2017-08-29 01:42:23 +08:00
|
|
|
|
|
|
|
dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(self._serial))
|
|
|
|
dfu.recover()
|
|
|
|
|
|
|
|
# reflash after recover
|
|
|
|
self.connect(True, True)
|
|
|
|
self.flash()
|
2019-04-10 05:09:18 +08:00
|
|
|
return True
|
2017-08-29 01:42:23 +08:00
|
|
|
|
2017-07-30 23:49:53 +08:00
|
|
|
@staticmethod
|
|
|
|
def flash_ota_st():
|
|
|
|
ret = os.system("cd %s && make clean && make ota" % (os.path.join(BASEDIR, "board")))
|
|
|
|
time.sleep(1)
|
2020-06-01 16:49:26 +08:00
|
|
|
return ret == 0
|
2017-07-30 23:49:53 +08:00
|
|
|
|
|
|
|
@staticmethod
|
2019-04-10 05:09:18 +08:00
|
|
|
def flash_ota_wifi(release=False):
|
|
|
|
release_str = "RELEASE=1" if release else ""
|
2020-06-01 16:49:26 +08:00
|
|
|
ret = os.system("cd {} && make clean && {} make ota".format(os.path.join(BASEDIR, "boardesp"), release_str))
|
2017-07-30 23:49:53 +08:00
|
|
|
time.sleep(1)
|
2020-06-01 16:49:26 +08:00
|
|
|
return ret == 0
|
2017-07-30 23:49:53 +08:00
|
|
|
|
2017-04-07 09:11:36 +08:00
|
|
|
@staticmethod
|
|
|
|
def list():
|
|
|
|
context = usb1.USBContext()
|
|
|
|
ret = []
|
2017-09-13 11:49:28 +08:00
|
|
|
try:
|
|
|
|
for device in context.getDeviceList(skip_on_error=True):
|
|
|
|
if device.getVendorID() == 0xbbaa and device.getProductID() in [0xddcc, 0xddee]:
|
|
|
|
try:
|
|
|
|
ret.append(device.getSerialNumber())
|
|
|
|
except Exception:
|
|
|
|
continue
|
|
|
|
except Exception:
|
2017-09-13 11:51:05 +08:00
|
|
|
pass
|
2017-04-18 16:12:04 +08:00
|
|
|
# TODO: detect if this is real
|
2020-06-01 16:49:26 +08:00
|
|
|
# ret += ["WIFI"]
|
2017-04-07 09:11:36 +08:00
|
|
|
return ret
|
|
|
|
|
2017-07-19 03:29:16 +08:00
|
|
|
def call_control_api(self, msg):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, msg, 0, 0, b'')
|
|
|
|
|
2017-04-07 09:11:36 +08:00
|
|
|
# ******************* health *******************
|
|
|
|
|
|
|
|
def health(self):
|
2021-06-05 13:07:45 +08:00
|
|
|
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xd2, 0, 0, 44)
|
|
|
|
a = struct.unpack("<IIIIIIIIBBBBBBBHBBB", dat)
|
2019-07-24 06:07:06 +08:00
|
|
|
return {
|
2019-11-22 08:47:49 +08:00
|
|
|
"uptime": a[0],
|
|
|
|
"voltage": a[1],
|
|
|
|
"current": a[2],
|
2019-12-20 10:16:31 +08:00
|
|
|
"can_rx_errs": a[3],
|
|
|
|
"can_send_errs": a[4],
|
|
|
|
"can_fwd_errs": a[5],
|
|
|
|
"gmlan_send_errs": a[6],
|
|
|
|
"faults": a[7],
|
|
|
|
"ignition_line": a[8],
|
|
|
|
"ignition_can": a[9],
|
|
|
|
"controls_allowed": a[10],
|
|
|
|
"gas_interceptor_detected": a[11],
|
|
|
|
"car_harness_status": a[12],
|
|
|
|
"usb_power_mode": a[13],
|
|
|
|
"safety_mode": a[14],
|
2021-05-03 20:59:36 +08:00
|
|
|
"safety_param": a[15],
|
|
|
|
"fault_status": a[16],
|
2021-06-05 13:07:45 +08:00
|
|
|
"power_save_enabled": a[17],
|
|
|
|
"heartbeat_lost": a[18],
|
2019-07-24 06:07:06 +08:00
|
|
|
}
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2017-04-29 07:56:40 +08:00
|
|
|
# ******************* control *******************
|
|
|
|
|
|
|
|
def enter_bootloader(self):
|
|
|
|
try:
|
2017-07-18 09:58:04 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xd1, 0, 0, b'')
|
2017-06-14 09:50:47 +08:00
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
2017-04-29 07:56:40 +08:00
|
|
|
|
2017-08-23 00:30:44 +08:00
|
|
|
def get_version(self):
|
2019-09-26 07:52:51 +08:00
|
|
|
return self._handle.controlRead(Panda.REQUEST_IN, 0xd6, 0, 0, 0x40).decode('utf8')
|
2017-08-23 00:30:44 +08:00
|
|
|
|
2019-12-10 02:54:48 +08:00
|
|
|
@staticmethod
|
|
|
|
def get_signature_from_firmware(fn):
|
|
|
|
f = open(fn, 'rb')
|
|
|
|
f.seek(-128, 2) # Seek from end of file
|
|
|
|
return f.read(128)
|
|
|
|
|
|
|
|
def get_signature(self):
|
|
|
|
part_1 = self._handle.controlRead(Panda.REQUEST_IN, 0xd3, 0, 0, 0x40)
|
|
|
|
part_2 = self._handle.controlRead(Panda.REQUEST_IN, 0xd4, 0, 0, 0x40)
|
2019-12-19 09:03:31 +08:00
|
|
|
return bytes(part_1 + part_2)
|
2019-12-10 02:54:48 +08:00
|
|
|
|
2019-07-24 06:07:06 +08:00
|
|
|
def get_type(self):
|
|
|
|
return self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40)
|
|
|
|
|
2019-08-29 03:57:42 +08:00
|
|
|
def is_white(self):
|
|
|
|
return self.get_type() == Panda.HW_TYPE_WHITE_PANDA
|
|
|
|
|
2018-02-17 03:07:20 +08:00
|
|
|
def is_grey(self):
|
2019-08-29 03:57:42 +08:00
|
|
|
return self.get_type() == Panda.HW_TYPE_GREY_PANDA
|
2019-07-24 06:07:06 +08:00
|
|
|
|
|
|
|
def is_black(self):
|
2019-08-29 03:57:42 +08:00
|
|
|
return self.get_type() == Panda.HW_TYPE_BLACK_PANDA
|
2018-02-17 03:07:20 +08:00
|
|
|
|
2021-07-03 05:27:04 +08:00
|
|
|
def is_pedal(self):
|
|
|
|
return self.get_type() == Panda.HW_TYPE_PEDAL
|
|
|
|
|
2019-10-26 07:22:42 +08:00
|
|
|
def is_uno(self):
|
|
|
|
return self.get_type() == Panda.HW_TYPE_UNO
|
|
|
|
|
2021-07-08 15:36:25 +08:00
|
|
|
def is_dos(self):
|
|
|
|
return self.get_type() == Panda.HW_TYPE_DOS
|
|
|
|
|
2019-11-06 10:25:42 +08:00
|
|
|
def has_obd(self):
|
2021-07-08 15:36:25 +08:00
|
|
|
return (self.is_uno() or self.is_dos() or self.is_black())
|
2019-11-06 10:25:42 +08:00
|
|
|
|
2017-04-29 07:56:40 +08:00
|
|
|
def get_serial(self):
|
2017-07-18 09:59:16 +08:00
|
|
|
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 0, 0, 0x20)
|
2017-06-14 09:50:47 +08:00
|
|
|
hashsig, calc_hash = dat[0x1c:], hashlib.sha1(dat[0:0x1c]).digest()[0:4]
|
2017-07-18 10:43:30 +08:00
|
|
|
assert(hashsig == calc_hash)
|
2020-06-01 16:49:26 +08:00
|
|
|
return [dat[0:0x10].decode("utf8"), dat[0x10:0x10 + 10].decode("utf8")]
|
2017-04-29 07:56:40 +08:00
|
|
|
|
2017-04-29 08:49:55 +08:00
|
|
|
def get_secret(self):
|
2017-07-18 09:59:16 +08:00
|
|
|
return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10)
|
2017-04-29 08:49:55 +08:00
|
|
|
|
2017-04-26 06:16:23 +08:00
|
|
|
# ******************* configuration *******************
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2017-08-24 00:28:52 +08:00
|
|
|
def set_usb_power(self, on):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, int(on), 0, b'')
|
|
|
|
|
2019-11-22 08:47:49 +08:00
|
|
|
def set_power_save(self, power_save_enabled=0):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe7, int(power_save_enabled), 0, b'')
|
|
|
|
|
2017-07-18 06:29:31 +08:00
|
|
|
def set_esp_power(self, on):
|
2017-07-18 09:58:04 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xd9, int(on), 0, b'')
|
2017-07-15 12:17:32 +08:00
|
|
|
|
2017-08-23 05:17:59 +08:00
|
|
|
def esp_reset(self, bootmode=0):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xda, int(bootmode), 0, b'')
|
|
|
|
time.sleep(0.2)
|
|
|
|
|
2021-06-22 04:54:09 +08:00
|
|
|
def set_safety_mode(self, mode=SAFETY_SILENT, disable_heartbeat=True):
|
2017-07-18 09:58:04 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdc, mode, 0, b'')
|
2021-06-22 04:54:09 +08:00
|
|
|
if disable_heartbeat:
|
|
|
|
self.set_heartbeat_disabled()
|
2017-07-15 12:59:23 +08:00
|
|
|
|
|
|
|
def set_can_forwarding(self, from_bus, to_bus):
|
2017-07-18 06:29:31 +08:00
|
|
|
# TODO: This feature may not work correctly with saturated buses
|
2017-07-18 09:58:04 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdd, from_bus, to_bus, b'')
|
2017-05-02 14:40:49 +08:00
|
|
|
|
2017-07-19 12:05:09 +08:00
|
|
|
def set_gmlan(self, bus=2):
|
2019-07-24 06:07:06 +08:00
|
|
|
# TODO: check panda type
|
2017-07-19 12:05:09 +08:00
|
|
|
if bus is None:
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdb, 0, 0, b'')
|
|
|
|
elif bus in [Panda.GMLAN_CAN2, Panda.GMLAN_CAN3]:
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdb, 1, bus, b'')
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2019-07-24 06:07:06 +08:00
|
|
|
def set_obd(self, obd):
|
|
|
|
# TODO: check panda type
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xdb, int(obd), 0, b'')
|
|
|
|
|
2017-07-15 11:25:13 +08:00
|
|
|
def set_can_loopback(self, enable):
|
2017-07-18 06:29:31 +08:00
|
|
|
# set can loopback mode for all buses
|
2017-07-18 09:58:04 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe5, int(enable), 0, b'')
|
2017-07-15 11:25:13 +08:00
|
|
|
|
2019-04-02 13:45:00 +08:00
|
|
|
def set_can_enable(self, bus_num, enable):
|
2020-09-28 22:19:09 +08:00
|
|
|
# sets the can transceiver enable pin
|
2019-04-02 13:45:00 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf4, int(bus_num), int(enable), b'')
|
|
|
|
|
2017-07-18 10:43:30 +08:00
|
|
|
def set_can_speed_kbps(self, bus, speed):
|
2020-06-01 16:49:26 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xde, bus, int(speed * 10), b'')
|
2017-07-18 10:43:30 +08:00
|
|
|
|
2017-04-26 06:16:23 +08:00
|
|
|
def set_uart_baud(self, uart, rate):
|
2020-06-01 16:49:26 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe4, uart, int(rate / 300), b'')
|
2017-04-26 06:16:23 +08:00
|
|
|
|
|
|
|
def set_uart_parity(self, uart, parity):
|
|
|
|
# parity, 0=off, 1=even, 2=odd
|
2017-07-18 09:58:04 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe2, uart, parity, b'')
|
2017-04-26 06:16:23 +08:00
|
|
|
|
2017-04-26 11:23:05 +08:00
|
|
|
def set_uart_callback(self, uart, install):
|
2017-07-18 09:58:04 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xe3, uart, int(install), b'')
|
2017-04-26 11:23:05 +08:00
|
|
|
|
2017-04-26 06:16:23 +08:00
|
|
|
# ******************* can *******************
|
|
|
|
|
2020-04-14 06:12:06 +08:00
|
|
|
# The panda will NAK CAN writes when there is CAN congestion.
|
2020-04-07 07:49:42 +08:00
|
|
|
# libusb will try to send it again, with a max timeout.
|
|
|
|
# Timeout is in ms. If set to 0, the timeout is infinite.
|
|
|
|
CAN_SEND_TIMEOUT_MS = 10
|
|
|
|
|
|
|
|
def can_send_many(self, arr, timeout=CAN_SEND_TIMEOUT_MS):
|
2017-04-07 09:11:36 +08:00
|
|
|
snds = []
|
2017-06-14 09:50:47 +08:00
|
|
|
transmit = 1
|
|
|
|
extended = 4
|
2017-04-07 09:11:36 +08:00
|
|
|
for addr, _, dat, bus in arr:
|
2017-06-26 14:03:08 +08:00
|
|
|
assert len(dat) <= 8
|
2018-01-24 10:40:53 +08:00
|
|
|
if DEBUG:
|
2020-06-19 02:17:00 +08:00
|
|
|
print(f" W 0x{addr:x}: 0x{dat.hex()}")
|
2017-05-08 15:18:50 +08:00
|
|
|
if addr >= 0x800:
|
|
|
|
rir = (addr << 3) | transmit | extended
|
|
|
|
else:
|
|
|
|
rir = (addr << 21) | transmit
|
|
|
|
snd = struct.pack("II", rir, len(dat) | (bus << 4)) + dat
|
2017-06-14 09:50:47 +08:00
|
|
|
snd = snd.ljust(0x10, b'\x00')
|
2017-04-07 09:11:36 +08:00
|
|
|
snds.append(snd)
|
|
|
|
|
2017-06-14 09:50:47 +08:00
|
|
|
while True:
|
2017-04-07 09:11:36 +08:00
|
|
|
try:
|
2017-07-30 23:26:48 +08:00
|
|
|
if self.wifi:
|
|
|
|
for s in snds:
|
|
|
|
self._handle.bulkWrite(3, s)
|
|
|
|
else:
|
2020-04-07 07:49:42 +08:00
|
|
|
self._handle.bulkWrite(3, b''.join(snds), timeout=timeout)
|
2017-04-07 09:11:36 +08:00
|
|
|
break
|
2017-06-14 09:50:47 +08:00
|
|
|
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
|
|
|
|
print("CAN: BAD SEND MANY, RETRYING")
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2020-04-07 07:49:42 +08:00
|
|
|
def can_send(self, addr, dat, bus, timeout=CAN_SEND_TIMEOUT_MS):
|
|
|
|
self.can_send_many([[addr, None, dat, bus]], timeout=timeout)
|
2017-04-07 09:11:36 +08:00
|
|
|
|
|
|
|
def can_recv(self):
|
2017-06-14 09:50:47 +08:00
|
|
|
dat = bytearray()
|
|
|
|
while True:
|
2017-04-07 09:11:36 +08:00
|
|
|
try:
|
2020-06-01 16:49:26 +08:00
|
|
|
dat = self._handle.bulkRead(1, 0x10 * 256)
|
2017-04-07 09:11:36 +08:00
|
|
|
break
|
2017-06-14 09:50:47 +08:00
|
|
|
except (usb1.USBErrorIO, usb1.USBErrorOverflow):
|
|
|
|
print("CAN: BAD RECV, RETRYING")
|
2019-09-26 05:34:28 +08:00
|
|
|
time.sleep(0.1)
|
2017-05-17 08:01:29 +08:00
|
|
|
return parse_can_buffer(dat)
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2017-08-17 17:03:08 +08:00
|
|
|
def can_clear(self, bus):
|
|
|
|
"""Clears all messages from the specified internal CAN ringbuffer as
|
|
|
|
though it were drained.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
bus (int): can bus number to clear a tx queue, or 0xFFFF to clear the
|
|
|
|
global can rx queue.
|
|
|
|
|
|
|
|
"""
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf1, bus, 0, b'')
|
|
|
|
|
2018-03-10 09:40:03 +08:00
|
|
|
# ******************* isotp *******************
|
|
|
|
|
2018-03-11 02:16:53 +08:00
|
|
|
def isotp_send(self, addr, dat, bus, recvaddr=None, subaddr=None):
|
2018-03-10 09:40:03 +08:00
|
|
|
return isotp_send(self, dat, addr, bus, recvaddr, subaddr)
|
|
|
|
|
2018-03-11 02:16:53 +08:00
|
|
|
def isotp_recv(self, addr, bus=0, sendaddr=None, subaddr=None):
|
2018-03-11 02:17:57 +08:00
|
|
|
return isotp_recv(self, addr, bus, sendaddr, subaddr)
|
2018-03-10 09:40:03 +08:00
|
|
|
|
2017-04-07 09:11:36 +08:00
|
|
|
# ******************* serial *******************
|
|
|
|
|
|
|
|
def serial_read(self, port_number):
|
2017-07-19 03:15:19 +08:00
|
|
|
ret = []
|
|
|
|
while 1:
|
|
|
|
lret = bytes(self._handle.controlRead(Panda.REQUEST_IN, 0xe0, port_number, 0, 0x40))
|
|
|
|
if len(lret) == 0:
|
|
|
|
break
|
|
|
|
ret.append(lret)
|
|
|
|
return b''.join(ret)
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2017-04-18 16:12:04 +08:00
|
|
|
def serial_write(self, port_number, ln):
|
2018-01-20 03:04:21 +08:00
|
|
|
ret = 0
|
|
|
|
for i in range(0, len(ln), 0x20):
|
2020-06-01 16:49:26 +08:00
|
|
|
ret += self._handle.bulkWrite(2, struct.pack("B", port_number) + ln[i:i + 0x20])
|
2018-01-20 03:04:21 +08:00
|
|
|
return ret
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2017-08-17 17:03:08 +08:00
|
|
|
def serial_clear(self, port_number):
|
|
|
|
"""Clears all messages (tx and rx) from the specified internal uart
|
|
|
|
ringbuffer as though it were drained.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
port_number (int): port number of the uart to clear.
|
|
|
|
|
|
|
|
"""
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf2, port_number, 0, b'')
|
2017-04-07 09:11:36 +08:00
|
|
|
|
|
|
|
# ******************* kline *******************
|
|
|
|
|
|
|
|
# pulse low for wakeup
|
2020-06-19 02:17:00 +08:00
|
|
|
def kline_wakeup(self, k=True, l=True):
|
|
|
|
assert k or l, "must specify k-line, l-line, or both"
|
2018-03-01 09:03:04 +08:00
|
|
|
if DEBUG:
|
|
|
|
print("kline wakeup...")
|
2020-06-19 02:17:00 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf0, 2 if k and l else int(l), 0, b'')
|
2018-03-01 09:03:04 +08:00
|
|
|
if DEBUG:
|
|
|
|
print("kline wakeup done")
|
2017-04-07 09:11:36 +08:00
|
|
|
|
2020-07-11 05:18:24 +08:00
|
|
|
def kline_5baud(self, addr, k=True, l=True):
|
|
|
|
assert k or l, "must specify k-line, l-line, or both"
|
|
|
|
if DEBUG:
|
|
|
|
print("kline 5 baud...")
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf4, 2 if k and l else int(l), addr, b'')
|
|
|
|
if DEBUG:
|
|
|
|
print("kline 5 baud done")
|
|
|
|
|
2017-04-07 09:11:36 +08:00
|
|
|
def kline_drain(self, bus=2):
|
|
|
|
# drain buffer
|
2017-06-14 09:50:47 +08:00
|
|
|
bret = bytearray()
|
|
|
|
while True:
|
2017-07-18 09:59:16 +08:00
|
|
|
ret = self._handle.controlRead(Panda.REQUEST_IN, 0xe0, bus, 0, 0x40)
|
2017-04-07 09:11:36 +08:00
|
|
|
if len(ret) == 0:
|
|
|
|
break
|
2018-03-01 09:45:54 +08:00
|
|
|
elif DEBUG:
|
2020-06-19 02:17:00 +08:00
|
|
|
print(f"kline drain: 0x{ret.hex()}")
|
2017-06-14 09:50:47 +08:00
|
|
|
bret += ret
|
2017-06-29 05:50:00 +08:00
|
|
|
return bytes(bret)
|
2017-04-07 09:11:36 +08:00
|
|
|
|
|
|
|
def kline_ll_recv(self, cnt, bus=2):
|
2017-06-14 09:50:47 +08:00
|
|
|
echo = bytearray()
|
2017-04-07 09:11:36 +08:00
|
|
|
while len(echo) != cnt:
|
2020-06-01 16:49:26 +08:00
|
|
|
ret = self._handle.controlRead(Panda.REQUEST_OUT, 0xe0, bus, 0, cnt - len(echo))
|
2018-03-01 09:03:04 +08:00
|
|
|
if DEBUG and len(ret) > 0:
|
2020-06-19 02:17:00 +08:00
|
|
|
print(f"kline recv: 0x{ret.hex()}")
|
2018-03-01 09:03:04 +08:00
|
|
|
echo += ret
|
2020-06-15 00:22:39 +08:00
|
|
|
return bytes(echo)
|
2017-04-07 09:11:36 +08:00
|
|
|
|
|
|
|
def kline_send(self, x, bus=2, checksum=True):
|
|
|
|
self.kline_drain(bus=bus)
|
|
|
|
if checksum:
|
2020-06-19 02:17:00 +08:00
|
|
|
x += bytes([sum(x) % 0x100])
|
2017-04-18 22:34:56 +08:00
|
|
|
for i in range(0, len(x), 0xf):
|
2020-06-01 16:49:26 +08:00
|
|
|
ts = x[i:i + 0xf]
|
2018-03-01 09:03:04 +08:00
|
|
|
if DEBUG:
|
2020-06-19 02:17:00 +08:00
|
|
|
print(f"kline send: 0x{ts.hex()}")
|
2019-10-16 03:05:33 +08:00
|
|
|
self._handle.bulkWrite(2, bytes([bus]) + ts)
|
2017-04-07 09:11:36 +08:00
|
|
|
echo = self.kline_ll_recv(len(ts), bus=bus)
|
|
|
|
if echo != ts:
|
2020-06-19 02:17:00 +08:00
|
|
|
print(f"**** ECHO ERROR {i} ****")
|
|
|
|
print(f"0x{echo.hex()}")
|
|
|
|
print(f"0x{ts.hex()}")
|
2017-04-07 09:11:36 +08:00
|
|
|
assert echo == ts
|
|
|
|
|
2020-06-19 02:17:00 +08:00
|
|
|
def kline_recv(self, bus=2, header_len=4):
|
|
|
|
# read header (last byte is length)
|
|
|
|
msg = self.kline_ll_recv(header_len, bus=bus)
|
|
|
|
# read data (add one byte to length for checksum)
|
|
|
|
msg += self.kline_ll_recv(msg[-1]+1, bus=bus)
|
2017-04-07 09:11:36 +08:00
|
|
|
return msg
|
2019-07-24 06:07:06 +08:00
|
|
|
|
|
|
|
def send_heartbeat(self):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf3, 0, 0, b'')
|
2019-10-26 07:22:42 +08:00
|
|
|
|
2021-06-22 04:54:09 +08:00
|
|
|
# disable heartbeat checks for use outside of openpilot
|
|
|
|
# sending a heartbeat will reenable the checks
|
|
|
|
def set_heartbeat_disabled(self):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf8, 0, 0, b'')
|
|
|
|
|
2019-10-26 07:22:42 +08:00
|
|
|
# ******************* RTC *******************
|
|
|
|
def set_datetime(self, dt):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xa1, int(dt.year), 0, b'')
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xa2, int(dt.month), 0, b'')
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xa3, int(dt.day), 0, b'')
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xa4, int(dt.isoweekday()), 0, b'')
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xa5, int(dt.hour), 0, b'')
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xa6, int(dt.minute), 0, b'')
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xa7, int(dt.second), 0, b'')
|
|
|
|
|
|
|
|
def get_datetime(self):
|
|
|
|
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa0, 0, 0, 8)
|
|
|
|
a = struct.unpack("HBBBBBB", dat)
|
|
|
|
return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6])
|
|
|
|
|
|
|
|
# ******************* IR *******************
|
|
|
|
def set_ir_power(self, percentage):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb0, int(percentage), 0, b'')
|
|
|
|
|
|
|
|
# ******************* Fan ******************
|
|
|
|
def set_fan_power(self, percentage):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb1, int(percentage), 0, b'')
|
|
|
|
|
|
|
|
def get_fan_rpm(self):
|
|
|
|
dat = self._handle.controlRead(Panda.REQUEST_IN, 0xb2, 0, 0, 2)
|
|
|
|
a = struct.unpack("H", dat)
|
2019-11-05 09:26:37 +08:00
|
|
|
return a[0]
|
|
|
|
|
2020-06-01 16:49:26 +08:00
|
|
|
# ****************** Phone *****************
|
2019-11-05 09:26:37 +08:00
|
|
|
def set_phone_power(self, enabled):
|
2019-11-21 03:56:26 +08:00
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, int(enabled), 0, b'')
|
2020-08-17 19:02:41 +08:00
|
|
|
|
|
|
|
# ************** Clock Source **************
|
|
|
|
def set_clock_source_mode(self, mode):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf5, int(mode), 0, b'')
|
2020-08-17 21:04:01 +08:00
|
|
|
|
|
|
|
# ****************** Siren *****************
|
|
|
|
def set_siren(self, enabled):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf6, int(enabled), 0, b'')
|
2021-01-05 21:51:58 +08:00
|
|
|
|
|
|
|
# ****************** Debug *****************
|
|
|
|
def set_green_led(self, enabled):
|
|
|
|
self._handle.controlWrite(Panda.REQUEST_OUT, 0xf7, int(enabled), 0, b'')
|