【问题标题】:Trouble connecting a Windows 10 IoT Core device to Azure IoT Hub from behind closed firewall从封闭的防火墙后面将 Windows 10 IoT Core 设备连接到 Azure IoT 中心时出现问题
【发布时间】:2018-01-12 03:46:25
【问题描述】:

我在地下室有一个不错的小项目,由一个连接到 Raspberry Pi 3 的 LED 组成。非常复杂,是的,我知道。除此之外,这个 Raspberry Pi 正在运行 Windows 10 IoT Core,我的目标是通过 Azure IoT Hub 服务中的直接方法来关闭和打开 LED。

除了我将在一个单独的问题中询问的一个奇怪的 UI 问题之外,这个系统或多或少地工作正常。我编写了一个 UWP 项目,它可以很好地切换 LED。回到 UI 正常工作的时候,在某一时刻,我可以通过一个可点击的按钮来切换灯光。再次,非常复杂。无论如何,Azure IoT Hub 已启动并正在运行,Raspberry Pi 已正确配置,并且直接方法已在设备上设置。我打算使用 Azure Functions 来使用 API 调用来调用 Direct Method,但在遇到问题后我将其简化,现在正在通过 Azure Portal 内置的“Direct Method”对话框进行测试。

我的问题是:当我从 Azure 门户的工具调用直接方法时(顺便说一句,使用起来有点烦人),然后等待 20 秒等待 5-seconds-and-then-it's-gone 结果弹出,我收到一条消息,说呼叫“等待连接时超时”。具体错误如下:

DeviceNotFoundException: Device {"Message":"{\"errorCode\":404103,\"trackingId\":\"9b39dbe7f22c4acda1abbaa1ccc4c410-G:3-TimeStamp:01/11/2018 22:31:55\",\"message\":\"Timed out waiting for device to connect.\",\"info\":{\"timeout\":\"00:00:00\"},\"timestampUtc\":\"2018-01-11T22:31:55.1883184Z\"}","ExceptionMessage":""} not registered

虽然我不能 100% 确定“未注册”部分,但我相当肯定这个问题源于我的互联网路由器。不久前,我家切换到 Hughesnet 作为我们的 ISP,这样做我们失去了接受入站流量的能力(即网络现在对外部请求关闭),我们无法设置 NAT转发一个或两个端口(没有可公开访问的 IP 地址,句号)。虽然我很想抱怨 HughesNet 服务的多个方面,但那是另一回事。

对于与此类似的项目,我已经能够设置一个持久的解决方法,例如反向 SSH 隧道。困难在于,我不太确定如何处理这个项目。

我的问题是:有没有办法让我的 Raspberry Pi 在这里接受来自 Azure IoT 中心的直接方法调用?也许是某种 SSH 隧道?

更新:代码

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.Devices.Gpio;
using System.Text;
using Windows.UI;
using Microsoft.Azure.Devices.Client;
using System.Threading.Tasks;

// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409

namespace UltimateLED2
{
public sealed partial class MainPage : Page
{
    const string DeviceId = "**********";
    const string DeviceKey = "**************************************";
    const string HubEndpoint = "*****.azure-devices.net";
    const int LEDPinNumber = 5;
    GpioPin LEDPin;
    bool LEDPinState;
    Brush StatusNormalBrush;
    DeviceClient deviceClient;

    public MainPage()
    {
        this.InitializeComponent();
        StatusNormalBrush = StatusIndicator.Fill;
        if (!TryInitGPIO().Result)
        {
            WriteMessage("GPIO initialization failed");
        }
        deviceClient = DeviceClient.Create(HubEndpoint,
            AuthenticationMethodFactory.CreateAuthenticationWithRegistrySymmetricKey(DeviceId, DeviceKey), TransportType.Mqtt_WebSocket_Only); 
        deviceClient.SetMethodHandlerAsync("ToggleLED", new MethodCallback(ToggleLEDMethod), null);
    }

    private async Task<MethodResponse> ToggleLEDMethod(MethodRequest methodRequest, object userContext)
    {
        WriteMessage("Recieved Direct Request to toggle LED");
        LEDPinState = !LEDPinState;
        await UpdateLight();
        return new MethodResponse(Encoding.UTF8.GetBytes("{\"LightIs\":\"" + (LEDPinState ? "On" : "Off") + "\"}"), 200);
    }

    public async Task<bool> TryInitGPIO()
    {
        GpioController gpioController = GpioController.GetDefault();
        if (gpioController == null)
        {
            WriteMessage("This Device is not IoT friendly!  (No GPIO Controller found)", true);
            return false;
        }
        if (gpioController.TryOpenPin(LEDPinNumber, GpioSharingMode.Exclusive, out LEDPin, out GpioOpenStatus openStatus))
        {
            WriteMessage($"Output Pin ({LEDPinNumber}) Opened Successfully!!");
        }
        else
        {
            WriteMessage($"Output Pin ({LEDPinNumber}) Failed to Open", true);
            return false;
        }

        LEDPin.SetDriveMode(GpioPinDriveMode.Output);
        LEDPin.Write(GpioPinValue.High);
        LEDPinState = true;
        await UpdateLight();
        WriteMessage("Output Pin initialized and on");
        return true;
    }

    private void WriteMessage(string message, bool isError = false)
    {
        StringBuilder sb = new StringBuilder(OutputBox.Text);
        if (isError)
        {
            sb.AppendLine();
            sb.AppendLine("*************ERROR**************");
        }
        sb.AppendLine(message);
        if (isError)
        {
            sb.AppendLine("*************END ERROR**************");
            sb.AppendLine();
        }
        OutputBox.Text = sb.ToString();  //Upon reviewing my code before posting it here, I noticed that this line of code directly modifies a UI element, and yet no errors are thrown (that I can see), whereas changing the color of my little light indicator circle below threw a threading error when I attempted to change the UI from another thread.  This function can be called synchronously from async methods, that run on different threads... does that not mean this function would be called on the different thread it was called from? 
    }

    private async void ManualToggle_Click(object sender, RoutedEventArgs e)
    {
        WriteMessage("Recieved Manual Toggle");
        LEDPinState = !LEDPinState;
        await UpdateLight();
    }

    private async Task UpdateLight()
    {

        LEDPin.Write(LEDPinState ? GpioPinValue.High : GpioPinValue.Low);
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
         {
             StatusIndicator.Fill = LEDPinState ? new SolidColorBrush(Colors.Red) : StatusNormalBrush;
         });


    }
}
}

谢谢! 卢卡斯·尼沃纳

【问题讨论】:

  • 您在Azure Portal中测试调用直接方法时,您是否运行了为命名方法注册了委托的设备客户端应用程序?如果您运行过,请提供相关代码吗?
  • @Lucas Direct Method 要求在设备和 Azure IoT Hub 之间使用面向连接的协议,例如 mqtt 或 amqp。换句话说,您的设备必须连接才能进行设备方法调用。使用 azure 门户查询资源管理器查询包含其 connectionState 属性的所有设备。注意,结果页面没有刷新按钮,所以在下一次执行查询之前关闭页面。
  • @Lucas 出于测试目的,您可以使用 MQTTBox 客户端 workswithweb.com/mqttbox.html 或 Azure IoT Hub Tester codeproject.com/Articles/1173356/Azure-IoT-Hub-Tester 来模拟具有 MQTT 协议的设备。
  • @MichaelXu-MSFT...我已经添加了代码
  • @RomanKiss 是的,我可以理解必须连接设备才能接受基于网络的直接方法调用(看起来非常合乎逻辑)。我的麻烦在于实际上打开了连接。 Azure 不能直接这样做,因为我的本地 Internet 连接没有面向公众的 IP 地址,因此必须以某种方式建立连接,作为从 Raspberry Pi 到世界其他地方的某种持久隧道。我的问题(措辞更具体一点)是:如何通过某种 SSH(或其他)隧道为 MQTT 创建持久连接?

标签: azure raspberry-pi3 windows-10-iot-core azure-iot-hub


【解决方案1】:

我已经用你提供的代码测试了这个问题,即使它不完整,我还是稍微修改了一下,以便它可以运行。方法 ToggleLED 可以从 Azure IoT Hub 调用。在您的代码中,方法 WriteMessage 需要使用 Dispatcher 来更新 TextBox,因为当直接方法调用时,它在新线程中运行(而不是在 UI 线程中)。对于您的问题,直接方法遵循请求 - 响应模式,用于需要立即确认其结果的通信,通常是设备的交互式控制,例如打开风扇。我对持久性的含义有点困惑连接。当您使用 MQTT 连接 Azure IoT Hub 时,如果没有关闭操作,则连接不会关闭,或者网络保持活动状态,还有一些其他异常。此外,IoT 中心不支持 QoS 2 消息。如果设备应用发布 QoS 2 的消息,IoT Hub 将关闭网络连接。

代码

public sealed partial class MainPage : Page
{
    const string DeviceId = "device1";
    const string DeviceKey = "<my-device-primarykey>";
    const string HubEndpoint = "<my-iot-hub>";
    const int LEDPinNumber = 5;
    GpioPin LEDPin;
    bool LEDPinState;
    Brush StatusNormalBrush;
    DeviceClient deviceClient;

    public MainPage()
    {
        this.InitializeComponent();

        if (!TryInitGPIO().Result)
        {
            WriteMessage("GPIO initialization failed");
        }

        deviceClient = DeviceClient.Create(HubEndpoint,
            AuthenticationMethodFactory.CreateAuthenticationWithRegistrySymmetricKey(DeviceId, DeviceKey), TransportType.Mqtt_WebSocket_Only);
        deviceClient.SetMethodHandlerAsync("ToggleLED", new MethodCallback(ToggleLEDMethod), null);
    }

    private Task<MethodResponse> ToggleLEDMethod(MethodRequest methodRequest, object userContext)
    {
        WriteMessage("Recieved Direct Request to toggle LED");
        LEDPinState = !LEDPinState;
        UpdateLight();
        return Task.FromResult(new MethodResponse(Encoding.UTF8.GetBytes("{\"LightIs\":\"" + (LEDPinState ? "On" : "Off") + "\"}"), 200));
    }

    public async Task<bool> TryInitGPIO()
    {
        GpioController gpioController = GpioController.GetDefault();
        if (gpioController == null)
        {
            WriteMessage("This Device is not IoT friendly!  (No GPIO Controller found)", true);
            return false;
        }
        if (gpioController.TryOpenPin(LEDPinNumber, GpioSharingMode.Exclusive, out LEDPin, out GpioOpenStatus openStatus))
        {
            WriteMessage($"Output Pin ({LEDPinNumber}) Opened Successfully!!");
        }
        else
        {
            WriteMessage($"Output Pin ({LEDPinNumber}) Failed to Open", true);
            return false;
        }

        LEDPin.SetDriveMode(GpioPinDriveMode.Output);
        LEDPin.Write(GpioPinValue.High);
        LEDPinState = true;
        UpdateLight();
        WriteMessage("Output Pin initialized and on");
        return true;
    }

    private async void WriteMessage(string message, bool isError = false)
    {
        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {

            StringBuilder sb = new StringBuilder(OutputBox.Text);
            if (isError)
            {
                sb.AppendLine();
                sb.AppendLine("*************ERROR**************");
            }
            sb.AppendLine(message);
            if (isError)
            {
                sb.AppendLine("*************END ERROR**************");
                sb.AppendLine();
            }
            OutputBox.Text = sb.ToString();  //Upon reviewing my code before posting it here, I noticed that this line of code directly modifies a UI element, and yet no errors are thrown (that I can see), whereas changing the color of my little light indicator circle below threw a threading error when I attempted to change the UI from another thread.  This function can be called synchronously from async methods, that run on different threads... does that not mean this function would be called on the different thread it was called from? 
        });
    }

    private async void ManualToggle_Click(object sender, RoutedEventArgs e)
    {
        WriteMessage("Recieved Manual Toggle");
        LEDPinState = !LEDPinState;
        UpdateLight();
    }

    private async void UpdateLight()
    {

        LEDPin.Write(LEDPinState ? GpioPinValue.High : GpioPinValue.Low);

        await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
        {
            //StatusIndicator.Fill = LEDPinState ? new SolidColorBrush(Colors.Red) : StatusNormalBrush;
            OutputBox.Text = "UpdateLight\r\n";
        });
    }

【讨论】:

  • 我取得了一点突破,似乎因为 UI 没有响应,所以甚至没有创建设备客户端。由于我对失败的假设似乎非常错误,因此我决定提出一个完全不同的问题,可以在 here 找到
猜你喜欢
  • 2017-06-09
  • 2023-01-27
  • 1970-01-01
  • 1970-01-01
  • 2018-11-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多