The Intelligent Conference Room project uses Wi-Fi to broadcast whether a room is occupied or not as well as it's temperature, pressure, altitude and light ambiance data every 10 seconds to cloud data channels. The data can be seen on Windows10 UWP app, iOS and Android app. You can view some prettier graphs of the data on Power BI as well. This architecture can be applied to solve various customer issues or come up with new products in the age of iSMAC.

I frequently visit IT corporates in helping them transition to mobile/cloud and IoT technologies. Whenever we try to reserve a room for white board sessions, we see most of rooms are booked. But when we physically go to the conference rooms, we see that some of the conference rooms are not occupied. So I thought it would a nice project for using power of Azure and I immediately started thinking how it might be done. We will walk through the architecture further in this blog.

This post is way beyond developing simple applications that run on IoT as we are expanding this concept to large-scale enterprise product development. I was trying to architect an enterprise product leveraging the power of Azure for IoT, Social, Mobile, Analytics, Cloud (#iSMAC).

Prerequisites:

Hardware

Raspberry Pi2 Model B

Breadboard

PIR Motion Sensor

Adafruit BMP280 Barometric Pressure & Altitude Sensor

MCP3008 - 8-Channel 10-Bit ADC With SPI Interface

Photo resistor

Adafruit Breadboard trim potentiometer - 10K

10K Ohm Resistor

LED (generic)

Adafruit Female to Male Jumper Wires

Adafruit Male to Male Jumper Wires

Software

Windows 10 PC

Visual Studio 2015 Community Edition or Enterprise Edition Update1

Windows 10 IoT Core

Azure Subscription

IoT Hub

Azure Storage Account

Stream Analytics

Power BI

Fritzing Image of the Project

Fritzing Image of the Project

High-Level Architectural Diagram

High-Level Architectural Diagram

You can skip the step 1 and step 2, if you already set Up your PC and Raspberry Pi2.

Step 1: Set up Your PC

First you need a Windows 10 PC running the public release of Windows 10 (version 10.0.10586) or better.

Install Visual Studio Community 2015 or Visual Studio Professional 2015 or Visual Studio Enterprise 2015 with Update 1.

Install Windows IoT Core Project Templates from here.

Enable developer mode on your Windows 10 device by following https://msdn.microsoft.com/library/windows/apps/xaml/dn706236.aspx

Step 2: Set up Raspberry Pi2

5v Micro USB power supply with at least 1.0A current. If you plan on using several power-hungry USB peripherals, use a higher current power supply instead (>2.0A).

8GB Micro SD card - class 10 or better.

HDMI cable and monitor (Optional)

Ethernet Cable

Micro SD card reader - due to an issue with most internal micro SD card readers, we suggest an external USB micro SD card reader.

Install the Windows 10 IoT Core tools:Download a Windows 10 IoT Core image from http://ms-iot.github.io/content/en-US/Downloads.htm Save the ISO to a local folder.Double click on the ISO (Iot Core RPi.iso). It will automatically mount itself as a virtual drive so you can access the contents.

Install Windows_10_IoT_Core_RPi2.msi. When installation is complete, flash.ffu will be located at C:\Program Files (x86)\Microsoft IoT\FFU\RaspberryPi2.

Eject the Virtual CD when installation is complete - this can be done by navigating to the top folder of File Explorer, right clicking on the virtual drive, and selecting "Eject" like shown in below figure 2.

figure 2

Insert a Micro SD Card into your SD card reader.

Use IoTCoreImageHelper.exe to flash the SD card. Search for "WindowsIoT" from start menu and select the shortcut "WindowsIoTImageHelper".

After launch the IoTCoreImageHelper.exe and select your SD Card and the flash.ffu found in the directory.

Once the process has completed, you are ready to run Windows 10 IoT Core on your Raspberry Pi 2.NOTE: IoTCoreImageHelper.exe is the recommended tool to flash the SD card. However, instructions are available for using http://ms-iot.github.io/content/en-US/win10/samples/DISM.htm directly.

Safely remove your USB SD card reader by clicking on "Safely Remove Hardware" in your task tray, or by finding the USB device in File Explorer, right clicking, and choosing "Eject". Failing to do this can cause corruption of the image.

Hook up your board

Insert the micro SD card you prepared into your Raspberry Pi 2.

Connect a network cable from your local network to the Ethernet port on the board. Make sure your development PC is on the same network.NOTE: If you don't have a local wired network, see http://ms-iot.github.io/content/en-US/win10/ConnectToDevice.htm for additional connection options.

Connect an HDMI (High-Definition Multimedia Interface) monitor to the HDMI port on the board (Optional).

Connect the power supply to the micro USB port on the board.

Step 3: Set Up Azure IoT Hub

Create and configure IoT Hub in Azure

Login to your azure new portal , click on browse> select IoT Hub and click on Add icon.

figure 3.a

figure 3.b

Give name for the IoT Hub, select pricing, enter resource name and region and finally click on create.

figure 3.c

Copy the host name which will be used in connection string at the time of registering device identity.

Now click on All Settings-> Select Shared Access Policies -> select iothubowner. Copy primary access key which will be used in later steps.

figure 3.d

figure 3.e

Device Identity Registry

The main purpose of device identity registry is to allow access to the device-facing endpoints. For each device it creates resources in the service, such as a queue in-flight cloud to device messages.

You can do this in different ways, here I am explaining this technique using console application and device explorer.

Using Console Application:

Create a console application and add references of Microsoft.Azure.Devices to your project, you can find it from NuGet package manager.

To create Device Identity registry you need connection string of your Azure IoT Hub.

static string connectionString = "HostName=********.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=**********";

In above code replace the host name and Shared access key with your IoT Hub values which you saved in earlier step.

Complete Code of Program.cs file

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.Azure.Devices; using Microsoft.Azure.Devices.Common.Exceptions; namespace CreateDeviceIdentityConsoleApp { class Program { static RegistryManager registryManager; static string connectionString = "HostName=IoTHub******.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=**************************"; static void Main(string[] args) { registryManager = RegistryManager.CreateFromConnectionString(connectionString); AddDeviceAsync().Wait(); Console.ReadLine(); } private async static Task AddDeviceAsync() { string deviceId = "ConferenceRoom1"; Device device; try { device = await registryManager.AddDeviceAsync(new Device(deviceId)); } catch (DeviceAlreadyExistsException) { device = await registryManager.GetDeviceAsync(deviceId); } Console.WriteLine("Generated device key: {0}", device.Authentication.SymmetricKey.PrimaryKey); } } }

In above code I am creating a device with name "ConferenceRoom1", the class RegistryManager class will creates the device identity registry in the IoT Hub with the help of connection string. If the device already exists, then just get the device information.

After creating/getting device information save the device.Authentication.SymmetricKey.PrimaryKey of the device which will be used in later steps.

figure 3.f

Using Device Explorer

To simulate the Device to cloud and cloud to device messages we have a tool called device explorer. Available from here.

Open Device Explorer, enter your IoT Hub connection string and click on Update.

figure 3.g

Now go to Management tab and click on Create button to create a device

figure 3.h

figure 3.i

Finally save the device Id and Primary Key values which will be used in later steps.

You can use either Console Application or device explorer to do device identity registry.

Step 4: Create an UWP app for Raspberry Pi 2 which will send sensors data to IoT Hub also called as Device to Cloud Messaging

After setting up the Development PC, Raspberry Pi and Azure IoT Hub, Open Visual Studio and create a new project by selecting Universal –> Blank App template as shown in screenshot below.

figure 4.a

Now add reference of Windows 10 IoT Extensions to the recently created project.

figure 4.b

Select an appropriate version of IoT Extensions, here I am using 10.0.10586.0 version and my Raspberry Pi 2 has the same version of Windows 10 IoT core installed.

figure 4.c





>Also add Microsoft.Azure.Devices.Client reference from NuGet Manager.

Reading PIR sensor data to identify motion

To detect motion, I used PIR sensor which comes with Ada fruit Starter kit. Also an LED will be turned on upon detecting any motion.

Initialize the GPIO to access the LED and PIR sensors

private void InitGPIO() { // get the GPIO controller var gpio = GpioController.GetDefault(); // return an error if there is no gpio controller if (gpio == null) { led = null; Debug.WriteLine("There is no GPIO controller."); return; } // set up the LED on the defined GPIO pin // and set it to High to turn off the LED led = gpio.OpenPin(ledPin); led.Write(GpioPinValue.High); led.SetDriveMode(GpioPinDriveMode.Output); // set up the PIR sensor's signal on the defined GPIO pin // and set it's initial value to Low pir = gpio.OpenPin(pirPin); pir.SetDriveMode(GpioPinDriveMode.Input); Debug.WriteLine("GPIO pins initialized correctly."); }

Now read the PIR sensor data and glow the LED if any detection happen, for that the below is the code

// read the signal from the PIR sensor // if it is high, then motion was detected if (pir.Read() == GpioPinValue.High) {

// turn on the LED

led.Write(GpioPinValue.Low); // update the sensor status in the UI Debug.WriteLine("Motion detected!") } else { // turn off the LED led.Write(GpioPinValue.High); // update the sensor status in the UI Debug.WriteLine("No motion detected."); }

Reading Temperature, Pressure and Light sensor values

In this project we also reading the temperature, pressure and light sensitivity data of the conference room.

For temperature we used BPM280 V1 sensor which comes with Ada fruit starter kit.

Initializing BPM280 sensor

//Method to initialize the BMP280 sensor public async Task Initialize() { Debug.WriteLine("BMP280::Initialize"); try { //Instantiate the I2CConnectionSettings using the device address of the BMP280 I2cConnectionSettings settings = new I2cConnectionSettings(BMP280_Address); //Set the I2C bus speed of connection to fast mode settings.BusSpeed = I2cBusSpeed.FastMode; //Use the I2CBus device selector to create an advanced query syntax string string aqs = I2cDevice.GetDeviceSelector(I2CControllerName); //Use the Windows.Devices.Enumeration.DeviceInformation class to create a collection using the advanced query syntax string DeviceInformationCollection dis = await DeviceInformation.FindAllAsync(aqs); //Instantiate the the BMP280 I2C device using the device id of the I2CBus and the I2CConnectionSettings bmp280 = await I2cDevice.FromIdAsync(dis[0].Id, settings); //Check if device was found if (bmp280 == null) { Debug.WriteLine("Device not found"); } } catch (Exception e) { Debug.WriteLine("Exception: " + e.Message + "

" + e.StackTrace); throw; } }

The following is the helper class which has all functionality to Initialize and read values from BPM280 Sensor

BPM280.cs

using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using Windows.Devices.Enumeration; using Windows.Devices.Gpio; using Windows.Devices.I2c; namespace ConferenceRoomsPIRPOC { public class BMP280_CalibrationData { //BMP280 Registers public UInt16 dig_T1 { get; set; } public Int16 dig_T2 { get; set; } public Int16 dig_T3 { get; set; } public UInt16 dig_P1 { get; set; } public Int16 dig_P2 { get; set; } public Int16 dig_P3 { get; set; } public Int16 dig_P4 { get; set; } public Int16 dig_P5 { get; set; } public Int16 dig_P6 { get; set; } public Int16 dig_P7 { get; set; } public Int16 dig_P8 { get; set; } public Int16 dig_P9 { get; set; } } public class BMP280 { //The BMP280 register addresses according the the datasheet: http://www.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf const byte BMP280_Address = 0x77; const byte BMP280_Signature = 0x58; enum eRegisters : byte { BMP280_REGISTER_DIG_T1 = 0x88, BMP280_REGISTER_DIG_T2 = 0x8A, BMP280_REGISTER_DIG_T3 = 0x8C, BMP280_REGISTER_DIG_P1 = 0x8E, BMP280_REGISTER_DIG_P2 = 0x90, BMP280_REGISTER_DIG_P3 = 0x92, BMP280_REGISTER_DIG_P4 = 0x94, BMP280_REGISTER_DIG_P5 = 0x96, BMP280_REGISTER_DIG_P6 = 0x98, BMP280_REGISTER_DIG_P7 = 0x9A, BMP280_REGISTER_DIG_P8 = 0x9C, BMP280_REGISTER_DIG_P9 = 0x9E, BMP280_REGISTER_CHIPID = 0xD0, BMP280_REGISTER_VERSION = 0xD1, BMP280_REGISTER_SOFTRESET = 0xE0, BMP280_REGISTER_CAL26 = 0xE1, // R calibration stored in 0xE1-0xF0 BMP280_REGISTER_CONTROLHUMID = 0xF2, BMP280_REGISTER_CONTROL = 0xF4, BMP280_REGISTER_CONFIG = 0xF5, BMP280_REGISTER_PRESSUREDATA_MSB = 0xF7, BMP280_REGISTER_PRESSUREDATA_LSB = 0xF8, BMP280_REGISTER_PRESSUREDATA_XLSB = 0xF9, // bits <7:4> BMP280_REGISTER_TEMPDATA_MSB = 0xFA, BMP280_REGISTER_TEMPDATA_LSB = 0xFB, BMP280_REGISTER_TEMPDATA_XLSB = 0xFC, // bits <7:4> BMP280_REGISTER_HUMIDDATA_MSB = 0xFD, BMP280_REGISTER_HUMIDDATA_LSB = 0xFE, }; //String for the friendly name of the I2C bus const string I2CControllerName = "I2C1"; //Create an I2C device private I2cDevice bmp280 = null; //Create new calibration data for the sensor BMP280_CalibrationData CalibrationData; //Variable to check if device is initialized bool init = false; //Method to initialize the BMP280 sensor public async Task Initialize() { Debug.WriteLine("BMP280::Initialize"); try { //Instantiate the I2CConnectionSettings using the device address of the BMP280 I2cConnectionSettings settings = new I2cConnectionSettings(BMP280_Address); //Set the I2C bus speed of connection to fast mode settings.BusSpeed = I2cBusSpeed.FastMode; //Use the I2CBus device selector to create an advanced query syntax string string aqs = I2cDevice.GetDeviceSelector(I2CControllerName); //Use the Windows.Devices.Enumeration.DeviceInformation class to create a collection using the advanced query syntax string DeviceInformationCollection dis = await DeviceInformation.FindAllAsync(aqs); //Instantiate the the BMP280 I2C device using the device id of the I2CBus and the I2CConnectionSettings bmp280 = await I2cDevice.FromIdAsync(dis[0].Id, settings); //Check if device was found if (bmp280 == null) { Debug.WriteLine("Device not found"); } } catch (Exception e) { Debug.WriteLine("Exception: " + e.Message + "

" + e.StackTrace); throw; } } private async Task Begin() { Debug.WriteLine("BMP280::Begin"); byte[] WriteBuffer = new byte[] { (byte)eRegisters.BMP280_REGISTER_CHIPID }; byte[] ReadBuffer = new byte[] { 0xFF }; //Read the device signature bmp280.WriteRead(WriteBuffer, ReadBuffer); Debug.WriteLine("BMP280 Signature: " + ReadBuffer[0].ToString()); //Verify the device signature if (ReadBuffer[0] != BMP280_Signature) { Debug.WriteLine("BMP280::Begin Signature Mismatch."); return; } //Set the initalize variable to true init = true; //Read the coefficients table CalibrationData = await ReadCoefficeints(); //Write control register await WriteControlRegister(); //Write humidity control register await WriteControlRegisterHumidity(); } //Method to write 0x03 to the humidity control register private async Task WriteControlRegisterHumidity() { byte[] WriteBuffer = new byte[] { (byte)eRegisters.BMP280_REGISTER_CONTROLHUMID, 0x03 }; bmp280.Write(WriteBuffer); await Task.Delay(1); return; } //Method to write 0x3F to the control register private async Task WriteControlRegister() { byte[] WriteBuffer = new byte[] { (byte)eRegisters.BMP280_REGISTER_CONTROL, 0x3F }; bmp280.Write(WriteBuffer); await Task.Delay(1); return; } //Method to read a 16-bit value from a register and return it in little endian format private UInt16 ReadUInt16_LittleEndian(byte register) { UInt16 value = 0; byte[] writeBuffer = new byte[] { 0x00 }; byte[] readBuffer = new byte[] { 0x00, 0x00 }; writeBuffer[0] = register; bmp280.WriteRead(writeBuffer, readBuffer); int h = readBuffer[1] << 8; int l = readBuffer[0]; value = (UInt16)(h + l); return value; } //Method to read an 8-bit value from a register private byte ReadByte(byte register) { byte value = 0; byte[] writeBuffer = new byte[] { 0x00 }; byte[] readBuffer = new byte[] { 0x00 }; writeBuffer[0] = register; bmp280.WriteRead(writeBuffer, readBuffer); value = readBuffer[0]; return value; } //Method to read the caliberation data from the registers private async Task<BMP280_CalibrationData> ReadCoefficeints() { // 16 bit calibration data is stored as Little Endian, the helper method will do the byte swap. CalibrationData = new BMP280_CalibrationData(); // Read temperature calibration data CalibrationData.dig_T1 = ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_T1); CalibrationData.dig_T2 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_T2); CalibrationData.dig_T3 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_T3); // Read presure calibration data CalibrationData.dig_P1 = ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P1); CalibrationData.dig_P2 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P2); CalibrationData.dig_P3 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P3); CalibrationData.dig_P4 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P4); CalibrationData.dig_P5 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P5); CalibrationData.dig_P6 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P6); CalibrationData.dig_P7 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P7); CalibrationData.dig_P8 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P8); CalibrationData.dig_P9 = (Int16)ReadUInt16_LittleEndian((byte)eRegisters.BMP280_REGISTER_DIG_P9); await Task.Delay(1); return CalibrationData; } //t_fine carries fine temperature as global value Int32 t_fine = Int32.MinValue; //Method to return the temperature in DegC. Resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC. private double BMP280_compensate_T_double(Int32 adc_T) { double var1, var2, T; //The temperature is calculated using the compensation formula in the BMP280 datasheet var1 = ((adc_T / 16384.0) - (CalibrationData.dig_T1 / 1024.0)) * CalibrationData.dig_T2; var2 = ((adc_T / 131072.0) - (CalibrationData.dig_T1 / 8192.0)) * CalibrationData.dig_T3; t_fine = (Int32)(var1 + var2); T = (var1 + var2) / 5120.0; return T; } //Method to returns the pressure in Pa, in Q24.8 format (24 integer bits and 8 fractional bits). //Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa private Int64 BMP280_compensate_P_Int64(Int32 adc_P) { Int64 var1, var2, p; //The pressure is calculated using the compensation formula in the BMP280 datasheet var1 = t_fine - 128000; var2 = var1 * var1 * (Int64)CalibrationData.dig_P6; var2 = var2 + ((var1 * (Int64)CalibrationData.dig_P5) << 17); var2 = var2 + ((Int64)CalibrationData.dig_P4 << 35); var1 = ((var1 * var1 * (Int64)CalibrationData.dig_P3) >> 8) + ((var1 * (Int64)CalibrationData.dig_P2) << 12); var1 = (((((Int64)1 << 47) + var1)) * (Int64)CalibrationData.dig_P1) >> 33; if (var1 == 0) { Debug.WriteLine("BMP280_compensate_P_Int64 Jump out to avoid / 0"); return 0; //Avoid exception caused by division by zero } //Perform calibration operations as per datasheet: http://www.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf p = 1048576 - adc_P; p = (((p << 31) - var2) * 3125) / var1; var1 = ((Int64)CalibrationData.dig_P9 * (p >> 13) * (p >> 13)) >> 25; var2 = ((Int64)CalibrationData.dig_P8 * p) >> 19; p = ((p + var1 + var2) >> 8) + ((Int64)CalibrationData.dig_P7 << 4); return p; } public async Task<float> ReadTemperature() { //Make sure the I2C device is initialized if (!init) await Begin(); //Read the MSB, LSB and bits 7:4 (XLSB) of the temperature from the BMP280 registers byte tmsb = ReadByte((byte)eRegisters.BMP280_REGISTER_TEMPDATA_MSB); byte tlsb = ReadByte((byte)eRegisters.BMP280_REGISTER_TEMPDATA_LSB); byte txlsb = ReadByte((byte)eRegisters.BMP280_REGISTER_TEMPDATA_XLSB); // bits 7:4 //Combine the values into a 32-bit integer Int32 t = (tmsb << 12) + (tlsb << 4) + (txlsb >> 4); //Convert the raw value to the temperature in degC double temp = BMP280_compensate_T_double(t); //Return the temperature as a float value return (float)temp; } public async Task<float> ReadPreasure() { //Make sure the I2C device is initialized if (!init) await Begin(); //Read the temperature first to load the t_fine value for compensation if (t_fine == Int32.MinValue) { await ReadTemperature(); } //Read the MSB, LSB and bits 7:4 (XLSB) of the pressure from the BMP280 registers byte tmsb = ReadByte((byte)eRegisters.BMP280_REGISTER_PRESSUREDATA_MSB); byte tlsb = ReadByte((byte)eRegisters.BMP280_REGISTER_PRESSUREDATA_LSB); byte txlsb = ReadByte((byte)eRegisters.BMP280_REGISTER_PRESSUREDATA_XLSB); // bits 7:4 //Combine the values into a 32-bit integer Int32 t = (tmsb << 12) + (tlsb << 4) + (txlsb >> 4); //Convert the raw value to the pressure in Pa Int64 pres = BMP280_compensate_P_Int64(t); //Return the temperature as a float value return ((float)pres) / 256; } //Method to take the sea level pressure in Hectopascals(hPa) as a parameter and calculate the altitude using current pressure. public async Task<float> ReadAltitude(float seaLevel) { //Make sure the I2C device is initialized if (!init) await Begin(); //Read the pressure first float pressure = await ReadPreasure(); //Convert the pressure to Hectopascals(hPa) pressure /= 100; //Calculate and return the altitude using the international barometric formula return 44330.0f * (1.0f - (float)Math.Pow((pressure / seaLevel), 0.1903f)); } } public class BMP280SensorData { public float Temperature { get; set; } public float Pressure { get; set; } public float Altitude { get; set; } } }

For reading light sensor values we need to use MCP3008 chip to convert the analog values to digital. Below is the helper class which will contain all methods to initialize MCP3008 chip and converting light values to voltages etc.

MCP3008.cs

using Windows.Devices.Enumeration; using Windows.Devices.Spi; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Diagnostics; namespace ConferenceRoomsPIRPOC { class MCP3008 { // Constants for the SPI controller chip interface private SpiDevice mcp3008; const int SPI_CHIP_SELECT_LINE = 0; // SPI0 CS0 pin 24 // ADC chip operation constants const byte MCP3008_SingleEnded = 0x08; const byte MCP3008_Differential = 0x00; // These are used when we calculate the voltage from the ADC units float ReferenceVoltage; public const uint Min = 0; public const uint Max = 1023; public MCP3008(float referenceVolgate) { Debug.WriteLine("MCP3008::New MCP3008"); // Store the reference voltage value for later use in the voltage calculation. ReferenceVoltage = referenceVolgate; } /// <summary> /// This method is used to configure the Pi2 to communicate over the SPI bus to the MCP3008 ADC chip. /// </summary> public async void Initialize() { Debug.WriteLine("MCP3008::Initialize"); try { // Setup the SPI bus configuration var settings = new SpiConnectionSettings(SPI_CHIP_SELECT_LINE); // 3.6MHz is the rated speed of the MCP3008 at 5v settings.ClockFrequency = 3600000; settings.Mode = SpiMode.Mode0; // Ask Windows for the list of SpiDevices // Get a selector string that will return all SPI controllers on the system string aqs = SpiDevice.GetDeviceSelector(); // Find the SPI bus controller devices with our selector string var dis = await DeviceInformation.FindAllAsync(aqs); // Create an SpiDevice with our bus controller and SPI settings mcp3008 = await SpiDevice.FromIdAsync(dis[0].Id, settings); if (mcp3008 == null) { Debug.WriteLine( "SPI Controller {0} is currently in use by another application. Please ensure that no other applications are using SPI.", dis[0].Id); return; } } catch (Exception e) { Debug.WriteLine("Exception: " + e.Message + "

" + e.StackTrace); throw; } } /// <summary> /// This method does the actual work of communicating over the SPI bus with the chip. /// To line everything up for ease of reading back (on byte boundary) we /// will pad the command start bit with 7 leading "0" bits /// /// Write 0000 000S GDDD xxxx xxxx xxxx /// Read ???? ???? ???? ?N98 7654 3210 /// S = start bit /// G = Single / Differential /// D = Chanel data /// ? = undefined, ignore /// N = 0 "Null bit" /// 9-0 = 10 data bits /// </summary> public int ReadADC(byte whichChannel) { byte command = whichChannel; command |= MCP3008_SingleEnded; command <<= 4; byte[] commandBuf = new byte[] { 0x01, command, 0x00 }; byte[] readBuf = new byte[] { 0x00, 0x00, 0x00 }; mcp3008.TransferFullDuplex(commandBuf, readBuf); int sample = readBuf[2] + ((readBuf[1] & 0x03) << 8); int s2 = sample & 0x3FF; Debug.Assert(sample == s2); return sample; } /// <summary> /// Returns the ADC value (uint) as a float voltage based on the configured reference voltage /// </summary> /// <param name="adc"> the ADC value to convert</param> /// <returns>The computed voltage based on the reference voltage</returns> public float ADCToVoltage(int adc) { return (float)adc * ReferenceVoltage / (float)Max; } } public class MCP3008SensorData { public int lowPotReadVal { get; set; } public int highPotReadVal { get; set; } public int cdsReadVal { get; set; } public float lowPotVoltage { get; set; } public float highPotVoltage { get; set; } public float cdsVoltage { get; set; } public string lightStatus { get; set; } } }

Note: While setting up the circuit, make sure your MCP3008 chip is oriented correctly. The chip has a half-moon shape marker along with a dot on one side. This should be oriented as shown in the fritzing diagram.

Below is the code for reading temperature and light sensor values from Helper classes

private async Task<BMP280SensorData> ReadBMP280SensorData() { var sensorData = new BMP280SensorData(); try { //Create a constant for pressure at sea level. //This is based on your local sea level pressure (Unit: Hectopascal) const float seaLevelPressure = 1013.25f; sensorData.Temperature = await BMP280.ReadTemperature(); sensorData.Pressure = await BMP280.ReadPreasure(); sensorData.Altitude = await BMP280.ReadAltitude(seaLevelPressure); } catch (Exception ex) { Debug.WriteLine(ex.Message); } return sensorData; } //For Light private MCP3008SensorData ReadLightStatusInRoom() { var MCP3008SensorData = new MCP3008SensorData(); try { if (mcp3008 == null) { Debug.WriteLine("Light Sensor data is not ready"); MCP3008SensorData.lightStatus = "N/A"; return MCP3008SensorData; } // The new light state, assume it's just right to start. eState newState = eState.JustRight; // Read from the ADC chip the current values of the two pots and the photo cell. MCP3008SensorData.lowPotReadVal = mcp3008.ReadADC(LowPotentiometerADCChannel); MCP3008SensorData.highPotReadVal = mcp3008.ReadADC(HighPotentiometerADCChannel); MCP3008SensorData.cdsReadVal = mcp3008.ReadADC(CDSADCChannel); // convert the ADC readings to voltages to make them more friendly. MCP3008SensorData.lowPotVoltage = mcp3008.ADCToVoltage(MCP3008SensorData.lowPotReadVal); MCP3008SensorData.highPotVoltage = mcp3008.ADCToVoltage(MCP3008SensorData.highPotReadVal); MCP3008SensorData.cdsVoltage = mcp3008.ADCToVoltage(MCP3008SensorData.cdsReadVal); // Let us know what was read in. Debug.WriteLine(String.Format("Read values {0}, {1}, {2} ", MCP3008SensorData.lowPotReadVal, MCP3008SensorData.highPotReadVal, MCP3008SensorData.cdsReadVal)); Debug.WriteLine(String.Format("Voltages {0}, {1}, {2} ", MCP3008SensorData.lowPotVoltage, MCP3008SensorData.highPotVoltage, MCP3008SensorData.cdsVoltage)); // Compute the new state by first checking if the light level is too low if (MCP3008SensorData.cdsVoltage < MCP3008SensorData.lowPotVoltage) { newState = eState.TooDark; } // And now check if it too high. if (MCP3008SensorData.cdsVoltage > MCP3008SensorData.highPotVoltage) { newState = eState.TooBright; } // Use another method to determine what to do with the state. MCP3008SensorData.lightStatus = CheckForStateValue(newState); return MCP3008SensorData; } catch (Exception) { MCP3008SensorData.lightStatus = "N/A"; return MCP3008SensorData; } } private string CheckForStateValue(eState newState) { String lightStatus; switch (newState) { case eState.JustRight: { lightStatus = JustRightLightString; } break; case eState.TooBright: { lightStatus = HighLightString; } break; case eState.TooDark: { lightStatus = LowLightString; } break; default: { lightStatus = "N/A"; } break; } return lightStatus; }

Sending sensor data to IoT Hub

The application reads data from sensors and send it to IoT Hub periodically. Following is the timer event code to read sensor data and send to IoT Hub.

private async void Timer_Tick(object sender, object e) { //Reading Temperature/Pressure/Altitude Information from BMP280 Sensor var BMP280SensorData = await ReadBMP280SensorData(); Debug.WriteLine("BMP280 Sensor data

Temperature:{0},

Pressure:{1},

Altitude:{2}", BMP280SensorData.Temperature, BMP280SensorData.Pressure, BMP280SensorData.Altitude); //Reading Lighting Information from Sensor var MCP3008SensorData = ReadLightStatusInRoom(); Debug.WriteLine("Light Status in Room: " + MCP3008SensorData.lightStatus); // read the signal from the PIR sensor // if it is high, then motion was detected if (pir.Read() == GpioPinValue.High) { // turn on the LED led.Write(GpioPinValue.Low); // update the sensor status in the UI Debug.WriteLine("Motion detected!"); //Sending Message to IoT Hub SendDeviceToCloudMessagesAsync("Occupied", BMP280SensorData, MCP3008SensorData); } else { // turn off the LED led.Write(GpioPinValue.High); // update the sensor status in the UI Debug.WriteLine("No motion detected."); //Sending Message to IoT Hub SendDeviceToCloudMessagesAsync("Not Occupied", BMP280SensorData, MCP3008SensorData); } } // sendstatus=false; private async void SendDeviceToCloudMessagesAsync(string status,BMP280SensorData BMP280SensorData, MCP3008SensorData MCP3008SensorData) { ConferenceRoomDataPoint conferenceRoomDataPoint = new ConferenceRoomDataPoint() { DeviceId = deviceName, Time = DateTime.UtcNow.ToString("o"), RoomTemp = BMP280SensorData.Temperature.ToString(), RoomPressure = BMP280SensorData.Pressure.ToString(), RoomAlt = BMP280SensorData.Altitude.ToString(), LightStatus = MCP3008SensorData.lightStatus, LightCDSValue= MCP3008SensorData.cdsReadVal.ToString(), LightCDSVoltageValue= MCP3008SensorData.cdsVoltage.ToString(), RoomStatus = status }; if (status == "Occupied") { conferenceRoomDataPoint.Color = "Red"; } else { conferenceRoomDataPoint.Color = "Green"; } var jsonString = JsonConvert.SerializeObject(conferenceRoomDataPoint); var jsonStringInBytes = new Message(Encoding.ASCII.GetBytes(jsonString)); await deviceClient.SendEventAsync(jsonStringInBytes); Debug.WriteLine("{0} > Sending message: {1}", DateTime.UtcNow, jsonString); }

Step 5: Create an UWP client app for reading data from IoT Hub also called as Cloud to Device Messaging

I have developed a client app which will read data from IoT Hub and displays whether the conference room is empty or not along with other sensor data.

In this project we used AzureSBLite reference to connect to IoT Hub and read messages from it. You can install the reference from NuGet manager.

Code for client app UI

<Page x:Class="ConferenceRoomsClientPOC.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:ConferenceRoomsClientPOC" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" > <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="60"/> <RowDefinition/> </Grid.RowDefinitions> <Grid x:Name="titleGrid" Grid.Column="0" Background="#184073"> <StackPanel x:Name="MainStkPnl" Margin="30 0 0 0" Orientation="Horizontal"> <TextBlock x:Uid="titleTxtBlk" x:Name="titleTxtBlk" Text="Conference Rooms Status" FontWeight="SemiBold" Foreground="White" Margin="0 0 0 0" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="40"/> </StackPanel> <ProgressBar x:Name="progressring" IsIndeterminate="False" Foreground="Orange" VerticalAlignment="Bottom" Margin="0,0,0,3" Visibility="Collapsed"/> </Grid> <Grid Grid.Row="1" Grid.ColumnSpan="2" Background="#3D3D3D" Opacity="0.8"/> <Grid Grid.Row="1" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0 0 50 0"> <TextBlock Text="1:00 pm-2:00 pm" FontSize="18" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> <ListView x:Name="roomsListView" Grid.Row="1" Grid.ColumnSpan="2" Margin="0,30,0,0" IsItemClickEnabled="True" Visibility="Collapsed" ItemsSource="{Binding}" HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"> <ListView.ItemContainerStyle> <Style TargetType="ListViewItem"> <Setter Property="HorizontalContentAlignment" Value="Stretch"/> </Style> </ListView.ItemContainerStyle> <ListView.ItemTemplate> <DataTemplate> <Grid Grid.Row="1" BorderThickness="2" BorderBrush="Black" Margin="5" HorizontalAlignment="Stretch" > <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock Text="{Binding DeviceId}" Style="{StaticResource SubheaderTextBlockStyle}" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="20" FontWeight="SemiBold" Foreground="White"/> <Grid Grid.Row="1" Background="{Binding Color}"> <Grid.RowDefinitions> <RowDefinition Height="60"/> <RowDefinition Height="80"/> <RowDefinition Height="60"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Margin="10"> <TextBlock Text="Room Status :" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> <TextBlock Text="{Binding RoomStatus}" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> </StackPanel> <StackPanel Orientation="Vertical" Grid.Row="1" Margin="10" VerticalAlignment="Top"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Room Temperature :" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> <TextBlock Text="{Binding RoomTemp}" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> <TextBlock Text="°C" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" FontSize="18"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Room Light :" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> <TextBlock Text="{Binding LightStatus}" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> <!--<TextBlock Text="°C" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" FontSize="18"/>--> </StackPanel> </StackPanel> <StackPanel Orientation="Horizontal" Grid.RowSpan="2" VerticalAlignment="Bottom" Margin="10 0 0 0"> <TextBlock Text="Last Updated :" FontSize="12" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" Margin="0 0 3 0"/> <TextBlock Text="{Binding Time}" FontSize="12" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold"/> </StackPanel> <Border Grid.Row="2" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" > <!--<TextBlock Text="1:00 pm-2:00 pm" FontSize="18" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center"/>--> <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch"> <TextBlock Text="Notification : " FontSize="18" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10 0 20 0"/> <ToggleSwitch x:Name="NotificationToggleSwtich" Foreground="White" Toggled="NotificationToggleSwtich_Toggled" OffContent="Off" RequestedTheme="Dark" OnContent="On" Margin="0,0"> <!--<ToggleSwitch.Header> <TextBlock Text="Notification" FontSize="18" Foreground="White" FontWeight="SemiBold" TextWrapping="Wrap" /> </ToggleSwitch.Header>--> </ToggleSwitch> </StackPanel> </Border> </Grid> </Grid> </DataTemplate> </ListView.ItemTemplate> </ListView> <GridView x:Name="roomsGridView" AutomationProperties.AutomationId="RoomsGridView" AutomationProperties.Name="Items" TabIndex="1" Grid.Row="1" Grid.ColumnSpan="2" Margin="30,30,0,0" ScrollViewer.HorizontalScrollMode="Enabled" ScrollViewer.HorizontalScrollBarVisibility="Auto" ItemsSource="{Binding}" SelectionMode="None" IsSwipeEnabled="false" IsItemClickEnabled="True" HorizontalAlignment="Center" VerticalAlignment="Center"> <GridView.ItemsPanel> <ItemsPanelTemplate> <WrapGrid Orientation="Horizontal"/> </ItemsPanelTemplate> </GridView.ItemsPanel> <GridView.ItemTemplate> <DataTemplate> <Grid HorizontalAlignment="Left" Width="250" Height="250" Grid.Row="1" Background="{Binding Color}" Margin="3 3 3 3" VerticalAlignment="Top"> <Grid.RowDefinitions> <RowDefinition Height="50"/> <RowDefinition/> <RowDefinition Height="40"/> </Grid.RowDefinitions> <Border Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}"> <TextBlock Text="{Binding RoomStatus}" FontSize="25" Foreground="White" TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="SemiBold" /> </Border> <StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{Binding DeviceId}" FontSize="25" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold"/> <StackPanel Orientation="Vertical" HorizontalAlignment="Center"> <StackPanel Orientation="Horizontal"> <TextBlock Text="Temp :" FontSize="20" Foreground="White" TextWrapping="Wrap" Margin="0 0 5 0" FontWeight="SemiBold"/> <TextBlock Text="{Binding RoomTemp}" FontSize="20" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold"/> <TextBlock Text="°C" FontSize="20" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="Room Light :" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> <TextBlock Text="{Binding LightStatus}" Foreground="White" Style="{StaticResource SubheaderTextBlockStyle}" FontWeight="SemiBold" FontSize="18"/> <!--<TextBlock Text="°C" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" FontSize="18"/>--> </StackPanel> </StackPanel> </StackPanel> <StackPanel Orientation="Horizontal" Grid.RowSpan="2" VerticalAlignment="Bottom" Margin="10 0 0 0"> <TextBlock Text="Last Updated :" FontSize="12" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" Margin="0 0 3 0"/> <TextBlock Text="{Binding Time}" FontSize="12" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold"/> </StackPanel> <Border Grid.Row="2" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}" > <!--<TextBlock Text="1:00 pm-2:00 pm" FontSize="18" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center"/>--> <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch"> <TextBlock Text="Notification : " FontSize="18" Foreground="White" TextWrapping="Wrap" FontWeight="SemiBold" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10 0 20 0"/> <ToggleSwitch x:Name="NotificationToggleSwtich" Foreground="White" RequestedTheme="Dark" Toggled="NotificationToggleSwtich_Toggled" OffContent="Off" OnContent="On" Margin="0,0"> </ToggleSwitch> </StackPanel> </Border> </Grid> </DataTemplate> </GridView.ItemTemplate> </GridView> </Grid> <CommandBar x:Name="MainCommandBar" Grid.RowSpan="2" Grid.ColumnSpan="2" Margin="0 0 0 8" HorizontalAlignment="Right" VerticalAlignment="Top" Background="#184073" Foreground="White" RequestedTheme="Dark"> <AppBarButton x:Name="RefreshIcon" RequestedTheme="Dark" Foreground="White" Click="Refresh_Click" Label="Refresh"> <SymbolIcon x:Name="RefreshSymbolIcon" Symbol="Refresh"/> </AppBarButton> </CommandBar> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="adaptiveStates" CurrentStateChanged="adaptiveStates_CurrentStateChanged"> <VisualState x:Name="narrowState"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="0" MinWindowHeight="0" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="titleTxtBlk.FontSize" Value="25"/> <Setter Target="MainCommandBar.VerticalAlignment" Value="Bottom"/> <Setter Target="MainCommandBar.HorizontalAlignment" Value="Stretch"/> <Setter Target="MainCommandBar.ClosedDisplayMode" Value="Compact"/> <Setter Target="MainCommandBar.Margin" Value="0 0 0 0"/> <Setter Target="roomsListView.Visibility" Value="Visible"/> <Setter Target="roomsGridView.Visibility" Value="Collapsed"/> </VisualState.Setters> </VisualState> <VisualState x:Name="portraitState"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="720" MinWindowHeight="0" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="titleTxtBlk.FontSize" Value="25"/> </VisualState.Setters> </VisualState> <VisualState x:Name="wideState"> <VisualState.StateTriggers> <AdaptiveTrigger MinWindowWidth="1024" MinWindowHeight="0" /> </VisualState.StateTriggers> <VisualState.Setters> <Setter Target="roomsListView.Visibility" Value="Collapsed"/> <Setter Target="roomsGridView.Visibility" Value="Visible"/> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Grid> </Page>

For receiving cloud to device messages we need EventHub endpoint and entity name. You will get these details under IoTHub Settings -> Messaging section under Device-to-CLoud Settings we have Event Hub-compatible name is nothing but EventHubEntity and Event Hub-compatible endpoint.

Code for reading Coud to Device messages

static string ConnectionString = "Endpoint=<Event Hub-compatible endpoint>;SharedAccessKeyName=iothubowner;SharedAccessKey=<key>"; static string eventHubEntity = "iothub-ehub-conference-*****-*******"; static string partitionId = "0"; static DateTime startingDateTimeUtc; EventHubConsumerGroup group; EventHubClient client; MessagingFactory factory; EventHubReceiver receiver; public async Task ReceiveDataFromCloud() { startingDateTimeUtc = DateTime.UtcNow; ServiceBusConnectionStringBuilder builder = new ServiceBusConnectionStringBuilder(ConnectionString); builder.TransportType = ppatierno.AzureSBLite.Messaging.TransportType.Amqp; factory = MessagingFactory.CreateFromConnectionString(ConnectionString); client = factory.CreateEventHubClient(eventHubEntity); group = client.GetDefaultConsumerGroup(); receiver = group.CreateReceiver(partitionId.ToString(), startingDateTimeUtc);//startingDateTimeUtc while (true) { EventData data = receiver.Receive(); if (data != null) { var receiveddata = Encoding.UTF8.GetString(data.GetBytes()); var messageString = JsonConvert.DeserializeObject<ConferenceRoomDataPoint>(receiveddata); if (messageString.DeviceId == "ConferenceRoom1") { conferenceRoomDataPointList[0].DeviceId = messageString.DeviceId; conferenceRoomDataPointList[0].RoomTemp = messageString.RoomTemp; conferenceRoomDataPointList[0].RoomStatus = messageString.RoomStatus; conferenceRoomDataPointList[0].Color = messageString.Color; conferenceRoomDataPointList[0].LightStatus = messageString.LightStatus; DateTime localDateTime = DateTime.Parse(messageString.Time); DateTime utcDateTime = localDateTime.ToLocalTime(); conferenceRoomDataPointList[0].Time = utcDateTime.ToString(); conferenceRoomDataPointList[0].RoomPressure = messageString.RoomPressure; conferenceRoomDataPointList[0].RoomAlt = messageString.RoomAlt; } if (messageString.DeviceId == "ConferenceRoom2") { conferenceRoomDataPointList[1].DeviceId = messageString.DeviceId; conferenceRoomDataPointList[1].RoomTemp = messageString.RoomTemp; conferenceRoomDataPointList[1].RoomStatus = messageString.RoomStatus; conferenceRoomDataPointList[1].Color = messageString.Color; conferenceRoomDataPointList[1].LightStatus = messageString.LightStatus; DateTime localDateTime = DateTime.Parse(messageString.Time); DateTime utcDateTime = localDateTime.ToLocalTime(); conferenceRoomDataPointList[1].Time = utcDateTime.ToString(); conferenceRoomDataPointList[1].RoomPressure = messageString.RoomPressure; conferenceRoomDataPointList[1].RoomAlt = messageString.RoomAlt; } //var message = new ConferenceRooms(Encoding.ASCII.GetBytes(receiveddata)); Debug.WriteLine("{0} {1} {2}", data.SequenceNumber, data.EnqueuedTimeUtc.ToLocalTime(), Encoding.UTF8.GetString(data.GetBytes())); } else { break; } await Task.Delay(2000); } receiver.Close(); client.Close(); factory.Close(); }

Client App UI:

1 / 2 • figure 5.a

Step 6: Set Up Azure Stream Analytics to send IoT Hub data to Power BI

Create a Stream Analytics Job in azure, Log in to your azure classic portal. Select Stream Analytics from left side list and click on New plus icon on bottom of the page. Select Data Services->Stream Analytics->Quick Create

figure 6.a

Enter Job Name, Select Region and related region storage account if already exist it selects automatically otherwise create a new storage account by specifying name for the storage account. Finally click on Create Stream Analytics Job which is located at the bottom of New dialog screen.

figure 6.b

Select the newly created stream analytics, click on Inputs tab and select Add Input option available on the page.

figure 6.c

On Add an Input Popup window select Data Stream as the input and click on next.

figure 6.d

Select IoT Hub as the data stream input and click next.

figure 6.e

Enter Input stream alias name which will be used in later steps, select subscription, choose an IoT Hub which we created in Step 3 SetUp Azure IoT Hub and select iothubowner as shared access policy name and click on next.

figure 6.f

Select Event serialization format as JSON and Encoding as UTF8 then click on create.

figure 6.g

Here conferenceroom is the alias name for my Input.

figure 6.h

Now add an output for the stream analytics job so that we can process the data which is coming from input stream and send it to the list of supported outputs.

figure 6.i

Here we are using Power BI as an output to the stream analytics job.

figure 6.j

For using Power BI you need to authorize Stream Analytics to access your organizational Microsoft Power BI subscription to create a live dashboard. If you are not yet registered, can register a free account using your organization mail Id through Sign up now link on the page.

figure 6.k

After successful authorization of your Power BI account it will ask you to enter Output alias name, a friendly name to reference in output queries. Provide a dataset name that it is desired for a Power BI output to use, table name which is under dataset of the Power BI output from stream analytics jobs (can only have one table in a dataset) and finally Workspace which is for enabling sharing data with other Power BI users, write data to group workspaces. You can select group workspaces inside Power BI account or chose My Workspace if you don't want to write to a group workspace.





figure 6.l

figure 6.m

Now goto Query tab and write a query to filter the data coming from IoT Hub input stream and give it to the Power BI.

Following is the query I used to filter the data coming from IoT Hub

SELECT DeviceId, RoomStatus, LightStatus, Max(Time) Time, Avg(RoomTemp) roomtempAVG, Avg(RoomPressure) AVGPressure, Avg(RoomAlt) AVGRoomAlt, Avg(LightCDSValue) AVGLightCDSValue, Avg(LightCDSVoltageValue) LightCDSVoltageValue INTO [conferenceroomsBI] FROM [conferenceroom] TIMESTAMP by Time GROUP BY DeviceId,RoomStatus,LightStatus,TumblingWindow(Second,10)

We can filter the data by selecting required columns and with useful information like average of the values etc.

figure 6.n

Now click on start icon from the bottom of the screen, If stream analytics fails to run then go to Dashboard of your stream analytics job select Operational Logs which is located right side of the page under Management Service.

figure 6.o

Select the log and click on Details icon which is located under bottom of the page.

figure 6.p

Step 7: Set Up Power BI

After successful starting Stream Analytics job, go to Power BI and check for the newly created dataset under datasets section and start creating charts based on the data that we received.

figure 7.a

figure 7.b

Based on the data received in Power BI, I created some real time charts like Average Temperature in a conference room, average light value in a conference room etc.

Let's see how we can create a chart for average temperature in a conference room.

First select a Guage chart which is located under Visualization tab

figure 7.c

Drag and drop the roomtempavg value field from fields tab to value field in Visualization tab.

figure 7.d

figure 7.e

Change the roomtempavg value sum to average





figure 7.f

figure 7.g

Click on Pin symbol on top of the chart to add it to dashboard. Save it before pining to the dashboard.

figure 7.h





Select the dashboard to where you want to pin it, if you don't have select New dashboard otherwise select Existing dashboard.

figure 7.i





figure 7.j



