Programming LEGO Mindstorms Robots using the My Namespace
- Posted: Nov 21, 2006 at 4:27 AM
- 1,100 Views
- 3 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
|This article walks you through the steps of creating an extension to the Visual Basic My namespace that will allow you to program Lego Mindstorms RCX robots. Note that with the introduction of Microsoft Robotics Studio there is an additional way to do programming with robotics.|
Time Required: 1-3 hours
Hardware: Lego Mindstorms RCX - Robotics Invention System 2.0
Download: My Extension for LEGO
Nostalgic memories returned as I opened the box to the Lego Mindstorms Robotic Invention System. I remembered the spaceships, fighter planes, sprawling space docks, towering skyscraper buildings—each coming to life accompanied by the soft click of LEGO bricks snapping together. And now you can automate your creations! If I had owned this kit as a kid there would have been some serious infractions of lights-out time at night.
With the release of the Microsoft.NET Interface for LEGO Mindstorms you can program your Mindstorms robot using any .NET language. In this article we will make the Microsoft.NET LEGO interface available via the Visual Basic My namespace.
You first need to download and install the free LEGO Mindstorms SDK 2.5. This will install the drivers required to send commands to Mindstorms robots via the infrared tower. You'll need Visual Basic 2005, which you can get for free from Visual Basic Express Edition 2005. Install the Microsoft.NET LEGO interface which will provide the interface between Visual Basic and your robot. You can get that by installing the Visual Basic LEGO Starter kits: Download Visual Basic LEGO Starter Kits If you run into any problems setting all of this up, see the setup tips in Tips and Tricks for LEGO Mindstorms programming by John Wingfield.
Make sure your path includes the LEGO SDK directory so that the system can find the following LEGO SDK DLLs: GhostAPI.dll, PbkComm32.dll, and PbkUsbPort.dll. On my machine I used the default installation directory for the Mindstorms SDK and my path is set to include C:\Program Files\LEGO Mindstorms\SDK\Bin\GhostAPI. You can add this to your path permanently via Control Panel, System, choose the Advanced Tab, and click the Environment Variables button near the bottom. Find 'Path' in the System variables section and choose edit to then add a semi-colon followed by the path of your Lego SDK dlls to the end of your current path. If you don't want to change your system path you can always copy these Dlls to a directory already included in your path or to the Bin directory of each project you make.
Setting up the My Namespace Extension
Now that you have the LEGO programming system installed you are ready to download the My Extension for LEGO. That's available from the download link at the start of this article. Once you have downloaded the My Extension for LEGO.vsi file, double-click it to install the template in Visual Studio which will provide the My interface for programming your robot.
The My Namespace
One of the new features making programming easier in Visual Basic 2005 is the My namespace. The My namespace puts commonly used functionality at your fingertips so you don't have to hunt for it in a sea of APIs. For instance, if you create a new VB 2005 Windows application and type My. in a code window, you'll find easy access to functionality related to your computer, application, project, etc. It is also now much easier to handle common application events, which can be accessed from the new project designer.
The Microsoft.NET Interface for LEGO Mindstorms provides functionality for controlling a Mindstorms robot. We are going to expose the most commonly used functionality from that interface and create a couple higher level functions that make some robot programming tasks easier. We'll make them available via the My namespace and also make the Mindstorms robot sensor events easy to access.
To get a flavor for what programming with the My namespace will be like, let's walk through the steps of writing a simple app. It will just spin the Roverbot around in circles for a few seconds or until it hits something.
After completing the setup steps above, create a new VB Windows Forms application and name it QuickSpin. We'll make a Windows Forms application instead of a console application because VB offers the application model and corresponding events with Windows Forms applications.
Once your new VB application is created we need to add a reference to the Microsoft.Net LEGO interface. From the Visual Studio 2005 menu, choose Project, Add Reference, and click browse. Navigate to the directory where you installed your VB Lego Starter Kit and select C4f.LegoRcx.dll. If you choose the default installation location when you installed the samples you will find C4f.LegoRcx.dll in c:\Documents and Settings\<your user name>\My Documents\MSDN\VB Lego Samples\Debug\C4f.LegoRcx.dll. You should always add this reference by hand if you intend to use the My LEGO Extension. This Dll is not in the GAC and unfortunately item templates can only automatically add references for assemblies in the GAC. So it is necessary to add this reference by hand or else you'll get compilation errors when you add the template.
To add the My extension choose Project/Add New Item. In the 'My templates' section of the dialog, near the bottom, will be 'My Extension for Lego'. Select it and click Add.
Sending commands to the robot
At this point you are ready to write code to control your robot using the My namespace. I'm going to assume that your robot is in the Roverbot configuration. Add a button to your form and double-click the button to get to the code view for the button click event.
Now if you type My.Lego you should see the various commands that you can use with your robot.
Go ahead and select RunMotorATimed and complete the line like this: My.Lego.RunMotorATime(8,10)
This instructs the robot to run motor A at full power for ten seconds.
Handling Sensor Events
My.Lego will give you access to a variety of commands for running your robot. But what about responding to sensor input from the robot? The My extension we create will provide for that as well.
Visual Basic 2005 Windows Form projects have an application model that is accessible via the project designer. Double-click on the My Project node in the solution explorer. Once in the project designer select the Application tab on the left, and in the lower right of the application pane you will find a button labeled 'View Application Events'. Clicking that button will take you to the code-behind for the application events. If you select (MyApplication Events) in the left dropdown you can then see the events available in the dropdown on the right.
Go ahead and select Sensor1
The events for Sensor1, Sensor2, and Sensor3 are raised based on the activity of the sensors attached to sensor ports one through three on your robot. Before these events will be raised you must first select what kind of sensor is attached to a given port. I find it convenient to set this in the Startup event which is accessible from the same dropdown as the Sensor events. For the RoverBot configuration I set Sensor1 to be the Touch sensor type with this line of code in the Startup event: My.Lego.Sensor1.SensorType = C4F.LegoRcx.RcxSensorType.Touch. Now when I handle the Sensor1 event I will get a value in e.Value of 1 when then touch sensor closes, and a value of 0 when the touch sensor opens. A closed state indicates that the sensor is in contact with something. An open state indicates that the sensor is no longer in contact with anything.
Note that the value you get in the sensor events depends on the type of sensor that fired the event. I find it useful to watch the view window on the RCX while activating the sensor in different situations to see what values it sends. To do this press the View button on the RCX until the indicator in the view window is under the sensor port you want to examine. Then activate the sensor and the view window on the RCX will show you the value being returned in response.
We want the Roverbot to stop if it encounters an obstacle. Note that the event will be raised twice when the touch sensor hits something. The first time is at the moment of contact. The second time is when the sensor breaks contact. We want to check the value in e.Value to see if it is 1 which indicates that the sensor has come in contact with something. If it is 0 we will just ignore the event since that means that we have just broken free of whatever the sensor had previously come in contact with.
The code to stop the Roverbot if it hits something should be placed in the Sensor1 event. The code is:
Private Sub MyApplication_Sensor1(ByVal sender As Object, _
ByVal e As C4F.LegoRcx.RcxSensorValueChangedEventArgs) Handles Me.Sensor1
If e.Value = 1 Then
Make sure your tower is plugged in and your robot is turned on. Then run your program and click the button on the form. The robot should turn around in circles for ten seconds and then come to a halt. If you place an obstacle in the way that is heavy enough to trigger the touch sensor, the robot should come to a halt.
We have now seen how to send commands to your Robot via the My namespace and how to handle events raised by your robot. See the API summary at the end of this article for a complete list of functions and events available from the My extension that you can experiment with.
Some common problems and issues you may encounter:
If you encounter a 'File Not Found' exception or 'Target Invocation Exception' the first time My.Lego is accessed, what is probably happening is that the Microsoft.Net interface can't find the LEGO RCX DLLs. Make sure they are in your path or in the same directory as the executable of the application you are building. The DLLs that have to be available are: GhostAPI.dll, PbkComm32.dll, and PbkUsbPort.dll. You can find these in the Lego SDK directory as described in the Getting Started Section.
If you receive an 'Unable to select first device' exception, make sure that your tower is plugged into the computer.
Every project that you add the My.Lego extension to will need a reference to C4f.LegoRcx.dll. If you get a bunch of errors that C4f.Something-or-other is undefined, you need to add this reference. You can find it in the directory where you installed your VB Lego Starter Kit. If you choose the default installation location when you installed the samples you will find it in c:\Documents and Settings\<your user name>\My Documents\MSDN\VB Lego Samples\Debug\C4f.LegoRcx.dll
I've found that the latency between the tower and the robot means that I don't want to be doing anything time critical in response to sensor events or sending commands. So writing a program to see if the robot can back away from the stairs is likely to be pretty hard on your robot. Sensor events and motor commands often take awhile to propagate. This makes some scenarios hard, such as following a trace with the light sensor, because by the time we get the event it may be too late to do anything about it. Also, keep an eye on the distance between the tower and your robot so they aren't too far apart and make sure that the line of sight between the two doesn't get obstructed.
If you aren't receiving sensor events make sure you have set the sensor type in your code. There appears to be a narrow window of time when setting the sensor type 'takes' so you should do it early on. I fare better when I set it in the Startup event.
If the robot isn't turned on at the time you run your program you may lose your setup commands and have difficulty getting a response from the sensors. These difficulties, and others you may encounter, are discussed in "The challenges of integrated ('tethered') programming" section of John Wingfield's Tips and Tricks for LEGO Mindstorms Programming article.
Show me the Code
Now that you know what the My extension can do, let's talk about how it works so you can apply the principles to make your own extensions. The principles discussed here illustrate not only how the Lego extension was made, but can be used to extend the My namespace in other ways as well.
So what's a Namespace?
When I refer to the new productivity feature in Visual Basic 2005 as the My namespace, it is because it the My feature is implemented and exposed as a namespace. So what is a namespace? Simply put, a namespace is a language construct that allows you to group a set of names.
Imagine you have two friends named Kevin. How do people know which one you are referring to when you mention the name in conversation? An easy way is to specify his last name as well. Each Kevin belongs to a family, and by using his surname you are clarifying which Kevin you are talking about. In this case you can think of a family name as a namespace. It provides a 'scope' so that when you refer to Kevin Darman, nobody is confused about whether you possibly meant Kevin Jones. In Visual Basic, this could look like this:
Here we have two Kevin classes. To avoid ambiguity we specify the namespace:
Dim MyFriend As New Darman.Kevin
By referring to Darman.Kevin, there is no confusion about which Kevin you mean.
The My namespace is automatically defined when you create a new project. One of the reasons we implemented this feature as a namespace is to allow for extensibility. Namespaces have a special property: the compiler combines all namespaces of the same name together. So if I define in one file:
And in another file, or in another location in the existing file, define:
Then the result is the same as if I had defined:
This means that we can add functions to the existing My namespace by defining another My namespace in one of our files. The compiler will combine it with the existing one in the project. When we type My. we will see a unified view of all the namespaces named My.
Adding to the My Namespace
To follow along with how to create a My namespace extension, open the MyLegoExtension.vb file that was added to the QuickSpin project we created above. The file was added when you added the My.Lego extension.
To add a function to a namespace we need somewhere to put it. We can't just define a Sub or Function at namespace level. The answer is to put our functionality in a Module.
Modules have an interesting property that makes them well suited to our purpose. Names defined in Modules don't have to be qualified by the module name. This is different than what happens with classes, for instance. If you refer to a member of a class instance you qualify it with the object name, e.g. Person.Name() But with modules this isn't necessary.
Let's put these two ideas together to expose the MyRcx object, which provides the high level API we want to expose for the Mindstorms robot. We will expose it as My.Lego as illustrated in this excerpt from MyLegoExtension.vb:
Friend Module MyLegoProject
Public ReadOnly Property Lego() As MyRcx
Private m_LegoBrick As New MyRcx
The first thing to note is the declaration of Namespace My. This means that everything inside will be grouped with the My namespace already defined in the project.
Next we see Module MyLegoProject. This provides us with a place to define the methods and properties we want to make available from the My namespace. Here we provide properties that return the objects that define the functionality we want to expose. The <HideModuleName()> attribute is a nicety that instructs Intellisense not to show the MyLegoProject module in the drop-down list. We don't want our plumbing cluttering up Intellisense since the module adds no value other than providing a place where we can define our property.
Within the module, the property returns an instance of the MyRcx object. MyRcx is a facade class. A facade acts as a 'front' for another class, or set of classes, so that only the particular functionality we want to highlight is exposed. In this case, I wanted to take the C4F.LegoRcx.Rcx class defined by the .NET LEGO interface, and expose only the most common functions. I also wanted to add new functionality, such as running a particular motor for a specified amount of time, which isn't directly exposed by the C4f.LegoRcx.Rcx class. The definition of this class is included as part of the My extension. See the MyRcx class in MyExtensionForLego.vb which was added to your project when you did the QuickSpin project earlier.
Adding Sensor Events
One of the features we added in Visual Basic 2005 is an application model. The application model provides a variety of features. There is a mechanism to display a splash screen during startup as well as a way to specify that only one instance of the application can be running at a time. We also added application events to signal application startup and shutdown. There is also an event that signals when network connectivity changes. This application model is also extensible.
In addition to telling the robot what to do, we can also know when the robot's sensors fire. To access the application events, load the project designer by double-clicking the 'My Project' node in the solution explorer. From the Application page, click the View Application Events button located in the lower right. Now you will be in the code window where you can handle application events. Our next task is to add the Sensor events to the list of events supported by the application.
Let's add events that will fire when one of the three sensors on the robot are triggered. To do that, we can take advantage of the fact that the Application class is defined as a partial class.
Partial classes were introduced to VB users in Visual Basic 2005. A partial class allows you to split up the definition of a class across multiple files. So for instance, if I had:
Partial Class Foo
And then in another file (or elsewhere within the same file):
Partial Class Foo
The compiler will combine these two definitions internally at compile time. So it is the same as if you had defined:
Why would anyone want to do that? You may have noticed in VB 2005 that when you go to the code-behind for a form that you don't see InitializeComponent() or the other Form related goo that you did in earlier versions of the product. Where did it go? It's still in your project. But in order to simplify the experience, the form code has been factored out into two partial classes. For each form you create there is a formName.Designer.vb file that contains all the form definition/initialization code defined in a partial class. And the code-behind, where you put your form code (the code you actually care about), is defined in separate partial class. At compile time the compiler assembles together all the partial classes that have the same name, and are defined in the same namespace.
The IDE makes use of partial classes to clear out the plumbing so you only have to be concerned with your own code while working with forms. We will use them to provide extensions to the partial application class defined in the project. And that's how we'll extend the application events to include the sensor events on the robot.
As a part of your VB 2005 Windows Forms application there is a MyApplication class defined within the My namespace. It provides the various application events and application behaviors such as displaying the splash screen, governing how the application shuts down, etc. It is also defined as a partial class. Which means we can extend it.
To extend the MyApplication class we first define it within the My namespace like this:
Partial Friend Class MyApplication
Now anything we add to the MyApplication class is going to become part of the MyApplication class already defined in the implicit My namespace in your project.
What we want to do is define events on the MyApplication object that are raised when the events on the Microsoft.Net LEGO interface are raised. Since My.Lego exposes the class that sources the various sensor objects (e.g. My.Lego.Sensor1) we can define Events in the MyApplication class that forward those events on to the application. For instance, in the MyLegoExtension.vb file you will see:
Partial Friend Class MyApplication
Public Custom Event Sensor1 As _
AddHandler(ByVal value As _
If m_Sensor1Handlers Is Nothing Then
AddHandler My.Lego.Sensor1.ValueChanged, _
m_Sensor1Handlers = _
RemoveHandler(ByVal value As _
RaiseEvent(ByVal sender As Object, ByVal e As _
If m_Sensor1Handlers IsNot Nothing Then
Private m_Sensor1Handlers As _
You may notice that these events are custom events. Custom events allow us to customize what happens when a handler is added, removed, or fired. The AddHandler code for this event only runs if somebody actually handles the event. It also provides an opportunity to access the instance of the LEGO robot object that will fire the events we want to listen to. By adding a handler to My.Lego.Sensor1.ValueChanged, I can access the Sensor1 event on the same instance of the Lego robot object that is being used everywhere else My.Lego is being used.
I'm also following a pattern with these events that we adhere to in the application model class. For all the events we raise we also provide an overridable method named OnEventName that raises them. This provides an extensibility point for people who want to derive from the MyApplication class. By overriding the corresponding OnEventName method, they can do pre or post-processing around the event. The default implementation of the OnEventName methods simply raises the event.
By defining the remaining events for Sensor2 and Sensor3 in like manner, we make the LEGO robot events accessible from the project designer via the Application Events button.
Share and Share Alike
Once you've made an extension, you may want to make available to others. To do so, you first export your extension using the export template feature in Visual Studio 2005. That is accessed from the Visual Studio menu under File, Export Template. Once exported, you can then package it up as a Visual Studio Content Installer package. To see what an installer package looks like, rename the MyLego Extension.vsi you downloaded to My Lego Extension.zip and extract the files. My installer package consists of a zip file for the My Lego extension template and a MyLEGO.vscontent file that describes the contents of the installer package. You create a Visual Studio Content Installer package by zipping all of those files up together and renaming the resulting zip file with a .VSI extension. For more details on creating VS Installer packages see: http://msdn2.microsoft.com/en-us/library/ms246580.aspx
I've included MyExtensionTest in the accompanying download which I used to develop the My namespace extension. You can look at it to see how you might use the My namespace interface in your Mindstorms projects. It has a test harness that allows you to drive your Roverbot by remote control from your computer. You can run the motors at specified speeds and for specified amounts of time, play tones, examine sensor values, etc. The sensor types are hardwired in the Startup event because they need to be set early on. The test harness assumes that sensor 1 is hooked up to the touch sensor and sensor 3 is hooked up to the light sensor. The debug output window indicates when the sensors are activated.
In this article we've covered many of the essential concepts behind extending the My namespace. Those concepts include introducing Namespace My into one of the files that will be a part of your project. Defining the objects or functions that provide the behavior you want to expose. Making those objects or functions available from a module that is defined in your My Namespace. Adding events to the partial MyApplication class which you define in your My Namespace. Those events then become available from the application page in the project designer.
For more detailed information on extending the My namespace you can start by reading My.Internals: Examining the Visual Basic My Feature You can also read more about modifying the My namespace and about the design guidelines for My namespace extensions at http://msdn.microsoft.com/msdnmag/issues/05/07/My/
Stay tuned for a future version of Visual Studio in which we hope to make the process of managing My extensions easier. But for now it is possible for people, who are 'in the know' as you now are, to create them. I wish you many happy hours modding your Lego programming interface. Note that I can't be held responsible for anyone who blows past lights-out time doing so
Tyler Whitney began working at Microsoft in 1998 and is currently a Technical Lead on the Visual Basic Architecture team. He was a member of the Visual Basic compiler team for the first release of Visual Basic on the .NET platform and then became responsible for the Visual Basic runtime. He is a founding member of the design team that produced the MY feature and corresponding RAD runtime classes. You can reach him through his blog at My 00000010 cents
Controls the power of the motor attached to terminal A on the RCX. Values for the motors are typically from -8 to 8. Positive values move the motor forwards. Negative values move the motor backwards. 0 puts the motor in 'float'
Controls the power of the motor attached to terminal B on the RCX. Values for the motors are typically from -8 to 8. Positive values move the motor forwards. Negative values move the motor backwards. 0 puts the motor in 'float'
Controls the power of the motor attached to terminal C on the RCX. Values for the motors are typically from -8 to 8. Positive values move the motor forwards. Negative values move the motor backwards. 0 puts the motor in 'float'
Plays the specified system sound on the RCX brick. Values can be any of the C4f.LegoRcx.RcxSoundTypes values.
Plays the specified tone for a specified period of time. Tone values can be any of the C4F.LegoRcx.RcxNote values.
Reverses the direction of MotorA but maintains the current speed.
Reverses the direction of MotorB but maintains the current speed.
Reverses the direction of MotorC but maintains the current speed.
Runs the motor attached to Terminal A at the specified speed for the specified number of seconds. Power values can be from -8 to 8 where negative values run the motor backwards and positive values run the motor frontward.
Runs the motor attached to Terminal B at the specified speed for the specified number of seconds. Values for the motors are typically from -8 to 8. Positive values move the motor forwards. Negative values move the motor backwards. 0 puts the motor in 'float'
Runs the motor attached to Terminal C at the specified speed for the specified number of seconds. Values for the motors are typically from -8 to 8. Positive values move the motor forwards. Negative values move the motor backwards. 0 puts the motor in 'float'
Provides access to the sensor connected to terminal 1. The sensor object allows you to set what kind of sensor is attached—which should be done before reading values from the sensor. You can also access the sensor object to see what value it is currently reporting.
Provides access to the sensor connected to terminal 2. The sensor object allows you to set what kind of sensor is attached—which should be done before reading values from the sensor. You can also access the sensor object to see what value it is currently reporting.
Provides access to the sensor connected to terminal 3. The sensor object allows you to set what kind of sensor is attached—which should be done before reading values from the sensor. You can also access the sensor object to see what value it is currently reporting.
Sets the display on the RCX to the specified value.
Turns the motor attached to terminal A off.
Turns the motor attached to terminal B off.
Turns the motor attached to terminal C off.
The My namespace exposes the most common functionality in the Common tab of IntelliSense. The following more advanced functionality is available from the All tab in Intellisense.
Returns the amount of power remaining on the RCX.
Returns the object representing the underlying RCX. This is useful when there is functionality not directly accessible from My hat you need, such as access to the Firmware version.
Initiates a connection between the tower and the RCX.
Provides access to the Motor object attached to terminal A. The Motor object allows you to examine motor state and power.
Provides access to the Motor object attached to terminal B. The Motor object allows you to examine motor state and power.
Provides access to the Motor object attached to terminal C. The Motor object allows you to examine motor state and power.
Sets the amount of time the RCX brick will idle without receiving any commands before it turns itself off.
Fires when Sensor1 is triggered. Corresponding value associated with the event is in e.Value()
Fires when Sensor1 is triggered. Corresponding value associated with the event is in e.Value()
Fires when Sensor1 is triggered. Corresponding value associated with the event is in e.Value()