Introduction: Animatron-6: Control Program for Animatronics

About: I was previously an IT professional and a Tech Director for a theater group. When I am not working, I love to putter around - whether it be a household project or animatronics. The interest in animatronics cam…

Animatron-6 was the software I developed to control my animatronic, presented here in 2011 http://goo.gl/1Cndha . It is a compiled Basic program, which interprets a proprietary language I designed, called A-Code, for animatronic code.http://goo.gl/eBljFO

Step 1: Description

Animatron is a simple program that reads descriptive phrases and translates them into servo commands to instruct the animatronic figure what to do. As built, it depends on a serial servo controller (like the Lynxmotion SSC-32) for the final motion. It will read the phrase and translate it into a motion. 

For example, “Eyes Open” sends the SSC-32 command “#2P750” through the serial connection.

See, I think the former is much easier to remember than the latter. Similarly, we define the phrase “Eyes Closed”, as well as approximately sixty two other phrases. These description and value pairs are an motion object.

Animatron has another feature. It can play these moves in a user defined sequence and these sequences can be saved to be called again or from another part of the program. To do this, it has defined several actions to be performed on the objects. There are  motion objects and others, too. There are currently sixteen action objects plus a handful of special cases.

Step 2: Scripts

These components form a group, of an action, an object and a value. Each line represents one line in a script. Multiple lines can be combined to form a player (a script executing in parallel with other scripts.

For example, we have Eyes Closed and Eyes Open. Introducing one other command “ScriptPause, nnn” (which pauses for nnn milliseconds), we can write a script to blink the eyes for three times, pausing three seconds between blinks.

:Blinky1
PlayMove,EyesClosed,30
ScriptPause 100
PlayMove, EyesOpen,30
Scriptpause, 3000
EndScript,Blinky1



Note the “30” at the end of the line. This value gives the servo time to respond and can be tuned per servo brand, by experimentation.
Another quick script flaps its wings:

:Flappy1
PlayMove, Arms Up, 30
ScriptPause, 75
PlayMove, Arms Down, 30
ScriptPause 75
EndScript, Flappy1

:Flappy
Playscript, Flappy1
Playscript, Flappy1
Playscript, Flappy1
EndScript, Flappy



Note that this is two scripts. One to define flapping the wings and a second to flap them three times.

Action Commands
First, there are currently 16 commands which the interpreter recognizes. A summary of these commands follows. (You can also get a sense of the history of adding features, by the position in the list of each command ) If you don’t want to get bored right now with the minutiae of the commands, skip forward to the program description to see how this all works.
Command Description
PlayMove send commands to controller
PlayScript execute script in parallel; script must be defined in same file
StartScript define new script; main script MUST be last in file
EndScript end of script routine definition
JumpTo goto command; label MUST exist (is not checked for)
Label definition of label used in "JumpTo" command
SyncPoint definition of scripts which will synchronize with other script(s)
EndSync definition of step in script at which to wait for synchronization
ScriptPause "Pause" or "Delay" command; pauses execution for n milliseconds
Say play external sound file; will cause servo defined in "scbase" (e.g. mouth servo) to synchronize to sound
RandomMove Randomly perform one of the following n actions
RandomPause Pause some random time between the two times specified on the command
CallScript Call a script rather than running it in parallel in its own player
EndWait network command to clear pauses in execution at a "NetWait" point; not used in scripts
NetWait Define a point in script at which a network command can cause a pause in execution
OneOnly define a command that will exit a script if it is already running in a separate player
ActionSeq loop sequentially through command group in a script.

Step 3: Descriptions of the Commands


The following sections provide short descriptions of the commands and how the program processes them. Note the following jargon is used to describe the commands:
  • ·         Script Actionis the action of the command.
  • ·         ScriptDescription is the object of the action, or in some cases a value used in the action.
  • ·         ScriptOption is another value field, used as input to the action.
  • ·         ScriptStack is a generated value – normally containing the address of the next instruction to be executed.

Step 4: Basic Move Commands


ScriptPause is a simple command, placed in this section because it is used to pause for long times (relatively speaking) between commands. Its ScriptOption value is the number of microseconds to pause, and is added to the current timer value and stared in the PlayerEndWait() cell. As each player is given its time slice, if the current time is less than the end wait time, nothing is done and execution passes to the next script/player/process.

PlayMove contains an index to the move table, whose corresponding string is sent to the serial port to control the animatronic’s servos. Its optional value is used to extend the time before the player executes again, to allow the servo to move and other processes time to execute as well. The next script step is set to the value of ScriptStack().

PlayScript creates another player. As described, PlayerStep() is set to the index value contained in ScriptDescription() and the PlayerEndWait() is initially set to 0. Note that there is no attempt to keep the array elements sequential. There may be unused player entries as scripts finish between active players. A PlayerStep() value of 0 indicates an unused or inactive player.

CallScript is similar to PlayScript, however the script is not executed in parallel or in a player... The commands therein are executed sequentially within the current script. This is accomplished by using the ScriptStack() value of the EndScript command to point back to the current script.

Step 5: Random (and Not So Random) Moves


RandomMove takes a parameter which represents a number of moves to select from. Then, the interpreter using a random number generator to select one of the next number of commands. Here again, the ScriptStack() value is used, as each of the following commands have the value here of the next command after the block of randomly selected commands.

RandomPause uses a random number generator to pause somewhere between the minimum and maximum pause time. This is a special case where the ScriptDescription() contains something other than an index to another piece of data, but a time specified in milliseconds.

ActionSeq is not so much a random command, but in reality quite the opposite. It is used to execute a block of commands in sequence each time the script is called. Hence, if there are four commands specified, the first time the script is called, the first command in the block is run; the fourth time the script is called, the fourth command is run; the fifth time the script is called, the first command is run. I use this to have the animatronic have a conversation, over a random period of time. Here again, the ScriptStack() value of the ActionSeq command is used to keep track of which command to run next.

Step 6: Talking


The Say command uses a Windows system call to play a .wav file in the background. My animatronics take the audio from my laptop and feed it into custom circuitry on board, which plays the sound in a speaker in the figures mouth and feeds back a digital value from the controllers ADC circuit, so that the software can move the mouth in synchronization with the sound.

Step 7: Other Commands

Naming Commands
The following commands, at this point, hopefully are self-explanatory.
* StartScript       * EndScript         * JumpTo            * Label 

Advanced Features
The following features won’t be described here, but are used for network control and synchronization between scripts. Initially, I thought they would be necessary but in practice do not use them much.*
* SyncPoint        * EndSync           * EndWait           * OneOnly

Step 8:

Description of the Program
Ok, here’s the time that I am going to fess up. I’m going to skip the description of the first part of the program. The short and sweet version is that it reads the translation file (containing the phrase and the exoteric command) and the show file (containing the script definitions and what we actually want it to do), and convert all that text into four integer arrays. Tokens, I call it. It is a pre-compiler or tokenizer, which parses the input and prepares it for a compiler or in our case, an interpreter. ‘Nuff said.

The 50,000 foot description of how the program works is presented in the picture below. We’ll dive into a little deeper after we review it.

Network control, talking control and other sensors is where external environmental inputs are processed. The main control loop is where the scripted actions occur. (And be careful when calling them scripted actions, as some of the commands cause random actions to occur.) After processing, there is a pointer to the array where the main script starts (each show must have a main script). Execution starts there. Whatever action is requested, the program jumps to the appropriate section of code and executes. (For the advanced, this is done with the Select…Case structure.) Each parallel instance of a script is called a Player (like thread, process, etc…)
One of the first steps is to reserve memory for the player pointer, and its expected end time. Then the code executes and returns control to the next player to be executed. Note that the first player may not be complete.  First, each player is given time enough to execute just one line. Second, the time specified on each line may not have elapsed. So the next time this script is given control, it is checked to see if its time has expired. If not, there is more time for another script to execute. If it has, the next line in the script runs. If the script has finished (time <=0 or script pointer set to zero), the control program continues on.

For example:

If ((TimeDone(Player)) And (Execution(Player)=Running) Then
TimeDone(Player)=Done
ScriptStep=Step(Player)

Select Case ScriptAction(ScriptStep)
Case PlayMove
MoveIndex=ScriptDescription(ScriptStep)
Put #1,,MoveCommand(MoveIndex)
TimeDone(Player)=Timer+ScriptOption(ScriptStep)/1000
Step(Player)=ScriptStack(ScriptStep)

Step 9: Summary


The secret lies in coding threads old style, with time slicing. A multi-threaded compiler could be used, but cooperation between threads would have to be thought out. Also, I didn’t describe it much here, but if the code can get a socket connection over the Internet, it overrides the scripted action and takes its commands from the great Web.