This project creates a minimal DSS service which wraps the ftMscLib.dll, enabling MRDS to communicate with an FT-32 brick. At the end there is a sample VPL program which uses the service to display a message on the FT-32 display.
Step 1: Download / Install
You will need
- Visual Studio 2008 or 2010 (express version available here)
- MRDS, download available here
- ftMscLib.dll and dllFunct.cs, both of which are in PC_Programming_RoboTXC_V1-2_11_Dec_2009, download available here
Step 2: Create a New Project in Visual Studio
- Open VS (run as administrator) and go to File | New | Project...
- In the Visual C# | Microsoft Robotics templates, select DSS Service (2.2)
- Name the project ft32, check Create directory for solution and hit OK
- A New DSS Service dialog will pop up, hit OK to use all the default values
- Compile the project. We're only using the boiler plate code generated from the template so far, so compilation should succeed.
Now let's do a sanity check:
- Open VPL (Start | Microsoft Robotics Developer Studio 2008 R3 | Visual Programming Language 2008 R3)
- On the left side is a text box that says Find Services... Enter 'ft'
- You should see ft32 in the list
To complete the thought, and to show that there's no magic involved here
- Open up Windows Explorer and navigate to the ...\Microsoft Robotics Dev Studio 2008 R3\bin folder
- Delete all ft32* files
- Close / reopen VPL
- ft32 should now be gone from the list of services
Step 3: Add ftMscLib.dll and Wrapper Code to the Project
Add the DLL
- Locate ftMscLib.dll in ...\PC_Programming_RoboTXC_V1-2_11_Dec_2009\PC_Programming_RoboTXC\RoboTxTest and copy it to your project directory
- In VS, right click the ft32 project and click Add | Existing Item...
- In the popup, set Objects of type: to All Files (*.*)
- Select ftMscLib.dll and click Add
- Highlight ftMscLib.dll in the Solution Explorer (on the right)
- Under Properties, set Build Action to Content
- Under Properties, set Copy to Output to Copy if newer
- On the main menu, go to Project | ft32 Properties...
- Select the Build tab and check Allow unsafe code
Build the project at this point to check your work. Go back to the ...\Microsoft Robotics Dev Studio 2008 R3\bin directory and find the ft* files. Among them you should see ftMscLib.dll.
Now add the C# wrapper to the project
- Locate dllFunct.cs in ...\PC_Programming_RoboTXC_V1-2_11_Dec_2009\PC_Programming_RoboTXC\Demo_Dll_C#\MotorStop\MotorStop\ and copy it to your project directory
- In VS, right click the ftMscLib project and click Add | Existing Item...
- Select dllFunct.cs and click Add
- Open dllFunct.cs and change the namespace to ft32
Build the project again to make sure there are no compilation errors.
Step 4: Add Operations to Call Into the DLL
The DLL API is straightforward and documented, I'm not going to provide much detail here. (Setup/teardown for the FT-16 is a little different.) The general idea is
- InitLib() is called to set up the DLL
- OpenComDevice() is called to establish a connection to the brick. This returns a handle required by subsequent calls.
- StartTransferArea() is called to start a group of operations
- then various other methods are invoked to do something interesting
- StopTransferArea() is called to signal the end of the group
- CloseDevice() disconnects the brick
- CloseLib() performs cleanup
For now let's leave the brick disconnected and focus on code. Open up ft32Types.cs and add the following classes at the bottom:
public class GetLibVersionStr : Query<GetLibVersionStrRequest, PortSet<ftMscLibVersion, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class GetLibVersionStrRequest
{
}
[DataContract, DataMemberConstructor]
public class ftMscLibVersion
{
[DataMember, DataMemberConstructor]
public string ftMscLibVersionStr;
public ftMscLibVersion() { }
public ftMscLibVersion(string version)
{
this.ftMscLibVersionStr = version;
}
}
public class InitLib : Update<InitLibRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class InitLibRequest
{
}
[DataContract, DataMemberConstructor]
public class ftMscLibStatus
{
[DataMember, DataMemberConstructor]
public uint ftStatusCode;
public ftMscLibStatus() { }
public ftMscLibStatus(uint code)
{
this.ftStatusCode = code;
}
}
public class CloseLib : Update<CloseLibRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class CloseLibRequest
{
}
public class OpenComDevice : Update<OpenComDeviceRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class OpenComDeviceRequest
{
[DataMember, DataMemberConstructor]
public string comStr;
[DataMember, DataMemberConstructor]
public uint bdr;
public OpenComDeviceRequest() { }
public OpenComDeviceRequest(string comStr, uint bdr)
{
this.comStr = comStr;
this.bdr = bdr;
}
}
public class CloseDevice : Update<CloseDeviceRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class CloseDeviceRequest
{
[DataMember, DataMemberConstructor]
public uint fthdl;
public CloseDeviceRequest() { }
public CloseDeviceRequest(uint fthdl)
{
this.fthdl = fthdl;
}
}
public class StartTransferArea : Update<StartTransferAreaRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class StartTransferAreaRequest
{
[DataMember, DataMemberConstructor]
public uint fthdl;
public StartTransferAreaRequest() { }
public StartTransferAreaRequest(uint fthdl)
{
this.fthdl = fthdl;
}
}
public class StopTransferArea : Update<StopTransferAreaRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class StopTransferAreaRequest
{
[DataMember, DataMemberConstructor]
public uint fthdl;
public StopTransferAreaRequest() { }
public StopTransferAreaRequest(uint fthdl)
{
this.fthdl = fthdl;
}
}
public class SetMotorValues : Update<SetMotorValuesRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class SetMotorValuesRequest
{
[DataMember, DataMemberConstructor]
public uint fthdl;
[DataMember, DataMemberConstructor]
public int shmId;
[DataMember, DataMemberConstructor]
public int id;
[DataMember, DataMemberConstructor]
public int duty_p;
[DataMember, DataMemberConstructor]
public int duty_m;
[DataMember, DataMemberConstructor]
public bool brake;
public SetMotorValuesRequest() { }
public SetMotorValuesRequest(uint fthdl, int shmId, int id, int duty_p, int duty_m, bool brake)
{
this.fthdl = fthdl;
this.shmId = shmId;
this.id = id;
this.duty_p = duty_p;
this.duty_m = duty_m;
this.brake = brake;
}
}
public class SetRoboTxMessage : Update<SetRoboTxMessageRequest, PortSet<ftMscLibStatus, Fault>>
{
}
[DataContract, DataMemberConstructor]
public class SetRoboTxMessageRequest
{
[DataMember, DataMemberConstructor]
public uint fthdl;
[DataMember, DataMemberConstructor]
public int shmId;
[DataMember, DataMemberConstructor]
public string msg;
public SetRoboTxMessageRequest() { }
public SetRoboTxMessageRequest(uint fthdl, int shmId, string msg)
{
this.fthdl = fthdl;
this.shmId = shmId;
this.msg = msg;
}
}
In the same file, find where ft32Operations is defined and replace it with this
/// <summary>
/// ftMscLib main operations port
/// </summary>
[ServicePort]
public class ft32Operations : PortSet
{
public ft32Operations()
: base(
typeof(DsspDefaultLookup),
typeof(DsspDefaultDrop),
typeof(Get),
typeof(Subscribe),
typeof(GetLibVersionStr),
typeof(InitLib),
typeof(CloseLib),
typeof(OpenComDevice),
typeof(CloseDevice),
typeof(StartTransferArea),
typeof(StopTransferArea),
typeof(SetMotorValues),
typeof(SetRoboTxMessage))
{ }
public void Post(DsspDefaultLookup msg) { base.PostUnknownType(msg); }
public void Post(DsspDefaultDrop msg) { base.PostUnknownType(msg); }
public void Post(Get msg) { base.PostUnknownType(msg); }
public void Post(Subscribe msg) { base.PostUnknownType(msg); }
public void Post(GetLibVersionStr msg) { base.PostUnknownType(msg); }
public void Post(InitLib msg) { base.PostUnknownType(msg); }
public void Post(CloseLib msg) { base.PostUnknownType(msg); }
public void Post(OpenComDevice msg) { base.PostUnknownType(msg); }
public void Post(CloseDevice msg) { base.PostUnknownType(msg); }
public void Post(StartTransferArea msg) { base.PostUnknownType(msg); }
public void Post(StopTransferArea msg) { base.PostUnknownType(msg); }
public void Post(SetMotorValues msg) { base.PostUnknownType(msg); }
public void Post(SetRoboTxMessage msg) { base.PostUnknownType(msg); }
}
Now open ft32.cs and add these handlers to the ft32Service
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void GetLibVersionStrHandler(GetLibVersionStr request)
{
string version = DLL.ftLibVersionStr();
if (!String.IsNullOrEmpty(version))
request.ResponsePort.Post(new ftMscLibVersion(version));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void InitLibHandler(InitLib request)
{
uint status = DLL.InitLib();
if (status == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(status));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Teardown)]
public void CloseLibHandler(CloseLib request)
{
uint status = DLL.CloseLib();
if (status == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(status));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void OpenComDeviceHandler(OpenComDevice request)
{
uint err = 0;
uint handle = DLL.OpenComDevice(request.Body.comStr, request.Body.bdr, ref err);
if (err == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(handle));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void CloseDeviceHandler(CloseDevice request)
{
uint status = DLL.CloseDevice(request.Body.fthdl);
if (status == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(status));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void StartTransferAreaHandler(StartTransferArea request)
{
uint status = DLL.StartTransferArea(request.Body.fthdl);
if (status == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(status));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void StopTransferAreaHandler(StopTransferArea request)
{
uint status = DLL.StopTransferArea(request.Body.fthdl);
if (status == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(status));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void SetMotorValuesHandler(SetMotorValues request)
{
uint status = DLL.SetMotorValues(request.Body.fthdl, request.Body.shmId,
request.Body.id, request.Body.duty_p, request.Body.duty_m, request.Body.brake);
if (status == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(status));
else
request.ResponsePort.Post(new Fault());
}
[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
public void SetRoboTxMessageRequestHandler(SetRoboTxMessage request)
{
uint status = DLL.SetRoboTxMessage(request.Body.fthdl, request.Body.shmId, request.Body.msg);
if (status == (uint)FTERR.FTLIB_ERR_SUCCESS)
request.ResponsePort.Post(new ftMscLibStatus(status));
else
request.ResponsePort.Post(new Fault());
}
private IEnumerator<ITask> LiveTest()
{
GetLibVersionStr getLibVersionStr = new GetLibVersionStr();
_mainPort.Post(getLibVersionStr);
yield return Arbiter.Receive(false, getLibVersionStr.ResponsePort,
delegate(ftMscLibVersion libVersionStr) { Console.WriteLine("GetLibVersionStr: " + libVersionStr.ftMscLibVersionStr); });
InitLib initLib = new InitLib();
_mainPort.Post(initLib);
yield return Arbiter.Receive(false, initLib.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("InitLib: " + status.ftStatusCode); });
OpenComDevice openComDevice = new OpenComDevice();
openComDevice.Body.comStr = "COM7";
openComDevice.Body.bdr = 38400;
uint ftHandle = 0;
_mainPort.Post(openComDevice);
yield return Arbiter.Receive(false, openComDevice.ResponsePort,
delegate(ftMscLibStatus status)
{
ftHandle = status.ftStatusCode;
Console.WriteLine("OpenComDevice: " + status.ftStatusCode);
});
StartTransferArea startTransferArea = new StartTransferArea();
startTransferArea.Body.fthdl = ftHandle;
_mainPort.Post(startTransferArea);
yield return Arbiter.Receive(false, startTransferArea.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("StartTransferArea: " + status.ftStatusCode); });
SetRoboTxMessage setRoboTxMessage = new SetRoboTxMessage();
setRoboTxMessage.Body.fthdl = ftHandle;
setRoboTxMessage.Body.shmId = 0;
setRoboTxMessage.Body.msg = "Hello!";
_mainPort.Post(setRoboTxMessage);
yield return Arbiter.Receive(false, setRoboTxMessage.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("SetRoboTxMessage: " + status.ftStatusCode); });
SetMotorValues setMotorValues = new SetMotorValues();
setMotorValues.Body.fthdl = ftHandle;
setMotorValues.Body.shmId = 0;
setMotorValues.Body.id = 0;
setMotorValues.Body.duty_p = 300;
setMotorValues.Body.duty_m = 0;
setMotorValues.Body.brake = false;
_mainPort.Post(setMotorValues);
yield return Arbiter.Receive(false, setMotorValues.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("SetMotorValues: " + status.ftStatusCode); });
yield return Arbiter.Receive(false, TimeoutPort(1000), delegate(DateTime timeout) { });
setMotorValues = new SetMotorValues();
setMotorValues.Body.fthdl = ftHandle;
setMotorValues.Body.shmId = 0;
setMotorValues.Body.id = 0;
setMotorValues.Body.duty_p = 0;
setMotorValues.Body.duty_m = 0;
setMotorValues.Body.brake = false;
_mainPort.Post(setMotorValues);
yield return Arbiter.Receive(false, setMotorValues.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("SetMotorValues: " + status.ftStatusCode); });
yield return Arbiter.Receive(false, TimeoutPort(1000), delegate(DateTime timeout) { });
setMotorValues = new SetMotorValues();
setMotorValues.Body.fthdl = ftHandle;
setMotorValues.Body.shmId = 0;
setMotorValues.Body.id = 0;
setMotorValues.Body.duty_p = 0;
setMotorValues.Body.duty_m = 300;
setMotorValues.Body.brake = false;
_mainPort.Post(setMotorValues);
yield return Arbiter.Receive(false, setMotorValues.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("SetMotorValues: " + status.ftStatusCode); });
yield return Arbiter.Receive(false, TimeoutPort(1000), delegate(DateTime timeout) { });
StopTransferArea stopFtTransferArea = new StopTransferArea();
stopFtTransferArea.Body.fthdl = ftHandle;
_mainPort.Post(stopFtTransferArea);
yield return Arbiter.Receive(false, stopFtTransferArea.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("StopTransferArea: " + status.ftStatusCode); });
CloseDevice closeDevice = new CloseDevice();
_mainPort.Post(closeDevice);
yield return Arbiter.Receive(false, closeDevice.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("CloseDevice: " + status.ftStatusCode); });
CloseLib closeLib = new CloseLib();
_mainPort.Post(closeLib);
yield return Arbiter.Receive(false, closeLib.ResponsePort,
delegate(ftMscLibStatus status) { Console.WriteLine("CloseFtLib: " + status.ftStatusCode); });
yield break;
}
}
Time to test it. Add a SpawnIterator call to the Start method, like this
/// <summary>
/// Service start
/// </summary>
protected override void Start()
{
//
// Add service specific initialization here
//
base.Start();
SpawnIterator(LiveTest);
}
Now connect the ROBO TX brick with a USB cable and connect a motor in M1. Run the program. The motor should run clockwise for one second, then counterclockwise for one second. You'll probably need to change the communications port (hard coded here to COM7).
That's a lot of code, but it's only the bare minimum needed to run the Hello program. To implement the entire ftMscLib API would basically be more of the same, and I'm working on it; however, this tutorial is already bloated enough. Wrapper code tends to be like that. Refactoring would help, as would more informative Fault messages, but this works and it will do for now.
If you don't quite understand, set a breakpoint and trace the test code.
Very briefly, what we did here was declare a bunch of types and decorate them with attributes to make the proxy/stub generator and DSS runtime happy. Then we added them to the PortSet, so the service can accept messages of these types. Then, we added handlers which are invoked when the service sees messages of these new types. Lastly, we added a test method to exercise this code. The test method is implemented as an iterator.
If you're not familiar with attributes or iterators or proxy DLLs, don't worry about it. Just make a note and read up on them later. MRDS is a large topic and I don't have the energy or deep enough understanding to explain it all here. For now, let's keep the demo moving along...
Step 5: Run the Hello Program
To use the ft32 service from VPL, comment out SpawnIterator call in the Start method.
The sample Hello program can be downloaded here. This is possibly the most irritating Hello program known to exist. Setup and teardown requires several library calls, and a message box pops up after each step.
Hello32.mvpl - Hello program for the FT-32 brick