IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Windows 10 IoT 开发:Azure远程控制开关灯泡

    汪宇杰发表于 2016-04-10 04:06:01
    love 0

    看过美剧《生活大爆炸》的朋友可能记得,里面有一个场景就是那群物理学家搞了个互联网控制的灯泡。从家里电脑上按下开关,信号绕地球一大群回来,点亮家里的台灯。虽然很屌丝,但是今天我成功在树莓派3上用Windows 10 IoT Core和Microsoft Azure实现了这个实验。

    首先,Azure的参考文献有两篇,强烈建议大家先阅读,本文不会再重复这两篇文章里的基础步骤:

    Get started with Azure IoT Hub for .NET

    https://azure.microsoft.com/en-us/documentation/articles/iot-hub-csharp-csharp-c2d/

    一、创建Azure IoT Hub和注册设备

    创建Iot Hub的步骤参考Get started with Azure IoT Hub for .NET里的"Create an IoT Hub"章节,步骤完全一致。

    注册设备我用了一个简单的方法,不用去做"Create a device identity"里的步骤。微软官方有个开源工具 https://github.com/Azure/azure-iot-sdks/tree/master/tools/DeviceExplorer 

    下载之后,到主界面填写连接字符串,然后点击Update按钮。

    连接字符串在Azure Portal里有可以拿到。参考文章里也有说明。主要用到的就是Hostname,SharedAccessKeyName,SharedAccessKey

    格式如下:

    HostName=你的HUB名称.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=你的KEY

    然后在Management标签下面点击Create,填写你的设备名称,勾选"Auto Generate Keys"注册你的树莓派。

    注册成功以后你应该能看到这个设备的信息被加入表格了,然后就可以进行下一步了。

    二、树莓派物理连接

    需要材料:LED灯泡一只、杜邦线2根

    LED 树莓派
    长脚 DC 3.3v
    短脚 GPIO 04

    注意,如果你用的LED额定电压不是3.3V的,请加上对应的电阻,以防烧掉。

    连接完成看上去就像这样:

    三、爆代码

    我们一共需要2个工程,第一个是树莓派上用的,这个是做接收端的,用来接受Azure发来的信息并控制LED的开关。另一个是电脑上用的,做“遥控器”,向Azure发送控制信息。

    最后工程的结构应该长的是这样的:

    树莓派端工程:

    用VS2015创建一个UWP工程,比如AzureRemoteLight,然后加入"Windows IoT Extensions for the UWP"的引用。

    还需要添加的NuGet引用是:

    "Microsoft.Azure.Devices.Client": "1.0.5"

    同时建议大家把依赖项Newtonsoft.Json更新到最新版,目前是

    "Newtonsoft.Json": "8.0.3"

    我自己的这个工程还引用了MvvmLight和我自己的Edi.UWP.Helpers,当然这些不是必须的,只是用来装逼的。完整的project.json:

    {
      "dependencies": {
        "Edi.UWP.Helpers": "1.0.11",
        "Microsoft.Azure.Devices.Client": "1.0.5",
        "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0",
        "MvvmLight": "5.2.0",
        "Newtonsoft.Json": "8.0.3"
      },
      "frameworks": {
        "uap10.0": {}
      },
      "runtimes": {
        "win10-arm": {},
        "win10-arm-aot": {},
        "win10-x86": {},
        "win10-x86-aot": {},
        "win10-x64": {},
        "win10-x64-aot": {}
      }
    }

    画界面:

    其实对于单纯完成开灯关灯这个功能来说,界面不是必要的。当然,有界面的话,逼格更高一点。我画了一个这样的界面,看起来很牛逼:

    主要就是2个地方:Azure IoT Hub Connection显示是否能成功连接到Azure(稍后会发一个message到azure更新这个label的状态)

    CloudToDeviceLog显示从Azure接受到的控制信息。

    完整XAML代码:

    <Page
        x:Class="AzureRemoteLight.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:AzureRemoteLight"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
        mc:Ignorable="d">
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Padding="12">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            
            <Image Source="Assets/cloud-hero.png" Grid.Row="0" Height="80" HorizontalAlignment="Right" VerticalAlignment="Top" />
            
            <StackPanel Grid.Row="0">
                <TextBlock Text="Windows 10 IoT + Microsoft Azure" Style="{StaticResource SubtitleTextBlockStyle}" />
                <TextBlock Text="Remote Light Control" Style="{StaticResource SubheaderTextBlockStyle}" />
            </StackPanel>
    
            <Grid Grid.Row="1" Margin="0,20,0,0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                
                <Image Grid.Row="0" Grid.RowSpan="2" Source="Assets/Windows_Insiders_Flag.png" HorizontalAlignment="Right" Height="300" VerticalAlignment="Bottom" />
                
                <StackPanel Orientation="Horizontal" Grid.Row="0">
                    <TextBlock Text="Azure IoT Hub Connection: " />
                    <TextBlock Text="{Binding IsAzureConnected}" />
                </StackPanel>
                
                <Border Grid.Row="1" BorderBrush="#CCC" BorderThickness="1" Margin="0,10,0,0" Padding="10">
                    <TextBlock TextWrapping="Wrap" Text="{Binding CloudToDeviceLog}" Foreground="#CCC" FontFamily="Consolas" />
                </Border>
            </Grid>
        </Grid>
    </Page>
    

    ViewModel代码

    首先,我们需要定义控制LED开关的两个对象,我的个人习惯是定义为属性,然后在执行的时候实例化。

    #region GPIO Settings
    
    public GpioController GpioController { get; }
    
    public GpioPin LedPin { get; }
    
    #endregion
    

    然后还有连接Azure IoT Hub的属性

    #region Azure IoT Hub Settings
    
    public DeviceClient DeviceClient { get; }
    
    public string IotHubUri { get; } = "你的HUB名称.azure-devices.net";
    
    public string DeviceKey { get; } = "设备对应的KEY";
    
    public string DeviceId => "你的设备名称";
    
    #endregion
    

    这些属性值可以到Device Explorer里的Management标签下面去拿。

    最后还有负责界面显示的两个属性

    #region Display Fields
    
    private bool _isAzureConnected;
    private string _cloudToDeviceLog;
    
    public bool IsAzureConnected
    {
        get { return _isAzureConnected; }
        set { _isAzureConnected = value; RaisePropertyChanged(); }
    }
    
    public string CloudToDeviceLog
    {
        get { return _cloudToDeviceLog; }
        set { _cloudToDeviceLog = value; RaisePropertyChanged(); }
    }
    
    #endregion
    

    然后在构造函数里初始化DeviceClient和GPIO端口

    public MainViewModel()
    {
        DeviceClient = DeviceClient.Create(IotHubUri, new DeviceAuthenticationWithRegistrySymmetricKey(DeviceId, DeviceKey));
    
        GpioController = GpioController.GetDefault();
        if (null != GpioController)
        {
            LedPin = GpioController.OpenPin(4);
            LedPin.SetDriveMode(GpioPinDriveMode.Output);
        }
    }
    

    还要写一个方法,向Azure发送一条信息,试试看连接是不是成功

    public async Task SendDeviceToCloudMessagesAsync()
    {
        try
        {
            var telemetryDataPoint = new
            {
                deviceId = DeviceId,
                message = "Hello"
            };
            var messageString = JsonConvert.SerializeObject(telemetryDataPoint);
            var message = new Message(Encoding.ASCII.GetBytes(messageString));
    
            await DeviceClient.SendEventAsync(message);
            Debug.WriteLine("{0} > Sending message: {1}", DateTime.Now, messageString);
    
            IsAzureConnected = true;
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
    

    别忘了在MainPage.xaml.cs里调用一下:

    public sealed partial class MainPage : Page
    {
        private MainViewModel _vm;
    
        public MainPage()
        {
            this.InitializeComponent();
    
            _vm = this.DataContext as MainViewModel;
    
            Loaded += async (sender, args) =>
            {
                // send device connected message
                await _vm.SendDeviceToCloudMessagesAsync();
            };
        }
    

    运行

    首先要保证树莓派的系统时间是正确的,这步是必须的,不然SAS Token是要过期的。如果时间不对,重启几次设备应该就能同步正确。w32tm /resync /force这条明令是逗你们玩的,千万别相信它有用。

    在运行之前,打开Device Explorer的Data标签,然后点击Monitor,以接受树莓派发送到Azure的信息。

    然后用ARM, Remote Machine的配置到树莓派上去部署和执行,成功的话,Device Explorer里会接受到这样的信息:

    然后我们就能继续爆代码了。

    在ViewModel里再加个方法,用来接收Azure发过来的信息,然后根据信息内容向LED输出高低电平实现控制灯泡开关:

    public async Task ReceiveCloudToDeviceMessageAsync()
    {
        CloudToDeviceLog = "Receiving events...";
        Debug.WriteLine("\nReceiving cloud to device messages from service");
    
        while (true)
        {
            Message receivedMessage = await DeviceClient.ReceiveAsync();
            if (receivedMessage == null) continue;
    
            var msg = Encoding.ASCII.GetString(receivedMessage.GetBytes());
            CloudToDeviceLog += "\nReceived message: " + msg;
    
            if (msg == "on")
            {
                LedPin.Write(GpioPinValue.Low);
            }
    
            if (msg == "off")
            {
                LedPin.Write(GpioPinValue.High);
            }
    
            await DeviceClient.CompleteAsync(receivedMessage);
        }
    }
    

    消息用的是string类型,如果内容是"on"就开灯,如果是"off"就关灯。

    当然,也要在MainPage.xaml.cs里再调用一下这个方法

    public sealed partial class MainPage : Page
    {
        private MainViewModel _vm;
    
        public MainPage()
        {
            this.InitializeComponent();
    
            _vm = this.DataContext as MainViewModel;
    
            Loaded += async (sender, args) =>
            {
                // send device connected message
                await _vm.SendDeviceToCloudMessagesAsync();
    
                // receive remote light control events
                await _vm.ReceiveCloudToDeviceMessageAsync();
            };
        }
    }
    

    现在再执行,就应该能在树莓派上看到这样的画面:

    至此,树莓派端工作就完成了。

    控制端工程

    创建一个WPF工程,比如LightController,不能是UWP(马上解释)。然后添加Microsoft.Azure.Devices的NuGet包。这个包不支持UWP,所以建不出UWP的控制端,Stupid!

    然后在MainWindow里画两个按钮,分别用来发送开灯的消息和关灯的消息:

    <Window x:Class="LightController.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:LightController"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                <Button x:Name="BtnTurnOn" Content="Turn Light On" HorizontalAlignment="Center" Click="BtnTurnOn_OnClick" />
                <Button x:Name="BtnTurnOff" Content="Turn Light Off" Margin="0,10,0,0" Click="BtnTurnOff_OnClick" />
            </StackPanel>
        </Grid>
    </Window>
    

    后台代码:

    public partial class MainWindow : Window
    {
        static ServiceClient serviceClient;
        static string connectionString = "你的IOT HUB连接字符串";
    
        public MainWindow()
        {
            InitializeComponent();
    
            serviceClient = ServiceClient.CreateFromConnectionString(connectionString);
        }
    
        private async Task TurnLight(bool isOn)
        {
            await SendCloudToDeviceMessageAsync(isOn);
        }
    
        private static async Task SendCloudToDeviceMessageAsync(bool isOn)
        {
            var commandMessage = new Message(Encoding.ASCII.GetBytes(isOn ? "on" : "off"));
            await serviceClient.SendAsync("你的设备名称", commandMessage);
        }
    
        private async void BtnTurnOn_OnClick(object sender, RoutedEventArgs e)
        {
            await TurnLight(true);
        }
    
        private async void BtnTurnOff_OnClick(object sender, RoutedEventArgs e)
        {
            await TurnLight(false);
        }
    }
    

    这里发送的消息也是string,和树莓派端的匹配,on表示开灯,off表示关灯。

    IOT HUB的连接字符串就是Device Explorer里的Configruation标签里用的连接字符串,它们是一模一样的!

    四、运行

    因为有控制端和接收端,所以我们要同时启动2个工程。在VS里,对solution名称点右键,选择属性,然后这样搞:

    启动后,在电脑上点击WPF控制端里的两个按钮就能控制LED开关了,并且在树莓派的屏幕上也能显示接受的控制信息:

    最后,完整代码在这里:

    https://github.com/EdiWang/Windows-IoT-AzureRemoteLight 



沪ICP备19023445号-2号
友情链接