It’s been a while since our last progress update, we’ve been rather busy with assessment at school. The worst of the assessment period is over now though, so we can focus on S.A.R.T! Now that our robot is almost built (more on that soon!), the team has been able to start focusing on the software side of the project.
As always, all our code is available on Github. Since the project’s inception, we’ve aimed to be as open source as possible! All the code for this year is in the develop branch of each repository. We did this in part because all of this year’s code is in very early stages and prone to bugs but also so the old code, which is far more useful, can be accessed easily by anyone.
In other news, one of our changes this year is to have the robot controller via controller. A trivial idea, but it turned out suprisingly difficult to implement. We hope to use the Steam controller this time round!
For those unfamilar, it has two touchpads, the analog stick, normal buttons and triggers and some cool grip buttons on the back of the controller. We chose this controller so we can use the right trackpad as a mouse, the joystick to move the robot and the other buttons as various other functions such as focusing the camera and moving specific motors.
On the client side of things, we have the SARTInterface. First of all, we need to get the data from the gamepad, format it and send it to the server, the robot. This proved to be suprisingly tough. HTML5 gamepad support is new, and the standards and APIs have changed significantly in the last few years. I had to sift through a lot of old, outdated tutorials to find something that would still work and would be good practice. Mozilla MDN pulled through, providing up-to-date documentation and a link to this example by Ted Mielczarek, which is what our final code is based off.
Once all the connection stuff is done and we have the gamepad ready to go, we read the relevant values from the controller (at the moment, just the left analog stick and the d-pad) and format it as a JSON string which is then transmitted over websockets to the robot. The final code can be found in gamepad.js.
Then on the robot side, we have servo_party_gamepad.py which handles converting the received controller values, converting them to values suitable for sending to the servos and finally, sending said values to the servos.
The websocket communication hasn’t changed from the method we did last year. The main loop runs like this:
def run(websocket, path):
buf = yield from websocket.recv()
if len(buf) > 0:
msg = json.loads(buf)
When it recieves the message (the aforementioned JSON formatted string), it calls a function called steering which handles the maths behind converting the numbers.
Converting the axis values to servo values was difficult. The servos take a value from 0 – 2048, with 0 – 1023 being clockwise and 1024 – 2048 being anti-clockwise.
The main idea is to have it so:
- If the stick is full forward or backward, run all motors at full speed
- If the stick is full left or right, have on side go forwards and the other back.
- If the stick is partially in one direction, reduce speed. Allow analog input.
- Allow for a mixture of the directions, e.g. a bit of turn and a bit of throttle.
Our algorithm was based on Pedro Wernecks’ answer to this StackOverflow question. Although we had to change a number of things, including swapping the x and y axes and flipping one of them. It was a bit of trial and error, Werneck’s code almost worked, but we had to hold the controller on it’s side! We also had to convert the values which were between -1 and 1, to the more appropriate 0 – 2048 range. I also added a deadzone, or else the robot would always be moving at a very slow speed, due to the nature of potentiometers.
Once we have some nice numbers, we check if the value is different to the last, if it is, we send it to the servos. At first we didn’t do this, but we soon discovered that the servos become heavily delayed as they try to process messages being sent to them every tick.\
Our final steering algorithm looks like this:
def steering(x, y):
y *= -1
x *= -1
if (x > -AXIS_THRESHOLD and x < AXIS_THRESHOLD):
x = 0
if (y > -AXIS_THRESHOLD and y < AXIS_THRESHOLD):
y = 0
# convert to polar
r = math.hypot(y, x)
t = math.atan2(x, y)
# rotate by 45 degrees
t += math.pi / 4
# back to cartesian
left = r * math.cos(t)
right = r * math.sin(t)
# rescale the new coords
left = left * math.sqrt(2)
right = right * math.sqrt(2)
# clamp to -1/+1
left = max(-1, min(left, 1))
right = max(-1, min(right, 1))
# Multiply by speed_factor to get our final speed to be sent to the servos
left *= speed_factor
right *= speed_factor
# Make sure we don't have any decimals
left = round(left)
right = round(right)
# Different motors need to spin in different directions. We account for that here.
if (left < 0):
left *= -1
left += 1024
if (right < 0):
right *= -1
elif right < 1024:
right += 1024
# Only send message if it's different to the last one
if (left != last_left and right != last_right):
# Store this message for comparison next time
last_left = left
last_right = right
I’ll probably make some more adjustments, I’m worried that too many messages are being sent when you move the stick. I only check for identical messages, not very similar ones.
Next we’ll make the controller do some more stuff, like focusing cameras and controlling specific motors.