Module code.core.motor2020
Author: Jonathan Rotter
look at motor2020++.py for comments
motor2020++.py features throttling of the vertical thrusters as the ensuing power surges have cause brown-outs during the 2019 competition, requiring a full restart of the ROV
This is the old code that does not feature this throttling And cause I'm lazy and the two files are almost the same, look at the other one for comments
Required 3rd-party libraries:
autobahn
twisted
pyfirmata
Expand source code
#!/usr/bin/env python3.4
'''Author: Jonathan Rotter
look at motor2020++.py for comments
motor2020++.py features throttling of the vertical thrusters
as the ensuing power surges have cause brown-outs during
the 2019 competition, requiring a full restart of the ROV
This is the old code that does not feature this throttling
And cause I'm lazy and the two files are almost the same,
look at the other one for comments
Required 3rd-party libraries:
`autobahn`
`twisted`
`pyfirmata`
'''
from sys import version
if version[0] != '3':
raise Exception('This is Python3 code')
import webSocketClient
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'''
PORT = '/dev/ttyACM0'
'''Location where the Arduino Mega can be found for pyfirmata'''
trimUp = {
'left': 0.0,
'right': 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'''
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
'''
emergencyPower = False
'''Controls whether or not the vertical thrusters
are restrained in their power usage as the
low quality motor controllers make them cause
a voltage drop that can and has caused a brown-out
in the raspberry pis'''
if __name__ == '__main__':
import pyfirmata
# setup pyFirmata
BOARD = pyfirmata.Arduino(PORT)
# setup an iterator for safety
iter8 = pyfirmata.util.Iterator(BOARD)
iter8.start()
# locate pins
pins = [
None, # no pin 0
None, # no pin 1
BOARD.get_pin('d:2:s'), # motor 2
BOARD.get_pin('d:3:s'), # motor 3
BOARD.get_pin('d:4:s'), # motor 4
BOARD.get_pin('d:5:s'), # motor 5
BOARD.get_pin('d:6:s'), # motor 6 - vertical thruster
BOARD.get_pin('d:7:s'), # motor 7 - vertical thruster
BOARD.get_pin('d:8:s'), # motor 8 - claw
BOARD.get_pin('d:9:s'), # motor 9
BOARD.get_pin('d:10:s'), # motor 10
None, # no pin 11
BOARD.get_pin('d:12:s'), # motor 12
]
'''All pins are put into a list
so that they can conveniently be referred to
via pins[pin num]
example: pins[8].write(150)
'''
def buttonPressed(button, num):
'''Specifies what to do if a button is pressed
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, emergencyPower
# num is zero or one
if num == 0:
if button == 'LB':
trimUp['left'] += 1
trimUp['right'] += 1
elif button == 'RB':
trimUp['left'] -= 1
trimUp['right'] -= 1
elif button == 'X':
emergencyPower = True
elif button == 'Y':
emergencyPower = False
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
'''
joysticks = json.loads(data)
assert len(joysticks) == 24 # verify correct count
joystick1 = dict(zip(axis + buttons, joysticks[:12]))
joystick2 = dict(zip(axis + buttons, joysticks[12:]))
old = [] # for debugging
del data
global justPressed
stickNum = 0
for stick, jPressed in zip((joystick1, joystick2), justPressed):
for k in stick:
if k not in buttons:
continue
v = stick[k]
if v == 1 and not jPressed[k]:
# just pressed
buttonPressed(k, stickNum)
jPressed[k] = True
elif v == 0 and jPressed[k]:
# just released
jPressed[k] = False
elif v not in [1, 0]:
raise ValueError('Got {0}, expected 0 or 1'.format(v))
else:
pass
stickNum += 1
del stickNum
motor_claw = 90
# print(joystick1)
if joystick1['A'] and joystick1['B']:
pass # do nothing cause both are pressed
elif joystick1['A']:
motor_claw = 150
elif joystick1['B']:
motor_claw = 30
# motor setup
# 150 150
# /a/ \b\
# 4 ///// \\\\\ 2
#
# 150
# 5 \\\\\ ///// 3
# \d\ /c/
# 150
# ______________a_b_c_d
# Back - - + +
# Front + + - -
# Strafe Left - + - +
# Strafe Right + - + -
# Rotate Left - + + -
# Rotate Right + - - +
#
yLeft = 60 * joystick1['yLeft']
xLeft = 60 * joystick1['xLeft'] # should be strafe
global emergencyPower
if emergencyPower:
yRight = 60 * joystick1['yRight']
else:
yRight = 30 * joystick1['yRight']
# reduce power to avoid brown outs
spin = 0
joystick1['triggerRight'] = (joystick1['triggerRight'] + 1) / 2
joystick1['triggerLeft'] = (joystick1['triggerLeft'] + 1) / 2
joystick2['triggerRight'] = (joystick2['triggerRight'] + 1) / 2
joystick2['triggerLeft'] = (joystick2['triggerLeft'] + 1) / 2
if joystick1['triggerRight'] >= 0.1 and joystick1['triggerLeft'] >= 0.1:
pass # do nothing cause both are pressed
else:
if joystick1['triggerRight'] > 0.1:
# spin right
spin = joystick1['triggerRight'] * 60
if joystick1['triggerLeft'] > 0.1:
# spin left
spin = -joystick1['triggerLeft'] * 60
motor_a = 90 + yLeft - xLeft - spin
motor_b = 90 + yLeft + xLeft + spin
motor_c = 90 - yLeft + xLeft - spin
motor_d = 90 - yLeft - xLeft + spin
# mixing
# m1 = 90+forwardval+strafeval+turnval
# m2 = 90+forwardval-strafeval-turnval
# m3 = 90-forwardval+strafeval
# m4 = 90-forwardval-strafeval
global trimUp
# motors are not running at 93 instead of 90
# why? I wish I knew
motor_up_left = 93 - (trimUp['left'] + yRight)
motor_up_right = 93 - (trimUp['right'] + yRight) # thrusters reversed
def bounds(x):
'''
Ensures that 30 <= x <= 150 for the motors
The motors don't respond to higher or lower values
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 < 30:
return 30
if x > 150:
return 150
return round(x, 3)
def specialBounds(x):
'''
Ensures that 20 <= x <= 210 for the vertical thrusters
Rounds result to three decimal points
Args:
x (int): Number to ensure is within 20 to 210
Returns:
x: An int that fits 20 <= x <= 210
'''
if x < 20:
return 20
if x > 210:
return 210
return round(x, 3)
motor_a = bounds(motor_a)
motor_b = bounds(motor_b)
motor_c = bounds(180 - motor_c) # reverse # de reverse
motor_d = bounds(motor_d)
motor_up_left = specialBounds(motor_up_left)
motor_up_right = specialBounds(motor_up_right)
# right
pins[4].write(motor_a)
pins[2].write(motor_b)
pins[3].write(motor_c)
pins[5].write(motor_d)
# 150 down up, 30 move up
pins[6].write(motor_up_right) # stop on 93
pins[7].write(motor_up_left) # stop on 93
pins[8].write(motor_claw)
# print datalist
for i in range(30):
# \r is a special character that makes print
# start at the beginning of the line, overwritting
# what is already there
#
# \033 is the ascii escape character for special
# terminal instructions
# \033[A moves the cursor up a line
# \033[K clears the line
# refer to http://ascii-table.com/ansi-escape-sequences.php
#
# the point of these are to clear the terminal screen
print('\r\033[A\033[K', end='')
print('Trim: [{0}, {1}]'.format(trimUp['left'], trimUp['right']))
print(joystick1)
print(joystick2)
print(motor_a, motor_b)
print(180-motor_c, motor_d)
print()
print(motor_up_left, motor_up_right)
print(motor_claw)
# global emergencyPower
if emergencyPower:
print('-----------------------')
print('-- MAX POWER ENABLED --')
print('-----------------------')
index = 0
for i in old:
print(index, i)
index += 1
if __name__ == '__main__':
webSocketClient.start('motor', process, ip="192.168.1.2")
# assumes that motor pi has ip 192.168.1.2
Global variables
var PORT-
Location where the Arduino Mega can be found for pyfirmata
var axis-
List of axis controls on the XBox controllers
-
List of buttons on the XBox controllers
var emergencyPower-
Controls whether or not the vertical thrusters are restrained in their power usage as the low quality motor controllers make them cause a voltage drop that can and has caused a brown-out in the raspberry pis
var justPressed-
Stores which buttons are currently pressed so that code activates only once per button press and does not repeat
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
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 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, emergencyPower # num is zero or one if num == 0: if button == 'LB': trimUp['left'] += 1 trimUp['right'] += 1 elif button == 'RB': trimUp['left'] -= 1 trimUp['right'] -= 1 elif button == 'X': emergencyPower = True elif button == 'Y': emergencyPower = False 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 ''' joysticks = json.loads(data) assert len(joysticks) == 24 # verify correct count joystick1 = dict(zip(axis + buttons, joysticks[:12])) joystick2 = dict(zip(axis + buttons, joysticks[12:])) old = [] # for debugging del data global justPressed stickNum = 0 for stick, jPressed in zip((joystick1, joystick2), justPressed): for k in stick: if k not in buttons: continue v = stick[k] if v == 1 and not jPressed[k]: # just pressed buttonPressed(k, stickNum) jPressed[k] = True elif v == 0 and jPressed[k]: # just released jPressed[k] = False elif v not in [1, 0]: raise ValueError('Got {0}, expected 0 or 1'.format(v)) else: pass stickNum += 1 del stickNum motor_claw = 90 # print(joystick1) if joystick1['A'] and joystick1['B']: pass # do nothing cause both are pressed elif joystick1['A']: motor_claw = 150 elif joystick1['B']: motor_claw = 30 # motor setup # 150 150 # /a/ \b\ # 4 ///// \\\\\ 2 # # 150 # 5 \\\\\ ///// 3 # \d\ /c/ # 150 # ______________a_b_c_d # Back - - + + # Front + + - - # Strafe Left - + - + # Strafe Right + - + - # Rotate Left - + + - # Rotate Right + - - + # yLeft = 60 * joystick1['yLeft'] xLeft = 60 * joystick1['xLeft'] # should be strafe global emergencyPower if emergencyPower: yRight = 60 * joystick1['yRight'] else: yRight = 30 * joystick1['yRight'] # reduce power to avoid brown outs spin = 0 joystick1['triggerRight'] = (joystick1['triggerRight'] + 1) / 2 joystick1['triggerLeft'] = (joystick1['triggerLeft'] + 1) / 2 joystick2['triggerRight'] = (joystick2['triggerRight'] + 1) / 2 joystick2['triggerLeft'] = (joystick2['triggerLeft'] + 1) / 2 if joystick1['triggerRight'] >= 0.1 and joystick1['triggerLeft'] >= 0.1: pass # do nothing cause both are pressed else: if joystick1['triggerRight'] > 0.1: # spin right spin = joystick1['triggerRight'] * 60 if joystick1['triggerLeft'] > 0.1: # spin left spin = -joystick1['triggerLeft'] * 60 motor_a = 90 + yLeft - xLeft - spin motor_b = 90 + yLeft + xLeft + spin motor_c = 90 - yLeft + xLeft - spin motor_d = 90 - yLeft - xLeft + spin # mixing # m1 = 90+forwardval+strafeval+turnval # m2 = 90+forwardval-strafeval-turnval # m3 = 90-forwardval+strafeval # m4 = 90-forwardval-strafeval global trimUp # motors are not running at 93 instead of 90 # why? I wish I knew motor_up_left = 93 - (trimUp['left'] + yRight) motor_up_right = 93 - (trimUp['right'] + yRight) # thrusters reversed def bounds(x): ''' Ensures that 30 <= x <= 150 for the motors The motors don't respond to higher or lower values 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 < 30: return 30 if x > 150: return 150 return round(x, 3) def specialBounds(x): ''' Ensures that 20 <= x <= 210 for the vertical thrusters Rounds result to three decimal points Args: x (int): Number to ensure is within 20 to 210 Returns: x: An int that fits 20 <= x <= 210 ''' if x < 20: return 20 if x > 210: return 210 return round(x, 3) motor_a = bounds(motor_a) motor_b = bounds(motor_b) motor_c = bounds(180 - motor_c) # reverse # de reverse motor_d = bounds(motor_d) motor_up_left = specialBounds(motor_up_left) motor_up_right = specialBounds(motor_up_right) # right pins[4].write(motor_a) pins[2].write(motor_b) pins[3].write(motor_c) pins[5].write(motor_d) # 150 down up, 30 move up pins[6].write(motor_up_right) # stop on 93 pins[7].write(motor_up_left) # stop on 93 pins[8].write(motor_claw) # print datalist for i in range(30): # \r is a special character that makes print # start at the beginning of the line, overwritting # what is already there # # \033 is the ascii escape character for special # terminal instructions # \033[A moves the cursor up a line # \033[K clears the line # refer to http://ascii-table.com/ansi-escape-sequences.php # # the point of these are to clear the terminal screen print('\r\033[A\033[K', end='') print('Trim: [{0}, {1}]'.format(trimUp['left'], trimUp['right'])) print(joystick1) print(joystick2) print(motor_a, motor_b) print(180-motor_c, motor_d) print() print(motor_up_left, motor_up_right) print(motor_claw) # global emergencyPower if emergencyPower: print('-----------------------') print('-- MAX POWER ENABLED --') print('-----------------------') index = 0 for i in old: print(index, i) index += 1