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

南楼画角 提交于 2020-08-11 01:22:25

问题


We have registered our IOT device on Azure IOT Central and able to visualise Telemetry data on IoT Central. But when we use the Primary key generated on IOT central in Android Sample app by Azure to send Telemetry we cannot visualise the data.

Am I missing anything? How do we register iOS and Android as a device on IoT Central to visualise the data?


回答1:


The following screens snippet show an example of the sending a telemetry data to the IoT Central App using the REST Post request:

Clicking on the button:

    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();
    }

the telemetry data is sent to the IoT Central App:

Note, that the requestUri and sasToken for the above example has been created on my separate tool, but they can be obtained from the custom azure function based on the scopeId, deviceId and SAS token of the IoTC App.

Update:

For Runtime registration (included assigning to the device template) can be used REST PUT request with the following payload:

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

Note, that the Authorization header such as a sasToken can be generated by the following:

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

The status of the registration process can be obtained by REST GET where the DeviceRegistrationResult give you a namespace of the underlying Azure IoT Hub of your IoT Central App such as assignedHub property.

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.

Update-2:

I am adding an example of the azure function (HttpTriggerGetConnectionInfo) to get the connection info for device http protocol to the IoT Central App:

The function requires to add the following variables to the Application settings:

  • AzureIoTC_scopeId
  • AzureIoTC_sasToken

run.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
}

The usage of the function for provisioning mydeviceid and assigned to urn:rk2019iotcpreview:Tester_653:1 device template:

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

The following screen snippet shows where we can obtained the CapabilityModelId (cmid) from our device template. Note, that the wrong value will return: 500 Internal Server Error response code.

The received response in the mobile app will allow to send the telemetry data to the IoT Central App:

{
  "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"
 }

The response object can be cached in the mobile app and refreshing before its expiring. Note, that the above sasToken is valid for 24 hours (default value).

The following screen snippet shows a concept of the sending a telemetry data from the mobile app to the Azure IoT Central App:




回答2:


the IoT Central team just recently released a mobile-device-as-a-gateway code sample for iOS and Android that helps you connect BLE devices via a mobile app into IoT Central. You can modify the sample to just send device telemetry instead of BLE data. Take a look at the sample code here: https://github.com/iot-for-all/iotc-cpm-sample



来源:https://stackoverflow.com/questions/62342491/how-do-we-connect-android-ios-app-to-azure-iot-central-and-send-telemetry-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!