Developer Center   

 

Walkthrough - Writing A Real-World Servo Plugin

This tutorial will walk you through creating a real-world servo device plugin. While the tutorial is presented in the context of the PhidgetServo 1-Motor (or 4-Motor) device, it also serves as a guide for other servo device types. The code is heavily commented to show you how you can use the code to help you write a plugin for your servo device. The actual functional code is about 75 lines.

Strategy

What we are doing: We are writing a MakeAffinity servo plugin for the PhidgetServo 1-Motor (or 4-Motor) device. Each PhidgetServo has a number of servo motors for which we can set the position in degrees. We can also read the position of each servo motor. We can also read the count of servo motors that the device has.

How the Phidgets API works: The Phidget's Manager provides a list of Phidgets devices that are present on your computer. There are many different types of Phidgets devices. And each device has a factory provided serial number that is unique across all Phidgets devices.

How our MakeAffinity plugin manager class will work: Our plugin's manager is responsible for creating instances of our plugin servo class. Therefore, our manager class will ask the Phidget's Manager how many Phidget Servo Controller devices are present, get their serial numbers, and for each device, instantiate a plugin servo class to control it.

How our MakeAffinity plugin servo class will work: Given a serial number for a Phidget Servo Controller device, our servo plugin class will open it, hold a reference to it, and work with it using the Phidgets API. Each servo class will then control its device appropriately when that class' methods are called. I.e. each class' methods will call its Phidgets device's methods to achieve what needs to be done. E.g. when our servo class' Initialise method is called, we will call the appropriate Phidget Servo Controller device's API methods to get it to a ready state.

  

You will need the following: 

1. A working installation of MakeAffinity Client (setup here)

2. Visual C# Express (it's nice and free).

3. Your device plugged in.

  

The tutorial assumes that you are familiar with MakeAffinity and C#. The tutorial uses Visual C# Express 2005. Instructions and screen shots may be slightly different from other versions of Visual C# Express but are just as applicable. The tutorial also assumes that you are familiar with the device that you are writing the plugin for.

The tutorial should take you less than 50 minutes.

Visual C# Express will be referred to as VCE for the sake of brevity. MakeAffinity Client will be referred to as the client for the same reason.

The VCE project for this walkthrough can be downloaded from the Developer Center under Downloads.

The complete colour-coded source code for this walkthrough is shown here.

 

The main steps of writing a plugin are:

Step 1: Create a new class library project in VCE.

Step 2: Add a reference to the client's MAFFDevice.dll and your device's driver or library (Phidgets).

Step 3: Set the output path of your plugin project to your client's plugin folder.

Step 4: Add client Start & Stop build events to your plugin project.

Step 5: Create a MyManager class that inherits from MakeAffinity.Plugin.Manager.Manager.

Step 6: Create a MyServo class that inherits from MakeAffinity.Plugin.Devices.Servo.

Step 7: Write code for the methods in MyServo.

Step 8: Write code for the methods in MyManager.

Step 9: Compile and test your plugin.

Step 10: A debugging scenario.

 

Step 1: Create a new class library project in VCE

 
In VCE, go to File -> New Project -> choose Class Library -> use MyPlugin as name -> click OK.

When VCE completes creating your new project, it will display your source file.

 

Step 2: Add a reference to MakeAffinity Client's MAFFDevice.dll and your driver's library (Phidgets).

A) Go to Project -> Add Reference to bring up the Add Reference dialog box.

B) Click on the Browse tab, locate MakeAffinity Client's MAFFDevice.dll and click OK.
  
  MAFFDevice.dll contains the device types you need to inherit from in your plugin.
  The file should be located in C:\Program Files\MakeAffinity\makeaffinity\bin
  
  Once added, MAFFDevice.dll will be listed under References in Solution Explorer.

C) Go to Project -> Add Reference to bring up the Add Reference dialog box again.

D) Click on the .NET tab, locate the Phidget21.NET for v2.x.x Runtime and click OK.

  Note: Make sure you choose the one for v2.x.x of the .NET Runtime. 

  This walkthrough is presented in the context of a Phidgets device and it assumes that you have the Phidgets driver installed. If you are not writing the plugin for the Phidgets device then you will need to add references to the driver API of the device that your are writing the plugin for.
  

Step 3: Set the output path of your plugin project to the Client's plugin folder 
 
A) Go to Project -> MyPlugin Properties to bring up the project properties dialog page.
 
B) Click on the Build tab.
 
C) Click on the Browse button under Output path to bring up the Select Output Path dialog box.
 
D) Locate and choose MakeAffinity Client's plugin folder.
 
  This will make VCE copy the project's compiled output to the folder so the client will
  always be able to use the latest compiled version.
  
  The folder should be located at C:\Program Files\MakeAffinity\makeaffinity\DevicePlugins

 

Step 4: Add client Start & Stop build events to your plugin project

A) Go to Project -> MyPlugin Properties to bring up the project properties dialog page.
 
B) Click on the Build Events tab.

C) Add this to the Pre-build event command line (including the double-quotes):

  "C:\Program Files\MakeAffinity\bin\ClientStop.exe"

D) Add this to the Post-build event command line (including the double-quotes):

  "C:\Program Files\MakeAffinity\bin\ClientStart.exe"

The steps above are to stop the client each time VCE begins any compilation activities, and restart the client when compilation completes so you can see any new code changes in action.

 

Step 5: Create a MyManager class that inherits from MakeAffinity.Plugin.Manager.Manager 

The original code in Class1.cs should look like this:

  using System;
  using System.Collections.Generic;
  using System.Text;

  namespace MyPlugin
  {
     public class Class1
     {
     }
  }


   
  
A) Replace all of the code in the Class1.cs with the following:

  using System;
  using System.Collections.Generic;
  using System.Text;

  using MakeAffinity.Plugin;

  namespace MyPlugin
  {
    public class MyManager: MakeAffinity.Plugin.Manager.Manager
    {
    }
  }


  The following is how the new code is different:
  
   1) Added the directive: using MakeAffinity.Plugin;
  
   2) Deleted Class1.
   
   3) Added a MyManager class which inherits from MakeAffinity.Plugin.Manager.Manager.  
  
 
B) Implement abstract class MakeAffinity.Plugin.Manager.Manager using IntelliSense.
 
  In the code, click on MakeAffinity.Plugin.Manager.Manager. The first letter will then appear underlined.

  Click the smart tag under MakeAffinity,
   and click Implement abstract class 'MakeAffinity.Plugin.Manager.Manager'.
   
  IntelliSense adds two override methods from the Manager class to the
   MyManager class: Initialise and Shutdown.

  

Step 6: Create a MyServo class that inherits from the MakeAffinity.Plugin.Devices.Servo type 

A) Add a MyServo class to the code so that the end result looks like the following.

  using System;
  using System.Collections.Generic;
  using System.Text;

  using MakeAffinity.Plugin;

  namespace MyPlugin
  {
    public class MyManager: MakeAffinity.Plugin.Manager.Manager
    {
      public override void Shutdown()
      {
        throw new Exception("The method or operation is not implemented.");
      }

      public override void Initialise()
      {
        throw new Exception("The method or operation is not implemented.");
      }
    }

    public class MyServo : MakeAffinity.Plugin.Devices.Servo
    {
    }
  }


B) Like before, implement abstract class MakeAffinity.Plugin.Devices.Servousing IntelliSense.
 
  In the code, click on MakeAffinity.Plugin.Devices.Servo. The first letter will then appear underlined.

  Click the smart tag under MakeAffinity,
   and click Implement abstract class 'MakeAffinity.Plugin.Devices.Servo'.
   
  IntelliSense adds nine override methods from the Servo class to the MyServo class:
   BrandName, ModelName, SerialNumber, Initialise, Shutdown, MakeSafeAll, ServoCount, GetPosition, SetPosition

 
Step 7: Write code for the methods in MyServo 

IntelliSense has helped us to create the method stubs. We can now write the code for them. We will focus on MyServo first. Implement the methods in MyServo as shown below.

A) Create three class fields: One to remember the Servo Controller's serial number, one to hold the reference to the Servo Controller device, and one to remember when we have already hooked up the Servo Controller's error event to our error message handler.

/// <summary>
/// externalDeviceSerialNumber: Used to store the actual phidget
/// device's serial number.
/// The serial number is given to our servo class by our plugin's manager
/// </summary>

int externalDeviceSerialNumber;

/// <summary>
/// externalDevice: Used as a reference to the actual phidgets device
/// so the class can control it. A null value indicates that
/// we have not initialised it. (phidgets API field)
/// </summary>

Phidgets.Servo externalDevice = null;

/// <summary>
/// hasHookedUpEventHandler: Set to true when the class
/// has hooked up its error reporting method to the device's
/// error event. This will be used to prevent us from hooking
/// it up more than once.
/// </summary>

bool hasHookedUpEventHandler = false;
 

B) Implement the common device identification methods (BrandName, ModelName, SerialNumber) so the client will know how to identify our device. We'll use the following scheme for our brand name, model name, and serial number.

Phidget's device naming convention is: Name = "Phidget Servo Controller 4-Motor", SerialNumber = an integer. For our plugin, we'll return the following: BrandName: "Phidgets", ModelName: "Servo Controller 4-Motor", SerialNumber: Use the device's serial number.

/// <summary>
/// Phidget's device naming convention is:
/// Name = "Phidget Servo Controller 4-motor"
/// Type = "PhidgetServo"
/// SerialNumber = an integer;
///
/// For our plugin, we'll return the following for identification:
/// BrandName: "Phidgets"
/// ModelName: "Servo Controller 4-motor"
/// SerialNumber: Use Phidget's own serial number
/// </summary>
public override string BrandName
{
  get { return "Phidgets"; }
}

/// <summary>
/// Phidget's device naming convention is:
/// Name = "Phidget Servo Controller 4-motor"
/// Type = "PhidgetServo"
/// SerialNumber = an integer;
///
/// For our plugin, we'll return the following for identification:
/// BrandName: "Phidgets"
/// ModelName: "Servo Controller 4-motor"
/// SerialNumber: Use Phidget's own serial number
/// </summary>

public override string ModelName
{
  get
  {
    //(phidgets API call)
    string[] tokens = externalDevice.Name.Split(new char[] { ' ' });

    return tokens[1] + " " + tokens[2] + " " + tokens[3];
  }
}

/// <summary>
/// Phidget's device naming convention is:
/// Name = "Phidget Servo Controller 4-motor"
/// Type = "PhidgetServo"
/// SerialNumber = an integer;
///
/// For our plugin, we'll return the following for identification:
/// BrandName: "Phidgets"
/// ModelName: "Servo Controller 4-motor"
/// SerialNumber: Use Phidget's own serial number
/// </summary>

public override int SerialNumber
{
  //(phidgets API call)
  get { return externalDevice.SerialNumber; }
}

   
C) Implement the common device management methods (Initialise, Shutdown, MakeSafeAll) so the client can manage our device.

/// <summary>
/// Make the actual device ready and remember that we've done so to
/// prevent us from 'over initialising' the actual device.
/// </summary>

public override void Initialise()
{
  // do not initialise again if already initialised.
  // externalDevice == null means not yet initialised.
  if (externalDevice != null) return;

  // instantiate the actual device (phidgets API call)
  externalDevice = new Phidgets.Servo();

  //hookup to the external device's error reporting mechanism if
  //we've not done so already

  if (!hasHookedUpEventHandler)
  {
    //(phidgets API call)
    externalDevice.Error += new Phidgets.Events.ErrorEventHandler (externalDevice_Error);

    // set to true so we won't do it more than once by mistake.
    hasHookedUpEventHandler = true;
  }

  // open the phidget device (phidgets API call)
  externalDevice.open(externalDeviceSerialNumber);

  // (phidgets API call)
  externalDevice.waitForAttachment(1000);

  // (phidgets API call)
  if (!externalDevice.Attached)
  {
    // (phidgets API call)
    externalDevice.close();

    ReportErrorMessage(
   this, 0, "Failed to initialise external device (PhidgetServo)", null);
  }
}

/// <summary>
/// Shutdown the actual device and set our internal variables
/// appropriately so if and when we need to initialise the
/// plugin we can do so correctly.
/// </summary>

public override void Shutdown()
{
  // make safe/off the actual device
  MakeSafeAll();

  // close the external device: Phidgets use the close method for
  // this. (phidgets API call)

  externalDevice.close();

  // set our reference to null so if our initialise method is
  // called again we can proceed with initialisation.
  // setting this to null also frees resources.

  externalDevice = null;
}

/// <summary>
/// Put the actual device into a safe/off state
/// </summary>
public override void MakeSafeAll()
{
  // set all of the actual device's servo motors' 
  // to off. -23 = power-off for PhidgetServo (phidgets API call)

  foreach (Phidgets.ServoServo ser in externalDevice.servos)
    ser.Position = -23;

  string tempStr = "MadeSafe: " +
    BrandName + ", " + ModelName + ", " + SerialNumber;
  ReportErrorMessage(this, tempStr);
}

  
D) Implement the methods that are specific to the servo plugin class (GetPosition, SetPosition, ServoCount).

/// <summary>
/// Read how many servo motors this device has
/// </summary>
public override int ServoCount
{
  // (phidgets API call)
  get { return externalDevice.servos.Count; }
}

/// <summary>
/// Set the position in degrees of a particular servo motor
/// </summary>
/// <param name="index"></param>
/// <param name="position"></param>

public override void SetPosition(int index, double position)
{
  // (phidgets API call)
  externalDevice.servos[index].Position = position;
}

/// <summary>
/// Read the current position of a particular servo motor
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public override double GetPosition(int index)
{
  // (phidgets API call)
  return externalDevice.servos[index].Position;
}

E) Implement the constructor and error event handler that we will hookup during Initialisation.

/// <summary>
/// The constructor takes the Phidget device's serial number
/// during instantiation. The number is given to us
/// by our plugin manager.
/// Our plugin manager gets it from the Phidget's manager.
/// </summary>
/// <param name="serialNumber"></param>

public MyServo(int serialNumber)
{
  // we'll use the serial number to find the device during
  // initialisation.
  this.externalDeviceSerialNumber = serialNumber;
}

/// <summary>
/// Our handler for the actual device's error event.
/// The method should convert the actual device's
/// error info into a format that the our
/// ReportErrorMessage method can use.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>

void externalDevice_Error(object sender, Phidgets.Events.ErrorEventArgs e)
{
  // we translate and pass on the actual device's error info
  ReportErrorMessage(this, e.Code, e.Description, null);
}


  

Step 8: Write code for the methods in MyManager  

A) Create two class fields: one to hold the reference to the Phidgets Manager, and one to remember when we have already hooked up the Phidget Manager's error event to our manager's error message handler. 

/// <summary>
/// hasHookedUpEventHandler: Set to true when the manager class
/// has hooked up its error reporting method to the Phidget
/// Manager's error event. This will be used to prevent
/// us from hooking it up more than once.
/// </summary>

bool hasHookedUpEventHandler = false;

/// <summary>
/// phidgetMgr: Our reference to the
/// Phidgets Manager (phidgets API field)
/// </summary>

Phidgets.Manager phidgetMgr = new Phidgets.Manager();
 

 B) Implement the error event handler that we will hookup to Phidget Manager during our manager's Initialisation.

/// <summary>
/// Our handler for Phidget Manager's error event.
/// The method should convert the Phidget Manager's
/// error info into a format that our
/// ReportErrorMessage method can use.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void phidgetMgr_Error(object sender, Phidgets.Events.ErrorEventArgs e)
{
  ReportErrorMessage(this, e.Code, e.Description, null);
}

C) Implement MyManager's Initialise method. 

/// <summary>
/// Manager.Initialise will ask the Phidget's Manager
/// how many "Phidget Servo Controller" devices are present,
/// get their serial numbers, and for each device,
/// instantiate a servo class to control it.
/// The servo objects are then stored in MAFFDevices
/// ready for the client to process
/// Manager.Initialise must put all devices in a
/// ready-to-use state.
/// </summary>
public override void Initialise()
{
  //hookup to Phidget Manager error reporting mechanism if
  //we've not done so already
  if (!hasHookedUpEventHandler)
  {
    //(phidgets API call)
    phidgetMgr.Error += new Phidgets.Events.ErrorEventHandler(phidgetMgr_Error);

    // set to true so we won't do it more than once by mistake.
    hasHookedUpEventHandler = true;
  }

  // open the phidget manager (phidgets API call)
  phidgetMgr.open();

  // Use the Phidget Manager to scan through the
  // list of phidget devices present on the system.
  // Instantiate a servo plugin class for each
  // "Phidget Servo Controller" device found,
  // using the device's serial number.
  // Initialise each instantiated servo plugin to make it
  // ready for use.
  // Add instantiated servo plugins to MAFFDevices collection.
  foreach (Phidgets.Phidget phidgetDev in phidgetMgr.Devices)
  {
    if (phidgetDev.Name.Contains("Phidget Servo Controller"))
    {
      MyServo wrapper = new MyServo(phidgetDev.SerialNumber);

      wrapper.Initialise();

      MAFFDevices.Add(wrapper);
    }
  }
}

D) Implement MyManager's Shutdown method

/// <summary>
/// Manager.Shutdown shuts down all devices and
/// clears the MAFFDevices collection.
/// IMPORTANT: Manager.Shutdown must put all devices
/// in a safe and off state when it completes
/// Further: Any allocated resources should also be made free.
/// </summary>

public override void Shutdown()
{
  //shutdown all plugin objects in MAFFDevices
  foreach (MakeAffinity.Plugin.Devices.Device dev in MAFFDevices)
  {
    dev.Shutdown();
  }

  //remove all plugin objects in MAFFDevices collection
  MAFFDevices.Clear();
}

 

Step 9: Compile and test your plugin

The code is now complete and is here for your reference (Code Listing 3).

A) Compile your project: Go to Build -> Build Solution

  VCE will launch the MakeAffinity Client when compilation completes without errors. If you encounter errors, compare your code to Code Listing 3.

B) At the client, click on Status button to see the status messages.

  You should see messages indicating that your plugin is working.

C) See your plugin in action at the website:

  Go to www.makeaffinity.com and log into your account.
  
  Go to Make -> Constructs tab -> Select any construct.

  Go to Actions tab -> Add Action -> Show My Devices.
   
  Your plugin device should show up in the Device dropdownlist with the serial number, brand name, and model name that you have determined in your code.

  You can now use your servo device to make a construct. 

  Change the Motor No. and Position at the Make Editor and click Test and observe your device in physical action.   

 

Step 10: A debugging scenario

Here, we will create errors in your plugin and show you how you can debug it.

A) Introduce an exception. Add the following code at the top of MyManager.Initialise:

  throw new Exception("Exception 1");

B) Compile your project: Go to VCE -> Build -> Build Solution

  VCE will launch the client when compilation completes.

C) Click on Client -> Status button to see status messages. You will see the following:

 ERROR: Unable to load plugin in MyPlugin.dll, Message: Exception 1

D) More detailed information about the error is in the client's log file at C:\Program Files\MakeAffinity\makeaffinity\Logs\log.txt:

ERROR: Unable to load plugin in MyPlugin.dll, Message: Exception 1
Exception generated:
 Message: Exception 1
 Source: MyPlugin
 Method: Void Initialise()
 Stack Trace:    at MyPlugin.MyManager.Initialise() in path\MyPlugin\MyPlugin\Class1.cs:line xx
   at v.c()

  The detailed error message includes useful information such as the error message itself, the source assembly, namespace, class, method, and line number, as well as a stack trace.

  The stack trace indicates that the fault is around line xx in the source code. This is where we deliberately introduced the exception for our debugging scenario.

  Right now, the plugin device will not appear in your Make Editor because it has failed to load successfully. Removing the erroneous line and rebuilding the project will remedy this. 

 

 

 

 

 End of tutorial   


 




MakeAffinity © 2009     About     Developers     Jobs     Company Info     Business Solutions     Partners     Terms     Help     |     mon.itor.us