#!/usr/bin/env python3
import time
import threading
import argparse
import numpy as np
from pprint import pprint
from inputs import get_gamepad
from kbhit import KBHit
from opendbc . car . structs import CarControl
from opendbc . car . panda_runner import PandaRunner
class Keyboard :
def __init__ ( self ) :
self . kb = KBHit ( )
self . axis_increment = 0.05 # 5% of full actuation each key press
self . axes_map = { ' w ' : ' gb ' , ' s ' : ' gb ' ,
' a ' : ' steer ' , ' d ' : ' steer ' }
self . axes_values = { ' gb ' : 0. , ' steer ' : 0. }
self . axes_order = [ ' gb ' , ' steer ' ]
self . cancel = False
def update ( self ) :
key = self . kb . getch ( ) . lower ( )
print ( key )
self . cancel = False
if key == ' r ' :
self . axes_values = { ax : 0. for ax in self . axes_values }
elif key == ' c ' :
self . cancel = True
elif key in self . axes_map :
axis = self . axes_map [ key ]
incr = self . axis_increment if key in [ ' w ' , ' a ' ] else - self . axis_increment
self . axes_values [ axis ] = float ( np . clip ( self . axes_values [ axis ] + incr , - 1 , 1 ) )
else :
return False
return True
class Joystick :
def __init__ ( self , gamepad = False ) :
# TODO: find a way to get this from API, perhaps "inputs" doesn't support it
if gamepad :
self . cancel_button = ' BTN_NORTH ' # (BTN_NORTH=X, ABS_RZ=Right Trigger)
accel_axis = ' ABS_Y '
steer_axis = ' ABS_RX '
else :
self . cancel_button = ' BTN_TRIGGER '
accel_axis = ' ABS_Y '
steer_axis = ' ABS_RX '
self . min_axis_value = { accel_axis : 0. , steer_axis : 0. }
self . max_axis_value = { accel_axis : 255. , steer_axis : 255. }
self . axes_values = { accel_axis : 0. , steer_axis : 0. }
self . axes_order = [ accel_axis , steer_axis ]
self . cancel = False
def update ( self ) :
joystick_event = get_gamepad ( ) [ 0 ]
event = ( joystick_event . code , joystick_event . state )
if event [ 0 ] == self . cancel_button :
if event [ 1 ] == 1 :
self . cancel = True
elif event [ 1 ] == 0 : # state 0 is falling edge
self . cancel = False
elif event [ 0 ] in self . axes_values :
self . max_axis_value [ event [ 0 ] ] = max ( event [ 1 ] , self . max_axis_value [ event [ 0 ] ] )
self . min_axis_value [ event [ 0 ] ] = min ( event [ 1 ] , self . min_axis_value [ event [ 0 ] ] )
norm = - float ( np . interp ( event [ 1 ] , [ self . min_axis_value [ event [ 0 ] ] , self . max_axis_value [ event [ 0 ] ] ] , [ - 1. , 1. ] ) )
self . axes_values [ event [ 0 ] ] = norm if abs ( norm ) > 0.05 else 0. # center can be noisy, deadzone of 5%
else :
return False
return True
def joystick_thread ( joystick ) :
while True :
joystick . update ( )
def main ( joystick ) :
threading . Thread ( target = joystick_thread , args = ( joystick , ) , daemon = True ) . start ( )
with PandaRunner ( ) as p :
CC = CarControl ( enabled = False )
while True :
CC . actuators . accel = float ( 4.0 * np . clip ( joystick . axes_values [ ' gb ' ] , - 1 , 1 ) )
CC . actuators . torque = float ( np . clip ( joystick . axes_values [ ' steer ' ] , - 1 , 1 ) )
pprint ( CC )
p . read ( )
p . write ( CC )
# 100Hz
time . sleep ( 0.01 )
if __name__ == ' __main__ ' :
parser = argparse . ArgumentParser ( description = ' Test the car interface with a joystick. Uses keyboard by default. ' ,
formatter_class = argparse . ArgumentDefaultsHelpFormatter )
parser . add_argument ( ' --mode ' , choices = [ ' keyboard ' , ' gamepad ' , ' joystick ' ] , default = ' keyboard ' )
args = parser . parse_args ( )
print ( )
joystick : Keyboard | Joystick
if args . mode == ' keyboard ' :
print ( ' Gas/brake control: `W` and `S` keys ' )
print ( ' Steering control: `A` and `D` keys ' )
print ( ' Buttons ' )
print ( ' - `R`: Resets axes ' )
print ( ' - `C`: Cancel cruise control ' )
joystick = Keyboard ( )
else :
joystick = Joystick ( gamepad = ( args . mode == ' gamepad ' ) )
main ( joystick )