Create your own plugin

Here is a how-to on writing a plugin. We will first go through the “Speed and distance” plugin. It is recommended to download the source code of this plugin and do some minor adjustments, compile it and see that you get it loaded before going into the details.

Plugin architecture

A YourDyno plugin must implement the following interface:

public interface IDataIOProvider
{
    string name { get; }
    string pluginDescription { get; }
    string version { get; }
    void initDynoDataConnection(DynoDataConnection dynoDataConnection); // access to data from dyno box and OBD2
    List<OnePlugInDataConnection> pluginDataConnections { get; } // the data that IDataIOProvider provides to YourDyno
    System.Windows.Forms.ToolStripMenuItem pluginMenuEntry { get; }
    event ConfigChangeEventHandler OnConfigurationChange; // trigger this event to update configuration (i.e. signal names, etc)
}

You can think of an interface as a “contract” that is defined upfront. The plugin code implements the logic that is behind that interface. For example, when YourDyno calls SpeedAndDistance.name, the plugin will return the name of the plugin, in this case “Speed and Distance”. When SpeedAndDistance.pluginDescription is called, your code returns the description of the plugin. This is implemented in the plugin code like this:

public string name
{
    get
    {
        return "Speed and Distance";
    }
}
 
public string pluginDescription
{
    get
    {
        return "This plugin provides gauges and logging of speed and distance.";
    }
}

Passing data to YourDyno

Each plugin can provide one or more data connections to YourDyno. Each data connection can be shown in a gauge in the Run window and be seen as a graph in the main results window. The data can be data that the plugin generates completely itself, for example an analog value read from an analog USB card. Or it can be data calculated from other data already available in YourDyno, for example speed (which is calculated from RPM and some other parameters). It can also be a mix of the two. A data connection is implemented in the form of a class called OnePlugInDataConnection and it looks like this:

public class OnePlugInDataConnection
{
    public string pluginName; // name of the plugin (same as 'name' in IModule and IDataIOProvider)
    public float y; // polled data 
    public int graphPane; // 0 = off, 1 = 1st graph pane, 2 = 2nd graph pane, etc
    public bool isY2Axis; // whether to use the left or right (Y2) axis in the graphs
    public bool showGaugeInRunWindow; 
    public string name; // Name is used to identify gauges and graphs
    public string unit; // the unit that the data is measured in (e.g. N, kW, C, K, etc). Can be empty ("")
    public bool applyNoiseFiltering; // if true, noise filtering will be applied
    public UnitType unitType;
}

Most of the above is self explanatory, maybe apart from graphPane. The graphPane is where you want the graph of the data to appear. YourDyno has three graphPanes, and you specify which one to use by this variable. Use 0 if you do not want the ability to plot the graph of the data. “name” here is the name of the data that this plugin connection provides, e.g. “Speed”. The “Speed and Distance” plugin implements two data connections, “speed” is one and “distance” is the other. This is done like this:

private OnePlugInDataConnection speed = new OnePlugInDataConnection();
private OnePlugInDataConnection distance = new OnePlugInDataConnection();

and they are configured in the constructor of the Plugin (public SpeedAndDistance()):

speed.name = "Speed";
if (Settings.Properties.Settings.Default.UnitSelection == "Metric")
    speed.unit = "kph";
else
    speed.unit = "mph";
speed.graphPane = 1;
speed.isY2Axis = true;
speed.showGaugeInRunWindow = true;
speed.applyNoiseFiltering = true;
data.Add(speed);
 
distance.name = "Distance";
if (Settings.Properties.Settings.Default.UnitSelection == "Metric")
    distance.unit = "m";
else
    distance.unit = "ft";
distance.graphPane = 1;
distance.isY2Axis = true;
distance.showGaugeInRunWindow = true; 
distance.applyNoiseFiltering = true;
data.Add(distance);
 
// this is necessary for YourDyno to associate each plugin channel with the right plugin
foreach (OnePlugInDataConnection plugin in data)
    plugin.pluginName = name;

The two data connections are setup and then they are added to the “data” List. That list looks like this:

private List<OnePlugInDataConnection> data = new List<OnePlugInDataConnection>();

and is returned whenever YourDyno calls the pluginDataConnections{get;}, like this:

public List<OnePlugInDataConnection> pluginDataConnections
{
    get
    {
        return data;
    }
}

In this example all data is updated every time YourDyno receives data from the YourDyno box. Alternatively, data can be updated on read, or in the background with a timer for example.

Take care if your data requires extensive calculations or reads from a slow device. Data is read about 100 times per second, so the implementation of public List<OnePlugInDataConnection> pluginDataConnections must be fast.

Reading data from YourDyno

YourDyno receives data from the YourDyno box roughly 100 times per second. Every time data is received, an internal event called OnDynoDataReceived is triggered, and this causes data from the YourDyno box to be reads and processed.

In order to read data from YourDyno into the plugin for further processing, the plugin has two options.

  • Hook onto the OnDynoDataReceived event
  • Poll DynoDataConnection.processedDynoSample

Setup of hooking into the OnDynoDataReceived event is done in the initDynoDataConnection call like the below. In this case all data is calculated in the same event handler:

private DynoDataConnection dynoDataConnection;
public void initDynoDataConnection(DynoDataConnection d)
{
    dynoDataConnection = d;
    dynoDataConnection.OnDynoDataReceived += DynoDataReceivedEventHandler;
}

The DynoDataReceivedEventHandler looks like this:

public void DynoDataReceivedEventHandler(object sender, OnDataReceivedEventArgs e)
{
    float rollerRPM;
    if (SensorAndBrakeConfig.NumberOfRPMSensors() == 2)
        rollerRPM = (float)(e.processedDynoSample.instantRoller1RPM + e.processedDynoSample.instantRoller2RPM) / 2;
    else
        rollerRPM = (float)e.processedDynoSample.instantRoller1RPM;
 
    if (Settings.Properties.Settings.Default.UnitSelection == "Metric")
    {
        speed.y = rollerRPM * Properties.Settings.Default.brakeCirc * 60 / 1000;
 
        if (!dynoDataConnection.isLogging)
            distance.y = 0;
        else
            distance.y += rollerRPM / 60 * Properties.Settings.Default.brakeCirc * (float)e.processedDynoSample.timeStamp;
    }
    else
    {
        speed.y = rollerRPM * Properties.Settings.Default.brakeCirc * 60 / 1000 * (float)0.621371;
 
        if (!dynoDataConnection.isLogging)
            distance.y = 0;
        else
            distance.y += rollerRPM / 60 * Properties.Settings.Default.brakeCirc * (float)e.processedDynoSample.timeStamp * (float) 3.28084;
    }

Note that the OnDataReceivedEventArgs contains processedDynoSample of type OneProcessedSample, which contains the data from YourDyno. Here is how OneProcessedSample looks:

public class OneProcessedSample
{
    public double engineRPM;
    public double timeStamp;
    public double engineHP;
    public double engineTorque;
    public double wheelHP;
    public double wheelTorque;
    public double wheelHP1;
    public double wheelTorque1;
    public double wheelHP2;
    public double wheelTorque2;
    public double aux1;
    public double EGT;
    public double aux2;
    public double aux3;
    public double instantEngineRPM; // used for gauge
    public double roller1RPM;
    public double roller2RPM;
    public double instantRoller1RPM; // used for PID reg
    public double instantRoller2RPM; // used for PID reg
    public float gearRatio;
}

A few notes here:

  • You can only read data from YourDyno itself, you cannot read data from other plugins in a plugin
  • engineHP and engineTorque are calculated after the run and therefore not available during a run. This is because when doing a retardation run, the engine power/torque cannot be calculated on the fly. You can read wheelHP and wheelTorque on the fly
  • wheelHP1, roller1RPM, etc are data for brake 1, Similar for parameters with a 2 in the name. The parameter without the number is the calculated total parameter, for example wheelHP is the sum of wheelHP1 and wheelHP2

Other YourDyno data access

The DynoDataConnection enables access to other data too, for example raw data, brake values, usb serial port name, firmware version, etc. Most of these are not typically needed for plugins. Plugins can program the brake, but be careful with this as only one process must program the brake at any time, otherwise there will be conflicts:

public void ProgramBrake(double brakeValue1, double brakeValue2)

The SensorAndBrakeConfig class can be useful to check what kind of setup the system has:

public static uint NumberOfRPMSensors()
public static uint NumberOfLoadCells()
public static int NumberOfBrakes()

Plugin configuration

In order to configure the plugin, you hook into the YourDyno Plugin menu. This entry in the interface is used for configuring a Plugin menu item:

System.Windows.Forms.ToolStripMenuItem pluginMenuEntry { get; }

YourDyno will add an entry in the Plugin menu based on this ToolStripMenuItem. Here is the code needed in the plugin to enable this:

private System.Windows.Forms.ToolStripMenuItem menuEntry = new ToolStripMenuItem();

Then in the constructor:

this.menuEntry.Name = "menuEntry";
this.menuEntry.Text = "Speed and Distance";
this.menuEntry.Click += new System.EventHandler(this.menuItem_Click);

Then finally:

public System.Windows.Forms.ToolStripMenuItem pluginMenuEntry
{
    get
    {
        return menuEntry;
    }
}
private void menuItem_Click(object sender, EventArgs e)
{
    setDistancePerRev.ShowDialog();
}

setDistancePerRev in this example is a Windows form, designed as any other form.

Last updated byJostein on March 10, 2019

Leave a Reply

Your email address will not be published. Required fields are marked *