|
import os |
|
import time |
|
|
|
import pygame |
|
|
|
import vale |
|
|
|
DRY_RUN = False |
|
|
|
VERT_AXIS = 1 |
|
HORZ_AXIS = 0 |
|
|
|
VERT_AXIS_DIR = -1 |
|
HORZ_AXIS_DIR = 1 |
|
|
|
CENTER_THRESH_VERT = 0.5 |
|
CENTER_THRESH_HORZ_MOVING = 0.2 |
|
CENTER_THRESH_HORZ_STOPPED = 0.5 |
|
|
|
ROTATION_TIME = 0.5 |
|
MOVEMENT_TIME = 1.5 |
|
|
|
rc = vale.RemoteControl(os.environ.get("VALE_URL")) |
|
|
|
if not DRY_RUN: |
|
rc.enter() |
|
|
|
# Define some colors. |
|
BLACK = pygame.Color('black') |
|
WHITE = pygame.Color('white') |
|
RED = pygame.Color('red') |
|
|
|
last_cmd_time = time.time() |
|
last_cmd = "stop" |
|
|
|
JS_PREVIEW_RADIUS = 150 |
|
JS_PREVIEW_CENTER = [300 + JS_PREVIEW_RADIUS, 300 + JS_PREVIEW_RADIUS] |
|
|
|
|
|
# This is a simple class that will help us print to the screen. |
|
# It has nothing to do with the joysticks, just outputting the |
|
# information. |
|
class TextPrint(object): |
|
def __init__(self): |
|
self.reset() |
|
self.font = pygame.font.Font(None, 20) |
|
|
|
def tprint(self, screen, textString): |
|
textBitmap = self.font.render(textString, True, BLACK) |
|
screen.blit(textBitmap, (self.x, self.y)) |
|
self.y += self.line_height |
|
|
|
def reset(self): |
|
self.x = 10 |
|
self.y = 10 |
|
self.line_height = 15 |
|
|
|
def indent(self): |
|
self.x += 10 |
|
|
|
def unindent(self): |
|
self.x -= 10 |
|
|
|
|
|
try: |
|
pygame.init() |
|
|
|
# Set the width and height of the screen (width, height). |
|
screen = pygame.display.set_mode((650, 650)) |
|
|
|
pygame.display.set_caption("Whack a Robot") |
|
|
|
# Loop until the user clicks the close button. |
|
done = False |
|
|
|
# Used to manage how fast the screen updates. |
|
clock = pygame.time.Clock() |
|
|
|
# Initialize the joysticks. |
|
pygame.joystick.init() |
|
|
|
# Get ready to print. |
|
textPrint = TextPrint() |
|
|
|
# -------- Main Program Loop ----------- |
|
while not done: |
|
# |
|
# EVENT PROCESSING STEP |
|
# |
|
# Possible joystick actions: JOYAXISMOTION, JOYBALLMOTION, JOYBUTTONDOWN, |
|
# JOYBUTTONUP, JOYHATMOTION |
|
for event in pygame.event.get(): # User did something. |
|
if event.type == pygame.QUIT: # If user clicked close. |
|
done = True # Flag that we are done so we exit this loop. |
|
elif event.type == pygame.JOYBUTTONDOWN: |
|
print("Joystick button pressed.") |
|
elif event.type == pygame.JOYBUTTONUP: |
|
print("Joystick button released.") |
|
|
|
# |
|
# DRAWING STEP |
|
# |
|
# First, clear the screen to white. Don't put other drawing commands |
|
# above this, or they will be erased with this command. |
|
screen.fill(WHITE) |
|
textPrint.reset() |
|
|
|
# Get count of joysticks. |
|
joystick_count = pygame.joystick.get_count() |
|
|
|
textPrint.tprint(screen, "Number of joysticks: {}".format(joystick_count)) |
|
textPrint.indent() |
|
|
|
joystick = pygame.joystick.Joystick(0) |
|
joystick.init() |
|
|
|
# Print JS info |
|
try: |
|
jid = joystick.get_instance_id() |
|
except AttributeError: |
|
# get_instance_id() is an SDL2 method |
|
jid = joystick.get_id() |
|
textPrint.tprint(screen, "Joystick {}".format(jid)) |
|
textPrint.indent() |
|
|
|
# Get the name from the OS for the controller/joystick. |
|
name = joystick.get_name() |
|
textPrint.tprint(screen, "Joystick name: {}".format(name)) |
|
|
|
try: |
|
guid = joystick.get_guid() |
|
except AttributeError: |
|
# get_guid() is an SDL2 method |
|
pass |
|
else: |
|
textPrint.tprint(screen, "GUID: {}".format(guid)) |
|
|
|
# Usually axis run in pairs, up/down for one, and left/right for |
|
# the other. |
|
axes = joystick.get_numaxes() |
|
textPrint.tprint(screen, "Number of axes: {}".format(axes)) |
|
textPrint.indent() |
|
|
|
for i in range(axes): |
|
axis = joystick.get_axis(i) |
|
textPrint.tprint(screen, "Axis {} value: {:>6.3f}".format(i, axis)) |
|
textPrint.unindent() |
|
|
|
buttons = joystick.get_numbuttons() |
|
textPrint.tprint(screen, "Number of buttons: {}".format(buttons)) |
|
textPrint.indent() |
|
|
|
for i in range(buttons): |
|
button = joystick.get_button(i) |
|
textPrint.tprint(screen, |
|
"Button {:>2} value: {}".format(i, button)) |
|
textPrint.unindent() |
|
|
|
hats = joystick.get_numhats() |
|
textPrint.tprint(screen, "Number of hats: {}".format(hats)) |
|
textPrint.indent() |
|
|
|
# Hat position. All or nothing for direction, not a float like |
|
# get_axis(). Position is a tuple of int values (x, y). |
|
for i in range(hats): |
|
hat = joystick.get_hat(i) |
|
textPrint.tprint(screen, "Hat {} value: {}".format(i, str(hat))) |
|
textPrint.unindent() |
|
|
|
textPrint.unindent() |
|
# End print JS info |
|
|
|
horz = joystick.get_axis(HORZ_AXIS) * HORZ_AXIS_DIR |
|
if last_cmd == 'stop' or last_cmd == 'straight' and last_cmd_time + MOVEMENT_TIME < time.time() or last_cmd == 'turn' and last_cmd_time + ROTATION_TIME < time.time(): |
|
thresh = CENTER_THRESH_HORZ_STOPPED |
|
else: |
|
thresh = CENTER_THRESH_HORZ_MOVING |
|
if abs(horz) < thresh: |
|
horz = 0 |
|
|
|
vert = joystick.get_axis(VERT_AXIS) * VERT_AXIS_DIR |
|
if abs(vert) < CENTER_THRESH_VERT: |
|
vert = 0 |
|
|
|
# JS crosshair |
|
pygame.draw.circle(screen, BLACK, JS_PREVIEW_CENTER, JS_PREVIEW_RADIUS, 1) |
|
pygame.draw.line(screen, BLACK, [JS_PREVIEW_CENTER[0], JS_PREVIEW_CENTER[1] - JS_PREVIEW_RADIUS], |
|
[JS_PREVIEW_CENTER[0], JS_PREVIEW_CENTER[1] + JS_PREVIEW_RADIUS]) |
|
pygame.draw.line(screen, BLACK, [JS_PREVIEW_CENTER[0] - JS_PREVIEW_RADIUS, JS_PREVIEW_CENTER[1]], |
|
[JS_PREVIEW_CENTER[0] + JS_PREVIEW_RADIUS, JS_PREVIEW_CENTER[1]]) |
|
|
|
pyg_horz = horz * HORZ_AXIS_DIR |
|
pyg_vert = vert * VERT_AXIS_DIR |
|
|
|
pygame.draw.line(screen, RED, JS_PREVIEW_CENTER, |
|
[JS_PREVIEW_CENTER[0] + JS_PREVIEW_RADIUS * pyg_horz, |
|
JS_PREVIEW_CENTER[1] + JS_PREVIEW_RADIUS * pyg_vert], 2) |
|
|
|
if not DRY_RUN: |
|
if horz == vert == 0 and last_cmd != "stop": |
|
# rc.exit() |
|
# rc.enter() |
|
rc.stop() |
|
last_cmd = 'stop' |
|
last_cmd_time = time.time() |
|
elif horz == 0 and vert != 0: |
|
timeout = 0 |
|
if last_cmd == "straight": |
|
timeout = MOVEMENT_TIME |
|
|
|
if last_cmd_time + timeout < time.time(): |
|
rc.move("forward" if vert > 0 else "backward") |
|
last_cmd = 'straight' |
|
last_cmd_time = time.time() |
|
elif vert == 0 and horz != 0: |
|
timeout = 0 |
|
if last_cmd == "turn": |
|
timeout = ROTATION_TIME |
|
|
|
if last_cmd_time + timeout < time.time(): |
|
rc.move("rotate_clockwise" if horz > 0 else "rotate_counterclockwise") |
|
last_cmd = 'turn' |
|
last_cmd_time = time.time() |
|
elif horz != 0 and vert != 0: |
|
# Sorta PWM rotation |
|
next_cmd = "forward" if vert > 0 else "backward" |
|
turn_timeout = ROTATION_TIME * abs(horz) |
|
straight_timeout = ROTATION_TIME * (1 - abs(horz)) |
|
|
|
if last_cmd == "stop": |
|
timeout = 0 |
|
elif last_cmd == "straight": |
|
timeout = turn_timeout |
|
next_cmd = "rotate_clockwise" if horz > 0 else "rotate_counterclockwise" |
|
else: |
|
timeout = straight_timeout |
|
next_cmd = "forward" if vert > 0 else "backward" |
|
|
|
if last_cmd_time + timeout < time.time(): |
|
rc.move(next_cmd) |
|
last_cmd = 'straight' if 'rotate' not in next_cmd else 'turn' |
|
last_cmd_time = time.time() |
|
|
|
# |
|
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT |
|
# |
|
|
|
# Go ahead and update the screen with what we've drawn. |
|
pygame.display.flip() |
|
|
|
# Limit to 20 frames per second. |
|
clock.tick(20) |
|
|
|
finally: |
|
if not DRY_RUN: |
|
rc.exit() |
|
|
|
# Close the window and quit. |
|
# If you forget this line, the program will 'hang' |
|
# on exit if running from IDLE. |
|
pygame.quit() |