Plugin development

Anders Malmgren edited this page Jun 11, 2013 · 11 revisions

To create an external Plugin to use with FreePIE reference the FreePIE.Core.Contracts assembly and implement the IPlugin interface.

[GlobalType(Type = typeof(MyPluginGlobal))]
public class MyPlugin : IPlugin
{
    public object CreateGlobal()
    {
        return new MyPluginGlobal(this);
    }

    public Action Start()
    {
        //This method is called just before script starts, here you can do your start up stuff
        return null;
    } 

    public void Stop()
    {
        //Clean up here
    }

    public event EventHandler Started; 

    public string FriendlyName
    {
        get { return "A name that will be used from GUI"; }
    }

    public bool GetProperty(int index, IPluginProperty property)
    {
    }

    public bool SetProperties(Dictionary<string, object> properties)
    {
    }

    public void DoBeforeNextExecute()
    {
        //This method will be executed each iteration of the script
    }
} 

[Global(Name = "myPlugin")]
public class MyPluginGlobal
{
    private readonly MyPlugin myPlugin; 

    public MyPluginGlobal(MyPlugin myPlugin)
    {
        this.myPlugin = myPlugin;
    }

    public void setYaw(double yaw)
    {
        //Call some method on plugin to set yaw
    }
}

You need to mark your Plugin with the GlobalType attribute, this is so that the engine knows which type that is accessible from script. IsIndexed is a optional setting on the attribute that tells the engine that the plugin global is indexed, this should be used when the CreateGlobal method returns a Collection of globals.

CreateGlobal should return a object with all your public members that you want to expose to the script. This object must also use the Global attribute, its used to give it a name that you can use from the script, use camel case.

Start is called just before the script starts to execute, here you can do start up stuff. Also if your plugin for some reason needs to be threaded you can return an action from this method, that action will be threaded.

    public Action Start()
    {
        return () =>
        {
            running = true;
            while (running)
            {
                //do threaded stuff
            }
        };
    }

Stop is called when the script stops, here you can dispose your resources, if you have a threaded action like above you have to make sure it will exit, something like this

    public void Stop()
    {
        running = false;
    } 

If your plugin is threaded you also need to trigger the Started event, this will tell the engine that your plugin is ready and that the script can start.

The property FriendlyName is used to give the plugin a readable name to use from the Settings page of the GUI it does not need to be camel case, or follow any special rules. Keep it pretty short since its presented in a Windows menu.

The GetProperty method is called at Gui start up and is used to populate the plugin settings. It will be called with an index 0 - n until the method returns false. It can look something like this.

    public bool GetProperty(int index, IPluginProperty property)
    {
        switch (index)
        {
            case 0:
                property.Name = "SomeProperty";
                property.Caption = "Property name";
                property.HelpText = "This is a helptext to describe what this option does"; 

                property.Choices.Add("This is option one in dropdown", 1);
                property.Choices.Add("This is option two in dropdown", 2); 

                property.DefaultValue = 1;
                return true;
            case 1:
                property.Name = "FloatValue";
                property.Caption = "Choose a number";
                property.DefaultValue = 1.0f;
                property.HelpText = "This is a number"; 

                return true;
        }
        return false;
    }

Its important that the property.DefaultValue is strongly typed because it will be used by the engine to determine the type of the property. Do property.DefaultValue = 1 not property.DefaultValue = "1"

SetProperties will be called just before the Start method, it will give you a dictionary with the key defined in the GetProperty method and the value is the selected value for the property. For the above GetProperty example the SetProperties should look something like this

    public bool SetProperties(Dictionary<string, object> properties)
    {
        int someProperty = (int)properties["SomeProperty"];
        float floatValue = (float)properties["FloatValue"];
    }

DoBeforeNextExecute Is a method that will be called on each iteration of the script, here you can read / write from / to your Hardware and also trigger a Update event. The script engine isn't thread safe and if your plugin is threaded its Update event has to be triggered from this method. An example on a usage for this method (Example is from the AHRS IMU plugin included in the FreePIE source)

    public void DoBeforeNextExecute()
    {
        if (newData)
        {
            OnUpdate();
            newData = false;
        }
    }

The background thread (See the section about Start) reads data from the hardware and if new data is available it will set the newData boolean to true. This will ensure that the OnUpdate event is triggered on the script thread,

Your plugin does not need to implement an event, but if you do please follow the name standard and name it update.

Your global object can have any kind of public methods, just make sure they are named using camel case. Its a good practice to keep the global object class clean of hardware implementation, just use it as a bridge for your plugin like this

    [Global(Name = "myPlugin")]
    public class MyPluginGlobal
    {
        private readonly MyPlugin myPlugin;

        public MyPluginGlobal(MyPlugin myPlugin)
        {
            this.myPlugin = myPlugin;
        }

        public double yaw
        {
            get { return myPlugin.Yaw; }
            set { myPlugin.Yaw = value; }
        } 
    }

If you are part of the FreePIE team and want to create an internal plugin (A plugin to be shipped with the FreePIE package) you should instead inherit from Plugin, and your global should inherit from UpdateblePluginGlobal, note that your global class only need to inherit from this base class if it needs Update event functionality.

For an example of all the features look at the AhrsImuPlugin csharp file

Debug external Plugin

Its a bit more complicated to debug a plugin that's not part of the FreePIE core. Right click your Plugin's Class Library Project and select Properties and Debug. Tick "Start external project" and browse to the install directory of FreePIE and select FreePIE.exe. You also need to copy the output of your Library to the Plugin folder of FreePIE. Select Build and change the output path from bin\xxx to the plugin folder of FreePIE. You can now debug like normal.

Note If you use the install folder under Program files and you have UAC turned on you will run into security problems, you can copy the install folder to a separate folder and debug from there.