Abstraction and conflict management of Pi bot commands
One point that became immediately clear upon running my Raspberry Pi remote control car was that any decent control system needed a way to accommodate new hardware and improved scripting without requiring significant, irreversable modifications to the program. The bot needed a way to accept easily upgradeable bite-size plugins to control things like moving forward or turning left–plugins that could accept a level of customization such that the GPIOs used could be arbitrary and their behavior could be subject to environmental factors like signal quality and altitude. In essence, I wanted a way to throw in a scripted-up steering servo without losing the ability to still steer with a regular hobby motor, and without leaving the unused steering scripts for the hobby motor in there to dirty up the code.
To this end I began working on a component of the car I call “the mill.” The mill’s job is to provide an API to execute “sequences,” which are objects that define how a particular physical action should occur. Each sequence contains, among other possible properties, a run script, a stop script, a list of conflicting sequences (e.g. the bot may not attempt to turn the drive motor forward and backwards at the same time), and a time-to-live value to protect against runaway on signal failure. The mill accepts requests to run sequences, safely kills off conflicting sequences, and handles the refreshing of timeouts of active sequences as control requests arrive from the client (or enforces the TTL if they don’t).
The nice thing about using the mill is that the node server remains clean. Instead of shoving the script to send quartets to a 28BYJ-48 stepper motor in with the main control script, it can be abstracted out as a sequence and included in the main script as a require. Then, calling it is as easy as mill.execute(stepperClockwise);
. Is the stepper already turning counter-clockwise? Don’t worry about it–the mill will identify the conflicting action, stop it, and execute the new action. Got a better idea for how to move the stepper? Upgrade the code within the sequence without touching the main program. Switched motors? Delete the require of this sequence and pop in a new require for the new motor’s sequence. Switched GPIOs? Just configure the sequence with goforward.pin(18);
after the require.
Beyond the benefits of not shorting out the bot and not having to chase after it when the signal fails and the drive motor doesn’t shut off, using the mill also allows commands to have relationships in the form of soft stops versus hard stops. As an example, if the steering wheels are currently turned left and a new command arrives to turn them right, that command would trigger a hard stop on the left turn sequence (i.e. the left turn sequence would safely turn off, then the right turn sequence would begin); however, if the steering is currently at 20% left and a command arrives for 80% left, performing a hard stop (steering from 20% left to straight, then to 80% left) wouldn’t make sense. In this case, the sequences can be set to have a soft stop relationship with themselves so that two different consecutive left turn commands can occur without a gap in which the steering returns back to zero. Further implications of this would be that sequences can have relationships not just with themselves, but with each other; therefore, a hard stop triggered by an expired TTL on one motor of a flying bot could trigger a hard stop on the other three motors, and that hard stop script could specify that the bot smoothly reduce motor speed over time for a soft, autonomous landing.
Enjoy a video of the mill in action :)