1

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?

DJ1
  • 11
  • 1
  • 4
  • Have you tried this sample app? https://github.com/Azure/iotc-android-sample – Matthijs van der Veer Jun 12 '20 at 10:51
  • @MatthijsvanderVeer I have not tried this app, will check, but do we have a common solution for Android and iOS app? is there a sample app for iotc-iOS as well? – DJ1 Jun 14 '20 at 11:08
  • @DJ1, Could you be more specific? Are you interesting in sending the telemetry data from the mobile App such as Android and iOS to the IoT Central App? – Roman Kiss Jun 14 '20 at 13:06
  • @RomanKiss Yes, I want to send telemetry data from Mobile app(Android and iOS) to the IoT Central app. My Android app is developed in Kotlin and iOS app in Swift. – DJ1 Jun 15 '20 at 03:56
  • @DJ1 I'm not aware of a default implementation offered by Microsoft for both platforms. But you can find their IOS samples [here](https://learn.microsoft.com/en-us/samples/azure-samples/azure-iot-samples-ios/azure-iot-samples-for-ios-platform/) – Matthijs van der Veer Jun 15 '20 at 05:42
  • @DJ1 Based on your requirements, the REST POST can be considered for sending a telemetry data to the IoTC App, https://docs.microsoft.com/en-us/rest/api/iothub/device/senddeviceevent The device provisioning can be implemented in the azure function with returning values for the above REST POST such as underlying *iothubname* and device *sas-token* for Authorization header. These values can be cached in the mobile app., so using the REST POST client will allow you to handle sending a telemetry data to the IoTC App on the both platforms. Notice, that this solution allows only sending a data. – Roman Kiss Jun 15 '20 at 06:26
  • @RomanKiss Thanks.. I will check using the REST POST if able to upload data to IoT Central app. – DJ1 Jun 15 '20 at 11:31
  • @MatthijsvanderVeer I am using this sample iOS app but not able to upload data to IOT Central app. Thanks. – DJ1 Jun 15 '20 at 11:31

2 Answers2

2

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

enter image description here

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:

enter image description here

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.

enter image description here

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:

enter image description here

Roman Kiss
  • 7,925
  • 1
  • 8
  • 21
  • Thanks. I have got the SAS token from scopeID, deviceID and PrimaryKey. But how to construct request uri? What will be the fullyQualified name iothubname? Am I referring to correct url? https://fully-qualified-iothubname.azure-devices.net/devices/{id}/messages/events?api-version=2020-03-13 – DJ1 Jun 15 '20 at 17:03
  • Also, I am provisioning the device on IoT Central app, that device is not visible on IoT Hub. So do we use IoT Hub name or the IoT Central app name in url? – DJ1 Jun 15 '20 at 17:11
  • see more details in my Update – Roman Kiss Jun 15 '20 at 17:51
  • Thanks a lot.. will try to implement using Azure Functions. – DJ1 Jun 16 '20 at 05:43
  • Thanks for detailed code. I am using your code on Azure portal to create an Azure function, but I am getting 500 Internal Server Error in output. I have set the ScopeID and SAS Token (Primary key) from IOT Central app. Am I missing anything? – DJ1 Jun 18 '20 at 17:43
  • It looks like, either is missing the *deviceid* in the query parameter or the *cmid* value is wrong. Note, if the *cmid* is missing in the query string, the device is registered only and not assigned to the device template, see the device status in the portal. – Roman Kiss Jun 19 '20 at 06:19
  • https://ipsconnectionfunction.azurewebsites.net/api/HttpTriggerGetConnectionInfo?code=Z/xefDFZraa3e/xc173kcMZoxlYVA9MjER5MkBMQiRwAS2VaaytySA==&deviceid=my-cloud-device&cmid=urn:ipsfieldtest:AndroidMobile_5j4:1 This is my function url, I am passing deviceid and cmid, still returns 500 internal server error. – DJ1 Jun 19 '20 at 08:00
  • How can I debug for the issue? SAS token I am taking it from SAS tokens on this app section in Device Connections. Is there problem with SAS token? – DJ1 Jun 19 '20 at 09:17
  • Try to run the function from the portal using the Test/Run page. Just add only a deviceid to the query – Roman Kiss Jun 19 '20 at 09:21
  • added only deviceid to query and tried to run from portal using Test/Run page, still I get 500 internal error only. And the device is also not registered, I cannot see it in Devices section. Is the issue with SAS token? – DJ1 Jun 19 '20 at 09:31
  • didi you see in the function's Logs the first log.LogInformation message? If yes, insert more log.LogInformation into the code, for example: after the deviceKey line, sas line, etc. to figure out the error place. Also, you can wrap the code by try/catch and you should have the reason of the error. – Roman Kiss Jun 19 '20 at 09:42
  • For testing I have hardcoded the values of scopeID and SASToken in the code. It works now I am getting the response as you mentioned and device is provisioned. Thanks. But after I post the requesturi I cannot see the data on Central app. In response I am getting 204 No content. How can I debug this issue? – DJ1 Jun 19 '20 at 10:47
  • The Http response status code = *204 No content* is correct and indicated that the telemetry data has been received by underlying IoT Hub. To see this telemetry data, go to *Data export* page and setup the target resource *Azure Blob Storage*. You should see all sent telemetry data in the blob file. That's a fast and easy way to see the telemetry data sent to the IoT Central App. Showing the telemetry data on the dashboard is related to your application (and device template) which I do recommend to create another thread. – Roman Kiss Jun 19 '20 at 11:16
  • Thanks a lot... I am able to visualise the data, issue was with query parameters, I am sending data in raw json now. I will now integrate the API's in my Mobile app. – DJ1 Jun 19 '20 at 12:00
  • OK, your question has been answered with an example how to handle sending a telemetry data from any Mobile App using the REST protocol to the IoT Central App. – Roman Kiss Jun 19 '20 at 12:09
  • Hi, Recently I saw that in IoT Central app if I create a new Device Template, we get a Interface id in the format like this "dtmi:ipsIotCentralApp:IPSDeviceTestTemplate21i;1". And if I use this for DeviceProvisioning function as cmid then I am getting below error in Azure function "Please follow the schema if you want to pass __iot:interfaces section under data. Format: '__iot:interfaces':{'CapabilityModelId': urn:companyname:template:templatename:version, 'CapabilityModel': 'The inline contents of interfaces and capability model.'}" Do you know if there are any changes in Azure IOT Central? – DJ1 Jun 10 '21 at 13:24
  • do you know if there are any changes in Device Template ID format? And any solution for above comment? – DJ1 Jun 14 '21 at 09:28
1

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

lmasieri
  • 26
  • 2