The Visual Studio 2017 Connected Service for Azure IoT Hub has received an update a couple of months ago.
This update had some visual updates and now supports a Singleton pattern for the Device client too.
But it also included support for both Device twins and Direct Methods. The latter feature looks a lot like the Commands method but there are some fundamental changes.
Yes, both solutions (Command and Direct Method) can execute code on a remote IoT Hub client. But the remote method just passes a message to the client. The Direct method can pass a message in a certain context. It calls a specific method (a client can have multiple methods registered) and passes the JSON parameter.
If you execute a Command, it feels like fire-and-forget. There is no descriptive response. But the caller of a Direct Method can wait until a response is accepted and a JSON value is returned.
Let’s check out Direct Methods.
It all starts with that Connected Service extension in Visual Studio 2017.
Generating simple Direct Methods
If you create an IoTHub client for a certain device, you will be asked which features you want to have it supported:
Direct Methods and Device twins are optional.
After creation, three methods are added to the generated client:
private static async Task<MethodResponse> OnSampleMethod1Called(MethodRequest methodRequest, object userContext) { Console.WriteLine("SampleMethod1 has been called"); return new MethodResponse(200); } private static async Task<MethodResponse> OnSampleMethod2Called(MethodRequest methodRequest, object userContext) { Console.WriteLine("SampleMethod2 has been called"); return new MethodResponse(200); } public static async Task RegisterDirectMethodsAsync() { CreateClient(); Console.WriteLine("Registering direct method callbacks"); await deviceClient.SetMethodHandlerAsync("SampleMethod1", OnSampleMethod1Called, null); await deviceClient.SetMethodHandlerAsync("SampleMethod2", OnSampleMethod2Called, null); }
Yes, two methods are added which have to be registered on the client, every time the client connects.
So, at a certain moment in time, you decide, when you are ready to execute methods, call the third ‘Register’ method.
Actually, the two methods are just behaving like Event handlers for the registered methods.
Why is my registration failing?
It could be possible that you get an exception like this:
It says the method or operation is not implemented. Probably, what it actually means is that the protocol you selected (it’s AMPQ, isn’t it?) does not support Direct Methods.
Note: You will have to wait until Microsoft does support it OR you will have to switch over to MQTT.
Executing a Direct Method using the Azure Portal
You can execute a method by hand using the Azure Portal.
Just go to the IoT Hub resource, check the Device Explorer pane and select a device. Next to the device credentials, the option to call the Direct Methods appears also:
You call a method by just filling in the name and optionally providing a JSON parameter:
Executing this call results in the execution of the related method on the client:
See how the JSON parameter is passed.
Extending the Direct Method on the client
Let’s extend the Direct Method with passing a response result:
private static async Task<MethodResponse> OnSampleMethod1Called(MethodRequest methodRequest, object userContext) { Console.WriteLine("SampleMethod1 has been called"); var responseJson = "{\"result\":42}"; var response = Encoding.ASCII.GetBytes(responseJson); return new MethodResponse(response, 200); }
So when the method is executed, a bit of JSON is returned.
Let’s call the method using an Azure Function. I used an Azure Function triggered manually:
using System; using Microsoft.Azure.Devices; using System.Text; using Newtonsoft.Json; public static async Task Run(string input, TraceWriter log) { log.Info($"C# manually triggered function called with input: {input}"); var connectionString = "HostName=codecamp42-ih.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=hveSbfuRt9yrv9kjjQa98KUiYyrjUAIQuPh1hJETjp8="; var serviceClient = ServiceClient.CreateFromConnectionString(connectionString); var cloudToDeviceMethod = new CloudToDeviceMethod("SampleMethod1"); cloudToDeviceMethod.SetPayloadJson("{aa:1, bb:\"two\"}"); var response = await serviceClient.InvokeDeviceMethodAsync( "EmulatedDeviceUwp", cloudToDeviceMethod); var json = response.GetPayloadAsJson(); log.Info($"Result '{json}' '{response.Status}'"); }
Note: You probably need to add a ‘project.json’ file containing the name of the used NuGet packages:
{ "frameworks": { "net46": { "dependencies": { "Microsoft.AspNet.WebApi.Client": "5.2.3", "Microsoft.AspNet.WebApi.Core": "5.2.3", "Microsoft.Azure.Amqp": "2.0.6", "Microsoft.Azure.Devices": "1.4.1", "Newtonsoft.Json": "10.0.3" } } } }
If you forget to register the methods on your device, you will see this:
2017-10-30T20:47:15.855 C# manually triggered function called with input: 2017-10-30T20:47:15.949 Exception while executing function: Functions.ManualTriggerCSharp1. Microsoft.Azure.WebJobs.Script: One or more errors occurred. Microsoft.Azure.Devices: Device {"Message":"{\"errorCode\":404103,\"trackingId\":\"68b36c0200194d59bf90ad40bed61f33-G:3-TimeStamp:10/30/2017 20:47:15\",\"message\":\"Timed out waiting for device to connect.\",\"info\":{\"timeout\":\"00:00:00\"},\"timestampUtc\":\"2017-10-30T20:47:15.7554008Z\"}","ExceptionMessage":""} not registered. 2017-10-30T20:47:15.965 Function completed (Failure, Id=da713dda-937d-46b3-a7d7-7490e3e13518, Duration=115ms)
You get this “Timed out waiting for device to connect”. So register your methods on the clients!
you get the correct outcome:
2017-10-30T20:50:39.803 C# manually triggered function called with input: 2017-10-30T20:50:39.975 Result '{"result":42}' '200' 2017-10-30T20:50:39.975 Function completed (Success, Id=265af489-1638-427d-a5a2-a8f77b6aab6a, Duration=179ms)
So you see the result of the Direct Method.
Conclusion
So it’s true, Direct Methods are very explicit commands which can be called only when the Client is online and has registered the method you want to call.