我们已经在Azure IOT Central上注册了IOT设备,并能够在IoT Central上可视化遥测数据。但是,当我们使用Azure在Android Sample应用程序的IOT中心生成的主键发送遥测时,我们无法可视化数据。
我错过了什么吗?我们如何将iOS和Android注册为IoT Central上的设备以可视化数据?
答案 0 :(得分:1)
以下屏幕片段显示了使用REST Post请求将遥测数据发送到IoT中央应用程序的示例:
点击按钮
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中央应用程序:
请注意,以上示例的 requestUri 和 sasToken 是在我单独的工具上创建的,但可以从基于< IoTC应用程序的em> scopeId , deviceId 和 SAS令牌。
更新:
对于运行时注册(包括分配给设备模板),可以REST PUT请求使用以下有效负载:
{
"registrationId":"yourDeviceId",
"payload": {
"__iot:interfaces": {
"CapabilityModelId":"yourCapabilityModelId"
}
}
}
请注意,可以通过以下方式生成Authorization标头(例如sasToken):
string sasToken = SharedAccessSignatureBuilder.GetSASToken($"{scopeId}/registrations/{deviceId}", deviceKey, "registration");
注册过程的状态可以通过REST GET获取,其中 DeviceRegistrationResult 为您提供IoT中心应用程序的基础Azure IoT中心的命名空间,例如 assignedHub < / em>属性。
如上所述,所有这些REST API调用都可以通过azure函数处理(实现),并且您的移动应用将在帖子中获取 requestUrl 和 sasToken 基于IoTC应用程序的scopeId,deviceId,deviceTemplateId和sas令牌的响应。
更新2:
我正在添加azure函数( HttpTriggerGetConnectionInfo )的示例,以获取设备http协议到IoT Central应用程序的连接信息:
该功能需要在“应用程序”设置中添加以下变量:
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
}
用于配置 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中心应用:
{
"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"
}
可以将响应对象缓存在移动应用中,并在其过期之前刷新。请注意,上述sasToken有效期为24小时(默认值)。
以下屏幕片段展示了将遥测数据从移动应用程序发送到Azure IoT中央应用程序的概念:
答案 1 :(得分:0)
IoT Central团队最近发布了适用于iOS和Android的网关移动设备代码示例,可帮助您通过移动应用程序将BLE设备连接到IoT Central。您可以修改样本以仅发送设备遥测而不是BLE数据。在此处查看示例代码:https://github.com/iot-for-all/iotc-cpm-sample