导语
3年前,我写过一篇《Windows 10 IoT Core Azure 远程控制LED》,实现了《生活大爆炸》中的注孤生实验,让信号从家里出发,绕地球转一圈,经过微软美国数据中心,返回家里点亮树莓派上连接的一个 LED 灯泡。然而3年后的现在,Windows 10 IoT Core 以及UWP 已经冰冰凉透心凉,甚至微软至今也没有支持树莓派4的 Windows 版本。我只能苟且偷生,委曲求全,开荤 Linux,使用 .NET Core 重现了这个实验。微软和社区对于 .NET Core IoT 非常积极,提供了比 UWP 好用不少的 IoT 基础库,让我这个项目迁移非常方便。
.NET Core IoT 全家桶:https://github.com/dotnet/iot
在开始之前,如果你还没有在树莓派上配置.NET Core环境,可以参考我之前写的:
在树莓派4上安装 .NET Core 3.0 运行时及 SDK
“自启动”树莓派上的 .NET Core 3.0 环境
基本原理
我们要从自己电脑上发送信号到 Azure IoT Hub,树莓派上的.NET Core程序会监听消息,并控制LED开关。
创建 Azure IoT Hub
请根据微软文档创建 IoT Hub。最后记下你的连接字符串,看起来就像这样:
HostName=<Your Hub Name>.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=<Your Key>
微软文档:https://docs.microsoft.com/en-us/azure/iot-hub/quickstart-send-telemetry-dotnet?WT.mc_id=AZ-MVP-5002809
下载并安装 Azure Device Explorer (https://aka.ms/aziotdevexp)
把连接字符串粘贴到 Configuration / Connection Information里,点击 Update 按钮。
切换到 Management 选项卡,点击 Create ,输入你的设备名称,勾选 Auto Generate Keys
树莓派物理连接
将一个LED连接到树莓派:
长脚连接到 GPIO 17
短脚连接到接地(GROUND)
附:树莓派4 GPIO 全家桶图(来自网络)
复制粘贴
创建一个 .NET Core 3.0 控制台程序。添加 Microsoft.Azure.Devices.Client 以及 System.Device.Gpio NuGet 包。前者用于和Azure IoT Hub通讯,后者用于控制 GPIO 来开关LED灯泡。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.21.1" />
<PackageReference Include="System.Device.Gpio" Version="1.0.0" />
</ItemGroup>
</Project>
Program.cs 最终代码如下。记得把里面的字符串常量换成你自己的 Azure IoT Hub 信息。
using System;
using System.Device.Gpio;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Client.Exceptions;
namespace AzureLightControl
{
class Program
{
private const string IotHubUri = "<Your Hub Name>.azure-devices.net";
private const string DeviceKey = "<Your Key>";
private const string DeviceId = "<Your Device ID>";
private const int Pin = 17;
private static CancellationToken _ct;
static async Task Main(string[] args)
{
Console.WriteLine("------------------------------");
Console.WriteLine(" Azure IoT Hub Light Control");
Console.WriteLine("------------------------------");
var cts = new CancellationTokenSource();
_ct = cts.Token;
Console.CancelKeyPress = (sender, eventArgs) =>
{
Console.WriteLine($"{DateTime.Now} > Cancelling...");
cts.Cancel();
eventArgs.Cancel = true;
};
try
{
var t = Task.Run(Run, cts.Token);
await t;
}
catch (IotHubCommunicationException)
{
Console.WriteLine($"{DateTime.Now} > Operation has been canceled.");
}
catch (OperationCanceledException)
{
Console.WriteLine($"{DateTime.Now} > Operation has been canceled.");
}
finally
{
cts.Dispose();
}
Console.ReadKey();
}
private static async Task Run()
{
using var deviceClient = DeviceClient.Create(IotHubUri, new DeviceAuthenticationWithRegistrySymmetricKey(DeviceId, DeviceKey));
using var controller = new GpioController();
controller.OpenPin(Pin, PinMode.Output);
Console.WriteLine($"{DateTime.Now} > Connected to the best cloud on the planet.");
Console.WriteLine($"Azure IoT Hub: {IotHubUri}");
Console.WriteLine($"Device ID: {DeviceId}");
Console.WriteLine($"{DateTime.Now} > GPIO pin enabled for use: {Pin}");
while (!_ct.IsCancellationRequested)
{
Console.WriteLine($"{DateTime.Now} > Waiting new message from Azure...");
var receivedMessage = await deviceClient.ReceiveAsync(_ct);
if (receivedMessage == null) continue;
var msg = Encoding.ASCII.GetString(receivedMessage.GetBytes());
Console.WriteLine($"{DateTime.Now} > Received message: {msg}");
switch (msg)
{
case "on":
Console.WriteLine($"{DateTime.Now} > Turn on the light.");
controller.Write(Pin, PinValue.High);
break;
case "off":
Console.WriteLine($"{DateTime.Now} > Turn off the light.");
controller.Write(Pin, PinValue.Low);
break;
default:
Console.WriteLine($"Unknown command: {msg}");
break;
}
await deviceClient.CompleteAsync(receivedMessage, _ct);
}
}
}
}
这里面,deviceClient.ReceiveAsync() 负责监听 Azure IoT Hub 有没有发来新消息,并处理收到的消息,完事后调用 CompleteAsync() 告诉 Azure 这个消息已经处理好了,这样的话设备再次连接到 Azure 就不会重复处理这条消息。
处理消息十分直接,读取消息内容为字符串,如果写着"on",就向GPIO 17输出高电位,即点亮灯泡。如果是"off",就输出低电位关闭灯泡。
能跑就行
将源代码或者发布后的dll全家桶复制到树莓派。然后在树莓派上用 .NET CLI 启动程序。在 PC 上,通过 Device Explorer 向设备发送 on 或 off 消息。
现在你学会了通过互联网控制家里灯泡开关的魔术: