Azure Percept DK Connection to IoT Central instead/With IoT Hub.

vinit sawant 11 Reputation points
2022-07-08T10:57:21.47+00:00

Hello, I am looking for a way by which we can connect the Azure Percept Edge Device to Iot Central instead of the Iot Hub which we can do in the OOBE Setup in the initial stage.
Or lets say we have the Percept connected to an Iot Hub and now I want to make the telemetry of IoT Hub to be consumed by an Iot Central Application.
How can we achieve this?

Azure Percept
Azure Percept
A comprehensive Azure platform with added security for creating edge artificial intelligence solutions.
72 questions
Azure IoT Edge
Azure IoT Edge
An Azure service that is used to deploy cloud workloads to run on internet of things (IoT) edge devices via standard containers.
581 questions
Azure IoT Central
Azure IoT Central
An Azure hosted internet of things (IoT) application platform.
365 questions
Azure IoT Hub
Azure IoT Hub
An Azure service that enables bidirectional communication between internet of things (IoT) devices and applications.
1,222 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Roman Kiss 2,246 Reputation points
    2022-07-10T16:30:53.74+00:00

    Hi,

    There is no direct integration for forwarding a telemetry data from the Azure IoT Hub to the Azure IoT Central App. However, you can use the Azure Event Grid for this integration like is shown in the following screen snippet:

    219301-image.png

    All "magic work" is done in the IoT Subscriber (HttpTrigger function) such as a device provisioning, connecting and sending the device telemetry data included its properties to the Azure IoT Central App. Based on the telemetry system/application properties within the CloudEvents data object, the IoT Subscriber can manage delivery a device telemetry to the target included assigning a device model.

    The following code snippet shows an example of the IoT Subscriber implementation:

    function.json:

    {  
      "bindings": [  
        {  
          "name": "eventGridEvent",  
          "authLevel": "function",  
          "methods": [  
            "post",  
            "options"  
          ],  
          "direction": "in",  
          "type": "httpTrigger"  
        },  
        {  
          "name": "$return",  
          "type": "http",  
          "direction": "out"  
        }  
      ]  
    }  
    

    run.csx:

    #r "Newtonsoft.Json"  
      
    using Microsoft.AspNetCore.Mvc;  
    using Microsoft.Extensions.Primitives;  
    using Newtonsoft.Json;  
    using Newtonsoft.Json.Linq;  
    using System;  
    using System.Net;  
    using System.Collections.Generic;  
    using System.Globalization;  
    using System.Linq;  
    using System.Net.Http;  
    using System.Security.Cryptography;  
    using System.Text;  
    using System.Text.RegularExpressions;  
    using System.Threading.Tasks;  
      
    public static async Task<ActionResult> Run(JObject eventGridEvent, HttpRequest req, ILogger log)  
    {   
        if (req.Method == HttpMethod.Options.ToString())  
        {  
            log.LogInformation("CloudEventSchema validation");                 
            req.HttpContext.Response.Headers.Add("Webhook-Allowed-Origin", req.Headers["WebHook-Request-Origin"].FirstOrDefault()?.Trim());  
            return (ActionResult)new OkResult();  
        }  
      
        // consumer of telemetry (iot central)  
        uint sasTokenTTLInHrs = 1;  
        string iotcScopeId = req.Headers["iotc-scopeId"].FirstOrDefault() ?? Environment.GetEnvironmentVariable("AzureIoTC_scopeId");   
        string iotcSasToken = req.Headers["iotc-sasToken"].FirstOrDefault() ?? Environment.GetEnvironmentVariable("AzureIoTC_sasToken");    
        
        // mandatory properties  
        string source = eventGridEvent["data"]?["systemProperties"]?["iothub-message-source"]?.Value<string>();  
        string deviceId = eventGridEvent["data"]?["systemProperties"]?["iothub-connection-device-id"]?.Value<string>();  
                      
        if (source == "Telemetry" && !string.IsNullOrEmpty(deviceId) && Regex.IsMatch(deviceId, @"^[a-z0-9\-]+$"))  
        {  
            var sysProp = eventGridEvent["data"]["systemProperties"];  
            var appProp = eventGridEvent["data"]["properties"];  
            // device model  
            var component = appProp?["iothub-app-component-name"]?.Value<string>() ?? sysProp["dt-subject"]?.Value<string>() ?? "";  
            var modelId = appProp?["iothub-app-model-id"]?.Value<string>() ?? sysProp["dt-dataschema"]?.Value<string>();  
            // creation time  
            var enqueuedtime = sysProp["iothub-enqueuedtime"]?.Value<DateTime>().ToString("o");  
            var ctime = appProp?["iothub-creation-time-utc"]?.Value<DateTime>().ToString("o");  
            // device group (device prefix)  
            var deviceGroup = appProp?["iothub-app-device-group"]?.Value<string>();  
            deviceId = $"{(deviceGroup == null ? "" : deviceGroup + "-")}{deviceId}";  
            // remove sysprop  
            ((JObject)eventGridEvent["data"]).Remove("systemProperties");  
      
            try  
            {  
                var info = await Connectivity.GetConnectionInfo(deviceId, modelId, iotcScopeId, iotcSasToken, log, sasTokenTTLInHrs);  
                using (HttpClient client = new HttpClient())  
                {  
                    client.DefaultRequestHeaders.Add("Authorization", info.SasToken);  
                    client.DefaultRequestHeaders.Add("dt-subject", component);  
                    client.DefaultRequestHeaders.Add("iothub-app-iothub-creation-time-utc", ctime ?? enqueuedtime);  
                    var response = await client.PostAsJsonAsync(info.RequestUri, eventGridEvent["data"]);  
                    response.EnsureSuccessStatusCode();  
                }  
                log.LogInformation($"POST: {info.RequestUri}\r\n{eventGridEvent["data"]}");  
            }  
            catch(Exception ex)  
            {  
                log.LogError(ex.InnerException == null ? ex.Message : ex.InnerException.Message);  
                Connectivity.RemoveDevice(deviceId);  
                throw ex; // for retrying and deadlettering undeliverable message  
            }  
        }  
        else  
        {  
            log.LogWarning($"Wrong event message:\r\n{eventGridEvent}");  
        }  
        return (ActionResult)new OkResult();  
    }  
      
    class ConnectivityInfo  
    {  
        public string IoTHubName { get; set; }  
        public string RequestUri { get; set; }  
        public string SasToken { get; set; }  
        public ulong SaSExpiry { get; set; }  
        public string ModelId { get; set; }  
        public string DeviceConnectionString { get; set; }  
    }  
      
      
    static class Connectivity  
    {  
        static Dictionary<string, ConnectivityInfo> devices = new Dictionary<string, ConnectivityInfo>();  
      
        public static async Task<ConnectivityInfo> GetConnectionInfo(string deviceId, string modelId, string iotcScopeId, string iotcSasToken, ILogger log, uint sasTokenTTLInHrs = 24, int retryCounter = 10, int pollingTimeInSeconds = 3)  
        {  
            if (devices.ContainsKey(deviceId))  
            {  
                if (!string.IsNullOrEmpty(modelId) && devices[deviceId].ModelId != modelId)  
                {  
                    log.LogWarning($"Reprovissiong device with new model");  
                    devices.Remove(deviceId);  
                }  
                else  
                {  
                    if (!SharedAccessSignatureBuilder.IsValidExpiry(devices[deviceId].SaSExpiry, 100))  
                    {  
                        log.LogWarning($"Refreshing sasToken");  
                        devices[deviceId].SasToken = SharedAccessSignatureBuilder.GetSASTokenFromConnectionString(devices[deviceId].DeviceConnectionString, sasTokenTTLInHrs);  
                        devices[deviceId].SaSExpiry = ulong.Parse(SharedAccessSignatureBuilder.GetExpiry(sasTokenTTLInHrs));  
                    }  
                    return devices[deviceId];  
                }  
            }  
      
            string deviceKey = SharedAccessSignatureBuilder.ComputeSignature(iotcSasToken, deviceId);  
            string address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/register?api-version=2021-06-01";  
            string sas = SharedAccessSignatureBuilder.GetSASToken($"{iotcScopeId}/registrations/{deviceId}", deviceKey, "registration", 1);  
      
            using (HttpClient client = new HttpClient())  
            {  
                client.DefaultRequestHeaders.Add("Authorization", sas);  
                client.DefaultRequestHeaders.Add("accept", "application/json");  
                string jsontext = string.IsNullOrEmpty(modelId) ? null : $"{<!-- -->{ \"modelId\":\"{modelId}\" }}";  
                var response = await client.PutAsync(address, new StringContent(JsonConvert.SerializeObject(new { registrationId = deviceId, payload = jsontext }), Encoding.UTF8, "application/json"));  
      
                var atype = new { errorCode = "", message = "", operationId = "", status = "", registrationState = new JObject() };  
                do  
                {  
                    dynamic operationStatus = JsonConvert.DeserializeAnonymousType(await response.Content.ReadAsStringAsync(), atype);  
                    if (!string.IsNullOrEmpty(operationStatus.errorCode))  
                    {  
                        throw new Exception($"{operationStatus.errorCode} - {operationStatus.message}");  
                    }  
                    response.EnsureSuccessStatusCode();  
                    if (operationStatus.status == "assigning")  
                    {  
                        Task.Delay(TimeSpan.FromSeconds(pollingTimeInSeconds)).Wait();  
                        address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/operations/{operationStatus.operationId}?api-version=2021-06-01";  
                        response = await client.GetAsync(address);  
                    }  
                    else if (operationStatus.status == "assigned")  
                    {  
                        var cinfo = new ConnectivityInfo();  
                        cinfo.ModelId = modelId;  
                        cinfo.IoTHubName = operationStatus.registrationState.assignedHub;  
                        cinfo.DeviceConnectionString = $"HostName={cinfo.IoTHubName};DeviceId={deviceId};SharedAccessKey={deviceKey}";  
                        cinfo.RequestUri = $"https://{cinfo.IoTHubName}/devices/{deviceId}/messages/events?api-version=2021-04-12";  
                        cinfo.SasToken = SharedAccessSignatureBuilder.GetSASToken($"{cinfo.IoTHubName}/{deviceId}", deviceKey, null, sasTokenTTLInHrs);  
                        cinfo.SaSExpiry = ulong.Parse(SharedAccessSignatureBuilder.GetExpiry(sasTokenTTLInHrs));  
                        devices.Add(deviceId, cinfo);  
                        log.LogInformation($"DeviceConnectionString: {cinfo.DeviceConnectionString}");                          
                        return cinfo;  
                    }  
                    else  
                    {  
                        throw new Exception($"{operationStatus.registrationState.status}: {operationStatus.registrationState.errorCode} - {operationStatus.registrationState.errorMessage}");  
                    }  
                } while (--retryCounter > 0);  
      
                throw new Exception("Registration device status retry timeout exprired, try again.");  
            }  
        }  
      
        public static void RemoveDevice(string deviceId)  
        {  
            if (devices.ContainsKey(deviceId))  
                devices.Remove(deviceId);  
        }  
    }  
      
    public sealed class SharedAccessSignatureBuilder  
    {  
        public static string GetHostNameNamespaceFromConnectionString(string connectionString)  
        {  
            return GetPartsFromConnectionString(connectionString)["HostName"].Split('.').FirstOrDefault();  
        }  
        public static string GetSASTokenFromConnectionString(string connectionString, uint hours = 24)  
        {  
            var parts = GetPartsFromConnectionString(connectionString);  
            if (parts.ContainsKey("HostName") && parts.ContainsKey("SharedAccessKey"))  
                return GetSASToken(parts["HostName"], parts["SharedAccessKey"], parts.Keys.Contains("SharedAccessKeyName") ? parts["SharedAccessKeyName"] : null, hours);  
            else  
                return string.Empty;  
        }  
        public static string GetSASToken(string resourceUri, string key, string keyName = null, uint hours = 24)  
        {  
            try  
            {  
                var expiry = GetExpiry(hours);  
                string stringToSign = System.Web.HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;  
                var signature = SharedAccessSignatureBuilder.ComputeSignature(key, stringToSign);  
                var sasToken = keyName == null ?  
                    String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", System.Web.HttpUtility.UrlEncode(resourceUri), System.Web.HttpUtility.UrlEncode(signature), expiry) :  
                    String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", System.Web.HttpUtility.UrlEncode(resourceUri), System.Web.HttpUtility.UrlEncode(signature), expiry, keyName);  
                return sasToken;  
            }  
            catch  
            {  
                return string.Empty;  
            }  
        }  
      
        #region Helpers  
        public static string ComputeSignature(string key, string stringToSign)  
        {  
            using (HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key)))  
            {  
                return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));  
            }  
        }  
      
        public static Dictionary<string, string> GetPartsFromConnectionString(string connectionString)  
        {  
            return connectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Split(new[] { '=' }, 2)).ToDictionary(x => x[0].Trim(), x => x[1].Trim(), StringComparer.OrdinalIgnoreCase);  
        }  
      
        // default expiring = 24 hours  
        public static string GetExpiry(uint hours = 24)  
        {  
            TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);  
            return Convert.ToString((ulong)sinceEpoch.TotalSeconds + 3600 * hours);  
        }  
      
        public static DateTime GetDateTimeUtcFromExpiry(ulong expiry)  
        {  
            return (new DateTime(1970, 1, 1)).AddSeconds(expiry);  
        }  
        public static bool IsValidExpiry(ulong expiry, ulong toleranceInSeconds = 0)  
        {  
            return GetDateTimeUtcFromExpiry(expiry) - TimeSpan.FromSeconds(toleranceInSeconds) > DateTime.UtcNow;  
        }  
        #endregion  
    }    
    

    Note, that the IoT Subscriber required to configure a target such as the Azure IoT Central App by scopeId and sasToken. It can be done either in the subscription Delivery Properties [iotc-scopeId, iotc-sasToken] or in the azure function configuration [AzureIoTC_scopeId, AzureIoTC_sasToken].

    The following screen snippet shows a Raw data in the Azure IoT Central App:

    219264-image.png

    Thanks
    Roman

    1 person found this answer helpful.
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.