-->

Saturday, March 30, 2013

We Didn't Start the Fire

...but a Digispark can sure light things up! A Digispark is a small, cheap Arduino board -- I bought a couple for $8.95 each -- which allows you to leave them in a project without feeling guilty about it or worrying too much about the cost. They are not as capable as a full-blown Arduino, but for smaller (and some medium sized) projects, they are powerful enough, and won't leave you with unused pins on the board, or have you worrying about underutilization of your hardware. Digispark was originally a Kickstarter project that I learned about through my friend 'Dillo, who has a great blog over at Roadknight Labs. I missed out on the Kickstarter, but managed to pick some up after the fact. You can order them directly here.

Incidentally, there are many other really cool Kickstarter projects out there, such as the Parallela, which is a massively parallel single board computer system (can't wait to get my hands on one!), and the 3Doodler, which allows you to draw 3D objects using the same plastics and technology as 3D printers such as the MakerBot and RepRaps. Ones I also have my eye on are the APOC Mini Radiation Detector, and the PowerPot, which allows you to produce electricity while camping by cooking food or boiling water.

What I decided to build with a Digispark was.. this glowy thing.





The basic components are a translucent light cover and a a shadow box I got at a thrift store...



...a Digispark Arduino board (which is about the size of a quarter), along with the Digispark RGB LED shield (which are shown here stacked together)...



...and a couple of switches.



The switch on the left is a numeric up/down switch, which goes from 0 to 6. The wiring on it is pretty simple -- there's a common terminal, and then three others, which indicate the current selection in binary. I couldn't find a datasheet for the switch, but a  little experimentation determined which was the common terminal.

The numeric switch with the leads attached.

From there, I wired up the Digispark directly to a USB cable. When it is being programmed, the device will be plugged directly into a computer, and under normal operation, it will be plugged into a 5v USB power supply.

The Digispark with a USB cable wired directly to it.
I found information for the wiring layout for USB here on Wikipedia. Wikipedia rocks! 

Here is a video of the digispark, wired up to the USB cable, running the demo RGB Arduino program.



From here, I soldered up the numeric switch to the Digispark board, using pins not used by the RGB shield. I used knots to indicate which line was which. The one with no knots is the common line (note -- in the picture, it is soldered to the wrong pin. I had it soldered to +5v, but it should have been connected to ground)



From here, the power switch got soldered to the board...

Adding the wires to the switch. Shrink tubing leaves everything so much better looking when you are done!


...and since the Digispark throws off so much of its power as waste heat, I decided to wire a 1K resistor in serial to the board. It works -- the board powers up with it and is still programmable, so I kept it. Here's hoping it will be a little more efficient!

I am really horrible at remembering how to decode resistor values, so, here's a nifty online calculator!

Putting it all together was fairly straightforward. One thing that needed to be considered, however, was that the Digispark board produces quite a lot of heat, and I needed to isolate it from the wood box I was mounting it on. For this, I just used a bolt and some zip ties. Holes were also cut for the two switches (and a power light), and hot glued from the back to keep them firmly in place.



From here, I decided I would rather have the base black than wood, so I used some chalkboard paint to spray paint it.



In order to not have wires dangling everywhere, I decided to close the box. For this, I repurposed a couple of coke cans...


...sealed up the seam with hot glue, and then added some rubber feet that I got at Vetco Electronics in Bellevue, WA to finish things off. (Vetco is an awesome store, by the way. Somewhat akin to Weirdstuff Warehouse in the San Francisco bay area -- both great places to go look through surplus electronics and find parts!)




And here's what we have so far, shown without the light cover:



Digisparks use a special version of the Arduino 1.x software, with addins for the board, such as the device programmer and libraries. You can download it from the wiki here, as well as find other good information about the board. One quirk I found that was, when reprogramming the board, you have to unplug the board and then plug it back in, at which point the Arduino plugin recognizes it and starts the programming process. Not a big deal, but not immediately obvious, either, and worth pointing out to anyone just getting started.

The code I wrote for it was a variation on the RGB Demo program, with other variations possible depending on the setting of the numeric switch. For instance, setting "0" is all white and as bright as it goes, "1" flickers between blue and green very rapidly, "2" is purple, and "3" is the default demo program, and so on. And the cool thing is, if I get tired of those options, it's very easy to reprogram it.

Here's the program I am currently running on it. Note that it's easily expandable using the switch() statement.




#include <DigisparkRGB.h>
#include <DigiUSB.h>

#define PIN1 5
#define PIN2 4
#define PIN3 3

byte RED = 0;
byte BLUE = 2;
byte GREEN = 1;
byte COLORS[] = {
  RED, 
  BLUE, 
  GREEN
};

// the setup routine runs once when you press reset:
void setup()  
{ 

  pinMode(PIN1, INPUT); 
  pinMode(PIN2, INPUT); 
  pinMode(PIN3, INPUT);

  digitalWrite(PIN1, HIGH);
  digitalWrite(PIN2, HIGH);
  digitalWrite(PIN3, HIGH);

  DigisparkRGBBegin();
  //DigiUSB.begin();
} 


void loop ()
{
  static bool flicker = false;
  byte val = 0;

  int pin1 = digitalRead(PIN1);
  int pin2 = digitalRead(PIN2);
  int pin3 = digitalRead(PIN3);

  flicker ^= true;

  //val = ((pin1==HIGH) ? 1 : 0) | ((pin2==HIGH) ? 2 : 0) | ((pin3==HIGH) ? 4 : 0);
  val = (pin1==HIGH ? 1 : 0)| ((pin2==HIGH) ? 2 : 0);

  //DigiUSB.println(val);
  //return;

  switch(val)
  {
  case 0:
    {
      DigisparkRGB(RED, 255);
      DigisparkRGB(GREEN, 255);
      DigisparkRGB(BLUE, 255);

      break;
    }

  case 1:
    {
      if(flicker) {
        DigisparkRGB(RED, 0);
        DigisparkRGB(GREEN, 255);
        DigisparkRGB(BLUE, 0);
      } 
      else 
      {
        DigisparkRGB(RED, 0);
        DigisparkRGB(GREEN, 255);
        DigisparkRGB(BLUE, 255);
      }

      break;
    }


  case 2:
    {
      DigisparkRGB(RED, 255);
      DigisparkRGB(GREEN, 0);
      DigisparkRGB(BLUE, 255);

      break;
    }
    
  case 3:
    {
      //DigisparkRGB(RED, 0);
      //DigisparkRGB(GREEN, 0);
      //DigisparkRGB(BLUE, 0);
      //DigisparkRGB(RED, 255);
      fade();

      break;
    }


  case 4:
    {
      if(flicker) 
      {
        DigisparkRGB(RED, 255);
        DigisparkRGB(GREEN, 255);
        DigisparkRGB(BLUE, 255);
      }
      else
      {
        DigisparkRGB(RED, 0);
        DigisparkRGB(GREEN, 0);
        DigisparkRGB(BLUE, 0);
      }
      break;
    }

  default: 
    //fade();
    break;
  }

}

void fade() 
{
  //direction: up = true, down = false
  static boolean dir = true;
  static int i = 0;

  //while(1)
  {
    fade(COLORS[i%3], dir);
    i++;
    dir = !dir;
  }
}

void fade(byte Led, boolean dir)
{
  int i;

  //if fading up
  if (dir)
  {
    for (i = 0; i < 256; i++) 
    {
      DigisparkRGB(Led, i);
      DigisparkRGBDelay(5);//1);
    }
  }
  else
  {
    for (i = 255; i >= 0; i--) 
    {
      DigisparkRGB(Led, i);
      DigisparkRGBDelay(5);//1);
    }
  }
}






And here it is, all put together.

Chalk heart drawing courtesy of my wonderful and artistic girlfriend, Charlotte.


So the veredict? Digisparks are awesome! I recommend picking up a few and playing around with them at your earliest convenience.

Here's a video of the glowy thing, running it through a couple of settings:


And another of it with the lights off (neither of the videos really do it complete justice)



And speaking of pretty lights  and glowing at night, here's a song by a group called "Pretty Lights", called "Gold Coast Hustle". It's good music to watch the glowy thing by.


Wednesday, February 20, 2013

Nature Abhors a Vacuum, and So Do My Cats

Today, I learned how to vacuum form plastic. It's pretty easy to get the general idea, but I need to refine my process. The plastic I used was a cut up milk carton, and the box is a cigar box I got at a thrift store. Here's how I did it.

1. Cut some holes in a box.


This is the original cigar box.







30 holes in the back will provide the suction to the sheet of plastic.

The large hole is where the vacuum cleaner will hook up






The holes I cut were a the corners of one inch squares. I managed to get 30 holes on the back of the cigar box. I used a 1/16" drill bit to do this. The hole on the side is for the vacuum cleaner, which will be used to suck the plastic down to the cigar box.


2. Put some junk on that box.


My girlfriend wouldn't allow me to use
 her ceramic Buddha salt shakers,
so I had to use a pair of pliers, instead.
















3. Attach a sheet of plastic to a frame






This is a cut open milk bottle, attached to a wooden frame the size of the cigar box with some thumbtacks. The thumbtacks melted a little bit on the next step.


4. Heat the plastic in the oven.


This part not shown, because it required quick back and forth from the oven before the plastic re-solidified. Basically what I did was to put the oven on broil for about five minutes, reduce to 350, and then put the frame with the plastic in. About two to three minutes later, the plastic was completely clear.


5. Suck the air out that box.


It's pretty opaque at this point,
 but when it was in the oven,
the plastic was perfectly see-through.















From the oven, I quickly moved it to the top of the cigar box, turned on the vacuum, and waited until it was again opaque.




6. Wait for it to cool, and you're done!























What I need to do from here is to figure out how to seal the plastic to the frame better, so that the suction from the vacuum doesn't have leakage.

Sunday, January 13, 2013

FINF is not FORTH!

... but it is pretty darned useful.

Today, I contributed to an open source project. I've never done that before. The project is a little command-line project for the Arduino, named FINF.

FINF is an open source partial implementation of the FORTH programming language, for Arduino. It gives you a command line on the serial port of your Arduino, and allows you to manipulate the hardware directly from the command line. For instance, you can read the GPIO lines, set the analog outs, set a PWM port, and so forth -- all without compiling or uploading any code. It's really great for rapid prototyping on the basic Arduino platform. FINF is a project started by Leandro Pereira, and he talks about the project on his blog, here.

A basic embedded FORTH on the Arduino is pretty darned cool -- however, I wanted a little bit more. For it to be really useful to me, I needed it to be able to control stepper motors and DC motors, as well as to be able to automatically run a program on startup (I store this in the Arduino's EEPROM), have variables and constants, be able to manipulate memory, and so forth. So I took Leandro's project and tweaked it. The results are up on GitHub, here: http://github.com/lpereira/finf

And Then I Bolted On Some Stuff

So, what we have now are the !, @, and ? FORTH operators, which allow memory access, the "variable" keyword, the PAD to manipulate strings, and bunch of other helpers, such as some to load programs from the EEPROM. More importantly, though, I have commands such as "forward", "back", "turn", and "motor" which allow me to control DC motors using an AdaFruit MShield motor controller, ("forward", "back", and "turn" assume a skid steer robot on one of the two banks, and "motor" directly turns the motor on at a specific speed). I also added a "step" command, which allows me to operate a stepper motor from the command line. Aside from that, I also built in some support for the SeeedStudio Relay Shield. Also, because of Leandro's original work, I have access to all the PWM ports, all the GPIO lines and analog inputs -- so I can do anything I want, such as controlling servos, reading potentiometers, monitoring pushbutton switches, motion detectors...

All this allows for some very rapid prototyping with the Arduino. Instead of writing Arduino code to try out an idea -- coding is always the longest and most involved part of these things -- I can, instead, just grab some parts, put them together, and open up a serial interface to the Arduino. From there I can just issue commands, and see if my idea works. I can't begin to tell you how freeing this is!

For instance, when I first got all this working, I snapped this together:


And then opened up a command line on the serial port of the arduino. The entire time I was doing this, I was also carrying on a conversation, explaining to my girlfriend what I was doing. In about ten minutes, I had assembled a small tank robot (I had already assembled the treads and motors part a while back), and watched it turn right, left, go forward, blink a few lights, and so forth -- as well as do a couple of patterns. No coding involved. Not one compile! 

Here's an example command line on the Arduino. I started by playing around with the platform I just built, and then I defined a command called "box". Executing "box" a couple times in a row makes the toy tank move in a square pattern.


From there, I grabbed a couple of steppers from my pile o' stuff, pulled the motor controller off the demo I had just made, plugged it into this:

About five to ten minutes and a Phillips head screwdriver later...

..and we have this. Two stepper motors hooked up to an Arduino Mega, easily controllable, no new code. From here it's a simple matter to hook up some servos, a couple of limit switches, some potentiometers, and build something.

Hey, wait a minute... is that a Whitebox Robotics PC-914 platform? With a Microsoft Kinect on it? and a Chumby Hacker Board? Why yes. Yes it is :-)


-- and controlled the stepper from the command line. All this in about 30 minutes. I don't want to even think about how much coding I would have had to do without FINF. So now, if I have an idea, I can just grab an Arduino, some parts, and go!

And Now, Some History

FORTH is a very cool language, and is very powerful. Stack based and very stack-centric, it's an early programming language which nevertheless allows for some modern paradigms such as reflection. FINF is an acronym, but FORTH is not -- even though ALL CAPS is the appropriate spelling. Originally written by Charles H. Moore, and early on used to control radio telescopes at NRAO, it has been used in many applications, such as embedded systems, CNCs, and so forth. I first used it when I was a kid, using a TRS-80 Color Computer (which still has an active community of enthusiasts), and used it to control my very first robotics project, which was a gutted Radio Shack remote control tank, wired up to two relays attached to one of the PIAs in the CoCo. It allowed me to define very English-like syntax to control the tank, where I could say things like "100 DEGREES TURN" and have it actually do it! 

If you would like to learn more about FORTH, I would highly suggest reading "Starting FORTH by Leo Brodie", which is available online. It's a classic on the language, and a lot of fun to read, as well. 

Monday, November 26, 2012

A Small Experiment in Emergent Behavior


So, I am not interested in robotics simply to create neat mechanical platforms that do interesting things (although, that's a lot of fun). I have other motives for doing this. One of the primary questions I want to answer is this: can I, as a layman, create a robotics platform, and then write code for it, that in any way models cognition, or in some way models living processes that interact with their environment in intelligent ways?

It's a big question. A really big question.

If you take away the “as a layman” phrase, some very bright people have been tackling this very problem. It starts small, with such things as the Braitenberg's vehicles, a gedanken on minimalistic open-loop control systems and artificial intelligence, and ends up with things like Asimo, the DARPA challenges, and things such as what Boston Dynamics is doing.

Along the way we meet people such as John McCarthy and the LISP programming language, other languages such as PROLOG, the concept of “strong” AI, SLAM algorithms, emergence, and many other attempts at forming machines that in some way act intelligently, attempts at understanding cognition, and so forth.

In a way, mankind has been on this search for a very long time, as evidenced by such things as the golem legends.

My latest attempt structures around emergence. Here's the basic idea:

1. Let's define a vehicle which has a limited number of moves it can do. Such as, it can move forward, back, and turn in any direction as long as the turn it makes is some multiple of 45. That means 0 degrees (no turn), 45, 90, 135, and 180, in either direction. We'll also let it turn 22.5 degrees (half of 45), so it can make small corrections.

2. For this vehicle, let's restrict the inputs. The robot will likely have some pretty varying input available on its sensors, but we'll do a bit of sensor fusion to calm down what we see and put it into buckets. For instance, let's say we have a sonar or IR sensor which has a range of 3 to 100 inches, but is sensitive in the millimeter range. Instead of returning things like 78.4589998 inches, let's put it into buckets such as “very near”, “near”, “middle range”, “far”. We'll do sensor fusion and return those strings instead of the raw numbers. Same for all the sensors. A compass sensor may, instead of returning the raw theta value for its pose, be distilled down into compass rose points. “45”, “NE”, or “Northeast” would all be fine here, as long as it's in buckets and not things like “47.35”. This will allow us to reason in more general terms about the robot's current state. We could expand this to any number of sensors and sensor types – and is similar to how the human brain generalizes things in, say, the visual cortex, where things proceed from more chaotic and raw values (retina, optic nerve, V1), to more abstract, more general values (dorsal and ventral streams on through to the prefrontal cortex/executive function areas). This would roughly model things that happen before V1, and we're not doing anything even remotely close to the PFC here. We're also being pretty simplistic – the process does not keep state of any sort, does not deal with such problems as visual flow, does not mimic working memory, etc. It just throws sensor data into buckets.

3. From here, let's create a mechanism that can define rules. Rules will consist of a set of triggers (the buckets from point #2), and a set of actions (the actions from point #1). The general idea is this: we will continually get sensor data from our environment, sorted into the buckets described above. When the distilled sensor data matches the triggers for the rule, a set of actions will be executed. In reality, getting a complete match on the sensor values we're looking for is probably not going to happen very often, so we'll define a minimum threshold – if, say, 75% of the triggers match the current sensor data, that's good enough -- run the actions. The actions will be executed sequentially. The list of actions can be arbitrarily long – but, I have found by experimenting and tweaking, for the program I wrote, two actions are ideal. The first consists of a turn (or no action), and the second consists of a movement (or no action). For instance, an action list may be something like {“turn left 45 degrees”, “go forward” }. The actions defined are simplistic (turns, forward or backward, or “don't do anything”), but could easily be more complex actions (“use the A* algorithm to find a path out of the pickle you've gotten yourself into”)

4. The next thing we'll need to do is to determine whether executing a rule actually worked, and for that we'll need a goal. After executing the rule, we will determine how close we are to the goal state, and we will also determine whether we are closer or farther away from the goal state than before we executed the rule. To do this we will measure the current state (for some arbitrary goal), execute the actions, and then, after we have executed the actions, again measure the robot's perceived state against the goal state we define, and see if things got better, got worse, or stayed the same. This will determine what's called the fitment of our rule. In the long run, rules with a better fitment will survive (and spawn new rules), while rules that have a poor fitment will be discarded. For my experiment, I decided to define the goal state as having all distance sensors be registering equal distance, but having the one pointing directly forward reporting “far”. This has the effect that, if you are in a circle looking out (all sensors are reporting the same distance except the front one, which doesn't see anything), you are in the goal state. As it turns out, the goal state is impossible to reach – which is good! That tension will make the robot come up with interesting results, and hopefully ones we have not anticipated. If so, if it does that, then this would be an example of emergence. (very cool book on related subjects: The Computational Beauty of Nature by Gary William Flake)

5. Next, we'll need a way to create new rules. There's several ways we can go about this, and it brings up the question of whether our robot has a priori knowledge of its environment, or not. Some animals do – for instance, newly born calves know how to stand within minutes of being born, insects automatically know how to fly, and so forth. On the other hand, you were not born with the ability to speak, or walk, or many other things – but you figured it out. The question here is, do we build in some base rules and watch them grow and/or be discarded, or do we start with tabla rasa, and then allow things to build? The first is faster but doesn't tell us as much, the second has the ability to be far slower and either tell us nothing, or produce very interesting results. In the end, I decided to go for the second approach – that being starting with no rules and to let things grow by themselves.

But how do we create new rules?

At first, I thought, let's assume infinite processing power and storage. Given that, and removing it as a concern, I can generate all possibilities of the input states, since they are finite (if we constrain the length of the list of triggers), and the actions we can do are also finite – after all, we are dealing with eight compass rose points we can turn to in the first slot, and then two actions, forward and backward, in the second slot. While large, the list is finite, so, let's flip things on their head a little, and start with the set of all possible rules. This approach would be similar to the approach given for linguistics by Noam Chomsky in Syntactic Structures, where he starts with the set of all possible words in a language, and then defines recursive rule sets which can generate all possible sentences in a language. His notation is also similar in form to Backus-Naur Form (BNF), although BNF generally describe finite state machines (but not always) and Chomsky's generative grammars produce infinite sets. What we are doing here is closer to a finite state machine, in that the set is indeed finite, since the length of the lists (triggers and actions) are constrained, and since the rules are not recursive (there are no subrules within rules), and the rules are essentially a directed acyclic graph (DAG) restricted to a depth of one. A good idea, but it produced a problem. Let's say I allow for just five triggers (a good idea, it turns out, since it models how the human brain focuses its attention on a limited amount of stimuli at a time), and we'll keep with just the two actions, turn and then go forward and back. We'll also say the triggers must be unique – that is, if you say you're looking for “left front sensor = very near”, there's no sense in listing it twice, so, each trigger slot holds a unique value. If we have just five sensors (for instance, front, back, left and right sonar, and a compass), and each can hold about eight values (compass rose points for the compass, variants of near, far, wherever you are for the sonar), that means that with our five slots, we have the following:


possible trigger values == 8 * 7 * 6 * 5 * 4 == 6720
possible actions == 8 *2 =16
all possible permutations == 107,520

Ok, not bad. A computer can deal with that many rules. Still, if we say that each rule takes one second to test, 107,520 seconds is about 29 hours. I can deal with that, but, let's say we add one more sensor...

possible trigger values == 8 * 7 * 6 * 5 * 4 * 3 == 20160
possible actions == 8 *2 =16
all possible permutations == 322,560
Hours to test all permutations == 89.6
Simulated skid steer robot,
showing simulated sonar sensors (the green lines),
the compass (small magenta line and circle), and
the path it has traveled (the curved line)

But, this isn't realistic. If we go past where we are now, adding a couple of sensors, or adding a new type of movement, the number of permutations skyrockets, and before long, you're dealing with centuries worth of processor time. I don't have access to a supercomputer (well, I do, actually, but that's a different story :-) ), so, I am actually limited in the amount of horsepower I can throw at it. Our original premise becomes unworkable, even thought it's a great thought experiment. Also, I have my doubts that this is the way nature works. Instead of throwing all possible permutations at a problem, I have a hunch that nature takes shortcuts, and figures out the permutations that have a chance of working. In fact, ACO and PCO are good examples of just that.

So, instead of trying all possible permutations, what I did is take some tried and true methods, but with a twist.

To generate the first rules, again, without a priori knowledge, I used a Monte Carlo method for the actions, and empirical sensor readings for the triggers. For instance, given a set of previously unencountered sensor readings, I would randomly generate a response, first randomly generating a turn (or deciding to do nothing), and then randomly deciding on a movement (forward or back). From there, we measure the fitment and cull bad rules. From the survivors, I used a combination of genetic algorithms, mutation of rules (such as, randomly delete a trigger entry, add a new one, randomly change a resulting action), and hill climb. These are all fairly common ways to solve this problem, but, it occurred to me, this is how nature evolves organisms, but, it's likely not how they learn – or, at the least, it's not the entire picture. Evolution is glacial, and learning is not. So, that spawned a couple of things. Firstly, this layman needs to learn more about how organisms learn, and secondly, how can I alter the program so that learning is not so bumbling and glacial, which is what I got at first by using GA's and hill climbing? My response to the second was to do what I called “hill climb in place” – I made each of the possible actions have an “undo”. For instance, turning left 45 degrees is undone by turning right 45 degrees. Forward is undone by backing up. And so on. Once that is in place, it allows the robot to do trial and error. Given a completely randomly generated set of actions, it can gradually move from the action it has, measure how well it is in relation to the goal, and in relation to its initial state, undo, and try again – gradually hill climbing, in place, to reach local optima, if any. Trial and error! Organisms do that, and it's not excruciatingly slow, and it works! One other tweak needed to be in place, though – actions need a cost. For instance, turning 180 degrees should cost more than turning 22. Once that is in place, it makes sense to try things out in ascending order according to cost, until you get to a state where your measurement against the desired goal went down, instead of up. From there, simply undo what you did last, and there you are – the best thing you could have done for the state you are in is the thing you did right before the thing you did that screwed everything up :-)



/// <summary>
/// The hill climb in place routine
/// </summary>
/// <returns>A rule aimed towards a local optimum</returns>
private static RuleAction HillClimbSingleActionInPlace(Rule rule, RuleAction action, int position, IRuleInformationProvider provider, Func<IEnumerable<Evidence>> getEvidence)
{
    RuleAction result = action;
    double lastBestResult = provider.GetCurrentScore();

    var possibleActions = provider.GetPossibleActionsByActionListPosition()[position]
                            .Where(action1 => action1.Value.CanUndo)
                            .Select(action1 => action1.Value)
                            .OrderBy(ruleAction => Math.Abs(ruleAction.Cost - action.Cost));

    foreach (var candidateAction in possibleActions.ToList())
    {
        var runResult = candidateAction.Action(rule, getEvidence());
        double currentScore = provider.GetCurrentScore();

        candidateAction.Undo(rule, getEvidence());

        if (currentScore >= lastBestResult)
        {
            lastBestResult = currentScore;
            result = candidateAction;
        }
        else break;
    }

    return result;
}




So, that's the outline of the program. A limited set of triggers and actions, which we use to define rules, and then we use Monte Carlo, genetic algorithms, random mutation, hill climbing, and a tweaked version of hill climbing which allows us to do trial and error, in place, and figure out the best action to take.

One other note: groups of neurons fatigue after a while. After firing repeatedly for a while, their signal strength degrades. This serves multiple purposes in the brain, and I imitate it here. In the program, it means that, even if a rule is a good fit for a situation, if it fires too many times in a row, it has to sit down and be quiet for a while, and allow other rules to have a chance.

So does it work?

Yep. Sure does.

I created a simulation of a robot in a maze, and what I have observed is this:


Early on, I see completely stochastic movement, and a lot of bad decisions, such as running into walls, turning in a direction that is bad, and so on. If I let it run for a while, say an hour or so, I start to see good decisions. I also see elegant motion. For instance, instead of jerkily moving away from a wall, I will instead see the robot moving in arcs and curves, smoothly going around obstacles or following a wall. Keep in mind, I never once told it how to follow a wall or how to move in an arc. This is all emergent behavior, derived from simpler building blocks. The robot came up with these solutions to its problems by itself, and those solutions mimic nature in form by the process of emergence. Since the rules fatigue, I have also seen the simulation fall into patterns of rules, or repeating sets of rules. For instance, instead of sitting in one spot and running a rule over and over (in which case, it will quickly fatigue), the robot would instead move in patterns that look like a five pointed star, or a small circle, where it executes one rule, and then another, and so on, eventually coming back to a spot where the first one again can fire. In this way, it takes a much, much longer for a rule to fatigue, since it has a rest of five turns in between when it is triggered. Again, this behavior is emergent – there is nothing in the programming which allows for patterns of rules, and, in fact, the program is generally stateless. Yet repeating patterns and interlocking sets of rules arise.


Robot simulation executing organic-like, curved paths in search of its goal state


So this is learning, in miniature, in a way. Both for me and for the robot. It points out how much can be made from small, simple pieces, given a mechanism to allow for emergent behavior. It shows the tendency of life to move away from entropy and towards structure.

And it shows me how much more I need to learn.


Saturday, September 8, 2012

FR3DDY

(addendum - FR3DDY got written about on Hack a Day! Awesome!)

So I've been up to some interesting stuff lately. Let me introduce you to FR3DDY.



Freddy (easier to type) is a Heathkit HERO 1 robot, model ETW-18. The HERO robot series are some of the quintessential examples of early personal robotics, and which many roboticists have drawn inspiration from, such as the White Box Robotics  group of robots (which now uses the Heathkit name), and, of course, my very own HouseBot. The ETW-18 model was the factory assembled model, whereas the ET-18 was the do-it-yourself kit.

When I was a kid, it was every nerd's dream to own one of these. But they were very expensive! Now that I'm older, and thanks to eBay, I can. They're occasionally available, but, they are all antiques. Some are in better shape than others. Freddy's in pretty good shape, but does need some work here and there.

So why the name Freddy? When you turn this old boy on, one of the first things it does is say "Ready!" in an old 1980's style, shall-we-play-a-game, War Games type voice. My girlfriend thinks it sounds like "FREDDY", so, Freddy it is, and "FR3DDY", just to emphasize the computer generated nature of it all.

video

Freddy saying "Freddy"!


Another thing that Freddy says from time to time is "LOW VOLTAGE", which comes out sounding like "NO CARNAGE". Coming from a robot, it's awfully nice to know that it doesn't intend to cause murder and mayhem. Freddy's a good robot that way.

video
"LOW VOLTAGE" or "NO CARNAGE"? You decide...



As one of the defining examples of early personal robotics, Freddy is, of course, a restoration project. First and foremost, he needs to be brought back to top running condition. And, in fact, when he came to me, he was not doing that bad. I've had to work a little on the sonar, and he's got some minor problems with the main drive wheel, and there were some missing outer side panels (since replaced), but in general, not too bad -- not too bad at all for being approximately 30 years old.

Work continues on the restoration project, but what fun is just doing that? The ET-18 and ETW-18 robots were primarily programmable in machine code, by directly entering bytecodes through the hex numeric keyboard on the robot's head. It's exactly as awful as it sounds, maybe more so! So what I did is contact this guy, who sold me an upgrade kit for the HERO BASIC ROM, plus the memory upgrade needed to support it. You also need some sort of serial or USB connection to hook to some sort of terminal. I generally use my laptop and puTTY for this. Installation was pretty simple -- it mostly just snapped in to existing ports inside the robot.

So that's cool, but, of course, not great. I'd love to be able to work with this machine using some more modern programming paradigms than a 30 year old dialect of BASIC.

I want to program it in Python.

And I don't want to have some huge wire running to it across the floor. So I'll need wifi or xbee of some sort. The problem here is, Freddy *is* a restoration project, so anything that I do has to be easily reversible, and as non-invasive to the original hardware as possible.

So enter OpenWrt. This is where the fun stuff begins! As you may know, a lot of routers these days are hackable. You can load OpenWrt, dd-WRT, Tomato, and so forth on your router, and turn a kind of clunky interface and router into a sleek, linux-driven router capable of doing a lot more than what it could just out of the box. My personal favorite for just being a router is Tomato. Unfortunately, Tomato has a read only file system, and, while it can be made writeable, it means compiling your own kernel, which means installing the toolchain, which means.. yeah... nah, let's find another route for now.

So what I did is use OpenWrt.  OpenWrt is perhaps a bit more basic than some others, as far as the interface, but it's very capable, and will certainly allow you to hack and tweak your router to how you want it, including having a writeable file system, allowing you to SSH and Telnet into the router, and allow you to install packages (through the opkg package manager).

Now, I need a router that has a serial port. Many have serial ports built in. I lucked out here -- I walked into my local thrift store, and found a WRT54GS, just sitting there waiting to be hacked! Five bucks! I grabbed it as quickly and as calmly as I could, and made for the door. I have an awesome thrift store near me, where I find all sorts of stuff like this --  and wild horses won't drag out of me where it is!


Freddy's new auxiliary brain, partially hacked


The WRT54xx models are the ones that started all of this hackable router business, and it has two built in serial ports, although you have to solder headers on them and do level conversion in order to use them. They also have several GPIO lines, although they're pretty much tied up monitoring the switches and running the lights on the router. If it's an open router thing you want to do, you can probably do it on one of these devices or one of their descendants -- although the early ones aren't so great in the memory department. (They didn't need to be -- they're just routers!)

So, after an install of OpenWrt, some configuring of the router to put it in client mode and have it automatically log into my house's wifi, allow for ssh and telnet login, and install of some packages, I now have a router that has a pared down version of Python on it, the PySerial library (to talk to Freddy through the serial port), and minicom (a hyperterm type program), nano (because the vi editor irks me), and is running BusyBox Linux.


Freddy's router up and running, logged in remotely via SSH, and doing some communication tests


Using the arduino software's built in serial port monitor, I can respond across the router's serial port


Plenty of this was needed - that really is the size of a soup bowl!


This is the same - or better - than some less capable and more expensive boards I've played around with in the past, such as the TS-7800 -- and with wifi, two serial ports, five ethernet ports, and a bunch of GPIO lines built in. Ready to be hooked up to Freddy!




The router, with wires soldered onto the serial port headers, 
and a USB FTDI Pro from CKDevices on port 0, and a BrainStem MAX232 level converter on port 1.

Some soldering on the router, and I have wires coming from the serial ports. Information on how to solder serial headers to your WRT54G, as well as a lot of other information about the WRT54G router, can be found here. Max232 serial converters can be found a lot of different places, but the one I used for this was the BrainStem level converter from Acroname Robotics. Serial port 0 (/dev/tts/0) is the debug port, where you can see the router boot sequence, telnet to a shell, and so forth. /dev/tts/1 is free to use, so, I can talk to Freddy on that, using either minicom, or writing programs in Python which send commands to the serial port. I could also hook up another device on /dev/tts/0, such as an arduino to run some sensors, but the arduino would have to be tolerant of the stream of data coming from the router during bootup -- probably by waiting for a specific code before it started interacting with the router.

So good enough, we have a little computer that runs linux, has wifi, and has Python installed on it -- although not all the batteries are included, as the Python philosophy would have us do. As you might surmise, the router doesn't have a whole lot of space on it, and after installing the bare minimum I need, I only have about 1.5 MB free. Fortunately, you can expand the storage it has by using an SDIO card, but that's a project for another day.

The next thing to do is to install this in Freddy, with minimal to no impact to the original machinery. The router normally runs off of 5 volts at 2.5 amps, but a little experimentation proved that it could do just fine on less than half that amperage. I assume the antenna output is less, but so far haven't seen any problems with it. What remains is to find a steady source of 5 volts on Freddy, plug up the serial port, and find a place for the router to live.

Always good to have handy...
A little bit of experimentation, and I found that the main processor board had a nice supply of 5 volts at a reasonable amount of amperage, and running the router off this line did not interfere with the main board in the robot.

If you blow a fuse while experimenting, wrapping it with tinfoil to make it work again is not the correct solution. This is a bad example and you should never do this. (it sure works in a pinch, though!)

Next was finding a place to mount the router. I had hoped to find a place on the lower chassis to mount it, perhaps with a little velcro, but the router was too thick. I ended up mounting it in the head, underneath the keyboard.

This almost worked out, not quite...


The router, mounted under the keyboard


The cover on, and the wire to the serial port hooked up


All put back together, my cat gives Freddy an inspection


And there you have it! A ~30 year old robot, accessible through wifi, capable of running Python (not to mention a built in, extendable web site run by the Lua programming language), and with minimal impact to the original hardware!

Just to watch it work, here's a demo of Freddy running a short Python script.

video


And here's the code:

import serial
from serial import *

import time

port = Serial(port='/dev/tts/1', baudrate=9600, bytesize=SEVENBITS, parity=PARITY_EVEN, stopbits=STOPBITS_ONE, timeout=1,  interCharTimeout=0.25)

def read():
 result = port.read(port.inWaiting())
 return result
 
def wait():
 i = 0
 while port.inWaiting() == 0 and i < 100:
    pass
  
def write(line):
 for c in list(line):
  port.write(c)
  time.sleep(0.05)

def writeln(line):
 write(line + '\r')
 time.sleep(1)

def execute(command):
 writeln(command)
 wait()
 return read()

def get(command):
 result = execute(command)
 return str(result.split('\r\n')[1])
 
execute('dprint "$hello"')
execute("head=45")
time.sleep(5)
execute('speak 64709')
time.sleep(5)
execute("head=0")
time.sleep(5)
execute('dprint "$freddy"')

print "run complete."

Thursday, August 23, 2012

In which I bark orders at a robot, and it actually listens!


So, here's my latest adventure with HouseBot. Since I have the Kinect, and since the Kinect has directional microphones in it, I decided to do a little experimenting around with the Microsoft Speech SDK. In an earlier iteration of HouseBot (well, same platform, much less powerful computer, much more finicky drive wheels.. lots of improvements since then), I had also played around with this, but without quite as much success. The main reason for that was that I was using a microphone plugged into the computer directly, instead of using the Kinect microphones. With the other microphone, you basically had to be right on top of it (and sometimes shout) in order to get it to respond. With the Kinect, since it is designed for gaming (et al), you can be across the room and still have the mic respond.

So, I think I'll just jump right into the finished result, and then do a little of a dive into how it's done. Here's a film of HouseBot responding to voice commands.



As you can see, she's still not going to get me a beer. *sigh*... Science, such a harsh mistress you are..


So how does it work?

In order to do something like this, you need to do a couple of things:

1. Build a robot
2. Build a vocabulary of the expected commands
3. Stream audio from the Kinect to the voice recognition code
4. On recognizing a command, take some action

Underneath it all, it is using the Microsoft Speech SDK, the Kinect for Windows SDK, and the Kinect For Windows Developer Toolkit (more info here). The code shown here, at least the setup code in the recognizer object, is an adaptation of the C# speech recognition example in the toolkit. I highly recommend the toolkit! I wrote all this in C# using Visual Studio 2010 , but there's no reason why this couldn't be developed in either one of the express editions, or, in one of my favorite freeware products, SharpDevelop (which is also great for developing in IronPython!)

So keep in mind, there's a lot of support code for the robot that I'm not going to show here. It'll be fairly obvious where I'm calling into the robot, but some of the basic idea is that the robot has various tools attached to it (objects that implement ITool), such as the light, the turret, and the speech generator. The robot itself is a platform (implements IMobilityPlatform) and a sensor provider (implements ISensorProvider). So, if you see something that says "UseTool", that method call is asking the tool to do its core action or some alternate action, and  if you tell a platform to turn or move forward, that's the robot itself. Some tools, such as the turret or voice generation tool also have some specialized actions -- such as "Say()" on the voice tool.

What it looks like when you are consuming all this is that first, we'll do a little setup:


      ITool recognizer;
      ITool light;
      KinectViewAngle kinectAngle;
      ArduinoStepperMotor turret;
      Voice voice;
      StringSensor speech;
      ObjectSensor objectSensor;
      Robotics.Framework.Oscillators.Timeout timeout = new Robotics.Framework.Oscillators.Timeout(new TimeSpan(hours: 0, minutes: 0, seconds: 2));

      public override void Setup()
        {
            recognizer = (Platform as IToolProvider).Tools.Where(tool => tool.Name == "Speech Recognizer").First();
            turret = (ArduinoStepperMotor)((Platform as IToolProvider).Tools.Where(tool => tool.Name == "Head Position Motor").First());
            voice = (Voice)((Platform as IToolProvider).Tools.Where(tool => tool.Name == "Speech").First());
            speech = (StringSensor)((Platform as ISensorProvider).Sensors["Speech Recognition"]);
            light = ((Platform as IToolProvider).Tools.Where(tool => tool.Name == "Light").First());
            kinectAngle = (KinectViewAngle)((Platform as IToolProvider).Tools.Where(tool => tool.Name == "Kinect View Angle 1").First());
            objectSensor = (ObjectSensor)((Platform as ISensorProvider).Sensors.Values.Where(sensor => sensor is ObjectSensor).First());
           
            speech.SensorChanged += new SensorEvent(speech_SensorChanged);

            recognizer.UseTool();
            timeout.Reset();
            voice.Say("ready.");
        }

And then we'll just wait for voice commands. A note - the recognizer object is the actual object doing the speech recognition, but, to make things easier, it fills in a value on a StringSensor object, which is a kind of base sensor object type I use on the robotics platform to easily represent and sense things that are string values (RFID sensor values, sensed speech, values coming from an IR remote receiver, things like that). This is the code that interprets commands:

        bool isProcessing = false;
        void speech_SensorChanged(object sender, SensorEventArgs args)
        {
            if (isProcessing) return;

            isProcessing = true;

            if (!string.IsNullOrEmpty(speech.StringValue) && timeout.IsElapsed)
            {
                timeout.Reset();
                if (isWaitingForCommand)
                {
                    switch (speech.StringValue)
                    {
                        case "speech on":
                            isQuietMode = false;
                            Say("Speech is turned on now.");
                            break;

                        case "speech off":
                            Say("Speech is turned off.");
                            isQuietMode = true;
                            break;

                        case "center":
                            Say("Centering the turret.");
                            turret.Center();
                            break;

                        case "forward":
                            Say("Moving forward.");
                            Platform.Forward(16);
                            break;

                        case "backward":
                            Say("Moving back.");
                            Platform.Backup(10);
                            break;

                        case "use turret":
                            Say("Commands will move the turret.");
                            isUsingTurret = true;
                            break;

                        case "use motors":
                            Say("Commands will move the drive motors.");
                            isUsingTurret = false;
                            break;

                        case "left":
                            if (isUsingTurret)
                            {
                                Say("Turning turret left.");
                                turret.MoveDegrees(-45);
                            }
                            else
                            {
                                Say("Turning left.");
                                Platform.Turn(-45);
                            }
                            break;

                        case "right":
                            if (isUsingTurret)
                            {
                                Say("Turning turret right.");
                                turret.MoveDegrees(45);
                            }
                            else
                            {
                                Say("Turning right.");
                                Platform.Turn(45);
                            }
                            break;

                        case "turn around":
                            Say("Turning around.");
                            Platform.Turn(180);
                            break;

                        case "get beer":
                            Say("Ha. Go get your own beer! They are in the fridge.");
                            kinectAngle.PositionAt(0, 15, 0);
                            turret.MoveDegrees(45);
                            Thread.Sleep(1000);
                            kinectAngle.PositionAt(0, 0, 0);
                            turret.Center();
                            break;

                        case "stop":
                            Platform.Stop();
                            Say("Stopping. Say 'row bought' to continue.");
                            isWaitingForCommand = false;
                            break;

                        case "light on":
                            light.UseTool();
                            break;

                        case "light off":
                            light.Stop();
                            break;

                        case "look up":
                            kinectAngle.PositionAt(0, 15, 0);
                            break;

                        case "look down":
                            kinectAngle.PositionAt(0, -15, 0);
                            break;

                        case "look middle":
                            kinectAngle.PositionAt(0, 0, 0);
                            break;

                        case "good job":
                            Say("Thank you.");
                            break;

                        case "robot":
                            Say("I am awaiting commands.");
                            break;

                        case "status":
                            Say(objectSensor.GetStatus());
                            break;

                        case "yes":
                        case "no":
                            break;

                        default:
                            //Say(string.Format("The phrase, '{0}', does not map to a command.", speech.StringValue));
                            break;

                    }
                }
                else
                {
                    if (speech.StringValue == "robot")
                    {
                        isWaitingForCommand = true;
                        Say("I am listening.");
                    }

                    if (speech.StringValue == "good job")
                    {
                        Say("Thank you.");
                    }
                }
            }

            speech.StringValue = String.Empty;
            isProcessing = false;
        }

Underneath the scenes, there's a little setup going on. In the recognizer object, we're setting up a vocabulary, getting a reference to the Kinect audio stream, and setting up a reference to the voice recognition engine provided from the Kinect, like so:


namespace Robotics.Platform.HouseBot.Kinect
{
    using System;
    using System.Threading;
    using Microsoft.Kinect;
    using Microsoft.Speech.AudioFormat;
    using Microsoft.Speech.Recognition;
    using Robotics.Framework.Tools;

    public class KinectSpeechRecognizer : ITool
    {
        const double ConfidenceThreshold = 0.5;  // Speech utterance confidence below which we treat speech as if it hadn't been heard

        public delegate void SpeechRecognizedEventHandler(object sender, SpeechRecognizedEventArgs args);
        public event SpeechRecognizedEventHandler SpeechRecognized = delegate { };

        RecognizerInfo recognizer;
        private KinectSensor sensor;
        private SpeechRecognitionEngine speechEngine;

        public KinectSpeechRecognizer(KinectSensor newSensor)
        {
            sensor = newSensor;
            InUse = false;
        }

        private RecognizerInfo GetKinectRecognizer()
        {
            var recognizers = SpeechRecognitionEngine.InstalledRecognizers();
            foreach (RecognizerInfo recognizer in recognizers)
            {
                string value;
                recognizer.AdditionalInfo.TryGetValue("Kinect", out value);
                if (value == "True" && recognizer.Culture.Name == "en-US")
                {
                    return recognizer;
                }
            }

            return null;
        }

        private void InitializeSpeechRecognition()
        {
            int i = 0;

            while (recognizer == null && ++i < 10)
            {
                recognizer = GetKinectRecognizer();
                if (recognizer == null)
                    Thread.CurrentThread.Join(500);
            }

            if (recognizer == null)
                return;

            speechEngine = new SpeechRecognitionEngine(recognizer.Id);

            var phrases = new Choices();

            phrases.Add(new SemanticResultValue("go forward", "forward"));
            phrases.Add(new SemanticResultValue("back up", "backward"));
            phrases.Add(new SemanticResultValue("stop", "stop"));
            phrases.Add(new SemanticResultValue("turn left", "left"));
            phrases.Add(new SemanticResultValue("turn right", "right"));
            phrases.Add(new SemanticResultValue("turn the light on", "light on"));
            phrases.Add(new SemanticResultValue("turn the light off", "light off"));
            phrases.Add(new SemanticResultValue("use the turret", "use turret"));
            phrases.Add(new SemanticResultValue("use the drive motors", "use motors"));
            phrases.Add(new SemanticResultValue("center", "center"));
            phrases.Add(new SemanticResultValue("robot", "robot"));
            phrases.Add(new SemanticResultValue("look up", "look up"));
            phrases.Add(new SemanticResultValue("look down", "look down"));
            phrases.Add(new SemanticResultValue("look straight", "look middle"));
            phrases.Add(new SemanticResultValue("good job", "good job"));
            phrases.Add(new SemanticResultValue("get me a beer", "get beer"));

            var grammarBuilder = new GrammarBuilder { Culture = recognizer.Culture };
            grammarBuilder.Append(phrases);

            var grammar = new Grammar(grammarBuilder);
            speechEngine.LoadGrammar(grammar);

            speechEngine.SpeechRecognized += speechEngine_SpeechRecognized;

            var stream = sensor.AudioSource.Start();
            speechEngine.SetInputToAudioStream(stream, new SpeechAudioFormatInfo(EncodingFormat.Pcm, 16000, 16, 1, 32000, 2, null));

            speechEngine.RecognizeAsync(RecognizeMode.Multiple);
        }

        void speechEngine_SpeechDetected(object sender, SpeechDetectedEventArgs e)
        {
            //  throw new NotImplementedException();
        }

        void speechEngine_SpeechHypothesized(object sender, SpeechHypothesizedEventArgs e)
        {
            // throw new NotImplementedException();
        }

        void speechEngine_SpeechRecognitionRejected(object sender, SpeechRecognitionRejectedEventArgs e)
        {
            // throw new NotImplementedException();
        }

        Robotics.Framework.Oscillators.Timeout timeout = new Robotics.Framework.Oscillators.Timeout(new TimeSpan(days: 0, hours: 0, minutes: 0, seconds: 1, milliseconds: 500));
        void speechEngine_SpeechRecognized(object sender, Microsoft.Speech.Recognition.SpeechRecognizedEventArgs e)
        {
            if (!timeout.IsElapsed)
                return;
            if (e.Result.Confidence >= ConfidenceThreshold)
                SpeechRecognized(this, new SpeechRecognizedEventArgs { RecognizedSpeech = e.Result.Semantics.Value.ToString(), });

            timeout.Reset();
        }

        public bool InUse { get; set; }

        public string Name { get; set; }

        public bool UseTool()
        {
            if (speechEngine == null)
                InitializeSpeechRecognition();
            else
                speechEngine.RecognizeAsync(RecognizeMode.Multiple);

            return true;
        }

        public bool UseTool(int action)
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            if (speechEngine != null)
                speechEngine.RecognizeAsyncStop();
        }

        public bool PositionAt(double degreesX, double degreesY, double radius)
        {
            throw new NotImplementedException();
        }
    }
}

So that's about it. One of the hurdles I had to overcome was to make sure I had references to the correct SDK objects. If I set the references to the Microsoft.Kinect assembly in the IDE, things didn't work correctly. I had to look at the csproj file in the developer toolkit example, and manually edit my csproj to match. Once that was figured out, it was smooth sailing. Play with the recognition threshold if you get spurious recognitions that you don't want. At one point I had it set to 0.9 -- less than 90% certainty, and it won't respond. This actually seemed a pretty good setting.

Enjoy!