XMPP is already implemented and supported through the Clayster.Metering.Xmpp
module that we mentioned earlier. This module models each entity in XMPP as a separate node in the Topology
data source. Connections with provisioning servers and thing registries are handled automatically through separate nodes dedicated to this task. Friendships are handled through simple child creation and removal operations. It can be done automatically through requests made by others or recommendations from the provisioning server, or manually by adding friends in the CMT. All we need to do is provide specialized classes that override base class functionality, and add specific features that are needed.
In our project, we will create a class for managing our sensor. We will derive it from the XmppSensor
class defined in Clayster.Metering.Xmpp
and provide the required default constructor through the following code:
public class Sensor : XmppSensor { public Sensor() { }
Each class managed by Clayster.Library.Abstract
, such as those used by the Topology
data source, must define a TagName
and a Namespace
property. These are used during import and export to identify the class in question as follows:
public override string TagName { get { return "IoTSensor"; } } public override string Namespace { get { return "http://www.clayster.com/learningiot/"; } }
We must also provide a human readable name to the class. Whenever objects of this class are displayed, for instance in the CMT, it is this human readable name that will be displayed, as shown in the following code:
public override string GetDisplayableTypeName (Language UserLanguage) { return "Learning IoT - Sensor"; }
When the system finds a new device, it needs to know which class best represents that device. This is done by forcing each XMPP device class to implement a Supports
method that returns to which degree the class handles said device, based on features and interoperability interfaces reported by the device. The class with the highest support grade is then picked to handle the newly found device.
By using the following code, we will override this method to provide a perfect match when our sensor is found:
public override SupportGrade Supports ( XmppDeviceInformation DeviceInformation) { if (Array.IndexOf<string> ( DeviceInformation.InteroperabilityInterfaces, "Clayster.LearningIoT.Sensor.Light") >= 0 && Array.IndexOf<string> ( DeviceInformation.InteroperabilityInterfaces, "Clayster.LearningIoT.Sensor.Motion") >= 0) { return SupportGrade.Perfect; } else return SupportGrade.NotAtAll; }
Manual readout of the sensor is already supported by the XmppSensor
class. This means you can already read data from the sensor from the CMT, for instance, as it is. However, this is not sufficient for our application. We want to subscribe to the data from the sensor. This subscription is application-specific, and therefore must be done by us in our application. We will send a new subscription every time the sensor reports an online or chat presence. The XmppSensor
class will then make sure that the subscription is sent again if the data is not received accordingly. The subscription call is similar to the one we did in the previous chapter. The subscription call is sent using the following code:
protected override void OnPresenceChanged (XmppPresence Presence) { if (Presence.Status == PresenceStatus.Online || Presence.Status == PresenceStatus.Chat) { this.SubscribeData (-1, ReadoutType.MomentaryValues, new FieldCondition[] { FieldCondition.IfChanged ("Temperature", 0.5), FieldCondition.IfChanged ("Light", 1), FieldCondition.IfChanged ("Motion", 1) }, null, null, new Duration (0, 0, 0, 0, 1, 0), true, this.NewSensorData, null); } }
Interpreting incoming sensor data is done using the Clayster platform in a way that is similar to what we did using the Clayster.Library.IoT
library in the previous chapters. We will start by looping through incoming fields:
private void NewSensorData (object Sender, SensorDataEventArgs e) { FieldNumeric Num; FieldBoolean Bool; double? LightPercent = null; bool? Motion = null; if(e.HasRecentFields) { foreach(Field Field in e.RecentFields) { switch(Field.FieldName) {
There is one added advantage of handling field values when we run them on the Clayster platform: we can do unit conversions very easily. We will illustrate this with the help of an example, where we handle the incoming field value - temperature. First, we will try to convert it to Celsius. If successful, we will report it to our controller application (that will soon be created):
case "Temperature": if ((Num = Field as FieldNumeric) != null) { Num = Num.Convert ("C"); if (Num.Unit == "C") Controller.SetTemperature (Num.Value); } break;
We will handle the Light
and Motion
values in a similar way. Finally, after all the fields have been processed, we will call the Controller application and ask it to check its control rules:
if (LightPercent.HasValue && Motion.HasValue) Controller.CheckControlRules ( LightPercent.Value, Motion.Value); } }
If implementing support for our Sensor
class was simple, implementing a class for our actuator is even simpler. Most of the actuator is already configured by the XmppActuator
class. So, we will first create an Actuator
class that is derived from this XmppActuator
class. We will provide it with a TagName
that will return "IoTActuator
" and the same namespace that the Sensor
class returns. We will use Learning IoT – Actuator
as a displayable type name. We will also override the Supports
method to return a perfect response when the corresponding interoperability interfaces are found.
Our Actuator
class is basically complete. The XmppActuator
class already has support for reading out the control form and publishing the available control parameters. This can be tested in the CMT, for instance, where the administrator configures control parameters accordingly.
To make control of the actuator a bit simpler, we will add customized control methods to our class. We already know that the parameters exist (or should exist) since the corresponding interoperability interfaces (contracts) are supported.
We will begin by adding a method to update the LEDs on the actuator:
public void UpdateLeds(int LedMask) { this.RequestConfiguration ((NodeConfigurationMethod)null, "R_Digital Outputs", LedMask, this.Id); }
The RequestConfiguration
method is called to perform a configuration. This method is defined by Clayster.Library.Meters
namespace, and can be called for all configurable nodes in the system. Configuration is then performed from a context that is defined by the node. The XmppActuator
class translates this configuration into the corresponding set operation, based on the data type of the parameter value.
The first parameter contains an optional callback method that is called after the parameter has been successfully (or unsuccessfully) configured. We don't require a callback, so we will only send a null parameter value. The second parameter contains the name of the parameter that needs to be configured. Local configurable parameters of the XmppActuator
class differ from its remote configurable parameters, which are prefixed by R_
. The third parameter value is the value that needs to be configured. The type of value to send here depends on the parameter used. The fourth and last parameter is a subject string that will be used when the corresponding configuration event is logged in the event log.
In a similar fashion, we will add a method for controlling the alarm state of the actuator and then our Actuator
class will be complete.
18.117.99.71