Python-物联网编程项目第二版-全-
Python 物联网编程项目第二版(全)
原文:
zh.annas-archive.org/md5/cc94a1519b00175ed4d99184d0e8cb31译者:飞龙
前言
《物联网编程项目》是一本全面的实战指南,旨在通过一系列逐步提高的物联网项目使用树莓派和相关外围设备带你入门。
在初始阶段,你将接触到围绕树莓皮的基础概念和组件,包括对各种型号、附件(如顶部附加的硬件 HAT)和兼容操作系统的详细探索。你将通过参与展示树莓皮与实时数据接口和控制物理设备的项目的旅程开始你的物联网之旅,为更高级的任务奠定基础。
随着你继续前进,你将沉浸在开发网络服务和物联网应用中,创建实时数据显示和创新解决方案,如天气指示器应用程序。这些应用不仅提供 Python 编程和数据采集技能,而且进入物理世界,利用硬件组件创造有形的结果。
在书的中间部分,重点放在从头开始构建家庭安全系统。你将了解树莓派的通用输入/输出(GPIO)引脚、传感器集成以及使用消息队列遥测传输(MQTT)协议的动手报警系统开发。当我们创建基于 MQTT 的独立物联网报警模块及其相关外围设备时,将达到一个关键点。
在旅程的后半段,叙述进展到更高级和自动化的物联网项目。你将构建一个具有 LoRa 功能的物联网监测站,能够测量各种环境因素,包括空气质量。这个由电池供电的监测站通过 LoRa 与 LoRaWAN 网络通信数据,展示了物联网设备与更广泛的网络基础设施的集成,并强调了可持续的、电池供电的解决方案。
系列项目的巅峰之作是创建一个利用机器人操作系统(ROS)的物联网机器人汽车。这一高级项目指导你构建一辆能够通过 MQTT 在互联网上发送传感信息的机器人汽车,通过网页浏览器或其他应用程序实现远程控制。该项目代表了物联网掌握的巅峰,将机器人技术、网络通信和远程控制功能整合到一个统一系统中。
在整本书中,你将获得构建实用、现实世界物联网解决方案的知识和技能,通过持续的项目增强建议培养创造力和创新精神。从 Python 编程到硬件接口,本书促进了对物联网原理的丰富、多层次的理解,鼓励准备应对物联网领域中各种复杂问题的能力,并为有志于物联网的爱好者指明了开发强大、多功能和创新物联网解决方案的方向。
让我们开始吧!
本书面向对象
物联网编程项目面向对物联网世界充满热情的技术爱好者、业余爱好者和专业人士。本书涵盖了包括 Web 服务、LoRa 通信、树莓派、树莓派 Pico 和与 GPIO 交互在内的多个主题。您还将了解 ROS、构建机器人汽车和实现视觉识别。为了充分利用本书,您应该具备基本的编程、电子和网络知识。这本综合指南非常适合那些希望通过参与实际动手项目来扩展其在物联网方面的知识和技能的人。
本书涵盖内容
第一章, 理解树莓派,在开始使用树莓派创建物联网项目之前,探讨了树莓派。
第二章, 利用树莓派使用 Web 服务,深入探讨编写 Python 代码将我们的树莓派转变为物联网设备的过程,通过利用 Web 服务来获取数据并在 Sense HAT 上创建可视化显示。这为更高级的物联网 Web 服务开发奠定了基础,通过如股票行情、天气显示和决策者应用等实际项目。
第三章, 构建物联网天气指示器,探讨了将伺服电机和 LED 与树莓派集成以创建实用的物联网天气指示器,利用其精度、控制和实时反馈能力来增强系统功能。
第四章, 构建物联网信息显示,展示了如何使用带有树莓派品牌的 7 英寸触摸屏构建物联网信息显示,显示实时天气和当地交通信息。本章从探索兼容屏幕开始,最终 culminating in a comprehensive dashboard 项目。
第五章, 探索 GPIO,通过构建物联网家庭安全应用,包括带有被动红外传感器(PIR)运动传感器、按钮和蜂鸣器的基本报警系统,更深入地探讨了树莓派和树莓派 Pico 上的 GPIO 端口功能。
第六章, 构建物联网报警模块,通过使用树莓派 Pico W、公共 MQTT 服务器和 MQTTHQ 网络客户端构建物联网报警模块,来增强我们的基本报警系统,其中运动检测触发消息和远程蜂鸣器激活。这将为我们物联网家庭安全系统奠定基础。
第七章, 构建物联网按钮,展示了如何使用紧凑的 M5Stack ATOM Matrix 和通用的树莓派 Pico W 构建物联网按钮的不同版本,这是我们物联网家庭安全系统的一个基本组件。
第八章, 创建物联网报警仪表盘,介绍了使用带有 7 英寸触摸屏的 Raspberry Pi 5 创建物联网报警仪表盘,允许我们启用和禁用报警模块,查看 MQTT 通知,并显示报警位置地图,从而完成我们的高级物联网报警系统,具有全球应用能力。
第九章, 理解 LoRa,探讨了物联网通信中的 LoRa(长距离的简称)技术,其在农业和智能城市中的应用,以及如何使用 Raspberry Pi Pico 和 Pico W 分别构建 LoRa 传感器发射器和接收器,展示了 LoRa 的广泛范围能力和高效的低功耗数据传输。
第十章, 将 LoRa 与互联网集成,展示了如何使用我们配备 Raspberry Pi Pico W 的 LoRa 接收器将来自远程 LoRa 发射器的传感器数据发布到 MQTT 服务器,修改我们的模拟仪表天气指示器以利用这些数据,并探索各种物联网通信技术,如 LoRaWAN 和蜂窝网络。
第十一章, 介绍 ROS,介绍了 ROS 在机器人领域的意义,如何在 Raspberry Pi 4 上安装 Ubuntu,以及使用 turtlesim 学习基本的 ROS 概念和操作,最终为我们构建高级物联网机器人 A.R.E.S.(高级机器人视觉系统)做准备。
第十二章, 创建物联网摇杆,展示了如何使用 Raspberry Pi Pico WH 创建物联网摇杆。您将使用它来远程控制 ROS TurtleSim 机器人,基于之前的项目,并展示物联网在机器人领域的实际应用。
第十三章, 介绍高级机器人视觉系统(A.R.E.S.),专注于将 TurtleSim 虚拟机器人转换为名为 ARES 的现实生活机器人,该机器人具有可通过 VLC 媒体播放器访问的视频流,并由第十二章中的物联网摇杆控制。我们将使用 Raspberry Pi 进行感官输入,使用 Raspberry Pi Pico 进行电机、LED 和蜂鸣器控制,并配备 3D 打印框架。
第十四章, 将计算机视觉添加到 A.R.E.S.,最终我们将向 ARES 添加计算机视觉,使其能够识别物体并发送文本警报。我们还将使用 OpenCV 和 You Only Look Once (YOLO)物体检测系统构建智能视频流应用。
为了充分利用这本书
您应该具备一些 Python 和 JavaScript 编程经验。重要技能包括能够使用 Raspberry Pi、Python 和 Web 服务创建物联网应用,以及参与由 Raspberry Pi 控制的机器人。
| 本书涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Python | Raspberry Pi OS 和 Ubuntu Linux |
| CircuitPython | |
| MQTT | |
| OpenCV | |
| LoRa | |
| ROS | |
| Raspberry Pi | |
| Raspberry Pi Pico | |
| Raspberry Pi Sense HAT | |
| 各种传感器(PIR 和温度) | |
| M5Stack ATOM Matrix | |
| Pico 机器人板 |
如果你使用的是本书的数字版,我们建议你亲自输入代码或从本书的 GitHub 仓库(下一节中提供链接)获取代码。这样做将帮助你避免与代码的复制和粘贴相关的任何潜在错误。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Internet-of-Things-Programming-Projects-2nd-Edition。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们!
使用的约定
本书使用了多种文本约定。
文本中的代码: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“我们定义了一个activate_buzzer()函数。”
代码块设置如下:
def timer_callback(self):
if self.mqtt_message.should_draw_circle:
self.vel_msg.linear.x = 1.0
self.vel_msg.angular.z = 1.0
else:
self.vel_msg.linear.x = 0.0
self.vel_msg.angular.z = 0.0
self.publisher.publish(self.vel_msg)
粗体: 表示新术语、重要单词或你在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“我们通过点击 Raspberry Pi 任务栏中的菜单图标,导航到编程类别,并选择Thonny来完成此操作。”
小贴士或重要提示
看起来像这样。
联系我们
欢迎你的反馈。
一般反馈: 如果你对本书的任何方面有疑问,请通过客户关怀@packtpub.com 给我们发邮件,并在邮件主题中提及书名。
勘误: 尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果你在这本书中发现了错误,我们将不胜感激,如果你能向我们报告这个错误。请访问www.packtpub.com/support/errata并填写表格。
盗版: 如果你在互联网上以任何形式遇到我们作品的非法副本,如果你能提供位置地址或网站名称,我们将不胜感激。请通过版权@packt.com 与我们联系,并提供材料的链接。
如果你有兴趣成为作者: 如果你有一个你擅长的主题,并且你对撰写或为书籍做出贡献感兴趣,请访问authors.packtpub.com。
分享你的想法
一旦你阅读了《物联网编程项目》,我们很乐意听听你的想法!请点击此处直接进入本书的亚马逊评论页面并分享你的反馈。
你的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。
下载这本书的免费 PDF 副本
感谢您购买本书!
你喜欢在路上阅读,但无法携带你的印刷书籍到处走吗?
你的电子书购买是否与你的选择设备不兼容?
别担心,现在每购买一本 Packt 书籍,你都可以免费获得该书的 DRM-free PDF 版本。
在任何地方、任何设备上阅读。直接从你最喜欢的技术书籍中搜索、复制和粘贴代码到你的应用程序中。
优惠远不止这些,你还可以获得独家折扣、时事通讯和每日邮箱中的精彩免费内容
按照以下简单步骤获取优惠:
- 扫描下面的二维码或访问以下链接

packt.link/free-ebook/9781835082959
-
提交你的购买证明
-
就这些!我们将直接将你的免费 PDF 和其他优惠发送到你的邮箱
第一部分:为物联网开发设置树莓派
在第一部分中,我们首先理解树莓派,然后我们将利用网络服务进行物联网应用,使用传感器和网页数据构建物联网天气指示器,并使用树莓派和触摸屏创建物联网信息显示,以实时更新天气和交通信息。
本部分包含以下章节:
-
第一章,理解树莓派
-
第二章,使用树莓派利用网络服务
-
第三章,构建物联网天气指示器
-
第四章,构建物联网信息显示
第一章:理解 Raspberry Pi
欢迎来到物联网项目以及 Raspberry Pi 的奇妙世界,这是《物联网编程项目》的第二版。
在本书中,我们将通过 Raspberry Pi 探索物联网项目。本书的第一部分,我们将探讨 Raspberry Pi 上的物联网项目,最初将其转变为带有 Sense HAT 的气象站以捕获实时数据。随后,我们将使用电机和通用输入/输出(GPIO)引脚将 Pi 重新用作模拟计量设备。
然后,我们将创建一个物联网家庭安全系统,使用 Raspberry Pi 作为警报系统的中心枢纽。我们还将开发一个具有 LoRa 功能的物联网远程监测站。本书的结尾是我们最雄心勃勃的项目:构建一个由 Raspberry Pi 驱动的物联网机器人汽车。
我们将从此章节开始,探讨各种 Raspberry Pi 型号及其重要性,观察它们在处理能力、内存和功能方面的演变和进步。
我们还将探讨物联网(IoT)领域中可用的 Raspberry Pi 替代品,使我们能够根据项目需求做出明智的决定。我们将这些替代品与 Raspberry Pi 进行比较,突出它们的独特规格和能力。
此外,我们将深入了解 Raspberry Pi 的硬件附加在顶部(HAT),它通过附加板扩展了 Raspberry Pi 的功能。具体来说,我们将探索 Pibrella HAT 和 Raspberry Pi Sense HAT,突出它们通过增加额外功能和功能来增强 Raspberry Pi 的能力。
我们的 Sense HAT 项目将利用图 1**.1中看到的可选定制 Raspberry Pi 外壳。此外壳允许我们将 Raspberry Pi 以垂直格式展示,并利用 Sense HAT 上的点阵显示屏。本书的 GitHub 仓库中提供了 Raspberry Pi 4B 和 Raspberry Pi 5 版本外壳的构建文件。

图 1.1 – 定制外壳中的 Raspberry Pi 4B 和 Sense HAT
我们还将讨论一系列与 Raspberry Pi 兼容的操作系统,这些操作系统超出了官方的 Raspberry Pi OS。这些选项满足各种应用需求,包括网络音频系统、航空相关项目、复古游戏和 3D 打印。
在物联网应用背景下,我们将强调 Raspberry Pi 的多功能和强大,因为它作为处理实时数据和控制物理设备的最佳平台,对于开发和应用多样化的物联网项目至关重要。
最后,我们将探索树莓派作为一个强大的开发平台,它配备了预安装的软件开发工具。我们将通过一系列使用树莓派和 Sense HAT 提取感官信息的项目来结束本章,这些项目用于构建滚动环境数据显示屏。
在本章中,我们将通过实际编程来提高我们的编程能力,并为我们在这本书中开发令人兴奋的物联网项目做好准备。尽管本章涵盖了大量的信息,但如果感到不知所措或无法立即消化第一章的所有信息,我们也不必担心。随着我们继续阅读本书,我们将获得更多的经验和理解,这将使早期介绍的概念更容易掌握。
我们将涵盖以下内容:
-
探索树莓派型号
-
探索树莓派的替代方案
-
查看 HAT 的强大功能
-
调查树莓派的操作系统
-
使用树莓派进行物联网
-
开始树莓派开发
技术要求
以下是要完成本章所需的:
-
晚期型号的树莓派,如树莓派 5 4/8 GB 型号或树莓派 4B 4/8 GB 型号
-
键盘、鼠标和显示器
-
树莓派 Sense HAT 是可选的,但推荐使用;我们将使用 Sense HAT 软件模拟器
该章节的 GitHub 仓库位于github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter1。
对于那些可以使用 3D 打印机或 3D 打印服务的人来说,本章 GitHub 仓库的“构建文件”目录中提供了.stl文件,用于构建可选的定制外壳。外壳的文件适用于树莓派 5 和树莓派 4B 版本。
还需要具备一定的编程知识。本书中将使用 Python 编程语言。我们将从基本的代码开始,随着我们在书中完成项目,我们将逐步过渡到更高级的编码。
探索树莓派型号
每款树莓派型号,从最初的树莓派 1 到当前的树莓派 5 以及紧凑型 Pi Zero,都具有独特的特性和功能,使其成为物联网开发者的一个令人兴奋的选择。每个型号在不同的领域表现出色,例如处理能力、尺寸和能效。
树莓派型号随着时间的推移发生了显著的变化,每一代都带来了在处理能力、内存和功能方面的显著进步。
在以下列表中,我们比较了各种树莓派型号,从第一个开始:
-
树莓派 1:于 2012 年 2 月推出,树莓派 1 以其性价比和易用性改变了计算世界。配备 700 MHz 处理器、512 MB RAM 和 35 美元的价格点,树莓派的第一款产品激发了数字创新,并突出了单板计算机的潜力。
-
树莓派 2:于 2015 年 2 月发布,树莓派 2 在第一代模型的基础上进行了改进,配备了 900 MHz 四核处理器,并将 RAM 增加到 1 GB。树莓派 2 还将 GPIO 从 26 引脚扩展到 40 引脚,允许出现新一代的 40 引脚 HAT。这些进步使得树莓派 2 成为复杂项目的中心,从机器人技术到物联网应用。
-
树莓派 3:于 2016 年 2 月发布,树莓派 3 配备了 1.2 GHz 四核处理器。这提高了性能 50-60%,并支持更资源密集的应用程序。与树莓派 2 类似,它保持了 1 GB RAM。集成了 Wi-Fi 和蓝牙 4.1,简化了连接性并释放了 USB 端口(树莓派 2 需要一个 USB Wi-Fi 拨片)。新的双核 VideoCore IV GPU 通过改进的视频功能增强了多媒体项目。图 1**.2 提供了树莓派 3 的布局,突出了其几个关键组件:

图 1.2 – 树莓派 3B
- 树莓派 4:于 2019 年 6 月发布,树莓派 4 标志着该系列的重大演变,在保持紧凑尺寸和性价比的同时,将单板计算机的能力推向了接近传统台式电脑的界限。Pi 4 的独特之处在于它提供的多种内存选项,包括 2 GB、4 GB 和 8 GB LPDDR4-3200 SDRAM,这比之前的 1 GB LPDDR2 RAM 有显著提升,使得多任务处理和数据密集型任务的处理更加流畅。改进的连接性包括千兆以太网、双频段 802.11ac Wi-Fi 和蓝牙 5.0。其多媒体能力通过两个支持 4K 分辨率的微型 HDMI 端口得到提升,允许同时操作两个显示器。树莓派 4 引入了两个 USB 3.0 端口以实现更快速的数据传输,并替换了微型 USB 电源连接器,采用了 USB-C,支持其增强功能。在 图 1**.3 中,我们可以看到树莓派 4 的布局,其中突出了其几个关键组件:

图 1.3 – 树莓派 4B
树莓派 4 包括两个微型 HDMI 端口(用于双显示器)、四个 USB 端口(两个 3.0,两个 2.0)、一个千兆以太网端口、一个 USB-C 电源端口、一个微型 SD 插槽、一个摄像头端口和一个 3.5mm 音频复合视频插孔。
-
树莓派 5:树莓派 5 于 2023 年 10 月发布,标志着该系列的重大进步,通过升级的 CPU 和 GPU,增强了教育和个人 DIY 应用的计算和多媒体能力。
价格为 4 GB 60 美元和 8 GB 80 美元,Raspberry Pi 5 配备了 2.4GHz 四核 Arm Cortex-A76 CPU、VideoCore VII GPU、双 4Kp60 HDMI 输出,以及包括 Wi-Fi 和蓝牙在内的各种连接选项。它还引入了电源按钮、增强的内存和 I/O 功能,包括两个四通道移动行业处理器接口(MIPI)摄像头/显示屏收发器。这些收发器提供了连接任意组合的两个摄像头或显示屏的灵活性,使其非常适合高级多媒体项目。
Raspberry Pi 5 还配备了 PCIe 2.0 x1 接口,允许连接快速外设,以扩展其在高级应用中的功能,例如高速网络或存储解决方案。
伴随的 Raspberry Pi Active Cooler,凭借其高效的散热器和风扇设计,可将 CPU 温度降低约 20 摄氏度,这对于密集型任务至关重要。它提供易于安装,风扇速度高达 8000 RPM,增强性能和耐用性。在 图 1**.4 中,Raspberry Pi 5 与其 Active Cooler 并排展示,关键组件如 PCIe 2.0 接口、GPIO 头、USB 端口、以太网端口、双微 HDMI 端口和两个 MIPI 收发器被标注如下:

图 1.4 – Raspberry Pi 5(右侧)和 Raspberry Pi Active Cooler(左侧)
尽管新款 Raspberry Pi 4B 也足够使用,但我们将为涉及单板计算机的项目使用 Raspberry Pi 5。
- Raspberry Pi Zero 和 Zero W:2015 年 11 月推出,Raspberry Pi Zero 将其尺寸缩小到仅为 65mm x 30mm。它以可承受的价格配备了 1 GHz 单核 CPU 和 512 MB 的 RAM,并配备了 mini-HDMI 端口、micro-USB OTG 端口、micro-USB 电源端口和 HAT 兼容的 40 引脚头,使其非常适合紧凑型应用,如物联网项目、可穿戴设备和嵌入式系统。Raspberry Pi Zero 2W 采用相同的形态设计,并于 2021 年推出。它配备了增强的 1 GHz 四核 ARM Cortex-A53 CPU,提高了性能并能够处理更复杂的工作。其内置 Wi-Fi 和蓝牙的无线功能进一步扩展了其多功能性,使其成为紧凑型项目的绝佳选择。图 1**.5 展示了 Raspberry Pi Zero 2W 的布局:

图 1.5 – Raspberry Pi Zero 2W
- Raspberry Pi Pico 和 Pico W:2021 年 1 月推出,Raspberry Pi Pico 是一款紧凑型微控制器板,专为嵌入式项目和底层编程设计。其尺寸仅为 51mm x 21mm,但非常灵活。它支持串行外设接口(SPI)进行高速数据交换,集成电路间接口(I2C)用于外设之间的通信,以及通用异步收发传输器(UART)进行串行通信。Raspberry Pi Pico W 于 2022 年 2 月推出,通过板载 Wi-Fi 和蓝牙扩展了这种灵活性。与传统 Raspberry Pi 板不同,这两种 Pico 型号提供了一套独特的通用(GP)引脚。重要的是,Pico 系列作为微控制器而不是单板计算机,没有操作系统。这使裸机编程成为可能,在这种方法中,代码直接在硬件上运行,从而实现更快、更高效的代码执行和精确控制。这使得 Raspberry Pi Pico 等设备非常适合需要即时反应和与硬件紧密交互的应用,使其非常适合对时间敏感的任务。在图 1.6中,我们看到一个 Raspberry Pi Pico W 及其 GP 引脚的映射:

图 1.6 – Raspberry Pi Pico W
我们将在微控制器比单板计算机更适合的项目中使用 Raspberry Pi Pico 的变体。
以下是一个表格,概述了 Raspberry Pi 型号之间的一些差异:

图 1.7 – Raspberry Pi 型号表
现在我们对 Raspberry Pi 设备的生态系统有了更好的了解,让我们来看看一些替代方案。
探索 Raspberry Pi 的替代方案
物联网(IoT)领域充满了各种单板计算机和微控制器,每种都提供独特的规格,以满足特定项目的需求。通过将这些替代方案与 Raspberry Pi 进行比较,我们可以就适合我们独特需求的理想平台做出明智的决定。以下是我们可能考虑用于我们的物联网项目的 Raspberry Pi 替代方案的概述。我们将只关注 Raspberry Pi,而不是 Raspberry Pi Pico,因为我们将在本章的编程项目中使用前者:
-
BeagleBone Black:BeagleBone Black 配备了 1GHz ARM Cortex-A8 处理器、512MB DDR3 RAM、4 GB 闪存和许多连接选项。其独特的特性是可编程实时单元(PRUs),允许进行精确的实时处理,非常适合机器人或工业自动化等应用。尽管如此,Raspberry Pi 因其更快的 1.5GHz 64 位四核处理器而仍然受欢迎,这对于资源密集型应用来说是有益的。然而,Raspberry Pi 的主要优势是其庞大的社区软件生态系统,这极大地帮助简化了开发。
-
Arduino Uno:与 Raspberry Pi Pico 一样,Arduino Uno 是一款微控制器而不是单板计算机。它使用 ATmega328P 微控制器,运行在 16 MHz,并具有 2 KB 的 RAM。Arduino Uno 以其易于使用的功能扩展盾而闪耀,包括 Wi-Fi、蓝牙和传感器功能,使其非常适合简单的物联网项目。它受益于广泛的生态系统和易于使用的集成开发环境(IDE)。然而,对于需要大量计算、多任务处理或大量数据处理的项目,更强大的 Raspberry Pi 是更好的选择。
-
ESP32:Espressif Systems 的 ESP32 是一款类似于 Arduino Uno 的微控制器,但具有独特的特性。其双核 Xtensa LX6 微处理器和 520 KB 的 SRAM,结合内置的 Wi-Fi 和蓝牙,为物联网应用提供了强大的无线连接。某些 ESP32 型号通过额外的传感器和通信协议进一步扩展了其潜力,例如用于长距离、低功耗通信的 LoRa。尽管它不匹配 Raspberry Pi 的处理能力和 RAM,但在优先考虑无线连接和低功耗使用的领域表现出色。其紧凑性和成本效益使其适用于从远程监控到家庭自动化和可穿戴设备的各种物联网项目。在图 1.8 中,我们看到一个集成了 LoRa 通信和有机发光二极管(OLED)屏幕的 ESP32(LoRa 板被屏幕覆盖):

图 1.8 – 带有 OLED 屏幕和 LoRa 的 ESP32
- Arduino Nano RP2040 Connect:Arduino Nano RP2040 Connect 的开发是为了将 Raspberry Pi 的 RP2040 微控制器集成到一个紧凑、功能丰富的 Arduino 板上,为物联网项目和嵌入式人工智能解决方案提供独特的性能和连接性结合。它结合了 RP2040 的双核处理能力、板载 Wi-Fi 和蓝牙、六轴 IMU、麦克风和 RGB LED,旨在提供无缝的物联网开发体验。它与 Arduino Cloud 平台的兼容性简化了项目管理,包括空中更新等功能。
现在我们已经探索了一些 Raspberry Pi 的替代方案,接下来我们将关注如何使用 HATs 来扩展 Raspberry Pi 的功能。
探索 HAT 的强大功能
Raspberry Pi HAT 是扩展 Raspberry Pi 功能的附加板,为各种应用提供广泛的功能。这些 HAT 通过添加传感器、执行器、显示屏、通信接口等功能,为增强 Raspberry Pi 的功能提供了一种简单方便的方式。在本节中,我们将探讨两个值得注意的 HAT:Pibrella HAT 和 Raspberry Pi Sense HAT。
Pibrella HAT
Pibrella HAT 是一款优秀的入门级板,旨在向所有年龄段的用户介绍电子和编程概念。它具有按钮、LED 灯和蜂鸣器,为使用 Raspberry Pi 的物理计算项目提供实际操作体验。凭借其直观的界面和 Python 库,Pibrella HAT 为学习和原型设计提供了一个很好的起点。
尽管 Pibrella HAT 是为 Raspberry Pi 的第一版设计的,因此只有 26 针 GPIO 连接,但它仍然可以与当前 40 针版本的 Raspberry Pi 一起使用。我们可以在图 1.9中看到 Pibrella HAT 被连接到 Raspberry Pi 3B:

图 1.9 – 将 Pibrella 连接到 Raspberry Pi 3B
使用 Pibrella HAT,用户可以探索物理计算的基础,了解输入和输出交互,并通过使用 Raspberry Pi 获得编程的实际经验。
Raspberry Pi Sense HAT
Raspberry Pi Sense HAT(如图图 1.10所示)是一款令人印象深刻的附加板,旨在增强 Raspberry Pi 在感应和环境监测应用方面的功能。配备了包括温度、湿度、压力、陀螺仪、加速度计和磁力计在内的各种传感器,Sense HAT 允许用户轻松地从周围环境中收集数据。其 LED 矩阵显示屏提供视觉输出,使用户能够显示实时信息,包括动画:

图 1.10 – Raspberry Pi Sense HAT
除了其广泛的应用范围,Sense HAT 还在 AstroPi 项目中发挥着至关重要的作用,该项目使学生能够在国际空间站上运行自己的实验。凭借其传感器和紧凑的形态,Sense HAT 是收集空间站中宝贵数据的理想选择。
在本章中,我们开始使用 Raspberry Pi 5 和 Sense HAT 编写代码。我们将编写代码以从 Sense HAT 的内置温度传感器获取当前温度,并将其作为滚动信息显示在 Sense HAT 的点阵显示屏上。
在我们这样做之前,我们将继续探索 Raspberry Pi,通过查看适用于 Raspberry Pi 的各种操作系统。
调查 Raspberry Pi 的操作系统
虽然官方的树莓派操作系统(以前称为 Raspbian)是树莓派最广泛使用的操作系统,但也支持各种其他操作系统。这些选项从为特定应用量身定制的专业系统,如包括 Volumio 在内的音频播放器,到更通用的系统,如 Ubuntu 和树莓派操作系统本身。在本节中,我们将探讨这些操作系统的选择及其独特功能。具体如下:
-
Volumio:对于我们这些希望构建可通过计算机或智能手机访问的网络音频系统的人来说,Volumio 是一个合适的选择。它将树莓派转换成一个无头音频播放器,使得键盘或鼠标变得不再必要。该系统通过 USB 或网络连接到我们的音频文件,并提供通过附加 HAT 增强音频输出质量的选择。值得注意的是,Volumio 包括 Spotify 插件,让我们可以通过音响系统流音乐。
-
PiFM 无线电发射器:这个系统图像将树莓派转换成一个 FM 发射器,向任何标准 FM 收音机广播音频文件。我们只需将一根线连接到 GPIO 引脚之一,就可以作为天线使用,从而产生一个令人惊讶的强大 FM 信号。
-
Stratux: Stratux 是一款开源航空软件,可以将树莓派转换成一个强大的自动相关监视广播(ADS-B)接收器。ADS-B 是一种现代航空标准,允许飞机与空中交通管制和其他飞机共享其位置、速度和其他飞行数据。通过在树莓派上安装 Stratux 并与额外的硬件配对,我们可以创建自己的 ADS-B 地面站。这使得我们能够接收附近飞机的实时数据,包括飞行轨迹、高度和速度。
-
RetroPie:对于我们这些游戏爱好者来说,RetroPie 将我们的树莓派转换成一个复古游戏机。它有效地模拟了包括 Amiga、Apple II、Atari 2600 和 20 世纪 80 年代初的任天堂娱乐系统在内的多个经典游戏机和计算机。
-
OctoPi:对于我们这些参与 3D 打印的人来说,OctoPi 将树莓派转换成一个 3D 打印服务器的服务器,通过网络连接远程提供控制和监控。

图 1.11 – OctoDash 主屏幕
OctoPi 的伴侣是OctoDash(如图 1.11 所示),一个触摸屏界面,提供了一个易于使用、视觉上吸引人的控制面板,用于管理和监控 3D 打印任务。使用 OctoPi 与 OctoDash 可以使与 3D 打印机的交互更加灵活和高效。
-
Raspberry Pi 的 Ubuntu 操作系统:Ubuntu 是一家领先的开放源代码 Linux 发行版公司,为 Raspberry Pi 提供了一个平台。Ubuntu 的关键优势之一是其与机器人操作系统(ROS)的兼容性,ROS 是一个适用于编写机器人软件的灵活框架。在 Raspberry Pi 上安装了 Ubuntu 的 ROS 后,我们可以参与从简单的爱好者项目到复杂的工业自动化系统在内的机器人项目。我们将从第十一章开始探索 ROS。
-
Raspberry Pi 操作系统:以前称为 Raspbian,Raspberry Pi 操作系统是 Raspberry Pi 上最广泛使用的操作系统,因为它具有直接的兼容性、轻量级设计和易于使用。专为 Raspberry Pi 量身定制,这个操作系统拥有丰富的教育软件和编程工具,因此与 Raspberry Pi 促进计算机科学和相关领域学习的使命相一致。使用 Raspberry Pi Imager 安装 Raspberry Pi 操作系统非常简单,这是一个简化将系统镜像烧录到 microSD 卡过程的工具。安装后,Raspberry Pi 操作系统提供图形用户界面以及一套全面的编程、互联网和多媒体应用程序,使其成为各种 Raspberry Pi 项目的多功能选择。
现在我们已经对 Raspberry Pi 可用的操作系统有了大致的了解,让我们探索在本书所基于的领域——物联网中使用 Raspberry Pi。
使用 Raspberry Pi 进行物联网
物联网革命性地改变了我们与技术互动的方式,使日常物品能够进行通信、自动化任务,并在互联网上生成有价值的用于数据。Raspberry Pi 是许多这些系统的核心。其多功能性和强大的处理能力使这款强大的单板计算机既能作为数据处理器,也能作为物理设备的控制器。
Raspberry Pi 独特地配备了 GPIO 引脚、USB 端口和 Wi-Fi 功能,它是实现物联网解决方案的经济高效且功能强大的工具。Raspberry Pi 在处理实时数据和硬件管理方面特别有价值。以下是一些基于 Raspberry Pi 的物联网系统示例。
利用网络服务进行物联网应用
在物联网应用中使用 Raspberry Pi 的一个独特优势是其处理实时数据并根据这些数据控制其他硬件元素的能力。当数据来自互联网时,这种能力尤其有用。这种设置的用途非常广泛,从环境监测到健康跟踪、交通管理等等。
以下示例介绍了一种此类应用的特定实例——一个基于 Raspberry Pi 的物联网系统,该系统根据当前的天气条件建议合适的服装(图 1**.12):

图 1.12 – 天气信息模拟仪表盘
在《物联网编程项目》的第一版中构建的应用程序,利用直观的模拟仪表盘根据天气条件建议适当的服装。它包括一个 LED,指示是否需要雨伞。
除了作为一个基于天气的应用程序之外,我们还可以修改这个由 Raspberry Pi 驱动的物联网解决方案,以适应各种需要从网络服务收集实时数据并将其表示为模拟数据和 LED 的应用。
这里有一些例子:
-
交通密度监测器:利用图 1.12 中描述的类似概念,此应用程序将收集来自城市交通监控 API 的实时交通数据。模拟仪表将显示当前的交通密度,LED 会闪烁以指示特定路线上的交通拥堵或拥堵。这允许通勤者选择最佳路线和时间出行。
-
健康监控系统:通过来自健康 API 或智能健康设备的数据,模拟仪表可以显示心率、血压或其他任何生命体征。LED 将作为异常值的即时视觉警报,如果需要,将提示立即进行医疗关注。
-
水质监测器:物联网设备可以连接到一个网络服务,该服务接收来自河流、湖泊或海洋中的水质传感器的数据。模拟仪表可以显示如 pH 值等指标,当读数表明可能存在危险条件时,LED 会闪烁。
-
农业监测器:连接到一个网络服务,该服务从农田中的传感器(如土壤湿度、温度等)中提取数据,模拟仪表可以显示当前条件,而 LED 可以指示灌溉条件成熟或存在霜冻风险。
超越仪表盘,Raspberry Pi 的强大功能在机器人领域特别有效。Raspberry Pi 作为这些系统的“大脑”,管理着诸如传感器数据分析、决策和电机控制等任务。物联网和机器人的集成在包括安全、自动化和监控在内的各个领域取得了重大进步。一个例子是 T.A.R.A.S.(代表 This Amazing Raspberry-Pi Automated Security agent),这是一个由 Raspberry Pi 和物联网原理驱动的自动化安全代理,如《物联网编程项目》第一版中所述。
重新介绍 T.A.R.A.S. – 一个基于物联网的机器人项目
T.A.R.A.S.,一个以作者的一位商业导师 Taras 命名的缩写词,作为自动化安全警卫。
这种物联网在机器人领域的应用展示了树莓派如何管理感知和运动功能。它使用 消息队列遥测传输 (MQTT) 消息,一种轻量级的发布-订阅网络协议,使得设备之间能够实现无缝通信。我们可以在 图 1**.13 中看到 T.A.R.A.S. 的图形:

图 1.13 – T.A.R.A.S. 通过发送和接收 MQTT 消息在互联网上进行通信
除了感知输入、LED 和蜂鸣器输出外,T.A.R.A.S. 将物联网中的可能性动态范围封装起来。在上一版中,我们为 T.A.R.A.S. 建立了一个基于网页的控制器,整合了本书中学习到的众多技能。
在本版 物联网编程项目 中,我们将与 T.A.R.A.S. 说再见,因为他已经退休并离开了安全领域。我们将迎来 A.R.E.S.,或 高级机器人安全眼。这个名字是为了纪念作者心爱的已故爱犬。A.R.E.S. 通过结合视觉识别和 ROS,将把安全和移动性提升到一个新的水平。
开始使用树莓派进行开发
我们的树莓派可以作为强大的开发平台,非常适合各种编程环境。默认基于 Linux 的树莓派操作系统配备了 Python、Scratch 和许多其他工具,这些工具既适合初学者也适合经验丰富的程序员。由于其小型化设计,它在物联网项目和边缘计算中特别有效。作为开发者,我们可以用它进行软件开发、网站托管、家庭自动化项目和原型设计,使其成为创新的多功能和易于获取的工具。
在本书中,我们将使用树莓派开发物联网项目,特别是使用配备 8 GB RAM 的树莓派 5 以获得最佳性能。虽然其他版本的树莓派可能足够用,但配备 8 GB RAM 的树莓派 5 目前是最强大的型号。我们的项目将包括一系列令人兴奋的应用,包括模拟仪表的天气仪表盘、互联网连接的家庭安全系统、物联网远程监控站和我们的高级物联网机器人汽车 A.R.E.S.。在本章的剩余部分,我们将熟悉从树莓派 Sense HAT 读取感知数据。
在深入开发之前,熟悉可用于树莓派的开发工具至关重要。
树莓派开发工具
以下是我们树莓派项目中可用的开发工具范围。需要注意的是,并非所有这些程序都预装在树莓派操作系统上,可能需要手动安装:
-
Python: Python 是一种高级解释型通用编程语言。其简洁的语法和可读性使其非常适合初学者,同时其强大的库和多功能性也适合高级用户进行复杂项目。Python 预装在 Raspberry Pi OS 中。
-
Thonny: Thonny 是一个预装在 Raspberry Pi OS 中的 Python IDE。它对初学者来说易于使用,包括 逐步调试 和 错误突出显示 等功能。它也足够强大,适合高级用户,提供用于更复杂编码的综合工具。我们将在这本书的项目中使用 Thonny。
-
Scratch: Scratch 是一种面向儿童的基于块的视觉编程语言。使用 Scratch,孩子们可以在学习编程基础知识的同时创建动画和游戏。
-
Greenfoot 和 BlueJ: 这两种都是 Java 的 IDE。它们主要用于教育,旨在帮助初学者掌握面向对象编程。
-
Mu – 这是一个面向初学者的 Python 编辑器。它设计得简单易懂。
-
Geany: 它是一个轻量级且高度可定制的集成开发环境(IDE),支持包括 C、Java、PHP、HTML、Python、Perl 和 Pascal 在内的多种编程语言。
-
Wolfram Mathematica 和 Wolfram 语言: Wolfram 提供了一种高级编程语言和交互式环境,用于编程、数学可视化和通用计算。
-
Node-RED: 这是一个基于流程的开源可视化编程工具。Node-RED 允许访问 API 和在线服务。
-
GCC 和 GDB: GNU 编译器集合(GCC)(包括 gcc 和 g++)和 GNU 调试器(GDB)允许我们编译和调试用 C 和 C++ 等语言编写的代码。
除了这些,我们还可以使用 apt 软件包管理器从 Raspberry Pi OS 存储库安装其他软件开发工具,或者手动下载并安装它们。例如,我们可能想安装 Git 用于版本控制,Docker 用于容器化,或者 Visual Studio Code 用于更高级的开发环境。
Raspberry Pi 支持广泛的开发工具,满足各种编程需求。通过查看这些工具,我们强调了 Raspberry Pi 的适应性和创新项目的潜力。
Raspberry Pi 和 Sense HAT 开发
通过学习如何为 Raspberry Pi Sense HAT 编写简单的代码,开始我们的物联网(IoT)开发之旅是一个很好的主意。Sense HAT 是一个出色的 IoT 设备,配备了各种传感器和 LED 矩阵显示屏,可以用来创建创新的应用程序。
对于我们的编码示例,我们将将树莓派 5 安全地安装在为本书特别设计的壳中。在图 1.14 中,我们可以看到我们将使用的壳的 CAD 渲染图。该外壳具有一个前盖,暴露了 Sense HAT 的点阵 LED 屏幕。它还包括一个圆形通风口,以确保壳内的热量适当散发。在壳的背面,我们集成了 GoPro 风格的钩子,这为我们提供了使用任何 GoPro 支架安装树莓派和 Sense HAT 的灵活性。构建外壳的文件可以从书籍的 GitHub 仓库中获取。使用标准 FDM 3D 打印机制作外壳应该是足够的;然而,使用液体树脂打印机和如 Siraya Tech Blu 等工程树脂可以获得更好的质量和耐用性。图 1.14 显示了组装好的外壳,其中打印的茎和底板取代了 GoPro 支架:

图 1.14 – 定制的 Raspberry Pi 5 Sense HAT 外壳
我们可以在书籍 GitHub 仓库中的“构建文件”文件夹内找到所有必要的 3D 打印文件(.stl文件)。值得注意的是,大多数树莓派外壳没有考虑到 HAT 的容纳空间,这使得我们的树莓派/Sense HAT 外壳成为本书的优秀配件。然而,重要的是要提到,外壳不是完成本书练习的必需品。此外,如果我们决定不购买 Sense HAT,我们仍然可以通过使用为 Raspberry Pi OS 提供的 Sense HAT 模拟器来运行示例。
在我们开始编写代码之前,我们将在我们的树莓派上设置开发环境。一旦我们的环境准备就绪,我们就会直接进入编码阶段。
让我们开始吧!
设置我们的开发环境
在本节中,我们将介绍为使用树莓派和 Sense HAT 进行开发而设置 Thonny 的过程。Thonny 是一个面向初学者的 IDE,它提供了一个用户友好的界面。使用 Thonny,我们可以轻松地编写、测试和调试我们的代码,从而让我们能够专注于开发过程,而不必处理不必要的复杂性。
我们将在我们的开发中使用 Python 虚拟环境。由于有一些库只能与 Python 的根安装一起工作,我们将在 Python 虚拟环境中使用系统包。为此,我们执行以下操作:
- 在我们的树莓派 5 上,我们通过点击左上角菜单中的第四个图标来打开终端应用程序(如图 1.15 所示):

图 1.15 – 打开终端应用程序(由红色箭头指示)
-
为了存储我们的项目文件,我们使用以下命令创建一个新的目录:
mkdir Chapter1 -
然后我们使用以下命令导航到新目录:
cd Chapter1 -
我们使用以下命令为我们的项目创建一个新的 Python 虚拟环境:
ch1-env and enable access to the system site packages. This allows the virtual environment to inherit packages from the global Python environment, which can be useful when certain libraries are installed system wide. Once the environment is set up, we can activate it and begin installing project-specific packages without affecting the global Python environment. -
在我们创建新的 Python 虚拟环境后,我们使用以下命令将其源代码导入:
ch1-env Python virtual environment:

图 1.16 – 使用 ch1-env 环境的终端
-
对于我们的项目,我们需要 Sense HAT 和 Sense HAT 模拟器库。Sense HAT 库已预安装在我们的 Python 虚拟环境中;然而,Sense HAT 模拟器需要通过终端命令手动安装:
pip install sense-emu -
安装 Sense HAT 模拟器库让我们可以使用实际的 Sense HAT 或 Sense HAT 模拟器运行代码。我们可以使用特定命令关闭终端:
exit -
现在,我们已经准备好加载 Thonny。我们通过点击 Raspberry Pi 任务栏中的 菜单 图标,导航到 编程 类别,并选择 Thonny(图 1.17)来完成此操作:

图 1.17 – Thonny IDE 界面
-
默认情况下,Thonny 使用 Raspberry Pi 内置的 Python 版本(图 1.17 中的红色箭头)。对于我们的项目,我们将使用我们刚刚创建的 Python 虚拟环境。首先,我们需要通过点击 查看 并选择 文件(如果尚未选择)来查看项目文件(我们可能需要先通过点击屏幕右上角的 切换到常规模式 标签来切换到常规模式)。
-
在
ch1-env目录中。 -
然后,我们右键单击文件夹并选择 激活虚拟环境 选项:

图 1.18 – 在 Thonny 中激活 ch1-env 虚拟环境
在创建项目文件夹、设置并激活 Python 虚拟环境以及安装 Sense HAT 模拟器包后,我们现在可以开始编写代码。
Sense HAT 开发 – 读取传感器数据
我们第一个项目的目标是创建一个程序,从 Sense HAT 读取传感器信息并在 Thonny Shell 中显示它。然而,在我们能够读取感官信息之前,我们必须确保 Sense HAT 已经正确连接到我们的 Raspberry Pi。
首先,按照以下步骤操作:
-
如果尚未运行,我们通过点击
ch1-env虚拟环境(如果尚未激活)来启动 Thonny。 -
然后,我们通过选择 文件 然后选择 新建 或通过按键盘上的 Ctrl+N 来创建一个新的标签页:

图 1.19 – 在 Thonny 中创建新文件
-
在新标签页中,我们输入以下代码:
from sense_hat import SenseHat sense_hat = SenseHat() temp = sense_hat.get_temperature() humidity = sense_hat.get_humidity() press = sense_hat.get_pressure() accel = sense_hat.get_accelerometer_raw() gyroscope = sense_hat.get_gyroscope_raw() print("Temperature: {:.2f}°C".format(temp)) print("Humidity: {:.2f}%".format(humidity)) print("Pressure: {:.2f} millibars".format(press)) print("Accelerometer Data: x={:.2f}, y={:.2f}, z={:.2f}".format(accel['x'], accel['y'], accel['z'])) print("Gyroscope Data: x={:.2f}, y={:.2f}, z={:.2f}".format(gyroscope['x'], gyroscope['y'], gyroscope['z'])) -
对于使用 Sense HAT 模拟器的我们来说,只需将代码的第一行更改为以下内容:
from sense_emu import SenseHat我们将代码保存为具有描述性的名称,例如
sensor-test.py。在我们运行代码之前,让我们将其分解以了解它:-
我们首先从
sense_hat库中导入SenseHat类。 -
创建了一个
SenseHat类的实例,并将其分配给变量sense_hat。 -
在
sense_hat对象上调用get_temperature()方法以检索温度数据,并将其分配给temp变量。 -
在
sense_hat对象上调用get_humidity()方法以检索湿度数据,并将其分配给humidity变量。 -
在
sense_hat对象上调用get_pressure()方法以检索空气压力数据,并将其分配给press变量。 -
在
sense_hat对象上调用get_accelerometer_raw()方法以检索原始加速度计数据,这包括 x、y 和 z 轴的值。数据被分配给accel变量。 -
在
sense_hat对象上调用get_gyroscope_raw()方法以检索原始陀螺仪数据,这同样包括 x、y 和 z 轴的值。数据被分配给gyroscope变量。 -
然后使用
print()函数和格式化字符串占位符{:.2f}以适当的格式打印出获得的数据,以显示两位小数的值。
-
-
我们可以通过点击绿色的 运行 按钮、按键盘上的 F5 或者在顶部菜单中选择 运行 然后选择 运行当前脚本 来运行代码。

图 1.20 – 在 Thonny 中运行程序
运行代码后,我们应该在 Shell 中观察到如下信息:

图 1.21 – 运行 sensor-test.py 后的结果
对于使用 Sense HAT 模拟器的我们来说,显示的值将由模拟器中设置的滑块值决定。
重要注意事项
我们可以忽略警告,因为 Sense HAT 不包括 TCS34725 颜色传感器。警告很可能是 Sense HAT Python 库内部问题引起的。我们的代码不受此警告的影响,可以继续按预期运行。
重要的是要注意,温度读数受到 Raspberry Pi 产生的热量影响,因此比实际室温要高。湿度读数也受到影响。然而,加速度计和陀螺仪的值准确地反映了 Sense HAT 的位置和方向,当外壳向不同方向旋转时可以观察到它们的变化。要查看加速度计和陀螺仪的更新值,必须在旋转外壳后再次运行代码。
现在我们已经学会了如何从 Sense HAT 读取感官数据,让我们将注意力转向点阵显示屏。在下一节中,我们将探讨如何使用点阵显示屏创建一个简单的动画。
Sense HAT 开发 – 创建动画
在本节中,我们将使用 Sense HAT 和 Raspberry Pi 创建一个爆炸动画。我们将使用 Thonny 作为我们的开发环境来编写和执行代码。
首先,按照以下步骤操作:
-
如果尚未运行,我们通过点击
ch1-env虚拟环境(如果尚未激活)来启动 Thonny。 -
然后,我们通过选择 文件 然后选择 新建 或按键盘上的 Ctrl + N 创建一个新的标签页。
-
我们通过输入我们的导入语句开始我们的代码:
from sense_hat import SenseHat import time-
我们从
sense_hat库中导入SenseHat模块,使我们能够与 Sense HAT 板交互。 -
我们还导入了
time模块,该模块将在我们的程序中用于添加延迟。
-
-
然后,我们设置我们的变量声明:
sense_hat = SenseHat() R = [255, 0, 0] # Red O = [255, 165, 0] # Orange Y = [255, 255, 0] # Yellow B = [0, 0, 0] # Black frame1 = [ B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, R, R, B, B, B, B, B, R, R, R, R, B, B, B, B, R, R, R, R, B, B, B, B, B, R, R, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B ] frame2 = [ B, B, Y, O, O, Y, B, B, B, Y, O, R, R, O, Y, B, Y, O, R, Y, Y, R, O, Y, O, R, Y, B, B, Y, R, O, O, R, Y, B, B, Y, R, O, Y, O, R, Y, Y, R, O, Y, B, Y, O, R, R, O, Y, B, B, B, Y, O, O, Y, B, B ] frame3 = [ O, R, R, Y, Y, R, R, O, R, Y, O, O, O, O, Y, R, Y, O, B, B, B, B, O, Y, O, B, B, B, B, B, B, O, O, B, B, B, B, B, B, O, Y, O, B, B, B, B, O, Y, R, Y, O, O, O, O, Y, R, O, R, R, Y, Y, R, R, O ] frames = [frame1, frame2, frame3]-
我们创建了一个名为
sense_hat的 SenseHAT 类实例,使我们能够访问 Sense HAT 的功能。 -
我们为红色、橙色、黄色和黑色定义了颜色值,这些颜色值将用于创建我们的动画。
-
我们创建了一个名为
frame1的颜色值列表,代表 LED 矩阵上期望的图像。 -
我们定义了
frame2和frame3作为额外的帧。 -
我们创建了一个名为
frames的列表,按照特定顺序保存定义的帧。
-
-
为了使我们的动画持续运行,我们设置了一个无限循环:
while True: for frame in frames: sense_hat.set_pixels(frame) time.sleep(0.5) sense_hat.clear() time.sleep(0.2)-
在循环中,我们遍历
frames列表中的每一帧。 -
我们使用
sense_hat.set_pixels(frame)将 LED 矩阵的像素设置为当前帧,显示相应的图像。 -
我们使用
time.sleep(0.5)添加了一个短暂的 0.5 秒暂停,以控制动画速度。 -
在显示所有帧后,我们使用
sense_hat.clear()清除 LED 矩阵,为下一次迭代做准备。 -
在再次启动动画循环之前,我们使用
time.sleep(0.2)添加了 0.2 秒的轻微延迟,从而创建了爆炸图案。
-
-
对于使用 Sense HAT 模拟器的我们,只需将第一行代码更改为以下内容,并在我们的 Raspberry Pi 上打开 Sense HAT 模拟器:
from sense_emu import SenseHat -
我们将代码保存为具有描述性名称的文件,例如
animation-test.py。我们通过点击绿色 运行 按钮、按键盘上的 F5 或点击顶部的 运行 菜单选项然后选择 运行当前脚本 来运行代码。我们应该在我们的 Sense HAT 或 Sense HAT 模拟器上观察到爆炸动画:

图 1.22 – Sense HAT 模拟器上的爆炸动画帧
在本节中,我们探讨了使用 Sense HAT 和 Raspberry Pi 创建动画。我们学习了如何编写代码在 LED 矩阵上显示一系列帧,从而实现爆炸动画。在本章的最后一个编程项目中,我们将创建 Sense HAT 点矩阵屏幕上的滚动消息。这条消息将动态显示温度、湿度和空气压力数据,以视觉吸引力的方式为我们提供有价值的环保见解。
Sense HAT 开发 – 创建滚动环境数据显示
在我们之前的项目基础上,我们在 Sense HAT 上读取传感数据并创建 LED 矩阵动画方面积累了经验,现在我们将深入创建一个使用 Sense HAT 的点阵显示屏的滚动文本应用程序。
首先,按照以下步骤操作:
-
如果尚未运行,我们通过点击
ch1-env虚拟环境(如果尚未激活)来启动 Thonny。 -
通过选择 文件 然后选择 新建 或者在键盘上按 Ctrl + N 来创建一个新的标签页。
-
我们首先导入我们需要的库:
from sense_hat import SenseHat import time-
我们从
sense_hat库中导入SenseHat模块,使我们能够与 Sense HAT 板进行交互。对于使用 Sense HAT 模拟器的我们,我们会使用sense_emu库。 -
我们还导入了
time模块,它将在我们的程序中用于添加延迟。
-
-
然后,我们在代码中设置变量声明:
sense_hat = SenseHat() speed = 0.05 sense_hat.set_rotation(270) red = [255, 0, 0] green = [0, 255, 0]-
我们创建了一个名为
sense_hat的SenseHAT类的实例。 -
我们使用变量
speed设置 LED 矩阵上消息的滚动速度。 -
行
sense_hat.set_rotation(270)调整 Sense HAT 的 LED 矩阵的方向,使其与定制外壳中的 Raspberry Pi 的方向相匹配。 -
我们定义了两个颜色变量,
red和green,用于文本颜色。
-
-
为了使滚动消息持续播放,我们创建了一个无限循环:
while True: sense_hat.show_message("Temperature: %.1fC, " % sense_hat.get_temperature(), scroll_speed=speed, text_colour=green) sense_hat.show_message("Humidity: %.1f%%, " % sense.get_humidity(), scroll_speed=speed, text_colour=green) sense_hat.show_message("Pressure: %.1fhPa" % sense.get_pressure(), scroll_speed=speed, text_colour=red) time.sleep(1)在无限循环中,我们执行以下操作:
-
我们在 LED 矩阵上显示温度读数,格式化为一位小数。
-
我们在 LED 矩阵上显示湿度读数,格式化为一位小数。
-
我们在 LED 矩阵上显示压力读数,格式化为一位小数。
-
在循环的下一迭代之前,我们暂停一秒钟。
-
-
我们使用描述性的名称,如
sensor-scroll.py来保存代码。 -
通过点击绿色 运行 按钮,按键盘上的 F5,或者在顶部菜单中选择 运行 然后选择 运行当前脚本 来运行代码。
我们应该观察到在 Sense HAT 的点阵屏幕(或 Sense HAT 模拟器)上显示的滚动消息。
在这个练习中,我们在 Sense HAT 上创建了一个滚动文本应用程序,显示实时环境数据。这个项目增强了我们在传感器数据采集和视觉展示方面的技能,使用 Sense HAT 的 LED 矩阵进行有效的数据可视化。完成这个最终章节项目后,我们现在可以应对使用 Sense HAT 和 Raspberry Pi 的更高级的物联网应用了。
摘要
在本章中,我们开始了我们的物联网项目之旅,使用 Raspberry Pi。我们探讨了各种 Raspberry Pi 型号、它们的独特特性和在物联网开发中的重要性。我们简要地看了看 Raspberry Pi 在物联网应用中的替代品,并探讨了 Raspberry Pi HAT,如 Pibrella HAT 和 Raspberry Pi Sense HAT。
此外,我们还研究了与官方树莓派操作系统兼容的操作系统,包括树莓派操作系统。我们强调了树莓派在物联网应用中的多功能性和强大功能,强调其处理实时数据和控制物理设备的能力。本章还介绍了树莓派作为一个强大的开发平台,配备了预安装的软件开发工具。
在本章结束时,我们进行了实际编程项目,利用 Sense HAT 并探索其在物联网应用中的潜力。这些项目包括一个滚动环境数据显示,提供了提取感官信息的动手经验,并创建动态视觉显示。
本章为我们提供了在开发物联网项目时可能使用的知识,以便做出明智的选择。了解不同树莓派型号和替代方案的优势,使我们能够为特定应用选择正确的设备。熟悉树莓派 HAT 和各种操作系统扩展了我们的工具箱。在本章中,我们通过了解其数据和控制能力,释放了树莓派的潜力。我们的 Sense HAT 实践为我们未来更复杂的项目做好了准备。
展望未来,在下一章中,我们将利用网络服务的力量,继续构建更复杂的物联网应用。
第二章:利用树莓派利用网络服务
在本章中,我们将开始编写用于网络服务的代码,将我们的树莓派变成一个物联网(IoT)设备。使用 Python,我们将设计程序从在线资源中提取数据,并使用这些数据在 Sense HAT 的点阵显示屏上创建视觉。本章中我们将涵盖的实用示例将作为更高级物联网网络服务开发的基石。
我们首先探索网络服务的世界——了解它们的作用以及我们如何利用它们来获得优势。我们可能会把网络服务视为互联网的生命线,在数字世界中循环传递重要数据。理解网络服务不仅仅是把另一个工具添加到我们的工具箱中;它是关于解锁一个充满无限潜力的世界。
随着我们不断进步,我们将通过一系列编程项目将理论知识转化为实际应用。这些项目专门设计用于利用 Alpha Vantage 和 OpenWeather 等提供商提供的先进网络服务。使用多功能的树莓派 Sense HAT 或其模拟器,我们将构建包括滚动股票行情、天气信息显示,甚至为青少年棒球比赛提供 GO-NO-GO 决策器的应用程序。这些项目不仅教会我们如何使用技术;它们让我们沉浸在物联网的基本原理中。
考虑一下,随着网络服务和树莓派的出现,前方等待我们的广阔机会。今天,我们从简单的股票行情开始。明天,我们可能会开发出一种设备,在最喜欢的球队得分或帮助我们导航交通时通知我们。这一章节是我们探索更广阔、更激动人心的物联网创新宇宙的第一步。
在本章中,我们将涵盖以下主题:
-
探索网络服务
-
创建滚动股票行情应用程序
-
开发天气显示应用程序
让我们开始吧!
技术要求
完成本章需要以下内容:
-
一台带有 4 GB 或 8 GB RAM 的 Raspberry Pi 5(首选);然而,可以使用最新型号的 Raspberry Pi,如 Raspberry Pi 4。
-
树莓派 Sense HAT(可以使用 Raspberry OS 模拟器代替)。
-
键盘、鼠标和显示器。
-
获取 3D 打印机或定制展台的 3D 打印服务。
-
本章的 GitHub 仓库位于
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter2。 -
对编程的一般了解。在这本书中,我们将使用 Python 编程语言。
探索网络服务
想象一下从我们的智能手机远程控制我们家的设备——这种惊人的便利是由网络服务提供的,无形的信息使者无缝连接我们的数字世界。
网络服务是当今互联网基础设施的重要组成部分。它们允许不同软件应用程序通过网络无缝交换数据。因此,它们是物联网应用,包括我们的树莓派物联网项目的一个基本工具。有了网络服务,我们可以利用在线数据的丰富资源,并通过我们的树莓派将其带入物理世界。
在本质上,网络服务是软件接口,它使一个软件系统能够通过网络与另一个系统交互。这种交互通常是通过特定的协议完成的,例如表示状态转移(REST)或简单对象访问协议(SOAP)。为了了解网络服务的强大功能,可以考虑图 2.1中所示的一个服务示例,该服务提供医院病房中患者的关键感官数据:

图 2.1 – 客户端应用程序从 FHIR 网络服务接收数据
在这里,我们看到一个表示快速健康互操作性资源网络服务的图表。FHIR(发音为“fire”)是由国际健康水平七组织(HL7)制定的标准,用于医疗保健信息的电子交换。像 FHIR 这样的网络服务提供通用可访问性,使医疗保健专业人员能够随时随地访问和共享患者数据。它们提供优越的互操作性,比本地网络的应用程序编程接口(API)调用更高效、更实用,尤其是在需要在不同系统和设备之间共享数据的医疗保健领域。
在本节中,我们将探讨网络服务使用的协议。在我们编写代码调用简单的网络服务之前,我们还将探索一些更受欢迎的网络服务。
理解网络服务的方法
在网络服务领域,两种突出的方法是 REST 和 SOAP 协议。RESTful 网络服务明确使用 HTTP 方法,更加直接和高效,因此成为许多开发者的首选。另一方面,SOAP 是一种允许在隔离系统中运行的程序通过 HTTP 及其基于 XML 的消息系统进行通信的协议。SOAP 具有高度的扩展性,并且对安全和可靠性有强大的支持,使其适合复杂的应用程序。
在我们即将到来的项目中,我们将主要利用 REST 与网络服务进行交互。然而,了解 SOAP 提供了对网络服务交互领域的更广泛视角。我们将首先探索 SOAP、其功能及其适用场景,然后再深入研究基于 REST 或 RESTful 的网络服务。
使用 SOAP 网络服务
要理解 SOAP,我们关注图 2.2中展示的定制排序系统。由于这个系统是为特定公司的业务操作内部构建的,因此它是一个复杂的企业级应用,在安全性和可靠性方面有显著要求。这个排序系统代表了基于 SOAP 的网络服务的理想用例:

图 2.2 – 使用 SOAP 连接服务器的定制排序应用程序
由于 SOAP 具有有状态的性质,应用程序可以管理涉及多个步骤的复杂事务管理,包括库存检查、支付处理到订单确认。SOAP 协议内置的安全功能和错误处理功能使得应用程序能够高效且安全地管理订单。
如图 2.2所示,在 SOAP 事务中,一个 XML 文件通过 HTTPS 传递。这个 XML 文件位于 SOAP 消息中,在通信中扮演着至关重要的角色。它携带详细的指令,指导服务器执行操作。XML 的结构化格式使得复杂的数据交换成为可能,即使是在可能具有不同数据格式的各种系统之间,定制的排序系统也能无缝交互。
如果 SOAP 是企业级应用的首选方法,那么 REST 则更适用于公共网络服务。REST 的简洁性、轻量级特性、可扩展性和面向资源的处理方式使其非常适合创建用户友好且可扩展的网络服务,这些服务通过标准的 HTTP 协议与资源进行交互。
探索 RESTful 网络服务
RESTful 网络服务是现代网络应用的关键组成部分,它允许客户端和服务器使用 REST 架构进行高效的通信。RESTful 网络服务使用 HTTP 方法如GET、POST、PUT和DELETE与资源进行交互,并且是无状态的。与通常使用 XML 的 SOAP 不同,RESTful 服务可以支持多种数据格式,包括 JSON 和 XML。这种灵活性通常导致人们认为 RESTful 服务比 SOAP 更易于使用且更具适应性。
几个广为人知的公共 RESTful 网络服务因其功能性和易用性而受到欢迎。其中之一是Twitter API,它允许开发者访问和交互核心 Twitter 数据,包括时间线、状态更新和其他信息。另一个值得注意的例子是 Google Maps API,它为开发者提供了在网页上使用 JavaScript 嵌入 Google Maps 的能力。
在图 2.3中,我们看到一个简化图,展示了 RESTful 网络服务使用GET、POST、PUT和DELETE HTTP 方法与网页进行通信。这些 HTTP 方法中的每一个都对应于与服务器交互的特定类型:

图 2.3 – RESTful Web 服务的简化图
GET方法检索现有数据,POST通常用于发送新数据以创建资源,PUT用于更新现有资源,而DELETE用于删除数据。
API 与 Web 服务的区别
我们可能会发现自己交替使用API和Web 服务这两个术语;然而,它们之间是有区别的。API是一个广泛的术语,定义了构建和与软件应用程序交互的规则和约定。API 可以在各种渠道上运行,而不仅仅是 Web。然而,Web 服务是一种特定的 API 类型,它通过互联网运行,通常使用 HTTP 等协议。本质上,所有 Web 服务都是 API,但并非所有 API 都是 Web 服务。
以 Twitter API 为例,我们可以为其开发客户端的 Web 服务,这些 HTTP 方法的运用如下:
-
GET:我们会使用这种方法从 Twitter 检索数据。例如,我们会使用GET请求来获取特定用户的推文或搜索包含特定标签的推文。 -
POST:当我们想在 Twitter 上创建新数据时,例如一条新推文,我们会使用这种方法。 -
PUT:我们不会使用这种方法,因为 Twitter API 原生不支持PUTHTTP 方法。 -
DELETE:我们会使用DELETE方法来删除 Twitter 上的现有数据。然而,由于 Twitter 的政策限制,删除功能有限,这种方法在 Twitter API 中并不常用。
我们可以在以下表格中看到 REST 方法概述:

图 2.4 – REST 方法概述
总结来说,REST 是一种简单直接的方法,它利用标准的 HTTP 或 HTTPS 方法,如PUT、POST、GET和DELETE,同时在数据格式方面提供灵活性,例如JavaScript 对象表示法(JSON)或 XML,而 SOAP 是一种通常使用 XML 来传输结构化消息的协议,并且可以在各种互联网协议(IP)套件网络,如 HTTP 或 HTTPS 上运行。
现在我们已经对 Web 服务和它们的实现方式有了基本的了解,让我们通过使用我们的 Raspberry Pi 和 Sense HAT 来创建一个现实世界的示例。
使用我们的 Raspberry Pi 和 Sense HAT 连接到 Web 服务
在本节中,我们将把我们的 Raspberry Pi 连接到 Web 服务,并在 Sense HAT(或模拟器)的点阵屏幕上显示结果。我们将连接到的服务将是一个虚拟 Web 服务,旨在评估 RESTful Web 服务的调用。
在图 2.5中,我们看到一个 Raspberry Pi 从 Web 服务中提取天气信息,并使用 Sense HAT 的点阵显示屏显示云图的示例:

图 2.5 – Sense HAT 显示表示当前天气状况的云图
云代表当前的天气状况(不要与代表 Web 服务的云混淆)。这样的应用程序可以在点阵屏幕上显示动画。我们甚至可以替换天气 Web 服务为另一个 Web 服务,并创建一个全新的应用程序,利用现有的代码轻松实现。
在创建 Web 服务客户端之前,我们需要设置我们的开发环境并安装代码运行所需的必要包。我们将整合一个 Python 虚拟环境来完成这一任务。
设置我们的开发环境
我们将为我们的开发使用 Python 虚拟环境。由于有一些库只与 Python 的根安装版本兼容,我们将在 Python 虚拟环境中使用系统包。为此,我们执行以下操作:
-
在我们的树莓派 5 上,我们打开一个终端应用程序。
-
为了存储我们的项目文件,我们使用以下命令创建一个新的目录:
mkdir Chapter2 -
然后,我们使用以下命令导航到新目录:
cd Chapter2 -
我们使用以下命令为我们的项目创建一个新的 Python 虚拟环境:
ch2-env and enable access to the system site packages. This allows the virtual environment to inherit packages from the global Python environment, which can be useful when certain libraries are installed system wide. Once the environment is set up, we can activate it and begin installing project-specific packages without affecting the global Python environment. -
在我们创建新的 Python 虚拟环境后,我们使用以下命令将其源码导入(设置 Python 虚拟环境):
ch2-env Python virtual environment:

图 2.6 – 使用 ch2-env 环境的终端
-
我们使用以下命令安装代码所需的额外包:
requests library in Python simplifies making HTTP requests to web servers, and the sense-emu library will give us the Sense HAT emulator to work with for our code. With the libraries installed, we may close the Terminal with the following command:退出
-
现在,我们已经准备好加载 Thonny。我们通过点击树莓派任务栏中的菜单图标,导航到编程类别,并选择Thonny来完成这一操作。
-
默认情况下,Thonny 使用树莓派内置的 Python 版本。对于我们的项目,我们将使用我们刚刚创建的 Python 虚拟环境。首先,我们需要通过点击查看并选择文件来查看项目文件(如果尚未选择)。
-
在文件部分,我们定位到
ch2-env目录。 -
然后,我们右键点击文件夹并选择激活虚拟环境选项:

图 2.7 – 在 Thonny 中激活 Python 虚拟环境
在我们创建项目文件夹、设置并激活 Python 虚拟环境,以及安装了requests包之后,我们现在可以开始编写代码。
编写我们的第一个 Web 服务代码
现在,我们已经准备好编写我们的第一个 Web 服务代码。此代码将对一个用于测试的虚拟 Web 服务进行调用。在成功传输后,成功信息将在我们的 Sense HAT 的点阵屏幕上滚动显示:
-
为了创建我们的 Web 服务应用程序,在 Thonny 中我们创建一个新的标签页。在标签页内,我们编写以下代码:
import requests from sense_hat import SenseHat response = requests.get( 'https://jsonplaceholder.typicode.com/posts' ) sense = SenseHat() sense.set_rotation(270) if response.status_code == 200: data = response.json() print(data[0]['title']) success_msg = 'Success with code: ' success_msg += str(response.status_code) sense.show_message(success_msg) else: error_msg = 'Failed with code: ' error_msg += str(response.status_code) print(error_msg) sense.show_message(error_msg) -
在我们运行代码之前,让我们将其分解:
-
我们首先导入
requests库,这是 Python 中用于发送 HTTP 请求的流行库。 -
我们从
sense_hat库中导入SenseHat类,使我们能够与 Sense HAT 板交互。对于使用 Sense HAT 模拟器的我们,我们会使用sense_emu库。 -
我们向指定的 URL 发送
GET请求('https://jsonplaceholder.typicode.com/posts'),这是一个提供 JSON 格式占位符数据的虚拟 API 的端点。服务器的响应存储在response变量中。 -
我们创建了一个
SenseHat类的实例。我们使用这个对象来控制 Sense HAT 的点阵显示屏。 -
sense.set_rotation(270)这一行调整了 Sense HAT 的 LED 矩阵的方向,使其与我们的定制 Raspberry Pi 案例中的 Raspberry Pi 方向相匹配(有关定制 Raspberry Pi 案例的信息,请参阅 第一章)。
然后,我们的代码检查 HTTP 响应的状态码:
-
如果状态码是
200,这表示一个成功的 HTTP 请求,那么接下来会发生以下情况:-
data = response.json(): 我们的代码将 JSON 响应体转换为 Python 数据结构。 -
print(data[0]['title']): 我们的代码将响应数据中的第一篇帖子的标题打印到 Thonny 的 Shell 中。 -
我们的代码在 Sense HAT 的 LED 矩阵上显示一个成功消息,指示成功的状态码。
-
-
如果状态码不是
200,这表示一个不成功的 HTTP 请求,以下情况会发生:-
我们的代码将错误消息打印到 Shell,指示不成功的状态码。
-
我们的代码在 Sense HAT 的 LED 矩阵上显示一个失败消息,指示不成功的状态码。
-
-
-
我们将代码保存为
webservice-test.py,然后通过点击顶部的绿色运行按钮、按键盘上的 F5 或者在顶部的 运行 菜单选项中然后点击 运行 当前脚本 来运行它。 -
在成功完成网络服务调用(对于这个虚拟服务来说是预期的)后,我们应该在 Shell 中看到以下标题打印出来:

图 2.8 – 使用 Thonny 测试网络服务调用
- 我们不应该过分关注标题的内容,因为它只是占位符数据。随后,在成功完成网络服务调用后,我们应该在 Sense HAT 显示屏(或模拟器)上观察到滚动消息。这条消息表示我们的调用状态成功,应该用
200HTTP 状态码表示。
虽然我们的代码缺少错误检查,但我们已经成功构建了我们第一个由 Raspberry Pi 驱动的物联网设备。需要注意的是,像互联网连接这样的考虑因素并没有包含在我们的简单代码中。
在本章的剩余部分,我们将把我们的物联网设备从简单的网络服务测试工具提升到一个更具吸引力的工具。我们将承担两个令人兴奋的项目:构建股票行情应用和基于天气的 GO-NO-GO 决策应用。
创建一个滚动股票行情应用
现在是时候构建我们的第一个实用物联网设备了。对于这个项目,我们将使用 Raspberry Pi 和 Sense HAT 创建一个股票行情应用。股票行情是一种设备,可以是物理的也可以是数字的,它实时显示股票价格。在我们的应用中,我们将从 Alpha Vantage(一个提供股票数据免费 API 的在线服务)获取实时股票价格。在我们的应用中,我们将检索纳斯达克证券交易所上市的苹果公司(股票代码为AAPL)的当前股票价格。
我们可以在图 2.9中看到我们的股票行情应用示意图。我们将使用 HTTP GET方法以 JSON 格式检索信息,这是一种轻量级的数据交换格式,易于人类阅读和编写,也易于机器解析和生成:

图 2.9 – 我们股票行情应用的示意图;双箭头表示对 Alpha Vantage 网络服务的调用及其后续响应
在我们的案例中,当我们请求AAPL股票符号的数据时,Alpha Vantage API会发送一个如图图 2.10所示的 JSON 对象响应:

图 2.10 – Alpha Vantage 网络服务调用的 JSON 对象响应
响应包含了每个成功的 API 请求的 10 个参数。在这些参数中,我们的股票行情应用将关注symbol、volume、price和change。这些具体的数据点将被用来创建一个信息,我们将滚动显示在 Sense HAT 的点阵屏幕上。
然而,在我们编写网络服务代码之前,我们必须首先从 Alpha Vantage 获取一个 API 密钥。这个密钥赋予我们进行必要的网络服务调用的权限。
获取 API 密钥
从 Alpha Vantage 获取 API 密钥是一个简单的过程,只需几个步骤即可完成。我们首先导航到 Alpha Vantage 网站www.alphavantage.co。
从那里,我们点击获取免费 API 密钥按钮 – 这个按钮应该很容易在主页上找到。点击此按钮将带我们到一个注册表单。我们填写表单上的必要详细信息,确保提供一个有效的电子邮件地址。一旦填写完表单,我们应该会得到一个 API 密钥,然后点击获取免费 API 密钥按钮。
重要提示
上述说明在撰写本文时有效。请遵循任何对获取 API 密钥过程的更改。
一旦颁发 API 密钥,我们必须将其复制并粘贴到文本编辑器中,因为我们每次调用 Alpha Vantage 网络服务都需要这个密钥。作为免费用户,我们每分钟限制 5 个 API 请求,每天总共 500 个 API 请求。
有了我们的 API 密钥,我们现在可以开始编写应用程序的代码。
编写网络服务客户端代码
在本节中,我们将开始开发用于获取苹果公司(AAPL)当前股票信息的网络服务代码。我们的目标是检索 Alpha Vantage 网络服务的 JSON 对象响应,这将包含我们用于滚动股票行情应用所需的相关股票数据。
要创建我们的网络服务代码,我们执行以下操作:
-
我们在 Raspberry Pi 上启动 Thonny 并使用上一节中的步骤激活
ch2-envPython 虚拟环境。 -
然后,我们在 Thonny 中打开一个新的标签页并输入以下代码:
import requests import json api_key = 'xxxxxxxxxxxxxxxx' symbol = 'AAPL' base_url = 'https://www.alphavantage.co/query?' function = 'GLOBAL_QUOTE' complete_url = f'{base_url}function={function}&symbol={symbol}&apikey={api_key}' response = requests.get(complete_url) data = response.json() print(json.dumps(data, indent=4)) -
在我们运行代码之前,让我们将其分解:
-
我们首先导入
requests模块,这是一个广泛使用的 Python 库,用于发送 HTTP 请求。它提供了发送 HTTP 请求(如GET、POST等)的便捷方法,并处理底层网络通信。 -
然后,我们导入 Python 的内置
json模块。json模块提供了处理 JSON 数据的方法。它允许将 Python 对象编码为 JSON 字符串(json.dumps())并将 JSON 字符串解码为 Python 对象(json.loads())。 -
我们将来自 Alpha Vantage 的个人 API 密钥存储在一个名为
api_key的变量中。 -
我们将
symbol变量设置为'AAPL',代表苹果公司的股票符号。 -
base_url变量存储 Alpha Vantage API 的基础 URL。 -
我们将
function变量设置为'GLOBAL_QUOTE',表示要检索全球股票报价的特定函数。 -
我们通过组合基础 URL、函数、符号和 API 密钥来构建
complete_url变量,从而形成 API 请求的完整 URL。 -
我们的代码随后使用
requests.get()向 Alpha Vantage API 发送GET请求,并将响应存储在response变量中。 -
我们使用
.json()从response对象中提取 JSON 响应,并将结果数据存储在data变量中。 -
最后,代码使用
json.dumps()并将indent参数设置为4来以格式化的 JSON 表示形式打印data。
-
-
我们将代码保存为
alphavantage-test.py,然后通过点击绿色运行按钮、按键盘上的 F5 或者在顶部菜单中选择 运行 选项然后选择 运行当前脚本 来运行它。 -
我们应该在控制台中看到类似于 图 2.11 中显示的 JSON 对象:

图 2.11 – 在 Thonny 的 Shell 中显示的 JSON 响应
重要提示 – 代码仅供演示
请注意,在提供的代码中,为了简化,省略了错误检查。如果这个应用程序要部署到生产环境(例如供客户使用),我们一定会包括适当的错误处理和错误检查机制,以确保应用程序的可靠性和稳定性。
当我们从互联网上拉取股票信息的代码完成后,现在是我们利用 Sense HAT 的点阵屏幕创建滚动股票行情的时候了。
优化我们的应用程序
通过我们对 Alpha Vantage 网络服务的理解,我们现在能够创建一个应用程序,它能够获取股票数据并将其转换为现实生活中的滚动股票行情。我们的应用程序利用了 Sense HAT 的点阵显示屏,将其变成股票行情的动态画布。我们不再将 JSON 响应打印到控制台,股票信息将优雅地滚动穿过 Sense HAT 显示屏,提供数据的视觉吸引表示。
要创建我们的网络服务代码,我们在 Raspberry Pi 上启动 Thonny 并创建一个新标签页:
-
我们在 Raspberry Pi 上启动 Thonny,并使用上一节中的步骤激活
ch2-envPython 虚拟环境。 -
在 Thonny 的新标签页中,我们首先导入必要的库:
import requests from sense_hat import SenseHat import time在我们的代码中,我们导入了
requests模块,以及从sense_hat模块中导入的SenseHat类。对于使用 Sense HAT 模拟器的我们,需要将此改为from sense_emu import SenseHat。然后我们导入time模块。 -
在我们的库就绪后,我们创建并设置我们在代码中使用的变量:
api_key = 'xxxxxxxxxxxxxxxx' symbol = 'AAPL' base_url = 'https://www.alphavantage.co/query?' function = 'GLOBAL_QUOTE' sense = SenseHat() sense.set_rotation(270) last_call_time = time.time() - 180 last_ticker_info = ""在这些代码行中,我们使用
api_key变量来存储我们访问网络服务的唯一 Alpha Vantage API 密钥。我们使用symbol变量来存储要获取数据的股票符号(例如,'AAPL')。base_url变量用于存储网络服务 API 的基本 URL。function变量用于定义要从网络服务 API 中调用的特定函数(例如,'GLOBAL_QUOTE')。然后我们创建SenseHat类的实例,并将其分配给sense变量以与 Sense HAT(或模拟器)交互。我们使用sense.set_rotation(270)将 Sense HAT 显示屏的旋转设置为270度,这样它就与我们的定制外壳中的 Raspberry Pi 的方向相匹配。对于模拟器,我们可以取消注释这一行。然后我们使用当前时间减去 180 秒初始化last_call_time变量,这样就可以立即对网络服务进行第一次调用。我们将last_ticker_info变量初始化为空字符串,以存储之前的股票行情信息。 -
在我们的变量声明下方,我们实现了一个无限循环以持续显示行情信息;然而,为了遵守每分钟 5 次请求和每天 500 次请求的 API 速率限制,我们在每次网络服务调用之间引入了 3 分钟的时间延迟。我们在变量声明下方键入以下代码:
while True: current_time = time.time() if current_time - last_call_time >= 180: complete_url = f'{base_url}function={ function}&symbol={ symbol}&apikey={api_key}' response = requests.get(complete_url) data = response.json() quote = data['Global Quote'] ticker_info = ( f"{quote['01\. symbol']} " f"Price: {quote['05\. price']} " ) ticker_info += ( f"Volume: {quote['06\. volume']} " f"Change: {quote['09\. change']}" ) last_ticker_info = ticker_info sense.show_message(ticker_info, scroll_speed=0.05, text_colour=[255, 255, 255]) last_call_time = current_time else: sense.show_message(last_ticker_info, scroll_speed=0.05, text_colour=[255, 255, 255]) time.sleep(1)我们的代码被包裹在一个
while True循环中,这确保了以下代码块的持续执行:-
使用
time.time()将current_time变量设置为当前时间。 -
我们的代码随后检查
current_time和last_call_time之间的差异是否大于或等于 180 秒。 -
如果为
True,则发生以下情况:-
使用 f-string 创建一个
complete_url变量,以形成 API 调用的 URL。 -
使用
requests.get(complete_url)向 API 发送 HTTPGET请求,并将响应存储在response变量中。 -
使用
response.json()将响应解析为 JSON,并分配给data变量。 -
从
data字典中提取相关的股票信息,并将其格式化为ticker_info字符串。 -
将
last_ticker_info变量更新以存储当前的ticker_info值。 -
使用
sense.show_message()将ticker_info字符串显示在 Sense HAT 的点阵显示屏上,滚动速度为 0.05 秒,白色文字颜色(255,255,255)。 -
将
last_call_time变量更新为当前时间(current_time),以标记上次 API 调用的时间戳。
-
-
如果为
False,则将上一个last_ticker_info变量显示在 Sense HAT 显示屏上,滚动速度为 0.05 秒,白色文字颜色(255,255,255)。 -
在循环的下一个迭代之前,我们的程序使用
time.sleep(1)暂停 1 秒钟。这是为了调节资源消耗和控制 Sense HAT 点阵显示屏的更新频率。
-
-
我们将我们的代码保存为
aapl-stock-ticker.py,然后通过点击绿色运行按钮,按键盘上的F5,或者在顶部菜单中选择运行然后运行当前脚本来运行它。 -
执行代码后,我们应该在 Sense HAT 的点阵屏幕上观察到股票信息滚动。如果我们使用模拟器,信息将在模拟的点阵显示上滚动,考虑到模拟器设置的 270 度方向。图 2.12提供了使用模拟器时这种显示的视觉表示:

图 2.12 – Sense HAT 模拟器上的股票行情信息
祝贺我们成功构建了我们第一个真正的物联网设备,一个使用 Raspberry Pi 和 Sense HAT 的股票行情显示设备!这个设备开启了一个超越仅显示股票信息的世界。在下一节中,我们将开始开发显示天气条件的应用程序。
开发天气显示应用程序
现在,作为经验丰富的物联网应用开发者,我们准备将我们的技能提升到下一个层次,并创建更复杂的项目。在本节中,我们将利用 Raspberry Pi 和 Sense HAT 的功能来创建一个天气显示应用和一个基于天气的 GO-NO-GO 决策应用。
在 图 2.13 中,我们看到一个图表,展示了我们的 Raspberry Pi 和 Sense HAT(封装在其定制外壳中)对 OpenWeather API 的调用。对于我们的天气显示应用,我们将遵循与滚动股票指示器类似的方法:

图 2.13 – 使用 OpenWeather API 获取当前天气状况
我们将首先从 OpenWeather 获取一个 API 密钥,并通过将响应打印到 Shell 中进行测试来验证 API 调用。然后,我们将利用 Sense HAT 创建一个指示器风格的显示,显示当前的天气状况。
最后,随着我们构建一个基于天气的 GO-NO-GO 决策应用,我们将用视觉效果替换滚动显示。
我们将首先获取一个 API 密钥。
获取 API 密钥
要利用 OpenWeather 网络服务,必须获取一个 API 密钥。这个 API 密钥作为一个唯一的标识符,允许访问 OpenWeather 网络服务。通过在 OpenWeather 网站上创建账户并订阅适当的服务来生成 API 密钥。API 密钥作为凭证,用于验证和授权我们对 OpenWeather 网络服务的请求,使我们能够获取世界各地各种位置的天气数据。重要的是要保密 API 密钥,并安全地存储它,因为它代表我们访问 OpenWeather API。
要从 OpenWeather 获取免费的 API 密钥,我们首先导航到位于 openweathermap.org/price 的 OpenWeather 价格页面。然后,我们滚动到 当前天气和预报集合 部分,并点击 获取 API 密钥 按钮:

图 2.14 – 从 OpenWeather 获取 API 密钥
我们遵循创建新账户的说明。成功创建账户后,我们就可以访问个人仪表板。在个人仪表板中,我们导航到 API 密钥 选项卡,并在 密钥 框中找到 API 密钥。
我们将密钥复制粘贴到文本编辑器中,因为我们每次调用 OpenWeather 网络服务时都需要这个密钥。作为免费用户,我们每分钟限制 60 个 API 请求,每月总共 1,000,000 个 API 请求。这对我们的应用来说应该足够了。
使用我们的 OpenWeather API 密钥,我们现在可以开始编写代码来测试这个网络服务。
创建一个滚动的天气信息指示器
使用我们的 OpenWeather API 密钥、Raspberry Pi 和 Sense HAT,我们现在将创建一个模拟我们滚动股票行情功能的滚动天气信息设备。我们将首先获取天气数据,并在 Thonny 的 Shell 中显示结果。
在我们满意我们的 API 密钥和网络服务工作后,我们将集成网络服务数据与 Sense HAT,显示滚动文本,显示温度和天气状况。
测试网络服务
在将 OpenWeather API 集成到我们的 Raspberry Pi 和 Sense HAT 之前,我们将通过一个简单的程序确保其功能。为了创建测试代码,我们执行以下操作:
-
我们在 Raspberry Pi 上启动 Thonny,激活
ch2-envPython 虚拟环境,并创建一个新标签。在标签内,我们编写以下代码:import requests url = "https://api.openweathermap.org/data/2.5/weather" api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" location = "Toronto" params = { "q": location, "appid": api_key, "units": "metric" } response = requests.get(url, params=params) if response.status_code == 200: data = response.json() temperature = data["main"]["temp"] description = data["weather"][0]["description"] print(f"The current temperature in {location} is {temperature}°C.") print(f"The weather is {description}.") else: print("Error: Failed to retrieve weather information.")在我们运行代码之前,让我们将其分解:
-
我们首先导入
requests模块以发送 HTTP 请求。 -
然后,我们将
url变量设置为 OpenWeather API 端点。 -
我们将
api_key变量设置为我们的 OpenWeather API 密钥。 -
我们将
location变量设置为我们要获取天气信息的所需位置。在我们的例子中,这是"Toronto"。 -
然后,我们创建一个名为
params的字典,其中包含 API 请求的参数,包括位置、API 密钥和所需的单位。 -
使用
requests.get()发送一个GET请求到 OpenWeather API,其中url和params作为参数。 -
然后,我们检查响应状态码是否为
200(表示请求成功)。 -
如果响应成功,我们使用
response.json()解析响应中的 JSON 数据,然后执行以下操作:-
我们从解析的数据中提取温度和天气描述。
-
然后,我们打印指定位置的当前温度和天气信息。
-
-
如果有错误(响应状态码不是
200),我们将打印一个错误消息,指出无法检索天气信息。
-
-
我们将我们的代码保存为
weather-api-test.py,然后通过点击绿色运行按钮、按键盘上的 F5 或者在顶部菜单中选择 运行 然后选择 运行当前脚本 来运行它。
在我们执行代码后,我们应该在 Shell 中看到一个消息:

图 2.15 – OpenWeather API 关于多伦多天气的信息
如我们所见,在撰写本文时,多伦多是 29.15 °C,天气晴朗。如果网络服务调用失败,我们将在控制台中看到 Error: Failed to retrieve weather information 错误。
在我们了解了如何使用 OpenWeather API 后,我们现在准备好使用 Sense HAT 创建我们的滚动天气信息指示器。为此,我们可以重用我们为滚动股票行情应用编写的代码的大部分。
Sense HAT 上的滚动天气信息
正如我们在上一个项目中指出的,我们的滚动股票行情应用的通用性使我们能够将其适应以显示除股票之外的各种类型的信息。在本节中,我们将利用这种适应性,通过集成 OpenWeather API 和我们的 API 密钥,将我们的行情转换为动态天气显示,滚动实时天气数据,如温度和当前状况。我们将能够重用滚动股票行情中的大量代码。为了创建滚动行情代码,我们执行以下操作:
-
我们在 Raspberry Pi 上启动 Thonny,激活
ch2-envPython 虚拟环境,并创建一个新标签。我们将从导入开始:import requests from sense_hat import SenseHat import time
重要提示
由于我们已经在我们之前的滚动股票行情应用中涵盖了这些导入,所以我们不需要再次介绍它们。
-
在导入之后,我们设置我们的变量:
api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' location = 'Toronto' base_url = 'https://api.openweathermap.org/data/2.5/weather' params = { 'q': location, 'appid': api_key, 'units': 'metric' } sense = SenseHat() sense.set_rotation(270) last_call_time = time.time() - 30 last_weather_info = ""在此代码块中,我们执行以下操作:
-
我们首先将
api_key变量分配给我们的 OpenWeather API 密钥。 -
我们将
location变量设置为想要获取天气信息的所需位置。在我们的例子中,这是'Toronto'。 -
然后,我们将
base_url变量设置为 OpenWeather API 端点。 -
我们创建了一个名为
params的字典,其中包含了 API 请求的参数,包括位置、API 密钥和所需的单位。 -
使用
requests.get()发送一个GET请求到 OpenWeather API,其中url和params作为参数。 -
然后,我们创建了一个
SenseHat类的实例,并将其分配给sense变量以与 Sense HAT(或模拟器)交互。 -
我们使用
sense.set_rotation(270)将 Sense HAT 显示的旋转设置为 270 度。这样做是为了使其与我们的定制外壳中的 Raspberry Pi 的方向相匹配。对于模拟器,我们可以取消注释这一行。 -
我们将
last_call_time设置为当前时间减去 30 秒。 -
然后,我们添加
last_weather_info,这是一个存储先前天气信息的变量。
-
-
在我们的变量声明下方,我们实现了一个无限循环,以持续显示天气行情信息;然而,为了遵守每分钟 60 次和每月 1,000,000 次的 API 速率限制,我们在每次网络服务调用之间引入了 30 秒的时间延迟。我们在变量声明下方输入以下代码:
while True: current_time = time.time() if current_time - last_call_time >= 30: response = requests.get(base_url, params=params) data = response.json() temperature = data['main']['temp'] description = data['weather'][0]['description'] weather_info = f"{location}: {temperature}°C, {description}" last_weather_info = weather_info sense.show_message(weather_info, scroll_speed=0.05, text_colour=[255, 255, 255]) last_call_time = current_time else: sense.show_message(last_weather_info, scroll_speed=0.05, text_colour=[255, 255, 255]) time.sleep(1) -
就像在我们的滚动股票行情应用中一样,我们的代码核心被包裹在一个
while True循环中,这确保了主代码的持续执行:-
我们使用
time.time()将current_time变量设置为当前时间。 -
然后,我们的代码检查
current_time和last_call_time之间的差异是否大于或等于 30 秒。 -
如果为
True,则发生以下情况:-
使用
requests.get()发送一个GET请求到 OpenWeather API,其中base_url和params作为参数。 -
响应被解析为 JSON 并使用
response.json()分配给data变量。 -
我们从解析的数据中提取温度和天气描述,并将其存储为
weather_info.。 -
将
last_weather_info变量更新以存储当前的weather_info值。 -
使用
sense.show_message()在 Sense HAT 的点阵显示屏上显示weather_info,滚动速度为 0.05 秒,文字颜色为白色(255,255,255)。 -
将
last_call_time变量更新为当前时间(current_time),以标记上次 API 调用的时间戳。
-
-
如果
False,则使用 0.05 秒的滚动速度和白色文字颜色(255,255,255)在 Sense HAT 显示屏上显示上一个last_weather_info变量。 -
程序随后使用
time.sleep(1)暂停 1 秒,然后进行循环的下一迭代。这样做是为了调节资源消耗和控制 Sense HAT 点阵显示屏的更新频率。
-
-
我们将代码保存为
weather-scroll.py,然后通过点击绿色运行按钮、按键盘上的F5键或点击顶部菜单的运行选项然后运行****当前脚本来运行它。
执行代码后,我们应该观察到天气信息在 Sense HAT 的点阵屏幕上滚动。如果我们使用模拟器,一条信息将在模拟的点阵显示屏上滚动。图 2.16提供了使用模拟器时这种显示的视觉表示:

图 2.16 – 天气信息在模拟点阵显示屏上滚动
一个重要的启示是利用现有代码创建新应用的力量。尽管股票信息和天气数据之间存在固有的差异,但获取这两个领域信息的过程却惊人地相似。有了这个认识,我们就可以利用相同的底层代码结构创建各种动态和吸引人的显示。
这里有一些我们可以构建的其他应用的例子:
-
新闻更新:通过修改代码,我们可以将我们的设备与新闻 API 集成,以显示来自流行新闻来源的实时标题或更新。
-
社交媒体通知:通过将我们的应用程序连接到社交媒体 API,我们可以配置它显示来自 Twitter 或 Facebook 等流行平台的通知。
-
体育比分:通过集成体育数据 API,我们的股票行情应用可以转变为实时体育比分板。它可以显示实时比分、比赛更新或即将到来的比赛日程。
-
个性化提醒:通过扩展代码的功能,我们可以编程股票行情应用以显示个性化的提醒或待办事项列表。
在本章的下一项和最后一项项目中,我们将用点阵图像和动画替换我们的滚动文本显示。这种从滚动文本到图像的转变提升了用户体验,并将使我们的项目更具视觉吸引力。
开发 GO-NO-GO 决策应用
考虑到一个青年棒球联赛的组织者角色,负责确保比赛场地的安全。这一责任的关键是做出基于天气的决定。如果场地过于潮湿,可能会影响比赛,可能导致比赛推迟或取消:

图 2.17 – 比赛是否应该继续?
另一个需要考虑的因素是球员的年龄。对于年轻球员来说,在雨中比赛会引发担忧,因为父母通常在场,可能会对不利的天气条件表示不满。另一方面,通常独立参加比赛的年长球员可能受潮湿条件的影响较小。
这些决策场景代表了一个开发物联网应用的机会,该应用可以根据天气条件和球员年龄显示视觉指示器,如GO或NO-GO图形(图 2**.17)。想象一下,每个棒球场地都配备一个 Raspberry Pi 和 Sense HAT 的设置,Sense HAT 显示屏提供实时指导,以确定比赛是否应该按计划进行,是否应该推迟,或者完全取消。这个物联网应用使决策更加高效,并提升了青年棒球联赛的整体体验和安全。
在我们的简化示例中,我们将专注于将基本的决策集成到我们的物联网应用中。根据球员的年龄和雨水的存在,Sense HAT 将显示绿色勾选标记或动画红色 X 标志。虽然我们可以引入额外的复杂性,但这个练习的主要目标是展示决策如何集成到物联网应用中。通过引入这些视觉指示器,我们赋予了实时决策能力。我们的物联网应用不再仅仅依赖于组织者,而是通过提供即时的指导来负责决定比赛是否应该继续或推迟。
我们将首先编写 Sense HAT 的指示代码。对于 GO,我们将显示一个简单的绿色勾选标记在黑色背景上。对于 NO-GO,我们将显示闪烁的红色 X。我们将使用 Sense HAT 模拟器运行我们的应用程序,因为对于本书来说,显示截图更容易;然而,强烈建议使用 Sense HAT,因为这使我们的应用程序成为一个真正的物联网设备。
我们将首先编写代码来显示绿色勾选标记。
在我们的 Sense HAT 上创建勾选标记
在本节中,我们将创建代码,在 Sense HAT 模拟器上显示绿色勾选标记在黑色背景上。为了增强代码实现和组织,我们将功能封装在一个 Python 类中。这种方法简化了集成过程并促进了代码的可重用性,使我们能够轻松地将绿色勾选标记显示集成到我们的物联网应用项目中。
在编写 GO-NO-GO 应用程序的代码之前,我们将在 Raspberry Pi 上创建一个名为GO-NO-GO的项目目录。这个专用文件夹将作为组织和管理与我们的项目相关的文件和资源的集中位置。为了创建勾选标记代码,我们执行以下操作:
-
我们在 Raspberry Pi 上启动 Thonny,激活
ch2-env虚拟环境,并创建一个新标签页。在标签页内,我们编写以下代码:from sense_emu import SenseHat class GreenCheck: black = (0, 0, 0) green = (0, 255, 0) check_mark_pixels = [ black, black, black, black, black, black, black, green, black, black, black, black, black, black, green, green, black, black, black, black, black, black, green, green, black, black, black, black, black, green, green, black, green, black, black, black, green, green, black, black, black, green, black, black, green, green, black, black, black, green, green, green, green, black, black, black, black, black, black, green, black, black, black, black ] def __init__(self, rotation=0): self.sense = SenseHat() self.sense.set_rotation(rotation) def display(self): self.sense.set_pixels(self.check_mark_pixels) if __name__ == "__main__": greenCheck = GreenCheck(rotation = 270) greenCheck.display()在我们的代码中,我们执行以下操作:
-
我们首先从
sense_hat模块导入SenseHat类(使用sense_emu进行模拟) -
然后我们定义一个
GreenCheck类,用于在 Sense HAT 上显示绿色勾选标记。 -
我们将黑色和绿色的颜色值设置为 RGB 元组。
-
然后我们定义一个表示勾选标记形状的像素值列表。
-
GreenCheck类使用可选的旋转参数初始化,默认值为0。 -
在
__init__方法中,我们创建了一个 Sense HAT 实例,并将旋转设置为rotation的值。 -
我们定义一个
display方法,将 Sense HAT 的像素设置为勾选标记的像素值。 -
我们使用
if __name__ == "__main__"来检查代码是否直接运行(不是导入)。 -
如果
True,我们执行以下操作:-
我们创建了一个名为
greenCheck的GreenCheck类实例,旋转值为270。 -
我们调用
display()方法,在 Sense HAT 上显示一个绿色的勾选标记。
-
-
-
我们将我们的代码保存为
green_checkmark.py在GO-NO-GO文件夹中,然后通过点击绿色运行按钮,按键盘上的F5,或者在顶部菜单中选择运行然后运行当前脚本来运行它。 -
执行代码后,我们应该在我们的 Sense HAT 模拟器上看到黑色背景上的绿色勾选标记:

图 2.18 – Sense HAT 点阵显示上的黑色背景上的绿色勾选标记
完成绿色勾选标记代码后,我们现在将注意力转向为我们的应用程序创建 NO-GO 动画(闪烁红色 X)。
在 Sense HAT 上创建 NO-GO 动画
我们设计的 NO-GO 动画在 Sense HAT 模拟器显示上产生闪烁效果,交替显示黑色背景上的红色 X 标志和全红色显示。为了创建闪烁 X 标志的代码,我们执行以下操作:
-
我们在 Raspberry Pi 上启动 Thonny,激活
ch2-envPython 虚拟环境,并创建一个新标签页。在标签页内,我们首先导入所需的包:from sense_emu import SenseHat import time -
一旦我们定义了包,我们接下来开始将我们的代码封装在一个 Python 类中:
class RedXAnimation: black = (0, 0, 0) red = (255, 0, 0) frame1 = [ red, black, black, black, black, black, black, red, black, red, black, black, black, black, red, black, black, black, red, black, black, red, black, black, black, black, black, red, red, black, black, black, black, black, black, red, red, black, black, black, black, black, red, black, black, red, black, black, black, red, black, black, black, black, red, black, red, black, black, black, black, black, black, red ] frame2 = [ red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red, red ]在我们的代码中,我们执行以下操作:
-
我们首先定义一个
RedXAnimation类。 -
然后我们将黑色和红色的颜色值设置为 RGB 元组。
-
我们将
frame1定义为表示黑色背景上红色 X 标志的像素值列表。 -
我们将
frame2定义为表示全红色显示的像素值列表。
-
-
从这里,我们为类的初始化方法编写代码:
def __init__(self, rotation=0): self.sense = SenseHat() self.sense.set_rotation(rotation)在我们的代码中,我们执行以下操作:
-
我们使用
__init__方法以可选的rotation参数(默认为0)初始化RedXAnimation对象。 -
在
__init__内部,创建了一个SenseHat实例,并根据提供的rotation值设置旋转。
-
-
display_animation()方法将循环显示 2 帧 59 秒。我们这样做是为了与未来的客户端代码保持一致:def display_animation(self, duration): num_frames = 2 frame_duration = duration / num_frames start_time = time.time() end_time = start_time + 59 while time.time() < end_time: for frame in [self.frame1, self.frame2]: self.sense.set_pixels(frame) time.sleep(frame_duration)在我们的代码中,以下情况发生:
-
我们的
display_animation()方法接受一个持续时间参数。 -
我们将帧数设置为
2.。 -
我们通过将总持续时间除以帧数来计算每帧的持续时间。
-
我们使用
time.time()将start_time变量设置为当前时间。 -
我们通过将 59 秒加到
start_time变量上来计算end_time值。 -
我们创建一个循环,直到当前时间超过
end_time值:-
我们的代码遍历列表
[self.frame1, self.frame2]中的每一帧。 -
我们使用
self.sense.set_pixels(frame)将 Sense HAT 显示屏像素设置为当前帧。 -
然后我们使用
time.sleep(frame_duration)暂停执行以帧的持续时间。
-
-
-
我们使用
if __name__ == "__main__":块来确保测试代码仅在脚本直接运行时执行(不是作为模块导入):if __name__ == "__main__": animation = RedXAnimation(rotation=270) animation.display_animation(duration=1)在我们的代码中,以下情况发生:
-
创建一个旋转值为 270 度的
RedXAnimation类实例,并将其分配给animation变量。 -
调用
animation对象的display_animation()方法,指定持续时间为 1 秒。
-
-
我们将我们的代码保存为
flashing_x.py在GO-NO-GO文件夹中,然后通过点击绿色运行按钮,按键盘上的 F5 或者在顶部菜单中选择 运行 然后选择 运行当前脚本 来运行它。
执行代码后,我们应该观察到红色 X 标志在黑色背景上变为全屏红色,然后再回到原来的状态。在 图 2.19 中,我们可以看到在模拟器上这会是什么样子:

图 2.19 – 红屏模式的 NO-GO 动画
我们在本节中创建的 NO-GO 动画在 Sense HAT 显示屏上提供了一个非常有效的视觉指示器。通过在黑色背景上的红色 X 标志和全红色显示之间交替,这个动画传达了需要取消比赛的不利条件。
为其他城市设置地理定位
为了找到城市的地理定位信息,如纬度和经度,像 GPS Coordinates (gps-coordinates.org/) 和 Latitude and Longitude Finder (www.latlong.net/) 这样的网站非常有用。它们允许我们输入一个地址或地点,并接收其精确的坐标。
为了完成我们的应用程序,我们现在将编写网络服务和逻辑层,并将包含我们的绿色勾选和红色 X 标志动画。
编写 GO-NO-GO 客户端代码
现在,是时候进入编写代码的激动人心阶段(当然,这是主观的)了,以确定游戏应该是 GO 还是 NO-GO,基于天气条件和玩家的年龄。我们的方法将非常直接:如果下雨且玩家年龄低于 16 岁,则为 NO-GO;否则,为 GO。虽然我们可以当然实现更复杂的逻辑,包括机器学习(ML),如果需要考虑多个参数,但为了简单起见,我们将专注于这个基本的决策过程。我们这样做如下:
-
为了创建客户端代码,我们在 Raspberry Pi 上启动 Thonny,激活我们的
ch2-envPython 虚拟环境,并创建一个新标签。在标签内,我们首先导入所需的包:import requests import time from green_checkmark import GreenCheck from flashing_x import RedXAnimation我们已经介绍了前三个包。对于这两个模块,我们做以下操作:
- 我们从
green_checkmark模块导入GreenCheck类,以显示绿色勾选,从flashing_x模块导入以显示红色 X 标志动画,当决策为NO-GO时。
- 我们从
-
在我们的包和模块就绪后,我们现在设置我们的变量:
latitude = '42.346268' longitude = '-71.095764' go = GreenCheck(rotation=270) no_go = RedXAnimation(rotation=270) timer = 1 age = 12 base_url = "https://api.openweathermap.org/data/2.5/weather" api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" params = { 'lat': latitude, 'lon': longitude, 'appid': api_key, 'units': 'metric' }在我们的代码中,我们做以下操作:
-
我们将
latitude设置为'42.346268',将longitude设置为'-71.095764',用于我们的棒球场。例如,这是美国马萨诸塞州波士顿芬威公园的 GPS 坐标。 -
我们创建一个名为
go的GreenCheck对象,旋转值为 270 度。 -
我们创建一个名为
no_go的RedXAnimation对象,旋转值为 270 度。 -
我们将
timer值设置为 1 秒。 -
我们将玩家的年龄设置为 12 岁。
-
我们的代码将
base_url值设置为"https://api.openweathermap.org/data/2.5/weather"。 -
接下来,我们添加我们的 OpenWeather
api_key值。 -
我们定义了一个
params字典,我们将用它来调用我们的网络服务(latitude、longitude、api_key和units)。
-
-
我们使用无限循环每 60 秒检查一次天气条件,并相应地更新 Sense HAT 上的显示:
while True: response = requests.get(base_url, params=params) if response.status_code == 200: data = response.json() temperature = data['main']['temp'] description = data['weather'][0]['main'] print(f"The current temperature is {temperature}°C.") print(f"The weather is {description}.") if description == 'Thunderstorm' or description == 'Rain' and age < 16: print("NO-GO!") no_go.display_animation(duration=1) timer = 1 else: print("GO!") go.display() timer = 60 else: print("Error: Failed to retrieve weather information.") time.sleep(timer)在我们的代码中,我们使用
while True设置了一个无限循环:-
我们使用
requests.get()向 OpenWeather API 发起一个GET请求,并将响应存储在response中。 -
如果响应状态码是
200,我们做以下操作:-
我们使用
response.json()将 JSON 响应解析为 Python 字典,并将其分配给data。 -
然后,我们从
data['main']['temp']检索当前温度并将其存储在temperature中。 -
我们从
data['weather'][0]['main']检索天气描述并将其存储在description中。 -
然后,我们打印当前的温度和天气描述。如果天气描述是
'Thunderstorm'或 ('Rain'且age < 16),我们将向 Shell 打印"NO-GO!",使用no_go.display_animation(duration=1)显示 NO-GO 动画,并将timer变量设置为 1 秒。这是为了使调用 web 服务前的总时间为 60 秒,因为动画将持续 59 秒。否则,我们将向 Shell 打印"GO!",使用go.display()显示绿色勾选动画,然后设置timer变量为 60 秒。
-
-
如果响应状态码不是
200,我们将打印错误信息。 -
我们使用
time.sleep(timer)暂停执行timer秒。这将导致在调用 OpenWeather web 服务之间产生 60 秒的延迟。
-
-
我们将代码保存为
go-no-go.py文件,存放在GO-NO-GO文件夹中,然后通过点击绿色运行按钮、按键盘上的 F5 键,或者在顶部菜单中选择 运行 选项,然后选择 运行 当前脚本 来运行它。运行代码后,我们将观察到我们的 Sense HAT(或模拟器)的点阵屏幕显示绿色勾选或闪烁的红色 X,指示波士顿芬威公园比赛的 GO 或 NO-GO 条件。如 *图 2**.20 所示,当前状态是由于雷暴的存在,涉及我们球员(16 岁以下)的比赛为 NO-GO:

图 2.20 – 使用 Sense HAT 模拟器的 GO-NO-GO 应用程序截图
如前所述,我们代码的灵活性允许我们轻松扩展决策逻辑。除了天气数据外,我们还可以扩展我们的应用程序,考虑其他因素,如风速、湿度或任何现场传感器读数。通过集成直接放置在棒球场上的传感器,我们可以收集土壤湿度水平或其他感兴趣测量值的实时数据。然后,我们可以将这些传感器信息广播到互联网上,使我们能够无缝地将它集成到我们的应用程序中。
为了使我们的应用程序更加动态,我们可以结合调度信息来确定在特定棒球场上任何给定时间安排比赛的球员的年龄。通过从电子表格或在线存储库中提取此信息,我们可以自动化获取球员年龄数据和其他与比赛相关的信息(如比赛是否为季后赛)的过程。这使得我们的应用程序能够动态调整其决策过程,确保更准确的 GO 或 NO-GO 决定。
构建其他 GO-NO-GO 应用程序
GO-NO-GO 应用程序标志着我们将使用 Sense HAT 构建的最后一本书中的项目。正如我们所展示的,Raspberry Pi 和 Sense HAT 的结合构成了一个强大的物联网设备。不难想象,我们如何轻松地将我们的棒球 GO-NO-GO 应用程序更改为其他场景。以下是我们可以构建的其他 GO-NO-GO 应用程序的几个示例:
-
航班状态检查器:通过集成航班跟踪 API,我们可以构建一个应用程序,该程序可以显示特定航班的 GO 或 NO-GO 状态。
-
交通状况监控器:利用交通数据 API,我们可以构建一个应用程序,该程序可以评估特定路线或特定地点的当前交通状况。
-
活动可用性指示器:通过集成活动票务 API,我们可以构建一个应用程序,该程序可以确定所需活动的门票可用性。
-
公共交通追踪器:通过连接到公共交通 API,我们可以构建一个应用程序,该程序可以提供公交车、火车或其他公共交通工具的实时状态更新。
GO-NO-GO 物联网应用程序只是我们利用 Web 服务和物联网的巨大潜力的一个缩影。有了 Raspberry Pi 和 Sense HAT,我们的潜力扩展到各种物联网应用,监控各种数据,并在与天气相关的场景之外促进创新。
摘要
在本章中,我们探讨了使用 Raspberry Pi 和 Sense HAT 进行 Web 服务开发的领域。我们首先了解了 Web 服务,并编写了 Web 服务代码。凭借我们的新知识,我们创建了我们的第一个物联网应用程序:一个滚动股票行情。通过连接到 Alpha Vantage Web 服务,我们检索了实时股票信息,并在 Sense HAT 的点阵显示屏上以连续滚动格式显示。这个项目展示了连接到 Web 服务以获取有用信息的简便性。
将 Web 服务与 Raspberry Pi 等设备集成是当今科技行业的一项关键技能。通过处理来自 Alpha Vantage 和 OpenWeather 等来源的数据,并在 Sense HAT 上显示,我们将理论与实践相结合。这种知识增强了我们的项目能力和专业技能,使我们能够在物联网和数据驱动领域占据有利位置。
然后,我们尝试构建一个天气显示应用程序。通过利用 OpenWeather API,我们获得了实时天气信息,并将其转换成 Sense HAT 上的滚动信息。然后,我们将我们的开发推进到下一步,并使用它来创建一个决策 GO-NO-GO 物联网应用程序。在 GO-NO-GO 应用程序中,我们使用天气条件和球员年龄作为标准,以确定棒球比赛是否应该继续(GO)或取消(NO-GO)。我们通过在 Sense HAT 上显示视觉指示器,如绿色勾号或闪烁的红色 X 标志来实现这一点。
在下一章中,我们将探讨涉及物理交互的物联网应用——特别是,电机的集成。通过将电机控制纳入我们的项目,我们可以创造动态和交互式的体验,从而弥合数字世界和物理世界之间的差距。
第三章:构建物联网天气指示器
在本章中,我们将学习伺服电机和 LED,然后利用这些知识,结合我们对树莓派和网络服务的理解,来创建一个实际项目:一个物联网天气指示器。
该天气指示器将从网络服务中获取天气信息,然后使用连接到伺服电机的指针来根据当前的户外条件指示合适的着装。它将包括一个在下雨时开启并在雷暴时闪烁的 LED。您用于创建此应用程序的配置可能用于其他应用程序,例如水质监测器或交通密度监测器。
我们将从查看伺服电机开始,这将帮助我们了解它们是什么以及我们如何在物联网应用程序中使用它们。然后,我们将专注于 LED。
随后,我们将开始构建一个物理支架来容纳我们应用程序的组件。这将是我们为本书构建的第二个专门设计的树莓派支架,也是第一个带有电机的支架。
虽然在第一章和第二章中讨论的 SenseHAT 外壳的使用是可选的,但建议为天气指示器构建此支架。这个构建将代表我们第一次尝试创建一个有形的、物理的实体,或者更具体地说,是一个物联网(IoT)事物。
一旦我们的设备组装完成,我们就会开始编码。我们的目标是从OpenWeatherMap网络服务中提取信息,并利用它来根据温度和风速来控制我们伺服电机上固定针的位置。我们还将根据某些天气条件调整 LED 的行为。通过实施这些过程,我们将展示如何将现实世界的数据转化为物理运动,架起数字世界和机械世界之间的桥梁:

图 3.1 – 将我们的天气指示器连接到网络服务
在本章中,我们将探讨以下主题:
-
探索伺服电机
-
探索 LED
-
使用 Python 控制伺服电机和 LED
-
构建天气指示器支架
-
为我们的应用程序开发代码
让我们开始吧!
技术要求
完成本章,您需要以下物品:
-
建议使用树莓派 5 4/8 GB 型号,但也可以使用带有 4 GB 或 8 GB 的树莓派 4B(本书中的图示显示的是树莓派 4B 型号)。
-
带有预装 Thonny 的最新树莓派操作系统。
-
键盘、鼠标和显示器。
-
1 个 SG90 伺服电机。
-
1 个 LED(单色)。
-
1 个 220 欧姆电阻。
-
½英寸 PVC 管。
-
用于树莓派通用输入/输出(GPIO)端口的跳线带连接器。
-
用于定制支架的 3D 打印机或 3D 打印服务。
-
对编程的一般了解。本书中将使用 Python 编程语言。
本章的 GitHub 仓库位于 github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter3。
研究伺服电机
伺服电机在机器人、自动化和其他需要精确控制角运动的领域中得到了广泛应用。将伺服电机连接到树莓派是一个简单的过程,为各种项目提供了令人兴奋的可能性。树莓派提供了一个合适的平台来接口和控制伺服电机。在本节中,我们将更详细地研究伺服电机。
探索 GPIO 端口
树莓派的 GPIO 端口允许直接硬件交互,使其成为动手项目的关键工具。它允许我们连接设备、传感器和电路,从而创建现实世界的应用,如机器人或报警系统。通过利用这些引脚,我们可以设计和构建与物理世界交互的项目。我们将在第五章中更深入地介绍 GPIO 端口。目前,只需知道我们可以简单地使用母头跳线连接器将伺服电机和 LED 等设备连接到 GPIO 端口即可。
我们将首先使用 GPIO 端口将 SG90 伺服电机连接到我们的树莓派 5。
将 SG90 伺服电机连接到我们的树莓派
SG90 伺服电机因其紧凑的尺寸、多功能性、精确控制和易于使用而广受欢迎。在将 SG90 伺服电机连接到我们的树莓派时,有必要修改母头三脚插座的线序。这个插座通常被称为JR 风格伺服插座或简单地称为伺服插座,其初始接线顺序与我们的树莓派所需的顺序相反。
参考图 3.2,我们可以通过以下步骤调整接线:
-
首先,识别插座中的三根线 – 红色(电源)、棕色(地)和橙色(信号)。
-
然后,使用尖锐物体(如 Xacto 刀)轻轻拉出插座中的线,同时用手握住塑料外壳。
-
然后,重新排列它们,使红色(电源)线在一端,棕色(地)线在中间,橙色(信号)线在红色线的另一端:

图 3.2 – 改变 SG90 伺服电机的接线顺序
- 在正确的接线顺序下,我们可以直接将伺服电机连接到我们的树莓派。以图 3.3 中的图为例,将母头三脚插座连接到树莓派的 +5V、GND 和 GPIO 14 引脚:

图 3.3 – 将我们的伺服电机连接到 Raspberry Pi
连接我们的伺服电机的另一种方法
对于那些不想修改伺服电机现有连接器的人来说,我们可以使用三根公对母跳线代替,公端插入连接器,母端插入 Raspberry Pi 的 GPIO 端口。
在我们的伺服电机连接到我们的 Raspberry Pi 后,让我们来研究伺服电机是什么以及我们如何使用它们。
理解伺服电机
伺服电机由一个直流(DC)电机、一个控制电路以及一个用于维持输出轴角位置的反馈机制组成。机器人、玩具和遥控汽车是伺服电机主要应用的一些领域。
伺服电机因其闭环反馈机制在控制电机位置方面的卓越精度而脱颖而出。该机制不断监控电机的实际位置,并调整它以匹配所需位置,确保准确可靠的性能。图 3.4展示了流行的 SG90 伺服电机:

图 3.4 – SG90 伺服电机
伺服电机的运动范围可能因型号而异。一些伺服电机设计为 180 度运动,这使得它们非常适合范围受限的应用,如控制机器人关节或稳定相机云台。其他伺服电机能够实现完整的 360 度旋转,这使得它们适用于连续运动应用,如转向机构或俯仰倾斜相机系统。
伺服电机的角度控制是通过脉冲宽度调制(PWM)实现的。PWM 涉及向伺服电机发送不同脉冲宽度,类似于调整音量旋钮来控制音量;在伺服电机中,这些脉冲决定了臂的位置。不同品牌的伺服电机有不同的最大和最小值,用于确定伺服针的角度。图 3.5展示了 PWM 与 180 度伺服电机位置之间的关系:

图 3.5 – PWM 和伺服位置
在掌握了伺服电机的基本原理及其与 Raspberry Pi 5 的连接方法后,我们将把注意力转向 LED。我们的探索将涵盖它们的工作原理以及将它们纳入我们项目的步骤,从而提供额外的视觉反馈层。
探索 LED
LED 最初在 20 世纪 60 年代初被开发出来。最早的 LED 是红色的,最初用于七段显示屏的指示器。如今,LED 几乎无处不在——从我们的电子设备和家用电器上的指示灯到电视和智能手机的屏幕。
LED 是一种简单的半导体器件。它有两个引脚——阳极(正极)和阴极(负极)。当正向电流从阳极通过二极管流向阴极时,它会发光。光的颜色取决于制造二极管所使用的材料,可以从红外到紫外,包括所有可见光谱的颜色。
LED 有多种类型,包括单色 LED、RGB LED,能够产生多种颜色,红外 LED,用于遥控器和夜视系统,以及双色 LED,可以发出两种不同的颜色。图 3.6显示了从单色红色到七色闪光 LED(从左数第二)再到能够显示任何颜色的 RGB LED(从右数第二)的 LED 阵列。对于我们的天气指示器,我们将使用单色 LED。颜色不重要:

图 3.6 – 各种格式的 LED
现在我们对 LED 有了基本的了解,是时候将 LED 连接到我们的树莓派上了。
将 LED 连接到我们的树莓派
将 LED 连接到树莓派需要我们仔细考虑电压要求——电压过高可能会导致 LED 烧毁。为此,我们必须在将 LED 通过跳线连接到 GPIO 端口之前,将其一端焊接上一个电阻。由于树莓派的 GPIO 引脚输出的电压高于大多数 LED 可以直接处理的电压,因此加入电阻对于调节电压至关重要,以确保 LED 正确运行。
图 3.7显示了连接我们的 LED 到树莓派 GPIO 端口的所需材料——一个 LED、两根带有雌性端子的跳线(棕色和红色)、热缩管和一个 220 欧姆电阻:

图 3.7 – 连接我们的 LED 到树莓派所需的部件
要将我们的 LED 连接到我们的树莓派,我们必须执行以下操作:
- 首先,将电阻焊接在 LED 的正极(阳极)或较长的腿上(见图 3.8 中的A):

图 3.8 – 将电阻和跳线焊接到我们的 LED 上
-
然后,将棕色跳线焊接在 LED 的负极(阴极)腿上(见图 3.8 中的B)。
-
然后,将红色跳线焊接在电阻的另一端。我们可以考虑电阻产生的额外长度,并相应地缩短红色电线(见图 3.8 中的B)。
-
为了加强新的连接并提供额外的电气绝缘,在焊接的电线和电阻上应用热缩管(见图 3.8 中的C)。
-
使用跳线,将棕色电线连接到树莓派的 GPIO GND,将红色电线连接到 GPIO 25(见图 3.9):

图 3.9 – 将我们的伺服电机和 LED 连接到 Raspberry Pi
现在我们已经连接了伺服电机和 LED 组件,让我们编写一些代码,以便我们可以通过 Python 控制我们的组件。
使用 Python 控制伺服电机和 LED
成功将伺服电机和 LED 连接到我们的 Raspberry Pi 后,我们将开始编写 Python 控制代码。为了便于操作,我们将使用GPIO Zero Python 库,这是一个用于 Raspberry Pi GPIO 编程的强大工具。在此过程中的第一步将是设置一个 Python 虚拟环境,以便我们可以开发我们的代码。
设置我们的开发环境
就像我们在第二章中所做的那样,我们将为我们的开发使用 Python 虚拟环境。由于有一些库只能与 Python 的根安装一起工作,因此我们将使用系统包在我们的 Python 虚拟环境中。为此,请按照以下步骤操作:
-
在我们的 Raspberry Pi 5 上,打开一个终端应用程序。
-
要存储我们的项目文件,创建一个新的目录,运行以下命令:
mkdir Chapter3 -
然后,导航到新目录:
cd Chapter3 -
为我们的项目创建一个新的 Python 虚拟环境:
ch3-env and enabled access to the system site packages. With our new Python virtual environment created, we can source into it with the following command:source ch3-env/bin/activate
-
使用以下命令安装我们代码所需的额外包:
requests library in Python simplifies making HTTP requests to web servers. We will use the requests library when we pull weather data from the web. With the requests library installed, we may close the Terminal by running the following command:退出
在创建项目文件夹、设置并激活 Python 虚拟环境以及安装requests包之后,我们现在可以开始编写代码。我们将首先通过 Python 代码使用终端控制伺服电机。
使用 GPIO Zero 控制伺服电机
GPIO Zero 是一个用于控制 Raspberry Pi 上 GPIO 引脚的 Python 库。它是由 Raspberry Pi 基金会的 Ben Nuttall 和 Dave Jones 于 2016 年创建的。GPIO Zero 提供了一个用户友好的高级接口,使得与 GPIO 一起工作更加容易,包括控制 LED、按钮、伺服电机等。它随最新的 Raspberry Pi 操作系统预安装。
Servo类是 GPIO Zero 的一部分,提供了一种控制伺服电机的方法。为了测试我们的伺服电机与 Raspberry Pi 的连接,请按照以下步骤操作:
-
打开一个终端窗口,通过运行以下命令导航到我们的项目文件夹:
ch3-env virtual environment with the following command:使用以下命令启动 ch3-env 虚拟环境并运行 Python:
Servo class and create an object called servo. After that, initialize it with the PIN we connected our Servo to in *Figure 3**.3*:from gpiozero import Servo
伺服类提供了几个有用的方法来控制伺服电机。要将伺服设置到最小位置,运行以下命令:
servo.min() -
执行此命令后,我们应该注意到我们的伺服电机已经完全转向一侧。为了将我们的伺服电机移动到中间位置,我们可以使用以下 Python 命令:
servo.mid() -
执行此命令后,我们应该观察到我们的伺服电机已经移动到中间位置。对于最终测试,我们将把伺服电机移动到最大位置:
servo.max() -
我们应该观察到伺服电机移动到最大位置(如果我们发现电机没有移动到最大位置,就像它移动到最小位置一样,请不要担心,我们将在本章后面校准电机)。要关闭我们的伺服连接,请运行以下命令:
servo.close()
为什么伺服电机会抖动?
当我们的 SG90 伺服电机通过 GPIO Zero 控制时,我们可能会观察到抖动。许多因素可能导致这个问题,从通过 GPIO 端口提供给伺服电机的电源,到伺服电机可能存在的机械问题,甚至库内的软件相关的问题。尽管对这些原因的调查超出了本项目范围,但我们必须承认电机抖动的可能性。一个简单的解决方案是在设置位置后,使用servo.close()命令关闭与伺服电机的连接。
在测试我们的伺服电机后,我们可以专注于 LED。在下一节中,我们将编写一些代码来使用 GPIO 库控制我们的 LED 状态。
使用 GPIO Zero 控制 LED
LED类是 GPIO Zero 库的一部分,提供了一个简单的接口来控制 LED。我们可以使用这个类来控制我们的 LED。要测试我们的 LED 连接,请按照以下步骤操作:
-
首先,打开一个新的终端窗口,并使用以下命令导航到我们的项目文件夹:
ch3-env virtual environment with the following command:source ch3-env/bin/activate
-
通过以下命令启动 Python:
LED class and create an object called led. Once you’ve done this, initialize it with the PIN we connected our LED to in *Figure 3**.9*:from gpiozero import LED
LED 类提供了几个有用的方法来控制 LED。要打开 LED,请输入以下命令:
led.on() -
执行此命令后,我们应该注意到我们的 LED 已经打开。对于下一个测试,我们将通过以下 Python 命令关闭 LED:
led.off() -
执行此命令后,我们应该观察到 LED 已经关闭。对于最后的测试,我们将闪烁 LED:
led.blink() -
我们应该观察到 LED 开始闪烁。要停止闪烁并关闭 LED,请运行以下命令:
led.off()
如果在测试过程中遇到问题,可能有几个潜在的原因:
-
接线错误:接线错误是最常见的问题之一。验证我们的连接并确保我们根据 Python 脚本正确接线 GPIO 引脚至关重要。
-
电源问题:电源不足也可能导致问题。虽然 Raspberry Pi 的 GPIO 引脚可能无法为某些伺服电机提供足够的电源,尤其是在负载下,导致伺服电机行为不可预测,但鉴于我们的 SG90 伺服电机和 LED 的功率需求较低,它们不应该遇到这个问题。
-
软件:软件相关的问题也可能导致问题。保持 Raspberry Pi OS 和 GPIO Zero 库的最新状态是一项基本的预防措施。
-
组件:问题可能存在于组件本身。通过使用已知的工作设备测试它们,我们可以确认或排除这种可能性。
现在我们已经测试了我们的伺服和 LED 组件以及相应的代码,我们可以构建一个支架来容纳我们的项目。
构建天气指示支架
在本节中,我们的重点将转向组装天气指示支架。虽然运行代码不是必需的,但构建支架强烈推荐,因为它为我们的项目增添了有形、现实世界的元素,使物联网中的“事物”栩栩如生。
尽管这本书并没有提供关于 3D 打印的全面指南,但我们将简要概述制造支架的关键步骤。我们将使用标准的熔融沉积建模(FDM)打印机,理想情况下是像“Ender-3”这样的打印机(220mm x 220mm x 250mm)。在这个练习中,我们选择聚乳酸(PLA)作为材料,因为它易于使用,非常适合初学者。另外,聚对苯二甲酸乙二醇酯改性(PETG)也是一个很好的选择,它提供了增强的强度和灵活性。在这里,我们将介绍打印和组装支架组件的基本知识。
我应该打印 PLA 还是 PETG?
当决定是否打印 PLA 或 PETG 时,我们需要考虑几个因素。PLA 因其易于使用而闻名,是初学者的热门选择。它以较低的温度打印,不像其他材料那样容易变形,并且通常提供更精确的细节。PETG 以其强度和灵活性而闻名,超过了 PLA。PETG 的打印温度高于 PLA,并且具有出色的层间粘合性,从而产生更坚固的打印件。然而,由于 PETG 有拉丝或滴漏的倾向,打印起来可能更具挑战性。对于本章中我们构建的天气指示支架,我们使用了 PLA。这是因为打印是在玻璃板上直接进行的,而 PETG 容易粘附在玻璃上,可能会在移除打印件时损坏构建板。
对于那些没有 3D 打印机或者不愿意自己打印部件的人来说,使用 Shapeways(www.shapeways.com)等 3D 打印服务是一个方便的替代方案。
现在我们已经讨论了获取 3D 打印部件的选项,无论是自己打印还是使用服务,让我们继续进行项目的下一个关键阶段——组装天气指示支架。
组装天气指示支架
如前所述,天气指示支架已被设计成由 FDM 3D 打印机生产的组件组成。与第一章和第二章中的 SenseHAT 外壳和支架不同,这个天气指示支架的设计方法是采用板状配置。这种方法有效地消除了在 FDM 技术制造的部件中常见的薄水平墙,这是一种结构上的弱点。
寻找 3D 打印的文件
针对特定 3D 打印的部件的 3D 模型文件可以从本章 GitHub 仓库的“构建文件”目录中下载。请注意,这些文件仅提供.stl 格式,不提供其他 3D 模型文件格式。
在我们开始构建我们的支架之前,让我们看看这些部件。
识别部件
图 3.10显示了组成气象指示器支架的部件。所有部件,除了底座外,都是使用 FDM 3D 打印机打印的。另外两个部件——半英寸 PVC 管和前贴纸——未显示:

图 3.10 – 气象指示器支架部件
让我们更仔细地看看每个部件:
-
底座:底座作为支架的基础。这是固定管状底座的组件。虽然底座是设计的一部分,但它可以根据具体的安装情况被认为是可选的。例如,支架可以直接固定在桌子上、墙上,甚至天花板上,根据个人偏好和需求提供灵活的定位。图 3.10 中显示的底座是用 20 毫米松木通过 CNC 路由器(一种用于切割各种硬材料的计算机控制切割机)制作的,尽管也可以使用 3D 打印机来制作这个部件。
-
后板和前板:这些半块粘合在一起形成支架的板。将它们分成两半是为了适应 3D 打印时需要平坦表面的需求,因为板本身没有固有的平坦面。板是我们支架的主要组件,因为它支撑着伺服电机、箭头和连接在背面的 Raspberry Pi。板被设计成可以容纳 Raspberry Pi、Raspberry Pi Zero 或 Raspberry Pi Pico W。
-
板钩:设计成与 GoPro 系列支架兼容,该钩子提供了板上板的主要连接。值得注意的是,板钩的故意打印方向和内置支撑消除了如果以传统平放方式打印(见图 3.11)可能会导致的线粘附问题:

图 3.11 – 板钩打印方向
-
管状底座和板支架:这两个组件固定了 1/2 英寸 PVC 管(未显示)。管状底座将 PVC 管固定到底座上,板支架将 PVC 管固定到板钩上。
-
8mm LED 固定器:我们使用 LED 固定器将 LED 固定在主板面的前面。
-
箭头:箭头连接到伺服电机,作为我们气象指示应用中的模拟指针。
-
对齐工具:这个工具在粘合时作为引导,确保后板和前板正确对齐,避免安装伺服电机时出现任何问题。
-
PVC 管(未显示):支架配备了一英寸半的 PVC 管,作为其茎或中心支撑。这种设计提供了根据我们的需求调整支架高度的灵活性。在不需要底座的情况下,例如将其固定在桌子上、墙上或天花板上,我们可以轻松地将支架的高度调整到所需水平。
-
Silhouette文件和.svg文件,其中包含切割线,位于我们 GitHub 仓库的Build Files文件夹中。 -
M5 20mm 螺栓和 M5 螺母(未显示):为了将板固定在支架的底座部分,我们使用 20mm 的 M5 螺栓和相应的 M5 螺母。
在识别了部件后,让我们开始创建支架的主板。
组装板
气象指示器支架的板或面板是通过将前板和后板粘合在一起制成的。要组装板,请按照以下步骤操作:
- 首先,将环氧胶水涂抹在其中一个板的平面一侧(见图 3.12 中的A)。涂抹胶水时,务必避免在伺服器或 LED 开口附近涂抹。建议将胶水直接涂抹在板的边界处:

图 3.12 – 组装板
-
在涂抹胶水后,将板的平面放在一起。使用对齐工具,确保板相互对齐(见图 3.12 中的B)。
-
一旦板对齐,移除对齐工具。
-
如果需要,在板上使用夹具以防止它们之间形成间隙。在继续之前,让胶水干燥(见图 3.12 中的C)。
-
在板干燥后,将环氧胶水涂抹在钩子的平面部分,并将其放置在板的背面凹槽中(见图 3.12 中的D)。
-
胶水干燥后,如果您愿意,可以对板进行喷漆。
-
要完成组装,打印并裁剪出面贴纸,并将其贴在板前侧的凹槽内(分别见图 3.13 中的A和B):

图 3.13 – 板面贴纸
主板构建完成后,现在是时候构建底座了,它将支撑主板。
组装底座
我们支架的底座部分包括底座、管底座、PVC 管和板支架。要组装底座,请按照以下步骤操作:
- 首先,将切割到所需长度的 PVC 管插入管底座(见图 3.14 中的A):

图 3.14 – 组装底座
-
然后,根据需要将管底座固定在底座、桌子、墙壁或天花板上(见图 3.14 中的B)。
-
要完成底座的组装,将板支架插入 PVC 管的顶部(见图 3.14 中的C)。
在组装底座时请注意,制造商的 PVC 管可能会有变化,这可能导致安装不当或插入底座部件时困难。如果管子太松,建议使用环氧胶将其牢固地固定。相反,如果管子太厚而无法正确安装,我们有两个选择:要么打磨管底或板支架的内侧,要么打磨管的外侧以实现正确且紧密的安装。
重要的是要注意,在我们的例子中,底座的组件被涂上了平黑色喷漆,因此任何由油漆引起的额外厚度都应该考虑在内。
底座组件构建完成后,我们就可以将电子组件集成到我们的天气指示器支架上了。
安装 Raspberry Pi、伺服电机和 LED
在我们的板和底座构建完成后,在组装支架之前,我们必须做的最后一件事是将我们的 Raspberry Pi、伺服电机和 LED 安装到板上,并将所有这些组件连接在一起。
要这样做,请按照以下步骤操作:
-
使用 10mm M2.5 螺栓,将我们的 Raspberry Pi 固定在板的底部,确保它与适当的支架正确对齐(见图 3**.15中的A)。
-
在热胶枪(推荐)或环氧胶的帮助下,将我们的 SG90 伺服电机固定在板的背面。为此,将 SG90 的前结构对准板上的孔(见图 3**.15中的B):

图 3.15 – 安装 Raspberry Pi、伺服电机和 LED
-
参考图 3.3*和图 3.9中的接线图,将伺服电机和 LED 连接到 Raspberry Pi 的 GPIO 端口(见图 3**.15中的C*)。
-
在我们的 Raspberry Pi 布线完成后,是时候将 LED 安装到位了。首先,将 LED 通过板上的 LED 孔(见图 3**.16中的A):

图 3.16 – 将我们的 LED 安装到位
- 然后,将 LED 通过 LED 支架,并将支架推入板子前端的 LED 孔中(见图 3**.16中的B)。
在板完全布线和所有组件牢固固定后,我们可以将板固定到底座上。
连接板到底座
通过将板固定到底座上,我们将完成天气指示器支架的构建。
要完成我们的支架,请按照以下步骤操作:
- 首先,将板背面的钩子与底座的板支架对齐(图 3**.17中的A):

图 3.17 – 将板固定到底座上
-
然后,插入一个 20mm M5 螺栓,并用 M5 螺母固定(见图 3**.17中的A处的红色矩形)。
-
最后,在完全拧紧螺栓之前,对板的倾斜角度进行任何最终调整(见图 3**.17中的B)。
有了这些,我们已经完成了我们的天气指示器支架的组装。这个设置可以用于许多其他物联网应用,包括监控交通状况或跟踪工业环境中的液体水平。
我们下一步是编写代码,从网络服务检索天气数据,并使用它来控制伺服电机和 LED。现在也是将箭头引入我们的设置的最佳时机,因为它在使用代码之前需要校准。
为我们的应用程序开发代码
在本节中,我们将把我们的树莓派连接到OpenWeatherMap.org网络服务,并使用这些数据来控制我们天气指示器上针的位置。为此,我们需要使用我们在OpenWeatherMap.org上设置账户时收到的 API 密钥,如第二章中所述。
在本节中,一旦安装,我们的树莓派将始终连接到显示器、键盘和鼠标。当需要独立设置我们的天气指示器时,我们只需要电源和一种通过 SSH 远程访问树莓派的方法。讨论如何配置树莓派以使其能够在无头模式下运行超出了本章的范围。然而,通过各种在线资源可以轻松找到此设置的详细说明。有用的起点包括官方树莓派文档、专注于技术的论坛如 Stack Overflow,以及专门的树莓派社区网站和博客。
我们将遵循图 3.18中概述的软件架构来编写我们的代码。我们架构的核心是WeatherData类。我们将使用这个类来连接到 OpenWeatherMap 网络服务:

图 3.18 – 我们天气指示器的软件架构
WeatherData类从 OpenWeatherMap API 获取并处理天气数据,而WeatherDashboard类控制显示机制:一个由伺服电机控制的指针指示器和根据天气状况改变状态的 LED。
这两个类协同工作,WeatherData获取并处理数据,然后由WeatherDashboard用于调整显示。为了保持数据最新,update_dashboard()函数被设置为在无限循环中运行,每 30 分钟刷新天气数据并更新显示。
为了解决伺服电机的独特硬件限制——即,如果移动后连接未关闭,则其倾向于抖动——我们将在循环的每个周期中创建一个新的WeatherDashboard实例(以及因此,一个新的Servo实例)。虽然这种方法在软件设计中可能不是最常见的方法,但它是一种实用的解决方案,通过重新打开每个新实例的连接成功地解决了伺服抖动问题。
我们将首先调整箭头(或指针)的位置以实现最佳应用性能,然后再创建我们的WeatherData和WeatherDashboard类。
调整指针
在我们能够使用我们的天气指示器应用程序之前,我们必须调整指针(或箭头的位置)。为此,请按照以下步骤操作:
-
关闭任何打开的终端,打开一个新的终端窗口,并运行以下命令导航到我们的项目文件夹:
ch3-env virtual environment with the following command:source ch3-env/bin/activate
-
使用以下命令启动 Python:
Servo class and create an object called servo:from gpiozero import Servo
servo = Servo(14)
-
接下来,使用以下命令将伺服电机设置为中间位置:
servo.mid() -
使用我们天气指示器面部的图片,将箭头安装到伺服电机的中位位置。
-
在指针(箭头)就位后,让我们进行一些测试。我们将首先使用以下命令将指针移动到最小位置:
WeatherData and WeatherDashboard classes, respectively.
现在,我们已经准备好编写代码来控制天气指示器上的指针和 LED。我们将从WeatherData类开始。
创建 WeatherData 类
WeatherData类旨在使用 OpenWeatherMap API 获取特定城市的天气信息,之后它将根据温度、风速和天气状况处理这些数据来计算伺服电机和 LED 的值。当用城市名称初始化时,该类会获取该城市的天气数据,并存储温度、天气状况和风速。它还提供了getServoValue()和getLEDValue()方法,分别根据检索到的天气数据确定伺服电机和 LED 的输出。
让我们开始:
- 通过点击树莓派任务栏中的菜单图标,导航到编程类别,并选择Thonny(图 3**.19)来启动 Thonny:

图 3.19 – 从主菜单打开 Thonny
-
默认情况下,Thonny 使用树莓派内置的 Python 版本。对于我们的项目,我们将使用我们刚刚创建的 Python 虚拟环境。首先,我们需要通过点击查看并选择文件来查看项目文件(如果尚未选择)。
-
在
ch3-env目录下。 -
然后,右键单击文件夹并选择激活 虚拟环境。
-
一旦进入 Thonny,通过选择文件然后新建或按Ctrl + N键在键盘上创建一个新的标签页。
-
让我们从输入我们的导入开始:
import requests这里,
import requests从 Python 导入requests库,该库用于发送 HTTP 请求,如 GET 和 POST 请求,以与 API 或 Web 服务交互(有关 HTTP 请求的说明,请参阅第二章)。 -
然后,定义我们的类名
WeatherData和类变量:class WeatherData: temperature = 0 weather_conditions = '' wind_speed = 0 city = '' -
我们的构造函数接受一个参数,
city。在构造函数中调用 OpenWeatherMap Web 服务(当创建WeatherData类的实例时):def __init__(self, city): self.city = city api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' base_url = "http://api.openweathermap.org/data/2.5/weather" complete_url = f"{base_url}?q={self.city}&appid={api_key}&units=metric" response = requests.get(complete_url) data = response.json() if data["cod"] != "404": main = data["main"] wind = data["wind"] weather = data["weather"] self.temperature = main["temp"] self.weather_conditions = weather[0]["main"] self.wind_speed = wind["speed"]让我们更仔细地看看我们的代码:
-
self.city = city:将城市名称保存到对象的属性中 -
api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx':设置 OpenWeatherMap API 密钥 -
base_url = "http://api.openweathermap.org/data/2.5/weather":设置 OpenWeatherMap API 的基础 URL -
complete_url = f"{base_url}?q={self.city}&appid={api_key}&units=metric":使用城市和 API 密钥格式化完整的 API URL -
response = requests.get(complete_url):向 API 发送 GET 请求并保存响应 -
data = response.json():将 API 响应从 JSON 格式转换为 Python 字典 -
if data["cod"] != "404":检查 API 响应是否不是 404 错误,这表明找不到城市 -
self.temperature = main["temp"]:将城市当前温度保存到对象的属性中 -
self.weather_conditions = weather[0]["main"]:将城市当前天气状况保存到对象的属性中 -
self.wind_speed = wind["speed"]:将城市当前风速保存到对象的属性中
-
-
通过调用 OpenWeatherMap 网络服务并设置实例变量,我们代码剩下的工作就是将天气数据转换为伺服电机和 LED 的值。我们将从伺服电机值和
getServoValue()方法开始:def getServoValue(self): if self.temperature < 0: temp_factor = 0 elif self.temperature > 30: temp_factor = 1 else: temp_factor = self.temperature / 30 wind_factor = self.wind_speed / 20 servo_value = -((temp_factor - (wind_factor/20)) * 2-1) return servo_value下面是我们的代码的分解:
-
首先,该方法检查温度(
self.temperature)是否小于0。如果温度低于零,则temp_factor设置为0。这意味着如果温度是负数,则不会对最终伺服值有任何贡献。 -
然后,我们检查温度是否大于
30。如果是这样,则temp_factor设置为1。 -
如果温度在 0 到 30 摄氏度之间,则我们会对温度数据进行标准化。标准化是将值转换为公共范围的过程。在这种情况下,温度数据被缩放到 0 到 1 的范围,假设温度在 0 到 30 摄氏度之间。通过将温度除以 30,任何在这个范围内的温度值都会按比例缩小到 0 到 1 之间的分数。例如,如果温度是 15 摄氏度,
temp_factor将是 0.5。这种标准化使得方法能够一致地处理温度,而不管它们的实际值是多少。 -
wind_factor = self.wind_speed / 20通过标准化风速来计算风力系数。就像温度标准化过程一样,这一行代码用于将风速转换为 0 到 1 之间的值。假设风速在 0 到 20 公里每小时之间。例如,如果风速是每小时 10 公里,wind_factor将是 0.5。在结合或比较不同类型的数据时,如本例中的风速和温度,标准化特别有用。 -
下一步是使用温度和风速因素来计算
servo_value。具体来说,它由于风速而产生了对温度的阻尼效应,表示为风寒因素。该公式通过将风速因素(wind_factor)的二十分之一从温度因素(temp_factor)中减去,从而表示每当风速增加 5%,温度的影响就会减少 1%。这是基于我们主观的假设,即风速具有冷却效果。在此对风寒进行调整后,得到的结果被转换以适应 -1 到 1 的范围,这对于伺服电机的操作是合适的。它将值乘以 2,将范围从 0 到 1 移动到 0 到 2,然后减去 1 以调整范围到 -1 到 1。最后,结果取反以考虑伺服电机的方向性(正如我们在校准指针时发现的那样)。计算出的servo_value代表根据调整后的温度和风速读数确定的伺服电机位置。 -
最后,我们的方法返回
servo_value,这是一个基于温度的值,然后根据风速进行调整。此值已准备好用作 GPIO Zero 库中伺服电机值属性的输入。
-
-
WeatherData类中的第二种方法用于确定 LED 的操作状态,该状态根据当前的天气条件而变化。更具体地说,LED 被编程在雷暴期间闪烁,在下雨时保持稳定照明,而在既不是雷暴也不是下雨的天气条件下关闭。我们称此方法为getLEDValue():def getLEDValue(self): if (self.weather_conditions=='Thunderstorm'): return 2; elif(self.weather_conditions=='Rain'): return 1 else: return 0在这里,
getLEDValue()方法为不同的天气条件指定特定的值:对于雷暴返回2,对于雨返回1,对于其他条件返回0。然后利用这些值来控制 LED 的行为——雷暴时闪烁(2),雨时稳定照明(1),晴朗时关闭(0)。 -
要直接测试我们的程序,请包含以下代码:
if __name__=="__main__": weather = WeatherData('Toronto') print(weather.getServoValue()) print(weather.getLEDValue())让我们更仔细地看看:
-
if __name__=="__main__":这是程序的入口点。它仅在脚本直接运行时执行,而不是当它作为另一个脚本中的模块导入时:-
weather = WeatherData('Toronto'):这创建了一个WeatherData类的对象,并用'Toronto'字符串初始化它。我们可以输入我们选择的任何城市。为了验证我们感兴趣的城市的天气数据是否可用,我们可以将其输入到 OpenWeatherMap 网站的搜索框中(openweathermap.org/)。 -
print(weather.getServoValue()):这是对weather对象的getServoValue()方法的函数调用。此方法根据多伦多的温度和风速数据计算一个值,然后将此值打印到控制台。 -
print(weather.getLEDValue()):这调用天气对象的getLEDValue()方法,根据多伦多的天气条件(雷暴、雨或其他)设置一个标志。这个标志决定了 LED 的状态(闪烁、开启或关闭或 2、1 或 0,分别)并且该方法返回这个状态。然后这个状态被打印到控制台。
-
-
-
保存代码并将文件命名为
WeatherData.py。通过点击绿色运行按钮,按键盘上的F5,或者在顶部菜单中选择运行选项然后选择运行当前脚本(图 3.20)来运行代码:

图 3.20 – 在 Thonny 中运行脚本
- 你应该会看到以下类似的响应:

图 3.21 – 运行 WeatherData.py 的输出
在编写并测试了WeatherData类之后,是时候继续编写控制天气指示器的代码了:WeatherDashboard类。
创建 WeatherDashboard 类
WeatherDashboard类对于天气指示器的运行至关重要,因为它负责控制指针的位置和 LED 的状态。这个类利用WeatherData类收集和解释天气数据,然后将这些数据用于指导物理显示的操作。伺服电机的位置,决定了指针的位置,由温度和风速决定,而 LED 的状态则表示特定的天气条件,如雨或雷暴。
要创建这个类,请按照以下步骤操作:
-
在 Thonny 中,如果尚未激活,激活
ch3-env虚拟环境。 -
然后,通过选择文件然后新建或在键盘上按Ctrl + N来创建一个新的标签页(图 3.22):

图 3.22 – 在 Thonny 中创建新文件
-
我们将从导入开始:
from gpiozero import Servo from gpiozero import LED from time import sleep from WeatherData import WeatherData在这里,我们做以下操作:
-
我们首先从
gpiozero库中导入Servo和LED类。 -
然后,我们从
time模块中导入sleep函数。 -
最后,我们导入
WeatherData类以获取天气条件数据,并根据这些数据推导出伺服电机和 LED 的控制值。
-
-
添加导入后,定义类及其变量:
class WeatherDashboard: servoCorrection = 0.5 maxPW = (2.0 + servoCorrection) / 1000 minPW = (1.0 - servoCorrection) / 1000在这里,我们有以下内容:
-
servoCorrection = 0.5:这一行声明了一个名为servoCorrection的类级别变量,并将其赋值为0.5。这用于调整伺服电机的最小和最大脉冲宽度。 -
maxPW = (2.0 + servoCorrection) / 1000:这一行计算并设置可以提供给伺服电机的最大脉冲宽度(maxPW)。它是通过将2.0加到servoCorrection变量上,然后将结果除以1000来转换为秒(伺服电机期望脉冲宽度以秒为单位)。 -
minPW = (1.0 - servoCorrection) / 1000:与上一行类似,此行计算并设置伺服电机的最小脉冲宽度(minPW)。它从1.0中减去servoCorrection,然后将结果除以1000,原因与之前相同。
-
-
在初始化
WeatherDashboard类时创建我们的Servo和LED实例对象:def __init__(self, city, servo_pin, led_pin): self.city = city self.servo = Servo(servo_pin, min_pulse_width=self.minPW, max_pulse_width=self.maxPW) self.led = LED(led_pin)让我们更仔细地看看代码:
-
self.city = city:此行设置实例变量city等于在创建类实例时传递的city参数。此变量表示将获取天气数据的城市。 -
self.servo = Servo(servo_pin, min_pulse_width=self.minPW, max_pulse_width=self.maxPW):此行从 GPIO Zero 库创建一个Servo对象。servo_pin参数指定伺服电机的连接的 GPIO 引脚。min_pulse_width=self.minPW和max_pulse_width=self.maxPW使用类变量设置伺服电机的最小和最大脉冲宽度。 -
self.led = LED(led_pin):此行从 GPIO Zero 库创建一个LED对象。led_pin参数指示 LED 连接的 GPIO 引脚。此 LED 将根据为指定城市获取的天气条件进行控制。
-
-
WeatherDashboard类的update_status()方法获取最新的天气信息,并相应地更新指针和 LED 的状态:def update_status(self): weather_data = WeatherData(self.city) self.servo.value = weather_data.getServoValue() led_status = weather_data.getLEDValue() if led_status == 0: self.led.off() elif led_status == 1: self.led.on() else: self.led.blink()这里是我们代码中的情况:
-
weather_data = WeatherData(self.city):这使用在创建WeatherDashboard实例时指定的城市创建WeatherData类的实例。此对象用于获取该城市的天气数据。 -
self.servo.value = weather_data.getServoValue():这调用weather_data对象上的getServoValue()方法,根据天气条件获取伺服电机的值。然后,将此值设置为伺服电机的新的位置。 -
led_status = weather_data.getLEDValue():这调用weather_data对象上的getLEDValue()方法,根据天气条件获取 LED 状态的值。 -
条件块根据
led_status值设置 LED 的状态。如果led_status为0,表示没有降雨或雷暴的平静天气条件,因此 LED 被关闭(self.led.off())。如果led_status为1,这表示降雨,导致 LED 被打开(self.led.on())。对于led_status的值为2的情况,这专门针对雷暴条件,LED 将闪烁(self.led.blink()),表示雷和闪电的间歇性。当WeatherData类检测到雷暴条件时,此值由getLEDValue()方法返回。
-
-
WeatherDashboard类中的最后一个方法closeServo(),简单地关闭我们在类中创建的Servo实例。这样做是为了防止伺服电机抖动:def closeServo(self): self.servo.close() -
最后,保存代码并将其命名为
WeatherDashboard.py。
我们暂时不会运行我们的代码,因为我们需要向文件中添加一个新的方法(但不是类)。我们将使用此方法来创建一个新的 WeatherDashboard 对象,并更新指针和 LED 的状态。
添加 updateDashboard() 函数和主方法
update_dashboard() 函数是一个独立的函数,它定义在同一个 Python 文件 (WeatherDashboard.py) 中,作为 WeatherDashboard 类的外部。它作为接口,用于封装更新天气仪表板状态的过程。创建 update_dashboard() 函数的原因是为了帮助解决与伺服电机抖动相关的问题,因为它提供了一种受控的方式,频繁地创建 WeatherDashboard 类的新实例,从而每次更新仪表板时都会重新打开 Servo 对象的连接。
让我们更仔细地看看:
-
在 Thonny 中打开
WeatherDashboard.py文件,并在底部添加以下方法:def update_dashboard(city, servo_pin, led_pin): weather_dashboard = WeatherDashboard(city, servo_pin, led_pin) weather_dashboard.update_status() sleep(2) weather_dashboard.closeServo()让我们更仔细地看看:
-
首先,我们定义
update_dashboard(city, servo_pin, led_pin)函数,该函数接受三个参数:需要天气数据的城市、伺服电机的引脚和 LED 的引脚。 -
在此函数内部,使用给定的城市、伺服引脚和 LED 引脚作为输入,创建了一个
WeatherDashboard类的实例,命名为weather_dashboard。 -
接下来,调用
WeatherDashboard实例的update_status()方法。此方法获取指定城市的当前天气数据,根据此数据确定伺服电机和 LED 的值,设置伺服电机的位置,并相应地设置 LED 的状态(关闭、开启或闪烁)。 -
程序随后使用
sleep(2)语句暂停 2 秒。这次暂停确保伺服电机有足够的时间移动到新位置,并且 LED 显示其新状态,然后再进行任何其他操作。 -
最后,调用
WeatherDashboard实例的closeServo()方法。此方法负责关闭与伺服电机的连接,这是为了避免伺服电机可能出现的抖动等问题。
-
-
接下来,我们将添加仅在以 Python 执行
WeatherDashboard.py文件时运行的代码。我们将使用此代码来持续运行我们的天气指示器:if __name__ == "__main__": city = 'Toronto' servo_pin = 14 led_pin = 25 while True: update_dashboard(city, servo_pin, led_pin) sleep(1800) # sleep for 30 minutes让我们更仔细地看看:
-
这段代码只有在根据
if __name__ == "__main__":直接执行此脚本 (WeatherDashboard.py) 时才会运行。 -
首先,我们将
city、servo_pin和led_pin变量分别设置为Toronto、14和25。每个读者都可以选择他们喜欢的城市。 -
while True:循环将不断执行其内部的代码,本质上使脚本无限期地运行,或者直到手动停止。 -
在循环内部,使用
city、servo_pin和led_pin值作为参数调用update_dashboard()函数。此函数更新天气仪表盘,有效地获取新的天气数据并相应地控制伺服和 LED。 -
更新仪表盘后,脚本进入休眠模式,持续 1,800 秒,即 30 分钟(
sleep(1800))。这意味着天气仪表盘每 30 分钟更新一次。 -
休眠期过后,循环重新开始,再次更新仪表盘。
-
-
重新保存代码,并将其命名为
WeatherData.py。 -
通过点击绿色运行按钮、按键盘上的F5键或在顶部菜单中选择运行然后运行当前脚本来运行我们的代码。
-
一旦脚本启动并运行,我们会看到针的位置和 LED 的状态准确地反映了当前的温度、风速和天气状况。
我们可以独立操作我们的天气指示器应用程序,无需连接键盘、鼠标或显示器。图 3.23显示了一个实际例子,说明我们的应用程序如何根据拍照时的多伦多天气来建议我们穿什么,同时也表明不需要带伞:

图 3.23 – 今天多伦多的天气看起来像是 T 恤天气
从我们的天气指示器在图 3.23上的针的位置,我们可以得出结论,今天多伦多的天气适合穿 T 恤。
摘要
在本章中,我们开发了一个天气指示器应用程序,这是一个旨在根据当前天气条件提供服装选择建议的工具。这个创新项目结合了编码专长和物理组件设计,不仅体现在计算机屏幕上,而且以真实世界、有形的形式呈现。其中最重要的是我们为天气指示器创建了一个定制的支架,该支架由 3D 打印部件制成。这个支架,它被设计用来容纳 LED 和伺服电机,为我们的项目提供了实用功能和美学吸引力,成功地弥合了数字世界和物理世界之间的差距。
在这次旅程中学到的经验和获得的技能具有广泛的应用。为了说明这一点,设想一个新的项目:一个利用环境传感器来跟踪室内气候条件,如温度、湿度和空气质量的项目。我们掌握的核心原则——从获取和解释数据到与硬件组件交互——可以直接应用于此类场景。
此外,很容易想象为我们的天气指示器添加更多功能。例如,想象用能够显示多种颜色的 RGB LED 替换我们的 LED。我们可以使用这些不同的颜色和不同的闪烁模式来指示其他类型的天气状况或警告。
在本章中,我们通过专注于与网络服务接口进行数据获取和分析,增加了我们在 Python 编程方面的专业知识。同时,我们进入了硬件接口的世界,创建了一个有形的物理组件:我们的定制天气指示器支架。
通过我们新获得的能力,我们准备应对各种复杂问题。正如我们在天气指示器项目中展示的那样,能够整合网络服务、Python 编程和物理组件可以产生对日常生活产生重大影响的应用。无论是根据天气条件提供建议选择服装,还是设想新的应用,如室内气候监测,我们的能力已被证明是多才多艺且极具价值的。
在下一章中,我们将使用 Raspberry Pi 7 英寸触摸屏构建一个物联网显示屏,展示实时天气和交通信息。这涉及到探索各种屏幕类型并创建一个多功能仪表盘,从而提升我们在 Raspberry Pi 应用程序和物联网项目开发方面的技能。
第四章:构建物联网信息显示屏
在本章中,我们将使用带有树莓派品牌 7 英寸触摸屏的设备构建一个物联网信息显示屏。我们将使用这个信息显示屏来显示实时天气信息和当地交通信息。
我们将从探索与我们的树莓派兼容的屏幕开始本章。我们将查看小型有机发光二极管(OLED)屏幕、点阵显示器、七段显示器和树莓派的小型 LCD 显示器。
在我们的项目中,我们将使用树莓派的 7 英寸触摸屏构建一个物联网信息显示屏。这个仪表盘不仅会显示天气详情,还会展示描绘当地交通状况的地图。
本章中获得的知识将为我们提供一套多功能工具箱,促进未来树莓派和物联网项目中的创造力和创新。
我们将涵盖以下主题:
-
调研与我们的树莓派兼容的显示屏,并探索屏幕类型
-
创建一个物联网信息显示屏
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
具备中级 Python 编程知识
-
一台较新的树莓派,最好是带有至少 4GB RAM 的树莓派 5
-
带有兼容壳体的树莓派品牌 7 英寸触摸屏(可选)
本章的代码可以在以下位置找到:
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter4
调研与我们的树莓派兼容的显示屏,并探索屏幕类型
树莓派提供了与各种外部屏幕接口的灵活性,满足不同的应用和需求。在图 4.1 中,我们看到我们可以连接到树莓派的小型显示屏示例:

图 4.1 – 可与树莓派一起使用的小型显示屏
图 4.1展示了针对不同用途定制的小型显示屏:用于精细细节的 OLED 屏幕(B、C、D);用于简洁文本的 16 x 2 LCD(F);以及用于清晰数字和字符的点阵(A)和段式显示(E)。这些选项提供了一系列灵活的视觉表示。
树莓派有几种更大的屏幕选项,包括标准的 4K 计算机显示器,以及我们图 4.2-2 中看到的小型显示器:

图 4.2 – 封装的树莓派 7 英寸触摸屏(A)和 3.5 英寸 LCD 屏幕(B)
图 4.2-1中的屏幕是树莓派品牌的 7 英寸触摸屏。它在这里展示在一个专门为其设计的壳体中。在图 4.2-2中,我们看到一个 3.5 英寸的 LCD 屏幕,它连接到树莓派的 GPIO 端口。
让我们更仔细地看看每种类型的屏幕,以了解它们的独特特性和应用。
在图 4.1和图 4.2中展示的每个屏幕都为特定应用提供了独特的优势,理解这些优势可以增强我们的树莓派项目。以下是对每个屏幕/显示器的描述:
-
小型 OLED 屏幕:OLED 屏幕(见图 4.1中的B、C和D)以其清晰的细节和能效著称。小型 OLED 屏幕(0.到 1.3 英寸)常用于需要最小空间和低功耗的项目。
OLED 屏幕用于显示系统状态或在小型设备如健身追踪器中。它们非常适合显示有限的信息,如图标、温度或时间,其紧凑的尺寸和能效使它们成为在无需过多功耗或空间的情况下进行清晰、简洁显示的理想选择。
-
16 x 2 液晶显示屏:16 x 2 液晶显示屏(见图 4.1中的F)由 16 列和 2 行的字符组成。它们提供了一个简单的界面来显示基于文本的信息,非常适合需要显示简洁文本数据的应用,例如在工业环境中,它们可能用于显示错误信息或生产计数等关键信息。此外,这些屏幕也常用于 3D 打印机,用于向用户显示控制信息。
-
8 x 8 点阵显示器:8 x 8 点阵显示器(见图 4.1中的A),由 64 个独立 LED 组成,排列成 8x8 的网格。通过独立控制每个 LED,这个网格可以创建简单的图像、字符或动画。这种显示器的较大版本常用于公共信息板上的滚动文字。值得注意的是,这种类型的显示器出现在我们第一章和第二章中使用的树莓派 Sense HAT 设备上。
-
七段显示器:七段显示器(见图 4.1中的E)由 7 个或有时 8 个单独的段(包括小数点)组成。通过控制哪些段被点亮,这种显示器可以形成数字和某些字母的形状。这种显示器最早在 20 世纪初发明,最初用于计算器、数字时钟等设备,随着亮度、能效等功能的提升,其应用范围也得以扩大,使得它们在现代数字时钟和工业计数器中仍然具有相关性。
-
树莓派 7 英寸触摸显示屏(见图 4.2中的A)是树莓派项目的多功能附件。这款触摸屏通过DSI(即数字串行接口)端口无缝连接,无需额外的驱动程序即可实现触摸功能,分辨率为 800 x 480 像素。
-
3.5 英寸液晶显示屏(见图 4.2中的 B)通过 GPIO 端口连接到树莓派。分辨率为 320 x 480 像素,这款紧凑型显示屏为手持设备和物联网项目提供了解决方案。这些屏幕也提供 5 英寸尺寸,通常连接到 HDMI 和 GPIO 端口。
现在我们已经了解了可以与树莓派一起使用的各种屏幕,是时候专注于本章的项目了:使用 7 英寸树莓派触摸屏(见图 4.2中的 A)创建物联网信息显示。
创建物联网信息显示
如本章开头所述,我们的物联网信息显示将显示实时天气预报和交通地图。
我们将使用安装在兼容外壳中的带有鼠标和键盘的树莓派品牌 7 英寸触摸屏来开发我们的物联网信息显示:

图 4.3 – 使用 7 英寸触摸屏的树莓派开发环境
树莓派品牌 7 英寸屏幕的关键优势是它与树莓派 5 上的MIPI(即移动行业处理器接口)端口相连,而不是 GPIO 或 HDMI 端口。这确保了无缝集成,消除了下载和安装用于触摸功能的额外驱动程序的需求。我们不会在这个项目中使用触摸功能。
使用我们的标准显示器
虽然这款带有树莓派品牌的 7 英寸显示器提供了令人印象深刻的特性,但它对于创建我们的物联网信息显示并非必需。我们可以利用已经连接到我们的树莓派的普通显示器。然而,使用普通显示器可能会导致显示周围出现大边框,因为我们的代码中组件的位置是硬编码的。
图 4.4概述了我们创建物联网信息显示时将遵循的软件架构:

图 4.4 – 物联网信息显示的软件架构
我们将使用WeatherData类从OpenWeatherMap API 中提取天气信息。
Dashboard类集中了应用程序的显示信息,我们将利用Kivy库来创建 GUI。通过全屏模式显示我们的 GUI,我们创建了一个类似信息亭的效果。
正如我们使用WeatherData类调用OpenWeatherMap API 一样,我们使用TrafficMap类调用 MapQuest Traffic API 以获取交通数据。与WeatherData类不同,TrafficMap类使用 GPS 坐标生成表示交通状况的图像文件。这种交通的视觉表示不仅增加了所提供信息的价值,而且也成为了我们物联网信息显示的焦点。
我们将首先设置我们的开发环境来开始编码。
设置我们的开发环境
我们将为我们的开发使用 Python 虚拟环境。由于有些库只与 Python 的根安装兼容,我们将在 Python 虚拟环境中使用系统包。为此,我们需要执行以下操作:
-
在我们的 Raspberry Pi 5 上,我们打开一个终端应用程序。
-
为了存储我们的项目文件,我们使用以下命令创建一个新的目录:
mkdir chapter4 -
我们随后使用以下命令导航到新目录:
cd chapter4 -
我们需要一个子目录来存放我们的项目。我们使用以下命令创建此文件夹:
mkdir IoTInformationDisplay -
我们使用以下命令为我们的项目创建一个新的 Python 虚拟环境:
ch4-env and enable access to the system site packages. This allows the virtual environment to inherit packages from the global Python environment, which can be useful when certain libraries are installed system wide. Once the environment is set up, we can activate it and begin installing project-specific packages without affecting the global Python environment. -
在我们创建新的 Python 虚拟环境后,我们使用以下命令将其源代码导入:
ch4-env Python virtual environment:

图 4.5 – 使用 dashboard-env 环境的终端
-
我们使用以下命令安装代码所需的 Python 包:
requests is a Python library simplifying HTTP requests, ideal for web service interactions. Kivy enables the development of multitouch, cross-platform applications with a focus on rich user interfaces. With the Python packages installed, we may close the Terminal with the following command:退出
-
我们现在准备好加载 Thonny。我们通过点击树莓派任务栏中的菜单图标,导航到编程类别,并选择Thonny来完成。
-
默认情况下,Thonny 使用树莓派内置的 Python 版本。对于我们的项目,我们将使用我们刚刚创建的 Python 虚拟环境。首先,我们需要通过点击查看并选择文件(如果尚未选择)来查看项目文件。
-
在“文件”部分,我们定位到
ch4-env目录。 -
然后,我们右键单击文件夹并选择激活虚拟环境选项:

图 4.6 – 在 Thonny 中激活 Python 虚拟环境
在我们的项目文件夹创建、Python 虚拟环境设置并激活,以及安装了项目所需的包之后,我们现在可以开始编写代码。
创建一个 WeatherData 类
我们使用一个WeatherData类来封装对OpenWeatherMap API 的调用。除了温度和天气状况,我们物联网信息显示的天气部分还显示天气状况图标以及着装图像。在*图 4**.7 中,我们看到用于着装的图像:

图 4.7 – 我们物联网信息显示中使用的着装图像
着装图像应该对我们很熟悉,因为我们创建天气指示器时使用了相同的图像。第三章。在我们的物联网信息显示中,这些图形将根据温度和风速的计算结果确定的因子来展示。
在本节中,我们将介绍WeatherData类的代码。
那么,让我们开始吧:
-
在 Thonny 中,我们通过选择文件然后新建或按键盘上的Ctrl + N来创建一个新的标签页。
-
在我们的代码中,我们首先输入我们的导入语句:
import requests import json -
我们随后定义我们的类名,
WeatherData,以及我们的类变量:class WeatherData: temperature = 0 weather_conditions = '' wind_speed = 0 city = '' -
从这里,我们定义我们的初始化方法:
def __init__(self, city): self.city = city api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' base_url = "http://api.openweathermap.org/data/2.5/weather" complete_url = f"{base_url}?q={self.city}&appid={api_key}&units=metric" response = requests.get(complete_url) data = response.json() if data["cod"] != "404": main = data["main"] wind = data["wind"] weather = data["weather"] self.temperature = main["temp"] self.weather_conditions = weather[0][" main"] self.wind_speed = wind["speed"] self.icon = weather[0]["icon"] -
我们然后定义
get_temperature()和get_conditions()方法:def get_conditions(self): return self.weather_conditions def get_temperature(self): return str(int(self.temperature)) -
为了适应添加天气状况图标,我们添加了一个名为
get_weather_conditions_icon()的方法:def get_weather_conditions_icon(self): return f"http://openweathermap.org/img/wn/{self.icon}.png"此方法使用 f-string 构建并返回一个 URL,这是 Python 中的一种特性,允许在字符串字面量中嵌入表达式。通过将
self.icon的值附加到 OpenWeatherMap 的基本 URL(f"http://openweathermap.org/img/wn/{self.icon}.png"),它形成了一个完整的 URL,该 URL 领向一个代表当前天气状况(如晴朗、多云或雨天)的 PNG 图像。这将使我们能够将代表当前天气状况的图标嵌入到我们的物联网信息显示中。 -
为了确定要显示的服装图像,我们需要两个额外的函数。第一个函数使用风速和温度返回一个适当称为
wind_temp_factor的因子:def get_wind_temp_factor(self): if self.temperature < 0: temp_factor = 0 elif self.temperature > 30: temp_factor = 30 else: temp_factor = self.temperature wind_factor = self.wind_speed / 20 wind_temp_factor = temp_factor - wind_factor return wind_temp_factor此方法首先将
self.temperature值限制在0到30之间(将其分配给temp_factor),然后除以风速20(将其分配给wind_factor),最后从temp_factor中减去wind_factor,将结果值作为wind_temp_factor返回。这些值都是任意的,可能需要更改。 -
我们
WeatherData类中的最后一个方法根据wind_temp_factor返回服装图像的路径:def get_attire_image(self): factor = self.get_wind_temp_factor() if factor < 8: return "images/gloves.png" elif factor < 18: return "images/long-shirt.png" elif factor < 25: return "images/short-shirt.png" else: return "images/shorts.png" -
我们代码的最后一部分位于
WeatherMap类外部,使我们能够测试该类:if __name__=="__main__": weather = WeatherData('Toronto') print(weather.get_temperature()) print(weather.get_attire_image()) print(weather.get_conditions()) print(weather.get_weather_conditions_icon()) WeatherData class for Toronto and then prints various weather-related information to our console. -
我们将代码保存为
WeatherData.py,位于IoTInformationDisplay项目子文件夹中。
我们现在准备构建一个 TrafficMap 类,该类将封装创建本地交通地图的代码。
创建 TrafficMap 类
我们使用 TrafficMap 类与 MapQuest API 进行接口,使我们的应用程序能够生成交通地图。为了连接到 MapQuest API,我们首先需要创建一个账户并生成一个 API 密钥。
为我们的应用程序生成 API 密钥
MapQuest 开发者提供各种工具和服务,使开发者能够访问地图、路线信息等。对于我们项目,我们需要从 MapQuest 获取一个 API 密钥以访问他们的网络服务,特别是交通地图数据。以下是设置免费账户和获取 API 密钥的步骤:
-
我们首先导航到 MapQuest 开发者网站 (
developer.mapquest.com/plans) 以访问开发者门户。 -
对于我们的应用程序,MapQuestGo 计划就足够了。此计划将为我们提供 15,000 个起始交易。要创建一个计划,我们点击 订阅 按钮,并按照概述的步骤进行。
-
一旦我们的个人资料创建完成,我们可以通过访问以下 URL 生成一个新的 API 密钥:
developer.mapquest.com/user/me/apps。 -
我们点击 创建新密钥 按钮,并输入应用程序名称:

图 4.8 – 在 MapQuest 开发者中创建新的 API 密钥
- 将生成一个新的 API 密钥,可以在 管理 密钥 部分查看:

图 4.9 – 在 MaqQuest 开发者中查看 API 密钥
我们将使用此密钥调用 MapQuest 交通 API。将 API 密钥复制并粘贴到可以稍后访问的文本文档中是个好主意。生成 API 密钥后,我们现在可以创建我们的 TrafficMap 类。
编写 TrafficMap 类
我们使用 Thonny 编写 TrafficMap 类:
-
我们通过点击树莓派任务栏中的 菜单 图标来启动 Thonny。然后,我们导航到 编程 类别并选择 Thonny。
-
一旦进入 Thonny,我们激活
ch4-env虚拟环境。 -
我们通过选择 文件 然后选择 新建 或按键盘上的 Ctrl + N 创建一个新的标签页。
-
在我们的代码中,我们首先输入我们的导入:
import requests -
对于我们的
TrafficMap类,我们只需要导入requests包。我们定义我们的类名TrafficMap和我们的初始化方法:class TrafficMap: def __init__(self, latitude, longitude, zoom): self.latitude = latitude self.longitude = longitude self.zoom = zoom self.size = "500,325" self.api_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"让我们看看我们刚刚添加的内容:
-
self.latitude: 指定地图上位置的纬度,用于将地图中心定位在该纬度。 -
self.longitude: 指定地图上位置的经度,用于将地图中心定位在该经度。 -
self.zoom: 设置地图的缩放级别,控制地图上可见的区域大小(例如,较高的值可能显示更近、更详细的视图)。 -
self.size: 设置地图的固定大小,定义像素宽度和高度。大小设置为"500,325"的字符串。 -
self.api_key: 存储硬编码的 API 密钥,这是对 API 进行身份验证所必需的。
-
-
我们
TrafficMap类的核心是get_traffic_map()方法。我们使用此方法调用 MapQuest 交通网络服务,并使用响应创建我们的交通地图:def get_traffic_map(self): base_url = "http://www.mapquestapi.com\ /staticmap/v5/map" params = { 'key': self.api_key, 'center': f"{self.latitude}, {self.longitude}", 'zoom': self.zoom, 'size': self.size, 'traffic': 'flow|cons|inc' } response = requests.get(base_url, params=params) if response.status_code == 200: with open('images/traffic_map.png', 'wb') as f: f.write(response.content) return "images/traffic_map.png" else: return "images/error.png"让我们看看我们刚刚添加的内容:
-
base_url: MapQuest 静态地图 API 的 URL 端点。 -
params: 我们使用这个包含 API 请求必要参数的字典:-
self.api_key: 指定与 MapQuest 静态地图 API 进行身份验证的 API 密钥。 -
center–f"{self.latitude},{self.longitude}": 使用对象的latitude和longitude属性定义地图的中心。 -
self.zoom: 指定地图的缩放级别,控制地图上可见的缩放或细节。 -
self.size: 使用对象先前定义的大小属性设置地图的大小,可能定义像素宽度和高度。 -
traffic: 'flow|cons|inc': 指定要在地图上包含的交通信息,表示不同类型的交通数据,如流量、拥堵和事件。
-
-
response = requests.get(base_url, params=params): 向存储在base_url中的 URL 发送一个带有在params中定义的参数的GET请求,并将响应存储在response变量中。 -
如果请求成功(
response.status_code == 200),则发生以下情况:-
在
images目录中创建或覆盖了一个图像文件(traffic_map.png)。 -
响应的内容(即图像数据)被写入到文件中。
-
该方法返回保存图像的路径:
"images/traffic_map.png"
-
-
如果请求不成功,该方法返回
images目录中预定义的error.png图像的路径。此图像包含Error loading traffic map消息。
-
重要提示
从这个方法中我们可以得到的主要启示是,与许多需要 JSON 库来解析的 API 响应不同,对 MapQuest Traffic API 的GET请求的响应直接包含交通地图的图像数据,因此它可以保存为图像文件,无需解析 JSON。
- 我们将代码保存为
TrafficMap.py在IoTWeatherDisplay项目子文件夹中。
随着WeatherData和TrafficMap类的创建完成,我们现在可以继续编写一个Dashboard类,该类将处理从这些网络服务检索到的信息显示。
添加 Dashboard 和 MyApp 类
对于Dashboard类,我们使用 Kivy 库。Kivy 是一个开源的 Python 框架,旨在开发可在各种平台上运行的多点触控应用程序,包括 Windows、macOS、Linux、iOS 和 Android。
在我们定义 Dashboard 类的同一 Python 文件中,我们将添加一个名为 MyApp 的 Kivy App 类。在这个代码中,MyApp 类是 Kivy 的 App 类的子类,通过在其 build()方法中创建 Dashboard 类的实例来定义应用程序的主要入口点。
要做到这一点,我们执行以下操作:
-
我们通过点击树莓派任务栏中的菜单图标来启动 Thonny。然后,我们导航到编程类别并选择Thonny。
-
一旦进入 Thonny,我们将激活
ch4-env虚拟环境。 -
我们可以通过选择文件然后新建,或者在键盘上按Ctrl + N来创建一个新的标签页。
-
我们通过配置 Kivy 以全屏和无边框模式运行来开始我们的代码:
from kivy.config import Config Config.set('graphics', 'fullscreen', 'auto') Config.set('graphics', 'borderless', '1')
为什么我们在添加其他导入之前要配置我们的 Kivy 应用程序?
在 Kivy 中导入其他包之前,我们配置我们的应用程序以确保设置在应用程序的生命周期开始时应用。如果配置在导入包之后完成,某些设置可能不会应用,或者可能导致意外的行为,因为 Kivy 组件可能在自定义设置设置之前就已经使用默认配置初始化了。
-
配置完成后,我们导入我们应用程序所需的其它包:
from kivy.app import App from kivy.uix.floatlayout import FloatLayout from kivy.uix.label import Label from kivy.uix.image import Image, AsyncImage from kivy.clock import Clock from WeatherData import WeatherData from TrafficMap import TrafficMap让我们看看我们刚刚添加了什么:
-
from kivy.app import App: 从 Kivy 框架中导入主应用程序类,这是构建和运行应用程序所必需的。 -
from kivy.uix.floatlayout import FloatLayout: 从 Kivy 中导入FloatLayout类,允许以自由形式定位和调整小部件的大小。我们这样做是为了能够在屏幕上的确切位置定位我们的 GUI 组件。 -
from kivy.uix.label import Label: 从 Kivy 中导入Label类,用于在屏幕上显示文本。 -
from kivy.uix.image import Image, AsyncImage: 从 Kivy 中导入Image和AsyncImage类,允许在应用程序中显示静态和异步图像。 -
from kivy.clock import Clock: 从 Kivy 中导入Clock类,使应用程序内周期性函数的安排成为可能。我们将使用此类每 30 分钟更新我们的仪表板。 -
from WeatherData import WeatherData: 导入我们自定义的WeatherData类,用于处理与天气相关的信息。 -
from TrafficMap import TrafficMap: 导入自定义的TrafficMap类,用于管理交通地图数据和图像。
-
-
在导入之后,我们定义我们的
Dashboard类及其初始化方法:class Dashboard(FloatLayout): def __init__(self, city, latitude, longitude, zoom): super(Dashboard, self).__init__() self.city = city self.traffic_map = TrafficMap(latitude, longitude, zoom) self.init_widgets() Clock.schedule_interval(self.update_status, 1800) self.update_status(0)让我们看看我们刚刚添加的内容:
-
我们的
Dashboard类继承自 Kivy 的FloatLayout类,允许定位和布局功能。 -
我们使用特定的参数(
city、latitude、longitude和zoom)初始化我们的类。 -
通过
super(Dashboard, self).__init__()调用FloatLayout超类的__init__()方法,确保适当的初始化并从父类继承行为。 -
然后,我们使用给定的
latitude、longitude和zoom参数设置交通地图。 -
我们使用 Kivy 的
Clock类来安排每1800秒(30分钟)的定期更新。 -
我们调用
self.init_widgets()方法来创建和定位布局内部的小部件。 -
在初始化过程中,我们调用一次
self.update_status(0)来设置仪表板的初始状态。如果未调用此方法,仪表板的初始状态可能未设置,可能导致在预定更新发生之前显示初始内容出现延迟,预定更新设置为每1800秒或30分钟发生一次。
-
-
在类初始化到位后,我们初始化小部件的状态。我们首先从
Temperature、Conditions和Attire小部件开始:def init_widgets(self): self.add_widget(Label(text="Temperature", pos=(-275, 175), color=(1, 1, 1, 1), font_size=22, font_name='fonts/ ArialBlack.ttf')) self.add_widget(Label( text="Conditions", pos=(-275, 60), color=(1, 1, 1, 1), font_size=22, font_name='fonts/ ArialBlack.ttf')) self.add_widget(Label(text="Attire", pos=(-280, -80), color=(1, 1, 1, 1), font_size=22, font_name='fonts/ ArialBlack.ttf')) self.add_widget(Image(source='images/box.png', pos=(-275, 145))) self.add_widget(Image(source='images/box.png', pos=(-275, 10))) self.add_widget(Image(source='images/box.png', pos=(-275, -127))) -
然后,我们添加一个
city字段、天气状况和交通地图:self.city_label = Label(text=self.city, pos=(250, 185), color=(1, 1, 1, 1), font_size=30, font_name='fonts/ ArialBlack.ttf') self.add_widget(self.city_label) self.temperature_label = Label(pos=(-275, 125), color=(1, 1, 1, 1), font_size=40, font_name='fonts/ ArialBlack.ttf') self.add_widget(self.temperature_label) self.conditions_image = AsyncImage(pos=(-278, 20)) self.add_widget(self.conditions_image) self.weather_conditions_label = Label( pos=(-280, -25), color=(1, 1, 1, 1), font_size=20, font_name='fonts/ ArialBlack.ttf') self.add_widget(self.weather_conditions_label) self.traffic_map_image = AsyncImage(pos=(120, -30)) self.add_widget(self.traffic_map_image) self.attire_image = Image(pos=(-280, -140)) self.add_widget(self.attire_image)init_widgets()方法负责初始化并添加静态和动态小部件到Dashboard布局中,Dashboard是 Kivy 的FloatLayout类的子类。
重要提示
静态小部件是那些在整个应用程序生命周期中保持不变且不需要动态更新的组件。这包括在指定位置添加显示温度、状况和着装文本的标签,并使用特定的颜色、字体大小和字体。还包括三个图像小部件,显示位于‘images/box.png’的图像,放置在布局的不同位置。
AsyncImage小部件用于显示天气状况图标,一个用于显示天气状况文本的标签,另一个AsyncImage小部件用于显示交通地图,以及一个图像小部件用于显示当前天气状况的适当着装。
init_widgets()方法利用 Kivy 的FloatLayout类提供的add_widget()方法将每个小部件添加到布局中。动态小部件也存储为Dashboard类的属性,以便以后可以轻松访问和更新。
-
我们使用
Dashboard类的update_status()方法,每隔30分钟(如初始化方法中设置)为我们的屏幕提供新的值:def update_status(self, *args): weather_data = WeatherData(self.city) self.traffic_map_image.source = self.traffic_map. get_traffic_map() self.attire_image.source = weather_data. get_attire_image() self.temperature_label.text = weather_data. get_temperature() + "\u00B0C" self.weather_conditions_label.text = weather_data.get_conditions() self.conditions_image.source = weather_data. get_weather_conditions_icon()Dashboard类中的update_status()方法在更新动态小部件以反映当前数据方面发挥着重要作用。最初,它使用当前城市创建WeatherData类的一个实例。然后,该方法继续更新traffic_map_image小部件的源,以一个新的交通地图图像反映当前状况,该图像是通过TrafficMap类的get_traffic_map()方法获得的。它还更改attire_image小部件的源,以表示适合当前天气的服装,利用WeatherData类的get_attire_image()方法。温度标签的文本通过从
WeatherData类获取当前温度来更新,并在后面附加度符号和字母C来表示摄氏度。weather_conditions_label小部件的文本被修改,以提供对当前天气状况的描述,这也来自WeatherData类。最后,
conditions_image小部件的来源被更新为一个代表当前天气状况的图标,这是通过使用WeatherData类中的get_weather_conditions_icon()方法实现的。 -
完成我们的
Dashboard类后,我们将接下来开发MyApp类。这个类将作为我们的 Kivy 应用程序的入口点,处理仪表盘界面的初始化和管理。我们将在同一文件中紧随Dashboard类之后立即创建这个类:class MyApp(App): def build(self): city = 'Toronto' latitude = 43.6426 longitude = -79.3871 zoom = 12 return Dashboard(city, latitude, longitude, zoom) if __name__ == '__main__': MyApp().run()MyApp类继承自 Kivy 的App类,负责初始化和运行主应用程序。在MyApp的build()方法中,指定了城市多伦多及其相应的纬度和经度坐标(43.6426和-79.3871),以及缩放级别。这些 GPS 坐标指向多伦多的位置(多伦多市中心靠近 CN 塔)。然后创建并返回一个
Dashboard对象,并带有这些参数。if __name__ == '__main__':这一行确保只有当脚本直接运行时(不是作为另一个脚本中的模块导入时)才会执行其后的代码。直接运行时,它创建MyApp类的一个实例并调用其run()方法,启动 Kivy 应用程序并显示与多伦多相关的指定细节的初始化仪表板。 -
我们将代码保存为
Dashboard.py在IoTWeatherDisplay项目子文件夹中。
在编写了所有这些代码之后,是时候在我们的 7 英寸屏幕上运行它了。
运行物联网信息显示应用程序
要通过 Dashboard.py 脚本执行我们的物联网信息显示,我们使用 Thonny。我们可以点击绿色的运行按钮并在键盘上按 F5,或者点击顶部的 运行 菜单选项,然后选择 运行 当前脚本。
在没有错误输入(或从 GitHub 仓库 github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter4 复制)所有上述代码的情况下,我们应该在我们的 7 英寸显示器上看到物联网信息显示全屏运行:

图 4.10 – 显示加拿大多伦多天气和交通信息的物联网信息显示
如我们所见,今天在多伦多是相对温暖的一天。在交通流量方面,有施工和其他需要避免的区域。天气晴朗,建议今天穿短袖衬衫。如果我们在一个标准计算机显示器上运行它,我们可能会看到显示周围有一个大黑边。
在创建物联网信息显示等特定应用中,可以从 Raspberry Pi 中移除鼠标和键盘,使设备可以作为带有 7 英寸触摸屏的亭子使用。
摘要
在本章中,我们探讨了与 Raspberry Pi 兼容的各种小型屏幕。然后我们使用 Raspberry Pi 的 7 英寸触摸屏创建了一个多功能的物联网信息显示,其中包括特定城市的天气预报和交通地图。
通过利用和优化网络服务数据,我们展示了 Raspberry Pi 在处理复杂信息显示任务方面的灵活性。本章开发的原则和代码可以适应其他用途,例如家庭自动化仪表板、公共信息亭或制造过程的工业监控系统。
在过去的几章中,我们主要关注显示信息——特别是通过网络服务提供的天气信息。在下一章中,我们将开始探索使用 Raspberry Pi 的感官信息,我们致力于构建物联网安全应用程序。
第二部分:构建物联网家庭安全仪表板
在第二部分中,我们探讨了树莓派和树莓派 Pico 上的通用输入/输出(GPIO)端口,使用树莓派 Pico W 和 PIR 运动传感器构建了一个物联网警报模块,利用 M5Stack ATOM Matrix 和树莓派 Pico W 创建了一个物联网按钮,并在带有 7 英寸触摸屏的树莓派 5 上开发了一个物联网警报仪表板,用于控制和监控。
本部分包含以下章节:
-
第五章, 探索 GPIO
-
第六章, 构建物联网警报模块
-
第七章, 构建物联网按钮
-
第八章, 创建一个物联网警报仪表板
第五章:探索 GPIO
在本书的前四章中,我们已涉及树莓派上的通用输入/输出(GPIO)端口。在第三章中,我们广泛地使用了它来构建我们的天气指示器。在本章中,我们将深入探讨树莓派 GPIO 端口的特性和应用,随着我们开始构建我们的物联网家庭安全应用。我们还将探索树莓派 Pico 上的 GPIO 端口,它是树莓派的微控制器兄弟。
在动手实验部分,我们将使用 PIR 运动传感器来检测人类存在,构建一个基本的报警系统。这个系统将集成一个按钮用于激活控制,以及一个蜂鸣器作为警报机制。通过这个实际练习,我们将展示树莓派如何与各种组件接口,以创建功能性的实际应用。
在本章中,我们将涵盖以下内容:
-
介绍树莓派和树莓派 Pico 上的 GPIO
-
理解传感器、执行器和指示器
-
构建一个简单的报警系统
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
具备 Python 编程的中间知识
-
一款较新的树莓派,最好是带有至少 4GB RAM 的树莓派 5
-
一个 PIR 传感器
-
一款 SFM-27 蜂鸣器
-
一个按钮,如街机风格的按钮
本章的代码可以在以下位置找到:
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter5
介绍树莓派上的 GPIO
GPIO 端口是树莓派和 Pico 上的一组 40 个引脚,允许与外部世界交互。这些引脚可以配置为输入或输出,并可以连接到传感器、LED 等。其中包括提供电源的端口、几个接地连接以及用于特定协议(如 I2C、UART、SPI 和 PCM)的 GPIO。在下面的表中,我们可以看到我们如何将引脚与特定的通信协议匹配:

图 5.1 – 树莓派 GPIO 引脚和通信协议
探索树莓派 GPIO 引脚图
在图 5.2中,我们可以看到树莓派和树莓派 Pico 的 GPIO 端口引脚图。概述了 GPIO 引脚编号以及可能配置为特殊操作的引脚。
重要提示
树莓派和树莓派 Pico 的许多购买都附带一个 GPIO 引脚图参考表。这个工具对于开发来说非常有用,因为它作为 GPIO(树莓派)和 GP(Pico)引脚编号的指南,这些编号是设备连接到 Pi 和 Pico 所需的。
我们可能在互联网上找到这些图表的多种版本。对于有兴趣的人来说,有一个交互式的树莓派 GPIO 引脚图在pinout.xyz。

图 5.2 – GPIO 引脚图
树莓派(Raspberry Pi)的 GPIO 端口包括 GPIO 0(EEPROM SDA)和 GPIO 1(EEPROM SCL)引脚,这些引脚使得与连接的 HAT 模块进行自动通信成为可能。此外,还有用于各种通信协议的引脚。
为了更好地理解树莓派和 Pico 的通信能力,让我们来检查我们可能配置设备与之工作的特定通信协议。
理解 GPIO 引脚通信协议
树莓派和 Pico 的 GPIO 引脚支持多个关键通信协议,如 I2C、SPI、UART 和 PCM。这些协议使得树莓派和 Pico 能够与各种设备和传感器交互。每种协议都有其独特的应用,从与传感器接口到数字音频传输。在接下来的章节中,我们将概述这些可能配置的通信方法。
I2C
I2C 是由飞利浦半导体(Philips Semiconductor)现在为恩智浦半导体(NXP Semiconductors)开发的串行通信协议。I2C 允许多个设备通过双线接口相互通信。
下面是连接的分解:
-
串行数据线(SDA):这是数据线。
-
串行时钟线(SLC):这是同步 I2C 总线上的数据传输的时钟线。
I2C 协议的关键特性包括以下内容:
-
多主多从:多个设备可以连接到总线上,并且可以支持超过一个主设备。
-
基于地址的通信:总线上的每个设备都有一个唯一的地址,允许进行定向通信。
-
简单的硬件连接:在设置 I2C 连接时,SDA 和 SCL 线只需要两个上拉电阻。
-
速度变体:I2C 支持不同的速度模式,包括标准模式(最高 100 Kbps)、快速模式(最高 400 Kbps)、高速模式(最高 3.4 Mbps)等。
在树莓派和 Pico 上,I2C 协议对于仅用两根线连接 PIR 传感器、迷你 OLED 屏幕和其他组件尤其有价值。
SPI
串行外设接口(SPI)是一种用于短距离通信的同步串行通信协议,主要用于嵌入式系统,用于连接微控制器和外围设备,如传感器、SD 卡和 LCD。
下面是连接的分解:
-
SDO—串行数据输出:该线路用于从控制器向外围设备发送数据。
-
SDI—串行数据输入:该线路允许外围设备将数据发送回控制器。
-
SCLK—串行时钟:类似于 I2C 中的 SCL 线,它提供时钟信号,同步设备间的数据传输。
-
CS0—芯片选择 0:当存在多个外围设备时,此线路对于选择特定设备至关重要。通过切换 CS0,控制器可以确定它与哪个设备进行通信。
-
CS1—芯片选择 1:与 CS0 类似,CS1 提供了一条额外的选择线路,使得在单个 SPI 总线上可以寻址更多设备。
以下是 SPI 的功能和优势:
-
全双工通信:SPI 支持同时双向通信,允许数据同时发送和接收。这意味着在 SPI 通信期间,数据可以同时从主设备流向从设备,以及从从设备流向主设备。这与 I2C 不同,I2C 在半双工模式下运行(在任何给定时刻可以发送或接收数据,但不能同时进行)。
-
速度:在数据传输速率方面,SPI 通常超过 I2C,使其适用于需要高速通信的应用。
-
直接硬件控制:与 I2C 中的特定寻址方案不同,SPI 通过 CE 引脚提供更直接的设备控制。
SPI 通信在连接如 SD 卡、显示屏、ADCs(即模拟-数字转换器)等组件时非常有用。一个例子是我们通过 SPI 连接 SD 卡进行数据记录的应用。
UART
UART,即通用异步收发器,是电子领域的一种常用通信协议,尤其以其在设备之间点对点通信中的简单性和有效性而闻名。UART 以其全双工通信能力而突出。这允许两个设备同时交换数据。
以下是连接的分解:
-
TX—发送:此连接用于将数据发送到另一个设备。
-
RX—接收:相反,此连接用于从另一个设备接收数据。
这些连接允许双向通信;当树莓派和 Pico 通过它们的 TX 引脚发送数据时,它们也可以通过它们的 RX 引脚接收数据。
与 I2C 和 SPI 等其他通信方法相比,UART 的一些特性包括:
-
对等通信:与 I2C 和 SPI 不同,后者有定义的主从关系,UART 设备作为对等体通信,没有指定的主设备或从设备。
-
异步机制:与 SPI 不同,UART 通信不依赖于共享的时钟信号。相反,在启动通信之前,两个设备必须就波特率达成一致。
由于其简单性,UART 通常用于串行控制台访问和与需要简单通信路径的外围设备接口。UART 和树莓派的一个常见用途是将无人机飞行控制器连接起来。
PCM
我们可以在树莓派上的 GPIO 12、35、38 和 40 上设置PCM(脉冲编码调制),这些引脚通过在固定时间间隔内采样它们的幅度并将其量化为数字代码来数字表示模拟信号。这些引脚专门用于设备上的 PCM 通信:
-
GPIO 12(PCM_CLK):这是时钟引脚,确保数据传输时的同步。
-
GPIO 35(PCM_FS):这是帧同步。它有助于定义数据帧的开始和结束。
-
GPIO 38(PCM_DIN):这是数据输入。这是树莓派从外部设备接收 PCM 数据的地方。
-
GPIO 40(PCM_DOUT):这是数据输出。树莓派使用此引脚将 PCM 数据发送到其他设备。
PCM 的一些显著特点包括以下内容:
-
数字表示法:PCM 将模拟信号转换为数字格式,使其在抗噪声和干扰的格式中保存原始信号的细微差别,非常适合。
-
在音频中常见:许多音频格式,如 WAV,使用 PCM 来数字表示声音,确保高保真度。
-
灵活性:PCM 可用于音频以外的各种应用,包括电信和数据存储。
在树莓派上,PCM 的实用性在各种任务中显而易见,从数字音频播放和录制到与需要精确的模拟到数字表示的设备进行接口。PCM 与树莓派的一个显著应用是在数字音频系统和声音接口领域。
以下是一个总结通信协议和树莓派以及树莓派 Pico 的表格:

图 5.3 – 总结的通信协议
现在我们已经了解了可以与树莓派和树莓派 Pico 一起使用的通信协议,让我们来看看传感器、执行器和指示器,以及我们如何将它们连接到我们的树莓派和树莓派 Pico 上。这是 GPIO 端口的主要用途。
理解传感器、执行器和指示器
树莓派和 Pico 的 GPIO 功能为连接各种传感器和驱动指示器(如 LED)以及控制执行器(如伺服电机)提供了基础。通过集成这些设备,我们的设备可以收集数据并根据这些信息执行响应动作。
例如,一个 PIR 传感器(图 5**.4中的A)可以检测房间内的运动,从而点亮 LED 或触发警报。使用温度和湿度传感器,如 DHT11(图 5**.4中的B),我们可以评估环境条件,并据此启动风扇或加热元件。
使用距离传感器(图 5**.4中的C),我们可以测量物体的接近程度,并指令伺服电机停止机器人,防止碰撞:

图 5.4 – 树莓派的传感器
我们可以将许多设备集成到我们的 Raspberry Pi 和 Pico 中。仅就机器人技术而言,它提供了用于引导机器人沿预定路径行驶的线跟踪传感器和飞行时间(TOF)传感器,这些传感器体积小且精确,距离传感器。在机器人技术之外,还有土壤湿度、雨量检测、光线和温度/湿度传感器,这为自调节温室创造了潜力。
在下一节中,我们将探讨如何使用 Python 和 GPIO Zero 库连接并读取 PIR 传感器(图 5.4 中的 A)和 Raspberry Pi 的数据。结合我们所学,我们将利用我们的知识来创建一个基本的报警系统,使用我们的 Raspberry Pi。
在我们编写与 PIR 传感器交互的代码之前,我们将首先设置我们的开发环境。
设置我们的开发环境
我们将使用 Python 虚拟环境进行我们的开发。由于有些库只与 Python 的根安装版本兼容,因此我们将使用系统包在我们的 Python 虚拟环境中。为此,我们执行以下操作:
-
在我们的 Raspberry Pi 5 上,我们打开一个终端。
-
为了存储我们的项目文件,我们使用以下命令创建一个新的目录:
mkdir chapter5 -
然后,我们使用以下命令导航到新目录:
cd chapter5 -
我们使用以下命令为我们的项目创建一个新的 Python 虚拟环境:
ch5-env and enable access to the system site packages. This allows the virtual environment to inherit packages from the global Python environment, which can be useful when certain libraries are installed system wide. Once the environment is set up, we can activate it and begin installing project-specific packages without affecting the global Python environment. -
在创建我们的新 Python 虚拟环境后,我们使用以下命令将其源代码导入:
ch5-env Python virtual environment:

图 5.5 – 使用 dashboard-env 环境的终端
-
我们使用以下命令关闭终端:
exit -
现在,我们已准备好加载 Thonny。我们通过点击 Raspberry Pi 任务栏中的菜单图标,导航到编程类别,并选择Thonny来完成此操作。
-
默认情况下,Thonny 使用 Raspberry Pi 内置的 Python 版本。对于我们的项目,我们将使用我们刚刚创建的 Python 虚拟环境。为了开始,我们需要通过点击视图并选择文件来查看项目文件(如果尚未选择)。
-
在“文件”部分,我们找到
ch5-env目录。 -
然后,我们右键点击文件夹并选择激活虚拟环境选项:

图 5.6 – 在 Thonny 中激活 Python 虚拟环境
在创建我们的项目文件夹并设置和激活我们的 Python 虚拟环境后,我们现在可以开始编写代码来访问连接到 GPIO 端口的传感器。我们将从探索 PIR 传感器开始。
探索 PIR 传感器
被动红外(PIR)传感器是专门用于检测红外辐射的设备,通常由生物体因体温而发出。这些传感器通过监测红外水平的变化来工作,这些变化发生在红外源(如人)在其视野中移动时。PIR 传感器可以通过其占主导地位的独特球壳来识别(图 5.4 中的 A)。
将 PIR 传感器连接到 Raspberry Pi 很简单。需要三个引脚:
-
VCC 连接到 Raspberry Pi 的 5V。
-
GND 连接到 GND 引脚。
-
信号连接到 GPIO 23。
在 图 5.7 中,我们看到一个 PIR 传感器通过面包板连接到 Raspberry Pi:

图 5.7 – 将 PIR 传感器连接到 Raspberry Pi
要从我们的 PIR 传感器获取感官数据,我们使用 GPIO Zero 库。为此,我们执行以下操作:
-
在 Thonny 中,我们通过选择 文件 然后选择 新建 或按键盘上的 Ctrl + N 来创建一个新的标签页。
-
在标签页中,我们输入以下代码:
from gpiozero import MotionSensor from time import sleep pir = MotionSensor(23) while True: if pir.motion_detected: print("Motion detected!!") else: print("Waiting……") sleep(5) -
我们将代码保存为
pir-test.py。 -
要在 Thonny 中运行代码,我们点击绿色运行按钮,按键盘上的 F5 或点击顶部的 运行 菜单选项,然后选择 运行 当前脚本。
-
当我们把手靠近 PIR 传感器时,我们应该看到消息
"Motion detected!!":

图 5.8 – 测试 PIR 传感器的结果
在测试了 PIR 传感器后,我们现在可以使用 Raspberry Pi 构建一个基本的报警系统。
构建一个简单的报警系统
Raspberry Pi 的 GPIO 引脚可以配置为特定的通信协议,如 I2C,或设置为标准输入/输出引脚以测量条件或水平。
在本章的最后部分,我们将使用我们的知识来构建一个简单的报警系统。我们的报警系统将包括一个按钮、一个 PIR 传感器和一个蜂鸣器。所有组件都连接到 GPIO 端口(图 5.9)。

图 5.9 – 蜂鸣器、按钮和 PIR 传感器通过扩展排线连接到 GPIO 端口
在 图 5.9 中,我们使用 GPIO 扩展排线将 GPIO 端口连接到面包板,这样我们就可以轻松地进行原型设计和重新配置连接。排线简化了过程并使布线整齐。使用 GPIO 扩展电缆完全是可选的。就像我们在 第四章 中做的那样,我们的 Raspberry Pi 安装了 Raspberry Pi 七英寸触摸屏及其相关的外壳。
我们使用以下图示将组件连接到 GPIO 端口:

图 5.10 – 基本报警电路
我们在 Thonny 中编写我们的报警系统代码:
-
在 Thonny 中,我们通过选择 文件 然后选择 新建 或按键盘上的 Ctrl + N 来创建一个新的标签页。
-
在标签页中,我们输入以下代码:
from gpiozero import MotionSensor, Button, Buzzer from time import sleep pir = MotionSensor(23) button = Button(20) buzzer = Buzzer(21) active = False def toggle_alarm(): global active if active: active = False buzzer.off() print("Alarm deactivated!") else: active = True print("Alarm activated!") def monitor(): while True: if active: pir.wait_for_motion() print("Motion detected!") sleep(5) if active: buzzer.on() print("Alarm triggered!") else: buzzer.off() button.when_pressed = toggle_alarm try: monitor() except KeyboardInterrupt: print("Exiting...") buzzer.off()在执行我们的代码之前,让我们先过一遍。
-
我们首先导入我们的库:
-
gpiozero用于 Raspberry Pi GPIO 操作 -
time用于创建睡眠间隔
-
-
我们然后定义我们的 GPIO 连接:
-
PIR 传感器连接到 GPIO 引脚 23
-
按钮连接到 GPIO 引脚 20
-
蜂鸣器连接到 GPIO 引脚 21
-
active:用于跟踪闹钟状态的变量(开启/关闭)
-
-
我们定义了我们的
toggle_alarm()函数:-
在活动和非活动状态之间切换闹钟状态
-
如果在蜂鸣器响起时解除闹钟,蜂鸣器会被关闭
-
-
我们接下来定义
monitor()函数:-
如果闹钟系统处于活动状态,则持续检查运动
-
如果检测到运动,程序将等待五秒钟以允许解除闹钟
-
如果在五秒钟延迟后闹钟仍然处于活动状态,蜂鸣器会响起
-
-
然后我们将
button.when_pressed的gpiozero属性设置为绑定到toggle_alarm()函数。这允许我们在按下按钮时立即执行一个函数。这种事件驱动的方法消除了连续轮询的需要。 -
我们的主要执行块运行
monitor()函数。 -
try / catch允许在键盘中断(Ctrl + C)的情况下干净地退出,确保在退出时蜂鸣器被关闭。
-
-
我们将程序保存为
basic-alarm.py。 -
要在 Thonny 中运行代码,我们点击绿色运行按钮,在键盘上按F5,或者在顶部点击运行菜单选项,然后点击运行****当前脚本。
-
我们通过按下按钮来激活闹钟。我们的单一传感器是 PIR 传感器,它监控运动。
-
一旦检测到运动,蜂鸣器激活前会有五秒钟的延迟。这为意识到闹钟的人提供了时间,在蜂鸣器响起之前将其关闭。
-
要关闭闹钟,我们只需再次按下按钮。
在接下来的章节中,我们将我们的基本闹钟系统转变为一个使用 Raspberry Pi Pico W 的物联网闹钟。这将使我们能够从全球任何位置监控传感器数据,提供像在我们旁边安装的传感器一样管理我们的闹钟系统的便利性和多功能性。
摘要
在第五章中,我们探讨了 Raspberry Pi 的 GPIO 引脚及其通信能力,包括 I2C、SPI、UART 和 PCM 等协议。我们强调了 GPIO 引脚图在处理 Raspberry Pi 和 Raspberry Pi Pico 时的意义。
我们专注于 PIR 传感器进行运动检测,并将其连接到我们的 Raspberry Pi。然后我们利用这些知识使用 PIR 传感器、按钮和蜂鸣器构建了一个基本的闹钟系统。尽管我们没有与 Pico 进行实际操作练习,但我们学到的原理和技术适用于 Raspberry Pi 和 Raspberry Pi Pico。
本章开始了我们物联网家庭安全系统的构建。在接下来的章节中,我们将向我们的基本闹钟添加功能,当我们将其转变为一个令人印象深刻的物联网家庭安全系统时,我们将创建一个物联网闹钟模块和一个物联网按钮来启动它。
第六章:构建物联网警报模块
在上一章中,我们探讨了 Raspberry Pi 的 GPIO 端口并构建了一个基本的警报系统。我们学习了不同的通信协议,并使用 GPIO 端口与一组传感器一起工作。在本章中,我们将使用 Raspberry Pi Pico W、公共消息队列遥测传输(MQTT)服务器和 MQTTHQ 网页客户端(图 6**.1)来增强我们的基本警报系统。
我们将使用 Raspberry Pi Pico W 来托管当运动消息发送到 MQTT 服务器并通过 MQTTHQ 网页客户端查看时:

图 6.1 – 使用 MQTT 的物联网警报模块
我们将从 MQTTHQ 网页客户端向 Raspberry Pi Pico W 发送蜂鸣器消息以激活蜂鸣器。这个 Raspberry Pi Pico W 和 MQTTHQ 网页客户端设置构成了我们物联网家庭安全系统的基础。
我们将从这个章节的开始,通过使用公共服务器作为我们的开发平台来探索 MQTT。然后,我们将熟悉 Raspberry Pi Pico W,突出其在物联网应用中的优势。最后,我们将通过将我们的物联网警报模块组件安装到定制的 3D 打印机箱中结束。
因此,在本章中,我们将涵盖以下主要主题:
-
探索 MQTT
-
使用 Raspberry Pi Pico W 和 MQTT
-
构建一个物联网警报模块机箱
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
Python 编程的中级知识。
-
1 x Raspberry Pi Pico WH(带引脚)用于与面包板或 Raspberry Pi Pico GPIO 扩展器一起使用。
-
1 x Raspberry Pi Pico W(不带引脚)用于安装到可选的 3D 打印机箱中。
-
1 x HC-SR501 PIR 传感器。
-
1 x LED,通过 220 欧姆电阻连接(请参阅第三章以了解构造)。
-
1 x SFM-27 激活蜂鸣器。
-
有访问 3D 打印机或 3D 打印服务的权限以打印可选的机箱。
本章的代码可以在以下位置找到:
探索 MQTT
MQTT是物联网的关键组件,它使得连接设备之间实现轻量级和高效的通信成为可能。在图 6**.1中,我们展示了 MQTT 在普遍的云符号中,象征着互联网。MQTT 基于发布-订阅模型,允许设备向特定主题发布消息,同时订阅相关主题。这个框架确保了高效和选择性的通信,使得设备只能接收与其功能相关的消息。MQTT 的轻量级设计最小化了资源开销,使其成为能力受限设备的理想选择。
我们通过查看 MQTT 中的发布-订阅模型是如何工作的来开始我们对 MQTT 的调查。
理解 MQTT 中的发布-订阅模型
MQTT 在使物联网设备之间通信方面有效,归功于其发布/订阅模型。该模型为设备之间提供了一种灵活且可扩展的通信方式。
在 MQTT 中,设备被分为两个角色——发布者和订阅者:
-
在
IoTAlarm主题下的motion消息,而温度传感器在temp主题下通信一个25 C的消息。 -
IoTAlarm和temp主题,而手机仅订阅了IoTAlarm主题。
MQTT 中的主题充当通道或通信路径。我们可以将主题视为标签或类别,消息属于这些类别,例如在图 6.2 中用黑色框和白色文字标记的IoTAlarm和temp主题。当发布者向特定主题发送消息时,MQTT 代理(服务器)管理该消息。
代理维护一个所有订阅该主题的订阅者列表,保证消息发送给每个订阅者。这种机制允许高效且具有选择性的通信,因为设备只接收它们已订阅的主题的消息。在图 6.2 中,我们看到我们的 PC 订阅了IoTAlarm和temp主题,而我们的手机仅订阅了IoTAlarm主题:

图 6.2 – MQTT 通信示意图
在我们尝试一些 MQTT 的动手经验之前,我们将探讨 MQTT 中的服务质量(QoS)级别。理解 QoS 级别至关重要,因为它们决定了 MQTT 中消息的可靠性和交付保证。
理解 MQTT 中的 QoS
QoS是 MQTT 的一个重要方面,它决定了 MQTT 代理(服务器)和 MQTT 客户端(设备或应用程序)之间消息传递的保证级别。
MQTT 提供了三个 QoS 级别:
-
QoS 0 (最多一次): 在此模式下,消息最多只发送一次,这意味着消息可能不会被发送到接收者,也可能在没有确认或收件保证的情况下丢失。这种 QoS 级别适用于消息丢失可以接受且消息传递不是关键的场景。
-
如果发送者收到(ACK)消息已被接收的确认,它将重新发送消息。这种 QoS 级别保证了消息被接收者接收,但可能导致重复消息。
-
QoS 2 (精确一次): QoS 2 提供了最高级别的保证。它确保消息恰好一次被发送到接收者。这种 QoS 级别涉及发送者和接收者之间更复杂的握手,以确保没有重复或消息丢失。
为了我们的开发目的,QoS 0 是足够的,因为它提供了合理的消息传递,无需 QoS 1 和 QoS 2 所需的更复杂的消息跟踪和确认机制。QoS 0 简化了代码中的消息处理,使其成为开发场景中的实际选择。
使用 MQTTHQ 网页客户端探索 MQTT 基础知识
为了获取 MQTT 的实际知识,我们将使用 MQTTHQ 网页客户端。这个基于网页的服务简化了学习 MQTT 的过程,消除了复杂安装或大量编程的需求。作为一个面向开发和测试的公共资源,它为我们提供了一个可访问的环境来探索和理解 MQTT 的功能。
我们首先在网页浏览器中打开网页客户端:
-
在我们的浏览器中,我们使用以下 URL 导航到客户端:
mqtthq.com/client。 -
为了确保我们可以使用客户端进行测试,我们验证从屏幕右上角的消息中我们是否连接到了 public.mqtthq.com:

图 6.3 – 连接到 mqtthq.com 客户端
如果没有出现表示 已连接 的消息,我们继续刷新页面,直到出现为止。
-
在
IoTAlarm中保留0,并点击 订阅 按钮。 -
我们应该注意到,接收到的有效载荷 下的文本更新为显示 等待数据…,并且 订阅 按钮已变为 取消订阅 按钮:

图 6.4 – 订阅 IoTAlarm 主题
- 在
IoTAlarm中保留0,将Hello World!消息替换为motion,然后点击 发布 按钮:

图 6.5 – 向 IoTAlarm 主题发布消息
- 我们应该注意到,我们的
motion消息现在已出现在 订阅主题 部分的 接收到的有效载荷 框下:

图 6.6 – 接收到的 MQTT 消息
- 为了确认我们已成功从发布者向订阅者发送 MQTT 消息,我们可以使用两台不同的计算机:一台用于发布消息,另一台用于订阅并接收它们。
通过这次练习,我们有效地展示了使用 MQTTHQ 网页客户端发布和订阅 MQTT 消息的过程。在下一节中,我们将利用 Raspberry Pi Pico W 的功能开始构建我们的物联网警报模块。
使用 Raspberry Pi Pico W 和 MQTT
在本节中,我们将使用 Raspberry Pi Pico W 构建我们应用程序的物理警报部分。这个微控制器不仅价格低廉,而且为我们的项目带来了一系列功能,使我们能够高效地执行任务,而无需使用像 Raspberry Pi 这样的 单板计算机(SBC)的全部功能。
Raspberry Pi Pico W 并不取代我们的 Raspberry Pi;它补充了它,为我们的工具箱增添了独特的优势。作为一个微控制器,Pico W 与 Raspberry Pi 相比,成本效益更高,并且由于其更简单的架构和较低的功耗,通常不会过热。这种区别对于我们的物联网报警模块等项目至关重要,其主要任务是捕捉感官数据——这不需要 SBC 的计算能力。这使我们能够将 Raspberry Pi 保留用于需要更多计算资源的任务。
作为微控制器,我们的 Raspberry Pi Pico W 启动迅速,为我们的程序提供快速启动。我们不需要加载一个沉重的操作系统。
介绍 RP2040 芯片
Raspberry Pi Pico W 使用的是 Raspberry Pi Foundation 创建的双核 ARM Cortex-M0+处理器 RP2040 芯片。该芯片通过将微控制器典型的简化操作与执行更复杂微计算机类型任务的能力相结合,被设计为微控制器和微计算机之间的桥梁。
我们 Raspberry Pi Pico W 中的 W 表示我们的微控制器支持 Wi-Fi。除了 Raspberry Pi Pico W,还有标准 Pico(不带 Wi-Fi)、Pico H(不带 Wi-Fi 且带有焊接引脚)和 Pico WH(带有 Wi-Fi 和焊接引脚)。
Raspberry Pi Pico W 所基于的 RP2040 芯片也存在于其他微控制器上,如 Arduino Nano RP2040 Connect、Pimoroni Tiny 2040 和 Adafruit Feather RP2040。在 图 6**.7 中,我们看到 Waveshare RP2040-Zero-M (A)、Raspberry Pi Pico (B) 和 Raspberry Pi Pico W (C):

图 6.7 – 基于 RP2040 的微控制器
要在面包板上构建我们的报警电路,我们需要在 Raspberry Pi Pico W 上安装引脚。我们可以自己焊接它们,或者选择购买 Pico WH 版本。
我们将开始构建我们的物联网报警模块,首先在面包板上构建电路。
配置我们的报警电路
在本节中,我们将使用 SFM-27 主动蜂鸣器、一个带有电阻的 LED 和一个 HC-SR501 PIR 传感器来构建我们的报警电路。在将组件移动到 3D 打印的盒子之前,我们将在面包板上配置我们的电路。我们可以用 Raspberry Pi Pico GPIO 扩展器代替面包板。对于面包板,我们使用焊接到我们的组件上的公跳线连接到面包板。对于 GPIO 扩展器,我们使用焊接到我们的组件上的母跳线进行连接。
在 图 6**.8 中,我们可以看到我们的电路在一个 Fritzing 图中展示。为了创建我们的电路,我们使用 图 6**.8 中概述的连接将我们的组件连接到 Raspberry Pi Pico W:

图 6.8 – Raspberry Pi Pico W 报警电路
注意我们与 Raspberry Pi Pico W 上的 VBUS 端口的连接。当 Pico 通过 USB 供电时,连接到 VBUS 端口的组件将接收大约 5V 的电压,这是标准的 USB 电压。我们将通过 USB 端口为我们的物联网报警模块供电。
下表概述了 Raspberry Pi Pico W 上的电源端口,并提供了关于我们如何在未来的项目中利用它们的见解:
| 端口 | 输入 电压用途 | 输出 电压用途 |
|---|---|---|
| VBUS | 用于从 5V USB 源供电。 | 当 Pico 通过 USB 供电时,可用于为外部组件供电 5V。 |
| VSYS | 接受 1.8V 至 5.5V 的外部电源。 | 通常不用于为外部组件供电。 |
| 3V3(OUT) | 不常用于输入。 | 为外部 3.3V 组件提供稳定的 3.3V 供电。 |
| 3V3_EN | 不是一个电源端口,但是一个控制引脚,用于启用/禁用 3.3V 供电。 | 不适用。 |
表 6.1 – Raspberry Pi Pico (W) 上的电源端口
在我们的电路连接好之后,我们就可以开始编码了。我们将首先设置 Thonny 以进行微控制器开发。
设置我们的开发环境
就像我们迄今为止所做的一切软件开发一样,我们将使用 Thonny 作为我们的 集成开发环境(IDE)。Thonny 的操作系统版本(Windows、macOS、Linux、Raspberry Pi OS 等)的选择是灵活的,因为我们的重点是编写用于连接微控制器的代码,而不是用于编码的计算机。
重要提示
注意,Thonny 的不同操作系统版本可能表现出不同水平的功能,这对于本章是必要的。本节的内容基于 Thonny 的 Windows 版本,提供的截图反映了这一点。
我们将使用 MicroPython,Python 的轻量级版本,针对微控制器进行了优化,来开发我们的代码。MicroPython 与 Python 共享核心语法和功能,但需要注意的是,由于其专注于资源受限的环境,它可能缺少标准 Python 中可用的某些广泛库和功能。然而,这些差异是 MicroPython 在微控制器编程中使用的效率权衡。
要在我们的 Raspberry Pi Pico W 上安装 MicroPython,我们执行以下操作:
-
如果我们的操作系统上没有 Thonny,我们将访问 Thonny 网站,并下载一个合适的版本(
thonny.org)。 -
我们随后使用适合我们操作系统的适当方法启动 Thonny。
-
在按住 Pico W 上的 BOOTSEL 按钮(USB 端口附近的小白按钮)的同时,我们将它插入一个可用的 USB 端口,并忽略可能出现的任何弹出窗口。
-
然后,我们点击屏幕右下角的解释器信息,并选择 安装 MicroPython…:

图 6.9 – 安装 MicroPython…选项
- 对于MicroPython 变体,我们选择Raspberry Pi • Pico W/ Pico WH:

图 6.10 – 在 Raspberry Pi Pico W 上安装 MicroPython
-
我们点击安装按钮,安装完成后点击关闭按钮。
-
要使 Thonny 配置为在我们的 Pico W 上运行 MicroPython 解释器,我们从屏幕的右下角选择它:

图 6.11 – 从我们的 Pico W 中选择 MicroPython 解释器
- 我们通过检查Shell来确认 Thonny 正在使用我们的 Raspberry Pi Pico W 上的 MicroPython 解释器:

图 6.12 – Thonny 中的 MicroPython 提示
- 要运行 MQTT 代码,我们需要安装 MQTT 库。为此,我们在搜索框中输入
umqtt,并点击在 PyPI 上搜索:

图 6.13 – 在我们的 Pico W 上安装 MQTT 库
-
我们选择
micropython-umqtt.simple包,并点击安装按钮。 -
然后我们点击关闭按钮关闭对话框。
现在我们已经在我们的 Raspberry Pi Pico W 上安装了 MicroPython 和 MQTT 库,我们准备开始编码。我们的初步重点将是连接组件,然后是实现 MQTT 相关代码。
编写报警模块客户端代码
到现在为止,我们应该已经非常熟悉 Thonny IDE 了。连接到 Raspberry Pi Pico W 上的 MicroPython 解释器不会显著改变我们与 Thonny 的交互。
尽管如此,在我们的开发过程中能够查看存储在 Pico W 和我们的计算机上的文件是有益的。这种可见性使我们能够轻松验证文件位置并有效地管理我们的项目。
要在 Thonny 中打开文件视图,我们点击顶部的视图菜单并选择文件:

图 6.14 – 在 Thonny 中启用文件视图
我们应该在 Thonny 的左侧看到我们的 Raspberry Pi Pico W 和我们的计算机上的项目文件视图。
现在我们准备好开始编写代码了。我们将从蜂鸣器开始。
通过代码激活蜂鸣器
图 6.1 展示了从 MQTT 代理发送到我们的 Raspberry Pi Pico W 的IoTAlarm类型消息。此消息的目的是激活我们的报警模块中的蜂鸣器。为了处理这个任务,我们将创建一个单独的程序。与监控我们电路中的 PIR 传感器或 LED 组件相比,激活蜂鸣器涉及一个稍微复杂的过程,因此我们希望将其代码分离。
要做到这一点,我们执行以下操作:
-
我们将 Raspberry Pi Pico W 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来完成此操作。
-
我们接着通过从屏幕右下角选择它来在我们的 Pico W 上激活 MicroPython 环境。
-
在一个新的编辑标签中,我们输入以下代码:
from machine import Pin, PWM import utime BUZZER_PIN = 16 buzzer = PWM(Pin(BUZZER_PIN)) BUZZER_FREQ = 4000 def activate_buzzer(duration=5): buzzer.freq(BUZZER_FREQ) buzzer.duty_u16(32768) utime.sleep(duration) buzzer.duty_u16(0) -
要保存文件,我们从下拉菜单中选择 文件 | 另存为...。这将打开以下对话框:

图 6.15 – 将文件保存到我们的 Raspberry Pi Pico W
-
在此对话框中,我们被提供选择存储文件位置的机会。要将文件保存在我们的 Raspberry Pi Pico W 上,我们点击相应的按钮。
-
我们将文件命名为
buzzer.py并点击Pin和PWM(用于machine模块)。 -
我们导入
utime类以实现计时功能。 -
我们将
BUZZER_PIN常量设置为 16。这对应于我们的蜂鸣器接线图。 -
我们通过在指定的
BUZZER_PIN常量上初始化PWM类来创建一个buzzer对象。这种基于 PWM 的方法允许我们快速改变提供给蜂鸣器的电压,从而控制声音的音调和音量。 -
我们接着将
BUZZER_FREQ常量设置为4000,表示用于蜂鸣器的 PWM 信号的频率。 -
我们接着定义一个
activate_buzzer()函数。此函数接受一个可选的duration参数(默认为 5 秒)。 -
在
activate_buzzer()函数内部,我们执行以下操作:-
我们将
buzzer对象的频率设置为指定的BUZZER_FREQ常量。 -
我们将蜂鸣器的占空比设置为 50%(32768 出自 65536 的完整 16 位范围),创建一个平衡的音调,蜂鸣器在信号的 16 位周期中活跃一半,另一半不活跃。
-
然后,我们的代码使用
utime.sleep()函数暂停程序指定的秒数。 -
在指定的时间后,将
buzzer对象的占空比设置为0,关闭蜂鸣器。
-
我们可以使用 Thonny 中的 Shell 来测试我们的代码。为此,我们执行以下操作:
- 在 Shell 中,我们从蜂鸣器文件中导入
activate_buzzer()函数:

图 6.16 – 导入 activate_buzzer() 函数
- 函数导入后,我们可以通过简单地调用它并按键盘上的 Enter 键来运行它:

图 6.17 – 激活蜂鸣器
- 在蜂鸣器正确接线后,我们应该听到它响 5 秒。要调整持续时间,我们只需将另一个值传递给
activate_buzzer()函数。
我们必须为自己鼓掌,因为我们刚刚编写并执行了我们的第一个 MicroPython 程序!蜂鸣器代码完成后,是时候为我们的报警模块创建主程序了。
创建主代码
在本节中,我们将编写代码来为我们的物联网警报模块供电。在这段代码中,我们将连接 PIR 传感器、LED、Wi-Fi 和 MQTT 代理。
在 MicroPython 中,两个文件控制 Pico W 的启动和代码执行:boot.py在启动时处理必要的初始化,而main.py包含主要的用户代码,用于自定义逻辑和函数。
对于我们的代码,我们不会关注boot.py。然而,我们将专注于main.py,这是当 Pico W 首次通电时负责启动应用程序的程序。
要编写我们的物联网警报模块代码,我们执行以下操作:
-
在 Thonny 的新标签页中,我们首先输入我们的导入代码:
import machine import utime import network import _thread from umqtt.simple import MQTTClient from buzzer import activate_buzzer在我们前面的代码中,我们有以下内容:
-
machine: 提供对微控制器相关的函数和类的访问。 -
utime: 提供与时间相关的函数和定时控制,用于管理延迟和时间戳。 -
network: 提供与配置和管理网络连接相关的网络功能。 -
_thread: 允许创建和管理线程,以实现代码的并发执行。 -
MQTTClient(来自umqtt.simple):提供通过 MQTT 进行通信的 MQTT 客户端功能。 -
activate_buzzer(来自buzzer):用于激活蜂鸣器的自定义函数。
-
-
然后我们设置我们的变量:
SSID = "WiFiNetworkName" PASSWORD = "xxxxxxxxx" MQTT_SERVER = "broker.mqtthq.com" MQTT_PORT = 1883 pir = machine.Pin(26, machine.Pin.IN) led = machine.Pin(21, machine.Pin.OUT) wlan = network.WLAN(network.STA_IF) mqtt_client = None在前面的代码中,我们有以下变量:
-
SSID: 存储程序连接的 Wi-Fi 网络名称(SSID)的变量 -
PASSWORD: 存储 Wi-Fi 网络密码的变量 -
MQTT_SERVER: 存储 MQTT 代理/服务器的地址(我们将使用broker.mqtt.com) -
MQTT_PORT: 存储 MQTT 端口号的变量 -
pir: 将 GPIO 引脚 26 配置为我们的 PIR 传感器的输入引脚 -
led: 将 GPIO 引脚 21 配置为输出引脚,以控制我们的 LED -
wlan: 初始化一个 WLAN(Wi-Fi)接口,用于以站(客户端)模式连接到 Wi-Fi 网络,允许我们的 MicroPython 设备充当客户端并加入现有的无线网络。此初始化是必需的,因为它允许我们的 Pico W 连接到现有的 Wi-Fi 网络,从而实现网络通信
-
-
在定义我们的变量之后,我们创建一个函数来将我们的 Raspberry Pi Pico W 连接到我们的 Wi-Fi 网络:
def connect_wifi(): wlan.active(True) wlan.connect(SSID, PASSWORD) while not wlan.isconnected(): print('Trying to connect to WiFi...') utime.sleep(5) print('WIFI connection established')在我们的代码中,我们有以下内容:
-
wlan.active(True): 激活 Wi-Fi 接口 -
wlan.connect(SSID, PASSWORD): 使用指定的 SSID(网络名称)和密码(网络密码)启动连接到 Wi-Fi 网络的操作 -
while not wlan.isconnected():: 此循环持续检查设备是否连接到 Wi-Fi 网络:-
print('Trying to connect to WiFi...'): 如果未连接,它将打印一条消息,指示正在进行的连接尝试 -
utime.sleep(5): 它暂停 5 秒钟,然后再检查连接状态
-
-
print('WIFI connection established'): 一旦连接,我们的代码将打印一条消息,确认已成功连接到 Wi-Fi 网络
-
-
在我们的 Wi-Fi 连接功能到位后,我们添加了一个处理从我们的 MQTT 代理接收到的
buzzer消息的功能:def sub_iotalarm(topic, msg): print((topic, msg)) if topic == b'IoTAlarm' and msg == b'buzzer': print("buzzer is detected") activate_buzzer()在我们的代码中,以下情况发生:
-
我们的
sub_iotalarm()函数通过首先打印接收到的主题和消息来处理传入的 MQTT 消息 -
如果主题是
IoTAlarm且消息是buzzer,它将调用activate_buzzer()函数来触发蜂鸣器
-
-
motion_handler()函数负责处理运动检测事件,打印通知,如果 MQTT 客户端已连接,则将motion消息发布到IoTAlarm主题:def motion_handler(pin): print('Motion detected!!') if mqtt_client is not None: mqtt_client.publish(b"IoTAlarm", b"motion") else: print("MQTT client is not connected.")在我们的代码中,以下情况发生:
-
我们的
motion_handler()函数接受一个名为pin的参数。这个参数作为中断处理程序预期参数的占位符是必需的;尽管我们在函数内部没有使用它,但它对于与中断系统保持兼容性是必要的。 -
我们使用
b前缀来表示字符串(IoTAlarm和motion)应该被当作字节对象处理,而不是文本(Unicode)字符串,这在发送二进制数据(如 MQTT 协议)时是必需的。
-
-
connect_mqtt()函数在代码和 MQTT 代理之间建立连接:def connect_mqtt(device_id, callback): global mqtt_client while mqtt_client is None: try: print("Trying to connect to MQTT Server...") mqtt_client = MQTTClient( device_id, MQTT_SERVER, MQTT_PORT) mqtt_client.set_callback(callback) mqtt_client.connect() print('MQTT connection established') except: mqtt_client = None print('Failed to connect to MQTT Server, retrying...') utime.sleep(5)在我们的代码中,以下情况发生:
-
connect_mqtt()函数在代码和 MQTT 服务器之间建立连接,接受两个参数:device_id用于设备识别和callback用于指定处理传入消息的函数(称为回调函数)。device_id参数是分配给每个 MQTT 客户端的唯一标识符,允许我们的 MQTT 代理在网络中区分特定的设备。 -
在
while循环中,我们的代码尝试使用给定的设备 ID 连接到 MQTT 服务器,配置callback函数以处理消息,并成功建立 MQTT 连接。如果连接遇到任何问题,我们的函数将在 5 秒暂停后重试。
-
什么是回调函数?
在我们的物联网报警系统背景下,回调函数被用作 MQTT 通信过程的一部分。在我们的代码中,我们使用sub_iotalarm()函数作为回调,这意味着当从 MQTT 代理接收到相关 MQTT 消息时,sub_iotalarm()函数会自动被调用。在我们的回调函数内部,我们根据接收到的消息定义了特定的操作,例如激活蜂鸣器。
-
最后一种方法控制 LED 的闪烁模式,指示应用程序的连接状态,并在 Raspberry Pi Pico W 独立于计算机运行时启用故障排除:
def connection_status(): while True: if wlan.isconnected(): if mqtt_client is not None: led.on() # Steady on when both WiFi and MQTT connected else: led.on() # Blink every half-second when only WiFi is connected utime.sleep(0.5) led.off() utime.sleep(0.5) else: led.on() # Blink every second when WiFi is not connected utime.sleep(1) led.off() utime.sleep(1)在我们的代码中,我们有以下内容:
-
Steady On:当 Wi-Fi 和 MQTT 都连接时,LED 保持常亮。这发生在 Wi-Fi 连接时(wlan.isconnected()为True)并且存在mqtt_client的值。 -
Fast Blink:当只有 Wi-Fi 连接时(MQTT 客户端为None),LED 每半秒快速闪烁一次。 -
慢闪:当 Wi-Fi 和 MQTT 都没有连接时,LED 灯以 1 秒的间隔缓慢闪烁。
-
-
为了使
connection_status()函数能够独立执行,我们的代码启动了一个新的线程。多线程允许多个任务或函数的并发执行,从而有效地利用 RP2040 的双核处理器同时运行不同的操作:_thread.start_new_thread(connection_status, ()) -
我们的代码随后调用函数,使用唯一的客户端 ID
IoTAlarmSystem连接到 Wi-Fi 和 MQTT 代理:connect_wifi() connect_mqtt("IoTAlarmSystem", sub_iotalarm) -
然后,我们订阅
IoTAlarm消息:mqtt_client.subscribe("IoTAlarm") -
为了启用我们的 PIR 传感器,我们使用以下方式设置其
irq()方法:pir.irq(trigger=machine.Pin.IRQ_RISING, handler=motion_handler)在我们的代码中,以下情况会发生:
- 会调用
motion_handler()函数,向 MQTT 代理发布motion消息。
- 会调用
-
在一个无限循环中,我们等待消息:
while True: mqtt_client.wait_msg()一旦收到消息,它就会被我们之前在代码中定义的回调函数
sub_iotalarm()处理。 -
我们将代码保存为
main.py到我们的 Raspberry Pi Pico W 上,以确保当我们打开电源或重置它时,我们的代码会自动运行。这是 MicroPython 的标准做法。
代码编写完成后,是时候用 MQTTHQ 网络客户端测试它了。
运行我们的警报模块应用程序
我们在 Raspberry Pi Pico W 上运行代码有两种选择。一种是用标准 USB 电源线给 Pico W 供电,这在测试后使用是合适的,但它不会提供访问调试打印消息的功能。第二种选择是在 Thonny 中运行我们的代码。这将使我们能够排除我们遇到的任何问题。为此,我们遵循以下步骤:
-
我们在 Thonny 中选择
main.py标签,确保我们选择的是安装在 Pico W 上的版本,而不是我们的操作系统上的版本。 -
我们点击绿色的运行按钮,在键盘上按F5,或者点击顶部的运行菜单选项,然后点击运行 当前脚本。
-
我们应该在 Shell 中观察到的消息是,我们的代码首先连接到 Wi-Fi 网络,然后连接到 MQTT 服务器。
-
我们也应该观察到我们的 LED 灯相应地闪烁,在连接到 Wi-Fi 网络之前缓慢闪烁,在连接到 Wi-Fi 网络但在连接到 MQTT 服务器之前快速闪烁,一旦建立两个连接,则稳定闪烁。
-
把手放在 PIR 传感器前面,我们应该观察到一条
Motion detected!!消息,然后是一条来自 MQTT 服务器的消息:

图 6.18 – 使用 PIR 传感器检测运动
-
如果我们只收到
Motion detected!!消息,但没有收到来自 MQTT 服务器(代理)的消息,那么我们的应用程序已经失去了与服务器的连接。这也应该通过我们的 LED 灯缓慢闪烁来指示。为了解决这个问题,我们分别使用停止和运行按钮停止并重新启动我们的程序。 -
为了验证我们的代码正在发送 MQTT 消息,我们遵循上一节中使用 MQTTHQ 网络客户端探索 MQTT 基础的步骤。在订阅
IoTAlarm主题后,网络客户端应该在我们的 PIR 传感器被触发时接收到一个motion消息:

图 6.19 – 接收运动消息
- 为了测试我们的蜂鸣器,我们使用 MQTTHQ 网络客户端发布一个
buzzer消息:

图 6.20 – 发布蜂鸣器消息
- 点击发布按钮后,我们应该听到我们的警报声持续 5 秒钟。
现在我们已经创建了我们的第一个 MQTT 应用程序!我们已经从我们的 Raspberry Pi Pico W 向互联网发送了 MQTT 消息。正如我们可以想象的那样,我们应用程序的可能性是巨大的。例如,我们可以通过添加更多传感器来扩展我们的物联网警报系统,例如用于安全的门/窗户接触传感器,或用于家庭气候监测的温度和湿度传感器。在下一章中,我们将添加对警报模块的监控和远程布防,因为我们继续构建我们的物联网家庭安全系统。
为了完成我们的物联网警报模块,我们现在将我们的组件安装到一个定制的 3D 打印外壳中。
构建物联网警报模块外壳
如我们之前所做的那样,我们将把我们的组件安装到一个定制设计的 3D 打印外壳中。图 6**.21展示了我们的警报模块外壳的渲染图,该外壳设计用于容纳 PIR 传感器、蜂鸣器、带有电阻的 LED 和 Raspberry Pi Pico W。
为了紧凑性,我们选择了不带引脚的 Raspberry Pi Pico W,简化了组件安装和焊接。需要注意的是,这个选择是可选的,我们也可以使用带引脚的 Raspberry Pi Pico WH:

图 6.21 – 警报模块定制外壳
在我们继续构建和测试我们的物联网警报模块之前,我们将首先识别部件。
识别定制外壳的部件
在图 6**.22中,我们可以看到组装我们的物联网警报模块定制外壳所需的部件:

图 6.22 – 警报模块部件
让我们逐一分析每个部分:
-
Raspberry Pi Pico W (A): 带引脚版本(如上图所示),或无引脚版本(推荐)。
-
背板 (B): 使用聚乳酸 (PLA), 丙烯腈丁二烯苯乙烯 (ABS), 或聚对苯二甲酸乙二醇酯 Glycol (PETG) 3D 打印而成。
-
钩子 (C): 使用 PLA、ABS、PETG 或工程级树脂(如上图所示)3D 打印而成。对于熔融沉积建模 (FDM)打印机,部件应侧向打印,并带有支撑以增强强度。
-
SFM-27 主动蜂鸣器 (D): 设计用于安装此蜂鸣器的外壳。
-
侧装支架 (E): 用于将报警模块安装在墙上的 3D 打印可选支架。可以使用液体树脂打印机(如图所示)打印 PLA、ABS、PETG 或工程级树脂。
使用 FDM 打印机打印分体支架
SenseHAT 外壳文件中的分体支架(Build Files 文件夹,第一章 仓库)非常适合 FDM 打印。通过分割并打印每个半部分在其侧面,支架获得了显著强度。还提供了一个配套底座。
-
带电阻的 LED (F): 请参阅 第三章 了解构造方法。
-
LED 座 (G): 用于将 LED 固定在外壳中。
-
前壳 (H): 使用 PLA、ABS 或 PETG 3D 打印。
-
HC-SR501 PIR 传感器 (I): 设计用于适配此 PIR 传感器的外壳。
-
6 个 M2 5 毫米螺丝(未显示)。
-
2 个 M4 10 毫米螺栓(未显示)。
-
2 个 M4 螺母(未显示)。
-
4 个 M3 10 毫米螺栓(未显示)。
-
带胶棒的胶枪(未显示)。
什么是工程级树脂?
液体树脂 3D 打印机通过使用紫外线将液体树脂逐层固化来创建形状。标准树脂通常用于小型艺术打印,提供出色的细节,但往往会导致部件脆弱。另一方面,工程树脂(如 Siraya Tech Blu)提供优异的强度,使其适合功能性组件。图 6.22 中的部件 C 和 E 使用标准树脂和 Siraya Tech Tenacious 的 80-20 混合打印,使部件更具柔韧性并减少其脆弱性。
3D 打印部件的文件位于本章 GitHub 仓库的 Build Files 目录中。
既然我们已经确定了构建报警模块外壳所需的部件,让我们将其组装起来。
构建报警模块外壳
图 6.23 展示了构建物联网报警模块外壳的步骤:

图 6.23 – 构建报警模块外壳的步骤
要构建外壳,我们执行以下操作(图 6.23 中表示不同组件的字母在以下步骤中分别提及各自组件的名称):
-
使用两个 M2 5 毫米螺丝,我们将钩子 (C) 螺旋到背板 (B) 上 (图 6.23,步骤 1*)。我们也可以使用环氧树脂胶水来完成这项工作。
-
使用四个 M2 5 毫米螺丝,我们将 Raspberry Pi Pico W 固定在背板 (B) 上,使 USB 端口向下并朝向开口 (图 6.23,步骤 2*)。
-
我们使用两个 M4 10 毫米螺栓和 M4 螺母将蜂鸣器 (D) 固定在前壳 (H) 上 (图 6.23,步骤 3*)。
-
然后我们使用 LED 座 (G) 将带有电阻 (F) 的 LED 安装在前壳 (H) 中 (图 6.23,步骤 4*)。
-
使用胶枪,我们将 PIR 传感器 (I) 固定在前壳 (H) 上。确保 PIR 控制器面向开口。我们还可以使用胶枪加强 LED 的放置 (图 6.23,步骤 5*)。
-
使用电烙铁,我们根据图 6.8中的布线图将组件焊接到 Raspberry Pi Pico W 上(图 6.23,步骤 6)。
-
使用四个 M3 10 毫米螺栓,我们将背板(B)固定到前面板(H)上(图 6.23,步骤 7)。
-
如果尚未安装,我们使用 Thonny 在 Raspberry Pi Pico W 上安装 MicroPython 和我们的客户端代码。
对于我们的设计,我们使用 micro-USB 端口为我们的 Raspberry Pi Pico W 供电并访问安装的程序。这使我们能够轻松更新我们的客户端软件,例如更改 Wi-Fi 网络和密码或使用的 MQTT 主题。
此外,我们的外壳还提供了对 PIR 传感器上控制器的访问,以便我们可以控制灵敏度和关闭时间。
HC-SR501 PIR 传感器上的控制是什么?
HC-SR501 PIR 传感器配备了两个可调节的控制:灵敏度控制,通过顺时针旋转增加灵敏度以微调传感器对运动的响应,逆时针旋转则降低灵敏度;时间延迟控制,调节检测到运动后输出信号持续的时间,顺时针旋转延长信号持续时间,逆时针旋转缩短信号持续时间。这些控制并排放置,可以使用小螺丝刀进行调整。
要操作我们的物联网报警模块,我们只需将一根 micro-USB 线从我们的 Raspberry Pi Pico W 连接到标准 USB 充电器。LED 灯最初应该快速闪烁,表示正在建立 Wi-Fi 连接,然后程序连接到 MQTT 代理时,LED 灯会慢速闪烁,最后,LED 灯会保持常亮,表示我们的模块已准备好使用。如果我们决定不打印图 6.22 E中的支架,我们可以将我们的模块安装在我们选择的 GoPro 相机支架上:

图 6.24 – 安装在 GoPro 相机支架上的报警模块
要测试我们的物联网报警模块,我们连接到mqtthq.com网络客户端并订阅IoTAlarm主题。将手放在 PIR 传感器上,我们应该在客户端看到motion消息出现。发布IoTAlarm主题并发送buzzer消息应该激活我们的蜂鸣器 5 秒钟。
我们刚刚构建了我们第一个基于 MQTT 的物联网报警模块,封装在一个物理外壳中,能够通过 MQTT 消息远程感应运动并激活报警。凭借其内置的 GoPro 挂钩,我们可以在任何有 Wi-Fi 连接的地方轻松安装我们的物联网报警模块。
摘要
在本章中,我们探讨了 MQTT 并使用它创建了一个基于 MQTT 的物联网报警模块。我们介绍了令人惊叹的 Raspberry Pi Pico W,这是一个补充我们的 Raspberry Pi 的微控制器。我们首先理解了 MQTT 的发布-订阅模型,它使得连接设备之间的高效和选择性通信成为可能。此外,我们还考察了在最大化 Raspberry Pi Pico 双核处理器利用率方面线程的重要性。
我们编写了连接到 Wi-Fi 和 MQTT 服务器的代码,处理运动检测和激活报警组件。我们学习了如何使用回调函数来处理 MQTT 消息。
此外,我们还涵盖了在 Raspberry Pi Pico W 上保存和运行我们的代码,使其成为一个独立的物联网报警系统。我们还 3D 打印了一个定制的盒子来容纳 PIR 传感器、LED 灯、蜂鸣器和 Raspberry Pi Pico W。
我们的基于 MQTT 的物联网报警模块现在已经完成,我们准备在扩展我们的物联网家庭安全系统功能的同时,探索进一步的增强。在下一章中,我们将构建一个物联网按钮,我们将使用它来控制我们的报警模块。
第七章:构建物联网按钮
在本章中,我们将构建我们物联网家庭安全系统的一个基本组件:一个物联网按钮。我们将使用不同的硬件基础构建这个按钮的两个版本——M5Stack ATOM Matrix 和 Raspberry Pi Pico W。
M5Stack ATOM Matrix 是一款紧凑型 ESP32 基于的微控制器,内置点阵显示屏,也用作触摸按钮,这种设计选择显著减小了其尺寸,使其成为物联网项目的超紧凑型解决方案。我们熟悉的 Raspberry Pi Pico W 是一个受欢迎的微控制器选项,以其多功能性和与各种外部外围设备的无缝集成而著称。
在我们创建更高级的版本之前,我们将从 ATOM Matrix 开始构建一个简单的物联网按钮:

图 7.1 – 物联网按钮架构
在我们的设计中,物联网警报模块(来自第六章)、Raspberry Pi Pico W 和 M5Stack ATOM Matrix 都使用 IoTAlarm 主题进行通信,如图 7.1 中的黑色框体上的白色文字所示。在我们的图形中,我们将主题移动到云中,因为我们现在熟悉 MQTT 协议,不再需要用视觉表示它。消息以白色框体和黑色文字表示,虽然每个设备都可以通过此主题读取和发送任何消息,但它们被配置为过滤相关消息并传输特定于设备的消息。在图 7.1 中,我们看到每个设备处理的特定消息以及我们两个物联网按钮微控制器实现的复杂性差异。
我们将使用 CloudAMQP 进行 MQTT 通信,与我们的第二个物联网按钮进行通信,确保通过其可扩展的消息服务实现高效可靠的数据传输。这提高了按钮的性能和实时数据交换的可靠性。
在我们导航这些按钮的创建过程时,我们在熟悉不同硬件平台的同时,加深了对 MQTT 通信和 Python 编程的理解。通过为我们的项目使用两个不同的基础,我们为自己提供了选择最佳平台以用于即将到来的物联网应用的洞察力。
在本章中,我们将涵盖以下主要内容:
-
介绍物联网按钮
-
使用 M5Stack ATOM Matrix 创建我们的物联网按钮
-
使用 Raspberry Pi Pico W 优化我们的物联网按钮
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
Python 编程的中级知识
-
1 个 M5Stack ATOM Matrix
-
1 个 Raspberry Pi Pico WH(带引脚)用于与面包板一起使用
-
1 个 Raspberry Pi Pico W(无引脚)将安装在一个可选的 3D 打印外壳中
-
1 个 24 毫米街机式按钮
-
1 个迷你 SPST 开关
-
1 个 0.96 英寸 OLED 屏幕
-
1 x LED 与 220 欧姆电阻连接(如前所述,在 第三章 中使用)
-
1 x SFM-27 主动蜂鸣器
-
12 x M2 5 毫米螺丝
-
2 x M4 20 毫米螺栓
-
2 x M4 螺母
-
4 x M3 10 毫米螺栓
-
1 x M5 25 毫米螺栓
-
1 x M5 螺母
-
1 x LED 支架
-
热胶枪
-
有 3D 打印机打印可选外壳的访问权限
本章的代码可以在以下位置找到:
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter7
介绍物联网按钮
在动画电影 WALL-E(2008 年)的结尾,麦考瑞上校与 AI 自动驾驶系统对抗,通过按下一个大蓝色按钮来重新控制 Axiom 宇宙飞船。这导致庞大的 Axiom 在返回地球时启动了超光速跳跃。在科幻小说中,单个按钮通常被视为创新的灯塔,一个强调关键时刻的戏剧性工具。在 WALL-E 的案例中,这个按钮超越了简单的单一动作结果,比如发出蜂鸣声,而是承载着人类未来的重量,触发了一系列行动,最终导致了人类和地球的救赎和重生。
在本章中,我们将构建自己的强大按钮,虽然它没有像 WALL-E 中的蓝色按钮那样的力量。然而,我们的按钮将带我们进入物联网按钮的世界,在这里,单个动作可以触发一系列自动化任务——在我们的案例中,是与我们在上一章中构建的物联网警报模块进行交互。
利用物联网按钮
物联网按钮在物联网领域中处于核心地位,借助互联网跨越距离,在全球范围内启动动作。考虑一下从多伦多按下按钮激活孟买机器的可能性,或者远程触发警报以帮助宿舍学生按时上课。这种全球范围将简单的按钮变成了强大的工具,使日常任务更加高效和互联。
以下是一些我们可以用物联网按钮做的事情:
-
智能家居控制按钮:这个按钮可以通过简单的配置命令控制各种家用电器和系统——单次或双击可以控制灯光
-
订单按钮:在零售环境中特别有用,这个按钮可以促进快速订购或重新订购特定产品,从而提高业务效率
-
反馈按钮:这些按钮可以安装在企业或服务环境中,以收集用户或客户的即时反馈,有助于保持高标准的服务
-
会议室预订按钮:在办公空间中,这些按钮可以帮助顺利预订会议室,防止预订冲突并提高效率
-
智能农业:这些按钮可以简化农业流程,例如立即灌溉农田区域或自动释放牲畜饲料
在本章中,我们将使用我们的物联网按钮与物联网报警模块进行交互。为此,我们将使用 M5Stack ATOM Matrix 构建一个简单的按钮,以及使用我们的 Raspberry Pi Pico W 构建一个更复杂的按钮。
探索物联网按钮开发中的各种技术
在物联网快速发展的领域中,技术的选择可以极大地影响我们创建的设备的功能性和适应性。在本章中,我们采用这种方法,因为我们使用了两个不同但有效的平台——M5Stack ATOM Matrix 和 Raspberry Pi Pico W——来构建不同复杂程度的物联网按钮。
我们从 M5Stack ATOM Matrix 开始构建我们的初始按钮。这款微控制器以其紧凑性而著称,它集成了点阵屏幕,该屏幕作为触摸按钮使用。其简单的设计不仅便于组装,还支持简单的按钮解决方案,非常适合我们的第一个物联网按钮(见图 7.2中的B):

图 7.2 – Raspberry Pi Pico W 和 M5Stack ATOM Matrix
在从 ATOM Matrix 项目中学到的知识的基础上,我们接下来将利用 Raspberry Pi Pico W 的能力(见图 7.2中的A)。我们因它的多功能性和与各种外围设备的兼容性而了解这个平台。这为我们提供了更多按钮开发的定制化。Raspberry Pi Pico W 使我们能够创建一个比 M5Stack ATOM Matrix 具有更高功能性的按钮。
与两种不同的技术基础一起工作不仅拓宽了我们的理解,而且鼓励我们对物联网项目开发采取灵活的方法。这种从简单到更高级按钮的进步旨在帮助我们稳步积累知识,为我们选择未来物联网项目的正确平台提供洞察力。
让我们开始吧。
使用 M5Stack ATOM Matrix 创建我们的物联网按钮
在图 7.1中,我们展示了右侧的 ATOM Matrix,它接收一个运动输入消息并发出一个蜂鸣器输出消息。这些消息分别与物联网报警模块上的被动红外(PIR)传感器检测和蜂鸣器激活相关联。利用其集成的点阵屏幕(也用作触摸按钮),我们将使用 M5Stack ATOM Matrix 来创建我们的第一个物联网按钮。
在我们开始设置和编程 ATOM Matrix 之前,让我们花点时间熟悉 M5Stack 提供的产品系列。
探索 M5Stack 设备
M5Stack 以其可堆叠的开发套件而闻名,适合爱好者和专业人士。这些套件基于 ESP32 微控制器,提供了功能和可扩展性,这对于物联网、人工智能和机器人项目至关重要。M5Stack 的模块提供了易于集成的各种功能,并配备了用户友好的开发环境。
在图 7.3 中,我们看到了各种 M5Stack 设备的照片:

图 7.3 – M5Stack 设备
让我们看看这些设备的功能:
-
M5Stack Basic (A): M5Stack Basic 是一款适用于物联网应用的全面中央控制器,由 Espressif ESP32 芯片组供电,内置两个 Xtensa® 32 位 LX6 微处理器,峰值频率为 240 MHz。它提供了一系列丰富的开发接口,包括模拟数字转换器 (ADC)、数字模拟转换器 (DAC)和集成电路间 (I2C),以及 15 个输入/输出(IO)引脚。它配备了一块 2.0 英寸的 HD 平面切换 (IPS)显示屏,并配有扬声器和 microSD 卡槽。
-
M5StickC PLUS (B): M5StickC PLUS 由 ESP32-PICO-D4 芯片供电,内置 Wi-Fi 功能。它配备了一块大型的 1.14 英寸屏幕,分辨率为 135 x 240 px。该板集成了红外线、实时时钟 (RTC)、麦克风和 LED 灯,并配备了强大的 120 mAh 电池。它支持 HAT 和 Unit 产品系列。
-
M5Stack Unit LoRaWAN915 (C): M5Stack Unit LoRaWAN915 是一款专为 915 MHz 频率通信设计的LoRaWAN(简称长距离广域网络)模块,利用 ASR6501 芯片组实现长距离连接,同时保持低功耗和高灵敏度。
-
ENV III HAT (D): ENV III HAT 是一款多功能的环保传感器,兼容 M5StickC 系列,内置 SHT30 和 QMP6988 以测量温度、湿度和大气压力。
-
ATOM Matrix (E): ATOM Matrix 是 M5Stack 最紧凑的开发板,尺寸为 2424 mm,为紧凑型嵌入式设备项目提供了广泛的 GPIO 引脚。由 ESP32-PICO-D4 芯片供电,它集成了 Wi-Fi 技术,并配备了 4 MB 的串行外围接口 (SPI)闪存。该板具有一个 55 RGB LED 矩阵、一个红外 LED、一个可编程按钮以提供额外的输入支持,以及内置的惯性测量单元 (IMU)传感器(MPU6886)。
对于我们的第一个物联网按钮,我们将使用 ATOM Matrix。虽然我们本可以选择 Basic、M5StickC PLUS 或更新的 ATOMS3(未展示),因为这些设备都提供了按钮和屏幕以供反馈,但我们选择了 ATOM Matrix,因为它提供了独特的小巧和简洁结合,非常适合这个入门级项目。此外,其集成的点阵屏幕,作为触摸按钮的双重功能,为用户提供了更直观和互动的体验。
M5Stack 提供了直观的工具来设置和编程我们的 ATOM Matrix。我们将从烧录工具开始配置我们的 Matrix。
将固件烧录到我们的 ATOM Matrix
M5Burner 允许我们将固件烧录到我们的 M5Stack 设备上。这个工具简化了我们的烧录过程。
要使用此工具,我们执行以下操作:
-
我们使用此 URL 从 M5Stack 网站下载安装文件:
docs.m5stack.com/en/download。 -
对于我们的项目,我们将下载并安装烧录工具的 Windows 版本。安装完成后,我们将运行程序并点击左侧的ATOM标签页,我们应该看到以下屏幕:

图 7.4 – M5Burner ATOM 屏幕
-
UIFlow_MATRIX固件是为 ATOM Matrix 设计的,它支持拖放图形编程,这可以转换为 MicroPython。它具有内置的库,并提供无线编程的空中传输(OTA)更新。我们点击下载按钮将固件下载到我们的本地计算机。 -
固件下载完成后,我们点击烧录按钮(以前称为下载按钮)开始将UIFlow_MATRIX固件烧录到我们的 ATOM Matrix。我们应该看到一个请求我们 Wi-Fi 信息的对话框。
-
通过点击右上角的蓝色按钮,我们可以自动填写我们电脑的 Wi-Fi 详情或手动输入。输入后,我们点击下一步继续。接下来应该看到烧录屏幕。
-
我们选择 ATOM Matrix 连接的端口,并点击开始按钮以启动固件烧录。然后,一个进度屏幕将显示烧录的当前状态。
-
成功完成后,我们点击绿色的烧录成功,点击此处 返回按钮:

图 7.5 – 烧录成功
我们现在已成功将UIFlow_MATRIX固件安装到我们的 ATOM Matrix 上。我们应该注意到,我们设备上的点阵屏幕会闪烁绿色。这个固件包含专用的库,我们将使用这些库来创建我们的第一个物联网按钮。
固件安装后,现在是时候配置我们的 ATOM Matrix,以便我们可以开始编程它。
配置 ATOM Matrix 以进行编程
要编程我们的 ATOM Matrix,我们将使用 M5Stack 的 UIFlow,特别是它的 Python 环境。UIFlow 作为一个在线 IDE,通过 API 密钥连接到我们的设备。为了成功开发,必须检索此密钥并正确配置我们的 ATOM Matrix。
要配置我们的 ATOM Matrix,我们执行以下操作:
-
在M5Burner窗口中,我们点击ATOM标签页。
-
我们点击UIFlow_Matrix部分中的配置按钮:

图 7.6 – UIFlow Matrix 配置按钮
-
这应该打开 UIFlow 配置 对话框,并且我们的 ATOM 矩阵连接的端口应该显示出来。我们点击 加载 以继续到下一个屏幕。
-
这将带我们到下一个屏幕,UIFlow 配置:

图 7.7 – 主要 UIFlow 配置屏幕
-
使用 图 7.7 作为参考,我们将关注由方框突出显示的参数。从 COM 开始,它应该设置为我们的 ATOM 矩阵连接的端口。
-
APIKEY 参数作为 UIFlow IDE 和我们的设备之间的连接桥梁。我们注意这个密钥并保持它易于访问。
-
启动模式 决定了设备的启动行为。我们将其配置为 互联网模式 以与 UIFlow IDE 连接。在编程我们的 ATOM 矩阵后,此模式将自动切换到 应用模式。在此之后,要重新连接到 IDE,我们需要重新访问 M5Burner 并将其重置为 互联网模式。
-
如果尚未设置正确的值,我们设置 WIFI SSID 和 WIFI 密码 参数。
-
我们点击蓝色 保存 按钮将参数保存到我们的 ATOM 矩阵中。
在配置好我们的 ATOM 矩阵后,我们就可以开始编程并将设备变成我们的第一个功能性的 IoT 按钮。
将我们的 ATOM 矩阵转换为 IoT 按钮
我们将使用 M5Stack 的 UIFlow 在线 IDE 进行开发,因为它为 M5Stack 设备提供了一个直接的编码平台。我们将使用从设备配置中记录的 API 密钥来连接到 IDE。
要将 MicroPython 代码添加到我们的 ATOM 矩阵中,我们执行以下操作:
-
在一个支持互联网的浏览器中,我们导航到以下 URL:
flow.m5stack.com。 -
我们将看到一个屏幕,可以选择
UIFlow1.0或UIFlow2.0。由于我们使用的是 ATOM 矩阵,我们选择 UIFlow1.0 并点击 确认。 -
在 IDE 中,我们点击 </> Python 选项卡,这样我们就可以用 MicroPython 编程我们的 ATOM 矩阵:

图 7.8 – UIFlow IDE
- 要将我们的 ATOM 矩阵链接到 UIFlow,我们点击屏幕左下角的 Api key 标签以打开 设置 屏幕:

图 7.9 – 设置屏幕
-
我们输入设备的 API 密钥并点击 确定 以将我们的 ATOM 矩阵连接到 UIFlow IDE。
-
在代码编辑器中,我们用以下代码覆盖了现有的代码:
from m5stack import * from m5ui import * from uiflow import * from m5mqtt import M5mqtt import time rgb.setColorAll(0x000000) def cb_IoTAlarm(topic_data): if topic_data == 'motion': rgb.setColorAll(0xff0000) wait(5) rgb.setColorAll(0x00cccc) def buttonA_pressFor(): m5mqtt.publish(str('IoTAlarm'), str('buzzer'), 0) btnA.pressFor(1, buttonA_pressFor) m5mqtt = M5mqtt( 'IoTMatrix', 'broker.mqtthq.com', 1883, '', '', 300 ) m5mqtt.subscribe(str('IoTAlarm'), cb_IoTAlarm) rgb.setColorAll(0x00cccc) m5mqtt.start()在将代码下载到我们的 ATOM 矩阵之前,让我们将其分解。我们首先从导入开始:
-
from m5stack import *:从 M5Stack 库导入所有函数和类。 -
from m5ui import *:导入所有与 UI 相关的函数和类,用于 M5Stack。 -
from uiflow import *:导入所有 UIFlow 特定的函数和类。 -
from m5mqtt import M5mqtt:导入M5mqtt类,允许进行 MQTT 通信。 -
import time:导入标准的 Pythontime库。
-
-
然后,我们使用以下命令将屏幕设置为黑色:
cb_IoTAlarm(topic_data). This does the following:1. Checks if the incoming message under the `IoTAlarm` topic is `motion`.2. If `motion` is received, the RGB LED color is set to red (`0xff0000`) for 5 seconds.3. Afterward, the RGB LED color is reset to a cyan color (`0x00cccc`). -
然后,我们定义一个函数来扫描按钮
A(屏幕)的按下,并称它为buttonA_pressFor()。在此方法中,当按钮
A按下 1 秒钟时,我们使用IoTAlarm主题和buzzer有效负载发布一条消息。 -
然后,我们使用给定的参数创建了一个名为
m5mqtt的M5mqtt类的实例:-
设备名称设置为
IoTMatrix。 -
MQTT 代理地址设置为
broker.mqtthq.com。 -
MQTT 端口设置为
1883。 -
我们为用户名和密码提供空字符串,因为不需要它们。
-
我们将 MQTT 保持活动时间设置为
300秒。
-
-
我们的代码随后订阅了
IoTAlarm主题,并使用以下代码设置了回调函数:m5mqtt.subscribe(str('IoTAlarm'), cb_IoTAlarm) -
然后,我们将 RGB LED 矩阵颜色设置为青色作为初始状态:
rgb.setColorAll(0x00cccc) -
最后一行启动 MQTT 客户端,使其能够发送和接收消息:
m5mqtt.start() -
将代码放置到位后,我们通过点击屏幕右下角的蓝色下载按钮将其下载到 ATOM Matrix。
在我们的 ATOM Matrix 上加载代码后,我们现在可以开始测试它。
测试我们的物联网按钮
在我们的初始测试中,我们将使用之前在第六章中介绍的 MQTTHQ 网络客户端,在测试我们的物联网按钮在物联网警报模块上之前。
要做到这一点,我们执行以下操作:
-
在浏览器中,我们导航到以下 URL:
mqtthq.com/client。 -
在
IoTAlarm。 -
在
运动消息中,点击橙色发布按钮。 -
在我们的 ATOM Matrix 上,我们应该观察到我们的屏幕在 5 秒钟后变回初始颜色。
-
在
IoTAlarm主题中。 -
在我们的 ATOM Matrix 上,我们按住并保持主按钮(屏幕)1 秒钟,然后释放。
-
在
buzzer消息中。 -
在 MQTTHQ 测试成功完成后,我们现在可以开始测试我们在物联网警报模块上的物联网按钮。使用微型 USB 线,我们将我们在第六章中创建的物联网警报模块插入到 USB 电源砖中。
-
初始化后,我们在 PIR 传感器前挥动手,观察 ATOM Matrix 上的屏幕在 5 秒钟内变为红色。
-
按住并保持 ATOM Matrix 上的主按钮(屏幕)一秒钟,然后释放,应该会在我们的物联网警报模块上触发蜂鸣器。
祝贺我们刚刚使用 M5Stack ATOM Matrix 创建了我们的第一个物联网按钮!由于我们的物联网警报模块和物联网按钮之间的连接是通过互联网进行的,因此我们可以将这两个设备放置在世界上的任何地方,并使它们进行通信。
尽管我们的 ATOM Matrix 具有方便的形状,就像所有 M5Stack 控制器一样,但我们确实有一个定制的支架,我们可以将其安装在上面(图 7**.10)。3D 打印机文件位于本章 GitHub 存储库的Build Files文件夹中:

图 7.10 – M5Stack ATOM Matrix 底座
要使用底座,我们将 ATOM Matrix(见图 7.10中的B)放入底座的杯形部分(见图 7.10中的A),USB-C 端口朝向底座的底部。底座有一个后孔,可以容纳一个 M2 5 毫米螺丝(未显示)以固定 ATOM Matrix,它后面有一个 2 毫米的安装孔。底座设计用来容纳一个 90 度角的 USB-C 电缆或适配器。此外,它还可以覆盖一个开口以隐藏 USB-C 电缆,使其适用于天花板或架子。
在我们的第一个物联网按钮完成之后,我们现在准备构建按钮的更复杂第二个版本。
使用 Raspberry Pi Pico W 改进我们的物联网按钮
随着我们的物联网警报系统变得越来越复杂,公共 MQTTHQ 服务器的局限性变得越来越明显。鉴于它是一个公共平台,其可靠性可能不一致。转向一个可靠、私有的服务器将显著提高我们的开发过程和系统可靠性。
在本节中,我们将使用 Raspberry Pi Pico W、蜂鸣器、街机风格的按钮、开关、LED 和 OLED 屏幕(图 7.11)来构建一个改进的物联网按钮。通过迁移到使用 CloudAMQP 的私有 MQTT 服务器,我们提高了项目的可靠性和效率。

图 7.11 – 改进的物联网按钮
虽然使用私有服务器是可选的,但它相对于继续使用公共 MQTTHQ 服务器来说是一个显著的升级。本节中的代码仍然可以使用公共 MQTTHQ 服务器(需要配置更改);然而,通过选择 CloudAMQP,我们将提高我们物联网警报系统的可靠性和安全性。
我们将首先使用 CloudAMQP 设置一个 MQTT 实例。
设置 CloudAMQP 实例
CloudAMQP 是一个针对物联网设备和应用程序优化的托管 MQTT 服务。它确保了具有 WebSocket 和保留消息等功能的可靠、实时消息传递。凭借其直观的界面,CloudAMQP 既适合爱好者也适合企业,是我们物联网警报系统的绝佳选择。
我们可以在此查看服务的定价 – www.cloudamqp.com/plans.xhtml
对于我们的需求,免费的小狐猴服务就足够了。在设置账户后,我们创建一个实例用于我们的项目。为此,我们执行以下操作:
-
登录我们的账户将显示实例页面。要创建一个新的实例,我们点击绿色的创建新实例按钮。
-
这将带我们到
IoTProgrammingProjects,将我们的计划设置为Little Lemur,并留空标签字段。我们点击绿色的选择区域按钮进入下一屏幕。 -
这将带我们到AWS下的
CA-Central Canada-1(Canada)。我们点击绿色的审查按钮进入下一屏幕。 -
在确认新实例界面,我们审查我们的实例设置。
-
要创建我们的实例
IoTProgrammingProjects,我们点击绿色的创建 实例按钮。 -
要获取更多关于我们实例的详细信息,我们点击
IoTAlarmSystem链接。 -
点击链接提供了我们的实例详情,我们将使用这些详情将我们的应用程序连接到 MQTT 服务器:

图 7.12 – MQTT 连接详情
在创建我们的实例并获取详细信息后,我们现在可以更新警报模块代码,从第六章更新到与新的 MQTT 服务器集成并增强其功能。
修改我们的警报模块代码
我们将修改第六章中的代码,以便我们的警报模块与更先进的第二个物联网按钮一起工作。我们的修订代码从公共 MQTTHQ 服务器转移到更可靠的 CloudAMQP 私有服务器。
为了支持这一点,我们的新代码添加了USERNAME、MQTT_PASSWORD和DEVICE_ID认证参数,以增强安全性。另一个重大更新是ARMED模式,它允许我们通过 MQTT 激活或解除系统。当检测到运动时,修改后的motion_handler()函数会考虑ARMED状态,发出蜂鸣声。
最后,我们的修订代码改变了 LED 反馈系统。除了显示 Wi-Fi 和 MQTT 连接状态外,LED 还会通过非常缓慢的闪烁来指示警报是否被激活。
我们物联网警报模块代码的新版本可以在本章 GitHub 仓库的CloudAMQP文件夹中找到。要将代码安装到物联网警报模块,我们遵循第六章中概述的步骤。
构建我们的 Raspberry Pi Pico W 物联网按钮
第二个物联网按钮在硬件和软件方面都有显著的进步,因为我们使用了 Raspberry Pi Pico W。Raspberry Pi Pico W 和 M5Stack ATOM Matrix ESP32 都作为令人印象深刻的微控制器脱颖而出。Pico W 因其双核 ARM Cortex-M0+处理器和 Wi-Fi 功能而引人注目,而 ATOM Matrix 则通过其 ESP32 芯片带来了 Wi-Fi 和蓝牙。然而,考虑到我们的第二个物联网按钮项目更重视计算能力和 Wi-Fi,我们将选择 Raspberry Pi Pico W。
添加 OLED 屏幕为我们提供了警报的状态,同时也充当了我们的 MQTT 消息的监控器。从公共 MQTTHQ 服务器切换到 CloudAMQP 的私有服务器在软件上提高了可靠性和安全性。这一举措降低了与公共服务器相关的风险。
查看我们的 Pico W 物联网按钮的组件
对于我们物联网按钮的第二版,我们将使用街机风格的按钮来发送消息以启动警报模块。OLED 屏幕将显示从物联网警报模块发送回来的 MQTT 消息。从物联网警报模块发送的 buzzer 消息将在构成物联网按钮第二版组装的活性蜂鸣器上启动旋律。要解除警报模块的警报,我们只需将开关从当前位置切换即可。
在 图 7.13 中,我们看到组成我们的 Raspberry Pi Pico W 物联网按钮的组件:

图 7.13 – 物联网按钮 V2 电路连接到 Pico GPIO 扩展器
以下是组件:
-
A:220 欧姆电阻的单色 LED
-
B:SM-127 活性蜂鸣器
-
C:0.96 英寸 OLED 屏幕
-
D:24 毫米街机按钮
-
E:Raspberry Pi Pico 的 GPIO 扩展板(可选)
-
F:用于开发用途的 Raspberry Pi Pico WH
-
G:单刀单掷开关
对于开发,我们将使用 Raspberry Pi Pico WH,但在安装到定制外壳时将切换到无头版本。
使用 GPIO 扩展器
GPIO 扩展器的使用(见 图 7.13 中的 E)是可选的。要使用扩展器,需要女性跳线连接。使用 GPIO 扩展器的好处是,由于其女性跳线连接,可以轻松地将组件过渡到未来项目中使用 Pico WH 的安装。
我们将通过检查标准面包板上的接线图来开始构建 Raspberry Pi Pico W 物联网按钮。
连接我们的 Raspberry Pi Pico W 物联网按钮
在 图 7.14 中,我们可以看到 Raspberry Pi Pico W 物联网按钮的接线图。我们将使用标准微型 USB 线连接到 Pico WH 的 USB 端口以供电。Pico W 的 3.3 V 引脚用于为面包板的轨道供电:

图 7.14 – Raspberry Pi Pico W 物联网按钮接线图
现在我们已经设置好接线,我们将进入编码阶段。我们将首先加载我们代码所需的必要软件包。
设置程序所需的软件包
运行我们 Raspberry Pi Pico W 物联网按钮代码所需的软件包是 micropython-umqtt.simple 软件包和 micropython-ssd1306 软件包。要将软件包加载到我们的 Raspberry Pi Pico W 上,我们执行以下操作:
-
使用 Thonny IDE,我们点击 工具 | 管理软件包。
-
在搜索框中,我们输入我们想要搜索的软件包名称,然后点击 在 PyPI 上搜索 按钮。
-
然后我们通过点击 安装 按钮来安装软件包。
如果加载软件包时出现错误,该怎么办?
在加载包时出现错误的情况下,我们可能只需将 GitHub 仓库中的library文件夹复制到我们的 Pico W 中。该文件夹可以在此章节的仓库中找到,位于Second IoT Button/library-files-from-pico-w。参见图 7.15以了解 Raspberry Pi Pico W 的文件结构。
- 在继续编写代码之前,我们应该验证我们的 Pico W 上的文件结构如下:

图 7.15 – Raspberry Pi Pico W 库结构
在安装了我们的包之后,现在是时候编写我们的代码了。我们将从控制蜂鸣器的代码开始,因为这将包含在主代码之外的一个单独的文件中。
激活蜂鸣器
在图 7.1中,我们看到来自物联网警报模块的buzzer消息。在我们重写的物联网警报模块代码中,当警报被激活并且 PIR 传感器检测到运动时,会发送一个buzzer消息。我们将在我们的 Raspberry Pi Pico W 物联网按钮中使用此消息来激活其警报,尽管我们将使用它通过其扬声器播放旋律。像物联网警报模块代码一样,我们将使用单独的文件来激活蜂鸣器。
要做到这一点,我们执行以下操作:
-
我们将 Raspberry Pi Pico W 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或另一个操作系统来做这件事。
-
然后,我们通过从屏幕的右下角选择它来激活 Pico W 上的 MicroPython 环境。
-
在一个新标签中,我们输入以下代码:
from machine import Pin, PWM import utime BUZZER_PIN = 16 buzzer = PWM(Pin(BUZZER_PIN)) def play_notes( notes = [ (330, 0.5), # E4 for 0.5 seconds (262, 0.5), # C4 for 0.5 seconds (330, 0.5), # E4 for 0.5 seconds (392, 0.5), # G4 for 0.5 seconds (349, 0.5), # F4 for 0.5 seconds (262, 1), # C4 for 0.5 seconds ] ): for freq, duration in notes: buzzer.freq(freq) buzzer.duty_u16(32768) utime.sleep(duration) buzzer.duty_u16(0)在我们测试代码之前,让我们将其分解:
-
我们首先从
machine模块导入Pin和PWM,以及utime模块。 -
我们将
BUZZER_PIN常量设置为引脚号16,这对应于我们的布线图(图 7.14)。 -
然后,我们使用定义的引脚通过
PWM类初始化buzzer。 -
我们的
play_notes()函数有一个默认参数notes,它是一个元组列表。每个元组代表赫兹频率(例如E4、C4等)和持续时间(以秒为单位)。 -
对于
notes列表中的每个频率-持续时间对,我们执行以下操作:-
我们将蜂鸣器的频率设置为指定的频率。
-
我们以 50%的占空比(
duty_u16(32768))激活蜂鸣器。这产生一个方波,定义了蜂鸣器发出的声音的特性。 -
我们使用
utime.sleep(duration)等待指定的时间。
-
-
在播放所有音符后,我们将蜂鸣器关闭(通过将其占空比设置为 0)。
-
-
要保存文件,我们点击
buzzer.py到我们的 Raspberry Pi Pico W。 -
为了测试我们的代码,我们使用新蜂鸣器脚本中的
play_notes()函数:

图 7.16 – 导入 play_notes()函数
- 要激活我们的蜂鸣器,我们只需调用该函数并按Enter键:

图 7.17 – 运行 play_notes() 函数
- 我们应该听到蜂鸣器播放其默认旋律。为了进一步测试我们的函数,让我们向其发送
[(262, 1),(330, 2),(392, 0.5),(262, 1)]音符:

图 7.18 – 使用 play_notes() 函数播放新旋律
- 我们应该注意到蜂鸣器播放了不同的旋律。
在我们的蜂鸣器代码就绪后,现在是时候专注于为主板供电的 Raspberry Pi Pico W IoT 按钮编写主要代码了。
编写我们 IoT 按钮的主要功能代码
在完成蜂鸣器脚本后,我们准备好开发增强型 IoT 按钮的主要代码。这个新版本可以启动 IoT 报警模块,并具有显示屏。屏幕指示报警模块的状态(要么是已启动,要么是解除),并显示来自 CloudAMQP 服务器的最新 MQTT 消息。
当 IoT 报警模块触发时,我们的 IoT 按钮的蜂鸣器提供可听反馈。它在接收到 buzzer 消息时激活。我们使用 IoT 按钮组件上的开关作为切换,以解除 IoT 报警模块的警报。
要为我们的增强型 IoT 按钮创建代码,我们执行以下操作:
-
我们将 Raspberry Pi Pico W 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来完成此操作。
-
然后,我们在屏幕的右下角选择 MicroPython 环境以激活 Pico W。
-
我们将从导入开始。在新标签页中,我们输入以下代码:
from machine import Pin, I2C import utime import network from umqtt.simple import MQTTClient from buzzer import play_notes import ssd1306 import _thread -
我们定义变量,其值来自我们的 CloudAMQP 账户(图 7**.12):
SSID = "MyWiFiNetwork" WIFI_PASSWORD = "xxxxxxxxxxxxx" led = machine.Pin(15, machine.Pin.OUT) button = Pin(0, Pin.IN, Pin.PULL_UP) switch = Pin(1, Pin.IN, Pin.PULL_UP) previous_switch_state = switch.value() MQTT_SERVER = "codfish.rmq.cloudamqp.com" MQTT_PORT = 1883 USERNAME = "xxxxxx" PASSWORD = "xxxxxx" DEVICE_ID = "IoTAlarmSystem" last_message = "" i2c = I2C(0, scl=Pin(9), sda=Pin(8)) display = ssd1306.SSD1306_I2C(128, 64, i2c) mqtt_client = None -
on_message_received()方法作为我们的 MQTT 客户端的回调。通过使用global关键字与last_message,我们确保此变量的更新在代码的全球范围内反映:def on_message_received(topic, msg): global last_message print("Received:", topic, msg) if topic == b"IoTAlarm": last_message = msg.decode() if msg == b"buzzer": play_notes() -
connect_wifi()函数初始化并激活 Pico W 的 Wi-Fi。如果未连接,它尝试使用预定义的 SSID 和密码加入网络。同时,LED 闪烁以指示连接过程。成功连接后,LED 保持点亮,并显示设备的 IP 地址:def connect_wifi(): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print('Connecting to WiFi...') wlan.connect(SSID, WIFI_PASSWORD) while not wlan.isconnected(): led.on() utime.sleep(0.5) led.off() utime.sleep(0.5) led.on() print('WiFi connected, IP:', wlan.ifconfig()[0]) -
connect_mqtt()函数尝试使用预定义的服务器详情建立 MQTT 连接。如果成功,它设置消息回调并订阅IoTAlarm主题。如果连接失败,函数将等待 5 秒并重试:def connect_mqtt(): global mqtt_client while mqtt_client is None: try: print('Trying to connect to MQTT Server...') mqtt_client = MQTTClient( DEVICE_ID, MQTT_SERVER, MQTT_PORT, USERNAME, PASSWORD ) mqtt_client.set_callback( on_message_received) mqtt_client.connect() mqtt_client.subscribe(b"IoTAlarm") print('MQTT connection established and subscribed to IoTAlarm') except: mqtt_client = None print('Failed to connect, retrying...') utime.sleep(5) -
display_status()函数每 5 秒更新一次显示。它显示 MQTT 连接状态在顶部,arm/disarm状态在中间,以及底部接收到的最新 MQTT 消息。如果连接到 MQTT,则显示MQTT 已连接;否则,显示MQTT 等待连接。我们在代码中单独的线程中运行此方法:def display_status(): global last_message is_armed = False while True: display.fill(0) if mqtt_client: msg = "MQTT Connected" else: msg = "MQTT waiting" display.text(msg, 0, 0) if last_message == "arm": is_armed = True elif last_message == "disarm": is_armed = False if is_armed: display.text("Status: Armed", 0, 20) else: display.text("Status: Disarmed", 0, 20) display.text("Msg: " + last_message, 0, 40) display.show() utime.sleep(5) -
main()函数启动 Wi-Fi 和 MQTT 连接,并持续检查按钮和开关状态。如果按钮被按下超过一秒钟,将通过 MQTT 发布启动消息。如果开关状态发生变化,则发送解除警报消息。该函数还会检查传入的 MQTT 消息。如果在检查消息时发生错误,则打印错误,系统等待 5 秒后继续检查。系统在循环迭代之间等待 0.1 秒以优化性能。我们首先定义main()函数和变量:def main(): global last_message, previous_switch_state connect_wifi() connect_mqtt() button_start_time = None -
然后,我们定义一个无限循环并检查主按钮是否被按下超过一秒钟(
1000毫秒)或更长时间:while True: if button.value() == 0: if button_start_time is None: button_start_time = utime.ticks_ms() else: if button_start_time is not None: button_elapsed_time = utime.ticks_diff(utime.ticks_ms(), button_start_time) if button_elapsed_time >= 1000: mqtt_client.publish( b"IoTAlarm", b"arm" ) last_message = "arm" button_start_time = None current_switch_state = switch.value() -
如果当前开关状态不等于上一个开关状态,我们的代码会发送一个
解除警报消息。这种条件检查使我们能够将开关用作切换,而不是定义一个明确的开启或关闭状态:if current_switch_state != previous_switch_state: mqtt_client.publish( b"IoTAlarm", b"disarm" ) last_message = "disarm" previous_switch_state = current_switch_state try: mqtt_client.check_msg() except Exception as e: print("Error checking MQTT message:", str(e)) utime.sleep(5) utime.sleep(0.1) -
然后,我们初始化一个新的线程来并发运行
display_status()函数。通过使用线程,它允许display_status()函数独立且同时与其他程序部分一起操作,确保显示状态持续更新,而不会阻碍或等待其他进程:_thread.start_new_thread(display_status, ()) -
最后,我们调用
main()函数,该函数指导程序的核心活动——处理连接、按钮输入和管理 MQTT 消息:main() -
要保存文件,我们点击
main.py到我们的 Raspberry Pi Pico W。
主代码编写完成后,是时候对其进行测试了。
运行增强的物联网按钮
在我们的 Raspberry Pi Pico W 物联网按钮应用程序上执行代码允许它与物联网警报模块交互。按钮可以启动或解除警报模块。我们将使用 Windows 中的 MQTT 探索器 应用程序来监控实时 MQTT 消息,以及发送消息来测试我们的增强型物联网按钮。
我们将首先从 MQTT 探索器应用程序发送消息。为此,我们执行以下操作:
- 在 Windows 的 Microsoft Store 中,我们搜索 MQTT 探索器应用:

图 7.19 – Microsoft Store 中的 MQTT 探索器
-
我们通过在 Thonny 中点击 运行 按钮或将我们的 Pico W 插入 USB 电源供应来在我们的 Raspberry Pi Pico W 物联网按钮应用程序上运行
main.py。 -
使用 MQTT 探索器,我们创建了一个名为
IoTAlarmSystem的连接,该连接使用来自 图 7.12 的 MQTT 服务器凭据:

图 7.20 – 使用 MQTT 探索器创建 MQTT 连接
- 使用 MQTT 探索器发送消息时,我们在主题中输入
IoTAlarm,在消息中输入test,然后点击 发布 按钮:

图 7.21 – 发送 MQTT 消息
-
我们应该观察到一条
test消息出现在我们的物联网按钮电路的 OLED 屏幕上。 -
然后,我们按下并保持物联网按钮电路上的街机按钮超过一秒钟。
-
我们应该在 MQTT 探索器应用程序上观察到
arm消息。 -
我们还应该注意到 IoT 警报模块上的 LED 每 5 秒闪烁一次。这表明 IoT 警报模块已武装。
-
我们通过在 PIR 传感器前挥动手来测试 IoT 警报模块。我们应该观察到警报响起。
-
我们应该注意到,经过一段时间后,增强型 IoT 按钮电路上的警报会关闭。
-
要解除警报,我们将开关从当前位置切换。这应该导致当 PIR 传感器检测到运动时,蜂鸣器关闭。这也应该禁用增强型 IoT 按钮上的蜂鸣器。
我们应该为自己鼓掌,因为我们已经成功构建了一个基本的 IoT 警报系统!在本章的最后部分,我们将把组件安装到定制的 3D 打印外壳中。
在定制外壳中安装组件
布线我们的组件和运行我们的代码是一个令人兴奋的步骤。然而,将它们装入定制外壳可以使我们的项目提升一个层次,并允许我们为实际用途使用我们的应用程序。定制外壳不仅提供保护,而且具有安装我们的高级 IoT 按钮在任何所需位置的灵活性。
在 图 7**.22 中,我们查看我们定制外壳的组件,全部为 3D 打印。部件 A 和 B 使用 Fused Deposition Modeling (FDM) 打印机生产,而 C 和 D 使用液体树脂打印机。虽然任何打印机类型都适用,但 FDM 打印需要在打印床上仔细定位部件以考虑层线强度:

图 7.22 – 增强型 IoT 按钮的定制外壳
我们定制外壳的部件如下分解:
-
A: 外壳
-
B: 后盖
-
C: 插件
-
D: 壁侧安装支架
所有部件都可以在本章 GitHub 存储库的 Build Files 文件夹的 Pico Button 子文件夹中找到。安装螺丝和 LED 座未显示。
使用 FDM 打印机打印分割支架
SenseHAT 外壳文件中的分割支架(Build Files 文件夹,第一章 存储库)非常适合 FDM 打印。通过分割并打印每个半边,支架获得了显著强度。还提供了一个配套底座。
要将组件安装到我们的定制外壳中,我们遵循 图 7**.23 中的步骤:
-
我们首先使用两个 M5 10 毫米螺栓和两个 M5 螺母(见 B 在 图 7**.13 中)将蜂鸣器(见 A 在 图 7**.22 中)安装到外壳上。
-
然后(图 7**.23,步骤 2),我们将开关(图 7**.13,G)和街机按钮(见 D 在 图 7**.13 中)安装到外壳中相应的孔中(见 A 在 图 7**.22 中):

图 7.23 – 构建增强型 IoT 按钮的外壳
-
使用 LED 支架,我们将 220 欧姆电阻的 LED(见图 7**.13中的A)安装到外壳中(见图 7**.22中的A),使用左侧孔(见图 7**.23,步骤 3)。
-
我们使用 4 个 M2 5 毫米螺丝或热胶枪的胶水(见图 7**.23,步骤 4)将 OLED 屏幕(见图 7**.13中的C)固定到外壳(见图 7**.22中的A)。
-
然后(见图 7**.23,步骤 4),我们将组件连接到新的树莓派 Pico W。
-
我们随后使用两个 M2 5 毫米螺丝将钩子(见图 7**.22中的C)安装到背板上(见图 7**.22中的B)(见图 7**.23,步骤 5)。
-
我们使用四个 M2 5 毫米螺丝(见图 7**.23,步骤 6)将 Pico W 固定在背板上(见图 7**.22中的B)。
-
然后(见图 7**.23,步骤 7),我们使用四个 M3 10 毫米螺栓将背板(见图 7**.22中的B)固定到外壳(见图 7**.13中的A)。
-
在将背板(见图 7**.22中的B)固定到外壳(见图 7**.22中的A)后,我们使用 M5 20 毫米螺栓和 M5 螺母将组件连接到安装支架(见图 7**.22中的D)。
-
使用“为我们的物联网按钮编码主要功能”部分的步骤,我们安装了我们的增强型物联网按钮的软件包和客户端代码。
在组件安全安装后,我们现在可以将我们的树莓派 Pico W 物联网按钮放置在任何希望的位置,无论是家庭、办公室还是车间环境。我们的设计不仅服务于我们的主要警报系统目的,而且使其适用于多种其他应用,扩大其用途,并在许多不同场景中提供创新的集成潜力。
摘要
在本章中,我们探讨了物联网按钮,从描述它们是什么以及它们在哪里使用开始。然后,我们探讨了我们可以用来构建物联网按钮的各种技术。
我们随后开始使用公共 MQTT 服务和 M5Stack ATOM Matrix 构建我们的第一个物联网按钮。我们能够将我们的物联网按钮连接到我们在第六章中构建的物联网警报模块。
从那里,我们使用 CloudAMQP 将我们的 MQTT 服务器升级为私有服务器。我们这样做是为了可靠性和安全性的原因,因为我们开始构建我们的物联网警报系统。在用树莓派 Pico W 和各种组件构建第二个物联网按钮之前,我们升级了物联网警报模块上的代码。
我们通过将我们的第二个(增强型)物联网按钮的组件安装到 3D 打印的盒子里来结束本章。这样做将我们的电路从教育工具转变为适合在商业环境中部署的工作设备。
在下一章中,我们将继续构建我们的物联网警报系统,回到树莓派,在那里我们将构建一个安全仪表板。
第八章:创建物联网警报仪表板
在当今数字化时代,物联网已经革命性地改变了安全,将基本的警报仪表板转变为全面、实时的安全监控器。在第六章中,我们通过构建一个能够检测运动并中继 MQTT 消息的物联网警报模块开始了构建物联网警报系统的过程。第七章介绍了两种版本的物联网按钮:第一个使用 M5Stack ATOM Matrix 和 LCD 矩阵屏幕,第二个集成了 OLED 屏幕、蜂鸣器、用于启动我们的物联网警报模块的街机式按钮以及用于解除的切换开关:

图 8.1 – 物联网警报系统
在本章中,我们将使用带有 7 英寸触摸屏的 Raspberry Pi 5 作为我们的物联网警报仪表板(图 8**.1)。这个仪表板将允许我们启动和解除我们的物联网警报模块,查看最新的 MQTT 通知,并观察一个标记我们警报激活位置的地图。它将订阅并发布到与其他物联网警报系统设备相同的 MQTT 主题,即物联网警报模块、M5Stack ATOM Matrix 物联网按钮和 Raspberry Pi Pico W 物联网按钮。
物联网警报仪表板完成了我们的高级物联网警报系统。我们的系统利用物联网技术和互联网的广泛覆盖范围进行全球应用部署。
在本章中,我们将涵盖以下主题:
-
探索物联网警报仪表板
-
创建 Raspberry Pi 5 物联网警报仪表板
-
构建外部警报蜂鸣器支架
-
运行我们的应用程序
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
具备 Python 编程的中级知识
-
一款新型 Raspberry Pi,例如 Raspberry Pi 5
-
带有兼容机箱的 Raspberry Pi 品牌 7 英寸触摸屏
-
1 个 SFM-27 主动蜂鸣器
-
2 个 M2 5mm 螺丝
-
2 个 M4 20mm 螺栓
-
2 个 M4 螺母
-
1 个 M5 20mm 螺栓
-
1 个 M5 螺母
-
复合(多线)带 USB 插头的电缆(废弃的 USB 充电电缆效果很好)
-
热胶枪
-
访问 3D 打印机或 3D 打印服务以打印可选的机箱
本章的代码可以在以下位置找到:
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter8
探索物联网警报仪表板
物联网的真正优势是其无限的能力,正如物联网警报仪表板所示。将 Raspberry Pi 5 等设备与物联网的广泛网络集成,开辟了新的可能性,尤其是在安全系统中。在家庭或商业警报系统中使用 Raspberry Pi,提供了巨大的创新潜力。这款多功能设备为超越传统警报系统的先进安全解决方案打开了可能性。
使用物联网警报仪表板进行工业流程
在现代工业环境中,监控安全和效率至关重要。通常集成到控制室的工业物联网警报仪表盘提供了设施运营的概述。它显示实时指标和系统状态,并在设备偏离标准参数时发送警报。
仪表盘的优势在于其能够通过互联网快速检测和沟通问题。例如,在石化厂中,罐体上的传感器将数据发送到仪表盘,如图图 8.2所示。在这里,罐体上的传感器发布“temp”和“level”MQTT 消息,分别指示罐内液体的温度和液位。Raspberry Pi 已被设置为订阅这些消息,并将此信息传递到网络界面和模拟仪表。如果罐体的液位下降或温度发生剧烈变化,系统会标记这种差异:

图 8.2 – 工业物联网应用
警报会通知团队,以便及时采取行动确保工人安全并预防潜在危险。仪表盘还可以启动自动化响应,例如关闭受影响区域。
除了安全之外,物联网仪表盘还提高了运营效率。分析长期数据可以帮助行业预测维护需求,减少停机时间。仪表盘还可以连接到供应系统,更新原材料水平和产品计数。通过使用来自各种来源的数据,行业可以提高安全和效率。
探索物联网安全警报仪表盘
对于现代安全,物联网的集成重新定义了警报仪表盘的功能。这些不再是过去的传统系统;增强型物联网警报仪表盘是动态的,提供远程访问和响应性操作。例如,随着智能家居和企业的兴起,安全漏洞不仅会触发响亮的警报器,还能通过业主的移动设备即时通知他们,启动实时视频画面捕捉,甚至与当地执法部门沟通,这一切都得益于物联网的连接性。
配备我们的 Raspberry Pi 5 及其 7 英寸触摸屏显示器,我们将为我们的物联网警报系统构建一个警报仪表盘(*图 8.3**)。使用这个仪表盘,我们可以使用 4 位数字密码来启用和禁用我们的物联网警报模块。我们的仪表盘将显示最新的IoTAlarm MQTT 消息,并提供触发警报的区域地图:

图 8.3 – 物联网警报仪表盘
由于我们的 Raspberry Pi 5 没有内置蜂鸣器,我们将添加一个外部蜂鸣器,并通过复合(多线)电缆通过 GPIO 端口连接。这个外部蜂鸣器将放置在一个定制设计的 3D 打印支架上,并在我们的启用物联网警报模块被触发时播放旋律。
我们将首先通过升级物联网警报模块的代码开始开发,使其能够与物联网警报仪表板的地图功能协同工作。
创建 Raspberry Pi 5 警报仪表板
自 2023 年底发布以来,Raspberry Pi 5 为紧凑型计算设定了新的标准,使开发者能够在各个领域构建更高效、更强大的应用程序。
Raspberry Pi 5 拥有先进的处理器,提高了我们的物联网警报仪表板的数据处理速度和多任务处理能力。其强大的软件支持和广泛的 Python 支持,提供了无与伦比的编程灵活性,适用于物联网警报系统的需求。搭配 7 英寸触摸屏,我们的 Raspberry Pi 5 为我们的系统提供了一个用户友好且高效的界面。
我们将开始开发我们的物联网警报仪表板,通过修改来自 第六章 的物联网警报模块代码,使其在 IoTAlarm MQTT 主题上发布 location 数据。一旦该数据被激活,我们的仪表板将能够精确地识别物联网警报模块的地理位置。
修改物联网警报模块代码
图 8.1 展示了我们的物联网警报模块的一个略微修改版本,其中添加了一个 location 消息,该消息将地理位置数据发送到我们的物联网警报仪表板。在这种情况下,我们可以将类似图 图 8.4 中所示的 GPS 模块集成到我们的物联网警报模块中:

图 8.4 – GPS 模块紧邻 Raspberry Pi Pico
GPS 测试代码
本章的 GitHub 仓库包含 BN-180 GPS 模块和 Raspberry Pi Pico 的测试代码。
然而,尽管其体积紧凑且易于连接到 Raspberry Pi Pico W,但由于我们的物联网警报模块是室内使用,GPS 将难以获得强信号:

图 8.5 – 物联网警报模块的全球部署
相反,我们将直接将 GPS 坐标嵌入到我们的物联网警报模块代码中,假设模块的位置在部署后保持静态。由于代码需要 Wi-Fi 配置更新,因此在我们现场部署物联网警报模块时调整 GPS 详细信息非常简单。这部分地理位置数据将作为增强我们模块代码的一部分,在 location 消息中发布。这种方法使我们能够将物联网警报模块全球部署,如图 图 8.5 所示。
为了发布地理位置信息,我们将修改存储在我们的物联网警报模块上的 main.py 文件中的 motion_handler() 方法。
使用微控制器时,与 C 相比使用 MicroPython 的优势
虽然 MicroPython 的运行速度比 C 语言慢,但当涉及到修改物联网警报模块的代码时,其适应性是显而易见的。使用 C 语言时,更改需要重新编译和外部代码跟踪。然而,MicroPython 可以直接在微控制器上编辑,在更改过程中绕过文件系统搜索。
要修改代码,我们必须执行以下操作:
-
首先,我们将 Raspberry Pi Pico W 连接到计算机的 USB 端口并启动 Thonny。
-
然后,我们从屏幕的右下角选择 Pico W 上的 MicroPython 环境以激活它:

图 8.6 – 在 Thonny 中选择 MicroPython 和 Pico(显示 Windows 版本)
- 在
main.py文件下打开它:

图 8.7 – 选择 main.py 文件
-
然后,我们重写
motion_handler()方法,使其如下所示:def motion_handler(pin): print('Motion detected!') if mqtt_client: if ARMED: activate_buzzer() mqtt_client.publish(b"IoTAlarm", b"buzzer") mqtt_client.publish(b"IoTAlarm", b"location:43.6426,-79.3871") else: mqtt_client.publish(b"IoTAlarm", b"motion") else: print("MQTT client not connected.") -
我们在这里的唯一更改是在
mqtt_client对象上添加另一个发布方法。在新的publish()方法中,我们创建了一个名为location的消息,它提供了加拿大多伦多 CN 塔的大致 GPS 坐标。
物联网警报模块地理位置
我们设计的物联网警报模块旨在永久安装在室内位置——即硬编码的地理位置。我们以加拿大多伦多的 CN 塔为例。我们鼓励您提供您自己的独特 GPS 坐标。
- 我们接着将我们的更改保存到我们的 Raspberry Pi Pico W。
我们将在下一节设置和启动 Raspberry Pi 5 上的物联网警报仪表板时看到这次调整的影响。
编写仪表板代码
对于物联网警报仪表板,我们将使用 Raspberry Pi 5、7 英寸触摸屏 Raspberry Pi 及其兼容的机箱。此配置类似于我们在第四章中建立的配置,增加了专用于物联网警报仪表板的外部蜂鸣器。
我们将从设置我们的开发环境和安装代码所需的包开始。
设置我们的开发环境
我们将为我们的开发使用 Python 虚拟环境。由于有一些库只与 Python 的根安装版本兼容,我们将在 Python 虚拟环境中使用系统包。为此,我们必须执行以下操作:
-
在我们的 Raspberry Pi 5 上,我们打开一个终端应用程序。
-
要存储我们的项目文件,我们运行以下命令创建一个新的目录:
mkdir dashboard -
然后,我们导航到新目录:
cd dashboard -
接下来,我们为我们的项目创建一个新的 Python 虚拟环境:
dashboard-env and enable access to the --system-site-packages. This allows the virtual environment to inherit packages from the global Python environment without affecting the global Python environment. -
创建了新的 Python 虚拟环境后,我们使用以下命令将其源代码导入:
dashboard-env Python virtual environment:

图 8.8 – 显示使用 dashboard-env 环境的终端
-
接下来,我们安装代码所需的额外包:
kivy-garden.mapview extension offers an interactive map widget, while paho.mqtt is a Python client library that enables MQTT communications; it’s popular in IoT due to its efficiency. We’re ensuring that we install the version of paho.mqtt that will work with our program by specifying that we want 1.5.1. -
在安装了额外的包后,我们关闭终端:
exit -
现在我们已经准备好加载 Thonny。为此,我们点击 Raspberry Pi 任务栏中的菜单图标,导航到编程类别,并选择Thonny。
-
默认情况下,Thonny 使用 Raspberry Pi 的内置 Python 版本。对于我们的项目,我们将使用我们刚刚创建的 Python 虚拟环境。首先,我们需要通过点击查看并选择文件来查看项目文件(如果它还没有被选中)。
-
在
文件部分,我们定位并打开dashboard-env目录。 -
然后,右键单击
pyvenv.cfg文件,并选择激活虚拟环境选项:

图 8.9 – 在 Thonny 中激活 Python 虚拟环境
激活 Python 虚拟环境
在上述步骤中,我们通过点击pyvenv.cfg文件激活了 Python 虚拟环境。这一步骤以这种方式概述是为了展示一种激活 Python 虚拟环境的方法,而不是之前章节中展示的方法。
在创建项目文件夹、设置 Python 虚拟环境并安装我们项目所需的包后,我们可以开始编写我们的物联网警报仪表板的代码。我们将把我们的代码分成两个文件——一个用于创建仪表板的 GUI,另一个用于激活蜂鸣器。但在我们这样做之前,我们必须将蜂鸣器连接到我们的 Raspberry Pi 5 的 GPIO 端口。
连接蜂鸣器
对于我们的项目,我们将使用一个 SFM-27 主动蜂鸣器。我们将蜂鸣器的正极线(红色)连接到 GPIO 4,负极线(黑色)连接到 Raspberry Pi 5 上的 GND。我们有将蜂鸣器安装到定制外壳的选项,我们将在本章后面讨论这一点。为了我们的代码开发和测试目的,直接将 SFM-127 主动蜂鸣器连接到 Raspberry Pi 5 就足够了:

图 8.10 – 将蜂鸣器连接到 Raspberry Pi 5 的 GPIO 端口
在蜂鸣器连接到我们的 Raspberry Pi 5 后,现在是时候编写和测试蜂鸣器代码了。我们将使用此代码在仪表板从 MQTT 服务器接收到buzzer消息时激活蜂鸣器。
编写和测试蜂鸣器代码
我们使用 Thonny 在我们的 7 英寸屏幕的 Raspberry Pi 5 上编写蜂鸣器代码。为了有更多的屏幕空间来辅助编码,我们可以通过 Raspberry Pi 5 的 mini-HDMI 端口添加另一个显示器,创建一个双显示器设置。
要编写和测试我们的蜂鸣器代码,请按照以下步骤操作:
-
我们通过点击 Raspberry Pi 任务栏中的菜单图标,导航到编程类别,并选择Thonny来启动 Thonny。
-
然后,我们激活
dashboard-envPython 虚拟环境。 -
一旦进入 Thonny,我们通过选择文件然后新建或按键盘上的Ctrl + N来创建一个新的标签页。
-
在我们的新文件中,我们输入以下代码:
from gpiozero import TonalBuzzer from gpiozero.tones import Tone from time import sleep class BuzzerMelody: def __init__(self, pin, notes=[('E4', 1), ('E4', 0.5), ('F4', 0.5), ('G4', 1.5)]): self.buzzer = TonalBuzzer(pin) self.melody = notes def play_melody(self): for note, duration in self.melody: self.buzzer.play(Tone(note)) sleep(duration) self.buzzer.stop() sleep(0.1) # pause between notes if __name__ == "__main__": buzzer_melody = BuzzerMelody(4) buzzer_melody.play_melody() -
我们将文件保存为
buzzer.py,位于 Raspberry Pi 5 的dashboard项目文件夹中。在测试我们的代码之前,让我们检查它:
-
我们首先从
gpiozero模块导入TonalBuzzer类。 -
然后,我们从
gpiozero.tones导入Tone类。 -
我们通过从
time模块导入sleep函数来完成我们的导入。 -
接下来,我们定义一个
BuzzerMelody类:初始化器 (__init__) 接受一个引脚和一个包含音符及其时长的列表。列表有一个默认旋律。在初始化器中,我们执行以下操作:-
使用提供的引脚初始化一个
TonalBuzzer对象 -
设置旋律
-
-
然后,我们定义
play_melody()方法。在这个方法中,我们执行以下操作:-
遍历旋律音符
-
按指定时长播放每个音符
-
确保音符播放后蜂鸣器停止
-
在音符之间添加短暂的暂停
-
-
如果脚本作为主程序执行,我们执行以下操作:
-
使用引脚 4 创建
BuzzerMelody类的实例 -
使用
play_melody()方法播放旋律
-
-
-
我们通过点击 Thonny 中的绿色运行按钮,按键盘上的 F5,或在顶部菜单中选择 运行 然后选择 运行当前脚本 来运行代码。
-
我们应该会听到蜂鸣器播放旋律。
在我们的蜂鸣器代码就绪后,是时候使用 Kivy 编写我们的主仪表盘代码了。
创建我们的 Kivy 仪表盘
如前所述,我们将使用 Raspberry Pi 5 的 7 英寸触摸屏来显示我们的仪表盘。使用此屏幕是可选的,因为我们可能使用标准显示器与 Raspberry Pi 一起使用。
要编写和测试我们的 Kivy 仪表盘代码,请按照以下步骤操作:
-
通过点击 Raspberry Pi 任务栏中的 菜单 图标,导航到 编程 类别,然后选择 Thonny 来启动 Thonny。
-
然后,我们激活
dashboard-envPython 虚拟环境。 -
一旦在 Thonny 中,通过选择 文件 然后选择 新建 或按键盘上的 Ctrl + N 创建一个新标签。
-
我们将使用必要的导入开始我们的代码:
from kivy.config import Config Config.set('graphics', 'fullscreen', 'auto') Config.set('graphics', 'borderless', '1') from kivy.app import App from kivy.uix.floatlayout import FloatLayout from kivy.uix.label import Label from kivy.uix.button import Button from kivy.clock import Clock from threading import Thread import paho.mqtt.client as mqtt from kivy_garden.mapview import MapView, MapMarkerPopup from buzzer import BuzzerMelody让我们检查这段代码:
-
kivy.config:我们的代码从 Kivy 导入配置设置,以调整应用程序的行为。 -
fullscreen:我们配置应用程序以全屏模式运行。 -
borderless:我们的设置消除了窗口边框。 -
kivy.app:我们的代码访问 Kivy 的主要应用程序类,用于初始化和操作 Kivy 应用程序。 -
kivy.uix.floatlayout:我们导入FloatLayout,这是一种灵活的布局机制,根据相对坐标放置小部件。 -
kivy.uix.label:我们的代码使用Label小部件,允许我们在应用程序中显示文本。 -
kivy.uix.button:我们整合了Button小部件,以便它可以与我们的键盘一起使用。 -
kivy.clock:我们利用Clock类来安排在定时间隔运行特定函数。 -
Threading:为了确保平滑的多任务处理,我们的代码使用线程进行并行操作。我们使用线程在单独的线程上运行start_mqtt()方法。 -
paho.mqtt.client:我们在代码中使用 MQTT 客户端库与我们的 MQTT 服务器进行通信。 -
kivy_garden.mapview:我们的代码引入了用于展示地图(MapView)和交互式地图标记(MapMarkerPopup)的类。我们使用这些类在地图上直观地表示警报被触发的确切位置。 -
buzzer.BuzzerMelody:我们导入BuzzerMelody类,以便我们可以激活外部蜂鸣器。
-
-
然后,为我们的代码设置变量声明:
MQTT_SERVER = "codfish.rmq.cloudamqp.com" MQTT_PORT = 1883 USERNAME = "<<MQTT server username>>" MQTT_PASSWORD = "<<MQTT server password>>" DEVICE_ID = "IoTAlarmDashboard" TOPIC = "IoTAlarm" -
从这里,我们必须定义继承自
FloatLayout类的AlarmDashboard类,并定义初始化方法:class AlarmDashboard(FloatLayout): def __init__(self, **kwargs): super(AlarmDashboard, self).__init__(**kwargs) self.password_toggle = "1234" self.entered_password = '' self.system_armed = False self.lockout = False self.last_message = '' self.client = mqtt.Client(client_id=DEVICE_ID) self.client.username_pw_set(USERNAME, MQTT_PASSWORD) self.client.on_message = self.on_message Thread(target=self.start_mqtt).start() self.init_widgets() self.buzzer = BuzzerMelody(4)让我们检查这段代码:
-
首先,我们初始化特定的属性,例如默认密码(
password_toggle)、当前输入的密码(entered_password)、系统状态标志(system_armed)、锁定状态(lockout)和最后接收到的消息(last_message)。 -
然后,我们设置一个具有唯一
client_id的 MQTT 客户端,并使用username_pw_set方法提供必要的认证细节。 -
客户端的
on_message属性被设置为名为on_message()的方法,我们的代码将使用该方法来处理传入的 MQTT 消息。 -
我们使用
start_mqtt方法在单独的线程上启动 MQTT 连接,以避免阻塞主应用程序。 -
我们将在后面定义的
init_widgets方法被调用以初始化和排列用户界面元素。 -
最后,我们使用
BuzzerMelody类的实例初始化buzzer属性,并将其设置在 GPIO 引脚 4 上。
-
-
现在,使用
on_message()方法来处理来自我们的 MQTT 服务器的消息:def on_message(self, client, userdata, msg): message = f"{str(msg.payload.decode('utf-8'))}" self.last_message = "Last message: " + message if message.startswith("location:"): parts = message.split(":")[1].split(",") lat = float(parts[0]) lon = float(parts[1]) Clock.schedule_once( lambda dt: self.update_map(lat, lon), 0 ) if message == "arm": Clock.schedule_once( lambda dt: self.update_system_status(True), 0 ) elif message == "disarm": Clock.schedule_once( lambda dt: self.update_system_status(False), 0 ) if message == "buzzer": self.buzzer.play_melody() Clock.schedule_once( lambda dt: self.update_message_display(), 0 )让我们检查这段代码:
-
接收到消息后,我们的代码将解码消息有效载荷,从字节转换为字符串
-
最新的消息存储时带有
Lastmessage:前缀 -
如果接收到的消息以
location:开头,它将提取纬度和经度值 -
这些值随后用于更新显示的地图,并安排调用
update_map()方法 -
如果接收到的消息是
arm,系统状态将通过update_system_status()方法更新为armed -
如果接收到的消息是
disarm,系统状态将通过update_system_status()方法更新为disarmed。 -
如果接收到的消息是
buzzer,将通过buzzer实例的play_melody()方法播放旋律 -
无论消息内容如何,都会使用
update_message_display()方法更新显示的消息
-
在我们的代码中使用 lambda()函数
在 Python 中,lambda() 函数用作匿名内联函数。它通常用于表达为单个语句的简短操作。具体来说,在我们的代码中,lambda() 函数与 Kivy 的 Clock.schedule_once() 方法配对,以延迟某些方法的执行。通过使用 Clock.schedule_once() 方法,可以安排一个函数在指定延迟后运行。当第二个参数为 0 时,表示函数应在下一个帧上立即调用。
我们代码中的每个 lambda() 函数都遵循 lambda dt: some_method(arguments) 模式。在这里,dt 代表自上一帧以来经过的时间。它是 Clock.schedule_once() 自动提供的参数。例如,Clock.schedule_once(lambda dt: self.update_map(lat, lon), 0) 将 self.update_map(lat, lon) 方法安排在即将到来的帧上执行,并使用已解析的 lat 和 lon 值作为其参数。本质上,这些 lambda() 函数充当一个通道,将参数传递给方法,并通过 Kivy 的调度机制在下一帧上设置它们以执行。利用 Clock() 函数确保我们的 UI 更新保持平滑并与显示的刷新率同步,同时也防止了主线程的阻塞,从而提高了应用程序的响应性。
-
我们的代码定义了一个名为
update_map()的方法,其主要目的是以两种主要方式更新显示的地图:def update_map(self, lat, lon): self.mapview.center_on(lat, lon) marker = MapMarkerPopup(lat=lat, lon=lon) self.mapview.add_widget(marker)让我们来看看这段代码:
-
我们的代码将地图的中心调整到新的纬度和经度坐标,这些坐标作为参数(
lat和lon)提供。 -
然后,它在地图上指定的坐标处放置一个标记(具体来说是一个可以显示弹出窗口的交互式标记)。这个标记指示触发警报的物联网警报模块的确切位置。
-
-
update_system_status()方法根据is_armed的值更新我们的仪表板上的状态消息:def update_system_status(self, is_armed): if is_armed: self.system_armed = True self.system_status.text = "System is ARMED" self.system_status.color = (0, 1, 0, 1) else: self.system_armed = False self.system_status.text = "System is DISARMED" self.system_status.color = (1, 0, 0, 1) -
然后,我们的代码定义了一个名为
start_mqtt()的方法,用于设置和初始化应用程序的 MQTT 通信:def start_mqtt(self): self.client.connect(MQTT_SERVER, MQTT_PORT) self.client.subscribe(TOPIC) self.client.loop_forever()让我们来看看这段代码:
-
该方法使用给定的服务器地址 (
MQTT_SERVER) 和端口号 (MQTT_PORT) 将 MQTT 客户端连接到指定的 MQTT 服务器。 -
一旦连接,客户端会订阅一个特定的主题 (
TOPIC),这意味着它将开始监听在该主题上发布的消息。 -
最后,调用客户端的
loop_forever()方法,该方法使 MQTT 客户端持续不断地检查传入的消息,并在应用程序运行期间处理这些消息。
-
-
我们的代码定义了一个名为
init_widgets()的方法,用于初始化并在我们的仪表板上放置各种用户界面组件。我们将从键盘初始化过程开始:def init_widgets(self): # Keypad buttons positions = [ (0.03, 0.75), (0.14, 0.75), (0.25, 0.75), (0.03, 0.55), (0.14, 0.55), (0.25, 0.55), (0.03, 0.35), (0.14, 0.35), (0.25, 0.35) ] for index, pos in enumerate(positions, 1): btn = Button( text=str(index), size_hint=(0.1, 0.1), pos_hint={'x': pos[0], 'y': pos[1]} ) btn.bind(on_press=self.handle_key_press) self.add_widget(btn)在这里,设置了一个按键布局,按钮以 3x3 网格排列。这些按钮的位置使用
positions列表指定,其中每个元组代表相对的x和y坐标。当我们遍历这些位置时,我们创建一个带有相应数字的按钮,并将其on_press事件绑定到handle_key_press()方法,该方法将捕获按钮按下动作。每个按钮一旦初始化,就使用add_widget()方法添加到仪表板中。 -
现在,编写 系统状态标签 的代码:
# System status self.system_status = Label( text="System is DISARMED", size_hint=(1, 0.2), pos_hint={'x': -0.3, 'y': 0.1}, font_size=30, color=(1, 0, 0, 1) ) self.add_widget(self.system_status)这个标签显示系统的状态,指示它是处于警戒状态还是解除警戒状态。它使用特定的文本大小、颜色和定位进行样式设置。一旦初始化,该标签就被添加到仪表板中。
-
接下来,设置一个用于 MQTT 消息显示 的标签:
# MQTT Messages self.message_display = Label( text="Waiting for message...", size_hint=(0.77, 0.6), pos_hint={'x': 0.23, 'y': 0.62}, font_size=25, color=(1, 1, 1, 1) ) self.add_widget(self.message_display)在这里,已经设置了一个标签来显示传入的 MQTT 消息。它有一个默认文本
Waiting for message...,并且与系统状态标签的样式相似。一旦创建,它就被添加到仪表板中。 -
最后,添加一个 MapView 小部件:
self.mapview = MapView( zoom=15, lat=52.379189, lon=4.899431, size_hint=(0.5, 0.7), pos_hint={'x': 0.45, 'y': 0.15} ) self.add_widget(self.mapview)在这里,我们初始化一个
MapView小部件来显示地理位置。它预设了特定的缩放级别和初始纬度和经度坐标。这个地图视图允许我们在地图上可视化位置。一旦初始化,它就被添加到我们的仪表板中。 -
update_message_display()方法用于刷新或更新message_display小部件中显示的文本,以显示系统接收到的最新消息:def update_message_display(self): self.message_display.text = self.last_message -
handle_key_press()方法管理用户在输入警报系统密码时与虚拟键盘的交互,确定输入代码的有效性,并根据密码输入调整警报系统的状态:def handle_key_press(self, instance): if not self.lockout: self.entered_password += instance.text print("The key:" + instance.text + " was pressed") if len(self.entered_password) == 4: if self.entered_password == self.password_toggle: if self.system_armed: self.system_armed = False self.system_status.text = "System is\ DISARMED" self.system_status.color = (1, 0, 0, 1) self.client.publish(TOPIC, "disarm") else: self.system_armed = True self.system_status.text = "System is ARMED" self.system_status.color = (0, 1, 0, 1) self.client.publish(TOPIC, "arm") self.entered_password = '' else: self.lockout = True Clock.schedule_once(self.end_lockout, 5) self.entered_password = ''让我们检查这段代码:
-
self.entered_password字符串,表示用户当前输入的密码。 -
handle_key_press()方法验证它是否与预定义的警报系统代码匹配。如果代码正确,以下操作会发生:-
警报系统的状态在
armed和disarmed之间切换。 -
向
IoTAlarm主题发送相应的消息(arm或disarm)。
-
-
lockout模式:-
启动一个计时器,在指定的时间(本例中为 5 秒)后结束锁定状态。
-
输入的代码已重置,以备再次尝试。
-
-
-
end_lockout()函数旨在终止系统的锁定状态。将lockout属性设置为False确保在锁定期间被限制的操作或交互现在被允许:def end_lockout(self, dt): self.lockout = False -
在这一点上,我们的代码引入了主应用程序类
MyApp,该类基于 Kivy 的 App 框架构建。在这个类中,build()方法构建并返回一个AlarmDashboard实例。如果我们直接运行此代码(未导入到另一个脚本中),则if __name__ == '__main__':检查确保创建并启动一个新的MyApp实例,从而启动整个警报应用程序:class MyApp(App): def build(self): return AlarmDashboard() if __name__ == '__main__': MyApp().run() -
将我们的代码保存为
dashboard.py在项目文件夹中。
在编写了仪表板代码并测试了蜂鸣器后,是时候为我们的蜂鸣器构建定制支架了。像往常一样,这是可选的;我们可以在不将蜂鸣器封装到支架中的情况下运行我们的应用程序。然而,这样做可以使我们的应用程序看起来更专业,增强用户体验,并提供更精致的展示,确保蜂鸣器在需要时可以安全且易于访问。
构建外部报警蜂鸣器支架
将我们的蜂鸣器封装在定制支架中增强了我们的物联网报警仪表板。在本节中,我们将构建支架。在我们组装之前,我们将首先识别组成定制蜂鸣器支架的部件。
识别部件
组成定制蜂鸣器支架的部件可以使用 3D 打印机或 3D 打印服务(如 Shapeways www.shapeways.com)制作。
这些部件在*图 8**.11 中显示:
- A:这是位于本章 GitHub 仓库
Build Files/Buzzer Stand文件夹中的Stand.stl文件的 3D 打印版本。这个版本的文件是用液体树脂 3D 打印机打印的,随后进行了喷漆。对于那些更喜欢使用 FDM 打印机的人来说,位于Build Files/Split Stand下的分体支架将是一个更好的选择:

图 8.11 – 定制蜂鸣器支架的部件
-
B:这是我们标准的钩子。它是由液体树脂 3D 打印的
Hook.stl文件版本,该文件位于本章 GitHub 仓库的Build Files文件夹中。 -
C:这是为该支架设计的标准 SFM-127 主动蜂鸣器。
-
D:这是涂漆的 FDM 3D 打印的
Front.stl文件版本,它也位于BuildFiles文件夹中。 -
E:使用 FDM 3D 打印机创建了一个
Back.stl文件的版本。该部件通过钩子(B)将前面(D)与支架(A)连接起来。 -
未显示:一根多芯电缆,如废弃的 USB 电缆。
-
未显示:2 个 M4 20mm 螺栓。
-
未显示:2 个 M3 10mm 螺栓。
-
未显示:2 个 M2 5mm 螺栓。
-
未显示:1 个 M5 20mm 螺栓。
-
未显示:1 个 M5 螺母。
-
未显示:2 个用于将蜂鸣器连接到 GPIO 端口的雌性跳线端子。
应使用哪种类型的 3D 打印机来创建部件?
在图 8.9 中,使用了 FDM 和液体树脂 3D 打印机创建了部件。打印机的选择取决于用户的经验和部件的设计。如图 8.10 中的E和D所示的平面部件,适用于 FDM 打印机。如图 8**.10 中的部件A所示的装饰性设计,则更适合液体树脂打印机。虽然钩子(B*)可以使用任何一种方法,但我们选择了一种使用工程级树脂(Siraya Tech Blu)的液体树脂打印机,以增加强度。
在确定了部件后,是时候构建我们的定制蜂鸣器支架了。
构建支架
要构建定制的蜂鸣器支架,我们必须遵循 图 8**.12 中所示的步骤:

图 8.12 – 构建定制蜂鸣器支架的步骤
让我们仔细看看:
-
使用两个 M4 10mm 螺栓,将蜂鸣器(见 C 在 图 8**.11 中)固定到前壳上(见 D 在 图 8**.11 中)。螺栓应紧紧拧入蜂鸣器。如果它们松动,我们可以用两个 M4 螺母拧紧。
-
使用多芯电缆,例如废弃的 USB 电缆,将两根线焊接到蜂鸣器上(见 C 在 图 8**.11 中)。
-
将多芯电缆穿过前壳上的孔(见 D 在 图 8**.11 中),并通过热胶枪涂抹胶水,将电缆固定到壳体上。
-
使用多芯电缆(在我们的例子中是红色和黑色)的两根相同电线,将两个母跳线端子压接到线的末端。另一种选择是将预先存在的跳线电缆焊接到末端而不是压接。
-
然后,通过使用两个 M2 5mm 螺丝或环氧树脂胶水,将钩子(见 B 在 图 8**.11 中)固定到背板上(见 E 在 图 8**.11 中)。
-
使用两个 M3 10mm 螺栓,将背板(见 E 在 图 8**.11 中)固定到前壳(见 D 在 图 8**.11 中)。
-
使用 M5 20mm 螺栓和 M5 螺母,将组装好的壳体固定到支架上(见 A 在 图 8**.11 中)。
在构建好定制支架后,我们可以按照 图 8**.10 中所示的接线图,将我们的蜂鸣器重新连接到 Raspberry Pi 5 的 GPIO 端口。
我们现在已准备好测试我们的应用程序。
运行我们的应用程序
现在是我们一直在过去几章中构建的时刻:测试我们的整个物联网报警系统。在构建好物联网报警仪表板后,我们的物联网报警系统就完成了:

图 8.13 – 完成的物联网报警系统
我们首先在 第六章 中构建了物联网报警模块(见 E 在 图 8**.13 中),然后继续在 第七章 中构建物联网按钮(见 A 和 D 在 图 8**.13 中)。在本章中,我们使用了带有活动蜂鸣器(见 B 和 C 在 图 8**.13 中)的 Raspberry Pi 5、7 英寸屏幕和外壳(见 B 在 图 8**.13 中)、键盘(见 F 在 图 8**.13 中)和鼠标(见 G 在 图 8**.13 中)来构建物联网报警仪表板。
要测试物联网报警仪表板,我们只需启动物联网报警模块,并通过在其 PIR 传感器前移动物体来激活它。为此,请按照以下步骤操作:
-
要启动物联网报警模块,使用我们的鼠标,将四位 PIN 码(1234)输入到物联网报警仪表板的键盘上。
-
我们应该注意到,物联网报警模块通过 LED 的长闪烁进入警戒模式。要激活报警,我们在 PIR 传感器前挥动手臂。
-
几秒钟后,我们应该听到物联网警报模块上的蜂鸣器声音,接着是物联网按钮(版本 2)和物联网警报仪表板上的蜂鸣器。
-
我们还应该注意到,在物联网警报仪表板的屏幕上应该显示一个带有 CN 塔标记的多伦多地图:

图 8.14 – 显示警报激活位置的物联网警报仪表板
在成功测试了物联网警报系统之后,我们完成了本书的第二部分。在整个这一过程中,我们探索了基于互联网的设备通信,并利用了 Raspberry Pi Pico W 微控制器和 Raspberry Pi 5 计算机的能力。这个基础对于本书中的高级项目将非常有价值。
如果我们真的想接受挑战,我们可以尽可能地将物联网警报系统的每个组件放置得远一些,也许可以通过将一个组件给另一个城市的友人来实现。进行这样的活动将帮助我们不仅欣赏物联网,而且总体上欣赏我们的互联网连接的世界。
摘要
在本章中,我们通过构建物联网警报仪表板完成了我们的物联网警报系统。我们首先通过考虑一个油罐的液位和温度的例子来理解物联网警报仪表板及其用法。在本章中,我们逐步介绍了设置、编码和测试我们的物联网警报仪表板的过程,包括添加一个外部蜂鸣器的定制支架。
我们使用 Kivy 在 Raspberry Pi 5 上构建了我们的物联网警报系统仪表板。该仪表板集成了地图功能,以确定任何激活的物联网警报模块的位置。
在我们结束对物联网警报系统的讨论时,我们期待着下一个令人兴奋的挑战:使用 LoRa 构建一个远程监控站。下一节将介绍我们关于长距离通信的知识,这将拓宽我们在物联网项目中的理解和能力。
第三部分:创建一个 LoRa 支持的物联网监控站
在这部分,我们将创建一个远程环境监控站,使用 LoRa 收集和传输温度和湿度数据,并通过互联网传输。我们将使用这些数据来控制一个模拟的天气指示器。
本部分包含以下章节:
-
第九章,理解 LoRa
-
第十章,将 LoRa 与互联网集成
第九章:理解 LoRa
在本章中,我们探索LoRa(即长距离)的世界,这是物联网通信的关键技术。LoRa 以其在最小功率下传输数据到长距离而闻名。我们将探讨其在农业等领域的实际应用,如实现大规模传感器网络的效率管理,以及在城市化环境中协助智能城市倡议,如街道照明控制。
我们还将研究射频频谱,了解不同频率如何分配给各种无线通信,使我们能够更好地理解 LoRa 技术的操作范围。通过研究频谱,我们可以确定哪些频段最适合 LoRa 传输。
然后,我们的重点转向实际方面:使用 Raspberry Pi Pico 和 Pico W 分别构建 LoRa 传感器发射器和 LoRa 接收器。我们将从组装发射器电路开始,整合 RFM95W LoRa 模块、DHT22 温度传感器和 Raspberry Pi Pico。然后,我们将将这些组件放入定制的 3D 打印外壳中。我们将强调使用标准的 Raspberry Pi Pico,因为它在不需要 Wi-Fi 的任务中效率高,得益于其较低的功耗和减少的固件开销。
对于接收器,我们使用 Raspberry Pi Pico W,专注于其 Wi-Fi 功能以供未来的开发使用。我们将为接收器构建一个定制外壳,就像发射器一样,但带有 LED 进行状态指示。
开发过程包括设置 CircuitPython,安装必要的库,以及编写发射器和接收器的代码。CircuitPython 是 MicroPython 的开源衍生产品,由 Adafruit 开发,旨在简化微控制器的编程。在我们的代码中,我们将使用传输之间的延迟来遵守欧洲占空比限制。
最后,我们将在户外测试我们的应用程序,展示 LoRa 令人印象深刻的范围能力,与 Wi-Fi 有限的范围相比。
本章我们将探讨以下主题:
-
探索 LoRa
-
构建 LoRa 传感器发射器
-
构建 LoRa 接收器
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
Python 编程的中级知识
-
1 x Raspberry Pi Pico WH(带引脚)用于开发(可添加引脚到 Raspberry Pi Pico W)
-
1 x Pico GPIO 扩展器用于开发
-
1 x Raspberry Pi Pico(用于 LoRa 传感器发射器)
-
1 x Raspberry Pi Pico W(用于 LoRa 接收器)
-
2 x RFM95W LoRa 模块
-
1 x DHT22 温度和湿度传感器
-
1 x LED(任何颜色)
-
1 x 220 欧姆电阻
-
4 x M3 10 毫米螺栓
-
12 x M2 5 毫米螺丝
-
2 x M5 20 毫米螺栓
-
2 x M5 螺母
-
热胶枪
-
有访问 3D 打印机或 3D 打印机服务以打印定制外壳的能力
-
定制外壳的构建文件可在我们的 GitHub 仓库中找到
本章的代码和构建文件可在此处找到:
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter9
探索 LoRa
LoRa 是一种无线通信技术,以其能够在长距离传输数据同时消耗极低功率的能力而闻名。由于其效率和可靠性在各种环境中,LoRa 在物联网领域变得越来越重要。作为扩展无线通信范围的解决方案,LoRa 技术已经发展到在连接广泛的物联网设备中扮演关键角色的地步。
在以下章节中,在我们探讨频谱及其与 LoRa 的关系之前,我们将查看 LoRa 的实际应用。
LoRa 技术的实际应用
LoRa 技术在农业等领域发挥着重要作用,使农民能够在大片土地上部署传感器网络来监测土壤湿度、温度和其他关键参数,从而提高作物管理和资源效率。
LoRa 节点为现代化农业实践提供了一种经济有效的解决方案,与成本更高的替代方案(如基于长期演进(LTE)的系统或广泛的线缆传感器网络)形成对比。虽然 LTE 支持更密集的数据应用,但其更高的功率需求和运营成本可能成为障碍。
在图 9**.1中,我们看到一个由 AI 生成的图像,展示了用于测量现代农场土壤和天气条件的 LoRa 节点:

图 9.1 – 使用 LoRa 技术的现代农场
在城市环境中,LoRa 可能被用于管理智能城市应用,如街灯。在图 9**.2中,我们看到一个用于城市环境的智能路灯。在这种情况下,灯光的控制由一个中央办公室通过发送到路灯的 LoRa 消息来决定是打开或关闭,或者控制其亮度:

图 9.2 – 使用 LoRa 消息进行控制的智能路灯
这种控制方法有益,因为它允许根据实时数据和需求远程管理照明,例如根据交通状况或一天中的时间调整亮度。
为了更好地理解 LoRa 通信技术的应用,让我们考察它所使用的射频频谱,重点关注不同频率如何满足各种应用需求和场景。
调查射频频谱
无线电频谱被用于各种无线通信,包括电视广播、移动数据、卫星以及 LoRa 等物联网技术。频率被分配给特定用途,以避免干扰并优化通信效率。例如,超高频(UHF)波段通常用于电视广播、移动电话和 Wi-Fi。每个频段都有独特的特性,如传播范围和穿透能力,使它们适用于特定应用。
通常,低频,以其较长的波长为特征,可以传播更远的距离,并且比高频更有效地穿透障碍。相反,高频虽然波长较短,但由于其较大的带宽,能够携带更多的数据。这些特性对于确定不同类型通信的合适频段至关重要。
低频穿透力的一个常见例子是,当我们身处俱乐部外时,经常能听到音乐中低音或低频部分,但不会听到高频部分,因为低频比高频更擅长穿透俱乐部的墙壁。这种穿透物体的能力使得低频特别适用于需要覆盖较大区域或穿越障碍的通信。例如,像 AM 广播中使用的低频波段可以覆盖广阔的地理区域,而尽管高频提供了更多的带宽,但它们的覆盖范围较短,更适合具有视距(LOS)通信的城市环境。高频对于像手机通信这样的技术来说更受欢迎,因为它们可以携带更多的数据,提供更大的语音和数据传输容量。在 图 9**.3 中,我们可以看到用于无线通信及其特定频段内运行的相关技术的频率谱图:

图 9.3 – 无线通信的频率谱
LoRa 在欧洲运行于 867-869 MHz 的未授权频段,在北美为 902-928 MHz,在澳大利亚为 915-928 MHz (图 9**.4). 这些频率的选择是为了平衡其覆盖范围和穿透力,非常适合 LoRa 所支持的低功耗、长距离通信。
具体的频段可以在 图 9**.4 中所示的范围内根据国家法规有所不同。我们应始终检查当地法规,以确保符合允许的特定频率:

图 9.4 – 根据位置确定的 LoRa 频率
使用未授权频段伴随着监管限制,以确保公平使用并最小化干扰。在欧洲,868 MHz 频段受到 1%的占空比限制,限制了传输时间。在北美,915 MHz 频段有逗留时间限制,限制了信号在信道上的占用时间。
这些限制鼓励了频谱的有效使用和创新通信协议的开发,正如 LoRa 在这些框架中的有效性所展示的那样。在我们即将在下一节中开始编写 LoRa 代码时,我们将考虑这些限制,构建一个 LoRa 传感器发射器。
现在我们已经对频谱和 LoRa 在其中的位置有了基本的了解,让我们来探讨扩频因子(SF),这是影响 LoRa 网络范围、数据速率和功率效率的关键参数。
理解 LoRa SF
LoRa 采用跳频扩频(CSS)技术,其中信号随时间变化频率以编码数据。这种技术提高了信号可靠性并最小化了功耗,使其非常适合长时间运行的物联网设备。LoRa 使用广泛的频率范围允许设备在长距离内保持连接,同时消耗更少的能量。在这个过程中,数据传输涉及在宽频谱内改变信号的频率,显著增强了其抗干扰和噪声的能力。
作为补充,LoRa 的 SF,这是 LoRa 通信中的一个关键参数,范围从SF7到SF12。SF 决定了每个符号(数据包)传输的持续时间,本质上平衡了传输范围和数据速率。较高的 SF 值,如SF12,可以扩展范围但降低数据速率,因此适合长距离通信。相比之下,较低的 SF 值,如SF7,在较短的距离上提供更快的速率。这种灵活性使得 LoRa 能够满足各种用例,从智能城市中人口密集的城市区域到需要长距离监控的偏远地区。
在图 9.5中,我们看到在农业环境中展示了 SF:

图 9.5 – 在农业环境中展示的 SF
随着 SF 数值的增加,范围也会增加,但更高的 SF 值由于传输时间的延长而需要更多的能量。为了有效通信,发送方和接收方必须配置相同的 SF。
现在我们已经探讨了 LoRa 通信技术,让我们将所学知识应用到 Raspberry Pi Pico 和 Pico W 上。
使用 Raspberry Pi Pico 和 Pico W 的 LoRa
在本章中,我们将开发一个 LoRa 传感器发射器和 LoRa 接收器。我们的 LoRa 传感器发射器将使用 Raspberry Pi Pico、DHT22 温度传感器和 RFM95W LoRa 模块。我们的 LoRa 接收器将使用 Raspberry Pi Pico W、LED 和 RFM95W LoRa 模块。我们可能会在 图 9.6 中看到我们的应用程序概述:

图 9.6 – LoRa 传感器发射器和 LoRa 接收器
我们将开始创建我们的应用程序,首先构建一个 LoRa 传感器发射器。在我们将温度传感器安装到测试电路中之前,我们将首先将 RFM95W LoRa 模块连接到 Raspberry Pi Pico。最后,我们将将这些组件安装到定制的 3D 打印机箱中。
构建 LoRa 传感器发射器
在本节中,我们将使用 Raspberry Pi Pico、RFM95W LoRa 模块和 DHT22 温度传感器构建一个带有温度传感器的 LoRa 传感器发射器。我们的目标是创建一个从 DHT22 温度传感器读取温度和湿度数据并使用 RFM95W LoRa 模块传输这些信息的设备。虽然我们可以轻松地将 Pico 替换为 Pico W,但我们将使用 Raspberry Pi Pico 而不是 Pico W 进行设计。
对于构建和测试我们的电路,我们将使用安装在 Pico GPIO 扩展器上的 Raspberry Pi Pico WH。使用 Pico WH 允许我们构建和测试适用于 Pico 和 Pico W 应用程序的电路。由于尺寸限制,我们将使用 Pico W 作为我们应用程序的接收器部分,而不是 Pico WH。
使用标准 Pico 而非 Pico W 的优势
除了成本优势外,Raspberry Pi Pico 相比于 Raspberry Pi Pico W,为我们提供的 LoRa 传感器发射器带来了更多好处。以其较低的功耗而闻名的标准 Pico,非常适合不需要 Wi-Fi 的任务。其更简单的设计导致固件开销减少,使设备能够将资源集中在特定任务上,而不是管理 Wi-Fi 连接。
我们还将构建一个定制的机箱来容纳我们的组件。这个机箱不仅提供保护,还将确保线路整齐,并提高我们 LoRa 传感器发射器的整体耐用性和便携性。我们将首先将电线添加到 RFM95W LoRa 模块,并将其连接到我们的 Raspberry Pi Pico WH 以进行开发。在我们将组件安装到定制机箱中时,我们将用 Pico 替换 Pico WH。
构建我们的电路
RFM95W 是一款紧凑型 LoRa 模块,因其长距离能力和低功耗而受到青睐。专为亚 GHz 频率操作设计,非常适合需要高效、长距离无线通信的应用。尽管体积小巧,RFM95W 在覆盖比传统无线技术更远距离方面表现出色,使其在开阔环境中特别有效。
我们将首先将跳线焊接在 RFM95W 上。
将电线添加到 RFM95W 上
RFM95W 模块的尺寸仅为 16 毫米乘以 16 毫米,非常小巧。在焊接跳线到 RFM95W 的孔时必须小心。这不是一个适合初学者焊接的工作。
在图 9.7的步骤 1中,我们看到在添加跳线之前 RFM95W 的样子。我们添加的跳线类型取决于我们选择的面包板选项。例如,在这种情况下,我们正在添加母跳线(图 9.7的步骤 2),因为我们将会使用 GPIO 扩展器为我们的 Raspberry Pi Pico WH:

图 9.7 – 将线连接到 RFM95W

图 9.8 – RFM95W 的接线引脚
我们不需要将线焊接在 RFM95W 的每个端子上。在图 9.8中,我们概述了需要跳线的端子和所需的线长:
我们的表格显示,我们使用的天线线长度取决于我们选择的 RFM95W LoRa 模块的具体频率型号。为了适应这种变化,我们设计了不同版本的定制外壳以适应每个型号。
每个版本都包含一个直天线线封装,有效地创建了一个针对相应频率定制的内置天线。这个设计特点对我们这些遇到过低成本天线无法准确匹配其标称频率的问题的人来说特别有吸引力。
Adafruit RFM95W LoRa 无线收发器扩展板
对于那些希望使用比标准 RFM95W 更大的 LoRa 板的人来说,Adafruit RFM95W LoRa 无线收发器扩展板是一个不错的选择。与我们的 RFM95W 不同,这个板子适合面包板使用,使用了引脚头。
为了加强 RFM95W 上跳线的焊接连接,我们可以使用热胶枪上的胶水,如图 9.7的步骤 3中详细说明。我们使用这些跳线将模块插入 Raspberry Pi Pico WH GPIO 扩展器进行初始测试和原型制作。
组装我们的电路
在将跳线焊接到我们的 RFM95W 模型后,我们现在可以在 Raspberry Pi Pico GPIO 扩展器上构建我们的电路。图 9.9展示了 Raspberry Pi Pico、Raspberry Pi Pico WH 和 RFM95W LoRa 模块的接线图:

图 9.9 – 将 RFM95W 模块连接到 Raspberry Pi Pico
为了完成我们的电路,我们使用图 9.10中的接线图将 DHT22 温度传感器添加到电路中:

图 9.10 – 将 DHT22 连接到 Raspberry Pi Pico (WH)
在图 9.11(A)中,我们通过使用带有 GPIO 扩展器的 Raspberry Pi Pico(在添加 DHT22 之前)来获得我们电路布局的实际视图。在我们的例子中,我们正在使用 Raspberry Pi Pico WH 和 GPIO 扩展器进行电路构建和测试:

图 9.11 – 配有 GPIO 扩展器和 RFM95W LoRa 模块的 Raspberry Pi Pico WH
我们可以在 图 9.11 (B) 中看到一个表格,显示了线缆连接,以及我们的 Raspberry Pi Pico 和 RFM95W 连接在一起时的样子(图 9.11 (C))。
在我们的电路连接好之后,我们现在可以编写代码,通过 LoRa 发送温度和湿度数据。我们将使用 Thonny 作为我们的开发环境,并使用 CircuitPython 固件。
开发代码
由于这些 Python 实现之间存在几个关键差异,因此我们的应用程序将使用 CircuitPython 而不是 MicroPython。CircuitPython 是 Adafruit 开发的 MicroPython 衍生品,为特定用例提供了更流畅的体验,特别是其全面的库支持。
我们将首先通过将 CircuitPython 固件安装到我们的 Raspberry Pi Pico WH 上来开始我们的开发。
设置 CircuitPython 和传感器库
我们使用 Thonny IDE 为 Raspberry Pi Pico (WH) 开发我们的应用程序,这是一个兼容各种开发环境(如 Raspberry Pi、Windows、Linux 和 macOS)的工具。在我们的示例中,我们正在 Windows 上使用 Thonny。
在设置 Raspberry Pi Pico (WH) 方面,通过 Thonny 安装 CircuitPython 是一个简单的过程,就像我们安装 MicroPython 一样。
要在我们的 Raspberry Pi Pico (WH) 上安装 CircuitPython,我们执行以下操作:
-
如果我们的操作系统上没有 Thonny,我们访问 Thonny 网站,下载合适的版本(
thonny.org)。 -
然后,我们使用适合我们操作系统的适当方法启动 Thonny。
-
在按住 Pico(WH)上的 BOOTSEL 按钮的同时,即靠近 USB 端口的白色小按钮,我们将它插入一个可用的 USB 端口,并忽略可能出现的任何弹出窗口:

图 9.12 – Pico WH(与 Pico 相似)上的 BOOTSEL 按钮
- 然后,我们在屏幕右下角点击解释器信息,并选择安装 CircuitPython…:

图 9.13 – 安装 CircuitPython… 选项
- 对于
RPI-RP2 (D:))。在我们的示例中,我们选择了Raspberry Pi • Pico / Pico H CircuitPython 变体和最新版本:

图 9.14 – 在 Raspberry Pi Pico W 上安装 MicroPython
-
尽管我们在 Pico WH 上进行开发,但我们将其视为一个用于开发的 Pico。我们点击安装按钮,然后在安装完成后点击关闭按钮。
-
要将 Thonny 配置为在我们的 Pico(WH)上运行 CircuitPython 解释器,我们从屏幕右下角选择它:

图 9.15 – 选择 CircuitPython 解释器
- 我们通过检查 Shell 来确认 Thonny 是否在我们的 Raspberry Pi Pico (WH) 上使用 CircuitPython 解释器:

图 9.16 – Thonny 中的 CircuitPython 提示
在安装了 CircuitPython 之后,我们现在可以安装我们代码所需的库。这涉及到从 Adafruit 网站下载包,并将我们需要的库文件复制到我们的 Raspberry Pi Pico (WH) 上。
要这样做,我们执行以下操作:
-
使用网页浏览器,我们导航到以下 URL:
circuitpython.org/libraries。 -
由于我们使用的是 CircuitPython 8,我们下载
adafruit-circuitpython-bundle-8.x-mpy-20231205.zipZIP 文件,并将其解压到我们的计算机上的某个位置。 -
我们感兴趣的文件是
adafruit_rfm9x.mpy和adafruit_dht.mpy,这两个文件都可以在解压目录的lib文件夹中找到。这些文件分别是我们的 RFM95W 和 DHT22 传感器的库文件。要从 Thonny 将这些库安装到我们的 Raspberry Pi Pico (WH) 上,我们在 文件 部分找到它们,然后右键点击以获取以下对话框:

图 9.17 – 将库文件上传到 Raspberry Pi Pico (WH)
- 我们必须确保将这些库上传到我们的 Pico (WH) 上的
lib文件夹,而不是根目录。这涉及到在 Thonny 的 CircuitPython 设备 部分下双击lib文件夹以打开它。在将库上传到 Pico (WH) 之后,我们的 Pico (WH) 上的文件结构应该如下所示:

图 9.18 – 上传库后 Pico 文件结构
在我们的电路搭建好,并安装了 CircuitPython 和库之后,现在是时候编写我们的代码了。正如我们将看到的,我们不需要大量的代码就能通过 LoRa 发送温度和湿度数据。
创建 LoRa 传输代码
为了符合不同的监管标准,我们将为我们的 LoRa 传输使用延迟。虽然欧洲的 1% 负载周期限制允许消息之间有 99 秒的延迟,但鉴于温度和湿度数据的稳定性,我们将此延迟扩展到 120 秒,这将导致负载周期大约为 0.83%。
尽管北美法规侧重于占用时间,即发射机占用频率通道的时间段而不是负载周期,但我们为了统一性采用这种负载周期方法。由于作者位于北美,我们将使用 RFM95W 的 915 MHz 频率版本来应用我们的应用。
要编写我们的 LoRa 传输代码,我们执行以下操作:
-
我们将我们的 Raspberry Pi Pico (WH) 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来完成这个操作。在我们的例子中,我们正在 Windows 上使用 Thonny。
-
然后,我们从屏幕的右下角选择 Pico (WH) 上的 CircuitPython 环境,以激活它。
-
在一个新标签页中,我们输入以下代码:
import time import board import busio import digitalio import adafruit_rfm9x import adafruit_dht spi = busio.SPI(board.GP18, MOSI=board.GP19, MISO=board.GP16) cs = digitalio.DigitalInOut(board.GP17) rst = digitalio.DigitalInOut(board.GP14) rfm9x = adafruit_rfm9x.RFM9x(spi, cs, rst, 915.0) dht_sensor = adafruit_dht.DHT22(board.GP4) print("Sending temperature and humidity data every 120 seconds") while True: try: temperature = dht_sensor.temperature humidity = dht_sensor.humidity data = f"Temp: {temperature}C, Humidity: {humidity}%" rfm9x.send(bytes(data, "utf-8")) print("Sent:", data) except RuntimeError as e: print("DHT22 read error:", e) time.sleep(120)在我们的代码中,我们执行以下操作:
-
time,board,busio,digitalio,adafruit_rfm9x(用于 LoRa 通信)和adafruit_dht(用于 DHT22 传感器)。 -
然后设置 SPI 通信:我们使用特定的 GPIO 引脚(GP18,GP19,GP16)配置 SPI,用于 RFM95W LoRa 模块。
-
我们初始化芯片选择(CS)和复位(RST)引脚:我们为 CS(GP17)和 RST(GP14)引脚设置数字 I/O。
-
然后创建一个 RFM95W LoRa 对象:我们初始化 RFM9x 对象以在 915.0 MHz 的频率上进行 LoRa 通信。此值应根据当地法规设置。
-
我们初始化 DHT22 传感器:我们在 GPIO 4(GP4)上设置 DHT22 温度和湿度传感器。
-
然后打印一个状态消息:我们指示每 120 秒将发送温度和湿度数据。
-
我们持续发送数据:在一个无限循环中,我们从 DHT22 传感器读取温度和湿度,格式化数据,并通过 LoRa 发送。如果发生读取错误,我们打印错误消息。
-
然后我们在传输之间延迟:我们在发送下一组数据之前等待 120 秒。
-
-
要保存文件,我们从下拉菜单中选择 文件 | 另存为...。这将打开以下对话框:

图 9.19 – 将文件保存到我们的 Raspberry Pi Pico (WH)
-
在此对话框中,我们被提供选择文件存储位置的选择。要将文件保存到我们的 Raspberry Pi Pico (WH),我们点击相应的按钮。
-
然后,我们将文件命名为
code.py并点击code.py是特殊的,因为系统在启动或重置时自动执行此文件,使其成为设备启动时运行的默认脚本。
我们使用的是哪个 SF?
在我们的代码中,我们没有明确设置 SF,因此使用的是 adafruit_rfm9x 库的默认值。通常,这是一个 SF 为 7 的值。由于我们正在使用默认的 SF 为 transmit 和 receive 节点,因此在我们的应用程序中不需要关注此设置。
-
要运行我们的代码,我们点击绿色运行按钮,在键盘上按 F5,或者点击顶部菜单的 运行 选项,然后点击 运行 当前脚本。
-
在 Shell 中,我们将看到一个通知,确认已创建包含温度和湿度数据的 LoRa 消息:

图 9.20 – LoRa 消息通知
回顾一下,我们刚刚创建了一个无线发送温度和湿度传感数据的 LoRa 传感器发射器。如果没有错误,我们可以合理地假设我们的 LoRa 消息已成功传输。然而,没有 LoRa 接收器,我们无法确认这一点。我们将在下一节中通过构建一个接收器来解决这个问题。在构建接收器之前,我们首先将我们的组件放入一个定制的 3D 打印外壳中。
在定制外壳中安装组件
继续我们之前项目的实践,我们将把我们的组件安装在一个定制外壳中。这种方法允许我们在需要的地方方便地放置我们的 LoRa 传感器发射器。我们可以在 图 9**.21 中看到我们的 LoRa 传感器发射器的定制外壳。
我们的定制外壳设计容纳了 DHT22 传感器,允许它从前端伸出,以进行准确的温度和湿度读取。天线,一根焊接在 RFM95W 模块上的直导线,被放置在连接到外壳底板的突出部分中。一个专门为此目的设计的天线盖完成了封闭,保护并隔离了天线。定制外壳的 915 MHz 版本在 图 9**.21 中显示:

图 9.21 – 用于我们 LoRa 发射节点的定制外壳
我们的定制外壳在后面设计了一个多功能的 GoPro 式挂钩,使得它能够与我们在本书早期章节中构建的各种支架兼容。这一特性使得我们的 LoRa 传感器发射器易于且灵活地安装。
Raspberry Pi Pico 上的微型 USB 端口是暴露的,这使得我们可以为我们的设备供电。我们也可以使用这个端口来编程我们的 Raspberry Pi Pico。
使用移动电源为我们的 Raspberry Pi Pico 供电
我们可以使用标准的手机移动电源远程为我们的节点供电。然而,选择一个不会因为低功耗而自动关机的移动电源很重要,因为 Raspberry Pi Pico 的功耗要求很低。
我们将开始构建我们的定制外壳,首先识别部件。
识别我们的定制 LoRa 外壳的部件
我们的定制外壳由 3D 打印部件组成,可以拧在一起。我们可以在 图 9**.22 中看到部件和主要组件:

图 9.22 – 定制外壳的部件
让我们逐一分析每个部件:
-
Raspberry Pi Pico (A 在 图 9**.22): 由于空间限制,我们使用无引脚版本的 Raspberry Pi Pico。
-
后盖 (B 在 图 9**.22): 后盖固定了 Raspberry Pi Pico 和 RFM95W LoRa 模块。天线延长部分的长度基于所使用的频率模型。在这个例子中,我们看到的是 915 MHz 模型。
-
天线盖 (C 在 图 9**.22): 天线盖用于将带后盖 (B) 的线状天线封闭起来。
-
前壳(图 9.22中的D):前壳固定 DHT22 传感器,并封闭后板(图 9.22中的B),以完成外壳。
-
DHT22 温度和湿度传感器(图 9.22中的E)。
-
RFM95W LoRa 模块(图 9.22中的F):所示版本是 915 MHz 型号。
-
连接器(图 9.22中的G)。
-
2 x M3 10 mm 螺栓(未显示)。
-
4 x M2 5 mm 螺丝(未显示)。
在识别了部件后,现在是时候组装我们的定制外壳了。
构建定制的 LoRa 外壳
要构建定制外壳,我们遵循图 9.23中显示的步骤和以下概述:

图 9.23 – 构建定制外壳的步骤
-
我们首先使用环氧胶或两个 M2 5 mm 螺丝(图 9.23,步骤 1)将连接器(图 9.22中的G)固定在后板(图 9.22中的B)上。
-
使用图 9.9和图 9.10中的接线图,我们将 RFM95W 和 DHT11 传感器的电线焊接到 Raspberry Pi Pico 上。
-
使用四个 M2 5 mm 螺丝,我们将 Raspberry Pi Pico(图 9.22中的A)固定在后板(图 9.22中的B)上,使得 USB 端口向下并指向后板底部或远离天线(图 9.23,步骤 2)。
-
使用热胶枪,我们将 DHT22(图 9.22中的E)固定在前壳(图 9.22中的D)上。或者,根据 DHT22 上存在的孔,可以使用两个 M3 5 mm 螺栓(图 9.23,步骤 3)。
-
我们将 RFM95W(图 9.22中的F)摩擦固定在后板(图 9.22中的B)上,使得天线线紧挨着后板上的线槽并延伸出来(图 9.23,步骤 3)。如果 RFM95W 没有固定好,可以使用热胶枪的胶水将其固定在原位。
-
使用两个 M3 10 mm 螺栓,我们将后板固定到前壳上(图 9.23,步骤 4)。
-
我们使用两个 M2 5 mm 螺丝将天线盖(图 9.22中的C)固定在后板的前面。
由于我们使用了一个单独的 Raspberry Pi Pico WH 来编写代码,因此我们需要在新的 Pico 上安装 CircuitPython、必要的库以及我们的代码。建议使用 Thonny 测试代码,以检查在将组件安装到定制外壳中时可能出现的任何问题。
现在我们已经准备好了 LoRa 传感器发射器,我们将继续构建一个 LoRa 接收器。这个设备将负责从发射器接收传感器信息。我们将保持接收器设计简单,其主要功能是确认接收 LoRa 消息。为此,一个 LED 灯足以指示状态。
构建 LoRa 接收器
在*图 9**.6 中,我们看到 LoRa 接收器正在处理来自我们的 LoRa 传感器发射器的消息。我们的接收器设计简单,只需要一个 LED 来确认接收到的消息。我们正在使用 Raspberry Pi Pico W 作为接收器,因为我们计划在下一章利用其 Wi-Fi 功能并将传感器数据发布到互联网。
我们不会介绍在 LoRa 接收器的 Pico W 上安装 CircuitPython 或所需库的步骤,因为我们已经为 LoRa 传感器发射器介绍了这些步骤,我们只需为接收器做相同的事情。我们应该使用本项目的 Pico W 版本的 CircuitPython,因为我们将在下一章实现 Wi-Fi 功能。
此外,我们不会详细说明为 LoRa 接收器构建定制外壳的过程,因为它与发射器的过程类似。关键的区别是在前壳中用带有电阻和 LED 支架的 LED 替换 DHT22 传感器。安装 LED 的步骤在第六章的图 6.22和图 6.23中已有概述。
在本节中,我们将重点关注 LoRa 接收器的代码,并突出展示 LoRa 传感器发射器和 LoRa 接收器在户外测试的结果。
我们将从 LED 与电阻连接到 Raspberry Pi Pico W 的布线图开始。
将 LED 连接到 Raspberry Pi Pico W
对于我们的 LoRa 接收器,我们需要一个 LED 来用于确认 LoRa 消息。根据我们在本书中到目前为止所学的内容,我们可以轻松地通过添加一个 OLED 屏幕等更健壮的视觉方式来增强我们的 LoRa 接收器。由于我们旨在仅关注确认 LoRa 信号,我们将坚持使用简单的 LED。我们可以在运行 LoRa 接收器的同时,在 Thonny 的 Shell 中监控 LoRa 消息:

图 9.24 – Raspberry Pi Pico W,220 欧姆电阻和 LED
要将 LED 连接到我们的 Raspberry Pi Pico W,我们需要在 LED 的正极上焊接一个 220 欧姆的电阻。然后我们将电阻连接到 Pico 的 GP5 端口。对于地线,我们将 LED 的负极连接到 Pico W 上的任何 GND 引脚(图 9**.24)。
将 220 欧姆电阻和 LED 连接到我们的 Pico W 后,我们使用上一节中构建 LoRa 传感器发射器的步骤将 RMM95W LoRa 模块连接到我们的 Pico W。
现在我们已经准备好开始编写代码。
创建接收 LoRa 消息的代码
我们的 LoRa 接收器代码使用 Adafruit adafruit_rfm9x库来监听 LoRa 消息。在接收到消息后,它将消息打印到 Thonny 的 Shell 中,并使 LED 闪烁两次。
要编写我们的 LoRa 接收器代码,我们需要做以下几步:
-
我们将 Raspberry Pi Pico W 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来完成这项工作。
-
然后,我们从屏幕的右下角选择 CircuitPython 环境,以激活我们的 Pico W。
-
在一个新标签页中,我们输入以下代码:
import time import board import busio import digitalio import adafruit_rfm9x spi = busio.SPI(board.GP18, MOSI=board.GP19, MISO=board.GP16) cs = digitalio.DigitalInOut(board.GP17) rst = digitalio.DigitalInOut(board.GP14) rfm9x = adafruit_rfm9x.RFM9x(spi, cs, rst, 915.0) led = digitalio.DigitalInOut(board.GP5) led.direction = digitalio.Direction.OUTPUT print("Listening for LoRa messages...") def flash_led(times, duration): for _ in range(times): led.value = True time.sleep(duration) led.value = False time.sleep(duration) while True: packet = rfm9x.receive() if packet is not None: print("Received message:", packet) flash_led(2, 0.5)在我们的代码中,我们执行以下操作:
-
我们首先导入必要的库:
time、board、busio、digitalio和adafruit_rfm9x用于 LoRa 通信。 -
然后我们通过配置 SPI 使用 GPIO 引脚 GP18(SCK)、GP19(MOSI)和 GP16(MISO)来设置 SPI 通信。
-
我们通过将 GP17 设置为 CS 和 GP14 设置为 RST 来初始化 CS 和 RST 引脚。
-
我们创建了一个 RFM95W LoRa 对象,并初始化该对象以进行 915.0 MHz 的通信。
-
我们在 GP5 初始化一个 LED。
-
然后我们打印一条状态消息,表明设备正在监听 LoRa 消息。
-
我们定义了一个用于闪烁 LED 的函数,这样我们就可以用设定的持续时间闪烁 LED 指定次数。
-
在一个连续的循环中,我们监听 LoRa 消息,检查传入的 LoRa 数据包,打印任何接收到的消息,并在接收到数据包时闪烁 LED 两次,每次 0.5 秒。
-
-
要保存文件,我们从下拉菜单中选择文件 | 另存为...。这将打开保存位置对话框。
-
在此对话框中,我们被提供了选择文件存储位置的选择。为了将其保存在我们的 Raspberry Pi Pico W 上,我们点击相应的按钮。
-
然后我们将文件命名为
code.py并点击确定。 -
要运行我们的代码,我们点击绿色运行按钮,在键盘上按F5,或者点击顶部的运行菜单选项,然后点击运行 当前脚本。
-
如果它还没有运行,我们打开电源并运行 LoRa 传感器发射器。
-
在 Shell 中,我们将看到一条通知,确认收到了 LoRa 消息:

图 9.25 – 接收 LoRa 消息
- 我们还应该观察我们的 LED 闪烁两次。
正面结果不仅证实了我们的 LoRa 接收器工作正常,而且我们的 LoRa 传感器发射器也是如此。为了充分利用我们的应用程序,我们需要将我们的发射器和接收器放在室外。为此,我们应该将我们的 LoRa 接收器安装在其自己的定制外壳中。正如提到的,我们遵循为 LoRa 传感器发射器概述的相同步骤,用电阻代替 LED,用 DHT22 温度传感器代替 LED 支架。
在 LoRa 传感器发射器和 LoRa 都准备好后,是时候将我们的应用程序带到户外了。
测试我们的应用程序
LoRa 通信以其覆盖长距离的能力而闻名,这是它在无线通信技术领域的独特之处。虽然典型的 LoRa 传输在城市环境中从几公里到农村地区超过 10 公里,但在最佳条件下,这项技术已经显示出更大的潜力。使用仅 25 mW 的传输功率,一项世界纪录被打破,LoRa 传输达到了 766 公里(476 英里)。这一记录突出了 LoRa 卓越的远程能力,尤其是在条件有利且设置优化以实现最大范围的情况下。
在图 9.26中,我们观察了在 160 米距离上测试我们应用程序的结果:

图 9.26 – 在 160 米距离上测试我们的 LoRa 传感器发射器和 LoRa 接收器
交替的测试方法
我们还可以通过应用吹风机等热源的热量来测试我们的应用程序,或者甚至将我们的 LoRa 发射器放入冰箱中并观察结果。
对于 LoRa 来说,这个距离虽然不大,但在考虑诸如农场监控等应用时却具有重要意义,在这些应用中,传感器可以分布在广阔的区域,远远超出传统 Wi-Fi 网络的覆盖范围,Wi-Fi 网络通常在室内约 50 米,在开阔空间中不到 100 米。
摘要
在本章中,我们探讨了 LoRa 技术,这是物联网通信的一个重要组成部分。我们首先讨论了 LoRa 在低功耗下传输数据的能力,强调了它在无线技术中的重要性。我们研究了其在农业中的应用,它改善了作物管理中的传感器网络管理,以及在城市环境中的智能城市倡议,如街道照明控制。
我们随后检查了 LoRa 在射频频谱中的技术方面。这包括理解频率是如何分配给无线通信的,这对于确定 LoRa 传输的正确频段非常重要。
在我们的动手部分,我们分别使用 Raspberry Pi Pico 和 Pico W 构建了 LoRa 传感器发射器和 LoRa 接收器。这包括构建一个带有 LoRa 模块和温度传感器的发射器,以及使用简单的 LED 确认的 LoRa 接收器。
在下一章中,我们将进一步探讨,我们将我们的 LoRa 接收器连接到互联网,并使用它来控制我们在第三章中创建的新版本模拟仪表式天气指示器。
第十章:将 LoRa 与互联网集成
在本章中,我们将从我们远程放置的 LoRa 传感器发射器获取传感信息,并使用配备 Raspberry Pi Pico W 的LoRa接收器将其发布到互联网。我们的互联网目的地将是 CloudAMQP 服务器上的 MQTT 实例。然后,我们将我们创建的修改过的模拟仪表天气指示器连接到 MQTT 服务器,并使用这些数据根据湿度读数定位指针和设置 LED 指示器的颜色。这种修改将涉及用 Raspberry Pi Pico WH(也称为带引脚的 Raspberry Pi Pico W)替换安装在模拟仪表天气指示器上的 Raspberry Pi,以及将单色 LED 替换为 RGB LED。
通过从标准天气网络服务过渡到使用远程 LoRa 传感器发射器来测量温度和湿度,并将这些数据转换为 MQTT 消息,我们实际上创建了一个定制的天气网络服务——一个由 LoRa 技术提供数据传输需求,MQTT 提供互联网通信的服务。
我们将以探讨各种其他物联网(IoT)通信技术来结束本章,例如LoRaWAN和蜂窝技术,并探讨每种技术的优缺点。
本章我们将涵盖以下内容:
-
将我们的 LoRa 接收器连接到互联网
-
创建一个新的天气指示器
-
探索其他物联网通信协议
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
具备 Python 编程的中间知识。
-
1 X Raspberry Pi Pico WH。
-
1 X 来自第九章的 LoRa 传感器发射器。
-
1 X 使用 Raspberry Pi Pico W 构建的 LoRa 接收器,见第九章。
-
用于 MQTT 服务器实例的 CloudAMQP 账户。
-
1 X SG90 伺服电机。
-
1 X 阳极 RGB LED。
-
3 X 220 欧姆电阻。
-
1 X 8 mm LED 支架。
-
9 X M3 10 mm 螺栓。
-
4 X M2 8 mm 螺钉。
-
1 X M5 20 mm 螺栓。
-
1 X M5 螺母。
-
用于构建天气指示器面板的环氧胶。
-
一把热胶枪。
-
有彩色打印机以打印面板图形。
-
一台数字切割机,如 Silhouette Cameo。这是可选的,因为面板图形可以手工切割。提供 Silhouette Studio 3 文件。
-
有访问 3D 打印机以打印天气指示器支架的能力。
本章的代码可以在以下位置找到:github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter10
将我们的 LoRa 接收器连接到互联网
我们的首要任务是修改基于 CircuitPython 的 LoRa 接收器的代码,使其能够转发由我们的 CloudAMQP 服务器接收到的 LoRa 消息。我们将使用 Adafruit CircuitPython 库进行此更新。在我们构建并编程新的天气指示器以处理 MQTT 消息之前,我们将使用 Windows 中的 MQTT-Explorer 应用程序测试 MQTT 功能。以下图表概述了本章的项目:

图 10.1 – 使用 LoRa 传输的传感器数据控制模拟仪表的天气指示器
在图 10.1 中,我们可以看到我们的 Raspberry Pi Pico LoRa 传感器发射器从第九章发送温度和湿度数据通过 LoRa 到我们也在第九章中构建的 LoRa 接收器。我们不需要更改发射器的代码,因为它执行了我们需要的操作。正如提到的,我们将根据需要更新 LoRa 接收器的代码,以便它能够连接到互联网并发送 MQTT 消息。
一旦我们确认我们的 MQTT 代码工作正常,我们将构建天气指示器的新版本,并编程它以相应地响应 MQTT 消息。
我们将首先将 CircuitPython MQTT 库添加到我们的 LoRa 接收器中。
安装 MQTT 的 CircuitPython 库
微控制器 MQTT 库使物联网设备能够使用 MQTT 协议,这是一种针对低带宽和最小设备资源优化的消息系统。这些库为各种微控制器平台设计,使设备能够连接到 MQTT 代理,发布消息和订阅主题。
在这些库中,Adafruit MiniMQTT 库对于 CircuitPython 设备来说尤为突出。这个库提供了一个简单的 API,适用于如 Raspberry Pi Pico W 这样的板子。它支持关键的 MQTT 功能,如发布/订阅,并与各种 MQTT 代理一起工作。
要安装 MiniMQTT 库,我们执行以下操作:
-
使用网络浏览器,我们导航到 URL
circuitpython.org/libraries。 -
由于我们使用的是 CircuitPython 8,我们下载
adafruit-circuitpython-bundle-8.x-mpy-20231205.zipZIP 文件,并将其解压缩到我们的计算机上的某个位置。 -
我们希望将库文件安装到我们的 Raspberry Pi Pico W 的
lib文件夹中,而不是根目录。因此,我们需要在 Thonny 的CircuitPython部分下双击lib文件夹以打开它。 -
我们感兴趣的 Adafruit 库中的文件位于
adafruit_minimqtt文件夹中。要将这些文件安装到我们的 Raspberry Pi Pico W 上(使用 Thonny),我们在 Thonny 的文件部分打开文件夹,然后右键单击以获取以下对话框:

图 10.2 – 将 MQTT 库上传到 Raspberry Pi Pico
- 在 Pico W 上上传库后,我们的 Pico 上的文件结构应如下所示:

图 10.3 – 上传 Mini MQTT 后的 Pico W 文件结构
在我们的 Raspberry Pi Pico W 上设置了 Adafruit MQTT 库后,我们的下一步是配置一个 CloudAMQP 实例来作为我们的 MQTT 消息的代理。
让我们立即这样做。
为我们的应用程序创建一个 CloudAMQP 实例
在 第七章 中已经设置了一个账户,我们现在准备创建一个 CloudAMQP 实例,作为从我们的 Raspberry Pi Pico W 发送的 MQTT 消息的代理。按照 第七章 中“设置 CloudAMQP 实例”部分的说明,我们创建了一个新实例,命名为 RemoteWeatherStation,并记录详细信息以供我们在 LoRa 接收器代码中使用。
我们将利用 CloudAMQP 实例的 WebSocket UI 来观察来自我们的 LoRa 发射器的消息,使我们能够在构建和实施新的天气指示器之前彻底测试我们的应用程序。
让我们继续修改 LoRa 接收器上的代码,添加 MQTT 功能,使其能够向 RemoteWeatherStation 实例发布 MQTT 消息。
向 LoRa 接收器添加 MQTT 功能
在创建 CloudAMQP 实例后,我们现在将增强 LoRa 接收器上的代码,以包含 Wi-Fi 和 MQTT 功能。这种修改使接收器能够有效地利用 LoRa 从我们的发射器接收传感数据,然后利用 Wi-Fi 进行互联网连接。
利用 MQTT 协议,接收器可以将这些传感数据传输到我们的 CloudAMQP 服务器。这种集成不仅展示了不同通信技术之间的协同作用——LoRa 用于本地、长距离传输,而 MQTT 通过 Wi-Fi 用于全球覆盖——而且极大地扩展了我们的物联网生态系统功能。我们的 LoRa 接收器成为一座桥梁,在本地收集传感信息,并通过互联网分发。
要修改 LoRa 接收器代码,我们执行以下操作:
-
我们将我们的 Raspberry Pi Pico W 从 LoRa 接收器连接到 USB 端口,并使用我们选择的计算机和操作系统启动 Thonny。
-
然后,我们通过选择屏幕右下角来激活我们的 Pico W 上的 CircuitPython 环境。
-
在编辑器部分的新标签页(按 Ctrl + N),我们首先输入我们的导入:
import time import board import busio import digitalio import adafruit_rfm9x import wifi import socketpool from adafruit_minimqtt.adafruit_minimqtt import MQTT在我们的代码中,我们有以下内容:
-
time库用于代码中的计时和延迟。 -
board模块提供了对微控制器基本引脚设置的访问。 -
busio模块用于创建总线通信接口,这对于像 RFM9x LoRa 模块这样的设备至关重要。 -
digitalio用于管理数字 I/O 引脚。 -
adafruit_rfm9x模块专门用于与安装在 LoRa 接收器上的 RFM95W LoRa 无线电模块进行接口。 -
包含了
wifi模块来处理微控制器上的 Wi-Fi 连接。 -
socketpool提供了一种管理网络套接字的方法,这对于互联网通信是必需的。在我们的 CircuitPython 代码中,此模块对于高效管理网络套接字至关重要,这对于通过adafruit_minimqtt库进行 MQTT 通信是必不可少的。它通过 Wi-Fi 提供稳定的 TCP/IP 连接。 -
导入
adafruit_minimqtt以启用 MQTT 协议通信,允许设备发布和订阅 MQTT 主题。
-
-
在导入之后,我们设置我们的 Wi-Fi 网络(SSID)和 Wi-Fi 密码:
WIFI_SSID = 'MySSID' WIFI_PASSWORD = 'wifi-password' -
然后,我们进入代码以初始化
GP5GPIO 引脚作为数字输出,以控制连接到该引脚上的微控制器上的 LED:led = digitalio.DigitalInOut(board.GP5) led.direction = digitalio.Direction.OUTPUT -
我们定义了
flash_led()方法,该方法闪烁 LED 指定次数,每次闪烁的持续时间由秒数设置:def flash_led(times, duration): for _ in range(times): led.value = True time.sleep(duration) led.value = False time.sleep(duration) -
接下来,我们定义
connect_to_wifi()函数。此函数反复尝试使用提供的 SSID 和密码连接到 Wi-Fi,通过闪烁 LED 两次,每次持续 2 秒,然后暂停 5 秒再重试来表示失败的尝试。在成功连接后,它退出循环,并闪烁 LED 四次,每次持续 1 秒,以表示成功的 Wi-Fi 连接:def connect_to_wifi(ssid, password): while True: try: print("Trying to connect to WiFi...") wifi.radio.connect(ssid, password) print("Connected to Wi-Fi!") flash_led(4, 1) break except Exception as e: print("Failed to connect to WiFi. Retrying...") flash_led(2, 2) time.sleep(5) connect_to_wifi(WIFI_SSID, WIFI_PASSWORD) flash_led(4, 1) -
然后,我们创建了一个名为
pool的变量,它是socketpool.SocketPool的一个实例,使用wifi.radio对象,该对象管理和提供 Wi-Fi 通信的网络套接字连接:pool = socketpool.SocketPool(wifi.radio) -
接下来,我们的代码设置 MQTT 配置,定义服务器地址 (
MQTT_SERVER)、端口号 (MQTT_PORT)、用户凭据 (USERNAME和PASSWORD)、设备标识符 (DEVICE_ID) 和 MQTT 主题 (MQTT_TOPIC) 以进行通信。我们从为我们的应用程序设置的 CloudAMQP 实例中获取这些值:MQTT_SERVER = "mqtt-server-url" MQTT_PORT = 18756 USERNAME = "instance-username" PASSWORD = "instance-password" DEVICE_ID = "LoRaReceiver" MQTT_TOPIC = "WeatherInfo" -
然后,我们将
GP18、GP19和GP16分别配置为 SCK、MOSI 和 MISO,并初始化数字 I/O 引脚GP17和GP14以用于 芯片选择 (CS) 和 复位 (RST) 功能。CS 引脚用于选择 LoRa 模块进行通信,而 RST 引脚用于复位模块,确保它从已知状态开始:spi = busio.SPI(board.GP18, MOSI=board.GP19, MISO=board.GP16) cs = digitalio.DigitalInOut(board.GP17) rst = digitalio.DigitalInOut(board.GP14) -
我们通过创建一个
adafruit_rfm9x.RFM9x实例来初始化 RFM9x LoRa 无线电模块,该实例使用先前配置的 SPI 总线 (spi)、芯片选择 (cs) 和复位 (rst) 引脚,并将工作频率设置为 915.0 MHz,因为我们的示例是为北美使用而构建的:rfm9x = adafruit_rfm9x.RFM9x(spi, cs, rst, 915.0) -
然后,我们通过创建 MQTT 实例并指定 MQTT 代理详情(
MQTT_SERVER和MQTT_PORT)、用户凭据(USERNAME和PASSWORD)以及先前创建的套接字池(pool)来设置 MQTT 客户端,用于网络通信:mqtt_client = MQTT(broker=MQTT_SERVER, port=MQTT_PORT, username=USERNAME, password=PASSWORD, socket_pool=pool) -
我们的代码将 MQTT 客户端连接到 MQTT 代理,并打印一条消息,表明系统现在已准备好监听传入的 LoRa 消息:
mqtt_client.connect() print("Listening for LoRa messages...") -
我们创建一个连续循环来检查 RFM9x LoRa 模块的传入数据包;接收到数据包后,我们的代码将消息解码为 UTF-8 格式,打印接收到的消息,LED 闪烁两次,每次 0.5 秒,然后将消息发布到指定的 MQTT 主题,并打印已发送 MQTT 消息的确认信息:
while True: packet = rfm9x.receive() if packet is not None: message = packet.decode("utf-8") print("Received LoRa message:", message) flash_led(2, 0.5) mqtt_client.publish(MQTT_TOPIC, message) print("Sent MQTT message:", message) -
要保存文件,我们从下拉菜单中选择文件 | 另存为...。这将打开保存位置对话框。

图 10.4 – 将文件保存到我们的 Raspberry Pi Pico W
-
在此对话框中,我们有机会选择文件存储的位置。要将文件保存在我们的 Raspberry Pi Pico W(CircuitPython 设备)上,我们点击相应的按钮。
-
然后,我们将文件命名为
code.py并点击确定。 -
要运行我们的代码,我们点击绿色的运行按钮,在键盘上按F5,或者在顶部的运行菜单选项中点击,然后选择运行 当前脚本。
-
如果它尚未运行,我们打开电源并运行 LoRa 传感器发射器。
-
我们通过检查 Windows 中的 MQTT-Explorer 应用中 MQTT 消息的接收情况来验证代码的正确运行。
-
我们还应观察我们的 LED 闪烁两次。
通过从 LoRa 传感器发射器读取温度和湿度读数,并使用我们的 LoRa 接收器将它们中继到我们的 MQTT 服务器,就像接力赛中的接力一样,我们有效地建立了自己的天气信息网络服务。
在设置准备就绪后,我们现在可以利用 MQTT 消息数据来操作我们的改进的模拟仪表式天气指示器。我们的第一步是构建设备,然后编程它以响应传入的数据。
创建新的天气指示器
在本节中,我们介绍一个升级的天气指示器,基于第三章中的版本。此型号使用 RGB LED 而不是单色 LED。我们将使用它通过将颜色设置为红色来指示低湿度,绿色表示 30%至 50%的湿度水平(被认为是人们舒适的湿度水平),以及蓝色表示 50%以上的湿度。设备现在使用经济的 Raspberry Pi Pico WH,而不是更昂贵的 Raspberry Pi 5。新增功能是 Raspberry Pi Pico WH 的复位按钮,允许我们在需要时重置 Pico。
我们升级的天气指示器设计包括一个新功能:一个专门为使用熔融沉积建模(FDM)3D 打印机更容易打印的分割支架。通过将支架分成两个独立的部分,每个部分都可以平印。当平印时,支架每个部分的层线与使用过程中遇到的主要应力轴垂直。这种层垂直排列有效地在整个结构上分布应力,使支架更具弹性,更不易在负载下断裂或变形。
我们将开始构建我们新的天气指示器,首先构建分裂支架。
构建分裂支架
我们可以使用本书中构建的任何支架来安装我们的天气指示器面板。在我们的例子中,我们介绍了一种可以使用标准 FDM 3D 打印机或液体树脂 3D 打印机打印的分裂支架。分裂支架的部件如下所示:

图 10.5 – 使用 PLA 和 FDM 3D 打印机打印的分裂支架
分裂支架的部件如下:
-
A:右侧分裂支架
-
B:左侧分裂支架
-
C:底座
-
D(未显示):9 个 M3 10 毫米螺栓
-
E(未显示):4 个 12 毫米橡胶垫(可选)
我们分裂支架的.stl文件位于本章 GitHub 仓库的Build Files文件夹中。图 10.5中显示的所有三个部件都可以在一个标准 Ender-3 尺寸的打印床上(220 毫米×220 毫米×250 毫米)一起打印。
当使用 FDM 打印机时,重要的是要带有支撑结构的切片打印文件,特别是当分裂支架的外环在打印成平面时在空间中漂浮。对于分裂支架,聚乳酸(PLA)材料是最佳选择,因为它易于打印且结果可靠。
在以下图中,我们可以看到我们如何在打印床上定位分裂支架的部件。

图 10.6 – 在打印床上布局 3D 打印的分裂支架部分
为了构建支架,我们按照图 10.7中显示的步骤进行:

图 10.7 – 构建分裂支架
-
利用 M3 丝锥,我们在分裂支架的右侧(图 10.5中的A)小心地创建螺纹。这一步是可选的,因为孔应该足够大,以便 M3 螺栓可以拧入而不需要攻丝。
-
使用五个 M3 10 毫米螺栓,我们将左侧分裂支架(图 10.5中的C)固定到右侧分裂支架(图 10.5中的A)。
-
接下来,我们使用环氧树脂胶水将四个标准 12 毫米橡胶垫粘贴到底座的底部作为脚,以增强粘附性。这一步虽然不是强制性的,但增加了稳定性,因为支架可以在没有橡胶垫的情况下工作。
-
使用 M3 丝锥,我们在底座上创建螺纹。这一步是可选的,因为孔应该足够大,以便 M3 螺栓可以拧入而不需要攻丝。
-
使用四个 M3 螺栓,我们将组装好的支架固定到底座上(图 10.5中的C)。
组装好的分裂支架可以喷漆以改善美观。一旦分裂支架完成,下一步就是组装我们的天气指示器的面板。
构建面板
我们新的增强型天气指示器的面板与我们之前在 第三章 中构建的面板非常相似。区别在于使用了 Raspberry Pi Pico WH 而不是 Raspberry Pi 5,以及 RGB LED,这将允许我们为指示器使用各种颜色。
我们可以在以下图中看到组成面板的部件:

图 10.8 – 组成我们天气指示器面板的部件
-
A: 面板正面。
-
B: 面板校准工具。
-
C: Raspberry Pi Pico WH。
-
D: 面板正面按钮。用于重置 Raspberry Pi Pico WH。
-
E: 钩子。
-
F: 面板背面。
-
G: 面板图形。
-
H: 带有电阻和跳线(跳转到即将到来的部分 添加跳线到 RGB LED 以构建此组件)的 RGB LED。
-
I: SG90 伺服电机。
-
J: 8 mm LED 支架。
要构建我们新的天气指示器的面板,我们遵循以下图中的步骤:

图 10.9 – 我们新的天气指示器面板的构造
-
使用校准工具(B 来自 图 10.8)和环氧树脂胶水,我们将面板正面(A 来自 图 10.8)与面板背面(F 来自 图 10.8)对齐并粘合。
-
我们将钩子(E 来自 图 10.8)用胶水粘在面板背面(F 来自 图 10.8)的槽中。
-
我们使用彩色打印机将面板图形(G 来自 图 10.8)打印在可打印的乙烯基上。接下来,我们使用数字切割机,如 Silhouette Cameo,或手工裁剪图形,并将其粘贴到面板的前面,确保孔的适当对齐。
-
我们将 SG90 伺服电机(I 来自 图 10.8)与相应的孔对齐,并用热胶枪的胶水将其固定在面板背面(F 来自 图 10.8)上。
-
我们使用四个 M3 8 mm 螺丝将 Raspberry Pi Pico WH(C 来自 图 10.8)固定在面板背面(F 来自 图 10.8)上,使得引脚向上,USB 端口向外。
-
我们将 RGB LED(H 来自 图 10.8)从面板背面推到前面,并用 8 mm LED 支架(J 来自 图 10.8)固定在位。
在构建了面板和底座之后,我们现在可以使用 M5 20 mm 螺栓和螺母将面板固定到底座上。完成此操作后,我们就准备好连接 RGB LED 和伺服电机到 Raspberry Pi Pico WH 了。
配置 RGB LED 指示器
对于我们的应用,我们将使用 RGB LED 来表示我们从远程 LoRa 发射器获得的湿度读数。RGB LED 允许我们将任何颜色表示出来,因为它将三个 LED(红色、绿色和蓝色)组合到一个外壳中。
在我们连接和安装 RGB LED 之前,让我们先了解一下 RGB LED 是什么。
理解 RGB LED
RGB LED 通过组合红、绿、蓝三种光线来产生各种颜色。每种颜色都有一个用于控制的引脚。它们有两种类型:公共阴极和公共阳极(图 10.10)。公共阴极 LED 具有一个所有颜色共用的单个接地引脚,需要向颜色引脚施加正电压以实现照明。公共阳极 LED 共享一个正引脚,需要在颜色引脚上连接接地才能点亮。

图 10.10 – 两种不同类型的 RGB LED
类型决定了我们如何连接和控制它们,公共阴极 LED 需要高信号激活,而公共阳极 LED 需要低信号激活。
区分公共阴极和公共阳极 RGB LED
要确定我们的 RGB LED 是公共阳极还是公共阴极类型,我们使用设置在二极管测试模式下的万用表。首先,我们识别 LED 的最长腿,因为这通常是公共连接。然后,我们将万用表的红表笔连接到最长腿,黑表笔连接到其他腿中的一个。如果 LED 点亮,则最长腿是阳极,表示这是一个公共阳极 LED。如果没有点亮,我们将交换表笔。黑表笔连接到最长腿时 LED 点亮,意味着它是一个公共阴极。
对于我们的应用,我们将使用公共阳极 RGB LED,并将它编程为根据湿度水平改变颜色。对于低湿度水平,它将显示红色,表示干燥条件。当湿度水平在 30%到 50%之间时,它将显示绿色,而当湿度水平高于 50%时,它将显示蓝色。
将跳线连接到我们的 RGB LED
为了将我们的 RGB LED 连接到 Raspberry Pi Pico WH,我们将在每个引脚上焊接雌性跳线。为了保护 RGB LED,我们将在红色、绿色和蓝色引脚上焊接 220 欧姆电阻,将它们放置在 LED 的腿和跳线之间。在我们的例子中,我们使用黄色线作为电源线,红色线作为红色 LED,绿色线作为绿色 LED,蓝色线作为蓝色 LED,如图所示。

图 10.11 – 将跳线连接到 RGB LED
在图 10.11的第2步中所示,应用热缩套管通过提供物理强度和电气绝缘来加强焊接连接。
将 RGB LED 连接到我们的 Raspberry Pi Pico WH
我们将使用 Raspberry Pi Pico WH 的 3V3 电源端口来供电我们的 RGB LED。对于接线,请使用以下图示:

图 10.12 – RGB LED 和 Raspberry Pi Pico WH 的接线图
连接方式如下:
-
3V3 电源连接到公共阳极。
-
红色通过一个 220 欧姆电阻连接到
GP15。 -
绿色通过一个 220 欧姆电阻连接到
GP13。 -
蓝色通过一个 220 欧姆电阻连接到
GP12。
将 RGB LED 连接到 Raspberry Pi Pico WH 后,现在是时候用代码测试它了。
测试我们的 RGB LED 电路
要测试我们的 RGB LED 电路,我们将运行代码来打开三种颜色中的每一种。我们将用 MicroPython 编写代码并使用 Thonny IDE。
要用代码测试我们的 RGB LED,我们做以下操作:
-
我们将 Raspberry Pi Pico WH 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统进行此操作。如果 MicroPython 尚未安装,我们按照第六章中“使用 Raspberry Pi Pico W 与 MQTT”部分的步骤,将其安装到我们的 Raspberry Pi Pico WH 上。
-
然后,我们在屏幕的右下角选择 Pico 来激活 MicroPython 环境。
-
在编辑器部分的新标签页(按Ctrl + N),我们输入以下代码:
from machine import Pin import utime red = Pin(15, Pin.OUT) green = Pin(13, Pin.OUT) blue = Pin(12, Pin.OUT) def set_color(r, g, b): red.value(r) green.value(g) blue.value(b) while True: # Red set_color(0, 1, 1) utime.sleep(1) # Green set_color(1, 0, 1) utime.sleep(1) # Blue set_color(1, 1, 0) utime.sleep(1) -
要保存文件,我们从下拉菜单中选择文件 | 另存为...。这将打开以下对话框:

图 10.13 – 将文件保存到我们的 Raspberry Pi Pico
-
在此对话框中,我们有机会选择文件存储的位置。要将文件保存到我们的 Raspberry Pi Pico WH,我们点击相应的按钮。
-
然后,我们给文件命名为
main.py并点击,main.py是特殊的,因为系统在启动或重置时会自动执行此文件,使其成为设备启动时运行的默认脚本。在测试我们的代码之前,让我们将其分解:
-
我们首先从
machine模块和utime模块导入Pin类。 -
然后,我们将 GPIO 引脚 GP15、GP13 和 GP12 初始化为输出,分别用于红色、绿色和蓝色 LED。
-
然后,我们定义
set_color(r, g, b)函数来控制 RGB LED 的颜色。 -
我们定义一个无限循环:
-
我们将 LED 设置为红色 1 秒钟。
-
然后,我们将 LED 变为绿色 1 秒钟。
-
最后,我们将 LED 设置为蓝色 1 秒钟。
-
-
-
要运行我们的代码,我们点击绿色运行按钮,在键盘上按F5或点击顶部的运行菜单选项,然后选择运行 当前脚本。
-
我们应该观察到我们的天气指示器上的 RGB LED 循环变红,然后变绿,然后变蓝。
在我们的 RGB LED 成功连接并测试后,现在是时候将伺服电机连接到我们的 Raspberry Pi Pico WH 了。
配置伺服电机
在我们的 RGB LED 安装并测试完毕后,现在是时候将我们的注意力转移到我们的天气指示器上的伺服电机。正如我们在第三章中提到的那样,将伺服电机集成到我们的设计中提供了一种将模拟和数字世界连接起来的优秀方法;它使我们能够创建一个模拟风格的仪表,其中由伺服电机精确移动的指针可以直观地表示各种数据点。
我们将首先连接伺服电机,然后再通过代码进行测试。
连接伺服电机
对于我们的天气指示器,我们将集成 SG90 伺服电机。SG90 伺服电机通常带有三根电线:电源线(通常是红色)、地线(通常是棕色或黑色)和信号线(通常是橙色或黄色)。
要将我们的伺服电机连接到 Raspberry Pi Pico WH,我们首先从连接器外壳中拔出电线,就像我们在第三章中所做的那样。

图 10.14 – 从 SG90 伺服电机的连接器中拔出电线
我们不会将电线重新插入连接器外壳,而是直接将它们连接到 Raspberry Pi Pico WH 的引脚上。
我们将使用 Raspberry Pi Pico WH 的 VBUS 电源端口为伺服电机供电,因为 VBUS 直接从 USB 连接提供必要的 5V 电源,这对于大多数伺服电机的典型工作电压(如 SG90)来说非常理想。
对于布线,请使用以下图示:

图 10.15 – 将伺服电机连接到 Raspberry Pi Pico WH
连接方式如下:
-
VBUS 电源连接到伺服电机的正极线。
-
信号线连接到
GP14。 -
GND 连接到 Raspberry Pi Pico WH 上的 GND 端口。
重要提示
为了简单起见,图 10.15仅显示了连接到 Raspberry Pi Pico WH 的伺服电机。然而,我们的实际电路包括 RGB LED 和伺服电机。
将伺服电机连接到 Raspberry Pi Pico WH 后,是时候用代码测试它了。
测试伺服电机
为了验证我们的伺服电机电路的功能,我们将执行一个测试代码,使电机在 180 度范围内循环,最终停在最小位置。一旦电机达到这个最小点,我们将箭头连接到我们的设备上。我们将用 MicroPython 编写代码,并使用 Thonny IDE。我们的代码将组织成两个文件:servo.py,用于管理伺服电机的控制,以及main.py,它将作为主要的执行脚本。
要用代码测试我们的伺服电机,我们执行以下操作:
-
我们将 Raspberry Pi Pico WH 连接到计算机上的 USB 端口,并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来做这件事。
-
然后,我们通过在屏幕的右下角选择它来激活 Pico 上的 MicroPython 环境。
-
我们将开始编写控制伺服电机的代码。在编辑器部分的新标签页(按Ctrl + N),我们输入以下代码:
from machine import Pin, PWM import utime class Servo: def __init__(self, pin): self.servo = PWM(Pin(pin)) self.servo.freq(50) def set_position(self, angle): # Reverse the angle reversed_angle = 180 - angle # Convert the reversed angle to duty cycle duty = int((reversed_angle / 18) + 2) self.servo.duty_u16(duty * 65536 // 100)在我们的代码中,我们首先导入必要的模块:
machine中的Pin和PWM用于硬件控制,以及utime用于时间相关函数。然后,我们定义
Servo类来控制伺服电机:-
__init__()方法将伺服电机初始化为指定 GPIO 引脚上的set_position()方法,将伺服电机设置为指定角度:-
我们反转角度以允许伺服电机的反向运动。
-
我们然后将这个反转的角度转换为相应的 PWM 占空比。
-
我们使用
duty_u16()方法设置占空比,将其缩放到 16 位值以进行 PWM 控制。
-
-
-
要保存文件,我们从下拉菜单中选择文件 | 另存为...。
-
我们可以选择将文件存储在哪里。为了将其保存在我们的 Raspberry Pi Pico WH 上,我们点击相应的按钮。
-
我们然后将文件命名为
servo.py并点击确定。 -
要创建我们的
main.py文件,我们在编辑器部分打开一个新的编辑器标签(按Ctrl + N),并输入以下代码:from servo import Servo import utime servo = Servo(14) servo.set_position(0) utime.sleep(1) servo.set_position(90) utime.sleep(1) servo.set_position(180) utime.sleep(1) # Return servo to initial position servo.set_position(0)在我们的代码中,我们有以下内容:
-
我们首先从新创建的
servo模块导入Servo类,以及utime用于计时功能。 -
我们然后创建一个名为
servo的Servo类实例,使用 GPIO 引脚 GP14。 -
我们的代码将伺服电机移动到 0 度,并等待 1 秒钟。
-
我们的代码然后将伺服电机调整到 90 度,并等待另外 1 秒钟。
-
我们将伺服电机设置为 180 度,然后暂停 1 秒钟。
-
最后,我们的代码将伺服电机返回到初始位置 0 度。
-
-
要运行我们的代码,我们点击绿色运行按钮,在键盘上按F5,或者在顶部点击运行菜单选项,然后选择运行****当前脚本。
-
我们应该观察到伺服电机在停止在最小位置之前会经过一系列的运动。与使用 Raspberry Pi 5 连接伺服电机相比,使用 Raspberry Pi Pico WH 的一个显著改进是伺服电机的抖动消失。与 Raspberry Pi 相比,当使用 Raspberry Pi Pico WH 时,伺服电机抖动更少,这是因为 Pico 的直接硬件级 PWM 控制确保了向伺服电机提供更精确和稳定的信号。
-
就在这个时候,我们在伺服电机上放置箭头。我们将箭头放置得使其指向我们的图形中的手套。箭头应该紧密贴合;然而,可能需要一点打磨才能使其贴合。

图 10.16 – 将指针放置在零位置
在成功连接并测试了 RGB LED 和伺服电机与我们的 Raspberry Pi Pico WH 之后,我们现在准备好开发控制我们的天气指示器的代码,利用从 MQTT 消息中提取的数据。
编程我们的天气指示器
我们的天气指示器开发流程简化了,因为组件直接放置在面板上,允许立即进行代码实现和测试,消除了需要搭建面包板的必要性。这也减少了出错的机会,因为我们不需要再次配置电路。
我们的应用软件架构如下所示。

图 10.17 – 天气显示代码布局
我们的代码组织成三个文件,每个文件包含一个不同的类。main.py文件包含WeatherDisplay类,该类从我们的 CloudAMQP 实例检索并解析 MQTT 消息。根据这些消息中的温度值,WeatherDisplay类使用Servo类调整伺服位置(从而调整天气指示器的指针),并使用Indicator类解释湿度数据以控制 RGB LED 的颜色。
要创建我们的天气指示器代码,我们执行以下操作:
-
我们将我们的 Raspberry Pi Pico WH 从天气指示器连接到电脑的 USB 端口,并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来完成这个操作。
-
然后,我们从屏幕的右下角选择 MicroPython 环境来激活我们的 Pico WH。
-
我们将从控制 RGB LED 的代码开始。这段代码将介绍
Indicator类,该类旨在管理 RGB LED 的颜色过渡,从红色到绿色再到蓝色,反映湿度级别的变化。在编辑器部分的新的标签页(按Ctrl + N),我们输入以下代码:from machine import Pin import utime class Indicator: def __init__(self): self.red = Pin(15, Pin.OUT) self.green = Pin(13, Pin.OUT) self.blue = Pin(12, Pin.OUT) def set_color(self, r, g, b): self.red.value(r) self.green.value(g) self.blue.value(b) def set_indicator(self, value): # Turn off all LEDs initially self.set_color(1, 1, 1) if value <= 30: # Turn on red LED self.set_color(0, 1, 1) elif 30 < value <= 50: # Turn on green LED self.set_color(1, 0, 1) else: # Turn on blue LED self.set_color(1, 1, 0) def flash_led(self, times): for _ in range(times): self.set_color(0, 0, 0) utime.sleep(0.5) self.set_color(1, 1, 1) utime.sleep(0.5)在我们的代码中,我们有以下内容:
-
我们首先从
machine模块导入Pin类,以及utime来处理基于时间的函数。 -
我们定义
Indicator类,并用代表红色、绿色和蓝色 LED 的三个属性初始化它,分别设置为 GP15、GP13 和 GP12 的输出引脚。 -
set_color()方法根据输入参数控制 RGB LED 中每个颜色的状态。在这个方法中,对于常见的阳极 RGB LED,二进制的0激活一个颜色(打开它),而1则使其不活跃(关闭它)。该方法用于选择性地打开 LED 的红色、绿色或蓝色组件。 -
然后,我们创建
set_indicator()方法:-
我们的代码最初关闭所有 LED。
-
我们的代码在值≤30 时打开红色 LED,在值介于 30 和 50 之间时打开绿色 LED,在值>50 时打开蓝色 LED。
-
-
在我们的
flash_led()方法中,我们闪烁所有三个 LED(白光)指定次数(times),每次闪烁持续 0.5 秒,LED 关闭间隔也是 0.5 秒。
-
-
要保存文件,我们从下拉菜单中选择文件 | 另存为...。
-
我们有选择存储文件位置的选择。要将文件保存在我们的 Raspberry Pi Pico WH 上,我们点击相应的按钮。
-
然后,我们将文件命名为
indicator.py并点击确定。 -
我们代码中的第二个类是伺服类的新版本。为了创建这个类,我们在编辑器部分打开一个新的标签页(按Ctrl + N),并输入以下代码:
from machine import Pin, PWM import utime class Servo: def __init__(self, pin): self.servo = PWM(Pin(pin)) self.servo.freq(50) def set_position(self, value): int_value = int(value) angle = 180 - (int_value / 40) * 180 angle = max(0, min(angle, 180)) # Convert the angle to duty cycle duty = int((angle / 18) + 2) self.servo.duty_u16(duty * 65536 // 100)在我们的代码中,我们首先从
machine导入Pin和PWM模块,以及utime用于时间相关函数。然后,我们定义
Servo类来控制伺服电机:-
在构造函数(
__init__())中,我们在指定的引脚上初始化一个 PWM 对象。 -
我们将 PWM 频率设置为 50 Hz,这是一个适合伺服电机的标准频率。
-
然后我们创建一个
set_position()方法,执行以下操作:-
将输入值转换为整数。
-
将输入范围(0-40)映射到伺服角度范围(0-180 度)的反向,以与我们的伺服电机安装对齐。
-
确保计算出的角度在有效范围内(0-180 度)。
-
将角度转换为适合伺服电机的占空比。
-
设置 PWM 占空比以定位伺服电机。
-
-
-
要保存文件,我们从下拉菜单中选择文件 | 另存为...:
-
我们可以选择将文件存储的位置。为了将其保存在我们的 Raspberry Pi Pico WH 上,我们点击相应的按钮。
-
然后我们将文件命名为
servo.py并点击确定。 -
位于主执行文件中的
WeatherDisplay类负责订阅WeatherInfoMQTT 主题并处理接收到的消息。为了其操作,需要micropython-umqtt.simple库。要安装此库,请参阅第七章中的改进我们的 Raspberry Pi Pico W 物联网按钮部分。 -
要创建
WeatherDisplay类,我们首先在编辑器部分的新标签中输入导入语句(按Ctrl + N):import network import utime from umqtt.simple import MQTTClient from servo import Servo from indicator import Indicator在我们的代码中,我们执行以下操作:
-
我们导入
network模块,用于 Wi-Fi 连接功能。 -
我们导入
utime模块,用于时间相关函数。 -
我们的代码从
umqtt.simple导入MQTTClient来处理 MQTT 通信。 -
我们从新创建的本地模块中导入我们的
Servo类,用于伺服电机控制。 -
然后我们从新创建的本地模块中导入我们的
Indicator类,用于 RGB LED 指示器控制。
-
-
然后我们定义类名和初始化方法,其中我们定义我们的 Wi-Fi 和 MQTT 参数:
class WeatherDisplay: def __init__(self): # WiFi Information self.ssid = "MySSID" self.wifi_password = "ssid-password" # MQTT Information self.mqtt_server = "driver.cloudmqtt.com" self.mqtt_port = 18756 self.username = "mqtt-username" self.mqtt_password = "mqtt-password" self.device_id = "WeatherDisplay" self.mqtt_topic = "WeatherInfo" self.indicator = Indicator() self.servo = Servo(14) -
connect_wifi()方法在 Raspberry Pi Pico WH 和本地 Wi-Fi 网络之间建立连接。它持续尝试连接,直到成功,并在建立连接后,通过四次闪烁白色 RGB LED 来指示成功:def connect_wifi(self): wlan = network.WLAN(network.STA_IF) wlan.active(True) if not wlan.isconnected(): print('Connecting to WiFi...') wlan.connect(self.ssid, self.wifi_password) while not wlan.isconnected(): pass print('WiFi connected, IP:', wlan.ifconfig()[0]) self.indicator.flash_led(4) -
我们使用
connect_mqtt()方法连接到我们的 CloudAMQP 实例,并订阅WeatherInfo主题(由mqtt_topic变量设置),并将我们的回调函数设置为on_message_received():def connect_mqtt(self): self.client = MQTTClient(self.device_id, self.mqtt_server, self.mqtt_port, self.username, self.mqtt_password) self.client.set_callback( self.on_message_received) self.client.connect() self.client.subscribe(self.mqtt_topic) -
on_message_received()函数通过解析温度和湿度数据,然后相应地更新伺服位置和 RGB LED 指示器来处理接收到的 MQTT 消息:def on_message_received(self, topic, msg): print("Received:", topic, msg.decode()) temperature, humidity = self.parse_message(msg) if temperature is not None: self.servo.set_position(temperature) if humidity is not None: self.indicator.set_indicator(humidity) -
parse_message()函数从解码的 MQTT 消息中提取并返回温度和湿度值,处理任何异常,如果解析失败则返回None值:def parse_message(self, msg): try: parts = msg.decode().split(',') temperature_str = parts[0].split('Temp:')[1].split('C')[0].strip() humidity_str = parts[1].split('Humidity:')[1].split('%')[0].strip() temperature = float(temperature_str) humidity = float(humidity_str) return temperature, humidity except Exception as e: print("Error parsing message:", str(e)) return None, None -
run方法是WeatherDisplay类中定义的最后一个方法,它初始化 Wi-Fi 连接,连接到 MQTT 客户端,并持续检查 MQTT 消息,处理在消息接收过程中遇到的任何错误:def run(self): self.connect_wifi() self.connect_mqtt() while True: try: self.client.check_msg() except Exception as e: print("Error checking MQTT message:", str(e)) utime.sleep(5) -
在编写
WeatherDisplay类代码后,我们实例化它并调用其run()方法,启动天气显示功能,这包括建立 Wi-Fi 和 MQTT 连接以及处理传入的消息:# Create and run the weather display weather_display = WeatherDisplay() weather_display.run() -
要保存我们的文件,我们从下拉菜单中选择文件 | 另存为...。
-
我们可以选择将文件存储在哪里。要将它保存在我们的 Raspberry Pi Pico WH 上,我们点击相应的按钮。
-
然后,我们将文件命名为
main.py并点击确定。 -
要运行我们的代码,我们点击绿色的运行按钮,在键盘上按F5,或者点击顶部菜单的运行选项,然后选择运行****当前脚本。
-
我们应该注意到在连接到我们的 Wi-Fi 网络后,RGB LED 闪烁四次。
-
我们应该注意到箭头移动到了一个位置,这个位置指示着来自我们 LoRa 传感器发射器上的温度传感器所测量的温度。
-
我们应该注意到 RGB LED 的颜色要么是红色、绿色或蓝色,这表示由我们的 LoRa 传感器发射器测量的湿度水平。

图 10.18 – 构成我们物联网天气服务的三个设备,从左到右分别是 LoRa 传感器发射器(第九章),LoRa 接收器/互联网网关(第九章),以及模拟仪表的天气指示器
在我们项目的完成之后,我们成功构建了一个物联网天气服务和显示控制台,结合了实时数据收集和交互式显示。该系统利用 MQTT 进行数据传输,伺服电机用于物理表示数据,以及 RGB LED 用于视觉警报。
探索其他物联网通信协议
与 LoRa 使用相同的亚千兆赫兹无线电频率,LoRaWAN 是一种先进的无线通信协议,它能够实现低功耗的长距离传输。这个共享的频率带是 LoRaWAN 和 LoRa 能够传输数公里数据的关键特性,这在传统连接稀缺的地区尤其有益。
在物联网应用(如天气监测)的背景下,LoRaWAN 提供了显著的优势。例如,一个收集环境数据(如温度和湿度)的传感器网络可以通过长距离传输这些信息到一个连接到互联网的中心网关,利用 LoRa 的长距离能力。然后,这个网关将数据中继到云端服务器进行处理和分析。
然而,对于我们的气象指示器项目,使用完整的 LoRaWAN 设置会被视为过度。我们的项目采用了一个更简单的设置,涉及两个 Raspberry Pi Pico 微控制器——一个配备 LoRa 进行数据传输(Raspberry Pi Pico)和另一个具有 Wi-Fi 功能(Raspberry Pi Pico W)。这种设置有效地展示了 LoRa 在短程物联网通信中的能力,利用了 LoRa 频率相同的远程、低功耗特性,但无需完整的 LoRaWAN 网络复杂性和基础设施要求。
蜂窝服务也可以用于物联网通信,提供广泛的覆盖范围和更高的数据传输速度。使用 4G LTE 或 5G 网络的蜂窝物联网设备可以在长距离传输大量数据。这使得蜂窝服务适合需要更多数据密集型应用或需要实时、高速通信的应用。
虽然蜂窝物联网提供了更广泛的覆盖范围和更高的数据吞吐量,但与基于 LoRa 的解决方案相比,它通常具有更高的功耗和复杂性。因此,对于我们的气象指示器等小型或低功耗项目,使用带有 Raspberry Pi Pico 微控制器的 LoRa 模块的简单性和效率提供了更合适且成本效益更高的解决方案。
Sigfox 是一家全球网络运营商,为物联网和机器对机器(M2M)通信提供专用蜂窝连接。它使用独特的无线传输技术,允许长距离、低功耗通信。Sigfox 在亚吉赫兹频段运行,旨在进行小数据负载传输,通常每条消息最多 12 字节。这种有限的数据容量使其非常适合需要发送小量、不频繁数据爆发设备,如智能电表的传感器、农业监控器和资产跟踪系统。Sigfox 的网络架构以其简单性、效率和经济性而著称,使其成为低成本和低功耗操作关键应用的热门选择。
LoRaWAN、蜂窝网络和 Sigfox 是物联网应用中使用的领先通信协议之一。每个都有其独特的特性和用例。以下是一个比较表格,概述了它们的优缺点:
| 协议 | 优点 | 缺点 |
|---|---|---|
| LoRaWAN |
-
长距离(高达 15 公里)
-
低功耗
-
城市环境渗透良好
-
开放式协议,生态系统不断增长
|
-
数据速率较低
-
带宽和占空比限制
-
需要网关进行互联网连接
|
| 蜂窝(4G/5G) |
|---|
-
高数据吞吐量
-
广泛覆盖和可靠性
-
支持实时通信
-
基础设施完善
|
-
功耗更高
-
订阅费用
-
对于简单任务可能过于强大
|
| Sigfox |
|---|
-
极低功耗
-
长距离能力
-
部署和维护简单
-
适用于小型、不频繁的数据传输
|
-
非常有限的数据负载(每条消息 12 字节)
-
每天限制为 140 条消息
-
有限灵活性的专有技术
|
| 带有 Wi-Fi 微控制器的 LoRa |
|---|
-
长距离(LoRa 可达 15 公里)
-
LoRa 传输的低功耗
-
小型项目成本效益高的解决方案
-
与现有 Wi-Fi 网络灵活且易于集成
|
-
仅限于 Wi-Fi 网络的范围进行互联网连接
-
与单个微控制器绑定,可能不适用于移动应用
-
需要额外的硬件(Wi-Fi 微控制器)进行互联网连接
-
与使用单个协议相比,可能需要更复杂的设置和编码
|
表 10.1 – 比较物联网通信协议
每个协议服务于不同的物联网场景:
-
LoRaWAN:适用于需要长距离通信和低功耗的应用,如农业传感器或智能城市应用。
-
蜂窝网络:适用于高数据量、实时应用,如视频监控、汽车应用或任何需要广泛地理覆盖的场景。
-
Sigfox:在设备只需在长距离发送少量数据的情况下表现优异,如公用事业计量或资产跟踪。
-
带有 Wi-Fi 微控制器的 LoRa:最适合需要 LoRa 的远程能力和 Wi-Fi 提供的互联网连接的小型、本地物联网项目,如智能家居、本地环境监测或 DIY 物联网项目。
对于像我们的天气指示器这样的小型或低功耗项目,使用带有 Raspberry Pi Pico 微控制器的 LoRa 模块比提到的选项提供更合适且成本效益更高的解决方案。LoRa 与微控制器平衡了范围、数据处理和能效,无需更复杂的基础设施。
摘要
在本章中,我们通过 MQTT 集成了基于 LoRa 的数据传输和互联网连接。我们修改了基于 CircuitPython 的 LoRa 接收器的代码,使其能够将传感器数据发送到 CloudAMQP 服务器,将其转变为互联网网关。天气指示器得到了升级,用 Raspberry Pi Pico WH 和 RGB LED 替换了 Raspberry Pi 5 和单色 LED,通过 MQTT 消息显示温度和湿度数据。
此外,我们还为指示器构建了一个新的分体支架,设计用于易于组装和改进稳定性。我们通过检查物联网通信协议,如 LoRaWAN、蜂窝网络和 Sigfox,评估它们对不同物联网应用的适用性来结束本章。
我们通过 MQTT 将 LoRa 与互联网连接进行了实际操作探索,展示了不同技术如何在物联网应用中协同工作。通过升级天气指示器,我们不仅学习了迭代设计和故障排除,还深入了解到了物联网项目中涉及到的决策过程,例如选择树莓派 Pico WH 而不是树莓派 5 来控制伺服电机。
本章标志着本书第三部分的结束,该部分专注于物联网通信,特别强调 LoRa。在下一章中,我们将转向机器人技术,开始我们的旅程,构建一辆互联网连接的机器人汽车。
第四部分:构建物联网机器人汽车
在这部分,我们利用前几章所获得的所有知识来构建一辆可以通过互联网控制的物联网机器人汽车。这个项目将是最高级的,并展示了物联网技术的终极力量,从任何地方控制任何事物。
本部分包含以下章节:
-
第十一章,介绍 ROS
-
第十二章,创建物联网摇杆
-
第十三章,介绍用于安全的先进机器人眼睛(A.R.E.S.)
-
第十四章,为 A.R.E.S.添加计算机视觉
第十一章:介绍 ROS
在本章中,我们介绍了机器人操作系统(ROS),这是一个用于开发机器人应用的强大工具。我们将探讨 ROS 在机器人领域的意义,并详细说明在 Raspberry Pi 4 上设置 ROS 的过程。这涉及到用 Ubuntu 替换标准 Raspberry Pi OS,因为 Ubuntu 与 ROS 特定版本的兼容性和优化。
我们将从设置和运行用户友好的 ROS 模拟器TurtleSim开始我们的动手练习。我们将这样做是为了了解基本的 ROS 概念和操作。从简单的键盘控制开始,我们将学习如何在模拟器环境中指挥和操纵虚拟机器人。随后,我们将通过使用消息队列遥测传输(MQTT)消息来控制 TurtleSim 模拟器,以此开始弥合模拟与现实应用之间的差距。
通过与 TurtleSim 合作开发技能,我们将为构建名为 A.R.E.S.(代表高级机器人眼睛用于安全)的高级物联网机器人做好准备。A.R.E.S.是本书的最终和最复杂的项目,并将包括剩余的章节。
本章我们将涵盖以下主题:
-
探索 ROS
-
在我们的 Raspberry Pi 上安装 Ubuntu 和 ROS
-
运行和控制模拟机器人
让我们开始吧!
技术要求
完成本章所需的以下要求:
-
Python 编程的中级知识
-
基本的 Linux 命令行知识
-
用于 MQTT 服务器实例的 CloudAMQP 账户
-
新款 Raspberry Pi 4 或任何能够安装 Ubuntu 的计算机(本章使用了安装了 Ubuntu 的 Mac mini)
-
microSD 卡和 microSD-USB 适配器
本章的代码可以在以下位置找到:
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter11
探索 ROS
在本节中,我们将探索 ROS 的基本知识。本节绝对不是对 ROS 网站上可能找到的优秀文档的深入替代。
ROS 是一个用于机器人应用的开源软件(OSS)开发套件,提供了一个连接研究和生产的标准平台。设计用于加速机器人开发过程,ROS 简化了机器人系统的创建。它是 100%开源且商业友好的。
我们的目标是建立一个共同的知识库,为我们提供完成本章项目以及最终 A.R.E.S.机器人所需的 ROS 的基本概念和工具。我们将从概述本章的主要项目开始:使用 MQTT 消息控制 ROS TurtleSim 虚拟机器人。
检查我们的 TurtleSim 控制器 ROS 应用
TurtleSim 是 ROS 提供的一个轻量级机器人模拟器,主要用于学习 ROS 概念的教育工具。它提供了一个简单的界面,用于教授 ROS 的基础知识,使用户能够在安全和受控的环境中实验命令并观察模拟机器人的行为。
在本章的主要项目中,我们将使用 MQTT 消息来控制 TurtleSim 实例,根据我们发送到 MQTT 实例的 move 主题的消息,命令机器人画圆或停止移动。
在 图 11.1 中,我们看到我们的应用程序被展示出来。使用 Windows 中的 MQTT-Explorer 创建的消息被发送到我们在 ROS 中创建的 circle 节点。根据消息,我们发送 draw_circle 或 stop,将 cmd_vel 主题的 ROS 消息发送到 TurtleSim 实例。这种内部消息使用发布者和订阅者,类似于 MQTT 通信的执行方式:

图 11.1 – 使用 MQTT 消息控制模拟机器人
对于本章的应用程序,我们将直接使用 MQTT-Explorer 应用程序发送消息。在下一章中,我们将使用 Raspberry Pi Pico W 构建一个物联网摇杆,并使用它来控制模拟机器人。
现在我们已经概述了本章的目标,让我们退后一步,对 ROS 概念有一个基本的了解。这个概述将为我们提供对其架构和在机器人技术中角色的基本洞察。
理解 ROS 节点通信
ROS 支持广泛的平台和应用,包括 Linux、Windows、macOS 和嵌入式系统,使其非常灵活。其模块化框架基于节点、主题、服务和动作的概念。ROS 中的节点是执行特定计算的单个进程,而主题作为节点之间通过发布者-订阅者机制交换消息的通信渠道。
对于我们的应用程序,我们将使用主题和发布者-订阅者模型,将来自我们自定义 circle 节点的 cmd_vel 主题的 vel_msg 消息发送到 TurtleSim 虚拟机器人的实例。尽管发布者可能有多个订阅者,但我们将只使用一个 TurtleSim 实例来订阅我们在自定义节点中构建的发布者。以下图示说明了这一点:

图 11.2 – ROS 内部发布者-订阅者模型视图
ROS 中的服务为节点提供了执行请求-响应交互的方式,这对于需要即时反馈的任务非常有用。动作提供了一种执行需要持续反馈和取消可能性的长时间运行任务的方法。我们不会在我们的应用程序中使用服务或动作。
现在我们对 ROS 节点通信有了一定的了解,让我们来看看 ROS 中的项目结构。
调查 ROS 项目的结构和组织
ROS 项目的结构是为了高效地开发和管理工作机器人应用而组织的。在其核心,一个 ROS 项目是围绕软件包和工作空间的概念构建的,这对于组织机器人项目的各种组件至关重要。以下列表总结了 ROS 项目的关键概念:
-
软件包和工作空间:一个 ROS 工作空间是一个目录(或文件夹),其中开发并编译 ROS 软件包。每个软件包,通常在工作空间内的一个目录,代表机器人特定的功能或组件,例如传感器、执行器、算法,甚至是一组相关的节点。软件包可以包含 ROS 节点、库、数据集、配置文件,或构成独立且可重用模块的任何内容。
-
ROS 节点和通信:在这些软件包中,主要的可执行单元是节点。每个节点被设计来执行特定的任务,例如控制电机、处理传感器数据或执行计算。节点通过主题、服务或动作相互通信。主题允许异步的发布-订阅通信,非常适合流式传输数据,如传感器读数或控制命令。服务提供同步的请求-响应交互,适用于需要即时反馈的任务。动作适合长时间运行的任务,这些任务需要连续的反馈和取消的可能性。如前所述,我们将创建一个自定义节点,使用主题和发布-订阅方法与 TurtleSim 模拟器进行通信。
-
在 ROS 1 中使用
catkin或在 ROS 2 中使用colcon来编译和管理软件包。构建系统处理依赖关系并将软件包集成到 ROS 生态系统中。我们将使用colcon来编译我们的项目。 -
ros2run命令。
我们将构建我们的工作空间,构建我们的软件包,并直接从命令行执行我们的程序。在我们这样做之前,让我们探索 ROS 发行版如何与 Ubuntu 版本相匹配。
将 ROS 发行版与 Ubuntu LTS 版本相匹配
每个 ROS 发行版都与指定的 Ubuntu 长期支持(LTS)版本配对,这种策略保证了稳定性和兼容性,因为它为 ROS 开发者提供了一个一致的代码库来工作。
ROS 的发布策略是明确的:每个 ROS 发布版对应一个 Ubuntu LTS 版本,发布后不再支持新的 Ubuntu 版本。为了利用 Ubuntu 与 ROS 的这种配对,我们将安装 Ubuntu 22.04 和 ROS 2 的 Humble Hawksbill 版本到我们的 Raspberry Pi 4(对于那些希望在除 Raspberry Pi 以外的计算机上运行 Ubuntu 的人,我们可能可以跳过下一节)。
我们将首先使用 Raspberry Pi Imager 将 Ubuntu 闪存到 microSD 卡上。
在我们的 Raspberry Pi 上安装 Ubuntu 和 ROS
在本节中,我们将逐步介绍如何在我们的 Raspberry Pi 4 上安装 Ubuntu 22.04 和 ROS Humble Hawksbill。这包括选择正确的 Ubuntu 镜像,使用 Raspberry Pi 镜像工具将其烧录到 microSD 卡,并设置 Raspberry Pi 以 Ubuntu 启动。
为什么我们使用 Raspberry Pi 4 而不是 Raspberry Pi 5?
在撰写本文时,Raspberry Pi 5 不支持 Ubuntu 22.04 以及 ROS 的最新版本。
我们将首先在我们的选择电脑上启动 Raspberry Pi 镜像工具。
在我们的 Raspberry Pi 4 上安装 Ubuntu
要将 Ubuntu 安装到我们的 Raspberry Pi 4,我们将使用 Raspberry Pi 镜像工具将操作系统烧录到 microSD 卡,然后将其安装到我们的 Pi 上。Raspberry Pi 镜像工具是由 Raspberry Pi 基金会创建的工具,用于简化使用 Raspberry Pi 操作系统和其他兼容系统镜像 microSD 卡的过程。
Raspberry Pi 镜像工具适用于 Windows、macOS 和 Linux,甚至可以安装在 Raspberry Pi 本身上。
在 Raspberry Pi 上使用 Raspberry Pi 镜像工具
我们可以使用 Raspberry Pi 镜像工具在我们的 Raspberry Pi 上将镜像烧录到 microSD 卡。这个过程涉及到使用连接到 Raspberry Pi USB 端口的 SD 卡读卡器。我们可以通过在 Raspberry Pi OS 的终端中使用 sudo apt install rpi-imager 命令来安装镜像工具。
将 Ubuntu 烧录到 microSD 卡以供 Raspberry Pi 使用,我们需要执行以下步骤:
-
使用网络浏览器,我们导航到以下网站下载 Raspberry Pi 镜像工具:
-
要开始镜像过程,我们将 microSD 卡插入 USB microSD 适配器,然后将适配器连接到电脑的 USB 端口:

图 11.3 – 插入 microSD 卡的 USB microSD 适配器
- 接下来,我们在电脑上安装 Raspberry Pi 镜像工具软件。安装完成后,启动镜像工具将显示主界面,准备好让我们选择操作系统镜像和安装的目标 microSD 卡:

图 11.4 – Raspberry Pi 镜像工具
-
在 Raspberry Pi 设备 下,我们选择 不筛选。
-
对于 操作系统,我们选择 其他通用操作系统,然后选择 Ubuntu,然后选择 Ubuntu Desktop 22.04.3 LTS (64-BIT)。
-
然后,我们点击 选择存储 按钮,并选择我们插入电脑的 microSD 卡。
-
我们的选择应该如下所示:

图 11.5 – 选择后的 Raspberry Pi 镜像工具
选择正确的 Ubuntu 版本
在编写本文时,ROS 2 的最新版本支持 Ubuntu 22.04。因此,在我们的示例中,我们将使用 Ubuntu 22.04 LTS,尽管最新的 Ubuntu 版本是 23.10。对于 ROS 2 的未来版本,选择与使用的 ROS 2 版本相对应的 Ubuntu 版本非常重要。
-
要开始烧录过程,我们点击下一步按钮,并在警告对话框中点击是按钮以继续。
-
写作过程完成后,Raspberry Pi Imager 将执行验证以确保图像已正确写入 microSD 卡。在验证成功后,我们应该看到一个写入成功屏幕,表明 microSD 卡现在可以用于我们的 Raspberry Pi,并安装了新操作系统。
-
在将图像烧录到我们的 microSD 卡后,我们接着将卡安装到我们的 Raspberry Pi 上,并按照步骤完成 Ubuntu 在 Raspberry Pi 上的安装。
现在 Ubuntu 已经在我们的 Raspberry Pi 上运行,下一步是安装适当的 ROS 版本。尽管名为 ROS,但它不是一个操作系统。相反,它作为一个中间件或软件框架运行,提供构建和管理机器人应用程序的工具和库。
将 ROS 添加到我们的 Ubuntu 安装中
在总结我们的探索 ROS部分时,我们强调了将每个 ROS 发行版与特定的 Ubuntu LTS 版本匹配的重要性,以确保稳定性。在 Raspberry Pi 上安装了 Ubuntu 22.04 后,我们现在准备安装 ROS。
到本文编写时为止,有两个与 Ubuntu 22.04 兼容的 ROS 版本:Humble Hawksbill 和 Iron Irwini。Humble Hawksbill 是一个 LTS 版本,这意味着它旨在提供稳定性和长期支持,非常适合长期项目和寻求稳定开发环境的人。另一方面,Iron Irwini 是一个非 LTS 版本,通常具有更多前沿的变更,但支持生命周期较短。
由于我们希望我们的应用稳定性高于新功能,我们将使用 Humble Hawksbill。
重要提示
这里提供的 ROS 安装说明是在编写时最新的。然而,建议您咨询官方网页以获取最新的指导,因为可能会有更新或变化。
要在我们的 Raspberry Pi 上安装 Humble Hawksbill,我们执行以下操作:
-
要查看 ROS 的最新版本,我们导航到以下网站:
-
要查看 ROS 的当前版本,我们滚动到页面上的安装部分,我们看到以下内容:

图 11.6 – ROS 的当前版本
-
要继续,我们在Humble Hawksbill部分下点击安装链接。
这将带我们到 Humble Hawksbill 安装页面。
-
要为 Ubuntu 安装 Humble Hawksbill,我们点击Debian 软件包链接(在以下图中用红色框突出显示),位于Ubuntu Linux – Jammy Jellyfish (22.04)下:

图 11.7 – ROS 的二进制软件包
- 要在 Ubuntu 中设置区域设置,我们只需从网页的设置区域设置部分复制命令。这可以通过点击代码框右上角的复制图标快速完成,该图标在我们悬停时出现。然后我们将这些命令粘贴并执行到 Ubuntu 终端中,以完成区域设置过程:

图 11.8 – 在 Ubuntu 中设置区域设置
- 要配置我们的系统以访问和验证 ROS 软件仓库,我们复制、粘贴并执行设置 源部分下的每个部分的代码:

图 11.9 – 配置我们的 Ubuntu 安装以访问 ROS 软件仓库
-
要更新我们系统的软件包,我们在终端中输入以下命令:
sudo apt update -
然后,我们使用以下命令升级系统上的软件包:
sudo apt upgrade -
我们将使用 ROS 的桌面版本。我们使用以下命令安装它:
sudo apt install ros-humble-desktop -
由于我们将创建自己的节点,我们需要 ROS 开发工具。我们使用以下命令安装这些工具:
sudo apt install ros-dev-tools
在安装了 ROS 和 ROS 开发工具后,我们现在可以开始探索了。我们将从一个简单的发布-订阅示例开始。
测试我们的 ROS 安装
我们可以使用不同的组件测试我们的新 ROS 安装,无论这些组件是用什么编程语言编写的。对此的一种常见方法,也是我们将采用的方法,是使用一个简单的发布-订阅模型,其中发布者是用 C++编写的,而订阅者是用 Python 编写的。
在图 11.10 中,显示的节点是 ROS 桌面安装的一部分。该图突出显示了一个发布者节点,用 C++编写,它向 chatter 主题发送一个 Hello World:消息,后跟一个顺序号。订阅者节点是用 Python 编写的,并订阅 chatter 主题:

图 11.10 – 使用 C++发布者和 Python 订阅者的 ROS 发布-订阅
要运行示例,我们执行以下操作:
-
在 Ubuntu 中,我们打开一个新的终端并输入以下命令:
source /opt/ros/humble/setup.bash我们使用此命令初始化当前终端会话的 ROS 环境,从而启用 ROS 命令和包的使用。
-
要启动发布者,我们输入以下内容:
talker from the demo_nodes_cpp package. We should observe messages printed to the console:

图 11.11 – 运行 talker 节点时的输出
从终端中我们可以看到,talker 节点发布了一个 Hello World: 后跟递增数字的输出。尽管我们无法从输出中得知,但我们节点发布的主题是 chatter 主题。
-
要接收消息,我们在 Ubuntu 中打开一个新的终端并启动以下命令的 Python 订阅节点:
source /opt/ros/humble/setup.bash ros2 run demo_nodes_py listener每当我们打开一个新的终端时,我们必须源 ROS 安装以启用 ROS 命令。我们应该在我们的新终端中观察到已发布的消息的确认:

图 11.12 – 从 talker 节点接收消息
通过这种方式,我们已经成功安装并测试了我们的 ROS 安装。在下一节中,我们将运行一个模拟机器人并通过 MQTT 消息来控制它。
运行和控制模拟机器人
在本节中,我们探讨使用 TurtleSim,这是一个轻量级且用户友好的工具,在 ROS 中用于模拟机器人的运动和行为。TurtleSim 是一个优秀的教育资源,特别是对于 ROS 的初学者来说,可以帮助他们理解基本概念,如节点交互、消息传递和简单的机器人命令。
通过使用 TurtleSim,我们将学习如何创建一个 ROS 节点来控制虚拟机器人。我们将使用 MQTT 消息来控制 TurtleSim 机器人。在接下来的章节中,我们将使用在这里学到的知识将我们的机器人转换为物理机器人,我们将其称为 A.R.E.S。
我们将首先在 Ubuntu 中启动一个 TurtleSim 节点,然后使用一个单独的 ROS 节点来控制它。
启动和测试 TurtleSim
如前所述,TurtleSim 设计用于帮助新用户通过简单界面熟悉 ROS 的功能,如节点、主题和服务。通过运行 TurtleSim,我们可以在一个受控环境中模拟机器人的运动和行为。
要启动 TurtleSim,我们执行以下操作:
-
在 Ubuntu 中,我们打开一个新的终端并输入以下命令:
source /opt/ros/humble/setup.bash turtlesim_node node from the turtlesim package. Once these commands are executed, a TurtleSim window should appear, displaying a graphic of a simulated turtle positioned in the center of the screen:

图 11.13 – TurtleSim 机器人
-
要控制海龟,我们使用另一个节点。为此,我们在 Ubuntu 中打开另一个终端并执行以下命令:
source /opt/ros/humble/setup.bash turtle_teleop_key node from the turtlesim package. This node allows us to control the TurtleSim robot with our keyboard. Our Terminal should look like the following:

图 11.14 – 运行 turtle_teleop_key 节点
- 我们可以通过按住键盘上的箭头键来移动我们的 TurtleSim 机器人。我们还可以使用终端中列出的任何键来旋转我们的机器人:

图 11.15 – 移动 TurtleSim 机器人的结果
-
要在 ROS 中可视化节点和主题连接,我们可以使用
rqt_graph,这是一个图形工具,显示节点如何交互。它对于调试和解析 ROS 系统中的网络特别有帮助。要启动rqt_graph,我们在新的 Ubuntu 终端中输入以下命令:source /opt/ros/humble/setup.bash rqt_graph tool. We should see the following window:

图 11.16 – 在 ROS 中使用 rqt_graph 工具
- 从我们的图中,
cmd_vel主题是 ROS 中的一个关键通信通道,它将作为控制器的teleop_turtle节点与模拟机器人的turtlesim节点连接起来。在我们的 ROS 图中,我们将 TurtleSim 实例称为turtle1,并且我们在与cmd_vel主题交互时使用这个名字来识别被控制的特定海龟。图还显示了rotate_absolute动作。对于我们的基本机器人控制应用,我们只对cmd_vel主题通信感兴趣。
为什么我们启动 turtle_teleop_key 但查看 teleop_turtle?
rqt_graph工具中teleop_turtle节点名称与ros2 run turtlesim turtle_teleop_key命令之间的区别源于框架的命名约定和结构。《turtle_teleop_key》命令指的是 ROS 中的可执行文件,当运行时,初始化一个 ROS 节点。这个节点在 ROS 环境中内部命名为teleop_turtle,用于通信和识别。这种方法允许 ROS 具有灵活性,其中单个可执行文件可以启动不同的节点,节点名称可以根据特定需求或配置动态更改。节点名称对于网络通信,如发布到主题,是至关重要的,而可执行文件名称只是用于启动节点。
我们的练习展示了我们如何使用一个节点来控制机器人——在这个例子中,是teleop_turtle节点,它允许我们通过键盘控制 TurtleSim 机器人。在下一节中,我们将创建自己的节点,这将使我们能够通过 MQTT 消息来控制机器人。
创建 ROS 工作空间和包
在 ROS 2 中,包的结构和创建对于组织、分发和编译我们的代码至关重要。在 ROS 2 中创建一个包涉及使用ament作为构建系统和colcon作为构建工具。我们有选择以 C++或 Python 创建包的选项。
ROS 2 包的内容取决于它是一个 CMake 包还是 Python 包。通常,CMake 包包括一个CMakeLists.txt文件和一个package.xml文件,以及源代码和包头文件的目录。另一方面,Python 包将包括package.xml和setup.py文件,以及一个与包同名且包含__init__.py文件等文件的目录。对于我们的应用,我们将使用 Python。
在 ROS 2 工作空间中,我们可以有多个包,每个包都有自己的文件夹,并且这些包可以是不同的构建类型。建议将包保存在我们工作空间的src文件夹中,以保持组织。
在以下图中,我们可以看到我们将要构建的 ROS 工作空间的结构,其中点(.)代表工作空间的根目录。工作空间只是我们的文件系统中的一个目录。在src文件夹下,我们有一个名为mqtt_robot的单个包:

图 11.17 – ROS 工作空间结构
为了构建我们的 ROS 应用程序,我们将首先创建一个工作空间,这是一个包含用于组织我们应用程序 ROS 包的 src 子文件夹的 Linux 目录。
要做到这一点,我们执行以下操作:
-
我们在 Ubuntu 终端中打开我们的家目录并执行以下命令:
mkdir -p ch11_ws/src
重要提示
在家目录中创建我们的工作空间简化了未来的导航,因为我们可以在命令中使用 ~ 字符作为快捷方式。
使用此命令,我们创建了一个工作空间。mkdir 命令中的 -p 标志确保任何必要的父目录作为新目录路径的一部分被创建。
-
创建文件夹后,我们使用以下命令导航到
src文件夹:cd ch11_ws/src -
为了初始化我们的 ROS 环境,我们执行以下命令:
source /opt/ros/humble/setup.bash -
然后,我们通过执行以下命令创建我们的包:
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name draw_circle mqtt_robot --license Apache-2.0在我们构建包的命令中,我们指定了 Apache 2.0 许可证。
什么是 Apache 2.0 许可证?
此许可证是一个开源许可证,允许商业和非商业用途以及修改,但要求在分发版本中披露重大更改,并明确授予用户专利权。在我们的情况下,此披露要求仅适用于我们修改构建工具或 Apache 2.0 许可证软件的现有代码,而不是在包创建后我们自己编写的代码。
- 通过这一行,我们创建了一个名为
mqtt_robot的新包,具有 Python 构建类型,并生成了一个名为draw_circle的节点。要查看新的文件结构,我们执行tree命令:

图 11.18 – src 目录下的工作空间文件结构
使用我们创建的 ch11_ws 工作空间和 mqtt_robot 包,我们现在可以开始修改生成的代码以适应我们的需求。我们将从 draw_circle.py 脚本开始。
修改生成的 Python 代码
在我们的包代码生成后,我们在 src 文件夹下看到一个名为 mqtt_robot 的文件夹。这个文件夹代表我们的包。在这个文件夹内部还有一个同名的文件夹,mqtt_robot。正是在这个第二个 mqtt_robot 文件夹中,我们找到了我们应用程序的主要 Python 代码。
为了创建我们应用程序的逻辑,我们将修改 draw_circle.py 脚本。为此,我们执行以下操作:
-
对于我们的应用程序,我们需要
paho-mqtt库来进行 MQTT 通信。在 Ubuntu 终端中,我们输入以下命令来安装库:draw_circle.py script with the following command:在文本编辑器中打开
draw_circle.py文件,我们执行以下命令:gedit draw_circle.py -
我们首先删除所有代码。然后,我们以导入开始编写新的代码:
import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist import paho.mqtt.client as mqtt在我们的代码中,我们有以下内容:
-
import rclpy:导入 ROS 2 客户端库,允许脚本与 ROS 2 功能交互并创建 ROS 2 节点。 -
from rclpy.node import Node: 从rclpy模块中导入Node类,使脚本能够为 ROS 2 应用程序定义自定义节点。 -
from geometry_msgs.msg import Twist: 从geometry_msgs包中导入Twist消息类型;我们使用它来发送命令以移动 TurtleSim 机器人。 -
import paho.mqtt.client as mqtt: 导入我们将用于 MQTT 协议通信的 Paho MQTT 客户端库。
-
-
我们定义了一个
MQTTMessage类,它包括一个初始化方法和一个设置标志的方法。__init__()方法将should_draw_circle属性初始化为False,而set_flag()方法在类接收到draw_circle消息时将此属性更新为True。当接收到stop消息时,我们将should_draw_circle设置为False:class MQTTMessage: def __init__(self): self.should_draw_circle = False def set_flag(self, message): if message == 'draw_circle': self.should_draw_circle = True elif message == 'stop': self.should_draw_circle = False -
我们从 ROS 2 的
Node类派生出CircleMover类。这个类旨在根据 MQTT 消息控制 TurtleSim 中的模拟海龟的运动。该类使用 MQTT 消息处理程序初始化,设置turtle1/cmd_vel主题的发布者以控制运动,并配置 MQTT 客户端以连接到代理并处理传入的消息:class CircleMover(Node): def __init__(self, mqtt_message): super().__init__('circle_mover') self.mqtt_message = mqtt_message self.publisher = self.create_publisher( Twist, 'turtle1/cmd_vel', 10) timer = 0.1 # seconds self.timer = self.create_timer( timer, self.timer_callback) self.vel_msg = Twist() # Initialize MQTT Client and set up callbacks self.mqtt_client = mqtt.Client() self.mqtt_client.on_connect = self.on_connect self.mqtt_client.on_message = self.on_message self.mqtt_client.username_pw_set("username", "password") self.mqtt_client.connect(" driver.cloudmqtt.com", port, 60) self.mqtt_client.loop_start() def on_connect(self, client, userdata, flags, rc): client.subscribe("move") def on_message(self, client, userdata, msg): self.mqtt_message.set_flag( msg.payload.decode()) -
类内部的
timer_callback()函数根据由 MQTT 消息设置的should_draw_circle标志确定海龟的运动,从而通过 MQTT 实现对海龟的动态控制:def timer_callback(self): if self.mqtt_message.should_draw_circle: self.vel_msg.linear.x = 1.0 self.vel_msg.angular.z = 1.0 else: self.vel_msg.linear.x = 0.0 self.vel_msg.angular.z = 0.0 self.publisher.publish(self.vel_msg) -
为了完成我们的代码,我们为我们的 ROS 2 Python 脚本定义了一个
main()函数,该函数初始化 ROS 客户端库,创建MQTTMessage类的一个实例,然后使用 MQTT 消息处理程序创建CircleMover类的一个实例。它使用rclpy.spin()方法运行 ROS 节点,保持节点活跃并响应回调。在终止时,它销毁节点并关闭 ROS 客户端库。我们使用main()函数作为脚本的入口点,在直接运行脚本时执行它:def main(args=None): rclpy.init(args=args) mqtt_message = MQTTMessage() circle_mover = CircleMover(mqtt_message) rclpy.spin(circle_mover) circle_mover.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() -
完成我们的代码后,我们使用相同的名称,
draw_circle.py,保存文件。
我们的下一步是更新 package.xml 文件,以包含我们的代码所需的 Python 库依赖项。
更新 package.xml
ROS 中的 package.xml 文件是一个描述符,它包含有关 ROS 包的基本信息,例如其名称、版本、维护者、许可证和依赖项。对于构建过程至关重要,它通知 colcon 构建工具编译包所需的依赖项。它由开发者创建和维护。
对于我们的目的,我们将修改 package.xml 以通知它我们的代码需要编译的 Python 库。
要做到这一点,我们执行以下操作:
-
我们打开一个 Ubuntu 终端并导航到包含
package.xml文件的文件夹:package.xml in a text editor, we execute the following command:):
<exec_depend>rclpy</exec_depend> <exec_depend>paho-mqtt</exec_depend> -
这些行指示编译器,
rclpy(ROS 的 Python 客户端库)和paho-mqtt(用于 MQTT 通信)是 ROS 包的执行依赖项,这意味着这些包是运行包中包含的 ROS 节点所必需的。我们保存我们的更改并关闭编辑器。
通过更新package.xml,我们现在可以编译我们的代码并运行我们的新节点。
编译和运行我们的代码
要编译我们的代码,我们使用colcon ROS 工具,这是一个用于编译 ROS 包、处理依赖项并在工作空间中跨多个包协调构建的命令行工具。对于我们的应用程序,我们需要它只编译一个包。
要编译和执行我们的新代码,我们执行以下操作:
-
在 Ubuntu 中,我们打开一个新的终端并源我们的 ROS 2 环境:
source /opt/ros/humble/setup.bash -
然后,我们导航到我们的工作空间根目录:
cd ~/ch11_ws -
要编译我们的代码,我们执行以下命令:
colcon build
为什么我们从 src 文件夹创建一个包,但从根目录编译?
值得注意的是,在 ROS 工作空间中,包的创建和编译发生在不同的级别。虽然我们在工作空间的src文件夹内创建单个包,但编译是在工作空间的根文件夹中进行的。这种区别是关键的:创建包是在src中的本地化操作,但使用colcon在工作空间根目录编译确保src中的所有包一起构建。
-
完成后,终端将出现一条确认成功构建的消息。
-
我们的代码编译完成后,现在是时候源我们的新 ROS 环境了。我们使用以下命令执行此操作:
source ~/ch11_ws/install/setup.bash -
这就像我们源 ROS 环境一样。要运行我们的节点,我们执行以下命令:
draw_circle, from the package we created, mqtt_robot. Upon execution, our Terminal will enter a wait state, ready to respond to incoming events or actions. -
如果尚未运行,我们启动一个 TurtleSim 实例。
我们可能会注意到没有任何动作发生。TurtleSim 中的海龟没有移动,而我们启动节点所用的终端处于等待状态。要使海龟移动,我们需要向我们的新节点发送一个 MQTT 消息。
让我们来做这件事。
使用 MQTT 消息控制我们的机器人
我们在图 11.1中看到了我们应用程序的高级概述,其中使用 MQTT-Explorer 的 MQTT 消息指导我们的 ROS 模拟机器人。draw_circle消息提示海龟画一个圆圈,而stop则停止其移动。这构成了我们项目的基础,我们将在接下来的章节中通过添加更多功能来扩展它。
要通过 MQTT 消息控制 TurtleSim 机器人,我们执行以下操作:
-
在 Windows 中使用 MQTT-Explorer 应用程序,我们向
move主题发布一个draw_circle消息。 -
发送后,我们应该观察到我们的 TurtleSim 机器人开始画圈移动:

图 11.19 – 从 MQTT 消息移动的 TurtleSim 机器人
-
要停止机器人,我们使用 MQTT-Explorer 应用程序在
move主题下发送一个stop消息。 -
我们应该观察到 TurtleSim 机器人停止移动。
我们使用 MQTT 消息控制虚拟机器人的演示为将所学概念应用到即将到来的真实机器人项目 A.R.E.S.奠定了基础。
摘要
在本章中,我们开始了对 ROS 的探索。我们首先在 Raspberry Pi 4 上设置了 ROS,选择 Ubuntu 而不是标准的 Raspberry Pi OS,以获得与 ROS 更好的兼容性。
我们的动手之旅从用户友好的 ROS 模拟器 TurtleSim 开始。我们学习了基本的 ROS 操作和概念,从键盘控制来操控虚拟机器人开始。然后我们进阶到使用 MQTT 消息进行控制,架起了模拟与现实应用之间的桥梁。
在 TurtleSim 上的这次体验为我们主要项目 A.R.E.S.打下了基础,A.R.E.S.是一个将在接下来的章节中开发的先进物联网机器人。
在下一章中,我们将回到构建物联网设备,因为我们正在构建一个 MQTT 游戏手柄来控制我们的 TurtleSim 机器人。
第十二章:创建物联网游戏手柄
在本章中,我们将创建一个用于远程控制 ROS TurtleSim 机器人的物联网游戏手柄。我们将基于第七章的物联网按钮项目以及第十一章对 TurtleSim 虚拟机器人的介绍来构建我们的经验。
利用 Raspberry Pi Pico WH 的 Wi-Fi 功能,我们将展示物联网在机器人领域的实际应用。本章概述了设计并构建一个 USB 供电的游戏手柄,集成了 PS2 游戏手柄模块、LED 和街机按钮等组件。我们将使用这个物联网游戏手柄在接下来的章节中控制我们的 A.R.E.S.机器人。
尽管 Raspberry Pi Pico W 也可以,但我们将使用 Raspberry Pi Pico WH 本章。
在本章中,我们将涵盖以下主题:
-
理解我们的物联网游戏手柄应用
-
连接我们的电路
-
开发我们的物联网游戏手柄代码
-
为我们的应用创建一个定制的 ROS 节点
-
构建物联网游戏手柄外壳
让我们开始吧!
技术要求
在本章中,你需要以下内容:
-
中级 Python 编程知识
-
基本的 Linux 命令行知识。
-
用于 MQTT 服务器实例的 CloudAMQP 账户
-
从上一章安装的 Ubuntu-ROS 计算机
-
可以访问 3D 打印机或 3D 打印服务
-
Raspberry Pi Pico WH
-
Raspberry Pi Pico WH GPIO 扩展器
-
PS2 游戏手柄模块
-
带有 220 欧姆电阻的 LED
-
24 毫米街机式按钮
-
4 个 M3 10 毫米螺栓
-
4 个 M2 5 毫米螺丝
-
8 个 M2.5 5 毫米螺栓
-
4 个 M2.5 10 毫米支撑螺母
-
1 个 8 毫米 LED 支架
-
连接 Raspberry Pi Pico WH 到 GPIO 扩展器的电线
-
定制外壳的构建文件可以在我们的 GitHub 仓库中找到
本章的代码可以在以下链接找到:
github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter12
理解我们的物联网游戏手柄应用
在第七章中,我们开发了一个远程启动物联网报警系统的设备。基于这一经验,本章的应用涉及使用 Raspberry Pi Pico WH 创建一个物联网游戏手柄。这个游戏手柄将远程控制 ROS TurtleSim 机器人,从而展示物联网在机器人领域的实际应用。
由于 Wi-Fi 功能和预先焊接的引脚头,我们将使用 Raspberry Pi Pico WH 进行设计。在我们的应用中,我们将集成 PS2 游戏手柄模块、LED 和街机式按钮与 Raspberry Pi Pico WH。Raspberry Pi Pico WH 将被编程以传输反映游戏手柄动作、游戏手柄按钮状态和街机按钮状态的 MQTT 消息。此外,LED 将具有双重功能,依次指示 Wi-Fi 连接状态和 MQTT 连接状态:

图 12.1 – 物联网游戏手柄应用概述
如我们在 图 12**.1 中所见,我们的 Raspberry Pi Pico WH 将 x 轴、y 轴和两个按钮的状态作为 MQTT 消息,在 JoystickPosition 主题下发送到我们的自定义 ROS 节点 robot_control。然后,我们的 ROS 节点反过来将 vel_msg 消息发送到 TurtleSim 机器人的一个实例。
我们将首先连接我们的物联网游戏手柄的电路。
连接电路
为了简化布线,我们正在使用 GPIO 扩展器以及我们的 Raspberry Pi Pico WH。我们稍后将要构建的机箱允许轻松转移测试电路的布线,突出了使用 GPIO 扩展器的实用性。我们可以在以下图中看到组成物联网游戏手柄的组件:

图 12.2 – 物联网游戏手柄的组件
在 图 12**.2 中,我们使用的组件包括一个 24 毫米的街机风格按钮,一个绿色 LED(任何颜色都可以)焊接到一个 220 欧姆电阻上(有关此处的说明,请参阅 第三章),以及一个 PS2 游戏手柄模块。
这种设置使得将我们的组件转移到定制机箱以进行最终安装变得更加容易。使用这些组件,我们根据 图 12**.3 作为参考连接电路:

图 12.3 – 连接物联网游戏手柄
连接如下:
-
街机风格按钮连接到 GP0 和 GND。
-
带有 220 欧姆电阻的 LED 正端连接到 GP5。
-
LED 的负端连接到 GND。
-
SW(开关)从 PS2 游戏手柄模块连接到 GP22。
-
VRY (y 轴) 从 PS2 游戏手柄模块连接到 GP26。
-
VRX (x 轴) 从 PS2 游戏手柄模块连接到 GP27。
-
+5V(电源)从 PS2 游戏手柄模块连接到 3V3。
-
PS2 游戏手柄模块的 GND 连接到 GND。
在电路布线完成后,我们就可以开始编写我们的应用程序了。
开发我们的物联网游戏手柄的代码
我们将在 Raspberry Pi Pico WH 上安装 CircuitPython 并使用 Thonny 进行开发。我们的 Pico WH 代码将包括两个文件,一个用于封装游戏手柄功能,另一个用于发送 MQTT 消息。
我们将首先设置我们的 Raspberry Pi Pico WH 以进行开发。
设置我们的 Raspberry Pi Pico WH
对于我们的物联网游戏手柄,我们将安装 CircuitPython 并使用 Adafruit MiniMQTT 库。我们同样可以使用 MicroPython 和 micropython-umqtt.simple 包。然而,在物联网游戏手柄应用中使用 CircuitPython 的 Raspberry Pi Pico WH 提供了比 MicroPython 更稳定、维护得更好的库。
要在 Raspberry Pi Pico WH 上安装 CircuitPython,我们执行以下与我们在 第九章 中所做的相同步骤:
-
如果我们的操作系统上没有 Thonny,我们访问 Thonny 网站,下载适当的版本 (
thonny.org)。 -
然后我们使用适合我们操作系统的适当方法启动 Thonny。
-
在按住 Pico WH 上的 BOOTSEL 按钮(USB 端口附近的小白按钮)的同时,我们将它插入一个可用的 USB 端口,并忽略可能出现的任何弹出窗口。
-
然后我们点击屏幕右下角的解释器信息,并选择 安装 CircuitPython…:

图 12.4 – 安装 CircuitPython… 选项
- 对于我们的目标版本,我们选择我们的 Pico WH(在我们的示例中为 RPI-RP2 (D:))。然后我们选择 CircuitPython 变种的最新版本 – Raspberry Pi • Pico W / Pico WH:

图 12.5 – 在 Raspberry Pi Pico WH 上安装 CircuitPython
-
安装完成后,我们点击 安装 按钮,然后点击 关闭 按钮。
-
要将 Thonny 配置为在我们的 Pico 上运行 CircuitPython 解释器,我们从屏幕右下角选择它:

图 12.6 – 从我们的 Pico WH 选择 CircuitPython 解释器
- 在我们的 Raspberry Pi Pico WH 上安装好 CircuitPython 后,下一步是安装 Adafruit MiniMQTT 库。为此,我们遵循 第十章 中 安装 CircuitPython 库的 MQTT 部分中概述的步骤。
当我们的 Raspberry Pi Pico WH 准备好开发时,是时候创建一个 Joystick 类了。
创建一个 Joystick 类
如前所述,我们的 Pico WH 代码分为两个文件,一个用于将摇杆封装在我们称为 Joystick 的类中,另一个基于该类的值发送 MQTT 消息。我们首先从摇杆代码开始。
要编写摇杆代码,我们执行以下操作:
-
我们将 Raspberry Pi Pico WH 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来完成此操作。
-
我们然后通过从屏幕的右下角选择它来激活 Pico WH 上的 CircuitPython 环境。
-
在一个新的编辑器中,我们开始编写代码,使用导入语句:
import board import digitalio import analogio import time在我们的代码中,我们有以下内容:
-
import board:访问特定于板的引脚和硬件接口,这对于与 Raspberry Pi Pico W 上的 GPIO 引脚进行接口至关重要。 -
import digitalio:管理数字输入和输出,例如读取按钮的状态或控制 LED,这对于数字信号交互至关重要。 -
import analogio:促进模拟输入功能,例如读取在一定范围内变化的传感器数据,这在涉及如电位计等可变输入的项目中是常见的要求。 -
import time:提供时间相关函数,使任务如引入程序执行延迟成为可能,这在控制操作流程和时机方面很有用。
-
-
我们接着定义一个
Joystick类并设置变量:class Joystick: def __init__(self): self.adc_x = analogio.AnalogIn(board.GP27) self.adc_y = analogio.AnalogIn(board.GP26) self.button = digitalio.DigitalInOut(board.GP0) self.button.direction = digitalio.Direction.INPUT self.button.pull = digitalio.Pull.UP self.button2 = digitalio.DigitalInOut(board.GP22) self.button2.direction = digitalio.Direction.INPUT self.button2.pull = digitalio.Pull.UP self.mid = 32767 self.dead_zone = 10000在我们的代码中,我们有以下内容:
-
初始化(
__init__()方法):我们的代码设置了Joystick类。 -
self.adc_x和self.adc_y:这些是摇杆x和y轴的模拟输入对象,分别连接到 GP27 和 GP26 引脚。它们从摇杆的电位计读取模拟值。 -
self.button:一个按钮的数字输入/输出对象,连接到 GP0 引脚。它配置为输入并带有上拉电阻,这是按钮的常见设置。此变量代表我们的街机风格按钮的状态。 -
self.button2:类似于self.button。这代表一个连接到 GP22 引脚的第二个按钮,也设置为输入并带有上拉电阻。此变量代表摇杆按钮的状态(通过按下摇杆激活)。 -
self.mid:模拟读数的中间值,用于确定摇杆的中性位置。 -
self.dead_zone:死区阈值决定了摇杆的灵敏度,区分轻微的无意动作和有意动作。这解释了当摇杆处于中性位置时的微小差异。
-
-
在设置好我们的变量后,我们编写了第一个方法,我们称之为
get_binary_value()。此函数旨在将摇杆的位置解释为二进制输出。它首先检查摇杆的当前位置,由value表示,是否在围绕中点的预定义死区(self.mid)内。如果是这样,它返回0,表示摇杆处于中性位置。如果摇杆的位置超出此死区,则函数返回-1(对于低于中点的位置,表示负方向)和1(对于高于中点的位置,表示正方向):def get_binary_value(self, value): if abs(value - self.mid) < self.dead_zone: return 0 return -1 if value < self.mid else 1 -
我们接着定义我们的第二个方法,
read()。此方法将摇杆和按钮的状态合并为一个单一输出。它使用get_binary_value()方法计算摇杆的x和y轴的二进制值,根据位置将模拟读数转换为二进制(-1、0或1)。它还评估两个按钮的状态,将它们的数字值转换为布尔格式(按下或未按下)。然后,该方法返回一个包含这些二进制值和按钮状态的元组:def read(self): x_val = self.get_binary_value(self.adc_x.value) y_val = self.get_binary_value(self.adc_y.value) button_state = not self.button.value button2_state = not self.button2.value return x_val, y_val, button_state, button2_state -
我们随后编写测试代码。我们使用此代码来测试我们的
Joystick类。它初始化Joystick类的一个实例,并进入一个无限循环以连续测试摇杆的功能。在循环的每次迭代中,它使用read()方法读取摇杆的x和y轴的当前位置以及两个按钮的状态。然后,这些值被打印到控制台,显示摇杆的x和y位置以及每个按钮的按下状态。if __name__ == "__main__":块确保只有当脚本作为主程序执行时,这个主循环才会运行,而不是当作为模块导入时,这使得我们能够轻松地测试我们的Joystick类:if __name__ == "__main__": joystick = Joystick() while True: x, y, button_state, button2_state = joystick.read() print("X Position:", x) print("Y Position:", y) print("Button 1 Pressed:", button_state) print("Button 2 Pressed:", button2_state) time.sleep(5) -
要保存文件,我们点击
joystick.py到我们的 Raspberry Pi Pico WH。 -
要运行我们的代码,我们点击绿色的运行按钮,在键盘上按F5,或者点击顶部的运行菜单选项,然后选择运行****当前脚本。
-
在 shell 中,我们应该观察到摇杆值随着摇杆的移动和按钮的按下而改变:

图 12.7 – 摇杆和按钮状态
在我们的Joystick类和joystick.py文件编写并成功测试后,我们现在可以编写代码以通过 MQTT 消息发送摇杆状态。
从我们的物联网摇杆发送 MQTT 消息
在创建并测试了Joystick类之后,现在是时候编写代码以通过 MQTT 消息发送摇杆和按钮状态了。
要编写摇杆代码,我们执行以下操作:
-
我们将 Raspberry Pi Pico WH 连接到 USB 端口并启动 Thonny。我们可以使用 Raspberry Pi 或其他操作系统来完成此操作。
-
我们随后通过从屏幕的右下角选择它来激活 Pico WH 上的 CircuitPython 环境(参见*图 12.6**)。
-
在一个新的编辑器中,我们以导入开始我们的代码:
import time import board import wifi import socketpool import digitalio from adafruit_minimqtt.adafruit_minimqtt import MQTT from joystick import Joystick在我们的代码中,我们有以下内容:
-
import time: 包含时间相关函数,在代码中用于延迟和定时控制。 -
import board: 提供了对 Raspberry Pi Pico WH 硬件特定细节的访问,特别是其 GPIO 引脚。 -
import wifi: 启用 Wi-Fi 功能,对于物联网应用中的网络连接至关重要。 -
import socketpool: 管理网络套接字,用于网络通信,如 MQTT 消息传递。 -
import digitalio: 允许进行数字输入和输出控制,这对于管理 LED、按钮和其他数字组件很有用。 -
from adafruit_minimqtt.adafruit_minimqtt import MQTT: 导入 Adafruit MiniMQTT 库,用于处理 MQTT 协议通信,这是物联网消息的标准。 -
from joystick import Joystick: 导入我们的自定义Joystick类,用于处理与我们的摇杆模块的接口逻辑。
-
-
我们随后设置变量声明:
WIFI_SSID = 'MySSID' WIFI_PASSWORD = 'SSID-password' MQTT_SERVER = "mqtt-server" MQTT_PORT = 18756 USERNAME = "mqtt-username" PASSWORD = "mqtt-password" MQTT_TOPIC = "JoystickPosition" led = digitalio.DigitalInOut(board.GP5) led.direction = digitalio.Direction.OUTPUT在我们的代码中,我们有以下内容:
-
WIFI_SSID和WIFI_PASSWORD: 这些变量存储 Wi-Fi 网络的凭据,对于将 Raspberry Pi Pico WH 连接到互联网至关重要。 -
MQTT_SERVER、MQTT_PORT、USERNAME、PASSWORD: 这些设置用于配置 MQTT 客户端。它们指定了连接到我们的 CloudAMQP 服务器所需的服务器地址、端口号和认证凭据。 -
MQTT_TOPIC: 定义了消息将要发布的 MQTT 主题。在这种情况下,它设置为JoystickPosition,表示与摇杆位置相关的消息将被发送到这个主题。 -
led = digitalio.DigitalInOut(board.GP5): 在 Raspberry Pi Pico W 的 GP5 引脚上初始化一个数字输出,用于我们的 LED。 -
led.direction = digitalio.Direction.OUTPUT: 将引脚方向设置为输出,允许我们的程序控制 LED(例如,打开或关闭)。
-
-
我们使用
flash_led()方法打开和关闭我们的 LED,将其用作状态指示器。我们的方法接受时间和持续时间值,以便根据特定的程序状态自定义 LED 的闪烁:def flash_led(times, duration): for _ in range(times): led.value = True time.sleep(duration) led.value = False time.sleep(duration) -
connect_to_wifi()方法用于将我们的 Raspberry Pi Pico WH 连接到我们的 Wi-Fi 路由器和互联网。此函数持续尝试连接到 Wi-Fi,使用WIFI_SSID和WIFI_PASSWORD凭据。在失败的情况下,它将打印错误消息,闪烁 LED,并在 3 秒暂停后重试:def connect_to_wifi(): while True: try: print("Trying to connect to WiFi...") wifi.radio.connect(WIFI_SSID, WIFI_PASSWORD) print("Connected to Wi-Fi!") break except Exception as e: print("Failed to connect to WiFi. Retrying...") flash_led(1, 2) time.sleep(3) -
connect_to_mqtt()函数尝试在循环中建立与 MQTT 服务器的连接。如果连接成功,它将打印确认消息并退出循环。在连接失败的情况下,它将报告失败,激活半秒的 LED 闪烁作为错误指示,然后等待 3 秒后重试:def connect_to_mqtt(mqtt_client): while True: try: print("Trying to connect to MQTT Broker...") mqtt_client.connect() print("Connected to MQTT server!") break except Exception as e: print("Failed to connect to MQTT. Retrying...") flash_led(1, 0.5) time.sleep(3) -
然后,我们的代码调用
connect_to_wifi()来建立 Wi-Fi 连接。接下来,从wifi.radio创建一个套接字池来管理网络通信。然后,使用指定的服务器、端口和用户凭据实例化一个 MQTT 客户端,并通过connect_to_mqtt(mqtt_client)连接到 MQTT 代理。在建立 MQTT 连接后,将 LED 设置为持续开启状态(led.value = True),作为设置成功的指示。最后,创建一个Joystick类的实例,准备捕获用户输入:connect_to_wifi() pool = socketpool.SocketPool(wifi.radio) mqtt_client = MQTT(broker=MQTT_SERVER, port=MQTT_PORT, username=USERNAME, password=PASSWORD, socket_pool=pool) connect_to_mqtt(mqtt_client) led.value = True joystick = Joystick() -
代码中的
send_mqtt_message()函数负责格式化和发送摇杆数据通过 MQTT。它接受摇杆的x和y轴的值以及两个按钮的状态。按钮的状态根据它们是否被按下转换为True或False。然后,该函数构建一个包含x和y位置以及两个按钮状态的消息字符串。该消息被发布到由MQTT_TOPIC定义的 MQTT 主题,允许通过 MQTT 协议传输摇杆的状态:def send_mqtt_message(x, y, button1, button2): button1_state = True if button1 else False button2_state = True if button2 else False message = f'X: {x}, Y: {y}, Button 1: \ {button1_state}, Button 2: {button2_state}' mqtt_client.publish(MQTT_TOPIC, message) -
代码中的
main()函数代表读取摇杆输入并发送相应 MQTT 消息的主要循环。在无限循环中,它不断调用joystick.read()方法以获取摇杆的 x 和 y 轴的当前位置以及两个按钮的状态。然后,它将这些值传递给send_mqtt_message()函数以格式化和传输它们作为 MQTT 消息。每个循环迭代结束时包含一个 1 秒延迟 (time.sleep(1)),以管理 MQTT 传输的频率。if __name__ == "__main__":块确保只有当脚本作为主程序执行时,此主循环才会运行,而不是当作为模块导入时:def main(): while True: x, y, button1_pressed, button2_pressed = joystick.read() send_mqtt_message(x, y, button1_pressed, button2_pressed) time.sleep(1) if __name__ == "__main__": main() -
要保存文件,我们点击
code.py到我们的 Raspberry Pi Pico WH。 -
要运行我们的代码,我们点击绿色的 运行 按钮,在键盘上按 F5,或者点击顶部的 运行 菜单选项,然后点击 运行 当前脚本。
-
为了测试我们的代码,我们将 MQTT-Explorer 应用程序连接到我们的 CloudAMQP 服务器,并观察接收到的消息。
-
我们应该观察到三个不同的输出值:
1、0和-1,分别表示它们的中间、正和负位置。此外,我们还应该看到两个按钮的状态(True或False):Button 1对应于街机风格的按钮,而Button 2表示摇杆的点击动作。
在我们的物联网摇杆代码完成后,是时候创建我们的自定义 robot_control ROS 节点,以便我们可以控制 TurtleSim 机器人。
为我们的应用程序创建一个自定义的 ROS 节点
在 第十一章 的 创建 ROS 工作空间和包 部分,我们概述了如何设置用于 TurtleSim 机器人控制的 circle 节点。遵循此蓝图,我们现在将在 第十一章 中提到的 ROS 配备的 Ubuntu 系统上创建一个 robot_control 节点。这包括为 robot_control 节点设置一个新的 ROS 工作空间和包,使我们能够使用新的物联网摇杆控制 TurtleSim 机器人。
为了确保清晰并避免与现有的 circle 节点发生任何潜在的混淆,尽管有可能重用 第十一章 中的那些,我们将为 robot_control 节点创建一个新的工作空间和包。这种方法使我们能够为每个项目维护独立的环境。
创建我们的自定义 robot_control 节点
由于我们已经在 Ubuntu 安装上安装了 paho-mqtt Python 库,因此我们不需要再次安装它。
要创建我们的新节点,我们执行以下操作:
-
我们在 Ubuntu 终端中打开并执行以下命令,在主目录下:
src folder with the following command:cd ch12_ws/src
-
要初始化 ROS 环境,我们执行以下命令:
source /opt/ros/humble/setup.bash -
然后我们通过执行以下命令创建我们的包:
ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name robot_control mqtt_robot mqtt_robot with the Python build type and generated a node named robot_control. This will give us a Python script named robot_control.py. To navigate to the folder that contains this script, we enter the following command:在文本编辑器中打开
robot_control.py文件,我们执行以下命令:gedit robot_control.py -
我们首先删除所有代码。然后,我们用导入语句开始我们的新代码:
import rclpy from rclpy.node import Node from geometry_msgs.msg import Twist import paho.mqtt.client as mqtt在我们的代码中,我们有以下内容:
-
import rclpy: 导入 ROS 2 的 Python 客户端库,允许脚本与 ROS 2 功能交互并创建 ROS 2 节点 -
from rclpy.node import Node: 从rclpy模块导入Node类,使脚本能够为 ROS 2 应用程序定义自定义节点 -
from geometry_msgs.msg import Twist: 从geometry_msgs包导入Twist消息类型;我们使用它来发送移动 TurtleSim 机器人的命令 -
import paho.mqtt.client as mqtt: 导入我们将用于 MQTT 协议通信的 Paho MQTT 客户端库
-
-
在我们的代码中,我们创建了一个
MQTTMessage类。此类根据 MQTT 消息的内容有效地解析和更新其属性,该消息包含摇杆位置数据和按钮状态:class MQTTMessage: def __init__(self): self.x = 0 self.y = 0 self.button1 = False self.button2 = False def update_values(self, message): parts = message.split(', ') self.x = float(parts[0].split(': ')[1]) self.y = float(parts[1].split(': ')[1]) self.button1=parts[2].split(': ')[1].strip() == "True" self.button2 = parts[3].split(': ')[1].strip() == "True"在我们的代码中,我们有以下内容:
-
class MQTTMessage: 定义用于处理 MQTT 消息的类。 -
__init__()方法:-
将
x和y初始化为0,代表默认位置。 -
将
button1和button2设置为False,表示它们的默认未按下状态。
-
-
update_values()方法:-
以
message字符串作为输入,并根据逗号将其分割成部分。 -
解析
message以提取并将X和Y值转换为浮点数。 -
通过分割字符串部分并将它们与
"True"进行比较来确定button1和button2的状态。使用strip()方法来删除任何前导/尾随空格。
-
-
-
RobotController类是Node类的子类:class RobotController(Node): def __init__(self, mqtt_message): super().__init__('robot_controller') self.mqtt_message = mqtt_message self.publisher = self.create_publisher( Twist, 'turtle1/cmd_vel', 10) timer_period = 0.1 self.timer = self.create_timer( timer_period, self.timer_callback) self.vel_msg = Twist() self.mqtt_client = mqtt.Client() self.mqtt_client.on_connect = self.on_connect self.mqtt_client.on_message = self.on_message self.mqtt_client.username_pw_set( "mqtt-username", "mqtt-password") self.mqtt_client.connect( "driver.cloudmqtt.com", 18756, 60) self.mqtt_client.loop_start()在我们的代码中,我们有以下内容:
-
我们将
RobotController定义为Node的子类 -
__init__()方法:-
使用名称
robot_controller初始化节点 -
存储作为参数传递的
mqtt_message -
在
turtle1/cmd_vel主题上创建 ROSTwist消息的发布者 -
设置一个周期性回调函数的定时器,间隔为 0.1 秒
-
将
self.vel_msg初始化为用于速度命令的Twist对象
-
-
MQTT 客户端配置:
-
创建一个新的 MQTT 客户端
-
设置连接和消息回调(
on_connect()和on_message()方法) -
配置客户端以 MQTT 的用户名和密码
-
建立与
driver.cloudmqtt.com上端口18756的 MQTT 服务器连接(在on_connect()方法中验证端口号用于处理 MQTT 客户端连接。在成功连接(由rc为0表示)后,它打印一条确认消息并将客户端订阅到JoystickPosition主题,使客户端能够接收相关消息。如果连接失败,它将显示一个包含特定rc代码的错误消息,以帮助诊断问题。该方法参数遵循 Paho MQTT 库的约定:def on_connect(self, client, userdata, flags, rc): if rc == 0: print("Connected successfully to MQTT Broker") client.subscribe("JoystickPosition") else: print(f"Failed to connect with error code {rc}.") -
on_message()方法用于处理传入的 MQTT 消息。在接收到消息后,它将消息有效载荷从字节解码为字符串,然后使用update_values()方法更新 MQTTmessage对象的新值:def on_message(self, client, userdata, msg): self.mqtt_message.update_values(msg.payload.decode()) -
timer_callback()方法是RobotController类中的最后一个方法。它根据两个按钮的状态调整机器人的移动。如果按下button1,则将机器人设置为逆时针画圆。按下button2则相反,使机器人沿顺时针方向移动。如果两个按钮都没有按下,则根据摇杆的Y和X位置分别设置机器人的线性和角速度。设置速度后,更新的vel_msg()函数被发布以控制机器人的移动:def timer_callback(self): if self.mqtt_message.button1: self.vel_msg.linear.x = 1.0 self.vel_msg.angular.z = 1.0 elif self.mqtt_message.button2: self.vel_msg.linear.x = -1.0 self.vel_msg.angular.z = -1.0 else: self.vel_msg.linear.x = float(self.mqtt_message.y) self.vel_msg.angular.z = float(self.mqtt_message.x) self.publisher.publish(self.vel_msg) -
main()函数在任意类外部执行,作为运行集成 MQTT 的 ROS 2 节点的入口点。它首先初始化 ROS 客户端库,然后创建MQTTMessage和RobotController类的实例,并将 MQTT 消息对象传递给后者。应用程序进入 ROS 事件循环以处理回调,包括 MQTT 消息,确保机器人对摇杆命令做出响应。退出时,通过销毁 ROS 节点和终止 ROS 客户端库来关闭:def main(args=None): rclpy.init(args=args) mqtt_message = MQTTMessage() robot_controller = RobotController(mqtt_message) rclpy.spin(robot_controller) robot_controller.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() -
代码完成后,我们使用相同的名称
robot_control.py保存文件。我们的下一步是更新package.xml文件,以包含我们的代码的 Python 库依赖项(有关 ROS 包的更多信息,请参阅第十一章)。为此,我们打开一个 Ubuntu 终端并导航到包含package.xml文件的文件夹:package.xml in a text editor, we execute the following command:):
<exec_depend>rclpy</exec_depend> <exec_depend>paho-mqtt</exec_depend> -
我们保存我们的更改并关闭编辑器。
-
-
通过更新robot_control.py和package.xml,我们现在可以编译我们的代码并运行我们的新节点。
使用我们的物联网摇杆控制 ROS TurtleSim 机器人
在我们可以运行我们的新节点之前,我们必须编译它。正如我们在第十一章中所做的那样,我们使用colcon ROS 工具编译我们的代码。为了编译和执行我们的新代码,我们执行以下操作:
-
在 Ubuntu 中,我们打开一个新的终端并源码我们的 ROS 2 环境:
source /opt/ros/humble/setup.bash -
然后,我们导航到工作区的根目录:
cd ~/ch12_ws -
要编译我们的代码,我们执行以下命令:
colcon build -
完成后,终端将显示一条确认成功构建的消息。
-
代码编译完成后,现在是时候源码我们的新 ROS 环境了。我们使用以下命令执行此操作:
source ~/ch12_ws/install/setup.bash -
这与我们的 ROS 环境源码方式类似。要运行我们的节点,我们执行以下命令:
robot_control, from the package we created, mqtt_robot. We should observe a message indicating that we have successfully connected to the MQTT broker:

图 12.8 – 运行 robot_control 节点
-
在另一个 Ubuntu 终端中,我们使用以下命令启动 TurtleSim 机器人的实例:
source /opt/ros/humble/setup.bash code.py program from Thonny. -
我们应该观察到我们可以使用我们的物联网摇杆导航 TurtleSim 机器人。按下并保持主街机式按钮将使机器人逆时针画圆,而点击并保持摇杆控制下应该使机器人顺时针画圆:

图 12.9 – 按压物联网摇杆上的街机风格按钮后,再按摇杆控制按钮的结果
通过这种方式,我们成功管理了使用我们的物联网摇杆对虚拟机器人的远程控制,展示了系统的全球覆盖范围。这个应用展示了物联网和机器人集成的大量潜力。
在最后一步,我们将物联网摇杆的组件封装在一个定制设计的外壳中。这通过使设备更容易管理和操作来提高可用性。此外,定制外壳为电子设备提供保护,并使我们的应用具有专业的外观。
构建物联网摇杆外壳
如前所述,我们的定制外壳使我们的物联网摇杆看起来更专业,并给我们提供了一个可以握在手中的东西。我们使用 3D 打印部件和一些常用组件来组装定制外壳。
我们案例的 3D 打印部件的.stl文件可以在本章 GitHub 仓库的Build Files部分找到。我们可以在以下图中看到组成外壳的部件:

图 12.10 – 组成物联网摇杆外壳的部件
组成物联网摇杆外壳的部件,如图 12.10所示,如下:
-
A: 后盖板。从
.stl文件中 3D 打印而成。 -
B: 前壳。从
.stl文件中 3D 打印而成(图中的部件已被上色)。 -
C: 4 个 M2.5 10 毫米支撑件。
-
D: 8 毫米 LED 支架。
-
E: PS2 摇杆模块的摇杆。从
.stl文件中 3D 打印而成。 -
F: PS2 摇杆模块的钻孔引导。从
.stl文件中 3D 打印而成。我们使用钻孔引导,因为不同版本的 PS2 摇杆模块的安装孔位置存在差异。我们示例中使用的版本在y轴上安装孔间距为 20.5 毫米,在x轴上为 26 毫米。 -
G: 1 个 M2 8 毫米螺丝(未显示)。
-
H: 4 个 M2 5 毫米螺丝(未显示)。
-
I: 8 个 M2.5 5 毫米螺栓(未显示)。
-
J: 4 个 M3 10 毫米螺栓(未显示)。
构建物联网摇杆外壳时,我们首先将摇杆(图 12.10中的E)固定到 PS2 摇杆上(如图 12.11所示)。我们用我们自己的摇杆替换 PS2 摇杆附带的摇杆,以便有更大的范围。我们可以在以下图中看到物联网摇杆的构建过程:

图 12.11 – 为 PS2 摇杆准备我们的定制外壳
为了准备我们的 PS2 摇杆外壳,我们执行以下操作:
-
将 M2 8 毫米螺丝固定到摇杆上,使其部分穿过摇杆(图 12.11中的步骤 1)。
-
通过手动拧紧螺丝将摇杆固定到 PS2 摇杆的茎部(图 12.11中的步骤 2)。
-
使用四个 M2.5 5 毫米螺栓将四个 M2.5 10 毫米支撑件固定到 PS2 摇杆上(图 12**.11* 中的 步骤 3)。
-
将钻孔引导器对准前壳的摇杆孔,使其与图 12.11 中 步骤 4 所示的方向一致。PS2 摇杆将被安装,使得引脚向右延伸。
-
使用适当尺寸的钻头,在前壳上钻四个孔(图 12.11 中的 步骤 5)。
在摇杆安装并钻孔后,现在是时候构建外壳了。为此,我们遵循以下图中的步骤:

图 12.12 – 构建 IoT 摇杆外壳
以图 12**.12* 为参考,我们按照以下步骤构建物联网摇杆外壳:
-
我们首先使用四个 M2 5 毫米螺丝将 Raspberry Pi Pico WH 固定在背板上,将 USB 端口向下定位以便于访问。这种设置确保 Pico 的 复位 按钮可以通过背板上的指定孔访问。选择 Pico WH 可以方便地与测试电路中的 GPIO 扩展器集成。虽然 Raspberry Pi Pico W 也可以使用,但需要焊接来安装,这使得 Pico WH 对于这个应用来说更加方便(图 12**.12* 中的 步骤 1)。
-
然后,我们使用四个 M2.5 5 毫米螺栓将 PS2 摇杆固定在前壳上。我们必须确保安装 P2 摇杆,使得引脚指向外壳的右侧(图 12**.12* 中的 步骤 2)。
-
使用 LED 座,我们将带有电阻的 LED 连接到前壳。我们使用适当的孔将街机风格的按钮固定在前壳上(图 12**.12* 中的 步骤 3)。
-
我们使用四个 M3 10 毫米螺栓将背板固定在前壳上(图 12**.12* 中的 步骤 4)。
-
为了给物联网摇杆供电,我们将一根微型 USB 线缆连接到 Raspberry Pi Pico WH 的 USB 端口。
现在,我们可以将我们的物联网摇杆插入并使用它来控制 TurtleSim 机器人。
摘要
在本章中,我们组装了我们的物联网摇杆,并使用它来控制 TurtleSim 虚拟机器人。我们从组件接线开始,然后继续编写代码,通过 MQTT 消息传输摇杆动作。
我们将组件封装在一个定制的 3D 打印外壳中,增强了摇杆的可用性和耐用性。通过这个应用,我们展示了物联网设备与机器人系统的无缝集成。
在下一章中,我们将把我们的虚拟 TurtleSim 机器人转换为现实生活中的物理机器人,并使用我们新的物联网摇杆来控制它。
第十三章:介绍用于安全的先进机器人眼睛(A.R.E.S.)
在本章中,我们将把我们的 TurtleSim 虚拟机器人转换为现实生活中的机器人,我们将称之为 A.R.E.S.(即 Advanced Robotic Eyes for Security)。A.R.E.S.将具有视频流,我们可以通过 VLC 媒体播放器在我们的本地网络中查看。我们将使用我们在第十二章中创建的物联网摇杆来控制 A.R.E.S.。
我们将使用树莓派作为大脑或感官输入,以及树莓派 Pico 来控制电机、LED 和蜂鸣器来构建 A.R.E.S.。我们将使用标准电机和我们的树莓派 Pico H 上的机器人板来控制电机。我们将使用位于本章 GitHub 仓库“构建文件”目录中的.stl文件来 3D 打印框架。
本章我们将涵盖以下主题:
-
探索我们的 A.R.E.S.应用程序
-
构建 A.R.E.S.
-
软件设置和配置
-
使用 ROS 编程 A.R.E.S.
让我们开始吧!
技术要求
为了全面学习本章内容,你需要以下条件:
-
Python 编程的中级知识
-
Linux 命令行的基本知识
-
用于 MQTT 服务器实例的 CloudAMQP 账户
-
来自第十二章的物联网摇杆
-
3D 打印机或 3D 打印服务的访问权限
-
自定义情况的构建文件可以在我们的 GitHub 仓库中找到
请参阅“构建 A.R.E.S.”部分以了解所需的硬件组件。
本章的代码可以在以下链接找到:
探索我们的 A.R.E.S.应用程序
A.R.E.S.机器人展示了各种物联网组件的集成。它通过我们在第十二章中创建的物联网摇杆进行操作,并通过 MQTT 与树莓派通信命令。我们的设计将包含树莓派 3B+和树莓派 Pico H。在下面的图中,我们可以看到 A.R.E.S.机器人的轮廓,包括从物联网摇杆的连接:

图 13.1 – A.R.E.S.机器人应用程序
作为大脑的树莓派 3B+使用UART(通用异步收发传输器)通信来中继命令到树莓派 Pico H,后者反过来控制汽车的运动、LED 和蜂鸣器,对输入做出动态响应。配备有 VL53L0X 传感器,A.R.E.S.可以测量距离,从而避免障碍物。此外,安装在 A.R.E.S.上的 M5Stack 摄像头可以实时传输视频流,可以通过 VLC 媒体播放器在任意计算机上查看,使用实时流协议(RTSP)。
使用树莓派 3B+为 A.R.E.S.编程
对于 A.R.E.S.,我们选择 Raspberry Pi 3B+而不是 4 或 5 等较新型号,因为它具有更高的能效和成本效益。它能够使用标准手机电池组运行,非常适合我们的需求,而其较低的价格和作为当前型号的可用性确保了经济和实用性的双重好处。
在启动 A.R.E.S.机器人项目时,我们首先组装 3D 打印的框架并安装必要的组件。A.R.E.S.设计得非常紧凑,使其成为教育用途的理想机器人平台。一旦框架完成,我们将继续进行软件配置,在我们的 Raspberry Pi 3B+上配置操作系统,并编程 Raspberry Pi Pico H。让我们从构建框架开始。
构建 A.R.E.S.
A.R.E.S.由 3D 打印部件和常见组件组成,如直流电机、LED、Raspberry Pi 3B+、Raspberry Pi Pico H、ToF(飞行时间),传感器、Wi-Fi 相机、电池组和各种螺栓和螺丝。
我们将开始构建 A.R.E.S.,首先识别 3D 打印的部件。
识别 3D 打印框架部件
我们可以在本章 GitHub 仓库的“构建文件”目录下找到这些部件的.stl文件。在以下图中,我们可以看到打印出的部件:

图 13.2 – A.R.E.S. 3D 打印部件
由 3D 打印部件组成的 A.R.E.S.框架部件如下:
-
A: 底座
-
B: 外壳
-
C: 面板
-
D: 电池组支架
-
E: 电机支架
-
F: 测试台(可选的用于测试目的的底座)
在识别了 3D 打印框架部件后,让我们看看用于构建 A.R.E.S.的组件。
识别用于创建 A.R.E.S.的组件。
我们使用的构建 A.R.E.S.的组件是标准电子组件,可以从亚马逊或 AliExpress 等在线供应商那里轻松购买。以下图展示了我们使用的组件:

图 13.3 – 组成 A.R.E.S.机器人的部件
构建 A.R.E.S.所需的组件如下:
-
A: 2 x LED 和 220 欧姆电阻
-
B: 2 x 5 毫米(8 毫米宽)LED 支架
-
C: 2 x TT 直流机器人电机
-
D: M5Stack 计时器相机 X 及其支架(未展示)
-
E: Adafruit VL53L0X ToF 传感器
-
F: 2 x TT 机器人汽车轮子
-
G: SFM-27 蜂鸣器
-
H: Raspberry Pi 3B+
-
I: 4 节 AA 电池的电池组(含电池)
-
J: 手机 USB 电池组
-
K: 带有 Kitronik Simply Robotics 电机驱动板的 Raspberry Pi Pico H
-
L: 短微 USB 到 USB 线
-
M: Grove 连接器到公跳线连接器,用于将相机连接到 Raspberry Pi 3B+的 GPIO 端口
-
N: 4 x 2 毫米厚,18 毫米直径的磁铁,带有双面粘合垫
-
O: 轮子(32 毫米宽度)
-
未显示:18 个 M3 10 毫米螺栓,4 个 M3 20 毫米螺栓,8 个 M3 螺母,6 个 M2.5 10 毫米螺栓,2 个 M4 10 毫米螺栓,4 个 M2.5 40 毫米支撑柱,3 个 M3 20 毫米支撑柱,跳线,带有连接器和电线的压接套件(可选但推荐),热胶枪,烙铁
将组件安装到位后,让我们开始构建我们的 A.R.E.S.机器人。
构建 A.R.E.S.
使用我们的 3D 打印框架部件和电子组件,现在是时候构建 A.R.E.S.了。为了构建 A.R.E.S.,我们使用以下图作为指南:

图 13.4 – 构建 A.R.E.S.机器人
步骤如下(编号步骤也对应图中的编号组件):
-
使用双面胶带(通常与产品一起包装),我们将两个磁铁(N来自图 13**.3)固定在外壳上(B来自图 13**.2)。
-
使用相反极性的磁铁(N来自图 13**.3),我们将磁铁(在固定前务必测试)固定到底座上(A来自图 13**.2)。
-
使用两个 M4 10 毫米螺栓,我们将 SFM-27 蜂鸣器(G来自图 13**.3)固定到底座上(A来自图 13**.2)。螺栓应插入蜂鸣器底座;然而,可能需要 M4 螺母。在此步骤中,我们还使用两个 M3 螺栓将万向轮(O来自图 13**.3)固定到底座上(A来自图 13**.2)。
-
我们将 20 厘米长的电线焊接在每个 TT 电机的端子上(C来自图 13**.3)。
-
使用电机支架(E来自图 13**.2),我们将 TT 电机(C来自图 13**.3)和 TT 机器人汽车轮子(F来自图 13**.3)固定在底座上(A来自图 13**.2)。
-
使用两个 M3 10 毫米螺栓,我们将 M5Stack Timer Camera X(D来自图 13**.3)附带的相机支架固定在面板上(C来自图 13**.2)。
-
使用 LED 支架(B来自图 13**.3)和带有电阻的 LED(A来自图 13**.3),我们将 LED 穿过面板上的适当孔(C来自图 13**.2)。我们使用热胶枪的胶水将 VL53L0X ToF 传感器(E来自图 13**.3)固定在面板上(C来自图 13**.2)。我们还可以将 LED 固定在原位,以防止它们移动。
-
需要访问 Raspberry Pi Pico H 上的 GP 引脚,但它们位于电机板的DIP(即双列直插式封装)插座内,因此无法访问。为了克服这个问题,我们需要在电机板的底部焊接引线脚,使我们能够将 SFM-27 蜂鸣器(G来自图 13**.3)和带有电阻的 LED(A来自图 13**.3)连接到 Raspberry Pi Pico H。
-
我们使用 10 毫米 M2.5 和 10 毫米 M3 螺栓,将四个 M2.5 40 毫米支撑件固定在底座的前面(图 13**.2中的A)和三个 M3 20 毫米支撑件固定在底座的后面(图 13**.2中的A)。我们可以使用电机板(图 13**.3中的K)将 Raspberry Pi 3B+(图 13**.3中的H)和 Pico H 固定在支撑件上;然而,这将是临时的,因为这些组件在我们接线 A.R.E.S.时会移动。我们还可以临时放置电池组提升器(图 13**.2中的D)。我们使用提升器来覆盖电线,提供一个平坦的表面放置电池组(图 13**.3中的I)。
在组装好框架并放置好组件后,现在是时候将我们的组件连接到 Raspberry Pi 和 Pico H 上了。
接线 A.R.E.S.
接线 A.R.E.S.需要连接到 Raspberry Pi 3B+和 Kitronik 电机板。使用图 13**.1作为参考,我们可以看到我们将 VL53L0X ToF 传感器和 M5Stack Timer Camera X 连接到 Raspberry Pi 3B+,将 TT DC 机器人电机、带电阻的 LED 和蜂鸣器通过电机板连接到 Raspberry Pi Pico H。我们还通过 UART 通信将 Raspberry Pi 3B+连接到 Raspberry Pi Pico H。
我们从机器人电机开始接线。在下面的图中,我们可以看到电机板的高清图,其中连接电池组和电机的端子被突出显示:

图 13.5 – Raspberry Pi Pico H 的 Kitronik 电机板
要接线我们的电机,我们执行以下操作:
-
将右侧电机,如图 13**.4的第 9 步所示,连接到电机板上的Motor0。在这个阶段,电线的极性不是关键,因为我们可以在必要时纠正它们的方向。
-
将左侧电机的电线连接到电机板上的Motor1。
-
将 AA 电池组的电线连接到电机板上的电池端子,注意极性。
在连接好机器人电机和电池线后,现在是时候接线其余的组件了。我们将使用标准的女性跳线来制作连接。虽然不是必需的,但使用压接套件制作自己的跳线可以使接线设置整洁有序。我们使用图 13**.6中的接线图作为参考:

图 13.6 – A.R.E.S.的组件接线图
要接线其余的组件,我们执行以下操作:
-
使用 Grove 连接器连接到女性跳线连接器(图 13**.3中的M),我们将 M5Stack Timer Camera X 连接到 Raspberry Pi 3B+,将 Raspberry Pi 的 5V 连接到摄像机的 V 连接器,将 Raspberry Pi 的 GND 连接到摄像机的 G。
-
我们将 ToF 传感器的 VIN 连接到 Raspberry Pi 的 3.3V。
-
我们将 ToF 传感器上的 SDA 连接到树莓派上的 SDA (GPIO 2)。
-
我们将 ToF 传感器上的 SCL 连接到树莓派上的 SCL (GPIO 3)。
-
我们将 ToF 传感器上的 GND 连接到树莓派的 GND。
-
我们将树莓派上的 TX (GPIO 14) 连接到树莓派 Pico H 的 RX (GP5) 或电机板上的 7 号引脚。
-
我们将树莓派上的 RX (GPIO 15) 连接到树莓派 Pico H 的 TX (GP4) 或电机板上的 6 号引脚。
-
我们将树莓派上的 GND 连接到树莓派 Pico H 的 GND 或电机板上的 GND (0V) 引脚。
-
我们将 SFM-27 蜂鸣器的正极线连接到树莓派 Pico H 的 GP0 或电机板上的 1 号引脚。
-
我们将 SFM-27 蜂鸣器的负极线连接到电机板上的 GND (0V) 引脚。
-
我们将 LED 的正极通过电阻连接到 GP1 和 GP2 或电机板上的 2 号和 4 号引脚。
-
我们将 LED 的负极通过电阻连接到电机板上的 GND (0V) 引脚。
在我们进行连接时,可能需要移动树莓派和电机板。此外,建议我们最初不要将面板(图 13**.2 中的 C)连接到底座(图 13**.2 中的 A),因为我们需要访问树莓派 3B+ 上的 microSD 端口。
在布线就绪后,让我们设置 A.R.E.S. 的软件。我们首先将 Ubuntu 安装到我们的树莓派 3B+ 上。
软件设置和配置
为了设置 A.R.E.S. 的软件架构,我们将从这个章节的 GitHub 仓库中运行一个脚本。脚本首先确保以 root 权限运行,更新和升级系统,并安装必要的工具和接口,例如 I2C(代表 Inter-Integrated Circuit)和 UART。然后继续安装 Adafruit Blinka 以支持 CircuitPython 库,设置 ROS Humble Hawksbill 用于机器人编程,并安装 Colcon 构建系统以进行软件编译。
脚本还负责通过 rosdep 进行依赖管理,并将 ROS 2 环境设置添加到 bashrc 文件中以便于访问。到过程结束时,我们的树莓派 3B+ 已完全配置为 A.R.E.S.。
在运行脚本之前,我们将使用 Raspberry Pi Imager 将 Ubuntu 烧录到 microSD 卡上,并将卡安装到我们的树莓派 3B+ 上。由于 A.R.E.S. 机器人的 microSD 卡插槽位于前面,因此面板将覆盖它。因此,在安装 Ubuntu 时,我们将保持面板与底座断开连接,如图所示:

图 13.7 – A.R.E.S. 的侧面视图,面板已断开以允许访问 microSD 卡
我们将从我们选择的计算机上运行 Raspberry Pi Imager。对于本章的示例,我们将将其安装在 Windows 计算机上。
在我们的树莓派 3B+ 上安装 Ubuntu
Raspberry Pi Imager 是一个多功能的工具,旨在简化在 Raspberry Pi 设备上安装操作系统的过程。由 Raspberry Pi 基金会开发,这个实用程序允许我们将各种操作系统闪存到 SD 卡上,然后可以在 Raspberry Pi 上启动和运行。
虽然 Raspberry Pi Imager 主要支持安装 Raspberry Pi OS(以前称为 Raspbian),但其功能扩展到一系列其他操作系统。这使我们能够尝试不同的环境或需要由替代操作系统更好地支持的功能。
要使用 Raspberry Pi Imager,我们只需在我们的计算机上下载并安装应用程序,从其广泛的列表中选择所需的操作系统,然后选择安装的目标 SD 卡。Raspberry Pi Imager 可以安装在包括 Windows、macOS 和 Linux 在内的各种操作系统上。例如,在本章中,我们将将其安装到 Windows 机器上。我们将烧录 Ubuntu 22.04 的命令行版本,以对应 Humble Hawksbill 版本的 ROS。
要使用 Raspberry Pi Imager 将 Ubuntu 安装到我们的 A.R.E.S.机器人上的 Raspberry Pi 3B+,我们导航到 URL 并下载我们正在使用的操作系统的 imager([www.raspberrypi.com/software/](https://www.raspberrypi.com/software/)),然后按照以下步骤安装工具:
-
我们将我们的 microSD 卡插入到计算机的端口上。
-
安装完成后,我们打开工具,选择Raspberry Pi 3作为Raspberry Pi 设备,选择UBUNTU SERVER 22.04.4 LTS (64-BIT)作为操作系统,以及我们插入的 microSD 卡作为存储选项:

图 13.8 – 设置 Raspberry Pi Imager
要继续,我们点击下一步按钮。这将带我们到使用操作系统定制?对话框:

图 13.9 – 图像定制对话框
- 由于我们想设置计算机名和网络名,我们点击编辑设置按钮,并得到以下屏幕:

图 13.10 – 操作系统定制屏幕
我们将主机名和用户名都设置为ares。我们为用户名提供了一个密码,并输入我们的 SSID(局域网网络)和 SSID 密码。
- 要通过 SSH 启用远程访问,我们点击顶部的服务选项卡并选择启用 SSH:

图 13.11 – 启用 SSH
-
要保存我们的设置,我们点击保存按钮。
-
要应用设置,我们点击是按钮。
我们将随后看到一个警告:

图 13.12 – 警告信息
- 我们点击是,因为我们想擦除 microSD 卡上的任何数据,并用 Ubuntu 操作系统替换它。
Raspberry Pi Imager 随后将安装 Ubuntu 22.04 操作系统到我们的 microSD 卡上,我们将将其安装到 A.R.E.S.上的 Raspberry Pi 3B+。我们不需要设置 Wi-Fi 网络或启用 SSH。
安装了 Ubuntu 后,现在是时候安装 ROS 以及我们为 A.R.E.S.需要的 Python 库了。我们将使用存储在我们 GitHub 仓库中的专用脚本来自动化这个过程。
运行安装脚本
在前面的章节中,我们手动安装了开发库,这是一个详尽但耗时的工作过程。现在,利用我们对 Python 库的熟悉,我们将通过 GitHub 仓库中的脚本简化 A.R.E.S.与 ROS 以及必要库的设置。
在 Ubuntu 上以 root 权限执行,此脚本自动化了包括 ROS 在内的安装过程。尽管直接安装到操作系统与最佳实践相悖,但它简化了过程。对于未来的项目,建议读者探索使用 Docker 等工具进行容器化。
要运行 A.R.E.S.安装脚本,我们执行以下操作:
-
以图 13.7为参考,我们确保我们能够访问 Raspberry Pi 3B+上的端口。
-
我们将显示器、键盘和鼠标连接到 Raspberry Pi,并插入新镜像的 microSD 卡。
由于 Ubuntu 的服务器版本是基于命令行的,因此当我们启动 Raspberry Pi 时,我们将不会看到一个 GUI。我们使用在镜像过程中设置的凭据登录。登录后,我们应该在
home目录中。我们可以使用pwd命令来验证这一点:

图 13.13 – 验证当前目录
-
设置脚本位于本书的 GitHub 仓库中。要下载脚本以及我们用于 A.R.E.S.机器人的 Python 代码,我们使用以下命令将仓库克隆到我们的 Raspberry Pi 上:
code. We may verify the creation of the code directory by running the ls command:

图 13.14 – 克隆仓库
脚本位于code目录的子目录中。我们使用以下命令将其复制到当前目录(.):
cp code/Chapter13/code/setup-ares.sh .
- 我们使用
ls命令验证脚本是否成功复制:

图 13.15 – 验证设置脚本成功复制
-
我们使用以下命令以管理员权限执行脚本:
sudo bash setup-ares.sh以管理员权限执行脚本确保它具有执行系统级更改和安装的必要权限,而不会遇到访问限制。我们的脚本最初会更新我们的系统,然后安装 ROS 和必要的 Python 库。这可能需要几分钟才能完成。完成后,我们应该会看到我们机器的 IP 地址,这样我们就可以通过 SSH 远程登录。当 A.R.E.S.远程运行时,这将很有必要:

图 13.16 – 运行设置脚本的结果
- 在完成我们的设置脚本后,我们现在可以将机器人面部固定到 A.R.E.S. 的底板上:

图 13.17 – A.R.E.S. 的正面视图
在构建 A.R.E.S. 和在 Raspberry Pi 3B+ 上安装操作系统后,现在是时候在我们的 Raspberry Pi Pico H 上安装代码了。参考 图 13.1,我们可以看到 A.R.E.S. 使用 Pico H 来控制电机、LED 和蜂鸣器。
我们将首先编写代码来控制 LED 和蜂鸣器。
为 Pico H 创建警报代码
要编程我们的 Pico H,我们需要将一个微型 USB 线连接到 Pico H 的 USB 端口。尽管我们打算错开 Pico H 和 Raspberry Pi 3B+ 的高度,但我们可能需要暂时从支架上卸下 Raspberry Pi 3B+,以便将微型 USB 线连接到 Pico H。
一旦连接了微型 USB 线,我们就可以将 Pico H 连接到我们选择的电脑上并运行 Thonny。我们将在 Pico H 上的一个名为 device_alarm.py 的文件中创建一个名为 Alarm 的类,以封装警报功能。为了简单起见,我们将 LED 的闪烁与蜂鸣器的激活结合起来。
要做到这一点,我们执行以下操作:
- 参考第十二章的 设置我们的 Raspberry Pi Pico WH 部分 (章节 12],尽管我们选择了 Raspberry Pi • Pico / Pico H 作为 CircuitPython 变体选项,但我们还是在我们的 Raspberry Pi Pico 上安装了 CircuitPython:

图 13.18 – 在我们的 Raspberry Pi Pico H 上安装 CircuitPython
-
然后,我们从屏幕的右下角选择 Pico H,激活其上的 CircuitPython 环境。
-
在一个新的编辑器中,我们以导入开始我们的代码:
import time import board import pwmio import digitalio在我们的代码中,我们有以下内容:
-
import time:提供时间相关函数,使任务如引入程序执行延迟成为可能,这对于控制操作流程和时序非常有用。 -
import board:访问特定于板的引脚和硬件接口,这对于与 Raspberry Pi Pico W 上的 GPIO 引脚进行接口至关重要。 -
import pwmio:我们使用这个库通过操作import digitalio:管理数字输入和输出,例如读取按钮的状态或控制 LED,这对于数字信号交互至关重要。
-
-
然后,我们定义一个
Alarm类并创建一个初始化方法:class Alarm: def __init__(self, buzzer_pin=board.GP1, led_pin1=board.GP0, led_pin2=board.GP2, frequency=4000): self.buzzer = pwmio.PWMOut(buzzer_pin, frequency=frequency, duty_cycle=0) self.led1 = digitalio.DigitalInOut(led_pin1) self.led1.direction = digitalio.Direction.OUTPUT self.led2 = digitalio.DigitalInOut(led_pin2) self.led2.direction = digitalio.Direction.OUTPUT在我们的代码中,以下操作发生:
-
我们定义一个名为
Alarm的类。__init__()方法接受可选参数,包括蜂鸣器引脚、两个 LED 引脚和蜂鸣器频率,并具有默认值。 -
然后,我们将指定引脚上的蜂鸣器初始化为 PWM 输出,给定频率,占空比为
0(关闭状态)。 -
我们的代码在指定的引脚上设置了两个 LED 作为数字输出,准备打开或关闭。
-
-
我们的这个类只包含一个方法,
activate_alarm():def activate_alarm(self, num_of_times=5): blink_rate = 0.5 for _ in range(num_of_times): self.buzzer.duty_cycle = 32768 self.led1.value = True self.led2.value = True time.sleep(blink_rate) self.buzzer.duty_cycle = 0 self.led1.value = False self.led2.value = False time.sleep(blink_rate)在我们的代码中,我们执行以下操作:
-
我们在
Alarm类中定义了一个activate_alarm()方法,用于指定次数(默认为5)激活警报。 -
在方法内部,我们将
blink_rate变量设置为0.5秒,然后循环指定次数,根据blink_rate变量切换蜂鸣器和 LED 灯的开关。
-
-
为了测试我们的代码和接线,我们使用以下代码:
alarm = Alarm(buzzer_pin=board.GP1, led_pin1=board.GP0, led_pin2=board.GP2) alarm.activate_alarm(10) -
要保存文件,我们点击
device_alarm.py到我们的 Raspberry Pi Pico H。 -
要运行我们的代码,我们点击绿色的运行按钮,在键盘上按F5,或者点击顶部的运行菜单选项,然后点击运行****当前脚本。
-
我们应该观察到蜂鸣器和 LED 灯闪烁 10 次。
提示
为了防止在我们的应用程序中执行测试代码,我们要么删除该代码段,要么将其注释掉。
在编写并测试了警报代码后,现在是时候测试电机了。
测试和控制电机
为了封装电机控制功能,我们在一个名为wheel.py的文件中创建了一个名为Wheel的类。
要做到这一点,我们执行以下操作:
- 我们的代码需要
PicoRobotics.py库,该库可能位于本章 GitHub 仓库的code|PicoH下。要使用 Thonny 将库下载到我们的 Pico H,我们首先在电脑上找到lib目录。然后我们右键点击lib目录并选择上传****到/:

图 13.19 – 将 lib 目录从我们的电脑上传到我们的 Pico H
-
我们在 Thonny 中打开一个新的编辑器,并通过将
KitronikPicoRobotics和time库导入到我们的程序中来开始编码:from PicoRobotics import KitronikPicoRobotics import time -
这些库将允许我们与 Pico 机器人板进行接口。然后我们定义我们的类和方法:
class Wheel: def __init__(self, speed): self.motor_board = KitronikPicoRobotics() self.speed = speed def forward(self): self.motor_board.motorOn(1, "f", self.speed) self.motor_board.motorOn(2, "f", self.speed) def reverse(self): self.motor_board.motorOn(1, "r", self.speed) self.motor_board.motorOn(2, "r", self.speed) def turn_right(self): self.motor_board.motorOn(1, "r", self.speed) self.motor_board.motorOn(2, "f", self.speed) def turn_left(self): self.motor_board.motorOn(1, "f", self.speed) self.motor_board.motorOn(2, "r", self.speed) def stop(self): self.motor_board.motorOff(1) self.motor_board.motorOff(2)在我们的代码中,以下情况发生:
-
我们定义了一个
Wheel类。 -
__init__()构造函数初始化类,设置实例变量,包括封装 Pico 机器人电机板功能的motor_board,以及用于控制电机速度的speed参数。 -
我们实现了一个
forward()方法,以指定速度使两个轮子向前移动。 -
我们实现了一个
reverse()方法,以指定速度使两个轮子反向移动。 -
然后,我们实现了一个
turn_right()方法,通过以指定速度正向运行左轮和反向运行右轮来使机器人向右旋转。 -
我们实现了一个
turn_left()方法,通过以指定速度反向运行右轮和正向运行左轮来使机器人向左旋转。 -
我们随后实现了一个
stop()方法来停止两个电机,使机器人的移动停止。
-
-
为了测试我们的代码和接线,我们使用以下代码:
#Test code wheel = Wheel() wheel.forward() time.sleep(1) wheel.reverse() time.sleep(1) wheel.turn_right() time.sleep(1) wheel.turn_left() time.sleep(1) wheel.stop() -
在运行代码之前,我们必须确保电机板上的电源开关已打开,并且 AA 电池组已连接:

图 13.20 – 电机板和 AA 电池组的特写
-
为了测试目的,我们将 A.R.E.S.放置在测试台上,使其车轮离地(图 13**.17)。
-
要保存文件,我们点击
wheel.py到我们的 Raspberry Pi Pico H。 -
要运行我们的代码,我们点击绿色的运行按钮,在键盘上按F5,或者在顶部的运行菜单选项中点击运行 当前脚本。
我们应该在 A.R.E.S.上观察车轮完成一系列动作,包括向前移动、向后移动、向右移动和向左移动,然后停止。
提示
为了避免我们的测试代码在测试之外运行,我们将其注释或删除,并将wheel.py保存到我们的 Pico H 上。
在这个阶段,我们通过调整电机板上的电机接线来确保车轮按预期方向移动,这可能涉及重新排列电机线端子的电线。
在正确配置和测试了连接到 Pico H 的 LED、蜂鸣器和电机后,我们现在将进行 Raspberry Pi Pico H 和板上 A.R.E.S.的 Raspberry Pi 3B+之间的通信测试。
测试 Pi 和 Pico 之间的通信
在图 13**.1中,我们观察到 Raspberry Pi 3B+和 A.R.E.S.上的 Raspberry Pi Pico H 之间的通信是通过 UART 完成的。具体来说,消息是从 Raspberry Pi 3B+发送到 Pico H 的,以控制连接到 Pico H 的 LED、蜂鸣器和电机。我们在构建 A.R.E.S.时通过各自的 GPIO 端口连接了这两个设备。
在本节中,我们将使用位于本章 GitHub 仓库中的 Python 测试脚本和我们在 Pico H 上创建的新文件来测试通信。我们将从 Pico H 开始。
创建 Pico H 脚本
为了在 Pico H 上创建将等待 Raspberry Pi 3B+命令的代码,我们执行以下操作:
-
我们在 Thonny 中打开一个新的编辑器,并开始通过导入我们程序所需的库来编写代码:
import board import busio import time from wheel import Wheel from device_alarm import Alarm在我们的代码中,我们执行以下操作:
-
我们首先导入
board模块以访问物理引脚定义。 -
然后,我们导入用于总线通信(UART)功能的
busio模块。 -
我们使用
time模块来执行延迟。 -
我们从我们的
wheel模块中导入Wheel类。 -
然后,我们从我们创建的
device_alarm模块中导入Alarm类。
-
-
在我们的导入就绪后,我们设置我们的变量声明:
wheel = Wheel(20) alarm = Alarm() uart = busio.UART(board.GP4, board.GP5, baudrate=115200)在我们的代码中,我们有以下内容:
-
wheel = Wheel(20): 创建一个Wheel类的实例,其速度参数设置为20。 -
alarm = Alarm(): 初始化Alarm类的一个实例。 -
uart = busio.UART(board.GP4, board.GP5, baudrate=115200): 使用 Pico H 上的GP4和GP5引脚建立 UART 通信链路,并将波特率设置为115200。
-
-
然后,我们创建一个函数来清除我们的 UART 缓冲区,通过连续读取直到没有数据剩余(通过移除可能导致错误的旧或不相关的数据来确保准确和最新的数据通信):
def clear_uart_buffer(): while uart.in_waiting > 0: uart.read(uart.in_waiting) -
我们的代码随后在一个连续循环中运行,等待通过 UART 接收消息:
while True: data = uart.read(uart.in_waiting or 32) while '<' in message_buffer and '>' in message_buffer: start_index = message_buffer.find('<') + 1 end_index = message_buffer.find('>', start_index) message = message_buffer[start_index:end_index].strip() message_buffer = message_buffer[end_index+1:] print("Received:", message) if message == 'f': print("Moving forward") wheel.forward() elif message == 'b': print("Moving in reverse") wheel.reverse() elif message == 'l': print("Left turn") wheel.turn_left() elif message == 'r': print("Right turn") wheel.turn_right() elif message == 'a': print("Alarm") wheel.stop() alarm.activate_alarm(2) elif message == 's': print("Stop") wheel.stop()在我们的代码中,以下操作发生:
-
我们从 UART 连接中连续读取最多 32 字节的数据。
-
我们移除包围的尖括号。
-
我们打印出接收到的消息以进行调试。
-
我们使用
if语句根据消息内容执行特定操作:-
如果消息是
'f',机器人会向前移动。 -
如果消息是
'b',机器人会向后移动。 -
如果消息是
'l',机器人会向左转。 -
如果消息是
'r',机器人会向右转。 -
如果消息是
'a',机器人会激活警报并停止移动。 -
如果消息不匹配任何指定的命令,机器人会停止任何移动。
-
-
我们随后清除 UART 缓冲区以移除任何剩余的数据。
-
然后我们引入一个短暂的 0.1 秒延迟,以防止 CPU 过载。
-
-
要保存文件,我们点击
code.py到我们的 Raspberry Pi Pico H。 -
要运行我们的代码,我们点击绿色的 运行 按钮,在键盘上按 F5 或者在顶部点击 运行 菜单选项,然后点击 运行 当前脚本。
执行 code.py 后,我们期望我们的代码在 Shell 中不会产生任何输出,这表明它处于等待来自 Raspberry Pi 的通信的状态。在 Pico H 设置并等待消息后,现在是时候从 Raspberry Pi 执行我们的测试脚本了。
从 Raspberry Pi 运行 UART 测试代码
在本章的 GitHub 仓库中,我们有一个名为 uart-test.py 的文件,我们可以用它来测试我们的 A.R.E.S. 机器人上 Raspberry Pi 和 Pico H 之间的连接。在本节中,我们将使用 PuTTY 从 Windows 计算机登录到我们的 Raspberry Pi 并运行测试,同时保持我们的 Pico 通过 Thonny 连接。
要做到这一点,我们执行以下操作:
- 使用 Windows 上的 PuTTY 等程序,或基于 Linux 的系统上的终端,我们使用运行设置脚本后获得的 IP 地址(作为主机名)登录我们的 Raspberry Pi 3B+:

图 13.21 – 在 Windows 中使用 PuTTY 登录我们的 Raspberry Pi 3B+
-
如果这是第一次通过 PuTTY 登录,我们可能会收到安全警报。我们点击 接受 以继续。
-
要将我们的测试程序复制到当前目录,我们运行以下命令(我们绝对不能忘记点号):
vi with the following command:vi uart-test.py
Running the command will produce the following output:

图 13.22 – 在 vi 编辑器中查看 uart-test.py
-
我们使用以下命令关闭编辑器:
code.py executing, the following command initiates the test:sudo python3 uart-test.py
-
我们应该观察到 LED 和蜂鸣器在两个脉冲中激活,同时 Thonny 的输出确认已收到警报消息:

图 13.23 – 运行 uart-test.py 测试脚本的输出结果
在我们的 A.R.E.S. 机器人上成功测试了 Raspberry Pi 和 Raspberry Pi Pico H 之间的 UART 连接后,现在是时候测试距离传感器了。
测试 ToF 传感器
为了测量 A.R.E.S.前方距离,我们将使用 Adafruit 提供的 VL53L0X ToF 传感器。该传感器能够以高精度测量从 30 毫米到 1.2 米的距离,通过使用微型激光来检测光传播的时间。其窄光束克服了声纳或红外(IR)传感器的局限性,使其非常适合在机器人和交互式项目中执行精密任务。兼容 3-5V 和 I2C 通信,它设计用于与各种微控制器轻松使用。
对于 A.R.E.S.,我们将 VL53L0X 连接到我们的 Raspberry Pi 3B+。我们将在设计中使用它,一旦检测到距离小于 10 厘米的物体,机器人就会停止前进。
要测试传感器,我们运行本章 GitHub 存储库中可用的测试脚本。要运行测试,我们执行以下操作:
-
使用 Windows 上的 PuTTY 程序或基于 Linux 的系统上的终端,我们使用在运行设置脚本后获得的 IP 地址登录到我们的 Raspberry Pi 3B+。
-
要将我们的测试程序复制到当前目录,我们运行以下命令(我们绝对不能忘记点号):
cp code/Chapter13/code/distance-sensor-test.py . -
要运行测试,我们执行以下命令:
python3 distance-sensor-test.py -
通过将手放置在传感器(集成到 A.R.E.S.的口中)的不同距离处,我们应该在终端中观察到传感器输出值的相应变化:

图 13.24 – 测试 VL53L0X ToF 传感器的输出
ToF 传感器与距离传感器是否相同?
ToF 传感器,通过测量光从物体反弹回来的时间,提供精确的距离读数。相比之下,传统的距离传感器,通常使用超声波或 IR 技术,基于声波或光强度来测量距离。ToF 传感器通常在各种范围内提供比这些常见距离传感器更高的精度和可靠性。
在 ToF 传感器运行后,我们准备配置 A.R.E.S.的相机,与 Raspberry Pi 和 Pico H 不同,该相机在 ROS 环境之外流式传输视频,任何网络设备都可以访问。
我们将使用 Arduino IDE 来编程相机。
从 A.R.E.S.流式传输视频。
对于视频流,我们将使用由 ESP32 芯片供电的 M5Stack Timer Camera X,该芯片配备 300 万像素(ov3660)传感器,可拍摄高达 2048x1536 像素的图像。尽管它支持 I2C 进行配置,但我们将通过 Raspberry Pi 3B+的 5V 电源直接为其供电,绕过 I2C 设置。该相机作为我们 A.R.E.S.机器人的“鼻子”。
我们将使用 Arduino IDE 和 M5Stack 提供的程序来设置相机。为此,我们执行以下操作:
-
使用网络浏览器,导航到 Arduino 网站,并从
www.arduino.cc/en/software下载最新的 Arduino IDE。 -
下载完成后,我们安装 Arduino IDE 并打开它。
-
要将我们的 M5Stack Timer Camera X 库和示例代码添加到 Arduino IDE 中,我们选择文件 | 首选项(在 Windows 中)并将以下网址添加到附加板管理器网址框中:
m5stack.oss-cn-shenzhen.aliyuncs.com/resource/arduino/package_m5stack_index.json. -
对话框应如下所示:

图 13.25 – 将 M5Stack 板添加到 Arduino IDE
-
我们点击确定以关闭对话框。
-
使用 USB-C 线缆,我们将 Timer Camera X 连接到运行 Arduino IDE 的电脑。我们可以从 A.R.E.S.的表面移除相机,以便更容易访问 USB-C 端口。
-
要将M5TimerCAM设置为设备,我们点击工具 | 板 | M5Stack | M5TimerCAM:

图 13.26 – 选择 M5TimerCAM
-
接下来,我们需要选择相机连接的端口。为此,我们点击工具 | 端口并选择相机连接的端口(如果只将相机连接到我们的电脑,将只有一个选项)。
-
要访问 M5Stack Timer Camera 示例代码,我们点击工具 | 管理库…并搜索Timer-CAM。
-
我们然后将鼠标悬停在章节标题旁边,直到出现三个点,然后选择示例 | rtsp_stream:

图 13.27 – 选择 rtsp_stream 示例代码
-
这将打开另一个包含示例代码的 Arduino 窗口。
-
我们需要串行监视器来找到视频将广播的地址。要加载串行监视器,我们点击工具 | 串行监视器:

图 13.28 – 查看串行监视器
-
我们将波特率设置为
115200并将 SSID 名称和密码输入到代码中(如图中所示的区域)。 -
要将代码上传到我们的相机,我们点击上传按钮,其外观如下:
. -
编译后,代码将上传到我们的相机。我们可以在串行监视器中查看
rtsp地址:

图 13.29 – 输出到串行监视器
- 我们复制
rtspURL 并将其粘贴到 VLC 媒体播放器中,方法是点击 VLC 媒体播放器中的媒体 | 打开网络流…:

图 13.30 – 使用 VLC 媒体播放器进行视频流
-
要开始流式传输,我们点击播放按钮。
-
我们应该观察到来自相机的视频流:

图 13.31 – 在 VLC 媒体播放器中显示的相机视频流
A.R.E.S. 目前缺少鼻子,但在测试了摄像头后,我们可以断开 USB-C 电缆并将其重新连接到 A.R.E.S.的面部。这完成了 A.R.E.S.组件的测试阶段,为我们开发 ROS 节点并允许通过互联网控制 A.R.E.S.铺平了道路。
使用 ROS 编程 A.R.E.S.
现在,A.R.E.S. 已经组装完成,并且必要的软件和固件已经安装,我们准备使用 ROS 通过互联网进行远程控制。在我们的设置过程中,我们安装了 ROS 以及所有必要的库。除了我们的设置脚本外,我们还从 GitHub 仓库下载了测试脚本,并运行它们以确保一切正常工作。
在我们的 GitHub 仓库中已经存在一个预存在的 ROS 工作空间。要使用此代码创建一个 ROS 节点,只需将预存在的工作空间转移到我们的 home 目录,并执行一个 colcon build 命令。
要做到这一点,我们执行以下操作:
-
使用 Windows 上的 PuTTY 程序或基于 Linux 的系统上的终端,我们使用在运行设置脚本后获得的 IP 地址登录到我们的 Raspberry Pi 3B+。
-
要将我们的 ROS 工作空间复制到当前目录,我们运行以下命令(我们绝对不能忘记这个点):
cp -r code/Chapter13/code/ares_ws . -
然后我们使用以下命令进入我们的工作空间:
cd ares_ws -
我们使用以下命令源 ROS 环境:
nano to view our code, we type the following:在 第十二章 的
robot_control.py文件中,我们将仅探索代码的某些部分。我们从初始化方法中的代码开始,该方法是用来给我们使用串行 0 端口的权限:password = 'sudo-password' command = 'chmod a+rw /dev/serial0' subprocess.run(f'echo {password} | sudo -S { command}', shell=True, check=True)
重要提示
由于安全考虑,强烈建议不要将管理员密码放在文件中。然而,在我们的应用程序在严格控制的开发环境中运行,且访问受到严格限制的情况下,我们绕过了这一指南。我们需要这个密码,以便我们可以更改 serial0 端口的权限。没有它,我们将无法访问,因此无法向 Pico H 发送命令。
在我们的代码中,以下操作会发生:
-
我们将
sudo用户的密码存储在password变量中。我们将更改/dev/serial0权限以供所有用户读写操作的命令存储在command变量中。 -
我们使用
sudo命令执行操作,无需手动输入密码,通过将密码管道输入到sudo -S中,利用subprocess.run并启用 shell 执行,以及通过check=True强制命令成功。 -
在初始化方法内部,我们还设置了
ser实例变量等于serial0,这是我们连接 Pico H 的端口:self.ser = serial.Serial('/dev/serial0', 115200, timeout=1) -
我们的
send_message()方法将命令格式化为位于开括号<和闭括号>之间,并通过串行端口发送消息:def send_message(self, command): if command.strip() == 's' and self.last_command_sent == 's': print("Skip sending 's' command to avoid sending it two times in a row") return framed_command = f"<{command}>\n" print(f"Sending framed command: {framed_command.strip()}") self.ser.write(framed_command.encode self.get_logger().info(f"Sent command: {command.strip()}") self.last_command_sent = command.strip()在我们的代码中,以下操作会发生:
-
我们检查当前命令是否为
's',以及上一个发送的命令是否也是's',以防止连续发送's'。这是因为在没有与物联网摇杆交互时,停止命令是默认命令,因此可能会使通信通道充满冗余信号,从而可能导致系统中的不必要的处理和响应延迟。 -
如果满足前面的条件,我们的代码将跳过发送命令,并记录相关信息。
-
我们随后使用开括号(
<)和闭括号(>)格式化command,然后跟一个换行符。 -
我们记录正在发送的封装命令。然后,我们的代码使用
.encode()将封装命令转换为字节,并通过串行端口发送。 -
我们记录原始命令(去除空白字符)的发送情况。然后,我们的代码将当前命令(去除空白字符)更新到
self.last_command_sent中,以供将来检查。
要构建我们的代码,我们执行
colcon命令:colcon build -
-
在构建我们的节点后,我们使用以下命令对其进行源码设置:
source install/setup.bash -
现在,我们已经准备好运行我们的节点,让我们的机器人通过物联网摇杆进行控制。我们使用以下命令来完成此操作:
ros2 run ares robot_control
小贴士
在我们发送命令之前,建议将 A.R.E.S. 放在测试台上。随着我们的节点运行,我们可以使用我们在 第十二章 中构建的物联网摇杆来控制 A.R.E.S.。
我们刚刚使用 MQTT 和 ROS 通过互联网控制了一个机器人。使用 MQTT 和 ROS 通过互联网控制机器人不仅证明了远程机器人操作的技术可行性,而且突出了远程监控和干预的潜在用途,这些用途在 灾难恢复(DR)、危险环境探索和医疗保健支持等领域至关重要。
摘要
在本章中,我们集成了 MQTT 和 ROS,并创建了 A.R.E.S. 机器人。使用 MQTT,一种轻量级消息协议,使我们能够在机器人和我们的物联网摇杆之间实现高效且可靠的通信。ROS 为我们提供了一个强大的框架,用于开发复杂的机器人应用。通过选择 ROS 构建 A.R.E.S.,我们利用其庞大的工具和库生态系统,确保我们的机器人不仅能够执行高级任务,而且能够适应未来的增强和扩展。
在构建和编程 A.R.E.S. 的过程中,我们很容易想象利用我们的知识来构建更先进的机器人,这些机器人能够执行复杂任务,与人类无缝交互,并能够自主适应各种环境和挑战。
在我们接下来的最后一章中,我们将为 A.R.E.S. 添加视觉识别功能。
第十四章:将计算机视觉添加到 A.R.E.S.
在本章的最后,我们将向 A.R.E.S.添加计算机视觉功能。这将使 A.R.E.S.能够识别物体,并通过短信通知我们这些物体的存在。在我们的例子中,我们将识别狗,尽管我们同样可以设置我们的目标识别代码来识别其他物体。
我们将通过探索计算机视觉及其定义来开始我们的旅程,然后再下载和安装开源计算机视觉(OpenCV)库和你只看一次(YOLO)目标检测系统。在设置好这些工具之后,我们将探索一些实际操作示例。
到本章结束时,你将构建一个智能视频流应用程序,该程序利用 A.R.E.S.摄像头的视频流。
本章我们将涵盖以下主题:
-
探索计算机视觉
-
将计算机视觉添加到 A.R.E.S.
-
发送文本警报
让我们开始吧!
技术要求
完成本章内容你需要以下条件:
-
Python 编程的中级知识
-
A.R.E.S.机器人来自第十三章
-
一台具有 GUI 风格的操作系统计算机,例如 Raspberry Pi 5、macOS 或 Windows
本章的代码可以在以下位置找到:github.com/PacktPublishing/-Internet-of-Things-Programming-Projects-2nd-Edition/tree/main/Chapter14。
探索计算机视觉
计算机视觉始于 20 世纪 50 年代,随着 20 世纪 60 年代图像处理算法的关键里程碑和 20 世纪 90 年代 GPU 的引入而显著发展。这些进步提高了处理速度和复杂计算,并实现了实时图像分析和复杂模型。现代计算机视觉技术是这些发展的结果。以下图表显示了计算机视觉在人工智能中的位置:

图 14.1 – 人工智能
图 14.1 展示了一系列同心圆,代表人工智能不同领域之间的关系。核心是计算机视觉,周围是目标检测,它是深度学习的一个子集,嵌套在机器学习之中。所有这些都包含在更广泛的人工智能领域内。
并非所有计算机视觉技术都涉及机器学习或深度学习,但计算机视觉的一部分——目标检测,通常是这样。
图像识别、目标识别、目标检测和图像分类之间的区别是什么?
在计算机视觉中,诸如 图像识别、目标识别、目标检测 和 图像分类 等术语描述了特定的过程。图像识别检测图像内的特征或模式。目标识别超越了这一点,以识别图像或视频中的特定对象,尽管它不指定它们的精确位置,而是专注于识别 存在什么 对象,而不是 它们在哪里。相反,目标检测不仅识别对象,还从空间上定位它们,通常使用边界框。同时,图像分类涉及分析整个图像,将其分配到特定的类别,例如确定图像是否显示狗、猫或汽车。对于 A.R.E.S.,我们希望有一个视频流,它会在检测到的对象周围创建一个边界框。因此,我们将在应用程序中使用目标检测。
在本章中,我们将 OpenCV 和 YOLO 深度学习算法集成到 A.R.E.S. 中,以便我们可以使用目标检测来检测狗的存在。
我们将通过熟悉 OpenCV 库来开始我们对计算机视觉的探索。
介绍 OpenCV
OpenCV 是计算机视觉领域的基础工具,提供了实时图像处理的大量功能。OpenCV 支持多种应用,从简单的图像变换到复杂的机器学习算法。
OpenCV 不仅允许快速原型设计,还支持跨各种操作系统的全面应用开发,使其成为爱好者和教育工作者以及商业开发者的绝佳选择。
在本节中,我们将探索 OpenCV 的核心功能。我们将从创建一个 Python 虚拟环境和项目文件夹开始。
使用 OpenCV 查看图像
开始使用 OpenCV 可以简单到在窗口中显示一个图像。这个基本练习介绍了 OpenCV 中用于图像加载、处理和窗口操作的关键函数。按照以下步骤操作:
-
首先打开一个终端窗口。我们可以在 Raspberry Pi 5 上使用 Raspberry Pi 操作系统,或者使用我们选择的另一个操作系统。
-
为了存储我们的项目文件,我们必须创建一个新的目录和子目录(用于图像),可以使用以下命令(这里使用的是 Linux 命令):
mkdir -p Chapter14/images -
然后,我们导航到新目录:
images subdirectory, we can run the following command:如果尚未安装 venv 库):
python -m venv ch14-env --system-site-packages -
在创建我们的新 Python 虚拟环境后,我们可以使用以下命令将其激活:
opencv-python library. We can install this library with the following Terminal command:pip install opencv-python
-
我们通过运行以下命令来关闭终端:
exit -
接下来,我们启动 Thonny 并激活我们新创建的 Python 虚拟环境:

图 14.2 – 激活我们的 Python 虚拟环境
-
接下来,我们将创建一个新标签页。我们可以通过选择 文件 然后选择 新建 或者通过在键盘上按 Ctrl + N 来实现。
-
在编辑器中输入以下代码:
import cv2 as cv img = cv.imread('images/Toronto.png') cv.imshow('Downtown Toronto', img) cv.waitKey(0) cv.destroyAllWindows()让我们更仔细地看看这段代码:
-
首先,我们导入
OpenCV库,并给它一个别名cv,以便在代码中更容易引用。 -
我们的代码接着将
Toronto.png图像文件从images文件夹读取到img变量中。 -
接下来,我们创建一个名为
Downtown Toronto的窗口,并在其中显示img。 -
然后,我们的代码在继续执行下一行代码之前无限期地等待一个按键事件。
0值表示它将等待直到按键被按下。 -
最后,我们销毁在会话期间创建的所有窗口,并确保在脚本运行后没有 OpenCV UI 窗口保持打开。这可能会引起内存泄漏。
-
-
我们将代码保存为具有描述性名称的文件,例如在
Chapter14项目文件夹中保存为Toronto.py。 -
我们通过点击绿色运行按钮,按键盘上的F5,或在顶部菜单中选择运行然后运行当前脚本来运行代码。
我们应该看到一个包含多伦多市图像的窗口出现:

图 14.3 – 显示多伦多市区的 OpenCV 窗口弹出窗口(图片:Maximillian Dow)
- 要关闭弹出窗口,我们按键盘上的任意键。
虽然这个练习非常简单,但它为更复杂的计算机视觉项目奠定了基础。在我们转向使用人工智能之前,我们将研究使用 OpenCV 来查看来自 A.R.E.S.的摄像头视频流。
使用 OpenCV 进行视频流处理
在第十三章中,我们使用 VLC 媒体播放器从 A.R.E.S.流式传输视频。为了利用来自 A.R.E.S.的视频,我们可以使用 OpenCV 进行实时图像和视频分析。
要使用 OpenCV 查看我们的视频流,我们必须执行以下操作:
-
我们启动 Thonny 并源码
ch14-envPython 虚拟环境。 -
我们通过选择文件然后新建或在键盘上按Ctrl + N来创建一个新的标签页。
-
我们在编辑器中输入以下代码:
import cv2 stream_url = '<<rtsp address>>' cap = cv2.VideoCapture(stream_url) if not cap.isOpened(): print("Error: Could not open stream") exit() while True: ret, frame = cap.read() if not ret: print("Error: Can't receive frame. Exiting ...") break cv2.imshow('A.R.E.S. Stream', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()让我们更仔细地看看这段代码:
-
我们首先导入 OpenCV 库。
-
然后,我们将 RTSP URL 定义为字符串,作为视频流源。
-
我们的代码创建了一个
VideoCapture对象,尝试从指定的 RTSP URL 打开视频流。如果无法打开流,将打印错误信息。 -
然后,我们启动一个无限循环,以连续从流中获取帧。
-
之后,我们尝试从流中读取下一帧。
-
如果无法接收到帧,我们打印错误信息并退出循环。
-
我们在标题为
A.R.E.S. Stream的窗口中显示当前帧。 -
然后,如果用户在键盘上按
q,我们允许用户手动关闭流窗口。 -
接着,释放视频捕获对象,释放资源并关闭视频文件或捕获设备。
-
最后,我们关闭所有 OpenCV 窗口,清理与窗口显示相关的任何剩余资源。
-
-
我们将代码保存为具有描述性的名称,例如
video-feed.py,在我们的Chapter14项目文件夹中。 -
我们通过点击绿色运行按钮,在键盘上按F5,或者在顶部菜单中选择运行,然后选择运行****当前脚本来运行代码。
-
我们应该会看到一个窗口出现,显示 A.R.E.S 的摄像头视频流:

图 14.4 – A.R.E.S 摄像头视频流
- 要关闭弹出窗口,我们在键盘上按
q键。
现在我们已经使用 OpenCV 查看图像和视频积累了一些经验,让我们更进一步,让它识别物体,特别是狗,在我们继续计算机视觉之旅的过程中。
我们将首先探讨神经网络及其在识别物体中的应用。
理解 YOLO 和神经网络
在本节中,我们将重点关注 YOLO 和神经网络的不同层次,以便我们可以构建能够识别狗的物体检测代码。将注意力转向图 14**.1,我们可以看到物体检测是计算机视觉的一部分,其中使用了深度学习和机器学习技术。
机器学习与深度学习
机器学习是人工智能的一个子集,其中算法使用统计方法使机器能够通过经验改进,通常需要手动选择特征。相比之下,深度学习是机器学习的一个专门子集,它使用神经网络,这些神经网络可以从大量数据中自动提取和学习特征。这对于图像和语音识别等复杂任务来说非常理想。虽然机器学习使用较少的数据并提供更多的模型透明度,但深度学习需要大量的数据和计算能力,通常作为一个黑盒运行,可解释性较低。
为了表示深度学习,YOLO 使用一个复杂的神经网络,它可以在一次扫描中评估图像。
探索物体检测
如前所述,物体检测是在图像或视频流中寻找物体的过程。以下图示展示了物体检测算法的连续阶段,以狗的图像为例:

图 14.5 – 计算机视觉中物体检测的阶段
首先,我们有原始图像作为输入。我们通过将其分解为输入像素,然后识别边缘、角和轮廓来进行结构解释。我们的算法接着识别单个物体部分,并在这些组件周围放置边界框。这导致了最终的输出,它突出了图像中检测到的物体。
现在我们已经了解了使用神经网络进行物体检测的工作原理,让我们考虑一个例子。在下一小节中,我们将使用 YOLO 在图片中识别一只狗。
使用 YOLO 在图片中识别狗
在本节中,我们将使用 OpenCV 和 YOLO 深度学习算法编写一个程序,用于在图片中检测狗。
要开始,我们需要下载 YOLO 配置文件、预训练权重和 coco.names 文件,该文件包含模型识别的类别列表。这些文件通常可在官方 YOLO 网站或专注于 YOLO 的信誉良好的 GitHub 仓库中找到。配置文件(yolov4.cfg)概述了网络架构,权重文件(yolov4.weights)包含训练好的模型参数,类别名称文件列出了 YOLO 模型可以检测的对象类别,所有这些对于当前的目标检测任务至关重要。
为了使这个过程更简单,我们已经在本章的 GitHub 仓库中包含了您进行此练习所需的所有文件。
yolo4.weights 文件是什么?
yolov4.weights 文件包含 YOLOv4 目标检测模型的预训练权重,使其能够准确地在图像和视频中检测和定位对象。由于此文件太大,无法包含在本章的 GitHub 仓库中,您需要从官方 YOLO 网站或 GitHub 仓库(github.com/AlexeyAB/darknet/releases)下载。
要创建我们的目标检测代码,请按照以下步骤操作:
-
我们首先打开一个终端窗口。我们可以在 Raspberry Pi 5 上使用 Raspberry Pi 操作系统,或者使用我们选择的另一个操作系统。
-
我们使用以下命令导航到
Chapter14项目目录:cd Chapter14 -
要存储我们的 YOLO 文件,使用以下命令创建一个新的目录:
coco.names, yolov4.cfg, and yolov4.weights files from the YOLO directory of this chapter’s GitHub repository to our local YOLO directory using whichever method suits us. -
对于我们的测试图像,我们将
dog.png文件从本章 GitHub 仓库的图像目录复制到我们的项目文件夹的图像目录。 -
我们启动 Thonny 并源码导入
ch14-envPython 虚拟环境。 -
我们可以通过选择 文件 然后选择 新建 或者在键盘上按 Ctrl + N 来创建一个新的标签页。
-
在编辑器中,我们添加代码所需的必要导入。在这里,我们需要 OpenCV 作为我们的库,以及 NumPy 用于其数学函数:
import cv2 import numpy as np -
现在,我们通过运行以下代码行来加载 YOLO 算法:
net = cv2.dnn.readNet("YOLO/yolov4.weights", "YOLO/yolov4.cfg") classes = [] layer_names = net.getLayerNames()让我们仔细看看这段代码:
-
首先,我们通过加载预训练权重(
yolov4.weights)和配置(yolov4.cfg)来初始化 YOLO 网络。这创建了一个准备进行目标检测的神经网络。 -
然后,我们创建一个空列表,用于存储 YOLO 可以检测的类别名称(例如,狗和猫),一旦从文件中读取,它们就会被存储在这个列表中。
-
然后,我们的代码检索 YOLO 网络中所有层的名称。这些层名称用于识别输出层。这对于获取检测结果至关重要。
-
-
我们输入以下代码来获取 YOLO 神经网络中最终层的索引。这些层直接使用 OpenCV 的
getUnconnectedOutLayers()方法输出检测结果:output_layer_indices = net.getUnconnectedOutLayers() -
接下来,通过使用
output_layer_indices提供的索引并调整为零基索引,从所有层名称列表中索引以创建 YOLO 神经网络的输出层名称列表。这对应于算法中的边界框阶段,如图 145*所示:output_layers = [layer_names[i - 1] for i in output_layer_indices] -
接下来,我们读取
coco.names文件,该文件包含 YOLO 模型可以识别的对象类别列表,并创建一个类别名称列表,通过从每一行中删除任何前导或尾随空白来创建。以下代码找到并存储"dog"类别在该列表中的索引,有效地使程序准备好在 YOLO 模型处理的图像中特定识别和识别狗:with open("YOLO/coco.names", "r") as f: classes = [line.strip() for line in f.readlines()] dog_class_id = classes.index("dog") -
以下代码从
images目录中读取dog.png图像,将其缩小到原始大小的 40%以减少计算负载,并提取其尺寸和颜色通道数。调整大小步骤至关重要,因为 YOLO 模型通常期望固定大小的输入,而调整大小有助于满足这一要求,同时由于图像尺寸较小,还可以加速检测过程:img = cv2.imread('images/dog.png') img = cv2.resize(img, None, fx=0.4, fy=0.4) height, width, channels = img.shape -
接下来,我们必须将调整大小的图像转换为blob——一个与神经网络兼容的预处理图像——通过归一化像素值并将大小设置为 416x416 像素,这是 YOLO 模型的标准输入大小。然后,我们必须将此 blob 设置为神经网络的输入。最后,我们必须使用指定的输出层通过网络执行正向传递以获得检测预测。这包括类别标签、置信度和边界框坐标。以下代码片段对应于图 145中显示的输入像素和边界框阶段之间的动作。它通过各种层处理图像以检测对象,代码片段中的最后一行产生导致输出*阶段的检测:
blob = cv2.dnn.blobFromImage(img, 0.00392, (416, 416), (0, 0, 0), True, crop=False) net.setInput(blob) outs = net.forward(output_layers) -
以下代码分析神经网络正向传递的结果,对检测到的
dog类对象进行过滤和处理,置信度高于 50%。它根据对象的中心、宽度和高度计算边界框坐标,然后将这些坐标以及检测置信度和类别 ID 存储在相应的列表中。这与图 145中显示的边界框阶段相一致,其中处理后的输出用于在图像中特定定位和分类检测到的对象。这为输出*阶段的最终视觉表示奠定了基础,在该阶段,这些边界框被绘制出来以指示图像中狗的位置:class_ids = [] confidences = [] boxes = [] for out in outs: for detect in out: scores = detect[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.5 and class_id == dog_class_id: center_x = int(detection[0] * width) center_y = int(detection[1] * height) w = int(detection[2] * width) h = int(detection[3] * height) x = int(center_x - w / 2) y = int(center_y - h / 2) boxes.append([x, y, w, h]) confidences.append(float(confidence)) class_ids.append(class_id) -
以下代码使用 OpenCV 的
NMSBoxes函数通过减少边界框之间的重叠来细化检测结果,确保每个检测到的对象只被表示一次。在根据它们的置信度和重叠情况确定最佳边界框之后,它遍历这些优化后的边界框以可视标注图像。这是通过为每个边界框绘制一个矩形并标注相应的类别名称来实现的。这一步标记并识别图像中检测到的对象(狗),与图 14.5中所示的输出阶段相一致:indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4) for i in indexes.flatten(): x, y, w, h = boxes[i] label = str(classes[class_ids[i]]) cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(img, label, (x, y + 30), cv2.FONT_HERSHEY_PLAIN, 3, (0, 255, 0), 3) -
在我们代码的最后一部分,我们显示带有边界框标记的已处理图像。
cv2.imshow("Image", img)函数在标题为"Image"的窗口中显示图像。cv2.waitKey(0)函数暂停脚本的执行,无限期地等待按键以继续,使用户能够查看图像所需的时间。最后,cv2.destroyAllWindows()关闭由脚本打开的所有 OpenCV 窗口,确保干净退出,不留下任何 GUI 窗口打开:cv2.imshow("Image", img) cv2.waitKey(0) cv2.destroyAllWindows() -
我们将代码保存为具有描述性的名称,例如
recognize-dog.py,在我们的Chapter14项目文件夹中。 -
我们通过点击绿色运行按钮,按键盘上的F5,或者在顶部菜单中选择运行,然后选择运行 当前脚本来运行代码。
-
我们应该会看到一个弹出窗口出现,窗口中有一个围绕狗脸的边界框:

图 14.6 – 用于识别狗的 YOLO 库
- 我们按键盘上的任意键来关闭弹出窗口。
如我们所见,我们的程序可以从图片中识别出狗。如果我们提供coco.names文件中识别到的任何对象的图片(例如,一个人),我们的程序应该能够识别出那个对象。
现在我们对 YOLO、神经网络和目标检测有了一定的了解,让我们将这个功能添加到 A.R.E.S 中。我们将编程我们的应用程序,以便每当 A.R.E.S 检测到狗时发送一条文本消息。
将计算机视觉添加到 A.R.E.S 中。
在上一节中,我们探讨了 OpenCV 和 YOLO,使用 OpenCV 查看图像和视频流,使用 YOLO 在图片中识别狗。在本节中,我们将应用我们所学到的知识来创建一个智能视频流应用程序,代表 A.R.E.S 的眼睛。我们将仅以狗为例,但我们可以轻松地将此应用程序改编为跟踪其他对象。
在创建视频流应用程序之前,我们将首先将我们的 YOLO 代码封装到一个名为DogTracker的类中。
创建 DogTracker 类
DogTracker类体现了 A.R.E.S 的人工智能组件。尽管它可以直接安装在 A.R.E.S 的 Raspberry Pi 3B+上并通过流窗口应用程序远程访问,但为了简单和性能提升,我们将将其安装在包含流式应用程序的计算机上。在我们的示例中,我们将使用 Windows PC。
要创建DogTracker类,我们必须执行以下步骤:
-
我们启动 Thonny 并激活我们的
ch14-envPython 虚拟环境。 -
通过选择文件然后新建或在键盘上按Ctrl + N创建一个新标签页。
-
接下来,我们添加必要的导入:
import cv2 import numpy as np -
然后,我们定义我们的类和初始化方法:
class DogDetector: def __init__(self, model_weights, model_cfg, class_file): self.net = cv2.dnn.readNet(model_weights, model_cfg) self.layer_names = self.net.getLayerNames() output_layer_indices = self.net.getUnconnectedOutLayers() if output_layer_indices.ndim == 1: self.output_layers = [self.layer_names[ i - 1] for i in output_layer_indices] else: self.output_layers = [self.layer_names[ i[0] - 1] for i in output_layer_indices] with open(class_file, "r") as f: self.classes = [line.strip() for line in f.readlines()] self.dog_class_id = self.classes.index("dog") -
现在,我们必须定义我们的唯一方法:
detect_dogs()。此方法通过 YOLO 神经网络模型处理视频帧以检测狗。它首先将输入帧调整大小以进行最佳处理,然后从调整大小的图像中创建一个 blob,该 blob 随后被输入到神经网络中。网络输出检测结果,包括检测对象的边界框、置信度和类别 ID。该方法检查每个检测是否满足置信度阈值并对应于狗的类别 ID。如果找到此类检测,它将计算并存储它们的边界框坐标。然后应用 NMS 以通过减少重叠来细化这些边界框。如果在此过程之后仍有任何框,它将确认狗的存在,在帧上绘制这些框,并对其进行标记。最后,该方法返回处理后的帧,以及一个布尔值,指示是否检测到狗:def detect_dogs(self, frame): img_resized = cv2.resize(frame, None, fx=0.4, fy=0.4) height, width, channels = img_resized.shape dog_detected = False blob = cv2.dnn.blobFromImage(img_resized, 0.00392, (416, 416), (0, 0, 0), True, crop=False) self.net.setInput(blob) outs = self.net.forward(self.output_layers) class_ids = [] confidences = [] boxes = [] for out in outs: for detection in out: scores = detection[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.5 and class_id == self.dog_class_id: center_x = int(detection[0] * width) center_y = int(detection[1] * height) w = int(detection[2] * width) h = int(detection[3] * height) x = int(center_x - w / 2) y = int(center_y - h / 2) boxes.append([x, y, w, h]) confidences.append(float(confidence)) class_ids.append(class_id) indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4) if indexes is not None and len(indexes) > 0: dog_detected = True indexes = indexes.flatten() for i in indexes: x, y, w, h = boxes[i] label = str(self.classes[class_ids[i]]) cv2.rectangle(img_resized, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(img_resized, label, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) return img_resized, dog_detected -
我们将代码保存为具有描述性的名称,例如
DogDetector.py,在我们的Chapter14项目文件夹中。
在这里,我们将上一节中的recognize-dog.py代码重新组织成一个类,我们将使用该类来创建我们的智能视频流器。有了这个类,现在是时候创建我们的流式应用程序了。我们将使用 OpenCV 来完成这项工作。
构建智能视频流器
我们将在DogDetector类内部使用detect_dogs()方法来从来自 A.R.E.S 的视频流中识别狗。如前所述,我们可以轻松地更改我们的代码,以便可以使用 YOLO 来识别其他对象。识别狗为我们有狗的朋友们提供了一个有趣的方式来编程 A.R.E.S 作为一个宠物检测机器人。我们将把我们的智能视频流器安装在与DogDetector类相同的计算机上。
要创建智能视频流器,请按照以下步骤操作:
-
我们启动 Thonny 并激活我们的
ch14-envPython 虚拟环境。 -
通过选择文件然后新建或在键盘上按Ctrl + N创建一个新标签页。
-
我们首先添加我们的导入:
import cv2 from DogDetector import DogDetector import time -
然后,我们定义我们的变量声明:
detector = DogDetector("YOLO/yolov4.weights", "YOLO/yolov4.cfg", "YOLO/coco.names") stream_url = '<<rtsp address>>' cap = cv2.VideoCapture(stream_url) cv2.namedWindow("Dog Detector", cv2.WINDOW_NORMAL) cv2.resizeWindow("Dog Detector", 800, 600) last_time = time.time() -
我们的大部分代码都位于一个无限循环中。此代码持续从视频源捕获帧,检查是否成功检索到每一帧。如果自上次处理的帧以来已过去一秒钟,它将使用
detect_dogs()方法在当前帧中检测到狗,更新时间标记,并显示结果;如果按下q键,循环中断,视频捕获以及任何 OpenCV 窗口将被干净地释放和关闭:try: while True: ret, frame = cap.read() if not ret: break current_time = time.time() if current_time - last_time >= 1.0: # 1.0 seconds result_frame, dog_detected = detector.detect_dogs(frame) last_time = current_time cv2.imshow("Dog Detector", result_frame) if dog_detected: print('Dog detected!') if cv2.waitKey(1) & 0xFF == ord('q'): break finally: cap.release() cv2.destroyAllWindows() -
我们将代码保存为具有描述性的名称,例如
smart-video-feed.py,在我们的Chapter14项目文件夹中。 -
我们通过点击绿色运行按钮,在键盘上按 F5,或者在顶部菜单中选择 运行 选项,然后选择 运行 当前脚本 来运行代码。
我们应该会看到一个弹出窗口出现,显示来自 A.R.E.S. 的视频流。我们的应用程序应该能够检测到狗的存在:

图 14.7 – 使用我们的智能视频流器检测狗
通过这种方式,我们已经成功地将人工智能以对象检测的形式添加到 A.R.E.S.。我们可以在 DogDetector 类中调整帧的大小、字体和颜色。尽管让对象检测在 A.R.E.S. 上工作令人印象深刻,但我们将进一步推进,引入文本通知,将智能视频流功能转变为真正的物联网应用程序。
发送文本警报
为了使 A.R.E.S. 成为一个真正的物联网设备,我们将添加文本功能。这将使 A.R.E.S. 能够在检测到感兴趣的对象时(在我们的案例中是狗)发送文本警报。我们将使用 Twilio 服务来完成这项工作。
在我们将文本消息功能集成到 A.R.E.S. 之前,我们将首先设置我们的 Twilio 账户并测试分配给我们的号码。我们必须确保我们仔细遵循即将到来的步骤,以便我们能够成功设置我们的 Twilio 账户。
设置我们的 Twilio 账户
设置 Twilio 账户涉及在他们网站上注册,在那里我们将获得一个账户 SID 和一个认证令牌,用于验证 API 请求。一旦注册,我们还可以获取一个 Twilio 电话号码,这对于通过他们的服务发送短信和打电话是必要的。在本节中,我们将设置我们的 Twilio 账户并发送一条测试短信。
要设置我们的 Twilio 账户,请按照以下步骤操作:
- 使用网络浏览器,我们导航到 www.twilio.com 并点击蓝色 免费开始 按钮:

图 14.8 – Twilio 网站
- 这将带我们到 注册 页面。在这里,我们可以创建一个 Twilio 账户或使用 Google 账户:

图 14.9 – Twilio 的注册页面
- 为了验证我们的新账户,我们输入一个电话号码并点击蓝色 通过 SMS 发送代码 按钮:

图 14.10 – 验证页面
- 我们应该收到包含验证码的短信。我们输入号码并点击蓝色 验证 按钮:

图 14.11 – 验证码步骤
- 这将带我们到 您已全部验证! 页面,该页面将为我们提供 恢复码 值。我们点击蓝色 继续 按钮进入下一页:

图 14.12 – 恢复码
- 下一页允许我们自定义 Twilio 体验:

图 14.13 – 自定义 Twilio
-
仪表板屏幕允许我们获取 Twilio 电话号码。我们点击蓝色 获取电话号码 按钮继续。
-
下一页提供了我们可以使用的 Twilio 电话号码。我们点击蓝色 下一步 按钮继续。
-
在下一屏幕上,我们可以测试我们的新 Twilio 号码。点击蓝色 发送测试 短信 按钮:

图 14.14 – 测试我们的新 Twilio 电话号码
- 我们应该在提供给 Twilio 的手机上收到包含测试消息 IoT 测试 的短信:

图 14.15 – 测试消息成功接收,如手机上所见
在设置好 Twilio 账户并测试了电话号码后,我们就可以将短信功能集成到 A.R.E.S. 中了。
将文本消息功能添加到 A.R.E.S.
要将文本消息功能集成到 A.R.E.S. 中,我们将开发一个名为 TwilioMessage 的新类和一个新的 smart-video-feed.py 脚本版本:

图 14.16 – 将文本功能添加到 A.R.E.S.
TwilioMessage 类将封装与 Twilio 服务器的通信。如图 图 14**.16 所示,我们的新 TwilioMessage 类从我们的智能视频流器调用并发送文本消息。
我们将首先创建这个类。
创建 TwilioMessage 类
要创建 TwilioMessage 类,我们必须执行以下操作:
-
我们启动 Thonny 并源我们的
ch14-envPython 虚拟环境。 -
由于我们需要从 Twilio 获取库以使我们的代码工作,我们将在 Python 虚拟环境中安装它。为此,在 Thonny 中通过点击 工具 | 打开系统 shell… 来打开系统壳:

图 14.17 – 打开系统壳…
-
在命令提示符下,我们执行以下命令:
pip install twilio -
一旦安装了库,我们就关闭终端。
-
我们通过选择 文件 然后选择 新建 或按键盘上的 Ctrl + N 创建一个新标签页。
-
我们将以下代码添加到编辑器中:
from twilio.rest import Client class TwilioMessage: def __init__(self, account_sid, auth_token, from_number): self.client = Client(account_sid, auth_token) self.from_number = from_number def send_sms(self, to_number, message): sms = self.client.messages.create( body=message, from_=self.from_number, to=to_number ) print(f"Message sent with SID: {sms.sid}") if __name__ == "__main__": twilio_message = TwilioMessage( ' account_sid', ' auth_token', '+twilio_number') twilio_message.send_sms('+our_number', 'Hello from A.R.E.S.')让我们仔细看看我们的代码:
-
首先,我们导入 Twilio 客户端并定义
TwilioMessage类。 -
然后,我们使用 Twilio 凭证(账户 SID、认证令牌和 Twilio 电话号码)初始化我们的类。
-
send_sms()方法向指定的号码发送短信,并在发送后打印消息 SID。 -
在主执行块中,创建了一个带有 Twilio 凭证的
TwilioMessage实例,并发送了一条测试短信到我们的号码。
-
-
我们将代码保存为具有描述性的名称,例如
TwilioMessage.py,在我们的Chapter14项目文件夹中。 -
我们通过点击绿色运行按钮、在键盘上按F5键或点击顶部的运行菜单选项然后运行****当前脚本来运行代码。
-
我们应该在手机上收到一条短信,内容为
Hello from A.R.E.S.。
在创建了TwilioMessage类之后,是时候修改智能视频流代码,以便在检测到狗或狗时发送短信。我们将在下一节中这样做。
修改智能视频流
在 A.R.E.S.中提供短信功能的最后一步是创建一个新的智能视频流脚本,当检测到狗时发送短信。为了限制发送的消息数量,我们将帧之间的时间间隔增加到 5 秒。
要修改我们的智能视频流,请按照以下步骤操作:
-
在 Thonny 中,我们通过选择文件然后新建或按键盘上的Ctrl + N键来创建一个新的标签页。
-
我们将以下代码添加到编辑器中:
import cv2 from DogDetector import DogDetector from TwilioMessage import TwilioMessage import time detector = DogDetector("YOLO/yolov4.weights", "YOLO/yolov4.cfg", "YOLO/coco.names") twilio_message = TwilioMessage('account_sid', 'auth_token', '+twilio_number') stream_url = '<<rtsp address>>' cap = cv2.VideoCapture(stream_url) cv2.namedWindow("Dog Detector", cv2.WINDOW_NORMAL) cv2.resizeWindow("Dog Detector", 800, 600) last_time = time.time() try: while True: ret, frame = cap.read() if not ret: break # Check if 1 second has passed current_time = time.time() if current_time - last_time >= 5.0: result_frame, dog_detected = detector.detect_dogs(frame) last_time = current_time cv2.imshow("Dog Detector", result_frame) if dog_detected: twilio_message.send_sms(' +phone_num', 'Dog(s) detected!') if cv2.waitKey(1) & 0xFF == ord('q'): break finally: cap.release() cv2.destroyAllWindows() -
我们将代码保存为具有描述性的名称,例如
smart-video-sms.py,在我们的Chapter14项目文件夹中。 -
我们通过点击绿色运行按钮、在键盘上按F5键或点击顶部的运行菜单选项然后运行****当前脚本来运行代码。
-
我们应该看到一个窗口出现,其中包含来自 A.R.E.S.的视频流,当有狗在场时提供对象检测功能。
-
当检测到狗时,我们应该收到一条短信:

图 14.18 – 表示检测到狗或狗的消息的文本消息
通过这样,我们已经成功地为 A.R.E.S.添加了短信功能,以便在检测到我们感兴趣的对象时(在这种情况下,是一只狗)发出警报。
摘要
在本章中,我们探讨了计算机视觉领域,并将其成功集成到我们的 A.R.E.S.机器人汽车中。通过采用这项技术,A.R.E.S.现在可以处理和解释视觉数据。我们还为 A.R.E.S.添加了短信功能,使我们的机器人汽车成为真正的物联网设备。
虽然尚未实现,但我们可以很容易地想象如何通过结合 YOLO 返回的数据将 A.R.E.S.提升到下一个水平,基于障碍物避免。我们也可以想象如果需要,A.R.E.S.如何跟随某个特定对象。
本章标志着我们共同物联网旅程的结束。在此过程中,我们探索了物联网的世界,同时利用并提升了我们的编程技能——从使用 Sense HAT 提供网络服务数据,到利用 LoRa 进行长距离通信,再到在我们的互联网控制下实现机器人汽车中的高级功能,如计算机视觉。这次冒险不仅教会了我们技术知识,还展示了物联网在创新和解决未来任何现实世界挑战中的潜力。
很高兴。


.
浙公网安备 33010602011771号