July 2012
Volume 27 Number 07
Internet of Things - A Smart Thermostat on the Service Bus
By Clemens Vasters | July 2012
Here’s a bold prediction: Connected devices are going to be big business, and understanding these devices will be really important for developers not too far down the road. “Obviously,” you say. But I don’t mean the devices on which you might read this article. I mean the ones that will keep you cool this summer, that help you wash your clothes and dishes, that brew your morning coffee or put together other devices on a factory floor.
In the June issue of MSDN Magazine (msdn.microsoft.com/magazine/jj133825), I explained a set of considerations and outlined an architecture for how to manage event and command flows from and to embedded (and mobile) devices using Azure Service Bus. In this article, I’ll take things a step further and look at code that creates and secures those event and command flows. And because a real understanding of embedded devices does require looking at one, I’ll build one and then wire it up to Azure Service Bus so it can send events related to its current state and be remotely controlled by messages via the Azure cloud.
Until just a few years ago, building a small device with a power supply, a microcontroller, and a set of sensors required quite a bit of skill in electronics hardware design as well as in putting it all together, not to mention good command of the soldering iron. I’ll happily admit that I’ve personally been fairly challenged in the hardware department—so much so that a friend of mine once declared if the world were attacked by alien robots he’d send me to the frontline and my mere presence would cause the assault to collapse in a grand firework of electrical shorts. But due to the rise of prototyping platforms such as Arduino/Netduino or .NET Gadgeteer, even folks who might do harm to man and machine swinging a soldering iron can now put together a fully functional small device, leveraging existing programming skills.
To stick with the scenario established in the last issue, I’ll build an “air conditioner” in the form of a thermostat-controlled fan, where the fan is the least interesting part from a wiring perspective. The components for the project are based on the .NET Gadgeteer model, involving a mainboard with a microcontroller, memory and a variety of pluggable modules. The mainboard for the project is a GHI Electronics FEZ Spider board with the following extension modules:
- From GHI Electronics
- Ethernet J11D Module to provide wired networking (a Wi-Fi module exists)
- USB Client DP Module as power supply and USB port for deployment
- Joystick for direct control of the device
- From Seeed Studio
- Temperature and humidity sensor
- Relays to switch the fan on or off
- OLED display to show the current status
Together, these parts cost around $230. That’s obviously more than soldering equivalent components onto a board, but did I mention that would require soldering? Also, this is a market that’s just starting to get going, so expect prices to go down as the base broadens.
To make the components come alive you’ll need Visual C# 2010 Express (at least), the .NET Micro Framework SDK, and the Gadgeteer SDK from GHI Electronics or Seeed. Once you have these installed, the development experience is—if you’ll allow the superlative—fairly spectacular and as visual as things can conceivably get in Visual Studio, as you can see in Figure 1.
Figure 1 Designing the Device in the .NET Gadgeteer
Figure 1 shows the design view of the .NET Gadgeteer program in Visual Studio. I thought of including a photo of the actual device with this article, but all the photo would do is confirm the diagram. This is exactly how it looks.
The file with the .gadgeteer extension contains an XML model that’s visualized in the editor. From that XML file, the Gadgeteer tooling auto-generates a partial Program class with wrappers for each of the modules plugged into the mainboard. Your code sits in Program.cs holding another part of the Program class, just like the codebehind model you’re familiar with from other .NET APIs.
You use the .NET Micro Framework with these devices. It’s an entirely open source version of the Microsoft .NET Framework that has been specifically created for small devices with limited compute power and not much memory. The .NET Micro Framework contains many of the familiar .NET Framework classes, but most have gone through a feature diet to reduce the overall code footprint. Because the Framework is a layer over the native hardware of the device and the device isn’t a general-purpose computer with an OS that handles all hardware abstraction (there’s really no OS here), the framework version you can use with a device depends on the board manufacturer supporting the prerequisites, which is obviously very different from the experience with a regular PC, where particularities of hardware are far removed from things as high-level as the .NET Framework.
There are several other differences compared with the regular .NET Framework, and the PC platform in general, that are—coming from a PC background—initially surprising. For example, the device here doesn’t have an onboard battery. No battery means no buffered clock, so the device has no idea of the correct wall-clock time when it wakes up. Lacking an OS, and with a third-party extension display, the device also doesn’t have onboard fonts you can use to draw strings for display. If you want to display a string, you’ll have to add a font to do so.
Likewise, the device doesn’t have a prepopulated, Windows Update-maintained certificate store. If you want to validate SSL/TLS certificates, you’ll have to deploy at least the root CA certificates into the device—and of course you’ll also have to have the current time to check the certificate’s validity. As you’ve might already guessed, the handling of certificates represents a bit of a hurdle for these devices, and the cryptography requirements for SSL/TLS are so significant in terms of computation effort, memory consumption and code footprint that not all devices can support them. However, because security is clearly becoming increasingly important, even in this space as devices need to communicate across the Internet, version 4.2 of the .NET Micro Framework brings significant improvements for SSL/TLS support for devices having sufficient resources to handle it. I’ll discuss this subject in more depth a bit later.
Thermostat Functionality
Implementing local thermostat functionality for this sample is pretty straightforward. I’ll check temperature and humidity on a schedule using the sensor and switch the fan connected via one of the relay ports off or on when the temperature drops below or rises above a certain threshold. The current status is displayed on the OLED screen and the joystick allows adjusting the target temperature manually.
As I start the device, I’ll wire up events to a timer to trigger the temperature readings and to read events from the joystick. When the joystick is pressed, I’ll suspend the timer, check the target temperature according to the joystick position, immediately request a new temperature reading from the sensor and resume the timer. When a temperature reading finishes, the TemperatureHumidityMeasurementComplete event gets raised by the sensor. I’ll then store the current readings and adjust the state of the relay to switch the fan if necessary. That’s the extent of the thermostat logic, which is shown in part in Figure 2.
Figure 2 Reading Temperature and Humidity
void WireEvents()
{
this.InitializeTemperatureSensor();
this.InitializeJoystick();
}
void InitializeTemperatureSensor()
{
this.temperatureCheckTimer = new Timer(5000);
this.temperatureCheckTimer.Tick += (t) =>
this.temperatureHumidity.RequestMeasurement();
this.temperatureCheckTimer.Start();
this.temperatureHumidity.MeasurementComplete
+= this.TemperatureHumidityMeasurementComplete;
}
void InitializeJoystick()
{
this.joystick.JoystickPressed += this.JoystickPressed;
}
void JoystickPressed(Joystick sender, Joystick.JoystickState state)
{
this.temperatureCheckTimer.Stop();
var jStick = this.joystick.GetJoystickPostion();
if (jStick.Y < .3 || jStick.X < .3)
{
settings.TargetTemperature -= .5;
StoreSettings(settings);
}
else if (jStick.Y > .7 || jStick.X > .7)
{
settings.TargetTemperature += .5;
StoreSettings(settings);
}
this.RedrawDisplay();
this.temperatureHumidity.RequestMeasurement();
this.temperatureCheckTimer.Start();
}
void TemperatureHumidityMeasurementComplete(TemperatureHumidity sender,
double temperature, double relativeHumidity)
{
var targetTemp = settings.TargetTemperature;
this.lastTemperatureReading = temperature;
this.lastHumidityReading = relativeHumidity;
this.relays.Relay1 = (lastTemperatureReading > targetTemp);
this.RedrawDisplay();
}
Whenever I adjust the target temperature in the JoystickPressed method, I store the new value in the Program class’ settings field and call StoreSettings. The settings field is of type ApplicationSettings, a serializable class in the device code that holds everything the device needs to remember across resets and power cycles. To persistently store the data, the .NET Micro Framework reserves some storage pages in the device’s non-volatile memory and provides access to this storage via the ExtendedWeakReference class. That’s probably not intuitive until you recognize that it’s primarily a mechanism to swap data out of main memory under pressure, and it conveniently doubles as a storage feature. The class holds weak references to objects, just as the regular WeakReference does in the .NET Framework, but will swap the data to non-volatile storage instead of discarding it once the garbage collector comes around. Because the data gets swapped out of main memory, it needs to be serialized for storage, which explains why the ApplicationSettings class (which you’ll see being used later when we discuss provisioning) needs to be serializable.
Recovering an object from its storage location or creating a new storage slot with the RecoverOrCreate method requires specifying a unique identifier. I only have one object to store, so I’ll use a fixed identifier (zero). After the object has been stored and recovered once, any updates need to be forced back into storage using the PushBackIntoRecoveryList method on the ExtendedWeakReference instance, so that’s what I do in StoreSettings to flush changes out, as shown in Figure 3.
Figure 3 Updating Stored Data
static ApplicationSettings GetSettings()
{
var data = ExtendedWeakReference.RecoverOrCreate(
typeof(ApplicationSettings),
0,
ExtendedWeakReference.c_SurviveBoot |
ExtendedWeakReference.c_SurvivePowerdown);
var settings = data.Target as ApplicationSettings;
if (settings == null)
{
data.Target = settings = ApplicationSettings.Defaults;
}
return settings;
}
static void StoreSettings(ApplicationSettings settings)
{
var data = ExtendedWeakReference.RecoverOrCreate(
typeof(ApplicationSettings),
0,
ExtendedWeakReference.c_SurviveBoot |
ExtendedWeakReference.c_SurvivePowerdown);
data.Target = settings;
data.PushBackIntoRecoverList();
}
Provisioning
In the beginning, the device is in “factory new” state—the device code has been deployed but the device hasn’t yet been initialized and therefore doesn’t have any current settings. You can see this state reflected in the GetSettings method when the settings object is still null and is therefore initialized with default settings.
Because I want to let the device communicate with and through an Internet infrastructure—Azure Service Bus—I need to equip the device with a set of credentials to talk to that infrastructure and also tell it which resources to talk to. That first step of setting up a factory new device with the required network configuration and setting up the matching resources on the server side is known as provisioning; I discussed the basic architectural model for it in the previous article.
In the device code I’ll be fairly strict about getting the device provisioned properly and will initiate the provisioning steps every time the device connects to the network and doesn’t have a valid configuration. For that, I keep a Boolean flag in the settings to tell me whether I’ve had previous success. If the flag isn’t set, I issue the call to the provisioning service hosted in Azure.
The provisioning service is responsible for verifying the identity of the device using its unique device identifier, which was registered on an allow-list maintained by the service when it was produced. Once the device is activated, it gets removed from the allow-list. To keep things reasonably simple for this article, however, I’ll skip the implementation of the allow-list management.
Once the device is considered legitimate, the provisioning service, following the model established in the previous article, allocates the device to a particular scale-unit and to a particular fan-out Topic inside of that scale-unit. For this example, I’m going to keep it simple and create a subscription for a single fixed Topic named devices that serves as the command channel from the cloud into the device, and for a Topic named events to collect information from the devices. In addition to creating the subscription and associating the device with the Topic, I’ll also create a service-identity for the device in the Access Control Service (a feature of Azure Active Directory) and grant that identity the necessary rights to send messages into the events Topic and to receive messages from the newly created subscription to the devices Topic. The device can perform exactly those two operations on Azure Service Bus—nothing more.
Figure 4 shows the core of the provisioning service. The service depends on the Azure Service Bus management API (the NamespaceManager) found in the core Microsoft.ServiceBus.dll assembly that ships as part of the Azure SDK or via NuGet. It also relies on a helper library for managing access control accounts and permissions available as part of the Authorization sample for Service Bus and, of course, also included in the downloadable code for this article.
Figure 4 The Provisioning Service
namespace BackendWebRole
{
using System;
using System.Configuration;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;
using Microsoft.ServiceBus;
using Microsoft.ServiceBus.AccessControlExtensions;
using Microsoft.ServiceBus.Messaging;
[ServiceContract(Namespace = "")]
public class ProvisioningService
{
const string DevicesTopicPath = "devices";
const string EventsTopicPath = "events";
static readonly AccessControlSettings AccessControlSettings;
static readonly string ManagementKey;
static readonly string NamespaceName;
static Random rnd = new Random();
static ProvisioningService()
{
NamespaceName = ConfigurationManager.AppSettings["serviceBusNamespace"];
ManagementKey = ConfigurationManager.AppSettings["managementKey"];
AccessControlSettings = new AccessControlSettings(
NamespaceName, ManagementKey);
}
[OperationContract, WebInvoke(Method = "POST", UriTemplate = "/setup")]
public void SetupDevice()
{
var rcx = WebOperationContext.Current.OutgoingResponse;
var qcx = WebOperationContext.Current.IncomingRequest;
var id = qcx.Headers["P-DeviceId"];
if (this.CheckAllowList(id))
{
try
{
var deviceConfig = new DeviceConfig();
CreateServiceIdentity(ref deviceConfig);
CreateAndSecureEntities(ref deviceConfig);
rcx.Headers["P-DeviceAccount"] = deviceConfig.DeviceAccount;
rcx.Headers["P-DeviceKey"] = deviceConfig.DeviceKey;
rcx.Headers["P-DeviceSubscriptionUri"] =
deviceConfig.DeviceSubscriptionUri;
rcx.Headers["P-EventSubmissionUri"] = deviceConfig.EventSubmissionUri;
rcx.StatusCode = HttpStatusCode.OK;
rcx.SuppressEntityBody = true;
}
catch (Exception)
{
rcx.StatusCode = HttpStatusCode.InternalServerError;
rcx.SuppressEntityBody = true;
}
}
else
{
rcx.StatusCode = HttpStatusCode.Forbidden;
rcx.SuppressEntityBody = true;
}
}
static void CreateAndSecureEntities(ref DeviceConfig deviceConfig)
{
var namespaceUri = ServiceBusEnvironment.CreateServiceUri(
Uri.UriSchemeHttps, NamespaceName, string.Empty);
var nsMgr = new NamespaceManager(namespaceUri,
TokenProvider.CreateSharedSecretTokenProvider("owner", ManagementKey));
var ruleDescription = new SqlFilter(
string.Format("DeviceId='{0}' OR Broadcast=true",
deviceConfig.DeviceAccount));
var subscription = nsMgr.CreateSubscription(
DevicesTopicPath, deviceConfig.DeviceAccount, ruleDescription);
deviceConfig.EventSubmissionUri = new Uri(
namespaceUri, EventsTopicPath).AbsoluteUri;
deviceConfig.DeviceSubscriptionUri =
new Uri(namespaceUri,
SubscriptionClient.FormatSubscriptionPath(
subscription.TopicPath,
subscription.Name)).AbsoluteUri;
GrantSendOnEventTopic(deviceConfig);
GrantListenOnDeviceSubscription(deviceConfig);
}
static void GrantSendOnEventTopic(DeviceConfig deviceConfig)
{
var settings = new AccessControlSettings(NamespaceName, ManagementKey);
var topicUri = ServiceBusEnvironment.CreateServiceUri(
Uri.UriSchemeHttp, NamespaceName, EventsTopicPath);
var list = NamespaceAccessControl.GetAccessControlList(topicUri, settings);
var identityReference =
IdentityReference.CreateServiceIdentityReference(
deviceConfig.DeviceAccount);
var existing = list.FirstOrDefault((r) =>
r.Condition.Equals(identityReference) &&
r.Right.Equals(ServiceBusRight.Send));
if (existing == null)
{
list.AddRule(identityReference, ServiceBusRight.Send);
list.SaveChanges();
}
}
static void GrantListenOnDeviceSubscription(DeviceConfig deviceConfig)
{
var settings = new AccessControlSettings(NamespaceName, ManagementKey);
var subscriptionUri = ServiceBusEnvironment.CreateServiceUri(
Uri.UriSchemeHttp,
NamespaceName,
SubscriptionClient.FormatSubscriptionPath(
DevicesTopicPath, deviceConfig.DeviceAccount));
var list = NamespaceAccessControl.GetAccessControlList(
subscriptionUri, settings);
var identityReference = IdentityReference.CreateServiceIdentityReference(
deviceConfig.DeviceAccount);
var existing = list.FirstOrDefault((r) =>
r.Condition.Equals(identityReference) &&
r.Right.Equals(ServiceBusRight.Listen));
if (existing == null)
{
list.AddRule(identityReference, ServiceBusRight.Listen);
list.SaveChanges();
}
}
static void CreateServiceIdentity(ref DeviceConfig deviceConfig)
{
var name = Guid.NewGuid().ToString("N");
var identity =
AccessControlServiceIdentity.Create(AccessControlSettings, name);
identity.Save();
deviceConfig.DeviceAccount = identity.Name;
deviceConfig.DeviceKey = identity.GetKeyAsBase64();
}
bool CheckAllowList(string id)
{
return true;
}
}
}
The service consists of a single HTTP resource, named /setup, implemented using the Windows Communication Foundation (WCF) Web operation SetupDevice, which accepts POST requests. You’ll notice that the method is parameterless and also doesn’t return an entity payload. That’s no accident. Instead of using an HTTP entity-body in XML, JSON or form-encoding to carry request and response information, I’m making it very simple for the device and putting the payloads in custom HTTP headers. This eliminates the need for a specific parser to analyze the payload, and it keeps the code footprint small. The HTTP client already knows how to parse headers, and for what I want to do here, that’s plenty.
The matching device code calling the HTTP resource is shown in Figure 5, and it’s difficult to imagine making that call any simpler than this. The device-identifier is sent in a header and the post-provisioning configuration settings are likewise returned via headers. No juggling of streams, no parsing, just simple key/value pairs the HTTP client readily understands.
Figure 5 Configuring the Device
bool PerformProvisioning()
{
[ ... display status ... ]
try
{
var wr = WebRequest.Create(
"https://cvdevices.cloudapp.net/Provisioning.svc/setup");
wr.Method = "POST";
wr.ContentLength = 0;
wr.Headers.Add("P-DeviceId", this.deviceId);
using (var wq = (HttpWebResponse)wr.GetResponse())
{
if (wq.StatusCode == HttpStatusCode.OK)
{
settings.DeviceAccount = wq.Headers["P-DeviceAccount"];
settings.DeviceKey = wq.Headers["P-DeviceKey"];
settings.DeviceSubscriptionUri = new Uri(
wq.Headers["P-DeviceSubscriptionUri"]);
settings.EventSubmissionUri = new Uri(
wq.Headers["P-EventSubmissionUri"]);
settings.NetworkProvisioningCompleted = true;
StoreSettings(settings);
return true;
}
}
}
catch (Exception e)
{
return false;
}
return false;
}
void NetworkAvailable(Module.NetworkModule sender,
Module.NetworkModule.NetworkState state)
{
ConvertBase64.ToBase64String(ethernet.NetworkSettings.PhysicalAddress);
if (state == Module.NetworkModule.NetworkState.Up)
{
try
{
Utility.SetLocalTime(NtpClient.GetNetworkTime());
}
catch
{
// Swallow any timer exceptions
}
if (!settings.NetworkProvisioningCompleted)
{
if (!this.PerformProvisioning())
{
return;
}
}
if (settings.NetworkProvisioningCompleted)
{
this.tokenProvider = new TokenProvider(
settings.DeviceAccount, settings.DeviceKey);
this.messagingClient = new MessagingClient(
settings.EventSubmissionUri, tokenProvider);
}
}
}
If the settings indicate that provisioning is necessary, the PerformProvisioning method is invoked from the NetworkAvailable function, which gets triggered when the network is up and the device is assigned an IP address via DHCP. Once provisioning is complete, the settings are used to configure the token provider and messaging client to talk to Azure Service Bus. You’ll also notice an NTP client call. NTP stands for “network time protocol” and I’ve borrowed a simple, BSD-licensed NTP client written by Michael Schwarz to enable the sample to get the current time, which you need if you want to check SSL certificate expiration.
As you can see back in Figure 4, SetupDevices calls the mock-check on the allow-list CheckAllowList and, if that’s successful, then calls CreateServiceIdentity and CreateAndSecureEntities. The CreateServiceIdentity method creates a new service identity along with a secret key in the Access Control namespace associated with the Azure Service Bus namespace configured for the application. The CreateAndSecureEntities method creates a new subscription for the entity on the devices Topic, configuring the subscription with a SQL rule that allows sending messages into the Topic to target either the specific subscription by including a DeviceId property set to the device-account name, or all subscriptions by including a Broadcast property with a Boolean value of true. After the subscription has been created, the method calls the GrantSendOnEventTopic and GrantListenOnDeviceSubscription methods that grant the required permissions on the entities to the new service identity using the Access Control library.
Once all that has been successfully completed, the results of the provisioning operations are mapped to headers in the HTTP response for the request and returned with an OK status code, and the device stores the results in non-volatile memory and sets the flag for NetworkProvisioningCompleted.
Sending Events and Receiving Commands
With provisioning completed, the device is now ready to send events to the Azure Service Bus events Topic and to receive commands from its subscription to the devices Topic. But before I go there, I have to discuss a sensitive issue: security.
As I mentioned earlier, SSL/TLS is an expensive protocol suite for small devices. That is to say, some devices won’t ever be able to support SSL/TLS, or they might support it only in a limited fashion because of compute capacity or memory constraints. As a matter of fact, though at the time of this writing the GHI Electronics FEZ Spider mainboard based on the .NET Micro Framework 4.1 I’m using here can nominally speak SSL/TLS and therefore HTTPS, its SSL/TLS firmware apparently can’t deal with the certificate chain presented to it by Azure Service Bus or the Access Control service. As the firmware for these devices gets updated to the new 4.2 version of the .NET Micro Framework, these limitations will go away for this particular device, but the issue that some devices are simply too constrained to deal with SSL/TLS remains true in principle, and there’s active discussion in the embedded device community on appropriate protocol choices that aren’t quite as heavyweight.
Thus, even though the device now has a proper account, it can’t get a token from the Access Control service because using HTTPS is a prerequisite for doing so. The same is true for sending a message into Azure Service Bus, which mandates HTTPS for all requests that require passing an access token, including all interactions with Queues and Topics. Moreover, if this sample were production code, I would, of course, have to expose the provisioning endpoint via HTTPS to protect the secret key as it’s returned to the device.
What now? Well, “what now” is ultimately about making the right trade-offs, and this definitely includes money—in manufacturing, price differences of a few cents add up when millions of a particular kind of device are being made. If the device isn’t capable of handling a required security protocol, the questions are how much harm could be caused by not having that protocol and how to close the gap between the device’s lack of required features and the infrastructure’s demands.
What should be clear is that any data stream that isn’t encrypted and signed is susceptible to eavesdropping and manipulation. When a device reports only sensor data, it’s worth considering whether a man-in-the-middle manipulation on such a network path is conceivably valuable for anyone or could be analytically detected. The result might sometimes be that sending the data in the clear is OK. Command and control paths are a different matter; as soon as the behavior of a device can be remotely controlled over a network, I can’t think of a case where I wouldn’t want to have that communication path protected at least with a signature for integrity. The value of privacy for command and control depends on the use case. If there’s mitigation against manipulation in place, setting the thermostat target temperature doesn’t seem to be worthy of lot of encryption effort.
The sample code that goes with this article includes two variations of the communication flow from the device to the cloud. The first is a much-simplified Azure Service Bus API that requires HTTPS and does the regular handshake of acquiring an Access Control token and talking to Azure Service Bus directly.
The second path uses the same general shape of Azure Service Bus HTTP protocol to send and receive messages, but it creates an HMACSHA256 signature over the message using the secret key it holds. This won’t protect the message from eavesdropping, but it does protect the message from manipulation and allows detecting replay attacks when including a unique message-id. The replies will be signed with the same key. Because Service Bus doesn’t yet support this authentication model—even though this article is a good indicator that Microsoft is actively thinking about this problem—the path uses a custom gateway service hosted alongside the provisioning service. The custom gateway checks and strips the signature and passes the remaining message on to Azure Service Bus. Using a custom gateway for protocol translation is also generally the right model if you need the cloud system to speak one of the myriad proprietary device protocols. But one thing to keep in mind about the custom gateway approach is that it needs to scale up to the number of devices that concurrently send messages, so making the gateway layer very thin and stateless is a good idea.
The distinction between the two paths is ultimately made by the provisioning service that either dispenses HTTP or HTTPS URIs. In the device code, the difference is handled by the TokenProvider. In the HTTPS case, the devices talk straight to Azure Service Bus, whereas in the HTTP case, the provisioning service talks to the custom gateway. The assumption here is that for the HTTP case, the devices get preprovisioned without exposing the secret key on an unprotected Internet communication path. In other words, the provisioning service runs in the factory, not in Azure.
The device code has two interactions with Azure Service Bus: sending events and receiving commands. I’m going to send out an event once every minute after a new temperature reading has been made, and I’ll also use that opportunity to grab any pending commands and execute them. To do that I’ll amend the TemperatureHumidityMeasurementComplete method shown in Figure 2, and add calls to SendEvent and ProcessCommands to be processed once every minute, as shown in Figure 6.
Figure 6 Sending Events and Receiving Commands
void TemperatureHumidityMeasurementComplete(TemperatureHumidity sender,
double temperature, double relativeHumidity)
{
[...] (see Figure 2)
if (settings.NetworkProvisioningCompleted &&
DateTime.UtcNow - settings.LastServerUpdate >
TimeSpan.FromTicks(TimeSpan.TicksPerMinute))
{
settings.LastServerUpdate = DateTime.UtcNow;
SendEvent(this.lastTemperatureReading, this.lastHumidityReading);
ProcessCommands();
}
}
void SendEvent(double d, double lastHumidityReading1)
{
try
{
messagingClient.Send(new SimpleMessage()
{
Properties = {
{"Temperature",d},
{"Humidity", lastHumidityReading1},
{"DeviceId", settings.DeviceAccount}
}
});
}
catch (Exception e)
{
Debug.Print(ethernet.ToString());
}
}
void ProcessCommands()
{
SimpleMessage cmd = null;
try
{
do
{
cmd = messagingClient.Receive(TimeSpan.Zero, ReceiveMode.ReceiveAndDelete);
if (cmd != null && cmd.Properties.Contains("Command"))
{
var commandType = (string)cmd.Properties["Command"];
switch (commandType)
{
case "SetTemperature":
if (cmd.Properties.Contains("Parameter"))
{
this.settings.TargetTemperature =
double.Parse((string)cmd.Properties["Parameter"]);
this.RedrawDisplay();
this.temperatureHumidity.RequestMeasurement();
StoreSettings(this.settings);
}
break;
}
}
}
while (cmd != null);
}
catch (Exception e)
{
Debug.Print(e.ToString());
}
}
The SendEvent method uses the messaging client, which gets initialized once network connectivity is available. The messaging client is a tiny version of the Azure Service Bus API that’s capable of sending and receiving messages to and from Service Bus Queues, Topics and Subscriptions. The ProcessCommands method uses the same client to fetch commands from the device’s subscription and processes them. For now, the device only understands the SetTemperature command with a Parameter indicating the absolute temperature to set as a numeric value (in Celsius, by the way). Mind that ProcessCommands specifies a TimeSpan.Zero timeout for receiving messages from the Azure Service Bus subscription, indicating that it’s not willing to wait for messages to arrive. I want to grab a message only if there’s one available and otherwise immediately back off. That reduces the traffic for the custom gateway (should I use HTTP and have one in place) and doesn’t require me to keep a receive loop open on the device. The trade-off is latency. In the worst case, commands have a latency of one minute. If that’s a problem, you can use a longer timeout that causes long polling (which the library supports) and, with that, drive down command latency to a few milliseconds.
The matching server side, for receiving events and sending commands to all registered devices by dropping messages into the Topic, simply follows the regular rules of the Azure Service Bus API and is part of the sample code you can download, so I’ll omit that code here.
Wrapping Up
The goal of this series on the Internet of Things is to provide some insight into the kinds of technologies we’re working on here at Microsoft to enable connected-device prototyping and development. We also want to show how cloud technologies such as Azure Service Bus and analytics technologies such as StreamInsight can help you manage the data flow from and to connected devices; how you can create large-scale cloud architectures for handling a great many devices; and how to aggregate and digest information from them.
On the way, I built an embedded device that you can place on any home network and control remotely from any other network, which is pretty cool if you ask me.
I believe we’re in the very early stages of this journey. In talking to Microsoft customers from all over, I’ve seen that a huge wave of connected and custom-built devices is on the way, and this is a great opportunity for .NET developers and innovative companies looking to build cloud offerings to connect to these devices and to combine them with other connected assets in inventive ways. Let’s see what you can do.
Clemens Vasters is the principal technical lead on the Azure Service Bus team. Vasters has been on the team from the earliest incubation stages and works on the technical feature roadmap for Azure Service Bus, which includes push notifications and high-scale signaling for Web and devices. He’s also a frequent conference speaker and architecture courseware author. Follow him on Twitter at twitter.com/clemensv.
Thanks to the following technical experts for reviewing this article: Elio Damaggio, Todd Holmquist-Sutherland, Abhishek Lal, Zach Libby, Colin Miller and Lorenzo Tessiore