0

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

Lucas Niewohner
  • 327
  • 3
  • 11
  • When you tested to invoke the direct method in Azure Portal, had you run the device client app which registered the delegate for the named method?If you had run it, could you please provide the related codes? – Michael Xu Jan 15 '18 at 05:32
  • 2
    @Lucas The Direct Method requires to use a connection oriented protocol between the device and Azure IoT Hub such as the mqtt or amqp. In other words, your device must be connected for device method call. Use an azure portal Query Explorer for query all devices included their connectionState property. Note, that the result page doesn't have a refresh button, so close the page before the next Execute query. – Roman Kiss Jan 15 '18 at 09:11
  • @Lucas For test purposes, you can use a MQTTBox client http://workswithweb.com/mqttbox.html or Azure IoT Hub Tester https://www.codeproject.com/Articles/1173356/Azure-IoT-Hub-Tester for simulating devices with MQTT protocol. – Roman Kiss Jan 15 '18 at 09:17
  • @MichaelXu-MSFT... I have added the code – Lucas Niewohner Jan 16 '18 at 14:47
  • @RomanKiss yes, I can understand that the device must be connected in order for it to accept network-based direct method calls (seems pretty darn logical). My trouble is in actually opening the connection. Azure cannot do so directly, since my local internet connection has no public-facing IP address, so the connection must somehow be made as some sort of persistent tunnel from the Raspberry Pi to the rest of the world. My question (phrased a bit more specifically) is: how could I create a persistent connection for MQTT through some sort of SSH (or other) tunnel? – Lucas Niewohner Jan 17 '18 at 03:40

1 Answers1

2

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";
        });
    }
Michael Xu
  • 4,382
  • 1
  • 8
  • 16
  • I've had a bit a breakthrough, and it seems as though that, since the UI isn't responding, the Device Client isn't even getting created. Since my assumptions about what was failing seem so incredibly wrong, I've decided to create an entirely different question, which can be found [here](https://stackoverflow.com/questions/48414311/raspberry-pi-3-running-windows-10-core-with-custom-program-shows-crossed-out-x) – Lucas Niewohner Jan 24 '18 at 03:23