Azure Percept Audio – Webhooks, Azure Functions, IoT Hub, Raspberry Pi and Home Automation

In the previous post we created our own custom command in Speech Studio to turn music on and off using the Azure Percept.

Azure Percept Audio – Custom Command Demo

In this post, we’ll take this further by delving into the world of Home Automation.

We’ll go through how to connect our command up to an Azure Function via the Command’s WebHook.

From the Azure Function, we’ll connect to an Azure IoT Hub. We can then invoke a Direct Method on a Connected Raspberry Pi.

This Direct Method will instruct the Raspberry Pi to turn on and off a Desk Lamp using a Relay.

A Recap of Custom Commands

Custom Commands allow us to create a command set that the Azure Percept can react to.

In the previous post, we created a custom command that allowed the Percept to react to a command to turn music on or off.

Custom Commands are primarily comprised of a set of example sentences and a completion operation.

In the previous post our completion operation was to simply respond to the user in voice that the music was turned on or off.

We made good use of Parameters so we could have a single command turn the music both on and off again.

What we’ll be doing

The following diagram gives us an overview of the major component parts of the solution;

Azure Percept Home Automation
Azure Percept Home Automation

We have the Azure Percept Audio in the top left. This of course is connected to the Carrier Board which I’ve not shown here.

The Percept makes use of the Speech Service to process spoken word detected through the Percept Audio SoM.

Making use of a Webhook, the Speech Service then calls in to an HTTP triggered Azure Function.

This Azure function takes any parameters passed by the Speech Service and then connects to an Azure IoT Hub using a Service Policy.

The IoT Hub has a Device registered to it which a Raspberry Pi running .NET 5 and C#9 is connected to.

The Azure Function invokes a Direct Method on the Raspberry Pi through the IoT Hub when it’s triggered, passing in any parameters which have been passed in from the Speech Service.

The Pi then actions the Direct Method and sets a GPIO pin connected to a relay which turns the mains power on to a Desk Lamp.

Creating an Azure Function App

Once a command has been recognised by the Percept we can configure a done action.

In the previous post we configured this “Done” action as a spoken response to the user.

However, we can also configure the command to call a webhook before then returning the result of that webhook back to the user.

Before we can configure the webhook however, we need the address for the webhook to call into.

If we head to the portal and create a new Azure Function App;

Azure Function App - Create
Azure Function App – Create

Hitting the blue create button, we’re taken to the first page of the Create Function App process;

Azure Function - Create Options
Azure Function – Create Options

Here we can choose our Subscription, Resource group and give our Function a unique Name.

We can leave the Publish type as “Code”, set the Runtime Stack to “.NET”, the Version to “3.1” and the region to either “West US” or “West Europe” to match that of our Percept Resources.

We can leave the options on the remaining tags as their default, we we can just hit the “Review + Create” button.

Check the options you’ve selected before hitting the blue “Create” button;

Azure Function - Confirm Create
Azure Function – Confirm Create

The Function App will then begin creating and complete some time later;

Once the Function has finished creating, we can navigate to our new Azure Function by hitting the blue “Go to resource” button;

Azure Function - Created
Azure Function – Created

Creating an Azure Function using VS Code

We’ll now use VS Code to create our Azure Function.

Before we do this, we need to create a directory for our Azure Function to live in, so go ahead and find a nice location for your azure function. We can then open this folder in VS Code, either by right clicking in empty space and clicking “Open with VS Code”, or opening VS Code and opening the folder directly;

VS Code - Open Folder
VS Code – Open Folder

Next we need to install the Azure plugin for VS Code if you don’t have it already;

Azure Tools for VS Code
Azure Tools for VS Code

You will need to sign in to Azure if you haven’t already.

Once you’re signed in to Azure, all of your resources will be listed in the left pain when you click the Azure Icon;

Azure Tools - Resources
Azure Tools – Resources

Next we need to create our Azure Function. We can do that from the VS Code Tool Palette by pressing ctrl+shift+p, then typing Azure Function in the search box;

Azure Function - Create Function
Azure Function – Create Function

We can then select the first item in the list “Azure Functions: Create Function“.

We’ll then see the following prompt: “The select folder is not a function project. Create new project?”

Azure Function – Create Project

We will then be prompted for which language we’d like to use;

Azure Function - Language Choice
Azure Function – Language Choice

We’ll choose “C#”. Next we’ll be asked which .NET runtime we want to use;

Azure Function - .NET Runtime Choice
Azure Function – .NET Runtime Choice

We’ll select “.NET Core 3 LTS”. We’ll then have a choice of Trigger for the Function;

Azure Function - Trigger Choice
Azure Function – Trigger Choice

We’ll select “HTTP trigger”. We’ll then have a chance to name our Function;

Azure Function - Trigger Name
Azure Function – Trigger Name

We’ll choose “PerceptTrigger” for our Trigger. Next, we can give our function a namespace;

Azure Function - Namespace
Azure Function – Namespace

We’ll use “Percept.Function“.

Finally we can choose the type of Authentication we want to use for our function;

Azure Function - Authentication
Azure Function – Authentication

We’ll choose “Function“, which will allow us to pass a token to the function to authenticate.

The Function will then be created;

Azure Function – Created

With that, the basics of our function has been created;

Azure Function - Original Code
Azure Function – Original Code

Adding the Azure IoT Hub SDK

Next, as we’re going to be interacting with an IoT Hub, we need to add the IoT Hub SDK.

We can do this from the terminal. Pressing ” ctrl+shift+’ ” (Control + Shift + Apostrophe), will launch the terminal;

VS Code - Terminal
VS Code – Terminal

We can install the IoT Hub SDK by running;

dotnet add package Microsoft.Azure.Devices
VS Code - Install IoT Hub SDK
VS Code – Install IoT Hub SDK

Building the Azure Function Code

Replace the contents of the PerceptTrigger.cs file with the following block of code;

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Azure.Devices;

namespace PerceptIoT.Function
{
    public static class PerceptTrigger
    {

        private static ServiceClient serviceClient;
        
        // Connection string for your IoT Hub
        private static string connectionString = System.Environment.GetEnvironmentVariable("IotHubConnectionString");
        // Device ID for Raspberry Pi
        private static string deviceID = System.Environment.GetEnvironmentVariable("DeviceID");

        [FunctionName("PerceptTrigger")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            log.LogInformation(requestBody);

            dynamic data = JsonConvert.DeserializeObject(requestBody);

            log.LogInformation($"The State was: {data.state} ");
            
            string responseMessage = $"This HTTP triggered function executed successfully. The State was {data.state}";

            serviceClient = ServiceClient.CreateFromConnectionString(connectionString);

            await InvokeMethodAsync(Convert.ToString(data.state));

            serviceClient.Dispose();

            return new OkObjectResult(responseMessage);
        }

         // Invoke the direct method on the device, passing the payload
        private static async Task InvokeMethodAsync(string state)
        {
            var methodInvocation = new CloudToDeviceMethod("ControlLight")
            {
                ResponseTimeout = TimeSpan.FromSeconds(30),
            };

            methodInvocation.SetPayloadJson("{\"state\": \"" + state + "\"}");

            // Invoke the direct method asynchronously and get the response from the simulated device.
            var response = await serviceClient.InvokeDeviceMethodAsync(deviceID, methodInvocation);

            Console.WriteLine($"\nResponse status: {response.Status}, payload:\n\t{response.GetPayloadAsJson()}");
        }
    }
}

Remember to change the “PerceptTrigger” references in the file to whatever you chose for your Function name.

Next we need to add a local debug setting for our IoT Hub Connection string. Replace the contents of your local.settings.json file with the following;

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "IotHubConnectionString": "ENTER YOUR IOT HUB SERVICE LEVEL ACCESS POLICY CONNECTION STRING",
    "DeviceID": "ENTER YOUR RASPBERRY PI DEVICE ID"
  }
}

Deploying the Function App

We’re now ready to deploy our Function. Using the Azure Tools Extension, in the “Functions” section, if you expand the subscription you created your Function App within, then right click we’ll be given a set of options;

VS Code - Deploy Azure Function
VS Code – Deploy Azure Function

If we select the “Deploy to Function App” option, we can begin deploying our new Azure Function.

We’ll be asked to confirm if we want to deploy the Function App;

VS Code - Deploy Function Confirmation
VS Code – Deploy Function Confirmation

Hitting “Deploy” will begin the process of deploying the Function;

VS Code - Deploying Function
VS Code – Deploying Function

Once the process is complete, our new Azure Function will appear in the functions list;

VS Code - Azure Function in List
VS Code – Azure Function in List

Adding an IoT Device to the IoT Hub

As we’ll be deploying code to a Raspberry Pi which will connect to our IoT Hub, and in turn allow the Azure Function to invoke a Direct Method

You will have created an IoT Hub as part of the Percept setup experience. We can find all of the IoT Hubs we’ve created using the portal.

Navigating to the Overview Page of our IoT Hub, we can click the IoT Devices Item;

IoT Hub - IoT Devices Menu Item
IoT Hub – IoT Devices Menu Item

We’ll then be shown the list of IoT Devices we currently have registered to our IoT Hub;

IoT Hub - IoT Device List
IoT Hub – IoT Device List

We can add a new IoT Device by hitting the “+ New” item in the toolbar;

IoT Hub - Create new IoT Device
IoT Hub – Create new IoT Device

We can name our device “devicecontrol”, leave all the options at their defaults and hit the blue “Save” button;

IoT Hub - Device List
IoT Hub – Device List

We’ll then see our new device listed in the Device Registry. You may need to hit the “Refresh” button in the toolbar.

Adding an IoT Hub Shared Access Policy

We now need a way for our Azure Function to connect to our Percept IoT Hub.

If we return to the overview page of our IoT Hub, we can add a new Shared Access Policy for our Function App to connect to;

IoT Hub - Shared Access Policies Menu Item
IoT Hub – Shared Access Policies Menu Item

We’ll now be shown all of the existing Built-In Shared Access Policies for our IoT Hub;

IoT Hub - Shared Access Policy List
IoT Hub – Shared Access Policy List

Our Function will need a policy with “Service Connect” access. We could use the built in “Service” policy, but I prefer to create a new policy for services to use.

If we click the “+ Add shared access policy” in the toolbar, we can add our own policy;

IoT Hub - Add Shared Access Policy
IoT Hub – Add Shared Access Policy

We can name our Shared Access Policy whatever makes sense, I’ve chose “perceptfunctionpolicy” here.

We can then select the “Service Connect” Permission and hit the blue “Add” button to create the Policy.

With the Policy created, it will appear in the list of Shared Access Policies;

IoT Hub - Added Shared Access Policy
IoT Hub – Added Shared Access Policy

If we click on our new Shared Access Policy, we can grab the Primary Connection string we need to allow our Azure Function to connect to the IoT Hub;

IoT Hub - Service Connect Shared Access Policy
IoT Hub – Service Connect Shared Access Policy

Adding a Function App Key

Now that we’ve grabbed our Shared Access Policy, we can add this along with the IoT Device ID of the Raspberry Pi IoT Device we added to the Function Keys for our Azure Function.

If we navigate to the overview page of our Function App;

Azure Function App - Functions Menu Item
Azure Function App – Functions Menu Item

We can navigate to our list of Azure Functions by clicking the “Functions” menu item on the left;

Azure Function App - Azure Function
Azure Function App – Azure Function

We’ll now see the Azure Function we created from VS Code, clicking on this will take us to the details page;

Azure Function - Function Keys Menu Item
Azure Function – Function Keys Menu Item

We can then click the Function Keys menu item so we can add our required information;

Azure Function - New Function Key Button
Azure Function – New Function Key Button

We can add a new Function Key by pressing the “+ New function key” button at the top of the page;

Azure Function - Connection String Key
Azure Function – Connection String Key

We can add a Function Key for the Service Shared Access Policy Connection String we added earlier and press the blue “OK” button.

We can repeat that for our IoT Device ID;

Azure Function - IoT Device ID Key
Azure Function – IoT Device ID Key

Our new Function Keys should now be shown in the list;

Azure Function - Function Keys
Azure Function – Function Keys

Finding the Azure Function URL

We’ll be invoking our function using an HTTP trigger. As such, we’ll need the Azure Function URL.

If we return to the overview page of our function;

Azure Function - Overview Page
Azure Function – Overview Page

We can grab the Azure Function URL by hitting the “Get Function URL” button;

Azure Function - Function URL
Azure Function – Function URL

We can press the Copy button to the right of the URL.

Keep this URL safe somewhere for now as we’ll need it when we create our Web Endpoint next.

Adding a Web Endpoint to the Custom Command

We can now return to our Custom Command project. The easiest way to do this is via Percept Studio;

https://portal.azure.com/#blade/AzureEdgeDevices/Main/overview

We can navigate to Speech and click on the Commands tab;

Percept Studio - Custom Commands
Percept Studio – Custom Commands

I’ve gone ahead and created a new Custom Command for this example, but you can absolutely use the PlayMusic-speech project we created in the previous post.

Clicking on our custom command will take us to the Speech Project;

Speech Studio – Example Sentences

I’ve created a new Command named “TurnLightOnOff” with the following Example Sentences;

Turn the light {OnOff}
Turn the lights {OnOff}
Turn the lamp {OnOff}
Turn {OnOff} the light
Turn {OnOff} the lights
Turn {OnOff} the lamp
Lights {OnOff}
Lamp {OnOff}
Switch {OnOff} the light
Switch {OnOff} the lights
Switch {OnOff} the lamp
Switch the light {OnOff}
Switch the lights {OnOff}
Switch the lamp {OnOff}

What we need to do now is to create a new “Web endpoint” for our command to call use when it’s executed;

Speech Studio - Web endpoints Menu Item
Speech Studio – Web endpoints Menu Item

Clicking on the “Web endpoints” menu item on the left will take us to the Web Endpoints page;

Speech Studio – Web Endpoints

We can now click on the “+ New web endpoint” button at the top of the centre panel to add a new endpoint;

Speech Studio - New Web endpoint Name
Speech Studio – New Web endpoint Name

We’ll name our Endpoint “LightControl” and hit the blue “Create” button;

Speech Studio - Webhook URL
Speech Studio – Webhook URL

We can now paste in our Function URL from the previous step into the “URL” text box. We can cut off the section beginning “?code=…” as we’ll be passing that in as a query parameter.

Speech Studio - Webhook Method
Speech Studio – Webhook Method

We need to set the “Method” option to “POST” here, as we’ll be passing the value of our “OnOff” parameter to our Azure Function in the body of the request.

We can now add the authentication code we cut from the end of the Function URL as a query parameter;

Speech Studio - Add Query Parameter Button
Speech Studio – Add Query Parameter Button

We can hit the “+ Add a query Parameter” button at the bottom of the right hand panel;

Speech Studio - Webhook Query Parameter
Speech Studio – Webhook Query Parameter

We enter “code” as the “Key” and we can paste in the Authentication Code in the “Value” box.

Remember to remove the “?code=” part in front of the Authentication code, so it’s just the random collection to letters and numbers.

We can save the parameter with the blue “Save” button.

Using the new Web Endpoint

We can now make use of our new Web Endpoint within our TurnLightOnOff Custom Command.

If we navigate to our TurnLightOnOff Custom command and to the “Done” section;

Speech Studio - Command Add Action Button
Speech Studio – Command Add Action Button

Make sure to remove any existing actions if you’re reusing anything from a previous post.

We can add an action for our Web Endpoint by hitting the “+ Add an action” button at the bottom of the right hand pane;

Speech Studio - New Action Call Web Endpoint Option
Speech Studio – New Action Call Web Endpoint Option

Selecting the “Call web endpoint” option will show the Call Web Endpoint dialog;

Speech Studio - New Action Select Endpoint
Speech Studio – New Action Select Endpoint

We can select our new “LightControl” Web Endpoint from the “Endpoint” dropdown.

Next we need to pass in the value for our “OnOff” paramater so that the Azure Function can pass this to the Raspberry Pi via the IoT Hub and a Direct Method. If we replace the “Body content (JSON)” section with the following;

{
  "state": "{OnOff}"
}

Speech Studio - Call Web Endpoint On Success Tab
Speech Studio – Call Web Endpoint On Success Tab

We now need to feedback a spoken response to the user once the Web Endpoint has been called. If we hit the “On Success (Required)” tab at the top;

Speech Studio - Call Web Endpoint On Success
Speech Studio – Call Web Endpoint On Success

We can select the “Send speech response” option from the “Action to execute” dropdown;

Speech Studio - Call Web Endpoint On Success Message
Speech Studio – Call Web Endpoint On Success Message

We can return the following response;

The light has been turned {OnOff}

We can then repeat this for the “On Failure (Required)” tab;

Speech Studio - Call Web Endpoint On Failure Response
Speech Studio – Call Web Endpoint On Failure Response

Where we can enter the following response in the “Simple Response Editor”;

Failed to turn the light {OnOff}

We can now save our Done Action by hitting the blue “Save” button;

Speech Studio - Save Completion Rule
Speech Studio – Save Completion Rule

Finally, we can hit the “Save” button, followed by the “Train” button and finally the Publish Button;

Speech Studio - Custom Command Published
Speech Studio – Custom Command Published

We can then return to Azure Percept Studio and make sure our custom Command is assigned to our Percept;

Azure Percept Studio - Assign Custom Command
Azure Percept Studio – Assign Custom Command

Select your Custom Command and hit the “Assign” button in the toolbar;

Speech Studio - Deploy to Device
Azure Percept Studio – Deploy to Device

Select your IoT Hub and Percept Device and hit the blue “Save” button to Deploy to your Device;

Azure Percept Studio - Custom Command Assigned
Azure Percept Studio – Custom Command Assigned

Raspberry Pi Code

Next we’ll get the Raspberry Pi set up ready to connect to the IoT Hub and receive the Direct Method Invocation from our Azure Function.

The first thing you’ll need to do is get your Raspberry Pi set up to run .NET 5. I’ve created a post around on how to that;

Install .NET 5 on a Raspberry Pi

Once you’ve got the Pi Setup, we can create the project. I’d do this either in your home directory, or if you’ve set up a SAMBA share, then that directory will be best.

We create a new .NET 5 console application with;

dotnet new console -o device_code
cd device_code

We can now add the nuget packages for both GPIO Control and Azure IoT Hub Client;

dotnet add package System.Device.GPIO
dotnet add package Microsoft.Azure.Devices.Client

Next paste the following code over your Program.cs file;

using System;
using System.IO;
using System.Text;
using Newtonsoft.Json;
using System.Threading.Tasks;
using System.Device.Gpio;
using Microsoft.Azure.Devices.Client;

namespace device_code
{
    class Program
    {
        static string DeviceConnectionString = "ENTER YOU DEVICE CONNECTION STRING";
        static int lightPin = 32;

        GpioController controller;
                                               
        static async Task Main(string[] args)
        {

            GpioController controller = new GpioController(PinNumberingScheme.Board);
            
            controller.OpenPin(lightPin, PinMode.Output);
            controller.Write(lightPin, PinValue.High);
            
            DeviceClient Client = DeviceClient.CreateFromConnectionString(DeviceConnectionString, Microsoft.Azure.Devices.Client.TransportType.Mqtt);

            await Client.SetMethodHandlerAsync("ControlLight", (MethodRequest methodRequest, object userContext) => {

                Console.WriteLine("IoT Hub invoked the 'ControlLight' method.");
                Console.WriteLine("Payload:");
                Console.WriteLine(methodRequest.DataAsJson);

                dynamic data = JsonConvert.DeserializeObject(methodRequest.DataAsJson);

                if (data.state == "on")
                {
                    controller.Write(lightPin, PinValue.Low);
                }
                else
                {
                    controller.Write(lightPin, PinValue.High);
                }

                var responseMessage = "{\"response\": \"OK\"}";

                return Task.FromResult(new MethodResponse(Encoding.ASCII.GetBytes(responseMessage), 200));

            }, null);

            while (true)
            {
                
            }

        }
    }
}

We now need to replace the placeholder for the IoT Device Connection String with the one from the device we created earlier.

If we return to the portal, to our IoT Hub IoT Device Registry Page and to the Device Details page for our “devicecontrol” device;

IoT Hub - Device Details
IoT Hub – Device Details

We can copy the Primary Connection string and then paste that into the Program.cs file where we have “ENTER YOU DEVICE CONNECTION STRING”.

Raspberry Pi Circuit

The hardware side of this solution centres around a Raspberry Pi. We make use of a Relay to turn the mains supply to a desk lamp on and off.

DISCLAIMER

As a word of warning. The mains relay part of solution should only be attempted if you are experienced in handling mains voltages, as this can be very dangerous if the correct safety precautions aren’t followed.

Needless to say, I can’t take any responsibility for damage or harm as a result of the following instructions!

With that out of the way, the following is the circuit diagram for the Raspberry Pi, Relay and Lamp;

Raspberry Pi – Circuit

The Raspberry Pi is connected as follows;

Pi Pin NumberFunctionRelay Pin LabelColour
25VVCCRed
20GNDGNDBlack
32Relay Control (GPIO 12)IN1Green
Raspberry Pi – Connections

I’ve actually enclosed my whole circuit (aside from the Pi), in a patress to keep the mains side sealed away;

Raspberry Pi – Enclosure

I’m using an Elegoo 4 way Relay unit… You can find this at Amazon here;

https://amzn.to/2UGX6pT

Testing the System

With all of these parts hooked together, you should be in a position to test turning the light on and off!

First we need to run the software on the Raspberry Pi.

From the directory you created the “device_code” project, run the code with;

dotnet run

Once the code is running, you can try turning the light on and off using your Percept.

The Raspberry Pi terminal window should feedback the instructions it recieves;

Raspberry Pi Device Code in Operation
Raspberry Pi Device Code in Operation

If everything has gone according to plan, you should now have a working system!

Custom Command in Action