Module code.mini-rov.hydrogenMiniROVClient2020
Author: Jonathan Rotter
Module for interpreting the state of the XBox controller into instructions for the motors of the mini ROV
May no longer be useful as a mini ROV was only part of the MATE 2019 Competition
Required 3rd-party libraries:
autobahn
twisted
Expand source code
#!/usr/bin/env python3.4
'''Author: Jonathan Rotter
Module for interpreting the state
of the XBox controller into instructions
for the motors of the mini ROV
May no longer be useful as a mini ROV
was only part of the MATE 2019 Competition
Required 3rd-party libraries:
`autobahn`
`twisted`
'''
if __name__ == 'mini-rov.hydrogenMiniROVClient2020':
# imported from module
import core.webSocketClient as webSocketClient
else:
import webSocketClient
import motorInterface
import json
axis = ['xLeft', 'yLeft', 'triggerLeft', 'xRight', 'yRight', 'triggerRight']
'''List of axis controls on the XBox controllers'''
buttons = ['A', 'B', 'X', 'Y', 'LB', 'RB']
'''List of buttons on the XBox controllers'''
# trim to adjust for buoyancy of the miniROV
trimUp = {
'center': 0.0
}
'''Stores the default power level of the vertical thrusters.
As the ROV does not have perfect density,
this value can be adjusted by the bumpers (LB, RB) on the controller
to keep the ROV at constant depth when at rest'''
# don't know what that means
# ||
# \/
# these are in a row
# the pins on the motor controller
m1 = motorInterface.motor(16, 26) # vertical
'''
This motor is IN3/4 on the edge of the motor controller
these pins are based on how the 2019 miniROV was wired up
'''
m2 = motorInterface.motor(12, 13) # unused
# 2nd motor controller
m3 = motorInterface.motor(27, 23)
'''object representing the side motor (maybe left)'''
m4 = motorInterface.motor(4, 18)
'''object representing the side motor (maybe right)'''
# functions for no real reason
def move1(pow):
'''Sets the power level of `m1`'''
m1.set(pow)
def move2(pow):
'''Sets the power level of `m2`'''
m2.set(pow)
def move3(pow):
'''Sets the power level of `m3`'''
m3.set(pow)
def move4(pow):
'''Sets the power level of `m4`'''
m4.set(pow)
# Used to keep track when a button is pressed and when released
# so that functions won't fire twice from one button press
justPressed = [
{
'A': False,
'B': False,
'X': False,
'Y': False,
'LB': False,
'RB': False
},
{
'A': False,
'B': False,
'X': False,
'Y': False,
'LB': False,
'RB': False
}
]
'''
Stores which buttons are currently pressed
so that code activates only once per
button press and does not repeat
'''
def buttonPressed(button, num):
'''Specifies what to do if a button is pressed.
All button related features should be added here.
Args:
button (str): The button name: ['A', 'B', 'X', 'Y', 'LB', 'RB']
num (int): The joystick number, should be either 0 or 1
'''
global trimUp
# num is 0 or 1, representing if it's the first or second xbox controller
# 0 is 1st controller
# 1 is 2nd controller
# LB and RB are used to set the trim of the miniROV
if num == 1: # 2nd controller
if button == 'LB':
trimUp['center'] += 1 # trim up when LB is pressed
elif button == 'RB':
trimUp['center'] -= 1 # trim down when RB is pressed
def process(data):
'''Uses a json file of the state of the XBox controller
to set the motors. The json passed in as `data` must have
all the labels found in axis and buttons, twice, once
for each controller. The first twelve are controller 1,
the second twelve are controller 2
Args:
data (str): A json string representing a dict of the controller's state
'''
# turn the data json string into a list
# data contains the value of the joysticks buttons/axis
joysticks = json.loads(data)
# make sure it has the right number of buttons/axis
assert len(joysticks) == 24
# axis and buttons are the labels
# match up the labels with the data values to make a dictionary
joystick1 = dict(zip(axis + buttons, joysticks[:12]))
joystick2 = dict(zip(axis + buttons, joysticks[12:]))
old = [] # for debugging
del data
global justPressed
stickNum = 0
# for each xbox controller
# (joystick1, joystick2) is a tuple of two dicts
# justPressed is a list of two dicts
for stick, jPressed in zip((joystick1, joystick2), justPressed):
# stick is a dictionary containing the labels and values of each button/axis on
# one of the joysticks
# jPressed is one of the dictionaries from the justPressed list
# for every button/axis on this xbox controller
for k in stick:
# k is the button/axis label
# ignore axis
if k not in buttons:
continue
# v is the value of button k
v = stick[k]
# if button k is pressed and wasn't pressed already
if v == 1 and not jPressed[k]:
# process that this button was just pressed
buttonPressed(k, stickNum)
# Update jPressed so that buttonPressed()
# won't fire twice for one button press
jPressed[k] = True
# if button k is not pressed but it was pressed before
elif v == 0 and jPressed[k]:
# Button k was just released
# Update that jPressed is no longer pressed
# so that buttonPressed() will activate again if it is pressed again
jPressed[k] = False
# if button k has a value other than the normal 0 or 1
elif v not in [1, 0]:
# should never happen
raise ValueError('Got {0}, expected 0 or 1'.format(v))
# if button k hasn't changed status
else:
pass
# add one to stickNum
# it is needed for buttonPressed()
stickNum += 1
del stickNum
# for mixing controls
yLeft = 50 * joystick2['yLeft']
yRight = 50 * joystick2['yRight']
# triggerRight and triggerLeft have values of -1 to +1,
# so adjust them to be 0 to +1
joystick2['triggerRight'] = (joystick2['triggerRight'] + 1) / 2
joystick2['triggerLeft'] = (joystick2['triggerLeft'] + 1) / 2
# vertical power level
vertical = 0
if joystick2['triggerRight'] >= 0.1 and joystick2['triggerLeft'] >= 0.1:
pass # do nothing cause both are pressed
else:
# the axis might not go to exact 0 so the if statements try to cut it out
if joystick2['triggerRight'] > 0.1:
vertical = joystick2['triggerRight'] * 50
if joystick2['triggerLeft'] > 0.1:
vertical = -joystick2['triggerLeft'] * 50
# Mini-ROV motor setup
# top view
# ____
# | |
# /a\| |/b\
# |____|
# (up)
#
motor_a = yLeft
motor_b = yRight
global trimUp
# add the trim to the vertical power level
motor_up = trimUp['center'] + vertical
def bounds(x):
'''
Ensures that -50 <= x <= 50 for the motors.
The maximum power setting is -100 to 100.
Rounds result to three decimal points
Args:
x (int): Number to ensure is within 30 to 150
Returns:
x: An int that fits 30 <= x <= 150
'''
if x < -50:
return -50
if x > 50:
return 50
# round the result just to have cleaner decimals
return round(x, 2)
# make sure each is within allowed bounds
motor_a = bounds(motor_a)
motor_b = bounds(motor_b)
motor_up = bounds(motor_up)
# set the motors
# which motor is which depends on the wiring of the miniROV
# This is set to the wiring of the 2019 miniROV
move1(motor_up)
move4(motor_a)
move3(motor_b)
# ----- print information -----
# clear the console window
for i in range(30):
# \r goes the the front of the line on the console
# \033[A goes up a line
# \033[K clears the line
print('\r\033[A\033[K', end='')
# display trim
print('Trim: {0}'.format(trimUp['center']))
# display joystick values
print(joystick1)
print(joystick2)
# display power levels
print(motor_a, motor_b)
print(motor_up)
print()
#
# for debugging
# anything added to the old list will be printed here
#
# !!! IMPORTANT FOR DEBUGGING !!!
# because of the screen clearing,
# nothing printed before it will actually be visible
# as it will be deleted by the screen clearing.
# thus, any debug info to print must be added to old
# so that it is printed here after the screen clearing
#
index = 0
for i in old:
print(index, i)
index += 1
# start the code
# ip should be of the surface pi
# type is 'miniROV'
if __name__ == '__main__':
webSocketClient.start('miniROV', process, ip="192.168.1.2")
Global variables
var axis-
List of axis controls on the XBox controllers
-
List of buttons on the XBox controllers
var justPressed-
Stores which buttons are currently pressed so that code activates only once per button press and does not repeat
var m1-
This motor is IN3/4 on the edge of the motor controller these pins are based on how the 2019 miniROV was wired up
var m3-
object representing the side motor (maybe left)
var m4-
object representing the side motor (maybe right)
var trimUp-
Stores the default power level of the vertical thrusters. As the ROV does not have perfect density, this value can be adjusted by the bumpers (LB, RB) on the controller to keep the ROV at constant depth when at rest
Functions
-
Specifies what to do if a button is pressed. All button related features should be added here.
Args
button:str- The button name: ['A', 'B', 'X', 'Y', 'LB', 'RB']
num:int- The joystick number, should be either 0 or 1
Expand source code
def buttonPressed(button, num): '''Specifies what to do if a button is pressed. All button related features should be added here. Args: button (str): The button name: ['A', 'B', 'X', 'Y', 'LB', 'RB'] num (int): The joystick number, should be either 0 or 1 ''' global trimUp # num is 0 or 1, representing if it's the first or second xbox controller # 0 is 1st controller # 1 is 2nd controller # LB and RB are used to set the trim of the miniROV if num == 1: # 2nd controller if button == 'LB': trimUp['center'] += 1 # trim up when LB is pressed elif button == 'RB': trimUp['center'] -= 1 # trim down when RB is pressed def move1(pow)-
Sets the power level of
m1Expand source code
def move1(pow): '''Sets the power level of `m1`''' m1.set(pow) def move2(pow)-
Sets the power level of
m2Expand source code
def move2(pow): '''Sets the power level of `m2`''' m2.set(pow) def move3(pow)-
Sets the power level of
m3Expand source code
def move3(pow): '''Sets the power level of `m3`''' m3.set(pow) def move4(pow)-
Sets the power level of
m4Expand source code
def move4(pow): '''Sets the power level of `m4`''' m4.set(pow) def process(data)-
Uses a json file of the state of the XBox controller to set the motors. The json passed in as
datamust have all the labels found in axis and buttons, twice, once for each controller. The first twelve are controller 1, the second twelve are controller 2Args
data:str- A json string representing a dict of the controller's state
Expand source code
def process(data): '''Uses a json file of the state of the XBox controller to set the motors. The json passed in as `data` must have all the labels found in axis and buttons, twice, once for each controller. The first twelve are controller 1, the second twelve are controller 2 Args: data (str): A json string representing a dict of the controller's state ''' # turn the data json string into a list # data contains the value of the joysticks buttons/axis joysticks = json.loads(data) # make sure it has the right number of buttons/axis assert len(joysticks) == 24 # axis and buttons are the labels # match up the labels with the data values to make a dictionary joystick1 = dict(zip(axis + buttons, joysticks[:12])) joystick2 = dict(zip(axis + buttons, joysticks[12:])) old = [] # for debugging del data global justPressed stickNum = 0 # for each xbox controller # (joystick1, joystick2) is a tuple of two dicts # justPressed is a list of two dicts for stick, jPressed in zip((joystick1, joystick2), justPressed): # stick is a dictionary containing the labels and values of each button/axis on # one of the joysticks # jPressed is one of the dictionaries from the justPressed list # for every button/axis on this xbox controller for k in stick: # k is the button/axis label # ignore axis if k not in buttons: continue # v is the value of button k v = stick[k] # if button k is pressed and wasn't pressed already if v == 1 and not jPressed[k]: # process that this button was just pressed buttonPressed(k, stickNum) # Update jPressed so that buttonPressed() # won't fire twice for one button press jPressed[k] = True # if button k is not pressed but it was pressed before elif v == 0 and jPressed[k]: # Button k was just released # Update that jPressed is no longer pressed # so that buttonPressed() will activate again if it is pressed again jPressed[k] = False # if button k has a value other than the normal 0 or 1 elif v not in [1, 0]: # should never happen raise ValueError('Got {0}, expected 0 or 1'.format(v)) # if button k hasn't changed status else: pass # add one to stickNum # it is needed for buttonPressed() stickNum += 1 del stickNum # for mixing controls yLeft = 50 * joystick2['yLeft'] yRight = 50 * joystick2['yRight'] # triggerRight and triggerLeft have values of -1 to +1, # so adjust them to be 0 to +1 joystick2['triggerRight'] = (joystick2['triggerRight'] + 1) / 2 joystick2['triggerLeft'] = (joystick2['triggerLeft'] + 1) / 2 # vertical power level vertical = 0 if joystick2['triggerRight'] >= 0.1 and joystick2['triggerLeft'] >= 0.1: pass # do nothing cause both are pressed else: # the axis might not go to exact 0 so the if statements try to cut it out if joystick2['triggerRight'] > 0.1: vertical = joystick2['triggerRight'] * 50 if joystick2['triggerLeft'] > 0.1: vertical = -joystick2['triggerLeft'] * 50 # Mini-ROV motor setup # top view # ____ # | | # /a\| |/b\ # |____| # (up) # motor_a = yLeft motor_b = yRight global trimUp # add the trim to the vertical power level motor_up = trimUp['center'] + vertical def bounds(x): ''' Ensures that -50 <= x <= 50 for the motors. The maximum power setting is -100 to 100. Rounds result to three decimal points Args: x (int): Number to ensure is within 30 to 150 Returns: x: An int that fits 30 <= x <= 150 ''' if x < -50: return -50 if x > 50: return 50 # round the result just to have cleaner decimals return round(x, 2) # make sure each is within allowed bounds motor_a = bounds(motor_a) motor_b = bounds(motor_b) motor_up = bounds(motor_up) # set the motors # which motor is which depends on the wiring of the miniROV # This is set to the wiring of the 2019 miniROV move1(motor_up) move4(motor_a) move3(motor_b) # ----- print information ----- # clear the console window for i in range(30): # \r goes the the front of the line on the console # \033[A goes up a line # \033[K clears the line print('\r\033[A\033[K', end='') # display trim print('Trim: {0}'.format(trimUp['center'])) # display joystick values print(joystick1) print(joystick2) # display power levels print(motor_a, motor_b) print(motor_up) print() # # for debugging # anything added to the old list will be printed here # # !!! IMPORTANT FOR DEBUGGING !!! # because of the screen clearing, # nothing printed before it will actually be visible # as it will be deleted by the screen clearing. # thus, any debug info to print must be added to old # so that it is printed here after the screen clearing # index = 0 for i in old: print(index, i) index += 1