Programming a Tank-Style Robot without ROS

Felcjo Ringo
The Startup
Published in
6 min readMar 9, 2020

--

Last year I bought a tank-style robot from Amazon that was a cheap $85. The selling point was the fact that it was basically a 2-in-1. I could program it to move its base, as well as do cool things with the 4-DoF arm. It came with all the basic sensors needed to make a decent RC and/or _basic_ autonomous robot: camera, 2x motors, 5x micro servos, ultrasonic sensor, line detector .. that’s about it. Oh, and the LEDs, which unfortunately don’t fit into the interior of the robot as there are too many wires.

The sucky part is that while the robot and its basic hardware were included, the RPi3B+ ($30), 18650 batteries ($19 .. ? wtf why can’t I use AA) were not. Add to that, I bought an XBOX360 controller ($23) to control it, as I couldn’t use the supplier’s broken GUI. So we’re really looking at ~$160 .. ugh.

Even worse, I couldn’t get the supplied GUI working on my Mac or Linux machines, and there were a lot of things that needed to be fixed in order to get this thing even remotely running. Thankfully, the sellers had a github page of their Open Sourced code that I began with.

Thus, last summer I humbly took on this project and added some much needed mid-level libraries to it, adding class functionality for the motors and servo arm. I even added a work-around to the tkinter GUI, so I could control the robot with key presses on my laptop. Once I tried to figure out how to stream the camera feed to my device, I tossed my hands up and left the robot to collect dust— I guess I can be pretty impatient.

Fast forward to a week ago and I started back up on this project. I added Xbox controller functionality (using xboxdrv — very useful!) so I could control the robot more easily. Yesterday, I studied up on ZeroMQ (zmq), which is a socket-layer API that allows the user to efficiently connect two (or more) processes/application across potentially different computers on a network. Thus I could finally get my Linux-based RPi and Mac talking to eachother. The video stream was pretty good, too, though I kept it at a nice 6 fps.

Grasping objects with the claw

Controlling the robot with the Xbox controller can be fun for a little while, but adding autonomy makes it even cooler. The supplied code came with a ‘find_color’ module, which is essentially an HSV detector and simple PID to move the robot near whatever color you specify. This code was taken from an implementation by Adrian Rosebrock on his pyimagesearch.com site (he’s fantastic btw). I got it working on the robot and set the HSV window to look for yellow things around the room. If the ultrasonic sensor, which measures distance, were of a higher quality (spoiler: it’s horrible), then the robot would stop at 0.5 meters away from the object. This module could be expanded with frontier exploration or something like that, but it does the job for now. Check out the video below to see it in action.

A few things I’ve learned when building this:

  1. Managing large, multithreaded software projects is hard. Globals, synchronization, threads all have to be meticulously taken into account. While this project is on the small side (a few thousand lines of code), it is large enough to where I’m getting a headache. When engineers get a headache, they may cut corners and not use best practices. When engineers don’t use best practices, it makes the code likelier to be riddled with bugs, and also harder to scale.
  2. Streaming encoded video over sockets is kinda cool.
  3. Object-Oriented Programming (OOP) is good in some cases, doesn’t make sense in others. Ultimately it is a design choice. For example, I chose to make the servo arm a class in python. This allows me to save the position of each servo internally so I can simply ‘add’ degrees (via PWM) in the future. In the same file (servo.py) there was a function to move the camera/ultrasonic sensor apparatus. The two devices sit fixed on a rotating servo, so it could be moved up or down. It didn’t make sense to turn the camera_angle() function into a class, but I still needed to retain state of the servo position. I ended up compromising by using globals.
  4. Software design is a skill. It takes time to learn how to do it well. I took issue with some of the design choices made in the supplied code, so I changed them to fit my style. Whether or not my choices were for the better remains to be seen, however it worked well for my coding mindset.
  5. What you sense is what you get. Not really something I learned on this robot, but when you’re dealing with faulty sensors, it makes it really difficult to program autonomy well. You can have the best algorithm around, but if you have a crappy sensor, the input to your algorithm is bad and your result will be worse.
Servo Arm class

Next Steps

I have a few next steps in mind for this project. I’ve written an implementation in JavaScript for forward and inverse kinematics for robot arms. It would be cool to get this ported to python and working on the robot. Some limitations I see are that the reach of the arm is pretty limited due to the hardware design, and its configuration space — that is the reachable set of points offered by the servo joints — is awkward to say the least.

Another thing that I would like is to build a web-based endpoint to view the published video stream. I’ve done something similar in the past with the RPi’s camera, but i can’t seem to find it on my hard drive … Anyways it shouldn’t take that long, all it needs is a python Flask server hosting the site on the same network as the robot.

This is and has been an interesting project for me. I’ve been using ROS on various projects for a while now, but I had never programmed a bare-bones robot such as this. It’s interesting to note that ROS affords you many things that take care of the headaches one may experience when programming a bare-bones robot. For example, publish/subscribe nodes are the name of the game in ROS. ROS’s architecture makes it easy for one node to publish on a topic, and for another node to subscribe and receive the data on that topic. This is done relatively effortlessly using the ROS API. However, sans ROS, there is a lot of overhead in code to get ZeroMQ (pyzmq) up and running and communicating between processes. It’s even a bigger headache to maintain all the variables and IPs and ports .. ugh.

ROS proves even more effective when it comes to debugging. Using logging levels to specify how much detail you want to print to the terminal versus the standard print() function is a big boost. As well, being able to use rqt_viewer to view video streams is a lot cleaner than writing a separate subscriber python script.

At any rate, to improve upon the architecture of this project, the logical result is to just explode what I have currently and create a separate ROS / LCM ecosystem. This might be fun for practice, but in practice it’s honestly kind of daunting. Why reinvent the wheel?

So, I’ve learned a few cool things in this project, and I have a fun robot to grab things around the house.

Thanks, and happy hacking.

--

--