First published: May 25, 2013
Compared to last year’s game and competition, this year’s was more frustrating.
This year’s game was focused around two aspects: Shooting Frisbees, and climbing pyramids. The game started with a 15 second autonomous mode where robots could start with up to 3 Frisbees and shoot them into large targets mounted about 5 to 8 feet above the ground. The robots could then drive around and collect more Frisbees from loading stations on the opposite ends of the field or pick them off the ground (with a max limit of 4 Frisbees at any time). The robot could also try and climb a “pyramid” with rungs 30 inches apart to earn bonus points.
Here is an explanatory video:
We decided to create a 4-wheel, 2-speed transmission robot that obtains Frisbees from the loading station (but doesn’t pick up from the floot), and shoots them using a rotating turret that can move along the x and y axises. The shooter itself comprises of a piston to shove the Frisbee into three spinning wheels which give the Frisbee both rotation and speed. We also planned to have an arm, but we never got that working. During the last few hours of the competition, we removed the turret and switched to a purely defensive robot to try and intercept Frisbees.
Mostly due to sheer luck, and strong alliance members, we placed 15th out of 50 teams at the Ellensburg Regional.
From a build perspective, the robot was a mixture of clever and horrendous. We had initially planned for an arm to climb the pyramid to the top level, but this was ultimately unsuccessful. Unfortunately, this greatly informed the design of the frame of the robot, and made it difficult to work with. However, we did win the Creativity Award at the regionals for our turret, which was fantastic. No other robot had a turret and shooter as flexible as ours: they were all either fixed, or could move in only one axis (usually the vertical y axis). Unfortunately, due to lack of practice, and lack of time to polish our robot, we ran into issue after issue with our turret that prevented us from fully realizing its potential.
From an electronics perspective, the robot was a complete nightmare. We had carved out a huge indent underneath the robot so we could climb up the diagonals of the pyramid, leaving barely any space for the electronics and pneumatics. We also wanted a lightweight design, so the turret and shooter itself was mounted as low as possible, leaving very little vertical space.
This meant that for the most part, the electronics were completely hidden away inside the robot, making it extremely difficult for us to diagnose and debug and electronics issues. For example, at the competition, our robot flaked out during one of our matches because one of the wires powering the wireless router needed to communicate with the robot fell out of its connection. This normally would have been immediately obvious and would have taken 30 seconds to fix, but ended up taking nearly an hour to diagnose and repair. We even had to call over a field technician to help debug the problem.
Luckily, for the most part, the electronics were sound. The only errors were due to the very physical nature of the competition: wires getting jostled out, or getting entangled and snapped in moving parts (the turret). I’m a bit perversely proud that I managed to get the thing working, but mostly embarrassed to be associated with it.
The code is a bit better, though. If there was one thing that characterized this year’s code, it would be how I kept having to struggle against the incorrect assumptions about the robot’s framework I had made last year and the year before that.
When I started coding this year, I wanted to make the code as flexible and adaptable as possible. One major problem that I encountered last year was that the build team kept making multiple robots, or modifying existing ones, making it difficult for the codebase to apply to different robots without hand-editing the files to make a bunch of tweaks and modifications.
I also wanted to play with the “Command-based” framework FIRST had provided last year, since it was deeply integrated with a variety of useful debugging and testing features.
To facilitate this, I split the code up into several tiers:
Objects that are provided by FIRST via the WPILib library and interact directly with specific pieces of hardware
Objects that represent a specific part of the robot, such as the drive or the shooter. I created base classes for each “type” of object, along with various abstract methods all subclasses had to define, allowing us to easily interchange one type of shooter for another, for example.
Objects that represent specific actions (or a group of actions) a robot can take using the subsystems. By linking various commands together, you can basically “script” robot behavior. Commands were an abstraction provided by FIRST, and could be called in sequence or parallel by a “Scheduler” (also provided by FIRST). They were conceptually threads, although they weren’t actually implemented as threads or processes.
An object which provided hooks to attach commands to based on various buttons that could be pressed on joysticks or Xbox controllers. Essentially, you could have a “Button” class (which was provided by the WPILib library), and attach Command objects to them that would be called when the buttons were pressed or held.
Oddly enough, WPILib did not provide the same sort of hooks for joystick axises. To mitigate this issue, I created an “Axis” class which commands could reference to poll the position of the Joystick axises when needed.
This created a weird sort of relationship: Joystick buttons would call commands, yet commands would call Joystick axises. I wanted to treat all Joystick “stuff” as part of a single class, but that didn’t turn out to be practical. Ultimately, this system worked well in practice.
Each robot was represented by a “profile” object which specified the specific types of WPILib objects a robot used, which could vary wildly from robot to robot. For example, on the robot, we used speed controllers to control the speed of motors. If I specified that I was using a “Victor” speed controller on port 8, but the hardware was actually using a “Jaguar” speed controller, the speed of the motor would be cut by about half, for example.
The profile would also specify all subsystems and commands used, and which operator interface it was using, and inject and pass on pointers to all WPILib objects, subsystems, and commands as needed. Although I had been unconsciously doing this in the past, this year I made it a deliberate practice to create all objects inside the profile object, and inject them as needed.
This system worked well, and allowed me to easily split tasks between different members of the programming team, but suffered from a few flaws. For one, it was a bit too verbose and bloated. In order to add a new subsystem, you had to create a new abstract base class in the subsystem layer and a subclass, the various commands you needed to run it, and find the appropriate hook in the operator interface layer to attach it to.
The code to spin the wheels to shoot the frisbee would have realistically taken perhaps 10-15 lines at most, counting the boilerplate to declare the WPILib objects. Under this framework, it would have taken 4 files and about 100 lines. The framework was theoretically sound, but next year should focus on balancing practicality and future expandability.
In addition, as I previously mentioned, a lot of what I was writing was contaminated by incorrect assumptions I had made in the past, especially within the Subsystem layer. For example, last year, I had made two different objects to deal with driving in Arcade mode (one joystick axis for magnitude, and another for rotation) and Tank mode (one joystick axis to control the left side of the wheels, the other for the right wheels). This year, I realized they were both dealing with the same object, and made them two methods in the Drive subsystem. Later, I realized that this was stupid, and moved them into two different command objects which would reference a Drive object.
One positive thing was that the vision tracking code actually worked, to some extent, this season. I used a program called Roborealms which was provided by FIRST. It runs on the computer, not on the robot, avoiding the issue of how the robot would freeze up due to the cRIO’s inability to process a stream of images from the camera in real-time. It also made it significantly easier to debug, since I didn’t need to physically connect to a cRIO to test my vision code. I could just hook up the camera to my laptop via ethernet, since it was an IP-based webcam. Unfortunately, there were bugs with trying to transfer data back and forth between the robot and the computer. I was running short on time, so was unable to fully debug this.
I also made a few tools to help me debug issues that would have confounded me before. Apparently, WPILib came bundled with tools to help parse the binary it produces into roughly human-readable text, allowing me to catch references to undefined symbols I would have previously only detected at runtime.
I also made a GUI tool to automatically download, build, and deploy code with a single click, for the convenience of non-programmers on our team. I was a bit proud of this, mostly because it involved me reverse-engineering WindRiver’s proprietary makefile format into a normal makefile (I did have help by looking through similar projects from other FIRST teams) and reusing WindRiver’s compiler to compile and deploy the code. WindRiver apparently uses gcc (or some variant of it), and deploying a binary was just a matter of ftp-ing it to the correct directory on the robot, but it was gratifying to be able to understand and hack together something that would have been inscrutable to me only a year ago.
Link to the robot code: http://code.google.com/p/skyline-robotics/source/browse/#svn%2F2013_MainRobot%2Ftrunkcomments powered by Disqus