物联网在家用自动化中的应用
我建立了自己的物联网 (IoT) 家庭自动化系统,该系统通过四种不同类型的微控制器控制着30种不同的设备,拥有近150条命令。本文讨论了物联网设计模式以及我所使用的设计模式中吸取的经验教训。
引言
我们生活在一个激动人心的时代,越来越多的日常物品“设备”变得智能化!“设备”拥有传感器,可以与其他“设备”通信,并可以对更多“设备”进行控制。物联网(IoT)正以巨大的方式降临到我们身边,人们正在迅速发明新的小工具来改善我们的生活。能够通过网络通信的微控制器的价格持续下降,开发人员现在可以廉价地修修补补,建造各种东西。开发人员和硬件爱好者不再需要等待别人发明或建造所有“酷”的东西!
物联网的价值在于数据和控制。对于家庭自动化而言,拥有事件日志,了解家庭成员何时做了某事,例如何时回家或何时打开壁炉,是非常有用的。物联网的控制方面对于家庭自动化来说真的非常棒。当我们带着互联网连接在世界任何地方旅行时,能够通过我的物联网猫玩具与猫玩耍真是太棒了!
我的物联网项目演进
我进军物联网领域最初是想在我办公室里时与家里游泳池里的孩子们玩耍。我用 Netduino 微控制器、几个伺服器、一个电磁阀和一根水管制作了一把物联网水枪。这个项目的详细信息可以在以下文章中阅读:
在水枪项目取得成功后,我开始构建其他可以通过互联网在家中控制的设备。我使用了一个中央 Netduino 微控制器,并开始为许多设备添加控制功能到同一个微处理器上。我穿墙、穿屋下、穿阁楼布线,以控制车库门、浇灌花园和控制壁炉等设备。我构建了可能是第一个用于壁炉的物联网控制系统。为了完善这个项目,我在一个二维码中嵌入了一个URL,该URL将打开一个显示倒计时时间的网页。页面上的JavaScript调用了一个Web API,该API充当代理,通过网络向Netduino微控制器传递消息,微控制器再驱动电磁阀打开壁炉的燃气。该项目的详细信息可以在以下文章中阅读:
将 jQuery Mobile 与 MVC 和 Netduino 结合用于家庭自动化
我有一个想法,要在万圣节远程控制院子里的怪物,但不想把电线从我现有的Netduino一直拉到房子前面。解决方案是购买一个20美元的以太网桥和第二个Netduino。
让每个设备都智能化
将电线穿过房子连接到中央微控制器变得极其笨重。我还开始担心,不想在房子里建造太多东西,以至于搬家时无法带走。随着物联网运动的兴起,我意识到我可以让房子里的每个“设备”都变得智能并接入网络,这样我就不需要额外布线。让每个“设备”都智能有很多好处,包括灵活地移动设备。
Logical Living 开源家庭自动化系统
我用廉价的微控制器、定制电路和其他大多家用组件构建了 Logical Living 开源家庭自动化系统。代码通过这篇和其他 Code Project 文章开源。移动网页界面有近一百五十个按钮,用于控制我房子里的功能。我有机地为每个“设备”构建了控制,并开始拥有一个智能化的房子。最好的部分是它没有花费很多钱,甚至我的笔记本电脑都比我的家庭自动化系统更贵。有多种用户界面,包括手势和语音,可以在以下文章中阅读:
使用Microsoft Kinect点云和语音识别进行家庭自动化
Logical Living 中使用的微控制器
我的家庭自动化系统使用了四种不同类型的微控制器。我了解了每种类型的优缺点。总共,我用近150个功能控制着30个设备。我还了解了不同物联网设计模式的优缺点。
Netduino Plus 2 - 万圣节
Netduino 是一个基于 .NET Micro Framework 的优秀开源电子原型平台。我正在使用 Netduino Plus 2 微控制器来控制壁炉、4个花园、车库、水枪、包括怪物在内的5个万圣节物品和一个猫玩具。Netduino 的代码易于维护,因为它是面向对象的,并且可以进行带断点等的真实调试。Netduino Plus 2 微控制器内置以太网适配器用于网络通信。此适配器不是 Wi-Fi,但可以通过大约 20 美元添加第三方 Wi-Fi 以太网桥。Netduino Plus 2 的外形尺寸与 Arduino 相同,为 Arduino 构建的扩展板可以与 Netduino 一起使用。Netduino 的社区不如 Arduino 那么庞大,但我更喜欢 Netduino 而不是大多数 Arduino 微控制器用于复杂项目,因为它在运行结构良好的面向对象代码方面的优势,而且大多数 Arduino 微控制器没有真正的调试功能。我的公司进行了大量的 C# 开发,因此能够在 .NET Micro Framework 上用 C# 编程是一个额外的优势。VB.Net 在该平台上也受支持,但大多数人使用 C# 编程。
我的物联网家庭自动化系统中的一个 netduino 负责控制 5 个万圣节物品。有一个僵尸在地上摩擦,一个骷髅跳起来,一个头骨通过绳子弹射出去,一个压缩空气惊吓,以及一个主要功能是棺材里的鬼魂,它有伺服器可以以不同角度平移和倾斜头部。所有设备都可以通过移动网站或通过 Microsoft Kinect v2 应用程序控制,该应用程序可以感应孩子们的位置并使场景根据孩子们的位置做出反应。当孩子们沿着展示品旁的人行道行走时,棺材里的鬼魂会转头看着其中一个孩子。
观看这个物联网万圣节视频,获取更多乐趣,包括骷髅发射器功能!
LogicalLiving.Netduino
项目为它所控制的一切都有一个类。此外,还有用于以太网通信、伺服系统以及一个带两个伺服系统的泛倾斜类。这些类在整个应用程序中都有使用,例如,PanTilt
类被重用于水枪、鬼头控制,以及本文后面将要讨论的猫玩具两次。
Halloween 类有私有变量,用于控制它所控制的一切。这些私有变量配置 Netduino 上的引脚为输入或输出。
private OutputPort _relayZombie = Config.ReusePins.GetInstance().OutputPort2;
private OutputPort _relaySkeleton = new OutputPort(Pins.GPIO_PIN_A4, false);
private OutputPort _launchSkullMotor = new OutputPort(Pins.GPIO_PIN_A3, false);
private OutputPort _airScare = new OutputPort(Pins.GPIO_PIN_D13, false);
PanTilt _ghost = Config.ReusePins.GetInstance().PanTilt1;
private InputPort _launchSkullLimitSwitch = new InputPort(Pins.GPIO_PIN_A2, true, Port.ResistorMode.PullUp);
我正在使用一个程序在多个Netduino上运行,其中不同的Netduino承担不同的职责。本文的“代码维护”部分对此架构有更多详细信息。上面的代码有一个_ghost
对象,它从ReusePins
单例类中获取引脚配置,以支持当程序在具有不同职责的不同Netduino上运行时,将这些引脚用于不同的功能。
public class ReusePins
{
#region Private Variables
private static ReusePins _reusePins;
#endregion
#region Public Static Methods
public static ReusePins GetInstance()
{
if (_reusePins == null)
{
_reusePins = new ReusePins();
_reusePins.PanTilt1 = new PanTilt(Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D6);
_reusePins.PanTilt2 = new PanTilt(Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D5);
_reusePins.OutputPort1 = new OutputPort(Pins.GPIO_PIN_D3, false);
_reusePins.OutputPort2 = new OutputPort(Pins.GPIO_PIN_A5, false);
}
return _reusePins;
}
#endregion
#region Public Properties
public PanTilt PanTilt1;
public PanTilt PanTilt2;
public OutputPort OutputPort1;
public OutputPort OutputPort2;
#endregion
}
下面的构造函数从配置类读取数据来设置伺服器的限制。将所有配置值放在一个自己的类中是一个好主意,这样就可以在一个地方进行更新。
public Halloween()
{
_ghost.Tilt.DegreeMax = (int)Config.Halloween.GhostTiltDegreeMax;
_ghost.Tilt.DegreeMin = (int)Config.Halloween.GhostTiltDegreeMin;
_ghost.Tilt.InvertAngle = true;
_ghost.Pan.DegreeMax = (int)Config.Halloween.GhostPanDegreeMax;
_ghost.Pan.DegreeMin = (int)Config.Halloween.GhostPanDegreeMin;
_ghost.Pan.InvertAngle = false;
_ghost.SweepSpeedMilliseconds = Config.Halloween.GhostSweepSpeedMilliseconds;
}
有控制所有怪物动作的方法。下面的方法启动僵尸移动,并通过我编写的 Time
类异步调用一个私有方法,以便轻松处理异步定时事件。
public void MoveZombieTime(int seconds)
{
this.MoveZombie = true;
Time.RunOnDelay(TurnOffZombieCallback, seconds * 1000);
}
private void TurnOffZombieCallback()
{
this.MoveZombie = false;
}
AirScare
是我添加的最新功能。它以极少的工作量和代码量带来了相当大的影响。我发现你可以使用一个10美元的电磁阀,它原本用于浇灌花园,现在可以用来释放压缩空气。我和孩子们建造了一个很酷的火箭发射器,我在万圣节重新利用这些零件,通过一根穿过灌木丛的软管喷射压缩空气,当孩子们靠近时,空气会吹向他们。空气喷射速度非常快,会发出吓人的轰鸣声!在万圣节,我随身带着手机,每当我想用110磅压缩空气的半秒钟喷射和美妙的轰鸣声吓唬孩子们时,我就会按下按钮。
public void AirScare()
{
_airScare.Write(true);
Thread.Sleep(500);
_airScare.Write(false);
}
我创建了一个惊吓程序,可以同时移动所有怪物。所有这些功能都可以通过移动网络界面从任何地方打开。这是我最常运行的,可以从远处吓唬人。MoveToPosition
是 PanTilt
类的一个方法,我最初是为水枪项目添加的,但它在任何你想缓慢扫入某个位置的时候都很方便。下面的代码将每度移动的扫描毫秒数设置为15毫秒。我编写的用于通过脉冲宽度调制(PWM)控制伺服系统的类也包含在源代码中。
public void Scare()
{
MoveZombieTime(20);
MakeSkeletonJump();
_ghost.SweepSpeedMilliseconds = 15;
Time.RunOnDelay(LaunchSkull, 4000);
for (int count = 0; count < 2; count++)
{
_ghost.MoveToPosition(170, 120);
_ghost.MoveToPosition(170, 60);
_ghost.MoveToPosition(10, 90);
_ghost.MoveToPosition(10, 120);
_ghost.MoveToPosition(10, 170);
_ghost.MoveToPosition(50, 50);
_ghost.MoveToPosition(110, 110);
_ghost.MoveToPosition(90, 90);
}
_ghost.DisengageServos();
}
下图展示了我如何将头骨安装到平移和倾斜伺服器上,甚至还为眼睛添加了 LED!
Netduino Plus 2 - 猫玩具
我们有一只被宠坏的猫,我找到了一个方法,在物联网的帮助下,让她更受宠爱!我制作了一个在线控制的物联网猫玩具,这样我就可以在任何有互联网连接的地方和猫玩耍。这个猫玩具有一个 Netduino Plus 2 微控制器和一个 Netgear 以太网桥,这样我就可以通过一个充当互联网网关的代理,通过 Wi-Fi 向猫玩具发送消息。它是一个独立的单元,唯一的外部电线是电源线。
激光器安装在一个平移和倾斜伺服组件上。控制猫玩具的方法在 LogicalLiving.Netduino
项目的 CatToy
类中。激光器可以以随机模式移动,但我发现如果猫永远抓不到激光器,它就会失去兴趣。激光器会扫到一个随机位置,然后有 25% 的时间会暂停一段随机的短时间,然后再扫到下一个位置。
public void RandomLaserPattern(int repeat)
{
this.FireLaser = true;
Random rnd = new Random();
for (int count = 0; count < repeat; count++)
{
_laser.SweepSpeedMilliseconds = rnd.Next(100);
_laser.MoveToPosition(rnd.Next(175), rnd.Next(60) + 90);
// Pause occasionally for half a second
if (rnd.Next(100) > 75)
Thread.Sleep(rnd.Next(500));
}
_laser.SweepSpeedMilliseconds = 50;
this.FireLaser = false;
_laser.DisengageServos();
}
还有一个用绳子连接到伺服器上的老鼠木偶,它会按照设定的模式移动。
public void MouseLeftRight()
{
_mouse.MoveToPosition(90, 90);
_mouse.Pan.Angle = 30;
Thread.Sleep(600);
_mouse.Pan.Angle = 150;
Thread.Sleep(600);
_mouse.Pan.Angle = 30;
Thread.Sleep(600);
_mouse.MoveToPosition(90, 90);
_mouse.DisengageServos();
}
public void MouseUpDown()
{
_mouse.MoveToPosition(90, 90);
_mouse.Tilt.Angle = 60;
Thread.Sleep(600);
_mouse.Tilt.Angle = 120;
Thread.Sleep(600);
_mouse.Tilt.Angle = 60;
Thread.Sleep(600);
_mouse.MoveToPosition(90, 90);
_mouse.DisengageServos();
}
值得注意的是,控制猫玩具激光、猫玩具老鼠木偶、万圣节鬼头或水枪的代码是多么相似。Netduino 的面向对象代码使其功能非常强大,并能实现大量可重用代码。
Areon Z-Stick Z-Wave USB 适配器
Z-Wave 是一种专为家庭自动化设计的无线通信协议。市面上有大量的商用 Z-Wave 设备,有些甚至在零售连锁店的货架上都能买到。我的家庭自动化系统包括 15 个 Z-Wave 设备,其中大部分是灯和其他 120 VAC 供电设备。Z-Wave 产品使用低成本、低功耗的射频收发芯片设计,通常控制一个灯或插座的成本约为 50 美元。所有设备都通过网状网络发送消息。网状网络利用智能路由高效地将消息传递到目标 Z-Wave 设备节点。消息具有“冗长”的特性,以支持路由能力并确认消息已到达正确的 Z-Wave 节点。Z-Wave 设备需要足够近才能通过其低功耗无线电进行通信,范围约为 100 英尺或 40 米。Z-Wave 设备本身可以构成一个出色的局域网,但需要一个代理作为网关才能在互联网上进行控制。我正在使用一个连接到我用 Web API 编写的代理的 Areon Z-Stick Z-Wave USB 适配器。
LogicalLiving.ZWave
项目包含了所有与 Z-Wave USB 适配器通信的方法,这些方法由 Web API 和 LogicalLiving.Zwave.DesktopMessenger
Windows 窗体应用程序使用。我构建了桌面消息传递窗体应用程序,以了解支持 Z-Wave 消息所需的发送和接收的字节数组。UI 中显示的字节数组消息帮助我弄清楚如何修改 Areon Z-Stick Z-Wave USB 适配器以发送和接收正确的字节数组。桌面消息传递程序也有助于排查 Z-Wave 设备不工作的问题。我现在最常使用桌面消息传递程序来解决的问题是找出设备的节点。
LogicalLiving.ZWave
项目的 CommunicateWithZWave
类的第一步是接收包含 deviceNode
和要设置的 deviceState
的消息。
public string Message(DeviceNode deviceNode, DeviceState deviceState)
{
if (_serialPort == null || !_serialPort.IsOpen)
OpenSerialPort();
string message = "";
if (deviceNode == DeviceNode.All)
{
UpdateStateOnAllDevices(deviceState);
}
else
{
message = AssuredZwaveMessage(deviceNode, deviceState);
}
return message;
}
如果串口尚未打开,则会打开串口并设置串口数据接收事件处理程序。
private void OpenSerialPort()
{
_serialPort = new SerialPort();
//You can look up the COM port number in the computer devices.
//It shows up as a CP2102 USB to UART Bridge Controller in the computer devices.
_serialPort.PortName = "COM3";
_serialPort.BaudRate = 115200;
_serialPort.Parity = Parity.None;
_serialPort.DataBits = 8;
_serialPort.StopBits = StopBits.One;
_serialPort.Handshake = Handshake.None;
_serialPort.DtrEnable = true;
_serialPort.RtsEnable = true;
_serialPort.NewLine = System.Environment.NewLine;
_serialPort.Open();
_serialPort.DataReceived += new SerialDataReceivedEventHandler(_serialPort_DataReceived);
}
AssuredZwaveMessage
是一个私有方法,用于向设备节点发送消息以设置设备状态。它有一个重试计数器,会尝试一百次,然后放弃。
private string AssuredZwaveMessage(DeviceNode deviceNode, DeviceState deviceState)
{
string returnMessage = "Message not sent!";
if (_serialPort.IsOpen)
{
byte[] message = new byte[] { 0x01, 0x09, 0x00, 0x13, (byte)deviceNode, 0x03, 0x20, 0x01,
(byte)deviceState, 0x05, 0x00 };
int retryCount = 0;
while (!SendMessage(message) && retryCount++ < 100)
{
Thread.Sleep(100);
}
returnMessage = ByteArrayToString(message);
}
return returnMessage;
}
SendMessage
方法将字节数组发送到 USB 适配器的串行端口。除了确认消息 (0x06) 之外,所有消息都需要在末尾包含校验和。写入串行端口需要是单线程的。
private Boolean SendMessage(byte[] message)
{
if (_serialPort.IsOpen == true)
{
//All messages other than Acknowledgement Messages (0x06) require a checksum
if (message[0] != 0x06)
{
if (!SetMessagingLock(true)) return false;
_sendAcknowledgementAfterDataReceived = false;
message[message.Length - 1] = GenerateChecksum(message);
}
_serialPort.Write(message, 0, message.Length);
SetMessagingLock(false);
return true;
}
return false;
}
除了确认消息,所有接收到的消息都需要发送自己的确认消息。当接收到串口消息时会触发一个事件,以便桌面消息应用程序可以显示接收到的消息。这些信息通常对 CommunicateWithZWave
是私有的,但在桌面消息应用程序中显示字节数组有助于您弄清楚 Z-Wave 设备需要发送和接收的消息。
private void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int bytesToRead = _serialPort.BytesToRead;
if ((bytesToRead != 0) & (_serialPort.IsOpen == true))
{
byte[] message = new byte[bytesToRead];
_serialPort.Read(message, 0, bytesToRead);
RaiseEvent(EventHandlerMessageReceived, ByteArrayToString(message));
if (_sendAcknowledgementAfterDataReceived)
{
SendAcknowledgementMessage();
}
_sendAcknowledgementAfterDataReceived = true;
}
}
IR 玩具
IR Toy 是一种 USB 设备,可以发送和接收红外信号。我使用 IR Toy 向我们的电视、电视和音乐的卫星接收器以及音频接收器发送红外命令。
有一个 WinLirc Windows 应用程序,您可以用来与 IR Toy 交互。WinLIRC 是 LIRC 的 Windows 等效版本,http://www.lirc.org,即 Linux 红外遥控程序。大多数商用遥控器的配置文件可以在此 URL 在线找到:http://lirc.sourceforge.net/remotes/。您需要编写自己的代理才能通过互联网控制 IR Toy。代理需要接收您的网络请求并启动一个进程来发送红外代码。
LogicalLiving.IRToy
项目负责根据 WinLirc 中加载的配置文件启动进程,向设备发送红外信号。发送红外命令就像启动一个进程一样简单。
private static void SendIR(string remoteControl, string remoteCommand)
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "C:\\LogicalLiving\\LogicalLiving.IRToy\\WinLirc\\Transmit.exe";
startInfo.Arguments = remoteControl + " " + remoteCommand + " 0";
Process processTransmit = Process.Start(startInfo);
processTransmit.WaitForExit();
return;
}
宏可以通过为每个命令启动一个新进程来构建。例如,如果您最喜欢的音乐电台是频道 6008,那么您将运行以下代码来发送更改频道的命令。
SendIR("dish5", "6");
SendIR("dish5", "0");
SendIR("dish5", "0");
SendIR("dish5", "8");
除了它能正常工作并且最终结果很棒,而且能够通过互联网控制电视很方便之外,我对我的红外代码并不感到自豪。我计划重构我的红外代码和硬件,使其在不连接电脑的设备上运行。目前,我让它在家庭办公室的工作站上运行,并使用一个红外转发器,它将红外信号转换为无线电信号,然后通过无线电接收器与设备进行视线连接。接收器读取无线电信号并向设备传输红外信号。
Spark Core
Spark Core 是一款微型 Wi-Fi 开发板,可轻松创建连接互联网的硬件。Spark Core 运行与 Arduino 相同的 C 语言编程语言,因此熟悉 Arduino 的大众可以从一开始就高效地进行开发。我更喜欢具有调试功能的面向对象语言,但 Spark Core 通过一些非常酷和令人难以置信的功能弥补了这一点,甚至更多。
- 它很小巧 - Spark Core 的外形尺寸大约是我一直使用的带 Wi-Fi 桥接功能的微控制器的十分之一。
- 轻松配置您的网络 Wi-Fi 设置 - 他们为 Android 和 iOS 创建了原生移动应用,您可以使用它们来配置连接您的 Wi-Fi 网络。一旦在应用中配置好您的 Wi-Fi 网络,您的手机就会无线地将配置发送给 Spark Core。这比我过去使用过的所有其他需要有线连接的设备都要容易得多。更新我的猫玩具(使用不同微控制器构建)上的 Wi-Fi 网络非常麻烦,因为我必须断开电线才能将 Wi-Fi 网桥连接到我的笔记本电脑,然后完成后再重新连接所有东西。
- 空中 (OTA) 代码更新促进了快速开发 - Spark Core 最好的功能是轻松地将代码更新刷写到您的远程设备。我的 Spark Core 在室外,今天又冷又下雨。我可以坐在办公室或任何有互联网连接的地方,远程刷写代码更新。将我的笔记本电脑插入一些其他物联网项目进行更新非常不方便。有一个我一直想添加一个月的小功能,是为一个在不同平台上构建的猫玩具项目准备的,但连接我的笔记本电脑到难以访问的微控制器的麻烦让我一直没有进行更新。
我制作了一棵20英尺高的圣诞树,由Spark Core控制。施工很简单,我在两个周末完成了这个项目。第一个周末我搭建了圣诞树,第二个周末我制作了控制电路并编写了软件。圣诞树的搭建方法是将12串彩灯连接到2根10英尺长的铸铁螺纹管道上,管道之间用一个水管接头连接。彩灯串用帐篷桩固定在地上。整个圣诞树和控制电路的建造费用约为150美元。我使用的彩灯需要120 VAC电源,我使用Spark Core来驱动固态继电器,这些继电器是为控制120 VAC或240 VAC设备而制造的。
在程序开始时命名引脚。
int treeRelay1 = D0;
int treeRelay2 = D1;
int treeRelay3 = D2;
int treeRelay4 = D3;
int treeRelay5 = D4;
int treeRelay6 = D5;
int treeRelay7 = D6;
int treeRelay8 = D7;
int treeRelay9 = A4;
int treeRelay10 = A5;
int treeRelay11 = A6;
int treeRelay12 = A7;
setup 方法在重置时运行一次,用于将 Spark Core 上的 12 个引脚配置为树的 12 个分支的数字输出引脚。
void setup()
{
pinMode(treeRelay1, OUTPUT);
pinMode(treeRelay2, OUTPUT);
pinMode(treeRelay3, OUTPUT);
pinMode(treeRelay4, OUTPUT);
pinMode(treeRelay5, OUTPUT);
pinMode(treeRelay6, OUTPUT);
pinMode(treeRelay7, OUTPUT);
pinMode(treeRelay8, OUTPUT);
pinMode(treeRelay9, OUTPUT);
pinMode(treeRelay10, OUTPUT);
pinMode(treeRelay11, OUTPUT);
pinMode(treeRelay12, OUTPUT);
}
lightTree
方法将树的 12 个通道全部设置为所需的状态,即开或关。
void lightTree(boolean tree1, boolean tree2, boolean tree3, boolean tree4, boolean tree5,
boolean tree6, boolean tree7, boolean tree8, boolean tree9, boolean tree10,
boolean tree11, boolean tree12, int delayMilliSeconds)
{
digitalWrite(treeRelay1, tree1);
digitalWrite(treeRelay2, tree2);
digitalWrite(treeRelay3, tree3);
digitalWrite(treeRelay4, tree4);
digitalWrite(treeRelay5, tree5);
digitalWrite(treeRelay6, tree6);
digitalWrite(treeRelay7, tree7);
digitalWrite(treeRelay8, tree8);
digitalWrite(treeRelay9, tree9);
digitalWrite(treeRelay10, tree10);
digitalWrite(treeRelay11, tree11);
digitalWrite(treeRelay12, tree12);
delay(delayMilliSeconds);
}
shiftTreeLeft
方法用于将所有灯光向左旋转。delayMilliSeconds
参数用于设置旋转帧之间的时间延迟。loopCount
用于设置旋转的帧数。大多数情况下,这个计数是 12,这样图案就可以在树的所有分支中旋转。
void shiftTreeLeft(boolean tree1, boolean tree2, boolean tree3, boolean tree4, boolean tree5,
boolean tree6, boolean tree7, boolean tree8, boolean tree9, boolean tree10,
boolean tree11, boolean tree12, int delayMilliSeconds, int loopCount)
{
for (int count = 1; count <= loopCount; count++)
{
lightTree(tree1, tree2, tree3, tree4, tree5, tree6, tree7, tree8, tree9, tree10,
tree11, tree12, delayMilliSeconds);
boolean treeHold = tree1;
tree1=tree2;
tree2=tree3;
tree3=tree4;
tree4=tree5;
tree5=tree6;
tree6=tree7;
tree7=tree8;
tree8=tree9;
tree9=tree10;
tree10=tree11;
tree11=tree12;
tree12=treeHold;
}
}
fadeTree
方法与 lightTree
方法相似,但树的 12 个分支中的每一个都有多个状态来表示亮度,而不仅仅是开或关的布尔状态。状态范围从 0 到 10,其中 0 表示关闭,5 表示 50% 亮度,10 表示全部开启。Spark Core 有 8 个引脚可用于脉冲宽度调制(PWM),但树上有 12 个分支需要单独控制。此方法充当一个粗略的脉冲宽度调制器(PWM),用于以正确的比例循环开启状态和关闭状态,以使树上每串灯具有所需的亮度级别。
void fadeTree(int tree1, int tree2, int tree3, int tree4, int tree5, int tree6, int tree7,
int tree8, int tree9, int tree10, int tree11, int tree12)
{
for (int pulse = 1; pulse <= 10; pulse++)
{
lightTree(pulse<=tree1, pulse <= tree2, pulse<=tree3, pulse<=tree4, pulse<=tree5,
pulse<=tree6, pulse<=tree7, pulse<=tree8, pulse<=tree9, pulse<=tree10,
pulse<=tree11, pulse<=tree12, 5);
}
}
fadeTree
方法可以多次调用,使用不同的数据帧来产生许多酷炫的效果。
void fadeTreeRotate()
{
fadeTree(10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 10);
fadeTree(8, 6, 4, 2, 0, 2, 4, 6, 8, 10, 10, 10);
fadeTree(6, 4, 2, 0, 2, 4, 6, 8, 10, 10, 10, 8);
fadeTree(4, 2, 0, 2, 4, 6, 8, 10, 10, 10, 8, 6);
fadeTree(2, 0, 2, 4, 6, 8, 10, 10, 10, 8, 6, 4);
fadeTree(0, 2, 4, 6, 8, 10, 10, 10, 8, 6, 4, 2);
fadeTree(2, 4, 6, 8, 10, 10, 10, 8, 6, 4, 2, 0);
fadeTree(4, 6, 8, 10, 10, 10, 8, 6, 4, 2, 0, 2);
fadeTree(6, 8, 10, 10, 10, 8, 6, 4, 2, 0, 2, 4);
fadeTree(8, 10, 10, 10, 8, 6, 4, 2, 0, 2, 4, 6);
fadeTree(10, 10, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8);
fadeTree(10, 10, 8, 6, 4, 2, 0, 2, 4, 6, 8, 10);
}
fadeAllTree
方法用于将树上的所有灯光从关闭渐亮到开启,然后全部渐暗关闭。
void fadeAllTree()
{
for (int count = 0; count <= 10; count++)
{
fadeTree(count,count,count,count,count,count,count,count,count,count,count,count);
}
for (int count = 10; count >= 0; count--)
{
fadeTree(count,count,count,count,count,count,count,count,count,count,count,count);
}
}
与其他 Arduino Wiring 编程框架一样,loop 方法会永远循环,使圣诞树重复其精彩的灯光秀!
void loop()
{
shiftTreeLeft(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 12);
shiftTreeRight(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 12);
fadeAllTree();
fadeAllTree();
fadeAllTree();
fadeAllTree();
shiftTreeLeft(1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 600, 5);
shiftTreeLeft(1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 40, 12*4);
lightTree(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1000);
diamond(false);
delay(300);
diamond(true);
shiftTreeLeft(1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 150, 12*4);
fadeTreeRotate();
fadeTreeRotate();
fadeTreeRotate();
fadeTreeRotate();
shiftTreeRight(1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 80, 12*6);
lightTree(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1000);
}
微控制器代码维护设计模式
为您的设备群中的每个“设备”维护单独的程序可能会变得很困难。您拥有的设备越多,维护不同版本的代码就越困难。在许多情况下,即使它们具有不同的功能,在同一类型的微控制器群上运行相同的程序也很容易。我目前有 3 个 netduino 运行相同的程序,而不是为每个 netduino 微控制器分别编写程序,每个 netduino 都根据其 MAC 地址学习自己的身份并设置其职责。相同的程序根据微控制器的身份开关而表现不同。
- Netduino 1 - 控制壁炉、花园、车库和水枪
- Netduino 2 - 控制猫玩具
- Netduino 3 - 控制 5 个万圣节物品
每个 netduino 上的程序根据其 MAC 地址确定的身份设置静态 IP 地址。更灵活的设计是让每个微控制器连接到网络并获取 DHCP IP 地址。然后微控制器将发送一条带有其 MAC 地址的消息到一个身份服务,该服务将返回一条包含该特定 netduino 的身份和配置信息的消息。
物联网通信设计模式
模式1 - 设备充当服务器
“设备”可以是一个接受入站请求的服务器。这种模式可以通过在“设备”上设置一个网络服务器来配置。对于内部网络来说,这在安全方面可能没问题,但对于互联网通信来说并不安全。一个主要问题是需要通过防火墙打开一个端口,以便将请求路由到设备。这可能不安全,并且移动到另一个网络时可移植性很差。“设备”也可能因请求过多而过载,并且这种模式与任何与其通信的东西都紧密耦合。
模式2 - 设备充当客户端
物联网通信最简单的设计模式是让“设备”充当连接到服务的客户端。通常“设备”会发布、放置或获取数据。这种模式比第一种模式更安全,因为无需在防火墙上打开任何端口。“设备”还可以控制连接服务的频率,因此不会因请求过多而过载。与第一种模式一样,“设备”仍然与其通信的服务紧密耦合。
模式3 - 设备轮询服务
使用轮询策略,“设备”可以在不打开防火墙端口的情况下从服务发送和接收消息。轮询策略可以通过多种方式实现,包括长轮询或 WebSockets。“设备”不会因请求过多而过载,但与“设备”通信的服务需要实现一个队列来存储消息,直到“设备”准备好处理它们。
模式4 - 设备通过代理通信
代理是“设备”与与其通信的任何事物之间的软件中间件。我之前的大多数物联网实现都是通过“设备”紧密耦合到一个代理来完成的,该代理将消息分派到其他“设备”。我了解到,精心设计的代理可以以松散耦合的方式连接发布者和订阅者,而无需在防火墙中打开端口。我正在重构我的代码以实现这种方法,利用 MQTT 协议和一个名为 Mosquitto 的开源代理。
模式5 - 设备与本地和远程代理通信
一种更灵活的物联网通信设计模式是在您的网络防火墙两侧都设置代理。其中一个代理应该是本地的,另一个托管在云中的网络之外。
这将提高可靠性。重要的是,失去互联网连接不应阻止您的家庭自动化设备与其它本地设备通信。如果您的设备和代理使用轮询策略进行通信,则无需在防火墙上打开任何端口,您的设备也不会因请求过多而过载。
我的物联网项目设计经验教训
从构建自己的物联网家庭自动化系统中,我学到了很多东西:
- 教训1 - 让每个设备都智能化。当所有设备都通过电线连接到中央控制器时,移动它们会很困难。如果每个“设备”都是独立的,那么移动起来就很容易,搬家时也很容易带走。
- 教训2 - 能够通过空中 (OTA) 更新程序 (固件),可以加快新功能的开发。
- 教训3 - 使用 DHCP 和身份服务,为设备组中每种类型的微控制器编写一个程序。
- 教训4 - 使用发布/订阅模型和代理来松散耦合所有设备。
我的物联网项目趣事
- 教训1 - 权力越大,责任越大!本文讨论的一个项目是向电视、DVR 和音乐发送红外信号。我将控制添加到我的移动网络界面中,并在我不在家时开始随意更改电视频道或音乐。这是我告诉家人我想念他们的方式,但他们并不这么认为!当我回家时,有人通过拆除关键电线禁用了控制!不过,我很自豪我的家人知道要拆除哪些电线才能禁用它!
- 教训2 - 使用物联网控制火源时要小心!我们家有只猫喜欢在奇怪的地方玩耍。当我为壁炉添加控制功能时非常兴奋,但猫咪也对这个项目很感兴趣,并想了解更多!我在系统中添加了语音识别功能,电视里的声音会偶尔说出短语来打开壁炉。我们也可以在离家时打开壁炉,这样回家时就会感到温暖舒适。一只被烧伤的小猫意味着我的物联网项目的终结,所以我赶紧用铁丝网把猫咪挡在外面!
- 教训3 - 如果你经常用水枪射击孩子,他们就会开始无视它!当控制像在手机上按几个按钮一样简单时,很难忍住不拿冷水射击你的朋友和家人。
- 教训4 - 当设备在家中使用时,它必须接近 100% 可靠。家庭成员不会原谅质量缺陷!如果家庭自动化不可靠,就不会被使用,家庭成员会让你知道这一点!我的 netduino 以前因为以太网通信问题,每隔几天就会死机。当我妻子在我不在家时打电话给我,因为花园没有浇水时,我知道可靠性方面存在严重的维护问题。我花了几天时间解决这个问题,最终通过让代码检测问题,然后重启设备来恢复。netduino 重启速度很快,即使是我编写的代码在启动时调用时间服务来设置日期和时间,也是如此。重启速度非常快,人们通常不会注意到停机时间。家庭自动化需要接近 100% 可靠,否则不值得投入维护精力或给其他家庭成员带来挫败感。
下载代码
希望您喜欢这篇文章,并希望能为您的项目提供一些想法!
- LogicalLiving 家庭自动化系统 - 4 MB
- LogicalLiving 家庭自动化系统 - Kinect for Windows v2 - 5 MB
- Spark Core 圣诞树代码 - 7 KB
如果您正在运行 Windows 7,请加载 Logical Living 的第一个链接。如果您有 Windows 8 或更高版本并且拥有 Kinect v2 传感器,请加载 Kinect v2 版本。您需要从 Microsoft 安装 Kinect SDK 2.0 才能使 Kinect 代码正常工作。语音识别代码需要 Kinect 语音语言包和语音运行时才能工作。您可以测试作为 SDK 示例安装的 speech basics-WPF 示例应用程序,以确保您已安装所有必需组件,使语音识别正常工作。Kinect Living 代码可以在互联网上的远程机器上运行,并与您本地网络上的 WebApi 进行交互。
在 Twitter 上关注 Dan
Dan 经常发布物联网主题和技术推文。在 Twitter 上关注 Dan:@LogicalDan