Quantcast
Channel: Fabric Controller» Windows Azure
Viewing all articles
Browse latest Browse all 46

IoT with Azure Service Bus Event Hubs: authenticating and sending from any type of device (.NET and JS samples)

$
0
0

Yesterday I wrote a post that introduces you Azure Service Bus Event Hubs. Today we’ll be covering a real IoT scenario, allowing your devices to authenticate with Event Hubs and send out events without needing the Service Bus SDK or even .NET for that matter.

In this example we’ll have multiple devices in our house sending the current temperature to an Event Hub.

Authentication

So the first thing I did is create a new Event Hub (explained in detail in my previous) post and create 2 Shared Access Policies, one to send events and one to process events:

Our devices will need to authenticate to identify the devices on the receiver side and to keep the hub secure, but we don’t want to include the shared secret in our devices. We want to embed a credential that is unique for that device (or at least load it from an api). This is why a Shared Access Policy allows you to generate a Shared Access Signature that is unique to that device (the SAS includes the identifier of the publisher).

Event Hubs support 2 transport types, AMQP and HTTPS. And the way signatures is are generated is a bit different based on the transport type. The RedDog.ServiceBus on NuGet library provides you with a helper class to generate a SAS:

namespace RedDog.ServiceBus
{
    public static class EventHubSharedAccessSignature
    {
        public static TokenProvider CreateTokenProviderForSender(string senderKeyName, string senderKey, string serviceNamespace, string hubName, string publisherName, TimeSpan tokenTimeToLive)
        {
            return TokenProvider.CreateSharedAccessSignatureTokenProvider(CreateForSender(senderKeyName, senderKey, serviceNamespace, hubName, publisherName, tokenTimeToLive));
        }

        public static string CreateForSender(string senderKeyName, string senderKey, string serviceNamespace, string hubName, string publisherName, TimeSpan tokenTimeToLive)
        {
            var serviceUri = ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, String.Format("{0}/publishers/{1}", hubName, publisherName))
                .ToString()
                .Trim('/');
            return SharedAccessSignatureTokenProvider.GetSharedAccessSignature(senderKeyName, senderKey, serviceUri, tokenTimeToLive);
        }

        public static string CreateForHttpSender(string senderKeyName, string senderKey, string serviceNamespace, string hubName, string publisherName, TimeSpan tokenTimeToLive)
        {
            var serviceUri = ServiceBusEnvironment.CreateServiceUri("https", serviceNamespace, String.Format("{0}/publishers/{1}/messages", hubName, publisherName))
                .ToString()
                .Trim('/');
            return SharedAccessSignatureTokenProvider.GetSharedAccessSignature(senderKeyName, senderKey, serviceUri, tokenTimeToLive);
        }
    }
}

To make things a little easier I created a small desktop application that allows you to generate Shared Access Signatures (download link):

So to generate a Shared Access Signature you’ll need the following information:

  • Your Service Bus Namespace
  • The name of your Event Hub
  • The name of the Publisher (this is something you can make up, but should be unique for each device)
  • Mode: Https or AMQP (used by the Service Bus SDK)
  • Sender Key Name: the name of the Shared Access Policy in the portal
  • Sender Key: the key of the Shared Access Policy in the portal
  • TTL: should be very long for devices that can’t renew tokens and less for devices that can update their tokens

Sending from a Javascript application

In this example I’ve used a standard Windows Store Javascript application, but this will also work with PhoneGap, Cordova or any other type of platform allowing you to run Javascript on your devices. The main goal of this example is to show that you don’t need .NET (you can use C, C++, PHP, Java, …) to send events to an Event Hub. So here’s the sample:

<input type="text" value="0" id="temp" />
<input type="button" value="Send Temperature" onclick="sendTemperature()" />
<label id="status"></label>
<script type="text/javascript">
function sendTemperature() {
    // Generate a SAS key with the Signature Generator.: https://github.com/sandrinodimattia/RedDog/releases
    // Could be provided by a Web API.
    var sas = "SharedAccessSignature sr=https%3a%2f%2freddogeventhub.servicebus.windows.net%2ftemperature%2fpublishers%2fphone%2fmessages&sig=Luqu%2fZQd6rfhCdGPTZlhMYCVtXM51QWsdSVlc08LGWc%3d&se=1405564221&skn=SenderDevice";

    var serviceNamespace = "reddogeventhub";
    var hubName = "temperature";
    var deviceName = "phone";

    var xmlHttpRequest = new XMLHttpRequest();
    xmlHttpRequest.open("POST", "https://" +
        serviceNamespace + ".servicebus.windows.net/" + hubName + "/publishers/" + deviceName + "/messages", true);
    xmlHttpRequest.setRequestHeader('Content-Type',
        "application/atom+xml;type=entry;charset=utf-8");
    xmlHttpRequest.setRequestHeader("Authorization", sas);

    xmlHttpRequest.onreadystatechange = function () {
        if (this.readyState == 4) {

            if (this.status == 201) {
                document.getElementById('status').innerText =
                    'Sent: ' + document.getElementById('temp').value;
            } else {
                document.getElementById('status').innerText =
                    this.status;
            }
        }
    };

    xmlHttpRequest.send("{ Temperature: " + document.getElementById('temp').value + " }");
}
</script>

As you can see the code is very simple, I just take the SAS I generated with the Signature Generator together with some info about my Event Hub and I made a simple POST request with a JSON body to the Event Hub. The service will reply with a 201 if the message was sent successfully. This could be useful for sending out GPS coordinates, accelerometer data, …

Sending from a legacy .NET application using HttpWebRequest (.NET 1.1, Windows CE, Silverlight, …)

OK so maybe you’re using .NET, but on a legacy Windows CE 5 device for example. You can build .NET applications for those devices, but forget NuGet, HttpClient and all that other crazy stuff those young people are using nowadays. It’s back to basics with HttpWebRequest. Here’s a sample that allows you to send the temperature using good old legacy code that should work on any device with support for .NET:

// Generate a SAS key with the Signature Generator.: https://github.com/sandrinodimattia/RedDog/releases
var sas = "SharedAccessSignature sr=https%3a%2f%2freddogeventhub.servicebus.windows.net%2ftemperature%2fpublishers%2fbathroom%2fmessages&sig=OsO5iA%2btDyxGLmFCcHNHmTRtMTr03VyZjAtdC5FFPEw%3d&se=1405562933&skn=SenderDevice";

// Namespace info.
var serviceNamespace = "reddogeventhub";
var hubName = "temperature";
var deviceName = "bathroom";            
            
Console.WriteLine("Starting device: {0}", deviceName);

var uri = new Uri(String.Format("https://{0}.servicebus.windows.net/{1}/publishers/{2}/messages", serviceNamespace, hubName, deviceName));

// Keep sending.
while (true)
{
    var eventData = new
    {
        Temperature = new Random().Next(20, 50)
    };

    var req = WebRequest.CreateHttp(uri);
    req.Method = "POST";
    req.Headers.Add("Authorization", sas);
    req.ContentType = "application/atom+xml;type=entry;charset=utf-8";

    using (var writer = new StreamWriter(req.GetRequestStream()))
    {
        writer.Write("{ Temperature: " + eventData.Temperature + "}");
    }

    using (var response = req.GetResponse() as HttpWebResponse)
    {
        Console.WriteLine("Sent temperature using legacy HttpWebRequest: {0}", eventData.Temperature);
        Console.WriteLine(" > Response: {0}", response.StatusCode);
                    
    }

    Thread.Sleep(new Random().Next(1000, 5000));
}

Here I’m doing exactly the same as in the previous example. I generate the uri based on the info of my Event Hub and then I add the Shared Access Signature (which is stored in the device, or which I get from time to time from an api) to the headers and send the temperature in JSON format. And as you can see I’m using raw, hand-coded JSON, without using JSON.NET :)

And that’s it, those old devices with all kinds of sensors can now be used to build some exciting stuff.

Sending from a modern .NET application using HttpClient

Next up is what everyone is using today: HttpClient. And here again the we’re doing the same: create the url, add the authorization header and send the event data.

// Generate a SAS key with the Signature Generator.: https://github.com/sandrinodimattia/RedDog/releases
var sas = "SharedAccessSignature sr=https%3a%2f%2freddogeventhub.servicebus.windows.net%2ftemperature%2fpublishers%2flivingroom%2fmessages&sig=I7n%2bqlIExBRs23V4mcYYfYVYhc6adOlMAeTY9VM9kNg%3d&se=1405562228&skn=SenderDevice";
            
// Namespace info.
var serviceNamespace = "reddogeventhub";
var hubName = "temperature";
var deviceName = "livingroom";

// Create client.
var httpClient = new HttpClient();
httpClient.BaseAddress = 
           new Uri(String.Format("https://{0}.servicebus.windows.net/", serviceNamespace));
httpClient.DefaultRequestHeaders
           .TryAddWithoutValidation("Authorization", sas);

Console.WriteLine("Starting device: {0}", deviceName);

// Keep sending.
while (true)
{
    var eventData = new
    {
        Temperature = new Random().Next(20, 50)
    };

    var postResult = httpClient.PostAsJsonAsync(
           String.Format("{0}/publishers/{1}/messages", hubName, deviceName), eventData).Result;

    Console.WriteLine("Sent temperature using HttpClient: {0}", 
           eventData.Temperature);
    Console.WriteLine(" > Response: {0}", 
           postResult.StatusCode);
    Console.WriteLine(" > Response Content: {0}", 
           postResult.Content.ReadAsStringAsync().Result);

    Thread.Sleep(new Random().Next(1000, 5000));
}

Sending from a modern .NET application using Service Bus SDK

For Server-side applications where you can/want to use the Service Bus SDK the following sample could come in handy. Keep in mind that you need to choose AMQP when generating the SAS since the Service Bus SDK does not support HTTPS.

// Generate a SAS key with the Signature Generator.: https://github.com/sandrinodimattia/RedDog/releases
// Be sure to choose the AMQP option.
var sas = "SharedAccessSignature sr=sb%3a%2f%2freddogeventhub.servicebus.windows.net%2ftemperature%2fpublishers%2fgarage&sig=sxgUcKBpmKU%2fsvg5%2bPzmo1%2bOBtCqVH3ZAGiAFNz3rY4%3d&se=1405563678&skn=SenderDevice";

// Namespace info.
var serviceNamespace = "reddogeventhub";
var hubName = "temperature";
var deviceName = "garage";

Console.WriteLine("Starting device: {0}", deviceName);

// Keep sending.
while (true)
{
    var eventData = new
    {
        Temperature = new Random().Next(20, 50)
    };
                
    var factory = MessagingFactory.Create(ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, ""), new MessagingFactorySettings
    {
        TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(sas),
        TransportType = TransportType.Amqp
    });

    var client = factory.CreateEventHubClient(String.Format("{0}/publishers/{1}", hubName, deviceName));

    var data = new EventData(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(eventData)));
    data.PartitionKey = deviceName;

    client.Send(data);

    Console.WriteLine("Sent temperature using EventHubClient: {0}", eventData.Temperature);

    Thread.Sleep(new Random().Next(1000, 5000));
}

Processing the events

And finally here’s a small application that will receive the events from all those devices and show them on your screen (for more information on how this works, take a look at my previous post):

var partitionCount = 8;
var serviceNamespace = "reddogeventhub";
var hubName = "temperature";
var receiverKeyName = "TemperatureProcessor";
var receiverKey = "Bk3bmYV1kiLYRPBac8OuAQYa1WlTRQEa5Vi1/WaGTh0=";

Console.WriteLine("Starting temperature processor with {0} partitions.", partitionCount);

CancellationTokenSource cts = new CancellationTokenSource();

for (int i = 0; i <= partitionCount - 1; i++)
{
    Task.Factory.StartNew((state) =>
    {
        Console.WriteLine("Starting worker to process partition: {0}", state);

        var factory = MessagingFactory.Create(ServiceBusEnvironment.CreateServiceUri("sb", serviceNamespace, ""), new MessagingFactorySettings()
        {
            TokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(receiverKeyName, receiverKey),
            TransportType = TransportType.Amqp
        });

        var receiver = factory.CreateEventHubClient(hubName)
            .GetDefaultConsumerGroup()
            .CreateReceiver(state.ToString(), DateTime.UtcNow);

        while (true)
        {
            // Receive could fail, I would need a retry policy etc...
            var messages = receiver.Receive(10);
            foreach (var message in messages)
            {
                var eventBody = Newtonsoft.Json.JsonConvert.DeserializeObject<TemperatureEvent>(Encoding.Default.GetString(message.GetBytes()));
                Console.WriteLine("{0} [{1}] Temperature: {2}", DateTime.Now, message.PartitionKey, eventBody.Temperature);
            }

            if (cts.IsCancellationRequested)
            {
                Console.WriteLine("Stopping: {0}", state);
                receiver.Close();
            }
        }
    }, i);
}

Console.ReadLine();
cts.Cancel();
Console.WriteLine("Wait for all receivers to close and then press ENTER.");
Console.ReadLine();

Here’s a screenshot of ‘devices’ and the processor in action:

The full sample is available on GitHub.

Enjoy!


Viewing all articles
Browse latest Browse all 46

Trending Articles