I've been using UNIX (and Linux) for many years as a desktop and server operating system. But the recent trend to small Linux-based computers like the Raspberry Pi, the Beagleboard, and other similar offerings have made me rethink how I deploy some of my systems. Granted, the real-time projects haven't changed much, but a lot of systems I work with fall more into the physical computing realm than the real-time realm.
If you've used UNIX or Linux from the command line, the idea is pretty smart. Instead of making big complex tools, you make small tools that you can string together using the shell to do powerful things. This idea has resurfaced in recent years as "service orchestration," but it is an old idea that has worked well for UNIX (at least at the command-line level; similar methods for the GUI world like Dbus have not been as ubiquitous).
This approach has lots of advantages. Simple tools are — well — simple. It is much easier to debug a program that's sole purpose is to sort its input, for example, than a giant program that tries to do many things. Another advantage is that simple tools can be combined in many ways to do things you were not even thinking about when you wrote the tools. Making simple changes isn't a big problem as long as you provided enough flexibility in the tools or the changes can be made in the script.
I've been taking this approach in some of my recent embedded projects. I develop a few tools to handle the complex parts and then orchestrate them with the shell script to do the actual task.
I've even gone a step further in some cases. The shell is really just a programming language with an interactive mode. With some fancy scripting, you make a custom command prompt that "understands" your custom system.
The plan is simple:
- Create a start up script that is used to launch the new custom shell mode.
- In the script, set up a custom path that points to the system's tool scripts and programs.
- Set up a custom prompt.
- Initialize anything necessary for system operations. This could be external resources or things like aliases or shell functions.
- Execute a subshell to provide a command prompt.
To illustrate, I picked up a GP-3 serial board with a USB port. This just provides some simple analog and digital I/O over a common serial port. It has an efficient protocol that is hard to get out of sync. Normally, I would use a library and some C code and just write all the input and output steps that I wanted. But to illustrate the custom shell technique, I wrote a series of shell scripts.
Although I could have managed all the different commands for the board, I only scripted a few. In particular, the commands provided are:
- help — Print some help text for the other commands
- high — Set an output high
- low — set and output low
- inp — Return 0 or 1 based on selected input
- toggle — Set high output low or vice versa
- ledon — Turn on onboard LED
- ledoff — Turn off onboard LED
Here's the startup script:
#!/bin/bash export PORT=/dev/ttyUSB0 # could make this a param DIR=~/projects/gp3sh # could deduce this stty -F $PORT clocal -crtscts raw 57600 cs8 -cstopb export PS1="GP3>" PATH="$DIR/bin:$PATH" unset PROMPT_COMMAND # stop things like autojump and fasd bash norc -i
That's it. The tools all expect
PORT to be set (and set up). Here's the ledon script:
#!/bin/bash echo -en '\xd' >$PORT
That's all. I even considered making it a shell function. Once the startup script executes, you'll see a GP3> prompt and issuing an
ledon command will light the LED. You could even write something like the following to give you the canonical blinking light.
while true do ledon sleep 1 ledoff sleep 1 done
The only tricky part is how to handle commands like
inp that need to read from the port. I'll show you that next time, along with a caveat about why the serial port tools probably ought not to be shell scripts (even though using them for orchestration is still a good idea).