Part 2: Controlling a Microbric Viper Robot with an IR Serial Port using .NET and PowerShell
- Posted: Feb 19, 2007 at 2:17PM
- 1,302 views
- 2 comments
Loading user information from Channel 9
Something went wrong getting user information from Channel 9
Loading user information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
|In this Part Two of "Some Assembly Required", Scott Hanselman extends his original Microbric Viper Robot/Iguanaworks IR solution to script-enable control of the robot with Lee Holme's "PowerShell LOGO." Background|
Time Required: 3-6 hours
Hardware: MicroBric Viper, Iguanaworks IR Serial Port
Summary: In this Part Two of "Some Assembly Required", Scott Hanselman extends his original Microbric Viper Robot/Iguanaworks IR solution to script-enable control of the robot with Lee Holme's "PowerShell LOGO."
The Microbric Viper is a robot construction kit based on a new solderless construction technique. The Viper uses a Basic Atom microcontroller that you program, shockingly, using BASIC. Modules like an Infrared Receiver, motors and switches can be fairly easily controlled as you just screw them directly into the mainboard and address them by number. Once I got my hands on a Microbric, I started trying to talk to the robot using my laptop's Infrared Port. It turns out after a number of failed attempts that Windows doesn't expose standard laptop IR ports as Serial Ports or in any way other than via the IRDA protocol. IRDA was not only overkill for this kind of communication but also a hassle to program to. What I really needed was an IR transmitter that I could talk to using an interface that was clear and clean like the Serial Port interface exposed by System.IO.Ports.
I approached the guys at Iguanaworks as they make an infrared transceiver that is addressable via a standard Serial Port. The Viper comes with a Sony Remote Control so I set off to figure out how to get the Iguanaworks IR transmitter to speak to the Viper using .NET.
There's Video of the whole thing working together posted in the Channel 9 Forums Screencast topic as seen in the screenshot above.
I used the custom IR Serial Port and created a console application that let me control the Microbric Viper using the keyboard of my computer. Now I'll create an application that'll let me control the Viper with script. What better script to use than Windows PowerShell?
Rather than just writing some PowerShell script that sends IR commands, I thought it'd be more interesting to host PowerShell within a WinForms application and write script that controls both the Viper and an an on screen "turtle," just like the LOGO Programming Language you may have learned in school.
Hosting PowerShell within your own WinForms application is amazingly easy. Almost embarrassingly easy. Since I wanted a LOGO-like experience, I started by calling Lee Holmes who has a deeply technical blog dedicated to PowerShell. He happily let me extend his existing application to include support for the Microbric Viper.
Lee's application is a simple WinForms project with an Image control and a textbox. There's a .NET object called turtle that you can think of as the "pen." The turtle has methods like Forward, Left, Right, etc.
The turtle can be controlled on the Actions tab via a series of links. Moving the turtle forward 10 pixels is as trivial as:
Controlling the turtle via the UI is simple enough, but scripting the turtle is the interesting part.
It's very significant to note that PowerShell is much more than a shell. It's not a shell at all at its heart - It's a UI-non-specific "runspace" for executing commands. PowerShell doesn't need to be seen in order to do its work. You can host PowerShell in any of your applications and instantly script enable them.
The first step is adding a .NET Assembly reference to System.Management.Automation. Within this assembly we'll be focused on the System.Management.Automation.Runspaces namespace.
We create a Runspace where our script will run like this:
runspace = RunspaceFactory.CreateRunspace(); runspace.Open();
Runspaces host Pipelines of commands strung together. We can create a pipeline within our Runspace and pass in the contents of the scriptText textbox from the main WinForm.
Pipeline pipeline = runspace.CreatePipeline(scriptText.Text);
The pipeline can be executed with
But we need to let PowerShell have access to an instance of our Turtle class so that it can call methods like turtle.Forward(). You can pass any object from your application into the PowerShell runspace like this:
1: runspace = RunspaceFactory.CreateRunspace();
3: runspace.SessionStateProxy.SetVariable("turtle", turtle);
4: Pipeline pipeline = runspace.CreatePipeline(scriptText.Text);
In this snippet an instance of the turtle class is passed into the SetVariable method along with the string "turtle." The string is the name of the PowerShell variable you want. If we'd passed in "foo",turtle then there'd be an instance of a turtle in a PowerShell variable accessible as "$foo."
Now that I've got access to the turtle from PowerShell, I could draw a square in PowerShell script however I like:
3: $a = 30
At this point, I've got full control of the turtle, so I can write whatever script I like. If you're going to host PowerShell in your own application be sure to consider the security ramifications. Someone could certainly include "dir" or "format c:" in the script, so you might consider a whitelist of allowed commands.
In the current version on Lee's website the turtle doesn't update as he draws. Instead the image is updated after the turtle has finished. Changing this won't make our robot integration work any better, but it would certainly be nice to see the robot move in the real world as it draws on screen.
To solve this we need to understand the relationship between our application and the PowerShell runspace we're hosting. When we pass the turtle instance in PowerShell, that instance is being accessed on another thread, in the "PowerShell world."
Since the PowerShell script is going to be accessing the turtle instance, and the turtle instance will be drawing to a Canvas object that we passed in, we'll need a way to refresh the WinForm's image. First we need to invoke the PowerShell pipeline asynchronously. The pipeline will fire off and immediately return control to our application. It will continue executing, accessing the turtle, while a Timer control refreshes the Image control on the WinForm every 1/4 second or so.
The turtle class is changed to a ViperTurtle class and we'll add the IR class from the last article. When the turtle moves, we'll not only draw to the canvas but also send Viper-specific IR codes to move the robot.
I had a few timing-related problem with this project.
First, since the IR transmitter is going as fast as I can send signals to it, I had to add a "WaitASec" method that sleeps for a a third of a second. The commands were coming so fast that the robot would get "stuck" as if a real person was pressing buttons too fast on a remote control. If a Forward command is followed by a Left command too quickly, the robot thinks that the Forward command is continuing...as if the button were being held down. If we wait a fraction of a second for the IR beam to shut off, it works much better.
The second issue was that the commands the Viper understands are relative - like "turn left." The longer we "hold down," or continue to send, a command, the longer the robot turns. However, the LOGO-like PowerShell script thinks in absolutes, like "left(90)" where 90 means ninety degrees exactly. If the robot skids or slides on the floor, it can also throw the turns off. I could, and can still, deal with this in a number of ways. First, I could create new IR commands for the Viper to respond to and program its firmware with these custom commands, like "TurnLeft5Degrees" or even at 1 or 2 degree increments. Then I'd need to calculate the circumference of the wheels on the robot and do the math backwards in order to find the correct number of milliseconds to pulse the Viper's onboard motors to turn those wheels. Whew!
Or, I could just build in a "fudge factor" and get it working by trial and error. This is a classic software engineering problem, isn't it? The amount of effort required to get 100% perfect turning weighed against how long it would take to get it working 85-90%.
I came up with these two constant values that worked pretty well for me. You might need to try different values based on the kind of surface you're using, or if you've built your own IR transmitter.
1: const double VIPERADJUST = 0.35;
2: const double VIPERADJUSTTURN = 0.06;
4: public void Left(int degrees)
6: direction = (direction + degrees) % 360;
8: ir.Send(LEFT, (int)(VIPERADJUSTTURN * degrees));
The IR's send method takes a Repeat value as its second parameter. This indicates how many times the IR command should repeat. I found that multiplying the number of degrees I wanted the robot to turn by a value of 0.06 would get me the correct turn. However, I can't send fractional packets, so I'm forced to lose precision as I cast the result of the double calculation down to an int. So a 90 degree turn becomes 5.4, which becomes the left command sent via IR 5 times. In practice it works pretty well, though. If I were to hook a pen up to the Viper in the future and create PenUp and PenDown commands in its firmware, I'd want to get tighter, more accurate turning.
Again, here's Video of the whole thing working together posted in the Channel 9 Forums Screencast topic as seen in the screenshot above.
The Microbric Viper can be ordered online in North America, check out www.microbric.com for North American distributors. It's only US$89 at Saelig and CAD$99 at RobotShop. They have a number of educational robots that can be assembled by kids of all ages and skill levels. They're great for the classroom, and include projects like Sumo Robots, and a line-following bot, as well as a Spiderbot that climbs rope - all from the same kit.
You can order the IR Transmitter/Receiver from IguanaWorks. The serial version works on Windows or Linux, and there's a Linux USB version. It's not just a Transmitter, but also a learning receiver that works with WinLIRC and turns your computer into a learning remote control and can be used for nearly any project that utilizes IR.
As with all my projects, there's things that could be extended, added, and improved on with this project. Here are some ideas to get you started.
Have fun and have no fear when faced with the words - Some Assembly Required!
Scott Hanselman is the Chief Architect at the Corillian Corporation, an eFinance enabler. He has thirteen years experience developing software in C, C++, VB, COM, and most recently in VB.NET and C#. Scott is proud to be both a Microsoft RD and Architecture MVP. He is co-author of Professional ASP.NET 2.0 with Bill Evjen, available on BookPool.com and Amazon. His thoughts on the Zen of .NET, Programming and Web Services can be found on his blog at http://www.computerzen.com. He thanks his wife and Zenzo for indulging him in these hobbies!