Driving the ROBO TX (FT-32) Brick With MRDS

How To

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

Step 2: Create a New Project in Visual Studio

Now let's do a sanity check:

To complete the thought, and to show that there's no magic involved here

Step 3: Add ftMscLib.dll and Wrapper Code to the Project

Add the DLL

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

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

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

Hello32.mvpl - Hello program for the FT-32 brick

 

Page last updated: