One thing that struck me as I was toying with the language Go was that I was no longer limited to simply returning raw sensor data for higher level operations. Most of the sensors live at the end of an I2C bus, and the model I've been using for the last year is to create registers of 32 byte buffers that represent commands and status bytes. This is terrific for small microcontrollers like the AT Tiny 84 and 85. And it's pretty efficient for transmitting information, since I have a tendency to get as much out of every bit being sent, with fixed point numbers, bit packed structures, and the like.
But that generally doesn't work so hot for compiled languages where it's more sensible (and safer) to use variable types that fit the data. Pretty much all the sensors return data in some native unit, be it degrees of angle or temperature, or distances like inches and centimetres. To fit some values into a single 8 bit byte I sometimes scale the data to fit, or clip it and shift the result so it can fit in a 0..255 range. The penalty is then to remember to shift and scale it back later, or even remember what units it was in the first place. That would be a HUGE distraction to manage if it weren't taken care of at a lower level of code. I'd rather focus on higher levels of problem solving in higher levels of code.
That train of thought led me to wonder about what kind of overall architecture the software should have. With the shift from the smaller and slower microcontroller to the bigger/better/stronger/faster Beaglebone Black, and the use of Go instead of 'arduino-ish' code, I had to start over; and since the code would be written in Go I had to start from scratch.
The first thing I did was write a few toy problem solvers to get used to working with Go, goroutines, and channels, to see how it behaved with single threaded/single core environments. It works. With Go 1.2 about to be released with some scheduler tweaks, it should work really well. Remember that the primary reason for using Go was the notion that it handles concurrency in goroutines with very little overhead, which makes calling blocking system-level calls like bus reads/writes a net-zero cpu cost option. That isn't to say it makes those calls 'free' or 'instant', but the net cost of calling blocking functions isn't felt by the whole system as a complete vapour-lock of cpu resources.
As I looked at different patterns of Go code that did the dispatch and collection of bus level functions and where it would makes sense (or not) to use goroutines I decided that at the lowest level possible all the status data should be held in appropriate variables, and even synthesize status data from two or more sources. Similarly I was thinking in terms of splitting the code in half, such that all this low level mucking about could be contained in a hardware abstraction layer (a HAL), so that I could explore higher level code with less chance of misadventure creeping into the lower levels of code.
This basically describes two basic modules, the Executive (or EXEC) and the HAL. In theory it should be possible to swap the EXEC from 'bot to 'bot and it should still work, so long as the HAL for each hardware platform exposes a similar interface for status and commanding.
But once I write code I really hate to revisit it. And the EXEC would be complicated enough that I wanted (again) to limit the damage I could do to it over time with re-writes and debugging by making it a write-once type of adventure, but give the user a 'playground' in which to express the highest levels of code safely. But what should that look like?
I thought initially it should be just YAML files; or maybe a less strict text document. I thought about the simplicity of the language Logo, since the basic driving functions are exactly like turtle graphics, but throw in unpredictable terrain, and it gets messy. I thought about event driven programming, perhaps like class-based or object-based systems that hook events to handlers... hmmm... ya... no. Not exactly.
I started nosing around the internets for some building blocks, and found that what I was describing is ground pretty much covered by the ideas described here (it's a kinda longish read, if you really digest it). But again ... simpler.
What's really missing here is the ability to be expressive about tasks within a 'sandbox'. In fact, it should be possible to run the code in a sandbox, without a physical robot, as a simulation.
One bit that I thought might fit in with something like YAML were rules, specifically a way to define polygons that represent kinds of terrain and boundary areas. In practice I'd probably write the map editor in Processing, since I've done a lot of work in that area to pre-process satellite photos, and it has a decent XML library, so maybe not YAML, but XML.
As I worked through the logic of location based rules / geofencing, I noticed that I was defining a hierarchy of rules like "try and not drive here" or "don't ever drive here!" even "this would be a really good place to drive" (well, expressed as an array of costs at 1m resolution... anyway...). But what these really represent are examples of Asimov's 'Three Laws of Robotics".
It goes like this (hit up Wikipedia if you must):
1. Don't hurt anyone
2. Don't hurt yourself
3. Follow users instructions
I've made it super-simple for discussion here. The idea is that it's really a drop-through sanity check on proposed actions. A proposed action (or drive segment, or whatever) must conform to these rules, which can be elaborated on.
For example, hazard avoidance is likely a good #1... so it shouldn't drive into users ankles. This thing is metal, and it would really smart if it did.
Not continuing a tough uphill drive that's causing the wheel motors to overheat, or the battery to drain too low would be good examples of #2. Motors and batteries are expensive!
And staying in bounds and attempting to reach a goal cell would be peachy rules for #3. Actually it might be a good #2 as well, since not staying in bounds might let it roam onto a busy street...
You get the idea. There are several rules that could be built at least in #1 and #2 that we shouldn't ever violate, and don't need to be re-written every time. Some of it could be baked into the way the lower level routines are hooked together, either within the HAL or the sandbox wrapper, so we should never be able to cause disaster. It's probably going to end up in the library level code.
That leaves #3 - "follow instructions".
With all the major safety constraints out of the way, we should be able to simplify things to the point where the instruction set is (almost):
STOP - stop driving in any direction
MOVE - move along a path to a destination in a certain time
PIVOT - stop and turn in place to a new heading
ACTION - do other non-driving things
And that about covers it for the highest level of output commands.
Input isn't so easy... there is a LOT of data that could be exposed. It probably needs a namespace to keep it all sorted out. Maybe an XML like document describing what variable to expose from which data source, and how. Hmmm...
As for language constructs, I'm all about the simple:
JUMP / RETURN - easy as pie. Except that introduces the notion of an instruction pointer.
IF / THEN / ELSE - piece of cake. Except that introduces expression evaluation.
Forth would be SO MUCH easier. A 'single stack' (not really, but that's what the user sees), RPN notation for stack values... and jump/return is simplified at the same time. Maybe Forth-like sections that could be dynamically invoked.
I'll have to sleep on it; I don't actually know if this will all fit in the hardware...
I sense a multi-core cpu in the rovers future...