Java, Object Oriented Programming, BlueJ, and us
— A longer term vision of how to grow our robot’s software —
This was easier. Sheldon Cooper and I are eye-to-eye about Wil Wheaton. That and I wanted to put some notes in a really easy to access place. Probably some of what I'm about to say is wrong, but bear with me. I think we have some good stuff here.
This week’s words worth knowing:
Class - from the O’Reilly Java 8 Pocket Guide, paraphrased: Classes represent some “thing” from the real world and consist of data about that thing and methods that act on that data.
IDE - Integrated Development Environment.
Method - A subroutine or function in Java.
Object - a “thing” in object oriented programming. An instance of a class is an “object.”
UML - Unified Modeling Language. An object oriented, standard way of representing classes, dependencies, and inheritances.
First up, a little more on the general schema of our software. Remember this picture?
Brutally speaking, we read inputs, process data, and write outputs. Here, we really need to consider every output from the central blue box (the “Nav” module) as a request rather than a s a command. The command will be the thing actually written to the motor or servo interface. The distinction is that the “nav module” isn’t the place to worry about final range checking or whether or not a thing actually can be written to. (as it sits this afternoon, we're using variables called "cmd" to convey a request. We'll deal with that later).
For example, our generic chassis has two motors on each side that are ganged together. From the “Nav” point of view, we issue one “request” to the left side power and another to the right side power. It really doesn’t matter to the Nav unit whether this command will be duplicated and sent to two motors on each side versus splitting the wires so two motors are driven by one physical output. Phrasing the output of the Nav module as a request gives us exactly the freedom we need to make it happen however we choose. In fact, if we change how the drive motors are connected, we won’t need to touch the Nav code at all - only the “Write Motor Commands.”
We probably ought to discuss the architecture a little bit more some time soon, but for now, that’s the direction …
OK, on with the show:
This post represents one vision of what I think our students will be able to do either in 2018 or for the next competition season. I can’t see them getting it in place for the December competition, nor do I think they need to. BUT, if they can learn how to do most of this by the time they exit our First Tech Challenge program, they’ll have an immensely powerful set of tools at their disposal, and some practice thinking about a control system as a “data pump” - a thing that takes in data, processes it, and spits out other data.
We keep that data moving efficiently and the pump will work really, really well. If we don’t … it’s a lot like what happens with a dirty air filter, a crimped header, or valves that don’t fully open on a gasoline engine. It’ll sorta kinda work, but we’ll never get the power out of it we paid for.
So far, this year’s robots look like they’re going to have three basic devices:
- Lifter: grabs and lifts a block. Also releases a block then lowers itself back to the original position.
- Ramp: a ramp that can be raised and lowered with a treadmill that moves things up and down the ramp.
- LoaderWheel: A set of wheels that spin forward or backward to load things onto the ramp or unclog the entrance to the ramp. These wheels may have a thing that deploys them.
All three of these things are similar in that they combine a motor (or two) and a servo (or no servos, or two servos).
We can represent our basic controller (with just a lifter) with a diagram like the one below:
This diagram is a basic “UML” diagram as generated by the BlueJ IDE. BlueJ isn’t as well known and doesn’t have the features of Netbeans or Eclipse (or Intellij for that matter), but it does spit out UML diagrams auto-magically. In fact, you start by making the diagrams then populate the code files under them. And that makes it kind of cool, in a nerdy way.
In a UML diagram the dotted lines indicate a functional relationship, while the solid line with the open arrowhead (from “lifter” to “cftcDevices”) shows inheritance. You will see this information again!. The “lifter” object (remember, an object is instance of a class) inherits the properties of the cftcDevices class plus may add more. The “infra” object invokes the “DriveRobotMain” and so on.
In this view of things, the “Nav” unit is inside the “DriveRobotMain” which itself should be 80% common across all platforms - Juden Ki and Kernel Panic, driver mode and autonomous.
In this picture, note the “infra” class. Since I’m basically doing a combination of porting and re-creating the robot code in BlueJ, I needed a stub to put in the “public static void main(String[] args)” method so I can run it. That’s what I’m doing with “infra” - it’s short for infrastructure.
Similarly, the “rUtilities” class contains some useful things we don’t want to repeat all over the place. Things like first order filters, a simple PID controller, etc. There isn’t any need to make more instances of it because the only things that change from one first order filter to the next are things like filter constants - and we can parameterize those.
Anyway, this is one of the things that Object Oriented languages allow. We can create a generic class for some sort of device and as we create it, construct a generic interface to it. We can use it directly (like with “rUtilities”) or we can use it as a template for creating other objects.
And those other objects can be precise copies of the original, a subset of the original, or they can “extend” the original. That is, add new and specific methods and/or data to the original.
OR we could view our software as a collection of tasks, and decompose a generic "OpMode" (generic to both teams) into both a "DriverOp" and an "AutoOp" (with specifics added to support the differences between driver controlled and autonomous modes), then break these down into the 4 autonomous modes and the driver controlled for each team.
Like this:
What kinds of things might be good to define in our basic classes (either cftcDevices or OpMode)? For starters, we could create some standard way of interfacing to all these devices with a simple data structure. Something like a 2-dimensional array, where the first row was for motors, the second row for positional servo commands and the third row for continuous rotation servo commands, like this (notice that these are “requests” to move a device to some position, not “commands”):
Why would we bother to do this? Mostly to reduce how code we have scattered throughout the system - especially duplicated code. If we have ONE interface we use for each “set of devices” and we decide to change or add something to it, we only have to add it in ONE place, rather than eight, 16, 24, or whatever.
There are some other things. For example, motor commands can range from -1.0 to 1.0. In our cftcDevices class we ought to be able to define a range check to ensure that no command outside of that range makes it to the actual motor. We could put this in every time we calculate a motor command in every piece of code that does that but that might mean 75 places to put code, to inspect code, to test code, and to maintain the same code. It also means 75 places to forget code. We can do better.
Most importantly I think, we’re giving our students the very best we have to give since it allows us to think about our robot as a collection of modules rather than a dizzy tangle of logic threads. If we keep the interfaces clean, there is much much much less software to worry about in the week before a competition.
We might even find ourselves thinking about renaming “Kernel Panic” to “Kernel Chill.”
/s/ Howard
No comments:
Post a Comment