Adding XMPP support to the controller

The controller project is different from the previous projects in that it will be a client of the other three projects. It will still need to register itself (using the model name LearningIoT-Controller) with the Thing Registry and use provisioning where applicable so that the provisioning server can be used to connect all the devices together by recommending who should befriend whom.

Setting up a sensor client interface

Once we have the JID of the sensor, we can request for or subscribe to data from it using the XmppSensorClient class:

xmppSensorClient = new XmppSensorClient (xmppClient);

Subscribing to sensor data

We initialize our relationship with the sensor by subscribing to the Light and Motion field values from it using the following code. We also specify that we want information when the light has changed to one unit of a percent, or if the motion has changed from true to false (which corresponds to a numerical change of one).

private static void InitSensor (string Jid)
{
  xmppSensorClient.SubscribeData (-1, Jid, ReadoutType.MomentaryValues, null, new FieldCondition[]
  {
    FieldCondition.IfChanged ("Light", 1), FieldCondition.IfChanged ("Motion", 1)
  },
  null, null, new Duration (0, 0, 0, 0, 1, 0), true, string.Empty, string.Empty, string.Empty,NewSensorData, null);
}

The subscription call takes a sequence of parameters, as follows:

  • An optional sequence number (-1) that can be used to identify the subscription.
  • The Jid of the sensor.
  • The types of fields you want.
  • Any underlying nodes to read (null in our case since the sensor is not a concentrator).
  • A set of fields with optional conditions to subscribe to.
  • An optional maximum age (null) of the historical data that is subscribed to. Since we don't subscribe to historical data, we can leave this as null.
  • An optional minimum interval time (null), setting a limit on how fast messages can be sent to us.
  • An optional maximum interval time (1 minute), making sure we receive messages at least this frequently.
  • If an immediate request is desired (true), sensor data will be sent immediately as soon as the subscription has been accepted.
  • A triple of security tokens representing the service, device, and unit. This can be used for extended identification with the provisioning server or if the subscription is a result of an external request and the identity of the requester is forwarded. We leave these as empty strings since the subscription is made directly from the controller.
  • A callback method to call when the sensor data has been received as a result of the subscription.
  • A state object to pass on to the callback method.

    Tip

    If you only want data once, you can use the RequestData method instead of the SubscribeData method. It takes similar parameters.

Handling incoming sensor data

Sensor data will have been parsed correctly before being passed on to the callback method provided. It will be available in the event arguments, as shown in the next code:

private static void NewSensorData (object Sender,SensorDataEventArgs e)
{
  FieldNumeric Num;
  FieldBoolean Bool;

In theory, sensor data can be reported in a sequence of messages, depending on how the sensor reads and processes field values and also the amount of values reported. The callback method will be called once for every message. The Done property lets you know whether the message is the last message in a sequence. Field values in the most recent message will be available in the RecentFields property, while the sum of all the fields during the readout is available in the TotalFields property. In our case, it is sufficient to loop through the the fields reported in the most recent message:

if (e.HasRecentFields)
{
  foreach (Field Field in e.RecentFields)
  {

Checking incoming fields is straightforward:

if (Field.FieldName == "Light" && 
  (Num = Field as FieldNumeric) != null && 
  Num.Unit == "%" && Num.Value >= 0 && Num.Value <= 100)
    lightPercent = Num.Value;
else if (Field.FieldName == "Motion" &&
  (Bool = Field as FieldBoolean) != null)
    motion = Bool.Value;

We end by checking the control rules as follows to see whether the state of the system has changed:

    }
    hasValues = true;
    CheckControlRules ();
  }
}

Setting up a controller client interface

Communicating with the actuator is simply done using the XmppControlClient class with the help of a controller interface:

xmppControlClient = new XmppControlClient (xmppClient);

Controlling parameters can be done in two ways: either through a control form or individual parameter set operations. The control form will contain all the controllable parameters and can also be used to see whether a parameter exists, what type it has, and what its boundary values are. It can also be used to set a group of parameters at once.

During the initialization of our actuator interface, we request the control form as follows:

private static void InitActuator (string Jid)
{
  xmppControlClient.GetForm (Jid, ControlFormResponse, Jid);
}

We handle the response in the following manner. If the Form property is null in the event arguments, an error will be reported:

private static void ControlFormResponse (object Sender, ControlFormEventArgs e)
{
  string Jid = (string)e.State;
  if (e.Form != null)
  {
    ...
  }
  else
  Log.Error (e.ErrorMessage, EventLevel.Major, Jid);
}

We will use the form mainly to know what parameters are available in the actuator and use individual set operations to control them. Both individual set operations and a group parameter set operation, which use a control form, are done using the overloaded versions of the Set method in the XmppControlClient class. The version that is used depends of the data type of the value parameter passed to the method. Setting up our digital output in the form of our integer control parameter is done as follows:

if (i >= 0 && xmppControlForm.ContainsField ("Digital Outputs"))
  xmppControlClient.Set (Jid, "Digital Outputs", i);

Setting up our Boolean alarm State parameter is done in the following manner:

if (b.HasValue && xmppControlForm.ContainsField ("State"))
  xmppControlClient.Set (Jid, "State", b.Value);

Setting up a camera client interface

To emulate the UPnP event subscription model in XMPP, we converted our camera to a sensor. For our purposes, we need to subscribe to the camera image URL field property, together with its corresponding Width and Height field properties, when we initialize our camera interface. We do this in the same way as for the sensor, except that here we use another sequence number (-2) to keep the two apart, as mentioned in the next code:

private static void InitCamera (string Jid)
{
  xmppSensorClient.SubscribeData (-2, Jid, ReadoutType.Identity, null,new FieldCondition[]
  {
    FieldCondition.Report ("URL"), FieldCondition.IfChanged ("Width", 1), FieldCondition.IfChanged ("Height", 1)
  }, null, null, new Duration (0, 0, 0, 0, 1, 0), true, string.Empty, string.Empty, string.Empty, NewCameraData, null);
}

Parsing this data is also done in the same way as we did in the sensor project.

Fetching the camera image over XMPP

The URL provided by the camera will differ from the URL provided over normal UPnP in that it will use the httpx URI scheme. In our case, the URL to the camera image will be something like httpx://[email protected]/camera. In order to be able to use the httpx URI scheme, we have to tell the framework which XMPP client to use. This is done by registering it with the HttpxUriScheme class, which is defined in the Clayster.Library.Internet.URIs namespace, as follows:

HttpxUriScheme.Register (xmppClient);

Once the XMPP client has been registered, the system will treat the httpx URI scheme as it would treat any other registered URI scheme, such as the http and https URI schemes. We get the image by calling the static HttpSocketClient.GetResource method with the URL, and it will figure out what to do. We embed the content of the response, as we did with the images that were fetched using UPnP:

Response = HttpSocketClient.GetResource(Url);
Msg.EmbedObject ("cam1img" + j.ToString (), Response.Header.ContentType, Response.Data);

Identifying peer capabilities

When things connect to the controller, they need to figure out what they can do, or what interoperability contracts they can publish so we can know what they are. As there is an interoperability server class, there is also an interoperability client class, as shown in the following code, that we can use for this purpose:

xmppInteroperabilityClient = new XmppInteroperabilityClient (
  xmppClient);

We will create a method that will be used to figure out what is behind a specific Jid by requesting its interoperability interfaces or contracts, as follows:

private static void CheckInterfaces (string Jid)
{
  xmppInteroperabilityClient.RequestInterfaces (Jid,
    (Interfaces, State) =>
    {
      ...
      xmppSettings.UpdateIfModified ();
    }, Jid);
}

The ellipsis ("…") in the preceding code corresponds to the different checks we do on the list of interfaces reported by the device. If we are not already connected to a sensor and a new thing with the corresponding light and motion interfaces are available, we remember the Jid (which is available in the State parameter as shown in the next code) for use as the sensor in our application:

if (string.IsNullOrEmpty (xmppSettings.Sensor) && Array.IndexOf<string> (Interfaces, "Clayster.LearningIoT.Sensor.Light") >= 0 && Array.IndexOf<string> (Interfaces, "Clayster.LearningIoT.Sensor.Motion") >= 0)
{
  xmppSettings.Sensor = (string)State;
  InitSensor (xmppSettings.Sensor);
}

In the same way, the actuator and camera are identified in a similar manner.

Reacting to peer presence

Now that we have a method to identify what peers are, we need to trigger the method somehow. To be able to communicate with a peer, we will need the full JID, not just the bare JID, which we have when negotiating friendship relationships. The full JID requires the reception of a presence message from the device, showing it is online. To avoid triggering the method every time a device goes online, we first keep an internal list of peers that have been newly accepted as friends. This can be done with the following code:

Dictionary<string,bool> NewlyAdded = new Dictionary<string, bool> ();

In the OnPresenceReceived event handler, when a Subscribed presence stanza has been received, confirming a new friendship, we store away the bare JID in the list, as follows:

case PresenceType.Subscribed:
  lock (NewlyAdded)
  {
    NewlyAdded [XmppClient.StripResource (
    Presence.From).ToLower ()] = true;
  }
  break;

As shown in the next code snippet, we also add a default clause to catch presence stanzas of types other than Subscribe, Subscribed, Unsubscribe, and Unsubscribed. If not offline, we will consider that the peer is about to go online:

default:
  string s = XmppClient.StripResource 
    (Presence.From).ToLower ();
  if (Presence.Status != PresenceStatus.Offline)
  {

First we need to check whether the device corresponds to a device the controller already uses. If this is the case, we need to reinitialize our subscriptions and get a new control form from the actuator since these might have been changed while offline. This can be done with the following code:

if (!string.IsNullOrEmpty (xmppSettings.Sensor) &&
  XmppClient.CompareJid (xmppSettings.Sensor, Presence.From))
    InitSensor (Presence.From);
else if (!string.IsNullOrEmpty (xmppSettings.Actuator) &&
  XmppClient.CompareJid (xmppSettings.Actuator, Presence.From))
    InitActuator (Presence.From);
else if (!string.IsNullOrEmpty (xmppSettings.Camera) &&
  XmppClient.CompareJid (xmppSettings.Camera, Presence.From))
    InitCamera (Presence.From);

If not an already known device, we check whether we need to have a look at the capabilities at all with the following code. If the controller has already identified the devices it uses, it doesn't need to analyze new friends:

  else if (string.IsNullOrEmpty (xmppSettings.Sensor) ||
    string.IsNullOrEmpty (xmppSettings.Actuator) ||
    string.IsNullOrEmpty (xmppSettings.Camera))
  {
    lock (NewlyAdded)
    {
      if (!NewlyAdded.ContainsKey (s))
        break;
      NewlyAdded.Remove (s);
    }
    CheckInterfaces (Presence.From);
  }
}
break;

Detecting rule changes

If the owner of a thing involved in a network changes the rules, it will ask the corresponding devices involved in the rule to clear their provisioning caches. This cache locally stores responses to previous provisioning questions, which might now be incorrect. The clearing of the cache is handled by our provisioning server class. But we can add an event handler to the OnClearCache event, which is raised whenever the cache is cleared, to reinitialize our connections, including event subscriptions and control forms. This event makes sure that the new rules that apply are taken into account. The following code is used in order to reinitialize our connections:

xmppProvisioningServer.OnClearCache += (Sender, e) =>
{
  if (!string.IsNullOrEmpty (xmppSettings.Sensor))
    InitSensor (xmppSettings.Sensor);
  if (!string.IsNullOrEmpty (xmppSettings.Actuator))
    InitActuator (xmppSettings.Actuator);
  if (!string.IsNullOrEmpty (xmppSettings.Camera))
    InitCamera (xmppSettings.Camera);
};
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset