|  |  |  | #!/usr/bin/env python3
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # This file is not used by OpenPilot. Only boardd.cc is used.
 | 
					
						
							|  |  |  | # The python version is slower, but has more options for development.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # TODO: merge the extra functionalities of this file (like MOCK) in boardd.c and
 | 
					
						
							|  |  |  | # delete this python version of boardd
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import os
 | 
					
						
							|  |  |  | import struct
 | 
					
						
							|  |  |  | import time
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import cereal.messaging as messaging
 | 
					
						
							|  |  |  | from common.realtime import Ratekeeper
 | 
					
						
							|  |  |  | from selfdrive.swaglog import cloudlog
 | 
					
						
							|  |  |  | from selfdrive.boardd.boardd import can_capnp_to_can_list
 | 
					
						
							|  |  |  | from cereal import car
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | SafetyModel = car.CarParams.SafetyModel
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # USB is optional
 | 
					
						
							|  |  |  | try:
 | 
					
						
							|  |  |  |   import usb1
 | 
					
						
							|  |  |  |   from usb1 import USBErrorIO, USBErrorOverflow  #pylint: disable=no-name-in-module
 | 
					
						
							|  |  |  | except Exception:
 | 
					
						
							|  |  |  |   pass
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # *** serialization functions ***
 | 
					
						
							|  |  |  | def can_list_to_can_capnp(can_msgs, msgtype='can'):
 | 
					
						
							|  |  |  |   dat = messaging.new_message(msgtype, len(can_msgs))
 | 
					
						
							|  |  |  |   for i, can_msg in enumerate(can_msgs):
 | 
					
						
							|  |  |  |     if msgtype == 'sendcan':
 | 
					
						
							|  |  |  |       cc = dat.sendcan[i]
 | 
					
						
							|  |  |  |     else:
 | 
					
						
							|  |  |  |       cc = dat.can[i]
 | 
					
						
							|  |  |  |     cc.address = can_msg[0]
 | 
					
						
							|  |  |  |     cc.busTime = can_msg[1]
 | 
					
						
							|  |  |  |     cc.dat = bytes(can_msg[2])
 | 
					
						
							|  |  |  |     cc.src = can_msg[3]
 | 
					
						
							|  |  |  |   return dat
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # *** can driver ***
 | 
					
						
							|  |  |  | def can_health():
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       dat = handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd2, 0, 0, 0x16)
 | 
					
						
							|  |  |  |       break
 | 
					
						
							|  |  |  |     except (USBErrorIO, USBErrorOverflow):
 | 
					
						
							|  |  |  |       cloudlog.exception("CAN: BAD HEALTH, RETRYING")
 | 
					
						
							|  |  |  |   v, i = struct.unpack("II", dat[0:8])
 | 
					
						
							|  |  |  |   ign_line, ign_can = struct.unpack("BB", dat[20:22])
 | 
					
						
							|  |  |  |   return {"voltage": v, "current": i, "ignition_line": bool(ign_line), "ignition_can": bool(ign_can)}
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def __parse_can_buffer(dat):
 | 
					
						
							|  |  |  |   ret = []
 | 
					
						
							|  |  |  |   for j in range(0, len(dat), 0x10):
 | 
					
						
							|  |  |  |     ddat = dat[j:j+0x10]
 | 
					
						
							|  |  |  |     f1, f2 = struct.unpack("II", ddat[0:8])
 | 
					
						
							|  |  |  |     ret.append((f1 >> 21, f2>>16, ddat[8:8+(f2&0xF)], (f2>>4)&0xFF))
 | 
					
						
							|  |  |  |   return ret
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def can_send_many(arr):
 | 
					
						
							|  |  |  |   snds = []
 | 
					
						
							|  |  |  |   for addr, _, dat, alt in arr:
 | 
					
						
							|  |  |  |     if addr < 0x800:  # only support 11 bit addr
 | 
					
						
							|  |  |  |       snd = struct.pack("II", ((addr << 21) | 1), len(dat) | (alt << 4)) + dat
 | 
					
						
							|  |  |  |       snd = snd.ljust(0x10, b'\x00')
 | 
					
						
							|  |  |  |       snds.append(snd)
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       handle.bulkWrite(3, b''.join(snds))
 | 
					
						
							|  |  |  |       break
 | 
					
						
							|  |  |  |     except (USBErrorIO, USBErrorOverflow):
 | 
					
						
							|  |  |  |       cloudlog.exception("CAN: BAD SEND MANY, RETRYING")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def can_recv():
 | 
					
						
							|  |  |  |   dat = b""
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     try:
 | 
					
						
							|  |  |  |       dat = handle.bulkRead(1, 0x10*256)
 | 
					
						
							|  |  |  |       break
 | 
					
						
							|  |  |  |     except (USBErrorIO, USBErrorOverflow):
 | 
					
						
							|  |  |  |       cloudlog.exception("CAN: BAD RECV, RETRYING")
 | 
					
						
							|  |  |  |   return __parse_can_buffer(dat)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def can_init():
 | 
					
						
							|  |  |  |   global handle, context
 | 
					
						
							|  |  |  |   handle = None
 | 
					
						
							|  |  |  |   cloudlog.info("attempting can init")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   context = usb1.USBContext()
 | 
					
						
							|  |  |  |   #context.setDebug(9)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for device in context.getDeviceList(skip_on_error=True):
 | 
					
						
							|  |  |  |     if device.getVendorID() == 0xbbaa and device.getProductID() == 0xddcc:
 | 
					
						
							|  |  |  |       handle = device.open()
 | 
					
						
							|  |  |  |       handle.claimInterface(0)
 | 
					
						
							|  |  |  |       handle.controlWrite(0x40, 0xdc, SafetyModel.allOutput, 0, b'')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if handle is None:
 | 
					
						
							|  |  |  |     cloudlog.warning("CAN NOT FOUND")
 | 
					
						
							|  |  |  |     exit(-1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   cloudlog.info("got handle")
 | 
					
						
							|  |  |  |   cloudlog.info("can init done")
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def boardd_mock_loop():
 | 
					
						
							|  |  |  |   can_init()
 | 
					
						
							|  |  |  |   handle.controlWrite(0x40, 0xdc, SafetyModel.allOutput, 0, b'')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   logcan = messaging.sub_sock('can')
 | 
					
						
							|  |  |  |   sendcan = messaging.pub_sock('sendcan')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     tsc = messaging.drain_sock(logcan, wait_for_one=True)
 | 
					
						
							|  |  |  |     snds = map(lambda x: can_capnp_to_can_list(x.can), tsc)
 | 
					
						
							|  |  |  |     snd = []
 | 
					
						
							|  |  |  |     for s in snds:
 | 
					
						
							|  |  |  |       snd += s
 | 
					
						
							|  |  |  |     snd = list(filter(lambda x: x[-1] <= 2, snd))
 | 
					
						
							|  |  |  |     snd_0 = len(list(filter(lambda x: x[-1] == 0, snd)))
 | 
					
						
							|  |  |  |     snd_1 = len(list(filter(lambda x: x[-1] == 1, snd)))
 | 
					
						
							|  |  |  |     snd_2 = len(list(filter(lambda x: x[-1] == 2, snd)))
 | 
					
						
							|  |  |  |     can_send_many(snd)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # recv @ 100hz
 | 
					
						
							|  |  |  |     can_msgs = can_recv()
 | 
					
						
							|  |  |  |     got_0 = len(list(filter(lambda x: x[-1] == 0+0x80, can_msgs)))
 | 
					
						
							|  |  |  |     got_1 = len(list(filter(lambda x: x[-1] == 1+0x80, can_msgs)))
 | 
					
						
							|  |  |  |     got_2 = len(list(filter(lambda x: x[-1] == 2+0x80, can_msgs)))
 | 
					
						
							|  |  |  |     print("sent %3d (%3d/%3d/%3d) got %3d (%3d/%3d/%3d)" %
 | 
					
						
							|  |  |  |       (len(snd), snd_0, snd_1, snd_2, len(can_msgs), got_0, got_1, got_2))
 | 
					
						
							|  |  |  |     m = can_list_to_can_capnp(can_msgs, msgtype='sendcan')
 | 
					
						
							|  |  |  |     sendcan.send(m.to_bytes())
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def boardd_test_loop():
 | 
					
						
							|  |  |  |   can_init()
 | 
					
						
							|  |  |  |   cnt = 0
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     can_send_many([[0xbb,0,"\xaa\xaa\xaa\xaa",0], [0xaa,0,"\xaa\xaa\xaa\xaa"+struct.pack("!I", cnt),1]])
 | 
					
						
							|  |  |  |     #can_send_many([[0xaa,0,"\xaa\xaa\xaa\xaa",0]])
 | 
					
						
							|  |  |  |     #can_send_many([[0xaa,0,"\xaa\xaa\xaa\xaa",1]])
 | 
					
						
							|  |  |  |     # recv @ 100hz
 | 
					
						
							|  |  |  |     can_msgs = can_recv()
 | 
					
						
							|  |  |  |     print("got %d" % (len(can_msgs)))
 | 
					
						
							|  |  |  |     time.sleep(0.01)
 | 
					
						
							|  |  |  |     cnt += 1
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # *** main loop ***
 | 
					
						
							|  |  |  | def boardd_loop(rate=100):
 | 
					
						
							|  |  |  |   rk = Ratekeeper(rate)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   can_init()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # *** publishes can and health
 | 
					
						
							|  |  |  |   logcan = messaging.pub_sock('can')
 | 
					
						
							|  |  |  |   health_sock = messaging.pub_sock('health')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # *** subscribes to can send
 | 
					
						
							|  |  |  |   sendcan = messaging.sub_sock('sendcan')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # drain sendcan to delete any stale messages from previous runs
 | 
					
						
							|  |  |  |   messaging.drain_sock(sendcan)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     # health packet @ 2hz
 | 
					
						
							|  |  |  |     if (rk.frame % (rate // 2)) == 0:
 | 
					
						
							|  |  |  |       health = can_health()
 | 
					
						
							|  |  |  |       msg = messaging.new_message('health')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # store the health to be logged
 | 
					
						
							|  |  |  |       msg.health.voltage = health['voltage']
 | 
					
						
							|  |  |  |       msg.health.current = health['current']
 | 
					
						
							|  |  |  |       msg.health.ignitionLine = health['ignition_line']
 | 
					
						
							|  |  |  |       msg.health.ignitionCan = health['ignition_can']
 | 
					
						
							|  |  |  |       msg.health.controlsAllowed = True
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       health_sock.send(msg.to_bytes())
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # recv @ 100hz
 | 
					
						
							|  |  |  |     can_msgs = can_recv()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # publish to logger
 | 
					
						
							|  |  |  |     # TODO: refactor for speed
 | 
					
						
							|  |  |  |     if len(can_msgs) > 0:
 | 
					
						
							|  |  |  |       dat = can_list_to_can_capnp(can_msgs).to_bytes()
 | 
					
						
							|  |  |  |       logcan.send(dat)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # send can if we have a packet
 | 
					
						
							|  |  |  |     tsc = messaging.recv_sock(sendcan)
 | 
					
						
							|  |  |  |     if tsc is not None:
 | 
					
						
							|  |  |  |       can_send_many(can_capnp_to_can_list(tsc.sendcan))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rk.keep_time()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # *** main loop ***
 | 
					
						
							|  |  |  | def boardd_proxy_loop(rate=100, address="192.168.2.251"):
 | 
					
						
							|  |  |  |   rk = Ratekeeper(rate)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   can_init()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # *** subscribes can
 | 
					
						
							|  |  |  |   logcan = messaging.sub_sock('can', addr=address)
 | 
					
						
							|  |  |  |   # *** publishes to can send
 | 
					
						
							|  |  |  |   sendcan = messaging.pub_sock('sendcan')
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   # drain sendcan to delete any stale messages from previous runs
 | 
					
						
							|  |  |  |   messaging.drain_sock(sendcan)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while 1:
 | 
					
						
							|  |  |  |     # recv @ 100hz
 | 
					
						
							|  |  |  |     can_msgs = can_recv()
 | 
					
						
							|  |  |  |     #for m in can_msgs:
 | 
					
						
							|  |  |  |     #  print("R: {0} {1}".format(hex(m[0]), str(m[2]).encode("hex")))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # publish to logger
 | 
					
						
							|  |  |  |     # TODO: refactor for speed
 | 
					
						
							|  |  |  |     if len(can_msgs) > 0:
 | 
					
						
							|  |  |  |       dat = can_list_to_can_capnp(can_msgs, "sendcan")
 | 
					
						
							|  |  |  |       sendcan.send(dat)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # send can if we have a packet
 | 
					
						
							|  |  |  |     tsc = messaging.recv_sock(logcan)
 | 
					
						
							|  |  |  |     if tsc is not None:
 | 
					
						
							|  |  |  |       cl = can_capnp_to_can_list(tsc.can)
 | 
					
						
							|  |  |  |       #for m in cl:
 | 
					
						
							|  |  |  |       #  print("S: {0} {1}".format(hex(m[0]), str(m[2]).encode("hex")))
 | 
					
						
							|  |  |  |       can_send_many(cl)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     rk.keep_time()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def main():
 | 
					
						
							|  |  |  |   if os.getenv("MOCK") is not None:
 | 
					
						
							|  |  |  |     boardd_mock_loop()
 | 
					
						
							|  |  |  |   elif os.getenv("PROXY") is not None:
 | 
					
						
							|  |  |  |     boardd_proxy_loop()
 | 
					
						
							|  |  |  |   elif os.getenv("BOARDTEST") is not None:
 | 
					
						
							|  |  |  |     boardd_test_loop()
 | 
					
						
							|  |  |  |   else:
 | 
					
						
							|  |  |  |     boardd_loop()
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == "__main__":
 | 
					
						
							|  |  |  |   main()
 |