|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# 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 zmq
|
|
|
|
import time
|
|
|
|
|
|
|
|
import selfdrive.messaging as messaging
|
|
|
|
from common.realtime import Ratekeeper
|
|
|
|
from selfdrive.services import service_list
|
|
|
|
from selfdrive.swaglog import cloudlog
|
|
|
|
|
|
|
|
# USB is optional
|
|
|
|
try:
|
|
|
|
import usb1
|
|
|
|
from usb1 import USBErrorIO, USBErrorOverflow #pylint: disable=no-name-in-module
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
|
|
|
|
SAFETY_NOOUTPUT = 0
|
|
|
|
SAFETY_HONDA = 1
|
|
|
|
SAFETY_TOYOTA = 2
|
|
|
|
SAFETY_CHRYSLER = 9
|
|
|
|
SAFETY_TOYOTA_NOLIMITS = 0x1336
|
|
|
|
SAFETY_ALLOUTPUT = 0x1337
|
|
|
|
|
|
|
|
# *** serialization functions ***
|
|
|
|
def can_list_to_can_capnp(can_msgs, msgtype='can'):
|
|
|
|
dat = messaging.new_message()
|
|
|
|
dat.init(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 = str(can_msg[2])
|
|
|
|
cc.src = can_msg[3]
|
|
|
|
return dat
|
|
|
|
|
|
|
|
def can_capnp_to_can_list(can, src_filter=None):
|
|
|
|
ret = []
|
|
|
|
for msg in can:
|
|
|
|
if src_filter is None or msg.src in src_filter:
|
|
|
|
ret.append((msg.address, msg.busTime, msg.dat, msg.src))
|
|
|
|
return ret
|
|
|
|
|
|
|
|
# *** can driver ***
|
|
|
|
def can_health():
|
|
|
|
while 1:
|
|
|
|
try:
|
|
|
|
dat = handle.controlRead(usb1.TYPE_VENDOR | usb1.RECIPIENT_DEVICE, 0xd2, 0, 0, 0x10)
|
|
|
|
break
|
|
|
|
except (USBErrorIO, USBErrorOverflow):
|
|
|
|
cloudlog.exception("CAN: BAD HEALTH, RETRYING")
|
|
|
|
v, i, started = struct.unpack("IIB", dat[0:9])
|
|
|
|
# TODO: units
|
|
|
|
return {"voltage": v, "current": i, "started": bool(started)}
|
|
|
|
|
|
|
|
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)&0xF))
|
|
|
|
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, '\x00')
|
|
|
|
snds.append(snd)
|
|
|
|
while 1:
|
|
|
|
try:
|
|
|
|
handle.bulkWrite(3, ''.join(snds))
|
|
|
|
break
|
|
|
|
except (USBErrorIO, USBErrorOverflow):
|
|
|
|
cloudlog.exception("CAN: BAD SEND MANY, RETRYING")
|
|
|
|
|
|
|
|
def can_recv():
|
|
|
|
dat = ""
|
|
|
|
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, SAFETY_ALLOUTPUT, 0, b'')
|
|
|
|
|
|
|
|
if handle is None:
|
|
|
|
cloudlog.warn("CAN NOT FOUND")
|
|
|
|
exit(-1)
|
|
|
|
|
|
|
|
cloudlog.info("got handle")
|
|
|
|
cloudlog.info("can init done")
|
|
|
|
|
|
|
|
def boardd_mock_loop():
|
|
|
|
context = zmq.Context()
|
|
|
|
can_init()
|
|
|
|
handle.controlWrite(0x40, 0xdc, SAFETY_ALLOUTPUT, 0, b'')
|
|
|
|
|
|
|
|
logcan = messaging.sub_sock(context, service_list['can'].port)
|
|
|
|
sendcan = messaging.pub_sock(context, service_list['sendcan'].port)
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
tsc = messaging.drain_sock(logcan, wait_for_one=True)
|
getting ready for Python 3 (#619)
* tabs to spaces
python 2 to 3: https://portingguide.readthedocs.io/en/latest/syntax.html#tabs-and-spaces
* use the new except syntax
python 2 to 3: https://portingguide.readthedocs.io/en/latest/exceptions.html#the-new-except-syntax
* make relative imports absolute
python 2 to 3: https://portingguide.readthedocs.io/en/latest/imports.html#absolute-imports
* Queue renamed to queue in python 3
Use the six compatibility library to support both python 2 and 3: https://portingguide.readthedocs.io/en/latest/stdlib-reorg.html#renamed-modules
* replace dict.has_key() with in
python 2 to 3: https://portingguide.readthedocs.io/en/latest/dicts.html#removed-dict-has-key
* make dict views compatible with python 3
python 2 to 3: https://portingguide.readthedocs.io/en/latest/dicts.html#dict-views-and-iterators
Where needed, wrapping things that will be a view in python 3 with a list(). For example, if it's accessed with []
Python 3 has no iter*() methods, so just using the values() instead of itervalues() as long as it's not too performance intensive. Note that any minor performance hit of using a list instead of a view will go away when switching to python 3. If it is intensive, we could use the six version.
* Explicitly use truncating division
python 2 to 3: https://portingguide.readthedocs.io/en/latest/numbers.html#division
python 3 treats / as float division. When we want the result to be an integer, use //
* replace map() with list comprehension where a list result is needed.
In python 3, map() returns an iterator.
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-map-and-filter
* replace filter() with list comprehension
In python 3, filter() returns an interatoooooooooooor.
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-map-and-filter
* wrap zip() in list() where we need the result to be a list
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-zip
* clean out some lint
Removes these pylint warnings:
************* Module selfdrive.car.chrysler.chryslercan
W: 15, 0: Unnecessary semicolon (unnecessary-semicolon)
W: 16, 0: Unnecessary semicolon (unnecessary-semicolon)
W: 25, 0: Unnecessary semicolon (unnecessary-semicolon)
************* Module common.dbc
W:101, 0: Anomalous backslash in string: '\?'. String constant might be missing an r prefix. (anomalous-backslash-in-string)
************* Module selfdrive.car.gm.interface
R:102, 6: Redefinition of ret.minEnableSpeed type from float to int (redefined-variable-type)
R:103, 6: Redefinition of ret.mass type from int to float (redefined-variable-type)
************* Module selfdrive.updated
R: 20, 6: Redefinition of r type from int to str (redefined-variable-type)
6 years ago
|
|
|
snds = [can_capnp_to_can_list(x.can) for x in tsc]
|
|
|
|
snds = [x for x in snds if x[-1] <= 1]
|
|
|
|
can_send_many(snds)
|
|
|
|
|
|
|
|
# recv @ 100hz
|
|
|
|
can_msgs = can_recv()
|
getting ready for Python 3 (#619)
* tabs to spaces
python 2 to 3: https://portingguide.readthedocs.io/en/latest/syntax.html#tabs-and-spaces
* use the new except syntax
python 2 to 3: https://portingguide.readthedocs.io/en/latest/exceptions.html#the-new-except-syntax
* make relative imports absolute
python 2 to 3: https://portingguide.readthedocs.io/en/latest/imports.html#absolute-imports
* Queue renamed to queue in python 3
Use the six compatibility library to support both python 2 and 3: https://portingguide.readthedocs.io/en/latest/stdlib-reorg.html#renamed-modules
* replace dict.has_key() with in
python 2 to 3: https://portingguide.readthedocs.io/en/latest/dicts.html#removed-dict-has-key
* make dict views compatible with python 3
python 2 to 3: https://portingguide.readthedocs.io/en/latest/dicts.html#dict-views-and-iterators
Where needed, wrapping things that will be a view in python 3 with a list(). For example, if it's accessed with []
Python 3 has no iter*() methods, so just using the values() instead of itervalues() as long as it's not too performance intensive. Note that any minor performance hit of using a list instead of a view will go away when switching to python 3. If it is intensive, we could use the six version.
* Explicitly use truncating division
python 2 to 3: https://portingguide.readthedocs.io/en/latest/numbers.html#division
python 3 treats / as float division. When we want the result to be an integer, use //
* replace map() with list comprehension where a list result is needed.
In python 3, map() returns an iterator.
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-map-and-filter
* replace filter() with list comprehension
In python 3, filter() returns an interatoooooooooooor.
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-map-and-filter
* wrap zip() in list() where we need the result to be a list
python 2 to 3: https://portingguide.readthedocs.io/en/latest/iterators.html#new-behavior-of-zip
* clean out some lint
Removes these pylint warnings:
************* Module selfdrive.car.chrysler.chryslercan
W: 15, 0: Unnecessary semicolon (unnecessary-semicolon)
W: 16, 0: Unnecessary semicolon (unnecessary-semicolon)
W: 25, 0: Unnecessary semicolon (unnecessary-semicolon)
************* Module common.dbc
W:101, 0: Anomalous backslash in string: '\?'. String constant might be missing an r prefix. (anomalous-backslash-in-string)
************* Module selfdrive.car.gm.interface
R:102, 6: Redefinition of ret.minEnableSpeed type from float to int (redefined-variable-type)
R:103, 6: Redefinition of ret.mass type from int to float (redefined-variable-type)
************* Module selfdrive.updated
R: 20, 6: Redefinition of r type from int to str (redefined-variable-type)
6 years ago
|
|
|
print("sent %d got %d" % (len(snds), len(can_msgs)))
|
|
|
|
m = can_list_to_can_capnp(can_msgs)
|
|
|
|
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=200):
|
|
|
|
rk = Ratekeeper(rate)
|
|
|
|
context = zmq.Context()
|
|
|
|
|
|
|
|
can_init()
|
|
|
|
|
|
|
|
# *** publishes can and health
|
|
|
|
logcan = messaging.pub_sock(context, service_list['can'].port)
|
|
|
|
health_sock = messaging.pub_sock(context, service_list['health'].port)
|
|
|
|
|
|
|
|
# *** subscribes to can send
|
|
|
|
sendcan = messaging.sub_sock(context, service_list['sendcan'].port)
|
|
|
|
|
|
|
|
# drain sendcan to delete any stale messages from previous runs
|
|
|
|
messaging.drain_sock(sendcan)
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
# health packet @ 1hz
|
|
|
|
if (rk.frame%rate) == 0:
|
|
|
|
health = can_health()
|
|
|
|
msg = messaging.new_message()
|
|
|
|
msg.init('health')
|
|
|
|
|
|
|
|
# store the health to be logged
|
|
|
|
msg.health.voltage = health['voltage']
|
|
|
|
msg.health.current = health['current']
|
|
|
|
msg.health.started = health['started']
|
|
|
|
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)
|
|
|
|
logcan.send(dat.to_bytes())
|
|
|
|
|
|
|
|
# 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=200, address="192.168.2.251"):
|
|
|
|
rk = Ratekeeper(rate)
|
|
|
|
context = zmq.Context()
|
|
|
|
|
|
|
|
can_init()
|
|
|
|
|
|
|
|
# *** subscribes can
|
|
|
|
logcan = messaging.sub_sock(context, service_list['can'].port, addr=address)
|
|
|
|
# *** publishes to can send
|
|
|
|
sendcan = messaging.pub_sock(context, service_list['sendcan'].port)
|
|
|
|
|
|
|
|
# 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.to_bytes())
|
|
|
|
|
|
|
|
# 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(gctx=None):
|
|
|
|
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()
|