簡體   English   中英

我們如何將 Android/iOS 應用程序連接到 Azure IoT Central 並發送遙測數據?

[英]How do we connect Android/iOS app to Azure IoT Central and send Telemetry data?

我們已經在 Azure IOT Central 上注冊了我們的 IOT 設備,並且能夠在 IoT Central 上可視化遙測數據。 但是,當我們使用 Android 示例應用程序 Azure 的 IOT 中心生成的主鍵來發送遙測時,我們無法可視化數據。

我錯過了什么嗎? 我們如何在 IoT Central 上將 iOS 和 Android 注冊為設備以可視化數據?

以下屏幕片段顯示了使用 REST 發布請求將遙測數據發送到 IoT Central 應用程序的示例:

在此處輸入圖像描述

點擊按鈕:

    private async void FabOnClick(object sender, EventArgs eventArgs)
    {
        double tempValue = double.Parse(floatingTemperatureLabel.Text);
        var payload = new { Temperature = tempValue };

        using (var client = new HttpClient())
        {
            client.DefaultRequestHeaders.Add("Authorization", sasToken);
            await client.PostAsync(requestUri, new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json"));
        }

        View view = (View) sender;
        Snackbar.Make(view, $"Sent: {tempValue}", Snackbar.LengthLong).SetAction("Action", (Android.Views.View.IOnClickListener)null).Show();
    }

遙測數據被發送到 IoT Central 應用程序:

在此處輸入圖像描述

請注意,上面示例的requestUrisasToken是在我的單獨工具上創建的,但它們可以從自定義azure function獲取

更新:

對於運行時注冊(包括分配給設備模板),可以使用具有以下負載的REST PUT請求:

{
  "registrationId":"yourDeviceId",
  "payload": {
     "__iot:interfaces": {
       "CapabilityModelId":"yourCapabilityModelId"
     }
   }
}

請注意,授權 header (例如 sasToken)可以通過以下方式生成:

string sasToken = SharedAccessSignatureBuilder.GetSASToken($"{scopeId}/registrations/{deviceId}", deviceKey, "registration");

注冊過程的狀態可以通過REST GET 獲取,其中DeviceRegistrationResult為您提供 IoT Central 應用程序的底層 Azure IoT Hub 的命名空間,例如assignedHub屬性。

As I mentioned the above, all these REST API calls can be handled (implemented) by azure function and your mobile app will got the requestUrl and sasToken in the post response based on the scopeId, deviceId, deviceTemplateId and sas-token of your IoTC App .

更新 2:

我正在添加 azure function ( HttpTriggerGetConnectionInfo ) 的示例,以獲取設備 http 協議的連接信息到 IoT Central 應用程序:

function 需要在應用程序設置中添加以下變量:

  • AzureIoTC_scopeId
  • AzureIoTC_sasToken

運行.csx:

#r "Newtonsoft.Json"

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
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;
using System.Web;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");

    int retryCounter = 10;
    int pollingTimeInSeconds = 3;

    string deviceId = req.Query["deviceid"];
    string cmid = req.Query["cmid"];

    if (!Regex.IsMatch(deviceId, @"^[a-z0-9\-]+$"))
        throw new Exception($"Invalid format: DeviceID must be alphanumeric, lowercase, and may contain hyphens");

    string iotcScopeId = System.Environment.GetEnvironmentVariable("AzureIoTC_scopeId"); 
    string iotcSasToken = System.Environment.GetEnvironmentVariable("AzureIoTC_sasToken"); 

    if(string.IsNullOrEmpty(iotcScopeId) || string.IsNullOrEmpty(iotcSasToken))
        throw new ArgumentNullException($"Missing the scopeId and/or sasToken of the IoT Central App");

    string deviceKey = SharedAccessSignatureBuilder.ComputeSignature(iotcSasToken, deviceId);

    string address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/register?api-version=2019-03-31";
    string sas = SharedAccessSignatureBuilder.GetSASToken($"{iotcScopeId}/registrations/{deviceId}", deviceKey, "registration");

    using (HttpClient client = new HttpClient())
    {
        client.DefaultRequestHeaders.Add("Authorization", sas);
        client.DefaultRequestHeaders.Add("accept", "application/json");
        string jsontext = string.IsNullOrEmpty(cmid) ? null : $"{{ \"__iot:interfaces\": {{ \"CapabilityModelId\":\"{cmid}\"}} }}";
        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=2019-03-31";
               response = await client.GetAsync(address);
            }
            else if (operationStatus.status == "assigned")
            {
               string assignedHub = operationStatus.registrationState.assignedHub;
               string cstr = $"HostName={assignedHub};DeviceId={deviceId};SharedAccessKey={deviceKey}";
               string requestUri = $"https://{assignedHub}/devices/{deviceId}/messages/events?api-version=2020-03-01";
               string deviceSasToken = SharedAccessSignatureBuilder.GetSASToken($"{assignedHub}/{deviceId}", deviceKey);

               log.LogInformation($"IoTC DeviceConnectionString:\n\t{cstr}");
               return new OkObjectResult(JObject.FromObject(new { iotHub = assignedHub, requestUri = requestUri, sasToken = deviceSasToken, deviceConnectionString = cstr }));
            }
            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 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}", HttpUtility.UrlEncode(resourceUri), HttpUtility.UrlEncode(signature), expiry) :
                String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", HttpUtility.UrlEncode(resourceUri), 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
    private 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;
    }

    public static string CreateSHA256Key(string secret)
    {
        using (var provider = new SHA256CryptoServiceProvider())
        {
            byte[] keyArray = provider.ComputeHash(UTF8Encoding.UTF8.GetBytes(secret));
            provider.Clear();
            return Convert.ToBase64String(keyArray);
        }
    }

    public static string CreateRNGKey(int keySize = 32)
    {
        byte[] keyArray = new byte[keySize];
        using (var provider = new RNGCryptoServiceProvider())
        {
            provider.GetNonZeroBytes(keyArray);
        }
        return Convert.ToBase64String(keyArray);
    }
    #endregion
}

使用 function 配置mydeviceid並分配給urn:rk2019iotcpreview:Tester_653:1設備模板:

GET: https://yourFncApp.azurewebsites.net/api/HttpTriggerGetConnectionInfo?code=****&deviceid=mydeviceid&cmid=urn:rk2019iotcpreview:Tester_653:1

以下屏幕片段顯示了我們可以從設備模板中獲取CapabilityModelId (cmid) 的位置。 請注意,錯誤值將返回: 500 Internal Server Error響應代碼。

在此處輸入圖像描述

移動應用程序中收到的響應將允許將遙測數據發送到 IoT Central 應用程序:

{
  "iotHub": "iotc-xxxxx.azure-devices.net",
  "requestUri": "https://iotc-xxxxx.azure-devices.net/devices/mydeviceid/messages/events?api-version=2020-03-01",
  "sasToken": "SharedAccessSignature sr=iotc-xxxxx.azure-devices.net%2fmydeviceid&sig=xxxxxxxx&se=1592414760",
  "deviceConnectionString": "HostName=iotc-xxxxx.azure-devices.net;DeviceId=mydeviceid;SharedAccessKey=xxxxxxx"
 }

響應 object 可以緩存在移動應用程序中,並在其過期之前刷新。 請注意,上述 sasToken 有效期為 24 小時(默認值)。

以下屏幕片段顯示了將遙測數據從移動應用程序發送到 Azure IoT Central 應用程序的概念:

在此處輸入圖像描述

IoT Central 團隊最近發布了 iOS 和 Android 的移動設備即網關代碼示例,可幫助您通過移動應用程序將 BLE 設備連接到 IoT Central。 您可以修改示例以僅發送設備遙測而不是 BLE 數據。 看看這里的示例代碼: https://github.com/iot-for-all/iotc-cpm-sample

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM