Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Walkthrough: Making a SetInMotion Device Plugin

0.00/5 (No votes)
11 Jun 2014 1  
Make a plugin for SetInMotion to talk to a device, and then control it from the entire SetInMotion ecosystem!

Introduction

There are tons of neat, awesome devices and services available in the world. When you want to do interesting things with them, it usually takes a lot of development work to add UI, connectivity, and so forth. What if you could write the bare minimum necessary to make your device or service work, and then immediately start using it with in interesting ways (even the cloud)? If that peaks your interest, read on...

Background

SetInMotion is a Windows .NET application and cloud service that serves as the 'glue' to connect things together in real-time. There are two API's available, one for Plugins (that work with the Windows application), and one for Cloud. This article discusses how to use the Plugin API.

The SetInMotion software is free to use in standalone (non-cloud) mode, and available at http://sim.x9tech.com. End-user examples and tutorials are highlighted on the SetInMotion Blog.

Channel Philosophy

What makes SetInMotion powerful is the "Channel" system. Every device within SetInMotion is required to report its state through its Channel properties. Let's take a look at how this might look in a more complex device with mostly outputs (an XBox Controller), and a simple device with a single input (a light bulb).

The XBox Controller and the Light Bulb are both devices. They both report their state via their Channel Properties. Channel property values can be:

  • An On/Off value, appropriate for buttons, relays, LED's, or anything that simply has an on/off or true/false state.
  • A percentage value (like 51.2), appropriate for dimmers, positions for servos, speeds, and so forth.
  • A text string (let's say you made a 'Scoreboard' device and one of the channel properties represents the Team Name).

When a device is added to the system, the user is required to define the channel name. The device then notifies SetInMotion of its properties and values (highlighted in green), and lets SetInMotion know when those values change. Likewise, SetInMotion notifies the device when something else changes its properties (like setting the light bulb's Intensity property). A device's channel property can be considered an input, an output, or both!

Because every device's inputs and outputs conform to the channel format, SetInMotion users are able to create Channel Actions that make things happen when channel properties change to do interesting things. In the example above, the user could choose to set the light bulb's Intensity property to 100 when the XBox Controller's ButtonA equals On, thus turning on the light bulb when the A button is pressed on the XBox Controller.

The power of Channels increases exponentially when SetInMotion is connected to the cloud, where other internet-connected applications can see and control these channels in real-time.

Create a Class Library, and add the SetInMotion API NuGet Package

SetInMotion plugins are class libraries. From Visual Studio, create a new Class Library project targeted at .NET 4.0 or .NET 4.5.

Then, add the NuGet package called SetInMotionApi to your project.

(The NuGet package is located here, for reference.)

When this package is installed, you'll see a reference to SIM.IO.API in your class library's references, as well as the SetInMotion API namespaces in your object browser.

You'll notice that there are various interfaces available.

Implement the IDevice interface

Any device in SetInMotion is exposed via a public class that implements the IDevice interface.

We'll create a device that brings the current DateTime.Now.Second into a SetInMotion channel. (This is about as simple as it gets.)

You can copy the following code to get started. Comments have been added in each required method.

using SIM.IO.API;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PluginDemo
{
    public class MyPlugin : IDevice
    {
        // The InputHostConnector should be implemented as a public property.
        // It is the object that the device will use to send channel value updates to SetInMotion.
        // SetInMotion will set this property just before Initialize() is called.
        public SIM.IO.API.HostConnectors.IHostConnector InputHostConnector { get; set; }

        public void DoBackgroundProcess()
        {
            // Anything the device needs to do at a regular interval - for example, polling/reading input values
            // and notifying SetInMotion when its channel property values change.
            InputHostConnector.SetChannelValue("CurrentTimeSeconds", DateTime.Now.Second.ToString());
        }

        public TimeSpan? GetBackgroundProcessInterval()
        {
            // Return a TimeSpan indicating how often SetInMotion should call DoBackgroundProcess().
            // As a best practice, use the built-in DoBackgroundProcess() methods provided, instead
            // of spawning new threads within the device.

            // If the device does not require any background processes, simply return null.
            return TimeSpan.FromSeconds(1);
        }

        public DeviceFaultStatus GetFaultStatus()
        {
            // If there's something wrong with the device (for example, the user unplugs it from the machine),
            // return something similar to the following:
            // return new DeviceFaultStatus(true, "Device is unplugged");
            // When a fault status is indicated, the device will appear in a red color within SetInMotion.

            // But in this case, everything is okay, so we'll return a non-faulted status:
            return new DeviceFaultStatus(false);
        }

        public string GetFriendlyName()
        {
            // The friendly name of the device, that will be displayed to the user.
            return "Current Seconds Clock";
        }

        private bool _initialized = false; // variable to keep track of initialization status
        public bool IsInitialized
        {
            get
            {
                // SetInMotion will check this property to determine if the device needs to be initialized.
                // If this property returns False, then SetInMotion will attempt to call Initialize()
                // at regular intervals until this property returns true.

                return _initialized;
            }
        }

        public void Initialize()
        {
            // Do whatever is necessary to initialize the device.
            // In this case, not much.

            _initialized = true;
        }

        public void Shutdown()
        {
            // When SetInMotion needs to shut down or remove the device, this method will be called.
            // Shut down the device and clean up any left-over resources.

            _initialized = false;
        }

        public void OnChannelValueChange(string channelId, string property, string value)
        {
            // SetInMotion notifies this device when any of its channel properties change.
            // channelId is the user-defined name of your device.
            //
            // This is where the device should process any channel changes considered to be "inputs"
            // of this device.
            //
            // If this device controlled motors, perhaps 'property' could be 'Speed' and the value
            // would indicate the motor speed.

            // In this case, this device doesn't have any outputs, so nothing needs to be done.
            return;
        }

        public void ExecuteCommand(DeviceCommand command)
        {
            // Sequences can send commands to devices. If the device should support commands,
            // they will arrive here. Parameters are parsed in advance and available via the
            // command.CommandSegments property as a string[].

            // This device supports no commands, so we will do nothing.
            return;
        }
    }
}

Important Device Concepts

There are 3 especially important things to keep in mind when designing and implementing any Device plugin:

InputHostConnector - Notify SetInMotion when things change.

SetInMotion assigns this object to your device prior to the initialization process. Any time something about your device changes, it should let SetInMotion know by calling:

InputHostConnector.SetChannelValue("MyProperty", "MyNewValue");

You don't have to worry about checking to make sure the value has changed since the last time you called - SetInMotion will deal with that for you.

SetInMotion is also designed to work fast. Try to design your device so that it detects changes quickly. That way, when a user presses a button or causes an interaction with your device plugin, they see near-immediate results.

Initialization, Shutdown, and Faults

Pay careful attention to get these areas right. When a user adds your device to SetInMotion, the following workflow takes place:

  1. SetInMotion will attempt to assign an InputHostConnector to your device.
  2. SetInMotion will check at regular intervals to find out if your device is initialized, via your IsInitialized property.
  3. SetInMotion will attempt to initialize your device with the Initialize() method if your IsInitialized property returns false.
  4. When the user shuts down or removes your device, SetInMotion will call the Shutdown() method.

A faulting status method is also checked regularly - GetFaultStatus(). This is the device's opportunity to tell SetInMotion (and the user) that something is wrong. It should make every effort to report anything wrong with the device's operation.

Devices should be able to auto-recover from user disconnections and reconnections.

Background Processes

When interacting with hardware, most likely you will need to poll or do things in the background at a regular interval. Normally, in a standalone application, you spawn and manage a thread. Don't do this! Extra threads that crash can bring down the entire SetInMotion application, creating frustrated users.

SetInMotion's Background Process is designed to make that easier, and automatically handles thread scheduling and error handling.

Simply tell SetInMotion how often your background processes needs to run via the GetBackgroundProcessesInterval() method (whether it's milliseconds or minutes), and do your work in the DoBackgroundProcess() method.

Testing Your Plugin

It's a pain to write test apps to test your plugin. So, we do it for you. Go get the Developer Sandbox from http://sim.x9tech.com/developer/sandbox and launch it after compiling your class library.

Click Browse... and select your Class Library's DLL.

You should now see that your device is available in the drop-down list:

From here, you can test initialization and shutdown of your device, as well as view your device's reported channels in real-time.

When your device is initialized, the Developer Sandbox will assign a random channel ID, to simulate the end-user providing a channel name.

If the channel is an input, the Developer Sandbox allows you to simulate setting the channel to a value of your choosing, or some of the more common values (0, 100, On, or Off).

Debugging Tip

If you want to step through how your device is processing while using the Developer Sandbox, in Visual Studio, click Debug > Attach to Process...

Attach to SIM.DeveloperSandbox.exe, and you will be able to set breakpoints in your plugin's code.

Publishing Your Plugin

Once you've tested the device, it's time to package and publish it! You can publish your plugin to your local machine, or the SetInMotion Cloud for others to use.

From the Developer Sandbox, click Package / Publish... and fill in the fields:

The Package/Publish dialog will automatically pull suggested names from your assembly's manifest.

Plugin Display Name - The name that will be displayed to SetInMotion users when installing and using the plugin.

Plugin System Name - This is how SetInMotion and the SetInMotion Store refers to your plugin. This must be unique for your plugin - you cannot upload a plugin with a system name that someone else's account has already published.

Version - This information is pulled from your plugin assembly's manifest. The initial version number doesn't matter, but as you re-publish your plugin, the version number in your class library's manifest (AssemblyInfo.cs) must be greater than the previously published version.

Deploy to My Machine

This option will bypass the SetInMotion Store and package/deploy the plugin to your local machine, great for testing with the actual SetInMotion software.

SetInMotion plugin packages are stored in %ProgramData%\SetInMotion\Plugins and are extracted at runtime. While you can deploy at anytime, you will need to restart SetInMotion for your new plugin to be detected.

Publish to SetInMotion Store

Publishing to the SetInMotion Store allows other users to see, install, update, and use your plugin. Plugin publishing requires a SetInMotion account.

That's it!

Congratulations! You should have a working SetInMotion Device Plugin at this point. If your device requires settings support, you can also implement the IDeviceWithSettings interface, but we'll save that for another walkthrough.

History

6/11/2014 - Initial article

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here