【问题标题】:Sending Cloud-To-Device message for devices provisoned through IoT-central为通过 IoT-central 配置的设备发送 Cloud-To-Device 消息
【发布时间】:2018-12-14 07:28:17
【问题描述】:

我一直在阅读这个新的 SaaS 产品的文档,但我没有看到任何关于能够向设备发送消息的内容,例如:打开/关闭设备

https://docs.microsoft.com/en-us/azure/iot-central/tutorial-add-device

我确实看到有一项规定可以通过更改设备双胞胎来更改设备的设置。

另外,我读到有一种方法可以向设备发送“回声”。但是,这些并不符合我的确切目的。

那么,有没有一种方法可以发送 C2D 消息,使用连接字符串,可以使用例程构建 - ? https://docs.microsoft.com/en-us/azure/iot-central/tutorial-add-device#prepare-the-client-code

我想通过 AzureFunction 发送此 C2D,但很高兴知道这是否可以以某种方式集成到 IoT-Central UI 中。

满足我要求的任何其他输入(打开/关闭设备)也会有很大帮助!

【问题讨论】:

  • 看看github.com/Azure/azure-functions-iothub-extension这个AF输出绑定扩展允许发送C2D消息,看它的实现方法SendCloudToDeviceMessageAsync。
  • 感谢您的回复。我只是想了解更多。使用我上面的连接字符串(即设备连接字符串),我可以发送 C2D 消息吗?因为我看到的示例通常使用服务连接字符串,因此它们对我有用。
  • 你是对的,Azure IoT Central 没有让你访问内部 IoT 中心面向服务的终结点。此端仅由 IoT-Central 管理,例如 Data Export、Application Builder、Analytics 等。

标签: azure-iot-hub azure-iot-central


【解决方案1】:

正如我在评论中提到的,Azure IoT Central 可以完全控制内部 IoT 中心面向服务的端点。但是,有一种方法,Azure IoT Central 允许对这个面向服务的端点进行有限访问,并使用 REST API 来处理设备孪生并在设备上调用直接方法。

以下是获取 REST Api 调用所需的授权标头的 sas 令牌的步骤:

  1. 从您的 Azure IoT Central 应用程序获取访问令牌。

    格式为:

    SharedAccessSignature sr=appId&sig=xxxxx&skn=myTokenName&se=1577730019340
    

    请注意,appId 显示的是您的 Azure IoT Central 应用程序的应用程序 ID

  2. 调用REST POST请求获取iothubTenantSasToken.sasToken

    POST https://api.azureiotcentral.com/v1-beta/applications/{appId}/diagnostics/sasTokens  
    Authorization:SharedAccessSignature sr=appId&sig=xxxxx&skn=myTokenName&se=1577730019340
    

    响应格式如下:

    {
      "iothubTenantSasToken": {
        "sasToken": "SharedAccessSignature sr=saas-iothub-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.azure-devices.net&sig=xxxxx&se=1546197703&skn=service"
        },
      "eventhubSasToken": {
        "sasToken": "SharedAccessSignature sr=sb%3A%2F%2Fep-ns-saas-ep-15-262-xxxxxxxxxx.servicebus.windows.net%2Fep-ehub-saas-iothu-1044564-xxxxxxxxxx&sig=xxxxxx&se=1546197703&skn=service",
        "entityPath": "ep-ehub-saas-iothu-1044564-xxxxxxxxxx",
        "hostname": "sb://ep-ns-saas-ep-15-262-xxxxxxxxxx.servicebus.windows.net/"
        },
      "expiry": 1546197703
    }
    
  3. 我们面向服务的端点调用的 sasToken 是:

    SharedAccessSignature sr=saas-iothub-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.azure-devices.net&sig=xxxxx&se=1546197703&skn=service
    

现在,我们可以使用一些 Azure IoT Hub REST API,基本上是 uri 路径中带有 twins 的调用,例如:

https://docs.microsoft.com/en-us/rest/api/iothub/service/gettwin

https://docs.microsoft.com/en-us/rest/api/iothub/service/updatetwin

https://docs.microsoft.com/en-us/rest/api/iothub/service/replacetwin

https://docs.microsoft.com/en-us/rest/api/iothub/service/invokedevicemethod

在设备1上调用直接方法的示例:

POST https://saas-iothub-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.azure-devices.net/twins/device1/methods?api-version=2018-06-30
Authorization:SharedAccessSignature sr=saas-iothub-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.azure-devices.net&sig=xxxxx&se=1546197703&skn=service

body:
    {
      "methodName": "writeLine",
      "timeoutInSeconds": 20,
      "payload": {
         "input1": 12345,
         "input2": "HelloDevice"
         }
    }

更新设备孪生标签属性的示例:

PATCH https://saas-iothub-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.azure-devices.net/twins/device1?api-version=2018-06-30
Authorization:SharedAccessSignature sr=saas-iothub-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.azure-devices.net&sig=xxxxx&se=1546197703&skn=service

body:
    {
      "tags": {
        "test":12345
        }
    }

请注意,sasToken 到期时间为 60 分钟。我确实建议缓存步骤 2 中的响应对象。并根据到期时间刷新。

更新:

以下是使用 IoT Central 访问令牌处理 azure 函数中的设备孪生和设备直接方法的步骤。

  1. 在您的 IoT Central 应用程序中生成访问令牌,请参阅以下屏幕 sn-p:

  1. 将此访问令牌添加到您的函数应用程序设置中。在此示例中,使用了 App Setting Name AzureIoTCAccessToken。请注意,此访问令牌可以存储在 Azure Key Vault 中,请参阅更多详细信息 here

  2. 在您的函数应用中创建 HttpTrigger 函数。

  3. 将 run.csx 替换为以下代码:

    #r "Newtonsoft.Json"
    #r "Microsoft.Azure.WebJobs.Extensions.Http"
    
    using System.Net;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Primitives;
    using Microsoft.Azure.WebJobs.Extensions.Http;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    using System.Linq;
    using System.Text;
    
    // reusable client proxy
    static HttpClientHelper iothub = new HttpClientHelper(Environment.GetEnvironmentVariable("AzureIoTCAccessToken"));
    
    public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");
    
        var atype = new { device = new { deviceId = "", properties = new JObject(), measurements = new JObject() } };
        var iotcobj = JsonConvert.DeserializeAnonymousType(await req.ReadAsStringAsync(), atype);
    
        // get deviceId, for test puspose use the device1
        string deviceId = iotcobj?.device?.deviceId ?? "device1";
    
        // get a device twins
        var response = await iothub.Client.GetAsync($"/twins/{deviceId}?api-version=2018-06-30");
        string jsontext = await response.Content.ReadAsStringAsync();
        log.LogInformation($"DeviceTwin: {JsonConvert.DeserializeObject(jsontext)}");
    
       // patch on desired property
       var patch = JsonConvert.SerializeObject(new { properties = new { desired = new { ping = DateTime.UtcNow } } });
       response = await iothub.Client.PatchAsync($"/twins/{deviceId}?api-version=2018-06-30", new StringContent(patch, Encoding.UTF8, "application/json"));
       jsontext = await response.Content.ReadAsStringAsync();
       log.LogInformation($"Patch: {JsonConvert.DeserializeObject(jsontext)}");
    
       // invoke a device method
       var method = new { methodName = "writeLine", timeoutInSeconds = 30, payload = new {input1 = 12345, input2 = "HelloDevice" } };
       response = await iothub.Client.PostAsJsonAsync($"/twins/{deviceId}/methods?api-version=2018-06-30", method );
       jsontext = await response.Content.ReadAsStringAsync();
       log.LogInformation($"DirectMethod: {JsonConvert.DeserializeObject(jsontext)}");
    
       return new OkObjectResult(jsontext);      
    }
    
    class HttpClientHelper
    {
        HttpClient client;
        string accessToken;
        dynamic iothub;
        long toleranceInSeconds = 60;
    
        public HttpClientHelper(string accessToken)
        {
            this.accessToken = accessToken;
            this.iothub = GetIoTHubTenant(accessToken);
            string hostname = GetHostNameFromSaSToken(this.iothub.iothubTenantSasToken.sasToken);
            client = new HttpClient() { BaseAddress = new Uri($"https://{hostname}") };
            client.DefaultRequestHeaders.Add("Authorization", iothub.iothubTenantSasToken.sasToken);
        }
        public HttpClient Client
        {
            get
            {
                if((new DateTime(1970, 1, 1)).AddSeconds(this.iothub.expiry - toleranceInSeconds) < DateTime.UtcNow)
                    SetAuthorizationHeader();
                return client;
            }
        }
        private void SetAuthorizationHeader()
        {
            lock (client)
            {
                if ((new DateTime(1970, 1, 1)).AddSeconds(this.iothub.expiry - toleranceInSeconds) < DateTime.UtcNow)
                {
                    if (client.DefaultRequestHeaders.Contains("Authorization"))
                        client.DefaultRequestHeaders.Remove("Authorization");
                    this.iothub = GetIoTHubTenant(this.accessToken);
                    client.DefaultRequestHeaders.Add("Authorization", this.iothub.iothubTenantSasToken.sasToken);
                }
            }
        }
        private string GetHostNameFromSaSToken(string sastoken)
        {
            var parts = sastoken.Replace("SharedAccessSignature", "").Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Split(new[] { '=' }, 2)).ToDictionary(x => x[0].Trim(), x => x[1].Trim());
            return parts["sr"] ?? "";
        }
    
        private dynamic GetIoTHubTenant(string iotcAccessToken)
        {
            string appId = GetHostNameFromSaSToken(iotcAccessToken);
            using (var hc = new HttpClient())
            {
                hc.DefaultRequestHeaders.Add("Authorization", accessToken);
                string address = $"https://api.azureiotcentral.com/v1-beta/applications/{appId}/diagnostics/sasTokens";
                var response = hc.PostAsync(address, new StringContent("{}", Encoding.UTF8, "application/json")).Result;
                return JsonConvert.DeserializeAnonymousType(response.Content.ReadAsStringAsync().Result, new { iothubTenantSasToken = new { sasToken = "" }, expiry = 0L });
            }
        }
    }
    

注意:,上述实现基于 IoT Central 应用程序生成的访问令牌,就像它最近发布的一般可用性一样,请参阅 here。在更改格式等的情况下,包含上述解决方案的所有客户端、测试人员等都会受到影响。

【讨论】:

  • 感谢 Roman 到目前为止为我提供的服务,感谢。您能否还概述一下我如何在您的步骤 1 中获得“访问令牌”。您是从 DPS SDK 或 dps-keygen 获得的吗?因为我看到的只是这张图片中的数据。 docs.microsoft.com/en-us/azure/iot-central/media/…请指教。
  • 转到您的 IoT Central 应用程序管理页面以生成此访问令牌并记住它以供使用。
  • 请注意,当您配置操作 webhook 的回调 url 时,可以通过 url 查询字符串将此访问令牌(其部分,如 sr、sig、skn 和 se)添加到 azure 函数。一旦我们在 AF 中获得了这个访问令牌,我们就可以按照步骤 2. 和 3. 获得对内部 iot hub 面向服务的端点的有限访问权限。
  • 感谢 Roman 如此详细的回答。虽然您描述的方法此时有效,但不建议这样做,因为访问令牌格式可能会更改,因此不应将其视为依赖项。访问令牌仅适用于 IoT Central Explorer CLI (docs.microsoft.com/en-us/azure/iot-central/…)。我们目前正在开发一种用于 IoT Central 的 API,以支持这样的场景。
【解决方案2】:

对于您的特定用例,今天执行此操作的唯一方法是从 Azure Functions 发送 trigger a Logic App workflow using its HTTP endpoint。在 Logic 应用中,您可以使用 Azure IoT Central 连接器 构建一个工作流来更新设备属性和设置。

我们正在开发 IoT Central 中的 API,可以照亮像您这样的用例,敬请期待!

【讨论】:

  • 我一直在尝试逻辑应用程序中的 IoT Central 连接器(预览版)以在设备上运行命令。选择设备模板和命令很容易,并从作为触发器的 POST 中的输入参数中获取 deviceId,但是当我查看运行历史记录时,它在 OUTPUTS 下失败,出现 404。查看显示原始输入会发现一条可能正确的路径:“路径”:“/applications/7270478a-e128-4b1c-8b09-e148f78893f1/devices/jeffsdeskjune/commands/sendmsg”。我不知道是什么问题。
  • @AlbertoVega-MSFT 在某种程度上。通过打开连续数据导出并从中捕获设备源,我可以创建一个从众所周知的设备 ID(又名设备名称、注册名称)到 internal-only-iotc-knows-it-afaik-device-id 的映射,并使用后者使用 IOTC 连接器向设备发送命令(因为前者不起作用)。我不知道这是否可以依靠前进,但它现在有效。我们将在周一与一些 MSFT 人员会面进行讨论。
【解决方案3】:

【讨论】:

【解决方案4】:

您可以尝试使用设置,有一种设置类型称为“切换”。为了实现它,您可以从 Azure 门户访问您的 IoT Central 应用程序。那么:

  • 转到设备资源管理器标签

  • 选择设备

  • 点击设备的“设置”标签
  • 点击右上角的“编辑模板”按钮
  • 在“库”下找到并点击“切换”

  • 输入切换功能的设置

如果您打算大规模实施,您可以Create and Run a Job

在 IoT Central 中,您可以使用作业大规模管理连接的设备。作业功能使您能够对设备属性、设置和命令执行批量更新。

【讨论】:

  • 感谢您的回复。虽然这是执行命令的一种方式,但它对我将任何特定数据发送到设备没有帮助。但更重要的是,我需要它从外部应用程序运行。我不确定这些作业是否可以从 Azure 函数执行?此外,在我的要求是分别控制单个设备的情况下,为每个设备创建一个作业也是不可行的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-30
  • 2023-03-12
相关资源
最近更新 更多