#!/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 ( )