Remote Control of .NET Gadgeteer Device via REST Web Service

This example extends code in a previous post: Using the .NET Gadgeteer DigitalOutput Interface. The previous example uses Gadgeteer.Interfaces.DigitalOutput to turn on a LED from application code instead of through a module driver.  .NET Gadgeteer device components can be controlled directly from code.  We’re going to extend this scenario to control various device components by setting their state via a Web service that is polled on a regular basis by the device.  In this implementation users can control devices from any application that can send a Web request with a POST method.  Other modules that use drivers can be controlled by the same scenario as in this simple use of the Gadgeteer.Interfaces.DigitalOutput interface.

REST Web Service to Control State of .NET Gadgeteer Device Remotely

Another previous example contains an explanation of the WCF REST Service Template 40(C#) and its use.  Documentation is abbreviated here for the Web service that this device polls for state information.

The Web service runs a single instance of the PollRemote class as specified by the InstanceContextMode.Single attribute.  This specifies that the state of the service is continuous.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

The state information, for which the device will poll, is of type PollItem.

using System;
namespace PollRemote
{
    public class PollItem
    {
        public bool CurrentState { get; set; }
        public DateTime TimeSet { get; set; }
        public int SettingsPerHour { get; set; }
    }
}

Because the service needs only a single instance of PollItem, the data can be managed by global variable in the PollRemote service instead of in a database or other storage. The members of the state item include: CurrentState; TimeSet; and SettingsPerHour.   These members are set each time a GET or POST request is received by the Web service as shown in the following code block.

The Boolean member, CurrentState, determines the state of the .NET Gadgeteer device when it polls the service on an interval set by the device. The TimeSet member supports a record of when the state of the device is changed.  Code could be added to throttle this if the SettingsPerHour start rising to a level that indicates a hack on the service.  Authentication is necessary in most production applications.

public class PollRemote
    {
        // TODO: Implement the collection resource that will contain the SampleItem instances
        PollItem state;
        TimeSpan hour = new TimeSpan(1, 0, 0, 0);

        [WebGet(UriTemplate = "")]
        public PollItem GetState()
        {
            if(state != null)
                return state;

            state = new PollItem();
            state.CurrentState = false;
            state.TimeSet = DateTime.Now;
            state.SettingsPerHour = 1;

            return state;
        }

        [WebInvoke(UriTemplate = "/{IsHigh}", Method = "POST")]
        public PollItem Create(PollItem instance, string IsHigh)
        {
            if (state == null)
            {
                state = new PollItem();
                state.CurrentState = bool.Parse(IsHigh);
                state.TimeSet = DateTime.Now;
                state.SettingsPerHour = 1;
                return state;
            }

            state.CurrentState = bool.Parse(IsHigh);
            DateTime oldSetting = state.TimeSet;
            state.TimeSet = DateTime.Now;
            bool test = ((state.TimeSet - oldSetting) < hour);
            state.SettingsPerHour = ((state.TimeSet - oldSetting) < hour)? state.SettingsPerHour + 1: 1;

            return state;
        }
    }

There are some details of this implementation that do not show in the code excerpted previously, so here is the complete PollRemote.cs file. Unnecessary using directives have been removed, but the boilerplate comments remain for orientation in the WCF REST Service Template 40(C#).

using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;

namespace PollRemote
{
    // Start the service and browse to http://<machine_name>:<port>/PollRemote/help to view the service's generated help page
    // NOTE: By default, a new instance of the service is created for each call; change the InstanceContextMode to Single if you want
    // a single instance of the service to process all calls.
    [ServiceContract]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]

    public class PollRemote
    {
        // TODO: Implement the collection resource that will contain the SampleItem instances
        PollItem state;
        TimeSpan hour = new TimeSpan(1, 0, 0, 0);

        [WebGet(UriTemplate = "")]
        public PollItem GetState()
        {
            if(state != null)
                return state;

            state = new PollItem();
            state.CurrentState = false;
            state.TimeSet = DateTime.Now;
            state.SettingsPerHour = 1;

            return state;
        }

        [WebInvoke(UriTemplate = "/{IsHigh}", Method = "POST")]
        public PollItem Create(PollItem instance, string IsHigh)
        {
            if (state == null)
            {
                state = new PollItem();
                state.CurrentState = bool.Parse(IsHigh);
                state.TimeSet = DateTime.Now;
                state.SettingsPerHour = 1;
                return state;
            }

            state.CurrentState = bool.Parse(IsHigh);
            DateTime oldSetting = state.TimeSet;
            state.TimeSet = DateTime.Now;
            bool test = ((state.TimeSet - oldSetting) < hour);
            state.SettingsPerHour = ((state.TimeSet - oldSetting) < hour)? state.SettingsPerHour + 1: 1;

            return state;
        }
    }
}

My Global.asax file is shown in the following code block. Using an empty string in the ServiceRoute constructor for the RoutePrefix parameter eliminates a redundant url syntax.

using System;
using System.ServiceModel.Activation;
using System.Web;
using System.Web.Routing;

namespace PollRemote
{
    public class Global : HttpApplication
    {
    void Application_Start(object sender, EventArgs e)
        {
            RegisterRoutes();
        }

        private void RegisterRoutes()
        {
            // Edit the base address of PollRemote by replacing the "PollRemote" string below
            RouteTable.Routes.Add(new ServiceRoute("", new WebServiceHostFactory(), typeof(PollRemote)));
        }
    }
}

For more information about installing this Web service on the host, see the REST Web Service to Record Data from a .NET Gadgeteer Sensor Device topic, which describes a similar implementation.

Device Code to Poll Web Service for Remote Control of .NET Gadgeteer Device

The core of this scenario is in the Gadgeteer.Timer.Tick event handler of the Gadgeteer.Timer object. This event activates polling the Web service and sets the polling interval.  The device polls the Web service by an HttpWebRequest.  An XmlTextReader reads the response and calls the Gadgeteer.Interfaces.DigitalOut.Write method to set the state of the component to be turned on or off.

    void timer_Tick(GT.Timer timer)
    {
        HttpWebRequest request =
        WebRequest.Create("http://integral-data.com/PollRemote/") as HttpWebRequest;

        HttpWebResponse response;
        request.Method = "GET";

        try
        {
             response = request.GetResponse() as HttpWebResponse;
             XmlTextReader reader = new XmlTextReader(response.GetResponseStream());

            if (reader.ReadToFollowing("CurrentState"))
            {
                if (reader.ReadElementString() == "true")
                    ledControl.Write(true);
                else
                    ledControl.Write(false);
            }

            response.Close();
        }
        catch (Exception ex)
        {
            string exception = ex.Message + " Inner:" + ex.InnerException;
        }

        request.Dispose();
    }

The complete code for the client device is shown in the following code block.

using System;
using Microsoft.SPOT;

using GT = Gadgeteer;
using GTM = Gadgeteer.Modules;
using Gadgeteer.Modules.GHIElectronics;
using System.Net;
using System.Xml;

namespace DigitalOut
{
    public partial class Program
    {
        GT.Interfaces.DigitalOutput ledControl;
        GT.Timer timer;
        void ProgramStarted()
        {
            GT.Socket socket = GT.Socket.GetSocket(10, true, null, null);
            ledControl = new GT.Interfaces.DigitalOutput(socket, GT.Socket.Pin.Four, false, null);

            timer = new GT.Timer(15000);
            timer.Tick += new GT.Timer.TickEventHandler(timer_Tick);

            button.ButtonPressed += new Button.ButtonEventHandler(button_ButtonPressed);

            ethernet.UseDHCP();
            ethernet.NetworkUp += new GTM.Module.NetworkModule.NetworkEventHandler(ethernet_NetworkUp);
            ethernet.NetworkDown += new GTM.Module.NetworkModule.NetworkEventHandler(ethernet_NetworkDown);

            led.TurnRed();

            Debug.Print("Program Started");
        }

        void ethernet_NetworkDown(GTM.Module.NetworkModule sender, GTM.Module.NetworkModule.NetworkState state)
        {
            led.BlinkRepeatedly(GT.Color.Red);
        }

        void ethernet_NetworkUp(GTM.Module.NetworkModule sender, GTM.Module.NetworkModule.NetworkState state)
        {
            led.TurnGreen();
        }

        void timer_Tick(GT.Timer timer)
        {
            HttpWebRequest request =
                WebRequest.Create("http://integral-data.com/PollRemote/") as HttpWebRequest;

            HttpWebResponse response;
            request.Method = "GET";

            try
            {
                response = request.GetResponse() as HttpWebResponse;
                XmlTextReader reader = new XmlTextReader(response.GetResponseStream());

                if (reader.ReadToFollowing("CurrentState"))
                {
                    if (reader.ReadElementString() == "true")
                        ledControl.Write(true);
                    else
                        ledControl.Write(false);
                }

                response.Close();
            }
            catch (Exception ex)
            {
                string exception = ex.Message + " Inner:" + ex.InnerException;
            }

            request.Dispose();
        }

        void button_ButtonPressed(Button sender, Button.ButtonState state)
        {
            if (button.IsLedOn)
            {
                timer.Stop();
                button.ToggleLED();
            }
            else
            {
                timer.Start();
                button.ToggleLED();
            }
        }
    }
}

Advertisements

, ,

  1. #1 by Dan on March 24, 2012 - 9:32 AM

    Love it!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: