问题
I have a nice little project here in my basement, consisting of an LED wired into a Raspberry Pi 3. Quite complex, yes, I know. Beyond that, this Raspberry Pi is running Windows 10 IoT Core, and my goal is to make it possible to toggle that LED off and on by means of a Direct Method from the Azure Iot Hub service.
Asides from an odd UI issue that I'll be asking about in a separate question, this system is more or less working properly. I've written up a UWP project, and it toggles the LED just fine. Back when the UI was working, which it was, at one point, I was able to toggle the light with a clickable button. Again, very complex. Anyway, The Azure IoT Hub is up and running, the Raspberry Pi properly provisioned, and the Direct Method set up on the device. I was intending to use Azure Functions to make it possible to use an API call to call the Direct Method, but after experiencing problems I simplified that out, and am now testing by means of the Azure Portal's built in "Direct Method" dialog.
My problem is this: when I call the direct method from the Azure Portal's tool (which is somewhat annoying to use, by the way), and wait 20 seconds for the 5-seconds-and-then-it's-gone result pop up, I get a message saying that the call "Timed out while waiting to connect". The exact error is as follows:
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
While I'm not 100% certain about the "not registered" part, I'm fairly certain this problem stems from my internet router. Not too long ago, my household switched over to Hughesnet as our ISP, and in doing so we lost the ability to accept inbound traffic (i.e., the network is now closed to outside requests), and we have no ability to set up NAT to forward a port or two in either (there is no publicly accessible IP address, period). While I'd love to grumble about more than a few aspects of HughesNet's service, that's another matter.
For projects similar to this one, I have been able to set up a persistent workaround, such a reverse SSH tunnel. The difficulty is, I'm not particularly sure how to do that with this project.
My question is: Is there a way to get my Raspberry Pi here to accept a Direct Method call from the Azure IoT Hub? Some sort of SSH tunnel, perhaps?
Update: Code
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;
});
}
}
}
Thanks! Lucas Niewohner
回答1:
I have tested the issue with the code you provided, even though it is incomplete, I modified slightly so that it can be run.The method ToggleLED can be called from Azure IoT Hub. In your code, the method WriteMessage need to use Dispatcher to update the TextBox,because when the direct method called,it runs in a new thread(not in UI thread). For your question, Direct methods follow a request-response pattern and are meant for communications that require immediate confirmation of their result, usually interactive control of the device, for example to turn on a fan.I am a little confused about the meaning of persistent connection.When you connect the Azure IoT Hub using MQTT, the connection will not be closed if there is no close action, or the network keeps alive,there is some other exception. In addition, IoT Hub does not support QoS 2 messages. If a device app publishes a message with QoS 2, IoT Hub closes the network connection.
Code:
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";
});
}
来源:https://stackoverflow.com/questions/48219258/trouble-connecting-a-windows-10-iot-core-device-to-azure-iot-hub-from-behind-clo