Philippe Libioulle
Published © GPL3+

Personal Home Safety Agent

Monitor smoke detectors, detect water leakages... Turn main water supply off in case of leakage. Notifications sent using Azure and WNS.

IntermediateFull instructions provided26,257
Personal Home Safety Agent

Things used in this project

Hardware components

Raspberry Pi 2 Model B
Raspberry Pi 2 Model B
×1
Water valve
×1
Water sensor
×1
Fire bell
×1
SSR Relay
×1
Smoke alarm relay module
×1
12v SPDT relay (duo)
×1
12v SPDT relay (single)
×1

Software apps and online services

SMS Messaging API
Twilio SMS Messaging API
Microsoft Azure
Microsoft Azure
Windows 10 IoT Core
Microsoft Windows 10 IoT Core

Story

Read more

Schematics

Fire detection subsystem

Here is how to interface with Kite 120X gateway.

Water valve subsystem

Here is how to drive the motorized valve and to get feedback

Outdoor bell subsystem

This is quite simple. You just need a small relay to turn the bell On/Off. The bell has its own 12vDC power supply since it takes more current than the Raspi could share.

Water detection

All detectors are wired in parallel. You could wire them zone by zone (basement, bathroom, kitchen, ..) if you want to get better info on leakage location.

Code

Main task running in background on the Raspberry

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Http;
using Windows.ApplicationModel.Background;
using Windows.Devices.Gpio;
using Windows.System.Threading;
using NotificationGateway;

// The Background Application template is documented at http://go.microsoft.com/fwlink/?LinkID=533884&clcid=0x409

namespace SafetyAgent
{
    public sealed class StartupTask : IBackgroundTask
    {
        BackgroundTaskDeferral deferral;
        private GpioPin waterSensorsPin;
        private GpioPin fireDetectorPin;
        private GpioPin outdoorBellPin;
        private MotorizedValve valve;
        private ThreadPoolTimer alarmTimer;
        private TimeSpan outdoorBellDelay = new TimeSpan(0, 1, 0); // i.e 1 minute


        const int waterSensorsPinId = 27;
        const int fireDetectorPinId = 22;
        const int motorizedValveOpenActuatorId = 24;
        const int motorizedValveCloseActuatorId = 25;
        const int motorizedValveOpenSensorId = 12;
        const int motorizedValveClosedSensorId = 16;
        const int outdoorBellPinId = 5;
        const bool notifyUsingAzureHub = true;
        const bool notifyUsingTwilio = true;

        public void Run(IBackgroundTaskInstance taskInstance)
        {            
            System.Diagnostics.Debug.WriteLine("Starting..");
            deferral = taskInstance.GetDeferral(); // Informs the system that the background task might continue to perform work after the IBackgroundTask.Run method returns.
            taskInstance.Canceled += TaskInstance_Canceled;
            // Setup the system
            SetupWaterLeakDetection();
            SetupFireDetection();              
            // Done, let's wait for events now
            System.Diagnostics.Debug.WriteLine("System is up and running");
        }
        /// <summary>
        /// Handle the canceled event. The background task will be canceled if the device is shutdown properly. Windows will also cancel tasks if they
        /// unregistered.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="reason"></param>
        private void TaskInstance_Canceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            //a few reasons that you may be interested in.
            switch (reason)
            {
                case BackgroundTaskCancellationReason.Abort:
                    //app unregistered background task (amoung other reasons).
                    break;
                case BackgroundTaskCancellationReason.Terminating:
                    //system shutdown
                    break;
                case BackgroundTaskCancellationReason.ConditionLoss:
                    break;
                case BackgroundTaskCancellationReason.SystemPolicy:
                    break;
            }
            deferral.Complete();
        }
        /// <summary>
        /// Setup GPIO pins where water sensors are connected and configure how system will react when pin state toggles
        /// </summary>        
        private void SetupWaterLeakDetection()
        {     
            // Enable water leak sensors      
            waterSensorsPin = GpioController.GetDefault().OpenPin(waterSensorsPinId);           
            waterSensorsPin.DebounceTimeout = new TimeSpan(0, 0, 0, 0, 50);  //Ignore changes in value of less than 50ms
            waterSensorsPin.SetDriveMode(GpioPinDriveMode.Input);
            waterSensorsPin.ValueChanged += WaterSensorPin_ValueChanged;
            // Enable motorized valve
            valve = new MotorizedValve(motorizedValveOpenActuatorId, motorizedValveCloseActuatorId, motorizedValveOpenSensorId, motorizedValveClosedSensorId);
            // Confirm system can drive the motorized valve, which is supposed to be open
            if (valve.Connect())
            {                
                valve.Open();
            }            
        }
        /// <summary>
        /// Raises when there is a change at the Water sensor pin
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void WaterSensorPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {           
            // System.Diagnostics.Debug.WriteLine("Water sensors pin value: " + args.Edge.ToString());
            if (args.Edge == GpioPinEdge.RisingEdge)
            {
                NotifyUsingAzureHub(string.Format("Water leak detected at {0}. System will automaticaly close main water valve", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));
                valve.ValveStateChanged += Valve_ValveState_Changed;
                valve.Close();                
            }
            else
            {               
                NotifyUsingAzureHub(string.Format("Water leak cancelled at {0}. You should restart the system manualy to reopen the water valve", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));
            }
        }
        /// <summary>
        /// Raises when the motorized valve state changes
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Valve_ValveState_Changed(object sender, ValveStateChangedInfo info)
        {
            if (info.newState == ValveState.Open)
            {
                NotifyUsingAzureHub(string.Format("Water valve has been opened at {0}", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));
            }
            if (info.newState == ValveState.Closed)
            {
                NotifyUsingAzureHub(string.Format("Water valve has been closed at {0}", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));
            }
            // System.Diagnostics.Debug.WriteLine(string.Format("Water valve was {0} and is now {1} at {2}", info.previousState.ToString(),
            //     info.newState.ToString(), DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));
        }
        /// <summary>
        /// Setup GPIO pins where water sensors are connected and configure how system will react when pin state toggles
        /// </summary>        
        private void SetupFireDetection()
        {
            // Sensor
            fireDetectorPin = GpioController.GetDefault().OpenPin(fireDetectorPinId);
            fireDetectorPin.DebounceTimeout = new TimeSpan(0, 0, 0, 0, 50);  //Ignore changes in value of less than 50ms
            fireDetectorPin.SetDriveMode(GpioPinDriveMode.Input);
            fireDetectorPin.ValueChanged += FireDetectorPin_ValueChanged;
            // Actuator  
            outdoorBellPin = GpioController.GetDefault().OpenPin(outdoorBellPinId);
            outdoorBellPin.SetDriveMode(GpioPinDriveMode.Output);
            outdoorBellPin.Write(GpioPinValue.Low);
        }
        /// <summary>
        /// Raises when there is a change at the Smoke detectors sensor pin
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void FireDetectorPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            // System.Diagnostics.Debug.WriteLine("Fire detectors pin value: " + args.Edge.ToString());
            if (args.Edge == GpioPinEdge.RisingEdge)
            {
                string msg = string.Format("Fire alarm detected at {0}", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss"));
                NotifyUsingTwilio(msg);
                NotifyUsingAzureHub(msg);
                // Start a timer to turn the outdoor bell On later if the alarm is not cancelled
                alarmTimer = ThreadPoolTimer.CreateTimer(Alarm_Timer_IsOver, outdoorBellDelay);
            }
            else
            {
                if (alarmTimer != null)
                {
                    alarmTimer.Cancel();
                }
                // turn outdoor bell Off
                if (outdoorBellPin.Read() == GpioPinValue.High)
                {
                    outdoorBellPin.Write(GpioPinValue.Low);
                    NotifyUsingAzureHub(string.Format("Outdoor bell turned off at {0}", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));
                }
                string msg2 = string.Format("Fire alarm cancelled at {0}", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss"));
                NotifyUsingTwilio(msg2);
                NotifyUsingAzureHub(msg2);
            }
        }
        /// <summary>
        /// After a predetermined delay, outdoor bell is turned On
        /// </summary>
        /// <param name="timer"></param>
        private void Alarm_Timer_IsOver(ThreadPoolTimer timer)
        {           
            outdoorBellPin.Write(GpioPinValue.High);
            NotifyUsingAzureHub(string.Format("Outdoor bell turned on at {0}", DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));
            alarmTimer.Cancel();
        }
        /// <summary>
        /// If Azure gateway is open, send the message as a Toast
        /// </summary>
        /// <param name="message"></param>
        private void NotifyUsingAzureHub(string message)
        {
            System.Diagnostics.Debug.WriteLine(message);
            if (notifyUsingAzureHub)
            {
                NotificationGateway.AzureGateway.NotifyUsingToast(message);
            }
        }
        /// <summary>
        /// If Twilio gateway is open, send the message as a SMS
        /// </summary>
        /// <param name="message"></param>
        private void NotifyUsingTwilio(string message)
        {
            System.Diagnostics.Debug.WriteLine(message);
            if (notifyUsingTwilio)
            {
                NotificationGateway.TwilioGateway.NotifyUsingSMS(message);
            }
        }
    }
}

MotorizedValve class

C#
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Gpio;

namespace SafetyAgent
{
    /// <summary>
    /// Mechanical state of the valve
    /// </summary>
    public enum ValveState
    {
        /// <summary>
        /// Open, water can flow
        /// </summary>
        Open,
        /// <summary>
        /// Was closed but is now opening
        /// </summary>
        Opening,
        /// <summary>
        /// Closed, no water can flow
        /// </summary>
        Closed,
        /// <summary>
        /// Was open but is now closing
        /// </summary>
        Closing,
        /// <summary>
        /// No data
        /// </summary>
        Unknown
    }
    
    public sealed class MotorizedValve 
    {
        private ValveState state = ValveState.Unknown;
        private GpioPin mainWaterValveActivationPin; // When that pin is set to High, the motor turns the valve On
        private GpioPin mainWaterValveDesactivationPin; // When that pin is set to High, the motor turns the valve Off
        private GpioPin mainWaterValveOpenSensorPin; //When that pin is low, it means the valve is open
        private GpioPin mainWaterValveClosedSensorPin; //When that pin is low, it means the valve is closed
        private bool isConnected = false;
        public event EventHandler<ValveStateChangedInfo> ValveStateChanged;

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="openActuatorPinId">Pin where the motor is connected</param>
        /// <param name="closeActuatorPinId">Pin where the motor is connected </param>
        /// <param name="openSensorPinId">Pin that goes LOW when the motorized valve is Open</param>
        /// <param name="closedSensorPinId">Pin that goes LOW when the motorized valve is Closed</param>
        public MotorizedValve(int openActuatorPinId, int closeActuatorPinId, int openSensorPinId, int closedSensorPinId)
        {
            // Setup the actuator used to open the valve
            mainWaterValveActivationPin = GpioController.GetDefault().OpenPin(openActuatorPinId);
            mainWaterValveActivationPin.SetDriveMode(GpioPinDriveMode.Output);
            mainWaterValveActivationPin.Write(GpioPinValue.Low);
            // Setup the actuator used to close the valve
            mainWaterValveDesactivationPin = GpioController.GetDefault().OpenPin(closeActuatorPinId);
            mainWaterValveDesactivationPin.SetDriveMode(GpioPinDriveMode.Output);
            mainWaterValveDesactivationPin.Write(GpioPinValue.Low);
            // Setup the sensor used to confirm the valve is open
            mainWaterValveOpenSensorPin = GpioController.GetDefault().OpenPin(openSensorPinId);
            mainWaterValveOpenSensorPin.DebounceTimeout = new TimeSpan(0, 0, 0, 0, 50);  //Ignore changes in value of less than 50ms
            mainWaterValveOpenSensorPin.SetDriveMode(GpioPinDriveMode.Input);
            mainWaterValveOpenSensorPin.ValueChanged += MainWaterValveOpenSensorPin_ValueChanged;
            // Setup the sensor used to confirm the valve is closed
            mainWaterValveClosedSensorPin = GpioController.GetDefault().OpenPin(closedSensorPinId);
            mainWaterValveClosedSensorPin.DebounceTimeout = new TimeSpan(0, 0, 0, 0, 50);  //Ignore changes in value of less than 50ms
            mainWaterValveClosedSensorPin.SetDriveMode(GpioPinDriveMode.Input);
            mainWaterValveClosedSensorPin.ValueChanged += MainWaterValveClosedSensorPin_ValueChanged;
        }

        /// <summary>
        /// Connect to the motorized valve and gets current state
        /// </summary>
        /// <returns></returns>
        public bool Connect()
        {
            state = ValveState.Unknown;
            System.Diagnostics.Debug.WriteLine("Connecting to the motorized valve...");
            // Read current state
            if (mainWaterValveClosedSensorPin.Read() == GpioPinValue.Low && mainWaterValveOpenSensorPin.Read() == GpioPinValue.High)
            {
                State = ValveState.Closed;
                isConnected = true;
                return isConnected;
            }
            if (mainWaterValveClosedSensorPin.Read() == GpioPinValue.High && mainWaterValveOpenSensorPin.Read() == GpioPinValue.Low)
            {
                State = ValveState.Open;
                isConnected = true;
                return isConnected;
            }
            System.Diagnostics.Debug.WriteLine("Unable to connect to the motorized valve and to get a valid status...");
            isConnected = false;
            return isConnected;
        }
        /// <summary>
        /// Raises when there is a change at the motorized valve "open" sensor
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void MainWaterValveOpenSensorPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            // System.Diagnostics.Debug.WriteLine("Motorized valve open pin value has changed: " + args.Edge.ToString());
            if (args.Edge == GpioPinEdge.FallingEdge)
            {
                State = ValveState.Open;
                mainWaterValveActivationPin.Write(GpioPinValue.Low);
                mainWaterValveDesactivationPin.Write(GpioPinValue.Low);
            }
        }
        /// <summary>
        /// Raises when there is a change at the motorized valve "closed" sensor
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void MainWaterValveClosedSensorPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            // System.Diagnostics.Debug.WriteLine("Motorized valve closed pin value has changed: " + args.Edge.ToString());
            if (args.Edge == GpioPinEdge.FallingEdge)
            {
                State = ValveState.Closed;
                mainWaterValveActivationPin.Write(GpioPinValue.Low);
                mainWaterValveDesactivationPin.Write(GpioPinValue.Low);
            }
        }
        /// <summary>
        /// Get the current mechanical state
        /// </summary>
        public ValveState State
        {
            get
            {
                return state;
            }
            private set
            {
                ValveState previous = state;
                state = value;
                OnValveStateChanged(new ValveStateChangedInfo { previousState = previous, newState = state });
            }
        }
        /// <summary>
        /// When valve state changes
        /// </summary>
        /// <param name="e">Contains previous and current states</param>
        private void OnValveStateChanged(ValveStateChangedInfo e)
        {
            //System.Diagnostics.Debug.WriteLine(string.Format("Water valve was {0} and is now {1} at {2}", e.previousState.ToString(),
            //     e.newState.ToString(), DateTime.Now.ToString("dd-MMM-yyyy HH:mm:ss")));

            EventHandler <ValveStateChangedInfo> handler = ValveStateChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }
        /// <summary>
        /// True when the system can interact with the valve sensors
        /// </summary>
        public bool IsConnected
        {
            get
            {
                return isConnected;
            }
        }
        /// <summary>
        /// Turn the valve On
        /// </summary>
        public void Open()
        {
            if (!isConnected)
            {
                throw new Exception("System has no connection with the motorized valve");
            }
            if (state == ValveState.Closed || state == ValveState.Closing || state == ValveState.Unknown)
            {
                System.Diagnostics.Debug.WriteLine("Opening the motorized valve...");
                State = ValveState.Opening;
                mainWaterValveDesactivationPin.Write(GpioPinValue.Low);
                mainWaterValveActivationPin.Write(GpioPinValue.High);
            }
        }
        /// <summary>
        /// Turn the valve Off
        /// </summary>
        public void Close()
        {
            if (!isConnected)
            {
                throw new Exception("System has no connection with the motorized valve");
            }
            if (state == ValveState.Open || state == ValveState.Opening || state == ValveState.Unknown)
            {
                System.Diagnostics.Debug.WriteLine("Closing the motorized valve...");
                State = ValveState.Closing;
                mainWaterValveDesactivationPin.Write(GpioPinValue.High);
                mainWaterValveActivationPin.Write(GpioPinValue.Low);
            }
        }
    }
    /// <summary>
    /// Info regarding previous and current valve state 
    /// </summary>
    public sealed class ValveStateChangedInfo
    {
        public ValveState previousState { get; set; }
        public ValveState newState { get; set; }
    }
}

Send SMS from your Raspberry using Twilio

C#
See https://www.twilio.com/blog/2011/09/using-the-twilio-rest-api-in-windows-8-metro-style-applications.html
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;

namespace NotificationGateway
{
    public class TwilioGateway
    {
        /// <summary>
        /// Sens an SMS using Twilio cloud based service
        /// See https://www.twilio.com/blog/2011/09/using-the-twilio-rest-api-in-windows-8-metro-style-applications.html for a detailed explanation
        /// </summary>
        /// <param name="content"></param>
        public static async void NotifyUsingSMS(string message)
        {
            const string from = "";
            const string to = "15819953112";                  
            var accountSid = "AC0czzzzzzzzzz4fe78c990f";  // see your Twilio account on line
            var authToken = "b9zzzzzzzzzzzzd8b4f276f9"; // see your Twilio account on line
            var targeturi = "https://api.twilio.com/2010-04-01/Accounts/{0}/SMS/Messages";

            var client = new System.Net.Http.HttpClient();
            client.DefaultRequestHeaders.Authorization = CreateBasicAuthenticationHeader(accountSid, authToken);

            var content = new StringContent(string.Format("From={0}&amp;To={1}&amp;Body={2}", from, to, message));
            content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");

            var response = await client.PostAsync(string.Format(targeturi, accountSid), content);
            if (response.IsSuccessStatusCode)
            {
                System.Diagnostics.Debug.WriteLine(string.Format("SMS sent to`{0}", to));
            }
            else
            {
                System.Diagnostics.Debug.WriteLine(string.Format("Unable to send SMS. Error is {0} - {1}", response.StatusCode, response.ReasonPhrase));
            }            
        }
        /// <summary>
        /// Twilio uses HTTP Basic Authentication. We need therefore to generate an authentication header
        /// </summary>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        private static AuthenticationHeaderValue CreateBasicAuthenticationHeader(string username, string password)
        {
            return new AuthenticationHeaderValue("Basic",
                System.Convert.ToBase64String(System.Text.UTF8Encoding.UTF8.GetBytes(string.Format("{0}:{1}", username, password))));
        }
    }
}

Send "Toast" notifications using Azure Notification Services

C#
Using full .Net framework, it takes 3 lines of code. With the limited framework we have on Windows 10 Core it is a little bit more work. Here it is...
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;

namespace NotificationGateway
{
    public class AzureGateway
    {
        public static async void NotifyUsingToast(string content)
        {      
            // "Endpoint=sb://iotcontest.servicebus.windows.net/;SharedAccessKeyName=DefaultFullSharedAccessSignature;SharedAccessKey=jSF6Szzzzzzz82r9avDo5PkECg2XVTU0D4Isi/4=";
            string baseAddress = "https://iotcontest.servicebus.windows.net/";
            string queueTopicName = "iotcontestnotificationhub";          
            string token = GetSASToken(baseAddress, "DefaultFullSharedAccessSignature", "jSF6SYX2M1OzzzzzzzzzzzzzzzzzzzVTU0D4Isi/4=");
            string body = string.Format("<toast><visual><binding template=\"ToastText01\"><text id=\"1\">{0}</text></binding></visual></toast>", content);

            Dictionary<string, string> props = new Dictionary<string, string>();
            props.Add("X-WNS-Type", "wns/toast");
            props.Add("ServiceBusNotification-Format", "windows");

            await SendWNSNativeNotification(baseAddress, queueTopicName, token, body, props);                                                  
        }

        /// <summary>
        /// Sends a WNS native notification through a notification hub.  Thanks to http://blogs.recneps.org/?tag=/Service-Bus&page=1 for the hints !
        /// </summary>
        /// <param name="baseAddress">Looks like https://{namespace}.servicebus.windows.net/</param>
        /// <param name="queueTopicName"> Notification Hub name </param>
        /// <param name="token">Token generated as specified in Shared Access Signature Authentication with Service Bus, or Service Bus authentication and authorization with Microsoft Azure Active Directory Access Control (also known as Access Control Service or ACS).</param>
        /// <param name="body">an XML document as specified by WNS. If the notification is of type wns/raw, the body is any text up to 5Kb. </param>
        /// <param name="properties"></param>   
        private static async System.Threading.Tasks.Task<HttpResponseMessage> SendWNSNativeNotification(string baseAddress, string queueTopicName, string token, string body, IDictionary<string, string> properties)
        {
            string fullServiceBusURI = baseAddress + queueTopicName + "/messages" + "?timeout=60&api-version=2015-01";
            return await SendViaHttp(token, body, properties, fullServiceBusURI, HttpMethod.Post);
        }

        /// <summary>
        /// Send notification to Azure REST API over http
        /// </summary>
        /// <param name="token"></param>
        /// <param name="body"></param>
        /// <param name="properties"></param>
        /// <param name="fullAddress"></param>
        /// <param name="httpMethod"></param>
        /// <returns></returns>
        private static async System.Threading.Tasks.Task<HttpResponseMessage> SendViaHttp(string token, string body, IDictionary<string, string> properties, string fullAddress, HttpMethod httpMethod)
        {
            HttpClient webClient = new HttpClient();
            HttpRequestMessage request = new HttpRequestMessage()
            {
                RequestUri = new Uri(fullAddress),
                Method = httpMethod,

            };
            webClient.DefaultRequestHeaders.Add("Authorization", token);

            if (properties != null)
            {
                foreach (string property in properties.Keys)
                {
                    request.Headers.Add(property, properties[property]);
                }
            }
            request.Content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("", body) });
            return await webClient.SendAsync(request);            
        }

        /// <summary>
        /// Compute a SAS token
        /// The token is constructed from the information extracted from the connection string and the current request that has to be authenticated.
        /// </summary>
        /// <param name="baseAddress">Looks like https://{namespace}.servicebus.windows.net/</param>
        /// <param name="SASKeyName">To send notifications, you usually use "DefaultFullSharedAccessSignature"</param>
        /// <param name="SASKeyValue"></param>
        /// <returns></returns>
        private static string GetSASToken(string baseAddress, string SASKeyName, string SASKeyValue)
        {
            string targetURI = WebUtility.UrlEncode(baseAddress.ToLower());
            TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
            string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + 3600);
            string stringToSign = WebUtility.UrlEncode(baseAddress) + "\n" + expiry;            
            string hash = HmacSha256(SASKeyValue, stringToSign);
            string sasToken = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
                 targetURI, WebUtility.UrlEncode(hash), expiry, SASKeyName);
            return sasToken;
        }

        public static string HmacSha256(string secretKey, string value)
        {
            // Move strings to buffers.
            var key = CryptographicBuffer.ConvertStringToBinary(secretKey, BinaryStringEncoding.Utf8);
            var msg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8);

            // Create HMAC.
            var objMacProv = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
            var hash = objMacProv.CreateHash(key);
            hash.Append(msg);
            return CryptographicBuffer.EncodeToBase64String(hash.GetValueAndReset());
        }

    }
}

Register your app to Azure in order to receive "toasts" later

C#
Create an app that will run on your PC/tablet... and then add this piece of code and call it from the OnLaunched method
/// <summary>
        ///  Retrieves the ChannelURI for the app from WNS, and then registers that ChannelURI with your notification hub.
        /// </summary>
        private async void InitNotificationsAsync()
        {
            var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();

            var hub = new NotificationHub("iotcontestnotificationhub", "Endpoint=sb://iotcontest.servicebus.windows.net/;SharedAccessKeyName=DefaultListenSharedAccessSignature;SharedAccessKey=Dk4rAV3xxxxxxxxxxxxefvDnKQpIQfR80bVc=");
            var result = await hub.RegisterNativeAsync(channel.Uri);

            // Displays the registration ID so you know it was successful
            if (result.RegistrationId != null)
            {
                var dialog = new MessageDialog("Registration successful: " + result.RegistrationId);
                dialog.Commands.Add(new UICommand("OK"));
                await dialog.ShowAsync();
            }

        }

Credits

Philippe Libioulle

Philippe Libioulle

7 projects • 49 followers

Comments